@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
@@ -11,21 +11,21 @@ import { resolveActiveCredential } from '../../core/credentials.js';
11
11
  * `pugi config` — operator-level configuration surface.
12
12
  *
13
13
  * Subcommands:
14
- * - `pugi config get <key>` read a value from `~/.pugi/config.json`
15
- * - `pugi config set <key> <value>` write a value
16
- * - `pugi config list` dump all values
17
- * - `pugi config trust .` trust the current workspace (delegates to core/trust.ts)
18
- * - `pugi config mcp trust <name>` flip MCP server to trusted
19
- * - `pugi config mcp deny <name>` flip MCP server to denied
20
- * - `pugi config mcp list` show declared servers + their trust state
14
+ * - `pugi config get <key>` read a value from `~/.pugi/config.json`
15
+ * - `pugi config set <key> <value>` write a value
16
+ * - `pugi config list` dump all values
17
+ * - `pugi config trust .` trust the current workspace (delegates to core/trust.ts)
18
+ * - `pugi config mcp trust <name>` flip MCP server to trusted
19
+ * - `pugi config mcp deny <name>` flip MCP server to denied
20
+ * - `pugi config mcp list` show declared servers + their trust state
21
21
  *
22
22
  * Schema (pugi-config-v1):
23
- * {
24
- * "permissionMode": "ask" | "acceptEdits" | "auto" | "plan" | "dontAsk" | "bypassPermissions",
25
- * "privacy": "local-only" | "metadata" | "full",
26
- * "model": "<id>" | null,
27
- * "preferredEndpoint": "https://api.pugi.io"
28
- * }
23
+ * {
24
+ * "permissionMode": "ask" | "acceptEdits" | "auto" | "plan" | "dontAsk" | "bypassPermissions",
25
+ * "privacy": "local-only" | "metadata" | "full",
26
+ * "model": "<id>" | null,
27
+ * "preferredEndpoint": "https://api.pugi.io"
28
+ * }
29
29
  *
30
30
  * The config file lives at `~/.pugi/config.json` (PUGI_HOME-aware) and uses
31
31
  * mode 0o600. Unknown keys are rejected by `set` so a typo never silently
@@ -39,9 +39,41 @@ const configSchema = z
39
39
  privacy: z.enum(['local-only', 'metadata', 'full']).optional(),
40
40
  model: z.string().nullable().optional(),
41
41
  preferredEndpoint: z.string().url().optional(),
42
+ // PUGI-260 — persistent default for the 1M context tier opt-in.
43
+ // `pugi config set contextTier 1m` (or the dotted form
44
+ // `context.tier 1m`) writes this; per-invocation `--context-tier=...`
45
+ // flags override it at request time. Closed enum mirrors the CLI
46
+ // flag и the admin-api DTO so a typo here surfaces as a Zod parse
47
+ // error при load, not a silent fallback. Stored on the flat user-
48
+ // level config (~/.pugi/config.json) so all workspaces inherit the
49
+ // same default — operators с consistent long-context workloads
50
+ // (large monorepos, audits) set it once instead of remembering к
51
+ // pass --context-tier=1m on every dispatch.
52
+ contextTier: z.enum(['1m', 'standard']).optional(),
42
53
  })
43
54
  .strict();
44
- const CONFIG_KEYS = ['permissionMode', 'privacy', 'model', 'preferredEndpoint'];
55
+ const CONFIG_KEYS = [
56
+ 'permissionMode',
57
+ 'privacy',
58
+ 'model',
59
+ 'preferredEndpoint',
60
+ // PUGI-260 — exposed на `pugi config list` so operators see the
61
+ // current default. Hidden synonym `context.tier` accepted by
62
+ // runConfigSet / runConfigGet for a dotted-key familiar UX.
63
+ 'contextTier',
64
+ ];
65
+ /**
66
+ * PUGI-260: legacy / nested key aliasing. `pugi config set context.tier 1m`
67
+ * is the documented form в the feat doc; we normalise it onto the flat
68
+ * `contextTier` key before the strict-schema validation так future
69
+ * settings.json migrations keep one canonical key. Mirrors the
70
+ * legacy privacy-mode aliasing that already lives in the file.
71
+ */
72
+ function normaliseConfigKey(raw) {
73
+ if (raw === 'context.tier')
74
+ return 'contextTier';
75
+ return raw;
76
+ }
45
77
  export async function runConfigCommand(args, ctx) {
46
78
  const sub = args[0];
47
79
  if (!sub || sub === '--help' || sub === '-h') {
@@ -63,18 +95,18 @@ export async function runConfigCommand(args, ctx) {
63
95
  ],
64
96
  }, [
65
97
  'Usage:',
66
- ' pugi config get <key> Read a config value.',
67
- ' pugi config set <key> <value> Write a config value.',
68
- ' pugi config list Show all config values.',
69
- ' pugi config trust . Trust the current workspace for hooks + MCP.',
70
- ' pugi config mcp trust <name> Mark an MCP server as trusted.',
71
- ' pugi config mcp deny <name> Block an MCP server.',
72
- ' pugi config mcp list Show declared MCP servers + trust state.',
73
- ' pugi config get routing Show effective routing table (defaults + tenant overrides).',
74
- ' pugi config set routing.<tag>.<budget>=<model> Override the model for one (tag, budget) lane.',
75
- ' pugi config unset routing.<tag>.<budget> Remove a routing override (revert to default).',
76
- ' pugi config get privacy Show current tenant privacy mode + last-flip metadata.',
77
- ' pugi config set privacy=<mode> Flip privacy mode (strict | balanced | permissive).',
98
+ ' pugi config get <key> Read a config value.',
99
+ ' pugi config set <key> <value> Write a config value.',
100
+ ' pugi config list Show all config values.',
101
+ ' pugi config trust . Trust the current workspace for hooks + MCP.',
102
+ ' pugi config mcp trust <name> Mark an MCP server as trusted.',
103
+ ' pugi config mcp deny <name> Block an MCP server.',
104
+ ' pugi config mcp list Show declared MCP servers + trust state.',
105
+ ' pugi config get routing Show effective routing table (defaults + tenant overrides).',
106
+ ' pugi config set routing.<tag>.<budget>=<model> Override the model for one (tag, budget) lane.',
107
+ ' pugi config unset routing.<tag>.<budget> Remove a routing override (revert to default).',
108
+ ' pugi config get privacy Show current tenant privacy mode + last-flip metadata.',
109
+ ' pugi config set privacy=<mode> Flip privacy mode (strict | balanced | permissive).',
78
110
  ].join('\n'));
79
111
  return;
80
112
  }
@@ -108,7 +140,7 @@ export async function runConfigCommand(args, ctx) {
108
140
  // names. The unit spec for config.ts has a regression test for
109
141
  // both code paths.
110
142
  //
111
- // Triple-review P2 fix (2026-05-25): the prior disambiguation
143
+ // Triple-review P2 fix : the prior disambiguation
112
144
  // only excluded the bare form (`privacy local-only`) - the `=`
113
145
  // form (`privacy=local-only`) still routed to runPrivacySet and
114
146
  // 4xx'd. We now check the value AFTER `=` and route legacy local
@@ -178,25 +210,27 @@ function isConfigKey(value) {
178
210
  return CONFIG_KEYS.includes(value);
179
211
  }
180
212
  function runConfigGet(args, ctx) {
181
- const key = args[0];
182
- if (!key)
213
+ const rawKey = args[0];
214
+ if (!rawKey)
183
215
  throw new Error('pugi config get requires a key.');
216
+ const key = normaliseConfigKey(rawKey);
184
217
  if (!isConfigKey(key)) {
185
- throw new Error(`Unknown config key "${key}". Allowed: ${CONFIG_KEYS.join(', ')}.`);
218
+ throw new Error(`Unknown config key "${rawKey}". Allowed: ${CONFIG_KEYS.join(', ')}.`);
186
219
  }
187
220
  const config = readConfig();
188
221
  const value = config[key] ?? null;
189
222
  ctx.writeOutput({ command: 'config.get', key, value }, value === null || value === undefined ? `${key} = (unset)` : `${key} = ${String(value)}`);
190
223
  }
191
224
  function runConfigSet(args, ctx) {
192
- const key = args[0];
225
+ const rawKey = args[0];
193
226
  const value = args.slice(1).join(' ');
194
- if (!key)
227
+ if (!rawKey)
195
228
  throw new Error('pugi config set requires a key.');
196
229
  if (value.length === 0)
197
230
  throw new Error('pugi config set requires a value.');
231
+ const key = normaliseConfigKey(rawKey);
198
232
  if (!isConfigKey(key)) {
199
- throw new Error(`Unknown config key "${key}". Allowed: ${CONFIG_KEYS.join(', ')}.`);
233
+ throw new Error(`Unknown config key "${rawKey}". Allowed: ${CONFIG_KEYS.join(', ')}.`);
200
234
  }
201
235
  const current = readConfig();
202
236
  // Build the candidate and validate via the schema so an invalid value
@@ -279,7 +313,7 @@ async function runConfigMcpList(ctx) {
279
313
  }
280
314
  ctx.writeOutput({ command: 'config.mcp.list', servers: declared, ledger }, [
281
315
  'MCP servers:',
282
- ...declared.map((server) => ` ${server.name.padEnd(20)} ${server.trust.padEnd(8)} ${server.command} ${server.args.join(' ')}`),
316
+ ...declared.map((server) => ` ${server.name.padEnd(20)} ${server.trust.padEnd(8)} ${server.command} ${server.args.join(' ')}`),
283
317
  ].join('\n'));
284
318
  }
285
319
  async function runConfigMcpFlip(args, ctx, state) {
@@ -297,7 +331,7 @@ async function runConfigMcpFlip(args, ctx, state) {
297
331
  : `MCP server "${name}" is now denied.`);
298
332
  }
299
333
  /* ------------------------------------------------------------------ */
300
- /* α6.10 multi-model routing — config.routing.* subcommands */
334
+ /* multi-model routing — config.routing.* subcommands */
301
335
  /* ------------------------------------------------------------------ */
302
336
  /**
303
337
  * Closed sets — match
@@ -360,7 +394,7 @@ async function runRoutingGet(ctx) {
360
394
  });
361
395
  const text = [
362
396
  'Routing table (effective = override | default):',
363
- ...cells.map((cell) => ` routing.${cell.tag.padEnd(10)}.${cell.budgetHint.padEnd(3)} = ${cell.model.padEnd(28)} (${cell.source})`),
397
+ ...cells.map((cell) => ` routing.${cell.tag.padEnd(10)}.${cell.budgetHint.padEnd(3)} = ${cell.model.padEnd(28)} (${cell.source})`),
364
398
  ].join('\n');
365
399
  ctx.writeOutput({
366
400
  command: 'config.routing.get',
@@ -439,7 +473,7 @@ async function runRoutingUnset(args, ctx) {
439
473
  : `routing.${tag}.${budget} had no override (nothing to remove).`);
440
474
  }
441
475
  /* ------------------------------------------------------------------ */
442
- /* alpha 6.13 privacy 3-mode - config.privacy.* subcommands */
476
+ /* alpha 6.13 privacy 3-mode - config.privacy.* subcommands */
443
477
  /* ------------------------------------------------------------------ */
444
478
  /**
445
479
  * Closed mirror of the server-side PRIVACY_MODES enum
@@ -459,7 +493,7 @@ function isPrivacyMode(value) {
459
493
  * Legacy local-config privacy values from before alpha 6.13. Kept so
460
494
  * `pugi config set privacy=local-only` continues to write to the local
461
495
  * config file (matching the bare-form behaviour). Triple-review P2 fix
462
- * (2026-05-25): the prior disambiguation only excluded the bare form;
496
+ * : the prior disambiguation only excluded the bare form;
463
497
  * the `=` form routed to runPrivacySet and 4xx'd on the unknown mode.
464
498
  */
465
499
  const LEGACY_LOCAL_PRIVACY_VALUES = [
@@ -536,7 +570,7 @@ async function runPrivacySet(args, ctx) {
536
570
  * — undici's `request` honours the global dispatcher reliably across the
537
571
  * pinned undici version. Kept local (not shared with `pugi whoami`) so
538
572
  * the routing surface is self-contained — extracting a common helper is
539
- * α6.10b cleanup once we see two callers.
573
+ * cleanup once we see two callers.
540
574
  */
541
575
  async function fetchJson(url, apiKey, options = {}) {
542
576
  const method = options.method ?? 'GET';
@@ -0,0 +1,199 @@
1
+ /**
2
+ * `pugi cost` / `pugi usage` command handler — L19 sprint .
3
+ *
4
+ * Shared backend for three operator surfaces:
5
+ *
6
+ * - `pugi cost` current session (default)
7
+ * - `pugi cost --all-sessions` 30-day rolling aggregate
8
+ * - `pugi cost --reset --yes` wipe current session counter (operator-only)
9
+ * - `pugi usage` alias of `pugi cost`
10
+ * - `/cost` REPL slash same handler, in-REPL output
11
+ * - `/usage` REPL slash same handler, alias of /cost
12
+ *
13
+ * Why a separate command from the existing `pugi budget`:
14
+ *
15
+ * - `pugi budget` walks `.pugi/events.jsonl` and bills against the
16
+ * event-log heuristic (per-command / per-persona attribution). It
17
+ * is the right surface for "what did this brief / this persona
18
+ * spend?". It does not break down by model and it does not persist
19
+ * a cross-session aggregate.
20
+ *
21
+ * - `pugi cost` (this command) reads the persisted `.pugi/cost.json`
22
+ * written by the `CostTracker`. It is the right surface for "what
23
+ * did this model spend?" and "what did I spend across the last 30
24
+ * days?". Token + USD figures are sourced from the rate card, which
25
+ * distinguishes hosted Claude (per-token billed) from open-weight
26
+ * Qwen / Kimi / DeepSeek (infra cost only).
27
+ *
28
+ * Both commands intentionally coexist — they answer adjacent but distinct
29
+ * operator questions. The L19 spec calls out `/cost` and `/usage` by
30
+ * name; the budget surface is unaffected.
31
+ */
32
+ import { existsSync, readFileSync } from 'node:fs';
33
+ import { resolve } from 'node:path';
34
+ import { createCostTracker, totalTokens, totalUsd, } from '../../core/cost/tracker.js';
35
+ import { buildCostView, renderCostTableText } from '../../tui/cost-table.js';
36
+ /**
37
+ * Parsed flag bundle. Exported for the test surface; production callers
38
+ * never touch it directly — `runCostCommand` owns parsing.
39
+ */
40
+ export function parseCostFlags(args) {
41
+ const flags = {
42
+ allSessions: false,
43
+ reset: false,
44
+ yes: false,
45
+ json: false,
46
+ windowDays: 30,
47
+ };
48
+ for (let i = 0; i < args.length; i += 1) {
49
+ const arg = args[i] ?? '';
50
+ if (arg === '--all-sessions')
51
+ flags.allSessions = true;
52
+ else if (arg === '--reset')
53
+ flags.reset = true;
54
+ else if (arg === '--yes' || arg === '-y')
55
+ flags.yes = true;
56
+ else if (arg === '--json')
57
+ flags.json = true;
58
+ else if (arg.startsWith('--window=')) {
59
+ const raw = Number.parseInt(arg.slice('--window='.length), 10);
60
+ if (Number.isFinite(raw) && raw > 0 && raw <= 365)
61
+ flags.windowDays = raw;
62
+ }
63
+ }
64
+ return flags;
65
+ }
66
+ export async function runCostCommand(args, ctx) {
67
+ const flags = parseCostFlags(args);
68
+ const sessionId = ctx.sessionId ?? deriveSessionIdFromEvents(ctx.workspaceRoot) ?? 'no-session';
69
+ const tracker = createCostTracker({
70
+ workspaceRoot: ctx.workspaceRoot,
71
+ sessionIdProvider: () => sessionId,
72
+ now: ctx.now,
73
+ });
74
+ // --reset: clear the current session counter. Operator-only — refuses
75
+ // without `--yes` so a typo / shell completion never wipes the meter.
76
+ if (flags.reset) {
77
+ if (!flags.yes) {
78
+ ctx.writeOutput({ command: 'cost', status: 'reset_pending_confirmation' }, 'pugi cost --reset clears the current session counter. Re-run with --yes to confirm.');
79
+ return;
80
+ }
81
+ const wiped = tracker.resetCurrent();
82
+ const payload = {
83
+ command: 'cost',
84
+ status: 'reset_ok',
85
+ wiped: wiped ?? null,
86
+ };
87
+ ctx.writeOutput(payload, wiped
88
+ ? `Cleared session ${wiped.sessionId} (${Object.keys(wiped.models).length} model(s) wiped).`
89
+ : 'No current session counter to clear.');
90
+ return;
91
+ }
92
+ const aggregate = flags.allSessions ? tracker.aggregateWithin(flags.windowDays) : (tracker.current() ?? emptyAggregate(sessionId, ctx.now ?? Date.now));
93
+ const tier = ctx.resolveTier ? await safeResolveTier(ctx.resolveTier) : null;
94
+ const heading = flags.allSessions
95
+ ? `Pugi cost / usage — aggregate (last ${flags.windowDays} days)`
96
+ : buildSessionHeading(aggregate, ctx.now ?? Date.now);
97
+ const view = buildCostView({ aggregate, heading, tier: tier ?? undefined });
98
+ const text = renderCostTableText(view);
99
+ ctx.writeOutput({
100
+ command: flags.allSessions ? 'cost.aggregate' : 'cost.session',
101
+ status: 'ok',
102
+ window: flags.allSessions ? `${flags.windowDays}d` : 'current',
103
+ tokens: {
104
+ input: view.totalInputTokens,
105
+ output: view.totalOutputTokens,
106
+ },
107
+ dollars: view.totalUsd,
108
+ perModel: view.rows.map((row) => ({
109
+ model: row.model,
110
+ input: row.inputTokens,
111
+ output: row.outputTokens,
112
+ usd: row.usd,
113
+ note: row.note ?? null,
114
+ })),
115
+ tier: tier ?? null,
116
+ }, text);
117
+ }
118
+ /**
119
+ * Render-only helper for the REPL slash. The slash dispatcher inside
120
+ * `session.ts` owns the side-effect of pushing system lines; this
121
+ * function builds the view and the text rendition so the slash handler
122
+ * can fan the lines into the existing `appendSystemLine` queue.
123
+ *
124
+ * Exposed here (not in the Ink module) so the slash path never imports
125
+ * Ink/React — keeps the REPL bundle slim and the slash handler async-free.
126
+ */
127
+ export function renderCostForSlash(input) {
128
+ const aggregate = input.allSessions
129
+ ? input.tracker.aggregateWithin(input.windowDays)
130
+ : (input.tracker.current() ?? emptyAggregate('no-session', input.now));
131
+ const heading = input.allSessions
132
+ ? `Pugi cost / usage — aggregate (last ${input.windowDays} days)`
133
+ : buildSessionHeading(aggregate, input.now);
134
+ const view = buildCostView({ aggregate, heading, tier: input.tier ?? undefined });
135
+ return { view, lines: renderCostTableText(view).split('\n') };
136
+ }
137
+ /**
138
+ * Derive a session id from `.pugi/events.jsonl` when the caller does not
139
+ * pass one. Walks the file once and picks the most recent `session.start`
140
+ * event's id. Falls back to `null` when the file is missing / corrupted
141
+ * — the caller substitutes a `'no-session'` placeholder so the table
142
+ * still renders an empty state instead of crashing.
143
+ */
144
+ function deriveSessionIdFromEvents(workspaceRoot) {
145
+ const path = resolve(workspaceRoot, '.pugi/events.jsonl');
146
+ if (!existsSync(path))
147
+ return null;
148
+ try {
149
+ const raw = readFileSync(path, 'utf8');
150
+ const lines = raw.split('\n').filter((line) => line.trim().length > 0);
151
+ // Walk from newest to oldest — `session.start` is rare, no reason to
152
+ // scan the whole file when the answer is at the tail.
153
+ for (let i = lines.length - 1; i >= 0; i -= 1) {
154
+ try {
155
+ const parsed = JSON.parse(lines[i]);
156
+ if (parsed.type === 'session' && parsed.name === 'start' && typeof parsed.sessionId === 'string') {
157
+ return parsed.sessionId;
158
+ }
159
+ }
160
+ catch {
161
+ // partial-write lines are ignored
162
+ }
163
+ }
164
+ }
165
+ catch {
166
+ // best-effort; absent events.jsonl is a normal first-boot state
167
+ }
168
+ return null;
169
+ }
170
+ function buildSessionHeading(aggregate, now) {
171
+ if (!aggregate || aggregate.sessionId === 'no-session' || aggregate.sessionId === 'aggregate') {
172
+ return 'Pugi cost / usage — no active session';
173
+ }
174
+ const start = Date.parse(aggregate.startedAt);
175
+ if (!Number.isFinite(start)) {
176
+ return `Pugi cost / usage — session ${aggregate.sessionId}`;
177
+ }
178
+ const elapsedMin = Math.max(0, Math.floor((now() - start) / 60_000));
179
+ return `Pugi cost / usage — session ${aggregate.sessionId} (${elapsedMin} min)`;
180
+ }
181
+ function emptyAggregate(sessionId, now) {
182
+ return {
183
+ sessionId,
184
+ startedAt: new Date(now()).toISOString(),
185
+ models: {},
186
+ };
187
+ }
188
+ async function safeResolveTier(resolver) {
189
+ try {
190
+ return await resolver();
191
+ }
192
+ catch {
193
+ return null;
194
+ }
195
+ }
196
+ // Re-export aggregate helpers so the cli.ts wire-up can read totals
197
+ // without reaching into the tracker module directly.
198
+ export { totalUsd, totalTokens };
199
+ //# sourceMappingURL=cost.js.map
@@ -10,7 +10,7 @@ export async function runDelegateCommand(args, ctx) {
10
10
  // in any position so `pugi delegate --wait dev "..."` and
11
11
  // `pugi delegate dev "..." --wait` both work; positional ordering of
12
12
  // slug + brief is preserved by filtering the flag out before the
13
- // slug/brief split (Claude P2 fix 2026-05-25).
13
+ // slug/brief split (Claude P2 fix).
14
14
  const wait = args.some((a) => a === '--wait');
15
15
  const positional = args.filter((a) => a !== '--wait');
16
16
  const slug = positional[0];
@@ -125,9 +125,9 @@ export async function runDelegateCommand(args, ctx) {
125
125
  case 'endpoint_missing':
126
126
  ctx.writeOutput({
127
127
  ok: false,
128
- error: 'runtime does not expose POST /api/pugi/sessions/:id/delegate (upgrade admin-api to α7.5+)',
128
+ error: 'runtime does not expose POST /api/pugi/sessions/:id/delegate (upgrade admin-api to +)',
129
129
  code: result.code,
130
- }, 'pugi delegate: runtime does not expose the delegate endpoint. Upgrade admin-api to α7.5+.');
130
+ }, 'pugi delegate: runtime does not expose the delegate endpoint. Upgrade admin-api to +.');
131
131
  process.exitCode = 1;
132
132
  return;
133
133
  case 'unauthenticated':
@@ -138,7 +138,7 @@ export async function runDelegateCommand(args, ctx) {
138
138
  }
139
139
  }
140
140
  /* ------------------------------------------------------------------ */
141
- /* --wait waiter */
141
+ /* --wait waiter */
142
142
  /* ------------------------------------------------------------------ */
143
143
  /**
144
144
  * Subscribe to the session SSE stream and resolve when the named
@@ -185,6 +185,11 @@ export async function waitForDelegateTerminal(config, sessionId, targetPersonaSl
185
185
  // terminal event (which carries only taskId) can be matched to the
186
186
  // delegate target without parsing the random nonce suffix.
187
187
  const taskToPersona = new Map();
188
+ // Buffer `agent.message` frames per taskId so the terminal event can
189
+ // surface the persona's full reply (Gemini P1 fix, PR). Keyed by
190
+ // taskId so a session producing multiple parallel persona dispatches
191
+ // does not cross-contaminate transcripts.
192
+ const transcriptByTask = new Map();
188
193
  const reader = response.body.getReader();
189
194
  const decoder = new TextDecoder('utf-8');
190
195
  let buffer = '';
@@ -206,6 +211,18 @@ export async function waitForDelegateTerminal(config, sessionId, targetPersonaSl
206
211
  if (parsed.type === 'agent.spawned') {
207
212
  taskToPersona.set(parsed.taskId, parsed.personaSlug);
208
213
  }
214
+ else if (parsed.type === 'agent.message') {
215
+ // Buffer the unfiltered reply by taskId. The terminal
216
+ // event below joins these into the `transcript` field
217
+ // when the matched persona reaches `agent.completed`.
218
+ const existing = transcriptByTask.get(parsed.taskId);
219
+ if (existing) {
220
+ existing.push(parsed.content);
221
+ }
222
+ else {
223
+ transcriptByTask.set(parsed.taskId, [parsed.content]);
224
+ }
225
+ }
209
226
  else if (parsed.type === 'agent.completed' ||
210
227
  parsed.type === 'agent.blocked' ||
211
228
  parsed.type === 'agent.failed') {
@@ -213,10 +230,13 @@ export async function waitForDelegateTerminal(config, sessionId, targetPersonaSl
213
230
  if (slug === targetPersonaSlug) {
214
231
  controller.abort();
215
232
  if (parsed.type === 'agent.completed') {
233
+ const frames = transcriptByTask.get(parsed.taskId) ?? [];
234
+ const transcript = frames.join('\n\n');
216
235
  return {
217
236
  kind: 'completed',
218
237
  personaSlug: slug,
219
238
  taskId: parsed.taskId,
239
+ transcript,
220
240
  };
221
241
  }
222
242
  if (parsed.type === 'agent.blocked') {
@@ -271,6 +291,9 @@ function parseDelegateFrame(data) {
271
291
  if (type === 'agent.spawned' && typeof raw.personaSlug === 'string') {
272
292
  return { type, taskId, personaSlug: raw.personaSlug };
273
293
  }
294
+ if (type === 'agent.message' && typeof raw.content === 'string') {
295
+ return { type, taskId, content: raw.content };
296
+ }
274
297
  if (type === 'agent.completed') {
275
298
  return { type, taskId };
276
299
  }
@@ -0,0 +1,126 @@
1
+ /**
2
+ * `pugi dispatch <sub>` — fork-subagent cache-handoff surface (—).
3
+ *
4
+ * Sub-commands:
5
+ *
6
+ * - `pugi dispatch list-cache-refs` — show active cache-inherit refs
7
+ * persisted under `.pugi/cache-refs/`. Renders an aligned table or
8
+ * JSON depending on `--json`.
9
+ *
10
+ * - `pugi dispatch clear-cache-refs [--older-than 1h] [-v]` — GC stale
11
+ * refs. Without `--older-than`, defaults to 24h (the same cutoff
12
+ * the REPL boot-time auto-sweep uses). The flag accepts duration
13
+ * strings the `parseDuration` helper recognises (`1h`, `30m`,
14
+ * `7d`, `500ms`, ...).
15
+ *
16
+ * The command lives in its own module (not inline in cli.ts) so the
17
+ * dispatch table stays narrow and tests can drive it without spinning
18
+ * up the full CLI.
19
+ */
20
+ import { cleanupStaleCacheRefs, parseDuration, } from '../../core/dispatch/cache-cleanup.js';
21
+ import { listCacheRefs } from '../../core/dispatch/cache-handoff.js';
22
+ const USAGE = [
23
+ 'Usage:',
24
+ ' pugi dispatch list-cache-refs',
25
+ ' Show every active subagent cache-inherit',
26
+ ' reference persisted under .pugi/cache-refs/.',
27
+ '',
28
+ ' pugi dispatch clear-cache-refs [--older-than <duration>] [-v]',
29
+ ' Evict stale refs. Accepts 500ms / 30s / 5m /',
30
+ ' 1h / 7d. Default --older-than 24h.',
31
+ ].join('\n');
32
+ export async function runDispatchCommand(args, ctx) {
33
+ const sub = args[0];
34
+ if (!sub || sub === '--help' || sub === '-h') {
35
+ ctx.writeOutput({ command: 'dispatch', usage: USAGE.split('\n') }, USAGE);
36
+ return;
37
+ }
38
+ switch (sub) {
39
+ case 'list-cache-refs':
40
+ return runListCacheRefs(ctx);
41
+ case 'clear-cache-refs':
42
+ return runClearCacheRefs(args.slice(1), ctx);
43
+ default:
44
+ ctx.writeOutput({
45
+ ok: false,
46
+ error: `unknown subcommand: ${sub}`,
47
+ usage: USAGE.split('\n'),
48
+ }, `pugi dispatch: unknown subcommand '${sub}'\n\n${USAGE}`);
49
+ process.exitCode = 2;
50
+ return;
51
+ }
52
+ }
53
+ function runListCacheRefs(ctx) {
54
+ const refs = listCacheRefs(ctx.workspaceRoot);
55
+ if (ctx.json) {
56
+ ctx.writeOutput({ ok: true, count: refs.length, refs }, '');
57
+ return;
58
+ }
59
+ if (refs.length === 0) {
60
+ ctx.writeOutput({ ok: true, count: 0, refs: [] }, 'No active cache-inherit refs under .pugi/cache-refs/.');
61
+ return;
62
+ }
63
+ // Aligned column table. Widths derived from the data — no hardcoded
64
+ // sizes so a long childAgentId does not overflow the layout.
65
+ const header = ['CHILD AGENT', 'CACHE ID', 'PARENT SESSION', 'CREATED'];
66
+ const rows = refs.map((r) => [
67
+ r.childAgentId,
68
+ r.cacheId,
69
+ r.parentSessionId,
70
+ r.createdAt,
71
+ ]);
72
+ const widths = header.map((h, i) => Math.max(h.length, ...rows.map((row) => (row[i] ?? '').length)));
73
+ const lines = [];
74
+ lines.push(header.map((h, i) => h.padEnd(widths[i] ?? 0)).join(' '));
75
+ lines.push(widths.map((w) => '-'.repeat(w)).join(' '));
76
+ for (const row of rows) {
77
+ lines.push(row.map((cell, i) => (cell ?? '').padEnd(widths[i] ?? 0)).join(' '));
78
+ }
79
+ ctx.writeOutput({ ok: true, count: refs.length, refs }, lines.join('\n'));
80
+ }
81
+ function runClearCacheRefs(args, ctx) {
82
+ let olderThanArg;
83
+ let verbose = false;
84
+ for (let i = 0; i < args.length; i++) {
85
+ const arg = args[i];
86
+ if (arg === '--older-than') {
87
+ olderThanArg = args[i + 1];
88
+ i++;
89
+ continue;
90
+ }
91
+ if (arg && arg.startsWith('--older-than=')) {
92
+ olderThanArg = arg.slice('--older-than='.length);
93
+ continue;
94
+ }
95
+ if (arg === '-v' || arg === '--verbose') {
96
+ verbose = true;
97
+ continue;
98
+ }
99
+ }
100
+ // Default 24h matches the REPL boot-time auto-sweep cutoff.
101
+ const olderThanMs = olderThanArg ? parseDuration(olderThanArg) : 24 * 60 * 60 * 1000;
102
+ if (olderThanMs === null) {
103
+ ctx.writeOutput({
104
+ ok: false,
105
+ error: `invalid --older-than value: ${olderThanArg ?? '(missing)'}`,
106
+ usage: USAGE.split('\n'),
107
+ }, `pugi dispatch clear-cache-refs: invalid --older-than '${olderThanArg ?? '(missing)'}'\n\n${USAGE}`);
108
+ process.exitCode = 2;
109
+ return;
110
+ }
111
+ const result = cleanupStaleCacheRefs(ctx.workspaceRoot, {
112
+ olderThanMs,
113
+ verbose,
114
+ });
115
+ const summary = `Removed ${result.removedCount} ref(s) (${result.corruptCount} corrupt), kept ${result.keptCount}.`;
116
+ ctx.writeOutput({
117
+ ok: true,
118
+ olderThanMs,
119
+ removedCount: result.removedCount,
120
+ keptCount: result.keptCount,
121
+ corruptCount: result.corruptCount,
122
+ removed: result.removed,
123
+ corrupt: result.corrupt,
124
+ }, summary);
125
+ }
126
+ //# sourceMappingURL=dispatch.js.map