@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.
- package/README.md +45 -4
- package/examples/pi-footer.json +275 -0
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +10 -2
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +10 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +24 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +91 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +7 -3
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/index.ts +7 -6
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +21 -12
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +4 -5
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +5 -8
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +22 -12
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +2 -4
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +8 -0
- package/node_modules/@herbertgao/pi-subagents/package.json +8 -4
- package/node_modules/@herbertgao/pi-subagents/src/agent-color.ts +72 -67
- package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +255 -0
- package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +191 -3
- package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +15 -2
- package/node_modules/@herbertgao/pi-subagents/src/index.ts +289 -113
- package/node_modules/@herbertgao/pi-subagents/src/output-file.ts +24 -1
- package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +24 -2
- package/node_modules/@herbertgao/pi-subagents/src/ui/schedule-menu.ts +9 -8
- package/node_modules/@herbertgao/pi-subagents/src/ui/select-item.ts +48 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/keyboard.md +7 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +7 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +16 -5
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +4 -2
- package/node_modules/@narumitw/pi-btw/README.md +8 -7
- package/node_modules/@narumitw/pi-btw/package.json +2 -2
- package/node_modules/@narumitw/pi-btw/src/menu.ts +1 -0
- package/node_modules/pi-footer/CHANGELOG.md +145 -0
- package/node_modules/pi-footer/LICENSE +21 -0
- package/node_modules/pi-footer/README.md +354 -0
- package/node_modules/pi-footer/package.json +68 -0
- package/node_modules/pi-footer/src/cache.ts +106 -0
- package/node_modules/pi-footer/src/colors.ts +237 -0
- package/node_modules/pi-footer/src/config.ts +202 -0
- package/node_modules/pi-footer/src/event-widgets.ts +41 -0
- package/node_modules/pi-footer/src/extension-statuses.ts +102 -0
- package/node_modules/pi-footer/src/git.ts +135 -0
- package/node_modules/pi-footer/src/index.ts +268 -0
- package/node_modules/pi-footer/src/metrics.ts +141 -0
- package/node_modules/pi-footer/src/presets.ts +383 -0
- package/node_modules/pi-footer/src/render.ts +114 -0
- package/node_modules/pi-footer/src/separators.ts +52 -0
- package/node_modules/pi-footer/src/types.ts +122 -0
- package/node_modules/pi-footer/src/ui/color-level-confirm.ts +22 -0
- package/node_modules/pi-footer/src/ui/color-options.ts +195 -0
- package/node_modules/pi-footer/src/ui/config-lifecycle.ts +61 -0
- package/node_modules/pi-footer/src/ui/edit-colors.ts +13 -0
- package/node_modules/pi-footer/src/ui/events.ts +35 -0
- package/node_modules/pi-footer/src/ui/extension-status-picker.ts +60 -0
- package/node_modules/pi-footer/src/ui/extension-statuses.ts +73 -0
- package/node_modules/pi-footer/src/ui/fields.ts +252 -0
- package/node_modules/pi-footer/src/ui/global-menu.ts +126 -0
- package/node_modules/pi-footer/src/ui/helpers.ts +44 -0
- package/node_modules/pi-footer/src/ui/layout.ts +17 -0
- package/node_modules/pi-footer/src/ui/line-list.ts +19 -0
- package/node_modules/pi-footer/src/ui/model.ts +64 -0
- package/node_modules/pi-footer/src/ui/navigation.ts +29 -0
- package/node_modules/pi-footer/src/ui/option-edit.ts +93 -0
- package/node_modules/pi-footer/src/ui/overlay-render.ts +103 -0
- package/node_modules/pi-footer/src/ui/screen-context.ts +18 -0
- package/node_modules/pi-footer/src/ui/screen-controller.ts +27 -0
- package/node_modules/pi-footer/src/ui/screen-render.ts +32 -0
- package/node_modules/pi-footer/src/ui/screen-state.ts +20 -0
- package/node_modules/pi-footer/src/ui/screen.ts +243 -0
- package/node_modules/pi-footer/src/ui/screens/add-widget.ts +92 -0
- package/node_modules/pi-footer/src/ui/screens/confirm-exit.ts +52 -0
- package/node_modules/pi-footer/src/ui/screens/controller.ts +12 -0
- package/node_modules/pi-footer/src/ui/screens/edit-colors.ts +68 -0
- package/node_modules/pi-footer/src/ui/screens/edit-widget.ts +115 -0
- package/node_modules/pi-footer/src/ui/screens/extension-status-row.ts +61 -0
- package/node_modules/pi-footer/src/ui/screens/global.ts +75 -0
- package/node_modules/pi-footer/src/ui/screens/line-list.ts +108 -0
- package/node_modules/pi-footer/src/ui/screens/main.ts +99 -0
- package/node_modules/pi-footer/src/ui/screens/terminal.ts +121 -0
- package/node_modules/pi-footer/src/ui/screens/widget-list.ts +154 -0
- package/node_modules/pi-footer/src/ui/terminal-menu.ts +32 -0
- package/node_modules/pi-footer/src/ui/theme.ts +48 -0
- package/node_modules/pi-footer/src/ui/title-bar.ts +100 -0
- package/node_modules/pi-footer/src/ui/widget-actions.ts +76 -0
- package/node_modules/pi-footer/src/ui.ts +66 -0
- package/node_modules/pi-footer/src/widgets/context.ts +28 -0
- package/node_modules/pi-footer/src/widgets/core/active-tools.ts +17 -0
- package/node_modules/pi-footer/src/widgets/core/context-window.ts +24 -0
- package/node_modules/pi-footer/src/widgets/core/cwd-basename.ts +19 -0
- package/node_modules/pi-footer/src/widgets/core/cwd.ts +108 -0
- package/node_modules/pi-footer/src/widgets/core/event.ts +34 -0
- package/node_modules/pi-footer/src/widgets/core/external-status.ts +102 -0
- package/node_modules/pi-footer/src/widgets/core/model-provider.ts +20 -0
- package/node_modules/pi-footer/src/widgets/core/model.ts +35 -0
- package/node_modules/pi-footer/src/widgets/core/provider.ts +17 -0
- package/node_modules/pi-footer/src/widgets/core/session-name.ts +17 -0
- package/node_modules/pi-footer/src/widgets/core/text-verbosity.ts +17 -0
- package/node_modules/pi-footer/src/widgets/core/thinking-level.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/ahead-behind.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/branch.ts +81 -0
- package/node_modules/pi-footer/src/widgets/git/clean.ts +19 -0
- package/node_modules/pi-footer/src/widgets/git/deletions.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/diff.ts +38 -0
- package/node_modules/pi-footer/src/widgets/git/insertions.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/remote.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/root.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/sha.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/staged.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/status.ts +19 -0
- package/node_modules/pi-footer/src/widgets/git/unstaged.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/untracked.ts +17 -0
- package/node_modules/pi-footer/src/widgets/instance.ts +118 -0
- package/node_modules/pi-footer/src/widgets/layout/custom-text.ts +18 -0
- package/node_modules/pi-footer/src/widgets/layout/flex-separator.ts +32 -0
- package/node_modules/pi-footer/src/widgets/layout/separator.ts +57 -0
- package/node_modules/pi-footer/src/widgets/layout/spacer.ts +31 -0
- package/node_modules/pi-footer/src/widgets/options.ts +122 -0
- package/node_modules/pi-footer/src/widgets/project/runtime.ts +361 -0
- package/node_modules/pi-footer/src/widgets/registry.ts +223 -0
- package/node_modules/pi-footer/src/widgets/session/assistant-messages.ts +17 -0
- package/node_modules/pi-footer/src/widgets/session/compactions.ts +17 -0
- package/node_modules/pi-footer/src/widgets/session/elapsed.ts +18 -0
- package/node_modules/pi-footer/src/widgets/session/last-activity.ts +18 -0
- package/node_modules/pi-footer/src/widgets/session/messages.ts +19 -0
- package/node_modules/pi-footer/src/widgets/session/session-id.ts +17 -0
- package/node_modules/pi-footer/src/widgets/session/session-start.ts +18 -0
- package/node_modules/pi-footer/src/widgets/session/tool-results.ts +17 -0
- package/node_modules/pi-footer/src/widgets/session/total-messages.ts +19 -0
- package/node_modules/pi-footer/src/widgets/session/total-time.ts +18 -0
- package/node_modules/pi-footer/src/widgets/session/user-messages.ts +17 -0
- package/node_modules/pi-footer/src/widgets/store.ts +26 -0
- package/node_modules/pi-footer/src/widgets/tokens/cache-hit-rate.ts +55 -0
- package/node_modules/pi-footer/src/widgets/tokens/cache-read.ts +18 -0
- package/node_modules/pi-footer/src/widgets/tokens/cache-write.ts +18 -0
- package/node_modules/pi-footer/src/widgets/tokens/context-bar.ts +108 -0
- package/node_modules/pi-footer/src/widgets/tokens/context-length.ts +26 -0
- package/node_modules/pi-footer/src/widgets/tokens/context-remaining.ts +29 -0
- package/node_modules/pi-footer/src/widgets/tokens/context.ts +29 -0
- package/node_modules/pi-footer/src/widgets/tokens/cost.ts +51 -0
- package/node_modules/pi-footer/src/widgets/tokens/input-speed.ts +25 -0
- package/node_modules/pi-footer/src/widgets/tokens/input-tokens.ts +18 -0
- package/node_modules/pi-footer/src/widgets/tokens/output-speed.ts +25 -0
- package/node_modules/pi-footer/src/widgets/tokens/output-tokens.ts +18 -0
- package/node_modules/pi-footer/src/widgets/tokens/tokens.ts +20 -0
- package/node_modules/pi-footer/src/widgets/tokens/total-speed.ts +25 -0
- package/node_modules/pi-footer/src/widgets/tokens/total-tokens.ts +18 -0
- package/node_modules/pi-footer/src/widgets/types.ts +181 -0
- package/node_modules/pi-footer/src/widgets/utils/colors.ts +11 -0
- package/node_modules/pi-footer/src/widgets/utils/context.ts +112 -0
- package/node_modules/pi-footer/src/widgets/utils/session.ts +19 -0
- package/node_modules/pi-footer/src/widgets/utils/token-format.ts +78 -0
- package/node_modules/pi-lens/CHANGELOG.md +2440 -954
- package/node_modules/pi-lens/README.md +13 -0
- package/node_modules/pi-lens/dist/clients/actionable-warnings.js +73 -18
- package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +21 -4
- package/node_modules/pi-lens/dist/clients/ast-grep-client.js +68 -16
- package/node_modules/pi-lens/dist/clients/ast-grep-types.js +0 -1
- package/node_modules/pi-lens/dist/clients/ast-grep-yaml-synth.js +37 -10
- package/node_modules/pi-lens/dist/clients/atomic-write-staging.js +63 -0
- package/node_modules/pi-lens/dist/clients/atomic-write.js +114 -14
- package/node_modules/pi-lens/dist/clients/bash-file-access.js +193 -23
- package/node_modules/pi-lens/dist/clients/biome-client.js +29 -25
- package/node_modules/pi-lens/dist/clients/bootstrap.js +11 -2
- package/node_modules/pi-lens/dist/clients/bounded-cache.js +34 -0
- package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +192 -0
- package/node_modules/pi-lens/dist/clients/bus-publish.js +10 -3
- package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +47 -5
- package/node_modules/pi-lens/dist/clients/cache-manager.js +97 -9
- package/node_modules/pi-lens/dist/clients/cache-observability.js +301 -14
- package/node_modules/pi-lens/dist/clients/call-graph.js +449 -117
- package/node_modules/pi-lens/dist/clients/child-unref.js +85 -0
- package/node_modules/pi-lens/dist/clients/codebase-model.js +52 -23
- package/node_modules/pi-lens/dist/clients/collateral-test-role.js +40 -0
- package/node_modules/pi-lens/dist/clients/complexity-client.js +2 -1
- package/node_modules/pi-lens/dist/clients/console-guard-install.js +14 -0
- package/node_modules/pi-lens/dist/clients/cooperative-budget.js +42 -0
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +2 -1
- package/node_modules/pi-lens/dist/clients/debug-handles.js +206 -0
- package/node_modules/pi-lens/dist/clients/debug-heap.js +167 -0
- package/node_modules/pi-lens/dist/clients/degradation-ledger.js +153 -0
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +347 -75
- package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +61 -11
- package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +10 -3
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +63 -20
- package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +23 -7
- package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +8 -18
- package/node_modules/pi-lens/dist/clients/dispatch/facts/tree-sitter-facts.js +8 -5
- package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +35 -0
- package/node_modules/pi-lens/dist/clients/dispatch/inline-suppressions.js +17 -8
- package/node_modules/pi-lens/dist/clients/dispatch/integration.js +1085 -681
- package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +14 -0
- package/node_modules/pi-lens/dist/clients/dispatch/plan.js +11 -1
- package/node_modules/pi-lens/dist/clients/dispatch/rule-id-normalize.js +50 -0
- package/node_modules/pi-lens/dist/clients/dispatch/rule-policy.js +135 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +23 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +5 -9
- package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +3 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +154 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +3 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +5 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +14 -6
- package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +4 -9
- package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +9 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +4 -8
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +2 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +163 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +37 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +3 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +13 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +3 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +277 -26
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +22 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +5 -4
- package/node_modules/pi-lens/dist/clients/disposition-publish.js +10 -3
- package/node_modules/pi-lens/dist/clients/durable-store.js +97 -0
- package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +94 -3
- package/node_modules/pi-lens/dist/clients/extension-log.js +139 -0
- package/node_modules/pi-lens/dist/clients/extension-mode.js +73 -0
- package/node_modules/pi-lens/dist/clients/file-kinds.js +57 -0
- package/node_modules/pi-lens/dist/clients/file-role.js +18 -3
- package/node_modules/pi-lens/dist/clients/file-utils.js +56 -34
- package/node_modules/pi-lens/dist/clients/format-events-publish.js +16 -3
- package/node_modules/pi-lens/dist/clients/format-service.js +24 -8
- package/node_modules/pi-lens/dist/clients/formatters-lazy.js +8 -0
- package/node_modules/pi-lens/dist/clients/formatters.js +358 -61
- package/node_modules/pi-lens/dist/clients/generated-artifacts.js +148 -15
- package/node_modules/pi-lens/dist/clients/git-guard.js +681 -11
- package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +4 -0
- package/node_modules/pi-lens/dist/clients/go-client.js +2 -1
- package/node_modules/pi-lens/dist/clients/govulncheck-client.js +8 -0
- package/node_modules/pi-lens/dist/clients/grammar-source.js +12 -4
- package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +21 -3
- package/node_modules/pi-lens/dist/clients/host-ports.js +31 -0
- package/node_modules/pi-lens/dist/clients/installer/index.js +390 -99
- package/node_modules/pi-lens/dist/clients/instance-reaper.js +142 -172
- package/node_modules/pi-lens/dist/clients/jscpd-client.js +31 -54
- package/node_modules/pi-lens/dist/clients/knip-client.js +15 -36
- package/node_modules/pi-lens/dist/clients/language-policy.js +14 -2
- package/node_modules/pi-lens/dist/clients/language-profile.js +13 -7
- package/node_modules/pi-lens/dist/clients/latency-logger.js +23 -1
- package/node_modules/pi-lens/dist/clients/lens-config.js +12 -1
- package/node_modules/pi-lens/dist/clients/lens-engine.js +86 -17
- package/node_modules/pi-lens/dist/clients/lens-events.js +27 -4
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
- package/node_modules/pi-lens/dist/clients/lens-map.js +94 -19
- package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +30 -0
- package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -1
- package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +1 -1
- package/node_modules/pi-lens/dist/clients/lsp/client.js +493 -48
- package/node_modules/pi-lens/dist/clients/lsp/config.js +13 -2
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +86 -0
- package/node_modules/pi-lens/dist/clients/lsp/edits.js +1012 -121
- package/node_modules/pi-lens/dist/clients/lsp/index.js +1807 -780
- package/node_modules/pi-lens/dist/clients/lsp/launch.js +32 -26
- package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +1 -1
- package/node_modules/pi-lens/dist/clients/lsp/ruby-drive-dirs.js +83 -0
- package/node_modules/pi-lens/dist/clients/lsp/server.js +196 -32
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +28 -4
- package/node_modules/pi-lens/dist/clients/lsp-lazy.js +8 -0
- package/node_modules/pi-lens/dist/clients/lsp-mutation.js +248 -0
- package/node_modules/pi-lens/dist/clients/mcp/analyze.js +123 -32
- package/node_modules/pi-lens/dist/clients/mcp/ipc.js +157 -24
- package/node_modules/pi-lens/dist/clients/mcp/session.js +216 -8
- package/node_modules/pi-lens/dist/clients/memory-sampler.js +115 -0
- package/node_modules/pi-lens/dist/clients/metrics-client.js +2 -1
- package/node_modules/pi-lens/dist/clients/metrics-history.js +2 -1
- package/node_modules/pi-lens/dist/clients/module-report-lsp.js +5 -0
- package/node_modules/pi-lens/dist/clients/module-report.js +168 -3
- package/node_modules/pi-lens/dist/clients/ndjson-logger.js +319 -118
- package/node_modules/pi-lens/dist/clients/opengrep-client.js +1 -1
- package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +120 -5
- package/node_modules/pi-lens/dist/clients/path-utils.js +110 -9
- package/node_modules/pi-lens/dist/clients/pipeline.js +47 -13
- package/node_modules/pi-lens/dist/clients/print-mode.js +21 -0
- package/node_modules/pi-lens/dist/clients/project-changes.js +40 -0
- package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +3 -2
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +8 -0
- package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +21 -1
- package/node_modules/pi-lens/dist/clients/project-lens-config.js +86 -9
- package/node_modules/pi-lens/dist/clients/project-report.js +27 -13
- package/node_modules/pi-lens/dist/clients/project-snapshot.js +175 -20
- package/node_modules/pi-lens/dist/clients/project-trust.js +181 -0
- package/node_modules/pi-lens/dist/clients/quiet-window.js +18 -4
- package/node_modules/pi-lens/dist/clients/read-bridge.js +147 -0
- package/node_modules/pi-lens/dist/clients/read-guard-logger.js +159 -1
- package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +150 -29
- package/node_modules/pi-lens/dist/clients/read-guard.js +185 -4
- package/node_modules/pi-lens/dist/clients/resource-sampler.js +70 -84
- package/node_modules/pi-lens/dist/clients/review-graph/builder.js +1122 -221
- package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +2 -1
- package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +21 -10
- package/node_modules/pi-lens/dist/clients/review-graph/symbol-id.js +46 -0
- package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +57 -6
- package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +51 -3
- package/node_modules/pi-lens/dist/clients/review-graph-logger.js +30 -1
- package/node_modules/pi-lens/dist/clients/ruff-client.js +27 -44
- package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +107 -73
- package/node_modules/pi-lens/dist/clients/runtime-context.js +36 -12
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +74 -6
- package/node_modules/pi-lens/dist/clients/runtime-session.js +405 -117
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +71 -48
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +218 -23
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +237 -64
- package/node_modules/pi-lens/dist/clients/rust-client.js +2 -1
- package/node_modules/pi-lens/dist/clients/safe-spawn.js +777 -62
- package/node_modules/pi-lens/dist/clients/security-scan-client.js +2 -1
- package/node_modules/pi-lens/dist/clients/session-state-store.js +3 -2
- package/node_modules/pi-lens/dist/clients/sg-runner.js +284 -174
- package/node_modules/pi-lens/dist/clients/slow-fs.js +2 -1
- package/node_modules/pi-lens/dist/clients/smells-rollup.js +224 -0
- package/node_modules/pi-lens/dist/clients/source-filter.js +184 -20
- package/node_modules/pi-lens/dist/clients/source-walker.js +66 -10
- package/node_modules/pi-lens/dist/clients/startup-scan.js +15 -16
- package/node_modules/pi-lens/dist/clients/startup-timing.js +1 -1
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +38 -8
- package/node_modules/pi-lens/dist/clients/tool-event.js +35 -0
- package/node_modules/pi-lens/dist/clients/tool-policy.js +388 -39
- package/node_modules/pi-lens/dist/clients/tool-render.js +155 -0
- package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +6 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +419 -50
- package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +21 -0
- package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +7 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +7 -2
- package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +44 -7
- package/node_modules/pi-lens/dist/clients/user-notify.js +51 -0
- package/node_modules/pi-lens/dist/clients/vanished-instance-marker.js +57 -0
- package/node_modules/pi-lens/dist/clients/warm-attach.js +33 -13
- package/node_modules/pi-lens/dist/clients/widget-state.js +300 -43
- package/node_modules/pi-lens/dist/clients/word-index.js +391 -64
- package/node_modules/pi-lens/dist/clients/workspace-topology.js +89 -6
- package/node_modules/pi-lens/dist/index.js +49361 -36888
- package/node_modules/pi-lens/dist/mcp/analyze-cli.js +182 -29
- package/node_modules/pi-lens/dist/mcp/server.js +211 -44
- package/node_modules/pi-lens/dist/tools/ast-grep-search.js +93 -27
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +200 -36
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +153 -40
- package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -3
- package/node_modules/pi-lens/dist/tools/module-report.js +9 -0
- package/node_modules/pi-lens/docs/agent-guide.md +2 -2
- package/node_modules/pi-lens/docs/agent-tools.md +21 -10
- package/node_modules/pi-lens/docs/analysisall.md +192 -0
- package/node_modules/pi-lens/docs/api-ports-inventory.md +165 -0
- package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +5 -2
- package/node_modules/pi-lens/docs/durable-store-audit-1202.md +32 -0
- package/node_modules/pi-lens/docs/environment-variables.md +44 -0
- package/node_modules/pi-lens/docs/fable.md +27 -0
- package/node_modules/pi-lens/docs/features.md +31 -2
- package/node_modules/pi-lens/docs/globalconfig.md +45 -1
- package/node_modules/pi-lens/docs/language-coverage.md +4 -3
- package/node_modules/pi-lens/docs/lsp-capability-matrix.md +2 -2
- package/node_modules/pi-lens/docs/mcp.md +37 -4
- package/node_modules/pi-lens/docs/servercapabilities.md +3 -3
- package/node_modules/pi-lens/docs/settings.md +12 -1
- package/node_modules/pi-lens/docs/tools.md +4 -1
- package/node_modules/pi-lens/docs/usage.md +18 -0
- package/node_modules/pi-lens/package.json +10 -10
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-except-test.yml +49 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-host-path-in-win32-branch-test.yml +87 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-nested-links-test.yml +11 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-raw-json-store-write-test.yml +17 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-win32-isabsolute-for-qualification-test.yml +16 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-except.yml +3 -15
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +25 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-nested-links.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +30 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +23 -0
- package/node_modules/pi-lens/rules/tree-sitter-queries/tsx/no-nested-links.yml +4 -6
- package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/duplicate-function-arg.yml +2 -2
- package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +55 -2
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +25 -0
- package/node_modules/pi-mcp-adapter/README.md +34 -3
- package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +1 -1
- package/node_modules/pi-mcp-adapter/commands.ts +15 -7
- package/node_modules/pi-mcp-adapter/direct-tools.ts +54 -7
- package/node_modules/pi-mcp-adapter/host-html-template.ts +0 -8
- package/node_modules/pi-mcp-adapter/index.ts +8 -3
- package/node_modules/pi-mcp-adapter/init.ts +29 -5
- package/node_modules/pi-mcp-adapter/lifecycle.ts +0 -4
- package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +1 -2
- package/node_modules/pi-mcp-adapter/mcp-auth.ts +60 -2
- package/node_modules/pi-mcp-adapter/mcp-panel.ts +45 -9
- package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +22 -20
- package/node_modules/pi-mcp-adapter/metadata-cache.ts +27 -4
- package/node_modules/pi-mcp-adapter/npx-resolver.ts +81 -26
- package/node_modules/pi-mcp-adapter/package.json +3 -3
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +108 -26
- package/node_modules/pi-mcp-adapter/search-ranking.ts +86 -7
- package/node_modules/pi-mcp-adapter/server-manager.ts +36 -7
- package/node_modules/pi-mcp-adapter/session-recovery.ts +8 -2
- package/node_modules/pi-mcp-adapter/tool-approval.ts +48 -7
- package/node_modules/pi-mcp-adapter/tool-metadata.ts +42 -4
- package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +138 -12
- package/node_modules/pi-mcp-adapter/types.ts +93 -27
- package/node_modules/pi-mcp-adapter/ui-server.ts +20 -1
- package/package.json +17 -13
- package/node_modules/pi-lens/dist/clients/ast-grep-parser.js +0 -86
- package/node_modules/pi-lens/dist/clients/lsp/interactive-install.js +0 -367
- package/node_modules/pi-lens/dist/clients/lsp/lsp-index.js +0 -10
- package/node_modules/pi-lens/dist/clients/lsp/server-strategies.js +0 -5
- package/node_modules/pi-lens/dist/clients/project-metadata.js +0 -690
- package/node_modules/pi-lens/dist/clients/source-groups.js +0 -96
- package/node_modules/pi-lens/dist/clients/startup-marker.js +0 -6
- 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;
|
|
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
|
-
|
|
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 ||
|
|
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",
|