@fungi.computer/oyster 0.2.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 (287) hide show
  1. package/README.md +376 -72
  2. package/dist/agents.d.ts +3 -2
  3. package/dist/agents.js +4 -2
  4. package/dist/app-builds.d.ts +81 -8
  5. package/dist/app-builds.js +210 -60
  6. package/dist/app-storage.d.ts +3 -3
  7. package/dist/approval-capability.d.ts +5 -0
  8. package/dist/approval-capability.js +46 -0
  9. package/dist/approval-home.d.ts +51 -0
  10. package/dist/approval-home.js +226 -0
  11. package/dist/apps/catalog.d.ts +335 -0
  12. package/dist/apps/catalog.js +46 -0
  13. package/dist/apps/gate.d.ts +20 -6
  14. package/dist/apps/gate.js +42 -16
  15. package/dist/apps/index.d.ts +3 -1
  16. package/dist/apps/index.js +3 -1
  17. package/dist/apps/installation.d.ts +132 -51
  18. package/dist/apps/installation.js +176 -70
  19. package/dist/apps/permissions.d.ts +247 -0
  20. package/dist/apps/permissions.js +255 -0
  21. package/dist/boot-capability-route.d.ts +15 -0
  22. package/dist/boot-capability-route.js +196 -0
  23. package/dist/boot-capability.d.ts +103 -0
  24. package/dist/boot-capability.js +141 -0
  25. package/dist/boot-construction.d.ts +12 -0
  26. package/dist/boot-construction.js +175 -0
  27. package/dist/boot-gate.d.ts +267 -0
  28. package/dist/boot-gate.js +236 -0
  29. package/dist/boot-host-script.d.ts +2 -0
  30. package/dist/boot-host-script.js +1 -0
  31. package/dist/boot-mount.d.ts +21 -0
  32. package/dist/boot-mount.js +65 -0
  33. package/dist/browser/boot.d.ts +73 -0
  34. package/dist/browser/boot.js +502 -0
  35. package/dist/browser/browser.d.ts +104 -0
  36. package/dist/browser/browser.js +374 -0
  37. package/dist/browser/capability-source.d.ts +76 -0
  38. package/dist/browser/capability-source.js +338 -0
  39. package/dist/browser/cdp-client.d.ts +56 -0
  40. package/dist/browser/cdp-client.js +184 -0
  41. package/dist/browser/errors.d.ts +79 -0
  42. package/dist/browser/errors.js +32 -0
  43. package/dist/browser/index.d.ts +16 -0
  44. package/dist/browser/index.js +14 -0
  45. package/dist/browser/lease-registry.d.ts +61 -0
  46. package/dist/browser/lease-registry.js +101 -0
  47. package/dist/browser/operations.d.ts +225 -0
  48. package/dist/browser/operations.js +137 -0
  49. package/dist/browser/page.d.ts +25 -0
  50. package/dist/browser/page.js +172 -0
  51. package/dist/browser/quick-reads.d.ts +29 -0
  52. package/dist/browser/quick-reads.js +160 -0
  53. package/dist/browser/sessions.d.ts +52 -0
  54. package/dist/browser/sessions.js +118 -0
  55. package/dist/browser/url-policy.d.ts +18 -0
  56. package/dist/browser/url-policy.js +58 -0
  57. package/dist/build/app-imports.d.ts +12 -0
  58. package/dist/build/app-imports.js +105 -0
  59. package/dist/build/build-policy.d.ts +5 -3
  60. package/dist/build/build-policy.js +5 -3
  61. package/dist/build/candidate-builder.d.ts +1 -1
  62. package/dist/build/candidate-cli.mjs +8375 -334
  63. package/dist/build/local-build.d.ts +2 -0
  64. package/dist/build/local-build.js +28 -6
  65. package/dist/build/snapshot-package-store.d.ts +1 -1
  66. package/dist/build/snapshot-package-store.js +7 -2
  67. package/dist/build/workspace-build.d.ts +92 -5
  68. package/dist/build/workspace-build.js +93 -19
  69. package/dist/build.d.ts +1 -1
  70. package/dist/build.js +1 -1
  71. package/dist/catalog-frame.js +1 -1
  72. package/dist/cloudflare.d.ts +13 -3
  73. package/dist/cloudflare.js +102 -4
  74. package/dist/frame-binding-wire-script.js +1 -1
  75. package/dist/frame-binding-wire.js +4 -2
  76. package/dist/git-auth.d.ts +0 -2
  77. package/dist/git-auth.js +0 -2
  78. package/dist/git.d.ts +0 -1
  79. package/dist/git.js +0 -2
  80. package/dist/host.d.ts +39 -13
  81. package/dist/host.js +268 -142
  82. package/dist/index.d.ts +25 -15
  83. package/dist/index.js +39 -16
  84. package/dist/install-release-cli.d.ts +2 -0
  85. package/dist/install-release-cli.js +46 -0
  86. package/dist/installation-client.d.ts +43 -0
  87. package/dist/installation-client.js +178 -0
  88. package/dist/installation-execution.d.ts +11 -0
  89. package/dist/installation-execution.js +184 -0
  90. package/dist/installation-operations.d.ts +1437 -0
  91. package/dist/installation-operations.js +664 -0
  92. package/dist/installation-store-generation.d.ts +3 -0
  93. package/dist/installation-store-generation.js +16 -0
  94. package/dist/instance-id.d.ts +10 -0
  95. package/dist/instance-id.js +10 -0
  96. package/dist/instance-refusal.d.ts +11 -0
  97. package/dist/instance-refusal.js +21 -0
  98. package/dist/internal/delivery.js +4 -2
  99. package/dist/internal/host-gate.js +7 -1
  100. package/dist/internal/registry-installations.js +4 -4
  101. package/dist/keys.d.ts +5 -1
  102. package/dist/keys.js +10 -2
  103. package/dist/machine-link/authority.d.ts +89 -0
  104. package/dist/machine-link/authority.js +37 -0
  105. package/dist/machine-link/client.d.ts +66 -0
  106. package/dist/machine-link/client.js +94 -0
  107. package/dist/machine-link/delivery.d.ts +18 -0
  108. package/dist/machine-link/delivery.js +55 -0
  109. package/dist/machine-link/enrollment.d.ts +111 -0
  110. package/dist/machine-link/enrollment.js +286 -0
  111. package/dist/machine-link/fences.d.ts +37 -0
  112. package/dist/machine-link/fences.js +166 -0
  113. package/dist/machine-link/home.d.ts +20 -0
  114. package/dist/machine-link/home.js +24 -0
  115. package/dist/machine-link/host.d.ts +43 -0
  116. package/dist/machine-link/host.js +189 -0
  117. package/dist/machine-link/index.d.ts +15 -0
  118. package/dist/machine-link/index.js +15 -0
  119. package/dist/machine-link/link.d.ts +48 -0
  120. package/dist/machine-link/link.js +223 -0
  121. package/dist/machine-link/presence.d.ts +10 -0
  122. package/dist/machine-link/presence.js +25 -0
  123. package/dist/machine-link/workers.d.ts +6 -0
  124. package/dist/machine-link/workers.js +6 -0
  125. package/dist/mcp-owner.d.ts +2 -2
  126. package/dist/mcp-owner.js +41 -33
  127. package/dist/mcp.d.ts +15 -3
  128. package/dist/mcp.js +146 -25
  129. package/dist/pack.d.ts +14 -3
  130. package/dist/pack.js +44 -19
  131. package/dist/page/app-access.d.ts +7 -0
  132. package/dist/page/app-access.js +75 -0
  133. package/dist/page/boot-host.js +57 -0
  134. package/dist/page/capabilities.d.ts +3 -3
  135. package/dist/page/capabilities.js +16 -12
  136. package/dist/page/computer-handles.d.ts +3 -3
  137. package/dist/page/computer-target.d.ts +3 -0
  138. package/dist/page/computer-target.js +9 -0
  139. package/dist/page/mount.d.ts +23 -3
  140. package/dist/page/mount.js +152 -16
  141. package/dist/page-assets.d.ts +22 -0
  142. package/dist/page-assets.js +28 -0
  143. package/dist/page-script.d.ts +3 -0
  144. package/dist/page-script.js +5 -4
  145. package/dist/release.d.ts +13 -6
  146. package/dist/release.js +10 -7
  147. package/dist/root-data.d.ts +16 -6
  148. package/dist/root-data.js +2 -2
  149. package/dist/source/index.d.ts +1 -1
  150. package/dist/source/team-source.d.ts +20 -8
  151. package/dist/source/team-source.js +39 -10
  152. package/dist/source/template-seed.d.ts +2 -2
  153. package/dist/standalone/agent-mail.d.ts +24 -0
  154. package/dist/standalone/agent-mail.js +62 -0
  155. package/dist/standalone/agent-store.d.ts +9 -0
  156. package/dist/standalone/agent-store.js +30 -0
  157. package/dist/standalone/agents.d.ts +68 -9
  158. package/dist/standalone/agents.js +428 -56
  159. package/dist/standalone/app-builds-binding.d.ts +20 -2
  160. package/dist/standalone/app-builds-binding.js +147 -23
  161. package/dist/standalone/app-set.d.ts +5 -2
  162. package/dist/standalone/app-set.js +174 -143
  163. package/dist/standalone/browser.d.ts +11 -0
  164. package/dist/standalone/browser.js +72 -0
  165. package/dist/standalone/builds.d.ts +13 -4
  166. package/dist/standalone/builds.js +28 -44
  167. package/dist/standalone/computer-egress.d.ts +2 -0
  168. package/dist/standalone/computer-egress.js +13 -4
  169. package/dist/standalone/computer-fleet.d.ts +43 -0
  170. package/dist/standalone/computer-fleet.js +200 -0
  171. package/dist/standalone/computer.d.ts +44 -9
  172. package/dist/standalone/computer.js +129 -22
  173. package/dist/standalone/install-apps-cli.js +43 -6
  174. package/dist/standalone/instance.d.ts +2 -2
  175. package/dist/standalone/instance.js +8 -11
  176. package/dist/standalone/machine-link.d.ts +354 -0
  177. package/dist/standalone/machine-link.js +368 -0
  178. package/dist/standalone/media.d.ts +2 -2
  179. package/dist/standalone/media.js +19 -16
  180. package/dist/standalone/preview.d.ts +2 -2
  181. package/dist/standalone/preview.js +37 -10
  182. package/dist/standalone/registry.d.ts +149 -9
  183. package/dist/standalone/registry.js +299 -8
  184. package/dist/standalone/worker.d.ts +19 -5
  185. package/dist/standalone/worker.js +90 -54
  186. package/dist/standalone/workflow-binding.d.ts +18 -22
  187. package/dist/standalone/workflow-binding.js +18 -18
  188. package/dist/standalone/workflow-control.d.ts +4 -0
  189. package/dist/standalone/workflow-control.js +40 -0
  190. package/dist/standalone/workflow-events.d.ts +2 -2
  191. package/dist/standalone/workflow-events.js +9 -10
  192. package/dist/standalone/workflow-git-capture.js +4 -3
  193. package/dist/standalone/workflow-host.d.ts +162 -161
  194. package/dist/standalone/workflow-host.js +101 -21
  195. package/dist/standalone/workflow-node-capture.js +11 -4
  196. package/dist/standalone/workflow-schedules.d.ts +5 -2
  197. package/dist/standalone/workflow-schedules.js +9 -10
  198. package/dist/standalone/workflow-trigger-host.d.ts +65 -5
  199. package/dist/standalone/workflow-trigger-host.js +2 -2
  200. package/dist/standalone/workflow-webhooks.d.ts +22 -0
  201. package/dist/standalone/workflow-webhooks.js +39 -0
  202. package/dist/standalone/workflows.d.ts +140 -43
  203. package/dist/standalone/workflows.js +198 -39
  204. package/dist/storage-frame.d.ts +6 -6
  205. package/dist/testing/capabilities.js +14 -3
  206. package/dist/testing/installation.js +26 -2
  207. package/dist/testing/worker.js +1 -1
  208. package/dist/testing/workers-runtime.d.ts +8 -2
  209. package/dist/testing/workers-runtime.js +20 -2
  210. package/dist/webhook-abuse.d.ts +25 -0
  211. package/dist/webhook-abuse.js +54 -0
  212. package/dist/webhook-custody.d.ts +144 -0
  213. package/dist/webhook-custody.js +222 -0
  214. package/dist/webhook-ingress.d.ts +36 -0
  215. package/dist/webhook-ingress.js +146 -0
  216. package/dist/webhooks.d.ts +111 -0
  217. package/dist/webhooks.js +189 -0
  218. package/dist/workers-git.d.ts +12 -17
  219. package/dist/workers-git.js +60 -52
  220. package/dist/workers.d.ts +224 -13
  221. package/dist/workers.js +351 -51
  222. package/dist/workflow-agent-authority.d.ts +8 -0
  223. package/dist/workflow-agent-authority.js +42 -0
  224. package/dist/workflows.d.ts +66 -0
  225. package/dist/workflows.js +97 -0
  226. package/docs/api/README.md +63 -37
  227. package/docs/api/agents.md +17 -0
  228. package/docs/api/app-storage.md +1 -1
  229. package/docs/api/apps.md +1394 -543
  230. package/docs/api/browser/boot.md +132 -0
  231. package/docs/api/browser.md +1440 -0
  232. package/docs/api/build-contract.md +38 -25
  233. package/docs/api/build.md +263 -84
  234. package/docs/api/cloudflare.md +93 -7
  235. package/docs/api/git.md +3 -13
  236. package/docs/api/host.md +944 -118
  237. package/docs/api/index.md +320 -18
  238. package/docs/api/keys.md +24 -1
  239. package/docs/api/mcp.md +59 -12
  240. package/docs/api/modules.md +33 -40
  241. package/docs/api/pack.md +57 -8
  242. package/docs/api/page/mount.md +92 -4
  243. package/docs/api/workers-git.md +103 -24
  244. package/docs/api/workers.md +1581 -244
  245. package/docs/site/agent-quickstart.mdx +28 -14
  246. package/examples/standalone/worker.ts +3 -1
  247. package/examples/standalone/wrangler.jsonc +2 -0
  248. package/package.json +73 -71
  249. package/dist/installation-consent.d.ts +0 -81
  250. package/dist/installation-consent.js +0 -84
  251. package/dist/shell/app-directory.d.ts +0 -542
  252. package/dist/shell/app-directory.js +0 -196
  253. package/dist/shell/browser-actions.d.ts +0 -7
  254. package/dist/shell/browser-actions.js +0 -56
  255. package/dist/shell/command-host.d.ts +0 -112
  256. package/dist/shell/command-host.js +0 -502
  257. package/dist/shell/commands.d.ts +0 -25
  258. package/dist/shell/commands.js +0 -78
  259. package/dist/shell/index.d.ts +0 -8
  260. package/dist/shell/index.js +0 -8
  261. package/dist/shell/keys.d.ts +0 -41
  262. package/dist/shell/keys.js +0 -134
  263. package/dist/shell/shell-module.d.ts +0 -55
  264. package/dist/shell/shell-module.js +0 -86
  265. package/dist/shell/window-manager.d.ts +0 -128
  266. package/dist/shell/window-manager.js +0 -730
  267. package/dist/shell/workbench-contract.d.ts +0 -68
  268. package/dist/shell/workbench-directory.d.ts +0 -332
  269. package/dist/shell/workbench-directory.js +0 -35
  270. package/dist/shell/workbench-host-types.d.ts +0 -96
  271. package/dist/shell/workbench-host-types.js +0 -1
  272. package/dist/shell/workbench-host.d.ts +0 -34
  273. package/dist/shell/workbench-host.js +0 -352
  274. package/dist/shell/workbench-model.d.ts +0 -194
  275. package/dist/shell/workbench-model.js +0 -53
  276. package/dist/shell/workbench.d.ts +0 -4
  277. package/dist/shell/workbench.js +0 -2
  278. package/docs/api/shell/app-directory.md +0 -599
  279. package/docs/api/shell/command-host.md +0 -224
  280. package/docs/api/shell/commands.md +0 -197
  281. package/docs/api/shell/keys.md +0 -183
  282. package/docs/api/shell/shell-module.md +0 -204
  283. package/docs/api/shell/window-manager.md +0 -267
  284. package/docs/api/shell/workbench-directory.md +0 -119
  285. package/docs/api/shell/workbench.md +0 -205
  286. package/docs/api/shell.md +0 -1915
  287. /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";
@@ -21,16 +30,49 @@ import {} from "../cloudflare.js";
21
30
  import { AppInstanceError } from "../index.js";
22
31
  import { createAgentAvatarStore, } from "../agent-avatars.js";
23
32
  import { defineIdFamily } from "@fungi.computer/cairn";
24
- import { agentIds, createAgentDirectory, createFleetSessions, DirectoryError, } from "@fungi.computer/shiitake/fleet";
25
- import { appSurfaceCache, oysterAppMethods, } from "../mcp.js";
33
+ import { agentIds, createAgentDirectory, createFleetSessions, commandSettlementSchema, DirectoryError, } from "@fungi.computer/shiitake/fleet";
26
34
  import { appToolPolicySchema } from "../apps/index.js";
27
35
  import { oysterPrincipalIsOwner, oysterPrincipalId, } from "../host.js";
28
36
  import { defaultTenant, tenantIdSchema, tenantScope, } from "../tenant.js";
29
37
  import { createCodemodeSandbox } from "@fungi.computer/mycelium/workers";
30
- import { Knapsack } from "@fungi.computer/knapsack";
31
38
  import { maxAgentMcpServers, } from "../agents.js";
32
39
  /** Default Workers AI chat model: one that reliably calls tools. */
33
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
+ }
34
76
  /**
35
77
  * Workers AI over the AI binding. Requests go through AI Gateway's
36
78
  * `workers-ai` passthrough on the binding, so no API token is needed.
@@ -47,26 +89,16 @@ function workersAiModels(ai, options = {}) {
47
89
  baseUrl: `https://workers-binding.ai/ai-gateway/gateways/${options.gateway ?? "default"}/workers-ai/v1`,
48
90
  };
49
91
  const fetch = createAiBindingFetch(ai);
50
- return {
51
- acquire: async () => {
52
- const streamSimple = await loadStreamSimple();
53
- return {
54
- model,
55
- stream: (selected, context, streamOptions) => streamSimple(selected, context, {
56
- ...streamOptions,
57
- fetch,
58
- apiKey: CLOUDFLARE_GATEWAY_BINDING_AUTH_SENTINEL,
59
- headers: {
60
- "cf-aig-authorization": `Bearer ${CLOUDFLARE_GATEWAY_BINDING_AUTH_SENTINEL}`,
61
- // The binding carries identity; a request auth header would be
62
- // taken as a provider key that overrides the gateway's own.
63
- Authorization: null,
64
- },
65
- }),
66
- release: () => Promise.resolve(),
67
- };
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,
68
100
  },
69
- };
101
+ });
70
102
  }
71
103
  /**
72
104
  * Pi's dispatching `streamSimple` evaluates every provider's model catalog when
@@ -122,6 +154,179 @@ const instructions = [
122
154
  export class StandaloneAgentBase extends ShiitakeDO {
123
155
  /** Each installed Release's verified catalog surface, built once per Release for this Agent's lifetime. */
124
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
+ }
125
330
  /** The root host for this Agent's pinned policy directory; unpinned Agents have no App tools. */
126
331
  async appHost() {
127
332
  const stored = await this.ctx.storage.get("app-policy-directory");
@@ -140,25 +345,19 @@ export class StandaloneAgentBase extends ShiitakeDO {
140
345
  const baseUrl = egress.baseUrl("ai-gateway");
141
346
  if (baseUrl === null)
142
347
  throw new Error("Configure the Luna AI Gateway Worker secret and gateway");
143
- const provider = openaiProvider();
144
- const listed = provider
145
- .getModels()
146
- .find((item) => item.id === "gpt-6-luna");
147
- if (listed === undefined)
148
- throw new Error("The OpenAI catalog has no Luna model");
149
- const model = { ...listed, id: "openai/gpt-6-luna", baseUrl };
150
348
  return {
151
- acquire: async () => {
152
- const streamSimple = await loadStreamSimple();
153
- return {
154
- model,
155
- stream: (selected, context, options) => streamSimple(selected, context, {
156
- ...options,
157
- apiKey: "oyster-provider-egress",
158
- fetch: providerFetch(egress, "ai-gateway"),
159
- }),
160
- release: () => Promise.resolve(),
161
- };
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);
162
361
  },
163
362
  };
164
363
  }
@@ -233,12 +432,20 @@ export class StandaloneAgentBase extends ShiitakeDO {
233
432
  await this.writeMcpServers(kept);
234
433
  return true;
235
434
  }
236
- 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));
237
442
  const principal = {
238
443
  actor: { kind: "agent", agentId: this.ctx.id.name ?? "agent" },
239
444
  };
240
445
  return {
241
446
  model: this.modelSource(),
447
+ meta: { schema: FungiMessageMetaSchema },
448
+ ...this.mail().host,
242
449
  instructions: {
243
450
  load: () => Promise.resolve({ revision: "oyster-4", text: instructions }),
244
451
  },
@@ -256,6 +463,7 @@ export class StandaloneAgentBase extends ShiitakeDO {
256
463
  release: () => Promise.resolve(),
257
464
  };
258
465
  const policy = (await host.appToolPolicy?.(principal.actor)) ?? null;
466
+ const { appSurfaceCache, oysterAppMethods } = await import("../mcp.js");
259
467
  this.#appSurfaces ??= appSurfaceCache(host);
260
468
  const source = await oysterAppMethods(host, principal, policy === null ? null : { kind: "agent", policy }, this.#appSurfaces);
261
469
  return source.acquire(context);
@@ -284,10 +492,18 @@ export class StandaloneAgentBase extends ShiitakeDO {
284
492
  // The Worker's own egress: public internet only.
285
493
  fetch: (url, init) => fetch(url, init),
286
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
+ })),
287
501
  ],
288
502
  sandbox: createCodemodeSandbox(this.env.LOADER, "oyster-workerd"),
289
503
  },
290
- extensions: { roster: [], grants: [] },
504
+ extensions: oysterAgentExtensions(conversationAgents.source(this.controlDirectory())),
505
+ ...keeping,
506
+ hostWork: [...this.mail().host.hostWork, ...keeping.hostWork],
291
507
  };
292
508
  }
293
509
  /** Send an installation-admitted message through fleet Session operations. */
@@ -343,11 +559,6 @@ const storedPolicySchema = Schema.Union([
343
559
  actingInstallation: actingInstallationSchema,
344
560
  }),
345
561
  ]);
346
- const requestIds = defineIdFamily({
347
- family: "oyster-agent",
348
- version: 1,
349
- kinds: { request: { prefix: "request" } },
350
- });
351
562
  const visitRow = Schema.Struct({
352
563
  agent_id: Schema.String,
353
564
  session_id: Schema.String,
@@ -359,6 +570,139 @@ export class AgentDirectory extends DurableObject {
359
570
  directory;
360
571
  avatars;
361
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
+ }
362
706
  grant() {
363
707
  return {
364
708
  agents: "all",
@@ -379,8 +723,9 @@ export class AgentDirectory extends DurableObject {
379
723
  return this.directory
380
724
  .list(this.grant())
381
725
  .toReversed()
382
- .map(({ agentId, name }) => ({
726
+ .map(({ agentId, name, createdBy }) => ({
383
727
  agentId,
728
+ createdBy,
384
729
  name,
385
730
  avatar: this.avatars.read(agentId),
386
731
  }));
@@ -408,6 +753,7 @@ export class AgentDirectory extends DurableObject {
408
753
  this.bump();
409
754
  return {
410
755
  agentId: renamed.agentId,
756
+ createdBy: renamed.createdBy,
411
757
  name: renamed.name,
412
758
  avatar: this.avatars.read(renamed.agentId),
413
759
  };
@@ -436,19 +782,24 @@ export class AgentDirectory extends DurableObject {
436
782
  this.bump();
437
783
  return {
438
784
  agentId: agent.agentId,
785
+ createdBy: agent.createdBy,
439
786
  name: agent.name,
440
787
  avatar: this.avatars.read(agent.agentId),
441
788
  };
442
789
  });
443
790
  }
444
- create(name, requestId, principal, actor, avatar) {
791
+ create(name, requestId, principal, actor, avatar, createdBy = null) {
445
792
  if (!oysterPrincipalIsOwner(principal))
446
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");
447
798
  return this.ctx.storage.transactionSync(() => {
448
799
  const { agent, fact } = this.directory.create({
449
800
  ...this.grant(),
450
801
  provenance: { actor: oysterPrincipalId(principal) },
451
- }, { name, requestId, configuration: "none" });
802
+ }, { name, requestId, configuration: "none", createdBy });
452
803
  if (fact !== null) {
453
804
  if (avatar !== undefined)
454
805
  this.avatars.write(agent.agentId, avatar);
@@ -463,6 +814,7 @@ export class AgentDirectory extends DurableObject {
463
814
  const stored = this.storedPolicy(agent.agentId);
464
815
  return {
465
816
  agentId: agent.agentId,
817
+ createdBy: agent.createdBy,
466
818
  name: agent.name,
467
819
  avatar: this.avatars.read(agent.agentId),
468
820
  policyRevision: stored === null
@@ -691,9 +1043,16 @@ export function standaloneAgents(env, scope) {
691
1043
  : { error: "not_found" },
692
1044
  removeMcpServer: async (agentId, serverId) => (await directory().has(agentId)) &&
693
1045
  (await env.AGENTS.getByName(agentId).removeMcpServer(serverId)),
694
- fetch: async (agentId, request) => (await directory().has(agentId))
695
- ? await env.AGENTS.getByName(agentId).fetch(request)
696
- : 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
+ },
697
1056
  inbox: async () => {
698
1057
  const [agents, visits, directoryRevision] = await Promise.all([
699
1058
  directory().list(),
@@ -752,7 +1111,15 @@ const agentBindingArgs = {
752
1111
  },
753
1112
  AGENTS: {
754
1113
  send: z.tuple([
755
- 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
+ }),
756
1123
  ]),
757
1124
  },
758
1125
  };
@@ -818,9 +1185,14 @@ export async function dispatchAgentBinding(env, scope, frame, props, method, arg
818
1185
  const [input] = agentBindingArgs.AGENTS.send.parse(args);
819
1186
  if (!(await directory.has(input.agentId)))
820
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");
821
1193
  frame.signal.throwIfAborted();
822
1194
  const accepted = await env.AGENTS.getByName(input.agentId).sendFromApp(installationId, input.sessionId, {
823
- requestId: requestIds.request.generate(),
1195
+ requestId: input.requestId,
824
1196
  text: input.text,
825
1197
  delivery: input.delivery,
826
1198
  });
@@ -7,17 +7,34 @@ import type { StandaloneComputerEnv } from "./computer.js";
7
7
  /** The Instance gate and native guest entrypoint share these argument codecs. */
8
8
  export declare const appsFrameMethods: {
9
9
  APPS: {
10
+ repository: {
11
+ decodeArgs: (input: unknown, options?: import("effect/SchemaAST").ParseOptions) => readonly [{
12
+ readonly repo: string;
13
+ }];
14
+ decodeResult: (input: unknown, options?: import("effect/SchemaAST").ParseOptions) => Schema.Json;
15
+ requiredOperations: () => string[];
16
+ };
17
+ register: {
18
+ decodeArgs: (input: unknown, options?: import("effect/SchemaAST").ParseOptions) => readonly [{
19
+ readonly repo: string;
20
+ }];
21
+ decodeResult: (input: unknown, options?: import("effect/SchemaAST").ParseOptions) => Schema.Json;
22
+ requiredOperations: () => string[];
23
+ };
10
24
  build: {
11
25
  decodeArgs: (raw: readonly unknown[]) => [{
12
26
  appId: string;
13
27
  sourceRevision: string;
28
+ installationId?: string | undefined;
14
29
  }];
15
30
  decodeResult: (input: unknown, options?: import("effect/SchemaAST").ParseOptions) => Schema.Json;
16
31
  requiredOperations: () => string[];
17
32
  };
18
33
  buildStatus: {
19
- decodeArgs: (raw: readonly unknown[]) => [{
20
- jobId: string;
34
+ decodeArgs: (input: unknown, options?: import("effect/SchemaAST").ParseOptions) => readonly [{
35
+ readonly jobId: string;
36
+ } | {
37
+ readonly repo: string;
21
38
  }];
22
39
  decodeResult: (input: unknown, options?: import("effect/SchemaAST").ParseOptions) => Schema.Json;
23
40
  requiredOperations: () => string[];
@@ -25,6 +42,7 @@ export declare const appsFrameMethods: {
25
42
  select: {
26
43
  decodeArgs: (raw: readonly unknown[]) => [{
27
44
  installationId: string;
45
+ jobId: string;
28
46
  release: unknown;
29
47
  }];
30
48
  decodeResult: (input: unknown, options?: import("effect/SchemaAST").ParseOptions) => Schema.Json;