@fungi.computer/oyster 0.0.0-stage → 0.0.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/LICENSE +21 -0
- package/README.md +1082 -2
- package/dist/agents.d.ts +114 -0
- package/dist/agents.js +322 -0
- package/dist/app-builds.d.ts +151 -0
- package/dist/app-builds.js +355 -0
- package/dist/app-sandbox.d.ts +2 -0
- package/dist/app-sandbox.js +2 -0
- package/dist/app-sources.d.ts +46 -0
- package/dist/app-sources.js +335 -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/build-jobs.d.ts +128 -0
- package/dist/build/build-jobs.js +197 -0
- package/dist/build/build-step.d.ts +26 -0
- package/dist/build/build-step.js +20 -0
- package/dist/build/bundled-html.d.ts +7 -0
- package/dist/build/bundled-html.js +64 -0
- package/dist/build/definition-emission.d.ts +34 -0
- package/dist/build/definition-emission.js +201 -0
- package/dist/build/effect.d.ts +231 -0
- package/dist/build/effect.js +396 -0
- package/dist/build/git-environment.d.ts +6 -0
- package/dist/build/git-environment.js +48 -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 +57 -0
- package/dist/build/local-build.js +1078 -0
- package/dist/build/r2-artifact-writer.d.ts +31 -0
- package/dist/build/r2-artifact-writer.js +179 -0
- package/dist/build/snapshot-package-store.d.ts +2 -0
- package/dist/build/snapshot-package-store.js +21 -0
- package/dist/build/snapshot-program-scripts.d.ts +6 -0
- package/dist/build/snapshot-program-scripts.js +2 -0
- package/dist/build/submission-contract.d.ts +384 -0
- package/dist/build/submission-contract.js +70 -0
- package/dist/build/workspace-build.d.ts +50 -0
- package/dist/build/workspace-build.js +156 -0
- package/dist/build-contract.d.ts +4 -0
- package/dist/build-contract.js +4 -0
- package/dist/build-jobs.d.ts +2 -0
- package/dist/build-jobs.js +2 -0
- package/dist/build.d.ts +11 -0
- package/dist/build.js +13 -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 +143 -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 +76 -0
- package/dist/pack.js +134 -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/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-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 +72 -0
- package/dist/release.js +164 -0
- package/dist/root-data.d.ts +86 -0
- package/dist/root-data.js +69 -0
- package/dist/shell/app-directory.d.ts +537 -0
- package/dist/shell/app-directory.js +195 -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 +31 -0
- package/dist/shell/shell-module.js +37 -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-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 +11 -0
- package/dist/source/index.js +11 -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-repository.d.ts +2 -0
- package/dist/source/template-repository.js +8 -0
- package/dist/source/template-write.d.ts +69 -0
- package/dist/source/template-write.js +344 -0
- package/dist/storage-frame.d.ts +64 -0
- package/dist/storage-frame.js +28 -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 +866 -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 +507 -0
- package/docs/api/build.md +1476 -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 +42 -0
- package/docs/api/pack.md +138 -0
- package/docs/api/page/mount.md +298 -0
- package/docs/api/root-data.md +111 -0
- package/docs/api/shell/app-directory.md +332 -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 +143 -0
- package/docs/api/shell/window-manager.md +267 -0
- package/docs/api/shell/workbench.md +186 -0
- package/docs/api/shell.md +1788 -0
- package/docs/api/tenant.md +194 -0
- package/docs/api/workers-git.md +1527 -0
- package/docs/api/workers.md +1678 -0
- package/docs/media/architecture.mmd +32 -0
- package/docs/media/architecture.png +0 -0
- package/docs/media/bonsai-network.png +0 -0
- package/docs/media/bonsai-repository.png +0 -0
- package/docs/site/agent-quickstart.mdx +349 -0
- package/examples/git/worker.ts +39 -0
- package/examples/git/wrangler.jsonc +32 -0
- package/examples/standalone/worker.ts +14 -0
- package/examples/standalone/wrangler.jsonc +21 -0
- package/package.json +249 -3
package/dist/mcp.d.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { type AppToolPolicy } from "./apps/tools.js";
|
|
2
|
+
import type { DeclaredOperations, Installation } from "./apps/installation.js";
|
|
3
|
+
import { type CapabilitySource, type Grants } from "@fungi.computer/mycelium";
|
|
4
|
+
import { appToolSchemas } from "@fungi.computer/forage";
|
|
5
|
+
import type { OysterHost, OysterPrincipal } from "./host.js";
|
|
6
|
+
import type { McpPrincipal, McpCatalogSnapshot } from "@fungi.computer/mycelium/mcp";
|
|
7
|
+
import { type VerifiedRelease } from "./release.js";
|
|
8
|
+
/** An installation ID as a guest binding name: `spawn-git` becomes `SPAWN_GIT`. */
|
|
9
|
+
export declare function appBindingName(installationId: string): string;
|
|
10
|
+
/** Why an App method call from the MCP catalog was refused. */
|
|
11
|
+
export declare class AppMethodRefused extends Error {
|
|
12
|
+
readonly code: string;
|
|
13
|
+
readonly name = "AppMethodRefused";
|
|
14
|
+
/** `detail` is the App's own caller-facing refusal message, when it gave one. */
|
|
15
|
+
constructor(code: string, detail?: string);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The App-tool access one caller's catalog capture used, re-checked at call
|
|
19
|
+
* time against a fresh read. An Agent's policy is one blanket level for
|
|
20
|
+
* every installation (`host.appToolPolicy`); a key's is its own grants,
|
|
21
|
+
* resolved per installation (`keyAppAccess`) -- an installation the key has
|
|
22
|
+
* no grant for is captured as absent, never merely refused, so it is never
|
|
23
|
+
* listed at all.
|
|
24
|
+
*/
|
|
25
|
+
export type OysterCallerAccess = Readonly<{
|
|
26
|
+
kind: "agent";
|
|
27
|
+
policy: AppToolPolicy;
|
|
28
|
+
}> | Readonly<{
|
|
29
|
+
kind: "key";
|
|
30
|
+
keyId: string;
|
|
31
|
+
byInstallation: ReadonlyMap<string, AppToolPolicy>;
|
|
32
|
+
}> | null;
|
|
33
|
+
/** What one Release contributes to every MCP catalog: its verified build,
|
|
34
|
+
* its declared operations and its compiled tool schemas. */
|
|
35
|
+
export type ReleaseSurface = Readonly<{
|
|
36
|
+
release: VerifiedRelease;
|
|
37
|
+
api: NonNullable<VerifiedRelease["api"]>;
|
|
38
|
+
declaredOperations: DeclaredOperations;
|
|
39
|
+
toolSchemas: ReturnType<typeof appToolSchemas>;
|
|
40
|
+
}>;
|
|
41
|
+
/**
|
|
42
|
+
* Per-host memo of each installed Release's catalog surface. A Release's
|
|
43
|
+
* artifacts are immutable and content-addressed, so verifying the build,
|
|
44
|
+
* deriving its operations and compiling its tool schemas happens once per
|
|
45
|
+
* Release ID and artifact digest, not on every catalog capture: an MCP call
|
|
46
|
+
* captures the catalog several times (refresh, session acquire, the pre-call
|
|
47
|
+
* authority check), and redoing this for every installed App on each one
|
|
48
|
+
* exhausted a Durable Object's CPU and memory. Only Release-derived facts are
|
|
49
|
+
* cached; installation authority, caller access and tool-family listings
|
|
50
|
+
* are still read fresh on every capture. A failed build is not cached.
|
|
51
|
+
*/
|
|
52
|
+
export type AppSurfaceCache = (release: Installation["release"]) => Promise<ReleaseSurface | null>;
|
|
53
|
+
/** Make one {@link AppSurfaceCache} for a host whose artifact store and capabilities stay fixed. */
|
|
54
|
+
export declare function appSurfaceCache(host: Pick<OysterHost, "artifacts" | "capabilities">): AppSurfaceCache;
|
|
55
|
+
/**
|
|
56
|
+
* Capture installed App methods for MCP or agents as one principal-scoped source.
|
|
57
|
+
* Enabled installations become `env.<APP>` modules using the Instance call path; a dotted method such as `repos.list` is `env.<APP>.repos.list()`.
|
|
58
|
+
* Visitors see only public read methods; owners see every discoverable
|
|
59
|
+
* method. A key sees only the installations its own grants admit, each at
|
|
60
|
+
* the level (`read`/`write`) those grants give it; Mycelium itself refuses a
|
|
61
|
+
* write call up front when the module's own grant is `read`
|
|
62
|
+
* (`mycelium/src/access.ts`). Call again to capture changes to Releases,
|
|
63
|
+
* installation authority or the caller's own grants.
|
|
64
|
+
*/
|
|
65
|
+
export declare function oysterAppMethods(host: Pick<OysterHost, "installations" | "instances" | "artifacts" | "guestLimits" | "backgroundOriginKey" | "computerId" | "capabilities" | "appToolPolicy" | "keys">, principal: OysterPrincipal, access: OysterCallerAccess, surfaces: AppSurfaceCache, preloadedInstallations?: readonly Installation[]): Promise<CapabilitySource & {
|
|
66
|
+
readonly grants: Grants;
|
|
67
|
+
}>;
|
|
68
|
+
/**
|
|
69
|
+
* Capture the host's fixed Computer for one caller as a Knapsack source.
|
|
70
|
+
* The host decides whether the caller holds Computer access; the Computer
|
|
71
|
+
* owner accepts every call. Null when the host has no Computer or the caller
|
|
72
|
+
* has no access. Grants name exactly the one captured module.
|
|
73
|
+
*/
|
|
74
|
+
export declare function oysterComputerTools(host: Pick<OysterHost, "computerId" | "computer">, principal: OysterPrincipal): Promise<(CapabilitySource & {
|
|
75
|
+
readonly grants: Grants;
|
|
76
|
+
}) | null>;
|
|
77
|
+
/**
|
|
78
|
+
* Load the current catalog for an authenticated owner or key: installed App
|
|
79
|
+
* methods, plus the host's Computer when the caller holds Computer access.
|
|
80
|
+
* A key sees exactly the installations and levels its own grants admit, and
|
|
81
|
+
* the Computer only with its own `computer.use` grant; each App retains its
|
|
82
|
+
* own caller checks regardless. The owner's stored write approval of an
|
|
83
|
+
* Agent's App tools is that principal's confirmation for App writes, so a
|
|
84
|
+
* headless client can write; the shared MCP server asks the client to
|
|
85
|
+
* approve every other write (an owner's own calls, the Computer). Each call
|
|
86
|
+
* still re-admits the key, rechecks that same stored approval, and passes
|
|
87
|
+
* the App's gate.
|
|
88
|
+
*/
|
|
89
|
+
export declare function oysterMcpCatalogLoad(host: Pick<OysterHost, "installations" | "instances" | "artifacts" | "guestLimits" | "backgroundOriginKey" | "computerId" | "computer" | "capabilities" | "appToolPolicy" | "keys">): (principal: McpPrincipal) => Promise<McpCatalogSnapshot>;
|
package/dist/mcp.js
ADDED
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
import { admits } from "./apps/gate.js";
|
|
2
|
+
import { causeClass } from "./cause-class.js";
|
|
3
|
+
import { deriveDeclaredOperations } from "./apps/operations.js";
|
|
4
|
+
import { appToolPolicySchema } from "./apps/tools.js";
|
|
5
|
+
import { emptyAppDeclaration } from "@fungi.computer/puffball/app";
|
|
6
|
+
import { admittedMethodCallerFacts, invocationCallerContext, } from "./invocation.js";
|
|
7
|
+
import { guestLimits } from "./guest-policy.js";
|
|
8
|
+
import { Mycelium, } from "@fungi.computer/mycelium";
|
|
9
|
+
import { appToolSchemas, appToolFamilySchemas } from "@fungi.computer/forage";
|
|
10
|
+
import { Knapsack } from "@fungi.computer/knapsack";
|
|
11
|
+
import * as Schema from "effect/Schema";
|
|
12
|
+
import { defineIdFamily } from "@fungi.computer/cairn/id";
|
|
13
|
+
import { keyAppAccess } from "./keys.js";
|
|
14
|
+
import { defaultAssetPolicy, sha256, verifyRelease, } from "./release.js";
|
|
15
|
+
/** Mints the requestId a tool family tool sends on the caller's behalf to its
|
|
16
|
+
* declared `call` method: each MCP call is its own independent run, never a
|
|
17
|
+
* replay the caller controls. */
|
|
18
|
+
const toolFamilyCallIds = defineIdFamily({
|
|
19
|
+
family: "oyster-tool-family-call",
|
|
20
|
+
version: 1,
|
|
21
|
+
kinds: { call: { prefix: "call" } },
|
|
22
|
+
});
|
|
23
|
+
/** An installation ID as a guest binding name: `spawn-git` becomes `SPAWN_GIT`. */
|
|
24
|
+
export function appBindingName(installationId) {
|
|
25
|
+
return installationId.replace(/[^A-Za-z0-9]+/gu, "_").toUpperCase();
|
|
26
|
+
}
|
|
27
|
+
/** Why an App method call from the MCP catalog was refused. */
|
|
28
|
+
export class AppMethodRefused extends Error {
|
|
29
|
+
code;
|
|
30
|
+
name = "AppMethodRefused";
|
|
31
|
+
/** `detail` is the App's own caller-facing refusal message, when it gave one. */
|
|
32
|
+
constructor(code, detail) {
|
|
33
|
+
super(detail === undefined
|
|
34
|
+
? `App method refused: ${code}`
|
|
35
|
+
: `App method refused: ${code}: ${detail}`);
|
|
36
|
+
this.code = code;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function capturedAccessFor(access, installationId) {
|
|
40
|
+
if (access === null)
|
|
41
|
+
return null;
|
|
42
|
+
return access.kind === "agent"
|
|
43
|
+
? access.policy
|
|
44
|
+
: (access.byInstallation.get(installationId) ?? null);
|
|
45
|
+
}
|
|
46
|
+
/** Re-read one actor's current App-tool access for one installation, fresh. */
|
|
47
|
+
async function freshAccessFor(host, actor, installationId) {
|
|
48
|
+
if (actor.kind === "agent")
|
|
49
|
+
return (await host.appToolPolicy?.(actor)) ?? null;
|
|
50
|
+
const key = (await host.keys?.live(actor.keyId)) ?? null;
|
|
51
|
+
return key === null ? null : keyAppAccess(key, installationId);
|
|
52
|
+
}
|
|
53
|
+
/** Make one {@link AppSurfaceCache} for a host whose artifact store and capabilities stay fixed. */
|
|
54
|
+
export function appSurfaceCache(host) {
|
|
55
|
+
const surfaces = new Map();
|
|
56
|
+
const build = async (installed) => {
|
|
57
|
+
const release = await verifyRelease(host.artifacts, installed, defaultAssetPolicy, false);
|
|
58
|
+
if (release.api === null)
|
|
59
|
+
return null;
|
|
60
|
+
return {
|
|
61
|
+
release,
|
|
62
|
+
api: release.api,
|
|
63
|
+
declaredOperations: await deriveDeclaredOperations(installed, release.declaration ?? emptyAppDeclaration, host.capabilities ?? []),
|
|
64
|
+
toolSchemas: appToolSchemas(release.api),
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
return (installed) => {
|
|
68
|
+
const key = `${installed.releaseId}:${installed.artifact.artifactDigest}`;
|
|
69
|
+
let entry = surfaces.get(key);
|
|
70
|
+
if (entry === undefined) {
|
|
71
|
+
entry = build(installed).catch((cause) => {
|
|
72
|
+
surfaces.delete(key);
|
|
73
|
+
throw cause;
|
|
74
|
+
});
|
|
75
|
+
surfaces.set(key, entry);
|
|
76
|
+
}
|
|
77
|
+
return entry;
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
async function appModules(host, surfaces, principal, installations, access, sessionId,
|
|
81
|
+
/** Shares one tool-family listing per Instance across the two
|
|
82
|
+
* `appModules` passes one `oysterAppMethods` call can make (the eager
|
|
83
|
+
* grants/version pass, and a session-scoped re-derivation); never shared
|
|
84
|
+
* across separate calls, so it never serves another request's listing. */
|
|
85
|
+
familyListingCache = new Map()) {
|
|
86
|
+
const limits = guestLimits(host.guestLimits);
|
|
87
|
+
const caller = invocationCallerContext(principal, host.computerId ?? null);
|
|
88
|
+
const { actor } = principal;
|
|
89
|
+
const visitor = actor.kind === "visitor";
|
|
90
|
+
const modules = {};
|
|
91
|
+
const levels = {};
|
|
92
|
+
for (const installation of installations) {
|
|
93
|
+
if (installation.status !== "enabled" || installation.instanceId === null)
|
|
94
|
+
continue;
|
|
95
|
+
if (visitor && installation.visibility !== "public")
|
|
96
|
+
continue;
|
|
97
|
+
const captured = capturedAccessFor(access, installation.installationId);
|
|
98
|
+
// A key that holds no grant for this installation never sees it listed
|
|
99
|
+
// at all. An Agent's one blanket policy either covers every
|
|
100
|
+
// installation or none, so this never hides one from an Agent; an
|
|
101
|
+
// unapproved Agent still sees the list, refused at call time, as today.
|
|
102
|
+
if (access?.kind === "key" && captured === null)
|
|
103
|
+
continue;
|
|
104
|
+
const surface = await surfaces(installation.release);
|
|
105
|
+
if (surface === null)
|
|
106
|
+
continue;
|
|
107
|
+
const { release, api, declaredOperations, toolSchemas } = surface;
|
|
108
|
+
const instanceId = installation.instanceId;
|
|
109
|
+
const declaration = release.declaration ?? emptyAppDeclaration;
|
|
110
|
+
const id = `oyster-app:${installation.installationId}`;
|
|
111
|
+
// A member/owner/visitor, or an Agent/key with no captured access,
|
|
112
|
+
// defaults to "write" here exactly as every module always did before:
|
|
113
|
+
// the deeper gate (`admits`), not this coarse Mycelium-level grant, is
|
|
114
|
+
// what actually enforces visitor/role ceilings. Only a captured
|
|
115
|
+
// Agent/key access with its own level narrows this.
|
|
116
|
+
levels[id] = captured?.level ?? "write";
|
|
117
|
+
// Shared by every operation this installation exposes (ordinary methods
|
|
118
|
+
// and, below, a tool family's dynamic per-row tools): the same staleness
|
|
119
|
+
// check, the same owner-approved policy check, the same `admits` gate and
|
|
120
|
+
// the same Instance call. A tool family tool is sugar over this same
|
|
121
|
+
// admitted call to its declared `call` method; it gets no separate check.
|
|
122
|
+
const callAppMethod = async (name, effect, input, signal) => {
|
|
123
|
+
signal?.throwIfAborted();
|
|
124
|
+
const currentInstallation = await host.installations.get(installation.installationId);
|
|
125
|
+
signal?.throwIfAborted();
|
|
126
|
+
if (currentInstallation?.status !== "enabled" ||
|
|
127
|
+
currentInstallation.instanceId !== instanceId ||
|
|
128
|
+
currentInstallation.authorityVersion !==
|
|
129
|
+
installation.authorityVersion ||
|
|
130
|
+
currentInstallation.release.releaseId !==
|
|
131
|
+
installation.release.releaseId ||
|
|
132
|
+
currentInstallation.release.artifact.artifactDigest !==
|
|
133
|
+
installation.release.artifact.artifactDigest)
|
|
134
|
+
throw new AppMethodRefused("stale_authority");
|
|
135
|
+
if (actor.kind === "agent" || actor.kind === "team-key") {
|
|
136
|
+
const fresh = Schema.decodeUnknownOption(appToolPolicySchema, {
|
|
137
|
+
onExcessProperty: "error",
|
|
138
|
+
})(await freshAccessFor(host, actor, installation.installationId));
|
|
139
|
+
const capturedParsed = Schema.decodeUnknownOption(appToolPolicySchema, {
|
|
140
|
+
onExcessProperty: "error",
|
|
141
|
+
})(captured);
|
|
142
|
+
// A call never pre-approved in the first place (no captured
|
|
143
|
+
// policy) is `not_approved`; one that WAS approved but whose
|
|
144
|
+
// authority has since changed or vanished entirely -- a key
|
|
145
|
+
// revoked or expired after capture, say -- is `stale_authority`,
|
|
146
|
+
// just like the other authority drift below. Only the absence
|
|
147
|
+
// of a captured policy gets the former code.
|
|
148
|
+
if (capturedParsed._tag === "None")
|
|
149
|
+
throw new AppMethodRefused("not_approved");
|
|
150
|
+
if (fresh._tag === "None" ||
|
|
151
|
+
fresh.value.revision !== capturedParsed.value.revision ||
|
|
152
|
+
fresh.value.grantedBy !== capturedParsed.value.grantedBy ||
|
|
153
|
+
fresh.value.level !== capturedParsed.value.level)
|
|
154
|
+
throw new AppMethodRefused("stale_authority");
|
|
155
|
+
}
|
|
156
|
+
signal?.throwIfAborted();
|
|
157
|
+
const json = Schema.decodeUnknownSync(Schema.Json)(input);
|
|
158
|
+
const decision = admits({
|
|
159
|
+
installation,
|
|
160
|
+
declaration,
|
|
161
|
+
declaredOperations,
|
|
162
|
+
api,
|
|
163
|
+
principal: {
|
|
164
|
+
actor,
|
|
165
|
+
role: "role" in principal ? principal.role : null,
|
|
166
|
+
},
|
|
167
|
+
access: captured === null
|
|
168
|
+
? null
|
|
169
|
+
: {
|
|
170
|
+
level: captured.level,
|
|
171
|
+
revision: captured.revision,
|
|
172
|
+
grantedBy: captured.grantedBy,
|
|
173
|
+
bindingName: appBindingName(installation.installationId),
|
|
174
|
+
},
|
|
175
|
+
}, { kind: "method", name, input: json, via: "mcp" });
|
|
176
|
+
if (decision.kind === "refused")
|
|
177
|
+
throw new AppMethodRefused(decision.code);
|
|
178
|
+
const result = await host.instances.get(instanceId, limits).call(await admittedMethodCallerFacts(host, installation, release, caller, decision.grants, { name, effect }, [], sessionId, actor.kind === "agent" && captured !== null
|
|
179
|
+
? {
|
|
180
|
+
policyRevision: captured.revision,
|
|
181
|
+
grantorMemberId: captured.grantedBy,
|
|
182
|
+
}
|
|
183
|
+
: undefined), api, name, json);
|
|
184
|
+
if (!result.ok)
|
|
185
|
+
throw new AppMethodRefused(result.code);
|
|
186
|
+
if (result.value.kind === "refused")
|
|
187
|
+
throw new AppMethodRefused(result.value.code, result.value.message);
|
|
188
|
+
return result.value.value;
|
|
189
|
+
};
|
|
190
|
+
const toolOperations = Object.entries(toolSchemas)
|
|
191
|
+
.filter(([, method]) => !visitor || method.effect === "read")
|
|
192
|
+
.map(([name, method]) => {
|
|
193
|
+
const appMethod = Mycelium.operation({
|
|
194
|
+
replay: "unsafe",
|
|
195
|
+
access: method.effect === "read" ? "read" : "write",
|
|
196
|
+
description: `${method.description}\nEffect: ${method.effect}.`,
|
|
197
|
+
input: method.input,
|
|
198
|
+
output: method.output,
|
|
199
|
+
execute: async (input, context) => {
|
|
200
|
+
const value = await callAppMethod(name, method.effect, input, context.signal);
|
|
201
|
+
return method.output.parse(value);
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
return [name, appMethod];
|
|
205
|
+
});
|
|
206
|
+
// A tool family publishes a dynamic, signed set of typed tools from one
|
|
207
|
+
// App (e.g. each published Workflow becomes its own tool). forage decodes
|
|
208
|
+
// that listing and compiles its schemas; it supplies no admission or
|
|
209
|
+
// invocation arguments by design, so the host supplies both here, reusing
|
|
210
|
+
// the exact same `callAppMethod` gate as every other method on this App.
|
|
211
|
+
// A visitor who cannot see non-read ordinary methods cannot see these
|
|
212
|
+
// either; a caller the App itself refuses to list for (stale authority,
|
|
213
|
+
// an unapproved App-tool policy, a missing capability grant) sees none.
|
|
214
|
+
const family = api.toolFamily;
|
|
215
|
+
const familyOperations = family === undefined ||
|
|
216
|
+
(visitor && api.methods[family.call]?.effect !== "read")
|
|
217
|
+
? []
|
|
218
|
+
: await (async () => {
|
|
219
|
+
try {
|
|
220
|
+
let listing = familyListingCache.get(instanceId);
|
|
221
|
+
if (listing === undefined) {
|
|
222
|
+
listing = await callAppMethod(family.list, "read", {});
|
|
223
|
+
familyListingCache.set(instanceId, listing);
|
|
224
|
+
}
|
|
225
|
+
const compiled = appToolFamilySchemas(api, listing);
|
|
226
|
+
if (compiled === null)
|
|
227
|
+
return [];
|
|
228
|
+
return compiled.tools.map((tool) => {
|
|
229
|
+
const operation = Mycelium.operation({
|
|
230
|
+
replay: "unsafe",
|
|
231
|
+
access: compiled.effect === "read" ? "read" : "write",
|
|
232
|
+
description: `${tool.description}\nEffect: ${compiled.effect}.`,
|
|
233
|
+
input: tool.input,
|
|
234
|
+
output: tool.output,
|
|
235
|
+
execute: async (input, context) => {
|
|
236
|
+
// The declaring App owns every field below except
|
|
237
|
+
// `requestId` (this call's own identity) and `input`
|
|
238
|
+
// (the caller's tool-shaped payload): `tool.row.call` is
|
|
239
|
+
// opaque data this host never reads into or defaults.
|
|
240
|
+
// `requestId` is last: a row whose own `call` data named
|
|
241
|
+
// that key would otherwise replay the same run forever.
|
|
242
|
+
const value = await callAppMethod(compiled.call, compiled.effect, {
|
|
243
|
+
...tool.row.call,
|
|
244
|
+
input,
|
|
245
|
+
requestId: toolFamilyCallIds.call.generate(),
|
|
246
|
+
}, context.signal);
|
|
247
|
+
return tool.output.parse(value);
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
return [
|
|
251
|
+
`${compiled.name}.${tool.row.tool}`,
|
|
252
|
+
operation,
|
|
253
|
+
];
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
// One App's bad listing -- a refused or throwing Instance
|
|
258
|
+
// call, or a reply forage's signed-listing codec rejects --
|
|
259
|
+
// withholds only this App's family tools. Every other App's
|
|
260
|
+
// ordinary and family operations are built independently and
|
|
261
|
+
// stay unaffected; the whole catalog build never aborts for it.
|
|
262
|
+
// An ordinary closed refusal (the same gate any App method
|
|
263
|
+
// already produces) stays quiet; anything else is reported
|
|
264
|
+
// exactly like any other AppInstance failure.
|
|
265
|
+
if (!(error instanceof AppMethodRefused) ||
|
|
266
|
+
error.code === "failed")
|
|
267
|
+
console.error("AppInstance failure", {
|
|
268
|
+
stage: "tool_family_listing",
|
|
269
|
+
installationId: installation.installationId,
|
|
270
|
+
causeClass: error instanceof AppMethodRefused
|
|
271
|
+
? "Refused"
|
|
272
|
+
: causeClass(error),
|
|
273
|
+
});
|
|
274
|
+
return [];
|
|
275
|
+
}
|
|
276
|
+
})();
|
|
277
|
+
modules[id] = Mycelium.module({
|
|
278
|
+
id,
|
|
279
|
+
name: appBindingName(installation.installationId),
|
|
280
|
+
description: release.declaration?.displayName ?? installation.installationId,
|
|
281
|
+
operations: Object.fromEntries([...toolOperations, ...familyOperations]),
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
return { modules, levels };
|
|
285
|
+
}
|
|
286
|
+
function serializableAccess(access) {
|
|
287
|
+
return access === null || access.kind === "agent"
|
|
288
|
+
? access
|
|
289
|
+
: {
|
|
290
|
+
kind: access.kind,
|
|
291
|
+
keyId: access.keyId,
|
|
292
|
+
byInstallation: [...access.byInstallation.entries()].sort(([left], [right]) => left.localeCompare(right)),
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Capture installed App methods for MCP or agents as one principal-scoped source.
|
|
297
|
+
* Enabled installations become `env.<APP>` modules using the Instance call path; a dotted method such as `repos.list` is `env.<APP>.repos.list()`.
|
|
298
|
+
* Visitors see only public read methods; owners see every discoverable
|
|
299
|
+
* method. A key sees only the installations its own grants admit, each at
|
|
300
|
+
* the level (`read`/`write`) those grants give it; Mycelium itself refuses a
|
|
301
|
+
* write call up front when the module's own grant is `read`
|
|
302
|
+
* (`mycelium/src/access.ts`). Call again to capture changes to Releases,
|
|
303
|
+
* installation authority or the caller's own grants.
|
|
304
|
+
*/
|
|
305
|
+
export async function oysterAppMethods(host, principal, access, surfaces, preloadedInstallations) {
|
|
306
|
+
const installations = preloadedInstallations ?? (await host.installations.list());
|
|
307
|
+
// Shared so a session-scoped re-derivation below reuses this call's tool-
|
|
308
|
+
// family listings instead of asking each Instance for them twice.
|
|
309
|
+
const familyListingCache = new Map();
|
|
310
|
+
const { modules: entries, levels } = await appModules(host, surfaces, principal, installations, access, undefined, familyListingCache);
|
|
311
|
+
const modules = Object.values(entries);
|
|
312
|
+
const version = await sha256(new TextEncoder().encode(JSON.stringify({
|
|
313
|
+
access: serializableAccess(access),
|
|
314
|
+
installations: [...installations]
|
|
315
|
+
.sort((a, b) => a.installationId.localeCompare(b.installationId))
|
|
316
|
+
.map((installation) => [
|
|
317
|
+
installation.installationId,
|
|
318
|
+
installation.instanceId,
|
|
319
|
+
installation.release.releaseId,
|
|
320
|
+
installation.release.artifact.artifactDigest,
|
|
321
|
+
installation.authorityVersion,
|
|
322
|
+
installation.status,
|
|
323
|
+
installation.visibility,
|
|
324
|
+
]),
|
|
325
|
+
})));
|
|
326
|
+
return {
|
|
327
|
+
id: "oyster-apps",
|
|
328
|
+
grants: levels,
|
|
329
|
+
acquire: async ({ signal, session }) => {
|
|
330
|
+
signal.throwIfAborted();
|
|
331
|
+
const acquiredModules = session === undefined
|
|
332
|
+
? modules
|
|
333
|
+
: Object.values((await appModules(host, surfaces, principal, installations, access, session.sessionId, familyListingCache)).modules);
|
|
334
|
+
signal.throwIfAborted();
|
|
335
|
+
return {
|
|
336
|
+
snapshot: { version, modules: acquiredModules },
|
|
337
|
+
release: () => Promise.resolve(),
|
|
338
|
+
};
|
|
339
|
+
},
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Capture the host's fixed Computer for one caller as a Knapsack source.
|
|
344
|
+
* The host decides whether the caller holds Computer access; the Computer
|
|
345
|
+
* owner accepts every call. Null when the host has no Computer or the caller
|
|
346
|
+
* has no access. Grants name exactly the one captured module.
|
|
347
|
+
*/
|
|
348
|
+
export async function oysterComputerTools(host, principal) {
|
|
349
|
+
if (host.computerId === undefined || host.computer === undefined)
|
|
350
|
+
return null;
|
|
351
|
+
const computer = await host.computer(principal);
|
|
352
|
+
if (computer === null)
|
|
353
|
+
return null;
|
|
354
|
+
const id = `computer:${host.computerId}`;
|
|
355
|
+
return {
|
|
356
|
+
...Knapsack.computer({
|
|
357
|
+
id,
|
|
358
|
+
name: "workspace",
|
|
359
|
+
namespace: ["computers"],
|
|
360
|
+
computer,
|
|
361
|
+
}),
|
|
362
|
+
grants: { [id]: "write" },
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Load the current catalog for an authenticated owner or key: installed App
|
|
367
|
+
* methods, plus the host's Computer when the caller holds Computer access.
|
|
368
|
+
* A key sees exactly the installations and levels its own grants admit, and
|
|
369
|
+
* the Computer only with its own `computer.use` grant; each App retains its
|
|
370
|
+
* own caller checks regardless. The owner's stored write approval of an
|
|
371
|
+
* Agent's App tools is that principal's confirmation for App writes, so a
|
|
372
|
+
* headless client can write; the shared MCP server asks the client to
|
|
373
|
+
* approve every other write (an owner's own calls, the Computer). Each call
|
|
374
|
+
* still re-admits the key, rechecks that same stored approval, and passes
|
|
375
|
+
* the App's gate.
|
|
376
|
+
*/
|
|
377
|
+
export function oysterMcpCatalogLoad(host) {
|
|
378
|
+
const surfaces = appSurfaceCache(host);
|
|
379
|
+
return async (principal) => {
|
|
380
|
+
const { actor } = principal;
|
|
381
|
+
// One read of the installation list serves both a key's grant check
|
|
382
|
+
// below and oysterAppMethods' own capture, instead of two.
|
|
383
|
+
const installations = await host.installations.list();
|
|
384
|
+
let access = null;
|
|
385
|
+
if (actor.kind === "agent") {
|
|
386
|
+
const policy = (await host.appToolPolicy?.(actor)) ?? null;
|
|
387
|
+
access = policy === null ? null : { kind: "agent", policy };
|
|
388
|
+
}
|
|
389
|
+
else if (actor.kind === "team-key") {
|
|
390
|
+
// Always a "key" access, even an empty one for an unknown, revoked,
|
|
391
|
+
// expired or suspended key: that keeps every installation invisible
|
|
392
|
+
// (via appModules' own `access?.kind === "key"` check) rather than
|
|
393
|
+
// falling through to the member/owner default of "write" below.
|
|
394
|
+
const key = (await host.keys?.live(actor.keyId)) ?? null;
|
|
395
|
+
const byInstallation = new Map();
|
|
396
|
+
if (key !== null)
|
|
397
|
+
for (const installation of installations) {
|
|
398
|
+
const level = keyAppAccess(key, installation.installationId);
|
|
399
|
+
if (level !== null)
|
|
400
|
+
byInstallation.set(installation.installationId, level);
|
|
401
|
+
}
|
|
402
|
+
access = { kind: "key", keyId: actor.keyId, byInstallation };
|
|
403
|
+
}
|
|
404
|
+
const apps = await oysterAppMethods(host, principal, access, surfaces, installations);
|
|
405
|
+
// Pre-approval ("never asked") is what a captured Agent/key policy
|
|
406
|
+
// itself grants; an owner's own calls, or an Agent/key with none, are
|
|
407
|
+
// never pre-approved even though their modules still default to the
|
|
408
|
+
// coarse "write" ceiling above.
|
|
409
|
+
const approvedWrites = access === null
|
|
410
|
+
? []
|
|
411
|
+
: Object.entries(apps.grants)
|
|
412
|
+
.filter(([, level]) => level === "write")
|
|
413
|
+
.map(([id]) => id);
|
|
414
|
+
const computer = await oysterComputerTools(host, principal);
|
|
415
|
+
if (computer === null)
|
|
416
|
+
return {
|
|
417
|
+
instructions: "Installed Apps on this Oyster. Each App is env.<APP>; dotted method names are nested properties, e.g. env.BONSAI.repos.list({}). Use forage.search / forage.describe to find methods.",
|
|
418
|
+
grants: apps.grants,
|
|
419
|
+
approvedWrites,
|
|
420
|
+
sources: [apps],
|
|
421
|
+
};
|
|
422
|
+
return {
|
|
423
|
+
instructions: 'Installed Apps and this Oyster\'s Computer. Each App is env.<APP>; dotted method names are nested properties, e.g. env.BONSAI.repos.list({}). The Computer is env.computers.workspace, e.g. env.computers.workspace.exec({command: "ls"}). Use forage.search / forage.describe to find methods.',
|
|
424
|
+
grants: { ...apps.grants, ...computer.grants },
|
|
425
|
+
approvedWrites,
|
|
426
|
+
sources: [apps, computer],
|
|
427
|
+
};
|
|
428
|
+
};
|
|
429
|
+
}
|
package/dist/media.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Media slot supplied by the host; Oyster owns routing, never generation. */
|
|
2
|
+
export declare const mediaSlot: {
|
|
3
|
+
readonly id: "media.generation";
|
|
4
|
+
readonly version: number;
|
|
5
|
+
readonly base: "/media/";
|
|
6
|
+
readonly methods: {
|
|
7
|
+
readonly describe: {
|
|
8
|
+
readonly kind: "json";
|
|
9
|
+
readonly method: "GET";
|
|
10
|
+
readonly path: "describe";
|
|
11
|
+
};
|
|
12
|
+
readonly generate: {
|
|
13
|
+
readonly kind: "json";
|
|
14
|
+
readonly method: "POST";
|
|
15
|
+
readonly path: "generate";
|
|
16
|
+
};
|
|
17
|
+
readonly cancelAttempt: {
|
|
18
|
+
readonly kind: "json";
|
|
19
|
+
readonly method: "POST";
|
|
20
|
+
readonly path: "cancel-attempt";
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
};
|
package/dist/media.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { mediaGeneration } from "@fungi.computer/agar/media";
|
|
2
|
+
/** Media slot supplied by the host; Oyster owns routing, never generation. */
|
|
3
|
+
export const mediaSlot = {
|
|
4
|
+
id: "media.generation",
|
|
5
|
+
version: mediaGeneration.version,
|
|
6
|
+
base: "/media/",
|
|
7
|
+
methods: {
|
|
8
|
+
describe: { kind: "json", method: "GET", path: "describe" },
|
|
9
|
+
generate: { kind: "json", method: "POST", path: "generate" },
|
|
10
|
+
cancelAttempt: { kind: "json", method: "POST", path: "cancel-attempt" },
|
|
11
|
+
},
|
|
12
|
+
};
|
package/dist/pack.d.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { type ArtifactRef, type Release } from "@fungi.computer/puffball";
|
|
2
|
+
import { type AppPublisherCoreConfigInput } from "./build.js";
|
|
3
|
+
/** One committed repository and its canonical App build recipe. */
|
|
4
|
+
export type PackOptions = Readonly<{
|
|
5
|
+
/** The App's ID, `app_<name>`. */
|
|
6
|
+
appId: string;
|
|
7
|
+
/** The Release ID; defaults to `rel_<commit prefix>`. */
|
|
8
|
+
releaseId?: string;
|
|
9
|
+
/** Repository root; all recipe paths are relative to this captured root. */
|
|
10
|
+
sourceDirectory: string;
|
|
11
|
+
/** Exact committed source, `git:<full commit SHA>`; dirty files are not inputs. */
|
|
12
|
+
sourceRevision: string;
|
|
13
|
+
/** The same pinned recipe input accepted by `oyster/build`. */
|
|
14
|
+
recipe: AppPublisherCoreConfigInput["recipe"];
|
|
15
|
+
/** Optional local pnpm content store used by the captured source compiler. */
|
|
16
|
+
packageStoreDirectory?: string;
|
|
17
|
+
/** Where to write `release.json` and the immutable `artifacts/` directory. */
|
|
18
|
+
outDirectory: string;
|
|
19
|
+
}>;
|
|
20
|
+
/** A packed Release on disk. */
|
|
21
|
+
export type PackedRelease = Readonly<{
|
|
22
|
+
release: Release;
|
|
23
|
+
/** Every artifact file by reference. */
|
|
24
|
+
artifacts: readonly Readonly<{
|
|
25
|
+
artifactRef: ArtifactRef;
|
|
26
|
+
path: string;
|
|
27
|
+
}>[];
|
|
28
|
+
}>;
|
|
29
|
+
/** Build an exact local Git commit with the canonical compiler and artifact writer. */
|
|
30
|
+
export declare function packRelease(options: PackOptions): Promise<PackedRelease>;
|
|
31
|
+
/**
|
|
32
|
+
* Capture an Artifacts Git commit, build its Release, upload it to an Oyster,
|
|
33
|
+
* and install it, or switch an existing installation to it
|
|
34
|
+
* ({@link installRelease}).
|
|
35
|
+
*/
|
|
36
|
+
export declare function installFromSource(input: Readonly<{
|
|
37
|
+
appId: string;
|
|
38
|
+
releaseId?: string;
|
|
39
|
+
recipe: PackOptions["recipe"];
|
|
40
|
+
/** Credential-free trusted Artifacts Git URL and its exact committed source. */
|
|
41
|
+
source: Readonly<{
|
|
42
|
+
remote: string;
|
|
43
|
+
token: string;
|
|
44
|
+
sourceRevision: string;
|
|
45
|
+
}>;
|
|
46
|
+
packageStoreDirectory?: string;
|
|
47
|
+
/** Destination Oyster origin. */
|
|
48
|
+
url: string;
|
|
49
|
+
/** Destination Oyster owner key; distinct from the source Git credential. */
|
|
50
|
+
key: string;
|
|
51
|
+
installationId: string;
|
|
52
|
+
}>): Promise<Response>;
|
|
53
|
+
/**
|
|
54
|
+
* Upload a packed Release to an Oyster and install it. The host installs a
|
|
55
|
+
* new installation (201) or switches an existing one to this Release (200),
|
|
56
|
+
* keeping its state; both go through its one install-or-select owner.
|
|
57
|
+
*/
|
|
58
|
+
export declare function installRelease(input: Readonly<{
|
|
59
|
+
directory: string;
|
|
60
|
+
url: string;
|
|
61
|
+
/** The Oyster owner key. */
|
|
62
|
+
key: string;
|
|
63
|
+
installationId?: string;
|
|
64
|
+
}>): Promise<Response>;
|
|
65
|
+
/** Project one exact first-party commit's editable source and seed it into
|
|
66
|
+
* an Oyster's own template, so `APPS.fork` can later fork it. The operator
|
|
67
|
+
* owner key authenticates; no provider credential ever leaves this call. */
|
|
68
|
+
export declare function publishSource(input: Readonly<{
|
|
69
|
+
appId: string;
|
|
70
|
+
entry: string;
|
|
71
|
+
repositoryPath: string;
|
|
72
|
+
sourceRevision: string;
|
|
73
|
+
recipe: PackOptions["recipe"];
|
|
74
|
+
url: string;
|
|
75
|
+
key: string;
|
|
76
|
+
}>): Promise<Response>;
|