@fungi.computer/oyster 0.5.0 → 0.7.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 +155 -63
- package/dist/agent-archive-projection.d.ts +8 -0
- package/dist/agent-archive-projection.js +49 -0
- package/dist/agent-schedules.d.ts +50 -0
- package/dist/agent-schedules.js +17 -0
- package/dist/agents.d.ts +35 -14
- package/dist/agents.js +97 -144
- package/dist/app-builds.d.ts +7 -2
- package/dist/app-builds.js +19 -2
- package/dist/approval-capability.d.ts +1 -1
- package/dist/approval-capability.js +11 -16
- package/dist/approval-home.d.ts +2 -1
- package/dist/approval-home.js +5 -3
- package/dist/apps/catalog.js +1 -1
- package/dist/apps/gate.d.ts +4 -7
- package/dist/apps/gate.js +14 -29
- package/dist/apps/index.d.ts +1 -1
- package/dist/apps/installation.d.ts +4 -42
- package/dist/apps/installation.js +7 -75
- package/dist/apps/operations.js +0 -10
- package/dist/apps/permissions.d.ts +18 -8
- package/dist/apps/permissions.js +7 -1
- package/dist/auth.js +9 -1
- package/dist/authorization.d.ts +307 -0
- package/dist/authorization.js +542 -0
- package/dist/boot-host-script.js +1 -1
- package/dist/build/build-jobs.d.ts +2 -2
- package/dist/build/candidate-builder.d.ts +2 -0
- package/dist/build/candidate-builder.js +5 -1
- package/dist/build/candidate-cli.js +3 -0
- package/dist/build/candidate-cli.mjs +67 -22
- package/dist/build/job-log.d.ts +23 -0
- package/dist/build/job-log.js +88 -0
- package/dist/build/local-build.d.ts +2 -0
- package/dist/build/local-build.js +11 -9
- package/dist/channels/binding.d.ts +8 -1
- package/dist/channels/binding.js +12 -0
- package/dist/channels/conversations.d.ts +16 -5
- package/dist/channels/visitor-channel.d.ts +53 -0
- package/dist/channels/visitor-channel.js +124 -0
- package/dist/channels/visitor-edge.d.ts +115 -0
- package/dist/channels/visitor-edge.js +582 -0
- package/dist/channels/visitor-request.d.ts +7 -0
- package/dist/channels/visitor-request.js +17 -0
- package/dist/channels/visitor-stream-slots.d.ts +2 -0
- package/dist/channels/visitor-stream-slots.js +33 -0
- package/dist/channels/visitors.d.ts +47 -0
- package/dist/channels/visitors.js +181 -0
- package/dist/cloudflare.d.ts +11 -2
- package/dist/cloudflare.js +27 -1
- package/dist/{page/computer-target.d.ts → computer-target.d.ts} +2 -0
- package/dist/{page/computer-target.js → computer-target.js} +3 -1
- package/dist/files.js +9 -8
- package/dist/git-admission.d.ts +3 -3
- package/dist/git-admission.js +4 -6
- package/dist/git-door.d.ts +8 -7
- package/dist/git-door.js +18 -17
- package/dist/git.d.ts +1 -1
- package/dist/host.d.ts +38 -42
- package/dist/host.js +182 -239
- package/dist/installation-execution.js +10 -5
- package/dist/installation-operations.d.ts +4 -180
- package/dist/installation-operations.js +1 -5
- package/dist/installation-store-generation.d.ts +4 -1
- package/dist/installation-store-generation.js +59 -6
- package/dist/internal/delivery.js +0 -1
- package/dist/internal/host-gate.d.ts +9 -6
- package/dist/internal/host-gate.js +19 -25
- package/dist/internal/invocation-timing.d.ts +16 -0
- package/dist/internal/invocation-timing.js +23 -0
- package/dist/internal/registry-installations.js +1 -0
- package/dist/invocation.d.ts +19 -11
- package/dist/invocation.js +171 -96
- package/dist/keys.d.ts +2 -19
- package/dist/keys.js +1 -37
- package/dist/mcp.d.ts +15 -27
- package/dist/mcp.js +105 -155
- package/dist/page/agent-browser.d.ts +15 -0
- package/dist/page/agent-browser.js +162 -0
- package/dist/page/app-permissions-port.d.ts +3 -0
- package/dist/page/app-permissions-port.js +43 -0
- package/dist/page/app-permissions.d.ts +11 -0
- package/dist/page/app-permissions.js +296 -0
- package/dist/page/capabilities.js +1 -1
- package/dist/page/child-selection.d.ts +27 -0
- package/dist/page/child-selection.js +38 -0
- package/dist/page/isolated-page-theme.d.ts +2 -0
- package/dist/page/isolated-page-theme.js +11 -0
- package/dist/page/mount.d.ts +2 -13
- package/dist/page/mount.js +8 -94
- package/dist/page-script.d.ts +0 -3
- package/dist/page-script.js +3 -4
- package/dist/release.d.ts +13 -0
- package/dist/release.js +10 -0
- package/dist/request-origin.d.ts +4 -0
- package/dist/request-origin.js +10 -0
- package/dist/standalone/agent-archive-owner.d.ts +42 -0
- package/dist/standalone/agent-archive-owner.js +116 -0
- package/dist/standalone/agent-archive-schedules.d.ts +6 -0
- package/dist/standalone/agent-archive-schedules.js +76 -0
- package/dist/standalone/agent-archive.d.ts +31 -0
- package/dist/standalone/agent-archive.js +67 -0
- package/dist/standalone/agent-computers.d.ts +7 -6
- package/dist/standalone/agent-computers.js +4 -7
- package/dist/standalone/agent-mail.d.ts +2 -2
- package/dist/standalone/agents.d.ts +157 -20
- package/dist/standalone/agents.js +242 -38
- package/dist/standalone/app-builds-binding.js +1 -1
- package/dist/standalone/app-set.js +2 -54
- package/dist/standalone/app-source.d.ts +3 -4
- package/dist/standalone/app-source.js +12 -9
- package/dist/standalone/builds.d.ts +2 -14
- package/dist/standalone/builds.js +15 -13
- package/dist/standalone/computer-fleet.d.ts +4 -4
- package/dist/standalone/computer-fleet.js +20 -43
- package/dist/standalone/computer.d.ts +4 -3
- package/dist/standalone/computer.js +52 -53
- package/dist/standalone/dashboard-approval.d.ts +10 -0
- package/dist/standalone/dashboard-approval.js +66 -0
- package/dist/standalone/machine-link.d.ts +4 -221
- package/dist/standalone/machine-link.js +21 -15
- package/dist/standalone/media.d.ts +1 -0
- package/dist/standalone/media.js +20 -14
- package/dist/standalone/owner-routes.d.ts +16 -0
- package/dist/standalone/owner-routes.js +21 -0
- package/dist/standalone/permission-resources.d.ts +5 -0
- package/dist/standalone/permission-resources.js +31 -0
- package/dist/standalone/registry.d.ts +7 -0
- package/dist/standalone/registry.js +3 -0
- package/dist/standalone/tenancy.js +3 -1
- package/dist/standalone/worker-routes.d.ts +19 -0
- package/dist/standalone/worker-routes.js +9 -0
- package/dist/standalone/worker.d.ts +1 -1
- package/dist/standalone/worker.js +27 -35
- package/dist/standalone/workflow-binding.d.ts +8 -3
- package/dist/standalone/workflow-binding.js +14 -5
- package/dist/standalone/workflow-git-capture.js +2 -1
- package/dist/standalone/workflow-host.d.ts +8 -103
- package/dist/standalone/workflow-host.js +1 -1
- package/dist/standalone/workflow-node-capture.js +2 -2
- package/dist/standalone/workflow-root-authority.d.ts +1 -1
- package/dist/standalone/workflow-root-authority.js +1 -1
- package/dist/standalone/workflow-schedules.d.ts +14 -5
- package/dist/standalone/workflow-schedules.js +11 -5
- package/dist/standalone/workflow-webhooks.d.ts +4 -18
- package/dist/standalone/workflow-webhooks.js +5 -5
- package/dist/standalone/workflows.d.ts +36 -28
- package/dist/standalone/workflows.js +6 -3
- package/dist/storage-frame.d.ts +6 -6
- package/dist/testing/capabilities.d.ts +7 -6
- package/dist/testing/capabilities.js +3 -6
- package/dist/testing/installation.d.ts +0 -1
- package/dist/testing/installation.js +2 -5
- package/dist/turnstile.d.ts +27 -0
- package/dist/turnstile.js +51 -0
- package/dist/workers-git.d.ts +12 -4
- package/dist/workers-git.js +58 -36
- package/dist/workers.d.ts +9 -1
- package/dist/workers.js +59 -20
- package/dist/workflows.d.ts +2 -0
- package/dist/workflows.js +1 -0
- package/docs/api/README.md +7 -0
- package/docs/api/agent-schedules.md +175 -0
- package/docs/api/agents.md +34 -13
- package/docs/api/app-storage.md +1 -1
- package/docs/api/apps.md +323 -225
- package/docs/api/authorization.md +1300 -0
- package/docs/api/build-contract.md +2 -2
- package/docs/api/build.md +40 -37
- package/docs/api/cloudflare.md +27 -2
- package/docs/api/git.md +20 -11
- package/docs/api/host.md +80 -199
- package/docs/api/index.md +1 -1
- package/docs/api/invocation.md +15 -11
- package/docs/api/keys.md +1 -98
- package/docs/api/mcp.md +43 -40
- package/docs/api/modules.md +2 -0
- package/docs/api/page/mount.md +16 -19
- package/docs/api/workers-git.md +46 -12
- package/docs/api/workers.md +125 -235
- package/docs/site/agent-quickstart.mdx +6 -2
- package/examples/standalone/wrangler.celld.jsonc +66 -0
- package/package.json +64 -21
- package/dist/standalone/channel-session-door.d.ts +0 -4
- package/dist/standalone/channel-session-door.js +0 -42
package/dist/invocation.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { approvedBindingGrants } from "./apps/gate.js";
|
|
2
2
|
import { appToolPolicySchema } from "./apps/tools.js";
|
|
3
|
-
import * as Effect from "effect/Effect";
|
|
4
3
|
import * as Schema from "effect/Schema";
|
|
5
|
-
import * as Semaphore from "effect/Semaphore";
|
|
6
4
|
import { defineIdFamily } from "@fungi.computer/cairn/id";
|
|
7
5
|
import { sha256DigestSchema } from "@fungi.computer/puffball/effect";
|
|
8
6
|
import { appBackendBindingKinds, appBackendBindingsSchema, appMethodCallContextSchema, } from "@fungi.computer/puffball/app";
|
|
@@ -152,28 +150,115 @@ function immutableFacts(facts) {
|
|
|
152
150
|
Object.freeze(facts.ancestry);
|
|
153
151
|
return Object.freeze(facts);
|
|
154
152
|
}
|
|
155
|
-
|
|
153
|
+
const concurrentReadFrames = 8;
|
|
154
|
+
/**
|
|
155
|
+
* First-come permits over one guest: a waiting exclusive frame holds back
|
|
156
|
+
* every frame queued after it, so a stream of reads never starves a write.
|
|
157
|
+
*/
|
|
158
|
+
function orderedPermits(total) {
|
|
159
|
+
let available = total;
|
|
160
|
+
const waiting = [];
|
|
161
|
+
const admit = () => {
|
|
162
|
+
for (let next = waiting[0]; next !== undefined; next = waiting[0]) {
|
|
163
|
+
const count = next.requested();
|
|
164
|
+
if (count > available)
|
|
165
|
+
break;
|
|
166
|
+
waiting.shift();
|
|
167
|
+
available -= count;
|
|
168
|
+
next.enter(count);
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
return async (requested, work) => {
|
|
172
|
+
const permits = await new Promise((enter) => {
|
|
173
|
+
waiting.push({ requested, enter });
|
|
174
|
+
admit();
|
|
175
|
+
});
|
|
176
|
+
try {
|
|
177
|
+
return await work();
|
|
178
|
+
}
|
|
179
|
+
catch (cause) {
|
|
180
|
+
throw cause instanceof Error
|
|
181
|
+
? cause
|
|
182
|
+
: new Error("App invocation failed", { cause });
|
|
183
|
+
}
|
|
184
|
+
finally {
|
|
185
|
+
available += permits;
|
|
186
|
+
admit();
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
/** Only foreground reads declared by the verified API may share the guest. */
|
|
191
|
+
function concurrentRead(facts) {
|
|
192
|
+
return (facts.subject.kind === "call" &&
|
|
193
|
+
facts.subject.replay === undefined &&
|
|
194
|
+
facts.subject.method?.effect === "read");
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* One Instance shares up to eight verified foreground reads; all other work
|
|
198
|
+
* runs alone in first-come order. Every finish fences its frame; the last live
|
|
199
|
+
* frame stops the native guest before closing authority and releasing admission.
|
|
200
|
+
* Binding checks alone cannot stop a timer mutating the facet's own SQLite.
|
|
201
|
+
* Revocation, expiry and a verified write preempting replay stop every live frame.
|
|
202
|
+
* Arrivals wait for a stopping guest. Once the opening deadline passes, new
|
|
203
|
+
* reads queue exclusively so the current cohort cannot be prolonged forever.
|
|
204
|
+
*/
|
|
156
205
|
export function createInvocationGate(ports) {
|
|
157
|
-
const
|
|
206
|
+
const permits = orderedPermits(concurrentReadFrames);
|
|
158
207
|
const now = ports.now ?? Date.now;
|
|
208
|
+
let stopping;
|
|
209
|
+
let cohortDeadlineAt;
|
|
210
|
+
const withPermits = (read, work) => permits(() => read && (cohortDeadlineAt === undefined || now() < cohortDeadlineAt)
|
|
211
|
+
? 1
|
|
212
|
+
: concurrentReadFrames, async () => {
|
|
213
|
+
while (stopping !== undefined)
|
|
214
|
+
await stopping;
|
|
215
|
+
return work();
|
|
216
|
+
});
|
|
217
|
+
const stopGuest = () => {
|
|
218
|
+
if (stopping !== undefined)
|
|
219
|
+
return stopping;
|
|
220
|
+
let aborted;
|
|
221
|
+
try {
|
|
222
|
+
aborted = ports.abortGuest();
|
|
223
|
+
}
|
|
224
|
+
catch (cause) {
|
|
225
|
+
aborted = Promise.reject(cause instanceof Error
|
|
226
|
+
? cause
|
|
227
|
+
: new Error("App guest stop failed", { cause }));
|
|
228
|
+
}
|
|
229
|
+
const stopped = aborted.finally(() => {
|
|
230
|
+
cohortDeadlineAt = undefined;
|
|
231
|
+
stopping = undefined;
|
|
232
|
+
});
|
|
233
|
+
stopping = stopped;
|
|
234
|
+
return stopped;
|
|
235
|
+
};
|
|
159
236
|
let foregroundWaiting = 0;
|
|
160
|
-
|
|
161
|
-
const
|
|
237
|
+
const live = new Map();
|
|
238
|
+
const finish = (state, reason) => {
|
|
162
239
|
if (state.closing)
|
|
163
240
|
return state.closing;
|
|
164
|
-
if (active === state)
|
|
165
|
-
active = undefined;
|
|
166
241
|
state.controller.abort(reason);
|
|
167
|
-
state.
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
await ports.close(state.frame);
|
|
173
|
-
}
|
|
174
|
-
})();
|
|
242
|
+
live.delete(state.frame.frameId);
|
|
243
|
+
state.closing =
|
|
244
|
+
live.size === 0
|
|
245
|
+
? stopGuest().finally(() => ports.close(state.frame))
|
|
246
|
+
: ports.close(state.frame);
|
|
175
247
|
return state.closing;
|
|
176
248
|
};
|
|
249
|
+
// The guest is shared: aborting it ends every live frame's guest work, and
|
|
250
|
+
// each of those frames closes only after the guest abort has completed.
|
|
251
|
+
const abortAll = (reason) => {
|
|
252
|
+
const states = [...live.values()];
|
|
253
|
+
for (const state of states) {
|
|
254
|
+
live.delete(state.frame.frameId);
|
|
255
|
+
state.controller.abort(reason);
|
|
256
|
+
}
|
|
257
|
+
const guest = stopGuest();
|
|
258
|
+
for (const state of states)
|
|
259
|
+
state.closing ??= guest.finally(() => ports.close(state.frame));
|
|
260
|
+
return Promise.all([guest, ...states.map((state) => state.closing)]).then(() => undefined);
|
|
261
|
+
};
|
|
177
262
|
const run = async (input, host, work) => {
|
|
178
263
|
let facts;
|
|
179
264
|
try {
|
|
@@ -196,64 +281,64 @@ export function createInvocationGate(ports) {
|
|
|
196
281
|
if (queuedForeground &&
|
|
197
282
|
facts.subject.kind === "call" &&
|
|
198
283
|
facts.subject.method?.effect === "write" &&
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
await
|
|
202
|
-
return await
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
});
|
|
237
|
-
stop = () => {
|
|
238
|
-
controller.signal.removeEventListener("abort", onAbort);
|
|
239
|
-
};
|
|
240
|
-
timer = setTimeout(() => {
|
|
241
|
-
controller.abort(new AppInstanceError("expired"));
|
|
242
|
-
}, Math.max(0, deadlineAt - now()));
|
|
284
|
+
[...live.values()].some(({ frame }) => frame.facts.subject.kind === "call" &&
|
|
285
|
+
frame.facts.subject.replay !== undefined))
|
|
286
|
+
await abortAll(new AppInstanceError("revoked"));
|
|
287
|
+
return await withPermits(concurrentRead(facts), async () => {
|
|
288
|
+
if (facts.subject.kind === "call" &&
|
|
289
|
+
facts.subject.replay !== undefined &&
|
|
290
|
+
foregroundWaiting > 0)
|
|
291
|
+
throw new AppInstanceError("revoked");
|
|
292
|
+
if (queuedForeground) {
|
|
293
|
+
foregroundWaiting -= 1;
|
|
294
|
+
queuedForeground = false;
|
|
295
|
+
}
|
|
296
|
+
const { deadlineAt } = facts;
|
|
297
|
+
if (deadlineAt <= now())
|
|
298
|
+
throw new AppInstanceError("expired");
|
|
299
|
+
const frameId = frameIds.frame.generate();
|
|
300
|
+
const controller = new AbortController();
|
|
301
|
+
const frame = Object.freeze({
|
|
302
|
+
frameId,
|
|
303
|
+
facts,
|
|
304
|
+
host,
|
|
305
|
+
signal: controller.signal,
|
|
306
|
+
});
|
|
307
|
+
const state = { frame, controller };
|
|
308
|
+
cohortDeadlineAt ??= deadlineAt;
|
|
309
|
+
live.set(frameId, state);
|
|
310
|
+
let timer;
|
|
311
|
+
let stop = () => undefined;
|
|
312
|
+
const revoked = new Promise((_resolve, reject) => {
|
|
313
|
+
const onAbort = () => {
|
|
314
|
+
const reason = controller.signal.reason;
|
|
315
|
+
reject(reason instanceof Error
|
|
316
|
+
? reason
|
|
317
|
+
: new AppInstanceError("revoked"));
|
|
318
|
+
};
|
|
319
|
+
controller.signal.addEventListener("abort", onAbort, {
|
|
320
|
+
once: true,
|
|
243
321
|
});
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
322
|
+
stop = () => {
|
|
323
|
+
controller.signal.removeEventListener("abort", onAbort);
|
|
324
|
+
};
|
|
325
|
+
timer = setTimeout(() => {
|
|
326
|
+
// An expired frame's guest may still be running. Each frame's own
|
|
327
|
+
// run awaits its close, so a failure reaches that caller.
|
|
328
|
+
void Promise.allSettled([
|
|
329
|
+
abortAll(new AppInstanceError("expired")),
|
|
330
|
+
]);
|
|
331
|
+
}, Math.max(0, deadlineAt - now()));
|
|
332
|
+
});
|
|
333
|
+
try {
|
|
334
|
+
return await Promise.race([work(frame), revoked]);
|
|
335
|
+
}
|
|
336
|
+
finally {
|
|
337
|
+
clearTimeout(timer);
|
|
338
|
+
stop();
|
|
339
|
+
await finish(state, new AppInstanceError("revoked"));
|
|
340
|
+
}
|
|
341
|
+
});
|
|
257
342
|
}
|
|
258
343
|
finally {
|
|
259
344
|
if (queuedForeground)
|
|
@@ -262,40 +347,30 @@ export function createInvocationGate(ports) {
|
|
|
262
347
|
};
|
|
263
348
|
return {
|
|
264
349
|
run,
|
|
265
|
-
exclusive: (work) =>
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
active: () => {
|
|
272
|
-
if (active === undefined ||
|
|
273
|
-
active.controller.signal.aborted ||
|
|
274
|
-
active.frame.facts.deadlineAt <= now())
|
|
350
|
+
exclusive: (work) => withPermits(false, work),
|
|
351
|
+
active: (frameId) => {
|
|
352
|
+
const state = live.get(frameId);
|
|
353
|
+
if (state === undefined ||
|
|
354
|
+
state.controller.signal.aborted ||
|
|
355
|
+
state.frame.facts.deadlineAt <= now())
|
|
275
356
|
throw new AppInstanceError("revoked");
|
|
276
|
-
return
|
|
357
|
+
return state.frame;
|
|
277
358
|
},
|
|
278
359
|
revoke: (reason = new AppInstanceError("revoked"), target) => {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
active.frame.facts.subject.context.workflow?.runId !== target.runId))
|
|
284
|
-
return Promise.resolve();
|
|
285
|
-
return close(active, reason);
|
|
360
|
+
const matched = [...live.values()].some(({ frame }) => target === undefined ||
|
|
361
|
+
(frame.facts.subject.kind === "call" &&
|
|
362
|
+
frame.facts.subject.context.workflow?.runId === target.runId));
|
|
363
|
+
return matched ? abortAll(reason) : Promise.resolve();
|
|
286
364
|
},
|
|
287
365
|
};
|
|
288
366
|
}
|
|
289
367
|
/**
|
|
290
368
|
* Project an admitted principal into App attribution; only the host chooses
|
|
291
|
-
* the fixed Computer context.
|
|
292
|
-
*
|
|
293
|
-
* without a Computer. Host admission reads the principal, never this projection.
|
|
369
|
+
* the fixed Computer context. Host admission reads the principal, never this
|
|
370
|
+
* projection.
|
|
294
371
|
*/
|
|
295
372
|
export function invocationCallerContext(principal, computerId) {
|
|
296
373
|
const { actor } = principal;
|
|
297
|
-
if (actor.kind === "visitor")
|
|
298
|
-
return { computerId: null, actor: { kind: "team-key", keyId: "visitor" } };
|
|
299
374
|
if (!("role" in principal))
|
|
300
375
|
return { computerId, actor };
|
|
301
376
|
return principal.soleMember === true
|
package/dist/keys.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { Grant, GrantVocabulary, Scope } from "@fungi.computer/ear-tag/grants";
|
|
2
2
|
import type { KeyPrincipal, KeyRecord, KeyStore } from "@fungi.computer/ear-tag/keys";
|
|
3
|
-
import type { AppToolPolicy } from "./apps/tools.js";
|
|
4
3
|
/** Every capability an Oyster key may carry. */
|
|
5
4
|
export type OysterKeyCapability = "apps.read" | "apps.use" | "agents.use" | "computer.use" | "machine.enroll";
|
|
6
5
|
/**
|
|
@@ -28,8 +27,8 @@ export type OysterKeyStore = KeyStore<OysterKeyCapability>;
|
|
|
28
27
|
/**
|
|
29
28
|
* The minimal live facts `OysterHost`'s `keys.live` port returns: enough to
|
|
30
29
|
* gate one call, never the key's name or timestamps. `null` for an unknown,
|
|
31
|
-
* revoked, expired or suspended key
|
|
32
|
-
*
|
|
30
|
+
* revoked, expired or suspended key, which `authorization.ts` treats as no
|
|
31
|
+
* authority.
|
|
33
32
|
*/
|
|
34
33
|
export type LiveOysterKey = Readonly<{
|
|
35
34
|
keyId: string;
|
|
@@ -37,19 +36,3 @@ export type LiveOysterKey = Readonly<{
|
|
|
37
36
|
grantVersion: number;
|
|
38
37
|
grants: readonly Grant<OysterKeyCapability>[];
|
|
39
38
|
}>;
|
|
40
|
-
/**
|
|
41
|
-
* One live key's access to one installation: `write` when its grants admit
|
|
42
|
-
* `apps.use` there, `read` when they admit `apps.read`, otherwise `null` --
|
|
43
|
-
* including for an installation added after the key was issued, unless the
|
|
44
|
-
* key holds "All Apps" (`targets: {kind: "all"}`). The same shape as an
|
|
45
|
-
* Agent's stored {@link AppToolPolicy}; its caller adds `bindingName` (which
|
|
46
|
-
* varies by caller: the plain installation id for the gate, the uppercased
|
|
47
|
-
* binding name for MCP) to reach the full `AppToolAccess`.
|
|
48
|
-
*/
|
|
49
|
-
export declare function keyAppAccess(key: LiveOysterKey, installationId: string): AppToolPolicy | null;
|
|
50
|
-
/** Agent access is scoped to the granted Agent ids, never inferred from App grants. */
|
|
51
|
-
export declare function keyAgentAccess(key: LiveOysterKey, agentId: string): boolean;
|
|
52
|
-
/** Whether a live key's own grants turn its Computer access on. Off by default. */
|
|
53
|
-
export declare function keyAllowsComputerUse(key: LiveOysterKey): boolean;
|
|
54
|
-
/** Whether a live key's grants permit a one-use device enrollment. */
|
|
55
|
-
export declare function keyAllowsMachineEnroll(key: LiveOysterKey): boolean;
|
package/dist/keys.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Oyster's own Ear Tag vocabulary: named keys an owner issues for selected
|
|
3
3
|
* Apps and Agents, its Computer or machine enrollment. App grants never imply
|
|
4
4
|
* Agent access. A key's id is its name, so name-derived identities stay stable.
|
|
5
|
+
* What a key reaches is decided only in `authorization.ts`.
|
|
5
6
|
*/
|
|
6
7
|
import { defineGrants } from "@fungi.computer/ear-tag/grants";
|
|
7
8
|
/**
|
|
@@ -32,40 +33,3 @@ export const computerUseGrant = {
|
|
|
32
33
|
capability: "computer.use",
|
|
33
34
|
targets: { kind: "all" },
|
|
34
35
|
};
|
|
35
|
-
/**
|
|
36
|
-
* One live key's access to one installation: `write` when its grants admit
|
|
37
|
-
* `apps.use` there, `read` when they admit `apps.read`, otherwise `null` --
|
|
38
|
-
* including for an installation added after the key was issued, unless the
|
|
39
|
-
* key holds "All Apps" (`targets: {kind: "all"}`). The same shape as an
|
|
40
|
-
* Agent's stored {@link AppToolPolicy}; its caller adds `bindingName` (which
|
|
41
|
-
* varies by caller: the plain installation id for the gate, the uppercased
|
|
42
|
-
* binding name for MCP) to reach the full `AppToolAccess`.
|
|
43
|
-
*/
|
|
44
|
-
export function keyAppAccess(key, installationId) {
|
|
45
|
-
const level = oysterKeyVocabulary.grantsAllow(key.grants, "apps.use", installationId)
|
|
46
|
-
? "write"
|
|
47
|
-
: oysterKeyVocabulary.grantsAllow(key.grants, "apps.read", installationId)
|
|
48
|
-
? "read"
|
|
49
|
-
: null;
|
|
50
|
-
if (level === null)
|
|
51
|
-
return null;
|
|
52
|
-
return {
|
|
53
|
-
level,
|
|
54
|
-
revision: `key:${key.keyId}:${String(key.grantVersion)}`,
|
|
55
|
-
grantedBy: key.owner,
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
/** Agent access is scoped to the granted Agent ids, never inferred from App grants. */
|
|
59
|
-
export function keyAgentAccess(key, agentId) {
|
|
60
|
-
return oysterKeyVocabulary.grantsAllow(key.grants, "agents.use", agentId);
|
|
61
|
-
}
|
|
62
|
-
/** Whether a live key's own grants turn its Computer access on. Off by default. */
|
|
63
|
-
export function keyAllowsComputerUse(key) {
|
|
64
|
-
return oysterKeyVocabulary.grantsAllow(key.grants, "computer.use");
|
|
65
|
-
}
|
|
66
|
-
/** Whether a live key's grants permit a one-use device enrollment. */
|
|
67
|
-
export function keyAllowsMachineEnroll(key) {
|
|
68
|
-
return (key.grants.length === 1 &&
|
|
69
|
-
key.grants[0]?.capability === "machine.enroll" &&
|
|
70
|
-
oysterKeyVocabulary.grantsAllow(key.grants, "machine.enroll"));
|
|
71
|
-
}
|
package/dist/mcp.d.ts
CHANGED
|
@@ -2,12 +2,11 @@ import { z } from "zod";
|
|
|
2
2
|
import { type AppToolPolicy } from "./apps/tools.js";
|
|
3
3
|
import type { DeclaredOperations, Installation } from "./apps/installation.js";
|
|
4
4
|
import { type CapabilitySource, type Grants, type ModuleRegistration } from "@fungi.computer/mycelium";
|
|
5
|
-
import { appToolSchemas } from "@fungi.computer/forage";
|
|
6
5
|
import { computerPageSchema } from "@fungi.computer/computer/fleet/inventory-wire";
|
|
7
6
|
import type { InvocationContext } from "@fungi.computer/mycelium";
|
|
8
7
|
import type { ComputerControlPort } from "./computer-control-port.js";
|
|
9
8
|
export type { ComputerControlPort } from "./computer-control-port.js";
|
|
10
|
-
import type { OysterHost
|
|
9
|
+
import type { OysterHost } from "./host.js";
|
|
11
10
|
import type { TenantPrincipal } from "./tenant.js";
|
|
12
11
|
import { type VerifiedRelease } from "./release.js";
|
|
13
12
|
/** Captured capabilities and grants offered to one admitted MCP caller. */
|
|
@@ -17,6 +16,8 @@ export type OysterMcpCatalogSnapshot = Readonly<{
|
|
|
17
16
|
/** An omitted module has no grant. */
|
|
18
17
|
grants: Grants;
|
|
19
18
|
}>;
|
|
19
|
+
/** Shared host result guidance for Agent and MCP instructions, not per method. */
|
|
20
|
+
export declare const appApprovalInstructions: string;
|
|
20
21
|
declare const fleetComputerId: z.ZodObject<{
|
|
21
22
|
computerId: z.ZodString;
|
|
22
23
|
}, z.core.$strict>;
|
|
@@ -33,10 +34,10 @@ declare const fleetCreation: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
33
34
|
}, z.core.$strict>;
|
|
34
35
|
}, z.core.$strict>, z.ZodObject<{
|
|
35
36
|
kind: z.ZodEnum<{
|
|
37
|
+
invalid: "invalid";
|
|
36
38
|
denied: "denied";
|
|
37
39
|
uncertain: "uncertain";
|
|
38
40
|
conflict: "conflict";
|
|
39
|
-
invalid: "invalid";
|
|
40
41
|
}>;
|
|
41
42
|
requestId: z.ZodString;
|
|
42
43
|
}, z.core.$strict>], "kind">;
|
|
@@ -135,10 +136,10 @@ export declare function computerControlSpecs(port: ComputerControlPort): {
|
|
|
135
136
|
}, z.core.$strict>;
|
|
136
137
|
}, z.core.$strict>, z.ZodObject<{
|
|
137
138
|
kind: z.ZodEnum<{
|
|
139
|
+
invalid: "invalid";
|
|
138
140
|
denied: "denied";
|
|
139
141
|
uncertain: "uncertain";
|
|
140
142
|
conflict: "conflict";
|
|
141
|
-
invalid: "invalid";
|
|
142
143
|
}>;
|
|
143
144
|
requestId: z.ZodString;
|
|
144
145
|
}, z.core.$strict>], "kind">;
|
|
@@ -159,7 +160,7 @@ export declare class AppMethodRefused extends Error {
|
|
|
159
160
|
* The App-tool access one caller's catalog capture used, re-checked at call
|
|
160
161
|
* time against a fresh read. An Agent's policy is one blanket level for
|
|
161
162
|
* every installation (`host.appToolPolicy`); a key's is its own grants,
|
|
162
|
-
* resolved per installation (`
|
|
163
|
+
* resolved per installation (`appToolAccess`) -- an installation the key has
|
|
163
164
|
* no grant for is captured as absent, never merely refused, so it is never
|
|
164
165
|
* listed at all.
|
|
165
166
|
*/
|
|
@@ -171,35 +172,23 @@ export type OysterCallerAccess = Readonly<{
|
|
|
171
172
|
keyId: string;
|
|
172
173
|
byInstallation: ReadonlyMap<string, AppToolPolicy>;
|
|
173
174
|
}> | null;
|
|
174
|
-
/**
|
|
175
|
-
* its declared operations and its compiled tool schemas. */
|
|
175
|
+
/** Immutable Release data shared by captures; no validator graphs live here. */
|
|
176
176
|
export type ReleaseSurface = Readonly<{
|
|
177
177
|
release: VerifiedRelease;
|
|
178
178
|
api: NonNullable<VerifiedRelease["api"]>;
|
|
179
179
|
declaredOperations: DeclaredOperations;
|
|
180
|
-
|
|
180
|
+
methods: NonNullable<VerifiedRelease["api"]>["methods"];
|
|
181
181
|
}>;
|
|
182
182
|
/**
|
|
183
|
-
* Per-host
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
* Release ID and artifact digest, not on every catalog capture: an MCP call
|
|
187
|
-
* captures the catalog several times (refresh, session acquire, the pre-call
|
|
188
|
-
* authority check), and redoing this for every installed App on each one
|
|
189
|
-
* exhausted a Durable Object's CPU and memory. Only Release-derived facts are
|
|
190
|
-
* cached; installation authority, caller access and tool-family listings
|
|
191
|
-
* are still read fresh on every capture. A failed build is not cached.
|
|
183
|
+
* Per-host verified data, keyed by immutable Release ID and artifact digest.
|
|
184
|
+
* Authority, caller access and tool-family listings stay fresh per capture.
|
|
185
|
+
* Failed verification is not cached.
|
|
192
186
|
*/
|
|
193
187
|
export type AppSurfaceCache = (release: Installation["release"]) => Promise<ReleaseSurface | null>;
|
|
194
188
|
/** Make one {@link AppSurfaceCache} for a host whose artifact store and capabilities stay fixed. */
|
|
195
189
|
export declare function appSurfaceCache(host: Pick<OysterHost, "artifacts" | "capabilities">): AppSurfaceCache;
|
|
196
190
|
/** Derive the current principal's admitted App modules and grants. */
|
|
197
|
-
export declare function oysterAppSnapshot(host: Pick<OysterHost, "installations" | "instances" | "artifacts" | "guestLimits" | "backgroundOriginKey" | "computerId" | "capabilities" | "appToolPolicy" | "keys">, surfaces: AppSurfaceCache, principal:
|
|
198
|
-
/** Shares one tool-family listing per Instance across the two
|
|
199
|
-
* `appModules` passes one `oysterAppMethods` call can make (the eager
|
|
200
|
-
* grants/version pass, and a session-scoped re-derivation); never shared
|
|
201
|
-
* across separate calls, so it never serves another request's listing. */
|
|
202
|
-
familyListingCache?: Map<string, unknown>): Promise<Readonly<{
|
|
191
|
+
export declare function oysterAppSnapshot(host: Pick<OysterHost, "installations" | "instances" | "artifacts" | "guestLimits" | "backgroundOriginKey" | "computerId" | "capabilities" | "appToolPolicy" | "keys" | "approvals">, surfaces: AppSurfaceCache, principal: TenantPrincipal, installations: readonly Installation[], access: OysterCallerAccess, sessionId: string | null, mode: "direct" | "approval"): Promise<Readonly<{
|
|
203
192
|
modules: Readonly<Record<string, ModuleRegistration>>;
|
|
204
193
|
levels: Readonly<Record<string, AppToolPolicy["level"]>>;
|
|
205
194
|
fences: Readonly<Record<string, string>>;
|
|
@@ -208,14 +197,13 @@ familyListingCache?: Map<string, unknown>): Promise<Readonly<{
|
|
|
208
197
|
/**
|
|
209
198
|
* Capture installed App methods for MCP or agents as one principal-scoped source.
|
|
210
199
|
* Enabled installations become `env.<APP>` modules using the Instance call path; a dotted method such as `repos.list` is `env.<APP>.repos.list()`.
|
|
211
|
-
*
|
|
212
|
-
* method. A key sees only the installations its own grants admit, each at
|
|
200
|
+
* Owners see every discoverable method. A key sees only the installations its own grants admit, each at
|
|
213
201
|
* the level (`read`/`write`) those grants give it; Mycelium itself refuses a
|
|
214
202
|
* write call up front when the module's own grant is `read`
|
|
215
203
|
* (`mycelium/src/access.ts`). Call again to capture changes to Releases,
|
|
216
204
|
* installation authority or the caller's own grants.
|
|
217
205
|
*/
|
|
218
|
-
export declare function oysterAppMethods(host: Pick<OysterHost, "installations" | "instances" | "artifacts" | "guestLimits" | "backgroundOriginKey" | "computerId" | "capabilities" | "appToolPolicy" | "keys" | "approvals">, principal:
|
|
206
|
+
export declare function oysterAppMethods(host: Pick<OysterHost, "installations" | "instances" | "artifacts" | "guestLimits" | "backgroundOriginKey" | "computerId" | "capabilities" | "appToolPolicy" | "keys" | "approvals">, principal: TenantPrincipal, access: OysterCallerAccess, surfaces: AppSurfaceCache, preloadedInstallations?: readonly Installation[]): Promise<CapabilitySource & {
|
|
219
207
|
readonly grants: Grants;
|
|
220
208
|
}>;
|
|
221
209
|
/**
|
|
@@ -224,7 +212,7 @@ export declare function oysterAppMethods(host: Pick<OysterHost, "installations"
|
|
|
224
212
|
* still checks its host-issued grant on every call. Null when the host has no
|
|
225
213
|
* Computer or the caller has no access. Grants name the captured module.
|
|
226
214
|
*/
|
|
227
|
-
export declare function oysterComputerTools(host: Pick<OysterHost, "computerId" | "computer">, principal:
|
|
215
|
+
export declare function oysterComputerTools(host: Pick<OysterHost, "computerId" | "computer">, principal: TenantPrincipal): Promise<(CapabilitySource & {
|
|
228
216
|
readonly grants: Grants;
|
|
229
217
|
}) | null>;
|
|
230
218
|
/**
|