@duckmind/dm-windows-x64 0.61.4 → 0.61.9

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 (279) hide show
  1. package/dm.exe +0 -0
  2. package/extensions/.dm-extensions.json +211 -67
  3. package/extensions/dm-subagents/agents/claude-code-writer.md +15 -0
  4. package/extensions/dm-subagents/agents/claude-code.md +15 -0
  5. package/extensions/dm-subagents/agents/codex-exec-writer.md +15 -0
  6. package/extensions/dm-subagents/agents/codex-exec.md +15 -0
  7. package/extensions/dm-subagents/agents/cursor-agent-writer.md +14 -0
  8. package/extensions/dm-subagents/agents/cursor-agent.md +14 -0
  9. package/extensions/dm-subagents/agents/delegate.md +3 -2
  10. package/extensions/dm-subagents/agents/oracle.md +10 -5
  11. package/extensions/dm-subagents/agents/researcher.md +2 -2
  12. package/extensions/dm-subagents/agents/reviewer.md +17 -7
  13. package/extensions/dm-subagents/agents/scout.md +5 -5
  14. package/extensions/dm-subagents/agents/worker.md +6 -2
  15. package/extensions/dm-subagents/async-retention-discovery-worker.mjs +167 -0
  16. package/extensions/dm-subagents/index.js +4 -0
  17. package/extensions/dm-subagents/inspector-runner.mjs +10 -0
  18. package/extensions/dm-subagents/install.mjs +3 -2
  19. package/extensions/dm-subagents/package.json +2 -2
  20. package/extensions/dm-subagents/prompts/council.md +60 -0
  21. package/extensions/dm-subagents/prompts/parallel-review.md +5 -1
  22. package/extensions/dm-subagents/prompts/review-loop.md +13 -7
  23. package/extensions/dm-subagents/skills/council-mode/SKILL.md +59 -0
  24. package/extensions/dm-subagents/skills/council-mode/references/pass-contracts.md +150 -0
  25. package/extensions/dm-subagents/skills/dm-subagents/SKILL.md +96 -911
  26. package/extensions/dm-subagents/skills/dm-subagents/references/constraints-and-recipes.md +70 -0
  27. package/extensions/dm-subagents/skills/dm-subagents/references/execution-controls.md +539 -0
  28. package/extensions/dm-subagents/skills/dm-subagents/references/management-authoring-rpc.md +161 -0
  29. package/extensions/dm-subagents/skills/dm-subagents/references/multi-lane-orchestration.md +51 -0
  30. package/extensions/dm-subagents/skills/dm-subagents/references/prompting-and-roles.md +295 -0
  31. package/extensions/dm-subagents/skills/dm-subagents/references/review-and-validation.md +73 -0
  32. package/extensions/dm-subagents/src/agents/agent-management.js +716 -484
  33. package/extensions/dm-subagents/src/agents/agent-refinements.js +563 -0
  34. package/extensions/dm-subagents/src/agents/agent-serializer.js +70 -5
  35. package/extensions/dm-subagents/src/agents/agents.js +1447 -299
  36. package/extensions/dm-subagents/src/agents/builtin-names.js +15 -0
  37. package/extensions/dm-subagents/src/agents/chain-serializer.js +12 -7
  38. package/extensions/dm-subagents/src/agents/frontmatter.js +64 -14
  39. package/extensions/dm-subagents/src/agents/identity.js +1 -1
  40. package/extensions/dm-subagents/src/agents/proactive-skills.js +14 -11
  41. package/extensions/dm-subagents/src/agents/runtime-agent-events.js +49 -0
  42. package/extensions/dm-subagents/src/agents/runtime-agent-registry.js +412 -0
  43. package/extensions/dm-subagents/src/agents/skills.js +52 -35
  44. package/extensions/dm-subagents/src/api/agents.js +6 -0
  45. package/extensions/dm-subagents/src/api/background-work.js +151 -0
  46. package/extensions/dm-subagents/src/api/capability-ceiling.js +12 -0
  47. package/extensions/dm-subagents/src/api/control-channel.js +3 -0
  48. package/extensions/dm-subagents/src/api/delegation.js +5 -0
  49. package/extensions/dm-subagents/src/api/dm-args.js +3 -0
  50. package/extensions/dm-subagents/src/api/external-job-provider.js +137 -0
  51. package/extensions/dm-subagents/src/api/external-runs.js +233 -0
  52. package/extensions/dm-subagents/src/api/intercom-bridge.js +3 -0
  53. package/extensions/dm-subagents/src/api/preflight.js +322 -0
  54. package/extensions/dm-subagents/src/api/project-panes.js +11 -0
  55. package/extensions/dm-subagents/src/api/shared-types.js +4 -0
  56. package/extensions/dm-subagents/src/extension/config.js +175 -0
  57. package/extensions/dm-subagents/src/extension/control-notices.js +4 -43
  58. package/extensions/dm-subagents/src/extension/doctor.js +71 -17
  59. package/extensions/dm-subagents/src/extension/fanout-child.js +49 -32
  60. package/extensions/dm-subagents/src/extension/index.js +711 -265
  61. package/extensions/dm-subagents/src/extension/public-execution.js +114 -0
  62. package/extensions/dm-subagents/src/extension/rpc.js +427 -22
  63. package/extensions/dm-subagents/src/extension/schemas.js +158 -65
  64. package/extensions/dm-subagents/src/extension/steering-notices.js +23 -0
  65. package/extensions/dm-subagents/src/extension/subagent-guide.js +31 -0
  66. package/extensions/dm-subagents/src/extension/tool-description.js +92 -74
  67. package/extensions/dm-subagents/src/extension/tool-result.js +7 -0
  68. package/extensions/dm-subagents/src/inspectors/herdr/actions.js +218 -0
  69. package/extensions/dm-subagents/src/inspectors/herdr/client.js +123 -0
  70. package/extensions/dm-subagents/src/inspectors/herdr/focus.js +47 -0
  71. package/extensions/dm-subagents/src/inspectors/herdr/inspector-runner.js +160 -0
  72. package/extensions/dm-subagents/src/inspectors/herdr/project-panes.js +618 -0
  73. package/extensions/dm-subagents/src/inspectors/herdr/session-roots-codec.js +21 -0
  74. package/extensions/dm-subagents/src/inspectors/herdr/shell-command.js +15 -0
  75. package/extensions/dm-subagents/src/integrations/herdr-status.js +377 -0
  76. package/extensions/dm-subagents/src/intercom/intercom-bridge.js +17 -13
  77. package/extensions/dm-subagents/src/intercom/native-supervisor-channel.js +371 -79
  78. package/extensions/dm-subagents/src/intercom/result-intercom.js +47 -7
  79. package/extensions/dm-subagents/src/missions/actions.js +394 -0
  80. package/extensions/dm-subagents/src/missions/goal-driver.js +149 -0
  81. package/extensions/dm-subagents/src/missions/lifecycle.js +331 -0
  82. package/extensions/dm-subagents/src/missions/store.js +548 -0
  83. package/extensions/dm-subagents/src/missions/types.js +9 -0
  84. package/extensions/dm-subagents/src/missions/workflow-state.js +245 -0
  85. package/extensions/dm-subagents/src/policy/authority.js +37 -0
  86. package/extensions/dm-subagents/src/profiles/profiles.js +36 -18
  87. package/extensions/dm-subagents/src/runs/background/active-async-capacity.js +427 -0
  88. package/extensions/dm-subagents/src/runs/background/active-run-index.js +122 -0
  89. package/extensions/dm-subagents/src/runs/background/async-execution.js +925 -137
  90. package/extensions/dm-subagents/src/runs/background/async-job-tracker.js +515 -148
  91. package/extensions/dm-subagents/src/runs/background/async-resume.js +378 -51
  92. package/extensions/dm-subagents/src/runs/background/async-retention.js +828 -0
  93. package/extensions/dm-subagents/src/runs/background/async-status-snapshot.js +31 -0
  94. package/extensions/dm-subagents/src/runs/background/async-status.js +259 -22
  95. package/extensions/dm-subagents/src/runs/background/auto-drain.js +46 -0
  96. package/extensions/dm-subagents/src/runs/background/chain-append.js +50 -15
  97. package/extensions/dm-subagents/src/runs/background/chain-root-attachment.js +67 -12
  98. package/extensions/dm-subagents/src/runs/background/completion-batcher.js +5 -1
  99. package/extensions/dm-subagents/src/runs/background/completion-dedupe.js +3 -11
  100. package/extensions/dm-subagents/src/runs/background/completion-replay.js +245 -0
  101. package/extensions/dm-subagents/src/runs/background/control-channel.js +423 -36
  102. package/extensions/dm-subagents/src/runs/background/fleet-view.js +132 -59
  103. package/extensions/dm-subagents/src/runs/background/index-segment.js +38 -0
  104. package/extensions/dm-subagents/src/runs/background/inspect-rpc.js +373 -0
  105. package/extensions/dm-subagents/src/runs/background/notify.js +357 -57
  106. package/extensions/dm-subagents/src/runs/background/owned-process-tree.js +86 -0
  107. package/extensions/dm-subagents/src/runs/background/process-terminal.js +269 -0
  108. package/extensions/dm-subagents/src/runs/background/result-delivery-ownership.js +34 -0
  109. package/extensions/dm-subagents/src/runs/background/result-files.js +469 -0
  110. package/extensions/dm-subagents/src/runs/background/result-watcher.js +540 -75
  111. package/extensions/dm-subagents/src/runs/background/resume-guidance.js +44 -0
  112. package/extensions/dm-subagents/src/runs/background/retained-children.js +119 -0
  113. package/extensions/dm-subagents/src/runs/background/run-id-query.js +5 -0
  114. package/extensions/dm-subagents/src/runs/background/run-id-resolver.js +93 -9
  115. package/extensions/dm-subagents/src/runs/background/run-status.js +303 -32
  116. package/extensions/dm-subagents/src/runs/background/scheduled-runs.js +784 -376
  117. package/extensions/dm-subagents/src/runs/background/stale-run-reconciler.js +75 -34
  118. package/extensions/dm-subagents/src/runs/background/steering.js +221 -0
  119. package/extensions/dm-subagents/src/runs/background/subagent-runner.js +3106 -844
  120. package/extensions/dm-subagents/src/runs/background/subagent-wait.js +529 -0
  121. package/extensions/dm-subagents/src/runs/background/terminal-run-index.js +106 -0
  122. package/extensions/dm-subagents/src/runs/background/top-level-async.js +1 -1
  123. package/extensions/dm-subagents/src/runs/background/wait-completions.js +155 -0
  124. package/extensions/dm-subagents/src/runs/background/wait-config.js +46 -0
  125. package/extensions/dm-subagents/src/runs/background/wait-subscriptions.js +278 -0
  126. package/extensions/dm-subagents/src/runs/background/wait-tool.js +47 -0
  127. package/extensions/dm-subagents/src/runs/foreground/async-dismiss-action.js +81 -0
  128. package/extensions/dm-subagents/src/runs/foreground/async-steering-action.js +245 -0
  129. package/extensions/dm-subagents/src/runs/foreground/async-stop-action.js +74 -0
  130. package/extensions/dm-subagents/src/runs/foreground/execution.js +1401 -342
  131. package/extensions/dm-subagents/src/runs/foreground/foreground-control.js +133 -0
  132. package/extensions/dm-subagents/src/runs/foreground/foreground-history.js +148 -0
  133. package/extensions/dm-subagents/src/runs/foreground/prompt-audit.js +139 -0
  134. package/extensions/dm-subagents/src/runs/foreground/subagent-executor.js +4278 -1441
  135. package/extensions/dm-subagents/src/runs/foreground/workflow-detach-reconcile.js +278 -0
  136. package/extensions/dm-subagents/src/runs/foreground/workflow-foreground-steering.js +155 -0
  137. package/extensions/dm-subagents/src/runs/shared/abort-recovery.js +97 -0
  138. package/extensions/dm-subagents/src/runs/shared/acceptance.js +597 -148
  139. package/extensions/dm-subagents/src/runs/shared/agent-contract.js +35 -0
  140. package/extensions/dm-subagents/src/runs/shared/async-status-projection.js +472 -0
  141. package/extensions/dm-subagents/src/runs/shared/background-process-options.js +6 -0
  142. package/extensions/dm-subagents/src/runs/shared/capability-ceiling.js +175 -0
  143. package/extensions/dm-subagents/src/runs/shared/child-identity.js +32 -0
  144. package/extensions/dm-subagents/src/runs/shared/child-launch-plan.js +65 -0
  145. package/extensions/dm-subagents/src/runs/shared/child-protocol.js +447 -0
  146. package/extensions/dm-subagents/src/runs/shared/claude-code-adapter.js +120 -0
  147. package/extensions/dm-subagents/src/runs/shared/codex-exec-adapter.js +129 -0
  148. package/extensions/dm-subagents/src/runs/shared/completion-evidence.js +40 -0
  149. package/extensions/dm-subagents/src/runs/shared/completion-guard.js +140 -83
  150. package/extensions/dm-subagents/src/runs/shared/context-mode.js +38 -0
  151. package/extensions/dm-subagents/src/runs/shared/cursor-agent-adapter.js +101 -0
  152. package/extensions/dm-subagents/src/runs/shared/dm-args.js +445 -72
  153. package/extensions/dm-subagents/src/runs/shared/dm-spawn.js +27 -16
  154. package/extensions/dm-subagents/src/runs/shared/dynamic-fanout.js +19 -6
  155. package/extensions/dm-subagents/src/runs/shared/extension-bindings.js +81 -0
  156. package/extensions/dm-subagents/src/runs/shared/external-cli-contract.js +134 -0
  157. package/extensions/dm-subagents/src/runs/shared/external-cli-preflight.js +98 -0
  158. package/extensions/dm-subagents/src/runs/shared/external-cli-runner.js +419 -0
  159. package/extensions/dm-subagents/src/runs/shared/external-job-bridge.js +404 -0
  160. package/extensions/dm-subagents/src/runs/shared/external-job-runner.js +334 -0
  161. package/extensions/dm-subagents/src/runs/shared/fast-mode-extension.js +8 -0
  162. package/extensions/dm-subagents/src/runs/shared/host-step-status.js +228 -0
  163. package/extensions/dm-subagents/src/runs/shared/lane-metadata.js +104 -0
  164. package/extensions/dm-subagents/src/runs/shared/launch-cwd.js +17 -0
  165. package/extensions/dm-subagents/src/runs/shared/llm-intent-arbiter.js +190 -0
  166. package/extensions/dm-subagents/src/runs/shared/long-running-guard.js +48 -3
  167. package/extensions/dm-subagents/src/runs/shared/mcp-config-sources.js +387 -0
  168. package/extensions/dm-subagents/src/runs/shared/mcp-direct-tool-allowlist.js +212 -137
  169. package/extensions/dm-subagents/src/runs/shared/mcp-direct-tool-grant.js +131 -0
  170. package/extensions/dm-subagents/src/runs/shared/model-exclusions.js +207 -0
  171. package/extensions/dm-subagents/src/runs/shared/model-fallback.js +225 -55
  172. package/extensions/dm-subagents/src/runs/shared/model-scope.js +85 -28
  173. package/extensions/dm-subagents/src/runs/shared/mutation-evidence.js +182 -0
  174. package/extensions/dm-subagents/src/runs/shared/nested-events.js +264 -102
  175. package/extensions/dm-subagents/src/runs/shared/nested-render.js +15 -5
  176. package/extensions/dm-subagents/src/runs/shared/orca-progress-tabs.js +505 -0
  177. package/extensions/dm-subagents/src/runs/shared/parallel-handoff.js +653 -0
  178. package/extensions/dm-subagents/src/runs/shared/parallel-utils.js +29 -12
  179. package/extensions/dm-subagents/src/runs/shared/permissions.js +108 -0
  180. package/extensions/dm-subagents/src/runs/shared/process-signal.js +13 -0
  181. package/extensions/dm-subagents/src/runs/shared/run-fanout-budget.js +257 -0
  182. package/extensions/dm-subagents/src/runs/shared/run-history.js +133 -13
  183. package/extensions/dm-subagents/src/runs/shared/runtime-acknowledged-extensions.js +62 -0
  184. package/extensions/dm-subagents/src/runs/shared/session-lease.js +225 -0
  185. package/extensions/dm-subagents/src/runs/shared/single-output.js +129 -27
  186. package/extensions/dm-subagents/src/runs/shared/spawn-budget.js +95 -0
  187. package/extensions/dm-subagents/src/runs/shared/structured-output.js +129 -10
  188. package/extensions/dm-subagents/src/runs/shared/subagent-control.js +66 -11
  189. package/extensions/dm-subagents/src/runs/shared/subagent-prompt-runtime.js +548 -70
  190. package/extensions/dm-subagents/src/runs/shared/subagent-startup-retry.js +50 -0
  191. package/extensions/dm-subagents/src/runs/shared/task-intent.js +130 -0
  192. package/extensions/dm-subagents/src/runs/shared/tool-availability.js +59 -0
  193. package/extensions/dm-subagents/src/runs/shared/tool-budget.js +7 -5
  194. package/extensions/dm-subagents/src/runs/shared/tool-timeout.js +64 -0
  195. package/extensions/dm-subagents/src/runs/shared/usage-budget.js +74 -0
  196. package/extensions/dm-subagents/src/runs/shared/workflow-graph.js +22 -0
  197. package/extensions/dm-subagents/src/runs/shared/worktree-cleanup-plan.js +721 -0
  198. package/extensions/dm-subagents/src/runs/shared/worktree.js +168 -19
  199. package/extensions/dm-subagents/src/shared/accessible-dir.js +35 -0
  200. package/extensions/dm-subagents/src/shared/agent-stream-options.js +3 -0
  201. package/extensions/dm-subagents/src/shared/artifacts.js +170 -11
  202. package/extensions/dm-subagents/src/shared/atomic-json.js +36 -38
  203. package/extensions/dm-subagents/src/shared/capacity-resilient-json.js +77 -0
  204. package/extensions/dm-subagents/src/shared/child-session-name.js +15 -0
  205. package/extensions/dm-subagents/src/shared/child-transcript.js +57 -2
  206. package/extensions/dm-subagents/src/shared/completion-owner.js +7 -0
  207. package/extensions/dm-subagents/src/shared/display-text.js +142 -0
  208. package/extensions/dm-subagents/src/shared/extension-context.js +17 -0
  209. package/extensions/dm-subagents/src/shared/file-coalescer.js +9 -0
  210. package/extensions/dm-subagents/src/shared/file-system-retry.js +56 -0
  211. package/extensions/dm-subagents/src/shared/fork-context.js +96 -33
  212. package/extensions/dm-subagents/src/shared/formatters.js +21 -7
  213. package/extensions/dm-subagents/src/shared/launch-contract.js +94 -0
  214. package/extensions/dm-subagents/src/shared/model-info.js +10 -5
  215. package/extensions/dm-subagents/src/shared/node-executable.js +19 -0
  216. package/extensions/dm-subagents/src/shared/prompt-resources.js +10 -0
  217. package/extensions/dm-subagents/src/shared/pruned-fork.js +427 -0
  218. package/extensions/dm-subagents/src/shared/session-file-trust.js +19 -0
  219. package/extensions/dm-subagents/src/shared/session-tokens.js +14 -3
  220. package/extensions/dm-subagents/src/shared/settings.js +39 -47
  221. package/extensions/dm-subagents/src/shared/shortcuts.js +16 -0
  222. package/extensions/dm-subagents/src/shared/status-format.js +9 -2
  223. package/extensions/dm-subagents/src/shared/thinking-ceiling.js +41 -0
  224. package/extensions/dm-subagents/src/shared/types.js +47 -7
  225. package/extensions/dm-subagents/src/shared/utf8.js +12 -0
  226. package/extensions/dm-subagents/src/shared/utils.js +151 -131
  227. package/extensions/dm-subagents/src/shared/watch-strategy.js +3 -0
  228. package/extensions/dm-subagents/src/shared/workflow-child-permit.js +84 -0
  229. package/extensions/dm-subagents/src/slash/delegation-adapters.js +274 -0
  230. package/extensions/dm-subagents/src/slash/delegation-json.js +113 -0
  231. package/extensions/dm-subagents/src/slash/delegation-request.js +152 -0
  232. package/extensions/dm-subagents/src/slash/prompt-template-bridge.js +294 -243
  233. package/extensions/dm-subagents/src/slash/prompt-workflows.js +35 -73
  234. package/extensions/dm-subagents/src/slash/selector.js +101 -0
  235. package/extensions/dm-subagents/src/slash/slash-bridge.js +17 -1
  236. package/extensions/dm-subagents/src/slash/slash-commands.js +722 -732
  237. package/extensions/dm-subagents/src/slash/slash-live-state.js +37 -19
  238. package/extensions/dm-subagents/src/slash/subagents-admin.js +410 -0
  239. package/extensions/dm-subagents/src/tui/fleet-status.js +824 -0
  240. package/extensions/dm-subagents/src/tui/fleet-transcript.js +479 -0
  241. package/extensions/dm-subagents/src/tui/fleet.js +1326 -0
  242. package/extensions/dm-subagents/src/tui/render-helpers.js +22 -0
  243. package/extensions/dm-subagents/src/tui/render.js +1511 -261
  244. package/extensions/dm-subagents/src/watchdog/change-signature.js +220 -0
  245. package/extensions/dm-subagents/src/watchdog/child-status.js +151 -0
  246. package/extensions/dm-subagents/src/watchdog/emission-guard.js +90 -0
  247. package/extensions/dm-subagents/src/watchdog/lsp-diagnostics.js +484 -0
  248. package/extensions/dm-subagents/src/watchdog/model-selection.js +154 -0
  249. package/extensions/dm-subagents/src/watchdog/permission-arbiter.js +138 -0
  250. package/extensions/dm-subagents/src/watchdog/register-child.js +112 -0
  251. package/extensions/dm-subagents/src/watchdog/register-main.js +419 -0
  252. package/extensions/dm-subagents/src/watchdog/render.js +54 -0
  253. package/extensions/dm-subagents/src/watchdog/review.js +251 -0
  254. package/extensions/dm-subagents/src/watchdog/runtime.js +803 -0
  255. package/extensions/dm-subagents/src/watchdog/scope.js +56 -0
  256. package/extensions/dm-subagents/src/watchdog/settings.js +515 -0
  257. package/extensions/dm-subagents/src/watchdog/tool-actions.js +151 -0
  258. package/extensions/dm-subagents/src/watchdog/turn-delta.js +169 -0
  259. package/extensions/dm-subagents/src/watchdog/types.js +29 -0
  260. package/extensions/dm-subagents/src/watchdog/warning-format.js +58 -0
  261. package/extensions/dm-subagents/src/workflows/chat-progress.js +116 -0
  262. package/extensions/dm-subagents/src/workflows/host-command.js +227 -0
  263. package/extensions/dm-subagents/src/workflows/scripted-workflow.js +2011 -0
  264. package/extensions/dm-subagents/src/workflows/workflow-child-summary.js +116 -0
  265. package/extensions/dm-subagents/src/workflows/workflow-preflight.js +243 -0
  266. package/extensions/dm-subagents/src/workflows/workflow-receipt.js +387 -0
  267. package/extensions/dm-subagents/src/workflows/workflow-settlement.js +189 -0
  268. package/package.json +4 -3
  269. package/extensions/dm-fff/package.json +0 -21
  270. package/extensions/dm-fff/src/index.js +0 -691
  271. package/extensions/dm-fff/src/query.js +0 -60
  272. package/extensions/dm-subagents/agents/context-builder.md +0 -46
  273. package/extensions/dm-subagents/agents/planner.md +0 -55
  274. package/extensions/dm-subagents/prompts/parallel-context-build.md +0 -55
  275. package/extensions/dm-subagents/prompts/parallel-handoff-plan.md +0 -61
  276. package/extensions/dm-subagents/src/runs/background/wait.js +0 -206
  277. package/extensions/dm-subagents/src/runs/foreground/chain-clarify.js +0 -1013
  278. package/extensions/dm-subagents/src/runs/foreground/chain-execution.js +0 -981
  279. package/extensions/dm-subagents/src/runs/shared/turn-budget.js +0 -50
@@ -0,0 +1,618 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { getPiSpawnCommand } from "../../runs/shared/dm-spawn.js";
4
+ import { getProjectSubagentsDir } from "../../shared/artifacts.js";
5
+ import { writeAtomicJson } from "../../shared/atomic-json.js";
6
+ import { createHerdrClient, detectHerdr } from "./client.js";
7
+ import { focusHerdrPane, herdrPaneFocusTarget, herdrPaneRecord } from "./focus.js";
8
+ import { formatShellCommand } from "./shell-command.js";
9
+ export const HERDR_PROJECT_PANE_ACTIONS = ["project.open", "project.status", "project.close"];
10
+ export const PROJECT_PANES_API_VERSION = 1;
11
+ export const PROJECT_PANE_TRUST_STATUS = "human-verification-required";
12
+ function toolResult(text, isError = false) {
13
+ return { content: [{ type: "text", text }], ...isError ? { isError: true } : {}, details: { mode: "management", results: [] } };
14
+ }
15
+ function projectPaneError(code, message, fields = {}) {
16
+ return { ok: false, error: { code, message, ...fields } };
17
+ }
18
+ function formatProjectPaneError(input) {
19
+ return `Herdr project pane error (${input.code}): ${input.message}`;
20
+ }
21
+ function projectPaneDir(projectRoot) {
22
+ return path.join(getProjectSubagentsDir(projectRoot), "project-panes");
23
+ }
24
+ export function projectPaneBindingPath(projectRoot) {
25
+ return path.join(projectPaneDir(projectRoot), "herdr.json");
26
+ }
27
+ function projectPaneRootIndexPath(ownerRoot) {
28
+ return path.join(projectPaneDir(ownerRoot), "herdr-roots.json");
29
+ }
30
+ function parseRootIndex(value) {
31
+ if (!value || typeof value !== "object" || Array.isArray(value))
32
+ throw new Error("Invalid Herdr project pane root index.");
33
+ const input = value;
34
+ if (input.schemaVersion !== 1 || input.kind !== "herdr-project-pane-roots" || !Array.isArray(input.projectRoots))
35
+ throw new Error("Invalid Herdr project pane root index.");
36
+ if (!input.projectRoots.every((root) => typeof root === "string" && root.length > 0))
37
+ throw new Error("Invalid Herdr project pane root index.");
38
+ return input.projectRoots;
39
+ }
40
+ export function listHerdrProjectPaneRoots(ownerRoot) {
41
+ try {
42
+ return parseRootIndex(JSON.parse(fs.readFileSync(projectPaneRootIndexPath(ownerRoot), "utf-8")));
43
+ } catch (error) {
44
+ const code = error.code;
45
+ if (code === "ENOENT" || code === "ENOTDIR")
46
+ return [];
47
+ throw error;
48
+ }
49
+ }
50
+ function writeHerdrProjectPaneRoot(ownerRoot, projectRoot) {
51
+ const projectRoots = [...new Set([...listHerdrProjectPaneRoots(ownerRoot), projectRoot])].sort();
52
+ writeAtomicJson(projectPaneRootIndexPath(ownerRoot), {
53
+ schemaVersion: 1,
54
+ kind: "herdr-project-pane-roots",
55
+ projectRoots
56
+ });
57
+ }
58
+ function removeHerdrProjectPaneRoot(ownerRoot, projectRoot) {
59
+ const projectRoots = listHerdrProjectPaneRoots(ownerRoot).filter((root) => root !== projectRoot);
60
+ const file = projectPaneRootIndexPath(ownerRoot);
61
+ if (projectRoots.length === 0) {
62
+ fs.rmSync(file, { force: true });
63
+ return;
64
+ }
65
+ writeAtomicJson(file, {
66
+ schemaVersion: 1,
67
+ kind: "herdr-project-pane-roots",
68
+ projectRoots
69
+ });
70
+ }
71
+ function parseBinding(value, strict = false) {
72
+ if (!value || typeof value !== "object" || Array.isArray(value))
73
+ return;
74
+ const input = value;
75
+ if (input.schemaVersion !== 1 || input.kind !== "herdr-project-pane")
76
+ return;
77
+ if (typeof input.projectRoot !== "string" || typeof input.paneId !== "string" || typeof input.openedAt !== "string" || typeof input.command !== "string")
78
+ return;
79
+ if (strict) {
80
+ if (![input.projectRoot, input.paneId, input.openedAt, input.command].every((field) => field.trim().length > 0))
81
+ return;
82
+ for (const field of ["lastFocusedAt", "herdrVersion", "startupMessage"]) {
83
+ if (input[field] !== undefined && typeof input[field] !== "string")
84
+ return;
85
+ }
86
+ }
87
+ return input;
88
+ }
89
+ function readBinding(projectRoot, strict) {
90
+ const file = projectPaneBindingPath(projectRoot);
91
+ let raw;
92
+ try {
93
+ raw = fs.readFileSync(file, "utf-8");
94
+ } catch (cause) {
95
+ const code = cause.code;
96
+ if (code === "ENOENT" || code === "ENOTDIR")
97
+ return { state: "absent" };
98
+ return { state: "read-error", cause };
99
+ }
100
+ try {
101
+ const binding = parseBinding(JSON.parse(raw), strict);
102
+ return binding ? { state: "valid", binding } : { state: "invalid" };
103
+ } catch {
104
+ return { state: "invalid" };
105
+ }
106
+ }
107
+ export function readHerdrProjectPaneBinding(projectRoot) {
108
+ const read = readBinding(projectRoot, false);
109
+ return read.state === "valid" ? read.binding : undefined;
110
+ }
111
+ export function readProjectPaneBinding(projectRoot) {
112
+ const read = readBinding(projectRoot, true);
113
+ if (read.state === "absent")
114
+ return { ok: true, data: undefined };
115
+ if (read.state === "read-error") {
116
+ const bindingPath = projectPaneBindingPath(projectRoot);
117
+ return projectPaneError("BINDING_READ_FAILED", `Failed to read project pane binding '${bindingPath}': ${read.cause instanceof Error ? read.cause.message : String(read.cause)}`, {
118
+ projectRoot,
119
+ bindingPath,
120
+ details: fileSystemErrorDetails(read.cause)
121
+ });
122
+ }
123
+ if (read.state === "invalid") {
124
+ return projectPaneError("INVALID_BINDING", `Project pane binding '${projectPaneBindingPath(projectRoot)}' is malformed.`, {
125
+ projectRoot,
126
+ bindingPath: projectPaneBindingPath(projectRoot)
127
+ });
128
+ }
129
+ return { ok: true, data: read.binding };
130
+ }
131
+ function herdrProjectPaneSnapshotFromBinding(binding, now = Date.now()) {
132
+ return {
133
+ projectRoot: binding.projectRoot,
134
+ bindingPath: projectPaneBindingPath(binding.projectRoot),
135
+ paneId: binding.paneId,
136
+ openedAt: binding.openedAt,
137
+ ...binding.lastFocusedAt ? { lastFocusedAt: binding.lastFocusedAt } : {},
138
+ state: "open",
139
+ agentStatus: "unknown",
140
+ ownership: "unknown",
141
+ safeToClose: false,
142
+ refreshedAt: now
143
+ };
144
+ }
145
+ export function restoreHerdrProjectPaneSnapshots(state, projectRoots, now = Date.now()) {
146
+ const restored = new Map(state.herdrProjectPanes ?? []);
147
+ for (const projectRoot of projectRoots) {
148
+ const binding = readHerdrProjectPaneBinding(projectRoot);
149
+ if (binding)
150
+ restored.set(binding.projectRoot, herdrProjectPaneSnapshotFromBinding(binding, now));
151
+ }
152
+ state.herdrProjectPanes = restored;
153
+ }
154
+ function sanitizedSummary(value) {
155
+ if (typeof value !== "string")
156
+ return;
157
+ const cleaned = value.replace(/[\u0000-\u001f\u007f]/g, " ").replace(/\s+/g, " ").trim();
158
+ return cleaned ? cleaned.slice(0, 120) : undefined;
159
+ }
160
+ function nestedRecord(value, key) {
161
+ if (!value || typeof value !== "object" || Array.isArray(value))
162
+ return;
163
+ const child = value[key];
164
+ return child && typeof child === "object" && !Array.isArray(child) ? child : undefined;
165
+ }
166
+ function paneSummary(pane) {
167
+ return sanitizedSummary(pane.summary) ?? sanitizedSummary(pane.state_text) ?? sanitizedSummary(pane.stateText) ?? sanitizedSummary(pane.token_summary) ?? sanitizedSummary(nestedRecord(pane, "tokens")?.summary) ?? sanitizedSummary(nestedRecord(pane, "metadata")?.summary) ?? sanitizedSummary(nestedRecord(nestedRecord(pane, "metadata"), "tokens")?.summary);
168
+ }
169
+ function projectPaneRuntime(value) {
170
+ const pane = herdrPaneRecord(value);
171
+ const focusTarget = herdrPaneFocusTarget(value);
172
+ const paneId = focusTarget.paneId;
173
+ if (!pane || !paneId)
174
+ return;
175
+ const text = (key) => typeof pane[key] === "string" ? pane[key] : undefined;
176
+ const agentStatus = text("agent_status") ?? text("agentStatus") ?? "unknown";
177
+ const summary = paneSummary(pane);
178
+ return {
179
+ paneId,
180
+ ...text("agent") ? { agent: text("agent") } : {},
181
+ agentStatus: agentStatus.toLowerCase(),
182
+ ...text("cwd") ? { cwd: text("cwd") } : {},
183
+ ...text("foreground_cwd") || text("foregroundCwd") ? { foregroundCwd: text("foreground_cwd") ?? text("foregroundCwd") } : {},
184
+ ...typeof pane.focused === "boolean" ? { focused: pane.focused } : {},
185
+ ...focusTarget.tabId ? { tabId: focusTarget.tabId } : {},
186
+ ...focusTarget.workspaceId ? { workspaceId: focusTarget.workspaceId } : {},
187
+ ...summary ? { summary } : {},
188
+ ...text("terminal_title_stripped") || text("terminal_title") || text("terminalTitle") ? { terminalTitle: text("terminal_title_stripped") ?? text("terminal_title") ?? text("terminalTitle") } : {}
189
+ };
190
+ }
191
+ function herdrProjectPaneSnapshotFromStatus(data, now) {
192
+ if (data.state === "absent" || !data.binding)
193
+ return;
194
+ return {
195
+ projectRoot: data.projectRoot,
196
+ bindingPath: data.bindingPath,
197
+ paneId: data.binding.paneId,
198
+ openedAt: data.binding.openedAt,
199
+ ...data.binding.lastFocusedAt ? { lastFocusedAt: data.binding.lastFocusedAt } : {},
200
+ state: data.state,
201
+ agentStatus: data.runtime?.agentStatus ?? "unknown",
202
+ ownership: data.ownership,
203
+ safeToClose: data.safeToClose,
204
+ refreshedAt: now,
205
+ ...data.runtime?.summary ? { summary: data.runtime.summary } : {},
206
+ ...data.runtime?.tabId ? { tabId: data.runtime.tabId } : {},
207
+ ...data.runtime?.workspaceId ? { workspaceId: data.runtime.workspaceId } : {},
208
+ ...data.runtime?.terminalTitle ? { terminalTitle: data.runtime.terminalTitle } : {},
209
+ ...data.staleReason ? { staleReason: data.staleReason.message } : {}
210
+ };
211
+ }
212
+ function rememberProjectPane(state, data, now) {
213
+ if (!state)
214
+ return;
215
+ state.herdrProjectPanes ??= new Map;
216
+ const snapshot = herdrProjectPaneSnapshotFromStatus(data, now);
217
+ if (snapshot)
218
+ state.herdrProjectPanes.set(data.projectRoot, snapshot);
219
+ else
220
+ state.herdrProjectPanes.delete(data.projectRoot);
221
+ }
222
+ function forgetProjectPane(state, projectRoot) {
223
+ state?.herdrProjectPanes?.delete(projectRoot);
224
+ }
225
+ function resolveProjectRoot(requested) {
226
+ const resolved = path.resolve(requested);
227
+ try {
228
+ const stat = fs.statSync(resolved);
229
+ if (!stat.isDirectory())
230
+ return projectPaneError("INVALID_PROJECT_ROOT", `Project pane target '${resolved}' is not a directory.`, { projectRoot: resolved });
231
+ return { ok: true, data: fs.realpathSync(resolved) };
232
+ } catch (cause) {
233
+ return projectPaneError("INVALID_PROJECT_ROOT", `Project pane target '${resolved}' is unavailable: ${cause instanceof Error ? cause.message : String(cause)}`, { projectRoot: resolved });
234
+ }
235
+ }
236
+ async function inspectPane(client, paneId, signal) {
237
+ const live = await client.run(["pane", "get", paneId], { timeoutMs: 5000, signal });
238
+ if (live.ok === false)
239
+ return projectPaneError(live.error.code, live.error.message, { details: live.error.details });
240
+ const runtime = projectPaneRuntime(live.data);
241
+ if (!runtime)
242
+ return projectPaneError("INVALID_PANE_RESPONSE", `Herdr pane get returned no pane runtime for '${paneId}'.`, { details: live.data });
243
+ return { ok: true, data: runtime };
244
+ }
245
+ function projectPaneCommand(message) {
246
+ const args = message?.trim() ? [message.trim()] : [];
247
+ const command = getPiSpawnCommand(args);
248
+ return formatShellCommand(command.command, command.args);
249
+ }
250
+ function canonicalRuntimePath(value) {
251
+ if (!value)
252
+ return;
253
+ try {
254
+ return fs.realpathSync(path.resolve(value));
255
+ } catch {
256
+ return path.resolve(value);
257
+ }
258
+ }
259
+ function projectPaneOwnership(runtime, binding, projectRoot) {
260
+ if (runtime.paneId !== binding.paneId)
261
+ return "mismatch";
262
+ const runtimeRoot = canonicalRuntimePath(runtime.cwd);
263
+ if (!runtimeRoot)
264
+ return "unknown";
265
+ return runtimeRoot === projectRoot ? "verified" : "mismatch";
266
+ }
267
+ function bindingForManager(projectRoot, legacyToolCompatibility) {
268
+ const read = readBinding(projectRoot, !legacyToolCompatibility);
269
+ if (read.state === "absent")
270
+ return { ok: true, data: undefined };
271
+ if (read.state === "read-error") {
272
+ if (legacyToolCompatibility)
273
+ return { ok: true, data: undefined };
274
+ const bindingPath = projectPaneBindingPath(projectRoot);
275
+ return projectPaneError("BINDING_READ_FAILED", `Failed to read project pane binding '${bindingPath}': ${read.cause instanceof Error ? read.cause.message : String(read.cause)}`, {
276
+ projectRoot,
277
+ bindingPath,
278
+ details: fileSystemErrorDetails(read.cause)
279
+ });
280
+ }
281
+ if (read.state === "invalid") {
282
+ if (legacyToolCompatibility)
283
+ return { ok: true, data: undefined };
284
+ return projectPaneError("INVALID_BINDING", `Project pane binding '${projectPaneBindingPath(projectRoot)}' is malformed.`, {
285
+ projectRoot,
286
+ bindingPath: projectPaneBindingPath(projectRoot)
287
+ });
288
+ }
289
+ const binding = read.binding;
290
+ if (!legacyToolCompatibility && canonicalRuntimePath(binding.projectRoot) !== projectRoot) {
291
+ return projectPaneError("INVALID_BINDING", `Project pane binding root '${binding.projectRoot}' does not match '${projectRoot}'.`, {
292
+ projectRoot,
293
+ bindingPath: projectPaneBindingPath(projectRoot),
294
+ details: binding
295
+ });
296
+ }
297
+ return { ok: true, data: binding };
298
+ }
299
+ function common(projectRoot) {
300
+ return {
301
+ apiVersion: PROJECT_PANES_API_VERSION,
302
+ projectRoot,
303
+ bindingPath: projectPaneBindingPath(projectRoot),
304
+ trust: PROJECT_PANE_TRUST_STATUS
305
+ };
306
+ }
307
+ function fileSystemErrorDetails(cause) {
308
+ if (!(cause instanceof Error))
309
+ return cause;
310
+ const code = cause.code;
311
+ return { name: cause.name, message: cause.message, ...code ? { code } : {} };
312
+ }
313
+ function removeProjectPaneBinding(projectRoot) {
314
+ const bindingPath = projectPaneBindingPath(projectRoot);
315
+ try {
316
+ fs.rmSync(bindingPath, { force: true });
317
+ return { ok: true, data: undefined };
318
+ } catch (cause) {
319
+ return projectPaneError("BINDING_REMOVE_FAILED", `Failed to remove project pane binding '${bindingPath}': ${cause instanceof Error ? cause.message : String(cause)}`, {
320
+ projectRoot,
321
+ bindingPath,
322
+ details: fileSystemErrorDetails(cause)
323
+ });
324
+ }
325
+ }
326
+ function createProjectPaneManagerInternal(options = {}) {
327
+ const client = options.client ?? createHerdrClient();
328
+ return {
329
+ async status(input) {
330
+ const root = resolveProjectRoot(input.cwd);
331
+ if (!root.ok)
332
+ return root;
333
+ const projectRoot = root.data;
334
+ const bindingResult = bindingForManager(projectRoot, options.legacyToolCompatibility);
335
+ if (!bindingResult.ok)
336
+ return bindingResult;
337
+ const existing = bindingResult.data;
338
+ if (!existing)
339
+ return { ok: true, data: { ...common(projectRoot), state: "absent", ownership: "unknown", safeToClose: true } };
340
+ const live = await inspectPane(client, existing.paneId, input.signal);
341
+ if (!live.ok) {
342
+ if (live.error.code === "INVALID_PANE_RESPONSE" && options.legacyToolCompatibility) {
343
+ const runtime = { paneId: existing.paneId, agentStatus: "unknown" };
344
+ return { ok: true, data: { ...common(projectRoot), state: "open", binding: existing, runtime, ownership: "unknown", safeToClose: false } };
345
+ }
346
+ if (live.error.code === "NOT_FOUND" || live.error.code === "PANE_GONE") {
347
+ return { ok: true, data: {
348
+ ...common(projectRoot),
349
+ state: "stale",
350
+ binding: existing,
351
+ ownership: "unknown",
352
+ safeToClose: false,
353
+ staleReason: { code: live.error.code, message: live.error.message }
354
+ } };
355
+ }
356
+ return { ok: false, error: { ...live.error, projectRoot, bindingPath: projectPaneBindingPath(projectRoot) } };
357
+ }
358
+ const ownership = projectPaneOwnership(live.data, existing, projectRoot);
359
+ return { ok: true, data: {
360
+ ...common(projectRoot),
361
+ state: "open",
362
+ binding: existing,
363
+ runtime: live.data,
364
+ ownership,
365
+ safeToClose: live.data.agentStatus === "idle" && ownership === "verified"
366
+ } };
367
+ },
368
+ async focus(input) {
369
+ const status = await this.status(input);
370
+ if (!status.ok)
371
+ return projectPaneError(status.error.code, status.error.message, status.error);
372
+ if (status.data.state !== "open" || !status.data.binding || !status.data.runtime) {
373
+ return projectPaneError("PANE_GONE", `No open Herdr project pane binding exists for '${status.data.projectRoot}'.`, {
374
+ projectRoot: status.data.projectRoot,
375
+ bindingPath: status.data.bindingPath
376
+ });
377
+ }
378
+ if (status.data.ownership !== "verified") {
379
+ return projectPaneError("PANE_OWNERSHIP_UNVERIFIED", `Project pane '${status.data.binding.paneId}' ownership is '${status.data.ownership}' for '${status.data.projectRoot}'.`, {
380
+ projectRoot: status.data.projectRoot,
381
+ bindingPath: status.data.bindingPath,
382
+ details: status.data.runtime
383
+ });
384
+ }
385
+ const focused = await focusHerdrPane(client, status.data.binding.paneId, input.signal);
386
+ if (!focused.ok)
387
+ return projectPaneError(focused.error.code, focused.error.message, {
388
+ projectRoot: status.data.projectRoot,
389
+ bindingPath: status.data.bindingPath,
390
+ details: focused.error.details
391
+ });
392
+ const now = (options.now?.() ?? new Date).toISOString();
393
+ const binding = { ...status.data.binding, lastFocusedAt: now };
394
+ try {
395
+ writeAtomicJson(status.data.bindingPath, binding);
396
+ } catch (cause) {
397
+ return projectPaneError("BINDING_WRITE_FAILED", `Failed to update project pane binding '${status.data.bindingPath}': ${cause instanceof Error ? cause.message : String(cause)}`, {
398
+ projectRoot: status.data.projectRoot,
399
+ bindingPath: status.data.bindingPath,
400
+ details: fileSystemErrorDetails(cause)
401
+ });
402
+ }
403
+ return { ok: true, data: {
404
+ ...common(status.data.projectRoot),
405
+ binding,
406
+ runtime: status.data.runtime,
407
+ ownership: status.data.ownership,
408
+ focused: focused.data
409
+ } };
410
+ },
411
+ async open(input) {
412
+ const root = resolveProjectRoot(input.cwd);
413
+ if (!root.ok)
414
+ return root;
415
+ const projectRoot = root.data;
416
+ const detected = await detectHerdr(client, input.signal);
417
+ if (!detected.ok)
418
+ return projectPaneError(detected.error.code, detected.error.message, { projectRoot, details: detected.error.details });
419
+ const bindingResult = bindingForManager(projectRoot, options.legacyToolCompatibility);
420
+ if (!bindingResult.ok)
421
+ return bindingResult;
422
+ const existing = bindingResult.data;
423
+ if (existing) {
424
+ const live = await inspectPane(client, existing.paneId, input.signal);
425
+ if (live.ok) {
426
+ const ownership = projectPaneOwnership(live.data, existing, projectRoot);
427
+ if (!options.legacyToolCompatibility && ownership !== "verified") {
428
+ return projectPaneError("PANE_OWNERSHIP_UNVERIFIED", `Project pane '${existing.paneId}' ownership is '${ownership}' for '${projectRoot}'.`, {
429
+ projectRoot,
430
+ bindingPath: projectPaneBindingPath(projectRoot),
431
+ details: live.data
432
+ });
433
+ }
434
+ return { ok: true, data: { ...common(projectRoot), disposition: "already-open", binding: existing, runtime: live.data } };
435
+ }
436
+ if (live.error.code === "INVALID_PANE_RESPONSE" && options.legacyToolCompatibility) {
437
+ return { ok: true, data: {
438
+ ...common(projectRoot),
439
+ disposition: "already-open",
440
+ binding: existing,
441
+ runtime: { paneId: existing.paneId, agentStatus: "unknown" }
442
+ } };
443
+ }
444
+ const stale = live.error.code === "NOT_FOUND" || live.error.code === "PANE_GONE";
445
+ if (!stale && !options.legacyToolCompatibility) {
446
+ return { ok: false, error: { ...live.error, projectRoot, bindingPath: projectPaneBindingPath(projectRoot) } };
447
+ }
448
+ }
449
+ const splitArgs = ["pane", "split", "--current", "--direction", "right", "--cwd", projectRoot];
450
+ splitArgs.push(input.focus === true ? "--focus" : "--no-focus");
451
+ const split = await client.run(splitArgs, { timeoutMs: 15000, signal: input.signal });
452
+ if (!split.ok)
453
+ return projectPaneError(split.error.code, split.error.message, { projectRoot, details: split.error.details });
454
+ const paneId = herdrPaneFocusTarget(split.data).paneId;
455
+ if (!paneId)
456
+ return projectPaneError(options.legacyToolCompatibility ? "PANE_GONE" : "INVALID_PANE_RESPONSE", "Herdr pane split returned no pane id.", { projectRoot, details: split.data });
457
+ const startupMessage = input.message?.trim();
458
+ const command = projectPaneCommand(startupMessage);
459
+ const started = await client.run(["pane", "run", paneId, command], { timeoutMs: 15000, signal: input.signal });
460
+ if (!started.ok) {
461
+ await client.run(["pane", "close", paneId], { timeoutMs: 5000 });
462
+ return projectPaneError(started.error.code, started.error.message, { projectRoot, details: started.error.details });
463
+ }
464
+ const now = (options.now?.() ?? new Date).toISOString();
465
+ const binding = {
466
+ schemaVersion: 1,
467
+ kind: "herdr-project-pane",
468
+ projectRoot,
469
+ paneId,
470
+ openedAt: now,
471
+ ...input.focus === true ? { lastFocusedAt: now } : {},
472
+ herdrVersion: detected.data.versionText,
473
+ command,
474
+ ...startupMessage ? { startupMessage } : {}
475
+ };
476
+ const bindingPath = projectPaneBindingPath(projectRoot);
477
+ try {
478
+ writeAtomicJson(bindingPath, binding);
479
+ } catch (cause) {
480
+ let cleanup;
481
+ try {
482
+ const closed = await client.run(["pane", "close", paneId], { timeoutMs: 5000 });
483
+ cleanup = closed.ok ? { paneClosed: true } : { paneClosed: false, error: closed.error };
484
+ } catch (cleanupCause) {
485
+ cleanup = { paneClosed: false, error: fileSystemErrorDetails(cleanupCause) };
486
+ }
487
+ const cleanupMessage = cleanup.paneClosed ? ` The newly opened pane '${paneId}' was closed.` : ` Cleanup could not close the newly opened pane '${paneId}'.`;
488
+ return projectPaneError("BINDING_WRITE_FAILED", `Failed to persist project pane binding '${bindingPath}': ${cause instanceof Error ? cause.message : String(cause)}.${cleanupMessage}`, {
489
+ projectRoot,
490
+ bindingPath,
491
+ details: { cause: fileSystemErrorDetails(cause), cleanup }
492
+ });
493
+ }
494
+ return { ok: true, data: { ...common(projectRoot), disposition: "opened", binding } };
495
+ },
496
+ async close(input) {
497
+ const root = resolveProjectRoot(input.cwd);
498
+ if (!root.ok)
499
+ return root;
500
+ const projectRoot = root.data;
501
+ const bindingResult = bindingForManager(projectRoot, options.legacyToolCompatibility);
502
+ if (!bindingResult.ok)
503
+ return bindingResult;
504
+ const existing = bindingResult.data;
505
+ if (!existing)
506
+ return { ok: true, data: { ...common(projectRoot), disposition: "absent" } };
507
+ const live = await inspectPane(client, existing.paneId, input.signal);
508
+ if (!live.ok) {
509
+ if (live.error.code === "NOT_FOUND" || live.error.code === "PANE_GONE") {
510
+ const removed = removeProjectPaneBinding(projectRoot);
511
+ if (!removed.ok)
512
+ return removed;
513
+ return { ok: true, data: { ...common(projectRoot), disposition: "stale-binding-removed", binding: existing } };
514
+ }
515
+ return { ok: false, error: { ...live.error, projectRoot, bindingPath: projectPaneBindingPath(projectRoot) } };
516
+ }
517
+ const runtime = live.data;
518
+ const ownership = projectPaneOwnership(runtime, existing, projectRoot);
519
+ if (ownership !== "verified") {
520
+ return projectPaneError("PANE_OWNERSHIP_UNVERIFIED", `Project pane '${existing.paneId}' ownership is '${ownership}' for '${projectRoot}'.`, {
521
+ projectRoot,
522
+ bindingPath: projectPaneBindingPath(projectRoot),
523
+ details: runtime
524
+ });
525
+ }
526
+ if (runtime.agentStatus !== "idle") {
527
+ return projectPaneError("PANE_NOT_IDLE", `Project pane '${existing.paneId}' is '${runtime.agentStatus}', not explicitly idle.`, {
528
+ projectRoot,
529
+ bindingPath: projectPaneBindingPath(projectRoot),
530
+ details: runtime
531
+ });
532
+ }
533
+ const closed = await client.run(["pane", "close", existing.paneId], { timeoutMs: 1e4, signal: input.signal });
534
+ if (!closed.ok && closed.error.code !== "NOT_FOUND" && closed.error.code !== "PANE_GONE") {
535
+ return projectPaneError(closed.error.code, closed.error.message, { projectRoot, bindingPath: projectPaneBindingPath(projectRoot), details: closed.error.details });
536
+ }
537
+ const disposition = closed.ok ? "closed" : "stale-binding-removed";
538
+ const removed = removeProjectPaneBinding(projectRoot);
539
+ if (!removed.ok)
540
+ return removed;
541
+ return { ok: true, data: { ...common(projectRoot), disposition, binding: existing, ...runtime ? { runtime } : {} } };
542
+ }
543
+ };
544
+ }
545
+ export function createProjectPaneManager(options = {}) {
546
+ return createProjectPaneManagerInternal(options);
547
+ }
548
+ export async function openProjectPane(options) {
549
+ return createProjectPaneManager().open(options);
550
+ }
551
+ export async function getProjectPaneStatus(options) {
552
+ return createProjectPaneManager().status(options);
553
+ }
554
+ export async function closeProjectPane(options) {
555
+ return createProjectPaneManager().close(options);
556
+ }
557
+ export async function focusProjectPane(options) {
558
+ return createProjectPaneManager().focus(options);
559
+ }
560
+ export async function handleHerdrProjectPaneAction(action, params, deps) {
561
+ const requested = params.cwd?.trim() || deps.cwd;
562
+ const ownerRoot = path.resolve(deps.cwd);
563
+ const manager = createProjectPaneManagerInternal({ client: deps.client, now: deps.now, legacyToolCompatibility: true });
564
+ const remember = (data) => rememberProjectPane(deps.state, data, deps.now?.().getTime() ?? Date.now());
565
+ if (action === "project.status") {
566
+ const status = await manager.status({ cwd: requested, signal: deps.signal });
567
+ if (!status.ok)
568
+ return toolResult(formatProjectPaneError(status.error), true);
569
+ remember(status.data);
570
+ if (status.data.state === "absent")
571
+ return toolResult(`No Herdr project pane binding exists for ${status.data.projectRoot}.`);
572
+ if (status.data.state === "stale") {
573
+ const reason = status.data.staleReason;
574
+ return toolResult(`${formatProjectPaneError(reason)}
575
+ Binding: ${status.data.bindingPath}`, true);
576
+ }
577
+ return toolResult(`Herdr project pane ${status.data.binding.paneId} is open for ${status.data.projectRoot}.
578
+ Binding: ${status.data.bindingPath}`);
579
+ }
580
+ if (action === "project.close") {
581
+ const closed = await manager.close({ cwd: requested, requireIdle: true, signal: deps.signal });
582
+ if (!closed.ok)
583
+ return toolResult(formatProjectPaneError(closed.error), true);
584
+ forgetProjectPane(deps.state, closed.data.projectRoot);
585
+ try {
586
+ removeHerdrProjectPaneRoot(ownerRoot, closed.data.projectRoot);
587
+ } catch (error) {
588
+ return toolResult(`Herdr project pane error (BINDING_REMOVE_FAILED): Failed to remove project pane root index '${projectPaneRootIndexPath(ownerRoot)}': ${error instanceof Error ? error.message : String(error)}`, true);
589
+ }
590
+ if (closed.data.disposition === "absent")
591
+ return toolResult(`No Herdr project pane binding exists for ${closed.data.projectRoot}.`);
592
+ return toolResult(`Closed Herdr project pane ${closed.data.binding.paneId} for ${closed.data.projectRoot}.`);
593
+ }
594
+ const opened = await manager.open({ cwd: requested, message: params.message, focus: params.focus, signal: deps.signal });
595
+ if (!opened.ok)
596
+ return toolResult(formatProjectPaneError(opened.error), true);
597
+ try {
598
+ writeHerdrProjectPaneRoot(ownerRoot, opened.data.projectRoot);
599
+ } catch (error) {
600
+ return toolResult(`Herdr project pane error (BINDING_WRITE_FAILED): Failed to persist project pane root index '${projectPaneRootIndexPath(ownerRoot)}': ${error instanceof Error ? error.message : String(error)}`, true);
601
+ }
602
+ const status = await manager.status({ cwd: opened.data.projectRoot, signal: deps.signal });
603
+ if (status.ok)
604
+ remember(status.data);
605
+ if (opened.data.disposition === "already-open") {
606
+ if (params.focus) {
607
+ const focused = await manager.focus({ cwd: opened.data.projectRoot, signal: deps.signal });
608
+ if (!focused.ok)
609
+ return toolResult(`Herdr project pane ${opened.data.binding.paneId} is already open for ${opened.data.projectRoot}. ${formatProjectPaneError(focused.error)}`, true);
610
+ const focusedStatus = await manager.status({ cwd: opened.data.projectRoot, signal: deps.signal });
611
+ if (focusedStatus.ok)
612
+ remember(focusedStatus.data);
613
+ return toolResult(`Herdr project pane ${opened.data.binding.paneId} is already open for ${opened.data.projectRoot}. Focused ${focused.data.focused.tabId ? `tab ${focused.data.focused.tabId}` : `workspace ${focused.data.focused.workspaceId}`}.`);
614
+ }
615
+ return toolResult(`Herdr project pane ${opened.data.binding.paneId} is already open for ${opened.data.projectRoot}.`);
616
+ }
617
+ return toolResult(`Opened Herdr project pane ${opened.data.binding.paneId} for ${opened.data.projectRoot}. The pane runs its own DM session; subagents launched there belong to that project.`);
618
+ }
@@ -0,0 +1,21 @@
1
+ export function encodeSessionRoots(roots) {
2
+ return Buffer.from(JSON.stringify(roots), "utf-8").toString("base64");
3
+ }
4
+ function parseStringArray(value) {
5
+ if (!Array.isArray(value) || value.some((root) => typeof root !== "string"))
6
+ return;
7
+ return value;
8
+ }
9
+ export function decodeSessionRoots(raw) {
10
+ try {
11
+ const decoded = parseStringArray(JSON.parse(Buffer.from(raw, "base64").toString("utf-8")));
12
+ if (decoded)
13
+ return decoded;
14
+ } catch {}
15
+ try {
16
+ const parsed = parseStringArray(JSON.parse(raw));
17
+ if (parsed)
18
+ return parsed;
19
+ } catch {}
20
+ throw new Error("--session-roots must be a base64-encoded or raw JSON array of strings.");
21
+ }
@@ -0,0 +1,15 @@
1
+ function shellQuote(value, platform) {
2
+ if (platform === "win32")
3
+ return `"${value.replaceAll('"', "\\\"")}"`;
4
+ return `'${value.replaceAll("'", "'\\''")}'`;
5
+ }
6
+ function isBareExecutable(value) {
7
+ return /^[\w./@:-]+$/.test(value);
8
+ }
9
+ export function formatShellCommand(exe, args, platform = process.platform) {
10
+ const quotedArgs = args.map((arg) => shellQuote(arg, platform));
11
+ if (platform === "win32")
12
+ return `& ${[shellQuote(exe, platform), ...quotedArgs].join(" ")}`;
13
+ const invocation = isBareExecutable(exe) ? exe : `sh -c 'exec "$0" "$@"' ${shellQuote(exe, platform)}`;
14
+ return [invocation, ...quotedArgs].join(" ");
15
+ }