@llui/agent 0.0.36 → 0.0.37

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.
@@ -8,23 +8,31 @@ import { type CodecRegistry } from '../codecs.js';
8
8
  * from `@llui/vite-plugin/src/msg-schema.ts`. Three coexisting forms:
9
9
  *
10
10
  * 1. Bare primitive: `'string' | 'number' | 'boolean' | 'unknown'`
11
- * and bare enum: `{enum: [...]}`. Compact form for unannotated
12
- * required fields.
11
+ * and bare enum: `{enum: [...]}` (values may be string, number,
12
+ * or boolean — the compiler preserves the literal kind so JSON
13
+ * round-trips don't lose type info).
13
14
  * 2. Bare nested types: `{kind: 'object', shape}` for inline /
14
15
  * followed-via-typeIndex shapes; `{kind: 'array', element}` for
15
- * `T[]` / `readonly T[]` / `Array<T>`. The synthesizer recurses
16
- * to build copy-paste-ready nested examples.
16
+ * `T[]` / `readonly T[]` / `Array<T>`; `{kind: 'discriminated-
17
+ * union', discriminant, variants}` for tagged unions of objects
18
+ * (e.g. `Format = {kind:'exact'} | {kind:'range', min, max}`).
19
+ * The synthesizer recurses to build copy-paste-ready nested
20
+ * examples; the validator walks the same tree.
17
21
  * 3. Rich descriptor: wraps any of the above with `{optional?,
18
22
  * priority?, hint?}` carrying TS optionality and `@should` hints.
19
23
  */
20
24
  export type MsgSchemaBareType = string | {
21
- enum: string[];
25
+ enum: ReadonlyArray<string | number | boolean>;
22
26
  } | {
23
27
  kind: 'object';
24
28
  shape: Record<string, MsgSchemaField>;
25
29
  } | {
26
30
  kind: 'array';
27
31
  element: MsgSchemaBareType;
32
+ } | {
33
+ kind: 'discriminated-union';
34
+ discriminant: string;
35
+ variants: Record<string, Record<string, MsgSchemaField>>;
28
36
  };
29
37
  export type MsgSchemaField = MsgSchemaBareType | {
30
38
  type: MsgSchemaBareType;
@@ -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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"}
@@ -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: [...]}`. 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
+ {"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\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"]}
@@ -183,6 +183,29 @@ function walkHintBare(path, t, out) {
183
183
  if (obj.kind === 'array') {
184
184
  walkHint(`${path}[]`, obj.element, out);
185
185
  }
186
+ if (obj.kind === 'discriminated-union') {
187
+ // Synthetic hint at the union's path summarizing the legal
188
+ // discriminant values. Lets the agent see "format expects one of:
189
+ // exact, range, compound" without walking the full schema. Then
190
+ // walk each branch's per-field hints with a path-suffix that
191
+ // disambiguates which branch the hint applies to.
192
+ const variants = obj.variants;
193
+ const discriminant = String(obj.discriminant);
194
+ const legalValues = Object.keys(variants);
195
+ if (legalValues.length > 0) {
196
+ out.push({
197
+ path,
198
+ hint: `Discriminated union — set \`${discriminant}\` to one of: ${legalValues
199
+ .map((v) => `'${v}'`)
200
+ .join(', ')}.`,
201
+ });
202
+ }
203
+ for (const [discValue, fields] of Object.entries(variants)) {
204
+ for (const [fieldName, fieldDesc] of Object.entries(fields)) {
205
+ walkHint(`${path}(${discriminant}=${discValue}).${fieldName}`, fieldDesc, out);
206
+ }
207
+ }
208
+ }
186
209
  }
187
210
  function isOptional(d) {
188
211
  return typeof d === 'object' && 'type' in d && d.optional === true;
@@ -211,7 +234,9 @@ function synthesizeBare(t) {
211
234
  return null;
212
235
  const obj = t;
213
236
  if ('enum' in obj && Array.isArray(obj.enum)) {
214
- // First option doubles as the canonical example.
237
+ // First option doubles as the canonical example. Native value type
238
+ // round-trips (string/number/boolean) since the compiler preserved
239
+ // the literal kind on emit.
215
240
  return obj.enum[0] ?? null;
216
241
  }
217
242
  if (obj.kind === 'object' && obj.shape !== null && typeof obj.shape === 'object') {
@@ -233,6 +258,30 @@ function synthesizeBare(t) {
233
258
  // see the per-entry shape without us guessing at array length.
234
259
  return [synthesizeBare(obj.element)];
235
260
  }
261
+ if (obj.kind === 'discriminated-union') {
262
+ // Synthesize the FIRST branch as the concrete example. The full
263
+ // shape (every legal branch + its payload) is preserved in
264
+ // `description.messages.variants[X]` from `describe_app`, so the
265
+ // agent that needs another branch reads the schema directly.
266
+ // `collectFieldHints` adds a synthetic enumeration of the legal
267
+ // `<discriminant>` values onto the hint surface so the agent
268
+ // doesn't have to dig into the schema for the simple case.
269
+ const variants = obj.variants;
270
+ const discriminant = String(obj.discriminant);
271
+ const firstEntry = Object.entries(variants).at(0);
272
+ if (!firstEntry)
273
+ return null;
274
+ const [firstValue, firstFields] = firstEntry;
275
+ const branch = { [discriminant]: firstValue };
276
+ for (const [name, descriptor] of Object.entries(firstFields)) {
277
+ const isOpt = isOptional(descriptor);
278
+ const pri = isShould(descriptor);
279
+ if (isOpt && pri !== 'should')
280
+ continue;
281
+ branch[name] = exampleValue(descriptor);
282
+ }
283
+ return branch;
284
+ }
236
285
  return null;
237
286
  }
238
287
  //# sourceMappingURL=list-actions.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"list-actions.js","sourceRoot":"","sources":["../../../src/client/rpc/list-actions.ts"],"names":[],"mappings":"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","sourcesContent":["import type { MessageAnnotations } from '../../protocol.js'\nimport type { MsgSchemaShape, MsgSchemaField } from '../factory.js'\n\ntype Binding = { variant: string }\ntype Annotations = Record<string, MessageAnnotations>\n\nexport type ListActionsHost = {\n getState(): unknown\n getBindingDescriptors(): Binding[] | null\n getMsgAnnotations(): Annotations | null\n getMsgSchema(): MsgSchemaShape | null\n getAgentAffordances(): ((state: unknown) => Array<{ type: string; [k: string]: unknown }>) | null\n}\n\n/**\n * `dispatchMode` on each action is `'shared'` (human can also click via\n * a UI affordance) or `'agent-only'` (no UI binding — agent is the only\n * dispatcher). `'human-only'` variants are filtered out before this\n * point — they never reach the LLM.\n *\n * `source` distinguishes WHERE the affordance came from:\n * - `'binding'` — a tagged event handler is currently mounted in a\n * live scope (refcount > 0). Variants inside dead branches —\n * `show({when: false})`, unmounted `branch()` cases, removed `each`\n * items — auto-vanish from this set as their lifetimes dispose.\n * This is the framework's \"what can the user click right now\"\n * answer, and it's the default surface for the agent.\n * - `'always-affordable'` — either the app's `agentAffordances(state)`\n * hook listed the variant, or the variant carries the\n * `@alwaysAffordable` JSDoc tag. Both are the explicit \"agent can\n * reach this even when no live UI binding maps to it\" knob — bulk\n * seed ops (`Matrix/AddAlternatives`) and similar agent-driven\n * paths typically land here.\n * - `'schema'` — variant is annotated `@agentOnly` (the canonical\n * \"no UI button maps to this; the agent is the only dispatcher\")\n * and isn't already covered above. The payload is schema-synthesized\n * and the agent fills it in.\n *\n * `'shared'` variants WITHOUT a live binding, without\n * `agentAffordances` mention, and without `@alwaysAffordable` are\n * **deliberately hidden**. They're reachable through UI navigation —\n * the human user can't click them right now, and dispatching them\n * blindly would mutate state that drives `show()`/`branch()` gates,\n * popping hidden UI subtrees into view in places the user didn't\n * navigate to.\n */\nexport type ListActionsResult = {\n actions: Array<{\n variant: string\n /**\n * Human-readable phrase from `@intent(\"…\")`, or `null` when the\n * variant is unannotated. Mirror of LapActionsResponse.intent —\n * callers should treat `null` as a documentation gap and not as\n * \"missing label, fall back to variant name\".\n */\n intent: string | null\n requiresConfirm: boolean\n dispatchMode: 'shared' | 'agent-only'\n source: 'binding' | 'always-affordable' | 'schema'\n selectorHint: string | null\n payloadHint: object | null\n /** Cautionary text from `@warning` JSDoc, or null. */\n warning: string | null\n /** Concrete examples from `@example` JSDoc, in source order. */\n examples: string[]\n /**\n * Effect kinds this variant emits, from `@emits(\"k1\", \"k2\")`.\n * Empty when not annotated. Lets the agent know what side\n * effects fire — useful for batching (\"100 dispatches × cloud-\n * save = bad\") and for confirming destructive flows.\n */\n emits: string[]\n /**\n * Per-field guidance lifted from `@should(\"…\")` JSDoc on payload\n * fields. Path is dot/bracket notation rooted at the payload\n * (e.g. `\"cells\"` for a top-level field, `\"cells[].meta\"` for an\n * array element's nested field). Useful when the field is typed\n * as `unknown` or as a polymorphic shape — the hint says \"type\n * matches the criterion's kind: number for quantity, …\" so the\n * agent doesn't have to guess from the bare schema.\n *\n * Empty when no field on this variant carries an `@should` hint.\n */\n fieldHints: Array<{ path: string; hint: string }>\n }>\n}\n\nexport function handleListActions(host: ListActionsHost): ListActionsResult {\n const annotations = host.getMsgAnnotations() ?? {}\n const state = host.getState()\n const descriptors = host.getBindingDescriptors() ?? []\n const affordances = host.getAgentAffordances()?.(state) ?? []\n const schema = host.getMsgSchema()\n\n const out: ListActionsResult['actions'] = []\n const seen = new Set<string>()\n\n // From bindings — these have UI affordances by definition, so they're\n // either 'shared' (default) or, in the malformed case where someone\n // bound an `@agentOnly` Msg in a view, 'agent-only'. Either way the\n // agent can dispatch them.\n for (const d of descriptors) {\n const ann = annotations[d.variant]\n if (ann?.dispatchMode === 'human-only') continue\n seen.add(d.variant)\n const variantSchema = schema?.variants[d.variant]\n out.push({\n variant: d.variant,\n intent: ann?.intent ?? null,\n requiresConfirm: ann?.requiresConfirm ?? false,\n dispatchMode: ann?.dispatchMode === 'agent-only' ? 'agent-only' : 'shared',\n source: 'binding',\n selectorHint: null,\n payloadHint: null,\n warning: ann?.warning ?? null,\n examples: ann?.examples ?? [],\n emits: ann?.emits ?? [],\n fieldHints: variantSchema ? collectFieldHints(variantSchema) : [],\n })\n }\n\n // From `agentAffordances(state)` — the integrator's explicit list of\n // currently-reachable Msgs, with concrete payloads they author.\n for (const msg of affordances) {\n const ann = annotations[msg.type]\n if (ann?.dispatchMode === 'human-only') continue\n seen.add(msg.type)\n const { type, ...rest } = msg\n const variantSchema = schema?.variants[type]\n out.push({\n variant: type,\n intent: ann?.intent ?? null,\n requiresConfirm: ann?.requiresConfirm ?? false,\n dispatchMode: ann?.dispatchMode === 'agent-only' ? 'agent-only' : 'shared',\n source: 'always-affordable',\n selectorHint: null,\n payloadHint: Object.keys(rest).length > 0 ? rest : null,\n warning: ann?.warning ?? null,\n examples: ann?.examples ?? [],\n emits: ann?.emits ?? [],\n fieldHints: variantSchema ? collectFieldHints(variantSchema) : [],\n })\n }\n\n // From `@alwaysAffordable` annotations — the per-variant equivalent\n // of `agentAffordances`. Variants tagged this way surface regardless\n // of whether a live binding maps to them, so bulk seed ops\n // (`Matrix/AddAlternatives`) and similar agent-driven paths are\n // available even when their UI counterparts (if any) aren't mounted.\n // The payload is schema-synthesized — `agentAffordances` is the way\n // to ship a concrete pre-filled payload alongside the affordance.\n for (const [variant, ann] of Object.entries(annotations)) {\n if (seen.has(variant)) continue\n if (ann.dispatchMode === 'human-only') continue\n if (!ann.alwaysAffordable) continue\n seen.add(variant)\n const fields = schema?.variants[variant]\n out.push({\n variant,\n intent: ann.intent,\n requiresConfirm: ann.requiresConfirm,\n dispatchMode: ann.dispatchMode === 'agent-only' ? 'agent-only' : 'shared',\n source: 'always-affordable',\n selectorHint: null,\n payloadHint: fields ? synthesizePayload(variant, fields) : null,\n warning: ann.warning,\n examples: ann.examples,\n emits: ann.emits,\n fieldHints: fields ? collectFieldHints(fields) : [],\n })\n }\n\n // From schema — variants that aren't already surfaced above and that\n // the author marked `@agentOnly`. The canonical \"no UI button maps\n // to this; the agent is the only dispatcher.\" Bulk edits, imports,\n // admin operations that have no human-facing affordance at all.\n //\n // `'shared'` variants WITHOUT a live binding stay hidden here — they\n // are reachable through UI navigation, and dispatching them while\n // their UI subtree is unmounted would pop hidden state in places the\n // user didn't navigate to. The explicit knobs are `@alwaysAffordable`\n // (handled above) or `agentAffordances(state)`.\n if (schema) {\n for (const [variant, fields] of Object.entries(schema.variants)) {\n if (seen.has(variant)) continue\n const ann = annotations[variant]\n if (ann?.dispatchMode !== 'agent-only') continue\n out.push({\n variant,\n intent: ann.intent,\n requiresConfirm: ann.requiresConfirm,\n dispatchMode: 'agent-only',\n source: 'schema',\n selectorHint: null,\n payloadHint: synthesizePayload(variant, fields),\n warning: ann.warning,\n examples: ann.examples,\n emits: ann.emits,\n fieldHints: collectFieldHints(fields),\n })\n }\n }\n\n return { actions: out }\n}\n\n/**\n * Build an example payload object the LLM can fill in. Required\n * fields always appear; optional fields appear only when annotated\n * `@should` (LLM is encouraged to fill them in). Fields without a\n * concrete primitive type (`'unknown'`) emit `null` placeholders the\n * LLM is expected to replace.\n *\n * The first key is `type` so the payload reads as a complete Msg\n * shape — copy-paste-ready into `send_message`.\n */\nfunction synthesizePayload(variant: string, fields: Record<string, MsgSchemaField>): object {\n const out: Record<string, unknown> = { type: variant }\n for (const [name, descriptor] of Object.entries(fields)) {\n const optional = isOptional(descriptor)\n const priority = isShould(descriptor)\n // Skip optional fields unless they're @should-flagged. Required\n // fields always appear.\n if (optional && priority !== 'should') continue\n out[name] = exampleValue(descriptor)\n }\n return out\n}\n\n/**\n * Walk a variant's field tree and collect every `@should` hint into a\n * flat list keyed by field path. Path conventions:\n * - top-level field: `\"cells\"`\n * - nested object property: `\"cells.value\"`\n * - array element: `\"cells[]\"` for the element itself; descendants\n * use `\"cells[].meta\"` and so on.\n *\n * Surfaces the same hints that show up nested inside\n * `description.messages.variants[X].field.hint` so callers don't have\n * to dig through the schema tree to find them.\n */\nfunction collectFieldHints(\n fields: Record<string, MsgSchemaField>,\n): Array<{ path: string; hint: string }> {\n const out: Array<{ path: string; hint: string }> = []\n for (const [name, descriptor] of Object.entries(fields)) {\n walkHint(name, descriptor, out)\n }\n return out\n}\n\nfunction walkHint(\n path: string,\n d: MsgSchemaField,\n out: Array<{ path: string; hint: string }>,\n): void {\n // Rich descriptor with a hint at this position.\n if (typeof d === 'object' && d !== null && 'type' in d) {\n if (typeof d.hint === 'string' && d.hint.length > 0) {\n out.push({ path, hint: d.hint })\n }\n walkHintBare(path, d.type, out)\n return\n }\n walkHintBare(path, d, out)\n}\n\nfunction walkHintBare(path: string, t: unknown, out: Array<{ path: string; hint: string }>): void {\n if (t === null || typeof t !== 'object') return\n const obj = t as Record<string, unknown>\n if (obj.kind === 'object' && obj.shape !== null && typeof obj.shape === 'object') {\n for (const [name, descriptor] of Object.entries(obj.shape as Record<string, MsgSchemaField>)) {\n walkHint(`${path}.${name}`, descriptor, out)\n }\n return\n }\n if (obj.kind === 'array') {\n walkHint(`${path}[]`, obj.element as MsgSchemaField, out)\n }\n}\n\nfunction isOptional(d: MsgSchemaField): boolean {\n return typeof d === 'object' && 'type' in d && d.optional === true\n}\n\nfunction isShould(d: MsgSchemaField): 'should' | undefined {\n return typeof d === 'object' && 'type' in d ? d.priority : undefined\n}\n\nfunction exampleValue(d: MsgSchemaField): unknown {\n // Unwrap rich descriptor to get the bare type for synthesis.\n const t =\n typeof d === 'object' && 'type' in d\n ? d.type\n : (d as\n | Exclude<MsgSchemaField, object>\n | Extract<MsgSchemaField, { kind?: string; enum?: string[] }>)\n return synthesizeBare(t as never)\n}\n\nfunction synthesizeBare(t: unknown): unknown {\n if (typeof t === 'string') {\n if (t === 'string') return ''\n if (t === 'number') return 0\n if (t === 'boolean') return false\n return null // 'unknown' or unrecognized keyword → placeholder\n }\n if (t === null || typeof t !== 'object') return null\n const obj = t as Record<string, unknown>\n if ('enum' in obj && Array.isArray(obj.enum)) {\n // First option doubles as the canonical example.\n return obj.enum[0] ?? null\n }\n if (obj.kind === 'object' && obj.shape !== null && typeof obj.shape === 'object') {\n // Recurse into the nested shape. Same optional-skip rule as the\n // top-level synthesizer: required fields appear, optional ones\n // appear only when @should-flagged.\n const out: Record<string, unknown> = {}\n for (const [name, descriptor] of Object.entries(obj.shape as Record<string, MsgSchemaField>)) {\n const isOpt = isOptional(descriptor)\n const pri = isShould(descriptor)\n if (isOpt && pri !== 'should') continue\n out[name] = exampleValue(descriptor)\n }\n return out\n }\n if (obj.kind === 'array') {\n // Wrap the synthesized element in a one-item array. Lets the LLM\n // see the per-entry shape without us guessing at array length.\n return [synthesizeBare(obj.element)]\n }\n return null\n}\n"]}
1
+ {"version":3,"file":"list-actions.js","sourceRoot":"","sources":["../../../src/client/rpc/list-actions.ts"],"names":[],"mappings":"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","sourcesContent":["import type { MessageAnnotations } from '../../protocol.js'\nimport type { MsgSchemaShape, MsgSchemaField } from '../factory.js'\n\ntype Binding = { variant: string }\ntype Annotations = Record<string, MessageAnnotations>\n\nexport type ListActionsHost = {\n getState(): unknown\n getBindingDescriptors(): Binding[] | null\n getMsgAnnotations(): Annotations | null\n getMsgSchema(): MsgSchemaShape | null\n getAgentAffordances(): ((state: unknown) => Array<{ type: string; [k: string]: unknown }>) | null\n}\n\n/**\n * `dispatchMode` on each action is `'shared'` (human can also click via\n * a UI affordance) or `'agent-only'` (no UI binding — agent is the only\n * dispatcher). `'human-only'` variants are filtered out before this\n * point — they never reach the LLM.\n *\n * `source` distinguishes WHERE the affordance came from:\n * - `'binding'` — a tagged event handler is currently mounted in a\n * live scope (refcount > 0). Variants inside dead branches —\n * `show({when: false})`, unmounted `branch()` cases, removed `each`\n * items — auto-vanish from this set as their lifetimes dispose.\n * This is the framework's \"what can the user click right now\"\n * answer, and it's the default surface for the agent.\n * - `'always-affordable'` — either the app's `agentAffordances(state)`\n * hook listed the variant, or the variant carries the\n * `@alwaysAffordable` JSDoc tag. Both are the explicit \"agent can\n * reach this even when no live UI binding maps to it\" knob — bulk\n * seed ops (`Matrix/AddAlternatives`) and similar agent-driven\n * paths typically land here.\n * - `'schema'` — variant is annotated `@agentOnly` (the canonical\n * \"no UI button maps to this; the agent is the only dispatcher\")\n * and isn't already covered above. The payload is schema-synthesized\n * and the agent fills it in.\n *\n * `'shared'` variants WITHOUT a live binding, without\n * `agentAffordances` mention, and without `@alwaysAffordable` are\n * **deliberately hidden**. They're reachable through UI navigation —\n * the human user can't click them right now, and dispatching them\n * blindly would mutate state that drives `show()`/`branch()` gates,\n * popping hidden UI subtrees into view in places the user didn't\n * navigate to.\n */\nexport type ListActionsResult = {\n actions: Array<{\n variant: string\n /**\n * Human-readable phrase from `@intent(\"…\")`, or `null` when the\n * variant is unannotated. Mirror of LapActionsResponse.intent —\n * callers should treat `null` as a documentation gap and not as\n * \"missing label, fall back to variant name\".\n */\n intent: string | null\n requiresConfirm: boolean\n dispatchMode: 'shared' | 'agent-only'\n source: 'binding' | 'always-affordable' | 'schema'\n selectorHint: string | null\n payloadHint: object | null\n /** Cautionary text from `@warning` JSDoc, or null. */\n warning: string | null\n /** Concrete examples from `@example` JSDoc, in source order. */\n examples: string[]\n /**\n * Effect kinds this variant emits, from `@emits(\"k1\", \"k2\")`.\n * Empty when not annotated. Lets the agent know what side\n * effects fire — useful for batching (\"100 dispatches × cloud-\n * save = bad\") and for confirming destructive flows.\n */\n emits: string[]\n /**\n * Per-field guidance lifted from `@should(\"…\")` JSDoc on payload\n * fields. Path is dot/bracket notation rooted at the payload\n * (e.g. `\"cells\"` for a top-level field, `\"cells[].meta\"` for an\n * array element's nested field). Useful when the field is typed\n * as `unknown` or as a polymorphic shape — the hint says \"type\n * matches the criterion's kind: number for quantity, …\" so the\n * agent doesn't have to guess from the bare schema.\n *\n * Empty when no field on this variant carries an `@should` hint.\n */\n fieldHints: Array<{ path: string; hint: string }>\n }>\n}\n\nexport function handleListActions(host: ListActionsHost): ListActionsResult {\n const annotations = host.getMsgAnnotations() ?? {}\n const state = host.getState()\n const descriptors = host.getBindingDescriptors() ?? []\n const affordances = host.getAgentAffordances()?.(state) ?? []\n const schema = host.getMsgSchema()\n\n const out: ListActionsResult['actions'] = []\n const seen = new Set<string>()\n\n // From bindings — these have UI affordances by definition, so they're\n // either 'shared' (default) or, in the malformed case where someone\n // bound an `@agentOnly` Msg in a view, 'agent-only'. Either way the\n // agent can dispatch them.\n for (const d of descriptors) {\n const ann = annotations[d.variant]\n if (ann?.dispatchMode === 'human-only') continue\n seen.add(d.variant)\n const variantSchema = schema?.variants[d.variant]\n out.push({\n variant: d.variant,\n intent: ann?.intent ?? null,\n requiresConfirm: ann?.requiresConfirm ?? false,\n dispatchMode: ann?.dispatchMode === 'agent-only' ? 'agent-only' : 'shared',\n source: 'binding',\n selectorHint: null,\n payloadHint: null,\n warning: ann?.warning ?? null,\n examples: ann?.examples ?? [],\n emits: ann?.emits ?? [],\n fieldHints: variantSchema ? collectFieldHints(variantSchema) : [],\n })\n }\n\n // From `agentAffordances(state)` — the integrator's explicit list of\n // currently-reachable Msgs, with concrete payloads they author.\n for (const msg of affordances) {\n const ann = annotations[msg.type]\n if (ann?.dispatchMode === 'human-only') continue\n seen.add(msg.type)\n const { type, ...rest } = msg\n const variantSchema = schema?.variants[type]\n out.push({\n variant: type,\n intent: ann?.intent ?? null,\n requiresConfirm: ann?.requiresConfirm ?? false,\n dispatchMode: ann?.dispatchMode === 'agent-only' ? 'agent-only' : 'shared',\n source: 'always-affordable',\n selectorHint: null,\n payloadHint: Object.keys(rest).length > 0 ? rest : null,\n warning: ann?.warning ?? null,\n examples: ann?.examples ?? [],\n emits: ann?.emits ?? [],\n fieldHints: variantSchema ? collectFieldHints(variantSchema) : [],\n })\n }\n\n // From `@alwaysAffordable` annotations — the per-variant equivalent\n // of `agentAffordances`. Variants tagged this way surface regardless\n // of whether a live binding maps to them, so bulk seed ops\n // (`Matrix/AddAlternatives`) and similar agent-driven paths are\n // available even when their UI counterparts (if any) aren't mounted.\n // The payload is schema-synthesized — `agentAffordances` is the way\n // to ship a concrete pre-filled payload alongside the affordance.\n for (const [variant, ann] of Object.entries(annotations)) {\n if (seen.has(variant)) continue\n if (ann.dispatchMode === 'human-only') continue\n if (!ann.alwaysAffordable) continue\n seen.add(variant)\n const fields = schema?.variants[variant]\n out.push({\n variant,\n intent: ann.intent,\n requiresConfirm: ann.requiresConfirm,\n dispatchMode: ann.dispatchMode === 'agent-only' ? 'agent-only' : 'shared',\n source: 'always-affordable',\n selectorHint: null,\n payloadHint: fields ? synthesizePayload(variant, fields) : null,\n warning: ann.warning,\n examples: ann.examples,\n emits: ann.emits,\n fieldHints: fields ? collectFieldHints(fields) : [],\n })\n }\n\n // From schema — variants that aren't already surfaced above and that\n // the author marked `@agentOnly`. The canonical \"no UI button maps\n // to this; the agent is the only dispatcher.\" Bulk edits, imports,\n // admin operations that have no human-facing affordance at all.\n //\n // `'shared'` variants WITHOUT a live binding stay hidden here — they\n // are reachable through UI navigation, and dispatching them while\n // their UI subtree is unmounted would pop hidden state in places the\n // user didn't navigate to. The explicit knobs are `@alwaysAffordable`\n // (handled above) or `agentAffordances(state)`.\n if (schema) {\n for (const [variant, fields] of Object.entries(schema.variants)) {\n if (seen.has(variant)) continue\n const ann = annotations[variant]\n if (ann?.dispatchMode !== 'agent-only') continue\n out.push({\n variant,\n intent: ann.intent,\n requiresConfirm: ann.requiresConfirm,\n dispatchMode: 'agent-only',\n source: 'schema',\n selectorHint: null,\n payloadHint: synthesizePayload(variant, fields),\n warning: ann.warning,\n examples: ann.examples,\n emits: ann.emits,\n fieldHints: collectFieldHints(fields),\n })\n }\n }\n\n return { actions: out }\n}\n\n/**\n * Build an example payload object the LLM can fill in. Required\n * fields always appear; optional fields appear only when annotated\n * `@should` (LLM is encouraged to fill them in). Fields without a\n * concrete primitive type (`'unknown'`) emit `null` placeholders the\n * LLM is expected to replace.\n *\n * The first key is `type` so the payload reads as a complete Msg\n * shape — copy-paste-ready into `send_message`.\n */\nfunction synthesizePayload(variant: string, fields: Record<string, MsgSchemaField>): object {\n const out: Record<string, unknown> = { type: variant }\n for (const [name, descriptor] of Object.entries(fields)) {\n const optional = isOptional(descriptor)\n const priority = isShould(descriptor)\n // Skip optional fields unless they're @should-flagged. Required\n // fields always appear.\n if (optional && priority !== 'should') continue\n out[name] = exampleValue(descriptor)\n }\n return out\n}\n\n/**\n * Walk a variant's field tree and collect every `@should` hint into a\n * flat list keyed by field path. Path conventions:\n * - top-level field: `\"cells\"`\n * - nested object property: `\"cells.value\"`\n * - array element: `\"cells[]\"` for the element itself; descendants\n * use `\"cells[].meta\"` and so on.\n *\n * Surfaces the same hints that show up nested inside\n * `description.messages.variants[X].field.hint` so callers don't have\n * to dig through the schema tree to find them.\n */\nfunction collectFieldHints(\n fields: Record<string, MsgSchemaField>,\n): Array<{ path: string; hint: string }> {\n const out: Array<{ path: string; hint: string }> = []\n for (const [name, descriptor] of Object.entries(fields)) {\n walkHint(name, descriptor, out)\n }\n return out\n}\n\nfunction walkHint(\n path: string,\n d: MsgSchemaField,\n out: Array<{ path: string; hint: string }>,\n): void {\n // Rich descriptor with a hint at this position.\n if (typeof d === 'object' && d !== null && 'type' in d) {\n if (typeof d.hint === 'string' && d.hint.length > 0) {\n out.push({ path, hint: d.hint })\n }\n walkHintBare(path, d.type, out)\n return\n }\n walkHintBare(path, d, out)\n}\n\nfunction walkHintBare(path: string, t: unknown, out: Array<{ path: string; hint: string }>): void {\n if (t === null || typeof t !== 'object') return\n const obj = t as Record<string, unknown>\n if (obj.kind === 'object' && obj.shape !== null && typeof obj.shape === 'object') {\n for (const [name, descriptor] of Object.entries(obj.shape as Record<string, MsgSchemaField>)) {\n walkHint(`${path}.${name}`, descriptor, out)\n }\n return\n }\n if (obj.kind === 'array') {\n walkHint(`${path}[]`, obj.element as MsgSchemaField, out)\n }\n if (obj.kind === 'discriminated-union') {\n // Synthetic hint at the union's path summarizing the legal\n // discriminant values. Lets the agent see \"format expects one of:\n // exact, range, compound\" without walking the full schema. Then\n // walk each branch's per-field hints with a path-suffix that\n // disambiguates which branch the hint applies to.\n const variants = obj.variants as Record<string, Record<string, MsgSchemaField>>\n const discriminant = String(obj.discriminant)\n const legalValues = Object.keys(variants)\n if (legalValues.length > 0) {\n out.push({\n path,\n hint: `Discriminated union — set \\`${discriminant}\\` to one of: ${legalValues\n .map((v) => `'${v}'`)\n .join(', ')}.`,\n })\n }\n for (const [discValue, fields] of Object.entries(variants)) {\n for (const [fieldName, fieldDesc] of Object.entries(fields)) {\n walkHint(`${path}(${discriminant}=${discValue}).${fieldName}`, fieldDesc, out)\n }\n }\n }\n}\n\nfunction isOptional(d: MsgSchemaField): boolean {\n return typeof d === 'object' && 'type' in d && d.optional === true\n}\n\nfunction isShould(d: MsgSchemaField): 'should' | undefined {\n return typeof d === 'object' && 'type' in d ? d.priority : undefined\n}\n\nfunction exampleValue(d: MsgSchemaField): unknown {\n // Unwrap rich descriptor to get the bare type for synthesis.\n const t =\n typeof d === 'object' && 'type' in d\n ? d.type\n : (d as\n | Exclude<MsgSchemaField, object>\n | Extract<MsgSchemaField, { kind?: string; enum?: string[] }>)\n return synthesizeBare(t as never)\n}\n\nfunction synthesizeBare(t: unknown): unknown {\n if (typeof t === 'string') {\n if (t === 'string') return ''\n if (t === 'number') return 0\n if (t === 'boolean') return false\n return null // 'unknown' or unrecognized keyword → placeholder\n }\n if (t === null || typeof t !== 'object') return null\n const obj = t as Record<string, unknown>\n if ('enum' in obj && Array.isArray(obj.enum)) {\n // First option doubles as the canonical example. Native value type\n // round-trips (string/number/boolean) since the compiler preserved\n // the literal kind on emit.\n return obj.enum[0] ?? null\n }\n if (obj.kind === 'object' && obj.shape !== null && typeof obj.shape === 'object') {\n // Recurse into the nested shape. Same optional-skip rule as the\n // top-level synthesizer: required fields appear, optional ones\n // appear only when @should-flagged.\n const out: Record<string, unknown> = {}\n for (const [name, descriptor] of Object.entries(obj.shape as Record<string, MsgSchemaField>)) {\n const isOpt = isOptional(descriptor)\n const pri = isShould(descriptor)\n if (isOpt && pri !== 'should') continue\n out[name] = exampleValue(descriptor)\n }\n return out\n }\n if (obj.kind === 'array') {\n // Wrap the synthesized element in a one-item array. Lets the LLM\n // see the per-entry shape without us guessing at array length.\n return [synthesizeBare(obj.element)]\n }\n if (obj.kind === 'discriminated-union') {\n // Synthesize the FIRST branch as the concrete example. The full\n // shape (every legal branch + its payload) is preserved in\n // `description.messages.variants[X]` from `describe_app`, so the\n // agent that needs another branch reads the schema directly.\n // `collectFieldHints` adds a synthetic enumeration of the legal\n // `<discriminant>` values onto the hint surface so the agent\n // doesn't have to dig into the schema for the simple case.\n const variants = obj.variants as Record<string, Record<string, MsgSchemaField>>\n const discriminant = String(obj.discriminant)\n const firstEntry = Object.entries(variants).at(0)\n if (!firstEntry) return null\n const [firstValue, firstFields] = firstEntry\n const branch: Record<string, unknown> = { [discriminant]: firstValue }\n for (const [name, descriptor] of Object.entries(firstFields)) {\n const isOpt = isOptional(descriptor)\n const pri = isShould(descriptor)\n if (isOpt && pri !== 'should') continue\n branch[name] = exampleValue(descriptor)\n }\n return branch\n }\n return null\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"send-message.d.ts","sourceRoot":"","sources":["../../../src/client/rpc/send-message.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAG3E,OAAO,KAAK,EACV,kBAAkB,EAClB,YAAY,EACZ,kBAAkB,EAClB,kBAAkB,EACnB,MAAM,mBAAmB,CAAA;AAE1B,MAAM,MAAM,eAAe,GAAG;IAC5B,GAAG,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAA;IAC3C,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,4DAA4D;IAC5D,OAAO,CAAC,EAAE,SAAS,GAAG,MAAM,GAAG,MAAM,CAAA;IACrC,6DAA6D;IAC7D,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,2DAA2D;IAC3D,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,6DAA6D;IAC7D,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB,CAAA;AAED,MAAM,MAAM,eAAe,GAAG,eAAe,GAAG;IAC9C,QAAQ,IAAI,OAAO,CAAA;IACnB,IAAI,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAAA;IACxB,KAAK,IAAI,IAAI,CAAA;IACb;;;;;;;;OAQG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAA;IAC3C,iBAAiB,IAAI,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,GAAG,IAAI,CAAA;IAC9D;;;;;;;;OAQG;IACH,sBAAsB,CAAC,EAAE,MAAM,YAAY,CAAC,QAAQ,CAAC,CAAA;IACrD,2EAA2E;IAC3E,cAAc,CAAC,KAAK,EAAE;QACpB,EAAE,EAAE,MAAM,CAAA;QACV,OAAO,EAAE,MAAM,CAAA;QACf,OAAO,EAAE,OAAO,CAAA;QAChB,MAAM,EAAE,MAAM,CAAA;QACd,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;QACrB,UAAU,EAAE,MAAM,CAAA;QAClB,MAAM,EAAE,SAAS,CAAA;KAClB,GAAG,IAAI,CAAA;CACT,CAAA;AAKD,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,eAAe,EACrB,IAAI,EAAE,eAAe,GACpB,OAAO,CAAC,kBAAkB,CAAC,CA8I7B;AA6CD,MAAM,MAAM,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,EAAE;IAAE,MAAM,EAAE,YAAY,CAAA;CAAE,CAAC,CAAA;AACtF,YAAY,EAAE,kBAAkB,EAAE,CAAA"}
1
+ {"version":3,"file":"send-message.d.ts","sourceRoot":"","sources":["../../../src/client/rpc/send-message.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAG3E,OAAO,KAAK,EACV,kBAAkB,EAClB,YAAY,EACZ,kBAAkB,EAClB,kBAAkB,EACnB,MAAM,mBAAmB,CAAA;AAE1B,MAAM,MAAM,eAAe,GAAG;IAC5B,GAAG,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAA;IAC3C,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,4DAA4D;IAC5D,OAAO,CAAC,EAAE,SAAS,GAAG,MAAM,GAAG,MAAM,CAAA;IACrC,6DAA6D;IAC7D,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,2DAA2D;IAC3D,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,6DAA6D;IAC7D,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB,CAAA;AAED,MAAM,MAAM,eAAe,GAAG,eAAe,GAAG;IAC9C,QAAQ,IAAI,OAAO,CAAA;IACnB,IAAI,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAAA;IACxB,KAAK,IAAI,IAAI,CAAA;IACb;;;;;;;;OAQG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAA;IAC3C,iBAAiB,IAAI,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,GAAG,IAAI,CAAA;IAC9D;;;;;;;;OAQG;IACH,sBAAsB,CAAC,EAAE,MAAM,YAAY,CAAC,QAAQ,CAAC,CAAA;IACrD,2EAA2E;IAC3E,cAAc,CAAC,KAAK,EAAE;QACpB,EAAE,EAAE,MAAM,CAAA;QACV,OAAO,EAAE,MAAM,CAAA;QACf,OAAO,EAAE,OAAO,CAAA;QAChB,MAAM,EAAE,MAAM,CAAA;QACd,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;QACrB,UAAU,EAAE,MAAM,CAAA;QAClB,MAAM,EAAE,SAAS,CAAA;KAClB,GAAG,IAAI,CAAA;CACT,CAAA;AAKD,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,eAAe,EACrB,IAAI,EAAE,eAAe,GACpB,OAAO,CAAC,kBAAkB,CAAC,CAgJ7B;AA6CD,MAAM,MAAM,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,EAAE;IAAE,MAAM,EAAE,YAAY,CAAA;CAAE,CAAC,CAAA;AACtF,YAAY,EAAE,kBAAkB,EAAE,CAAA"}
@@ -1,6 +1,7 @@
1
1
  import { randomUUID } from '../uuid.js';
2
2
  import { handleListActions } from './list-actions.js';
3
3
  import { computeStateDiff } from '../../state-diff.js';
4
+ import { validatePayload } from './validate-payload.js';
4
5
  const DEFAULT_QUIET_MS = 100;
5
6
  const DEFAULT_TIMEOUT_MS = 5_000;
6
7
  export async function handleSendMessage(host, args) {
@@ -22,16 +23,18 @@ export async function handleSendMessage(host, args) {
22
23
  // Schema validation: when the compiler emitted a `__msgSchema`,
23
24
  // check the payload against this variant's field shape before
24
25
  // dispatch. Catches the everyday agent bug — missing required
25
- // field, type-mismatched value, typo in a key name — early, with a
26
- // structured error the LLM can correct from. The reducer is the
27
- // last line of defense; this is the first.
28
- const schema = host.getMsgSchema?.();
29
- const schemaVariant = schema?.variants[args.msg.type];
30
- if (schemaVariant !== undefined) {
31
- const violation = validatePayload(args.msg, schemaVariant);
32
- if (violation !== null) {
33
- return { status: 'rejected', reason: 'invalid', detail: violation };
34
- }
26
+ // field, wrong enum value, missing discriminant on a tagged union,
27
+ // typo in a key name early, with structured errors the LLM can
28
+ // correct from in one round trip. Reducers stay the last line of
29
+ // defense; this is the first.
30
+ const schema = host.getMsgSchema?.() ?? null;
31
+ const validation = validatePayload(args.msg, schema);
32
+ if (!validation.ok) {
33
+ return {
34
+ status: 'rejected',
35
+ reason: 'invalid',
36
+ detail: validation.errors.map((e) => `${e.path}: ${e.message}`).join('; '),
37
+ };
35
38
  }
36
39
  if (ann?.requiresConfirm) {
37
40
  const id = randomUUID();
@@ -151,90 +154,4 @@ function now() {
151
154
  ? performance.now()
152
155
  : Date.now();
153
156
  }
154
- /**
155
- * Validate `msg` against the variant's field schema. Returns null on
156
- * pass, a human-readable error string on fail. The check is shallow:
157
- * only top-level fields are walked, and `'unknown'` types pass any
158
- * value (the compiler couldn't statically resolve the type, so the
159
- * agent has to take what update() will accept on faith).
160
- *
161
- * Extra fields not in the schema are tolerated. TypeScript's structural
162
- * subtyping is permissive here too, and Msg payloads often carry
163
- * fields the discriminator didn't list (analytics tags, request IDs,
164
- * etc.). Rejecting them would be both surprising and blocked by edits
165
- * to update.ts that add fields ahead of the schema regenerating.
166
- */
167
- function validatePayload(msg, fields) {
168
- for (const [name, descriptor] of Object.entries(fields)) {
169
- const fieldType = unwrapFieldType(descriptor);
170
- const optional = isFieldOptional(descriptor);
171
- const present = name in msg;
172
- if (!present) {
173
- if (!optional) {
174
- return `${msg.type}: missing required field '${name}' (expected ${formatType(fieldType)})`;
175
- }
176
- continue;
177
- }
178
- const value = msg[name];
179
- const typeError = checkType(value, fieldType);
180
- if (typeError !== null) {
181
- return `${msg.type}: field '${name}' ${typeError}`;
182
- }
183
- }
184
- return null;
185
- }
186
- function unwrapFieldType(d) {
187
- return typeof d === 'object' && d !== null && 'type' in d ? d.type : d;
188
- }
189
- function isFieldOptional(d) {
190
- return typeof d === 'object' && d !== null && 'type' in d && d.optional === true;
191
- }
192
- function checkType(value, t) {
193
- if (typeof t === 'string') {
194
- if (t === 'unknown')
195
- return null;
196
- if (t === 'string')
197
- return typeof value === 'string' ? null : `expected string, got ${typeof value}`;
198
- if (t === 'number')
199
- return typeof value === 'number' ? null : `expected number, got ${typeof value}`;
200
- if (t === 'boolean')
201
- return typeof value === 'boolean' ? null : `expected boolean, got ${typeof value}`;
202
- // Unknown literal type code — be lenient. The compiler emits these
203
- // for keywords the resolver doesn't recognize; rejecting would
204
- // false-positive on every release that added a new primitive.
205
- return null;
206
- }
207
- if ('enum' in t) {
208
- // Enum: value must be one of the listed strings.
209
- if (typeof value !== 'string') {
210
- return `expected one of ${formatEnum(t)}, got ${typeof value}`;
211
- }
212
- if (!t.enum.includes(value)) {
213
- return `expected one of ${formatEnum(t)}, got ${JSON.stringify(value)}`;
214
- }
215
- return null;
216
- }
217
- // Object/array nested types — defer to the reducer for deep
218
- // validation. The compiler chased the shape into the schema for
219
- // synthesis purposes only; deep-checking every nested field would
220
- // be slow, hard to express good errors for, and duplicates what
221
- // TypeScript already does at the call site.
222
- if (t.kind === 'object') {
223
- return value === null || typeof value !== 'object'
224
- ? `expected object, got ${value === null ? 'null' : typeof value}`
225
- : null;
226
- }
227
- // 'array'
228
- return Array.isArray(value) ? null : `expected array, got ${typeof value}`;
229
- }
230
- function formatType(t) {
231
- if (typeof t === 'string')
232
- return t;
233
- if ('enum' in t)
234
- return formatEnum(t);
235
- return t.kind;
236
- }
237
- function formatEnum(t) {
238
- return `[${t.enum.map((v) => JSON.stringify(v)).join(', ')}]`;
239
- }
240
157
  //# sourceMappingURL=send-message.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"send-message.js","sourceRoot":"","sources":["../../../src/client/rpc/send-message.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from '../uuid.js'\nimport { handleListActions, type ListActionsHost } from './list-actions.js'\nimport { computeStateDiff } from '../../state-diff.js'\nimport type { MsgSchemaField } from '../factory.js'\nimport type {\n LapActionsResponse,\n LapDrainMeta,\n LapMessageResponse,\n MessageAnnotations,\n} from '../../protocol.js'\n\nexport type SendMessageArgs = {\n msg: { type: string; [k: string]: unknown }\n reason?: string\n /** See LapMessageRequest['waitFor']. Default: 'drained'. */\n waitFor?: 'drained' | 'idle' | 'none'\n /** See LapMessageRequest['drainQuietMs']. Default: 100ms. */\n drainQuietMs?: number\n /** See LapMessageRequest['timeoutMs']. Default: 5000ms. */\n timeoutMs?: number\n /** See LapMessageRequest['includeState']. Default: false. */\n includeState?: boolean\n}\n\nexport type SendMessageHost = ListActionsHost & {\n getState(): unknown\n send(msg: unknown): void\n flush(): void\n /**\n * Register a listener called after every update cycle commits —\n * backed by `AppHandle.subscribe`. Returns an unsubscribe function.\n * The drain loop uses this to detect message-queue quiescence: each\n * listener fire resets the quiet-window timer; no fires for\n * `drainQuietMs` means the loop has gone idle and async effects (if\n * any) have either completed or are persistent\n * (websocket/interval/storageWatch).\n */\n subscribe(listener: () => void): () => void\n getMsgAnnotations(): Record<string, MessageAnnotations> | null\n /**\n * Snapshot and clear the drain-error buffer. The agent factory\n * installs persistent `window.error` / `unhandledrejection`\n * listeners that accumulate into this buffer; calling this at the\n * start of a drain discards stale errors from prior windows, and\n * calling it at the end yields just the errors that fired during\n * this drain. Optional — when omitted (e.g., Node test harness\n * without `window`), the drain envelope records an empty array.\n */\n getAndClearDrainErrors?: () => LapDrainMeta['errors']\n /** Called when @requiresConfirm; caller stores a ConfirmEntry in state. */\n proposeConfirm(entry: {\n id: string\n variant: string\n payload: unknown\n intent: string\n reason: string | null\n proposedAt: number\n status: 'pending'\n }): void\n}\n\nconst DEFAULT_QUIET_MS = 100\nconst DEFAULT_TIMEOUT_MS = 5_000\n\nexport async function handleSendMessage(\n host: SendMessageHost,\n args: SendMessageArgs,\n): Promise<LapMessageResponse> {\n if (!args.msg || typeof args.msg.type !== 'string') {\n return { status: 'rejected', reason: 'invalid' }\n }\n const annotations = host.getMsgAnnotations() ?? {}\n const ann = annotations[args.msg.type]\n\n // If annotations map is non-empty and this variant isn't in it, it's an\n // unknown msg type that the app never declared — reject early so the\n // browser never dispatches an unrecognised variant into update().\n const hasAnnotations = Object.keys(annotations).length > 0\n if (hasAnnotations && !ann) {\n return { status: 'rejected', reason: 'invalid', detail: `unknown variant: ${args.msg.type}` }\n }\n\n if (ann?.dispatchMode === 'human-only') {\n return { status: 'rejected', reason: 'human-only' }\n }\n\n // Schema validation: when the compiler emitted a `__msgSchema`,\n // check the payload against this variant's field shape before\n // dispatch. Catches the everyday agent bug — missing required\n // field, type-mismatched value, typo in a key name — early, with a\n // structured error the LLM can correct from. The reducer is the\n // last line of defense; this is the first.\n const schema = host.getMsgSchema?.()\n const schemaVariant = schema?.variants[args.msg.type]\n if (schemaVariant !== undefined) {\n const violation = validatePayload(args.msg, schemaVariant)\n if (violation !== null) {\n return { status: 'rejected', reason: 'invalid', detail: violation }\n }\n }\n\n if (ann?.requiresConfirm) {\n const id = randomUUID()\n const { type: _type, ...payload } = args.msg\n host.proposeConfirm({\n id,\n variant: args.msg.type,\n payload,\n intent: ann?.intent ?? args.msg.type,\n reason: args.reason ?? null,\n proposedAt: Date.now(),\n status: 'pending',\n })\n return { status: 'pending-confirmation', confirmId: id }\n }\n\n const waitFor = args.waitFor ?? 'drained'\n const quietMs = Math.max(0, args.drainQuietMs ?? DEFAULT_QUIET_MS)\n const capMs = Math.max(0, args.timeoutMs ?? DEFAULT_TIMEOUT_MS)\n\n // Snapshot pre-dispatch state for diffing. The host's `getState`\n // returns a reference; capturing it here keeps a pre-mutation\n // pointer even after `host.send` triggers reducer-driven state\n // replacement (state itself is immutable per LLui's TEA contract,\n // so the reference stays valid).\n const prevState = host.getState()\n\n const includeState = args.includeState === true\n\n if (waitFor === 'none') {\n host.send(args.msg)\n return dispatched(host, emptyDrain(), prevState, includeState)\n }\n\n if (waitFor === 'idle') {\n host.send(args.msg)\n host.flush()\n await Promise.resolve()\n return dispatched(\n host,\n { effectsObserved: 1, durationMs: 0, timedOut: false, errors: [] },\n prevState,\n includeState,\n )\n }\n\n // waitFor === 'drained' — message-queue quiescence detection.\n // Clear any errors buffered before this call so `drain.errors`\n // attributes only to this window.\n host.getAndClearDrainErrors?.()\n\n const t0 = now()\n let observed = 0\n let wake: ((reason: 'msg' | 'timeout') => void) | null = null\n const unsub = host.subscribe(() => {\n observed++\n const w = wake\n wake = null\n w?.('msg')\n })\n try {\n host.send(args.msg)\n host.flush()\n\n while (true) {\n const elapsed = now() - t0\n if (elapsed >= capMs) {\n return dispatched(\n host,\n {\n effectsObserved: observed,\n durationMs: elapsed,\n timedOut: true,\n errors: host.getAndClearDrainErrors?.() ?? [],\n },\n prevState,\n includeState,\n )\n }\n const budget = Math.min(quietMs, capMs - elapsed)\n // When the cap is within `quietMs` of `elapsed`, the quiet\n // window is truncated. In that case a timeout resolution does\n // NOT mean we detected quiescence — it means the cap cut the\n // window short. Only a full-length quiet window that elapses\n // without a new commit counts as real idle.\n const fullQuiet = budget >= quietMs\n const reason = await awaitQuietOrMsg(budget, (resolve) => {\n wake = resolve\n })\n if (reason === 'timeout') {\n return dispatched(\n host,\n {\n effectsObserved: observed,\n durationMs: now() - t0,\n timedOut: !fullQuiet,\n errors: host.getAndClearDrainErrors?.() ?? [],\n },\n prevState,\n includeState,\n )\n }\n // A commit fired during the wait — flush any queued follow-ups so\n // effects dispatched by that cycle run before we re-check.\n host.flush()\n }\n } finally {\n unsub()\n }\n}\n\nfunction dispatched(\n host: SendMessageHost,\n drain: LapDrainMeta,\n prevState: unknown,\n includeState: boolean,\n): LapMessageResponse {\n const stateAfter = host.getState()\n const base = {\n status: 'dispatched' as const,\n stateDiff: computeStateDiff(prevState, stateAfter),\n actions: handleListActions(host).actions,\n drain,\n }\n return includeState ? { ...base, stateAfter } : base\n}\n\nfunction emptyDrain(): LapDrainMeta {\n return { effectsObserved: 0, durationMs: 0, timedOut: false, errors: [] }\n}\n\nfunction awaitQuietOrMsg(\n budgetMs: number,\n registerWake: (resolve: (r: 'msg' | 'timeout') => void) => void,\n): Promise<'msg' | 'timeout'> {\n return new Promise<'msg' | 'timeout'>((resolve) => {\n let settled = false\n const guarded = (r: 'msg' | 'timeout') => {\n if (settled) return\n settled = true\n resolve(r)\n }\n registerWake(guarded)\n setTimeout(() => guarded('timeout'), budgetMs)\n })\n}\n\nfunction now(): number {\n return typeof performance !== 'undefined' && typeof performance.now === 'function'\n ? performance.now()\n : Date.now()\n}\n\n// Helper types for external callers that want the dispatched envelope.\nexport type DispatchedEnvelope = Extract<LapMessageResponse, { status: 'dispatched' }>\nexport type { LapActionsResponse }\n\n/**\n * Validate `msg` against the variant's field schema. Returns null on\n * pass, a human-readable error string on fail. The check is shallow:\n * only top-level fields are walked, and `'unknown'` types pass any\n * value (the compiler couldn't statically resolve the type, so the\n * agent has to take what update() will accept on faith).\n *\n * Extra fields not in the schema are tolerated. TypeScript's structural\n * subtyping is permissive here too, and Msg payloads often carry\n * fields the discriminator didn't list (analytics tags, request IDs,\n * etc.). Rejecting them would be both surprising and blocked by edits\n * to update.ts that add fields ahead of the schema regenerating.\n */\nfunction validatePayload(\n msg: { type: string; [k: string]: unknown },\n fields: Record<string, MsgSchemaField>,\n): string | null {\n for (const [name, descriptor] of Object.entries(fields)) {\n const fieldType = unwrapFieldType(descriptor)\n const optional = isFieldOptional(descriptor)\n const present = name in msg\n if (!present) {\n if (!optional) {\n return `${msg.type}: missing required field '${name}' (expected ${formatType(fieldType)})`\n }\n continue\n }\n const value = msg[name]\n const typeError = checkType(value, fieldType)\n if (typeError !== null) {\n return `${msg.type}: field '${name}' ${typeError}`\n }\n }\n return null\n}\n\ntype BareType = Exclude<MsgSchemaField, { type: unknown }>\n\nfunction unwrapFieldType(d: MsgSchemaField): BareType {\n return typeof d === 'object' && d !== null && 'type' in d ? (d.type as BareType) : (d as BareType)\n}\n\nfunction isFieldOptional(d: MsgSchemaField): boolean {\n return typeof d === 'object' && d !== null && 'type' in d && d.optional === true\n}\n\nfunction checkType(value: unknown, t: BareType): string | null {\n if (typeof t === 'string') {\n if (t === 'unknown') return null\n if (t === 'string')\n return typeof value === 'string' ? null : `expected string, got ${typeof value}`\n if (t === 'number')\n return typeof value === 'number' ? null : `expected number, got ${typeof value}`\n if (t === 'boolean')\n return typeof value === 'boolean' ? null : `expected boolean, got ${typeof value}`\n // Unknown literal type code — be lenient. The compiler emits these\n // for keywords the resolver doesn't recognize; rejecting would\n // false-positive on every release that added a new primitive.\n return null\n }\n if ('enum' in t) {\n // Enum: value must be one of the listed strings.\n if (typeof value !== 'string') {\n return `expected one of ${formatEnum(t)}, got ${typeof value}`\n }\n if (!t.enum.includes(value)) {\n return `expected one of ${formatEnum(t)}, got ${JSON.stringify(value)}`\n }\n return null\n }\n // Object/array nested types — defer to the reducer for deep\n // validation. The compiler chased the shape into the schema for\n // synthesis purposes only; deep-checking every nested field would\n // be slow, hard to express good errors for, and duplicates what\n // TypeScript already does at the call site.\n if (t.kind === 'object') {\n return value === null || typeof value !== 'object'\n ? `expected object, got ${value === null ? 'null' : typeof value}`\n : null\n }\n // 'array'\n return Array.isArray(value) ? null : `expected array, got ${typeof value}`\n}\n\nfunction formatType(t: BareType): string {\n if (typeof t === 'string') return t\n if ('enum' in t) return formatEnum(t)\n return t.kind\n}\n\nfunction formatEnum(t: { enum: string[] }): string {\n return `[${t.enum.map((v) => JSON.stringify(v)).join(', ')}]`\n}\n"]}
1
+ {"version":3,"file":"send-message.js","sourceRoot":"","sources":["../../../src/client/rpc/send-message.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from '../uuid.js'\nimport { handleListActions, type ListActionsHost } from './list-actions.js'\nimport { computeStateDiff } from '../../state-diff.js'\nimport { validatePayload } from './validate-payload.js'\nimport type {\n LapActionsResponse,\n LapDrainMeta,\n LapMessageResponse,\n MessageAnnotations,\n} from '../../protocol.js'\n\nexport type SendMessageArgs = {\n msg: { type: string; [k: string]: unknown }\n reason?: string\n /** See LapMessageRequest['waitFor']. Default: 'drained'. */\n waitFor?: 'drained' | 'idle' | 'none'\n /** See LapMessageRequest['drainQuietMs']. Default: 100ms. */\n drainQuietMs?: number\n /** See LapMessageRequest['timeoutMs']. Default: 5000ms. */\n timeoutMs?: number\n /** See LapMessageRequest['includeState']. Default: false. */\n includeState?: boolean\n}\n\nexport type SendMessageHost = ListActionsHost & {\n getState(): unknown\n send(msg: unknown): void\n flush(): void\n /**\n * Register a listener called after every update cycle commits —\n * backed by `AppHandle.subscribe`. Returns an unsubscribe function.\n * The drain loop uses this to detect message-queue quiescence: each\n * listener fire resets the quiet-window timer; no fires for\n * `drainQuietMs` means the loop has gone idle and async effects (if\n * any) have either completed or are persistent\n * (websocket/interval/storageWatch).\n */\n subscribe(listener: () => void): () => void\n getMsgAnnotations(): Record<string, MessageAnnotations> | null\n /**\n * Snapshot and clear the drain-error buffer. The agent factory\n * installs persistent `window.error` / `unhandledrejection`\n * listeners that accumulate into this buffer; calling this at the\n * start of a drain discards stale errors from prior windows, and\n * calling it at the end yields just the errors that fired during\n * this drain. Optional — when omitted (e.g., Node test harness\n * without `window`), the drain envelope records an empty array.\n */\n getAndClearDrainErrors?: () => LapDrainMeta['errors']\n /** Called when @requiresConfirm; caller stores a ConfirmEntry in state. */\n proposeConfirm(entry: {\n id: string\n variant: string\n payload: unknown\n intent: string\n reason: string | null\n proposedAt: number\n status: 'pending'\n }): void\n}\n\nconst DEFAULT_QUIET_MS = 100\nconst DEFAULT_TIMEOUT_MS = 5_000\n\nexport async function handleSendMessage(\n host: SendMessageHost,\n args: SendMessageArgs,\n): Promise<LapMessageResponse> {\n if (!args.msg || typeof args.msg.type !== 'string') {\n return { status: 'rejected', reason: 'invalid' }\n }\n const annotations = host.getMsgAnnotations() ?? {}\n const ann = annotations[args.msg.type]\n\n // If annotations map is non-empty and this variant isn't in it, it's an\n // unknown msg type that the app never declared — reject early so the\n // browser never dispatches an unrecognised variant into update().\n const hasAnnotations = Object.keys(annotations).length > 0\n if (hasAnnotations && !ann) {\n return { status: 'rejected', reason: 'invalid', detail: `unknown variant: ${args.msg.type}` }\n }\n\n if (ann?.dispatchMode === 'human-only') {\n return { status: 'rejected', reason: 'human-only' }\n }\n\n // Schema validation: when the compiler emitted a `__msgSchema`,\n // check the payload against this variant's field shape before\n // dispatch. Catches the everyday agent bug — missing required\n // field, wrong enum value, missing discriminant on a tagged union,\n // typo in a key name — early, with structured errors the LLM can\n // correct from in one round trip. Reducers stay the last line of\n // defense; this is the first.\n const schema = host.getMsgSchema?.() ?? null\n const validation = validatePayload(args.msg, schema)\n if (!validation.ok) {\n return {\n status: 'rejected',\n reason: 'invalid',\n detail: validation.errors.map((e) => `${e.path}: ${e.message}`).join('; '),\n }\n }\n\n if (ann?.requiresConfirm) {\n const id = randomUUID()\n const { type: _type, ...payload } = args.msg\n host.proposeConfirm({\n id,\n variant: args.msg.type,\n payload,\n intent: ann?.intent ?? args.msg.type,\n reason: args.reason ?? null,\n proposedAt: Date.now(),\n status: 'pending',\n })\n return { status: 'pending-confirmation', confirmId: id }\n }\n\n const waitFor = args.waitFor ?? 'drained'\n const quietMs = Math.max(0, args.drainQuietMs ?? DEFAULT_QUIET_MS)\n const capMs = Math.max(0, args.timeoutMs ?? DEFAULT_TIMEOUT_MS)\n\n // Snapshot pre-dispatch state for diffing. The host's `getState`\n // returns a reference; capturing it here keeps a pre-mutation\n // pointer even after `host.send` triggers reducer-driven state\n // replacement (state itself is immutable per LLui's TEA contract,\n // so the reference stays valid).\n const prevState = host.getState()\n\n const includeState = args.includeState === true\n\n if (waitFor === 'none') {\n host.send(args.msg)\n return dispatched(host, emptyDrain(), prevState, includeState)\n }\n\n if (waitFor === 'idle') {\n host.send(args.msg)\n host.flush()\n await Promise.resolve()\n return dispatched(\n host,\n { effectsObserved: 1, durationMs: 0, timedOut: false, errors: [] },\n prevState,\n includeState,\n )\n }\n\n // waitFor === 'drained' — message-queue quiescence detection.\n // Clear any errors buffered before this call so `drain.errors`\n // attributes only to this window.\n host.getAndClearDrainErrors?.()\n\n const t0 = now()\n let observed = 0\n let wake: ((reason: 'msg' | 'timeout') => void) | null = null\n const unsub = host.subscribe(() => {\n observed++\n const w = wake\n wake = null\n w?.('msg')\n })\n try {\n host.send(args.msg)\n host.flush()\n\n while (true) {\n const elapsed = now() - t0\n if (elapsed >= capMs) {\n return dispatched(\n host,\n {\n effectsObserved: observed,\n durationMs: elapsed,\n timedOut: true,\n errors: host.getAndClearDrainErrors?.() ?? [],\n },\n prevState,\n includeState,\n )\n }\n const budget = Math.min(quietMs, capMs - elapsed)\n // When the cap is within `quietMs` of `elapsed`, the quiet\n // window is truncated. In that case a timeout resolution does\n // NOT mean we detected quiescence — it means the cap cut the\n // window short. Only a full-length quiet window that elapses\n // without a new commit counts as real idle.\n const fullQuiet = budget >= quietMs\n const reason = await awaitQuietOrMsg(budget, (resolve) => {\n wake = resolve\n })\n if (reason === 'timeout') {\n return dispatched(\n host,\n {\n effectsObserved: observed,\n durationMs: now() - t0,\n timedOut: !fullQuiet,\n errors: host.getAndClearDrainErrors?.() ?? [],\n },\n prevState,\n includeState,\n )\n }\n // A commit fired during the wait — flush any queued follow-ups so\n // effects dispatched by that cycle run before we re-check.\n host.flush()\n }\n } finally {\n unsub()\n }\n}\n\nfunction dispatched(\n host: SendMessageHost,\n drain: LapDrainMeta,\n prevState: unknown,\n includeState: boolean,\n): LapMessageResponse {\n const stateAfter = host.getState()\n const base = {\n status: 'dispatched' as const,\n stateDiff: computeStateDiff(prevState, stateAfter),\n actions: handleListActions(host).actions,\n drain,\n }\n return includeState ? { ...base, stateAfter } : base\n}\n\nfunction emptyDrain(): LapDrainMeta {\n return { effectsObserved: 0, durationMs: 0, timedOut: false, errors: [] }\n}\n\nfunction awaitQuietOrMsg(\n budgetMs: number,\n registerWake: (resolve: (r: 'msg' | 'timeout') => void) => void,\n): Promise<'msg' | 'timeout'> {\n return new Promise<'msg' | 'timeout'>((resolve) => {\n let settled = false\n const guarded = (r: 'msg' | 'timeout') => {\n if (settled) return\n settled = true\n resolve(r)\n }\n registerWake(guarded)\n setTimeout(() => guarded('timeout'), budgetMs)\n })\n}\n\nfunction now(): number {\n return typeof performance !== 'undefined' && typeof performance.now === 'function'\n ? performance.now()\n : Date.now()\n}\n\n// Helper types for external callers that want the dispatched envelope.\nexport type DispatchedEnvelope = Extract<LapMessageResponse, { status: 'dispatched' }>\nexport type { LapActionsResponse }\n"]}
@@ -0,0 +1,43 @@
1
+ import type { MsgSchemaShape } from '../factory.js';
2
+ /**
3
+ * Schema-driven payload validation for agent-dispatched Msgs. Walks
4
+ * the compiler-emitted schema against a candidate Msg and reports
5
+ * structural errors with a path-keyed list — the kind of feedback an
6
+ * LLM can act on in a single round trip ("set kind to one of: 'exact',
7
+ * 'range', 'compound'") instead of probing one field at a time.
8
+ *
9
+ * **What this is not.** This is not a TS type-checker. The schema is
10
+ * best-effort: cross-file types, generics, complex unions, and
11
+ * conditional types still surface as `'unknown'` and the validator
12
+ * accepts anything for those. The validator's job is to catch the
13
+ * mistakes a schema-aware LLM makes — wrong enum values, missing
14
+ * discriminants, primitive type mismatches — not to mirror the entire
15
+ * TypeScript surface area.
16
+ *
17
+ * **Tolerance for `'unknown'`.** Treat `'unknown'` as "any goes." Don't
18
+ * report errors against fields whose schema we don't know — those are
19
+ * the schema's gaps, not the agent's.
20
+ */
21
+ export type ValidationError = {
22
+ /**
23
+ * Dot-bracket path rooted at the Msg payload (NOT including `type`).
24
+ * - top-level field: `'cells'`
25
+ * - nested object property: `'cells.value'`
26
+ * - array element: `'cells[0]'` (concrete index from the input)
27
+ * - discriminated-union branch: `'format(kind=range).max'` — the
28
+ * parenthesised `<discriminant>=<value>` segment names which branch
29
+ * the error applies to, distinguishing the same field name across
30
+ * branches.
31
+ */
32
+ path: string;
33
+ code: 'unknown-variant' | 'missing' | 'wrong-type' | 'not-in-enum' | 'not-array' | 'not-object' | 'missing-discriminant' | 'unknown-discriminant-value';
34
+ message: string;
35
+ };
36
+ export type ValidationResult = {
37
+ ok: true;
38
+ } | {
39
+ ok: false;
40
+ errors: ValidationError[];
41
+ };
42
+ export declare function validatePayload(msg: unknown, schema: MsgSchemaShape | null): ValidationResult;
43
+ //# sourceMappingURL=validate-payload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-payload.d.ts","sourceRoot":"","sources":["../../../src/client/rpc/validate-payload.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAqC,MAAM,eAAe,CAAA;AAEtF;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,MAAM,eAAe,GAAG;IAC5B;;;;;;;;;OASG;IACH,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EACA,iBAAiB,GACjB,SAAS,GACT,YAAY,GACZ,aAAa,GACb,WAAW,GACX,YAAY,GACZ,sBAAsB,GACtB,4BAA4B,CAAA;IAChC,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,eAAe,EAAE,CAAA;CAAE,CAAA;AAEtF,wBAAgB,eAAe,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,cAAc,GAAG,IAAI,GAAG,gBAAgB,CAoD7F"}
@@ -0,0 +1,186 @@
1
+ export function validatePayload(msg, schema) {
2
+ if (msg === null || typeof msg !== 'object' || Array.isArray(msg)) {
3
+ return {
4
+ ok: false,
5
+ errors: [{ path: '', code: 'not-object', message: 'msg must be a plain object' }],
6
+ };
7
+ }
8
+ const m = msg;
9
+ const variantKey = m[schema?.discriminant ?? 'type'];
10
+ if (typeof variantKey !== 'string') {
11
+ return {
12
+ ok: false,
13
+ errors: [
14
+ {
15
+ path: schema?.discriminant ?? 'type',
16
+ code: 'missing',
17
+ message: `msg.${schema?.discriminant ?? 'type'} must be a string variant tag`,
18
+ },
19
+ ],
20
+ };
21
+ }
22
+ // No schema available — the compiler didn't emit one, or the LLM is
23
+ // talking to a build that predates schema emission. Accept the msg
24
+ // structurally; the reducer will validate semantically.
25
+ if (!schema)
26
+ return { ok: true };
27
+ const variantSchema = schema.variants[variantKey];
28
+ if (!variantSchema) {
29
+ return {
30
+ ok: false,
31
+ errors: [
32
+ {
33
+ path: schema.discriminant,
34
+ code: 'unknown-variant',
35
+ message: `'${variantKey}' is not a known variant. Legal values: ${Object.keys(schema.variants)
36
+ .map((v) => `'${v}'`)
37
+ .join(', ')}.`,
38
+ },
39
+ ],
40
+ };
41
+ }
42
+ const errors = [];
43
+ const payload = {};
44
+ for (const [k, v] of Object.entries(m)) {
45
+ if (k !== schema.discriminant)
46
+ payload[k] = v;
47
+ }
48
+ validateObjectShape(payload, variantSchema, '', errors);
49
+ return errors.length === 0 ? { ok: true } : { ok: false, errors };
50
+ }
51
+ function validateObjectShape(value, shape, pathPrefix, errors) {
52
+ for (const [name, descriptor] of Object.entries(shape)) {
53
+ const fieldPath = pathPrefix === '' ? name : `${pathPrefix}.${name}`;
54
+ const present = Object.prototype.hasOwnProperty.call(value, name);
55
+ const optional = isOptional(descriptor);
56
+ const fieldValue = present ? value[name] : undefined;
57
+ if (!present || fieldValue === undefined) {
58
+ if (!optional) {
59
+ errors.push({
60
+ path: fieldPath,
61
+ code: 'missing',
62
+ message: `required field is missing`,
63
+ });
64
+ }
65
+ continue;
66
+ }
67
+ validateField(fieldValue, fieldType(descriptor), fieldPath, errors);
68
+ }
69
+ }
70
+ function validateField(value, type, path, errors) {
71
+ if (type === 'unknown')
72
+ return; // schema gap; accept anything
73
+ if (typeof type === 'string') {
74
+ // Primitive keyword: 'string', 'number', 'boolean'.
75
+ if (typeof value !== type) {
76
+ errors.push({
77
+ path,
78
+ code: 'wrong-type',
79
+ message: `expected ${type}, got ${describeType(value)}`,
80
+ });
81
+ }
82
+ return;
83
+ }
84
+ if ('enum' in type) {
85
+ // Use Object.is so NaN and -0 match correctly; falls back to ===
86
+ // semantics for ordinary values.
87
+ const ok = type.enum.some((legal) => Object.is(legal, value) || legal === value);
88
+ if (!ok) {
89
+ errors.push({
90
+ path,
91
+ code: 'not-in-enum',
92
+ message: `'${String(value)}' is not in the enum. Legal values: ${type.enum
93
+ .map((v) => formatEnumValue(v))
94
+ .join(', ')}.`,
95
+ });
96
+ }
97
+ return;
98
+ }
99
+ if (type.kind === 'object') {
100
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) {
101
+ errors.push({
102
+ path,
103
+ code: 'not-object',
104
+ message: `expected object, got ${describeType(value)}`,
105
+ });
106
+ return;
107
+ }
108
+ validateObjectShape(value, type.shape, path, errors);
109
+ return;
110
+ }
111
+ if (type.kind === 'array') {
112
+ if (!Array.isArray(value)) {
113
+ errors.push({
114
+ path,
115
+ code: 'not-array',
116
+ message: `expected array, got ${describeType(value)}`,
117
+ });
118
+ return;
119
+ }
120
+ for (let i = 0; i < value.length; i++) {
121
+ validateField(value[i], type.element, `${path}[${i}]`, errors);
122
+ }
123
+ return;
124
+ }
125
+ if (type.kind === 'discriminated-union') {
126
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) {
127
+ errors.push({
128
+ path,
129
+ code: 'not-object',
130
+ message: `expected discriminated-union object, got ${describeType(value)}`,
131
+ });
132
+ return;
133
+ }
134
+ const obj = value;
135
+ const discValue = obj[type.discriminant];
136
+ if (typeof discValue !== 'string') {
137
+ errors.push({
138
+ path: `${path}.${type.discriminant}`,
139
+ code: 'missing-discriminant',
140
+ message: `discriminant '${type.discriminant}' must be one of: ${Object.keys(type.variants)
141
+ .map((v) => `'${v}'`)
142
+ .join(', ')}`,
143
+ });
144
+ return;
145
+ }
146
+ const branchSchema = type.variants[discValue];
147
+ if (!branchSchema) {
148
+ errors.push({
149
+ path: `${path}.${type.discriminant}`,
150
+ code: 'unknown-discriminant-value',
151
+ message: `'${discValue}' is not a legal '${type.discriminant}'. Legal values: ${Object.keys(type.variants)
152
+ .map((v) => `'${v}'`)
153
+ .join(', ')}.`,
154
+ });
155
+ return;
156
+ }
157
+ // Recurse into the matched branch's payload (excluding the
158
+ // discriminant itself, which is already validated above).
159
+ const branchPayload = {};
160
+ for (const [k, v] of Object.entries(obj)) {
161
+ if (k !== type.discriminant)
162
+ branchPayload[k] = v;
163
+ }
164
+ const branchPath = `${path}(${type.discriminant}=${discValue})`;
165
+ validateObjectShape(branchPayload, branchSchema, branchPath, errors);
166
+ }
167
+ }
168
+ function isOptional(d) {
169
+ return typeof d === 'object' && d !== null && 'type' in d && d.optional === true;
170
+ }
171
+ function fieldType(d) {
172
+ if (typeof d === 'object' && d !== null && 'type' in d)
173
+ return d.type;
174
+ return d;
175
+ }
176
+ function describeType(v) {
177
+ if (v === null)
178
+ return 'null';
179
+ if (Array.isArray(v))
180
+ return 'array';
181
+ return typeof v;
182
+ }
183
+ function formatEnumValue(v) {
184
+ return typeof v === 'string' ? `'${v}'` : String(v);
185
+ }
186
+ //# sourceMappingURL=validate-payload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-payload.js","sourceRoot":"","sources":["../../../src/client/rpc/validate-payload.ts"],"names":[],"mappings":"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","sourcesContent":["import type { MsgSchemaShape, MsgSchemaField, MsgSchemaBareType } from '../factory.js'\n\n/**\n * Schema-driven payload validation for agent-dispatched Msgs. Walks\n * the compiler-emitted schema against a candidate Msg and reports\n * structural errors with a path-keyed list — the kind of feedback an\n * LLM can act on in a single round trip (\"set kind to one of: 'exact',\n * 'range', 'compound'\") instead of probing one field at a time.\n *\n * **What this is not.** This is not a TS type-checker. The schema is\n * best-effort: cross-file types, generics, complex unions, and\n * conditional types still surface as `'unknown'` and the validator\n * accepts anything for those. The validator's job is to catch the\n * mistakes a schema-aware LLM makes — wrong enum values, missing\n * discriminants, primitive type mismatches — not to mirror the entire\n * TypeScript surface area.\n *\n * **Tolerance for `'unknown'`.** Treat `'unknown'` as \"any goes.\" Don't\n * report errors against fields whose schema we don't know — those are\n * the schema's gaps, not the agent's.\n */\n\nexport type ValidationError = {\n /**\n * Dot-bracket path rooted at the Msg payload (NOT including `type`).\n * - top-level field: `'cells'`\n * - nested object property: `'cells.value'`\n * - array element: `'cells[0]'` (concrete index from the input)\n * - discriminated-union branch: `'format(kind=range).max'` — the\n * parenthesised `<discriminant>=<value>` segment names which branch\n * the error applies to, distinguishing the same field name across\n * branches.\n */\n path: string\n code:\n | 'unknown-variant'\n | 'missing'\n | 'wrong-type'\n | 'not-in-enum'\n | 'not-array'\n | 'not-object'\n | 'missing-discriminant'\n | 'unknown-discriminant-value'\n message: string\n}\n\nexport type ValidationResult = { ok: true } | { ok: false; errors: ValidationError[] }\n\nexport function validatePayload(msg: unknown, schema: MsgSchemaShape | null): ValidationResult {\n if (msg === null || typeof msg !== 'object' || Array.isArray(msg)) {\n return {\n ok: false,\n errors: [{ path: '', code: 'not-object', message: 'msg must be a plain object' }],\n }\n }\n const m = msg as Record<string, unknown>\n const variantKey = m[schema?.discriminant ?? 'type']\n if (typeof variantKey !== 'string') {\n return {\n ok: false,\n errors: [\n {\n path: schema?.discriminant ?? 'type',\n code: 'missing',\n message: `msg.${schema?.discriminant ?? 'type'} must be a string variant tag`,\n },\n ],\n }\n }\n\n // No schema available — the compiler didn't emit one, or the LLM is\n // talking to a build that predates schema emission. Accept the msg\n // structurally; the reducer will validate semantically.\n if (!schema) return { ok: true }\n\n const variantSchema = schema.variants[variantKey]\n if (!variantSchema) {\n return {\n ok: false,\n errors: [\n {\n path: schema.discriminant,\n code: 'unknown-variant',\n message: `'${variantKey}' is not a known variant. Legal values: ${Object.keys(\n schema.variants,\n )\n .map((v) => `'${v}'`)\n .join(', ')}.`,\n },\n ],\n }\n }\n\n const errors: ValidationError[] = []\n const payload: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(m)) {\n if (k !== schema.discriminant) payload[k] = v\n }\n validateObjectShape(payload, variantSchema, '', errors)\n return errors.length === 0 ? { ok: true } : { ok: false, errors }\n}\n\nfunction validateObjectShape(\n value: Record<string, unknown>,\n shape: Record<string, MsgSchemaField>,\n pathPrefix: string,\n errors: ValidationError[],\n): void {\n for (const [name, descriptor] of Object.entries(shape)) {\n const fieldPath = pathPrefix === '' ? name : `${pathPrefix}.${name}`\n const present = Object.prototype.hasOwnProperty.call(value, name)\n const optional = isOptional(descriptor)\n const fieldValue = present ? value[name] : undefined\n\n if (!present || fieldValue === undefined) {\n if (!optional) {\n errors.push({\n path: fieldPath,\n code: 'missing',\n message: `required field is missing`,\n })\n }\n continue\n }\n\n validateField(fieldValue, fieldType(descriptor), fieldPath, errors)\n }\n}\n\nfunction validateField(\n value: unknown,\n type: MsgSchemaBareType,\n path: string,\n errors: ValidationError[],\n): void {\n if (type === 'unknown') return // schema gap; accept anything\n if (typeof type === 'string') {\n // Primitive keyword: 'string', 'number', 'boolean'.\n if (typeof value !== type) {\n errors.push({\n path,\n code: 'wrong-type',\n message: `expected ${type}, got ${describeType(value)}`,\n })\n }\n return\n }\n if ('enum' in type) {\n // Use Object.is so NaN and -0 match correctly; falls back to ===\n // semantics for ordinary values.\n const ok = type.enum.some((legal) => Object.is(legal, value) || legal === value)\n if (!ok) {\n errors.push({\n path,\n code: 'not-in-enum',\n message: `'${String(value)}' is not in the enum. Legal values: ${type.enum\n .map((v) => formatEnumValue(v))\n .join(', ')}.`,\n })\n }\n return\n }\n if (type.kind === 'object') {\n if (value === null || typeof value !== 'object' || Array.isArray(value)) {\n errors.push({\n path,\n code: 'not-object',\n message: `expected object, got ${describeType(value)}`,\n })\n return\n }\n validateObjectShape(value as Record<string, unknown>, type.shape, path, errors)\n return\n }\n if (type.kind === 'array') {\n if (!Array.isArray(value)) {\n errors.push({\n path,\n code: 'not-array',\n message: `expected array, got ${describeType(value)}`,\n })\n return\n }\n for (let i = 0; i < value.length; i++) {\n validateField(value[i], type.element, `${path}[${i}]`, errors)\n }\n return\n }\n if (type.kind === 'discriminated-union') {\n if (value === null || typeof value !== 'object' || Array.isArray(value)) {\n errors.push({\n path,\n code: 'not-object',\n message: `expected discriminated-union object, got ${describeType(value)}`,\n })\n return\n }\n const obj = value as Record<string, unknown>\n const discValue = obj[type.discriminant]\n if (typeof discValue !== 'string') {\n errors.push({\n path: `${path}.${type.discriminant}`,\n code: 'missing-discriminant',\n message: `discriminant '${type.discriminant}' must be one of: ${Object.keys(type.variants)\n .map((v) => `'${v}'`)\n .join(', ')}`,\n })\n return\n }\n const branchSchema = type.variants[discValue]\n if (!branchSchema) {\n errors.push({\n path: `${path}.${type.discriminant}`,\n code: 'unknown-discriminant-value',\n message: `'${discValue}' is not a legal '${type.discriminant}'. Legal values: ${Object.keys(\n type.variants,\n )\n .map((v) => `'${v}'`)\n .join(', ')}.`,\n })\n return\n }\n // Recurse into the matched branch's payload (excluding the\n // discriminant itself, which is already validated above).\n const branchPayload: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(obj)) {\n if (k !== type.discriminant) branchPayload[k] = v\n }\n const branchPath = `${path}(${type.discriminant}=${discValue})`\n validateObjectShape(branchPayload, branchSchema, branchPath, errors)\n }\n}\n\nfunction isOptional(d: MsgSchemaField): boolean {\n return typeof d === 'object' && d !== null && 'type' in d && d.optional === true\n}\n\nfunction fieldType(d: MsgSchemaField): MsgSchemaBareType {\n if (typeof d === 'object' && d !== null && 'type' in d) return d.type\n return d\n}\n\nfunction describeType(v: unknown): string {\n if (v === null) return 'null'\n if (Array.isArray(v)) return 'array'\n return typeof v\n}\n\nfunction formatEnumValue(v: string | number | boolean): string {\n return typeof v === 'string' ? `'${v}'` : String(v)\n}\n"]}
@@ -1,4 +1,6 @@
1
1
  import type { StateDiff } from '../../state-diff.js';
2
+ import { type ValidationError } from './validate-payload.js';
3
+ import type { MsgSchemaShape } from '../factory.js';
2
4
  /**
3
5
  * Predict the result of dispatching `msg` without actually applying
4
6
  * it. Runs the reducer in isolation against the current state,
@@ -44,6 +46,16 @@ export type WouldDispatchHost = {
44
46
  state: unknown;
45
47
  effects: unknown[];
46
48
  } | null;
49
+ /**
50
+ * The compiler-emitted Msg schema, when available. Used to
51
+ * validate the candidate payload structurally before running the
52
+ * reducer — wrong enum values, missing discriminants, primitive
53
+ * type mismatches surface as structured errors the agent can fix
54
+ * in one round trip instead of probing field by field. Hosts
55
+ * without schema metadata return null and the validator skips,
56
+ * keeping the tool permissive (the reducer still runs).
57
+ */
58
+ getMsgSchema?(): MsgSchemaShape | null;
47
59
  };
48
60
  export type WouldDispatchArgs = {
49
61
  msg: {
@@ -61,6 +73,17 @@ export type WouldDispatchResult = {
61
73
  status: 'rejected';
62
74
  reason: 'invalid' | 'unsupported';
63
75
  detail?: string;
76
+ } | {
77
+ /**
78
+ * The candidate Msg failed schema validation BEFORE the reducer
79
+ * ran. `errors` lists every structural mismatch with a path-
80
+ * keyed description. The agent reads this as "fix these fields
81
+ * and retry" — no reducer side-effects to roll back, no state
82
+ * change to predict around.
83
+ */
84
+ status: 'rejected';
85
+ reason: 'schema-mismatch';
86
+ errors: ValidationError[];
64
87
  };
65
88
  export declare function handleWouldDispatch(host: WouldDispatchHost, args: WouldDispatchArgs): WouldDispatchResult;
66
89
  //# sourceMappingURL=would-dispatch.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"would-dispatch.d.ts","sourceRoot":"","sources":["../../../src/client/rpc/would-dispatch.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,IAAI,OAAO,CAAA;IACnB;;;;;;;OAOG;IACH,UAAU,CAAC,GAAG,EAAE;QACd,IAAI,EAAE,MAAM,CAAA;QACZ,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;KACrB,GAAG;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,OAAO,EAAE,CAAA;KAAE,GAAG,IAAI,CAAA;CAClD,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,GAAG,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAA;CAC5C,CAAA;AAED,MAAM,MAAM,mBAAmB,GAC3B;IACE,MAAM,EAAE,WAAW,CAAA;IACnB,mEAAmE;IACnE,SAAS,EAAE,SAAS,CAAA;IACpB,0EAA0E;IAC1E,OAAO,EAAE,OAAO,EAAE,CAAA;CACnB,GACD;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,SAAS,GAAG,aAAa,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAE9E,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,iBAAiB,EACvB,IAAI,EAAE,iBAAiB,GACtB,mBAAmB,CAkBrB"}
1
+ {"version":3,"file":"would-dispatch.d.ts","sourceRoot":"","sources":["../../../src/client/rpc/would-dispatch.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AACpD,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAC7E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAEnD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,IAAI,OAAO,CAAA;IACnB;;;;;;;OAOG;IACH,UAAU,CAAC,GAAG,EAAE;QACd,IAAI,EAAE,MAAM,CAAA;QACZ,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;KACrB,GAAG;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,OAAO,EAAE,CAAA;KAAE,GAAG,IAAI,CAAA;IACjD;;;;;;;;OAQG;IACH,YAAY,CAAC,IAAI,cAAc,GAAG,IAAI,CAAA;CACvC,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,GAAG,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAA;CAC5C,CAAA;AAED,MAAM,MAAM,mBAAmB,GAC3B;IACE,MAAM,EAAE,WAAW,CAAA;IACnB,mEAAmE;IACnE,SAAS,EAAE,SAAS,CAAA;IACpB,0EAA0E;IAC1E,OAAO,EAAE,OAAO,EAAE,CAAA;CACnB,GACD;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,SAAS,GAAG,aAAa,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAC1E;IACE;;;;;;OAMG;IACH,MAAM,EAAE,UAAU,CAAA;IAClB,MAAM,EAAE,iBAAiB,CAAA;IACzB,MAAM,EAAE,eAAe,EAAE,CAAA;CAC1B,CAAA;AAEL,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,iBAAiB,EACvB,IAAI,EAAE,iBAAiB,GACtB,mBAAmB,CA6BrB"}
@@ -1,8 +1,18 @@
1
1
  import { computeStateDiff } from '../../state-diff.js';
2
+ import { validatePayload } from './validate-payload.js';
2
3
  export function handleWouldDispatch(host, args) {
3
4
  if (!args.msg || typeof args.msg.type !== 'string') {
4
5
  return { status: 'rejected', reason: 'invalid', detail: 'msg.type must be a string' };
5
6
  }
7
+ // Schema-driven preflight. Permissive when no schema is available —
8
+ // the reducer still validates semantically. With a schema, surface
9
+ // structured errors so the agent doesn't iterate one field per
10
+ // round trip.
11
+ const schema = host.getMsgSchema?.() ?? null;
12
+ const validation = validatePayload(args.msg, schema);
13
+ if (!validation.ok) {
14
+ return { status: 'rejected', reason: 'schema-mismatch', errors: validation.errors };
15
+ }
6
16
  const result = host.runReducer(args.msg);
7
17
  if (result === null) {
8
18
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"would-dispatch.js","sourceRoot":"","sources":["../../../src/client/rpc/would-dispatch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AA6DtD,MAAM,UAAU,mBAAmB,CACjC,IAAuB,EACvB,IAAuB;IAEvB,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QACnD,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,2BAA2B,EAAE,CAAA;IACvF,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACxC,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QACpB,OAAO;YACL,MAAM,EAAE,UAAU;YAClB,MAAM,EAAE,aAAa;YACrB,MAAM,EAAE,6DAA6D;SACtE,CAAA;IACH,CAAC;IACD,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAA;IACjC,OAAO;QACL,MAAM,EAAE,WAAW;QACnB,SAAS,EAAE,gBAAgB,CAAC,SAAS,EAAE,MAAM,CAAC,KAAK,CAAC;QACpD,OAAO,EAAE,MAAM,CAAC,OAAO;KACxB,CAAA;AACH,CAAC","sourcesContent":["import { computeStateDiff } from '../../state-diff.js'\nimport type { StateDiff } from '../../state-diff.js'\n\n/**\n * Predict the result of dispatching `msg` without actually applying\n * it. Runs the reducer in isolation against the current state,\n * returns the would-be diff and the would-fire effects, but doesn't\n * commit or run anything. Lets the agent reason about a candidate\n * action before pulling the trigger:\n *\n * - \"If I dispatch X, what will change?\" — read `stateDiff`.\n * - \"Will it fire effects? Which ones?\" — read `effects`.\n * - \"Should I batch?\" — predict each, see whether the diffs\n * compose without conflict.\n *\n * The contract is bounded by TEA's purity assumption: the reducer\n * must be a pure function `(state, msg) → [newState, effects]`. LLui\n * reducers are pure by convention (the runtime never re-runs them\n * speculatively for any other reason, so impurity would already be\n * a latent bug). Apps whose reducers branch on `Date.now()` or read\n * `localStorage` will see prediction drift from real dispatch by\n * exactly that amount of impurity — usually negligible, sometimes\n * surprising; document at the call site.\n *\n * **No effects fire.** The returned `effects` array is the literal\n * effect descriptors the reducer produced — what `onEffect` would\n * have received. The agent reads them; the runtime ignores them.\n * This is the entire reason the tool exists separately from\n * `send_message`: a real dispatch hits the cloud / analytics /\n * persistence; a predicted one doesn't.\n */\nexport type WouldDispatchHost = {\n getState(): unknown\n /**\n * Run the reducer in isolation. `[newState, effects]` shape.\n * Implemented by the AppHandle as a thin wrapper around\n * `inst.def.update(state, msg)` — no flush, no subscribe, no\n * commit. Implementations that can't run the reducer (e.g.\n * test harnesses with no live instance) return null and the\n * tool reports unsupported.\n */\n runReducer(msg: {\n type: string\n [k: string]: unknown\n }): { state: unknown; effects: unknown[] } | null\n}\n\nexport type WouldDispatchArgs = {\n msg: { type: string; [k: string]: unknown }\n}\n\nexport type WouldDispatchResult =\n | {\n status: 'predicted'\n /** Diff from current state to the predicted post-reducer state. */\n stateDiff: StateDiff\n /** Effects the reducer would emit. Order matches the reducer's return. */\n effects: unknown[]\n }\n | { status: 'rejected'; reason: 'invalid' | 'unsupported'; detail?: string }\n\nexport function handleWouldDispatch(\n host: WouldDispatchHost,\n args: WouldDispatchArgs,\n): WouldDispatchResult {\n if (!args.msg || typeof args.msg.type !== 'string') {\n return { status: 'rejected', reason: 'invalid', detail: 'msg.type must be a string' }\n }\n const result = host.runReducer(args.msg)\n if (result === null) {\n return {\n status: 'rejected',\n reason: 'unsupported',\n detail: 'host does not expose a reducer (no live component instance)',\n }\n }\n const prevState = host.getState()\n return {\n status: 'predicted',\n stateDiff: computeStateDiff(prevState, result.state),\n effects: result.effects,\n }\n}\n"]}
1
+ {"version":3,"file":"would-dispatch.js","sourceRoot":"","sources":["../../../src/client/rpc/would-dispatch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AAEtD,OAAO,EAAE,eAAe,EAAwB,MAAM,uBAAuB,CAAA;AAmF7E,MAAM,UAAU,mBAAmB,CACjC,IAAuB,EACvB,IAAuB;IAEvB,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QACnD,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,2BAA2B,EAAE,CAAA;IACvF,CAAC;IAED,oEAAoE;IACpE,mEAAmE;IACnE,+DAA+D;IAC/D,cAAc;IACd,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,EAAE,EAAE,IAAI,IAAI,CAAA;IAC5C,MAAM,UAAU,GAAG,eAAe,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;IACpD,IAAI,CAAC,UAAU,CAAC,EAAE,EAAE,CAAC;QACnB,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,CAAA;IACrF,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACxC,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QACpB,OAAO;YACL,MAAM,EAAE,UAAU;YAClB,MAAM,EAAE,aAAa;YACrB,MAAM,EAAE,6DAA6D;SACtE,CAAA;IACH,CAAC;IACD,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAA;IACjC,OAAO;QACL,MAAM,EAAE,WAAW;QACnB,SAAS,EAAE,gBAAgB,CAAC,SAAS,EAAE,MAAM,CAAC,KAAK,CAAC;QACpD,OAAO,EAAE,MAAM,CAAC,OAAO;KACxB,CAAA;AACH,CAAC","sourcesContent":["import { computeStateDiff } from '../../state-diff.js'\nimport type { StateDiff } from '../../state-diff.js'\nimport { validatePayload, type ValidationError } from './validate-payload.js'\nimport type { MsgSchemaShape } from '../factory.js'\n\n/**\n * Predict the result of dispatching `msg` without actually applying\n * it. Runs the reducer in isolation against the current state,\n * returns the would-be diff and the would-fire effects, but doesn't\n * commit or run anything. Lets the agent reason about a candidate\n * action before pulling the trigger:\n *\n * - \"If I dispatch X, what will change?\" — read `stateDiff`.\n * - \"Will it fire effects? Which ones?\" — read `effects`.\n * - \"Should I batch?\" — predict each, see whether the diffs\n * compose without conflict.\n *\n * The contract is bounded by TEA's purity assumption: the reducer\n * must be a pure function `(state, msg) → [newState, effects]`. LLui\n * reducers are pure by convention (the runtime never re-runs them\n * speculatively for any other reason, so impurity would already be\n * a latent bug). Apps whose reducers branch on `Date.now()` or read\n * `localStorage` will see prediction drift from real dispatch by\n * exactly that amount of impurity — usually negligible, sometimes\n * surprising; document at the call site.\n *\n * **No effects fire.** The returned `effects` array is the literal\n * effect descriptors the reducer produced — what `onEffect` would\n * have received. The agent reads them; the runtime ignores them.\n * This is the entire reason the tool exists separately from\n * `send_message`: a real dispatch hits the cloud / analytics /\n * persistence; a predicted one doesn't.\n */\nexport type WouldDispatchHost = {\n getState(): unknown\n /**\n * Run the reducer in isolation. `[newState, effects]` shape.\n * Implemented by the AppHandle as a thin wrapper around\n * `inst.def.update(state, msg)` — no flush, no subscribe, no\n * commit. Implementations that can't run the reducer (e.g.\n * test harnesses with no live instance) return null and the\n * tool reports unsupported.\n */\n runReducer(msg: {\n type: string\n [k: string]: unknown\n }): { state: unknown; effects: unknown[] } | null\n /**\n * The compiler-emitted Msg schema, when available. Used to\n * validate the candidate payload structurally before running the\n * reducer — wrong enum values, missing discriminants, primitive\n * type mismatches surface as structured errors the agent can fix\n * in one round trip instead of probing field by field. Hosts\n * without schema metadata return null and the validator skips,\n * keeping the tool permissive (the reducer still runs).\n */\n getMsgSchema?(): MsgSchemaShape | null\n}\n\nexport type WouldDispatchArgs = {\n msg: { type: string; [k: string]: unknown }\n}\n\nexport type WouldDispatchResult =\n | {\n status: 'predicted'\n /** Diff from current state to the predicted post-reducer state. */\n stateDiff: StateDiff\n /** Effects the reducer would emit. Order matches the reducer's return. */\n effects: unknown[]\n }\n | { status: 'rejected'; reason: 'invalid' | 'unsupported'; detail?: string }\n | {\n /**\n * The candidate Msg failed schema validation BEFORE the reducer\n * ran. `errors` lists every structural mismatch with a path-\n * keyed description. The agent reads this as \"fix these fields\n * and retry\" — no reducer side-effects to roll back, no state\n * change to predict around.\n */\n status: 'rejected'\n reason: 'schema-mismatch'\n errors: ValidationError[]\n }\n\nexport function handleWouldDispatch(\n host: WouldDispatchHost,\n args: WouldDispatchArgs,\n): WouldDispatchResult {\n if (!args.msg || typeof args.msg.type !== 'string') {\n return { status: 'rejected', reason: 'invalid', detail: 'msg.type must be a string' }\n }\n\n // Schema-driven preflight. Permissive when no schema is available —\n // the reducer still validates semantically. With a schema, surface\n // structured errors so the agent doesn't iterate one field per\n // round trip.\n const schema = host.getMsgSchema?.() ?? null\n const validation = validatePayload(args.msg, schema)\n if (!validation.ok) {\n return { status: 'rejected', reason: 'schema-mismatch', errors: validation.errors }\n }\n\n const result = host.runReducer(args.msg)\n if (result === null) {\n return {\n status: 'rejected',\n reason: 'unsupported',\n detail: 'host does not expose a reducer (no live component instance)',\n }\n }\n const prevState = host.getState()\n return {\n status: 'predicted',\n stateDiff: computeStateDiff(prevState, result.state),\n effects: result.effects,\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llui/agent",
3
- "version": "0.0.36",
3
+ "version": "0.0.37",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "exports": {