@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,22 +2,22 @@
2
2
  * One-line startup hint shown when /task-config asks for a thinking level the
3
3
  * connected model will not honour.
4
4
  *
5
- * WHY IT HAS TO EXIST. pi never says it ignored or downgraded a level. Measured
6
- * live with a proxy on the request body: a model with `reasoning: false` given
7
- * `--thinking medium` sends no reasoning field at all, and a model whose
8
- * `thinkingLevelMap` nulls `off` given `--thinking off` is clamped UP to
9
- * `medium` thinking stays on. Both are silent. A reasoning profile the user
10
- * set and the model erased is worse than no profile feature, because it looks
11
- * like it worked.
5
+ * WHY IT HAS TO EXIST. pi silently rewrites the level and says nothing. Its own
6
+ * `clampThinkingLevel` (pi-ai models.js) shows both halves: a model with
7
+ * `reasoning: false` supports only `["off"]`, so every requested level collapses
8
+ * there; and a level the model's `thinkingLevelMap` nulls is filtered out, after
9
+ * which the clamp scans UPWARD before downward so asking for `off` on a model
10
+ * that nulls `off` gets the next level that IS available, and thinking stays on.
11
+ * A reasoning profile the user set and the model erased is worse than no profile
12
+ * feature, because it looks like it worked.
12
13
  *
13
14
  * ANTI-NAG. This warns once per session and clears on the first keystroke, and
14
15
  * that is the whole mechanism — deliberately no "already warned about model X"
15
16
  * file. Such a record goes stale the moment models.json is edited, and would
16
17
  * suppress the warning at exactly the moment a `/model` switch made it true.
17
- * brave-warning.ts nags every session for a standing misconfiguration and that
18
- * is correct; this is the same class. The real anti-nag is `inherit`: with the
19
- * shipped all-`inherit` table, `reasoningMismatches` returns empty for every
20
- * model and nothing renders at all.
18
+ * brave-warning.ts nags every session for a standing misconfiguration and that is
19
+ * correct; this is the same class. Setting a group back to `inherit` is what
20
+ * silences it: an all-`inherit` table yields no mismatches for any model.
21
21
  */
22
22
  import { getConfig } from '../config/config.js';
23
23
  import { effectiveReasoning } from '../config/reasoning.js';
@@ -30,9 +30,9 @@ const WIDGET_KEY = 'pi-task-reasoning-warning';
30
30
  *
31
31
  * Names the MODEL it checked, because children carry no `-m` and resolve pi's
32
32
  * default model, which need not be the host session's — a warning that does not
33
- * say what it looked at cannot be acted on. Names at most two groups; the count
34
- * carries the rest, since a line long enough to list seven is a line nobody
35
- * reads.
33
+ * say what it looked at cannot be acted on. Names at most two groups and appends
34
+ * `(+N more)` only when there are more than two, since a line long enough to list
35
+ * every group is a line nobody reads. Null when nothing mismatched.
36
36
  */
37
37
  export function formatReasoningWarning(modelName, mismatches) {
38
38
  if (mismatches.length === 0)
@@ -51,9 +51,10 @@ export function formatReasoningWarning(modelName, mismatches) {
51
51
  * The extra cause line, when the SERVER disagrees with models.json.
52
52
  *
53
53
  * This is the `/login llama.cpp` case and the only thing the host-side clamp
54
- * cannot see: pi's built-in llama.cpp provider hardcodes `reasoning: false`, so
55
- * a perfectly capable server is described to pi as having no reasoning at all.
56
- * Returns null whenever the two agree, or when there was nothing to compare.
54
+ * cannot see: pi ships llama.cpp as a built-in extension whose provider entry
55
+ * hardcodes `reasoning: false`, so a perfectly capable server is described to pi
56
+ * as having no reasoning at all. Returns null whenever the two agree, and null
57
+ * when the probe answered nothing, so an unreachable server adds no cause line.
57
58
  */
58
59
  export function formatCapabilityConflict(serverSupportsEffort, modelDeclaresReasoning) {
59
60
  if (serverSupportsEffort === null)
@@ -90,9 +91,10 @@ probe = probeChatTemplateCaps) {
90
91
  const base = formatReasoningWarning(model?.name ?? model?.id ?? 'unknown', mismatches);
91
92
  if (base === null)
92
93
  return null;
93
- // Fire-and-forget: the server probe only ever REFINES the cause line, so
94
- // it must not delay the warning or be able to prevent it. A 2s budget and
95
- // a swallowed failure mean a non-llama.cpp backend costs nothing.
94
+ // Fire-and-forget: the server probe only ever REFINES the cause line, so it
95
+ // must not delay the warning or be able to prevent it. `probeChatTemplateCaps`
96
+ // carries its own short timeout and returns null on any failure, so a
97
+ // backend that does not answer `/props` costs nothing.
96
98
  const baseUrl = model?.baseUrl;
97
99
  if (model === undefined || baseUrl === undefined || baseUrl === '')
98
100
  return { text: base };
@@ -2,13 +2,11 @@
2
2
  * research-cache — a per-run cache of docs/search/fetch worker RESULTS, shared
3
3
  * across the sibling task pipelines of one /task-auto run.
4
4
  *
5
- * The failure this serves (mx5 run 8, F10): the research phase alone burned 75 of
6
- * 363 minutes because ~20 sibling task pipelines each re-fetched the SAME external
7
- * docs and re-ran the SAME searches (the tailwind CLI docs fetched anew for task
8
- * after task). Each of those worker results is a deterministic function of (tool,
9
- * package/url, query) that does not change within a run so the first pipeline to
10
- * ask a question can answer every later one from a shared digest instead of a fresh
11
- * network round-trip plus child-summariser spawn.
5
+ * The failure it serves: every sibling task pipeline in a run re-fetches the SAME
6
+ * external docs and re-runs the SAME searches. Each of those worker results is a
7
+ * deterministic function of (tool, package/url, query) that does not change within a
8
+ * run so the first pipeline to ask a question can answer every later one from a
9
+ * shared digest instead of a fresh network round-trip plus a child-summariser spawn.
12
10
  *
13
11
  * SCOPE — stable external lookups only: npm-package docs, web search, web fetch. A
14
12
  * PROJECT-SOURCE (`.`) docs lookup is deliberately NOT cached: the working tree
@@ -25,27 +23,17 @@
25
23
  * a run started with the feature flag OFF (no id in the environment) does not cache
26
24
  * at all — the cache is inert unless the orchestrator turned it on for this run.
27
25
  *
28
- * RESUME REUSE (mx5 run 13, measured from the file's own git history the cache is
29
- * committed with every task, so the whole run is recoverable): a 32-task run built the
30
- * cache to 201 entries over 20 tasks under one run id, then three /task-auto-resume
31
- * invocations each stamped a fresh id and the first store of each dropped everything —
32
- * 201 → 11 → 3 → 5 → 8. The audit read the 8-entry tail and concluded the cache was
33
- * near-useless; it was in fact working, and the resume threw the work away. The old
34
- * comment called a resume re-fetch "only slightly less reuse", which holds for a
35
- * 5-task run and fails badly for a 32-task one.
26
+ * RESUME REUSE. A resume that stamps a fresh id makes the first store drop everything
27
+ * the interrupted run had built, so the longer the run, the more a resume throws away.
28
+ * A resume therefore REUSES the interrupted run's id but only on POSITIVE evidence
29
+ * that the digests still describe the same dependency surface.
36
30
  *
37
- * So a resume now REUSES the interrupted run's id but only on POSITIVE evidence that
38
- * the digests still describe the same dependency surface.
31
+ * PER-PACKAGE INVALIDATION. One fingerprint over the whole dependency block cannot be
32
+ * that evidence: a greenfield run ADDS dependencies every few tasks, so every resume
33
+ * sees a moved fingerprint and keeps nothing. Adding a package invalidates nothing that
34
+ * was already cached; only the package a digest is ABOUT going stale does.
39
35
  *
40
- * PER-PACKAGE INVALIDATION (mx5 run 14). The first shape of that evidence was one md5
41
- * over the whole dependency block: any change anywhere ⇒ wipe. That gate can never hold
42
- * on the projects /task actually builds — a greenfield run ADDS dependencies every few
43
- * tasks (run 14: hono/zod at T10, shadcn at T25, playwright-ct at T28), so all five of
44
- * its resumes saw a moved fingerprint and the run ended with ONE cached entry. Adding a
45
- * package invalidates nothing that was already cached; only the package a digest is
46
- * ABOUT going stale does.
47
- *
48
- * So invalidation is now per entry. Every docs entry records the package it describes
36
+ * So invalidation is per entry. Every docs entry records the package it describes
49
37
  * and the version declared for it at store time (a structured field on the entry, not
50
38
  * something re-parsed out of the key — keys embed the tool name with a \0 separator and
51
39
  * are the wrong place to carry meaning). A resume keeps the run id and drops only the
@@ -53,45 +41,38 @@
53
41
  *
54
42
  * STALENESS TRADE, decided deliberately: search and fetch entries are kept
55
43
  * unconditionally, even across a dependency bump. A kept search result about package X
56
- * may describe an older X. That is accepted — docs is the version-sensitive channel (it
57
- * is version-pinned to the INSTALLED package since pi-worker fixes #1) and search is
58
- * discovery, where re-running every query on every resume costs far more than the rare
59
- * staleness costs. A run that must not tolerate it can disable the cache outright.
44
+ * may describe an older X. That is accepted — docs is the version-sensitive channel,
45
+ * pinned to the INSTALLED package, and search is discovery, where re-running every query
46
+ * on every resume costs far more than the rare staleness does. A run that must not
47
+ * tolerate it can disable the cache outright.
60
48
  *
61
49
  * A file written before per-entry provenance shipped (no `pkgv` marker) carries no way
62
50
  * to tell its docs entries apart, so it still falls back to a fresh id: every
63
51
  * inconclusive path costs time, never correctness.
64
52
  *
65
- * CONCURRENT WRITERS (nexttask TASK 4). Storing is a read-modify-write over ONE file,
66
- * and its writers are concurrent on two axes at once: makeWorkerTool registers the
67
- * research tools with executionMode 'parallel', so a single child can issue 4-6 docs
68
- * calls in the same millisecond, and the research phase runs four worker children as
69
- * separate PROCESSES. Unsynchronised, that is a classic lost update — last writer wins,
70
- * and everything read before it is discarded. Measured on this box: 40 concurrent stores
71
- * left 1 entry in-process and 11 of 40 across four child processes.
53
+ * CONCURRENT WRITERS. Storing is a read-modify-write over ONE file, and its writers are
54
+ * concurrent on two axes at once: makeWorkerTool registers the research tools with
55
+ * `executionMode: 'parallel'`, so a single child can issue several docs calls in the same
56
+ * millisecond, and the research phase runs its worker children as separate PROCESSES.
57
+ * Unsynchronised, that is a classic lost update — last writer wins, and everything read
58
+ * before it is discarded.
72
59
  *
73
60
  * So the read-modify-write is serialised twice over: an in-process queue per cache file
74
61
  * (siblings inside one child never touch the filesystem lock at all) wrapped in an
75
62
  * advisory lock directory beside the file, which is what makes it hold across processes.
76
- * An atomic `mkdir` is the lock — it is the one primitive that both POSIX and Windows
77
- * give us with create-or-fail semantics and no fd bookkeeping.
63
+ * An atomic `mkdir` is the lock — create-or-fail with no fd bookkeeping. With both in
64
+ * place, 40 concurrent stores keep 40 entries, whether they come from one process or
65
+ * from four.
78
66
  *
79
67
  * The lock is BEST-EFFORT LIKE EVERYTHING ELSE HERE: acquisition is bounded, and a
80
68
  * writer that cannot get in within the timeout SKIPS its store rather than waiting. A
81
69
  * skipped store costs one re-lookup later; a blocked store would stall a worker, which
82
- * this cache is never allowed to do.
83
- *
84
- * WHAT THE FIX IS WORTH, measured before it was built (scripts/research-cache-
85
- * write-loss-step0.ts, re-runnable): the lost updates are real but nearly free. mx5 lost
86
- * ~70 of 204 attempted keys, all in the docs channel — and asked ZERO of them twice, so
87
- * nothing lost was ever wanted again. IAR1 and godot-engine retained more distinct keys
88
- * than the logs show attempted, i.e. lost nothing at all. Estimated recovery on all three
89
- * projects: 0s per run. This is a correctness fix, not a performance one; it stops the
90
- * cache silently discarding work, and it will matter to a run whose research phase does
91
- * repeat itself. Do not oversell it.
70
+ * this cache is never allowed to do. This is a correctness fix, not a performance one:
71
+ * it stops the cache silently discarding work.
92
72
  *
93
73
  * Stored under `.pi-tasks/` (sibling of env-notes.md / contracts.md), which the
94
- * git-state guard and discardEdits both exclude. Best-effort throughout: any I/O or
74
+ * git-state guard excludes by pathspec (`:(exclude).pi-tasks`) and discardEdits leaves
75
+ * alone. Best-effort throughout: any I/O or
95
76
  * parse failure falls back to a live fetch — the cache only ever saves time, it can
96
77
  * never change an answer or block a worker.
97
78
  */
@@ -103,16 +84,14 @@ const RESEARCH_CACHE_FILE = 'research-cache.json';
103
84
  export const RESEARCH_RUN_ID_ENV = 'PI_TASK_RUN_ID';
104
85
  /**
105
86
  * Cap stored entries so a chatty run cannot grow the file unboundedly; the newest
106
- * (by write time) are kept. Sized well above a 20-task run's distinct external
107
- * lookups (dozens), so a real run never evicts a still-useful digest.
87
+ * (by write time) are kept and the oldest evicted.
108
88
  */
109
89
  const MAX_ENTRIES = 250;
110
90
  /** Lock directory guarding the cache file's read-modify-write, created beside it. */
111
91
  const LOCK_SUFFIX = '.lock';
112
92
  /**
113
- * How long a writer waits for the lock before giving up and skipping its store. Sized
114
- * well above a real critical section (one small read + one small write, sub-millisecond
115
- * at these file sizes) and well below anything a worker would notice.
93
+ * How long a writer waits for the lock before giving up and skipping its store. The
94
+ * critical section it queues behind is one small read plus one small write.
116
95
  */
117
96
  const LOCK_TIMEOUT_MS = 2_000;
118
97
  /** Poll interval while the lock is held by someone else. */
@@ -125,26 +104,20 @@ const LOCK_POLL_MS = 10;
125
104
  const RENAME_TIMEOUT_MS = 500;
126
105
  /**
127
106
  * A lock older than this is treated as abandoned and removed. Two writers can both
128
- * decide that and both proceed, which degrades exactly to the pre-lock behaviour (one
129
- * lost update) — strictly better than a crashed child wedging the cache for the rest of
130
- * the run. Sized far above the critical section, so a live holder is never stolen from.
107
+ * decide that and both proceed, which degrades to one lost update — strictly better
108
+ * than a crashed child wedging the cache for the rest of the run. Sized far above the
109
+ * critical section, so a live holder is never stolen from.
131
110
  */
132
111
  const LOCK_STALE_MS = 30_000;
133
112
  /**
134
113
  * Filesystem errors that mean "try again in a moment", not "this will never work".
135
114
  *
136
115
  * POSIX gives `mkdir` two clean answers when someone else holds the lock: it succeeds,
137
- * or it is EEXIST. Windows has a third. A directory that another process is removing
138
- * enters a DELETE-PENDING state, and a create against it fails with EPERM/EACCES/EBUSY
139
- * instead of EEXIST so the exact moment the previous writer released the lock is a
140
- * window in which the next writer's `mkdir` fails with a code that used to be read as
141
- * fatal. The store was then silently skipped: no throw, no log, exit code 0, one entry
142
- * missing. That is the whole of CI's `39 of 40` on windows-latest; the same run's
143
- * ubuntu half is green because POSIX never produces the code.
144
- *
145
- * `rename` over an existing file has the same shape on Windows — it fails while any
146
- * other handle is open on the target, including a scanner's — so the cache write
147
- * retries on this set too.
116
+ * or it is EEXIST. Other platforms add more, and a lock contention reported under a code
117
+ * an EEXIST-only check reads as fatal skips the store SILENTLY — no throw, no log, exit
118
+ * code 0, one entry missing. `rename` over an existing file has the same shape. Both
119
+ * therefore retry on this whole set; research-cache.test.ts is what holds the membership
120
+ * in place.
148
121
  */
149
122
  const RETRYABLE_FS_CODES = new Set(['EEXIST', 'EPERM', 'EACCES', 'EBUSY', 'ENOTEMPTY']);
150
123
  /** Is this a transient filesystem error worth retrying inside the caller's deadline? */
@@ -343,11 +316,10 @@ async function writeCacheFile(cwd, out) {
343
316
  try {
344
317
  await fsp.mkdir(tasksDir(cwd), { recursive: true });
345
318
  await fsp.writeFile(tmp, JSON.stringify(out), 'utf8');
346
- // The replace, not the write, is the part Windows can transiently refuse — any
347
- // other open handle on the target (a reader, a scanner) fails it with EPERM.
348
- // The whole point of the lock above is that this write is not lost, so a
349
- // transient refusal is retried inside the same bounded budget rather than
350
- // swallowed. The lock is still held throughout.
319
+ // The replace, not the write, is the part a filesystem can transiently refuse
320
+ // when another handle is open on the target. The whole point of the lock above
321
+ // is that this write is not lost, so a refusal in RETRYABLE_FS_CODES is retried
322
+ // inside a bounded budget rather than swallowed. The lock is held throughout.
351
323
  const deadline = Date.now() + RENAME_TIMEOUT_MS;
352
324
  for (;;) {
353
325
  try {
@@ -391,10 +363,10 @@ async function acquireLock(lockPath, deadline) {
391
363
  catch (err) {
392
364
  if (!isRetryableFsError(err))
393
365
  return false;
394
- // Not EEXIST but still retryableWindows delete-pending (see
395
- // RETRYABLE_FS_CODES). There is no lock to inspect for staleness: the
396
- // directory is on its way out, so wait one poll and try to create it again
397
- // rather than reporting a hold that nobody has.
366
+ // Retryable but NOT EEXISTthe directory is on its way out (see
367
+ // RETRYABLE_FS_CODES), so there is no lock to inspect for staleness. Wait
368
+ // one poll and try to create it again rather than reporting a hold that
369
+ // nobody has.
398
370
  if (!isHeldError(err)) {
399
371
  if (Date.now() >= deadline)
400
372
  return false;
@@ -45,11 +45,13 @@ export async function search(input) {
45
45
  }
46
46
  catch (err) {
47
47
  // Which throws already carry a finished user-facing message is the engine's
48
- // own fact, so it is a row. This used to be an `err.name ===
49
- // 'BraveSearchError'` STRING match in a brave-only branch — the check
50
- // existed only because brave was reached down a different path from the
51
- // other two, and it is the reason a subclass rename would have gone
52
- // unnoticed.
48
+ // own fact, so `errorName` is a row rather than an `err.name` check welded
49
+ // into a brave-only branch. It stays a STRING match either way a class
50
+ // rename that leaves the row untouched goes unnoticed, and only the wrapped
51
+ // wording changes.
52
+ //
53
+ // No `errorName` means every throw's message is used verbatim; a name that
54
+ // does not match is passed through `wrapUnknownError`.
53
55
  const message = err instanceof Error ? err.message : String(err);
54
56
  const known = adapter.errorName === undefined
55
57
  || (err instanceof Error && err.name === adapter.errorName);
@@ -8,26 +8,27 @@ export interface SearchResult {
8
8
  * Which engine backs pi-worker-search and the freshness/enrichment lookups.
9
9
  * - `exa` — Exa's public MCP endpoint; no key needed (default).
10
10
  * - `ddg` — DuckDuckGo's HTML endpoint; no key needed.
11
- * - `brave` — Brave Search API; needs BRAVE_SEARCH_API_KEY.
11
+ * - `brave` — Brave Search API; needs BRAVE_SEARCH_API_KEY, or BRAVE_API_KEY.
12
12
  */
13
13
  export type SearchProvider = 'exa' | 'ddg' | 'brave';
14
14
  export declare const SEARCH_PROVIDERS: readonly SearchProvider[];
15
15
  /**
16
16
  * The API-key env vars an engine needs, in lookup order. Empty = keyless.
17
17
  *
18
- * Declared beside the engine ids rather than inside `search()`, because two
19
- * places ask the question: the search itself, and `searchConfigured` in
20
- * phases.ts, which decides whether the APIS research worker is even given the
21
- * search tool. That second copy was a hand-written re-statement of brave's env
22
- * pair with a comment saying it "mirrors search-core's lookup" — the shape a
23
- * table exists to make impossible.
18
+ * Declared beside the engine ids rather than inside `search()`, because two places
19
+ * ask the question: the search itself, and `searchConfigured` in phases.ts, which
20
+ * decides whether the APIS research worker is handed the search tool at all. Both
21
+ * go through {@link searchProviderKey}, so neither can restate the env pair.
24
22
  */
25
23
  export declare const SEARCH_PROVIDER_KEY_ENV: Record<SearchProvider, readonly string[]>;
26
- /** The engine's key, or `null` when it needs one and none is set. `''` = keyless. */
24
+ /** The engine's key, or `null` when it needs one and none is set. `''` = keyless.
25
+ * The vars are tried in list order and the first non-empty one wins. */
27
26
  export declare function searchProviderKey(provider: SearchProvider, getEnv: (k: string) => string | undefined): string | null;
28
27
  /**
29
28
  * Human-readable engine names for the config UI. The short ids stay the stored
30
- * value (config-file compat); only the display layer uses these.
29
+ * value, so a config file survives a label change; register.ts is the display
30
+ * layer that maps between them, and search-core uses a label only inside a
31
+ * fallback message.
31
32
  */
32
33
  export declare const SEARCH_PROVIDER_LABELS: Record<SearchProvider, string>;
33
34
  export declare function providerForLabel(label: string): SearchProvider | undefined;
@@ -2,19 +2,18 @@ export const SEARCH_PROVIDERS = ['exa', 'ddg', 'brave'];
2
2
  /**
3
3
  * The API-key env vars an engine needs, in lookup order. Empty = keyless.
4
4
  *
5
- * Declared beside the engine ids rather than inside `search()`, because two
6
- * places ask the question: the search itself, and `searchConfigured` in
7
- * phases.ts, which decides whether the APIS research worker is even given the
8
- * search tool. That second copy was a hand-written re-statement of brave's env
9
- * pair with a comment saying it "mirrors search-core's lookup" — the shape a
10
- * table exists to make impossible.
5
+ * Declared beside the engine ids rather than inside `search()`, because two places
6
+ * ask the question: the search itself, and `searchConfigured` in phases.ts, which
7
+ * decides whether the APIS research worker is handed the search tool at all. Both
8
+ * go through {@link searchProviderKey}, so neither can restate the env pair.
11
9
  */
12
10
  export const SEARCH_PROVIDER_KEY_ENV = {
13
11
  exa: [],
14
12
  ddg: [],
15
13
  brave: ['BRAVE_SEARCH_API_KEY', 'BRAVE_API_KEY']
16
14
  };
17
- /** The engine's key, or `null` when it needs one and none is set. `''` = keyless. */
15
+ /** The engine's key, or `null` when it needs one and none is set. `''` = keyless.
16
+ * The vars are tried in list order and the first non-empty one wins. */
18
17
  export function searchProviderKey(provider, getEnv) {
19
18
  const vars = SEARCH_PROVIDER_KEY_ENV[provider];
20
19
  if (vars.length === 0)
@@ -28,7 +27,9 @@ export function searchProviderKey(provider, getEnv) {
28
27
  }
29
28
  /**
30
29
  * Human-readable engine names for the config UI. The short ids stay the stored
31
- * value (config-file compat); only the display layer uses these.
30
+ * value, so a config file survives a label change; register.ts is the display
31
+ * layer that maps between them, and search-core uses a label only inside a
32
+ * fallback message.
32
33
  */
33
34
  export const SEARCH_PROVIDER_LABELS = {
34
35
  exa: 'Exa',
@@ -1,29 +1,28 @@
1
1
  /**
2
2
  * A one-line startup hint in the TUI, and the whole widget lifetime around it.
3
3
  *
4
- * WHY IT IS ONE MODULE. Two hints exist (brave-warning, reasoning-warning) and
5
- * both had written the same ritual out: the `session_start` subscription, the
6
- * TUI gate, the `setWidget` in a try/catch, the `onTerminalInput` that clears on
7
- * the first keystroke, the unsubscribe, and the swallow for a stale ctx — down
8
- * to a byte-identical comment. Two adapters is a real seam, so the ritual lives
9
- * here once and each hint supplies only its `compose`.
4
+ * WHY IT IS ONE MODULE. Its two callers brave-warning and reasoning-warning
5
+ * would otherwise each write the same ritual: the `session_start` subscription,
6
+ * the TUI gate, the `setWidget` in a try/catch, the `onTerminalInput` that clears
7
+ * on the first keystroke, the unsubscribe, and the swallow for a stale ctx. Here
8
+ * the ritual lives once and each hint supplies only its `compose`.
10
9
  *
11
10
  * The REFINE half is why this is not just deduplication. A hint may learn
12
- * something after it has painted (the reasoning hint probes the model's server),
11
+ * something after it has painted the reasoning hint probes the model's server
13
12
  * and the rule that a refinement must never repaint a widget the user already
14
- * dismissed lived in one closure variable in one of the two files. It is now a
15
- * property of this module, asserted once.
13
+ * dismissed is a property of this module rather than a closure variable in one of
14
+ * the two files. session-hint.test.ts is where it is asserted.
16
15
  */
17
16
  import type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';
18
17
  export interface SessionHint {
19
18
  /** The line to paint now. */
20
19
  text: string;
21
20
  /**
22
- * A later refinement of that line, if this hint has one. Resolving to null —
23
- * or rejecting — leaves the first line standing, so a refinement can never
24
- * remove a warning it was only meant to sharpen. It is fire-and-forget: it
25
- * cannot delay or prevent the first paint, and it is dropped if the user has
26
- * already cleared the hint.
21
+ * A later refinement of that line, if this hint has one. Resolving to null — or
22
+ * rejecting — leaves the first line standing, so a refinement can never remove a
23
+ * warning it was only meant to sharpen. It is fire-and-forget: it cannot delay
24
+ * or prevent the first paint, and a refinement that lands AFTER the user cleared
25
+ * the hint repaints nothing.
27
26
  */
28
27
  refine?: Promise<string | null>;
29
28
  }
@@ -1,18 +1,17 @@
1
1
  /**
2
2
  * A one-line startup hint in the TUI, and the whole widget lifetime around it.
3
3
  *
4
- * WHY IT IS ONE MODULE. Two hints exist (brave-warning, reasoning-warning) and
5
- * both had written the same ritual out: the `session_start` subscription, the
6
- * TUI gate, the `setWidget` in a try/catch, the `onTerminalInput` that clears on
7
- * the first keystroke, the unsubscribe, and the swallow for a stale ctx — down
8
- * to a byte-identical comment. Two adapters is a real seam, so the ritual lives
9
- * here once and each hint supplies only its `compose`.
4
+ * WHY IT IS ONE MODULE. Its two callers brave-warning and reasoning-warning
5
+ * would otherwise each write the same ritual: the `session_start` subscription,
6
+ * the TUI gate, the `setWidget` in a try/catch, the `onTerminalInput` that clears
7
+ * on the first keystroke, the unsubscribe, and the swallow for a stale ctx. Here
8
+ * the ritual lives once and each hint supplies only its `compose`.
10
9
  *
11
10
  * The REFINE half is why this is not just deduplication. A hint may learn
12
- * something after it has painted (the reasoning hint probes the model's server),
11
+ * something after it has painted the reasoning hint probes the model's server
13
12
  * and the rule that a refinement must never repaint a widget the user already
14
- * dismissed lived in one closure variable in one of the two files. It is now a
15
- * property of this module, asserted once.
13
+ * dismissed is a property of this module rather than a closure variable in one of
14
+ * the two files. session-hint.test.ts is where it is asserted.
16
15
  */
17
16
  /**
18
17
  * Register one startup hint.
@@ -19,19 +19,16 @@ export interface ChildOutcome {
19
19
  * The one place worker child-failure is turned into a user-facing message.
20
20
  * Returns `null` when the child succeeded (caller proceeds to format output).
21
21
  *
22
- * It ASKS THE LADDER now (`classifyWorkerFailure` -> `describeWorkerFailure`)
23
- * rather than re-deriving the answer from two fields. It used to be
24
- * `if (aborted) return abortedMessage`, and every kill path also sets `aborted`,
25
- * so a wall-clock kill, a hung command, a dead backend, a loop kill and a user
26
- * ESC all produced the same four words while `childFailureReason`, one line
27
- * later at the only caller that had the richer result, computed the real cause
28
- * and sent it to a debug trail nobody reads. That is the same shape as the bug
29
- * `worker-failure.ts`'s own header records, one layer out: a second author of a
30
- * taxonomy the module exists to own.
22
+ * It ASKS THE LADDER (`classifyWorkerFailure` -> `describeWorkerFailure`) rather
23
+ * than re-deriving the answer from two fields. Every kill path also sets
24
+ * `aborted`, so an `if (aborted) return abortedMessage` would give a wall-clock
25
+ * kill, a hung command, a dead backend, a loop kill and a user ESC the same four
26
+ * words. Through the ladder each gets its own diagnosis.
31
27
  *
32
- * A caller with only a `ChildOutcome` is unaffected. It carries no kill flags,
33
- * so the ladder falls through to `aborted`/`exit` and returns exactly what this
34
- * function returned before which `shared.test.ts` pins.
28
+ * A caller with only a `ChildOutcome` is unaffected: it carries no kill flags, so
29
+ * the ladder falls through to `aborted`/`exit` null on a clean exit, the abort
30
+ * message when aborted, and `Worker exited N` plus the stderr tail otherwise.
31
+ * `shared.test.ts` pins those three.
35
32
  */
36
33
  export declare function formatChildFailure(child: ChildOutcome | (WorkerFailureInput & {
37
34
  stderr?: string;
@@ -39,15 +36,13 @@ export declare function formatChildFailure(child: ChildOutcome | (WorkerFailureI
39
36
  /**
40
37
  * What a worker tool PRODUCED — an answer, or a statement that it has none.
41
38
  *
42
- * This used to be a bare `{text, details}` bag, and "did it succeed" was
43
- * re-derived downstream from `details.childExitCode === 0`. That derivation was
44
- * wrong in the one case it most needed to be right: a signal-killed child reports
45
- * `code ?? 0` = 0 (shared/child-process.ts), so an aborted lookup arrived with
46
- * exit code 0, `docsCacheable`/`fetchCacheable` said yes, and `"Docs lookup
47
- * aborted."` was memoised for the whole run and re-served to every later sibling —
48
- * exactly the failure `abstention.ts` exists to stop, with escalation unable to
49
- * re-fire. `docsFailureResult`'s own contract said it recorded the code "NOT as
50
- * 0"; the value it copied was 0.
39
+ * As a bare `{text, details}` bag, "did it succeed" gets re-derived downstream
40
+ * from `details.childExitCode === 0`. That derivation is wrong in the one case it
41
+ * most needs to be right: node reports a signal kill as `close` with
42
+ * `code === null`, which child-process.ts settles as `code ?? 0` — so an ABORTED
43
+ * lookup arrives carrying exit code 0, the cache rules say yes, and
44
+ * `"Docs lookup aborted."` is memoised for the whole run and re-served to every
45
+ * later sibling.
51
46
  *
52
47
  * Stating the outcome makes that unrepresentable: `makeWorkerTool` stores only an
53
48
  * `answer`, so no cache rule has to know anything about process health, and the
@@ -92,20 +87,21 @@ export interface WorkerToolSpec<TParams extends TSchema, TDetails> {
92
87
  run(params: Static<TParams>, signal: AbortSignal | undefined, ctx: ExtensionContext): Promise<WorkerOutcome<TDetails>>;
93
88
  renderCall(args: Static<TParams>, theme: Theme): Text;
94
89
  /**
95
- * Per-run research-cache policy (F10). Return a stable cache key for this call —
90
+ * Per-run research-cache policy. Return a stable cache key for this call —
96
91
  * a result keyed on it is a deterministic function of the inputs that does not
97
92
  * change within a run, so a later sibling task can reuse it instead of re-running
98
93
  * the network fetch + child summariser. Return `null` to opt a particular call
99
94
  * OUT of caching (e.g. a project-source `.` lookup, whose answer the working tree
100
95
  * mutates within a run). Omit entirely and the tool is never cached. The stored
101
- * key is namespaced by tool name, so keys need only be unique within a tool.
96
+ * key is namespaced by tool name joined with a literal NUL, which is why the
97
+ * separator is invisible to `grep` and to a plain editor.
102
98
  */
103
99
  cacheKey?(params: Static<TParams>): string | null;
104
100
  /**
105
101
  * The npm package this call's answer is ABOUT, for a package-scoped tool (docs).
106
102
  * Recorded as structured provenance on the cache entry so a resume can drop just the
107
- * entries whose package moved version, instead of the whole run's cache (run 14: a
108
- * greenfield run adds packages every few tasks, so a whole-file gate never holds).
103
+ * entries whose package moved version, instead of the whole run's cache. A
104
+ * greenfield run adds packages every few tasks, so a whole-file gate never holds.
109
105
  * Omit — or return undefined — and the entry survives every resume of its run.
110
106
  */
111
107
  cachePkg?(params: Static<TParams>): string | undefined;
Binary file
@@ -1,11 +1,18 @@
1
1
  import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
2
2
  /**
3
- * Loaded via `-e` into the TOOLING research worker only. Two in-run thrash
4
- * blocks, each returning a reason the model receives as an error tool result
5
- * (agent-loop: block → createErrorToolResult) so the worker continues instead of
6
- * looping:
7
- * - read: blocks a re-read of LINES already delivered; forward paging passes.
8
- * - grep/find/ls: blocks an identical repeat of the same call (args-keyed).
9
- * See single-read-guard.ts for why this is scoped to TOOLING.
3
+ * Loaded via `-e` into two kinds of child, both of which have their source
4
+ * INLINED and so can only be thrashing when they re-read: the TOOLING research
5
+ * worker (phases.ts) and the planning children (auto-orchestrator.ts).
6
+ *
7
+ * Two in-run blocks. Each returns a `reason`, which pi turns into an error tool
8
+ * result `agent-loop.js` does `createErrorToolResult(beforeResult.reason)` on a
9
+ * blocked call so the child reads the explanation and continues instead of
10
+ * dying:
11
+ * - read: blocks a re-read of LINES already delivered; forward paging passes,
12
+ * and the reason names the line to resume from.
13
+ * - grep/find/ls: blocks a byte-identical repeat, keyed on the args; a call
14
+ * with any different argument passes.
15
+ * A tool outside this set is never touched. See single-read-guard.ts for why the
16
+ * read-once rule is safe only where it is armed.
10
17
  */
11
18
  export default function (pi: ExtensionAPI): void;
@@ -3,13 +3,20 @@ import { RepeatedCallGuard, SingleReadGuard } from './single-read-guard.js';
3
3
  /** Read-like tools whose byte-identical repeats are blocked (read is handled separately). */
4
4
  const DEDUP_TOOLS = new Set(['grep', 'find', 'ls']);
5
5
  /**
6
- * Loaded via `-e` into the TOOLING research worker only. Two in-run thrash
7
- * blocks, each returning a reason the model receives as an error tool result
8
- * (agent-loop: block → createErrorToolResult) so the worker continues instead of
9
- * looping:
10
- * - read: blocks a re-read of LINES already delivered; forward paging passes.
11
- * - grep/find/ls: blocks an identical repeat of the same call (args-keyed).
12
- * See single-read-guard.ts for why this is scoped to TOOLING.
6
+ * Loaded via `-e` into two kinds of child, both of which have their source
7
+ * INLINED and so can only be thrashing when they re-read: the TOOLING research
8
+ * worker (phases.ts) and the planning children (auto-orchestrator.ts).
9
+ *
10
+ * Two in-run blocks. Each returns a `reason`, which pi turns into an error tool
11
+ * result `agent-loop.js` does `createErrorToolResult(beforeResult.reason)` on a
12
+ * blocked call so the child reads the explanation and continues instead of
13
+ * dying:
14
+ * - read: blocks a re-read of LINES already delivered; forward paging passes,
15
+ * and the reason names the line to resume from.
16
+ * - grep/find/ls: blocks a byte-identical repeat, keyed on the args; a call
17
+ * with any different argument passes.
18
+ * A tool outside this set is never touched. See single-read-guard.ts for why the
19
+ * read-once rule is safe only where it is armed.
13
20
  */
14
21
  export default function (pi) {
15
22
  const reads = new SingleReadGuard();