@oh-my-pi/pi-coding-agent 15.7.2 → 15.7.5

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 (188) hide show
  1. package/CHANGELOG.md +114 -6
  2. package/dist/types/cli/args.d.ts +1 -1
  3. package/dist/types/cli/extension-flags.d.ts +36 -0
  4. package/dist/types/config/config-file.d.ts +4 -0
  5. package/dist/types/config/file-lock.d.ts +23 -0
  6. package/dist/types/config/keybindings.d.ts +2 -1
  7. package/dist/types/config/model-registry.d.ts +6 -0
  8. package/dist/types/config/settings-schema.d.ts +69 -65
  9. package/dist/types/edit/hashline/diff.d.ts +3 -3
  10. package/dist/types/eval/__tests__/agent-bridge.test.d.ts +1 -0
  11. package/dist/types/eval/__tests__/budget-bridge.test.d.ts +1 -0
  12. package/dist/types/eval/__tests__/idle-timeout.test.d.ts +1 -0
  13. package/dist/types/eval/agent-bridge.d.ts +25 -0
  14. package/dist/types/eval/backend.d.ts +17 -2
  15. package/dist/types/eval/budget-bridge.d.ts +29 -0
  16. package/dist/types/eval/idle-timeout.d.ts +28 -0
  17. package/dist/types/eval/js/executor.d.ts +8 -0
  18. package/dist/types/eval/js/tool-bridge.d.ts +2 -1
  19. package/dist/types/eval/py/executor.d.ts +13 -0
  20. package/dist/types/exec/bash-executor.d.ts +1 -0
  21. package/dist/types/extensibility/custom-tools/types.d.ts +2 -2
  22. package/dist/types/extensibility/extensions/runner.d.ts +7 -0
  23. package/dist/types/extensibility/plugins/git-url.d.ts +11 -1
  24. package/dist/types/extensibility/plugins/manager.d.ts +12 -1
  25. package/dist/types/extensibility/shared-events.d.ts +2 -2
  26. package/dist/types/internal-urls/local-protocol.d.ts +19 -9
  27. package/dist/types/internal-urls/types.d.ts +14 -0
  28. package/dist/types/lsp/client.d.ts +3 -0
  29. package/dist/types/mcp/manager.d.ts +14 -5
  30. package/dist/types/memory-backend/index.d.ts +1 -1
  31. package/dist/types/memory-backend/resolve.d.ts +1 -1
  32. package/dist/types/memory-backend/types.d.ts +3 -3
  33. package/dist/types/mnemopi/backend.d.ts +4 -0
  34. package/dist/types/mnemopi/config.d.ts +29 -0
  35. package/dist/types/mnemopi/state.d.ts +72 -0
  36. package/dist/types/modes/components/custom-editor.d.ts +2 -2
  37. package/dist/types/modes/components/omfg-panel.d.ts +19 -0
  38. package/dist/types/modes/controllers/command-controller.d.ts +6 -0
  39. package/dist/types/modes/controllers/input-controller.d.ts +1 -3
  40. package/dist/types/modes/controllers/omfg-controller.d.ts +10 -0
  41. package/dist/types/modes/controllers/omfg-rule.d.ts +26 -0
  42. package/dist/types/modes/gradient-highlight.d.ts +5 -1
  43. package/dist/types/modes/interactive-mode.d.ts +7 -3
  44. package/dist/types/modes/magic-keywords.d.ts +14 -0
  45. package/dist/types/modes/markdown-prose.d.ts +27 -0
  46. package/dist/types/modes/orchestrate.d.ts +7 -2
  47. package/dist/types/modes/shared.d.ts +1 -1
  48. package/dist/types/modes/turn-budget.d.ts +18 -0
  49. package/dist/types/modes/types.d.ts +7 -3
  50. package/dist/types/modes/ultrathink.d.ts +7 -2
  51. package/dist/types/modes/workflow.d.ts +15 -0
  52. package/dist/types/sdk.d.ts +13 -3
  53. package/dist/types/session/agent-session.d.ts +36 -17
  54. package/dist/types/session/session-manager.d.ts +18 -0
  55. package/dist/types/session/session-storage.d.ts +6 -0
  56. package/dist/types/session/shake-types.d.ts +24 -0
  57. package/dist/types/task/executor.d.ts +2 -2
  58. package/dist/types/task/repair-args.d.ts +52 -0
  59. package/dist/types/tiny/models.d.ts +1 -1
  60. package/dist/types/tiny/title-client.d.ts +28 -2
  61. package/dist/types/tiny/title-protocol.d.ts +8 -5
  62. package/dist/types/tools/find.d.ts +1 -1
  63. package/dist/types/tools/index.d.ts +19 -3
  64. package/dist/types/tools/memory-edit.d.ts +1 -1
  65. package/dist/types/tools/path-utils.d.ts +7 -0
  66. package/dist/types/tui/output-block.d.ts +7 -7
  67. package/examples/sdk/09-api-keys-and-oauth.ts +2 -2
  68. package/package.json +10 -10
  69. package/scripts/build-binary.ts +0 -1
  70. package/src/autoresearch/tools/run-experiment.ts +45 -113
  71. package/src/cli/args.ts +39 -16
  72. package/src/cli/extension-flags.ts +48 -0
  73. package/src/cli/plugin-cli.ts +11 -2
  74. package/src/cli.ts +59 -0
  75. package/src/config/config-file.ts +98 -13
  76. package/src/config/file-lock.ts +60 -17
  77. package/src/config/keybindings.ts +78 -27
  78. package/src/config/model-registry.ts +7 -1
  79. package/src/config/settings-schema.ts +73 -67
  80. package/src/config/settings.ts +22 -0
  81. package/src/edit/hashline/diff.ts +81 -24
  82. package/src/edit/renderer.ts +16 -12
  83. package/src/eval/__tests__/agent-bridge.test.ts +433 -0
  84. package/src/eval/__tests__/budget-bridge.test.ts +69 -0
  85. package/src/eval/__tests__/idle-timeout.test.ts +66 -0
  86. package/src/eval/__tests__/shared-executors.test.ts +21 -0
  87. package/src/eval/agent-bridge.ts +295 -0
  88. package/src/eval/backend.ts +17 -2
  89. package/src/eval/budget-bridge.ts +48 -0
  90. package/src/eval/idle-timeout.ts +80 -0
  91. package/src/eval/js/executor.ts +35 -7
  92. package/src/eval/js/index.ts +2 -1
  93. package/src/eval/js/shared/prelude.txt +85 -1
  94. package/src/eval/js/tool-bridge.ts +9 -0
  95. package/src/eval/py/executor.ts +41 -14
  96. package/src/eval/py/index.ts +2 -1
  97. package/src/eval/py/prelude.py +132 -1
  98. package/src/exec/bash-executor.ts +2 -3
  99. package/src/extensibility/custom-tools/types.ts +2 -2
  100. package/src/extensibility/extensions/runner.ts +12 -2
  101. package/src/extensibility/plugins/git-url.ts +90 -4
  102. package/src/extensibility/plugins/manager.ts +103 -7
  103. package/src/extensibility/shared-events.ts +2 -2
  104. package/src/internal-urls/docs-index.generated.ts +88 -88
  105. package/src/internal-urls/local-protocol.ts +23 -11
  106. package/src/internal-urls/types.ts +15 -0
  107. package/src/lsp/client.ts +28 -5
  108. package/src/main.ts +44 -55
  109. package/src/mcp/manager.ts +87 -4
  110. package/src/memory-backend/index.ts +1 -1
  111. package/src/memory-backend/resolve.ts +3 -3
  112. package/src/memory-backend/types.ts +3 -3
  113. package/src/{mnemosyne → mnemopi}/backend.ts +70 -70
  114. package/src/{mnemosyne → mnemopi}/config.ts +36 -36
  115. package/src/{mnemosyne → mnemopi}/state.ts +67 -67
  116. package/src/modes/components/agent-dashboard.ts +6 -6
  117. package/src/modes/components/custom-editor.ts +4 -11
  118. package/src/modes/components/extensions/state-manager.ts +3 -4
  119. package/src/modes/components/footer.ts +8 -9
  120. package/src/modes/components/hook-selector.ts +86 -20
  121. package/src/modes/components/oauth-selector.ts +93 -21
  122. package/src/modes/components/omfg-panel.ts +141 -0
  123. package/src/modes/components/settings-defs.ts +2 -2
  124. package/src/modes/components/tips.txt +2 -1
  125. package/src/modes/components/tool-execution.ts +38 -19
  126. package/src/modes/components/tree-selector.ts +4 -3
  127. package/src/modes/components/user-message-selector.ts +94 -19
  128. package/src/modes/components/user-message.ts +8 -1
  129. package/src/modes/controllers/command-controller.ts +25 -0
  130. package/src/modes/controllers/event-controller.ts +68 -3
  131. package/src/modes/controllers/input-controller.ts +14 -11
  132. package/src/modes/controllers/mcp-command-controller.ts +1 -1
  133. package/src/modes/controllers/omfg-controller.ts +283 -0
  134. package/src/modes/controllers/omfg-rule.ts +647 -0
  135. package/src/modes/controllers/selector-controller.ts +1 -0
  136. package/src/modes/gradient-highlight.ts +23 -6
  137. package/src/modes/interactive-mode.ts +41 -7
  138. package/src/modes/magic-keywords.ts +20 -0
  139. package/src/modes/markdown-prose.ts +247 -0
  140. package/src/modes/orchestrate.ts +17 -11
  141. package/src/modes/shared.ts +3 -11
  142. package/src/modes/turn-budget.ts +31 -0
  143. package/src/modes/types.ts +7 -1
  144. package/src/modes/ultrathink.ts +16 -10
  145. package/src/modes/utils/hotkeys-markdown.ts +1 -1
  146. package/src/modes/workflow.ts +42 -0
  147. package/src/prompts/system/omfg-user.md +51 -0
  148. package/src/prompts/system/project-prompt.md +3 -2
  149. package/src/prompts/system/subagent-system-prompt.md +12 -8
  150. package/src/prompts/system/system-prompt.md +9 -6
  151. package/src/prompts/system/workflow-notice.md +70 -0
  152. package/src/prompts/tools/eval.md +13 -1
  153. package/src/prompts/tools/memory-edit.md +1 -1
  154. package/src/sdk.ts +63 -33
  155. package/src/session/agent-session.ts +290 -55
  156. package/src/session/session-manager.ts +32 -0
  157. package/src/session/session-storage.ts +68 -8
  158. package/src/session/shake-types.ts +43 -0
  159. package/src/slash-commands/builtin-registry.ts +39 -16
  160. package/src/task/executor.ts +17 -7
  161. package/src/task/index.ts +9 -8
  162. package/src/task/repair-args.ts +117 -0
  163. package/src/tiny/models.ts +2 -2
  164. package/src/tiny/title-client.ts +133 -43
  165. package/src/tiny/title-protocol.ts +10 -5
  166. package/src/tiny/worker.ts +3 -46
  167. package/src/tools/ast-edit.ts +3 -0
  168. package/src/tools/ast-grep.ts +3 -0
  169. package/src/tools/eval.ts +202 -26
  170. package/src/tools/find.ts +20 -6
  171. package/src/tools/gh.ts +1 -0
  172. package/src/tools/grouped-file-output.ts +9 -2
  173. package/src/tools/index.ts +17 -5
  174. package/src/tools/memory-edit.ts +4 -4
  175. package/src/tools/memory-recall.ts +5 -5
  176. package/src/tools/memory-reflect.ts +5 -5
  177. package/src/tools/memory-retain.ts +4 -4
  178. package/src/tools/path-utils.ts +13 -2
  179. package/src/tools/read.ts +1 -0
  180. package/src/tools/render-utils.ts +2 -1
  181. package/src/tools/search.ts +491 -76
  182. package/src/tui/output-block.ts +37 -75
  183. package/src/utils/git.ts +9 -3
  184. package/dist/types/mnemosyne/backend.d.ts +0 -4
  185. package/dist/types/mnemosyne/config.d.ts +0 -29
  186. package/dist/types/mnemosyne/state.d.ts +0 -72
  187. /package/dist/types/{mnemosyne → mnemopi}/index.d.ts +0 -0
  188. /package/src/{mnemosyne → mnemopi}/index.ts +0 -0
@@ -1,4 +1,6 @@
1
+ import * as path from "node:path";
1
2
  import { $env, isCompiledBinary, logger } from "@oh-my-pi/pi-utils";
3
+ import type { Subprocess } from "bun";
2
4
  import { settings } from "../config/settings";
3
5
  import { tinyModelDeviceSettingToEnv } from "./device";
4
6
  import { tinyModelDtypeSettingToEnv } from "./dtype";
@@ -12,6 +14,14 @@ import {
12
14
  } from "./models";
13
15
  import type { TinyTitleProgressEvent, TinyTitleWorkerInbound, TinyTitleWorkerOutbound } from "./title-protocol";
14
16
 
17
+ /**
18
+ * Abstraction over the tiny-model subprocess. Modelled as a worker interface
19
+ * so existing callers (titles, memory completions, downloads) compose the
20
+ * same way; the runtime implementation is a Bun child process so
21
+ * `onnxruntime-node`'s NAPI finalizer never runs inside the main agent
22
+ * address space — that destructor segfaults Bun on Windows during shutdown
23
+ * (issue #1606).
24
+ */
15
25
  interface WorkerHandle {
16
26
  send(message: TinyTitleWorkerInbound): void;
17
27
  onMessage(handler: (message: TinyTitleWorkerOutbound) => void): () => void;
@@ -31,6 +41,12 @@ export interface TinyTitleDownloadOptions {
31
41
 
32
42
  const SMOKE_TEST_TIMEOUT_MS = 5_000;
33
43
 
44
+ /**
45
+ * Hidden subcommand on the main CLI that boots the tiny-model worker in the
46
+ * spawned subprocess. Kept in sync with the dispatch in `cli.ts`.
47
+ */
48
+ export const TINY_WORKER_ARG = "--tiny-worker";
49
+
34
50
  function readTinyModelSetting(path: "providers.tinyModelDevice" | "providers.tinyModelDtype"): string | undefined {
35
51
  try {
36
52
  const value = settings.get(path);
@@ -66,49 +82,128 @@ export function tinyWorkerEnvOverlay(
66
82
  }
67
83
 
68
84
  /**
69
- * Env handed to the tiny-model worker. The `PI_TINY_DEVICE` / `PI_TINY_DTYPE` env
70
- * vars win; otherwise the persisted `providers.tinyModelDevice` /
71
- * `providers.tinyModelDtype` settings are mapped onto those vars so the worker's
72
- * env-based resolution picks them up. Resolved once at spawn (pipelines are cached).
85
+ * Env handed to the tiny-model subprocess. The `PI_TINY_DEVICE` / `PI_TINY_DTYPE`
86
+ * env vars win; otherwise the persisted `providers.tinyModelDevice` /
87
+ * `providers.tinyModelDtype` settings are mapped onto those vars so the
88
+ * subprocess's env-based resolution picks them up. Resolved once at spawn
89
+ * (pipelines are cached for the lifetime of the subprocess).
73
90
  */
74
- function tinyWorkerEnv(): Record<string, string> | undefined {
91
+ function tinyWorkerEnv(): Record<string, string> {
75
92
  const overlay = tinyWorkerEnvOverlay(
76
93
  $env,
77
94
  readTinyModelSetting("providers.tinyModelDevice"),
78
95
  readTinyModelSetting("providers.tinyModelDtype"),
79
96
  );
80
- if (Object.keys(overlay).length === 0) return undefined;
81
- return { ...($env as Record<string, string>), ...overlay };
97
+ const base = $env as Record<string, string | undefined>;
98
+ const merged: Record<string, string> = {};
99
+ for (const key in base) {
100
+ const value = base[key];
101
+ if (typeof value === "string") merged[key] = value;
102
+ }
103
+ for (const key in overlay) merged[key] = overlay[key];
104
+ return merged;
82
105
  }
83
106
 
84
- export function createTinyTitleWorker(): Worker {
85
- const env = tinyWorkerEnv();
86
- const options: WorkerOptions = env ? { type: "module", env } : { type: "module" };
87
- return isCompiledBinary()
88
- ? new Worker("./packages/coding-agent/src/tiny/worker.ts", options)
89
- : new Worker(new URL("./worker.ts", import.meta.url).href, options);
107
+ /**
108
+ * Resolve the argv used to relaunch the agent CLI into tiny-worker mode. In a
109
+ * compiled binary the entry point is the binary itself; in dev/source the
110
+ * spawned `bun` needs the absolute path to `cli.ts` so it can resolve module
111
+ * imports against the on-disk source tree.
112
+ */
113
+ function tinyWorkerSpawnCmd(): string[] {
114
+ if (isCompiledBinary()) return [process.execPath, TINY_WORKER_ARG];
115
+ const cliPath = path.resolve(import.meta.dir, "..", "cli.ts");
116
+ return [process.execPath, cliPath, TINY_WORKER_ARG];
117
+ }
118
+
119
+ interface SpawnedSubprocess {
120
+ proc: Subprocess<"ignore", "inherit", "inherit">;
121
+ inbound: Set<(message: TinyTitleWorkerOutbound) => void>;
122
+ errors: Set<(error: Error) => void>;
123
+ /**
124
+ * Flipped to `true` by {@link wrapSubprocess}'s `terminate()` right
125
+ * before it SIGKILLs the child so `onExit` can distinguish the
126
+ * expected hard-kill from a crash/OOM/external signal. Only the
127
+ * latter is surfaced as a worker error.
128
+ */
129
+ intentionalExit: { value: boolean };
90
130
  }
91
131
 
92
- function wrapBunWorker(worker: Worker): WorkerHandle {
93
- (worker as Worker & { unref?: () => void }).unref?.();
132
+ /**
133
+ * Spawn the tiny-model worker as a subprocess. Exported for tests and the
134
+ * smoke probe; production callers go through {@link spawnTinyTitleWorker}
135
+ * which wraps the result in a {@link WorkerHandle}.
136
+ */
137
+ export function createTinyTitleSubprocess(): SpawnedSubprocess {
138
+ const inbound = new Set<(message: TinyTitleWorkerOutbound) => void>();
139
+ const errors = new Set<(error: Error) => void>();
140
+ const intentionalExit = { value: false };
141
+ const proc = Bun.spawn({
142
+ cmd: tinyWorkerSpawnCmd(),
143
+ env: tinyWorkerEnv(),
144
+ stdin: "ignore",
145
+ stdout: "inherit",
146
+ stderr: "inherit",
147
+ serialization: "advanced",
148
+ windowsHide: true,
149
+ ipc(message) {
150
+ for (const handler of inbound) handler(message as TinyTitleWorkerOutbound);
151
+ },
152
+ onExit(_proc, exitCode, signalCode) {
153
+ // Clean exit. The child only exits via SIGKILL in practice, but
154
+ // treat code 0 as a no-op for symmetry.
155
+ if (exitCode === 0) return;
156
+ // `exitCode === null` + non-null `signalCode` covers both the
157
+ // expected SIGKILL from `terminate()` AND external kills
158
+ // (SIGSEGV from a native crash, SIGKILL from the OOM killer, an
159
+ // operator `kill -9`, etc.). Swallow only the expected one;
160
+ // every other signal exit is a real worker death that must
161
+ // fault every in-flight request so callers don't await forever.
162
+ if (exitCode === null && intentionalExit.value) return;
163
+ const reason = exitCode !== null ? `code ${exitCode}` : `signal ${signalCode ?? "unknown"}`;
164
+ const err = new Error(`tiny model subprocess exited with ${reason}`);
165
+ for (const handler of errors) handler(err);
166
+ },
167
+ });
168
+ // Don't keep the parent event loop alive on account of an idle worker; the
169
+ // agent dispose path calls `terminate()` explicitly when shutting down.
170
+ proc.unref();
171
+ return { proc, inbound, errors, intentionalExit };
172
+ }
173
+
174
+ function wrapSubprocess({ proc, inbound, errors, intentionalExit }: SpawnedSubprocess): WorkerHandle {
94
175
  return {
95
176
  send(message) {
96
- worker.postMessage(message);
177
+ try {
178
+ proc.send(message);
179
+ } catch (error) {
180
+ logger.debug("tiny-title: send to subprocess failed", {
181
+ error: error instanceof Error ? error.message : String(error),
182
+ });
183
+ }
97
184
  },
98
185
  onMessage(handler) {
99
- const wrap = (event: MessageEvent): void => handler(event.data as TinyTitleWorkerOutbound);
100
- worker.addEventListener("message", wrap);
101
- return () => worker.removeEventListener("message", wrap);
186
+ inbound.add(handler);
187
+ return () => inbound.delete(handler);
102
188
  },
103
189
  onError(handler) {
104
- const wrap = (event: ErrorEvent): void => {
105
- handler(event.error instanceof Error ? event.error : new Error(event.message || "tiny title worker error"));
106
- };
107
- worker.addEventListener("error", wrap);
108
- return () => worker.removeEventListener("error", wrap);
190
+ errors.add(handler);
191
+ return () => errors.delete(handler);
109
192
  },
110
193
  async terminate() {
111
- worker.terminate();
194
+ // SIGKILL: the whole point of the subprocess isolation is that the
195
+ // parent never runs `onnxruntime-node`'s NAPI finalizer. A polite
196
+ // SIGTERM lets the subprocess try to clean up, which is exactly the
197
+ // codepath that crashes Bun on Windows. Hard-kill instead — the
198
+ // model lives in process memory and the OS reclaims everything.
199
+ // Flip the intentional-exit flag *before* killing so `onExit` can
200
+ // tell this apart from a crash or external SIGKILL.
201
+ intentionalExit.value = true;
202
+ try {
203
+ proc.kill("SIGKILL");
204
+ } catch {
205
+ // Already gone.
206
+ }
112
207
  },
113
208
  };
114
209
  }
@@ -126,10 +221,6 @@ function spawnInlineUnavailableWorker(error: unknown): WorkerHandle {
126
221
  emit({ type: "pong", id: message.id });
127
222
  return;
128
223
  }
129
- if (message.type === "close") {
130
- emit({ type: "closed" });
131
- return;
132
- }
133
224
  emit({ type: "error", id: message.id, error: errorMessage });
134
225
  });
135
226
  },
@@ -148,9 +239,9 @@ function spawnInlineUnavailableWorker(error: unknown): WorkerHandle {
148
239
 
149
240
  function spawnTinyTitleWorker(): WorkerHandle {
150
241
  try {
151
- return wrapBunWorker(createTinyTitleWorker());
242
+ return wrapSubprocess(createTinyTitleSubprocess());
152
243
  } catch (error) {
153
- logger.warn("Tiny title Worker spawn failed; local titles disabled", {
244
+ logger.warn("Tiny title worker spawn failed; local titles disabled", {
154
245
  error: error instanceof Error ? error.message : String(error),
155
246
  });
156
247
  return spawnInlineUnavailableWorker(error);
@@ -293,9 +384,9 @@ export class TinyTitleClient {
293
384
  }
294
385
  this.#pending.clear();
295
386
  try {
296
- worker?.send({ type: "close" });
387
+ await worker?.terminate();
297
388
  } catch {
298
- // Worker may already be gone.
389
+ // Already gone.
299
390
  }
300
391
  }
301
392
 
@@ -317,7 +408,6 @@ export class TinyTitleClient {
317
408
  this.#emitProgress(message.event);
318
409
  return;
319
410
  }
320
- if (message.type === "closed") return;
321
411
  if (message.type === "pong") return;
322
412
 
323
413
  const pending = this.#pending.get(message.id);
@@ -371,25 +461,25 @@ export async function smokeTestTinyTitleWorker({
371
461
  }: {
372
462
  timeoutMs?: number;
373
463
  } = {}): Promise<void> {
374
- const worker = createTinyTitleWorker();
464
+ const handle = wrapSubprocess(createTinyTitleSubprocess());
375
465
  const { promise, resolve, reject } = Promise.withResolvers<void>();
376
466
  const timer = setTimeout(() => reject(new Error(`tiny title worker did not pong within ${timeoutMs}ms`)), timeoutMs);
377
- worker.onmessage = (event: MessageEvent<TinyTitleWorkerOutbound>) => {
378
- const message = event.data;
467
+ const unsubscribeMessage = handle.onMessage(message => {
379
468
  if (message.type === "pong") {
380
469
  resolve();
381
470
  return;
382
471
  }
472
+ if (message.type === "log") return;
383
473
  reject(new Error(`tiny title worker: expected pong, got ${JSON.stringify(message)}`));
384
- };
385
- worker.onerror = (event: ErrorEvent) => {
386
- reject(event.error instanceof Error ? event.error : new Error(event.message || "tiny title worker error"));
387
- };
474
+ });
475
+ const unsubscribeError = handle.onError(reject);
388
476
  try {
389
- worker.postMessage({ type: "ping", id: "smoke" } satisfies TinyTitleWorkerInbound);
477
+ handle.send({ type: "ping", id: "smoke" } satisfies TinyTitleWorkerInbound);
390
478
  await promise;
391
479
  } finally {
392
480
  clearTimeout(timer);
393
- worker.terminate();
481
+ unsubscribeMessage();
482
+ unsubscribeError();
483
+ await handle.terminate();
394
484
  }
395
485
  }
@@ -31,8 +31,7 @@ export type TinyTitleWorkerInbound =
31
31
  | { type: "ping"; id: string }
32
32
  | { type: "generate"; id: string; modelKey: TinyTitleLocalModelKey; message: string }
33
33
  | { type: "complete"; id: string; modelKey: TinyLocalModelKey; prompt: string; maxTokens?: number }
34
- | { type: "download"; id: string; modelKey: TinyLocalModelKey }
35
- | { type: "close" };
34
+ | { type: "download"; id: string; modelKey: TinyLocalModelKey };
36
35
 
37
36
  export type TinyTitleWorkerOutbound =
38
37
  | { type: "pong"; id: string }
@@ -41,11 +40,17 @@ export type TinyTitleWorkerOutbound =
41
40
  | { type: "downloaded"; id: string }
42
41
  | { type: "error"; id: string; error: string }
43
42
  | { type: "progress"; id: string; event: TinyTitleProgressEvent }
44
- | { type: "log"; level: "debug" | "warn" | "error"; msg: string; meta?: Record<string, unknown> }
45
- | { type: "closed" };
43
+ | { type: "log"; level: "debug" | "warn" | "error"; msg: string; meta?: Record<string, unknown> };
46
44
 
45
+ /**
46
+ * Wire transport between the parent (`TinyTitleClient`) and the tiny-model
47
+ * subprocess. The parent owns the subprocess lifecycle (graceful work, hard
48
+ * kill on shutdown); the protocol therefore carries no explicit close
49
+ * handshake — once the parent decides to terminate, it signals the OS to
50
+ * reap the child so `onnxruntime-node`'s NAPI finalizer never runs in any
51
+ * shared address space. See `title-client.ts` for the spawn/kill glue.
52
+ */
47
53
  export interface TinyTitleTransport {
48
54
  send(message: TinyTitleWorkerOutbound): void;
49
55
  onMessage(handler: (message: TinyTitleWorkerInbound) => void): () => void;
50
- close(): void;
51
56
  }
@@ -1,7 +1,6 @@
1
1
  import * as fs from "node:fs/promises";
2
2
  import { createRequire } from "node:module";
3
3
  import * as path from "node:path";
4
- import { parentPort } from "node:worker_threads";
5
4
  import type {
6
5
  ProgressInfo,
7
6
  TextGenerationPipeline,
@@ -20,12 +19,7 @@ import {
20
19
  type TinyTitleLocalModelSpec,
21
20
  } from "./models";
22
21
  import { formatTitleUserMessage, normalizeGeneratedTitle } from "./text";
23
- import type {
24
- TinyTitleProgressEvent,
25
- TinyTitleTransport,
26
- TinyTitleWorkerInbound,
27
- TinyTitleWorkerOutbound,
28
- } from "./title-protocol";
22
+ import type { TinyTitleProgressEvent, TinyTitleTransport, TinyTitleWorkerInbound } from "./title-protocol";
29
23
 
30
24
  const TITLE_PREFILL = "<title>";
31
25
  const TITLE_CLOSE = "</title>";
@@ -472,8 +466,8 @@ function buildCompletionPrompt(generator: TextGenerationPipeline, promptText: st
472
466
  }
473
467
 
474
468
  /**
475
- * Generic single-turn completion used by Mnemosyne memory tasks (fact extraction
476
- * and consolidation). The caller (Mnemosyne) supplies the full task prompt; we
469
+ * Generic single-turn completion used by Mnemopi memory tasks (fact extraction
470
+ * and consolidation). The caller (Mnemopi) supplies the full task prompt; we
477
471
  * wrap it as the user turn, decode greedily, and return the raw text for the
478
472
  * caller's own parser. Output is capped to keep local inference latency bounded.
479
473
  */
@@ -497,16 +491,6 @@ async function generateCompletion(
497
491
  return generated === "" ? null : generated;
498
492
  }
499
493
 
500
- function releasePipelines(): void {
501
- // Intentionally NOT calling `pipeline.dispose()`. transformers.js disposes the
502
- // underlying onnxruntime InferenceSession, freeing native memory that Bun's
503
- // worker/NAPI teardown then frees a second time — a double-free that aborts the
504
- // process on quit ("malloc: pointer being freed was not allocated" /
505
- // "NAPI FATAL ERROR"). The worker is torn down immediately after `close`, so the
506
- // OS reclaims the model memory regardless; skipping dispose avoids the crash.
507
- pipelines.clear();
508
- }
509
-
510
494
  function enqueueRequest(
511
495
  transport: TinyTitleTransport,
512
496
  request: Extract<TinyTitleWorkerInbound, { type: "generate" | "complete" | "download" }>,
@@ -555,33 +539,6 @@ export function startTinyTitleWorker(transport: TinyTitleTransport): void {
555
539
  transport.send({ type: "pong", id: message.id });
556
540
  return;
557
541
  }
558
- if (message.type === "close") {
559
- releasePipelines();
560
- transport.send({ type: "closed" });
561
- transport.close();
562
- return;
563
- }
564
542
  enqueueRequest(transport, message);
565
543
  });
566
544
  }
567
-
568
- if (!parentPort) throw new Error("tiny-title-worker: missing parentPort");
569
-
570
- const port = parentPort;
571
- const transport: TinyTitleTransport = {
572
- send: (message: TinyTitleWorkerOutbound) => port.postMessage(message),
573
- onMessage: handler => {
574
- const wrap = (data: unknown): void => handler(data as TinyTitleWorkerInbound);
575
- port.on("message", wrap);
576
- return () => port.off("message", wrap);
577
- },
578
- close: () => {
579
- try {
580
- port.close();
581
- } catch {
582
- // Already closed.
583
- }
584
- },
585
- };
586
-
587
- startTinyTitleWorker(transport);
@@ -230,6 +230,9 @@ export class AstEditTool implements AgentTool<typeof astEditSchema, AstEditToolD
230
230
  rawPaths: params.paths,
231
231
  cwd: this.session.cwd,
232
232
  internalUrlAction: "rewrite",
233
+ settings: this.session.settings,
234
+ signal,
235
+ localProtocolOptions: this.session.localProtocolOptions,
233
236
  });
234
237
  const { searchPath: resolvedSearchPath, scopePath, isDirectory, multiTargets, globFilter } = scope;
235
238
 
@@ -155,6 +155,9 @@ export class AstGrepTool implements AgentTool<typeof astGrepSchema, AstGrepToolD
155
155
  rawPaths: params.paths,
156
156
  cwd: this.session.cwd,
157
157
  internalUrlAction: "search",
158
+ settings: this.session.settings,
159
+ signal,
160
+ localProtocolOptions: this.session.localProtocolOptions,
158
161
  });
159
162
  const { searchPath: resolvedSearchPath, scopePath, isDirectory, multiTargets, globFilter } = scope;
160
163