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.
- package/README.md +41 -8
- package/dist/{chunk-DCVVTP3V.cjs → chunk-5UUJ3F7C.cjs} +53 -16
- package/dist/chunk-5UUJ3F7C.cjs.map +1 -0
- package/dist/{chunk-GDB56ZQZ.cjs → chunk-AFNWLRKV.cjs} +126 -15
- package/dist/chunk-AFNWLRKV.cjs.map +1 -0
- package/dist/chunk-B2EFZMGY.js +159 -0
- package/dist/chunk-B2EFZMGY.js.map +1 -0
- package/dist/chunk-LDECTDPU.cjs +169 -0
- package/dist/chunk-LDECTDPU.cjs.map +1 -0
- package/dist/{chunk-F3WKIRAU.js → chunk-R3SGP2UY.js} +53 -16
- package/dist/chunk-R3SGP2UY.js.map +1 -0
- package/dist/{chunk-UQLG55ST.js → chunk-VQETSIY5.js} +124 -13
- package/dist/chunk-VQETSIY5.js.map +1 -0
- package/dist/core/index.cjs +7 -7
- package/dist/core/index.d.cts +5 -5
- package/dist/core/index.d.ts +5 -5
- package/dist/core/index.js +3 -3
- package/dist/index.cjs +40 -8
- package/dist/index.d.cts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +3 -3
- package/dist/react/index.d.cts +3 -3
- package/dist/react/index.d.ts +3 -3
- package/dist/spec/index.cjs +34 -2
- package/dist/spec/index.d.cts +22 -272
- package/dist/spec/index.d.ts +22 -272
- package/dist/spec/index.js +1 -1
- package/dist/{surface-runtime-BzIKgL_I.d.cts → surface-runtime-CSZ9UtBO.d.cts} +23 -2
- package/dist/{surface-runtime-DeByfbKg.d.ts → surface-runtime-D5nINY4b.d.ts} +23 -2
- package/dist/transport/index.cjs +2 -2
- package/dist/transport/index.d.cts +3 -3
- package/dist/transport/index.d.ts +3 -3
- package/dist/transport/index.js +1 -1
- package/dist/types-CmJb1BQ8.d.cts +403 -0
- package/dist/types-CmJb1BQ8.d.ts +403 -0
- package/dist/{websocket-YfWILdZ4.d.cts → websocket-Ce3tDSsJ.d.ts} +26 -7
- package/dist/{websocket-c5tIUWY5.d.ts → websocket-Ds2Rh_jL.d.cts} +26 -7
- package/package.json +4 -3
- package/spec/OUI-SPEC-v0.1.md +63 -7
- package/spec/approval-vectors.json +88 -0
- package/dist/chunk-BMCL2K74.js +0 -12
- package/dist/chunk-BMCL2K74.js.map +0 -1
- package/dist/chunk-DCVVTP3V.cjs.map +0 -1
- package/dist/chunk-F3WKIRAU.js.map +0 -1
- package/dist/chunk-GDB56ZQZ.cjs.map +0 -1
- package/dist/chunk-UQLG55ST.js.map +0 -1
- package/dist/chunk-ZKM4XWAB.cjs +0 -14
- 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,
|
|
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
|
|
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,
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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(
|
|
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-
|
|
155
|
-
//# sourceMappingURL=chunk-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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 =
|
|
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
|
-
|
|
495
|
-
|
|
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(
|
|
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-
|
|
596
|
-
//# sourceMappingURL=chunk-
|
|
706
|
+
//# sourceMappingURL=chunk-AFNWLRKV.cjs.map
|
|
707
|
+
//# sourceMappingURL=chunk-AFNWLRKV.cjs.map
|