@fungi.computer/oyster 0.2.0 → 0.3.1

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 (291) hide show
  1. package/README.md +396 -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 +213 -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/older-oyster-store-refusal.d.ts +4 -0
  130. package/dist/older-oyster-store-refusal.js +18 -0
  131. package/dist/older-oyster-store.d.ts +21 -0
  132. package/dist/older-oyster-store.js +43 -0
  133. package/dist/pack.d.ts +14 -3
  134. package/dist/pack.js +44 -19
  135. package/dist/page/app-access.d.ts +7 -0
  136. package/dist/page/app-access.js +75 -0
  137. package/dist/page/boot-host.js +57 -0
  138. package/dist/page/capabilities.d.ts +3 -3
  139. package/dist/page/capabilities.js +16 -12
  140. package/dist/page/computer-handles.d.ts +3 -3
  141. package/dist/page/computer-target.d.ts +3 -0
  142. package/dist/page/computer-target.js +9 -0
  143. package/dist/page/mount.d.ts +23 -3
  144. package/dist/page/mount.js +152 -16
  145. package/dist/page-assets.d.ts +22 -0
  146. package/dist/page-assets.js +28 -0
  147. package/dist/page-script.d.ts +3 -0
  148. package/dist/page-script.js +5 -4
  149. package/dist/release.d.ts +13 -6
  150. package/dist/release.js +10 -7
  151. package/dist/root-data.d.ts +16 -6
  152. package/dist/root-data.js +2 -2
  153. package/dist/source/index.d.ts +1 -1
  154. package/dist/source/team-source.d.ts +20 -8
  155. package/dist/source/team-source.js +39 -10
  156. package/dist/source/template-seed.d.ts +2 -2
  157. package/dist/standalone/agent-mail.d.ts +24 -0
  158. package/dist/standalone/agent-mail.js +62 -0
  159. package/dist/standalone/agent-store.d.ts +9 -0
  160. package/dist/standalone/agent-store.js +30 -0
  161. package/dist/standalone/agents.d.ts +68 -9
  162. package/dist/standalone/agents.js +428 -56
  163. package/dist/standalone/app-builds-binding.d.ts +20 -2
  164. package/dist/standalone/app-builds-binding.js +147 -23
  165. package/dist/standalone/app-set.d.ts +5 -2
  166. package/dist/standalone/app-set.js +186 -144
  167. package/dist/standalone/browser.d.ts +11 -0
  168. package/dist/standalone/browser.js +72 -0
  169. package/dist/standalone/builds.d.ts +13 -4
  170. package/dist/standalone/builds.js +28 -44
  171. package/dist/standalone/computer-egress.d.ts +2 -0
  172. package/dist/standalone/computer-egress.js +13 -4
  173. package/dist/standalone/computer-fleet.d.ts +43 -0
  174. package/dist/standalone/computer-fleet.js +200 -0
  175. package/dist/standalone/computer.d.ts +44 -9
  176. package/dist/standalone/computer.js +129 -22
  177. package/dist/standalone/install-apps-cli.js +43 -6
  178. package/dist/standalone/instance.d.ts +2 -2
  179. package/dist/standalone/instance.js +8 -11
  180. package/dist/standalone/machine-link.d.ts +354 -0
  181. package/dist/standalone/machine-link.js +368 -0
  182. package/dist/standalone/media.d.ts +2 -2
  183. package/dist/standalone/media.js +19 -16
  184. package/dist/standalone/preview.d.ts +2 -2
  185. package/dist/standalone/preview.js +37 -10
  186. package/dist/standalone/registry.d.ts +151 -9
  187. package/dist/standalone/registry.js +305 -8
  188. package/dist/standalone/worker.d.ts +19 -5
  189. package/dist/standalone/worker.js +90 -54
  190. package/dist/standalone/workflow-binding.d.ts +18 -22
  191. package/dist/standalone/workflow-binding.js +18 -18
  192. package/dist/standalone/workflow-control.d.ts +4 -0
  193. package/dist/standalone/workflow-control.js +40 -0
  194. package/dist/standalone/workflow-events.d.ts +2 -2
  195. package/dist/standalone/workflow-events.js +9 -10
  196. package/dist/standalone/workflow-git-capture.js +4 -3
  197. package/dist/standalone/workflow-host.d.ts +162 -161
  198. package/dist/standalone/workflow-host.js +101 -21
  199. package/dist/standalone/workflow-node-capture.js +11 -4
  200. package/dist/standalone/workflow-schedules.d.ts +5 -2
  201. package/dist/standalone/workflow-schedules.js +9 -10
  202. package/dist/standalone/workflow-trigger-host.d.ts +65 -5
  203. package/dist/standalone/workflow-trigger-host.js +2 -2
  204. package/dist/standalone/workflow-webhooks.d.ts +22 -0
  205. package/dist/standalone/workflow-webhooks.js +39 -0
  206. package/dist/standalone/workflows.d.ts +140 -43
  207. package/dist/standalone/workflows.js +198 -39
  208. package/dist/storage-frame.d.ts +6 -6
  209. package/dist/testing/capabilities.js +14 -3
  210. package/dist/testing/installation.js +26 -2
  211. package/dist/testing/worker.js +1 -1
  212. package/dist/testing/workers-runtime.d.ts +8 -2
  213. package/dist/testing/workers-runtime.js +20 -2
  214. package/dist/webhook-abuse.d.ts +25 -0
  215. package/dist/webhook-abuse.js +54 -0
  216. package/dist/webhook-custody.d.ts +144 -0
  217. package/dist/webhook-custody.js +222 -0
  218. package/dist/webhook-ingress.d.ts +36 -0
  219. package/dist/webhook-ingress.js +146 -0
  220. package/dist/webhooks.d.ts +111 -0
  221. package/dist/webhooks.js +189 -0
  222. package/dist/workers-git.d.ts +12 -17
  223. package/dist/workers-git.js +64 -52
  224. package/dist/workers.d.ts +226 -13
  225. package/dist/workers.js +363 -51
  226. package/dist/workflow-agent-authority.d.ts +8 -0
  227. package/dist/workflow-agent-authority.js +42 -0
  228. package/dist/workflows.d.ts +66 -0
  229. package/dist/workflows.js +97 -0
  230. package/docs/api/README.md +63 -37
  231. package/docs/api/agents.md +17 -0
  232. package/docs/api/app-storage.md +1 -1
  233. package/docs/api/apps.md +1394 -543
  234. package/docs/api/browser/boot.md +132 -0
  235. package/docs/api/browser.md +1440 -0
  236. package/docs/api/build-contract.md +38 -25
  237. package/docs/api/build.md +263 -84
  238. package/docs/api/cloudflare.md +93 -7
  239. package/docs/api/git.md +3 -13
  240. package/docs/api/host.md +944 -118
  241. package/docs/api/index.md +320 -18
  242. package/docs/api/keys.md +24 -1
  243. package/docs/api/mcp.md +59 -12
  244. package/docs/api/modules.md +33 -40
  245. package/docs/api/pack.md +57 -8
  246. package/docs/api/page/mount.md +92 -4
  247. package/docs/api/workers-git.md +103 -24
  248. package/docs/api/workers.md +1594 -244
  249. package/docs/site/agent-quickstart.mdx +28 -14
  250. package/examples/standalone/worker.ts +3 -1
  251. package/examples/standalone/wrangler.jsonc +2 -0
  252. package/package.json +73 -71
  253. package/dist/installation-consent.d.ts +0 -81
  254. package/dist/installation-consent.js +0 -84
  255. package/dist/shell/app-directory.d.ts +0 -542
  256. package/dist/shell/app-directory.js +0 -196
  257. package/dist/shell/browser-actions.d.ts +0 -7
  258. package/dist/shell/browser-actions.js +0 -56
  259. package/dist/shell/command-host.d.ts +0 -112
  260. package/dist/shell/command-host.js +0 -502
  261. package/dist/shell/commands.d.ts +0 -25
  262. package/dist/shell/commands.js +0 -78
  263. package/dist/shell/index.d.ts +0 -8
  264. package/dist/shell/index.js +0 -8
  265. package/dist/shell/keys.d.ts +0 -41
  266. package/dist/shell/keys.js +0 -134
  267. package/dist/shell/shell-module.d.ts +0 -55
  268. package/dist/shell/shell-module.js +0 -86
  269. package/dist/shell/window-manager.d.ts +0 -128
  270. package/dist/shell/window-manager.js +0 -730
  271. package/dist/shell/workbench-contract.d.ts +0 -68
  272. package/dist/shell/workbench-directory.d.ts +0 -332
  273. package/dist/shell/workbench-directory.js +0 -35
  274. package/dist/shell/workbench-host-types.d.ts +0 -96
  275. package/dist/shell/workbench-host-types.js +0 -1
  276. package/dist/shell/workbench-host.d.ts +0 -34
  277. package/dist/shell/workbench-host.js +0 -352
  278. package/dist/shell/workbench-model.d.ts +0 -194
  279. package/dist/shell/workbench-model.js +0 -53
  280. package/dist/shell/workbench.d.ts +0 -4
  281. package/dist/shell/workbench.js +0 -2
  282. package/docs/api/shell/app-directory.md +0 -599
  283. package/docs/api/shell/command-host.md +0 -224
  284. package/docs/api/shell/commands.md +0 -197
  285. package/docs/api/shell/keys.md +0 -183
  286. package/docs/api/shell/shell-module.md +0 -204
  287. package/docs/api/shell/window-manager.md +0 -267
  288. package/docs/api/shell/workbench-directory.md +0 -119
  289. package/docs/api/shell/workbench.md +0 -205
  290. package/docs/api/shell.md +0 -1915
  291. /package/dist/{shell/workbench-contract.js → page/boot-host.d.ts} +0 -0
@@ -1,15 +1,19 @@
1
+ import type { Release } from "@fungi.computer/puffball";
1
2
  import { OysterRegistry, type OysterWorkerEnv } from "../workers.js";
2
- import type { TenantId } from "../tenant.js";
3
+ import { type OysterHost } from "../host.js";
4
+ import { type TenantScope, type TenantId } from "../tenant.js";
3
5
  import { type OysterGitEnv } from "../workers-git.js";
4
6
  import { type AppSourceForkResult, type TemplateSeedClaim } from "../source/index.js";
5
7
  import { type CloudflareArtifactsOptions } from "@fungi.computer/burrow/cloudflare";
6
- import { type OysterBuildSource, type OysterBuildView } from "../app-builds.js";
8
+ import { type OysterBuildStartResult, type OysterBuildView } from "../app-builds.js";
7
9
  import type { TeamAppCandidateManifest } from "../build-contract.js";
8
10
  import type { BuildGitEvent } from "../build-egress.js";
9
11
  import { type StandaloneTenancyEnv } from "./tenancy.js";
10
12
  import type { ComputerDO } from "./computer.js";
13
+ import type { ComputerEnv } from "@fungi.computer/computer";
14
+ import { type StandaloneFleetRequest } from "./computer-fleet.js";
11
15
  /** What the registry needs to build Apps on the standalone's one Computer. */
12
- export type StandaloneRegistryEnv = OysterWorkerEnv & StandaloneTenancyEnv & OysterGitEnv & Readonly<{
16
+ export type StandaloneRegistryEnv = OysterWorkerEnv & Pick<ComputerEnv, "COMPUTER_VM_DEFAULT_HOST"> & StandaloneTenancyEnv & OysterGitEnv & Readonly<{
13
17
  OYSTER_COMPUTER_ID: string;
14
18
  COMPUTER: DurableObjectNamespace<ComputerDO>;
15
19
  }>;
@@ -23,9 +27,77 @@ export type BuildTarget = Readonly<{
23
27
  * composition may supply; absent, Git goes to the provider's own remotes. */
24
28
  export type StandaloneRegistryGit = Pick<CloudflareArtifactsOptions, "gitFetch" | "gitHttp">;
25
29
  /** The existing REGISTRY class identity with standalone host-owned admission. */
26
- export declare class StandaloneRegistry extends OysterRegistry {
30
+ /**
31
+ * Operations other Worker contexts call through the REGISTRY Durable Object
32
+ * namespace: the APPS binding (app-builds-binding.ts) and the fleet routes
33
+ * (computer-fleet.ts). Each is an RPC entrypoint, not an in-process helper.
34
+ */
35
+ export type StandaloneRegistryRpc = {
36
+ computerFleet(memberId: string, request: StandaloneFleetRequest): ReturnType<StandaloneRegistry["computerFleet"]>;
37
+ registerRepositoryApp(input: Parameters<StandaloneRegistry["registerRepositoryApp"]>[0]): ReturnType<StandaloneRegistry["registerRepositoryApp"]>;
38
+ repositoryBuildSource(installationId: string, repository: string): ReturnType<StandaloneRegistry["repositoryBuildSource"]>;
39
+ readRepositoryBuild(installationId: string, repository: string): ReturnType<StandaloneRegistry["readRepositoryBuild"]>;
40
+ installWorkflowBuild(jobId: string, target: string, release: Release, memberId: string, callerInstallationId: string): ReturnType<StandaloneRegistry["installWorkflowBuild"]>;
41
+ };
42
+ export declare abstract class StandaloneRegistry extends OysterRegistry implements StandaloneRegistryRpc {
27
43
  #private;
44
+ protected abstract hostFor(scope: TenantScope): Promise<OysterHost>;
28
45
  constructor(state: DurableObjectState, env: StandaloneRegistryEnv, git?: StandaloneRegistryGit);
46
+ /** Whether installations or build jobs were written by Oyster 0.2 or earlier. */
47
+ storeFromOlderOyster(): boolean;
48
+ /** Re-admit the live owner before inventory access or a hosted start. */
49
+ computerFleet(memberId: string, request: StandaloneFleetRequest): Promise<Readonly<{
50
+ computerId: string;
51
+ name: string;
52
+ createdAt: number;
53
+ updatedAt: number;
54
+ }> | {
55
+ readonly computers: Readonly<{
56
+ computerId: string;
57
+ name: string;
58
+ createdAt: number;
59
+ updatedAt: number;
60
+ }>[];
61
+ readonly nextOffset: number | null;
62
+ } | ({
63
+ state: "running";
64
+ } & Disposable) | {
65
+ error: string;
66
+ computers?: undefined;
67
+ nextOffset?: undefined;
68
+ computerId?: undefined;
69
+ enabled?: undefined;
70
+ } | {
71
+ computers: ({
72
+ computerId: string;
73
+ name: string;
74
+ } | Readonly<{
75
+ computerId: string;
76
+ name: string;
77
+ createdAt: number;
78
+ updatedAt: number;
79
+ }>)[];
80
+ nextOffset: number | null;
81
+ error?: undefined;
82
+ computerId?: undefined;
83
+ enabled?: undefined;
84
+ } | {
85
+ computerId: string;
86
+ error?: undefined;
87
+ computers?: undefined;
88
+ nextOffset?: undefined;
89
+ enabled?: undefined;
90
+ } | {
91
+ enabled: boolean;
92
+ error?: undefined;
93
+ computers?: undefined;
94
+ nextOffset?: undefined;
95
+ computerId?: undefined;
96
+ } | null>;
97
+ /** A durable selection snapshot; the configured default remains the App build Computer. */
98
+ selectedComputer(): Promise<string>;
99
+ /** Frame dispatch checks inventory ownership, not a mutable UI selection. */
100
+ ownsComputer(computerId: string): boolean;
29
101
  /** One atomic registry admission proves the stored creator is still the owner. */
30
102
  protected currentOwner(memberId: string, tenantId: TenantId): Promise<string | null>;
31
103
  /** Operator-only: one smart-HTTP exchange with an App's template
@@ -39,7 +111,7 @@ export declare class StandaloneRegistry extends OysterRegistry {
39
111
  * published seed revision builds on the Computer. */
40
112
  publishSource(rawAppId: string, claim: TemplateSeedClaim, teamId: string): Promise<{
41
113
  readonly kind: "conflict";
42
- readonly receipt?: undefined;
114
+ receipt?: undefined;
43
115
  } | {
44
116
  readonly kind: "recorded" | "existing";
45
117
  readonly receipt: {
@@ -52,7 +124,7 @@ export declare class StandaloneRegistry extends OysterRegistry {
52
124
  };
53
125
  } | {
54
126
  readonly kind: "unavailable";
55
- readonly receipt?: undefined;
127
+ receipt?: undefined;
56
128
  }>;
57
129
  /** Fork a published platform App's source into its own Team-owned App,
58
130
  * as repository `name` of the calling installation's Git (so its owner
@@ -68,15 +140,85 @@ export declare class StandaloneRegistry extends OysterRegistry {
68
140
  repositoryPrefix: string;
69
141
  name: string;
70
142
  }>): Promise<AppSourceForkResult>;
71
- /** Owner: record an App's build source (one repository, pinned recipe). */
72
- registerBuildSource(source: OysterBuildSource): Promise<void>;
143
+ /** Register the committed root App, never the installation's mutable Files projection. */
144
+ registerRepositoryApp(input: Readonly<{
145
+ memberId: string;
146
+ installationId: string;
147
+ repository: string;
148
+ }>): Promise<Readonly<{
149
+ kind: "registered";
150
+ appId: string;
151
+ sourceRevision: string;
152
+ }> | Readonly<{
153
+ kind: "unauthorized" | "invalid_app" | "conflict";
154
+ }>>;
73
155
  /** Owner: start one build of an App's source at one commit; it runs on. */
74
156
  startBuild(input: {
75
157
  appId: string;
76
158
  sourceRevision: string;
77
159
  requestedBy: string;
78
- }): Promise<OysterBuildView>;
160
+ workflow?: {
161
+ requestId: string;
162
+ /** The installation whose admitted Workflow node asks; it must own the source. */
163
+ callerInstallationId: string;
164
+ installationId?: string;
165
+ };
166
+ }): Promise<OysterBuildStartResult>;
167
+ /** The calling installation's existing source record, never a registration. */
168
+ repositoryBuildSource(installationId: string, repository: string): Promise<{
169
+ appId: import("@fungi.computer/puffball").AppId;
170
+ } | null>;
79
171
  readBuild(jobId: string): Promise<OysterBuildView | null>;
172
+ /** Repository status belongs to the same captured Git owner as registration. */
173
+ readRepositoryBuild(installationId: string, repository: string): Promise<Readonly<{
174
+ jobId: string;
175
+ appId: string;
176
+ releaseId: string;
177
+ sourceRevision: string;
178
+ status: "running" | "succeeded" | "failed" | "revoked";
179
+ detail: string | null;
180
+ release: Release | null;
181
+ installation: import("../app-builds.js").OysterBuildInstallation | null;
182
+ }> | null>;
183
+ /** Only the APPS dispatcher of an admitted Workflow node calls this operation.
184
+ * Reads and alarms never choose a Release. The build row owns the decision. */
185
+ installWorkflowBuild(jobId: string, target: string, release: Release, memberId: string, callerInstallationId: string): Promise<{
186
+ status: number;
187
+ body: {
188
+ error: string;
189
+ installationId?: undefined;
190
+ status?: undefined;
191
+ code?: undefined;
192
+ pending?: undefined;
193
+ };
194
+ } | {
195
+ status: number;
196
+ body: {
197
+ installationId: string;
198
+ status: string;
199
+ error?: undefined;
200
+ code?: undefined;
201
+ pending?: undefined;
202
+ };
203
+ } | {
204
+ status: number;
205
+ body: {
206
+ code: string;
207
+ error: string;
208
+ installationId?: undefined;
209
+ status?: undefined;
210
+ pending?: undefined;
211
+ };
212
+ } | {
213
+ status: number;
214
+ body: {
215
+ pending: boolean;
216
+ error?: undefined;
217
+ installationId?: undefined;
218
+ status?: undefined;
219
+ code?: undefined;
220
+ };
221
+ }>;
80
222
  /** The Computer re-admits each build execution here. */
81
223
  admitBuildExecution(jobId: string, computerObjectId: string): Promise<boolean>;
82
224
  /** The egress: which live job a build report from this Computer speaks for. */
@@ -1,4 +1,8 @@
1
+ import { canonicalJson } from "@fungi.computer/braid";
1
2
  import { OysterRegistry, r2ArtifactStore, } from "../workers.js";
3
+ import { installOrSelect } from "../host.js";
4
+ import { installationOperationsPort } from "../installation-operations.js";
5
+ import { tenantScope } from "../tenant.js";
2
6
  import { artifactsOf } from "../workers-git.js";
3
7
  import { forkAppSource, recordTemplateSeed, templateRepositoryForApp, } from "../source/index.js";
4
8
  import { exchangeOysterTemplateGit, exchangeOysterTemplateSource, } from "../app-sources.js";
@@ -7,22 +11,149 @@ import { defineIdFamily } from "@fungi.computer/cairn/id";
7
11
  import * as Schema from "effect/Schema";
8
12
  import { parseAppId } from "@fungi.computer/puffball";
9
13
  import { appIdSchema } from "@fungi.computer/puffball/effect";
10
- import { createOysterAppBuilds, } from "../app-builds.js";
14
+ import { createOysterAppBuilds, OysterBuildStartError, } from "../app-builds.js";
15
+ import { buildJobsFromOlderOyster } from "../older-oyster-store.js";
16
+ import { readBuildManifest, appBuildRecipe } from "../build/workspace-build.js";
11
17
  import { standaloneTenancy } from "./tenancy.js";
18
+ import { createFleetComputers, ComputerInventoryError, } from "@fungi.computer/computer/fleet";
19
+ import { standaloneFleetRequestSchema, } from "./computer-fleet.js";
12
20
  const standaloneAppForkIds = defineIdFamily({
13
21
  family: "oyster-app-fork",
14
22
  version: 1,
15
23
  kinds: { app: { prefix: "app" } },
16
24
  });
17
- /** The existing REGISTRY class identity with standalone host-owned admission. */
25
+ /** Whether a registered source lives in this installation's own repository. */
26
+ function sourceOwnedBy(source, installationId) {
27
+ return (source?.location.kind === "installation" &&
28
+ source.location.installationId === installationId);
29
+ }
18
30
  export class StandaloneRegistry extends OysterRegistry {
19
31
  #env;
20
32
  #git;
21
33
  #builds;
34
+ #computers;
35
+ #fleetStorage;
22
36
  constructor(state, env, git = {}) {
23
37
  super(state, env);
24
38
  this.#env = env;
25
39
  this.#git = git;
40
+ this.#fleetStorage = state.storage;
41
+ // Native hosts own infrastructure capacity. Standalone has no Team quota.
42
+ this.#computers = createFleetComputers(state.storage, {
43
+ limit: Number.MAX_SAFE_INTEGER,
44
+ retryAfterSeconds: 30,
45
+ });
46
+ this.#computers.ensure();
47
+ }
48
+ /** Whether installations or build jobs were written by Oyster 0.2 or earlier. */
49
+ storeFromOlderOyster() {
50
+ return (super.storeFromOlderOyster() ||
51
+ buildJobsFromOlderOyster(this.ctx.storage.sql));
52
+ }
53
+ /** Re-admit the live owner before inventory access or a hosted start. */
54
+ async computerFleet(memberId, request) {
55
+ const shared = await standaloneTenancy(this.#env);
56
+ if ((await shared.owner())?.actor.memberId !== memberId)
57
+ return { error: "owner_required" };
58
+ const input = standaloneFleetRequestSchema.parse(request);
59
+ switch (input.operation) {
60
+ case "create": {
61
+ const hostId = this.#env.COMPUTER_VM_DEFAULT_HOST;
62
+ let created;
63
+ try {
64
+ created = this.#computers.create({ kind: "owner", id: memberId }, input.request, [
65
+ hostId === undefined
66
+ ? { kind: "platform-container" }
67
+ : { kind: "on-box-vm", hostId },
68
+ ]).computer;
69
+ }
70
+ catch (cause) {
71
+ if (cause instanceof ComputerInventoryError)
72
+ return { error: cause.code };
73
+ throw cause;
74
+ }
75
+ const backings = this.#computers.readBacking(created.computerId);
76
+ if (backings === null)
77
+ throw new Error("Computer backing is missing");
78
+ await this.#env.COMPUTER.getByName(created.computerId).initializeFleetBackings(backings);
79
+ return created;
80
+ }
81
+ case "list":
82
+ return this.#computers.list(input.page);
83
+ case "choices": {
84
+ // The configured build Computer is an existing supported workspace,
85
+ // not a fabricated fleet creation receipt. Keep its UI row outside inventory.
86
+ const { limit, offset } = input.page;
87
+ const first = offset === 0;
88
+ const configured = {
89
+ computerId: this.#env.OYSTER_COMPUTER_ID,
90
+ name: this.#env.OYSTER_COMPUTER_ID,
91
+ };
92
+ if (first && limit === 1)
93
+ return {
94
+ computers: [configured],
95
+ nextOffset: this.#computers.count() > 0 ? 1 : null,
96
+ };
97
+ const page = this.#computers.list({
98
+ limit: limit - (first ? 1 : 0),
99
+ offset: Math.max(0, offset - 1),
100
+ });
101
+ return {
102
+ computers: first ? [configured, ...page.computers] : page.computers,
103
+ nextOffset: page.nextOffset === null ? null : page.nextOffset + 1,
104
+ };
105
+ }
106
+ case "read":
107
+ return this.#computers.read(input.computerId);
108
+ case "resolve":
109
+ // Context may name the configured build Computer, which has no fleet
110
+ // creation receipt. Keep canonical-owner admission above this lookup.
111
+ return this.ownsComputer(input.computerId)
112
+ ? { computerId: input.computerId }
113
+ : null;
114
+ case "read-internet-access":
115
+ case "set-internet-access": {
116
+ if (this.#computers.read(input.computerId) === null)
117
+ return null;
118
+ const computer = this.#env.COMPUTER.getByName(input.computerId);
119
+ return {
120
+ enabled: await computer.internetAccess(input.operation === "set-internet-access"
121
+ ? input.enabled
122
+ : undefined),
123
+ };
124
+ }
125
+ case "select": {
126
+ const computer = this.#computers.read(input.computerId);
127
+ if (computer === null)
128
+ return null;
129
+ await this.#fleetStorage.put("selected-computer", input.computerId);
130
+ return computer;
131
+ }
132
+ case "start":
133
+ if (this.#computers.read(input.computerId) === null)
134
+ return null;
135
+ return this.#env.COMPUTER.getByName(input.computerId).startHostedComputer({
136
+ computerId: input.computerId,
137
+ actions: new Set(["start"]),
138
+ expiresAt: Date.now() + 60_000,
139
+ provenance: { actor: memberId },
140
+ });
141
+ }
142
+ }
143
+ /** A durable selection snapshot; the configured default remains the App build Computer. */
144
+ async selectedComputer() {
145
+ const selected = await this.#fleetStorage.get("selected-computer");
146
+ if (selected === undefined)
147
+ return this.#env.OYSTER_COMPUTER_ID;
148
+ const computerId = Schema.decodeUnknownSync(Schema.NonEmptyString)(selected);
149
+ if (this.#computers.read(computerId) === null)
150
+ throw new Error("Selected Computer is missing");
151
+ return computerId;
152
+ }
153
+ /** Frame dispatch checks inventory ownership, not a mutable UI selection. */
154
+ ownsComputer(computerId) {
155
+ return (computerId === this.#env.OYSTER_COMPUTER_ID ||
156
+ this.#computers.read(computerId) !== null);
26
157
  }
27
158
  /** One atomic registry admission proves the stored creator is still the owner. */
28
159
  async currentOwner(memberId, tenantId) {
@@ -134,6 +265,7 @@ export class StandaloneRegistry extends OysterRegistry {
134
265
  name,
135
266
  objectId: env.COMPUTER.idFromName(name).toString(),
136
267
  port: {
268
+ buildBackend: () => env.COMPUTER.getByName(name).buildBackend(),
137
269
  runBuild: (input) => env.COMPUTER.getByName(name).runBuild(input),
138
270
  },
139
271
  },
@@ -143,19 +275,184 @@ export class StandaloneRegistry extends OysterRegistry {
143
275
  });
144
276
  return this.#builds;
145
277
  }
146
- /** Owner: record an App's build source (one repository, pinned recipe). */
147
- async registerBuildSource(source) {
148
- (await this.#appBuilds()).registerSource(source);
278
+ /** Register the committed root App, never the installation's mutable Files projection. */
279
+ async registerRepositoryApp(input) {
280
+ const shared = await standaloneTenancy(this.#env);
281
+ if ((await shared.owner())?.actor.memberId !== input.memberId)
282
+ return { kind: "unauthorized" };
283
+ const scope = tenantScope(shared.tenantId, this.#env.OYSTER_ORIGIN);
284
+ const repository = await artifactsOf(this.#env).get(scope.repositoryPrefix(input.installationId) + input.repository);
285
+ const [head] = await repository.log({ ref: "main", limit: 1, offset: 0 });
286
+ if (head === undefined)
287
+ return { kind: "invalid_app" };
288
+ const tree = await repository.readTree(head.treeHash);
289
+ const entry = tree?.find((item) => item.name === "package.json" && item.type === "blob");
290
+ if (entry === undefined)
291
+ return { kind: "invalid_app" };
292
+ const blob = await repository.readBlob(entry.hash);
293
+ // Package metadata is a bounded input; this is not an App build-output budget.
294
+ if (blob === null || blob.size > 1_048_576)
295
+ return { kind: "invalid_app" };
296
+ const packageJson = await blob.text();
297
+ let app;
298
+ try {
299
+ const manifest = readBuildManifest(packageJson);
300
+ if (manifest.fungi.appId === undefined ||
301
+ manifest.fungi.build === undefined)
302
+ return { kind: "invalid_app" };
303
+ app = {
304
+ appId: manifest.fungi.appId,
305
+ recipe: appBuildRecipe(manifest, ""),
306
+ };
307
+ }
308
+ catch (cause) {
309
+ if (Schema.isSchemaError(cause))
310
+ return { kind: "invalid_app" };
311
+ throw cause;
312
+ }
313
+ const registered = (await this.#appBuilds()).registerRepositorySource({
314
+ appId: app.appId,
315
+ location: {
316
+ kind: "installation",
317
+ installationId: input.installationId,
318
+ repository: input.repository,
319
+ },
320
+ recipe: app.recipe,
321
+ });
322
+ if (registered.kind === "conflict")
323
+ return registered;
324
+ return {
325
+ kind: "registered",
326
+ appId: registered.source.appId,
327
+ sourceRevision: "git:" + head.hash,
328
+ };
149
329
  }
150
330
  /** Owner: start one build of an App's source at one commit; it runs on. */
151
331
  async startBuild(input) {
152
- const { build, run } = await (await this.#appBuilds()).start(input);
153
- this.ctx.waitUntil(run());
154
- return build;
332
+ try {
333
+ const builds = await this.#appBuilds();
334
+ const request = {
335
+ appId: input.appId,
336
+ sourceRevision: input.sourceRevision,
337
+ requestedBy: input.requestedBy,
338
+ };
339
+ if (input.workflow !== undefined) {
340
+ // A Workflow builds only its own installation's registered App. A
341
+ // foreign App reads as unregistered, never as another's existence.
342
+ if (!sourceOwnedBy(builds.readSource(input.appId), input.workflow.callerInstallationId))
343
+ return { kind: "refused", reason: "no_source" };
344
+ request.requestId = input.workflow.requestId;
345
+ if (input.workflow.installationId !== undefined)
346
+ request.installAt = input.workflow.installationId;
347
+ }
348
+ const { build, run } = await builds.start(request);
349
+ this.ctx.waitUntil(run());
350
+ return { kind: "started", build };
351
+ }
352
+ catch (cause) {
353
+ if (cause instanceof OysterBuildStartError)
354
+ return { kind: "refused", reason: cause.reason };
355
+ throw cause;
356
+ }
357
+ }
358
+ /** The calling installation's existing source record, never a registration. */
359
+ async repositoryBuildSource(installationId, repository) {
360
+ const source = (await this.#appBuilds()).readRepositorySource(installationId, repository);
361
+ return source === null ? null : { appId: source.appId };
155
362
  }
156
363
  async readBuild(jobId) {
157
364
  return (await this.#appBuilds()).read(jobId);
158
365
  }
366
+ /** Repository status belongs to the same captured Git owner as registration. */
367
+ async readRepositoryBuild(installationId, repository) {
368
+ const latest = (await this.#appBuilds()).latestRepositoryBuild(installationId, repository);
369
+ return latest === null ? null : this.readBuild(latest.jobId);
370
+ }
371
+ /** Only the APPS dispatcher of an admitted Workflow node calls this operation.
372
+ * Reads and alarms never choose a Release. The build row owns the decision. */
373
+ async installWorkflowBuild(jobId, target, release, memberId, callerInstallationId) {
374
+ const builds = await this.#appBuilds();
375
+ const build = builds.read(jobId);
376
+ if (build === null ||
377
+ !sourceOwnedBy(builds.readSource(build.appId), callerInstallationId) ||
378
+ build.installation?.installationId !== target ||
379
+ build.release === null ||
380
+ canonicalJson(Schema.decodeUnknownSync(Schema.Json)(build.release)) !==
381
+ canonicalJson(Schema.decodeUnknownSync(Schema.Json)(release)))
382
+ return {
383
+ status: 409,
384
+ body: { error: "Release does not match this build and installation" },
385
+ };
386
+ const shared = await standaloneTenancy(this.#env);
387
+ if ((await shared.owner())?.actor.memberId !== memberId)
388
+ return { status: 403, body: { error: "Workflow authority revoked" } };
389
+ await builds.install(jobId, async (release, installationId) => {
390
+ const owner = await shared.owner();
391
+ if (owner?.actor.memberId !== memberId)
392
+ return {
393
+ kind: "failed",
394
+ installationId,
395
+ detail: "App owner unavailable",
396
+ };
397
+ const scope = tenantScope(shared.tenantId, this.#env.OYSTER_ORIGIN);
398
+ const root = await this.hostFor(scope);
399
+ // Use the in-process owner port; do not RPC this registry back into itself.
400
+ const host = {
401
+ ...root,
402
+ installations: {
403
+ ...root.installations,
404
+ get: (id) => Promise.resolve(this.get(id)),
405
+ generation: () => Promise.resolve(this.generation()),
406
+ // Only the owner's journal commits; nothing here writes around it.
407
+ compareAndPut: () => Promise.reject(new Error("Workflow installation commits only through its owner")),
408
+ operations: installationOperationsPort((request) => this.installationOperation(request), { tenantId: scope.tenantId, origin: scope.origin }),
409
+ },
410
+ };
411
+ const principal = await host.auth.resolve(owner.actor);
412
+ if (principal === null)
413
+ return {
414
+ kind: "failed",
415
+ installationId,
416
+ detail: "App owner unavailable",
417
+ };
418
+ const response = await installOrSelect(host, installationId, release, principal, "install-or-select", "workflow",
419
+ // One retained owner command per build: a retry observes it.
420
+ "workflow-build:" + jobId);
421
+ if (response.ok)
422
+ return { kind: "installed", installationId };
423
+ const body = await response.json();
424
+ if (Schema.is(Schema.Struct({ error: Schema.Literal("needs_consent") }))(body))
425
+ return { kind: "needs_consent", installationId };
426
+ return {
427
+ kind: "failed",
428
+ installationId,
429
+ detail: "Automatic installation refused (HTTP " +
430
+ String(response.status) +
431
+ ")",
432
+ };
433
+ });
434
+ const outcome = builds.read(jobId)?.installation;
435
+ switch (outcome?.kind) {
436
+ case "installed":
437
+ return {
438
+ status: 200,
439
+ body: { installationId: target, status: "enabled" },
440
+ };
441
+ case "needs_consent":
442
+ return {
443
+ status: 409,
444
+ body: {
445
+ code: "needs_consent",
446
+ error: "New Release needs owner consent",
447
+ },
448
+ };
449
+ case "failed":
450
+ return { status: 503, body: { error: outcome.detail } };
451
+ default:
452
+ // Another installer holds this owner: the Workflow waits and retries.
453
+ return { status: 202, body: { pending: true } };
454
+ }
455
+ }
159
456
  /** The Computer re-admits each build execution here. */
160
457
  async admitBuildExecution(jobId, computerObjectId) {
161
458
  return (await this.#appBuilds()).admitExecution(jobId, computerObjectId);
@@ -14,19 +14,27 @@ export { OysterBinding } from "./instance.js";
14
14
  import { StandaloneWorkflowRunsBase, type StandaloneWorkflowEnv } from "./workflows.js";
15
15
  import { type StandaloneTenancyEnv } from "./tenancy.js";
16
16
  export { StandaloneTenants } from "./tenancy.js";
17
+ import { type StandaloneMachineLinkBindings } from "./machine-link.js";
18
+ export { MachineLinkDO } from "./machine-link.js";
17
19
  import { type OysterComputerEgressProps } from "./computer-egress.js";
18
20
  import { type AppSourceBindingEnv } from "./app-source.js";
19
21
  export { StandaloneGraphWorkflow } from "./workflows.js";
20
22
  export { MediaOperationDO } from "@fungi.computer/darkroom/cloudflare";
21
23
  /** Every binding the standalone Bonsai root receives. */
22
- export type StandaloneEnv = OysterGitWorkerEnv & StandaloneTenancyEnv & StandaloneComputerEnv & StandaloneAgentsEnv & OysterMediaEnv & StandaloneWorkflowEnv & AppSourceBindingEnv & {
24
+ export type StandaloneEnv = OysterGitWorkerEnv & StandaloneTenancyEnv & StandaloneComputerEnv & StandaloneAgentsEnv & StandaloneMachineLinkBindings & OysterMediaEnv & StandaloneWorkflowEnv & AppSourceBindingEnv & {
23
25
  OYSTER_HOME_INSTALLATION: string;
24
26
  /** `disabled` closes the Computer's public download lane. */
25
27
  COMPUTER_PUBLIC_EGRESS?: string;
26
28
  };
27
29
  export { ComputerDO } from "./computer.js";
28
30
  export { WorkspaceProxy, WorkspaceServiceProxy, } from "@fungi.computer/computer";
29
- export { StandaloneRegistry as OysterRegistry } from "./registry.js";
31
+ import { StandaloneRegistry as BaseRegistry, type StandaloneRegistryGit } from "./registry.js";
32
+ /** The registry uses the same root, with its own in-process Computer selection. */
33
+ export declare class OysterRegistry extends BaseRegistry {
34
+ #private;
35
+ constructor(state: DurableObjectState, env: StandaloneEnv, git?: StandaloneRegistryGit);
36
+ protected hostFor(scope: TenantScope): Promise<import("../workers-git.js").OysterGitWorkerHost>;
37
+ }
30
38
  export { OysterKeys } from "../workers-git.js";
31
39
  export { StandaloneEventDelivery as OysterWebhooks } from "./instance.js";
32
40
  export { AgentDirectory } from "./agents.js";
@@ -35,15 +43,21 @@ export declare class OysterMcp extends OysterMcp_base {
35
43
  }
36
44
  /** Background wakes readmit members through this root's tenancy, as HTTP and MCP do. */
37
45
  export declare class StandaloneInstance extends ComputerInstance<StandaloneEnv> {
38
- protected hostFor(scope: TenantScope): import("../workers-git.js").OysterGitWorkerHost;
46
+ protected hostFor(scope: TenantScope): Promise<import("../workers-git.js").OysterGitWorkerHost>;
39
47
  }
40
48
  /** Agent App tools derive the same catalog as HTTP and MCP for the Agent's pinned tenant. */
41
49
  export declare class StandaloneAgent extends StandaloneAgentBase<StandaloneEnv> {
42
- protected hostFor(scope: TenantScope): import("../workers-git.js").OysterGitWorkerHost;
50
+ protected hostFor(scope: TenantScope): Promise<import("../workers-git.js").OysterGitWorkerHost>;
43
51
  }
44
52
  /** Workflow runs admit through the same root composition as HTTP. */
45
53
  export declare class StandaloneWorkflowRuns extends StandaloneWorkflowRunsBase<StandaloneEnv> {
46
- protected hostFor(scope: TenantScope): import("../workers-git.js").OysterGitWorkerHost;
54
+ protected hostFor(scope: TenantScope): Promise<import("../workers-git.js").OysterGitWorkerHost>;
55
+ }
56
+ declare const OysterInstallationExecutor_base: new (ctx: ExecutionContext, env: StandaloneEnv) => WorkerEntrypoint<StandaloneEnv, unknown> & {
57
+ run(): Promise<import("../installation-operations.js").InstallationOutcome>;
58
+ };
59
+ /** Private loopback execution uses this root's ordinary host composition. */
60
+ export declare class OysterInstallationExecutor extends OysterInstallationExecutor_base {
47
61
  }
48
62
  /** The Computer's egress gateway: its own HTTPS Git back into this Worker. */
49
63
  export declare class OysterComputerEgress extends WorkerEntrypoint<StandaloneEnv, OysterComputerEgressProps> {