@fungi.computer/oyster 0.1.0 → 0.3.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 +376 -72
- package/dist/agent-avatars.d.ts +103 -0
- package/dist/agent-avatars.js +66 -0
- package/dist/agents.d.ts +15 -4
- package/dist/agents.js +43 -4
- package/dist/app-builds.d.ts +81 -8
- package/dist/app-builds.js +210 -60
- package/dist/app-storage.d.ts +3 -3
- package/dist/approval-capability.d.ts +5 -0
- package/dist/approval-capability.js +46 -0
- package/dist/approval-home.d.ts +51 -0
- package/dist/approval-home.js +226 -0
- package/dist/apps/catalog.d.ts +335 -0
- package/dist/apps/catalog.js +46 -0
- package/dist/apps/gate.d.ts +26 -12
- package/dist/apps/gate.js +48 -22
- package/dist/apps/index.d.ts +3 -1
- package/dist/apps/index.js +3 -1
- package/dist/apps/installation.d.ts +147 -66
- package/dist/apps/installation.js +193 -86
- package/dist/apps/permissions.d.ts +247 -0
- package/dist/apps/permissions.js +255 -0
- package/dist/boot-capability-route.d.ts +15 -0
- package/dist/boot-capability-route.js +196 -0
- package/dist/boot-capability.d.ts +103 -0
- package/dist/boot-capability.js +141 -0
- package/dist/boot-construction.d.ts +12 -0
- package/dist/boot-construction.js +175 -0
- package/dist/boot-gate.d.ts +267 -0
- package/dist/boot-gate.js +236 -0
- package/dist/boot-host-script.d.ts +2 -0
- package/dist/boot-host-script.js +1 -0
- package/dist/boot-mount.d.ts +21 -0
- package/dist/boot-mount.js +65 -0
- package/dist/browser/boot.d.ts +73 -0
- package/dist/browser/boot.js +502 -0
- package/dist/browser/browser.d.ts +104 -0
- package/dist/browser/browser.js +374 -0
- package/dist/browser/capability-source.d.ts +76 -0
- package/dist/browser/capability-source.js +338 -0
- package/dist/browser/cdp-client.d.ts +56 -0
- package/dist/browser/cdp-client.js +184 -0
- package/dist/browser/errors.d.ts +79 -0
- package/dist/browser/errors.js +32 -0
- package/dist/browser/index.d.ts +16 -0
- package/dist/browser/index.js +14 -0
- package/dist/browser/lease-registry.d.ts +61 -0
- package/dist/browser/lease-registry.js +101 -0
- package/dist/browser/operations.d.ts +225 -0
- package/dist/browser/operations.js +137 -0
- package/dist/browser/page.d.ts +25 -0
- package/dist/browser/page.js +172 -0
- package/dist/browser/quick-reads.d.ts +29 -0
- package/dist/browser/quick-reads.js +160 -0
- package/dist/browser/sessions.d.ts +52 -0
- package/dist/browser/sessions.js +118 -0
- package/dist/browser/url-policy.d.ts +18 -0
- package/dist/browser/url-policy.js +58 -0
- package/dist/build/app-imports.d.ts +12 -0
- package/dist/build/app-imports.js +105 -0
- package/dist/build/build-policy.d.ts +5 -3
- package/dist/build/build-policy.js +5 -3
- package/dist/build/candidate-builder.d.ts +1 -1
- package/dist/build/candidate-cli.mjs +8428 -337
- package/dist/build/local-build.d.ts +2 -0
- package/dist/build/local-build.js +28 -6
- package/dist/build/snapshot-package-store.d.ts +1 -1
- package/dist/build/snapshot-package-store.js +7 -2
- package/dist/build/workspace-build.d.ts +92 -5
- package/dist/build/workspace-build.js +93 -19
- package/dist/build-egress.js +4 -4
- package/dist/build.d.ts +1 -1
- package/dist/build.js +1 -1
- package/dist/catalog-frame.js +1 -1
- package/dist/cloudflare.d.ts +13 -3
- package/dist/cloudflare.js +106 -8
- package/dist/frame-binding-wire-script.js +1 -1
- package/dist/frame-binding-wire.d.ts +1 -1
- package/dist/frame-binding-wire.js +5 -3
- package/dist/git-auth.d.ts +0 -2
- package/dist/git-auth.js +0 -2
- package/dist/git-journal.js +4 -4
- package/dist/git.d.ts +0 -1
- package/dist/git.js +0 -2
- package/dist/host.d.ts +39 -13
- package/dist/host.js +268 -142
- package/dist/index.d.ts +26 -15
- package/dist/index.js +40 -16
- package/dist/install-release-cli.d.ts +2 -0
- package/dist/install-release-cli.js +46 -0
- package/dist/installation-client.d.ts +43 -0
- package/dist/installation-client.js +178 -0
- package/dist/installation-execution.d.ts +11 -0
- package/dist/installation-execution.js +184 -0
- package/dist/installation-operations.d.ts +1437 -0
- package/dist/installation-operations.js +664 -0
- package/dist/installation-store-generation.d.ts +3 -0
- package/dist/installation-store-generation.js +16 -0
- package/dist/instance-id.d.ts +10 -0
- package/dist/instance-id.js +10 -0
- package/dist/instance-refusal.d.ts +11 -0
- package/dist/instance-refusal.js +21 -0
- package/dist/internal/delivery.js +4 -2
- package/dist/internal/host-gate.js +7 -1
- package/dist/internal/registry-installations.js +4 -4
- package/dist/keys.d.ts +5 -1
- package/dist/keys.js +10 -2
- package/dist/machine-link/authority.d.ts +89 -0
- package/dist/machine-link/authority.js +37 -0
- package/dist/machine-link/client.d.ts +66 -0
- package/dist/machine-link/client.js +94 -0
- package/dist/machine-link/delivery.d.ts +18 -0
- package/dist/machine-link/delivery.js +55 -0
- package/dist/machine-link/enrollment.d.ts +111 -0
- package/dist/machine-link/enrollment.js +286 -0
- package/dist/machine-link/fences.d.ts +37 -0
- package/dist/machine-link/fences.js +166 -0
- package/dist/machine-link/home.d.ts +20 -0
- package/dist/machine-link/home.js +24 -0
- package/dist/machine-link/host.d.ts +43 -0
- package/dist/machine-link/host.js +189 -0
- package/dist/machine-link/index.d.ts +15 -0
- package/dist/machine-link/index.js +15 -0
- package/dist/machine-link/link.d.ts +48 -0
- package/dist/machine-link/link.js +223 -0
- package/dist/machine-link/presence.d.ts +10 -0
- package/dist/machine-link/presence.js +25 -0
- package/dist/machine-link/workers.d.ts +6 -0
- package/dist/machine-link/workers.js +6 -0
- package/dist/mcp-owner.d.ts +2 -2
- package/dist/mcp-owner.js +41 -33
- package/dist/mcp.d.ts +15 -3
- package/dist/mcp.js +146 -25
- package/dist/pack.d.ts +14 -3
- package/dist/pack.js +44 -19
- package/dist/page/app-access.d.ts +7 -0
- package/dist/page/app-access.js +75 -0
- package/dist/page/boot-host.js +57 -0
- package/dist/page/capabilities.d.ts +3 -3
- package/dist/page/capabilities.js +16 -12
- package/dist/page/computer-handles.d.ts +3 -3
- package/dist/page/computer-target.d.ts +3 -0
- package/dist/page/computer-target.js +9 -0
- package/dist/page/mount.d.ts +23 -7
- package/dist/page/mount.js +152 -16
- package/dist/page-assets.d.ts +22 -0
- package/dist/page-assets.js +28 -0
- package/dist/page-script.d.ts +3 -0
- package/dist/page-script.js +5 -4
- package/dist/release.d.ts +13 -6
- package/dist/release.js +10 -7
- package/dist/root-data.d.ts +16 -6
- package/dist/root-data.js +2 -2
- package/dist/source/index.d.ts +1 -1
- package/dist/source/team-source.d.ts +20 -8
- package/dist/source/team-source.js +39 -10
- package/dist/source/template-seed.d.ts +2 -2
- package/dist/standalone/agent-mail.d.ts +24 -0
- package/dist/standalone/agent-mail.js +62 -0
- package/dist/standalone/agent-store.d.ts +9 -0
- package/dist/standalone/agent-store.js +30 -0
- package/dist/standalone/agents.d.ts +78 -12
- package/dist/standalone/agents.js +470 -59
- package/dist/standalone/app-builds-binding.d.ts +20 -2
- package/dist/standalone/app-builds-binding.js +151 -27
- package/dist/standalone/app-set.d.ts +5 -2
- package/dist/standalone/app-set.js +174 -143
- package/dist/standalone/browser.d.ts +11 -0
- package/dist/standalone/browser.js +72 -0
- package/dist/standalone/builds.d.ts +13 -4
- package/dist/standalone/builds.js +28 -44
- package/dist/standalone/computer-egress.d.ts +2 -0
- package/dist/standalone/computer-egress.js +13 -4
- package/dist/standalone/computer-fleet.d.ts +43 -0
- package/dist/standalone/computer-fleet.js +200 -0
- package/dist/standalone/computer.d.ts +44 -9
- package/dist/standalone/computer.js +132 -25
- package/dist/standalone/install-apps-cli.js +43 -6
- package/dist/standalone/instance.d.ts +2 -2
- package/dist/standalone/instance.js +8 -11
- package/dist/standalone/machine-link.d.ts +354 -0
- package/dist/standalone/machine-link.js +368 -0
- package/dist/standalone/media.d.ts +2 -2
- package/dist/standalone/media.js +19 -16
- package/dist/standalone/preview.d.ts +2 -2
- package/dist/standalone/preview.js +39 -12
- package/dist/standalone/registry.d.ts +149 -9
- package/dist/standalone/registry.js +299 -8
- package/dist/standalone/worker.d.ts +19 -5
- package/dist/standalone/worker.js +90 -54
- package/dist/standalone/workflow-binding.d.ts +28 -32
- package/dist/standalone/workflow-binding.js +20 -20
- package/dist/standalone/workflow-control.d.ts +4 -0
- package/dist/standalone/workflow-control.js +40 -0
- package/dist/standalone/workflow-events.d.ts +2 -2
- package/dist/standalone/workflow-events.js +9 -10
- package/dist/standalone/workflow-git-capture.js +4 -3
- package/dist/standalone/workflow-host.d.ts +177 -152
- package/dist/standalone/workflow-host.js +137 -30
- package/dist/standalone/workflow-node-capture.js +11 -4
- package/dist/standalone/workflow-schedules.d.ts +5 -2
- package/dist/standalone/workflow-schedules.js +9 -10
- package/dist/standalone/workflow-trigger-host.d.ts +88 -6
- package/dist/standalone/workflow-trigger-host.js +2 -2
- package/dist/standalone/workflow-webhooks.d.ts +22 -0
- package/dist/standalone/workflow-webhooks.js +39 -0
- package/dist/standalone/workflows.d.ts +203 -50
- package/dist/standalone/workflows.js +198 -39
- package/dist/storage-frame.d.ts +6 -6
- package/dist/testing/capabilities.js +14 -3
- package/dist/testing/installation.js +26 -2
- package/dist/testing/worker.js +1 -1
- package/dist/testing/workers-runtime.d.ts +8 -2
- package/dist/testing/workers-runtime.js +20 -2
- package/dist/webhook-abuse.d.ts +25 -0
- package/dist/webhook-abuse.js +54 -0
- package/dist/webhook-custody.d.ts +144 -0
- package/dist/webhook-custody.js +222 -0
- package/dist/webhook-ingress.d.ts +36 -0
- package/dist/webhook-ingress.js +146 -0
- package/dist/webhooks.d.ts +111 -0
- package/dist/webhooks.js +189 -0
- package/dist/workers-git.d.ts +12 -17
- package/dist/workers-git.js +60 -55
- package/dist/workers.d.ts +224 -13
- package/dist/workers.js +351 -51
- package/dist/workflow-agent-authority.d.ts +8 -0
- package/dist/workflow-agent-authority.js +42 -0
- package/dist/workflows.d.ts +66 -0
- package/dist/workflows.js +97 -0
- package/docs/api/README.md +63 -37
- package/docs/api/agents.md +17 -0
- package/docs/api/app-storage.md +1 -1
- package/docs/api/apps.md +1394 -543
- package/docs/api/browser/boot.md +132 -0
- package/docs/api/browser.md +1440 -0
- package/docs/api/build-contract.md +38 -25
- package/docs/api/build.md +263 -84
- package/docs/api/cloudflare.md +93 -7
- package/docs/api/git.md +3 -13
- package/docs/api/host.md +944 -118
- package/docs/api/index.md +320 -18
- package/docs/api/keys.md +24 -1
- package/docs/api/mcp.md +59 -12
- package/docs/api/modules.md +33 -40
- package/docs/api/pack.md +57 -8
- package/docs/api/page/mount.md +92 -4
- package/docs/api/workers-git.md +103 -24
- package/docs/api/workers.md +1581 -244
- package/docs/site/agent-quickstart.mdx +28 -14
- package/examples/standalone/worker.ts +3 -1
- package/examples/standalone/wrangler.jsonc +2 -0
- package/package.json +73 -69
- package/dist/installation-consent.d.ts +0 -79
- package/dist/installation-consent.js +0 -82
- package/dist/shell/app-directory.d.ts +0 -542
- package/dist/shell/app-directory.js +0 -199
- package/dist/shell/browser-actions.d.ts +0 -7
- package/dist/shell/browser-actions.js +0 -56
- package/dist/shell/command-host.d.ts +0 -112
- package/dist/shell/command-host.js +0 -502
- package/dist/shell/commands.d.ts +0 -25
- package/dist/shell/commands.js +0 -78
- package/dist/shell/index.d.ts +0 -8
- package/dist/shell/index.js +0 -8
- package/dist/shell/keys.d.ts +0 -41
- package/dist/shell/keys.js +0 -134
- package/dist/shell/shell-module.d.ts +0 -55
- package/dist/shell/shell-module.js +0 -86
- package/dist/shell/window-manager.d.ts +0 -124
- package/dist/shell/window-manager.js +0 -711
- package/dist/shell/workbench-contract.d.ts +0 -68
- package/dist/shell/workbench-directory.d.ts +0 -332
- package/dist/shell/workbench-directory.js +0 -35
- package/dist/shell/workbench-host-types.d.ts +0 -96
- package/dist/shell/workbench-host-types.js +0 -1
- package/dist/shell/workbench-host.d.ts +0 -34
- package/dist/shell/workbench-host.js +0 -347
- package/dist/shell/workbench-model.d.ts +0 -192
- package/dist/shell/workbench-model.js +0 -53
- package/dist/shell/workbench.d.ts +0 -4
- package/dist/shell/workbench.js +0 -2
- package/docs/api/shell/app-directory.md +0 -599
- package/docs/api/shell/command-host.md +0 -224
- package/docs/api/shell/commands.md +0 -197
- package/docs/api/shell/keys.md +0 -183
- package/docs/api/shell/shell-module.md +0 -204
- package/docs/api/shell/window-manager.md +0 -267
- package/docs/api/shell/workbench-directory.md +0 -119
- package/docs/api/shell/workbench.md +0 -205
- package/docs/api/shell.md +0 -1915
- /package/dist/{shell/workbench-contract.js → page/boot-host.d.ts} +0 -0
|
@@ -1,5 +1,13 @@
|
|
|
1
|
+
import { observeProviderStatus } from "@fungi.computer/mule/effect";
|
|
1
2
|
import { standaloneTenancy, } from "./tenancy.js";
|
|
2
3
|
import { AcceptedSchema } from "@fungi.computer/shiitake/client";
|
|
4
|
+
import { createShiitakeClient } from "@fungi.computer/shiitake/client";
|
|
5
|
+
import { AgentCreateRefused, } from "@fungi.computer/shiitake/agent-control";
|
|
6
|
+
import { AgentDelegation, AgentDisclosureRefused, machineAgentCodecs, } from "@fungi.computer/shiitake/delegation";
|
|
7
|
+
import { FungiMessageMetaSchema } from "@fungi.computer/agar/agents";
|
|
8
|
+
import { agentMail } from "./agent-mail.js";
|
|
9
|
+
import { createStandaloneMachineEnrollment, standaloneMachineAgentHome, standaloneMachineLinkAuthority, } from "./machine-link.js";
|
|
10
|
+
import { createMachineLinkFences, MachineEnrollmentRefused, MachineLinkRefused, createMachineAgentClient, } from "../machine-link/index.js";
|
|
3
11
|
import { memberInboxItemSchema } from "@fungi.computer/puffball/inbox";
|
|
4
12
|
/**
|
|
5
13
|
* Agents on a standalone Oyster Worker. Each agent is Shiitake's public
|
|
@@ -11,7 +19,8 @@ import { memberInboxItemSchema } from "@fungi.computer/puffball/inbox";
|
|
|
11
19
|
import { agentDirectoryForScope } from "./agent-directory.js";
|
|
12
20
|
import { DurableObject } from "cloudflare:workers";
|
|
13
21
|
import { ShiitakeDO } from "@fungi.computer/shiitake/cloudflare";
|
|
14
|
-
import {
|
|
22
|
+
import { agentStoreKeeping } from "./agent-store.js";
|
|
23
|
+
import { standaloneBrowserSource } from "./browser.js";
|
|
15
24
|
import { configuredProviderEgress, providerFetch, } from "@fungi.computer/veil/gatekeeper";
|
|
16
25
|
import { CLOUDFLARE_GATEWAY_BINDING_AUTH_SENTINEL, createAiBindingFetch, } from "@earendil-works/pi-ai/api/cloudflare-ai-binding";
|
|
17
26
|
import { cloudflareWorkersAIProvider } from "@earendil-works/pi-ai/providers/cloudflare-workers-ai";
|
|
@@ -19,17 +28,51 @@ import * as Schema from "effect/Schema";
|
|
|
19
28
|
import { z } from "zod";
|
|
20
29
|
import {} from "../cloudflare.js";
|
|
21
30
|
import { AppInstanceError } from "../index.js";
|
|
31
|
+
import { createAgentAvatarStore, } from "../agent-avatars.js";
|
|
22
32
|
import { defineIdFamily } from "@fungi.computer/cairn";
|
|
23
|
-
import { agentIds, createAgentDirectory, createFleetSessions, DirectoryError, } from "@fungi.computer/shiitake/fleet";
|
|
24
|
-
import { appSurfaceCache, oysterAppMethods, } from "../mcp.js";
|
|
33
|
+
import { agentIds, createAgentDirectory, createFleetSessions, commandSettlementSchema, DirectoryError, } from "@fungi.computer/shiitake/fleet";
|
|
25
34
|
import { appToolPolicySchema } from "../apps/index.js";
|
|
26
35
|
import { oysterPrincipalIsOwner, oysterPrincipalId, } from "../host.js";
|
|
27
36
|
import { defaultTenant, tenantIdSchema, tenantScope, } from "../tenant.js";
|
|
28
37
|
import { createCodemodeSandbox } from "@fungi.computer/mycelium/workers";
|
|
29
|
-
import { Knapsack } from "@fungi.computer/knapsack";
|
|
30
38
|
import { maxAgentMcpServers, } from "../agents.js";
|
|
31
39
|
/** Default Workers AI chat model: one that reliably calls tools. */
|
|
32
40
|
const defaultAgentModel = "@cf/moonshotai/kimi-k2.6";
|
|
41
|
+
/** Own transport evidence and exact-message decisions for one model lease. */
|
|
42
|
+
function observedModels(model, defaults) {
|
|
43
|
+
return {
|
|
44
|
+
acquire: async ({ signal }) => {
|
|
45
|
+
const streamSimple = await loadStreamSimple();
|
|
46
|
+
const retryableMessages = new WeakSet();
|
|
47
|
+
return {
|
|
48
|
+
model,
|
|
49
|
+
retryable: (message) => !signal.aborted &&
|
|
50
|
+
message.stopReason === "error" &&
|
|
51
|
+
retryableMessages.has(message),
|
|
52
|
+
stream: (selected, context, options) => {
|
|
53
|
+
const upstream = { ...options, ...defaults };
|
|
54
|
+
const observed = observeProviderStatus(upstream);
|
|
55
|
+
const response = streamSimple(selected, context, {
|
|
56
|
+
...upstream,
|
|
57
|
+
fetch: observed.fetch,
|
|
58
|
+
onProviderStreamEvent: observed.onProviderStreamEvent,
|
|
59
|
+
});
|
|
60
|
+
// Observe Pi's ordinary settled-result interface without replacing
|
|
61
|
+
// its stream, iterator, result or transport.
|
|
62
|
+
void response.result().then((message) => {
|
|
63
|
+
if (message.stopReason === "error" &&
|
|
64
|
+
!signal.aborted &&
|
|
65
|
+
!upstream.signal?.aborted &&
|
|
66
|
+
observed.rejection()?.code === "provider_unavailable")
|
|
67
|
+
retryableMessages.add(message);
|
|
68
|
+
});
|
|
69
|
+
return response;
|
|
70
|
+
},
|
|
71
|
+
release: () => Promise.resolve(),
|
|
72
|
+
};
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
33
76
|
/**
|
|
34
77
|
* Workers AI over the AI binding. Requests go through AI Gateway's
|
|
35
78
|
* `workers-ai` passthrough on the binding, so no API token is needed.
|
|
@@ -46,26 +89,16 @@ function workersAiModels(ai, options = {}) {
|
|
|
46
89
|
baseUrl: `https://workers-binding.ai/ai-gateway/gateways/${options.gateway ?? "default"}/workers-ai/v1`,
|
|
47
90
|
};
|
|
48
91
|
const fetch = createAiBindingFetch(ai);
|
|
49
|
-
return {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
apiKey: CLOUDFLARE_GATEWAY_BINDING_AUTH_SENTINEL,
|
|
58
|
-
headers: {
|
|
59
|
-
"cf-aig-authorization": `Bearer ${CLOUDFLARE_GATEWAY_BINDING_AUTH_SENTINEL}`,
|
|
60
|
-
// The binding carries identity; a request auth header would be
|
|
61
|
-
// taken as a provider key that overrides the gateway's own.
|
|
62
|
-
Authorization: null,
|
|
63
|
-
},
|
|
64
|
-
}),
|
|
65
|
-
release: () => Promise.resolve(),
|
|
66
|
-
};
|
|
92
|
+
return observedModels(model, {
|
|
93
|
+
fetch,
|
|
94
|
+
apiKey: CLOUDFLARE_GATEWAY_BINDING_AUTH_SENTINEL,
|
|
95
|
+
headers: {
|
|
96
|
+
"cf-aig-authorization": `Bearer ${CLOUDFLARE_GATEWAY_BINDING_AUTH_SENTINEL}`,
|
|
97
|
+
// The binding carries identity; a request auth header would be
|
|
98
|
+
// taken as a provider key that overrides the gateway's own.
|
|
99
|
+
Authorization: null,
|
|
67
100
|
},
|
|
68
|
-
};
|
|
101
|
+
});
|
|
69
102
|
}
|
|
70
103
|
/**
|
|
71
104
|
* Pi's dispatching `streamSimple` evaluates every provider's model catalog when
|
|
@@ -121,6 +154,179 @@ const instructions = [
|
|
|
121
154
|
export class StandaloneAgentBase extends ShiitakeDO {
|
|
122
155
|
/** Each installed Release's verified catalog surface, built once per Release for this Agent's lifetime. */
|
|
123
156
|
#appSurfaces;
|
|
157
|
+
uiOwner;
|
|
158
|
+
async machineClient() {
|
|
159
|
+
const pin = await this.policyDirectory();
|
|
160
|
+
const agentId = this.ctx.id.name;
|
|
161
|
+
const identity = () => pin === null || agentId === undefined
|
|
162
|
+
? null
|
|
163
|
+
: { teamId: pin.directoryName, agentId };
|
|
164
|
+
return createMachineAgentClient({
|
|
165
|
+
identity,
|
|
166
|
+
home: standaloneMachineAgentHome({
|
|
167
|
+
identity,
|
|
168
|
+
directory: this.env.AGENT_DIRECTORY,
|
|
169
|
+
}),
|
|
170
|
+
links: this.env.MACHINE_LINK,
|
|
171
|
+
codecs: machineAgentCodecs,
|
|
172
|
+
// Lend the client only the three hosted Agent entry points it routes to.
|
|
173
|
+
hosted: {
|
|
174
|
+
getByName: (agentId) => {
|
|
175
|
+
const agent = this.env.AGENTS.getByName(agentId);
|
|
176
|
+
return {
|
|
177
|
+
deliver: (envelope) => agent.deliver(envelope),
|
|
178
|
+
fetch: (request) => agent.fetch(request),
|
|
179
|
+
settlement: (principal, sessionId, commandId) => agent.settlement(principal, sessionId, commandId),
|
|
180
|
+
};
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
async ui() {
|
|
186
|
+
const { Knapsack } = await import("@fungi.computer/knapsack");
|
|
187
|
+
return (this.uiOwner ??= Knapsack.ui({
|
|
188
|
+
id: "oyster-ui",
|
|
189
|
+
hasSession: (sessionId) => this.hasStoredSession(sessionId),
|
|
190
|
+
}));
|
|
191
|
+
}
|
|
192
|
+
hostFetch(request) {
|
|
193
|
+
return new URL(request.url).pathname.startsWith("/browser/")
|
|
194
|
+
? this.ui().then((connection) => connection.fetch(request))
|
|
195
|
+
: undefined;
|
|
196
|
+
}
|
|
197
|
+
async closeShiitake() {
|
|
198
|
+
try {
|
|
199
|
+
await (await this.ui()).close();
|
|
200
|
+
}
|
|
201
|
+
finally {
|
|
202
|
+
await super.closeShiitake();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
controlDirectory() {
|
|
206
|
+
const list = async (signal) => {
|
|
207
|
+
signal.throwIfAborted();
|
|
208
|
+
const pin = await this.policyDirectory();
|
|
209
|
+
if (pin === null)
|
|
210
|
+
throw new Error("Agent directory is unavailable");
|
|
211
|
+
const agents = await this.env.AGENT_DIRECTORY.getByName(pin.directoryName).list();
|
|
212
|
+
if (!agents.some((agent) => agent.agentId === this.ctx.id.name))
|
|
213
|
+
throw new Error("Agent is outside this scope");
|
|
214
|
+
signal.throwIfAborted();
|
|
215
|
+
return agents;
|
|
216
|
+
};
|
|
217
|
+
return {
|
|
218
|
+
list,
|
|
219
|
+
create: async (input, signal) => {
|
|
220
|
+
await list(signal);
|
|
221
|
+
const { assertOrdinaryAgentCreation } = await import("@fungi.computer/shiitake-extensions/roster/conversation-agents");
|
|
222
|
+
assertOrdinaryAgentCreation(input);
|
|
223
|
+
const pin = await this.policyDirectory();
|
|
224
|
+
const createdBy = this.ctx.id.name;
|
|
225
|
+
if (pin === null || createdBy === undefined)
|
|
226
|
+
throw new AgentCreateRefused("no_creator", "Agent directory is unavailable");
|
|
227
|
+
const directory = this.env.AGENT_DIRECTORY.getByName(pin.directoryName);
|
|
228
|
+
const owner = await directory.readCreator(createdBy);
|
|
229
|
+
if (owner === null)
|
|
230
|
+
throw new AgentCreateRefused("no_creator", "Agent credential owner is unavailable");
|
|
231
|
+
const entry = await directory.create(input.name ?? `Agent ${input.requestId}`, input.requestId, { actor: { kind: "member", memberId: owner }, role: "owner" }, "owner-direct", undefined, createdBy);
|
|
232
|
+
await this.env.AGENTS.getByName(entry.agentId).usePolicyDirectory(pin);
|
|
233
|
+
return entry;
|
|
234
|
+
},
|
|
235
|
+
send: (input, sessionId, signal, invocation) => this.delegation().send(input, sessionId, signal, invocation),
|
|
236
|
+
recall: (input, sessionId, signal) => this.delegation().recall(input, sessionId, signal),
|
|
237
|
+
open: async (agentId, signal) => {
|
|
238
|
+
const admit = async () => {
|
|
239
|
+
if (!(await list(signal)).some((agent) => agent.agentId === agentId))
|
|
240
|
+
throw new Error("Agent is outside this scope");
|
|
241
|
+
};
|
|
242
|
+
await admit();
|
|
243
|
+
const agent = createShiitakeClient({
|
|
244
|
+
baseUrl: "https://agent.internal",
|
|
245
|
+
fetch: async (input, init) => {
|
|
246
|
+
await admit();
|
|
247
|
+
const request = new Request(input, init);
|
|
248
|
+
if (new URL(request.url).origin !== "https://agent.internal")
|
|
249
|
+
throw new Error("Agent transport origin is invalid");
|
|
250
|
+
return (await this.machineClient()).dispatch(agentId, request);
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
return { agent, release: () => agent.close() };
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
delegationOwner;
|
|
258
|
+
delegation() {
|
|
259
|
+
return (this.delegationOwner ??= new AgentDelegation({
|
|
260
|
+
storage: this.ctx.storage,
|
|
261
|
+
expiresAt: () => Date.now() + 60_000,
|
|
262
|
+
identity: async () => {
|
|
263
|
+
const pin = await this.policyDirectory();
|
|
264
|
+
const agentId = this.ctx.id.name;
|
|
265
|
+
return pin === null || agentId === undefined
|
|
266
|
+
? null
|
|
267
|
+
: { scopeId: pin.directoryName, agentId };
|
|
268
|
+
},
|
|
269
|
+
directory: () => this.controlDirectory(),
|
|
270
|
+
deliver: (mail) => this.mail().deliver(mail),
|
|
271
|
+
prepare: () => Promise.resolve(),
|
|
272
|
+
accept: async (mail, reply) => (await this.shiitake()).acceptMail(mail, mail.meta, reply),
|
|
273
|
+
}));
|
|
274
|
+
}
|
|
275
|
+
/** Host-only mail admission; an HTTP caller never receives this binding. */
|
|
276
|
+
async deliver(envelope) {
|
|
277
|
+
return this.delegation().receive(envelope);
|
|
278
|
+
}
|
|
279
|
+
mailOwner;
|
|
280
|
+
conversationAgentsOwner;
|
|
281
|
+
/** The Agents each of this Agent's conversations created or sent work to. */
|
|
282
|
+
async conversationAgents() {
|
|
283
|
+
const { createConversationAgents } = await import("@fungi.computer/shiitake-extensions/roster/conversation-agents");
|
|
284
|
+
return (this.conversationAgentsOwner ??= createConversationAgents(this.ctx.storage));
|
|
285
|
+
}
|
|
286
|
+
mail() {
|
|
287
|
+
return (this.mailOwner ??= agentMail({
|
|
288
|
+
storage: this.ctx.storage,
|
|
289
|
+
namespace: {
|
|
290
|
+
getByName: (agentId) => ({
|
|
291
|
+
deliver: async (envelope) => (await this.machineClient()).namespace
|
|
292
|
+
.getByName(agentId)
|
|
293
|
+
.deliver(envelope),
|
|
294
|
+
}),
|
|
295
|
+
},
|
|
296
|
+
identity: async () => {
|
|
297
|
+
const pin = await this.policyDirectory();
|
|
298
|
+
const agentId = this.ctx.id.name;
|
|
299
|
+
return pin === null || agentId === undefined
|
|
300
|
+
? null
|
|
301
|
+
: { scopeId: pin.directoryName, agentId };
|
|
302
|
+
},
|
|
303
|
+
replySettlement: async (mail) => {
|
|
304
|
+
const pin = await this.policyDirectory();
|
|
305
|
+
if (pin === null || mail.reply === undefined)
|
|
306
|
+
throw new Error("Delegated reply identity is unavailable");
|
|
307
|
+
return (await this.machineClient()).settlement(mail.from.agentId, mail.from.sessionId, mail.reply.commandId, mail.to.agentId);
|
|
308
|
+
},
|
|
309
|
+
wake: () => this.wakeRuntime(),
|
|
310
|
+
}));
|
|
311
|
+
}
|
|
312
|
+
/** Undefined is disclosure refusal; unavailable evidence fails before RPC. */
|
|
313
|
+
async settlement(principal, sessionId, commandId) {
|
|
314
|
+
const pin = await this.policyDirectory();
|
|
315
|
+
if (pin?.directoryName !== principal.teamId)
|
|
316
|
+
return undefined;
|
|
317
|
+
try {
|
|
318
|
+
const facts = await this.delegation().settlement({ scopeId: principal.teamId, agentId: principal.actor.id }, sessionId, commandId, await this.shiitake());
|
|
319
|
+
if (facts === undefined)
|
|
320
|
+
throw new Error("Settled delegated command evidence is unavailable");
|
|
321
|
+
return Schema.encodeSync(Schema.fromJsonString(commandSettlementSchema))(facts);
|
|
322
|
+
}
|
|
323
|
+
catch (cause) {
|
|
324
|
+
// A disclosure refusal is data before crossing the DO RPC boundary.
|
|
325
|
+
if (cause instanceof AgentDisclosureRefused)
|
|
326
|
+
return undefined;
|
|
327
|
+
throw cause;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
124
330
|
/** The root host for this Agent's pinned policy directory; unpinned Agents have no App tools. */
|
|
125
331
|
async appHost() {
|
|
126
332
|
const stored = await this.ctx.storage.get("app-policy-directory");
|
|
@@ -139,25 +345,19 @@ export class StandaloneAgentBase extends ShiitakeDO {
|
|
|
139
345
|
const baseUrl = egress.baseUrl("ai-gateway");
|
|
140
346
|
if (baseUrl === null)
|
|
141
347
|
throw new Error("Configure the Luna AI Gateway Worker secret and gateway");
|
|
142
|
-
const provider = openaiProvider();
|
|
143
|
-
const listed = provider
|
|
144
|
-
.getModels()
|
|
145
|
-
.find((item) => item.id === "gpt-6-luna");
|
|
146
|
-
if (listed === undefined)
|
|
147
|
-
throw new Error("The OpenAI catalog has no Luna model");
|
|
148
|
-
const model = { ...listed, id: "openai/gpt-6-luna", baseUrl };
|
|
149
348
|
return {
|
|
150
|
-
acquire: async () => {
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
349
|
+
acquire: async (input) => {
|
|
350
|
+
const { openaiProvider } = await import("@earendil-works/pi-ai/providers/openai");
|
|
351
|
+
const listed = openaiProvider()
|
|
352
|
+
.getModels()
|
|
353
|
+
.find((item) => item.id === "gpt-6-luna");
|
|
354
|
+
if (listed === undefined)
|
|
355
|
+
throw new Error("The OpenAI catalog has no Luna model");
|
|
356
|
+
const model = { ...listed, id: "openai/gpt-6-luna", baseUrl };
|
|
357
|
+
return observedModels(model, {
|
|
358
|
+
apiKey: "oyster-provider-egress",
|
|
359
|
+
fetch: providerFetch(egress, "ai-gateway"),
|
|
360
|
+
}).acquire(input);
|
|
161
361
|
},
|
|
162
362
|
};
|
|
163
363
|
}
|
|
@@ -232,12 +432,20 @@ export class StandaloneAgentBase extends ShiitakeDO {
|
|
|
232
432
|
await this.writeMcpServers(kept);
|
|
233
433
|
return true;
|
|
234
434
|
}
|
|
235
|
-
shiitakeOptions() {
|
|
435
|
+
async shiitakeOptions() {
|
|
436
|
+
const [{ oysterAgentExtensions }, { Knapsack }] = await Promise.all([
|
|
437
|
+
import("@fungi.computer/shiitake-extensions/roster/conversation-agents"),
|
|
438
|
+
import("@fungi.computer/knapsack"),
|
|
439
|
+
]);
|
|
440
|
+
const conversationAgents = await this.conversationAgents();
|
|
441
|
+
const keeping = agentStoreKeeping(this.ctx.storage, (request) => this.readColdSessions(request));
|
|
236
442
|
const principal = {
|
|
237
443
|
actor: { kind: "agent", agentId: this.ctx.id.name ?? "agent" },
|
|
238
444
|
};
|
|
239
445
|
return {
|
|
240
446
|
model: this.modelSource(),
|
|
447
|
+
meta: { schema: FungiMessageMetaSchema },
|
|
448
|
+
...this.mail().host,
|
|
241
449
|
instructions: {
|
|
242
450
|
load: () => Promise.resolve({ revision: "oyster-4", text: instructions }),
|
|
243
451
|
},
|
|
@@ -255,6 +463,7 @@ export class StandaloneAgentBase extends ShiitakeDO {
|
|
|
255
463
|
release: () => Promise.resolve(),
|
|
256
464
|
};
|
|
257
465
|
const policy = (await host.appToolPolicy?.(principal.actor)) ?? null;
|
|
466
|
+
const { appSurfaceCache, oysterAppMethods } = await import("../mcp.js");
|
|
258
467
|
this.#appSurfaces ??= appSurfaceCache(host);
|
|
259
468
|
const source = await oysterAppMethods(host, principal, policy === null ? null : { kind: "agent", policy }, this.#appSurfaces);
|
|
260
469
|
return source.acquire(context);
|
|
@@ -283,10 +492,18 @@ export class StandaloneAgentBase extends ShiitakeDO {
|
|
|
283
492
|
// The Worker's own egress: public internet only.
|
|
284
493
|
fetch: (url, init) => fetch(url, init),
|
|
285
494
|
}),
|
|
495
|
+
conversationAgents.control(() => this.controlDirectory()),
|
|
496
|
+
(await this.ui()).source,
|
|
497
|
+
standaloneBrowserSource(this.env.BROWSER, () => ({
|
|
498
|
+
teamId: this.env.OYSTER_ORIGIN,
|
|
499
|
+
agentId: principal.actor.agentId,
|
|
500
|
+
})),
|
|
286
501
|
],
|
|
287
502
|
sandbox: createCodemodeSandbox(this.env.LOADER, "oyster-workerd"),
|
|
288
503
|
},
|
|
289
|
-
extensions:
|
|
504
|
+
extensions: oysterAgentExtensions(conversationAgents.source(this.controlDirectory())),
|
|
505
|
+
...keeping,
|
|
506
|
+
hostWork: [...this.mail().host.hostWork, ...keeping.hostWork],
|
|
290
507
|
};
|
|
291
508
|
}
|
|
292
509
|
/** Send an installation-admitted message through fleet Session operations. */
|
|
@@ -342,11 +559,6 @@ const storedPolicySchema = Schema.Union([
|
|
|
342
559
|
actingInstallation: actingInstallationSchema,
|
|
343
560
|
}),
|
|
344
561
|
]);
|
|
345
|
-
const requestIds = defineIdFamily({
|
|
346
|
-
family: "oyster-agent",
|
|
347
|
-
version: 1,
|
|
348
|
-
kinds: { request: { prefix: "request" } },
|
|
349
|
-
});
|
|
350
562
|
const visitRow = Schema.Struct({
|
|
351
563
|
agent_id: Schema.String,
|
|
352
564
|
session_id: Schema.String,
|
|
@@ -356,7 +568,141 @@ const counterRow = Schema.Struct({ value: Schema.Number });
|
|
|
356
568
|
/** Names this Oyster's agents and remembers which Sessions the member opened. */
|
|
357
569
|
export class AgentDirectory extends DurableObject {
|
|
358
570
|
directory;
|
|
571
|
+
avatars;
|
|
359
572
|
composedTenancy;
|
|
573
|
+
machineEnrollmentOwner;
|
|
574
|
+
machineFencesOwner;
|
|
575
|
+
machineScope() {
|
|
576
|
+
return tenantScope(defaultTenant, this.env.OYSTER_ORIGIN);
|
|
577
|
+
}
|
|
578
|
+
machineEnrollment() {
|
|
579
|
+
const env = this.env;
|
|
580
|
+
const scope = this.machineScope();
|
|
581
|
+
return (this.machineEnrollmentOwner ??= createStandaloneMachineEnrollment({
|
|
582
|
+
owner: this.ctx.storage,
|
|
583
|
+
scopeId: scope.durableName("agents"),
|
|
584
|
+
ownerId: async () => {
|
|
585
|
+
if (env.TENANTS === undefined)
|
|
586
|
+
return null;
|
|
587
|
+
if (this.ctx.id.name !== scope.durableName("agents"))
|
|
588
|
+
return null;
|
|
589
|
+
// An inactive/missing scope must not cold-start tenancy's seed path.
|
|
590
|
+
const tenant = await env.TENANTS.getByName("registry").get(defaultTenant);
|
|
591
|
+
if (tenant?.status !== "active")
|
|
592
|
+
return null;
|
|
593
|
+
const tenancy = await standaloneTenancy({
|
|
594
|
+
...env,
|
|
595
|
+
TENANTS: env.TENANTS,
|
|
596
|
+
});
|
|
597
|
+
return (await tenancy.owner())?.actor.memberId ?? null;
|
|
598
|
+
},
|
|
599
|
+
liveKey: (keyId) => env.KEYS.getByName(scope.durableName("keys")).live(keyId),
|
|
600
|
+
onAgentCreated: () => {
|
|
601
|
+
this.bump();
|
|
602
|
+
},
|
|
603
|
+
}));
|
|
604
|
+
}
|
|
605
|
+
/** Host-only join: consumption, enrollment and directory share one commit. */
|
|
606
|
+
async enrollMachine(keyId, input) {
|
|
607
|
+
try {
|
|
608
|
+
return {
|
|
609
|
+
kind: "enrolled",
|
|
610
|
+
value: await this.machineEnrollment().enrollWithKey(keyId, input),
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
catch (cause) {
|
|
614
|
+
if (cause instanceof MachineEnrollmentRefused)
|
|
615
|
+
return {
|
|
616
|
+
kind: "refused",
|
|
617
|
+
error: cause.reason === "request_conflict" || cause.reason === "capacity"
|
|
618
|
+
? cause.reason
|
|
619
|
+
: "not_allowed",
|
|
620
|
+
};
|
|
621
|
+
if (cause instanceof MachineLinkRefused)
|
|
622
|
+
return { kind: "refused", error: "not_allowed" };
|
|
623
|
+
if (cause instanceof DOMException && cause.name === "DataError")
|
|
624
|
+
return { kind: "refused", error: "invalid_enrollment" };
|
|
625
|
+
throw cause;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
machineDevice(id) {
|
|
629
|
+
return this.machineEnrollment().admitDevice(id);
|
|
630
|
+
}
|
|
631
|
+
machineFence(id) {
|
|
632
|
+
return this.machineEnrollment().currentFence(id);
|
|
633
|
+
}
|
|
634
|
+
machineDirectory(id) {
|
|
635
|
+
return this.machineEnrollment().publicDirectory(id);
|
|
636
|
+
}
|
|
637
|
+
machineTarget(caller, agentId, requester) {
|
|
638
|
+
return this.machineEnrollment().target(caller, agentId, requester);
|
|
639
|
+
}
|
|
640
|
+
machineKeyRetired(deviceId) {
|
|
641
|
+
return this.machineEnrollment().keyRetired(deviceId);
|
|
642
|
+
}
|
|
643
|
+
/** Catalog home only; no hosted DO is opened to discover a machine home. */
|
|
644
|
+
agentHome(agentId) {
|
|
645
|
+
if (!this.has(agentId))
|
|
646
|
+
return Promise.resolve(null);
|
|
647
|
+
if (this.ctx.id.name !==
|
|
648
|
+
tenantScope(defaultTenant, this.env.OYSTER_ORIGIN).durableName("agents"))
|
|
649
|
+
return Promise.resolve({ kind: "hosted" });
|
|
650
|
+
return Promise.resolve(this.machineEnrollment().home(agentId) ?? { kind: "hosted" });
|
|
651
|
+
}
|
|
652
|
+
machineFences() {
|
|
653
|
+
if (this.env.TENANTS === undefined)
|
|
654
|
+
throw new MachineLinkRefused("actor_not_allowed");
|
|
655
|
+
const env = { ...this.env, TENANTS: this.env.TENANTS };
|
|
656
|
+
const enrollment = this.machineEnrollment();
|
|
657
|
+
const authority = () => standaloneMachineLinkAuthority(env);
|
|
658
|
+
return (this.machineFencesOwner ??= createMachineLinkFences({
|
|
659
|
+
owner: this.ctx.storage,
|
|
660
|
+
enrollment,
|
|
661
|
+
readFact: (deviceId) => authority().currentFence({
|
|
662
|
+
teamId: this.machineScope().durableName("agents"),
|
|
663
|
+
deviceId,
|
|
664
|
+
}),
|
|
665
|
+
push: (fact) => authority().deliverControlFact(fact),
|
|
666
|
+
wakeNow: () => {
|
|
667
|
+
this.ctx.waitUntil(this.ctx.storage.setAlarm(Date.now()).catch((cause) => {
|
|
668
|
+
console.error("machine-link.first-fence-wake", cause);
|
|
669
|
+
}));
|
|
670
|
+
},
|
|
671
|
+
retryDelayMs: (failures) => Math.min(60_000, 1_000 * 2 ** Math.min(failures, 6)),
|
|
672
|
+
report: (operation, cause) => {
|
|
673
|
+
console.error(operation, cause);
|
|
674
|
+
},
|
|
675
|
+
}));
|
|
676
|
+
}
|
|
677
|
+
async armMachineFenceAlarm() {
|
|
678
|
+
const due = this.machineFences().family.nextDue();
|
|
679
|
+
if (due === null)
|
|
680
|
+
await this.ctx.storage.deleteAlarm();
|
|
681
|
+
else
|
|
682
|
+
await this.ctx.storage.setAlarm(due);
|
|
683
|
+
}
|
|
684
|
+
/** Only the current canonical owner can retire a device, before fence push. */
|
|
685
|
+
async revokeMachineDevice(memberId, deviceId) {
|
|
686
|
+
if (this.env.TENANTS === undefined)
|
|
687
|
+
throw new MachineLinkRefused("actor_not_allowed");
|
|
688
|
+
const owner = await (await standaloneTenancy({ ...this.env, TENANTS: this.env.TENANTS })).owner();
|
|
689
|
+
if (owner?.actor.memberId !== memberId)
|
|
690
|
+
throw new MachineLinkRefused("actor_not_allowed");
|
|
691
|
+
try {
|
|
692
|
+
return await this.machineFences().revoke(memberId, deviceId);
|
|
693
|
+
}
|
|
694
|
+
finally {
|
|
695
|
+
await this.armMachineFenceAlarm();
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
async alarm() {
|
|
699
|
+
try {
|
|
700
|
+
await this.machineFences().family.deliver();
|
|
701
|
+
}
|
|
702
|
+
finally {
|
|
703
|
+
await this.armMachineFenceAlarm();
|
|
704
|
+
}
|
|
705
|
+
}
|
|
360
706
|
grant() {
|
|
361
707
|
return {
|
|
362
708
|
agents: "all",
|
|
@@ -368,6 +714,7 @@ export class AgentDirectory extends DurableObject {
|
|
|
368
714
|
constructor(ctx, env) {
|
|
369
715
|
super(ctx, env);
|
|
370
716
|
this.directory = createAgentDirectory(ctx.storage.sql);
|
|
717
|
+
this.avatars = createAgentAvatarStore(ctx.storage.sql);
|
|
371
718
|
ctx.storage.sql.exec("CREATE TABLE IF NOT EXISTS visits (agent_id TEXT NOT NULL, session_id TEXT NOT NULL, visited_at INTEGER NOT NULL, PRIMARY KEY (agent_id, session_id)) STRICT");
|
|
372
719
|
ctx.storage.sql.exec("CREATE TABLE IF NOT EXISTS counters (name TEXT PRIMARY KEY, value INTEGER NOT NULL) STRICT");
|
|
373
720
|
ctx.storage.sql.exec("CREATE TABLE IF NOT EXISTS app_tool_policies (agent_id TEXT PRIMARY KEY, policy TEXT NOT NULL) STRICT");
|
|
@@ -376,7 +723,12 @@ export class AgentDirectory extends DurableObject {
|
|
|
376
723
|
return this.directory
|
|
377
724
|
.list(this.grant())
|
|
378
725
|
.toReversed()
|
|
379
|
-
.map(({ agentId, name }) => ({
|
|
726
|
+
.map(({ agentId, name, createdBy }) => ({
|
|
727
|
+
agentId,
|
|
728
|
+
createdBy,
|
|
729
|
+
name,
|
|
730
|
+
avatar: this.avatars.read(agentId),
|
|
731
|
+
}));
|
|
380
732
|
}
|
|
381
733
|
has(agentId) {
|
|
382
734
|
const id = agentIds.agent.parse(agentId);
|
|
@@ -399,7 +751,12 @@ export class AgentDirectory extends DurableObject {
|
|
|
399
751
|
name,
|
|
400
752
|
});
|
|
401
753
|
this.bump();
|
|
402
|
-
return {
|
|
754
|
+
return {
|
|
755
|
+
agentId: renamed.agentId,
|
|
756
|
+
createdBy: renamed.createdBy,
|
|
757
|
+
name: renamed.name,
|
|
758
|
+
avatar: this.avatars.read(renamed.agentId),
|
|
759
|
+
};
|
|
403
760
|
}
|
|
404
761
|
catch (cause) {
|
|
405
762
|
if (cause instanceof DirectoryError && cause.code === "not_found")
|
|
@@ -410,15 +767,42 @@ export class AgentDirectory extends DurableObject {
|
|
|
410
767
|
}
|
|
411
768
|
});
|
|
412
769
|
}
|
|
413
|
-
|
|
770
|
+
/** Owner-only avatar change; the Agent wears it in every App and list. */
|
|
771
|
+
setAvatar(agentId, avatar, principal) {
|
|
772
|
+
if (!oysterPrincipalIsOwner(principal))
|
|
773
|
+
return { error: "not_granted" };
|
|
774
|
+
const id = agentIds.agent.parse(agentId);
|
|
775
|
+
if (!id.ok)
|
|
776
|
+
return { error: "not_found" };
|
|
777
|
+
return this.ctx.storage.transactionSync(() => {
|
|
778
|
+
const agent = this.directory.read(this.grant(), id.value);
|
|
779
|
+
if (agent === null)
|
|
780
|
+
return { error: "not_found" };
|
|
781
|
+
this.avatars.write(agent.agentId, avatar);
|
|
782
|
+
this.bump();
|
|
783
|
+
return {
|
|
784
|
+
agentId: agent.agentId,
|
|
785
|
+
createdBy: agent.createdBy,
|
|
786
|
+
name: agent.name,
|
|
787
|
+
avatar: this.avatars.read(agent.agentId),
|
|
788
|
+
};
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
create(name, requestId, principal, actor, avatar, createdBy = null) {
|
|
414
792
|
if (!oysterPrincipalIsOwner(principal))
|
|
415
793
|
throw new Error("Only the authenticated owner creates Agents");
|
|
794
|
+
if (createdBy !== null &&
|
|
795
|
+
(!this.has(createdBy) ||
|
|
796
|
+
this.readCreator(createdBy) !== oysterPrincipalId(principal)))
|
|
797
|
+
throw new AgentCreateRefused("no_creator", "Agent is outside this credential owner's scope");
|
|
416
798
|
return this.ctx.storage.transactionSync(() => {
|
|
417
799
|
const { agent, fact } = this.directory.create({
|
|
418
800
|
...this.grant(),
|
|
419
801
|
provenance: { actor: oysterPrincipalId(principal) },
|
|
420
|
-
}, { name, requestId, configuration: "none" });
|
|
802
|
+
}, { name, requestId, configuration: "none", createdBy });
|
|
421
803
|
if (fact !== null) {
|
|
804
|
+
if (avatar !== undefined)
|
|
805
|
+
this.avatars.write(agent.agentId, avatar);
|
|
422
806
|
this.bump();
|
|
423
807
|
this.writePolicy(agent.agentId, {
|
|
424
808
|
status: "pending",
|
|
@@ -430,7 +814,9 @@ export class AgentDirectory extends DurableObject {
|
|
|
430
814
|
const stored = this.storedPolicy(agent.agentId);
|
|
431
815
|
return {
|
|
432
816
|
agentId: agent.agentId,
|
|
817
|
+
createdBy: agent.createdBy,
|
|
433
818
|
name: agent.name,
|
|
819
|
+
avatar: this.avatars.read(agent.agentId),
|
|
434
820
|
policyRevision: stored === null
|
|
435
821
|
? null
|
|
436
822
|
: stored.status === "pending"
|
|
@@ -610,12 +996,13 @@ export function standaloneAgents(env, scope) {
|
|
|
610
996
|
const toAgent = (entry) => ({
|
|
611
997
|
agentId: entry.agentId,
|
|
612
998
|
name: entry.name,
|
|
999
|
+
avatar: entry.avatar,
|
|
613
1000
|
sessionOperations: everyOperation,
|
|
614
1001
|
});
|
|
615
1002
|
return {
|
|
616
1003
|
list: async () => (await directory().list()).map(toAgent),
|
|
617
|
-
create: async (name, requestId, principal, actor) => {
|
|
618
|
-
const entry = await directory().create(name, requestId, principal, actor);
|
|
1004
|
+
create: async (name, requestId, principal, actor, avatar) => {
|
|
1005
|
+
const entry = await directory().create(name, requestId, principal, actor, avatar);
|
|
619
1006
|
await env.AGENTS.getByName(entry.agentId).usePolicyDirectory({
|
|
620
1007
|
directoryName,
|
|
621
1008
|
tenantId: scope?.tenantId ?? defaultTenant,
|
|
@@ -632,6 +1019,10 @@ export function standaloneAgents(env, scope) {
|
|
|
632
1019
|
const result = await directory().rename(agentId, name, principal);
|
|
633
1020
|
return "error" in result ? result : toAgent(result);
|
|
634
1021
|
},
|
|
1022
|
+
setAvatar: async (agentId, avatar, principal) => {
|
|
1023
|
+
const result = await directory().setAvatar(agentId, avatar, principal);
|
|
1024
|
+
return "error" in result ? result : toAgent(result);
|
|
1025
|
+
},
|
|
635
1026
|
policyStatus: async (agentId) => directory().policyStatus(agentId),
|
|
636
1027
|
appToolPolicy: (actor) => directory().appToolPolicy(actor, scope?.tenantId ?? defaultTenant),
|
|
637
1028
|
policyRevision: (agentId) => directory().policyRevision(agentId),
|
|
@@ -652,9 +1043,16 @@ export function standaloneAgents(env, scope) {
|
|
|
652
1043
|
: { error: "not_found" },
|
|
653
1044
|
removeMcpServer: async (agentId, serverId) => (await directory().has(agentId)) &&
|
|
654
1045
|
(await env.AGENTS.getByName(agentId).removeMcpServer(serverId)),
|
|
655
|
-
fetch: async (agentId, request) =>
|
|
656
|
-
|
|
657
|
-
|
|
1046
|
+
fetch: async (agentId, request) => {
|
|
1047
|
+
const home = await directory().agentHome(agentId);
|
|
1048
|
+
if (home?.kind === "machine") {
|
|
1049
|
+
const refusal = new MachineLinkRefused("actor_not_allowed");
|
|
1050
|
+
return Response.json({ error: refusal.reason }, { status: 409 });
|
|
1051
|
+
}
|
|
1052
|
+
return home?.kind === "hosted"
|
|
1053
|
+
? await env.AGENTS.getByName(agentId).fetch(request)
|
|
1054
|
+
: null;
|
|
1055
|
+
},
|
|
658
1056
|
inbox: async () => {
|
|
659
1057
|
const [agents, visits, directoryRevision] = await Promise.all([
|
|
660
1058
|
directory().list(),
|
|
@@ -713,7 +1111,15 @@ const agentBindingArgs = {
|
|
|
713
1111
|
},
|
|
714
1112
|
AGENTS: {
|
|
715
1113
|
send: z.tuple([
|
|
716
|
-
agentTarget.extend({
|
|
1114
|
+
agentTarget.extend({
|
|
1115
|
+
requestId: z
|
|
1116
|
+
.string()
|
|
1117
|
+
.min(1)
|
|
1118
|
+
.max(256)
|
|
1119
|
+
.refine((value) => new TextEncoder().encode(value).byteLength <= 256),
|
|
1120
|
+
text: z.string(),
|
|
1121
|
+
delivery: z.literal("followUp"),
|
|
1122
|
+
}),
|
|
717
1123
|
]),
|
|
718
1124
|
},
|
|
719
1125
|
};
|
|
@@ -779,9 +1185,14 @@ export async function dispatchAgentBinding(env, scope, frame, props, method, arg
|
|
|
779
1185
|
const [input] = agentBindingArgs.AGENTS.send.parse(args);
|
|
780
1186
|
if (!(await directory.has(input.agentId)))
|
|
781
1187
|
throw new Error("No such agent");
|
|
1188
|
+
const home = await directory.agentHome(input.agentId);
|
|
1189
|
+
if (home?.kind === "machine")
|
|
1190
|
+
throw new MachineLinkRefused("actor_not_allowed");
|
|
1191
|
+
if (home === null)
|
|
1192
|
+
throw new AppInstanceError("revoked");
|
|
782
1193
|
frame.signal.throwIfAborted();
|
|
783
1194
|
const accepted = await env.AGENTS.getByName(input.agentId).sendFromApp(installationId, input.sessionId, {
|
|
784
|
-
requestId:
|
|
1195
|
+
requestId: input.requestId,
|
|
785
1196
|
text: input.text,
|
|
786
1197
|
delivery: input.delivery,
|
|
787
1198
|
});
|