@caupulican/pi-adaptative 0.81.38 → 0.81.40

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 (881) hide show
  1. package/CHANGELOG.md +131 -0
  2. package/README.md +2 -0
  3. package/dist/bundled-resources/extensions/tmux-agent-manager/README.md +18 -1
  4. package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.d.ts +133 -0
  5. package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.d.ts.map +1 -0
  6. package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.js +198 -0
  7. package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.js.map +1 -0
  8. package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.ts +311 -0
  9. package/dist/bundled-resources/extensions/tmux-agent-manager/index.d.ts +31 -2
  10. package/dist/bundled-resources/extensions/tmux-agent-manager/index.d.ts.map +1 -1
  11. package/dist/bundled-resources/extensions/tmux-agent-manager/index.js +823 -79
  12. package/dist/bundled-resources/extensions/tmux-agent-manager/index.js.map +1 -1
  13. package/dist/bundled-resources/extensions/tmux-agent-manager/index.ts +1043 -86
  14. package/dist/bundled-resources/runtimes/pi-shell-engine/commands/__init__.py +43 -0
  15. package/dist/bundled-resources/runtimes/pi-shell-engine/commands/fs.py +270 -0
  16. package/dist/bundled-resources/runtimes/pi-shell-engine/commands/search.py +252 -0
  17. package/dist/bundled-resources/runtimes/pi-shell-engine/commands/strings.py +399 -0
  18. package/dist/bundled-resources/runtimes/pi-shell-engine/commands/text.py +575 -0
  19. package/dist/bundled-resources/runtimes/pi-shell-engine/context.py +52 -0
  20. package/dist/bundled-resources/runtimes/pi-shell-engine/errors.py +49 -0
  21. package/dist/bundled-resources/runtimes/pi-shell-engine/exec.py +734 -0
  22. package/dist/bundled-resources/runtimes/pi-shell-engine/expand.py +238 -0
  23. package/dist/bundled-resources/runtimes/pi-shell-engine/main.py +132 -0
  24. package/dist/bundled-resources/runtimes/pi-shell-engine/nodes.py +116 -0
  25. package/dist/bundled-resources/runtimes/pi-shell-engine/parser.py +287 -0
  26. package/dist/bundled-resources/runtimes/pi-shell-engine/proc.py +137 -0
  27. package/dist/bundled-resources/runtimes/pi-shell-engine/state.py +100 -0
  28. package/dist/bundled-resources/runtimes/pi-shell-engine/tokens.py +579 -0
  29. package/dist/bundled-resources/skills/tool-call-repair/SKILL.md +19 -9
  30. package/dist/bundled-resources/skills/tool-call-repair/references/failure-grammar.md +22 -5
  31. package/dist/bundled-resources/skills/tool-call-repair/references/repair-catalogue.md +7 -7
  32. package/dist/bundled-resources/skills/tool-call-repair/references/text-protocol-grammar.md +28 -7
  33. package/dist/cli/args.d.ts +5 -0
  34. package/dist/cli/args.d.ts.map +1 -1
  35. package/dist/cli/args.js +24 -0
  36. package/dist/cli/args.js.map +1 -1
  37. package/dist/core/agent-directory-layout.d.ts +15 -0
  38. package/dist/core/agent-directory-layout.d.ts.map +1 -0
  39. package/dist/core/agent-directory-layout.js +72 -0
  40. package/dist/core/agent-directory-layout.js.map +1 -0
  41. package/dist/core/agent-paths.d.ts +66 -0
  42. package/dist/core/agent-paths.d.ts.map +1 -0
  43. package/dist/core/agent-paths.js +170 -0
  44. package/dist/core/agent-paths.js.map +1 -0
  45. package/dist/core/agent-session-runtime.d.ts +52 -8
  46. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  47. package/dist/core/agent-session-runtime.js +397 -79
  48. package/dist/core/agent-session-runtime.js.map +1 -1
  49. package/dist/core/agent-session-services.d.ts +1 -0
  50. package/dist/core/agent-session-services.d.ts.map +1 -1
  51. package/dist/core/agent-session-services.js +4 -3
  52. package/dist/core/agent-session-services.js.map +1 -1
  53. package/dist/core/agent-session.d.ts +255 -103
  54. package/dist/core/agent-session.d.ts.map +1 -1
  55. package/dist/core/agent-session.js +754 -1173
  56. package/dist/core/agent-session.js.map +1 -1
  57. package/dist/core/autonomy/approval-gate.d.ts +3 -2
  58. package/dist/core/autonomy/approval-gate.d.ts.map +1 -1
  59. package/dist/core/autonomy/approval-gate.js +6 -28
  60. package/dist/core/autonomy/approval-gate.js.map +1 -1
  61. package/dist/core/autonomy/bounded-completion.d.ts +1 -1
  62. package/dist/core/autonomy/bounded-completion.d.ts.map +1 -1
  63. package/dist/core/autonomy/bounded-completion.js +1 -1
  64. package/dist/core/autonomy/bounded-completion.js.map +1 -1
  65. package/dist/core/autonomy/contracts.d.ts +26 -9
  66. package/dist/core/autonomy/contracts.d.ts.map +1 -1
  67. package/dist/core/autonomy/contracts.js.map +1 -1
  68. package/dist/core/autonomy/foreground-envelope.d.ts +8 -2
  69. package/dist/core/autonomy/foreground-envelope.d.ts.map +1 -1
  70. package/dist/core/autonomy/foreground-envelope.js +8 -25
  71. package/dist/core/autonomy/foreground-envelope.js.map +1 -1
  72. package/dist/core/autonomy/gates.d.ts.map +1 -1
  73. package/dist/core/autonomy/gates.js +8 -10
  74. package/dist/core/autonomy/gates.js.map +1 -1
  75. package/dist/core/autonomy/lane-private-paths.d.ts +2 -0
  76. package/dist/core/autonomy/lane-private-paths.d.ts.map +1 -0
  77. package/dist/core/autonomy/lane-private-paths.js +16 -0
  78. package/dist/core/autonomy/lane-private-paths.js.map +1 -0
  79. package/dist/core/autonomy/lane-tool-surface.d.ts +10 -0
  80. package/dist/core/autonomy/lane-tool-surface.d.ts.map +1 -1
  81. package/dist/core/autonomy/lane-tool-surface.js +46 -8
  82. package/dist/core/autonomy/lane-tool-surface.js.map +1 -1
  83. package/dist/core/autonomy/lane-tracker.d.ts +24 -1
  84. package/dist/core/autonomy/lane-tracker.d.ts.map +1 -1
  85. package/dist/core/autonomy/lane-tracker.js +43 -1
  86. package/dist/core/autonomy/lane-tracker.js.map +1 -1
  87. package/dist/core/autonomy/risk-assessment.d.ts +1 -2
  88. package/dist/core/autonomy/risk-assessment.d.ts.map +1 -1
  89. package/dist/core/autonomy/risk-assessment.js +1 -123
  90. package/dist/core/autonomy/risk-assessment.js.map +1 -1
  91. package/dist/core/autonomy/session-lane-record.d.ts +4 -4
  92. package/dist/core/autonomy/session-lane-record.d.ts.map +1 -1
  93. package/dist/core/autonomy/session-lane-record.js +16 -29
  94. package/dist/core/autonomy/session-lane-record.js.map +1 -1
  95. package/dist/core/autonomy/telemetry-events.d.ts +1 -1
  96. package/dist/core/autonomy/telemetry-events.d.ts.map +1 -1
  97. package/dist/core/autonomy/telemetry-events.js +1 -1
  98. package/dist/core/autonomy/telemetry-events.js.map +1 -1
  99. package/dist/core/autonomy-telemetry.d.ts +7 -7
  100. package/dist/core/autonomy-telemetry.d.ts.map +1 -1
  101. package/dist/core/autonomy-telemetry.js +9 -9
  102. package/dist/core/autonomy-telemetry.js.map +1 -1
  103. package/dist/core/background-lane-controller.d.ts +90 -156
  104. package/dist/core/background-lane-controller.d.ts.map +1 -1
  105. package/dist/core/background-lane-controller.js +192 -968
  106. package/dist/core/background-lane-controller.js.map +1 -1
  107. package/dist/core/bash-execution-controller.d.ts +4 -0
  108. package/dist/core/bash-execution-controller.d.ts.map +1 -1
  109. package/dist/core/bash-execution-controller.js +7 -1
  110. package/dist/core/bash-execution-controller.js.map +1 -1
  111. package/dist/core/capability-contract.d.ts +4 -0
  112. package/dist/core/capability-contract.d.ts.map +1 -0
  113. package/dist/core/capability-contract.js +28 -0
  114. package/dist/core/capability-contract.js.map +1 -0
  115. package/dist/core/compaction-controller.d.ts +90 -0
  116. package/dist/core/compaction-controller.d.ts.map +1 -0
  117. package/dist/core/compaction-controller.js +452 -0
  118. package/dist/core/compaction-controller.js.map +1 -0
  119. package/dist/core/compaction-support.d.ts +24 -13
  120. package/dist/core/compaction-support.d.ts.map +1 -1
  121. package/dist/core/compaction-support.js +43 -7
  122. package/dist/core/compaction-support.js.map +1 -1
  123. package/dist/core/context/context-artifacts.d.ts +6 -2
  124. package/dist/core/context/context-artifacts.d.ts.map +1 -1
  125. package/dist/core/context/context-artifacts.js +34 -2
  126. package/dist/core/context/context-artifacts.js.map +1 -1
  127. package/dist/core/context/context-audit.d.ts +33 -1
  128. package/dist/core/context/context-audit.d.ts.map +1 -1
  129. package/dist/core/context/context-audit.js +31 -5
  130. package/dist/core/context/context-audit.js.map +1 -1
  131. package/dist/core/context/context-store-retention.d.ts +8 -2
  132. package/dist/core/context/context-store-retention.d.ts.map +1 -1
  133. package/dist/core/context/context-store-retention.js +158 -35
  134. package/dist/core/context/context-store-retention.js.map +1 -1
  135. package/dist/core/context/okf-memory.d.ts.map +1 -1
  136. package/dist/core/context/okf-memory.js +9 -11
  137. package/dist/core/context/okf-memory.js.map +1 -1
  138. package/dist/core/context/sqlite-runtime-index.d.ts.map +1 -1
  139. package/dist/core/context/sqlite-runtime-index.js +6 -8
  140. package/dist/core/context/sqlite-runtime-index.js.map +1 -1
  141. package/dist/core/context-gc.d.ts +3 -0
  142. package/dist/core/context-gc.d.ts.map +1 -1
  143. package/dist/core/context-gc.js +26 -6
  144. package/dist/core/context-gc.js.map +1 -1
  145. package/dist/core/context-pipeline.d.ts +26 -4
  146. package/dist/core/context-pipeline.d.ts.map +1 -1
  147. package/dist/core/context-pipeline.js +146 -18
  148. package/dist/core/context-pipeline.js.map +1 -1
  149. package/dist/core/cost-guard.d.ts +19 -3
  150. package/dist/core/cost-guard.d.ts.map +1 -1
  151. package/dist/core/cost-guard.js +18 -3
  152. package/dist/core/cost-guard.js.map +1 -1
  153. package/dist/core/default-tool-surface.d.ts.map +1 -1
  154. package/dist/core/default-tool-surface.js +1 -0
  155. package/dist/core/default-tool-surface.js.map +1 -1
  156. package/dist/core/delegation/managed-lane-controller.d.ts +34 -0
  157. package/dist/core/delegation/managed-lane-controller.d.ts.map +1 -0
  158. package/dist/core/delegation/managed-lane-controller.js +217 -0
  159. package/dist/core/delegation/managed-lane-controller.js.map +1 -0
  160. package/dist/core/delegation/session-worker-claim.d.ts +44 -0
  161. package/dist/core/delegation/session-worker-claim.d.ts.map +1 -0
  162. package/dist/core/delegation/session-worker-claim.js +98 -0
  163. package/dist/core/delegation/session-worker-claim.js.map +1 -0
  164. package/dist/core/delegation/worker-actions.d.ts +7 -5
  165. package/dist/core/delegation/worker-actions.d.ts.map +1 -1
  166. package/dist/core/delegation/worker-actions.js +20 -4
  167. package/dist/core/delegation/worker-actions.js.map +1 -1
  168. package/dist/core/delegation/worker-claim.d.ts +56 -0
  169. package/dist/core/delegation/worker-claim.d.ts.map +1 -0
  170. package/dist/core/delegation/worker-claim.js +239 -0
  171. package/dist/core/delegation/worker-claim.js.map +1 -0
  172. package/dist/core/delegation/worker-delegation-controller.d.ts +110 -0
  173. package/dist/core/delegation/worker-delegation-controller.d.ts.map +1 -0
  174. package/dist/core/delegation/worker-delegation-controller.js +861 -0
  175. package/dist/core/delegation/worker-delegation-controller.js.map +1 -0
  176. package/dist/core/delegation/worker-delegation-request.d.ts +11 -0
  177. package/dist/core/delegation/worker-delegation-request.d.ts.map +1 -0
  178. package/dist/core/delegation/worker-delegation-request.js +2 -0
  179. package/dist/core/delegation/worker-delegation-request.js.map +1 -0
  180. package/dist/core/delegation/worker-dispatch-scheduler.d.ts +40 -0
  181. package/dist/core/delegation/worker-dispatch-scheduler.d.ts.map +1 -0
  182. package/dist/core/delegation/worker-dispatch-scheduler.js +93 -0
  183. package/dist/core/delegation/worker-dispatch-scheduler.js.map +1 -0
  184. package/dist/core/delegation/worker-execution-policy.d.ts +62 -0
  185. package/dist/core/delegation/worker-execution-policy.d.ts.map +1 -0
  186. package/dist/core/delegation/worker-execution-policy.js +160 -0
  187. package/dist/core/delegation/worker-execution-policy.js.map +1 -0
  188. package/dist/core/delegation/worker-lane-projection.d.ts +14 -0
  189. package/dist/core/delegation/worker-lane-projection.d.ts.map +1 -0
  190. package/dist/core/delegation/worker-lane-projection.js +100 -0
  191. package/dist/core/delegation/worker-lane-projection.js.map +1 -0
  192. package/dist/core/delegation/worker-lifecycle.d.ts +97 -0
  193. package/dist/core/delegation/worker-lifecycle.d.ts.map +1 -0
  194. package/dist/core/delegation/worker-lifecycle.js +339 -0
  195. package/dist/core/delegation/worker-lifecycle.js.map +1 -0
  196. package/dist/core/delegation/worker-notification-coordinator.d.ts +36 -0
  197. package/dist/core/delegation/worker-notification-coordinator.d.ts.map +1 -0
  198. package/dist/core/delegation/worker-notification-coordinator.js +90 -0
  199. package/dist/core/delegation/worker-notification-coordinator.js.map +1 -0
  200. package/dist/core/delegation/worker-profile-resolver.d.ts +55 -0
  201. package/dist/core/delegation/worker-profile-resolver.d.ts.map +1 -0
  202. package/dist/core/delegation/worker-profile-resolver.js +113 -0
  203. package/dist/core/delegation/worker-profile-resolver.js.map +1 -0
  204. package/dist/core/delegation/worker-runner.d.ts +25 -14
  205. package/dist/core/delegation/worker-runner.d.ts.map +1 -1
  206. package/dist/core/delegation/worker-runner.js +122 -53
  207. package/dist/core/delegation/worker-runner.js.map +1 -1
  208. package/dist/core/delegation/worker-task-label.d.ts +3 -0
  209. package/dist/core/delegation/worker-task-label.d.ts.map +1 -0
  210. package/dist/core/delegation/worker-task-label.js +11 -0
  211. package/dist/core/delegation/worker-task-label.js.map +1 -0
  212. package/dist/core/doctor.d.ts +8 -0
  213. package/dist/core/doctor.d.ts.map +1 -1
  214. package/dist/core/doctor.js +32 -1
  215. package/dist/core/doctor.js.map +1 -1
  216. package/dist/core/extensions/index.d.ts +1 -1
  217. package/dist/core/extensions/index.d.ts.map +1 -1
  218. package/dist/core/extensions/index.js.map +1 -1
  219. package/dist/core/extensions/loader.d.ts +5 -1
  220. package/dist/core/extensions/loader.d.ts.map +1 -1
  221. package/dist/core/extensions/loader.js +68 -13
  222. package/dist/core/extensions/loader.js.map +1 -1
  223. package/dist/core/extensions/runner.d.ts.map +1 -1
  224. package/dist/core/extensions/runner.js +7 -0
  225. package/dist/core/extensions/runner.js.map +1 -1
  226. package/dist/core/extensions/storage.d.ts +8 -0
  227. package/dist/core/extensions/storage.d.ts.map +1 -0
  228. package/dist/core/extensions/storage.js +28 -0
  229. package/dist/core/extensions/storage.js.map +1 -0
  230. package/dist/core/extensions/types.d.ts +101 -1
  231. package/dist/core/extensions/types.d.ts.map +1 -1
  232. package/dist/core/extensions/types.js.map +1 -1
  233. package/dist/core/failure-corpus.d.ts +1 -15
  234. package/dist/core/failure-corpus.d.ts.map +1 -1
  235. package/dist/core/failure-corpus.js +9 -37
  236. package/dist/core/failure-corpus.js.map +1 -1
  237. package/dist/core/foreground-recovery-controller.d.ts +52 -0
  238. package/dist/core/foreground-recovery-controller.d.ts.map +1 -0
  239. package/dist/core/foreground-recovery-controller.js +111 -0
  240. package/dist/core/foreground-recovery-controller.js.map +1 -0
  241. package/dist/core/gateways/channel-provider.d.ts +3 -0
  242. package/dist/core/gateways/channel-provider.d.ts.map +1 -1
  243. package/dist/core/gateways/channel-provider.js +31 -6
  244. package/dist/core/gateways/channel-provider.js.map +1 -1
  245. package/dist/core/goal-loop-controller.d.ts +17 -1
  246. package/dist/core/goal-loop-controller.d.ts.map +1 -1
  247. package/dist/core/goal-loop-controller.js +79 -11
  248. package/dist/core/goal-loop-controller.js.map +1 -1
  249. package/dist/core/goals/goal-acceptance.d.ts +11 -0
  250. package/dist/core/goals/goal-acceptance.d.ts.map +1 -0
  251. package/dist/core/goals/goal-acceptance.js +25 -0
  252. package/dist/core/goals/goal-acceptance.js.map +1 -0
  253. package/dist/core/goals/goal-auto-continue-controller.d.ts +27 -0
  254. package/dist/core/goals/goal-auto-continue-controller.d.ts.map +1 -0
  255. package/dist/core/goals/goal-auto-continue-controller.js +93 -0
  256. package/dist/core/goals/goal-auto-continue-controller.js.map +1 -0
  257. package/dist/core/goals/goal-continuation-controller.d.ts +48 -2
  258. package/dist/core/goals/goal-continuation-controller.d.ts.map +1 -1
  259. package/dist/core/goals/goal-continuation-controller.js +87 -0
  260. package/dist/core/goals/goal-continuation-controller.js.map +1 -1
  261. package/dist/core/goals/goal-continuation-defaults.d.ts +39 -0
  262. package/dist/core/goals/goal-continuation-defaults.d.ts.map +1 -1
  263. package/dist/core/goals/goal-continuation-defaults.js +42 -0
  264. package/dist/core/goals/goal-continuation-defaults.js.map +1 -1
  265. package/dist/core/goals/goal-continuation-prompt.d.ts +5 -1
  266. package/dist/core/goals/goal-continuation-prompt.d.ts.map +1 -1
  267. package/dist/core/goals/goal-continuation-prompt.js +56 -15
  268. package/dist/core/goals/goal-continuation-prompt.js.map +1 -1
  269. package/dist/core/goals/goal-lifecycle.d.ts +20 -0
  270. package/dist/core/goals/goal-lifecycle.d.ts.map +1 -0
  271. package/dist/core/goals/goal-lifecycle.js +17 -0
  272. package/dist/core/goals/goal-lifecycle.js.map +1 -0
  273. package/dist/core/goals/goal-runtime-snapshot.d.ts +105 -4
  274. package/dist/core/goals/goal-runtime-snapshot.d.ts.map +1 -1
  275. package/dist/core/goals/goal-runtime-snapshot.js +91 -6
  276. package/dist/core/goals/goal-runtime-snapshot.js.map +1 -1
  277. package/dist/core/goals/goal-state.d.ts +90 -1
  278. package/dist/core/goals/goal-state.d.ts.map +1 -1
  279. package/dist/core/goals/goal-state.js +78 -15
  280. package/dist/core/goals/goal-state.js.map +1 -1
  281. package/dist/core/goals/goal-tool-core.d.ts +58 -2
  282. package/dist/core/goals/goal-tool-core.d.ts.map +1 -1
  283. package/dist/core/goals/goal-tool-core.js +82 -6
  284. package/dist/core/goals/goal-tool-core.js.map +1 -1
  285. package/dist/core/goals/session-goal-state.d.ts +13 -6
  286. package/dist/core/goals/session-goal-state.d.ts.map +1 -1
  287. package/dist/core/goals/session-goal-state.js +20 -28
  288. package/dist/core/goals/session-goal-state.js.map +1 -1
  289. package/dist/core/human-input.d.ts +100 -0
  290. package/dist/core/human-input.d.ts.map +1 -0
  291. package/dist/core/human-input.js +362 -0
  292. package/dist/core/human-input.js.map +1 -0
  293. package/dist/core/index.d.ts +9 -1
  294. package/dist/core/index.d.ts.map +1 -1
  295. package/dist/core/index.js +9 -1
  296. package/dist/core/index.js.map +1 -1
  297. package/dist/core/{telemetry.d.ts → install-telemetry.d.ts} +1 -1
  298. package/dist/core/install-telemetry.d.ts.map +1 -0
  299. package/dist/core/{telemetry.js → install-telemetry.js} +1 -1
  300. package/dist/core/install-telemetry.js.map +1 -0
  301. package/dist/core/keybindings.d.ts +27 -1
  302. package/dist/core/keybindings.d.ts.map +1 -1
  303. package/dist/core/keybindings.js +34 -8
  304. package/dist/core/keybindings.js.map +1 -1
  305. package/dist/core/learning/learning-audit.d.ts +2 -4
  306. package/dist/core/learning/learning-audit.d.ts.map +1 -1
  307. package/dist/core/learning/learning-audit.js +9 -29
  308. package/dist/core/learning/learning-audit.js.map +1 -1
  309. package/dist/core/learning/learning-gate.d.ts.map +1 -1
  310. package/dist/core/learning/learning-gate.js +1 -6
  311. package/dist/core/learning/learning-gate.js.map +1 -1
  312. package/dist/core/learning/observation-store.d.ts +12 -3
  313. package/dist/core/learning/observation-store.d.ts.map +1 -1
  314. package/dist/core/learning/observation-store.js +30 -15
  315. package/dist/core/learning/observation-store.js.map +1 -1
  316. package/dist/core/learning/session-learning-decision.d.ts +2 -4
  317. package/dist/core/learning/session-learning-decision.d.ts.map +1 -1
  318. package/dist/core/learning/session-learning-decision.js +9 -29
  319. package/dist/core/learning/session-learning-decision.js.map +1 -1
  320. package/dist/core/learning/skill-curator.d.ts +5 -1
  321. package/dist/core/learning/skill-curator.d.ts.map +1 -1
  322. package/dist/core/learning/skill-curator.js +21 -19
  323. package/dist/core/learning/skill-curator.js.map +1 -1
  324. package/dist/core/local-runtime-controller.d.ts +65 -3
  325. package/dist/core/local-runtime-controller.d.ts.map +1 -1
  326. package/dist/core/local-runtime-controller.js +186 -26
  327. package/dist/core/local-runtime-controller.js.map +1 -1
  328. package/dist/core/memory/providers/file-store.d.ts +1 -1
  329. package/dist/core/memory/providers/file-store.d.ts.map +1 -1
  330. package/dist/core/memory/providers/file-store.js +6 -6
  331. package/dist/core/memory/providers/file-store.js.map +1 -1
  332. package/dist/core/memory-controller.d.ts.map +1 -1
  333. package/dist/core/memory-controller.js +4 -4
  334. package/dist/core/memory-controller.js.map +1 -1
  335. package/dist/core/model-capability.d.ts +34 -0
  336. package/dist/core/model-capability.d.ts.map +1 -1
  337. package/dist/core/model-capability.js +43 -1
  338. package/dist/core/model-capability.js.map +1 -1
  339. package/dist/core/model-resolver.d.ts +1 -1
  340. package/dist/core/model-resolver.d.ts.map +1 -1
  341. package/dist/core/model-resolver.js +6 -9
  342. package/dist/core/model-resolver.js.map +1 -1
  343. package/dist/core/model-router/intent-classifier.d.ts +1 -4
  344. package/dist/core/model-router/intent-classifier.d.ts.map +1 -1
  345. package/dist/core/model-router/intent-classifier.js +1 -156
  346. package/dist/core/model-router/intent-classifier.js.map +1 -1
  347. package/dist/core/model-router/tool-escalation.d.ts +15 -0
  348. package/dist/core/model-router/tool-escalation.d.ts.map +1 -1
  349. package/dist/core/model-router/tool-escalation.js +23 -1
  350. package/dist/core/model-router/tool-escalation.js.map +1 -1
  351. package/dist/core/model-router-controller.d.ts +34 -7
  352. package/dist/core/model-router-controller.d.ts.map +1 -1
  353. package/dist/core/model-router-controller.js +95 -16
  354. package/dist/core/model-router-controller.js.map +1 -1
  355. package/dist/core/model-selection-controller.d.ts +4 -4
  356. package/dist/core/model-selection-controller.d.ts.map +1 -1
  357. package/dist/core/model-selection-controller.js.map +1 -1
  358. package/dist/core/model-tool-protocol.d.ts +23 -0
  359. package/dist/core/model-tool-protocol.d.ts.map +1 -0
  360. package/dist/core/model-tool-protocol.js +43 -0
  361. package/dist/core/model-tool-protocol.js.map +1 -0
  362. package/dist/core/models/adaptation-store.d.ts +10 -5
  363. package/dist/core/models/adaptation-store.d.ts.map +1 -1
  364. package/dist/core/models/adaptation-store.js +129 -81
  365. package/dist/core/models/adaptation-store.js.map +1 -1
  366. package/dist/core/models/default-model-suggestions.d.ts.map +1 -1
  367. package/dist/core/models/default-model-suggestions.js +17 -0
  368. package/dist/core/models/default-model-suggestions.js.map +1 -1
  369. package/dist/core/models/fitness-store.d.ts +4 -11
  370. package/dist/core/models/fitness-store.d.ts.map +1 -1
  371. package/dist/core/models/fitness-store.js +87 -51
  372. package/dist/core/models/fitness-store.js.map +1 -1
  373. package/dist/core/models/host-state-store.d.ts +34 -0
  374. package/dist/core/models/host-state-store.d.ts.map +1 -0
  375. package/dist/core/models/host-state-store.js +86 -0
  376. package/dist/core/models/host-state-store.js.map +1 -0
  377. package/dist/core/models/llamacpp-runtime.d.ts +180 -0
  378. package/dist/core/models/llamacpp-runtime.d.ts.map +1 -0
  379. package/dist/core/models/llamacpp-runtime.js +475 -0
  380. package/dist/core/models/llamacpp-runtime.js.map +1 -0
  381. package/dist/core/models/local-registration.d.ts +40 -0
  382. package/dist/core/models/local-registration.d.ts.map +1 -1
  383. package/dist/core/models/local-registration.js +94 -5
  384. package/dist/core/models/local-registration.js.map +1 -1
  385. package/dist/core/models/local-runtime.d.ts.map +1 -1
  386. package/dist/core/models/local-runtime.js +6 -5
  387. package/dist/core/models/local-runtime.js.map +1 -1
  388. package/dist/core/models/model-ref.d.ts +7 -0
  389. package/dist/core/models/model-ref.d.ts.map +1 -1
  390. package/dist/core/models/model-ref.js +26 -0
  391. package/dist/core/models/model-ref.js.map +1 -1
  392. package/dist/core/models/needle-runtime.d.ts +257 -0
  393. package/dist/core/models/needle-runtime.d.ts.map +1 -0
  394. package/dist/core/models/needle-runtime.js +519 -0
  395. package/dist/core/models/needle-runtime.js.map +1 -0
  396. package/dist/core/models/prism-llamacpp-lifecycle.d.ts +89 -0
  397. package/dist/core/models/prism-llamacpp-lifecycle.d.ts.map +1 -0
  398. package/dist/core/models/prism-llamacpp-lifecycle.js +121 -0
  399. package/dist/core/models/prism-llamacpp-lifecycle.js.map +1 -0
  400. package/dist/core/orchestration/agent-resume.d.ts +24 -0
  401. package/dist/core/orchestration/agent-resume.d.ts.map +1 -0
  402. package/dist/core/orchestration/agent-resume.js +90 -0
  403. package/dist/core/orchestration/agent-resume.js.map +1 -0
  404. package/dist/core/orchestration/capability-gateway.d.ts +61 -0
  405. package/dist/core/orchestration/capability-gateway.d.ts.map +1 -0
  406. package/dist/core/orchestration/capability-gateway.js +152 -0
  407. package/dist/core/orchestration/capability-gateway.js.map +1 -0
  408. package/dist/core/orchestration/contracts.d.ts +343 -0
  409. package/dist/core/orchestration/contracts.d.ts.map +1 -0
  410. package/dist/core/orchestration/contracts.js +135 -0
  411. package/dist/core/orchestration/contracts.js.map +1 -0
  412. package/dist/core/orchestration/delegation-ledger.d.ts +56 -0
  413. package/dist/core/orchestration/delegation-ledger.d.ts.map +1 -0
  414. package/dist/core/orchestration/delegation-ledger.js +197 -0
  415. package/dist/core/orchestration/delegation-ledger.js.map +1 -0
  416. package/dist/core/orchestration/event-store.d.ts +83 -0
  417. package/dist/core/orchestration/event-store.d.ts.map +1 -0
  418. package/dist/core/orchestration/event-store.js +560 -0
  419. package/dist/core/orchestration/event-store.js.map +1 -0
  420. package/dist/core/orchestration/lane-tool-manifests.d.ts +9 -0
  421. package/dist/core/orchestration/lane-tool-manifests.d.ts.map +1 -0
  422. package/dist/core/orchestration/lane-tool-manifests.js +37 -0
  423. package/dist/core/orchestration/lane-tool-manifests.js.map +1 -0
  424. package/dist/core/orchestration/model-binding.d.ts +15 -0
  425. package/dist/core/orchestration/model-binding.d.ts.map +1 -0
  426. package/dist/core/orchestration/model-binding.js +29 -0
  427. package/dist/core/orchestration/model-binding.js.map +1 -0
  428. package/dist/core/orchestration/policy-compiler.d.ts +49 -0
  429. package/dist/core/orchestration/policy-compiler.d.ts.map +1 -0
  430. package/dist/core/orchestration/policy-compiler.js +191 -0
  431. package/dist/core/orchestration/policy-compiler.js.map +1 -0
  432. package/dist/core/orchestration/policy-gate.d.ts +18 -0
  433. package/dist/core/orchestration/policy-gate.d.ts.map +1 -0
  434. package/dist/core/orchestration/policy-gate.js +24 -0
  435. package/dist/core/orchestration/policy-gate.js.map +1 -0
  436. package/dist/core/orchestration/profile-registry.d.ts +51 -0
  437. package/dist/core/orchestration/profile-registry.d.ts.map +1 -0
  438. package/dist/core/orchestration/profile-registry.js +393 -0
  439. package/dist/core/orchestration/profile-registry.js.map +1 -0
  440. package/dist/core/orchestration/profile-store.d.ts +31 -0
  441. package/dist/core/orchestration/profile-store.d.ts.map +1 -0
  442. package/dist/core/orchestration/profile-store.js +136 -0
  443. package/dist/core/orchestration/profile-store.js.map +1 -0
  444. package/dist/core/orchestration/risk-budget.d.ts +9 -0
  445. package/dist/core/orchestration/risk-budget.d.ts.map +1 -0
  446. package/dist/core/orchestration/risk-budget.js +48 -0
  447. package/dist/core/orchestration/risk-budget.js.map +1 -0
  448. package/dist/core/orchestration/task-runtime.d.ts +168 -0
  449. package/dist/core/orchestration/task-runtime.d.ts.map +1 -0
  450. package/dist/core/orchestration/task-runtime.js +1600 -0
  451. package/dist/core/orchestration/task-runtime.js.map +1 -0
  452. package/dist/core/orchestration/work-state-projection.d.ts +32 -0
  453. package/dist/core/orchestration/work-state-projection.d.ts.map +1 -0
  454. package/dist/core/orchestration/work-state-projection.js +116 -0
  455. package/dist/core/orchestration/work-state-projection.js.map +1 -0
  456. package/dist/core/orchestration/worker-execution-contract.d.ts +21 -0
  457. package/dist/core/orchestration/worker-execution-contract.d.ts.map +1 -0
  458. package/dist/core/orchestration/worker-execution-contract.js +179 -0
  459. package/dist/core/orchestration/worker-execution-contract.js.map +1 -0
  460. package/dist/core/orchestration/worker-result-adapter.d.ts +21 -0
  461. package/dist/core/orchestration/worker-result-adapter.d.ts.map +1 -0
  462. package/dist/core/orchestration/worker-result-adapter.js +109 -0
  463. package/dist/core/orchestration/worker-result-adapter.js.map +1 -0
  464. package/dist/core/package-manager.d.ts.map +1 -1
  465. package/dist/core/package-manager.js +11 -10
  466. package/dist/core/package-manager.js.map +1 -1
  467. package/dist/core/process-identity.d.ts +11 -0
  468. package/dist/core/process-identity.d.ts.map +1 -0
  469. package/dist/core/process-identity.js +26 -0
  470. package/dist/core/process-identity.js.map +1 -0
  471. package/dist/core/process-matrix/codes.d.ts +83 -0
  472. package/dist/core/process-matrix/codes.d.ts.map +1 -0
  473. package/dist/core/process-matrix/codes.js +15 -0
  474. package/dist/core/process-matrix/codes.js.map +1 -0
  475. package/dist/core/process-matrix/resume-launcher.d.ts +26 -0
  476. package/dist/core/process-matrix/resume-launcher.d.ts.map +1 -0
  477. package/dist/core/process-matrix/resume-launcher.js +62 -0
  478. package/dist/core/process-matrix/resume-launcher.js.map +1 -0
  479. package/dist/core/process-matrix/runtime.d.ts +78 -0
  480. package/dist/core/process-matrix/runtime.d.ts.map +1 -0
  481. package/dist/core/process-matrix/runtime.js +633 -0
  482. package/dist/core/process-matrix/runtime.js.map +1 -0
  483. package/dist/core/process-matrix/store.d.ts +25 -0
  484. package/dist/core/process-matrix/store.d.ts.map +1 -0
  485. package/dist/core/process-matrix/store.js +189 -0
  486. package/dist/core/process-matrix/store.js.map +1 -0
  487. package/dist/core/process-matrix/supervisor.d.ts +75 -0
  488. package/dist/core/process-matrix/supervisor.d.ts.map +1 -0
  489. package/dist/core/process-matrix/supervisor.js +161 -0
  490. package/dist/core/process-matrix/supervisor.js.map +1 -0
  491. package/dist/core/profile-registry.d.ts +7 -0
  492. package/dist/core/profile-registry.d.ts.map +1 -1
  493. package/dist/core/profile-registry.js +25 -7
  494. package/dist/core/profile-registry.js.map +1 -1
  495. package/dist/core/python-runtime.d.ts.map +1 -1
  496. package/dist/core/python-runtime.js +3 -3
  497. package/dist/core/python-runtime.js.map +1 -1
  498. package/dist/core/reflection-controller.d.ts +33 -9
  499. package/dist/core/reflection-controller.d.ts.map +1 -1
  500. package/dist/core/reflection-controller.js +217 -127
  501. package/dist/core/reflection-controller.js.map +1 -1
  502. package/dist/core/reload-blockers.d.ts +36 -0
  503. package/dist/core/reload-blockers.d.ts.map +1 -1
  504. package/dist/core/reload-blockers.js +50 -8
  505. package/dist/core/reload-blockers.js.map +1 -1
  506. package/dist/core/research/evidence-bundle.d.ts.map +1 -1
  507. package/dist/core/research/evidence-bundle.js +1 -6
  508. package/dist/core/research/evidence-bundle.js.map +1 -1
  509. package/dist/core/research/lane-model-resolver.d.ts +33 -0
  510. package/dist/core/research/lane-model-resolver.d.ts.map +1 -0
  511. package/dist/core/research/lane-model-resolver.js +65 -0
  512. package/dist/core/research/lane-model-resolver.js.map +1 -0
  513. package/dist/core/research/model-fitness-controller.d.ts +38 -0
  514. package/dist/core/research/model-fitness-controller.d.ts.map +1 -0
  515. package/dist/core/research/model-fitness-controller.js +106 -0
  516. package/dist/core/research/model-fitness-controller.js.map +1 -0
  517. package/dist/core/research/model-fitness.d.ts +1 -1
  518. package/dist/core/research/model-fitness.d.ts.map +1 -1
  519. package/dist/core/research/model-fitness.js +4 -4
  520. package/dist/core/research/model-fitness.js.map +1 -1
  521. package/dist/core/research/research-gate.d.ts.map +1 -1
  522. package/dist/core/research/research-gate.js +7 -7
  523. package/dist/core/research/research-gate.js.map +1 -1
  524. package/dist/core/research/research-lane-controller.d.ts +62 -0
  525. package/dist/core/research/research-lane-controller.d.ts.map +1 -0
  526. package/dist/core/research/research-lane-controller.js +292 -0
  527. package/dist/core/research/research-lane-controller.js.map +1 -0
  528. package/dist/core/research/session-evidence-bundle.d.ts +2 -4
  529. package/dist/core/research/session-evidence-bundle.d.ts.map +1 -1
  530. package/dist/core/research/session-evidence-bundle.js +10 -46
  531. package/dist/core/research/session-evidence-bundle.js.map +1 -1
  532. package/dist/core/resource-loader.d.ts.map +1 -1
  533. package/dist/core/resource-loader.js +15 -9
  534. package/dist/core/resource-loader.js.map +1 -1
  535. package/dist/core/risk-classifier.d.ts +6 -0
  536. package/dist/core/risk-classifier.d.ts.map +1 -0
  537. package/dist/core/risk-classifier.js +225 -0
  538. package/dist/core/risk-classifier.js.map +1 -0
  539. package/dist/core/runtime-builder.d.ts +76 -17
  540. package/dist/core/runtime-builder.d.ts.map +1 -1
  541. package/dist/core/runtime-builder.js +367 -124
  542. package/dist/core/runtime-builder.js.map +1 -1
  543. package/dist/core/scout-controller.d.ts +6 -0
  544. package/dist/core/scout-controller.d.ts.map +1 -1
  545. package/dist/core/scout-controller.js +66 -52
  546. package/dist/core/scout-controller.js.map +1 -1
  547. package/dist/core/sdk.d.ts +6 -3
  548. package/dist/core/sdk.d.ts.map +1 -1
  549. package/dist/core/sdk.js +64 -27
  550. package/dist/core/sdk.js.map +1 -1
  551. package/dist/core/session-analytics.d.ts.map +1 -1
  552. package/dist/core/session-analytics.js +28 -1
  553. package/dist/core/session-analytics.js.map +1 -1
  554. package/dist/core/session-image-store.d.ts +33 -0
  555. package/dist/core/session-image-store.d.ts.map +1 -0
  556. package/dist/core/session-image-store.js +283 -0
  557. package/dist/core/session-image-store.js.map +1 -0
  558. package/dist/core/session-role.d.ts +33 -0
  559. package/dist/core/session-role.d.ts.map +1 -0
  560. package/dist/core/session-role.js +58 -0
  561. package/dist/core/session-role.js.map +1 -0
  562. package/dist/core/session-snapshot.d.ts +34 -0
  563. package/dist/core/session-snapshot.d.ts.map +1 -0
  564. package/dist/core/session-snapshot.js +68 -0
  565. package/dist/core/session-snapshot.js.map +1 -0
  566. package/dist/core/session-supervision-runtime.d.ts +31 -0
  567. package/dist/core/session-supervision-runtime.d.ts.map +1 -0
  568. package/dist/core/session-supervision-runtime.js +97 -0
  569. package/dist/core/session-supervision-runtime.js.map +1 -0
  570. package/dist/core/settings-manager.d.ts +51 -7
  571. package/dist/core/settings-manager.d.ts.map +1 -1
  572. package/dist/core/settings-manager.js +168 -82
  573. package/dist/core/settings-manager.js.map +1 -1
  574. package/dist/core/system-prompt-builder.d.ts +12 -0
  575. package/dist/core/system-prompt-builder.d.ts.map +1 -1
  576. package/dist/core/system-prompt-builder.js +6 -0
  577. package/dist/core/system-prompt-builder.js.map +1 -1
  578. package/dist/core/system-prompt.d.ts +14 -1
  579. package/dist/core/system-prompt.d.ts.map +1 -1
  580. package/dist/core/system-prompt.js +20 -3
  581. package/dist/core/system-prompt.js.map +1 -1
  582. package/dist/core/tasks/session-task-state.d.ts +13 -6
  583. package/dist/core/tasks/session-task-state.d.ts.map +1 -1
  584. package/dist/core/tasks/session-task-state.js +20 -22
  585. package/dist/core/tasks/session-task-state.js.map +1 -1
  586. package/dist/core/tasks/task-contract-monitor.d.ts +45 -0
  587. package/dist/core/tasks/task-contract-monitor.d.ts.map +1 -0
  588. package/dist/core/tasks/task-contract-monitor.js +56 -0
  589. package/dist/core/tasks/task-contract-monitor.js.map +1 -0
  590. package/dist/core/tasks/task-projection.d.ts +20 -0
  591. package/dist/core/tasks/task-projection.d.ts.map +1 -0
  592. package/dist/core/tasks/task-projection.js +47 -0
  593. package/dist/core/tasks/task-projection.js.map +1 -0
  594. package/dist/core/tasks/task-state.d.ts +12 -0
  595. package/dist/core/tasks/task-state.d.ts.map +1 -1
  596. package/dist/core/tasks/task-state.js +56 -12
  597. package/dist/core/tasks/task-state.js.map +1 -1
  598. package/dist/core/tool-capability-policy.d.ts +13 -0
  599. package/dist/core/tool-capability-policy.d.ts.map +1 -0
  600. package/dist/core/tool-capability-policy.js +85 -0
  601. package/dist/core/tool-capability-policy.js.map +1 -0
  602. package/dist/core/tool-gate-controller.d.ts.map +1 -1
  603. package/dist/core/tool-gate-controller.js +5 -0
  604. package/dist/core/tool-gate-controller.js.map +1 -1
  605. package/dist/core/tool-protocol-controller.d.ts +91 -0
  606. package/dist/core/tool-protocol-controller.d.ts.map +1 -0
  607. package/dist/core/tool-protocol-controller.js +512 -0
  608. package/dist/core/tool-protocol-controller.js.map +1 -0
  609. package/dist/core/tool-recovery-log-records.d.ts +6 -0
  610. package/dist/core/tool-recovery-log-records.d.ts.map +1 -1
  611. package/dist/core/tool-recovery-log-records.js +13 -31
  612. package/dist/core/tool-recovery-log-records.js.map +1 -1
  613. package/dist/core/tool-selection/promotion.d.ts +54 -0
  614. package/dist/core/tool-selection/promotion.d.ts.map +1 -0
  615. package/dist/core/tool-selection/promotion.js +81 -0
  616. package/dist/core/tool-selection/promotion.js.map +1 -0
  617. package/dist/core/tool-selection/tool-performance-store.d.ts +39 -6
  618. package/dist/core/tool-selection/tool-performance-store.d.ts.map +1 -1
  619. package/dist/core/tool-selection/tool-performance-store.js +153 -110
  620. package/dist/core/tool-selection/tool-performance-store.js.map +1 -1
  621. package/dist/core/tool-selection/tool-selection-controller.d.ts +45 -0
  622. package/dist/core/tool-selection/tool-selection-controller.d.ts.map +1 -1
  623. package/dist/core/tool-selection/tool-selection-controller.js +96 -0
  624. package/dist/core/tool-selection/tool-selection-controller.js.map +1 -1
  625. package/dist/core/tools/ask-question.d.ts +124 -0
  626. package/dist/core/tools/ask-question.d.ts.map +1 -0
  627. package/dist/core/tools/ask-question.js +693 -0
  628. package/dist/core/tools/ask-question.js.map +1 -0
  629. package/dist/core/tools/bash.d.ts +18 -0
  630. package/dist/core/tools/bash.d.ts.map +1 -1
  631. package/dist/core/tools/bash.js +95 -16
  632. package/dist/core/tools/bash.js.map +1 -1
  633. package/dist/core/tools/delegate-status.d.ts +44 -2
  634. package/dist/core/tools/delegate-status.d.ts.map +1 -1
  635. package/dist/core/tools/delegate-status.js +214 -19
  636. package/dist/core/tools/delegate-status.js.map +1 -1
  637. package/dist/core/tools/delegate.d.ts +15 -13
  638. package/dist/core/tools/delegate.d.ts.map +1 -1
  639. package/dist/core/tools/delegate.js +124 -22
  640. package/dist/core/tools/delegate.js.map +1 -1
  641. package/dist/core/tools/extensionify.d.ts.map +1 -1
  642. package/dist/core/tools/extensionify.js +1 -0
  643. package/dist/core/tools/extensionify.js.map +1 -1
  644. package/dist/core/tools/goal.d.ts +94 -3
  645. package/dist/core/tools/goal.d.ts.map +1 -1
  646. package/dist/core/tools/goal.js +165 -15
  647. package/dist/core/tools/goal.js.map +1 -1
  648. package/dist/core/tools/model-fitness.d.ts +7 -0
  649. package/dist/core/tools/model-fitness.d.ts.map +1 -1
  650. package/dist/core/tools/model-fitness.js +4 -4
  651. package/dist/core/tools/model-fitness.js.map +1 -1
  652. package/dist/core/tools/orchestration-panel.d.ts +43 -0
  653. package/dist/core/tools/orchestration-panel.d.ts.map +1 -0
  654. package/dist/core/tools/orchestration-panel.js +168 -0
  655. package/dist/core/tools/orchestration-panel.js.map +1 -0
  656. package/dist/core/tools/run-process.d.ts +26 -0
  657. package/dist/core/tools/run-process.d.ts.map +1 -0
  658. package/dist/core/tools/run-process.js +138 -0
  659. package/dist/core/tools/run-process.js.map +1 -0
  660. package/dist/core/tools/shell-contract-router.d.ts +6 -1
  661. package/dist/core/tools/shell-contract-router.d.ts.map +1 -1
  662. package/dist/core/tools/shell-contract-router.js +23 -2
  663. package/dist/core/tools/shell-contract-router.js.map +1 -1
  664. package/dist/core/tools/shell-session.d.ts +89 -0
  665. package/dist/core/tools/shell-session.d.ts.map +1 -0
  666. package/dist/core/tools/shell-session.js +432 -0
  667. package/dist/core/tools/shell-session.js.map +1 -0
  668. package/dist/core/tools/task-steps.d.ts +6 -0
  669. package/dist/core/tools/task-steps.d.ts.map +1 -1
  670. package/dist/core/tools/task-steps.js +165 -8
  671. package/dist/core/tools/task-steps.js.map +1 -1
  672. package/dist/core/tools/tmux-dispatch.d.ts +84 -0
  673. package/dist/core/tools/tmux-dispatch.d.ts.map +1 -0
  674. package/dist/core/tools/tmux-dispatch.js +91 -0
  675. package/dist/core/tools/tmux-dispatch.js.map +1 -0
  676. package/dist/core/tools/windows-shell-engine.d.ts +42 -0
  677. package/dist/core/tools/windows-shell-engine.d.ts.map +1 -0
  678. package/dist/core/tools/windows-shell-engine.js +151 -0
  679. package/dist/core/tools/windows-shell-engine.js.map +1 -0
  680. package/dist/core/tools/windows-shell-state.d.ts +40 -0
  681. package/dist/core/tools/windows-shell-state.d.ts.map +1 -0
  682. package/dist/core/tools/windows-shell-state.js +59 -0
  683. package/dist/core/tools/windows-shell-state.js.map +1 -0
  684. package/dist/core/tools/worktree-sync.d.ts +24 -0
  685. package/dist/core/tools/worktree-sync.d.ts.map +1 -0
  686. package/dist/core/tools/worktree-sync.js +338 -0
  687. package/dist/core/tools/worktree-sync.js.map +1 -0
  688. package/dist/core/trust-manager.d.ts +4 -1
  689. package/dist/core/trust-manager.d.ts.map +1 -1
  690. package/dist/core/trust-manager.js +20 -2
  691. package/dist/core/trust-manager.js.map +1 -1
  692. package/dist/core/util/atomic-file.d.ts +55 -0
  693. package/dist/core/util/atomic-file.d.ts.map +1 -0
  694. package/dist/core/util/atomic-file.js +255 -0
  695. package/dist/core/util/atomic-file.js.map +1 -0
  696. package/dist/core/util/bounded-jsonl.d.ts +10 -0
  697. package/dist/core/util/bounded-jsonl.d.ts.map +1 -0
  698. package/dist/core/util/bounded-jsonl.js +75 -0
  699. package/dist/core/util/bounded-jsonl.js.map +1 -0
  700. package/dist/core/util/minimatch-cache.d.ts +33 -0
  701. package/dist/core/util/minimatch-cache.d.ts.map +1 -0
  702. package/dist/core/util/minimatch-cache.js +0 -0
  703. package/dist/core/util/minimatch-cache.js.map +1 -0
  704. package/dist/core/util/value-guards.d.ts +5 -0
  705. package/dist/core/util/value-guards.d.ts.map +1 -0
  706. package/dist/core/util/value-guards.js +12 -0
  707. package/dist/core/util/value-guards.js.map +1 -0
  708. package/dist/core/worktree-sync/codes.d.ts +227 -0
  709. package/dist/core/worktree-sync/codes.d.ts.map +1 -0
  710. package/dist/core/worktree-sync/codes.js +14 -0
  711. package/dist/core/worktree-sync/codes.js.map +1 -0
  712. package/dist/core/worktree-sync/git-engine.d.ts +156 -0
  713. package/dist/core/worktree-sync/git-engine.d.ts.map +1 -0
  714. package/dist/core/worktree-sync/git-engine.js +1191 -0
  715. package/dist/core/worktree-sync/git-engine.js.map +1 -0
  716. package/dist/core/worktree-sync/lane-gate.d.ts +75 -0
  717. package/dist/core/worktree-sync/lane-gate.d.ts.map +1 -0
  718. package/dist/core/worktree-sync/lane-gate.js +360 -0
  719. package/dist/core/worktree-sync/lane-gate.js.map +1 -0
  720. package/dist/core/worktree-sync/runtime.d.ts +47 -0
  721. package/dist/core/worktree-sync/runtime.d.ts.map +1 -0
  722. package/dist/core/worktree-sync/runtime.js +96 -0
  723. package/dist/core/worktree-sync/runtime.js.map +1 -0
  724. package/dist/core/worktree-sync/store.d.ts +69 -0
  725. package/dist/core/worktree-sync/store.d.ts.map +1 -0
  726. package/dist/core/worktree-sync/store.js +252 -0
  727. package/dist/core/worktree-sync/store.js.map +1 -0
  728. package/dist/core/worktree-sync/watcher.d.ts +29 -0
  729. package/dist/core/worktree-sync/watcher.d.ts.map +1 -0
  730. package/dist/core/worktree-sync/watcher.js +93 -0
  731. package/dist/core/worktree-sync/watcher.js.map +1 -0
  732. package/dist/index.d.ts +13 -3
  733. package/dist/index.d.ts.map +1 -1
  734. package/dist/index.js +12 -2
  735. package/dist/index.js.map +1 -1
  736. package/dist/main.d.ts.map +1 -1
  737. package/dist/main.js +179 -1
  738. package/dist/main.js.map +1 -1
  739. package/dist/migrations.d.ts +7 -0
  740. package/dist/migrations.d.ts.map +1 -1
  741. package/dist/migrations.js +197 -1
  742. package/dist/migrations.js.map +1 -1
  743. package/dist/modes/interactive/auto-learn-controller.d.ts +16 -1
  744. package/dist/modes/interactive/auto-learn-controller.d.ts.map +1 -1
  745. package/dist/modes/interactive/auto-learn-controller.js +50 -8
  746. package/dist/modes/interactive/auto-learn-controller.js.map +1 -1
  747. package/dist/modes/interactive/clipboard-input.d.ts +7 -1
  748. package/dist/modes/interactive/clipboard-input.d.ts.map +1 -1
  749. package/dist/modes/interactive/clipboard-input.js +44 -6
  750. package/dist/modes/interactive/clipboard-input.js.map +1 -1
  751. package/dist/modes/interactive/components/profile-resource-editor.d.ts.map +1 -1
  752. package/dist/modes/interactive/components/profile-resource-editor.js +23 -1
  753. package/dist/modes/interactive/components/profile-resource-editor.js.map +1 -1
  754. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  755. package/dist/modes/interactive/components/settings-selector.js +2 -2
  756. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  757. package/dist/modes/interactive/config-backup.d.ts.map +1 -1
  758. package/dist/modes/interactive/config-backup.js +5 -4
  759. package/dist/modes/interactive/config-backup.js.map +1 -1
  760. package/dist/modes/interactive/extension-ui-host.d.ts +14 -0
  761. package/dist/modes/interactive/extension-ui-host.d.ts.map +1 -1
  762. package/dist/modes/interactive/extension-ui-host.js +76 -15
  763. package/dist/modes/interactive/extension-ui-host.js.map +1 -1
  764. package/dist/modes/interactive/interactive-mode.d.ts +4 -0
  765. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  766. package/dist/modes/interactive/interactive-mode.js +67 -1
  767. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  768. package/dist/modes/interactive/key-handlers.d.ts.map +1 -1
  769. package/dist/modes/interactive/key-handlers.js +1 -1
  770. package/dist/modes/interactive/key-handlers.js.map +1 -1
  771. package/dist/modes/interactive/local-model-commands.d.ts +43 -0
  772. package/dist/modes/interactive/local-model-commands.d.ts.map +1 -1
  773. package/dist/modes/interactive/local-model-commands.js +290 -3
  774. package/dist/modes/interactive/local-model-commands.js.map +1 -1
  775. package/dist/modes/interactive/profile-menu-controller.d.ts.map +1 -1
  776. package/dist/modes/interactive/profile-menu-controller.js +7 -7
  777. package/dist/modes/interactive/profile-menu-controller.js.map +1 -1
  778. package/dist/modes/interactive/report-commands.d.ts.map +1 -1
  779. package/dist/modes/interactive/report-commands.js +21 -0
  780. package/dist/modes/interactive/report-commands.js.map +1 -1
  781. package/dist/modes/interactive/session-flow-commands.d.ts +17 -2
  782. package/dist/modes/interactive/session-flow-commands.d.ts.map +1 -1
  783. package/dist/modes/interactive/session-flow-commands.js +74 -21
  784. package/dist/modes/interactive/session-flow-commands.js.map +1 -1
  785. package/dist/modes/interactive/session-io-commands.d.ts.map +1 -1
  786. package/dist/modes/interactive/session-io-commands.js +16 -6
  787. package/dist/modes/interactive/session-io-commands.js.map +1 -1
  788. package/dist/modes/interactive/session-replacement-errors.d.ts +7 -0
  789. package/dist/modes/interactive/session-replacement-errors.d.ts.map +1 -0
  790. package/dist/modes/interactive/session-replacement-errors.js +15 -0
  791. package/dist/modes/interactive/session-replacement-errors.js.map +1 -0
  792. package/dist/modes/interactive/startup-checks.d.ts.map +1 -1
  793. package/dist/modes/interactive/startup-checks.js +1 -1
  794. package/dist/modes/interactive/startup-checks.js.map +1 -1
  795. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  796. package/dist/modes/rpc/rpc-mode.js +82 -23
  797. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  798. package/dist/modes/rpc/rpc-types.d.ts +15 -0
  799. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  800. package/dist/modes/rpc/rpc-types.js +138 -1
  801. package/dist/modes/rpc/rpc-types.js.map +1 -1
  802. package/dist/utils/clipboard-image.d.ts.map +1 -1
  803. package/dist/utils/clipboard-image.js +2 -13
  804. package/dist/utils/clipboard-image.js.map +1 -1
  805. package/dist/utils/clipboard-native.d.ts +1 -0
  806. package/dist/utils/clipboard-native.d.ts.map +1 -1
  807. package/dist/utils/clipboard-native.js.map +1 -1
  808. package/dist/utils/clipboard.d.ts +1 -0
  809. package/dist/utils/clipboard.d.ts.map +1 -1
  810. package/dist/utils/clipboard.js +52 -1
  811. package/dist/utils/clipboard.js.map +1 -1
  812. package/dist/utils/fs-watch.d.ts +11 -0
  813. package/dist/utils/fs-watch.d.ts.map +1 -1
  814. package/dist/utils/fs-watch.js +20 -2
  815. package/dist/utils/fs-watch.js.map +1 -1
  816. package/dist/utils/highlight-js-languages.d.ts +4 -0
  817. package/dist/utils/highlight-js-languages.d.ts.map +1 -0
  818. package/dist/utils/highlight-js-languages.js +573 -0
  819. package/dist/utils/highlight-js-languages.js.map +1 -0
  820. package/dist/utils/platform.d.ts +3 -0
  821. package/dist/utils/platform.d.ts.map +1 -0
  822. package/dist/utils/platform.js +8 -0
  823. package/dist/utils/platform.js.map +1 -0
  824. package/dist/utils/shell.d.ts +7 -1
  825. package/dist/utils/shell.d.ts.map +1 -1
  826. package/dist/utils/shell.js +21 -6
  827. package/dist/utils/shell.js.map +1 -1
  828. package/dist/utils/syntax-highlight.d.ts.map +1 -1
  829. package/dist/utils/syntax-highlight.js +53 -5
  830. package/dist/utils/syntax-highlight.js.map +1 -1
  831. package/dist/utils/tools-manager.d.ts.map +1 -1
  832. package/dist/utils/tools-manager.js +112 -1
  833. package/dist/utils/tools-manager.js.map +1 -1
  834. package/dist/utils/work-directory.d.ts +11 -0
  835. package/dist/utils/work-directory.d.ts.map +1 -1
  836. package/dist/utils/work-directory.js +30 -9
  837. package/dist/utils/work-directory.js.map +1 -1
  838. package/docs/UI_POLISH_ORCHESTRATION.md +44 -0
  839. package/docs/UI_QUALITY_ORCHESTRATION.md +30 -0
  840. package/docs/docs.json +4 -0
  841. package/docs/extensions.md +62 -2
  842. package/docs/harness-architecture.md +156 -0
  843. package/docs/keybindings.md +11 -1
  844. package/docs/process-matrix.md +153 -0
  845. package/docs/rpc.md +45 -2
  846. package/docs/sessions.md +15 -0
  847. package/docs/settings.md +128 -11
  848. package/docs/task-steps.md +8 -0
  849. package/docs/telemetry.md +19 -0
  850. package/docs/tmux-agent-manager.md +92 -2
  851. package/docs/ui-reference/README.md +9 -0
  852. package/docs/ui-reference/orchestration-after.txt +7 -0
  853. package/docs/ui-reference/orchestration-before.txt +7 -0
  854. package/docs/ui-reference/orchestration-reference.txt +6 -0
  855. package/docs/usage.md +14 -2
  856. package/docs/windows.md +52 -3
  857. package/docs/work-directory.md +42 -2
  858. package/docs/worktree-sync.md +250 -0
  859. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  860. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  861. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  862. package/examples/extensions/question.ts +34 -245
  863. package/examples/extensions/questionnaire.ts +41 -407
  864. package/examples/extensions/sandbox/package-lock.json +2 -2
  865. package/examples/extensions/sandbox/package.json +1 -1
  866. package/examples/extensions/with-deps/package-lock.json +2 -2
  867. package/examples/extensions/with-deps/package.json +1 -1
  868. package/npm-shrinkwrap.json +12 -12
  869. package/package.json +10 -4
  870. package/dist/core/delegation/session-worker-result.d.ts +0 -16
  871. package/dist/core/delegation/session-worker-result.d.ts.map +0 -1
  872. package/dist/core/delegation/session-worker-result.js +0 -53
  873. package/dist/core/delegation/session-worker-result.js.map +0 -1
  874. package/dist/core/delegation/worker-result.d.ts +0 -15
  875. package/dist/core/delegation/worker-result.d.ts.map +0 -1
  876. package/dist/core/delegation/worker-result.js +0 -161
  877. package/dist/core/delegation/worker-result.js.map +0 -1
  878. package/dist/core/telemetry.d.ts.map +0 -1
  879. package/dist/core/telemetry.js.map +0 -1
  880. package/docs/integration-sweep-builder-blueprint-2026-07-09.md +0 -365
  881. package/docs/integration-sweep-resume-2026-07-09.md +0 -407
@@ -4,9 +4,37 @@ import * as fs from "node:fs";
4
4
  import { homedir } from "node:os";
5
5
  import * as path from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
- import { type ExtensionAPI, type ExtensionContext, getProcessWorkRun } from "@caupulican/pi-adaptative";
7
+ import {
8
+ type ExtensionAPI,
9
+ type ExtensionContext,
10
+ getProcessWorkRun,
11
+ PI_ORCHESTRATION_AGENT_ID_ENV,
12
+ } from "@caupulican/pi-adaptative";
8
13
  import type { AgentToolResult } from "@caupulican/pi-agent-core";
14
+ import type { Usage } from "@caupulican/pi-ai";
9
15
  import { Type } from "typebox";
16
+ import {
17
+ buildGrant,
18
+ buildGrantUsageEntry,
19
+ buildLaunchProfileFlags,
20
+ buildTombstone,
21
+ countGrantUsages,
22
+ DEFAULT_READ_BIASED_TOOLS,
23
+ decodeTmuxWorkerUsageClaim,
24
+ describeGrant,
25
+ GRANT_CUSTOM_TYPE,
26
+ GRANT_USAGE_CUSTOM_TYPE,
27
+ type GrantDispatchParams,
28
+ grantCovers,
29
+ isTmuxDispatchGrant,
30
+ isTmuxDispatchGrantTombstone,
31
+ type LaunchProfileFlag,
32
+ type LaunchProfileSource,
33
+ launchProfileSourceFromGrant,
34
+ ONE_SHOT_LAUNCH_PROFILE_SOURCE,
35
+ type Provider,
36
+ type TmuxDispatchGrant,
37
+ } from "./dispatch-grant.ts";
10
38
 
11
39
  export const piConfig = { tools: ["tmux_agent_manager"] };
12
40
 
@@ -20,6 +48,8 @@ type Action =
20
48
  | "workspace_plan"
21
49
  | "launch_workspace"
22
50
  | "fire_task"
51
+ | "send_followup"
52
+ | "dismiss"
23
53
  | "job_status"
24
54
  | "list_jobs"
25
55
  | "set_variable"
@@ -27,10 +57,25 @@ type Action =
27
57
  | "list_templates"
28
58
  | "show_template"
29
59
  | "stop_job"
30
- | "stop_session";
31
- type Provider = "pi" | "codex" | "agy" | "claude" | "opencode" | "custom";
60
+ | "stop_session"
61
+ | "grant_dispatch"
62
+ | "revoke_grant";
32
63
 
33
- type AgentSpec = { provider?: Provider; name?: string; command?: string; cwd?: string; tools?: string[] };
64
+ type AgentSpec = {
65
+ provider?: Provider;
66
+ name?: string;
67
+ command?: string;
68
+ cwd?: string;
69
+ tools?: string[];
70
+ /**
71
+ * Worktree-sync lane key this agent is bound to (core-internal only -- set by the goal->tmux
72
+ * lane-first dispatch adapter, `tmux-dispatch.ts`'s `dispatchTmuxWorker`, never by the model's own
73
+ * `fire_task` params). A `provider:"pi"` agent carrying this launches with `--worktree-lane <key>`
74
+ * appended and one extra lane-doctrine sentence in its scoped `--append-system-prompt` -- see
75
+ * `applyLaunchProfile`/`dispatch-grant.ts`'s `buildLaunchProfileFlags`.
76
+ */
77
+ worktreeLane?: string;
78
+ };
34
79
  type TeamTemplate = {
35
80
  name: string;
36
81
  description: string;
@@ -53,12 +98,33 @@ type Params = {
53
98
  teamTemplate?: string;
54
99
  task?: string;
55
100
  jobId?: string;
101
+ agentId?: string;
56
102
  variableName?: string;
57
103
  variableValue?: string;
58
104
  deadlineSeconds?: number;
59
105
  dryRun?: boolean;
60
106
  force?: boolean;
61
107
  confirm?: string;
108
+ /** Goal this dispatch/grant is scoped to. Goal-bound dispatch wires this through to grant
109
+ * coverage, managed-lane ownership, and the child process's task-recovery fence. */
110
+ goalId?: string;
111
+ /** grant_dispatch: target agent for the standing grant. revoke_grant: unused (the active grant's own
112
+ * agent is used for its tombstone; this field is not required to revoke). */
113
+ agent?: Provider;
114
+ /** grant_dispatch: allowlisted tool names pushed into the child `pi`'s own --tools flag. */
115
+ allowedTools?: string[];
116
+ /** grant_dispatch: resource profile name pushed into the child `pi`'s own --resource-profile flag. */
117
+ resourceProfile?: string;
118
+ /** grant_dispatch: write paths the grant's scoped system prompt tells the child worker to stay within. */
119
+ writePaths?: string[];
120
+ /** grant_dispatch: number of real launches (fire_task/send_followup dispatches) this grant authorizes. */
121
+ maxLaunches?: number;
122
+ /** grant_dispatch: grant validity window from creation time. Omit for a grant that never expires (until revoke_grant). */
123
+ expiresInMinutes?: number;
124
+ /** grant_dispatch: advisory USD ceiling. Never enforced across the process boundary — a claim to review only. */
125
+ maxUsdAdvisory?: number;
126
+ /** revoke_grant: grantId to revoke. Optional — omitted means "revoke whichever grant is currently active". */
127
+ grantId?: string;
62
128
  };
63
129
 
64
130
  type RunResult = { ok: boolean; status: number | null; stdout: string; stderr: string; error?: string; args: string[] };
@@ -92,6 +158,25 @@ type FireAgentPlan = {
92
158
  resultPath: string;
93
159
  paneId?: string;
94
160
  result?: AgentResult;
161
+ /** Last turn dispatched to this agent's pane via fire_task (1) or send_followup (>=2). Absent means 1. */
162
+ currentTurn?: number;
163
+ /** Last turn whose terminal handoff was delivered to the parent. Absent means 0 (never notified). */
164
+ notifiedTurn?: number;
165
+ /** A follow-up turn durably reserved before its prompt is injected. Reconciliation may re-arm its
166
+ * watcher, but never re-injects it: a retry must not duplicate an uncertain external side effect. */
167
+ pendingTurn?: number;
168
+ /** Mirrors `AgentSpec.worktreeLane` -- carried onto the plan so `applyLaunchProfile` (per-agent
169
+ * flags) and the dispatch-phase `reportManagedLane` call can both read it after `buildFireTaskPlan`. */
170
+ worktreeLane?: string;
171
+ /** Durable host-lifecycle metadata for the current turn. */
172
+ dispatchAuthorizationId?: string;
173
+ dispatchAuthorizationKind?: "standing-grant" | "one-shot-owner-approval" | "legacy-recovery";
174
+ dispatchAllowedTools?: string[];
175
+ dispatchWritePaths?: string[];
176
+ dispatchMaxCostUsd?: number;
177
+ dispatchInstructions?: string;
178
+ dispatchGoalId?: string;
179
+ dispatchLeaseTtlMs?: number;
95
180
  };
96
181
  type FireTaskPlan = {
97
182
  id: string;
@@ -111,7 +196,185 @@ type FireTaskPlan = {
111
196
  agents: FireAgentPlan[];
112
197
  launchCommands: string[];
113
198
  variables?: VariableState;
199
+ /** Mirrors the follow-up target agent's currentTurn, for external observability (job_status). */
200
+ currentTurn?: number;
201
+ /** Set when session-level reconcile finds the tmux session gone while the job was not terminal. Informational only; nothing is ever killed to produce this state. */
202
+ orphanedAt?: string;
203
+ /** Set by action=dismiss: the manager stops tracking/re-arming this job. The tmux session is left running. */
204
+ dismissedAt?: string;
205
+ };
206
+ /** The subset of FireTaskPlan the pane watcher generator actually reads — narrow on purpose so a
207
+ * synthesized single-agent, single-turn spec (send_followup, reconcile resume) can reuse it without
208
+ * fabricating unrelated FireTaskPlan fields (createdAt, jobPath, task, ...). */
209
+ type PaneWatcherJobSpec = Pick<FireTaskPlan, "id" | "sessionName" | "deadlineSeconds" | "agents">;
210
+ type ManagedLaneDispatchBridge = {
211
+ sequence: number;
212
+ instructions: string;
213
+ profileId: string;
214
+ provider: string;
215
+ authorizationId: string;
216
+ authorizationKind: "standing-grant" | "one-shot-owner-approval" | "legacy-recovery";
217
+ allowedTools: readonly string[];
218
+ writePaths: readonly string[];
219
+ maxCostUsd?: number;
220
+ leaseTtlMs: number;
114
221
  };
222
+ type ManagedLaneBridgeEventBase = {
223
+ laneId: string;
224
+ goalId?: string;
225
+ worktreeLaneKey?: string;
226
+ };
227
+ type ManagedLaneBridgeEvent =
228
+ | (ManagedLaneBridgeEventBase & { phase: "dispatch"; dispatch: ManagedLaneDispatchBridge })
229
+ | (ManagedLaneBridgeEventBase & {
230
+ phase: "terminal";
231
+ status?: string;
232
+ reasonCode?: string;
233
+ changedFiles?: string[];
234
+ usage?: Usage;
235
+ });
236
+ /** The subset of `ExtensionAPI` this file calls through a narrow local type instead of the full
237
+ * `ExtensionAPI`, so a lightweight test double only needs to implement what a given test path actually
238
+ * exercises. Production hosts provide `reportManagedLane`, making its pre-launch durable reservation a
239
+ * hard gate. The optional shape only preserves standalone extension-test hosts; without the bridge,
240
+ * lifecycle durability is explicitly unavailable. `reportSpawnedUsage` remains an advisory claim sink.
241
+ * `appendEntry`/`getFlag`/`registerFlag` back the STANDING GRANT itself (session persistence + the
242
+ * non-interactive opt-in flag); `registerFlag` is called unconditionally at extension load so it stays
243
+ * optional-chained too, while `appendEntry`/`getFlag` are asserted present at the point a grant action
244
+ * actually needs them (a host that can't persist a grant should fail that action clearly, not pretend). */
245
+ type HostBridge = {
246
+ reportManagedLane?: (event: ManagedLaneBridgeEvent) => void;
247
+ reportSpawnedUsage?: (usage: Usage, opts?: { label?: string; sourceSessionId?: string; reportId?: string }) => void;
248
+ appendEntry?: (customType: string, data?: unknown) => void;
249
+ getFlag?: (name: string) => boolean | string | undefined;
250
+ registerFlag?: (
251
+ name: string,
252
+ options: { description?: string; type: "boolean" | "string"; default?: boolean | string },
253
+ ) => void;
254
+ };
255
+ function agentLaneId(jobId: string, agentId: string): string {
256
+ return `tmux:${jobId}:${agentId}`;
257
+ }
258
+
259
+ function managedDispatch(job: FireTaskPlan, agent: FireAgentPlan): ManagedLaneDispatchBridge {
260
+ const sequence = agent.currentTurn ?? 1;
261
+ const instructions = agent.dispatchInstructions ?? job.task;
262
+ const authorizationId = agent.dispatchAuthorizationId ?? `legacy-job:${job.id}:turn:${sequence}`;
263
+ return {
264
+ sequence,
265
+ instructions,
266
+ profileId: `tmux:${agent.provider}`,
267
+ provider: agent.provider,
268
+ authorizationId,
269
+ authorizationKind: agent.dispatchAuthorizationKind ?? "legacy-recovery",
270
+ allowedTools: agent.dispatchAllowedTools ?? DEFAULT_READ_BIASED_TOOLS,
271
+ writePaths: agent.dispatchWritePaths ?? [],
272
+ ...(agent.dispatchMaxCostUsd !== undefined ? { maxCostUsd: agent.dispatchMaxCostUsd } : {}),
273
+ leaseTtlMs: agent.dispatchLeaseTtlMs ?? job.deadlineSeconds * 1_000,
274
+ };
275
+ }
276
+
277
+ function managedAuthorization(
278
+ grant: TmuxDispatchGrant | undefined,
279
+ fallbackId: string,
280
+ ): {
281
+ authorizationId: string;
282
+ authorizationKind: "standing-grant" | "one-shot-owner-approval";
283
+ launchSource: LaunchProfileSource;
284
+ allowedTools: string[];
285
+ writePaths: string[];
286
+ maxCostUsd?: number;
287
+ } {
288
+ const launchSource = grant ? launchProfileSourceFromGrant(grant) : ONE_SHOT_LAUNCH_PROFILE_SOURCE;
289
+ return {
290
+ authorizationId: grant?.grantId ?? fallbackId,
291
+ authorizationKind: grant ? "standing-grant" : "one-shot-owner-approval",
292
+ launchSource,
293
+ allowedTools: [...(launchSource.allowedTools?.length ? launchSource.allowedTools : DEFAULT_READ_BIASED_TOOLS)],
294
+ writePaths: [...(launchSource.writePaths ?? [])],
295
+ ...(grant?.budget.maxUsdAdvisory !== undefined ? { maxCostUsd: grant.budget.maxUsdAdvisory } : {}),
296
+ };
297
+ }
298
+
299
+ function applyManagedAuthorization(
300
+ agent: FireAgentPlan,
301
+ authorization: ReturnType<typeof managedAuthorization>,
302
+ args: { instructions: string; goalId?: string; leaseTtlMs: number },
303
+ ): void {
304
+ agent.dispatchAuthorizationId = authorization.authorizationId;
305
+ agent.dispatchAuthorizationKind = authorization.authorizationKind;
306
+ agent.dispatchAllowedTools = [...authorization.allowedTools];
307
+ agent.dispatchWritePaths = [...authorization.writePaths];
308
+ if (authorization.maxCostUsd !== undefined) agent.dispatchMaxCostUsd = authorization.maxCostUsd;
309
+ else delete agent.dispatchMaxCostUsd;
310
+ agent.dispatchInstructions = args.instructions;
311
+ agent.dispatchGoalId = args.goalId;
312
+ agent.dispatchLeaseTtlMs = args.leaseTtlMs;
313
+ }
314
+
315
+ function reportManagedDispatchReservations(
316
+ bridge: HostBridge,
317
+ job: FireTaskPlan,
318
+ agents: readonly FireAgentPlan[],
319
+ ): void {
320
+ if (!bridge.reportManagedLane) return;
321
+ const attemptedLaneIds: string[] = [];
322
+ try {
323
+ for (const agent of agents) {
324
+ const laneId = agentLaneId(job.id, agent.id);
325
+ attemptedLaneIds.push(laneId);
326
+ bridge.reportManagedLane({
327
+ laneId,
328
+ phase: "dispatch",
329
+ goalId: agent.dispatchGoalId,
330
+ worktreeLaneKey: agent.worktreeLane,
331
+ dispatch: managedDispatch(job, agent),
332
+ });
333
+ }
334
+ } catch (error: unknown) {
335
+ for (const laneId of attemptedLaneIds) {
336
+ try {
337
+ bridge.reportManagedLane({
338
+ laneId,
339
+ phase: "terminal",
340
+ status: "failed",
341
+ reasonCode: "managed_process_launch_reservation_failed",
342
+ });
343
+ } catch {
344
+ // Preserve the reservation error; durable leases fence any compensation failure.
345
+ }
346
+ }
347
+ throw error;
348
+ }
349
+ }
350
+
351
+ function reportManagedLaunchFailure(bridge: HostBridge, job: FireTaskPlan, reasonCode: string): void {
352
+ if (!bridge.reportManagedLane) return;
353
+ for (const agent of job.agents) {
354
+ reportManagedAgentFailure(bridge, job.id, agent.id, reasonCode);
355
+ }
356
+ }
357
+
358
+ function reportManagedAgentFailure(bridge: HostBridge, jobId: string, agentId: string, reasonCode: string): void {
359
+ if (!bridge.reportManagedLane) return;
360
+ try {
361
+ bridge.reportManagedLane({
362
+ laneId: agentLaneId(jobId, agentId),
363
+ phase: "terminal",
364
+ status: "failed",
365
+ reasonCode,
366
+ });
367
+ } catch {
368
+ // Preserve the launch failure; durable leases fence any terminal-report failure.
369
+ }
370
+ }
371
+
372
+ function sameStringValues(left: readonly string[], right: readonly string[]): boolean {
373
+ if (left.length !== right.length) return false;
374
+ const sortedLeft = [...left].sort();
375
+ const sortedRight = [...right].sort();
376
+ return sortedLeft.every((value, index) => value === sortedRight[index]);
377
+ }
115
378
 
116
379
  const EXTENSION_ROOT = path.dirname(fileURLToPath(import.meta.url));
117
380
  export function getTmuxAgentManagerDataRoot(): string {
@@ -266,6 +529,9 @@ function runTmux(args: string[], timeoutMs = 5_000): RunResult {
266
529
  args: [tmux, ...args],
267
530
  };
268
531
  }
532
+ function requireTmuxRun(result: RunResult, action: string): void {
533
+ if (!result.ok) throw new Error(`${action} failed: ${result.error || result.stderr || `exit ${result.status}`}`);
534
+ }
269
535
  function detectTmux(): TmuxDetection {
270
536
  const tmuxBin = process.env.PI_TMUX_MANAGER_TMUX_BIN || findExecutable("tmux");
271
537
  const errors: string[] = [];
@@ -431,7 +697,7 @@ function normalizeAgents(params: Params): AgentSpec[] {
431
697
  if (!baseCommand) throw new Error(`agent ${name} needs command when provider=custom`);
432
698
  const tools = Array.isArray(agent.tools) && agent.tools.length > 0 ? agent.tools : undefined;
433
699
  const command = tools ? `${baseCommand} --tools ${tools.join(",")}` : baseCommand;
434
- return { provider, name, command, cwd: agent.cwd, tools };
700
+ return { provider, name, command, cwd: agent.cwd, tools, worktreeLane: agent.worktreeLane };
435
701
  });
436
702
  }
437
703
  function defaultProviderInvocation(provider: Provider): string {
@@ -500,7 +766,7 @@ function buildWorkspacePlan(ctx: ExtensionContext, params: Params) {
500
766
  );
501
767
  return { workspaceName, sessionName, cwd, agents, launchCommands };
502
768
  }
503
- export function makePaneWatcherScript(job: FireTaskPlan): string {
769
+ export function makePaneWatcherScript(job: PaneWatcherJobSpec): string {
504
770
  const agentCases = job.agents.flatMap((agent) => [
505
771
  ` ${agent.id})`,
506
772
  ` agent_name=${quoteShell(agent.name)}`,
@@ -639,6 +905,7 @@ function buildFireTaskPlan(ctx: ExtensionContext, params: Params): FireTaskPlan
639
905
  promptPath: path.join(jobDir, `${slug}.prompt.md`),
640
906
  logPath: path.join(jobDir, `${slug}.log`),
641
907
  resultPath: path.join(jobDir, `${slug}.result.json`),
908
+ worktreeLane: agent.worktreeLane,
642
909
  };
643
910
  });
644
911
  const job: FireTaskPlan = {
@@ -731,9 +998,78 @@ function targetDisplayName(agent: AgentSpec, index: number): string {
731
998
  function tmuxSessionExists(name: string): boolean {
732
999
  return runTmux(["has-session", "-t", name], 2_000).ok;
733
1000
  }
1001
+ function tmuxPaneExists(sessionName: string, paneId: string): boolean {
1002
+ const result = runTmux(["list-panes", "-t", sessionName, "-F", "#{pane_id}"], 2_000);
1003
+ if (!result.ok) return false;
1004
+ return result.stdout
1005
+ .split(/\s+/)
1006
+ .map((line) => line.trim())
1007
+ .includes(paneId);
1008
+ }
1009
+ /** True when tmux already has a pipe-pane consumer attached to this pane (tmux's own `#{pane_pipe}`
1010
+ * format flag). Session-level reconcile MUST check this before re-arming: a pane that still has an
1011
+ * active pipe already has a live watcher; replacing it would close that watcher's stdin and race its
1012
+ * own EOF-triggered `finish(failed, pane-stream-ended)` against the real marker that may be about to
1013
+ * arrive, possibly recording a false failure for a job that was about to succeed on its own. Re-arming
1014
+ * is only safe (and only useful) when tmux reports no pipe is currently attached. */
1015
+ function tmuxPaneHasPipe(paneId: string): boolean {
1016
+ const result = runTmux(["display-message", "-p", "-t", paneId, "#{pane_pipe}"], 2_000);
1017
+ return result.ok && result.stdout.trim() === "1";
1018
+ }
1019
+ function turnMarkers(
1020
+ job: PaneWatcherJobSpec,
1021
+ agent: FireAgentPlan,
1022
+ turn: number,
1023
+ ): { doneMarker: string; blockedMarker: string } {
1024
+ if (turn <= 1) return { doneMarker: agent.doneMarker, blockedMarker: agent.blockedMarker };
1025
+ const base = `TMUX_${crypto.createHash("sha1").update(`${job.id}:${agent.id}:${turn}`).digest("hex").slice(0, 10).toUpperCase()}`;
1026
+ return { doneMarker: `${base}_DONE`, blockedMarker: `${base}_BLOCKED` };
1027
+ }
1028
+ function turnResultPath(jobDir: string, agentId: string, turn: number, baseResultPath: string): string {
1029
+ return turn <= 1 ? baseResultPath : path.join(jobDir, `${agentId}.turn-${turn}.result.json`);
1030
+ }
1031
+ function turnPromptPath(jobDir: string, agentId: string, turn: number, basePromptPath: string): string {
1032
+ return turn <= 1 ? basePromptPath : path.join(jobDir, `${agentId}.turn-${turn}.prompt.md`);
1033
+ }
1034
+ function turnWatcherPath(job: FireTaskPlan, turn: number): string {
1035
+ return turn <= 1 ? job.watcherPath : path.join(job.jobDir, `pane-watcher.turn-${turn}.sh`);
1036
+ }
1037
+ /** The result-file path the CURRENT turn's watcher will write to for this agent (turn 1 keeps the
1038
+ * fixed back-compat path). Stop handling must target this path, not the fixed one, or a stop signal
1039
+ * issued mid-follow-up would land on a stale turn's file that nothing is watching anymore. */
1040
+ function currentResultPath(job: FireTaskPlan, agent: FireAgentPlan): string {
1041
+ return turnResultPath(job.jobDir, agent.id, agent.currentTurn ?? 1, agent.resultPath);
1042
+ }
1043
+
1044
+ function persistAgentTurnFailure(jobId: string, agentId: string, turn: number, reason: string): void {
1045
+ persistJobPatch(jobId, (current) => {
1046
+ const agent = current.agents.find((entry) => entry.id === agentId);
1047
+ if (!agent || agent.currentTurn !== turn) return undefined;
1048
+ const result: AgentResult = {
1049
+ status: "failed",
1050
+ detectedAt: new Date().toISOString(),
1051
+ reason,
1052
+ };
1053
+ fs.writeFileSync(currentResultPath(current, agent), `${JSON.stringify(result, null, 2)}\n`, { mode: 0o600 });
1054
+ agent.result = result;
1055
+ delete agent.pendingTurn;
1056
+ return current;
1057
+ });
1058
+ }
1059
+ /** Atomically read-modify-write job.json, always from a FRESH read (never from a caller's possibly
1060
+ * stale in-memory copy), mirroring the temp+rename pattern the original notify path used. */
1061
+ function persistJobPatch(jobId: string, mutate: (current: FireTaskPlan) => FireTaskPlan | undefined): FireTaskPlan {
1062
+ const jobPath = path.join(jobsRoot(), jobId, "job.json");
1063
+ const current = JSON.parse(fs.readFileSync(jobPath, "utf8")) as FireTaskPlan;
1064
+ const mutated = mutate(current) ?? current;
1065
+ const temporaryPath = `${jobPath}.tmp-${process.pid}-${Date.now()}`;
1066
+ fs.writeFileSync(temporaryPath, `${JSON.stringify(mutated, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
1067
+ fs.renameSync(temporaryPath, jobPath);
1068
+ return mutated;
1069
+ }
734
1070
  function sendCommandToPane(paneId: string, command: string): void {
735
- runTmux(["send-keys", "-t", paneId, "-l", command]);
736
- runTmux(["send-keys", "-t", paneId, "Enter"]);
1071
+ requireTmuxRun(runTmux(["send-keys", "-t", paneId, "-l", command]), `tmux command injection for ${paneId}`);
1072
+ requireTmuxRun(runTmux(["send-keys", "-t", paneId, "Enter"]), `tmux command submit for ${paneId}`);
737
1073
  }
738
1074
  function launchTmuxSession(
739
1075
  sessionName: string,
@@ -754,36 +1090,114 @@ function launchTmuxSession(
754
1090
  runs.push(create);
755
1091
  if (!create.ok)
756
1092
  throw new Error(`tmux new-session failed: ${create.error || create.stderr || `exit ${create.status}`}`);
757
- const firstPane = create.stdout.trim();
758
- paneIds.push(firstPane);
759
- sendCommandToPane(firstPane, first.command);
760
- for (const pane of panes.slice(1)) {
761
- const split = runTmux(
762
- ["split-window", "-d", "-P", "-F", "#{pane_id}", "-t", `${sessionName}:0`, "-c", pane.cwd || cwd],
763
- 5_000,
764
- );
765
- runs.push(split);
766
- if (!split.ok)
767
- throw new Error(`tmux split-window failed: ${split.error || split.stderr || `exit ${split.status}`}`);
768
- const paneId = split.stdout.trim();
769
- paneIds.push(paneId);
770
- sendCommandToPane(paneId, pane.command);
1093
+ try {
1094
+ const firstPane = create.stdout.trim();
1095
+ paneIds.push(firstPane);
1096
+ sendCommandToPane(firstPane, first.command);
1097
+ for (const pane of panes.slice(1)) {
1098
+ const split = runTmux(
1099
+ ["split-window", "-d", "-P", "-F", "#{pane_id}", "-t", `${sessionName}:0`, "-c", pane.cwd || cwd],
1100
+ 5_000,
1101
+ );
1102
+ runs.push(split);
1103
+ if (!split.ok)
1104
+ throw new Error(`tmux split-window failed: ${split.error || split.stderr || `exit ${split.status}`}`);
1105
+ const paneId = split.stdout.trim();
1106
+ paneIds.push(paneId);
1107
+ sendCommandToPane(paneId, pane.command);
1108
+ }
1109
+ runs.push(runTmux(["select-layout", "-t", `${sessionName}:0`, "tiled"], 3_000));
1110
+ return { runs, paneIds };
1111
+ } catch (error: unknown) {
1112
+ runTmux(["kill-session", "-t", sessionName], 5_000);
1113
+ throw error;
771
1114
  }
772
- runs.push(runTmux(["select-layout", "-t", `${sessionName}:0`, "tiled"], 3_000));
773
- return { runs, paneIds };
774
1115
  }
775
1116
  function injectPromptToPane(paneId: string, prompt: string, provider: Provider): void {
776
1117
  const text = interactivePromptText(prompt);
777
1118
  if (provider === "claude") {
778
1119
  const bufferName = `pi-tmux-${crypto.randomBytes(4).toString("hex")}`;
779
- runTmux(["set-buffer", "-b", bufferName, text], 10_000);
780
- runTmux(["paste-buffer", "-b", bufferName, "-t", paneId], 10_000);
781
- runTmux(["send-keys", "-t", paneId, "Enter"], 3_000);
782
- runTmux(["delete-buffer", "-b", bufferName], 3_000);
1120
+ requireTmuxRun(runTmux(["set-buffer", "-b", bufferName, text], 10_000), "tmux prompt buffer creation");
1121
+ try {
1122
+ requireTmuxRun(
1123
+ runTmux(["paste-buffer", "-b", bufferName, "-t", paneId], 10_000),
1124
+ `tmux prompt injection for ${paneId}`,
1125
+ );
1126
+ requireTmuxRun(runTmux(["send-keys", "-t", paneId, "Enter"], 3_000), `tmux prompt submit for ${paneId}`);
1127
+ } finally {
1128
+ runTmux(["delete-buffer", "-b", bufferName], 3_000);
1129
+ }
783
1130
  return;
784
1131
  }
785
- runTmux(["send-keys", "-t", paneId, "-l", text], 10_000);
786
- runTmux(["send-keys", "-t", paneId, provider === "agy" ? "C-m" : "Enter"], 3_000);
1132
+ requireTmuxRun(runTmux(["send-keys", "-t", paneId, "-l", text], 10_000), `tmux prompt injection for ${paneId}`);
1133
+ requireTmuxRun(
1134
+ runTmux(["send-keys", "-t", paneId, provider === "agy" ? "C-m" : "Enter"], 3_000),
1135
+ `tmux prompt submit for ${paneId}`,
1136
+ );
1137
+ }
1138
+ /** Write a fresh per-turn prompt + single-agent watcher script and arm `pipe-pane -O` for it. Used by
1139
+ * send_followup to open a NEW turn on an already-live pane. Does not inject the prompt text into the
1140
+ * pane itself — callers do that separately via injectPromptToPane, after this has armed the watcher,
1141
+ * so the completion contract is listening before the worker can possibly reply. */
1142
+ function dispatchAgentTurn(
1143
+ job: FireTaskPlan,
1144
+ agent: FireAgentPlan,
1145
+ turn: number,
1146
+ promptText: string,
1147
+ ): { promptPath: string; resultPath: string; watcherPath: string } {
1148
+ if (!agent.paneId) throw new Error(`tmux agent ${agent.name} has no recorded pane id`);
1149
+ const markers = turnMarkers(job, agent, turn);
1150
+ const resultPath = turnResultPath(job.jobDir, agent.id, turn, agent.resultPath);
1151
+ const promptPath = turnPromptPath(job.jobDir, agent.id, turn, agent.promptPath);
1152
+ const watcherPath = turnWatcherPath(job, turn);
1153
+ fs.writeFileSync(promptPath, `${promptText}\n`, { mode: 0o600 });
1154
+ const turnSpec: PaneWatcherJobSpec = {
1155
+ id: job.id,
1156
+ sessionName: job.sessionName,
1157
+ deadlineSeconds: job.deadlineSeconds,
1158
+ agents: [
1159
+ { ...agent, doneMarker: markers.doneMarker, blockedMarker: markers.blockedMarker, resultPath, promptPath },
1160
+ ],
1161
+ };
1162
+ fs.writeFileSync(watcherPath, makePaneWatcherScript(turnSpec), { mode: 0o700 });
1163
+ fs.chmodSync(watcherPath, 0o700);
1164
+ const armed = runTmux([
1165
+ "pipe-pane",
1166
+ "-O",
1167
+ "-t",
1168
+ agent.paneId,
1169
+ `sh ${quoteShell(watcherPath)} ${quoteShell(agent.id)}`,
1170
+ ]);
1171
+ if (!armed.ok)
1172
+ throw new Error(
1173
+ `Failed to arm completion watcher for turn ${turn}: ${armed.error || armed.stderr || armed.stdout}`,
1174
+ );
1175
+ return { promptPath, resultPath, watcherPath };
1176
+ }
1177
+ /** Session-level reconcile's resume step: re-arm `pipe-pane` for an agent whose current turn is still
1178
+ * pending on a pane tmux confirms is alive AND has no pipe already attached (see tmuxPaneHasPipe). Only
1179
+ * regenerates the watcher SCRIPT file if it is missing (e.g. this is the first reconcile after a reload
1180
+ * whose disk state predates a crash) — the prompt was already injected once and is not re-sent. */
1181
+ function rearmAgentWatcher(job: FireTaskPlan, agent: FireAgentPlan): void {
1182
+ if (!agent.paneId) return;
1183
+ const turn = agent.currentTurn ?? 1;
1184
+ const markers = turnMarkers(job, agent, turn);
1185
+ const resultPath = turnResultPath(job.jobDir, agent.id, turn, agent.resultPath);
1186
+ const promptPath = turnPromptPath(job.jobDir, agent.id, turn, agent.promptPath);
1187
+ const watcherPath = turnWatcherPath(job, turn);
1188
+ if (!fs.existsSync(watcherPath)) {
1189
+ const turnSpec: PaneWatcherJobSpec = {
1190
+ id: job.id,
1191
+ sessionName: job.sessionName,
1192
+ deadlineSeconds: job.deadlineSeconds,
1193
+ agents: [
1194
+ { ...agent, doneMarker: markers.doneMarker, blockedMarker: markers.blockedMarker, resultPath, promptPath },
1195
+ ],
1196
+ };
1197
+ fs.writeFileSync(watcherPath, makePaneWatcherScript(turnSpec), { mode: 0o700 });
1198
+ fs.chmodSync(watcherPath, 0o700);
1199
+ }
1200
+ runTmux(["pipe-pane", "-O", "-t", agent.paneId, `sh ${quoteShell(watcherPath)} ${quoteShell(agent.id)}`]);
787
1201
  }
788
1202
  function stopTmuxSession(
789
1203
  sessionName: string,
@@ -804,9 +1218,10 @@ function isAlreadyExistsError(error: unknown): boolean {
804
1218
  }
805
1219
  function markStopRequested(job: FireTaskPlan): void {
806
1220
  for (const agent of job.agents) {
807
- if (fs.existsSync(agent.resultPath)) continue;
1221
+ const resultPath = currentResultPath(job, agent);
1222
+ if (fs.existsSync(resultPath)) continue;
808
1223
  try {
809
- fs.writeFileSync(`${agent.resultPath}.stop-requested`, `${new Date().toISOString()}\n`, {
1224
+ fs.writeFileSync(`${resultPath}.stop-requested`, `${new Date().toISOString()}\n`, {
810
1225
  encoding: "utf8",
811
1226
  mode: 0o600,
812
1227
  flag: "wx",
@@ -817,11 +1232,12 @@ function markStopRequested(job: FireTaskPlan): void {
817
1232
  }
818
1233
  }
819
1234
  function clearStopRequested(job: FireTaskPlan): void {
820
- for (const agent of job.agents) fs.rmSync(`${agent.resultPath}.stop-requested`, { force: true });
1235
+ for (const agent of job.agents) fs.rmSync(`${currentResultPath(job, agent)}.stop-requested`, { force: true });
821
1236
  }
822
1237
  function persistStoppedResults(job: FireTaskPlan): void {
823
1238
  for (const agent of job.agents) {
824
- if (!fs.existsSync(agent.resultPath)) {
1239
+ const resultPath = currentResultPath(job, agent);
1240
+ if (!fs.existsSync(resultPath)) {
825
1241
  const result = {
826
1242
  jobId: job.id,
827
1243
  agentId: agent.id,
@@ -834,7 +1250,7 @@ function persistStoppedResults(job: FireTaskPlan): void {
834
1250
  notifiedBy: "stop-action-event",
835
1251
  };
836
1252
  try {
837
- fs.writeFileSync(agent.resultPath, `${JSON.stringify(result, null, 2)}\n`, {
1253
+ fs.writeFileSync(resultPath, `${JSON.stringify(result, null, 2)}\n`, {
838
1254
  encoding: "utf8",
839
1255
  mode: 0o600,
840
1256
  flag: "wx",
@@ -843,7 +1259,7 @@ function persistStoppedResults(job: FireTaskPlan): void {
843
1259
  if (!isAlreadyExistsError(error)) throw error;
844
1260
  }
845
1261
  }
846
- fs.rmSync(`${agent.resultPath}.stop-requested`, { force: true });
1262
+ fs.rmSync(`${resultPath}.stop-requested`, { force: true });
847
1263
  }
848
1264
  }
849
1265
  function stopTmuxSessionWithJobSignals(
@@ -888,8 +1304,10 @@ function loadJob(id: string): FireTaskPlan {
888
1304
  const parsed = JSON.parse(fs.readFileSync(jobPath, "utf8")) as FireTaskPlan;
889
1305
  if (!Array.isArray(parsed.agents)) throw new Error(`tmux job has invalid agents: ${id}`);
890
1306
  for (const agent of parsed.agents) {
1307
+ // Read from the CURRENT turn's result path (turn 1 is the fixed back-compat path) so a job
1308
+ // mid-follow-up is correctly seen as non-terminal until the NEW turn settles.
891
1309
  try {
892
- agent.result = JSON.parse(fs.readFileSync(agent.resultPath, "utf8")) as AgentResult;
1310
+ agent.result = JSON.parse(fs.readFileSync(currentResultPath(parsed, agent), "utf8")) as AgentResult;
893
1311
  } catch {
894
1312
  delete agent.result;
895
1313
  }
@@ -930,7 +1348,13 @@ function listJobs(): unknown[] {
930
1348
  workspaceName: job.workspaceName,
931
1349
  sessionName: job.sessionName,
932
1350
  createdAt: job.createdAt,
933
- agents: job.agents.map((agent) => ({ name: agent.name, status: agent.result?.status || "pending" })),
1351
+ orphaned: Boolean(job.orphanedAt),
1352
+ dismissed: Boolean(job.dismissedAt),
1353
+ agents: job.agents.map((agent) => ({
1354
+ name: agent.name,
1355
+ status: agent.result?.status || "pending",
1356
+ turn: agent.currentTurn ?? 1,
1357
+ })),
934
1358
  };
935
1359
  } catch (error: unknown) {
936
1360
  return { id: d.name, error: errorMessage(error) };
@@ -957,12 +1381,130 @@ function setVariable(jobId: string, name: string, value: string) {
957
1381
  fs.writeFileSync(job.varsPath, `${JSON.stringify(current, null, 2)}\n`, { mode: 0o600 });
958
1382
  return { varsPath: job.varsPath, variables: current.variables, updatedAt: current.updatedAt };
959
1383
  }
1384
+ // ---------------------------------------------------------------------------
1385
+ // STANDING GRANT — approval-gated tmux dispatch. The pure decode/decision logic lives in
1386
+ // dispatch-grant.ts; the functions below are the session-access GLUE: read the latest grant custom
1387
+ // entry (skipping past malformed payloads, but stopping DEAD on a tombstone — never resurrecting an
1388
+ // older grant beneath a revocation), count spend, and gate a real launch.
1389
+ // ---------------------------------------------------------------------------
1390
+
1391
+ /** Most recent VALID `tmux-dispatch-grant` entry on the active branch, or undefined when there is
1392
+ * none, it was revoked (a tombstone is a hard stop — never skipped to find an older grant), or every
1393
+ * entry found fails to decode. Mirrors the branch-walk idiom used by goal/task session state. */
1394
+ function resolveLatestGrantEntry(ctx: ExtensionContext): TmuxDispatchGrant | undefined {
1395
+ let fromId: string | undefined;
1396
+ for (;;) {
1397
+ const entry = ctx.sessionManager.getLatestCustomEntryOnBranch(GRANT_CUSTOM_TYPE, fromId);
1398
+ if (!entry) return undefined;
1399
+ if (isTmuxDispatchGrantTombstone(entry.data)) return undefined;
1400
+ if (isTmuxDispatchGrant(entry.data)) return entry.data;
1401
+ if (!entry.parentId) return undefined;
1402
+ fromId = entry.parentId;
1403
+ }
1404
+ }
1405
+ /** Decoded `data` payloads of every custom entry of `customType` on the active branch (root→leaf
1406
+ * order), for spend-counting — a single `getLatestCustomEntryOnBranch` call only ever returns ONE
1407
+ * entry, but budget spend requires ALL of them. */
1408
+ function collectCustomEntryData(ctx: ExtensionContext, customType: string): unknown[] {
1409
+ const result: unknown[] = [];
1410
+ for (const entry of ctx.sessionManager.getBranch()) {
1411
+ const loose = entry as unknown as { type?: string; customType?: string; data?: unknown };
1412
+ if (loose.type === "custom" && loose.customType === customType) result.push(loose.data);
1413
+ }
1414
+ return result;
1415
+ }
1416
+ /** Gate a REAL tmux dispatch (fire_task non-dryRun, send_followup) per the standing-grant doctrine:
1417
+ * a valid covering grant dispatches UNATTENDED (spending one usage); absent that, an
1418
+ * interactive host may approve a ONE-SHOT launch; absent BOTH, this REFUSES — never a silent launch.
1419
+ * Returns the covering grant when one authorized this launch (so the caller can derive launch-profile
1420
+ * flags from its envelope), or `{}` for a one-shot approval (no envelope to derive from — the caller
1421
+ * falls back to the conservative default profile). */
1422
+ async function authorizeLaunch(
1423
+ bridge: HostBridge,
1424
+ ctx: ExtensionContext,
1425
+ request: { agents: readonly Provider[]; goalId?: string; jobId: string; description: string },
1426
+ ): Promise<{ grant?: TmuxDispatchGrant }> {
1427
+ if (request.agents.length === 0) throw new Error("tmux dispatch refused: launch has no agents");
1428
+ const grant = resolveLatestGrantEntry(ctx);
1429
+ // A standing grant names one provider. It must cover every child and spend one unit for every
1430
+ // child process; a matching first pane must never authorize an unrestricted mixed team.
1431
+ if (grant && request.agents.every((agent) => grantCovers(grant, { agent, goalId: request.goalId }))) {
1432
+ const used = countGrantUsages(grant.grantId, collectCustomEntryData(ctx, GRANT_USAGE_CUSTOM_TYPE));
1433
+ if (grant.budget.maxLaunches - used >= request.agents.length) {
1434
+ if (!bridge.appendEntry)
1435
+ throw new Error("this host does not support session custom entries; cannot spend the tmux dispatch grant.");
1436
+ for (let index = 0; index < request.agents.length; index++) {
1437
+ bridge.appendEntry(
1438
+ GRANT_USAGE_CUSTOM_TYPE,
1439
+ buildGrantUsageEntry(grant.grantId, `${request.jobId}:${index}`),
1440
+ );
1441
+ }
1442
+ return { grant };
1443
+ }
1444
+ }
1445
+ if (ctx.hasUI) {
1446
+ const approved = await ctx.ui.confirm(
1447
+ "tmux dispatch approval",
1448
+ [
1449
+ `No standing grant currently authorizes every child of: ${request.description}.`,
1450
+ `Agents: ${request.agents.join(", ")}${request.goalId ? `, goal: ${request.goalId}` : ""}.`,
1451
+ "Approve this ONE-SHOT launch? Run tmux_agent_manager action=grant_dispatch to authorize future launches without this prompt.",
1452
+ ].join("\n"),
1453
+ );
1454
+ if (!approved) throw new Error(`tmux dispatch declined by the owner: ${request.description}`);
1455
+ return {};
1456
+ }
1457
+ throw new Error(
1458
+ `no standing grant for tmux dispatch; run grant_dispatch first: every child must be covered. ${request.description}. Refusing to launch without a grant or interactive approval.`,
1459
+ );
1460
+ }
1461
+ /** Render grant/one-shot-derived launch-profile flags into a pi child's start command. Values
1462
+ * are shell-quoted; flags are not (they're fixed literals, never user input). */
1463
+ function appendLaunchProfileFlags(command: string, flags: LaunchProfileFlag[]): string {
1464
+ const rendered = flags.map((flag) =>
1465
+ flag.value !== undefined ? `${flag.flag} ${quoteShell(flag.value)}` : flag.flag,
1466
+ );
1467
+ return [command, ...rendered].join(" ");
1468
+ }
1469
+ /** Apply the launch profile to every provider="pi" agent in the job (fire_task only — send_followup
1470
+ * reuses an already-launched pane, so there is no new child command to configure). Non-pi agents are
1471
+ * bounded by the grant only at the launch layer (agent/budget/count); their internal tool-loop
1472
+ * enforcement is the target CLI's own responsibility (documented limitation, not a hidden gap). */
1473
+ function applyLaunchProfile(job: FireTaskPlan, source: LaunchProfileSource): void {
1474
+ for (const agent of job.agents) {
1475
+ if (agent.provider !== "pi") continue;
1476
+ // Per-agent flags: `worktreeLane` is a per-agent property (only a lane-first goal->tmux
1477
+ // dispatch sets it), so the shared `source` envelope is extended with it just for this agent's
1478
+ // flag build -- every other agent in a multi-agent team keeps the plain grant-derived profile.
1479
+ const flags = buildLaunchProfileFlags(
1480
+ agent.worktreeLane ? { ...source, worktreeLane: agent.worktreeLane } : source,
1481
+ );
1482
+ const profiledCommand = appendLaunchProfileFlags(
1483
+ agent.command || defaultProviderInvocation(agent.provider),
1484
+ flags,
1485
+ );
1486
+ agent.command = `env ${PI_ORCHESTRATION_AGENT_ID_ENV}=${quoteShell(agentLaneId(job.id, agent.id))} ${profiledCommand}`;
1487
+ }
1488
+ }
1489
+ /** Read an OPTIONAL, cooperative worker-reported usage claim for the turn that just went terminal (a
1490
+ * sibling `<result-path>.usage.json` file next to the watcher's own result file). Absent file ⇒ no
1491
+ * claim was offered ⇒ nothing is reported (never fabricated). */
1492
+ function readWorkerUsageClaim(job: FireTaskPlan, agent: FireAgentPlan): Usage | undefined {
1493
+ const usagePath = `${currentResultPath(job, agent)}.usage.json`;
1494
+ try {
1495
+ return decodeTmuxWorkerUsageClaim(JSON.parse(fs.readFileSync(usagePath, "utf8")));
1496
+ } catch {
1497
+ return undefined;
1498
+ }
1499
+ }
1500
+
960
1501
  async function executeTool(
961
1502
  _toolCallId: string,
962
1503
  params: Params,
963
1504
  _signal: AbortSignal | undefined,
964
1505
  _onUpdate: unknown,
965
1506
  ctx: ExtensionContext,
1507
+ bridge: HostBridge,
966
1508
  ): Promise<AgentToolResult<unknown>> {
967
1509
  const action = params.action || "status";
968
1510
  const detection = detectTmux();
@@ -1015,6 +1557,62 @@ async function executeTool(
1015
1557
  details: { action, jobId: params.jobId, variableName: params.variableName, result },
1016
1558
  };
1017
1559
  }
1560
+ if (action === "grant_dispatch") {
1561
+ if (!params.agent) throw new Error("grant_dispatch requires agent");
1562
+ if (!Object.hasOwn(PROVIDER_COMMANDS, params.agent))
1563
+ throw new Error(`grant_dispatch: unsupported agent: ${params.agent}`);
1564
+ const grantParams: GrantDispatchParams = {
1565
+ agent: params.agent,
1566
+ goalId: params.goalId,
1567
+ allowedTools: params.allowedTools,
1568
+ resourceProfile: params.resourceProfile,
1569
+ writePaths: params.writePaths,
1570
+ maxLaunches: typeof params.maxLaunches === "number" ? params.maxLaunches : Number.NaN,
1571
+ expiresInMinutes: params.expiresInMinutes,
1572
+ maxUsdAdvisory: params.maxUsdAdvisory,
1573
+ };
1574
+ const grant = buildGrant(grantParams);
1575
+ const summary = describeGrant(grant);
1576
+ // OWNER-AUTHORIZED, NEVER SILENT: an interactive host must explicitly confirm the exact
1577
+ // grant details; a non-interactive host (print/rpc, or the unattended goal loop) has no confirm
1578
+ // surface, so it needs an explicit opt-in flag instead — absent either, refuse to create the grant.
1579
+ if (ctx.hasUI) {
1580
+ const approved = await ctx.ui.confirm("Authorize tmux dispatch grant", summary);
1581
+ if (!approved) throw new Error("grant_dispatch was declined by the owner.");
1582
+ } else if (bridge.getFlag?.("allow-tmux-dispatch") !== true) {
1583
+ throw new Error(
1584
+ "grant_dispatch requires interactive approval; no UI is available in this mode. Pass --allow-tmux-dispatch to authorize grant creation non-interactively.",
1585
+ );
1586
+ }
1587
+ if (!bridge.appendEntry)
1588
+ throw new Error("this host does not support session custom entries; cannot persist a tmux dispatch grant.");
1589
+ bridge.appendEntry(GRANT_CUSTOM_TYPE, grant);
1590
+ return {
1591
+ content: [{ type: "text", text: `Created tmux dispatch grant ${grant.grantId}.\n${summary}` }],
1592
+ details: { action, grant },
1593
+ };
1594
+ }
1595
+ if (action === "revoke_grant") {
1596
+ const activeGrant = resolveLatestGrantEntry(ctx);
1597
+ const targetGrantId = firstString(params.grantId) || activeGrant?.grantId;
1598
+ if (!targetGrantId) throw new Error("revoke_grant: no active tmux dispatch grant to revoke.");
1599
+ if (activeGrant && activeGrant.grantId !== targetGrantId)
1600
+ throw new Error(
1601
+ `revoke_grant: grantId ${targetGrantId} is not the active grant (active: ${activeGrant.grantId}).`,
1602
+ );
1603
+ if (!bridge.appendEntry)
1604
+ throw new Error("this host does not support session custom entries; cannot revoke a tmux dispatch grant.");
1605
+ bridge.appendEntry(GRANT_CUSTOM_TYPE, buildTombstone(targetGrantId));
1606
+ return {
1607
+ content: [
1608
+ {
1609
+ type: "text",
1610
+ text: `Revoked tmux dispatch grant ${targetGrantId}. Future launches need a new grant_dispatch or interactive/opt-in approval.`,
1611
+ },
1612
+ ],
1613
+ details: { action, grantId: targetGrantId },
1614
+ };
1615
+ }
1018
1616
  const guard = await guardTmux(ctx, detection, `tmux ${action}`);
1019
1617
  if (!guard.allowed)
1020
1618
  return { content: [{ type: "text", text: guard.text }], details: { action, detection, guard, skipped: true } };
@@ -1079,39 +1677,227 @@ async function executeTool(
1079
1677
  throw new Error(
1080
1678
  `tmux session already exists: ${job.sessionName}. Use stop_job/stop_session first or choose a different workspaceName.`,
1081
1679
  );
1082
- const archivedJobDir = prepareJobDirForLaunch(job, params.force);
1083
- const panes = job.agents.map((agent) => ({
1084
- title: agent.name,
1085
- cwd: agent.cwd,
1086
- command: agent.command || defaultProviderInvocation(agent.provider),
1087
- }));
1088
- const launch = launchTmuxSession(job.sessionName, job.cwd, panes);
1089
- job.agents.forEach((agent, index) => {
1090
- agent.paneId = launch.paneIds[index];
1680
+ // APPROVAL-GATED LAUNCH: resolved before any tmux/FS side effect. A standing grant must
1681
+ // authorize every child provider and spend its budget per child process.
1682
+ const authorization = await authorizeLaunch(bridge, ctx, {
1683
+ agents: job.agents.map((agent) => agent.provider),
1684
+ goalId: params.goalId,
1685
+ jobId: job.id,
1686
+ description: `fire_task launch of job ${job.id} (${job.agents.length} child process${job.agents.length === 1 ? "" : "es"}: ${job.agents.map((agent) => `${agent.name}/${agent.provider}`).join(", ")})`,
1091
1687
  });
1092
- writeFireTaskFiles(job);
1093
- const watcherPanes = startPaneWatchers(job);
1688
+ const managedAuthority = managedAuthorization(authorization.grant, `one-shot:${job.id}:turn:1`);
1094
1689
  for (const agent of job.agents) {
1095
- if (!agent.paneId) continue;
1096
- injectPromptToPane(agent.paneId, fs.readFileSync(agent.promptPath, "utf8"), agent.provider);
1690
+ agent.currentTurn = 1;
1691
+ applyManagedAuthorization(agent, managedAuthority, {
1692
+ instructions: job.task,
1693
+ goalId: params.goalId,
1694
+ leaseTtlMs: job.deadlineSeconds * 1_000,
1695
+ });
1696
+ }
1697
+ applyLaunchProfile(job, {
1698
+ ...managedAuthority.launchSource,
1699
+ ...(params.goalId ? { taskRef: params.goalId } : {}),
1700
+ // Process-matrix parent identity: the dispatched child self-registers as a worker of THIS
1701
+ // session and winds down gracefully if this session disappears (see dispatch-grant.ts's
1702
+ // `LaunchProfileSource.parentPid`/`parentSession` doc).
1703
+ parentPid: process.pid,
1704
+ parentSession: ctx.sessionManager.getSessionId?.() ?? ctx.sessionManager.getSessionFile(),
1705
+ });
1706
+ // The host commits the full execution grant and starts the durable lease before any child
1707
+ // process, job artifact, watcher, or prompt side effect occurs.
1708
+ reportManagedDispatchReservations(bridge, job, job.agents);
1709
+ let sessionCreated = false;
1710
+ try {
1711
+ const archivedJobDir = prepareJobDirForLaunch(job, params.force);
1712
+ const panes = job.agents.map((agent) => ({
1713
+ title: agent.name,
1714
+ cwd: agent.cwd,
1715
+ command: agent.command || defaultProviderInvocation(agent.provider),
1716
+ }));
1717
+ const launch = launchTmuxSession(job.sessionName, job.cwd, panes);
1718
+ sessionCreated = true;
1719
+ job.agents.forEach((agent, index) => {
1720
+ agent.paneId = launch.paneIds[index];
1721
+ });
1722
+ writeFireTaskFiles(job);
1723
+ const watcherPanes = startPaneWatchers(job);
1724
+ for (const agent of job.agents) {
1725
+ if (!agent.paneId) continue;
1726
+ injectPromptToPane(agent.paneId, fs.readFileSync(agent.promptPath, "utf8"), agent.provider);
1727
+ }
1728
+ return {
1729
+ content: [
1730
+ {
1731
+ type: "text",
1732
+ text: [
1733
+ `Launched tmux interactive fire-and-forget job ${job.id}.`,
1734
+ `Session: ${job.sessionName}`,
1735
+ `Attach: tmux attach -t ${job.sessionName}`,
1736
+ `Job state: ${job.jobPath}`,
1737
+ archivedJobDir ? `Archived previous job dir: ${archivedJobDir}` : undefined,
1738
+ "Completion: event-driven pane output watchers armed (no polling).",
1739
+ ]
1740
+ .filter(Boolean)
1741
+ .join("\n"),
1742
+ },
1743
+ ],
1744
+ details: {
1745
+ action,
1746
+ detection,
1747
+ job,
1748
+ runs: launch.runs,
1749
+ paneIds: launch.paneIds,
1750
+ watcherPanes,
1751
+ archivedJobDir,
1752
+ },
1753
+ };
1754
+ } catch (error: unknown) {
1755
+ if (sessionCreated) runTmux(["kill-session", "-t", job.sessionName], 5_000);
1756
+ reportManagedLaunchFailure(bridge, job, "managed_process_launch_failed");
1757
+ throw error;
1758
+ }
1759
+ }
1760
+ if (action === "send_followup") {
1761
+ if (!params.jobId) throw new Error("send_followup requires jobId");
1762
+ const followupTask = firstString(params.task, params.body);
1763
+ if (!followupTask) throw new Error("send_followup requires task (or body) with the follow-up objective");
1764
+ const job = loadJob(params.jobId);
1765
+ if (job.dismissedAt)
1766
+ throw new Error(`tmux job ${job.id} was dismissed; use fire_task to relaunch instead of send_followup`);
1767
+ if (job.agents.length === 0) throw new Error(`tmux job has no agents: ${job.id}`);
1768
+ const targetAgent = params.agentId ? job.agents.find((agent) => agent.id === params.agentId) : job.agents[0];
1769
+ if (!targetAgent) throw new Error(`tmux job ${job.id} has no agent ${params.agentId}`);
1770
+ if (!targetAgent.paneId)
1771
+ throw new Error(`tmux agent ${targetAgent.name} has no recorded pane id; cannot send a follow-up`);
1772
+ if (targetAgent.pendingTurn !== undefined)
1773
+ throw new Error(
1774
+ `send_followup refused: turn ${targetAgent.pendingTurn} for ${targetAgent.id} is pending reconciliation; refusing to risk a duplicate prompt`,
1775
+ );
1776
+ const turn = (targetAgent.currentTurn ?? 1) + 1;
1777
+ const markers = turnMarkers(job, targetAgent, turn);
1778
+ const promptText = managedPrompt(
1779
+ followupTask,
1780
+ job.id,
1781
+ targetAgent,
1782
+ markers.doneMarker,
1783
+ markers.blockedMarker,
1784
+ job.varsPath,
1785
+ );
1786
+ if (params.dryRun === true)
1787
+ return {
1788
+ content: [
1789
+ {
1790
+ type: "text",
1791
+ text: `DRY RUN tmux send_followup\nJob: ${job.id}\nAgent: ${targetAgent.name}\nTurn: ${turn}`,
1792
+ },
1793
+ ],
1794
+ details: { action, detection, dryRun: true, jobId: job.id, agentId: targetAgent.id, turn },
1795
+ };
1796
+ if (!tmuxSessionExists(job.sessionName))
1797
+ throw new Error(
1798
+ `tmux session is gone: ${job.sessionName}. The worker pane cannot receive a follow-up; use fire_task to relaunch.`,
1799
+ );
1800
+ if (!tmuxPaneExists(job.sessionName, targetAgent.paneId))
1801
+ throw new Error(
1802
+ `tmux pane ${targetAgent.paneId} for ${targetAgent.name} is gone. The worker cannot receive a follow-up; use fire_task to relaunch.`,
1803
+ );
1804
+ // APPROVAL-GATED LAUNCH (doctrine-regression mandatory): a follow-up dispatches a fresh
1805
+ // turn into an already-running child, so there is no new child command to profile — only the
1806
+ // grant/one-shot authorization is resolved here (no applyLaunchProfile call).
1807
+ const authorization = await authorizeLaunch(bridge, ctx, {
1808
+ agents: [targetAgent.provider],
1809
+ goalId: params.goalId,
1810
+ jobId: `${job.id}:turn:${turn}`,
1811
+ description: `send_followup turn ${turn} to ${targetAgent.name} in job ${job.id}`,
1812
+ });
1813
+ const managedAuthority = managedAuthorization(authorization.grant, `one-shot:${job.id}:turn:${turn}`);
1814
+ const launchAllowedTools = targetAgent.dispatchAllowedTools ?? [...DEFAULT_READ_BIASED_TOOLS];
1815
+ const launchWritePaths = targetAgent.dispatchWritePaths ?? [];
1816
+ if (
1817
+ !sameStringValues(launchAllowedTools, managedAuthority.allowedTools) ||
1818
+ !sameStringValues(launchWritePaths, managedAuthority.writePaths)
1819
+ ) {
1820
+ throw new Error(
1821
+ `send_followup refused: authorization scope differs from the already-running process for ${targetAgent.name}; relaunch the worker to apply a new tool or write-path scope.`,
1822
+ );
1823
+ }
1824
+ // Reserve before watcher/prompt side effects. Reconcile re-arms a reserved turn but never
1825
+ // re-injects it, so crash recovery cannot duplicate a provider prompt.
1826
+ const reserved = persistJobPatch(job.id, (current) => {
1827
+ const agent = current.agents.find((entry) => entry.id === targetAgent.id);
1828
+ if (!agent || agent.pendingTurn !== undefined) return undefined;
1829
+ agent.currentTurn = turn;
1830
+ agent.pendingTurn = turn;
1831
+ agent.result = undefined;
1832
+ applyManagedAuthorization(agent, managedAuthority, {
1833
+ instructions: followupTask,
1834
+ goalId: params.goalId,
1835
+ leaseTtlMs: job.deadlineSeconds * 1_000,
1836
+ });
1837
+ current.currentTurn = turn;
1838
+ return current;
1839
+ });
1840
+ const reservedAgent = reserved.agents.find((agent) => agent.id === targetAgent.id);
1841
+ if (!reservedAgent || reservedAgent.pendingTurn !== turn)
1842
+ throw new Error(`send_followup refused: failed to reserve turn ${turn} for ${targetAgent.id}`);
1843
+ try {
1844
+ reportManagedDispatchReservations(bridge, reserved, [reservedAgent]);
1845
+ } catch (error: unknown) {
1846
+ persistAgentTurnFailure(job.id, reservedAgent.id, turn, "managed_process_launch_reservation_failed");
1847
+ throw error;
1848
+ }
1849
+ let written: ReturnType<typeof dispatchAgentTurn>;
1850
+ try {
1851
+ written = dispatchAgentTurn(reserved, reservedAgent, turn, promptText);
1852
+ injectPromptToPane(reservedAgent.paneId as string, promptText, reservedAgent.provider);
1853
+ persistJobPatch(job.id, (current) => {
1854
+ const agent = current.agents.find((entry) => entry.id === reservedAgent.id);
1855
+ if (!agent || agent.pendingTurn !== turn) return undefined;
1856
+ delete agent.pendingTurn;
1857
+ return current;
1858
+ });
1859
+ } catch (error: unknown) {
1860
+ persistAgentTurnFailure(job.id, reservedAgent.id, turn, "managed_process_followup_failed");
1861
+ reportManagedAgentFailure(bridge, job.id, reservedAgent.id, "managed_process_followup_failed");
1862
+ throw error;
1097
1863
  }
1098
1864
  return {
1099
1865
  content: [
1100
1866
  {
1101
1867
  type: "text",
1102
1868
  text: [
1103
- `Launched tmux interactive fire-and-forget job ${job.id}.`,
1869
+ `Sent follow-up turn ${turn} to ${targetAgent.name} in job ${job.id}.`,
1104
1870
  `Session: ${job.sessionName}`,
1105
- `Attach: tmux attach -t ${job.sessionName}`,
1106
- `Job state: ${job.jobPath}`,
1107
- archivedJobDir ? `Archived previous job dir: ${archivedJobDir}` : undefined,
1108
- "Completion: event-driven pane output watchers armed (no polling).",
1109
- ]
1110
- .filter(Boolean)
1111
- .join("\n"),
1871
+ `Prompt: ${written.promptPath}`,
1872
+ `Completion: event-driven pane watcher re-armed for turn ${turn} (no polling).`,
1873
+ ].join("\n"),
1874
+ },
1875
+ ],
1876
+ details: { action, detection, jobId: job.id, agentId: targetAgent.id, turn, ...written },
1877
+ };
1878
+ }
1879
+ if (action === "dismiss") {
1880
+ if (!params.jobId) throw new Error("dismiss requires jobId");
1881
+ const job = loadJob(params.jobId);
1882
+ if (job.dismissedAt)
1883
+ return {
1884
+ content: [{ type: "text", text: `tmux job ${job.id} was already dismissed.` }],
1885
+ details: { action, jobId: job.id, alreadyDismissed: true },
1886
+ };
1887
+ persistJobPatch(job.id, (current) => {
1888
+ current.dismissedAt = new Date().toISOString();
1889
+ return current;
1890
+ });
1891
+ for (const agent of job.agents)
1892
+ bridge.reportManagedLane?.({ laneId: agentLaneId(job.id, agent.id), phase: "terminal", status: "dismissed" });
1893
+ return {
1894
+ content: [
1895
+ {
1896
+ type: "text",
1897
+ text: `Dismissed tmux job ${job.id}. Session '${job.sessionName}' left running (not killed); attach with: tmux attach -t ${job.sessionName}. Use stop_session/stop_job to terminate it.`,
1112
1898
  },
1113
1899
  ],
1114
- details: { action, detection, job, runs: launch.runs, paneIds: launch.paneIds, watcherPanes, archivedJobDir },
1900
+ details: { action, jobId: job.id, sessionName: job.sessionName },
1115
1901
  };
1116
1902
  }
1117
1903
  if (action === "notify") {
@@ -1193,19 +1979,71 @@ function formatFireTaskHandoff(job: FireTaskPlan): string {
1193
1979
  ].join("\n");
1194
1980
  }
1195
1981
 
1196
- function markFireTaskNotified(job: FireTaskPlan): void {
1197
- const current = JSON.parse(fs.readFileSync(job.jobPath, "utf8")) as FireTaskPlan;
1198
- current.notifiedAt = new Date().toISOString();
1199
- const temporaryPath = `${job.jobPath}.tmp-${process.pid}-${Date.now()}`;
1200
- fs.writeFileSync(temporaryPath, `${JSON.stringify(current, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
1201
- fs.renameSync(temporaryPath, job.jobPath);
1202
- job.notifiedAt = current.notifiedAt;
1982
+ /** Diff live tmux sessions against this session's job records. Mirrors the invariant behind
1983
+ * `LocalRuntimeController.reconcile` (local-runtime-controller.ts): reconcile only ever OBSERVES —
1984
+ * it never kills a session it did not provably start. A job whose tmux session is gone is marked
1985
+ * orphaned (informational, one-shot); a job whose session is alive but current turn is still
1986
+ * pending is resumed by re-arming its watcher, but ONLY when tmux confirms no pipe is already
1987
+ * attached to that pane (see tmuxPaneHasPipe) — otherwise a live watcher would be raced and could
1988
+ * be made to report a false failure. */
1989
+ function reconcileTmuxSessions(ctx: ExtensionContext, bridge: HostBridge): void {
1990
+ const sessionFile = ctx.sessionManager.getSessionFile();
1991
+ const jobs = loadJobPlans().filter((job) => job.parentSessionFile === sessionFile && !job.dismissedAt);
1992
+ if (jobs.length === 0) return;
1993
+ const detection = detectTmux();
1994
+ if (!detection.cliAvailable) return;
1995
+ const liveSessions = new Set(detection.sessions);
1996
+ for (const job of jobs) {
1997
+ if (!liveSessions.has(job.sessionName)) {
1998
+ if (!isFireTaskTerminal(job) && !job.orphanedAt) {
1999
+ persistJobPatch(job.id, (current) => {
2000
+ current.orphanedAt = new Date().toISOString();
2001
+ return current;
2002
+ });
2003
+ if (ctx.hasUI)
2004
+ ctx.ui.notify(
2005
+ `tmux job ${job.id} is orphaned: session '${job.sessionName}' is gone and the job never reached a terminal state. Nothing was killed; this is informational only.`,
2006
+ "warning",
2007
+ );
2008
+ // Nothing is killed here — only the LANE record is released. A dead session means the
2009
+ // worker itself can no longer report its own terminal state, so a goal bound to it would
2010
+ // wait forever without this: release the managed lane per non-terminal agent so the goal's
2011
+ // continuation stops waiting on a worker that will never report back.
2012
+ for (const agent of job.agents) {
2013
+ if (agent.result !== undefined) continue;
2014
+ bridge.reportManagedLane?.({
2015
+ laneId: agentLaneId(job.id, agent.id),
2016
+ phase: "terminal",
2017
+ status: "orphaned",
2018
+ reasonCode: "tmux_session_orphaned",
2019
+ });
2020
+ }
2021
+ }
2022
+ continue;
2023
+ }
2024
+ if (isFireTaskTerminal(job)) continue;
2025
+ for (const agent of job.agents) {
2026
+ if (agent.result !== undefined) continue;
2027
+ if (!agent.paneId || !tmuxPaneExists(job.sessionName, agent.paneId)) continue;
2028
+ if (tmuxPaneHasPipe(agent.paneId)) continue;
2029
+ reportManagedDispatchReservations(bridge, job, [agent]);
2030
+ rearmAgentWatcher(job, agent);
2031
+ }
2032
+ }
1203
2033
  }
1204
2034
 
1205
2035
  export default function tmuxAgentManagerExtension(pi: ExtensionAPI) {
1206
2036
  let handoffContext: ExtensionContext | undefined;
1207
2037
  let handoffTail = Promise.resolve();
1208
2038
  const jobWatchers = new Map<string, fs.FSWatcher>();
2039
+ const bridge: HostBridge = pi;
2040
+ // Non-interactive opt-in for grant_dispatch: optional-chained so a lightweight test double
2041
+ // that doesn't implement registerFlag still loads the extension; a real host always has it.
2042
+ bridge.registerFlag?.("allow-tmux-dispatch", {
2043
+ type: "boolean",
2044
+ description:
2045
+ "Authorize tmux_agent_manager grant_dispatch to create a standing tmux dispatch grant without interactive approval (print/rpc/non-interactive mode only).",
2046
+ });
1209
2047
 
1210
2048
  const closeJobWatchers = () => {
1211
2049
  for (const watcher of jobWatchers.values()) watcher.close();
@@ -1214,23 +2052,64 @@ export default function tmuxAgentManagerExtension(pi: ExtensionAPI) {
1214
2052
 
1215
2053
  const refreshJobHandoffs = async (ctx: ExtensionContext): Promise<void> => {
1216
2054
  const sessionFile = ctx.sessionManager.getSessionFile();
1217
- const jobs = loadJobPlans().filter((job) => !job.parentSessionFile || job.parentSessionFile === sessionFile);
2055
+ const jobs = loadJobPlans().filter(
2056
+ (job) => (!job.parentSessionFile || job.parentSessionFile === sessionFile) && !job.dismissedAt,
2057
+ );
1218
2058
  for (const job of jobs) {
1219
- if (!isFireTaskTerminal(job) || job.notifiedAt) continue;
1220
- pi.sendMessage(
1221
- {
1222
- customType: "tmux-background-completion",
1223
- content: formatFireTaskHandoff(job),
1224
- display: true,
1225
- details: {
1226
- jobId: job.id,
1227
- sessionName: job.sessionName,
1228
- agents: job.agents.map((agent) => ({ id: agent.id, name: agent.name, status: agent.result?.status })),
2059
+ if (!isFireTaskTerminal(job)) continue;
2060
+ // Per-turn notify: a job is not permanently "closed" after its first terminal turn — a
2061
+ // follow-up reopens it, so the gate is per-agent (notifiedTurn < currentTurn), not a
2062
+ // single whole-job notifiedAt flag.
2063
+ const pendingAgents = job.agents.filter((agent) => (agent.notifiedTurn ?? 0) < (agent.currentTurn ?? 1));
2064
+ if (pendingAgents.length === 0) continue;
2065
+ for (const agent of pendingAgents) {
2066
+ // Advisory-only, and only ever reported when the worker itself chose to write the
2067
+ // claim file — never fabricated, never a hard cross-process cap.
2068
+ const usage = readWorkerUsageClaim(job, agent);
2069
+ bridge.reportManagedLane?.({
2070
+ laneId: agentLaneId(job.id, agent.id),
2071
+ phase: "terminal",
2072
+ status: agent.result?.status,
2073
+ usage,
2074
+ });
2075
+ if (usage) {
2076
+ bridge.reportSpawnedUsage?.(usage, {
2077
+ label: "tmux-worker",
2078
+ reportId: `tmux-worker:${job.sessionName}:${job.id}:${agent.currentTurn ?? 1}`,
2079
+ });
2080
+ }
2081
+ }
2082
+ // Production hosts own the sole durable wake path through reportManagedLane. The direct
2083
+ // message remains only for lightweight/standalone hosts that omit that bridge entirely.
2084
+ if (!bridge.reportManagedLane) {
2085
+ pi.sendMessage(
2086
+ {
2087
+ customType: "tmux-background-completion",
2088
+ content: formatFireTaskHandoff(job),
2089
+ display: true,
2090
+ details: {
2091
+ jobId: job.id,
2092
+ sessionName: job.sessionName,
2093
+ agents: job.agents.map((agent) => ({
2094
+ id: agent.id,
2095
+ name: agent.name,
2096
+ status: agent.result?.status,
2097
+ })),
2098
+ },
1229
2099
  },
1230
- },
1231
- { triggerTurn: true, deliverAs: "followUp" },
1232
- );
1233
- markFireTaskNotified(job);
2100
+ { triggerTurn: true, deliverAs: "followUp" },
2101
+ );
2102
+ }
2103
+ // reportManagedLane persisted the terminal result + outbox synchronously before returning.
2104
+ // This marker only suppresses duplicate extension reports; it is not notification truth.
2105
+ persistJobPatch(job.id, (current) => {
2106
+ current.notifiedAt = new Date().toISOString();
2107
+ for (const agent of current.agents) {
2108
+ const inMemoryAgent = job.agents.find((entry) => entry.id === agent.id);
2109
+ agent.notifiedTurn = inMemoryAgent?.currentTurn ?? agent.currentTurn ?? 1;
2110
+ }
2111
+ return current;
2112
+ });
1234
2113
  if (ctx.hasUI) ctx.ui.notify(`tmux background task ${job.id} completed.`, "info");
1235
2114
  }
1236
2115
 
@@ -1277,6 +2156,7 @@ export default function tmuxAgentManagerExtension(pi: ExtensionAPI) {
1277
2156
  pi.on("session_start", async (_event, ctx) => {
1278
2157
  handoffContext = ctx;
1279
2158
  closeJobWatchers();
2159
+ reconcileTmuxSessions(ctx, bridge);
1280
2160
  await queueJobHandoffRefresh(ctx);
1281
2161
  });
1282
2162
  pi.on("session_shutdown", async () => {
@@ -1297,9 +2177,14 @@ export default function tmuxAgentManagerExtension(pi: ExtensionAPI) {
1297
2177
  "Prefer action=fire_task for interactive worker batches. It returns after event watchers and prompts are armed; do not wait, poll, or peek for completion.",
1298
2178
  "Use action=list_templates/show_template before assembling repeated teams; pass teamTemplate when a built-in team fits.",
1299
2179
  "Use action=workspace_plan before launch_workspace when designing a pane layout.",
2180
+ "Use action=send_followup to re-inject a new prompt into a live fire_task job's pane mid-run, without relaunching. It re-arms the completion watcher for a fresh turn and reuses the same event-driven handoff.",
2181
+ "Use action=dismiss to stop tracking a job without killing its tmux session; the pane keeps running and can still be attached to or stopped later with stop_job/stop_session.",
1300
2182
  "Use stop_job/stop_session dry-run first, then confirm=yes-tmux-stop for real cleanup.",
1301
2183
  "Do not put secrets in task text or command strings; prompts, commands, and logs persist under ~/.pi/agent/work/background/tmux-agent-manager/state/jobs.",
1302
2184
  "tmux display/status actions are metadata only; validate worker result/log files before claiming task completion.",
2185
+ "fire_task and send_followup are APPROVAL-GATED: a real (non-dryRun) dispatch needs either a standing grant (action=grant_dispatch, once per session/goal) or a one-shot interactive approval. With no grant and no UI available, the dispatch is refused, never silent — run grant_dispatch first.",
2186
+ "Use action=grant_dispatch to authorize repeated unattended dispatch: set agent, maxLaunches, and optionally goalId/allowedTools/resourceProfile/writePaths/expiresInMinutes. Requires interactive confirmation, or the --allow-tmux-dispatch flag when no UI is available. Use action=revoke_grant to end it early.",
2187
+ "A grant-covered pi child launches with a RESTRICTED profile (--tools/--resource-profile or --no-extensions --no-skills, plus a scoped --append-system-prompt naming the grant and its hard stops) — the envelope lives in the CHILD's own launch config, not an in-process sandbox. Non-pi agents (agy/claude/codex/opencode/custom) are bounded by the grant at launch only; their internal tool loop is that CLI's own responsibility.",
1303
2188
  ],
1304
2189
  parameters: Type.Object({
1305
2190
  action: Type.Optional(
@@ -1314,6 +2199,8 @@ export default function tmuxAgentManagerExtension(pi: ExtensionAPI) {
1314
2199
  Type.Literal("workspace_plan"),
1315
2200
  Type.Literal("launch_workspace"),
1316
2201
  Type.Literal("fire_task"),
2202
+ Type.Literal("send_followup"),
2203
+ Type.Literal("dismiss"),
1317
2204
  Type.Literal("job_status"),
1318
2205
  Type.Literal("list_jobs"),
1319
2206
  Type.Literal("set_variable"),
@@ -1322,10 +2209,12 @@ export default function tmuxAgentManagerExtension(pi: ExtensionAPI) {
1322
2209
  Type.Literal("show_template"),
1323
2210
  Type.Literal("stop_job"),
1324
2211
  Type.Literal("stop_session"),
2212
+ Type.Literal("grant_dispatch"),
2213
+ Type.Literal("revoke_grant"),
1325
2214
  ],
1326
2215
  {
1327
2216
  description:
1328
- "status, setup_help, guard, notify, set_status, clear_status, workspace_plan, launch_workspace, fire_task, job_status, list_jobs, set_variable, list_variables, list_templates, show_template, stop_job, or stop_session. Default status.",
2217
+ "status, setup_help, guard, notify, set_status, clear_status, workspace_plan, launch_workspace, fire_task, send_followup, dismiss, job_status, list_jobs, set_variable, list_variables, list_templates, show_template, stop_job, stop_session, grant_dispatch, or revoke_grant. Default status.",
1329
2218
  },
1330
2219
  ),
1331
2220
  ),
@@ -1367,12 +2256,20 @@ export default function tmuxAgentManagerExtension(pi: ExtensionAPI) {
1367
2256
  ),
1368
2257
  task: Type.Optional(
1369
2258
  Type.String({
1370
- description: "Fire-and-forget worker objective. Required for fire_task unless body is provided.",
2259
+ description:
2260
+ "Fire-and-forget worker objective. Required for fire_task unless body is provided. Also used as the follow-up objective for send_followup.",
1371
2261
  }),
1372
2262
  ),
1373
2263
  jobId: Type.Optional(
1374
2264
  Type.String({
1375
- description: "Job id for fire_task (optional), job_status/list_variables/set_variable (required).",
2265
+ description:
2266
+ "Job id for fire_task (optional), job_status/list_variables/set_variable/send_followup/dismiss (required).",
2267
+ }),
2268
+ ),
2269
+ agentId: Type.Optional(
2270
+ Type.String({
2271
+ description:
2272
+ "Target agent id for send_followup. Defaults to the job's first (primary) agent. Multi-agent-per-turn follow-up is not supported; only one agent's pane receives each follow-up.",
1376
2273
  }),
1377
2274
  ),
1378
2275
  variableName: Type.Optional(
@@ -1432,9 +2329,69 @@ export default function tmuxAgentManagerExtension(pi: ExtensionAPI) {
1432
2329
  confirm: Type.Optional(
1433
2330
  Type.String({ description: "Required for real stop_job/stop_session: yes-tmux-stop." }),
1434
2331
  ),
2332
+ goalId: Type.Optional(
2333
+ Type.String({
2334
+ description:
2335
+ "Goal this dispatch/grant is scoped to. For grant_dispatch: an unscoped grant (omitted) covers any goal; a scoped grant covers only launches naming the same goalId. For fire_task/send_followup: tags the request for grant-coverage matching.",
2336
+ }),
2337
+ ),
2338
+ agent: Type.Optional(
2339
+ Type.Union(
2340
+ [
2341
+ Type.Literal("pi"),
2342
+ Type.Literal("codex"),
2343
+ Type.Literal("agy"),
2344
+ Type.Literal("claude"),
2345
+ Type.Literal("opencode"),
2346
+ Type.Literal("custom"),
2347
+ ],
2348
+ { description: "grant_dispatch: the provider this standing grant authorizes." },
2349
+ ),
2350
+ ),
2351
+ allowedTools: Type.Optional(
2352
+ Type.Array(Type.String(), {
2353
+ description:
2354
+ "grant_dispatch: tool allowlist pushed into the child pi's own --tools flag. Defaults to a read-biased safe set (read, grep, find, ls) when omitted.",
2355
+ }),
2356
+ ),
2357
+ resourceProfile: Type.Optional(
2358
+ Type.String({
2359
+ description:
2360
+ "grant_dispatch: resource profile name pushed into the child pi's own --resource-profile flag. When omitted, the child launches with --no-extensions --no-skills instead.",
2361
+ }),
2362
+ ),
2363
+ writePaths: Type.Optional(
2364
+ Type.Array(Type.String(), {
2365
+ description: "grant_dispatch: write paths named in the child's scoped --append-system-prompt role text.",
2366
+ }),
2367
+ ),
2368
+ maxLaunches: Type.Optional(
2369
+ Type.Number({
2370
+ description: "grant_dispatch: number of real fire_task/send_followup dispatches this grant authorizes.",
2371
+ minimum: 1,
2372
+ }),
2373
+ ),
2374
+ expiresInMinutes: Type.Optional(
2375
+ Type.Number({
2376
+ description:
2377
+ "grant_dispatch: grant validity window from creation. Omit for a grant that never expires (until revoke_grant).",
2378
+ minimum: 1,
2379
+ }),
2380
+ ),
2381
+ maxUsdAdvisory: Type.Optional(
2382
+ Type.Number({
2383
+ description:
2384
+ "grant_dispatch: advisory USD ceiling. Never enforced across the process boundary (the child bills under its own auth) — a claim to review only.",
2385
+ }),
2386
+ ),
2387
+ grantId: Type.Optional(
2388
+ Type.String({
2389
+ description: "revoke_grant: grant id to revoke. Omit to revoke whichever grant is currently active.",
2390
+ }),
2391
+ ),
1435
2392
  }),
1436
2393
  async execute(toolCallId, params, signal, onUpdate, ctx) {
1437
- const result = await executeTool(toolCallId, params as Params, signal, onUpdate, ctx);
2394
+ const result = await executeTool(toolCallId, params as Params, signal, onUpdate, ctx, bridge);
1438
2395
  await queueJobHandoffRefresh(ctx);
1439
2396
  return result;
1440
2397
  },