@caupulican/pi-adaptative 0.94.0 → 0.95.0

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 (371) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/dist/bundled-resources/extensions/tmux-agent-manager/README.md +32 -13
  3. package/dist/bundled-resources/extensions/tmux-agent-manager/index.d.ts +7 -4
  4. package/dist/bundled-resources/extensions/tmux-agent-manager/index.d.ts.map +1 -1
  5. package/dist/bundled-resources/extensions/tmux-agent-manager/index.js +173 -293
  6. package/dist/bundled-resources/extensions/tmux-agent-manager/index.js.map +1 -1
  7. package/dist/bundled-resources/extensions/tmux-agent-manager/index.ts +247 -392
  8. package/dist/bundled-resources/extensions/tmux-agent-manager/launch-profile.d.ts +52 -0
  9. package/dist/bundled-resources/extensions/tmux-agent-manager/launch-profile.d.ts.map +1 -0
  10. package/dist/bundled-resources/extensions/tmux-agent-manager/launch-profile.js +118 -0
  11. package/dist/bundled-resources/extensions/tmux-agent-manager/launch-profile.js.map +1 -0
  12. package/dist/bundled-resources/extensions/tmux-agent-manager/launch-profile.ts +169 -0
  13. package/dist/bundled-resources/extensions/tmux-agent-manager/templates/builder-validator.json +1 -1
  14. package/dist/bundled-resources/extensions/tmux-agent-manager/templates/provider-prompt-smoke.json +1 -1
  15. package/dist/bundled-resources/skills/pi-harness-learning/SKILL.md +13 -7
  16. package/dist/bundled-resources/skills/worker-profile-writer/SKILL.md +11 -11
  17. package/dist/core/agent-paths.d.ts +2 -0
  18. package/dist/core/agent-paths.d.ts.map +1 -1
  19. package/dist/core/agent-paths.js +7 -0
  20. package/dist/core/agent-paths.js.map +1 -1
  21. package/dist/core/agent-session.d.ts +11 -41
  22. package/dist/core/agent-session.d.ts.map +1 -1
  23. package/dist/core/agent-session.js +98 -161
  24. package/dist/core/agent-session.js.map +1 -1
  25. package/dist/core/autonomy/envelope-enforcement.d.ts +6 -1
  26. package/dist/core/autonomy/envelope-enforcement.d.ts.map +1 -1
  27. package/dist/core/autonomy/envelope-enforcement.js +26 -25
  28. package/dist/core/autonomy/envelope-enforcement.js.map +1 -1
  29. package/dist/core/autonomy/lane-private-paths.d.ts.map +1 -1
  30. package/dist/core/autonomy/lane-private-paths.js +2 -1
  31. package/dist/core/autonomy/lane-private-paths.js.map +1 -1
  32. package/dist/core/autonomy/lane-tool-surface.d.ts +8 -8
  33. package/dist/core/autonomy/lane-tool-surface.d.ts.map +1 -1
  34. package/dist/core/autonomy/lane-tool-surface.js +45 -30
  35. package/dist/core/autonomy/lane-tool-surface.js.map +1 -1
  36. package/dist/core/autonomy/worker-session-private-scope.d.ts +12 -0
  37. package/dist/core/autonomy/worker-session-private-scope.d.ts.map +1 -0
  38. package/dist/core/autonomy/worker-session-private-scope.js +42 -0
  39. package/dist/core/autonomy/worker-session-private-scope.js.map +1 -0
  40. package/dist/core/autonomy/worker-tool-adapter-registry.d.ts +65 -0
  41. package/dist/core/autonomy/worker-tool-adapter-registry.d.ts.map +1 -0
  42. package/dist/core/autonomy/worker-tool-adapter-registry.js +145 -0
  43. package/dist/core/autonomy/worker-tool-adapter-registry.js.map +1 -0
  44. package/dist/core/background-lane-controller.d.ts +9 -1
  45. package/dist/core/background-lane-controller.d.ts.map +1 -1
  46. package/dist/core/background-lane-controller.js +76 -6
  47. package/dist/core/background-lane-controller.js.map +1 -1
  48. package/dist/core/background-tool-task-controller.d.ts +23 -2
  49. package/dist/core/background-tool-task-controller.d.ts.map +1 -1
  50. package/dist/core/background-tool-task-controller.js +132 -17
  51. package/dist/core/background-tool-task-controller.js.map +1 -1
  52. package/dist/core/context/long-term-memory-trigger.d.ts +3 -1
  53. package/dist/core/context/long-term-memory-trigger.d.ts.map +1 -1
  54. package/dist/core/context/long-term-memory-trigger.js +1 -1
  55. package/dist/core/context/long-term-memory-trigger.js.map +1 -1
  56. package/dist/core/context/okf-memory-provider.d.ts +3 -0
  57. package/dist/core/context/okf-memory-provider.d.ts.map +1 -1
  58. package/dist/core/context/okf-memory-provider.js +66 -31
  59. package/dist/core/context/okf-memory-provider.js.map +1 -1
  60. package/dist/core/context/okf-memory.d.ts +18 -1
  61. package/dist/core/context/okf-memory.d.ts.map +1 -1
  62. package/dist/core/context/okf-memory.js +48 -0
  63. package/dist/core/context/okf-memory.js.map +1 -1
  64. package/dist/core/cost-guard-controller.d.ts +37 -0
  65. package/dist/core/cost-guard-controller.d.ts.map +1 -0
  66. package/dist/core/cost-guard-controller.js +71 -0
  67. package/dist/core/cost-guard-controller.js.map +1 -0
  68. package/dist/core/cost-guard.d.ts +9 -4
  69. package/dist/core/cost-guard.d.ts.map +1 -1
  70. package/dist/core/cost-guard.js +7 -3
  71. package/dist/core/cost-guard.js.map +1 -1
  72. package/dist/core/delegation/worker-agent-control.d.ts +4 -0
  73. package/dist/core/delegation/worker-agent-control.d.ts.map +1 -1
  74. package/dist/core/delegation/worker-agent-control.js.map +1 -1
  75. package/dist/core/delegation/worker-attempt-executor.d.ts +1 -1
  76. package/dist/core/delegation/worker-attempt-executor.d.ts.map +1 -1
  77. package/dist/core/delegation/worker-attempt-executor.js +0 -1
  78. package/dist/core/delegation/worker-attempt-executor.js.map +1 -1
  79. package/dist/core/delegation/worker-authority-resolver.d.ts +16 -3
  80. package/dist/core/delegation/worker-authority-resolver.d.ts.map +1 -1
  81. package/dist/core/delegation/worker-authority-resolver.js +114 -55
  82. package/dist/core/delegation/worker-authority-resolver.js.map +1 -1
  83. package/dist/core/delegation/worker-claim.d.ts +1 -2
  84. package/dist/core/delegation/worker-claim.d.ts.map +1 -1
  85. package/dist/core/delegation/worker-claim.js.map +1 -1
  86. package/dist/core/delegation/worker-delegation-controller.d.ts +19 -6
  87. package/dist/core/delegation/worker-delegation-controller.d.ts.map +1 -1
  88. package/dist/core/delegation/worker-delegation-controller.js +125 -40
  89. package/dist/core/delegation/worker-delegation-controller.js.map +1 -1
  90. package/dist/core/delegation/worker-delegation-request.d.ts +3 -3
  91. package/dist/core/delegation/worker-delegation-request.d.ts.map +1 -1
  92. package/dist/core/delegation/worker-delegation-request.js +13 -19
  93. package/dist/core/delegation/worker-delegation-request.js.map +1 -1
  94. package/dist/core/delegation/worker-execution-policy.d.ts +3 -2
  95. package/dist/core/delegation/worker-execution-policy.d.ts.map +1 -1
  96. package/dist/core/delegation/worker-execution-policy.js +54 -39
  97. package/dist/core/delegation/worker-execution-policy.js.map +1 -1
  98. package/dist/core/delegation/worker-fleet-limits.d.ts +3 -3
  99. package/dist/core/delegation/worker-fleet-limits.d.ts.map +1 -1
  100. package/dist/core/delegation/worker-fleet-limits.js +7 -7
  101. package/dist/core/delegation/worker-fleet-limits.js.map +1 -1
  102. package/dist/core/delegation/worker-machine-scope.d.ts +12 -0
  103. package/dist/core/delegation/worker-machine-scope.d.ts.map +1 -0
  104. package/dist/core/delegation/worker-machine-scope.js +38 -0
  105. package/dist/core/delegation/worker-machine-scope.js.map +1 -0
  106. package/dist/core/delegation/worker-notification-coordinator.d.ts +10 -0
  107. package/dist/core/delegation/worker-notification-coordinator.d.ts.map +1 -1
  108. package/dist/core/delegation/worker-notification-coordinator.js +69 -3
  109. package/dist/core/delegation/worker-notification-coordinator.js.map +1 -1
  110. package/dist/core/delegation/worker-recovery-coordinator.d.ts.map +1 -1
  111. package/dist/core/delegation/worker-recovery-coordinator.js +6 -1
  112. package/dist/core/delegation/worker-recovery-coordinator.js.map +1 -1
  113. package/dist/core/delegation/worker-runner.d.ts +5 -7
  114. package/dist/core/delegation/worker-runner.d.ts.map +1 -1
  115. package/dist/core/delegation/worker-runner.js +1 -4
  116. package/dist/core/delegation/worker-runner.js.map +1 -1
  117. package/dist/core/delegation/worker-write-reservation-coordinator.d.ts +3 -2
  118. package/dist/core/delegation/worker-write-reservation-coordinator.d.ts.map +1 -1
  119. package/dist/core/delegation/worker-write-reservation-coordinator.js +50 -25
  120. package/dist/core/delegation/worker-write-reservation-coordinator.js.map +1 -1
  121. package/dist/core/durable-custom-message-turn-controller.d.ts +6 -0
  122. package/dist/core/durable-custom-message-turn-controller.d.ts.map +1 -1
  123. package/dist/core/durable-custom-message-turn-controller.js +38 -15
  124. package/dist/core/durable-custom-message-turn-controller.js.map +1 -1
  125. package/dist/core/extension-binding-controller.d.ts +5 -1
  126. package/dist/core/extension-binding-controller.d.ts.map +1 -1
  127. package/dist/core/extension-binding-controller.js +15 -0
  128. package/dist/core/extension-binding-controller.js.map +1 -1
  129. package/dist/core/extensions/factory-runtime.d.ts.map +1 -1
  130. package/dist/core/extensions/factory-runtime.js +5 -0
  131. package/dist/core/extensions/factory-runtime.js.map +1 -1
  132. package/dist/core/extensions/runner.d.ts.map +1 -1
  133. package/dist/core/extensions/runner.js +1 -0
  134. package/dist/core/extensions/runner.js.map +1 -1
  135. package/dist/core/extensions/types.d.ts +8 -4
  136. package/dist/core/extensions/types.d.ts.map +1 -1
  137. package/dist/core/extensions/types.js.map +1 -1
  138. package/dist/core/foreground-terminal-handoff-controller.d.ts +31 -0
  139. package/dist/core/foreground-terminal-handoff-controller.d.ts.map +1 -1
  140. package/dist/core/foreground-terminal-handoff-controller.js +239 -55
  141. package/dist/core/foreground-terminal-handoff-controller.js.map +1 -1
  142. package/dist/core/goals/goal-session-controller.d.ts +2 -0
  143. package/dist/core/goals/goal-session-controller.d.ts.map +1 -1
  144. package/dist/core/goals/goal-session-controller.js +8 -0
  145. package/dist/core/goals/goal-session-controller.js.map +1 -1
  146. package/dist/core/goals/goal-tool-core.d.ts +5 -4
  147. package/dist/core/goals/goal-tool-core.d.ts.map +1 -1
  148. package/dist/core/goals/goal-tool-core.js +23 -19
  149. package/dist/core/goals/goal-tool-core.js.map +1 -1
  150. package/dist/core/learning/auto-learn-settings.d.ts +84 -0
  151. package/dist/core/learning/auto-learn-settings.d.ts.map +1 -0
  152. package/dist/core/learning/auto-learn-settings.js +66 -0
  153. package/dist/core/learning/auto-learn-settings.js.map +1 -0
  154. package/dist/core/learning/learning-audit.d.ts +5 -1
  155. package/dist/core/learning/learning-audit.d.ts.map +1 -1
  156. package/dist/core/learning/learning-audit.js +32 -1
  157. package/dist/core/learning/learning-audit.js.map +1 -1
  158. package/dist/core/learning/reflection-engine.d.ts +24 -0
  159. package/dist/core/learning/reflection-engine.d.ts.map +1 -1
  160. package/dist/core/learning/reflection-engine.js +104 -8
  161. package/dist/core/learning/reflection-engine.js.map +1 -1
  162. package/dist/core/local-prefix-warm-controller.d.ts +24 -0
  163. package/dist/core/local-prefix-warm-controller.d.ts.map +1 -0
  164. package/dist/core/local-prefix-warm-controller.js +79 -0
  165. package/dist/core/local-prefix-warm-controller.js.map +1 -0
  166. package/dist/core/memory/okf-project-memory-store.d.ts +28 -0
  167. package/dist/core/memory/okf-project-memory-store.d.ts.map +1 -0
  168. package/dist/core/memory/okf-project-memory-store.js +111 -0
  169. package/dist/core/memory/okf-project-memory-store.js.map +1 -0
  170. package/dist/core/memory/providers/file-store.d.ts +44 -1
  171. package/dist/core/memory/providers/file-store.d.ts.map +1 -1
  172. package/dist/core/memory/providers/file-store.js +341 -24
  173. package/dist/core/memory/providers/file-store.js.map +1 -1
  174. package/dist/core/memory/worker-memory-tools.d.ts +5 -0
  175. package/dist/core/memory/worker-memory-tools.d.ts.map +1 -0
  176. package/dist/core/memory/worker-memory-tools.js +5 -0
  177. package/dist/core/memory/worker-memory-tools.js.map +1 -0
  178. package/dist/core/memory-controller.d.ts +10 -1
  179. package/dist/core/memory-controller.d.ts.map +1 -1
  180. package/dist/core/memory-controller.js +72 -8
  181. package/dist/core/memory-controller.js.map +1 -1
  182. package/dist/core/model-capability.d.ts +7 -0
  183. package/dist/core/model-capability.d.ts.map +1 -1
  184. package/dist/core/model-capability.js +12 -0
  185. package/dist/core/model-capability.js.map +1 -1
  186. package/dist/core/model-registry.d.ts.map +1 -1
  187. package/dist/core/model-registry.js +2 -10
  188. package/dist/core/model-registry.js.map +1 -1
  189. package/dist/core/model-router-controller.d.ts +3 -2
  190. package/dist/core/model-router-controller.d.ts.map +1 -1
  191. package/dist/core/model-router-controller.js +3 -2
  192. package/dist/core/model-router-controller.js.map +1 -1
  193. package/dist/core/orchestration/contracts.d.ts +16 -12
  194. package/dist/core/orchestration/contracts.d.ts.map +1 -1
  195. package/dist/core/orchestration/contracts.js +2 -2
  196. package/dist/core/orchestration/contracts.js.map +1 -1
  197. package/dist/core/orchestration/lane-tool-manifests.d.ts +2 -2
  198. package/dist/core/orchestration/lane-tool-manifests.d.ts.map +1 -1
  199. package/dist/core/orchestration/lane-tool-manifests.js +6 -1
  200. package/dist/core/orchestration/lane-tool-manifests.js.map +1 -1
  201. package/dist/core/orchestration/profile-registry.d.ts.map +1 -1
  202. package/dist/core/orchestration/profile-registry.js +14 -0
  203. package/dist/core/orchestration/profile-registry.js.map +1 -1
  204. package/dist/core/orchestration/task-profile-writer.d.ts +7 -4
  205. package/dist/core/orchestration/task-profile-writer.d.ts.map +1 -1
  206. package/dist/core/orchestration/task-profile-writer.js +71 -59
  207. package/dist/core/orchestration/task-profile-writer.js.map +1 -1
  208. package/dist/core/orchestration/thinking-level-schema.d.ts +7 -0
  209. package/dist/core/orchestration/thinking-level-schema.d.ts.map +1 -0
  210. package/dist/core/orchestration/thinking-level-schema.js +19 -0
  211. package/dist/core/orchestration/thinking-level-schema.js.map +1 -0
  212. package/dist/core/orchestration/worker-execution-contract.d.ts.map +1 -1
  213. package/dist/core/orchestration/worker-execution-contract.js +12 -5
  214. package/dist/core/orchestration/worker-execution-contract.js.map +1 -1
  215. package/dist/core/provider-prompt-contracts.d.ts +1 -4
  216. package/dist/core/provider-prompt-contracts.d.ts.map +1 -1
  217. package/dist/core/provider-prompt-contracts.js +13 -29
  218. package/dist/core/provider-prompt-contracts.js.map +1 -1
  219. package/dist/core/provider-request-context-controller.d.ts +2 -0
  220. package/dist/core/provider-request-context-controller.d.ts.map +1 -1
  221. package/dist/core/provider-request-context-controller.js +10 -3
  222. package/dist/core/provider-request-context-controller.js.map +1 -1
  223. package/dist/core/reflection-controller.d.ts +63 -27
  224. package/dist/core/reflection-controller.d.ts.map +1 -1
  225. package/dist/core/reflection-controller.js +300 -132
  226. package/dist/core/reflection-controller.js.map +1 -1
  227. package/dist/core/runtime-builder.d.ts +1 -0
  228. package/dist/core/runtime-builder.d.ts.map +1 -1
  229. package/dist/core/runtime-builder.js +25 -14
  230. package/dist/core/runtime-builder.js.map +1 -1
  231. package/dist/core/secrets/credential-exposure-guard.d.ts.map +1 -1
  232. package/dist/core/secrets/credential-exposure-guard.js +55 -10
  233. package/dist/core/secrets/credential-exposure-guard.js.map +1 -1
  234. package/dist/core/session-role.d.ts +6 -8
  235. package/dist/core/session-role.d.ts.map +1 -1
  236. package/dist/core/session-role.js +8 -18
  237. package/dist/core/session-role.js.map +1 -1
  238. package/dist/core/settings-manager.d.ts +13 -16
  239. package/dist/core/settings-manager.d.ts.map +1 -1
  240. package/dist/core/settings-manager.js +53 -50
  241. package/dist/core/settings-manager.js.map +1 -1
  242. package/dist/core/skill-vault.d.ts +15 -0
  243. package/dist/core/skill-vault.d.ts.map +1 -1
  244. package/dist/core/skill-vault.js +79 -45
  245. package/dist/core/skill-vault.js.map +1 -1
  246. package/dist/core/skills.d.ts +3 -0
  247. package/dist/core/skills.d.ts.map +1 -1
  248. package/dist/core/skills.js +6 -6
  249. package/dist/core/skills.js.map +1 -1
  250. package/dist/core/system-prompt-builder.d.ts +2 -0
  251. package/dist/core/system-prompt-builder.d.ts.map +1 -1
  252. package/dist/core/system-prompt-builder.js +52 -12
  253. package/dist/core/system-prompt-builder.js.map +1 -1
  254. package/dist/core/system-prompt.js +1 -1
  255. package/dist/core/system-prompt.js.map +1 -1
  256. package/dist/core/tool-capability-policy.d.ts +3 -0
  257. package/dist/core/tool-capability-policy.d.ts.map +1 -1
  258. package/dist/core/tool-capability-policy.js +13 -2
  259. package/dist/core/tool-capability-policy.js.map +1 -1
  260. package/dist/core/toolkit/script-runner.d.ts +2 -1
  261. package/dist/core/toolkit/script-runner.d.ts.map +1 -1
  262. package/dist/core/toolkit/script-runner.js +10 -2
  263. package/dist/core/toolkit/script-runner.js.map +1 -1
  264. package/dist/core/tools/delegate-status.d.ts +3 -1
  265. package/dist/core/tools/delegate-status.d.ts.map +1 -1
  266. package/dist/core/tools/delegate-status.js +5 -1
  267. package/dist/core/tools/delegate-status.js.map +1 -1
  268. package/dist/core/tools/delegate.d.ts +4 -13
  269. package/dist/core/tools/delegate.d.ts.map +1 -1
  270. package/dist/core/tools/delegate.js +173 -73
  271. package/dist/core/tools/delegate.js.map +1 -1
  272. package/dist/core/tools/goal.d.ts +3 -4
  273. package/dist/core/tools/goal.d.ts.map +1 -1
  274. package/dist/core/tools/goal.js +24 -7
  275. package/dist/core/tools/goal.js.map +1 -1
  276. package/dist/core/tools/profile-writer.d.ts +2 -8
  277. package/dist/core/tools/profile-writer.d.ts.map +1 -1
  278. package/dist/core/tools/profile-writer.js +6 -18
  279. package/dist/core/tools/profile-writer.js.map +1 -1
  280. package/dist/core/tools/run-toolkit-script.d.ts +1 -1
  281. package/dist/core/tools/run-toolkit-script.d.ts.map +1 -1
  282. package/dist/core/tools/run-toolkit-script.js +2 -2
  283. package/dist/core/tools/run-toolkit-script.js.map +1 -1
  284. package/dist/core/tools/skill-audit.d.ts +28 -1
  285. package/dist/core/tools/skill-audit.d.ts.map +1 -1
  286. package/dist/core/tools/skill-audit.js +56 -12
  287. package/dist/core/tools/skill-audit.js.map +1 -1
  288. package/dist/core/tools/skill.d.ts +12 -1
  289. package/dist/core/tools/skill.d.ts.map +1 -1
  290. package/dist/core/tools/skill.js +52 -0
  291. package/dist/core/tools/skill.js.map +1 -1
  292. package/dist/core/tools/skillify.d.ts.map +1 -1
  293. package/dist/core/tools/skillify.js +16 -7
  294. package/dist/core/tools/skillify.js.map +1 -1
  295. package/dist/core/tools/task-steps.js +1 -1
  296. package/dist/core/tools/task-steps.js.map +1 -1
  297. package/dist/core/tools/tmux-dispatch.d.ts +4 -16
  298. package/dist/core/tools/tmux-dispatch.d.ts.map +1 -1
  299. package/dist/core/tools/tmux-dispatch.js +5 -20
  300. package/dist/core/tools/tmux-dispatch.js.map +1 -1
  301. package/dist/core/tools/tool-task.d.ts +1 -0
  302. package/dist/core/tools/tool-task.d.ts.map +1 -1
  303. package/dist/core/tools/tool-task.js +13 -8
  304. package/dist/core/tools/tool-task.js.map +1 -1
  305. package/dist/core/util/atomic-file.d.ts +2 -0
  306. package/dist/core/util/atomic-file.d.ts.map +1 -1
  307. package/dist/core/util/atomic-file.js +4 -0
  308. package/dist/core/util/atomic-file.js.map +1 -1
  309. package/dist/core/worktree-sync/codes.d.ts +1 -2
  310. package/dist/core/worktree-sync/codes.d.ts.map +1 -1
  311. package/dist/core/worktree-sync/codes.js +1 -2
  312. package/dist/core/worktree-sync/codes.js.map +1 -1
  313. package/dist/core/worktree-sync/lane-gate.d.ts +4 -8
  314. package/dist/core/worktree-sync/lane-gate.d.ts.map +1 -1
  315. package/dist/core/worktree-sync/lane-gate.js +5 -51
  316. package/dist/core/worktree-sync/lane-gate.js.map +1 -1
  317. package/dist/index.d.ts +5 -0
  318. package/dist/index.d.ts.map +1 -1
  319. package/dist/index.js +7 -0
  320. package/dist/index.js.map +1 -1
  321. package/dist/modes/interactive/auto-learn-controller.d.ts +6 -7
  322. package/dist/modes/interactive/auto-learn-controller.d.ts.map +1 -1
  323. package/dist/modes/interactive/auto-learn-controller.js +32 -207
  324. package/dist/modes/interactive/auto-learn-controller.js.map +1 -1
  325. package/dist/modes/interactive/autonomy-commands.js +2 -2
  326. package/dist/modes/interactive/autonomy-commands.js.map +1 -1
  327. package/dist/modes/interactive/components/activity-lane.d.ts.map +1 -1
  328. package/dist/modes/interactive/components/activity-lane.js +12 -23
  329. package/dist/modes/interactive/components/activity-lane.js.map +1 -1
  330. package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
  331. package/dist/modes/interactive/components/custom-message.js +142 -1
  332. package/dist/modes/interactive/components/custom-message.js.map +1 -1
  333. package/dist/modes/interactive/components/fitness-role-selector.d.ts.map +1 -1
  334. package/dist/modes/interactive/components/fitness-role-selector.js +2 -2
  335. package/dist/modes/interactive/components/fitness-role-selector.js.map +1 -1
  336. package/dist/modes/interactive/components/footer.js +1 -1
  337. package/dist/modes/interactive/components/footer.js.map +1 -1
  338. package/dist/modes/interactive/components/settings-selector.d.ts +4 -0
  339. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  340. package/dist/modes/interactive/components/settings-selector.js +150 -73
  341. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  342. package/dist/modes/interactive/report-commands.d.ts.map +1 -1
  343. package/dist/modes/interactive/report-commands.js +8 -4
  344. package/dist/modes/interactive/report-commands.js.map +1 -1
  345. package/dist/modes/interactive/settings-selector-flow.d.ts.map +1 -1
  346. package/dist/modes/interactive/settings-selector-flow.js +10 -0
  347. package/dist/modes/interactive/settings-selector-flow.js.map +1 -1
  348. package/docs/extensions.md +8 -0
  349. package/docs/harness-architecture.md +28 -32
  350. package/docs/index.md +1 -1
  351. package/docs/process-matrix.md +8 -6
  352. package/docs/resources.md +1 -1
  353. package/docs/settings.md +61 -51
  354. package/docs/tmux-agent-manager.md +47 -40
  355. package/docs/usage.md +1 -1
  356. package/docs/worker-profiles.md +8 -6
  357. package/docs/worktree-sync.md +26 -20
  358. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  359. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  360. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  361. package/examples/extensions/sandbox/package-lock.json +2 -2
  362. package/examples/extensions/sandbox/package.json +1 -1
  363. package/examples/extensions/with-deps/package-lock.json +2 -2
  364. package/examples/extensions/with-deps/package.json +1 -1
  365. package/npm-shrinkwrap.json +12 -12
  366. package/package.json +4 -4
  367. package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.d.ts +0 -133
  368. package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.d.ts.map +0 -1
  369. package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.js +0 -199
  370. package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.js.map +0 -1
  371. package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.ts +0 -312
@@ -7,34 +7,24 @@ import { fileURLToPath } from "node:url";
7
7
  import {
8
8
  type ExtensionAPI,
9
9
  type ExtensionContext,
10
+ encodeWorkerSessionAllowedPaths,
10
11
  getProcessWorkRun,
12
+ orchestrationThinkingLevelSchema,
11
13
  PI_ORCHESTRATION_AGENT_ID_ENV,
14
+ PI_WORKER_ALLOWED_PATHS_ENV,
12
15
  } from "@caupulican/pi-adaptative";
13
- import type { AgentToolResult } from "@caupulican/pi-agent-core";
16
+ import type { AgentToolResult, ThinkingLevel } from "@caupulican/pi-agent-core";
14
17
  import type { Usage } from "@caupulican/pi-ai";
15
18
  import { Type } from "typebox";
16
19
  import {
17
- buildGrant,
18
- buildGrantUsageEntry,
19
20
  buildLaunchProfileFlags,
20
- buildTombstone,
21
- countGrantUsages,
22
- DEFAULT_READ_BIASED_TOOLS,
21
+ DEFAULT_MANAGED_WORKER_TOOLS,
23
22
  decodeTmuxWorkerUsageClaim,
24
- describeGrant,
25
- GRANT_CUSTOM_TYPE,
26
- GRANT_USAGE_CUSTOM_TYPE,
27
- type GrantDispatchParams,
28
- grantCovers,
29
- isTmuxDispatchGrant,
30
- isTmuxDispatchGrantTombstone,
23
+ deriveWorkerLaunchProfile,
31
24
  type LaunchProfileFlag,
32
- type LaunchProfileSource,
33
- launchProfileSourceFromGrant,
34
- ONE_SHOT_LAUNCH_PROFILE_SOURCE,
35
25
  type Provider,
36
- type TmuxDispatchGrant,
37
- } from "./dispatch-grant.ts";
26
+ type WorkerLaunchProfile,
27
+ } from "./launch-profile.ts";
38
28
 
39
29
  export const piConfig = { tools: ["tmux_agent_manager"] };
40
30
 
@@ -57,22 +47,23 @@ type Action =
57
47
  | "list_templates"
58
48
  | "show_template"
59
49
  | "stop_job"
60
- | "stop_session"
61
- | "grant_dispatch"
62
- | "revoke_grant";
50
+ | "stop_session";
63
51
 
64
52
  type AgentSpec = {
65
53
  provider?: Provider;
66
54
  name?: string;
67
55
  command?: string;
56
+ /** Optional workspace. Sets cwd; narrows Pi structured file tools but not external host processes. */
57
+ path?: string;
58
+ /** Legacy process cwd override. Does not narrow the managed profile. */
68
59
  cwd?: string;
69
60
  tools?: string[];
61
+ resourceProfile?: string;
62
+ thinkingLevel?: ThinkingLevel;
70
63
  /**
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`.
64
+ * Pi-only worktree-sync lane binding. Goal dispatch supplies this automatically; an explicit
65
+ * fire_task profile may also narrow a Pi worker to a named lane. The launch adds
66
+ * `--worktree-lane <key>` and the lane doctrine through the immutable profile.
76
67
  */
77
68
  worktreeLane?: string;
78
69
  };
@@ -105,26 +96,12 @@ type Params = {
105
96
  dryRun?: boolean;
106
97
  force?: boolean;
107
98
  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. */
99
+ /** Non-retained launch identity for a fresh fire_task. Unlike jobId, extension identity memory
100
+ * never auto-fills this field from an earlier result. */
101
+ launchKey?: string;
102
+ /** Goal this dispatch is scoped to. Goal-bound dispatch wires this through to managed-lane
103
+ * ownership and the child process's task-recovery fence. */
110
104
  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;
128
105
  };
129
106
 
130
107
  type RunResult = { ok: boolean; status: number | null; stdout: string; stderr: string; error?: string; args: string[] };
@@ -158,6 +135,8 @@ type FireAgentPlan = {
158
135
  resultPath: string;
159
136
  paneId?: string;
160
137
  result?: AgentResult;
138
+ resourceProfile?: string;
139
+ thinkingLevel?: ThinkingLevel;
161
140
  /** Last turn dispatched to this agent's pane via fire_task (1) or send_followup (>=2). Absent means 1. */
162
141
  currentTurn?: number;
163
142
  /** Last turn whose terminal handoff was delivered to the parent. Absent means 0 (never notified). */
@@ -169,11 +148,11 @@ type FireAgentPlan = {
169
148
  * flags) and the dispatch-phase `reportManagedLane` call can both read it after `buildFireTaskPlan`. */
170
149
  worktreeLane?: string;
171
150
  /** Durable host-lifecycle metadata for the current turn. */
151
+ dispatchProfileId?: string;
172
152
  dispatchAuthorizationId?: string;
173
- dispatchAuthorizationKind?: "standing-grant" | "one-shot-owner-approval" | "legacy-recovery";
153
+ dispatchAuthorizationKind?: "profile-derived" | "legacy-recovery";
174
154
  dispatchAllowedTools?: string[];
175
155
  dispatchWritePaths?: string[];
176
- dispatchMaxCostUsd?: number;
177
156
  dispatchInstructions?: string;
178
157
  dispatchGoalId?: string;
179
158
  dispatchLeaseTtlMs?: number;
@@ -200,7 +179,7 @@ type FireTaskPlan = {
200
179
  currentTurn?: number;
201
180
  /** 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
181
  orphanedAt?: string;
203
- /** Set by action=dismiss: the manager stops tracking/re-arming this job. The tmux session is left running. */
182
+ /** Set by action=dismiss after its pane watchers are detached. The tmux session is left running. */
204
183
  dismissedAt?: string;
205
184
  };
206
185
  /** The subset of FireTaskPlan the pane watcher generator actually reads — narrow on purpose so a
@@ -213,10 +192,9 @@ type ManagedLaneDispatchBridge = {
213
192
  profileId: string;
214
193
  provider: string;
215
194
  authorizationId: string;
216
- authorizationKind: "standing-grant" | "one-shot-owner-approval" | "legacy-recovery";
195
+ authorizationKind: "profile-derived" | "legacy-recovery";
217
196
  allowedTools: readonly string[];
218
197
  writePaths: readonly string[];
219
- maxCostUsd?: number;
220
198
  leaseTtlMs: number;
221
199
  };
222
200
  type ManagedLaneBridgeEventBase = {
@@ -238,19 +216,14 @@ type ManagedLaneBridgeEvent =
238
216
  * exercises. Production hosts provide `reportManagedLane`, making its pre-launch durable reservation a
239
217
  * hard gate. The optional shape only preserves standalone extension-test hosts; without the bridge,
240
218
  * 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). */
219
+ * `getActiveTools` supplies the parent's current tool surface so a new worker profile can inherit its
220
+ * policy-owned worker-compatible subset without asking the model to reconstruct it. */
245
221
  type HostBridge = {
246
222
  reportManagedLane?: (event: ManagedLaneBridgeEvent) => void;
247
223
  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;
224
+ getActiveTools?: () => string[];
225
+ getThinkingLevel?: () => ThinkingLevel;
226
+ getEffectiveResourceProfile?: ExtensionAPI["getEffectiveResourceProfile"];
254
227
  };
255
228
  function agentLaneId(jobId: string, agentId: string): string {
256
229
  return `tmux:${jobId}:${agentId}`;
@@ -263,50 +236,33 @@ function managedDispatch(job: FireTaskPlan, agent: FireAgentPlan): ManagedLaneDi
263
236
  return {
264
237
  sequence,
265
238
  instructions,
266
- profileId: `tmux:${agent.provider}`,
239
+ profileId: agent.dispatchProfileId ?? `tmux:${agent.provider}`,
267
240
  provider: agent.provider,
268
241
  authorizationId,
269
242
  authorizationKind: agent.dispatchAuthorizationKind ?? "legacy-recovery",
270
- allowedTools: agent.dispatchAllowedTools ?? DEFAULT_READ_BIASED_TOOLS,
243
+ allowedTools: agent.dispatchAllowedTools ?? DEFAULT_MANAGED_WORKER_TOOLS,
271
244
  writePaths: agent.dispatchWritePaths ?? [],
272
- ...(agent.dispatchMaxCostUsd !== undefined ? { maxCostUsd: agent.dispatchMaxCostUsd } : {}),
273
245
  leaseTtlMs: agent.dispatchLeaseTtlMs ?? job.deadlineSeconds * 1_000,
274
246
  };
275
247
  }
276
248
 
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
- };
249
+ function applyManagedProfile(
250
+ agent: FireAgentPlan,
251
+ profile: WorkerLaunchProfile,
252
+ args: { instructions: string; goalId?: string; leaseTtlMs: number },
253
+ ): void {
254
+ agent.dispatchProfileId = profile.identity;
255
+ agent.dispatchAuthorizationId = profile.identity;
256
+ agent.dispatchAuthorizationKind = "profile-derived";
257
+ agent.dispatchAllowedTools = [...profile.allowedTools];
258
+ agent.dispatchWritePaths = [...profile.writePaths];
259
+ applyManagedTurn(agent, args);
297
260
  }
298
261
 
299
- function applyManagedAuthorization(
262
+ function applyManagedTurn(
300
263
  agent: FireAgentPlan,
301
- authorization: ReturnType<typeof managedAuthorization>,
302
264
  args: { instructions: string; goalId?: string; leaseTtlMs: number },
303
265
  ): 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
266
  agent.dispatchInstructions = args.instructions;
311
267
  agent.dispatchGoalId = args.goalId;
312
268
  agent.dispatchLeaseTtlMs = args.leaseTtlMs;
@@ -369,13 +325,6 @@ function reportManagedAgentFailure(bridge: HostBridge, jobId: string, agentId: s
369
325
  }
370
326
  }
371
327
 
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
- }
378
-
379
328
  const EXTENSION_ROOT = path.dirname(fileURLToPath(import.meta.url));
380
329
  export function getTmuxAgentManagerDataRoot(): string {
381
330
  const agentDir =
@@ -433,7 +382,7 @@ const TEAM_TEMPLATES: TeamTemplate[] = [
433
382
  ],
434
383
  notes: [
435
384
  "Use only when model-token/auth validation is intended.",
436
- "The manager opens each CLI in tmux, injects the prompt, captures pane output, and watches DONE/BLOCKED markers.",
385
+ "The manager arms pane capture, starts each CLI with its task, and watches DONE/BLOCKED markers.",
437
386
  ],
438
387
  },
439
388
  {
@@ -464,7 +413,7 @@ const TEAM_TEMPLATES: TeamTemplate[] = [
464
413
  { provider: "pi", name: "coordinator" },
465
414
  ],
466
415
  notes: [
467
- "Use for owner-approved implementation/QA batches; never claim complete until result files and validation evidence are inspected.",
416
+ "Use for implementation/QA batches; never claim complete until result files and validation evidence are inspected.",
468
417
  ],
469
418
  },
470
419
  ];
@@ -592,7 +541,7 @@ function formatDetection(d: TmuxDetection): string {
592
541
  }
593
542
  function setupHelp(d: TmuxDetection): string {
594
543
  if (d.cliAvailable)
595
- return `tmux CLI available: ${d.tmuxBin || "tmux"}\nUse native delegate for ordinary subagents. Use tmux_agent_manager only for an owner-requested persistent interactive pane; set dryRun=true only when a preview is useful.`;
544
+ return `tmux CLI available: ${d.tmuxBin || "tmux"}\nUse native delegate for ordinary subagents. Use tmux_agent_manager when a persistent interactive provider pane benefits the task; set dryRun=true only when a preview is useful.`;
596
545
  return [
597
546
  "tmux CLI not found.",
598
547
  "Ordinary subagents remain available through native delegate; do not abort delegation.",
@@ -614,10 +563,10 @@ async function guardTmux(_ctx: ExtensionContext, detection: TmuxDetection, purpo
614
563
  }
615
564
  function tmuxManagerInstructions(toolName: string, detection: TmuxDetection): string {
616
565
  return [
617
- `tmux is available (${detection.version || detection.tmuxBin || "tmux"}). Native delegate remains the standard subagent route. Use tmux_agent_manager for managed ${toolName} panes only when the owner explicitly requested persistent interactive provider processes.`,
566
+ `tmux is available (${detection.version || detection.tmuxBin || "tmux"}). Native delegate remains the standard subagent route. Use tmux_agent_manager when managed ${toolName} panes need persistent interactive provider processes.`,
618
567
  "Suggested next call:",
619
568
  'tmux_agent_manager({ action: "fire_task", teamTemplate: "builder-validator", task: "<objective>" })',
620
- "The launch returns immediately after panes, event watchers, and prompt handoffs are armed.",
569
+ "The launch returns after pane capture is armed and each provider command receives its initial task.",
621
570
  "Managed mode creates panes, prompt files, result files, event-driven terminal handoffs, tmux display-message notifications, @pi_* status options, shared variables, and one-shot deadlines.",
622
571
  ].join("\n");
623
572
  }
@@ -710,11 +659,26 @@ function normalizeAgents(params: Params): AgentSpec[] {
710
659
  if (!Object.hasOwn(PROVIDER_COMMANDS, provider)) throw new Error(`unsupported provider: ${provider}`);
711
660
  const name =
712
661
  firstString(agent.name, provider === "custom" ? `custom-${index + 1}` : provider) || `agent-${index + 1}`;
713
- const baseCommand = firstString(agent.command, PROVIDER_COMMANDS[provider]);
714
- if (!baseCommand) throw new Error(`agent ${name} needs command when provider=custom`);
662
+ const command = firstString(agent.command, PROVIDER_COMMANDS[provider]);
663
+ if (!command) throw new Error(`agent ${name} needs command when provider=custom`);
715
664
  const tools = Array.isArray(agent.tools) && agent.tools.length > 0 ? agent.tools : undefined;
716
- const command = tools ? `${baseCommand} --tools ${tools.join(",")}` : baseCommand;
717
- return { provider, name, command, cwd: agent.cwd, tools, worktreeLane: agent.worktreeLane };
665
+ const resourceProfile = firstString(agent.resourceProfile);
666
+ if (provider !== "pi" && (tools || resourceProfile || agent.thinkingLevel || agent.worktreeLane)) {
667
+ throw new Error(
668
+ `agent ${name}: tools, resourceProfile, thinkingLevel, and worktreeLane overrides are Pi-only; configure ${provider}'s native CLI in command instead`,
669
+ );
670
+ }
671
+ return {
672
+ provider,
673
+ name,
674
+ command,
675
+ path: firstString(agent.path),
676
+ cwd: agent.cwd,
677
+ tools,
678
+ resourceProfile,
679
+ thinkingLevel: agent.thinkingLevel,
680
+ worktreeLane: agent.worktreeLane,
681
+ };
718
682
  });
719
683
  }
720
684
  function defaultProviderInvocation(provider: Provider): string {
@@ -736,6 +700,13 @@ function defaultProviderInvocation(provider: Provider): string {
736
700
  function interactivePromptText(prompt: string): string {
737
701
  return prompt.replace(/\r?\n/g, "\\n");
738
702
  }
703
+ function initialPromptInvocation(command: string, provider: Provider, promptPath: string): string {
704
+ const promptArgument = `"$(cat ${quoteShell(promptPath)})"`;
705
+ if (provider === "agy" && !/(?:^|\s)(?:-i|--prompt-interactive)(?:\s|$)/.test(command)) {
706
+ return `${command} --prompt-interactive ${promptArgument}`;
707
+ }
708
+ return `${command} ${promptArgument}`;
709
+ }
739
710
  function spellMarker(marker: string): string {
740
711
  return marker.split("").join(" ");
741
712
  }
@@ -758,7 +729,7 @@ function managedPrompt(
758
729
  "Rules:",
759
730
  "- Work autonomously until honest PASS or BLOCKED.",
760
731
  "- Verify important claims before PASS; include concise evidence.",
761
- "- If user input, credentials, destructive approval, publishing, or unavailable dependency blocks you, report BLOCKED instead of waiting silently.",
732
+ "- Use the assigned profile without asking for routine permission. Report BLOCKED only when missing external information or an unavailable dependency makes the objective impossible.",
762
733
  "- Never print secrets/tokens. Prompts, commands, and logs persist under ~/.pi/agent/work/background/tmux-agent-manager/state/jobs.",
763
734
  "- If a task names an external decision variable, read it only at the decision point; never loop or poll the shared JSON file.",
764
735
  "",
@@ -773,7 +744,7 @@ function buildWorkspacePlan(ctx: ExtensionContext, params: Params) {
773
744
  const cwd = resolveCwd(ctx, params.cwd);
774
745
  const agents = normalizeAgents(params).map((agent, index) => ({
775
746
  ...agent,
776
- cwd: resolveCwd(ctx, agent.cwd || cwd),
747
+ cwd: resolveCwd(ctx, agent.path || agent.cwd || cwd),
777
748
  id: `${safeName(agent.name || agent.provider || "agent")}-${index + 1}`,
778
749
  }));
779
750
  const workspaceName = firstString(params.workspaceName, `pi-agents-${Date.now().toString(36)}`) || "pi-agents";
@@ -853,7 +824,12 @@ export function makePaneWatcherScript(job: PaneWatcherJobSpec): string {
853
824
  " sleep_pid=$!",
854
825
  ' wait "$sleep_pid" 2>/dev/null || exit 0',
855
826
  " sleep_pid=",
856
- ' if [ -e "$stop_request_path" ]; then finish stopped stop-requested-event; else finish timeout deadline-event; fi',
827
+ ' if [ -e "$stop_request_path" ]; then',
828
+ " finish stopped stop-requested-event",
829
+ " else",
830
+ " finish timeout deadline-event",
831
+ ' [ -z "$pane_id" ] || tmux kill-pane -t "$pane_id" >/dev/null 2>&1 || true',
832
+ " fi",
857
833
  ' kill -TERM "$parent_pid" 2>/dev/null || true',
858
834
  ") &",
859
835
  "timer_pid=$!",
@@ -897,15 +873,16 @@ function buildFireTaskPlan(ctx: ExtensionContext, params: Params): FireTaskPlan
897
873
  const task = firstString(params.task, params.body);
898
874
  if (!task) throw new Error("fire_task requires task (or body) with the worker objective");
899
875
  const cwd = resolveCwd(ctx, params.cwd);
900
- const id = firstString(params.jobId) || makeJobId();
876
+ const id = firstString(params.launchKey) || makeJobId();
901
877
  if (!/^[a-zA-Z0-9_.:-]{4,80}$/.test(id))
902
- throw new Error("jobId must be 4-80 chars: letters, numbers, _, ., :, or -");
878
+ throw new Error("launchKey must be 4-80 chars: letters, numbers, _, ., :, or -");
903
879
  const jobDir = path.join(jobsRoot(), id);
904
880
  const workspaceName = firstString(params.workspaceName, `Pi job ${id}`) || `Pi job ${id}`;
905
881
  const sessionName = safeName(workspaceName, id);
906
882
  const rawAgents = normalizeAgents(params);
907
883
  const agents: FireAgentPlan[] = rawAgents.map((agent, index) => {
908
884
  const provider = agent.provider || "custom";
885
+ const agentCwd = resolveCwd(ctx, agent.path || agent.cwd || cwd);
909
886
  const slug = `${safeName(agent.name || provider)}-${index + 1}`;
910
887
  const markerBase = `TMUX_${crypto.createHash("sha1").update(`${id}:${slug}`).digest("hex").slice(0, 10).toUpperCase()}`;
911
888
  const doneMarker = `${markerBase}_DONE`;
@@ -915,14 +892,17 @@ function buildFireTaskPlan(ctx: ExtensionContext, params: Params): FireTaskPlan
915
892
  name: firstString(agent.name, provider) || slug,
916
893
  provider,
917
894
  command: firstString(agent.command, PROVIDER_COMMANDS[provider]),
918
- cwd: resolveCwd(ctx, agent.cwd || cwd),
919
- // tools already baked into command by normalizeAgents
895
+ cwd: agentCwd,
896
+ dispatchAllowedTools: agent.tools ? [...agent.tools] : undefined,
897
+ dispatchWritePaths: agent.path ? [agentCwd] : undefined,
920
898
  doneMarker,
921
899
  blockedMarker,
922
900
  promptPath: path.join(jobDir, `${slug}.prompt.md`),
923
901
  logPath: path.join(jobDir, `${slug}.log`),
924
902
  resultPath: path.join(jobDir, `${slug}.result.json`),
925
903
  worktreeLane: agent.worktreeLane,
904
+ resourceProfile: agent.resourceProfile,
905
+ thinkingLevel: agent.thinkingLevel,
926
906
  };
927
907
  });
928
908
  const job: FireTaskPlan = {
@@ -949,7 +929,7 @@ function buildFireTaskPlan(ctx: ExtensionContext, params: Params): FireTaskPlan
949
929
  };
950
930
  job.launchCommands = agents.map(
951
931
  (agent) =>
952
- `${agent.command || defaultProviderInvocation(agent.provider)} then inject ${path.basename(agent.promptPath)}`,
932
+ `${agent.command || defaultProviderInvocation(agent.provider)} with initial task from ${path.basename(agent.promptPath)}`,
953
933
  );
954
934
  return job;
955
935
  }
@@ -964,7 +944,7 @@ function prepareJobDirForLaunch(job: FireTaskPlan, force?: boolean): string | un
964
944
  if (!fs.existsSync(job.jobDir)) return undefined;
965
945
  if (!force)
966
946
  throw new Error(
967
- `tmux job already exists: ${job.id}. Use a new jobId, or pass force:true to archive the old job before launching.`,
947
+ `tmux job already exists: ${job.id}. Use a new launchKey, or pass force:true to archive the old job before launching.`,
968
948
  );
969
949
  return archiveExistingJobDir(job.jobDir);
970
950
  }
@@ -1092,6 +1072,7 @@ function launchTmuxSession(
1092
1072
  sessionName: string,
1093
1073
  cwd: string,
1094
1074
  panes: Array<{ title: string; cwd: string; command: string }>,
1075
+ options: { deferCommands?: boolean } = {},
1095
1076
  ): { runs: RunResult[]; paneIds: string[] } {
1096
1077
  if (tmuxSessionExists(sessionName))
1097
1078
  throw new Error(
@@ -1110,7 +1091,7 @@ function launchTmuxSession(
1110
1091
  try {
1111
1092
  const firstPane = create.stdout.trim();
1112
1093
  paneIds.push(firstPane);
1113
- sendCommandToPane(firstPane, first.command);
1094
+ if (!options.deferCommands) sendCommandToPane(firstPane, first.command);
1114
1095
  for (const pane of panes.slice(1)) {
1115
1096
  const split = runTmux(
1116
1097
  ["split-window", "-d", "-P", "-F", "#{pane_id}", "-t", `${sessionName}:0`, "-c", pane.cwd || cwd],
@@ -1121,7 +1102,7 @@ function launchTmuxSession(
1121
1102
  throw new Error(`tmux split-window failed: ${split.error || split.stderr || `exit ${split.status}`}`);
1122
1103
  const paneId = split.stdout.trim();
1123
1104
  paneIds.push(paneId);
1124
- sendCommandToPane(paneId, pane.command);
1105
+ if (!options.deferCommands) sendCommandToPane(paneId, pane.command);
1125
1106
  }
1126
1107
  runs.push(runTmux(["select-layout", "-t", `${sessionName}:0`, "tiled"], 3_000));
1127
1108
  return { runs, paneIds };
@@ -1411,110 +1392,66 @@ function setVariable(jobId: string, name: string, value: string) {
1411
1392
  fs.writeFileSync(job.varsPath, `${JSON.stringify(current, null, 2)}\n`, { mode: 0o600 });
1412
1393
  return { varsPath: job.varsPath, variables: current.variables, updatedAt: current.updatedAt };
1413
1394
  }
1414
- // ---------------------------------------------------------------------------
1415
- // STANDING GRANT approval-gated tmux dispatch. The pure decode/decision logic lives in
1416
- // dispatch-grant.ts; the functions below are the session-access GLUE: read the latest grant custom
1417
- // entry (skipping past malformed payloads, but stopping DEAD on a tombstone — never resurrecting an
1418
- // older grant beneath a revocation), count spend, and gate a real launch.
1419
- // ---------------------------------------------------------------------------
1420
-
1421
- /** Most recent VALID `tmux-dispatch-grant` entry on the active branch, or undefined when there is
1422
- * none, it was revoked (a tombstone is a hard stop — never skipped to find an older grant), or every
1423
- * entry found fails to decode. Mirrors the branch-walk idiom used by goal/task session state. */
1424
- function resolveLatestGrantEntry(ctx: ExtensionContext): TmuxDispatchGrant | undefined {
1425
- let fromId: string | undefined;
1426
- for (;;) {
1427
- const entry = ctx.sessionManager.getLatestCustomEntryOnBranch(GRANT_CUSTOM_TYPE, fromId);
1428
- if (!entry) return undefined;
1429
- if (isTmuxDispatchGrantTombstone(entry.data)) return undefined;
1430
- if (isTmuxDispatchGrant(entry.data)) return entry.data;
1431
- if (!entry.parentId) return undefined;
1432
- fromId = entry.parentId;
1433
- }
1434
- }
1435
- /** Decoded `data` payloads of every custom entry of `customType` on the active branch (root→leaf
1436
- * order), for spend-counting — a single `getLatestCustomEntryOnBranch` call only ever returns ONE
1437
- * entry, but budget spend requires ALL of them. */
1438
- function collectCustomEntryData(ctx: ExtensionContext, customType: string): unknown[] {
1439
- const result: unknown[] = [];
1440
- for (const entry of ctx.sessionManager.getBranch()) {
1441
- const loose = entry as unknown as { type?: string; customType?: string; data?: unknown };
1442
- if (loose.type === "custom" && loose.customType === customType) result.push(loose.data);
1443
- }
1444
- return result;
1445
- }
1446
- /** Gate a REAL tmux dispatch (fire_task non-dryRun, send_followup) per the standing-grant doctrine:
1447
- * a valid covering grant dispatches UNATTENDED (spending one usage); absent that, an
1448
- * interactive host may approve a ONE-SHOT launch; absent BOTH, this REFUSES — never a silent launch.
1449
- * Returns the covering grant when one authorized this launch (so the caller can derive launch-profile
1450
- * flags from its envelope), or `{}` for a one-shot approval (no envelope to derive from — the caller
1451
- * falls back to the conservative default profile). */
1452
- async function authorizeLaunch(
1453
- bridge: HostBridge,
1454
- ctx: ExtensionContext,
1455
- request: { agents: readonly Provider[]; goalId?: string; jobId: string; description: string },
1456
- ): Promise<{ grant?: TmuxDispatchGrant }> {
1457
- if (request.agents.length === 0) throw new Error("tmux dispatch refused: launch has no agents");
1458
- const grant = resolveLatestGrantEntry(ctx);
1459
- // A standing grant names one provider. It must cover every child and spend one unit for every
1460
- // child process; a matching first pane must never authorize an unrestricted mixed team.
1461
- if (grant && request.agents.every((agent) => grantCovers(grant, { agent, goalId: request.goalId }))) {
1462
- const used = countGrantUsages(grant.grantId, collectCustomEntryData(ctx, GRANT_USAGE_CUSTOM_TYPE));
1463
- if (grant.budget.maxLaunches - used >= request.agents.length) {
1464
- if (!bridge.appendEntry)
1465
- throw new Error("this host does not support session custom entries; cannot spend the tmux dispatch grant.");
1466
- for (let index = 0; index < request.agents.length; index++) {
1467
- bridge.appendEntry(
1468
- GRANT_USAGE_CUSTOM_TYPE,
1469
- buildGrantUsageEntry(grant.grantId, `${request.jobId}:${index}`),
1470
- );
1471
- }
1472
- return { grant };
1473
- }
1474
- }
1475
- if (ctx.hasUI) {
1476
- const approved = await ctx.ui.confirm(
1477
- "tmux dispatch approval",
1478
- [
1479
- `No standing grant currently authorizes every child of: ${request.description}.`,
1480
- `Agents: ${request.agents.join(", ")}${request.goalId ? `, goal: ${request.goalId}` : ""}.`,
1481
- "Approve this ONE-SHOT launch? Run tmux_agent_manager action=grant_dispatch to authorize future launches without this prompt.",
1482
- ].join("\n"),
1483
- );
1484
- if (!approved) throw new Error(`tmux dispatch declined by the owner: ${request.description}`);
1485
- return {};
1486
- }
1487
- throw new Error(
1488
- `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.`,
1489
- );
1490
- }
1491
- /** Render grant/one-shot-derived launch-profile flags into a pi child's start command. Values
1492
- * are shell-quoted; flags are not (they're fixed literals, never user input). */
1395
+ /** Render a host-derived launch profile into a Pi child's start command. Values are shell-quoted;
1396
+ * flags are fixed literals. */
1493
1397
  function appendLaunchProfileFlags(command: string, flags: LaunchProfileFlag[]): string {
1494
1398
  const rendered = flags.map((flag) =>
1495
1399
  flag.value !== undefined ? `${flag.flag} ${quoteShell(flag.value)}` : flag.flag,
1496
1400
  );
1497
1401
  return [command, ...rendered].join(" ");
1498
1402
  }
1499
- /** Apply the launch profile to every provider="pi" agent in the job (fire_task only — send_followup
1500
- * reuses an already-launched pane, so there is no new child command to configure). Non-pi agents are
1501
- * bounded by the grant only at the launch layer (agent/budget/count); their internal tool-loop
1502
- * enforcement is the target CLI's own responsibility (documented limitation, not a hidden gap). */
1503
- function applyLaunchProfile(job: FireTaskPlan, source: LaunchProfileSource): void {
1504
- for (const agent of job.agents) {
1505
- if (agent.provider !== "pi") continue;
1506
- // Per-agent flags: `worktreeLane` is a per-agent property (only a lane-first goal->tmux
1507
- // dispatch sets it), so the shared `source` envelope is extended with it just for this agent's
1508
- // flag build -- every other agent in a multi-agent team keeps the plain grant-derived profile.
1509
- const flags = buildLaunchProfileFlags(
1510
- agent.worktreeLane ? { ...source, worktreeLane: agent.worktreeLane } : source,
1511
- );
1512
- const profiledCommand = appendLaunchProfileFlags(
1513
- agent.command || defaultProviderInvocation(agent.provider),
1514
- flags,
1515
- );
1516
- agent.command = `env ${PI_ORCHESTRATION_AGENT_ID_ENV}=${quoteShell(agentLaneId(job.id, agent.id))} ${profiledCommand}`;
1403
+ function deriveLaunchProfile(
1404
+ bridge: HostBridge,
1405
+ ctx: ExtensionContext,
1406
+ job: FireTaskPlan,
1407
+ agent: FireAgentPlan,
1408
+ goalId?: string,
1409
+ ): WorkerLaunchProfile {
1410
+ if (agent.provider !== "pi") {
1411
+ return deriveWorkerLaunchProfile({
1412
+ identity: `tmux-profile:${job.id}:${agent.id}`,
1413
+ // External interactive CLIs expose their own native, unrestricted tool surfaces. Model the
1414
+ // managed boundary honestly as one host-trusted process instead of projecting Pi tool names.
1415
+ allowedTools: ["bash"],
1416
+ writePaths: [],
1417
+ parentPid: process.pid,
1418
+ parentSession: ctx.sessionManager.getSessionId?.() ?? ctx.sessionManager.getSessionFile(),
1419
+ taskRef: goalId,
1420
+ });
1517
1421
  }
1422
+ const effectiveResources = agent.resourceProfile ? undefined : bridge.getEffectiveResourceProfile?.();
1423
+ const serializedResources =
1424
+ effectiveResources && Object.keys(effectiveResources).length > 0 ? JSON.stringify(effectiveResources) : undefined;
1425
+ const inheritedResourceProfile = serializedResources
1426
+ ? `tmux-inherited-${crypto.createHash("sha256").update(serializedResources).digest("hex").slice(0, 12)}`
1427
+ : undefined;
1428
+ return deriveWorkerLaunchProfile({
1429
+ identity: `tmux-profile:${job.id}:${agent.id}`,
1430
+ inheritedTools: bridge.getActiveTools?.(),
1431
+ allowedTools: agent.dispatchAllowedTools,
1432
+ resourceProfile: agent.resourceProfile ?? inheritedResourceProfile,
1433
+ resourceProfileJson:
1434
+ inheritedResourceProfile && effectiveResources
1435
+ ? JSON.stringify({ [inheritedResourceProfile]: effectiveResources })
1436
+ : undefined,
1437
+ writePaths: agent.dispatchWritePaths ?? (agent.worktreeLane ? [agent.cwd] : []),
1438
+ thinkingLevel: agent.thinkingLevel ?? bridge.getThinkingLevel?.(),
1439
+ worktreeLane: agent.worktreeLane,
1440
+ parentPid: process.pid,
1441
+ parentSession: ctx.sessionManager.getSessionId?.() ?? ctx.sessionManager.getSessionFile(),
1442
+ taskRef: goalId,
1443
+ });
1444
+ }
1445
+
1446
+ /** Apply the already-compiled immutable profile to a Pi child. Other provider CLIs retain their own
1447
+ * native tool surface; the profile remains the host's durable audit contract for every provider. */
1448
+ function applyLaunchProfile(job: FireTaskPlan, agent: FireAgentPlan, profile: WorkerLaunchProfile): void {
1449
+ if (agent.provider !== "pi") return;
1450
+ const profiledCommand = appendLaunchProfileFlags(
1451
+ agent.command || defaultProviderInvocation(agent.provider),
1452
+ buildLaunchProfileFlags(profile),
1453
+ );
1454
+ agent.command = `env ${PI_ORCHESTRATION_AGENT_ID_ENV}=${quoteShell(agentLaneId(job.id, agent.id))} ${PI_WORKER_ALLOWED_PATHS_ENV}=${quoteShell(encodeWorkerSessionAllowedPaths(profile.writePaths))} ${profiledCommand}`;
1518
1455
  }
1519
1456
  /** Read an OPTIONAL, cooperative worker-reported usage claim for the turn that just went terminal (a
1520
1457
  * sibling `<result-path>.usage.json` file next to the watcher's own result file). Absent file ⇒ no
@@ -1587,62 +1524,6 @@ async function executeTool(
1587
1524
  details: { action, jobId: params.jobId, variableName: params.variableName, result },
1588
1525
  };
1589
1526
  }
1590
- if (action === "grant_dispatch") {
1591
- if (!params.agent) throw new Error("grant_dispatch requires agent");
1592
- if (!Object.hasOwn(PROVIDER_COMMANDS, params.agent))
1593
- throw new Error(`grant_dispatch: unsupported agent: ${params.agent}`);
1594
- const grantParams: GrantDispatchParams = {
1595
- agent: params.agent,
1596
- goalId: params.goalId,
1597
- allowedTools: params.allowedTools,
1598
- resourceProfile: params.resourceProfile,
1599
- writePaths: params.writePaths,
1600
- maxLaunches: typeof params.maxLaunches === "number" ? params.maxLaunches : Number.NaN,
1601
- expiresInMinutes: params.expiresInMinutes,
1602
- maxUsdAdvisory: params.maxUsdAdvisory,
1603
- };
1604
- const grant = buildGrant(grantParams);
1605
- const summary = describeGrant(grant);
1606
- // OWNER-AUTHORIZED, NEVER SILENT: an interactive host must explicitly confirm the exact
1607
- // grant details; a non-interactive host (print/rpc, or the unattended goal loop) has no confirm
1608
- // surface, so it needs an explicit opt-in flag instead — absent either, refuse to create the grant.
1609
- if (ctx.hasUI) {
1610
- const approved = await ctx.ui.confirm("Authorize tmux dispatch grant", summary);
1611
- if (!approved) throw new Error("grant_dispatch was declined by the owner.");
1612
- } else if (bridge.getFlag?.("allow-tmux-dispatch") !== true) {
1613
- throw new Error(
1614
- "grant_dispatch requires interactive approval; no UI is available in this mode. Pass --allow-tmux-dispatch to authorize grant creation non-interactively.",
1615
- );
1616
- }
1617
- if (!bridge.appendEntry)
1618
- throw new Error("this host does not support session custom entries; cannot persist a tmux dispatch grant.");
1619
- bridge.appendEntry(GRANT_CUSTOM_TYPE, grant);
1620
- return {
1621
- content: [{ type: "text", text: `Created tmux dispatch grant ${grant.grantId}.\n${summary}` }],
1622
- details: { action, grant },
1623
- };
1624
- }
1625
- if (action === "revoke_grant") {
1626
- const activeGrant = resolveLatestGrantEntry(ctx);
1627
- const targetGrantId = firstString(params.grantId) || activeGrant?.grantId;
1628
- if (!targetGrantId) throw new Error("revoke_grant: no active tmux dispatch grant to revoke.");
1629
- if (activeGrant && activeGrant.grantId !== targetGrantId)
1630
- throw new Error(
1631
- `revoke_grant: grantId ${targetGrantId} is not the active grant (active: ${activeGrant.grantId}).`,
1632
- );
1633
- if (!bridge.appendEntry)
1634
- throw new Error("this host does not support session custom entries; cannot revoke a tmux dispatch grant.");
1635
- bridge.appendEntry(GRANT_CUSTOM_TYPE, buildTombstone(targetGrantId));
1636
- return {
1637
- content: [
1638
- {
1639
- type: "text",
1640
- text: `Revoked tmux dispatch grant ${targetGrantId}. Future launches need a new grant_dispatch or interactive/opt-in approval.`,
1641
- },
1642
- ],
1643
- details: { action, grantId: targetGrantId },
1644
- };
1645
- }
1646
1527
  const guard = await guardTmux(ctx, detection, `tmux ${action}`);
1647
1528
  if (!guard.allowed)
1648
1529
  return { content: [{ type: "text", text: guard.text }], details: { action, detection, guard, skipped: true } };
@@ -1707,33 +1588,19 @@ async function executeTool(
1707
1588
  throw new Error(
1708
1589
  `tmux session already exists: ${job.sessionName}. Use stop_job/stop_session first or choose a different workspaceName.`,
1709
1590
  );
1710
- // APPROVAL-GATED LAUNCH: resolved before any tmux/FS side effect. A standing grant must
1711
- // authorize every child provider and spend its budget per child process.
1712
- const authorization = await authorizeLaunch(bridge, ctx, {
1713
- agents: job.agents.map((agent) => agent.provider),
1714
- goalId: params.goalId,
1715
- jobId: job.id,
1716
- 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(", ")})`,
1717
- });
1718
- const managedAuthority = managedAuthorization(authorization.grant, `one-shot:${job.id}:turn:1`);
1591
+ // Compile each immutable worker profile from the parent surface plus per-agent overrides before
1592
+ // any durable reservation, filesystem artifact, pane, watcher, or prompt side effect.
1719
1593
  for (const agent of job.agents) {
1594
+ const profile = deriveLaunchProfile(bridge, ctx, job, agent, params.goalId);
1720
1595
  agent.currentTurn = 1;
1721
- applyManagedAuthorization(agent, managedAuthority, {
1596
+ applyManagedProfile(agent, profile, {
1722
1597
  instructions: job.task,
1723
1598
  goalId: params.goalId,
1724
1599
  leaseTtlMs: job.deadlineSeconds * 1_000,
1725
1600
  });
1601
+ applyLaunchProfile(job, agent, profile);
1726
1602
  }
1727
- applyLaunchProfile(job, {
1728
- ...managedAuthority.launchSource,
1729
- ...(params.goalId ? { taskRef: params.goalId } : {}),
1730
- // Process-matrix parent identity: the dispatched child self-registers as a worker of THIS
1731
- // session and winds down gracefully if this session disappears (see dispatch-grant.ts's
1732
- // `LaunchProfileSource.parentPid`/`parentSession` doc).
1733
- parentPid: process.pid,
1734
- parentSession: ctx.sessionManager.getSessionId?.() ?? ctx.sessionManager.getSessionFile(),
1735
- });
1736
- // The host commits the full execution grant and starts the durable lease before any child
1603
+ // The host commits the full execution profile and starts the durable lease before any child
1737
1604
  // process, job artifact, watcher, or prompt side effect occurs.
1738
1605
  reportManagedDispatchReservations(bridge, job, job.agents);
1739
1606
  let sessionCreated = false;
@@ -1744,7 +1611,9 @@ async function executeTool(
1744
1611
  cwd: agent.cwd,
1745
1612
  command: agent.command || defaultProviderInvocation(agent.provider),
1746
1613
  }));
1747
- const launch = launchTmuxSession(job.sessionName, job.cwd, panes);
1614
+ // Create idle panes first. The initial provider task is part of the provider command, but no
1615
+ // command starts until its output watcher owns the pane stream.
1616
+ const launch = launchTmuxSession(job.sessionName, job.cwd, panes, { deferCommands: true });
1748
1617
  sessionCreated = true;
1749
1618
  job.agents.forEach((agent, index) => {
1750
1619
  agent.paneId = launch.paneIds[index];
@@ -1753,7 +1622,14 @@ async function executeTool(
1753
1622
  const watcherPanes = startPaneWatchers(job);
1754
1623
  for (const agent of job.agents) {
1755
1624
  if (!agent.paneId) continue;
1756
- injectPromptToPane(agent.paneId, fs.readFileSync(agent.promptPath, "utf8"), agent.provider);
1625
+ sendCommandToPane(
1626
+ agent.paneId,
1627
+ initialPromptInvocation(
1628
+ agent.command || defaultProviderInvocation(agent.provider),
1629
+ agent.provider,
1630
+ agent.promptPath,
1631
+ ),
1632
+ );
1757
1633
  }
1758
1634
  return {
1759
1635
  content: [
@@ -1831,26 +1707,8 @@ async function executeTool(
1831
1707
  throw new Error(
1832
1708
  `tmux pane ${targetAgent.paneId} for ${targetAgent.name} is gone. The worker cannot receive a follow-up; use fire_task to relaunch.`,
1833
1709
  );
1834
- // APPROVAL-GATED LAUNCH (doctrine-regression mandatory): a follow-up dispatches a fresh
1835
- // turn into an already-running child, so there is no new child command to profile — only the
1836
- // grant/one-shot authorization is resolved here (no applyLaunchProfile call).
1837
- const authorization = await authorizeLaunch(bridge, ctx, {
1838
- agents: [targetAgent.provider],
1839
- goalId: params.goalId,
1840
- jobId: `${job.id}:turn:${turn}`,
1841
- description: `send_followup turn ${turn} to ${targetAgent.name} in job ${job.id}`,
1842
- });
1843
- const managedAuthority = managedAuthorization(authorization.grant, `one-shot:${job.id}:turn:${turn}`);
1844
- const launchAllowedTools = targetAgent.dispatchAllowedTools ?? [...DEFAULT_READ_BIASED_TOOLS];
1845
- const launchWritePaths = targetAgent.dispatchWritePaths ?? [];
1846
- if (
1847
- !sameStringValues(launchAllowedTools, managedAuthority.allowedTools) ||
1848
- !sameStringValues(launchWritePaths, managedAuthority.writePaths)
1849
- ) {
1850
- throw new Error(
1851
- `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.`,
1852
- );
1853
- }
1710
+ // The live process keeps the immutable profile persisted at birth. A follow-up only advances
1711
+ // instructions and its fenced sequence; it cannot widen or replace the process profile.
1854
1712
  // Reserve before watcher/prompt side effects. Reconcile re-arms a reserved turn but never
1855
1713
  // re-injects it, so crash recovery cannot duplicate a provider prompt.
1856
1714
  const reserved = persistJobPatch(job.id, (current) => {
@@ -1859,9 +1717,9 @@ async function executeTool(
1859
1717
  agent.currentTurn = turn;
1860
1718
  agent.pendingTurn = turn;
1861
1719
  agent.result = undefined;
1862
- applyManagedAuthorization(agent, managedAuthority, {
1720
+ applyManagedTurn(agent, {
1863
1721
  instructions: followupTask,
1864
- goalId: params.goalId,
1722
+ goalId: params.goalId ?? agent.dispatchGoalId,
1865
1723
  leaseTtlMs: job.deadlineSeconds * 1_000,
1866
1724
  });
1867
1725
  current.currentTurn = turn;
@@ -1914,6 +1772,15 @@ async function executeTool(
1914
1772
  content: [{ type: "text", text: `tmux job ${job.id} was already dismissed.` }],
1915
1773
  details: { action, jobId: job.id, alreadyDismissed: true },
1916
1774
  };
1775
+ if (tmuxSessionExists(job.sessionName)) {
1776
+ for (const agent of job.agents) {
1777
+ if (!agent.paneId || !tmuxPaneExists(job.sessionName, agent.paneId)) continue;
1778
+ requireTmuxRun(
1779
+ runTmux(["pipe-pane", "-t", agent.paneId]),
1780
+ `tmux completion watcher detach for ${agent.paneId}`,
1781
+ );
1782
+ }
1783
+ }
1917
1784
  persistJobPatch(job.id, (current) => {
1918
1785
  current.dismissedAt = new Date().toISOString();
1919
1786
  return current;
@@ -2009,6 +1876,13 @@ function formatFireTaskHandoff(job: FireTaskPlan): string {
2009
1876
  ].join("\n");
2010
1877
  }
2011
1878
 
1879
+ function compactTmuxAgentStatus(status: string | undefined): "succeeded" | "partial" | "failed" | "canceled" {
1880
+ if (status === "done" || status === "succeeded" || status === "completed") return "succeeded";
1881
+ if (status === "blocked" || status === "partial") return "partial";
1882
+ if (status === "canceled" || status === "cancelled" || status === "stopped") return "canceled";
1883
+ return "failed";
1884
+ }
1885
+
2012
1886
  /** Diff live tmux sessions against this session's job records. Mirrors the invariant behind
2013
1887
  * `LocalRuntimeController.reconcile` (local-runtime-controller.ts): reconcile only ever OBSERVES —
2014
1888
  * it never kills a session it did not provably start. A job whose tmux session is gone is marked
@@ -2067,13 +1941,6 @@ export default function tmuxAgentManagerExtension(pi: ExtensionAPI) {
2067
1941
  let handoffTail = Promise.resolve();
2068
1942
  const jobWatchers = new Map<string, fs.FSWatcher>();
2069
1943
  const bridge: HostBridge = pi;
2070
- // Non-interactive opt-in for grant_dispatch: optional-chained so a lightweight test double
2071
- // that doesn't implement registerFlag still loads the extension; a real host always has it.
2072
- bridge.registerFlag?.("allow-tmux-dispatch", {
2073
- type: "boolean",
2074
- description:
2075
- "Authorize tmux_agent_manager grant_dispatch to create a standing tmux dispatch grant without interactive approval (print/rpc/non-interactive mode only).",
2076
- });
2077
1944
 
2078
1945
  const closeJobWatchers = () => {
2079
1946
  for (const watcher of jobWatchers.values()) watcher.close();
@@ -2114,16 +1981,13 @@ export default function tmuxAgentManagerExtension(pi: ExtensionAPI) {
2114
1981
  if (!bridge.reportManagedLane) {
2115
1982
  pi.sendMessage(
2116
1983
  {
2117
- customType: "tmux-background-completion",
1984
+ customType: "background-worker-completion",
2118
1985
  content: formatFireTaskHandoff(job),
2119
1986
  display: true,
2120
1987
  details: {
2121
- jobId: job.id,
2122
- sessionName: job.sessionName,
2123
- agents: job.agents.map((agent) => ({
2124
- id: agent.id,
2125
- name: agent.name,
2126
- status: agent.result?.status,
1988
+ records: job.agents.map((agent) => ({
1989
+ laneId: agentLaneId(job.id, agent.id),
1990
+ status: compactTmuxAgentStatus(agent.result?.status),
2127
1991
  })),
2128
1992
  },
2129
1993
  },
@@ -2140,7 +2004,6 @@ export default function tmuxAgentManagerExtension(pi: ExtensionAPI) {
2140
2004
  }
2141
2005
  return current;
2142
2006
  });
2143
- if (ctx.hasUI) ctx.ui.notify(`tmux background task ${job.id} completed.`, "info");
2144
2007
  }
2145
2008
 
2146
2009
  const activeIds = new Set(jobs.filter((job) => !isFireTaskTerminal(job)).map((job) => job.id));
@@ -2199,22 +2062,22 @@ export default function tmuxAgentManagerExtension(pi: ExtensionAPI) {
2199
2062
  name: "tmux_agent_manager",
2200
2063
  label: "tmux Agent Manager",
2201
2064
  description:
2202
- "Specialized tmux-backed interactive provider/workspace manager. Native delegate is the standard cross-platform subagent path. Use this tool only when the owner explicitly requests persistent interactive CLI panes and tmux is available.",
2203
- promptSnippet: "tmux interactive panes only by owner request after guard; ordinary agents use native delegate.",
2065
+ "Tmux-backed persistent interactive provider/workspace manager. Native delegate remains the standard cross-platform route; use this when a task benefits from persistent CLI panes.",
2066
+ promptSnippet:
2067
+ "Persistent tmux workers with inherited, immutable launch profiles; ordinary agents use native delegate.",
2204
2068
  promptGuidelines: [
2205
- "Ordinary agents: delegate. tmux only owner-requested panes after guard; never native Windows.",
2206
- "WSL tmux only if installed/requested. Never edit profiles during dispatch; native resolver owns them.",
2207
- "fire_task starts batch, returns after watcher armed; never wait/poll/peek.",
2069
+ "Ordinary agents: delegate. Tmux availability detection is automatic; guard is diagnostic only. Never use native Windows tmux.",
2070
+ "fire_task derives one immutable profile per agent; omitted fields inherit parent tools, thinking, and machine scope.",
2071
+ "tools, thinkingLevel, resourceProfile, and worktreeLane are Pi-only; configure external CLIs in command.",
2072
+ "Pi path narrows structured file tools; external path sets cwd only and is audited as a machine-wide host process.",
2073
+ "fire_task arms capture before atomically starting each CLI with its task; never wait/poll/peek.",
2208
2074
  "Teams: list/show_template, teamTemplate. Layout: workspace_plan, then launch_workspace.",
2209
2075
  "send_followup prompts live pane/re-arms watcher; no relaunch. dismiss stops tracking, not session.",
2210
2076
  "Cleanup: stop_job/stop_session dryRun, then confirm=yes-tmux-stop.",
2211
2077
  "No secrets in task/command; all persist: ~/.pi/agent/work/background/tmux-agent-manager/state/jobs.",
2212
2078
  "Display/status metadata only; verify result/log before completion claim.",
2213
- "Real fire_task/send_followup needs grant or one-shot UI approval. No grant/UI: refused; run grant_dispatch.",
2214
- "grant_dispatch: agent,maxLaunches; optional goalId,allowedTools,resourceProfile,writePaths,expiresInMinutes.",
2215
- "Grant needs UI confirmation, or --allow-tmux-dispatch without UI; revoke_grant ends it.",
2216
- "Pi child: restricted tools/resource profile or no extensions/skills, scoped hard-stop prompt; launch config holds grant.",
2217
- "Non-pi CLI grant applies at launch only; tool loop is CLI-owned.",
2079
+ "Workers continue autonomously inside the assigned profile; no separate approval handshake exists.",
2080
+ "Non-Pi process behavior is CLI-owned; never represent Pi-only profile controls as enforced for an external CLI.",
2218
2081
  ],
2219
2082
  parameters: Type.Object({
2220
2083
  action: Type.Optional(
@@ -2239,12 +2102,10 @@ export default function tmuxAgentManagerExtension(pi: ExtensionAPI) {
2239
2102
  Type.Literal("show_template"),
2240
2103
  Type.Literal("stop_job"),
2241
2104
  Type.Literal("stop_session"),
2242
- Type.Literal("grant_dispatch"),
2243
- Type.Literal("revoke_grant"),
2244
2105
  ],
2245
2106
  {
2246
2107
  description:
2247
- "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.",
2108
+ "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, or stop_session. Default status.",
2248
2109
  },
2249
2110
  ),
2250
2111
  ),
@@ -2293,7 +2154,13 @@ export default function tmuxAgentManagerExtension(pi: ExtensionAPI) {
2293
2154
  jobId: Type.Optional(
2294
2155
  Type.String({
2295
2156
  description:
2296
- "Job id for fire_task (optional), job_status/list_variables/set_variable/send_followup/dismiss (required).",
2157
+ "Existing job id for job_status/list_variables/set_variable/send_followup/dismiss. Never creates a fresh fire_task job.",
2158
+ }),
2159
+ ),
2160
+ launchKey: Type.Optional(
2161
+ Type.String({
2162
+ description:
2163
+ "Optional fresh fire_task identity. Use only for intentional stable naming; omitted creates a unique key. Never auto-retained from results.",
2297
2164
  }),
2298
2165
  ),
2299
2166
  agentId: Type.Optional(
@@ -2314,7 +2181,7 @@ export default function tmuxAgentManagerExtension(pi: ExtensionAPI) {
2314
2181
  deadlineSeconds: Type.Optional(
2315
2182
  Type.Number({
2316
2183
  description:
2317
- "For fire_task: seconds before each event-driven pane watcher records a timeout. Default 1200.",
2184
+ "For fire_task: seconds before each event-driven watcher records timeout and terminates its owned pane. Default 1200.",
2318
2185
  minimum: 5,
2319
2186
  maximum: MAX_DEADLINE_SECONDS,
2320
2187
  }),
@@ -2327,10 +2194,41 @@ export default function tmuxAgentManagerExtension(pi: ExtensionAPI) {
2327
2194
  command: Type.Optional(
2328
2195
  Type.String({
2329
2196
  description:
2330
- "Interactive CLI start command for this pane, e.g. claude, agy, pi, or a custom wrapper CLI. The manager injects the prompt after launch and captures pane output.",
2197
+ "Interactive CLI start command. Capture is armed first, then the initial prompt file is passed as the final CLI argument; Agy also gets --prompt-interactive.",
2198
+ }),
2199
+ ),
2200
+ cwd: Type.Optional(
2201
+ Type.String({
2202
+ description: "Legacy per-agent working directory; does not narrow filesystem scope.",
2203
+ }),
2204
+ ),
2205
+ path: Type.Optional(
2206
+ Type.String({
2207
+ description:
2208
+ "Optional workspace cwd. For Pi it narrows structured file tools; external CLIs remain machine-wide host-trusted processes.",
2331
2209
  }),
2332
2210
  ),
2333
- cwd: Type.Optional(Type.String({ description: "Optional per-agent cwd." })),
2211
+ tools: Type.Optional(
2212
+ Type.Array(Type.String(), {
2213
+ description:
2214
+ "Pi-only policy-owned tool override. Omitted inherits the parent's eligible worker tools; parent-only controls are removed.",
2215
+ minItems: 1,
2216
+ }),
2217
+ ),
2218
+ resourceProfile: Type.Optional(
2219
+ Type.String({
2220
+ description:
2221
+ "Pi-only resource-profile override. Omitted inherits the parent's effective resources.",
2222
+ }),
2223
+ ),
2224
+ thinkingLevel: Type.Optional(
2225
+ orchestrationThinkingLevelSchema(
2226
+ "Pi-only thinking override. Omitted inherits the orchestrator's current level.",
2227
+ ),
2228
+ ),
2229
+ worktreeLane: Type.Optional(
2230
+ Type.String({ description: "Pi-only worktree-sync lane binding for this worker." }),
2231
+ ),
2334
2232
  }),
2335
2233
  { description: "Agents/providers to lay out in tmux. Default: pi, agy, codex." },
2336
2234
  ),
@@ -2344,7 +2242,7 @@ export default function tmuxAgentManagerExtension(pi: ExtensionAPI) {
2344
2242
  force: Type.Optional(
2345
2243
  Type.Boolean({
2346
2244
  description:
2347
- "For fire_task only: archive an existing job directory with the same jobId before launch. Existing tmux sessions are still refused; stop them first.",
2245
+ "For fire_task only: archive an existing job directory with the same launchKey before launch. Existing tmux sessions are still refused; stop them first.",
2348
2246
  }),
2349
2247
  ),
2350
2248
  confirm: Type.Optional(
@@ -2352,50 +2250,7 @@ export default function tmuxAgentManagerExtension(pi: ExtensionAPI) {
2352
2250
  ),
2353
2251
  goalId: Type.Optional(
2354
2252
  Type.String({
2355
- description:
2356
- "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.",
2357
- }),
2358
- ),
2359
- agent: Type.Optional(providerSchema("grant_dispatch: the provider this standing grant authorizes.")),
2360
- allowedTools: Type.Optional(
2361
- Type.Array(Type.String(), {
2362
- description:
2363
- "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.",
2364
- }),
2365
- ),
2366
- resourceProfile: Type.Optional(
2367
- Type.String({
2368
- description:
2369
- "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.",
2370
- }),
2371
- ),
2372
- writePaths: Type.Optional(
2373
- Type.Array(Type.String(), {
2374
- description: "grant_dispatch: write paths named in the child's scoped --append-system-prompt role text.",
2375
- }),
2376
- ),
2377
- maxLaunches: Type.Optional(
2378
- Type.Number({
2379
- description: "grant_dispatch: number of real fire_task/send_followup dispatches this grant authorizes.",
2380
- minimum: 1,
2381
- }),
2382
- ),
2383
- expiresInMinutes: Type.Optional(
2384
- Type.Number({
2385
- description:
2386
- "grant_dispatch: grant validity window from creation. Omit for a grant that never expires (until revoke_grant).",
2387
- minimum: 1,
2388
- }),
2389
- ),
2390
- maxUsdAdvisory: Type.Optional(
2391
- Type.Number({
2392
- description:
2393
- "grant_dispatch: advisory USD ceiling. Never enforced across the process boundary (the child bills under its own auth) — a claim to review only.",
2394
- }),
2395
- ),
2396
- grantId: Type.Optional(
2397
- Type.String({
2398
- description: "revoke_grant: grant id to revoke. Omit to revoke whichever grant is currently active.",
2253
+ description: "Goal this fire_task/send_followup belongs to for durable lane ownership and recovery.",
2399
2254
  }),
2400
2255
  ),
2401
2256
  }),