@fungi.computer/oyster 0.3.1 → 0.3.2

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 CHANGED
@@ -124,7 +124,8 @@ npx wrangler deploy --var OYSTER_ORIGIN:$OYSTER_URL
124
124
  ```
125
125
 
126
126
  Set `OYSTER_ORIGIN` in `wrangler.jsonc` instead if you deploy again later; every
127
- deploy replaces the Worker's variables.
127
+ deploy replaces the Worker's variables. If you rename the Worker's `name` in
128
+ `wrangler.jsonc`, update `OYSTER_ORIGIN` to that Worker's URL at the same time.
128
129
 
129
130
  ### API-token permissions and teardown
130
131
 
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { type BootUi, type UiBootCheckOutcome } from "./boot-gate.js";
3
3
  import type { BootPageResult, BootPageTarget } from "./browser/boot.js";
4
- export declare const BOOT_CAPABILITY_HEADER = "x-oyster-boot-capability";
4
+ export declare const BOOT_CAPABILITY_HEADER = "x-oyster-boot-token";
5
5
  declare const bootCapabilityAssetSchema: z.ZodObject<{
6
6
  path: z.ZodString;
7
7
  contentType: z.ZodString;
@@ -4,7 +4,7 @@ import { z } from "zod";
4
4
  import { parseArtifactRef, parseReleaseId, parseSha256Digest, } from "@fungi.computer/puffball";
5
5
  import { BOOT_DEADLINE_MS, BOOT_TRANSPORT_GRACE_MS, BootGateError, bootMountDiagnosticsSchema, } from "./boot-gate.js";
6
6
  import { defaultAssetPolicy } from "./release.js";
7
- export const BOOT_CAPABILITY_HEADER = "x-oyster-boot-capability";
7
+ export const BOOT_CAPABILITY_HEADER = "x-oyster-boot-token";
8
8
  const bootCapabilityAssetSchema = z.strictObject({
9
9
  path: z.string().min(1).max(4096),
10
10
  contentType: z.string().min(1).max(256),
package/dist/mcp.d.ts CHANGED
@@ -3,8 +3,15 @@ import type { DeclaredOperations, Installation } from "./apps/installation.js";
3
3
  import { type CapabilitySource, type Grants, type ModuleRegistration } from "@fungi.computer/mycelium";
4
4
  import { appToolSchemas } from "@fungi.computer/forage";
5
5
  import type { OysterHost, OysterPrincipal } from "./host.js";
6
- import type { McpPrincipal, McpCatalogSnapshot } from "@fungi.computer/mycelium/mcp";
6
+ import type { TenantPrincipal } from "./tenant.js";
7
7
  import { type VerifiedRelease } from "./release.js";
8
+ /** Captured capabilities and grants offered to one admitted MCP caller. */
9
+ export type OysterMcpCatalogSnapshot = Readonly<{
10
+ instructions: string;
11
+ sources: readonly CapabilitySource[];
12
+ /** An omitted module has no grant. */
13
+ grants: Grants;
14
+ }>;
8
15
  /** An installation ID as a guest binding name: `spawn-git` becomes `SPAWN_GIT`. */
9
16
  export declare function appBindingName(installationId: string): string;
10
17
  /** Why an App method call from the MCP catalog was refused. */
@@ -98,4 +105,4 @@ export declare function oysterComputerTools(host: Pick<OysterHost, "computerId"
98
105
  * still re-admits the key, rechecks that same stored approval, and passes
99
106
  * the App's gate.
100
107
  */
101
- export declare function oysterMcpCatalogLoad(host: Pick<OysterHost, "installations" | "instances" | "artifacts" | "guestLimits" | "backgroundOriginKey" | "computerId" | "computer" | "capabilities" | "appToolPolicy" | "keys" | "workflows">): (principal: McpPrincipal) => Promise<McpCatalogSnapshot>;
108
+ export declare function oysterMcpCatalogLoad(host: Pick<OysterHost, "installations" | "instances" | "artifacts" | "guestLimits" | "backgroundOriginKey" | "computerId" | "computer" | "capabilities" | "appToolPolicy" | "keys" | "workflows">): (principal: TenantPrincipal) => Promise<OysterMcpCatalogSnapshot>;
package/dist/mcp.js CHANGED
@@ -388,6 +388,7 @@ export async function oysterComputerTools(host, principal) {
388
388
  id,
389
389
  name: "workspace",
390
390
  namespace: ["computers"],
391
+ confirmCalls: true,
391
392
  computer,
392
393
  }),
393
394
  grants: { [id]: "write" },
@@ -2,11 +2,16 @@ import { type StandaloneTenancyEnv } from "./tenancy.js";
2
2
  import type { PostEnvelope } from "@fungi.computer/carrier/post";
3
3
  import { type StandaloneMachineLinkBindings, type StandaloneMachineJoinResult } from "./machine-link.js";
4
4
  import { type MachineAgentSettlementPrincipal, type MachineAgentHome, type MachineEnrollment, type MachineLinkCaller, type MachineLinkIdentity } from "../machine-link/index.js";
5
+ /** Workers AI binding surface used by the standalone provider adapter. */
6
+ export type AiBinding = {
7
+ aiGatewayLogId: string | null;
8
+ /** AI binding transport supplied by the Worker runtime. */
9
+ fetch?: (input: Request | string | URL, init?: RequestInit) => Promise<Response>;
10
+ };
5
11
  import { DurableObject } from "cloudflare:workers";
6
12
  import type { StandaloneComputerEnv } from "./computer.js";
7
13
  import { ShiitakeDO } from "@fungi.computer/shiitake/cloudflare";
8
14
  import type { CloudflareShiitakeOptions } from "@fungi.computer/shiitake/cloudflare";
9
- import { type AiBinding } from "@earendil-works/pi-ai/api/cloudflare-ai-binding";
10
15
  import * as Schema from "effect/Schema";
11
16
  import { type FrameBindingProps } from "../cloudflare.js";
12
17
  import type { Avatar } from "@fungi.computer/sprite/avatar";
@@ -1,3 +1,4 @@
1
+ import { normalizeModel, streamModel } from "@fungi.computer/mule/effect";
1
2
  import { observeProviderStatus } from "@fungi.computer/mule/effect";
2
3
  import { standaloneTenancy, } from "./tenancy.js";
3
4
  import { AcceptedSchema } from "@fungi.computer/shiitake/client";
@@ -41,18 +42,18 @@ const defaultAgentModel = "@cf/moonshotai/kimi-k2.6";
41
42
  /** Own transport evidence and exact-message decisions for one model lease. */
42
43
  function observedModels(model, defaults) {
43
44
  return {
44
- acquire: async ({ signal }) => {
45
- const streamSimple = await loadStreamSimple();
45
+ acquire: ({ signal }) => {
46
+ const streamSimple = streamModel;
46
47
  const retryableMessages = new WeakSet();
47
- return {
48
+ return Promise.resolve({
48
49
  model,
49
50
  retryable: (message) => !signal.aborted &&
50
51
  message.stopReason === "error" &&
51
52
  retryableMessages.has(message),
52
- stream: (selected, context, options) => {
53
+ stream: async (selected, context, options) => {
53
54
  const upstream = { ...options, ...defaults };
54
55
  const observed = observeProviderStatus(upstream);
55
- const response = streamSimple(selected, context, {
56
+ const response = await streamSimple(selected, context, {
56
57
  ...upstream,
57
58
  fetch: observed.fetch,
58
59
  onProviderStreamEvent: observed.onProviderStreamEvent,
@@ -69,7 +70,7 @@ function observedModels(model, defaults) {
69
70
  return response;
70
71
  },
71
72
  release: () => Promise.resolve(),
72
- };
73
+ });
73
74
  },
74
75
  };
75
76
  }
@@ -89,7 +90,7 @@ function workersAiModels(ai, options = {}) {
89
90
  baseUrl: `https://workers-binding.ai/ai-gateway/gateways/${options.gateway ?? "default"}/workers-ai/v1`,
90
91
  };
91
92
  const fetch = createAiBindingFetch(ai);
92
- return observedModels(model, {
93
+ return observedModels(normalizeModel(model), {
93
94
  fetch,
94
95
  apiKey: CLOUDFLARE_GATEWAY_BINDING_AUTH_SENTINEL,
95
96
  headers: {
@@ -100,11 +101,6 @@ function workersAiModels(ai, options = {}) {
100
101
  },
101
102
  });
102
103
  }
103
- /**
104
- * Pi's dispatching `streamSimple` evaluates every provider's model catalog when
105
- * it loads; only a run needs it, so acquisition loads it instead of startup.
106
- */
107
- const loadStreamSimple = async () => (await import("@earendil-works/pi-ai/compat")).streamSimple;
108
104
  const policyDirectorySchema = Schema.Struct({
109
105
  directoryName: Schema.NonEmptyString,
110
106
  tenantId: tenantIdSchema,
@@ -354,7 +350,7 @@ export class StandaloneAgentBase extends ShiitakeDO {
354
350
  if (listed === undefined)
355
351
  throw new Error("The OpenAI catalog has no Luna model");
356
352
  const model = { ...listed, id: "openai/gpt-6-luna", baseUrl };
357
- return observedModels(model, {
353
+ return observedModels(normalizeModel(model), {
358
354
  apiKey: "oyster-provider-egress",
359
355
  fetch: providerFetch(egress, "ai-gateway"),
360
356
  }).acquire(input);
@@ -203,7 +203,7 @@ export async function dispatchAppsBuildsBinding(env, scope, frame, props, method
203
203
  const registry = buildRegistry(env, scope);
204
204
  const build = "repo" in input
205
205
  ? await registry.readRepositoryBuild(frame.facts.selection.owner.id, input.repo)
206
- : await registry.readBuild(input.jobId);
206
+ : await registry.readInstallationBuild(frame.facts.selection.owner.id, input.jobId);
207
207
  frame.signal.throwIfAborted();
208
208
  const missingJob = build === null && "jobId" in input;
209
209
  return Schema.decodeUnknownSync(Schema.Json)({
@@ -37,6 +37,7 @@ export type StandaloneRegistryRpc = {
37
37
  registerRepositoryApp(input: Parameters<StandaloneRegistry["registerRepositoryApp"]>[0]): ReturnType<StandaloneRegistry["registerRepositoryApp"]>;
38
38
  repositoryBuildSource(installationId: string, repository: string): ReturnType<StandaloneRegistry["repositoryBuildSource"]>;
39
39
  readRepositoryBuild(installationId: string, repository: string): ReturnType<StandaloneRegistry["readRepositoryBuild"]>;
40
+ readInstallationBuild(installationId: string, jobId: string): ReturnType<StandaloneRegistry["readInstallationBuild"]>;
40
41
  installWorkflowBuild(jobId: string, target: string, release: Release, memberId: string, callerInstallationId: string): ReturnType<StandaloneRegistry["installWorkflowBuild"]>;
41
42
  };
42
43
  export declare abstract class StandaloneRegistry extends OysterRegistry implements StandaloneRegistryRpc {
@@ -169,6 +170,8 @@ export declare abstract class StandaloneRegistry extends OysterRegistry implemen
169
170
  appId: import("@fungi.computer/puffball").AppId;
170
171
  } | null>;
171
172
  readBuild(jobId: string): Promise<OysterBuildView | null>;
173
+ /** A job ID alone never grants another installation access to its status. */
174
+ readInstallationBuild(installationId: string, jobId: string): Promise<OysterBuildView | null>;
172
175
  /** Repository status belongs to the same captured Git owner as registration. */
173
176
  readRepositoryBuild(installationId: string, repository: string): Promise<Readonly<{
174
177
  jobId: string;
@@ -363,6 +363,15 @@ export class StandaloneRegistry extends OysterRegistry {
363
363
  async readBuild(jobId) {
364
364
  return (await this.#appBuilds()).read(jobId);
365
365
  }
366
+ /** A job ID alone never grants another installation access to its status. */
367
+ async readInstallationBuild(installationId, jobId) {
368
+ const builds = await this.#appBuilds();
369
+ const build = builds.read(jobId);
370
+ return build !== null &&
371
+ sourceOwnedBy(builds.readSource(build.appId), installationId)
372
+ ? build
373
+ : null;
374
+ }
366
375
  /** Repository status belongs to the same captured Git owner as registration. */
367
376
  async readRepositoryBuild(installationId, repository) {
368
377
  const latest = (await this.#appBuilds()).latestRepositoryBuild(installationId, repository);
@@ -7,12 +7,23 @@ export type StandaloneSeed = Readonly<{
7
7
  origin: string;
8
8
  owner: Identity;
9
9
  }>;
10
+ export type StandaloneSeedRefusal = Readonly<{
11
+ kind: "refused";
12
+ code: "seed_configuration_changed";
13
+ seededOrigin: string;
14
+ configuredOrigin: string;
15
+ recovery: "fresh_worker_name_or_reset_worker_data";
16
+ }>;
17
+ export declare class StandaloneSeedConfigurationChanged extends Error {
18
+ readonly refusal: StandaloneSeedRefusal;
19
+ constructor(refusal: StandaloneSeedRefusal);
20
+ }
10
21
  /** A fresh registry namespace with one host-provisioned standalone tenant. */
11
22
  export declare class StandaloneTenants extends TenancyRegistryDO {
12
23
  #private;
13
24
  constructor(state: DurableObjectState);
14
25
  /** Reuse only the exact configured tenant and its current canonical owner. */
15
- seed(input: StandaloneSeed): Promise<Tenant>;
26
+ seed(input: StandaloneSeed): Promise<Tenant | StandaloneSeedRefusal>;
16
27
  }
17
28
  /** The same trusted bindings are used by HTTP, MCP and cold root readers. */
18
29
  export type StandaloneTenancyEnv = Pick<OysterGitWorkerEnv, "OYSTER_ORIGIN" | "OYSTER_OWNER_NAME" | "OYSTER_OWNER_KEY" | "KEYS"> & Readonly<{
@@ -38,5 +49,7 @@ export type StandaloneTenancy = Readonly<{
38
49
  oyster: OysterTenancy;
39
50
  owner(): Promise<StandaloneOwner | null>;
40
51
  }>;
52
+ /** A request may seed the standalone tenant only at its configured origin. */
53
+ export declare function standaloneTenancyForRequest(env: StandaloneTenancyEnv, request: Request): Promise<StandaloneTenancy>;
41
54
  /** Seed once per environment, then admit every entry from one fresh registry read. */
42
55
  export declare function standaloneTenancy(env: StandaloneTenancyEnv): Promise<StandaloneTenancy>;
@@ -3,6 +3,7 @@ import { createTenancy, } from "../tenancy/index.js";
3
3
  import { ownerKey } from "../tenancy/owner-key.js";
4
4
  import { hosts } from "../tenancy/resolve.js";
5
5
  import { ownerOrKeyAuth } from "../auth.js";
6
+ import { HTTPException } from "hono/http-exception";
6
7
  import { defaultTenant, tenantScope, } from "../tenant.js";
7
8
  import * as Schema from "effect/Schema";
8
9
  const seedSchema = Schema.Struct({
@@ -13,6 +14,14 @@ const seedSchema = Schema.Struct({
13
14
  }),
14
15
  });
15
16
  const seedKey = "standalone-owner-seed";
17
+ export class StandaloneSeedConfigurationChanged extends Error {
18
+ refusal;
19
+ constructor(refusal) {
20
+ super("Standalone seed configuration changed");
21
+ this.name = "StandaloneSeedConfigurationChanged";
22
+ this.refusal = refusal;
23
+ }
24
+ }
16
25
  /** A fresh registry namespace with one host-provisioned standalone tenant. */
17
26
  export class StandaloneTenants extends TenancyRegistryDO {
18
27
  #state;
@@ -31,8 +40,15 @@ export class StandaloneTenants extends TenancyRegistryDO {
31
40
  const marker = Schema.decodeUnknownSync(seedSchema)(raw);
32
41
  if (marker.origin !== scope.origin ||
33
42
  marker.owner.provider !== input.owner.provider ||
34
- marker.owner.subject !== input.owner.subject)
35
- throw new Error("Standalone seed configuration changed");
43
+ marker.owner.subject !== input.owner.subject) {
44
+ return {
45
+ kind: "refused",
46
+ code: "seed_configuration_changed",
47
+ seededOrigin: marker.origin,
48
+ configuredOrigin: scope.origin,
49
+ recovery: "fresh_worker_name_or_reset_worker_data",
50
+ };
51
+ }
36
52
  }
37
53
  let tenant = await this.get(defaultTenant);
38
54
  if (tenant === null) {
@@ -69,6 +85,22 @@ export class StandaloneTenants extends TenancyRegistryDO {
69
85
  });
70
86
  }
71
87
  }
88
+ /** A request may seed the standalone tenant only at its configured origin. */
89
+ export async function standaloneTenancyForRequest(env, request) {
90
+ const configuredOrigin = tenantScope(defaultTenant, env.OYSTER_ORIGIN).origin;
91
+ const requestOrigin = new URL(request.url).origin;
92
+ if (requestOrigin !== configuredOrigin)
93
+ throw new HTTPException(404, {
94
+ res: Response.json({
95
+ kind: "refused",
96
+ code: "origin_mismatch",
97
+ configuredOrigin,
98
+ requestOrigin,
99
+ message: `Request origin ${requestOrigin} does not match configured OYSTER_ORIGIN ${configuredOrigin}. Set OYSTER_ORIGIN to this Worker's URL.`,
100
+ }, { status: 404 }),
101
+ });
102
+ return standaloneTenancy(env);
103
+ }
72
104
  const environments = new WeakMap();
73
105
  /** Seed once per environment, then admit every entry from one fresh registry read. */
74
106
  export function standaloneTenancy(env) {
@@ -90,10 +122,12 @@ async function composeTenancy(env) {
90
122
  provider: "standalone-owner",
91
123
  subject: env.OYSTER_OWNER_NAME ?? "owner",
92
124
  };
93
- await env.TENANTS.getByName("registry").seed({
125
+ const seeded = await env.TENANTS.getByName("registry").seed({
94
126
  origin: scope.origin,
95
127
  owner: identity,
96
128
  });
129
+ if ("kind" in seeded)
130
+ throw new StandaloneSeedConfigurationChanged(seeded);
97
131
  const registry = durableObjectRegistry(env.TENANTS, { readonly: true });
98
132
  /** The canonical owner, only while the one admitted record still proves it. */
99
133
  const ownerOf = ({ tenant, membership, }) => tenant.id !== defaultTenant ||
@@ -17,7 +17,7 @@ import { StandaloneInstance as ComputerInstance } from "./instance.js";
17
17
  // Every guest binding in every frame is this one entrypoint, routed by props.
18
18
  export { OysterBinding } from "./instance.js";
19
19
  import { StandaloneWorkflowRunsBase, } from "./workflows.js";
20
- import { standaloneTenancy } from "./tenancy.js";
20
+ import { standaloneTenancyForRequest, StandaloneSeedConfigurationChanged, } from "./tenancy.js";
21
21
  export { StandaloneTenants } from "./tenancy.js";
22
22
  import { standalonePreviewRequest } from "./preview.js";
23
23
  import { standaloneMachineLinkRoutes, } from "./machine-link.js";
@@ -25,6 +25,7 @@ export { MachineLinkDO } from "./machine-link.js";
25
25
  import { standaloneWorkflowControl } from "./workflow-control.js";
26
26
  import { standaloneWorkflowWebhookRoutes, standaloneWorkflowWebhookControls, } from "./workflow-webhooks.js";
27
27
  import { Hono } from "hono";
28
+ import { HTTPException } from "hono/http-exception";
28
29
  import { standaloneBuildRoutes } from "./builds.js";
29
30
  import { standaloneComputerFleetRoutes, standaloneComputerSelection, } from "./computer-fleet.js";
30
31
  import { serveOysterComputerEgress, } from "./computer-egress.js";
@@ -59,7 +60,7 @@ export { AgentDirectory } from "./agents.js";
59
60
  const standaloneRoot = (selectComputer) => {
60
61
  const root = oysterGitRoot({
61
62
  tenancy: (env) => ({
62
- resolve: async (request) => (await standaloneTenancy(env)).oyster.resolve(request),
63
+ resolve: async (request) => (await standaloneTenancyForRequest(env, request)).oyster.resolve(request),
63
64
  }),
64
65
  homeInstallationId: (env) => env.OYSTER_HOME_INSTALLATION,
65
66
  agents: standaloneAgents,
@@ -153,14 +154,21 @@ export class OysterComputerEgress extends WorkerEntrypoint {
153
154
  });
154
155
  }
155
156
  }
157
+ const standaloneWorkerErrorHandler = (error, context) => {
158
+ if (error instanceof StandaloneSeedConfigurationChanged) {
159
+ const { refusal } = error;
160
+ return context.json({
161
+ ...refusal,
162
+ message: `Standalone seed/origin mismatch: existing data was seeded for ${refusal.seededOrigin}, but OYSTER_ORIGIN is ${refusal.configuredOrigin}. Deploy under a fresh Worker name or reset this Worker's data.`,
163
+ }, 409);
164
+ }
165
+ if (error instanceof HTTPException)
166
+ return error.getResponse();
167
+ console.error(error);
168
+ return context.text("Internal Server Error", 500);
169
+ };
156
170
  export default {
157
171
  async fetch(request, env, context) {
158
- const path = new URL(request.url).pathname;
159
- if (path === "/local/mesh/join" ||
160
- new URLPattern({
161
- pathname: "/api/teams/:teamId/local/mesh/:deviceId/connect",
162
- }).test(request.url))
163
- return standaloneMachineLinkRoutes(env).fetch(request);
164
172
  const preview = await standalonePreviewRequest(request, env, (computerId) => computerRegistry(env).ownsComputer(computerId), (computerId, signature, port, service) => env.COMPUTER.getByName(computerId).portPreview(signature, port, service));
165
173
  if (preview !== null)
166
174
  return preview;
@@ -171,6 +179,13 @@ export default {
171
179
  if (fetchOyster === undefined)
172
180
  throw new Error("Oyster fetch handler is missing");
173
181
  return new Hono()
182
+ .onError(standaloneWorkerErrorHandler)
183
+ .all("/local/mesh/join", () => standaloneMachineLinkRoutes(env)
184
+ .onError(standaloneWorkerErrorHandler)
185
+ .fetch(request, env, context))
186
+ .all("/api/teams/:teamId/local/mesh/:deviceId/connect", () => standaloneMachineLinkRoutes(env)
187
+ .onError(standaloneWorkerErrorHandler)
188
+ .fetch(request, env, context))
174
189
  .route("/", standaloneWorkflowWebhookRoutes(env, async (incoming) => {
175
190
  const admitted = await oysterRoot.admit(env, incoming);
176
191
  return admitted === null
@@ -178,6 +193,6 @@ export default {
178
193
  : oysterRoot.hostFor(env, admitted.scope);
179
194
  }))
180
195
  .all("*", () => fetchOyster(request, env, context))
181
- .fetch(request);
196
+ .fetch(request, env, context);
182
197
  },
183
198
  };
@@ -889,7 +889,15 @@ export function createOysterGitWorker(root) {
889
889
  hosts = new Map();
890
890
  environments.set(env, hosts);
891
891
  }
892
- const resolved = await root.admit(env, request);
892
+ let resolved;
893
+ try {
894
+ resolved = await root.admit(env, request);
895
+ }
896
+ catch (error) {
897
+ if (error instanceof HTTPException)
898
+ return error.getResponse();
899
+ throw error;
900
+ }
893
901
  if (resolved === null)
894
902
  return new Response("Not found", { status: 404 });
895
903
  const { scope } = resolved;
package/docs/api/mcp.md CHANGED
@@ -67,6 +67,20 @@ readonly code: string;
67
67
 
68
68
  ## Type Aliases
69
69
 
70
+ ### OysterMcpCatalogSnapshot
71
+
72
+ ```ts
73
+ type OysterMcpCatalogSnapshot = Readonly<{
74
+ instructions: string;
75
+ sources: readonly CapabilitySource[];
76
+ grants: Grants;
77
+ }>;
78
+ ```
79
+
80
+ Captured capabilities and grants offered to one admitted MCP caller.
81
+
82
+ ---
83
+
70
84
  ### OysterCallerAccess
71
85
 
72
86
  ```ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fungi.computer/oyster",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "author": "Levi <levi@technomancy.dev>",
5
5
  "description": "Run your own Apps on Cloudflare Workers or celld: installable App Releases, each with its own Durable Object, SQLite database and declared bindings",
6
6
  "type": "module",
@@ -246,33 +246,33 @@
246
246
  "@earendil-works/pi-ai": "1.3.0",
247
247
  "@fungi.computer/agar": "0.3.0",
248
248
  "@fungi.computer/bird-dog": "0.1.0",
249
- "@fungi.computer/braid": "0.2.0",
249
+ "@fungi.computer/braid": "0.2.1",
250
250
  "@fungi.computer/burrow": "0.1.0",
251
251
  "@fungi.computer/cairn": "0.1.0",
252
252
  "@fungi.computer/capnweb": "0.12.0",
253
253
  "@fungi.computer/caps": "0.2.0",
254
254
  "@fungi.computer/carrier": "0.1.0",
255
- "@fungi.computer/computer": "0.2.1",
256
- "@fungi.computer/computer-contract": "0.0.1",
255
+ "@fungi.computer/computer": "0.2.2",
256
+ "@fungi.computer/computer-contract": "0.0.2",
257
257
  "@fungi.computer/darkroom": "0.2.0",
258
258
  "@fungi.computer/ear-tag": "0.1.0",
259
259
  "@fungi.computer/forage": "0.2.0",
260
260
  "@fungi.computer/gills": "0.2.0",
261
- "@fungi.computer/knapsack": "0.2.0",
262
- "@fungi.computer/mycelium": "0.2.0",
261
+ "@fungi.computer/knapsack": "0.2.1",
262
+ "@fungi.computer/mycelium": "0.2.1",
263
263
  "@fungi.computer/nacre": "0.0.1",
264
264
  "@fungi.computer/puffball": "0.2.0",
265
265
  "@fungi.computer/seam": "0.1.0",
266
- "@fungi.computer/shiitake": "0.3.0",
267
- "@fungi.computer/shiitake-extensions": "0.1.0",
266
+ "@fungi.computer/shiitake": "0.3.1",
267
+ "@fungi.computer/shiitake-extensions": "0.1.1",
268
268
  "@fungi.computer/sigils": "0.2.0",
269
269
  "@fungi.computer/sprite": "0.2.0",
270
270
  "@fungi.computer/sprite-zdog": "0.2.0",
271
271
  "@fungi.computer/veil": "0.2.0",
272
272
  "@fungi.computer/watchdog": "0.1.0",
273
273
  "@fungi.computer/whistle": "0.1.0",
274
- "@fungi.computer/workflows-wire": "0.2.0",
275
- "@fungi.computer/zookeeper": "0.0.1",
274
+ "@fungi.computer/workflows-wire": "0.2.1",
275
+ "@fungi.computer/zookeeper": "0.0.2",
276
276
  "@modelcontextprotocol/server": "2.0.0",
277
277
  "@pnpm/lockfile.fs": "1100.2.13",
278
278
  "@pnpm/lockfile.pruner": "1100.0.27",