You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Sibling to #62 and #63. The third leg of the self-hosting story.
#62 gives Claude a headless browser. #63 gives the user a preview pane. This issue gives Claude a structured map of the Workstation UI itself, so when Claude is running inside Klonode and a user asks "open the GraphView and select the auth folder," Claude doesn't have to take a screenshot and guess pixel coordinates — it queries a real component registry and calls a real action tool.
What
A self-introspection layer the Workstation publishes about itself, plus the tools to query and act on it:
1. Component registry
Every interactive Svelte component in packages/ui/src/lib/components/ gets:
A stable component ID (e.g. chat-panel, tree-view, graph-view, github-view, layer-inspector, editor-context, editor-toolbar)
A role description — one sentence on what the component does
A list of child components / slots it contains
A list of actions it exposes (e.g. chat-panel.send-message, tree-view.expand-node, graph-view.select-node, editor-context.start-edit)
A list of observable state keys (e.g. chat-panel.current-session-id, tree-view.expanded-paths, graph-view.selected-node-id)
The registry lives in code, not docs — generated from a defineComponent({...}) helper that each component calls once at module load. That guarantees the registry stays in sync with the actual UI.
2. State snapshot tool: klonode_workstation_snapshot
Returns a structured JSON snapshot of the current Workstation state:
Each registered action validates its args via the registry and routes to the actual Svelte store update or method call.
4. Discovery tool: klonode_workstation_describe
Returns the full registry of components, their actions, and their observable state — so Claude can ask "what can I do in this UI?" without prior knowledge. This is what read_page is for an HTML page, but for Klonode's own component tree.
Why this matters
Stable contract. Pixel coordinates change every time we ship a UI tweak. A component ID and an action name are stable across redesigns.
Cheaper than screenshots. A snapshot is a few hundred bytes vs hundreds of KB for a screenshot, and Claude doesn't burn vision tokens parsing it.
Actually reliable. Claude doesn't have to guess whether a click landed on the right button — the action either succeeds or returns a typed error.
// packages/ui/src/lib/components/TreeView/TreeView.svelte<scriptlang="ts">import{ defineComponent }from'$lib/workstation/registry';defineComponent({id: 'tree-view',role: 'Sidebar tree of the project file structure with expand/collapse and selection',parent: 'workstation-layout',actions: {'expand-node': {args: {path: 'string'}},'collapse-node': {args: {path: 'string'}},'select-node': {args: {node_id: 'string'}},'reveal-path': {args: {path: 'string'}},},state: ['expanded-paths','selected-node-id'],});</script>
Registry storage
A single Svelte store under packages/ui/src/lib/workstation/registry.ts collects every defineComponent() call at boot. The store is the source of truth that the snapshot, action, and describe tools all query.
Tool wiring
The three tools (snapshot, action, describe) are exposed as a small MCP server bundled with the Workstation backend. When Klonode spawns a Claude session (per #62's plumbing), this MCP gets attached automatically.
Action handlers
Each action is implemented as a function the component registers alongside its defineComponent() declaration:
klonode_workstation_describe returns at least 8 registered components (chat-panel, tree-view, graph-view, github-view, editor-context, layer-inspector, heatmap-overlay, settings)
klonode_workstation_snapshot returns the current selected node, active pane, and active chat session id without taking a screenshot
klonode_workstation_action with { component: 'tree-view', action: 'select-node', args: { node_id: <real_id> } } actually selects the node in the UI (verified by a follow-up snapshot)
Adding a new interactive component requires calling defineComponent — there's a CI check (or at minimum a runtime warning) for components that have UI but no registry entry
Documented in CONTEXT.md for packages/ui/src/lib/workstation/
Risks / open questions
Action surface size. Don't try to expose every internal store mutation — only user-facing actions that make sense for an external agent to perform. Start with the obvious ones (select node, switch tab, send message, expand/collapse, scroll-to) and grow from real Claude usage logs.
State snapshot freshness. The snapshot has to reflect the current Svelte store state when the tool is called, not a stale cache. The MCP server reads stores directly via Svelte's get() so this is fine — but make sure the test suite verifies it.
Multi-window / multi-session. If the user has two Workstation windows open, which one does the snapshot describe? Probably scope by the WebSocket session ID the spawned Claude is bound to.
Sibling to #62 and #63. The third leg of the self-hosting story.
#62 gives Claude a headless browser. #63 gives the user a preview pane. This issue gives Claude a structured map of the Workstation UI itself, so when Claude is running inside Klonode and a user asks "open the GraphView and select the auth folder," Claude doesn't have to take a screenshot and guess pixel coordinates — it queries a real component registry and calls a real action tool.
What
A self-introspection layer the Workstation publishes about itself, plus the tools to query and act on it:
1. Component registry
Every interactive Svelte component in
packages/ui/src/lib/components/gets:chat-panel,tree-view,graph-view,github-view,layer-inspector,editor-context,editor-toolbar)chat-panel.send-message,tree-view.expand-node,graph-view.select-node,editor-context.start-edit)chat-panel.current-session-id,tree-view.expanded-paths,graph-view.selected-node-id)The registry lives in code, not docs — generated from a
defineComponent({...})helper that each component calls once at module load. That guarantees the registry stays in sync with the actual UI.2. State snapshot tool:
klonode_workstation_snapshotReturns a structured JSON snapshot of the current Workstation state:
{ "active_pane": "graph-view", "panes_open": ["tree-view", "graph-view", "chat-panel", "editor-context"], "selected_node": { "id": "node_42", "path": "packages/ui/src/lib/auth", "layer": 2 }, "active_chat_session": { "id": "session_xyz", "agent": "co", "messages": 8 }, "layout": "split-vertical", "viewport": { "width": 1920, "height": 1080 } }This is a first-class tool Claude can call from any spawned session. No screenshots, no regex parsing.
3. Action tools:
klonode_workstation_actionA single dispatch tool that takes a component ID and an action name:
{ "component": "tree-view", "action": "expand-node", "args": { "path": "packages/ui/src/lib/auth" } } { "component": "graph-view", "action": "select-node", "args": { "node_id": "node_42" } } { "component": "chat-panel", "action": "send-message", "args": { "session_id": "session_xyz", "text": "summarize this folder" } } { "component": "editor-context", "action": "start-edit", "args": {} }Each registered action validates its args via the registry and routes to the actual Svelte store update or method call.
4. Discovery tool:
klonode_workstation_describeReturns the full registry of components, their actions, and their observable state — so Claude can ask "what can I do in this UI?" without prior knowledge. This is what
read_pageis for an HTML page, but for Klonode's own component tree.Why this matters
Implementation sketch
Component definition
Registry storage
A single Svelte store under
packages/ui/src/lib/workstation/registry.tscollects everydefineComponent()call at boot. The store is the source of truth that the snapshot, action, and describe tools all query.Tool wiring
The three tools (
snapshot,action,describe) are exposed as a small MCP server bundled with the Workstation backend. When Klonode spawns a Claude session (per #62's plumbing), this MCP gets attached automatically.Action handlers
Each action is implemented as a function the component registers alongside its
defineComponent()declaration:Files
packages/ui/src/lib/workstation/registry.ts— central registry store +defineComponent/defineComponentActionhelperspackages/ui/src/lib/workstation/mcp-server.ts— the MCP server that exposes the three toolspackages/ui/src/routes/api/workstation/snapshot/+server.ts— HTTP endpoint behind the MCPpackages/ui/src/routes/api/workstation/action/+server.ts— HTTP endpoint behind the MCPpackages/ui/src/lib/components/to calldefineComponentonceAcceptance criteria
klonode_workstation_describereturns at least 8 registered components (chat-panel, tree-view, graph-view, github-view, editor-context, layer-inspector, heatmap-overlay, settings)klonode_workstation_snapshotreturns the current selected node, active pane, and active chat session id without taking a screenshotklonode_workstation_actionwith{ component: 'tree-view', action: 'select-node', args: { node_id: <real_id> } }actually selects the node in the UI (verified by a follow-up snapshot)defineComponent— there's a CI check (or at minimum a runtime warning) for components that have UI but no registry entryCONTEXT.mdforpackages/ui/src/lib/workstation/Risks / open questions
get()so this is fine — but make sure the test suite verifies it.Out of scope