@mjasnikovs/pi-task 0.38.29 → 0.38.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (370) hide show
  1. package/dist/config/config.d.ts +70 -70
  2. package/dist/config/config.js +26 -35
  3. package/dist/config/extension-list.d.ts +6 -5
  4. package/dist/config/extension-list.js +3 -2
  5. package/dist/config/reasoning-args.d.ts +9 -7
  6. package/dist/config/reasoning-args.js +12 -10
  7. package/dist/config/reasoning.d.ts +44 -105
  8. package/dist/config/reasoning.js +27 -704
  9. package/dist/config/register.d.ts +34 -48
  10. package/dist/config/register.js +41 -51
  11. package/dist/config/tool-list.d.ts +16 -16
  12. package/dist/config/tool-list.js +1 -1
  13. package/dist/remote/bridge.d.ts +19 -10
  14. package/dist/remote/bridge.js +3 -2
  15. package/dist/remote/broadcast.js +3 -1
  16. package/dist/remote/events.js +12 -11
  17. package/dist/remote/history.d.ts +1 -1
  18. package/dist/remote/protocol.d.ts +6 -3
  19. package/dist/remote/protocol.js +2 -1
  20. package/dist/remote/push.d.ts +16 -16
  21. package/dist/remote/push.js +27 -27
  22. package/dist/remote/register.d.ts +3 -3
  23. package/dist/remote/register.js +17 -19
  24. package/dist/remote/server.d.ts +9 -8
  25. package/dist/remote/server.js +15 -14
  26. package/dist/remote/session-state.d.ts +5 -4
  27. package/dist/remote/session-state.js +8 -5
  28. package/dist/remote/sw.d.ts +7 -6
  29. package/dist/remote/sw.js +7 -6
  30. package/dist/remote/tailscale.d.ts +4 -2
  31. package/dist/remote/tailscale.js +4 -2
  32. package/dist/remote/ui-highlight.js +6 -5
  33. package/dist/remote/ui-render.js +4 -4
  34. package/dist/remote/ui-script.js +24 -24
  35. package/dist/remote/ui-styles.d.ts +1 -1
  36. package/dist/remote/ui-styles.js +10 -13
  37. package/dist/remote/ui-tools.js +9 -6
  38. package/dist/shared/child-extensions.d.ts +29 -17
  39. package/dist/shared/child-extensions.js +29 -17
  40. package/dist/shared/child-output.d.ts +30 -24
  41. package/dist/shared/child-output.js +25 -17
  42. package/dist/shared/child-process.d.ts +47 -40
  43. package/dist/shared/child-process.js +50 -59
  44. package/dist/shared/command-watchdog.d.ts +22 -16
  45. package/dist/shared/command-watchdog.js +28 -21
  46. package/dist/shared/fs-text.d.ts +16 -10
  47. package/dist/shared/fs-text.js +16 -10
  48. package/dist/shared/git-runner.d.ts +25 -25
  49. package/dist/shared/git-runner.js +25 -25
  50. package/dist/shared/leaked-tool-call.d.ts +17 -11
  51. package/dist/shared/leaked-tool-call.js +23 -15
  52. package/dist/shared/model-endpoint.d.ts +29 -16
  53. package/dist/shared/model-endpoint.js +33 -21
  54. package/dist/shared/pi-invocation.d.ts +7 -4
  55. package/dist/shared/pi-invocation.js +12 -7
  56. package/dist/shared/pkg-version.d.ts +13 -5
  57. package/dist/shared/pkg-version.js +13 -5
  58. package/dist/shared/reasoning-capability.d.ts +35 -24
  59. package/dist/shared/reasoning-capability.js +35 -24
  60. package/dist/shared/stream-watchdog.d.ts +60 -44
  61. package/dist/shared/stream-watchdog.js +62 -45
  62. package/dist/task/accept-debt.d.ts +41 -43
  63. package/dist/task/accept-debt.js +73 -65
  64. package/dist/task/api-synthesis.d.ts +24 -21
  65. package/dist/task/api-synthesis.js +32 -26
  66. package/dist/task/apis-contract.d.ts +32 -64
  67. package/dist/task/apis-contract.js +32 -64
  68. package/dist/task/artifact-closure.d.ts +27 -13
  69. package/dist/task/artifact-closure.js +95 -67
  70. package/dist/task/auto-commit.d.ts +46 -35
  71. package/dist/task/auto-commit.js +51 -38
  72. package/dist/task/auto-io.d.ts +45 -25
  73. package/dist/task/auto-io.js +57 -29
  74. package/dist/task/auto-orchestrator.d.ts +26 -24
  75. package/dist/task/auto-orchestrator.js +178 -162
  76. package/dist/task/auto-prompts.d.ts +36 -24
  77. package/dist/task/auto-prompts.js +40 -26
  78. package/dist/task/autofix-ledger.d.ts +27 -25
  79. package/dist/task/autofix-ledger.js +29 -26
  80. package/dist/task/batch-test-task.d.ts +20 -12
  81. package/dist/task/batch-test-task.js +67 -60
  82. package/dist/task/boot-probe.d.ts +60 -44
  83. package/dist/task/boot-probe.js +91 -72
  84. package/dist/task/cancel-input.d.ts +30 -16
  85. package/dist/task/cancel-input.js +20 -11
  86. package/dist/task/cancel-points.d.ts +27 -20
  87. package/dist/task/cancel-points.js +30 -22
  88. package/dist/task/child-runner.d.ts +46 -51
  89. package/dist/task/child-runner.js +48 -49
  90. package/dist/task/child-status.d.ts +23 -16
  91. package/dist/task/child-status.js +23 -16
  92. package/dist/task/clamp-output.js +12 -5
  93. package/dist/task/command-run.d.ts +31 -28
  94. package/dist/task/command-run.js +44 -35
  95. package/dist/task/command-shrink.d.ts +25 -18
  96. package/dist/task/command-shrink.js +37 -31
  97. package/dist/task/command-watchdog.d.ts +9 -6
  98. package/dist/task/command-watchdog.js +21 -15
  99. package/dist/task/context-attribution.d.ts +34 -26
  100. package/dist/task/context-attribution.js +34 -26
  101. package/dist/task/context-silence.d.ts +39 -29
  102. package/dist/task/context-silence.js +35 -25
  103. package/dist/task/context-usage.d.ts +16 -9
  104. package/dist/task/context-usage.js +16 -9
  105. package/dist/task/contracts.d.ts +8 -4
  106. package/dist/task/contracts.js +25 -17
  107. package/dist/task/coverage-loop.d.ts +22 -18
  108. package/dist/task/coverage-loop.js +35 -30
  109. package/dist/task/critique-probes.d.ts +13 -14
  110. package/dist/task/critique-probes.js +50 -39
  111. package/dist/task/debug-log.d.ts +13 -5
  112. package/dist/task/debug-log.js +32 -20
  113. package/dist/task/decompose-fidelity.d.ts +11 -9
  114. package/dist/task/decompose-fidelity.js +38 -33
  115. package/dist/task/decompose-granularity.d.ts +41 -38
  116. package/dist/task/decompose-granularity.js +41 -38
  117. package/dist/task/deep-render-check.d.ts +22 -14
  118. package/dist/task/deep-render-check.js +40 -31
  119. package/dist/task/dropped-input.d.ts +12 -7
  120. package/dist/task/dropped-input.js +5 -2
  121. package/dist/task/enforce-attribution.d.ts +38 -47
  122. package/dist/task/enforce-attribution.js +46 -52
  123. package/dist/task/enforce-guidelines.d.ts +31 -20
  124. package/dist/task/enforce-guidelines.js +32 -21
  125. package/dist/task/enrichment.d.ts +7 -2
  126. package/dist/task/enrichment.js +26 -14
  127. package/dist/task/env-notes.d.ts +16 -7
  128. package/dist/task/env-notes.js +48 -31
  129. package/dist/task/env-template-closure.d.ts +4 -4
  130. package/dist/task/env-template-closure.js +42 -34
  131. package/dist/task/external-context.d.ts +28 -21
  132. package/dist/task/external-context.js +17 -12
  133. package/dist/task/failure-classifier.d.ts +4 -5
  134. package/dist/task/failure-classifier.js +6 -7
  135. package/dist/task/file-inventory.d.ts +15 -11
  136. package/dist/task/file-inventory.js +25 -22
  137. package/dist/task/final-gate-fix.d.ts +74 -86
  138. package/dist/task/final-gate-fix.js +97 -116
  139. package/dist/task/final-gate-progress.d.ts +29 -46
  140. package/dist/task/final-gate-progress.js +40 -51
  141. package/dist/task/final-gate.d.ts +64 -97
  142. package/dist/task/final-gate.js +192 -199
  143. package/dist/task/fix-child.d.ts +21 -27
  144. package/dist/task/fix-child.js +21 -27
  145. package/dist/task/foreign-path.d.ts +6 -5
  146. package/dist/task/foreign-path.js +0 -0
  147. package/dist/task/frozen-conflict.d.ts +9 -10
  148. package/dist/task/frozen-conflict.js +61 -64
  149. package/dist/task/frozen-path-guard.d.ts +35 -14
  150. package/dist/task/frozen-path-guard.js +56 -39
  151. package/dist/task/gate-child.d.ts +27 -28
  152. package/dist/task/gate-child.js +36 -35
  153. package/dist/task/gate-deps.d.ts +34 -27
  154. package/dist/task/gate-deps.js +169 -159
  155. package/dist/task/gate-tally.d.ts +77 -80
  156. package/dist/task/gate-tally.js +65 -68
  157. package/dist/task/git-state-guard.d.ts +15 -11
  158. package/dist/task/git-state-guard.js +76 -66
  159. package/dist/task/impl-widget.d.ts +25 -16
  160. package/dist/task/impl-widget.js +27 -17
  161. package/dist/task/implementation-thinking.d.ts +33 -31
  162. package/dist/task/implementation-thinking.js +5 -6
  163. package/dist/task/implementation-turn.d.ts +34 -31
  164. package/dist/task/implementation-turn.js +29 -27
  165. package/dist/task/inline-markdown.d.ts +20 -7
  166. package/dist/task/inline-markdown.js +15 -6
  167. package/dist/task/launch-config-gap.js +25 -39
  168. package/dist/task/launch-contract.d.ts +18 -21
  169. package/dist/task/launch-contract.js +28 -30
  170. package/dist/task/launch-manifest.d.ts +6 -2
  171. package/dist/task/launch-manifest.js +35 -34
  172. package/dist/task/ledger.js +16 -14
  173. package/dist/task/lint-fix.d.ts +6 -8
  174. package/dist/task/lint-fix.js +67 -69
  175. package/dist/task/loop-detector.d.ts +9 -8
  176. package/dist/task/loop-detector.js +16 -12
  177. package/dist/task/mid-run-input.d.ts +17 -15
  178. package/dist/task/mid-run-input.js +17 -15
  179. package/dist/task/orchestrator.d.ts +24 -28
  180. package/dist/task/orchestrator.js +62 -64
  181. package/dist/task/orientation.d.ts +18 -23
  182. package/dist/task/orientation.js +24 -31
  183. package/dist/task/owned-freeze-conflict.d.ts +21 -20
  184. package/dist/task/owned-freeze-conflict.js +52 -85
  185. package/dist/task/owned-freeze-reassign.d.ts +40 -60
  186. package/dist/task/owned-freeze-reassign.js +41 -61
  187. package/dist/task/parsers.d.ts +4 -2
  188. package/dist/task/parsers.js +4 -4
  189. package/dist/task/phases.d.ts +41 -48
  190. package/dist/task/phases.js +179 -248
  191. package/dist/task/plan-io.d.ts +6 -7
  192. package/dist/task/plan-io.js +6 -7
  193. package/dist/task/plan-orchestrator.d.ts +10 -8
  194. package/dist/task/plan-orchestrator.js +14 -10
  195. package/dist/task/plan-prompts.d.ts +6 -5
  196. package/dist/task/plan-prompts.js +6 -5
  197. package/dist/task/plan-readonly.d.ts +4 -5
  198. package/dist/task/plan-readonly.js +4 -5
  199. package/dist/task/plan-rounds.d.ts +17 -29
  200. package/dist/task/plan-rounds.js +21 -34
  201. package/dist/task/plan-session.d.ts +58 -72
  202. package/dist/task/plan-session.js +61 -83
  203. package/dist/task/probe-gaming.d.ts +28 -27
  204. package/dist/task/probe-gaming.js +0 -0
  205. package/dist/task/prohibition-probe.d.ts +14 -16
  206. package/dist/task/prompts.d.ts +3 -4
  207. package/dist/task/prompts.js +17 -26
  208. package/dist/task/qa-transcript.d.ts +15 -22
  209. package/dist/task/qa-transcript.js +15 -21
  210. package/dist/task/question-box.d.ts +17 -13
  211. package/dist/task/question-box.js +19 -15
  212. package/dist/task/question-dedup.d.ts +6 -7
  213. package/dist/task/question-dedup.js +13 -14
  214. package/dist/task/question-dialog.d.ts +22 -32
  215. package/dist/task/question-dialog.js +22 -32
  216. package/dist/task/question-source.d.ts +18 -44
  217. package/dist/task/question-source.js +22 -51
  218. package/dist/task/refuted-constraint.d.ts +11 -31
  219. package/dist/task/refuted-constraint.js +27 -51
  220. package/dist/task/regenerable-artifacts.d.ts +12 -31
  221. package/dist/task/regenerable-artifacts.js +12 -31
  222. package/dist/task/render-check.d.ts +11 -22
  223. package/dist/task/render-check.js +33 -46
  224. package/dist/task/repo-health-check.d.ts +10 -14
  225. package/dist/task/repo-health-check.js +17 -23
  226. package/dist/task/requirements.d.ts +38 -71
  227. package/dist/task/requirements.js +78 -126
  228. package/dist/task/research-fanout-budget.d.ts +51 -88
  229. package/dist/task/research-fanout-budget.js +51 -88
  230. package/dist/task/research-worker.d.ts +29 -39
  231. package/dist/task/research-worker.js +37 -61
  232. package/dist/task/resume-gap.d.ts +14 -15
  233. package/dist/task/root-cause-repair.d.ts +9 -9
  234. package/dist/task/root-cause-repair.js +28 -40
  235. package/dist/task/run-bracket.d.ts +10 -13
  236. package/dist/task/run-end.d.ts +12 -22
  237. package/dist/task/run-end.js +8 -16
  238. package/dist/task/run-final-gate.d.ts +19 -21
  239. package/dist/task/run-final-gate.js +62 -80
  240. package/dist/task/runner-globs.d.ts +12 -13
  241. package/dist/task/runner-globs.js +12 -13
  242. package/dist/task/runner-resolve.d.ts +9 -9
  243. package/dist/task/runner-resolve.js +22 -23
  244. package/dist/task/script-escape.d.ts +10 -12
  245. package/dist/task/script-escape.js +13 -14
  246. package/dist/task/serve-entry.d.ts +1 -1
  247. package/dist/task/serve-entry.js +22 -25
  248. package/dist/task/service-blocks.js +4 -2
  249. package/dist/task/shipped-source.d.ts +11 -29
  250. package/dist/task/shipped-source.js +11 -29
  251. package/dist/task/skip-escape.js +10 -14
  252. package/dist/task/spec-urls.d.ts +26 -65
  253. package/dist/task/spec-urls.js +26 -65
  254. package/dist/task/spec-validation.d.ts +17 -20
  255. package/dist/task/spec-validation.js +17 -20
  256. package/dist/task/stall-detector.d.ts +23 -30
  257. package/dist/task/stall-detector.js +23 -30
  258. package/dist/task/stream-watchdog.d.ts +14 -12
  259. package/dist/task/stream-watchdog.js +14 -12
  260. package/dist/task/substitution-probe.d.ts +17 -20
  261. package/dist/task/substitution-probe.js +17 -20
  262. package/dist/task/task-gates.d.ts +36 -41
  263. package/dist/task/task-gates.js +95 -106
  264. package/dist/task/task-io.d.ts +4 -4
  265. package/dist/task/task-io.js +4 -4
  266. package/dist/task/task-parsers.js +4 -3
  267. package/dist/task/task-provenance.d.ts +2 -2
  268. package/dist/task/task-provenance.js +11 -13
  269. package/dist/task/task-types.d.ts +4 -3
  270. package/dist/task/terminal-outcome.d.ts +14 -16
  271. package/dist/task/terminal-outcome.js +12 -14
  272. package/dist/task/test-assembly.d.ts +13 -20
  273. package/dist/task/test-assembly.js +13 -20
  274. package/dist/task/timings.d.ts +5 -3
  275. package/dist/task/timings.js +5 -3
  276. package/dist/task/title-label.d.ts +9 -4
  277. package/dist/task/title-label.js +9 -4
  278. package/dist/task/type-only-answer.d.ts +44 -52
  279. package/dist/task/type-only-answer.js +44 -52
  280. package/dist/task/unfailable-command.d.ts +18 -24
  281. package/dist/task/unfailable-command.js +21 -27
  282. package/dist/task/unknown-routing.d.ts +10 -4
  283. package/dist/task/unknown-routing.js +10 -4
  284. package/dist/task/user-directives.d.ts +5 -8
  285. package/dist/task/user-directives.js +5 -8
  286. package/dist/task/verify-quality.d.ts +18 -22
  287. package/dist/task/verify-quality.js +45 -46
  288. package/dist/task/verify-reconcile.d.ts +15 -10
  289. package/dist/task/verify-reconcile.js +45 -43
  290. package/dist/task/verify-resolution.d.ts +24 -20
  291. package/dist/task/verify-resolution.js +51 -50
  292. package/dist/task/verify-work.d.ts +59 -66
  293. package/dist/task/verify-work.js +101 -138
  294. package/dist/task/widget.d.ts +15 -14
  295. package/dist/task/widget.js +22 -17
  296. package/dist/task/wiring-claims.d.ts +25 -32
  297. package/dist/task/wiring-claims.js +30 -35
  298. package/dist/task/write-guard.d.ts +39 -39
  299. package/dist/task/write-guard.js +48 -51
  300. package/dist/task/yolo.d.ts +34 -30
  301. package/dist/task/yolo.js +42 -37
  302. package/dist/workers/abstention.d.ts +21 -41
  303. package/dist/workers/abstention.js +27 -48
  304. package/dist/workers/brave-search.d.ts +4 -3
  305. package/dist/workers/brave-search.js +5 -2
  306. package/dist/workers/brave-warning.d.ts +7 -4
  307. package/dist/workers/brave-warning.js +19 -7
  308. package/dist/workers/ddg-search.d.ts +6 -6
  309. package/dist/workers/ddg-search.js +18 -12
  310. package/dist/workers/docs-cache.js +5 -2
  311. package/dist/workers/docs-chunk.d.ts +30 -37
  312. package/dist/workers/docs-chunk.js +37 -41
  313. package/dist/workers/docs-core.d.ts +28 -44
  314. package/dist/workers/docs-core.js +25 -44
  315. package/dist/workers/docs-index.js +4 -3
  316. package/dist/workers/docs-lookup.d.ts +15 -22
  317. package/dist/workers/docs-lookup.js +12 -21
  318. package/dist/workers/docs-project.d.ts +15 -9
  319. package/dist/workers/docs-project.js +17 -10
  320. package/dist/workers/docs-resolve.d.ts +19 -20
  321. package/dist/workers/docs-resolve.js +35 -32
  322. package/dist/workers/docs-retrieve.d.ts +5 -6
  323. package/dist/workers/docs-retrieve.js +18 -15
  324. package/dist/workers/exa-search.d.ts +9 -6
  325. package/dist/workers/exa-search.js +23 -12
  326. package/dist/workers/fetch-core.d.ts +13 -16
  327. package/dist/workers/fetch-core.js +23 -23
  328. package/dist/workers/focused-extractor.d.ts +12 -12
  329. package/dist/workers/focused-extractor.js +16 -19
  330. package/dist/workers/html-clean.js +24 -14
  331. package/dist/workers/http-request.d.ts +28 -20
  332. package/dist/workers/http-request.js +22 -17
  333. package/dist/workers/npm-version.d.ts +28 -11
  334. package/dist/workers/npm-version.js +24 -15
  335. package/dist/workers/phantom-imports.d.ts +15 -12
  336. package/dist/workers/phantom-imports.js +30 -24
  337. package/dist/workers/pi-worker-core.d.ts +69 -71
  338. package/dist/workers/pi-worker-core.js +100 -109
  339. package/dist/workers/pi-worker-docs.d.ts +24 -19
  340. package/dist/workers/pi-worker-docs.js +67 -76
  341. package/dist/workers/pi-worker-fetch.d.ts +7 -3
  342. package/dist/workers/pi-worker-fetch.js +27 -19
  343. package/dist/workers/pi-worker-search.js +12 -8
  344. package/dist/workers/pi-worker.d.ts +9 -4
  345. package/dist/workers/pi-worker.js +21 -14
  346. package/dist/workers/reasoning-warning.d.ts +18 -17
  347. package/dist/workers/reasoning-warning.js +22 -20
  348. package/dist/workers/research-cache.js +50 -78
  349. package/dist/workers/search-core.js +7 -5
  350. package/dist/workers/search-types.d.ts +10 -9
  351. package/dist/workers/search-types.js +9 -8
  352. package/dist/workers/session-hint.d.ts +13 -14
  353. package/dist/workers/session-hint.js +8 -9
  354. package/dist/workers/shared.d.ts +21 -25
  355. package/dist/workers/shared.js +0 -0
  356. package/dist/workers/single-read-extension.d.ts +14 -7
  357. package/dist/workers/single-read-extension.js +14 -7
  358. package/dist/workers/single-read-guard.d.ts +25 -28
  359. package/dist/workers/single-read-guard.js +32 -32
  360. package/dist/workers/typeonly-log.d.ts +12 -9
  361. package/dist/workers/typeonly-log.js +29 -33
  362. package/dist/workers/worker-channels.d.ts +15 -23
  363. package/dist/workers/worker-channels.js +15 -23
  364. package/dist/workers/worker-failure.d.ts +38 -46
  365. package/dist/workers/worker-failure.js +31 -39
  366. package/dist/workers/worker-kill.d.ts +25 -26
  367. package/dist/workers/worker-kill.js +16 -19
  368. package/dist/workers/worker-profiles.d.ts +43 -53
  369. package/dist/workers/worker-profiles.js +30 -38
  370. package/package.json +10 -8
@@ -2,41 +2,43 @@
2
2
  * git-state-guard — deterministic repo-state snapshot/reconcile around the
3
3
  * read-only gate children (verify, recommend).
4
4
  *
5
- * The failure this closes (proven twice on mx5): those children hold a `read,bash`
6
- * contract whose "never modify the tree" clause is prompt-level only, and the live
7
- * local model breaks it. Run 6's verify child ran `git stash; git checkout HEAD~1;
8
- * tsc; git checkout HEAD` with NO pop the task's whole uncommitted implementation
9
- * vanished into a stash, the verify judged an empty tree (a full re-implementation
10
- * was burned), and the orphaned stash detonated two days later when a later impl
11
- * turn popped it onto a 14-commits-newer HEAD (unresolvable UU conflict, the
12
- * /task-auto checklist reverted to a stale state). The same child has been observed
13
- * running `eslint --fix .` mid-verification and ad-hoc DDL against the test DB.
5
+ * Those children hold a `read,bash` contract whose "never modify the tree" clause
6
+ * is PROMPT-LEVEL ONLY: `bash` can stash, check out, commit and rewrite files, and
7
+ * nothing but the prompt says not to. A child that stashes the task's uncommitted
8
+ * work and never pops it leaves the verify judging an empty tree AND leaves an
9
+ * orphan stash that only detonates when something later pops it onto a moved HEAD.
10
+ * So this is capability-shaped rather than prompt-shaped: snapshot the repo state
11
+ * BEFORE the child runs, deterministically restore whatever it moved afterwards,
12
+ * no model in the loop.
14
13
  *
15
- * Prompt rules are evidence-insufficient for this class (FROZEN-CONTRACT framing
16
- * A/B'd 0–1/5 compliance), so this is a capability-shaped fix: snapshot the repo
17
- * state BEFORE the child runs, and afterwards deterministically restore anything
18
- * it moved no model in the loop.
19
- *
20
- * What is captured / reconciled:
21
- * - HEAD (sha + symbolic branch ref): a child that checked out another commit
22
- * and never came back is checked back out.
23
- * - The WORKTREE CONTENT as a git tree object, built through a temporary index
24
- * (`read-tree --empty` + `add -A` + `write-tree`, excluding .pi-tasks — the
25
- * gate's own debug logs land there DURING the run). This snapshots tracked
26
- * *and* untracked (non-ignored) files without touching the real index or the
27
- * stash. Restoration re-materialises every changed/deleted file from the
28
- * snapshot tree and deletes files the child created.
14
+ * What is captured and reconciled each one run against a real repo:
15
+ * - HEAD (sha + symbolic branch ref). A child that checked out another commit,
16
+ * detached, is put back on its branch AND its sha. LIMIT: a child that COMMITS
17
+ * on the current branch is NOT undone the restore checks out
18
+ * `before.branchRef`, and that branch now points at the child's commit, so the
19
+ * `checked HEAD back out to <branch>` line is honest about the ref and says
20
+ * nothing about the sha. The move is still classed verdict-tainting, so the
21
+ * verdict is discarded; the commit stays.
22
+ * - The WORKTREE CONTENT as a git tree object, built through a THROWAWAY index
23
+ * (`read-tree --empty` + `add -A` + `write-tree`, excluding `.pi-tasks` — the
24
+ * gate's own debug logs land there DURING the run). Measured on a repo with a
25
+ * staged file, an untracked file, a gitignored file and a `.pi-tasks/` log: the
26
+ * tree holds the tracked and untracked files and neither of the other two, and
27
+ * the REAL index still shows the same staged path afterwards. Restoration
28
+ * re-materialises every changed or deleted file and deletes what the child
29
+ * created.
29
30
  * - The STASH ref: entries the child pushed are dropped AFTER the worktree is
30
- * restored from the snapshot (the snapshot, not the stash, is the source of
31
- * truth), so no landmine stash survives the reconcile.
31
+ * restored from the snapshot the snapshot, not the stash, is the source of
32
+ * truth so no orphan stash survives the reconcile.
32
33
  *
33
34
  * The real index's staging state is deliberately NOT restored: pre-commit gate
34
35
  * children run against a tree whose work is unstaged, and the auto-commit that
35
- * follows re-stages everything with `add -A` anyway.
36
+ * follows re-stages everything with `git add -A` (auto-commit.ts:192).
36
37
  *
37
- * Everything is best-effort: a repo where git itself fails (not a work tree, git
38
- * missing) disables the guard (capture returns ok:false and reconcile no-ops)
39
- * the gate must keep working in non-git projects exactly as before.
38
+ * Everything is best-effort: a repo where git fails disables the guard capture
39
+ * returns `ok: false` and reconcile no-ops. Measured: a fresh `git init` with no
40
+ * commits answers non-zero to `rev-parse -q --verify HEAD`, which is the unborn-HEAD
41
+ * case the capture bails on.
40
42
  */
41
43
  import { readFileSync } from 'node:fs';
42
44
  import * as fsp from 'node:fs/promises';
@@ -48,29 +50,30 @@ import { isRegenerableArtifact } from './regenerable-artifacts.js';
48
50
  const EXCLUDE_TASKS_DIR = ':(exclude).pi-tasks';
49
51
  /**
50
52
  * Untracked paths that are regenerable test/build OUTPUT, not graded source. A gate
51
- * child creating or rewriting one of these has not mutated the work under judgement,
52
- * so its verdict stands. Gitignored files never reach the snapshot (git add -A skips
53
- * them); this list is for the ones a typical project leaves UNIGNORED — Playwright's
54
- * `test-results/` and `playwright-report/` above all, the exact churn that discarded
55
- * verify verdicts across mx5 run 9. Kept deliberately narrow: anything not matched
56
- * here that a child modifies/deletes is treated as graded state (verdict-tainting).
53
+ * child creating or rewriting one of these has not mutated the work under
54
+ * judgement, so its verdict stands. Gitignored files never reach the snapshot at
55
+ * all — measured, `add -A` skips them — so this list is for the ones a typical
56
+ * project leaves UNIGNORED, `test-results/` and `playwright-report/` above all.
57
+ * Kept deliberately narrow: anything NOT matched here that a child modifies or
58
+ * deletes is graded state, and taints the verdict.
57
59
  *
58
- * The list itself now lives in `regenerable-artifacts.ts` the deletion guard and
59
- * the per-task commit need the same knowledge, and three private copies of it is
60
- * how mx5 run 20 spent two thirds of its repair budget on three screenshots.
60
+ * The list lives in `regenerable-artifacts.ts` because three call sites need the
61
+ * same knowledge this guard, the write-guard's deletion check, and the per-task
62
+ * commit and three private copies would drift.
61
63
  */
62
64
  const isBenignArtifact = isRegenerableArtifact;
63
65
  /**
64
- * Regenerable machine state that is benign EVEN WHEN TRACKED a project that
65
- * mistakenly commits it (mx5 run 10 does exactly this) must not have a gate child's
66
- * incidental rewrite of it discard the verdict. Two classes:
67
- * - Playwright component-test build cache (`ctCacheDir` run 10 committed 60+
68
- * `.playwright-cache/assets/*.js` bundles; a `test:ct` run rewrites them every
69
- * time), and
70
- * - the test runner's `.last-run.json` run-state file.
71
- * DELIBERATELY narrow: snapshot BASELINE images (`*-snapshots/*.png`) are NOT here —
66
+ * Regenerable machine state that is benign EVEN WHEN TRACKED, so a project that
67
+ * commits it does not have a child's incidental rewrite discard the verdict. Two
68
+ * classes: the component-test build cache under `ctCacheDir`, which a component
69
+ * test run rewrites every time, and the test runner's `.last-run.json` run-state
70
+ * file.
71
+ *
72
+ * DELIBERATELY narrow. Snapshot BASELINE images (`*-snapshots/*.png`) are NOT here:
72
73
  * a child that rewrites a baseline to make a screenshot test pass is the real
73
- * mutate-to-pass catch (run 10's other half), so those stay verdict-tainting.
74
+ * mutate-to-pass catch. Measured on a real repo a tracked file under a custom
75
+ * `ctCacheDir` and a tracked `.last-run.json` both restore WITHOUT tainting, while
76
+ * a tracked `tests/a-snapshots/x.png` taints.
74
77
  */
75
78
  const ALWAYS_REGENERABLE_PATTERNS = [/(?:^|\/)\.last-run\.json$/];
76
79
  /** Playwright config files that may declare a custom `ctCacheDir`. */
@@ -80,7 +83,10 @@ const CT_CONFIG_FILES = [
80
83
  'playwright.config.ts',
81
84
  'playwright.config.js'
82
85
  ];
83
- /** ctCacheDir defaults Playwright uses when a config does not override it. */
86
+ /** The ctCacheDir values assumed when no config declares one. Playwright is not a
87
+ * dependency here, so these are not verifiable against an installed package —
88
+ * what IS verified is that a config declaring `ctCacheDir: './custom-cache/'` is
89
+ * parsed and its directory treated as regenerable. */
84
90
  const DEFAULT_CT_CACHE_DIRS = ['.playwright-cache', 'playwright/.cache'];
85
91
  /**
86
92
  * The component-test cache dir(s) for this project: the `ctCacheDir` any Playwright
@@ -183,14 +189,15 @@ function pushCapped(actions, verb, paths) {
183
189
  }
184
190
  /**
185
191
  * Restore every file recorded in `beforeTree` (content + deletions) and remove
186
- * files that exist in `afterTree` but not in `beforeTree` (files the child
187
- * created). Uses a throwaway index seeded from the snapshot tree; `checkout-index
192
+ * files that exist in `afterTree` but not in `beforeTree` the ones the child
193
+ * created. Uses a throwaway index seeded from the snapshot tree; `checkout-index
188
194
  * -a -f` re-materialises the snapshot verbatim.
189
195
  *
190
- * Returns whether any restored change was *verdict-tainting* a modified/deleted
191
- * path that is tracked-in-HEAD or an untracked non-artifact (see isBenignArtifact).
192
- * Creations and test-runner-artifact churn restore identically but do NOT taint.
193
- * Each changed path is itemised (capped) so the gate trail says WHICH files moved.
196
+ * Returns whether any restored change was *verdict-tainting*: a modified or
197
+ * deleted path that is tracked-in-HEAD, or an untracked non-artifact (see
198
+ * isBenignArtifact). Creations and test-runner-artifact churn restore identically
199
+ * but do NOT taint. Each changed path is itemised, capped, so the gate trail says
200
+ * WHICH files moved.
194
201
  */
195
202
  async function restoreWorktree(cwd, git, beforeTree, afterTree, tracked, ctCacheDirs, actions) {
196
203
  const tmpIndex = path.join(os.tmpdir(), `pi-task-guard-restore-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`);
@@ -209,8 +216,11 @@ async function restoreWorktree(cwd, git, beforeTree, afterTree, tracked, ctCache
209
216
  const trimmed = line.trim();
210
217
  if (trimmed.length === 0)
211
218
  continue;
212
- // "M\tpath", "A\tpath", "D\tpath", "T\tpath" — no -M, so renames show
213
- // as a D + an A pair; both get classified on their own merits.
219
+ // "M\tpath", "A\tpath", "D\tpath", "T\tpath" — no -M, so a rename
220
+ // shows as a D + an A pair and each half is classified on its own
221
+ // merits. Measured: renaming a tracked `a.txt` to `b.txt` yields
222
+ // "removed child-created file b.txt" plus "restored deleted file
223
+ // a.txt", and taints.
214
224
  const tab = trimmed.indexOf('\t');
215
225
  if (tab < 0)
216
226
  continue;
@@ -225,7 +235,7 @@ async function restoreWorktree(cwd, git, beforeTree, afterTree, tracked, ctCache
225
235
  || (isBenignArtifact(name) && !tracked.has(name))) {
226
236
  // Regenerable test/build output — not graded work. Either an
227
237
  // always-regenerable class (ct cache / run-state, benign even when
228
- // tracked — mx5 run 10) or untracked test-runner output.
238
+ // tracked) or untracked test-runner output.
229
239
  artifactChanges.push(name);
230
240
  }
231
241
  else if (code === 'D') {
@@ -268,11 +278,11 @@ async function restoreWorktree(cwd, git, beforeTree, afterTree, tracked, ctCache
268
278
  * whatever a gate child moved. Ordering matters:
269
279
  *
270
280
  * 1. HEAD first — a child parked on another commit must be back on the original
271
- * ref before the worktree comparison/restore makes sense.
272
- * 2. Worktree content from the snapshot TREE (not from any stash the child may
273
- * have pushed the snapshot is the authoritative "as the child found it").
274
- * 3. Child-pushed stash entries are dropped LAST, once the work they swallowed
275
- * is already restored — this is exactly the orphan that detonated mx5 run 6.
281
+ * ref before the worktree comparison and restore make sense.
282
+ * 2. Worktree content from the snapshot TREE, never from a stash the child may
283
+ * have pushed: the snapshot is the authoritative "as the child found it".
284
+ * 3. Child-pushed stash entries are dropped LAST, once the work they swallowed is
285
+ * already restored — dropping first would destroy the only other copy.
276
286
  *
277
287
  * Never throws; failures degrade to actions[] lines so the caller can log them.
278
288
  */
@@ -308,9 +318,9 @@ export async function reconcileGitState(cwd, before, signal, spawnFn) {
308
318
  }
309
319
  }
310
320
  // 3. Stash entries the child pushed. Drop stash@{0} until the ref matches the
311
- // snapshot again (bounded a child pushes at most a handful; 10 is beyond
312
- // anything observed). A stash the child POPPED (ref gone/behind) cannot be
313
- // reconstructed report it instead of guessing.
321
+ // snapshot again, bounded at 10 so a runaway cannot loop here. A stash the
322
+ // child POPPED the ref is gone or no longer contains the snapshot's tip —
323
+ // cannot be reconstructed, so report it instead of guessing.
314
324
  const stashNow = async () => {
315
325
  const s = await git(['rev-parse', '-q', '--verify', 'refs/stash']);
316
326
  return s.exitCode === 0 ? s.stdout.trim() : null;
@@ -318,7 +328,7 @@ export async function reconcileGitState(cwd, before, signal, spawnFn) {
318
328
  let stash = await stashNow();
319
329
  if (stash !== before.stashSha) {
320
330
  // A child that pushed/popped a stash moved graded work in or out of the tree
321
- // (mx5 run 6's stash-and-abandon) — always verdict-tainting.
331
+ // (a stash-and-abandon) — always verdict-tainting.
322
332
  tainted = true;
323
333
  if (before.stashSha === null || (await stashContains(git, stash, before.stashSha))) {
324
334
  let dropped = 0;
@@ -1,24 +1,30 @@
1
1
  /**
2
2
  * Implementation-turn status widget.
3
3
  *
4
- * The phase widget (widget.ts) is disposed at spec-handoff, so the host agent's
5
- * implementation turn the longest, most visible part of a run otherwise shows
6
- * only pi's bare "⠸ Working…" indicator. This module keeps the SAME rich status
7
- * block alive across that turn (task id · implementing/elapsed/context bar · ↳ last
8
- * tool), driven by the host's own live `ctx.getContextUsage()`.
4
+ * `TaskRunner._deliverSpec` is preceded by `_disposeWidget()`, so the phase widget
5
+ * (widget.ts) is gone before the spec is handed off. Without this module the host
6
+ * agent's implementation turn the longest, most visible part of a run — would
7
+ * show only pi's own working indicator, whose default message is the literal
8
+ * `"Working..."`. This keeps the SAME rich status block alive across that turn
9
+ * (task id · implementing/elapsed/context bar · ↳ last tool), driven by the host's
10
+ * own live `ctx.getContextUsage()`, which pi declares as
11
+ * `getContextUsage(): ContextUsage | undefined`.
9
12
  *
10
- * It is event-driven rather than poll-wrapped because the two delivery paths differ:
13
+ * Event-driven rather than poll-wrapped, because the two delivery paths differ:
11
14
  *
12
- * • /task (fire-and-forget): the command returns right after `sendUserMessage`,
13
- * so the host runs the impl turn AFTER our code is gone — only an `agent_start`
14
- * handler can pick it up. Armed one-shot: `agent_end` disarms it.
15
- * /task-auto (awaited): the caller blocks across `waitForIdle` plus any
16
- * compaction-resume / steer turns. Armed sticky: each `agent_start` re-shows the
17
- * widget, `agent_end` only hides it between turns, and the caller disarms once
18
- * the whole implementation phase has settled.
15
+ * • /task (fire-and-forget): the command returns right after
16
+ * `piApi.sendUserMessage(spec, {deliverAs: 'followUp'})`, so the host runs the
17
+ * impl turn AFTER this code is gone and only an `agent_start` handler can pick
18
+ * it up. Armed one-shot, and `agent_end` disarms it.
19
+ * /task-auto (awaited): the caller blocks across `waitForIdle` plus any resume
20
+ * or steer turns. Armed sticky each `agent_start` re-shows the widget,
21
+ * `agent_end` only hides it between turns, and the caller disarms in a
22
+ * `finally` once the whole implementation phase has settled.
19
23
  *
20
- * Only one task runs at a time (single active task), so a single module-level slot
21
- * is sufficient.
24
+ * `_deliverSpec` picks between them with `oneShot: !this._implAwaited`.
25
+ *
26
+ * A single module-level slot is enough because only one task runs at a time —
27
+ * `orchestrator.ts` keeps one module-level `activeTask`.
22
28
  */
23
29
  import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
24
30
  export interface ImplWidgetMeta {
@@ -36,5 +42,8 @@ export declare function armImplWidget(meta: ImplWidgetMeta, opts: {
36
42
  }): void;
37
43
  /** Tear down the widget and clear the armed slot (sticky/awaited path). */
38
44
  export declare function disarmImplWidget(): void;
39
- /** Wire the agent-lifecycle handlers that drive the widget. Call once at setup. */
45
+ /** Wire the agent-lifecycle handlers that drive the widget. Call once at setup.
46
+ * All three events are pi's own: `agent_start`, `tool_execution_start` and
47
+ * `agent_end` each have an `on()` overload, and ToolExecutionStartEvent carries
48
+ * the `toolName` and `args` read below. */
40
49
  export declare function setupImplWidget(pi: ExtensionAPI): void;
@@ -1,24 +1,30 @@
1
1
  /**
2
2
  * Implementation-turn status widget.
3
3
  *
4
- * The phase widget (widget.ts) is disposed at spec-handoff, so the host agent's
5
- * implementation turn the longest, most visible part of a run otherwise shows
6
- * only pi's bare "⠸ Working…" indicator. This module keeps the SAME rich status
7
- * block alive across that turn (task id · implementing/elapsed/context bar · ↳ last
8
- * tool), driven by the host's own live `ctx.getContextUsage()`.
4
+ * `TaskRunner._deliverSpec` is preceded by `_disposeWidget()`, so the phase widget
5
+ * (widget.ts) is gone before the spec is handed off. Without this module the host
6
+ * agent's implementation turn the longest, most visible part of a run — would
7
+ * show only pi's own working indicator, whose default message is the literal
8
+ * `"Working..."`. This keeps the SAME rich status block alive across that turn
9
+ * (task id · implementing/elapsed/context bar · ↳ last tool), driven by the host's
10
+ * own live `ctx.getContextUsage()`, which pi declares as
11
+ * `getContextUsage(): ContextUsage | undefined`.
9
12
  *
10
- * It is event-driven rather than poll-wrapped because the two delivery paths differ:
13
+ * Event-driven rather than poll-wrapped, because the two delivery paths differ:
11
14
  *
12
- * • /task (fire-and-forget): the command returns right after `sendUserMessage`,
13
- * so the host runs the impl turn AFTER our code is gone — only an `agent_start`
14
- * handler can pick it up. Armed one-shot: `agent_end` disarms it.
15
- * /task-auto (awaited): the caller blocks across `waitForIdle` plus any
16
- * compaction-resume / steer turns. Armed sticky: each `agent_start` re-shows the
17
- * widget, `agent_end` only hides it between turns, and the caller disarms once
18
- * the whole implementation phase has settled.
15
+ * • /task (fire-and-forget): the command returns right after
16
+ * `piApi.sendUserMessage(spec, {deliverAs: 'followUp'})`, so the host runs the
17
+ * impl turn AFTER this code is gone and only an `agent_start` handler can pick
18
+ * it up. Armed one-shot, and `agent_end` disarms it.
19
+ * /task-auto (awaited): the caller blocks across `waitForIdle` plus any resume
20
+ * or steer turns. Armed sticky each `agent_start` re-shows the widget,
21
+ * `agent_end` only hides it between turns, and the caller disarms in a
22
+ * `finally` once the whole implementation phase has settled.
19
23
  *
20
- * Only one task runs at a time (single active task), so a single module-level slot
21
- * is sufficient.
24
+ * `_deliverSpec` picks between them with `oneShot: !this._implAwaited`.
25
+ *
26
+ * A single module-level slot is enough because only one task runs at a time —
27
+ * `orchestrator.ts` keeps one module-level `activeTask`.
22
28
  */
23
29
  import { WIDGET_KEY, WIDGET_REFRESH_MS, buildImplLines, buildImplData } from './widget.js';
24
30
  import { setTaskWidget } from '../remote/session-state.js';
@@ -27,7 +33,8 @@ let lastLine;
27
33
  let timer = null;
28
34
  let activeCtx = null;
29
35
  /** Map the host's live context usage to the widget snapshot, or undefined when the
30
- * token count is unknown (e.g. right after compaction, before the next response). */
36
+ * token count is unknown. pi types `ContextUsage.tokens` as `number | null` and
37
+ * documents the null as "right after compaction, before next LLM response". */
31
38
  function snapshot(ctx) {
32
39
  const u = ctx.getContextUsage?.();
33
40
  if (!u || u.tokens == null)
@@ -100,7 +107,10 @@ export function disarmImplWidget() {
100
107
  activeCtx = null;
101
108
  lastLine = undefined;
102
109
  }
103
- /** Wire the agent-lifecycle handlers that drive the widget. Call once at setup. */
110
+ /** Wire the agent-lifecycle handlers that drive the widget. Call once at setup.
111
+ * All three events are pi's own: `agent_start`, `tool_execution_start` and
112
+ * `agent_end` each have an `on()` overload, and ToolExecutionStartEvent carries
113
+ * the `toolName` and `args` read below. */
104
114
  export function setupImplWidget(pi) {
105
115
  pi.on('agent_start', (_event, ctx) => {
106
116
  if (!armed)
@@ -2,41 +2,43 @@
2
2
  * Hold the host session at the `implementation` group's thinking level for the
3
3
  * duration of one implementation turn, then put it back.
4
4
  *
5
- * WHY THIS IS NOT LIKE THE OTHER SIX GROUPS
6
- * -----------------------------------------
7
- * Every other group is a child process, so its level is one argv flag and it
8
- * dies with the child. The implementation turn runs in the USER'S OWN session
9
- * (orchestrator.ts `sendSpec` sendUserMessage superviseImplementation), so
10
- * the only lever is `pi.setThinkingLevel`, which is session-global and visible.
5
+ * WHY THIS GROUP IS NOT LIKE THE OTHERS
6
+ * -------------------------------------
7
+ * Every other reasoning group runs in a child process, so its level is one argv
8
+ * flag (`--thinking <level>`, built in reasoning-args.ts) and it dies with the
9
+ * child. The implementation turn runs in the USER'S OWN session
10
+ * (orchestrator.ts `sendSpec` -> `sendUserMessage` -> `superviseImplementation`),
11
+ * so the only lever is `pi.setThinkingLevel`, which is session-global.
11
12
  *
12
- * THREE THINGS pi DOES that this has to survive. All three read from
13
- * pi-coding-agent's agent-session `setThinkingLevel`:
13
+ * THREE THINGS pi DOES that this has to survive:
14
14
  *
15
- * 1. IT PERSISTS. On a real change it calls
16
- * `settingsManager.setDefaultThinkingLevel(...)`, writing
17
- * `~/.pi/agent/settings.json`. This is not a session-local toggle without
18
- * the restore, running one task would silently rewrite the user's global
19
- * default. That makes `release()` load-bearing, not tidy-up.
20
- * 2. IT CLAMPS, to what the model declares it supports. We may ask for `medium`
21
- * and be given `off`. So the restore writes back what was READ after
22
- * setting, never what was asked for otherwise a clamp would ratchet the
23
- * stored default a little further every run.
24
- * 3. IT IS OBSERVABLE, and the user can change it mid-turn (shift+tab cycles
25
- * the level). Restoring blindly would clobber a choice they just made. We
26
- * detect it by comparing the live level at release against what we applied:
27
- * if it has moved, somebody else moved it, and we leave it alone.
15
+ * 1. IT PERSISTS. pi-coding-agent's agent-session `setThinkingLevel` calls
16
+ * `settingsManager.setDefaultThinkingLevel(...)` whenever the effective
17
+ * level actually changes, and that writes pi's global settings file
18
+ * (`~/.pi/agent/settings.json`). Without the restore, running one task would
19
+ * silently rewrite the user's global default. That makes `release()`
20
+ * load-bearing, not tidy-up.
21
+ * 2. IT CLAMPS, to the levels the model declares. A model with no reasoning
22
+ * support offers only `off`, so asking for `medium` yields `off`. The
23
+ * restore therefore writes back what was READ after setting, never what was
24
+ * asked for otherwise a clamp would ratchet the stored default a little
25
+ * further every run.
26
+ * 3. IT IS OBSERVABLE, and the user can change it mid-turn: `shift+tab` is the
27
+ * default binding for `app.thinking.cycle`, and a change invalidates the
28
+ * footer. Restoring blindly would clobber a choice they just made. We detect
29
+ * it by comparing the live level at release against what we applied: if it
30
+ * has moved, somebody else moved it, and we leave it alone.
28
31
  *
29
- * We compare rather than subscribe because `pi.on` returns no unsubscribe
30
- * handle, so a per-turn listener could only ever be added, never removed. The
31
- * comparison answers the same question with no accumulating state.
32
+ * We compare rather than subscribe because the extension API's `on(...)` returns
33
+ * `void` — there is no unsubscribe handle so a per-turn listener could only
34
+ * ever be added, never removed. The comparison answers the same question with no
35
+ * accumulating state.
32
36
  */
33
37
  import type { ThinkingLevel } from '@earendil-works/pi-agent-core';
34
38
  import { type GroupSetting } from '../config/reasoning.js';
35
39
  /**
36
- * The slice of the extension API this needs, named so tests can drive it without
37
- * a live pi session. Every other dependency in `RunSingleTaskOptions` is
38
- * injectable; this one has to be too, or the restore logic is only exercisable
39
- * by running a real task.
40
+ * The slice of the extension API this needs, named so tests can drive the
41
+ * hold-and-restore with a fake object instead of a live pi session.
40
42
  */
41
43
  export interface ThinkingControl {
42
44
  get(): ThinkingLevel;
@@ -47,8 +49,8 @@ export interface ThinkingControl {
47
49
  * that puts it back. Always call the returned function — `finally`, not the
48
50
  * happy path.
49
51
  *
50
- * `inherit` makes NO call at all, not even a redundant set-to-current: a set
51
- * that happens to be a no-op still goes through pi's change detection, and the
52
- * shipped default must not touch the user's settings file.
52
+ * `inherit` makes NO call at all, not even a redundant set-to-current. It means
53
+ * the same thing here as in `thinkingArgs`, which emits no `--thinking` flag for
54
+ * it: leave the level wherever it already is.
53
55
  */
54
56
  export declare function holdImplementationThinking(control: ThinkingControl, setting?: GroupSetting): () => void;
@@ -5,9 +5,9 @@ import { resolveReasoning } from '../config/reasoning.js';
5
5
  * that puts it back. Always call the returned function — `finally`, not the
6
6
  * happy path.
7
7
  *
8
- * `inherit` makes NO call at all, not even a redundant set-to-current: a set
9
- * that happens to be a no-op still goes through pi's change detection, and the
10
- * shipped default must not touch the user's settings file.
8
+ * `inherit` makes NO call at all, not even a redundant set-to-current. It means
9
+ * the same thing here as in `thinkingArgs`, which emits no `--thinking` flag for
10
+ * it: leave the level wherever it already is.
11
11
  */
12
12
  export function holdImplementationThinking(control, setting = resolveReasoning('implementation', getConfig())) {
13
13
  if (setting === 'inherit')
@@ -21,9 +21,8 @@ export function holdImplementationThinking(control, setting = resolveReasoning('
21
21
  return () => { };
22
22
  let released = false;
23
23
  return () => {
24
- // Idempotent: the caller's `finally` may run alongside an outer one on an
25
- // abort path, and a second restore would fight a user change made in
26
- // between.
24
+ // Idempotent by contract: only the first call restores. A later call
25
+ // would write `before` on top of whatever the level is by then.
27
26
  if (released)
28
27
  return;
29
28
  released = true;
@@ -7,12 +7,13 @@
7
7
  * • `aborted` — a user ESC (or the command watchdog) cut the turn short;
8
8
  * • `compaction` — a threshold auto-compaction parked the turn at idle without
9
9
  * auto-continuing (the runtime expects a manual continue);
10
- * • `error` — the model/provider died mid-turn after pi's own retries;
10
+ * • `error` — the model or provider failed after pi exhausted the retries
11
+ * in its own retry settings;
11
12
  * • `stop` — genuine completion.
12
- * `classifyTurnEnd` reads the session entries and names ONE of those, in the
13
- * precedence the supervision sequence needs; `superviseImplementation` then
14
- * resumes across compactions, lets the user steer after an interrupt, and reports
15
- * the terminal outcome. The orchestrator calls it once.
13
+ * `classifyTurnEnd` reads the session entries and names ONE of those;
14
+ * `superviseImplementation` then resumes across compactions, lets the user steer
15
+ * after an interrupt, and reports the terminal outcome. The orchestrator calls it
16
+ * from one place, inside the `sendSpec` closure.
16
17
  */
17
18
  import type { ExtensionCommandContext } from '@earendil-works/pi-coding-agent';
18
19
  /** How the most recent implementation turn ended. See {@link classifyTurnEnd}. */
@@ -34,21 +35,20 @@ export type SessionEntryLike = {
34
35
  /**
35
36
  * Classify how the most recent turn ended, from the session entries alone.
36
37
  *
37
- * Precedence, when several signals are present at once (this is the order the
38
- * supervision sequence has always applied, now stated in one place):
38
+ * Precedence, when several signals are present at once:
39
39
  * 1. `aborted` — the last assistant message has stopReason "aborted". A user
40
40
  * ESC (or watchdog abort) wins over everything: it is not a
41
41
  * compaction pause, and the steer loop owns it.
42
42
  * 2. `compaction` — a `compaction` entry sits AFTER the last assistant message.
43
- * Position-based, not timestamp-based: the runtime appends the
44
- * boundary to the tail of the branch after the message that
45
- * triggered it (`appendCompaction` `_appendEntry` push), so a
43
+ * Position-based, not timestamp-based: `appendCompaction`
44
+ * pushes the boundary onto the tail of the entry list, and
45
+ * `getEntries()` returns that list in append order, so a
46
46
  * trailing compaction means we are parked with no continuation.
47
- * A finished turn ends on an assistant message; an *overflow*
48
- * compaction self-retries and never leaves us idle here.
49
- * 3. `error` — the last assistant message has stopReason "error": the
50
- * model/provider died (context-overflow 400, disconnect, 5xx)
51
- * after pi exhausted its own retries.
47
+ * A finished turn ends on an assistant message. An overflow
48
+ * compaction that is going to retry continues the turn itself
49
+ * and never reaches us idle.
50
+ * 3. `error` — the last assistant message has stopReason "error": the model
51
+ * or provider failed after pi exhausted its own retries.
52
52
  * 4. `stop` — anything else, including a session with no assistant turn.
53
53
  */
54
54
  export declare function classifyTurnEnd(entries: ReadonlyArray<SessionEntryLike>): TurnEnd;
@@ -78,10 +78,11 @@ export type SteerCtx = ExtensionCommandContext & {
78
78
  };
79
79
  /**
80
80
  * Timing knobs for the watchdog-abort guard in {@link steerUntilDone}, injectable
81
- * so tests exercise the grace expiry without a 10-second wait. `graceMs` bounds
82
- * how long the loop waits for the watchdog's follow-up to be DELIVERED (not to
83
- * finish its turn may legitimately run for minutes afterwards); delivery is
84
- * normally near-instant, so the grace only expires on a stale flag.
81
+ * so a test can exercise the grace expiry without waiting it out. `graceMs`
82
+ * bounds how long the loop waits for the watchdog's follow-up to be DELIVERED,
83
+ * not to finish: once it lands, the wait for its turn is unbounded. `onFire`
84
+ * sends the follow-up in the same block that raised the flag, so the grace
85
+ * expires only when the flag was already stale.
85
86
  */
86
87
  export interface SteerWatchdogDeps {
87
88
  consume: () => boolean;
@@ -124,18 +125,18 @@ export declare function turnDepsFor(ctx: SteerCtx, opts?: SuperviseOptions): Imp
124
125
  /**
125
126
  * Nudge that resumes an implementation turn the runtime parked at a compaction
126
127
  * boundary. It must let a turn that was genuinely finished (then tipped over the
127
- * threshold by its own final message) confirm completion without inventing busywork
128
- * we cannot tell "paused mid-task by compaction" from "finished, then compacted"
129
- * from the boundary alone, so the wording lets a done turn end in one line.
128
+ * threshold by its own final message) confirm completion without inventing busywork.
129
+ * The classifier sees only the boundary's POSITION, so it cannot tell "paused
130
+ * mid-task by compaction" from "finished, then compacted"; the wording lets a done
131
+ * turn end in one line.
130
132
  */
131
133
  export declare const CONTINUE_AFTER_COMPACTION: string;
132
134
  /**
133
135
  * Safety cap on compaction-driven resumes for a single implementation turn. Each
134
- * resume follows a real compaction (which only fires after the model produced a
135
- * turn large enough to cross the threshold), so a legitimately large task may
136
- * resume a handful of times; the cap exists only to stop a pathological loop from
137
- * auto-sending forever with no user in the loop. Hitting it stops resuming and lets
138
- * the verify gate / `/task-auto-resume` catch any leftover incompleteness.
136
+ * resume follows a real compaction, which pi only runs once `shouldCompact` says
137
+ * the context crossed its threshold. The cap exists to stop a pathological loop
138
+ * from auto-sending forever with no user watching. Hitting it stops resuming and
139
+ * lets the verify gate and `/task-auto-resume` catch any leftover incompleteness.
139
140
  */
140
141
  export declare const MAX_COMPACTION_RESUMES = 20;
141
142
  /**
@@ -154,10 +155,12 @@ export declare function resumeAcrossCompactions(deps: ImplementationTurnDeps): P
154
155
  * `waitForIdle` resolves both on natural completion AND on an ESC (which aborts
155
156
  * the turn → idle). When the last turn was aborted, the host's main input loop is
156
157
  * blocked inside our command handler, so a message typed in the editor would only
157
- * queue, never run (interactive-mode routes idle input through onInputCallback,
158
- * which is unset while we hold the loop). We therefore solicit the steering text
159
- * ourselves and feed it back as another turn via sendUserMessage which runs to
160
- * completion when the session is idle. Repeat until a turn finishes uninterrupted.
158
+ * queue, never run: interactive-mode's submit handler calls `onInputCallback` when
159
+ * the session is idle, and that callback is set only inside `getUserInput()` the
160
+ * REPL loop we are holding so the text lands in `pendingUserInputs` instead. We
161
+ * therefore solicit the steering text ourselves and feed it back as another turn
162
+ * via `sendUserMessage`, which forwards to `prompt()` and, on an idle session, runs
163
+ * the turn rather than queueing it. Repeat until a turn finishes uninterrupted.
161
164
  *
162
165
  * A WATCHDOG abort also ends the turn with stopReason 'aborted' — indistinguishable
163
166
  * from a human ESC by the session entries alone at that instant. The watchdog