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