@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
@@ -1,5 +1,7 @@
1
+ import { callHierarchyAt, codeActionsAt, diagnosticsFor, findDefinition as symbolsFindDefinition, findImplementations, findReferences as symbolsFindReferences, findTypeDefinition, findWorkspaceSymbol, formatFile, hoverSymbol, listDocumentSymbols, renameSymbol, signatureAt, transportFromLspClient, } from '../core/lsp/symbol-tools.js';
1
2
  import { gateOnCancellation, OperatorAbortedError } from './file-tools.js';
2
3
  import { recordToolCall, recordToolResult } from '../core/session.js';
4
+ import { getGlobalSymbolCache, SymbolCache } from '../core/lsp/symbol-cache.js';
3
5
  /** Cap for any single LSP tool's payload size. Keeps model context lean. */
4
6
  const LSP_PAYLOAD_CAP_BYTES = 8 * 1024;
5
7
  export async function lspHover(ctx, lang, file, line, col) {
@@ -184,6 +186,380 @@ function capDiagnostics(items) {
184
186
  }
185
187
  return { value: items.slice(0, 1), truncated: true };
186
188
  }
189
+ /* ------------------------------------------------------------------------- */
190
+ /* PUGI-78 Phase 1: symbols.* namespace tools (13 categories). */
191
+ /* ------------------------------------------------------------------------- */
192
+ /**
193
+ * PUGI-78 Phase 1: symbols.find_definition.
194
+ *
195
+ * Pugi reads a whole file (~5-50 KB tokens) every time the model wants
196
+ * to know "where is foo defined?". The LSP wire here returns ~200 bytes
197
+ * (file + line + character). Two orders of magnitude reduction in token
198
+ * cost per refactor turn, hence the BIG-leverage classification in the
199
+ * spec (10-100x token savings per refactor).
200
+ *
201
+ * Failure folds to `lsp_unavailable` when no client is registered for
202
+ * the inferred language. The agent surface is expected to fall back to
203
+ * grep when this fires (the tool-preference prompt rule steers it).
204
+ */
205
+ export async function symbolsFindDefinitionTool(ctx, lang, file, line, col) {
206
+ const toolCallId = recordToolCall(ctx.session, 'symbols_find_definition', `${lang}:${file}:${line}:${col}`);
207
+ return guard(ctx, 'symbols_find_definition', toolCallId, async () => {
208
+ const client = ctx.lspClients?.get(lang);
209
+ if (!client)
210
+ return unavailable(lang);
211
+ const cache = getSymbolCacheFromCtx(ctx);
212
+ const cwd = workspaceForCache(ctx);
213
+ const key = SymbolCache.makeKey(lang, cwd, 'find_definition', { file, line, col });
214
+ const cached = cache?.get(key);
215
+ if (cached !== undefined) {
216
+ if (cached === null)
217
+ return notFound('definition');
218
+ return { ok: true, value: cached };
219
+ }
220
+ const result = await symbolsFindDefinition(transportFromLspClient(client), file, line, col);
221
+ cache?.set(key, result);
222
+ if (!result)
223
+ return notFound('definition');
224
+ return { ok: true, value: result };
225
+ });
226
+ }
227
+ /**
228
+ * PUGI-78 Phase 1: symbols.find_references.
229
+ *
230
+ * Returns the flat list of call sites for the symbol at (line, col).
231
+ * The 200-row cap from the legacy `lspReferences` carries over via
232
+ * `capSymbolReferences`.
233
+ */
234
+ export async function symbolsFindReferencesTool(ctx, lang, file, line, col) {
235
+ const toolCallId = recordToolCall(ctx.session, 'symbols_find_references', `${lang}:${file}:${line}:${col}`);
236
+ return guard(ctx, 'symbols_find_references', toolCallId, async () => {
237
+ const client = ctx.lspClients?.get(lang);
238
+ if (!client)
239
+ return unavailable(lang);
240
+ const cache = getSymbolCacheFromCtx(ctx);
241
+ const cwd = workspaceForCache(ctx);
242
+ const key = SymbolCache.makeKey(lang, cwd, 'find_references', { file, line, col });
243
+ const cached = cache?.get(key);
244
+ if (cached !== undefined) {
245
+ const capped = capSymbolReferences(cached);
246
+ return { ok: true, value: capped.value, ...(capped.truncated ? { truncated: true } : {}) };
247
+ }
248
+ const result = await symbolsFindReferences(transportFromLspClient(client), file, line, col);
249
+ cache?.set(key, result);
250
+ const capped = capSymbolReferences(result);
251
+ return { ok: true, value: capped.value, ...(capped.truncated ? { truncated: true } : {}) };
252
+ });
253
+ }
254
+ /**
255
+ * PUGI-78 Phase 1: symbols.list_in_file.
256
+ *
257
+ * Outline view — the document-symbol surface. Returns the flat list of
258
+ * top-level + nested symbols with their kind. Cache HIT on the same
259
+ * file across consecutive turns is the common case (the model usually
260
+ * lists, then dives into a single symbol).
261
+ */
262
+ export async function symbolsListInFileTool(ctx, lang, file) {
263
+ const toolCallId = recordToolCall(ctx.session, 'symbols_list_in_file', `${lang}:${file}`);
264
+ return guard(ctx, 'symbols_list_in_file', toolCallId, async () => {
265
+ const client = ctx.lspClients?.get(lang);
266
+ if (!client)
267
+ return unavailable(lang);
268
+ const cache = getSymbolCacheFromCtx(ctx);
269
+ const cwd = workspaceForCache(ctx);
270
+ const key = SymbolCache.makeKey(lang, cwd, 'list_in_file', { file });
271
+ const cached = cache?.get(key);
272
+ if (cached !== undefined) {
273
+ return { ok: true, value: [...cached] };
274
+ }
275
+ const result = await listDocumentSymbols(transportFromLspClient(client), file);
276
+ cache?.set(key, result);
277
+ return { ok: true, value: result };
278
+ });
279
+ }
280
+ /**
281
+ * PUGI-78 Phase 1: symbols.rename.
282
+ *
283
+ * Returns the workspace-edit preview the server proposes. The
284
+ * dispatcher applies via `apply_patch` in a future ticket; Phase 1 is
285
+ * read-only — the agent surface gets the affected file list + per-edit
286
+ * line/character so the model can summarize the change.
287
+ */
288
+ export async function symbolsRenameTool(ctx, lang, file, line, col, newName) {
289
+ const toolCallId = recordToolCall(ctx.session, 'symbols_rename', `${lang}:${file}:${line}:${col}:${newName}`);
290
+ return guard(ctx, 'symbols_rename', toolCallId, async () => {
291
+ const client = ctx.lspClients?.get(lang);
292
+ if (!client)
293
+ return unavailable(lang);
294
+ // No cache for rename — the edit set depends on the live source
295
+ // file state. A subsequent rename of the same symbol after the
296
+ // first applied would otherwise return stale edits.
297
+ const result = await renameSymbol(transportFromLspClient(client), file, line, col, newName);
298
+ if (!result)
299
+ return notFound('rename');
300
+ return { ok: true, value: result };
301
+ });
302
+ }
303
+ /**
304
+ * PUGI-78 Phase 1: symbols.hover.
305
+ *
306
+ * Returns the hover content (type info + docstring) at the position.
307
+ * Body capped at 4 KB so a verbose generic does not blow context.
308
+ */
309
+ export async function symbolsHoverTool(ctx, lang, file, line, col) {
310
+ const toolCallId = recordToolCall(ctx.session, 'symbols_hover', `${lang}:${file}:${line}:${col}`);
311
+ return guard(ctx, 'symbols_hover', toolCallId, async () => {
312
+ const client = ctx.lspClients?.get(lang);
313
+ if (!client)
314
+ return unavailable(lang);
315
+ const cache = getSymbolCacheFromCtx(ctx);
316
+ const cwd = workspaceForCache(ctx);
317
+ const key = SymbolCache.makeKey(lang, cwd, 'hover', { file, line, col });
318
+ const cached = cache?.get(key);
319
+ if (cached !== undefined) {
320
+ if (cached === null)
321
+ return notFound('hover');
322
+ const next = {
323
+ ok: true,
324
+ value: { content: cached.content, ...(cached.range ? { range: cached.range } : {}) },
325
+ ...(cached.truncated ? { truncated: true } : {}),
326
+ };
327
+ return next;
328
+ }
329
+ const result = await hoverSymbol(transportFromLspClient(client), file, line, col);
330
+ cache?.set(key, result);
331
+ if (!result)
332
+ return notFound('hover');
333
+ return {
334
+ ok: true,
335
+ value: { content: result.content, ...(result.range ? { range: result.range } : {}) },
336
+ ...(result.truncated ? { truncated: true } : {}),
337
+ };
338
+ });
339
+ }
340
+ /**
341
+ * PUGI-78 Phase 1: symbols.signature.
342
+ *
343
+ * Function signature at a call site. Returns the active overload's
344
+ * label, parameters, and active-parameter index.
345
+ */
346
+ export async function symbolsSignatureTool(ctx, lang, file, line, col) {
347
+ const toolCallId = recordToolCall(ctx.session, 'symbols_signature', `${lang}:${file}:${line}:${col}`);
348
+ return guard(ctx, 'symbols_signature', toolCallId, async () => {
349
+ const client = ctx.lspClients?.get(lang);
350
+ if (!client)
351
+ return unavailable(lang);
352
+ const result = await signatureAt(transportFromLspClient(client), file, line, col);
353
+ if (!result)
354
+ return notFound('signature');
355
+ return { ok: true, value: result };
356
+ });
357
+ }
358
+ /**
359
+ * PUGI-78 Phase 1: symbols.workspace_symbols.
360
+ *
361
+ * Workspace-wide fuzzy search. The server picks the matching algorithm
362
+ * (substring / fuzzy / prefix); we forward verbatim.
363
+ */
364
+ export async function symbolsWorkspaceSymbolsTool(ctx, lang, query) {
365
+ const toolCallId = recordToolCall(ctx.session, 'symbols_workspace_symbols', `${lang}:${query}`);
366
+ return guard(ctx, 'symbols_workspace_symbols', toolCallId, async () => {
367
+ const client = ctx.lspClients?.get(lang);
368
+ if (!client)
369
+ return unavailable(lang);
370
+ const cache = getSymbolCacheFromCtx(ctx);
371
+ const cwd = workspaceForCache(ctx);
372
+ const key = SymbolCache.makeKey(lang, cwd, 'workspace_symbols', { query });
373
+ const cached = cache?.get(key);
374
+ if (cached !== undefined) {
375
+ return { ok: true, value: [...cached] };
376
+ }
377
+ const result = await findWorkspaceSymbol(transportFromLspClient(client), query);
378
+ cache?.set(key, result);
379
+ return { ok: true, value: result };
380
+ });
381
+ }
382
+ /**
383
+ * PUGI-78 Phase 1: symbols.call_hierarchy.
384
+ *
385
+ * Returns the incoming + outgoing call edges at the symbol position.
386
+ * The two arrays are independent — a function may have many incoming
387
+ * callers and zero outgoing calls (a pure leaf), or vice versa.
388
+ */
389
+ export async function symbolsCallHierarchyTool(ctx, lang, file, line, col) {
390
+ const toolCallId = recordToolCall(ctx.session, 'symbols_call_hierarchy', `${lang}:${file}:${line}:${col}`);
391
+ return guard(ctx, 'symbols_call_hierarchy', toolCallId, async () => {
392
+ const client = ctx.lspClients?.get(lang);
393
+ if (!client)
394
+ return unavailable(lang);
395
+ const cache = getSymbolCacheFromCtx(ctx);
396
+ const cwd = workspaceForCache(ctx);
397
+ const key = SymbolCache.makeKey(lang, cwd, 'call_hierarchy', { file, line, col });
398
+ const cached = cache?.get(key);
399
+ if (cached !== undefined) {
400
+ return { ok: true, value: { incoming: [...cached.incoming], outgoing: [...cached.outgoing] } };
401
+ }
402
+ const result = await callHierarchyAt(transportFromLspClient(client), file, line, col);
403
+ cache?.set(key, result);
404
+ return { ok: true, value: result };
405
+ });
406
+ }
407
+ /**
408
+ * PUGI-78 Phase 1: symbols.implementations.
409
+ *
410
+ * Implementations of an interface / abstract method. Returns the flat
411
+ * list of concrete sites.
412
+ */
413
+ export async function symbolsImplementationsTool(ctx, lang, file, line, col) {
414
+ const toolCallId = recordToolCall(ctx.session, 'symbols_implementations', `${lang}:${file}:${line}:${col}`);
415
+ return guard(ctx, 'symbols_implementations', toolCallId, async () => {
416
+ const client = ctx.lspClients?.get(lang);
417
+ if (!client)
418
+ return unavailable(lang);
419
+ const cache = getSymbolCacheFromCtx(ctx);
420
+ const cwd = workspaceForCache(ctx);
421
+ const key = SymbolCache.makeKey(lang, cwd, 'implementations', { file, line, col });
422
+ const cached = cache?.get(key);
423
+ if (cached !== undefined) {
424
+ const capped = capSymbolReferences(cached);
425
+ return { ok: true, value: capped.value, ...(capped.truncated ? { truncated: true } : {}) };
426
+ }
427
+ const result = await findImplementations(transportFromLspClient(client), file, line, col);
428
+ cache?.set(key, result);
429
+ const capped = capSymbolReferences(result);
430
+ return { ok: true, value: capped.value, ...(capped.truncated ? { truncated: true } : {}) };
431
+ });
432
+ }
433
+ /**
434
+ * PUGI-78 Phase 1: symbols.type_definition.
435
+ *
436
+ * Type definition (vs value definition). Useful for "what type is foo?"
437
+ * questions when the model has a variable and wants its type declaration.
438
+ */
439
+ export async function symbolsTypeDefinitionTool(ctx, lang, file, line, col) {
440
+ const toolCallId = recordToolCall(ctx.session, 'symbols_type_definition', `${lang}:${file}:${line}:${col}`);
441
+ return guard(ctx, 'symbols_type_definition', toolCallId, async () => {
442
+ const client = ctx.lspClients?.get(lang);
443
+ if (!client)
444
+ return unavailable(lang);
445
+ const cache = getSymbolCacheFromCtx(ctx);
446
+ const cwd = workspaceForCache(ctx);
447
+ const key = SymbolCache.makeKey(lang, cwd, 'type_definition', { file, line, col });
448
+ const cached = cache?.get(key);
449
+ if (cached !== undefined) {
450
+ if (cached === null)
451
+ return notFound('type_definition');
452
+ return { ok: true, value: cached };
453
+ }
454
+ const result = await findTypeDefinition(transportFromLspClient(client), file, line, col);
455
+ cache?.set(key, result);
456
+ if (!result)
457
+ return notFound('type_definition');
458
+ return { ok: true, value: result };
459
+ });
460
+ }
461
+ /**
462
+ * PUGI-78 Phase 1: symbols.code_actions.
463
+ *
464
+ * Quick-fix list at the given range. Server-provided actions include
465
+ * auto-import, unused-import removal, refactor extractions, etc.
466
+ */
467
+ export async function symbolsCodeActionsTool(ctx, lang, file, startLine, startChar, endLine, endChar) {
468
+ const toolCallId = recordToolCall(ctx.session, 'symbols_code_actions', `${lang}:${file}:${startLine}:${startChar}-${endLine}:${endChar}`);
469
+ return guard(ctx, 'symbols_code_actions', toolCallId, async () => {
470
+ const client = ctx.lspClients?.get(lang);
471
+ if (!client)
472
+ return unavailable(lang);
473
+ const result = await codeActionsAt(transportFromLspClient(client), file, startLine, startChar, endLine, endChar);
474
+ return { ok: true, value: result };
475
+ });
476
+ }
477
+ /**
478
+ * PUGI-78 Phase 1: symbols.format.
479
+ *
480
+ * Formatter — returns the text edits the LSP server would apply for
481
+ * `textDocument/formatting`. Phase 1 is read-only (returns the edits);
482
+ * the dispatcher composes + applies in a future ticket.
483
+ */
484
+ export async function symbolsFormatTool(ctx, lang, file, options) {
485
+ const toolCallId = recordToolCall(ctx.session, 'symbols_format', `${lang}:${file}`);
486
+ return guard(ctx, 'symbols_format', toolCallId, async () => {
487
+ const client = ctx.lspClients?.get(lang);
488
+ if (!client)
489
+ return unavailable(lang);
490
+ const result = await formatFile(transportFromLspClient(client), file, options);
491
+ return { ok: true, value: result };
492
+ });
493
+ }
494
+ /**
495
+ * PUGI-78 Phase 1: symbols.diagnostics.
496
+ *
497
+ * Pulls the cached `publishDiagnostics` payload for the file. Mirrors
498
+ * the legacy `lspDiagnostics` tool but exposed via the namespaced
499
+ * `symbols.*` surface for discoverability.
500
+ */
501
+ export async function symbolsDiagnosticsTool(ctx, lang, file) {
502
+ const toolCallId = recordToolCall(ctx.session, 'symbols_diagnostics', `${lang}:${file}`);
503
+ return guard(ctx, 'symbols_diagnostics', toolCallId, async () => {
504
+ const client = ctx.lspClients?.get(lang);
505
+ if (!client)
506
+ return unavailable(lang);
507
+ const result = await diagnosticsFor(transportFromLspClient(client), file);
508
+ const capped = capDiagnostics(result);
509
+ return { ok: true, value: capped.value, ...(capped.truncated ? { truncated: true } : {}) };
510
+ });
511
+ }
512
+ function workspaceForCache(ctx) {
513
+ // ToolContext exposes `root` (the workspace root). Fall back to a
514
+ // tagged anonymous workspace when somehow absent so the cache still
515
+ // keys deterministically.
516
+ const root = ctx.root;
517
+ if (typeof root === 'string' && root.length > 0)
518
+ return root;
519
+ return '<unknown>';
520
+ }
521
+ function getSymbolCacheFromCtx(ctx) {
522
+ // The agent dispatcher injects an optional cache via the ctx — when
523
+ // present we use the dispatcher-scoped cache so test harnesses can
524
+ // inject a deterministic clock. When absent, we fall back to the
525
+ // process-global cache.
526
+ if (ctx.symbolCache)
527
+ return ctx.symbolCache;
528
+ return getGlobalSymbolCache();
529
+ }
530
+ function notFound(verb) {
531
+ return {
532
+ ok: false,
533
+ reason: 'lsp_not_found',
534
+ detail: `LSP returned no ${verb} for the requested position.`,
535
+ };
536
+ }
537
+ function capSymbolReferences(items) {
538
+ const COUNT_CAP = 200;
539
+ if (items.length === 0)
540
+ return { value: items, truncated: false };
541
+ const trimmed = items.slice(0, COUNT_CAP);
542
+ const serialized = JSON.stringify(trimmed);
543
+ if (Buffer.byteLength(serialized, 'utf8') <= LSP_PAYLOAD_CAP_BYTES && trimmed.length === items.length) {
544
+ return { value: trimmed, truncated: false };
545
+ }
546
+ let upper = trimmed.length;
547
+ while (upper > 1) {
548
+ const half = Math.floor(upper / 2);
549
+ const sub = trimmed.slice(0, half);
550
+ if (Buffer.byteLength(JSON.stringify(sub), 'utf8') <= LSP_PAYLOAD_CAP_BYTES) {
551
+ return { value: sub, truncated: true };
552
+ }
553
+ upper = half;
554
+ }
555
+ return { value: trimmed.slice(0, 1), truncated: true };
556
+ }
187
557
  /** Test-only surface so specs can poke truncation directly. */
188
- export const __test__ = { truncate, capLocations, capDiagnostics, LSP_PAYLOAD_CAP_BYTES };
558
+ export const __test__ = {
559
+ truncate,
560
+ capLocations,
561
+ capDiagnostics,
562
+ capSymbolReferences,
563
+ LSP_PAYLOAD_CAP_BYTES,
564
+ };
189
565
  //# sourceMappingURL=lsp-tools.js.map
@@ -0,0 +1,260 @@
1
+ import { callTool } from '../core/mcp/client.js';
2
+ import { getMcpPermission, setMcpPermission, } from '../core/mcp/permission.js';
3
+ /**
4
+ * Tool dispatcher for MCP-invoked tools (β4 M1 + M3 + M5).
5
+ *
6
+ * Tool names use the `mcp__<server>__<tool>` namespace (double-underscore
7
+ * separator, mirroring the upstream tool's MCP envelope). The triple-underscore
8
+ * forms (`mcp__server__tool__sub`) collapse into the third segment when
9
+ * the upstream server itself uses underscores in its tool names — `split`
10
+ * on the first two `__` only, so any further `__` in the tool name part
11
+ * survive intact (e.g. `mcp__github__create_issue` -> server=`github`,
12
+ * tool=`create_issue`).
13
+ *
14
+ * Why double-underscore: native Pugi tools use single-token names
15
+ * (`read`, `grep`, `edit`, `bash`). The double-underscore prefix
16
+ * unambiguously segregates the MCP namespace from native names without
17
+ * needing per-name regex matching at every dispatch site.
18
+ *
19
+ * Permission flow:
20
+ * 1. Server trust gate (handled at registry-load time). If a server is
21
+ * not `trusted`, its tools never reach the engine loop.
22
+ * 2. Per-(server, tool) permission cache (`./mcp/permission.ts`).
23
+ * Unset on first dispatch -> caller must prompt. Cached `allow_always`
24
+ * auto-passes; cached `deny` auto-refuses.
25
+ *
26
+ * This module is the bridge — it parses the namespaced name, finds the
27
+ * live connection in the registry, consults the cache, and (when
28
+ * approved) routes through `client.callTool`. Prompting is the executor's
29
+ * responsibility; this module exposes the cache lookup + dispatch
30
+ * primitives so the executor stays small.
31
+ */
32
+ /**
33
+ * Prefix every MCP tool name carries on the engine-loop wire.
34
+ */
35
+ export const MCP_TOOL_PREFIX = 'mcp__';
36
+ /**
37
+ * Parse `mcp__<server>__<tool>` into `{ serverName, toolName }`. Returns
38
+ * null when the input does not match the namespace — callers use this as
39
+ * the "is this an MCP tool?" predicate.
40
+ *
41
+ * Server names cannot contain `__` by registry validation (they are JSON
42
+ * object keys); tool names CAN (e.g. `create_issue` has a single `_` but
43
+ * `read_directory` has none, so the only ambiguity is when an upstream
44
+ * tool uses double-underscore in its slug — extremely rare, but if it
45
+ * happens the second `__` boundary still parses correctly because we
46
+ * split on the FIRST occurrence after the prefix).
47
+ */
48
+ export function parseMcpToolName(name) {
49
+ if (!name.startsWith(MCP_TOOL_PREFIX))
50
+ return null;
51
+ const tail = name.slice(MCP_TOOL_PREFIX.length);
52
+ const sep = tail.indexOf('__');
53
+ if (sep === -1)
54
+ return null;
55
+ const serverName = tail.slice(0, sep);
56
+ const toolName = tail.slice(sep + 2);
57
+ if (serverName.length === 0 || toolName.length === 0)
58
+ return null;
59
+ return { serverName, toolName };
60
+ }
61
+ /**
62
+ * Build the namespaced tool name from a server + tool pair. Inverse of
63
+ * `parseMcpToolName`. Used by `buildMcpToolDefs` to emit the schema.
64
+ */
65
+ export function buildMcpToolName(serverName, toolName) {
66
+ return `${MCP_TOOL_PREFIX}${serverName}__${toolName}`;
67
+ }
68
+ /**
69
+ * Build engine-loop tool definitions from every trusted server's
70
+ * surfaced tools. Empty array when no MCP servers are trusted — the
71
+ * schema builder can call this unconditionally without checking first.
72
+ */
73
+ export function buildMcpToolDefs(registry) {
74
+ if (!registry)
75
+ return [];
76
+ const defs = [];
77
+ for (const state of registry.servers.values()) {
78
+ if (state.trust !== 'trusted')
79
+ continue;
80
+ for (const tool of state.surfacedTools) {
81
+ defs.push({
82
+ name: buildMcpToolName(state.name, tool.name),
83
+ description: descriptionFor(state.name, tool),
84
+ // The upstream server returns its own JSON Schema in `inputSchema`.
85
+ // We surface it verbatim — the loop client passes it through to
86
+ // the model, and the model emits arguments matching the upstream
87
+ // shape. Default to `{ type: 'object' }` when missing so the
88
+ // OpenAI-shaped tool envelope still validates.
89
+ parameters: tool.inputSchema ?? { type: 'object' },
90
+ });
91
+ }
92
+ }
93
+ // Sort stable so the schema bundle hash (used for caching/audit) is
94
+ // deterministic regardless of Map iteration order.
95
+ return defs.sort((a, b) => a.name.localeCompare(b.name));
96
+ }
97
+ function descriptionFor(serverName, tool) {
98
+ const base = tool.description?.trim() ?? `MCP tool ${tool.name} on server ${serverName}.`;
99
+ return `[MCP:${serverName}] ${base}`;
100
+ }
101
+ /**
102
+ * Look up the live connection + tool metadata for a parsed MCP tool name.
103
+ * Returns null when the server is not trusted, not connected, or does
104
+ * not expose the named tool. Callers MUST handle null — never throw,
105
+ * because the model may emit stale tool names after a server restart.
106
+ */
107
+ export function resolveMcpTool(registry, parsed) {
108
+ if (!registry)
109
+ return null;
110
+ const state = registry.servers.get(parsed.serverName);
111
+ if (!state || state.trust !== 'trusted' || !state.connection)
112
+ return null;
113
+ const tool = state.surfacedTools.find((t) => t.name === parsed.toolName);
114
+ if (!tool)
115
+ return null;
116
+ return { state, connection: state.connection, tool };
117
+ }
118
+ /**
119
+ * The default prompt — used when no interactive bridge is wired (CI,
120
+ * non-TTY pipes). Returns `deny` so an unattended run never silently
121
+ * fires an MCP call the operator never approved. The deny is NOT
122
+ * persisted, so the next run with a wired prompt still has a chance to
123
+ * approve.
124
+ */
125
+ export const defaultNonInteractiveMcpPrompt = async () => 'unset';
126
+ /**
127
+ * Dispatch one MCP tool call. The flow:
128
+ *
129
+ * 1. Parse the namespaced tool name. Return error string when
130
+ * malformed — the model sees the error and can self-correct.
131
+ * 2. Resolve the live connection. Return error when the server is not
132
+ * trusted/connected or the tool is unknown.
133
+ * 3. Consult the permission cache. `deny` short-circuits. `allow_always`
134
+ * proceeds. `unset` invokes the prompt; the operator's verdict is
135
+ * persisted (allow_always/deny) or used one-shot (allow_once).
136
+ * 4. Parse the arguments string. Bad JSON -> error string.
137
+ * 5. Call `client.callTool` and stringify the content for the model.
138
+ *
139
+ * Throws ONLY on unrecoverable transport failures (e.g. the connection
140
+ * died mid-call). Tool-level errors from the upstream server are
141
+ * surfaced as `[MCP error] <message>` strings so the model can recover.
142
+ */
143
+ export async function dispatchMcpTool(input) {
144
+ const parsed = parseMcpToolName(input.name);
145
+ if (!parsed) {
146
+ return `[MCP dispatch error] tool name "${input.name}" does not match the ${MCP_TOOL_PREFIX}<server>__<tool> namespace`;
147
+ }
148
+ const resolved = resolveMcpTool(input.registry, parsed);
149
+ if (!resolved) {
150
+ return `[MCP dispatch error] no trusted+connected server "${parsed.serverName}" exposes a tool named "${parsed.toolName}"`;
151
+ }
152
+ let args;
153
+ try {
154
+ args = parseArgumentsRaw(input.argumentsRaw);
155
+ }
156
+ catch (error) {
157
+ return `[MCP dispatch error] invalid JSON in arguments for ${input.name}: ${error instanceof Error ? error.message : String(error)}`;
158
+ }
159
+ // Permission gate.
160
+ const cached = getMcpPermission(parsed.serverName, parsed.toolName);
161
+ let effective = cached;
162
+ if (cached === 'unset') {
163
+ const verdict = await input.prompt({
164
+ serverName: parsed.serverName,
165
+ toolName: parsed.toolName,
166
+ toolDescription: resolved.tool.description ?? '',
167
+ callArguments: args,
168
+ });
169
+ effective = verdict;
170
+ if (verdict === 'allow_always' || verdict === 'deny') {
171
+ setMcpPermission(parsed.serverName, parsed.toolName, verdict, resolveDecidedBy(input.decidedBy));
172
+ }
173
+ }
174
+ if (effective === 'deny') {
175
+ return `[MCP refused] operator denied ${parsed.serverName}:${parsed.toolName}`;
176
+ }
177
+ if (effective !== 'allow_once' && effective !== 'allow_always') {
178
+ // Includes `unset` returned by the non-interactive default prompt.
179
+ return `[MCP refused] no operator approval for ${parsed.serverName}:${parsed.toolName} (run from a TTY to approve)`;
180
+ }
181
+ // Dispatch.
182
+ let result;
183
+ try {
184
+ result = await callTool(resolved.connection, parsed.toolName, args, {
185
+ ...(input.timeoutMs !== undefined ? { timeoutMs: input.timeoutMs } : {}),
186
+ });
187
+ }
188
+ catch (error) {
189
+ // Transport-level failure (timeout, child died mid-call). Surface
190
+ // as a recoverable string so the model can degrade gracefully.
191
+ return `[MCP transport error] ${parsed.serverName}:${parsed.toolName}: ${error instanceof Error ? error.message : String(error)}`;
192
+ }
193
+ return renderMcpToolResult(result.content, result.isError, parsed);
194
+ }
195
+ function parseArgumentsRaw(raw) {
196
+ if (!raw || raw.trim() === '')
197
+ return {};
198
+ const parsed = JSON.parse(raw);
199
+ if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
200
+ throw new Error('arguments must be a JSON object');
201
+ }
202
+ return parsed;
203
+ }
204
+ function resolveDecidedBy(override) {
205
+ return (override?.trim() ||
206
+ process.env.PUGI_TRUSTED_BY?.trim() ||
207
+ process.env.USER?.trim() ||
208
+ process.env.USERNAME?.trim() ||
209
+ 'cli');
210
+ }
211
+ /**
212
+ * Project the MCP `content` payload into a single text string the model
213
+ * can ingest. MCP servers reply with `content: [{ type: 'text', text }]`
214
+ * by convention; we concatenate every `type: text` chunk and surface a
215
+ * `[MCP non-text content]` marker for other content kinds (images,
216
+ * resource references) which are not yet wired into Pugi's loop.
217
+ *
218
+ * `isError: true` from the upstream maps to a `[MCP error] ...` prefix
219
+ * so the model knows the call failed at the server, not at the
220
+ * transport.
221
+ */
222
+ export function renderMcpToolResult(content, isError, parsed) {
223
+ const text = projectTextContent(content);
224
+ const prefix = isError ? `[MCP error ${parsed.serverName}:${parsed.toolName}] ` : '';
225
+ if (text === null) {
226
+ // Fallback to a JSON dump so the model sees SOMETHING — better than
227
+ // an opaque empty string when the upstream uses image / resource
228
+ // content kinds.
229
+ try {
230
+ return `${prefix}${JSON.stringify(content)}`;
231
+ }
232
+ catch {
233
+ return `${prefix}[MCP non-serialisable content]`;
234
+ }
235
+ }
236
+ return `${prefix}${text}`;
237
+ }
238
+ function projectTextContent(content) {
239
+ if (content === null || content === undefined)
240
+ return '';
241
+ if (typeof content === 'string')
242
+ return content;
243
+ if (!Array.isArray(content))
244
+ return null;
245
+ const parts = [];
246
+ for (const entry of content) {
247
+ if (entry && typeof entry === 'object' && !Array.isArray(entry)) {
248
+ const obj = entry;
249
+ if (obj.type === 'text' && typeof obj.text === 'string') {
250
+ parts.push(obj.text);
251
+ continue;
252
+ }
253
+ }
254
+ // Non-text chunk — record a marker so the model knows something was
255
+ // dropped from the response.
256
+ parts.push('[MCP non-text content chunk]');
257
+ }
258
+ return parts.join('\n');
259
+ }
260
+ //# sourceMappingURL=mcp-tool.js.map