@pugi/cli 0.1.0-beta.9 → 0.1.0-beta.91

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 (411) hide show
  1. package/CHANGELOG.md +132 -0
  2. package/LICENSE +1 -1
  3. package/assets/pugi-prozr2-mascot.ansi +9 -0
  4. package/bin/run.js +33 -1
  5. package/dist/commands/deploy.js +40 -40
  6. package/dist/commands/flatten.js +191 -0
  7. package/dist/commands/jobs-watch.js +201 -0
  8. package/dist/commands/jobs.js +42 -27
  9. package/dist/commands/smoke.js +133 -0
  10. package/dist/core/agent-progress/cleanup.js +134 -0
  11. package/dist/core/agent-progress/schema.js +144 -0
  12. package/dist/core/agent-progress/writer.js +101 -0
  13. package/dist/core/agents/adaptive-router.js +330 -0
  14. package/dist/core/agents/query-decomposer.js +297 -0
  15. package/dist/core/agents/registry.js +3 -3
  16. package/dist/core/approvals/shortcut-resolver.js +98 -0
  17. package/dist/core/artifact-chain/dispatcher.js +148 -0
  18. package/dist/core/artifact-chain/exporter.js +164 -0
  19. package/dist/core/artifact-chain/state.js +243 -0
  20. package/dist/core/artifact-chain/steps.js +169 -0
  21. package/dist/core/ask-user/question.js +92 -0
  22. package/dist/core/audit/audit-trail.js +275 -0
  23. package/dist/core/auth/ensure-authenticated.js +129 -0
  24. package/dist/core/auth/env-provider.js +238 -0
  25. package/dist/core/auto-open-browser.js +4 -4
  26. package/dist/core/auto-update/channels.js +122 -0
  27. package/dist/core/auto-update/checker.js +241 -0
  28. package/dist/core/auto-update/state.js +235 -0
  29. package/dist/core/bare-mode/index.js +107 -0
  30. package/dist/core/bash/redirect.js +281 -0
  31. package/dist/core/bash-classifier.js +436 -40
  32. package/dist/core/checkpoint/resumer.js +149 -0
  33. package/dist/core/checkpoint/rewinder.js +291 -0
  34. package/dist/core/checkpoints/shadow-git.js +670 -0
  35. package/dist/core/citations/parser.js +109 -0
  36. package/dist/core/classifier/yolo-classifier.js +88 -0
  37. package/dist/core/codegraph/decision-store.js +248 -0
  38. package/dist/core/codegraph/detect-repo.js +459 -0
  39. package/dist/core/codegraph/install.js +134 -0
  40. package/dist/core/codegraph/offer-hook.js +220 -0
  41. package/dist/core/compact/auto-trigger.js +96 -0
  42. package/dist/core/compact/buffer-rewriter.js +115 -0
  43. package/dist/core/compact/summarizer.js +208 -0
  44. package/dist/core/compact/token-counter.js +108 -0
  45. package/dist/core/consensus/anvil-fanout.js +25 -25
  46. package/dist/core/consensus/diff-capture.js +121 -12
  47. package/dist/core/consensus/rubric.js +21 -21
  48. package/dist/core/context/builder.js +6 -6
  49. package/dist/core/context/compaction-events.js +8 -8
  50. package/dist/core/context/compaction.js +31 -31
  51. package/dist/core/context/index.js +15 -8
  52. package/dist/core/context/invariants.js +51 -51
  53. package/dist/core/context/markdown-loader.js +28 -10
  54. package/dist/core/context/markdown-traverse.js +255 -0
  55. package/dist/core/context/pugiignore.js +41 -41
  56. package/dist/core/context/repo-skeleton.js +37 -37
  57. package/dist/core/context/tool-eviction.js +55 -0
  58. package/dist/core/context/watcher.js +32 -32
  59. package/dist/core/context/working-set.js +23 -23
  60. package/dist/core/coordinator/agent-tools.js +77 -0
  61. package/dist/core/coordinator/agent-toolset.js +65 -0
  62. package/dist/core/coordinator/fsm.js +73 -0
  63. package/dist/core/coordinator/mode-fsm.js +70 -0
  64. package/dist/core/cost/rate-card.js +129 -0
  65. package/dist/core/cost/tracker.js +221 -0
  66. package/dist/core/credentials.js +13 -13
  67. package/dist/core/cron/scheduler.js +138 -0
  68. package/dist/core/denial-tracking/index.js +8 -0
  69. package/dist/core/denial-tracking/state.js +264 -0
  70. package/dist/core/diagnostics/probe-runner.js +93 -0
  71. package/dist/core/diagnostics/probes/api.js +46 -0
  72. package/dist/core/diagnostics/probes/auth.js +93 -0
  73. package/dist/core/diagnostics/probes/bare-mode.js +42 -0
  74. package/dist/core/diagnostics/probes/cli-version.js +127 -0
  75. package/dist/core/diagnostics/probes/config.js +72 -0
  76. package/dist/core/diagnostics/probes/denial-tracking.js +57 -0
  77. package/dist/core/diagnostics/probes/disk.js +81 -0
  78. package/dist/core/diagnostics/probes/engine-live.js +46 -0
  79. package/dist/core/diagnostics/probes/git.js +65 -0
  80. package/dist/core/diagnostics/probes/hooks.js +118 -0
  81. package/dist/core/diagnostics/probes/mcp.js +75 -0
  82. package/dist/core/diagnostics/probes/node.js +59 -0
  83. package/dist/core/diagnostics/probes/pnpm.js +36 -0
  84. package/dist/core/diagnostics/probes/pugi-md.js +89 -0
  85. package/dist/core/diagnostics/probes/sandbox.js +40 -0
  86. package/dist/core/diagnostics/probes/session.js +74 -0
  87. package/dist/core/diagnostics/probes/status-snapshot.js +488 -0
  88. package/dist/core/diagnostics/probes/workspace.js +63 -0
  89. package/dist/core/diagnostics/types.js +70 -0
  90. package/dist/core/dispatch/cache-cleanup.js +197 -0
  91. package/dist/core/dispatch/cache-handoff.js +295 -0
  92. package/dist/core/edits/apply-patch-layer-e.js +189 -0
  93. package/dist/core/edits/dispatch.js +333 -7
  94. package/dist/core/edits/format-detector.js +260 -0
  95. package/dist/core/edits/format-matrix.js +26 -0
  96. package/dist/core/edits/fuzzy-ladder.js +650 -0
  97. package/dist/core/edits/index.js +5 -1
  98. package/dist/core/edits/journal.js +199 -0
  99. package/dist/core/edits/layer-a-apply.js +15 -15
  100. package/dist/core/edits/layer-a-fuzzy-apply.js +198 -0
  101. package/dist/core/edits/layer-b-apply.js +9 -9
  102. package/dist/core/edits/layer-c-apply.js +6 -6
  103. package/dist/core/edits/layer-d-ast.js +557 -14
  104. package/dist/core/edits/marker-parser.js +12 -12
  105. package/dist/core/edits/security-gate.js +27 -27
  106. package/dist/core/edits/verify-hook.js +273 -0
  107. package/dist/core/edits/worktree.js +29 -29
  108. package/dist/core/engine/anvil-client.js +214 -26
  109. package/dist/core/engine/auto-compact.js +179 -0
  110. package/dist/core/engine/budgets.js +186 -0
  111. package/dist/core/engine/context-prefix.js +155 -0
  112. package/dist/core/engine/index.js +1 -1
  113. package/dist/core/engine/intensity.js +158 -0
  114. package/dist/core/engine/intent.js +260 -0
  115. package/dist/core/engine/native-pugi.js +1295 -227
  116. package/dist/core/engine/prompts.js +129 -19
  117. package/dist/core/engine/strip-internal-fields.js +124 -0
  118. package/dist/core/engine/tool-bridge.js +1792 -59
  119. package/dist/core/evaluation/golden-dataset.js +293 -0
  120. package/dist/core/feedback/queue.js +177 -0
  121. package/dist/core/feedback/submitter.js +145 -0
  122. package/dist/core/file-cache.js +113 -1
  123. package/dist/core/flatten/flatten-repo.js +439 -0
  124. package/dist/core/format/osc8-link.js +28 -0
  125. package/dist/core/hook-chains.js +392 -0
  126. package/dist/core/hooks/citation-verify-hook.js +138 -0
  127. package/dist/core/hooks/citation-verify.js +112 -0
  128. package/dist/core/hooks/events.js +46 -0
  129. package/dist/core/hooks/index.js +15 -0
  130. package/dist/core/hooks/registry.js +216 -0
  131. package/dist/core/hooks/runner.js +236 -0
  132. package/dist/core/hooks/v2/event-emitter.js +115 -0
  133. package/dist/core/hooks/v2/executor.js +282 -0
  134. package/dist/core/hooks/v2/index.js +25 -0
  135. package/dist/core/hooks/v2/lifecycle.js +104 -0
  136. package/dist/core/hooks/v2/loader.js +216 -0
  137. package/dist/core/hooks/v2/matcher.js +125 -0
  138. package/dist/core/hooks/v2/trust.js +143 -0
  139. package/dist/core/hooks/v2/types.js +86 -0
  140. package/dist/core/hooks/worktree-events.js +158 -0
  141. package/dist/core/image/renderer.js +71 -0
  142. package/dist/core/init/detector.js +582 -0
  143. package/dist/core/init/template-renderer.js +242 -0
  144. package/dist/core/jobs/registry.js +18 -18
  145. package/dist/core/ledger/results-tsv.js +142 -0
  146. package/dist/core/log-discipline/stdout-redirect.js +51 -0
  147. package/dist/core/lsp/cache.js +105 -0
  148. package/dist/core/lsp/client.js +551 -41
  149. package/dist/core/lsp/language-detect.js +66 -0
  150. package/dist/core/lsp/post-edit-diagnostics.js +171 -0
  151. package/dist/core/lsp/server-detect.js +173 -0
  152. package/dist/core/lsp/symbol-cache.js +162 -0
  153. package/dist/core/lsp/symbol-tools.js +664 -0
  154. package/dist/core/mcp/client.js +97 -28
  155. package/dist/core/mcp/http-server.js +553 -0
  156. package/dist/core/mcp/orchestrator-tools.js +662 -0
  157. package/dist/core/mcp/permission.js +190 -0
  158. package/dist/core/mcp/registry.js +39 -17
  159. package/dist/core/mcp/server-tools.js +219 -0
  160. package/dist/core/mcp/server.js +397 -0
  161. package/dist/core/mcp/trust.js +10 -10
  162. package/dist/core/memory/dual-write.js +416 -0
  163. package/dist/core/memory/passive-extract.js +130 -0
  164. package/dist/core/memory/phase1-kinds.js +20 -0
  165. package/dist/core/memory/secret-scanner.js +304 -0
  166. package/dist/core/memory-sync/queue.js +170 -0
  167. package/dist/core/metrics/extract.js +113 -0
  168. package/dist/core/modes/roo-modes.js +68 -0
  169. package/dist/core/onboarding/ensure-initialized.js +133 -0
  170. package/dist/core/onboarding/marker.js +111 -0
  171. package/dist/core/onboarding/telemetry-state.js +108 -0
  172. package/dist/core/output-style/presets.js +176 -0
  173. package/dist/core/output-style/state.js +185 -0
  174. package/dist/core/path-security.js +287 -5
  175. package/dist/core/permission.js +82 -22
  176. package/dist/core/permissions/auto-classifier.js +124 -0
  177. package/dist/core/permissions/bash-parser.js +371 -0
  178. package/dist/core/permissions/circuit-breaker.js +83 -0
  179. package/dist/core/permissions/constrained-edit.js +91 -0
  180. package/dist/core/permissions/gate.js +278 -0
  181. package/dist/core/permissions/index.js +20 -0
  182. package/dist/core/permissions/mode.js +174 -0
  183. package/dist/core/permissions/network-egress.js +137 -0
  184. package/dist/core/permissions/state.js +241 -0
  185. package/dist/core/permissions/tool-class.js +93 -0
  186. package/dist/core/plan-mode/ui-state.js +51 -0
  187. package/dist/core/plans/plan-artifact.js +721 -0
  188. package/dist/core/policy-limits/etag-store.js +122 -0
  189. package/dist/core/prd-check/parser.js +215 -0
  190. package/dist/core/prd-check/reporter.js +127 -0
  191. package/dist/core/prd-check/session-review.js +557 -0
  192. package/dist/core/prd-check/verifiers.js +223 -0
  193. package/dist/core/prompt-cache/client-cache.js +99 -0
  194. package/dist/core/prompts/assembly.js +29 -0
  195. package/dist/core/prompts/registry.js +364 -0
  196. package/dist/core/pugi-md/cc-compat-rules.js +735 -0
  197. package/dist/core/pugi-md/context-injector.js +76 -0
  198. package/dist/core/pugi-md/walk-up.js +207 -0
  199. package/dist/core/python/uv-installer.js +270 -0
  200. package/dist/core/python/uv-resolver.js +83 -0
  201. package/dist/core/rate-limit/narrator.js +146 -0
  202. package/dist/core/recipes/cli-types.js +20 -0
  203. package/dist/core/recipes/loader.js +103 -0
  204. package/dist/core/recipes/runner.js +345 -0
  205. package/dist/core/recipes/schema.js +587 -0
  206. package/dist/core/release-notes/parser.js +241 -0
  207. package/dist/core/release-notes/state.js +116 -0
  208. package/dist/core/repl/ask.js +37 -37
  209. package/dist/core/repl/cancellation.js +26 -26
  210. package/dist/core/repl/cap-warning.js +4 -4
  211. package/dist/core/repl/clipboard-read.js +11 -11
  212. package/dist/core/repl/dispatch-fsm.js +12 -12
  213. package/dist/core/repl/history-search.js +15 -15
  214. package/dist/core/repl/history.js +28 -18
  215. package/dist/core/repl/kill-ring.js +5 -5
  216. package/dist/core/repl/model-pricing.js +135 -0
  217. package/dist/core/repl/privacy-banner.js +22 -22
  218. package/dist/core/repl/session.js +2148 -217
  219. package/dist/core/repl/slash-commands.js +501 -41
  220. package/dist/core/repl/store/index.js +1 -1
  221. package/dist/core/repl/store/jsonl-log.js +22 -22
  222. package/dist/core/repl/store/lockfile.js +10 -10
  223. package/dist/core/repl/store/session-store.js +136 -107
  224. package/dist/core/repl/store/types.js +15 -15
  225. package/dist/core/repl/store/uuid-v7.js +12 -12
  226. package/dist/core/repl/workspace-context.js +43 -21
  227. package/dist/core/repo-map/build.js +125 -0
  228. package/dist/core/repo-map/cache.js +185 -0
  229. package/dist/core/repo-map/extractor.js +254 -0
  230. package/dist/core/repo-map/formatter.js +145 -0
  231. package/dist/core/repo-map/page-rank.js +105 -0
  232. package/dist/core/repo-map/scanner.js +211 -0
  233. package/dist/core/retry-budget/budget.js +284 -0
  234. package/dist/core/retry-budget/index.js +5 -0
  235. package/dist/core/retry-budget/retry-cap.js +74 -0
  236. package/dist/core/routing/lead-worker.js +43 -0
  237. package/dist/core/routing/pre-flight-estimator.js +108 -0
  238. package/dist/core/runs/run-tree.js +103 -0
  239. package/dist/core/security/injection-scanner.js +367 -0
  240. package/dist/core/security/output-filter.js +418 -0
  241. package/dist/core/session/env-file.js +105 -0
  242. package/dist/core/session/section-budgets.js +140 -0
  243. package/dist/core/session.js +92 -0
  244. package/dist/core/settings.js +324 -5
  245. package/dist/core/share/formatter.js +271 -0
  246. package/dist/core/share/redactor.js +221 -0
  247. package/dist/core/share/uploader.js +267 -0
  248. package/dist/core/skills/defaults.js +30 -30
  249. package/dist/core/skills/loader.js +22 -22
  250. package/dist/core/skills/sources.js +27 -27
  251. package/dist/core/smoke/headless-driver.js +174 -0
  252. package/dist/core/smoke/orchestrator.js +194 -0
  253. package/dist/core/smoke/runner.js +238 -0
  254. package/dist/core/smoke/scenario-parser.js +316 -0
  255. package/dist/core/statusline.js +99 -0
  256. package/dist/core/subagents/dispatcher-real.js +600 -0
  257. package/dist/core/subagents/dispatcher.js +132 -43
  258. package/dist/core/subagents/index.js +19 -6
  259. package/dist/core/subagents/isolation-matrix.js +213 -0
  260. package/dist/core/subagents/spawn.js +19 -4
  261. package/dist/core/telemetry/emitter.js +229 -0
  262. package/dist/core/telemetry/queue.js +251 -0
  263. package/dist/core/theme/context.js +91 -0
  264. package/dist/core/theme/presets.js +228 -0
  265. package/dist/core/theme/state.js +181 -0
  266. package/dist/core/todos/invariant.js +10 -0
  267. package/dist/core/todos/state.js +177 -0
  268. package/dist/core/tool-schema/compressor.js +89 -0
  269. package/dist/core/transport/version-interceptor.js +166 -0
  270. package/dist/core/trust.js +2 -2
  271. package/dist/core/tui/thinking-block.js +64 -0
  272. package/dist/core/vim/keymap.js +288 -0
  273. package/dist/core/vim/state.js +92 -0
  274. package/dist/core/watch-markers/marker-watcher.js +133 -0
  275. package/dist/core/worktree/include-parser.js +249 -0
  276. package/dist/core/worktree-manager/cleanup.js +123 -0
  277. package/dist/core/worktree-manager/manager.js +303 -0
  278. package/dist/index.js +36 -0
  279. package/dist/runtime/bootstrap.js +190 -0
  280. package/dist/runtime/cli.js +4185 -549
  281. package/dist/runtime/commands/agents.js +31 -31
  282. package/dist/runtime/commands/budget.js +5 -5
  283. package/dist/runtime/commands/cancel.js +231 -0
  284. package/dist/runtime/commands/chain.js +489 -0
  285. package/dist/runtime/commands/codegraph-status.js +227 -0
  286. package/dist/runtime/commands/compact.js +297 -0
  287. package/dist/runtime/commands/config.js +73 -39
  288. package/dist/runtime/commands/cost.js +199 -0
  289. package/dist/runtime/commands/delegate.js +27 -4
  290. package/dist/runtime/commands/dispatch.js +126 -0
  291. package/dist/runtime/commands/doctor.js +579 -0
  292. package/dist/runtime/commands/feedback.js +184 -0
  293. package/dist/runtime/commands/hooks.js +187 -0
  294. package/dist/runtime/commands/init.js +254 -0
  295. package/dist/runtime/commands/lsp.js +200 -38
  296. package/dist/runtime/commands/mcp.js +879 -0
  297. package/dist/runtime/commands/memory.js +582 -0
  298. package/dist/runtime/commands/model.js +237 -0
  299. package/dist/runtime/commands/onboarding.js +275 -0
  300. package/dist/runtime/commands/patch.js +12 -12
  301. package/dist/runtime/commands/permissions.js +112 -0
  302. package/dist/runtime/commands/plan.js +143 -0
  303. package/dist/runtime/commands/prd-check.js +285 -0
  304. package/dist/runtime/commands/privacy.js +17 -17
  305. package/dist/runtime/commands/recipe.js +325 -0
  306. package/dist/runtime/commands/redo-blob-store.js +92 -0
  307. package/dist/runtime/commands/redo.js +361 -0
  308. package/dist/runtime/commands/release-notes.js +229 -0
  309. package/dist/runtime/commands/repo-map.js +95 -0
  310. package/dist/runtime/commands/report.js +299 -0
  311. package/dist/runtime/commands/resume.js +118 -0
  312. package/dist/runtime/commands/review-consensus.js +68 -53
  313. package/dist/runtime/commands/rewind.js +333 -0
  314. package/dist/runtime/commands/roster.js +14 -14
  315. package/dist/runtime/commands/sessions.js +163 -0
  316. package/dist/runtime/commands/share.js +316 -0
  317. package/dist/runtime/commands/skills.js +31 -31
  318. package/dist/runtime/commands/status.js +186 -0
  319. package/dist/runtime/commands/stickers.js +82 -0
  320. package/dist/runtime/commands/style.js +194 -0
  321. package/dist/runtime/commands/theme.js +196 -0
  322. package/dist/runtime/commands/undo.js +54 -22
  323. package/dist/runtime/commands/update.js +289 -0
  324. package/dist/runtime/commands/vim.js +140 -0
  325. package/dist/runtime/commands/worktree.js +8 -8
  326. package/dist/runtime/commands/worktrees.js +155 -0
  327. package/dist/runtime/headless-repl.js +195 -0
  328. package/dist/runtime/headless.js +543 -0
  329. package/dist/runtime/load-hooks-or-exit.js +71 -0
  330. package/dist/runtime/plan-decompose.js +22 -22
  331. package/dist/runtime/sigint-guard.js +272 -0
  332. package/dist/runtime/update-check.js +28 -28
  333. package/dist/runtime/version.js +65 -0
  334. package/dist/runtime/worktree-bootstrap.js +579 -0
  335. package/dist/skills/bundled/batch.js +617 -0
  336. package/dist/skills/bundled/index.js +45 -0
  337. package/dist/skills/bundled/loop.js +358 -0
  338. package/dist/skills/bundled/remember.js +383 -0
  339. package/dist/skills/bundled/simplify.js +289 -0
  340. package/dist/skills/bundled/skillify.js +373 -0
  341. package/dist/skills/bundled/stuck.js +558 -0
  342. package/dist/skills/bundled/verify.js +439 -0
  343. package/dist/testing/vcr.js +486 -0
  344. package/dist/tools/agent-tool.js +229 -0
  345. package/dist/tools/apply-patch.js +89 -28
  346. package/dist/tools/ask-user-question.js +337 -0
  347. package/dist/tools/ask-user.js +115 -0
  348. package/dist/tools/bash.js +624 -46
  349. package/dist/tools/brief.js +224 -0
  350. package/dist/tools/cron.js +433 -0
  351. package/dist/tools/enter-worktree.js +250 -0
  352. package/dist/tools/exit-worktree.js +147 -0
  353. package/dist/tools/file-tools.js +161 -44
  354. package/dist/tools/lsp-tools.js +377 -1
  355. package/dist/tools/mcp-tool.js +260 -0
  356. package/dist/tools/multi-edit.js +361 -0
  357. package/dist/tools/powershell.js +268 -0
  358. package/dist/tools/registry.js +99 -4
  359. package/dist/tools/skill-tool.js +96 -0
  360. package/dist/tools/sleep.js +99 -0
  361. package/dist/tools/synthetic-output.js +133 -0
  362. package/dist/tools/tasks.js +208 -0
  363. package/dist/tools/todo-write.js +184 -0
  364. package/dist/tools/verify-plan-execution.js +295 -0
  365. package/dist/tools/web-fetch-injection-scanner.js +207 -0
  366. package/dist/tools/web-fetch.js +195 -10
  367. package/dist/tools/web-search.js +458 -0
  368. package/dist/tui/agent-progress-card.js +111 -0
  369. package/dist/tui/agent-tree.js +11 -1
  370. package/dist/tui/ask-modal.js +14 -14
  371. package/dist/tui/ask-user-question-chips.js +315 -0
  372. package/dist/tui/ask-user-question-prompt.js +203 -0
  373. package/dist/tui/compact-banner.js +81 -0
  374. package/dist/tui/conversation-pane.js +85 -11
  375. package/dist/tui/cost-table.js +111 -0
  376. package/dist/tui/device-flow.js +2 -2
  377. package/dist/tui/doctor-table.js +46 -0
  378. package/dist/tui/feedback-prompt.js +156 -0
  379. package/dist/tui/input-box.js +247 -32
  380. package/dist/tui/login-picker.js +3 -3
  381. package/dist/tui/markdown-render.js +6 -6
  382. package/dist/tui/multi-file-diff-approval.js +375 -0
  383. package/dist/tui/onboarding-wizard.js +240 -0
  384. package/dist/tui/permissions-picker.js +86 -0
  385. package/dist/tui/render.js +36 -1
  386. package/dist/tui/repl-render.js +176 -25
  387. package/dist/tui/repl-splash-art.js +16 -16
  388. package/dist/tui/repl-splash-mascot.js +48 -24
  389. package/dist/tui/repl-splash.js +22 -22
  390. package/dist/tui/repl.js +125 -45
  391. package/dist/tui/slash-palette.js +6 -6
  392. package/dist/tui/splash.js +2 -2
  393. package/dist/tui/status-bar.js +109 -31
  394. package/dist/tui/status-table.js +7 -0
  395. package/dist/tui/stickers-art.js +136 -0
  396. package/dist/tui/style-table.js +28 -0
  397. package/dist/tui/theme-table.js +29 -0
  398. package/dist/tui/thinking-spinner.js +123 -0
  399. package/dist/tui/tool-stream-pane.js +53 -4
  400. package/dist/tui/update-banner.js +27 -2
  401. package/dist/tui/vim-input.js +267 -0
  402. package/dist/tui/welcome-banner.js +107 -0
  403. package/dist/tui/welcome-data.js +293 -0
  404. package/dist/tui/workspace-context.js +2 -2
  405. package/package.json +31 -16
  406. package/test/scenarios/codegen-create-file.scenario.txt +13 -0
  407. package/test/scenarios/compact-force.scenario.txt +12 -0
  408. package/test/scenarios/identity.scenario.txt +12 -0
  409. package/test/scenarios/persona-handoff.scenario.txt +12 -0
  410. package/test/scenarios/walkback.scenario.txt +12 -0
  411. package/dist/core/engine/compaction-hook.js +0 -154
@@ -0,0 +1,600 @@
1
+ /**
2
+ * Real subagent execution (β2 S1 —).
3
+ *
4
+ * Replaces the M1 stub in `dispatcher.ts` with a genuine Anvil-backed
5
+ * child engine loop. Given a SubagentTask + an EngineLoopClient, this
6
+ * module:
7
+ *
8
+ * 1. Resolves the role's persona + isolation tier (delegated to
9
+ * `dispatcher.ts` so the policy lives in one file).
10
+ * 2. Optionally provisions a scratch worktree when the role's
11
+ * isolation tier requires write isolation (β2 S5 wiring point).
12
+ * 3. Builds a per-child tools schema gated by the isolation matrix
13
+ * (`isolation-matrix.ts::allowedToolsForRole`) so the child loop
14
+ * cannot see tools its role is forbidden to use.
15
+ * 4. Runs `runEngineLoop` with the role-specific persona, budget,
16
+ * and tools against the supplied EngineLoopClient (each child
17
+ * gets its OWN logical Anvil session — same wire transport, but
18
+ * a fresh transcript so the child cannot read parent context the
19
+ * operator did not authorise).
20
+ * 5. Streams `subagent.tool_call` events back through the parent's
21
+ * session journal as each child tool call fires (β2 S7 cross-
22
+ * agent state surface).
23
+ * 6. Aggregates `filesChanged` + `toolCallCount` + token totals from
24
+ * the child loop and returns them in the SubagentResult.
25
+ *
26
+ * What this module deliberately does NOT do:
27
+ *
28
+ * - Promote the child's worktree back to the parent's main tree.
29
+ * `pugi worktree promote` is operator-driven so the operator can
30
+ * review the diff before it lands; the dispatcher only creates
31
+ * the scratch worktree, runs the child in it, and leaves the
32
+ * handle attached to the SubagentResult.filesChanged for the
33
+ * caller to act on.
34
+ *
35
+ * - Drop the worktree on failure. Same reasoning: the operator
36
+ * should be able to inspect what the child did before the scratch
37
+ * dir disappears. Cleanup happens via `pugi worktree drop` or via
38
+ * the explicit `cleanup` callback returned on the worktree handle.
39
+ *
40
+ * - Run grand-children. The capability matrix (S4) blocks the
41
+ * `agent` tool for every role except orchestrator, so a coder/
42
+ * reviewer/verifier cannot spawn a child of its own. Bounded
43
+ * spawn depth = 1, which keeps the budget rollup tractable.
44
+ *
45
+ * Cross-reference: docs/research/2026-05-26-pugi-cli-consolidated-sprint-plan.md
46
+ * §β2 S1 + S7.
47
+ */
48
+ import { randomUUID } from 'node:crypto';
49
+ import { relative as relativePath } from 'node:path';
50
+ import { runEngineLoop, } from '@pugi/sdk';
51
+ import { resolveBudget } from '../engine/budgets.js';
52
+ import { buildExecutor, buildToolsSchema, } from '../engine/tool-bridge.js';
53
+ import { systemPromptFor } from '../engine/prompts.js';
54
+ import { getPersonaForRole } from '../agents/registry.js';
55
+ import { FileReadCache } from '../file-cache.js';
56
+ import { loadSettings } from '../settings.js';
57
+ import { openSession } from '../session.js';
58
+ import { createWorktree, } from '../edits/worktree.js';
59
+ import { classifyBash } from '../bash-classifier.js';
60
+ import { allowedToolsForRole, bashIsReadOnlyForRole, capabilitiesForRole, } from './isolation-matrix.js';
61
+ import { budgetForRole, isolationForRole, } from './dispatcher.js';
62
+ /**
63
+ * Drive a real subagent dispatch. Returns the typed SubagentResult plus
64
+ * the optional WorktreeHandle (when isolation === 'worktree') so the
65
+ * caller can wire `pugi worktree promote/drop` follow-ups.
66
+ *
67
+ * Throws when the engine loop crashes uncaught — the caller should
68
+ * translate that into `recordSubagentFailed` on the parent session.
69
+ */
70
+ export async function runRealDispatch(task, ctx) {
71
+ const persona = getPersonaForRole(task.role);
72
+ const isolation = isolationForRole(task.role);
73
+ const budget = budgetForRole(task.role, task.budget);
74
+ const capabilities = capabilitiesForRole(task.role);
75
+ const now = ctx.now ?? defaultNow;
76
+ const startedAt = Date.now();
77
+ // ---------------------------------------------------------------- //
78
+ // 1. Provision child workspace + session //
79
+ // ---------------------------------------------------------------- //
80
+ // The child's filesystem root depends on the isolation tier:
81
+ //
82
+ // - prompt_only / shared_fs_readonly / shared_fs_serialized →
83
+ // reuse the parent's workspaceRoot. Read isolation is enforced
84
+ // by the capability matrix (no `write` cap → no write tool in
85
+ // the schema). Serialized writes are gated by the file-tools
86
+ // layer's existing in-process lock (per-process serialization
87
+ // is enough at M1 because only one child runs per parent).
88
+ //
89
+ // - worktree → spawn `.pugi/worktrees/<uuid>` via createWorktree
90
+ // and pin the child's workspaceRoot there. The parent's tree is
91
+ // untouched; promote/drop is operator-driven afterwards.
92
+ let childRoot = ctx.workspaceRoot;
93
+ let worktreeHandle;
94
+ // β2a r1 (Codex P1): the original gate required
95
+ // `isolationForRole()` to return `'worktree'` before honoring
96
+ // `ctx.useWorktreeIsolation`. But the role→isolation matrix never
97
+ // returns `'worktree'` for any writer (coders land at
98
+ // `shared_fs_serialized`), so the agentTool's explicit
99
+ // `useWorktreeIsolation: true` request was silently dropped and the
100
+ // coder mutated the parent checkout directly. Fix: treat an explicit
101
+ // `true` as an INDEPENDENT trigger so callers (agentTool with
102
+ // `isolation: 'worktree'`) actually get a scratch worktree.
103
+ //
104
+ // The three states map to:
105
+ // - true → force worktree regardless of role tier (explicit opt-in)
106
+ // - false → skip worktree even if role tier would require it (spec
107
+ // escape hatch + caller opt-out)
108
+ // - undefined → honor role tier default (`'worktree'` → create)
109
+ const useWorktree = ctx.useWorktreeIsolation === true
110
+ ? true
111
+ : ctx.useWorktreeIsolation === false
112
+ ? false
113
+ : isolation === 'worktree';
114
+ // β2a r2 (Codex P2): compute the EFFECTIVE isolation tier
115
+ // for audit metadata. The role-default `isolation` value is the
116
+ // declarative tier from `isolationForRole()`, but when
117
+ // `ctx.useWorktreeIsolation === true` forces a scratch worktree for a
118
+ // role whose default is `shared_fs_serialized` (coder/release/devops/
119
+ // design_qa), the actual workspace boundary is `worktree`. Emitting
120
+ // the stale role-default in the `subagent.spawned` event was
121
+ // misleading audit consumers (SSE / cabinet UI) into thinking the
122
+ // child mutated the parent tree when it actually mutated a scratch
123
+ // worktree — and vice versa when `false` overrode a `worktree`-tier
124
+ // role into shared_fs. The `isolation` field on the lifecycle event
125
+ // now reflects the ACTUAL workspace decision.
126
+ const isolationEffective = useWorktree
127
+ ? 'worktree'
128
+ : isolation;
129
+ if (useWorktree) {
130
+ const wt = createWorktree({ cwd: ctx.workspaceRoot });
131
+ if (!wt.ok) {
132
+ // Translate worktree failure to a clean blocked result instead
133
+ // of throwing — the caller can surface it as
134
+ // subagent.blocked / tool_unavailable. The dispatcher.ts wrapper
135
+ // does the emission so the audit log lines up with other
136
+ // blocked dispatches.
137
+ //
138
+ // β2a r2 (Codex P2): isolation here falls back to the role
139
+ // default because the worktree failed to provision — the child
140
+ // never landed on a `worktree` boundary, so reporting the
141
+ // effective tier would lie. Use the declarative role default.
142
+ ctx.appendEvent({
143
+ id: randomUUID(),
144
+ sessionId: ctx.sessionId,
145
+ timestamp: now(),
146
+ type: 'subagent.spawned',
147
+ taskId: task.id,
148
+ role: task.role,
149
+ personaSlug: persona.slug,
150
+ parentSessionId: ctx.sessionId,
151
+ isolation,
152
+ });
153
+ const blockedResult = {
154
+ taskId: task.id,
155
+ role: task.role,
156
+ personaSlug: persona.slug,
157
+ status: 'blocked',
158
+ summary: `worktree provisioning failed: ${wt.reason}: ${wt.detail}`,
159
+ filesChanged: [],
160
+ toolCallCount: 0,
161
+ tokensIn: 0,
162
+ tokensOut: 0,
163
+ durationMs: Date.now() - startedAt,
164
+ };
165
+ ctx.appendEvent({
166
+ id: randomUUID(),
167
+ sessionId: ctx.sessionId,
168
+ timestamp: now(),
169
+ type: 'subagent.blocked',
170
+ taskId: task.id,
171
+ role: task.role,
172
+ personaSlug: persona.slug,
173
+ reason: 'tool_unavailable',
174
+ detail: `worktree provisioning failed (${wt.reason}): ${wt.detail}`,
175
+ });
176
+ return { result: blockedResult };
177
+ }
178
+ childRoot = wt.value.path;
179
+ worktreeHandle = wt.value;
180
+ }
181
+ const childSession = ctx.childSession ?? openSession(childRoot);
182
+ const settings = loadSettings(childRoot);
183
+ // ---------------------------------------------------------------- //
184
+ // 2. Emit subagent.spawned //
185
+ // ---------------------------------------------------------------- //
186
+ // β2a r2 (Codex P2): use the EFFECTIVE isolation tier so
187
+ // audit consumers see the actual workspace boundary (`worktree` when
188
+ // `ctx.useWorktreeIsolation === true` forced a scratch worktree for
189
+ // a role whose default was `shared_fs_serialized`). The stale
190
+ // role-default emission was a P2 misdirection — operators watching
191
+ // `pugi sessions stream` saw `shared_fs_serialized` while the child
192
+ // was actually mutating `.pugi/worktrees/<uuid>/`.
193
+ ctx.appendEvent({
194
+ id: randomUUID(),
195
+ sessionId: ctx.sessionId,
196
+ timestamp: now(),
197
+ type: 'subagent.spawned',
198
+ taskId: task.id,
199
+ role: task.role,
200
+ personaSlug: persona.slug,
201
+ parentSessionId: ctx.sessionId,
202
+ isolation: isolationEffective,
203
+ });
204
+ // ---------------------------------------------------------------- //
205
+ // 3. Build per-child tools schema + executor //
206
+ // ---------------------------------------------------------------- //
207
+ // The schema is filtered by the capability matrix (S4). We start
208
+ // from buildToolsSchema's default set and intersect with
209
+ // `allowedToolsForRole`. The executor enforces the same set as a
210
+ // defense-in-depth gate — even if a model hallucinates an unknown
211
+ // tool name (or a future schema bug accidentally advertises one),
212
+ // the executor rejects it before any file-tools layer sees it.
213
+ const commandKind = ctx.commandKind ?? commandKindForRole(task.role);
214
+ const allowedTools = allowedToolsForRole(task.role);
215
+ // β2a r1 (Backend Architect P1): the executor's
216
+ // `allowFetch` flag previously only checked the workspace settings
217
+ // (`web.fetch.enabled`). For roles whose capability set DOES NOT
218
+ // include `web_fetch` (every non-orchestrator role today) the
219
+ // schema filter would strip the tool advertisement, but a model
220
+ // that fabricated a `web_fetch` call would still hit the executor
221
+ // with `allowFetch: true` and the call would succeed. Fix: AND the
222
+ // role-capability gate into the flag so a fabricated call also
223
+ // gets refused by the capability gate above; the AND below is the
224
+ // executor-level belt-and-braces.
225
+ const allowFetchForChild = allowedTools.has('web_fetch') && settings.web?.fetch?.enabled === true;
226
+ const schemaOpts = {
227
+ allowFetch: allowFetchForChild,
228
+ };
229
+ const fullSchema = buildToolsSchema(commandKind, schemaOpts);
230
+ const filteredSchema = fullSchema.filter((tool) => allowedTools.has(tool.name));
231
+ const toolCtx = {
232
+ root: childRoot,
233
+ settings,
234
+ session: childSession,
235
+ readCache: new FileReadCache(),
236
+ };
237
+ const rawExecutor = buildExecutor({
238
+ kind: commandKind,
239
+ ctx: toolCtx,
240
+ sessionId: childSession.id,
241
+ workspaceRoot: childRoot,
242
+ interactive: false,
243
+ allowFetch: allowFetchForChild,
244
+ });
245
+ // β2a r1 (Codex P1): roles in the bash_read_only tier
246
+ // (verifier today) need an EXTRA pre-flight on every `bash` call so
247
+ // a write-class command (echo into a file, sed -i, rm) is refused
248
+ // before bashToolSync's permission layer sees it. The bash tool
249
+ // defaults to permission mode `auto`, which permits write_workspace
250
+ // class commands; without this gate the no-edit contract is purely
251
+ // honor-system. Read + build_test are still allowed so test
252
+ // commands work; everything else surfaces a CAPABILITY_REFUSED
253
+ // sentinel back to the model so it can adapt.
254
+ const bashReadOnly = bashIsReadOnlyForRole(task.role);
255
+ const BASH_READ_ONLY_ALLOWED = new Set(['read', 'build_test']);
256
+ // ---------------------------------------------------------------- //
257
+ // 4. Wrap executor with the isolation-matrix refusal gate + //
258
+ // cross-agent event stream (S4 + S7) //
259
+ // ---------------------------------------------------------------- //
260
+ // Every tool call the child makes is:
261
+ // (a) rejected if the role lacks the capability (defense in depth);
262
+ // (b) emitted as a subagent.tool_call event into the parent
263
+ // journal so `pugi sessions stream <id>` surfaces it inline;
264
+ // (c) classified for the filesChanged summary.
265
+ const filesChanged = new Set();
266
+ let childToolCallCount = 0;
267
+ const gatedExecutor = async (input) => {
268
+ if (!allowedTools.has(input.name)) {
269
+ // Note: this is in addition to the schema filter — a model that
270
+ // fabricates a tool call for a name we deliberately did NOT
271
+ // advertise still gets rejected here. The error string is read
272
+ // by runEngineLoop and fed back into the next turn's tool frame.
273
+ throw new Error(`CAPABILITY_REFUSED: role '${task.role}' is not allowed to call '${input.name}' (${capabilities.rationale})`);
274
+ }
275
+ // β2a r1 (Codex P1): bash read-only gate for roles
276
+ // whose capability set is `bash_read_only` (verifier). Pre-classify
277
+ // the command and refuse anything outside read/build_test BEFORE
278
+ // the tool runs — the underlying bashToolSync's permission layer
279
+ // would otherwise wave write_workspace through under the default
280
+ // `auto` mode.
281
+ if (input.name === 'bash' && bashReadOnly) {
282
+ const cmd = extractBashCommand(input.arguments);
283
+ if (cmd !== null) {
284
+ const classification = classifyBash(cmd, {
285
+ workspaceRoot: childRoot,
286
+ additionalDirectories: [],
287
+ });
288
+ if (!BASH_READ_ONLY_ALLOWED.has(classification.class)) {
289
+ throw new Error(`CAPABILITY_REFUSED: role '${task.role}' may only run read/build_test bash commands; '${classification.class}' refused (${classification.reason})`);
290
+ }
291
+ }
292
+ }
293
+ childToolCallCount += 1;
294
+ // β2 S7 cross-agent state: emit subagent.tool_call BEFORE dispatch
295
+ // so the parent journal sees the attempt even if the tool throws.
296
+ ctx.appendEvent({
297
+ id: randomUUID(),
298
+ sessionId: ctx.sessionId,
299
+ timestamp: now(),
300
+ type: 'subagent.tool_call',
301
+ taskId: task.id,
302
+ role: task.role,
303
+ personaSlug: persona.slug,
304
+ toolName: input.name,
305
+ toolCallId: input.callId,
306
+ });
307
+ const result = await rawExecutor(input);
308
+ if (input.name === 'write' || input.name === 'edit') {
309
+ const path = extractPathArg(input.arguments);
310
+ if (path)
311
+ filesChanged.add(path);
312
+ }
313
+ return result;
314
+ };
315
+ // ---------------------------------------------------------------- //
316
+ // 5. Build the engine loop budget envelope from the per-role //
317
+ // SubagentBudget (tokens/dollars/wallClockMs). //
318
+ // ---------------------------------------------------------------- //
319
+ // The engine loop driver uses {maxTokens, maxToolCalls}; we
320
+ // translate the subagent budget into the loop budget shape and
321
+ // fall back to the per-command defaults from `resolveBudget` for
322
+ // the call-count ceiling (subagent budget does not carry that knob).
323
+ const commandBudget = resolveBudget(commandKind, settings, { maxTokens: budget.tokens });
324
+ // ---------------------------------------------------------------- //
325
+ // 6. Drive the child loop //
326
+ // ---------------------------------------------------------------- //
327
+ const hooks = {
328
+ // We deliberately do NOT proxy onTurnStart/onTurnComplete to the
329
+ // parent journal — the child's own session already records those
330
+ // via the per-loop session mirror, and re-emitting them at parent
331
+ // scope would double-count tokens in the cabinet UI.
332
+ //
333
+ // onToolCall + onToolResult are also folded into the gatedExecutor
334
+ // above (the cross-agent event stream) so the loop's hook surface
335
+ // stays empty here.
336
+ };
337
+ let outcome;
338
+ try {
339
+ outcome = await runEngineLoop({
340
+ client: ctx.engineClient,
341
+ executor: gatedExecutor,
342
+ systemPrompt: systemPromptFor(commandKind),
343
+ userPrompt: task.prompt,
344
+ tools: filteredSchema,
345
+ budget: commandBudget,
346
+ personaSlug: persona.slug,
347
+ hooks,
348
+ ...(ctx.signal ? { signal: ctx.signal } : {}),
349
+ });
350
+ }
351
+ catch (error) {
352
+ const message = error instanceof Error ? error.message : String(error);
353
+ const failedResult = {
354
+ taskId: task.id,
355
+ role: task.role,
356
+ personaSlug: persona.slug,
357
+ status: 'failed',
358
+ summary: `engine loop crashed: ${message}`,
359
+ filesChanged: Array.from(filesChanged).sort(),
360
+ toolCallCount: childToolCallCount,
361
+ tokensIn: 0,
362
+ tokensOut: 0,
363
+ durationMs: Date.now() - startedAt,
364
+ };
365
+ ctx.appendEvent({
366
+ id: randomUUID(),
367
+ sessionId: ctx.sessionId,
368
+ timestamp: now(),
369
+ type: 'subagent.failed',
370
+ taskId: task.id,
371
+ role: task.role,
372
+ personaSlug: persona.slug,
373
+ error: message,
374
+ });
375
+ return { result: failedResult, ...(worktreeHandle ? { worktreeHandle } : {}) };
376
+ }
377
+ // ---------------------------------------------------------------- //
378
+ // 7. Translate loop outcome → SubagentResult //
379
+ // ---------------------------------------------------------------- //
380
+ const { status, terminalEvent } = translateOutcome(outcome);
381
+ const summary = composeSummary(outcome, status, capabilities.rationale, worktreeHandle, ctx.workspaceRoot);
382
+ const result = {
383
+ taskId: task.id,
384
+ role: task.role,
385
+ personaSlug: persona.slug,
386
+ status,
387
+ summary,
388
+ filesChanged: Array.from(filesChanged).sort(),
389
+ // childToolCallCount is the gate-level count (every tool the child
390
+ // attempted, including refusals). outcome.toolCallCount is what
391
+ // the engine loop actually ran. We use outcome's number because
392
+ // it matches the audit log's tool_call records — refusals were
393
+ // already surfaced as subagent.tool_call events before the throw.
394
+ toolCallCount: outcome.toolCallCount,
395
+ tokensIn: estimateTokensIn(outcome),
396
+ tokensOut: estimateTokensOut(outcome),
397
+ durationMs: Date.now() - startedAt,
398
+ };
399
+ // Emit the terminal event last so an audit-log reader sees the
400
+ // chronological order: spawned → tool_call(s) → completed/blocked/failed.
401
+ if (terminalEvent === 'completed') {
402
+ ctx.appendEvent({
403
+ id: randomUUID(),
404
+ sessionId: ctx.sessionId,
405
+ timestamp: now(),
406
+ type: 'subagent.completed',
407
+ taskId: task.id,
408
+ role: task.role,
409
+ personaSlug: persona.slug,
410
+ toolCallCount: result.toolCallCount,
411
+ tokensIn: result.tokensIn,
412
+ tokensOut: result.tokensOut,
413
+ durationMs: result.durationMs,
414
+ });
415
+ }
416
+ else if (terminalEvent === 'blocked') {
417
+ ctx.appendEvent({
418
+ id: randomUUID(),
419
+ sessionId: ctx.sessionId,
420
+ timestamp: now(),
421
+ type: 'subagent.blocked',
422
+ taskId: task.id,
423
+ role: task.role,
424
+ personaSlug: persona.slug,
425
+ reason: blockedReasonFor(outcome),
426
+ detail: outcome.reason ?? 'engine loop blocked without a specific reason',
427
+ });
428
+ }
429
+ else {
430
+ ctx.appendEvent({
431
+ id: randomUUID(),
432
+ sessionId: ctx.sessionId,
433
+ timestamp: now(),
434
+ type: 'subagent.failed',
435
+ taskId: task.id,
436
+ role: task.role,
437
+ personaSlug: persona.slug,
438
+ error: outcome.reason ?? 'unknown engine loop failure',
439
+ });
440
+ }
441
+ return { result, ...(worktreeHandle ? { worktreeHandle } : {}) };
442
+ }
443
+ /* ---------------------------------------------------------------- */
444
+ /* Helpers */
445
+ /* ---------------------------------------------------------------- */
446
+ /**
447
+ * Default command kind for a role. Writers default to `code` (20 calls
448
+ * / 30k tokens envelope per β1 Pl9); readers default to `explain` (5 /
449
+ * 20k). The caller can always override via ctx.commandKind.
450
+ */
451
+ function commandKindForRole(role) {
452
+ switch (role) {
453
+ case 'coder':
454
+ case 'release':
455
+ case 'devops':
456
+ case 'design_qa':
457
+ return 'code';
458
+ case 'verifier':
459
+ // verifier needs bash for tests but should not be writing —
460
+ // `fix` envelope is the right compromise (read + bash + edit-ish
461
+ // tokens, no full build budget).
462
+ return 'fix';
463
+ case 'orchestrator':
464
+ // orchestrator runs in parent context — `code` matches the
465
+ // parent's default and gives it room to delegate.
466
+ return 'code';
467
+ case 'architect':
468
+ case 'reviewer':
469
+ case 'researcher':
470
+ default:
471
+ return 'explain';
472
+ }
473
+ }
474
+ function translateOutcome(outcome) {
475
+ switch (outcome.status) {
476
+ case 'completed':
477
+ return { status: 'shipped', terminalEvent: 'completed' };
478
+ case 'budget_exhausted':
479
+ case 'tool_refused':
480
+ case 'aborted':
481
+ // `aborted` maps to `blocked` per the same logic as native-pugi.ts:
482
+ // the operator chose the outcome.
483
+ return { status: 'blocked', terminalEvent: 'blocked' };
484
+ case 'failed':
485
+ return { status: 'failed', terminalEvent: 'failed' };
486
+ }
487
+ }
488
+ function blockedReasonFor(outcome) {
489
+ switch (outcome.status) {
490
+ case 'budget_exhausted':
491
+ return 'budget_exhausted';
492
+ case 'tool_refused':
493
+ return 'plan_mode_refused';
494
+ case 'aborted':
495
+ // Operator abort surfaces as permission_denied — the operator
496
+ // pulled consent, which is the same shape as a permission rule
497
+ // refusing the call.
498
+ return 'permission_denied';
499
+ default:
500
+ return 'tool_unavailable';
501
+ }
502
+ }
503
+ function composeSummary(outcome, status, rationale, worktreeHandle, workspaceRoot) {
504
+ const finalText = outcome.finalText.trim();
505
+ const lines = [];
506
+ if (finalText) {
507
+ lines.push(finalText);
508
+ }
509
+ else if (outcome.reason) {
510
+ lines.push(`[${outcome.status}] ${outcome.reason}`);
511
+ }
512
+ else {
513
+ lines.push(`[${outcome.status}] no answer returned`);
514
+ }
515
+ if (worktreeHandle) {
516
+ // β2a r1 (Backend Architect P1): emit the worktree
517
+ // path RELATIVE to the workspace root. The summary text flows to
518
+ // the parent transcript and from there to the provider when the
519
+ // operator runs in `studio` / `provider-direct` privacy mode;
520
+ // shipping `/Users/<operator>/Web/...` (absolute) leaked the
521
+ // operator's home directory to the provider on every spawn. The
522
+ // relative form (`.pugi/worktrees/<uuid>`) is enough for the
523
+ // operator's local `pugi worktree promote/drop` commands.
524
+ const relPath = relativePath(workspaceRoot, worktreeHandle.path) || worktreeHandle.path;
525
+ lines.push('');
526
+ lines.push(`worktree: ${relPath}`);
527
+ lines.push(`base: ${worktreeHandle.baseSha}`);
528
+ lines.push(`promote via: pugi worktree promote ${relPath}`);
529
+ lines.push(`drop via: pugi worktree drop ${relPath}`);
530
+ }
531
+ // β2a r1 (Backend Architect P2): the rationale string
532
+ // duplicates the role's capability matrix entry, which on `shipped`
533
+ // outcomes is noise that bloats the parent transcript and provides
534
+ // an extra signal a prompt-injecting child could echo back. Limit
535
+ // it to `blocked` (where it explains the refusal) — failed
536
+ // outcomes already carry the engine loop's own reason field.
537
+ if (status === 'blocked') {
538
+ lines.push('');
539
+ lines.push(`role-capability rationale: ${rationale}`);
540
+ }
541
+ return lines.join('\n');
542
+ }
543
+ /**
544
+ * The engine-loop outcome carries a single `tokensUsed` counter (total
545
+ * prompt + completion tokens). We don't have a precise prompt/completion
546
+ * split surfaced from the loop driver — approximate the split as 70/30
547
+ * (typical chat-completion shape for tool-use loops where the model's
548
+ * replies are short tool_call frames). The numbers are surfaced in the
549
+ * cabinet UI as a guideline, not for billing reconciliation (billing
550
+ * lives at the runtime adapter side).
551
+ */
552
+ function estimateTokensIn(outcome) {
553
+ return Math.round(outcome.tokensUsed * 0.7);
554
+ }
555
+ function estimateTokensOut(outcome) {
556
+ return Math.round(outcome.tokensUsed * 0.3);
557
+ }
558
+ function extractPathArg(raw) {
559
+ if (!raw)
560
+ return null;
561
+ try {
562
+ const parsed = JSON.parse(raw);
563
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
564
+ const path = parsed.path;
565
+ if (typeof path === 'string' && path.length > 0)
566
+ return path;
567
+ }
568
+ }
569
+ catch {
570
+ // bad JSON — ignored.
571
+ }
572
+ return null;
573
+ }
574
+ /**
575
+ * β2a r1 : pull the `command` field out of a bash tool
576
+ * call's JSON arguments. Used by the bash read-only gate to feed the
577
+ * classifier. Returns null on bad JSON / missing field so the caller
578
+ * can fail-safe to allow (the underlying tool will surface its own
579
+ * parse error).
580
+ */
581
+ function extractBashCommand(raw) {
582
+ if (!raw)
583
+ return null;
584
+ try {
585
+ const parsed = JSON.parse(raw);
586
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
587
+ const cmd = parsed.command;
588
+ if (typeof cmd === 'string' && cmd.length > 0)
589
+ return cmd;
590
+ }
591
+ }
592
+ catch {
593
+ // bad JSON — let the tool surface the parse error.
594
+ }
595
+ return null;
596
+ }
597
+ function defaultNow() {
598
+ return new Date().toISOString();
599
+ }
600
+ //# sourceMappingURL=dispatcher-real.js.map