@oh-my-pi/pi-coding-agent 15.5.15 → 15.7.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 (274) hide show
  1. package/CHANGELOG.md +81 -0
  2. package/dist/types/capability/rule-buckets.d.ts +30 -0
  3. package/dist/types/capability/rule.d.ts +7 -0
  4. package/dist/types/cli/classify-install-target.d.ts +0 -10
  5. package/dist/types/cli/completion-gen.d.ts +80 -0
  6. package/dist/types/cli/initial-message.d.ts +1 -1
  7. package/dist/types/cli/tiny-models-cli.d.ts +9 -0
  8. package/dist/types/commands/complete.d.ts +6 -0
  9. package/dist/types/commands/completions.d.ts +13 -0
  10. package/dist/types/commands/setup.d.ts +10 -1
  11. package/dist/types/commands/tiny-models.d.ts +22 -0
  12. package/dist/types/commit/analysis/conventional.d.ts +1 -1
  13. package/dist/types/commit/analysis/summary.d.ts +1 -1
  14. package/dist/types/commit/changelog/generate.d.ts +1 -1
  15. package/dist/types/commit/changelog/index.d.ts +2 -2
  16. package/dist/types/commit/map-reduce/map-phase.d.ts +1 -1
  17. package/dist/types/commit/map-reduce/reduce-phase.d.ts +1 -1
  18. package/dist/types/config/model-id-affixes.d.ts +10 -0
  19. package/dist/types/config/settings-schema.d.ts +402 -17
  20. package/dist/types/discovery/builtin-defaults.d.ts +1 -0
  21. package/dist/types/discovery/builtin-rules/index.d.ts +7 -0
  22. package/dist/types/discovery/helpers.d.ts +1 -1
  23. package/dist/types/discovery/index.d.ts +1 -0
  24. package/dist/types/discovery/substitute-plugin-root.d.ts +0 -4
  25. package/dist/types/edit/hashline/block-resolver.d.ts +9 -0
  26. package/dist/types/edit/hashline/index.d.ts +1 -0
  27. package/dist/types/eval/js/shared/rewrite-imports.d.ts +16 -1
  28. package/dist/types/eval/py/kernel.d.ts +3 -0
  29. package/dist/types/eval/py/runtime.d.ts +11 -1
  30. package/dist/types/export/html/template.generated.d.ts +1 -1
  31. package/dist/types/internal-urls/agent-protocol.d.ts +2 -1
  32. package/dist/types/internal-urls/artifact-protocol.d.ts +2 -1
  33. package/dist/types/internal-urls/local-protocol.d.ts +2 -1
  34. package/dist/types/internal-urls/memory-protocol.d.ts +2 -1
  35. package/dist/types/internal-urls/omp-protocol.d.ts +2 -1
  36. package/dist/types/internal-urls/router.d.ts +8 -1
  37. package/dist/types/internal-urls/rule-protocol.d.ts +2 -1
  38. package/dist/types/internal-urls/skill-protocol.d.ts +2 -1
  39. package/dist/types/internal-urls/types.d.ts +26 -0
  40. package/dist/types/main.d.ts +1 -0
  41. package/dist/types/memory-backend/index.d.ts +1 -0
  42. package/dist/types/memory-backend/resolve.d.ts +2 -1
  43. package/dist/types/memory-backend/types.d.ts +7 -1
  44. package/dist/types/mnemosyne/backend.d.ts +4 -0
  45. package/dist/types/mnemosyne/config.d.ts +29 -0
  46. package/dist/types/mnemosyne/index.d.ts +3 -0
  47. package/dist/types/mnemosyne/state.d.ts +72 -0
  48. package/dist/types/modes/components/custom-editor.d.ts +2 -3
  49. package/dist/types/modes/components/hook-selector.d.ts +27 -0
  50. package/dist/types/modes/components/index.d.ts +2 -0
  51. package/dist/types/modes/components/segment-track.d.ts +22 -0
  52. package/dist/types/modes/components/status-line/context-thresholds.d.ts +6 -0
  53. package/dist/types/modes/components/tiny-title-download-progress.d.ts +11 -0
  54. package/dist/types/modes/components/welcome.d.ts +22 -0
  55. package/dist/types/modes/controllers/extension-ui-controller.d.ts +4 -1
  56. package/dist/types/modes/gradient-highlight.d.ts +23 -0
  57. package/dist/types/modes/interactive-mode.d.ts +7 -4
  58. package/dist/types/modes/internal-url-autocomplete.d.ts +43 -0
  59. package/dist/types/modes/orchestrate.d.ts +10 -0
  60. package/dist/types/modes/setup-wizard/index.d.ts +16 -0
  61. package/dist/types/modes/setup-wizard/scenes/glyph.d.ts +2 -0
  62. package/dist/types/modes/setup-wizard/scenes/outro.d.ts +2 -0
  63. package/dist/types/modes/setup-wizard/scenes/providers.d.ts +2 -0
  64. package/dist/types/modes/setup-wizard/scenes/sign-in.d.ts +19 -0
  65. package/dist/types/modes/setup-wizard/scenes/splash.d.ts +11 -0
  66. package/dist/types/modes/setup-wizard/scenes/theme.d.ts +2 -0
  67. package/dist/types/modes/setup-wizard/scenes/types.d.ts +43 -0
  68. package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +19 -0
  69. package/dist/types/modes/setup-wizard/wizard-overlay.d.ts +14 -0
  70. package/dist/types/modes/theme/defaults/index.d.ts +8406 -8406
  71. package/dist/types/modes/theme/shimmer.d.ts +2 -0
  72. package/dist/types/modes/theme/theme.d.ts +11 -0
  73. package/dist/types/modes/types.d.ts +5 -1
  74. package/dist/types/modes/ultrathink.d.ts +3 -3
  75. package/dist/types/modes/utils/keybinding-matchers.d.ts +5 -0
  76. package/dist/types/sdk.d.ts +3 -0
  77. package/dist/types/session/agent-session.d.ts +33 -0
  78. package/dist/types/system-prompt.d.ts +2 -0
  79. package/dist/types/task/executor.d.ts +2 -0
  80. package/dist/types/task/render.d.ts +5 -1
  81. package/dist/types/tiny/device.d.ts +78 -0
  82. package/dist/types/tiny/dtype.d.ts +85 -0
  83. package/dist/types/tiny/models.d.ts +185 -0
  84. package/dist/types/tiny/text.d.ts +19 -0
  85. package/dist/types/tiny/title-client.d.ts +32 -0
  86. package/dist/types/tiny/title-protocol.d.ts +74 -0
  87. package/dist/types/tiny/worker.d.ts +2 -0
  88. package/dist/types/tools/bash.d.ts +3 -2
  89. package/dist/types/tools/eval.d.ts +1 -1
  90. package/dist/types/tools/index.d.ts +7 -4
  91. package/dist/types/tools/memory-edit.d.ts +40 -0
  92. package/dist/types/tools/{hindsight-recall.d.ts → memory-recall.d.ts} +6 -6
  93. package/dist/types/tools/{hindsight-reflect.d.ts → memory-reflect.d.ts} +6 -6
  94. package/dist/types/tools/memory-render.d.ts +60 -0
  95. package/dist/types/tools/{hindsight-retain.d.ts → memory-retain.d.ts} +6 -6
  96. package/dist/types/tools/todo-write.d.ts +8 -0
  97. package/dist/types/tools/tool-result.d.ts +2 -0
  98. package/dist/types/tui/code-cell.d.ts +2 -0
  99. package/dist/types/tui/output-block.d.ts +17 -0
  100. package/dist/types/utils/title-generator.d.ts +3 -0
  101. package/package.json +18 -14
  102. package/scripts/build-binary.ts +1 -0
  103. package/src/capability/rule-buckets.ts +64 -0
  104. package/src/capability/rule.ts +8 -0
  105. package/src/cli/completion-gen.ts +550 -0
  106. package/src/cli/setup-cli.ts +5 -3
  107. package/src/cli/tiny-models-cli.ts +127 -0
  108. package/src/cli-commands.ts +3 -0
  109. package/src/cli.ts +9 -15
  110. package/src/commands/complete.ts +66 -0
  111. package/src/commands/completions.ts +60 -0
  112. package/src/commands/setup.ts +29 -4
  113. package/src/commands/tiny-models.ts +36 -0
  114. package/src/config/model-equivalence.ts +43 -2
  115. package/src/config/model-id-affixes.ts +64 -0
  116. package/src/config/model-registry.ts +84 -10
  117. package/src/config/settings-schema.ts +275 -15
  118. package/src/discovery/builtin-defaults.ts +39 -0
  119. package/src/discovery/builtin-rules/index.ts +48 -0
  120. package/src/discovery/builtin-rules/rs-box-leak.md +48 -0
  121. package/src/discovery/builtin-rules/rs-future-prelude.md +23 -0
  122. package/src/discovery/builtin-rules/rs-lazylock.md +51 -0
  123. package/src/discovery/builtin-rules/rs-match-ergonomics.md +67 -0
  124. package/src/discovery/builtin-rules/rs-parking-lot.md +44 -0
  125. package/src/discovery/builtin-rules/rs-result-type.md +19 -0
  126. package/src/discovery/builtin-rules/ts-bare-catch.md +38 -0
  127. package/src/discovery/builtin-rules/ts-import-type.md +42 -0
  128. package/src/discovery/builtin-rules/ts-no-any.md +56 -0
  129. package/src/discovery/builtin-rules/ts-no-dynamic-import.md +39 -0
  130. package/src/discovery/builtin-rules/ts-no-return-type.md +45 -0
  131. package/src/discovery/builtin-rules/ts-no-tiny-functions.md +50 -0
  132. package/src/discovery/builtin-rules/ts-promise-with-resolvers.md +65 -0
  133. package/src/discovery/builtin-rules/ts-set-map.md +28 -0
  134. package/src/discovery/index.ts +1 -0
  135. package/src/edit/hashline/block-resolver.ts +14 -0
  136. package/src/edit/hashline/diff.ts +9 -8
  137. package/src/edit/hashline/execute.ts +2 -1
  138. package/src/edit/hashline/index.ts +1 -0
  139. package/src/eval/__tests__/shared-executors.test.ts +36 -0
  140. package/src/eval/js/shared/local-module-loader.ts +13 -1
  141. package/src/eval/js/shared/rewrite-imports.ts +31 -26
  142. package/src/eval/py/kernel.ts +37 -15
  143. package/src/eval/py/runtime.ts +57 -28
  144. package/src/export/html/template.generated.ts +1 -1
  145. package/src/export/html/template.js +0 -12
  146. package/src/export/ttsr.ts +2 -0
  147. package/src/internal-urls/agent-protocol.ts +18 -1
  148. package/src/internal-urls/artifact-protocol.ts +19 -1
  149. package/src/internal-urls/docs-index.generated.ts +8 -7
  150. package/src/internal-urls/local-protocol.ts +14 -1
  151. package/src/internal-urls/memory-protocol.ts +6 -1
  152. package/src/internal-urls/omp-protocol.ts +5 -1
  153. package/src/internal-urls/router.ts +20 -1
  154. package/src/internal-urls/rule-protocol.ts +8 -1
  155. package/src/internal-urls/skill-protocol.ts +8 -1
  156. package/src/internal-urls/types.ts +27 -0
  157. package/src/lsp/render.ts +1 -1
  158. package/src/main.ts +18 -1
  159. package/src/mcp/oauth-flow.ts +2 -2
  160. package/src/memory-backend/index.ts +1 -0
  161. package/src/memory-backend/resolve.ts +4 -1
  162. package/src/memory-backend/types.ts +8 -1
  163. package/src/mnemosyne/backend.ts +374 -0
  164. package/src/mnemosyne/config.ts +160 -0
  165. package/src/mnemosyne/index.ts +3 -0
  166. package/src/mnemosyne/state.ts +548 -0
  167. package/src/modes/acp/acp-agent.ts +11 -6
  168. package/src/modes/components/agent-dashboard.ts +4 -4
  169. package/src/modes/components/custom-editor.ts +3 -2
  170. package/src/modes/components/diff.ts +2 -2
  171. package/src/modes/components/extensions/extension-list.ts +3 -2
  172. package/src/modes/components/footer.ts +5 -6
  173. package/src/modes/components/history-search.ts +3 -3
  174. package/src/modes/components/hook-selector.ts +92 -8
  175. package/src/modes/components/index.ts +2 -0
  176. package/src/modes/components/mcp-add-wizard.ts +3 -3
  177. package/src/modes/components/model-selector.ts +5 -4
  178. package/src/modes/components/oauth-selector.ts +3 -3
  179. package/src/modes/components/segment-track.ts +52 -0
  180. package/src/modes/components/session-observer-overlay.ts +19 -13
  181. package/src/modes/components/session-selector.ts +3 -3
  182. package/src/modes/components/settings-defs.ts +7 -0
  183. package/src/modes/components/status-line/context-thresholds.ts +11 -0
  184. package/src/modes/components/status-line/segments.ts +2 -2
  185. package/src/modes/components/tiny-title-download-progress.ts +90 -0
  186. package/src/modes/components/tips.txt +13 -0
  187. package/src/modes/components/tool-execution.ts +72 -4
  188. package/src/modes/components/tree-selector.ts +3 -3
  189. package/src/modes/components/user-message-selector.ts +3 -3
  190. package/src/modes/components/welcome.ts +102 -43
  191. package/src/modes/controllers/command-controller.ts +16 -1
  192. package/src/modes/controllers/extension-ui-controller.ts +3 -1
  193. package/src/modes/controllers/input-controller.ts +69 -21
  194. package/src/modes/gradient-highlight.ts +70 -0
  195. package/src/modes/interactive-mode.ts +75 -114
  196. package/src/modes/internal-url-autocomplete.ts +143 -0
  197. package/src/modes/orchestrate.ts +36 -0
  198. package/src/modes/prompt-action-autocomplete.ts +12 -0
  199. package/src/modes/setup-wizard/index.ts +88 -0
  200. package/src/modes/setup-wizard/scenes/glyph.ts +96 -0
  201. package/src/modes/setup-wizard/scenes/outro.ts +35 -0
  202. package/src/modes/setup-wizard/scenes/providers.ts +69 -0
  203. package/src/modes/setup-wizard/scenes/sign-in.ts +193 -0
  204. package/src/modes/setup-wizard/scenes/splash.ts +201 -0
  205. package/src/modes/setup-wizard/scenes/theme.ts +299 -0
  206. package/src/modes/setup-wizard/scenes/types.ts +48 -0
  207. package/src/modes/setup-wizard/scenes/web-search.ts +128 -0
  208. package/src/modes/setup-wizard/wizard-overlay.ts +275 -0
  209. package/src/modes/theme/shimmer.ts +5 -0
  210. package/src/modes/theme/theme.ts +44 -20
  211. package/src/modes/types.ts +6 -1
  212. package/src/modes/ultrathink.ts +9 -53
  213. package/src/modes/utils/keybinding-matchers.ts +11 -0
  214. package/src/prompts/system/memory-consolidation-system.md +8 -0
  215. package/src/prompts/system/memory-extraction-system.md +26 -0
  216. package/src/prompts/{commands/orchestrate.md → system/orchestrate-notice.md} +6 -17
  217. package/src/prompts/system/system-prompt.md +2 -0
  218. package/src/prompts/system/tiny-title-system.md +8 -0
  219. package/src/prompts/tools/memory-edit.md +8 -0
  220. package/src/prompts/tools/read.md +4 -0
  221. package/src/prompts/tools/task.md +4 -7
  222. package/src/sdk.ts +13 -21
  223. package/src/session/agent-session.ts +128 -44
  224. package/src/slash-commands/builtin-registry.ts +18 -1
  225. package/src/system-prompt.ts +4 -0
  226. package/src/task/commands.ts +1 -5
  227. package/src/task/executor.ts +8 -0
  228. package/src/task/index.ts +2 -0
  229. package/src/task/render.ts +69 -26
  230. package/src/tiny/device.ts +117 -0
  231. package/src/tiny/dtype.ts +101 -0
  232. package/src/tiny/models.ts +218 -0
  233. package/src/tiny/text.ts +54 -0
  234. package/src/tiny/title-client.ts +395 -0
  235. package/src/tiny/title-protocol.ts +51 -0
  236. package/src/tiny/worker.ts +587 -0
  237. package/src/tools/bash.ts +74 -29
  238. package/src/tools/browser/tab-worker.ts +1 -1
  239. package/src/tools/eval.ts +9 -4
  240. package/src/tools/index.ts +17 -22
  241. package/src/tools/memory-edit.ts +59 -0
  242. package/src/tools/memory-recall.ts +100 -0
  243. package/src/tools/memory-reflect.ts +88 -0
  244. package/src/tools/memory-render.ts +185 -0
  245. package/src/tools/memory-retain.ts +91 -0
  246. package/src/tools/read.ts +1 -0
  247. package/src/tools/renderers.ts +4 -2
  248. package/src/tools/todo-write.ts +128 -29
  249. package/src/tools/tool-result.ts +8 -0
  250. package/src/tui/code-cell.ts +6 -1
  251. package/src/tui/output-block.ts +199 -38
  252. package/src/utils/title-generator.ts +115 -13
  253. package/dist/types/tools/recipe/index.d.ts +0 -46
  254. package/dist/types/tools/recipe/render.d.ts +0 -36
  255. package/dist/types/tools/recipe/runner.d.ts +0 -60
  256. package/dist/types/tools/recipe/runners/cargo.d.ts +0 -16
  257. package/dist/types/tools/recipe/runners/index.d.ts +0 -2
  258. package/dist/types/tools/recipe/runners/just.d.ts +0 -2
  259. package/dist/types/tools/recipe/runners/make.d.ts +0 -2
  260. package/dist/types/tools/recipe/runners/pkg.d.ts +0 -2
  261. package/dist/types/tools/recipe/runners/task.d.ts +0 -2
  262. package/src/prompts/tools/recipe.md +0 -16
  263. package/src/tools/hindsight-recall.ts +0 -69
  264. package/src/tools/hindsight-reflect.ts +0 -58
  265. package/src/tools/hindsight-retain.ts +0 -57
  266. package/src/tools/recipe/index.ts +0 -81
  267. package/src/tools/recipe/render.ts +0 -19
  268. package/src/tools/recipe/runner.ts +0 -219
  269. package/src/tools/recipe/runners/cargo.ts +0 -131
  270. package/src/tools/recipe/runners/index.ts +0 -8
  271. package/src/tools/recipe/runners/just.ts +0 -73
  272. package/src/tools/recipe/runners/make.ts +0 -101
  273. package/src/tools/recipe/runners/pkg.ts +0 -167
  274. package/src/tools/recipe/runners/task.ts +0 -72
@@ -0,0 +1,587 @@
1
+ import * as fs from "node:fs/promises";
2
+ import { createRequire } from "node:module";
3
+ import * as path from "node:path";
4
+ import { parentPort } from "node:worker_threads";
5
+ import type {
6
+ ProgressInfo,
7
+ TextGenerationPipeline,
8
+ TextGenerationStringOutput,
9
+ StoppingCriteria as TransformersStoppingCriteria,
10
+ } from "@huggingface/transformers";
11
+ import { getTinyModelsCacheDir, isCompiledBinary, prompt } from "@oh-my-pi/pi-utils";
12
+ import packageJson from "../../package.json" with { type: "json" };
13
+ import tinyTitleSystemPrompt from "../prompts/system/tiny-title-system.md" with { type: "text" };
14
+ import { resolveTinyModelDevicePreference, type TinyModelDevice, tinyModelDeviceLoadOrder } from "./device";
15
+ import { resolveTinyModelDtypeOverride, type TinyModelDtype } from "./dtype";
16
+ import {
17
+ getTinyLocalModelSpec,
18
+ type TinyLocalModelKey,
19
+ type TinyTitleLocalModelKey,
20
+ type TinyTitleLocalModelSpec,
21
+ } from "./models";
22
+ import { formatTitleUserMessage, normalizeGeneratedTitle } from "./text";
23
+ import type {
24
+ TinyTitleProgressEvent,
25
+ TinyTitleTransport,
26
+ TinyTitleWorkerInbound,
27
+ TinyTitleWorkerOutbound,
28
+ } from "./title-protocol";
29
+
30
+ const TITLE_PREFILL = "<title>";
31
+ const TITLE_CLOSE = "</title>";
32
+ const TITLE_MAX_NEW_TOKENS = 20;
33
+ const STOP_DECODE_WINDOW_TOKENS = 32;
34
+ const MEMORY_COMPLETION_MAX_NEW_TOKENS = 256;
35
+ const TINY_TITLE_SYSTEM_PROMPT = prompt.render(tinyTitleSystemPrompt);
36
+ const TRANSFORMERS_PACKAGE = "@huggingface/transformers";
37
+ const sourceRequire = createRequire(import.meta.url);
38
+ const INSTALL_LOCK_ATTEMPTS = 240;
39
+ const INSTALL_LOCK_SLEEP_MS = 250;
40
+
41
+ const tinyModelDevicePreference = resolveTinyModelDevicePreference();
42
+ const tinyModelDtypeOverride = resolveTinyModelDtypeOverride();
43
+
44
+ interface TransformersRuntime {
45
+ env: {
46
+ cacheDir?: string;
47
+ allowLocalModels?: boolean;
48
+ logLevel?: unknown;
49
+ };
50
+ LogLevel: {
51
+ ERROR: unknown;
52
+ };
53
+ StoppingCriteria: new () => TransformersStoppingCriteria;
54
+ pipeline: (
55
+ task: "text-generation",
56
+ model: string,
57
+ options: {
58
+ device: TinyModelDevice;
59
+ dtype: TinyModelDtype;
60
+ progress_callback: (info: ProgressInfo) => void;
61
+ },
62
+ ) => Promise<TextGenerationPipeline>;
63
+ }
64
+
65
+ const pipelines = new Map<TinyLocalModelKey, Promise<TextGenerationPipeline>>();
66
+
67
+ function resolveTransformersVersionSpec(): string {
68
+ const manifest = packageJson as {
69
+ optionalDependencies?: Record<string, string>;
70
+ dependencies?: Record<string, string>;
71
+ };
72
+ const versionSpec =
73
+ manifest.optionalDependencies?.[TRANSFORMERS_PACKAGE] ?? manifest.dependencies?.[TRANSFORMERS_PACKAGE];
74
+ if (!versionSpec) throw new Error(`${TRANSFORMERS_PACKAGE} is missing from package.json optionalDependencies`);
75
+ if (!versionSpec.startsWith("catalog:")) return versionSpec;
76
+ const installed = sourceRequire(`${TRANSFORMERS_PACKAGE}/package.json`) as { version: string };
77
+ return installed.version;
78
+ }
79
+ let cachedTransformersVersionSpec: string | undefined;
80
+ /**
81
+ * Lazily resolve (and memoize) the transformers version spec. In the
82
+ * `catalog:` case {@link resolveTransformersVersionSpec} `require`s the
83
+ * installed `@huggingface/transformers/package.json`, so touching it forces
84
+ * the dependency to exist. Defer it to the compiled-binary runtime-install
85
+ * path — which only runs when a local title model is actually generated or
86
+ * downloaded — so loading this worker (smoke-test ping, online title path)
87
+ * never triggers the transformers resolve/install dance.
88
+ */
89
+ function getTransformersVersionSpec(): string {
90
+ cachedTransformersVersionSpec ??= resolveTransformersVersionSpec();
91
+ return cachedTransformersVersionSpec;
92
+ }
93
+ function getTransformersRuntimeKey(): string {
94
+ return getTransformersVersionSpec().replace(/[^A-Za-z0-9._-]/g, "_");
95
+ }
96
+ let generateQueue = Promise.resolve();
97
+ let transformersRuntime: Promise<TransformersRuntime> | null = null;
98
+
99
+ function errorText(error: unknown): string {
100
+ return error instanceof Error ? (error.stack ?? error.message) : String(error);
101
+ }
102
+
103
+ function isErrnoCode(error: unknown, code: string): boolean {
104
+ return typeof error === "object" && error !== null && "code" in error && error.code === code;
105
+ }
106
+
107
+ function sendLog(
108
+ transport: TinyTitleTransport,
109
+ level: "debug" | "warn" | "error",
110
+ msg: string,
111
+ meta?: Record<string, unknown>,
112
+ ): void {
113
+ transport.send({ type: "log", level, msg, meta });
114
+ }
115
+
116
+ function getTinyTitleRuntimeDir(): string {
117
+ return path.join(
118
+ path.dirname(getTinyModelsCacheDir()),
119
+ "tiny-title-runtime",
120
+ `transformers-${getTransformersRuntimeKey()}`,
121
+ );
122
+ }
123
+
124
+ async function acquireInstallLock(runtimeDir: string): Promise<() => Promise<void>> {
125
+ const lockDir = `${runtimeDir}.lock`;
126
+ for (let attempt = 0; attempt < INSTALL_LOCK_ATTEMPTS; attempt++) {
127
+ try {
128
+ await fs.mkdir(lockDir);
129
+ return async () => {
130
+ await fs.rm(lockDir, { recursive: true, force: true });
131
+ };
132
+ } catch (error) {
133
+ if (!isErrnoCode(error, "EEXIST")) throw error;
134
+ await Bun.sleep(INSTALL_LOCK_SLEEP_MS);
135
+ }
136
+ }
137
+ throw new Error(`Timed out waiting for tiny title runtime install lock: ${lockDir}`);
138
+ }
139
+
140
+ async function isCompiledRuntimeInstalled(runtimeDir: string): Promise<boolean> {
141
+ return Bun.file(path.join(runtimeDir, "node_modules", "@huggingface", "transformers", "package.json")).exists();
142
+ }
143
+
144
+ async function writeRuntimeManifest(runtimeDir: string): Promise<void> {
145
+ await fs.mkdir(runtimeDir, { recursive: true });
146
+ await Bun.write(
147
+ path.join(runtimeDir, "package.json"),
148
+ `${JSON.stringify(
149
+ {
150
+ private: true,
151
+ type: "module",
152
+ dependencies: {
153
+ [TRANSFORMERS_PACKAGE]: getTransformersVersionSpec(),
154
+ },
155
+ trustedDependencies: ["onnxruntime-node"],
156
+ },
157
+ null,
158
+ "\t",
159
+ )}\n`,
160
+ );
161
+ }
162
+
163
+ async function readPipe(stream: ReadableStream<Uint8Array> | null): Promise<string> {
164
+ if (!stream) return "";
165
+ return new Response(stream).text();
166
+ }
167
+
168
+ async function runRuntimeInstall(runtimeDir: string): Promise<void> {
169
+ const proc = Bun.spawn([process.execPath, "install", "--cwd", runtimeDir, "--production"], {
170
+ env: { ...Bun.env, BUN_BE_BUN: "1" },
171
+ stdout: "pipe",
172
+ stderr: "pipe",
173
+ });
174
+ const [stdout, stderr, exitCode] = await Promise.all([
175
+ readPipe(proc.stdout as ReadableStream<Uint8Array> | null),
176
+ readPipe(proc.stderr as ReadableStream<Uint8Array> | null),
177
+ proc.exited,
178
+ ]);
179
+ if (exitCode === 0) return;
180
+ const output = `${stdout}\n${stderr}`.trim();
181
+ throw new Error(
182
+ `Failed to install tiny title runtime with ${process.execPath} install (exit ${exitCode}): ${output}`,
183
+ );
184
+ }
185
+
186
+ function sendRuntimeInstallProgress(
187
+ transport: TinyTitleTransport,
188
+ requestId: string,
189
+ modelKey: TinyLocalModelKey,
190
+ status: "initiate" | "download" | "done",
191
+ ): void {
192
+ transport.send({
193
+ type: "progress",
194
+ id: requestId,
195
+ event: {
196
+ modelKey,
197
+ status,
198
+ name: `${TRANSFORMERS_PACKAGE}@${getTransformersVersionSpec()}`,
199
+ },
200
+ });
201
+ }
202
+
203
+ async function ensureCompiledTransformersRuntime(
204
+ transport: TinyTitleTransport,
205
+ requestId: string,
206
+ modelKey: TinyLocalModelKey,
207
+ ): Promise<string> {
208
+ const runtimeDir = getTinyTitleRuntimeDir();
209
+ if (await isCompiledRuntimeInstalled(runtimeDir)) return runtimeDir;
210
+
211
+ sendRuntimeInstallProgress(transport, requestId, modelKey, "initiate");
212
+ const releaseLock = await acquireInstallLock(runtimeDir);
213
+ try {
214
+ if (await isCompiledRuntimeInstalled(runtimeDir)) return runtimeDir;
215
+ await writeRuntimeManifest(runtimeDir);
216
+ sendRuntimeInstallProgress(transport, requestId, modelKey, "download");
217
+ await runRuntimeInstall(runtimeDir);
218
+ sendRuntimeInstallProgress(transport, requestId, modelKey, "done");
219
+ return runtimeDir;
220
+ } finally {
221
+ await releaseLock();
222
+ }
223
+ }
224
+
225
+ function configureTransformers(transformers: TransformersRuntime): TransformersRuntime {
226
+ transformers.env.cacheDir = getTinyModelsCacheDir();
227
+ transformers.env.allowLocalModels = false;
228
+ transformers.env.logLevel = transformers.LogLevel.ERROR;
229
+ return transformers;
230
+ }
231
+
232
+ async function loadTransformers(
233
+ transport: TinyTitleTransport,
234
+ requestId: string,
235
+ modelKey: TinyLocalModelKey,
236
+ ): Promise<TransformersRuntime> {
237
+ if (transformersRuntime) return transformersRuntime;
238
+ transformersRuntime = (async () => {
239
+ if (!isCompiledBinary()) return configureTransformers(sourceRequire(TRANSFORMERS_PACKAGE) as TransformersRuntime);
240
+ const runtimeDir = await ensureCompiledTransformersRuntime(transport, requestId, modelKey);
241
+ const require_ = createRequire(path.join(runtimeDir, "package.json"));
242
+ return configureTransformers(require_(TRANSFORMERS_PACKAGE) as TransformersRuntime);
243
+ })().catch(error => {
244
+ transformersRuntime = null;
245
+ throw error;
246
+ });
247
+ return transformersRuntime;
248
+ }
249
+
250
+ function createStopOnTextCriteria(
251
+ transformers: TransformersRuntime,
252
+ tokenizer: TextGenerationPipeline["tokenizer"],
253
+ text: string,
254
+ ): TransformersStoppingCriteria {
255
+ class StopOnTextCriteria extends transformers.StoppingCriteria {
256
+ #tokenizer: TextGenerationPipeline["tokenizer"];
257
+ #text: string;
258
+
259
+ constructor() {
260
+ super();
261
+ this.#tokenizer = tokenizer;
262
+ this.#text = text;
263
+ }
264
+
265
+ _call(inputIds: number[][]): boolean[] {
266
+ return inputIds.map(ids => {
267
+ const tail = ids.slice(-STOP_DECODE_WINDOW_TOKENS);
268
+ const decoded = this.#tokenizer.decode(tail, {
269
+ skip_special_tokens: false,
270
+ clean_up_tokenization_spaces: false,
271
+ });
272
+ return decoded.includes(this.#text);
273
+ });
274
+ }
275
+ }
276
+ return new StopOnTextCriteria();
277
+ }
278
+
279
+ function toProgressEvent(modelKey: TinyLocalModelKey, info: ProgressInfo): TinyTitleProgressEvent {
280
+ if (info.status === "ready") {
281
+ return { modelKey, status: info.status, task: info.task, model: info.model };
282
+ }
283
+ if (info.status === "progress_total") {
284
+ return {
285
+ modelKey,
286
+ status: info.status,
287
+ name: info.name,
288
+ progress: info.progress,
289
+ loaded: info.loaded,
290
+ total: info.total,
291
+ files: info.files,
292
+ };
293
+ }
294
+ if (info.status === "progress") {
295
+ return {
296
+ modelKey,
297
+ status: info.status,
298
+ name: info.name,
299
+ file: info.file,
300
+ progress: info.progress,
301
+ loaded: info.loaded,
302
+ total: info.total,
303
+ };
304
+ }
305
+ return { modelKey, status: info.status, name: info.name, file: info.file };
306
+ }
307
+
308
+ function sendProgress(
309
+ transport: TinyTitleTransport,
310
+ id: string,
311
+ modelKey: TinyLocalModelKey,
312
+ info: ProgressInfo,
313
+ ): void {
314
+ transport.send({ type: "progress", id, event: toProgressEvent(modelKey, info) });
315
+ }
316
+
317
+ function errorMessage(error: unknown): string {
318
+ return error instanceof Error ? error.message : String(error);
319
+ }
320
+
321
+ async function loadPipelineOnDevice(
322
+ transformers: TransformersRuntime,
323
+ spec: TinyTitleLocalModelSpec,
324
+ modelKey: TinyLocalModelKey,
325
+ transport: TinyTitleTransport,
326
+ requestId: string,
327
+ device: TinyModelDevice,
328
+ ): Promise<TextGenerationPipeline> {
329
+ return transformers.pipeline("text-generation", spec.repo, {
330
+ device,
331
+ dtype: tinyModelDtypeOverride ?? spec.dtype,
332
+ progress_callback: info => sendProgress(transport, requestId, modelKey, info),
333
+ });
334
+ }
335
+
336
+ async function loadPipelineWithDeviceFallback(
337
+ transformers: TransformersRuntime,
338
+ spec: TinyTitleLocalModelSpec,
339
+ modelKey: TinyLocalModelKey,
340
+ transport: TinyTitleTransport,
341
+ requestId: string,
342
+ ): Promise<{ generator: TextGenerationPipeline; device: TinyModelDevice }> {
343
+ const devices = tinyModelDeviceLoadOrder(tinyModelDevicePreference);
344
+ if (devices[0] !== tinyModelDevicePreference.device) {
345
+ sendLog(transport, "warn", "tiny-model: requested device is unsafe in the worker; using CPU", {
346
+ modelKey,
347
+ repo: spec.repo,
348
+ requestedDevice: tinyModelDevicePreference.device,
349
+ device: devices[0],
350
+ });
351
+ }
352
+ for (let i = 0; i < devices.length; i += 1) {
353
+ const device = devices[i]!;
354
+ try {
355
+ return {
356
+ generator: await loadPipelineOnDevice(transformers, spec, modelKey, transport, requestId, device),
357
+ device,
358
+ };
359
+ } catch (error) {
360
+ if (i === devices.length - 1) throw error;
361
+ const fallbackDevice = devices[i + 1]!;
362
+ sendLog(transport, "warn", "tiny-model: accelerated device failed; falling back", {
363
+ modelKey,
364
+ repo: spec.repo,
365
+ device,
366
+ fallbackDevice,
367
+ error: errorMessage(error),
368
+ });
369
+ }
370
+ }
371
+ throw new Error("No tiny model devices configured");
372
+ }
373
+
374
+ async function loadPipeline(
375
+ modelKey: TinyLocalModelKey,
376
+ transport: TinyTitleTransport,
377
+ requestId: string,
378
+ ): Promise<TextGenerationPipeline> {
379
+ const spec = getTinyLocalModelSpec(modelKey);
380
+ if (!spec) throw new Error(`Unknown tiny local model: ${modelKey}`);
381
+ const cached = pipelines.get(modelKey);
382
+ if (cached) {
383
+ void cached
384
+ .then(() => {
385
+ transport.send({
386
+ type: "progress",
387
+ id: requestId,
388
+ event: { modelKey, status: "ready", task: "text-generation", model: spec.repo },
389
+ });
390
+ })
391
+ .catch(() => undefined);
392
+ return cached;
393
+ }
394
+
395
+ const transformers = await loadTransformers(transport, requestId, modelKey);
396
+ const startedAt = performance.now();
397
+ const loaded = loadPipelineWithDeviceFallback(transformers, spec, modelKey, transport, requestId).then(
398
+ ({ generator, device }) => {
399
+ sendLog(transport, "debug", "tiny-model: local model loaded", {
400
+ modelKey,
401
+ repo: spec.repo,
402
+ device,
403
+ requestedDevice: tinyModelDevicePreference.device,
404
+ dtype: tinyModelDtypeOverride ?? spec.dtype,
405
+ elapsedMs: Math.round(performance.now() - startedAt),
406
+ });
407
+ transport.send({
408
+ type: "progress",
409
+ id: requestId,
410
+ event: { modelKey, status: "ready", task: "text-generation", model: spec.repo },
411
+ });
412
+ return generator;
413
+ },
414
+ error => {
415
+ pipelines.delete(modelKey);
416
+ throw error;
417
+ },
418
+ );
419
+ pipelines.set(modelKey, loaded);
420
+ return loaded;
421
+ }
422
+
423
+ function buildPrompt(generator: TextGenerationPipeline, message: string): string {
424
+ const chat = [
425
+ { role: "system", content: TINY_TITLE_SYSTEM_PROMPT },
426
+ { role: "user", content: formatTitleUserMessage(message) },
427
+ ];
428
+ const chatTemplateOptions = {
429
+ add_generation_prompt: true,
430
+ tokenize: false,
431
+ enable_thinking: false,
432
+ };
433
+ return `${generator.tokenizer.apply_chat_template(chat, chatTemplateOptions)}${TITLE_PREFILL}`;
434
+ }
435
+
436
+ function extractTinyTitle(text: string): string | null {
437
+ const titleStart = text.lastIndexOf(TITLE_PREFILL);
438
+ const withoutPrefix = titleStart >= 0 ? text.slice(titleStart + TITLE_PREFILL.length) : text;
439
+ const closeIndex = withoutPrefix.indexOf(TITLE_CLOSE);
440
+ const withoutClose = closeIndex >= 0 ? withoutPrefix.slice(0, closeIndex) : withoutPrefix;
441
+ const tagIndex = withoutClose.indexOf("<");
442
+ const withoutTag = tagIndex >= 0 ? withoutClose.slice(0, tagIndex) : withoutClose;
443
+ return normalizeGeneratedTitle(withoutTag);
444
+ }
445
+
446
+ async function generateTitle(
447
+ transport: TinyTitleTransport,
448
+ requestId: string,
449
+ modelKey: TinyTitleLocalModelKey,
450
+ message: string,
451
+ ): Promise<string | null> {
452
+ const generator = await loadPipeline(modelKey, transport, requestId);
453
+ const promptText = buildPrompt(generator, message);
454
+ const transformers = await loadTransformers(transport, requestId, modelKey);
455
+ const output = (await generator(promptText, {
456
+ max_new_tokens: TITLE_MAX_NEW_TOKENS,
457
+ do_sample: false,
458
+ return_full_text: false,
459
+ stopping_criteria: createStopOnTextCriteria(transformers, generator.tokenizer, TITLE_CLOSE),
460
+ })) as TextGenerationStringOutput;
461
+ return extractTinyTitle(output[0]?.generated_text ?? "");
462
+ }
463
+
464
+ function buildCompletionPrompt(generator: TextGenerationPipeline, promptText: string): string {
465
+ const chat = [{ role: "user", content: promptText }];
466
+ const chatTemplateOptions = {
467
+ add_generation_prompt: true,
468
+ tokenize: false,
469
+ enable_thinking: false,
470
+ };
471
+ return `${generator.tokenizer.apply_chat_template(chat, chatTemplateOptions)}`;
472
+ }
473
+
474
+ /**
475
+ * Generic single-turn completion used by Mnemosyne memory tasks (fact extraction
476
+ * and consolidation). The caller (Mnemosyne) supplies the full task prompt; we
477
+ * wrap it as the user turn, decode greedily, and return the raw text for the
478
+ * caller's own parser. Output is capped to keep local inference latency bounded.
479
+ */
480
+ async function generateCompletion(
481
+ transport: TinyTitleTransport,
482
+ requestId: string,
483
+ modelKey: TinyLocalModelKey,
484
+ promptText: string,
485
+ maxTokens: number | undefined,
486
+ ): Promise<string | null> {
487
+ const generator = await loadPipeline(modelKey, transport, requestId);
488
+ const text = buildCompletionPrompt(generator, promptText);
489
+ const requested = maxTokens ?? MEMORY_COMPLETION_MAX_NEW_TOKENS;
490
+ const maxNewTokens = Math.min(Math.max(1, requested), MEMORY_COMPLETION_MAX_NEW_TOKENS);
491
+ const output = (await generator(text, {
492
+ max_new_tokens: maxNewTokens,
493
+ do_sample: false,
494
+ return_full_text: false,
495
+ })) as TextGenerationStringOutput;
496
+ const generated = (output[0]?.generated_text ?? "").trim();
497
+ return generated === "" ? null : generated;
498
+ }
499
+
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
+ function enqueueRequest(
511
+ transport: TinyTitleTransport,
512
+ request: Extract<TinyTitleWorkerInbound, { type: "generate" | "complete" | "download" }>,
513
+ ): void {
514
+ generateQueue = generateQueue.then(
515
+ async () => {
516
+ await handleQueuedRequest(transport, request);
517
+ },
518
+ async () => {
519
+ await handleQueuedRequest(transport, request);
520
+ },
521
+ );
522
+ }
523
+
524
+ async function handleQueuedRequest(
525
+ transport: TinyTitleTransport,
526
+ request: Extract<TinyTitleWorkerInbound, { type: "generate" | "complete" | "download" }>,
527
+ ): Promise<void> {
528
+ try {
529
+ if (request.type === "download") {
530
+ await loadPipeline(request.modelKey, transport, request.id);
531
+ transport.send({ type: "downloaded", id: request.id });
532
+ return;
533
+ }
534
+ if (request.type === "complete") {
535
+ const text = await generateCompletion(
536
+ transport,
537
+ request.id,
538
+ request.modelKey,
539
+ request.prompt,
540
+ request.maxTokens,
541
+ );
542
+ transport.send({ type: "completion", id: request.id, text });
543
+ return;
544
+ }
545
+ const title = await generateTitle(transport, request.id, request.modelKey, request.message);
546
+ transport.send({ type: "title", id: request.id, title });
547
+ } catch (error) {
548
+ transport.send({ type: "error", id: request.id, error: errorText(error) });
549
+ }
550
+ }
551
+
552
+ export function startTinyTitleWorker(transport: TinyTitleTransport): void {
553
+ transport.onMessage(message => {
554
+ if (message.type === "ping") {
555
+ transport.send({ type: "pong", id: message.id });
556
+ return;
557
+ }
558
+ if (message.type === "close") {
559
+ releasePipelines();
560
+ transport.send({ type: "closed" });
561
+ transport.close();
562
+ return;
563
+ }
564
+ enqueueRequest(transport, message);
565
+ });
566
+ }
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);