@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,278 @@
1
+ import { readdir, realpath, stat } from "node:fs/promises";
2
+ import { basename, dirname, join, resolve } from "node:path";
3
+ import * as Effect from "effect/Effect";
4
+ import { parseModelPreferenceJson } from "@fungi.computer/shiitake/model-preference";
5
+ import { z } from "zod";
6
+ import { readAgentDefaultModelJson } from "./private-local-agent-default-model.js";
7
+ import { localAgentFolderSchema, resolveAgentFolder, } from "./private-local-sandbox-policy.js";
8
+ import { logEvent } from "./log-event.js";
9
+ import { readSessionSummaries, } from "@fungi.computer/shiitake/effect";
10
+ import { createPrivateSqliteOwner, openPrivateReadOnlySqliteDatabase, privateSqliteFailureCode, } from "./private-sqlite-owner.js";
11
+ function compareSummaries(left, right) {
12
+ return (right.updatedAt - left.updatedAt ||
13
+ right.createdAt - left.createdAt ||
14
+ left.title.localeCompare(right.title) ||
15
+ left.databasePath.localeCompare(right.databasePath) ||
16
+ left.sessionId.localeCompare(right.sessionId));
17
+ }
18
+ class LocalSessionDiscoveryError extends Error {
19
+ kind;
20
+ constructor(kind, options) {
21
+ super("Local session store could not be read", options);
22
+ this.kind = kind;
23
+ }
24
+ }
25
+ function sqliteProblemKind(code) {
26
+ if (code === "busy" || code === "locked")
27
+ return "in_use";
28
+ if (code === "corrupt" || code === "not_a_database")
29
+ return "corrupt";
30
+ return "unreadable";
31
+ }
32
+ function problemKind(cause) {
33
+ return cause instanceof LocalSessionDiscoveryError
34
+ ? cause.kind
35
+ : sqliteProblemKind(privateSqliteFailureCode(cause));
36
+ }
37
+ async function immediateStoreCandidates(root, relative) {
38
+ const directory = resolve(root, relative);
39
+ let entries;
40
+ try {
41
+ entries = await readdir(directory, { withFileTypes: true });
42
+ }
43
+ catch (cause) {
44
+ if (cause instanceof Error && "code" in cause && cause.code === "ENOENT")
45
+ return [];
46
+ throw cause;
47
+ }
48
+ return Promise.all(entries
49
+ .filter((entry) => (entry.isDirectory() || entry.isSymbolicLink()) &&
50
+ entry.name.length > 0)
51
+ .map(async (entry) => {
52
+ const path = join(directory, entry.name, "agent.sqlite");
53
+ try {
54
+ const metadata = await stat(path);
55
+ if (!metadata.isFile())
56
+ return undefined;
57
+ return { path };
58
+ }
59
+ catch (cause) {
60
+ if (cause instanceof Error &&
61
+ "code" in cause &&
62
+ cause.code === "ENOENT")
63
+ return undefined;
64
+ throw cause;
65
+ }
66
+ })).then((values) => values.filter((value) => value !== undefined));
67
+ }
68
+ /** The one plain phrase for a store written before the current schema, with how to reset it. */
69
+ export function unreadableStoreReason(agentId) {
70
+ return `predates the current schema; move .fungi/agents/${agentId} aside to reset it`;
71
+ }
72
+ /**
73
+ * Whether a store's Agent is archived. A store this cannot read counts as
74
+ * not archived, so discovery still reports it as a problem.
75
+ */
76
+ export function isArchivedStore(path) {
77
+ try {
78
+ return readPrivateLocalAgentProfile(path).archived;
79
+ }
80
+ catch (cause) {
81
+ if (!(cause instanceof LocalStoreSchemaError))
82
+ logEvent("store-archived", cause);
83
+ return false;
84
+ }
85
+ }
86
+ export async function listPrivateLocalAgentStores(root, visibility = "active") {
87
+ const candidates = await immediateStoreCandidates(root, ".fungi/agents");
88
+ const paths = new Set();
89
+ for (const candidate of candidates) {
90
+ try {
91
+ const path = await realpath(candidate.path);
92
+ if (visibility === "all" ||
93
+ isArchivedStore(path) === (visibility === "archived"))
94
+ paths.add(path);
95
+ }
96
+ catch (cause) {
97
+ if (cause instanceof Error && "code" in cause && cause.code === "ENOENT")
98
+ continue;
99
+ throw cause;
100
+ }
101
+ }
102
+ return [...paths];
103
+ }
104
+ const storedProfileSchema = z.strictObject({
105
+ name: z.string().nullable(),
106
+ createdBy: z.string().nullable(),
107
+ archived: z.union([z.literal(0), z.literal(1)]),
108
+ requireSandbox: z.union([z.literal(0), z.literal(1)]),
109
+ folder: localAgentFolderSchema,
110
+ model: z.string(),
111
+ });
112
+ /** A Local or Woodstock store outside the current schema; Local never migrates it. */
113
+ export class LocalStoreSchemaError extends Error {
114
+ databasePath;
115
+ name = "LocalStoreSchemaError";
116
+ constructor(databasePath, options) {
117
+ super(`Agent store ${databasePath} is incompatible with this version of Fungi. To reset, stop the owning Fungi service, then move this database, its .owner.sqlite sidecar and any associated SQLite journal files aside together. Create a fresh Agent; existing Sessions are not migrated. Keep the moved files if you need their data.`, options);
118
+ this.databasePath = databasePath;
119
+ }
120
+ }
121
+ /**
122
+ * One Agent store's chosen display name, default model, whether it is
123
+ * archived and the Agent that created it (absent when a human made it), read
124
+ * without opening a host.
125
+ */
126
+ export function readPrivateLocalAgentProfile(path) {
127
+ const database = openPrivateReadOnlySqliteDatabase(path);
128
+ try {
129
+ const columns = database
130
+ .prepare("PRAGMA table_info(private_local_identity)")
131
+ .all();
132
+ if (!["name", "created_by", "archived", "require_sandbox", "folder"].every((required) => columns.some((column) => column.name === required)))
133
+ throw new LocalStoreSchemaError(path);
134
+ const identity = database
135
+ .prepare("SELECT name, created_by, archived, require_sandbox, folder FROM private_local_identity WHERE singleton = 1")
136
+ .get();
137
+ const stored = storedProfileSchema.parse({
138
+ name: identity?.name,
139
+ createdBy: identity?.created_by,
140
+ archived: identity?.archived,
141
+ requireSandbox: identity?.require_sandbox,
142
+ folder: identity?.folder,
143
+ model: readAgentDefaultModelJson(database),
144
+ });
145
+ return {
146
+ name: stored.name ?? undefined,
147
+ createdBy: stored.createdBy,
148
+ archived: stored.archived === 1,
149
+ requireSandbox: stored.requireSandbox === 1,
150
+ folder: stored.folder,
151
+ model: parseModelPreferenceJson(stored.model),
152
+ };
153
+ }
154
+ finally {
155
+ database.close();
156
+ }
157
+ }
158
+ export async function readPrivateLocalSessionSummaries(path) {
159
+ const database = openPrivateReadOnlySqliteDatabase(path);
160
+ try {
161
+ database.exec("PRAGMA foreign_keys = ON");
162
+ let sqliteFailure;
163
+ const owner = createPrivateSqliteOwner(database, (code) => {
164
+ sqliteFailure = code;
165
+ });
166
+ const summaries = [];
167
+ let after;
168
+ try {
169
+ do {
170
+ const page = await Effect.runPromise(readSessionSummaries(owner, after));
171
+ for (const row of page.sessions) {
172
+ // One unreadable Session marks this store corrupt, as before.
173
+ if (row.kind === "unavailable")
174
+ throw new LocalSessionDiscoveryError("corrupt");
175
+ summaries.push({
176
+ sessionId: row.sessionId,
177
+ title: row.title,
178
+ createdAt: row.createdAt,
179
+ updatedAt: row.updatedAt,
180
+ databasePath: path,
181
+ });
182
+ }
183
+ after = page.next;
184
+ } while (after !== undefined);
185
+ }
186
+ catch (cause) {
187
+ if (cause instanceof LocalSessionDiscoveryError)
188
+ throw cause;
189
+ throw new LocalSessionDiscoveryError(sqliteProblemKind(sqliteFailure), {
190
+ cause,
191
+ });
192
+ }
193
+ return summaries.sort(compareSummaries);
194
+ }
195
+ finally {
196
+ database.close();
197
+ }
198
+ }
199
+ /**
200
+ * Discover existing folder sessions without opening or reconciling a host.
201
+ * Archived Agents and their Sessions are left out, so no picker or "newest
202
+ * Session" choice can land on one; `archived` lists only those instead.
203
+ */
204
+ export async function readPrivateLocalSessionDiscovery(invocationRoot, request = {}) {
205
+ request.signal?.throwIfAborted();
206
+ const root = await realpath(resolve(invocationRoot));
207
+ const paths = await listPrivateLocalAgentStores(root, request.archived === true ? "archived" : "active");
208
+ const discovered = [];
209
+ const agents = [];
210
+ const profiles = new Map();
211
+ const problems = [];
212
+ for (const path of paths) {
213
+ request.signal?.throwIfAborted();
214
+ const agentId = basename(dirname(path));
215
+ try {
216
+ const summaries = await readPrivateLocalSessionSummaries(path);
217
+ discovered.push(...summaries);
218
+ let profile;
219
+ let unreadable;
220
+ try {
221
+ profile = readPrivateLocalAgentProfile(path);
222
+ resolveAgentFolder(root, profile.folder);
223
+ profiles.set(agentId, profile);
224
+ }
225
+ catch (cause) {
226
+ // Shown on the row so the user knows to reset the store; opening it
227
+ // refuses with the same error.
228
+ if (!(cause instanceof LocalStoreSchemaError))
229
+ throw cause;
230
+ unreadable = unreadableStoreReason(agentId);
231
+ }
232
+ agents.push({
233
+ agentId,
234
+ name: profile?.name,
235
+ unreadable,
236
+ databasePath: path,
237
+ sessionCount: summaries.length,
238
+ updatedAt: summaries[0]?.updatedAt,
239
+ });
240
+ }
241
+ catch (cause) {
242
+ problems.push({ kind: problemKind(cause) });
243
+ agents.push({ agentId, databasePath: path, sessionCount: 0 });
244
+ }
245
+ }
246
+ discovered.sort(compareSummaries);
247
+ // The wire carries identity; clients derive the “made by” label from names.
248
+ const labelled = agents.map((agent) => {
249
+ const createdBy = profiles.get(agent.agentId)?.createdBy;
250
+ if (createdBy === undefined)
251
+ return agent;
252
+ return {
253
+ ...agent,
254
+ createdBy,
255
+ };
256
+ });
257
+ return { root, agents: labelled, sessions: discovered, problems };
258
+ }
259
+ /** Project the service-owned discovery into choices that resume through it. */
260
+ export function projectPrivateLocalSessionDiscovery(discovered, connect) {
261
+ const sessions = discovered.sessions.map(({ databasePath, sessionId, ...display }) => Object.freeze({
262
+ ...display,
263
+ databasePath,
264
+ sessionId,
265
+ resume: (resumeRequest) => connect({
266
+ invocationRoot: discovered.root,
267
+ databasePath,
268
+ sessionId,
269
+ fungiMcp: resumeRequest?.fungiMcp,
270
+ requireSandbox: resumeRequest?.requireSandbox,
271
+ }, resumeRequest),
272
+ }));
273
+ return Object.freeze({
274
+ agents: Object.freeze(discovered.agents),
275
+ sessions: Object.freeze(sessions),
276
+ problems: Object.freeze(discovered.problems),
277
+ });
278
+ }
@@ -0,0 +1,44 @@
1
+ import type { ShiitakeAgent, ShiitakeSession } from "@fungi.computer/shiitake";
2
+ import type { ShiitakeEventSourceConstructor } from "@fungi.computer/shiitake/client";
3
+ import { openPrivateLocalRuntimeHost, type PrivateLocalRuntimeHostOptions } from "./private-local-runtime-host.js";
4
+ export type PrivateLocalShiitakeServer = Readonly<{
5
+ baseUrl: string;
6
+ close: () => Promise<void>;
7
+ }>;
8
+ export type PrivateLocalShiitakeServerOpener = (fetch: (request: Request) => Response | Promise<Response>) => PrivateLocalShiitakeServer;
9
+ export type PrivateLocalShiitakeHostOpener = (input: PrivateLocalRuntimeHostOptions) => Promise<PrivateLocalShiitakeHost>;
10
+ /** One private loopback Hono owner plus a neutral client mounted on it. */
11
+ export type PrivateLocalShiitakeHost = Readonly<{
12
+ /** Stable Local directory identity of this authorized Agent. */
13
+ agentId: string;
14
+ /** Canonical file path owned by the server-backed local host. */
15
+ databasePath: string;
16
+ /** Private loopback origin for another neutral Shiitake client. */
17
+ baseUrl: string;
18
+ /** Client-owned neutral Session; close only releases client watches. */
19
+ client: ShiitakeAgent;
20
+ session: ShiitakeSession;
21
+ /** Stop the transport before releasing the RuntimeHost and database. */
22
+ close: () => Promise<void>;
23
+ }>;
24
+ /** Open one bounded private local Hono host on a loopback-only ephemeral port. */
25
+ export declare function openPrivateLocalShiitakeHost(input: PrivateLocalRuntimeHostOptions, openServer: PrivateLocalShiitakeServerOpener, openHost?: typeof openPrivateLocalRuntimeHost, eventSource?: ShiitakeEventSourceConstructor): Promise<PrivateLocalShiitakeHost>;
26
+ /** Explicit static input for one independently owned local worker route. */
27
+ export type PrivateLocalShiitakeWorkerInput = Readonly<{
28
+ name: string;
29
+ owner: PrivateLocalRuntimeHostOptions;
30
+ }>;
31
+ /** One mounted route and its resolved durable Agent store. */
32
+ export type PrivateLocalShiitakeWorkerRoute = Readonly<{
33
+ name: string;
34
+ routePath: string;
35
+ databasePath: string;
36
+ }>;
37
+ /** One physical server hosting a static batch of independent local owners. */
38
+ export type PrivateLocalShiitakeWorkerBatch = Readonly<{
39
+ baseUrl: string;
40
+ workers: readonly PrivateLocalShiitakeWorkerRoute[];
41
+ close: () => Promise<void>;
42
+ }>;
43
+ /** Mount a static batch of Local Agents behind exactly one maintained Hono server. */
44
+ export declare function openPrivateLocalShiitakeWorkerBatch(inputs: readonly PrivateLocalShiitakeWorkerInput[], openServer: PrivateLocalShiitakeServerOpener, openHost?: typeof openPrivateLocalRuntimeHost): Promise<PrivateLocalShiitakeWorkerBatch>;
@@ -0,0 +1,143 @@
1
+ import { logEvent } from "./log-event.js";
2
+ import { Hono } from "hono";
3
+ import * as Schema from "effect/Schema";
4
+ import { createShiitakeClient } from "@fungi.computer/shiitake/client";
5
+ import { createShiitakeHttpRoutes } from "@fungi.computer/shiitake/http";
6
+ import { openPrivateLocalRuntimeHost, } from "./private-local-runtime-host.js";
7
+ function asPrivateShiitakeFailure(cause, message) {
8
+ return cause instanceof Error ? cause : new Error(message, { cause });
9
+ }
10
+ function aggregatePrivateShiitakeFailures(failures, message) {
11
+ if (failures.length === 1) {
12
+ const [failure] = failures;
13
+ if (failure === undefined)
14
+ throw new Error("Missing private Shiitake failure");
15
+ return failure;
16
+ }
17
+ return new AggregateError(failures, message);
18
+ }
19
+ async function capturePrivateShiitakeCloseFailure(failures, close, message) {
20
+ try {
21
+ await close();
22
+ }
23
+ catch (cause) {
24
+ logEvent(message, cause);
25
+ failures.push(asPrivateShiitakeFailure(cause, message));
26
+ }
27
+ }
28
+ async function closePrivateShiitakeResources(failures, client, server, owner) {
29
+ if (client !== undefined)
30
+ await capturePrivateShiitakeCloseFailure(failures, client.close, "Private Shiitake client close failed");
31
+ if (server !== undefined)
32
+ await capturePrivateShiitakeCloseFailure(failures, server.close, "Private Shiitake server close failed");
33
+ await capturePrivateShiitakeCloseFailure(failures, owner.close, "Private Shiitake owner close failed");
34
+ }
35
+ /** Open one bounded private local Hono host on a loopback-only ephemeral port. */
36
+ export async function openPrivateLocalShiitakeHost(input, openServer, openHost = openPrivateLocalRuntimeHost, eventSource) {
37
+ const owner = await openHost(input);
38
+ const routes = createShiitakeHttpRoutes((sessionId) => owner.getSession(sessionId), owner.listSessions, owner.createSession, owner.deleteSession, () => owner.extensions);
39
+ const fetch = (request) => routes.fetch(request);
40
+ let server;
41
+ let client;
42
+ try {
43
+ server = openServer(fetch);
44
+ const mountedServer = server;
45
+ const clientOptions = eventSource === undefined
46
+ ? { baseUrl: mountedServer.baseUrl }
47
+ : { baseUrl: mountedServer.baseUrl, eventSource };
48
+ const mountedClient = createShiitakeClient(clientOptions);
49
+ client = mountedClient;
50
+ let closing;
51
+ return Object.freeze({
52
+ agentId: owner.agentId,
53
+ databasePath: owner.databasePath,
54
+ baseUrl: mountedServer.baseUrl,
55
+ client: mountedClient,
56
+ session: mountedClient.session("main"),
57
+ close: () => {
58
+ closing ??= (async () => {
59
+ const failures = [];
60
+ await closePrivateShiitakeResources(failures, mountedClient, mountedServer, owner);
61
+ if (failures.length > 0)
62
+ throw aggregatePrivateShiitakeFailures(failures, "Private Shiitake host close failed");
63
+ })();
64
+ return closing;
65
+ },
66
+ });
67
+ }
68
+ catch (cause) {
69
+ const failures = [
70
+ asPrivateShiitakeFailure(cause, "Private Shiitake host initialization failed"),
71
+ ];
72
+ await closePrivateShiitakeResources(failures, client, server, owner);
73
+ throw aggregatePrivateShiitakeFailures(failures, "Private Shiitake host initialization failed");
74
+ }
75
+ }
76
+ const localWorkerNameSchema = Schema.String.pipe(Schema.check(Schema.isMinLength(1)), Schema.check(Schema.isPattern(/^[A-Za-z0-9][A-Za-z0-9_-]*$/u)));
77
+ const decodeLocalWorkerName = Schema.decodeUnknownResult(localWorkerNameSchema);
78
+ function validateLocalWorkerInputs(inputs) {
79
+ if (inputs.length === 0)
80
+ throw new Error("Private local Shiitake worker batch cannot be empty");
81
+ const names = new Set();
82
+ return inputs.map((input) => {
83
+ const decoded = decodeLocalWorkerName(input.name);
84
+ if (decoded._tag === "Failure")
85
+ throw new Error("Invalid private local Shiitake worker route identity: " + input.name);
86
+ if (names.has(decoded.success))
87
+ throw new Error("Duplicate private local Shiitake worker route identity: " +
88
+ decoded.success);
89
+ names.add(decoded.success);
90
+ return Object.freeze({ ...input, name: decoded.success });
91
+ });
92
+ }
93
+ async function closePrivateLocalWorkerBatch(failures, server, owners) {
94
+ if (server !== undefined)
95
+ await capturePrivateShiitakeCloseFailure(failures, server.close, "Private Shiitake worker server close failed");
96
+ for (const { owner } of owners)
97
+ await capturePrivateShiitakeCloseFailure(failures, owner.close, "Private Shiitake worker owner close failed");
98
+ }
99
+ /** Mount a static batch of Local Agents behind exactly one maintained Hono server. */
100
+ export async function openPrivateLocalShiitakeWorkerBatch(inputs, openServer, openHost = openPrivateLocalRuntimeHost) {
101
+ const validated = validateLocalWorkerInputs(inputs);
102
+ const owners = [];
103
+ let server;
104
+ try {
105
+ const app = new Hono();
106
+ const workers = [];
107
+ for (const input of validated) {
108
+ const owner = await openHost(input.owner);
109
+ owners.push({ name: input.name, owner });
110
+ const routes = createShiitakeHttpRoutes((sessionId) => owner.getSession(sessionId), owner.listSessions, owner.createSession, owner.deleteSession, () => owner.extensions);
111
+ const routePath = "/workers/" + input.name;
112
+ app.route(routePath, routes);
113
+ workers.push(Object.freeze({
114
+ name: input.name,
115
+ routePath,
116
+ databasePath: owner.databasePath,
117
+ }));
118
+ }
119
+ server = openServer((request) => app.fetch(request));
120
+ const mountedServer = server;
121
+ let closing;
122
+ return Object.freeze({
123
+ baseUrl: mountedServer.baseUrl,
124
+ workers: Object.freeze(workers),
125
+ close: () => {
126
+ closing ??= (async () => {
127
+ const failures = [];
128
+ await closePrivateLocalWorkerBatch(failures, mountedServer, owners);
129
+ if (failures.length > 0)
130
+ throw aggregatePrivateShiitakeFailures(failures, "Private Shiitake worker batch close failed");
131
+ })();
132
+ return closing;
133
+ },
134
+ });
135
+ }
136
+ catch (cause) {
137
+ const failures = [
138
+ asPrivateShiitakeFailure(cause, "Private Shiitake worker batch initialization failed"),
139
+ ];
140
+ await closePrivateLocalWorkerBatch(failures, server, owners);
141
+ throw aggregatePrivateShiitakeFailures(failures, "Private Shiitake worker batch initialization failed");
142
+ }
143
+ }
@@ -0,0 +1,51 @@
1
+ import { getSupportedThinkingLevels, type Api, type Model, type Models, type ThinkingLevel } from "@earendil-works/pi-ai";
2
+ import { type ModelPreference } from "@fungi.computer/shiitake/model-preference";
3
+ /** Which layer decided a startup model: a Session override, the Agent default, or the available login. */
4
+ export type LocalStartupModelSource = "session" | "agent" | "available";
5
+ /** The one model choice a run or a new chat starts from, before it is checked against its provider. */
6
+ export type LocalStartupChoice = Readonly<{
7
+ preference: ModelPreference;
8
+ source: LocalStartupModelSource;
9
+ }>;
10
+ /** The effective model a new chat of one Agent starts with. */
11
+ export type LocalStartupModel = Readonly<{
12
+ providerId: string;
13
+ modelId: string;
14
+ reasoning: ThinkingLevel | undefined;
15
+ source: LocalStartupModelSource;
16
+ }>;
17
+ /** Validate a requested thinking level against the registered model. */
18
+ export declare function validateLocalReasoning(model: Parameters<typeof getSupportedThinkingLevels>[0], requested: ThinkingLevel | undefined): void;
19
+ /** Local's default is medium, clamped through Pi's model capabilities. */
20
+ export declare function defaultLocalReasoning(model: Parameters<typeof getSupportedThinkingLevels>[0]): ThinkingLevel | undefined;
21
+ /**
22
+ * The thinking level a chosen model runs with: the choice's own level, else
23
+ * the opener's level when this model supports it, else Local's default.
24
+ */
25
+ export declare function startupReasoning(selected: Model<Api>, preference: ModelPreference, ownerReasoning?: ThinkingLevel): ThinkingLevel | undefined;
26
+ /** Resolve explicit layers without checking logins or discovering models. */
27
+ export declare function resolveKnownLocalStartupChoice(input: Readonly<{
28
+ session: ModelPreference | null;
29
+ agent: ModelPreference | null;
30
+ }>): LocalStartupChoice | null;
31
+ /**
32
+ * Resolve the explicit choice, or discover the available login. An explicit
33
+ * layer never falls through on an auth failure; the caller reports it.
34
+ */
35
+ export declare function resolveLocalStartupChoice(input: Readonly<{
36
+ models: Models;
37
+ session: ModelPreference | null;
38
+ agent: ModelPreference | null;
39
+ signal: AbortSignal;
40
+ }>): Promise<LocalStartupChoice | null>;
41
+ /**
42
+ * The model a new chat of the Agent at `databasePath` starts with; no store
43
+ * means a new Agent, which starts from the available login. A store that
44
+ * predates the current schema fails loudly. The landing and a
45
+ * fresh Session both resolve through this chain.
46
+ */
47
+ export declare function resolveAgentStartupModel(input: Readonly<{
48
+ models: Models;
49
+ databasePath: string | undefined;
50
+ signal: AbortSignal;
51
+ }>): Promise<LocalStartupModel | null>;
@@ -0,0 +1,86 @@
1
+ import { clampThinkingLevel, getSupportedThinkingLevels, } from "@earendil-works/pi-ai";
2
+ import {} from "@fungi.computer/shiitake/model-preference";
3
+ import { readPrivateLocalAgentProfile } from "./private-local-session-discovery.js";
4
+ import { defaultLocalModel } from "./private-local-default-model.js";
5
+ /** Validate a requested thinking level against the registered model. */
6
+ export function validateLocalReasoning(model, requested) {
7
+ if (requested === undefined)
8
+ return;
9
+ const supported = getSupportedThinkingLevels(model).filter((level) => level !== "off");
10
+ if (!supported.includes(requested))
11
+ throw new Error("Reasoning level " +
12
+ requested +
13
+ " is not supported by " +
14
+ model.provider +
15
+ "/" +
16
+ model.id +
17
+ "; supported levels: " +
18
+ (supported.join(", ") || "none"));
19
+ }
20
+ /** Local's default is medium, clamped through Pi's model capabilities. */
21
+ export function defaultLocalReasoning(model) {
22
+ const level = clampThinkingLevel(model, "medium");
23
+ return level === "off" ? undefined : level;
24
+ }
25
+ /**
26
+ * The thinking level a chosen model runs with: the choice's own level, else
27
+ * the opener's level when this model supports it, else Local's default.
28
+ */
29
+ export function startupReasoning(selected, preference, ownerReasoning) {
30
+ return (preference.reasoning ??
31
+ (ownerReasoning !== undefined &&
32
+ getSupportedThinkingLevels(selected).includes(ownerReasoning)
33
+ ? ownerReasoning
34
+ : defaultLocalReasoning(selected)));
35
+ }
36
+ /** Resolve explicit layers without checking logins or discovering models. */
37
+ export function resolveKnownLocalStartupChoice(input) {
38
+ if (input.session !== null)
39
+ return { preference: input.session, source: "session" };
40
+ if (input.agent !== null)
41
+ return { preference: input.agent, source: "agent" };
42
+ return null;
43
+ }
44
+ /**
45
+ * Resolve the explicit choice, or discover the available login. An explicit
46
+ * layer never falls through on an auth failure; the caller reports it.
47
+ */
48
+ export async function resolveLocalStartupChoice(input) {
49
+ const known = resolveKnownLocalStartupChoice(input);
50
+ if (known !== null)
51
+ return known;
52
+ const available = await defaultLocalModel(input.models, input.signal);
53
+ return available === null
54
+ ? null
55
+ : { preference: available, source: "available" };
56
+ }
57
+ /**
58
+ * The model a new chat of the Agent at `databasePath` starts with; no store
59
+ * means a new Agent, which starts from the available login. A store that
60
+ * predates the current schema fails loudly. The landing and a
61
+ * fresh Session both resolve through this chain.
62
+ */
63
+ export async function resolveAgentStartupModel(input) {
64
+ const agent = input.databasePath === undefined
65
+ ? null
66
+ : readPrivateLocalAgentProfile(input.databasePath).model;
67
+ const choice = await resolveLocalStartupChoice({
68
+ models: input.models,
69
+ session: null,
70
+ agent,
71
+ signal: input.signal,
72
+ });
73
+ if (choice === null)
74
+ return null;
75
+ const { providerId, modelId } = choice.preference;
76
+ const selected = input.models.getModel(providerId, modelId);
77
+ return {
78
+ providerId,
79
+ modelId,
80
+ // A catalog not loaded yet cannot name a level; the run resolves it later.
81
+ reasoning: selected === undefined
82
+ ? choice.preference.reasoning
83
+ : startupReasoning(selected, choice.preference),
84
+ source: choice.source,
85
+ };
86
+ }
@@ -0,0 +1,27 @@
1
+ import { z } from "zod";
2
+ /** The service reports each attempted Agent, including partial failures. */
3
+ export declare const stopAllResultSchema: z.ZodObject<{
4
+ rootAgentId: z.ZodString;
5
+ agents: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
6
+ agentId: z.ZodString;
7
+ name: z.ZodString;
8
+ outcome: z.ZodEnum<{
9
+ stopped: "stopped";
10
+ idle: "idle";
11
+ }>;
12
+ }, z.core.$strict>, z.ZodObject<{
13
+ agentId: z.ZodString;
14
+ name: z.ZodString;
15
+ outcome: z.ZodLiteral<"failed">;
16
+ reason: z.ZodString;
17
+ }, z.core.$strict>], "outcome">>;
18
+ discoveryFailures: z.ZodArray<z.ZodObject<{
19
+ agentId: z.ZodString;
20
+ reason: z.ZodString;
21
+ }, z.core.$strict>>;
22
+ }, z.core.$strict>;
23
+ export type StopAllResult = z.infer<typeof stopAllResultSchema>;
24
+ export declare const stopAllRequestSchema: z.ZodObject<{
25
+ invocationRoot: z.ZodString;
26
+ agentId: z.ZodString;
27
+ }, z.core.$strict>;
@@ -0,0 +1,26 @@
1
+ import { z } from "zod";
2
+ /** The service reports each attempted Agent, including partial failures. */
3
+ export const stopAllResultSchema = z.strictObject({
4
+ rootAgentId: z.string(),
5
+ agents: z.array(z.discriminatedUnion("outcome", [
6
+ z.strictObject({
7
+ agentId: z.string(),
8
+ name: z.string(),
9
+ outcome: z.enum(["stopped", "idle"]),
10
+ }),
11
+ z.strictObject({
12
+ agentId: z.string(),
13
+ name: z.string(),
14
+ outcome: z.literal("failed"),
15
+ reason: z.string(),
16
+ }),
17
+ ])),
18
+ discoveryFailures: z.array(z.strictObject({
19
+ agentId: z.string(),
20
+ reason: z.string(),
21
+ })),
22
+ });
23
+ export const stopAllRequestSchema = z.strictObject({
24
+ invocationRoot: z.string().min(1),
25
+ agentId: z.string().min(1),
26
+ });
@@ -0,0 +1,19 @@
1
+ import type { Json } from "@fungi.computer/shiitake/extensions";
2
+ import { z } from "zod";
3
+ export declare const localUiAttachmentLifetimeSchema: z.ZodEnum<{
4
+ watch: "watch";
5
+ connection: "connection";
6
+ }>;
7
+ export type LocalUiAttachmentLifetime = z.infer<typeof localUiAttachmentLifetimeSchema>;
8
+ /** Host-held authority; a JSON identity or pending id cannot create one. */
9
+ export type LocalUiAttachment = Readonly<{
10
+ sessionId: string;
11
+ signal: AbortSignal;
12
+ close: () => void;
13
+ }>;
14
+ export declare function createLocalUiAttachment(sessionId: string, lifetime?: LocalUiAttachmentLifetime): LocalUiAttachment;
15
+ export declare function startLocalUiWatch(attachment: LocalUiAttachment, sessionId: string): (() => void) | undefined;
16
+ /** Authority lasts for one command, not model work that might wake later. */
17
+ export declare function runLocalUiCommand(attachment: LocalUiAttachment, sessionId: string, body: () => Promise<Json>): Promise<Json>;
18
+ /** Consume host authority before registering the post-commit continuation. */
19
+ export declare function consumeLocalUiCommand(sessionId: string): ((complete: (committed: boolean) => Promise<Json>) => void) | undefined;