@llui/agent 0.0.31 → 0.0.34

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.
Files changed (72) hide show
  1. package/README.md +82 -1
  2. package/dist/client/agentConfirm.d.ts +48 -18
  3. package/dist/client/agentConfirm.d.ts.map +1 -1
  4. package/dist/client/agentConfirm.js +28 -25
  5. package/dist/client/agentConfirm.js.map +1 -1
  6. package/dist/client/agentConnect.d.ts +95 -34
  7. package/dist/client/agentConnect.d.ts.map +1 -1
  8. package/dist/client/agentConnect.js +81 -47
  9. package/dist/client/agentConnect.js.map +1 -1
  10. package/dist/client/agentLog.d.ts +31 -14
  11. package/dist/client/agentLog.d.ts.map +1 -1
  12. package/dist/client/agentLog.js +39 -20
  13. package/dist/client/agentLog.js.map +1 -1
  14. package/dist/client/effect-handler.d.ts +23 -0
  15. package/dist/client/effect-handler.d.ts.map +1 -1
  16. package/dist/client/effect-handler.js +185 -126
  17. package/dist/client/effect-handler.js.map +1 -1
  18. package/dist/client/effects.d.ts +13 -2
  19. package/dist/client/effects.d.ts.map +1 -1
  20. package/dist/client/effects.js.map +1 -1
  21. package/dist/client/factory.d.ts +55 -3
  22. package/dist/client/factory.d.ts.map +1 -1
  23. package/dist/client/factory.js +30 -5
  24. package/dist/client/factory.js.map +1 -1
  25. package/dist/client/rpc/describe-visible-content.d.ts +18 -5
  26. package/dist/client/rpc/describe-visible-content.d.ts.map +1 -1
  27. package/dist/client/rpc/describe-visible-content.js +112 -7
  28. package/dist/client/rpc/describe-visible-content.js.map +1 -1
  29. package/dist/client/rpc/list-actions.d.ts +52 -2
  30. package/dist/client/rpc/list-actions.d.ts.map +1 -1
  31. package/dist/client/rpc/list-actions.js +187 -5
  32. package/dist/client/rpc/list-actions.js.map +1 -1
  33. package/dist/client/rpc/query-state.d.ts +32 -0
  34. package/dist/client/rpc/query-state.d.ts.map +1 -0
  35. package/dist/client/rpc/query-state.js +82 -0
  36. package/dist/client/rpc/query-state.js.map +1 -0
  37. package/dist/client/rpc/send-message.d.ts +2 -0
  38. package/dist/client/rpc/send-message.d.ts.map +1 -1
  39. package/dist/client/rpc/send-message.js +119 -9
  40. package/dist/client/rpc/send-message.js.map +1 -1
  41. package/dist/client/rpc/would-dispatch.d.ts +66 -0
  42. package/dist/client/rpc/would-dispatch.d.ts.map +1 -0
  43. package/dist/client/rpc/would-dispatch.js +21 -0
  44. package/dist/client/rpc/would-dispatch.js.map +1 -0
  45. package/dist/client/ws-client.d.ts +3 -1
  46. package/dist/client/ws-client.d.ts.map +1 -1
  47. package/dist/client/ws-client.js +29 -0
  48. package/dist/client/ws-client.js.map +1 -1
  49. package/dist/codecs.d.ts +107 -0
  50. package/dist/codecs.d.ts.map +1 -0
  51. package/dist/codecs.js +166 -0
  52. package/dist/codecs.js.map +1 -0
  53. package/dist/protocol.d.ts +155 -6
  54. package/dist/protocol.d.ts.map +1 -1
  55. package/dist/protocol.js +7 -1
  56. package/dist/protocol.js.map +1 -1
  57. package/dist/server/lap/forward.d.ts +13 -0
  58. package/dist/server/lap/forward.d.ts.map +1 -1
  59. package/dist/server/lap/forward.js +74 -0
  60. package/dist/server/lap/forward.js.map +1 -1
  61. package/dist/server/lap/router.d.ts.map +1 -1
  62. package/dist/server/lap/router.js +7 -1
  63. package/dist/server/lap/router.js.map +1 -1
  64. package/dist/server/ws/pairing-registry.d.ts +22 -6
  65. package/dist/server/ws/pairing-registry.d.ts.map +1 -1
  66. package/dist/server/ws/pairing-registry.js +49 -0
  67. package/dist/server/ws/pairing-registry.js.map +1 -1
  68. package/dist/state-diff.d.ts +52 -0
  69. package/dist/state-diff.d.ts.map +1 -0
  70. package/dist/state-diff.js +119 -0
  71. package/dist/state-diff.js.map +1 -0
  72. package/package.json +11 -5
@@ -1 +1 @@
1
- {"version":3,"file":"factory.js","sourceRoot":"","sources":["../../src/client/factory.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AppHandle } from '@llui/dom'\nimport type { AgentEffect } from './effects.js'\nimport type { AgentConfirmState } from './agentConfirm.js'\nimport type {\n AgentDocs,\n AgentContext,\n LapDrainMeta,\n MessageAnnotations,\n MessageSchemaEntry,\n} from '../protocol.js'\nimport { attachWsClient, type WsLike, type RpcHosts } from './ws-client.js'\nimport { createEffectHandler } from './effect-handler.js'\n\ntype ComponentMetadata = {\n __msgSchema?: unknown\n __stateSchema?: unknown\n __msgAnnotations?: Record<string, MessageAnnotations>\n __bindingDescriptors?: Array<{ variant: string }>\n __schemaHash?: string\n name: string\n agentAffordances?: (state: unknown) => Array<{ type: string; [k: string]: unknown }>\n agentDocs?: AgentDocs\n agentContext?: (state: unknown) => AgentContext\n}\n\nexport type CreateAgentClientOpts<State, Msg> = {\n handle: AppHandle\n def: ComponentMetadata\n appVersion?: string\n rootElement: Element | null\n slices: {\n getConnect: (s: State) => unknown\n getConfirm: (s: State) => AgentConfirmState\n wrapConnectMsg: (m: unknown) => Msg\n wrapConfirmMsg: (m: unknown) => Msg\n /**\n * Optional: wrap an agentLog msg so the client-side activity feed\n * mirrors what Claude is doing. If omitted, outbound log-append\n * frames still go to the server, but the local agent.log slice\n * stays empty (the UI won't show activity).\n */\n wrapLogMsg?: (m: unknown) => Msg\n }\n}\n\nexport type AgentClient = {\n effectHandler: (effect: AgentEffect) => Promise<void>\n start(): void\n stop(): void\n}\n\nexport function createAgentClient<State, Msg>(\n opts: CreateAgentClientOpts<State, Msg>,\n): AgentClient {\n let ws: WebSocket | null = null\n let wsClient: ReturnType<typeof attachWsClient> | null = null\n let confirmPollTimer: ReturnType<typeof setInterval> | null = null\n let stateSubscription: (() => void) | null = null\n const resolvedConfirms = new Set<string>()\n\n // Drain-error buffer: populated by persistent `window.error` and\n // `window.unhandledrejection` listeners installed on `start()`. The\n // send-message drain loop consumes and clears it per call so the\n // envelope surfaces only errors that fired during that window.\n const drainErrors: LapDrainMeta['errors'] = []\n let errorListenersInstalled = false\n let onErrorEvt: ((e: ErrorEvent) => void) | null = null\n let onRejectionEvt: ((e: PromiseRejectionEvent) => void) | null = null\n\n function installErrorListeners(): void {\n if (errorListenersInstalled) return\n if (typeof window === 'undefined') return\n onErrorEvt = (e: ErrorEvent) => {\n drainErrors.push({\n kind: 'error',\n message: e.message ?? String(e.error ?? 'unknown error'),\n stack: e.error instanceof Error ? e.error.stack : undefined,\n })\n }\n onRejectionEvt = (e: PromiseRejectionEvent) => {\n const r = e.reason\n const message = r instanceof Error ? r.message : typeof r === 'string' ? r : safeStringify(r)\n drainErrors.push({\n kind: 'unhandledrejection',\n message,\n stack: r instanceof Error ? r.stack : undefined,\n })\n }\n window.addEventListener('error', onErrorEvt)\n window.addEventListener('unhandledrejection', onRejectionEvt)\n errorListenersInstalled = true\n }\n\n function removeErrorListeners(): void {\n if (!errorListenersInstalled) return\n if (typeof window === 'undefined') return\n if (onErrorEvt) window.removeEventListener('error', onErrorEvt)\n if (onRejectionEvt) window.removeEventListener('unhandledrejection', onRejectionEvt)\n onErrorEvt = null\n onRejectionEvt = null\n errorListenersInstalled = false\n }\n\n const rpcHost: RpcHosts = {\n getState: () => opts.handle.getState(),\n send: (m) => opts.handle.send(m),\n flush: () => opts.handle.flush(),\n subscribe: (listener) => opts.handle.subscribe(() => listener()),\n getAndClearDrainErrors: () => drainErrors.splice(0, drainErrors.length),\n getMsgAnnotations: () => opts.def.__msgAnnotations ?? null,\n getBindingDescriptors: () => opts.def.__bindingDescriptors ?? null,\n getAgentAffordances: () => opts.def.agentAffordances ?? null,\n getAgentContext: () => opts.def.agentContext ?? null,\n getRootElement: () => opts.rootElement,\n proposeConfirm: (entry) => {\n opts.handle.send(opts.slices.wrapConfirmMsg({ type: 'Propose', entry }))\n },\n }\n\n const helloBuilder = () => ({\n t: 'hello' as const,\n appName: opts.def.name,\n appVersion: opts.appVersion ?? '0.0.0',\n msgSchema: (opts.def.__msgSchema ?? {}) as Record<string, MessageSchemaEntry>,\n stateSchema: (opts.def.__stateSchema ?? {}) as object,\n affordancesSample: opts.def.agentAffordances\n ? opts.def.agentAffordances(opts.handle.getState())\n : [],\n docs: opts.def.agentDocs ?? null,\n schemaHash: opts.def.__schemaHash ?? '',\n })\n\n const effectHandler = createEffectHandler({\n send: (m) => opts.handle.send(m),\n wrapAgentConnect: (m) => opts.slices.wrapConnectMsg(m),\n forward: (payload) => opts.handle.send(payload),\n openWs: (token, wsUrl) => {\n if (ws) ws.close()\n ws = new WebSocket(`${wsUrl}?token=${encodeURIComponent(token)}`)\n wsClient = attachWsClient(ws as unknown as WsLike, rpcHost, helloBuilder, {\n onActivated: () => {\n opts.handle.send(opts.slices.wrapConnectMsg({ type: 'ActivatedByClaude' }))\n },\n onLogEntry: opts.slices.wrapLogMsg\n ? (entry) => {\n opts.handle.send(opts.slices.wrapLogMsg!({ type: 'Append', entry }))\n }\n : undefined,\n })\n ws.addEventListener('open', () => {\n opts.handle.send(opts.slices.wrapConnectMsg({ type: 'WsOpened' }))\n })\n ws.addEventListener('close', () => {\n opts.handle.send(opts.slices.wrapConnectMsg({ type: 'WsClosed' }))\n })\n },\n closeWs: () => {\n wsClient?.close()\n ws = null\n wsClient = null\n },\n })\n\n const pollConfirms = () => {\n const state = opts.handle.getState() as State\n const confirm = opts.slices.getConfirm(state)\n for (const entry of confirm.pending) {\n if (entry.status === 'pending') continue\n if (resolvedConfirms.has(entry.id)) continue\n resolvedConfirms.add(entry.id)\n if (entry.status === 'approved') {\n wsClient?.resolveConfirm(entry.id, 'confirmed', opts.handle.getState())\n } else if (entry.status === 'rejected') {\n wsClient?.resolveConfirm(entry.id, 'user-cancelled')\n }\n }\n }\n\n return {\n effectHandler,\n start() {\n if (!confirmPollTimer) confirmPollTimer = setInterval(pollConfirms, 200)\n if (!stateSubscription) {\n stateSubscription = opts.handle.subscribe((state) => {\n wsClient?.emitStateUpdate('/', state)\n })\n }\n installErrorListeners()\n },\n stop() {\n if (confirmPollTimer) clearInterval(confirmPollTimer)\n confirmPollTimer = null\n if (stateSubscription) {\n stateSubscription()\n stateSubscription = null\n }\n removeErrorListeners()\n drainErrors.length = 0\n wsClient?.close()\n },\n }\n}\n\nfunction safeStringify(v: unknown): string {\n try {\n return JSON.stringify(v)\n } catch {\n return String(v)\n }\n}\n"]}
1
+ {"version":3,"file":"factory.js","sourceRoot":"","sources":["../../src/client/factory.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AppHandle } from '@llui/dom'\nimport type { AgentEffect } from './effects.js'\nimport type { AgentConfirmState } from './agentConfirm.js'\nimport type {\n AgentDocs,\n AgentContext,\n LapDrainMeta,\n MessageAnnotations,\n MessageSchemaEntry,\n} from '../protocol.js'\nimport { attachWsClient, type WsLike, type RpcHosts } from './ws-client.js'\nimport { createEffectHandler } from './effect-handler.js'\nimport { makeDefaultCodecs, encodeForWire, decodeFromWire, type CodecRegistry } from '../codecs.js'\n\n/**\n * The shape the compiler emits as `__msgSchema`. Mirrors `MsgField`\n * from `@llui/vite-plugin/src/msg-schema.ts`. Three coexisting forms:\n *\n * 1. Bare primitive: `'string' | 'number' | 'boolean' | 'unknown'`\n * and bare enum: `{enum: [...]}`. Compact form for unannotated\n * required fields.\n * 2. Bare nested types: `{kind: 'object', shape}` for inline /\n * followed-via-typeIndex shapes; `{kind: 'array', element}` for\n * `T[]` / `readonly T[]` / `Array<T>`. The synthesizer recurses\n * to build copy-paste-ready nested examples.\n * 3. Rich descriptor: wraps any of the above with `{optional?,\n * priority?, hint?}` carrying TS optionality and `@should` hints.\n */\nexport type MsgSchemaBareType =\n | string\n | { enum: string[] }\n | { kind: 'object'; shape: Record<string, MsgSchemaField> }\n | { kind: 'array'; element: MsgSchemaBareType }\n\nexport type MsgSchemaField =\n | MsgSchemaBareType\n | {\n type: MsgSchemaBareType\n optional?: boolean\n priority?: 'should'\n hint?: string\n }\n\nexport type MsgSchemaShape = {\n discriminant: string\n variants: Record<string, Record<string, MsgSchemaField>>\n}\n\ntype ComponentMetadata = {\n __msgSchema?: unknown\n __stateSchema?: unknown\n __msgAnnotations?: Record<string, MessageAnnotations>\n __schemaHash?: string\n name: string\n agentAffordances?: (state: unknown) => Array<{ type: string; [k: string]: unknown }>\n agentDocs?: AgentDocs\n agentContext?: (state: unknown) => AgentContext\n}\n\nexport type CreateAgentClientOpts<State, Msg> = {\n handle: AppHandle\n def: ComponentMetadata\n appVersion?: string\n rootElement: Element | null\n slices: {\n getConnect: (s: State) => unknown\n getConfirm: (s: State) => AgentConfirmState\n wrapConnectMsg: (m: unknown) => Msg\n wrapConfirmMsg: (m: unknown) => Msg\n /**\n * Optional: wrap an agentLog msg so the client-side activity feed\n * mirrors what Claude is doing. If omitted, outbound log-append\n * frames still go to the server, but the local agent.log slice\n * stays empty (the UI won't show activity).\n */\n wrapLogMsg?: (m: unknown) => Msg\n }\n /**\n * Codec registry for non-JSON-safe values (Date, Blob, Map, …)\n * crossing the LAP boundary. Defaults to `makeDefaultCodecs()`\n * which ships `iso-date` and `epoch-millis`. Provide a custom\n * registry to register additional codecs (e.g. `base64-blob` for\n * file uploads). See `@llui/agent/codecs` for the convention.\n */\n codecs?: CodecRegistry\n /**\n * Base path for agent HTTP endpoints. Default: `'/agent'` (matches\n * the canonical paths in `@llui/vite-plugin`'s dev middleware and\n * `@llui/agent/server`). The mint URL, resume URLs, and revoke URL\n * derive from this so consumers don't have to keep them in sync.\n *\n * Override when:\n * - **Cross-origin agent server**: pass the full base, e.g.\n * `'https://api.example.com/agent'` or `'http://localhost:8787/agent'`.\n * - **`@cloudflare/vite-plugin` in dev**: pass `'/cdn-cgi/agent'`\n * because cloudflare-vite shadows non-`/cdn-cgi/*` routes.\n */\n agentBasePath?: string\n}\n\nexport type AgentClient = {\n effectHandler: (effect: AgentEffect) => Promise<void>\n start(): void\n stop(): void\n}\n\nexport function createAgentClient<State, Msg>(\n opts: CreateAgentClientOpts<State, Msg>,\n): AgentClient {\n let ws: WebSocket | null = null\n let wsClient: ReturnType<typeof attachWsClient> | null = null\n let confirmPollTimer: ReturnType<typeof setInterval> | null = null\n let stateSubscription: (() => void) | null = null\n const resolvedConfirms = new Set<string>()\n\n // Drain-error buffer: populated by persistent `window.error` and\n // `window.unhandledrejection` listeners installed on `start()`. The\n // send-message drain loop consumes and clears it per call so the\n // envelope surfaces only errors that fired during that window.\n const drainErrors: LapDrainMeta['errors'] = []\n let errorListenersInstalled = false\n let onErrorEvt: ((e: ErrorEvent) => void) | null = null\n let onRejectionEvt: ((e: PromiseRejectionEvent) => void) | null = null\n\n function installErrorListeners(): void {\n if (errorListenersInstalled) return\n if (typeof window === 'undefined') return\n onErrorEvt = (e: ErrorEvent) => {\n drainErrors.push({\n kind: 'error',\n message: e.message ?? String(e.error ?? 'unknown error'),\n stack: e.error instanceof Error ? e.error.stack : undefined,\n })\n }\n onRejectionEvt = (e: PromiseRejectionEvent) => {\n const r = e.reason\n const message = r instanceof Error ? r.message : typeof r === 'string' ? r : safeStringify(r)\n drainErrors.push({\n kind: 'unhandledrejection',\n message,\n stack: r instanceof Error ? r.stack : undefined,\n })\n }\n window.addEventListener('error', onErrorEvt)\n window.addEventListener('unhandledrejection', onRejectionEvt)\n errorListenersInstalled = true\n }\n\n function removeErrorListeners(): void {\n if (!errorListenersInstalled) return\n if (typeof window === 'undefined') return\n if (onErrorEvt) window.removeEventListener('error', onErrorEvt)\n if (onRejectionEvt) window.removeEventListener('unhandledrejection', onRejectionEvt)\n onErrorEvt = null\n onRejectionEvt = null\n errorListenersInstalled = false\n }\n\n // Codec registry handles non-JSON-safe values (Date, etc.) crossing\n // the LAP boundary. `getState` encodes outgoing snapshots; `send`\n // decodes incoming agent messages before they hit the reducer. The\n // tagged-value convention is documented in `@llui/agent/codecs`.\n const codecs = opts.codecs ?? makeDefaultCodecs()\n\n const rpcHost: RpcHosts = {\n getState: () => encodeForWire(opts.handle.getState(), codecs),\n send: (m) => opts.handle.send(decodeFromWire(m, codecs)),\n flush: () => opts.handle.flush(),\n subscribe: (listener) => opts.handle.subscribe(() => listener()),\n getAndClearDrainErrors: () => drainErrors.splice(0, drainErrors.length),\n getMsgAnnotations: () => opts.def.__msgAnnotations ?? null,\n // The compiler-injected message schema. Used by `list_actions` to\n // synthesize payload examples for `@agentOnly` variants that have\n // no live UI binding — the agent should still see them as\n // affordances even though no human can click them.\n getMsgSchema: () => (opts.def.__msgSchema as MsgSchemaShape | undefined) ?? null,\n // Run the reducer in isolation for `would_dispatch`. Wraps the\n // AppHandle's same-named method so the host doesn't need a direct\n // reference to the live ComponentInstance.\n runReducer: (msg) => opts.handle.runReducer(msg),\n // Live binding descriptors: read from the runtime registry that\n // tracks which Msg variants are dispatchable from currently-mounted\n // event handlers. Empty array when the app wasn't compiled with\n // agent metadata (no tagger pass) or has no view bindings yet —\n // both produce the same \"no live affordances\" signal at the agent\n // layer.\n getBindingDescriptors: () => opts.handle.getBindingDescriptors(),\n getAgentAffordances: () => opts.def.agentAffordances ?? null,\n getAgentContext: () => opts.def.agentContext ?? null,\n getRootElement: () => opts.rootElement,\n proposeConfirm: (entry) => {\n opts.handle.send(opts.slices.wrapConfirmMsg({ type: 'Propose', entry }))\n },\n }\n\n const helloBuilder = () => ({\n t: 'hello' as const,\n appName: opts.def.name,\n appVersion: opts.appVersion ?? '0.0.0',\n msgSchema: (opts.def.__msgSchema ?? {}) as Record<string, MessageSchemaEntry>,\n stateSchema: (opts.def.__stateSchema ?? {}) as object,\n affordancesSample: opts.def.agentAffordances\n ? opts.def.agentAffordances(opts.handle.getState())\n : [],\n docs: opts.def.agentDocs ?? null,\n schemaHash: opts.def.__schemaHash ?? '',\n })\n\n const effectHandler = createEffectHandler({\n send: (m) => opts.handle.send(m),\n wrapAgentConnect: (m) => opts.slices.wrapConnectMsg(m),\n forward: (payload) => opts.handle.send(payload),\n agentBasePath: opts.agentBasePath,\n openWs: (token, wsUrl) => {\n if (ws) ws.close()\n ws = new WebSocket(`${wsUrl}?token=${encodeURIComponent(token)}`)\n wsClient = attachWsClient(ws as unknown as WsLike, rpcHost, helloBuilder, {\n onActivated: () => {\n opts.handle.send(opts.slices.wrapConnectMsg({ type: 'ActivatedByClaude' }))\n },\n onLogEntry: opts.slices.wrapLogMsg\n ? (entry) => {\n opts.handle.send(opts.slices.wrapLogMsg!({ type: 'Append', entry }))\n }\n : undefined,\n })\n ws.addEventListener('open', () => {\n opts.handle.send(opts.slices.wrapConnectMsg({ type: 'WsOpened' }))\n })\n ws.addEventListener('close', () => {\n opts.handle.send(opts.slices.wrapConnectMsg({ type: 'WsClosed' }))\n })\n },\n closeWs: () => {\n wsClient?.close()\n ws = null\n wsClient = null\n },\n })\n\n const pollConfirms = () => {\n const state = opts.handle.getState() as State\n const confirm = opts.slices.getConfirm(state)\n for (const entry of confirm.pending) {\n if (entry.status === 'pending') continue\n if (resolvedConfirms.has(entry.id)) continue\n resolvedConfirms.add(entry.id)\n if (entry.status === 'approved') {\n wsClient?.resolveConfirm(\n entry.id,\n 'confirmed',\n encodeForWire(opts.handle.getState(), codecs),\n )\n } else if (entry.status === 'rejected') {\n wsClient?.resolveConfirm(entry.id, 'user-cancelled')\n }\n }\n }\n\n return {\n effectHandler,\n start() {\n if (!confirmPollTimer) confirmPollTimer = setInterval(pollConfirms, 200)\n if (!stateSubscription) {\n stateSubscription = opts.handle.subscribe((state) => {\n // Same codec convention as `getState`: outgoing snapshots\n // pass through the encoder so non-JSON-safe values (Date,\n // etc.) become tagged-wire form.\n wsClient?.emitStateUpdate('/', encodeForWire(state, codecs))\n })\n }\n installErrorListeners()\n },\n stop() {\n if (confirmPollTimer) clearInterval(confirmPollTimer)\n confirmPollTimer = null\n if (stateSubscription) {\n stateSubscription()\n stateSubscription = null\n }\n removeErrorListeners()\n drainErrors.length = 0\n wsClient?.close()\n },\n }\n}\n\nfunction safeStringify(v: unknown): string {\n try {\n return JSON.stringify(v)\n } catch {\n return String(v)\n }\n}\n"]}
@@ -1,22 +1,35 @@
1
- import type { OutlineNode } from '../../protocol.js';
1
+ import type { MessageAnnotations, OutlineNode } from '../../protocol.js';
2
2
  export type DescribeVisibleArgs = Record<string, never>;
3
3
  export type DescribeVisibleResult = {
4
4
  outline: OutlineNode[];
5
+ /**
6
+ * `'data-agent'` when the outline was scoped to author-tagged zones.
7
+ * `'fallback'` when the app has no `data-agent` attributes and the
8
+ * walker fell back to a generic semantic-element pass over the root.
9
+ * `'truncated'` when the fallback outline hit the node-count cap and
10
+ * stopped early; the caller can ask follow-up questions through
11
+ * `query_dom` or by inspecting state directly.
12
+ */
13
+ source: 'data-agent' | 'fallback' | 'truncated';
5
14
  };
6
15
  export type DescribeVisibleHost = {
7
16
  getRootElement(): Element | null;
8
17
  getBindingDescriptors(): Array<{
9
18
  variant: string;
10
19
  }> | null;
11
- getMsgAnnotations(): Record<string, {
12
- intent: string | null;
13
- humanOnly: boolean;
14
- }> | null;
20
+ getMsgAnnotations(): Record<string, MessageAnnotations> | null;
15
21
  };
16
22
  /**
17
23
  * Walk data-agent-tagged subtrees and produce a structured outline.
18
24
  * Buttons cross-reference __bindingDescriptors so Claude can tie
19
25
  * visible text to variant names.
26
+ *
27
+ * If the app has no `data-agent` tags, fall back to a depth-limited
28
+ * walk of the entire root element. This makes the tool useful for apps
29
+ * that haven't (yet) tagged their views — typical first-pass dogfood
30
+ * targets — instead of returning an empty outline that conveys
31
+ * nothing. The fallback path sets `source: 'fallback'` so the caller
32
+ * can tell the outline is best-effort.
20
33
  */
21
34
  export declare function handleDescribeVisibleContent(host: DescribeVisibleHost): DescribeVisibleResult;
22
35
  //# sourceMappingURL=describe-visible-content.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"describe-visible-content.d.ts","sourceRoot":"","sources":["../../../src/client/rpc/describe-visible-content.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAEpD,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;AACvD,MAAM,MAAM,qBAAqB,GAAG;IAAE,OAAO,EAAE,WAAW,EAAE,CAAA;CAAE,CAAA;AAE9D,MAAM,MAAM,mBAAmB,GAAG;IAChC,cAAc,IAAI,OAAO,GAAG,IAAI,CAAA;IAChC,qBAAqB,IAAI,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG,IAAI,CAAA;IAC1D,iBAAiB,IAAI,MAAM,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC,GAAG,IAAI,CAAA;CAC1F,CAAA;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,mBAAmB,GAAG,qBAAqB,CAa7F"}
1
+ {"version":3,"file":"describe-visible-content.d.ts","sourceRoot":"","sources":["../../../src/client/rpc/describe-visible-content.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAExE,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;AACvD,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,EAAE,WAAW,EAAE,CAAA;IACtB;;;;;;;OAOG;IACH,MAAM,EAAE,YAAY,GAAG,UAAU,GAAG,WAAW,CAAA;CAChD,CAAA;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,cAAc,IAAI,OAAO,GAAG,IAAI,CAAA;IAChC,qBAAqB,IAAI,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG,IAAI,CAAA;IAC1D,iBAAiB,IAAI,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,GAAG,IAAI,CAAA;CAC/D,CAAA;AAcD;;;;;;;;;;;GAWG;AACH,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,mBAAmB,GAAG,qBAAqB,CAqB7F"}
@@ -1,20 +1,46 @@
1
+ /**
2
+ * Hard caps for the fallback walk. The author-tagged path doesn't need
3
+ * caps — by definition the author chose what to expose. The fallback is
4
+ * walking arbitrary DOM, so it has to bound the work.
5
+ *
6
+ * 200 nodes covers ~3–5 visible screens of typical app UI; depth 8 is
7
+ * deep enough for nested content trees but stops us from descending
8
+ * into giant virtualised lists or syntax-highlighted code blocks.
9
+ */
10
+ const FALLBACK_MAX_NODES = 200;
11
+ const FALLBACK_MAX_DEPTH = 8;
1
12
  /**
2
13
  * Walk data-agent-tagged subtrees and produce a structured outline.
3
14
  * Buttons cross-reference __bindingDescriptors so Claude can tie
4
15
  * visible text to variant names.
16
+ *
17
+ * If the app has no `data-agent` tags, fall back to a depth-limited
18
+ * walk of the entire root element. This makes the tool useful for apps
19
+ * that haven't (yet) tagged their views — typical first-pass dogfood
20
+ * targets — instead of returning an empty outline that conveys
21
+ * nothing. The fallback path sets `source: 'fallback'` so the caller
22
+ * can tell the outline is best-effort.
5
23
  */
6
24
  export function handleDescribeVisibleContent(host) {
7
25
  const root = host.getRootElement();
8
26
  if (!root)
9
- return { outline: [] };
10
- const out = [];
27
+ return { outline: [], source: 'data-agent' };
11
28
  const allZones = Array.from(root.querySelectorAll('[data-agent]'));
12
- // Only walk top-level zones; skip zones that are descendants of other zones
13
- const topLevel = allZones.filter((zone) => !allZones.some((other) => other !== zone && other.contains(zone)));
14
- for (const zone of topLevel) {
15
- walk(zone, out);
29
+ if (allZones.length > 0) {
30
+ const out = [];
31
+ // Only walk top-level zones; skip zones that are descendants of other zones
32
+ const topLevel = allZones.filter((zone) => !allZones.some((other) => other !== zone && other.contains(zone)));
33
+ for (const zone of topLevel) {
34
+ walk(zone, out);
35
+ }
36
+ return { outline: out, source: 'data-agent' };
16
37
  }
17
- return { outline: out };
38
+ // Fallback: walk the entire root with caps. Useful for apps without
39
+ // any data-agent annotations — at minimum the agent gets headings,
40
+ // buttons, links, lists, and visible text it can use to orient.
41
+ const out = [];
42
+ const truncated = walkFallback(root, out, 0);
43
+ return { outline: out, source: truncated ? 'truncated' : 'fallback' };
18
44
  }
19
45
  function walk(el, out) {
20
46
  const tag = el.tagName.toLowerCase();
@@ -63,4 +89,83 @@ function walk(el, out) {
63
89
  walk(child, out);
64
90
  }
65
91
  }
92
+ /**
93
+ * Depth- and count-limited walk for the fallback path. Returns true
94
+ * iff the cap was hit (at least one element was skipped). Same node
95
+ * vocabulary as `walk` but prunes more aggressively:
96
+ *
97
+ * - `<a>` without `href`, `<button>` without text — skipped (often
98
+ * icon-only chrome that adds noise without helping the agent).
99
+ * - Nested wrapper divs without semantic content — descended into
100
+ * but not emitted.
101
+ * - Standalone text nodes — only emitted when the parent has no
102
+ * semantic children (preserves the existing `walk` heuristic).
103
+ */
104
+ function walkFallback(el, out, depth) {
105
+ if (out.length >= FALLBACK_MAX_NODES)
106
+ return true;
107
+ if (depth > FALLBACK_MAX_DEPTH)
108
+ return false;
109
+ const tag = el.tagName.toLowerCase();
110
+ // Skip script/style/noscript subtrees — never user-facing content.
111
+ if (tag === 'script' || tag === 'style' || tag === 'noscript' || tag === 'template')
112
+ return false;
113
+ const text = (el.textContent ?? '').trim();
114
+ if (/^h[1-6]$/.test(tag)) {
115
+ out.push({ kind: 'heading', level: Number(tag[1]), text });
116
+ return false;
117
+ }
118
+ if (tag === 'button') {
119
+ if (text.length > 0 || el.getAttribute('aria-label')) {
120
+ out.push({
121
+ kind: 'button',
122
+ text: text || el.getAttribute('aria-label') || '',
123
+ disabled: el.disabled,
124
+ actionVariant: el.getAttribute('data-agent') ?? null,
125
+ });
126
+ }
127
+ return out.length >= FALLBACK_MAX_NODES;
128
+ }
129
+ if (tag === 'a' && el.getAttribute('href')) {
130
+ out.push({ kind: 'link', text, href: el.getAttribute('href') ?? '' });
131
+ return out.length >= FALLBACK_MAX_NODES;
132
+ }
133
+ if (tag === 'input') {
134
+ const inputType = el.type ?? 'text';
135
+ // `hidden` inputs are almost always form-state plumbing; skip them.
136
+ if (inputType !== 'hidden') {
137
+ out.push({
138
+ kind: 'input',
139
+ label: el.getAttribute('aria-label') ?? el.getAttribute('name') ?? null,
140
+ value: el.value ?? null,
141
+ type: inputType,
142
+ });
143
+ }
144
+ return out.length >= FALLBACK_MAX_NODES;
145
+ }
146
+ if (tag === 'ul' || tag === 'ol') {
147
+ const items = [];
148
+ for (const child of Array.from(el.children)) {
149
+ if (child.tagName.toLowerCase() === 'li') {
150
+ const itemText = (child.textContent ?? '').trim();
151
+ if (itemText.length > 0)
152
+ items.push({ kind: 'item', text: itemText });
153
+ }
154
+ }
155
+ if (items.length > 0)
156
+ out.push({ kind: 'list', items });
157
+ return out.length >= FALLBACK_MAX_NODES;
158
+ }
159
+ // Bare text leaf: only emit if the element has a non-empty text and
160
+ // no element children. Same rule as the tagged walker.
161
+ if (text.length > 0 && el.children.length === 0) {
162
+ out.push({ kind: 'text', text });
163
+ return out.length >= FALLBACK_MAX_NODES;
164
+ }
165
+ for (const child of Array.from(el.children)) {
166
+ if (walkFallback(child, out, depth + 1))
167
+ return true;
168
+ }
169
+ return false;
170
+ }
66
171
  //# sourceMappingURL=describe-visible-content.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"describe-visible-content.js","sourceRoot":"","sources":["../../../src/client/rpc/describe-visible-content.ts"],"names":[],"mappings":"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","sourcesContent":["import type { OutlineNode } from '../../protocol.js'\n\nexport type DescribeVisibleArgs = Record<string, never>\nexport type DescribeVisibleResult = { outline: OutlineNode[] }\n\nexport type DescribeVisibleHost = {\n getRootElement(): Element | null\n getBindingDescriptors(): Array<{ variant: string }> | null\n getMsgAnnotations(): Record<string, { intent: string | null; humanOnly: boolean }> | null\n}\n\n/**\n * Walk data-agent-tagged subtrees and produce a structured outline.\n * Buttons cross-reference __bindingDescriptors so Claude can tie\n * visible text to variant names.\n */\nexport function handleDescribeVisibleContent(host: DescribeVisibleHost): DescribeVisibleResult {\n const root = host.getRootElement()\n if (!root) return { outline: [] }\n const out: OutlineNode[] = []\n const allZones = Array.from(root.querySelectorAll('[data-agent]'))\n // Only walk top-level zones; skip zones that are descendants of other zones\n const topLevel = allZones.filter(\n (zone) => !allZones.some((other) => other !== zone && other.contains(zone)),\n )\n for (const zone of topLevel) {\n walk(zone, out)\n }\n return { outline: out }\n}\n\nfunction walk(el: Element, out: OutlineNode[]): void {\n const tag = el.tagName.toLowerCase()\n const text = (el.textContent ?? '').trim()\n if (/^h[1-6]$/.test(tag)) {\n out.push({ kind: 'heading', level: Number(tag[1]), text })\n return\n }\n if (tag === 'button') {\n out.push({\n kind: 'button',\n text,\n disabled: (el as HTMLButtonElement).disabled,\n actionVariant: el.getAttribute('data-agent') ?? null,\n })\n return\n }\n if (tag === 'a' && el.getAttribute('href')) {\n out.push({ kind: 'link', text, href: el.getAttribute('href') ?? '' })\n return\n }\n if (tag === 'input') {\n out.push({\n kind: 'input',\n label: el.getAttribute('aria-label') ?? el.getAttribute('name') ?? null,\n value: (el as HTMLInputElement).value ?? null,\n type: (el as HTMLInputElement).type ?? 'text',\n })\n return\n }\n if (tag === 'ul' || tag === 'ol') {\n const items: OutlineNode[] = []\n for (const child of Array.from(el.children)) {\n if (child.tagName.toLowerCase() === 'li') {\n items.push({ kind: 'item', text: (child.textContent ?? '').trim() })\n }\n }\n out.push({ kind: 'list', items })\n return\n }\n if (text.length > 0 && el.children.length === 0) {\n out.push({ kind: 'text', text })\n return\n }\n for (const child of Array.from(el.children)) {\n walk(child, out)\n }\n}\n"]}
1
+ {"version":3,"file":"describe-visible-content.js","sourceRoot":"","sources":["../../../src/client/rpc/describe-visible-content.ts"],"names":[],"mappings":"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","sourcesContent":["import type { MessageAnnotations, OutlineNode } from '../../protocol.js'\n\nexport type DescribeVisibleArgs = Record<string, never>\nexport type DescribeVisibleResult = {\n outline: OutlineNode[]\n /**\n * `'data-agent'` when the outline was scoped to author-tagged zones.\n * `'fallback'` when the app has no `data-agent` attributes and the\n * walker fell back to a generic semantic-element pass over the root.\n * `'truncated'` when the fallback outline hit the node-count cap and\n * stopped early; the caller can ask follow-up questions through\n * `query_dom` or by inspecting state directly.\n */\n source: 'data-agent' | 'fallback' | 'truncated'\n}\n\nexport type DescribeVisibleHost = {\n getRootElement(): Element | null\n getBindingDescriptors(): Array<{ variant: string }> | null\n getMsgAnnotations(): Record<string, MessageAnnotations> | null\n}\n\n/**\n * Hard caps for the fallback walk. The author-tagged path doesn't need\n * caps — by definition the author chose what to expose. The fallback is\n * walking arbitrary DOM, so it has to bound the work.\n *\n * 200 nodes covers ~3–5 visible screens of typical app UI; depth 8 is\n * deep enough for nested content trees but stops us from descending\n * into giant virtualised lists or syntax-highlighted code blocks.\n */\nconst FALLBACK_MAX_NODES = 200\nconst FALLBACK_MAX_DEPTH = 8\n\n/**\n * Walk data-agent-tagged subtrees and produce a structured outline.\n * Buttons cross-reference __bindingDescriptors so Claude can tie\n * visible text to variant names.\n *\n * If the app has no `data-agent` tags, fall back to a depth-limited\n * walk of the entire root element. This makes the tool useful for apps\n * that haven't (yet) tagged their views — typical first-pass dogfood\n * targets — instead of returning an empty outline that conveys\n * nothing. The fallback path sets `source: 'fallback'` so the caller\n * can tell the outline is best-effort.\n */\nexport function handleDescribeVisibleContent(host: DescribeVisibleHost): DescribeVisibleResult {\n const root = host.getRootElement()\n if (!root) return { outline: [], source: 'data-agent' }\n const allZones = Array.from(root.querySelectorAll('[data-agent]'))\n if (allZones.length > 0) {\n const out: OutlineNode[] = []\n // Only walk top-level zones; skip zones that are descendants of other zones\n const topLevel = allZones.filter(\n (zone) => !allZones.some((other) => other !== zone && other.contains(zone)),\n )\n for (const zone of topLevel) {\n walk(zone, out)\n }\n return { outline: out, source: 'data-agent' }\n }\n // Fallback: walk the entire root with caps. Useful for apps without\n // any data-agent annotations — at minimum the agent gets headings,\n // buttons, links, lists, and visible text it can use to orient.\n const out: OutlineNode[] = []\n const truncated = walkFallback(root, out, 0)\n return { outline: out, source: truncated ? 'truncated' : 'fallback' }\n}\n\nfunction walk(el: Element, out: OutlineNode[]): void {\n const tag = el.tagName.toLowerCase()\n const text = (el.textContent ?? '').trim()\n if (/^h[1-6]$/.test(tag)) {\n out.push({ kind: 'heading', level: Number(tag[1]), text })\n return\n }\n if (tag === 'button') {\n out.push({\n kind: 'button',\n text,\n disabled: (el as HTMLButtonElement).disabled,\n actionVariant: el.getAttribute('data-agent') ?? null,\n })\n return\n }\n if (tag === 'a' && el.getAttribute('href')) {\n out.push({ kind: 'link', text, href: el.getAttribute('href') ?? '' })\n return\n }\n if (tag === 'input') {\n out.push({\n kind: 'input',\n label: el.getAttribute('aria-label') ?? el.getAttribute('name') ?? null,\n value: (el as HTMLInputElement).value ?? null,\n type: (el as HTMLInputElement).type ?? 'text',\n })\n return\n }\n if (tag === 'ul' || tag === 'ol') {\n const items: OutlineNode[] = []\n for (const child of Array.from(el.children)) {\n if (child.tagName.toLowerCase() === 'li') {\n items.push({ kind: 'item', text: (child.textContent ?? '').trim() })\n }\n }\n out.push({ kind: 'list', items })\n return\n }\n if (text.length > 0 && el.children.length === 0) {\n out.push({ kind: 'text', text })\n return\n }\n for (const child of Array.from(el.children)) {\n walk(child, out)\n }\n}\n\n/**\n * Depth- and count-limited walk for the fallback path. Returns true\n * iff the cap was hit (at least one element was skipped). Same node\n * vocabulary as `walk` but prunes more aggressively:\n *\n * - `<a>` without `href`, `<button>` without text — skipped (often\n * icon-only chrome that adds noise without helping the agent).\n * - Nested wrapper divs without semantic content — descended into\n * but not emitted.\n * - Standalone text nodes — only emitted when the parent has no\n * semantic children (preserves the existing `walk` heuristic).\n */\nfunction walkFallback(el: Element, out: OutlineNode[], depth: number): boolean {\n if (out.length >= FALLBACK_MAX_NODES) return true\n if (depth > FALLBACK_MAX_DEPTH) return false\n const tag = el.tagName.toLowerCase()\n // Skip script/style/noscript subtrees — never user-facing content.\n if (tag === 'script' || tag === 'style' || tag === 'noscript' || tag === 'template') return false\n const text = (el.textContent ?? '').trim()\n if (/^h[1-6]$/.test(tag)) {\n out.push({ kind: 'heading', level: Number(tag[1]), text })\n return false\n }\n if (tag === 'button') {\n if (text.length > 0 || el.getAttribute('aria-label')) {\n out.push({\n kind: 'button',\n text: text || el.getAttribute('aria-label') || '',\n disabled: (el as HTMLButtonElement).disabled,\n actionVariant: el.getAttribute('data-agent') ?? null,\n })\n }\n return out.length >= FALLBACK_MAX_NODES\n }\n if (tag === 'a' && el.getAttribute('href')) {\n out.push({ kind: 'link', text, href: el.getAttribute('href') ?? '' })\n return out.length >= FALLBACK_MAX_NODES\n }\n if (tag === 'input') {\n const inputType = (el as HTMLInputElement).type ?? 'text'\n // `hidden` inputs are almost always form-state plumbing; skip them.\n if (inputType !== 'hidden') {\n out.push({\n kind: 'input',\n label: el.getAttribute('aria-label') ?? el.getAttribute('name') ?? null,\n value: (el as HTMLInputElement).value ?? null,\n type: inputType,\n })\n }\n return out.length >= FALLBACK_MAX_NODES\n }\n if (tag === 'ul' || tag === 'ol') {\n const items: OutlineNode[] = []\n for (const child of Array.from(el.children)) {\n if (child.tagName.toLowerCase() === 'li') {\n const itemText = (child.textContent ?? '').trim()\n if (itemText.length > 0) items.push({ kind: 'item', text: itemText })\n }\n }\n if (items.length > 0) out.push({ kind: 'list', items })\n return out.length >= FALLBACK_MAX_NODES\n }\n // Bare text leaf: only emit if the element has a non-empty text and\n // no element children. Same rule as the tagged walker.\n if (text.length > 0 && el.children.length === 0) {\n out.push({ kind: 'text', text })\n return out.length >= FALLBACK_MAX_NODES\n }\n for (const child of Array.from(el.children)) {\n if (walkFallback(child, out, depth + 1)) return true\n }\n return false\n}\n"]}
@@ -1,4 +1,5 @@
1
1
  import type { MessageAnnotations } from '../../protocol.js';
2
+ import type { MsgSchemaShape } from '../factory.js';
2
3
  type Binding = {
3
4
  variant: string;
4
5
  };
@@ -7,19 +8,68 @@ export type ListActionsHost = {
7
8
  getState(): unknown;
8
9
  getBindingDescriptors(): Binding[] | null;
9
10
  getMsgAnnotations(): Annotations | null;
11
+ getMsgSchema(): MsgSchemaShape | null;
10
12
  getAgentAffordances(): ((state: unknown) => Array<{
11
13
  type: string;
12
14
  [k: string]: unknown;
13
15
  }>) | null;
14
16
  };
17
+ /**
18
+ * `dispatchMode` on each action is `'shared'` (human can also click via
19
+ * a UI affordance) or `'agent-only'` (no UI binding — agent is the only
20
+ * dispatcher). `'human-only'` variants are filtered out before this
21
+ * point — they never reach the LLM.
22
+ *
23
+ * `source` distinguishes WHERE the affordance came from:
24
+ * - `'binding'` — a tagged event handler is currently mounted.
25
+ * - `'always-affordable'` — the app's `agentAffordances(state)` hook
26
+ * listed it as available right now.
27
+ * - `'schema'` — neither of the above; the variant is in the
28
+ * Msg union, annotated `@agentOnly`, and the payload is example-
29
+ * only (no live UI binding maps to it). Bulk-edit operations
30
+ * typically land here.
31
+ */
15
32
  export type ListActionsResult = {
16
33
  actions: Array<{
17
34
  variant: string;
18
- intent: string;
35
+ /**
36
+ * Human-readable phrase from `@intent("…")`, or `null` when the
37
+ * variant is unannotated. Mirror of LapActionsResponse.intent —
38
+ * callers should treat `null` as a documentation gap and not as
39
+ * "missing label, fall back to variant name".
40
+ */
41
+ intent: string | null;
19
42
  requiresConfirm: boolean;
20
- source: 'binding' | 'always-affordable';
43
+ dispatchMode: 'shared' | 'agent-only';
44
+ source: 'binding' | 'always-affordable' | 'schema';
21
45
  selectorHint: string | null;
22
46
  payloadHint: object | null;
47
+ /** Cautionary text from `@warning` JSDoc, or null. */
48
+ warning: string | null;
49
+ /** Concrete examples from `@example` JSDoc, in source order. */
50
+ examples: string[];
51
+ /**
52
+ * Effect kinds this variant emits, from `@emits("k1", "k2")`.
53
+ * Empty when not annotated. Lets the agent know what side
54
+ * effects fire — useful for batching ("100 dispatches × cloud-
55
+ * save = bad") and for confirming destructive flows.
56
+ */
57
+ emits: string[];
58
+ /**
59
+ * Per-field guidance lifted from `@should("…")` JSDoc on payload
60
+ * fields. Path is dot/bracket notation rooted at the payload
61
+ * (e.g. `"cells"` for a top-level field, `"cells[].meta"` for an
62
+ * array element's nested field). Useful when the field is typed
63
+ * as `unknown` or as a polymorphic shape — the hint says "type
64
+ * matches the criterion's kind: number for quantity, …" so the
65
+ * agent doesn't have to guess from the bare schema.
66
+ *
67
+ * Empty when no field on this variant carries an `@should` hint.
68
+ */
69
+ fieldHints: Array<{
70
+ path: string;
71
+ hint: string;
72
+ }>;
23
73
  }>;
24
74
  };
25
75
  export declare function handleListActions(host: ListActionsHost): ListActionsResult;
@@ -1 +1 @@
1
- {"version":3,"file":"list-actions.d.ts","sourceRoot":"","sources":["../../../src/client/rpc/list-actions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAE3D,KAAK,OAAO,GAAG;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAAA;AAClC,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAA;AAErD,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,IAAI,OAAO,CAAA;IACnB,qBAAqB,IAAI,OAAO,EAAE,GAAG,IAAI,CAAA;IACzC,iBAAiB,IAAI,WAAW,GAAG,IAAI,CAAA;IACvC,mBAAmB,IAAI,CAAC,CAAC,KAAK,EAAE,OAAO,KAAK,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC,CAAC,GAAG,IAAI,CAAA;CAClG,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,EAAE,KAAK,CAAC;QACb,OAAO,EAAE,MAAM,CAAA;QACf,MAAM,EAAE,MAAM,CAAA;QACd,eAAe,EAAE,OAAO,CAAA;QACxB,MAAM,EAAE,SAAS,GAAG,mBAAmB,CAAA;QACvC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;QAC3B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;KAC3B,CAAC,CAAA;CACH,CAAA;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,eAAe,GAAG,iBAAiB,CAsC1E"}
1
+ {"version":3,"file":"list-actions.d.ts","sourceRoot":"","sources":["../../../src/client/rpc/list-actions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAkB,MAAM,eAAe,CAAA;AAEnE,KAAK,OAAO,GAAG;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAAA;AAClC,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAA;AAErD,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,IAAI,OAAO,CAAA;IACnB,qBAAqB,IAAI,OAAO,EAAE,GAAG,IAAI,CAAA;IACzC,iBAAiB,IAAI,WAAW,GAAG,IAAI,CAAA;IACvC,YAAY,IAAI,cAAc,GAAG,IAAI,CAAA;IACrC,mBAAmB,IAAI,CAAC,CAAC,KAAK,EAAE,OAAO,KAAK,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC,CAAC,GAAG,IAAI,CAAA;CAClG,CAAA;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,EAAE,KAAK,CAAC;QACb,OAAO,EAAE,MAAM,CAAA;QACf;;;;;WAKG;QACH,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;QACrB,eAAe,EAAE,OAAO,CAAA;QACxB,YAAY,EAAE,QAAQ,GAAG,YAAY,CAAA;QACrC,MAAM,EAAE,SAAS,GAAG,mBAAmB,GAAG,QAAQ,CAAA;QAClD,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;QAC3B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;QAC1B,sDAAsD;QACtD,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;QACtB,gEAAgE;QAChE,QAAQ,EAAE,MAAM,EAAE,CAAA;QAClB;;;;;WAKG;QACH,KAAK,EAAE,MAAM,EAAE,CAAA;QACf;;;;;;;;;;WAUG;QACH,UAAU,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAClD,CAAC,CAAA;CACH,CAAA;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,eAAe,GAAG,iBAAiB,CAoG1E"}
@@ -3,36 +3,218 @@ export function handleListActions(host) {
3
3
  const state = host.getState();
4
4
  const descriptors = host.getBindingDescriptors() ?? [];
5
5
  const affordances = host.getAgentAffordances()?.(state) ?? [];
6
+ const schema = host.getMsgSchema();
6
7
  const out = [];
7
- // From bindings
8
+ const seen = new Set();
9
+ // From bindings — these have UI affordances by definition, so they're
10
+ // either 'shared' (default) or, in the malformed case where someone
11
+ // bound an `@agentOnly` Msg in a view, 'agent-only'. Either way the
12
+ // agent can dispatch them.
8
13
  for (const d of descriptors) {
9
14
  const ann = annotations[d.variant];
10
- if (ann?.humanOnly)
15
+ if (ann?.dispatchMode === 'human-only')
11
16
  continue;
17
+ seen.add(d.variant);
18
+ const variantSchema = schema?.variants[d.variant];
12
19
  out.push({
13
20
  variant: d.variant,
14
- intent: ann?.intent ?? d.variant,
21
+ intent: ann?.intent ?? null,
15
22
  requiresConfirm: ann?.requiresConfirm ?? false,
23
+ dispatchMode: ann?.dispatchMode === 'agent-only' ? 'agent-only' : 'shared',
16
24
  source: 'binding',
17
25
  selectorHint: null,
18
26
  payloadHint: null,
27
+ warning: ann?.warning ?? null,
28
+ examples: ann?.examples ?? [],
29
+ emits: ann?.emits ?? [],
30
+ fieldHints: variantSchema ? collectFieldHints(variantSchema) : [],
19
31
  });
20
32
  }
21
33
  // From always-affordable
22
34
  for (const msg of affordances) {
23
35
  const ann = annotations[msg.type];
24
- if (ann?.humanOnly)
36
+ if (ann?.dispatchMode === 'human-only')
25
37
  continue;
38
+ seen.add(msg.type);
26
39
  const { type, ...rest } = msg;
40
+ const variantSchema = schema?.variants[type];
27
41
  out.push({
28
42
  variant: type,
29
- intent: ann?.intent ?? type,
43
+ intent: ann?.intent ?? null,
30
44
  requiresConfirm: ann?.requiresConfirm ?? false,
45
+ dispatchMode: ann?.dispatchMode === 'agent-only' ? 'agent-only' : 'shared',
31
46
  source: 'always-affordable',
32
47
  selectorHint: null,
33
48
  payloadHint: Object.keys(rest).length > 0 ? rest : null,
49
+ warning: ann?.warning ?? null,
50
+ examples: ann?.examples ?? [],
51
+ emits: ann?.emits ?? [],
52
+ fieldHints: variantSchema ? collectFieldHints(variantSchema) : [],
34
53
  });
35
54
  }
55
+ // From schema — variants that aren't already surfaced as bindings
56
+ // or always-affordable, and that the author intentionally documented
57
+ // for agent use. Two cases land here:
58
+ //
59
+ // 1. `@agentOnly` variants — the canonical "no UI button maps to
60
+ // this; the agent is the only dispatcher." Bulk edits, imports,
61
+ // admin operations.
62
+ // 2. `'shared'` variants with `@intent` but no live binding — the
63
+ // author wrote a description for the variant, signalling it's
64
+ // a real agent-callable dispatch even when the corresponding UI
65
+ // affordance is closed (e.g. `Matrix/SetQuantityValue` lives
66
+ // inside the cell editor; without this case, an agent that
67
+ // wants to set one cell is forced to use the bulk
68
+ // `Matrix/SetManyCells` with a 1-element array).
69
+ //
70
+ // `'shared'` variants WITHOUT `@intent` stay hidden — undocumented
71
+ // shared variants are usually internal (effect-result messages,
72
+ // router-internal acks) that aren't intended as agent affordances.
73
+ // `'human-only'` is always filtered.
74
+ if (schema) {
75
+ for (const [variant, fields] of Object.entries(schema.variants)) {
76
+ if (seen.has(variant))
77
+ continue;
78
+ const ann = annotations[variant];
79
+ if (ann?.dispatchMode === 'human-only')
80
+ continue;
81
+ const isAgentOnly = ann?.dispatchMode === 'agent-only';
82
+ const isDocumentedShared = ann?.dispatchMode !== 'agent-only' && Boolean(ann?.intent);
83
+ if (!isAgentOnly && !isDocumentedShared)
84
+ continue;
85
+ out.push({
86
+ variant,
87
+ intent: ann?.intent ?? null,
88
+ requiresConfirm: ann?.requiresConfirm ?? false,
89
+ dispatchMode: isAgentOnly ? 'agent-only' : 'shared',
90
+ source: 'schema',
91
+ selectorHint: null,
92
+ payloadHint: synthesizePayload(variant, fields),
93
+ warning: ann?.warning ?? null,
94
+ examples: ann?.examples ?? [],
95
+ emits: ann?.emits ?? [],
96
+ fieldHints: collectFieldHints(fields),
97
+ });
98
+ }
99
+ }
36
100
  return { actions: out };
37
101
  }
102
+ /**
103
+ * Build an example payload object the LLM can fill in. Required
104
+ * fields always appear; optional fields appear only when annotated
105
+ * `@should` (LLM is encouraged to fill them in). Fields without a
106
+ * concrete primitive type (`'unknown'`) emit `null` placeholders the
107
+ * LLM is expected to replace.
108
+ *
109
+ * The first key is `type` so the payload reads as a complete Msg
110
+ * shape — copy-paste-ready into `send_message`.
111
+ */
112
+ function synthesizePayload(variant, fields) {
113
+ const out = { type: variant };
114
+ for (const [name, descriptor] of Object.entries(fields)) {
115
+ const optional = isOptional(descriptor);
116
+ const priority = isShould(descriptor);
117
+ // Skip optional fields unless they're @should-flagged. Required
118
+ // fields always appear.
119
+ if (optional && priority !== 'should')
120
+ continue;
121
+ out[name] = exampleValue(descriptor);
122
+ }
123
+ return out;
124
+ }
125
+ /**
126
+ * Walk a variant's field tree and collect every `@should` hint into a
127
+ * flat list keyed by field path. Path conventions:
128
+ * - top-level field: `"cells"`
129
+ * - nested object property: `"cells.value"`
130
+ * - array element: `"cells[]"` for the element itself; descendants
131
+ * use `"cells[].meta"` and so on.
132
+ *
133
+ * Surfaces the same hints that show up nested inside
134
+ * `description.messages.variants[X].field.hint` so callers don't have
135
+ * to dig through the schema tree to find them.
136
+ */
137
+ function collectFieldHints(fields) {
138
+ const out = [];
139
+ for (const [name, descriptor] of Object.entries(fields)) {
140
+ walkHint(name, descriptor, out);
141
+ }
142
+ return out;
143
+ }
144
+ function walkHint(path, d, out) {
145
+ // Rich descriptor with a hint at this position.
146
+ if (typeof d === 'object' && d !== null && 'type' in d) {
147
+ if (typeof d.hint === 'string' && d.hint.length > 0) {
148
+ out.push({ path, hint: d.hint });
149
+ }
150
+ walkHintBare(path, d.type, out);
151
+ return;
152
+ }
153
+ walkHintBare(path, d, out);
154
+ }
155
+ function walkHintBare(path, t, out) {
156
+ if (t === null || typeof t !== 'object')
157
+ return;
158
+ const obj = t;
159
+ if (obj.kind === 'object' && obj.shape !== null && typeof obj.shape === 'object') {
160
+ for (const [name, descriptor] of Object.entries(obj.shape)) {
161
+ walkHint(`${path}.${name}`, descriptor, out);
162
+ }
163
+ return;
164
+ }
165
+ if (obj.kind === 'array') {
166
+ walkHint(`${path}[]`, obj.element, out);
167
+ }
168
+ }
169
+ function isOptional(d) {
170
+ return typeof d === 'object' && 'type' in d && d.optional === true;
171
+ }
172
+ function isShould(d) {
173
+ return typeof d === 'object' && 'type' in d ? d.priority : undefined;
174
+ }
175
+ function exampleValue(d) {
176
+ // Unwrap rich descriptor to get the bare type for synthesis.
177
+ const t = typeof d === 'object' && 'type' in d
178
+ ? d.type
179
+ : d;
180
+ return synthesizeBare(t);
181
+ }
182
+ function synthesizeBare(t) {
183
+ if (typeof t === 'string') {
184
+ if (t === 'string')
185
+ return '';
186
+ if (t === 'number')
187
+ return 0;
188
+ if (t === 'boolean')
189
+ return false;
190
+ return null; // 'unknown' or unrecognized keyword → placeholder
191
+ }
192
+ if (t === null || typeof t !== 'object')
193
+ return null;
194
+ const obj = t;
195
+ if ('enum' in obj && Array.isArray(obj.enum)) {
196
+ // First option doubles as the canonical example.
197
+ return obj.enum[0] ?? null;
198
+ }
199
+ if (obj.kind === 'object' && obj.shape !== null && typeof obj.shape === 'object') {
200
+ // Recurse into the nested shape. Same optional-skip rule as the
201
+ // top-level synthesizer: required fields appear, optional ones
202
+ // appear only when @should-flagged.
203
+ const out = {};
204
+ for (const [name, descriptor] of Object.entries(obj.shape)) {
205
+ const isOpt = isOptional(descriptor);
206
+ const pri = isShould(descriptor);
207
+ if (isOpt && pri !== 'should')
208
+ continue;
209
+ out[name] = exampleValue(descriptor);
210
+ }
211
+ return out;
212
+ }
213
+ if (obj.kind === 'array') {
214
+ // Wrap the synthesized element in a one-item array. Lets the LLM
215
+ // see the per-entry shape without us guessing at array length.
216
+ return [synthesizeBare(obj.element)];
217
+ }
218
+ return null;
219
+ }
38
220
  //# sourceMappingURL=list-actions.js.map