web+coder: segmented per-agent slash menu (agent commands + skills) + cross-agent skill execution

Coder / menu now shows two groups: the active agent's commands first (manifest + live ACP available_commands), BooCoder skills second. SlashCommandPicker gains an opt-in groups prop (flat items path unchanged -> BooChat byte-identical, parity verified); ChatInput takes slashGroups; CoderPane builds the groups. Skills run under the selected agent: coder skill_invoke accepts a provider and, when external, injects the server-side skill body into a dispatched task instead of native inference. Also folds in the initial-chat skill fix (handleLandingSkill: create chat -> assign to pane -> invoke, same transition as a text send) that resolves the landing-page blank screen. BooChat slash menu + skill invocation unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-29 14:38:39 +00:00
parent 8bf86ecb92
commit 23a33e893a
9 changed files with 232 additions and 62 deletions

View File

@@ -24,6 +24,7 @@ export interface UseSessionChatsResult {
deleteChat: (chatId: string) => Promise<void>;
renameChat: (chatId: string, name: string) => Promise<void>;
handleLandingSend: (paneIdx: number, content: string) => Promise<void>;
handleLandingSkill: (paneIdx: number, skillName: string, userMessage: string | null) => Promise<void>;
}
export function useSessionChats(
@@ -166,6 +167,25 @@ export function useSessionChats(
}
}, [sessionId]);
// Slash-command equivalent of handleLandingSend: the initial (landing) chat
// must create the chat AND assign it to the pane (openChatInPane) before
// invoking the skill, so the pane transitions to ChatPane and subscribes to
// the chat's stream. Skipping the assignment left the pane stuck on the
// landing page while the skill ran invisibly (and could blank the pane).
const handleLandingSkill = useCallback(
async (paneIdx: number, skillName: string, userMessage: string | null) => {
try {
const chat = await api.chats.create(sessionId);
setChats((prev) => (prev.some((c) => c.id === chat.id) ? prev : [chat, ...prev]));
openChatInPaneRef.current(paneIdx, chat.id);
await api.chats.skillInvoke(chat.id, skillName, userMessage);
} catch (err) {
toast.error(err instanceof Error ? err.message : `/${skillName} failed`);
}
},
[sessionId],
);
return {
chats,
setChats,
@@ -175,5 +195,6 @@ export function useSessionChats(
deleteChat,
renameChat,
handleLandingSend,
handleLandingSkill,
};
}