@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.
@@ -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
- // `address` is set to the hex pubkey (NOT an SS58 address). vault-sdk's
40
- // WalletAuthProvider reads `publicKey`/`type`, not `address`, to build the
41
- // auth headers — matching the toClientSdkWallet note below.
42
- address: pubkey,
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
- const makeSdk = () => new VaultSDK({
56
- endpoint: m.endpoints.vault,
57
- garEndpoint: m.endpoints.gar,
58
- marketplaceEndpoint: m.endpoints.marketplace,
59
- s3GatewayAuthInfoUrl: m.endpoints.s3GatewayAuthInfo,
60
- signer: vaultSdkSigner,
61
- });
62
- return createVaultClient(m, makeSdk);
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
- const sdk = new VaultSDK({
89
- endpoint: manifest.endpoints.vault,
90
- garEndpoint: manifest.endpoints.gar,
91
- marketplaceEndpoint: manifest.endpoints.marketplace,
92
- s3GatewayAuthInfoUrl: manifest.endpoints.s3GatewayAuthInfo,
93
- signer: vaultSdkSigner,
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
- void import('./mount.js').then(({ mountKind }) => mountKind(app, manifest.config, window.WidgetRuntime, mountDeps));
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
  }
@@ -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).
@@ -175,7 +175,12 @@ async function requestIdentity() {
175
175
  scheduleRetryOrTimeout();
176
176
  return;
177
177
  }
178
- const result = { pubkey: d.pubkey, sigType: d.sigType ?? 'ed25519' };
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);
@@ -20,7 +20,10 @@ export interface BuildManifestInput {
20
20
  queries?: Array<{
21
21
  id: string;
22
22
  label?: string;
23
- sql: string;
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 —
@@ -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() { await ensureClient(); return { publicKey, status }; },
31
- async connect() { await ensureClient(); return { publicKey, status }; },
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: Array<{
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[];
@@ -1,8 +1,9 @@
1
- import { type WidgetManifest, type QueryResult } from './types.js';
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): VaultClient;
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 {};