@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
@@ -2,29 +2,39 @@
2
2
  * Owner routes for App builds on the standalone's Computer. They run after
3
3
  * the host's owner guard; the build owner is the registry.
4
4
  *
5
- * - `PUT /api/apps/:appId/build-source` records an installation repository
6
- * and recipe an App builds from (the recipe is normalized and pinned here).
7
- * Publishing an App's source (`PUT /api/apps/:appId/source`) instead makes
8
- * its template the build source, buildable at the returned seed revision.
5
+ * Repository Apps register through APPS.register from their committed root
6
+ * metadata. Publishing an App's source (PUT /api/apps/:appId/source) instead
7
+ * makes its template the build source at the returned seed revision.
8
+ *
9
9
  * - `POST /api/builds` starts a build of that source at one exact commit.
10
10
  * - `GET /api/builds/:jobId` reads it; a finished build carries its Release,
11
11
  * which the owner installs or selects through the installation routes.
12
12
  */
13
13
  import { Hono } from "hono";
14
14
  import * as Schema from "effect/Schema";
15
- import { capturedPinnedRecipeSchema, normalizeAppPublisherRecipe, pinnedRecipeSchema, } from "../build-contract.js";
16
- import { oysterBuildSourceSchema } from "../app-builds.js";
15
+ import { oysterBuildStartMessages, } from "../app-builds.js";
17
16
  import { oysterPrincipalIsOwner } from "../host.js";
18
17
  import { appIdSchema, releaseSchema, sourceRevisionSchema, } from "@fungi.computer/puffball/effect";
19
- const sourceInput = Schema.Struct({
20
- installationId: Schema.String,
21
- repository: Schema.String,
22
- recipe: pinnedRecipeSchema,
23
- });
24
18
  const buildInput = Schema.Struct({
25
19
  appId: appIdSchema,
26
20
  sourceRevision: sourceRevisionSchema,
27
21
  });
22
+ /** Both HTTP and APPS expose the same closed preflight reason. */
23
+ export function buildStartFailureReply(reason) {
24
+ return {
25
+ status: reason === "invalid_revision"
26
+ ? 400
27
+ : reason === "unavailable" || reason === "no_backend"
28
+ ? 503
29
+ : 409,
30
+ body: {
31
+ code: reason,
32
+ error: reason === "unavailable"
33
+ ? "Build is unavailable"
34
+ : oysterBuildStartMessages[reason],
35
+ },
36
+ };
37
+ }
28
38
  /** The build routes over the tenant registry that owns builds, resolved per
29
39
  * request so mounting the routes reaches no registry. */
30
40
  export function standaloneBuildRoutes(registryFor) {
@@ -32,36 +42,6 @@ export function standaloneBuildRoutes(registryFor) {
32
42
  routes.use("/api/builds/*", async (context, next) => oysterPrincipalIsOwner(context.get("principal"))
33
43
  ? next()
34
44
  : Response.json({ error: "Owner only" }, { status: 403 }));
35
- routes.put("/api/apps/:appId/build-source", async (context) => {
36
- if (!oysterPrincipalIsOwner(context.get("principal")))
37
- return Response.json({ error: "Owner only" }, { status: 403 });
38
- let source;
39
- try {
40
- const appId = Schema.decodeUnknownSync(appIdSchema)(context.req.param("appId"));
41
- const input = Schema.decodeUnknownSync(sourceInput, {
42
- onExcessProperty: "error",
43
- })(await context.req.json());
44
- const pinned = normalizeAppPublisherRecipe({
45
- appId,
46
- recipe: Schema.encodeSync(pinnedRecipeSchema)(input.recipe),
47
- });
48
- source = Schema.decodeUnknownSync(oysterBuildSourceSchema)({
49
- appId,
50
- location: {
51
- kind: "installation",
52
- installationId: input.installationId,
53
- repository: input.repository,
54
- },
55
- recipe: Schema.encodeSync(capturedPinnedRecipeSchema)(pinned.recipe),
56
- buildRecipeDigest: pinned.buildRecipeDigest,
57
- });
58
- }
59
- catch {
60
- return Response.json({ error: "Invalid build source" }, { status: 400 });
61
- }
62
- await registryFor().registerBuildSource(source);
63
- return new Response(null, { status: 204 });
64
- });
65
45
  routes.post("/api/builds", async (context) => {
66
46
  const principal = context.get("principal");
67
47
  if (!oysterPrincipalIsOwner(principal))
@@ -76,14 +56,18 @@ export function standaloneBuildRoutes(registryFor) {
76
56
  return Response.json({ error: "Invalid build" }, { status: 400 });
77
57
  }
78
58
  try {
79
- const build = await registryFor().startBuild({
59
+ const result = await registryFor().startBuild({
80
60
  ...input,
81
61
  requestedBy: principal.actor.memberId,
82
62
  });
83
- return Response.json(build, { status: 202 });
63
+ if (result.kind === "started")
64
+ return Response.json(result.build, { status: 202 });
65
+ const reply = buildStartFailureReply(result.reason);
66
+ return Response.json(reply.body, { status: reply.status });
84
67
  }
85
68
  catch {
86
- return Response.json({ error: "App has no build source" }, { status: 409 });
69
+ const reply = buildStartFailureReply("unavailable");
70
+ return Response.json(reply.body, { status: reply.status });
87
71
  }
88
72
  });
89
73
  routes.get("/api/builds/:jobId", async (context) => {
@@ -32,6 +32,8 @@ export declare function serveOysterComputerEgress(request: Request, options: Rea
32
32
  computerId: string;
33
33
  /** `COMPUTER_PUBLIC_EGRESS`; only `disabled` closes the public lane. */
34
34
  publicEgress: string | undefined;
35
+ /** Live setting from the Computer identified by the host's loopback props. */
36
+ internetAccess(): Promise<boolean>;
35
37
  build: OysterComputerBuildPort;
36
38
  forward(request: Request): Promise<Response>;
37
39
  }>): Promise<Response>;
@@ -26,12 +26,12 @@ const templateRoute = /^\/([a-zA-Z0-9._-]{1,128}?)\.git\/(info\/refs|git-upload-
26
26
  * Worker's own fetch, whose door authenticates it as for any other client.
27
27
  */
28
28
  export async function serveOysterComputerEgress(request, options) {
29
- const deny = (host, decision) => computerEgressDenied({
29
+ const deny = (host, decision, status = 403) => computerEgressDenied({
30
30
  computerId: options.computerId,
31
31
  teamId: null,
32
32
  host,
33
33
  method: request.method,
34
- status: 403,
34
+ status,
35
35
  decision,
36
36
  });
37
37
  let url;
@@ -51,10 +51,19 @@ export async function serveOysterComputerEgress(request, options) {
51
51
  if (decision.kind === "origin-git")
52
52
  response = await options.forward(originGitRequest(request, url));
53
53
  else if (decision.kind === "public") {
54
+ let enabled;
55
+ let globalEnabled;
56
+ try {
57
+ globalEnabled = publicEgressEnabled(options.publicEgress);
58
+ enabled = globalEnabled ? await options.internetAccess() : true;
59
+ }
60
+ catch {
61
+ return deny(url.hostname, "policy-unavailable", 503);
62
+ }
54
63
  const lane = decidePublicLane({
55
- publicEgressEnabled: publicEgressEnabled(options.publicEgress),
64
+ publicEgressEnabled: globalEnabled,
56
65
  teamDisabled: false,
57
- computerInternetAccessEnabled: true,
66
+ computerInternetAccessEnabled: enabled,
58
67
  });
59
68
  if (lane.kind === "denied")
60
69
  return deny(url.hostname, lane.reason);
@@ -0,0 +1,43 @@
1
+ import { Hono } from "hono";
2
+ import { z } from "zod";
3
+ import { type OysterAuth, type OysterEnv } from "../host.js";
4
+ import type { StandaloneRegistry } from "./registry.js";
5
+ export declare const standaloneFleetRequestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
6
+ operation: z.ZodLiteral<"create">;
7
+ request: z.ZodObject<{
8
+ name: z.ZodOptional<z.ZodString>;
9
+ requestId: z.ZodString;
10
+ }, z.core.$strict>;
11
+ }, z.core.$strict>, z.ZodObject<{
12
+ operation: z.ZodEnum<{
13
+ list: "list";
14
+ choices: "choices";
15
+ }>;
16
+ page: z.ZodObject<{
17
+ limit: z.ZodDefault<z.ZodNumber>;
18
+ offset: z.ZodDefault<z.ZodNumber>;
19
+ }, z.core.$strict>;
20
+ }, z.core.$strict>, z.ZodObject<{
21
+ operation: z.ZodEnum<{
22
+ read: "read";
23
+ resolve: "resolve";
24
+ select: "select";
25
+ start: "start";
26
+ }>;
27
+ computerId: z.ZodString;
28
+ }, z.core.$strict>, z.ZodObject<{
29
+ operation: z.ZodLiteral<"read-internet-access">;
30
+ computerId: z.ZodString;
31
+ }, z.core.$strict>, z.ZodObject<{
32
+ operation: z.ZodLiteral<"set-internet-access">;
33
+ computerId: z.ZodString;
34
+ enabled: z.ZodBoolean;
35
+ }, z.core.$strict>], "operation">;
36
+ export type StandaloneFleetRequest = z.input<typeof standaloneFleetRequestSchema>;
37
+ /** A window's requested context is a hint, never authority or a default mutation. */
38
+ export declare function standaloneComputerSelection(registry: Pick<StandaloneRegistry, "computerFleet" | "selectedComputer">, incoming?: Readonly<{
39
+ request: Request;
40
+ auth: OysterAuth;
41
+ }>): Promise<string>;
42
+ /** Tenant-owner management controls over the same durable fleet as Hub. */
43
+ export declare function standaloneComputerFleetRoutes(registryFor: () => DurableObjectStub<StandaloneRegistry>): Hono<OysterEnv>;
@@ -0,0 +1,200 @@
1
+ import { Hono } from "hono";
2
+ import { HTTPException } from "hono/http-exception";
3
+ import { z } from "zod";
4
+ import { computerCreateRequestSchema, computerPageSchema, } from "@fungi.computer/computer/fleet";
5
+ import { oysterPrincipalIsOwner, } from "../host.js";
6
+ /** Fleet controls never accept an actor from the request body. */
7
+ const fleetComputerId = z.string().min(1).max(256);
8
+ export const standaloneFleetRequestSchema = z.discriminatedUnion("operation", [
9
+ z.strictObject({
10
+ operation: z.literal("create"),
11
+ request: computerCreateRequestSchema,
12
+ }),
13
+ z.strictObject({
14
+ operation: z.enum(["list", "choices"]),
15
+ page: computerPageSchema,
16
+ }),
17
+ z.strictObject({
18
+ operation: z.enum(["read", "resolve", "start", "select"]),
19
+ computerId: fleetComputerId,
20
+ }),
21
+ z.strictObject({
22
+ operation: z.literal("read-internet-access"),
23
+ computerId: fleetComputerId,
24
+ }),
25
+ z.strictObject({
26
+ operation: z.literal("set-internet-access"),
27
+ computerId: fleetComputerId,
28
+ enabled: z.boolean(),
29
+ }),
30
+ ]);
31
+ function fleetStatus(result) {
32
+ if (result === null)
33
+ return 404;
34
+ if ("error" in result)
35
+ return result.error === "owner_required"
36
+ ? 403
37
+ : result.error === "partial_init"
38
+ ? 503
39
+ : 409;
40
+ return 200;
41
+ }
42
+ function fleetResponse(result) {
43
+ return Response.json(result ?? { error: "not_found" }, {
44
+ status: fleetStatus(result),
45
+ });
46
+ }
47
+ /** A window's requested context is a hint, never authority or a default mutation. */
48
+ export async function standaloneComputerSelection(registry, incoming) {
49
+ if (incoming === undefined)
50
+ return registry.selectedComputer();
51
+ const requested = new URL(incoming.request.url).searchParams.getAll("computerId");
52
+ if (requested.length === 0)
53
+ return registry.selectedComputer();
54
+ const admission = await incoming.auth.authenticate(incoming.request);
55
+ if (admission.kind === "refused")
56
+ throw new HTTPException(admission.status, {
57
+ res: Response.json({ error: "Computer selection refused" }, {
58
+ status: admission.status,
59
+ headers: admission.retryAfter === undefined
60
+ ? {}
61
+ : { "Retry-After": String(admission.retryAfter) },
62
+ }),
63
+ });
64
+ if (!oysterPrincipalIsOwner(admission.principal))
65
+ throw new HTTPException(403, { message: "Owner only" });
66
+ const computerId = z.tuple([fleetComputerId]).safeParse(requested);
67
+ if (!computerId.success)
68
+ throw new HTTPException(400, { message: "Invalid Computer selection" });
69
+ const result = await registry.computerFleet(admission.principal.actor.memberId, {
70
+ operation: "resolve",
71
+ computerId: computerId.data[0],
72
+ });
73
+ const status = fleetStatus(result);
74
+ if (status !== 200)
75
+ throw new HTTPException(status, { res: fleetResponse(result) });
76
+ return computerId.data[0];
77
+ }
78
+ /** Tenant-owner management controls over the same durable fleet as Hub. */
79
+ export function standaloneComputerFleetRoutes(registryFor) {
80
+ const routes = new Hono();
81
+ routes.use("/api/computers/*", async (context, next) => oysterPrincipalIsOwner(context.get("principal"))
82
+ ? next()
83
+ : context.json({ error: "Owner only" }, 403));
84
+ routes.onError((error, context) => {
85
+ if (error instanceof z.ZodError || error instanceof SyntaxError)
86
+ return context.json({ error: "invalid_input" }, 400);
87
+ throw error;
88
+ });
89
+ routes.get("/api/workbench/directory", async (context) => {
90
+ const principal = context.get("principal");
91
+ if (!oysterPrincipalIsOwner(principal))
92
+ return context.json({ error: "Owner only" }, 403);
93
+ const registry = registryFor();
94
+ const admitted = await registry.computerFleet(principal.actor.memberId, {
95
+ operation: "list",
96
+ page: { limit: 1, offset: 0 },
97
+ });
98
+ if (admitted === null || "error" in admitted)
99
+ return fleetResponse(admitted);
100
+ const [directory, computerId] = await Promise.all([
101
+ registry.directory(),
102
+ registry.selectedComputer(),
103
+ ]);
104
+ return context.json({
105
+ ...directory,
106
+ computer: { computerId, name: computerId },
107
+ computerContext: "owner-fleet",
108
+ });
109
+ });
110
+ routes.get("/api/computers", async (context) => {
111
+ const principal = context.get("principal");
112
+ if (!oysterPrincipalIsOwner(principal))
113
+ return context.json({ error: "Owner only" }, 403);
114
+ const page = computerPageSchema.parse({
115
+ limit: Number(context.req.query("limit") ?? 100),
116
+ offset: Number(context.req.query("offset") ?? 0),
117
+ });
118
+ return fleetResponse(await registryFor().computerFleet(principal.actor.memberId, {
119
+ operation: "list",
120
+ page,
121
+ }));
122
+ });
123
+ routes.get("/api/workbench/computers", async (context) => {
124
+ const principal = context.get("principal");
125
+ if (!oysterPrincipalIsOwner(principal))
126
+ return context.json({ error: "Owner only" }, 403);
127
+ return fleetResponse(await registryFor().computerFleet(principal.actor.memberId, {
128
+ operation: "choices",
129
+ page: computerPageSchema.parse({
130
+ limit: Number(context.req.query("limit") ?? 100),
131
+ offset: Number(context.req.query("offset") ?? 0),
132
+ }),
133
+ }));
134
+ });
135
+ routes.post("/api/computers", async (context) => {
136
+ const principal = context.get("principal");
137
+ if (!oysterPrincipalIsOwner(principal))
138
+ return context.json({ error: "Owner only" }, 403);
139
+ const input = computerCreateRequestSchema.parse(await context.req.json());
140
+ return fleetResponse(await registryFor().computerFleet(principal.actor.memberId, {
141
+ operation: "create",
142
+ request: input,
143
+ }));
144
+ });
145
+ routes.post("/api/computers/:computerId/start", async (context) => {
146
+ const principal = context.get("principal");
147
+ if (!oysterPrincipalIsOwner(principal))
148
+ return context.json({ error: "Owner only" }, 403);
149
+ const result = await registryFor().computerFleet(principal.actor.memberId, {
150
+ operation: "start",
151
+ computerId: context.req.param("computerId"),
152
+ });
153
+ return fleetResponse(result);
154
+ });
155
+ routes.get("/api/computers/:computerId/internet-access", async (context) => {
156
+ const principal = context.get("principal");
157
+ if (!oysterPrincipalIsOwner(principal))
158
+ return context.json({ error: "Owner only" }, 403);
159
+ return fleetResponse(await registryFor().computerFleet(principal.actor.memberId, {
160
+ operation: "read-internet-access",
161
+ computerId: context.req.param("computerId"),
162
+ }));
163
+ });
164
+ routes.put("/api/computers/:computerId/internet-access", async (context) => {
165
+ const principal = context.get("principal");
166
+ if (!oysterPrincipalIsOwner(principal))
167
+ return context.json({ error: "Owner only" }, 403);
168
+ const input = z
169
+ .strictObject({ enabled: z.boolean() })
170
+ .parse(await context.req.json());
171
+ return fleetResponse(await registryFor().computerFleet(principal.actor.memberId, {
172
+ operation: "set-internet-access",
173
+ computerId: context.req.param("computerId"),
174
+ enabled: input.enabled,
175
+ }));
176
+ });
177
+ routes.get("/api/computers/:computerId", async (context) => {
178
+ const principal = context.get("principal");
179
+ if (!oysterPrincipalIsOwner(principal))
180
+ return context.json({ error: "Owner only" }, 403);
181
+ const result = await registryFor().computerFleet(principal.actor.memberId, {
182
+ operation: "read",
183
+ computerId: context.req.param("computerId"),
184
+ });
185
+ return fleetResponse(result);
186
+ });
187
+ routes.put("/api/computers/selected", async (context) => {
188
+ const principal = context.get("principal");
189
+ if (!oysterPrincipalIsOwner(principal))
190
+ return context.json({ error: "Owner only" }, 403);
191
+ const input = z
192
+ .strictObject({ computerId: fleetComputerId })
193
+ .parse(await context.req.json());
194
+ return fleetResponse(await registryFor().computerFleet(principal.actor.memberId, {
195
+ operation: "select",
196
+ computerId: input.computerId,
197
+ }));
198
+ });
199
+ return routes;
200
+ }
@@ -1,17 +1,21 @@
1
1
  import { type AppMethodRefusedReply } from "@fungi.computer/agar/backend";
2
- import { type WorkflowGitPassResult } from "@fungi.computer/agar/computer-contracts";
3
2
  import { type ComputerPorts } from "@fungi.computer/agar/computer";
4
3
  import { type PreviewSelector } from "./preview.js";
5
4
  import { type StandaloneTenancyEnv } from "./tenancy.js";
6
5
  import * as z from "zod";
7
- import { computerProcessSchema, computerToolResultSchema, readOutputSchema } from "@fungi.computer/computer/operations";
8
- import { ComputerDO as NativeComputerDO, listOutputSchema, writeOutputSchema, mkdirOutputSchema, computerPortsOutputSchema, computerPortOutputSchema, computerPortRemovalOutputSchema, processListOutputSchema, processKillOutputSchema, computerOperationResultSchema, type AttachedBackend, type ComputerEnv, type ComputerValue } from "@fungi.computer/computer";
6
+ import { computerProcessSchema, computerToolResultSchema, readOutputSchema } from "@fungi.computer/computer-contract";
7
+ import { ComputerDO as NativeComputerDO, type AttachedBackend, type ComputerEnv } from "@fungi.computer/computer";
8
+ import { listOutputSchema, writeOutputSchema, mkdirOutputSchema, computerPortsOutputSchema, computerPortOutputSchema, computerPortRemovalOutputSchema, processListOutputSchema, processKillOutputSchema, computerOperationResultSchema, type ComputerExecutionHostHooks, type ComputerValue } from "@fungi.computer/computer-contract";
9
+ import { type WorkflowGitCredential, type WorkflowGitPassGrant } from "@fungi.computer/braid/runtime";
9
10
  import { type ComputerAction, type ComputerGrant } from "@fungi.computer/computer/fleet";
10
11
  import type { InvocationFrame } from "../invocation.js";
11
12
  import { type OysterCapability } from "../host.js";
12
13
  import type { OysterBindingRow, OysterInvocationHost, OysterWorkerEnv, OysterWorkspace } from "../workers.js";
13
14
  import type { OysterKeys, OysterKeysLivePort } from "../workers-git.js";
15
+ import { type OysterBuildBackend } from "../app-builds.js";
14
16
  import type { StandaloneRegistry } from "./registry.js";
17
+ import type { TenantScope } from "../tenant.js";
18
+ import { type WorkflowGitStepEnv } from "./workflow-binding.js";
15
19
  /** Native bindings selected by the standalone composition, never by an App. */
16
20
  export type StandaloneComputerEnv = ComputerEnv & Pick<OysterWorkerEnv, "RELEASES"> & Readonly<{
17
21
  OYSTER_COMPUTER_ID: string;
@@ -25,7 +29,7 @@ export type StandaloneComputerEnv = ComputerEnv & Pick<OysterWorkerEnv, "RELEASE
25
29
  OYSTER_OWNER_NAME?: string;
26
30
  }>;
27
31
  /** Finite native results cross the Workers RPC mapping without recursive JSON types. */
28
- export type StandaloneComputerResult = WorkflowGitPassResult | z.output<typeof listOutputSchema> | Readonly<{
32
+ export type StandaloneComputerResult = z.output<typeof listOutputSchema> | Readonly<{
29
33
  path: string;
30
34
  byteOffset: number;
31
35
  content: string;
@@ -46,12 +50,35 @@ export type ComputerPortPreviewsReply = Readonly<{
46
50
  * The Computer owner's bindings: the standalone Computer env plus the tenancy
47
51
  * registry that names the live canonical owner a port preview must belong to.
48
52
  */
49
- export type StandaloneComputerOwnerEnv = StandaloneComputerEnv & StandaloneTenancyEnv;
53
+ export type StandaloneComputerOwnerEnv = StandaloneComputerEnv & StandaloneTenancyEnv & Readonly<{
54
+ /** Each Workflows library's run owner issues its own Git credentials. */
55
+ WORKFLOW_RUNS: Readonly<{
56
+ getByName(libraryId: string): StandaloneWorkflowGitIssuer;
57
+ }>;
58
+ }>;
59
+ /** The library run owner's per-invocation Workflow Git credential ports. */
60
+ export type StandaloneWorkflowGitIssuer = Readonly<{
61
+ issueWorkflowGit(invocationId: string, grant: WorkflowGitPassGrant): Promise<WorkflowGitCredential>;
62
+ renewWorkflowGit(invocationId: string, grant: WorkflowGitPassGrant): Promise<void>;
63
+ closeWorkflowGit(invocationId: string, grant: WorkflowGitPassGrant): Promise<void>;
64
+ }>;
65
+ /** The Worker's terminal route reaches this native fetch through the stub. */
66
+ export type ComputerFetch = {
67
+ fetch(request: Request): Promise<Response>;
68
+ };
50
69
  /** Durable workspace and existing backings, with fleet checks at native acceptance. */
51
- export declare class ComputerDO extends NativeComputerDO {
70
+ export declare class ComputerDO extends NativeComputerDO implements ComputerFetch {
52
71
  #private;
53
72
  private readonly previewBindings;
54
73
  constructor(state: DurableObjectState, previewBindings: StandaloneComputerOwnerEnv);
74
+ /** Install the fleet's creation projection before exposing its new identity. */
75
+ initializeFleetBackings(backings: readonly AttachedBackend[]): void;
76
+ /** An admitted fleet control starts exactly this Computer's hosted backend. */
77
+ startHostedComputer(authority: ComputerGrant): Promise<{
78
+ state: "running";
79
+ }>;
80
+ /** A read-only preflight over the maintained hosted-route selector. */
81
+ buildBackend(): OysterBuildBackend;
55
82
  /**
56
83
  * Run one App build job's fixed command, under the build budget. The
57
84
  * registry that minted the job re-admits it for this Computer first.
@@ -69,11 +96,17 @@ export declare class ComputerDO extends NativeComputerDO {
69
96
  /** Issue preview tickets for every exposed port, or refuse typed. */
70
97
  portPreviews(authority: ComputerGrant, selectorInput: PreviewSelector): Promise<ComputerPortPreviewsReply>;
71
98
  portPreview(signature: string, port: number, request: Request): Promise<Response>;
99
+ /**
100
+ * The host side of native execution (Computer ABI 0edb0e4a): the grant's
101
+ * library run owner issues one credential for the invocation and the
102
+ * Computer hands it only to that command's Git environment.
103
+ */
104
+ protected executionHooks(): ComputerExecutionHostHooks;
72
105
  protected attachedBackends(): readonly AttachedBackend[];
73
106
  /** One advertised Computer tool, accepted against the host-issued grant. */
74
107
  tool(authority: ComputerGrant, capabilityId: string, input: ComputerValue): Promise<StandaloneComputerResult>;
75
108
  /** JSON operations keep the Computer's maintained parsers and execution engine. */
76
- value(authority: ComputerGrant, action: ComputerAction, input: ComputerValue): Promise<StandaloneComputerResult>;
109
+ value(authority: ComputerGrant, action: ComputerAction, input: ComputerValue, executionContext?: ComputerValue): Promise<StandaloneComputerResult>;
77
110
  fileList(authority: ComputerGrant, path: string, limit: number, offset: number): Promise<{
78
111
  path: string;
79
112
  entries: {
@@ -105,7 +138,7 @@ export declare class ComputerDO extends NativeComputerDO {
105
138
  * omit it, since a team-key principal then simply never reaches the
106
139
  * Computer, rather than reading `env.KEYS` directly and skipping the check.
107
140
  */
108
- export declare function standaloneWorkspace(env: StandaloneComputerEnv, keysLive?: OysterKeysLivePort): OysterWorkspace;
141
+ export declare function standaloneWorkspace(env: StandaloneComputerEnv, keysLive?: OysterKeysLivePort, selectedComputerId?: string): OysterWorkspace;
109
142
  /**
110
143
  * Owner/key capability routes over the selected Computer's native
111
144
  * namespace. `keysLive` is the same owner-checked port `standaloneWorkspace`
@@ -118,5 +151,7 @@ export type ComputerFrameMethods = Readonly<{
118
151
  }>;
119
152
  /** Closed COMPUTER binding codecs, joined into the standalone Instance. */
120
153
  export declare const computerFrameMethods: ComputerFrameMethods;
154
+ /** Media and Computer bindings capture the same inventory-admitted workspace. */
155
+ export declare function standaloneFrameWorkspace(env: StandaloneComputerEnv, scope: TenantScope, frame: InvocationFrame<OysterInvocationHost>): Promise<OysterWorkspace>;
121
156
  /** Dispatch one admitted COMPUTER operation under its captured frame. */
122
- export declare function computerInvocation(env: StandaloneComputerEnv, frame: InvocationFrame<OysterInvocationHost>, method: string, args: readonly unknown[]): Promise<StandaloneComputerResult>;
157
+ export declare function computerInvocation(env: StandaloneComputerEnv & WorkflowGitStepEnv, scope: TenantScope, frame: InvocationFrame<OysterInvocationHost>, method: string, args: readonly unknown[]): Promise<StandaloneComputerResult>;