@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,33 @@
1
+ import { execFile as execFileCallback } from "node:child_process";
2
+ import { appendFile, mkdir, readFile } from "node:fs/promises";
3
+ import { resolve, join } from "node:path";
4
+ import { promisify } from "node:util";
5
+ const execFile = promisify(execFileCallback);
6
+ const excludeEntry = ".fungi/";
7
+ /** Keep generated project state out of Git without changing tracked ignore rules. */
8
+ export async function ensurePrivateLocalGitExclude(projectRoot) {
9
+ let common;
10
+ try {
11
+ const result = await execFile("git", ["rev-parse", "--git-common-dir"], {
12
+ cwd: projectRoot,
13
+ });
14
+ common = resolve(projectRoot, result.stdout.trim());
15
+ }
16
+ catch (cause) {
17
+ // A non-Git project still keeps its local Fungi store.
18
+ if (cause instanceof Error && "code" in cause && cause.code === 128)
19
+ return;
20
+ throw cause;
21
+ }
22
+ const info = join(common, "info");
23
+ await mkdir(info, { recursive: true });
24
+ const exclude = join(info, "exclude");
25
+ const current = await readFile(exclude, "utf8").catch((cause) => {
26
+ if (cause instanceof Error && "code" in cause && cause.code === "ENOENT")
27
+ return "";
28
+ throw cause;
29
+ });
30
+ if (current.split(/\r?\n/u).some((line) => line.trim() === excludeEntry))
31
+ return;
32
+ await appendFile(exclude, `${current !== "" && !current.endsWith("\n") ? "\n" : ""}${excludeEntry}\n`);
33
+ }
@@ -0,0 +1,5 @@
1
+ export type RequestIdleClock = {
2
+ timeout: (request: Request, seconds: number) => void;
3
+ };
4
+ /** Bun's socket clock must stay off while a command or quiet SSE is pending. */
5
+ export declare function dispatchPrivateLocalRequest(request: Request, clock: RequestIdleClock, fetch: (request: Request) => Response | Promise<Response>): Promise<Response>;
@@ -0,0 +1,14 @@
1
+ /** Bun's socket clock must stay off while a command or quiet SSE is pending. */
2
+ export async function dispatchPrivateLocalRequest(request, clock, fetch) {
3
+ clock.timeout(request, 0);
4
+ try {
5
+ const response = await fetch(request);
6
+ if (!response.headers.get("content-type")?.startsWith("text/event-stream"))
7
+ clock.timeout(request, 10);
8
+ return response;
9
+ }
10
+ catch (cause) {
11
+ clock.timeout(request, 10);
12
+ throw cause;
13
+ }
14
+ }
@@ -0,0 +1,8 @@
1
+ import { type LocalSandboxPolicy } from "./private-local-sandbox-policy.js";
2
+ import type { LinuxSandboxPayload } from "./private-local-bwrap.js";
3
+ /** Ordinary upstream bwrap argv. There is no approval or bare-command path. */
4
+ export declare function linuxSandboxCommand(policy: LocalSandboxPolicy, payload: LinuxSandboxPayload, cwd: string, argv: readonly [string, ...string[]], host?: NodeJS.ProcessEnv): {
5
+ args: string[];
6
+ executable: string;
7
+ seccompFilter: Uint8Array;
8
+ };
@@ -0,0 +1,67 @@
1
+ import { existsSync, mkdirSync, realpathSync, statSync, writeFileSync, } from "node:fs";
2
+ import { isAbsolute, join } from "node:path";
3
+ import { protectedSandboxPaths, } from "./private-local-sandbox-policy.js";
4
+ /** Ordinary upstream bwrap argv. There is no approval or bare-command path. */
5
+ export function linuxSandboxCommand(policy, payload, cwd, argv, host = process.env) {
6
+ const root = policy.writableRoot;
7
+ // Bind sources must exist. Only the trusted host prepares the reserved paths.
8
+ mkdirSync(join(root, ".fungi"), { recursive: true });
9
+ for (const git of policy.immovable) {
10
+ mkdirSync(join(git, "hooks"), { recursive: true });
11
+ for (const config of ["config", "config.worktree"])
12
+ if (!existsSync(join(git, config)))
13
+ writeFileSync(join(git, config), "", { flag: "wx" });
14
+ }
15
+ const args = [
16
+ "--unshare-all",
17
+ "--die-with-parent",
18
+ "--new-session",
19
+ "--seccomp",
20
+ "3",
21
+ "--ro-bind",
22
+ "/",
23
+ "/",
24
+ // Private system mounts precede the folder bind so /tmp projects survive.
25
+ "--tmpfs",
26
+ "/tmp",
27
+ "--tmpfs",
28
+ "/run",
29
+ "--dev",
30
+ "/dev",
31
+ "--proc",
32
+ "/proc",
33
+ "--bind",
34
+ root,
35
+ root,
36
+ ];
37
+ for (const path of policy.immovable)
38
+ args.push("--bind", path, path);
39
+ for (const path of protectedSandboxPaths(policy.readOnly))
40
+ args.push("--ro-bind", path, path);
41
+ if (existsSync("/var/run") && realpathSync("/var/run") !== "/run")
42
+ args.push("--tmpfs", realpathSync("/var/run"));
43
+ const runtime = host.XDG_RUNTIME_DIR;
44
+ if (runtime && isAbsolute(runtime) && existsSync(runtime))
45
+ args.push("--tmpfs", realpathSync(runtime));
46
+ // Stopgap masks, not an inventory of all possible host IPC. /run also
47
+ // covers the usual /var/run alias. These follow folder/protected binds so
48
+ // a selected socket inside the project is not exposed again by a later bind.
49
+ // Keep unusual selected SSH-agent paths
50
+ // and Docker Desktop sockets out without masking all of the user's HOME.
51
+ if (host.HOME && isAbsolute(host.HOME)) {
52
+ for (const path of [
53
+ join(host.HOME, ".docker", "run"),
54
+ join(host.HOME, ".docker", "desktop"),
55
+ ])
56
+ if (existsSync(path) && statSync(path).isDirectory())
57
+ args.push("--tmpfs", realpathSync(path));
58
+ }
59
+ const agentSocket = host.SSH_AUTH_SOCK;
60
+ if (agentSocket &&
61
+ isAbsolute(agentSocket) &&
62
+ existsSync(agentSocket) &&
63
+ statSync(agentSocket).isSocket())
64
+ args.push("--ro-bind", "/dev/null", realpathSync(agentSocket));
65
+ args.push("--chdir", cwd, "--", ...argv);
66
+ return { ...payload, args };
67
+ }
@@ -0,0 +1,13 @@
1
+ import type { BirdDogMail } from "@fungi.computer/bird-dog";
2
+ import { type BirdDogEffect } from "@fungi.computer/bird-dog/effect";
3
+ import type { PrivateSqliteDatabase } from "./private-sqlite-owner.js";
4
+ import type { Accepted } from "@fungi.computer/shiitake";
5
+ export type PrivateLocalMailOptions = Readonly<{
6
+ /** Admit one reply at its addressed Agent; a rejection retries later. */
7
+ deliver: (mail: BirdDogMail) => Promise<Accepted>;
8
+ scheduleWake?: (at: number) => void;
9
+ now?: () => number;
10
+ }>;
11
+ /** Whether this Agent's outbox still owes any reply a delivery attempt. */
12
+ export declare function hasPrivateLocalPendingMail(database: PrivateSqliteDatabase): Promise<boolean>;
13
+ export declare function createPrivateLocalMailRoom(database: PrivateSqliteDatabase, options: PrivateLocalMailOptions): BirdDogEffect;
@@ -0,0 +1,77 @@
1
+ import { BirdDogError, hasPendingDelivery, make as makeBirdDog, } from "@fungi.computer/bird-dog/effect";
2
+ import * as Effect from "effect/Effect";
3
+ import { messageAuthor } from "@fungi.computer/agar/agents";
4
+ import { z } from "zod";
5
+ const outboxValue = z.object({ value: z.string() });
6
+ const outboxRows = z.array(z.object({ key: z.string(), value: z.string() }).readonly());
7
+ function storeFailure(cause) {
8
+ return new BirdDogError("local mail store is unavailable", { cause });
9
+ }
10
+ /** The Agent store's BirdDog outbox rows. */
11
+ function localOutbox(database) {
12
+ return {
13
+ read: (key) => Effect.try({
14
+ try: () => {
15
+ const statement = database.prepare("SELECT value FROM local_birddog_outbox WHERE key = ?");
16
+ const row = outboxValue.safeParse(statement.get(key));
17
+ return row.success ? row.data.value : undefined;
18
+ },
19
+ catch: storeFailure,
20
+ }),
21
+ put: (key, value) => Effect.try({
22
+ try: () => {
23
+ database
24
+ .prepare("INSERT INTO local_birddog_outbox (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value")
25
+ .run(key, value);
26
+ },
27
+ catch: storeFailure,
28
+ }),
29
+ list: (limit, afterKey) => Effect.try({
30
+ try: () => outboxRows.parse(database
31
+ .prepare("SELECT key, value FROM local_birddog_outbox WHERE (? IS NULL OR key > ?) ORDER BY key LIMIT ?")
32
+ .all(afterKey ?? null, afterKey ?? null, limit)),
33
+ catch: storeFailure,
34
+ }),
35
+ delete: (key) => Effect.try({
36
+ try: () => {
37
+ database
38
+ .prepare("DELETE FROM local_birddog_outbox WHERE key = ?")
39
+ .run(key);
40
+ },
41
+ catch: storeFailure,
42
+ }),
43
+ };
44
+ }
45
+ /** Whether this Agent's outbox still owes any reply a delivery attempt. */
46
+ export function hasPrivateLocalPendingMail(database) {
47
+ return Effect.runPromise(hasPendingDelivery(localOutbox(database)));
48
+ }
49
+ export function createPrivateLocalMailRoom(database, options) {
50
+ database.exec("CREATE TABLE IF NOT EXISTS local_birddog_outbox (key TEXT PRIMARY KEY NOT NULL, value TEXT NOT NULL)");
51
+ const room = Effect.runSync(makeBirdDog({
52
+ outbox: localOutbox(database),
53
+ post: (mail) => Effect.tryPromise({
54
+ try: async () => {
55
+ await options.deliver(mail);
56
+ return true;
57
+ },
58
+ catch: (cause) => new BirdDogError("local recipient rejected mail", { cause }),
59
+ }).pipe(Effect.catch((cause) => Effect.logError("Local mail recipient rejected delivery", cause).pipe(Effect.as(false)))),
60
+ scheduleWake: (at) => Effect.sync(() => options.scheduleWake?.(at)),
61
+ now: options.now ?? (() => Date.now()),
62
+ }));
63
+ return {
64
+ ...room,
65
+ fanOut: (settlement) => {
66
+ if (settlement.job.outcome !== "cancelled")
67
+ return room.fanOut(settlement);
68
+ const author = messageAuthor(settlement.meta);
69
+ return room.fanOut({
70
+ ...settlement,
71
+ content: author === undefined
72
+ ? "Work cancelled."
73
+ : `Work cancelled by ${author.name}.`,
74
+ });
75
+ },
76
+ };
77
+ }
@@ -0,0 +1,34 @@
1
+ import { z } from "zod";
2
+ import { type LocalMachineEnrollmentAccount } from "./private-local-mesh-record.js";
3
+ export declare const localMeshJoinSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
4
+ origin: z.ZodURL;
5
+ invocationRoot: z.ZodString;
6
+ machineLabel: z.ZodString;
7
+ agents: z.ZodArray<z.ZodString>;
8
+ kind: z.ZodLiteral<"oauth">;
9
+ teamId: z.ZodString;
10
+ }, z.core.$strict>, z.ZodObject<{
11
+ origin: z.ZodURL;
12
+ invocationRoot: z.ZodString;
13
+ machineLabel: z.ZodString;
14
+ agents: z.ZodArray<z.ZodString>;
15
+ kind: z.ZodLiteral<"oyster-key">;
16
+ key: z.ZodString;
17
+ }, z.core.$strict>], "kind">;
18
+ /**
19
+ * Persist the device pin before authenticated enrollment I/O. This is enrollment
20
+ * only: it does not report a socket, command Accepted or connected mesh.
21
+ */
22
+ export declare function enrollPrivateLocalMesh(raw: z.infer<typeof localMeshJoinSchema>, options: {
23
+ account: LocalMachineEnrollmentAccount;
24
+ credentialPath: string;
25
+ fetch?: typeof globalThis.fetch;
26
+ signal?: AbortSignal;
27
+ }): Promise<{
28
+ state: string;
29
+ deviceId: string;
30
+ agents: {
31
+ selection: number;
32
+ agentId: string;
33
+ }[];
34
+ }>;
@@ -0,0 +1,188 @@
1
+ import { Buffer } from "node:buffer";
2
+ import { generateKeyPairSync } from "node:crypto";
3
+ import { realpath } from "node:fs/promises";
4
+ import { basename, dirname, relative, isAbsolute, resolve } from "node:path";
5
+ import { defineIdFamily } from "@fungi.computer/cairn/id";
6
+ import { z } from "zod";
7
+ import { readBoundedResponse } from "./private-bounded-response.js";
8
+ import { fungiAccountFetch } from "./private-fungi-account.js";
9
+ import { parseFungiMcpEndpoint } from "./private-fungi-mcp-endpoint.js";
10
+ import { listPrivateLocalAgentStores, readPrivateLocalAgentProfile, } from "./private-local-session-discovery.js";
11
+ import { machineBindingSchema, localMachineEnrollmentSchema, } from "./private-local-mesh-record.js";
12
+ const id = z.string().min(1).max(256);
13
+ const joinCommon = z.strictObject({
14
+ origin: z.url(),
15
+ invocationRoot: z.string().min(1).max(4096),
16
+ machineLabel: z.string().trim().min(1).max(256),
17
+ agents: z
18
+ .array(id)
19
+ .max(256)
20
+ .refine((agents) => new Set(agents).size === agents.length),
21
+ });
22
+ export const localMeshJoinSchema = z.discriminatedUnion("kind", [
23
+ joinCommon.extend({ kind: z.literal("oauth"), teamId: id }),
24
+ joinCommon.extend({
25
+ kind: z.literal("oyster-key"),
26
+ key: z.string().startsWith("oyk_").min(5).max(4096),
27
+ }),
28
+ ]);
29
+ const responseSchema = z.strictObject({
30
+ kind: z.literal("joined"),
31
+ deviceId: id,
32
+ agents: z.array(machineBindingSchema).max(256),
33
+ });
34
+ const oysterResponseSchema = responseSchema.extend({ scopeId: id });
35
+ const requests = defineIdFamily({
36
+ family: "local-machine-enrollment",
37
+ version: 1,
38
+ kinds: { join: { prefix: "machine_join" } },
39
+ });
40
+ function deviceKey() {
41
+ const pair = generateKeyPairSync("ed25519");
42
+ const jwk = z
43
+ .object({ x: z.string() })
44
+ .parse(pair.publicKey.export({ format: "jwk" }));
45
+ return {
46
+ publicKey: Buffer.from(jwk.x, "base64url").toString("base64"),
47
+ privateKey: pair.privateKey
48
+ .export({ format: "der", type: "pkcs8" })
49
+ .toString("base64"),
50
+ };
51
+ }
52
+ /**
53
+ * Persist the device pin before authenticated enrollment I/O. This is enrollment
54
+ * only: it does not report a socket, command Accepted or connected mesh.
55
+ */
56
+ export async function enrollPrivateLocalMesh(raw, options) {
57
+ const input = localMeshJoinSchema.parse(raw);
58
+ const endpoint = parseFungiMcpEndpoint(input.origin);
59
+ if (endpoint.pathname !== "/")
60
+ throw new Error("Mesh enrollment needs an origin");
61
+ const origin = endpoint.origin;
62
+ const authority = {
63
+ kind: input.kind === "oauth" ? "oauth" : "oyster",
64
+ origin,
65
+ };
66
+ if (input.kind === "oauth") {
67
+ const saved = await options.account.read();
68
+ if (!saved.some((record) => record.kind === "oauth" && record.origin === origin))
69
+ throw new Error("Sign in to this Fungi issuer before joining a machine");
70
+ }
71
+ const invocationRoot = await realpath(resolve(input.invocationRoot));
72
+ const stores = await listPrivateLocalAgentStores(invocationRoot);
73
+ const selected = input.agents.map((localAgentId) => {
74
+ const databasePath = stores.find((path) => basename(dirname(path)) === localAgentId);
75
+ if (databasePath === undefined)
76
+ throw new Error("The selected Agent is not active in this project");
77
+ const within = relative(invocationRoot, databasePath);
78
+ if (isAbsolute(within) || within === ".." || within.startsWith("../"))
79
+ throw new Error("The selected Agent is outside this project");
80
+ const profile = readPrivateLocalAgentProfile(databasePath);
81
+ return { localAgentId, databasePath, name: profile.name ?? localAgentId };
82
+ });
83
+ const proposed = {
84
+ state: "pending",
85
+ requestId: requests.join.generate(),
86
+ target: input.kind === "oauth"
87
+ ? { kind: "oauth", teamId: input.teamId }
88
+ : { kind: "oyster" },
89
+ invocationRoot,
90
+ machineLabel: input.machineLabel,
91
+ ...deviceKey(),
92
+ agents: selected,
93
+ };
94
+ let chosen;
95
+ await options.account.update(authority, (current) => {
96
+ const prior = current.find((record) => JSON.stringify(record.target) === JSON.stringify(proposed.target));
97
+ if (prior !== undefined) {
98
+ if (prior.invocationRoot !== invocationRoot ||
99
+ prior.machineLabel !== input.machineLabel ||
100
+ JSON.stringify(prior.agents) !== JSON.stringify(selected))
101
+ throw new Error("This scope already has another machine enrollment intent");
102
+ chosen = prior;
103
+ return [...current];
104
+ }
105
+ chosen = proposed;
106
+ return [...current, proposed];
107
+ });
108
+ if (chosen === undefined)
109
+ throw new Error("The credential owner did not persist an enrollment");
110
+ const record = chosen;
111
+ if (record.state === "enrolled")
112
+ return {
113
+ state: "enrolled",
114
+ deviceId: record.deviceId,
115
+ agents: record.bindings,
116
+ };
117
+ const url = new URL(input.kind === "oauth"
118
+ ? `/api/teams/${encodeURIComponent(input.teamId)}/local/mesh/join`
119
+ : "/local/mesh/join", origin);
120
+ // Only OAuth owns refresh/retry. An Oyster key is ephemeral enrollment
121
+ // authority; neither the credential owner nor later Seam dials receive it.
122
+ const request = input.kind === "oauth"
123
+ ? fungiAccountFetch(origin, options)
124
+ : (options.fetch ?? globalThis.fetch);
125
+ const headers = new Headers({ "content-type": "application/json" });
126
+ if (input.kind === "oyster-key")
127
+ headers.set("Authorization", `Bearer ${input.key}`);
128
+ const init = {
129
+ method: "POST",
130
+ headers,
131
+ redirect: "error",
132
+ body: JSON.stringify({
133
+ requestId: record.requestId,
134
+ machineLabel: record.machineLabel,
135
+ publicKey: record.publicKey,
136
+ // Local directory selectors and database paths remain private.
137
+ agents: record.agents.map(({ name }) => ({ name })),
138
+ }),
139
+ };
140
+ if (options.signal !== undefined)
141
+ init.signal = options.signal;
142
+ let response;
143
+ try {
144
+ response = await request(url, init);
145
+ }
146
+ finally {
147
+ if (input.kind === "oyster-key") {
148
+ // Release Local-owned references before decoding/storing the response.
149
+ // This cannot erase immutable string bytes or caller/fetch-owned copies.
150
+ headers.delete("Authorization");
151
+ input.key = "";
152
+ }
153
+ }
154
+ if (!response.ok)
155
+ throw new Error(`Machine enrollment refused with HTTP ${String(response.status)}`);
156
+ const payload = JSON.parse(await readBoundedResponse(response, 64 * 1024, "Machine enrollment"));
157
+ const result = input.kind === "oauth"
158
+ ? { ...responseSchema.parse(payload), scopeId: input.teamId }
159
+ : oysterResponseSchema.parse(payload);
160
+ if (result.agents.length !== record.agents.length ||
161
+ result.agents.some((binding, index) => binding.selection !== index) ||
162
+ new Set(result.agents.map((binding) => binding.agentId)).size !==
163
+ result.agents.length)
164
+ throw new Error("Machine enrollment does not match the selected Agent set");
165
+ const enrolled = localMachineEnrollmentSchema.parse({
166
+ ...record,
167
+ state: "enrolled",
168
+ deviceId: result.deviceId,
169
+ scopeId: result.scopeId,
170
+ bindings: result.agents,
171
+ });
172
+ await options.account.update(authority, (current) => {
173
+ const matching = current.find((candidate) => candidate.requestId === record.requestId);
174
+ if (matching === undefined)
175
+ throw new Error("Machine enrollment was removed before acknowledgment");
176
+ if (matching.state === "enrolled" &&
177
+ (matching.deviceId !== result.deviceId ||
178
+ matching.scopeId !== result.scopeId ||
179
+ JSON.stringify(matching.bindings) !== JSON.stringify(result.agents)))
180
+ throw new Error("Home changed an acknowledged machine enrollment");
181
+ return current.map((candidate) => candidate.requestId === record.requestId ? enrolled : candidate);
182
+ });
183
+ return {
184
+ state: "enrolled",
185
+ deviceId: result.deviceId,
186
+ agents: result.agents,
187
+ };
188
+ }
@@ -0,0 +1,21 @@
1
+ import type { PostEnvelope, PostDeliveryResult } from "@fungi.computer/carrier/post";
2
+ import type { LocalMachineEnrollment, MachineEnrollmentAuthority } from "./private-local-mesh-record.js";
3
+ export type EnrolledLocalMachine = Extract<LocalMachineEnrollment, {
4
+ state: "enrolled";
5
+ }> & {
6
+ authority: MachineEnrollmentAuthority;
7
+ };
8
+ export declare const localMeshLinkKey: (record: EnrolledLocalMachine) => string;
9
+ /**
10
+ * Service-owned native I/O only. The account owns pins, Carrier owns retry
11
+ * custody, and the addressed runtime owns acceptance. No mail is queued here.
12
+ */
13
+ export declare function createLocalMeshLinks(options: {
14
+ credentialPath: string;
15
+ receive: (record: EnrolledLocalMachine, envelope: PostEnvelope) => Promise<PostDeliveryResult>;
16
+ }): {
17
+ refresh: () => Promise<void>;
18
+ isCurrent: (record: EnrolledLocalMachine) => Promise<boolean>;
19
+ deliver: (record: EnrolledLocalMachine, envelope: PostEnvelope) => Promise<PostDeliveryResult>;
20
+ close: () => Promise<void>;
21
+ };