oui-spec 0.4.1 → 0.6.0

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 (48) hide show
  1. package/README.md +41 -8
  2. package/dist/{chunk-DCVVTP3V.cjs → chunk-5UUJ3F7C.cjs} +53 -16
  3. package/dist/chunk-5UUJ3F7C.cjs.map +1 -0
  4. package/dist/{chunk-GDB56ZQZ.cjs → chunk-AFNWLRKV.cjs} +126 -15
  5. package/dist/chunk-AFNWLRKV.cjs.map +1 -0
  6. package/dist/chunk-B2EFZMGY.js +159 -0
  7. package/dist/chunk-B2EFZMGY.js.map +1 -0
  8. package/dist/chunk-LDECTDPU.cjs +169 -0
  9. package/dist/chunk-LDECTDPU.cjs.map +1 -0
  10. package/dist/{chunk-F3WKIRAU.js → chunk-R3SGP2UY.js} +53 -16
  11. package/dist/chunk-R3SGP2UY.js.map +1 -0
  12. package/dist/{chunk-UQLG55ST.js → chunk-VQETSIY5.js} +124 -13
  13. package/dist/chunk-VQETSIY5.js.map +1 -0
  14. package/dist/core/index.cjs +7 -7
  15. package/dist/core/index.d.cts +5 -5
  16. package/dist/core/index.d.ts +5 -5
  17. package/dist/core/index.js +3 -3
  18. package/dist/index.cjs +40 -8
  19. package/dist/index.d.cts +4 -3
  20. package/dist/index.d.ts +4 -3
  21. package/dist/index.js +3 -3
  22. package/dist/react/index.d.cts +3 -3
  23. package/dist/react/index.d.ts +3 -3
  24. package/dist/spec/index.cjs +34 -2
  25. package/dist/spec/index.d.cts +22 -272
  26. package/dist/spec/index.d.ts +22 -272
  27. package/dist/spec/index.js +1 -1
  28. package/dist/{surface-runtime-BzIKgL_I.d.cts → surface-runtime-CSZ9UtBO.d.cts} +23 -2
  29. package/dist/{surface-runtime-DeByfbKg.d.ts → surface-runtime-D5nINY4b.d.ts} +23 -2
  30. package/dist/transport/index.cjs +2 -2
  31. package/dist/transport/index.d.cts +3 -3
  32. package/dist/transport/index.d.ts +3 -3
  33. package/dist/transport/index.js +1 -1
  34. package/dist/types-CmJb1BQ8.d.cts +403 -0
  35. package/dist/types-CmJb1BQ8.d.ts +403 -0
  36. package/dist/{websocket-YfWILdZ4.d.cts → websocket-Ce3tDSsJ.d.ts} +26 -7
  37. package/dist/{websocket-c5tIUWY5.d.ts → websocket-Ds2Rh_jL.d.cts} +26 -7
  38. package/package.json +4 -3
  39. package/spec/OUI-SPEC-v0.1.md +63 -7
  40. package/spec/approval-vectors.json +88 -0
  41. package/dist/chunk-BMCL2K74.js +0 -12
  42. package/dist/chunk-BMCL2K74.js.map +0 -1
  43. package/dist/chunk-DCVVTP3V.cjs.map +0 -1
  44. package/dist/chunk-F3WKIRAU.js.map +0 -1
  45. package/dist/chunk-GDB56ZQZ.cjs.map +0 -1
  46. package/dist/chunk-UQLG55ST.js.map +0 -1
  47. package/dist/chunk-ZKM4XWAB.cjs +0 -14
  48. package/dist/chunk-ZKM4XWAB.cjs.map +0 -1
package/README.md CHANGED
@@ -53,17 +53,26 @@ flowchart TD
53
53
  Every action request carries a `requestId`, and the client answers it exactly
54
54
  once with an `OUIActionResult` (spec §7.3):
55
55
 
56
- | Event | Direction | Carries |
57
- | -------------------- | ---------------- | ---------------------------------------------------------------------------------------------------- |
58
- | `{ns}:dispatch` | runtime → client | `OUIActionRequest` — `requestId`, `surfaceId`, `actionId`, `params` |
59
- | `{ns}:action:result` | client → runtime | `OUIActionResult` — success or error, and the client's surfaces and observations once the UI settled |
60
- | `{ns}:observation` | client → runtime | state updates as they happen |
56
+ | Event | Direction | Carries |
57
+ | -------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
58
+ | `{ns}:dispatch` | runtime → client | `OUIActionRequest` — `requestId`, `surfaceId`, `actionId`, `params` |
59
+ | `{ns}:action:result` | client → runtime | `OUIActionResult` — success or error, the client's observations once the UI settled, its surfaces' hash, and its surfaces when they changed |
60
+ | `{ns}:observation` | client → runtime | state updates as they happen |
61
61
 
62
62
  The result includes what the client can do **after** the action. An action can
63
63
  change that: navigating unmounts one page's surfaces and mounts another's. An
64
64
  agent that only knew the capability set from before the action would be acting
65
65
  on the page it just left.
66
66
 
67
+ Surfaces are heavy (every action's schema), so a result repeats them only when
68
+ the agent runtime does not hold them: the runtime sends the `surfacesHash` it
69
+ holds as the request's `knownSurfaces`, and the result carries `surfaces` only
70
+ when the client's hash differs. A result the receiver refuses (too large,
71
+ invalid) is acknowledged as refused, and the client sends it again trimmed,
72
+ with `delivery` saying what was left out and why: the agent always learns the
73
+ action's outcome. A request is acknowledged on receipt when the sender asks,
74
+ and a repeat of a request whose answer is on its way gets no second copy.
75
+
67
76
  An async action (`async: true`) is acknowledged at once with `interim: true`,
68
77
  and answered a second time, under the same `requestId`, when its polling
69
78
  finishes. Progress in between is published as observations.
@@ -164,8 +173,8 @@ function CounterPage() {
164
173
  }
165
174
  ```
166
175
 
167
- `runtime.snapshot()` returns `{ surfaces, observations }` for everything
168
- mounted. An agent runtime that keeps no registry of its own can take it with
176
+ `runtime.snapshot()` returns `{ surfaces, observations, surfacesHash }` for
177
+ everything mounted. An agent runtime that keeps no registry of its own can take it with
169
178
  each request (for example, with each chat turn); set `announce: false` and the
170
179
  runtime sends nothing but results over the socket.
171
180
 
@@ -247,7 +256,7 @@ sequenceDiagram
247
256
  WS->>Browser: oui:dispatch
248
257
  Note over Browser: Handler runs once (de-duplicated by requestId)
249
258
  Note over Browser: Waits until the UI settles
250
- Browser->>WS: oui:action:result { requestId, success, data, surfaces, observations }
259
+ Browser->>WS: oui:action:result { requestId, success, data, observations, surfacesHash, surfaces if changed }
251
260
  WS->>Agent: result for requestId
252
261
  ```
253
262
 
@@ -265,6 +274,28 @@ here is its id. The agent is not held until a two-minute render finishes. The
265
274
  client polls or subscribes, publishes progress as observations, and sends the
266
275
  request's final result when the job ends.
267
276
 
277
+ ### Approvals
278
+
279
+ Some actions can't be undone: an order placed, a payment made, a message sent, something published, or something the person made deleted. An action declares this with `effect: 'transaction'`, or with `confirm: true` for a destructive write. It then runs only on the user's approval of the exact request.
280
+
281
+ 1. The agent runtime stops at the action and shows the user a confirmation built from the action's `title`, `description` and input schema.
282
+ 2. The user confirms. The approval store answers this tab, and only this tab, with a grant. The tab records it:
283
+
284
+ ```ts
285
+ runtime.grantApproval({ approvalId, argsHash, expiresAt });
286
+ ```
287
+
288
+ 3. The request that runs the action carries `approval: { approvalId, argsHash }`. The runtime runs it only when all of these hold:
289
+ - the grant exists in this tab;
290
+ - the grant is unused and unexpired;
291
+ - `argsHash(params)` equals the hash the user approved.
292
+
293
+ Otherwise it answers `{ success: false, error: { code: 'APPROVAL_REQUIRED' } }` and never calls the handler. That applies even during a turn the `accept` gate admits.
294
+
295
+ 4. A grant admits one request.
296
+
297
+ `argsHash` is SHA-256 over the RFC 8785 (JCS) canonical JSON of the params, as lowercase hex. Every implementation (the agent runtime, the approval store, this runtime, and engines in other languages) must reproduce [`spec/approval-vectors.json`](./spec/approval-vectors.json), which is also published as `oui-spec/approval-vectors.json`.
298
+
268
299
  ---
269
300
 
270
301
  ## Polling & Async Operations
@@ -703,6 +734,8 @@ They even share JSON Schema for parameter validation — a deliberate design cho
703
734
  - [x] `oui-spec/transport` — WebSocket + Direct transports
704
735
  - [x] `createOUI()` — gated instantiation; integration requirements enforced at compile time
705
736
  - [x] `createSurfaceRuntime()` — client-side record of mounted surfaces; every request answered with a result and the snapshot after it
737
+ - [x] Approvals — `transaction` and destructive actions run only on a grant from the user's own confirmation, bound to the exact params (0.5.0)
738
+ - [x] Results carry surfaces only when they changed (`knownSurfaces` / `surfacesHash`), and a refused result is sent again trimmed, saying why (0.6.0)
706
739
  - [ ] `oui-spec/devtools` — Surface inspector / debugger
707
740
  - [ ] `oui-spec/vue` — Vue bindings
708
741
  - [ ] `oui-spec/validator` — Runtime schema validation
@@ -24,7 +24,8 @@ function createWebSocketTransport(socket, config) {
24
24
  if (shouldBuffer) {
25
25
  while (buffer.length > 0) {
26
26
  const msg = buffer.shift();
27
- socket.emit(msg.event, msg.data);
27
+ if (msg.ack) socket.emit(msg.event, msg.data, msg.ack);
28
+ else socket.emit(msg.event, msg.data);
28
29
  }
29
30
  }
30
31
  connectionHandlers.forEach((h) => h(true));
@@ -33,11 +34,12 @@ function createWebSocketTransport(socket, config) {
33
34
  connected = false;
34
35
  connectionHandlers.forEach((h) => h(false));
35
36
  });
36
- function emit(event, data) {
37
+ function emit(event, data, ack) {
37
38
  if (connected) {
38
- socket.emit(event, data);
39
+ if (ack) socket.emit(event, data, ack);
40
+ else socket.emit(event, data);
39
41
  } else if (shouldBuffer && buffer.length < maxBuffer) {
40
- buffer.push({ event, data });
42
+ buffer.push({ event, data, ...ack ? { ack } : {} });
41
43
  }
42
44
  }
43
45
  return {
@@ -48,27 +50,50 @@ function createWebSocketTransport(socket, config) {
48
50
  onAction(handler) {
49
51
  return listen(
50
52
  `${ns}:dispatch`,
51
- (data) => {
53
+ (data, ack) => {
54
+ const receipt = typeof ack === "function" ? (a) => ack(a) : void 0;
52
55
  if (!data || typeof data.requestId !== "string" || !data.requestId || typeof data.surfaceId !== "string" || typeof data.actionId !== "string") {
53
56
  console.warn(
54
57
  "[OUI] Dropped a dispatch without requestId/surfaceId/actionId",
55
58
  data
56
59
  );
60
+ receipt?.({
61
+ ok: false,
62
+ reason: "a request needs requestId, surfaceId and actionId"
63
+ });
57
64
  return;
58
65
  }
59
- handler({
60
- requestId: data.requestId,
61
- surfaceId: data.surfaceId,
62
- actionId: data.actionId,
63
- params: data.params ?? {},
64
- timestamp: data.timestamp ?? Date.now()
65
- });
66
+ handler(
67
+ {
68
+ requestId: data.requestId,
69
+ surfaceId: data.surfaceId,
70
+ actionId: data.actionId,
71
+ params: data.params ?? {},
72
+ timestamp: data.timestamp ?? Date.now(),
73
+ ...isApproval(data.approval) ? {
74
+ approval: {
75
+ approvalId: data.approval.approvalId,
76
+ argsHash: data.approval.argsHash
77
+ }
78
+ } : {},
79
+ // The surfaces the agent runtime holds, so the answer need not repeat them (§7.3.4).
80
+ ...typeof data.knownSurfaces === "string" && data.knownSurfaces ? { knownSurfaces: data.knownSurfaces } : {}
81
+ },
82
+ receipt
83
+ );
66
84
  }
67
85
  );
68
86
  },
69
87
  // ─── Result ─────────────────────────────────────────────────
70
- sendResult(result) {
71
- emit(`${ns}:action:result`, result);
88
+ sendResult(result, onAcknowledged) {
89
+ emit(
90
+ `${ns}:action:result`,
91
+ result,
92
+ onAcknowledged ? (response) => {
93
+ const ack = acknowledgmentOf(response);
94
+ if (ack) onAcknowledged(ack);
95
+ } : void 0
96
+ );
72
97
  },
73
98
  onResult(handler) {
74
99
  return listen(
@@ -149,7 +174,19 @@ function createWebSocketTransport(socket, config) {
149
174
  }
150
175
  };
151
176
  }
177
+ function isApproval(value) {
178
+ const a = value;
179
+ return !!a && typeof a.approvalId === "string" && !!a.approvalId && typeof a.argsHash === "string";
180
+ }
181
+ function acknowledgmentOf(response) {
182
+ const r = response;
183
+ if (!r || typeof r !== "object") return null;
184
+ if (r.ok === true) return { ok: true };
185
+ if (r.ok !== false) return null;
186
+ const why = typeof r.error === "string" ? r.error : typeof r.reason === "string" ? r.reason : "";
187
+ return { ok: false, reason: why || "refused" };
188
+ }
152
189
 
153
190
  exports.createWebSocketTransport = createWebSocketTransport;
154
- //# sourceMappingURL=chunk-DCVVTP3V.cjs.map
155
- //# sourceMappingURL=chunk-DCVVTP3V.cjs.map
191
+ //# sourceMappingURL=chunk-5UUJ3F7C.cjs.map
192
+ //# sourceMappingURL=chunk-5UUJ3F7C.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/transport/websocket.ts"],"names":[],"mappings":"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","file":"chunk-5UUJ3F7C.cjs","sourcesContent":["import type {\n OUIAcknowledgment,\n OUITransport,\n OUITransportConfig,\n OUIActionHandler,\n OUIObservationHandler,\n OUIResultHandler,\n} from \"./types.js\";\nimport type {\n OUISurface,\n OUIObservationUpdate,\n OUIActionRequest,\n OUIActionResult,\n OUIActionApproval,\n} from \"../spec/index.js\";\n\n/**\n * WebSocket transport using Socket.IO.\n * Maps OUI protocol events to socket events with a configurable namespace prefix.\n *\n * Events:\n * {ns}:dispatch — action request (runtime → client)\n * {ns}:action:result — action result (client → runtime)\n * {ns}:observation — observation (client → runtime)\n * {ns}:surface:register — registration (client → runtime)\n * {ns}:surface:deregister — deregistration (client → runtime)\n */\nexport function createWebSocketTransport(\n socket: SocketLike,\n config?: OUITransportConfig,\n): OUITransport {\n const ns = config?.namespace ?? \"oui\";\n const buffer: Array<{ event: string; data: unknown; ack?: Ack }> = [];\n const maxBuffer = config?.maxBufferSize ?? 100;\n const shouldBuffer = config?.bufferWhileDisconnected ?? true;\n\n let connected = socket.connected ?? false;\n const connectionHandlers: Array<(c: boolean) => void> = [];\n\n // Every listener this transport attaches, so dispose() can remove exactly\n // these and nothing the integrator attached to the same socket.\n const attached: Array<{ event: string; handler: (...args: any[]) => void }> =\n [];\n function listen(\n event: string,\n handler: (...args: any[]) => void,\n ): () => void {\n socket.on(event, handler);\n const entry = { event, handler };\n attached.push(entry);\n return () => {\n socket.off(event, handler);\n const i = attached.indexOf(entry);\n if (i >= 0) attached.splice(i, 1);\n };\n }\n\n listen(\"connect\", () => {\n connected = true;\n // Flush before announcing: anything queued while offline was sent earlier\n // than whatever a connection handler is about to send.\n if (shouldBuffer) {\n while (buffer.length > 0) {\n const msg = buffer.shift()!;\n if (msg.ack) socket.emit(msg.event, msg.data, msg.ack);\n else socket.emit(msg.event, msg.data);\n }\n }\n connectionHandlers.forEach((h) => h(true));\n });\n\n listen(\"disconnect\", () => {\n connected = false;\n connectionHandlers.forEach((h) => h(false));\n });\n\n function emit(event: string, data: unknown, ack?: Ack) {\n if (connected) {\n if (ack) socket.emit(event, data, ack);\n else socket.emit(event, data);\n } else if (shouldBuffer && buffer.length < maxBuffer) {\n buffer.push({ event, data, ...(ack ? { ack } : {}) });\n }\n }\n\n return {\n // ─── Dispatch ───────────────────────────────────────────────\n dispatch(request: OUIActionRequest) {\n emit(`${ns}:dispatch`, request);\n },\n\n onAction(handler: OUIActionHandler) {\n return listen(\n `${ns}:dispatch`,\n (data: Partial<OUIActionRequest> | undefined, ack?: unknown) => {\n // The sender's receipt, when it asked for one (§7.3.7).\n const receipt =\n typeof ack === \"function\"\n ? (a: OUIAcknowledgment) => (ack as (r: unknown) => void)(a)\n : undefined;\n // A request without a requestId cannot be answered, and answering is\n // the contract. Refuse it loudly rather than run an action whose\n // result has nowhere to go.\n if (\n !data ||\n typeof data.requestId !== \"string\" ||\n !data.requestId ||\n typeof data.surfaceId !== \"string\" ||\n typeof data.actionId !== \"string\"\n ) {\n console.warn(\n \"[OUI] Dropped a dispatch without requestId/surfaceId/actionId\",\n data,\n );\n receipt?.({\n ok: false,\n reason: \"a request needs requestId, surfaceId and actionId\",\n });\n return;\n }\n handler(\n {\n requestId: data.requestId,\n surfaceId: data.surfaceId,\n actionId: data.actionId,\n params: data.params ?? {},\n timestamp: data.timestamp ?? Date.now(),\n ...(isApproval(data.approval)\n ? {\n approval: {\n approvalId: data.approval.approvalId,\n argsHash: data.approval.argsHash,\n },\n }\n : {}),\n // The surfaces the agent runtime holds, so the answer need not repeat them (§7.3.4).\n ...(typeof data.knownSurfaces === \"string\" && data.knownSurfaces\n ? { knownSurfaces: data.knownSurfaces }\n : {}),\n },\n receipt,\n );\n },\n );\n },\n\n // ─── Result ─────────────────────────────────────────────────\n sendResult(\n result: OUIActionResult,\n onAcknowledged?: (ack: OUIAcknowledgment) => void,\n ) {\n // Acknowledged, so a refusal is heard rather than silent (§7.3.6).\n emit(\n `${ns}:action:result`,\n result,\n onAcknowledged\n ? (response: unknown) => {\n const ack = acknowledgmentOf(response);\n if (ack) onAcknowledged(ack);\n }\n : undefined,\n );\n },\n\n onResult(handler: OUIResultHandler) {\n return listen(`${ns}:action:result`, (data: OUIActionResult) =>\n handler(data),\n );\n },\n\n // ─── Observation Channel ────────────────────────────────────\n pushObservation(update: OUIObservationUpdate) {\n emit(`${ns}:observation`, update);\n },\n\n onObservation(handler: OUIObservationHandler) {\n return listen(`${ns}:observation`, (data: OUIObservationUpdate) =>\n handler(data),\n );\n },\n\n // ─── Surface Lifecycle ──────────────────────────────────────\n registerSurface(surface: OUISurface) {\n emit(`${ns}:surface:register`, { surface, timestamp: Date.now() });\n },\n\n deregisterSurface(surfaceId: string) {\n emit(`${ns}:surface:deregister`, { surfaceId, timestamp: Date.now() });\n },\n\n onSurfaceRegister(handler: (surface: OUISurface) => void) {\n return listen(`${ns}:surface:register`, (data: { surface: OUISurface }) =>\n handler(data.surface),\n );\n },\n\n onSurfaceDeregister(handler: (surfaceId: string) => void) {\n return listen(`${ns}:surface:deregister`, (data: { surfaceId: string }) =>\n handler(data.surfaceId),\n );\n },\n\n // ─── Connection ─────────────────────────────────────────────\n get connected() {\n return connected;\n },\n\n async connect() {\n if (connected) return;\n return new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(() => {\n reject(\n new Error(\n `OUI transport connect timeout (${config?.connectTimeoutMs ?? 10000}ms)`,\n ),\n );\n }, config?.connectTimeoutMs ?? 10000);\n\n socket.once(\"connect\", () => {\n clearTimeout(timeout);\n resolve();\n });\n\n if (typeof socket.connect === \"function\") {\n socket.connect();\n }\n });\n },\n\n disconnect() {\n if (typeof socket.disconnect === \"function\") {\n socket.disconnect();\n }\n },\n\n onConnectionChange(handler: (c: boolean) => void) {\n connectionHandlers.push(handler);\n return () => {\n const idx = connectionHandlers.indexOf(handler);\n if (idx >= 0) connectionHandlers.splice(idx, 1);\n };\n },\n\n dispose() {\n for (const { event, handler } of attached.splice(0)) {\n socket.off(event, handler);\n }\n connectionHandlers.length = 0;\n buffer.length = 0;\n },\n };\n}\n\n/**\n * Minimal socket interface — compatible with Socket.IO client or server socket.\n * Only the methods OUI actually uses.\n */\nexport interface SocketLike {\n readonly connected?: boolean;\n /** An optional last argument is the receiver's acknowledgment callback, as Socket.IO takes it. */\n emit(event: string, ...args: any[]): void;\n on(event: string, handler: (...args: any[]) => void): void;\n off(event: string, handler: (...args: any[]) => void): void;\n once(event: string, handler: (...args: any[]) => void): void;\n connect?(): void;\n disconnect?(): void;\n}\n\n/** An approval as the wire carries it; anything else is dropped, so the runtime treats the request as unapproved. */\nfunction isApproval(value: unknown): value is OUIActionApproval {\n const a = value as Partial<OUIActionApproval> | null | undefined;\n return (\n !!a &&\n typeof a.approvalId === \"string\" &&\n !!a.approvalId &&\n typeof a.argsHash === \"string\"\n );\n}\n\n/** The receiver's acknowledgment callback. */\ntype Ack = (response: unknown) => void;\n\n/**\n * A receiver's acknowledgment: `{ ok: true }`, or `{ ok: false, error }` /\n * `{ ok: false, reason }` as a refusal; null for anything else.\n */\nfunction acknowledgmentOf(response: unknown): OUIAcknowledgment | null {\n const r = response as\n { ok?: unknown; error?: unknown; reason?: unknown } | null | undefined;\n if (!r || typeof r !== \"object\") return null;\n if (r.ok === true) return { ok: true };\n if (r.ok !== false) return null;\n const why =\n typeof r.error === \"string\"\n ? r.error\n : typeof r.reason === \"string\"\n ? r.reason\n : \"\";\n return { ok: false, reason: why || \"refused\" };\n}\n"]}
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
- var chunkDCVVTP3V_cjs = require('./chunk-DCVVTP3V.cjs');
3
+ var chunkLDECTDPU_cjs = require('./chunk-LDECTDPU.cjs');
4
+ var chunk5UUJ3F7C_cjs = require('./chunk-5UUJ3F7C.cjs');
4
5
 
5
6
  // src/core/brand.ts
6
7
  function brand(value) {
@@ -83,6 +84,8 @@ function actionToManifest(action) {
83
84
  return {
84
85
  id: action.id,
85
86
  description: action.description,
87
+ ...action.title !== void 0 ? { title: action.title } : {},
88
+ ...action.effect !== void 0 ? { effect: action.effect } : {},
86
89
  input: action.input,
87
90
  output: action.output,
88
91
  confirm: action.confirm,
@@ -127,7 +130,7 @@ function createOUI(config) {
127
130
  seen.add(surface.id);
128
131
  }
129
132
  const transport = brand(
130
- chunkDCVVTP3V_cjs.createWebSocketTransport(socket, transportConfig)
133
+ chunk5UUJ3F7C_cjs.createWebSocketTransport(socket, transportConfig)
131
134
  );
132
135
  const byId = new Map(surfaces.map((s) => [s.id, s]));
133
136
  return {
@@ -155,12 +158,14 @@ function createOUI(config) {
155
158
  var DEFAULT_QUIET_MS = 250;
156
159
  var DEFAULT_SETTLE_TIMEOUT_MS = 5e3;
157
160
  var DEFAULT_DEDUPE_WINDOW = 500;
161
+ var DEFAULT_REANSWER_AFTER_MS = 8e3;
158
162
  var SETTLE_TICK_MS = 50;
159
163
  function createSurfaceRuntime(options = {}) {
160
164
  const announce = options.announce ?? true;
161
165
  const quietMs = options.settle?.quietMs ?? DEFAULT_QUIET_MS;
162
166
  const settleTimeoutMs = options.settle?.timeoutMs ?? DEFAULT_SETTLE_TIMEOUT_MS;
163
167
  const dedupeWindow = options.dedupeWindow ?? DEFAULT_DEDUPE_WINDOW;
168
+ const reanswerAfterMs = options.reanswerAfterMs ?? DEFAULT_REANSWER_AFTER_MS;
164
169
  let nextKey = 1;
165
170
  const entries = [];
166
171
  const observations = /* @__PURE__ */ new Map();
@@ -185,7 +190,32 @@ function createSurfaceRuntime(options = {}) {
185
190
  function acknowledged(requestId) {
186
191
  return ackFor(requestId).promise;
187
192
  }
193
+ const delivery = /* @__PURE__ */ new Map();
194
+ const answeredHash = /* @__PURE__ */ new Map();
188
195
  let lastChangeAt = Date.now();
196
+ const grants = /* @__PURE__ */ new Map();
197
+ async function approvalRefusal(request) {
198
+ const approval = request.approval;
199
+ if (!approval)
200
+ return "it needs the user's approval, and the request carries none";
201
+ let hash;
202
+ try {
203
+ hash = await chunkLDECTDPU_cjs.argsHash(request.params ?? {});
204
+ } catch (err) {
205
+ return `its params cannot be checked against an approval: ${err instanceof Error ? err.message : String(err)}`;
206
+ }
207
+ const grant = grants.get(approval.approvalId);
208
+ if (!grant) return "this tab holds no unused approval for it";
209
+ if (Date.now() >= grant.expiresAt) {
210
+ grants.delete(approval.approvalId);
211
+ return "its approval has expired";
212
+ }
213
+ if (approval.argsHash !== grant.argsHash || hash !== grant.argsHash) {
214
+ return "its params are not the ones the user approved";
215
+ }
216
+ grants.delete(approval.approvalId);
217
+ return null;
218
+ }
189
219
  let socket = null;
190
220
  let transport = null;
191
221
  let detachTransport = [];
@@ -275,17 +305,20 @@ function createSurfaceRuntime(options = {}) {
275
305
  const finish = async (success, data, error) => {
276
306
  await acknowledged(requestId);
277
307
  const snap = snapshot();
278
- transport?.sendResult({
308
+ const known = answeredHash.get(requestId);
309
+ answer({
279
310
  requestId,
280
311
  success,
281
312
  ...data !== void 0 ? { data: toJsonSafe(data) } : {},
282
313
  ...error ? { error } : {},
283
314
  interim: false,
284
315
  timestamp: Date.now(),
285
- surfaces: snap.surfaces,
286
- observations: snap.observations
316
+ ...snap.surfacesHash !== known ? { surfaces: snap.surfaces } : {},
317
+ observations: snap.observations,
318
+ surfacesHash: snap.surfacesHash
287
319
  });
288
320
  acks.delete(requestId);
321
+ answeredHash.delete(requestId);
289
322
  };
290
323
  const observationId = `${surfaceId}:${actionId}:status`;
291
324
  const pollerKey = `${surfaceId}\0${actionId}`;
@@ -414,7 +447,17 @@ function createSurfaceRuntime(options = {}) {
414
447
  }
415
448
  };
416
449
  } else {
417
- result = await entry.surface.executeAction(
450
+ const declared = entry.surface.actions.find(
451
+ (a) => a.id === request.actionId
452
+ );
453
+ const refusal = declared && chunkLDECTDPU_cjs.requiresApproval(declared.effect, declared.confirm) ? await approvalRefusal(request) : null;
454
+ result = refusal ? {
455
+ success: false,
456
+ error: {
457
+ code: "APPROVAL_REQUIRED",
458
+ message: `"${request.actionId}" was not run: ${refusal}.`
459
+ }
460
+ } : await entry.surface.executeAction(
418
461
  request.actionId,
419
462
  request.params ?? {},
420
463
  entry.getContext()
@@ -437,6 +480,7 @@ function createSurfaceRuntime(options = {}) {
437
480
  }
438
481
  const settled = await waitUntilSettled(startedAt);
439
482
  const snap = snapshot();
483
+ if (interim) answeredHash.set(request.requestId, snap.surfacesHash);
440
484
  return {
441
485
  requestId: request.requestId,
442
486
  success: result.success,
@@ -445,11 +489,39 @@ function createSurfaceRuntime(options = {}) {
445
489
  ...interim ? { interim: true } : {},
446
490
  durationMs: Date.now() - startedAt,
447
491
  timestamp: Date.now(),
448
- surfaces: snap.surfaces,
492
+ // Only what the agent runtime does not already hold (§7.3.4).
493
+ ...snap.surfacesHash !== request.knownSurfaces ? { surfaces: snap.surfaces } : {},
449
494
  observations: snap.observations,
495
+ surfacesHash: snap.surfacesHash,
450
496
  settled
451
497
  };
452
498
  }
499
+ function answer(result, onReceived) {
500
+ const steps = [
501
+ ["surfaces", "observations"],
502
+ ["surfaces", "observations", "data"]
503
+ ];
504
+ const send = (frame, step) => {
505
+ transport?.sendResult(frame, (ack) => {
506
+ if (ack.ok) {
507
+ onReceived?.();
508
+ return;
509
+ }
510
+ const omitted = steps[step];
511
+ if (!omitted) {
512
+ console.warn(
513
+ `[OUI] The answer to ${result.requestId} was refused even trimmed: ${ack.reason}`
514
+ );
515
+ return;
516
+ }
517
+ console.warn(
518
+ `[OUI] The answer to ${result.requestId} was refused (${ack.reason}); sending it without ${omitted.join(", ")}`
519
+ );
520
+ send(trimmed(result, ack.reason, omitted), step + 1);
521
+ });
522
+ };
523
+ send(result, 0);
524
+ }
453
525
  function execute(request) {
454
526
  const seen = results.get(request.requestId);
455
527
  if (seen) return seen;
@@ -458,6 +530,7 @@ function createSurfaceRuntime(options = {}) {
458
530
  while (results.size > dedupeWindow) {
459
531
  const oldest = results.keys().next().value;
460
532
  results.delete(oldest);
533
+ delivery.delete(oldest);
461
534
  }
462
535
  return pending;
463
536
  }
@@ -470,7 +543,11 @@ function createSurfaceRuntime(options = {}) {
470
543
  if (values && values.size > 0)
471
544
  obs[e.surface.id] = Object.fromEntries(values);
472
545
  }
473
- return { surfaces, observations: obs };
546
+ return {
547
+ surfaces,
548
+ observations: obs,
549
+ surfacesHash: chunkLDECTDPU_cjs.surfacesHash(surfaces)
550
+ };
474
551
  }
475
552
  function attach(next) {
476
553
  if (next === socket) return;
@@ -480,21 +557,36 @@ function createSurfaceRuntime(options = {}) {
480
557
  transport = null;
481
558
  socket = next;
482
559
  if (!next) return;
483
- const t = chunkDCVVTP3V_cjs.createWebSocketTransport(next, options.transport);
560
+ const t = chunk5UUJ3F7C_cjs.createWebSocketTransport(next, options.transport);
484
561
  transport = t;
485
562
  detachTransport.push(
486
- t.onAction((request) => {
563
+ t.onAction((request, receipt) => {
487
564
  if (options.accept && !options.accept(request)) {
488
565
  console.warn(
489
566
  `[OUI] Refused a request the client does not accept: ${request.surfaceId}.${request.actionId} (${request.requestId})`
490
567
  );
568
+ receipt?.({
569
+ ok: false,
570
+ reason: "this client does not accept requests now"
571
+ });
491
572
  return;
492
573
  }
574
+ receipt?.({ ok: true });
575
+ const id = request.requestId;
576
+ const prior = delivery.get(id);
577
+ if (prior && prior.state !== "sent") return;
578
+ if (prior && Date.now() - prior.at < reanswerAfterMs) return;
579
+ if (!prior) delivery.set(id, { state: "running", at: Date.now() });
493
580
  void execute(request).then((result) => {
494
- transport?.sendResult(result);
495
- const ack = ackFor(request.requestId);
581
+ if (results.has(id))
582
+ delivery.set(id, { state: "sent", at: Date.now() });
583
+ answer(result, () => {
584
+ if (results.has(id))
585
+ delivery.set(id, { state: "received", at: Date.now() });
586
+ });
587
+ const ack = ackFor(id);
496
588
  ack.resolve();
497
- if (!result.interim) acks.delete(request.requestId);
589
+ if (!result.interim) acks.delete(id);
498
590
  });
499
591
  })
500
592
  );
@@ -557,6 +649,16 @@ function createSurfaceRuntime(options = {}) {
557
649
  changed();
558
650
  };
559
651
  },
652
+ grantApproval(grant) {
653
+ if (!grant || typeof grant.approvalId !== "string" || !grant.approvalId || typeof grant.argsHash !== "string" || !chunkLDECTDPU_cjs.ARGS_HASH_PATTERN.test(grant.argsHash) || !Number.isFinite(grant.expiresAt)) {
654
+ throw new Error(
655
+ "[OUI] grantApproval needs an approvalId, an args hash and an expiry"
656
+ );
657
+ }
658
+ const now = Date.now();
659
+ for (const [id, g] of grants) if (now >= g.expiresAt) grants.delete(id);
660
+ grants.set(grant.approvalId, { ...grant });
661
+ },
560
662
  subscribe(listener) {
561
663
  listeners.add(listener);
562
664
  return () => {
@@ -574,9 +676,18 @@ function createSurfaceRuntime(options = {}) {
574
676
  holds.clear();
575
677
  listeners.clear();
576
678
  acks.clear();
679
+ answeredHash.clear();
680
+ delivery.clear();
681
+ grants.clear();
577
682
  }
578
683
  };
579
684
  }
685
+ function trimmed(result, reason, omitted) {
686
+ const out = { ...result };
687
+ for (const field of omitted) delete out[field];
688
+ out.delivery = { trimmed: true, reason, omitted: [...omitted] };
689
+ return out;
690
+ }
580
691
  function sleep(ms) {
581
692
  return new Promise((resolve) => setTimeout(resolve, ms));
582
693
  }
@@ -592,5 +703,5 @@ function toJsonSafe(value) {
592
703
  exports.createOUI = createOUI;
593
704
  exports.createSurfaceRuntime = createSurfaceRuntime;
594
705
  exports.defineSurface = defineSurface;
595
- //# sourceMappingURL=chunk-GDB56ZQZ.cjs.map
596
- //# sourceMappingURL=chunk-GDB56ZQZ.cjs.map
706
+ //# sourceMappingURL=chunk-AFNWLRKV.cjs.map
707
+ //# sourceMappingURL=chunk-AFNWLRKV.cjs.map