@llui/agent 0.0.32 → 0.0.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +82 -1
- package/dist/client/agentConfirm.d.ts +48 -18
- package/dist/client/agentConfirm.d.ts.map +1 -1
- package/dist/client/agentConfirm.js +28 -25
- package/dist/client/agentConfirm.js.map +1 -1
- package/dist/client/agentConnect.d.ts +95 -34
- package/dist/client/agentConnect.d.ts.map +1 -1
- package/dist/client/agentConnect.js +85 -47
- package/dist/client/agentConnect.js.map +1 -1
- package/dist/client/agentLog.d.ts +31 -14
- package/dist/client/agentLog.d.ts.map +1 -1
- package/dist/client/agentLog.js +39 -20
- package/dist/client/agentLog.js.map +1 -1
- package/dist/client/effect-handler.d.ts +23 -0
- package/dist/client/effect-handler.d.ts.map +1 -1
- package/dist/client/effect-handler.js +185 -126
- package/dist/client/effect-handler.js.map +1 -1
- package/dist/client/effects.d.ts +13 -2
- package/dist/client/effects.d.ts.map +1 -1
- package/dist/client/effects.js.map +1 -1
- package/dist/client/factory.d.ts +55 -3
- package/dist/client/factory.d.ts.map +1 -1
- package/dist/client/factory.js +30 -5
- package/dist/client/factory.js.map +1 -1
- package/dist/client/rpc/describe-visible-content.d.ts +18 -5
- package/dist/client/rpc/describe-visible-content.d.ts.map +1 -1
- package/dist/client/rpc/describe-visible-content.js +112 -7
- package/dist/client/rpc/describe-visible-content.js.map +1 -1
- package/dist/client/rpc/list-actions.d.ts +52 -2
- package/dist/client/rpc/list-actions.d.ts.map +1 -1
- package/dist/client/rpc/list-actions.js +187 -5
- package/dist/client/rpc/list-actions.js.map +1 -1
- package/dist/client/rpc/query-state.d.ts +32 -0
- package/dist/client/rpc/query-state.d.ts.map +1 -0
- package/dist/client/rpc/query-state.js +82 -0
- package/dist/client/rpc/query-state.js.map +1 -0
- package/dist/client/rpc/send-message.d.ts +2 -0
- package/dist/client/rpc/send-message.d.ts.map +1 -1
- package/dist/client/rpc/send-message.js +119 -9
- package/dist/client/rpc/send-message.js.map +1 -1
- package/dist/client/rpc/would-dispatch.d.ts +66 -0
- package/dist/client/rpc/would-dispatch.d.ts.map +1 -0
- package/dist/client/rpc/would-dispatch.js +21 -0
- package/dist/client/rpc/would-dispatch.js.map +1 -0
- package/dist/client/ws-client.d.ts +3 -1
- package/dist/client/ws-client.d.ts.map +1 -1
- package/dist/client/ws-client.js +29 -0
- package/dist/client/ws-client.js.map +1 -1
- package/dist/codecs.d.ts +107 -0
- package/dist/codecs.d.ts.map +1 -0
- package/dist/codecs.js +166 -0
- package/dist/codecs.js.map +1 -0
- package/dist/protocol.d.ts +172 -12
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +7 -1
- package/dist/protocol.js.map +1 -1
- package/dist/server/cloudflare/durable-object.d.ts +11 -4
- package/dist/server/cloudflare/durable-object.d.ts.map +1 -1
- package/dist/server/cloudflare/durable-object.js.map +1 -1
- package/dist/server/cloudflare/index.d.ts +8 -4
- package/dist/server/cloudflare/index.d.ts.map +1 -1
- package/dist/server/cloudflare/index.js +8 -4
- package/dist/server/cloudflare/index.js.map +1 -1
- package/dist/server/cloudflare/worker.d.ts +10 -2
- package/dist/server/cloudflare/worker.d.ts.map +1 -1
- package/dist/server/cloudflare/worker.js +13 -6
- package/dist/server/cloudflare/worker.js.map +1 -1
- package/dist/server/core-entry.d.ts +2 -2
- package/dist/server/core-entry.d.ts.map +1 -1
- package/dist/server/core-entry.js +1 -1
- package/dist/server/core-entry.js.map +1 -1
- package/dist/server/core.d.ts +1 -3
- package/dist/server/core.d.ts.map +1 -1
- package/dist/server/core.js +13 -12
- package/dist/server/core.js.map +1 -1
- package/dist/server/factory.d.ts +1 -1
- package/dist/server/factory.d.ts.map +1 -1
- package/dist/server/factory.js +1 -2
- package/dist/server/factory.js.map +1 -1
- package/dist/server/http/mint.d.ts +6 -1
- package/dist/server/http/mint.d.ts.map +1 -1
- package/dist/server/http/mint.js +14 -6
- package/dist/server/http/mint.js.map +1 -1
- package/dist/server/http/resume.d.ts +3 -1
- package/dist/server/http/resume.d.ts.map +1 -1
- package/dist/server/http/resume.js +9 -7
- package/dist/server/http/resume.js.map +1 -1
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/lap/confirm-result.d.ts +0 -1
- package/dist/server/lap/confirm-result.d.ts.map +1 -1
- package/dist/server/lap/confirm-result.js +1 -1
- package/dist/server/lap/confirm-result.js.map +1 -1
- package/dist/server/lap/describe.d.ts +13 -2
- package/dist/server/lap/describe.d.ts.map +1 -1
- package/dist/server/lap/describe.js +23 -6
- package/dist/server/lap/describe.js.map +1 -1
- package/dist/server/lap/forward.d.ts +13 -1
- package/dist/server/lap/forward.d.ts.map +1 -1
- package/dist/server/lap/forward.js +75 -1
- package/dist/server/lap/forward.js.map +1 -1
- package/dist/server/lap/message.d.ts +0 -1
- package/dist/server/lap/message.d.ts.map +1 -1
- package/dist/server/lap/message.js +1 -1
- package/dist/server/lap/message.js.map +1 -1
- package/dist/server/lap/observe.d.ts +0 -1
- package/dist/server/lap/observe.d.ts.map +1 -1
- package/dist/server/lap/observe.js +1 -1
- package/dist/server/lap/observe.js.map +1 -1
- package/dist/server/lap/router.d.ts.map +1 -1
- package/dist/server/lap/router.js +7 -1
- package/dist/server/lap/router.js.map +1 -1
- package/dist/server/lap/wait.d.ts +0 -1
- package/dist/server/lap/wait.d.ts.map +1 -1
- package/dist/server/lap/wait.js +1 -1
- package/dist/server/lap/wait.js.map +1 -1
- package/dist/server/options.d.ts +7 -5
- package/dist/server/options.d.ts.map +1 -1
- package/dist/server/options.js.map +1 -1
- package/dist/server/token-store.d.ts +22 -0
- package/dist/server/token-store.d.ts.map +1 -1
- package/dist/server/token-store.js +24 -0
- package/dist/server/token-store.js.map +1 -1
- package/dist/server/token.d.ts +32 -17
- package/dist/server/token.d.ts.map +1 -1
- package/dist/server/token.js +40 -103
- package/dist/server/token.js.map +1 -1
- package/dist/server/web/upgrade.d.ts +1 -1
- package/dist/server/web/upgrade.js +1 -1
- package/dist/server/web/upgrade.js.map +1 -1
- package/dist/server/ws/pairing-registry.d.ts +22 -6
- package/dist/server/ws/pairing-registry.d.ts.map +1 -1
- package/dist/server/ws/pairing-registry.js +49 -0
- package/dist/server/ws/pairing-registry.js.map +1 -1
- package/dist/server/ws/upgrade.d.ts +0 -1
- package/dist/server/ws/upgrade.d.ts.map +1 -1
- package/dist/server/ws/upgrade.js +12 -4
- package/dist/server/ws/upgrade.js.map +1 -1
- package/dist/state-diff.d.ts +52 -0
- package/dist/state-diff.d.ts.map +1 -0
- package/dist/state-diff.js +119 -0
- package/dist/state-diff.js.map +1 -0
- package/package.json +7 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list-actions.js","sourceRoot":"","sources":["../../../src/client/rpc/list-actions.ts"],"names":[],"mappings":"AAuBA,MAAM,UAAU,iBAAiB,CAAC,IAAqB;IACrD,MAAM,WAAW,GAAG,IAAI,CAAC,iBAAiB,EAAE,IAAI,EAAE,CAAA;IAClD,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAA;IAC7B,MAAM,WAAW,GAAG,IAAI,CAAC,qBAAqB,EAAE,IAAI,EAAE,CAAA;IACtD,MAAM,WAAW,GAAG,IAAI,CAAC,mBAAmB,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAA;IAE7D,MAAM,GAAG,GAAiC,EAAE,CAAA;IAE5C,gBAAgB;IAChB,KAAK,MAAM,CAAC,IAAI,WAAW,EAAE,CAAC;QAC5B,MAAM,GAAG,GAAG,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,CAAA;QAClC,IAAI,GAAG,EAAE,SAAS;YAAE,SAAQ;QAC5B,GAAG,CAAC,IAAI,CAAC;YACP,OAAO,EAAE,CAAC,CAAC,OAAO;YAClB,MAAM,EAAE,GAAG,EAAE,MAAM,IAAI,CAAC,CAAC,OAAO;YAChC,eAAe,EAAE,GAAG,EAAE,eAAe,IAAI,KAAK;YAC9C,MAAM,EAAE,SAAS;YACjB,YAAY,EAAE,IAAI;YAClB,WAAW,EAAE,IAAI;SAClB,CAAC,CAAA;IACJ,CAAC;IAED,yBAAyB;IACzB,KAAK,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;QAC9B,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;QACjC,IAAI,GAAG,EAAE,SAAS;YAAE,SAAQ;QAC5B,MAAM,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,GAAG,GAAG,CAAA;QAC7B,GAAG,CAAC,IAAI,CAAC;YACP,OAAO,EAAE,IAAI;YACb,MAAM,EAAE,GAAG,EAAE,MAAM,IAAI,IAAI;YAC3B,eAAe,EAAE,GAAG,EAAE,eAAe,IAAI,KAAK;YAC9C,MAAM,EAAE,mBAAmB;YAC3B,YAAY,EAAE,IAAI;YAClB,WAAW,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI;SACxD,CAAC,CAAA;IACJ,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,CAAA;AACzB,CAAC","sourcesContent":["import type { MessageAnnotations } from '../../protocol.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 getAgentAffordances(): ((state: unknown) => Array<{ type: string; [k: string]: unknown }>) | null\n}\n\nexport type ListActionsResult = {\n actions: Array<{\n variant: string\n intent: string\n requiresConfirm: boolean\n source: 'binding' | 'always-affordable'\n selectorHint: string | null\n payloadHint: object | null\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\n const out: ListActionsResult['actions'] = []\n\n // From bindings\n for (const d of descriptors) {\n const ann = annotations[d.variant]\n if (ann?.humanOnly) continue\n out.push({\n variant: d.variant,\n intent: ann?.intent ?? d.variant,\n requiresConfirm: ann?.requiresConfirm ?? false,\n source: 'binding',\n selectorHint: null,\n payloadHint: null,\n })\n }\n\n // From always-affordable\n for (const msg of affordances) {\n const ann = annotations[msg.type]\n if (ann?.humanOnly) continue\n const { type, ...rest } = msg\n out.push({\n variant: type,\n intent: ann?.intent ?? type,\n requiresConfirm: ann?.requiresConfirm ?? false,\n source: 'always-affordable',\n selectorHint: null,\n payloadHint: Object.keys(rest).length > 0 ? rest : null,\n })\n }\n\n return { actions: out }\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.\n * - `'always-affordable'` — the app's `agentAffordances(state)` hook\n * listed it as available right now.\n * - `'schema'` — neither of the above; the variant is in the\n * Msg union, annotated `@agentOnly`, and the payload is example-\n * only (no live UI binding maps to it). Bulk-edit operations\n * typically land here.\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 always-affordable\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 schema — variants that aren't already surfaced as bindings\n // or always-affordable, and that the author intentionally documented\n // for agent use. Two cases land here:\n //\n // 1. `@agentOnly` variants — the canonical \"no UI button maps to\n // this; the agent is the only dispatcher.\" Bulk edits, imports,\n // admin operations.\n // 2. `'shared'` variants with `@intent` but no live binding — the\n // author wrote a description for the variant, signalling it's\n // a real agent-callable dispatch even when the corresponding UI\n // affordance is closed (e.g. `Matrix/SetQuantityValue` lives\n // inside the cell editor; without this case, an agent that\n // wants to set one cell is forced to use the bulk\n // `Matrix/SetManyCells` with a 1-element array).\n //\n // `'shared'` variants WITHOUT `@intent` stay hidden — undocumented\n // shared variants are usually internal (effect-result messages,\n // router-internal acks) that aren't intended as agent affordances.\n // `'human-only'` is always filtered.\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 === 'human-only') continue\n const isAgentOnly = ann?.dispatchMode === 'agent-only'\n const isDocumentedShared = ann?.dispatchMode !== 'agent-only' && Boolean(ann?.intent)\n if (!isAgentOnly && !isDocumentedShared) continue\n out.push({\n variant,\n intent: ann?.intent ?? null,\n requiresConfirm: ann?.requiresConfirm ?? false,\n dispatchMode: isAgentOnly ? 'agent-only' : 'shared',\n source: 'schema',\n selectorHint: null,\n payloadHint: synthesizePayload(variant, fields),\n warning: ann?.warning ?? null,\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"]}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve a JSON-Pointer-shaped path against the current state and
|
|
3
|
+
* return just that slice. Saves bandwidth on `observe` for complex
|
|
4
|
+
* apps where the agent only needs to check a single field.
|
|
5
|
+
*
|
|
6
|
+
* Path syntax (RFC 6901):
|
|
7
|
+
* `''` → the whole state (escape hatch; same as get_state)
|
|
8
|
+
* `'/auth/user'` → state.auth.user
|
|
9
|
+
* `'/items/0/id'` → state.items[0].id
|
|
10
|
+
* `'/key~1with~1slash'` → state['key/with/slash']
|
|
11
|
+
* `'/key~0tilde'` → state['key~tilde']
|
|
12
|
+
*
|
|
13
|
+
* Returns `{ value: unknown, found: true }` on hit, `{ found: false,
|
|
14
|
+
* detail: string }` on miss. The miss is non-fatal: the agent might
|
|
15
|
+
* be polling an optional field that isn't present yet, and a soft
|
|
16
|
+
* miss lets it observe the absence directly rather than catching a
|
|
17
|
+
* thrown error.
|
|
18
|
+
*/
|
|
19
|
+
export type QueryStateHost = {
|
|
20
|
+
getState(): unknown;
|
|
21
|
+
};
|
|
22
|
+
export type QueryStateResult = {
|
|
23
|
+
found: true;
|
|
24
|
+
value: unknown;
|
|
25
|
+
} | {
|
|
26
|
+
found: false;
|
|
27
|
+
detail: string;
|
|
28
|
+
};
|
|
29
|
+
export declare function handleQueryState(host: QueryStateHost, args: {
|
|
30
|
+
path: string;
|
|
31
|
+
}): QueryStateResult;
|
|
32
|
+
//# sourceMappingURL=query-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-state.d.ts","sourceRoot":"","sources":["../../../src/client/rpc/query-state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,IAAI,OAAO,CAAA;CACpB,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAAE,KAAK,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAEjG,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,gBAAgB,CAE/F"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve a JSON-Pointer-shaped path against the current state and
|
|
3
|
+
* return just that slice. Saves bandwidth on `observe` for complex
|
|
4
|
+
* apps where the agent only needs to check a single field.
|
|
5
|
+
*
|
|
6
|
+
* Path syntax (RFC 6901):
|
|
7
|
+
* `''` → the whole state (escape hatch; same as get_state)
|
|
8
|
+
* `'/auth/user'` → state.auth.user
|
|
9
|
+
* `'/items/0/id'` → state.items[0].id
|
|
10
|
+
* `'/key~1with~1slash'` → state['key/with/slash']
|
|
11
|
+
* `'/key~0tilde'` → state['key~tilde']
|
|
12
|
+
*
|
|
13
|
+
* Returns `{ value: unknown, found: true }` on hit, `{ found: false,
|
|
14
|
+
* detail: string }` on miss. The miss is non-fatal: the agent might
|
|
15
|
+
* be polling an optional field that isn't present yet, and a soft
|
|
16
|
+
* miss lets it observe the absence directly rather than catching a
|
|
17
|
+
* thrown error.
|
|
18
|
+
*/
|
|
19
|
+
export function handleQueryState(host, args) {
|
|
20
|
+
return resolvePath(host.getState(), args.path);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Walk the path from `root` segment by segment. Empty path returns
|
|
24
|
+
* root unchanged (RFC 6901's "whole document" reference). Each
|
|
25
|
+
* segment is unescaped before the lookup; the unescape order matters
|
|
26
|
+
* (`~1` first, then `~0`) to avoid double-decoding.
|
|
27
|
+
*/
|
|
28
|
+
function resolvePath(root, path) {
|
|
29
|
+
if (path === '')
|
|
30
|
+
return { found: true, value: root };
|
|
31
|
+
if (!path.startsWith('/')) {
|
|
32
|
+
return { found: false, detail: `path must be empty or start with '/' (got: ${quote(path)})` };
|
|
33
|
+
}
|
|
34
|
+
// Split on '/' but skip the leading slash. Hand-rolled to avoid
|
|
35
|
+
// edge cases with empty segments (path "//foo" is valid in RFC
|
|
36
|
+
// 6901 — references the empty string key).
|
|
37
|
+
const rawSegments = path.slice(1).split('/');
|
|
38
|
+
let cur = root;
|
|
39
|
+
for (let i = 0; i < rawSegments.length; i++) {
|
|
40
|
+
const segment = unescapeSegment(rawSegments[i]);
|
|
41
|
+
if (cur === null || cur === undefined) {
|
|
42
|
+
return {
|
|
43
|
+
found: false,
|
|
44
|
+
detail: `path "${path}" walks through ${cur === null ? 'null' : 'undefined'} at segment ${i} (${quote(segment)})`,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
if (Array.isArray(cur)) {
|
|
48
|
+
const idx = Number(segment);
|
|
49
|
+
if (!Number.isInteger(idx) || idx < 0 || idx >= cur.length) {
|
|
50
|
+
return {
|
|
51
|
+
found: false,
|
|
52
|
+
detail: `path "${path}" — array at segment ${i} has length ${cur.length}, can't index by ${quote(segment)}`,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
cur = cur[idx];
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (typeof cur !== 'object') {
|
|
59
|
+
return {
|
|
60
|
+
found: false,
|
|
61
|
+
detail: `path "${path}" walks through non-object (${typeof cur}) at segment ${i} (${quote(segment)})`,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
const obj = cur;
|
|
65
|
+
if (!(segment in obj)) {
|
|
66
|
+
return {
|
|
67
|
+
found: false,
|
|
68
|
+
detail: `path "${path}" — key ${quote(segment)} not present at segment ${i}`,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
cur = obj[segment];
|
|
72
|
+
}
|
|
73
|
+
return { found: true, value: cur };
|
|
74
|
+
}
|
|
75
|
+
/** Inverse of state-diff's `escapeSegment`: `~1` → `/`, `~0` → `~`. */
|
|
76
|
+
function unescapeSegment(seg) {
|
|
77
|
+
return seg.replace(/~1/g, '/').replace(/~0/g, '~');
|
|
78
|
+
}
|
|
79
|
+
function quote(s) {
|
|
80
|
+
return JSON.stringify(s);
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=query-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-state.js","sourceRoot":"","sources":["../../../src/client/rpc/query-state.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Resolve a JSON-Pointer-shaped path against the current state and\n * return just that slice. Saves bandwidth on `observe` for complex\n * apps where the agent only needs to check a single field.\n *\n * Path syntax (RFC 6901):\n * `''` → the whole state (escape hatch; same as get_state)\n * `'/auth/user'` → state.auth.user\n * `'/items/0/id'` → state.items[0].id\n * `'/key~1with~1slash'` → state['key/with/slash']\n * `'/key~0tilde'` → state['key~tilde']\n *\n * Returns `{ value: unknown, found: true }` on hit, `{ found: false,\n * detail: string }` on miss. The miss is non-fatal: the agent might\n * be polling an optional field that isn't present yet, and a soft\n * miss lets it observe the absence directly rather than catching a\n * thrown error.\n */\n\nexport type QueryStateHost = {\n getState(): unknown\n}\n\nexport type QueryStateResult = { found: true; value: unknown } | { found: false; detail: string }\n\nexport function handleQueryState(host: QueryStateHost, args: { path: string }): QueryStateResult {\n return resolvePath(host.getState(), args.path)\n}\n\n/**\n * Walk the path from `root` segment by segment. Empty path returns\n * root unchanged (RFC 6901's \"whole document\" reference). Each\n * segment is unescaped before the lookup; the unescape order matters\n * (`~1` first, then `~0`) to avoid double-decoding.\n */\nfunction resolvePath(root: unknown, path: string): QueryStateResult {\n if (path === '') return { found: true, value: root }\n if (!path.startsWith('/')) {\n return { found: false, detail: `path must be empty or start with '/' (got: ${quote(path)})` }\n }\n\n // Split on '/' but skip the leading slash. Hand-rolled to avoid\n // edge cases with empty segments (path \"//foo\" is valid in RFC\n // 6901 — references the empty string key).\n const rawSegments = path.slice(1).split('/')\n\n let cur: unknown = root\n for (let i = 0; i < rawSegments.length; i++) {\n const segment = unescapeSegment(rawSegments[i]!)\n if (cur === null || cur === undefined) {\n return {\n found: false,\n detail: `path \"${path}\" walks through ${cur === null ? 'null' : 'undefined'} at segment ${i} (${quote(segment)})`,\n }\n }\n if (Array.isArray(cur)) {\n const idx = Number(segment)\n if (!Number.isInteger(idx) || idx < 0 || idx >= cur.length) {\n return {\n found: false,\n detail: `path \"${path}\" — array at segment ${i} has length ${cur.length}, can't index by ${quote(segment)}`,\n }\n }\n cur = cur[idx]\n continue\n }\n if (typeof cur !== 'object') {\n return {\n found: false,\n detail: `path \"${path}\" walks through non-object (${typeof cur}) at segment ${i} (${quote(segment)})`,\n }\n }\n const obj = cur as Record<string, unknown>\n if (!(segment in obj)) {\n return {\n found: false,\n detail: `path \"${path}\" — key ${quote(segment)} not present at segment ${i}`,\n }\n }\n cur = obj[segment]\n }\n return { found: true, value: cur }\n}\n\n/** Inverse of state-diff's `escapeSegment`: `~1` → `/`, `~0` → `~`. */\nfunction unescapeSegment(seg: string): string {\n return seg.replace(/~1/g, '/').replace(/~0/g, '~')\n}\n\nfunction quote(s: string): string {\n return JSON.stringify(s)\n}\n"]}
|
|
@@ -12,6 +12,8 @@ export type SendMessageArgs = {
|
|
|
12
12
|
drainQuietMs?: number;
|
|
13
13
|
/** See LapMessageRequest['timeoutMs']. Default: 5000ms. */
|
|
14
14
|
timeoutMs?: number;
|
|
15
|
+
/** See LapMessageRequest['includeState']. Default: false. */
|
|
16
|
+
includeState?: boolean;
|
|
15
17
|
};
|
|
16
18
|
export type SendMessageHost = ListActionsHost & {
|
|
17
19
|
getState(): unknown;
|
|
@@ -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;
|
|
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,5 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from '../uuid.js';
|
|
2
2
|
import { handleListActions } from './list-actions.js';
|
|
3
|
+
import { computeStateDiff } from '../../state-diff.js';
|
|
3
4
|
const DEFAULT_QUIET_MS = 100;
|
|
4
5
|
const DEFAULT_TIMEOUT_MS = 5_000;
|
|
5
6
|
export async function handleSendMessage(host, args) {
|
|
@@ -15,8 +16,22 @@ export async function handleSendMessage(host, args) {
|
|
|
15
16
|
if (hasAnnotations && !ann) {
|
|
16
17
|
return { status: 'rejected', reason: 'invalid', detail: `unknown variant: ${args.msg.type}` };
|
|
17
18
|
}
|
|
18
|
-
if (ann?.
|
|
19
|
-
return { status: 'rejected', reason: '
|
|
19
|
+
if (ann?.dispatchMode === 'human-only') {
|
|
20
|
+
return { status: 'rejected', reason: 'human-only' };
|
|
21
|
+
}
|
|
22
|
+
// Schema validation: when the compiler emitted a `__msgSchema`,
|
|
23
|
+
// check the payload against this variant's field shape before
|
|
24
|
+
// 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
|
+
}
|
|
20
35
|
}
|
|
21
36
|
if (ann?.requiresConfirm) {
|
|
22
37
|
const id = randomUUID();
|
|
@@ -35,15 +50,22 @@ export async function handleSendMessage(host, args) {
|
|
|
35
50
|
const waitFor = args.waitFor ?? 'drained';
|
|
36
51
|
const quietMs = Math.max(0, args.drainQuietMs ?? DEFAULT_QUIET_MS);
|
|
37
52
|
const capMs = Math.max(0, args.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
53
|
+
// Snapshot pre-dispatch state for diffing. The host's `getState`
|
|
54
|
+
// returns a reference; capturing it here keeps a pre-mutation
|
|
55
|
+
// pointer even after `host.send` triggers reducer-driven state
|
|
56
|
+
// replacement (state itself is immutable per LLui's TEA contract,
|
|
57
|
+
// so the reference stays valid).
|
|
58
|
+
const prevState = host.getState();
|
|
59
|
+
const includeState = args.includeState === true;
|
|
38
60
|
if (waitFor === 'none') {
|
|
39
61
|
host.send(args.msg);
|
|
40
|
-
return dispatched(host, emptyDrain());
|
|
62
|
+
return dispatched(host, emptyDrain(), prevState, includeState);
|
|
41
63
|
}
|
|
42
64
|
if (waitFor === 'idle') {
|
|
43
65
|
host.send(args.msg);
|
|
44
66
|
host.flush();
|
|
45
67
|
await Promise.resolve();
|
|
46
|
-
return dispatched(host, { effectsObserved: 1, durationMs: 0, timedOut: false, errors: [] });
|
|
68
|
+
return dispatched(host, { effectsObserved: 1, durationMs: 0, timedOut: false, errors: [] }, prevState, includeState);
|
|
47
69
|
}
|
|
48
70
|
// waitFor === 'drained' — message-queue quiescence detection.
|
|
49
71
|
// Clear any errors buffered before this call so `drain.errors`
|
|
@@ -69,7 +91,7 @@ export async function handleSendMessage(host, args) {
|
|
|
69
91
|
durationMs: elapsed,
|
|
70
92
|
timedOut: true,
|
|
71
93
|
errors: host.getAndClearDrainErrors?.() ?? [],
|
|
72
|
-
});
|
|
94
|
+
}, prevState, includeState);
|
|
73
95
|
}
|
|
74
96
|
const budget = Math.min(quietMs, capMs - elapsed);
|
|
75
97
|
// When the cap is within `quietMs` of `elapsed`, the quiet
|
|
@@ -87,7 +109,7 @@ export async function handleSendMessage(host, args) {
|
|
|
87
109
|
durationMs: now() - t0,
|
|
88
110
|
timedOut: !fullQuiet,
|
|
89
111
|
errors: host.getAndClearDrainErrors?.() ?? [],
|
|
90
|
-
});
|
|
112
|
+
}, prevState, includeState);
|
|
91
113
|
}
|
|
92
114
|
// A commit fired during the wait — flush any queued follow-ups so
|
|
93
115
|
// effects dispatched by that cycle run before we re-check.
|
|
@@ -98,13 +120,15 @@ export async function handleSendMessage(host, args) {
|
|
|
98
120
|
unsub();
|
|
99
121
|
}
|
|
100
122
|
}
|
|
101
|
-
function dispatched(host, drain) {
|
|
102
|
-
|
|
123
|
+
function dispatched(host, drain, prevState, includeState) {
|
|
124
|
+
const stateAfter = host.getState();
|
|
125
|
+
const base = {
|
|
103
126
|
status: 'dispatched',
|
|
104
|
-
|
|
127
|
+
stateDiff: computeStateDiff(prevState, stateAfter),
|
|
105
128
|
actions: handleListActions(host).actions,
|
|
106
129
|
drain,
|
|
107
130
|
};
|
|
131
|
+
return includeState ? { ...base, stateAfter } : base;
|
|
108
132
|
}
|
|
109
133
|
function emptyDrain() {
|
|
110
134
|
return { effectsObserved: 0, durationMs: 0, timedOut: false, errors: [] };
|
|
@@ -127,4 +151,90 @@ function now() {
|
|
|
127
151
|
? performance.now()
|
|
128
152
|
: Date.now();
|
|
129
153
|
}
|
|
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
|
+
}
|
|
130
240
|
//# 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 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}\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?.humanOnly) {\n return { status: 'rejected', reason: 'humanOnly' }\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 if (waitFor === 'none') {\n host.send(args.msg)\n return dispatched(host, emptyDrain())\n }\n\n if (waitFor === 'idle') {\n host.send(args.msg)\n host.flush()\n await Promise.resolve()\n return dispatched(host, { effectsObserved: 1, durationMs: 0, timedOut: false, errors: [] })\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(host, {\n effectsObserved: observed,\n durationMs: elapsed,\n timedOut: true,\n errors: host.getAndClearDrainErrors?.() ?? [],\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(host, {\n effectsObserved: observed,\n durationMs: now() - t0,\n timedOut: !fullQuiet,\n errors: host.getAndClearDrainErrors?.() ?? [],\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(host: SendMessageHost, drain: LapDrainMeta): LapMessageResponse {\n return {\n status: 'dispatched',\n stateAfter: host.getState(),\n actions: handleListActions(host).actions,\n drain,\n }\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"]}
|
|
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"]}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { StateDiff } from '../../state-diff.js';
|
|
2
|
+
/**
|
|
3
|
+
* Predict the result of dispatching `msg` without actually applying
|
|
4
|
+
* it. Runs the reducer in isolation against the current state,
|
|
5
|
+
* returns the would-be diff and the would-fire effects, but doesn't
|
|
6
|
+
* commit or run anything. Lets the agent reason about a candidate
|
|
7
|
+
* action before pulling the trigger:
|
|
8
|
+
*
|
|
9
|
+
* - "If I dispatch X, what will change?" — read `stateDiff`.
|
|
10
|
+
* - "Will it fire effects? Which ones?" — read `effects`.
|
|
11
|
+
* - "Should I batch?" — predict each, see whether the diffs
|
|
12
|
+
* compose without conflict.
|
|
13
|
+
*
|
|
14
|
+
* The contract is bounded by TEA's purity assumption: the reducer
|
|
15
|
+
* must be a pure function `(state, msg) → [newState, effects]`. LLui
|
|
16
|
+
* reducers are pure by convention (the runtime never re-runs them
|
|
17
|
+
* speculatively for any other reason, so impurity would already be
|
|
18
|
+
* a latent bug). Apps whose reducers branch on `Date.now()` or read
|
|
19
|
+
* `localStorage` will see prediction drift from real dispatch by
|
|
20
|
+
* exactly that amount of impurity — usually negligible, sometimes
|
|
21
|
+
* surprising; document at the call site.
|
|
22
|
+
*
|
|
23
|
+
* **No effects fire.** The returned `effects` array is the literal
|
|
24
|
+
* effect descriptors the reducer produced — what `onEffect` would
|
|
25
|
+
* have received. The agent reads them; the runtime ignores them.
|
|
26
|
+
* This is the entire reason the tool exists separately from
|
|
27
|
+
* `send_message`: a real dispatch hits the cloud / analytics /
|
|
28
|
+
* persistence; a predicted one doesn't.
|
|
29
|
+
*/
|
|
30
|
+
export type WouldDispatchHost = {
|
|
31
|
+
getState(): unknown;
|
|
32
|
+
/**
|
|
33
|
+
* Run the reducer in isolation. `[newState, effects]` shape.
|
|
34
|
+
* Implemented by the AppHandle as a thin wrapper around
|
|
35
|
+
* `inst.def.update(state, msg)` — no flush, no subscribe, no
|
|
36
|
+
* commit. Implementations that can't run the reducer (e.g.
|
|
37
|
+
* test harnesses with no live instance) return null and the
|
|
38
|
+
* tool reports unsupported.
|
|
39
|
+
*/
|
|
40
|
+
runReducer(msg: {
|
|
41
|
+
type: string;
|
|
42
|
+
[k: string]: unknown;
|
|
43
|
+
}): {
|
|
44
|
+
state: unknown;
|
|
45
|
+
effects: unknown[];
|
|
46
|
+
} | null;
|
|
47
|
+
};
|
|
48
|
+
export type WouldDispatchArgs = {
|
|
49
|
+
msg: {
|
|
50
|
+
type: string;
|
|
51
|
+
[k: string]: unknown;
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
export type WouldDispatchResult = {
|
|
55
|
+
status: 'predicted';
|
|
56
|
+
/** Diff from current state to the predicted post-reducer state. */
|
|
57
|
+
stateDiff: StateDiff;
|
|
58
|
+
/** Effects the reducer would emit. Order matches the reducer's return. */
|
|
59
|
+
effects: unknown[];
|
|
60
|
+
} | {
|
|
61
|
+
status: 'rejected';
|
|
62
|
+
reason: 'invalid' | 'unsupported';
|
|
63
|
+
detail?: string;
|
|
64
|
+
};
|
|
65
|
+
export declare function handleWouldDispatch(host: WouldDispatchHost, args: WouldDispatchArgs): WouldDispatchResult;
|
|
66
|
+
//# sourceMappingURL=would-dispatch.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { computeStateDiff } from '../../state-diff.js';
|
|
2
|
+
export function handleWouldDispatch(host, args) {
|
|
3
|
+
if (!args.msg || typeof args.msg.type !== 'string') {
|
|
4
|
+
return { status: 'rejected', reason: 'invalid', detail: 'msg.type must be a string' };
|
|
5
|
+
}
|
|
6
|
+
const result = host.runReducer(args.msg);
|
|
7
|
+
if (result === null) {
|
|
8
|
+
return {
|
|
9
|
+
status: 'rejected',
|
|
10
|
+
reason: 'unsupported',
|
|
11
|
+
detail: 'host does not expose a reducer (no live component instance)',
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
const prevState = host.getState();
|
|
15
|
+
return {
|
|
16
|
+
status: 'predicted',
|
|
17
|
+
stateDiff: computeStateDiff(prevState, result.state),
|
|
18
|
+
effects: result.effects,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=would-dispatch.js.map
|
|
@@ -0,0 +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,5 +1,7 @@
|
|
|
1
1
|
import type { HelloFrame, LogEntry } from '../protocol.js';
|
|
2
2
|
import { type GetStateHost } from './rpc/get-state.js';
|
|
3
|
+
import { type QueryStateHost } from './rpc/query-state.js';
|
|
4
|
+
import { type WouldDispatchHost } from './rpc/would-dispatch.js';
|
|
3
5
|
import { type SendMessageHost } from './rpc/send-message.js';
|
|
4
6
|
import { type ListActionsHost } from './rpc/list-actions.js';
|
|
5
7
|
import { type QueryDomHost } from './rpc/query-dom.js';
|
|
@@ -14,7 +16,7 @@ export interface WsLike {
|
|
|
14
16
|
}) => void): void;
|
|
15
17
|
addEventListener(event: 'open' | 'close', h: () => void): void;
|
|
16
18
|
}
|
|
17
|
-
export type RpcHosts = GetStateHost & SendMessageHost & ListActionsHost & QueryDomHost & DescribeVisibleHost & DescribeContextHost & ObserveHost;
|
|
19
|
+
export type RpcHosts = GetStateHost & QueryStateHost & SendMessageHost & ListActionsHost & QueryDomHost & DescribeVisibleHost & DescribeContextHost & ObserveHost & WouldDispatchHost;
|
|
18
20
|
export type HelloBuilder = () => HelloFrame;
|
|
19
21
|
export type WsClient = {
|
|
20
22
|
/** Resolve a pending confirmation; emits confirm-resolved frame to the server. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ws-client.d.ts","sourceRoot":"","sources":["../../src/client/ws-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,UAAU,EACV,QAAQ,EAGT,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACtE,OAAO,EAAqB,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAC/E,OAAO,EAAqB,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAC/E,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACtE,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,mCAAmC,CAAA;AAC1C,OAAO,EAAyB,KAAK,mBAAmB,EAAE,MAAM,2BAA2B,CAAA;AAC3F,OAAO,EAAiB,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAElE,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,KAAK,IAAI,IAAI,CAAA;IACb,gBAAgB,CAAC,KAAK,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,GAAG,WAAW,CAAA;KAAE,KAAK,IAAI,GAAG,IAAI,CAAA;IACxF,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,EAAE,CAAC,EAAE,MAAM,IAAI,GAAG,IAAI,CAAA;CAC/D;AAED,MAAM,MAAM,QAAQ,GAAG,YAAY,GACjC,eAAe,GACf,eAAe,GACf,YAAY,GACZ,mBAAmB,GACnB,mBAAmB,GACnB,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"ws-client.d.ts","sourceRoot":"","sources":["../../src/client/ws-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,UAAU,EACV,QAAQ,EAGT,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACtE,OAAO,EAAoB,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAC5E,OAAO,EAAuB,KAAK,iBAAiB,EAAE,MAAM,yBAAyB,CAAA;AACrF,OAAO,EAAqB,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAC/E,OAAO,EAAqB,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAC/E,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACtE,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,mCAAmC,CAAA;AAC1C,OAAO,EAAyB,KAAK,mBAAmB,EAAE,MAAM,2BAA2B,CAAA;AAC3F,OAAO,EAAiB,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAElE,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,KAAK,IAAI,IAAI,CAAA;IACb,gBAAgB,CAAC,KAAK,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,GAAG,WAAW,CAAA;KAAE,KAAK,IAAI,GAAG,IAAI,CAAA;IACxF,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,EAAE,CAAC,EAAE,MAAM,IAAI,GAAG,IAAI,CAAA;CAC/D;AAED,MAAM,MAAM,QAAQ,GAAG,YAAY,GACjC,cAAc,GACd,eAAe,GACf,eAAe,GACf,YAAY,GACZ,mBAAmB,GACnB,mBAAmB,GACnB,WAAW,GACX,iBAAiB,CAAA;AAEnB,MAAM,MAAM,YAAY,GAAG,MAAM,UAAU,CAAA;AAE3C,MAAM,MAAM,QAAQ,GAAG;IACrB,kFAAkF;IAClF,cAAc,CACZ,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,WAAW,GAAG,gBAAgB,EACvC,UAAU,CAAC,EAAE,OAAO,GACnB,IAAI,CAAA;IACP,kFAAkF;IAClF,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,GAAG,IAAI,CAAA;IACxD,kGAAkG;IAClG,aAAa,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,CAAA;IACpC,gCAAgC;IAChC,KAAK,IAAI,IAAI,CAAA;CACd,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,MAAM,IAAI,CAAA;IACxB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAA;CACvC,CAAA;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,EAAE,EAAE,MAAM,EACV,GAAG,EAAE,QAAQ,EACb,KAAK,EAAE,YAAY,EACnB,IAAI,GAAE,YAAiB,GACtB,QAAQ,CA8FV"}
|