@fungi.computer/oyster 0.0.1 → 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/README.md +228 -176
- package/dist/app-builds.d.ts +76 -17
- package/dist/app-builds.js +207 -63
- package/dist/app-sources.d.ts +7 -22
- package/dist/app-sources.js +49 -14
- package/dist/build/artifact-writer.d.ts +40 -0
- package/dist/build/artifact-writer.js +181 -0
- package/dist/build/build-jobs.js +7 -1
- package/dist/build/build-policy.d.ts +10 -0
- package/dist/build/build-policy.js +10 -0
- package/dist/build/build-step.d.ts +12 -1
- package/dist/build/build-step.js +9 -1
- 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/effect.d.ts +18 -0
- package/dist/build/effect.js +52 -12
- package/dist/build/git-environment.d.ts +3 -0
- package/dist/build/git-environment.js +21 -9
- package/dist/build/local-build.d.ts +13 -3
- package/dist/build/local-build.js +149 -99
- package/dist/build/snapshot-package-store.d.ts +10 -0
- package/dist/build/snapshot-package-store.js +15 -0
- package/dist/build/snapshot-program-scripts.js +1 -1
- 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 +461 -17
- package/dist/build/submission-contract.js +136 -26
- package/dist/build/workspace-build.d.ts +7 -2
- package/dist/build/workspace-build.js +55 -11
- package/dist/build-contract.d.ts +3 -0
- package/dist/build-contract.js +3 -0
- package/dist/build-egress.d.ts +62 -0
- package/dist/build-egress.js +148 -0
- package/dist/build.d.ts +1 -0
- package/dist/build.js +1 -0
- package/dist/mcp-owner.js +20 -8
- package/dist/pack.d.ts +6 -3
- package/dist/pack.js +28 -11
- package/dist/page/gill-observation.d.ts +4 -0
- package/dist/page/gill-observation.js +31 -0
- package/dist/page/window-launch.d.ts +18 -0
- package/dist/page/window-launch.js +11 -0
- package/dist/page-script.js +2 -2
- package/dist/release.d.ts +11 -0
- package/dist/release.js +4 -1
- package/dist/shell/app-directory.d.ts +13 -8
- package/dist/shell/app-directory.js +4 -0
- package/dist/shell/shell-module.d.ts +26 -2
- package/dist/shell/shell-module.js +51 -2
- package/dist/shell/workbench-directory.d.ts +332 -0
- package/dist/shell/workbench-directory.js +35 -0
- package/dist/source/index.d.ts +6 -7
- package/dist/source/index.js +6 -7
- package/dist/source/template-push.d.ts +33 -0
- package/dist/source/template-push.js +223 -0
- package/dist/source/template-repository.d.ts +2 -0
- package/dist/source/template-repository.js +13 -0
- package/dist/source/{template-write.d.ts → template-seed.d.ts} +19 -20
- 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/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/workers.d.ts +2 -2
- package/dist/workers.js +3 -1
- package/docs/api/build-contract.md +829 -32
- package/docs/api/build.md +253 -17
- package/docs/api/modules.md +40 -32
- package/docs/api/pack.md +6 -3
- package/docs/api/shell/app-directory.md +267 -0
- package/docs/api/shell/shell-module.md +64 -3
- package/docs/api/shell/workbench-directory.md +119 -0
- package/docs/api/shell/workbench.md +19 -0
- package/docs/api/shell.md +127 -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/workers-git.md +140 -21
- package/docs/api/workers.md +15 -2
- package/docs/site/agent-quickstart.mdx +52 -55
- package/examples/standalone/worker.ts +7 -14
- package/examples/standalone/wrangler.jsonc +67 -6
- package/package.json +72 -21
- package/dist/build/r2-artifact-writer.d.ts +0 -31
- package/dist/build/r2-artifact-writer.js +0 -179
- package/dist/source/template-write.js +0 -344
- package/docs/media/architecture.mmd +0 -32
- package/docs/media/architecture.png +0 -0
- package/docs/media/bonsai-network.png +0 -0
- package/docs/media/bonsai-repository.png +0 -0
- package/examples/git/worker.ts +0 -39
- package/examples/git/wrangler.jsonc +0 -32
package/dist/mcp-owner.js
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
import { DurableObject } from "cloudflare:workers";
|
|
2
2
|
import { nanoid } from "@fungi.computer/cairn";
|
|
3
|
-
import { createMcp, createMcpCatalog, } from "@fungi.computer/mycelium/mcp";
|
|
4
3
|
import { createCodemodeSandbox } from "@fungi.computer/mycelium/workers";
|
|
5
|
-
import { parseJSONRPCMessage, serializeMessage, } from "@modelcontextprotocol/server";
|
|
6
4
|
import * as Schema from "effect/Schema";
|
|
7
5
|
import { oysterMcpCatalogLoad } from "./mcp.js";
|
|
8
6
|
import { tenantIdSchema, tenantScope } from "./tenant.js";
|
|
7
|
+
/**
|
|
8
|
+
* The MCP protocol SDK builds hundreds of schemas when it loads. Only the first
|
|
9
|
+
* MCP request needs them, so the owner loads it then and the Worker's startup
|
|
10
|
+
* (which every request and every deploy pays) never does.
|
|
11
|
+
*/
|
|
12
|
+
const loadMcpProtocol = async () => {
|
|
13
|
+
const [mycelium, sdk] = await Promise.all([
|
|
14
|
+
import("@fungi.computer/mycelium/mcp"),
|
|
15
|
+
import("@modelcontextprotocol/server"),
|
|
16
|
+
]);
|
|
17
|
+
return { mycelium, sdk };
|
|
18
|
+
};
|
|
9
19
|
const pinSchema = Schema.Struct({
|
|
10
20
|
tenant_id: tenantIdSchema,
|
|
11
21
|
origin: Schema.String,
|
|
@@ -37,7 +47,7 @@ export class OysterMcpOwner extends DurableObject {
|
|
|
37
47
|
ctx.storage.sql.exec("DELETE FROM oyster_mcp_sessions");
|
|
38
48
|
});
|
|
39
49
|
}
|
|
40
|
-
#eventStore(catalogId) {
|
|
50
|
+
#eventStore(catalogId, { sdk }) {
|
|
41
51
|
const sql = this.ctx.storage.sql;
|
|
42
52
|
const find = (eventId) => {
|
|
43
53
|
const row = sql
|
|
@@ -50,7 +60,7 @@ export class OysterMcpOwner extends DurableObject {
|
|
|
50
60
|
return {
|
|
51
61
|
storeEvent: (streamId, message) => {
|
|
52
62
|
const id = nanoid();
|
|
53
|
-
sql.exec("INSERT INTO oyster_mcp_events (catalog_id, event_id, stream_id, message) VALUES (?,?,?,?)", catalogId, id, streamId, serializeMessage(message));
|
|
63
|
+
sql.exec("INSERT INTO oyster_mcp_events (catalog_id, event_id, stream_id, message) VALUES (?,?,?,?)", catalogId, id, streamId, sdk.serializeMessage(message));
|
|
54
64
|
return Promise.resolve(id);
|
|
55
65
|
},
|
|
56
66
|
getStreamIdForEventId: (id) => Promise.resolve(find(id)?.stream_id),
|
|
@@ -63,7 +73,7 @@ export class OysterMcpOwner extends DurableObject {
|
|
|
63
73
|
.toArray();
|
|
64
74
|
for (const raw of rows) {
|
|
65
75
|
const row = Schema.decodeUnknownSync(eventSchema)(raw);
|
|
66
|
-
await send(row.event_id, parseJSONRPCMessage(JSON.parse(row.message)));
|
|
76
|
+
await send(row.event_id, sdk.parseJSONRPCMessage(JSON.parse(row.message)));
|
|
67
77
|
}
|
|
68
78
|
return after.stream_id;
|
|
69
79
|
},
|
|
@@ -87,10 +97,12 @@ export class OysterMcpOwner extends DurableObject {
|
|
|
87
97
|
else {
|
|
88
98
|
sql.exec("INSERT INTO oyster_mcp_tenant (id, tenant_id, origin) VALUES (1,?,?)", scope.tenantId, scope.origin);
|
|
89
99
|
}
|
|
100
|
+
// Loaded before the check so the session map is still created exactly once.
|
|
101
|
+
const protocol = await loadMcpProtocol();
|
|
90
102
|
if (this.#mcp === undefined) {
|
|
91
103
|
const host = this.#createHost(this.env, scope);
|
|
92
104
|
const persistence = {
|
|
93
|
-
eventStore: (id) => this.#eventStore(id),
|
|
105
|
+
eventStore: (id) => this.#eventStore(id, protocol),
|
|
94
106
|
initialized: (id, session) => {
|
|
95
107
|
sql.exec("INSERT INTO oyster_mcp_sessions (protocol_id, catalog_id, principal) VALUES (?,?,?)", id, session.id, JSON.stringify(session.principal));
|
|
96
108
|
return Promise.resolve();
|
|
@@ -103,9 +115,9 @@ export class OysterMcpOwner extends DurableObject {
|
|
|
103
115
|
return Promise.resolve();
|
|
104
116
|
},
|
|
105
117
|
};
|
|
106
|
-
this.#mcp = createMcp({
|
|
118
|
+
this.#mcp = protocol.mycelium.createMcp({
|
|
107
119
|
persistence,
|
|
108
|
-
catalog: createMcpCatalog({
|
|
120
|
+
catalog: protocol.mycelium.createMcpCatalog({
|
|
109
121
|
load: oysterMcpCatalogLoad(host),
|
|
110
122
|
sandbox: createCodemodeSandbox(this.env.LOADER),
|
|
111
123
|
}),
|
package/dist/pack.d.ts
CHANGED
|
@@ -62,9 +62,12 @@ export declare function installRelease(input: Readonly<{
|
|
|
62
62
|
key: string;
|
|
63
63
|
installationId?: string;
|
|
64
64
|
}>): Promise<Response>;
|
|
65
|
-
/** Project one exact first-party commit's editable source
|
|
66
|
-
* an Oyster's own template
|
|
67
|
-
*
|
|
65
|
+
/** Project one exact first-party commit's editable source, `git push` it to
|
|
66
|
+
* an Oyster's own template for that App, and claim the pushed head, so
|
|
67
|
+
* `APPS.fork` and Computer builds can later read it. The operator owner key
|
|
68
|
+
* authenticates both; it reaches Git only through its config environment and
|
|
69
|
+
* no provider credential ever leaves the Oyster. Answers the claim response:
|
|
70
|
+
* `{ kind: "recorded" | "existing", receipt }` on success. */
|
|
68
71
|
export declare function publishSource(input: Readonly<{
|
|
69
72
|
appId: string;
|
|
70
73
|
entry: string;
|
package/dist/pack.js
CHANGED
|
@@ -5,8 +5,9 @@ import * as Effect from "effect/Effect";
|
|
|
5
5
|
import * as Schema from "effect/Schema";
|
|
6
6
|
import { releaseSchema } from "@fungi.computer/puffball/effect";
|
|
7
7
|
import { parseArtifactRef, parseReleaseId, parseSha256Digest, parseSourceRevision, } from "@fungi.computer/puffball";
|
|
8
|
-
import { commitPreparedCandidate, createAppCandidatePreparer, createArtifactsSourceCapture, createFirstPartyAppSourceDistribution, createLocalArtifactWriter, createLocalBuildExecutor, createLocalCommitSourceCapture, } from "./build.js";
|
|
9
|
-
import {
|
|
8
|
+
import { commitPreparedCandidate, createAppCandidatePreparer, createArtifactsSourceCapture, createFirstPartyAppSourceDistribution, createLocalArtifactWriter, createLocalBuildExecutor, createLocalCommitSourceCapture, normalizeAppPublisherRecipe, } from "./build.js";
|
|
9
|
+
import { templateSeedClaimSchema, } from "./source/template-seed.js";
|
|
10
|
+
import { pushTemplateSource } from "./source/template-push.js";
|
|
10
11
|
import { sha256 } from "./release.js";
|
|
11
12
|
async function filesUnder(directory) {
|
|
12
13
|
const entries = await readdir(directory, {
|
|
@@ -103,9 +104,12 @@ export async function installRelease(input) {
|
|
|
103
104
|
}),
|
|
104
105
|
});
|
|
105
106
|
}
|
|
106
|
-
/** Project one exact first-party commit's editable source
|
|
107
|
-
* an Oyster's own template
|
|
108
|
-
*
|
|
107
|
+
/** Project one exact first-party commit's editable source, `git push` it to
|
|
108
|
+
* an Oyster's own template for that App, and claim the pushed head, so
|
|
109
|
+
* `APPS.fork` and Computer builds can later read it. The operator owner key
|
|
110
|
+
* authenticates both; it reaches Git only through its config environment and
|
|
111
|
+
* no provider credential ever leaves the Oyster. Answers the claim response:
|
|
112
|
+
* `{ kind: "recorded" | "existing", receipt }` on success. */
|
|
109
113
|
export async function publishSource(input) {
|
|
110
114
|
const revision = parseSourceRevision(input.sourceRevision);
|
|
111
115
|
const distribution = await Effect.runPromise(Effect.scoped(Effect.gen(function* () {
|
|
@@ -116,19 +120,32 @@ export async function publishSource(input) {
|
|
|
116
120
|
source,
|
|
117
121
|
});
|
|
118
122
|
})));
|
|
119
|
-
const
|
|
123
|
+
const app = encodeURIComponent(input.appId);
|
|
124
|
+
const authorization = `Bearer ${input.key}`;
|
|
125
|
+
const { seedRevision } = await pushTemplateSource({
|
|
120
126
|
appId: input.appId,
|
|
121
127
|
sourceRevision: revision,
|
|
122
|
-
sourceTreeDigest: distribution.sourceTreeDigest,
|
|
123
|
-
recipe: input.recipe,
|
|
124
128
|
files: distribution.files,
|
|
129
|
+
remote: new URL(`/api/apps/${app}/source.git`, input.url).href,
|
|
130
|
+
headers: { Authorization: authorization },
|
|
125
131
|
});
|
|
126
|
-
|
|
132
|
+
const claim = {
|
|
133
|
+
appId: distribution.appId,
|
|
134
|
+
sourceRevision: revision,
|
|
135
|
+
sourceTreeDigest: distribution.sourceTreeDigest,
|
|
136
|
+
seedRevision,
|
|
137
|
+
// The template pins the same captured recipe its forks build with.
|
|
138
|
+
recipe: normalizeAppPublisherRecipe({
|
|
139
|
+
appId: input.appId,
|
|
140
|
+
recipe: input.recipe,
|
|
141
|
+
}).recipe,
|
|
142
|
+
};
|
|
143
|
+
return fetch(new URL(`/api/apps/${app}/source`, input.url), {
|
|
127
144
|
method: "PUT",
|
|
128
145
|
headers: {
|
|
129
|
-
Authorization:
|
|
146
|
+
Authorization: authorization,
|
|
130
147
|
"Content-Type": "application/json",
|
|
131
148
|
},
|
|
132
|
-
body: JSON.stringify(
|
|
149
|
+
body: JSON.stringify(Schema.encodeUnknownSync(templateSeedClaimSchema)(claim)),
|
|
133
150
|
});
|
|
134
151
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** One desktop observation notifies the shell until it is closed. */
|
|
2
|
+
export declare function observeDesktopGill(onChanged: () => Promise<void>, openSource: () => (Pick<EventTarget, "addEventListener"> & {
|
|
3
|
+
close(): void;
|
|
4
|
+
}) | null, focus: Pick<EventTarget, "addEventListener" | "removeEventListener">): () => void;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** One desktop observation notifies the shell until it is closed. */
|
|
2
|
+
export function observeDesktopGill(onChanged, openSource, focus) {
|
|
3
|
+
let closed = false;
|
|
4
|
+
const notify = () => {
|
|
5
|
+
if (!closed)
|
|
6
|
+
void onChanged().catch((cause) => {
|
|
7
|
+
console.error("Desktop Gill observation failed", cause);
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
let source = null;
|
|
11
|
+
const close = () => {
|
|
12
|
+
if (closed)
|
|
13
|
+
return;
|
|
14
|
+
closed = true;
|
|
15
|
+
source?.close();
|
|
16
|
+
focus.removeEventListener("focus", notify);
|
|
17
|
+
};
|
|
18
|
+
try {
|
|
19
|
+
source = openSource();
|
|
20
|
+
if (source !== null)
|
|
21
|
+
for (const event of ["ready", "rows", "reset", "changed"])
|
|
22
|
+
source.addEventListener(event, notify);
|
|
23
|
+
else
|
|
24
|
+
focus.addEventListener("focus", notify);
|
|
25
|
+
return close;
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
close();
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { AppOpenRequest } from "@fungi.computer/agar";
|
|
2
|
+
/**
|
|
3
|
+
* A window's current launch target: the open request and Computer its child
|
|
4
|
+
* App should see. `reconfigure` moves this forward as the window is handed a
|
|
5
|
+
* new file; a later restore (`setVisibility`) must read the latest target
|
|
6
|
+
* here, never the window's first open request.
|
|
7
|
+
*/
|
|
8
|
+
export type WindowLaunchTarget = Readonly<{
|
|
9
|
+
openRequest: AppOpenRequest | null;
|
|
10
|
+
computerId: string | null;
|
|
11
|
+
}>;
|
|
12
|
+
export type WindowLaunchTracker = Readonly<{
|
|
13
|
+
current: () => WindowLaunchTarget;
|
|
14
|
+
advance: (next: WindowLaunchTarget) => void;
|
|
15
|
+
}>;
|
|
16
|
+
/** One window's launch history: starts at its first open, moves forward on
|
|
17
|
+
* every `reconfigure`, and never resets on its own. */
|
|
18
|
+
export declare function createWindowLaunchTracker(initial: WindowLaunchTarget): WindowLaunchTracker;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** One window's launch history: starts at its first open, moves forward on
|
|
2
|
+
* every `reconfigure`, and never resets on its own. */
|
|
3
|
+
export function createWindowLaunchTracker(initial) {
|
|
4
|
+
let current = initial;
|
|
5
|
+
return {
|
|
6
|
+
current: () => current,
|
|
7
|
+
advance: (next) => {
|
|
8
|
+
current = next;
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
}
|