@workerdeck/react 0.6.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { SessionHandle, WorkerDeckClient } from "@workerdeck/client";
2
- import { ContextUsage, ModelOption, PermissionMode, PermissionRequest, ProfileEngine, RateLimitInfo, SessionEvent, SessionInfo, SessionStatus, SlashCommandInfo, ToolExecutionBackend } from "@workerdeck/protocol";
2
+ import { ContextUsage, MessageAttachment, ModelOption, PermissionMode, PermissionRequest, ProfileEngine, RateLimitInfo, SessionEvent, SessionInfo, SessionStatus, SlashCommandInfo, ToolExecutionBackend } from "@workerdeck/protocol";
3
3
  import { RunScriptResult, SandboxEngine, SandboxVfs } from "@workerdeck/sandbox";
4
4
 
5
5
  //#region src/transcript.d.ts
@@ -11,6 +11,7 @@ type TranscriptItem = {
11
11
  kind: 'user';
12
12
  id: string;
13
13
  text: string;
14
+ attachments?: MessageAttachment[];
14
15
  } | {
15
16
  kind: 'assistant_text';
16
17
  id: string;
@@ -79,12 +80,19 @@ type TranscriptState = {
79
80
  * affordances; absent (an older server) reads as 'claude'. */
80
81
  engine?: ProfileEngine; /** Models the session can switch to (from the `capabilities` event). */
81
82
  models?: ModelOption[]; /** Slash commands the CLI accepts (from the `capabilities` event). */
82
- commands?: SlashCommandInfo[]; /** Seeded from `system_init`, updated on `permission_mode_changed`. */
83
+ commands?: SlashCommandInfo[];
84
+ /** What this session's default model resolves to (from `capabilities`). Known
85
+ * before the first turn, which `model` is not — a promptless session has no
86
+ * `system_init` until it is spoken to. */
87
+ defaultModel?: string; /** Seeded from `system_init`, updated on `permission_mode_changed`. */
83
88
  permissionMode?: PermissionMode; /** Latest context-window snapshot; absent until the first turn completes. */
84
89
  contextUsage?: ContextUsage;
85
90
  /** Latest rate-limit snapshot per window ('five_hour', 'seven_day', ...).
86
91
  * Absent for API-key sessions — render nothing, not 0%. */
87
92
  rateLimits?: Record<string, RateLimitInfo>;
93
+ /** claude.ai plan the rate-limit windows belong to ('pro', 'max', ...), from
94
+ * `plan_info`. Absent for API-key sessions, like the windows themselves. */
95
+ subscriptionType?: string;
88
96
  items: TranscriptItem[];
89
97
  pendingApprovals: PermissionRequest[];
90
98
  totalCostUsd: number;
@@ -114,8 +122,8 @@ type UseClaudeSessionResult = {
114
122
  * socket — e.g. useToolCallHost: the bridge asks the first attached client,
115
123
  * so a host on a second handle would never see the requests. Undefined until
116
124
  * attached and after unmount. */
117
- handle: SessionHandle | undefined;
118
- send: (text: string) => void;
125
+ handle: SessionHandle | undefined; /** Attachment ids come from `client.uploadAttachment`, in send order. */
126
+ send: (text: string, attachmentIds?: string[]) => void;
119
127
  approve: (requestId: string, updatedInput?: Record<string, unknown>) => void;
120
128
  deny: (requestId: string, message?: string) => void;
121
129
  interrupt: () => void;
package/build/index.mjs CHANGED
@@ -77,7 +77,8 @@ function applyEvent(state, event) {
77
77
  case "capabilities": return {
78
78
  ...base,
79
79
  models: event.models,
80
- commands: event.commands
80
+ commands: event.commands,
81
+ defaultModel: event.defaultModel ?? base.defaultModel
81
82
  };
82
83
  case "model_changed": return event.model === void 0 ? base : {
83
84
  ...base,
@@ -102,6 +103,10 @@ function applyEvent(state, event) {
102
103
  }
103
104
  };
104
105
  }
106
+ case "plan_info": return {
107
+ ...base,
108
+ subscriptionType: event.subscriptionType
109
+ };
105
110
  case "user_message": {
106
111
  let items = base.items;
107
112
  for (const block of contentToBlocks(event.message.content)) if (block.type === "tool_result") {
@@ -127,7 +132,8 @@ function applyEvent(state, event) {
127
132
  else items = upsert(items, {
128
133
  kind: "user",
129
134
  id: event.uuid ?? `user-${event.seq}`,
130
- text
135
+ text,
136
+ attachments: event.attachments
131
137
  });
132
138
  }
133
139
  return {
@@ -331,7 +337,7 @@ function useClaudeSession(client, sessionId, options) {
331
337
  state,
332
338
  connected,
333
339
  handle: handleState,
334
- send: (text) => handleRef.current?.send(text),
340
+ send: (text, attachmentIds) => handleRef.current?.send(text, attachmentIds),
335
341
  approve: (requestId, updatedInput) => handleRef.current?.approve(requestId, updatedInput),
336
342
  deny: (requestId, message) => handleRef.current?.deny(requestId, message),
337
343
  interrupt: () => handleRef.current?.interrupt(),
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/transcript.ts","../src/use-session.ts","../src/tool-host.ts","../src/use-tool-host.ts"],"sourcesContent":["import type {\n ContentBlock,\n ContextUsage,\n ModelOption,\n PermissionMode,\n PermissionRequest,\n ProfileEngine,\n RateLimitInfo,\n SessionEvent,\n SessionInfo,\n SessionStatus,\n SlashCommandInfo,\n ToolExecutionBackend,\n ToolExecutionOutput,\n ToolResultBlock,\n} from '@workerdeck/protocol'\n\n/**\n * Pure transcript state machine over the wire-protocol event stream. Framework-free\n * so it can be unit-tested and reused outside React.\n */\n\nexport type TranscriptItem =\n | { kind: 'user'; id: string; text: string }\n | {\n kind: 'assistant_text'\n id: string\n text: string\n streaming: boolean\n parentToolUseId: string | null\n }\n | { kind: 'thinking'; id: string; text: string; parentToolUseId: string | null }\n | {\n kind: 'tool_call'\n id: string\n name: string\n input: unknown\n parentToolUseId: string | null\n /**\n * - `running` — the model called it; execution has not been reported\n * - `pending` — dispatched to an executor (bridged to this client, queued)\n * - `deferred` — parked beyond this turn; may outlive the session's liveness\n * - `settled` / `failed` — terminal\n *\n * Derive UI from this, not from `result` being present: a pending or\n * deferred call has no result yet and is not the same as a running one.\n */\n status: 'running' | 'pending' | 'deferred' | 'settled' | 'failed'\n result?: { text: string; isError: boolean }\n /** Correlation id when this call is executed outside the model loop. */\n executionId?: string\n /** Which backend is executing it, when known. */\n backend?: ToolExecutionBackend\n /** Logs captured by the executor (guest console output). */\n logs?: string[]\n }\n | {\n kind: 'turn_result'\n id: string\n subtype: string\n isError: boolean\n durationMs: number\n totalCostUsd: number\n errors?: string[]\n }\n | { kind: 'notice'; id: string; level: 'info' | 'error'; text: string }\n /** The agent handed over a session file (`file_delivered`). Render a download\n * card; the file is served by GET /sessions/:id/files/<path> while the\n * session lives. */\n | { kind: 'file_delivered'; id: string; path: string; bytes: number; description?: string }\n\nexport type TranscriptState = {\n status: SessionStatus\n statusDetail?: string\n model?: string\n cwd?: string\n sdkSessionId?: string\n /** Engine running the session, from the attach snapshot. Gates CLI-only\n * affordances; absent (an older server) reads as 'claude'. */\n engine?: ProfileEngine\n /** Models the session can switch to (from the `capabilities` event). */\n models?: ModelOption[]\n /** Slash commands the CLI accepts (from the `capabilities` event). */\n commands?: SlashCommandInfo[]\n /** Seeded from `system_init`, updated on `permission_mode_changed`. */\n permissionMode?: PermissionMode\n /** Latest context-window snapshot; absent until the first turn completes. */\n contextUsage?: ContextUsage\n /** Latest rate-limit snapshot per window ('five_hour', 'seven_day', ...).\n * Absent for API-key sessions — render nothing, not 0%. */\n rateLimits?: Record<string, RateLimitInfo>\n items: TranscriptItem[]\n pendingApprovals: PermissionRequest[]\n totalCostUsd: number\n lastSeq: number\n}\n\nexport const initialTranscriptState: TranscriptState = {\n status: 'starting',\n items: [],\n pendingApprovals: [],\n totalCostUsd: 0,\n lastSeq: 0,\n}\n\nconst STREAMING_ID = 'streaming'\nconst STREAMING_THINKING_ID = 'streaming-thinking'\n\nfunction blockText(content: ToolResultBlock['content']): string {\n if (content === undefined) return ''\n if (typeof content === 'string') return content\n return content\n .map((part) => (typeof part.text === 'string' ? part.text : ''))\n .filter(Boolean)\n .join('\\n')\n}\n\nfunction contentToBlocks(content: string | ContentBlock[]): ContentBlock[] {\n return typeof content === 'string' ? [{ type: 'text', text: content }] : content\n}\n\n/** Render an execution's by-value output for the transcript. */\nfunction outputText(output: ToolExecutionOutput): string {\n if (output.type === 'text') return output.value\n try {\n return JSON.stringify(output.value)\n } catch {\n return String(output.value)\n }\n}\n\n/** CLI-side command output arrives as user text wrapped in local-command tags. */\nconst LOCAL_COMMAND_OUTPUT = /^<local-command-(stdout|stderr)>([\\s\\S]*?)<\\/local-command-\\1>$/\n\nfunction upsert(items: TranscriptItem[], item: TranscriptItem): TranscriptItem[] {\n const index = items.findIndex((existing) => existing.id === item.id && existing.kind === item.kind)\n if (index === -1) return [...items, item]\n const next = [...items]\n next[index] = item\n return next\n}\n\n/**\n * Seed transcript state from the attach snapshot (the `attached` frame's SessionInfo).\n * A promptless session emits no `system_init` until its first message, so fields like\n * `permissionMode` and `model` would otherwise stay empty — fill only what events\n * haven't set yet; the event stream stays authoritative.\n */\nexport function seedFromSessionInfo(state: TranscriptState, info: SessionInfo): TranscriptState {\n return {\n ...state,\n // Before any event has arrived, the snapshot status is fresher than 'starting'.\n status: state.lastSeq === 0 ? info.status : state.status,\n model: state.model ?? info.model,\n permissionMode: state.permissionMode ?? info.permissionMode,\n cwd: state.cwd ?? info.cwd,\n sdkSessionId: state.sdkSessionId ?? info.sdkSessionId,\n // Never changes for a live session, and no event carries it — the snapshot is\n // the only source, so take it whenever it is present.\n engine: info.engine ?? state.engine,\n }\n}\n\nexport function applyEvent(state: TranscriptState, event: SessionEvent): TranscriptState {\n if (event.seq <= state.lastSeq) return state\n const base: TranscriptState = { ...state, lastSeq: event.seq }\n\n switch (event.type) {\n case 'system_init':\n return {\n ...base,\n model: event.model,\n cwd: event.cwd,\n sdkSessionId: event.sdkSessionId,\n permissionMode: event.permissionMode,\n }\n\n case 'status_changed':\n return { ...base, status: event.status, statusDetail: event.detail }\n\n case 'capabilities':\n return { ...base, models: event.models, commands: event.commands }\n\n case 'model_changed':\n // undefined = reset to the server default; keep showing the last known model.\n return event.model === undefined ? base : { ...base, model: event.model }\n\n case 'permission_mode_changed':\n return { ...base, permissionMode: event.mode }\n\n case 'context_usage':\n return { ...base, contextUsage: event.usage }\n\n case 'rate_limit': {\n // Keyed by window so five_hour and seven_day updates don't clobber each other.\n const key = event.info.rateLimitType\n if (!key) return base\n return { ...base, rateLimits: { ...base.rateLimits, [key]: event.info } }\n }\n\n case 'user_message': {\n let items = base.items\n for (const block of contentToBlocks(event.message.content)) {\n if (block.type === 'tool_result') {\n const toolResult = block as ToolResultBlock\n const isError = toolResult.is_error === true\n items = items.map((item) =>\n item.kind === 'tool_call' && item.id === toolResult.tool_use_id\n ? {\n ...item,\n status: isError ? 'failed' : 'settled',\n result: { text: blockText(toolResult.content), isError },\n }\n : item,\n )\n } else if (block.type === 'text' && !event.synthetic) {\n const text = (block as { text: string }).text\n const localOutput = LOCAL_COMMAND_OUTPUT.exec(text.trim())\n if (localOutput) {\n items = upsert(items, {\n kind: 'notice',\n id: event.uuid ?? `user-${event.seq}`,\n level: localOutput[1] === 'stderr' ? 'error' : 'info',\n text: localOutput[2].trim(),\n })\n } else {\n items = upsert(items, {\n kind: 'user',\n id: event.uuid ?? `user-${event.seq}`,\n text,\n })\n }\n }\n }\n return { ...base, items }\n }\n\n case 'assistant_message': {\n // Encrypted thinking arrives as a signature-only block on the final message: `thinking`\n // is '' and the human-readable summary, when the model surfaces one at all, exists only\n // in the thinking_delta stream. Carry the streamed text over rather than let the full\n // message overwrite it with nothing.\n let streamedThinking =\n base.items.find(\n (item): item is Extract<TranscriptItem, { kind: 'thinking' }> =>\n item.kind === 'thinking' && item.id === STREAMING_THINKING_ID,\n )?.text ?? ''\n // The full message supersedes any in-flight streamed text/thinking.\n let items = base.items.filter(\n (item) =>\n !(item.kind === 'assistant_text' && item.id === STREAMING_ID) &&\n !(item.kind === 'thinking' && item.id === STREAMING_THINKING_ID),\n )\n const blocks = contentToBlocks(event.message.content)\n blocks.forEach((block, index) => {\n const id = `${event.uuid}-${index}`\n if (block.type === 'text') {\n items = upsert(items, {\n kind: 'assistant_text',\n id,\n text: (block as { text: string }).text,\n streaming: false,\n parentToolUseId: event.parentToolUseId,\n })\n } else if (block.type === 'thinking') {\n const text = (block as { thinking: string }).thinking || streamedThinking\n // One streamed thought backfills at most one block, so a multi-block message\n // doesn't repeat it.\n streamedThinking = ''\n // No summary anywhere: drop the block instead of leaving a \"Thought process\" row\n // that expands to nothing (and, across consecutive messages, stacks up).\n if (text.trim() === '') return\n items = upsert(items, {\n kind: 'thinking',\n id,\n text,\n parentToolUseId: event.parentToolUseId,\n })\n } else if (block.type === 'tool_use') {\n const toolUse = block as { id: string; name: string; input: unknown }\n items = upsert(items, {\n kind: 'tool_call',\n id: toolUse.id,\n name: toolUse.name,\n input: toolUse.input,\n parentToolUseId: event.parentToolUseId,\n status: 'running',\n })\n }\n })\n return { ...base, items }\n }\n\n case 'stream_delta': {\n const delta = event.event as {\n type: string\n delta?: { type?: string; text?: string; thinking?: string }\n }\n if (delta.type !== 'content_block_delta') return base\n if (delta.delta?.type === 'text_delta') {\n const existing = base.items.find(\n (item): item is Extract<TranscriptItem, { kind: 'assistant_text' }> =>\n item.kind === 'assistant_text' && item.id === STREAMING_ID,\n )\n const item: TranscriptItem = {\n kind: 'assistant_text',\n id: STREAMING_ID,\n text: (existing?.text ?? '') + (delta.delta.text ?? ''),\n streaming: true,\n parentToolUseId: event.parentToolUseId,\n }\n return { ...base, items: upsert(base.items, item) }\n }\n if (delta.delta?.type === 'thinking_delta') {\n const existing = base.items.find(\n (item): item is Extract<TranscriptItem, { kind: 'thinking' }> =>\n item.kind === 'thinking' && item.id === STREAMING_THINKING_ID,\n )\n const item: TranscriptItem = {\n kind: 'thinking',\n id: STREAMING_THINKING_ID,\n text: (existing?.text ?? '') + (delta.delta.thinking ?? ''),\n parentToolUseId: event.parentToolUseId,\n }\n return { ...base, items: upsert(base.items, item) }\n }\n return base\n }\n\n case 'turn_result':\n return {\n ...base,\n // total_cost_usd is session-cumulative on each SDK result message.\n totalCostUsd: event.totalCostUsd,\n items: [\n ...base.items,\n {\n kind: 'turn_result',\n id: `turn-${event.seq}`,\n subtype: event.subtype,\n isError: event.isError,\n durationMs: event.durationMs,\n totalCostUsd: event.totalCostUsd,\n errors: event.errors,\n },\n ],\n }\n\n case 'permission_requested':\n return { ...base, pendingApprovals: [...base.pendingApprovals, event.request] }\n\n case 'permission_resolved':\n return {\n ...base,\n pendingApprovals: base.pendingApprovals.filter((r) => r.id !== event.requestId),\n }\n\n // Execution lifecycle for tool calls that run outside the model loop\n // (bridged to this client, queued, or deferred). Keyed by executionId, which\n // equals the tool_use id for calls the model made. Events for an unknown id\n // are ignored rather than fabricating an item: the tool_use that explains it\n // may simply not have arrived (or belongs to another session).\n case 'execution_dispatched':\n return {\n ...base,\n items: base.items.map((item) =>\n item.kind === 'tool_call' && item.id === event.executionId\n ? {\n ...item,\n status: event.deferred ? 'deferred' : 'pending',\n executionId: event.executionId,\n backend: event.backend,\n }\n : item,\n ),\n }\n\n case 'execution_result':\n return {\n ...base,\n items: base.items.map((item) =>\n item.kind === 'tool_call' && item.id === event.executionId\n ? {\n ...item,\n status: 'settled',\n executionId: event.executionId,\n result: { text: outputText(event.output), isError: false },\n logs: event.logs ?? item.logs,\n }\n : item,\n ),\n }\n\n case 'execution_failed':\n return {\n ...base,\n items: base.items.map((item) =>\n item.kind === 'tool_call' && item.id === event.executionId\n ? {\n ...item,\n status: 'failed',\n executionId: event.executionId,\n result: { text: `${event.reason}: ${event.error}`, isError: true },\n logs: event.logs ?? item.logs,\n }\n : item,\n ),\n }\n\n case 'file_delivered':\n return {\n ...base,\n items: [\n ...base.items,\n {\n kind: 'file_delivered',\n id: `file-${event.seq}`,\n path: event.path,\n bytes: event.bytes,\n description: event.description,\n },\n ],\n }\n\n case 'session_error':\n return {\n ...base,\n items: [\n ...base.items,\n { kind: 'notice', id: `err-${event.seq}`, level: 'error', text: event.message },\n ],\n }\n\n case 'session_closed':\n return {\n ...base,\n items: [\n ...base.items,\n {\n kind: 'notice',\n id: `closed-${event.seq}`,\n level: 'info',\n text: `Session closed (${event.reason})`,\n },\n ],\n }\n\n case 'sdk_event':\n default:\n return base\n }\n}\n","import { useEffect, useMemo, useReducer, useRef, useState } from 'react'\nimport type { WorkerDeckClient, SessionHandle } from '@workerdeck/client'\nimport type { AttachedFrame, PermissionMode, SessionEvent } from '@workerdeck/protocol'\nimport {\n applyEvent,\n initialTranscriptState,\n seedFromSessionInfo,\n type TranscriptState,\n} from './transcript.ts'\n\n/** Session events drive the reducer; the attach snapshot seeds fields (permission\n * mode, model) that a promptless session's event stream doesn't carry yet. */\nfunction reduce(state: TranscriptState, action: SessionEvent | AttachedFrame): TranscriptState {\n return action.type === 'attached'\n ? seedFromSessionInfo(state, action.session)\n : applyEvent(state, action)\n}\n\nexport type UseClaudeSessionOptions = {\n /** Called when the server rejects a command with a protocol_error frame — e.g. a\n * permission-mode switch the CLI refuses. Without a handler these are dropped\n * silently and the UI looks like \"nothing happened\". */\n onProtocolError?: (message: string) => void\n}\n\nexport type UseClaudeSessionResult = {\n state: TranscriptState\n connected: boolean\n /** The live attach handle, for wiring companions that must ride the SAME\n * socket — e.g. useToolCallHost: the bridge asks the first attached client,\n * so a host on a second handle would never see the requests. Undefined until\n * attached and after unmount. */\n handle: SessionHandle | undefined\n send: (text: string) => void\n approve: (requestId: string, updatedInput?: Record<string, unknown>) => void\n deny: (requestId: string, message?: string) => void\n interrupt: () => void\n setPermissionMode: (mode: PermissionMode) => void\n setModel: (model?: string) => void\n closeSession: () => void\n}\n\n/** Attach to a session and maintain live transcript state. Detaches on unmount. */\nexport function useClaudeSession(\n client: WorkerDeckClient,\n sessionId: string | undefined,\n options?: UseClaudeSessionOptions,\n): UseClaudeSessionResult {\n const [state, dispatch] = useReducer(reduce, initialTranscriptState)\n const [connected, setConnected] = useState(false)\n // Ref for the stable callbacks below; state so consumers of `handle` re-render\n // when the socket opens or the session switches.\n const [handleState, setHandleState] = useState<SessionHandle | undefined>()\n const handleRef = useRef<SessionHandle | null>(null)\n // Ref'd so a new inline callback doesn't tear down and reopen the socket.\n const onProtocolErrorRef = useRef(options?.onProtocolError)\n onProtocolErrorRef.current = options?.onProtocolError\n\n useEffect(() => {\n if (!sessionId) return\n const handle = client.attach(sessionId)\n handleRef.current = handle\n setHandleState(handle)\n const offEvent = handle.on('event', (event: SessionEvent) => dispatch(event))\n const offAttached = handle.on('attached', (frame: AttachedFrame) => dispatch(frame))\n const offConn = handle.on('connectionChange', setConnected)\n const offProtocolError = handle.on('protocolError', (message: string) => {\n onProtocolErrorRef.current?.(message)\n })\n return () => {\n offEvent()\n offAttached()\n offConn()\n offProtocolError()\n handle.detach()\n handleRef.current = null\n setHandleState(undefined)\n }\n }, [client, sessionId])\n\n return useMemo(\n () => ({\n state,\n connected,\n handle: handleState,\n send: (text) => handleRef.current?.send(text),\n approve: (requestId, updatedInput) => handleRef.current?.approve(requestId, updatedInput),\n deny: (requestId, message) => handleRef.current?.deny(requestId, message),\n interrupt: () => handleRef.current?.interrupt(),\n setPermissionMode: (mode) => handleRef.current?.setPermissionMode(mode),\n setModel: (model) => handleRef.current?.setModel(model),\n closeSession: () => handleRef.current?.closeSession(),\n }),\n [state, connected, handleState],\n )\n}\n","import type { SessionHandle } from '@workerdeck/client'\nimport type { RunScriptResult, SandboxEngine, SandboxVfs } from '@workerdeck/sandbox'\nimport type { ToolCallRequestFrame } from '@workerdeck/protocol'\n\n/** What the host was asked to do and how it went (for UI/telemetry). */\nexport type ToolHostExecution = {\n executionId: string\n toolName: string\n status: 'running' | 'settled' | 'failed' | 'canceled'\n reason?: string\n startedAt: number\n endedAt?: number\n}\n\nexport type ToolHostRunner = (request: {\n script: string\n vfs: SandboxVfs\n timeoutMs: number\n memoryLimitBytes: number\n signal: AbortSignal\n}) => Promise<RunScriptResult>\n\nexport type ToolCallHostOptions = {\n /** Tools this client will execute. Anything else is refused, so a server can\n * never talk this tab into running something it didn't opt into.\n * Default: `['eval_script']`. */\n tools?: string[]\n /** Guest wall-clock limit, unless the request asks for less. Default 5000. */\n timeoutMs?: number\n /** Guest allocator cap, unless the request asks for less. Default 64 MiB. */\n memoryLimitBytes?: number\n /**\n * Load the WASM guest engine. Called at most once, on the first bridged call\n * — nothing is downloaded or parsed until a session actually bridges one.\n * Defaults to `@workerdeck/sandbox` with the single-file browser build.\n */\n loadEngine?: () => Promise<SandboxEngine>\n /**\n * Run the script. Defaults to executing on this thread, which is fine for the\n * short, time-boxed evaluations this is built for. Supply your own (a Web\n * Worker running the same engine) to keep long evaluations off the UI thread\n * — the guest deadline preempts the interpreter, but only between bytecode\n * ops on whichever thread it runs on.\n */\n execute?: ToolHostRunner\n /** Host-gated fetch for the guest. Omitted = the guest has no network at all. */\n fetchText?: (url: string) => Promise<string>\n /** Observe executions (rendering, logging). */\n onExecution?: (execution: ToolHostExecution) => void\n}\n\n/**\n * Answers server-bridged tool calls by executing them in this browser tab.\n * Framework-free — {@link useToolCallHost} is a thin React wrapper.\n *\n * The point is data locality: documents fetched or held client-side can be\n * evaluated here and never touch the server. The engine loads lazily, so a page\n * that never bridges a call never pays for the WASM guest.\n */\nexport function createToolCallHost(\n handle: SessionHandle,\n options: ToolCallHostOptions = {},\n): { dispose: () => void } {\n const inFlight = new Map<string, AbortController>()\n let enginePromise: Promise<SandboxEngine> | undefined\n let disposed = false\n\n const track = (execution: ToolHostExecution) => options.onExecution?.(execution)\n\n const refuse = (frame: ToolCallRequestFrame, reason: string, error: string, startedAt: number) => {\n handle.sendToolCallError(frame.executionId, reason, error)\n track({\n executionId: frame.executionId,\n toolName: frame.toolName,\n status: 'failed',\n reason,\n startedAt,\n endedAt: Date.now(),\n })\n }\n\n const run = async (frame: ToolCallRequestFrame): Promise<void> => {\n const startedAt = Date.now()\n const allowed = options.tools ?? ['eval_script']\n if (!allowed.includes(frame.toolName)) {\n refuse(frame, 'unsupported_tool', `this client does not execute '${frame.toolName}'`, startedAt)\n return\n }\n const script = (frame.input as { script?: unknown } | undefined)?.script\n if (typeof script !== 'string') {\n refuse(frame, 'invalid_input', 'expected a string `script` input', startedAt)\n return\n }\n\n const controller = new AbortController()\n inFlight.set(frame.executionId, controller)\n track({ executionId: frame.executionId, toolName: frame.toolName, status: 'running', startedAt })\n\n try {\n const sandbox = await import('@workerdeck/sandbox')\n const vfs = sandbox.createVfs(frame.vfsSeed)\n // Never exceed what the server asked for: it owns the deadline it will\n // give up at, and answering after that is wasted work.\n const timeoutMs = Math.min(\n frame.limits?.timeoutMs ?? Number.POSITIVE_INFINITY,\n options.timeoutMs ?? 5000,\n )\n const memoryLimitBytes = Math.min(\n frame.limits?.memoryLimitBytes ?? Number.POSITIVE_INFINITY,\n options.memoryLimitBytes ?? 64 * 1024 * 1024,\n )\n\n const result = options.execute\n ? await options.execute({ script, vfs, timeoutMs, memoryLimitBytes, signal: controller.signal })\n : await (async () => {\n enginePromise ??= (options.loadEngine ?? defaultLoadEngine)()\n return sandbox.runScript(await enginePromise, {\n script,\n vfs,\n timeoutMs,\n memoryLimitBytes,\n signal: controller.signal,\n fetchText: options.fetchText,\n })\n })()\n\n // Cancelled or torn down while we worked: the server is no longer waiting.\n if (disposed || !inFlight.has(frame.executionId)) return\n const logs = result.logs.map((l) => `[${l.level}] ${l.text}`)\n if (result.ok) {\n handle.sendToolCallResult(frame.executionId, { type: 'json', value: result.value }, logs)\n track({\n executionId: frame.executionId,\n toolName: frame.toolName,\n status: 'settled',\n startedAt,\n endedAt: Date.now(),\n })\n } else {\n handle.sendToolCallError(frame.executionId, result.reason, result.error, logs)\n track({\n executionId: frame.executionId,\n toolName: frame.toolName,\n status: 'failed',\n reason: result.reason,\n startedAt,\n endedAt: Date.now(),\n })\n }\n } catch (error) {\n if (disposed || !inFlight.has(frame.executionId)) return\n // Engine load failures land here — tell the server so the agent can adapt\n // instead of waiting out the deadline.\n refuse(frame, 'host_error', error instanceof Error ? error.message : String(error), startedAt)\n } finally {\n inFlight.delete(frame.executionId)\n }\n }\n\n const offRequest = handle.on('toolCallRequest', (frame) => void run(frame))\n const offCancel = handle.on('toolCallCanceled', ({ executionId, reason }) => {\n const controller = inFlight.get(executionId)\n if (!controller) return\n controller.abort()\n inFlight.delete(executionId)\n track({\n executionId,\n toolName: '',\n status: 'canceled',\n reason,\n startedAt: Date.now(),\n endedAt: Date.now(),\n })\n })\n\n return {\n dispose: () => {\n disposed = true\n offRequest()\n offCancel()\n for (const controller of inFlight.values()) controller.abort()\n inFlight.clear()\n },\n }\n}\n\n/** The single-file browser build keeps this to one lazy chunk — no separate\n * .wasm fetch, and nothing at all until the first bridged call. */\nasync function defaultLoadEngine(): Promise<SandboxEngine> {\n const [sandbox, variant] = await Promise.all([\n import('@workerdeck/sandbox'),\n import('@jitl/quickjs-singlefile-browser-release-asyncify'),\n ])\n return sandbox.loadEngine(variant as never)\n}\n","import { useEffect, useRef, useState } from 'react'\nimport type { SessionHandle } from '@workerdeck/client'\nimport {\n createToolCallHost,\n type ToolCallHostOptions,\n type ToolHostExecution,\n} from './tool-host.ts'\n\nexport type UseToolCallHostOptions = ToolCallHostOptions & {\n /** Turn the host off without unmounting. Default true. */\n enabled?: boolean\n /** How many recent executions to keep for rendering. Default 50. */\n historyLimit?: number\n}\n\n/**\n * React wrapper around {@link createToolCallHost}: subscribes while mounted and\n * exposes recent executions for rendering. All the logic lives in the\n * framework-free host — this only manages the subscription's lifetime.\n */\nexport function useToolCallHost(\n handle: SessionHandle | undefined,\n options: UseToolCallHostOptions = {},\n): { executions: ToolHostExecution[] } {\n const [executions, setExecutions] = useState<ToolHostExecution[]>([])\n // Read options at call time so re-renders never tear down the subscription.\n const optionsRef = useRef(options)\n optionsRef.current = options\n\n useEffect(() => {\n if (!handle || options.enabled === false) return\n const host = createToolCallHost(handle, {\n // Delegate every option through the ref, so a caller passing inline\n // objects/closures (the common case) doesn't resubscribe each render.\n get tools() {\n return optionsRef.current.tools\n },\n get timeoutMs() {\n return optionsRef.current.timeoutMs\n },\n get memoryLimitBytes() {\n return optionsRef.current.memoryLimitBytes\n },\n get loadEngine() {\n return optionsRef.current.loadEngine\n },\n get execute() {\n return optionsRef.current.execute\n },\n get fetchText() {\n return optionsRef.current.fetchText\n },\n onExecution: (execution) => {\n optionsRef.current.onExecution?.(execution)\n const limit = optionsRef.current.historyLimit ?? 50\n setExecutions((prev) => [\n ...prev.filter((e) => e.executionId !== execution.executionId),\n execution,\n ].slice(-limit))\n },\n })\n return () => host.dispose()\n }, [handle, options.enabled])\n\n return { executions }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/transcript.ts","../src/use-session.ts","../src/tool-host.ts","../src/use-tool-host.ts"],"sourcesContent":["import type {\n ContentBlock,\n ContextUsage,\n MessageAttachment,\n ModelOption,\n PermissionMode,\n PermissionRequest,\n ProfileEngine,\n RateLimitInfo,\n SessionEvent,\n SessionInfo,\n SessionStatus,\n SlashCommandInfo,\n ToolExecutionBackend,\n ToolExecutionOutput,\n ToolResultBlock,\n} from '@workerdeck/protocol'\n\n/**\n * Pure transcript state machine over the wire-protocol event stream. Framework-free\n * so it can be unit-tested and reused outside React.\n */\n\nexport type TranscriptItem =\n | { kind: 'user'; id: string; text: string; attachments?: MessageAttachment[] }\n | {\n kind: 'assistant_text'\n id: string\n text: string\n streaming: boolean\n parentToolUseId: string | null\n }\n | { kind: 'thinking'; id: string; text: string; parentToolUseId: string | null }\n | {\n kind: 'tool_call'\n id: string\n name: string\n input: unknown\n parentToolUseId: string | null\n /**\n * - `running` — the model called it; execution has not been reported\n * - `pending` — dispatched to an executor (bridged to this client, queued)\n * - `deferred` — parked beyond this turn; may outlive the session's liveness\n * - `settled` / `failed` — terminal\n *\n * Derive UI from this, not from `result` being present: a pending or\n * deferred call has no result yet and is not the same as a running one.\n */\n status: 'running' | 'pending' | 'deferred' | 'settled' | 'failed'\n result?: { text: string; isError: boolean }\n /** Correlation id when this call is executed outside the model loop. */\n executionId?: string\n /** Which backend is executing it, when known. */\n backend?: ToolExecutionBackend\n /** Logs captured by the executor (guest console output). */\n logs?: string[]\n }\n | {\n kind: 'turn_result'\n id: string\n subtype: string\n isError: boolean\n durationMs: number\n totalCostUsd: number\n errors?: string[]\n }\n | { kind: 'notice'; id: string; level: 'info' | 'error'; text: string }\n /** The agent handed over a session file (`file_delivered`). Render a download\n * card; the file is served by GET /sessions/:id/files/<path> while the\n * session lives. */\n | { kind: 'file_delivered'; id: string; path: string; bytes: number; description?: string }\n\nexport type TranscriptState = {\n status: SessionStatus\n statusDetail?: string\n model?: string\n cwd?: string\n sdkSessionId?: string\n /** Engine running the session, from the attach snapshot. Gates CLI-only\n * affordances; absent (an older server) reads as 'claude'. */\n engine?: ProfileEngine\n /** Models the session can switch to (from the `capabilities` event). */\n models?: ModelOption[]\n /** Slash commands the CLI accepts (from the `capabilities` event). */\n commands?: SlashCommandInfo[]\n /** What this session's default model resolves to (from `capabilities`). Known\n * before the first turn, which `model` is not — a promptless session has no\n * `system_init` until it is spoken to. */\n defaultModel?: string\n /** Seeded from `system_init`, updated on `permission_mode_changed`. */\n permissionMode?: PermissionMode\n /** Latest context-window snapshot; absent until the first turn completes. */\n contextUsage?: ContextUsage\n /** Latest rate-limit snapshot per window ('five_hour', 'seven_day', ...).\n * Absent for API-key sessions — render nothing, not 0%. */\n rateLimits?: Record<string, RateLimitInfo>\n /** claude.ai plan the rate-limit windows belong to ('pro', 'max', ...), from\n * `plan_info`. Absent for API-key sessions, like the windows themselves. */\n subscriptionType?: string\n items: TranscriptItem[]\n pendingApprovals: PermissionRequest[]\n totalCostUsd: number\n lastSeq: number\n}\n\nexport const initialTranscriptState: TranscriptState = {\n status: 'starting',\n items: [],\n pendingApprovals: [],\n totalCostUsd: 0,\n lastSeq: 0,\n}\n\nconst STREAMING_ID = 'streaming'\nconst STREAMING_THINKING_ID = 'streaming-thinking'\n\nfunction blockText(content: ToolResultBlock['content']): string {\n if (content === undefined) return ''\n if (typeof content === 'string') return content\n return content\n .map((part) => (typeof part.text === 'string' ? part.text : ''))\n .filter(Boolean)\n .join('\\n')\n}\n\nfunction contentToBlocks(content: string | ContentBlock[]): ContentBlock[] {\n return typeof content === 'string' ? [{ type: 'text', text: content }] : content\n}\n\n/** Render an execution's by-value output for the transcript. */\nfunction outputText(output: ToolExecutionOutput): string {\n if (output.type === 'text') return output.value\n try {\n return JSON.stringify(output.value)\n } catch {\n return String(output.value)\n }\n}\n\n/** CLI-side command output arrives as user text wrapped in local-command tags. */\nconst LOCAL_COMMAND_OUTPUT = /^<local-command-(stdout|stderr)>([\\s\\S]*?)<\\/local-command-\\1>$/\n\nfunction upsert(items: TranscriptItem[], item: TranscriptItem): TranscriptItem[] {\n const index = items.findIndex((existing) => existing.id === item.id && existing.kind === item.kind)\n if (index === -1) return [...items, item]\n const next = [...items]\n next[index] = item\n return next\n}\n\n/**\n * Seed transcript state from the attach snapshot (the `attached` frame's SessionInfo).\n * A promptless session emits no `system_init` until its first message, so fields like\n * `permissionMode` and `model` would otherwise stay empty — fill only what events\n * haven't set yet; the event stream stays authoritative.\n */\nexport function seedFromSessionInfo(state: TranscriptState, info: SessionInfo): TranscriptState {\n return {\n ...state,\n // Before any event has arrived, the snapshot status is fresher than 'starting'.\n status: state.lastSeq === 0 ? info.status : state.status,\n model: state.model ?? info.model,\n permissionMode: state.permissionMode ?? info.permissionMode,\n cwd: state.cwd ?? info.cwd,\n sdkSessionId: state.sdkSessionId ?? info.sdkSessionId,\n // Never changes for a live session, and no event carries it — the snapshot is\n // the only source, so take it whenever it is present.\n engine: info.engine ?? state.engine,\n }\n}\n\nexport function applyEvent(state: TranscriptState, event: SessionEvent): TranscriptState {\n if (event.seq <= state.lastSeq) return state\n const base: TranscriptState = { ...state, lastSeq: event.seq }\n\n switch (event.type) {\n case 'system_init':\n return {\n ...base,\n model: event.model,\n cwd: event.cwd,\n sdkSessionId: event.sdkSessionId,\n permissionMode: event.permissionMode,\n }\n\n case 'status_changed':\n return { ...base, status: event.status, statusDetail: event.detail }\n\n case 'capabilities':\n return {\n ...base,\n models: event.models,\n commands: event.commands,\n defaultModel: event.defaultModel ?? base.defaultModel,\n }\n\n case 'model_changed':\n // undefined = reset to the server default; keep showing the last known model.\n return event.model === undefined ? base : { ...base, model: event.model }\n\n case 'permission_mode_changed':\n return { ...base, permissionMode: event.mode }\n\n case 'context_usage':\n return { ...base, contextUsage: event.usage }\n\n case 'rate_limit': {\n // Keyed by window so five_hour and seven_day updates don't clobber each other.\n const key = event.info.rateLimitType\n if (!key) return base\n return { ...base, rateLimits: { ...base.rateLimits, [key]: event.info } }\n }\n\n case 'plan_info':\n return { ...base, subscriptionType: event.subscriptionType }\n\n case 'user_message': {\n let items = base.items\n for (const block of contentToBlocks(event.message.content)) {\n if (block.type === 'tool_result') {\n const toolResult = block as ToolResultBlock\n const isError = toolResult.is_error === true\n items = items.map((item) =>\n item.kind === 'tool_call' && item.id === toolResult.tool_use_id\n ? {\n ...item,\n status: isError ? 'failed' : 'settled',\n result: { text: blockText(toolResult.content), isError },\n }\n : item,\n )\n } else if (block.type === 'text' && !event.synthetic) {\n const text = (block as { text: string }).text\n const localOutput = LOCAL_COMMAND_OUTPUT.exec(text.trim())\n if (localOutput) {\n items = upsert(items, {\n kind: 'notice',\n id: event.uuid ?? `user-${event.seq}`,\n level: localOutput[1] === 'stderr' ? 'error' : 'info',\n text: localOutput[2].trim(),\n })\n } else {\n items = upsert(items, {\n kind: 'user',\n id: event.uuid ?? `user-${event.seq}`,\n text,\n // References, not bytes — render them by fetching\n // `/sessions/:id/attachments/:attachmentId`.\n attachments: event.attachments,\n })\n }\n }\n }\n return { ...base, items }\n }\n\n case 'assistant_message': {\n // Encrypted thinking arrives as a signature-only block on the final message: `thinking`\n // is '' and the human-readable summary, when the model surfaces one at all, exists only\n // in the thinking_delta stream. Carry the streamed text over rather than let the full\n // message overwrite it with nothing.\n let streamedThinking =\n base.items.find(\n (item): item is Extract<TranscriptItem, { kind: 'thinking' }> =>\n item.kind === 'thinking' && item.id === STREAMING_THINKING_ID,\n )?.text ?? ''\n // The full message supersedes any in-flight streamed text/thinking.\n let items = base.items.filter(\n (item) =>\n !(item.kind === 'assistant_text' && item.id === STREAMING_ID) &&\n !(item.kind === 'thinking' && item.id === STREAMING_THINKING_ID),\n )\n const blocks = contentToBlocks(event.message.content)\n blocks.forEach((block, index) => {\n const id = `${event.uuid}-${index}`\n if (block.type === 'text') {\n items = upsert(items, {\n kind: 'assistant_text',\n id,\n text: (block as { text: string }).text,\n streaming: false,\n parentToolUseId: event.parentToolUseId,\n })\n } else if (block.type === 'thinking') {\n const text = (block as { thinking: string }).thinking || streamedThinking\n // One streamed thought backfills at most one block, so a multi-block message\n // doesn't repeat it.\n streamedThinking = ''\n // No summary anywhere: drop the block instead of leaving a \"Thought process\" row\n // that expands to nothing (and, across consecutive messages, stacks up).\n if (text.trim() === '') return\n items = upsert(items, {\n kind: 'thinking',\n id,\n text,\n parentToolUseId: event.parentToolUseId,\n })\n } else if (block.type === 'tool_use') {\n const toolUse = block as { id: string; name: string; input: unknown }\n items = upsert(items, {\n kind: 'tool_call',\n id: toolUse.id,\n name: toolUse.name,\n input: toolUse.input,\n parentToolUseId: event.parentToolUseId,\n status: 'running',\n })\n }\n })\n return { ...base, items }\n }\n\n case 'stream_delta': {\n const delta = event.event as {\n type: string\n delta?: { type?: string; text?: string; thinking?: string }\n }\n if (delta.type !== 'content_block_delta') return base\n if (delta.delta?.type === 'text_delta') {\n const existing = base.items.find(\n (item): item is Extract<TranscriptItem, { kind: 'assistant_text' }> =>\n item.kind === 'assistant_text' && item.id === STREAMING_ID,\n )\n const item: TranscriptItem = {\n kind: 'assistant_text',\n id: STREAMING_ID,\n text: (existing?.text ?? '') + (delta.delta.text ?? ''),\n streaming: true,\n parentToolUseId: event.parentToolUseId,\n }\n return { ...base, items: upsert(base.items, item) }\n }\n if (delta.delta?.type === 'thinking_delta') {\n const existing = base.items.find(\n (item): item is Extract<TranscriptItem, { kind: 'thinking' }> =>\n item.kind === 'thinking' && item.id === STREAMING_THINKING_ID,\n )\n const item: TranscriptItem = {\n kind: 'thinking',\n id: STREAMING_THINKING_ID,\n text: (existing?.text ?? '') + (delta.delta.thinking ?? ''),\n parentToolUseId: event.parentToolUseId,\n }\n return { ...base, items: upsert(base.items, item) }\n }\n return base\n }\n\n case 'turn_result':\n return {\n ...base,\n // total_cost_usd is session-cumulative on each SDK result message.\n totalCostUsd: event.totalCostUsd,\n items: [\n ...base.items,\n {\n kind: 'turn_result',\n id: `turn-${event.seq}`,\n subtype: event.subtype,\n isError: event.isError,\n durationMs: event.durationMs,\n totalCostUsd: event.totalCostUsd,\n errors: event.errors,\n },\n ],\n }\n\n case 'permission_requested':\n return { ...base, pendingApprovals: [...base.pendingApprovals, event.request] }\n\n case 'permission_resolved':\n return {\n ...base,\n pendingApprovals: base.pendingApprovals.filter((r) => r.id !== event.requestId),\n }\n\n // Execution lifecycle for tool calls that run outside the model loop\n // (bridged to this client, queued, or deferred). Keyed by executionId, which\n // equals the tool_use id for calls the model made. Events for an unknown id\n // are ignored rather than fabricating an item: the tool_use that explains it\n // may simply not have arrived (or belongs to another session).\n case 'execution_dispatched':\n return {\n ...base,\n items: base.items.map((item) =>\n item.kind === 'tool_call' && item.id === event.executionId\n ? {\n ...item,\n status: event.deferred ? 'deferred' : 'pending',\n executionId: event.executionId,\n backend: event.backend,\n }\n : item,\n ),\n }\n\n case 'execution_result':\n return {\n ...base,\n items: base.items.map((item) =>\n item.kind === 'tool_call' && item.id === event.executionId\n ? {\n ...item,\n status: 'settled',\n executionId: event.executionId,\n result: { text: outputText(event.output), isError: false },\n logs: event.logs ?? item.logs,\n }\n : item,\n ),\n }\n\n case 'execution_failed':\n return {\n ...base,\n items: base.items.map((item) =>\n item.kind === 'tool_call' && item.id === event.executionId\n ? {\n ...item,\n status: 'failed',\n executionId: event.executionId,\n result: { text: `${event.reason}: ${event.error}`, isError: true },\n logs: event.logs ?? item.logs,\n }\n : item,\n ),\n }\n\n case 'file_delivered':\n return {\n ...base,\n items: [\n ...base.items,\n {\n kind: 'file_delivered',\n id: `file-${event.seq}`,\n path: event.path,\n bytes: event.bytes,\n description: event.description,\n },\n ],\n }\n\n case 'session_error':\n return {\n ...base,\n items: [\n ...base.items,\n { kind: 'notice', id: `err-${event.seq}`, level: 'error', text: event.message },\n ],\n }\n\n case 'session_closed':\n return {\n ...base,\n items: [\n ...base.items,\n {\n kind: 'notice',\n id: `closed-${event.seq}`,\n level: 'info',\n text: `Session closed (${event.reason})`,\n },\n ],\n }\n\n case 'sdk_event':\n default:\n return base\n }\n}\n","import { useEffect, useMemo, useReducer, useRef, useState } from 'react'\nimport type { WorkerDeckClient, SessionHandle } from '@workerdeck/client'\nimport type { AttachedFrame, PermissionMode, SessionEvent } from '@workerdeck/protocol'\nimport {\n applyEvent,\n initialTranscriptState,\n seedFromSessionInfo,\n type TranscriptState,\n} from './transcript.ts'\n\n/** Session events drive the reducer; the attach snapshot seeds fields (permission\n * mode, model) that a promptless session's event stream doesn't carry yet. */\nfunction reduce(state: TranscriptState, action: SessionEvent | AttachedFrame): TranscriptState {\n return action.type === 'attached'\n ? seedFromSessionInfo(state, action.session)\n : applyEvent(state, action)\n}\n\nexport type UseClaudeSessionOptions = {\n /** Called when the server rejects a command with a protocol_error frame — e.g. a\n * permission-mode switch the CLI refuses. Without a handler these are dropped\n * silently and the UI looks like \"nothing happened\". */\n onProtocolError?: (message: string) => void\n}\n\nexport type UseClaudeSessionResult = {\n state: TranscriptState\n connected: boolean\n /** The live attach handle, for wiring companions that must ride the SAME\n * socket — e.g. useToolCallHost: the bridge asks the first attached client,\n * so a host on a second handle would never see the requests. Undefined until\n * attached and after unmount. */\n handle: SessionHandle | undefined\n /** Attachment ids come from `client.uploadAttachment`, in send order. */\n send: (text: string, attachmentIds?: string[]) => void\n approve: (requestId: string, updatedInput?: Record<string, unknown>) => void\n deny: (requestId: string, message?: string) => void\n interrupt: () => void\n setPermissionMode: (mode: PermissionMode) => void\n setModel: (model?: string) => void\n closeSession: () => void\n}\n\n/** Attach to a session and maintain live transcript state. Detaches on unmount. */\nexport function useClaudeSession(\n client: WorkerDeckClient,\n sessionId: string | undefined,\n options?: UseClaudeSessionOptions,\n): UseClaudeSessionResult {\n const [state, dispatch] = useReducer(reduce, initialTranscriptState)\n const [connected, setConnected] = useState(false)\n // Ref for the stable callbacks below; state so consumers of `handle` re-render\n // when the socket opens or the session switches.\n const [handleState, setHandleState] = useState<SessionHandle | undefined>()\n const handleRef = useRef<SessionHandle | null>(null)\n // Ref'd so a new inline callback doesn't tear down and reopen the socket.\n const onProtocolErrorRef = useRef(options?.onProtocolError)\n onProtocolErrorRef.current = options?.onProtocolError\n\n useEffect(() => {\n if (!sessionId) return\n const handle = client.attach(sessionId)\n handleRef.current = handle\n setHandleState(handle)\n const offEvent = handle.on('event', (event: SessionEvent) => dispatch(event))\n const offAttached = handle.on('attached', (frame: AttachedFrame) => dispatch(frame))\n const offConn = handle.on('connectionChange', setConnected)\n const offProtocolError = handle.on('protocolError', (message: string) => {\n onProtocolErrorRef.current?.(message)\n })\n return () => {\n offEvent()\n offAttached()\n offConn()\n offProtocolError()\n handle.detach()\n handleRef.current = null\n setHandleState(undefined)\n }\n }, [client, sessionId])\n\n return useMemo(\n () => ({\n state,\n connected,\n handle: handleState,\n send: (text, attachmentIds) => handleRef.current?.send(text, attachmentIds),\n approve: (requestId, updatedInput) => handleRef.current?.approve(requestId, updatedInput),\n deny: (requestId, message) => handleRef.current?.deny(requestId, message),\n interrupt: () => handleRef.current?.interrupt(),\n setPermissionMode: (mode) => handleRef.current?.setPermissionMode(mode),\n setModel: (model) => handleRef.current?.setModel(model),\n closeSession: () => handleRef.current?.closeSession(),\n }),\n [state, connected, handleState],\n )\n}\n","import type { SessionHandle } from '@workerdeck/client'\nimport type { RunScriptResult, SandboxEngine, SandboxVfs } from '@workerdeck/sandbox'\nimport type { ToolCallRequestFrame } from '@workerdeck/protocol'\n\n/** What the host was asked to do and how it went (for UI/telemetry). */\nexport type ToolHostExecution = {\n executionId: string\n toolName: string\n status: 'running' | 'settled' | 'failed' | 'canceled'\n reason?: string\n startedAt: number\n endedAt?: number\n}\n\nexport type ToolHostRunner = (request: {\n script: string\n vfs: SandboxVfs\n timeoutMs: number\n memoryLimitBytes: number\n signal: AbortSignal\n}) => Promise<RunScriptResult>\n\nexport type ToolCallHostOptions = {\n /** Tools this client will execute. Anything else is refused, so a server can\n * never talk this tab into running something it didn't opt into.\n * Default: `['eval_script']`. */\n tools?: string[]\n /** Guest wall-clock limit, unless the request asks for less. Default 5000. */\n timeoutMs?: number\n /** Guest allocator cap, unless the request asks for less. Default 64 MiB. */\n memoryLimitBytes?: number\n /**\n * Load the WASM guest engine. Called at most once, on the first bridged call\n * — nothing is downloaded or parsed until a session actually bridges one.\n * Defaults to `@workerdeck/sandbox` with the single-file browser build.\n */\n loadEngine?: () => Promise<SandboxEngine>\n /**\n * Run the script. Defaults to executing on this thread, which is fine for the\n * short, time-boxed evaluations this is built for. Supply your own (a Web\n * Worker running the same engine) to keep long evaluations off the UI thread\n * — the guest deadline preempts the interpreter, but only between bytecode\n * ops on whichever thread it runs on.\n */\n execute?: ToolHostRunner\n /** Host-gated fetch for the guest. Omitted = the guest has no network at all. */\n fetchText?: (url: string) => Promise<string>\n /** Observe executions (rendering, logging). */\n onExecution?: (execution: ToolHostExecution) => void\n}\n\n/**\n * Answers server-bridged tool calls by executing them in this browser tab.\n * Framework-free — {@link useToolCallHost} is a thin React wrapper.\n *\n * The point is data locality: documents fetched or held client-side can be\n * evaluated here and never touch the server. The engine loads lazily, so a page\n * that never bridges a call never pays for the WASM guest.\n */\nexport function createToolCallHost(\n handle: SessionHandle,\n options: ToolCallHostOptions = {},\n): { dispose: () => void } {\n const inFlight = new Map<string, AbortController>()\n let enginePromise: Promise<SandboxEngine> | undefined\n let disposed = false\n\n const track = (execution: ToolHostExecution) => options.onExecution?.(execution)\n\n const refuse = (frame: ToolCallRequestFrame, reason: string, error: string, startedAt: number) => {\n handle.sendToolCallError(frame.executionId, reason, error)\n track({\n executionId: frame.executionId,\n toolName: frame.toolName,\n status: 'failed',\n reason,\n startedAt,\n endedAt: Date.now(),\n })\n }\n\n const run = async (frame: ToolCallRequestFrame): Promise<void> => {\n const startedAt = Date.now()\n const allowed = options.tools ?? ['eval_script']\n if (!allowed.includes(frame.toolName)) {\n refuse(frame, 'unsupported_tool', `this client does not execute '${frame.toolName}'`, startedAt)\n return\n }\n const script = (frame.input as { script?: unknown } | undefined)?.script\n if (typeof script !== 'string') {\n refuse(frame, 'invalid_input', 'expected a string `script` input', startedAt)\n return\n }\n\n const controller = new AbortController()\n inFlight.set(frame.executionId, controller)\n track({ executionId: frame.executionId, toolName: frame.toolName, status: 'running', startedAt })\n\n try {\n const sandbox = await import('@workerdeck/sandbox')\n const vfs = sandbox.createVfs(frame.vfsSeed)\n // Never exceed what the server asked for: it owns the deadline it will\n // give up at, and answering after that is wasted work.\n const timeoutMs = Math.min(\n frame.limits?.timeoutMs ?? Number.POSITIVE_INFINITY,\n options.timeoutMs ?? 5000,\n )\n const memoryLimitBytes = Math.min(\n frame.limits?.memoryLimitBytes ?? Number.POSITIVE_INFINITY,\n options.memoryLimitBytes ?? 64 * 1024 * 1024,\n )\n\n const result = options.execute\n ? await options.execute({ script, vfs, timeoutMs, memoryLimitBytes, signal: controller.signal })\n : await (async () => {\n enginePromise ??= (options.loadEngine ?? defaultLoadEngine)()\n return sandbox.runScript(await enginePromise, {\n script,\n vfs,\n timeoutMs,\n memoryLimitBytes,\n signal: controller.signal,\n fetchText: options.fetchText,\n })\n })()\n\n // Cancelled or torn down while we worked: the server is no longer waiting.\n if (disposed || !inFlight.has(frame.executionId)) return\n const logs = result.logs.map((l) => `[${l.level}] ${l.text}`)\n if (result.ok) {\n handle.sendToolCallResult(frame.executionId, { type: 'json', value: result.value }, logs)\n track({\n executionId: frame.executionId,\n toolName: frame.toolName,\n status: 'settled',\n startedAt,\n endedAt: Date.now(),\n })\n } else {\n handle.sendToolCallError(frame.executionId, result.reason, result.error, logs)\n track({\n executionId: frame.executionId,\n toolName: frame.toolName,\n status: 'failed',\n reason: result.reason,\n startedAt,\n endedAt: Date.now(),\n })\n }\n } catch (error) {\n if (disposed || !inFlight.has(frame.executionId)) return\n // Engine load failures land here — tell the server so the agent can adapt\n // instead of waiting out the deadline.\n refuse(frame, 'host_error', error instanceof Error ? error.message : String(error), startedAt)\n } finally {\n inFlight.delete(frame.executionId)\n }\n }\n\n const offRequest = handle.on('toolCallRequest', (frame) => void run(frame))\n const offCancel = handle.on('toolCallCanceled', ({ executionId, reason }) => {\n const controller = inFlight.get(executionId)\n if (!controller) return\n controller.abort()\n inFlight.delete(executionId)\n track({\n executionId,\n toolName: '',\n status: 'canceled',\n reason,\n startedAt: Date.now(),\n endedAt: Date.now(),\n })\n })\n\n return {\n dispose: () => {\n disposed = true\n offRequest()\n offCancel()\n for (const controller of inFlight.values()) controller.abort()\n inFlight.clear()\n },\n }\n}\n\n/** The single-file browser build keeps this to one lazy chunk — no separate\n * .wasm fetch, and nothing at all until the first bridged call. */\nasync function defaultLoadEngine(): Promise<SandboxEngine> {\n const [sandbox, variant] = await Promise.all([\n import('@workerdeck/sandbox'),\n import('@jitl/quickjs-singlefile-browser-release-asyncify'),\n ])\n return sandbox.loadEngine(variant as never)\n}\n","import { useEffect, useRef, useState } from 'react'\nimport type { SessionHandle } from '@workerdeck/client'\nimport {\n createToolCallHost,\n type ToolCallHostOptions,\n type ToolHostExecution,\n} from './tool-host.ts'\n\nexport type UseToolCallHostOptions = ToolCallHostOptions & {\n /** Turn the host off without unmounting. Default true. */\n enabled?: boolean\n /** How many recent executions to keep for rendering. Default 50. */\n historyLimit?: number\n}\n\n/**\n * React wrapper around {@link createToolCallHost}: subscribes while mounted and\n * exposes recent executions for rendering. All the logic lives in the\n * framework-free host — this only manages the subscription's lifetime.\n */\nexport function useToolCallHost(\n handle: SessionHandle | undefined,\n options: UseToolCallHostOptions = {},\n): { executions: ToolHostExecution[] } {\n const [executions, setExecutions] = useState<ToolHostExecution[]>([])\n // Read options at call time so re-renders never tear down the subscription.\n const optionsRef = useRef(options)\n optionsRef.current = options\n\n useEffect(() => {\n if (!handle || options.enabled === false) return\n const host = createToolCallHost(handle, {\n // Delegate every option through the ref, so a caller passing inline\n // objects/closures (the common case) doesn't resubscribe each render.\n get tools() {\n return optionsRef.current.tools\n },\n get timeoutMs() {\n return optionsRef.current.timeoutMs\n },\n get memoryLimitBytes() {\n return optionsRef.current.memoryLimitBytes\n },\n get loadEngine() {\n return optionsRef.current.loadEngine\n },\n get execute() {\n return optionsRef.current.execute\n },\n get fetchText() {\n return optionsRef.current.fetchText\n },\n onExecution: (execution) => {\n optionsRef.current.onExecution?.(execution)\n const limit = optionsRef.current.historyLimit ?? 50\n setExecutions((prev) => [\n ...prev.filter((e) => e.executionId !== execution.executionId),\n execution,\n ].slice(-limit))\n },\n })\n return () => host.dispose()\n }, [handle, options.enabled])\n\n return { executions }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workerdeck/react",
3
- "version": "0.6.0",
3
+ "version": "0.9.0",
4
4
  "type": "module",
5
5
  "description": "Headless React layer for WorkerDeck: useClaudeSession hook + pure transcript reducer. No styling opinion — @workerdeck/ui is the styled layer on top.",
6
6
  "license": "MIT",
@@ -17,9 +17,9 @@
17
17
  }
18
18
  },
19
19
  "dependencies": {
20
- "@workerdeck/client": "0.6.0",
21
- "@workerdeck/sandbox": "0.6.0",
22
- "@workerdeck/protocol": "0.6.0"
20
+ "@workerdeck/client": "0.9.0",
21
+ "@workerdeck/sandbox": "0.9.0",
22
+ "@workerdeck/protocol": "0.9.0"
23
23
  },
24
24
  "peerDependencies": {
25
25
  "react": "^18.0.0 || ^19.0.0",
@@ -41,8 +41,8 @@
41
41
  "tsdown": "^0.21.10",
42
42
  "vitest": "^3.2.0",
43
43
  "ws": "^8.21.1",
44
- "@workerdeck/core": "0.6.0",
45
- "@workerdeck/server": "0.6.0"
44
+ "@workerdeck/core": "0.9.0",
45
+ "@workerdeck/server": "0.9.0"
46
46
  },
47
47
  "author": "Tobias Strebitzer",
48
48
  "repository": {