@fungi.computer/oyster 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents.d.ts +2 -1
- package/dist/agents.js +63 -8
- package/dist/apps/gate.d.ts +4 -0
- package/dist/apps/gate.js +3 -0
- package/dist/boot-host-script.js +1 -1
- package/dist/build/candidate-cli.mjs +19 -4
- package/dist/channels/binding.d.ts +63 -0
- package/dist/channels/binding.js +85 -0
- package/dist/channels/conversations.d.ts +235 -0
- package/dist/channels/conversations.js +705 -0
- package/dist/channels/ids.d.ts +8 -0
- package/dist/channels/ids.js +15 -0
- package/dist/host.d.ts +1 -1
- package/dist/host.js +21 -4
- package/dist/keys.d.ts +4 -1
- package/dist/keys.js +15 -4
- package/dist/machine-link/authority.d.ts +7 -0
- package/dist/machine-link/delivery.d.ts +12 -0
- package/dist/machine-link/delivery.js +22 -1
- package/dist/machine-link/disposition.d.ts +16 -0
- package/dist/machine-link/disposition.js +65 -0
- package/dist/machine-link/host.d.ts +11 -2
- package/dist/machine-link/host.js +125 -5
- package/dist/machine-link/link.d.ts +4 -0
- package/dist/machine-link/link.js +47 -1
- package/dist/page-script.js +4 -4
- package/dist/standalone/agent-mail.js +4 -1
- package/dist/standalone/agents.d.ts +8 -0
- package/dist/standalone/agents.js +21 -2
- package/dist/standalone/channel-session-door.d.ts +4 -0
- package/dist/standalone/channel-session-door.js +42 -0
- package/dist/standalone/machine-link.d.ts +2 -0
- package/dist/standalone/machine-link.js +34 -1
- package/dist/testing/capabilities.d.ts +2 -0
- package/dist/testing/capabilities.js +3 -1
- package/dist/workers-git.js +1 -1
- package/dist/workers.d.ts +3 -0
- package/docs/api/agents.md +8 -4
- package/docs/api/apps.md +8 -4
- package/docs/api/browser.md +40 -16
- package/docs/api/keys.md +25 -3
- package/docs/api/workers.md +21 -14
- package/docs/site/agent-quickstart.mdx +23 -0
- package/examples/standalone/wrangler.jsonc +3 -4
- package/package.json +28 -16
|
@@ -23,6 +23,7 @@ export function agentMail(options) {
|
|
|
23
23
|
if (tick === undefined)
|
|
24
24
|
throw new Error("Agent Post lane is not open");
|
|
25
25
|
await tick();
|
|
26
|
+
await mail.stageUndeliverableReplies();
|
|
26
27
|
};
|
|
27
28
|
const mail = new AgentMail({ ...options, post: runtime, drain });
|
|
28
29
|
const host = {
|
|
@@ -57,6 +58,8 @@ export function agentMail(options) {
|
|
|
57
58
|
};
|
|
58
59
|
return {
|
|
59
60
|
host,
|
|
60
|
-
deliver: (value) =>
|
|
61
|
+
deliver: (value) => {
|
|
62
|
+
return mail.send(value);
|
|
63
|
+
},
|
|
61
64
|
};
|
|
62
65
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { type StandaloneTenancyEnv } from "./tenancy.js";
|
|
2
2
|
import type { PostEnvelope } from "@fungi.computer/carrier/post";
|
|
3
|
+
import { type ChannelConversations } from "../channels/conversations.js";
|
|
4
|
+
import type { ConversationBinding } from "../channels/binding.js";
|
|
3
5
|
import { type StandaloneMachineLinkBindings, type StandaloneMachineJoinResult } from "./machine-link.js";
|
|
4
6
|
import { type MachineAgentSettlementPrincipal, type MachineAgentHome, type MachineEnrollment, type MachineLinkCaller, type MachineLinkIdentity } from "../machine-link/index.js";
|
|
5
7
|
/** Workers AI binding surface used by the standalone provider adapter. */
|
|
@@ -78,6 +80,12 @@ export declare abstract class StandaloneAgentBase<Env extends StandaloneAgentEnv
|
|
|
78
80
|
#private;
|
|
79
81
|
/** The deployable root's host for this Agent's pinned tenant. */
|
|
80
82
|
protected abstract hostFor(scope: TenantScope): Promise<OysterHost>;
|
|
83
|
+
/**
|
|
84
|
+
* Compose Oyster's channel owner on this Agent's real store and Shiitake.
|
|
85
|
+
* A host must supply its own binding lookup after authenticating its edge;
|
|
86
|
+
* this does not install an inbound route or mint provider credentials.
|
|
87
|
+
*/
|
|
88
|
+
protected channelConversations(binding: (bindingId: string) => ConversationBinding | null): ChannelConversations;
|
|
81
89
|
private uiOwner;
|
|
82
90
|
private machineClient;
|
|
83
91
|
private computerSnapshot;
|
|
@@ -7,6 +7,8 @@ import { AgentCreateRefused, } from "@fungi.computer/shiitake/agent-control";
|
|
|
7
7
|
import { AgentDelegation, AgentDisclosureRefused, machineAgentCodecs, } from "@fungi.computer/shiitake/delegation";
|
|
8
8
|
import { FungiMessageMetaSchema } from "@fungi.computer/agar/agents";
|
|
9
9
|
import { agentMail } from "./agent-mail.js";
|
|
10
|
+
import { createChannelConversations, } from "../channels/conversations.js";
|
|
11
|
+
import { channelSessionDoor } from "./channel-session-door.js";
|
|
10
12
|
import { createStandaloneMachineEnrollment, standaloneMachineAgentHome, standaloneMachineLinkAuthority, } from "./machine-link.js";
|
|
11
13
|
import { createMachineLinkFences, MachineEnrollmentRefused, MachineLinkRefused, createMachineAgentClient, } from "../machine-link/index.js";
|
|
12
14
|
import { memberInboxItemSchema } from "@fungi.computer/puffball/inbox";
|
|
@@ -228,6 +230,22 @@ const instructions = [
|
|
|
228
230
|
].join("\n");
|
|
229
231
|
/** One agent: Shiitake's Cloudflare host over this Durable Object; its root supplies `hostFor`. */
|
|
230
232
|
export class StandaloneAgentBase extends ShiitakeDO {
|
|
233
|
+
/**
|
|
234
|
+
* Compose Oyster's channel owner on this Agent's real store and Shiitake.
|
|
235
|
+
* A host must supply its own binding lookup after authenticating its edge;
|
|
236
|
+
* this does not install an inbound route or mint provider credentials.
|
|
237
|
+
*/
|
|
238
|
+
channelConversations(binding) {
|
|
239
|
+
return createChannelConversations({
|
|
240
|
+
sql: this.ctx.storage.sql,
|
|
241
|
+
transactionSync: (body) => this.ctx.storage.transactionSync(body),
|
|
242
|
+
binding,
|
|
243
|
+
door: channelSessionDoor(() => this.shiitake(), () => {
|
|
244
|
+
// Standalone Agents have no Hub Team grants to deliver.
|
|
245
|
+
}),
|
|
246
|
+
now: () => Date.now(),
|
|
247
|
+
});
|
|
248
|
+
}
|
|
231
249
|
/** Each installed Release's verified catalog surface, built once per Release for this Agent's lifetime. */
|
|
232
250
|
#appSurfaces;
|
|
233
251
|
uiOwner;
|
|
@@ -479,10 +497,11 @@ export class StandaloneAgentBase extends ShiitakeDO {
|
|
|
479
497
|
throw new Error("Delegated reply identity is unavailable");
|
|
480
498
|
if (mail.reply.outcome === "undeliverable")
|
|
481
499
|
return undefined;
|
|
482
|
-
if (mail.from.
|
|
483
|
-
throw new Error("Delegated reply
|
|
500
|
+
if (mail.from.kind !== "agent")
|
|
501
|
+
throw new Error("Delegated reply Agent identity is unavailable");
|
|
484
502
|
return (await this.machineClient()).settlement(mail.from.agentId, mail.from.sessionId, mail.reply.commandId, mail.to.agentId);
|
|
485
503
|
},
|
|
504
|
+
onUndeliverable: (original) => this.delegation().localUndeliverable(original),
|
|
486
505
|
wake: () => this.wakeRuntime(),
|
|
487
506
|
}));
|
|
488
507
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ShiitakeOwnerAgent } from "@fungi.computer/shiitake/cloudflare";
|
|
2
|
+
import type { SessionDoor } from "../channels/conversations.js";
|
|
3
|
+
/** Translate a standalone Agent's Shiitake owner into Oyster's channel Session port. */
|
|
4
|
+
export declare function channelSessionDoor(open: () => Promise<ShiitakeOwnerAgent>, deliverGrants: (sessionId: string) => void): SessionDoor;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { authorMeta } from "@fungi.computer/agar/agents";
|
|
2
|
+
import { ShiitakeError } from "@fungi.computer/shiitake";
|
|
3
|
+
/** Translate a standalone Agent's Shiitake owner into Oyster's channel Session port. */
|
|
4
|
+
export function channelSessionDoor(open, deliverGrants) {
|
|
5
|
+
return {
|
|
6
|
+
createSession: async (requestId) => (await open()).createSession(requestId),
|
|
7
|
+
deliverGrants,
|
|
8
|
+
withdraw: async (sessionId, requestId) => (await open()).session(sessionId).withdraw(requestId),
|
|
9
|
+
latestPending: async (sessionId, visitor) => (await open())
|
|
10
|
+
.withMeta(authorMeta(visitor))
|
|
11
|
+
.session(sessionId)
|
|
12
|
+
.latestPending(),
|
|
13
|
+
prompt: async (sessionId, { requestId, text, meta }) => {
|
|
14
|
+
// The conversation row exists now, so its channel admission is known;
|
|
15
|
+
// deliver Agent tools before the first run can call one.
|
|
16
|
+
deliverGrants(sessionId);
|
|
17
|
+
const agent = await open();
|
|
18
|
+
return await agent.withMeta(meta).session(sessionId).command({
|
|
19
|
+
type: "prompt",
|
|
20
|
+
requestId,
|
|
21
|
+
text,
|
|
22
|
+
delivery: "followUp",
|
|
23
|
+
});
|
|
24
|
+
},
|
|
25
|
+
line: async (sessionId, input) => {
|
|
26
|
+
const agent = await open();
|
|
27
|
+
const meta = input.author === "operator" ? authorMeta(input.identity) : input.meta;
|
|
28
|
+
await agent.withMeta(meta).session(sessionId).command({
|
|
29
|
+
type: "operator",
|
|
30
|
+
requestId: input.requestId,
|
|
31
|
+
author: input.author,
|
|
32
|
+
text: input.text,
|
|
33
|
+
});
|
|
34
|
+
},
|
|
35
|
+
errorKind: (error, requestId) => error instanceof ShiitakeError &&
|
|
36
|
+
error.boundary === "contract" &&
|
|
37
|
+
error.cause === "closed" &&
|
|
38
|
+
error.requestId === requestId
|
|
39
|
+
? "closed_request"
|
|
40
|
+
: null,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -268,6 +268,8 @@ export declare class MachineLinkDO extends DurableObject<StandaloneMachineLinkEn
|
|
|
268
268
|
alarm(): Promise<void>;
|
|
269
269
|
fetch(request: Request): Promise<Response>;
|
|
270
270
|
deliver(id: MachineLinkIdentity, caller: MachineLinkCaller, agentId: string, envelope: PostEnvelope): Promise<PostDeliveryResult>;
|
|
271
|
+
deliverRelayDisposition(id: MachineLinkIdentity, relay: MachineLinkIdentity, original: PostEnvelope, reply: PostEnvelope): Promise<PostDeliveryResult>;
|
|
272
|
+
hasStagedDisposition(id: MachineLinkIdentity, original: PostEnvelope, reply: PostEnvelope): Promise<boolean>;
|
|
271
273
|
operation(id: MachineLinkIdentity, caller: MachineLinkCaller, agentId: string, operation: MachineAgentOperation): Promise<import("effect/Schema").Json>;
|
|
272
274
|
applyFence(fact: MachineLinkFenceFact): Promise<MachineLinkFenceResult>;
|
|
273
275
|
preflightErasure(teamId: string, deviceId: string): Promise<void>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { machineAgentReservations } from "@fungi.computer/shiitake/delegation";
|
|
1
|
+
import { machineAgentCodecs, machineAgentReservations, } from "@fungi.computer/shiitake/delegation";
|
|
2
2
|
import { createMachineEnrollment, MachineEnrollmentRefused, MachineLinkRefused, } from "../machine-link/index.js";
|
|
3
3
|
import { keyAllowsMachineEnroll, } from "../keys.js";
|
|
4
4
|
import { agentIds, createAgentDirectory, } from "@fungi.computer/shiitake/fleet";
|
|
@@ -8,6 +8,7 @@ import { bodyLimit } from "hono/body-limit";
|
|
|
8
8
|
import { validator } from "hono/validator";
|
|
9
9
|
import { machineEnrollmentSchema, machineLinkName, } from "../machine-link/index.js";
|
|
10
10
|
import { createMachineLink } from "../machine-link/workers.js";
|
|
11
|
+
import { deliverUndeliverableMail } from "../machine-link/delivery.js";
|
|
11
12
|
import { defaultTenant, tenantScope } from "../tenant.js";
|
|
12
13
|
import { presentedGitKey } from "../git-auth.js";
|
|
13
14
|
import { standaloneTenancy } from "./tenancy.js";
|
|
@@ -49,6 +50,7 @@ export function standaloneMachineLinkAuthority(env) {
|
|
|
49
50
|
return directory().machineDevice(id);
|
|
50
51
|
},
|
|
51
52
|
currentFence: async (id) => id.teamId !== teamId ? null : directory().machineFence(id),
|
|
53
|
+
agentHome: async (id, agentId) => id.teamId !== teamId ? null : directory().agentHome(agentId),
|
|
52
54
|
directory: async (id) => {
|
|
53
55
|
if (!(await authority.admitDevice(id)).live)
|
|
54
56
|
throw new MachineLinkRefused("actor_not_allowed");
|
|
@@ -69,6 +71,31 @@ export function standaloneMachineLinkAuthority(env) {
|
|
|
69
71
|
throw new MachineLinkRefused("actor_not_allowed");
|
|
70
72
|
return env.MACHINE_LINK.getByName(machineLinkName(next)).deliver(next, caller, facts.targetAgentId, envelope);
|
|
71
73
|
},
|
|
74
|
+
deliverUndeliverable: async (id, original, reply) => {
|
|
75
|
+
if ((await authority.currentFence(id)) === null)
|
|
76
|
+
return "refused";
|
|
77
|
+
const mail = original.event;
|
|
78
|
+
if (mail.from.kind !== "agent")
|
|
79
|
+
return "refused";
|
|
80
|
+
const home = await authority.agentHome(id, mail.from.agentId);
|
|
81
|
+
if (home === null)
|
|
82
|
+
throw new MachineLinkRefused("unavailable");
|
|
83
|
+
return await deliverUndeliverableMail({
|
|
84
|
+
identity: id,
|
|
85
|
+
original,
|
|
86
|
+
reply,
|
|
87
|
+
senderHome: home,
|
|
88
|
+
hosted: env.AGENTS,
|
|
89
|
+
accepted: machineAgentCodecs.accepted,
|
|
90
|
+
machine: async (deviceId, envelope) => {
|
|
91
|
+
const destination = { teamId, deviceId };
|
|
92
|
+
return await env.MACHINE_LINK.getByName(machineLinkName(destination)).deliverRelayDisposition(destination, id, original, envelope);
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
},
|
|
96
|
+
verifyRelayDisposition: async (relay, original, reply) => {
|
|
97
|
+
return await env.MACHINE_LINK.getByName(machineLinkName(relay)).hasStagedDisposition(relay, original, reply);
|
|
98
|
+
},
|
|
72
99
|
canErase: async (id) => {
|
|
73
100
|
if (id.teamId !== teamId)
|
|
74
101
|
return false;
|
|
@@ -185,6 +212,12 @@ export class MachineLinkDO extends DurableObject {
|
|
|
185
212
|
deliver(id, caller, agentId, envelope) {
|
|
186
213
|
return this.link.deliver(id, caller, agentId, envelope);
|
|
187
214
|
}
|
|
215
|
+
deliverRelayDisposition(id, relay, original, reply) {
|
|
216
|
+
return this.link.deliverRelayDisposition(id, relay, original, reply);
|
|
217
|
+
}
|
|
218
|
+
hasStagedDisposition(id, original, reply) {
|
|
219
|
+
return this.link.hasStagedDisposition(id, original, reply);
|
|
220
|
+
}
|
|
188
221
|
operation(id, caller, agentId, operation) {
|
|
189
222
|
return this.link.operation(id, caller, agentId, operation);
|
|
190
223
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
2
|
import { type AppCapabilityId } from "@fungi.computer/puffball/app";
|
|
3
3
|
import { type OysterAgents } from "../agents.js";
|
|
4
|
+
import type { LiveOysterKey } from "../keys.js";
|
|
4
5
|
import { type FileSource } from "../files.js";
|
|
5
6
|
import { type OysterAuthResult, type OysterCapability } from "../host.js";
|
|
6
7
|
/** How one capability fixture host admits its single installation. */
|
|
@@ -19,6 +20,7 @@ export type CapabilityFixtureOptions = Readonly<{
|
|
|
19
20
|
visitor?: boolean;
|
|
20
21
|
authenticate?: (request: Request) => Promise<OysterAuthResult>;
|
|
21
22
|
agents?: OysterAgents;
|
|
23
|
+
liveKey?: (keyId: string) => Promise<LiveOysterKey | null>;
|
|
22
24
|
}>;
|
|
23
25
|
/** One Oyster host with a single installation whose capability grants the options shape. */
|
|
24
26
|
export declare function capabilityFixture(options?: CapabilityFixtureOptions): Promise<{
|
|
@@ -84,7 +84,7 @@ export async function capabilityFixture(options = {}) {
|
|
|
84
84
|
if (options.visitor)
|
|
85
85
|
installation = { ...installation, visibility: "public" };
|
|
86
86
|
const readOnly = () => Promise.reject(new Error("Fixture installations are read-only"));
|
|
87
|
-
|
|
87
|
+
let base = {
|
|
88
88
|
bootUi: () => Promise.resolve({ kind: "passed" }),
|
|
89
89
|
auth: {
|
|
90
90
|
resolve: () => Promise.reject(new Error("Capability fixture resolves no actor")),
|
|
@@ -116,6 +116,8 @@ export async function capabilityFixture(options = {}) {
|
|
|
116
116
|
},
|
|
117
117
|
capabilities,
|
|
118
118
|
};
|
|
119
|
+
if (options.liveKey !== undefined)
|
|
120
|
+
base = { ...base, keys: { live: options.liveKey } };
|
|
119
121
|
const host = options.agents === undefined
|
|
120
122
|
? base
|
|
121
123
|
: { ...base, ownerRoutes: agentApprovalRoutes(options.agents) };
|
package/dist/workers-git.js
CHANGED
|
@@ -1117,7 +1117,7 @@ export async function oysterGitWorkerHost(env, options, selected, request) {
|
|
|
1117
1117
|
const base = oysterWorkerHost(env, auth, policyOptions);
|
|
1118
1118
|
const capabilities = [
|
|
1119
1119
|
...(base.capabilities ?? []),
|
|
1120
|
-
...(agents === undefined ? [] : agentCapabilities(agents)),
|
|
1120
|
+
...(agents === undefined ? [] : agentCapabilities(agents, keysLive)),
|
|
1121
1121
|
...(options.capabilities?.(env, workspace, keysLive) ?? []),
|
|
1122
1122
|
];
|
|
1123
1123
|
let gitOptions = { scope };
|
package/dist/workers.d.ts
CHANGED
|
@@ -138,6 +138,7 @@ export declare class OysterRegistry<Env extends OysterWorkerEnv = OysterWorkerEn
|
|
|
138
138
|
};
|
|
139
139
|
readonly reversibility: "reversible" | "irreversible-external";
|
|
140
140
|
readonly requestedAt: number;
|
|
141
|
+
readonly denialNote?: string | undefined;
|
|
141
142
|
readonly evidence?: readonly {
|
|
142
143
|
readonly kind: "artifact";
|
|
143
144
|
readonly ref: string;
|
|
@@ -155,6 +156,7 @@ export declare class OysterRegistry<Env extends OysterWorkerEnv = OysterWorkerEn
|
|
|
155
156
|
}>, actor: HostActor, input: ApprovalAnswerInput): Promise<Readonly<{
|
|
156
157
|
kind: "refused";
|
|
157
158
|
reason: "pending" | "expired" | "not-found" | "not-eligible" | "already-answered" | "stale-revision" | "declined" | "fenced" | "not-granted" | "read-only" | "in-flight" | "outcome-unknown" | "identity-conflict";
|
|
159
|
+
denialNote?: string;
|
|
158
160
|
}> | Readonly<{
|
|
159
161
|
kind: "answered";
|
|
160
162
|
view: import("@fungi.computer/approvals").ApprovalView;
|
|
@@ -166,6 +168,7 @@ export declare class OysterRegistry<Env extends OysterWorkerEnv = OysterWorkerEn
|
|
|
166
168
|
}>, actor: HostActor, input: AutoApproveInput): Promise<Readonly<{
|
|
167
169
|
kind: "refused";
|
|
168
170
|
reason: "pending" | "expired" | "not-found" | "not-eligible" | "already-answered" | "stale-revision" | "declined" | "fenced" | "not-granted" | "read-only" | "in-flight" | "outcome-unknown" | "identity-conflict";
|
|
171
|
+
denialNote?: string;
|
|
169
172
|
}> | Readonly<{
|
|
170
173
|
kind: "updated";
|
|
171
174
|
rule: AutoApproveInput;
|
package/docs/api/agents.md
CHANGED
|
@@ -358,16 +358,20 @@ optional bearer.
|
|
|
358
358
|
### agentCapabilities()
|
|
359
359
|
|
|
360
360
|
```ts
|
|
361
|
-
function agentCapabilities(
|
|
361
|
+
function agentCapabilities(
|
|
362
|
+
agents,
|
|
363
|
+
liveKey,
|
|
364
|
+
): readonly Readonly<object & object>[];
|
|
362
365
|
```
|
|
363
366
|
|
|
364
367
|
Agent adapters admitted against each App's declared capabilities.
|
|
365
368
|
|
|
366
369
|
#### Parameters
|
|
367
370
|
|
|
368
|
-
| Parameter | Type
|
|
369
|
-
| --------- |
|
|
370
|
-
| `agents` | [`OysterAgents`](#oysteragents)
|
|
371
|
+
| Parameter | Type |
|
|
372
|
+
| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
373
|
+
| `agents` | [`OysterAgents`](#oysteragents) |
|
|
374
|
+
| `liveKey` | (`keyId`) => `Promise`\< \| `Readonly`\<\{ `keyId`: `string`; `owner`: `string`; `grantVersion`: `number`; `grants`: readonly `Readonly`\<\{ `capability`: [`OysterKeyCapability`](keys.md#oysterkeycapability); `targets`: `GrantTargets`; \}\>[]; \}\> \| `null`\> |
|
|
371
375
|
|
|
372
376
|
#### Returns
|
|
373
377
|
|
package/docs/api/apps.md
CHANGED
|
@@ -852,6 +852,10 @@ const gateRequestSchema: ZodLazy<ZodDiscriminatedUnion<[ZodObject<{
|
|
|
852
852
|
| "channels.read"
|
|
853
853
|
| "channels.personal.manage", unknown>>;
|
|
854
854
|
operation: ZodString;
|
|
855
|
+
effect: ZodEnum<{
|
|
856
|
+
read: "read";
|
|
857
|
+
write: "write";
|
|
858
|
+
}>;
|
|
855
859
|
}, $strict>], "kind">>;
|
|
856
860
|
```
|
|
857
861
|
|
|
@@ -1676,10 +1680,10 @@ stay outside this pure gate.
|
|
|
1676
1680
|
|
|
1677
1681
|
#### Parameters
|
|
1678
1682
|
|
|
1679
|
-
| Parameter | Type
|
|
1680
|
-
| ------------ |
|
|
1681
|
-
| `facts` | [`GateFacts`](#gatefacts)
|
|
1682
|
-
| `rawRequest` | \| \{ `kind`: `"method"`; `name`: `string`; `input`: `unknown`; `via`: `"ui"` \| `"agent"` \| `"mcp"` \| `"workflow"`; `resource?`: `Readonly`\<\{ `kind`: `string`; `id`: `string`; \}\>; \} \| \{ `kind`: `"capability"`; `capability`: \| `"computer.list"` \| `"computer.readFile"` \| `"computer.writeFile"` \| `"computer.execute"` \| `"approvals"` \| `"agent.list"` \| `"agent.manage"` \| `"agent.rename"` \| `"agent.avatar"` \| `"agent.toolPolicy"` \| `"agent.read"` \| `"agent.send"` \| `"llm.generation"` \| `"media.generation"` \| `"games:servers"` \| `"git.repository"` \| `"apps.open"` \| `"apps.call"` \| `"app.storage"` \| `"app.events.emit"` \| `"team.workbenchHost"` \| `"marketplace.browse"` \| `"marketplace.purchase"` \| `"workflow.run"` \| `"workflow.publish"` \| `"workflow.arm"` \| `"world"` \| `"channels.read"` \| `"channels.personal.manage"`; `operation?`: `string`; `resource`: \| `"any"` \| `Readonly`\<\{ `kind`: `string`; `id`: `string`; \}\>; \} \| \{ `kind`: `"capability-route"`; `capability`: \| `"computer.list"` \| `"computer.readFile"` \| `"computer.writeFile"` \| `"computer.execute"` \| `"approvals"` \| `"agent.list"` \| `"agent.manage"` \| `"agent.rename"` \| `"agent.avatar"` \| `"agent.toolPolicy"` \| `"agent.read"` \| `"agent.send"` \| `"llm.generation"` \| `"media.generation"` \| `"games:servers"` \| `"git.repository"` \| `"apps.open"` \| `"apps.call"` \| `"app.storage"` \| `"app.events.emit"` \| `"team.workbenchHost"` \| `"marketplace.browse"` \| `"marketplace.purchase"` \| `"workflow.run"` \| `"workflow.publish"` \| `"workflow.arm"` \| `"world"` \| `"channels.read"` \| `"channels.personal.manage"`; `operation`: `string`; \} \| \{ `kind`: `"binding"`; `binding`: \| `"BACKGROUND"` \| `"GIT"` \| `"EVENTS"` \| `"APPS"` \| `"WORKFLOWS"` \| `"APP_STORAGE"` \| `"MEDIA"` \| `"COMPUTER"` \| `"INBOX"` \| `"AGENTS"`; `operation`: `string`; \} \| \{ `kind`: `"list-tools"`; \} \| \{ `kind`: `"backend"`; \} |
|
|
1683
|
+
| Parameter | Type |
|
|
1684
|
+
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1685
|
+
| `facts` | [`GateFacts`](#gatefacts) |
|
|
1686
|
+
| `rawRequest` | \| \{ `kind`: `"method"`; `name`: `string`; `input`: `unknown`; `via`: `"ui"` \| `"agent"` \| `"mcp"` \| `"workflow"`; `resource?`: `Readonly`\<\{ `kind`: `string`; `id`: `string`; \}\>; \} \| \{ `kind`: `"capability"`; `capability`: \| `"computer.list"` \| `"computer.readFile"` \| `"computer.writeFile"` \| `"computer.execute"` \| `"approvals"` \| `"agent.list"` \| `"agent.manage"` \| `"agent.rename"` \| `"agent.avatar"` \| `"agent.toolPolicy"` \| `"agent.read"` \| `"agent.send"` \| `"llm.generation"` \| `"media.generation"` \| `"games:servers"` \| `"git.repository"` \| `"apps.open"` \| `"apps.call"` \| `"app.storage"` \| `"app.events.emit"` \| `"team.workbenchHost"` \| `"marketplace.browse"` \| `"marketplace.purchase"` \| `"workflow.run"` \| `"workflow.publish"` \| `"workflow.arm"` \| `"world"` \| `"channels.read"` \| `"channels.personal.manage"`; `operation?`: `string`; `resource`: \| `"any"` \| `Readonly`\<\{ `kind`: `string`; `id`: `string`; \}\>; \} \| \{ `kind`: `"capability-route"`; `capability`: \| `"computer.list"` \| `"computer.readFile"` \| `"computer.writeFile"` \| `"computer.execute"` \| `"approvals"` \| `"agent.list"` \| `"agent.manage"` \| `"agent.rename"` \| `"agent.avatar"` \| `"agent.toolPolicy"` \| `"agent.read"` \| `"agent.send"` \| `"llm.generation"` \| `"media.generation"` \| `"games:servers"` \| `"git.repository"` \| `"apps.open"` \| `"apps.call"` \| `"app.storage"` \| `"app.events.emit"` \| `"team.workbenchHost"` \| `"marketplace.browse"` \| `"marketplace.purchase"` \| `"workflow.run"` \| `"workflow.publish"` \| `"workflow.arm"` \| `"world"` \| `"channels.read"` \| `"channels.personal.manage"`; `operation`: `string`; `effect`: `"read"` \| `"write"`; \} \| \{ `kind`: `"binding"`; `binding`: \| `"BACKGROUND"` \| `"GIT"` \| `"EVENTS"` \| `"APPS"` \| `"WORKFLOWS"` \| `"APP_STORAGE"` \| `"MEDIA"` \| `"COMPUTER"` \| `"INBOX"` \| `"AGENTS"`; `operation`: `string`; \} \| \{ `kind`: `"list-tools"`; \} \| \{ `kind`: `"backend"`; \} |
|
|
1683
1687
|
|
|
1684
1688
|
#### Returns
|
|
1685
1689
|
|
package/docs/api/browser.md
CHANGED
|
@@ -91,7 +91,7 @@ Data.TaggedError("CloudBrowserUrlDenied").message;
|
|
|
91
91
|
|
|
92
92
|
### CloudBrowserUpstreamError
|
|
93
93
|
|
|
94
|
-
The provider
|
|
94
|
+
The configured provider answered with a failure or is unreachable.
|
|
95
95
|
|
|
96
96
|
#### Extends
|
|
97
97
|
|
|
@@ -593,10 +593,11 @@ type BrowserLimits = Readonly<{
|
|
|
593
593
|
|
|
594
594
|
`@fungi.computer/oyster/browser`: the one agent browser for the Hub and a
|
|
595
595
|
standalone Oyster. A host builds the port from a provider (`browserRunSessions`
|
|
596
|
-
or `bindingSessions
|
|
597
|
-
required metering owner and an event sink;
|
|
598
|
-
egress policy and billing order;
|
|
599
|
-
`cloudBrowser` tool. Raw CDP stays
|
|
596
|
+
or `bindingSessions` or `onboxBrowserSessions` on staging, plus Browser Run
|
|
597
|
+
Quick Reads when it has them), its required metering owner and an event sink;
|
|
598
|
+
`createBrowser` owns lease ownership, egress policy and billing order;
|
|
599
|
+
`browserCapabilitySource` is the agent's `cloudBrowser` tool. Raw CDP stays
|
|
600
|
+
internal to this directory.
|
|
600
601
|
|
|
601
602
|
---
|
|
602
603
|
|
|
@@ -643,10 +644,11 @@ type Browser = Readonly<{
|
|
|
643
644
|
|
|
644
645
|
`@fungi.computer/oyster/browser`: the one agent browser for the Hub and a
|
|
645
646
|
standalone Oyster. A host builds the port from a provider (`browserRunSessions`
|
|
646
|
-
or `bindingSessions
|
|
647
|
-
required metering owner and an event sink;
|
|
648
|
-
egress policy and billing order;
|
|
649
|
-
`cloudBrowser` tool. Raw CDP stays
|
|
647
|
+
or `bindingSessions` or `onboxBrowserSessions` on staging, plus Browser Run
|
|
648
|
+
Quick Reads when it has them), its required metering owner and an event sink;
|
|
649
|
+
`createBrowser` owns lease ownership, egress policy and billing order;
|
|
650
|
+
`browserCapabilitySource` is the agent's `cloudBrowser` tool. Raw CDP stays
|
|
651
|
+
internal to this directory.
|
|
650
652
|
|
|
651
653
|
---
|
|
652
654
|
|
|
@@ -1058,9 +1060,9 @@ caller (`browser.ts`) owns the deadline through `signal`.
|
|
|
1058
1060
|
type BrowserSessions = Readonly<{}>;
|
|
1059
1061
|
```
|
|
1060
1062
|
|
|
1061
|
-
The provider half of the host port.
|
|
1062
|
-
|
|
1063
|
-
|
|
1063
|
+
The provider half of the host port. Each step is separate so `createBrowser`
|
|
1064
|
+
owns a provider session id from the moment it exists, including when its CDP
|
|
1065
|
+
connect then fails.
|
|
1064
1066
|
|
|
1065
1067
|
---
|
|
1066
1068
|
|
|
@@ -1338,10 +1340,11 @@ function createBrowser(host, limits): Browser;
|
|
|
1338
1340
|
|
|
1339
1341
|
`@fungi.computer/oyster/browser`: the one agent browser for the Hub and a
|
|
1340
1342
|
standalone Oyster. A host builds the port from a provider (`browserRunSessions`
|
|
1341
|
-
or `bindingSessions
|
|
1342
|
-
required metering owner and an event sink;
|
|
1343
|
-
egress policy and billing order;
|
|
1344
|
-
`cloudBrowser` tool. Raw CDP stays
|
|
1343
|
+
or `bindingSessions` or `onboxBrowserSessions` on staging, plus Browser Run
|
|
1344
|
+
Quick Reads when it has them), its required metering owner and an event sink;
|
|
1345
|
+
`createBrowser` owns lease ownership, egress policy and billing order;
|
|
1346
|
+
`browserCapabilitySource` is the agent's `cloudBrowser` tool. Raw CDP stays
|
|
1347
|
+
internal to this directory.
|
|
1345
1348
|
|
|
1346
1349
|
#### Parameters
|
|
1347
1350
|
|
|
@@ -1438,3 +1441,24 @@ A standalone Oyster's own Workers BROWSER binding.
|
|
|
1438
1441
|
#### Returns
|
|
1439
1442
|
|
|
1440
1443
|
[`BrowserSessions`](#browsersessions)
|
|
1444
|
+
|
|
1445
|
+
---
|
|
1446
|
+
|
|
1447
|
+
### onboxBrowserSessions()
|
|
1448
|
+
|
|
1449
|
+
```ts
|
|
1450
|
+
function onboxBrowserSessions(origin, fetcher?): BrowserSessions;
|
|
1451
|
+
```
|
|
1452
|
+
|
|
1453
|
+
Staging uses Chromium's native CDP browser contexts; no session store.
|
|
1454
|
+
|
|
1455
|
+
#### Parameters
|
|
1456
|
+
|
|
1457
|
+
| Parameter | Type |
|
|
1458
|
+
| --------- | ------------------------- |
|
|
1459
|
+
| `origin` | `string` |
|
|
1460
|
+
| `fetcher` | [`HttpFetch`](#httpfetch) |
|
|
1461
|
+
|
|
1462
|
+
#### Returns
|
|
1463
|
+
|
|
1464
|
+
[`BrowserSessions`](#browsersessions)
|
package/docs/api/keys.md
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
```ts
|
|
16
16
|
type OysterKeyCapability =
|
|
17
|
-
"apps.read" | "apps.use" | "computer.use" | "machine.enroll";
|
|
17
|
+
"apps.read" | "apps.use" | "agents.use" | "computer.use" | "machine.enroll";
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
Every capability an Oyster key may carry.
|
|
@@ -88,8 +88,9 @@ const oysterKeyVocabulary: GrantVocabulary<OysterKeyCapability>;
|
|
|
88
88
|
`apps.use` also reaches `apps.read` (one level, per `defineGrants`'s own
|
|
89
89
|
`implies`): a key that may change an App's data may also read it. `computer.use`
|
|
90
90
|
has no narrower capability under it; the Computer is on or off, host-wide.
|
|
91
|
-
`
|
|
92
|
-
|
|
91
|
+
`agents.use` selects Agent ids independently of App grants. `machine.enroll`
|
|
92
|
+
admits enrollment only; it implies no App or Computer access. The enrollment
|
|
93
|
+
owner, not this vocabulary, owns one-use consumption.
|
|
93
94
|
|
|
94
95
|
---
|
|
95
96
|
|
|
@@ -159,6 +160,27 @@ name for MCP) to reach the full `AppToolAccess`.
|
|
|
159
160
|
|
|
160
161
|
---
|
|
161
162
|
|
|
163
|
+
### keyAgentAccess()
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
function keyAgentAccess(key, agentId): boolean;
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Agent access is scoped to the granted Agent ids, never inferred from App grants.
|
|
170
|
+
|
|
171
|
+
#### Parameters
|
|
172
|
+
|
|
173
|
+
| Parameter | Type |
|
|
174
|
+
| --------- | --------------------------------- |
|
|
175
|
+
| `key` | [`LiveOysterKey`](#liveoysterkey) |
|
|
176
|
+
| `agentId` | `string` |
|
|
177
|
+
|
|
178
|
+
#### Returns
|
|
179
|
+
|
|
180
|
+
`boolean`
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
162
184
|
### keyAllowsComputerUse()
|
|
163
185
|
|
|
164
186
|
```ts
|
package/docs/api/workers.md
CHANGED
|
@@ -253,6 +253,7 @@ approvalGet(
|
|
|
253
253
|
| "outcome-unknown"
|
|
254
254
|
| "identity-conflict"
|
|
255
255
|
| null;
|
|
256
|
+
denialNote?: string;
|
|
256
257
|
requestedAt: number;
|
|
257
258
|
expiresAt: number;
|
|
258
259
|
updatedAt: number;
|
|
@@ -289,9 +290,9 @@ Read one member-scoped view; the native home resolves membership again.
|
|
|
289
290
|
`"not-found"` \| `"not-eligible"` \| `"already-answered"` \| `"stale-revision"`
|
|
290
291
|
\| `"declined"` \| `"fenced"` \| `"not-granted"` \| `"read-only"` \|
|
|
291
292
|
`"in-flight"` \| `"outcome-unknown"` \| `"identity-conflict"` \| `null`;
|
|
292
|
-
`
|
|
293
|
-
`revision`: `number`; \}; `canAnswer`: `boolean`;
|
|
294
|
-
`boolean`; \} \| `null`\>
|
|
293
|
+
`denialNote?`: `string`; `requestedAt`: `number`; `expiresAt`: `number`;
|
|
294
|
+
`updatedAt`: `number`; `revision`: `number`; \}; `canAnswer`: `boolean`;
|
|
295
|
+
`canSetAutoApprove`: `boolean`; \} \| `null`\>
|
|
295
296
|
|
|
296
297
|
##### approvalAnswer()
|
|
297
298
|
|
|
@@ -353,6 +354,7 @@ approvalAnswer(
|
|
|
353
354
|
| "outcome-unknown"
|
|
354
355
|
| "identity-conflict"
|
|
355
356
|
| null;
|
|
357
|
+
denialNote?: string;
|
|
356
358
|
requestedAt: number;
|
|
357
359
|
expiresAt: number;
|
|
358
360
|
updatedAt: number;
|
|
@@ -377,6 +379,7 @@ approvalAnswer(
|
|
|
377
379
|
| "in-flight"
|
|
378
380
|
| "outcome-unknown"
|
|
379
381
|
| "identity-conflict";
|
|
382
|
+
denialNote?: string;
|
|
380
383
|
}>>;
|
|
381
384
|
```
|
|
382
385
|
|
|
@@ -388,10 +391,11 @@ Persist an eligible answer and attempt its native decision work once.
|
|
|
388
391
|
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
389
392
|
| `pin` | `Readonly`\<\{ `tenantId`: `string`; `origin`: `string`; \}\> |
|
|
390
393
|
| `actor` | \| \{ `kind`: `"member"`; `memberId`: `string`; \} \| \{ `kind`: `"agent"`; `agentId`: `string`; \} \| \{ `kind`: `"team-key"`; `keyId`: `string`; \} |
|
|
391
|
-
| `input` | \{ `recordId`: `string`; `decision`: `"approve"` \| `"deny"`; `expectedRevision`: `number`; \}
|
|
394
|
+
| `input` | \{ `recordId`: `string`; `decision`: `"approve"` \| `"deny"`; `expectedRevision`: `number`; `denialNote?`: `string`; \} |
|
|
392
395
|
| `input.recordId` | `string` |
|
|
393
396
|
| `input.decision` | `"approve"` \| `"deny"` |
|
|
394
397
|
| `input.expectedRevision` | `number` |
|
|
398
|
+
| `input.denialNote?` | `string` |
|
|
395
399
|
|
|
396
400
|
###### Returns
|
|
397
401
|
|
|
@@ -408,13 +412,14 @@ Persist an eligible answer and attempt its native decision work once.
|
|
|
408
412
|
`reason`: \| `"pending"` \| `"expired"` \| `"not-found"` \| `"not-eligible"` \|
|
|
409
413
|
`"already-answered"` \| `"stale-revision"` \| `"declined"` \| `"fenced"` \|
|
|
410
414
|
`"not-granted"` \| `"read-only"` \| `"in-flight"` \| `"outcome-unknown"` \|
|
|
411
|
-
`"identity-conflict"` \| `null`; `
|
|
412
|
-
`
|
|
413
|
-
`canSetAutoApprove`: `boolean`; \}; \}\> \|
|
|
414
|
-
`
|
|
415
|
-
`"
|
|
416
|
-
`"
|
|
417
|
-
`"identity-conflict"`;
|
|
415
|
+
`"identity-conflict"` \| `null`; `denialNote?`: `string`; `requestedAt`:
|
|
416
|
+
`number`; `expiresAt`: `number`; `updatedAt`: `number`; `revision`: `number`;
|
|
417
|
+
\}; `canAnswer`: `boolean`; `canSetAutoApprove`: `boolean`; \}; \}\> \|
|
|
418
|
+
`Readonly`\<\{ `kind`: `"refused"`; `reason`: \| `"pending"` \| `"expired"` \|
|
|
419
|
+
`"not-found"` \| `"not-eligible"` \| `"already-answered"` \| `"stale-revision"`
|
|
420
|
+
\| `"declined"` \| `"fenced"` \| `"not-granted"` \| `"read-only"` \|
|
|
421
|
+
`"in-flight"` \| `"outcome-unknown"` \| `"identity-conflict"`; `denialNote?`:
|
|
422
|
+
`string`; \}\>\>
|
|
418
423
|
|
|
419
424
|
##### approvalSetAutoApprove()
|
|
420
425
|
|
|
@@ -439,6 +444,7 @@ approvalSetAutoApprove(
|
|
|
439
444
|
| "in-flight"
|
|
440
445
|
| "outcome-unknown"
|
|
441
446
|
| "identity-conflict";
|
|
447
|
+
denialNote?: string;
|
|
442
448
|
}>
|
|
443
449
|
| Readonly<{
|
|
444
450
|
kind: "updated";
|
|
@@ -478,9 +484,10 @@ it.
|
|
|
478
484
|
`"expired"` \| `"not-found"` \| `"not-eligible"` \| `"already-answered"` \|
|
|
479
485
|
`"stale-revision"` \| `"declined"` \| `"fenced"` \| `"not-granted"` \|
|
|
480
486
|
`"read-only"` \| `"in-flight"` \| `"outcome-unknown"` \| `"identity-conflict"`;
|
|
481
|
-
\}\> \| `Readonly`\<\{ `kind`: `"updated"`; `rule`: \{
|
|
482
|
-
`string` \| `null`; \}; `family`: \{ `module`: `string`
|
|
483
|
-
`"write"` \| `"external"`; \}; `enabled`: `boolean`; \};
|
|
487
|
+
`denialNote?`: `string`; \}\> \| `Readonly`\<\{ `kind`: `"updated"`; `rule`: \{
|
|
488
|
+
`scope`: \{ `agentId`: `string` \| `null`; \}; `family`: \{ `module`: `string`
|
|
489
|
+
\| `null`; `access`: `"write"` \| `"external"`; \}; `enabled`: `boolean`; \};
|
|
490
|
+
\}\>\>
|
|
484
491
|
|
|
485
492
|
##### installationOperation()
|
|
486
493
|
|
|
@@ -10,6 +10,29 @@ Run every command exactly as written, from the directory each step names.
|
|
|
10
10
|
Compare its output with the expected output before you go on. When a step fails,
|
|
11
11
|
read the "If it fails" notes under it before trying anything else.
|
|
12
12
|
|
|
13
|
+
## Before you start
|
|
14
|
+
|
|
15
|
+
- **Cloudflare plan:** SQLite-backed Durable Objects are available on Workers
|
|
16
|
+
Free; they do not by themselves require Paid. Cloudflare's SQLite-backed
|
|
17
|
+
Durable Objects limits list 30 seconds of CPU per request by default,
|
|
18
|
+
configurable to 5 minutes. See Cloudflare's
|
|
19
|
+
[Durable Objects limits](https://developers.cloudflare.com/durable-objects/platform/limits/)
|
|
20
|
+
and
|
|
21
|
+
[Durable Objects pricing](https://developers.cloudflare.com/durable-objects/platform/pricing/).
|
|
22
|
+
- **Agent chat and AI Gateway:** By default, Agent chat uses the Workers AI
|
|
23
|
+
binding; it needs no separate provider key or Gateway setup. Prints uses your
|
|
24
|
+
account's default AI Gateway (created automatically); set `AI_GATEWAY_ID` to
|
|
25
|
+
use another. The routed models include `openai/gpt-image-2` and video IDs
|
|
26
|
+
`bytedance/seedance-2.0`, `bytedance/seedance-2.0-fast`,
|
|
27
|
+
`bytedance/seedance-2.0-mini`, `google/veo-3.1`, `google/veo-3.1-fast` and
|
|
28
|
+
`minimax/h3-max`.
|
|
29
|
+
|
|
30
|
+
- **Deployment plan:** Step 7 needs Workers Paid because this deployment uses
|
|
31
|
+
Containers for the Computer image; Cloudflare lists Containers as available on
|
|
32
|
+
the
|
|
33
|
+
[Workers Paid plan](https://developers.cloudflare.com/containers/platform/).
|
|
34
|
+
SQLite-backed Durable Objects alone do not require Paid.
|
|
35
|
+
|
|
13
36
|
## What Oyster is
|
|
14
37
|
|
|
15
38
|
- Oyster hosts Apps on one Cloudflare Worker, or on celld, a Workers-compatible
|