@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.
- package/README.md +396 -72
- package/dist/agents.d.ts +3 -2
- package/dist/agents.js +4 -2
- package/dist/app-builds.d.ts +81 -8
- package/dist/app-builds.js +213 -60
- package/dist/app-storage.d.ts +3 -3
- package/dist/approval-capability.d.ts +5 -0
- package/dist/approval-capability.js +46 -0
- package/dist/approval-home.d.ts +51 -0
- package/dist/approval-home.js +226 -0
- package/dist/apps/catalog.d.ts +335 -0
- package/dist/apps/catalog.js +46 -0
- package/dist/apps/gate.d.ts +20 -6
- package/dist/apps/gate.js +42 -16
- package/dist/apps/index.d.ts +3 -1
- package/dist/apps/index.js +3 -1
- package/dist/apps/installation.d.ts +132 -51
- package/dist/apps/installation.js +176 -70
- package/dist/apps/permissions.d.ts +247 -0
- package/dist/apps/permissions.js +255 -0
- package/dist/boot-capability-route.d.ts +15 -0
- package/dist/boot-capability-route.js +196 -0
- package/dist/boot-capability.d.ts +103 -0
- package/dist/boot-capability.js +141 -0
- package/dist/boot-construction.d.ts +12 -0
- package/dist/boot-construction.js +175 -0
- package/dist/boot-gate.d.ts +267 -0
- package/dist/boot-gate.js +236 -0
- package/dist/boot-host-script.d.ts +2 -0
- package/dist/boot-host-script.js +1 -0
- package/dist/boot-mount.d.ts +21 -0
- package/dist/boot-mount.js +65 -0
- package/dist/browser/boot.d.ts +73 -0
- package/dist/browser/boot.js +502 -0
- package/dist/browser/browser.d.ts +104 -0
- package/dist/browser/browser.js +374 -0
- package/dist/browser/capability-source.d.ts +76 -0
- package/dist/browser/capability-source.js +338 -0
- package/dist/browser/cdp-client.d.ts +56 -0
- package/dist/browser/cdp-client.js +184 -0
- package/dist/browser/errors.d.ts +79 -0
- package/dist/browser/errors.js +32 -0
- package/dist/browser/index.d.ts +16 -0
- package/dist/browser/index.js +14 -0
- package/dist/browser/lease-registry.d.ts +61 -0
- package/dist/browser/lease-registry.js +101 -0
- package/dist/browser/operations.d.ts +225 -0
- package/dist/browser/operations.js +137 -0
- package/dist/browser/page.d.ts +25 -0
- package/dist/browser/page.js +172 -0
- package/dist/browser/quick-reads.d.ts +29 -0
- package/dist/browser/quick-reads.js +160 -0
- package/dist/browser/sessions.d.ts +52 -0
- package/dist/browser/sessions.js +118 -0
- package/dist/browser/url-policy.d.ts +18 -0
- package/dist/browser/url-policy.js +58 -0
- package/dist/build/app-imports.d.ts +12 -0
- package/dist/build/app-imports.js +105 -0
- package/dist/build/build-policy.d.ts +5 -3
- package/dist/build/build-policy.js +5 -3
- package/dist/build/candidate-builder.d.ts +1 -1
- package/dist/build/candidate-cli.mjs +8375 -334
- package/dist/build/local-build.d.ts +2 -0
- package/dist/build/local-build.js +28 -6
- package/dist/build/snapshot-package-store.d.ts +1 -1
- package/dist/build/snapshot-package-store.js +7 -2
- package/dist/build/workspace-build.d.ts +92 -5
- package/dist/build/workspace-build.js +93 -19
- package/dist/build.d.ts +1 -1
- package/dist/build.js +1 -1
- package/dist/catalog-frame.js +1 -1
- package/dist/cloudflare.d.ts +13 -3
- package/dist/cloudflare.js +102 -4
- package/dist/frame-binding-wire-script.js +1 -1
- package/dist/frame-binding-wire.js +4 -2
- package/dist/git-auth.d.ts +0 -2
- package/dist/git-auth.js +0 -2
- package/dist/git.d.ts +0 -1
- package/dist/git.js +0 -2
- package/dist/host.d.ts +39 -13
- package/dist/host.js +268 -142
- package/dist/index.d.ts +25 -15
- package/dist/index.js +39 -16
- package/dist/install-release-cli.d.ts +2 -0
- package/dist/install-release-cli.js +46 -0
- package/dist/installation-client.d.ts +43 -0
- package/dist/installation-client.js +178 -0
- package/dist/installation-execution.d.ts +11 -0
- package/dist/installation-execution.js +184 -0
- package/dist/installation-operations.d.ts +1437 -0
- package/dist/installation-operations.js +664 -0
- package/dist/installation-store-generation.d.ts +3 -0
- package/dist/installation-store-generation.js +16 -0
- package/dist/instance-id.d.ts +10 -0
- package/dist/instance-id.js +10 -0
- package/dist/instance-refusal.d.ts +11 -0
- package/dist/instance-refusal.js +21 -0
- package/dist/internal/delivery.js +4 -2
- package/dist/internal/host-gate.js +7 -1
- package/dist/internal/registry-installations.js +4 -4
- package/dist/keys.d.ts +5 -1
- package/dist/keys.js +10 -2
- package/dist/machine-link/authority.d.ts +89 -0
- package/dist/machine-link/authority.js +37 -0
- package/dist/machine-link/client.d.ts +66 -0
- package/dist/machine-link/client.js +94 -0
- package/dist/machine-link/delivery.d.ts +18 -0
- package/dist/machine-link/delivery.js +55 -0
- package/dist/machine-link/enrollment.d.ts +111 -0
- package/dist/machine-link/enrollment.js +286 -0
- package/dist/machine-link/fences.d.ts +37 -0
- package/dist/machine-link/fences.js +166 -0
- package/dist/machine-link/home.d.ts +20 -0
- package/dist/machine-link/home.js +24 -0
- package/dist/machine-link/host.d.ts +43 -0
- package/dist/machine-link/host.js +189 -0
- package/dist/machine-link/index.d.ts +15 -0
- package/dist/machine-link/index.js +15 -0
- package/dist/machine-link/link.d.ts +48 -0
- package/dist/machine-link/link.js +223 -0
- package/dist/machine-link/presence.d.ts +10 -0
- package/dist/machine-link/presence.js +25 -0
- package/dist/machine-link/workers.d.ts +6 -0
- package/dist/machine-link/workers.js +6 -0
- package/dist/mcp-owner.d.ts +2 -2
- package/dist/mcp-owner.js +41 -33
- package/dist/mcp.d.ts +15 -3
- package/dist/mcp.js +146 -25
- package/dist/older-oyster-store-refusal.d.ts +4 -0
- package/dist/older-oyster-store-refusal.js +18 -0
- package/dist/older-oyster-store.d.ts +21 -0
- package/dist/older-oyster-store.js +43 -0
- package/dist/pack.d.ts +14 -3
- package/dist/pack.js +44 -19
- package/dist/page/app-access.d.ts +7 -0
- package/dist/page/app-access.js +75 -0
- package/dist/page/boot-host.js +57 -0
- package/dist/page/capabilities.d.ts +3 -3
- package/dist/page/capabilities.js +16 -12
- package/dist/page/computer-handles.d.ts +3 -3
- package/dist/page/computer-target.d.ts +3 -0
- package/dist/page/computer-target.js +9 -0
- package/dist/page/mount.d.ts +23 -3
- package/dist/page/mount.js +152 -16
- package/dist/page-assets.d.ts +22 -0
- package/dist/page-assets.js +28 -0
- package/dist/page-script.d.ts +3 -0
- package/dist/page-script.js +5 -4
- package/dist/release.d.ts +13 -6
- package/dist/release.js +10 -7
- package/dist/root-data.d.ts +16 -6
- package/dist/root-data.js +2 -2
- package/dist/source/index.d.ts +1 -1
- package/dist/source/team-source.d.ts +20 -8
- package/dist/source/team-source.js +39 -10
- package/dist/source/template-seed.d.ts +2 -2
- package/dist/standalone/agent-mail.d.ts +24 -0
- package/dist/standalone/agent-mail.js +62 -0
- package/dist/standalone/agent-store.d.ts +9 -0
- package/dist/standalone/agent-store.js +30 -0
- package/dist/standalone/agents.d.ts +68 -9
- package/dist/standalone/agents.js +428 -56
- package/dist/standalone/app-builds-binding.d.ts +20 -2
- package/dist/standalone/app-builds-binding.js +147 -23
- package/dist/standalone/app-set.d.ts +5 -2
- package/dist/standalone/app-set.js +186 -144
- package/dist/standalone/browser.d.ts +11 -0
- package/dist/standalone/browser.js +72 -0
- package/dist/standalone/builds.d.ts +13 -4
- package/dist/standalone/builds.js +28 -44
- package/dist/standalone/computer-egress.d.ts +2 -0
- package/dist/standalone/computer-egress.js +13 -4
- package/dist/standalone/computer-fleet.d.ts +43 -0
- package/dist/standalone/computer-fleet.js +200 -0
- package/dist/standalone/computer.d.ts +44 -9
- package/dist/standalone/computer.js +129 -22
- package/dist/standalone/install-apps-cli.js +43 -6
- package/dist/standalone/instance.d.ts +2 -2
- package/dist/standalone/instance.js +8 -11
- package/dist/standalone/machine-link.d.ts +354 -0
- package/dist/standalone/machine-link.js +368 -0
- package/dist/standalone/media.d.ts +2 -2
- package/dist/standalone/media.js +19 -16
- package/dist/standalone/preview.d.ts +2 -2
- package/dist/standalone/preview.js +37 -10
- package/dist/standalone/registry.d.ts +151 -9
- package/dist/standalone/registry.js +305 -8
- package/dist/standalone/worker.d.ts +19 -5
- package/dist/standalone/worker.js +90 -54
- package/dist/standalone/workflow-binding.d.ts +18 -22
- package/dist/standalone/workflow-binding.js +18 -18
- package/dist/standalone/workflow-control.d.ts +4 -0
- package/dist/standalone/workflow-control.js +40 -0
- package/dist/standalone/workflow-events.d.ts +2 -2
- package/dist/standalone/workflow-events.js +9 -10
- package/dist/standalone/workflow-git-capture.js +4 -3
- package/dist/standalone/workflow-host.d.ts +162 -161
- package/dist/standalone/workflow-host.js +101 -21
- package/dist/standalone/workflow-node-capture.js +11 -4
- package/dist/standalone/workflow-schedules.d.ts +5 -2
- package/dist/standalone/workflow-schedules.js +9 -10
- package/dist/standalone/workflow-trigger-host.d.ts +65 -5
- package/dist/standalone/workflow-trigger-host.js +2 -2
- package/dist/standalone/workflow-webhooks.d.ts +22 -0
- package/dist/standalone/workflow-webhooks.js +39 -0
- package/dist/standalone/workflows.d.ts +140 -43
- package/dist/standalone/workflows.js +198 -39
- package/dist/storage-frame.d.ts +6 -6
- package/dist/testing/capabilities.js +14 -3
- package/dist/testing/installation.js +26 -2
- package/dist/testing/worker.js +1 -1
- package/dist/testing/workers-runtime.d.ts +8 -2
- package/dist/testing/workers-runtime.js +20 -2
- package/dist/webhook-abuse.d.ts +25 -0
- package/dist/webhook-abuse.js +54 -0
- package/dist/webhook-custody.d.ts +144 -0
- package/dist/webhook-custody.js +222 -0
- package/dist/webhook-ingress.d.ts +36 -0
- package/dist/webhook-ingress.js +146 -0
- package/dist/webhooks.d.ts +111 -0
- package/dist/webhooks.js +189 -0
- package/dist/workers-git.d.ts +12 -17
- package/dist/workers-git.js +64 -52
- package/dist/workers.d.ts +226 -13
- package/dist/workers.js +363 -51
- package/dist/workflow-agent-authority.d.ts +8 -0
- package/dist/workflow-agent-authority.js +42 -0
- package/dist/workflows.d.ts +66 -0
- package/dist/workflows.js +97 -0
- package/docs/api/README.md +63 -37
- package/docs/api/agents.md +17 -0
- package/docs/api/app-storage.md +1 -1
- package/docs/api/apps.md +1394 -543
- package/docs/api/browser/boot.md +132 -0
- package/docs/api/browser.md +1440 -0
- package/docs/api/build-contract.md +38 -25
- package/docs/api/build.md +263 -84
- package/docs/api/cloudflare.md +93 -7
- package/docs/api/git.md +3 -13
- package/docs/api/host.md +944 -118
- package/docs/api/index.md +320 -18
- package/docs/api/keys.md +24 -1
- package/docs/api/mcp.md +59 -12
- package/docs/api/modules.md +33 -40
- package/docs/api/pack.md +57 -8
- package/docs/api/page/mount.md +92 -4
- package/docs/api/workers-git.md +103 -24
- package/docs/api/workers.md +1594 -244
- package/docs/site/agent-quickstart.mdx +28 -14
- package/examples/standalone/worker.ts +3 -1
- package/examples/standalone/wrangler.jsonc +2 -0
- package/package.json +73 -71
- package/dist/installation-consent.d.ts +0 -81
- package/dist/installation-consent.js +0 -84
- package/dist/shell/app-directory.d.ts +0 -542
- package/dist/shell/app-directory.js +0 -196
- package/dist/shell/browser-actions.d.ts +0 -7
- package/dist/shell/browser-actions.js +0 -56
- package/dist/shell/command-host.d.ts +0 -112
- package/dist/shell/command-host.js +0 -502
- package/dist/shell/commands.d.ts +0 -25
- package/dist/shell/commands.js +0 -78
- package/dist/shell/index.d.ts +0 -8
- package/dist/shell/index.js +0 -8
- package/dist/shell/keys.d.ts +0 -41
- package/dist/shell/keys.js +0 -134
- package/dist/shell/shell-module.d.ts +0 -55
- package/dist/shell/shell-module.js +0 -86
- package/dist/shell/window-manager.d.ts +0 -128
- package/dist/shell/window-manager.js +0 -730
- package/dist/shell/workbench-contract.d.ts +0 -68
- package/dist/shell/workbench-directory.d.ts +0 -332
- package/dist/shell/workbench-directory.js +0 -35
- package/dist/shell/workbench-host-types.d.ts +0 -96
- package/dist/shell/workbench-host-types.js +0 -1
- package/dist/shell/workbench-host.d.ts +0 -34
- package/dist/shell/workbench-host.js +0 -352
- package/dist/shell/workbench-model.d.ts +0 -194
- package/dist/shell/workbench-model.js +0 -53
- package/dist/shell/workbench.d.ts +0 -4
- package/dist/shell/workbench.js +0 -2
- package/docs/api/shell/app-directory.md +0 -599
- package/docs/api/shell/command-host.md +0 -224
- package/docs/api/shell/commands.md +0 -197
- package/docs/api/shell/keys.md +0 -183
- package/docs/api/shell/shell-module.md +0 -204
- package/docs/api/shell/window-manager.md +0 -267
- package/docs/api/shell/workbench-directory.md +0 -119
- package/docs/api/shell/workbench.md +0 -205
- package/docs/api/shell.md +0 -1915
- /package/dist/{shell/workbench-contract.js → page/boot-host.d.ts} +0 -0
|
@@ -2,150 +2,66 @@ import { execFile } from "node:child_process";
|
|
|
2
2
|
import { mkdtemp, rm } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { setTimeout as sleep } from "node:timers/promises";
|
|
5
|
+
import * as Option from "effect/Option";
|
|
5
6
|
import * as Schema from "effect/Schema";
|
|
7
|
+
import { olderOysterStoreCode } from "../older-oyster-store.js";
|
|
6
8
|
import { publishSource } from "../pack.js";
|
|
9
|
+
import { appBuildRecipe, readBuildManifest } from "../build/workspace-build.js";
|
|
7
10
|
import { parseSourceRevision } from "@fungi.computer/puffball";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
maxManifestBytes: 1_048_576,
|
|
13
|
-
maxObjects: 1024,
|
|
14
|
-
maxObjectBytes: 16_777_216,
|
|
15
|
-
maxTotalBytes: 67_108_864,
|
|
16
|
-
};
|
|
17
|
-
const facet = { kind: "durable-facet", stateSchema: 1 };
|
|
11
|
+
import { releaseSchema } from "@fungi.computer/puffball/effect";
|
|
12
|
+
import { grantCommandSchema, installationSchema, } from "../apps/installation.js";
|
|
13
|
+
import { appPermissionPageSchema } from "../apps/permissions.js";
|
|
14
|
+
import { installOnOyster, InstallationObservationError, } from "../installation-client.js";
|
|
18
15
|
export const standaloneAppSet = [
|
|
19
16
|
{
|
|
20
17
|
installationId: "tourtois",
|
|
18
|
+
dashboard: true,
|
|
21
19
|
directory: "apps/tourtois",
|
|
22
20
|
repository: "tourtois",
|
|
23
21
|
appId: "app_tourtois",
|
|
24
|
-
recipe: {
|
|
25
|
-
entry: "apps/tourtois/index.html",
|
|
26
|
-
assets,
|
|
27
|
-
assetBase: "./",
|
|
28
|
-
backend: {
|
|
29
|
-
entry: "apps/tourtois/backend/index.ts",
|
|
30
|
-
exportName: "Tourtois",
|
|
31
|
-
compatibilityDate: "2026-09-04",
|
|
32
|
-
runtime: facet,
|
|
33
|
-
},
|
|
34
|
-
maxOutputBytes: 67_108_864,
|
|
35
|
-
},
|
|
36
22
|
},
|
|
37
23
|
{
|
|
38
24
|
installationId: "bonsai",
|
|
39
25
|
directory: "apps/bonsai",
|
|
40
26
|
repository: "bonsai",
|
|
41
27
|
appId: "app_bonsai",
|
|
42
|
-
recipe: {
|
|
43
|
-
entry: "apps/bonsai/web/index.html",
|
|
44
|
-
assets,
|
|
45
|
-
assetBase: "./",
|
|
46
|
-
maxOutputBytes: 67_108_864,
|
|
47
|
-
backend: {
|
|
48
|
-
entry: "apps/bonsai/backend/index.ts",
|
|
49
|
-
exportName: "BonsaiApp",
|
|
50
|
-
compatibilityDate: "2026-09-04",
|
|
51
|
-
runtime: facet,
|
|
52
|
-
},
|
|
53
|
-
},
|
|
54
28
|
},
|
|
55
29
|
{
|
|
56
30
|
installationId: "shed",
|
|
57
31
|
directory: "apps/shed",
|
|
58
32
|
repository: "shed",
|
|
59
33
|
appId: "app_shed",
|
|
60
|
-
recipe: {
|
|
61
|
-
entry: "apps/shed/index.html",
|
|
62
|
-
assets: {
|
|
63
|
-
maxManifestBytes: 1_048_576,
|
|
64
|
-
maxObjects: 512,
|
|
65
|
-
maxObjectBytes: 4_194_304,
|
|
66
|
-
maxTotalBytes: 16_777_216,
|
|
67
|
-
},
|
|
68
|
-
assetBase: "./",
|
|
69
|
-
maxOutputBytes: 16_777_216,
|
|
70
|
-
backend: {
|
|
71
|
-
entry: "apps/shed/backend/index.ts",
|
|
72
|
-
exportName: "Shed",
|
|
73
|
-
compatibilityDate: "2026-09-25",
|
|
74
|
-
runtime: facet,
|
|
75
|
-
},
|
|
76
|
-
},
|
|
77
34
|
},
|
|
78
35
|
{
|
|
79
36
|
installationId: "inkcap",
|
|
80
37
|
directory: "apps/inkcap",
|
|
81
38
|
repository: "inkcap",
|
|
82
39
|
appId: "app_inkcap",
|
|
83
|
-
recipe: {
|
|
84
|
-
entry: "apps/inkcap/index.html",
|
|
85
|
-
assets,
|
|
86
|
-
assetBase: "./",
|
|
87
|
-
maxOutputBytes: 16_777_216,
|
|
88
|
-
backend: {
|
|
89
|
-
entry: "apps/inkcap/backend/index.ts",
|
|
90
|
-
exportName: "Inkcap",
|
|
91
|
-
compatibilityDate: "2026-09-25",
|
|
92
|
-
runtime: facet,
|
|
93
|
-
},
|
|
94
|
-
},
|
|
95
40
|
},
|
|
96
41
|
{
|
|
97
42
|
installationId: "trellis",
|
|
98
43
|
directory: "apps/trellis",
|
|
99
44
|
repository: "trellis",
|
|
100
45
|
appId: "app_trellis",
|
|
101
|
-
recipe: {
|
|
102
|
-
entry: "apps/trellis/index.html",
|
|
103
|
-
maxOutputBytes: 16_777_216,
|
|
104
|
-
backend: {
|
|
105
|
-
entry: "apps/trellis/backend/index.ts",
|
|
106
|
-
exportName: "Trellis",
|
|
107
|
-
compatibilityDate: "2026-09-04",
|
|
108
|
-
runtime: facet,
|
|
109
|
-
},
|
|
110
|
-
},
|
|
111
46
|
},
|
|
112
47
|
{
|
|
113
48
|
installationId: "prints",
|
|
114
49
|
directory: "apps/prints",
|
|
115
50
|
repository: "prints",
|
|
116
51
|
appId: "app_prints",
|
|
117
|
-
recipe: {
|
|
118
|
-
entry: "apps/prints/index.html",
|
|
119
|
-
assets,
|
|
120
|
-
assetBase: "./",
|
|
121
|
-
maxOutputBytes: 16_777_216,
|
|
122
|
-
backend: {
|
|
123
|
-
entry: "apps/prints/backend/index.ts",
|
|
124
|
-
exportName: "Prints",
|
|
125
|
-
compatibilityDate: "2026-09-04",
|
|
126
|
-
runtime: facet,
|
|
127
|
-
},
|
|
128
|
-
},
|
|
129
52
|
},
|
|
130
53
|
{
|
|
131
54
|
installationId: "grove",
|
|
132
55
|
directory: "apps/grove",
|
|
133
56
|
repository: "grove",
|
|
134
57
|
appId: "app_grove",
|
|
135
|
-
recipe: {
|
|
136
|
-
entry: "apps/grove/index.html",
|
|
137
|
-
assets,
|
|
138
|
-
assetBase: "./",
|
|
139
|
-
maxOutputBytes: 67_108_864,
|
|
140
|
-
backend: {
|
|
141
|
-
entry: "apps/grove/backend/index.ts",
|
|
142
|
-
exportName: "Grove",
|
|
143
|
-
compatibilityDate: "2026-09-04",
|
|
144
|
-
runtime: facet,
|
|
145
|
-
},
|
|
146
|
-
},
|
|
147
58
|
},
|
|
148
59
|
];
|
|
60
|
+
/** The host's refusal of a store written by Oyster 0.2 or earlier. */
|
|
61
|
+
const olderOysterStoreRefusal = Schema.Struct({
|
|
62
|
+
error: Schema.Literal(olderOysterStoreCode),
|
|
63
|
+
message: Schema.String,
|
|
64
|
+
});
|
|
149
65
|
const publishedSchema = Schema.Struct({
|
|
150
66
|
kind: Schema.Literals(["recorded", "existing"]),
|
|
151
67
|
receipt: Schema.Struct({
|
|
@@ -181,23 +97,6 @@ const appBuildWaitMs = 20 * 60_000;
|
|
|
181
97
|
export const defaultStarterBuildConcurrency = 2;
|
|
182
98
|
/** The most builds at once on the 12 GiB Computer. */
|
|
183
99
|
const maxStarterBuildConcurrency = 3;
|
|
184
|
-
/** A recipe rooted at `from` (a checkout directory), rooted at `to` instead. */
|
|
185
|
-
function relocateRecipe(recipe, from, to) {
|
|
186
|
-
const move = (path) => {
|
|
187
|
-
if (!path.startsWith(`${from}/`))
|
|
188
|
-
throw new Error(`${path} is outside its App directory ${from}`);
|
|
189
|
-
const rest = path.slice(from.length + 1);
|
|
190
|
-
return to === "" ? rest : `${to}/${rest}`;
|
|
191
|
-
};
|
|
192
|
-
const backend = recipe.backend;
|
|
193
|
-
if (backend?.entry === undefined)
|
|
194
|
-
return { ...recipe, entry: move(recipe.entry) };
|
|
195
|
-
return {
|
|
196
|
-
...recipe,
|
|
197
|
-
entry: move(recipe.entry),
|
|
198
|
-
backend: { ...backend, entry: move(backend.entry) },
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
100
|
const run = (command, args, cwd) => new Promise((resolveRun, reject) => {
|
|
202
101
|
execFile(command, args, { cwd, encoding: "utf8", maxBuffer: 1_048_576 }, (error, stdout) => {
|
|
203
102
|
if (error)
|
|
@@ -220,7 +119,10 @@ export async function installStandaloneAppSet(options) {
|
|
|
220
119
|
concurrency < 1 ||
|
|
221
120
|
concurrency > maxStarterBuildConcurrency)
|
|
222
121
|
throw new Error(`Starter builds run 1 to ${String(maxStarterBuildConcurrency)} at a time`);
|
|
223
|
-
const authorization = {
|
|
122
|
+
const authorization = {
|
|
123
|
+
Authorization: `Bearer ${options.key}`,
|
|
124
|
+
Accept: "application/json",
|
|
125
|
+
};
|
|
224
126
|
const owner = async (method, path, body, signal) => {
|
|
225
127
|
const init = body === undefined
|
|
226
128
|
? { method, headers: authorization }
|
|
@@ -233,6 +135,59 @@ export async function installStandaloneAppSet(options) {
|
|
|
233
135
|
init.signal = signal;
|
|
234
136
|
return fetch(new URL(path, options.url), init);
|
|
235
137
|
};
|
|
138
|
+
const approveChosenDashboard = async (installed, generation) => {
|
|
139
|
+
const name = installed.installationId;
|
|
140
|
+
const path = `/api/installations/${encodeURIComponent(name)}/grants`;
|
|
141
|
+
const projectionResponse = await owner("GET", path);
|
|
142
|
+
if (!projectionResponse.ok)
|
|
143
|
+
throw new Error(`${name} approval read failed (${String(projectionResponse.status)})`);
|
|
144
|
+
const projection = appPermissionPageSchema.parse(await projectionResponse.json());
|
|
145
|
+
const { permissions } = projection;
|
|
146
|
+
const digest = installed.release.artifact.artifactDigest;
|
|
147
|
+
if (projection.storeGeneration !== generation ||
|
|
148
|
+
projection.installationId !== name ||
|
|
149
|
+
projection.artifactDigest !== digest ||
|
|
150
|
+
permissions.declaredOperations.artifactDigest !== digest ||
|
|
151
|
+
permissions.authority.authorityVersion !==
|
|
152
|
+
installed.authority.authorityVersion ||
|
|
153
|
+
permissions.authority.declaredOperationsDigest !==
|
|
154
|
+
installed.authority.declaredOperationsDigest ||
|
|
155
|
+
permissions.declaredOperations.digest !==
|
|
156
|
+
installed.authority.declaredOperationsDigest)
|
|
157
|
+
throw new Error(`${name} approval snapshot changed`);
|
|
158
|
+
const shell = permissions.declaredOperations.capabilities.find((row) => row.capability === "team.workbenchHost");
|
|
159
|
+
if (shell === undefined)
|
|
160
|
+
return;
|
|
161
|
+
const command = grantCommandSchema.parse({
|
|
162
|
+
kind: "change-grants",
|
|
163
|
+
changes: [
|
|
164
|
+
{
|
|
165
|
+
capability: shell.capability,
|
|
166
|
+
grant: { operations: shell.operations, resources: "tenant-scope" },
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
});
|
|
170
|
+
const saved = await owner("PUT", path, Schema.decodeUnknownSync(Schema.Json)({
|
|
171
|
+
storeGeneration: projection.storeGeneration,
|
|
172
|
+
authorityVersion: installed.authority.authorityVersion,
|
|
173
|
+
artifactDigest: digest,
|
|
174
|
+
command,
|
|
175
|
+
}));
|
|
176
|
+
if (!saved.ok)
|
|
177
|
+
throw new Error(`${name} approval failed (${String(saved.status)})`);
|
|
178
|
+
const receipt = installationSchema.parse(await saved.json());
|
|
179
|
+
if (receipt.installationId !== name ||
|
|
180
|
+
receipt.instanceId !== installed.instanceId ||
|
|
181
|
+
receipt.release.artifact.artifactDigest !== digest ||
|
|
182
|
+
receipt.authority.authorityVersion !==
|
|
183
|
+
installed.authority.authorityVersion + 1 ||
|
|
184
|
+
!receipt.authority.approvedGrants.some((grant) => grant.capability === "team.workbenchHost" &&
|
|
185
|
+
grant.resources === "tenant-scope" &&
|
|
186
|
+
shell.operations.every((operation) => grant.operations.includes(operation))))
|
|
187
|
+
throw new Error(`${name} approval incomplete`);
|
|
188
|
+
};
|
|
189
|
+
const idle = options.idle ??
|
|
190
|
+
((ms, signal) => sleep(ms, undefined, { signal }));
|
|
236
191
|
/** Start one build and wait for its terminal outcome within the build
|
|
237
192
|
* budget; the budget cancels every request and body read it covers. */
|
|
238
193
|
const buildOnComputer = async (name, appId, seedRevision) => {
|
|
@@ -246,11 +201,13 @@ export async function installStandaloneAppSet(options) {
|
|
|
246
201
|
jobId = build.jobId;
|
|
247
202
|
options.report(`Building ${name} on the Computer (${build.jobId})`);
|
|
248
203
|
while (build.status === "running") {
|
|
249
|
-
await sleep(options.pollMs ?? 5_000, undefined, { signal: budget });
|
|
250
204
|
const read = await owner("GET", `/api/builds/${encodeURIComponent(build.jobId)}`, undefined, budget);
|
|
251
205
|
if (!read.ok)
|
|
252
206
|
throw new Error(`${name} build is missing (${String(read.status)})`);
|
|
253
207
|
build = Schema.decodeUnknownSync(buildSchema)(await read.json());
|
|
208
|
+
// Read first: a build already finished costs no idle wait.
|
|
209
|
+
if (build.status === "running")
|
|
210
|
+
await idle(options.pollMs ?? 5_000, budget);
|
|
254
211
|
}
|
|
255
212
|
return build;
|
|
256
213
|
}
|
|
@@ -276,13 +233,19 @@ export async function installStandaloneAppSet(options) {
|
|
|
276
233
|
* own repository; `release` removes a clone once its template is seeded. */
|
|
277
234
|
const locate = async (app) => {
|
|
278
235
|
const source = options.source;
|
|
279
|
-
if (source.kind === "checkout")
|
|
236
|
+
if (source.kind === "checkout") {
|
|
237
|
+
const sourceRevision = parseSourceRevision(source.revision);
|
|
238
|
+
const metadata = await run("git", [
|
|
239
|
+
"show",
|
|
240
|
+
sourceRevision.slice(4) + ":" + app.directory + "/package.json",
|
|
241
|
+
], source.directory);
|
|
280
242
|
return {
|
|
281
243
|
repositoryPath: source.directory,
|
|
282
|
-
sourceRevision
|
|
283
|
-
recipe: app.
|
|
244
|
+
sourceRevision,
|
|
245
|
+
recipe: appBuildRecipe(readBuildManifest(metadata), app.directory),
|
|
284
246
|
release: () => Promise.resolve(),
|
|
285
247
|
};
|
|
248
|
+
}
|
|
286
249
|
const clone = await mkdtemp(join(source.scratch, `${app.repository}-`));
|
|
287
250
|
try {
|
|
288
251
|
await run("git", [
|
|
@@ -298,7 +261,7 @@ export async function installStandaloneAppSet(options) {
|
|
|
298
261
|
return {
|
|
299
262
|
repositoryPath: clone,
|
|
300
263
|
sourceRevision: parseSourceRevision(`git:${head}`),
|
|
301
|
-
recipe:
|
|
264
|
+
recipe: appBuildRecipe(readBuildManifest(await run("git", ["show", head + ":package.json"], clone)), ""),
|
|
302
265
|
release: () => rm(clone, { recursive: true, force: true }),
|
|
303
266
|
};
|
|
304
267
|
}
|
|
@@ -307,21 +270,38 @@ export async function installStandaloneAppSet(options) {
|
|
|
307
270
|
throw cause;
|
|
308
271
|
}
|
|
309
272
|
};
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
273
|
+
const installed = new Map();
|
|
274
|
+
/** Set by the first failure, so no further seed or build starts. */
|
|
275
|
+
const stop = { requested: false };
|
|
276
|
+
/** Every failure, in the order it happened; the first is the one thrown. */
|
|
277
|
+
const failures = [];
|
|
278
|
+
const seededQueue = [];
|
|
279
|
+
const waiting = [];
|
|
280
|
+
const seeding = { finished: false };
|
|
281
|
+
const takeSeeded = () => new Promise((resolveTake) => {
|
|
282
|
+
const next = seededQueue.shift();
|
|
283
|
+
if (next !== undefined || seeding.finished)
|
|
284
|
+
resolveTake(next);
|
|
285
|
+
else
|
|
286
|
+
waiting.push(resolveTake);
|
|
287
|
+
});
|
|
288
|
+
const seedApp = async (app) => {
|
|
313
289
|
const name = app.installationId;
|
|
314
290
|
const fetchStarted = performance.now();
|
|
315
291
|
const located = await locate(app);
|
|
316
292
|
const publishStarted = performance.now();
|
|
317
293
|
try {
|
|
318
294
|
const published = await publish(app, located);
|
|
319
|
-
if (!published.ok)
|
|
295
|
+
if (!published.ok) {
|
|
296
|
+
const refusal = Schema.decodeUnknownOption(Schema.fromJsonString(olderOysterStoreRefusal))(await published.text());
|
|
297
|
+
if (Option.isSome(refusal))
|
|
298
|
+
throw new Error(refusal.value.message);
|
|
320
299
|
throw new Error(`${name} source publish failed (${String(published.status)})`);
|
|
300
|
+
}
|
|
321
301
|
const { kind, receipt } = Schema.decodeUnknownSync(publishedSchema)(await published.json());
|
|
322
302
|
options.report(`${name} source ${kind} at ${receipt.seedRevision}`);
|
|
323
303
|
const ready = performance.now();
|
|
324
|
-
|
|
304
|
+
return {
|
|
325
305
|
app,
|
|
326
306
|
kind,
|
|
327
307
|
seedRevision: receipt.seedRevision,
|
|
@@ -329,45 +309,99 @@ export async function installStandaloneAppSet(options) {
|
|
|
329
309
|
fetch: publishStarted - fetchStarted,
|
|
330
310
|
publish: ready - publishStarted,
|
|
331
311
|
ready,
|
|
332
|
-
}
|
|
312
|
+
};
|
|
333
313
|
}
|
|
334
314
|
finally {
|
|
335
315
|
await located.release();
|
|
336
316
|
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
317
|
+
};
|
|
318
|
+
const seedAll = async () => {
|
|
319
|
+
try {
|
|
320
|
+
for (const app of standaloneAppSet) {
|
|
321
|
+
if (stop.requested)
|
|
322
|
+
break;
|
|
323
|
+
const seeded = await seedApp(app);
|
|
324
|
+
const waiter = waiting.shift();
|
|
325
|
+
if (waiter === undefined)
|
|
326
|
+
seededQueue.push(seeded);
|
|
327
|
+
else
|
|
328
|
+
waiter(seeded);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
catch (cause) {
|
|
332
|
+
stop.requested = true;
|
|
333
|
+
failures.push(cause);
|
|
334
|
+
throw cause;
|
|
335
|
+
}
|
|
336
|
+
finally {
|
|
337
|
+
seeding.finished = true;
|
|
338
|
+
for (const waiter of waiting.splice(0))
|
|
339
|
+
waiter(undefined);
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
// `concurrency` builds at once, each installing as its build succeeds.
|
|
345
343
|
const slot = async () => {
|
|
346
|
-
for (let next =
|
|
344
|
+
for (let next = await takeSeeded(); next !== undefined && !stop.requested; next = await takeSeeded()) {
|
|
347
345
|
const name = next.app.installationId;
|
|
348
346
|
try {
|
|
349
347
|
const buildStarted = performance.now();
|
|
350
348
|
const build = await buildOnComputer(name, next.app.appId, next.seedRevision);
|
|
351
349
|
if (build.status !== "succeeded" || build.release === null)
|
|
352
350
|
throw new Error(`${name} build ${build.status}: ${build.detail ?? "no Release"}`);
|
|
351
|
+
const release = Schema.decodeUnknownSync(releaseSchema)(build.release, {
|
|
352
|
+
onExcessProperty: "error",
|
|
353
|
+
});
|
|
354
|
+
if (release.appId !== next.app.appId)
|
|
355
|
+
throw new Error(`${name} build belongs to another App`);
|
|
353
356
|
const installStarted = performance.now();
|
|
354
|
-
|
|
355
|
-
|
|
357
|
+
let installation = {
|
|
358
|
+
url: options.url,
|
|
359
|
+
key: options.key,
|
|
360
|
+
release,
|
|
356
361
|
installationId: name,
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
options.
|
|
362
|
+
signal: AbortSignal.timeout(options.waitMs ?? appBuildWaitMs),
|
|
363
|
+
idle,
|
|
364
|
+
};
|
|
365
|
+
if (options.pollMs !== undefined)
|
|
366
|
+
installation = { ...installation, pollMs: options.pollMs };
|
|
367
|
+
const operation = await installOnOyster(installation);
|
|
368
|
+
if (operation.state === "unconfirmed")
|
|
369
|
+
throw new InstallationObservationError(operation.requestId);
|
|
370
|
+
if (operation.state === "refused")
|
|
371
|
+
throw new Error(`${name} installation refused (${operation.code})`);
|
|
372
|
+
const record = operation.installation;
|
|
373
|
+
if (record === null)
|
|
374
|
+
throw new Error(`${name} completed installation version is unavailable`);
|
|
375
|
+
if (record.appId !== release.appId ||
|
|
376
|
+
record.release.releaseId !== release.releaseId ||
|
|
377
|
+
record.release.artifact.artifactDigest !==
|
|
378
|
+
release.artifact.artifactDigest)
|
|
379
|
+
throw new Error(`${name} installation does not match its build`);
|
|
380
|
+
// Only the owner can attest exclusive fresh creation. A replacement or
|
|
381
|
+
// tombstone remint never replays a default grant.
|
|
382
|
+
if (operation.creation === "fresh") {
|
|
383
|
+
if (record.status !== "enabled")
|
|
384
|
+
throw new Error(`${name} fresh installation is not enabled`);
|
|
385
|
+
if (next.app.dashboard === true)
|
|
386
|
+
await approveChosenDashboard(record, operation.reference.generation);
|
|
387
|
+
}
|
|
388
|
+
options.report(`${operation.creation === "replacement" ? "Replaced" : "Installed"} ${name} from its build ${build.jobId}`);
|
|
389
|
+
// Report-only UI check: never blocks, but the owner sees it here.
|
|
390
|
+
const uiCheck = operation.uiCheck;
|
|
391
|
+
if (uiCheck?.kind === "failed")
|
|
392
|
+
options.report(`Warning: ${name} is installed, but its UI check failed (${uiCheck.code}${uiCheck.diagnostics === undefined ? "" : `: ${uiCheck.diagnostics.failedPredicate}`}).`);
|
|
393
|
+
else if (uiCheck?.kind === "not_checked")
|
|
394
|
+
options.report(`Warning: ${name} is installed, but its UI was not checked (${uiCheck.code}).`);
|
|
361
395
|
const phases = build.detail === null
|
|
362
396
|
? undefined
|
|
363
397
|
: Schema.decodeUnknownOption(computerPhasesSchema)(build.detail);
|
|
364
398
|
installed.set(name, {
|
|
365
|
-
installationId:
|
|
399
|
+
installationId: record.installationId,
|
|
366
400
|
published: next.kind,
|
|
367
401
|
seedRevision: next.seedRevision,
|
|
368
402
|
sourceRevision: next.sourceRevision,
|
|
369
403
|
jobId: build.jobId,
|
|
370
|
-
installed:
|
|
404
|
+
installed: operation.creation === "replacement" ? "replaced" : "installed",
|
|
371
405
|
release: build.release,
|
|
372
406
|
timings: {
|
|
373
407
|
fetch: next.fetch,
|
|
@@ -383,11 +417,19 @@ export async function installStandaloneAppSet(options) {
|
|
|
383
417
|
}
|
|
384
418
|
catch (cause) {
|
|
385
419
|
stop.requested = true;
|
|
420
|
+
failures.push(cause);
|
|
386
421
|
throw cause;
|
|
387
422
|
}
|
|
388
423
|
}
|
|
389
424
|
};
|
|
390
|
-
|
|
425
|
+
// Wait for every seed and build to settle before reporting: nothing of this
|
|
426
|
+
// call may still run, or install, after it rejects.
|
|
427
|
+
await Promise.allSettled([
|
|
428
|
+
seedAll(),
|
|
429
|
+
...Array.from({ length: concurrency }, slot),
|
|
430
|
+
]);
|
|
431
|
+
if (failures.length > 0)
|
|
432
|
+
throw failures[0];
|
|
391
433
|
return standaloneAppSet.flatMap((app) => {
|
|
392
434
|
const result = installed.get(app.installationId);
|
|
393
435
|
return result === undefined ? [] : [result];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { CapabilitySource } from "@fungi.computer/mycelium";
|
|
2
|
+
import { type BrowserBinding } from "../browser/index.js";
|
|
3
|
+
/**
|
|
4
|
+
* The agent's `cloudBrowser` tool on a standalone Oyster, or an empty source
|
|
5
|
+
* when the deployment declares no BROWSER binding. Driven sessions only: no
|
|
6
|
+
* Quick Reads, no Computer save, no member handoff on this host.
|
|
7
|
+
*/
|
|
8
|
+
export declare function standaloneBrowserSource(binding: BrowserBinding | undefined, identity: () => Readonly<{
|
|
9
|
+
teamId: string;
|
|
10
|
+
agentId: string;
|
|
11
|
+
}>): CapabilitySource;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { bindingSessions, browserCapabilitySource, createBrowser, } from "../browser/index.js";
|
|
2
|
+
import { causeClass } from "../cause-class.js";
|
|
3
|
+
/**
|
|
4
|
+
* A standalone Oyster's `BrowserHost`: its own Workers BROWSER binding, no
|
|
5
|
+
* Quick Reads (the binding has none), and the owner-funded meter below.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* A standalone Oyster has no credit ledger: its owner's Cloudflare account
|
|
9
|
+
* pays Browser Rendering directly. This is that host's explicit metering
|
|
10
|
+
* decision, not a missing meter; the Hub supplies its Billing owner instead.
|
|
11
|
+
*/
|
|
12
|
+
const ownerFundedMeter = {
|
|
13
|
+
hold: () => Promise.resolve(),
|
|
14
|
+
start: () => Promise.resolve(),
|
|
15
|
+
settle: () => Promise.resolve(),
|
|
16
|
+
release: () => Promise.resolve(),
|
|
17
|
+
};
|
|
18
|
+
/** One Browser per binding per isolate, so a lease survives between calls. */
|
|
19
|
+
const browsers = new WeakMap();
|
|
20
|
+
function standaloneBrowser(binding) {
|
|
21
|
+
let browser = browsers.get(binding);
|
|
22
|
+
if (browser === undefined) {
|
|
23
|
+
browser = createBrowser({
|
|
24
|
+
sessions: bindingSessions(binding),
|
|
25
|
+
quickReads: null,
|
|
26
|
+
meter: () => ownerFundedMeter,
|
|
27
|
+
report: (event) => {
|
|
28
|
+
// Code-only: never a URL, page text or provider body.
|
|
29
|
+
console.warn("Oyster browser", {
|
|
30
|
+
kind: event.kind,
|
|
31
|
+
executionId: event.executionId,
|
|
32
|
+
cause: causeClass(event.cause),
|
|
33
|
+
});
|
|
34
|
+
},
|
|
35
|
+
}, {
|
|
36
|
+
maxConcurrentPerTeam: 2,
|
|
37
|
+
maxLifetimeMs: 30 * 60_000,
|
|
38
|
+
idleTimeoutMs: 5 * 60_000,
|
|
39
|
+
});
|
|
40
|
+
browsers.set(binding, browser);
|
|
41
|
+
}
|
|
42
|
+
return browser;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The agent's `cloudBrowser` tool on a standalone Oyster, or an empty source
|
|
46
|
+
* when the deployment declares no BROWSER binding. Driven sessions only: no
|
|
47
|
+
* Quick Reads, no Computer save, no member handoff on this host.
|
|
48
|
+
*/
|
|
49
|
+
export function standaloneBrowserSource(binding, identity) {
|
|
50
|
+
if (binding === undefined)
|
|
51
|
+
return {
|
|
52
|
+
id: "cloud-browser",
|
|
53
|
+
acquire: () => Promise.resolve({
|
|
54
|
+
snapshot: { version: "0", modules: [] },
|
|
55
|
+
release: () => Promise.resolve(),
|
|
56
|
+
}),
|
|
57
|
+
};
|
|
58
|
+
const browser = standaloneBrowser(binding);
|
|
59
|
+
return browserCapabilitySource({
|
|
60
|
+
identity,
|
|
61
|
+
browser: {
|
|
62
|
+
call: (owner, op, signal) => browser.call(owner, op, signal),
|
|
63
|
+
handoff: (owner, leaseId) => {
|
|
64
|
+
browser.handoff(owner, leaseId);
|
|
65
|
+
return Promise.resolve();
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
quickReads: false,
|
|
69
|
+
saveFile: null,
|
|
70
|
+
requestHandoff: null,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
@@ -2,17 +2,26 @@
|
|
|
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
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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
|
+
import { type OysterBuildStartReason } from "../app-builds.js";
|
|
14
15
|
import { type OysterEnv } from "../host.js";
|
|
15
16
|
import type { StandaloneRegistry } from "./registry.js";
|
|
17
|
+
/** Both HTTP and APPS expose the same closed preflight reason. */
|
|
18
|
+
export declare function buildStartFailureReply(reason: OysterBuildStartReason | "unavailable"): {
|
|
19
|
+
status: number;
|
|
20
|
+
body: {
|
|
21
|
+
code: "unavailable" | "invalid_revision" | "no_source" | "not_template_seed" | "no_backend";
|
|
22
|
+
error: string;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
16
25
|
/** The build routes over the tenant registry that owns builds, resolved per
|
|
17
26
|
* request so mounting the routes reaches no registry. */
|
|
18
27
|
export declare function standaloneBuildRoutes(registryFor: () => DurableObjectStub<StandaloneRegistry>): Hono<OysterEnv>;
|