@llui/agent 0.0.39 → 0.0.41

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.
@@ -1 +1 @@
1
- {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/client/factory.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/client/factory.ts"],"names":[],"mappings":"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"}
@@ -58,6 +58,10 @@ export function createAgentClient(opts) {
58
58
  // decodes incoming agent messages before they hit the reducer. The
59
59
  // tagged-value convention is documented in `@llui/agent/codecs`.
60
60
  const codecs = opts.codecs ?? makeDefaultCodecs();
61
+ // Track the most recent send_message outcome so `describe_context`
62
+ // can prepend a synthetic hint about it. Apps used to roll their
63
+ // own `lastDispatchError` state field; the framework now owns it.
64
+ let lastDispatchOutcome = null;
61
65
  const rpcHost = {
62
66
  getState: () => encodeForWire(opts.handle.getState(), codecs),
63
67
  send: (m) => opts.handle.send(decodeFromWire(m, codecs)),
@@ -83,11 +87,18 @@ export function createAgentClient(opts) {
83
87
  getBindingDescriptors: () => opts.handle.getBindingDescriptors(),
84
88
  getAgentAffordances: () => opts.def.agentAffordances ?? null,
85
89
  getAgentContext: () => opts.def.agentContext ?? null,
90
+ getLastDispatchOutcome: () => lastDispatchOutcome,
86
91
  getRootElement: () => opts.rootElement,
87
92
  proposeConfirm: (entry) => {
88
93
  opts.handle.send(opts.slices.wrapConfirmMsg({ type: 'Propose', entry }));
89
94
  },
90
95
  };
96
+ // Exposed so the WS client can update on each send_message reply.
97
+ // Closure scope keeps the field write-protected — only the WS layer
98
+ // mutates it; everyone else reads through the getter above.
99
+ const recordDispatchOutcome = (outcome) => {
100
+ lastDispatchOutcome = outcome;
101
+ };
91
102
  const helloBuilder = () => ({
92
103
  t: 'hello',
93
104
  appName: opts.def.name,
@@ -118,6 +129,7 @@ export function createAgentClient(opts) {
118
129
  opts.handle.send(opts.slices.wrapLogMsg({ type: 'Append', entry }));
119
130
  }
120
131
  : undefined,
132
+ onDispatchOutcome: recordDispatchOutcome,
121
133
  });
122
134
  ws.addEventListener('open', () => {
123
135
  opts.handle.send(opts.slices.wrapConnectMsg({ type: 'WsOpened' }));
@@ -163,6 +175,18 @@ export function createAgentClient(opts) {
163
175
  });
164
176
  }
165
177
  installErrorListeners();
178
+ // Catch per-binding throws into drain.errors so a single bad
179
+ // binding doesn't blank the page AND the agent learns about it.
180
+ // Runtime contract: leaves the binding's `lastValue` unchanged
181
+ // (DOM stays at last-rendered value), continues with siblings,
182
+ // calls this hook once per binding throw.
183
+ opts.handle.setOnBindingError((info) => {
184
+ drainErrors.push({
185
+ kind: 'error',
186
+ message: `[binding ${info.kind}${info.key ? `:${info.key}` : ''}] ${info.message}`,
187
+ stack: info.stack,
188
+ });
189
+ });
166
190
  },
167
191
  stop() {
168
192
  if (confirmPollTimer)
@@ -173,6 +197,7 @@ export function createAgentClient(opts) {
173
197
  stateSubscription = null;
174
198
  }
175
199
  removeErrorListeners();
200
+ opts.handle.setOnBindingError(null);
176
201
  drainErrors.length = 0;
177
202
  wsClient?.close();
178
203
  },
@@ -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'\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: [...]}` (values may be string, number,\n * or boolean — the compiler preserves the literal kind so JSON\n * round-trips don't lose type info).\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>`; `{kind: 'discriminated-\n * union', discriminant, variants}` for tagged unions of objects\n * (e.g. `Format = {kind:'exact'} | {kind:'range', min, max}`).\n * The synthesizer recurses to build copy-paste-ready nested\n * examples; the validator walks the same tree.\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: ReadonlyArray<string | number | boolean> }\n | { kind: 'object'; shape: Record<string, MsgSchemaField> }\n | { kind: 'array'; element: MsgSchemaBareType }\n | {\n kind: 'discriminated-union'\n discriminant: string\n variants: Record<string, Record<string, MsgSchemaField>>\n }\n\nexport type MsgSchemaField =\n | MsgSchemaBareType\n | {\n type: MsgSchemaBareType\n optional?: boolean\n priority?: 'should'\n hint?: string\n /**\n * Boolean JS expression authored with `@validates(\"expr\")` JSDoc.\n * Has `v` bound to the field value at runtime; the validator\n * compiles it lazily with `new Function('v', 'return (' + src +\n * ')')` and caches the function across calls. Use for invariants\n * the type system can't express — numeric ranges, format\n * predicates, length bounds.\n */\n validates?: 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
+ {"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: [...]}` (values may be string, number,\n * or boolean — the compiler preserves the literal kind so JSON\n * round-trips don't lose type info).\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>`; `{kind: 'discriminated-\n * union', discriminant, variants}` for tagged unions of objects\n * (e.g. `Format = {kind:'exact'} | {kind:'range', min, max}`).\n * The synthesizer recurses to build copy-paste-ready nested\n * examples; the validator walks the same tree.\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: ReadonlyArray<string | number | boolean> }\n | { kind: 'object'; shape: Record<string, MsgSchemaField> }\n | { kind: 'array'; element: MsgSchemaBareType }\n | {\n kind: 'discriminated-union'\n discriminant: string\n variants: Record<string, Record<string, MsgSchemaField>>\n }\n\nexport type MsgSchemaField =\n | MsgSchemaBareType\n | {\n type: MsgSchemaBareType\n optional?: boolean\n priority?: 'should'\n hint?: string\n /**\n * Boolean JS expression authored with `@validates(\"expr\")` JSDoc.\n * Has `v` bound to the field value at runtime; the validator\n * compiles it lazily with `new Function('v', 'return (' + src +\n * ')')` and caches the function across calls. Use for invariants\n * the type system can't express — numeric ranges, format\n * predicates, length bounds.\n */\n validates?: 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 // Track the most recent send_message outcome so `describe_context`\n // can prepend a synthetic hint about it. Apps used to roll their\n // own `lastDispatchError` state field; the framework now owns it.\n let lastDispatchOutcome: import('./rpc/describe-context.js').LastDispatchOutcome | null = null\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 getLastDispatchOutcome: () => lastDispatchOutcome,\n getRootElement: () => opts.rootElement,\n proposeConfirm: (entry) => {\n opts.handle.send(opts.slices.wrapConfirmMsg({ type: 'Propose', entry }))\n },\n }\n\n // Exposed so the WS client can update on each send_message reply.\n // Closure scope keeps the field write-protected — only the WS layer\n // mutates it; everyone else reads through the getter above.\n const recordDispatchOutcome = (outcome: typeof lastDispatchOutcome): void => {\n lastDispatchOutcome = outcome\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 onDispatchOutcome: recordDispatchOutcome,\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 // Catch per-binding throws into drain.errors so a single bad\n // binding doesn't blank the page AND the agent learns about it.\n // Runtime contract: leaves the binding's `lastValue` unchanged\n // (DOM stays at last-rendered value), continues with siblings,\n // calls this hook once per binding throw.\n opts.handle.setOnBindingError((info) => {\n drainErrors.push({\n kind: 'error',\n message: `[binding ${info.kind}${info.key ? `:${info.key}` : ''}] ${info.message}`,\n stack: info.stack,\n })\n })\n },\n stop() {\n if (confirmPollTimer) clearInterval(confirmPollTimer)\n confirmPollTimer = null\n if (stateSubscription) {\n stateSubscription()\n stateSubscription = null\n }\n removeErrorListeners()\n opts.handle.setOnBindingError(null)\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,7 +1,41 @@
1
1
  import type { AgentContext } from '../../protocol.js';
2
+ /**
3
+ * Snapshot of the most recent `send_message` outcome — what the agent
4
+ * just did and whether it landed cleanly. Tracked by the WS RPC layer
5
+ * after every dispatch, surfaced through `describe_context` so apps
6
+ * don't have to roll their own `lastDispatchError` field in state.
7
+ *
8
+ * `status: 'dispatched'` means the reducer ran and state advanced;
9
+ * `errors` here describes downstream effects that failed during the
10
+ * drain window (Phase-5 catch-and-report). `status: 'rejected'`
11
+ * means the validator caught a structural issue before the reducer.
12
+ * `status: 'reducer-threw'` means the reducer itself threw — state
13
+ * may be partially advanced, but the dispatch should be treated as
14
+ * a failure for retry purposes.
15
+ */
16
+ export type LastDispatchOutcome = {
17
+ variant: string;
18
+ status: 'dispatched' | 'rejected' | 'reducer-threw';
19
+ errors?: ReadonlyArray<{
20
+ message: string;
21
+ }>;
22
+ warnings?: ReadonlyArray<{
23
+ path: string;
24
+ message: string;
25
+ }>;
26
+ at: number;
27
+ };
2
28
  export type DescribeContextHost = {
3
29
  getState(): unknown;
4
30
  getAgentContext(): ((state: unknown) => AgentContext) | null;
31
+ /**
32
+ * Optional accessor for the most recent dispatch outcome. When the
33
+ * outcome had errors or warnings, `describe_context` prepends a
34
+ * synthetic hint so the agent reads "Last dispatch X failed/landed
35
+ * with caveats" without the app having to maintain a parallel state
36
+ * field. Lenient: undefined / null accessor disables the feature.
37
+ */
38
+ getLastDispatchOutcome?: () => LastDispatchOutcome | null;
5
39
  };
6
40
  export type DescribeContextResult = {
7
41
  context: AgentContext;
@@ -1 +1 @@
1
- {"version":3,"file":"describe-context.d.ts","sourceRoot":"","sources":["../../../src/client/rpc/describe-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAErD,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,IAAI,OAAO,CAAA;IACnB,eAAe,IAAI,CAAC,CAAC,KAAK,EAAE,OAAO,KAAK,YAAY,CAAC,GAAG,IAAI,CAAA;CAC7D,CAAA;AACD,MAAM,MAAM,qBAAqB,GAAG;IAAE,OAAO,EAAE,YAAY,CAAA;CAAE,CAAA;AAI7D,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,mBAAmB,GAAG,qBAAqB,CAItF"}
1
+ {"version":3,"file":"describe-context.d.ts","sourceRoot":"","sources":["../../../src/client/rpc/describe-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAErD;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,YAAY,GAAG,UAAU,GAAG,eAAe,CAAA;IACnD,MAAM,CAAC,EAAE,aAAa,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAC3C,QAAQ,CAAC,EAAE,aAAa,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAC3D,EAAE,EAAE,MAAM,CAAA;CACX,CAAA;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,IAAI,OAAO,CAAA;IACnB,eAAe,IAAI,CAAC,CAAC,KAAK,EAAE,OAAO,KAAK,YAAY,CAAC,GAAG,IAAI,CAAA;IAC5D;;;;;;OAMG;IACH,sBAAsB,CAAC,EAAE,MAAM,mBAAmB,GAAG,IAAI,CAAA;CAC1D,CAAA;AACD,MAAM,MAAM,qBAAqB,GAAG;IAAE,OAAO,EAAE,YAAY,CAAA;CAAE,CAAA;AAI7D,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,mBAAmB,GAAG,qBAAqB,CAWtF"}
@@ -1,8 +1,47 @@
1
1
  const EMPTY = { summary: '', hints: [], cautions: [] };
2
2
  export function handleDescribeContext(host) {
3
3
  const fn = host.getAgentContext();
4
- if (!fn)
5
- return { context: EMPTY };
6
- return { context: fn(host.getState()) };
4
+ const baseContext = fn ? fn(host.getState()) : EMPTY;
5
+ const outcome = host.getLastDispatchOutcome?.() ?? null;
6
+ const synthetic = outcome ? formatLastOutcomeHint(outcome) : null;
7
+ if (!synthetic)
8
+ return { context: baseContext };
9
+ // Prepend the synthetic hint so it's the first thing the agent sees.
10
+ // The app's own hints follow.
11
+ const hints = [synthetic, ...(baseContext.hints ?? [])];
12
+ return { context: { ...baseContext, hints } };
13
+ }
14
+ /**
15
+ * Render the outcome as a single hint string. Returns null for clean
16
+ * outcomes — no need to add noise to the context when nothing went
17
+ * wrong on the last dispatch.
18
+ */
19
+ function formatLastOutcomeHint(outcome) {
20
+ const errors = outcome.errors ?? [];
21
+ const warnings = outcome.warnings ?? [];
22
+ if (outcome.status === 'dispatched' && errors.length === 0 && warnings.length === 0) {
23
+ return null;
24
+ }
25
+ if (outcome.status === 'rejected') {
26
+ const errMsgs = errors.length > 0 ? errors.map((e) => e.message).join('; ') : 'no detail';
27
+ return `LAST DISPATCH REJECTED: ${outcome.variant} — ${errMsgs}. Fix the payload and retry.`;
28
+ }
29
+ if (outcome.status === 'reducer-threw') {
30
+ const msg = errors.length > 0 ? (errors[0]?.message ?? 'reducer threw') : 'reducer threw';
31
+ return `LAST DISPATCH ERRORED MID-FLIGHT: ${outcome.variant} — ${msg}. State may be partially advanced; observe before retrying.`;
32
+ }
33
+ // dispatched with errors / warnings
34
+ const parts = [];
35
+ if (errors.length > 0) {
36
+ parts.push(`${String(errors.length)} downstream error${errors.length === 1 ? '' : 's'}: ${errors
37
+ .map((e) => e.message)
38
+ .join('; ')}`);
39
+ }
40
+ if (warnings.length > 0) {
41
+ parts.push(`${String(warnings.length)} validation warning${warnings.length === 1 ? '' : 's'}: ${warnings
42
+ .map((w) => `${w.path}: ${w.message}`)
43
+ .join('; ')}`);
44
+ }
45
+ return `LAST DISPATCH (${outcome.variant}) landed with caveats — ${parts.join(' / ')}.`;
7
46
  }
8
47
  //# sourceMappingURL=describe-context.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"describe-context.js","sourceRoot":"","sources":["../../../src/client/rpc/describe-context.ts"],"names":[],"mappings":"AAQA,MAAM,KAAK,GAAiB,EAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAA;AAEpE,MAAM,UAAU,qBAAqB,CAAC,IAAyB;IAC7D,MAAM,EAAE,GAAG,IAAI,CAAC,eAAe,EAAE,CAAA;IACjC,IAAI,CAAC,EAAE;QAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAA;IAClC,OAAO,EAAE,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,EAAE,CAAA;AACzC,CAAC","sourcesContent":["import type { AgentContext } from '../../protocol.js'\n\nexport type DescribeContextHost = {\n getState(): unknown\n getAgentContext(): ((state: unknown) => AgentContext) | null\n}\nexport type DescribeContextResult = { context: AgentContext }\n\nconst EMPTY: AgentContext = { summary: '', hints: [], cautions: [] }\n\nexport function handleDescribeContext(host: DescribeContextHost): DescribeContextResult {\n const fn = host.getAgentContext()\n if (!fn) return { context: EMPTY }\n return { context: fn(host.getState()) }\n}\n"]}
1
+ {"version":3,"file":"describe-context.js","sourceRoot":"","sources":["../../../src/client/rpc/describe-context.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentContext } from '../../protocol.js'\n\n/**\n * Snapshot of the most recent `send_message` outcome — what the agent\n * just did and whether it landed cleanly. Tracked by the WS RPC layer\n * after every dispatch, surfaced through `describe_context` so apps\n * don't have to roll their own `lastDispatchError` field in state.\n *\n * `status: 'dispatched'` means the reducer ran and state advanced;\n * `errors` here describes downstream effects that failed during the\n * drain window (Phase-5 catch-and-report). `status: 'rejected'`\n * means the validator caught a structural issue before the reducer.\n * `status: 'reducer-threw'` means the reducer itself threw — state\n * may be partially advanced, but the dispatch should be treated as\n * a failure for retry purposes.\n */\nexport type LastDispatchOutcome = {\n variant: string\n status: 'dispatched' | 'rejected' | 'reducer-threw'\n errors?: ReadonlyArray<{ message: string }>\n warnings?: ReadonlyArray<{ path: string; message: string }>\n at: number\n}\n\nexport type DescribeContextHost = {\n getState(): unknown\n getAgentContext(): ((state: unknown) => AgentContext) | null\n /**\n * Optional accessor for the most recent dispatch outcome. When the\n * outcome had errors or warnings, `describe_context` prepends a\n * synthetic hint so the agent reads \"Last dispatch X failed/landed\n * with caveats\" without the app having to maintain a parallel state\n * field. Lenient: undefined / null accessor disables the feature.\n */\n getLastDispatchOutcome?: () => LastDispatchOutcome | null\n}\nexport type DescribeContextResult = { context: AgentContext }\n\nconst EMPTY: AgentContext = { summary: '', hints: [], cautions: [] }\n\nexport function handleDescribeContext(host: DescribeContextHost): DescribeContextResult {\n const fn = host.getAgentContext()\n const baseContext = fn ? fn(host.getState()) : EMPTY\n const outcome = host.getLastDispatchOutcome?.() ?? null\n const synthetic = outcome ? formatLastOutcomeHint(outcome) : null\n if (!synthetic) return { context: baseContext }\n\n // Prepend the synthetic hint so it's the first thing the agent sees.\n // The app's own hints follow.\n const hints: string[] = [synthetic, ...(baseContext.hints ?? [])]\n return { context: { ...baseContext, hints } }\n}\n\n/**\n * Render the outcome as a single hint string. Returns null for clean\n * outcomes — no need to add noise to the context when nothing went\n * wrong on the last dispatch.\n */\nfunction formatLastOutcomeHint(outcome: LastDispatchOutcome): string | null {\n const errors = outcome.errors ?? []\n const warnings = outcome.warnings ?? []\n if (outcome.status === 'dispatched' && errors.length === 0 && warnings.length === 0) {\n return null\n }\n if (outcome.status === 'rejected') {\n const errMsgs = errors.length > 0 ? errors.map((e) => e.message).join('; ') : 'no detail'\n return `LAST DISPATCH REJECTED: ${outcome.variant} — ${errMsgs}. Fix the payload and retry.`\n }\n if (outcome.status === 'reducer-threw') {\n const msg = errors.length > 0 ? (errors[0]?.message ?? 'reducer threw') : 'reducer threw'\n return `LAST DISPATCH ERRORED MID-FLIGHT: ${outcome.variant} — ${msg}. State may be partially advanced; observe before retrying.`\n }\n // dispatched with errors / warnings\n const parts: string[] = []\n if (errors.length > 0) {\n parts.push(\n `${String(errors.length)} downstream error${errors.length === 1 ? '' : 's'}: ${errors\n .map((e) => e.message)\n .join('; ')}`,\n )\n }\n if (warnings.length > 0) {\n parts.push(\n `${String(warnings.length)} validation warning${warnings.length === 1 ? '' : 's'}: ${warnings\n .map((w) => `${w.path}: ${w.message}`)\n .join('; ')}`,\n )\n }\n return `LAST DISPATCH (${outcome.variant}) landed with caveats — ${parts.join(' / ')}.`\n}\n"]}
@@ -1,6 +1,20 @@
1
1
  import type { MessageAnnotations, OutlineNode } from '../../protocol.js';
2
2
  export type DescribeVisibleArgs = Record<string, never>;
3
3
  export type DescribeVisibleResult = {
4
+ /**
5
+ * The user's current URL — `window.location.href`, or `null` when
6
+ * running outside a browser (SSR / Node test harness).
7
+ *
8
+ * Why this is on the visible-content tool: the agent has no other
9
+ * way to verify "did my dispatch actually navigate the user?" Apps
10
+ * that bundle navigation into a Msg's effect chain (the canonical
11
+ * pattern) update the URL on commit; the agent reads it back here
12
+ * to confirm the user's view tracked the state change. When the URL
13
+ * doesn't move after a state-creating dispatch, the agent learns
14
+ * the dispatch landed but the navigation didn't — common when the
15
+ * Msg returned partial effects.
16
+ */
17
+ url: string | null;
4
18
  outline: OutlineNode[];
5
19
  /**
6
20
  * `'data-agent'` when the outline was scoped to author-tagged zones.
@@ -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,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
+ {"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;;;;;;;;;;;;OAYG;IACH,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB,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,CAsB7F"}
@@ -22,9 +22,10 @@ const FALLBACK_MAX_DEPTH = 8;
22
22
  * can tell the outline is best-effort.
23
23
  */
24
24
  export function handleDescribeVisibleContent(host) {
25
+ const url = readCurrentUrl();
25
26
  const root = host.getRootElement();
26
27
  if (!root)
27
- return { outline: [], source: 'data-agent' };
28
+ return { url, outline: [], source: 'data-agent' };
28
29
  const allZones = Array.from(root.querySelectorAll('[data-agent]'));
29
30
  if (allZones.length > 0) {
30
31
  const out = [];
@@ -33,14 +34,27 @@ export function handleDescribeVisibleContent(host) {
33
34
  for (const zone of topLevel) {
34
35
  walk(zone, out);
35
36
  }
36
- return { outline: out, source: 'data-agent' };
37
+ return { url, outline: out, source: 'data-agent' };
37
38
  }
38
39
  // Fallback: walk the entire root with caps. Useful for apps without
39
40
  // any data-agent annotations — at minimum the agent gets headings,
40
41
  // buttons, links, lists, and visible text it can use to orient.
41
42
  const out = [];
42
43
  const truncated = walkFallback(root, out, 0);
43
- return { outline: out, source: truncated ? 'truncated' : 'fallback' };
44
+ return { url, outline: out, source: truncated ? 'truncated' : 'fallback' };
45
+ }
46
+ /**
47
+ * Read `window.location.href` defensively. Returns null when running
48
+ * outside a browser (SSR, Node test harness) — same contract as
49
+ * `getRootElement` returning null. Callers don't have to special-case
50
+ * non-browser environments.
51
+ */
52
+ function readCurrentUrl() {
53
+ if (typeof window === 'undefined')
54
+ return null;
55
+ if (typeof window.location === 'undefined')
56
+ return null;
57
+ return window.location.href;
44
58
  }
45
59
  function walk(el, out) {
46
60
  const tag = el.tagName.toLowerCase();
@@ -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 { 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
+ {"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 /**\n * The user's current URL — `window.location.href`, or `null` when\n * running outside a browser (SSR / Node test harness).\n *\n * Why this is on the visible-content tool: the agent has no other\n * way to verify \"did my dispatch actually navigate the user?\" Apps\n * that bundle navigation into a Msg's effect chain (the canonical\n * pattern) update the URL on commit; the agent reads it back here\n * to confirm the user's view tracked the state change. When the URL\n * doesn't move after a state-creating dispatch, the agent learns\n * the dispatch landed but the navigation didn't — common when the\n * Msg returned partial effects.\n */\n url: string | null\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 url = readCurrentUrl()\n const root = host.getRootElement()\n if (!root) return { url, 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 { url, 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 { url, outline: out, source: truncated ? 'truncated' : 'fallback' }\n}\n\n/**\n * Read `window.location.href` defensively. Returns null when running\n * outside a browser (SSR, Node test harness) — same contract as\n * `getRootElement` returning null. Callers don't have to special-case\n * non-browser environments.\n */\nfunction readCurrentUrl(): string | null {\n if (typeof window === 'undefined') return null\n if (typeof window.location === 'undefined') return null\n return window.location.href\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 +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;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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;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,CAqH1E"}
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;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,CA0J1E"}
@@ -2,7 +2,20 @@ export function handleListActions(host) {
2
2
  const annotations = host.getMsgAnnotations() ?? {};
3
3
  const state = host.getState();
4
4
  const descriptors = host.getBindingDescriptors() ?? [];
5
- const affordances = host.getAgentAffordances()?.(state) ?? [];
5
+ const affordancesFn = host.getAgentAffordances();
6
+ const affordances = affordancesFn ? affordancesFn(state) : [];
7
+ // When the app provides `agentAffordances(state)`, it's opted into
8
+ // explicit affordance control: only state-relevant Msgs are listed.
9
+ // `@agentOnly` schema-source variants are then filtered to those the
10
+ // hook returned — so a bulk-edit Msg like `Matrix/AddCriteria`
11
+ // doesn't surface on the home page just because it's tagged
12
+ // `@agentOnly`. Apps without `agentAffordances` keep the previous
13
+ // permissive default ("everything's available unless you say
14
+ // otherwise") since flipping that without explicit opt-in would
15
+ // break consumers who rely on schema-source surfacing of
16
+ // bulk Msgs.
17
+ const explicitAffordances = affordancesFn !== null;
18
+ const affordanceVariants = new Set(affordances.map((m) => m.type));
6
19
  const schema = host.getMsgSchema();
7
20
  const out = [];
8
21
  const seen = new Set();
@@ -10,10 +23,24 @@ export function handleListActions(host) {
10
23
  // either 'shared' (default) or, in the malformed case where someone
11
24
  // bound an `@agentOnly` Msg in a view, 'agent-only'. Either way the
12
25
  // agent can dispatch them.
26
+ //
27
+ // Filtered against the Msg schema: a binding whose variant isn't in
28
+ // the user's Msg union is a library-internal Msg leaking through
29
+ // `tagSend` translator wiring (the sortable component's `move`,
30
+ // `drop`, `cancel`, etc. — they're routed into the user's update.ts
31
+ // via a different shape but their lib names slip into the binding
32
+ // registry). The agent has no use for those names — `would_dispatch`
33
+ // / `send_message` would reject them as `unknown-variant` anyway —
34
+ // so they pollute the affordance list. When a schema is available,
35
+ // the schema's variant set is the source of truth.
13
36
  for (const d of descriptors) {
37
+ if (schema && !(d.variant in schema.variants))
38
+ continue;
14
39
  const ann = annotations[d.variant];
15
40
  if (ann?.dispatchMode === 'human-only')
16
41
  continue;
42
+ if (!passesRouteGate(ann, state))
43
+ continue;
17
44
  seen.add(d.variant);
18
45
  const variantSchema = schema?.variants[d.variant];
19
46
  out.push({
@@ -36,6 +63,8 @@ export function handleListActions(host) {
36
63
  const ann = annotations[msg.type];
37
64
  if (ann?.dispatchMode === 'human-only')
38
65
  continue;
66
+ if (!passesRouteGate(ann, state))
67
+ continue;
39
68
  seen.add(msg.type);
40
69
  const { type, ...rest } = msg;
41
70
  const variantSchema = schema?.variants[type];
@@ -67,6 +96,8 @@ export function handleListActions(host) {
67
96
  continue;
68
97
  if (!ann.alwaysAffordable)
69
98
  continue;
99
+ if (!passesRouteGate(ann, state))
100
+ continue;
70
101
  seen.add(variant);
71
102
  const fields = schema?.variants[variant];
72
103
  out.push({
@@ -93,6 +124,14 @@ export function handleListActions(host) {
93
124
  // their UI subtree is unmounted would pop hidden state in places the
94
125
  // user didn't navigate to. The explicit knobs are `@alwaysAffordable`
95
126
  // (handled above) or `agentAffordances(state)`.
127
+ //
128
+ // When the app provides `agentAffordances`, this pass is filtered:
129
+ // an `@agentOnly` variant only surfaces if the hook returned it.
130
+ // That makes route-gated bulk Msgs (`Matrix/AddCriteria` available
131
+ // only when a matrix is loaded) work as expected — they stop
132
+ // appearing on the home page just because they're tagged
133
+ // `@agentOnly`. Apps without `agentAffordances` keep the previous
134
+ // permissive default.
96
135
  if (schema) {
97
136
  for (const [variant, fields] of Object.entries(schema.variants)) {
98
137
  if (seen.has(variant))
@@ -100,6 +139,10 @@ export function handleListActions(host) {
100
139
  const ann = annotations[variant];
101
140
  if (ann?.dispatchMode !== 'agent-only')
102
141
  continue;
142
+ if (explicitAffordances && !affordanceVariants.has(variant))
143
+ continue;
144
+ if (!passesRouteGate(ann, state))
145
+ continue;
103
146
  out.push({
104
147
  variant,
105
148
  intent: ann.intent,
@@ -127,6 +170,47 @@ export function handleListActions(host) {
127
170
  * The first key is `type` so the payload reads as a complete Msg
128
171
  * shape — copy-paste-ready into `send_message`.
129
172
  */
173
+ /**
174
+ * Evaluate `@routeGated("predicate")` against the current state.
175
+ * Returns true (variant passes) when:
176
+ * - the variant has no `@routeGated` annotation, OR
177
+ * - the predicate evaluates truthy with `state` bound.
178
+ *
179
+ * Predicate is compiled lazily via `new Function('state', 'return (' +
180
+ * src + ')')` and cached in a module-level Map. Compile failures
181
+ * (syntactically broken predicates) degrade to "true" so a single
182
+ * malformed annotation doesn't paralyze the affordance pass — the
183
+ * build-time linter is the right place to catch syntactic issues.
184
+ * Evaluation throws fail-closed (return false) since a predicate that
185
+ * crashes on the current state shouldn't surface the variant.
186
+ */
187
+ function passesRouteGate(ann, state) {
188
+ const src = ann?.routeGate;
189
+ if (!src)
190
+ return true;
191
+ const predicate = compileRouteGate(src);
192
+ try {
193
+ return Boolean(predicate(state));
194
+ }
195
+ catch {
196
+ return false;
197
+ }
198
+ }
199
+ const routeGateCache = new Map();
200
+ function compileRouteGate(src) {
201
+ let fn = routeGateCache.get(src);
202
+ if (fn)
203
+ return fn;
204
+ try {
205
+ // eslint-disable-next-line @typescript-eslint/no-implied-eval
206
+ fn = new Function('state', `return (${src})`);
207
+ }
208
+ catch {
209
+ fn = () => true;
210
+ }
211
+ routeGateCache.set(src, fn);
212
+ return fn;
213
+ }
130
214
  function synthesizePayload(variant, fields) {
131
215
  const out = { type: variant };
132
216
  for (const [name, descriptor] of Object.entries(fields)) {
@@ -165,6 +249,15 @@ function walkHint(path, d, out) {
165
249
  if (typeof d.hint === 'string' && d.hint.length > 0) {
166
250
  out.push({ path, hint: d.hint });
167
251
  }
252
+ // `@validates(...)` predicates surface alongside `@should` hints
253
+ // so the agent sees the constraint at affordance time rather than
254
+ // only as a post-dispatch rejection. The verbatim predicate text
255
+ // is what the runtime evaluates; agents trained on JS read it
256
+ // directly. Prefix `validates: ` to disambiguate from freeform
257
+ // `@should` text.
258
+ if (typeof d.validates === 'string' && d.validates.length > 0) {
259
+ out.push({ path, hint: `validates: ${d.validates}` });
260
+ }
168
261
  walkHintBare(path, d.type, out);
169
262
  return;
170
263
  }