@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.
- package/dist/client/factory.d.ts.map +1 -1
- package/dist/client/factory.js +59 -0
- package/dist/client/factory.js.map +1 -1
- package/dist/client/rpc/observe.d.ts +20 -0
- package/dist/client/rpc/observe.d.ts.map +1 -0
- package/dist/client/rpc/observe.js +9 -0
- package/dist/client/rpc/observe.js.map +1 -0
- package/dist/client/rpc/send-message.d.ts +32 -3
- package/dist/client/rpc/send-message.d.ts.map +1 -1
- package/dist/client/rpc/send-message.js +94 -4
- package/dist/client/rpc/send-message.js.map +1 -1
- package/dist/client/ws-client.d.ts +2 -1
- package/dist/client/ws-client.d.ts.map +1 -1
- package/dist/client/ws-client.js +4 -0
- package/dist/client/ws-client.js.map +1 -1
- package/dist/protocol.d.ts +64 -1
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js.map +1 -1
- package/dist/server/cloudflare/durable-object.d.ts +58 -0
- package/dist/server/cloudflare/durable-object.d.ts.map +1 -0
- package/dist/server/cloudflare/durable-object.js +33 -0
- package/dist/server/cloudflare/durable-object.js.map +1 -0
- package/dist/server/cloudflare/index.d.ts +49 -0
- package/dist/server/cloudflare/index.d.ts.map +1 -0
- package/dist/server/cloudflare/index.js +49 -0
- package/dist/server/cloudflare/index.js.map +1 -0
- package/dist/server/cloudflare/worker.d.ts +40 -0
- package/dist/server/cloudflare/worker.d.ts.map +1 -0
- package/dist/server/cloudflare/worker.js +55 -0
- package/dist/server/cloudflare/worker.js.map +1 -0
- package/dist/server/core-entry.d.ts +27 -0
- package/dist/server/core-entry.d.ts.map +1 -0
- package/dist/server/core-entry.js +19 -0
- package/dist/server/core-entry.js.map +1 -0
- package/dist/server/core.d.ts +78 -0
- package/dist/server/core.d.ts.map +1 -0
- package/dist/server/core.js +84 -0
- package/dist/server/core.js.map +1 -0
- package/dist/server/factory.d.ts +5 -3
- package/dist/server/factory.d.ts.map +1 -1
- package/dist/server/factory.js +18 -58
- package/dist/server/factory.js.map +1 -1
- package/dist/server/http/mint.d.ts.map +1 -1
- package/dist/server/http/mint.js +2 -3
- package/dist/server/http/mint.js.map +1 -1
- package/dist/server/http/resume.js +1 -1
- package/dist/server/http/resume.js.map +1 -1
- package/dist/server/identity.d.ts +5 -1
- package/dist/server/identity.d.ts.map +1 -1
- package/dist/server/identity.js +49 -11
- package/dist/server/identity.js.map +1 -1
- package/dist/server/index.d.ts +16 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +13 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/lap/confirm-result.d.ts +2 -2
- 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 +4 -4
- package/dist/server/lap/describe.d.ts.map +1 -1
- package/dist/server/lap/describe.js +4 -4
- package/dist/server/lap/describe.js.map +1 -1
- package/dist/server/lap/forward.d.ts +2 -2
- package/dist/server/lap/forward.d.ts.map +1 -1
- package/dist/server/lap/forward.js +1 -1
- package/dist/server/lap/forward.js.map +1 -1
- package/dist/server/lap/message.d.ts +2 -2
- package/dist/server/lap/message.d.ts.map +1 -1
- package/dist/server/lap/message.js +7 -3
- package/dist/server/lap/message.js.map +1 -1
- package/dist/server/lap/observe.d.ts +27 -0
- package/dist/server/lap/observe.d.ts.map +1 -0
- package/dist/server/lap/observe.js +77 -0
- package/dist/server/lap/observe.js.map +1 -0
- package/dist/server/lap/router.d.ts.map +1 -1
- package/dist/server/lap/router.js +3 -0
- package/dist/server/lap/router.js.map +1 -1
- package/dist/server/lap/wait.d.ts +2 -2
- 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 +25 -1
- package/dist/server/options.d.ts.map +1 -1
- package/dist/server/options.js.map +1 -1
- package/dist/server/token.d.ts +7 -3
- package/dist/server/token.d.ts.map +1 -1
- package/dist/server/token.js +66 -26
- package/dist/server/token.js.map +1 -1
- package/dist/server/web/adapter.d.ts +16 -0
- package/dist/server/web/adapter.d.ts.map +1 -0
- package/dist/server/web/adapter.js +45 -0
- package/dist/server/web/adapter.js.map +1 -0
- package/dist/server/web/index.d.ts +12 -0
- package/dist/server/web/index.d.ts.map +1 -0
- package/dist/server/web/index.js +12 -0
- package/dist/server/web/index.js.map +1 -0
- package/dist/server/web/upgrade.d.ts +41 -0
- package/dist/server/web/upgrade.d.ts.map +1 -0
- package/dist/server/web/upgrade.js +96 -0
- package/dist/server/web/upgrade.js.map +1 -0
- package/dist/server/ws/pairing-registry.d.ts +84 -21
- package/dist/server/ws/pairing-registry.d.ts.map +1 -1
- package/dist/server/ws/pairing-registry.js +89 -151
- package/dist/server/ws/pairing-registry.js.map +1 -1
- package/dist/server/ws/rpc.d.ts +39 -0
- package/dist/server/ws/rpc.d.ts.map +1 -0
- package/dist/server/ws/rpc.js +126 -0
- package/dist/server/ws/rpc.js.map +1 -0
- package/dist/server/ws/upgrade.d.ts +3 -3
- package/dist/server/ws/upgrade.d.ts.map +1 -1
- package/dist/server/ws/upgrade.js +2 -2
- package/dist/server/ws/upgrade.js.map +1 -1
- 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,
|
|
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"}
|
package/dist/client/factory.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
33
|
-
|
|
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
|
-
|
|
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;
|
|
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"}
|
package/dist/client/ws-client.js
CHANGED
|
@@ -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"]}
|
package/dist/protocol.d.ts
CHANGED
|
@@ -51,17 +51,70 @@ export type LapMessageRequest = {
|
|
|
51
51
|
[k: string]: unknown;
|
|
52
52
|
};
|
|
53
53
|
reason?: string;
|
|
54
|
-
|
|
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'];
|