@fungi.computer/oyster 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +1134 -2
- package/dist/agents.d.ts +114 -0
- package/dist/agents.js +322 -0
- package/dist/app-builds.d.ts +210 -0
- package/dist/app-builds.js +499 -0
- package/dist/app-sandbox.d.ts +2 -0
- package/dist/app-sandbox.js +2 -0
- package/dist/app-sources.d.ts +31 -0
- package/dist/app-sources.js +370 -0
- package/dist/app-storage.d.ts +141 -0
- package/dist/app-storage.js +281 -0
- package/dist/apps/gate.d.ts +105 -0
- package/dist/apps/gate.js +273 -0
- package/dist/apps/index.d.ts +5 -0
- package/dist/apps/index.js +5 -0
- package/dist/apps/installation.d.ts +248 -0
- package/dist/apps/installation.js +307 -0
- package/dist/apps/operations.d.ts +8 -0
- package/dist/apps/operations.js +98 -0
- package/dist/apps/tools.d.ts +33 -0
- package/dist/apps/tools.js +17 -0
- package/dist/auth.d.ts +11 -0
- package/dist/auth.js +93 -0
- package/dist/backend-request.d.ts +3 -0
- package/dist/backend-request.js +53 -0
- package/dist/build/artifact-writer.d.ts +40 -0
- package/dist/build/artifact-writer.js +181 -0
- package/dist/build/build-jobs.d.ts +128 -0
- package/dist/build/build-jobs.js +203 -0
- package/dist/build/build-policy.d.ts +10 -0
- package/dist/build/build-policy.js +10 -0
- package/dist/build/build-step.d.ts +37 -0
- package/dist/build/build-step.js +28 -0
- package/dist/build/bundled-html.d.ts +7 -0
- package/dist/build/bundled-html.js +64 -0
- package/dist/build/candidate-builder.d.ts +68 -0
- package/dist/build/candidate-builder.js +91 -0
- package/dist/build/candidate-cli.d.ts +1 -0
- package/dist/build/candidate-cli.js +119 -0
- package/dist/build/candidate-cli.mjs +28170 -0
- package/dist/build/content-objects.d.ts +37 -0
- package/dist/build/content-objects.js +53 -0
- package/dist/build/definition-emission.d.ts +34 -0
- package/dist/build/definition-emission.js +201 -0
- package/dist/build/effect.d.ts +249 -0
- package/dist/build/effect.js +436 -0
- package/dist/build/git-environment.d.ts +9 -0
- package/dist/build/git-environment.js +60 -0
- package/dist/build/html-recipe.d.ts +20 -0
- package/dist/build/html-recipe.js +51 -0
- package/dist/build/local-build.d.ts +67 -0
- package/dist/build/local-build.js +1128 -0
- package/dist/build/snapshot-package-store.d.ts +12 -0
- package/dist/build/snapshot-package-store.js +36 -0
- package/dist/build/snapshot-program-scripts.d.ts +6 -0
- package/dist/build/snapshot-program-scripts.js +2 -0
- package/dist/build/snapshot-programs/snapshot-path.d.ts +2 -0
- package/dist/build/snapshot-programs/snapshot-path.js +6 -0
- package/dist/build/submission-contract.d.ts +828 -0
- package/dist/build/submission-contract.js +180 -0
- package/dist/build/workspace-build.d.ts +55 -0
- package/dist/build/workspace-build.js +200 -0
- package/dist/build-contract.d.ts +7 -0
- package/dist/build-contract.js +7 -0
- package/dist/build-egress.d.ts +62 -0
- package/dist/build-egress.js +148 -0
- package/dist/build-jobs.d.ts +2 -0
- package/dist/build-jobs.js +2 -0
- package/dist/build.d.ts +12 -0
- package/dist/build.js +14 -0
- package/dist/capability-descriptor.d.ts +46 -0
- package/dist/capability-descriptor.js +51 -0
- package/dist/catalog-frame.d.ts +39 -0
- package/dist/catalog-frame.js +72 -0
- package/dist/cause-class.d.ts +19 -0
- package/dist/cause-class.js +54 -0
- package/dist/cloudflare.d.ts +123 -0
- package/dist/cloudflare.js +391 -0
- package/dist/computer-terminal.d.ts +38 -0
- package/dist/computer-terminal.js +410 -0
- package/dist/computer.d.ts +60 -0
- package/dist/computer.js +33 -0
- package/dist/event-recipients.d.ts +57 -0
- package/dist/event-recipients.js +144 -0
- package/dist/facet-invocation.d.ts +4 -0
- package/dist/facet-invocation.js +45 -0
- package/dist/files.d.ts +42 -0
- package/dist/files.js +123 -0
- package/dist/frame-binding-wire-script.d.ts +2 -0
- package/dist/frame-binding-wire-script.js +1 -0
- package/dist/frame-binding-wire.d.ts +28 -0
- package/dist/frame-binding-wire.js +54 -0
- package/dist/git-admission.d.ts +52 -0
- package/dist/git-admission.js +188 -0
- package/dist/git-auth.d.ts +11 -0
- package/dist/git-auth.js +27 -0
- package/dist/git-door.d.ts +35 -0
- package/dist/git-door.js +108 -0
- package/dist/git-journal.d.ts +31 -0
- package/dist/git-journal.js +188 -0
- package/dist/git.d.ts +31 -0
- package/dist/git.js +64 -0
- package/dist/guest-policy.d.ts +9 -0
- package/dist/guest-policy.js +13 -0
- package/dist/host.d.ts +244 -0
- package/dist/host.js +1043 -0
- package/dist/index.d.ts +166 -0
- package/dist/index.js +320 -0
- package/dist/installation-consent.d.ts +79 -0
- package/dist/installation-consent.js +82 -0
- package/dist/internal/background-origins.d.ts +3 -0
- package/dist/internal/background-origins.js +8 -0
- package/dist/internal/delivery.d.ts +12 -0
- package/dist/internal/delivery.js +167 -0
- package/dist/internal/host-gate.d.ts +14 -0
- package/dist/internal/host-gate.js +62 -0
- package/dist/internal/registry-installations.d.ts +6 -0
- package/dist/internal/registry-installations.js +12 -0
- package/dist/invocation.d.ts +251 -0
- package/dist/invocation.js +377 -0
- package/dist/keys.d.ts +48 -0
- package/dist/keys.js +52 -0
- package/dist/mcp-owner.d.ts +27 -0
- package/dist/mcp-owner.js +155 -0
- package/dist/mcp.d.ts +89 -0
- package/dist/mcp.js +429 -0
- package/dist/media.d.ts +23 -0
- package/dist/media.js +12 -0
- package/dist/pack.d.ts +79 -0
- package/dist/pack.js +151 -0
- package/dist/page/capabilities.d.ts +23 -0
- package/dist/page/capabilities.js +256 -0
- package/dist/page/computer-handles.d.ts +79 -0
- package/dist/page/computer-handles.js +44 -0
- package/dist/page/gill-observation.d.ts +4 -0
- package/dist/page/gill-observation.js +31 -0
- package/dist/page/lifetime.d.ts +6 -0
- package/dist/page/lifetime.js +19 -0
- package/dist/page/mount.d.ts +163 -0
- package/dist/page/mount.js +390 -0
- package/dist/page/storage.d.ts +3 -0
- package/dist/page/storage.js +82 -0
- package/dist/page/terminal.d.ts +5 -0
- package/dist/page/terminal.js +7 -0
- package/dist/page/window-launch.d.ts +18 -0
- package/dist/page/window-launch.js +11 -0
- package/dist/page-script.d.ts +14 -0
- package/dist/page-script.js +5 -0
- package/dist/r2-files.d.ts +3 -0
- package/dist/r2-files.js +79 -0
- package/dist/release.d.ts +83 -0
- package/dist/release.js +167 -0
- package/dist/root-data.d.ts +86 -0
- package/dist/root-data.js +69 -0
- package/dist/shell/app-directory.d.ts +542 -0
- package/dist/shell/app-directory.js +199 -0
- package/dist/shell/browser-actions.d.ts +7 -0
- package/dist/shell/browser-actions.js +56 -0
- package/dist/shell/command-host.d.ts +112 -0
- package/dist/shell/command-host.js +502 -0
- package/dist/shell/commands.d.ts +25 -0
- package/dist/shell/commands.js +78 -0
- package/dist/shell/index.d.ts +8 -0
- package/dist/shell/index.js +8 -0
- package/dist/shell/keys.d.ts +41 -0
- package/dist/shell/keys.js +134 -0
- package/dist/shell/shell-module.d.ts +55 -0
- package/dist/shell/shell-module.js +86 -0
- package/dist/shell/window-manager.d.ts +124 -0
- package/dist/shell/window-manager.js +711 -0
- package/dist/shell/workbench-contract.d.ts +68 -0
- package/dist/shell/workbench-contract.js +1 -0
- package/dist/shell/workbench-directory.d.ts +332 -0
- package/dist/shell/workbench-directory.js +35 -0
- package/dist/shell/workbench-host-types.d.ts +96 -0
- package/dist/shell/workbench-host-types.js +1 -0
- package/dist/shell/workbench-host.d.ts +34 -0
- package/dist/shell/workbench-host.js +347 -0
- package/dist/shell/workbench-model.d.ts +192 -0
- package/dist/shell/workbench-model.js +53 -0
- package/dist/shell/workbench.d.ts +4 -0
- package/dist/shell/workbench.js +2 -0
- package/dist/source/fork.d.ts +27 -0
- package/dist/source/fork.js +46 -0
- package/dist/source/index.d.ts +10 -0
- package/dist/source/index.js +10 -0
- package/dist/source/seed-receipt.d.ts +53 -0
- package/dist/source/seed-receipt.js +18 -0
- package/dist/source/team-source.d.ts +32 -0
- package/dist/source/team-source.js +42 -0
- package/dist/source/template-push.d.ts +33 -0
- package/dist/source/template-push.js +223 -0
- package/dist/source/template-repository.d.ts +4 -0
- package/dist/source/template-repository.js +21 -0
- package/dist/source/template-seed.d.ts +68 -0
- package/dist/source/template-seed.js +80 -0
- package/dist/standalone/agent-directory.d.ts +4 -0
- package/dist/standalone/agent-directory.js +4 -0
- package/dist/standalone/agents.d.ts +220 -0
- package/dist/standalone/agents.js +795 -0
- package/dist/standalone/app-builds-binding.d.ts +36 -0
- package/dist/standalone/app-builds-binding.js +119 -0
- package/dist/standalone/app-set.d.ts +91 -0
- package/dist/standalone/app-set.js +395 -0
- package/dist/standalone/app-source.d.ts +41 -0
- package/dist/standalone/app-source.js +59 -0
- package/dist/standalone/builds.d.ts +18 -0
- package/dist/standalone/builds.js +101 -0
- package/dist/standalone/computer-egress.d.ts +37 -0
- package/dist/standalone/computer-egress.js +111 -0
- package/dist/standalone/computer.d.ts +122 -0
- package/dist/standalone/computer.js +714 -0
- package/dist/standalone/install-apps-cli.d.ts +2 -0
- package/dist/standalone/install-apps-cli.js +126 -0
- package/dist/standalone/instance.d.ts +35 -0
- package/dist/standalone/instance.js +74 -0
- package/dist/standalone/media.d.ts +76 -0
- package/dist/standalone/media.js +227 -0
- package/dist/standalone/preview.d.ts +51 -0
- package/dist/standalone/preview.js +167 -0
- package/dist/standalone/registry.d.ts +102 -0
- package/dist/standalone/registry.js +193 -0
- package/dist/standalone/tenancy.d.ts +42 -0
- package/dist/standalone/tenancy.js +241 -0
- package/dist/standalone/worker.d.ts +55 -0
- package/dist/standalone/worker.js +147 -0
- package/dist/standalone/workflow-authority-readers.d.ts +16 -0
- package/dist/standalone/workflow-authority-readers.js +35 -0
- package/dist/standalone/workflow-binding.d.ts +216 -0
- package/dist/standalone/workflow-binding.js +217 -0
- package/dist/standalone/workflow-events.d.ts +46 -0
- package/dist/standalone/workflow-events.js +39 -0
- package/dist/standalone/workflow-git-capture.d.ts +7 -0
- package/dist/standalone/workflow-git-capture.js +62 -0
- package/dist/standalone/workflow-host.d.ts +931 -0
- package/dist/standalone/workflow-host.js +751 -0
- package/dist/standalone/workflow-node-capture.d.ts +13 -0
- package/dist/standalone/workflow-node-capture.js +72 -0
- package/dist/standalone/workflow-root-authority.d.ts +27 -0
- package/dist/standalone/workflow-root-authority.js +87 -0
- package/dist/standalone/workflow-schedules.d.ts +52 -0
- package/dist/standalone/workflow-schedules.js +40 -0
- package/dist/standalone/workflow-trigger-host.d.ts +1694 -0
- package/dist/standalone/workflow-trigger-host.js +52 -0
- package/dist/standalone/workflows.d.ts +657 -0
- package/dist/standalone/workflows.js +554 -0
- package/dist/storage-frame.d.ts +64 -0
- package/dist/storage-frame.js +28 -0
- package/dist/tenancy/auth0.d.ts +64 -0
- package/dist/tenancy/auth0.js +198 -0
- package/dist/tenancy/cloudflare/rate-limit.d.ts +8 -0
- package/dist/tenancy/cloudflare/rate-limit.js +19 -0
- package/dist/tenancy/cloudflare/registry.d.ts +50 -0
- package/dist/tenancy/cloudflare/registry.js +301 -0
- package/dist/tenancy/cloudflare.d.ts +2 -0
- package/dist/tenancy/cloudflare.js +2 -0
- package/dist/tenancy/cold-directory.d.ts +13 -0
- package/dist/tenancy/cold-directory.js +43 -0
- package/dist/tenancy/contracts.d.ts +195 -0
- package/dist/tenancy/contracts.js +1 -0
- package/dist/tenancy/index.d.ts +19 -0
- package/dist/tenancy/index.js +156 -0
- package/dist/tenancy/owner-key.d.ts +26 -0
- package/dist/tenancy/owner-key.js +63 -0
- package/dist/tenancy/oyster.d.ts +4 -0
- package/dist/tenancy/oyster.js +25 -0
- package/dist/tenancy/registry-record.d.ts +7 -0
- package/dist/tenancy/registry-record.js +44 -0
- package/dist/tenancy/registry.d.ts +27 -0
- package/dist/tenancy/registry.js +138 -0
- package/dist/tenancy/resolve.d.ts +7 -0
- package/dist/tenancy/resolve.js +64 -0
- package/dist/tenant.d.ts +81 -0
- package/dist/tenant.js +71 -0
- package/dist/testing/capabilities.d.ts +29 -0
- package/dist/testing/capabilities.js +130 -0
- package/dist/testing/catalog.d.ts +3 -0
- package/dist/testing/catalog.js +53 -0
- package/dist/testing/index.d.ts +10 -0
- package/dist/testing/index.js +10 -0
- package/dist/testing/installation.d.ts +11 -0
- package/dist/testing/installation.js +25 -0
- package/dist/testing/native-worker.d.ts +21 -0
- package/dist/testing/native-worker.js +106 -0
- package/dist/testing/worker.d.ts +18 -0
- package/dist/testing/worker.js +67 -0
- package/dist/testing/workers-runtime.d.ts +24 -0
- package/dist/testing/workers-runtime.js +33 -0
- package/dist/webhook-delivery.d.ts +14 -0
- package/dist/webhook-delivery.js +17 -0
- package/dist/workers-git.d.ts +285 -0
- package/dist/workers-git.js +1174 -0
- package/dist/workers.d.ts +352 -0
- package/dist/workers.js +868 -0
- package/docs/api/README.md +82 -0
- package/docs/api/agents.md +332 -0
- package/docs/api/app-storage.md +209 -0
- package/docs/api/apps.md +1375 -0
- package/docs/api/auth.md +63 -0
- package/docs/api/build-contract.md +1304 -0
- package/docs/api/build.md +1712 -0
- package/docs/api/cloudflare.md +421 -0
- package/docs/api/computer.md +203 -0
- package/docs/api/files.md +117 -0
- package/docs/api/git.md +470 -0
- package/docs/api/host.md +1328 -0
- package/docs/api/index.md +430 -0
- package/docs/api/invocation.md +738 -0
- package/docs/api/keys.md +175 -0
- package/docs/api/mcp.md +301 -0
- package/docs/api/media.md +40 -0
- package/docs/api/modules.md +50 -0
- package/docs/api/pack.md +141 -0
- package/docs/api/page/mount.md +298 -0
- package/docs/api/root-data.md +111 -0
- package/docs/api/shell/app-directory.md +599 -0
- package/docs/api/shell/command-host.md +224 -0
- package/docs/api/shell/commands.md +197 -0
- package/docs/api/shell/keys.md +183 -0
- package/docs/api/shell/shell-module.md +204 -0
- package/docs/api/shell/window-manager.md +267 -0
- package/docs/api/shell/workbench-directory.md +119 -0
- package/docs/api/shell/workbench.md +205 -0
- package/docs/api/shell.md +1915 -0
- package/docs/api/tenancy/auth0.md +110 -0
- package/docs/api/tenancy/cloudflare.md +221 -0
- package/docs/api/tenancy/cold-directory.md +46 -0
- package/docs/api/tenancy/owner-key.md +63 -0
- package/docs/api/tenancy/registry.md +77 -0
- package/docs/api/tenancy/resolve.md +69 -0
- package/docs/api/tenancy.md +430 -0
- package/docs/api/tenant.md +194 -0
- package/docs/api/workers-git.md +1646 -0
- package/docs/api/workers.md +1691 -0
- package/docs/site/agent-quickstart.mdx +346 -0
- package/examples/standalone/worker.ts +7 -0
- package/examples/standalone/wrangler.jsonc +82 -0
- package/package.json +300 -3
package/dist/agents.d.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agents for Apps on a standalone Oyster: one adapter shape and the routes
|
|
3
|
+
* the App page calls. Apps reach agents through Agar's `agent.list`,
|
|
4
|
+
* `agent.manage`, `agent.read` and `agent.send` contracts, so an App written
|
|
5
|
+
* for the Hub (Grove) runs unchanged. Each agent's own transport is
|
|
6
|
+
* Shiitake's HTTP routes (`/sessions…`, `/acp`), served by the adapter.
|
|
7
|
+
*/
|
|
8
|
+
import { Hono } from "hono";
|
|
9
|
+
import type { AppToolPolicy } from "./apps/tools.js";
|
|
10
|
+
import * as Schema from "effect/Schema";
|
|
11
|
+
import { type OysterCapability, type OysterEnv, type OysterPrincipal } from "./host.js";
|
|
12
|
+
import type { HostActor } from "@fungi.computer/puffball/app";
|
|
13
|
+
import type { MemberInboxItem, MemberInboxSession } from "@fungi.computer/puffball/inbox";
|
|
14
|
+
/** One agent as Apps see it, in Agar's `appAgentSchema` shape. */
|
|
15
|
+
export type OysterAgent = Readonly<{
|
|
16
|
+
agentId: string;
|
|
17
|
+
name: string;
|
|
18
|
+
sessionOperations: readonly ("agent.read" | "agent.send" | "agent.manage")[];
|
|
19
|
+
}>;
|
|
20
|
+
/** An Agent owner's Session facts before the member identity and visit are added. */
|
|
21
|
+
export type OysterSessionRow = Omit<Extract<MemberInboxSession, {
|
|
22
|
+
title: string;
|
|
23
|
+
}>, "agentId" | "visitedAt"> | Omit<Extract<MemberInboxSession, {
|
|
24
|
+
state: "unavailable";
|
|
25
|
+
}>, "agentId">;
|
|
26
|
+
/** Both hosts expose the same Inbox item vocabulary. */
|
|
27
|
+
export type OysterInboxItem = MemberInboxItem;
|
|
28
|
+
/** The whole inbox at one revision; the revision only grows. */
|
|
29
|
+
export type OysterInbox = Readonly<{
|
|
30
|
+
items: readonly OysterInboxItem[];
|
|
31
|
+
revision: number;
|
|
32
|
+
}>;
|
|
33
|
+
/** Host-attested source of a policy request; request headers are not this fact. */
|
|
34
|
+
export type AgentPolicyActor = "owner-direct" | Readonly<{
|
|
35
|
+
installationId: string;
|
|
36
|
+
}>;
|
|
37
|
+
/** An App's requested tools await the owner's direct approval. */
|
|
38
|
+
export type PendingAgentPolicy = Readonly<{
|
|
39
|
+
status: "pending";
|
|
40
|
+
revision: string;
|
|
41
|
+
}>;
|
|
42
|
+
/** One remote MCP server connected to an Agent; its bearer token is never returned. */
|
|
43
|
+
export type AgentMcpServer = Readonly<{
|
|
44
|
+
serverId: string;
|
|
45
|
+
/** The guest calls this server's tools as `env.mcp.<name>.<tool>()`. */
|
|
46
|
+
name: string;
|
|
47
|
+
url: string;
|
|
48
|
+
hasBearer: boolean;
|
|
49
|
+
}>;
|
|
50
|
+
/** An owner's request to connect one remote MCP server to an Agent. */
|
|
51
|
+
export type AgentMcpServerInput = typeof agentMcpServerInputSchema.Type;
|
|
52
|
+
/** Why connecting an MCP server was refused. */
|
|
53
|
+
export type AgentMcpServerRefusal = Readonly<{
|
|
54
|
+
error: "not_found" | "duplicate_name" | "limit";
|
|
55
|
+
}>;
|
|
56
|
+
/** At most this many MCP servers per Agent. */
|
|
57
|
+
export declare const maxAgentMcpServers = 16;
|
|
58
|
+
/** Owner input for one MCP server: a guest identifier, an `https:` endpoint and an optional bearer. */
|
|
59
|
+
export declare const agentMcpServerInputSchema: Schema.Struct<{
|
|
60
|
+
readonly name: Schema.String;
|
|
61
|
+
readonly url: Schema.String;
|
|
62
|
+
readonly bearer: Schema.NullOr<Schema.String>;
|
|
63
|
+
}>;
|
|
64
|
+
/** This Agent's App-tool policy, in the shape Apps read it. */
|
|
65
|
+
export type AgentToolPolicyStatus = Readonly<{
|
|
66
|
+
status: "pending" | "approved";
|
|
67
|
+
revision: string;
|
|
68
|
+
}>;
|
|
69
|
+
/** Where an Oyster's agents live and run. */
|
|
70
|
+
export type OysterAgents = Readonly<{
|
|
71
|
+
/** Every agent, oldest first. */
|
|
72
|
+
list(): Promise<readonly OysterAgent[]>;
|
|
73
|
+
/** Create an agent; retrying with the same `requestId` returns the same one. */
|
|
74
|
+
create(name: string, requestId: string, principal: OysterPrincipal, actor: AgentPolicyActor): Promise<OysterAgent>;
|
|
75
|
+
/** Owner-only rename; refuses a name already held by another Agent. */
|
|
76
|
+
rename(agentId: string, name: string, principal: OysterPrincipal): Promise<OysterAgent | Readonly<{
|
|
77
|
+
error: "not_found" | "name_taken" | "not_granted";
|
|
78
|
+
}>>;
|
|
79
|
+
/** This Agent's App-tool policy status, or `null` for no such Agent. */
|
|
80
|
+
policyStatus(agentId: string): Promise<AgentToolPolicyStatus | null>;
|
|
81
|
+
/** Read this Agent's stored App-tool approval; absence requires re-approval. A key's App access is its own grants (Ear Tag), not this policy. */
|
|
82
|
+
appToolPolicy(actor: Extract<HostActor, {
|
|
83
|
+
kind: "agent";
|
|
84
|
+
}>): Promise<AppToolPolicy | null>;
|
|
85
|
+
/** Read the current revision for direct owner approval. */
|
|
86
|
+
policyRevision(agentId: string): Promise<string | null>;
|
|
87
|
+
/** Replace an Agent's tool approval under its authenticated attaching human. */
|
|
88
|
+
approveAppToolPolicy(agentId: string, principal: OysterPrincipal, level: AppToolPolicy["level"], expectedRevision: string | null, actor: AgentPolicyActor): Promise<AppToolPolicy | PendingAgentPolicy | Readonly<{
|
|
89
|
+
error: "not_found" | "revision_conflict" | "not_granted";
|
|
90
|
+
}>>;
|
|
91
|
+
/** An Agent's connected MCP servers, or `null` when there is no such Agent. */
|
|
92
|
+
mcpServers(agentId: string): Promise<readonly AgentMcpServer[] | null>;
|
|
93
|
+
/** Connect one remote MCP server; the Agent's next run sees its tools. */
|
|
94
|
+
addMcpServer(agentId: string, input: AgentMcpServerInput): Promise<AgentMcpServer | AgentMcpServerRefusal>;
|
|
95
|
+
/** Disconnect one MCP server; `false` when the Agent or server is unknown. */
|
|
96
|
+
removeMcpServer(agentId: string, serverId: string): Promise<boolean>;
|
|
97
|
+
/** Serve one Shiitake transport request (`/sessions…`, `/acp`) for an agent. */
|
|
98
|
+
fetch(agentId: string, request: Request): Promise<Response | null>;
|
|
99
|
+
/** This member's Session and Workflow review rows, admitted by the host. */
|
|
100
|
+
inbox(): Promise<OysterInbox>;
|
|
101
|
+
/** Current inbox revision without enumerating Session rows. */
|
|
102
|
+
revision(): Promise<number>;
|
|
103
|
+
/** Record that the member opened a Session. */
|
|
104
|
+
visit(agentId: string, sessionId: string): Promise<boolean>;
|
|
105
|
+
}>;
|
|
106
|
+
/** Agent adapters admitted against each App's declared capabilities. */
|
|
107
|
+
export declare function agentCapabilities(agents: OysterAgents): readonly OysterCapability[];
|
|
108
|
+
/** Host-owned direct approval under the admitted owner; never an App capability. */
|
|
109
|
+
export declare function agentApprovalRoutes(agents: OysterAgents): Hono<OysterEnv>;
|
|
110
|
+
/**
|
|
111
|
+
* Host-owned Agent MCP server custody for the owner directly; never an App
|
|
112
|
+
* capability. Bearer tokens go in and never come back out.
|
|
113
|
+
*/
|
|
114
|
+
export declare function agentMcpServerRoutes(agents: OysterAgents): Hono<OysterEnv>;
|
package/dist/agents.js
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agents for Apps on a standalone Oyster: one adapter shape and the routes
|
|
3
|
+
* the App page calls. Apps reach agents through Agar's `agent.list`,
|
|
4
|
+
* `agent.manage`, `agent.read` and `agent.send` contracts, so an App written
|
|
5
|
+
* for the Hub (Grove) runs unchanged. Each agent's own transport is
|
|
6
|
+
* Shiitake's HTTP routes (`/sessions…`, `/acp`), served by the adapter.
|
|
7
|
+
*/
|
|
8
|
+
import { Hono } from "hono";
|
|
9
|
+
import * as Schema from "effect/Schema";
|
|
10
|
+
import { oysterPrincipalIsOwner, } from "./host.js";
|
|
11
|
+
import { agentList, agentManage, agentRename, agentSessionRead, agentSessionWrite, agentToolPolicy, } from "@fungi.computer/agar/agents";
|
|
12
|
+
/** At most this many MCP servers per Agent. */
|
|
13
|
+
export const maxAgentMcpServers = 16;
|
|
14
|
+
/** Owner input for one MCP server: a guest identifier, an `https:` endpoint and an optional bearer. */
|
|
15
|
+
export const agentMcpServerInputSchema = Schema.Struct({
|
|
16
|
+
name: Schema.String.check(Schema.isPattern(/^[a-z][a-z0-9_]{0,31}$/u)),
|
|
17
|
+
url: Schema.String.check(Schema.isMaxLength(2_048), Schema.makeFilter((value) => {
|
|
18
|
+
if (!URL.canParse(value))
|
|
19
|
+
return "Use an https:// MCP endpoint";
|
|
20
|
+
const url = new URL(value);
|
|
21
|
+
return url.protocol === "https:" &&
|
|
22
|
+
url.username === "" &&
|
|
23
|
+
url.password === "" &&
|
|
24
|
+
url.hash === ""
|
|
25
|
+
? undefined
|
|
26
|
+
: "Use an https:// MCP endpoint without credentials or a fragment";
|
|
27
|
+
})),
|
|
28
|
+
bearer: Schema.NullOr(Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(4_096), Schema.isPattern(/^[\x21-\x7e]+$/u))),
|
|
29
|
+
});
|
|
30
|
+
const createSchema = Schema.Struct({
|
|
31
|
+
name: Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(256)),
|
|
32
|
+
requestId: Schema.String.check(Schema.isPattern(/^[A-Za-z0-9_.:-]{1,256}$/u)),
|
|
33
|
+
});
|
|
34
|
+
const renameSchema = Schema.Struct({
|
|
35
|
+
name: Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(256)),
|
|
36
|
+
});
|
|
37
|
+
const visitSchema = Schema.Struct({
|
|
38
|
+
agentId: Schema.String,
|
|
39
|
+
sessionId: Schema.String,
|
|
40
|
+
});
|
|
41
|
+
const agentIdPattern = /^[a-z0-9_-]{1,64}$/u;
|
|
42
|
+
/** How often an open inbox stream re-reads the revision. */
|
|
43
|
+
const inboxPollMs = 2_000;
|
|
44
|
+
const maxAgentRequestBytes = 512 * 1024;
|
|
45
|
+
function refusal(status, error) {
|
|
46
|
+
return Response.json({ error }, { status });
|
|
47
|
+
}
|
|
48
|
+
/** Agent adapters admitted against each App's declared capabilities. */
|
|
49
|
+
export function agentCapabilities(agents) {
|
|
50
|
+
const routes = createAgentRoutes(agents);
|
|
51
|
+
const shared = { base: "/", routes };
|
|
52
|
+
return [
|
|
53
|
+
{
|
|
54
|
+
...shared,
|
|
55
|
+
id: "agent.list",
|
|
56
|
+
permission: "agent.list",
|
|
57
|
+
version: agentList.version,
|
|
58
|
+
methods: { list: { kind: "json", method: "GET", path: "agents" } },
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
...shared,
|
|
62
|
+
id: "agent.manage",
|
|
63
|
+
permission: "agent.manage",
|
|
64
|
+
version: agentManage.version,
|
|
65
|
+
methods: {
|
|
66
|
+
create: { kind: "json", method: "POST", path: "agents" },
|
|
67
|
+
open: { kind: "transport", method: "ANY", path: "agents/:agentId" },
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
...shared,
|
|
72
|
+
id: "agent.rename",
|
|
73
|
+
permission: "agent.rename",
|
|
74
|
+
version: agentRename.version,
|
|
75
|
+
methods: {
|
|
76
|
+
rename: { kind: "json", method: "PUT", path: "agent-names/:agentId" },
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
...shared,
|
|
81
|
+
id: "agent.toolPolicy",
|
|
82
|
+
permission: "agent.toolPolicy",
|
|
83
|
+
version: agentToolPolicy.version,
|
|
84
|
+
methods: {
|
|
85
|
+
status: {
|
|
86
|
+
kind: "json",
|
|
87
|
+
method: "GET",
|
|
88
|
+
path: "agent-policies/:agentId",
|
|
89
|
+
},
|
|
90
|
+
approve: {
|
|
91
|
+
kind: "json",
|
|
92
|
+
method: "PUT",
|
|
93
|
+
path: "agent-policies/:agentId",
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
...shared,
|
|
99
|
+
id: "agent.read",
|
|
100
|
+
permission: "agent.read",
|
|
101
|
+
version: agentSessionRead.version,
|
|
102
|
+
methods: {
|
|
103
|
+
open: { kind: "transport", method: "GET", path: "agents/:agentId" },
|
|
104
|
+
openInbox: { kind: "transport", method: "GET", path: "member" },
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
...shared,
|
|
109
|
+
id: "agent.send",
|
|
110
|
+
permission: "agent.send",
|
|
111
|
+
version: agentSessionWrite.version,
|
|
112
|
+
methods: {
|
|
113
|
+
open: { kind: "transport", method: "ANY", path: "agents/:agentId" },
|
|
114
|
+
openInbox: { kind: "transport", method: "ANY", path: "member" },
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
];
|
|
118
|
+
}
|
|
119
|
+
function createAgentRoutes(agents) {
|
|
120
|
+
const routes = new Hono();
|
|
121
|
+
routes.get("/agents", async () => Response.json(await agents.list()));
|
|
122
|
+
routes.post("/agents", async (context) => {
|
|
123
|
+
const { principal } = context.env;
|
|
124
|
+
if (!oysterPrincipalIsOwner(principal))
|
|
125
|
+
return refusal(403, "Only the owner creates agents");
|
|
126
|
+
const body = Schema.decodeUnknownOption(createSchema)(await context.req.json());
|
|
127
|
+
if (body._tag === "None")
|
|
128
|
+
return refusal(400, "Name the agent");
|
|
129
|
+
// The owner's own authenticated request creates this Agent; its App tools
|
|
130
|
+
// are approved the same way direct owner approval is, with no second path.
|
|
131
|
+
return Response.json(await agents.create(body.value.name, body.value.requestId, principal, "owner-direct"));
|
|
132
|
+
});
|
|
133
|
+
routes.put("/agent-names/:agentId", async (context) => {
|
|
134
|
+
const { principal } = context.env;
|
|
135
|
+
if (!oysterPrincipalIsOwner(principal))
|
|
136
|
+
return refusal(403, "Only the owner renames agents");
|
|
137
|
+
const agentId = context.req.param("agentId");
|
|
138
|
+
if (!agentIdPattern.test(agentId))
|
|
139
|
+
return refusal(404, "No such agent");
|
|
140
|
+
const body = Schema.decodeUnknownOption(renameSchema)(await context.req.json());
|
|
141
|
+
if (body._tag === "None")
|
|
142
|
+
return refusal(400, "Name the agent");
|
|
143
|
+
const result = await agents.rename(agentId, body.value.name, principal);
|
|
144
|
+
if ("error" in result)
|
|
145
|
+
return refusal(result.error === "not_found"
|
|
146
|
+
? 404
|
|
147
|
+
: result.error === "name_taken"
|
|
148
|
+
? 409
|
|
149
|
+
: 403, result.error);
|
|
150
|
+
return Response.json(result);
|
|
151
|
+
});
|
|
152
|
+
routes.get("/agent-policies/:agentId", async (context) => {
|
|
153
|
+
const agentId = context.req.param("agentId");
|
|
154
|
+
if (!agentIdPattern.test(agentId))
|
|
155
|
+
return refusal(404, "No such agent");
|
|
156
|
+
const status = await agents.policyStatus(agentId);
|
|
157
|
+
return status === null
|
|
158
|
+
? refusal(404, "No such agent")
|
|
159
|
+
: Response.json(status);
|
|
160
|
+
});
|
|
161
|
+
routes.put("/agent-policies/:agentId", async (context) => approvePolicy(agents, context.env.principal, "owner-direct", context.req.param("agentId"), await context.req.text()));
|
|
162
|
+
routes.all("/agents/:agentId/*", async (context) => {
|
|
163
|
+
const agentId = context.req.param("agentId");
|
|
164
|
+
if (!agentIdPattern.test(agentId))
|
|
165
|
+
return refusal(404, "No such agent");
|
|
166
|
+
const incoming = context.req.raw;
|
|
167
|
+
const url = new URL(incoming.url);
|
|
168
|
+
const path = url.pathname.slice(`/agents/${agentId}`.length);
|
|
169
|
+
if (!(path === "/acp" ||
|
|
170
|
+
path === "/sessions" ||
|
|
171
|
+
path.startsWith("/sessions/")))
|
|
172
|
+
return refusal(404, "Not found");
|
|
173
|
+
const length = Number(incoming.headers.get("content-length") ?? "0");
|
|
174
|
+
if (length > maxAgentRequestBytes)
|
|
175
|
+
return refusal(413, "Request too large");
|
|
176
|
+
const target = new URL(`https://agent.internal${path}${url.search}`);
|
|
177
|
+
const response = await agents.fetch(agentId, new Request(target, incoming));
|
|
178
|
+
return response ?? refusal(404, "No such agent");
|
|
179
|
+
});
|
|
180
|
+
routes.get("/member/inbox", async () => {
|
|
181
|
+
const inbox = await agents.inbox();
|
|
182
|
+
return Response.json({
|
|
183
|
+
items: inbox.items,
|
|
184
|
+
next: null,
|
|
185
|
+
cursor: inbox.revision,
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
routes.get("/member/inbox/notices", () => Response.json({ items: [], next: null }));
|
|
189
|
+
routes.post("/member/inbox/visits", async (context) => {
|
|
190
|
+
const body = Schema.decodeUnknownOption(visitSchema)(await context.req.json());
|
|
191
|
+
if (body._tag === "None")
|
|
192
|
+
return refusal(400, "Invalid visit");
|
|
193
|
+
return (await agents.visit(body.value.agentId, body.value.sessionId))
|
|
194
|
+
? new Response(null, { status: 204 })
|
|
195
|
+
: refusal(404, "No such Session");
|
|
196
|
+
});
|
|
197
|
+
// The feed is small (one person's agents), so a change is announced as a
|
|
198
|
+
// `reset` and the client re-reads the whole inbox.
|
|
199
|
+
routes.get("/member/inbox/events", (context) => {
|
|
200
|
+
const after = Number(context.req.query("after") ?? "0");
|
|
201
|
+
const signal = context.req.raw.signal;
|
|
202
|
+
const encoder = new TextEncoder();
|
|
203
|
+
let revision = after;
|
|
204
|
+
const stream = new ReadableStream({
|
|
205
|
+
async start(controller) {
|
|
206
|
+
controller.enqueue(encoder.encode(": open\n\n"));
|
|
207
|
+
while (!signal.aborted) {
|
|
208
|
+
const current = await agents.revision();
|
|
209
|
+
if (current !== revision) {
|
|
210
|
+
revision = current;
|
|
211
|
+
controller.enqueue(encoder.encode("event: reset\ndata: {}\n\n"));
|
|
212
|
+
}
|
|
213
|
+
else
|
|
214
|
+
controller.enqueue(encoder.encode(": keep-alive\n\n"));
|
|
215
|
+
await new Promise((resolve) => setTimeout(resolve, inboxPollMs));
|
|
216
|
+
}
|
|
217
|
+
controller.close();
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
return new Response(stream, {
|
|
221
|
+
headers: {
|
|
222
|
+
"Content-Type": "text/event-stream",
|
|
223
|
+
"Cache-Control": "no-store",
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
return routes;
|
|
228
|
+
}
|
|
229
|
+
async function approvePolicy(agents, principal, actor, agentId, body) {
|
|
230
|
+
if (!oysterPrincipalIsOwner(principal))
|
|
231
|
+
return refusal(403, "Only the owner approves Agent tools");
|
|
232
|
+
const input = Schema.decodeUnknownOption(Schema.fromJsonString(Schema.Struct({
|
|
233
|
+
level: Schema.Literals(["read", "write"]),
|
|
234
|
+
expectedRevision: Schema.NullOr(Schema.NonEmptyString),
|
|
235
|
+
})), { onExcessProperty: "error" })(body);
|
|
236
|
+
if (input._tag === "None")
|
|
237
|
+
return refusal(400, "Choose read or write tools");
|
|
238
|
+
if (!agentIdPattern.test(agentId) ||
|
|
239
|
+
!(await agents.list()).some((agent) => agent.agentId === agentId))
|
|
240
|
+
return refusal(404, "No such agent");
|
|
241
|
+
const result = await agents.approveAppToolPolicy(agentId, principal, input.value.level, input.value.expectedRevision, actor);
|
|
242
|
+
const failureStatuses = {
|
|
243
|
+
not_found: 404,
|
|
244
|
+
not_granted: 403,
|
|
245
|
+
revision_conflict: 409,
|
|
246
|
+
};
|
|
247
|
+
return "error" in result
|
|
248
|
+
? refusal(failureStatuses[result.error], result.error)
|
|
249
|
+
: Response.json(result);
|
|
250
|
+
}
|
|
251
|
+
/** Host-owned direct approval under the admitted owner; never an App capability. */
|
|
252
|
+
export function agentApprovalRoutes(agents) {
|
|
253
|
+
return new Hono()
|
|
254
|
+
.get("/api/agent-policies/:agentId", async (context) => {
|
|
255
|
+
if (context.req.raw.headers.has("X-Oyster-Installation"))
|
|
256
|
+
return refusal(403, "Owner direct only");
|
|
257
|
+
if (!oysterPrincipalIsOwner(context.get("principal")))
|
|
258
|
+
return refusal(403, "Owner only");
|
|
259
|
+
const agentId = context.req.param("agentId");
|
|
260
|
+
if (!(await agents.list()).some((agent) => agent.agentId === agentId))
|
|
261
|
+
return refusal(404, "No such agent");
|
|
262
|
+
return Response.json({
|
|
263
|
+
expectedRevision: await agents.policyRevision(agentId),
|
|
264
|
+
});
|
|
265
|
+
})
|
|
266
|
+
.put("/api/agent-policies/:agentId", async (context) => context.req.raw.headers.has("X-Oyster-Installation")
|
|
267
|
+
? refusal(403, "Missing admitted Agent installation")
|
|
268
|
+
: approvePolicy(agents, context.get("principal"), "owner-direct", context.req.param("agentId"), await context.req.text()));
|
|
269
|
+
}
|
|
270
|
+
const serverIdPattern = /^[a-z0-9_-]{1,64}$/u;
|
|
271
|
+
const mcpRefusalStatus = { not_found: 404, duplicate_name: 409, limit: 409 };
|
|
272
|
+
/**
|
|
273
|
+
* Host-owned Agent MCP server custody for the owner directly; never an App
|
|
274
|
+
* capability. Bearer tokens go in and never come back out.
|
|
275
|
+
*/
|
|
276
|
+
export function agentMcpServerRoutes(agents) {
|
|
277
|
+
const routes = new Hono();
|
|
278
|
+
const ownerOnly = async (context, next) => {
|
|
279
|
+
if (context.req.raw.headers.has("X-Oyster-Installation"))
|
|
280
|
+
return refusal(403, "Owner direct only");
|
|
281
|
+
if (!oysterPrincipalIsOwner(context.get("principal")))
|
|
282
|
+
return refusal(403, "Only the owner connects MCP servers");
|
|
283
|
+
await next();
|
|
284
|
+
return undefined;
|
|
285
|
+
};
|
|
286
|
+
routes.use("/api/agent-mcp-servers", ownerOnly);
|
|
287
|
+
routes.use("/api/agent-mcp-servers/*", ownerOnly);
|
|
288
|
+
// One owner read for the settings panel: every Agent with its servers.
|
|
289
|
+
routes.get("/api/agent-mcp-servers", async () => {
|
|
290
|
+
const listed = await agents.list();
|
|
291
|
+
const rows = await Promise.all(listed.map(async (agent) => ({
|
|
292
|
+
agentId: agent.agentId,
|
|
293
|
+
name: agent.name,
|
|
294
|
+
servers: (await agents.mcpServers(agent.agentId)) ?? [],
|
|
295
|
+
})));
|
|
296
|
+
return Response.json({ agents: rows });
|
|
297
|
+
});
|
|
298
|
+
routes.post("/api/agent-mcp-servers/:agentId", async (context) => {
|
|
299
|
+
if (!agentIdPattern.test(context.req.param("agentId")))
|
|
300
|
+
return refusal(404, "No such agent");
|
|
301
|
+
const length = Number(context.req.header("content-length") ?? "0");
|
|
302
|
+
if (length > 16 * 1024)
|
|
303
|
+
return refusal(413, "Request too large");
|
|
304
|
+
const input = Schema.decodeUnknownOption(Schema.fromJsonString(agentMcpServerInputSchema), { onExcessProperty: "error" })(await context.req.text());
|
|
305
|
+
if (input._tag === "None")
|
|
306
|
+
return refusal(400, "Name the server (lowercase letters, digits, _) and give its https:// endpoint");
|
|
307
|
+
const added = await agents.addMcpServer(context.req.param("agentId"), input.value);
|
|
308
|
+
return "error" in added
|
|
309
|
+
? refusal(mcpRefusalStatus[added.error], added.error)
|
|
310
|
+
: Response.json(added, { status: 201 });
|
|
311
|
+
});
|
|
312
|
+
routes.delete("/api/agent-mcp-servers/:agentId/:serverId", async (context) => {
|
|
313
|
+
const serverId = context.req.param("serverId");
|
|
314
|
+
if (!agentIdPattern.test(context.req.param("agentId")) ||
|
|
315
|
+
!serverIdPattern.test(serverId))
|
|
316
|
+
return refusal(404, "No such server");
|
|
317
|
+
return (await agents.removeMcpServer(context.req.param("agentId"), serverId))
|
|
318
|
+
? new Response(null, { status: 204 })
|
|
319
|
+
: refusal(404, "No such server");
|
|
320
|
+
});
|
|
321
|
+
return routes;
|
|
322
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Oyster's App build owner: an App's registered source, its build jobs on the
|
|
3
|
+
* host's own Computer, and the Releases those builds produce.
|
|
4
|
+
*
|
|
5
|
+
* A build runs the shared job lifecycle (`build/build-jobs.ts`) and the fixed
|
|
6
|
+
* captured builder command on one Computer. The Computer holds no key: its
|
|
7
|
+
* clone is admitted at egress for exactly the job's one repository (read
|
|
8
|
+
* only), and its candidate upload for exactly the job. A source is either a
|
|
9
|
+
* repository in an installation's Git (a fork, through the Git door) or the
|
|
10
|
+
* App's own published template, built only at a recorded seed revision of
|
|
11
|
+
* its pinned recipe (no installed App is needed). The build streams each
|
|
12
|
+
* built object into the host's artifact store itself, through the egress, and
|
|
13
|
+
* then submits a small manifest naming them. This owner only records facts:
|
|
14
|
+
* it checks each named object's stored size and digest, writes the asset
|
|
15
|
+
* manifest and App root in Oyster's one immutable artifact representation
|
|
16
|
+
* (the same objects a packed upload writes), verifies the Release like any
|
|
17
|
+
* installed one, and records it; installing it is a separate owner act.
|
|
18
|
+
*/
|
|
19
|
+
import * as Schema from "effect/Schema";
|
|
20
|
+
import type { WatchdogSqliteOwner } from "@fungi.computer/watchdog";
|
|
21
|
+
import { type Release } from "@fungi.computer/puffball";
|
|
22
|
+
import { type TeamAppCandidateManifest } from "./build/submission-contract.js";
|
|
23
|
+
import { type ArtifactHeads, type ArtifactStore } from "./release.js";
|
|
24
|
+
import type { PublisherTemplateSeedReceipts } from "./source/seed-receipt.js";
|
|
25
|
+
/** A Computer's whole-build budget: a cold build on a 6 GiB Computer measured 185-270 s. */
|
|
26
|
+
export declare const oysterAppBuildTimeoutMs = 900000;
|
|
27
|
+
/** Host names the Computer's egress serves for a build; never public hosts. */
|
|
28
|
+
export declare const oysterBuildGitHost = "git.team.internal";
|
|
29
|
+
export declare const oysterBuildReportHost = "build.team.internal";
|
|
30
|
+
/** Where an App's source is cloned from. */
|
|
31
|
+
export declare const oysterBuildLocationSchema: Schema.Union<readonly [Schema.Struct<{
|
|
32
|
+
readonly kind: Schema.Literal<"installation">;
|
|
33
|
+
/** The installation whose Git holds the repository (Bonsai for a fork). */
|
|
34
|
+
readonly installationId: Schema.String;
|
|
35
|
+
readonly repository: Schema.String;
|
|
36
|
+
}>, Schema.Struct<{
|
|
37
|
+
readonly kind: Schema.Literal<"template">;
|
|
38
|
+
}>]>;
|
|
39
|
+
export type OysterBuildLocation = Schema.Schema.Type<typeof oysterBuildLocationSchema>;
|
|
40
|
+
/** An App's registered build source: where it is cloned and its pinned recipe. */
|
|
41
|
+
export declare const oysterBuildSourceSchema: Schema.Struct<{
|
|
42
|
+
readonly appId: Schema.refine<import("@fungi.computer/puffball").AppId, Schema.String>;
|
|
43
|
+
readonly location: Schema.Union<readonly [Schema.Struct<{
|
|
44
|
+
readonly kind: Schema.Literal<"installation">;
|
|
45
|
+
/** The installation whose Git holds the repository (Bonsai for a fork). */
|
|
46
|
+
readonly installationId: Schema.String;
|
|
47
|
+
readonly repository: Schema.String;
|
|
48
|
+
}>, Schema.Struct<{
|
|
49
|
+
readonly kind: Schema.Literal<"template">;
|
|
50
|
+
}>]>;
|
|
51
|
+
readonly recipe: Schema.Struct<{
|
|
52
|
+
readonly kind: Schema.optional<Schema.Literals<readonly ["bundled-html", "source-html"]>>;
|
|
53
|
+
readonly bundle: Schema.Literal<true>;
|
|
54
|
+
readonly entry: Schema.String;
|
|
55
|
+
readonly format: Schema.Literals<readonly ["html", "esm", "assets"]>;
|
|
56
|
+
readonly assets: Schema.optional<Schema.Struct<{
|
|
57
|
+
readonly maxManifestBytes: Schema.Number;
|
|
58
|
+
readonly maxObjects: Schema.Number;
|
|
59
|
+
readonly maxObjectBytes: Schema.Number;
|
|
60
|
+
readonly maxTotalBytes: Schema.Number;
|
|
61
|
+
}>>;
|
|
62
|
+
readonly assetBase: Schema.optionalKey<Schema.String>;
|
|
63
|
+
readonly toolchain: Schema.$Record<Schema.String, Schema.String>;
|
|
64
|
+
readonly maxOutputBytes: Schema.Number;
|
|
65
|
+
readonly platform: Schema.Literals<readonly ["browser", "neutral"]>;
|
|
66
|
+
readonly backend: Schema.Union<readonly [Schema.Null, Schema.Struct<{
|
|
67
|
+
readonly entry: Schema.optional<Schema.String>;
|
|
68
|
+
readonly runtime: Schema.Struct<{
|
|
69
|
+
readonly kind: Schema.Literal<"durable-facet">;
|
|
70
|
+
readonly stateSchema: Schema.Number;
|
|
71
|
+
}>;
|
|
72
|
+
readonly compatibilityDate: Schema.String;
|
|
73
|
+
readonly exportName: Schema.String;
|
|
74
|
+
}>]>;
|
|
75
|
+
readonly target: Schema.Tuple<readonly [Schema.Literal<"es2022">]>;
|
|
76
|
+
}>;
|
|
77
|
+
readonly buildRecipeDigest: Schema.refine<import("@fungi.computer/puffball").Sha256Digest, Schema.String>;
|
|
78
|
+
}>;
|
|
79
|
+
export type OysterBuildSource = Schema.Schema.Type<typeof oysterBuildSourceSchema>;
|
|
80
|
+
/** What an owner sees of one build. */
|
|
81
|
+
export type OysterBuildView = Readonly<{
|
|
82
|
+
jobId: string;
|
|
83
|
+
appId: string;
|
|
84
|
+
releaseId: string;
|
|
85
|
+
sourceRevision: string;
|
|
86
|
+
status: "running" | "succeeded" | "failed" | "revoked";
|
|
87
|
+
detail: string | null;
|
|
88
|
+
release: Release | null;
|
|
89
|
+
}>;
|
|
90
|
+
export type OysterBuildComputer = Readonly<{
|
|
91
|
+
/** Run the fixed build command; the Computer re-admits the job first.
|
|
92
|
+
* A missing `output` means no terminal result was established: the
|
|
93
|
+
* command may still be running, so it is never read as an exit. */
|
|
94
|
+
runBuild(input: {
|
|
95
|
+
jobId: string;
|
|
96
|
+
command: string;
|
|
97
|
+
timeoutMs: number;
|
|
98
|
+
}): Promise<Readonly<{
|
|
99
|
+
output?: Readonly<{
|
|
100
|
+
exitCode: number | null;
|
|
101
|
+
stderr?: string;
|
|
102
|
+
}> | null;
|
|
103
|
+
}>>;
|
|
104
|
+
}>;
|
|
105
|
+
export type OysterAppBuildOptions = Readonly<{
|
|
106
|
+
owner: WatchdogSqliteOwner;
|
|
107
|
+
/** The tenant the jobs belong to (the job lifecycle's team). */
|
|
108
|
+
tenantId: string;
|
|
109
|
+
/** The fixed Computer this host builds on, and its Durable Object id. */
|
|
110
|
+
computer: Readonly<{
|
|
111
|
+
name: string;
|
|
112
|
+
objectId: string;
|
|
113
|
+
port: OysterBuildComputer;
|
|
114
|
+
}>;
|
|
115
|
+
/** The host's artifact store; a build's objects are already in it. */
|
|
116
|
+
artifacts: ArtifactStore & ArtifactHeads;
|
|
117
|
+
/** The seed receipts a template build must match. */
|
|
118
|
+
templates: Pick<PublisherTemplateSeedReceipts, "read">;
|
|
119
|
+
now?: () => number;
|
|
120
|
+
}>;
|
|
121
|
+
/**
|
|
122
|
+
* The SQLite tables one Oyster build owner keeps. Creation and any carrying
|
|
123
|
+
* forward of retained facts run as one transaction of the owner's storage, so
|
|
124
|
+
* an interrupted conversion leaves the facts as they were for a retry.
|
|
125
|
+
*/
|
|
126
|
+
export declare function migrateOysterAppBuilds(owner: WatchdogSqliteOwner): void;
|
|
127
|
+
/** The build owner over one tenant's SQLite, Computer and artifact store. */
|
|
128
|
+
export declare function createOysterAppBuilds(options: OysterAppBuildOptions): Readonly<{
|
|
129
|
+
/** Record (or replace) an App's build source; the caller is the owner. */
|
|
130
|
+
registerSource(source: OysterBuildSource): OysterBuildSource;
|
|
131
|
+
readSource: (appId: string) => OysterBuildSource | null;
|
|
132
|
+
/**
|
|
133
|
+
* Mint one build job for an App's registered source at one exact commit.
|
|
134
|
+
* A template source builds only a seed revision recorded for its pinned
|
|
135
|
+
* recipe.
|
|
136
|
+
* Answers the job and its one run, which the caller keeps alive (the
|
|
137
|
+
* upload grant exists only inside that run's fixed command).
|
|
138
|
+
*/
|
|
139
|
+
start(input: {
|
|
140
|
+
appId: string;
|
|
141
|
+
sourceRevision: string;
|
|
142
|
+
requestedBy: string;
|
|
143
|
+
}): Promise<Readonly<{
|
|
144
|
+
build: OysterBuildView;
|
|
145
|
+
run: () => Promise<OysterBuildView>;
|
|
146
|
+
}>>;
|
|
147
|
+
read(jobId: string): OysterBuildView | null;
|
|
148
|
+
/** A built Release by id, for an owner to install or select. */
|
|
149
|
+
release(releaseId: string): Release | null;
|
|
150
|
+
/** May this Computer execute this job now? Re-checked by the Computer. */
|
|
151
|
+
admitExecution(jobId: string, computerObjectId: string): boolean;
|
|
152
|
+
/** The live job a build report from this Computer may speak for. */
|
|
153
|
+
admitBuild(jobId: string, computerObjectId: string): Readonly<{
|
|
154
|
+
credentialId: string;
|
|
155
|
+
}> | null;
|
|
156
|
+
/**
|
|
157
|
+
* The clone credential the egress presents for this Computer's live job
|
|
158
|
+
* on exactly this repository, read only; null when no job admits it.
|
|
159
|
+
*/
|
|
160
|
+
gitPassFor(computerObjectId: string, target: Readonly<{
|
|
161
|
+
installationId: string;
|
|
162
|
+
repository: string;
|
|
163
|
+
scope: "read" | "write";
|
|
164
|
+
}>): string | null;
|
|
165
|
+
/** Admit a presented clone credential at the Git door: one repository, read. */
|
|
166
|
+
admitGitPass(pass: string, target: Readonly<{
|
|
167
|
+
installationId: string;
|
|
168
|
+
repository: string;
|
|
169
|
+
scope: "read" | "write";
|
|
170
|
+
}>): Readonly<{
|
|
171
|
+
requestedBy: string;
|
|
172
|
+
}> | null;
|
|
173
|
+
/**
|
|
174
|
+
* May this Computer's clone read this template repository now? Only while
|
|
175
|
+
* a live template job on this Computer names it.
|
|
176
|
+
*/
|
|
177
|
+
admitTemplateClone(computerObjectId: string, repository: string): boolean;
|
|
178
|
+
/** A reported artifact or status for a live job. */
|
|
179
|
+
record(event: Readonly<{
|
|
180
|
+
kind: "build-artifact";
|
|
181
|
+
jobId: string;
|
|
182
|
+
credentialId: string;
|
|
183
|
+
digest: string;
|
|
184
|
+
bytes: number;
|
|
185
|
+
}> | Readonly<{
|
|
186
|
+
kind: "build-status";
|
|
187
|
+
jobId: string;
|
|
188
|
+
credentialId: string;
|
|
189
|
+
status: "succeeded" | "failed";
|
|
190
|
+
detail: string | null;
|
|
191
|
+
}>): void;
|
|
192
|
+
/**
|
|
193
|
+
* May this live job's build upload one object now, and how large? The
|
|
194
|
+
* grant is the one inside the job's fixed command; the bound is its
|
|
195
|
+
* recipe's (`pinnedRecipeObjectBound`).
|
|
196
|
+
*/
|
|
197
|
+
admitObjectUpload(jobId: string, computerObjectId: string, grant: string): Promise<Readonly<{
|
|
198
|
+
maxObjectBytes: number;
|
|
199
|
+
}> | null>;
|
|
200
|
+
/**
|
|
201
|
+
* The Computer's candidate manifest for a live job: checked against the
|
|
202
|
+
* job and its pinned recipe, each named object found stored with its
|
|
203
|
+
* size and verified digest (one at a time; only the backend descriptor
|
|
204
|
+
* is read), its asset manifest and App root written, verified as an
|
|
205
|
+
* installable Release and recorded. An over-policy candidate stores
|
|
206
|
+
* nothing.
|
|
207
|
+
*/
|
|
208
|
+
submitCandidate(jobId: string, manifest: TeamAppCandidateManifest, grant: string, credentialId: string): Promise<Response>;
|
|
209
|
+
}>;
|
|
210
|
+
export type OysterAppBuilds = ReturnType<typeof createOysterAppBuilds>;
|