@fungi.computer/truffle 0.4.0 → 0.6.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 (51) hide show
  1. package/README.md +27 -4
  2. package/dist/internal/private-workerd-sandbox-worker.mjs +243 -273
  3. package/dist/internal/sandbox-metafile.json +35 -30
  4. package/dist/local-board-agents.d.ts +46 -0
  5. package/dist/local-board-agents.js +138 -0
  6. package/dist/local-board-shipping.d.ts +129 -0
  7. package/dist/local-board-shipping.js +325 -0
  8. package/dist/local-extensions.d.ts +4 -2
  9. package/dist/local-extensions.js +56 -22
  10. package/dist/local-login.js +33 -25
  11. package/dist/private-fungi-mcp-connection.js +6 -1
  12. package/dist/private-invocation-computer.d.ts +6 -2
  13. package/dist/private-invocation-computer.js +20 -13
  14. package/dist/private-local-agent-control.d.ts +5 -1
  15. package/dist/private-local-agent-control.js +11 -1
  16. package/dist/private-local-auth-rpc.d.ts +5 -0
  17. package/dist/private-local-auth-rpc.js +46 -4
  18. package/dist/private-local-client-version.js +3 -1
  19. package/dist/private-local-computer-processes.d.ts +7 -2
  20. package/dist/private-local-computer-processes.js +47 -23
  21. package/dist/private-local-delegation.d.ts +18 -0
  22. package/dist/private-local-delegation.js +84 -26
  23. package/dist/private-local-exec-environment.d.ts +5 -2
  24. package/dist/private-local-exec-environment.js +35 -1
  25. package/dist/private-local-exec-review.js +14 -7
  26. package/dist/private-local-exec-rules.d.ts +7 -0
  27. package/dist/private-local-exec-rules.js +23 -6
  28. package/dist/private-local-extension-records.d.ts +1 -1
  29. package/dist/private-local-runtime-host.js +54 -27
  30. package/dist/private-local-sandbox-policy.js +4 -0
  31. package/dist/private-local-sandbox.d.ts +3 -1
  32. package/dist/private-local-sandbox.js +2 -2
  33. package/dist/private-local-schedule-client.d.ts +12 -0
  34. package/dist/private-local-schedule-client.js +41 -0
  35. package/dist/private-local-schedules.d.ts +84 -0
  36. package/dist/private-local-schedules.js +65 -0
  37. package/dist/private-local-service-contract.d.ts +4 -0
  38. package/dist/private-local-service-discovery.d.ts +3 -3
  39. package/dist/private-local-service-discovery.js +1 -1
  40. package/dist/private-local-service-listener.js +9 -0
  41. package/dist/private-local-service-owner.d.ts +130 -0
  42. package/dist/private-local-service-owner.js +90 -2
  43. package/dist/private-local-service.d.ts +3 -0
  44. package/dist/private-local-service.js +9 -1
  45. package/dist/private-local-session-discovery.d.ts +10 -4
  46. package/dist/private-local-session-discovery.js +37 -4
  47. package/dist/private-provider-rejection.d.ts +1 -1
  48. package/dist/private-provider-rejection.js +28 -8
  49. package/dist/private-sqlite-owner.d.ts +7 -1
  50. package/dist/private-sqlite-owner.js +14 -2
  51. package/package.json +29 -16
@@ -1,3 +1,4 @@
1
+ import { statSync } from "node:fs";
1
2
  import { readdir, realpath, stat } from "node:fs/promises";
2
3
  import { basename, dirname, join, resolve } from "node:path";
3
4
  import * as Effect from "effect/Effect";
@@ -151,12 +152,44 @@ export class LocalStoreSchemaError extends Error {
151
152
  this.databasePath = databasePath;
152
153
  }
153
154
  }
155
+ /** Profiles held at once; directory listings touch every store each call. */
156
+ const cachedProfiles = 1_024;
157
+ const profiles = new Map();
158
+ /** Every committed write moves the store file (or its WAL); none rewrites it in place unseen. */
159
+ function storeVersion(path) {
160
+ const version = (file) => {
161
+ const metadata = statSync(file, { bigint: true, throwIfNoEntry: false });
162
+ return metadata === undefined
163
+ ? "-"
164
+ : `${String(metadata.dev)}:${String(metadata.ino)}:${String(metadata.size)}:${String(metadata.mtimeNs)}:${String(metadata.ctimeNs)}`;
165
+ };
166
+ return `${version(path)}/${version(`${path}-wal`)}`;
167
+ }
154
168
  /**
155
169
  * One Agent store's chosen display name, default model, whether it is
156
170
  * archived and the Agent that created it (absent when a human made it), read
157
- * without opening a host.
171
+ * without opening a host. Directory listings and the sub-agents footer read
172
+ * every store's profile on each call; an unchanged store file answers from
173
+ * memory instead of reopening SQLite (each Bun open/close also leaks native
174
+ * memory).
158
175
  */
159
176
  export function readPrivateLocalAgentProfile(path) {
177
+ const version = storeVersion(path);
178
+ const cached = profiles.get(path);
179
+ if (cached?.version === version)
180
+ return cached.profile;
181
+ const profile = openAgentProfile(path);
182
+ // The version is taken before the read, so a write racing it misses next time.
183
+ profiles.delete(path);
184
+ profiles.set(path, { version, profile });
185
+ if (profiles.size > cachedProfiles) {
186
+ const oldest = profiles.keys().next().value;
187
+ if (oldest !== undefined)
188
+ profiles.delete(oldest);
189
+ }
190
+ return profile;
191
+ }
192
+ function openAgentProfile(path) {
160
193
  const database = openPrivateReadOnlySqliteDatabase(path);
161
194
  try {
162
195
  const columns = database
@@ -184,15 +217,15 @@ export function readPrivateLocalAgentProfile(path) {
184
217
  folder: identity?.folder,
185
218
  model: readAgentDefaultModelJson(database),
186
219
  });
187
- return {
220
+ return Object.freeze({
188
221
  name: stored.name ?? undefined,
189
222
  createdBy: stored.createdBy,
190
223
  archived: stored.archived === 1,
191
224
  mode: stored.mode,
192
- writableFolders: stored.writableFolders,
225
+ writableFolders: Object.freeze(stored.writableFolders),
193
226
  folder: stored.folder,
194
227
  model: parseModelPreferenceJson(stored.model),
195
- };
228
+ });
196
229
  }
197
230
  finally {
198
231
  database.close();
@@ -1,5 +1,5 @@
1
1
  import type { AssistantMessage } from "@earendil-works/pi-ai";
2
- import type { ProviderRejection } from "@fungi.computer/mule";
2
+ import { type ProviderRejection } from "@fungi.computer/mule";
3
3
  /** Evidence owned by one Local request, never inferred from assistant prose. */
4
4
  export type ModelFailureInput = Readonly<{
5
5
  refusal?: Readonly<{
@@ -1,24 +1,44 @@
1
1
  import { isContextOverflow } from "@earendil-works/pi-ai/utils/overflow";
2
+ import { isRetryableProviderRejection, } from "@fungi.computer/mule";
2
3
  /** Local retries only a typed transient, never cancellation or a turn refusal. */
3
4
  export function modelFailureRetryable(failure) {
4
5
  return (failure.refusal === undefined &&
5
6
  failure.upstream?.stopReason !== "aborted" &&
6
- failure.rejection?.code === "provider_unavailable");
7
+ isRetryableProviderRejection(failure.rejection));
7
8
  }
8
9
  /** The one owned wording for a failed Local model request. */
9
10
  export function modelFailureMessage(provider, failure) {
11
+ const name = provider === "anthropic"
12
+ ? "Anthropic"
13
+ : provider === "openai"
14
+ ? "OpenAI"
15
+ : provider;
10
16
  if (failure.refusal !== undefined)
11
17
  return `${failure.refusal.category}: ${failure.refusal.message}`;
12
18
  // Typed subscription evidence wins over all SDK/provider wording.
13
- if (failure.rejection?.code === "usage-limit-reached")
14
- return `${provider} usage limit reached (usage-limit-reached). Check ChatGPT Settings > Usage, then try again later.`;
15
- if (modelFailureRetryable(failure))
16
- return `${provider} service unavailable. Try again shortly.`;
19
+ if (failure.rejection?.code === "usage-limit-reached") {
20
+ const subscription = provider === "anthropic"
21
+ ? " on your Claude subscription"
22
+ : provider === "openai"
23
+ ? " on your ChatGPT subscription"
24
+ : "";
25
+ const resetAt = failure.rejection.resetAt;
26
+ const reset = resetAt === undefined
27
+ ? ""
28
+ : `; resets at ${new Intl.DateTimeFormat(undefined, { hour: "2-digit", minute: "2-digit", hourCycle: "h23" }).format(resetAt)}`;
29
+ return `${name} usage limit reached${subscription}${reset}.`;
30
+ }
31
+ if (failure.rejection?.code === "rate-limit")
32
+ return `${name} rate limit reached; try again shortly.`;
33
+ if (failure.rejection?.code === "overloaded")
34
+ return `${name} is overloaded; try again shortly.`;
35
+ if (failure.rejection?.code === "provider_unavailable")
36
+ return `${name} is unavailable; try again shortly.`;
17
37
  if (failure.upstream !== undefined) {
18
38
  if (isContextOverflow(failure.upstream))
19
- return `${provider} request exceeds the context window. Compact the Session or choose another model.`;
39
+ return `${name} request exceeds the context window; compact the Session or choose another model.`;
20
40
  }
21
41
  if (failure.rejection?.code === "provider_rejected")
22
- return `${provider} rejected the model request (${failure.rejection.code}, HTTP ${String(failure.rejection.status)}). Check /login or choose another model with /model.`;
23
- return `${provider} model request failed without a provider rejection. Retry the request.`;
42
+ return `${name} rejected the request (HTTP ${String(failure.rejection.status)}); check your sign-in or choose another model.`;
43
+ return `${name} model request failed; try again.`;
24
44
  }
@@ -18,13 +18,19 @@ export type PrivateBunSqliteDatabase = {
18
18
  };
19
19
  /** The actual Node connection or Local's explicit native Bun adapter. */
20
20
  export type PrivateSqliteDatabase = DatabaseSync | PrivateBunSqliteDatabase;
21
+ /**
22
+ * How long a store's writer waits out a reader's shared lock before its
23
+ * commit fails. Status reads and the Board read a live store; a reader holds
24
+ * its lock for one statement, so this bounds a rare wait, never a hang.
25
+ */
26
+ export declare const storeWriteBusyTimeoutMs = 2000;
21
27
  /**
22
28
  * A listing owns this database until its existing finally close. Bun's
23
29
  * node:sqlite prepare/close leaves native statements alive until GC. Native
24
30
  * close(true) on the native Bun driver explicitly finalizes every statement
25
31
  * before releasing the connection, including when a read or decoding fails.
26
32
  */
27
- export declare function openPrivateReadOnlySqliteDatabase(path: string): PrivateSqliteDatabase;
33
+ export declare function openPrivateReadOnlySqliteDatabase(path: string, timeout?: number): PrivateSqliteDatabase;
28
34
  /** Writable connections own their statements through deterministic close. */
29
35
  export declare function openPrivateWritableSqliteDatabase(path: string, timeout: number): PrivateSqliteDatabase;
30
36
  export type PrivateSqliteFailureCode = "busy" | "locked" | "corrupt" | "not_a_database" | "other";
@@ -7,14 +7,26 @@ const decodeSqliteRow = Schema.decodeUnknownSync(sqliteRowSchema);
7
7
  const bunSqlite = Object.hasOwn(process.versions, "bun")
8
8
  ? await import("bun:sqlite")
9
9
  : undefined;
10
+ /**
11
+ * How long a reader waits out a live runtime's commit. Stores use a rollback
12
+ * journal, so a commit briefly locks readers out; a reader that gives up at
13
+ * once would read a busy store as an unreadable one.
14
+ */
15
+ const readBusyTimeoutMs = 250;
16
+ /**
17
+ * How long a store's writer waits out a reader's shared lock before its
18
+ * commit fails. Status reads and the Board read a live store; a reader holds
19
+ * its lock for one statement, so this bounds a rare wait, never a hang.
20
+ */
21
+ export const storeWriteBusyTimeoutMs = 2_000;
10
22
  /**
11
23
  * A listing owns this database until its existing finally close. Bun's
12
24
  * node:sqlite prepare/close leaves native statements alive until GC. Native
13
25
  * close(true) on the native Bun driver explicitly finalizes every statement
14
26
  * before releasing the connection, including when a read or decoding fails.
15
27
  */
16
- export function openPrivateReadOnlySqliteDatabase(path) {
17
- return openPrivateSqliteDatabase(path, true, 0);
28
+ export function openPrivateReadOnlySqliteDatabase(path, timeout = readBusyTimeoutMs) {
29
+ return openPrivateSqliteDatabase(path, true, timeout);
18
30
  }
19
31
  /** Writable connections own their statements through deterministic close. */
20
32
  export function openPrivateWritableSqliteDatabase(path, timeout) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fungi.computer/truffle",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "private": false,
5
5
  "description": "Truffle: Fungi's local Agent runtime, bundled into the fungi command",
6
6
  "type": "module",
@@ -57,6 +57,14 @@
57
57
  "./rules": {
58
58
  "types": "./dist/private-local-exec-rules.d.ts",
59
59
  "import": "./dist/private-local-exec-rules.js"
60
+ },
61
+ "./board-agents": {
62
+ "types": "./dist/local-board-agents.d.ts",
63
+ "import": "./dist/local-board-agents.js"
64
+ },
65
+ "./board-shipping": {
66
+ "types": "./dist/local-board-shipping.d.ts",
67
+ "import": "./dist/local-board-shipping.js"
60
68
  }
61
69
  },
62
70
  "files": [
@@ -69,26 +77,26 @@
69
77
  "@cf-wasm/photon": "0.4.0",
70
78
  "@earendil-works/pi-ai": "1.3.0",
71
79
  "@effect/platform-node": "4.0.2",
72
- "@fungi.computer/agar": "0.4.0",
80
+ "@fungi.computer/agar": "0.6.0",
73
81
  "@fungi.computer/approvals": "0.2.0",
74
- "@fungi.computer/bird-dog": "0.2.1",
82
+ "@fungi.computer/bird-dog": "0.3.1",
75
83
  "@fungi.computer/cairn": "0.1.0",
76
84
  "@fungi.computer/capnweb": "0.12.0",
77
85
  "@fungi.computer/carrier": "0.2.0",
78
86
  "@fungi.computer/cordyceps": "0.4.0",
79
87
  "@fungi.computer/gills": "0.2.0",
80
- "@fungi.computer/graft": "0.1.3",
81
- "@fungi.computer/knapsack": "0.3.1",
82
- "@fungi.computer/lantern": "0.3.0",
83
- "@fungi.computer/mycelium": "0.2.2",
84
- "@fungi.computer/nacre": "0.0.3",
88
+ "@fungi.computer/graft": "0.1.5",
89
+ "@fungi.computer/knapsack": "0.4.1",
90
+ "@fungi.computer/lantern": "0.3.1",
91
+ "@fungi.computer/mycelium": "0.3.0",
92
+ "@fungi.computer/nacre": "0.1.1",
85
93
  "@fungi.computer/seam": "0.2.0",
86
- "@fungi.computer/shiitake": "0.5.0",
87
- "@fungi.computer/shiitake-extensions": "0.3.0",
94
+ "@fungi.computer/shiitake": "0.7.0",
95
+ "@fungi.computer/shiitake-extensions": "0.5.0",
88
96
  "@fungi.computer/veil": "0.2.1",
89
97
  "@fungi.computer/watchdog": "0.1.0",
90
- "@fungi.computer/whistle": "0.2.0",
91
- "@fungi.computer/zookeeper": "0.0.4",
98
+ "@fungi.computer/whistle": "0.3.0",
99
+ "@fungi.computer/zookeeper": "0.0.6",
92
100
  "@jellybrick/dbus-next": "0.11.4",
93
101
  "@modelcontextprotocol/client": "2.0.0",
94
102
  "@napi-rs/keyring": "2.1.2",
@@ -99,6 +107,7 @@
99
107
  "image-size": "2.0.4",
100
108
  "jose": "6.2.11",
101
109
  "miniflare": "5.20260828.0-alpha",
110
+ "npm-package-arg": "14.0.0",
102
111
  "proper-lockfile": "4.1.2",
103
112
  "re2js": "1.3.3",
104
113
  "semver": "7.8.5",
@@ -109,13 +118,14 @@
109
118
  },
110
119
  "devDependencies": {
111
120
  "@cloudflare/codemode": "0.5.1",
112
- "@fungi.computer/mule": "0.2.0",
121
+ "@fungi.computer/mule": "0.2.1",
113
122
  "@modelcontextprotocol/server": "2.0.0",
114
123
  "@types/node": "24.13.3",
124
+ "@types/npm-package-arg": "6.1.4",
115
125
  "@types/proper-lockfile": "4.1.4",
116
126
  "@types/semver": "7.8.0",
117
127
  "@types/write-file-atomic": "4.0.3",
118
- "bun": "1.4.2",
128
+ "bun": "1.4.4",
119
129
  "bun-types": "1.4.1",
120
130
  "esbuild": "0.28.2",
121
131
  "typescript": "5.9.3",
@@ -156,12 +166,15 @@
156
166
  "build": "if [ ! -f src/private-local-native-digests.js ]; then pnpm run build:native; fi && node scripts/generate-native-digests.mjs --verify && pnpm run clean && tsc -p tsconfig.build.json && cp src/private-local-native-digests.js dist/private-local-native-digests.js && node scripts/copy-seatbelt.mjs && esbuild src/internal/private-workerd-sandbox-worker.mjs --outfile=dist/internal/private-workerd-sandbox-worker.mjs --bundle --format=esm --platform=neutral --conditions=workerd,browser --target=es2024 --external:cloudflare:workers --metafile=dist/internal/sandbox-metafile.json --log-level=warning && esbuild src/internal/local-image-fit-worker.mjs --outfile=dist/internal/local-image-fit-worker.mjs --bundle --format=esm --platform=node --target=es2024 --metafile=dist/internal/image-fit-metafile.json --log-level=warning",
157
167
  "clean": "node --input-type=module --eval \"import { rm } from 'node:fs/promises'; await rm('dist', { recursive: true, force: true });\"",
158
168
  "test": "pnpm run test:node && pnpm run test:merge && pnpm run test:stop-all",
169
+ "soak:memory": "bun scripts/service-memory-soak.mjs",
170
+ "soak:sqlite-open-close": "bun scripts/sqlite-open-close-repro.mjs",
159
171
  "test:stop-all": "bun test ./test/private-local-stop-all.bun.test.ts",
160
172
  "test:full": "pnpm run test && pnpm run test:bun",
161
173
  "test:node": "../../node_modules/.bin/vitest run",
174
+ "test:sandbox:native": "../../node_modules/.bin/vitest run --config vitest.native.config.ts",
162
175
  "test:bun": "pnpm run test:mesh-link && pnpm run test:bun:batch && bun test ./test/private-workerd-deadline.bun.test.ts",
163
176
  "test:mesh-link": "bun test ./test/private-local-mesh-links.bun.test.ts",
164
- "test:bun:batch": "bun test ./test/private-sqlite-approval-driver.bun.test.ts ./test/mcp-form-service.bun.test.ts ./test/private-local-service-discovery.bun.test.ts ./test/private-local-service-listener.bun.test.ts ./test/private-local-service-manager.bun.test.ts ./test/private-local-service-rebind.bun.test.ts ./test/private-local-service-stop.bun.test.ts ./test/private-local-service-ensure.bun.test.ts ./test/private-local-service-identity.bun.test.ts ./test/private-local-service-kill-resume.bun.test.ts ./test/private-local-service-idle-owners.bun.test.ts ./test/private-local-service-idle-delegation.bun.test.ts ./test/private-local-mail-retry.bun.test.ts ./test/private-local-service-log-native.bun.test.ts ./test/private-local-service-log.bun.test.ts ./test/private-local-sqlite-lifetime.bun.test.ts ./test/private-sqlite-driver.bun.test.ts ./test/private-local-reload.bun.test.ts ./test/private-local-auth-rpc.bun.test.ts ./test/private-workerd-forage.bun.test.ts ./test/local-extensions.bun.test.ts ./test/local-extension-archive.bun.test.ts ./test/local-zookeeper-slot.bun.test.ts ./test/local-extension-skills.bun.test.ts ./test/private-local-sandbox-service.bun.test.ts ./test/private-local-native-service.bun.test.ts",
177
+ "test:bun:batch": "bun test ./test/private-sqlite-approval-driver.bun.test.ts ./test/mcp-form-service.bun.test.ts ./test/private-local-service-discovery.bun.test.ts ./test/private-local-service-listener.bun.test.ts ./test/private-local-schedules.bun.test.ts ./test/private-local-service-manager.bun.test.ts ./test/private-local-service-rebind.bun.test.ts ./test/private-local-service-stop.bun.test.ts ./test/private-local-service-ensure.bun.test.ts ./test/private-local-service-identity.bun.test.ts ./test/private-local-service-kill-resume.bun.test.ts ./test/private-local-service-idle-owners.bun.test.ts ./test/private-local-service-idle-delegation.bun.test.ts ./test/private-local-mail-retry.bun.test.ts ./test/private-local-service-log-native.bun.test.ts ./test/private-local-service-log.bun.test.ts ./test/private-local-sqlite-lifetime.bun.test.ts ./test/private-sqlite-driver.bun.test.ts ./test/private-local-reload.bun.test.ts ./test/private-local-auth-rpc.bun.test.ts ./test/private-workerd-forage.bun.test.ts ./test/local-extensions.bun.test.ts ./test/local-extension-archive.bun.test.ts ./test/local-zookeeper-slot.bun.test.ts ./test/local-extension-skills.bun.test.ts ./test/private-local-sandbox-service.bun.test.ts ./test/private-local-native-service.bun.test.ts",
165
178
  "build:native": "fungi-apply-patch-build --out native/bin && node scripts/build-bwrap.mjs --out native/bin && node scripts/generate-native-digests.mjs",
166
179
  "test:patch": "node --test --test-timeout=30000 test/apply-patch.native.mjs",
167
180
  "test:sandbox:linux": "node --import tsx test/fixtures/local-sandbox-proof.mts",
@@ -169,7 +182,7 @@
169
182
  "format:check": "node ../../tooling/repository/scripts/format-package-check.mjs",
170
183
  "lint:anti-slop": "node ../../tooling/repository/scripts/lint-package-anti-slop.mjs",
171
184
  "test:unit": "node ../../tooling/repository/scripts/test-file-pool.mjs pnpm run test:node && pnpm run test:mesh-link && pnpm run test:bun:batch && pnpm run test:stop-all",
172
- "test:merge": "pnpm run test:patch && case \"$(uname -s)\" in Darwin) pnpm run test:sandbox:macos ;; *) pnpm run test:sandbox:linux ;; esac",
185
+ "test:merge": "pnpm run test:sandbox:native && pnpm run test:patch && case \"$(uname -s)\" in Darwin) pnpm run test:sandbox:macos ;; *) pnpm run test:sandbox:linux ;; esac",
173
186
  "proof:goal-loop-sol": "bun scripts/goal-loop-model-proof.mjs",
174
187
  "proof:unpatched-login": "node scripts/prove-unpatched-login.mjs",
175
188
  "proof:sqlite-close": "bun test ./test/private-local-sqlite-close.bun.test.ts"