@mjasnikovs/pi-task 0.38.29 → 0.38.30

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 (370) hide show
  1. package/dist/config/config.d.ts +70 -70
  2. package/dist/config/config.js +26 -35
  3. package/dist/config/extension-list.d.ts +6 -5
  4. package/dist/config/extension-list.js +3 -2
  5. package/dist/config/reasoning-args.d.ts +9 -7
  6. package/dist/config/reasoning-args.js +12 -10
  7. package/dist/config/reasoning.d.ts +44 -105
  8. package/dist/config/reasoning.js +27 -704
  9. package/dist/config/register.d.ts +34 -48
  10. package/dist/config/register.js +41 -51
  11. package/dist/config/tool-list.d.ts +16 -16
  12. package/dist/config/tool-list.js +1 -1
  13. package/dist/remote/bridge.d.ts +19 -10
  14. package/dist/remote/bridge.js +3 -2
  15. package/dist/remote/broadcast.js +3 -1
  16. package/dist/remote/events.js +12 -11
  17. package/dist/remote/history.d.ts +1 -1
  18. package/dist/remote/protocol.d.ts +6 -3
  19. package/dist/remote/protocol.js +2 -1
  20. package/dist/remote/push.d.ts +16 -16
  21. package/dist/remote/push.js +27 -27
  22. package/dist/remote/register.d.ts +3 -3
  23. package/dist/remote/register.js +17 -19
  24. package/dist/remote/server.d.ts +9 -8
  25. package/dist/remote/server.js +15 -14
  26. package/dist/remote/session-state.d.ts +5 -4
  27. package/dist/remote/session-state.js +8 -5
  28. package/dist/remote/sw.d.ts +7 -6
  29. package/dist/remote/sw.js +7 -6
  30. package/dist/remote/tailscale.d.ts +4 -2
  31. package/dist/remote/tailscale.js +4 -2
  32. package/dist/remote/ui-highlight.js +6 -5
  33. package/dist/remote/ui-render.js +4 -4
  34. package/dist/remote/ui-script.js +24 -24
  35. package/dist/remote/ui-styles.d.ts +1 -1
  36. package/dist/remote/ui-styles.js +10 -13
  37. package/dist/remote/ui-tools.js +9 -6
  38. package/dist/shared/child-extensions.d.ts +29 -17
  39. package/dist/shared/child-extensions.js +29 -17
  40. package/dist/shared/child-output.d.ts +30 -24
  41. package/dist/shared/child-output.js +25 -17
  42. package/dist/shared/child-process.d.ts +47 -40
  43. package/dist/shared/child-process.js +50 -59
  44. package/dist/shared/command-watchdog.d.ts +22 -16
  45. package/dist/shared/command-watchdog.js +28 -21
  46. package/dist/shared/fs-text.d.ts +16 -10
  47. package/dist/shared/fs-text.js +16 -10
  48. package/dist/shared/git-runner.d.ts +25 -25
  49. package/dist/shared/git-runner.js +25 -25
  50. package/dist/shared/leaked-tool-call.d.ts +17 -11
  51. package/dist/shared/leaked-tool-call.js +23 -15
  52. package/dist/shared/model-endpoint.d.ts +29 -16
  53. package/dist/shared/model-endpoint.js +33 -21
  54. package/dist/shared/pi-invocation.d.ts +7 -4
  55. package/dist/shared/pi-invocation.js +12 -7
  56. package/dist/shared/pkg-version.d.ts +13 -5
  57. package/dist/shared/pkg-version.js +13 -5
  58. package/dist/shared/reasoning-capability.d.ts +35 -24
  59. package/dist/shared/reasoning-capability.js +35 -24
  60. package/dist/shared/stream-watchdog.d.ts +60 -44
  61. package/dist/shared/stream-watchdog.js +62 -45
  62. package/dist/task/accept-debt.d.ts +41 -43
  63. package/dist/task/accept-debt.js +73 -65
  64. package/dist/task/api-synthesis.d.ts +24 -21
  65. package/dist/task/api-synthesis.js +32 -26
  66. package/dist/task/apis-contract.d.ts +32 -64
  67. package/dist/task/apis-contract.js +32 -64
  68. package/dist/task/artifact-closure.d.ts +27 -13
  69. package/dist/task/artifact-closure.js +95 -67
  70. package/dist/task/auto-commit.d.ts +46 -35
  71. package/dist/task/auto-commit.js +51 -38
  72. package/dist/task/auto-io.d.ts +45 -25
  73. package/dist/task/auto-io.js +57 -29
  74. package/dist/task/auto-orchestrator.d.ts +26 -24
  75. package/dist/task/auto-orchestrator.js +178 -162
  76. package/dist/task/auto-prompts.d.ts +36 -24
  77. package/dist/task/auto-prompts.js +40 -26
  78. package/dist/task/autofix-ledger.d.ts +27 -25
  79. package/dist/task/autofix-ledger.js +29 -26
  80. package/dist/task/batch-test-task.d.ts +20 -12
  81. package/dist/task/batch-test-task.js +67 -60
  82. package/dist/task/boot-probe.d.ts +60 -44
  83. package/dist/task/boot-probe.js +91 -72
  84. package/dist/task/cancel-input.d.ts +30 -16
  85. package/dist/task/cancel-input.js +20 -11
  86. package/dist/task/cancel-points.d.ts +27 -20
  87. package/dist/task/cancel-points.js +30 -22
  88. package/dist/task/child-runner.d.ts +46 -51
  89. package/dist/task/child-runner.js +48 -49
  90. package/dist/task/child-status.d.ts +23 -16
  91. package/dist/task/child-status.js +23 -16
  92. package/dist/task/clamp-output.js +12 -5
  93. package/dist/task/command-run.d.ts +31 -28
  94. package/dist/task/command-run.js +44 -35
  95. package/dist/task/command-shrink.d.ts +25 -18
  96. package/dist/task/command-shrink.js +37 -31
  97. package/dist/task/command-watchdog.d.ts +9 -6
  98. package/dist/task/command-watchdog.js +21 -15
  99. package/dist/task/context-attribution.d.ts +34 -26
  100. package/dist/task/context-attribution.js +34 -26
  101. package/dist/task/context-silence.d.ts +39 -29
  102. package/dist/task/context-silence.js +35 -25
  103. package/dist/task/context-usage.d.ts +16 -9
  104. package/dist/task/context-usage.js +16 -9
  105. package/dist/task/contracts.d.ts +8 -4
  106. package/dist/task/contracts.js +25 -17
  107. package/dist/task/coverage-loop.d.ts +22 -18
  108. package/dist/task/coverage-loop.js +35 -30
  109. package/dist/task/critique-probes.d.ts +13 -14
  110. package/dist/task/critique-probes.js +50 -39
  111. package/dist/task/debug-log.d.ts +13 -5
  112. package/dist/task/debug-log.js +32 -20
  113. package/dist/task/decompose-fidelity.d.ts +11 -9
  114. package/dist/task/decompose-fidelity.js +38 -33
  115. package/dist/task/decompose-granularity.d.ts +41 -38
  116. package/dist/task/decompose-granularity.js +41 -38
  117. package/dist/task/deep-render-check.d.ts +22 -14
  118. package/dist/task/deep-render-check.js +40 -31
  119. package/dist/task/dropped-input.d.ts +12 -7
  120. package/dist/task/dropped-input.js +5 -2
  121. package/dist/task/enforce-attribution.d.ts +38 -47
  122. package/dist/task/enforce-attribution.js +46 -52
  123. package/dist/task/enforce-guidelines.d.ts +31 -20
  124. package/dist/task/enforce-guidelines.js +32 -21
  125. package/dist/task/enrichment.d.ts +7 -2
  126. package/dist/task/enrichment.js +26 -14
  127. package/dist/task/env-notes.d.ts +16 -7
  128. package/dist/task/env-notes.js +48 -31
  129. package/dist/task/env-template-closure.d.ts +4 -4
  130. package/dist/task/env-template-closure.js +42 -34
  131. package/dist/task/external-context.d.ts +28 -21
  132. package/dist/task/external-context.js +17 -12
  133. package/dist/task/failure-classifier.d.ts +4 -5
  134. package/dist/task/failure-classifier.js +6 -7
  135. package/dist/task/file-inventory.d.ts +15 -11
  136. package/dist/task/file-inventory.js +25 -22
  137. package/dist/task/final-gate-fix.d.ts +74 -86
  138. package/dist/task/final-gate-fix.js +97 -116
  139. package/dist/task/final-gate-progress.d.ts +29 -46
  140. package/dist/task/final-gate-progress.js +40 -51
  141. package/dist/task/final-gate.d.ts +64 -97
  142. package/dist/task/final-gate.js +192 -199
  143. package/dist/task/fix-child.d.ts +21 -27
  144. package/dist/task/fix-child.js +21 -27
  145. package/dist/task/foreign-path.d.ts +6 -5
  146. package/dist/task/foreign-path.js +0 -0
  147. package/dist/task/frozen-conflict.d.ts +9 -10
  148. package/dist/task/frozen-conflict.js +61 -64
  149. package/dist/task/frozen-path-guard.d.ts +35 -14
  150. package/dist/task/frozen-path-guard.js +56 -39
  151. package/dist/task/gate-child.d.ts +27 -28
  152. package/dist/task/gate-child.js +36 -35
  153. package/dist/task/gate-deps.d.ts +34 -27
  154. package/dist/task/gate-deps.js +169 -159
  155. package/dist/task/gate-tally.d.ts +77 -80
  156. package/dist/task/gate-tally.js +65 -68
  157. package/dist/task/git-state-guard.d.ts +15 -11
  158. package/dist/task/git-state-guard.js +76 -66
  159. package/dist/task/impl-widget.d.ts +25 -16
  160. package/dist/task/impl-widget.js +27 -17
  161. package/dist/task/implementation-thinking.d.ts +33 -31
  162. package/dist/task/implementation-thinking.js +5 -6
  163. package/dist/task/implementation-turn.d.ts +34 -31
  164. package/dist/task/implementation-turn.js +29 -27
  165. package/dist/task/inline-markdown.d.ts +20 -7
  166. package/dist/task/inline-markdown.js +15 -6
  167. package/dist/task/launch-config-gap.js +25 -39
  168. package/dist/task/launch-contract.d.ts +18 -21
  169. package/dist/task/launch-contract.js +28 -30
  170. package/dist/task/launch-manifest.d.ts +6 -2
  171. package/dist/task/launch-manifest.js +35 -34
  172. package/dist/task/ledger.js +16 -14
  173. package/dist/task/lint-fix.d.ts +6 -8
  174. package/dist/task/lint-fix.js +67 -69
  175. package/dist/task/loop-detector.d.ts +9 -8
  176. package/dist/task/loop-detector.js +16 -12
  177. package/dist/task/mid-run-input.d.ts +17 -15
  178. package/dist/task/mid-run-input.js +17 -15
  179. package/dist/task/orchestrator.d.ts +24 -28
  180. package/dist/task/orchestrator.js +62 -64
  181. package/dist/task/orientation.d.ts +18 -23
  182. package/dist/task/orientation.js +24 -31
  183. package/dist/task/owned-freeze-conflict.d.ts +21 -20
  184. package/dist/task/owned-freeze-conflict.js +52 -85
  185. package/dist/task/owned-freeze-reassign.d.ts +40 -60
  186. package/dist/task/owned-freeze-reassign.js +41 -61
  187. package/dist/task/parsers.d.ts +4 -2
  188. package/dist/task/parsers.js +4 -4
  189. package/dist/task/phases.d.ts +41 -48
  190. package/dist/task/phases.js +179 -248
  191. package/dist/task/plan-io.d.ts +6 -7
  192. package/dist/task/plan-io.js +6 -7
  193. package/dist/task/plan-orchestrator.d.ts +10 -8
  194. package/dist/task/plan-orchestrator.js +14 -10
  195. package/dist/task/plan-prompts.d.ts +6 -5
  196. package/dist/task/plan-prompts.js +6 -5
  197. package/dist/task/plan-readonly.d.ts +4 -5
  198. package/dist/task/plan-readonly.js +4 -5
  199. package/dist/task/plan-rounds.d.ts +17 -29
  200. package/dist/task/plan-rounds.js +21 -34
  201. package/dist/task/plan-session.d.ts +58 -72
  202. package/dist/task/plan-session.js +61 -83
  203. package/dist/task/probe-gaming.d.ts +28 -27
  204. package/dist/task/probe-gaming.js +0 -0
  205. package/dist/task/prohibition-probe.d.ts +14 -16
  206. package/dist/task/prompts.d.ts +3 -4
  207. package/dist/task/prompts.js +17 -26
  208. package/dist/task/qa-transcript.d.ts +15 -22
  209. package/dist/task/qa-transcript.js +15 -21
  210. package/dist/task/question-box.d.ts +17 -13
  211. package/dist/task/question-box.js +19 -15
  212. package/dist/task/question-dedup.d.ts +6 -7
  213. package/dist/task/question-dedup.js +13 -14
  214. package/dist/task/question-dialog.d.ts +22 -32
  215. package/dist/task/question-dialog.js +22 -32
  216. package/dist/task/question-source.d.ts +18 -44
  217. package/dist/task/question-source.js +22 -51
  218. package/dist/task/refuted-constraint.d.ts +11 -31
  219. package/dist/task/refuted-constraint.js +27 -51
  220. package/dist/task/regenerable-artifacts.d.ts +12 -31
  221. package/dist/task/regenerable-artifacts.js +12 -31
  222. package/dist/task/render-check.d.ts +11 -22
  223. package/dist/task/render-check.js +33 -46
  224. package/dist/task/repo-health-check.d.ts +10 -14
  225. package/dist/task/repo-health-check.js +17 -23
  226. package/dist/task/requirements.d.ts +38 -71
  227. package/dist/task/requirements.js +78 -126
  228. package/dist/task/research-fanout-budget.d.ts +51 -88
  229. package/dist/task/research-fanout-budget.js +51 -88
  230. package/dist/task/research-worker.d.ts +29 -39
  231. package/dist/task/research-worker.js +37 -61
  232. package/dist/task/resume-gap.d.ts +14 -15
  233. package/dist/task/root-cause-repair.d.ts +9 -9
  234. package/dist/task/root-cause-repair.js +28 -40
  235. package/dist/task/run-bracket.d.ts +10 -13
  236. package/dist/task/run-end.d.ts +12 -22
  237. package/dist/task/run-end.js +8 -16
  238. package/dist/task/run-final-gate.d.ts +19 -21
  239. package/dist/task/run-final-gate.js +62 -80
  240. package/dist/task/runner-globs.d.ts +12 -13
  241. package/dist/task/runner-globs.js +12 -13
  242. package/dist/task/runner-resolve.d.ts +9 -9
  243. package/dist/task/runner-resolve.js +22 -23
  244. package/dist/task/script-escape.d.ts +10 -12
  245. package/dist/task/script-escape.js +13 -14
  246. package/dist/task/serve-entry.d.ts +1 -1
  247. package/dist/task/serve-entry.js +22 -25
  248. package/dist/task/service-blocks.js +4 -2
  249. package/dist/task/shipped-source.d.ts +11 -29
  250. package/dist/task/shipped-source.js +11 -29
  251. package/dist/task/skip-escape.js +10 -14
  252. package/dist/task/spec-urls.d.ts +26 -65
  253. package/dist/task/spec-urls.js +26 -65
  254. package/dist/task/spec-validation.d.ts +17 -20
  255. package/dist/task/spec-validation.js +17 -20
  256. package/dist/task/stall-detector.d.ts +23 -30
  257. package/dist/task/stall-detector.js +23 -30
  258. package/dist/task/stream-watchdog.d.ts +14 -12
  259. package/dist/task/stream-watchdog.js +14 -12
  260. package/dist/task/substitution-probe.d.ts +17 -20
  261. package/dist/task/substitution-probe.js +17 -20
  262. package/dist/task/task-gates.d.ts +36 -41
  263. package/dist/task/task-gates.js +95 -106
  264. package/dist/task/task-io.d.ts +4 -4
  265. package/dist/task/task-io.js +4 -4
  266. package/dist/task/task-parsers.js +4 -3
  267. package/dist/task/task-provenance.d.ts +2 -2
  268. package/dist/task/task-provenance.js +11 -13
  269. package/dist/task/task-types.d.ts +4 -3
  270. package/dist/task/terminal-outcome.d.ts +14 -16
  271. package/dist/task/terminal-outcome.js +12 -14
  272. package/dist/task/test-assembly.d.ts +13 -20
  273. package/dist/task/test-assembly.js +13 -20
  274. package/dist/task/timings.d.ts +5 -3
  275. package/dist/task/timings.js +5 -3
  276. package/dist/task/title-label.d.ts +9 -4
  277. package/dist/task/title-label.js +9 -4
  278. package/dist/task/type-only-answer.d.ts +44 -52
  279. package/dist/task/type-only-answer.js +44 -52
  280. package/dist/task/unfailable-command.d.ts +18 -24
  281. package/dist/task/unfailable-command.js +21 -27
  282. package/dist/task/unknown-routing.d.ts +10 -4
  283. package/dist/task/unknown-routing.js +10 -4
  284. package/dist/task/user-directives.d.ts +5 -8
  285. package/dist/task/user-directives.js +5 -8
  286. package/dist/task/verify-quality.d.ts +18 -22
  287. package/dist/task/verify-quality.js +45 -46
  288. package/dist/task/verify-reconcile.d.ts +15 -10
  289. package/dist/task/verify-reconcile.js +45 -43
  290. package/dist/task/verify-resolution.d.ts +24 -20
  291. package/dist/task/verify-resolution.js +51 -50
  292. package/dist/task/verify-work.d.ts +59 -66
  293. package/dist/task/verify-work.js +101 -138
  294. package/dist/task/widget.d.ts +15 -14
  295. package/dist/task/widget.js +22 -17
  296. package/dist/task/wiring-claims.d.ts +25 -32
  297. package/dist/task/wiring-claims.js +30 -35
  298. package/dist/task/write-guard.d.ts +39 -39
  299. package/dist/task/write-guard.js +48 -51
  300. package/dist/task/yolo.d.ts +34 -30
  301. package/dist/task/yolo.js +42 -37
  302. package/dist/workers/abstention.d.ts +21 -41
  303. package/dist/workers/abstention.js +27 -48
  304. package/dist/workers/brave-search.d.ts +4 -3
  305. package/dist/workers/brave-search.js +5 -2
  306. package/dist/workers/brave-warning.d.ts +7 -4
  307. package/dist/workers/brave-warning.js +19 -7
  308. package/dist/workers/ddg-search.d.ts +6 -6
  309. package/dist/workers/ddg-search.js +18 -12
  310. package/dist/workers/docs-cache.js +5 -2
  311. package/dist/workers/docs-chunk.d.ts +30 -37
  312. package/dist/workers/docs-chunk.js +37 -41
  313. package/dist/workers/docs-core.d.ts +28 -44
  314. package/dist/workers/docs-core.js +25 -44
  315. package/dist/workers/docs-index.js +4 -3
  316. package/dist/workers/docs-lookup.d.ts +15 -22
  317. package/dist/workers/docs-lookup.js +12 -21
  318. package/dist/workers/docs-project.d.ts +15 -9
  319. package/dist/workers/docs-project.js +17 -10
  320. package/dist/workers/docs-resolve.d.ts +19 -20
  321. package/dist/workers/docs-resolve.js +35 -32
  322. package/dist/workers/docs-retrieve.d.ts +5 -6
  323. package/dist/workers/docs-retrieve.js +18 -15
  324. package/dist/workers/exa-search.d.ts +9 -6
  325. package/dist/workers/exa-search.js +23 -12
  326. package/dist/workers/fetch-core.d.ts +13 -16
  327. package/dist/workers/fetch-core.js +23 -23
  328. package/dist/workers/focused-extractor.d.ts +12 -12
  329. package/dist/workers/focused-extractor.js +16 -19
  330. package/dist/workers/html-clean.js +24 -14
  331. package/dist/workers/http-request.d.ts +28 -20
  332. package/dist/workers/http-request.js +22 -17
  333. package/dist/workers/npm-version.d.ts +28 -11
  334. package/dist/workers/npm-version.js +24 -15
  335. package/dist/workers/phantom-imports.d.ts +15 -12
  336. package/dist/workers/phantom-imports.js +30 -24
  337. package/dist/workers/pi-worker-core.d.ts +69 -71
  338. package/dist/workers/pi-worker-core.js +100 -109
  339. package/dist/workers/pi-worker-docs.d.ts +24 -19
  340. package/dist/workers/pi-worker-docs.js +67 -76
  341. package/dist/workers/pi-worker-fetch.d.ts +7 -3
  342. package/dist/workers/pi-worker-fetch.js +27 -19
  343. package/dist/workers/pi-worker-search.js +12 -8
  344. package/dist/workers/pi-worker.d.ts +9 -4
  345. package/dist/workers/pi-worker.js +21 -14
  346. package/dist/workers/reasoning-warning.d.ts +18 -17
  347. package/dist/workers/reasoning-warning.js +22 -20
  348. package/dist/workers/research-cache.js +50 -78
  349. package/dist/workers/search-core.js +7 -5
  350. package/dist/workers/search-types.d.ts +10 -9
  351. package/dist/workers/search-types.js +9 -8
  352. package/dist/workers/session-hint.d.ts +13 -14
  353. package/dist/workers/session-hint.js +8 -9
  354. package/dist/workers/shared.d.ts +21 -25
  355. package/dist/workers/shared.js +0 -0
  356. package/dist/workers/single-read-extension.d.ts +14 -7
  357. package/dist/workers/single-read-extension.js +14 -7
  358. package/dist/workers/single-read-guard.d.ts +25 -28
  359. package/dist/workers/single-read-guard.js +32 -32
  360. package/dist/workers/typeonly-log.d.ts +12 -9
  361. package/dist/workers/typeonly-log.js +29 -33
  362. package/dist/workers/worker-channels.d.ts +15 -23
  363. package/dist/workers/worker-channels.js +15 -23
  364. package/dist/workers/worker-failure.d.ts +38 -46
  365. package/dist/workers/worker-failure.js +31 -39
  366. package/dist/workers/worker-kill.d.ts +25 -26
  367. package/dist/workers/worker-kill.js +16 -19
  368. package/dist/workers/worker-profiles.d.ts +43 -53
  369. package/dist/workers/worker-profiles.js +30 -38
  370. package/package.json +10 -8
@@ -1,33 +1,33 @@
1
1
  /**
2
- * git-runner — the ONE way this codebase runs `git`.
3
- *
4
- * WHY IT EXISTS. The right shape already existed, privately, inside
5
- * `task/git-state-guard.ts`: an abort-aware async runner carrying an injectable
6
- * `spawnFn` seam, returning `{stdout, exitCode}` and never throwing. Nothing
7
- * outside that file could reach it, so ~27 other sites hand-rolled their own —
8
- * with FOUR incompatible failure contracts (throws / returns null / returns '' /
9
- * returns a result object) and THREE different maxBuffer limits. A caller reading
10
- * one of them learns nothing about the next, and a git failure means something
11
- * different at every site. Lifting the seam here does not add a capability; it
12
- * removes the ambiguity about which contract you are holding.
2
+ * git-runner — the shared async way to run `git`, and the one to reach for.
13
3
  *
14
4
  * THE CONTRACT, deliberately narrow:
15
- * - NEVER THROWS. A missing git, a non-repo cwd, a fatal subcommand — all of
16
- * them arrive as a non-zero `exitCode`. Callers branch on the number, they do
17
- * not wrap in try/catch. (`git rev-parse --verify HEAD` on an unborn HEAD is
18
- * an ordinary answer, not an exception, and every guard built on this one
19
- * treats "git could not tell me" as "no claim" rather than a crash.)
5
+ * - NEVER THROWS. Every failure arrives as a non-zero `exitCode`, measured
6
+ * rather than assumed: a non-repo cwd answers 128, a bogus subcommand
7
+ * answers 1, `git rev-parse --verify HEAD` on a freshly-init'd repo with an
8
+ * unborn HEAD answers 128, and even a MISSING binary comes back as
9
+ * `{exitCode: 1, aborted: false}` instead of rejecting. Callers branch on
10
+ * the number, they do not wrap in try/catch, and every guard built on this
11
+ * treats "git could not tell me" as "no claim" rather than a crash.
20
12
  * - Only `stdout` and `exitCode` are exposed. stderr is deliberately absent:
21
- * the callers that legitimately need it (auto-commit's identity-failure
22
- * sniffing) also need `aborted`, and widening this type for them would push
23
- * two fields nobody else reads onto every call site.
13
+ * the caller that legitimately needs it (auto-commit's identity-failure
14
+ * sniffing) also needs `aborted`, and widening this type for it would push
15
+ * two fields nobody else reads onto every call site. That one caller goes
16
+ * to `runChildDefault` directly instead.
24
17
  * - `signal` is honoured by the underlying runChild, including its listener
25
- * detach discipline (GitHub issue #9) — a run-long orchestrator signal does
26
- * not accumulate one listener per git invocation.
27
- * - `env` entries are MERGED over `process.env` (the GIT_INDEX_FILE
28
- * throwaway-index pattern), not substituted for it.
29
- * - `spawnFn` is the test seam: pass a fake from `test-utils/fake-spawn.ts` and
30
- * the runner never touches a real repo.
18
+ * detach discipline — a run-long orchestrator signal does not accumulate
19
+ * one listener per git invocation.
20
+ * - `env` entries are MERGED over `process.env`, not substituted for it, which
21
+ * is what lets git-state-guard point `GIT_INDEX_FILE` at a throwaway index
22
+ * without losing PATH and HOME. Confirmed: such a call still resolves the
23
+ * repo normally.
24
+ * - `spawnFn` is the test seam: pass a fake from `test/test-utils/fake-spawn.ts`
25
+ * and the runner never touches a real repo.
26
+ *
27
+ * It is NOT universal, which matters before assuming any git call arrived here.
28
+ * Several sites still invoke git themselves with `spawnSync`, each carrying its
29
+ * own timeout and one its own maxBuffer, and accept-debt goes through the
30
+ * bounded command runner instead.
31
31
  */
32
32
  import { type SpawnFn } from './child-process.js';
33
33
  export interface GitRunner {
@@ -1,33 +1,33 @@
1
1
  /**
2
- * git-runner — the ONE way this codebase runs `git`.
3
- *
4
- * WHY IT EXISTS. The right shape already existed, privately, inside
5
- * `task/git-state-guard.ts`: an abort-aware async runner carrying an injectable
6
- * `spawnFn` seam, returning `{stdout, exitCode}` and never throwing. Nothing
7
- * outside that file could reach it, so ~27 other sites hand-rolled their own —
8
- * with FOUR incompatible failure contracts (throws / returns null / returns '' /
9
- * returns a result object) and THREE different maxBuffer limits. A caller reading
10
- * one of them learns nothing about the next, and a git failure means something
11
- * different at every site. Lifting the seam here does not add a capability; it
12
- * removes the ambiguity about which contract you are holding.
2
+ * git-runner — the shared async way to run `git`, and the one to reach for.
13
3
  *
14
4
  * THE CONTRACT, deliberately narrow:
15
- * - NEVER THROWS. A missing git, a non-repo cwd, a fatal subcommand — all of
16
- * them arrive as a non-zero `exitCode`. Callers branch on the number, they do
17
- * not wrap in try/catch. (`git rev-parse --verify HEAD` on an unborn HEAD is
18
- * an ordinary answer, not an exception, and every guard built on this one
19
- * treats "git could not tell me" as "no claim" rather than a crash.)
5
+ * - NEVER THROWS. Every failure arrives as a non-zero `exitCode`, measured
6
+ * rather than assumed: a non-repo cwd answers 128, a bogus subcommand
7
+ * answers 1, `git rev-parse --verify HEAD` on a freshly-init'd repo with an
8
+ * unborn HEAD answers 128, and even a MISSING binary comes back as
9
+ * `{exitCode: 1, aborted: false}` instead of rejecting. Callers branch on
10
+ * the number, they do not wrap in try/catch, and every guard built on this
11
+ * treats "git could not tell me" as "no claim" rather than a crash.
20
12
  * - Only `stdout` and `exitCode` are exposed. stderr is deliberately absent:
21
- * the callers that legitimately need it (auto-commit's identity-failure
22
- * sniffing) also need `aborted`, and widening this type for them would push
23
- * two fields nobody else reads onto every call site.
13
+ * the caller that legitimately needs it (auto-commit's identity-failure
14
+ * sniffing) also needs `aborted`, and widening this type for it would push
15
+ * two fields nobody else reads onto every call site. That one caller goes
16
+ * to `runChildDefault` directly instead.
24
17
  * - `signal` is honoured by the underlying runChild, including its listener
25
- * detach discipline (GitHub issue #9) — a run-long orchestrator signal does
26
- * not accumulate one listener per git invocation.
27
- * - `env` entries are MERGED over `process.env` (the GIT_INDEX_FILE
28
- * throwaway-index pattern), not substituted for it.
29
- * - `spawnFn` is the test seam: pass a fake from `test-utils/fake-spawn.ts` and
30
- * the runner never touches a real repo.
18
+ * detach discipline — a run-long orchestrator signal does not accumulate
19
+ * one listener per git invocation.
20
+ * - `env` entries are MERGED over `process.env`, not substituted for it, which
21
+ * is what lets git-state-guard point `GIT_INDEX_FILE` at a throwaway index
22
+ * without losing PATH and HOME. Confirmed: such a call still resolves the
23
+ * repo normally.
24
+ * - `spawnFn` is the test seam: pass a fake from `test/test-utils/fake-spawn.ts`
25
+ * and the runner never touches a real repo.
26
+ *
27
+ * It is NOT universal, which matters before assuming any git call arrived here.
28
+ * Several sites still invoke git themselves with `spawnSync`, each carrying its
29
+ * own timeout and one its own maxBuffer, and accept-debt goes through the
30
+ * bounded command runner instead.
31
31
  */
32
32
  import { runChildDefault } from './child-process.js';
33
33
  export function makeGit(cwd, signal, spawnFn) {
@@ -2,10 +2,9 @@
2
2
  * Detect tool calls that leaked into a child's assistant *text* instead of
3
3
  * being executed.
4
4
  *
5
- * Background: every child pi runs under `--mode json`; pi-task only ever treats
6
- * a structured `tool_execution_start` event as a tool call (see
7
- * shared/child-process.ts). When a local model emits a call in a markup dialect
8
- * pi's harness doesn't recognise — e.g.
5
+ * A call only counts when a structured `tool_execution_start` event fires
6
+ * shared/child-process.ts recognises nothing else. Model-side tool-call markup
7
+ * like
9
8
  *
10
9
  * <tool_call>
11
10
  * <function=bash>
@@ -13,14 +12,21 @@
13
12
  * </function>
14
13
  * </tool_call>
15
14
  *
16
- * pi passes the raw markup through as ordinary assistant text. The command never
17
- * runs, no event fires, and pi-task's guards (loop detector, widget) never see
18
- * it. The phase then "passes" on its only gates (non-empty text + exit 0) and
19
- * the unexecuted call flows downstream — a silently skipped beat.
15
+ * is not a format pi itself parses: those tags appear nowhere in any installed
16
+ * pi package. So whether such a turn RUNS is decided entirely by the inference
17
+ * server in front of it, and both outcomes are real.
20
18
  *
21
- * This is fundamentally an upstream mismatch (model output format pi's parser)
22
- * that pi-task cannot fix. What it CAN do is notice the leaked markup and refuse
23
- * to accept the turn, so the skip becomes visible instead of silent.
19
+ * Checked against a live endpoint, the server parsed the dialect itself and
20
+ * handed pi structured tool calls the markup EXECUTED rather than leaking, and
21
+ * the parse was sloppy enough to fold the closing tags and the surrounding prose
22
+ * into the command argument. A server that does not parse it produces the case
23
+ * this module exists for: pi receives the markup as ordinary assistant text, the
24
+ * command never runs, no event fires, and pi-task's guards never see it. The
25
+ * turn then clears its only acceptance gates — non-empty assistant text and exit
26
+ * 0 — and the unexecuted call flows downstream as a silently skipped beat.
27
+ *
28
+ * pi-task cannot fix the mismatch. What it CAN do is notice the markup in the
29
+ * answer and refuse the turn, so the skip becomes visible instead of silent.
24
30
  */
25
31
  export declare const MAX_LEAK_RETRIES = 2;
26
32
  /**
@@ -2,10 +2,9 @@
2
2
  * Detect tool calls that leaked into a child's assistant *text* instead of
3
3
  * being executed.
4
4
  *
5
- * Background: every child pi runs under `--mode json`; pi-task only ever treats
6
- * a structured `tool_execution_start` event as a tool call (see
7
- * shared/child-process.ts). When a local model emits a call in a markup dialect
8
- * pi's harness doesn't recognise — e.g.
5
+ * A call only counts when a structured `tool_execution_start` event fires
6
+ * shared/child-process.ts recognises nothing else. Model-side tool-call markup
7
+ * like
9
8
  *
10
9
  * <tool_call>
11
10
  * <function=bash>
@@ -13,25 +12,34 @@
13
12
  * </function>
14
13
  * </tool_call>
15
14
  *
16
- * pi passes the raw markup through as ordinary assistant text. The command never
17
- * runs, no event fires, and pi-task's guards (loop detector, widget) never see
18
- * it. The phase then "passes" on its only gates (non-empty text + exit 0) and
19
- * the unexecuted call flows downstream — a silently skipped beat.
15
+ * is not a format pi itself parses: those tags appear nowhere in any installed
16
+ * pi package. So whether such a turn RUNS is decided entirely by the inference
17
+ * server in front of it, and both outcomes are real.
20
18
  *
21
- * This is fundamentally an upstream mismatch (model output format pi's parser)
22
- * that pi-task cannot fix. What it CAN do is notice the leaked markup and refuse
23
- * to accept the turn, so the skip becomes visible instead of silent.
19
+ * Checked against a live endpoint, the server parsed the dialect itself and
20
+ * handed pi structured tool calls the markup EXECUTED rather than leaking, and
21
+ * the parse was sloppy enough to fold the closing tags and the surrounding prose
22
+ * into the command argument. A server that does not parse it produces the case
23
+ * this module exists for: pi receives the markup as ordinary assistant text, the
24
+ * command never runs, no event fires, and pi-task's guards never see it. The
25
+ * turn then clears its only acceptance gates — non-empty assistant text and exit
26
+ * 0 — and the unexecuted call flows downstream as a silently skipped beat.
27
+ *
28
+ * pi-task cannot fix the mismatch. What it CAN do is notice the markup in the
29
+ * answer and refuse the turn, so the skip becomes visible instead of silent.
24
30
  */
25
31
  // A child that wrote a tool call as plain text (wrong dialect, never executed)
26
32
  // gets re-prompted with a correction hint up to this many times before the
27
33
  // caller gives up. Mirrors MAX_LOOP_RESTARTS: 3 attempts total.
28
34
  export const MAX_LEAK_RETRIES = 2;
29
- // The Hermes-style wrapper a leaked call is most often wrapped in. pi-task never
30
- // legitimately emits this tag, so its presence alone is a confident signal.
35
+ // The Hermes-style wrapper. Nothing else in this codebase emits the tag its
36
+ // only other appearance is the correction hint below, which names it back to the
37
+ // model — so seeing it in an answer is signal enough on its own.
31
38
  const TOOL_CALL_WRAPPER = /<tool_call\b[^>]*>/i;
32
39
  // The "XML function call" dialect: <function=name> … <parameter=key>. Either tag
33
- // alone is too weak (a stray "<function=x>" can appear in prose or source), so we
34
- // require the structural pair before flagging it.
40
+ // alone is too weak one can appear in prose or in source so the structural
41
+ // PAIR is required. Confirmed: a lone <function=bash> and a lone
42
+ // <parameter=command> each return null, and only the two together flag.
35
43
  const FUNCTION_TAG = /<function=[\w.-]+\s*>/i;
36
44
  const PARAMETER_TAG = /<parameter=[\w.-]+\s*>/i;
37
45
  /**
@@ -1,21 +1,26 @@
1
1
  /** Base URLs of every custom provider pi is configured with (possibly none). */
2
2
  export declare function discoverModelEndpoints(agentDir?: string): string[];
3
3
  /**
4
- * true → at least one endpoint ANSWERED (any HTTP status counts a 404 still
5
- * proves the server is alive); false every probe was refused or hung past the
6
- * timeout. An empty list is true: nothing to probe means never kill.
4
+ * true → at least one endpoint ANSWERED. Any HTTP status counts, because the
5
+ * question is liveness, not correctness: probing a path that 404s still returns
6
+ * true, while a closed port returns false. Both confirmed against a live server.
7
+ * An empty list is true — nothing to probe means never kill.
8
+ *
9
+ * `models` is joined RELATIVELY here, and deliberately: it lives under the
10
+ * OpenAI-compatible prefix a baseUrl already carries. Contrast `/props` below.
7
11
  */
8
12
  export declare function probeModelEndpoints(urls: string[], timeoutMs?: number): Promise<boolean>;
9
13
  /**
10
14
  * What a llama.cpp server's own chat template can actually do about reasoning,
11
15
  * as reported by `GET /props`.
12
16
  *
13
- * This is the only source of truth that does NOT come from models.json. It
14
- * answers the one question the host-side clamp cannot: *is models.json lying
15
- * about the server?* the case that matters being pi's built-in llama.cpp
16
- * provider, which hardcodes `reasoning: false`, so anyone who reached their
17
- * server through `/login llama.cpp` rather than a hand-written provider entry
18
- * has a dead knob and nothing to tell them so.
17
+ * This is the only source of truth that does NOT come from models.json, and it
18
+ * answers the question the host-side clamp cannot: is models.json lying about
19
+ * the server? The case that matters is pi's built-in llama.cpp provider, which
20
+ * really does hardcode `reasoning: false` the literal line is in
21
+ * `extensions/llama/provider.js`, under `LLAMA_PROVIDER_ID = "llama.cpp"`. So
22
+ * anyone who reached their server through `/login llama.cpp` rather than a
23
+ * hand-written provider entry has a dead knob and nothing to tell them so.
19
24
  */
20
25
  export interface ChatTemplateCaps {
21
26
  /** The template reads `reasoning_effort` — i.e. levels, not just on/off. */
@@ -30,13 +35,21 @@ export interface ChatTemplateCaps {
30
35
  * answer worth having" — not a llama.cpp server, unreachable, or a body in a
31
36
  * shape this does not recognise.
32
37
  *
33
- * `null` is a first-class result, not an error: every non-llama.cpp backend
34
- * returns it, and the caller must degrade to the models.json view rather than
35
- * warn about a server it could not read. Never throws.
38
+ * `null` is a first-class result, not an error: a backend that does not serve
39
+ * this shape returns it, and the caller must degrade to the models.json view
40
+ * rather than warn about a server it could not read. Never throws — a closed
41
+ * port answers `null`, confirmed.
42
+ *
43
+ * The LEADING SLASH in `/props` is load-bearing, because `/props` lives at the
44
+ * server ROOT while a configured baseUrl carries an OpenAI-compatible prefix.
45
+ * How a relative `'props'` resolves depends on whether that prefix ends in a
46
+ * slash, which is not something this can rely on:
47
+ *
48
+ * new URL('props', 'http://host/v1') → /props
49
+ * new URL('props', 'http://host/v1/') → /v1/props ← 404
50
+ * new URL('/props', either) → /props
36
51
  *
37
- * The LEADING SLASH in `/props` is load-bearing. A configured baseUrl normally
38
- * ends in `/v1` (llama-server's OpenAI-compatible prefix) while `/props` lives at
39
- * the server root, so a relative `'props'` would resolve to `/v1/props` and 404 —
40
- * which this would report as `null`, i.e. as a silent loss of the better signal.
52
+ * A 404 would come back from here as `null`, silently losing the better signal.
53
+ * The absolute form is right for both.
41
54
  */
42
55
  export declare function probeChatTemplateCaps(baseUrl: string, timeoutMs?: number): Promise<ChatTemplateCaps | null>;
@@ -2,18 +2,18 @@
2
2
  * model-endpoint — discovery + reachability probe for the model backend(s) a
3
3
  * child pi process talks to.
4
4
  *
5
- * The failure this serves (mx5 run 7, validated): the model server went down
6
- * mid-gate-child and the child hung MUTE for 64 minutes pi's own
7
- * connection-error handling only fires when a request FAILS, not when the
8
- * backend freezes and the open request simply never answers. The stall guard in
9
- * runChild uses this module to tell the two apart: no output could be honest
10
- * long work (prompt processing emits nothing for minutes), so only "no output
11
- * AND the endpoint does not answer" is treated as a dead backend.
5
+ * The failure this serves: the model server dies mid-child and the child hangs
6
+ * mute. pi's own connection-error handling cannot help, because it runs from a
7
+ * catch a request that FAILS reaches it, a request that simply never answers
8
+ * does not. The stall guard in runChild uses this module to tell the two apart:
9
+ * silence alone could be honest long work, since prompt processing emits nothing
10
+ * while it runs, so only "no output AND the endpoint does not answer" counts as
11
+ * a dead backend.
12
12
  *
13
- * Discovery is generic: the custom providers pi itself is configured with
14
- * (models.json `providers.*.baseUrl`), no provider or server names hardcoded.
15
- * No discoverable endpoint nothing to probe the guard NEVER kills (a child
16
- * on a backend we cannot see must get the benefit of the doubt).
13
+ * Discovery is generic the custom providers pi is configured with, read from
14
+ * models.json `providers.*.baseUrl`, with no provider or server name hardcoded.
15
+ * No discoverable endpoint means nothing to probe, and the guard then NEVER
16
+ * kills: a child on a backend we cannot see gets the benefit of the doubt.
17
17
  */
18
18
  import * as fs from 'node:fs';
19
19
  import * as os from 'node:os';
@@ -34,9 +34,13 @@ export function discoverModelEndpoints(agentDir = path.join(os.homedir(), '.pi',
34
34
  }
35
35
  }
36
36
  /**
37
- * true → at least one endpoint ANSWERED (any HTTP status counts a 404 still
38
- * proves the server is alive); false every probe was refused or hung past the
39
- * timeout. An empty list is true: nothing to probe means never kill.
37
+ * true → at least one endpoint ANSWERED. Any HTTP status counts, because the
38
+ * question is liveness, not correctness: probing a path that 404s still returns
39
+ * true, while a closed port returns false. Both confirmed against a live server.
40
+ * An empty list is true — nothing to probe means never kill.
41
+ *
42
+ * `models` is joined RELATIVELY here, and deliberately: it lives under the
43
+ * OpenAI-compatible prefix a baseUrl already carries. Contrast `/props` below.
40
44
  */
41
45
  export async function probeModelEndpoints(urls, timeoutMs = 5_000) {
42
46
  if (urls.length === 0)
@@ -59,14 +63,22 @@ export async function probeModelEndpoints(urls, timeoutMs = 5_000) {
59
63
  * answer worth having" — not a llama.cpp server, unreachable, or a body in a
60
64
  * shape this does not recognise.
61
65
  *
62
- * `null` is a first-class result, not an error: every non-llama.cpp backend
63
- * returns it, and the caller must degrade to the models.json view rather than
64
- * warn about a server it could not read. Never throws.
66
+ * `null` is a first-class result, not an error: a backend that does not serve
67
+ * this shape returns it, and the caller must degrade to the models.json view
68
+ * rather than warn about a server it could not read. Never throws — a closed
69
+ * port answers `null`, confirmed.
70
+ *
71
+ * The LEADING SLASH in `/props` is load-bearing, because `/props` lives at the
72
+ * server ROOT while a configured baseUrl carries an OpenAI-compatible prefix.
73
+ * How a relative `'props'` resolves depends on whether that prefix ends in a
74
+ * slash, which is not something this can rely on:
75
+ *
76
+ * new URL('props', 'http://host/v1') → /props
77
+ * new URL('props', 'http://host/v1/') → /v1/props ← 404
78
+ * new URL('/props', either) → /props
65
79
  *
66
- * The LEADING SLASH in `/props` is load-bearing. A configured baseUrl normally
67
- * ends in `/v1` (llama-server's OpenAI-compatible prefix) while `/props` lives at
68
- * the server root, so a relative `'props'` would resolve to `/v1/props` and 404 —
69
- * which this would report as `null`, i.e. as a silent loss of the better signal.
80
+ * A 404 would come back from here as `null`, silently losing the better signal.
81
+ * The absolute form is right for both.
70
82
  */
71
83
  export async function probeChatTemplateCaps(baseUrl, timeoutMs = 2_000) {
72
84
  try {
@@ -1,11 +1,14 @@
1
1
  /** Pick the right way to re-invoke pi: prefer the current pi script under the
2
- * current node/bun runtime; fall back to the `pi` shim on PATH. Mirrors the
3
- * pattern from pi-coding-agent's official subagent example.
2
+ * current node/bun runtime; fall back to the `pi` shim on PATH. This mirrors
3
+ * pi's own subagent example, which ships a function of the same name and the
4
+ * same three-step shape in `examples/extensions/subagent/index.ts`. What is
5
+ * added here is the `PI_BIN` override, the `/$bunfs/root/` guard, and `stdin`.
4
6
  *
5
7
  * `stdin`, when given, is the prompt to feed the child over stdin instead of as
6
8
  * an argv element — pi reads its prompt from stdin when no positional message
7
- * is passed, which keeps a large prompt off the length-limited command line
8
- * (see runChild / GitHub issue #1). It is threaded through unchanged. */
9
+ * is passed, which keeps a large prompt off the argv ceiling that would
10
+ * otherwise fail the spawn outright (see runChild). It is threaded through
11
+ * unchanged: every branch below returns it as given. */
9
12
  export declare function getPiInvocation(args: string[], stdin?: string): {
10
13
  command: string;
11
14
  args: string[];
@@ -1,17 +1,22 @@
1
1
  import * as fs from 'node:fs';
2
2
  import * as path from 'node:path';
3
3
  /** Pick the right way to re-invoke pi: prefer the current pi script under the
4
- * current node/bun runtime; fall back to the `pi` shim on PATH. Mirrors the
5
- * pattern from pi-coding-agent's official subagent example.
4
+ * current node/bun runtime; fall back to the `pi` shim on PATH. This mirrors
5
+ * pi's own subagent example, which ships a function of the same name and the
6
+ * same three-step shape in `examples/extensions/subagent/index.ts`. What is
7
+ * added here is the `PI_BIN` override, the `/$bunfs/root/` guard, and `stdin`.
6
8
  *
7
9
  * `stdin`, when given, is the prompt to feed the child over stdin instead of as
8
10
  * an argv element — pi reads its prompt from stdin when no positional message
9
- * is passed, which keeps a large prompt off the length-limited command line
10
- * (see runChild / GitHub issue #1). It is threaded through unchanged. */
11
+ * is passed, which keeps a large prompt off the argv ceiling that would
12
+ * otherwise fail the spawn outright (see runChild). It is threaded through
13
+ * unchanged: every branch below returns it as given. */
11
14
  export function getPiInvocation(args, stdin) {
12
- // Test/dev override: point at a specific pi binary directly. Bypasses the
13
- // re-invoke-current-script heuristic, which goes wrong under `bun test`
14
- // (currentScript is the .test.ts file, not a pi entrypoint).
15
+ // Test/dev override: point at a specific pi binary directly. It bypasses the
16
+ // re-invoke-current-script heuristic below, which goes wrong under the test
17
+ // runner confirmed by printing it from inside a test, where `process.argv[1]`
18
+ // is the `.test.ts` file itself. Without this the child would be the runtime
19
+ // re-running a test file, not pi.
15
20
  if (process.env.PI_BIN) {
16
21
  return { command: process.env.PI_BIN, args, stdin };
17
22
  }
@@ -1,8 +1,16 @@
1
1
  /**
2
- * The installed pi-task version, read from package.json at runtime so nothing
3
- * has to be regenerated on release. Two levels up holds for both src/<dir>
4
- * (tests) and dist/<dir> (build), since tsc preserves the layout under rootDir.
5
- * Falls back to '0.0.0' rather than throwing: every caller here is cosmetic
6
- * (a User-Agent, a title bar) and none is worth failing over.
2
+ * The installed pi-task version, read from package.json on every call so no
3
+ * build step has to bake it in and nothing needs regenerating on release.
4
+ *
5
+ * Two levels up is right for BOTH trees: `src/<dir>/../..` and `dist/<dir>/../..`
6
+ * each land on the package root, because tsconfig.build.json sets
7
+ * `rootDir: "src"` and tsc therefore reproduces the directory layout under dist.
8
+ * Called from each, both answer the version package.json declares.
9
+ *
10
+ * There are two ways to reach the '0.0.0' fallback and neither throws: an absent
11
+ * or unreadable package.json is caught, and a `version` that is not a string
12
+ * fails the typeof guard. Both were run. Throwing would be wrong for what the
13
+ * two callers do with it — one builds a fetch User-Agent, the other the title on
14
+ * the /task-config settings box. Neither is worth failing a session over.
7
15
  */
8
16
  export declare function readPkgVersion(): string;
@@ -2,11 +2,19 @@ import { readFileSync } from 'node:fs';
2
2
  import { dirname, join } from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
4
  /**
5
- * The installed pi-task version, read from package.json at runtime so nothing
6
- * has to be regenerated on release. Two levels up holds for both src/<dir>
7
- * (tests) and dist/<dir> (build), since tsc preserves the layout under rootDir.
8
- * Falls back to '0.0.0' rather than throwing: every caller here is cosmetic
9
- * (a User-Agent, a title bar) and none is worth failing over.
5
+ * The installed pi-task version, read from package.json on every call so no
6
+ * build step has to bake it in and nothing needs regenerating on release.
7
+ *
8
+ * Two levels up is right for BOTH trees: `src/<dir>/../..` and `dist/<dir>/../..`
9
+ * each land on the package root, because tsconfig.build.json sets
10
+ * `rootDir: "src"` and tsc therefore reproduces the directory layout under dist.
11
+ * Called from each, both answer the version package.json declares.
12
+ *
13
+ * There are two ways to reach the '0.0.0' fallback and neither throws: an absent
14
+ * or unreadable package.json is caught, and a `version` that is not a string
15
+ * fails the typeof guard. Both were run. Throwing would be wrong for what the
16
+ * two callers do with it — one builds a fetch User-Agent, the other the title on
17
+ * the /task-config settings box. Neither is worth failing a session over.
10
18
  */
11
19
  export function readPkgVersion() {
12
20
  try {
@@ -3,31 +3,38 @@
3
3
  *
4
4
  * WHY A LOCAL COPY OF PI'S CLAMP
5
5
  * ------------------------------
6
- * pi never reports that it ignored or downgraded a level. Measured live against
7
- * this machine's llama-server, with a proxy capturing the request body:
6
+ * pi does not report that it ignored or downgraded a level. Captured on the wire,
7
+ * with a logging proxy between pi and the model server. All three runs completed
8
+ * normally and printed no warning of any kind:
8
9
  *
9
10
  * 1. a model with `reasoning: false` + `--thinking medium`
10
- * → the body carries NO reasoning field at all. No error, no warning.
11
- * 2. `thinkingLevelMap: {off: null, ...}` + `--thinking off`
12
- * silently clamped UP to `medium`. Thinking stays on.
11
+ * → the request body carries NO reasoning field and no
12
+ * `chat_template_kwargs`. The level is erased, not refused.
13
+ * 2. `thinkingLevelMap: {off: null, …}` + `--thinking off`
14
+ * → thinking STAYS ON: the body arrives with
15
+ * `chat_template_kwargs.enable_thinking: true`. The clamp walks UP one
16
+ * rung at a time, so `off` lands on `minimal` — not on `medium`.
13
17
  * 3. `--thinking low` where `low: null`
14
- * → silently clamped to `medium`.
18
+ * → `medium` on the wire.
15
19
  *
16
20
  * All three are the same arithmetic, and it is pure: `getSupportedThinkingLevels`
17
- * / `clampThinkingLevel` in @earendil-works/pi-ai's models module. Reproducing it
21
+ * / `clampThinkingLevel` in @earendil-works/pi-ai's `models.js`. Reproducing it
18
22
  * lets one predicate — `clampToModel(m, wanted) !== wanted` — catch all three
19
23
  * host-side, before a single request is sent.
20
24
  *
21
25
  * Reimplemented rather than imported because `@earendil-works/pi-ai` is neither a
22
- * dependency nor a peerDependency of pi-task: it is present only because
23
- * pi-coding-agent hoists it, so importing it would take a hard dependency on a
24
- * transitive package to get twenty lines of arithmetic. SOURCE OF TRUTH is that
25
- * module; `reasoning-capability.test.ts` is where a change upstream shows up.
26
+ * dependency, a devDependency nor a peerDependency of pi-task it is in none of
27
+ * the three, and sits in node_modules only because pi-coding-agent depends on it.
28
+ * Importing it would take a hard dependency on a transitive package for twenty
29
+ * lines of arithmetic. SOURCE OF TRUTH is that module, and what follows is
30
+ * line-for-line identical to it, ladder included. Nothing here imports pi-ai, so
31
+ * an upstream change will not fail a test — the two have to be re-compared.
26
32
  */
27
33
  import { type ReasoningGroup, type GroupSetting } from '../config/reasoning.js';
28
34
  /**
29
- * pi's own level ladder, in order. The order is the whole algorithm: an
30
- * unsupported level is resolved by walking UP first, then down.
35
+ * pi's own level ladder, in order the same seven names, in the same sequence,
36
+ * as `EXTENDED_THINKING_LEVELS` in pi-ai's models.js. The order IS the algorithm:
37
+ * an unsupported level is resolved by walking UP first, then down.
31
38
  */
32
39
  export declare const THINKING_LADDER: readonly ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
33
40
  export type LadderLevel = (typeof THINKING_LADDER)[number];
@@ -48,9 +55,11 @@ export interface ReasoningModelFacts {
48
55
  * 1: the knob is not rejected, it is erased.
49
56
  * - a MISSING map entry means "supported" for the standard levels but
50
57
  * "unsupported" for `xhigh` / `max`, which are opt-in and must be declared.
51
- * This is why config/reasoning.ts does not offer those two: a model with no
52
- * map at all would receive the raw string, and Qwen3.8's chat template
53
- * answers an unknown effort with HTTP 500 rather than a clamp.
58
+ * This is why config/reasoning.ts offers neither: without a map the raw
59
+ * string reaches the server, and a chat template that does not know the level
60
+ * fails rather than clamping. Confirmed against a live server an effort
61
+ * string its template does not handle comes back HTTP 500, raised from inside
62
+ * the template itself, while one it does handle returns 200.
54
63
  */
55
64
  export declare function supportedThinkingLevels(model: ReasoningModelFacts): LadderLevel[];
56
65
  /**
@@ -69,15 +78,17 @@ export interface ReasoningMismatch {
69
78
  /**
70
79
  * Every group whose setting the model will silently change.
71
80
  *
72
- * `inherit` groups are skipped entirely, and that is what keeps a default
73
- * install permanently quiet: with the shipped all-`inherit` table this returns
74
- * an empty array for every model, including one with no reasoning at all.
81
+ * `inherit` groups are skipped entirely, because an inherited group asks for
82
+ * nothing. That is not the same as a quiet default: the shipped table is mostly
83
+ * DECIDED, with a single cell left on `inherit`, so a default install is not
84
+ * silent. Run against a `reasoning: false` model it reports a mismatch for every
85
+ * group whose level that model cannot honour.
75
86
  *
76
87
  * It reports mismatches in BOTH directions, which is wider than "warn when
77
- * reasoning is on but unsupported". The failure actually captured on this
78
- * machine is the mirror of that — `off` clamped UP to `medium`, so a user who
79
- * turned thinking off still pays for it — and it is the same comparison. Warning
80
- * about one direction while staying silent about the other would ship this
81
- * feature with its own measured failure mode unreported.
88
+ * reasoning is on but unsupported". The mirror case is the one captured on the
89
+ * wire — `off` clamped UP with `enable_thinking: true` still going out, so a
90
+ * user who turned thinking off still pays for it — and it is the same
91
+ * comparison. Warning about one direction while staying silent about the other
92
+ * would ship this feature unable to see its own failure mode.
82
93
  */
83
94
  export declare function reasoningMismatches(model: ReasoningModelFacts | undefined, levels: Readonly<Record<ReasoningGroup, GroupSetting>>): ReasoningMismatch[];