feat(plugins): add chat-input-actions slot for plugin toolbar icons (#1794)

* feat(plugins): add chat-input-actions slot for plugin toolbar icons

Let plugins render icon buttons into the chat composer toolbar, beside
the model picker, mic, and send button, via the existing named-slot
mechanism (registerComponent). The host mounts the slot in both the
desktop and mobile toolbars and forwards { taskId, taskTitle,
activeSessionId, sessionIds } as slotProps — the active session plus
every kandev session id on the task. Resolving a session id to an
agent/ACP transcript id is left to the plugin (server-side, via the Host
data API); the host only propagates ids.

Docs (PLUGIN-API.md, public plugins/plugins-authoring) document the new
slot and its props.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(plugins): read store optionally in chat-input-actions slot

ChatInputPluginActions read the app store via useAppStore, which throws
outside a StateProvider — breaking existing toolbar tests that render the
composer in isolation. Switch to useOptionalAppStore (fallback to no
sessions) so the slot never hard-requires a provider.

Also: gate the slot behind hideAgentControls to match the other
session-specific toolbar controls, drop a redundant String() coercion,
tidy a dangling docs reference, and cover the taskId=null path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
João Salavisa
2026-07-19 12:26:50 +01:00
committed by GitHub
parent 1dc96a92c6
commit 4b1320ffb0
8 changed files with 267 additions and 5 deletions
+12 -1
View File
@@ -123,7 +123,13 @@ interface PluginRegistry {
// Named slot injection. Host renders all components registered for a slot via
// <PluginSlot name="..." props={...}/>. Initial slots: "task-sidebar",
// "settings-nav", "main-nav-footer".
// "settings-nav", "main-nav-footer", "chat-input-actions". The last renders
// icon buttons in the chat composer toolbar (beside the model picker, mic,
// and send) and forwards `{ taskId, taskTitle, activeSessionId, sessionIds }`
// as `slotProps` — the active session plus every kandev session id on the
// task. Resolving a session id to an agent/ACP transcript id (e.g. to key
// tokscale cost data on a session) is the plugin's job, done server-side in
// the plugin backend via the Host data API; the host only propagates ids.
registerComponent(slot: string, Component: React.ComponentType<{ slotProps?: unknown }>): void;
// WS action handler. Bridged into the existing lib/ws dispatch; called with the
@@ -161,6 +167,11 @@ defaults, or the bare component when the route opted out (`topbar: false`).
message to any `registry.getWsHandlers(action)`.
- `components/plugins/plugin-slot.tsx`: `<PluginSlot name props/>` renders all
slot components; drop into task detail sidebar + settings nav as initial hosts.
The chat composer toolbar
(`components/task/chat/chat-input-toolbar-desktop.tsx` and
`-mobile.tsx`, via `chat-input-plugin-actions.tsx`) hosts the
`chat-input-actions` slot, passing
`{ taskId, taskTitle, activeSessionId, sessionIds }`.
## Security posture (documented, enforced where cheap)