@cef-ai/widget-runtime 1.1.1 → 2.0.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/dist/bridge-wallet.js +19 -4
- package/dist/entry.js +36 -17
- package/dist/host-bridge.d.ts +59 -4
- package/dist/host-bridge.js +6 -1
- package/dist/manifest.d.ts +4 -1
- package/dist/manifest.js +12 -0
- package/dist/resolve-identity.d.ts +21 -1
- package/dist/resolve-identity.js +2 -0
- package/dist/runtime.d.ts +3 -0
- package/dist/runtime.js +20 -3
- package/dist/types.d.ts +60 -6
- package/dist/vault-client.d.ts +47 -2
- package/dist/vault-client.js +100 -56
- package/dist/vault-sdk-config.d.ts +26 -0
- package/dist/vault-sdk-config.js +37 -0
- package/dist/widget-runtime.js +145 -140
- package/package.json +2 -2
package/dist/bridge-wallet.js
CHANGED
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
// All hex values in the client-sdk shape are WITHOUT a 0x prefix, matching
|
|
15
15
|
// the proven embed-wallet behaviour (see embed-adapter.ts history).
|
|
16
16
|
import { hexToU8a } from '@polkadot/util';
|
|
17
|
+
import { encodeAddress } from '@polkadot/util-crypto';
|
|
18
|
+
/** Cere's registered SS58 prefix. */
|
|
19
|
+
const CERE_SS58_PREFIX = 54;
|
|
17
20
|
// ── Internal helpers ──────────────────────────────────────────────────────────
|
|
18
21
|
/** Encode bytes as lowercase hex WITHOUT a `0x` prefix. */
|
|
19
22
|
function bytesToHexNo0x(bytes) {
|
|
@@ -36,10 +39,22 @@ function bytesToHexNo0x(bytes) {
|
|
|
36
39
|
export function toVaultSdkSigner(pubkey, sigType, signFn) {
|
|
37
40
|
return {
|
|
38
41
|
type: sigType,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
/*
|
|
43
|
+
* A REAL SS58 ADDRESS, not the hex pubkey.
|
|
44
|
+
*
|
|
45
|
+
* It was the hex, justified by "vault-sdk's WalletAuthProvider reads
|
|
46
|
+
* `publicKey`/`type`, not `address`". True of that provider, and a trap: it
|
|
47
|
+
* made the field's correctness depend on which auth provider happened to be
|
|
48
|
+
* in use. The moment a widget carries a host delegation it uses
|
|
49
|
+
* `DelegationAuthProvider` instead, something downstream decodes `address`,
|
|
50
|
+
* and a hex string fails SS58's checksum — surfacing in the widget as
|
|
51
|
+
* "Something went wrong: Invalid checksum", which names neither the field
|
|
52
|
+
* nor the value.
|
|
53
|
+
*
|
|
54
|
+
* Encoding it costs one call and removes the dependency entirely: the
|
|
55
|
+
* address is right whoever reads it.
|
|
56
|
+
*/
|
|
57
|
+
address: encodeAddress(hexToU8a(pubkey), CERE_SS58_PREFIX),
|
|
43
58
|
publicKey: hexToU8a(pubkey),
|
|
44
59
|
isReady: async () => true,
|
|
45
60
|
sign: (bytes) => signFn(bytes),
|
package/dist/entry.js
CHANGED
|
@@ -5,6 +5,7 @@ import { toVaultSdkSigner } from './bridge-wallet.js';
|
|
|
5
5
|
import { connectStandaloneWallet } from './standalone-wallet.js';
|
|
6
6
|
import { createIdentityResolver } from './resolve-identity.js';
|
|
7
7
|
import { createVaultClient } from './vault-client.js';
|
|
8
|
+
import { toVaultSdkConfig } from './vault-sdk-config.js';
|
|
8
9
|
import { createWidgetRuntime } from './runtime.js';
|
|
9
10
|
import { captureAndUploadAudio, createVaultObjectUploader } from './audio/capture-upload.js';
|
|
10
11
|
import { decodeToMono16k } from './audio/segment.js';
|
|
@@ -38,13 +39,19 @@ function boot() {
|
|
|
38
39
|
bridge,
|
|
39
40
|
connectWallet: () => connectStandaloneWallet(manifest.wallet),
|
|
40
41
|
});
|
|
42
|
+
/** The host's delegation, captured when identity resolved. See resolveIdentity. */
|
|
43
|
+
let hostDelegation;
|
|
41
44
|
const deps = {
|
|
42
45
|
resolveIdentity: async () => {
|
|
43
46
|
const id = await resolveSigningIdentity();
|
|
47
|
+
// Remembered for buildVaultClient, which is synchronous and runs after
|
|
48
|
+
// this. The resolver is memoised, so this is the same identity.
|
|
49
|
+
hostDelegation = id.token;
|
|
44
50
|
return { source: id.source, publicKey: id.publicKey, sigType: id.sigType };
|
|
45
51
|
},
|
|
46
52
|
buildVaultClient: (m, id) => {
|
|
47
53
|
const sigType = id.sigType ?? 'ed25519';
|
|
54
|
+
const hostToken = hostDelegation;
|
|
48
55
|
// Defer to the memoized identity for signing — resolveIdentity() has
|
|
49
56
|
// already run by the time buildVaultClient is called, so this resolves
|
|
50
57
|
// to the same (host or wallet) signer without a second handshake/popup.
|
|
@@ -52,14 +59,17 @@ function boot() {
|
|
|
52
59
|
const vaultSdkSigner = toVaultSdkSigner(id.publicKey, sigType, signFn);
|
|
53
60
|
// vault-sdk SDK instance: used for vault.ensure(), cubby reads, publish,
|
|
54
61
|
// connectAgent, agentStatus — the single SDK for the whole runtime.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
// `toVaultSdkConfig` carries `endpoints.rpc` as `chainUrl`: ensure()'s
|
|
63
|
+
// wallet path grants vault-api an on-chain NonTransfer proxy before the
|
|
64
|
+
// tokenless claim and cannot run without a chain URL.
|
|
65
|
+
// THE HOST'S DELEGATION, WHEN IT GAVE ONE. `auth` overrides `signer`, so
|
|
66
|
+
// this handle structurally cannot sign — and on the passkey wallet that is
|
|
67
|
+
// the difference between one ceremony and one per request: a keyless
|
|
68
|
+
// session steps up on every signature, so a widget that polls a query
|
|
69
|
+
// asked for a passkey forever. `sign` stays wired for hosts that supply
|
|
70
|
+
// no token.
|
|
71
|
+
const makeSdk = () => new VaultSDK(toVaultSdkConfig(m.endpoints, vaultSdkSigner, hostToken));
|
|
72
|
+
return createVaultClient(m, makeSdk, hostToken);
|
|
63
73
|
},
|
|
64
74
|
};
|
|
65
75
|
// Build the mount deps (audio uploader) for config-driven audio kinds.
|
|
@@ -85,13 +95,12 @@ function boot() {
|
|
|
85
95
|
uploaderP = (async () => {
|
|
86
96
|
const id = await resolveSigningIdentity();
|
|
87
97
|
const vaultSdkSigner = toVaultSdkSigner(id.publicKey, id.sigType, id.sign);
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
});
|
|
98
|
+
// Same delegation preference as the vault client: an upload that signs
|
|
99
|
+
// per chunk on a keyless session is a ceremony per chunk.
|
|
100
|
+
// Same config builder as the main vault client above — in particular
|
|
101
|
+
// the same `chainUrl`, since this `ensure({ onboard: false })` runs
|
|
102
|
+
// the vault-delegate grant step too.
|
|
103
|
+
const sdk = new VaultSDK(toVaultSdkConfig(manifest.endpoints, vaultSdkSigner, id.token));
|
|
95
104
|
const ensuredVault = await sdk.vault.ensure({ onboard: false });
|
|
96
105
|
return createVaultObjectUploader({ objects: ensuredVault.scope(manifest.scope).objects });
|
|
97
106
|
})().catch((err) => { uploaderP = null; throw err; });
|
|
@@ -127,9 +136,19 @@ function boot() {
|
|
|
127
136
|
// script and are unaffected.)
|
|
128
137
|
onDomReady(() => {
|
|
129
138
|
const app = document.getElementById('app');
|
|
130
|
-
if (app) {
|
|
131
|
-
|
|
139
|
+
if (!app) {
|
|
140
|
+
// SAY SO. This used to be a bare `if (app)`, so a widget whose entry
|
|
141
|
+
// HTML had no mount point loaded its runtime, resolved its identity,
|
|
142
|
+
// ran nothing, and rendered whatever static markup the file happened to
|
|
143
|
+
// contain. From the outside that is indistinguishable from a query
|
|
144
|
+
// returning no rows — the page is simply there, and wrong, with an
|
|
145
|
+
// empty console. Cost us a devnet round trip to find.
|
|
146
|
+
console.error(`[widget-runtime] widget "${manifest.widgetId}" declares kind "${manifest.kind}" ` +
|
|
147
|
+
'but its entry HTML has no <div id="app"></div>, so there is nowhere to render. ' +
|
|
148
|
+
'Add the element, or declare kind "custom" and mount it yourself.');
|
|
149
|
+
return;
|
|
132
150
|
}
|
|
151
|
+
void import('./mount.js').then(({ mountKind }) => mountKind(app, manifest.config, window.WidgetRuntime, mountDeps));
|
|
133
152
|
});
|
|
134
153
|
}
|
|
135
154
|
}
|
package/dist/host-bridge.d.ts
CHANGED
|
@@ -21,6 +21,19 @@ export declare const UNSUPPORTED_VERSION_CODE = "unsupported-version";
|
|
|
21
21
|
/** Largest `bytes` payload a host will sign, and largest signature accepted. */
|
|
22
22
|
export declare const MAX_SIGN_BYTES: number;
|
|
23
23
|
export type SigType = VaultSigType;
|
|
24
|
+
/**
|
|
25
|
+
* What the host can tell a widget about WHY it was opened.
|
|
26
|
+
*
|
|
27
|
+
* Every field optional, and a widget may ignore any of them — see
|
|
28
|
+
* `IdentityResponse.context`. Additive only: a new key must never change what a
|
|
29
|
+
* widget that does not read it does.
|
|
30
|
+
*/
|
|
31
|
+
export interface WidgetHostContext {
|
|
32
|
+
/** The workflow execution being viewed, when the widget was opened from one. */
|
|
33
|
+
runId?: string;
|
|
34
|
+
/** One record, when the widget was opened from a row rather than a run. */
|
|
35
|
+
recordId?: string;
|
|
36
|
+
}
|
|
24
37
|
export interface IdentityRequest {
|
|
25
38
|
type: typeof IDENTITY_REQUEST_TYPE;
|
|
26
39
|
requestId: string;
|
|
@@ -34,6 +47,42 @@ export interface IdentityResponse {
|
|
|
34
47
|
pubkey?: string;
|
|
35
48
|
/** Defaults to 'ed25519' when omitted. */
|
|
36
49
|
sigType?: SigType;
|
|
50
|
+
/**
|
|
51
|
+
* A bearer delegation the widget may use INSTEAD of asking the host to sign.
|
|
52
|
+
*
|
|
53
|
+
* WHY IT EXISTS. Without one, every vault request a widget makes is signed —
|
|
54
|
+
* and on the passkey wallet a signature from a keyless session raises a
|
|
55
|
+
* step-up. A widget that polls a query therefore raised a passkey prompt per
|
|
56
|
+
* poll, forever. The host already holds a session and can mint a delegation
|
|
57
|
+
* with no ceremony at all, so handing one over turns N ceremonies into none.
|
|
58
|
+
*
|
|
59
|
+
* OPTIONAL, AND THAT IS THE COMPATIBILITY STORY. A host that does not send
|
|
60
|
+
* one gets exactly the old behaviour: the widget falls back to
|
|
61
|
+
* `cef-widget:sign-request` per operation. Nothing already deployed changes.
|
|
62
|
+
*/
|
|
63
|
+
token?: string;
|
|
64
|
+
/**
|
|
65
|
+
* WHICH RUN THE HOST IS SHOWING — so a widget opened from an execution can
|
|
66
|
+
* show THAT execution rather than the latest.
|
|
67
|
+
*
|
|
68
|
+
* Asked for by name, and left undesigned until now: "we should be able to
|
|
69
|
+
* pass a parameter, so that the widget opens the right records … a simple
|
|
70
|
+
* 'id' parameter would suffice and then anyone who decides to build a widget
|
|
71
|
+
* can decide to support that parameter or not."
|
|
72
|
+
*
|
|
73
|
+
* That last clause is the design. This is CONTEXT, not a command: a widget
|
|
74
|
+
* that ignores it behaves exactly as it does today, and a host that sends
|
|
75
|
+
* none is the existing case. Neither side has to change for the other's to
|
|
76
|
+
* work, which is why it is a field on the handshake rather than a new
|
|
77
|
+
* message or a required query parameter.
|
|
78
|
+
*
|
|
79
|
+
* A BAG, not a bare string, and the reason is the thing it will grow into: an
|
|
80
|
+
* execution id is what an execution view has, but the same slot is where a
|
|
81
|
+
* record id belongs when a widget is opened from a row — and those are
|
|
82
|
+
* different keys, which nobody has yet decided are the same. Naming the field
|
|
83
|
+
* now costs nothing; conflating them would cost a migration.
|
|
84
|
+
*/
|
|
85
|
+
context?: WidgetHostContext;
|
|
37
86
|
error?: string;
|
|
38
87
|
/** Machine-readable reason; see UNSUPPORTED_VERSION_CODE. */
|
|
39
88
|
errorCode?: typeof UNSUPPORTED_VERSION_CODE;
|
|
@@ -73,12 +122,18 @@ export declare function isUnsupportedVersion(v: unknown): boolean;
|
|
|
73
122
|
*/
|
|
74
123
|
export declare function isByteArray(value: unknown): value is number[];
|
|
75
124
|
export interface HostBridge {
|
|
76
|
-
requestIdentity: () => Promise<
|
|
77
|
-
pubkey: string;
|
|
78
|
-
sigType: SigType;
|
|
79
|
-
} | null>;
|
|
125
|
+
requestIdentity: () => Promise<HostIdentity | null>;
|
|
80
126
|
sign: (bytes: Uint8Array) => Promise<Uint8Array>;
|
|
81
127
|
}
|
|
128
|
+
/** What the host answered the handshake with. */
|
|
129
|
+
export interface HostIdentity {
|
|
130
|
+
pubkey: string;
|
|
131
|
+
sigType: SigType;
|
|
132
|
+
/** A bearer delegation, when the host supplied one. See IdentityResponse.token. */
|
|
133
|
+
token?: string;
|
|
134
|
+
/** Why the host opened this widget. See IdentityResponse.context. */
|
|
135
|
+
context?: WidgetHostContext;
|
|
136
|
+
}
|
|
82
137
|
/**
|
|
83
138
|
* Returns the host bridge client. The bridge is stateless across calls
|
|
84
139
|
* (identity is cached internally after the first successful handshake).
|
package/dist/host-bridge.js
CHANGED
|
@@ -175,7 +175,12 @@ async function requestIdentity() {
|
|
|
175
175
|
scheduleRetryOrTimeout();
|
|
176
176
|
return;
|
|
177
177
|
}
|
|
178
|
-
const result = {
|
|
178
|
+
const result = {
|
|
179
|
+
pubkey: d.pubkey,
|
|
180
|
+
sigType: d.sigType ?? 'ed25519',
|
|
181
|
+
...(d.token ? { token: d.token } : {}),
|
|
182
|
+
...(d.context ? { context: d.context } : {}),
|
|
183
|
+
};
|
|
179
184
|
finish();
|
|
180
185
|
cachedIdentity = result;
|
|
181
186
|
outerResolve(result);
|
package/dist/manifest.d.ts
CHANGED
|
@@ -20,7 +20,10 @@ export interface BuildManifestInput {
|
|
|
20
20
|
queries?: Array<{
|
|
21
21
|
id: string;
|
|
22
22
|
label?: string;
|
|
23
|
-
sql
|
|
23
|
+
sql?: string;
|
|
24
|
+
cubby?: string;
|
|
25
|
+
tool?: 'search' | 'get' | 'neighbours' | 'countByType';
|
|
26
|
+
limit?: number;
|
|
24
27
|
timeoutMs?: number;
|
|
25
28
|
}>;
|
|
26
29
|
events?: WidgetManifest['events'];
|
package/dist/manifest.js
CHANGED
|
@@ -5,11 +5,23 @@ export function buildWidgetManifest(input) {
|
|
|
5
5
|
// Build queries[]: explicit array takes priority; otherwise fall back to single query shorthand.
|
|
6
6
|
let resolvedQueries;
|
|
7
7
|
if (input.queries && input.queries.length > 0) {
|
|
8
|
+
// FIELD BY FIELD, and every new field has to be added here.
|
|
9
|
+
//
|
|
10
|
+
// This is the only copy of the manifest the runtime ever sees — it is baked
|
|
11
|
+
// into the widget's entry HTML as `window.WidgetSandbox.manifest` — so a
|
|
12
|
+
// field dropped here fails in the most expensive way available: the widget
|
|
13
|
+
// loads, the query runs, and it reads the wrong thing. Losing `tool` turns
|
|
14
|
+
// a memory read into a cubby read with no SQL; losing `cubby` sends a query
|
|
15
|
+
// to the widget's default alias and shows an empty table that looks like
|
|
16
|
+
// "nothing happened yet".
|
|
8
17
|
resolvedQueries = input.queries.map((q) => ({
|
|
9
18
|
id: q.id,
|
|
10
19
|
label: q.label ?? q.id,
|
|
11
20
|
sql: q.sql,
|
|
12
21
|
timeoutMs: q.timeoutMs ?? 60000,
|
|
22
|
+
...(q.cubby ? { cubby: q.cubby } : {}),
|
|
23
|
+
...(q.tool ? { tool: q.tool } : {}),
|
|
24
|
+
...(q.limit !== undefined ? { limit: q.limit } : {}),
|
|
13
25
|
}));
|
|
14
26
|
}
|
|
15
27
|
else if (input.query) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { HostBridge } from './host-bridge.js';
|
|
1
|
+
import type { HostBridge, WidgetHostContext } from './host-bridge.js';
|
|
2
2
|
import type { StandaloneWallet } from './standalone-wallet.js';
|
|
3
3
|
/**
|
|
4
4
|
* A resolved signing identity plus its raw-bytes signer. Shared by both the
|
|
@@ -9,6 +9,26 @@ export interface ResolvedSigningIdentity {
|
|
|
9
9
|
publicKey: string;
|
|
10
10
|
sigType: 'ed25519' | 'sr25519';
|
|
11
11
|
sign: (bytes: Uint8Array) => Promise<Uint8Array>;
|
|
12
|
+
/**
|
|
13
|
+
* A bearer delegation the host supplied, when it did.
|
|
14
|
+
*
|
|
15
|
+
* PREFER IT OVER `sign` FOR EVERY VAULT REQUEST. On the passkey wallet a
|
|
16
|
+
* signature from a keyless session raises a step-up, so a widget that signs
|
|
17
|
+
* each request raises a ceremony each request — a polling query asks forever.
|
|
18
|
+
* A delegation is minted once by the host, with no ceremony at all, and
|
|
19
|
+
* authenticates exactly the same reads and writes.
|
|
20
|
+
*
|
|
21
|
+
* `sign` stays for hosts that supply no token, and for anything a delegation
|
|
22
|
+
* genuinely cannot carry.
|
|
23
|
+
*/
|
|
24
|
+
token?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Why the host opened this widget — the run being viewed, or the record.
|
|
27
|
+
*
|
|
28
|
+
* CONTEXT, NOT A COMMAND. A widget that ignores it behaves exactly as it does
|
|
29
|
+
* today, which is the compatibility story for every widget already deployed.
|
|
30
|
+
*/
|
|
31
|
+
context?: WidgetHostContext;
|
|
12
32
|
}
|
|
13
33
|
/**
|
|
14
34
|
* The widget is framed by a host, but the host never supplied an identity —
|
package/dist/resolve-identity.js
CHANGED
|
@@ -36,6 +36,8 @@ export function createIdentityResolver(deps) {
|
|
|
36
36
|
publicKey: hostId.pubkey,
|
|
37
37
|
sigType: hostId.sigType,
|
|
38
38
|
sign: (bytes) => deps.bridge.sign(bytes),
|
|
39
|
+
...(hostId.token ? { token: hostId.token } : {}),
|
|
40
|
+
...(hostId.context ? { context: hostId.context } : {}),
|
|
39
41
|
};
|
|
40
42
|
}
|
|
41
43
|
// Framed but unanswered: the host owns the identity. Do not downgrade.
|
package/dist/runtime.d.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import type { WidgetManifest, WidgetRuntimeApi } from './types.js';
|
|
2
|
+
import type { WidgetHostContext } from './host-bridge.js';
|
|
2
3
|
import type { VaultClient } from './vault-client.js';
|
|
3
4
|
export interface ResolvedRuntimeIdentity {
|
|
4
5
|
source: string;
|
|
5
6
|
publicKey: string;
|
|
6
7
|
sigType?: 'ed25519' | 'sr25519';
|
|
8
|
+
/** Why the host opened this widget — see `Identity.context`. */
|
|
9
|
+
context?: WidgetHostContext;
|
|
7
10
|
}
|
|
8
11
|
export interface RuntimeDeps {
|
|
9
12
|
resolveIdentity: (manifest: WidgetManifest) => Promise<ResolvedRuntimeIdentity>;
|
package/dist/runtime.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
export function createWidgetRuntime(manifest, deps) {
|
|
2
2
|
let status = 'anon';
|
|
3
3
|
let publicKey = '';
|
|
4
|
+
let context;
|
|
4
5
|
let clientP;
|
|
5
6
|
const subs = new Set();
|
|
6
7
|
const emit = (s) => {
|
|
7
8
|
status = s;
|
|
8
|
-
const snapshot = { publicKey, status };
|
|
9
|
+
const snapshot = { publicKey, status, ...(context ? { context } : {}) };
|
|
9
10
|
for (const cb of subs)
|
|
10
11
|
cb(snapshot);
|
|
11
12
|
};
|
|
@@ -14,6 +15,16 @@ export function createWidgetRuntime(manifest, deps) {
|
|
|
14
15
|
emit('connecting');
|
|
15
16
|
const id = await deps.resolveIdentity(manifest);
|
|
16
17
|
publicKey = id.publicKey;
|
|
18
|
+
/*
|
|
19
|
+
* THE RUN THE HOST OPENED THIS ON, kept so the widget can ask for it.
|
|
20
|
+
*
|
|
21
|
+
* It arrived on the handshake and stopped here: the resolver put it on
|
|
22
|
+
* the resolved identity and nothing carried it the last step into
|
|
23
|
+
* `identity()`. A widget could therefore be handed the execution it was
|
|
24
|
+
* opened for and have no way to read it — which is the entire reason the
|
|
25
|
+
* field exists.
|
|
26
|
+
*/
|
|
27
|
+
context = id.context;
|
|
17
28
|
const client = deps.buildVaultClient(manifest, id);
|
|
18
29
|
emit('ready');
|
|
19
30
|
return client;
|
|
@@ -27,8 +38,14 @@ export function createWidgetRuntime(manifest, deps) {
|
|
|
27
38
|
return {
|
|
28
39
|
async query(ref, params) { return (await ensureClient()).query(ref, params); },
|
|
29
40
|
async publish(type, payload, context) { return (await ensureClient()).publish(type, payload, context); },
|
|
30
|
-
async identity() {
|
|
31
|
-
|
|
41
|
+
async identity() {
|
|
42
|
+
await ensureClient();
|
|
43
|
+
return { publicKey, status, ...(context ? { context } : {}) };
|
|
44
|
+
},
|
|
45
|
+
async connect() {
|
|
46
|
+
await ensureClient();
|
|
47
|
+
return { publicKey, status, ...(context ? { context } : {}) };
|
|
48
|
+
},
|
|
32
49
|
async connectAgent() { await (await ensureClient()).connectAgent(); },
|
|
33
50
|
async agentStatus() { return (await ensureClient()).agentStatus(); },
|
|
34
51
|
onIdentityChange(cb) { subs.add(cb); return () => subs.delete(cb); },
|
package/dist/types.d.ts
CHANGED
|
@@ -1,3 +1,51 @@
|
|
|
1
|
+
import type { WidgetHostContext } from './host-bridge.js';
|
|
2
|
+
/**
|
|
3
|
+
* One named read a widget may perform.
|
|
4
|
+
*
|
|
5
|
+
* TWO PLACES TO READ FROM, because a widget has two honest questions and only
|
|
6
|
+
* one of them used to be answerable:
|
|
7
|
+
*
|
|
8
|
+
* the AGENT SERVICE's cubbies — what this agent and its siblings did. A
|
|
9
|
+
* workflow runner, a coordinator and three specialists are one service; a
|
|
10
|
+
* widget that could only read one of them could only tell part of the story.
|
|
11
|
+
* There is no separate per-agent store any more: `ctx.cubby` addresses the
|
|
12
|
+
* service, so the widget's own agent's rows are already in there.
|
|
13
|
+
*
|
|
14
|
+
* the vault's MEMORY BANK — what the organisation concluded, which outlives
|
|
15
|
+
* every run.
|
|
16
|
+
*
|
|
17
|
+
* `sql` means the first and `tool` means the second, and the two are mutually
|
|
18
|
+
* exclusive. There is no `source` discriminator: beside two already-disjoint
|
|
19
|
+
* fields it would only be a way for them to disagree.
|
|
20
|
+
*/
|
|
21
|
+
export interface WidgetQueryDecl {
|
|
22
|
+
id: string;
|
|
23
|
+
label: string;
|
|
24
|
+
/**
|
|
25
|
+
* SQL against one of the agent service's cubbies. Mutually exclusive with
|
|
26
|
+
* {@link tool}, and one of the two is what says where this query reads.
|
|
27
|
+
*/
|
|
28
|
+
sql?: string;
|
|
29
|
+
/** Which of the service's cubbies. Defaults to the widget's `cubbyAlias`. */
|
|
30
|
+
cubby?: string;
|
|
31
|
+
/**
|
|
32
|
+
* A read from the vault's memory bank, named rather than expressed as SQL —
|
|
33
|
+
* the bank is a gated graph API, not a table, and a SQL string would imply a
|
|
34
|
+
* reach it does not offer.
|
|
35
|
+
*
|
|
36
|
+
* `search` takes the match string as the first parameter; `get` and
|
|
37
|
+
* `neighbours` take a record id; `countByType` takes none.
|
|
38
|
+
*
|
|
39
|
+
* THERE IS NO THIRD PLACE TO READ FROM, so there is no `source` field to
|
|
40
|
+
* choose one. A widget sees the agent service's cubbies and the vault's
|
|
41
|
+
* memory; `sql` means the first and `tool` means the second. A discriminator
|
|
42
|
+
* beside two already-disjoint fields is a way for them to disagree.
|
|
43
|
+
*/
|
|
44
|
+
tool?: 'search' | 'get' | 'neighbours' | 'countByType';
|
|
45
|
+
/** `tool` only: row cap. The bank's own default applies when absent. */
|
|
46
|
+
limit?: number;
|
|
47
|
+
timeoutMs?: number;
|
|
48
|
+
}
|
|
1
49
|
export interface WidgetManifest {
|
|
2
50
|
schemaVersion: 1;
|
|
3
51
|
widgetId: string;
|
|
@@ -16,12 +64,7 @@ export interface WidgetManifest {
|
|
|
16
64
|
s3GatewayAuthInfo: string;
|
|
17
65
|
rpc: string;
|
|
18
66
|
};
|
|
19
|
-
queries:
|
|
20
|
-
id: string;
|
|
21
|
-
label: string;
|
|
22
|
-
sql: string;
|
|
23
|
-
timeoutMs?: number;
|
|
24
|
-
}>;
|
|
67
|
+
queries: WidgetQueryDecl[];
|
|
25
68
|
events?: Array<{
|
|
26
69
|
type: string;
|
|
27
70
|
schemaRef?: string;
|
|
@@ -33,6 +76,17 @@ export type IdentityStatus = 'anon' | 'connecting' | 'ready';
|
|
|
33
76
|
export interface Identity {
|
|
34
77
|
publicKey: string;
|
|
35
78
|
status: IdentityStatus;
|
|
79
|
+
/**
|
|
80
|
+
* WHY THE HOST OPENED THIS WIDGET — the run being viewed, or the record.
|
|
81
|
+
*
|
|
82
|
+
* Absent when the widget is standalone, or when the host said nothing, and
|
|
83
|
+
* ABSENT rather than empty: a widget doing `'context' in id` must be able to
|
|
84
|
+
* tell "the host told me nothing" from "the host told me something empty".
|
|
85
|
+
*
|
|
86
|
+
* This is what lets one widget serve two jobs: opened from the Widgets tab it
|
|
87
|
+
* shows the latest, opened from an execution it shows THAT execution.
|
|
88
|
+
*/
|
|
89
|
+
context?: WidgetHostContext;
|
|
36
90
|
}
|
|
37
91
|
export interface QueryResult {
|
|
38
92
|
columns: string[];
|
package/dist/vault-client.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type QueryResult, type WidgetManifest } from './types.js';
|
|
2
2
|
export interface VaultSdkLike {
|
|
3
3
|
vault: {
|
|
4
4
|
ensure: (opts?: {
|
|
5
5
|
onboard?: boolean;
|
|
6
|
+
delegationToken?: string;
|
|
6
7
|
}) => Promise<VaultLike>;
|
|
7
8
|
};
|
|
8
9
|
}
|
|
@@ -25,6 +26,31 @@ interface VaultLike {
|
|
|
25
26
|
eventId: string;
|
|
26
27
|
}>;
|
|
27
28
|
};
|
|
29
|
+
/**
|
|
30
|
+
* The agent-SERVICE cubbies: one database per service, global within the
|
|
31
|
+
* vault, so a widget reads what its agent's siblings wrote wherever the run
|
|
32
|
+
* came from. Optional on the structural type because a host running an older
|
|
33
|
+
* vault-sdk does not have `vault.service`.
|
|
34
|
+
*/
|
|
35
|
+
service?: (asPubKey: string) => {
|
|
36
|
+
cubby: (alias: string) => CubbyHandleLike;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* The vault's memory bank — the read half. Same `{columns, rows}` shape the
|
|
40
|
+
* cubby returns, which is why one normaliser serves both.
|
|
41
|
+
*/
|
|
42
|
+
memory?: MemoryLike;
|
|
43
|
+
}
|
|
44
|
+
/** The bank's read surface, as much of it as a widget can name. */
|
|
45
|
+
interface MemoryLike {
|
|
46
|
+
search: (match: string, opts?: {
|
|
47
|
+
limit?: number;
|
|
48
|
+
}) => Promise<unknown>;
|
|
49
|
+
get: (id: string) => Promise<unknown>;
|
|
50
|
+
neighbours: (id: string, opts?: {
|
|
51
|
+
limit?: number;
|
|
52
|
+
}) => Promise<unknown>;
|
|
53
|
+
countByType: () => Promise<unknown>;
|
|
28
54
|
}
|
|
29
55
|
interface CubbyHandleLike {
|
|
30
56
|
query: (sql: string, params?: unknown[]) => Promise<unknown>;
|
|
@@ -41,5 +67,24 @@ export interface VaultClient {
|
|
|
41
67
|
connectAgent(): Promise<void>;
|
|
42
68
|
agentStatus(): Promise<'connected' | 'disconnected'>;
|
|
43
69
|
}
|
|
44
|
-
export declare function createVaultClient(manifest: WidgetManifest, makeSdk: () => VaultSdkLike
|
|
70
|
+
export declare function createVaultClient(manifest: WidgetManifest, makeSdk: () => VaultSdkLike,
|
|
71
|
+
/**
|
|
72
|
+
* The host's delegation, when it gave one — handed to `ensure()` as well as
|
|
73
|
+
* used for auth.
|
|
74
|
+
*
|
|
75
|
+
* WHY BOTH. With a token the SDK is built with `auth` and NO `signer`, on
|
|
76
|
+
* purpose: the handle structurally cannot sign, which is what keeps a keyless
|
|
77
|
+
* passkey session from stepping up on every request. But `vault.ensure()`
|
|
78
|
+
* does not look at `auth` — it needs a `delegationToken` or a `signer`, and
|
|
79
|
+
* with neither it throws before the first query runs:
|
|
80
|
+
*
|
|
81
|
+
* vault.ensure() needs either a `delegationToken` or a `signer` configured
|
|
82
|
+
* on VaultSDKConfig (the signer drives the on-chain vault-delegate grant).
|
|
83
|
+
*
|
|
84
|
+
* So a framed widget with a host delegation — the configuration this whole
|
|
85
|
+
* tier is built around — could authenticate every request and never reach
|
|
86
|
+
* one. The token IS the authorization, which is exactly the case `ensure`
|
|
87
|
+
* documents `delegationToken` for.
|
|
88
|
+
*/
|
|
89
|
+
hostToken?: string): VaultClient;
|
|
45
90
|
export {};
|