@fungi.computer/truffle 0.0.0-stage → 0.1.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.
Files changed (116) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +294 -2
  3. package/computerd-loopback.cjs +16 -0
  4. package/dist/auth-cli.d.ts +36 -0
  5. package/dist/auth-cli.js +351 -0
  6. package/dist/fungi-account-command.d.ts +18 -0
  7. package/dist/fungi-account-command.js +92 -0
  8. package/dist/index.d.ts +19 -0
  9. package/dist/index.js +17 -0
  10. package/dist/internal/private-workerd-sandbox-worker.mjs +27488 -0
  11. package/dist/internal/sandbox-metafile.json +6726 -0
  12. package/dist/linear-auth.d.ts +4 -0
  13. package/dist/linear-auth.js +72 -0
  14. package/dist/local-extensions.d.ts +97 -0
  15. package/dist/local-extensions.js +484 -0
  16. package/dist/local-login.d.ts +55 -0
  17. package/dist/local-login.js +167 -0
  18. package/dist/local-models.d.ts +14 -0
  19. package/dist/local-models.js +77 -0
  20. package/dist/local-skill-source.d.ts +6 -0
  21. package/dist/local-skill-source.js +357 -0
  22. package/dist/log-event.d.ts +2 -0
  23. package/dist/log-event.js +9 -0
  24. package/dist/private-bun-shiitake-host.d.ts +11 -0
  25. package/dist/private-bun-shiitake-host.js +66 -0
  26. package/dist/private-file-credential-store.d.ts +33 -0
  27. package/dist/private-file-credential-store.js +283 -0
  28. package/dist/private-files-patch.d.ts +3 -0
  29. package/dist/private-files-patch.js +86 -0
  30. package/dist/private-fungi-account.d.ts +235 -0
  31. package/dist/private-fungi-account.js +1149 -0
  32. package/dist/private-fungi-auth-error.d.ts +5 -0
  33. package/dist/private-fungi-auth-error.js +7 -0
  34. package/dist/private-fungi-mcp-connection.d.ts +22 -0
  35. package/dist/private-fungi-mcp-connection.js +143 -0
  36. package/dist/private-fungi-mcp-endpoint.d.ts +2 -0
  37. package/dist/private-fungi-mcp-endpoint.js +15 -0
  38. package/dist/private-fungi-mcp-selection.d.ts +14 -0
  39. package/dist/private-fungi-mcp-selection.js +36 -0
  40. package/dist/private-fungi-model-api.d.ts +6 -0
  41. package/dist/private-fungi-model-api.js +125 -0
  42. package/dist/private-fungi-model-catalog.d.ts +6 -0
  43. package/dist/private-fungi-model-catalog.js +78 -0
  44. package/dist/private-fungi-model-provider.d.ts +6 -0
  45. package/dist/private-fungi-model-provider.js +46 -0
  46. package/dist/private-fungi-pending-logouts.d.ts +34 -0
  47. package/dist/private-fungi-pending-logouts.js +136 -0
  48. package/dist/private-invocation-computer.d.ts +37 -0
  49. package/dist/private-invocation-computer.js +459 -0
  50. package/dist/private-invocation-files.d.ts +15 -0
  51. package/dist/private-invocation-files.js +449 -0
  52. package/dist/private-local-acp-stdio.d.ts +17 -0
  53. package/dist/private-local-acp-stdio.js +142 -0
  54. package/dist/private-local-auth-rpc.d.ts +69 -0
  55. package/dist/private-local-auth-rpc.js +273 -0
  56. package/dist/private-local-computer-attach.d.ts +49 -0
  57. package/dist/private-local-computer-attach.js +216 -0
  58. package/dist/private-local-computer-backend.d.ts +282 -0
  59. package/dist/private-local-computer-backend.js +402 -0
  60. package/dist/private-local-computer-processes.d.ts +199 -0
  61. package/dist/private-local-computer-processes.js +589 -0
  62. package/dist/private-local-default-model.d.ts +11 -0
  63. package/dist/private-local-default-model.js +45 -0
  64. package/dist/private-local-delegation.d.ts +29 -0
  65. package/dist/private-local-delegation.js +78 -0
  66. package/dist/private-local-effective-model.d.ts +19 -0
  67. package/dist/private-local-effective-model.js +14 -0
  68. package/dist/private-local-exec-launch.d.ts +41 -0
  69. package/dist/private-local-exec-launch.js +51 -0
  70. package/dist/private-local-exec-path.d.ts +6 -0
  71. package/dist/private-local-exec-path.js +40 -0
  72. package/dist/private-local-git-exclude.d.ts +2 -0
  73. package/dist/private-local-git-exclude.js +33 -0
  74. package/dist/private-local-http-dispatch.d.ts +5 -0
  75. package/dist/private-local-http-dispatch.js +14 -0
  76. package/dist/private-local-mail.d.ts +11 -0
  77. package/dist/private-local-mail.js +69 -0
  78. package/dist/private-local-model-settings.d.ts +5 -0
  79. package/dist/private-local-model-settings.js +31 -0
  80. package/dist/private-local-runtime-host.d.ts +141 -0
  81. package/dist/private-local-runtime-host.js +988 -0
  82. package/dist/private-local-service-contract.d.ts +90 -0
  83. package/dist/private-local-service-contract.js +1 -0
  84. package/dist/private-local-service-discovery.d.ts +101 -0
  85. package/dist/private-local-service-discovery.js +221 -0
  86. package/dist/private-local-service-ids.d.ts +3 -0
  87. package/dist/private-local-service-ids.js +8 -0
  88. package/dist/private-local-service-listener.d.ts +15 -0
  89. package/dist/private-local-service-listener.js +211 -0
  90. package/dist/private-local-service-owner.d.ts +1567 -0
  91. package/dist/private-local-service-owner.js +371 -0
  92. package/dist/private-local-service-process.d.ts +1 -0
  93. package/dist/private-local-service-process.js +6 -0
  94. package/dist/private-local-service.d.ts +17 -0
  95. package/dist/private-local-service.js +330 -0
  96. package/dist/private-local-session-discovery.d.ts +19 -0
  97. package/dist/private-local-session-discovery.js +157 -0
  98. package/dist/private-local-shiitake-host.d.ts +44 -0
  99. package/dist/private-local-shiitake-host.js +143 -0
  100. package/dist/private-local-subagents.d.ts +27 -0
  101. package/dist/private-local-subagents.js +77 -0
  102. package/dist/private-local-worker-server.d.ts +15 -0
  103. package/dist/private-local-worker-server.js +136 -0
  104. package/dist/private-sqlite-owner.d.ts +7 -0
  105. package/dist/private-sqlite-owner.js +103 -0
  106. package/dist/private-team-computers.d.ts +38 -0
  107. package/dist/private-team-computers.js +101 -0
  108. package/dist/private-workerd-sandbox.d.ts +3 -0
  109. package/dist/private-workerd-sandbox.js +347 -0
  110. package/dist/team-apps.d.ts +24 -0
  111. package/dist/team-apps.js +123 -0
  112. package/dist/tracker-git-credential.d.ts +15 -0
  113. package/dist/tracker-git-credential.js +87 -0
  114. package/native/bin/build.json +4037 -0
  115. package/native/bin/fungi-apply-patch +0 -0
  116. package/package.json +148 -3
@@ -0,0 +1,143 @@
1
+ import { logEvent } from "./log-event.js";
2
+ import { Hono } from "hono";
3
+ import * as Schema from "effect/Schema";
4
+ import { createShiitakeClient } from "@fungi.computer/shiitake/client";
5
+ import { createShiitakeHttpRoutes } from "@fungi.computer/shiitake/http";
6
+ import { openPrivateLocalRuntimeHost, } from "./private-local-runtime-host.js";
7
+ function asPrivateShiitakeFailure(cause, message) {
8
+ return cause instanceof Error ? cause : new Error(message, { cause });
9
+ }
10
+ function aggregatePrivateShiitakeFailures(failures, message) {
11
+ if (failures.length === 1) {
12
+ const [failure] = failures;
13
+ if (failure === undefined)
14
+ throw new Error("Missing private Shiitake failure");
15
+ return failure;
16
+ }
17
+ return new AggregateError(failures, message);
18
+ }
19
+ async function capturePrivateShiitakeCloseFailure(failures, close, message) {
20
+ try {
21
+ await close();
22
+ }
23
+ catch (cause) {
24
+ logEvent(message, cause);
25
+ failures.push(asPrivateShiitakeFailure(cause, message));
26
+ }
27
+ }
28
+ async function closePrivateShiitakeResources(failures, client, server, owner) {
29
+ if (client !== undefined)
30
+ await capturePrivateShiitakeCloseFailure(failures, client.close, "Private Shiitake client close failed");
31
+ if (server !== undefined)
32
+ await capturePrivateShiitakeCloseFailure(failures, server.close, "Private Shiitake server close failed");
33
+ await capturePrivateShiitakeCloseFailure(failures, owner.close, "Private Shiitake owner close failed");
34
+ }
35
+ /** Open one bounded private local Hono host on a loopback-only ephemeral port. */
36
+ export async function openPrivateLocalShiitakeHost(input, openServer, openHost = openPrivateLocalRuntimeHost, eventSource) {
37
+ const owner = await openHost(input);
38
+ const routes = createShiitakeHttpRoutes(owner.getSession, owner.listSessions, owner.createSession, owner.deleteSession, () => owner.extensions);
39
+ const fetch = (request) => routes.fetch(request);
40
+ let server;
41
+ let client;
42
+ try {
43
+ server = openServer(fetch);
44
+ const mountedServer = server;
45
+ const clientOptions = eventSource === undefined
46
+ ? { baseUrl: mountedServer.baseUrl }
47
+ : { baseUrl: mountedServer.baseUrl, eventSource };
48
+ const mountedClient = createShiitakeClient(clientOptions);
49
+ client = mountedClient;
50
+ let closing;
51
+ return Object.freeze({
52
+ agentId: owner.agentId,
53
+ databasePath: owner.databasePath,
54
+ baseUrl: mountedServer.baseUrl,
55
+ client: mountedClient,
56
+ session: mountedClient.session("main"),
57
+ close: () => {
58
+ closing ??= (async () => {
59
+ const failures = [];
60
+ await closePrivateShiitakeResources(failures, mountedClient, mountedServer, owner);
61
+ if (failures.length > 0)
62
+ throw aggregatePrivateShiitakeFailures(failures, "Private Shiitake host close failed");
63
+ })();
64
+ return closing;
65
+ },
66
+ });
67
+ }
68
+ catch (cause) {
69
+ const failures = [
70
+ asPrivateShiitakeFailure(cause, "Private Shiitake host initialization failed"),
71
+ ];
72
+ await closePrivateShiitakeResources(failures, client, server, owner);
73
+ throw aggregatePrivateShiitakeFailures(failures, "Private Shiitake host initialization failed");
74
+ }
75
+ }
76
+ const localWorkerNameSchema = Schema.String.pipe(Schema.check(Schema.isMinLength(1)), Schema.check(Schema.isPattern(/^[A-Za-z0-9][A-Za-z0-9_-]*$/u)));
77
+ const decodeLocalWorkerName = Schema.decodeUnknownResult(localWorkerNameSchema);
78
+ function validateLocalWorkerInputs(inputs) {
79
+ if (inputs.length === 0)
80
+ throw new Error("Private local Shiitake worker batch cannot be empty");
81
+ const names = new Set();
82
+ return inputs.map((input) => {
83
+ const decoded = decodeLocalWorkerName(input.name);
84
+ if (decoded._tag === "Failure")
85
+ throw new Error("Invalid private local Shiitake worker route identity: " + input.name);
86
+ if (names.has(decoded.success))
87
+ throw new Error("Duplicate private local Shiitake worker route identity: " +
88
+ decoded.success);
89
+ names.add(decoded.success);
90
+ return Object.freeze({ ...input, name: decoded.success });
91
+ });
92
+ }
93
+ async function closePrivateLocalWorkerBatch(failures, server, owners) {
94
+ if (server !== undefined)
95
+ await capturePrivateShiitakeCloseFailure(failures, server.close, "Private Shiitake worker server close failed");
96
+ for (const { owner } of owners)
97
+ await capturePrivateShiitakeCloseFailure(failures, owner.close, "Private Shiitake worker owner close failed");
98
+ }
99
+ /** Mount a static batch of Local Agents behind exactly one maintained Hono server. */
100
+ export async function openPrivateLocalShiitakeWorkerBatch(inputs, openServer, openHost = openPrivateLocalRuntimeHost) {
101
+ const validated = validateLocalWorkerInputs(inputs);
102
+ const owners = [];
103
+ let server;
104
+ try {
105
+ const app = new Hono();
106
+ const workers = [];
107
+ for (const input of validated) {
108
+ const owner = await openHost(input.owner);
109
+ owners.push({ name: input.name, owner });
110
+ const routes = createShiitakeHttpRoutes(owner.getSession, owner.listSessions, owner.createSession, owner.deleteSession, () => owner.extensions);
111
+ const routePath = "/workers/" + input.name;
112
+ app.route(routePath, routes);
113
+ workers.push(Object.freeze({
114
+ name: input.name,
115
+ routePath,
116
+ databasePath: owner.databasePath,
117
+ }));
118
+ }
119
+ server = openServer((request) => app.fetch(request));
120
+ const mountedServer = server;
121
+ let closing;
122
+ return Object.freeze({
123
+ baseUrl: mountedServer.baseUrl,
124
+ workers: Object.freeze(workers),
125
+ close: () => {
126
+ closing ??= (async () => {
127
+ const failures = [];
128
+ await closePrivateLocalWorkerBatch(failures, mountedServer, owners);
129
+ if (failures.length > 0)
130
+ throw aggregatePrivateShiitakeFailures(failures, "Private Shiitake worker batch close failed");
131
+ })();
132
+ return closing;
133
+ },
134
+ });
135
+ }
136
+ catch (cause) {
137
+ const failures = [
138
+ asPrivateShiitakeFailure(cause, "Private Shiitake worker batch initialization failed"),
139
+ ];
140
+ await closePrivateLocalWorkerBatch(failures, server, owners);
141
+ throw aggregatePrivateShiitakeFailures(failures, "Private Shiitake worker batch initialization failed");
142
+ }
143
+ }
@@ -0,0 +1,27 @@
1
+ import { type AgentControlDirectory } from "@fungi.computer/shiitake/agent-control";
2
+ import type { DatabaseSync } from "node:sqlite";
3
+ /**
4
+ * One Local Agent's own record of the Agents each of its Sessions created or
5
+ * sent work to. This host writes it as its Agent-control directory accepts a
6
+ * create or a send. It feeds the read-only sub-agent view and is not a managed
7
+ * parent/child relationship.
8
+ */
9
+ export declare function createPrivateLocalSubagents(input: Readonly<{
10
+ database: DatabaseSync;
11
+ directory: AgentControlDirectory;
12
+ }>): {
13
+ control: Readonly<{
14
+ id: string;
15
+ acquire: (context: import("@fungi.computer/mycelium").CapabilitySourceContext) => Promise<import("@fungi.computer/mycelium").CapabilitySourceAcquisition>;
16
+ }>;
17
+ source: Readonly<{
18
+ read: (sessionId: string, limit: number) => Promise<Readonly<{
19
+ agents: readonly import("@fungi.computer/shiitake-extensions/subagents/host").SubagentRecord[];
20
+ more: number;
21
+ }>>;
22
+ open: (agentId: string, signal: AbortSignal) => Promise<Readonly<{
23
+ agent: import("@fungi.computer/shiitake-extensions/subagents/host").SubagentAgent;
24
+ release: () => Promise<void>;
25
+ }>>;
26
+ }>;
27
+ };
@@ -0,0 +1,77 @@
1
+ import { createAgentControlSource, } from "@fungi.computer/shiitake/agent-control";
2
+ import { z } from "zod";
3
+ const subagentRow = z.lazy(() => z.strictObject({
4
+ agent_id: z.string().min(1).max(256),
5
+ session_id: z.string().min(1).max(256).nullable(),
6
+ total: z.number().int().nonnegative(),
7
+ }));
8
+ /**
9
+ * One Local Agent's own record of the Agents each of its Sessions created or
10
+ * sent work to. This host writes it as its Agent-control directory accepts a
11
+ * create or a send. It feeds the read-only sub-agent view and is not a managed
12
+ * parent/child relationship.
13
+ */
14
+ export function createPrivateLocalSubagents(input) {
15
+ input.database.exec(`CREATE TABLE IF NOT EXISTS private_local_subagents (
16
+ parent_session_id TEXT NOT NULL,
17
+ agent_id TEXT NOT NULL,
18
+ session_id TEXT,
19
+ at INTEGER NOT NULL,
20
+ PRIMARY KEY (parent_session_id, agent_id)
21
+ ) STRICT`);
22
+ input.database.exec("CREATE INDEX IF NOT EXISTS idx_private_local_subagents_parent ON private_local_subagents(parent_session_id, at)");
23
+ // A retried create keeps its first row.
24
+ const created = input.database.prepare("INSERT OR IGNORE INTO private_local_subagents(parent_session_id, agent_id, session_id, at) VALUES (?, ?, NULL, ?)");
25
+ // A send names the conversation to open and moves the row to the front.
26
+ const sent = input.database.prepare("INSERT INTO private_local_subagents(parent_session_id, agent_id, session_id, at) VALUES (?, ?, ?, ?) ON CONFLICT(parent_session_id, agent_id) DO UPDATE SET session_id = excluded.session_id, at = excluded.at");
27
+ const page = input.database.prepare("SELECT agent_id, session_id, COUNT(*) OVER () AS total FROM private_local_subagents WHERE parent_session_id = ? ORDER BY at DESC, agent_id LIMIT ?");
28
+ /** One Session's directory: its accepted creates and sends are recorded. */
29
+ function recording(parentSessionId) {
30
+ const { create, send } = input.directory;
31
+ let directory = input.directory;
32
+ if (create !== undefined && parentSessionId !== undefined)
33
+ directory = {
34
+ ...directory,
35
+ create: async (request, signal) => {
36
+ const agent = await create(request, signal);
37
+ created.run(parentSessionId, agent.agentId, Date.now());
38
+ return agent;
39
+ },
40
+ };
41
+ if (send !== undefined)
42
+ directory = {
43
+ ...directory,
44
+ send: async (task, sourceSessionId, signal, invocation) => {
45
+ const accepted = await send(task, sourceSessionId, signal, invocation);
46
+ sent.run(sourceSessionId, task.agentId, task.sessionId, Date.now());
47
+ return accepted;
48
+ },
49
+ };
50
+ return directory;
51
+ }
52
+ /**
53
+ * The ordinary Agent-control tools, bound to the Session that acquires them
54
+ * for one run, so a create is recorded against that Session.
55
+ */
56
+ const control = {
57
+ id: createAgentControlSource(input.directory).id,
58
+ acquire: (context) => createAgentControlSource(recording(context.session?.sessionId)).acquire(context),
59
+ };
60
+ const source = {
61
+ read: (sessionId, limit) => {
62
+ const rows = page
63
+ .all(sessionId, limit)
64
+ .map((row) => subagentRow.parse(row));
65
+ return Promise.resolve({
66
+ agents: rows.map((row) => ({
67
+ agentId: row.agent_id,
68
+ name: row.agent_id,
69
+ sessionId: row.session_id,
70
+ })),
71
+ more: Math.max(0, (rows[0]?.total ?? 0) - rows.length),
72
+ });
73
+ },
74
+ open: input.directory.open,
75
+ };
76
+ return { control, source };
77
+ }
@@ -0,0 +1,15 @@
1
+ import type { openPrivateLocalBunShiitakeWorkerBatch } from "./private-bun-shiitake-host.js";
2
+ export type ShutdownSignal = "SIGINT" | "SIGTERM";
3
+ export type Signals = Readonly<{
4
+ on: (signal: ShutdownSignal, listener: () => void) => void;
5
+ off: (signal: ShutdownSignal, listener: () => void) => void;
6
+ }>;
7
+ export type Output = Pick<NodeJS.WritableStream, "write">;
8
+ export type OpenWorkerBatch = typeof openPrivateLocalBunShiitakeWorkerBatch;
9
+ export type PrivateLocalWorkerServerIo = Readonly<{
10
+ cwd: string;
11
+ stdout?: Output | undefined;
12
+ signals?: Signals | undefined;
13
+ }>;
14
+ /** Retained explicit static worker batch; ordinary clients use the persistent manager. */
15
+ export declare function runPrivateLocalWorkerServer(workersFile: string, listen: string, io: PrivateLocalWorkerServerIo, openBatch?: OpenWorkerBatch): Promise<void>;
@@ -0,0 +1,136 @@
1
+ import { logEvent } from "./log-event.js";
2
+ import { fungiMcpSelectionSchema, readFungiMcpSelection, } from "./private-fungi-mcp-selection.js";
3
+ import { readFile } from "node:fs/promises";
4
+ import process from "node:process";
5
+ import { dirname, resolve } from "node:path";
6
+ import { z } from "zod";
7
+ const reasoningSchema = z.enum([
8
+ "minimal",
9
+ "low",
10
+ "medium",
11
+ "high",
12
+ "xhigh",
13
+ "max",
14
+ ]);
15
+ const ownerSchema = z.strictObject({
16
+ invocationRoot: z.string().min(1),
17
+ databasePath: z.string().min(1).optional(),
18
+ modelId: z.string().min(1).optional(),
19
+ reasoning: reasoningSchema.optional(),
20
+ fungiMcp: fungiMcpSelectionSchema.optional(),
21
+ });
22
+ const workerSchema = z.strictObject({
23
+ name: z.string(),
24
+ owner: ownerSchema,
25
+ });
26
+ const workersSchema = z.array(workerSchema).min(1);
27
+ function parseWorkers(value, filePath) {
28
+ const decoded = workersSchema.safeParse(value);
29
+ if (!decoded.success)
30
+ throw new Error("Invalid workers file: " + filePath);
31
+ const root = dirname(filePath);
32
+ return decoded.data.map(({ name, owner }) => ({
33
+ name,
34
+ owner: {
35
+ ...owner,
36
+ invocationRoot: resolve(root, owner.invocationRoot),
37
+ },
38
+ }));
39
+ }
40
+ async function readWorkers(filePath, cwd) {
41
+ const absolutePath = resolve(cwd, filePath);
42
+ let value;
43
+ try {
44
+ value = JSON.parse(await readFile(absolutePath, "utf8"));
45
+ }
46
+ catch (cause) {
47
+ throw new Error("Could not read workers file: " + absolutePath, { cause });
48
+ }
49
+ const workers = [];
50
+ for (const { name, owner } of parseWorkers(value, absolutePath)) {
51
+ const { fungiMcp, ...local } = owner;
52
+ let configured = local;
53
+ if (fungiMcp !== undefined)
54
+ configured = {
55
+ ...configured,
56
+ fungiMcp: readFungiMcpSelection(fungiMcp),
57
+ };
58
+ workers.push({ name, owner: configured });
59
+ }
60
+ return workers;
61
+ }
62
+ function asFailure(cause, message) {
63
+ return cause instanceof Error ? cause : new Error(message, { cause });
64
+ }
65
+ function readyRecord(batch) {
66
+ return JSON.stringify({
67
+ status: "ready",
68
+ baseUrl: batch.baseUrl,
69
+ workers: batch.workers.map(({ name, routePath, databasePath }) => ({
70
+ name,
71
+ routePath,
72
+ databasePath,
73
+ })),
74
+ });
75
+ }
76
+ /** Retained explicit static worker batch; ordinary clients use the persistent manager. */
77
+ export async function runPrivateLocalWorkerServer(workersFile, listen, io, openBatch = async (...args) => {
78
+ const { openPrivateLocalBunShiitakeWorkerBatch } = await import("./private-bun-shiitake-host.js");
79
+ return openPrivateLocalBunShiitakeWorkerBatch(...args);
80
+ }) {
81
+ const workers = await readWorkers(workersFile, io.cwd);
82
+ const stdout = io.stdout ?? process.stdout;
83
+ const signals = io.signals ?? process;
84
+ const stopped = Promise.withResolvers();
85
+ const stop = { requested: false };
86
+ let batch;
87
+ let initiating;
88
+ let closeFailure;
89
+ const onSignal = () => {
90
+ if (stop.requested)
91
+ return;
92
+ stop.requested = true;
93
+ stopped.resolve(undefined);
94
+ };
95
+ signals.on("SIGINT", onSignal);
96
+ signals.on("SIGTERM", onSignal);
97
+ try {
98
+ try {
99
+ batch = await openBatch(workers, listen);
100
+ if (!stop.requested) {
101
+ try {
102
+ stdout.write(readyRecord(batch) + "\n");
103
+ }
104
+ catch (cause) {
105
+ logEvent("worker-server.ready-output", cause);
106
+ initiating = asFailure(cause, "Worker server ready output failed");
107
+ }
108
+ }
109
+ if (initiating === undefined)
110
+ await stopped.promise;
111
+ }
112
+ catch (cause) {
113
+ logEvent("worker-server.opening", cause);
114
+ initiating = asFailure(cause, "Worker server opening failed");
115
+ }
116
+ if (batch !== undefined) {
117
+ try {
118
+ await batch.close();
119
+ }
120
+ catch (cause) {
121
+ logEvent("worker-server.close", cause);
122
+ closeFailure = asFailure(cause, "Worker server close failed");
123
+ }
124
+ }
125
+ }
126
+ finally {
127
+ signals.off("SIGINT", onSignal);
128
+ signals.off("SIGTERM", onSignal);
129
+ }
130
+ if (initiating !== undefined && closeFailure !== undefined)
131
+ throw new AggregateError([initiating, closeFailure], "Worker server startup and close failed");
132
+ if (initiating !== undefined)
133
+ throw initiating;
134
+ if (closeFailure !== undefined)
135
+ throw closeFailure;
136
+ }
@@ -0,0 +1,7 @@
1
+ import { DatabaseSync } from "node:sqlite";
2
+ import type { SqliteOwner } from "@fungi.computer/shiitake";
3
+ export type PrivateSqliteFailureCode = "busy" | "locked" | "corrupt" | "not_a_database" | "other";
4
+ /** Decode Node SQLite's structured result code before higher owners sanitize it. */
5
+ export declare function privateSqliteFailureCode(cause: unknown): PrivateSqliteFailureCode | undefined;
6
+ /** Adapt one synchronous native database to Shiitake's public owner shape. */
7
+ export declare function createPrivateSqliteOwner(database: DatabaseSync, onFailure?: (code: PrivateSqliteFailureCode) => void): SqliteOwner;
@@ -0,0 +1,103 @@
1
+ import * as Schema from "effect/Schema";
2
+ import { sqliteRowSchema } from "@fungi.computer/shiitake";
3
+ import { DatabaseSync } from "node:sqlite";
4
+ import { z } from "zod";
5
+ const decodeSqliteRow = Schema.decodeUnknownSync(sqliteRowSchema);
6
+ const sqliteFailureSchema = z.object({
7
+ code: z.literal("ERR_SQLITE_ERROR"),
8
+ errcode: z.number().int(),
9
+ });
10
+ const sqlitePrimaryCodeMask = 0xff;
11
+ /** Decode Node SQLite's structured result code before higher owners sanitize it. */
12
+ export function privateSqliteFailureCode(cause) {
13
+ const parsed = sqliteFailureSchema.safeParse(cause);
14
+ if (!parsed.success)
15
+ return undefined;
16
+ switch (parsed.data.errcode & sqlitePrimaryCodeMask) {
17
+ case 5:
18
+ return "busy";
19
+ case 6:
20
+ return "locked";
21
+ case 11:
22
+ return "corrupt";
23
+ case 26:
24
+ return "not_a_database";
25
+ default:
26
+ return "other";
27
+ }
28
+ }
29
+ const readerStatement = /^\s*(?:SELECT|PRAGMA|WITH)\b/iu;
30
+ function nativeInput(value) {
31
+ return value instanceof ArrayBuffer ? new Uint8Array(value) : value;
32
+ }
33
+ function nativeOutput(value) {
34
+ if (value === null)
35
+ return value;
36
+ if (value instanceof Uint8Array)
37
+ return new Uint8Array(value).slice().buffer;
38
+ if (Object.prototype.toString.call(value) === "[object Number]")
39
+ return Number(value);
40
+ if (Object.prototype.toString.call(value) === "[object String]")
41
+ return String(value);
42
+ return Number(value);
43
+ }
44
+ function nativeRow(row) {
45
+ return Object.fromEntries(Object.entries(row).map(([key, value]) => [key, nativeOutput(value)]));
46
+ }
47
+ /** Adapt one synchronous native database to Shiitake's public owner shape. */
48
+ export function createPrivateSqliteOwner(database, onFailure) {
49
+ const observeFailure = (cause) => {
50
+ const code = privateSqliteFailureCode(cause);
51
+ if (code !== undefined)
52
+ onFailure?.(code);
53
+ throw cause;
54
+ };
55
+ return Object.freeze({
56
+ sql: Object.freeze({
57
+ exec: (statement, ...bindings) => {
58
+ let rows;
59
+ try {
60
+ const prepared = database.prepare(statement);
61
+ const values = bindings.map(nativeInput);
62
+ rows = readerStatement.test(statement)
63
+ ? prepared.all(...values).map(nativeRow)
64
+ : (prepared.run(...values), []);
65
+ }
66
+ catch (cause) {
67
+ return observeFailure(cause);
68
+ }
69
+ return Object.freeze({
70
+ toArray: () => rows.map((row) => decodeSqliteRow(row)),
71
+ one: () => {
72
+ const row = rows[0];
73
+ if (row === undefined)
74
+ throw new Error("Expected one SQLite row");
75
+ return decodeSqliteRow(row);
76
+ },
77
+ });
78
+ },
79
+ }),
80
+ transactionSync: (operation) => {
81
+ try {
82
+ database.exec("BEGIN");
83
+ }
84
+ catch (cause) {
85
+ return observeFailure(cause);
86
+ }
87
+ try {
88
+ const value = operation();
89
+ database.exec("COMMIT");
90
+ return value;
91
+ }
92
+ catch (cause) {
93
+ try {
94
+ database.exec("ROLLBACK");
95
+ }
96
+ catch (rollbackCause) {
97
+ observeFailure(rollbackCause);
98
+ }
99
+ return observeFailure(cause);
100
+ }
101
+ },
102
+ });
103
+ }
@@ -0,0 +1,38 @@
1
+ import type { FetchLike } from "@modelcontextprotocol/client";
2
+ import { z } from "zod";
3
+ declare const stateSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
4
+ state: z.ZodEnum<{
5
+ running: "running";
6
+ sleeping: "sleeping";
7
+ starting: "starting";
8
+ }>;
9
+ }, z.core.$strict>, z.ZodObject<{
10
+ state: z.ZodLiteral<"queued">;
11
+ position: z.ZodNumber;
12
+ }, z.core.$strict>], "state">;
13
+ export type ComputerState = z.output<typeof stateSchema>;
14
+ export type Options = Readonly<{
15
+ credentialPath?: string;
16
+ fetch?: FetchLike;
17
+ signal?: AbortSignal;
18
+ }>;
19
+ /** Team directory and real runtime liveness, using Local's existing authenticated MCP transport. */
20
+ export declare function listPrivateTeamComputers(rawEndpoint: string, options?: Options): Promise<({
21
+ state: "running" | "sleeping" | "starting";
22
+ computerId: string;
23
+ name: string;
24
+ } | {
25
+ state: "queued";
26
+ position: number;
27
+ computerId: string;
28
+ name: string;
29
+ })[]>;
30
+ /** Await admission while reporting live queue positions. Ctrl-C stops the local wait. */
31
+ export declare function startPrivateTeamComputer(rawEndpoint: string, name: string, options: Options & Readonly<{
32
+ onState: (state: ComputerState) => void;
33
+ }>): Promise<{
34
+ [x: string]: unknown;
35
+ computerId: string;
36
+ name: string;
37
+ }>;
38
+ export {};
@@ -0,0 +1,101 @@
1
+ import { setTimeout as delay } from "node:timers/promises";
2
+ import { z } from "zod";
3
+ import { callFungiMcpTeamControl, fungiMcpTeamEndpoint, } from "./private-fungi-account.js";
4
+ const stateSchema = z.discriminatedUnion("state", [
5
+ z.strictObject({ state: z.enum(["running", "sleeping", "starting"]) }),
6
+ z.strictObject({
7
+ state: z.literal("queued"),
8
+ position: z.number().int().positive(),
9
+ }),
10
+ ]);
11
+ const computerSchema = z.looseObject({
12
+ computerId: z.string().min(1).max(256),
13
+ name: z.string().min(1).max(256),
14
+ });
15
+ const pageSchema = z.looseObject({
16
+ computers: z.array(computerSchema).max(100),
17
+ nextOffset: z.number().int().nonnegative().nullable(),
18
+ });
19
+ async function control(endpoint, operation, input, schema, options) {
20
+ options.signal?.throwIfAborted();
21
+ const text = await callFungiMcpTeamControl(endpoint, `return await computers.${operation}(${JSON.stringify(input)});`, { ...options, timeoutMs: operation === "start" ? 30 * 60 * 1_000 : 60_000 });
22
+ options.signal?.throwIfAborted();
23
+ if (text === undefined)
24
+ throw new Error("The Hub did not complete Computer control. Check your Team login and access, then retry.");
25
+ let value;
26
+ try {
27
+ value = JSON.parse(text);
28
+ }
29
+ catch {
30
+ throw new Error("The Hub returned an invalid Computer response.");
31
+ }
32
+ const result = schema.safeParse(value);
33
+ if (!result.success)
34
+ throw new Error("The Hub returned an invalid Computer response.");
35
+ return result.data;
36
+ }
37
+ async function directory(endpoint, options) {
38
+ const computers = [];
39
+ let offset = 0;
40
+ for (let pageNumber = 0; pageNumber < 100; pageNumber++) {
41
+ const page = await control(endpoint, "list", { limit: 100, offset }, pageSchema, options);
42
+ computers.push(...page.computers);
43
+ if (page.nextOffset === null)
44
+ return computers;
45
+ if (page.nextOffset <= offset)
46
+ throw new Error("The Hub returned an invalid Computer page cursor.");
47
+ offset = page.nextOffset;
48
+ }
49
+ throw new Error("This Team has too many Computer pages to list in one command.");
50
+ }
51
+ /** Team directory and real runtime liveness, using Local's existing authenticated MCP transport. */
52
+ export async function listPrivateTeamComputers(rawEndpoint, options = {}) {
53
+ const { endpoint } = fungiMcpTeamEndpoint(rawEndpoint);
54
+ const computers = await directory(endpoint, options);
55
+ const rows = [];
56
+ for (const computer of computers)
57
+ rows.push({
58
+ ...computer,
59
+ ...(await control(endpoint, "state", { computerId: computer.computerId }, stateSchema, options)),
60
+ });
61
+ return rows;
62
+ }
63
+ /** Await admission while reporting live queue positions. Ctrl-C stops the local wait. */
64
+ export async function startPrivateTeamComputer(rawEndpoint, name, options) {
65
+ const { endpoint } = fungiMcpTeamEndpoint(rawEndpoint);
66
+ const computers = await directory(endpoint, options);
67
+ const matches = computers.filter((computer) => computer.computerId === name || computer.name === name);
68
+ const computer = matches[0];
69
+ if (computer === undefined || matches.length !== 1)
70
+ throw new Error(matches.length === 0
71
+ ? `No Computer named ${name} in this Team.`
72
+ : `Computer ${name} is ambiguous; use its id.`);
73
+ options.onState({ state: "starting" });
74
+ const polling = new AbortController();
75
+ const signal = options.signal === undefined
76
+ ? polling.signal
77
+ : AbortSignal.any([options.signal, polling.signal]);
78
+ const request = control(endpoint, "start", { computerId: computer.computerId }, stateSchema, { ...options, signal });
79
+ // Observe rejection immediately while the independently authenticated status call is in flight.
80
+ const completion = request.then((state) => ({ state }), (error) => ({ error }));
81
+ try {
82
+ for (;;) {
83
+ const outcome = await Promise.race([
84
+ completion,
85
+ delay(1_000, undefined, { signal }).then(() => undefined),
86
+ ]);
87
+ if (outcome !== undefined) {
88
+ if ("error" in outcome)
89
+ throw outcome.error;
90
+ if (outcome.state.state !== "running")
91
+ throw new Error("The Hub did not confirm that the Computer started.");
92
+ options.onState(outcome.state);
93
+ return computer;
94
+ }
95
+ options.onState(await control(endpoint, "state", { computerId: computer.computerId }, stateSchema, { ...options, signal }));
96
+ }
97
+ }
98
+ finally {
99
+ polling.abort();
100
+ }
101
+ }
@@ -0,0 +1,3 @@
1
+ import { type Sandbox } from "@fungi.computer/mycelium";
2
+ /** Construct the private per-invocation workerd Sandbox. */
3
+ export declare function createPrivateWorkerdSandbox(): Sandbox;