@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,22 @@
1
+ import { type ServiceDrainPhase } from "./private-local-service-log.js";
2
+ import { runLocalAuthCli } from "./auth-cli.js";
3
+ import { type PrivateLocalConfigRoot } from "./private-local-config-root.js";
4
+ import { listPrivateModels } from "./local-models.js";
5
+ import { type OpenLocalServiceRuntime } from "./private-local-service-owner.js";
6
+ export declare function createPrivateLocalServiceShutdown(input: {
7
+ closeOwners: () => Promise<void>;
8
+ closeServer: () => Promise<void>;
9
+ closeDiagnostics?: () => void;
10
+ removeRecord: () => Promise<void>;
11
+ releaseOwner: () => Promise<void>;
12
+ }): {
13
+ finish: () => Promise<void>;
14
+ isDraining: () => boolean;
15
+ phase: () => ServiceDrainPhase;
16
+ };
17
+ /** Native Bun listener retained from TUI; service owns both listener and stores. */
18
+ export declare function runPrivateLocalService(directory: string, openRuntime?: OpenLocalServiceRuntime, listModels?: typeof listPrivateModels, runAuth?: typeof runLocalAuthCli,
19
+ /** How often the service releases Agents nothing uses. */
20
+ idleSweepMs?: number,
21
+ /** Narrow manifest I/O port, independent of the Agent-runtime opener. */
22
+ readRecoveryManifest?: (path: string) => Promise<string>, root?: PrivateLocalConfigRoot): Promise<void>;
@@ -0,0 +1,407 @@
1
+ import { localFailureMetadata, logEvent } from "./log-event.js";
2
+ import { bodyLimit } from "hono/body-limit";
3
+ import { enrollPrivateLocalMesh, localMeshJoinSchema, } from "./private-local-mesh-enrollment.js";
4
+ import { createLocalMachineEnrollmentAccount, registerFungiAccountOwner, signOutOwnedFungiAccount, } from "./private-fungi-account.js";
5
+ import { createServiceLogLifecycle, } from "./private-local-service-log.js";
6
+ import { stopAllRequestSchema } from "./private-local-stop-all.js";
7
+ import { FungiMcpSelectionConflict } from "./private-fungi-mcp-selection.js";
8
+ import { LocalSandboxUnavailable } from "./private-local-containment.js";
9
+ import { PrivateLocalAuthTarget } from "./private-local-auth-rpc.js";
10
+ import { runLocalAuthCli } from "./auth-cli.js";
11
+ import { capturePrivateLocalConfigRoot, } from "./private-local-config-root.js";
12
+ import { Hono } from "hono";
13
+ import { correlationKey } from "@fungi.computer/cairn";
14
+ import { createLocalUiAttachment, } from "./private-local-ui-attachment.js";
15
+ import { localServiceIds } from "./private-local-service-ids.js";
16
+ import writeFileAtomic from "write-file-atomic";
17
+ import { z } from "zod";
18
+ import { privateLocalExecPathSchema } from "./private-local-exec-path.js";
19
+ import { listPrivateModels } from "./local-models.js";
20
+ import { privateSqliteFailureCode } from "./private-sqlite-owner.js";
21
+ import { openPrivateLocalBunServer } from "./private-bun-shiitake-host.js";
22
+ import { readOwnProcessStart } from "./private-local-process-identity.js";
23
+ import { LocalStoreSchemaError, readPrivateLocalSessionDiscovery, } from "./private-local-session-discovery.js";
24
+ import { AgentArchiveRefused } from "@fungi.computer/shiitake/agent-control";
25
+ import { createLocalServiceOwners, LocalAgentSelectionAmbiguous, LocalSessionPathConflict, } from "./private-local-service-owner.js";
26
+ import { acquireServiceLock, archiveRequestSchema, discoverRequestSchema, privateLocalServiceProtocol, privateLocalServiceVersion, recordPath, removeOwnedServiceRecord, selectionSchema, sessionCreationSchema, sessionInspectionSchema, sandboxStatusSchema, } from "./private-local-service-discovery.js";
27
+ export function createPrivateLocalServiceShutdown(input) {
28
+ let draining;
29
+ let finishing;
30
+ let phase = "active";
31
+ const finish = () => {
32
+ finishing ??= (async () => {
33
+ // Keep discovery and the lease until owners settle. The process owner
34
+ // bounds this whole sequence, including a hung owner or listener.
35
+ phase = "owners";
36
+ draining ??= input.closeOwners();
37
+ await draining;
38
+ phase = "listener";
39
+ await input.closeServer();
40
+ // Stop diagnostics before releasing ownership. Its resource latches I/O
41
+ // faults and disables writes; a diagnostic fault cannot strand a service
42
+ // registration/lease or make normal shutdown reject.
43
+ try {
44
+ input.closeDiagnostics?.();
45
+ }
46
+ catch (cause) {
47
+ // Diagnostics cannot strand registration/lease or fail a normal stop.
48
+ // The owned resource latches its typed fault; this protects cleanup
49
+ // against an unexpected disposal callback failure as well.
50
+ logEvent("service-log.dispose", cause);
51
+ }
52
+ try {
53
+ phase = "record";
54
+ await input.removeRecord();
55
+ }
56
+ finally {
57
+ phase = "lease";
58
+ await input.releaseOwner();
59
+ }
60
+ phase = "closed";
61
+ })();
62
+ return finishing;
63
+ };
64
+ return {
65
+ finish,
66
+ isDraining: () => draining !== undefined,
67
+ phase: () => phase,
68
+ };
69
+ }
70
+ /** Native Bun listener retained from TUI; service owns both listener and stores. */
71
+ export async function runPrivateLocalService(directory, openRuntime, listModels = listPrivateModels, runAuth = runLocalAuthCli,
72
+ /** How often the service releases Agents nothing uses. */
73
+ idleSweepMs,
74
+ /** Narrow manifest I/O port, independent of the Agent-runtime opener. */
75
+ readRecoveryManifest, root = capturePrivateLocalConfigRoot()) {
76
+ // Publish the same discriminator in the pointer and the acquired owner row.
77
+ const startedAt = await readOwnProcessStart();
78
+ const release = await acquireServiceLock(directory, "owner");
79
+ const serviceId = localServiceIds.service.generate();
80
+ const version = privateLocalServiceVersion();
81
+ const jsc = await import("bun:jsc");
82
+ const diagnostics = createServiceLogLifecycle({
83
+ directory,
84
+ pid: process.pid,
85
+ version,
86
+ readMemory: () => ({
87
+ rss: process.memoryUsage().rss,
88
+ heap: jsc.heapStats(),
89
+ }),
90
+ });
91
+ const owners = createLocalServiceOwners(openRuntime, directory, undefined, idleSweepMs, readRecoveryManifest, diagnostics.recoveryDisabled, root);
92
+ const prefix = `/service/${serviceId}`;
93
+ const releaseAccountOwner = registerFungiAccountOwner(root.credentialPath, () => owners.refreshMesh());
94
+ const app = new Hono();
95
+ app.onError((error, context) => {
96
+ diagnostics.failure(localFailureMetadata("service.request", error));
97
+ if (error instanceof LocalStoreSchemaError)
98
+ return context.json({
99
+ code: "store_schema_incompatible",
100
+ databasePath: error.databasePath,
101
+ error: error.message,
102
+ }, 409);
103
+ if (error instanceof LocalSandboxUnavailable)
104
+ return context.json({ code: "sandbox_unavailable", reason: error.reason }, 409);
105
+ if (error instanceof z.ZodError)
106
+ return context.json({
107
+ code: "invalid_selection",
108
+ issues: error.issues.map((issue) => ({
109
+ field: issue.path.join("."),
110
+ reason: issue.message,
111
+ })),
112
+ }, 400);
113
+ if (error instanceof LocalSessionPathConflict)
114
+ return context.json({ code: "session_path_conflict", error: error.message }, 409);
115
+ if (error instanceof LocalAgentSelectionAmbiguous)
116
+ return context.json({ code: "agent_selection_ambiguous", agentIds: error.agentIds }, 409);
117
+ if (error instanceof FungiMcpSelectionConflict)
118
+ return context.json({ error: error.message }, 409);
119
+ if (error instanceof AgentArchiveRefused)
120
+ return context.json({
121
+ code: "agent_archive_refused",
122
+ reason: error.reason,
123
+ error: error.message,
124
+ }, 409);
125
+ const sqliteFailure = privateSqliteFailureCode(error);
126
+ if (sqliteFailure === "busy" || sqliteFailure === "locked")
127
+ return context.json({ code: "store_in_use" }, 409);
128
+ logEvent("service-listener.request", error);
129
+ return context.json({
130
+ code: "internal_error",
131
+ error: error instanceof Error ? error.message : "Unknown failure",
132
+ }, 500);
133
+ });
134
+ const routes = new Map();
135
+ const stopped = Promise.withResolvers();
136
+ let server;
137
+ /** Identity-only pointer; live routes add current diagnostics separately. */
138
+ let record;
139
+ let recordWritten = false;
140
+ /**
141
+ * Mount an Agent's route by store, not by its open runtime: a request on a
142
+ * route reopens an Agent the service released while it was idle.
143
+ */
144
+ const mount = (selected, fungiMcp, attachment) => {
145
+ // Inspectors share a read route; each UI gets a private host-issued route.
146
+ let mounted = attachment === undefined ? routes.get(selected.databasePath) : undefined;
147
+ if (!mounted) {
148
+ const path = `/agents/${correlationKey()}`;
149
+ const routePrefix = prefix + path;
150
+ const target = {
151
+ invocationRoot: selected.invocationRoot,
152
+ databasePath: selected.databasePath,
153
+ fungiMcp,
154
+ };
155
+ mounted = {
156
+ path,
157
+ fetch: (request) => {
158
+ const url = new URL(request.url);
159
+ url.pathname = url.pathname.slice(routePrefix.length) || "/";
160
+ if (request.method === "DELETE" &&
161
+ url.pathname === "/" &&
162
+ attachment !== undefined) {
163
+ attachment.close();
164
+ return new Response(null, { status: 204 });
165
+ }
166
+ return owners.serve(target, new Request(url.toString(), request), attachment);
167
+ },
168
+ effectiveModel: (sessionId, signal) => owners.effectiveModel(target, sessionId, signal),
169
+ sandboxStatus: (signal) => owners.sandboxStatus(target, signal),
170
+ };
171
+ const key = attachment === undefined ? selected.databasePath : path;
172
+ routes.set(key, mounted);
173
+ attachment?.signal.addEventListener("abort", () => {
174
+ routes.delete(key);
175
+ }, { once: true });
176
+ }
177
+ return {
178
+ agentId: selected.agentId,
179
+ databasePath: selected.databasePath,
180
+ sessionId: selected.sessionId,
181
+ routePath: mounted.path,
182
+ };
183
+ };
184
+ const shutdown = createPrivateLocalServiceShutdown({
185
+ closeOwners: owners.close,
186
+ closeServer: async () => {
187
+ await server?.close();
188
+ },
189
+ removeRecord: async () => {
190
+ if (recordWritten)
191
+ await removeOwnedServiceRecord(directory, serviceId);
192
+ },
193
+ releaseOwner: release,
194
+ closeDiagnostics: diagnostics.close,
195
+ });
196
+ const drainDeadlineMs = 4_000;
197
+ let deadline;
198
+ const finish = () => {
199
+ // Leave headroom before the maintained client's 5-second SIGKILL fallback.
200
+ // Never reset the overall budget for another Agent or another signal.
201
+ deadline ??= setTimeout(() => {
202
+ // The background service has no stderr (spawned with stdio "ignore");
203
+ // the private service log is its only durable diagnostic record.
204
+ diagnostics.drainTimeout({
205
+ deadlineMs: drainDeadlineMs,
206
+ phase: shutdown.phase(),
207
+ unfinishedOwners: owners.unfinishedOwners(),
208
+ });
209
+ // A Promise race cannot interrupt Agent.close's uninterruptible work.
210
+ // Committed SQLite facts and recovery references remain for the next
211
+ // owner; do not pretend those unfinished Agents closed successfully.
212
+ process.exit(1);
213
+ }, drainDeadlineMs);
214
+ const finishing = shutdown.finish();
215
+ void finishing.then(() => {
216
+ clearTimeout(deadline);
217
+ stopped.resolve(undefined);
218
+ }, (cause) => {
219
+ stopped.reject(cause);
220
+ });
221
+ return finishing;
222
+ };
223
+ const onSignal = (reason) => {
224
+ diagnostics.stopping(reason);
225
+ void finish();
226
+ };
227
+ const onInterrupt = () => {
228
+ onSignal("SIGINT");
229
+ };
230
+ const onTerminate = () => {
231
+ onSignal("SIGTERM");
232
+ };
233
+ const isLocalRequest = (request) => !request.headers.has("origin") &&
234
+ new URL(request.url).hostname === "127.0.0.1";
235
+ try {
236
+ app.use("*", async (context, next) => {
237
+ const request = context.req.raw;
238
+ // Local client only: no browser-origin admission or DNS rebinding authority.
239
+ if (!isLocalRequest(request))
240
+ return context.body(null, 403);
241
+ const path = new URL(request.url).pathname;
242
+ // While SIGTERM drains its owners, only the handshake answers, so
243
+ // `fungi stop` can still confirm the process it is waiting on.
244
+ if (shutdown.isDraining() && path !== `${prefix}/status`)
245
+ return context.body(null, 503);
246
+ await next();
247
+ return;
248
+ });
249
+ app.get(`${prefix}/status`, (context) => record === undefined
250
+ ? context.body(null, 503)
251
+ : context.json({
252
+ ...record,
253
+ diagnostics: diagnostics.getStatus(),
254
+ }));
255
+ app.get(`${prefix}/models`, async (context) => context.json(await listModels(root.credentialPath)));
256
+ app.post(`${prefix}/mesh/join`, bodyLimit({ maxSize: 512 * 1024 }), async (context) => {
257
+ const enrolled = await enrollPrivateLocalMesh(localMeshJoinSchema.parse(await context.req.json()), {
258
+ credentialPath: root.credentialPath,
259
+ signal: context.req.raw.signal,
260
+ account: createLocalMachineEnrollmentAccount(root.credentialPath),
261
+ });
262
+ await owners.refreshMesh();
263
+ // Enrollment is not a confirmed shared connection or command receipt.
264
+ return context.json(enrolled, 202);
265
+ });
266
+ app.post(`${prefix}/discover`, async (context) => {
267
+ const input = discoverRequestSchema.parse(await context.req.json());
268
+ return context.json(await readPrivateLocalSessionDiscovery(input.invocationRoot, {
269
+ signal: context.req.raw.signal,
270
+ archived: input.archived,
271
+ }));
272
+ });
273
+ app.post(`${prefix}/reload`, async (context) => {
274
+ try {
275
+ const input = z
276
+ .strictObject({
277
+ invocationRoot: z.string().min(1),
278
+ clientPath: privateLocalExecPathSchema,
279
+ })
280
+ .parse(await context.req.json());
281
+ await owners.reload(input.invocationRoot, input.clientPath);
282
+ return context.json({
283
+ message: "Configuration reloaded; applies from the next turn",
284
+ });
285
+ }
286
+ catch (cause) {
287
+ return context.json({
288
+ error: `${cause instanceof Error ? cause.message : String(cause)}\nOld configuration kept.`,
289
+ }, 400);
290
+ }
291
+ });
292
+ app.post(`${prefix}/stop-all`, async (context) => context.json(await owners.stopAll(stopAllRequestSchema.parse(await context.req.json()))));
293
+ app.post(`${prefix}/archive`, async (context) => {
294
+ await owners.archive(archiveRequestSchema.parse(await context.req.json()), context.req.raw.signal);
295
+ return context.body(null, 204);
296
+ });
297
+ app.get(`${prefix}/agents/:route/sessions/:sessionId/effective-model`, async (context) => {
298
+ const route = [...routes.values()].find((entry) => entry.path === `/agents/${context.req.param("route")}`);
299
+ if (route === undefined)
300
+ return context.body(null, 404);
301
+ return context.json(await route.effectiveModel(context.req.param("sessionId"), context.req.raw.signal));
302
+ });
303
+ app.get(`${prefix}/agents/:route/sandbox`, async (context) => {
304
+ const route = [...routes.values()].find((entry) => entry.path === `/agents/${context.req.param("route")}`);
305
+ if (route === undefined)
306
+ return context.body(null, 404);
307
+ return context.json(sandboxStatusSchema.parse(await route.sandboxStatus(context.req.raw.signal)));
308
+ });
309
+ app.post(`${prefix}/sessions`, async (context) => {
310
+ const input = sessionCreationSchema.parse(await context.req.json());
311
+ const selected = await owners.createSession(input);
312
+ try {
313
+ return context.json(mount(selected, input.fungiMcp, createLocalUiAttachment(selected.sessionId, input.uiAttachmentLifetime)));
314
+ }
315
+ finally {
316
+ selected.release();
317
+ }
318
+ });
319
+ app.post(`${prefix}/select`, async (context) => {
320
+ const input = selectionSchema.parse(await context.req.json());
321
+ const selected = await owners.select(input);
322
+ try {
323
+ return context.json(mount(selected, input.fungiMcp, createLocalUiAttachment(selected.sessionId, input.uiAttachmentLifetime)));
324
+ }
325
+ finally {
326
+ selected.release();
327
+ }
328
+ });
329
+ app.post(`${prefix}/inspect`, async (context) => {
330
+ const input = sessionInspectionSchema.parse(await context.req.json());
331
+ const selected = await owners.select(input);
332
+ try {
333
+ return context.json({
334
+ diagnostics: diagnostics.getStatus(),
335
+ selected: mount(selected, input.fungiMcp, undefined),
336
+ snapshot: await selected.session.read({
337
+ limit: input.limit,
338
+ includeSandboxFailures: true,
339
+ }),
340
+ });
341
+ }
342
+ finally {
343
+ selected.release();
344
+ }
345
+ });
346
+ app.all(`${prefix}/agents/*`, (context) => {
347
+ const path = new URL(context.req.url).pathname;
348
+ for (const route of routes.values()) {
349
+ if (path.startsWith(`${prefix}${route.path}/`))
350
+ return route.fetch(context.req.raw);
351
+ }
352
+ return context.body(null, 404);
353
+ });
354
+ server = openPrivateLocalBunServer((request) => app.fetch(request), undefined, {
355
+ path: `${prefix}/auth`,
356
+ admit: (request) => isLocalRequest(request) && !shutdown.isDraining(),
357
+ createTarget: () => new PrivateLocalAuthTarget((argv, options) => runAuth(argv, {
358
+ ...options,
359
+ credentialPath: root.credentialPath,
360
+ }), async (request) => {
361
+ if (request.credentialPath !== root.credentialPath)
362
+ throw new Error("Fungi account belongs to another Local home");
363
+ return await signOutOwnedFungiAccount(request.endpoint, {
364
+ credentialPath: root.credentialPath,
365
+ });
366
+ }),
367
+ });
368
+ record = {
369
+ protocol: privateLocalServiceProtocol,
370
+ version,
371
+ serviceId,
372
+ pid: process.pid,
373
+ startedAt,
374
+ baseUrl: server.baseUrl,
375
+ };
376
+ await writeFileAtomic(recordPath(directory), JSON.stringify(record), {
377
+ mode: 0o600,
378
+ });
379
+ recordWritten = true;
380
+ diagnostics.ready(serviceId);
381
+ process.on("SIGINT", onInterrupt);
382
+ process.on("SIGTERM", onTerminate);
383
+ // Health and shutdown must answer before any saved Agent is reopened.
384
+ // Owner shutdown tracks this task; requests share its ordinary ownerFor.
385
+ void owners.restore();
386
+ void owners.refreshMesh().catch((cause) => {
387
+ logEvent("service-listener.mesh-start", cause);
388
+ });
389
+ await stopped.promise;
390
+ }
391
+ catch (cause) {
392
+ logEvent("service-listener.lifecycle", cause);
393
+ diagnostics.failure(localFailureMetadata("service-listener.lifecycle", cause));
394
+ diagnostics.stopping(recordWritten ? "runtime-failure" : "startup-failure");
395
+ throw cause;
396
+ }
397
+ finally {
398
+ process.off("SIGINT", onInterrupt);
399
+ process.off("SIGTERM", onTerminate);
400
+ try {
401
+ await finish();
402
+ }
403
+ finally {
404
+ releaseAccountOwner();
405
+ }
406
+ }
407
+ }
@@ -0,0 +1,128 @@
1
+ import type { heapStats } from "bun:jsc";
2
+ import { localFailureMetadata } from "./log-event.js";
3
+ import { z } from "zod";
4
+ export declare const serviceLogRecordBytes: number;
5
+ export declare const serviceLogSampleIntervalMs: number;
6
+ export type HeapStatistics = ReturnType<typeof heapStats>;
7
+ export type FailureMetadata = ReturnType<typeof localFailureMetadata>;
8
+ export declare const serviceDiagnosticsStatusSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
9
+ state: z.ZodLiteral<"active">;
10
+ }, z.core.$strict>, z.ZodObject<{
11
+ state: z.ZodLiteral<"failed">;
12
+ code: z.ZodEnum<{
13
+ "sink-open-failed": "sink-open-failed";
14
+ "sink-write-failed": "sink-write-failed";
15
+ "sink-close-failed": "sink-close-failed";
16
+ }>;
17
+ since: z.ZodISODateTime;
18
+ }, z.core.$strict>, z.ZodObject<{
19
+ state: z.ZodLiteral<"recovery-disabled">;
20
+ code: z.ZodEnum<{
21
+ "read-failed": "read-failed";
22
+ "decode-failed": "decode-failed";
23
+ }>;
24
+ since: z.ZodISODateTime;
25
+ }, z.core.$strict>], "state">;
26
+ export type ServiceDiagnosticsStatus = z.infer<typeof serviceDiagnosticsStatusSchema>;
27
+ export type ServiceDiagnosticsFailureCode = Extract<ServiceDiagnosticsStatus, {
28
+ state: "failed";
29
+ }>["code"];
30
+ export type RecoveryPersistenceFailureCode = Extract<ServiceDiagnosticsStatus, {
31
+ state: "recovery-disabled";
32
+ }>["code"];
33
+ export type ServiceLogWriter = Readonly<{
34
+ writeLine: (line: string) => void;
35
+ close: () => void;
36
+ }>;
37
+ export type OpenServiceLogWriter = (directory: string) => ServiceLogWriter;
38
+ /** Catchable observations, not an inferred cause for a missing final record. */
39
+ export type ServiceLogEvent = Readonly<{
40
+ event: "starting";
41
+ }> | Readonly<{
42
+ event: "ready";
43
+ serviceId: string;
44
+ }> | Readonly<{
45
+ event: "stopping" | "stopped";
46
+ reason: "SIGINT" | "SIGTERM" | "startup-failure" | "runtime-failure";
47
+ }> | Readonly<{
48
+ event: "failure";
49
+ failure: FailureMetadata;
50
+ }> | Readonly<{
51
+ event: "recovery-persistence-disabled";
52
+ code: RecoveryPersistenceFailureCode;
53
+ }> | Readonly<{
54
+ event: "memory";
55
+ rss: number;
56
+ heap: HeapStatistics;
57
+ }> | Readonly<{
58
+ event: "memory-unavailable";
59
+ failure: FailureMetadata;
60
+ }> | Readonly<{
61
+ event: "drain-timeout";
62
+ deadlineMs: number;
63
+ phase: ServiceDrainPhase;
64
+ unfinishedOwners: readonly string[];
65
+ unfinishedOwnerCount: number;
66
+ }>;
67
+ /** Where SIGTERM drain stood; owners settle before listener, record, lease. */
68
+ export type ServiceDrainPhase = "active" | "owners" | "listener" | "record" | "lease" | "closed";
69
+ export type ServiceLogRecord = ServiceLogEvent & Readonly<{
70
+ source: "local-service";
71
+ timestamp: string;
72
+ pid: number;
73
+ version: string;
74
+ }>;
75
+ export type ServiceLogShutdownReason = Extract<ServiceLogEvent, {
76
+ event: "stopping" | "stopped";
77
+ }>["reason"];
78
+ export type ServiceLogLifecycle = Readonly<{
79
+ getStatus: () => ServiceDiagnosticsStatus;
80
+ ready: (serviceId: string) => void;
81
+ stopping: (reason: ServiceLogShutdownReason) => void;
82
+ failure: (failure: FailureMetadata) => void;
83
+ recoveryDisabled: (code: RecoveryPersistenceFailureCode) => void;
84
+ /** Synchronous, so it lands before the bounded drain exits the process. */
85
+ drainTimeout: (drain: Readonly<{
86
+ deadlineMs: number;
87
+ phase: ServiceDrainPhase;
88
+ unfinishedOwners: readonly string[];
89
+ }>) => void;
90
+ close: () => void;
91
+ }>;
92
+ /**
93
+ * Preserve ALL maintained count entries, in their producer order, when they fit.
94
+ * If they do not, explicitly omit the maps as a whole. No ranking/top-N policy,
95
+ * count substitution, exception serialization or byte-sliced JSON.
96
+ */
97
+ export declare function encodeServiceLogRecord(record: ServiceLogRecord): string;
98
+ /**
99
+ * One timer per service lifetime. The listener must close it before disposing
100
+ * the writer, and dispose the writer before releasing its existing owner lease.
101
+ * Both entry paths use this same owned resource.
102
+ */
103
+ export declare function createServiceLogSampler(input: Readonly<{
104
+ readMemory: () => Readonly<{
105
+ rss: number;
106
+ heap: HeapStatistics;
107
+ }>;
108
+ emit: (event: ServiceLogEvent) => void;
109
+ }>): {
110
+ start(): void;
111
+ close(): void;
112
+ };
113
+ /**
114
+ * Diagnostics resources only: no service lock, signal handler or runtime drain.
115
+ * The existing listener remains the owner. Native file I/O stays in this module.
116
+ * Writer faults latch a typed live failure, stop sampling and close once.
117
+ * They never escape into a request, turn, signal handler or timer callback.
118
+ */
119
+ export declare function createServiceLogLifecycle(input: Readonly<{
120
+ pid: number;
121
+ version: string;
122
+ directory: string;
123
+ openWriter?: OpenServiceLogWriter;
124
+ readMemory: () => Readonly<{
125
+ rss: number;
126
+ heap: HeapStatistics;
127
+ }>;
128
+ }>): ServiceLogLifecycle;