@caupulican/pi-adaptative 0.81.37 → 0.81.39

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 (451) hide show
  1. package/CHANGELOG.md +91 -0
  2. package/README.md +1 -1
  3. package/dist/bundled-resources/extensions/tmux-agent-manager/README.md +18 -1
  4. package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.d.ts +131 -0
  5. package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.d.ts.map +1 -0
  6. package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.js +196 -0
  7. package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.js.map +1 -0
  8. package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.ts +308 -0
  9. package/dist/bundled-resources/extensions/tmux-agent-manager/index.d.ts +22 -2
  10. package/dist/bundled-resources/extensions/tmux-agent-manager/index.d.ts.map +1 -1
  11. package/dist/bundled-resources/extensions/tmux-agent-manager/index.js +585 -24
  12. package/dist/bundled-resources/extensions/tmux-agent-manager/index.js.map +1 -1
  13. package/dist/bundled-resources/extensions/tmux-agent-manager/index.ts +749 -27
  14. package/dist/bundled-resources/runtimes/pi-shell-engine/commands/__init__.py +43 -0
  15. package/dist/bundled-resources/runtimes/pi-shell-engine/commands/fs.py +270 -0
  16. package/dist/bundled-resources/runtimes/pi-shell-engine/commands/search.py +252 -0
  17. package/dist/bundled-resources/runtimes/pi-shell-engine/commands/strings.py +399 -0
  18. package/dist/bundled-resources/runtimes/pi-shell-engine/commands/text.py +575 -0
  19. package/dist/bundled-resources/runtimes/pi-shell-engine/context.py +52 -0
  20. package/dist/bundled-resources/runtimes/pi-shell-engine/errors.py +49 -0
  21. package/dist/bundled-resources/runtimes/pi-shell-engine/exec.py +734 -0
  22. package/dist/bundled-resources/runtimes/pi-shell-engine/expand.py +238 -0
  23. package/dist/bundled-resources/runtimes/pi-shell-engine/main.py +132 -0
  24. package/dist/bundled-resources/runtimes/pi-shell-engine/nodes.py +116 -0
  25. package/dist/bundled-resources/runtimes/pi-shell-engine/parser.py +287 -0
  26. package/dist/bundled-resources/runtimes/pi-shell-engine/proc.py +137 -0
  27. package/dist/bundled-resources/runtimes/pi-shell-engine/state.py +100 -0
  28. package/dist/bundled-resources/runtimes/pi-shell-engine/tokens.py +579 -0
  29. package/dist/bundled-resources/skills/tool-call-repair/SKILL.md +19 -9
  30. package/dist/bundled-resources/skills/tool-call-repair/references/failure-grammar.md +22 -5
  31. package/dist/bundled-resources/skills/tool-call-repair/references/repair-catalogue.md +5 -5
  32. package/dist/bundled-resources/skills/tool-call-repair/references/text-protocol-grammar.md +28 -7
  33. package/dist/cli/args.d.ts +3 -0
  34. package/dist/cli/args.d.ts.map +1 -1
  35. package/dist/cli/args.js +15 -0
  36. package/dist/cli/args.js.map +1 -1
  37. package/dist/core/agent-paths.d.ts +49 -0
  38. package/dist/core/agent-paths.d.ts.map +1 -0
  39. package/dist/core/agent-paths.js +107 -0
  40. package/dist/core/agent-paths.js.map +1 -0
  41. package/dist/core/agent-session-services.d.ts.map +1 -1
  42. package/dist/core/agent-session-services.js +3 -3
  43. package/dist/core/agent-session-services.js.map +1 -1
  44. package/dist/core/agent-session.d.ts +234 -17
  45. package/dist/core/agent-session.d.ts.map +1 -1
  46. package/dist/core/agent-session.js +484 -62
  47. package/dist/core/agent-session.js.map +1 -1
  48. package/dist/core/autonomy/contracts.d.ts +9 -0
  49. package/dist/core/autonomy/contracts.d.ts.map +1 -1
  50. package/dist/core/autonomy/contracts.js.map +1 -1
  51. package/dist/core/autonomy/lane-tracker.d.ts +10 -1
  52. package/dist/core/autonomy/lane-tracker.d.ts.map +1 -1
  53. package/dist/core/autonomy/lane-tracker.js +5 -1
  54. package/dist/core/autonomy/lane-tracker.js.map +1 -1
  55. package/dist/core/background-lane-controller.d.ts +122 -6
  56. package/dist/core/background-lane-controller.d.ts.map +1 -1
  57. package/dist/core/background-lane-controller.js +447 -90
  58. package/dist/core/background-lane-controller.js.map +1 -1
  59. package/dist/core/bash-execution-controller.d.ts +4 -0
  60. package/dist/core/bash-execution-controller.d.ts.map +1 -1
  61. package/dist/core/bash-execution-controller.js +7 -1
  62. package/dist/core/bash-execution-controller.js.map +1 -1
  63. package/dist/core/compaction-support.d.ts +13 -3
  64. package/dist/core/compaction-support.d.ts.map +1 -1
  65. package/dist/core/compaction-support.js +43 -7
  66. package/dist/core/compaction-support.js.map +1 -1
  67. package/dist/core/context/context-audit.d.ts +33 -1
  68. package/dist/core/context/context-audit.d.ts.map +1 -1
  69. package/dist/core/context/context-audit.js +31 -5
  70. package/dist/core/context/context-audit.js.map +1 -1
  71. package/dist/core/context/sqlite-runtime-index.d.ts.map +1 -1
  72. package/dist/core/context/sqlite-runtime-index.js +13 -7
  73. package/dist/core/context/sqlite-runtime-index.js.map +1 -1
  74. package/dist/core/context-gc.d.ts.map +1 -1
  75. package/dist/core/context-gc.js +16 -1
  76. package/dist/core/context-gc.js.map +1 -1
  77. package/dist/core/context-pipeline.d.ts +26 -4
  78. package/dist/core/context-pipeline.d.ts.map +1 -1
  79. package/dist/core/context-pipeline.js +137 -14
  80. package/dist/core/context-pipeline.js.map +1 -1
  81. package/dist/core/cost-guard.d.ts +19 -3
  82. package/dist/core/cost-guard.d.ts.map +1 -1
  83. package/dist/core/cost-guard.js +18 -3
  84. package/dist/core/cost-guard.js.map +1 -1
  85. package/dist/core/delegation/session-worker-result.d.ts +34 -4
  86. package/dist/core/delegation/session-worker-result.d.ts.map +1 -1
  87. package/dist/core/delegation/session-worker-result.js +64 -4
  88. package/dist/core/delegation/session-worker-result.js.map +1 -1
  89. package/dist/core/delegation/worker-actions.d.ts.map +1 -1
  90. package/dist/core/delegation/worker-actions.js +1 -1
  91. package/dist/core/delegation/worker-actions.js.map +1 -1
  92. package/dist/core/delegation/worker-result.d.ts +42 -1
  93. package/dist/core/delegation/worker-result.d.ts.map +1 -1
  94. package/dist/core/delegation/worker-result.js +68 -0
  95. package/dist/core/delegation/worker-result.js.map +1 -1
  96. package/dist/core/extensions/loader.d.ts.map +1 -1
  97. package/dist/core/extensions/loader.js +23 -7
  98. package/dist/core/extensions/loader.js.map +1 -1
  99. package/dist/core/extensions/runner.d.ts.map +1 -1
  100. package/dist/core/extensions/runner.js +1 -0
  101. package/dist/core/extensions/runner.js.map +1 -1
  102. package/dist/core/extensions/types.d.ts +51 -0
  103. package/dist/core/extensions/types.d.ts.map +1 -1
  104. package/dist/core/extensions/types.js.map +1 -1
  105. package/dist/core/goal-loop-controller.d.ts +17 -1
  106. package/dist/core/goal-loop-controller.d.ts.map +1 -1
  107. package/dist/core/goal-loop-controller.js +79 -11
  108. package/dist/core/goal-loop-controller.js.map +1 -1
  109. package/dist/core/goals/goal-continuation-controller.d.ts +48 -2
  110. package/dist/core/goals/goal-continuation-controller.d.ts.map +1 -1
  111. package/dist/core/goals/goal-continuation-controller.js +77 -0
  112. package/dist/core/goals/goal-continuation-controller.js.map +1 -1
  113. package/dist/core/goals/goal-continuation-defaults.d.ts +39 -0
  114. package/dist/core/goals/goal-continuation-defaults.d.ts.map +1 -1
  115. package/dist/core/goals/goal-continuation-defaults.js +42 -0
  116. package/dist/core/goals/goal-continuation-defaults.js.map +1 -1
  117. package/dist/core/goals/goal-continuation-prompt.d.ts +4 -0
  118. package/dist/core/goals/goal-continuation-prompt.d.ts.map +1 -1
  119. package/dist/core/goals/goal-continuation-prompt.js +51 -10
  120. package/dist/core/goals/goal-continuation-prompt.js.map +1 -1
  121. package/dist/core/goals/goal-runtime-snapshot.d.ts +101 -2
  122. package/dist/core/goals/goal-runtime-snapshot.d.ts.map +1 -1
  123. package/dist/core/goals/goal-runtime-snapshot.js +87 -4
  124. package/dist/core/goals/goal-runtime-snapshot.js.map +1 -1
  125. package/dist/core/goals/goal-state.d.ts +89 -1
  126. package/dist/core/goals/goal-state.d.ts.map +1 -1
  127. package/dist/core/goals/goal-state.js +71 -5
  128. package/dist/core/goals/goal-state.js.map +1 -1
  129. package/dist/core/goals/goal-tool-core.d.ts +56 -2
  130. package/dist/core/goals/goal-tool-core.d.ts.map +1 -1
  131. package/dist/core/goals/goal-tool-core.js +111 -5
  132. package/dist/core/goals/goal-tool-core.js.map +1 -1
  133. package/dist/core/goals/session-goal-state.d.ts +11 -2
  134. package/dist/core/goals/session-goal-state.d.ts.map +1 -1
  135. package/dist/core/goals/session-goal-state.js +32 -17
  136. package/dist/core/goals/session-goal-state.js.map +1 -1
  137. package/dist/core/keybindings.d.ts +10 -0
  138. package/dist/core/keybindings.d.ts.map +1 -1
  139. package/dist/core/keybindings.js +10 -2
  140. package/dist/core/keybindings.js.map +1 -1
  141. package/dist/core/learning/observation-store.d.ts +12 -3
  142. package/dist/core/learning/observation-store.d.ts.map +1 -1
  143. package/dist/core/learning/observation-store.js +30 -15
  144. package/dist/core/learning/observation-store.js.map +1 -1
  145. package/dist/core/learning/skill-curator.d.ts +5 -1
  146. package/dist/core/learning/skill-curator.d.ts.map +1 -1
  147. package/dist/core/learning/skill-curator.js +21 -19
  148. package/dist/core/learning/skill-curator.js.map +1 -1
  149. package/dist/core/local-runtime-controller.d.ts +65 -3
  150. package/dist/core/local-runtime-controller.d.ts.map +1 -1
  151. package/dist/core/local-runtime-controller.js +186 -26
  152. package/dist/core/local-runtime-controller.js.map +1 -1
  153. package/dist/core/memory/providers/file-store.d.ts +1 -1
  154. package/dist/core/memory/providers/file-store.d.ts.map +1 -1
  155. package/dist/core/memory/providers/file-store.js +6 -6
  156. package/dist/core/memory/providers/file-store.js.map +1 -1
  157. package/dist/core/model-capability.d.ts +34 -0
  158. package/dist/core/model-capability.d.ts.map +1 -1
  159. package/dist/core/model-capability.js +42 -1
  160. package/dist/core/model-capability.js.map +1 -1
  161. package/dist/core/model-router/tool-escalation.d.ts +15 -0
  162. package/dist/core/model-router/tool-escalation.d.ts.map +1 -1
  163. package/dist/core/model-router/tool-escalation.js +23 -1
  164. package/dist/core/model-router/tool-escalation.js.map +1 -1
  165. package/dist/core/model-router-controller.d.ts +34 -7
  166. package/dist/core/model-router-controller.d.ts.map +1 -1
  167. package/dist/core/model-router-controller.js +95 -16
  168. package/dist/core/model-router-controller.js.map +1 -1
  169. package/dist/core/models/adaptation-store.d.ts +7 -0
  170. package/dist/core/models/adaptation-store.d.ts.map +1 -1
  171. package/dist/core/models/adaptation-store.js +47 -11
  172. package/dist/core/models/adaptation-store.js.map +1 -1
  173. package/dist/core/models/default-model-suggestions.d.ts.map +1 -1
  174. package/dist/core/models/default-model-suggestions.js +17 -0
  175. package/dist/core/models/default-model-suggestions.js.map +1 -1
  176. package/dist/core/models/fitness-store.d.ts +3 -0
  177. package/dist/core/models/fitness-store.d.ts.map +1 -1
  178. package/dist/core/models/fitness-store.js +11 -2
  179. package/dist/core/models/fitness-store.js.map +1 -1
  180. package/dist/core/models/llamacpp-runtime.d.ts +180 -0
  181. package/dist/core/models/llamacpp-runtime.d.ts.map +1 -0
  182. package/dist/core/models/llamacpp-runtime.js +475 -0
  183. package/dist/core/models/llamacpp-runtime.js.map +1 -0
  184. package/dist/core/models/local-registration.d.ts +40 -0
  185. package/dist/core/models/local-registration.d.ts.map +1 -1
  186. package/dist/core/models/local-registration.js +94 -5
  187. package/dist/core/models/local-registration.js.map +1 -1
  188. package/dist/core/models/local-runtime.d.ts.map +1 -1
  189. package/dist/core/models/local-runtime.js +9 -7
  190. package/dist/core/models/local-runtime.js.map +1 -1
  191. package/dist/core/models/model-ref.d.ts +7 -0
  192. package/dist/core/models/model-ref.d.ts.map +1 -1
  193. package/dist/core/models/model-ref.js +26 -0
  194. package/dist/core/models/model-ref.js.map +1 -1
  195. package/dist/core/models/needle-runtime.d.ts +257 -0
  196. package/dist/core/models/needle-runtime.d.ts.map +1 -0
  197. package/dist/core/models/needle-runtime.js +519 -0
  198. package/dist/core/models/needle-runtime.js.map +1 -0
  199. package/dist/core/models/prism-llamacpp-lifecycle.d.ts +89 -0
  200. package/dist/core/models/prism-llamacpp-lifecycle.d.ts.map +1 -0
  201. package/dist/core/models/prism-llamacpp-lifecycle.js +121 -0
  202. package/dist/core/models/prism-llamacpp-lifecycle.js.map +1 -0
  203. package/dist/core/package-manager.d.ts.map +1 -1
  204. package/dist/core/package-manager.js +11 -10
  205. package/dist/core/package-manager.js.map +1 -1
  206. package/dist/core/process-matrix/codes.d.ts +72 -0
  207. package/dist/core/process-matrix/codes.d.ts.map +1 -0
  208. package/dist/core/process-matrix/codes.js +15 -0
  209. package/dist/core/process-matrix/codes.js.map +1 -0
  210. package/dist/core/process-matrix/runtime.d.ts +63 -0
  211. package/dist/core/process-matrix/runtime.d.ts.map +1 -0
  212. package/dist/core/process-matrix/runtime.js +310 -0
  213. package/dist/core/process-matrix/runtime.js.map +1 -0
  214. package/dist/core/process-matrix/store.d.ts +22 -0
  215. package/dist/core/process-matrix/store.d.ts.map +1 -0
  216. package/dist/core/process-matrix/store.js +80 -0
  217. package/dist/core/process-matrix/store.js.map +1 -0
  218. package/dist/core/process-matrix/supervisor.d.ts +72 -0
  219. package/dist/core/process-matrix/supervisor.d.ts.map +1 -0
  220. package/dist/core/process-matrix/supervisor.js +130 -0
  221. package/dist/core/process-matrix/supervisor.js.map +1 -0
  222. package/dist/core/profile-registry.d.ts +7 -0
  223. package/dist/core/profile-registry.d.ts.map +1 -1
  224. package/dist/core/profile-registry.js +20 -0
  225. package/dist/core/profile-registry.js.map +1 -1
  226. package/dist/core/python-runtime.d.ts.map +1 -1
  227. package/dist/core/python-runtime.js +3 -3
  228. package/dist/core/python-runtime.js.map +1 -1
  229. package/dist/core/reflection-controller.d.ts +29 -5
  230. package/dist/core/reflection-controller.d.ts.map +1 -1
  231. package/dist/core/reflection-controller.js +215 -126
  232. package/dist/core/reflection-controller.js.map +1 -1
  233. package/dist/core/reload-blockers.d.ts +36 -0
  234. package/dist/core/reload-blockers.d.ts.map +1 -1
  235. package/dist/core/reload-blockers.js +44 -0
  236. package/dist/core/reload-blockers.js.map +1 -1
  237. package/dist/core/resource-loader.d.ts.map +1 -1
  238. package/dist/core/resource-loader.js +8 -7
  239. package/dist/core/resource-loader.js.map +1 -1
  240. package/dist/core/runtime-builder.d.ts +58 -5
  241. package/dist/core/runtime-builder.d.ts.map +1 -1
  242. package/dist/core/runtime-builder.js +209 -25
  243. package/dist/core/runtime-builder.js.map +1 -1
  244. package/dist/core/scout-controller.d.ts +6 -0
  245. package/dist/core/scout-controller.d.ts.map +1 -1
  246. package/dist/core/scout-controller.js +66 -52
  247. package/dist/core/scout-controller.js.map +1 -1
  248. package/dist/core/sdk.d.ts.map +1 -1
  249. package/dist/core/sdk.js +3 -3
  250. package/dist/core/sdk.js.map +1 -1
  251. package/dist/core/session-role.d.ts +31 -0
  252. package/dist/core/session-role.d.ts.map +1 -0
  253. package/dist/core/session-role.js +52 -0
  254. package/dist/core/session-role.js.map +1 -0
  255. package/dist/core/settings-manager.d.ts +42 -3
  256. package/dist/core/settings-manager.d.ts.map +1 -1
  257. package/dist/core/settings-manager.js +94 -60
  258. package/dist/core/settings-manager.js.map +1 -1
  259. package/dist/core/system-prompt-builder.d.ts +12 -0
  260. package/dist/core/system-prompt-builder.d.ts.map +1 -1
  261. package/dist/core/system-prompt-builder.js +6 -0
  262. package/dist/core/system-prompt-builder.js.map +1 -1
  263. package/dist/core/system-prompt.d.ts +14 -1
  264. package/dist/core/system-prompt.d.ts.map +1 -1
  265. package/dist/core/system-prompt.js +20 -3
  266. package/dist/core/system-prompt.js.map +1 -1
  267. package/dist/core/tasks/session-task-state.d.ts +11 -2
  268. package/dist/core/tasks/session-task-state.d.ts.map +1 -1
  269. package/dist/core/tasks/session-task-state.js +27 -11
  270. package/dist/core/tasks/session-task-state.js.map +1 -1
  271. package/dist/core/tasks/task-contract-monitor.d.ts +45 -0
  272. package/dist/core/tasks/task-contract-monitor.d.ts.map +1 -0
  273. package/dist/core/tasks/task-contract-monitor.js +56 -0
  274. package/dist/core/tasks/task-contract-monitor.js.map +1 -0
  275. package/dist/core/tasks/task-state.d.ts +8 -0
  276. package/dist/core/tasks/task-state.d.ts.map +1 -1
  277. package/dist/core/tasks/task-state.js +28 -1
  278. package/dist/core/tasks/task-state.js.map +1 -1
  279. package/dist/core/tool-gate-controller.d.ts.map +1 -1
  280. package/dist/core/tool-gate-controller.js +5 -0
  281. package/dist/core/tool-gate-controller.js.map +1 -1
  282. package/dist/core/tool-recovery-log-records.d.ts +7 -0
  283. package/dist/core/tool-recovery-log-records.d.ts.map +1 -1
  284. package/dist/core/tool-recovery-log-records.js +14 -6
  285. package/dist/core/tool-recovery-log-records.js.map +1 -1
  286. package/dist/core/tool-selection/promotion.d.ts +54 -0
  287. package/dist/core/tool-selection/promotion.d.ts.map +1 -0
  288. package/dist/core/tool-selection/promotion.js +81 -0
  289. package/dist/core/tool-selection/promotion.js.map +1 -0
  290. package/dist/core/tool-selection/tool-performance-store.d.ts +37 -0
  291. package/dist/core/tool-selection/tool-performance-store.d.ts.map +1 -1
  292. package/dist/core/tool-selection/tool-performance-store.js +87 -3
  293. package/dist/core/tool-selection/tool-performance-store.js.map +1 -1
  294. package/dist/core/tool-selection/tool-selection-controller.d.ts +45 -0
  295. package/dist/core/tool-selection/tool-selection-controller.d.ts.map +1 -1
  296. package/dist/core/tool-selection/tool-selection-controller.js +96 -0
  297. package/dist/core/tool-selection/tool-selection-controller.js.map +1 -1
  298. package/dist/core/tools/bash.d.ts +18 -0
  299. package/dist/core/tools/bash.d.ts.map +1 -1
  300. package/dist/core/tools/bash.js +97 -18
  301. package/dist/core/tools/bash.js.map +1 -1
  302. package/dist/core/tools/delegate-status.d.ts +14 -0
  303. package/dist/core/tools/delegate-status.d.ts.map +1 -1
  304. package/dist/core/tools/delegate-status.js +82 -7
  305. package/dist/core/tools/delegate-status.js.map +1 -1
  306. package/dist/core/tools/delegate.d.ts.map +1 -1
  307. package/dist/core/tools/delegate.js +25 -8
  308. package/dist/core/tools/delegate.js.map +1 -1
  309. package/dist/core/tools/find.d.ts.map +1 -1
  310. package/dist/core/tools/find.js +52 -44
  311. package/dist/core/tools/find.js.map +1 -1
  312. package/dist/core/tools/goal.d.ts +94 -3
  313. package/dist/core/tools/goal.d.ts.map +1 -1
  314. package/dist/core/tools/goal.js +165 -15
  315. package/dist/core/tools/goal.js.map +1 -1
  316. package/dist/core/tools/grep.d.ts.map +1 -1
  317. package/dist/core/tools/grep.js +5 -4
  318. package/dist/core/tools/grep.js.map +1 -1
  319. package/dist/core/tools/model-fitness.d.ts +7 -0
  320. package/dist/core/tools/model-fitness.d.ts.map +1 -1
  321. package/dist/core/tools/model-fitness.js +2 -2
  322. package/dist/core/tools/model-fitness.js.map +1 -1
  323. package/dist/core/tools/render-utils.d.ts.map +1 -1
  324. package/dist/core/tools/render-utils.js +1 -1
  325. package/dist/core/tools/render-utils.js.map +1 -1
  326. package/dist/core/tools/shell-contract-router.d.ts +6 -1
  327. package/dist/core/tools/shell-contract-router.d.ts.map +1 -1
  328. package/dist/core/tools/shell-contract-router.js +69 -13
  329. package/dist/core/tools/shell-contract-router.js.map +1 -1
  330. package/dist/core/tools/shell-session.d.ts +89 -0
  331. package/dist/core/tools/shell-session.d.ts.map +1 -0
  332. package/dist/core/tools/shell-session.js +432 -0
  333. package/dist/core/tools/shell-session.js.map +1 -0
  334. package/dist/core/tools/task-steps.d.ts +4 -0
  335. package/dist/core/tools/task-steps.d.ts.map +1 -1
  336. package/dist/core/tools/task-steps.js +63 -8
  337. package/dist/core/tools/task-steps.js.map +1 -1
  338. package/dist/core/tools/tmux-dispatch.d.ts +86 -0
  339. package/dist/core/tools/tmux-dispatch.d.ts.map +1 -0
  340. package/dist/core/tools/tmux-dispatch.js +91 -0
  341. package/dist/core/tools/tmux-dispatch.js.map +1 -0
  342. package/dist/core/tools/windows-shell-engine.d.ts +42 -0
  343. package/dist/core/tools/windows-shell-engine.d.ts.map +1 -0
  344. package/dist/core/tools/windows-shell-engine.js +153 -0
  345. package/dist/core/tools/windows-shell-engine.js.map +1 -0
  346. package/dist/core/tools/windows-shell-state.d.ts +40 -0
  347. package/dist/core/tools/windows-shell-state.d.ts.map +1 -0
  348. package/dist/core/tools/windows-shell-state.js +59 -0
  349. package/dist/core/tools/windows-shell-state.js.map +1 -0
  350. package/dist/core/tools/worktree-sync.d.ts +24 -0
  351. package/dist/core/tools/worktree-sync.d.ts.map +1 -0
  352. package/dist/core/tools/worktree-sync.js +338 -0
  353. package/dist/core/tools/worktree-sync.js.map +1 -0
  354. package/dist/core/trust-manager.d.ts +4 -1
  355. package/dist/core/trust-manager.d.ts.map +1 -1
  356. package/dist/core/trust-manager.js +20 -2
  357. package/dist/core/trust-manager.js.map +1 -1
  358. package/dist/core/util/atomic-file.d.ts +55 -0
  359. package/dist/core/util/atomic-file.d.ts.map +1 -0
  360. package/dist/core/util/atomic-file.js +255 -0
  361. package/dist/core/util/atomic-file.js.map +1 -0
  362. package/dist/core/util/minimatch-cache.d.ts +33 -0
  363. package/dist/core/util/minimatch-cache.d.ts.map +1 -0
  364. package/dist/core/util/minimatch-cache.js +0 -0
  365. package/dist/core/util/minimatch-cache.js.map +1 -0
  366. package/dist/core/worktree-sync/codes.d.ts +227 -0
  367. package/dist/core/worktree-sync/codes.d.ts.map +1 -0
  368. package/dist/core/worktree-sync/codes.js +14 -0
  369. package/dist/core/worktree-sync/codes.js.map +1 -0
  370. package/dist/core/worktree-sync/git-engine.d.ts +156 -0
  371. package/dist/core/worktree-sync/git-engine.d.ts.map +1 -0
  372. package/dist/core/worktree-sync/git-engine.js +1191 -0
  373. package/dist/core/worktree-sync/git-engine.js.map +1 -0
  374. package/dist/core/worktree-sync/lane-gate.d.ts +75 -0
  375. package/dist/core/worktree-sync/lane-gate.d.ts.map +1 -0
  376. package/dist/core/worktree-sync/lane-gate.js +360 -0
  377. package/dist/core/worktree-sync/lane-gate.js.map +1 -0
  378. package/dist/core/worktree-sync/runtime.d.ts +47 -0
  379. package/dist/core/worktree-sync/runtime.d.ts.map +1 -0
  380. package/dist/core/worktree-sync/runtime.js +96 -0
  381. package/dist/core/worktree-sync/runtime.js.map +1 -0
  382. package/dist/core/worktree-sync/store.d.ts +69 -0
  383. package/dist/core/worktree-sync/store.d.ts.map +1 -0
  384. package/dist/core/worktree-sync/store.js +247 -0
  385. package/dist/core/worktree-sync/store.js.map +1 -0
  386. package/dist/core/worktree-sync/watcher.d.ts +29 -0
  387. package/dist/core/worktree-sync/watcher.d.ts.map +1 -0
  388. package/dist/core/worktree-sync/watcher.js +93 -0
  389. package/dist/core/worktree-sync/watcher.js.map +1 -0
  390. package/dist/main.d.ts.map +1 -1
  391. package/dist/main.js +94 -0
  392. package/dist/main.js.map +1 -1
  393. package/dist/migrations.d.ts +9 -0
  394. package/dist/migrations.d.ts.map +1 -1
  395. package/dist/migrations.js +38 -0
  396. package/dist/migrations.js.map +1 -1
  397. package/dist/modes/interactive/auto-learn-controller.d.ts +16 -1
  398. package/dist/modes/interactive/auto-learn-controller.d.ts.map +1 -1
  399. package/dist/modes/interactive/auto-learn-controller.js +50 -8
  400. package/dist/modes/interactive/auto-learn-controller.js.map +1 -1
  401. package/dist/modes/interactive/components/profile-resource-editor.d.ts.map +1 -1
  402. package/dist/modes/interactive/components/profile-resource-editor.js +23 -1
  403. package/dist/modes/interactive/components/profile-resource-editor.js.map +1 -1
  404. package/dist/modes/interactive/interactive-mode.d.ts +1 -0
  405. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  406. package/dist/modes/interactive/interactive-mode.js +4 -0
  407. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  408. package/dist/modes/interactive/local-model-commands.d.ts +43 -0
  409. package/dist/modes/interactive/local-model-commands.d.ts.map +1 -1
  410. package/dist/modes/interactive/local-model-commands.js +290 -3
  411. package/dist/modes/interactive/local-model-commands.js.map +1 -1
  412. package/dist/modes/interactive/session-flow-commands.d.ts.map +1 -1
  413. package/dist/modes/interactive/session-flow-commands.js +24 -4
  414. package/dist/modes/interactive/session-flow-commands.js.map +1 -1
  415. package/dist/utils/fs-watch.d.ts +11 -0
  416. package/dist/utils/fs-watch.d.ts.map +1 -1
  417. package/dist/utils/fs-watch.js +20 -2
  418. package/dist/utils/fs-watch.js.map +1 -1
  419. package/dist/utils/highlight-js-languages.d.ts +4 -0
  420. package/dist/utils/highlight-js-languages.d.ts.map +1 -0
  421. package/dist/utils/highlight-js-languages.js +573 -0
  422. package/dist/utils/highlight-js-languages.js.map +1 -0
  423. package/dist/utils/shell.d.ts +7 -1
  424. package/dist/utils/shell.d.ts.map +1 -1
  425. package/dist/utils/shell.js +39 -9
  426. package/dist/utils/shell.js.map +1 -1
  427. package/dist/utils/syntax-highlight.d.ts.map +1 -1
  428. package/dist/utils/syntax-highlight.js +53 -5
  429. package/dist/utils/syntax-highlight.js.map +1 -1
  430. package/dist/utils/tools-manager.d.ts.map +1 -1
  431. package/dist/utils/tools-manager.js +112 -1
  432. package/dist/utils/tools-manager.js.map +1 -1
  433. package/docs/development.md +2 -0
  434. package/docs/packages.md +1 -1
  435. package/docs/process-matrix.md +120 -0
  436. package/docs/settings.md +5 -2
  437. package/docs/tmux-agent-manager.md +85 -2
  438. package/docs/windows.md +52 -3
  439. package/docs/work-directory.md +29 -0
  440. package/docs/worktree-sync.md +250 -0
  441. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  442. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  443. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  444. package/examples/extensions/sandbox/package-lock.json +2 -2
  445. package/examples/extensions/sandbox/package.json +1 -1
  446. package/examples/extensions/with-deps/package-lock.json +2 -2
  447. package/examples/extensions/with-deps/package.json +1 -1
  448. package/npm-shrinkwrap.json +12 -12
  449. package/package.json +10 -4
  450. package/docs/integration-sweep-builder-blueprint-2026-07-09.md +0 -365
  451. package/docs/integration-sweep-resume-2026-07-09.md +0 -407
@@ -1 +1 @@
1
- {"version":3,"file":"model-ref.js","sourceRoot":"","sources":["../../../src/core/models/model-ref.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Model source normalizer (local-model-lifecycle-design.md, \"one panel, three types\"):\n * everything a user can paste — an ollama tag, an hf.co GGUF ref, a full HuggingFace URL, a\n * copied `ollama pull ...` install command, or an API `provider/model` name — normalizes to ONE\n * typed source. Pure string work: pasted install commands are PARSED for their reference and\n * NEVER executed as shell; unknown forms are rejected with the reason.\n */\n\nexport type ModelSource =\n\t| { type: \"api\"; ref: string }\n\t| { type: \"local\"; pullRef: string }\n\t| { type: \"transformers\"; modelId: string; ref: string }\n\t| { type: \"rejected\"; reason: string };\n\nconst OLLAMA_TAG = /^[a-z0-9][a-z0-9._-]*(?::[A-Za-z0-9._-]+)?$/;\nconst HF_REF = /^hf\\.co\\/([\\w.-]+)\\/([\\w.-]+)(?::([\\w.-]+))?$/i;\nconst HF_URL = /^https?:\\/\\/(?:www\\.)?huggingface\\.co\\/([\\w.-]+)\\/([\\w.-]+)(?:\\/.*)?$/i;\nconst SHELL_METACHARS = /[;&|`$<>(){}\\\\]/;\nconst PI_MANAGED_TRANSFORMERS_MODEL_IDS = new Set([\"openbmb/MiniCPM5-1B\"]);\n\nfunction normalizeHuggingFaceReference(org: string, repo: string, quant?: string): ModelSource {\n\tconst modelId = `${org}/${repo}`;\n\tif (!quant && PI_MANAGED_TRANSFORMERS_MODEL_IDS.has(modelId)) {\n\t\treturn { type: \"transformers\", modelId, ref: `hf.co/${modelId}` };\n\t}\n\treturn { type: \"local\", pullRef: `hf.co/${modelId}${quant ? `:${quant}` : \"\"}` };\n}\n\nexport function normalizeModelSource(rawInput: string): ModelSource {\n\tconst input = rawInput.trim();\n\tif (input.length === 0) return { type: \"rejected\", reason: \"empty input\" };\n\tif (input.length > 500) return { type: \"rejected\", reason: \"input too long to be a model reference\" };\n\n\t// Pasted install command: extract the reference, never execute anything.\n\tconst installCommand = /^ollama\\s+(?:pull|run)\\s+(.+)$/i.exec(input);\n\tif (installCommand) {\n\t\tconst argument = installCommand[1]!.trim().split(/\\s+/)[0] ?? \"\";\n\t\tif (SHELL_METACHARS.test(argument)) {\n\t\t\treturn { type: \"rejected\", reason: \"install command argument contains shell metacharacters\" };\n\t\t}\n\t\tconst inner = normalizeModelSource(argument);\n\t\tif (inner.type === \"local\") return inner;\n\t\treturn { type: \"rejected\", reason: `could not extract a model reference from the install command` };\n\t}\n\n\tif (SHELL_METACHARS.test(input) || /\\s/.test(input)) {\n\t\treturn { type: \"rejected\", reason: \"not a recognized model reference (contains spaces or shell characters)\" };\n\t}\n\n\t// Full HuggingFace URL -> either a pi-managed Transformers model (curated full-base ids) or an\n\t// Ollama hf.co pull ref (GGUF/quant refs still belong to Ollama and keep their :quant suffix).\n\tconst hfUrl = HF_URL.exec(input);\n\tif (hfUrl) {\n\t\treturn normalizeHuggingFaceReference(hfUrl[1]!, hfUrl[2]!);\n\t}\n\n\t// hf.co/org/repo[:quant]\n\tconst hfRef = HF_REF.exec(input);\n\tif (hfRef) {\n\t\treturn normalizeHuggingFaceReference(hfRef[1]!, hfRef[2]!, hfRef[3]);\n\t}\n\n\tif (input.includes(\"://\")) {\n\t\treturn { type: \"rejected\", reason: \"only huggingface.co URLs are recognized as local model links\" };\n\t}\n\n\t// provider/model -> API-registered model (nothing to install; auth + selection only).\n\tif (input.includes(\"/\")) {\n\t\tconst [provider, ...rest] = input.split(\"/\");\n\t\tconst model = rest.join(\"/\");\n\t\tif (provider && model && !model.includes(\"/\")) {\n\t\t\treturn { type: \"api\", ref: `${provider}/${model}` };\n\t\t}\n\t\treturn { type: \"rejected\", reason: \"expected provider/model or hf.co/org/repo[:quant]\" };\n\t}\n\n\t// Bare ollama tag (\"qwen3:1.7b\", \"pi-lifter:latest\", \"llama3\").\n\tif (OLLAMA_TAG.test(input)) {\n\t\treturn { type: \"local\", pullRef: input };\n\t}\n\n\treturn { type: \"rejected\", reason: \"not a recognized model reference\" };\n}\n\n/**\n * Whether an installed-model listing entry (`installedName`, as `OllamaRuntime.list()` reports it)\n * is the same model as `ref` (a user-typed reference, or a `models.json`-registered id). Ollama\n * always reports installed models with an explicit tag — a pull with no tag is normalized to\n * `:latest` server-side — but a `ref` is frequently bare (\"llama3\"), so an exact string compare\n * alone misses that pairing. Exact match always counts; otherwise treat a bare ref against its\n * `:latest`-tagged listing as the same model, and the reverse (a `ref` explicitly pinned to\n * `:latest` against a bare listing), since the local-runtime interface is kept runtime-agnostic\n * (see local-runtime.ts's module docstring) and a future/alternate runtime could list bare names.\n * Shared by every ref-vs-installed-list comparison — see `AgentSession._warnIfManualModelChoiceIsRisky`\n * and `removeLocalModel` in interactive-mode.ts, which had the identical bug independently.\n */\nexport function matchesInstalledLocalModel(ref: string, installedName: string): boolean {\n\tif (ref === installedName) return true;\n\tconst hasTag = (value: string): boolean => value.includes(\":\");\n\tif (!hasTag(ref) && installedName === `${ref}:latest`) return true;\n\tif (!hasTag(installedName) && ref === `${installedName}:latest`) return true;\n\treturn false;\n}\n"]}
1
+ {"version":3,"file":"model-ref.js","sourceRoot":"","sources":["../../../src/core/models/model-ref.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAUnD,MAAM,UAAU,GAAG,6CAA6C,CAAC;AACjE,MAAM,MAAM,GAAG,gDAAgD,CAAC;AAChE,MAAM,MAAM,GAAG,wEAAwE,CAAC;AACxF,MAAM,eAAe,GAAG,iBAAiB,CAAC;AAC1C,MAAM,iCAAiC,GAAG,IAAI,GAAG,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC;AAC3E;;;;;;;GAOG;AACH,MAAM,wBAAwB,GAAG,IAAI,GAAG,CAAiB,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAC7G;;;;;;;GAOG;AACH,MAAM,eAAe,GAAG,uBAAuB,CAAC;AAEhD,SAAS,6BAA6B,CAAC,GAAW,EAAE,IAAY,EAAE,KAAc,EAAe;IAC9F,MAAM,OAAO,GAAG,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;IACjC,MAAM,YAAY,GAAG,wBAAwB,CAAC,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;IACzE,IAAI,YAAY,EAAE,CAAC;QAClB,OAAO,EAAE,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,YAAY,EAAE,GAAG,EAAE,SAAS,YAAY,EAAE,EAAE,CAAC;IACxF,CAAC;IACD,IAAI,CAAC,KAAK,IAAI,OAAO,CAAC,WAAW,EAAE,KAAK,eAAe,CAAC,WAAW,EAAE,EAAE,CAAC;QACvE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,SAAS,eAAe,EAAE,EAAE,CAAC;IAC5D,CAAC;IACD,IAAI,CAAC,KAAK,IAAI,iCAAiC,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;QAC9D,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,GAAG,EAAE,SAAS,OAAO,EAAE,EAAE,CAAC;IACnE,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC;AAAA,CACjF;AAED,MAAM,UAAU,oBAAoB,CAAC,QAAgB,EAAe;IACnE,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC;IAC9B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC;IAC3E,IAAI,KAAK,CAAC,MAAM,GAAG,GAAG;QAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,wCAAwC,EAAE,CAAC;IAEtG,yEAAyE;IACzE,MAAM,cAAc,GAAG,iCAAiC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACrE,IAAI,cAAc,EAAE,CAAC;QACpB,MAAM,QAAQ,GAAG,cAAc,CAAC,CAAC,CAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACjE,IAAI,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;YACpC,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,wDAAwD,EAAE,CAAC;QAC/F,CAAC;QACD,MAAM,KAAK,GAAG,oBAAoB,CAAC,QAAQ,CAAC,CAAC;QAC7C,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO;YAAE,OAAO,KAAK,CAAC;QACzC,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,8DAA8D,EAAE,CAAC;IACrG,CAAC;IAED,IAAI,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACrD,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,wEAAwE,EAAE,CAAC;IAC/G,CAAC;IAED,+FAA+F;IAC/F,+FAA+F;IAC/F,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACjC,IAAI,KAAK,EAAE,CAAC;QACX,OAAO,6BAA6B,CAAC,KAAK,CAAC,CAAC,CAAE,EAAE,KAAK,CAAC,CAAC,CAAE,CAAC,CAAC;IAC5D,CAAC;IAED,yBAAyB;IACzB,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACjC,IAAI,KAAK,EAAE,CAAC;QACX,OAAO,6BAA6B,CAAC,KAAK,CAAC,CAAC,CAAE,EAAE,KAAK,CAAC,CAAC,CAAE,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACtE,CAAC;IAED,IAAI,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,8DAA8D,EAAE,CAAC;IACrG,CAAC;IAED,sFAAsF;IACtF,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACzB,MAAM,CAAC,QAAQ,EAAE,GAAG,IAAI,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC7C,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,QAAQ,IAAI,KAAK,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YAC/C,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,QAAQ,IAAI,KAAK,EAAE,EAAE,CAAC;QACrD,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,mDAAmD,EAAE,CAAC;IAC1F,CAAC;IAED,gEAAgE;IAChE,IAAI,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC5B,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC1C,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,kCAAkC,EAAE,CAAC;AAAA,CACxE;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,0BAA0B,CAAC,GAAW,EAAE,aAAqB,EAAW;IACvF,IAAI,GAAG,KAAK,aAAa;QAAE,OAAO,IAAI,CAAC;IACvC,MAAM,MAAM,GAAG,CAAC,KAAa,EAAW,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IAC/D,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,aAAa,KAAK,GAAG,GAAG,SAAS;QAAE,OAAO,IAAI,CAAC;IACnE,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,GAAG,KAAK,GAAG,aAAa,SAAS;QAAE,OAAO,IAAI,CAAC;IAC7E,OAAO,KAAK,CAAC;AAAA,CACb","sourcesContent":["/**\n * Model source normalizer (local-model-lifecycle-design.md, \"one panel, three types\"):\n * everything a user can paste — an ollama tag, an hf.co GGUF ref, a full HuggingFace URL, a\n * copied `ollama pull ...` install command, or an API `provider/model` name — normalizes to ONE\n * typed source. Pure string work: pasted install commands are PARSED for their reference and\n * NEVER executed as shell; unknown forms are rejected with the reason.\n */\n\nimport { BONSAI_27B } from \"./llamacpp-runtime.ts\";\n\nexport type ModelSource =\n\t| { type: \"api\"; ref: string }\n\t| { type: \"local\"; pullRef: string }\n\t| { type: \"transformers\"; modelId: string; ref: string }\n\t| { type: \"prism-llamacpp\"; modelId: string; ref: string }\n\t| { type: \"needle\"; ref: string }\n\t| { type: \"rejected\"; reason: string };\n\nconst OLLAMA_TAG = /^[a-z0-9][a-z0-9._-]*(?::[A-Za-z0-9._-]+)?$/;\nconst HF_REF = /^hf\\.co\\/([\\w.-]+)\\/([\\w.-]+)(?::([\\w.-]+))?$/i;\nconst HF_URL = /^https?:\\/\\/(?:www\\.)?huggingface\\.co\\/([\\w.-]+)\\/([\\w.-]+)(?:\\/.*)?$/i;\nconst SHELL_METACHARS = /[;&|`$<>(){}\\\\]/;\nconst PI_MANAGED_TRANSFORMERS_MODEL_IDS = new Set([\"openbmb/MiniCPM5-1B\"]);\n/**\n * Curated prism-ml models that require pi's managed prism llama.cpp fork (see llamacpp-runtime.ts's\n * module docstring: stock llama.cpp/Ollama cannot serve these weights). These GGUF refs route to the\n * \"prism-llamacpp\" source instead of Ollama's local pull path regardless of a supplied quant suffix\n * — the curated descriptor already pins the exact quant file to fetch, so a quant on the input ref is\n * accepted but not required. Keyed lowercase for case-insensitive matching; values are the\n * canonical-cased modelId, sourced from the descriptor itself so the two never drift apart.\n */\nconst PRISM_LLAMACPP_MODEL_IDS = new Map<string, string>([[BONSAI_27B.repo.toLowerCase(), BONSAI_27B.repo]]);\n/**\n * The single curated needle model id (see needle-runtime.ts) — a standalone function-call test\n * bench with no GGUF/quant concept at all (a pip-installed Python package + one pinned pickle\n * checkpoint), so unlike the prism route above, a quant suffix on the input is NOT accepted — it\n * falls through to the ordinary local/Ollama route instead, mirroring the transformers route's\n * `!quant` gate. Only this exact repo routes to \"needle\"; any other Cactus-Compute/* ref keeps the\n * ordinary local route.\n */\nconst NEEDLE_MODEL_ID = \"Cactus-Compute/needle\";\n\nfunction normalizeHuggingFaceReference(org: string, repo: string, quant?: string): ModelSource {\n\tconst modelId = `${org}/${repo}`;\n\tconst prismModelId = PRISM_LLAMACPP_MODEL_IDS.get(modelId.toLowerCase());\n\tif (prismModelId) {\n\t\treturn { type: \"prism-llamacpp\", modelId: prismModelId, ref: `hf.co/${prismModelId}` };\n\t}\n\tif (!quant && modelId.toLowerCase() === NEEDLE_MODEL_ID.toLowerCase()) {\n\t\treturn { type: \"needle\", ref: `hf.co/${NEEDLE_MODEL_ID}` };\n\t}\n\tif (!quant && PI_MANAGED_TRANSFORMERS_MODEL_IDS.has(modelId)) {\n\t\treturn { type: \"transformers\", modelId, ref: `hf.co/${modelId}` };\n\t}\n\treturn { type: \"local\", pullRef: `hf.co/${modelId}${quant ? `:${quant}` : \"\"}` };\n}\n\nexport function normalizeModelSource(rawInput: string): ModelSource {\n\tconst input = rawInput.trim();\n\tif (input.length === 0) return { type: \"rejected\", reason: \"empty input\" };\n\tif (input.length > 500) return { type: \"rejected\", reason: \"input too long to be a model reference\" };\n\n\t// Pasted install command: extract the reference, never execute anything.\n\tconst installCommand = /^ollama\\s+(?:pull|run)\\s+(.+)$/i.exec(input);\n\tif (installCommand) {\n\t\tconst argument = installCommand[1]!.trim().split(/\\s+/)[0] ?? \"\";\n\t\tif (SHELL_METACHARS.test(argument)) {\n\t\t\treturn { type: \"rejected\", reason: \"install command argument contains shell metacharacters\" };\n\t\t}\n\t\tconst inner = normalizeModelSource(argument);\n\t\tif (inner.type === \"local\") return inner;\n\t\treturn { type: \"rejected\", reason: `could not extract a model reference from the install command` };\n\t}\n\n\tif (SHELL_METACHARS.test(input) || /\\s/.test(input)) {\n\t\treturn { type: \"rejected\", reason: \"not a recognized model reference (contains spaces or shell characters)\" };\n\t}\n\n\t// Full HuggingFace URL -> either a pi-managed Transformers model (curated full-base ids) or an\n\t// Ollama hf.co pull ref (GGUF/quant refs still belong to Ollama and keep their :quant suffix).\n\tconst hfUrl = HF_URL.exec(input);\n\tif (hfUrl) {\n\t\treturn normalizeHuggingFaceReference(hfUrl[1]!, hfUrl[2]!);\n\t}\n\n\t// hf.co/org/repo[:quant]\n\tconst hfRef = HF_REF.exec(input);\n\tif (hfRef) {\n\t\treturn normalizeHuggingFaceReference(hfRef[1]!, hfRef[2]!, hfRef[3]);\n\t}\n\n\tif (input.includes(\"://\")) {\n\t\treturn { type: \"rejected\", reason: \"only huggingface.co URLs are recognized as local model links\" };\n\t}\n\n\t// provider/model -> API-registered model (nothing to install; auth + selection only).\n\tif (input.includes(\"/\")) {\n\t\tconst [provider, ...rest] = input.split(\"/\");\n\t\tconst model = rest.join(\"/\");\n\t\tif (provider && model && !model.includes(\"/\")) {\n\t\t\treturn { type: \"api\", ref: `${provider}/${model}` };\n\t\t}\n\t\treturn { type: \"rejected\", reason: \"expected provider/model or hf.co/org/repo[:quant]\" };\n\t}\n\n\t// Bare ollama tag (\"qwen3:1.7b\", \"pi-lifter:latest\", \"llama3\").\n\tif (OLLAMA_TAG.test(input)) {\n\t\treturn { type: \"local\", pullRef: input };\n\t}\n\n\treturn { type: \"rejected\", reason: \"not a recognized model reference\" };\n}\n\n/**\n * Whether an installed-model listing entry (`installedName`, as `OllamaRuntime.list()` reports it)\n * is the same model as `ref` (a user-typed reference, or a `models.json`-registered id). Ollama\n * always reports installed models with an explicit tag — a pull with no tag is normalized to\n * `:latest` server-side — but a `ref` is frequently bare (\"llama3\"), so an exact string compare\n * alone misses that pairing. Exact match always counts; otherwise treat a bare ref against its\n * `:latest`-tagged listing as the same model, and the reverse (a `ref` explicitly pinned to\n * `:latest` against a bare listing), since the local-runtime interface is kept runtime-agnostic\n * (see local-runtime.ts's module docstring) and a future/alternate runtime could list bare names.\n * Shared by every ref-vs-installed-list comparison — see `AgentSession._warnIfManualModelChoiceIsRisky`\n * and `removeLocalModel` in interactive-mode.ts, which had the identical bug independently.\n */\nexport function matchesInstalledLocalModel(ref: string, installedName: string): boolean {\n\tif (ref === installedName) return true;\n\tconst hasTag = (value: string): boolean => value.includes(\":\");\n\tif (!hasTag(ref) && installedName === `${ref}:latest`) return true;\n\tif (!hasTag(installedName) && ref === `${installedName}:latest`) return true;\n\treturn false;\n}\n"]}
@@ -0,0 +1,257 @@
1
+ import type { RuntimeCommandRunner } from "./local-runtime.ts";
2
+ /**
3
+ * Managed runtime for needle (https://github.com/cactus-compute/needle), a 26M-parameter single-shot
4
+ * function-calling model. Needle exposes no server — the only integration surface is CLI/library
5
+ * invocation — so this module owns clone+build of a pinned commit into a contained venv, base
6
+ * checkpoint download, and one-shot `needle run` invocation/parsing. Mirrors local-runtime.ts's
7
+ * OllamaRuntime/TransformersRuntime and llamacpp-runtime.ts's PrismLlamaCppRuntime: injectable seams
8
+ * for exists/fetch/run-command, pi-owned directories under agentDir (runtimes/, models/), onProgress
9
+ * as best-effort UI feedback, and honest error taxonomies instead of silent fallbacks.
10
+ *
11
+ * Investigation findings (verified against a real clone + real venv + real CPU inference run, not
12
+ * just the README):
13
+ *
14
+ * - `needle run` (the CLI's single-inference subcommand) takes a REQUIRED `--checkpoint` and does
15
+ * NOT auto-download one — unlike `needle playground`/`finetune`, which resolve a checkpoint
16
+ * internally. The pretrained base checkpoint (`needle.pkl`) lives on `Cactus-Compute/needle` on
17
+ * Hugging Face and is fetched directly by {@link NeedleRuntime.downloadWeights} (never invoked
18
+ * implicitly by installManaged/runFunctionCall — this module never silently starts a large
19
+ * download on the caller's behalf).
20
+ * - SECURITY (owner-flagged, verified during investigation): `Cactus-Compute/needle` on Hugging Face
21
+ * publishes THREE weight artifacts — `needle.pkl` (52,633,098 bytes), `model.safetensors`
22
+ * (reported as a different param count on the HF model card, 30.4M vs the 26,315,421 a real
23
+ * `needle run` printed — likely not even the same checkpoint), and `needle-cq4.zip`. I grepped the
24
+ * ENTIRE needle package (cli.py, model/run.py, model/export.py, model/quantize.py,
25
+ * training/{finetune,pretrain,train}.py, utils/distributed.py, ui/server.py — every `pickle`/`.zip`
26
+ * hit in the repo) and confirmed every checkpoint load/save path in the codebase — including the
27
+ * one `needle run` uses — is `pickle.load`/`pickle.dump` on a `.pkl` file. There is NO safetensors
28
+ * reader anywhere in the package; `model.safetensors` is not consumed by any code this module
29
+ * drives. `needle-cq4.zip` ("Cactus Quantized 4-bit"?) isn't referenced by any code here either —
30
+ * the README states production inference runs on the separate Cactus C++ engine
31
+ * (cactus-compute/cactus), which very plausibly consumes its own format; that repo was not audited
32
+ * here. Bottom line: **this module has no choice but to fetch and deserialize `needle.pkl` via
33
+ * Python's `pickle` module, which executes arbitrary code by construction** — there is no
34
+ * safetensors alternative available in the code path we drive. Given that, {@link
35
+ * NeedleRuntime.downloadWeights} pins the exact sha256 ({@link NEEDLE_WEIGHTS_SHA256}) and byte
36
+ * count ({@link NEEDLE_WEIGHTS_BYTES}) of the file as verified on 2026-07-18 (direct `sha256sum` of
37
+ * a real download, cross-checked against Hugging Face's own `x-linked-etag`/`x-linked-size`
38
+ * headers on the resolve redirect, at repo commit `5f89b4307696d669c3df1d38ae057e6e1728b107`) and
39
+ * VERIFIES every download against that pin, refusing to keep any file that doesn't match — this
40
+ * catches a compromised/tampered/swapped artifact but does NOT make loading a pickle file safe in
41
+ * the abstract; the actual `pickle.load` execution happens entirely inside needle's own Python
42
+ * process, outside this module's control. The owner should decide whether serving this model at
43
+ * all is acceptable given upstream's format choice.
44
+ * - The upstream `setup` script does more than a contained managed install can honor: it can `sudo
45
+ * apt-get install` a newer Python or `python3-venv`, `sudo modprobe` TPU kernel modules, toggle
46
+ * transparent hugepages via a root-owned sysfs write, and interactively prompts for a
47
+ * WANDB_API_KEY. None of that is available under containment (no writes outside the pi-owned dir,
48
+ * no sudo, no interactive prompts, no global installs) and none of it is required for the
49
+ * documented Mac/PC single-inference quickstart path. {@link NeedleRuntime.installManaged} instead
50
+ * replicates only the containable core: `python3 -m venv`, `pip install -e <clonedRepo>` (the
51
+ * script's own `pip install -e . -q`), and the CPU (or CUDA, when an NVIDIA GPU is detected) JAX
52
+ * backend install. TPU support and the sudo-gated OS package/kernel-module steps are intentionally
53
+ * NOT replicated.
54
+ * - `needle run`'s console-script entry point (`<venv>/bin/needle`, installed by
55
+ * `pip install -e .` from the `[project.scripts]` table) is the only way to invoke the CLI:
56
+ * `needle/cli.py` has no `if __name__ == "__main__":` guard, so `python -m needle.cli` silently
57
+ * does nothing — the installed console script is required.
58
+ * - A verified real stdout capture of `needle run --checkpoint <path> --query "..." --tools '[...]'`
59
+ * is multi-line diagnostic prose followed by the tool call:
60
+ * ```
61
+ * Loading checkpoint: <path>
62
+ * Downloading pretrained tokenizer from HuggingFace... (first run only — cached after)
63
+ * Model parameters: 26,315,421
64
+ *
65
+ * Query: <query>
66
+ * Tools: <tools JSON, truncated to 80 chars>...
67
+ *
68
+ * <tool_call>[{"name":"get_weather","arguments":{"location":"SanFrancisco"}}]
69
+ * ```
70
+ * The `<tool_call>` marker is a literal SentencePiece user-defined-symbol token, always emitted as
71
+ * the first generated token per the model's own answer format. {@link NeedleRuntime}'s stdout
72
+ * parser locates that marker and strictly `JSON.parse`s everything after it — never guessing a
73
+ * result from unparseable output.
74
+ * - Known upstream quirk (verified against a live run with a camelCase tool name): `needle run`'s CLI
75
+ * wrapper (`main()` in `needle/model/run.py`) streams tokens straight to stdout and never applies
76
+ * the tool-name restoration its own `generate()` return value receives — a tool name that wasn't
77
+ * already snake_case (e.g. `getWeatherNow`) is reported back snake-cased (`get_weather_now`), not
78
+ * in its original casing. This is a limitation of the upstream CLI, not something patched here.
79
+ * - Pinned commit `ffb1c5144c5a16cb8ec650dbc8a6f6fd3854f8f2` was captured via a real `git clone` of
80
+ * the repo's default branch HEAD on 2026-07-18 (the repo's own history shows no pushes since
81
+ * 2026-07-01, so this is not stale). A plain `git clone --depth 1` only reliably lands on a pinned
82
+ * commit when it still happens to be the remote's current tip; {@link NeedleRuntime.installManaged}
83
+ * instead fetches the exact commit SHA directly (`git fetch --depth 1 origin <sha>`), which GitHub
84
+ * serves for any reachable commit on a public repo, so the pin holds even after upstream moves on.
85
+ */
86
+ export declare const NEEDLE_REPO_URL = "https://github.com/cactus-compute/needle.git";
87
+ /**
88
+ * Pinned needle commit — captured via a real `git clone` of the repo's default-branch HEAD on
89
+ * 2026-07-18 (`git ls-remote https://github.com/cactus-compute/needle HEAD` resolves to the same
90
+ * SHA). Bump here (re-verify with the same command) when a newer commit is needed.
91
+ */
92
+ export declare const NEEDLE_PINNED_COMMIT = "ffb1c5144c5a16cb8ec650dbc8a6f6fd3854f8f2";
93
+ /**
94
+ * Pinned integrity for `needle.pkl` — see the module-level SECURITY note: this is a pickle file
95
+ * (arbitrary code execution on load by construction), so every download is verified against this
96
+ * exact sha256+size before being kept. Captured 2026-07-18: `sha256sum` of a real download matched
97
+ * Hugging Face's `x-linked-etag` header on the resolve redirect at repo commit
98
+ * `5f89b4307696d669c3df1d38ae057e6e1728b107`. Bump both together (re-verify the same way) only for a
99
+ * deliberate, reviewed upstream weights update.
100
+ */
101
+ export declare const NEEDLE_WEIGHTS_SHA256 = "40a32e91d1d4197bf15ba559b74f6727c342dc8746918742fc7d8e2c1f18df40";
102
+ export declare const NEEDLE_WEIGHTS_BYTES = 52633098;
103
+ export interface NeedleDetectResult {
104
+ installed: boolean;
105
+ installDir?: string;
106
+ commit?: string;
107
+ pythonAvailable: boolean;
108
+ checkpointPresent: boolean;
109
+ }
110
+ export interface NeedleFunctionCallRequest {
111
+ query: string;
112
+ /** A JSON-serializable tool schema value (never a pre-stringified JSON string) — serialized
113
+ * exactly once by {@link NeedleRuntime.runFunctionCall}, never by the caller. */
114
+ tools: unknown;
115
+ }
116
+ export interface NeedleFunctionCall {
117
+ name: string;
118
+ arguments: Record<string, unknown>;
119
+ }
120
+ export type NeedleFunctionCallResult = {
121
+ ok: true;
122
+ call: NeedleFunctionCall;
123
+ } | {
124
+ ok: false;
125
+ error: string;
126
+ rawOutput: string;
127
+ };
128
+ export interface NeedleSmokeTestResult {
129
+ ok: boolean;
130
+ latencyMs: number;
131
+ call?: NeedleFunctionCall;
132
+ error?: string;
133
+ }
134
+ export interface NeedleWeightsDownloadResult {
135
+ ok: boolean;
136
+ path?: string;
137
+ skipped?: boolean;
138
+ error?: string;
139
+ }
140
+ interface NeedleWeightsIntegrity {
141
+ sha256: string;
142
+ bytes: number;
143
+ }
144
+ export interface NeedleRuntimeDeps {
145
+ existsFn?: (path: string) => boolean;
146
+ fetchFn?: typeof fetch;
147
+ /** Runs git/pip/python commands. Injectable so installManaged/runFunctionCall's orchestration is
148
+ * testable without a real clone/venv/inference. Defaults to a real spawn-and-collect-output
149
+ * runner (installs can run long; a single inference call is comparatively quick but still shells
150
+ * out to a real interpreter). */
151
+ runCommand?: RuntimeCommandRunner;
152
+ /** Whether a named command exists on PATH (git, python3, nvidia-smi). */
153
+ hasCommand?: (command: string) => boolean;
154
+ hasNvidiaGpu?: () => boolean;
155
+ platform?: () => string;
156
+ /** Expected weights sha256+size. Defaults to {@link NEEDLE_WEIGHTS_SHA256}/{@link
157
+ * NEEDLE_WEIGHTS_BYTES} — the real pinned values. Overridable ONLY so tests can exercise the full
158
+ * download+verify path with small synthetic content instead of the real 52MB pickle; this is a
159
+ * constructor-level seam for a trusted caller, the same trust boundary every other injectable dep
160
+ * in this class already relies on (fetchFn/runCommand could equally be used to bypass anything).
161
+ */
162
+ weightsIntegrity?: NeedleWeightsIntegrity;
163
+ }
164
+ export declare class NeedleRuntime {
165
+ private readonly _agentDir;
166
+ private readonly _exists;
167
+ private readonly _fetch;
168
+ private readonly _runCommand;
169
+ private readonly _hasCommand;
170
+ private readonly _hasNvidiaGpu;
171
+ private readonly _platform;
172
+ private readonly _weightsIntegrity;
173
+ constructor(args: {
174
+ agentDir: string;
175
+ deps?: NeedleRuntimeDeps;
176
+ });
177
+ runtimeDir(): string;
178
+ modelsDir(): string;
179
+ checkpointPath(): string;
180
+ private _srcDir;
181
+ private _venvDir;
182
+ private _pythonPath;
183
+ /** The installed `needle` console script (from `[project.scripts]`, written by `pip install -e`)
184
+ * — the only invokable entry point, since `needle/cli.py` has no `__main__` guard. */
185
+ private _entryPath;
186
+ private _manifestPath;
187
+ private _readManifest;
188
+ private _writeManifest;
189
+ detect(): Promise<NeedleDetectResult>;
190
+ /**
191
+ * Clone the pinned needle commit and build a contained, pi-owned runtime (consent-gated by the
192
+ * caller, same contract as OllamaRuntime#installManaged / PrismLlamaCppRuntime#installManaged —
193
+ * this method only does the mechanical clone+build+verify and reports the outcome honestly). See
194
+ * the module docstring for exactly which upstream `setup` steps are and are not replicated under
195
+ * containment.
196
+ */
197
+ installManaged(onProgress?: (status: string) => void): Promise<{
198
+ ok: boolean;
199
+ error?: string;
200
+ }>;
201
+ private _cloneAtPinnedCommit;
202
+ /**
203
+ * Download the pretrained base weights (`needle.pkl`). `needle run` takes an explicit
204
+ * `--checkpoint` and does NOT auto-download one (unlike `needle playground`/`finetune`) — see the
205
+ * module docstring. Never called implicitly by installManaged/runFunctionCall; the caller decides
206
+ * when to pull weights.
207
+ *
208
+ * `needle.pkl` is a pickle file — arbitrary code execution on load by construction (see the
209
+ * module-level SECURITY note) — so every download is verified against the pinned sha256+size
210
+ * ({@link NEEDLE_WEIGHTS_SHA256}/{@link NEEDLE_WEIGHTS_BYTES}, overridable only via the
211
+ * `weightsIntegrity` test seam) computed in the same streaming pass that writes the file, with the
212
+ * partial/mismatched file deleted on ANY failure — this never leaves an unverified or wrong pickle
213
+ * sitting where {@link runFunctionCall} would load it.
214
+ */
215
+ downloadWeights(onProgress?: (status: string) => void): Promise<NeedleWeightsDownloadResult>;
216
+ private _fileSizeBytes;
217
+ private _cleanupPartial;
218
+ /**
219
+ * Run one single-shot inference through `needle run --checkpoint ... --query ... --tools ...`
220
+ * inside the contained venv's installed console script. Argv is passed as an array to the
221
+ * injected `runCommand` seam — never a shell string — so query/tools text containing quotes,
222
+ * spaces, or other shell-hostile characters is passed through literally (verified against a real
223
+ * invocation; see the module docstring). `tools` is serialized with `JSON.stringify` exactly once.
224
+ */
225
+ runFunctionCall(request: NeedleFunctionCallRequest, options?: {
226
+ checkpointPath?: string;
227
+ }): Promise<NeedleFunctionCallResult>;
228
+ /**
229
+ * Strictly parse a real `needle run` stdout capture:
230
+ * ```
231
+ * Loading checkpoint: <path>
232
+ * Model parameters: <n>
233
+ *
234
+ * Query: <query>
235
+ * Tools: <tools, truncated to 80 chars>...
236
+ *
237
+ * <tool_call>[{"name":"...","arguments":{...}}]
238
+ * ```
239
+ * (diagnostic lines above the marker vary — e.g. a one-time "Downloading pretrained tokenizer..."
240
+ * line on a cold cache — so parsing locates the `<tool_call>` marker rather than assuming a fixed
241
+ * line count/position). Unparseable output is always an error carrying the raw text, never a
242
+ * guessed result.
243
+ */
244
+ private _parseFunctionCall;
245
+ /** Canned get_weather query/tool through {@link runFunctionCall}; the wiring task uses this as the
246
+ * post-install verification step. */
247
+ smokeTest(onProgress?: (status: string) => void): Promise<NeedleSmokeTestResult>;
248
+ /**
249
+ * No-op: every needle invocation here is a single-shot `runCommand` call that spawns, waits for
250
+ * exit, and returns — there is no persistent/detached child process this runtime owns between
251
+ * calls for dispose() to track or kill (contrast OllamaRuntime/PrismLlamaCppRuntime's long-lived
252
+ * `serve()` child).
253
+ */
254
+ dispose(): void;
255
+ }
256
+ export {};
257
+ //# sourceMappingURL=needle-runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"needle-runtime.d.ts","sourceRoot":"","sources":["../../../src/core/models/needle-runtime.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from \"node:crypto\";\nimport { createWriteStream, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from \"node:fs\";\nimport { platform as osPlatform } from \"node:os\";\nimport { join } from \"node:path\";\nimport { type Readable, Transform } from \"node:stream\";\nimport { pipeline } from \"node:stream/promises\";\nimport { spawnProcess, spawnProcessSync, waitForChildProcessWithTermination } from \"../../utils/child-process.ts\";\nimport { modelsDir as agentModelsDir, runtimesDir as agentRuntimesDir } from \"../agent-paths.ts\";\nimport type { RuntimeCommandResult, RuntimeCommandRunner } from \"./local-runtime.ts\";\n\n/**\n * Managed runtime for needle (https://github.com/cactus-compute/needle), a 26M-parameter single-shot\n * function-calling model. Needle exposes no server — the only integration surface is CLI/library\n * invocation — so this module owns clone+build of a pinned commit into a contained venv, base\n * checkpoint download, and one-shot `needle run` invocation/parsing. Mirrors local-runtime.ts's\n * OllamaRuntime/TransformersRuntime and llamacpp-runtime.ts's PrismLlamaCppRuntime: injectable seams\n * for exists/fetch/run-command, pi-owned directories under agentDir (runtimes/, models/), onProgress\n * as best-effort UI feedback, and honest error taxonomies instead of silent fallbacks.\n *\n * Investigation findings (verified against a real clone + real venv + real CPU inference run, not\n * just the README):\n *\n * - `needle run` (the CLI's single-inference subcommand) takes a REQUIRED `--checkpoint` and does\n * NOT auto-download one — unlike `needle playground`/`finetune`, which resolve a checkpoint\n * internally. The pretrained base checkpoint (`needle.pkl`) lives on `Cactus-Compute/needle` on\n * Hugging Face and is fetched directly by {@link NeedleRuntime.downloadWeights} (never invoked\n * implicitly by installManaged/runFunctionCall — this module never silently starts a large\n * download on the caller's behalf).\n * - SECURITY (owner-flagged, verified during investigation): `Cactus-Compute/needle` on Hugging Face\n * publishes THREE weight artifacts — `needle.pkl` (52,633,098 bytes), `model.safetensors`\n * (reported as a different param count on the HF model card, 30.4M vs the 26,315,421 a real\n * `needle run` printed — likely not even the same checkpoint), and `needle-cq4.zip`. I grepped the\n * ENTIRE needle package (cli.py, model/run.py, model/export.py, model/quantize.py,\n * training/{finetune,pretrain,train}.py, utils/distributed.py, ui/server.py — every `pickle`/`.zip`\n * hit in the repo) and confirmed every checkpoint load/save path in the codebase — including the\n * one `needle run` uses — is `pickle.load`/`pickle.dump` on a `.pkl` file. There is NO safetensors\n * reader anywhere in the package; `model.safetensors` is not consumed by any code this module\n * drives. `needle-cq4.zip` (\"Cactus Quantized 4-bit\"?) isn't referenced by any code here either —\n * the README states production inference runs on the separate Cactus C++ engine\n * (cactus-compute/cactus), which very plausibly consumes its own format; that repo was not audited\n * here. Bottom line: **this module has no choice but to fetch and deserialize `needle.pkl` via\n * Python's `pickle` module, which executes arbitrary code by construction** — there is no\n * safetensors alternative available in the code path we drive. Given that, {@link\n * NeedleRuntime.downloadWeights} pins the exact sha256 ({@link NEEDLE_WEIGHTS_SHA256}) and byte\n * count ({@link NEEDLE_WEIGHTS_BYTES}) of the file as verified on 2026-07-18 (direct `sha256sum` of\n * a real download, cross-checked against Hugging Face's own `x-linked-etag`/`x-linked-size`\n * headers on the resolve redirect, at repo commit `5f89b4307696d669c3df1d38ae057e6e1728b107`) and\n * VERIFIES every download against that pin, refusing to keep any file that doesn't match — this\n * catches a compromised/tampered/swapped artifact but does NOT make loading a pickle file safe in\n * the abstract; the actual `pickle.load` execution happens entirely inside needle's own Python\n * process, outside this module's control. The owner should decide whether serving this model at\n * all is acceptable given upstream's format choice.\n * - The upstream `setup` script does more than a contained managed install can honor: it can `sudo\n * apt-get install` a newer Python or `python3-venv`, `sudo modprobe` TPU kernel modules, toggle\n * transparent hugepages via a root-owned sysfs write, and interactively prompts for a\n * WANDB_API_KEY. None of that is available under containment (no writes outside the pi-owned dir,\n * no sudo, no interactive prompts, no global installs) and none of it is required for the\n * documented Mac/PC single-inference quickstart path. {@link NeedleRuntime.installManaged} instead\n * replicates only the containable core: `python3 -m venv`, `pip install -e <clonedRepo>` (the\n * script's own `pip install -e . -q`), and the CPU (or CUDA, when an NVIDIA GPU is detected) JAX\n * backend install. TPU support and the sudo-gated OS package/kernel-module steps are intentionally\n * NOT replicated.\n * - `needle run`'s console-script entry point (`<venv>/bin/needle`, installed by\n * `pip install -e .` from the `[project.scripts]` table) is the only way to invoke the CLI:\n * `needle/cli.py` has no `if __name__ == \"__main__\":` guard, so `python -m needle.cli` silently\n * does nothing — the installed console script is required.\n * - A verified real stdout capture of `needle run --checkpoint <path> --query \"...\" --tools '[...]'`\n * is multi-line diagnostic prose followed by the tool call:\n * ```\n * Loading checkpoint: <path>\n * Downloading pretrained tokenizer from HuggingFace... (first run only — cached after)\n * Model parameters: 26,315,421\n *\n * Query: <query>\n * Tools: <tools JSON, truncated to 80 chars>...\n *\n * <tool_call>[{\"name\":\"get_weather\",\"arguments\":{\"location\":\"SanFrancisco\"}}]\n * ```\n * The `<tool_call>` marker is a literal SentencePiece user-defined-symbol token, always emitted as\n * the first generated token per the model's own answer format. {@link NeedleRuntime}'s stdout\n * parser locates that marker and strictly `JSON.parse`s everything after it — never guessing a\n * result from unparseable output.\n * - Known upstream quirk (verified against a live run with a camelCase tool name): `needle run`'s CLI\n * wrapper (`main()` in `needle/model/run.py`) streams tokens straight to stdout and never applies\n * the tool-name restoration its own `generate()` return value receives — a tool name that wasn't\n * already snake_case (e.g. `getWeatherNow`) is reported back snake-cased (`get_weather_now`), not\n * in its original casing. This is a limitation of the upstream CLI, not something patched here.\n * - Pinned commit `ffb1c5144c5a16cb8ec650dbc8a6f6fd3854f8f2` was captured via a real `git clone` of\n * the repo's default branch HEAD on 2026-07-18 (the repo's own history shows no pushes since\n * 2026-07-01, so this is not stale). A plain `git clone --depth 1` only reliably lands on a pinned\n * commit when it still happens to be the remote's current tip; {@link NeedleRuntime.installManaged}\n * instead fetches the exact commit SHA directly (`git fetch --depth 1 origin <sha>`), which GitHub\n * serves for any reachable commit on a public repo, so the pin holds even after upstream moves on.\n */\n\nexport const NEEDLE_REPO_URL = \"https://github.com/cactus-compute/needle.git\";\n\n/**\n * Pinned needle commit — captured via a real `git clone` of the repo's default-branch HEAD on\n * 2026-07-18 (`git ls-remote https://github.com/cactus-compute/needle HEAD` resolves to the same\n * SHA). Bump here (re-verify with the same command) when a newer commit is needed.\n */\nexport const NEEDLE_PINNED_COMMIT = \"ffb1c5144c5a16cb8ec650dbc8a6f6fd3854f8f2\";\n\nconst NEEDLE_HF_MODEL_REPO = \"Cactus-Compute/needle\";\nconst NEEDLE_CHECKPOINT_FILENAME = \"needle.pkl\";\nconst NEEDLE_TOOL_CALL_MARKER = \"<tool_call>\";\n\n/**\n * Pinned integrity for `needle.pkl` — see the module-level SECURITY note: this is a pickle file\n * (arbitrary code execution on load by construction), so every download is verified against this\n * exact sha256+size before being kept. Captured 2026-07-18: `sha256sum` of a real download matched\n * Hugging Face's `x-linked-etag` header on the resolve redirect at repo commit\n * `5f89b4307696d669c3df1d38ae057e6e1728b107`. Bump both together (re-verify the same way) only for a\n * deliberate, reviewed upstream weights update.\n */\nexport const NEEDLE_WEIGHTS_SHA256 = \"40a32e91d1d4197bf15ba559b74f6727c342dc8746918742fc7d8e2c1f18df40\";\nexport const NEEDLE_WEIGHTS_BYTES = 52_633_098;\n\nconst NEEDLE_COMMAND_TIMEOUT_MS = 10 * 60_000;\nconst NEEDLE_KILL_GRACE_MS = 2_000;\n\nconst NEEDLE_SMOKE_QUERY = \"What's the weather in San Francisco?\";\nconst NEEDLE_SMOKE_TOOLS = [\n\t{\n\t\tname: \"get_weather\",\n\t\tdescription: \"Get current weather for a city.\",\n\t\tparameters: { location: { type: \"string\", description: \"City name.\", required: true } },\n\t},\n];\n\nexport interface NeedleDetectResult {\n\tinstalled: boolean;\n\tinstallDir?: string;\n\tcommit?: string;\n\tpythonAvailable: boolean;\n\tcheckpointPresent: boolean;\n}\n\nexport interface NeedleFunctionCallRequest {\n\tquery: string;\n\t/** A JSON-serializable tool schema value (never a pre-stringified JSON string) — serialized\n\t * exactly once by {@link NeedleRuntime.runFunctionCall}, never by the caller. */\n\ttools: unknown;\n}\n\nexport interface NeedleFunctionCall {\n\tname: string;\n\targuments: Record<string, unknown>;\n}\n\nexport type NeedleFunctionCallResult =\n\t| { ok: true; call: NeedleFunctionCall }\n\t| { ok: false; error: string; rawOutput: string };\n\nexport interface NeedleSmokeTestResult {\n\tok: boolean;\n\tlatencyMs: number;\n\tcall?: NeedleFunctionCall;\n\terror?: string;\n}\n\nexport interface NeedleWeightsDownloadResult {\n\tok: boolean;\n\tpath?: string;\n\tskipped?: boolean;\n\terror?: string;\n}\n\ninterface NeedleWeightsIntegrity {\n\tsha256: string;\n\tbytes: number;\n}\n\nexport interface NeedleRuntimeDeps {\n\texistsFn?: (path: string) => boolean;\n\tfetchFn?: typeof fetch;\n\t/** Runs git/pip/python commands. Injectable so installManaged/runFunctionCall's orchestration is\n\t * testable without a real clone/venv/inference. Defaults to a real spawn-and-collect-output\n\t * runner (installs can run long; a single inference call is comparatively quick but still shells\n\t * out to a real interpreter). */\n\trunCommand?: RuntimeCommandRunner;\n\t/** Whether a named command exists on PATH (git, python3, nvidia-smi). */\n\thasCommand?: (command: string) => boolean;\n\thasNvidiaGpu?: () => boolean;\n\tplatform?: () => string;\n\t/** Expected weights sha256+size. Defaults to {@link NEEDLE_WEIGHTS_SHA256}/{@link\n\t * NEEDLE_WEIGHTS_BYTES} — the real pinned values. Overridable ONLY so tests can exercise the full\n\t * download+verify path with small synthetic content instead of the real 52MB pickle; this is a\n\t * constructor-level seam for a trusted caller, the same trust boundary every other injectable dep\n\t * in this class already relies on (fetchFn/runCommand could equally be used to bypass anything).\n\t */\n\tweightsIntegrity?: NeedleWeightsIntegrity;\n}\n\ninterface NeedleInstallMetadata {\n\tcommit: string;\n\tentryCommand: string;\n}\n\nfunction tailLines(value: string | undefined, count = 3): string {\n\treturn value?.trim().split(\"\\n\").slice(-count).join(\"\\n\") || \"unknown error\";\n}\n\n/** Pass bytes through unchanged while feeding them into a running hash — lets a single `pipeline()`\n * call both write the download to disk and compute its sha256 in one streaming pass. */\nfunction hashingPassThrough(hash: ReturnType<typeof createHash>): Transform {\n\treturn new Transform({\n\t\ttransform(chunk: Buffer, _encoding, callback) {\n\t\t\thash.update(chunk);\n\t\t\tcallback(null, chunk);\n\t\t},\n\t});\n}\n\nasync function defaultRunCommand(\n\tcommand: string,\n\targs: string[],\n\toptions: { env?: NodeJS.ProcessEnv; onOutput?: (chunk: string) => void } = {},\n): Promise<RuntimeCommandResult> {\n\ttry {\n\t\tconst proc = spawnProcess(command, args, {\n\t\t\tdetached: process.platform !== \"win32\",\n\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\tenv: options.env ? { ...process.env, ...options.env } : process.env,\n\t\t});\n\t\tlet stdout = \"\";\n\t\tlet stderr = \"\";\n\t\tproc.stdout.setEncoding(\"utf8\");\n\t\tproc.stderr.setEncoding(\"utf8\");\n\t\tproc.stdout.on(\"data\", (chunk: string) => {\n\t\t\tstdout = `${stdout}${chunk}`.slice(-1024 * 1024);\n\t\t\toptions.onOutput?.(chunk);\n\t\t});\n\t\tproc.stderr.on(\"data\", (chunk: string) => {\n\t\t\tstderr = `${stderr}${chunk}`.slice(-1024 * 1024);\n\t\t\toptions.onOutput?.(chunk);\n\t\t});\n\t\tconst terminal = await waitForChildProcessWithTermination(proc, {\n\t\t\ttimeoutMs: NEEDLE_COMMAND_TIMEOUT_MS,\n\t\t\tkillGraceMs: NEEDLE_KILL_GRACE_MS,\n\t\t});\n\t\tconst timedOut = terminal.reason === \"timeout\";\n\t\treturn {\n\t\t\tok: terminal.code === 0,\n\t\t\tstdout,\n\t\t\tstderr,\n\t\t\tcode: terminal.code,\n\t\t\t...(terminal.code === 0\n\t\t\t\t? {}\n\t\t\t\t: {\n\t\t\t\t\t\terror: timedOut\n\t\t\t\t\t\t\t? `${command} timed out after ${NEEDLE_COMMAND_TIMEOUT_MS}ms`\n\t\t\t\t\t\t\t: stderr.trim() || stdout.trim() || `exit code ${terminal.code ?? \"unknown\"}`,\n\t\t\t\t\t}),\n\t\t};\n\t} catch (error) {\n\t\treturn { ok: false, stdout: \"\", stderr: \"\", error: error instanceof Error ? error.message : String(error) };\n\t}\n}\n\nexport class NeedleRuntime {\n\tprivate readonly _agentDir: string;\n\tprivate readonly _exists: (path: string) => boolean;\n\tprivate readonly _fetch: typeof fetch;\n\tprivate readonly _runCommand: RuntimeCommandRunner;\n\tprivate readonly _hasCommand: (command: string) => boolean;\n\tprivate readonly _hasNvidiaGpu: () => boolean;\n\tprivate readonly _platform: () => string;\n\tprivate readonly _weightsIntegrity: NeedleWeightsIntegrity;\n\n\tconstructor(args: { agentDir: string; deps?: NeedleRuntimeDeps }) {\n\t\tthis._agentDir = args.agentDir;\n\t\tthis._exists = args.deps?.existsFn ?? existsSync;\n\t\tthis._fetch = args.deps?.fetchFn ?? fetch;\n\t\tthis._runCommand = args.deps?.runCommand ?? defaultRunCommand;\n\t\tthis._hasCommand =\n\t\t\targs.deps?.hasCommand ??\n\t\t\t((command) =>\n\t\t\t\tspawnProcessSync(command, [\"--version\"], { encoding: \"utf8\", timeout: 5_000 }).error === undefined);\n\t\tthis._hasNvidiaGpu = args.deps?.hasNvidiaGpu ?? (() => this._hasCommand(\"nvidia-smi\"));\n\t\tthis._platform = args.deps?.platform ?? osPlatform;\n\t\tthis._weightsIntegrity = args.deps?.weightsIntegrity ?? {\n\t\t\tsha256: NEEDLE_WEIGHTS_SHA256,\n\t\t\tbytes: NEEDLE_WEIGHTS_BYTES,\n\t\t};\n\t}\n\n\truntimeDir(): string {\n\t\treturn agentRuntimesDir(\"needle\", this._agentDir);\n\t}\n\n\tmodelsDir(): string {\n\t\treturn agentModelsDir(\"needle\", this._agentDir);\n\t}\n\n\tcheckpointPath(): string {\n\t\treturn join(this.modelsDir(), NEEDLE_CHECKPOINT_FILENAME);\n\t}\n\n\tprivate _srcDir(): string {\n\t\treturn join(this.runtimeDir(), \"src\");\n\t}\n\n\tprivate _venvDir(): string {\n\t\treturn join(this.runtimeDir(), \"venv\");\n\t}\n\n\tprivate _pythonPath(): string {\n\t\treturn this._platform() === \"win32\"\n\t\t\t? join(this._venvDir(), \"Scripts\", \"python.exe\")\n\t\t\t: join(this._venvDir(), \"bin\", \"python\");\n\t}\n\n\t/** The installed `needle` console script (from `[project.scripts]`, written by `pip install -e`)\n\t * — the only invokable entry point, since `needle/cli.py` has no `__main__` guard. */\n\tprivate _entryPath(): string {\n\t\treturn this._platform() === \"win32\"\n\t\t\t? join(this._venvDir(), \"Scripts\", \"needle.exe\")\n\t\t\t: join(this._venvDir(), \"bin\", \"needle\");\n\t}\n\n\tprivate _manifestPath(): string {\n\t\treturn join(this.runtimeDir(), \"install.json\");\n\t}\n\n\tprivate _readManifest(): NeedleInstallMetadata | undefined {\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(readFileSync(this._manifestPath(), \"utf8\")) as {\n\t\t\t\tcommit?: unknown;\n\t\t\t\tentryCommand?: unknown;\n\t\t\t};\n\t\t\treturn typeof parsed.commit === \"string\" && typeof parsed.entryCommand === \"string\"\n\t\t\t\t? { commit: parsed.commit, entryCommand: parsed.entryCommand }\n\t\t\t\t: undefined;\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\tprivate _writeManifest(metadata: NeedleInstallMetadata): void {\n\t\twriteFileSync(this._manifestPath(), JSON.stringify(metadata, null, \"\\t\"));\n\t}\n\n\tasync detect(): Promise<NeedleDetectResult> {\n\t\tconst installed = this._exists(this._entryPath());\n\t\treturn {\n\t\t\tinstalled,\n\t\t\tinstallDir: installed ? this.runtimeDir() : undefined,\n\t\t\tcommit: this._readManifest()?.commit,\n\t\t\tpythonAvailable: this._hasCommand(\"python3\"),\n\t\t\tcheckpointPresent: this._exists(this.checkpointPath()),\n\t\t};\n\t}\n\n\t/**\n\t * Clone the pinned needle commit and build a contained, pi-owned runtime (consent-gated by the\n\t * caller, same contract as OllamaRuntime#installManaged / PrismLlamaCppRuntime#installManaged —\n\t * this method only does the mechanical clone+build+verify and reports the outcome honestly). See\n\t * the module docstring for exactly which upstream `setup` steps are and are not replicated under\n\t * containment.\n\t */\n\tasync installManaged(onProgress?: (status: string) => void): Promise<{ ok: boolean; error?: string }> {\n\t\tif (!this._hasCommand(\"git\")) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\terror: `git-missing: git is required to clone needle (${NEEDLE_REPO_URL}). Install git (e.g. \\`apt-get install git\\`, \\`brew install git\\`) and try again.`,\n\t\t\t};\n\t\t}\n\t\tif (!this._hasCommand(\"python3\")) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\terror: \"python-missing: python3 (>=3.11) is required to run needle. Install Python 3.11+ (e.g. `apt-get install python3 python3-venv`, `brew install python@3.12`) and try again.\",\n\t\t\t};\n\t\t}\n\n\t\tmkdirSync(this.runtimeDir(), { recursive: true });\n\t\tconst srcDir = this._srcDir();\n\t\tif (this._exists(srcDir)) rmSync(srcDir, { recursive: true, force: true });\n\n\t\tonProgress?.(`Cloning ${NEEDLE_REPO_URL}…`);\n\t\tconst cloned = await this._cloneAtPinnedCommit(srcDir);\n\t\tif (!cloned.ok) return cloned;\n\n\t\tonProgress?.(`Creating isolated Python venv at ${this._venvDir()}…`);\n\t\tconst venv = await this._runCommand(\"python3\", [\"-m\", \"venv\", this._venvDir()]);\n\t\tif (!venv.ok) return { ok: false, error: `setup-fail: ${tailLines(venv.error ?? venv.stderr)}` };\n\t\tif (!this._exists(this._pythonPath())) {\n\t\t\treturn { ok: false, error: `setup-fail: venv created but ${this._pythonPath()} was not found` };\n\t\t}\n\n\t\tonProgress?.(\"Upgrading pip inside the pi-managed venv…\");\n\t\tconst pipUpgrade = await this._runCommand(this._pythonPath(), [\"-m\", \"pip\", \"install\", \"--upgrade\", \"pip\"]);\n\t\tif (!pipUpgrade.ok) {\n\t\t\treturn { ok: false, error: `setup-fail: ${tailLines(pipUpgrade.error ?? pipUpgrade.stderr)}` };\n\t\t}\n\n\t\tonProgress?.(\"Installing needle (editable) into the pi-managed venv…\");\n\t\tconst install = await this._runCommand(this._pythonPath(), [\"-m\", \"pip\", \"install\", \"-e\", srcDir]);\n\t\tif (!install.ok) return { ok: false, error: `setup-fail: ${tailLines(install.error ?? install.stderr)}` };\n\n\t\tconst cudaAvailable = this._hasNvidiaGpu();\n\t\tonProgress?.(cudaAvailable ? \"Installing JAX (CUDA 12 backend)…\" : \"Installing JAX (CPU backend)…\");\n\t\tconst jaxArgs = cudaAvailable ? [\"jax[cuda12]\"] : [\"jax\"];\n\t\tconst jax = await this._runCommand(this._pythonPath(), [\"-m\", \"pip\", \"install\", \"-U\", ...jaxArgs]);\n\t\tif (!jax.ok) return { ok: false, error: `setup-fail: ${tailLines(jax.error ?? jax.stderr)}` };\n\n\t\tonProgress?.(\"Verifying the needle install…\");\n\t\tconst verify = await this._runCommand(this._pythonPath(), [\"-c\", \"import jax, needle; print(needle.__file__)\"]);\n\t\tif (!verify.ok) return { ok: false, error: `verify-fail: ${tailLines(verify.error ?? verify.stderr)}` };\n\n\t\tconst entryCommand = this._entryPath();\n\t\tif (!this._exists(entryCommand)) {\n\t\t\treturn { ok: false, error: `verify-fail: install reported success but ${entryCommand} was not found` };\n\t\t}\n\n\t\tthis._writeManifest({ commit: NEEDLE_PINNED_COMMIT, entryCommand });\n\t\tonProgress?.(\"needle installed.\");\n\t\treturn { ok: true };\n\t}\n\n\t/**\n\t * A plain `git clone --depth 1` only lands on {@link NEEDLE_PINNED_COMMIT} when it still happens\n\t * to be the remote's current default-branch tip. Fetching the exact SHA directly instead (verified\n\t * against the real repo) pins reliably even after upstream moves on: GitHub serves any reachable\n\t * commit for a public repo via `git fetch <sha>`, not just refs.\n\t */\n\tprivate async _cloneAtPinnedCommit(srcDir: string): Promise<{ ok: boolean; error?: string }> {\n\t\tconst init = await this._runCommand(\"git\", [\"init\", \"-q\", srcDir]);\n\t\tif (!init.ok) return { ok: false, error: `clone-fail: ${tailLines(init.error ?? init.stderr)}` };\n\t\tconst remote = await this._runCommand(\"git\", [\"-C\", srcDir, \"remote\", \"add\", \"origin\", NEEDLE_REPO_URL]);\n\t\tif (!remote.ok) return { ok: false, error: `clone-fail: ${tailLines(remote.error ?? remote.stderr)}` };\n\t\tconst fetched = await this._runCommand(\"git\", [\n\t\t\t\"-C\",\n\t\t\tsrcDir,\n\t\t\t\"fetch\",\n\t\t\t\"--depth\",\n\t\t\t\"1\",\n\t\t\t\"origin\",\n\t\t\tNEEDLE_PINNED_COMMIT,\n\t\t]);\n\t\tif (!fetched.ok) return { ok: false, error: `clone-fail: ${tailLines(fetched.error ?? fetched.stderr)}` };\n\t\tconst checkout = await this._runCommand(\"git\", [\"-C\", srcDir, \"checkout\", \"-q\", \"FETCH_HEAD\"]);\n\t\tif (!checkout.ok) return { ok: false, error: `clone-fail: ${tailLines(checkout.error ?? checkout.stderr)}` };\n\t\treturn { ok: true };\n\t}\n\n\t/**\n\t * Download the pretrained base weights (`needle.pkl`). `needle run` takes an explicit\n\t * `--checkpoint` and does NOT auto-download one (unlike `needle playground`/`finetune`) — see the\n\t * module docstring. Never called implicitly by installManaged/runFunctionCall; the caller decides\n\t * when to pull weights.\n\t *\n\t * `needle.pkl` is a pickle file — arbitrary code execution on load by construction (see the\n\t * module-level SECURITY note) — so every download is verified against the pinned sha256+size\n\t * ({@link NEEDLE_WEIGHTS_SHA256}/{@link NEEDLE_WEIGHTS_BYTES}, overridable only via the\n\t * `weightsIntegrity` test seam) computed in the same streaming pass that writes the file, with the\n\t * partial/mismatched file deleted on ANY failure — this never leaves an unverified or wrong pickle\n\t * sitting where {@link runFunctionCall} would load it.\n\t */\n\tasync downloadWeights(onProgress?: (status: string) => void): Promise<NeedleWeightsDownloadResult> {\n\t\tconst destPath = this.checkpointPath();\n\t\tconst url = `https://huggingface.co/${NEEDLE_HF_MODEL_REPO}/resolve/main/${NEEDLE_CHECKPOINT_FILENAME}`;\n\t\tmkdirSync(this.modelsDir(), { recursive: true });\n\n\t\tif (this._exists(destPath) && this._fileSizeBytes(destPath) === this._weightsIntegrity.bytes) {\n\t\t\tonProgress?.(\n\t\t\t\t`${NEEDLE_CHECKPOINT_FILENAME} already downloaded (${this._weightsIntegrity.bytes} bytes) — skipping.`,\n\t\t\t);\n\t\t\treturn { ok: true, path: destPath, skipped: true };\n\t\t}\n\n\t\tonProgress?.(`Downloading ${NEEDLE_CHECKPOINT_FILENAME} from ${NEEDLE_HF_MODEL_REPO}…`);\n\t\tlet response: Response;\n\t\ttry {\n\t\t\tresponse = await this._fetch(url);\n\t\t} catch (error) {\n\t\t\treturn { ok: false, error: `download-fail: ${error instanceof Error ? error.message : String(error)}` };\n\t\t}\n\t\tif (!response.ok || !response.body) {\n\t\t\treturn { ok: false, error: `download-fail: HTTP ${response.status}` };\n\t\t}\n\n\t\tconst hash = createHash(\"sha256\");\n\t\ttry {\n\t\t\tawait pipeline(response.body as unknown as Readable, hashingPassThrough(hash), createWriteStream(destPath));\n\t\t} catch (error) {\n\t\t\tthis._cleanupPartial(destPath);\n\t\t\treturn { ok: false, error: `download-fail: ${error instanceof Error ? error.message : String(error)}` };\n\t\t}\n\n\t\tconst actualBytes = this._fileSizeBytes(destPath);\n\t\tconst actualSha256 = hash.digest(\"hex\");\n\t\tif (actualBytes !== this._weightsIntegrity.bytes || actualSha256 !== this._weightsIntegrity.sha256) {\n\t\t\tthis._cleanupPartial(destPath);\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\terror: `integrity-fail: downloaded ${NEEDLE_CHECKPOINT_FILENAME} does not match the pinned checksum — expected sha256 ${this._weightsIntegrity.sha256} (${this._weightsIntegrity.bytes} bytes), got ${actualSha256} (${actualBytes ?? 0} bytes). Refusing to keep a pickle file that failed integrity verification.`,\n\t\t\t};\n\t\t}\n\n\t\tonProgress?.(`${NEEDLE_CHECKPOINT_FILENAME} downloaded and verified (sha256 ${actualSha256}).`);\n\t\treturn { ok: true, path: destPath };\n\t}\n\n\tprivate _fileSizeBytes(path: string): number | undefined {\n\t\ttry {\n\t\t\treturn statSync(path).size;\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\tprivate _cleanupPartial(path: string): void {\n\t\ttry {\n\t\t\trmSync(path, { force: true });\n\t\t} catch {\n\t\t\t// best-effort cleanup\n\t\t}\n\t}\n\n\t/**\n\t * Run one single-shot inference through `needle run --checkpoint ... --query ... --tools ...`\n\t * inside the contained venv's installed console script. Argv is passed as an array to the\n\t * injected `runCommand` seam — never a shell string — so query/tools text containing quotes,\n\t * spaces, or other shell-hostile characters is passed through literally (verified against a real\n\t * invocation; see the module docstring). `tools` is serialized with `JSON.stringify` exactly once.\n\t */\n\tasync runFunctionCall(\n\t\trequest: NeedleFunctionCallRequest,\n\t\toptions?: { checkpointPath?: string },\n\t): Promise<NeedleFunctionCallResult> {\n\t\tconst entryCommand = this._entryPath();\n\t\tif (!this._exists(entryCommand)) {\n\t\t\treturn { ok: false, error: \"not-installed: run installManaged() first\", rawOutput: \"\" };\n\t\t}\n\t\tconst checkpointPath = options?.checkpointPath ?? this.checkpointPath();\n\t\tif (!this._exists(checkpointPath)) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\terror: `checkpoint-missing: no checkpoint at ${checkpointPath} — run downloadWeights() first`,\n\t\t\t\trawOutput: \"\",\n\t\t\t};\n\t\t}\n\n\t\tconst argv = [\n\t\t\t\"run\",\n\t\t\t\"--checkpoint\",\n\t\t\tcheckpointPath,\n\t\t\t\"--query\",\n\t\t\trequest.query,\n\t\t\t\"--tools\",\n\t\t\tJSON.stringify(request.tools),\n\t\t];\n\t\tconst result = await this._runCommand(entryCommand, argv);\n\t\tconst rawOutput = [result.stdout, result.stderr].filter(Boolean).join(\"\\n\");\n\t\tif (!result.ok) {\n\t\t\treturn { ok: false, error: `run-fail: ${tailLines(result.error ?? result.stderr)}`, rawOutput };\n\t\t}\n\t\treturn this._parseFunctionCall(result.stdout, rawOutput);\n\t}\n\n\t/**\n\t * Strictly parse a real `needle run` stdout capture:\n\t * ```\n\t * Loading checkpoint: <path>\n\t * Model parameters: <n>\n\t *\n\t * Query: <query>\n\t * Tools: <tools, truncated to 80 chars>...\n\t *\n\t * <tool_call>[{\"name\":\"...\",\"arguments\":{...}}]\n\t * ```\n\t * (diagnostic lines above the marker vary — e.g. a one-time \"Downloading pretrained tokenizer...\"\n\t * line on a cold cache — so parsing locates the `<tool_call>` marker rather than assuming a fixed\n\t * line count/position). Unparseable output is always an error carrying the raw text, never a\n\t * guessed result.\n\t */\n\tprivate _parseFunctionCall(stdout: string, rawOutput: string): NeedleFunctionCallResult {\n\t\tconst markerIndex = stdout.indexOf(NEEDLE_TOOL_CALL_MARKER);\n\t\tif (markerIndex < 0) {\n\t\t\treturn { ok: false, error: `unparseable-output: no ${NEEDLE_TOOL_CALL_MARKER} marker in stdout`, rawOutput };\n\t\t}\n\t\tconst jsonText = stdout.slice(markerIndex + NEEDLE_TOOL_CALL_MARKER.length).trim();\n\t\tlet parsed: unknown;\n\t\ttry {\n\t\t\tparsed = JSON.parse(jsonText);\n\t\t} catch (error) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\terror: `unparseable-output: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t\trawOutput,\n\t\t\t};\n\t\t}\n\t\tif (!Array.isArray(parsed) || parsed.length === 0) {\n\t\t\treturn { ok: false, error: \"unparseable-output: expected a non-empty tool-call array\", rawOutput };\n\t\t}\n\t\tconst first = parsed[0] as { name?: unknown; arguments?: unknown };\n\t\tif (\n\t\t\ttypeof first !== \"object\" ||\n\t\t\tfirst === null ||\n\t\t\ttypeof first.name !== \"string\" ||\n\t\t\ttypeof first.arguments !== \"object\" ||\n\t\t\tfirst.arguments === null\n\t\t) {\n\t\t\treturn { ok: false, error: \"unparseable-output: tool call missing name/arguments\", rawOutput };\n\t\t}\n\t\treturn { ok: true, call: { name: first.name, arguments: first.arguments as Record<string, unknown> } };\n\t}\n\n\t/** Canned get_weather query/tool through {@link runFunctionCall}; the wiring task uses this as the\n\t * post-install verification step. */\n\tasync smokeTest(onProgress?: (status: string) => void): Promise<NeedleSmokeTestResult> {\n\t\tonProgress?.(\"Running needle smoke test…\");\n\t\tconst startedAt = Date.now();\n\t\tconst result = await this.runFunctionCall({ query: NEEDLE_SMOKE_QUERY, tools: NEEDLE_SMOKE_TOOLS });\n\t\tconst latencyMs = Date.now() - startedAt;\n\t\tif (!result.ok) {\n\t\t\tonProgress?.(`Smoke test failed: ${result.error}`);\n\t\t\treturn { ok: false, latencyMs, error: result.error };\n\t\t}\n\t\tonProgress?.(`Smoke test ok (${latencyMs}ms): ${result.call.name}`);\n\t\treturn { ok: true, latencyMs, call: result.call };\n\t}\n\n\t/**\n\t * No-op: every needle invocation here is a single-shot `runCommand` call that spawns, waits for\n\t * exit, and returns — there is no persistent/detached child process this runtime owns between\n\t * calls for dispose() to track or kill (contrast OllamaRuntime/PrismLlamaCppRuntime's long-lived\n\t * `serve()` child).\n\t */\n\tdispose(): void {}\n}\n"]}