@fungi.computer/truffle 0.0.0-stage → 0.2.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 (207) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +542 -2
  3. package/computerd-loopback.cjs +16 -0
  4. package/dist/auth-cli.d.ts +46 -0
  5. package/dist/auth-cli.js +373 -0
  6. package/dist/fungi-account-command.d.ts +18 -0
  7. package/dist/fungi-account-command.js +95 -0
  8. package/dist/index.d.ts +27 -0
  9. package/dist/index.js +23 -0
  10. package/dist/internal/image-fit-metafile.json +106 -0
  11. package/dist/internal/local-image-fit-worker.mjs +960 -0
  12. package/dist/internal/private-workerd-sandbox-worker.mjs +27487 -0
  13. package/dist/internal/sandbox-metafile.json +6726 -0
  14. package/dist/linear-auth.d.ts +4 -0
  15. package/dist/linear-auth.js +72 -0
  16. package/dist/local-archive-handoff.d.ts +12 -0
  17. package/dist/local-archive-handoff.js +21 -0
  18. package/dist/local-extensions.d.ts +143 -0
  19. package/dist/local-extensions.js +624 -0
  20. package/dist/local-image-fit.d.ts +30 -0
  21. package/dist/local-image-fit.js +105 -0
  22. package/dist/local-login.d.ts +55 -0
  23. package/dist/local-login.js +167 -0
  24. package/dist/local-models.d.ts +16 -0
  25. package/dist/local-models.js +82 -0
  26. package/dist/local-session-archives.d.ts +8 -0
  27. package/dist/local-session-archives.js +152 -0
  28. package/dist/local-skill-source.d.ts +21 -0
  29. package/dist/local-skill-source.js +433 -0
  30. package/dist/log-event.d.ts +9 -0
  31. package/dist/log-event.js +13 -0
  32. package/dist/private-bounded-response.d.ts +2 -0
  33. package/dist/private-bounded-response.js +41 -0
  34. package/dist/private-bun-shiitake-host.d.ts +11 -0
  35. package/dist/private-bun-shiitake-host.js +66 -0
  36. package/dist/private-file-credential-store.d.ts +58 -0
  37. package/dist/private-file-credential-store.js +331 -0
  38. package/dist/private-files-patch.d.ts +4 -0
  39. package/dist/private-files-patch.js +104 -0
  40. package/dist/private-fungi-account.d.ts +252 -0
  41. package/dist/private-fungi-account.js +1329 -0
  42. package/dist/private-fungi-auth-error.d.ts +5 -0
  43. package/dist/private-fungi-auth-error.js +7 -0
  44. package/dist/private-fungi-mcp-connection.d.ts +26 -0
  45. package/dist/private-fungi-mcp-connection.js +162 -0
  46. package/dist/private-fungi-mcp-endpoint.d.ts +2 -0
  47. package/dist/private-fungi-mcp-endpoint.js +15 -0
  48. package/dist/private-fungi-mcp-selection-schema.d.ts +7 -0
  49. package/dist/private-fungi-mcp-selection-schema.js +15 -0
  50. package/dist/private-fungi-mcp-selection.d.ts +14 -0
  51. package/dist/private-fungi-mcp-selection.js +49 -0
  52. package/dist/private-fungi-model-api.d.ts +6 -0
  53. package/dist/private-fungi-model-api.js +125 -0
  54. package/dist/private-fungi-model-catalog.d.ts +16 -0
  55. package/dist/private-fungi-model-catalog.js +86 -0
  56. package/dist/private-fungi-model-provider.d.ts +6 -0
  57. package/dist/private-fungi-model-provider.js +51 -0
  58. package/dist/private-fungi-pending-logouts.d.ts +34 -0
  59. package/dist/private-fungi-pending-logouts.js +135 -0
  60. package/dist/private-fungi-service-logout.d.ts +44 -0
  61. package/dist/private-fungi-service-logout.js +59 -0
  62. package/dist/private-invocation-computer.d.ts +58 -0
  63. package/dist/private-invocation-computer.js +498 -0
  64. package/dist/private-invocation-files.d.ts +30 -0
  65. package/dist/private-invocation-files.js +520 -0
  66. package/dist/private-local-acp-stdio.d.ts +17 -0
  67. package/dist/private-local-acp-stdio.js +145 -0
  68. package/dist/private-local-admission-gate.d.ts +14 -0
  69. package/dist/private-local-admission-gate.js +52 -0
  70. package/dist/private-local-agent-control.d.ts +101 -0
  71. package/dist/private-local-agent-control.js +30 -0
  72. package/dist/private-local-agent-default-model.d.ts +12 -0
  73. package/dist/private-local-agent-default-model.js +31 -0
  74. package/dist/private-local-auth-rpc.d.ts +129 -0
  75. package/dist/private-local-auth-rpc.js +396 -0
  76. package/dist/private-local-bwrap.d.ts +13 -0
  77. package/dist/private-local-bwrap.js +70 -0
  78. package/dist/private-local-client-version.d.ts +5 -0
  79. package/dist/private-local-client-version.js +43 -0
  80. package/dist/private-local-computer-attach.d.ts +49 -0
  81. package/dist/private-local-computer-attach.js +230 -0
  82. package/dist/private-local-computer-backend.d.ts +282 -0
  83. package/dist/private-local-computer-backend.js +402 -0
  84. package/dist/private-local-computer-processes.d.ts +202 -0
  85. package/dist/private-local-computer-processes.js +613 -0
  86. package/dist/private-local-config-root.d.ts +21 -0
  87. package/dist/private-local-config-root.js +19 -0
  88. package/dist/private-local-containment.d.ts +51 -0
  89. package/dist/private-local-containment.js +161 -0
  90. package/dist/private-local-default-model.d.ts +11 -0
  91. package/dist/private-local-default-model.js +45 -0
  92. package/dist/private-local-delegation.d.ts +36 -0
  93. package/dist/private-local-delegation.js +133 -0
  94. package/dist/private-local-effective-model.d.ts +27 -0
  95. package/dist/private-local-effective-model.js +17 -0
  96. package/dist/private-local-exec-environment.d.ts +16 -0
  97. package/dist/private-local-exec-environment.js +11 -0
  98. package/dist/private-local-exec-launch.d.ts +41 -0
  99. package/dist/private-local-exec-launch.js +51 -0
  100. package/dist/private-local-exec-path.d.ts +6 -0
  101. package/dist/private-local-exec-path.js +40 -0
  102. package/dist/private-local-git-exclude.d.ts +2 -0
  103. package/dist/private-local-git-exclude.js +33 -0
  104. package/dist/private-local-http-dispatch.d.ts +5 -0
  105. package/dist/private-local-http-dispatch.js +14 -0
  106. package/dist/private-local-linux-sandbox.d.ts +8 -0
  107. package/dist/private-local-linux-sandbox.js +67 -0
  108. package/dist/private-local-mail.d.ts +13 -0
  109. package/dist/private-local-mail.js +77 -0
  110. package/dist/private-local-mesh-enrollment.d.ts +34 -0
  111. package/dist/private-local-mesh-enrollment.js +188 -0
  112. package/dist/private-local-mesh-links.d.ts +21 -0
  113. package/dist/private-local-mesh-links.js +229 -0
  114. package/dist/private-local-mesh-record.d.ts +120 -0
  115. package/dist/private-local-mesh-record.js +55 -0
  116. package/dist/private-local-process-identity.d.ts +20 -0
  117. package/dist/private-local-process-identity.js +40 -0
  118. package/dist/private-local-runtime-host.d.ts +192 -0
  119. package/dist/private-local-runtime-host.js +1177 -0
  120. package/dist/private-local-sandbox-policy.d.ts +21 -0
  121. package/dist/private-local-sandbox-policy.js +121 -0
  122. package/dist/private-local-sandbox.d.ts +37 -0
  123. package/dist/private-local-sandbox.js +114 -0
  124. package/dist/private-local-seatbelt.d.ts +24 -0
  125. package/dist/private-local-seatbelt.js +68 -0
  126. package/dist/private-local-service-contract.d.ts +170 -0
  127. package/dist/private-local-service-contract.js +1 -0
  128. package/dist/private-local-service-discovery.d.ts +217 -0
  129. package/dist/private-local-service-discovery.js +322 -0
  130. package/dist/private-local-service-ids.d.ts +3 -0
  131. package/dist/private-local-service-ids.js +8 -0
  132. package/dist/private-local-service-lease.d.ts +16 -0
  133. package/dist/private-local-service-lease.js +105 -0
  134. package/dist/private-local-service-listener.d.ts +22 -0
  135. package/dist/private-local-service-listener.js +407 -0
  136. package/dist/private-local-service-log.d.ts +128 -0
  137. package/dist/private-local-service-log.js +321 -0
  138. package/dist/private-local-service-owner.d.ts +135 -0
  139. package/dist/private-local-service-owner.js +1369 -0
  140. package/dist/private-local-service-process.d.ts +1 -0
  141. package/dist/private-local-service-process.js +8 -0
  142. package/dist/private-local-service-termination.d.ts +7 -0
  143. package/dist/private-local-service-termination.js +67 -0
  144. package/dist/private-local-service.d.ts +46 -0
  145. package/dist/private-local-service.js +606 -0
  146. package/dist/private-local-session-discovery.d.ts +66 -0
  147. package/dist/private-local-session-discovery.js +278 -0
  148. package/dist/private-local-shiitake-host.d.ts +44 -0
  149. package/dist/private-local-shiitake-host.js +143 -0
  150. package/dist/private-local-startup-model.d.ts +51 -0
  151. package/dist/private-local-startup-model.js +86 -0
  152. package/dist/private-local-stop-all.d.ts +27 -0
  153. package/dist/private-local-stop-all.js +26 -0
  154. package/dist/private-local-ui-attachment.d.ts +19 -0
  155. package/dist/private-local-ui-attachment.js +71 -0
  156. package/dist/private-local-worker-server.d.ts +16 -0
  157. package/dist/private-local-worker-server.js +144 -0
  158. package/dist/private-mcp-form-contract.d.ts +186 -0
  159. package/dist/private-mcp-form-contract.js +53 -0
  160. package/dist/private-mcp-form-elicitation.d.ts +18 -0
  161. package/dist/private-mcp-form-elicitation.js +97 -0
  162. package/dist/private-mcp-form-session.d.ts +172 -0
  163. package/dist/private-mcp-form-session.js +148 -0
  164. package/dist/private-openai-claims.d.ts +30 -0
  165. package/dist/private-openai-claims.js +69 -0
  166. package/dist/private-oyster-mcp-auth.d.ts +13 -0
  167. package/dist/private-oyster-mcp-auth.js +57 -0
  168. package/dist/private-provider-rejection.d.ts +15 -0
  169. package/dist/private-provider-rejection.js +24 -0
  170. package/dist/private-sqlite-owner.d.ts +39 -0
  171. package/dist/private-sqlite-owner.js +169 -0
  172. package/dist/private-team-computers.d.ts +38 -0
  173. package/dist/private-team-computers.js +110 -0
  174. package/dist/private-workerd-sandbox.d.ts +3 -0
  175. package/dist/private-workerd-sandbox.js +347 -0
  176. package/dist/seatbelt/safe-base.sbpl +79 -0
  177. package/dist/subscription-activation.d.ts +9 -0
  178. package/dist/subscription-activation.js +62 -0
  179. package/dist/subscription-identity.d.ts +23 -0
  180. package/dist/subscription-identity.js +124 -0
  181. package/dist/subscription-turn.d.ts +17 -0
  182. package/dist/subscription-turn.js +143 -0
  183. package/dist/team-apps.d.ts +24 -0
  184. package/dist/team-apps.js +128 -0
  185. package/dist/tracker-git-credential.d.ts +15 -0
  186. package/dist/tracker-git-credential.js +87 -0
  187. package/native/bin/LICENSE.bubblewrap +502 -0
  188. package/native/bin/LICENSE.libcap +112 -0
  189. package/native/bin/LICENSE.sandbox-runtime +201 -0
  190. package/native/bin/SOURCE.bubblewrap.tar.xz +0 -0
  191. package/native/bin/build.json +4037 -0
  192. package/native/bin/bwrap-build-source/Dockerfile +48 -0
  193. package/native/bin/bwrap-build-source/README.md +96 -0
  194. package/native/bin/bwrap-build-source/build.mjs +52 -0
  195. package/native/bin/bwrap-build-source/linux-arm64.ini +14 -0
  196. package/native/bin/bwrap-build-source/pin.mjs +21 -0
  197. package/native/bin/bwrap-build-source/record.mjs +74 -0
  198. package/native/bin/bwrap-build-source/vendor/LICENSE.sandbox-runtime +201 -0
  199. package/native/bin/bwrap-build-source/vendor/README.md +23 -0
  200. package/native/bin/bwrap-build-source/vendor/seccomp-unix-block.c +280 -0
  201. package/native/bin/bwrap-build.json +44 -0
  202. package/native/bin/fungi-apply-patch +0 -0
  203. package/native/bin/fungi-bwrap +0 -0
  204. package/native/bin/fungi-bwrap.sha256 +1 -0
  205. package/native/bin/fungi-seccomp.bpf +0 -0
  206. package/native/bin/fungi-seccomp.bpf.sha256 +1 -0
  207. package/package.json +161 -3
@@ -0,0 +1,169 @@
1
+ import * as Schema from "effect/Schema";
2
+ import { sqliteRowSchema } from "@fungi.computer/shiitake";
3
+ import { DatabaseSync } from "node:sqlite";
4
+ import { fromNodeSqlite } from "@fungi.computer/watchdog/node-sqlite";
5
+ import { z } from "zod";
6
+ const decodeSqliteRow = Schema.decodeUnknownSync(sqliteRowSchema);
7
+ const bunSqlite = Object.hasOwn(process.versions, "bun")
8
+ ? await import("bun:sqlite")
9
+ : undefined;
10
+ /**
11
+ * A listing owns this database until its existing finally close. Bun's
12
+ * node:sqlite prepare/close leaves native statements alive until GC. Native
13
+ * close(true) on the native Bun driver explicitly finalizes every statement
14
+ * before releasing the connection, including when a read or decoding fails.
15
+ */
16
+ export function openPrivateReadOnlySqliteDatabase(path) {
17
+ return openPrivateSqliteDatabase(path, true, 0);
18
+ }
19
+ /** Writable connections own their statements through deterministic close. */
20
+ export function openPrivateWritableSqliteDatabase(path, timeout) {
21
+ return openPrivateSqliteDatabase(path, false, timeout);
22
+ }
23
+ function openPrivateSqliteDatabase(path, readOnly, timeout) {
24
+ if (bunSqlite === undefined)
25
+ return new DatabaseSync(path, { readOnly, timeout });
26
+ const database = new bunSqlite.Database(path, {
27
+ readonly: readOnly,
28
+ create: !readOnly,
29
+ strict: true,
30
+ });
31
+ try {
32
+ database.run(`PRAGMA busy_timeout = ${String(timeout)}`);
33
+ if (!readOnly)
34
+ database.run("PRAGMA foreign_keys = ON");
35
+ }
36
+ catch (cause) {
37
+ database.close(true);
38
+ throw cause;
39
+ }
40
+ return {
41
+ get isTransaction() {
42
+ return database.inTransaction;
43
+ },
44
+ exec: (statement) => {
45
+ database.run(statement);
46
+ },
47
+ prepare: (statement) => {
48
+ const query = database.prepare(statement);
49
+ return {
50
+ all: (...bindings) => query.all(...bindings),
51
+ get: (...bindings) => query.get(...bindings) ?? undefined,
52
+ run: (...bindings) => query.run(...bindings),
53
+ };
54
+ },
55
+ transactionSync: (operation) => database.transaction(operation)(),
56
+ close: () => {
57
+ database.close(true);
58
+ },
59
+ };
60
+ }
61
+ const sqliteFailureSchema = z.union([
62
+ z
63
+ .object({
64
+ code: z.literal("ERR_SQLITE_ERROR"),
65
+ errcode: z.number().int(),
66
+ })
67
+ .transform((failure) => failure.errcode),
68
+ z
69
+ .object({
70
+ code: z.string().startsWith("SQLITE_"),
71
+ errno: z.number().int(),
72
+ })
73
+ .transform((failure) => failure.errno),
74
+ ]);
75
+ const sqlitePrimaryCodeMask = 0xff;
76
+ /** Decode the maintained drivers' structured result codes before sanitizing. */
77
+ export function privateSqliteFailureCode(cause) {
78
+ const parsed = sqliteFailureSchema.safeParse(cause);
79
+ if (!parsed.success)
80
+ return undefined;
81
+ switch (parsed.data & sqlitePrimaryCodeMask) {
82
+ case 5:
83
+ return "busy";
84
+ case 6:
85
+ return "locked";
86
+ case 11:
87
+ return "corrupt";
88
+ case 26:
89
+ return "not_a_database";
90
+ default:
91
+ return "other";
92
+ }
93
+ }
94
+ const readerStatement = /^\s*(?:SELECT|PRAGMA|WITH)\b/iu;
95
+ function nativeInput(value) {
96
+ return value instanceof ArrayBuffer ? new Uint8Array(value) : value;
97
+ }
98
+ function nativeOutput(value) {
99
+ if (value === null)
100
+ return value;
101
+ if (value instanceof Uint8Array)
102
+ return new Uint8Array(value).slice().buffer;
103
+ if (Object.prototype.toString.call(value) === "[object Number]")
104
+ return Number(value);
105
+ if (Object.prototype.toString.call(value) === "[object String]")
106
+ return String(value);
107
+ return Number(value);
108
+ }
109
+ function nativeRow(row) {
110
+ return Object.fromEntries(Object.entries(row).map(([key, value]) => [key, nativeOutput(value)]));
111
+ }
112
+ /**
113
+ * Keep Local's native failure reporting around a host-supplied SQL owner.
114
+ * The supplied owner retains transaction mechanics and connection lifetime.
115
+ */
116
+ export function observePrivateSqliteOwner(owner, onFailure) {
117
+ const observeFailure = (cause) => {
118
+ const code = privateSqliteFailureCode(cause);
119
+ if (code !== undefined)
120
+ onFailure?.(code);
121
+ throw cause;
122
+ };
123
+ return Object.freeze({
124
+ sql: Object.freeze({
125
+ exec: (statement, ...bindings) => {
126
+ try {
127
+ return owner.sql.exec(statement, ...bindings);
128
+ }
129
+ catch (cause) {
130
+ return observeFailure(cause);
131
+ }
132
+ },
133
+ }),
134
+ transactionSync: (operation) => {
135
+ try {
136
+ return owner.transactionSync(operation);
137
+ }
138
+ catch (cause) {
139
+ return observeFailure(cause);
140
+ }
141
+ },
142
+ });
143
+ }
144
+ /** Adapt one synchronous native database to Shiitake's public owner shape. */
145
+ export function createPrivateSqliteOwner(database, onFailure) {
146
+ if (database instanceof DatabaseSync)
147
+ return observePrivateSqliteOwner(fromNodeSqlite(database), onFailure);
148
+ return observePrivateSqliteOwner({
149
+ sql: {
150
+ exec: (statement, ...bindings) => {
151
+ const prepared = database.prepare(statement);
152
+ const values = bindings.map(nativeInput);
153
+ const rows = readerStatement.test(statement)
154
+ ? prepared.all(...values).map(nativeRow)
155
+ : (prepared.run(...values), []);
156
+ return Object.freeze({
157
+ toArray: () => rows.map((row) => decodeSqliteRow(row)),
158
+ one: () => {
159
+ const row = rows[0];
160
+ if (row === undefined)
161
+ throw new Error("Expected one SQLite row");
162
+ return decodeSqliteRow(row);
163
+ },
164
+ });
165
+ },
166
+ },
167
+ transactionSync: database.transactionSync,
168
+ }, onFailure);
169
+ }
@@ -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
+ starting: "starting";
7
+ sleeping: "sleeping";
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, input?: Options): Promise<({
21
+ state: "running" | "starting" | "sleeping";
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, input: 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,110 @@
1
+ import { setTimeout as delay } from "node:timers/promises";
2
+ import { z } from "zod";
3
+ import { capturePrivateLocalConfigRoot } from "./private-local-config-root.js";
4
+ import { callFungiMcpTeamControl, fungiMcpTeamEndpoint, } from "./private-fungi-account.js";
5
+ const stateSchema = z.discriminatedUnion("state", [
6
+ z.strictObject({ state: z.enum(["running", "sleeping", "starting"]) }),
7
+ z.strictObject({
8
+ state: z.literal("queued"),
9
+ position: z.number().int().positive(),
10
+ }),
11
+ ]);
12
+ const computerSchema = z.looseObject({
13
+ computerId: z.string().min(1).max(256),
14
+ name: z.string().min(1).max(256),
15
+ });
16
+ const pageSchema = z.looseObject({
17
+ computers: z.array(computerSchema).max(100),
18
+ nextOffset: z.number().int().nonnegative().nullable(),
19
+ });
20
+ async function control(endpoint, operation, input, schema, options) {
21
+ options.signal?.throwIfAborted();
22
+ const text = await callFungiMcpTeamControl(endpoint, `return await computers.${operation}(${JSON.stringify(input)});`, { ...options, timeoutMs: operation === "start" ? 30 * 60 * 1_000 : 60_000 });
23
+ options.signal?.throwIfAborted();
24
+ if (text === undefined)
25
+ throw new Error("The Hub did not complete Computer control. Check your Team login and access, then retry.");
26
+ let value;
27
+ try {
28
+ value = JSON.parse(text);
29
+ }
30
+ catch {
31
+ throw new Error("The Hub returned an invalid Computer response.");
32
+ }
33
+ const result = schema.safeParse(value);
34
+ if (!result.success)
35
+ throw new Error("The Hub returned an invalid Computer response.");
36
+ return result.data;
37
+ }
38
+ async function directory(endpoint, options) {
39
+ const computers = [];
40
+ let offset = 0;
41
+ for (let pageNumber = 0; pageNumber < 100; pageNumber++) {
42
+ const page = await control(endpoint, "list", { limit: 100, offset }, pageSchema, options);
43
+ computers.push(...page.computers);
44
+ if (page.nextOffset === null)
45
+ return computers;
46
+ if (page.nextOffset <= offset)
47
+ throw new Error("The Hub returned an invalid Computer page cursor.");
48
+ offset = page.nextOffset;
49
+ }
50
+ throw new Error("This Team has too many Computer pages to list in one command.");
51
+ }
52
+ /** Team directory and real runtime liveness, using Local's existing authenticated MCP transport. */
53
+ export async function listPrivateTeamComputers(rawEndpoint, input = {}) {
54
+ const options = {
55
+ ...input,
56
+ credentialPath: input.credentialPath ?? capturePrivateLocalConfigRoot().credentialPath,
57
+ };
58
+ const { endpoint } = fungiMcpTeamEndpoint(rawEndpoint);
59
+ const computers = await directory(endpoint, options);
60
+ const rows = [];
61
+ for (const computer of computers)
62
+ rows.push({
63
+ ...computer,
64
+ ...(await control(endpoint, "state", { computerId: computer.computerId }, stateSchema, options)),
65
+ });
66
+ return rows;
67
+ }
68
+ /** Await admission while reporting live queue positions. Ctrl-C stops the local wait. */
69
+ export async function startPrivateTeamComputer(rawEndpoint, name, input) {
70
+ const options = {
71
+ ...input,
72
+ credentialPath: input.credentialPath ?? capturePrivateLocalConfigRoot().credentialPath,
73
+ };
74
+ const { endpoint } = fungiMcpTeamEndpoint(rawEndpoint);
75
+ const computers = await directory(endpoint, options);
76
+ const matches = computers.filter((computer) => computer.computerId === name || computer.name === name);
77
+ const computer = matches[0];
78
+ if (computer === undefined || matches.length !== 1)
79
+ throw new Error(matches.length === 0
80
+ ? `No Computer named ${name} in this Team.`
81
+ : `Computer ${name} is ambiguous; use its id.`);
82
+ options.onState({ state: "starting" });
83
+ const polling = new AbortController();
84
+ const signal = options.signal === undefined
85
+ ? polling.signal
86
+ : AbortSignal.any([options.signal, polling.signal]);
87
+ const request = control(endpoint, "start", { computerId: computer.computerId }, stateSchema, { ...options, signal });
88
+ // Observe rejection immediately while the independently authenticated status call is in flight.
89
+ const completion = request.then((state) => ({ state }), (error) => ({ error }));
90
+ try {
91
+ for (;;) {
92
+ const outcome = await Promise.race([
93
+ completion,
94
+ delay(1_000, undefined, { signal }).then(() => undefined),
95
+ ]);
96
+ if (outcome !== undefined) {
97
+ if ("error" in outcome)
98
+ throw outcome.error;
99
+ if (outcome.state.state !== "running")
100
+ throw new Error("The Hub did not confirm that the Computer started.");
101
+ options.onState(outcome.state);
102
+ return computer;
103
+ }
104
+ options.onState(await control(endpoint, "state", { computerId: computer.computerId }, stateSchema, { ...options, signal }));
105
+ }
106
+ }
107
+ finally {
108
+ polling.abort();
109
+ }
110
+ }
@@ -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;
@@ -0,0 +1,347 @@
1
+ import { sandboxBindingNames } from "@fungi.computer/mycelium/sandbox-guest";
2
+ import { recoverSandboxFailure, sandboxTransportMessage, sandboxCapabilityFailure, sandboxTransportErrno, } from "@fungi.computer/mycelium/sandbox-boundary";
3
+ import { nanoid } from "@fungi.computer/cairn";
4
+ import { readFile } from "node:fs/promises";
5
+ import { request as httpRequest } from "node:http";
6
+ import { SandboxCrashed, DispatchLost, BadResponse, Cancelled, sandboxFailure, } from "@fungi.computer/mycelium";
7
+ import { FilesEditError } from "@fungi.computer/knapsack";
8
+ import { convertV4MiniflareOptions, Miniflare, Response as MiniflareResponse, } from "miniflare";
9
+ import { z } from "zod";
10
+ import { sandboxWireSchema, sandboxWireFailure, } from "@fungi.computer/mycelium/sandbox-wire";
11
+ import { FilesPathError } from "./private-invocation-files.js";
12
+ const jsonValueSchema = z.json();
13
+ const bindingCallSchema = z.strictObject({
14
+ namespace: z.string().max(160),
15
+ operation: z.string().max(160),
16
+ input: jsonValueSchema,
17
+ });
18
+ const bindingErrorSchema = z.discriminatedUnion("code", [
19
+ z.strictObject({
20
+ code: z.literal("no_such_operation"),
21
+ path: z.string().max(321),
22
+ suggestions: z.array(z.string().max(160)).max(3),
23
+ }),
24
+ z.strictObject({
25
+ code: z.enum(["invalid_input", "operation_failed", "closing"]),
26
+ message: z.string().max(512),
27
+ }),
28
+ ]);
29
+ const bindingResponseSchema = z.discriminatedUnion("ok", [
30
+ z.strictObject({ ok: z.literal(true), value: jsonValueSchema }),
31
+ z.strictObject({ ok: z.literal(false), error: bindingErrorSchema }),
32
+ ]);
33
+ const workerBundle = readFile(new URL("./internal/private-workerd-sandbox-worker.mjs", import.meta.url), "utf8");
34
+ // Files owns admission of its diagnostics; the shared boundary alone decodes rejections.
35
+ const filesDiagnosticSchema = z
36
+ .union([z.instanceof(FilesPathError), z.instanceof(FilesEditError)])
37
+ .transform((error) => ({ kind: "Files", message: error.message }));
38
+ function operationTokens(path) {
39
+ return path
40
+ .toLowerCase()
41
+ .split(/[^a-z0-9]+/u)
42
+ .filter(Boolean);
43
+ }
44
+ function commonPrefixLength(left, right) {
45
+ let index = 0;
46
+ while (index < left.length &&
47
+ index < right.length &&
48
+ left[index] === right[index])
49
+ index += 1;
50
+ return index;
51
+ }
52
+ function oneEditApart(left, right) {
53
+ if (Math.abs(left.length - right.length) > 1)
54
+ return false;
55
+ let leftIndex = 0;
56
+ let rightIndex = 0;
57
+ let changed = false;
58
+ while (leftIndex < left.length && rightIndex < right.length) {
59
+ if (left[leftIndex] === right[rightIndex]) {
60
+ leftIndex += 1;
61
+ rightIndex += 1;
62
+ continue;
63
+ }
64
+ if (changed)
65
+ return false;
66
+ changed = true;
67
+ if (left.length >= right.length)
68
+ leftIndex += 1;
69
+ if (right.length >= left.length)
70
+ rightIndex += 1;
71
+ }
72
+ return changed || left.length !== right.length;
73
+ }
74
+ function operationSuggestions(request, operation) {
75
+ const requestedTokens = operationTokens(operation);
76
+ const scored = Object.entries(request.bindings).flatMap(([namespace, methods]) => Object.keys(methods).map((method) => {
77
+ const path = `${namespace}.${method}`;
78
+ const candidateTokens = operationTokens(path);
79
+ const score = requestedTokens.reduce((total, requested) => total +
80
+ Math.max(0, ...candidateTokens.map((candidate) => candidate === requested
81
+ ? 4
82
+ : oneEditApart(candidate, requested)
83
+ ? 3
84
+ : commonPrefixLength(candidate, requested) >= 3
85
+ ? 2
86
+ : 0)), 0);
87
+ return { path, score };
88
+ }));
89
+ return scored
90
+ .filter(({ score }) => score > 0)
91
+ .sort((left, right) => right.score - left.score || left.path.localeCompare(right.path))
92
+ .slice(0, 3)
93
+ .map(({ path }) => path);
94
+ }
95
+ function jsonResponse(value, status = 200) {
96
+ return new MiniflareResponse(JSON.stringify(bindingResponseSchema.parse(value)), {
97
+ status,
98
+ headers: { "content-type": "application/json" },
99
+ });
100
+ }
101
+ async function invokeBinding(request, bindingRequest, failures) {
102
+ let call;
103
+ try {
104
+ call = bindingCallSchema.parse(await bindingRequest.json());
105
+ }
106
+ catch {
107
+ return jsonResponse({
108
+ ok: false,
109
+ error: { code: "invalid_input", message: "Invalid binding call" },
110
+ }, 400);
111
+ }
112
+ const operation = request.bindings[call.namespace]?.[call.operation];
113
+ if (operation === undefined) {
114
+ return jsonResponse({
115
+ ok: false,
116
+ error: {
117
+ code: "no_such_operation",
118
+ path: `${call.namespace}.${call.operation}`,
119
+ suggestions: operationSuggestions(request, call.operation),
120
+ },
121
+ }, 404);
122
+ }
123
+ try {
124
+ const value = jsonValueSchema.parse(await operation(call.input));
125
+ return jsonResponse({ ok: true, value });
126
+ }
127
+ catch (cause) {
128
+ const failure = sandboxCapabilityFailure(cause, call.namespace, call.operation, filesDiagnosticSchema);
129
+ const ref = nanoid();
130
+ if (failures.size === 128)
131
+ failures.delete(failures.keys().next().value ?? "");
132
+ failures.set(ref, failure);
133
+ return jsonResponse({
134
+ ok: false,
135
+ error: {
136
+ code: failure._tag === "CapabilityFailed" &&
137
+ failure.cause.kind === "InvalidInput"
138
+ ? "invalid_input"
139
+ : "operation_failed",
140
+ message: sandboxTransportMessage(failure, ref),
141
+ },
142
+ }, 400);
143
+ }
144
+ }
145
+ function createInvocationCallbacks(request) {
146
+ const failures = new Map();
147
+ const activeCallbacks = new Set();
148
+ let acceptingCallbacks = true;
149
+ const fetch = (bindingRequest) => {
150
+ if (!acceptingCallbacks) {
151
+ return Promise.resolve(jsonResponse({
152
+ ok: false,
153
+ error: { code: "closing", message: "Invocation is closing" },
154
+ }, 503));
155
+ }
156
+ const callback = invokeBinding(request, bindingRequest, failures);
157
+ activeCallbacks.add(callback);
158
+ void callback.then(() => activeCallbacks.delete(callback), () => activeCallbacks.delete(callback));
159
+ return callback;
160
+ };
161
+ return Object.freeze({
162
+ failures,
163
+ fetch,
164
+ closeAdmission: () => {
165
+ acceptingCallbacks = false;
166
+ },
167
+ drain: async () => {
168
+ await Promise.allSettled(activeCallbacks);
169
+ },
170
+ });
171
+ }
172
+ function createInvocationMiniflare(bundle, serviceBinding) {
173
+ return new Miniflare(convertV4MiniflareOptions({
174
+ modules: [
175
+ {
176
+ type: "ESModule",
177
+ path: "private-workerd-sandbox-worker.mjs",
178
+ contents: bundle,
179
+ },
180
+ ],
181
+ compatibilityDate: "2026-09-02",
182
+ compatibilityFlags: ["nodejs_compat"],
183
+ workerLoaders: { LOADER: {} },
184
+ serviceBindings: { INVOCATION: serviceBinding },
185
+ }));
186
+ }
187
+ async function dispatchInvocation(miniflare, request, signal) {
188
+ let response;
189
+ const startedAt = performance.now();
190
+ let startedDispatch = false;
191
+ try {
192
+ // Bun's built-in undici Pool ignores Miniflare's zero-timeout settings;
193
+ // dispatchFetch inherits Bun.fetch's 300 s idle timer. Use the public
194
+ // workerd listener with node:http, whose request has no implicit deadline.
195
+ // Mycelium's signal remains the invocation's cancellation authority.
196
+ const url = new URL("/execute", await miniflare.ready);
197
+ startedDispatch = true;
198
+ response = await new Promise((resolve, reject) => {
199
+ const outgoing = httpRequest(url, {
200
+ method: "POST",
201
+ headers: { "content-type": "application/json" },
202
+ agent: false,
203
+ signal,
204
+ }, (incoming) => {
205
+ resolve(new MiniflareResponse(incoming, {
206
+ status: incoming.statusCode ?? 500,
207
+ }));
208
+ });
209
+ outgoing.on("error", reject);
210
+ outgoing.end(JSON.stringify({
211
+ code: request.code,
212
+ timeoutMs: request.timeoutMs,
213
+ bindings: sandboxBindingNames(request.bindings),
214
+ namespacePaths: request.namespacePaths ?? {},
215
+ }));
216
+ });
217
+ }
218
+ catch (cause) {
219
+ return {
220
+ ok: false,
221
+ cause: signal.aborted
222
+ ? new Cancelled({ by: "caller" })
223
+ : startedDispatch
224
+ ? new DispatchLost({
225
+ elapsedMs: Math.round(performance.now() - startedAt),
226
+ transport: "node:http",
227
+ errno: sandboxTransportErrno(cause),
228
+ rawCause: cause,
229
+ })
230
+ : new SandboxCrashed({ rawCause: cause }),
231
+ };
232
+ }
233
+ let payload;
234
+ try {
235
+ payload = await response.json();
236
+ }
237
+ catch (cause) {
238
+ return {
239
+ ok: false,
240
+ cause: new BadResponse({
241
+ reason: "response is not JSON",
242
+ rawCause: cause,
243
+ }),
244
+ };
245
+ }
246
+ const parsed = sandboxWireSchema.safeParse(payload);
247
+ if (!parsed.success) {
248
+ return {
249
+ ok: false,
250
+ cause: new BadResponse({
251
+ reason: "response does not match sandbox envelope",
252
+ rawCause: parsed.error,
253
+ }),
254
+ };
255
+ }
256
+ const envelope = parsed.data;
257
+ return envelope.ok
258
+ ? { ok: true, value: envelope.value }
259
+ : {
260
+ ok: false,
261
+ cause: sandboxWireFailure(envelope.error),
262
+ };
263
+ }
264
+ function createDisposal(miniflare, callbacks) {
265
+ let disposal;
266
+ return () => {
267
+ callbacks.closeAdmission();
268
+ disposal ??= miniflare.dispose();
269
+ return disposal;
270
+ };
271
+ }
272
+ async function awaitCleanup(disposal, callbacks) {
273
+ const [disposed] = await Promise.allSettled([disposal, callbacks.drain()]);
274
+ if (disposed.status === "rejected")
275
+ throw disposed.reason;
276
+ }
277
+ async function executeInvocation(request, signal) {
278
+ signal.throwIfAborted();
279
+ let bundle;
280
+ try {
281
+ bundle = await workerBundle;
282
+ }
283
+ catch (rawCause) {
284
+ throw new SandboxCrashed({ rawCause });
285
+ }
286
+ signal.throwIfAborted();
287
+ const callbacks = createInvocationCallbacks(request);
288
+ let miniflare;
289
+ try {
290
+ miniflare = createInvocationMiniflare(bundle, callbacks.fetch);
291
+ }
292
+ catch (rawCause) {
293
+ throw new SandboxCrashed({ rawCause });
294
+ }
295
+ const beginDisposal = createDisposal(miniflare, callbacks);
296
+ const onAbort = () => {
297
+ void beginDisposal().catch((cause) => {
298
+ console.error("Local workerd disposal failed", cause);
299
+ return undefined;
300
+ });
301
+ };
302
+ if (signal.aborted)
303
+ onAbort();
304
+ else
305
+ signal.addEventListener("abort", onAbort, { once: true });
306
+ let result;
307
+ try {
308
+ signal.throwIfAborted();
309
+ result = await dispatchInvocation(miniflare, request, signal);
310
+ }
311
+ catch (cause) {
312
+ result = { ok: false, cause: sandboxFailure(cause) };
313
+ }
314
+ finally {
315
+ signal.removeEventListener("abort", onAbort);
316
+ }
317
+ try {
318
+ await awaitCleanup(beginDisposal(), callbacks);
319
+ }
320
+ catch (cause) {
321
+ // Disposal is an operator annotation. Keep the original guest/transport outcome.
322
+ console.error("sandbox_cleanup_failure", {
323
+ executionId: request.executionId,
324
+ rawCause: cause,
325
+ });
326
+ }
327
+ if (!result.ok) {
328
+ throw recoverSandboxFailure(result.cause, callbacks.failures);
329
+ }
330
+ return result.value;
331
+ }
332
+ /** Construct the private per-invocation workerd Sandbox. */
333
+ export function createPrivateWorkerdSandbox() {
334
+ return Object.freeze({
335
+ adapter: "local-workerd",
336
+ execute: async (request, signal) => {
337
+ try {
338
+ return await executeInvocation(request, signal);
339
+ }
340
+ catch (cause) {
341
+ throw signal.aborted
342
+ ? new Cancelled({ by: "caller" })
343
+ : sandboxFailure(cause);
344
+ }
345
+ },
346
+ });
347
+ }