@mjasnikovs/pi-task 0.38.29 → 0.38.31

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 (373) 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/index.js +2 -0
  14. package/dist/remote/bridge.d.ts +19 -10
  15. package/dist/remote/bridge.js +3 -2
  16. package/dist/remote/broadcast.js +3 -1
  17. package/dist/remote/events.js +12 -11
  18. package/dist/remote/history.d.ts +1 -1
  19. package/dist/remote/protocol.d.ts +6 -3
  20. package/dist/remote/protocol.js +2 -1
  21. package/dist/remote/push.d.ts +16 -16
  22. package/dist/remote/push.js +27 -27
  23. package/dist/remote/register.d.ts +3 -3
  24. package/dist/remote/register.js +17 -19
  25. package/dist/remote/server.d.ts +9 -8
  26. package/dist/remote/server.js +15 -14
  27. package/dist/remote/session-state.d.ts +5 -4
  28. package/dist/remote/session-state.js +8 -5
  29. package/dist/remote/sw.d.ts +7 -6
  30. package/dist/remote/sw.js +7 -6
  31. package/dist/remote/tailscale.d.ts +4 -2
  32. package/dist/remote/tailscale.js +4 -2
  33. package/dist/remote/ui-highlight.js +6 -5
  34. package/dist/remote/ui-render.js +4 -4
  35. package/dist/remote/ui-script.js +24 -24
  36. package/dist/remote/ui-styles.d.ts +1 -1
  37. package/dist/remote/ui-styles.js +10 -13
  38. package/dist/remote/ui-tools.js +9 -6
  39. package/dist/shared/child-extensions.d.ts +29 -17
  40. package/dist/shared/child-extensions.js +29 -17
  41. package/dist/shared/child-output.d.ts +30 -24
  42. package/dist/shared/child-output.js +25 -17
  43. package/dist/shared/child-process.d.ts +47 -40
  44. package/dist/shared/child-process.js +50 -59
  45. package/dist/shared/command-watchdog.d.ts +85 -16
  46. package/dist/shared/command-watchdog.js +115 -21
  47. package/dist/shared/fs-text.d.ts +16 -10
  48. package/dist/shared/fs-text.js +16 -10
  49. package/dist/shared/git-runner.d.ts +25 -25
  50. package/dist/shared/git-runner.js +25 -25
  51. package/dist/shared/leaked-tool-call.d.ts +17 -11
  52. package/dist/shared/leaked-tool-call.js +23 -15
  53. package/dist/shared/model-endpoint.d.ts +29 -16
  54. package/dist/shared/model-endpoint.js +33 -21
  55. package/dist/shared/pi-invocation.d.ts +7 -4
  56. package/dist/shared/pi-invocation.js +12 -7
  57. package/dist/shared/pkg-version.d.ts +13 -5
  58. package/dist/shared/pkg-version.js +13 -5
  59. package/dist/shared/reasoning-capability.d.ts +35 -24
  60. package/dist/shared/reasoning-capability.js +35 -24
  61. package/dist/shared/stream-watchdog.d.ts +60 -44
  62. package/dist/shared/stream-watchdog.js +62 -45
  63. package/dist/task/accept-debt.d.ts +41 -43
  64. package/dist/task/accept-debt.js +73 -65
  65. package/dist/task/api-synthesis.d.ts +24 -21
  66. package/dist/task/api-synthesis.js +32 -26
  67. package/dist/task/apis-contract.d.ts +32 -64
  68. package/dist/task/apis-contract.js +32 -64
  69. package/dist/task/artifact-closure.d.ts +27 -13
  70. package/dist/task/artifact-closure.js +95 -67
  71. package/dist/task/auto-commit.d.ts +46 -35
  72. package/dist/task/auto-commit.js +51 -38
  73. package/dist/task/auto-io.d.ts +45 -25
  74. package/dist/task/auto-io.js +57 -29
  75. package/dist/task/auto-orchestrator.d.ts +26 -24
  76. package/dist/task/auto-orchestrator.js +192 -165
  77. package/dist/task/auto-prompts.d.ts +36 -24
  78. package/dist/task/auto-prompts.js +40 -26
  79. package/dist/task/autofix-ledger.d.ts +27 -25
  80. package/dist/task/autofix-ledger.js +29 -26
  81. package/dist/task/batch-test-task.d.ts +20 -12
  82. package/dist/task/batch-test-task.js +67 -60
  83. package/dist/task/boot-probe.d.ts +60 -44
  84. package/dist/task/boot-probe.js +91 -72
  85. package/dist/task/cancel-input.d.ts +30 -16
  86. package/dist/task/cancel-input.js +20 -11
  87. package/dist/task/cancel-points.d.ts +27 -20
  88. package/dist/task/cancel-points.js +30 -22
  89. package/dist/task/child-runner.d.ts +124 -55
  90. package/dist/task/child-runner.js +298 -90
  91. package/dist/task/child-status.d.ts +23 -16
  92. package/dist/task/child-status.js +23 -16
  93. package/dist/task/clamp-output.js +12 -5
  94. package/dist/task/command-run.d.ts +31 -28
  95. package/dist/task/command-run.js +44 -35
  96. package/dist/task/command-shrink.d.ts +25 -18
  97. package/dist/task/command-shrink.js +37 -31
  98. package/dist/task/command-watchdog.d.ts +9 -6
  99. package/dist/task/command-watchdog.js +21 -15
  100. package/dist/task/context-attribution.d.ts +34 -26
  101. package/dist/task/context-attribution.js +34 -26
  102. package/dist/task/context-silence.d.ts +39 -29
  103. package/dist/task/context-silence.js +35 -25
  104. package/dist/task/context-usage.d.ts +16 -9
  105. package/dist/task/context-usage.js +16 -9
  106. package/dist/task/contracts.d.ts +8 -4
  107. package/dist/task/contracts.js +25 -17
  108. package/dist/task/coverage-loop.d.ts +22 -18
  109. package/dist/task/coverage-loop.js +35 -30
  110. package/dist/task/critique-probes.d.ts +13 -14
  111. package/dist/task/critique-probes.js +50 -39
  112. package/dist/task/debug-log.d.ts +13 -5
  113. package/dist/task/debug-log.js +32 -20
  114. package/dist/task/decompose-fidelity.d.ts +11 -9
  115. package/dist/task/decompose-fidelity.js +38 -33
  116. package/dist/task/decompose-granularity.d.ts +41 -38
  117. package/dist/task/decompose-granularity.js +41 -38
  118. package/dist/task/deep-render-check.d.ts +22 -14
  119. package/dist/task/deep-render-check.js +40 -31
  120. package/dist/task/dropped-input.d.ts +12 -7
  121. package/dist/task/dropped-input.js +5 -2
  122. package/dist/task/enforce-attribution.d.ts +38 -47
  123. package/dist/task/enforce-attribution.js +46 -52
  124. package/dist/task/enforce-guidelines.d.ts +31 -20
  125. package/dist/task/enforce-guidelines.js +32 -21
  126. package/dist/task/enrichment.d.ts +7 -2
  127. package/dist/task/enrichment.js +26 -14
  128. package/dist/task/env-notes.d.ts +16 -7
  129. package/dist/task/env-notes.js +48 -31
  130. package/dist/task/env-template-closure.d.ts +4 -4
  131. package/dist/task/env-template-closure.js +42 -34
  132. package/dist/task/external-context.d.ts +28 -21
  133. package/dist/task/external-context.js +17 -12
  134. package/dist/task/failure-classifier.d.ts +4 -5
  135. package/dist/task/failure-classifier.js +30 -8
  136. package/dist/task/file-inventory.d.ts +15 -11
  137. package/dist/task/file-inventory.js +25 -22
  138. package/dist/task/final-gate-fix.d.ts +74 -86
  139. package/dist/task/final-gate-fix.js +97 -116
  140. package/dist/task/final-gate-progress.d.ts +29 -46
  141. package/dist/task/final-gate-progress.js +40 -51
  142. package/dist/task/final-gate.d.ts +64 -97
  143. package/dist/task/final-gate.js +192 -199
  144. package/dist/task/fix-child.d.ts +21 -27
  145. package/dist/task/fix-child.js +21 -27
  146. package/dist/task/foreign-path.d.ts +6 -5
  147. package/dist/task/foreign-path.js +0 -0
  148. package/dist/task/frozen-conflict.d.ts +9 -10
  149. package/dist/task/frozen-conflict.js +61 -64
  150. package/dist/task/frozen-path-guard.d.ts +35 -14
  151. package/dist/task/frozen-path-guard.js +56 -39
  152. package/dist/task/gate-child.d.ts +27 -28
  153. package/dist/task/gate-child.js +36 -35
  154. package/dist/task/gate-deps.d.ts +34 -27
  155. package/dist/task/gate-deps.js +169 -159
  156. package/dist/task/gate-tally.d.ts +77 -80
  157. package/dist/task/gate-tally.js +65 -68
  158. package/dist/task/git-state-guard.d.ts +15 -11
  159. package/dist/task/git-state-guard.js +76 -66
  160. package/dist/task/impl-widget.d.ts +25 -16
  161. package/dist/task/impl-widget.js +27 -17
  162. package/dist/task/implementation-guards.d.ts +26 -0
  163. package/dist/task/implementation-guards.js +177 -0
  164. package/dist/task/implementation-thinking.d.ts +33 -31
  165. package/dist/task/implementation-thinking.js +5 -6
  166. package/dist/task/implementation-turn.d.ts +39 -31
  167. package/dist/task/implementation-turn.js +41 -28
  168. package/dist/task/inline-markdown.d.ts +20 -7
  169. package/dist/task/inline-markdown.js +15 -6
  170. package/dist/task/launch-config-gap.js +25 -39
  171. package/dist/task/launch-contract.d.ts +18 -21
  172. package/dist/task/launch-contract.js +28 -30
  173. package/dist/task/launch-manifest.d.ts +6 -2
  174. package/dist/task/launch-manifest.js +35 -34
  175. package/dist/task/ledger.js +16 -14
  176. package/dist/task/lint-fix.d.ts +6 -8
  177. package/dist/task/lint-fix.js +67 -69
  178. package/dist/task/loop-detector.d.ts +27 -8
  179. package/dist/task/loop-detector.js +38 -14
  180. package/dist/task/mid-run-input.d.ts +17 -15
  181. package/dist/task/mid-run-input.js +17 -15
  182. package/dist/task/orchestrator.d.ts +24 -28
  183. package/dist/task/orchestrator.js +89 -66
  184. package/dist/task/orientation.d.ts +18 -23
  185. package/dist/task/orientation.js +24 -31
  186. package/dist/task/owned-freeze-conflict.d.ts +21 -20
  187. package/dist/task/owned-freeze-conflict.js +52 -85
  188. package/dist/task/owned-freeze-reassign.d.ts +40 -60
  189. package/dist/task/owned-freeze-reassign.js +41 -61
  190. package/dist/task/parsers.d.ts +4 -2
  191. package/dist/task/parsers.js +4 -4
  192. package/dist/task/phases.d.ts +41 -48
  193. package/dist/task/phases.js +196 -252
  194. package/dist/task/plan-io.d.ts +6 -7
  195. package/dist/task/plan-io.js +6 -7
  196. package/dist/task/plan-orchestrator.d.ts +10 -8
  197. package/dist/task/plan-orchestrator.js +14 -10
  198. package/dist/task/plan-prompts.d.ts +6 -5
  199. package/dist/task/plan-prompts.js +6 -5
  200. package/dist/task/plan-readonly.d.ts +4 -5
  201. package/dist/task/plan-readonly.js +4 -5
  202. package/dist/task/plan-rounds.d.ts +17 -29
  203. package/dist/task/plan-rounds.js +21 -34
  204. package/dist/task/plan-session.d.ts +58 -72
  205. package/dist/task/plan-session.js +61 -83
  206. package/dist/task/probe-gaming.d.ts +28 -27
  207. package/dist/task/probe-gaming.js +0 -0
  208. package/dist/task/prohibition-probe.d.ts +14 -16
  209. package/dist/task/prompts.d.ts +3 -4
  210. package/dist/task/prompts.js +17 -26
  211. package/dist/task/qa-transcript.d.ts +15 -22
  212. package/dist/task/qa-transcript.js +15 -21
  213. package/dist/task/question-box.d.ts +17 -13
  214. package/dist/task/question-box.js +19 -15
  215. package/dist/task/question-dedup.d.ts +6 -7
  216. package/dist/task/question-dedup.js +13 -14
  217. package/dist/task/question-dialog.d.ts +22 -32
  218. package/dist/task/question-dialog.js +22 -32
  219. package/dist/task/question-source.d.ts +18 -44
  220. package/dist/task/question-source.js +22 -51
  221. package/dist/task/refuted-constraint.d.ts +11 -31
  222. package/dist/task/refuted-constraint.js +27 -51
  223. package/dist/task/regenerable-artifacts.d.ts +12 -31
  224. package/dist/task/regenerable-artifacts.js +12 -31
  225. package/dist/task/render-check.d.ts +11 -22
  226. package/dist/task/render-check.js +33 -46
  227. package/dist/task/repo-health-check.d.ts +10 -14
  228. package/dist/task/repo-health-check.js +17 -23
  229. package/dist/task/requirements.d.ts +38 -71
  230. package/dist/task/requirements.js +78 -126
  231. package/dist/task/research-fanout-budget.d.ts +51 -88
  232. package/dist/task/research-fanout-budget.js +51 -88
  233. package/dist/task/research-worker.d.ts +29 -39
  234. package/dist/task/research-worker.js +37 -61
  235. package/dist/task/resume-gap.d.ts +14 -15
  236. package/dist/task/root-cause-repair.d.ts +9 -9
  237. package/dist/task/root-cause-repair.js +28 -40
  238. package/dist/task/run-bracket.d.ts +10 -13
  239. package/dist/task/run-end.d.ts +12 -22
  240. package/dist/task/run-end.js +8 -16
  241. package/dist/task/run-final-gate.d.ts +19 -21
  242. package/dist/task/run-final-gate.js +62 -80
  243. package/dist/task/runner-globs.d.ts +12 -13
  244. package/dist/task/runner-globs.js +12 -13
  245. package/dist/task/runner-resolve.d.ts +9 -9
  246. package/dist/task/runner-resolve.js +22 -23
  247. package/dist/task/script-escape.d.ts +10 -12
  248. package/dist/task/script-escape.js +13 -14
  249. package/dist/task/serve-entry.d.ts +1 -1
  250. package/dist/task/serve-entry.js +22 -25
  251. package/dist/task/service-blocks.js +4 -2
  252. package/dist/task/shipped-source.d.ts +11 -29
  253. package/dist/task/shipped-source.js +11 -29
  254. package/dist/task/skip-escape.js +10 -14
  255. package/dist/task/spec-urls.d.ts +26 -65
  256. package/dist/task/spec-urls.js +26 -65
  257. package/dist/task/spec-validation.d.ts +17 -20
  258. package/dist/task/spec-validation.js +17 -20
  259. package/dist/task/stall-detector.d.ts +23 -30
  260. package/dist/task/stall-detector.js +23 -30
  261. package/dist/task/stream-watchdog.d.ts +14 -12
  262. package/dist/task/stream-watchdog.js +14 -12
  263. package/dist/task/substitution-probe.d.ts +17 -20
  264. package/dist/task/substitution-probe.js +17 -20
  265. package/dist/task/task-gates.d.ts +36 -41
  266. package/dist/task/task-gates.js +95 -106
  267. package/dist/task/task-io.d.ts +4 -4
  268. package/dist/task/task-io.js +4 -4
  269. package/dist/task/task-parsers.js +4 -3
  270. package/dist/task/task-provenance.d.ts +2 -2
  271. package/dist/task/task-provenance.js +11 -13
  272. package/dist/task/task-types.d.ts +4 -3
  273. package/dist/task/terminal-outcome.d.ts +14 -16
  274. package/dist/task/terminal-outcome.js +12 -14
  275. package/dist/task/test-assembly.d.ts +13 -20
  276. package/dist/task/test-assembly.js +13 -20
  277. package/dist/task/timings.d.ts +5 -3
  278. package/dist/task/timings.js +5 -3
  279. package/dist/task/title-label.d.ts +9 -4
  280. package/dist/task/title-label.js +9 -4
  281. package/dist/task/type-only-answer.d.ts +44 -52
  282. package/dist/task/type-only-answer.js +44 -52
  283. package/dist/task/unfailable-command.d.ts +18 -24
  284. package/dist/task/unfailable-command.js +21 -27
  285. package/dist/task/unknown-routing.d.ts +10 -4
  286. package/dist/task/unknown-routing.js +10 -4
  287. package/dist/task/user-directives.d.ts +5 -8
  288. package/dist/task/user-directives.js +5 -8
  289. package/dist/task/verify-quality.d.ts +18 -22
  290. package/dist/task/verify-quality.js +45 -46
  291. package/dist/task/verify-reconcile.d.ts +15 -10
  292. package/dist/task/verify-reconcile.js +45 -43
  293. package/dist/task/verify-resolution.d.ts +24 -20
  294. package/dist/task/verify-resolution.js +51 -50
  295. package/dist/task/verify-work.d.ts +59 -66
  296. package/dist/task/verify-work.js +101 -138
  297. package/dist/task/widget.d.ts +15 -14
  298. package/dist/task/widget.js +22 -17
  299. package/dist/task/wiring-claims.d.ts +25 -32
  300. package/dist/task/wiring-claims.js +30 -35
  301. package/dist/task/write-guard.d.ts +39 -39
  302. package/dist/task/write-guard.js +48 -51
  303. package/dist/task/yolo.d.ts +34 -30
  304. package/dist/task/yolo.js +42 -37
  305. package/dist/workers/abstention.d.ts +21 -41
  306. package/dist/workers/abstention.js +27 -48
  307. package/dist/workers/brave-search.d.ts +4 -3
  308. package/dist/workers/brave-search.js +5 -2
  309. package/dist/workers/brave-warning.d.ts +7 -4
  310. package/dist/workers/brave-warning.js +19 -7
  311. package/dist/workers/ddg-search.d.ts +6 -6
  312. package/dist/workers/ddg-search.js +18 -12
  313. package/dist/workers/docs-cache.js +5 -2
  314. package/dist/workers/docs-chunk.d.ts +30 -37
  315. package/dist/workers/docs-chunk.js +37 -41
  316. package/dist/workers/docs-core.d.ts +28 -44
  317. package/dist/workers/docs-core.js +25 -44
  318. package/dist/workers/docs-index.js +4 -3
  319. package/dist/workers/docs-lookup.d.ts +15 -22
  320. package/dist/workers/docs-lookup.js +12 -21
  321. package/dist/workers/docs-project.d.ts +15 -9
  322. package/dist/workers/docs-project.js +17 -10
  323. package/dist/workers/docs-resolve.d.ts +19 -20
  324. package/dist/workers/docs-resolve.js +35 -32
  325. package/dist/workers/docs-retrieve.d.ts +5 -6
  326. package/dist/workers/docs-retrieve.js +18 -15
  327. package/dist/workers/exa-search.d.ts +9 -6
  328. package/dist/workers/exa-search.js +23 -12
  329. package/dist/workers/fetch-core.d.ts +13 -16
  330. package/dist/workers/fetch-core.js +23 -23
  331. package/dist/workers/focused-extractor.d.ts +13 -12
  332. package/dist/workers/focused-extractor.js +27 -19
  333. package/dist/workers/html-clean.js +24 -14
  334. package/dist/workers/http-request.d.ts +28 -20
  335. package/dist/workers/http-request.js +22 -17
  336. package/dist/workers/npm-version.d.ts +28 -11
  337. package/dist/workers/npm-version.js +24 -15
  338. package/dist/workers/phantom-imports.d.ts +15 -12
  339. package/dist/workers/phantom-imports.js +30 -24
  340. package/dist/workers/pi-worker-core.d.ts +65 -96
  341. package/dist/workers/pi-worker-core.js +93 -181
  342. package/dist/workers/pi-worker-docs.d.ts +24 -19
  343. package/dist/workers/pi-worker-docs.js +67 -76
  344. package/dist/workers/pi-worker-fetch.d.ts +7 -3
  345. package/dist/workers/pi-worker-fetch.js +27 -19
  346. package/dist/workers/pi-worker-search.js +12 -8
  347. package/dist/workers/pi-worker.d.ts +9 -4
  348. package/dist/workers/pi-worker.js +21 -14
  349. package/dist/workers/reasoning-warning.d.ts +18 -17
  350. package/dist/workers/reasoning-warning.js +22 -20
  351. package/dist/workers/research-cache.js +50 -78
  352. package/dist/workers/search-core.js +7 -5
  353. package/dist/workers/search-types.d.ts +10 -9
  354. package/dist/workers/search-types.js +9 -8
  355. package/dist/workers/session-hint.d.ts +13 -14
  356. package/dist/workers/session-hint.js +8 -9
  357. package/dist/workers/shared.d.ts +21 -25
  358. package/dist/workers/shared.js +0 -0
  359. package/dist/workers/single-read-extension.d.ts +14 -7
  360. package/dist/workers/single-read-extension.js +14 -7
  361. package/dist/workers/single-read-guard.d.ts +27 -30
  362. package/dist/workers/single-read-guard.js +36 -36
  363. package/dist/workers/typeonly-log.d.ts +12 -9
  364. package/dist/workers/typeonly-log.js +29 -33
  365. package/dist/workers/worker-channels.d.ts +15 -23
  366. package/dist/workers/worker-channels.js +15 -23
  367. package/dist/workers/worker-failure.d.ts +38 -46
  368. package/dist/workers/worker-failure.js +31 -39
  369. package/dist/workers/worker-kill.d.ts +25 -26
  370. package/dist/workers/worker-kill.js +16 -19
  371. package/dist/workers/worker-profiles.d.ts +54 -56
  372. package/dist/workers/worker-profiles.js +63 -39
  373. package/package.json +10 -8
@@ -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[];
@@ -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 { REASONING_GROUPS } 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 const THINKING_LADDER = ['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'];
33
40
  /**
@@ -38,9 +45,11 @@ export const THINKING_LADDER = ['off', 'minimal', 'low', 'medium', 'high', 'xhig
38
45
  * 1: the knob is not rejected, it is erased.
39
46
  * - a MISSING map entry means "supported" for the standard levels but
40
47
  * "unsupported" for `xhigh` / `max`, which are opt-in and must be declared.
41
- * This is why config/reasoning.ts does not offer those two: a model with no
42
- * map at all would receive the raw string, and Qwen3.8's chat template
43
- * answers an unknown effort with HTTP 500 rather than a clamp.
48
+ * This is why config/reasoning.ts offers neither: without a map the raw
49
+ * string reaches the server, and a chat template that does not know the level
50
+ * fails rather than clamping. Confirmed against a live server an effort
51
+ * string its template does not handle comes back HTTP 500, raised from inside
52
+ * the template itself, while one it does handle returns 200.
44
53
  */
45
54
  export function supportedThinkingLevels(model) {
46
55
  if (!model.reasoning)
@@ -80,16 +89,18 @@ export function clampToModel(model, level) {
80
89
  /**
81
90
  * Every group whose setting the model will silently change.
82
91
  *
83
- * `inherit` groups are skipped entirely, and that is what keeps a default
84
- * install permanently quiet: with the shipped all-`inherit` table this returns
85
- * an empty array for every model, including one with no reasoning at all.
92
+ * `inherit` groups are skipped entirely, because an inherited group asks for
93
+ * nothing. That is not the same as a quiet default: the shipped table is mostly
94
+ * DECIDED, with a single cell left on `inherit`, so a default install is not
95
+ * silent. Run against a `reasoning: false` model it reports a mismatch for every
96
+ * group whose level that model cannot honour.
86
97
  *
87
98
  * It reports mismatches in BOTH directions, which is wider than "warn when
88
- * reasoning is on but unsupported". The failure actually captured on this
89
- * machine is the mirror of that — `off` clamped UP to `medium`, so a user who
90
- * turned thinking off still pays for it — and it is the same comparison. Warning
91
- * about one direction while staying silent about the other would ship this
92
- * feature with its own measured failure mode unreported.
99
+ * reasoning is on but unsupported". The mirror case is the one captured on the
100
+ * wire — `off` clamped UP with `enable_thinking: true` still going out, so a
101
+ * user who turned thinking off still pays for it — and it is the same
102
+ * comparison. Warning about one direction while staying silent about the other
103
+ * would ship this feature unable to see its own failure mode.
93
104
  */
94
105
  export function reasoningMismatches(model, levels) {
95
106
  // No model resolved yet (session still starting, or none selected): say
@@ -2,25 +2,30 @@
2
2
  * Model-stream watchdog — the inactivity machine for a stream that goes SILENT
3
3
  * without erroring.
4
4
  *
5
- * WHY (mx5 run 14): three main-session implementation turns died mid-turn the
6
- * session jsonl's last event is an ordinary assistant message, then nothing,
7
- * forever, while the model server stayed Up(healthy) the whole time. A hung or
8
- * silently-dropped stream throws NOTHING, so:
9
- * - the connection-error retry (child-runner.ts) never fires: it needs a
10
- * thrown/reported ModelError,
11
- * - the command watchdog never fires: it only covers TOOL executions,
12
- * - the child stall guard never fires: it treats a reachable endpoint as proof
13
- * of life, which it is the endpoint was fine, the stream was not.
14
- * Cost in run 14: ~2.9h of dead air awaiting manual restarts.
5
+ * WHY: a turn can die mid-stream the last thing recorded is an ordinary
6
+ * assistant message, then nothing, while the model server stays healthy. A hung
7
+ * or silently-dropped stream throws NOTHING, so none of the three guards that
8
+ * already exist can see it:
9
+ * - the connection-error retry never fires. child-runner reaches it only
10
+ * inside `if (r.modelError)`, and a silent hang reports no error at all.
11
+ * - the command watchdog never fires: it arms per tool call and covers only
12
+ * TOOL executions.
13
+ * - the child stall guard never fires: it kills only when a probe finds the
14
+ * endpoint UNREACHABLE, and here the endpoint answers fine. The stream is
15
+ * what died, not the server.
15
16
  *
16
- * WHAT THIS MEASURES: time since the LAST stream event of ANY kind text token,
17
- * tool-call delta, thinking delta, provider response header. NOT wall-clock, and
18
- * NOT "time to first token". One token every 30s is a working local model and
19
- * must never be killed; zero events for the whole window is a hang.
17
+ * WHAT THIS MEASURES: time since the last sign of life, NOT wall-clock and NOT
18
+ * time-to-first-token. What counts as a sign differs by surface — for the main
19
+ * session it is any of six pi events (`before_provider_request`,
20
+ * `after_provider_response`, `turn_start`, `message_start`, `message_update`,
21
+ * `message_end`), with every text, thinking and tool-call delta arriving as
22
+ * `message_update`; for a child it is any stdout or stderr CHUNK. A slow model
23
+ * that emits something occasionally is working and must never be killed. Zero
24
+ * for the whole window is a hang.
20
25
  *
21
- * WHY THE DEFAULT IS GENEROUS: on a local llama-server a 32k-context prompt can
22
- * spend many minutes in prompt processing emitting nothing at all. A 60-120s
23
- * ceiling would kill every long prompt on local hardware. See
26
+ * WHY THE DEFAULT IS GENEROUS: prompt processing legitimately emits nothing
27
+ * while it runs, so a tight ceiling would kill honest long prompts. This guard
28
+ * exists to bound dead air, not to police slowness. See
24
29
  * DEFAULT_STREAM_INACTIVITY_MS.
25
30
  *
26
31
  * TWO SURFACES, one machine (same split as command-watchdog.ts):
@@ -38,15 +43,19 @@
38
43
  import type { TimerHandle } from './command-watchdog.js';
39
44
  export type { TimerHandle };
40
45
  /**
41
- * Default inactivity ceiling: 10 minutes. Chosen from the constraint that a local
42
- * model's first token can legitimately be many minutes away the guard exists to
43
- * turn hours of dead air into minutes, not to police slowness.
46
+ * Default inactivity ceiling. Sized by the constraint that a local model's first
47
+ * token can legitimately be a long way off, so the number has to clear honest
48
+ * prompt processing rather than sit close to it.
44
49
  */
45
50
  export declare const DEFAULT_STREAM_INACTIVITY_MS: number;
46
51
  /**
47
- * How often the machine checks the idle clock. A poll (rather than re-arming a
48
- * timeout on every token) keeps cost O(1) per window instead of O(1) per token —
49
- * a streaming turn emits thousands of events. Same idiom as the child stall guard.
52
+ * How often the machine checks the idle clock. A poll, rather than re-arming a
53
+ * timeout on every token, keeps the cost per window constant instead of per
54
+ * event and "thousands of events" is not a figure of speech: one captured turn
55
+ * emitted over two thousand, nearly all of them `message_update`.
56
+ *
57
+ * A quarter of the window, clamped: never tighter than 50ms, never looser than
58
+ * 30s. The child stall guard polls with the same shape and its own divisor.
50
59
  */
51
60
  export declare function pollIntervalMs(timeoutMs: number): number;
52
61
  export interface StreamWatchdogDeps {
@@ -69,12 +78,14 @@ export declare class StreamWatchdog {
69
78
  * legitimately idle, and that window belongs to the command watchdog, not to
70
79
  * this one — without it a 10-minute build looks identical to a hung stream.
71
80
  *
72
- * A SET, not a boolean: pi runs a tool batch in parallel (agent-loop.js
73
- * `executeToolCallsParallel` emits every tool_execution_start up front, then one
74
- * end per call as each settles, and answers an immediate call inline while an
75
- * earlier one is still running). A boolean would be cleared by the FIRST end and
76
- * leave the still-running sibling the long build exposed to a false fire.
77
- * Same per-toolCallId idiom the command watchdog uses.
81
+ * A SET, not a boolean, and pi's own loop is why. `executeToolCallsParallel`
82
+ * in agent-loop.js emits EVERY `tool_execution_start` up front, before any
83
+ * call is prepared; a call that prepares "immediate" gets its end emitted
84
+ * inline right there, while the rest are deferred as closures and settle
85
+ * through a `Promise.all`. So an end can arrive while earlier siblings are
86
+ * still running. A boolean would be cleared by that first end and leave the
87
+ * long build exposed to a false fire. Same per-toolCallId idiom the command
88
+ * watchdog uses.
78
89
  */
79
90
  private readonly active;
80
91
  /** Nesting depth for callers that cannot supply an id, counted so an unkeyed
@@ -107,27 +118,32 @@ export declare class StreamWatchdog {
107
118
  * cancels it on stop/fire, and runChild's cleanup() stops the watchdog on every
108
119
  * settle path (close, error, abort), so it cannot outlive the child it watches.
109
120
  *
110
- * The poll is REF'd, deliberately. It used to be unref'd, on the reasoning that a
111
- * pending watchdog poll should never keep the process alive at exit — but under
112
- * Bun on WINDOWS an unref'd timer does not fire at all once nothing ref'd is
113
- * pending. Measured on a windows-latest runner (bun 1.3.14): an unref'd interval
114
- * with nothing else pending never fired in 20s, while the identical ref'd one
115
- * fired at 101ms; on linux both fire at 100ms.
121
+ * The poll is REF'd, deliberately. Unref'd reads better a pending watchdog poll
122
+ * should never keep the process alive at exit — but an unref'd timer is only
123
+ * guaranteed to fire while something ref'd is still pending, and that guarantee
124
+ * is not the platform-independent thing it looks like.
125
+ *
126
+ * Measured here, on both runtimes: with nothing else pending an unref'd timer
127
+ * never fires at all, the process just exits; with a spawned child holding its
128
+ * stdout and stderr pipes open it fires on schedule throughout. A silent child
129
+ * is the SECOND case — its pipes are still ref'd work — so on this platform the
130
+ * unref would not have broken the child guard. The reason to keep it ref'd is
131
+ * that this machine also runs in the main session, where no such handle is
132
+ * guaranteed, and the failure mode is silent: a watchdog that never fires looks
133
+ * exactly like a stream that never hung.
116
134
  *
117
- * That state — no ref'd work outstanding — is EXACTLY the state this watchdog
118
- * exists to police: a child whose model stream has gone silent. So the unref
119
- * disabled the guard precisely when it was needed, and every stream-stall
120
- * integration test hung forever on windows (CI runs 30476786365, 30477802633,
121
- * 30478455402 — it hangs at v0.24.1 too, so this predates that release).
122
135
  * A poll that can delay exit by one interval is the cheaper failure.
123
136
  */
124
137
  export declare const realStreamTimerDeps: Pick<StreamWatchdogDeps, 'now' | 'schedule' | 'cancel'>;
125
138
  /**
126
139
  * The cause string a CHILD's stream stall is reported as. Phrased so
127
- * {@link isConnectionError} (child-runner.ts) matches it — the whole point is to
128
- * route a silent hang into the retry path that already exists for a LOUD
129
- * connection failure, rather than inventing a second one. Honest about who
130
- * killed it: pi-task aborted the request, the provider did not report anything.
140
+ * {@link isConnectionError} (child-runner.ts) matches it — checked by running the
141
+ * round-trip, and it does. The whole point is to route a silent hang into the
142
+ * retry path that already exists for a LOUD connection failure rather than
143
+ * inventing a second one, so the phrase "connection lost" is load-bearing, not
144
+ * decoration: reword it past that predicate and stream stalls stop retrying.
145
+ * Honest about who killed it, too — pi-task aborted the request, the provider
146
+ * reported nothing.
131
147
  */
132
148
  export declare function streamStallCause(idleMs: number): string;
133
149
  /**