@herbertgao/pi-extensions 2026.8.4 → 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 +4 -4
- 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-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 +11 -12
- 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
|
@@ -31,7 +31,17 @@ import {
|
|
|
31
31
|
} from "@earendil-works/pi-tui"
|
|
32
32
|
import { Type } from "@sinclair/typebox"
|
|
33
33
|
import { abortable } from "./abortable.js"
|
|
34
|
-
import { renderAgentName } from "./agent-color.js"
|
|
34
|
+
import { hasAgentBadge, renderAgentName } from "./agent-color.js"
|
|
35
|
+
import {
|
|
36
|
+
buildNewAgentFile,
|
|
37
|
+
disableInContent,
|
|
38
|
+
enableInContent,
|
|
39
|
+
findAgentFile,
|
|
40
|
+
isEmptyStub,
|
|
41
|
+
personalAgentsDir,
|
|
42
|
+
projectAgentsDir,
|
|
43
|
+
serializeAgentFile,
|
|
44
|
+
} from "./agent-file-toggle.js"
|
|
35
45
|
import { AgentManager } from "./agent-manager.js"
|
|
36
46
|
import {
|
|
37
47
|
getAgentConversation,
|
|
@@ -74,6 +84,7 @@ import {
|
|
|
74
84
|
import { getMaxSubagentDepth, setMaxSubagentDepth } from "./nested-tools.js"
|
|
75
85
|
import {
|
|
76
86
|
createOutputFilePath,
|
|
87
|
+
ensureOutputFile,
|
|
77
88
|
getOutputTranscriptDefault,
|
|
78
89
|
setOutputTranscriptDefault,
|
|
79
90
|
streamToOutputFile,
|
|
@@ -121,6 +132,7 @@ import {
|
|
|
121
132
|
} from "./ui/agent-widget.js"
|
|
122
133
|
import { FleetList, type FleetUICtx } from "./ui/fleet-list.js"
|
|
123
134
|
import { showSchedulesMenu } from "./ui/schedule-menu.js"
|
|
135
|
+
import { selectItem } from "./ui/select-item.js"
|
|
124
136
|
import {
|
|
125
137
|
addUsage,
|
|
126
138
|
getLifetimeTotal,
|
|
@@ -362,6 +374,43 @@ function buildNotificationDetails(
|
|
|
362
374
|
}
|
|
363
375
|
}
|
|
364
376
|
|
|
377
|
+
/**
|
|
378
|
+
* Format an agent's tool scope for the Agent tool description.
|
|
379
|
+
*
|
|
380
|
+
* This suffix describes BUILT-IN scope only — extension tools are resolved when
|
|
381
|
+
* the agent runs (extensions can register asynchronously), so they cannot be
|
|
382
|
+
* enumerated while the description is being built. That is why an agent with
|
|
383
|
+
* `tools: "*, ext:mcp/search"` renders "*" and always has.
|
|
384
|
+
*
|
|
385
|
+
* Two distinctions matter, both of them capability claims the orchestrator acts on:
|
|
386
|
+
*
|
|
387
|
+
* - absent vs empty. `builtinToolNames: undefined` means the agent never narrowed
|
|
388
|
+
* its tools (the shipped defaults); `[]` is what `tools: none` and an `ext:`-only
|
|
389
|
+
* `tools:` parse to, and the runtime really does hand those agents no built-ins.
|
|
390
|
+
* Rendering both "*" tells the orchestrator a tool-less agent can run `bash`.
|
|
391
|
+
* - empty-with-extensions vs empty-without. Zero built-ins does NOT imply zero
|
|
392
|
+
* tools: `tools: none` alongside `extensions:` still surfaces every extension
|
|
393
|
+
* tool (see test/fixtures/.pi/agents/tools-none.md, which expects three). Calling
|
|
394
|
+
* that "none" understates the agent instead of overstating it — better, but still
|
|
395
|
+
* wrong, and it would route work away from the only agent able to do it. "none"
|
|
396
|
+
* is therefore reserved for agents that genuinely can call nothing: `isolated`
|
|
397
|
+
* agents and those with `extensions: false`.
|
|
398
|
+
*/
|
|
399
|
+
export function formatToolsSuffix(cfg: AgentConfig | undefined): string {
|
|
400
|
+
const tools = cfg?.builtinToolNames
|
|
401
|
+
if (!tools) return "*"
|
|
402
|
+
if (tools.length === 0) {
|
|
403
|
+
// `isolated` overrides extensions to false in the runner, so both mean the
|
|
404
|
+
// agent has no extension tools either — and then it truly has nothing.
|
|
405
|
+
const noExtensionTools = cfg?.isolated === true || cfg?.extensions === false
|
|
406
|
+
return noExtensionTools ? "none" : "no built-ins, extension tools only"
|
|
407
|
+
}
|
|
408
|
+
const isFullSet =
|
|
409
|
+
tools.length === BUILTIN_TOOL_NAMES.length &&
|
|
410
|
+
BUILTIN_TOOL_NAMES.every((t) => tools.includes(t))
|
|
411
|
+
return isFullSet ? "*" : tools.join(", ")
|
|
412
|
+
}
|
|
413
|
+
|
|
365
414
|
export default function (pi: ExtensionAPI) {
|
|
366
415
|
// Child AgentSessions load normal extensions. Re-entering this extension there
|
|
367
416
|
// would create another manager and leak handlers. Nested orchestration is
|
|
@@ -640,6 +689,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
640
689
|
undefined,
|
|
641
690
|
(record) => {
|
|
642
691
|
if (record.parentAgentId) return
|
|
692
|
+
// Agent-tool spawns refresh these surfaces in their tool handler, but RPC
|
|
693
|
+
// and scheduler spawns enter through the manager directly.
|
|
694
|
+
if (currentCtx?.hasUI) {
|
|
695
|
+
widget.ensureTimer()
|
|
696
|
+
widget.update()
|
|
697
|
+
fleet.ensureTimer()
|
|
698
|
+
fleet.update()
|
|
699
|
+
}
|
|
643
700
|
// Emit started event when agent transitions to running (including from queue)
|
|
644
701
|
pi.events.emit("subagents:started", {
|
|
645
702
|
id: record.id,
|
|
@@ -752,6 +809,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
752
809
|
// bound session_start, so a filtered-out activation never advertises (#142).
|
|
753
810
|
pi.on("session_start", async (_event, ctx) => {
|
|
754
811
|
currentCtx = ctx
|
|
812
|
+
if (ctx.hasUI) {
|
|
813
|
+
widget.setUICtx(ctx.ui)
|
|
814
|
+
fleet.setUICtx(ctx.ui as any)
|
|
815
|
+
}
|
|
755
816
|
manager.clearCompleted(true)
|
|
756
817
|
// Guard mirrors the `!scheduler.isActive()` pattern below: session_start
|
|
757
818
|
// fires once per activation, but a double-bind must not leak listeners.
|
|
@@ -935,16 +996,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
935
996
|
widget.onTurnStart()
|
|
936
997
|
})
|
|
937
998
|
|
|
938
|
-
/** Format an agent's tool scope: "*" when it has all built-ins, else a comma-separated list. */
|
|
939
|
-
const formatToolsSuffix = (cfg: AgentConfig | undefined): string => {
|
|
940
|
-
const tools = cfg?.builtinToolNames
|
|
941
|
-
if (!tools || tools.length === 0) return "*"
|
|
942
|
-
const isFullSet =
|
|
943
|
-
tools.length === BUILTIN_TOOL_NAMES.length &&
|
|
944
|
-
BUILTIN_TOOL_NAMES.every((t) => tools.includes(t))
|
|
945
|
-
return isFullSet ? "*" : tools.join(", ")
|
|
946
|
-
}
|
|
947
|
-
|
|
948
999
|
/** Build the full type list text dynamically from available agents only. */
|
|
949
1000
|
const buildTypeListText = () => {
|
|
950
1001
|
const available = getAvailableTypes()
|
|
@@ -1195,7 +1246,7 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1195
1246
|
resume: Type.Optional(
|
|
1196
1247
|
Type.String({
|
|
1197
1248
|
description:
|
|
1198
|
-
"Optional agent ID to resume from. Continues from previous context.",
|
|
1249
|
+
"Optional agent ID to resume from. Continues from previous context. Combine with run_in_background to resume detached and be notified on completion. An agent can only be resumed once its current run has finished — use steer_subagent to reach one mid-run.",
|
|
1199
1250
|
}),
|
|
1200
1251
|
),
|
|
1201
1252
|
isolated: Type.Optional(
|
|
@@ -1222,20 +1273,34 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1222
1273
|
// ---- Custom rendering: Claude Code style ----
|
|
1223
1274
|
|
|
1224
1275
|
renderCall(args, theme, context) {
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1276
|
+
// A badge closes its own background, which would clear the tool block's row tint
|
|
1277
|
+
// for the rest of the line, so the badge restores it. The tint is opened here too:
|
|
1278
|
+
// the TUI's Box paints it, but HTML export takes it from CSS, and restoring a
|
|
1279
|
+
// background the line never opened is what banded the export before. The line is
|
|
1280
|
+
// deliberately left open — Box.applyBackgroundToLine pads to width and *then*
|
|
1281
|
+
// wraps, so closing here would leave that padding untinted, and HTML export closes
|
|
1282
|
+
// any open span per line anyway. No badge means no tint, so an uncolored agent
|
|
1283
|
+
// renders exactly the line it always did.
|
|
1284
|
+
const rowBackground = hasAgentBadge(args.subagent_type)
|
|
1285
|
+
? theme.getBgAnsi(
|
|
1286
|
+
context.isPartial
|
|
1287
|
+
? "toolPendingBg"
|
|
1288
|
+
: context.isError
|
|
1289
|
+
? "toolErrorBg"
|
|
1290
|
+
: "toolSuccessBg",
|
|
1291
|
+
)
|
|
1292
|
+
: ""
|
|
1231
1293
|
const desc = args.description ?? ""
|
|
1232
1294
|
const name = renderAgentName(args.subagent_type, theme, {
|
|
1233
1295
|
fallbackColor: "toolTitle",
|
|
1234
|
-
restoreBackground,
|
|
1296
|
+
restoreBackground: rowBackground,
|
|
1235
1297
|
bold: true,
|
|
1236
1298
|
})
|
|
1237
1299
|
return new Text(
|
|
1238
|
-
|
|
1300
|
+
rowBackground +
|
|
1301
|
+
"▸ " +
|
|
1302
|
+
name +
|
|
1303
|
+
(desc ? " " + theme.fg("muted", desc) : ""),
|
|
1239
1304
|
0,
|
|
1240
1305
|
0,
|
|
1241
1306
|
)
|
|
@@ -1555,6 +1620,144 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1555
1620
|
`Agent "${params.resume}" has no active session to resume.`,
|
|
1556
1621
|
)
|
|
1557
1622
|
}
|
|
1623
|
+
|
|
1624
|
+
// Background resume: detached run that notifies on completion, mirroring
|
|
1625
|
+
// a background spawn. Previously run_in_background was silently ignored
|
|
1626
|
+
// on resume (this branch returned before the background branch below),
|
|
1627
|
+
// so a resumed agent always blocked the main loop until it finished.
|
|
1628
|
+
if (runInBackground) {
|
|
1629
|
+
const id = existing.id
|
|
1630
|
+
// A detached resume hands control back while the record stays
|
|
1631
|
+
// "running", so nothing stops the model from resuming the same agent
|
|
1632
|
+
// again mid-run. manager.resume() refuses that (it would orphan the
|
|
1633
|
+
// live run's abort controller); say why here, where the model can act
|
|
1634
|
+
// on it, instead of letting it read as a generic failure.
|
|
1635
|
+
if (existing.status === "running" || existing.status === "queued") {
|
|
1636
|
+
return textResult(
|
|
1637
|
+
`Agent "${params.resume}" is still ${existing.status} — it can only be resumed once its current run finishes.\n` +
|
|
1638
|
+
`Use steer_subagent to send it a message mid-run, or get_subagent_result to wait for it.`,
|
|
1639
|
+
)
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
const joinMode = resolveJoinMode(defaultJoinMode, true)
|
|
1643
|
+
existing.toolCallId = toolCallId
|
|
1644
|
+
if (joinMode) existing.joinMode = joinMode
|
|
1645
|
+
// Reuse the agent's transcript rather than starting a fresh one: the
|
|
1646
|
+
// path is deterministic per agent+session, so writing an initial entry
|
|
1647
|
+
// would truncate the previous run's turns (see ensureOutputFile).
|
|
1648
|
+
if (existing.outputFile) {
|
|
1649
|
+
// Preserve the original transcript path across parent session
|
|
1650
|
+
// switches; records intentionally survive those switches for
|
|
1651
|
+
// resume. Only create a path for an older run that never wrote
|
|
1652
|
+
// one and is now being resumed with transcripts enabled.
|
|
1653
|
+
ensureOutputFile(existing.outputFile)
|
|
1654
|
+
} else if (outputTranscript) {
|
|
1655
|
+
existing.outputFile = createOutputFilePath(
|
|
1656
|
+
ctx.cwd,
|
|
1657
|
+
id,
|
|
1658
|
+
ctx.sessionManager.getSessionId(),
|
|
1659
|
+
)
|
|
1660
|
+
ensureOutputFile(existing.outputFile)
|
|
1661
|
+
}
|
|
1662
|
+
// Anchor streaming past the turns already on disk, captured BEFORE the
|
|
1663
|
+
// run starts. The resumed prompt lands as an ordinary user message at
|
|
1664
|
+
// this index, so it is written exactly once.
|
|
1665
|
+
const transcriptAnchor = existing.session.messages.length
|
|
1666
|
+
|
|
1667
|
+
const { state: bgState, callbacks: bgCallbacks } =
|
|
1668
|
+
createActivityTracker(effectiveMaxTurns)
|
|
1669
|
+
// resumeAgent has no onSessionCreated — the session predates this run —
|
|
1670
|
+
// so seed it directly, or the widget shows no context % for the agent.
|
|
1671
|
+
bgState.session = existing.session
|
|
1672
|
+
|
|
1673
|
+
// No `signal`: a background spawn deliberately omits it, and a detached
|
|
1674
|
+
// resume must behave the same. Passing it would abort this agent when
|
|
1675
|
+
// the parent turn is interrupted (user Esc), while agents started with
|
|
1676
|
+
// run_in_background in that same turn keep going.
|
|
1677
|
+
const record = await manager.resume(
|
|
1678
|
+
params.resume,
|
|
1679
|
+
params.prompt,
|
|
1680
|
+
undefined,
|
|
1681
|
+
{
|
|
1682
|
+
isBackground: true,
|
|
1683
|
+
onToolActivity: bgCallbacks.onToolActivity,
|
|
1684
|
+
onTurnEnd: bgCallbacks.onTurnEnd,
|
|
1685
|
+
onAssistantUsage: bgCallbacks.onAssistantUsage,
|
|
1686
|
+
// Fires when the run actually starts — immediately, or on queue
|
|
1687
|
+
// drain. Wiring it here (rather than after resume() returns) means a
|
|
1688
|
+
// resume stopped while still queued never started streaming, so
|
|
1689
|
+
// there is no subscription left behind for a later run to trip over.
|
|
1690
|
+
onStarted: () => {
|
|
1691
|
+
const rec = manager.getRecord(id)
|
|
1692
|
+
if (rec?.session && rec.outputFile) {
|
|
1693
|
+
rec.outputCleanup = streamToOutputFile(
|
|
1694
|
+
rec.session,
|
|
1695
|
+
rec.outputFile,
|
|
1696
|
+
id,
|
|
1697
|
+
ctx.cwd,
|
|
1698
|
+
transcriptAnchor,
|
|
1699
|
+
)
|
|
1700
|
+
}
|
|
1701
|
+
},
|
|
1702
|
+
},
|
|
1703
|
+
)
|
|
1704
|
+
if (!record) {
|
|
1705
|
+
return textResult(`Failed to resume agent "${params.resume}".`)
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
if (joinMode != null && joinMode !== "async") {
|
|
1709
|
+
currentBatchAgents.push({ id, joinMode })
|
|
1710
|
+
if (batchFinalizeTimer) clearTimeout(batchFinalizeTimer)
|
|
1711
|
+
batchFinalizeTimer = setTimeout(finalizeBatch, 100)
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
agentActivity.set(id, bgState)
|
|
1715
|
+
// This agent already finished once, so the widget holds a finished-age
|
|
1716
|
+
// for it that is past the linger limit — without clearing it, the
|
|
1717
|
+
// resumed run's ✓/✗ line never renders and the agent just vanishes.
|
|
1718
|
+
widget.markRunning(id)
|
|
1719
|
+
widget.ensureTimer()
|
|
1720
|
+
widget.update()
|
|
1721
|
+
fleet.ensureTimer()
|
|
1722
|
+
fleet.update()
|
|
1723
|
+
|
|
1724
|
+
// Resume ignores subagent_type (the record keeps the type it was
|
|
1725
|
+
// spawned with), so report the record's own identity — a "created"
|
|
1726
|
+
// event carrying the caller's type would re-register the agent under
|
|
1727
|
+
// the wrong one in cross-extension mirrors keyed by id.
|
|
1728
|
+
pi.events.emit("subagents:created", {
|
|
1729
|
+
id,
|
|
1730
|
+
type: existing.type,
|
|
1731
|
+
description: existing.description,
|
|
1732
|
+
isBackground: true,
|
|
1733
|
+
})
|
|
1734
|
+
|
|
1735
|
+
const isQueued = record.status === "queued"
|
|
1736
|
+
return textResult(
|
|
1737
|
+
`Agent ${isQueued ? "queued" : "resumed"} in background.\n` +
|
|
1738
|
+
`Agent ID: ${id}\n` +
|
|
1739
|
+
`Type: ${existing.type}\n` +
|
|
1740
|
+
(record.outputFile
|
|
1741
|
+
? `Output file: ${record.outputFile}\n`
|
|
1742
|
+
: "") +
|
|
1743
|
+
(isQueued
|
|
1744
|
+
? `Position: queued (max ${manager.getMaxConcurrent()} concurrent)\n`
|
|
1745
|
+
: "") +
|
|
1746
|
+
`\nYou will be notified when this agent completes.\n` +
|
|
1747
|
+
`Use get_subagent_result to retrieve full results, or steer_subagent to send it messages.`,
|
|
1748
|
+
{
|
|
1749
|
+
...detailBase,
|
|
1750
|
+
subagentType: existing.type,
|
|
1751
|
+
displayName: existing.type,
|
|
1752
|
+
toolUses: record.toolUses,
|
|
1753
|
+
tokens: "",
|
|
1754
|
+
durationMs: 0,
|
|
1755
|
+
status: "background" as const,
|
|
1756
|
+
agentId: id,
|
|
1757
|
+
},
|
|
1758
|
+
)
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1558
1761
|
const record = await manager.resume(
|
|
1559
1762
|
params.resume,
|
|
1560
1763
|
params.prompt,
|
|
@@ -1992,27 +2195,9 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1992
2195
|
|
|
1993
2196
|
// ---- /agents interactive menu ----
|
|
1994
2197
|
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
/** Find the file path of a custom agent by name, in discovery-precedence order (project, workspace, then global). */
|
|
2000
|
-
function findAgentFile(
|
|
2001
|
-
name: string,
|
|
2002
|
-
):
|
|
2003
|
-
| { path: string; location: "project" | "workspace" | "personal" }
|
|
2004
|
-
| undefined {
|
|
2005
|
-
const projectPath = join(projectAgentsDir(), `${name}.md`)
|
|
2006
|
-
if (existsSync(projectPath))
|
|
2007
|
-
return { path: projectPath, location: "project" }
|
|
2008
|
-
const workspacePath = join(workspaceAgentsDir(), `${name}.md`)
|
|
2009
|
-
if (existsSync(workspacePath))
|
|
2010
|
-
return { path: workspacePath, location: "workspace" }
|
|
2011
|
-
const personalPath = join(personalAgentsDir(), `${name}.md`)
|
|
2012
|
-
if (existsSync(personalPath))
|
|
2013
|
-
return { path: personalPath, location: "personal" }
|
|
2014
|
-
return undefined
|
|
2015
|
-
}
|
|
2198
|
+
// Directory resolution and the frontmatter edits live in agent-file-toggle.ts
|
|
2199
|
+
// so they are reachable from tests — this command handler is only registered
|
|
2200
|
+
// through `registerCommand`, which every test mocks.
|
|
2016
2201
|
|
|
2017
2202
|
function getModelLabel(type: string, registry?: ModelRegistry): string {
|
|
2018
2203
|
const cfg = getAgentConfig(type)
|
|
@@ -2189,19 +2374,15 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
2189
2374
|
return
|
|
2190
2375
|
}
|
|
2191
2376
|
|
|
2192
|
-
|
|
2377
|
+
// Numbered + item-paired. Two same-type agents spawned together with the
|
|
2378
|
+
// same description render identically here, and resolving the choice by
|
|
2379
|
+
// string match would open whichever came first.
|
|
2380
|
+
const record = await selectItem(ctx.ui, "Running agents", agents, (a) => {
|
|
2193
2381
|
const dn = getDisplayName(a.type)
|
|
2194
2382
|
const dur = formatDuration(a.startedAt, a.completedAt)
|
|
2195
2383
|
return `${dn} (${a.description}) · ${a.toolUses} tools · ${a.status} · ${dur}`
|
|
2196
2384
|
})
|
|
2197
|
-
|
|
2198
|
-
const choice = await ctx.ui.select("Running agents", options)
|
|
2199
|
-
if (!choice) return
|
|
2200
|
-
|
|
2201
|
-
// Find the selected agent by matching the option index
|
|
2202
|
-
const idx = options.indexOf(choice)
|
|
2203
|
-
if (idx < 0) return
|
|
2204
|
-
const record = agents[idx]
|
|
2385
|
+
if (!record) return
|
|
2205
2386
|
|
|
2206
2387
|
await viewAgentConversation(ctx, record)
|
|
2207
2388
|
// Back-navigation: re-show the list
|
|
@@ -2351,40 +2532,7 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
2351
2532
|
if (!overwrite) return
|
|
2352
2533
|
}
|
|
2353
2534
|
|
|
2354
|
-
|
|
2355
|
-
const fmFields: string[] = []
|
|
2356
|
-
fmFields.push(`description: ${JSON.stringify(cfg.description)}`)
|
|
2357
|
-
if (cfg.displayName) fmFields.push(`display_name: ${cfg.displayName}`)
|
|
2358
|
-
if (cfg.color) fmFields.push(`color: ${JSON.stringify(cfg.color)}`)
|
|
2359
|
-
fmFields.push(`tools: ${cfg.builtinToolNames?.join(", ") || "all"}`)
|
|
2360
|
-
if (cfg.model) fmFields.push(`model: ${cfg.model}`)
|
|
2361
|
-
if (cfg.thinking) fmFields.push(`thinking: ${cfg.thinking}`)
|
|
2362
|
-
if (cfg.maxTurns) fmFields.push(`max_turns: ${cfg.maxTurns}`)
|
|
2363
|
-
if (cfg.allowedSubagents !== undefined) {
|
|
2364
|
-
fmFields.push(
|
|
2365
|
-
`allowed_subagents: ${cfg.allowedSubagents === "all" ? "all" : cfg.allowedSubagents.join(", ")}`,
|
|
2366
|
-
)
|
|
2367
|
-
}
|
|
2368
|
-
fmFields.push(`prompt_mode: ${cfg.promptMode}`)
|
|
2369
|
-
if (cfg.extensions === false) fmFields.push("extensions: false")
|
|
2370
|
-
else if (Array.isArray(cfg.extensions))
|
|
2371
|
-
fmFields.push(`extensions: ${cfg.extensions.join(", ")}`)
|
|
2372
|
-
if (cfg.excludeExtensions?.length)
|
|
2373
|
-
fmFields.push(`exclude_extensions: ${cfg.excludeExtensions.join(", ")}`)
|
|
2374
|
-
if (cfg.skills === false) fmFields.push("skills: false")
|
|
2375
|
-
else if (Array.isArray(cfg.skills))
|
|
2376
|
-
fmFields.push(`skills: ${cfg.skills.join(", ")}`)
|
|
2377
|
-
if (cfg.disallowedTools?.length)
|
|
2378
|
-
fmFields.push(`disallowed_tools: ${cfg.disallowedTools.join(", ")}`)
|
|
2379
|
-
if (cfg.inheritContext) fmFields.push("inherit_context: true")
|
|
2380
|
-
if (cfg.runInBackground) fmFields.push("run_in_background: true")
|
|
2381
|
-
if (cfg.outputTranscript === false)
|
|
2382
|
-
fmFields.push("output_transcript: false")
|
|
2383
|
-
if (cfg.isolated) fmFields.push("isolated: true")
|
|
2384
|
-
if (cfg.memory) fmFields.push(`memory: ${cfg.memory}`)
|
|
2385
|
-
if (cfg.isolation) fmFields.push(`isolation: ${cfg.isolation}`)
|
|
2386
|
-
|
|
2387
|
-
const content = `---\n${fmFields.join("\n")}\n---\n\n${cfg.systemPrompt}\n`
|
|
2535
|
+
const content = serializeAgentFile(cfg)
|
|
2388
2536
|
|
|
2389
2537
|
const { writeFileSync } = await import("node:fs")
|
|
2390
2538
|
writeFileSync(targetPath, content, "utf-8")
|
|
@@ -2398,11 +2546,20 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
2398
2546
|
if (file) {
|
|
2399
2547
|
// Existing file — set enabled: false in frontmatter (idempotent)
|
|
2400
2548
|
const content = readFileSync(file.path, "utf-8")
|
|
2401
|
-
|
|
2549
|
+
const { content: updated, outcome } = disableInContent(content)
|
|
2550
|
+
if (outcome === "already-disabled") {
|
|
2402
2551
|
ctx.ui.notify(`${name} is already disabled.`, "info")
|
|
2403
2552
|
return
|
|
2404
2553
|
}
|
|
2405
|
-
|
|
2554
|
+
if (outcome === "no-frontmatter") {
|
|
2555
|
+
// Nothing to edit — say so rather than rewriting the file unchanged and
|
|
2556
|
+
// reporting success for a change that never happened.
|
|
2557
|
+
ctx.ui.notify(
|
|
2558
|
+
`Cannot disable ${name}: ${file.path} has no frontmatter block.`,
|
|
2559
|
+
"error",
|
|
2560
|
+
)
|
|
2561
|
+
return
|
|
2562
|
+
}
|
|
2406
2563
|
const { writeFileSync } = await import("node:fs")
|
|
2407
2564
|
writeFileSync(file.path, updated, "utf-8")
|
|
2408
2565
|
reloadCustomAgents()
|
|
@@ -2435,11 +2592,17 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
2435
2592
|
if (!file) return
|
|
2436
2593
|
|
|
2437
2594
|
const content = readFileSync(file.path, "utf-8")
|
|
2438
|
-
const updated = content
|
|
2595
|
+
const { content: updated, changed } = enableInContent(content)
|
|
2596
|
+
if (!changed && !isEmptyStub(updated)) {
|
|
2597
|
+
// The file carries no `enabled: false` to remove, so it was never disabled
|
|
2598
|
+
// by us — reporting success here would hide a no-op.
|
|
2599
|
+
ctx.ui.notify(`${name} is not disabled in ${file.path}.`, "info")
|
|
2600
|
+
return
|
|
2601
|
+
}
|
|
2439
2602
|
const { writeFileSync } = await import("node:fs")
|
|
2440
2603
|
|
|
2441
2604
|
// If the file was just a stub ("---\n---\n"), delete it to restore the built-in default
|
|
2442
|
-
if (
|
|
2605
|
+
if (isEmptyStub(updated)) {
|
|
2443
2606
|
unlinkSync(file.path)
|
|
2444
2607
|
reloadCustomAgents()
|
|
2445
2608
|
ctx.ui.notify(`Enabled ${name} (removed ${file.path})`, "info")
|
|
@@ -2507,7 +2670,7 @@ The file format is a markdown file with YAML frontmatter and a system prompt bod
|
|
|
2507
2670
|
---
|
|
2508
2671
|
name: <optional UI display name; Claude Code-compatible alias for display_name>
|
|
2509
2672
|
description: <one-line description shown in UI>
|
|
2510
|
-
color: <optional name badge color: red, blue, green, yellow, purple, orange, pink, cyan, an Agency Agents alias, or quoted "#RRGGBB">
|
|
2673
|
+
color: <optional agent name badge color: red, blue, green, yellow, purple, orange, pink, cyan, an Agency Agents alias, or quoted "#RRGGBB">
|
|
2511
2674
|
tools: <comma-separated built-in tools: read, bash, edit, write, grep, find, ls. Use "none" for no tools. Omit for all tools>
|
|
2512
2675
|
model: <optional model as "provider/modelId", e.g. "anthropic/claude-haiku-4-5". Omit to inherit parent model>
|
|
2513
2676
|
thinking: <optional thinking level: ${THINKING_LEVELS.join(", ")}. Omit to inherit>
|
|
@@ -2614,16 +2777,12 @@ Write the file using the write tool. Only write the file, nothing else.`
|
|
|
2614
2777
|
])
|
|
2615
2778
|
if (!modelChoice) return
|
|
2616
2779
|
|
|
2617
|
-
let
|
|
2618
|
-
if (modelChoice === "haiku")
|
|
2619
|
-
|
|
2620
|
-
else if (modelChoice === "
|
|
2621
|
-
modelLine = "\nmodel: anthropic/claude-sonnet-4-6"
|
|
2622
|
-
else if (modelChoice === "opus")
|
|
2623
|
-
modelLine = "\nmodel: anthropic/claude-opus-4-6"
|
|
2780
|
+
let model: string | undefined
|
|
2781
|
+
if (modelChoice === "haiku") model = "anthropic/claude-haiku-4-5"
|
|
2782
|
+
else if (modelChoice === "sonnet") model = "anthropic/claude-sonnet-4-6"
|
|
2783
|
+
else if (modelChoice === "opus") model = "anthropic/claude-opus-4-6"
|
|
2624
2784
|
else if (modelChoice === "custom...") {
|
|
2625
|
-
|
|
2626
|
-
if (customModel) modelLine = `\nmodel: ${customModel}`
|
|
2785
|
+
model = (await ctx.ui.input("Model (provider/modelId)")) || undefined
|
|
2627
2786
|
}
|
|
2628
2787
|
|
|
2629
2788
|
// 5. Thinking
|
|
@@ -2634,23 +2793,17 @@ Write the file using the write tool. Only write the file, nothing else.`
|
|
|
2634
2793
|
])
|
|
2635
2794
|
if (!thinkingChoice) return
|
|
2636
2795
|
|
|
2637
|
-
let thinkingLine = ""
|
|
2638
|
-
if (thinkingChoice !== "inherit")
|
|
2639
|
-
thinkingLine = `\nthinking: ${thinkingChoice}`
|
|
2640
|
-
|
|
2641
2796
|
// 6. System prompt
|
|
2642
2797
|
const systemPrompt = await ctx.ui.editor("System prompt", "")
|
|
2643
2798
|
if (systemPrompt === undefined) return
|
|
2644
2799
|
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
${systemPrompt}
|
|
2653
|
-
`
|
|
2800
|
+
const content = buildNewAgentFile({
|
|
2801
|
+
description,
|
|
2802
|
+
tools,
|
|
2803
|
+
model,
|
|
2804
|
+
thinking: thinkingChoice === "inherit" ? undefined : thinkingChoice,
|
|
2805
|
+
systemPrompt,
|
|
2806
|
+
})
|
|
2654
2807
|
|
|
2655
2808
|
mkdirSync(targetDir, { recursive: true })
|
|
2656
2809
|
const targetPath = join(targetDir, `${name}.md`)
|
|
@@ -2669,7 +2822,16 @@ ${systemPrompt}
|
|
|
2669
2822
|
ctx.ui.notify(`Created ${targetPath}`, "info")
|
|
2670
2823
|
}
|
|
2671
2824
|
|
|
2672
|
-
|
|
2825
|
+
/**
|
|
2826
|
+
* Every settings mutation writes this WHOLE object back to disk, so a field
|
|
2827
|
+
* missing here is erased from the user's subagents.json the next time they
|
|
2828
|
+
* toggle something unrelated. `SubagentsSettings` has every field optional,
|
|
2829
|
+
* so a `: SubagentsSettings` return annotation would let a newly-added setting
|
|
2830
|
+
* be forgotten here and still type-check. `satisfies` instead: it still checks
|
|
2831
|
+
* each value's type and rejects a mistyped key, but leaves the return type
|
|
2832
|
+
* inferred so `_NoMissingSettingsKeys` below can check completeness.
|
|
2833
|
+
*/
|
|
2834
|
+
function snapshotSettings() {
|
|
2673
2835
|
return {
|
|
2674
2836
|
maxConcurrent: manager.getMaxConcurrent(),
|
|
2675
2837
|
// 0 = unlimited — per SubagentsSettings.defaultMaxTurns docstring and
|
|
@@ -2691,9 +2853,23 @@ ${systemPrompt}
|
|
|
2691
2853
|
// explicit configuration — which then fails loudly if general-purpose later
|
|
2692
2854
|
// goes away. undefined is dropped by JSON.stringify.
|
|
2693
2855
|
fallbackSubagent: getFallbackSubagent(),
|
|
2694
|
-
}
|
|
2856
|
+
} satisfies SubagentsSettings
|
|
2695
2857
|
}
|
|
2696
2858
|
|
|
2859
|
+
// Compile-time completeness guard for snapshotSettings(). If a field is added
|
|
2860
|
+
// to SubagentsSettings and not mirrored above, this Exclude is non-empty and
|
|
2861
|
+
// fails to satisfy `never` — turning a silent settings-erasure bug into a
|
|
2862
|
+
// typecheck error. `npm run typecheck` runs in CI.
|
|
2863
|
+
type _NoMissingSettingsKeys =
|
|
2864
|
+
Exclude<
|
|
2865
|
+
keyof SubagentsSettings,
|
|
2866
|
+
keyof ReturnType<typeof snapshotSettings>
|
|
2867
|
+
> extends never
|
|
2868
|
+
? true
|
|
2869
|
+
: ["snapshotSettings() is missing a SubagentsSettings key"]
|
|
2870
|
+
const _settingsSnapshotIsComplete: _NoMissingSettingsKeys = true
|
|
2871
|
+
void _settingsSnapshotIsComplete
|
|
2872
|
+
|
|
2697
2873
|
const NUMERIC_IDS = new Set([
|
|
2698
2874
|
"maxConcurrent",
|
|
2699
2875
|
"defaultMaxTurns",
|
|
@@ -66,6 +66,23 @@ export function createOutputFilePath(
|
|
|
66
66
|
return join(dir, `${agentId}.output`)
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Ensure a transcript file exists without disturbing what is already in it.
|
|
71
|
+
*
|
|
72
|
+
* A resume reuses the agent's existing transcript (same deterministic path), so
|
|
73
|
+
* it must never call `writeInitialEntry` — that truncates, discarding turns the
|
|
74
|
+
* completion notification still points the user at, and any history the session
|
|
75
|
+
* has since compacted away is gone for good. Appending nothing creates the file
|
|
76
|
+
* when this is the agent's first transcript and is a no-op when it is not.
|
|
77
|
+
*/
|
|
78
|
+
export function ensureOutputFile(path: string): void {
|
|
79
|
+
try {
|
|
80
|
+
appendFileSync(path, "", "utf-8")
|
|
81
|
+
} catch {
|
|
82
|
+
/* ignore — streaming writes are best-effort too */
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
69
86
|
/** Write the initial user prompt entry. */
|
|
70
87
|
export function writeInitialEntry(
|
|
71
88
|
path: string,
|
|
@@ -93,8 +110,14 @@ export function streamToOutputFile(
|
|
|
93
110
|
path: string,
|
|
94
111
|
agentId: string,
|
|
95
112
|
cwd: string,
|
|
113
|
+
startIndex?: number,
|
|
96
114
|
): () => void {
|
|
97
|
-
|
|
115
|
+
// Index of the first message this stream is responsible for. A spawn writes
|
|
116
|
+
// messages[0] as the initial prompt entry, so it starts at 1. A resume hands
|
|
117
|
+
// in the session's length as of just before the run: the session already
|
|
118
|
+
// holds every prior turn, and re-emitting those would duplicate history that
|
|
119
|
+
// is already in the file.
|
|
120
|
+
let writtenCount = startIndex ?? 1
|
|
98
121
|
|
|
99
122
|
const flush = () => {
|
|
100
123
|
const messages = session.messages
|
|
@@ -358,6 +358,17 @@ export class AgentWidget {
|
|
|
358
358
|
}
|
|
359
359
|
}
|
|
360
360
|
|
|
361
|
+
/**
|
|
362
|
+
* Drop an agent's finished-age (call when a settled agent starts running
|
|
363
|
+
* again, i.e. a background resume). markFinished only seeds an age it has not
|
|
364
|
+
* seen before, so a resumed agent would otherwise keep the age from its
|
|
365
|
+
* previous run — already past the linger limit, hiding the new run's
|
|
366
|
+
* completion line entirely.
|
|
367
|
+
*/
|
|
368
|
+
markRunning(agentId: string) {
|
|
369
|
+
this.finishedTurnAge.delete(agentId)
|
|
370
|
+
}
|
|
371
|
+
|
|
361
372
|
/** Render a finished agent line. */
|
|
362
373
|
private renderFinishedLine(
|
|
363
374
|
a: {
|
|
@@ -536,6 +547,16 @@ export class AgentWidget {
|
|
|
536
547
|
let hiddenRunning = 0
|
|
537
548
|
let hiddenFinished = 0
|
|
538
549
|
|
|
550
|
+
// Reserve the queued line's row up front. It is a single summary of N
|
|
551
|
+
// waiting agents, so it cannot be folded into the "+N more" count (which
|
|
552
|
+
// is denominated in agents) without either under-reporting it as 1 or
|
|
553
|
+
// inflating the total with agents that were never getting their own rows.
|
|
554
|
+
// Reserving costs at most one running agent — which IS counted below —
|
|
555
|
+
// and makes the drop unreachable. It matters most exactly when it used to
|
|
556
|
+
// vanish: the pool is saturated and the queue is what the user needs to see.
|
|
557
|
+
const queuedReserve = queuedLine ? 1 : 0
|
|
558
|
+
budget -= queuedReserve
|
|
559
|
+
|
|
539
560
|
// 1. Running agents (2 lines each)
|
|
540
561
|
for (const pair of runningLines) {
|
|
541
562
|
if (budget >= 2) {
|
|
@@ -546,8 +567,9 @@ export class AgentWidget {
|
|
|
546
567
|
}
|
|
547
568
|
}
|
|
548
569
|
|
|
549
|
-
// 2. Queued line
|
|
550
|
-
if (queuedLine
|
|
570
|
+
// 2. Queued line (always fits — its row was reserved above)
|
|
571
|
+
if (queuedLine) {
|
|
572
|
+
budget += queuedReserve
|
|
551
573
|
lines.push(queuedLine)
|
|
552
574
|
budget--
|
|
553
575
|
}
|