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

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 (409) 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 +1731 -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/enter-worktree.js +250 -0
  351. package/dist/tools/exit-worktree.js +147 -0
  352. package/dist/tools/file-tools.js +161 -44
  353. package/dist/tools/lsp-tools.js +377 -1
  354. package/dist/tools/mcp-tool.js +260 -0
  355. package/dist/tools/multi-edit.js +361 -0
  356. package/dist/tools/powershell.js +268 -0
  357. package/dist/tools/registry.js +86 -4
  358. package/dist/tools/skill-tool.js +96 -0
  359. package/dist/tools/sleep.js +99 -0
  360. package/dist/tools/synthetic-output.js +133 -0
  361. package/dist/tools/tasks.js +208 -0
  362. package/dist/tools/todo-write.js +184 -0
  363. package/dist/tools/verify-plan-execution.js +295 -0
  364. package/dist/tools/web-fetch-injection-scanner.js +207 -0
  365. package/dist/tools/web-fetch.js +195 -10
  366. package/dist/tools/web-search.js +458 -0
  367. package/dist/tui/agent-progress-card.js +111 -0
  368. package/dist/tui/agent-tree.js +11 -1
  369. package/dist/tui/ask-modal.js +14 -14
  370. package/dist/tui/ask-user-question-chips.js +315 -0
  371. package/dist/tui/ask-user-question-prompt.js +203 -0
  372. package/dist/tui/compact-banner.js +81 -0
  373. package/dist/tui/conversation-pane.js +85 -11
  374. package/dist/tui/cost-table.js +111 -0
  375. package/dist/tui/device-flow.js +2 -2
  376. package/dist/tui/doctor-table.js +46 -0
  377. package/dist/tui/feedback-prompt.js +156 -0
  378. package/dist/tui/input-box.js +247 -32
  379. package/dist/tui/login-picker.js +3 -3
  380. package/dist/tui/markdown-render.js +6 -6
  381. package/dist/tui/onboarding-wizard.js +240 -0
  382. package/dist/tui/permissions-picker.js +86 -0
  383. package/dist/tui/render.js +36 -1
  384. package/dist/tui/repl-render.js +176 -25
  385. package/dist/tui/repl-splash-art.js +16 -16
  386. package/dist/tui/repl-splash-mascot.js +48 -24
  387. package/dist/tui/repl-splash.js +22 -22
  388. package/dist/tui/repl.js +125 -45
  389. package/dist/tui/slash-palette.js +6 -6
  390. package/dist/tui/splash.js +2 -2
  391. package/dist/tui/status-bar.js +109 -31
  392. package/dist/tui/status-table.js +7 -0
  393. package/dist/tui/stickers-art.js +136 -0
  394. package/dist/tui/style-table.js +28 -0
  395. package/dist/tui/theme-table.js +29 -0
  396. package/dist/tui/thinking-spinner.js +123 -0
  397. package/dist/tui/tool-stream-pane.js +53 -4
  398. package/dist/tui/update-banner.js +27 -2
  399. package/dist/tui/vim-input.js +267 -0
  400. package/dist/tui/welcome-banner.js +107 -0
  401. package/dist/tui/welcome-data.js +293 -0
  402. package/dist/tui/workspace-context.js +2 -2
  403. package/package.json +31 -16
  404. package/test/scenarios/codegen-create-file.scenario.txt +13 -0
  405. package/test/scenarios/compact-force.scenario.txt +12 -0
  406. package/test/scenarios/identity.scenario.txt +12 -0
  407. package/test/scenarios/persona-handoff.scenario.txt +12 -0
  408. package/test/scenarios/walkback.scenario.txt +12 -0
  409. package/dist/core/engine/compaction-hook.js +0 -154
@@ -0,0 +1,361 @@
1
+ /**
2
+ * multi_edit tool — β7 .
3
+ *
4
+ * Dispatches an ordered batch of file edits as a single transaction. Each
5
+ * edit is one Layer A (oldString -> newString) operation against one
6
+ * workspace file. Either every edit lands, or none do — failures roll
7
+ * the workspace back to the pre-dispatch state using the same journal +
8
+ * snapshot machinery the β1b Pl8 transactional layer uses for the
9
+ * marker-driven dispatcher.
10
+ *
11
+ * Why multi_edit when `edit` already exists:
12
+ *
13
+ * The single-shot `edit` tool is the right primitive for one mutation;
14
+ * the model uses it dozens of times in a typical session. A coordinated
15
+ * refactor (rename across 8 files, add an import to 12 modules, peel a
16
+ * helper into 5 callers) is currently 8/12/5 separate `edit` calls.
17
+ * Each call is its own audit + permission check + atomic write, which
18
+ * is the right shape for the audit story but means the model can leave
19
+ * the workspace half-mutated when one of the calls fails partway. The
20
+ * model also pays the round-trip latency once per call.
21
+ *
22
+ * `multi_edit` collapses the 8/12/5 calls into one tool dispatch with
23
+ * transactional semantics: snapshot every target file, attempt every
24
+ * edit against an in-memory buffer, then commit the writes only after
25
+ * all in-memory edits succeed. A failure rolls back via journal +
26
+ * in-memory snapshot — same code path as the dispatcher.
27
+ *
28
+ * Security: every target file routes through the same `applySecurityGate`
29
+ * chokepoint Layer A/B/C inherit. A path that escapes the workspace,
30
+ * points at a protected basename (`.env`, `*.pem`, ...), or symlinks
31
+ * outside the tree is refused BEFORE any read.
32
+ *
33
+ * Concurrency: marked `concurrencySafe: false` in the tool registry. The
34
+ * model MUST NOT issue another `multi_edit` (or any write tool) in
35
+ * parallel with one in flight; the journal serialises one dispatch per
36
+ * session.
37
+ *
38
+ * Output cap: a 50-edit batch is the soft ceiling. Beyond that the tool
39
+ * refuses with `too_many_edits` — the operator can split the refactor.
40
+ * Empirically a coordinated refactor that needs 50+ atomic edits should
41
+ * be a per-file Layer C rewrite instead.
42
+ *
43
+ * Brand voice: ASCII only, no emoji, no banned words.
44
+ */
45
+ import { existsSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
46
+ import { applySecurityGate } from '../core/edits/security-gate.js';
47
+ import { appendEntry, snapshotForDispatch, } from '../core/edits/journal.js';
48
+ import { rollbackDispatch } from '../core/edits/dispatch.js';
49
+ import { gateOnCancellation, OperatorAbortedError } from './file-tools.js';
50
+ import { recordFileMutation, recordToolCall, recordToolResult } from '../core/session.js';
51
+ /** Soft cap on per-dispatch edit count. See module docstring. */
52
+ const MULTI_EDIT_MAX = 50;
53
+ /**
54
+ * Apply a batch of file edits transactionally. Returns a structured
55
+ * result; never throws on operator-attributable failure (security,
56
+ * missing file, no_match) — only on infrastructure error (filesystem
57
+ * permission denied mid-write after the snapshot, etc.).
58
+ */
59
+ export function multiEdit(ctx, edits, opts = {}) {
60
+ const toolCallId = recordToolCall(ctx.session, 'multi_edit', `${edits.length} edits across ${new Set(edits.map((e) => e.file)).size} files`);
61
+ try {
62
+ gateOnCancellation(ctx, 'multi_edit');
63
+ }
64
+ catch (error) {
65
+ if (error instanceof OperatorAbortedError) {
66
+ recordToolResult(ctx.session, toolCallId, 'cancelled', error.message);
67
+ throw error;
68
+ }
69
+ throw error;
70
+ }
71
+ if (edits.length === 0) {
72
+ const result = {
73
+ ok: false,
74
+ filesChanged: [],
75
+ editsApplied: 0,
76
+ reason: 'empty_batch',
77
+ detail: 'multi_edit received zero edits',
78
+ perEdit: [],
79
+ };
80
+ recordToolResult(ctx.session, toolCallId, 'error', 'empty_batch');
81
+ return result;
82
+ }
83
+ if (edits.length > MULTI_EDIT_MAX) {
84
+ const result = {
85
+ ok: false,
86
+ filesChanged: [],
87
+ editsApplied: 0,
88
+ reason: 'too_many_edits',
89
+ detail: `multi_edit batch of ${edits.length} exceeds cap ${MULTI_EDIT_MAX}; split the refactor`,
90
+ perEdit: [],
91
+ };
92
+ recordToolResult(ctx.session, toolCallId, 'error', 'too_many_edits');
93
+ return result;
94
+ }
95
+ // SECURITY GATE pass over every distinct file BEFORE any read.
96
+ // A single rejected file aborts the whole batch — the transactional
97
+ // contract requires we never partial-mutate.
98
+ const uniqueFiles = Array.from(new Set(edits.map((e) => e.file)));
99
+ const resolvedByFile = new Map();
100
+ for (const f of uniqueFiles) {
101
+ const gate = applySecurityGate(f, { cwd: ctx.root, toolName: 'layer-c' });
102
+ if (!gate.ok) {
103
+ const result = {
104
+ ok: false,
105
+ filesChanged: [],
106
+ editsApplied: 0,
107
+ reason: gate.reason,
108
+ detail: `${f}: ${gate.detail}`,
109
+ perEdit: edits.map((e, i) => ({
110
+ index: i,
111
+ file: e.file,
112
+ ok: false,
113
+ reason: gate.reason,
114
+ detail: e.file === f ? gate.detail : 'batch aborted by sibling security failure',
115
+ })),
116
+ };
117
+ recordToolResult(ctx.session, toolCallId, 'error', `${gate.reason}: ${f}`);
118
+ return result;
119
+ }
120
+ resolvedByFile.set(f, gate.absPath);
121
+ }
122
+ // Snapshot existing files BEFORE any in-memory edit so a partial-write
123
+ // rollback is deterministic. The snapshot also captures sha256 of each
124
+ // pre-existing file so post-failure restore can verify the in-memory
125
+ // buffer still matches.
126
+ const snapshot = snapshotForDispatch(ctx.root, uniqueFiles);
127
+ const preContent = new Map();
128
+ for (const entry of snapshot) {
129
+ if (!entry.existed)
130
+ continue;
131
+ const abs = resolvedByFile.get(entry.path);
132
+ if (!abs)
133
+ continue;
134
+ try {
135
+ preContent.set(entry.path, readFileSync(abs));
136
+ }
137
+ catch {
138
+ // Best-effort. A read failure here will surface again when the
139
+ // per-edit phase tries to read the same file — let that path
140
+ // produce the operator-facing error.
141
+ }
142
+ }
143
+ // In-memory edit phase. For each edit we work on the latest version
144
+ // of the file (so two edits against the same file stack). Failure
145
+ // here is the common case — `no_match`, `ambiguous_match`, missing
146
+ // file — and aborts the whole batch.
147
+ const bodyByFile = new Map();
148
+ const perEdit = [];
149
+ for (let i = 0; i < edits.length; i += 1) {
150
+ const edit = edits[i];
151
+ const abs = resolvedByFile.get(edit.file);
152
+ if (!abs) {
153
+ // Should be unreachable — every distinct file went through the
154
+ // gate above. Belt + braces.
155
+ perEdit.push({ index: i, file: edit.file, ok: false, reason: 'write_error', detail: 'no resolved path' });
156
+ const result = {
157
+ ok: false,
158
+ filesChanged: [],
159
+ editsApplied: 0,
160
+ reason: 'write_error',
161
+ detail: `${edit.file}: no resolved path`,
162
+ perEdit,
163
+ };
164
+ recordToolResult(ctx.session, toolCallId, 'error', 'write_error');
165
+ return result;
166
+ }
167
+ let body = bodyByFile.get(edit.file);
168
+ if (body === undefined) {
169
+ if (!existsSync(abs)) {
170
+ const detail = `file does not exist: ${edit.file}`;
171
+ perEdit.push({ index: i, file: edit.file, ok: false, reason: 'file_missing', detail });
172
+ const result = {
173
+ ok: false,
174
+ filesChanged: [],
175
+ editsApplied: 0,
176
+ reason: 'file_missing',
177
+ detail,
178
+ perEdit,
179
+ };
180
+ recordToolResult(ctx.session, toolCallId, 'error', 'file_missing');
181
+ return result;
182
+ }
183
+ try {
184
+ body = readFileSync(abs, 'utf8');
185
+ }
186
+ catch (error) {
187
+ const detail = error instanceof Error ? error.message : String(error);
188
+ perEdit.push({ index: i, file: edit.file, ok: false, reason: 'write_error', detail });
189
+ const result = {
190
+ ok: false,
191
+ filesChanged: [],
192
+ editsApplied: 0,
193
+ reason: 'write_error',
194
+ detail: `${edit.file}: ${detail}`,
195
+ perEdit,
196
+ };
197
+ recordToolResult(ctx.session, toolCallId, 'error', 'write_error');
198
+ return result;
199
+ }
200
+ }
201
+ if (edit.oldString === edit.newString) {
202
+ perEdit.push({
203
+ index: i,
204
+ file: edit.file,
205
+ ok: false,
206
+ reason: 'identical_replacement',
207
+ detail: 'oldString and newString are identical',
208
+ });
209
+ const result = {
210
+ ok: false,
211
+ filesChanged: [],
212
+ editsApplied: 0,
213
+ reason: 'identical_replacement',
214
+ detail: `edit ${i} (${edit.file}): oldString and newString are identical`,
215
+ perEdit,
216
+ };
217
+ recordToolResult(ctx.session, toolCallId, 'error', 'identical_replacement');
218
+ return result;
219
+ }
220
+ const matches = countOccurrences(body, edit.oldString);
221
+ if (matches === 0) {
222
+ const detail = `edit ${i} (${edit.file}): oldString not found`;
223
+ perEdit.push({ index: i, file: edit.file, ok: false, reason: 'no_match', detail });
224
+ const result = {
225
+ ok: false,
226
+ filesChanged: [],
227
+ editsApplied: 0,
228
+ reason: 'no_match',
229
+ detail,
230
+ perEdit,
231
+ };
232
+ recordToolResult(ctx.session, toolCallId, 'error', 'no_match');
233
+ return result;
234
+ }
235
+ if (matches > 1) {
236
+ const detail = `edit ${i} (${edit.file}): oldString matches ${matches} times — expand context to make it unique`;
237
+ perEdit.push({ index: i, file: edit.file, ok: false, reason: 'ambiguous_match', detail });
238
+ const result = {
239
+ ok: false,
240
+ filesChanged: [],
241
+ editsApplied: 0,
242
+ reason: 'ambiguous_match',
243
+ detail,
244
+ perEdit,
245
+ };
246
+ recordToolResult(ctx.session, toolCallId, 'error', 'ambiguous_match');
247
+ return result;
248
+ }
249
+ body = body.replace(edit.oldString, edit.newString);
250
+ bodyByFile.set(edit.file, body);
251
+ perEdit.push({ index: i, file: edit.file, ok: true });
252
+ }
253
+ if (opts.dryRun) {
254
+ const result = {
255
+ ok: true,
256
+ filesChanged: Array.from(bodyByFile.keys()),
257
+ editsApplied: edits.length,
258
+ perEdit,
259
+ };
260
+ recordToolResult(ctx.session, toolCallId, 'success', `dry-run ${edits.length} edits ok`);
261
+ return result;
262
+ }
263
+ // Persist the snapshot to the journal BEFORE the first write. A crash
264
+ // mid-write then has a recoverable trail in `.pugi/sessions/<id>/journal.jsonl`.
265
+ // Best-effort; a journal write failure does not block the edits (the
266
+ // in-memory rollback path still covers same-process failures).
267
+ if (ctx.session.enabled) {
268
+ appendEntry(ctx.root, ctx.session.id, {
269
+ ts: Date.now(),
270
+ taskId: `multi_edit-${toolCallId}`,
271
+ files: snapshot,
272
+ });
273
+ }
274
+ // Commit phase. Atomic writes one file at a time. A failure rolls
275
+ // back via the same dispatcher rollback used by the marker layer.
276
+ const written = [];
277
+ for (const [file, body] of bodyByFile) {
278
+ const abs = resolvedByFile.get(file);
279
+ try {
280
+ atomicWrite(abs, body);
281
+ written.push(file);
282
+ }
283
+ catch (error) {
284
+ const detail = error instanceof Error ? error.message : String(error);
285
+ // Roll back every file we already touched plus restore the
286
+ // not-yet-touched ones that existed before (defensive — the
287
+ // rollback function is idempotent on untouched paths).
288
+ const rollback = rollbackDispatch(ctx.root, snapshot, preContent);
289
+ if (!rollback.ok) {
290
+ const result = {
291
+ ok: false,
292
+ filesChanged: [],
293
+ editsApplied: 0,
294
+ reason: 'rollback_failed',
295
+ detail: `${file}: ${detail}; rollback also failed: ${rollback.detail}`,
296
+ perEdit,
297
+ };
298
+ recordToolResult(ctx.session, toolCallId, 'error', 'rollback_failed');
299
+ return result;
300
+ }
301
+ const result = {
302
+ ok: false,
303
+ filesChanged: [],
304
+ editsApplied: 0,
305
+ reason: 'write_error',
306
+ detail: `${file}: ${detail}`,
307
+ perEdit,
308
+ };
309
+ recordToolResult(ctx.session, toolCallId, 'error', `write_error: ${detail}`);
310
+ return result;
311
+ }
312
+ }
313
+ for (const file of written) {
314
+ recordFileMutation(ctx.session, {
315
+ toolCallId,
316
+ path: file,
317
+ operation: 'update',
318
+ });
319
+ }
320
+ recordToolResult(ctx.session, toolCallId, 'success', `applied ${edits.length} edits across ${written.length} files`);
321
+ return {
322
+ ok: true,
323
+ filesChanged: written,
324
+ editsApplied: edits.length,
325
+ perEdit,
326
+ };
327
+ }
328
+ function countOccurrences(haystack, needle) {
329
+ if (needle.length === 0)
330
+ return 0;
331
+ let count = 0;
332
+ let from = 0;
333
+ while (true) {
334
+ const idx = haystack.indexOf(needle, from);
335
+ if (idx === -1)
336
+ return count;
337
+ count += 1;
338
+ from = idx + needle.length;
339
+ }
340
+ }
341
+ /** Atomic write helper — mirrors Layer A / Layer D. */
342
+ function atomicWrite(absPath, contents) {
343
+ const suffix = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
344
+ const tmp = `${absPath}.pugi-tmp-${suffix}`;
345
+ try {
346
+ writeFileSync(tmp, contents, { encoding: 'utf8', mode: 0o600 });
347
+ renameSync(tmp, absPath);
348
+ }
349
+ catch (error) {
350
+ try {
351
+ unlinkSync(tmp);
352
+ }
353
+ catch {
354
+ // tmp file may not exist if writeFileSync itself failed.
355
+ }
356
+ throw error;
357
+ }
358
+ }
359
+ /** Test-only surface. */
360
+ export const __test__ = { MULTI_EDIT_MAX };
361
+ //# sourceMappingURL=multi-edit.js.map
@@ -0,0 +1,268 @@
1
+ /**
2
+ * PowerShell tool — .
3
+ *
4
+ * Windows operators cannot run native `*.ps1` scripts via the bash tool
5
+ * (which spawns `/bin/sh`). This tool spawns `pwsh -NoProfile -Command`
6
+ * на cross-platform PowerShell 7+ binary so Windows-first workflows are
7
+ * first-class на Pugi.
8
+ *
9
+ * independent implementation re-implementation. Surface mirrors bashTool's permission
10
+ * gate, env sanitiser, output cap, timeout, and exit-code propagation;
11
+ * the only difference is the shell binary selection. Per-platform
12
+ * resolution:
13
+ * - All OS: try `pwsh` on $PATH first (PowerShell 7+ cross-platform).
14
+ * - Windows fallback: `powershell.exe` (Windows PowerShell 5.1 baked-in).
15
+ * - Other OS without pwsh: tool returns a clear "powershell binary
16
+ * not found" error so the operator can install pwsh or fall back
17
+ * к bash.
18
+ *
19
+ * Permission class: reuses the bash classifier — destructive patterns,
20
+ * sandbox detection, and additional-directories checks are command-string
21
+ * based and apply equally to pwsh and sh.
22
+ */
23
+ import { spawnSync } from 'node:child_process';
24
+ import { listDestructivePatterns } from '../core/bash-classifier.js';
25
+ import { recordToolCall, recordToolResult } from '../core/session.js';
26
+ export const POWERSHELL_OUTPUT_CAP_BYTES = 64 * 1024;
27
+ export const POWERSHELL_DEFAULT_TIMEOUT_MS = 30_000;
28
+ export const POWERSHELL_MAX_TIMEOUT_MS = 120_000;
29
+ /**
30
+ * PowerShell-specific destructive patterns. Layered ON TOP of the
31
+ * shared `listDestructivePatterns()` from the bash classifier (which
32
+ * covers `rm -rf`, `DROP TABLE`, etc — patterns that also surface в
33
+ * pwsh-via-aliases). These are the cmdlet forms unique to pwsh.
34
+ *
35
+ * Patterns are case-insensitive matched against the command string
36
+ * (pwsh cmdlets accept any case: `remove-item -force` == `Remove-Item -Force`).
37
+ */
38
+ const PWSH_DESTRUCTIVE_PATTERNS = [
39
+ // Recursive force delete via cmdlet
40
+ 'remove-item -recurse -force',
41
+ 'remove-item -force -recurse',
42
+ 'ri -recurse -force',
43
+ 'ri -force -recurse',
44
+ 'rmdir -recurse -force',
45
+ 'rmdir -force -recurse',
46
+ // Disk / volume operations
47
+ 'format-volume',
48
+ 'clear-disk',
49
+ 'reset-physicaldisk',
50
+ // System state
51
+ 'stop-computer',
52
+ 'restart-computer',
53
+ 'shutdown',
54
+ // Security weakening
55
+ 'set-executionpolicy unrestricted',
56
+ 'set-executionpolicy bypass',
57
+ // Service / process attack surface
58
+ 'invoke-webrequest', // common phishing-script vector when piped to iex
59
+ 'iex (new-object', // download-execute pattern
60
+ // Credential exfil
61
+ 'get-credential | export-clixml',
62
+ ];
63
+ /**
64
+ * Normalize whitespace before pattern matching: collapse runs of
65
+ * whitespace к single space + lowercase. Defends against the
66
+ * `iex(New-Object`/`IEX (New-Object` style bypass where pattern
67
+ * `iex (new-object` would miss the no-space or double-space variant.
68
+ */
69
+ function normalizeForMatch(text) {
70
+ return text.toLowerCase().replace(/\s+/g, ' ');
71
+ }
72
+ function findPwshDestructiveMatch(cmd) {
73
+ const normalized = normalizeForMatch(cmd);
74
+ for (const pattern of PWSH_DESTRUCTIVE_PATTERNS) {
75
+ if (normalized.includes(normalizeForMatch(pattern)))
76
+ return pattern;
77
+ }
78
+ // Fall back к the shared bash destructive list (covers cross-shell
79
+ // patterns like `rm -rf /`, `DROP DATABASE`). Shared patterns may
80
+ // contain uppercase (case-insensitive SQL verbs); normalize both
81
+ // sides before compare.
82
+ const shared = listDestructivePatterns();
83
+ for (const pattern of shared) {
84
+ if (normalized.includes(normalizeForMatch(pattern)))
85
+ return pattern;
86
+ }
87
+ return null;
88
+ }
89
+ /**
90
+ * PowerShell-aware permission decision. Differs from
91
+ * `evaluateBashPermission` в two ways:
92
+ *
93
+ * 1. Default class is `allow` (after destructive check) instead of
94
+ * `unknown → deny`. The bash classifier rejects any first-token
95
+ * it does not recognise — appropriate for bash where every verb
96
+ * is a separate binary, hostile for pwsh where the Verb-Noun
97
+ * cmdlet convention means thousands of legitimate verbs exist
98
+ * (`Get-Process`, `$PSVersionTable`, `Select-Object`, ...).
99
+ *
100
+ * 2. Destructive patterns combine the shared bash denylist (covers
101
+ * cross-shell patterns like `rm -rf`) с pwsh-specific cmdlet
102
+ * forms (`Remove-Item -Recurse -Force`, `Format-Volume`, etc).
103
+ *
104
+ * Mode FSM mirrors bash: plan → deny ALL, ask → ask, auto/bypass → allow,
105
+ * destructive class → deny unless `bypassPermissions + human + ENV override`.
106
+ */
107
+ function evaluatePwshPermission(cmd, mode, source) {
108
+ const destructive = findPwshDestructiveMatch(cmd);
109
+ if (destructive !== null) {
110
+ const overrideOk = mode === 'bypassPermissions' &&
111
+ source === 'human' &&
112
+ process.env['PUGI_DESTRUCTIVE_OVERRIDE'] === '1';
113
+ if (overrideOk) {
114
+ return {
115
+ decision: 'allow',
116
+ reason: `destructive pwsh pattern '${destructive}' allowed via override (bypassPermissions + human + PUGI_DESTRUCTIVE_OVERRIDE=1)`,
117
+ };
118
+ }
119
+ return {
120
+ decision: 'deny',
121
+ reason: `destructive pwsh pattern '${destructive}' is always denied (override requires bypassPermissions + human + PUGI_DESTRUCTIVE_OVERRIDE=1)`,
122
+ };
123
+ }
124
+ // Non-destructive pwsh command — mode FSM.
125
+ switch (mode) {
126
+ case 'plan':
127
+ return { decision: 'deny', reason: 'plan mode denies all shell dispatches' };
128
+ case 'ask':
129
+ case 'acceptEdits':
130
+ return { decision: 'ask', reason: 'pwsh command requires operator confirmation' };
131
+ case 'auto':
132
+ case 'dontAsk':
133
+ case 'bypassPermissions':
134
+ return { decision: 'allow', reason: 'pwsh command allowed by mode' };
135
+ default:
136
+ return { decision: 'ask', reason: `unknown mode ${mode}; defaulting к ask` };
137
+ }
138
+ }
139
+ /** Cached binary path so repeated calls inside one session skip the probe. */
140
+ let cachedShellBinary;
141
+ function resolveShellBinary() {
142
+ if (cachedShellBinary !== undefined)
143
+ return cachedShellBinary;
144
+ // Try pwsh (cross-platform PowerShell 7+) first.
145
+ const pwshProbe = spawnSync('pwsh', ['-NoProfile', '-Command', 'exit 0'], {
146
+ encoding: 'utf8',
147
+ stdio: ['ignore', 'ignore', 'ignore'],
148
+ timeout: 3000,
149
+ });
150
+ if (pwshProbe.status === 0) {
151
+ cachedShellBinary = 'pwsh';
152
+ return 'pwsh';
153
+ }
154
+ // Windows fallback к the baked-in PowerShell 5.1.
155
+ if (process.platform === 'win32') {
156
+ const wpsProbe = spawnSync('powershell.exe', ['-NoProfile', '-Command', 'exit 0'], {
157
+ encoding: 'utf8',
158
+ stdio: ['ignore', 'ignore', 'ignore'],
159
+ timeout: 3000,
160
+ });
161
+ if (wpsProbe.status === 0) {
162
+ cachedShellBinary = 'powershell.exe';
163
+ return 'powershell.exe';
164
+ }
165
+ }
166
+ cachedShellBinary = null;
167
+ return null;
168
+ }
169
+ function sanitizeTimeout(value) {
170
+ if (value === undefined || !Number.isFinite(value) || value <= 0) {
171
+ return POWERSHELL_DEFAULT_TIMEOUT_MS;
172
+ }
173
+ return Math.min(value, POWERSHELL_MAX_TIMEOUT_MS);
174
+ }
175
+ function buildChildEnv() {
176
+ const env = { ...process.env };
177
+ delete env['PUGI_API_KEY'];
178
+ delete env['PUGI_LOGIN_TOKEN'];
179
+ return env;
180
+ }
181
+ /**
182
+ * Sync PowerShell dispatch. Mirrors bashToolSync shape so dispatchTool
183
+ * can call either tool with the same context shape.
184
+ */
185
+ export function powerShellToolSync(input, ctx) {
186
+ const cmd = input.cmd ?? '';
187
+ const source = ctx.source ?? 'agent';
188
+ const toolCallId = recordToolCall(ctx.session, 'powershell', cmd);
189
+ // pwsh-aware permission gate (NOT the bash classifier). Bash classifier
190
+ // would reject `$PSVersionTable`, `Get-Process`, etc as "Unrecognized
191
+ // command" → default-deny, making the pwsh tool useless. The pwsh gate
192
+ // applies the shared destructive denylist (rm -rf / DROP TABLE) + a
193
+ // pwsh-specific list (Remove-Item -Recurse -Force / Format-Volume /
194
+ // Set-ExecutionPolicy Unrestricted / iex (New-Object ...)) and
195
+ // defaults non-destructive cmdlets к allow under mode FSM.
196
+ const decision = evaluatePwshPermission(cmd, ctx.settings.permissions.mode, source);
197
+ if (decision.decision !== 'allow') {
198
+ const reason = `Permission ${decision.decision}: ${decision.reason}`;
199
+ recordToolResult(ctx.session, toolCallId, 'error', reason);
200
+ return {
201
+ stdout: '',
202
+ stderr: `Permission ${decision.decision}: ${decision.reason}`,
203
+ exitCode: 126,
204
+ truncated: false,
205
+ timedOut: false,
206
+ shellBinary: 'unresolved',
207
+ };
208
+ }
209
+ const shellBinary = resolveShellBinary();
210
+ if (shellBinary === null) {
211
+ const reason = 'powershell binary not found (tried pwsh' +
212
+ (process.platform === 'win32' ? ', powershell.exe' : '') +
213
+ '). Install PowerShell 7+ from https://aka.ms/powershell or use the bash tool instead.';
214
+ recordToolResult(ctx.session, toolCallId, 'error', reason);
215
+ return {
216
+ stdout: '',
217
+ stderr: reason,
218
+ exitCode: 127,
219
+ truncated: false,
220
+ timedOut: false,
221
+ shellBinary: 'unavailable',
222
+ };
223
+ }
224
+ const timeoutMs = sanitizeTimeout(input.timeoutMs);
225
+ const childEnv = buildChildEnv();
226
+ const cwd = input.cwd ?? ctx.root;
227
+ const result = spawnSync(shellBinary, ['-NoProfile', '-Command', cmd], {
228
+ cwd,
229
+ env: childEnv,
230
+ encoding: 'utf8',
231
+ stdio: ['ignore', 'pipe', 'pipe'],
232
+ timeout: timeoutMs,
233
+ maxBuffer: 10 * 1024 * 1024,
234
+ });
235
+ const stdoutFull = (result.stdout ?? '').toString();
236
+ const stderrFull = (result.stderr ?? '').toString();
237
+ const combined = stdoutFull.length + stderrFull.length;
238
+ const truncated = combined > POWERSHELL_OUTPUT_CAP_BYTES;
239
+ let stdoutOut = stdoutFull;
240
+ let stderrOut = stderrFull;
241
+ if (truncated) {
242
+ const halfCap = POWERSHELL_OUTPUT_CAP_BYTES / 2;
243
+ stdoutOut = stdoutFull.slice(0, halfCap);
244
+ stderrOut = stderrFull.slice(0, halfCap);
245
+ }
246
+ const timedOut = result.error?.code === 'ETIMEDOUT' ||
247
+ result.signal === 'SIGTERM';
248
+ const exitCode = timedOut ? 124 : result.status ?? 1;
249
+ if (timedOut) {
250
+ recordToolResult(ctx.session, toolCallId, 'error', `powershell timed out after ${timeoutMs}ms`);
251
+ }
252
+ else {
253
+ recordToolResult(ctx.session, toolCallId, 'success', `powershell exit=${exitCode} bytes=${combined} binary=${shellBinary}`);
254
+ }
255
+ return {
256
+ stdout: stdoutOut,
257
+ stderr: stderrOut,
258
+ exitCode,
259
+ truncated,
260
+ timedOut,
261
+ shellBinary,
262
+ };
263
+ }
264
+ /** Visible-for-spec helper: forces a re-probe on next call. */
265
+ export function _resetShellBinaryCacheForSpec() {
266
+ cachedShellBinary = undefined;
267
+ }
268
+ //# sourceMappingURL=powershell.js.map