@cef-ai/widget-runtime 1.1.1 → 2.1.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 +10 -1
- package/dist/bridge-wallet.js +19 -4
- package/dist/entry.js +58 -18
- package/dist/host-bridge.d.ts +84 -4
- package/dist/host-bridge.js +6 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/manifest.d.ts +4 -1
- package/dist/manifest.js +12 -0
- package/dist/mount.js +26 -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 +96 -2
- package/dist/vault-client.js +172 -59
- 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/vault-client.d.ts
CHANGED
|
@@ -1,11 +1,46 @@
|
|
|
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>;
|
|
8
|
+
/**
|
|
9
|
+
* SOMEBODY ELSE'S VAULT, by id — what `ensure()` structurally cannot give
|
|
10
|
+
* you. `ensure` claims the CALLER'S OWN vault, and the host's delegation
|
|
11
|
+
* authenticates the caller rather than naming a vault, so with only those
|
|
12
|
+
* two a widget opened on an org's run read the viewer's own database.
|
|
13
|
+
*
|
|
14
|
+
* OPTIONAL on this structural type because a host pinned to an older
|
|
15
|
+
* vault-sdk does not have it — and a widget the host named a vault for must
|
|
16
|
+
* then refuse rather than quietly read the wrong one.
|
|
17
|
+
*/
|
|
18
|
+
byId?: (vaultId: string) => Promise<VaultLike>;
|
|
7
19
|
};
|
|
8
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* The host named a vault this widget cannot open.
|
|
23
|
+
*
|
|
24
|
+
* WHY THIS IS AN ERROR AND NOT AN EMPTY RESULT, which is the whole point of the
|
|
25
|
+
* type. The failure it replaces was silent: the widget opened the wrong vault,
|
|
26
|
+
* queried it successfully, got zero rows, and rendered "No candidate has been
|
|
27
|
+
* reviewed yet" — about a run whose own `_cubbyQueries` log showed the INSERT
|
|
28
|
+
* landing with `rowsAffected: 1`. A confident lie, indistinguishable from the
|
|
29
|
+
* honest empty case, and it cost hours. So when the id is supplied and unusable
|
|
30
|
+
* the runtime says so instead of answering; there is no fallback path, because
|
|
31
|
+
* the fallback IS the bug.
|
|
32
|
+
*
|
|
33
|
+
* Shaped after `WidgetSignedOutError`: a named class whose message names itself
|
|
34
|
+
* first, so it reads the same in a console, in a rejected promise, and in the
|
|
35
|
+
* frame `mountKind` renders it into.
|
|
36
|
+
*/
|
|
37
|
+
export declare class WidgetVaultUnreachableError extends Error {
|
|
38
|
+
/** The vault the host named. Carried so a renderer can name it too. */
|
|
39
|
+
readonly vaultId: string;
|
|
40
|
+
/** What `vault.byId` said, when it said anything. */
|
|
41
|
+
readonly reason: string;
|
|
42
|
+
constructor(vaultId: string, cause?: unknown);
|
|
43
|
+
}
|
|
9
44
|
interface VaultLike {
|
|
10
45
|
id: string;
|
|
11
46
|
agents: {
|
|
@@ -25,6 +60,31 @@ interface VaultLike {
|
|
|
25
60
|
eventId: string;
|
|
26
61
|
}>;
|
|
27
62
|
};
|
|
63
|
+
/**
|
|
64
|
+
* The agent-SERVICE cubbies: one database per service, global within the
|
|
65
|
+
* vault, so a widget reads what its agent's siblings wrote wherever the run
|
|
66
|
+
* came from. Optional on the structural type because a host running an older
|
|
67
|
+
* vault-sdk does not have `vault.service`.
|
|
68
|
+
*/
|
|
69
|
+
service?: (asPubKey: string) => {
|
|
70
|
+
cubby: (alias: string) => CubbyHandleLike;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* The vault's memory bank — the read half. Same `{columns, rows}` shape the
|
|
74
|
+
* cubby returns, which is why one normaliser serves both.
|
|
75
|
+
*/
|
|
76
|
+
memory?: MemoryLike;
|
|
77
|
+
}
|
|
78
|
+
/** The bank's read surface, as much of it as a widget can name. */
|
|
79
|
+
interface MemoryLike {
|
|
80
|
+
search: (match: string, opts?: {
|
|
81
|
+
limit?: number;
|
|
82
|
+
}) => Promise<unknown>;
|
|
83
|
+
get: (id: string) => Promise<unknown>;
|
|
84
|
+
neighbours: (id: string, opts?: {
|
|
85
|
+
limit?: number;
|
|
86
|
+
}) => Promise<unknown>;
|
|
87
|
+
countByType: () => Promise<unknown>;
|
|
28
88
|
}
|
|
29
89
|
interface CubbyHandleLike {
|
|
30
90
|
query: (sql: string, params?: unknown[]) => Promise<unknown>;
|
|
@@ -41,5 +101,39 @@ export interface VaultClient {
|
|
|
41
101
|
connectAgent(): Promise<void>;
|
|
42
102
|
agentStatus(): Promise<'connected' | 'disconnected'>;
|
|
43
103
|
}
|
|
44
|
-
export declare function createVaultClient(manifest: WidgetManifest, makeSdk: () => VaultSdkLike
|
|
104
|
+
export declare function createVaultClient(manifest: WidgetManifest, makeSdk: () => VaultSdkLike,
|
|
105
|
+
/**
|
|
106
|
+
* The host's delegation, when it gave one — handed to `ensure()` as well as
|
|
107
|
+
* used for auth.
|
|
108
|
+
*
|
|
109
|
+
* WHY BOTH. With a token the SDK is built with `auth` and NO `signer`, on
|
|
110
|
+
* purpose: the handle structurally cannot sign, which is what keeps a keyless
|
|
111
|
+
* passkey session from stepping up on every request. But `vault.ensure()`
|
|
112
|
+
* does not look at `auth` — it needs a `delegationToken` or a `signer`, and
|
|
113
|
+
* with neither it throws before the first query runs:
|
|
114
|
+
*
|
|
115
|
+
* vault.ensure() needs either a `delegationToken` or a `signer` configured
|
|
116
|
+
* on VaultSDKConfig (the signer drives the on-chain vault-delegate grant).
|
|
117
|
+
*
|
|
118
|
+
* So a framed widget with a host delegation — the configuration this whole
|
|
119
|
+
* tier is built around — could authenticate every request and never reach
|
|
120
|
+
* one. The token IS the authorization, which is exactly the case `ensure`
|
|
121
|
+
* documents `delegationToken` for.
|
|
122
|
+
*/
|
|
123
|
+
hostToken?: string,
|
|
124
|
+
/**
|
|
125
|
+
* WHICH VAULT, when the host named one (`WidgetHostContext.vaultId`).
|
|
126
|
+
*
|
|
127
|
+
* WHY IT IS SEPARATE FROM THE TOKEN, which is the mistake this fixes. The
|
|
128
|
+
* host already passed a credential, so the widget authenticated fine and
|
|
129
|
+
* raised no passkey — but a delegation says WHO, not WHICH VAULT, and
|
|
130
|
+
* `ensure()` claims the caller's OWN. A workflow runs in the vault of the org
|
|
131
|
+
* that owns its agent service, so for a member of somebody else's service the
|
|
132
|
+
* two are different databases and the widget read the empty one.
|
|
133
|
+
*
|
|
134
|
+
* ABSENT IS THE DEFAULT AND STAYS EXACTLY AS IT WAS: `ensure({onboard:false})`
|
|
135
|
+
* with the host's delegation. Present means open THAT vault and, if it cannot
|
|
136
|
+
* be opened, refuse — see {@link WidgetVaultUnreachableError}.
|
|
137
|
+
*/
|
|
138
|
+
hostVaultId?: string): VaultClient;
|
|
45
139
|
export {};
|
package/dist/vault-client.js
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
// packages/widget-runtime/src/vault-client.ts
|
|
2
|
-
import {
|
|
2
|
+
import {} from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* The host named a vault this widget cannot open.
|
|
5
|
+
*
|
|
6
|
+
* WHY THIS IS AN ERROR AND NOT AN EMPTY RESULT, which is the whole point of the
|
|
7
|
+
* type. The failure it replaces was silent: the widget opened the wrong vault,
|
|
8
|
+
* queried it successfully, got zero rows, and rendered "No candidate has been
|
|
9
|
+
* reviewed yet" — about a run whose own `_cubbyQueries` log showed the INSERT
|
|
10
|
+
* landing with `rowsAffected: 1`. A confident lie, indistinguishable from the
|
|
11
|
+
* honest empty case, and it cost hours. So when the id is supplied and unusable
|
|
12
|
+
* the runtime says so instead of answering; there is no fallback path, because
|
|
13
|
+
* the fallback IS the bug.
|
|
14
|
+
*
|
|
15
|
+
* Shaped after `WidgetSignedOutError`: a named class whose message names itself
|
|
16
|
+
* first, so it reads the same in a console, in a rejected promise, and in the
|
|
17
|
+
* frame `mountKind` renders it into.
|
|
18
|
+
*/
|
|
19
|
+
export class WidgetVaultUnreachableError extends Error {
|
|
20
|
+
constructor(vaultId, cause) {
|
|
21
|
+
const reason = cause === undefined
|
|
22
|
+
? 'this build of the runtime has no vault.byId to open it with'
|
|
23
|
+
: (cause instanceof Error ? cause.message : String(cause));
|
|
24
|
+
super(`WidgetVaultUnreachableError: the host opened this widget on vault ${vaultId}, `
|
|
25
|
+
+ `and it cannot be read from here (${reason}). Falling back to your own vault would `
|
|
26
|
+
+ 'answer from a different database — which looks exactly like a run that produced nothing.');
|
|
27
|
+
this.name = 'WidgetVaultUnreachableError';
|
|
28
|
+
this.vaultId = vaultId;
|
|
29
|
+
this.reason = reason;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
3
32
|
/**
|
|
4
33
|
* Normalise the raw result from cubbies.query into a uniform QueryResult.
|
|
5
34
|
* The platform returns `{columns: string[], rows: unknown[][]}` (positional).
|
|
@@ -23,78 +52,163 @@ function normaliseQueryResult(raw) {
|
|
|
23
52
|
console.warn('[widget-runtime] unrecognized cubby query result shape; treating as empty', raw);
|
|
24
53
|
return { columns: [], rows: [], meta: { rowCount: 0 } };
|
|
25
54
|
}
|
|
55
|
+
export function createVaultClient(manifest, makeSdk,
|
|
26
56
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
57
|
+
* The host's delegation, when it gave one — handed to `ensure()` as well as
|
|
58
|
+
* used for auth.
|
|
59
|
+
*
|
|
60
|
+
* WHY BOTH. With a token the SDK is built with `auth` and NO `signer`, on
|
|
61
|
+
* purpose: the handle structurally cannot sign, which is what keeps a keyless
|
|
62
|
+
* passkey session from stepping up on every request. But `vault.ensure()`
|
|
63
|
+
* does not look at `auth` — it needs a `delegationToken` or a `signer`, and
|
|
64
|
+
* with neither it throws before the first query runs:
|
|
65
|
+
*
|
|
66
|
+
* vault.ensure() needs either a `delegationToken` or a `signer` configured
|
|
67
|
+
* on VaultSDKConfig (the signer drives the on-chain vault-delegate grant).
|
|
68
|
+
*
|
|
69
|
+
* So a framed widget with a host delegation — the configuration this whole
|
|
70
|
+
* tier is built around — could authenticate every request and never reach
|
|
71
|
+
* one. The token IS the authorization, which is exactly the case `ensure`
|
|
72
|
+
* documents `delegationToken` for.
|
|
29
73
|
*/
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
74
|
+
hostToken,
|
|
75
|
+
/**
|
|
76
|
+
* WHICH VAULT, when the host named one (`WidgetHostContext.vaultId`).
|
|
77
|
+
*
|
|
78
|
+
* WHY IT IS SEPARATE FROM THE TOKEN, which is the mistake this fixes. The
|
|
79
|
+
* host already passed a credential, so the widget authenticated fine and
|
|
80
|
+
* raised no passkey — but a delegation says WHO, not WHICH VAULT, and
|
|
81
|
+
* `ensure()` claims the caller's OWN. A workflow runs in the vault of the org
|
|
82
|
+
* that owns its agent service, so for a member of somebody else's service the
|
|
83
|
+
* two are different databases and the widget read the empty one.
|
|
84
|
+
*
|
|
85
|
+
* ABSENT IS THE DEFAULT AND STAYS EXACTLY AS IT WAS: `ensure({onboard:false})`
|
|
86
|
+
* with the host's delegation. Present means open THAT vault and, if it cannot
|
|
87
|
+
* be opened, refuse — see {@link WidgetVaultUnreachableError}.
|
|
88
|
+
*/
|
|
89
|
+
hostVaultId) {
|
|
42
90
|
let sdk;
|
|
43
91
|
let vaultP;
|
|
44
|
-
let agentHandleP;
|
|
45
92
|
const getSdk = () => (sdk ?? (sdk = makeSdk()));
|
|
93
|
+
/**
|
|
94
|
+
* THE VAULT THIS WIDGET READS — the one the host named, else the caller's.
|
|
95
|
+
*
|
|
96
|
+
* There is deliberately no third branch. A named vault that will not open is
|
|
97
|
+
* an error, never a quiet fall-through to `ensure()`: the fall-through is
|
|
98
|
+
* indistinguishable from an empty result, and being indistinguishable from an
|
|
99
|
+
* empty result is the bug.
|
|
100
|
+
*/
|
|
101
|
+
const openVault = async () => {
|
|
102
|
+
if (!hostVaultId) {
|
|
103
|
+
// `onboard: false` preserves the old lightweight ensure(): claim the vault
|
|
104
|
+
// with a wallet-built delegation token, skipping the gateway onboarding
|
|
105
|
+
// status/bootstrap round-trip (widgets assume a provisioned wallet).
|
|
106
|
+
return getSdk().vault.ensure({
|
|
107
|
+
onboard: false,
|
|
108
|
+
...(hostToken ? { delegationToken: hostToken } : {}),
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
const vaultNs = getSdk().vault;
|
|
112
|
+
if (typeof vaultNs.byId !== 'function') {
|
|
113
|
+
throw new WidgetVaultUnreachableError(hostVaultId);
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
return await vaultNs.byId(hostVaultId);
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
// Not found, no access, network — all one answer to the reader: this is
|
|
120
|
+
// not your vault and nothing here can show you what is in it.
|
|
121
|
+
throw new WidgetVaultUnreachableError(hostVaultId, err);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
46
124
|
const getVault = () => {
|
|
47
|
-
|
|
48
|
-
// with a wallet-built delegation token, skipping the gateway onboarding
|
|
49
|
-
// status/bootstrap round-trip (widgets assume a provisioned wallet).
|
|
50
|
-
vaultP ?? (vaultP = getSdk().vault.ensure({ onboard: false }).catch((err) => {
|
|
125
|
+
vaultP ?? (vaultP = openVault().catch((err) => {
|
|
51
126
|
vaultP = undefined;
|
|
52
127
|
throw err;
|
|
53
128
|
}));
|
|
54
129
|
return vaultP;
|
|
55
130
|
};
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
131
|
+
const declFor = (ref) => manifest.queries.find((q) => q.id === ref);
|
|
132
|
+
/**
|
|
133
|
+
* The agent service a widget belongs to — the half of its agent id before the
|
|
134
|
+
* colon.
|
|
135
|
+
*
|
|
136
|
+
* DERIVED, never declared. A widget that could name its own service could
|
|
137
|
+
* name somebody else's, and then a query's reach would be a field in a file
|
|
138
|
+
* uploaded to a content gateway rather than a property of who published it.
|
|
139
|
+
* This way the only service a widget can read is the one that shipped it.
|
|
140
|
+
*/
|
|
141
|
+
const ownService = () => {
|
|
142
|
+
const at = manifest.agentId.indexOf(':');
|
|
143
|
+
return at > 0 ? manifest.agentId.slice(0, at) : '';
|
|
144
|
+
};
|
|
145
|
+
/** Read the service's cubby — what this agent's siblings wrote. The default. */
|
|
146
|
+
const queryService = async (decl, params) => {
|
|
147
|
+
const vault = await getVault();
|
|
148
|
+
if (!vault.service) {
|
|
149
|
+
throw new Error('[widget-runtime] this host\'s vault-sdk has no agent-service cubby surface, '
|
|
150
|
+
+ `so the query "${decl.id}" cannot be answered here`);
|
|
151
|
+
}
|
|
152
|
+
const asPubKey = ownService();
|
|
153
|
+
if (!asPubKey) {
|
|
154
|
+
throw new Error(`[widget-runtime] "${decl.id}" reads the agent service's cubby, but `
|
|
155
|
+
+ `"${manifest.agentId}" carries no service key to read it from`);
|
|
156
|
+
}
|
|
157
|
+
// WHICH of the service's cubbies. `cubbyAlias` is the default, and it is
|
|
158
|
+
// legitimately EMPTY for a widget that declares no cubby of its own —
|
|
159
|
+
// `buildWidgetManifestForDecl` writes "" in that case — so a `service`
|
|
160
|
+
// query that also names none has no alias at all. Left unguarded that
|
|
161
|
+
// reaches vault-api as `/cubbies//query` and comes back as an alias-shape
|
|
162
|
+
// 400, which sends the author to look at their SQL.
|
|
163
|
+
const alias = decl.cubby ?? manifest.cubbyAlias;
|
|
164
|
+
if (!alias) {
|
|
165
|
+
throw new Error(`[widget-runtime] "${decl.id}" reads the agent service's cubby but names `
|
|
166
|
+
+ 'none, and this widget has no cubbyAlias to fall back on — set `cubby` on the query');
|
|
167
|
+
}
|
|
168
|
+
return vault.service(asPubKey).cubby(alias).query(decl.sql ?? '', params);
|
|
169
|
+
};
|
|
170
|
+
/**
|
|
171
|
+
* Read the vault's memory bank.
|
|
172
|
+
*
|
|
173
|
+
* Named tools rather than SQL, because the bank is a gated graph API and not
|
|
174
|
+
* a table: the gate decides which records the caller may see, and a SQL
|
|
175
|
+
* string would imply a reach the surface does not offer.
|
|
176
|
+
*/
|
|
177
|
+
const queryMemory = async (decl, params) => {
|
|
178
|
+
const vault = await getVault();
|
|
179
|
+
if (!vault.memory) {
|
|
180
|
+
throw new Error('[widget-runtime] this host\'s vault-sdk has no memory surface, '
|
|
181
|
+
+ `so the query "${decl.id}" cannot be answered here`);
|
|
182
|
+
}
|
|
183
|
+
const first = params[0];
|
|
184
|
+
const limit = decl.limit;
|
|
185
|
+
switch (decl.tool ?? 'search') {
|
|
186
|
+
case 'search':
|
|
187
|
+
return vault.memory.search(String(first ?? ''), limit ? { limit } : undefined);
|
|
188
|
+
case 'get':
|
|
189
|
+
return vault.memory.get(String(first ?? ''));
|
|
190
|
+
case 'neighbours':
|
|
191
|
+
return vault.memory.neighbours(String(first ?? ''), limit ? { limit } : undefined);
|
|
192
|
+
case 'countByType':
|
|
193
|
+
return vault.memory.countByType();
|
|
194
|
+
default:
|
|
195
|
+
throw new Error(`[widget-runtime] "${decl.id}" names no memory tool this runtime knows`);
|
|
196
|
+
}
|
|
68
197
|
};
|
|
69
|
-
const resolveSql = (ref) => manifest.queries.find((q) => q.id === ref)?.sql ?? ref;
|
|
70
198
|
return {
|
|
71
199
|
async query(ref, params) {
|
|
72
|
-
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
let cubbyHandle;
|
|
83
|
-
try {
|
|
84
|
-
cubbyHandle = handle.cubby(manifest.cubbyAlias);
|
|
85
|
-
}
|
|
86
|
-
catch {
|
|
87
|
-
throw new AgentNotConnectedError(manifest.agentId);
|
|
88
|
-
}
|
|
89
|
-
raw = await cubbyHandle.query(resolveSql(ref), params ?? []);
|
|
90
|
-
}
|
|
91
|
-
catch (err) {
|
|
92
|
-
agentHandleP = undefined; // force a fresh handle on the next read
|
|
93
|
-
if (err instanceof AgentNotConnectedError)
|
|
94
|
-
throw err;
|
|
95
|
-
mapQueryError(err, manifest.agentId);
|
|
200
|
+
const decl = declFor(ref);
|
|
201
|
+
// TWO PLACES, AND THE DECLARATION SAYS WHICH. `tool` is the memory bank;
|
|
202
|
+
// anything else is SQL against one of the agent service's cubbies. A ref
|
|
203
|
+
// matching no declaration is raw SQL, which is a cubby read.
|
|
204
|
+
//
|
|
205
|
+
// Neither is agent-connection shaped — a cubby read addresses the
|
|
206
|
+
// SERVICE, the bank addresses the vault — so neither can fail as "agent
|
|
207
|
+
// not connected".
|
|
208
|
+
if (decl?.tool) {
|
|
209
|
+
return normaliseQueryResult(await queryMemory(decl, params ?? []));
|
|
96
210
|
}
|
|
97
|
-
return normaliseQueryResult(
|
|
211
|
+
return normaliseQueryResult(await queryService(decl ?? { id: ref, label: ref, sql: ref }, params ?? []));
|
|
98
212
|
},
|
|
99
213
|
async publish(type, payload, context) {
|
|
100
214
|
const vault = await getVault();
|
|
@@ -106,7 +220,6 @@ export function createVaultClient(manifest, makeSdk) {
|
|
|
106
220
|
// The vault-api resolves the manifest from the DDC registry and the SDK
|
|
107
221
|
// signs the GAR agreement internally — no marketplace.getAgent() fetch.
|
|
108
222
|
await vault.agents.connect({ agentId: manifest.agentId, scope: manifest.scope });
|
|
109
|
-
agentHandleP = undefined; // drop any cached pre-connect handle
|
|
110
223
|
},
|
|
111
224
|
async agentStatus() {
|
|
112
225
|
try {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { VaultSDKConfig, Signer } from '@cef-ai/vault-sdk';
|
|
2
|
+
import type { WidgetManifest } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Build the VaultSDK config the runtime constructs everywhere it talks to a
|
|
5
|
+
* user's vault — `buildVaultClient`'s SDK (queries/publish/connect) and the
|
|
6
|
+
* ADR-002 audio uploader's SDK. One builder so the two cannot drift.
|
|
7
|
+
*
|
|
8
|
+
* A TOKEN BEATS A SIGNER, when the host supplied one. `auth` overrides
|
|
9
|
+
* `signer` in `VaultSDKConfig`, so a config built with a delegation provider
|
|
10
|
+
* structurally CANNOT sign — which is the point: on the passkey wallet every
|
|
11
|
+
* signature from a keyless session raises a step-up, so a widget that signs per
|
|
12
|
+
* request raises a ceremony per request. The delegation was minted once, by a
|
|
13
|
+
* host that already had a session, and authenticates the same calls.
|
|
14
|
+
*
|
|
15
|
+
* `chainUrl` (the manifest's `endpoints.rpc`) is required by
|
|
16
|
+
* `vault.ensure()`'s wallet path: the SDK grants vault-api durable write
|
|
17
|
+
* delegation by submitting an on-chain `NonTransfer` proxy before claiming
|
|
18
|
+
* the vault, and needs a Cere node WS URL to do it. Both runtime call sites
|
|
19
|
+
* use `ensure({ onboard: false })` — that skips the gateway onboarding
|
|
20
|
+
* round-trip but NOT the grant step, so without `chainUrl` every widget boot
|
|
21
|
+
* would die on an actionable-but-user-visible config error. The manifest's
|
|
22
|
+
* `rpc` is baked per environment by the CLI's env table (devnet / testnet /
|
|
23
|
+
* mainnet), so the runtime inherits the deployment's chain instead of
|
|
24
|
+
* hard-coding one.
|
|
25
|
+
*/
|
|
26
|
+
export declare function toVaultSdkConfig(endpoints: WidgetManifest['endpoints'], signer: Signer, token?: string): VaultSDKConfig;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// packages/widget-runtime/src/vault-sdk-config.ts
|
|
2
|
+
import { DelegationAuthProvider } from '@cef-ai/vault-sdk';
|
|
3
|
+
/**
|
|
4
|
+
* Build the VaultSDK config the runtime constructs everywhere it talks to a
|
|
5
|
+
* user's vault — `buildVaultClient`'s SDK (queries/publish/connect) and the
|
|
6
|
+
* ADR-002 audio uploader's SDK. One builder so the two cannot drift.
|
|
7
|
+
*
|
|
8
|
+
* A TOKEN BEATS A SIGNER, when the host supplied one. `auth` overrides
|
|
9
|
+
* `signer` in `VaultSDKConfig`, so a config built with a delegation provider
|
|
10
|
+
* structurally CANNOT sign — which is the point: on the passkey wallet every
|
|
11
|
+
* signature from a keyless session raises a step-up, so a widget that signs per
|
|
12
|
+
* request raises a ceremony per request. The delegation was minted once, by a
|
|
13
|
+
* host that already had a session, and authenticates the same calls.
|
|
14
|
+
*
|
|
15
|
+
* `chainUrl` (the manifest's `endpoints.rpc`) is required by
|
|
16
|
+
* `vault.ensure()`'s wallet path: the SDK grants vault-api durable write
|
|
17
|
+
* delegation by submitting an on-chain `NonTransfer` proxy before claiming
|
|
18
|
+
* the vault, and needs a Cere node WS URL to do it. Both runtime call sites
|
|
19
|
+
* use `ensure({ onboard: false })` — that skips the gateway onboarding
|
|
20
|
+
* round-trip but NOT the grant step, so without `chainUrl` every widget boot
|
|
21
|
+
* would die on an actionable-but-user-visible config error. The manifest's
|
|
22
|
+
* `rpc` is baked per environment by the CLI's env table (devnet / testnet /
|
|
23
|
+
* mainnet), so the runtime inherits the deployment's chain instead of
|
|
24
|
+
* hard-coding one.
|
|
25
|
+
*/
|
|
26
|
+
export function toVaultSdkConfig(endpoints, signer, token) {
|
|
27
|
+
const base = {
|
|
28
|
+
endpoint: endpoints.vault,
|
|
29
|
+
garEndpoint: endpoints.gar,
|
|
30
|
+
marketplaceEndpoint: endpoints.marketplace,
|
|
31
|
+
s3GatewayAuthInfoUrl: endpoints.s3GatewayAuthInfo,
|
|
32
|
+
chainUrl: endpoints.rpc,
|
|
33
|
+
};
|
|
34
|
+
if (token)
|
|
35
|
+
return { ...base, auth: new DelegationAuthProvider(token) };
|
|
36
|
+
return { ...base, signer };
|
|
37
|
+
}
|