@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.
Files changed (292) hide show
  1. package/README.md +376 -72
  2. package/dist/agent-avatars.d.ts +103 -0
  3. package/dist/agent-avatars.js +66 -0
  4. package/dist/agents.d.ts +15 -4
  5. package/dist/agents.js +43 -4
  6. package/dist/app-builds.d.ts +81 -8
  7. package/dist/app-builds.js +210 -60
  8. package/dist/app-storage.d.ts +3 -3
  9. package/dist/approval-capability.d.ts +5 -0
  10. package/dist/approval-capability.js +46 -0
  11. package/dist/approval-home.d.ts +51 -0
  12. package/dist/approval-home.js +226 -0
  13. package/dist/apps/catalog.d.ts +335 -0
  14. package/dist/apps/catalog.js +46 -0
  15. package/dist/apps/gate.d.ts +26 -12
  16. package/dist/apps/gate.js +48 -22
  17. package/dist/apps/index.d.ts +3 -1
  18. package/dist/apps/index.js +3 -1
  19. package/dist/apps/installation.d.ts +147 -66
  20. package/dist/apps/installation.js +193 -86
  21. package/dist/apps/permissions.d.ts +247 -0
  22. package/dist/apps/permissions.js +255 -0
  23. package/dist/boot-capability-route.d.ts +15 -0
  24. package/dist/boot-capability-route.js +196 -0
  25. package/dist/boot-capability.d.ts +103 -0
  26. package/dist/boot-capability.js +141 -0
  27. package/dist/boot-construction.d.ts +12 -0
  28. package/dist/boot-construction.js +175 -0
  29. package/dist/boot-gate.d.ts +267 -0
  30. package/dist/boot-gate.js +236 -0
  31. package/dist/boot-host-script.d.ts +2 -0
  32. package/dist/boot-host-script.js +1 -0
  33. package/dist/boot-mount.d.ts +21 -0
  34. package/dist/boot-mount.js +65 -0
  35. package/dist/browser/boot.d.ts +73 -0
  36. package/dist/browser/boot.js +502 -0
  37. package/dist/browser/browser.d.ts +104 -0
  38. package/dist/browser/browser.js +374 -0
  39. package/dist/browser/capability-source.d.ts +76 -0
  40. package/dist/browser/capability-source.js +338 -0
  41. package/dist/browser/cdp-client.d.ts +56 -0
  42. package/dist/browser/cdp-client.js +184 -0
  43. package/dist/browser/errors.d.ts +79 -0
  44. package/dist/browser/errors.js +32 -0
  45. package/dist/browser/index.d.ts +16 -0
  46. package/dist/browser/index.js +14 -0
  47. package/dist/browser/lease-registry.d.ts +61 -0
  48. package/dist/browser/lease-registry.js +101 -0
  49. package/dist/browser/operations.d.ts +225 -0
  50. package/dist/browser/operations.js +137 -0
  51. package/dist/browser/page.d.ts +25 -0
  52. package/dist/browser/page.js +172 -0
  53. package/dist/browser/quick-reads.d.ts +29 -0
  54. package/dist/browser/quick-reads.js +160 -0
  55. package/dist/browser/sessions.d.ts +52 -0
  56. package/dist/browser/sessions.js +118 -0
  57. package/dist/browser/url-policy.d.ts +18 -0
  58. package/dist/browser/url-policy.js +58 -0
  59. package/dist/build/app-imports.d.ts +12 -0
  60. package/dist/build/app-imports.js +105 -0
  61. package/dist/build/build-policy.d.ts +5 -3
  62. package/dist/build/build-policy.js +5 -3
  63. package/dist/build/candidate-builder.d.ts +1 -1
  64. package/dist/build/candidate-cli.mjs +8428 -337
  65. package/dist/build/local-build.d.ts +2 -0
  66. package/dist/build/local-build.js +28 -6
  67. package/dist/build/snapshot-package-store.d.ts +1 -1
  68. package/dist/build/snapshot-package-store.js +7 -2
  69. package/dist/build/workspace-build.d.ts +92 -5
  70. package/dist/build/workspace-build.js +93 -19
  71. package/dist/build-egress.js +4 -4
  72. package/dist/build.d.ts +1 -1
  73. package/dist/build.js +1 -1
  74. package/dist/catalog-frame.js +1 -1
  75. package/dist/cloudflare.d.ts +13 -3
  76. package/dist/cloudflare.js +106 -8
  77. package/dist/frame-binding-wire-script.js +1 -1
  78. package/dist/frame-binding-wire.d.ts +1 -1
  79. package/dist/frame-binding-wire.js +5 -3
  80. package/dist/git-auth.d.ts +0 -2
  81. package/dist/git-auth.js +0 -2
  82. package/dist/git-journal.js +4 -4
  83. package/dist/git.d.ts +0 -1
  84. package/dist/git.js +0 -2
  85. package/dist/host.d.ts +39 -13
  86. package/dist/host.js +268 -142
  87. package/dist/index.d.ts +26 -15
  88. package/dist/index.js +40 -16
  89. package/dist/install-release-cli.d.ts +2 -0
  90. package/dist/install-release-cli.js +46 -0
  91. package/dist/installation-client.d.ts +43 -0
  92. package/dist/installation-client.js +178 -0
  93. package/dist/installation-execution.d.ts +11 -0
  94. package/dist/installation-execution.js +184 -0
  95. package/dist/installation-operations.d.ts +1437 -0
  96. package/dist/installation-operations.js +664 -0
  97. package/dist/installation-store-generation.d.ts +3 -0
  98. package/dist/installation-store-generation.js +16 -0
  99. package/dist/instance-id.d.ts +10 -0
  100. package/dist/instance-id.js +10 -0
  101. package/dist/instance-refusal.d.ts +11 -0
  102. package/dist/instance-refusal.js +21 -0
  103. package/dist/internal/delivery.js +4 -2
  104. package/dist/internal/host-gate.js +7 -1
  105. package/dist/internal/registry-installations.js +4 -4
  106. package/dist/keys.d.ts +5 -1
  107. package/dist/keys.js +10 -2
  108. package/dist/machine-link/authority.d.ts +89 -0
  109. package/dist/machine-link/authority.js +37 -0
  110. package/dist/machine-link/client.d.ts +66 -0
  111. package/dist/machine-link/client.js +94 -0
  112. package/dist/machine-link/delivery.d.ts +18 -0
  113. package/dist/machine-link/delivery.js +55 -0
  114. package/dist/machine-link/enrollment.d.ts +111 -0
  115. package/dist/machine-link/enrollment.js +286 -0
  116. package/dist/machine-link/fences.d.ts +37 -0
  117. package/dist/machine-link/fences.js +166 -0
  118. package/dist/machine-link/home.d.ts +20 -0
  119. package/dist/machine-link/home.js +24 -0
  120. package/dist/machine-link/host.d.ts +43 -0
  121. package/dist/machine-link/host.js +189 -0
  122. package/dist/machine-link/index.d.ts +15 -0
  123. package/dist/machine-link/index.js +15 -0
  124. package/dist/machine-link/link.d.ts +48 -0
  125. package/dist/machine-link/link.js +223 -0
  126. package/dist/machine-link/presence.d.ts +10 -0
  127. package/dist/machine-link/presence.js +25 -0
  128. package/dist/machine-link/workers.d.ts +6 -0
  129. package/dist/machine-link/workers.js +6 -0
  130. package/dist/mcp-owner.d.ts +2 -2
  131. package/dist/mcp-owner.js +41 -33
  132. package/dist/mcp.d.ts +15 -3
  133. package/dist/mcp.js +146 -25
  134. package/dist/pack.d.ts +14 -3
  135. package/dist/pack.js +44 -19
  136. package/dist/page/app-access.d.ts +7 -0
  137. package/dist/page/app-access.js +75 -0
  138. package/dist/page/boot-host.js +57 -0
  139. package/dist/page/capabilities.d.ts +3 -3
  140. package/dist/page/capabilities.js +16 -12
  141. package/dist/page/computer-handles.d.ts +3 -3
  142. package/dist/page/computer-target.d.ts +3 -0
  143. package/dist/page/computer-target.js +9 -0
  144. package/dist/page/mount.d.ts +23 -7
  145. package/dist/page/mount.js +152 -16
  146. package/dist/page-assets.d.ts +22 -0
  147. package/dist/page-assets.js +28 -0
  148. package/dist/page-script.d.ts +3 -0
  149. package/dist/page-script.js +5 -4
  150. package/dist/release.d.ts +13 -6
  151. package/dist/release.js +10 -7
  152. package/dist/root-data.d.ts +16 -6
  153. package/dist/root-data.js +2 -2
  154. package/dist/source/index.d.ts +1 -1
  155. package/dist/source/team-source.d.ts +20 -8
  156. package/dist/source/team-source.js +39 -10
  157. package/dist/source/template-seed.d.ts +2 -2
  158. package/dist/standalone/agent-mail.d.ts +24 -0
  159. package/dist/standalone/agent-mail.js +62 -0
  160. package/dist/standalone/agent-store.d.ts +9 -0
  161. package/dist/standalone/agent-store.js +30 -0
  162. package/dist/standalone/agents.d.ts +78 -12
  163. package/dist/standalone/agents.js +470 -59
  164. package/dist/standalone/app-builds-binding.d.ts +20 -2
  165. package/dist/standalone/app-builds-binding.js +151 -27
  166. package/dist/standalone/app-set.d.ts +5 -2
  167. package/dist/standalone/app-set.js +174 -143
  168. package/dist/standalone/browser.d.ts +11 -0
  169. package/dist/standalone/browser.js +72 -0
  170. package/dist/standalone/builds.d.ts +13 -4
  171. package/dist/standalone/builds.js +28 -44
  172. package/dist/standalone/computer-egress.d.ts +2 -0
  173. package/dist/standalone/computer-egress.js +13 -4
  174. package/dist/standalone/computer-fleet.d.ts +43 -0
  175. package/dist/standalone/computer-fleet.js +200 -0
  176. package/dist/standalone/computer.d.ts +44 -9
  177. package/dist/standalone/computer.js +132 -25
  178. package/dist/standalone/install-apps-cli.js +43 -6
  179. package/dist/standalone/instance.d.ts +2 -2
  180. package/dist/standalone/instance.js +8 -11
  181. package/dist/standalone/machine-link.d.ts +354 -0
  182. package/dist/standalone/machine-link.js +368 -0
  183. package/dist/standalone/media.d.ts +2 -2
  184. package/dist/standalone/media.js +19 -16
  185. package/dist/standalone/preview.d.ts +2 -2
  186. package/dist/standalone/preview.js +39 -12
  187. package/dist/standalone/registry.d.ts +149 -9
  188. package/dist/standalone/registry.js +299 -8
  189. package/dist/standalone/worker.d.ts +19 -5
  190. package/dist/standalone/worker.js +90 -54
  191. package/dist/standalone/workflow-binding.d.ts +28 -32
  192. package/dist/standalone/workflow-binding.js +20 -20
  193. package/dist/standalone/workflow-control.d.ts +4 -0
  194. package/dist/standalone/workflow-control.js +40 -0
  195. package/dist/standalone/workflow-events.d.ts +2 -2
  196. package/dist/standalone/workflow-events.js +9 -10
  197. package/dist/standalone/workflow-git-capture.js +4 -3
  198. package/dist/standalone/workflow-host.d.ts +177 -152
  199. package/dist/standalone/workflow-host.js +137 -30
  200. package/dist/standalone/workflow-node-capture.js +11 -4
  201. package/dist/standalone/workflow-schedules.d.ts +5 -2
  202. package/dist/standalone/workflow-schedules.js +9 -10
  203. package/dist/standalone/workflow-trigger-host.d.ts +88 -6
  204. package/dist/standalone/workflow-trigger-host.js +2 -2
  205. package/dist/standalone/workflow-webhooks.d.ts +22 -0
  206. package/dist/standalone/workflow-webhooks.js +39 -0
  207. package/dist/standalone/workflows.d.ts +203 -50
  208. package/dist/standalone/workflows.js +198 -39
  209. package/dist/storage-frame.d.ts +6 -6
  210. package/dist/testing/capabilities.js +14 -3
  211. package/dist/testing/installation.js +26 -2
  212. package/dist/testing/worker.js +1 -1
  213. package/dist/testing/workers-runtime.d.ts +8 -2
  214. package/dist/testing/workers-runtime.js +20 -2
  215. package/dist/webhook-abuse.d.ts +25 -0
  216. package/dist/webhook-abuse.js +54 -0
  217. package/dist/webhook-custody.d.ts +144 -0
  218. package/dist/webhook-custody.js +222 -0
  219. package/dist/webhook-ingress.d.ts +36 -0
  220. package/dist/webhook-ingress.js +146 -0
  221. package/dist/webhooks.d.ts +111 -0
  222. package/dist/webhooks.js +189 -0
  223. package/dist/workers-git.d.ts +12 -17
  224. package/dist/workers-git.js +60 -55
  225. package/dist/workers.d.ts +224 -13
  226. package/dist/workers.js +351 -51
  227. package/dist/workflow-agent-authority.d.ts +8 -0
  228. package/dist/workflow-agent-authority.js +42 -0
  229. package/dist/workflows.d.ts +66 -0
  230. package/dist/workflows.js +97 -0
  231. package/docs/api/README.md +63 -37
  232. package/docs/api/agents.md +17 -0
  233. package/docs/api/app-storage.md +1 -1
  234. package/docs/api/apps.md +1394 -543
  235. package/docs/api/browser/boot.md +132 -0
  236. package/docs/api/browser.md +1440 -0
  237. package/docs/api/build-contract.md +38 -25
  238. package/docs/api/build.md +263 -84
  239. package/docs/api/cloudflare.md +93 -7
  240. package/docs/api/git.md +3 -13
  241. package/docs/api/host.md +944 -118
  242. package/docs/api/index.md +320 -18
  243. package/docs/api/keys.md +24 -1
  244. package/docs/api/mcp.md +59 -12
  245. package/docs/api/modules.md +33 -40
  246. package/docs/api/pack.md +57 -8
  247. package/docs/api/page/mount.md +92 -4
  248. package/docs/api/workers-git.md +103 -24
  249. package/docs/api/workers.md +1581 -244
  250. package/docs/site/agent-quickstart.mdx +28 -14
  251. package/examples/standalone/worker.ts +3 -1
  252. package/examples/standalone/wrangler.jsonc +2 -0
  253. package/package.json +73 -69
  254. package/dist/installation-consent.d.ts +0 -79
  255. package/dist/installation-consent.js +0 -82
  256. package/dist/shell/app-directory.d.ts +0 -542
  257. package/dist/shell/app-directory.js +0 -199
  258. package/dist/shell/browser-actions.d.ts +0 -7
  259. package/dist/shell/browser-actions.js +0 -56
  260. package/dist/shell/command-host.d.ts +0 -112
  261. package/dist/shell/command-host.js +0 -502
  262. package/dist/shell/commands.d.ts +0 -25
  263. package/dist/shell/commands.js +0 -78
  264. package/dist/shell/index.d.ts +0 -8
  265. package/dist/shell/index.js +0 -8
  266. package/dist/shell/keys.d.ts +0 -41
  267. package/dist/shell/keys.js +0 -134
  268. package/dist/shell/shell-module.d.ts +0 -55
  269. package/dist/shell/shell-module.js +0 -86
  270. package/dist/shell/window-manager.d.ts +0 -124
  271. package/dist/shell/window-manager.js +0 -711
  272. package/dist/shell/workbench-contract.d.ts +0 -68
  273. package/dist/shell/workbench-directory.d.ts +0 -332
  274. package/dist/shell/workbench-directory.js +0 -35
  275. package/dist/shell/workbench-host-types.d.ts +0 -96
  276. package/dist/shell/workbench-host-types.js +0 -1
  277. package/dist/shell/workbench-host.d.ts +0 -34
  278. package/dist/shell/workbench-host.js +0 -347
  279. package/dist/shell/workbench-model.d.ts +0 -192
  280. package/dist/shell/workbench-model.js +0 -53
  281. package/dist/shell/workbench.d.ts +0 -4
  282. package/dist/shell/workbench.js +0 -2
  283. package/docs/api/shell/app-directory.md +0 -599
  284. package/docs/api/shell/command-host.md +0 -224
  285. package/docs/api/shell/commands.md +0 -197
  286. package/docs/api/shell/keys.md +0 -183
  287. package/docs/api/shell/shell-module.md +0 -204
  288. package/docs/api/shell/window-manager.md +0 -267
  289. package/docs/api/shell/workbench-directory.md +0 -119
  290. package/docs/api/shell/workbench.md +0 -205
  291. package/docs/api/shell.md +0 -1915
  292. /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 { openaiProvider } from "@earendil-works/pi-ai/providers/openai";
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
- acquire: async () => {
51
- const streamSimple = await loadStreamSimple();
52
- return {
53
- model,
54
- stream: (selected, context, streamOptions) => streamSimple(selected, context, {
55
- ...streamOptions,
56
- fetch,
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 streamSimple = await loadStreamSimple();
152
- return {
153
- model,
154
- stream: (selected, context, options) => streamSimple(selected, context, {
155
- ...options,
156
- apiKey: "oyster-provider-egress",
157
- fetch: providerFetch(egress, "ai-gateway"),
158
- }),
159
- release: () => Promise.resolve(),
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: { roster: [], grants: [] },
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 }) => ({ 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 { agentId: renamed.agentId, name: renamed.name };
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
- create(name, requestId, principal, actor) {
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) => (await directory().has(agentId))
656
- ? await env.AGENTS.getByName(agentId).fetch(request)
657
- : null,
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({ text: z.string(), delivery: z.literal("followUp") }),
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: requestIds.request.generate(),
1195
+ requestId: input.requestId,
785
1196
  text: input.text,
786
1197
  delivery: input.delivery,
787
1198
  });