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:
@@ -510,6 +510,31 @@ export function CoderPane({
|
||||
[displayedCommands],
|
||||
);
|
||||
|
||||
// v2.5.9: segmented slash menu — the active agent's commands first, then
|
||||
// BooCoder skills. boocode has no separate "commands" group (it IS native),
|
||||
// so it shows only Skills. Empty groups are dropped.
|
||||
const agentCommands = useMemo(
|
||||
() =>
|
||||
agentConfig.provider === 'boocode'
|
||||
? []
|
||||
: mergeCommandsByName(providerCommands, liveTaskCommands),
|
||||
[agentConfig.provider, providerCommands, liveTaskCommands],
|
||||
);
|
||||
const skillItems = useMemo(
|
||||
() => skills.map((s) => ({ name: s.name, description: s.description })),
|
||||
[skills],
|
||||
);
|
||||
const slashGroups = useMemo(() => {
|
||||
const groups: Array<{ label: string; items: Array<{ name: string; description?: string }> }> = [];
|
||||
if (agentCommands.length > 0) {
|
||||
groups.push({ label: `${agentConfig.provider} commands`, items: agentCommands });
|
||||
}
|
||||
if (skillItems.length > 0) {
|
||||
groups.push({ label: 'Skills', items: skillItems });
|
||||
}
|
||||
return groups;
|
||||
}, [agentCommands, skillItems, agentConfig.provider]);
|
||||
|
||||
const { messages, setMessages, connected, loadMessages } = useCoderMessages(sessionId, chatId, {
|
||||
onConnectedChange,
|
||||
onPermissionRequested: (prompt) => {
|
||||
@@ -736,19 +761,35 @@ export function CoderPane({
|
||||
|
||||
const handleChatInputSlash = useCallback(async (skillName: string, userMessage: string) => {
|
||||
if (!chatId) return;
|
||||
if (agentConfig.provider === 'boocode' && skillsByName.has(skillName)) {
|
||||
setSending(true);
|
||||
setPermissionPrompt(null);
|
||||
setLiveTaskCommands([]);
|
||||
try {
|
||||
await api.coder.skillInvoke(sessionId, paneId, skillName, userMessage.length > 0 ? userMessage : null);
|
||||
} catch (err) {
|
||||
toast.error(err instanceof Error ? err.message : 'skill invocation failed');
|
||||
} finally {
|
||||
setSending(false);
|
||||
}
|
||||
// Only BooCoder skills route here; an agent's own commands (not skills) fall
|
||||
// through to a literal send in ChatInput. Skills run under the active
|
||||
// provider: boocode → native inference; external → body injected into a task.
|
||||
if (!skillsByName.has(skillName)) return;
|
||||
setSending(true);
|
||||
setPermissionPrompt(null);
|
||||
setLiveTaskCommands([]);
|
||||
try {
|
||||
const data = await api.coder.skillInvoke(
|
||||
sessionId,
|
||||
paneId,
|
||||
skillName,
|
||||
userMessage.length > 0 ? userMessage : null,
|
||||
agentConfig.provider !== 'boocode'
|
||||
? {
|
||||
provider: agentConfig.provider,
|
||||
model: agentConfig.model || undefined,
|
||||
mode_id: agentConfig.modeId ?? undefined,
|
||||
thinking_option_id: agentConfig.thinkingOptionId ?? undefined,
|
||||
}
|
||||
: undefined,
|
||||
);
|
||||
if (data.task_id) setActiveTaskId(data.task_id);
|
||||
} catch (err) {
|
||||
toast.error(err instanceof Error ? err.message : 'skill invocation failed');
|
||||
} finally {
|
||||
setSending(false);
|
||||
}
|
||||
}, [chatId, sessionId, paneId, agentConfig.provider, skillsByName]);
|
||||
}, [chatId, sessionId, paneId, agentConfig, skillsByName]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full bg-background">
|
||||
@@ -810,6 +851,7 @@ export function CoderPane({
|
||||
projectId={projectPath ?? ''}
|
||||
onSend={handleChatInputSend}
|
||||
onSlashCommand={handleChatInputSlash}
|
||||
slashGroups={slashGroups}
|
||||
chatId={chatId ?? undefined}
|
||||
chatLabel="BooCode"
|
||||
messages={messages as unknown as import('@/api/types').Message[]}
|
||||
|
||||
Reference in New Issue
Block a user