@herbertgao/pi-extensions 2026.8.3 → 2026.8.5

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 (408) hide show
  1. package/README.md +45 -4
  2. package/examples/pi-footer.json +275 -0
  3. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +10 -2
  4. package/node_modules/@herbertgao/pi-cc-extensions/README.md +10 -2
  5. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +24 -0
  6. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +91 -1
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +7 -3
  8. package/node_modules/@herbertgao/pi-cc-extensions/extensions/index.ts +7 -6
  9. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +21 -12
  10. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +4 -5
  11. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +5 -8
  12. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +22 -12
  13. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +2 -4
  14. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  15. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +8 -0
  16. package/node_modules/@herbertgao/pi-subagents/package.json +8 -4
  17. package/node_modules/@herbertgao/pi-subagents/src/agent-color.ts +72 -67
  18. package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +255 -0
  19. package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +191 -3
  20. package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +15 -2
  21. package/node_modules/@herbertgao/pi-subagents/src/index.ts +289 -113
  22. package/node_modules/@herbertgao/pi-subagents/src/output-file.ts +24 -1
  23. package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +24 -2
  24. package/node_modules/@herbertgao/pi-subagents/src/ui/schedule-menu.ts +9 -8
  25. package/node_modules/@herbertgao/pi-subagents/src/ui/select-item.ts +48 -0
  26. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/keyboard.md +7 -0
  27. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
  28. package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +7 -0
  29. package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +16 -5
  30. package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +4 -2
  31. package/node_modules/@narumitw/pi-btw/README.md +8 -7
  32. package/node_modules/@narumitw/pi-btw/package.json +2 -2
  33. package/node_modules/@narumitw/pi-btw/src/menu.ts +1 -0
  34. package/node_modules/pi-footer/CHANGELOG.md +145 -0
  35. package/node_modules/pi-footer/LICENSE +21 -0
  36. package/node_modules/pi-footer/README.md +354 -0
  37. package/node_modules/pi-footer/package.json +68 -0
  38. package/node_modules/pi-footer/src/cache.ts +106 -0
  39. package/node_modules/pi-footer/src/colors.ts +237 -0
  40. package/node_modules/pi-footer/src/config.ts +202 -0
  41. package/node_modules/pi-footer/src/event-widgets.ts +41 -0
  42. package/node_modules/pi-footer/src/extension-statuses.ts +102 -0
  43. package/node_modules/pi-footer/src/git.ts +135 -0
  44. package/node_modules/pi-footer/src/index.ts +268 -0
  45. package/node_modules/pi-footer/src/metrics.ts +141 -0
  46. package/node_modules/pi-footer/src/presets.ts +383 -0
  47. package/node_modules/pi-footer/src/render.ts +114 -0
  48. package/node_modules/pi-footer/src/separators.ts +52 -0
  49. package/node_modules/pi-footer/src/types.ts +122 -0
  50. package/node_modules/pi-footer/src/ui/color-level-confirm.ts +22 -0
  51. package/node_modules/pi-footer/src/ui/color-options.ts +195 -0
  52. package/node_modules/pi-footer/src/ui/config-lifecycle.ts +61 -0
  53. package/node_modules/pi-footer/src/ui/edit-colors.ts +13 -0
  54. package/node_modules/pi-footer/src/ui/events.ts +35 -0
  55. package/node_modules/pi-footer/src/ui/extension-status-picker.ts +60 -0
  56. package/node_modules/pi-footer/src/ui/extension-statuses.ts +73 -0
  57. package/node_modules/pi-footer/src/ui/fields.ts +252 -0
  58. package/node_modules/pi-footer/src/ui/global-menu.ts +126 -0
  59. package/node_modules/pi-footer/src/ui/helpers.ts +44 -0
  60. package/node_modules/pi-footer/src/ui/layout.ts +17 -0
  61. package/node_modules/pi-footer/src/ui/line-list.ts +19 -0
  62. package/node_modules/pi-footer/src/ui/model.ts +64 -0
  63. package/node_modules/pi-footer/src/ui/navigation.ts +29 -0
  64. package/node_modules/pi-footer/src/ui/option-edit.ts +93 -0
  65. package/node_modules/pi-footer/src/ui/overlay-render.ts +103 -0
  66. package/node_modules/pi-footer/src/ui/screen-context.ts +18 -0
  67. package/node_modules/pi-footer/src/ui/screen-controller.ts +27 -0
  68. package/node_modules/pi-footer/src/ui/screen-render.ts +32 -0
  69. package/node_modules/pi-footer/src/ui/screen-state.ts +20 -0
  70. package/node_modules/pi-footer/src/ui/screen.ts +243 -0
  71. package/node_modules/pi-footer/src/ui/screens/add-widget.ts +92 -0
  72. package/node_modules/pi-footer/src/ui/screens/confirm-exit.ts +52 -0
  73. package/node_modules/pi-footer/src/ui/screens/controller.ts +12 -0
  74. package/node_modules/pi-footer/src/ui/screens/edit-colors.ts +68 -0
  75. package/node_modules/pi-footer/src/ui/screens/edit-widget.ts +115 -0
  76. package/node_modules/pi-footer/src/ui/screens/extension-status-row.ts +61 -0
  77. package/node_modules/pi-footer/src/ui/screens/global.ts +75 -0
  78. package/node_modules/pi-footer/src/ui/screens/line-list.ts +108 -0
  79. package/node_modules/pi-footer/src/ui/screens/main.ts +99 -0
  80. package/node_modules/pi-footer/src/ui/screens/terminal.ts +121 -0
  81. package/node_modules/pi-footer/src/ui/screens/widget-list.ts +154 -0
  82. package/node_modules/pi-footer/src/ui/terminal-menu.ts +32 -0
  83. package/node_modules/pi-footer/src/ui/theme.ts +48 -0
  84. package/node_modules/pi-footer/src/ui/title-bar.ts +100 -0
  85. package/node_modules/pi-footer/src/ui/widget-actions.ts +76 -0
  86. package/node_modules/pi-footer/src/ui.ts +66 -0
  87. package/node_modules/pi-footer/src/widgets/context.ts +28 -0
  88. package/node_modules/pi-footer/src/widgets/core/active-tools.ts +17 -0
  89. package/node_modules/pi-footer/src/widgets/core/context-window.ts +24 -0
  90. package/node_modules/pi-footer/src/widgets/core/cwd-basename.ts +19 -0
  91. package/node_modules/pi-footer/src/widgets/core/cwd.ts +108 -0
  92. package/node_modules/pi-footer/src/widgets/core/event.ts +34 -0
  93. package/node_modules/pi-footer/src/widgets/core/external-status.ts +102 -0
  94. package/node_modules/pi-footer/src/widgets/core/model-provider.ts +20 -0
  95. package/node_modules/pi-footer/src/widgets/core/model.ts +35 -0
  96. package/node_modules/pi-footer/src/widgets/core/provider.ts +17 -0
  97. package/node_modules/pi-footer/src/widgets/core/session-name.ts +17 -0
  98. package/node_modules/pi-footer/src/widgets/core/text-verbosity.ts +17 -0
  99. package/node_modules/pi-footer/src/widgets/core/thinking-level.ts +17 -0
  100. package/node_modules/pi-footer/src/widgets/git/ahead-behind.ts +17 -0
  101. package/node_modules/pi-footer/src/widgets/git/branch.ts +81 -0
  102. package/node_modules/pi-footer/src/widgets/git/clean.ts +19 -0
  103. package/node_modules/pi-footer/src/widgets/git/deletions.ts +17 -0
  104. package/node_modules/pi-footer/src/widgets/git/diff.ts +38 -0
  105. package/node_modules/pi-footer/src/widgets/git/insertions.ts +17 -0
  106. package/node_modules/pi-footer/src/widgets/git/remote.ts +17 -0
  107. package/node_modules/pi-footer/src/widgets/git/root.ts +17 -0
  108. package/node_modules/pi-footer/src/widgets/git/sha.ts +17 -0
  109. package/node_modules/pi-footer/src/widgets/git/staged.ts +17 -0
  110. package/node_modules/pi-footer/src/widgets/git/status.ts +19 -0
  111. package/node_modules/pi-footer/src/widgets/git/unstaged.ts +17 -0
  112. package/node_modules/pi-footer/src/widgets/git/untracked.ts +17 -0
  113. package/node_modules/pi-footer/src/widgets/instance.ts +118 -0
  114. package/node_modules/pi-footer/src/widgets/layout/custom-text.ts +18 -0
  115. package/node_modules/pi-footer/src/widgets/layout/flex-separator.ts +32 -0
  116. package/node_modules/pi-footer/src/widgets/layout/separator.ts +57 -0
  117. package/node_modules/pi-footer/src/widgets/layout/spacer.ts +31 -0
  118. package/node_modules/pi-footer/src/widgets/options.ts +122 -0
  119. package/node_modules/pi-footer/src/widgets/project/runtime.ts +361 -0
  120. package/node_modules/pi-footer/src/widgets/registry.ts +223 -0
  121. package/node_modules/pi-footer/src/widgets/session/assistant-messages.ts +17 -0
  122. package/node_modules/pi-footer/src/widgets/session/compactions.ts +17 -0
  123. package/node_modules/pi-footer/src/widgets/session/elapsed.ts +18 -0
  124. package/node_modules/pi-footer/src/widgets/session/last-activity.ts +18 -0
  125. package/node_modules/pi-footer/src/widgets/session/messages.ts +19 -0
  126. package/node_modules/pi-footer/src/widgets/session/session-id.ts +17 -0
  127. package/node_modules/pi-footer/src/widgets/session/session-start.ts +18 -0
  128. package/node_modules/pi-footer/src/widgets/session/tool-results.ts +17 -0
  129. package/node_modules/pi-footer/src/widgets/session/total-messages.ts +19 -0
  130. package/node_modules/pi-footer/src/widgets/session/total-time.ts +18 -0
  131. package/node_modules/pi-footer/src/widgets/session/user-messages.ts +17 -0
  132. package/node_modules/pi-footer/src/widgets/store.ts +26 -0
  133. package/node_modules/pi-footer/src/widgets/tokens/cache-hit-rate.ts +55 -0
  134. package/node_modules/pi-footer/src/widgets/tokens/cache-read.ts +18 -0
  135. package/node_modules/pi-footer/src/widgets/tokens/cache-write.ts +18 -0
  136. package/node_modules/pi-footer/src/widgets/tokens/context-bar.ts +108 -0
  137. package/node_modules/pi-footer/src/widgets/tokens/context-length.ts +26 -0
  138. package/node_modules/pi-footer/src/widgets/tokens/context-remaining.ts +29 -0
  139. package/node_modules/pi-footer/src/widgets/tokens/context.ts +29 -0
  140. package/node_modules/pi-footer/src/widgets/tokens/cost.ts +51 -0
  141. package/node_modules/pi-footer/src/widgets/tokens/input-speed.ts +25 -0
  142. package/node_modules/pi-footer/src/widgets/tokens/input-tokens.ts +18 -0
  143. package/node_modules/pi-footer/src/widgets/tokens/output-speed.ts +25 -0
  144. package/node_modules/pi-footer/src/widgets/tokens/output-tokens.ts +18 -0
  145. package/node_modules/pi-footer/src/widgets/tokens/tokens.ts +20 -0
  146. package/node_modules/pi-footer/src/widgets/tokens/total-speed.ts +25 -0
  147. package/node_modules/pi-footer/src/widgets/tokens/total-tokens.ts +18 -0
  148. package/node_modules/pi-footer/src/widgets/types.ts +181 -0
  149. package/node_modules/pi-footer/src/widgets/utils/colors.ts +11 -0
  150. package/node_modules/pi-footer/src/widgets/utils/context.ts +112 -0
  151. package/node_modules/pi-footer/src/widgets/utils/session.ts +19 -0
  152. package/node_modules/pi-footer/src/widgets/utils/token-format.ts +78 -0
  153. package/node_modules/pi-lens/CHANGELOG.md +2440 -954
  154. package/node_modules/pi-lens/README.md +13 -0
  155. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +73 -18
  156. package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +21 -4
  157. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +68 -16
  158. package/node_modules/pi-lens/dist/clients/ast-grep-types.js +0 -1
  159. package/node_modules/pi-lens/dist/clients/ast-grep-yaml-synth.js +37 -10
  160. package/node_modules/pi-lens/dist/clients/atomic-write-staging.js +63 -0
  161. package/node_modules/pi-lens/dist/clients/atomic-write.js +114 -14
  162. package/node_modules/pi-lens/dist/clients/bash-file-access.js +193 -23
  163. package/node_modules/pi-lens/dist/clients/biome-client.js +29 -25
  164. package/node_modules/pi-lens/dist/clients/bootstrap.js +11 -2
  165. package/node_modules/pi-lens/dist/clients/bounded-cache.js +34 -0
  166. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +192 -0
  167. package/node_modules/pi-lens/dist/clients/bus-publish.js +10 -3
  168. package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +47 -5
  169. package/node_modules/pi-lens/dist/clients/cache-manager.js +97 -9
  170. package/node_modules/pi-lens/dist/clients/cache-observability.js +301 -14
  171. package/node_modules/pi-lens/dist/clients/call-graph.js +449 -117
  172. package/node_modules/pi-lens/dist/clients/child-unref.js +85 -0
  173. package/node_modules/pi-lens/dist/clients/codebase-model.js +52 -23
  174. package/node_modules/pi-lens/dist/clients/collateral-test-role.js +40 -0
  175. package/node_modules/pi-lens/dist/clients/complexity-client.js +2 -1
  176. package/node_modules/pi-lens/dist/clients/console-guard-install.js +14 -0
  177. package/node_modules/pi-lens/dist/clients/cooperative-budget.js +42 -0
  178. package/node_modules/pi-lens/dist/clients/dead-code-client.js +2 -1
  179. package/node_modules/pi-lens/dist/clients/debug-handles.js +206 -0
  180. package/node_modules/pi-lens/dist/clients/debug-heap.js +167 -0
  181. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +153 -0
  182. package/node_modules/pi-lens/dist/clients/dependency-checker.js +347 -75
  183. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +61 -11
  184. package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +10 -3
  185. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +63 -20
  186. package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +23 -7
  187. package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +8 -18
  188. package/node_modules/pi-lens/dist/clients/dispatch/facts/tree-sitter-facts.js +8 -5
  189. package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +35 -0
  190. package/node_modules/pi-lens/dist/clients/dispatch/inline-suppressions.js +17 -8
  191. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +1085 -681
  192. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +14 -0
  193. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +11 -1
  194. package/node_modules/pi-lens/dist/clients/dispatch/rule-id-normalize.js +50 -0
  195. package/node_modules/pi-lens/dist/clients/dispatch/rule-policy.js +135 -0
  196. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +23 -5
  197. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +5 -9
  198. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +3 -2
  199. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +2 -1
  200. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +154 -0
  201. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +3 -2
  202. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +5 -1
  203. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +14 -6
  204. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +4 -9
  205. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +9 -5
  206. package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +4 -8
  207. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +2 -3
  208. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -3
  209. package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +2 -1
  210. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +2 -1
  211. package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +163 -0
  212. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +37 -5
  213. package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +3 -1
  214. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +13 -7
  215. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +3 -0
  216. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +277 -26
  217. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +22 -0
  218. package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +5 -4
  219. package/node_modules/pi-lens/dist/clients/disposition-publish.js +10 -3
  220. package/node_modules/pi-lens/dist/clients/durable-store.js +97 -0
  221. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +94 -3
  222. package/node_modules/pi-lens/dist/clients/extension-log.js +139 -0
  223. package/node_modules/pi-lens/dist/clients/extension-mode.js +73 -0
  224. package/node_modules/pi-lens/dist/clients/file-kinds.js +57 -0
  225. package/node_modules/pi-lens/dist/clients/file-role.js +18 -3
  226. package/node_modules/pi-lens/dist/clients/file-utils.js +56 -34
  227. package/node_modules/pi-lens/dist/clients/format-events-publish.js +16 -3
  228. package/node_modules/pi-lens/dist/clients/format-service.js +24 -8
  229. package/node_modules/pi-lens/dist/clients/formatters-lazy.js +8 -0
  230. package/node_modules/pi-lens/dist/clients/formatters.js +358 -61
  231. package/node_modules/pi-lens/dist/clients/generated-artifacts.js +148 -15
  232. package/node_modules/pi-lens/dist/clients/git-guard.js +681 -11
  233. package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +4 -0
  234. package/node_modules/pi-lens/dist/clients/go-client.js +2 -1
  235. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +8 -0
  236. package/node_modules/pi-lens/dist/clients/grammar-source.js +12 -4
  237. package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +21 -3
  238. package/node_modules/pi-lens/dist/clients/host-ports.js +31 -0
  239. package/node_modules/pi-lens/dist/clients/installer/index.js +390 -99
  240. package/node_modules/pi-lens/dist/clients/instance-reaper.js +142 -172
  241. package/node_modules/pi-lens/dist/clients/jscpd-client.js +31 -54
  242. package/node_modules/pi-lens/dist/clients/knip-client.js +15 -36
  243. package/node_modules/pi-lens/dist/clients/language-policy.js +14 -2
  244. package/node_modules/pi-lens/dist/clients/language-profile.js +13 -7
  245. package/node_modules/pi-lens/dist/clients/latency-logger.js +23 -1
  246. package/node_modules/pi-lens/dist/clients/lens-config.js +12 -1
  247. package/node_modules/pi-lens/dist/clients/lens-engine.js +86 -17
  248. package/node_modules/pi-lens/dist/clients/lens-events.js +27 -4
  249. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
  250. package/node_modules/pi-lens/dist/clients/lens-map.js +94 -19
  251. package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +30 -0
  252. package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -1
  253. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +1 -1
  254. package/node_modules/pi-lens/dist/clients/lsp/client.js +493 -48
  255. package/node_modules/pi-lens/dist/clients/lsp/config.js +13 -2
  256. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +86 -0
  257. package/node_modules/pi-lens/dist/clients/lsp/edits.js +1012 -121
  258. package/node_modules/pi-lens/dist/clients/lsp/index.js +1807 -780
  259. package/node_modules/pi-lens/dist/clients/lsp/launch.js +32 -26
  260. package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +1 -1
  261. package/node_modules/pi-lens/dist/clients/lsp/ruby-drive-dirs.js +83 -0
  262. package/node_modules/pi-lens/dist/clients/lsp/server.js +196 -32
  263. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +28 -4
  264. package/node_modules/pi-lens/dist/clients/lsp-lazy.js +8 -0
  265. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +248 -0
  266. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +123 -32
  267. package/node_modules/pi-lens/dist/clients/mcp/ipc.js +157 -24
  268. package/node_modules/pi-lens/dist/clients/mcp/session.js +216 -8
  269. package/node_modules/pi-lens/dist/clients/memory-sampler.js +115 -0
  270. package/node_modules/pi-lens/dist/clients/metrics-client.js +2 -1
  271. package/node_modules/pi-lens/dist/clients/metrics-history.js +2 -1
  272. package/node_modules/pi-lens/dist/clients/module-report-lsp.js +5 -0
  273. package/node_modules/pi-lens/dist/clients/module-report.js +168 -3
  274. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +319 -118
  275. package/node_modules/pi-lens/dist/clients/opengrep-client.js +1 -1
  276. package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +120 -5
  277. package/node_modules/pi-lens/dist/clients/path-utils.js +110 -9
  278. package/node_modules/pi-lens/dist/clients/pipeline.js +47 -13
  279. package/node_modules/pi-lens/dist/clients/print-mode.js +21 -0
  280. package/node_modules/pi-lens/dist/clients/project-changes.js +40 -0
  281. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +3 -2
  282. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +8 -0
  283. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +21 -1
  284. package/node_modules/pi-lens/dist/clients/project-lens-config.js +86 -9
  285. package/node_modules/pi-lens/dist/clients/project-report.js +27 -13
  286. package/node_modules/pi-lens/dist/clients/project-snapshot.js +175 -20
  287. package/node_modules/pi-lens/dist/clients/project-trust.js +181 -0
  288. package/node_modules/pi-lens/dist/clients/quiet-window.js +18 -4
  289. package/node_modules/pi-lens/dist/clients/read-bridge.js +147 -0
  290. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +159 -1
  291. package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +150 -29
  292. package/node_modules/pi-lens/dist/clients/read-guard.js +185 -4
  293. package/node_modules/pi-lens/dist/clients/resource-sampler.js +70 -84
  294. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +1122 -221
  295. package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +2 -1
  296. package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +21 -10
  297. package/node_modules/pi-lens/dist/clients/review-graph/symbol-id.js +46 -0
  298. package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +57 -6
  299. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +51 -3
  300. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +30 -1
  301. package/node_modules/pi-lens/dist/clients/ruff-client.js +27 -44
  302. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +107 -73
  303. package/node_modules/pi-lens/dist/clients/runtime-context.js +36 -12
  304. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +74 -6
  305. package/node_modules/pi-lens/dist/clients/runtime-session.js +405 -117
  306. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +71 -48
  307. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +218 -23
  308. package/node_modules/pi-lens/dist/clients/runtime-turn.js +237 -64
  309. package/node_modules/pi-lens/dist/clients/rust-client.js +2 -1
  310. package/node_modules/pi-lens/dist/clients/safe-spawn.js +777 -62
  311. package/node_modules/pi-lens/dist/clients/security-scan-client.js +2 -1
  312. package/node_modules/pi-lens/dist/clients/session-state-store.js +3 -2
  313. package/node_modules/pi-lens/dist/clients/sg-runner.js +284 -174
  314. package/node_modules/pi-lens/dist/clients/slow-fs.js +2 -1
  315. package/node_modules/pi-lens/dist/clients/smells-rollup.js +224 -0
  316. package/node_modules/pi-lens/dist/clients/source-filter.js +184 -20
  317. package/node_modules/pi-lens/dist/clients/source-walker.js +66 -10
  318. package/node_modules/pi-lens/dist/clients/startup-scan.js +15 -16
  319. package/node_modules/pi-lens/dist/clients/startup-timing.js +1 -1
  320. package/node_modules/pi-lens/dist/clients/test-runner-client.js +38 -8
  321. package/node_modules/pi-lens/dist/clients/tool-event.js +35 -0
  322. package/node_modules/pi-lens/dist/clients/tool-policy.js +388 -39
  323. package/node_modules/pi-lens/dist/clients/tool-render.js +155 -0
  324. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +6 -1
  325. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +419 -50
  326. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +21 -0
  327. package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +7 -1
  328. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +7 -2
  329. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +44 -7
  330. package/node_modules/pi-lens/dist/clients/user-notify.js +51 -0
  331. package/node_modules/pi-lens/dist/clients/vanished-instance-marker.js +57 -0
  332. package/node_modules/pi-lens/dist/clients/warm-attach.js +33 -13
  333. package/node_modules/pi-lens/dist/clients/widget-state.js +300 -43
  334. package/node_modules/pi-lens/dist/clients/word-index.js +391 -64
  335. package/node_modules/pi-lens/dist/clients/workspace-topology.js +89 -6
  336. package/node_modules/pi-lens/dist/index.js +49361 -36888
  337. package/node_modules/pi-lens/dist/mcp/analyze-cli.js +182 -29
  338. package/node_modules/pi-lens/dist/mcp/server.js +211 -44
  339. package/node_modules/pi-lens/dist/tools/ast-grep-search.js +93 -27
  340. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +200 -36
  341. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +153 -40
  342. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -3
  343. package/node_modules/pi-lens/dist/tools/module-report.js +9 -0
  344. package/node_modules/pi-lens/docs/agent-guide.md +2 -2
  345. package/node_modules/pi-lens/docs/agent-tools.md +21 -10
  346. package/node_modules/pi-lens/docs/analysisall.md +192 -0
  347. package/node_modules/pi-lens/docs/api-ports-inventory.md +165 -0
  348. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +5 -2
  349. package/node_modules/pi-lens/docs/durable-store-audit-1202.md +32 -0
  350. package/node_modules/pi-lens/docs/environment-variables.md +44 -0
  351. package/node_modules/pi-lens/docs/fable.md +27 -0
  352. package/node_modules/pi-lens/docs/features.md +31 -2
  353. package/node_modules/pi-lens/docs/globalconfig.md +45 -1
  354. package/node_modules/pi-lens/docs/language-coverage.md +4 -3
  355. package/node_modules/pi-lens/docs/lsp-capability-matrix.md +2 -2
  356. package/node_modules/pi-lens/docs/mcp.md +37 -4
  357. package/node_modules/pi-lens/docs/servercapabilities.md +3 -3
  358. package/node_modules/pi-lens/docs/settings.md +12 -1
  359. package/node_modules/pi-lens/docs/tools.md +4 -1
  360. package/node_modules/pi-lens/docs/usage.md +18 -0
  361. package/node_modules/pi-lens/package.json +10 -10
  362. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-except-test.yml +49 -0
  363. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-host-path-in-win32-branch-test.yml +87 -0
  364. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-nested-links-test.yml +11 -1
  365. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-raw-json-store-write-test.yml +17 -0
  366. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-win32-isabsolute-for-qualification-test.yml +16 -0
  367. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-except.yml +3 -15
  368. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +25 -0
  369. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-nested-links.yml +7 -0
  370. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +30 -0
  371. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +23 -0
  372. package/node_modules/pi-lens/rules/tree-sitter-queries/tsx/no-nested-links.yml +4 -6
  373. package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/duplicate-function-arg.yml +2 -2
  374. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +55 -2
  375. package/node_modules/pi-mcp-adapter/CHANGELOG.md +25 -0
  376. package/node_modules/pi-mcp-adapter/README.md +34 -3
  377. package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +1 -1
  378. package/node_modules/pi-mcp-adapter/commands.ts +15 -7
  379. package/node_modules/pi-mcp-adapter/direct-tools.ts +54 -7
  380. package/node_modules/pi-mcp-adapter/host-html-template.ts +0 -8
  381. package/node_modules/pi-mcp-adapter/index.ts +8 -3
  382. package/node_modules/pi-mcp-adapter/init.ts +29 -5
  383. package/node_modules/pi-mcp-adapter/lifecycle.ts +0 -4
  384. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +1 -2
  385. package/node_modules/pi-mcp-adapter/mcp-auth.ts +60 -2
  386. package/node_modules/pi-mcp-adapter/mcp-panel.ts +45 -9
  387. package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +22 -20
  388. package/node_modules/pi-mcp-adapter/metadata-cache.ts +27 -4
  389. package/node_modules/pi-mcp-adapter/npx-resolver.ts +81 -26
  390. package/node_modules/pi-mcp-adapter/package.json +3 -3
  391. package/node_modules/pi-mcp-adapter/proxy-modes.ts +108 -26
  392. package/node_modules/pi-mcp-adapter/search-ranking.ts +86 -7
  393. package/node_modules/pi-mcp-adapter/server-manager.ts +36 -7
  394. package/node_modules/pi-mcp-adapter/session-recovery.ts +8 -2
  395. package/node_modules/pi-mcp-adapter/tool-approval.ts +48 -7
  396. package/node_modules/pi-mcp-adapter/tool-metadata.ts +42 -4
  397. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +138 -12
  398. package/node_modules/pi-mcp-adapter/types.ts +93 -27
  399. package/node_modules/pi-mcp-adapter/ui-server.ts +20 -1
  400. package/package.json +17 -13
  401. package/node_modules/pi-lens/dist/clients/ast-grep-parser.js +0 -86
  402. package/node_modules/pi-lens/dist/clients/lsp/interactive-install.js +0 -367
  403. package/node_modules/pi-lens/dist/clients/lsp/lsp-index.js +0 -10
  404. package/node_modules/pi-lens/dist/clients/lsp/server-strategies.js +0 -5
  405. package/node_modules/pi-lens/dist/clients/project-metadata.js +0 -690
  406. package/node_modules/pi-lens/dist/clients/source-groups.js +0 -96
  407. package/node_modules/pi-lens/dist/clients/startup-marker.js +0 -6
  408. package/node_modules/pi-lens/dist/clients/types.js +0 -11
@@ -0,0 +1,255 @@
1
+ /**
2
+ * agent-file-toggle.ts — Pure helpers for the `/agents` file-editing operations:
3
+ * locating an agent's .md file, toggling its `enabled:` frontmatter flag, and
4
+ * serializing an AgentConfig back to frontmatter for eject.
5
+ *
6
+ * These live outside src/index.ts so they can be tested directly: the `/agents`
7
+ * command handler is an ~890-line closure reached only through `registerCommand`,
8
+ * which every test mocks.
9
+ *
10
+ * The read side of this data (src/custom-agents.ts) parses frontmatter with a
11
+ * real YAML parser, so it honors `enabled: false` at any position in the block.
12
+ * This module must agree with it, and splits the work accordingly:
13
+ *
14
+ * - Deciding whether a file is disabled is a *read*, so it calls that same parser
15
+ * (`isDisabledContent`) instead of mirroring it. A mirror has to be right about
16
+ * YAML's boolean spellings and about pi's fence scan, and a regex was wrong
17
+ * about both.
18
+ * - *Editing* cannot go through the parser, because re-serializing a parsed
19
+ * document would reformat a file the README tells users to hand-author —
20
+ * discarding their comments, key order, and quoting. So the edits are line-wise
21
+ * and preserve everything they don't touch.
22
+ *
23
+ * That leaves removal best-effort: it recognizes the parser's case-insensitive
24
+ * bare `false` spellings and trailing comments, and reports `changed: false`
25
+ * for values it cannot rewrite, so the caller refuses honestly rather than
26
+ * announcing a change it did not make.
27
+ */
28
+
29
+ import { existsSync } from "node:fs"
30
+ import { join } from "node:path"
31
+ import { getAgentDir, parseFrontmatter } from "@earendil-works/pi-coding-agent"
32
+ import type { AgentConfig } from "./types.js"
33
+
34
+ export type AgentFileLocation = "project" | "workspace" | "personal"
35
+
36
+ export const projectAgentsDir = (cwd: string = process.cwd()) =>
37
+ join(cwd, ".pi", "agents")
38
+ export const workspaceAgentsDir = (cwd: string = process.cwd()) =>
39
+ join(cwd, ".agents", "agents")
40
+ export const personalAgentsDir = () => join(getAgentDir(), "agents")
41
+
42
+ /**
43
+ * Find the file path of a custom agent by name, in discovery-precedence order
44
+ * (project, workspace, then global). Mirrors the load-side precedence in
45
+ * src/custom-agents.ts — if the two drift, `/agents` edits a file the loader
46
+ * isn't reading.
47
+ */
48
+ export function findAgentFile(
49
+ name: string,
50
+ cwd: string = process.cwd(),
51
+ ): { path: string; location: AgentFileLocation } | undefined {
52
+ const projectPath = join(projectAgentsDir(cwd), `${name}.md`)
53
+ if (existsSync(projectPath)) return { path: projectPath, location: "project" }
54
+ const workspacePath = join(workspaceAgentsDir(cwd), `${name}.md`)
55
+ if (existsSync(workspacePath))
56
+ return { path: workspacePath, location: "workspace" }
57
+ const personalPath = join(personalAgentsDir(), `${name}.md`)
58
+ if (existsSync(personalPath))
59
+ return { path: personalPath, location: "personal" }
60
+ return undefined
61
+ }
62
+
63
+ export type DisableOutcome = "disabled" | "already-disabled" | "no-frontmatter"
64
+
65
+ /** A line that sets `enabled: false`, accepting YAML case and comments. */
66
+ const ENABLED_FALSE = /^[ \t]*enabled:[ \t]*false[ \t]*(?:#[^\r\n]*)?$/i
67
+ /** An opening or closing `---` fence line. */
68
+ const FENCE = /^---[ \t]*$/
69
+
70
+ /**
71
+ * Split a file into its frontmatter lines and everything else, agreeing with
72
+ * what `parseFrontmatter` (the load side) considers a frontmatter block.
73
+ *
74
+ * Lines keep their terminators, so an edit preserves the file's existing line
75
+ * endings instead of rewriting CRLF to LF. Returns undefined when there is no
76
+ * usable block — notably for a BOM-prefixed file, which the parser also reads
77
+ * as having none, so writing a key into it would change nothing on load.
78
+ */
79
+ function splitFrontmatter(
80
+ content: string,
81
+ ):
82
+ | { lines: string[]; openIdx: number; closeIdx: number; eol: string }
83
+ | undefined {
84
+ const lines = content.split(/(?<=\n)/)
85
+ if (lines.length === 0 || !FENCE.test(lines[0].replace(/\r?\n$/, "")))
86
+ return undefined
87
+ const closeIdx = lines.findIndex(
88
+ (l, i) => i > 0 && FENCE.test(l.replace(/\r?\n$/, "")),
89
+ )
90
+ if (closeIdx === -1) return undefined
91
+ return {
92
+ lines,
93
+ openIdx: 0,
94
+ closeIdx,
95
+ eol: lines[0].endsWith("\r\n") ? "\r\n" : "\n",
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Does the loader consider this file disabled?
101
+ *
102
+ * Detection is a READ operation, so it asks the same parser the loader uses
103
+ * rather than mirroring it with a regex — that mirror has to be right about
104
+ * YAML's boolean spellings (`False`, `FALSE`, a trailing `# comment`, a quoted
105
+ * key) *and* about pi's fence scan, which closes the block on any line starting
106
+ * `---` and so ends it early on `----`. A throw means the file is already
107
+ * unparseable, which is what the loader sees too: it skips the agent, so there
108
+ * is no "disabled" state to report.
109
+ */
110
+ export function isDisabledContent(content: string): boolean {
111
+ try {
112
+ return (
113
+ parseFrontmatter<Record<string, unknown>>(content).frontmatter.enabled ===
114
+ false
115
+ )
116
+ } catch {
117
+ return false
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Add `enabled: false` to a file's frontmatter.
123
+ *
124
+ * `outcome` distinguishes a real edit from a no-op so the caller can report
125
+ * honestly instead of unconditionally claiming success.
126
+ */
127
+ export function disableInContent(content: string): {
128
+ content: string
129
+ outcome: DisableOutcome
130
+ } {
131
+ const block = splitFrontmatter(content)
132
+ if (!block) return { content, outcome: "no-frontmatter" }
133
+ if (isDisabledContent(content))
134
+ return { content, outcome: "already-disabled" }
135
+ const lines = [...block.lines]
136
+ lines.splice(1, 0, `enabled: false${block.eol}`)
137
+ return { content: lines.join(""), outcome: "disabled" }
138
+ }
139
+
140
+ /**
141
+ * Remove `enabled: false` from a file's frontmatter, wherever it appears in the
142
+ * block — the loader honors the key at any position, so the two must agree or a
143
+ * hand-authored agent can be disabled and never re-enabled.
144
+ *
145
+ * `changed` is false when the key wasn't found, so the caller can avoid
146
+ * reporting "Enabled <name>" for a write that did nothing.
147
+ */
148
+ export function enableInContent(content: string): {
149
+ content: string
150
+ changed: boolean
151
+ } {
152
+ const block = splitFrontmatter(content)
153
+ if (!block) return { content, changed: false }
154
+ const kept = block.lines.filter(
155
+ (l, i) =>
156
+ !(
157
+ i > 0 &&
158
+ i < block.closeIdx &&
159
+ ENABLED_FALSE.test(l.replace(/\r?\n$/, ""))
160
+ ),
161
+ )
162
+ if (kept.length === block.lines.length) return { content, changed: false }
163
+ return { content: kept.join(""), changed: true }
164
+ }
165
+
166
+ /** Is this the empty stub `/agents` writes when disabling a built-in default? */
167
+ export function isEmptyStub(content: string): boolean {
168
+ return content.replace(/\r\n/g, "\n").trim() === "---\n---"
169
+ }
170
+
171
+ /** The answers `/agents → Create agent → Manual` collects, before serialization. */
172
+ export interface NewAgentInput {
173
+ description: string
174
+ /** Already-resolved `tools:` value ("none", "all", or a CSV of tool names). */
175
+ tools: string
176
+ /** `provider/modelId`, or undefined to inherit the parent's model. */
177
+ model?: string
178
+ /** A pi thinking level, or undefined to inherit. */
179
+ thinking?: string
180
+ systemPrompt: string
181
+ }
182
+
183
+ /**
184
+ * Build the .md file the create wizard writes.
185
+ *
186
+ * `description` and `model` come straight from a free-text prompt, so they are
187
+ * quoted rather than interpolated — `serializeAgentFile` above quotes the
188
+ * description for the same reason. An unquoted YAML scalar mishandles ordinary
189
+ * input in two ways, and both are silent: a colon ("Scout: find things") makes
190
+ * the file unparseable, and since #212 an unparseable agent file is *skipped*,
191
+ * so the wizard reports success for an agent that does not exist; a `#`
192
+ * ("audit #security") opens a comment and truncates the value. `model` can
193
+ * carry a colon too — pi accepts a `provider/model:thinking` suffix.
194
+ *
195
+ * `tools` and `thinking` are not quoted: both are chosen from fixed menus, and
196
+ * `tools` is a CSV that must stay a bare scalar for the loader's parser.
197
+ */
198
+ export function buildNewAgentFile(input: NewAgentInput): string {
199
+ const modelLine = input.model ? `\nmodel: ${JSON.stringify(input.model)}` : ""
200
+ const thinkingLine = input.thinking ? `\nthinking: ${input.thinking}` : ""
201
+ return `---
202
+ description: ${JSON.stringify(input.description)}
203
+ tools: ${input.tools}${modelLine}${thinkingLine}
204
+ prompt_mode: replace
205
+ ---
206
+
207
+ ${input.systemPrompt}
208
+ `
209
+ }
210
+
211
+ /** Render a built-in tool list as a `tools:` frontmatter value. */
212
+ function formatToolsField(tools: string[] | undefined): string {
213
+ if (tools === undefined) return "all"
214
+ if (tools.length === 0) return "none"
215
+ return tools.join(", ")
216
+ }
217
+
218
+ /** Serialize an AgentConfig to a full .md file (frontmatter + system prompt) for eject. */
219
+ export function serializeAgentFile(cfg: AgentConfig): string {
220
+ const fmFields: string[] = []
221
+ fmFields.push(`description: ${JSON.stringify(cfg.description)}`)
222
+ if (cfg.displayName) fmFields.push(`display_name: ${cfg.displayName}`)
223
+ if (cfg.color) fmFields.push(`color: ${JSON.stringify(cfg.color)}`)
224
+ // Absent means "all built-ins"; an EMPTY list means explicitly zero. Writing
225
+ // `all` for both would hand a deliberately tool-less agent the whole toolbox
226
+ // the first time it is ejected.
227
+ fmFields.push(`tools: ${formatToolsField(cfg.builtinToolNames)}`)
228
+ if (cfg.model) fmFields.push(`model: ${cfg.model}`)
229
+ if (cfg.thinking) fmFields.push(`thinking: ${cfg.thinking}`)
230
+ if (cfg.maxTurns) fmFields.push(`max_turns: ${cfg.maxTurns}`)
231
+ if (cfg.allowedSubagents !== undefined) {
232
+ fmFields.push(
233
+ `allowed_subagents: ${cfg.allowedSubagents === "all" ? "all" : cfg.allowedSubagents.join(", ")}`,
234
+ )
235
+ }
236
+ fmFields.push(`prompt_mode: ${cfg.promptMode}`)
237
+ if (cfg.extensions === false) fmFields.push("extensions: false")
238
+ else if (Array.isArray(cfg.extensions))
239
+ fmFields.push(`extensions: ${cfg.extensions.join(", ")}`)
240
+ if (cfg.excludeExtensions?.length)
241
+ fmFields.push(`exclude_extensions: ${cfg.excludeExtensions.join(", ")}`)
242
+ if (cfg.skills === false) fmFields.push("skills: false")
243
+ else if (Array.isArray(cfg.skills))
244
+ fmFields.push(`skills: ${cfg.skills.join(", ")}`)
245
+ if (cfg.disallowedTools?.length)
246
+ fmFields.push(`disallowed_tools: ${cfg.disallowedTools.join(", ")}`)
247
+ if (cfg.inheritContext) fmFields.push("inherit_context: true")
248
+ if (cfg.runInBackground) fmFields.push("run_in_background: true")
249
+ if (cfg.outputTranscript === false) fmFields.push("output_transcript: false")
250
+ if (cfg.isolated) fmFields.push("isolated: true")
251
+ if (cfg.memory) fmFields.push(`memory: ${cfg.memory}`)
252
+ if (cfg.isolation) fmFields.push(`isolation: ${cfg.isolation}`)
253
+
254
+ return `---\n${fmFields.join("\n")}\n---\n\n${cfg.systemPrompt}\n`
255
+ }
@@ -144,6 +144,38 @@ interface SpawnOptions {
144
144
  rootSessionId?: string
145
145
  }
146
146
 
147
+ interface ResumeOptions {
148
+ /**
149
+ * Run the resumed turn detached in the background: return immediately with
150
+ * the record still "running" (or "queued" at the concurrency limit) and
151
+ * notify on completion via onComplete, exactly like a background spawn.
152
+ * Default (false/undefined) runs the resume inline and returns the settled
153
+ * record — the historical behavior.
154
+ */
155
+ isBackground?: boolean
156
+ /** Called on tool start/end with activity info (for streaming progress to UI). */
157
+ onToolActivity?: (activity: ToolActivity) => void
158
+ /** Called at the end of each resumed agentic turn with the cumulative count. */
159
+ onTurnEnd?: (turnCount: number) => void
160
+ /** Called once per assistant message_end with that message's usage delta. */
161
+ onAssistantUsage?: (usage: {
162
+ input: number
163
+ output: number
164
+ cacheWrite: number
165
+ }) => void
166
+ /** Called when the session successfully compacts. */
167
+ onCompaction?: (info: CompactionInfo) => void
168
+ /**
169
+ * Background resume only: called synchronously when the run actually starts —
170
+ * immediately, or later from drainQueue. Callers wire per-run side effects
171
+ * (output-file streaming) here rather than at the call site, so a resume that
172
+ * is stopped while still queued never leaves a subscription behind: `abort()`
173
+ * drops a queued record without reaching `settle()`, which is what would have
174
+ * torn that subscription down.
175
+ */
176
+ onStarted?: () => void
177
+ }
178
+
147
179
  export class AgentManager {
148
180
  private agents = new Map<string, AgentRecord>()
149
181
  private cleanupInterval: ReturnType<typeof setInterval>
@@ -156,7 +188,7 @@ export class AgentManager {
156
188
  private worktreeRepos = new Set<string>()
157
189
 
158
190
  /** Queue of background agents waiting to start. */
159
- private queue: { id: string; args: SpawnArgs }[] = []
191
+ private queue: { id: string; start: () => void }[] = []
160
192
  /** Number of currently running background agents. */
161
193
  private runningBackground = 0
162
194
 
@@ -236,7 +268,7 @@ export class AgentManager {
236
268
  this.runningBackground >= this.maxConcurrent
237
269
  ) {
238
270
  // Queue it — will be started when a running agent completes
239
- this.queue.push({ id, args })
271
+ this.queue.push({ id, start: () => this.startAgent(id, record, args) })
240
272
  return id
241
273
  }
242
274
 
@@ -508,7 +540,7 @@ export class AgentManager {
508
540
  const record = this.agents.get(next.id)
509
541
  if (record?.status !== "queued") continue
510
542
  try {
511
- this.startAgent(next.id, record, next.args)
543
+ next.start()
512
544
  } catch (err) {
513
545
  // Late failure (e.g. strict worktree-isolation) — surface on the record
514
546
  // so the user/agent can see it via /agents, then keep draining.
@@ -570,10 +602,51 @@ export class AgentManager {
570
602
  id: string,
571
603
  prompt: string,
572
604
  signal?: AbortSignal,
605
+ options?: ResumeOptions,
573
606
  ): Promise<AgentRecord | undefined> {
574
607
  const record = this.agents.get(id)
575
608
  if (!record?.session) return undefined
576
609
 
610
+ // Background resume: settle asynchronously and notify on completion exactly
611
+ // like a background spawn, returning immediately with the record still
612
+ // "running" — or "queued" when at the concurrency limit. Previously
613
+ // run_in_background was ignored on resume (the Agent tool's resume branch
614
+ // returned before its background branch, and resume() only ever awaited
615
+ // inline), so a resumed agent always blocked the caller until it finished.
616
+ if (options?.isBackground) {
617
+ // Never re-enter a run that is still in flight. Detaching means the caller
618
+ // gets control back while the record stays "running", so nothing stops the
619
+ // model from resuming the same agent again. Starting a second run would
620
+ // overwrite record.abortController — orphaning the live run beyond the
621
+ // reach of `/agents` stop and abortAll() — double-count the pool slot, and
622
+ // then reject from session.prompt() with "Agent is already processing",
623
+ // whose settle path would abort the LIVE run's children and report a
624
+ // failure for a run that is still going. Refuse instead, leaving the
625
+ // record untouched; the caller decides whether to wait or steer.
626
+ if (record.status === "running" || record.status === "queued")
627
+ return undefined
628
+
629
+ record.isBackground = true
630
+ record.resultConsumed = false
631
+ record.result = undefined
632
+ record.error = undefined
633
+ record.completedAt = undefined
634
+ record.status = "queued"
635
+
636
+ const start = () => this.startResume(id, record, prompt, signal, options)
637
+ if (
638
+ occupiesPoolSlot(record) &&
639
+ this.runningBackground >= this.maxConcurrent
640
+ ) {
641
+ // At the concurrency limit — queue it, drains when a slot frees.
642
+ this.queue.push({ id, start })
643
+ } else {
644
+ start()
645
+ }
646
+ return record
647
+ }
648
+
649
+ // Foreground resume: run inline and return the settled record.
577
650
  record.status = "running"
578
651
  record.startedAt = Date.now()
579
652
  record.completedAt = undefined
@@ -584,13 +657,17 @@ export class AgentManager {
584
657
  const { text, failure } = await resumeAgent(record.session, prompt, {
585
658
  onToolActivity: (activity) => {
586
659
  if (activity.type === "end") record.toolUses++
660
+ options?.onToolActivity?.(activity)
587
661
  },
662
+ onTurnEnd: options?.onTurnEnd,
588
663
  onAssistantUsage: (usage) => {
589
664
  addUsage(record.lifetimeUsage, usage)
665
+ options?.onAssistantUsage?.(usage)
590
666
  },
591
667
  onCompaction: (info) => {
592
668
  record.compactionCount++
593
669
  this.onCompact?.(record, info)
670
+ options?.onCompaction?.(info)
594
671
  },
595
672
  signal,
596
673
  })
@@ -613,6 +690,117 @@ export class AgentManager {
613
690
  return record
614
691
  }
615
692
 
693
+ /**
694
+ * Start a background resume run: detached, settling and notifying like
695
+ * startAgent's background path. Invoked immediately, or from drainQueue when
696
+ * a concurrency slot frees. The session already exists (resume reuses it), so
697
+ * there is no onSessionCreated to hang per-run wiring off — callers use
698
+ * `options.onStarted`, which fires on both the immediate and the drained path.
699
+ */
700
+ private startResume(
701
+ id: string,
702
+ record: AgentRecord,
703
+ prompt: string,
704
+ parentSignal: AbortSignal | undefined,
705
+ options: ResumeOptions,
706
+ ) {
707
+ if (!record.session) return
708
+
709
+ record.status = "running"
710
+ record.startedAt = Date.now()
711
+ if (occupiesPoolSlot(record)) this.runningBackground++
712
+ this.onStart?.(record)
713
+
714
+ // Fresh abort controller so /agents stop and steering target THIS run rather
715
+ // than the previous one's settled controller.
716
+ const abortController = new AbortController()
717
+ record.abortController = abortController
718
+ // Optional, and NOT what the Agent tool passes for a detached resume: a
719
+ // parent signal aborts on the parent's own interrupt (user Esc), which is
720
+ // right for a foreground run whose result the caller is awaiting, and wrong
721
+ // for a detached one — background spawns omit it for exactly this reason.
722
+ let detachParentSignal: (() => void) | undefined
723
+ if (parentSignal) {
724
+ const onParentAbort = () => this.abort(id)
725
+ parentSignal.addEventListener("abort", onParentAbort, { once: true })
726
+ detachParentSignal = () =>
727
+ parentSignal.removeEventListener("abort", onParentAbort)
728
+ }
729
+
730
+ // Per-run side effects (output streaming) — see ResumeOptions.onStarted.
731
+ // After the record is in its running shape, before the run is kicked off.
732
+ try {
733
+ options.onStarted?.()
734
+ } catch {
735
+ /* ignore caller wiring errors */
736
+ }
737
+
738
+ const settle = () => {
739
+ detachParentSignal?.()
740
+ detachParentSignal = undefined
741
+ // Final flush of streaming output file
742
+ if (record.outputCleanup) {
743
+ try {
744
+ record.outputCleanup()
745
+ } catch {
746
+ /* ignore */
747
+ }
748
+ record.outputCleanup = undefined
749
+ }
750
+ // Children spawned during the resumed turn must not outlive it.
751
+ this.abortOwnedChildren(id)
752
+ if (occupiesPoolSlot(record)) this.runningBackground--
753
+ try {
754
+ this.onComplete?.(record)
755
+ } catch {
756
+ /* ignore completion side-effect errors */
757
+ }
758
+ this.drainQueue()
759
+ }
760
+
761
+ const promise = resumeAgent(record.session, prompt, {
762
+ onToolActivity: (activity) => {
763
+ if (activity.type === "end") record.toolUses++
764
+ options.onToolActivity?.(activity)
765
+ },
766
+ onTurnEnd: options.onTurnEnd,
767
+ onAssistantUsage: (usage) => {
768
+ addUsage(record.lifetimeUsage, usage)
769
+ options.onAssistantUsage?.(usage)
770
+ },
771
+ onCompaction: (info) => {
772
+ record.compactionCount++
773
+ this.onCompact?.(record, info)
774
+ options.onCompaction?.(info)
775
+ },
776
+ signal: abortController.signal,
777
+ })
778
+ .then(({ text, failure }) => {
779
+ // Don't overwrite status if externally stopped via abort().
780
+ if (record.status !== "stopped") {
781
+ // Same contract as the spawn path (#144): a failed final turn is an
782
+ // error, not a completion — but the resumed text stays available.
783
+ record.status = failure ? "error" : "completed"
784
+ if (failure) record.error = failure
785
+ }
786
+ record.result = text
787
+ record.completedAt ??= Date.now()
788
+ settle()
789
+ return text
790
+ })
791
+ .catch((err) => {
792
+ if (record.status !== "stopped") {
793
+ record.status = "error"
794
+ record.error = err instanceof Error ? err.message : String(err)
795
+ }
796
+ record.completedAt ??= Date.now()
797
+ settle()
798
+ return ""
799
+ })
800
+
801
+ record.promise = promise
802
+ }
803
+
616
804
  /**
617
805
  * Send a steering message to an agent from the UI (mirrors the steer_subagent
618
806
  * tool). A live session delivers it now — it interrupts the agent after its
@@ -365,7 +365,7 @@ export function setGraceTurns(n: number): void {
365
365
  * Try to find the right model for an agent type.
366
366
  * Priority: explicit option > config.model > parent model.
367
367
  */
368
- function resolveDefaultModel(
368
+ export function resolveDefaultModel(
369
369
  parentModel: Model<any> | undefined,
370
370
  registry: {
371
371
  find(provider: string, modelId: string): Model<any> | undefined
@@ -946,6 +946,9 @@ export async function runAgent(
946
946
  ? SessionManager.create(
947
947
  effectiveCwd,
948
948
  configuredSessionDir ?? defaultSessionDir,
949
+ {
950
+ parentSession: ctx.sessionManager.getSessionFile(),
951
+ },
949
952
  )
950
953
  : SessionManager.inMemory(effectiveCwd)
951
954
 
@@ -1125,6 +1128,8 @@ export async function resumeAgent(
1125
1128
  prompt: string,
1126
1129
  options: {
1127
1130
  onToolActivity?: (activity: ToolActivity) => void
1131
+ /** Called at the end of each resumed agentic turn with the 1-based count. */
1132
+ onTurnEnd?: (turnCount: number) => void
1128
1133
  onAssistantUsage?: (usage: {
1129
1134
  input: number
1130
1135
  output: number
@@ -1143,10 +1148,18 @@ export async function resumeAgent(
1143
1148
  const startLen = session.messages.length
1144
1149
  const collector = collectResponseText(session)
1145
1150
  const cleanupAbort = forwardAbortSignal(session, options.signal)
1151
+ let turnCount = 0
1146
1152
 
1147
1153
  const unsubEvents =
1148
- options.onToolActivity || options.onAssistantUsage || options.onCompaction
1154
+ options.onToolActivity ||
1155
+ options.onTurnEnd ||
1156
+ options.onAssistantUsage ||
1157
+ options.onCompaction
1149
1158
  ? session.subscribe((event: AgentSessionEvent) => {
1159
+ if (event.type === "turn_end") {
1160
+ turnCount++
1161
+ options.onTurnEnd?.(turnCount)
1162
+ }
1150
1163
  if (event.type === "tool_execution_start")
1151
1164
  options.onToolActivity?.({
1152
1165
  type: "start",