@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.
- package/README.md +376 -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 +210 -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/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 +174 -143
- 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 +149 -9
- package/dist/standalone/registry.js +299 -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 +60 -52
- package/dist/workers.d.ts +224 -13
- package/dist/workers.js +351 -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 +1581 -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
|
@@ -4,146 +4,55 @@ import { join } from "node:path";
|
|
|
4
4
|
import { setTimeout as sleep } from "node:timers/promises";
|
|
5
5
|
import * as Schema from "effect/Schema";
|
|
6
6
|
import { publishSource } from "../pack.js";
|
|
7
|
+
import { appBuildRecipe, readBuildManifest } from "../build/workspace-build.js";
|
|
7
8
|
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 };
|
|
9
|
+
import { releaseSchema } from "@fungi.computer/puffball/effect";
|
|
10
|
+
import { grantCommandSchema, installationSchema, } from "../apps/installation.js";
|
|
11
|
+
import { appPermissionPageSchema } from "../apps/permissions.js";
|
|
12
|
+
import { installOnOyster, InstallationObservationError, } from "../installation-client.js";
|
|
18
13
|
export const standaloneAppSet = [
|
|
19
14
|
{
|
|
20
15
|
installationId: "tourtois",
|
|
16
|
+
dashboard: true,
|
|
21
17
|
directory: "apps/tourtois",
|
|
22
18
|
repository: "tourtois",
|
|
23
19
|
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
20
|
},
|
|
37
21
|
{
|
|
38
22
|
installationId: "bonsai",
|
|
39
23
|
directory: "apps/bonsai",
|
|
40
24
|
repository: "bonsai",
|
|
41
25
|
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
26
|
},
|
|
55
27
|
{
|
|
56
28
|
installationId: "shed",
|
|
57
29
|
directory: "apps/shed",
|
|
58
30
|
repository: "shed",
|
|
59
31
|
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
32
|
},
|
|
78
33
|
{
|
|
79
34
|
installationId: "inkcap",
|
|
80
35
|
directory: "apps/inkcap",
|
|
81
36
|
repository: "inkcap",
|
|
82
37
|
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
38
|
},
|
|
96
39
|
{
|
|
97
40
|
installationId: "trellis",
|
|
98
41
|
directory: "apps/trellis",
|
|
99
42
|
repository: "trellis",
|
|
100
43
|
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
44
|
},
|
|
112
45
|
{
|
|
113
46
|
installationId: "prints",
|
|
114
47
|
directory: "apps/prints",
|
|
115
48
|
repository: "prints",
|
|
116
49
|
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
50
|
},
|
|
130
51
|
{
|
|
131
52
|
installationId: "grove",
|
|
132
53
|
directory: "apps/grove",
|
|
133
54
|
repository: "grove",
|
|
134
55
|
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
56
|
},
|
|
148
57
|
];
|
|
149
58
|
const publishedSchema = Schema.Struct({
|
|
@@ -181,23 +90,6 @@ const appBuildWaitMs = 20 * 60_000;
|
|
|
181
90
|
export const defaultStarterBuildConcurrency = 2;
|
|
182
91
|
/** The most builds at once on the 12 GiB Computer. */
|
|
183
92
|
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
93
|
const run = (command, args, cwd) => new Promise((resolveRun, reject) => {
|
|
202
94
|
execFile(command, args, { cwd, encoding: "utf8", maxBuffer: 1_048_576 }, (error, stdout) => {
|
|
203
95
|
if (error)
|
|
@@ -220,7 +112,10 @@ export async function installStandaloneAppSet(options) {
|
|
|
220
112
|
concurrency < 1 ||
|
|
221
113
|
concurrency > maxStarterBuildConcurrency)
|
|
222
114
|
throw new Error(`Starter builds run 1 to ${String(maxStarterBuildConcurrency)} at a time`);
|
|
223
|
-
const authorization = {
|
|
115
|
+
const authorization = {
|
|
116
|
+
Authorization: `Bearer ${options.key}`,
|
|
117
|
+
Accept: "application/json",
|
|
118
|
+
};
|
|
224
119
|
const owner = async (method, path, body, signal) => {
|
|
225
120
|
const init = body === undefined
|
|
226
121
|
? { method, headers: authorization }
|
|
@@ -233,6 +128,59 @@ export async function installStandaloneAppSet(options) {
|
|
|
233
128
|
init.signal = signal;
|
|
234
129
|
return fetch(new URL(path, options.url), init);
|
|
235
130
|
};
|
|
131
|
+
const approveChosenDashboard = async (installed, generation) => {
|
|
132
|
+
const name = installed.installationId;
|
|
133
|
+
const path = `/api/installations/${encodeURIComponent(name)}/grants`;
|
|
134
|
+
const projectionResponse = await owner("GET", path);
|
|
135
|
+
if (!projectionResponse.ok)
|
|
136
|
+
throw new Error(`${name} approval read failed (${String(projectionResponse.status)})`);
|
|
137
|
+
const projection = appPermissionPageSchema.parse(await projectionResponse.json());
|
|
138
|
+
const { permissions } = projection;
|
|
139
|
+
const digest = installed.release.artifact.artifactDigest;
|
|
140
|
+
if (projection.storeGeneration !== generation ||
|
|
141
|
+
projection.installationId !== name ||
|
|
142
|
+
projection.artifactDigest !== digest ||
|
|
143
|
+
permissions.declaredOperations.artifactDigest !== digest ||
|
|
144
|
+
permissions.authority.authorityVersion !==
|
|
145
|
+
installed.authority.authorityVersion ||
|
|
146
|
+
permissions.authority.declaredOperationsDigest !==
|
|
147
|
+
installed.authority.declaredOperationsDigest ||
|
|
148
|
+
permissions.declaredOperations.digest !==
|
|
149
|
+
installed.authority.declaredOperationsDigest)
|
|
150
|
+
throw new Error(`${name} approval snapshot changed`);
|
|
151
|
+
const shell = permissions.declaredOperations.capabilities.find((row) => row.capability === "team.workbenchHost");
|
|
152
|
+
if (shell === undefined)
|
|
153
|
+
return;
|
|
154
|
+
const command = grantCommandSchema.parse({
|
|
155
|
+
kind: "change-grants",
|
|
156
|
+
changes: [
|
|
157
|
+
{
|
|
158
|
+
capability: shell.capability,
|
|
159
|
+
grant: { operations: shell.operations, resources: "tenant-scope" },
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
});
|
|
163
|
+
const saved = await owner("PUT", path, Schema.decodeUnknownSync(Schema.Json)({
|
|
164
|
+
storeGeneration: projection.storeGeneration,
|
|
165
|
+
authorityVersion: installed.authority.authorityVersion,
|
|
166
|
+
artifactDigest: digest,
|
|
167
|
+
command,
|
|
168
|
+
}));
|
|
169
|
+
if (!saved.ok)
|
|
170
|
+
throw new Error(`${name} approval failed (${String(saved.status)})`);
|
|
171
|
+
const receipt = installationSchema.parse(await saved.json());
|
|
172
|
+
if (receipt.installationId !== name ||
|
|
173
|
+
receipt.instanceId !== installed.instanceId ||
|
|
174
|
+
receipt.release.artifact.artifactDigest !== digest ||
|
|
175
|
+
receipt.authority.authorityVersion !==
|
|
176
|
+
installed.authority.authorityVersion + 1 ||
|
|
177
|
+
!receipt.authority.approvedGrants.some((grant) => grant.capability === "team.workbenchHost" &&
|
|
178
|
+
grant.resources === "tenant-scope" &&
|
|
179
|
+
shell.operations.every((operation) => grant.operations.includes(operation))))
|
|
180
|
+
throw new Error(`${name} approval incomplete`);
|
|
181
|
+
};
|
|
182
|
+
const idle = options.idle ??
|
|
183
|
+
((ms, signal) => sleep(ms, undefined, { signal }));
|
|
236
184
|
/** Start one build and wait for its terminal outcome within the build
|
|
237
185
|
* budget; the budget cancels every request and body read it covers. */
|
|
238
186
|
const buildOnComputer = async (name, appId, seedRevision) => {
|
|
@@ -246,11 +194,13 @@ export async function installStandaloneAppSet(options) {
|
|
|
246
194
|
jobId = build.jobId;
|
|
247
195
|
options.report(`Building ${name} on the Computer (${build.jobId})`);
|
|
248
196
|
while (build.status === "running") {
|
|
249
|
-
await sleep(options.pollMs ?? 5_000, undefined, { signal: budget });
|
|
250
197
|
const read = await owner("GET", `/api/builds/${encodeURIComponent(build.jobId)}`, undefined, budget);
|
|
251
198
|
if (!read.ok)
|
|
252
199
|
throw new Error(`${name} build is missing (${String(read.status)})`);
|
|
253
200
|
build = Schema.decodeUnknownSync(buildSchema)(await read.json());
|
|
201
|
+
// Read first: a build already finished costs no idle wait.
|
|
202
|
+
if (build.status === "running")
|
|
203
|
+
await idle(options.pollMs ?? 5_000, budget);
|
|
254
204
|
}
|
|
255
205
|
return build;
|
|
256
206
|
}
|
|
@@ -276,13 +226,19 @@ export async function installStandaloneAppSet(options) {
|
|
|
276
226
|
* own repository; `release` removes a clone once its template is seeded. */
|
|
277
227
|
const locate = async (app) => {
|
|
278
228
|
const source = options.source;
|
|
279
|
-
if (source.kind === "checkout")
|
|
229
|
+
if (source.kind === "checkout") {
|
|
230
|
+
const sourceRevision = parseSourceRevision(source.revision);
|
|
231
|
+
const metadata = await run("git", [
|
|
232
|
+
"show",
|
|
233
|
+
sourceRevision.slice(4) + ":" + app.directory + "/package.json",
|
|
234
|
+
], source.directory);
|
|
280
235
|
return {
|
|
281
236
|
repositoryPath: source.directory,
|
|
282
|
-
sourceRevision
|
|
283
|
-
recipe: app.
|
|
237
|
+
sourceRevision,
|
|
238
|
+
recipe: appBuildRecipe(readBuildManifest(metadata), app.directory),
|
|
284
239
|
release: () => Promise.resolve(),
|
|
285
240
|
};
|
|
241
|
+
}
|
|
286
242
|
const clone = await mkdtemp(join(source.scratch, `${app.repository}-`));
|
|
287
243
|
try {
|
|
288
244
|
await run("git", [
|
|
@@ -298,7 +254,7 @@ export async function installStandaloneAppSet(options) {
|
|
|
298
254
|
return {
|
|
299
255
|
repositoryPath: clone,
|
|
300
256
|
sourceRevision: parseSourceRevision(`git:${head}`),
|
|
301
|
-
recipe:
|
|
257
|
+
recipe: appBuildRecipe(readBuildManifest(await run("git", ["show", head + ":package.json"], clone)), ""),
|
|
302
258
|
release: () => rm(clone, { recursive: true, force: true }),
|
|
303
259
|
};
|
|
304
260
|
}
|
|
@@ -307,9 +263,22 @@ export async function installStandaloneAppSet(options) {
|
|
|
307
263
|
throw cause;
|
|
308
264
|
}
|
|
309
265
|
};
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
266
|
+
const installed = new Map();
|
|
267
|
+
/** Set by the first failure, so no further seed or build starts. */
|
|
268
|
+
const stop = { requested: false };
|
|
269
|
+
/** Every failure, in the order it happened; the first is the one thrown. */
|
|
270
|
+
const failures = [];
|
|
271
|
+
const seededQueue = [];
|
|
272
|
+
const waiting = [];
|
|
273
|
+
const seeding = { finished: false };
|
|
274
|
+
const takeSeeded = () => new Promise((resolveTake) => {
|
|
275
|
+
const next = seededQueue.shift();
|
|
276
|
+
if (next !== undefined || seeding.finished)
|
|
277
|
+
resolveTake(next);
|
|
278
|
+
else
|
|
279
|
+
waiting.push(resolveTake);
|
|
280
|
+
});
|
|
281
|
+
const seedApp = async (app) => {
|
|
313
282
|
const name = app.installationId;
|
|
314
283
|
const fetchStarted = performance.now();
|
|
315
284
|
const located = await locate(app);
|
|
@@ -321,7 +290,7 @@ export async function installStandaloneAppSet(options) {
|
|
|
321
290
|
const { kind, receipt } = Schema.decodeUnknownSync(publishedSchema)(await published.json());
|
|
322
291
|
options.report(`${name} source ${kind} at ${receipt.seedRevision}`);
|
|
323
292
|
const ready = performance.now();
|
|
324
|
-
|
|
293
|
+
return {
|
|
325
294
|
app,
|
|
326
295
|
kind,
|
|
327
296
|
seedRevision: receipt.seedRevision,
|
|
@@ -329,45 +298,99 @@ export async function installStandaloneAppSet(options) {
|
|
|
329
298
|
fetch: publishStarted - fetchStarted,
|
|
330
299
|
publish: ready - publishStarted,
|
|
331
300
|
ready,
|
|
332
|
-
}
|
|
301
|
+
};
|
|
333
302
|
}
|
|
334
303
|
finally {
|
|
335
304
|
await located.release();
|
|
336
305
|
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
306
|
+
};
|
|
307
|
+
const seedAll = async () => {
|
|
308
|
+
try {
|
|
309
|
+
for (const app of standaloneAppSet) {
|
|
310
|
+
if (stop.requested)
|
|
311
|
+
break;
|
|
312
|
+
const seeded = await seedApp(app);
|
|
313
|
+
const waiter = waiting.shift();
|
|
314
|
+
if (waiter === undefined)
|
|
315
|
+
seededQueue.push(seeded);
|
|
316
|
+
else
|
|
317
|
+
waiter(seeded);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
catch (cause) {
|
|
321
|
+
stop.requested = true;
|
|
322
|
+
failures.push(cause);
|
|
323
|
+
throw cause;
|
|
324
|
+
}
|
|
325
|
+
finally {
|
|
326
|
+
seeding.finished = true;
|
|
327
|
+
for (const waiter of waiting.splice(0))
|
|
328
|
+
waiter(undefined);
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
// `concurrency` builds at once, each installing as its build succeeds.
|
|
345
332
|
const slot = async () => {
|
|
346
|
-
for (let next =
|
|
333
|
+
for (let next = await takeSeeded(); next !== undefined && !stop.requested; next = await takeSeeded()) {
|
|
347
334
|
const name = next.app.installationId;
|
|
348
335
|
try {
|
|
349
336
|
const buildStarted = performance.now();
|
|
350
337
|
const build = await buildOnComputer(name, next.app.appId, next.seedRevision);
|
|
351
338
|
if (build.status !== "succeeded" || build.release === null)
|
|
352
339
|
throw new Error(`${name} build ${build.status}: ${build.detail ?? "no Release"}`);
|
|
340
|
+
const release = Schema.decodeUnknownSync(releaseSchema)(build.release, {
|
|
341
|
+
onExcessProperty: "error",
|
|
342
|
+
});
|
|
343
|
+
if (release.appId !== next.app.appId)
|
|
344
|
+
throw new Error(`${name} build belongs to another App`);
|
|
353
345
|
const installStarted = performance.now();
|
|
354
|
-
|
|
355
|
-
|
|
346
|
+
let installation = {
|
|
347
|
+
url: options.url,
|
|
348
|
+
key: options.key,
|
|
349
|
+
release,
|
|
356
350
|
installationId: name,
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
options.
|
|
351
|
+
signal: AbortSignal.timeout(options.waitMs ?? appBuildWaitMs),
|
|
352
|
+
idle,
|
|
353
|
+
};
|
|
354
|
+
if (options.pollMs !== undefined)
|
|
355
|
+
installation = { ...installation, pollMs: options.pollMs };
|
|
356
|
+
const operation = await installOnOyster(installation);
|
|
357
|
+
if (operation.state === "unconfirmed")
|
|
358
|
+
throw new InstallationObservationError(operation.requestId);
|
|
359
|
+
if (operation.state === "refused")
|
|
360
|
+
throw new Error(`${name} installation refused (${operation.code})`);
|
|
361
|
+
const record = operation.installation;
|
|
362
|
+
if (record === null)
|
|
363
|
+
throw new Error(`${name} completed installation version is unavailable`);
|
|
364
|
+
if (record.appId !== release.appId ||
|
|
365
|
+
record.release.releaseId !== release.releaseId ||
|
|
366
|
+
record.release.artifact.artifactDigest !==
|
|
367
|
+
release.artifact.artifactDigest)
|
|
368
|
+
throw new Error(`${name} installation does not match its build`);
|
|
369
|
+
// Only the owner can attest exclusive fresh creation. A replacement or
|
|
370
|
+
// tombstone remint never replays a default grant.
|
|
371
|
+
if (operation.creation === "fresh") {
|
|
372
|
+
if (record.status !== "enabled")
|
|
373
|
+
throw new Error(`${name} fresh installation is not enabled`);
|
|
374
|
+
if (next.app.dashboard === true)
|
|
375
|
+
await approveChosenDashboard(record, operation.reference.generation);
|
|
376
|
+
}
|
|
377
|
+
options.report(`${operation.creation === "replacement" ? "Replaced" : "Installed"} ${name} from its build ${build.jobId}`);
|
|
378
|
+
// Report-only UI check: never blocks, but the owner sees it here.
|
|
379
|
+
const uiCheck = operation.uiCheck;
|
|
380
|
+
if (uiCheck?.kind === "failed")
|
|
381
|
+
options.report(`Warning: ${name} is installed, but its UI check failed (${uiCheck.code}${uiCheck.diagnostics === undefined ? "" : `: ${uiCheck.diagnostics.failedPredicate}`}).`);
|
|
382
|
+
else if (uiCheck?.kind === "not_checked")
|
|
383
|
+
options.report(`Warning: ${name} is installed, but its UI was not checked (${uiCheck.code}).`);
|
|
361
384
|
const phases = build.detail === null
|
|
362
385
|
? undefined
|
|
363
386
|
: Schema.decodeUnknownOption(computerPhasesSchema)(build.detail);
|
|
364
387
|
installed.set(name, {
|
|
365
|
-
installationId:
|
|
388
|
+
installationId: record.installationId,
|
|
366
389
|
published: next.kind,
|
|
367
390
|
seedRevision: next.seedRevision,
|
|
368
391
|
sourceRevision: next.sourceRevision,
|
|
369
392
|
jobId: build.jobId,
|
|
370
|
-
installed:
|
|
393
|
+
installed: operation.creation === "replacement" ? "replaced" : "installed",
|
|
371
394
|
release: build.release,
|
|
372
395
|
timings: {
|
|
373
396
|
fetch: next.fetch,
|
|
@@ -383,11 +406,19 @@ export async function installStandaloneAppSet(options) {
|
|
|
383
406
|
}
|
|
384
407
|
catch (cause) {
|
|
385
408
|
stop.requested = true;
|
|
409
|
+
failures.push(cause);
|
|
386
410
|
throw cause;
|
|
387
411
|
}
|
|
388
412
|
}
|
|
389
413
|
};
|
|
390
|
-
|
|
414
|
+
// Wait for every seed and build to settle before reporting: nothing of this
|
|
415
|
+
// call may still run, or install, after it rejects.
|
|
416
|
+
await Promise.allSettled([
|
|
417
|
+
seedAll(),
|
|
418
|
+
...Array.from({ length: concurrency }, slot),
|
|
419
|
+
]);
|
|
420
|
+
if (failures.length > 0)
|
|
421
|
+
throw failures[0];
|
|
391
422
|
return standaloneAppSet.flatMap((app) => {
|
|
392
423
|
const result = installed.get(app.installationId);
|
|
393
424
|
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>;
|