@ixo/oracle-runtime-workers 0.2.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 +110 -0
- package/package.json +77 -0
- package/src/attachments/classify.ts +114 -0
- package/src/attachments/content-blocks.ts +51 -0
- package/src/attachments/download.ts +235 -0
- package/src/attachments/extract.ts +241 -0
- package/src/attachments/index.ts +84 -0
- package/src/attachments/magic.ts +133 -0
- package/src/attachments/pipeline.ts +409 -0
- package/src/attachments/retention.ts +205 -0
- package/src/attachments/route.ts +41 -0
- package/src/attachments/sandbox-archive.ts +90 -0
- package/src/attachments/sanitize.ts +35 -0
- package/src/attachments/types.ts +86 -0
- package/src/attachments/view.ts +107 -0
- package/src/core/env.ts +200 -0
- package/src/core/index.ts +513 -0
- package/src/core/llm.ts +842 -0
- package/src/core/main-agent-types.ts +123 -0
- package/src/core/main-agent.ts +399 -0
- package/src/core/manifest.ts +365 -0
- package/src/core/meta-tools.ts +232 -0
- package/src/core/middlewares/byo-history-sanitizer.ts +165 -0
- package/src/core/middlewares/capability-gate.ts +76 -0
- package/src/core/middlewares/index.ts +18 -0
- package/src/core/middlewares/page-context.ts +98 -0
- package/src/core/middlewares/safety-guardrail.ts +117 -0
- package/src/core/middlewares/summarization.ts +99 -0
- package/src/core/middlewares/tool-repetition-guard.ts +125 -0
- package/src/core/middlewares/tool-validation.ts +83 -0
- package/src/core/middlewares/work-status.ts +57 -0
- package/src/core/openrouter-pricing.ts +124 -0
- package/src/core/plugin-loader.ts +192 -0
- package/src/core/plugins/index.ts +6 -0
- package/src/core/plugins/skills/index.ts +8 -0
- package/src/core/plugins/skills/skills-tools.ts +356 -0
- package/src/core/plugins/skills/skills.plugin.ts +113 -0
- package/src/core/plugins/weather/index.ts +8 -0
- package/src/core/plugins/weather/weather-client.ts +153 -0
- package/src/core/plugins/weather/weather-middleware.ts +27 -0
- package/src/core/plugins/weather/weather-sub-agent.ts +65 -0
- package/src/core/plugins/weather/weather-tools.ts +103 -0
- package/src/core/plugins/weather/weather.plugin.ts +227 -0
- package/src/core/prompt-composer.ts +469 -0
- package/src/core/registries.ts +642 -0
- package/src/core/runtime-context.ts +619 -0
- package/src/core/state.ts +108 -0
- package/src/core/sub-agent-fallback.ts +187 -0
- package/src/core/subagent-as-tool.ts +322 -0
- package/src/core/template.ts +245 -0
- package/src/core/utils.ts +246 -0
- package/src/core/wrap-plugin-tool.ts +100 -0
- package/src/do/ambient.ts +266 -0
- package/src/do/contracts.ts +562 -0
- package/src/do/debug-timers.ts +120 -0
- package/src/do/fresh-stub.ts +32 -0
- package/src/do/gateway-retry.ts +73 -0
- package/src/do/secrets-adapter.ts +32 -0
- package/src/do/size-alerts.ts +76 -0
- package/src/do/sse-stream.ts +472 -0
- package/src/do/transcript.ts +192 -0
- package/src/do/turn-body.ts +84 -0
- package/src/do/turn-metadata.ts +146 -0
- package/src/do/ucan-service.ts +339 -0
- package/src/do/user-oracle-do.ts +3035 -0
- package/src/gateway-worker.ts +82 -0
- package/src/index.ts +237 -0
- package/src/llm/byo-adapter.ts +59 -0
- package/src/llm/byo-catalog.ts +520 -0
- package/src/llm/byo-client.ts +280 -0
- package/src/llm/byo-routes.ts +255 -0
- package/src/llm/byo-service.ts +836 -0
- package/src/llm/chatgpt-oauth.ts +430 -0
- package/src/llm/provider-error.ts +283 -0
- package/src/matrix/gateway-do.ts +918 -0
- package/src/matrix/ingest.ts +209 -0
- package/src/matrix/replay-format.ts +34 -0
- package/src/matrix/reply-chain.ts +109 -0
- package/src/matrix/room-state-codec.ts +115 -0
- package/src/matrix/user-homeserver.ts +106 -0
- package/src/matrix/work-status.ts +294 -0
- package/src/memory/session-history-indexer.ts +328 -0
- package/src/owner-store/ixo-vfs-store.ts +570 -0
- package/src/owner-store/matrix-media-store.ts +136 -0
- package/src/owner-store/migrating-store.ts +185 -0
- package/src/owner-store/owner-copy-errors.ts +175 -0
- package/src/owner-store/retry.ts +45 -0
- package/src/owner-store/tus-upload.ts +333 -0
- package/src/owner-store/types.ts +0 -0
- package/src/plugin-api/define-plugin.ts +45 -0
- package/src/plugin-api/index.ts +56 -0
- package/src/plugin-api/oracle-plugin.ts +145 -0
- package/src/plugin-api/tool-helper.ts +82 -0
- package/src/plugin-api/types.ts +816 -0
- package/src/plugin-api/ucan-errors.ts +16 -0
- package/src/plugins/agui/agui-agent.ts +115 -0
- package/src/plugins/agui/agui.plugin.ts +148 -0
- package/src/plugins/agui/index.ts +2 -0
- package/src/plugins/attachments/attachments.plugin.ts +54 -0
- package/src/plugins/attachments/index.ts +6 -0
- package/src/plugins/attachments/view-attachment-tool.ts +91 -0
- package/src/plugins/base64.ts +26 -0
- package/src/plugins/composio/composio-tools.ts +365 -0
- package/src/plugins/composio/composio-ucan.ts +43 -0
- package/src/plugins/composio/composio.plugin.ts +214 -0
- package/src/plugins/composio/index.ts +15 -0
- package/src/plugins/domain-indexer/domain-indexer-agent.ts +57 -0
- package/src/plugins/domain-indexer/domain-indexer-tools.ts +253 -0
- package/src/plugins/domain-indexer/domain-indexer.plugin.ts +100 -0
- package/src/plugins/domain-indexer/index.ts +3 -0
- package/src/plugins/editor/blocknote-bridge.ts +120 -0
- package/src/plugins/editor/content-session.ts +331 -0
- package/src/plugins/editor/content-tools.ts +686 -0
- package/src/plugins/editor/document-model.ts +0 -0
- package/src/plugins/editor/editor-access-denied-tool.ts +58 -0
- package/src/plugins/editor/editor-agent.ts +34 -0
- package/src/plugins/editor/editor-config.ts +64 -0
- package/src/plugins/editor/editor-mx.ts +128 -0
- package/src/plugins/editor/editor.plugin.ts +143 -0
- package/src/plugins/editor/failures.ts +154 -0
- package/src/plugins/editor/index.ts +31 -0
- package/src/plugins/editor/jsdom-shim.ts +55 -0
- package/src/plugins/editor/prompts.ts +59 -0
- package/src/plugins/editor/prop-policy.ts +147 -0
- package/src/plugins/editor/provider.ts +386 -0
- package/src/plugins/editor/room-membership.ts +95 -0
- package/src/plugins/editor/standalone-editor-tool.ts +200 -0
- package/src/plugins/firecrawl/firecrawl-agent.ts +93 -0
- package/src/plugins/firecrawl/firecrawl-tools.ts +220 -0
- package/src/plugins/firecrawl/firecrawl.plugin.ts +105 -0
- package/src/plugins/firecrawl/index.ts +15 -0
- package/src/plugins/flows/action-metadata.ts +3505 -0
- package/src/plugins/flows/actions.ts +190 -0
- package/src/plugins/flows/edit.ts +314 -0
- package/src/plugins/flows/errors.ts +38 -0
- package/src/plugins/flows/explain.ts +43 -0
- package/src/plugins/flows/flow-doc.ts +111 -0
- package/src/plugins/flows/flows.plugin.ts +129 -0
- package/src/plugins/flows/forms.ts +330 -0
- package/src/plugins/flows/index.ts +2 -0
- package/src/plugins/flows/linkage.ts +147 -0
- package/src/plugins/flows/port-types.ts +53 -0
- package/src/plugins/flows/prompts.ts +65 -0
- package/src/plugins/flows/read.ts +392 -0
- package/src/plugins/flows/references.ts +66 -0
- package/src/plugins/flows/test-support.ts +165 -0
- package/src/plugins/flows/tools/authoring.ts +516 -0
- package/src/plugins/flows/tools/discovery.ts +119 -0
- package/src/plugins/flows/tools/forms.ts +142 -0
- package/src/plugins/flows/tools/inspect.ts +165 -0
- package/src/plugins/flows/tools/linkage.ts +117 -0
- package/src/plugins/flows/tools/settings.ts +184 -0
- package/src/plugins/flows/translator.ts +328 -0
- package/src/plugins/flows/types.ts +210 -0
- package/src/plugins/flows/ydoc-helpers.ts +543 -0
- package/src/plugins/index.ts +70 -0
- package/src/plugins/mcp-call-timeout.ts +38 -0
- package/src/plugins/mcp-tool-adapter.ts +87 -0
- package/src/plugins/memory/index.ts +17 -0
- package/src/plugins/memory/memory-tools.ts +463 -0
- package/src/plugins/memory/memory-ucan.ts +51 -0
- package/src/plugins/memory/memory.plugin.ts +122 -0
- package/src/plugins/memory/types.ts +147 -0
- package/src/plugins/portal/action-log.ts +34 -0
- package/src/plugins/portal/index.ts +2 -0
- package/src/plugins/portal/portal.plugin.ts +203 -0
- package/src/plugins/sandbox/index.ts +34 -0
- package/src/plugins/sandbox/sandbox-bridge.ts +295 -0
- package/src/plugins/sandbox/sandbox-mcp.ts +127 -0
- package/src/plugins/sandbox/sandbox-write-blob.ts +103 -0
- package/src/plugins/sandbox/sandbox.plugin.ts +599 -0
- package/src/plugins/tasks/index.ts +17 -0
- package/src/plugins/tasks/manifest.ts +81 -0
- package/src/plugins/tasks/middleware.ts +219 -0
- package/src/plugins/tasks/tasks-tools.ts +414 -0
- package/src/plugins/tasks/tasks.plugin.ts +74 -0
- package/src/plugins/ucan-failure.ts +133 -0
- package/src/plugins/user-preferences/index.ts +22 -0
- package/src/plugins/user-preferences/schema.ts +31 -0
- package/src/plugins/user-preferences/user-preferences-store.ts +172 -0
- package/src/plugins/user-preferences/user-preferences-tool.ts +131 -0
- package/src/plugins/user-preferences/user-preferences.plugin.ts +137 -0
- package/src/plugins/vfs/index.ts +40 -0
- package/src/plugins/vfs/vfs-auth.ts +56 -0
- package/src/plugins/vfs/vfs-client.ts +655 -0
- package/src/plugins/vfs/vfs-content.ts +181 -0
- package/src/plugins/vfs/vfs-errors.ts +163 -0
- package/src/plugins/vfs/vfs-sandbox-tools.ts +272 -0
- package/src/plugins/vfs/vfs-tools.ts +635 -0
- package/src/plugins/vfs/vfs.plugin.ts +201 -0
- package/src/realtime/frontend-call-registry.ts +163 -0
- package/src/realtime/realtime-endpoint.ts +619 -0
- package/src/realtime/session-socket-hub.ts +249 -0
- package/src/realtime/socket-io-codec.ts +187 -0
- package/src/secrets/jwe.ts +74 -0
- package/src/secrets/pin-cipher.ts +43 -0
- package/src/secrets/secrets-service.ts +319 -0
- package/src/shell/app.ts +564 -0
- package/src/shell/auth.ts +290 -0
- package/src/sqlite/blob-codec.ts +100 -0
- package/src/sqlite/blob-compactor.ts +146 -0
- package/src/sqlite/cache-config.ts +45 -0
- package/src/sqlite/database.ts +643 -0
- package/src/sqlite/do-vfs.ts +1485 -0
- package/src/sqlite/index.ts +61 -0
- package/src/sqlite/serialization.ts +370 -0
- package/src/sqlite/sessions-store.ts +311 -0
- package/src/sqlite/sqlite-saver.ts +919 -0
- package/src/sqlite/vacuum-policy.ts +77 -0
- package/src/sqlite/wa-sqlite-loader.ts +123 -0
- package/src/sqlite/wasm.d.ts +10 -0
- package/src/tasks/schedule.ts +174 -0
- package/src/tasks/scheduler.ts +753 -0
- package/src/tasks/spec.ts +125 -0
- package/src/tasks/store.ts +351 -0
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Oracle-side UCAN helpers — a port of the Node runtime's `UcanService`
|
|
3
|
+
* minting paths (`createInvocationFromDelegation`, `mintSelfSignedInvocation`,
|
|
4
|
+
* `resolveServiceDid`, `getServiceDelegation`) with no NestJS and no
|
|
5
|
+
* process-wide singletons. One instance per user object; caches are
|
|
6
|
+
* per-instance and short-lived.
|
|
7
|
+
*
|
|
8
|
+
* The oracle signs with an Ed25519 mnemonic (`ORACLE_SIGNING_MNEMONIC`, the
|
|
9
|
+
* same material the Node runtime loads from its Matrix account room). Without
|
|
10
|
+
* it every mint returns `{ error }` and `hasSigningKey()` is false — plugins
|
|
11
|
+
* gate their mint-capable tools on that, exactly as on Node.
|
|
12
|
+
*/
|
|
13
|
+
import {
|
|
14
|
+
createInvocation,
|
|
15
|
+
parseDelegation,
|
|
16
|
+
serializeInvocation,
|
|
17
|
+
signerFromMnemonic,
|
|
18
|
+
type SupportedDID,
|
|
19
|
+
} from '@ixo/ucan';
|
|
20
|
+
import type { RuntimeContext, UcanDelegation } from '../plugin-api/types';
|
|
21
|
+
|
|
22
|
+
type MintResult = { invocation: string } | { error: string };
|
|
23
|
+
type ServiceDelegationResult =
|
|
24
|
+
| { token: string; with: string }
|
|
25
|
+
| { error: 'no-delegation' | 'store-error'; detail?: string };
|
|
26
|
+
|
|
27
|
+
export interface UcanServiceOptions {
|
|
28
|
+
oracleDid: string;
|
|
29
|
+
signingMnemonic?: string;
|
|
30
|
+
logger?: { warn(msg: string): void; debug?(msg: string): void };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function toSupportedDid(did: string): SupportedDID {
|
|
34
|
+
if (did.startsWith('did:ixo:') || did.startsWith('did:key:'))
|
|
35
|
+
return did as SupportedDID;
|
|
36
|
+
throw new Error(`Unsupported oracle DID method: ${did}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function abilityCovers(granted: string, required: string): boolean {
|
|
40
|
+
if (granted === '*' || granted === required) return true;
|
|
41
|
+
if (granted.endsWith('/*')) {
|
|
42
|
+
const ns = granted.slice(0, -2);
|
|
43
|
+
return required === ns || required.startsWith(`${ns}/`);
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Upper bound on a delegated invocation's lifetime (mirrors the Node runtime). */
|
|
49
|
+
const MAX_INVOCATION_TTL_SECONDS = 60 * 60;
|
|
50
|
+
|
|
51
|
+
export class WorkersUcanService {
|
|
52
|
+
private readonly serviceDidCache = new Map<
|
|
53
|
+
string,
|
|
54
|
+
{ did: string; expiresAt: number }
|
|
55
|
+
>();
|
|
56
|
+
private readonly storeDelegationCache = new Map<
|
|
57
|
+
string,
|
|
58
|
+
{ value: { token: string; with: string }; expiresAt: number }
|
|
59
|
+
>();
|
|
60
|
+
|
|
61
|
+
constructor(private readonly opts: UcanServiceOptions) {}
|
|
62
|
+
|
|
63
|
+
hasSigningKey(): boolean {
|
|
64
|
+
return Boolean(this.opts.signingMnemonic);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async resolveServiceDid(serviceUrl: string): Promise<string | null> {
|
|
68
|
+
try {
|
|
69
|
+
const origin = new URL(serviceUrl).origin;
|
|
70
|
+
const cached = this.serviceDidCache.get(origin);
|
|
71
|
+
if (cached && cached.expiresAt > Date.now()) return cached.did;
|
|
72
|
+
const res = await fetch(`${origin}/.well-known/did.json`);
|
|
73
|
+
if (!res.ok) {
|
|
74
|
+
this.opts.logger?.warn(`[UCAN] did.json ${origin}: HTTP ${res.status}`);
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
const doc: { id?: string } = await res.json<{ id?: string }>();
|
|
78
|
+
if (!doc.id) return null;
|
|
79
|
+
this.serviceDidCache.set(origin, {
|
|
80
|
+
did: doc.id,
|
|
81
|
+
expiresAt: Date.now() + 60 * 60 * 1000,
|
|
82
|
+
});
|
|
83
|
+
return doc.id;
|
|
84
|
+
} catch (error) {
|
|
85
|
+
this.opts.logger?.warn(
|
|
86
|
+
`[UCAN] resolveServiceDid ${serviceUrl}: ${error instanceof Error ? error.message : String(error)}`,
|
|
87
|
+
);
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async createInvocationFromDelegation(
|
|
93
|
+
delegationCar: string,
|
|
94
|
+
serviceUrlOrDid: string,
|
|
95
|
+
capability: { can: string; with: string; nb?: Record<string, unknown> },
|
|
96
|
+
options: { maxTtlSeconds?: number } = {},
|
|
97
|
+
): Promise<MintResult> {
|
|
98
|
+
if (!this.opts.signingMnemonic)
|
|
99
|
+
return {
|
|
100
|
+
error:
|
|
101
|
+
'Oracle has no signing key configured — set ORACLE_SIGNING_MNEMONIC',
|
|
102
|
+
};
|
|
103
|
+
if (!delegationCar)
|
|
104
|
+
return { error: 'delegationCar (base64 CAR) is required' };
|
|
105
|
+
// Plugins that already resolved the service DID (memory, composio) pass
|
|
106
|
+
// the DID itself — mint straight to it, as the Node runtime's
|
|
107
|
+
// `mintInvocationForServiceDid` does. A DID is not a fetchable URL:
|
|
108
|
+
// `new URL('did:web:x').origin` is "null", so routing it through
|
|
109
|
+
// `resolveServiceDid` can never succeed.
|
|
110
|
+
const serviceDid = serviceUrlOrDid.startsWith('did:')
|
|
111
|
+
? serviceUrlOrDid
|
|
112
|
+
: await this.resolveServiceDid(serviceUrlOrDid);
|
|
113
|
+
if (!serviceDid)
|
|
114
|
+
return {
|
|
115
|
+
error: `Could not resolve worker DID via ${serviceUrlOrDid}/.well-known/did.json`,
|
|
116
|
+
};
|
|
117
|
+
try {
|
|
118
|
+
const { signer } = await signerFromMnemonic(
|
|
119
|
+
this.opts.signingMnemonic,
|
|
120
|
+
toSupportedDid(this.opts.oracleDid),
|
|
121
|
+
);
|
|
122
|
+
let delegation: Awaited<ReturnType<typeof parseDelegation>>;
|
|
123
|
+
try {
|
|
124
|
+
delegation = await parseDelegation(delegationCar);
|
|
125
|
+
} catch (err) {
|
|
126
|
+
return {
|
|
127
|
+
error: `Could not parse delegation CAR: ${err instanceof Error ? err.message : String(err)}`,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
const audience = delegation.audience?.did?.();
|
|
131
|
+
if (audience && audience !== this.opts.oracleDid) {
|
|
132
|
+
return {
|
|
133
|
+
error: `Delegation audience (${audience}) does not match this oracle DID (${this.opts.oracleDid})`,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
137
|
+
// Same lifetime rule as the Node runtime (`MAX_INVOCATION_TTL_SECONDS`):
|
|
138
|
+
// an invocation lives up to an hour, capped by the delegation. An MCP
|
|
139
|
+
// session minted at turn start keeps re-using its bearer for the whole
|
|
140
|
+
// turn, so a 60 s token expired under long tool calls.
|
|
141
|
+
let expiration =
|
|
142
|
+
nowSeconds + (options.maxTtlSeconds ?? MAX_INVOCATION_TTL_SECONDS);
|
|
143
|
+
const delegationExp =
|
|
144
|
+
typeof delegation.expiration === 'number' &&
|
|
145
|
+
isFinite(delegation.expiration)
|
|
146
|
+
? delegation.expiration
|
|
147
|
+
: null;
|
|
148
|
+
if (delegationExp !== null) {
|
|
149
|
+
if (delegationExp <= nowSeconds)
|
|
150
|
+
return {
|
|
151
|
+
error: `Delegation expired at ${new Date(delegationExp * 1000).toISOString()}`,
|
|
152
|
+
};
|
|
153
|
+
expiration = Math.min(expiration, delegationExp);
|
|
154
|
+
}
|
|
155
|
+
const invocation = await createInvocation({
|
|
156
|
+
issuer: signer,
|
|
157
|
+
audience: serviceDid,
|
|
158
|
+
capability: {
|
|
159
|
+
can: capability.can as `${string}/${string}`,
|
|
160
|
+
with: capability.with as `${string}:${string}`,
|
|
161
|
+
// Caveats (e.g. the VFS `nb.hidden` reveal set) pass through
|
|
162
|
+
// verbatim; services intersect them across the proof chain.
|
|
163
|
+
...(capability.nb !== undefined ? { nb: capability.nb } : {}),
|
|
164
|
+
},
|
|
165
|
+
proofs: [delegation],
|
|
166
|
+
expiration,
|
|
167
|
+
facts: [{ nonce: crypto.randomUUID() }],
|
|
168
|
+
});
|
|
169
|
+
return { invocation: await serializeInvocation(invocation) };
|
|
170
|
+
} catch (error) {
|
|
171
|
+
return {
|
|
172
|
+
error: `Failed to mint invocation: ${error instanceof Error ? error.message : String(error)}`,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async mintSelfSignedInvocation(
|
|
178
|
+
serviceUrl: string,
|
|
179
|
+
capability: { can: string; with: string },
|
|
180
|
+
options: { maxTtlSeconds?: number } = {},
|
|
181
|
+
): Promise<MintResult> {
|
|
182
|
+
if (!this.opts.signingMnemonic)
|
|
183
|
+
return {
|
|
184
|
+
error:
|
|
185
|
+
'Oracle has no signing key configured — set ORACLE_SIGNING_MNEMONIC',
|
|
186
|
+
};
|
|
187
|
+
const serviceDid = await this.resolveServiceDid(serviceUrl);
|
|
188
|
+
if (!serviceDid)
|
|
189
|
+
return {
|
|
190
|
+
error: `Could not resolve worker DID via ${serviceUrl}/.well-known/did.json`,
|
|
191
|
+
};
|
|
192
|
+
try {
|
|
193
|
+
const { signer } = await signerFromMnemonic(
|
|
194
|
+
this.opts.signingMnemonic,
|
|
195
|
+
toSupportedDid(this.opts.oracleDid),
|
|
196
|
+
);
|
|
197
|
+
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
198
|
+
const invocation = await createInvocation({
|
|
199
|
+
issuer: signer,
|
|
200
|
+
audience: serviceDid,
|
|
201
|
+
capability: {
|
|
202
|
+
can: capability.can as `${string}/${string}`,
|
|
203
|
+
with: capability.with as `${string}:${string}`,
|
|
204
|
+
},
|
|
205
|
+
proofs: [],
|
|
206
|
+
expiration: nowSeconds + (options.maxTtlSeconds ?? 120),
|
|
207
|
+
facts: [{ nonce: crypto.randomUUID() }],
|
|
208
|
+
});
|
|
209
|
+
return { invocation: await serializeInvocation(invocation) };
|
|
210
|
+
} catch (error) {
|
|
211
|
+
return {
|
|
212
|
+
error: `Failed to mint invocation: ${error instanceof Error ? error.message : String(error)}`,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async getServiceDelegation(
|
|
218
|
+
userDid: string,
|
|
219
|
+
opts: { storeUrl: string; resource: string; requiredAbility: string },
|
|
220
|
+
): Promise<ServiceDelegationResult> {
|
|
221
|
+
const cacheKey = `${userDid}:${opts.resource}:${opts.requiredAbility}`;
|
|
222
|
+
const cached = this.storeDelegationCache.get(cacheKey);
|
|
223
|
+
if (cached && cached.expiresAt > Date.now()) return cached.value;
|
|
224
|
+
|
|
225
|
+
const inv = await this.mintSelfSignedInvocation(
|
|
226
|
+
opts.storeUrl,
|
|
227
|
+
{ can: 'store/get', with: 'ixo:ucan-store' },
|
|
228
|
+
{ maxTtlSeconds: 120 },
|
|
229
|
+
);
|
|
230
|
+
if ('error' in inv) return { error: 'store-error', detail: inv.error };
|
|
231
|
+
|
|
232
|
+
interface StoreDelegation {
|
|
233
|
+
token: string;
|
|
234
|
+
capabilities: Array<{ can: string; with: string }>;
|
|
235
|
+
expiresAt: number | null;
|
|
236
|
+
lifecycleState: string;
|
|
237
|
+
}
|
|
238
|
+
let res: Response;
|
|
239
|
+
try {
|
|
240
|
+
res = await fetch(
|
|
241
|
+
`${opts.storeUrl}/api/delegations?rootIssuer=${encodeURIComponent(userDid)}`,
|
|
242
|
+
{
|
|
243
|
+
headers: {
|
|
244
|
+
authorization: `Bearer ${inv.invocation}`,
|
|
245
|
+
'x-auth-type': 'ucan',
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
);
|
|
249
|
+
} catch (error) {
|
|
250
|
+
return {
|
|
251
|
+
error: 'store-error',
|
|
252
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
if (!res.ok)
|
|
256
|
+
return res.status === 404
|
|
257
|
+
? { error: 'no-delegation' }
|
|
258
|
+
: { error: 'store-error', detail: `store ${res.status}` };
|
|
259
|
+
let body: { delegations?: StoreDelegation[] };
|
|
260
|
+
try {
|
|
261
|
+
body = await res.json();
|
|
262
|
+
} catch (error) {
|
|
263
|
+
return {
|
|
264
|
+
error: 'store-error',
|
|
265
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
269
|
+
for (const row of body.delegations ?? []) {
|
|
270
|
+
if (row.lifecycleState !== 'active') continue;
|
|
271
|
+
if (row.expiresAt != null && row.expiresAt <= nowSeconds) continue;
|
|
272
|
+
const match = (row.capabilities ?? []).find(
|
|
273
|
+
(c) =>
|
|
274
|
+
(c.with === opts.resource || c.with.startsWith(opts.resource)) &&
|
|
275
|
+
abilityCovers(c.can, opts.requiredAbility),
|
|
276
|
+
);
|
|
277
|
+
if (!match) continue;
|
|
278
|
+
const value = { token: row.token, with: match.with };
|
|
279
|
+
const ttl =
|
|
280
|
+
row.expiresAt != null ? Math.min(row.expiresAt - nowSeconds, 600) : 600;
|
|
281
|
+
if (ttl > 0)
|
|
282
|
+
this.storeDelegationCache.set(cacheKey, {
|
|
283
|
+
value,
|
|
284
|
+
expiresAt: Date.now() + ttl * 1000,
|
|
285
|
+
});
|
|
286
|
+
return value;
|
|
287
|
+
}
|
|
288
|
+
return { error: 'no-delegation' };
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Build the `ctx.ucan` surface for one turn. `delegation` is the user's
|
|
293
|
+
* delegation for this oracle (from the request header or the room state) —
|
|
294
|
+
* `mintInvocation` proves through it; capability checks read it.
|
|
295
|
+
*/
|
|
296
|
+
forTurn(
|
|
297
|
+
userDid: string,
|
|
298
|
+
delegation: UcanDelegation | undefined,
|
|
299
|
+
): RuntimeContext['ucan'] {
|
|
300
|
+
const caps = delegation?.capabilities ?? [];
|
|
301
|
+
const has = (resource: string, action: string) =>
|
|
302
|
+
caps.some(
|
|
303
|
+
(c) =>
|
|
304
|
+
(c.resource === resource || c.resource.startsWith(resource)) &&
|
|
305
|
+
abilityCovers(c.action, action),
|
|
306
|
+
);
|
|
307
|
+
return {
|
|
308
|
+
hasCapability: has,
|
|
309
|
+
requireCapability: (resource, action) => {
|
|
310
|
+
if (!has(resource, action))
|
|
311
|
+
throw new Error(`Missing UCAN capability ${action} on ${resource}`);
|
|
312
|
+
},
|
|
313
|
+
hasSigningKey: () => this.hasSigningKey(),
|
|
314
|
+
resolveServiceDid: (url) => this.resolveServiceDid(url),
|
|
315
|
+
mintInvocation: async (target, opts) => {
|
|
316
|
+
if (!delegation?.raw)
|
|
317
|
+
throw new Error(
|
|
318
|
+
`No delegation from ${userDid} available to mint an invocation from`,
|
|
319
|
+
);
|
|
320
|
+
const [can] = [opts?.can ?? '*'];
|
|
321
|
+
const minted = await this.createInvocationFromDelegation(
|
|
322
|
+
delegation.raw,
|
|
323
|
+
target.did,
|
|
324
|
+
{
|
|
325
|
+
can,
|
|
326
|
+
with: target.capability,
|
|
327
|
+
},
|
|
328
|
+
);
|
|
329
|
+
if ('error' in minted) throw new Error(minted.error);
|
|
330
|
+
return minted.invocation;
|
|
331
|
+
},
|
|
332
|
+
createInvocationFromDelegation: (car, url, cap, o) =>
|
|
333
|
+
this.createInvocationFromDelegation(car, url, cap, o),
|
|
334
|
+
mintSelfSignedInvocation: (url, cap, o) =>
|
|
335
|
+
this.mintSelfSignedInvocation(url, cap, o),
|
|
336
|
+
getServiceDelegation: (did, o) => this.getServiceDelegation(did, o),
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
}
|