@cef-ai/widget-runtime 2.3.1 → 2.4.1
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 +23 -1
- package/dist/index.d.ts +1 -1
- package/dist/runtime.js +23 -0
- package/dist/types.d.ts +42 -0
- package/dist/vault-client.d.ts +31 -10
- package/dist/vault-client.js +70 -0
- package/dist/widget-runtime.js +76 -76
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Browser runtime for CEF widgets. It boots from `window.WidgetSandbox.manifest`,
|
|
4
4
|
resolves the user's identity, exposes `window.WidgetRuntime`
|
|
5
|
-
(`query` / `publish` / `connect` / `connectAgent` / `agentStatus` / `identity`),
|
|
5
|
+
(`query` / `publish` / `subscribe` / `connect` / `connectAgent` / `agentStatus` / `identity`),
|
|
6
6
|
and renders config-driven widget kinds (record / list / dashboard / submit /
|
|
7
7
|
conversation / composite) plus custom widgets. It also carries the audio
|
|
8
8
|
capture/upload path. The runtime **reads** a manifest; the `@cef-ai/cli` build
|
|
@@ -12,6 +12,28 @@ The esbuild IIFE build emits `dist/widget-runtime.js` (the browser bundle) and
|
|
|
12
12
|
`dist/widget.html` (the shell); `tsc` emits the library entry (`dist/index.js`)
|
|
13
13
|
for consumers that import the helpers directly.
|
|
14
14
|
|
|
15
|
+
## Following a stream
|
|
16
|
+
|
|
17
|
+
`subscribe` polls one stream of the widget's scope (the scope `publish` writes
|
|
18
|
+
to) and hands each poll's new events to the callback as one ordered,
|
|
19
|
+
de-duplicated batch:
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
const { requestId } = …;
|
|
23
|
+
await WidgetRuntime.publish('workflow.start', input, requestId, { target });
|
|
24
|
+
const stop = WidgetRuntime.subscribe(
|
|
25
|
+
requestId,
|
|
26
|
+
{ types: ['agent.progress'], intervalMs: 2500 },
|
|
27
|
+
(events) => events.forEach(render),
|
|
28
|
+
);
|
|
29
|
+
// later: stop();
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
It polls every 2500ms by default, backs off (doubling, up to `maxBackoffMs`,
|
|
33
|
+
default 30s) while polls fail and reports each failure to `onError`, and stops
|
|
34
|
+
when the returned function is called. Reading a stream needs `ddc-read` and a
|
|
35
|
+
read grant on the scope, which a widget's delegation carries.
|
|
36
|
+
|
|
15
37
|
## Identity (multi-source)
|
|
16
38
|
|
|
17
39
|
The runtime resolves a signing identity from the best available source:
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { buildWidgetManifest, type BuildManifestInput } from './manifest.js';
|
|
2
2
|
export { injectWidgetBridge, type BridgeInput } from './bridge.js';
|
|
3
|
-
export type { WidgetManifest, WidgetRuntimeApi, Identity, QueryResult, PublishOptions } from './types.js';
|
|
3
|
+
export type { WidgetManifest, WidgetRuntimeApi, Identity, QueryResult, PublishOptions, SubscribeOptions, WidgetEvent } from './types.js';
|
|
4
4
|
export { AgentNotConnectedError } from './types.js';
|
|
5
5
|
export { validateConfig } from './validate-config.js';
|
|
6
6
|
export { mountKind } from './mount.js';
|
package/dist/runtime.js
CHANGED
|
@@ -49,5 +49,28 @@ export function createWidgetRuntime(manifest, deps) {
|
|
|
49
49
|
async connectAgent() { await (await ensureClient()).connectAgent(); },
|
|
50
50
|
async agentStatus() { return (await ensureClient()).agentStatus(); },
|
|
51
51
|
onIdentityChange(cb) { subs.add(cb); return () => subs.delete(cb); },
|
|
52
|
+
/*
|
|
53
|
+
* SIGN-OUT STOPS IT. The runtime's only way to `anon` is a sign-in that
|
|
54
|
+
* failed — once a client is built it is kept, and nothing upstream says a
|
|
55
|
+
* session ended — so a subscription made before or during that sign-in is
|
|
56
|
+
* the one sign-out can strand, and it ends here, with the reason handed to
|
|
57
|
+
* `onError` so the widget is not left waiting on events that cannot come.
|
|
58
|
+
*/
|
|
59
|
+
subscribe(streamContext, opts, onEvents) {
|
|
60
|
+
let cancelled = false;
|
|
61
|
+
let stopClient;
|
|
62
|
+
ensureClient().then((client) => { if (!cancelled)
|
|
63
|
+
stopClient = client.subscribe(streamContext, opts, onEvents); }, (err) => {
|
|
64
|
+
if (cancelled)
|
|
65
|
+
return;
|
|
66
|
+
(opts.onError ?? ((e) => { console.warn('[widget-runtime] subscribe failed:', e); }))(err);
|
|
67
|
+
});
|
|
68
|
+
return () => {
|
|
69
|
+
if (cancelled)
|
|
70
|
+
return;
|
|
71
|
+
cancelled = true;
|
|
72
|
+
stopClient?.();
|
|
73
|
+
};
|
|
74
|
+
},
|
|
52
75
|
};
|
|
53
76
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -112,6 +112,34 @@ export interface PublishOptions {
|
|
|
112
112
|
/** The one agent this event is for, `<asPubkey>:<name>`. */
|
|
113
113
|
target?: string;
|
|
114
114
|
}
|
|
115
|
+
/** One event from a vault stream, as {@link WidgetRuntimeApi.subscribe} delivers it. */
|
|
116
|
+
export interface WidgetEvent<P = unknown> {
|
|
117
|
+
eventId: string;
|
|
118
|
+
type: string;
|
|
119
|
+
context: string;
|
|
120
|
+
payload: P;
|
|
121
|
+
/** Who published it — `app`, `agent:<id>`, … — when the vault recorded one. */
|
|
122
|
+
from?: string;
|
|
123
|
+
/** When the vault accepted it (RFC 3339). */
|
|
124
|
+
timestamp: string;
|
|
125
|
+
}
|
|
126
|
+
export interface SubscribeOptions {
|
|
127
|
+
/** Deliver only these event types. All types when absent. */
|
|
128
|
+
types?: string[];
|
|
129
|
+
/** Poll interval in ms. Default 2500. */
|
|
130
|
+
intervalMs?: number;
|
|
131
|
+
/**
|
|
132
|
+
* Ceiling for the wait between polls while they keep failing: it doubles
|
|
133
|
+
* from `intervalMs` up to this, and resets on the next success. Default 30000.
|
|
134
|
+
*/
|
|
135
|
+
maxBackoffMs?: number;
|
|
136
|
+
/**
|
|
137
|
+
* A failed poll, or a failure to open the vault at all. Polling carries on
|
|
138
|
+
* (backing off) after a failed poll; it stops when the vault cannot be
|
|
139
|
+
* opened. Default: console.warn.
|
|
140
|
+
*/
|
|
141
|
+
onError?: (err: unknown) => void;
|
|
142
|
+
}
|
|
115
143
|
export interface WidgetRuntimeApi {
|
|
116
144
|
query(ref: string, params?: unknown[]): Promise<QueryResult>;
|
|
117
145
|
/**
|
|
@@ -130,6 +158,20 @@ export interface WidgetRuntimeApi {
|
|
|
130
158
|
connectAgent(): Promise<void>;
|
|
131
159
|
agentStatus(): Promise<'connected' | 'disconnected'>;
|
|
132
160
|
onIdentityChange(cb: (id: Identity) => void): () => void;
|
|
161
|
+
/**
|
|
162
|
+
* Follow one stream of the widget's scope — the scope `publish` writes to —
|
|
163
|
+
* and get its events as they arrive.
|
|
164
|
+
*
|
|
165
|
+
* `onEvents` receives each poll's new events as one batch, in stream order,
|
|
166
|
+
* each event at most once (de-duplicated by `eventId`); an empty poll calls
|
|
167
|
+
* nothing. Events already in the stream when you subscribe are delivered in
|
|
168
|
+
* the first batch, so subscribing just after a `publish` misses nothing.
|
|
169
|
+
*
|
|
170
|
+
* Returns the unsubscribe function. Polling also stops by itself when the
|
|
171
|
+
* reader's identity drops to `anon`. A failed poll never throws out of the
|
|
172
|
+
* timer: it goes to `onError`, and the next poll waits longer.
|
|
173
|
+
*/
|
|
174
|
+
subscribe<P = unknown>(context: string, opts: SubscribeOptions, onEvents: (events: WidgetEvent<P>[]) => void): () => void;
|
|
133
175
|
}
|
|
134
176
|
export declare class AgentNotConnectedError extends Error {
|
|
135
177
|
readonly agentId: string;
|
package/dist/vault-client.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import { type QueryResult, type WidgetManifest, type PublishOptions } from './types.js';
|
|
1
|
+
import { type QueryResult, type WidgetManifest, type PublishOptions, type SubscribeOptions, type WidgetEvent } from './types.js';
|
|
2
|
+
/** Default poll interval for {@link VaultClient.subscribe}. */
|
|
3
|
+
export declare const SUBSCRIBE_INTERVAL_MS = 2500;
|
|
4
|
+
/** Default ceiling for the error backoff of {@link VaultClient.subscribe}. */
|
|
5
|
+
export declare const SUBSCRIBE_MAX_BACKOFF_MS = 30000;
|
|
2
6
|
export interface VaultSdkLike {
|
|
3
7
|
vault: {
|
|
4
8
|
ensure: (opts?: {
|
|
@@ -71,15 +75,7 @@ interface VaultLike {
|
|
|
71
75
|
settings?: Record<string, unknown>;
|
|
72
76
|
}) => Promise<AgentHandleLike>;
|
|
73
77
|
};
|
|
74
|
-
scope: (name: string) =>
|
|
75
|
-
publish: (input: {
|
|
76
|
-
type: string;
|
|
77
|
-
context: string;
|
|
78
|
-
payload: unknown;
|
|
79
|
-
}) => Promise<{
|
|
80
|
-
eventId: string;
|
|
81
|
-
}>;
|
|
82
|
-
};
|
|
78
|
+
scope: (name: string) => ScopeLike;
|
|
83
79
|
/**
|
|
84
80
|
* The agent-SERVICE cubbies: one database per service, global within the
|
|
85
81
|
* vault, so a widget reads what its agent's siblings wrote wherever the run
|
|
@@ -95,6 +91,30 @@ interface VaultLike {
|
|
|
95
91
|
*/
|
|
96
92
|
memory?: MemoryLike;
|
|
97
93
|
}
|
|
94
|
+
interface ScopeLike {
|
|
95
|
+
publish: (input: {
|
|
96
|
+
type: string;
|
|
97
|
+
context: string;
|
|
98
|
+
payload: unknown;
|
|
99
|
+
}) => Promise<{
|
|
100
|
+
eventId: string;
|
|
101
|
+
}>;
|
|
102
|
+
/**
|
|
103
|
+
* vault-sdk's stream poller. Optional on the structural type for the same
|
|
104
|
+
* reason `service` is: an older vault-sdk does not have the backoff option,
|
|
105
|
+
* or (older still) the method.
|
|
106
|
+
*/
|
|
107
|
+
subscribe?: (filter: {
|
|
108
|
+
context: string;
|
|
109
|
+
types?: string[];
|
|
110
|
+
}, handler: (event: WidgetEvent) => void, opts: {
|
|
111
|
+
intervalMs?: number;
|
|
112
|
+
maxBackoffMs?: number;
|
|
113
|
+
onError?: (err: unknown) => void;
|
|
114
|
+
}) => {
|
|
115
|
+
unsubscribe(): void;
|
|
116
|
+
};
|
|
117
|
+
}
|
|
98
118
|
/** The bank's read surface, as much of it as a widget can name. */
|
|
99
119
|
interface MemoryLike {
|
|
100
120
|
search: (match: string, opts?: {
|
|
@@ -120,6 +140,7 @@ export interface VaultClient {
|
|
|
120
140
|
}>;
|
|
121
141
|
connectAgent(): Promise<void>;
|
|
122
142
|
agentStatus(): Promise<'connected' | 'disconnected'>;
|
|
143
|
+
subscribe<P = unknown>(context: string, opts: SubscribeOptions, onEvents: (events: WidgetEvent<P>[]) => void): () => void;
|
|
123
144
|
}
|
|
124
145
|
export declare function createVaultClient(manifest: WidgetManifest, makeSdk: () => VaultSdkLike,
|
|
125
146
|
/**
|
package/dist/vault-client.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
// packages/widget-runtime/src/vault-client.ts
|
|
2
2
|
import {} from './types.js';
|
|
3
|
+
/** Default poll interval for {@link VaultClient.subscribe}. */
|
|
4
|
+
export const SUBSCRIBE_INTERVAL_MS = 2500;
|
|
5
|
+
/** Default ceiling for the error backoff of {@link VaultClient.subscribe}. */
|
|
6
|
+
export const SUBSCRIBE_MAX_BACKOFF_MS = 30000;
|
|
3
7
|
/**
|
|
4
8
|
* The host named a vault this widget cannot open.
|
|
5
9
|
*
|
|
@@ -227,6 +231,72 @@ hostVaultId) {
|
|
|
227
231
|
...(options?.target ? { target: options.target } : {}),
|
|
228
232
|
});
|
|
229
233
|
},
|
|
234
|
+
subscribe(context, opts, onEvents) {
|
|
235
|
+
const onError = opts.onError
|
|
236
|
+
?? ((err) => { console.warn('[widget-runtime] subscribe poll failed:', err); });
|
|
237
|
+
let stopped = false;
|
|
238
|
+
let sub;
|
|
239
|
+
let batch = [];
|
|
240
|
+
let flushTimer;
|
|
241
|
+
/*
|
|
242
|
+
* ONE CALLBACK PER POLL, NOT PER EVENT. vault-sdk hands events over one
|
|
243
|
+
* at a time, synchronously within a poll, and the next poll is a network
|
|
244
|
+
* round-trip away — so a zero-delay timer armed by the first event of a
|
|
245
|
+
* poll fires once that poll's events are all in. A renderer gets the
|
|
246
|
+
* batch it can pace, instead of N re-renders it cannot tell apart.
|
|
247
|
+
*/
|
|
248
|
+
const flush = () => {
|
|
249
|
+
flushTimer = undefined;
|
|
250
|
+
const out = batch;
|
|
251
|
+
batch = [];
|
|
252
|
+
if (out.length === 0)
|
|
253
|
+
return;
|
|
254
|
+
try {
|
|
255
|
+
onEvents(out);
|
|
256
|
+
}
|
|
257
|
+
catch (err) {
|
|
258
|
+
onError(err);
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
void getVault().then((vault) => {
|
|
262
|
+
if (stopped)
|
|
263
|
+
return;
|
|
264
|
+
const scope = vault.scope(manifest.scope);
|
|
265
|
+
if (typeof scope.subscribe !== 'function') {
|
|
266
|
+
throw new Error('[widget-runtime] this host\'s vault-sdk cannot follow a stream (no scope.subscribe)');
|
|
267
|
+
}
|
|
268
|
+
sub = scope.subscribe({ context, ...(opts.types ? { types: opts.types } : {}) }, (event) => {
|
|
269
|
+
if (stopped)
|
|
270
|
+
return;
|
|
271
|
+
batch.push({
|
|
272
|
+
eventId: event.eventId,
|
|
273
|
+
type: event.type,
|
|
274
|
+
context: event.context,
|
|
275
|
+
payload: event.payload,
|
|
276
|
+
...(event.from ? { from: event.from } : {}),
|
|
277
|
+
timestamp: event.timestamp,
|
|
278
|
+
});
|
|
279
|
+
flushTimer ?? (flushTimer = setTimeout(flush, 0));
|
|
280
|
+
}, {
|
|
281
|
+
intervalMs: opts.intervalMs ?? SUBSCRIBE_INTERVAL_MS,
|
|
282
|
+
maxBackoffMs: opts.maxBackoffMs ?? SUBSCRIBE_MAX_BACKOFF_MS,
|
|
283
|
+
onError,
|
|
284
|
+
});
|
|
285
|
+
}).catch((err) => {
|
|
286
|
+
// The vault would not open (signed out, unreachable): nothing to poll.
|
|
287
|
+
if (!stopped)
|
|
288
|
+
onError(err);
|
|
289
|
+
});
|
|
290
|
+
return () => {
|
|
291
|
+
if (stopped)
|
|
292
|
+
return;
|
|
293
|
+
stopped = true;
|
|
294
|
+
if (flushTimer !== undefined)
|
|
295
|
+
clearTimeout(flushTimer);
|
|
296
|
+
batch = [];
|
|
297
|
+
sub?.unsubscribe();
|
|
298
|
+
};
|
|
299
|
+
},
|
|
230
300
|
async connectAgent() {
|
|
231
301
|
const vault = await getVault();
|
|
232
302
|
// 2.x connect is card-only: pass the agentId + scope straight through.
|