@llui/agent 0.0.29 → 0.0.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/dist/client/factory.d.ts.map +1 -1
  2. package/dist/client/factory.js +59 -0
  3. package/dist/client/factory.js.map +1 -1
  4. package/dist/client/rpc/observe.d.ts +20 -0
  5. package/dist/client/rpc/observe.d.ts.map +1 -0
  6. package/dist/client/rpc/observe.js +9 -0
  7. package/dist/client/rpc/observe.js.map +1 -0
  8. package/dist/client/rpc/send-message.d.ts +32 -3
  9. package/dist/client/rpc/send-message.d.ts.map +1 -1
  10. package/dist/client/rpc/send-message.js +94 -4
  11. package/dist/client/rpc/send-message.js.map +1 -1
  12. package/dist/client/ws-client.d.ts +2 -1
  13. package/dist/client/ws-client.d.ts.map +1 -1
  14. package/dist/client/ws-client.js +4 -0
  15. package/dist/client/ws-client.js.map +1 -1
  16. package/dist/protocol.d.ts +64 -1
  17. package/dist/protocol.d.ts.map +1 -1
  18. package/dist/protocol.js.map +1 -1
  19. package/dist/server/cloudflare/durable-object.d.ts +58 -0
  20. package/dist/server/cloudflare/durable-object.d.ts.map +1 -0
  21. package/dist/server/cloudflare/durable-object.js +33 -0
  22. package/dist/server/cloudflare/durable-object.js.map +1 -0
  23. package/dist/server/cloudflare/index.d.ts +49 -0
  24. package/dist/server/cloudflare/index.d.ts.map +1 -0
  25. package/dist/server/cloudflare/index.js +49 -0
  26. package/dist/server/cloudflare/index.js.map +1 -0
  27. package/dist/server/cloudflare/worker.d.ts +40 -0
  28. package/dist/server/cloudflare/worker.d.ts.map +1 -0
  29. package/dist/server/cloudflare/worker.js +55 -0
  30. package/dist/server/cloudflare/worker.js.map +1 -0
  31. package/dist/server/core-entry.d.ts +27 -0
  32. package/dist/server/core-entry.d.ts.map +1 -0
  33. package/dist/server/core-entry.js +19 -0
  34. package/dist/server/core-entry.js.map +1 -0
  35. package/dist/server/core.d.ts +78 -0
  36. package/dist/server/core.d.ts.map +1 -0
  37. package/dist/server/core.js +84 -0
  38. package/dist/server/core.js.map +1 -0
  39. package/dist/server/factory.d.ts +5 -3
  40. package/dist/server/factory.d.ts.map +1 -1
  41. package/dist/server/factory.js +18 -58
  42. package/dist/server/factory.js.map +1 -1
  43. package/dist/server/http/mint.d.ts.map +1 -1
  44. package/dist/server/http/mint.js +2 -3
  45. package/dist/server/http/mint.js.map +1 -1
  46. package/dist/server/http/resume.js +1 -1
  47. package/dist/server/http/resume.js.map +1 -1
  48. package/dist/server/identity.d.ts +5 -1
  49. package/dist/server/identity.d.ts.map +1 -1
  50. package/dist/server/identity.js +49 -11
  51. package/dist/server/identity.js.map +1 -1
  52. package/dist/server/index.d.ts +16 -1
  53. package/dist/server/index.d.ts.map +1 -1
  54. package/dist/server/index.js +13 -1
  55. package/dist/server/index.js.map +1 -1
  56. package/dist/server/lap/confirm-result.d.ts +2 -2
  57. package/dist/server/lap/confirm-result.d.ts.map +1 -1
  58. package/dist/server/lap/confirm-result.js +1 -1
  59. package/dist/server/lap/confirm-result.js.map +1 -1
  60. package/dist/server/lap/describe.d.ts +4 -4
  61. package/dist/server/lap/describe.d.ts.map +1 -1
  62. package/dist/server/lap/describe.js +4 -4
  63. package/dist/server/lap/describe.js.map +1 -1
  64. package/dist/server/lap/forward.d.ts +2 -2
  65. package/dist/server/lap/forward.d.ts.map +1 -1
  66. package/dist/server/lap/forward.js +1 -1
  67. package/dist/server/lap/forward.js.map +1 -1
  68. package/dist/server/lap/message.d.ts +2 -2
  69. package/dist/server/lap/message.d.ts.map +1 -1
  70. package/dist/server/lap/message.js +7 -3
  71. package/dist/server/lap/message.js.map +1 -1
  72. package/dist/server/lap/observe.d.ts +27 -0
  73. package/dist/server/lap/observe.d.ts.map +1 -0
  74. package/dist/server/lap/observe.js +77 -0
  75. package/dist/server/lap/observe.js.map +1 -0
  76. package/dist/server/lap/router.d.ts.map +1 -1
  77. package/dist/server/lap/router.js +3 -0
  78. package/dist/server/lap/router.js.map +1 -1
  79. package/dist/server/lap/wait.d.ts +2 -2
  80. package/dist/server/lap/wait.d.ts.map +1 -1
  81. package/dist/server/lap/wait.js +1 -1
  82. package/dist/server/lap/wait.js.map +1 -1
  83. package/dist/server/options.d.ts +25 -1
  84. package/dist/server/options.d.ts.map +1 -1
  85. package/dist/server/options.js.map +1 -1
  86. package/dist/server/token.d.ts +7 -3
  87. package/dist/server/token.d.ts.map +1 -1
  88. package/dist/server/token.js +66 -26
  89. package/dist/server/token.js.map +1 -1
  90. package/dist/server/web/adapter.d.ts +16 -0
  91. package/dist/server/web/adapter.d.ts.map +1 -0
  92. package/dist/server/web/adapter.js +45 -0
  93. package/dist/server/web/adapter.js.map +1 -0
  94. package/dist/server/web/index.d.ts +12 -0
  95. package/dist/server/web/index.d.ts.map +1 -0
  96. package/dist/server/web/index.js +12 -0
  97. package/dist/server/web/index.js.map +1 -0
  98. package/dist/server/web/upgrade.d.ts +41 -0
  99. package/dist/server/web/upgrade.d.ts.map +1 -0
  100. package/dist/server/web/upgrade.js +96 -0
  101. package/dist/server/web/upgrade.js.map +1 -0
  102. package/dist/server/ws/pairing-registry.d.ts +84 -21
  103. package/dist/server/ws/pairing-registry.d.ts.map +1 -1
  104. package/dist/server/ws/pairing-registry.js +89 -151
  105. package/dist/server/ws/pairing-registry.js.map +1 -1
  106. package/dist/server/ws/rpc.d.ts +39 -0
  107. package/dist/server/ws/rpc.d.ts.map +1 -0
  108. package/dist/server/ws/rpc.js +126 -0
  109. package/dist/server/ws/rpc.js.map +1 -0
  110. package/dist/server/ws/upgrade.d.ts +3 -3
  111. package/dist/server/ws/upgrade.d.ts.map +1 -1
  112. package/dist/server/ws/upgrade.js +2 -2
  113. package/dist/server/ws/upgrade.js.map +1 -1
  114. package/package.json +14 -2
@@ -1 +1 @@
1
- {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/client/factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAC1C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAC1D,OAAO,KAAK,EACV,SAAS,EACT,YAAY,EACZ,kBAAkB,EAEnB,MAAM,gBAAgB,CAAA;AAIvB,KAAK,iBAAiB,GAAG;IACvB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAA;IACrD,oBAAoB,CAAC,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACjD,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,IAAI,EAAE,MAAM,CAAA;IACZ,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC,CAAA;IACpF,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,YAAY,CAAA;CAChD,CAAA;AAED,MAAM,MAAM,qBAAqB,CAAC,KAAK,EAAE,GAAG,IAAI;IAC9C,MAAM,EAAE,SAAS,CAAA;IACjB,GAAG,EAAE,iBAAiB,CAAA;IACtB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,OAAO,GAAG,IAAI,CAAA;IAC3B,MAAM,EAAE;QACN,UAAU,EAAE,CAAC,CAAC,EAAE,KAAK,KAAK,OAAO,CAAA;QACjC,UAAU,EAAE,CAAC,CAAC,EAAE,KAAK,KAAK,iBAAiB,CAAA;QAC3C,cAAc,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,GAAG,CAAA;QACnC,cAAc,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,GAAG,CAAA;QACnC;;;;;WAKG;QACH,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,GAAG,CAAA;KACjC,CAAA;CACF,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,aAAa,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACrD,KAAK,IAAI,IAAI,CAAA;IACb,IAAI,IAAI,IAAI,CAAA;CACb,CAAA;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,GAAG,EAC1C,IAAI,EAAE,qBAAqB,CAAC,KAAK,EAAE,GAAG,CAAC,GACtC,WAAW,CAoGb"}
1
+ {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/client/factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAC1C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAC1D,OAAO,KAAK,EACV,SAAS,EACT,YAAY,EAEZ,kBAAkB,EAEnB,MAAM,gBAAgB,CAAA;AAIvB,KAAK,iBAAiB,GAAG;IACvB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAA;IACrD,oBAAoB,CAAC,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACjD,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,IAAI,EAAE,MAAM,CAAA;IACZ,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC,CAAA;IACpF,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,YAAY,CAAA;CAChD,CAAA;AAED,MAAM,MAAM,qBAAqB,CAAC,KAAK,EAAE,GAAG,IAAI;IAC9C,MAAM,EAAE,SAAS,CAAA;IACjB,GAAG,EAAE,iBAAiB,CAAA;IACtB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,OAAO,GAAG,IAAI,CAAA;IAC3B,MAAM,EAAE;QACN,UAAU,EAAE,CAAC,CAAC,EAAE,KAAK,KAAK,OAAO,CAAA;QACjC,UAAU,EAAE,CAAC,CAAC,EAAE,KAAK,KAAK,iBAAiB,CAAA;QAC3C,cAAc,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,GAAG,CAAA;QACnC,cAAc,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,GAAG,CAAA;QACnC;;;;;WAKG;QACH,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,GAAG,CAAA;KACjC,CAAA;CACF,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,aAAa,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACrD,KAAK,IAAI,IAAI,CAAA;IACb,IAAI,IAAI,IAAI,CAAA;CACb,CAAA;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,GAAG,EAC1C,IAAI,EAAE,qBAAqB,CAAC,KAAK,EAAE,GAAG,CAAC,GACtC,WAAW,CAoJb"}
@@ -6,10 +6,58 @@ export function createAgentClient(opts) {
6
6
  let confirmPollTimer = null;
7
7
  let stateSubscription = null;
8
8
  const resolvedConfirms = new Set();
9
+ // Drain-error buffer: populated by persistent `window.error` and
10
+ // `window.unhandledrejection` listeners installed on `start()`. The
11
+ // send-message drain loop consumes and clears it per call so the
12
+ // envelope surfaces only errors that fired during that window.
13
+ const drainErrors = [];
14
+ let errorListenersInstalled = false;
15
+ let onErrorEvt = null;
16
+ let onRejectionEvt = null;
17
+ function installErrorListeners() {
18
+ if (errorListenersInstalled)
19
+ return;
20
+ if (typeof window === 'undefined')
21
+ return;
22
+ onErrorEvt = (e) => {
23
+ drainErrors.push({
24
+ kind: 'error',
25
+ message: e.message ?? String(e.error ?? 'unknown error'),
26
+ stack: e.error instanceof Error ? e.error.stack : undefined,
27
+ });
28
+ };
29
+ onRejectionEvt = (e) => {
30
+ const r = e.reason;
31
+ const message = r instanceof Error ? r.message : typeof r === 'string' ? r : safeStringify(r);
32
+ drainErrors.push({
33
+ kind: 'unhandledrejection',
34
+ message,
35
+ stack: r instanceof Error ? r.stack : undefined,
36
+ });
37
+ };
38
+ window.addEventListener('error', onErrorEvt);
39
+ window.addEventListener('unhandledrejection', onRejectionEvt);
40
+ errorListenersInstalled = true;
41
+ }
42
+ function removeErrorListeners() {
43
+ if (!errorListenersInstalled)
44
+ return;
45
+ if (typeof window === 'undefined')
46
+ return;
47
+ if (onErrorEvt)
48
+ window.removeEventListener('error', onErrorEvt);
49
+ if (onRejectionEvt)
50
+ window.removeEventListener('unhandledrejection', onRejectionEvt);
51
+ onErrorEvt = null;
52
+ onRejectionEvt = null;
53
+ errorListenersInstalled = false;
54
+ }
9
55
  const rpcHost = {
10
56
  getState: () => opts.handle.getState(),
11
57
  send: (m) => opts.handle.send(m),
12
58
  flush: () => opts.handle.flush(),
59
+ subscribe: (listener) => opts.handle.subscribe(() => listener()),
60
+ getAndClearDrainErrors: () => drainErrors.splice(0, drainErrors.length),
13
61
  getMsgAnnotations: () => opts.def.__msgAnnotations ?? null,
14
62
  getBindingDescriptors: () => opts.def.__bindingDescriptors ?? null,
15
63
  getAgentAffordances: () => opts.def.agentAffordances ?? null,
@@ -89,6 +137,7 @@ export function createAgentClient(opts) {
89
137
  wsClient?.emitStateUpdate('/', state);
90
138
  });
91
139
  }
140
+ installErrorListeners();
92
141
  },
93
142
  stop() {
94
143
  if (confirmPollTimer)
@@ -98,8 +147,18 @@ export function createAgentClient(opts) {
98
147
  stateSubscription();
99
148
  stateSubscription = null;
100
149
  }
150
+ removeErrorListeners();
151
+ drainErrors.length = 0;
101
152
  wsClient?.close();
102
153
  },
103
154
  };
104
155
  }
156
+ function safeStringify(v) {
157
+ try {
158
+ return JSON.stringify(v);
159
+ }
160
+ catch {
161
+ return String(v);
162
+ }
163
+ }
105
164
  //# sourceMappingURL=factory.js.map
@@ -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 MessageAnnotations,\n MessageSchemaEntry,\n} from '../protocol.js'\nimport { attachWsClient, type WsLike, type RpcHosts } from './ws-client.js'\nimport { createEffectHandler } from './effect-handler.js'\n\ntype ComponentMetadata = {\n __msgSchema?: unknown\n __stateSchema?: unknown\n __msgAnnotations?: Record<string, MessageAnnotations>\n __bindingDescriptors?: Array<{ variant: string }>\n __schemaHash?: string\n name: string\n agentAffordances?: (state: unknown) => Array<{ type: string; [k: string]: unknown }>\n agentDocs?: AgentDocs\n agentContext?: (state: unknown) => AgentContext\n}\n\nexport type CreateAgentClientOpts<State, Msg> = {\n handle: AppHandle\n def: ComponentMetadata\n appVersion?: string\n rootElement: Element | null\n slices: {\n getConnect: (s: State) => unknown\n getConfirm: (s: State) => AgentConfirmState\n wrapConnectMsg: (m: unknown) => Msg\n wrapConfirmMsg: (m: unknown) => Msg\n /**\n * Optional: wrap an agentLog msg so the client-side activity feed\n * mirrors what Claude is doing. If omitted, outbound log-append\n * frames still go to the server, but the local agent.log slice\n * stays empty (the UI won't show activity).\n */\n wrapLogMsg?: (m: unknown) => Msg\n }\n}\n\nexport type AgentClient = {\n effectHandler: (effect: AgentEffect) => Promise<void>\n start(): void\n stop(): void\n}\n\nexport function createAgentClient<State, Msg>(\n opts: CreateAgentClientOpts<State, Msg>,\n): AgentClient {\n let ws: WebSocket | null = null\n let wsClient: ReturnType<typeof attachWsClient> | null = null\n let confirmPollTimer: ReturnType<typeof setInterval> | null = null\n let stateSubscription: (() => void) | null = null\n const resolvedConfirms = new Set<string>()\n\n const rpcHost: RpcHosts = {\n getState: () => opts.handle.getState(),\n send: (m) => opts.handle.send(m),\n flush: () => opts.handle.flush(),\n getMsgAnnotations: () => opts.def.__msgAnnotations ?? null,\n getBindingDescriptors: () => opts.def.__bindingDescriptors ?? null,\n getAgentAffordances: () => opts.def.agentAffordances ?? null,\n getAgentContext: () => opts.def.agentContext ?? null,\n getRootElement: () => opts.rootElement,\n proposeConfirm: (entry) => {\n opts.handle.send(opts.slices.wrapConfirmMsg({ type: 'Propose', entry }))\n },\n }\n\n const helloBuilder = () => ({\n t: 'hello' as const,\n appName: opts.def.name,\n appVersion: opts.appVersion ?? '0.0.0',\n msgSchema: (opts.def.__msgSchema ?? {}) as Record<string, MessageSchemaEntry>,\n stateSchema: (opts.def.__stateSchema ?? {}) as object,\n affordancesSample: opts.def.agentAffordances\n ? opts.def.agentAffordances(opts.handle.getState())\n : [],\n docs: opts.def.agentDocs ?? null,\n schemaHash: opts.def.__schemaHash ?? '',\n })\n\n const effectHandler = createEffectHandler({\n send: (m) => opts.handle.send(m),\n wrapAgentConnect: (m) => opts.slices.wrapConnectMsg(m),\n forward: (payload) => opts.handle.send(payload),\n openWs: (token, wsUrl) => {\n if (ws) ws.close()\n ws = new WebSocket(`${wsUrl}?token=${encodeURIComponent(token)}`)\n wsClient = attachWsClient(ws as unknown as WsLike, rpcHost, helloBuilder, {\n onActivated: () => {\n opts.handle.send(opts.slices.wrapConnectMsg({ type: 'ActivatedByClaude' }))\n },\n onLogEntry: opts.slices.wrapLogMsg\n ? (entry) => {\n opts.handle.send(opts.slices.wrapLogMsg!({ type: 'Append', entry }))\n }\n : undefined,\n })\n ws.addEventListener('open', () => {\n opts.handle.send(opts.slices.wrapConnectMsg({ type: 'WsOpened' }))\n })\n ws.addEventListener('close', () => {\n opts.handle.send(opts.slices.wrapConnectMsg({ type: 'WsClosed' }))\n })\n },\n closeWs: () => {\n wsClient?.close()\n ws = null\n wsClient = null\n },\n })\n\n const pollConfirms = () => {\n const state = opts.handle.getState() as State\n const confirm = opts.slices.getConfirm(state)\n for (const entry of confirm.pending) {\n if (entry.status === 'pending') continue\n if (resolvedConfirms.has(entry.id)) continue\n resolvedConfirms.add(entry.id)\n if (entry.status === 'approved') {\n wsClient?.resolveConfirm(entry.id, 'confirmed', opts.handle.getState())\n } else if (entry.status === 'rejected') {\n wsClient?.resolveConfirm(entry.id, 'user-cancelled')\n }\n }\n }\n\n return {\n effectHandler,\n start() {\n if (!confirmPollTimer) confirmPollTimer = setInterval(pollConfirms, 200)\n if (!stateSubscription) {\n stateSubscription = opts.handle.subscribe((state) => {\n wsClient?.emitStateUpdate('/', state)\n })\n }\n },\n stop() {\n if (confirmPollTimer) clearInterval(confirmPollTimer)\n confirmPollTimer = null\n if (stateSubscription) {\n stateSubscription()\n stateSubscription = null\n }\n wsClient?.close()\n },\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'\n\ntype ComponentMetadata = {\n __msgSchema?: unknown\n __stateSchema?: unknown\n __msgAnnotations?: Record<string, MessageAnnotations>\n __bindingDescriptors?: Array<{ variant: string }>\n __schemaHash?: string\n name: string\n agentAffordances?: (state: unknown) => Array<{ type: string; [k: string]: unknown }>\n agentDocs?: AgentDocs\n agentContext?: (state: unknown) => AgentContext\n}\n\nexport type CreateAgentClientOpts<State, Msg> = {\n handle: AppHandle\n def: ComponentMetadata\n appVersion?: string\n rootElement: Element | null\n slices: {\n getConnect: (s: State) => unknown\n getConfirm: (s: State) => AgentConfirmState\n wrapConnectMsg: (m: unknown) => Msg\n wrapConfirmMsg: (m: unknown) => Msg\n /**\n * Optional: wrap an agentLog msg so the client-side activity feed\n * mirrors what Claude is doing. If omitted, outbound log-append\n * frames still go to the server, but the local agent.log slice\n * stays empty (the UI won't show activity).\n */\n wrapLogMsg?: (m: unknown) => Msg\n }\n}\n\nexport type AgentClient = {\n effectHandler: (effect: AgentEffect) => Promise<void>\n start(): void\n stop(): void\n}\n\nexport function createAgentClient<State, Msg>(\n opts: CreateAgentClientOpts<State, Msg>,\n): AgentClient {\n let ws: WebSocket | null = null\n let wsClient: ReturnType<typeof attachWsClient> | null = null\n let confirmPollTimer: ReturnType<typeof setInterval> | null = null\n let stateSubscription: (() => void) | null = null\n const resolvedConfirms = new Set<string>()\n\n // Drain-error buffer: populated by persistent `window.error` and\n // `window.unhandledrejection` listeners installed on `start()`. The\n // send-message drain loop consumes and clears it per call so the\n // envelope surfaces only errors that fired during that window.\n const drainErrors: LapDrainMeta['errors'] = []\n let errorListenersInstalled = false\n let onErrorEvt: ((e: ErrorEvent) => void) | null = null\n let onRejectionEvt: ((e: PromiseRejectionEvent) => void) | null = null\n\n function installErrorListeners(): void {\n if (errorListenersInstalled) return\n if (typeof window === 'undefined') return\n onErrorEvt = (e: ErrorEvent) => {\n drainErrors.push({\n kind: 'error',\n message: e.message ?? String(e.error ?? 'unknown error'),\n stack: e.error instanceof Error ? e.error.stack : undefined,\n })\n }\n onRejectionEvt = (e: PromiseRejectionEvent) => {\n const r = e.reason\n const message = r instanceof Error ? r.message : typeof r === 'string' ? r : safeStringify(r)\n drainErrors.push({\n kind: 'unhandledrejection',\n message,\n stack: r instanceof Error ? r.stack : undefined,\n })\n }\n window.addEventListener('error', onErrorEvt)\n window.addEventListener('unhandledrejection', onRejectionEvt)\n errorListenersInstalled = true\n }\n\n function removeErrorListeners(): void {\n if (!errorListenersInstalled) return\n if (typeof window === 'undefined') return\n if (onErrorEvt) window.removeEventListener('error', onErrorEvt)\n if (onRejectionEvt) window.removeEventListener('unhandledrejection', onRejectionEvt)\n onErrorEvt = null\n onRejectionEvt = null\n errorListenersInstalled = false\n }\n\n const rpcHost: RpcHosts = {\n getState: () => opts.handle.getState(),\n send: (m) => opts.handle.send(m),\n flush: () => opts.handle.flush(),\n subscribe: (listener) => opts.handle.subscribe(() => listener()),\n getAndClearDrainErrors: () => drainErrors.splice(0, drainErrors.length),\n getMsgAnnotations: () => opts.def.__msgAnnotations ?? null,\n getBindingDescriptors: () => opts.def.__bindingDescriptors ?? null,\n getAgentAffordances: () => opts.def.agentAffordances ?? null,\n getAgentContext: () => opts.def.agentContext ?? null,\n getRootElement: () => opts.rootElement,\n proposeConfirm: (entry) => {\n opts.handle.send(opts.slices.wrapConfirmMsg({ type: 'Propose', entry }))\n },\n }\n\n const helloBuilder = () => ({\n t: 'hello' as const,\n appName: opts.def.name,\n appVersion: opts.appVersion ?? '0.0.0',\n msgSchema: (opts.def.__msgSchema ?? {}) as Record<string, MessageSchemaEntry>,\n stateSchema: (opts.def.__stateSchema ?? {}) as object,\n affordancesSample: opts.def.agentAffordances\n ? opts.def.agentAffordances(opts.handle.getState())\n : [],\n docs: opts.def.agentDocs ?? null,\n schemaHash: opts.def.__schemaHash ?? '',\n })\n\n const effectHandler = createEffectHandler({\n send: (m) => opts.handle.send(m),\n wrapAgentConnect: (m) => opts.slices.wrapConnectMsg(m),\n forward: (payload) => opts.handle.send(payload),\n openWs: (token, wsUrl) => {\n if (ws) ws.close()\n ws = new WebSocket(`${wsUrl}?token=${encodeURIComponent(token)}`)\n wsClient = attachWsClient(ws as unknown as WsLike, rpcHost, helloBuilder, {\n onActivated: () => {\n opts.handle.send(opts.slices.wrapConnectMsg({ type: 'ActivatedByClaude' }))\n },\n onLogEntry: opts.slices.wrapLogMsg\n ? (entry) => {\n opts.handle.send(opts.slices.wrapLogMsg!({ type: 'Append', entry }))\n }\n : undefined,\n })\n ws.addEventListener('open', () => {\n opts.handle.send(opts.slices.wrapConnectMsg({ type: 'WsOpened' }))\n })\n ws.addEventListener('close', () => {\n opts.handle.send(opts.slices.wrapConnectMsg({ type: 'WsClosed' }))\n })\n },\n closeWs: () => {\n wsClient?.close()\n ws = null\n wsClient = null\n },\n })\n\n const pollConfirms = () => {\n const state = opts.handle.getState() as State\n const confirm = opts.slices.getConfirm(state)\n for (const entry of confirm.pending) {\n if (entry.status === 'pending') continue\n if (resolvedConfirms.has(entry.id)) continue\n resolvedConfirms.add(entry.id)\n if (entry.status === 'approved') {\n wsClient?.resolveConfirm(entry.id, 'confirmed', opts.handle.getState())\n } else if (entry.status === 'rejected') {\n wsClient?.resolveConfirm(entry.id, 'user-cancelled')\n }\n }\n }\n\n return {\n effectHandler,\n start() {\n if (!confirmPollTimer) confirmPollTimer = setInterval(pollConfirms, 200)\n if (!stateSubscription) {\n stateSubscription = opts.handle.subscribe((state) => {\n wsClient?.emitStateUpdate('/', state)\n })\n }\n installErrorListeners()\n },\n stop() {\n if (confirmPollTimer) clearInterval(confirmPollTimer)\n confirmPollTimer = null\n if (stateSubscription) {\n stateSubscription()\n stateSubscription = null\n }\n removeErrorListeners()\n drainErrors.length = 0\n wsClient?.close()\n },\n }\n}\n\nfunction safeStringify(v: unknown): string {\n try {\n return JSON.stringify(v)\n } catch {\n return String(v)\n }\n}\n"]}
@@ -0,0 +1,20 @@
1
+ import type { AgentContext, LapActionsResponse } from '../../protocol.js';
2
+ import { type ListActionsHost } from './list-actions.js';
3
+ export type ObserveHost = ListActionsHost & {
4
+ getAgentContext(): ((state: unknown) => AgentContext) | null;
5
+ };
6
+ /**
7
+ * Dynamic slice of the unified `observe` response. The server-side LAP
8
+ * endpoint composes this with the static description (name/version/
9
+ * messages/docs from the cached hello frame) to produce the full
10
+ * `LapObserveResponse`. Keeping the split here means a single WS
11
+ * round-trip captures everything that changes with state, while the
12
+ * static metadata stays server-side where it's already cached.
13
+ */
14
+ export type ObserveResult = {
15
+ state: unknown;
16
+ actions: LapActionsResponse['actions'];
17
+ context: AgentContext | null;
18
+ };
19
+ export declare function handleObserve(host: ObserveHost): ObserveResult;
20
+ //# sourceMappingURL=observe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"observe.d.ts","sourceRoot":"","sources":["../../../src/client/rpc/observe.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AACzE,OAAO,EAAqB,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAE3E,MAAM,MAAM,WAAW,GAAG,eAAe,GAAG;IAC1C,eAAe,IAAI,CAAC,CAAC,KAAK,EAAE,OAAO,KAAK,YAAY,CAAC,GAAG,IAAI,CAAA;CAC7D,CAAA;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,EAAE,OAAO,CAAA;IACd,OAAO,EAAE,kBAAkB,CAAC,SAAS,CAAC,CAAA;IACtC,OAAO,EAAE,YAAY,GAAG,IAAI,CAAA;CAC7B,CAAA;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,WAAW,GAAG,aAAa,CAM9D"}
@@ -0,0 +1,9 @@
1
+ import { handleListActions } from './list-actions.js';
2
+ export function handleObserve(host) {
3
+ const state = host.getState();
4
+ const actions = handleListActions(host).actions;
5
+ const contextFn = host.getAgentContext();
6
+ const context = contextFn ? contextFn(state) : null;
7
+ return { state, actions, context };
8
+ }
9
+ //# sourceMappingURL=observe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"observe.js","sourceRoot":"","sources":["../../../src/client/rpc/observe.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAwB,MAAM,mBAAmB,CAAA;AAoB3E,MAAM,UAAU,aAAa,CAAC,IAAiB;IAC7C,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAA;IAC7B,MAAM,OAAO,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC,OAAO,CAAA;IAC/C,MAAM,SAAS,GAAG,IAAI,CAAC,eAAe,EAAE,CAAA;IACxC,MAAM,OAAO,GAAG,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IACnD,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,CAAA;AACpC,CAAC","sourcesContent":["import type { AgentContext, LapActionsResponse } from '../../protocol.js'\nimport { handleListActions, type ListActionsHost } from './list-actions.js'\n\nexport type ObserveHost = ListActionsHost & {\n getAgentContext(): ((state: unknown) => AgentContext) | null\n}\n\n/**\n * Dynamic slice of the unified `observe` response. The server-side LAP\n * endpoint composes this with the static description (name/version/\n * messages/docs from the cached hello frame) to produce the full\n * `LapObserveResponse`. Keeping the split here means a single WS\n * round-trip captures everything that changes with state, while the\n * static metadata stays server-side where it's already cached.\n */\nexport type ObserveResult = {\n state: unknown\n actions: LapActionsResponse['actions']\n context: AgentContext | null\n}\n\nexport function handleObserve(host: ObserveHost): ObserveResult {\n const state = host.getState()\n const actions = handleListActions(host).actions\n const contextFn = host.getAgentContext()\n const context = contextFn ? contextFn(state) : null\n return { state, actions, context }\n}\n"]}
@@ -1,18 +1,43 @@
1
- import type { LapMessageResponse, MessageAnnotations } from '../../protocol.js';
1
+ import { type ListActionsHost } from './list-actions.js';
2
+ import type { LapActionsResponse, LapDrainMeta, LapMessageResponse, MessageAnnotations } from '../../protocol.js';
2
3
  export type SendMessageArgs = {
3
4
  msg: {
4
5
  type: string;
5
6
  [k: string]: unknown;
6
7
  };
7
8
  reason?: string;
8
- waitFor?: 'idle' | 'none';
9
+ /** See LapMessageRequest['waitFor']. Default: 'drained'. */
10
+ waitFor?: 'drained' | 'idle' | 'none';
11
+ /** See LapMessageRequest['drainQuietMs']. Default: 100ms. */
12
+ drainQuietMs?: number;
13
+ /** See LapMessageRequest['timeoutMs']. Default: 5000ms. */
9
14
  timeoutMs?: number;
10
15
  };
11
- export type SendMessageHost = {
16
+ export type SendMessageHost = ListActionsHost & {
12
17
  getState(): unknown;
13
18
  send(msg: unknown): void;
14
19
  flush(): void;
20
+ /**
21
+ * Register a listener called after every update cycle commits —
22
+ * backed by `AppHandle.subscribe`. Returns an unsubscribe function.
23
+ * The drain loop uses this to detect message-queue quiescence: each
24
+ * listener fire resets the quiet-window timer; no fires for
25
+ * `drainQuietMs` means the loop has gone idle and async effects (if
26
+ * any) have either completed or are persistent
27
+ * (websocket/interval/storageWatch).
28
+ */
29
+ subscribe(listener: () => void): () => void;
15
30
  getMsgAnnotations(): Record<string, MessageAnnotations> | null;
31
+ /**
32
+ * Snapshot and clear the drain-error buffer. The agent factory
33
+ * installs persistent `window.error` / `unhandledrejection`
34
+ * listeners that accumulate into this buffer; calling this at the
35
+ * start of a drain discards stale errors from prior windows, and
36
+ * calling it at the end yields just the errors that fired during
37
+ * this drain. Optional — when omitted (e.g., Node test harness
38
+ * without `window`), the drain envelope records an empty array.
39
+ */
40
+ getAndClearDrainErrors?: () => LapDrainMeta['errors'];
16
41
  /** Called when @requiresConfirm; caller stores a ConfirmEntry in state. */
17
42
  proposeConfirm(entry: {
18
43
  id: string;
@@ -25,4 +50,8 @@ export type SendMessageHost = {
25
50
  }): void;
26
51
  };
27
52
  export declare function handleSendMessage(host: SendMessageHost, args: SendMessageArgs): Promise<LapMessageResponse>;
53
+ export type DispatchedEnvelope = Extract<LapMessageResponse, {
54
+ status: 'dispatched';
55
+ }>;
56
+ export type { LapActionsResponse };
28
57
  //# sourceMappingURL=send-message.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"send-message.d.ts","sourceRoot":"","sources":["../../../src/client/rpc/send-message.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAE/E,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,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACzB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,IAAI,OAAO,CAAA;IACnB,IAAI,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAAA;IACxB,KAAK,IAAI,IAAI,CAAA;IACb,iBAAiB,IAAI,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,GAAG,IAAI,CAAA;IAC9D,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;AAED,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,eAAe,EACrB,IAAI,EAAE,eAAe,GACpB,OAAO,CAAC,kBAAkB,CAAC,CAwC7B"}
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;AAC3E,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;CACnB,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,CAsG7B;AAsCD,MAAM,MAAM,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,EAAE;IAAE,MAAM,EAAE,YAAY,CAAA;CAAE,CAAC,CAAA;AACtF,YAAY,EAAE,kBAAkB,EAAE,CAAA"}
@@ -1,4 +1,7 @@
1
1
  import { randomUUID } from '../uuid.js';
2
+ import { handleListActions } from './list-actions.js';
3
+ const DEFAULT_QUIET_MS = 100;
4
+ const DEFAULT_TIMEOUT_MS = 5_000;
2
5
  export async function handleSendMessage(host, args) {
3
6
  if (!args.msg || typeof args.msg.type !== 'string') {
4
7
  return { status: 'rejected', reason: 'invalid' };
@@ -29,12 +32,99 @@ export async function handleSendMessage(host, args) {
29
32
  });
30
33
  return { status: 'pending-confirmation', confirmId: id };
31
34
  }
32
- host.send(args.msg);
33
- if (args.waitFor !== 'none') {
35
+ const waitFor = args.waitFor ?? 'drained';
36
+ const quietMs = Math.max(0, args.drainQuietMs ?? DEFAULT_QUIET_MS);
37
+ const capMs = Math.max(0, args.timeoutMs ?? DEFAULT_TIMEOUT_MS);
38
+ if (waitFor === 'none') {
39
+ host.send(args.msg);
40
+ return dispatched(host, emptyDrain());
41
+ }
42
+ if (waitFor === 'idle') {
43
+ host.send(args.msg);
34
44
  host.flush();
35
- // Let the microtask queue settle:
36
45
  await Promise.resolve();
46
+ return dispatched(host, { effectsObserved: 1, durationMs: 0, timedOut: false, errors: [] });
47
+ }
48
+ // waitFor === 'drained' — message-queue quiescence detection.
49
+ // Clear any errors buffered before this call so `drain.errors`
50
+ // attributes only to this window.
51
+ host.getAndClearDrainErrors?.();
52
+ const t0 = now();
53
+ let observed = 0;
54
+ let wake = null;
55
+ const unsub = host.subscribe(() => {
56
+ observed++;
57
+ const w = wake;
58
+ wake = null;
59
+ w?.('msg');
60
+ });
61
+ try {
62
+ host.send(args.msg);
63
+ host.flush();
64
+ while (true) {
65
+ const elapsed = now() - t0;
66
+ if (elapsed >= capMs) {
67
+ return dispatched(host, {
68
+ effectsObserved: observed,
69
+ durationMs: elapsed,
70
+ timedOut: true,
71
+ errors: host.getAndClearDrainErrors?.() ?? [],
72
+ });
73
+ }
74
+ const budget = Math.min(quietMs, capMs - elapsed);
75
+ // When the cap is within `quietMs` of `elapsed`, the quiet
76
+ // window is truncated. In that case a timeout resolution does
77
+ // NOT mean we detected quiescence — it means the cap cut the
78
+ // window short. Only a full-length quiet window that elapses
79
+ // without a new commit counts as real idle.
80
+ const fullQuiet = budget >= quietMs;
81
+ const reason = await awaitQuietOrMsg(budget, (resolve) => {
82
+ wake = resolve;
83
+ });
84
+ if (reason === 'timeout') {
85
+ return dispatched(host, {
86
+ effectsObserved: observed,
87
+ durationMs: now() - t0,
88
+ timedOut: !fullQuiet,
89
+ errors: host.getAndClearDrainErrors?.() ?? [],
90
+ });
91
+ }
92
+ // A commit fired during the wait — flush any queued follow-ups so
93
+ // effects dispatched by that cycle run before we re-check.
94
+ host.flush();
95
+ }
96
+ }
97
+ finally {
98
+ unsub();
37
99
  }
38
- return { status: 'dispatched', stateAfter: host.getState() };
100
+ }
101
+ function dispatched(host, drain) {
102
+ return {
103
+ status: 'dispatched',
104
+ stateAfter: host.getState(),
105
+ actions: handleListActions(host).actions,
106
+ drain,
107
+ };
108
+ }
109
+ function emptyDrain() {
110
+ return { effectsObserved: 0, durationMs: 0, timedOut: false, errors: [] };
111
+ }
112
+ function awaitQuietOrMsg(budgetMs, registerWake) {
113
+ return new Promise((resolve) => {
114
+ let settled = false;
115
+ const guarded = (r) => {
116
+ if (settled)
117
+ return;
118
+ settled = true;
119
+ resolve(r);
120
+ };
121
+ registerWake(guarded);
122
+ setTimeout(() => guarded('timeout'), budgetMs);
123
+ });
124
+ }
125
+ function now() {
126
+ return typeof performance !== 'undefined' && typeof performance.now === 'function'
127
+ ? performance.now()
128
+ : Date.now();
39
129
  }
40
130
  //# sourceMappingURL=send-message.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"send-message.js","sourceRoot":"","sources":["../../../src/client/rpc/send-message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AA2BvC,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,IAAqB,EACrB,IAAqB;IAErB,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,CAAA;IAClD,CAAC;IACD,MAAM,WAAW,GAAG,IAAI,CAAC,iBAAiB,EAAE,IAAI,EAAE,CAAA;IAClD,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IAEtC,wEAAwE;IACxE,qEAAqE;IACrE,kEAAkE;IAClE,MAAM,cAAc,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,MAAM,GAAG,CAAC,CAAA;IAC1D,IAAI,cAAc,IAAI,CAAC,GAAG,EAAE,CAAC;QAC3B,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,oBAAoB,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAA;IAC/F,CAAC;IAED,IAAI,GAAG,EAAE,SAAS,EAAE,CAAC;QACnB,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,CAAA;IACpD,CAAC;IACD,IAAI,GAAG,EAAE,eAAe,EAAE,CAAC;QACzB,MAAM,EAAE,GAAG,UAAU,EAAE,CAAA;QACvB,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,CAAA;QAC5C,IAAI,CAAC,cAAc,CAAC;YAClB,EAAE;YACF,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI;YACtB,OAAO;YACP,MAAM,EAAE,GAAG,EAAE,MAAM,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI;YACpC,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,IAAI;YAC3B,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE;YACtB,MAAM,EAAE,SAAS;SAClB,CAAC,CAAA;QACF,OAAO,EAAE,MAAM,EAAE,sBAAsB,EAAE,SAAS,EAAE,EAAE,EAAE,CAAA;IAC1D,CAAC;IAED,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACnB,IAAI,IAAI,CAAC,OAAO,KAAK,MAAM,EAAE,CAAC;QAC5B,IAAI,CAAC,KAAK,EAAE,CAAA;QACZ,kCAAkC;QAClC,MAAM,OAAO,CAAC,OAAO,EAAE,CAAA;IACzB,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,IAAI,CAAC,QAAQ,EAAE,EAAE,CAAA;AAC9D,CAAC","sourcesContent":["import { randomUUID } from '../uuid.js'\nimport type { LapMessageResponse, MessageAnnotations } from '../../protocol.js'\n\nexport type SendMessageArgs = {\n msg: { type: string; [k: string]: unknown }\n reason?: string\n waitFor?: 'idle' | 'none'\n timeoutMs?: number\n}\n\nexport type SendMessageHost = {\n getState(): unknown\n send(msg: unknown): void\n flush(): void\n getMsgAnnotations(): Record<string, MessageAnnotations> | null\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\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 host.send(args.msg)\n if (args.waitFor !== 'none') {\n host.flush()\n // Let the microtask queue settle:\n await Promise.resolve()\n }\n return { status: 'dispatched', stateAfter: host.getState() }\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 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"]}
@@ -5,6 +5,7 @@ import { type ListActionsHost } from './rpc/list-actions.js';
5
5
  import { type QueryDomHost } from './rpc/query-dom.js';
6
6
  import { type DescribeVisibleHost } from './rpc/describe-visible-content.js';
7
7
  import { type DescribeContextHost } from './rpc/describe-context.js';
8
+ import { type ObserveHost } from './rpc/observe.js';
8
9
  export interface WsLike {
9
10
  send(data: string): void;
10
11
  close(): void;
@@ -13,7 +14,7 @@ export interface WsLike {
13
14
  }) => void): void;
14
15
  addEventListener(event: 'open' | 'close', h: () => void): void;
15
16
  }
16
- export type RpcHosts = GetStateHost & SendMessageHost & ListActionsHost & QueryDomHost & DescribeVisibleHost & DescribeContextHost;
17
+ export type RpcHosts = GetStateHost & SendMessageHost & ListActionsHost & QueryDomHost & DescribeVisibleHost & DescribeContextHost & ObserveHost;
17
18
  export type HelloBuilder = () => HelloFrame;
18
19
  export type WsClient = {
19
20
  /** 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;AAE3F,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,CAAA;AAErB,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,CAsFV"}
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;AAEb,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,CAsFV"}
@@ -4,6 +4,7 @@ import { handleListActions } from './rpc/list-actions.js';
4
4
  import { handleQueryDom } from './rpc/query-dom.js';
5
5
  import { handleDescribeVisibleContent, } from './rpc/describe-visible-content.js';
6
6
  import { handleDescribeContext } from './rpc/describe-context.js';
7
+ import { handleObserve } from './rpc/observe.js';
7
8
  /**
8
9
  * Wires up a WebSocket to serve rpc requests from the server. See spec §9.4.
9
10
  */
@@ -109,6 +110,8 @@ async function dispatch(tool, args, rpc) {
109
110
  return handleDescribeVisibleContent(rpc);
110
111
  case 'describe_context':
111
112
  return handleDescribeContext(rpc);
113
+ case 'observe':
114
+ return handleObserve(rpc);
112
115
  default:
113
116
  throw { code: 'invalid', detail: `unknown tool: ${tool}` };
114
117
  }
@@ -119,6 +122,7 @@ const READ_TOOLS = new Set([
119
122
  'describe_context',
120
123
  'query_dom',
121
124
  'describe_visible_content',
125
+ 'observe',
122
126
  ]);
123
127
  function getLogKindForTool(tool, result, err) {
124
128
  if (err !== null)
@@ -1 +1 @@
1
- {"version":3,"file":"ws-client.js","sourceRoot":"","sources":["../../src/client/ws-client.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n ClientFrame,\n ServerFrame,\n HelloFrame,\n LogEntry,\n LogKind,\n MessageAnnotations,\n} from '../protocol.js'\nimport { handleGetState, type GetStateHost } from './rpc/get-state.js'\nimport { handleSendMessage, type SendMessageHost } from './rpc/send-message.js'\nimport { handleListActions, type ListActionsHost } from './rpc/list-actions.js'\nimport { handleQueryDom, type QueryDomHost } from './rpc/query-dom.js'\nimport {\n handleDescribeVisibleContent,\n type DescribeVisibleHost,\n} from './rpc/describe-visible-content.js'\nimport { handleDescribeContext, type DescribeContextHost } from './rpc/describe-context.js'\n\nexport interface WsLike {\n send(data: string): void\n close(): void\n addEventListener(event: 'message', h: (e: { data: string | ArrayBuffer }) => void): void\n addEventListener(event: 'open' | 'close', h: () => void): void\n}\n\nexport type RpcHosts = GetStateHost &\n SendMessageHost &\n ListActionsHost &\n QueryDomHost &\n DescribeVisibleHost &\n DescribeContextHost\n\nexport type HelloBuilder = () => HelloFrame\n\nexport type WsClient = {\n /** Resolve a pending confirmation; emits confirm-resolved frame to the server. */\n resolveConfirm(\n confirmId: string,\n outcome: 'confirmed' | 'user-cancelled',\n stateAfter?: unknown,\n ): void\n /** Emit a state-update frame so the server can resolve waitForChange promises. */\n emitStateUpdate(path: string, stateAfter: unknown): void\n /** Emit a log-append frame so the server can mirror client-observed actions to the audit sink. */\n emitLogAppend(entry: LogEntry): void\n /** Close the socket cleanly. */\n close(): void\n}\n\nexport type WsClientOpts = {\n /** Called once when the server sends an `{t: 'active'}` frame. Idempotent. */\n onActivated?: () => void\n /**\n * Called with every LogEntry emitted by the ws-client (one per rpc\n * dispatched or errored). Used by the factory to mirror the entries\n * into the app's local `agent.log` slice so the UI can show activity.\n * The ws-client still sends the outbound `log-append` frame to the\n * server regardless.\n */\n onLogEntry?: (entry: LogEntry) => void\n}\n\n/**\n * Wires up a WebSocket to serve rpc requests from the server. See spec §9.4.\n */\nexport function attachWsClient(\n ws: WsLike,\n rpc: RpcHosts,\n hello: HelloBuilder,\n opts: WsClientOpts = {},\n): WsClient {\n let activated = false\n ws.addEventListener('open', () => {\n ws.send(JSON.stringify(hello()))\n })\n ws.addEventListener('message', async (ev) => {\n let frame: ServerFrame\n try {\n const raw = typeof ev.data === 'string' ? ev.data : new TextDecoder().decode(ev.data)\n frame = JSON.parse(raw) as ServerFrame\n } catch {\n return\n }\n if (frame.t === 'revoked') {\n ws.close()\n return\n }\n if (frame.t === 'active') {\n if (!activated) {\n activated = true\n opts.onActivated?.()\n }\n return\n }\n if (frame.t !== 'rpc') return\n let result: unknown\n let rpcErr: { code?: string; detail?: string } | null = null\n try {\n result = await dispatch(frame.tool, frame.args, rpc)\n const reply: ClientFrame = { t: 'rpc-reply', id: frame.id, result }\n ws.send(JSON.stringify(reply))\n } catch (e: unknown) {\n rpcErr = e as { code?: string; detail?: string }\n // When a plain JS exception bubbles up (TypeError, RangeError, etc.),\n // rpcErr has no .code/.detail. Enrich the detail with the actual\n // message + stack so the server/Claude can see the real cause.\n const detail =\n rpcErr.detail ??\n (e instanceof Error\n ? `${e.name}: ${e.message}${e.stack ? '\\n' + e.stack.split('\\n').slice(0, 5).join('\\n') : ''}`\n : undefined)\n const errFrame: ClientFrame = {\n t: 'rpc-error',\n id: frame.id,\n code: rpcErr.code ?? 'internal',\n detail,\n }\n ws.send(JSON.stringify(errFrame))\n // Also log to the browser console so operators see the real cause even\n // when the server/Claude just show \"internal\".\n console.error(`[llui-agent] rpc handler threw for ${frame.tool}:`, e)\n }\n const kind = getLogKindForTool(frame.tool, result, rpcErr)\n const logEntry: LogEntry = {\n id: frame.id,\n at: Date.now(),\n kind,\n variant: extractVariant(frame.tool, frame.args),\n intent: buildIntent(frame.tool, frame.args, rpc.getMsgAnnotations()),\n }\n opts.onLogEntry?.(logEntry)\n ws.send(JSON.stringify({ t: 'log-append', entry: logEntry } satisfies ClientFrame))\n })\n\n return {\n resolveConfirm(confirmId, outcome, stateAfter) {\n const frame: ClientFrame = {\n t: 'confirm-resolved',\n confirmId,\n outcome,\n stateAfter,\n }\n ws.send(JSON.stringify(frame))\n },\n emitStateUpdate(path, stateAfter) {\n const frame: ClientFrame = { t: 'state-update', path, stateAfter }\n ws.send(JSON.stringify(frame))\n },\n emitLogAppend(entry) {\n const frame: ClientFrame = { t: 'log-append', entry }\n ws.send(JSON.stringify(frame))\n },\n close() {\n ws.close()\n },\n }\n}\n\nasync function dispatch(tool: string, args: unknown, rpc: RpcHosts): Promise<unknown> {\n switch (tool) {\n case 'get_state':\n return handleGetState(rpc, (args ?? {}) as { path?: string })\n case 'list_actions':\n return handleListActions(rpc)\n case 'send_message':\n return handleSendMessage(rpc, args as never)\n case 'query_dom':\n return handleQueryDom(rpc, args as never)\n case 'describe_visible_content':\n return handleDescribeVisibleContent(rpc)\n case 'describe_context':\n return handleDescribeContext(rpc)\n default:\n throw { code: 'invalid', detail: `unknown tool: ${tool}` }\n }\n}\n\nconst READ_TOOLS = new Set([\n 'get_state',\n 'list_actions',\n 'describe_context',\n 'query_dom',\n 'describe_visible_content',\n])\n\nfunction getLogKindForTool(\n tool: string,\n result: unknown,\n err: { code?: string; detail?: string } | null,\n): LogKind {\n if (err !== null) return 'error'\n if (tool === 'send_message') {\n const r = result as { status?: string } | null\n const status = r?.status\n if (status === 'dispatched' || status === 'confirmed') return 'dispatched'\n if (status === 'pending-confirmation') return 'proposed'\n if (status === 'rejected') return 'blocked'\n return 'dispatched'\n }\n if (READ_TOOLS.has(tool)) return 'read'\n return 'read'\n}\n\nfunction extractVariant(tool: string, args: unknown): string | undefined {\n if (tool === 'send_message') {\n const a = args as { msg?: { type?: string } } | null\n const t = a?.msg?.type\n return typeof t === 'string' ? t : undefined\n }\n return undefined\n}\n\n// Human-readable label for each rpc. For send_message, prefer the @intent\n// annotation authored on the Msg union; fall back to the raw variant name.\n// For read tools, return a short fixed label so the activity feed doesn't\n// show opaque tool ids like \"describe_visible_content\".\nfunction buildIntent(\n tool: string,\n args: unknown,\n annotations: Record<string, MessageAnnotations> | null,\n): string {\n if (tool === 'send_message') {\n const a = args as { msg?: { type?: string } } | null\n const variant = typeof a?.msg?.type === 'string' ? a.msg.type : undefined\n const annotated = variant ? annotations?.[variant]?.intent : null\n if (annotated) return annotated\n return variant ?? 'Send message'\n }\n if (tool === 'get_state') return 'Read app state'\n if (tool === 'list_actions') return 'List available actions'\n if (tool === 'describe_context') return 'Read current context'\n if (tool === 'describe_visible_content') return 'Read visible content'\n if (tool === 'query_dom') {\n const a = args as { name?: string } | null\n return a?.name ? `Query DOM: ${a.name}` : 'Query DOM'\n }\n return tool\n}\n"]}
1
+ {"version":3,"file":"ws-client.js","sourceRoot":"","sources":["../../src/client/ws-client.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n ClientFrame,\n ServerFrame,\n HelloFrame,\n LogEntry,\n LogKind,\n MessageAnnotations,\n} from '../protocol.js'\nimport { handleGetState, type GetStateHost } from './rpc/get-state.js'\nimport { handleSendMessage, type SendMessageHost } from './rpc/send-message.js'\nimport { handleListActions, type ListActionsHost } from './rpc/list-actions.js'\nimport { handleQueryDom, type QueryDomHost } from './rpc/query-dom.js'\nimport {\n handleDescribeVisibleContent,\n type DescribeVisibleHost,\n} from './rpc/describe-visible-content.js'\nimport { handleDescribeContext, type DescribeContextHost } from './rpc/describe-context.js'\nimport { handleObserve, type ObserveHost } from './rpc/observe.js'\n\nexport interface WsLike {\n send(data: string): void\n close(): void\n addEventListener(event: 'message', h: (e: { data: string | ArrayBuffer }) => void): void\n addEventListener(event: 'open' | 'close', h: () => void): void\n}\n\nexport type RpcHosts = GetStateHost &\n SendMessageHost &\n ListActionsHost &\n QueryDomHost &\n DescribeVisibleHost &\n DescribeContextHost &\n ObserveHost\n\nexport type HelloBuilder = () => HelloFrame\n\nexport type WsClient = {\n /** Resolve a pending confirmation; emits confirm-resolved frame to the server. */\n resolveConfirm(\n confirmId: string,\n outcome: 'confirmed' | 'user-cancelled',\n stateAfter?: unknown,\n ): void\n /** Emit a state-update frame so the server can resolve waitForChange promises. */\n emitStateUpdate(path: string, stateAfter: unknown): void\n /** Emit a log-append frame so the server can mirror client-observed actions to the audit sink. */\n emitLogAppend(entry: LogEntry): void\n /** Close the socket cleanly. */\n close(): void\n}\n\nexport type WsClientOpts = {\n /** Called once when the server sends an `{t: 'active'}` frame. Idempotent. */\n onActivated?: () => void\n /**\n * Called with every LogEntry emitted by the ws-client (one per rpc\n * dispatched or errored). Used by the factory to mirror the entries\n * into the app's local `agent.log` slice so the UI can show activity.\n * The ws-client still sends the outbound `log-append` frame to the\n * server regardless.\n */\n onLogEntry?: (entry: LogEntry) => void\n}\n\n/**\n * Wires up a WebSocket to serve rpc requests from the server. See spec §9.4.\n */\nexport function attachWsClient(\n ws: WsLike,\n rpc: RpcHosts,\n hello: HelloBuilder,\n opts: WsClientOpts = {},\n): WsClient {\n let activated = false\n ws.addEventListener('open', () => {\n ws.send(JSON.stringify(hello()))\n })\n ws.addEventListener('message', async (ev) => {\n let frame: ServerFrame\n try {\n const raw = typeof ev.data === 'string' ? ev.data : new TextDecoder().decode(ev.data)\n frame = JSON.parse(raw) as ServerFrame\n } catch {\n return\n }\n if (frame.t === 'revoked') {\n ws.close()\n return\n }\n if (frame.t === 'active') {\n if (!activated) {\n activated = true\n opts.onActivated?.()\n }\n return\n }\n if (frame.t !== 'rpc') return\n let result: unknown\n let rpcErr: { code?: string; detail?: string } | null = null\n try {\n result = await dispatch(frame.tool, frame.args, rpc)\n const reply: ClientFrame = { t: 'rpc-reply', id: frame.id, result }\n ws.send(JSON.stringify(reply))\n } catch (e: unknown) {\n rpcErr = e as { code?: string; detail?: string }\n // When a plain JS exception bubbles up (TypeError, RangeError, etc.),\n // rpcErr has no .code/.detail. Enrich the detail with the actual\n // message + stack so the server/Claude can see the real cause.\n const detail =\n rpcErr.detail ??\n (e instanceof Error\n ? `${e.name}: ${e.message}${e.stack ? '\\n' + e.stack.split('\\n').slice(0, 5).join('\\n') : ''}`\n : undefined)\n const errFrame: ClientFrame = {\n t: 'rpc-error',\n id: frame.id,\n code: rpcErr.code ?? 'internal',\n detail,\n }\n ws.send(JSON.stringify(errFrame))\n // Also log to the browser console so operators see the real cause even\n // when the server/Claude just show \"internal\".\n console.error(`[llui-agent] rpc handler threw for ${frame.tool}:`, e)\n }\n const kind = getLogKindForTool(frame.tool, result, rpcErr)\n const logEntry: LogEntry = {\n id: frame.id,\n at: Date.now(),\n kind,\n variant: extractVariant(frame.tool, frame.args),\n intent: buildIntent(frame.tool, frame.args, rpc.getMsgAnnotations()),\n }\n opts.onLogEntry?.(logEntry)\n ws.send(JSON.stringify({ t: 'log-append', entry: logEntry } satisfies ClientFrame))\n })\n\n return {\n resolveConfirm(confirmId, outcome, stateAfter) {\n const frame: ClientFrame = {\n t: 'confirm-resolved',\n confirmId,\n outcome,\n stateAfter,\n }\n ws.send(JSON.stringify(frame))\n },\n emitStateUpdate(path, stateAfter) {\n const frame: ClientFrame = { t: 'state-update', path, stateAfter }\n ws.send(JSON.stringify(frame))\n },\n emitLogAppend(entry) {\n const frame: ClientFrame = { t: 'log-append', entry }\n ws.send(JSON.stringify(frame))\n },\n close() {\n ws.close()\n },\n }\n}\n\nasync function dispatch(tool: string, args: unknown, rpc: RpcHosts): Promise<unknown> {\n switch (tool) {\n case 'get_state':\n return handleGetState(rpc, (args ?? {}) as { path?: string })\n case 'list_actions':\n return handleListActions(rpc)\n case 'send_message':\n return handleSendMessage(rpc, args as never)\n case 'query_dom':\n return handleQueryDom(rpc, args as never)\n case 'describe_visible_content':\n return handleDescribeVisibleContent(rpc)\n case 'describe_context':\n return handleDescribeContext(rpc)\n case 'observe':\n return handleObserve(rpc)\n default:\n throw { code: 'invalid', detail: `unknown tool: ${tool}` }\n }\n}\n\nconst READ_TOOLS = new Set([\n 'get_state',\n 'list_actions',\n 'describe_context',\n 'query_dom',\n 'describe_visible_content',\n 'observe',\n])\n\nfunction getLogKindForTool(\n tool: string,\n result: unknown,\n err: { code?: string; detail?: string } | null,\n): LogKind {\n if (err !== null) return 'error'\n if (tool === 'send_message') {\n const r = result as { status?: string } | null\n const status = r?.status\n if (status === 'dispatched' || status === 'confirmed') return 'dispatched'\n if (status === 'pending-confirmation') return 'proposed'\n if (status === 'rejected') return 'blocked'\n return 'dispatched'\n }\n if (READ_TOOLS.has(tool)) return 'read'\n return 'read'\n}\n\nfunction extractVariant(tool: string, args: unknown): string | undefined {\n if (tool === 'send_message') {\n const a = args as { msg?: { type?: string } } | null\n const t = a?.msg?.type\n return typeof t === 'string' ? t : undefined\n }\n return undefined\n}\n\n// Human-readable label for each rpc. For send_message, prefer the @intent\n// annotation authored on the Msg union; fall back to the raw variant name.\n// For read tools, return a short fixed label so the activity feed doesn't\n// show opaque tool ids like \"describe_visible_content\".\nfunction buildIntent(\n tool: string,\n args: unknown,\n annotations: Record<string, MessageAnnotations> | null,\n): string {\n if (tool === 'send_message') {\n const a = args as { msg?: { type?: string } } | null\n const variant = typeof a?.msg?.type === 'string' ? a.msg.type : undefined\n const annotated = variant ? annotations?.[variant]?.intent : null\n if (annotated) return annotated\n return variant ?? 'Send message'\n }\n if (tool === 'get_state') return 'Read app state'\n if (tool === 'list_actions') return 'List available actions'\n if (tool === 'describe_context') return 'Read current context'\n if (tool === 'describe_visible_content') return 'Read visible content'\n if (tool === 'query_dom') {\n const a = args as { name?: string } | null\n return a?.name ? `Query DOM: ${a.name}` : 'Query DOM'\n }\n return tool\n}\n"]}
@@ -51,17 +51,70 @@ export type LapMessageRequest = {
51
51
  [k: string]: unknown;
52
52
  };
53
53
  reason?: string;
54
- waitFor?: 'idle' | 'none';
54
+ /**
55
+ * Backpressure contract for how long `/message` waits before returning:
56
+ * - `drained` (default): dispatch, then loop until the message queue is
57
+ * idle for `drainQuietMs` ms or the 5s hard cap trips. Captures any
58
+ * effect round-trips (http/delay/debounce) that feed back as messages.
59
+ * - `idle`: dispatch + flush + one microtask yield. Captures the
60
+ * synchronous update cycle but not async effects.
61
+ * - `none`: dispatch and return without flushing. For high-throughput
62
+ * fire-and-forget dispatch.
63
+ */
64
+ waitFor?: 'drained' | 'idle' | 'none';
65
+ /**
66
+ * Quiescence window when `waitFor === 'drained'`. Drain completes when
67
+ * no new update cycle fires for this many ms. Default 100ms — long
68
+ * enough for a localhost HTTP round-trip, short enough to be
69
+ * imperceptible. Ignored for `idle` / `none`.
70
+ */
71
+ drainQuietMs?: number;
72
+ /**
73
+ * Hard cap on total wait time. When `waitFor === 'drained'`, this is
74
+ * the upper bound on how long the drain loop can run; if reached, the
75
+ * response carries `drain.timedOut: true` with partial results. For
76
+ * `pending-confirmation` messages, this is how long to wait for
77
+ * the user's confirm/reject. Default 5_000ms.
78
+ */
55
79
  timeoutMs?: number;
56
80
  };
57
81
  export type LapMessageRejectReason = 'humanOnly' | 'user-cancelled' | 'timeout' | 'invalid' | 'schema-error' | 'revoked' | 'paused';
82
+ /**
83
+ * Drain metadata attached to `dispatched` / `confirmed` responses.
84
+ * `effectsObserved` counts update-cycle commits (not individual effects) —
85
+ * it's a proxy for "how much activity happened during the drain window."
86
+ * `errors` surfaces sync throws from `onEffect` and unhandled rejections
87
+ * from effect handlers that fired during the drain window, so the LLM
88
+ * can see when an HTTP handler crashed silently.
89
+ */
90
+ export type LapDrainMeta = {
91
+ effectsObserved: number;
92
+ durationMs: number;
93
+ timedOut: boolean;
94
+ errors: Array<{
95
+ kind: 'error' | 'unhandledrejection';
96
+ message: string;
97
+ stack?: string;
98
+ }>;
99
+ };
58
100
  export type LapMessageResponse = {
59
101
  status: 'dispatched';
60
102
  stateAfter: unknown;
103
+ actions: LapActionsResponse['actions'];
104
+ drain: LapDrainMeta;
61
105
  } | {
62
106
  status: 'pending-confirmation';
63
107
  confirmId: string;
64
108
  } | {
109
+ /**
110
+ * The user approved a `pending-confirmation` message. `stateAfter`
111
+ * is the state snapshot captured when the approve was resolved;
112
+ * effects produced by the approved dispatch may still be in
113
+ * flight. The LLM should follow up with an `observe` call to
114
+ * pick up a drained view and fresh actions — by design the
115
+ * confirm path doesn't carry drain semantics because approval
116
+ * can arrive arbitrarily later than the original request.
117
+ */
65
118
  status: 'confirmed';
66
119
  stateAfter: unknown;
67
120
  } | {
@@ -149,6 +202,12 @@ export type AgentContext = {
149
202
  export type LapContextResponse = {
150
203
  context: AgentContext;
151
204
  };
205
+ export type LapObserveResponse = {
206
+ state: unknown;
207
+ actions: LapActionsResponse['actions'];
208
+ description: LapDescribeResponse;
209
+ context: AgentContext | null;
210
+ };
152
211
  export type LapEndpointMap = {
153
212
  '/lap/v1/describe': {
154
213
  req: null;
@@ -186,6 +245,10 @@ export type LapEndpointMap = {
186
245
  req: null;
187
246
  res: LapContextResponse;
188
247
  };
248
+ '/lap/v1/observe': {
249
+ req: null;
250
+ res: LapObserveResponse;
251
+ };
189
252
  };
190
253
  export type LapPath = keyof LapEndpointMap;
191
254
  export type LapRequest<P extends LapPath> = LapEndpointMap[P]['req'];