@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.
Files changed (171) hide show
  1. package/README.md +228 -176
  2. package/dist/app-builds.d.ts +76 -17
  3. package/dist/app-builds.js +207 -63
  4. package/dist/app-sources.d.ts +7 -22
  5. package/dist/app-sources.js +49 -14
  6. package/dist/build/artifact-writer.d.ts +40 -0
  7. package/dist/build/artifact-writer.js +181 -0
  8. package/dist/build/build-jobs.js +7 -1
  9. package/dist/build/build-policy.d.ts +10 -0
  10. package/dist/build/build-policy.js +10 -0
  11. package/dist/build/build-step.d.ts +12 -1
  12. package/dist/build/build-step.js +9 -1
  13. package/dist/build/candidate-builder.d.ts +68 -0
  14. package/dist/build/candidate-builder.js +91 -0
  15. package/dist/build/candidate-cli.d.ts +1 -0
  16. package/dist/build/candidate-cli.js +119 -0
  17. package/dist/build/candidate-cli.mjs +28170 -0
  18. package/dist/build/content-objects.d.ts +37 -0
  19. package/dist/build/content-objects.js +53 -0
  20. package/dist/build/effect.d.ts +18 -0
  21. package/dist/build/effect.js +52 -12
  22. package/dist/build/git-environment.d.ts +3 -0
  23. package/dist/build/git-environment.js +21 -9
  24. package/dist/build/local-build.d.ts +13 -3
  25. package/dist/build/local-build.js +149 -99
  26. package/dist/build/snapshot-package-store.d.ts +10 -0
  27. package/dist/build/snapshot-package-store.js +15 -0
  28. package/dist/build/snapshot-program-scripts.js +1 -1
  29. package/dist/build/snapshot-programs/snapshot-path.d.ts +2 -0
  30. package/dist/build/snapshot-programs/snapshot-path.js +6 -0
  31. package/dist/build/submission-contract.d.ts +461 -17
  32. package/dist/build/submission-contract.js +136 -26
  33. package/dist/build/workspace-build.d.ts +7 -2
  34. package/dist/build/workspace-build.js +55 -11
  35. package/dist/build-contract.d.ts +3 -0
  36. package/dist/build-contract.js +3 -0
  37. package/dist/build-egress.d.ts +62 -0
  38. package/dist/build-egress.js +148 -0
  39. package/dist/build.d.ts +1 -0
  40. package/dist/build.js +1 -0
  41. package/dist/mcp-owner.js +20 -8
  42. package/dist/pack.d.ts +6 -3
  43. package/dist/pack.js +28 -11
  44. package/dist/page/gill-observation.d.ts +4 -0
  45. package/dist/page/gill-observation.js +31 -0
  46. package/dist/page/window-launch.d.ts +18 -0
  47. package/dist/page/window-launch.js +11 -0
  48. package/dist/page-script.js +2 -2
  49. package/dist/release.d.ts +11 -0
  50. package/dist/release.js +4 -1
  51. package/dist/shell/app-directory.d.ts +13 -8
  52. package/dist/shell/app-directory.js +4 -0
  53. package/dist/shell/shell-module.d.ts +26 -2
  54. package/dist/shell/shell-module.js +51 -2
  55. package/dist/shell/workbench-directory.d.ts +332 -0
  56. package/dist/shell/workbench-directory.js +35 -0
  57. package/dist/source/index.d.ts +6 -7
  58. package/dist/source/index.js +6 -7
  59. package/dist/source/template-push.d.ts +33 -0
  60. package/dist/source/template-push.js +223 -0
  61. package/dist/source/template-repository.d.ts +2 -0
  62. package/dist/source/template-repository.js +13 -0
  63. package/dist/source/{template-write.d.ts → template-seed.d.ts} +19 -20
  64. package/dist/source/template-seed.js +80 -0
  65. package/dist/standalone/agent-directory.d.ts +4 -0
  66. package/dist/standalone/agent-directory.js +4 -0
  67. package/dist/standalone/agents.d.ts +220 -0
  68. package/dist/standalone/agents.js +795 -0
  69. package/dist/standalone/app-builds-binding.d.ts +36 -0
  70. package/dist/standalone/app-builds-binding.js +119 -0
  71. package/dist/standalone/app-set.d.ts +91 -0
  72. package/dist/standalone/app-set.js +395 -0
  73. package/dist/standalone/app-source.d.ts +41 -0
  74. package/dist/standalone/app-source.js +59 -0
  75. package/dist/standalone/builds.d.ts +18 -0
  76. package/dist/standalone/builds.js +101 -0
  77. package/dist/standalone/computer-egress.d.ts +37 -0
  78. package/dist/standalone/computer-egress.js +111 -0
  79. package/dist/standalone/computer.d.ts +122 -0
  80. package/dist/standalone/computer.js +714 -0
  81. package/dist/standalone/install-apps-cli.d.ts +2 -0
  82. package/dist/standalone/install-apps-cli.js +126 -0
  83. package/dist/standalone/instance.d.ts +35 -0
  84. package/dist/standalone/instance.js +74 -0
  85. package/dist/standalone/media.d.ts +76 -0
  86. package/dist/standalone/media.js +227 -0
  87. package/dist/standalone/preview.d.ts +51 -0
  88. package/dist/standalone/preview.js +167 -0
  89. package/dist/standalone/registry.d.ts +102 -0
  90. package/dist/standalone/registry.js +193 -0
  91. package/dist/standalone/tenancy.d.ts +42 -0
  92. package/dist/standalone/tenancy.js +241 -0
  93. package/dist/standalone/worker.d.ts +55 -0
  94. package/dist/standalone/worker.js +147 -0
  95. package/dist/standalone/workflow-authority-readers.d.ts +16 -0
  96. package/dist/standalone/workflow-authority-readers.js +35 -0
  97. package/dist/standalone/workflow-binding.d.ts +216 -0
  98. package/dist/standalone/workflow-binding.js +217 -0
  99. package/dist/standalone/workflow-events.d.ts +46 -0
  100. package/dist/standalone/workflow-events.js +39 -0
  101. package/dist/standalone/workflow-git-capture.d.ts +7 -0
  102. package/dist/standalone/workflow-git-capture.js +62 -0
  103. package/dist/standalone/workflow-host.d.ts +931 -0
  104. package/dist/standalone/workflow-host.js +751 -0
  105. package/dist/standalone/workflow-node-capture.d.ts +13 -0
  106. package/dist/standalone/workflow-node-capture.js +72 -0
  107. package/dist/standalone/workflow-root-authority.d.ts +27 -0
  108. package/dist/standalone/workflow-root-authority.js +87 -0
  109. package/dist/standalone/workflow-schedules.d.ts +52 -0
  110. package/dist/standalone/workflow-schedules.js +40 -0
  111. package/dist/standalone/workflow-trigger-host.d.ts +1694 -0
  112. package/dist/standalone/workflow-trigger-host.js +52 -0
  113. package/dist/standalone/workflows.d.ts +657 -0
  114. package/dist/standalone/workflows.js +554 -0
  115. package/dist/tenancy/auth0.d.ts +64 -0
  116. package/dist/tenancy/auth0.js +198 -0
  117. package/dist/tenancy/cloudflare/rate-limit.d.ts +8 -0
  118. package/dist/tenancy/cloudflare/rate-limit.js +19 -0
  119. package/dist/tenancy/cloudflare/registry.d.ts +50 -0
  120. package/dist/tenancy/cloudflare/registry.js +301 -0
  121. package/dist/tenancy/cloudflare.d.ts +2 -0
  122. package/dist/tenancy/cloudflare.js +2 -0
  123. package/dist/tenancy/cold-directory.d.ts +13 -0
  124. package/dist/tenancy/cold-directory.js +43 -0
  125. package/dist/tenancy/contracts.d.ts +195 -0
  126. package/dist/tenancy/contracts.js +1 -0
  127. package/dist/tenancy/index.d.ts +19 -0
  128. package/dist/tenancy/index.js +156 -0
  129. package/dist/tenancy/owner-key.d.ts +26 -0
  130. package/dist/tenancy/owner-key.js +63 -0
  131. package/dist/tenancy/oyster.d.ts +4 -0
  132. package/dist/tenancy/oyster.js +25 -0
  133. package/dist/tenancy/registry-record.d.ts +7 -0
  134. package/dist/tenancy/registry-record.js +44 -0
  135. package/dist/tenancy/registry.d.ts +27 -0
  136. package/dist/tenancy/registry.js +138 -0
  137. package/dist/tenancy/resolve.d.ts +7 -0
  138. package/dist/tenancy/resolve.js +64 -0
  139. package/dist/workers.d.ts +2 -2
  140. package/dist/workers.js +3 -1
  141. package/docs/api/build-contract.md +829 -32
  142. package/docs/api/build.md +253 -17
  143. package/docs/api/modules.md +40 -32
  144. package/docs/api/pack.md +6 -3
  145. package/docs/api/shell/app-directory.md +267 -0
  146. package/docs/api/shell/shell-module.md +64 -3
  147. package/docs/api/shell/workbench-directory.md +119 -0
  148. package/docs/api/shell/workbench.md +19 -0
  149. package/docs/api/shell.md +127 -0
  150. package/docs/api/tenancy/auth0.md +110 -0
  151. package/docs/api/tenancy/cloudflare.md +221 -0
  152. package/docs/api/tenancy/cold-directory.md +46 -0
  153. package/docs/api/tenancy/owner-key.md +63 -0
  154. package/docs/api/tenancy/registry.md +77 -0
  155. package/docs/api/tenancy/resolve.md +69 -0
  156. package/docs/api/tenancy.md +430 -0
  157. package/docs/api/workers-git.md +140 -21
  158. package/docs/api/workers.md +15 -2
  159. package/docs/site/agent-quickstart.mdx +52 -55
  160. package/examples/standalone/worker.ts +7 -14
  161. package/examples/standalone/wrangler.jsonc +67 -6
  162. package/package.json +72 -21
  163. package/dist/build/r2-artifact-writer.d.ts +0 -31
  164. package/dist/build/r2-artifact-writer.js +0 -179
  165. package/dist/source/template-write.js +0 -344
  166. package/docs/media/architecture.mmd +0 -32
  167. package/docs/media/architecture.png +0 -0
  168. package/docs/media/bonsai-network.png +0 -0
  169. package/docs/media/bonsai-repository.png +0 -0
  170. package/examples/git/worker.ts +0 -39
  171. 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 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. */
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 { templateWriteInputSchema } from "./source/template-write.js";
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 and seed it into
107
- * an Oyster's own template, so `APPS.fork` can later fork it. The operator
108
- * owner key authenticates; no provider credential ever leaves this call. */
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 body = Schema.encodeUnknownSync(templateWriteInputSchema)({
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
- return fetch(new URL(`/api/apps/${encodeURIComponent(input.appId)}/source`, input.url), {
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: `Bearer ${input.key}`,
146
+ Authorization: authorization,
130
147
  "Content-Type": "application/json",
131
148
  },
132
- body: JSON.stringify(body),
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
+ }