lsd-pi 1.2.4 → 1.3.6
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 +22 -16
- package/dist/app-paths.d.ts +4 -0
- package/dist/app-paths.js +4 -0
- package/dist/bedrock-auth.d.ts +4 -0
- package/dist/bedrock-auth.js +4 -0
- package/dist/bundled-extension-paths.d.ts +4 -0
- package/dist/bundled-extension-paths.js +4 -0
- package/dist/cli-theme.d.ts +2 -2
- package/dist/cli-theme.js +13 -14
- package/dist/cli.js +43 -3
- package/dist/codex-rotate-settings.d.ts +4 -0
- package/dist/codex-rotate-settings.js +4 -0
- package/dist/help-text.d.ts +4 -0
- package/dist/help-text.js +4 -0
- package/dist/lsd-brand.d.ts +4 -0
- package/dist/lsd-brand.js +4 -0
- package/dist/onboarding-llm.d.ts +5 -0
- package/dist/onboarding-llm.js +5 -0
- package/dist/project-sessions.d.ts +4 -0
- package/dist/project-sessions.js +4 -0
- package/dist/resources/agents/generic.md +1 -0
- package/dist/resources/agents/scout.md +8 -1
- package/dist/resources/agents/worker.md +1 -0
- package/dist/resources/extensions/ask-user-questions.js +70 -0
- package/dist/resources/extensions/bg-shell/bg-shell-tool.js +6 -16
- package/dist/resources/extensions/browser-tools/tools/codegen.js +5 -5
- package/dist/resources/extensions/browser-tools/tools/navigation.js +107 -178
- package/dist/resources/extensions/browser-tools/tools/network-mock.js +112 -167
- package/dist/resources/extensions/browser-tools/tools/pages.js +182 -234
- package/dist/resources/extensions/browser-tools/tools/refs.js +202 -461
- package/dist/resources/extensions/browser-tools/tools/session.js +176 -323
- package/dist/resources/extensions/browser-tools/tools/state-persistence.js +91 -154
- package/dist/resources/extensions/browser-tools/utils.js +1 -1
- package/dist/resources/extensions/mac-tools/index.js +19 -34
- package/dist/resources/extensions/memory/index.js +20 -2
- package/dist/resources/extensions/shared/interview-ui.js +103 -20
- package/dist/resources/extensions/slash-commands/extension-manifest.json +2 -2
- package/dist/resources/extensions/slash-commands/fast.js +73 -0
- package/dist/resources/extensions/slash-commands/index.js +2 -0
- package/dist/resources/extensions/slash-commands/plan.js +54 -28
- package/dist/resources/extensions/slash-commands/tools.js +40 -4
- package/dist/resources/extensions/subagent/agent-switcher-component.js +208 -0
- package/dist/resources/extensions/subagent/agent-switcher-model.js +107 -0
- package/dist/resources/extensions/subagent/background-job-manager.js +24 -6
- package/dist/resources/extensions/subagent/background-runner.js +4 -0
- package/dist/resources/extensions/subagent/in-process-runner.js +387 -0
- package/dist/resources/extensions/subagent/index.js +715 -370
- package/dist/resources/extensions/subagent/launch-helpers.js +19 -5
- package/dist/resources/extensions/subagent/legacy-runner.js +503 -0
- package/dist/resources/extensions/voice/index.js +96 -36
- package/dist/resources/extensions/voice/push-to-talk.js +26 -0
- package/dist/shared-paths.d.ts +4 -0
- package/dist/shared-paths.js +4 -0
- package/dist/shared-preferences.d.ts +4 -0
- package/dist/shared-preferences.js +4 -0
- package/dist/startup-model-validation.d.ts +1 -1
- package/dist/startup-timings.d.ts +4 -0
- package/dist/startup-timings.js +4 -0
- package/dist/update-check.d.ts +4 -0
- package/dist/update-check.js +4 -0
- package/dist/update-cmd.d.ts +4 -0
- package/dist/update-cmd.js +4 -0
- package/dist/welcome-screen.js +4 -4
- package/dist/wizard.d.ts +4 -0
- package/dist/wizard.js +4 -0
- package/package.json +1 -1
- package/packages/pi-agent-core/dist/agent.d.ts +28 -0
- package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent.js +105 -5
- package/packages/pi-agent-core/dist/agent.js.map +1 -1
- package/packages/pi-agent-core/dist/types.d.ts +13 -2
- package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/types.js.map +1 -1
- package/packages/pi-agent-core/src/agent.ts +142 -6
- package/packages/pi-agent-core/src/types.ts +12 -3
- package/packages/pi-ai/dist/adaptive/classifier.d.ts +29 -0
- package/packages/pi-ai/dist/adaptive/classifier.d.ts.map +1 -0
- package/packages/pi-ai/dist/adaptive/classifier.js +72 -0
- package/packages/pi-ai/dist/adaptive/classifier.js.map +1 -0
- package/packages/pi-ai/dist/adaptive/classifier.test.d.ts +2 -0
- package/packages/pi-ai/dist/adaptive/classifier.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/adaptive/classifier.test.js +32 -0
- package/packages/pi-ai/dist/adaptive/classifier.test.js.map +1 -0
- package/packages/pi-ai/dist/index.d.ts +1 -0
- package/packages/pi-ai/dist/index.d.ts.map +1 -1
- package/packages/pi-ai/dist/index.js +1 -0
- package/packages/pi-ai/dist/index.js.map +1 -1
- package/packages/pi-ai/dist/providers/amazon-bedrock.js +0 -2
- package/packages/pi-ai/dist/providers/amazon-bedrock.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.js +0 -2
- package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
- package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts +1 -1
- package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
- package/packages/pi-ai/dist/providers/google-gemini-cli.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/google-gemini-cli.js +0 -4
- package/packages/pi-ai/dist/providers/google-gemini-cli.js.map +1 -1
- package/packages/pi-ai/dist/providers/google-vertex.js +0 -5
- package/packages/pi-ai/dist/providers/google-vertex.js.map +1 -1
- package/packages/pi-ai/dist/providers/google.js +0 -5
- package/packages/pi-ai/dist/providers/google.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts +35 -2
- package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.js +32 -6
- package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.test.js +127 -16
- package/packages/pi-ai/dist/providers/openai-codex-responses.test.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.d.ts +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.js +0 -1
- package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.d.ts +9 -2
- package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.js +67 -0
- package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.js.map +1 -0
- package/packages/pi-ai/dist/providers/openai-responses.js +21 -3
- package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-shared.d.ts +0 -1
- package/packages/pi-ai/dist/providers/openai-shared.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-shared.js +0 -4
- package/packages/pi-ai/dist/providers/openai-shared.js.map +1 -1
- package/packages/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/simple-options.js +2 -1
- package/packages/pi-ai/dist/providers/simple-options.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +6 -2
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/src/adaptive/classifier.test.ts +38 -0
- package/packages/pi-ai/src/adaptive/classifier.ts +107 -0
- package/packages/pi-ai/src/index.ts +1 -0
- package/packages/pi-ai/src/providers/amazon-bedrock.ts +0 -2
- package/packages/pi-ai/src/providers/anthropic-shared.ts +0 -2
- package/packages/pi-ai/src/providers/azure-openai-responses.ts +1 -1
- package/packages/pi-ai/src/providers/google-gemini-cli.ts +0 -4
- package/packages/pi-ai/src/providers/google-vertex.ts +0 -5
- package/packages/pi-ai/src/providers/google.ts +0 -5
- package/packages/pi-ai/src/providers/openai-codex-responses.test.ts +143 -20
- package/packages/pi-ai/src/providers/openai-codex-responses.ts +48 -7
- package/packages/pi-ai/src/providers/openai-completions.ts +1 -2
- package/packages/pi-ai/src/providers/openai-responses.fast-mode.test.ts +73 -0
- package/packages/pi-ai/src/providers/openai-responses.ts +27 -4
- package/packages/pi-ai/src/providers/openai-shared.ts +0 -3
- package/packages/pi-ai/src/providers/simple-options.ts +2 -1
- package/packages/pi-ai/src/types.ts +6 -2
- package/packages/pi-coding-agent/dist/cli/args.js +2 -2
- package/packages/pi-coding-agent/dist/cli/args.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts +7 -2
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +53 -20
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/keybindings.d.ts +1 -1
- package/packages/pi-coding-agent/dist/core/keybindings.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/keybindings.js +2 -0
- package/packages/pi-coding-agent/dist/core/keybindings.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/lsp.md +3 -1
- package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +36 -8
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.test.js +37 -0
- package/packages/pi-coding-agent/dist/core/sdk.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.d.ts +8 -0
- package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.js +4 -0
- package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +18 -7
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.js +35 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js +32 -2
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/skills.js +4 -1
- package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/slash-commands.js +2 -1
- package/packages/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +12 -3
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tool-priority.d.ts +4 -0
- package/packages/pi-coding-agent/dist/core/tool-priority.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/tool-priority.js +18 -0
- package/packages/pi-coding-agent/dist/core/tool-priority.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/tool-priority.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/tool-priority.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/tool-priority.test.js +27 -0
- package/packages/pi-coding-agent/dist/core/tool-priority.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/tools/grep.js +1 -1
- package/packages/pi-coding-agent/dist/core/tools/grep.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.js +2 -0
- package/packages/pi-coding-agent/dist/core/tools/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/pty.d.ts +10 -1
- package/packages/pi-coding-agent/dist/core/tools/pty.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/pty.js +29 -3
- package/packages/pi-coding-agent/dist/core/tools/pty.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.js +26 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.d.ts +45 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.js +314 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.js +122 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/embedded-terminal.js +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/embedded-terminal.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +19 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +11 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +49 -6
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/thinking-selector.js +1 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/thinking-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +6 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +18 -4
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.d.ts +13 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.js +49 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.js +197 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +106 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +7 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +3 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +4 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +88 -2
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +41 -0
- package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts +2 -2
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js +10 -6
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/print-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/print-mode.js +6 -0
- package/packages/pi-coding-agent/dist/modes/print-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js +20 -0
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/tests/path-display.test.js +15 -0
- package/packages/pi-coding-agent/dist/tests/path-display.test.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/cli/args.ts +2 -2
- package/packages/pi-coding-agent/src/core/agent-session.ts +58 -21
- package/packages/pi-coding-agent/src/core/keybindings.ts +4 -1
- package/packages/pi-coding-agent/src/core/lsp/lsp.md +3 -1
- package/packages/pi-coding-agent/src/core/sdk.test.ts +45 -0
- package/packages/pi-coding-agent/src/core/sdk.ts +39 -8
- package/packages/pi-coding-agent/src/core/session-manager.ts +12 -0
- package/packages/pi-coding-agent/src/core/settings-manager.fast-mode.test.ts +46 -0
- package/packages/pi-coding-agent/src/core/settings-manager.ts +50 -9
- package/packages/pi-coding-agent/src/core/skills.ts +4 -1
- package/packages/pi-coding-agent/src/core/slash-commands.ts +2 -1
- package/packages/pi-coding-agent/src/core/system-prompt.ts +14 -3
- package/packages/pi-coding-agent/src/core/tool-priority.test.ts +30 -0
- package/packages/pi-coding-agent/src/core/tool-priority.ts +17 -0
- package/packages/pi-coding-agent/src/core/tools/grep.ts +1 -1
- package/packages/pi-coding-agent/src/core/tools/index.ts +3 -0
- package/packages/pi-coding-agent/src/core/tools/pty.ts +45 -6
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-summary-line.test.ts +31 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/btw-overlay.test.ts +172 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/btw-overlay.ts +402 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/embedded-terminal.ts +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +18 -2
- package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +63 -9
- package/packages/pi-coding-agent/src/modes/interactive/components/thinking-selector.ts +1 -2
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +1154 -1136
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-summary-line.ts +64 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.ts +228 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +494 -389
- package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +7 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +3 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +103 -3
- package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +60 -1
- package/packages/pi-coding-agent/src/modes/interactive/theme/theme.ts +11 -7
- package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +1 -1
- package/packages/pi-coding-agent/src/modes/print-mode.ts +6 -0
- package/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts +29 -0
- package/packages/pi-coding-agent/src/tests/path-display.test.ts +17 -0
- package/packages/pi-tui/dist/components/loader.d.ts +5 -2
- package/packages/pi-tui/dist/components/loader.d.ts.map +1 -1
- package/packages/pi-tui/dist/components/loader.js +33 -3
- package/packages/pi-tui/dist/components/loader.js.map +1 -1
- package/packages/pi-tui/src/components/loader.ts +31 -3
- package/packages/rpc-client/src/index.ts +1 -1
- package/packages/rpc-client/src/rpc-client.ts +29 -0
- package/packages/rpc-client/src/rpc-types.ts +1 -1
- package/pkg/dist/modes/interactive/theme/theme.d.ts +2 -2
- package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/pkg/dist/modes/interactive/theme/theme.js +10 -6
- package/pkg/dist/modes/interactive/theme/theme.js.map +1 -1
- package/pkg/dist/modes/interactive/theme/themes.js +1 -1
- package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/agents/generic.md +1 -0
- package/src/resources/agents/scout.md +8 -1
- package/src/resources/agents/worker.md +1 -0
- package/src/resources/extensions/ask-user-questions.ts +88 -0
- package/src/resources/extensions/bg-shell/bg-shell-tool.ts +6 -16
- package/src/resources/extensions/browser-tools/tools/codegen.ts +5 -5
- package/src/resources/extensions/browser-tools/tools/navigation.ts +118 -196
- package/src/resources/extensions/browser-tools/tools/network-mock.ts +114 -205
- package/src/resources/extensions/browser-tools/tools/pages.ts +183 -237
- package/src/resources/extensions/browser-tools/tools/refs.ts +193 -507
- package/src/resources/extensions/browser-tools/tools/session.ts +182 -321
- package/src/resources/extensions/browser-tools/tools/state-persistence.ts +94 -172
- package/src/resources/extensions/browser-tools/utils.ts +1 -1
- package/src/resources/extensions/mac-tools/index.ts +19 -34
- package/src/resources/extensions/memory/index.ts +22 -2
- package/src/resources/extensions/shared/interview-ui.ts +108 -15
- package/src/resources/extensions/shared/tests/ask-user-freetext.test.ts +61 -0
- package/src/resources/extensions/shared/tests/custom-ui-fallbacks.test.ts +46 -0
- package/src/resources/extensions/slash-commands/extension-manifest.json +2 -2
- package/src/resources/extensions/slash-commands/fast.ts +89 -0
- package/src/resources/extensions/slash-commands/index.ts +2 -0
- package/src/resources/extensions/slash-commands/plan.ts +59 -30
- package/src/resources/extensions/slash-commands/tools.ts +43 -4
- package/src/resources/extensions/subagent/agent-switcher-component.ts +228 -0
- package/src/resources/extensions/subagent/agent-switcher-model.ts +160 -0
- package/src/resources/extensions/subagent/background-job-manager.ts +57 -6
- package/src/resources/extensions/subagent/background-runner.ts +8 -0
- package/src/resources/extensions/subagent/background-types.ts +4 -0
- package/src/resources/extensions/subagent/in-process-runner.ts +534 -0
- package/src/resources/extensions/subagent/index.ts +998 -493
- package/src/resources/extensions/subagent/launch-helpers.ts +15 -4
- package/src/resources/extensions/subagent/legacy-runner.ts +607 -0
- package/src/resources/extensions/voice/index.ts +308 -238
- package/src/resources/extensions/voice/push-to-talk.ts +42 -0
- package/src/resources/extensions/voice/tests/push-to-talk.test.ts +109 -0
|
@@ -12,49 +12,240 @@
|
|
|
12
12
|
* Uses JSON mode to capture structured output from subagents.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import { spawn, execFileSync, type ChildProcess } from "node:child_process";
|
|
16
15
|
import * as crypto from "node:crypto";
|
|
17
16
|
import * as fs from "node:fs";
|
|
18
17
|
import * as os from "node:os";
|
|
19
18
|
import * as path from "node:path";
|
|
20
19
|
import type { AgentToolResult } from "@gsd/pi-agent-core";
|
|
21
|
-
import type { Message } from "@gsd/pi-ai";
|
|
20
|
+
import type { ImageContent, Message } from "@gsd/pi-ai";
|
|
22
21
|
import { StringEnum } from "@gsd/pi-ai";
|
|
23
22
|
import {
|
|
24
23
|
type ExtensionAPI,
|
|
25
|
-
getAgentDir,
|
|
26
24
|
getMarkdownTheme,
|
|
27
25
|
} from "@gsd/pi-coding-agent";
|
|
28
26
|
import { Container, Key, Markdown, Spacer, Text } from "@gsd/pi-tui";
|
|
29
27
|
import { Type } from "@sinclair/typebox";
|
|
30
28
|
import { formatTokenCount, shortcutDesc } from "../shared/mod.js";
|
|
31
29
|
import { type AgentConfig, type AgentScope, discoverAgents } from "./agents.js";
|
|
32
|
-
import { buildSubagentProcessArgs, getBundledExtensionPathsFromEnv } from "./launch-helpers.js";
|
|
33
30
|
import {
|
|
34
31
|
type IsolationEnvironment,
|
|
35
|
-
type IsolationMode,
|
|
36
32
|
type MergeResult,
|
|
37
33
|
createIsolation,
|
|
38
34
|
mergeDeltaPatches,
|
|
39
35
|
readIsolationMode,
|
|
40
36
|
} from "./isolation.js";
|
|
41
37
|
import { registerWorker, updateWorker } from "./worker-registry.js";
|
|
42
|
-
import { handleSubagentPermissionRequest, isSubagentPermissionRequest } from "./approval-proxy.js";
|
|
43
38
|
import { resolveConfiguredSubagentModel } from "./configured-model.js";
|
|
44
|
-
import {
|
|
45
|
-
normalizeSubagentModel,
|
|
46
|
-
resolveSubagentModel,
|
|
47
|
-
} from "./model-resolution.js";
|
|
39
|
+
import { resolveSubagentModel } from "./model-resolution.js";
|
|
48
40
|
import { loadEffectivePreferences } from "../shared/preferences.js";
|
|
41
|
+
import {
|
|
42
|
+
readBudgetSubagentModelFromSettings,
|
|
43
|
+
runLegacySingleAgent,
|
|
44
|
+
stopLegacySubagents,
|
|
45
|
+
} from "./legacy-runner.js";
|
|
46
|
+
import {
|
|
47
|
+
MAX_IN_PROCESS_SUBAGENT_DEPTH,
|
|
48
|
+
startInProcessSingleAgent,
|
|
49
|
+
type SubagentHandle,
|
|
50
|
+
} from "./in-process-runner.js";
|
|
49
51
|
import { CmuxClient, shellEscape } from "../cmux/index.js";
|
|
50
52
|
import { BackgroundJobManager, type BackgroundSubagentJob } from "./background-job-manager.js";
|
|
51
53
|
import { runSubagentInBackground } from "./background-runner.js";
|
|
54
|
+
import { showAgentSwitcher } from "./agent-switcher-component.js";
|
|
55
|
+
import {
|
|
56
|
+
buildAgentSwitchTargets,
|
|
57
|
+
type AgentSwitchTarget,
|
|
58
|
+
} from "./agent-switcher-model.js";
|
|
52
59
|
|
|
53
60
|
const MAX_PARALLEL_TASKS = 8;
|
|
54
61
|
const MAX_CONCURRENCY = 4;
|
|
55
62
|
const COLLAPSED_ITEM_COUNT = 10;
|
|
56
63
|
const DEFAULT_AWAIT_SUBAGENT_TIMEOUT_SECONDS = 120;
|
|
57
|
-
const
|
|
64
|
+
const USE_IN_PROCESS_SUBAGENT = process.env.LSD_SUBAGENT_IN_PROCESS !== "0";
|
|
65
|
+
|
|
66
|
+
type AgentSessionState = "running" | "completed" | "failed";
|
|
67
|
+
|
|
68
|
+
interface AgentSessionLink {
|
|
69
|
+
id: string;
|
|
70
|
+
agentName: string;
|
|
71
|
+
task: string;
|
|
72
|
+
parentSessionFile: string;
|
|
73
|
+
subagentSessionFile: string;
|
|
74
|
+
createdAt: number;
|
|
75
|
+
updatedAt: number;
|
|
76
|
+
state: AgentSessionState;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const agentSessionLinksById = new Map<string, AgentSessionLink>();
|
|
80
|
+
const agentSessionIdsByParent = new Map<string, string[]>();
|
|
81
|
+
const parentSessionByChild = new Map<string, string>();
|
|
82
|
+
|
|
83
|
+
interface LiveSubagentRuntime {
|
|
84
|
+
sessionFile?: string;
|
|
85
|
+
parentSessionFile?: string;
|
|
86
|
+
agentName: string;
|
|
87
|
+
isBusy: () => boolean;
|
|
88
|
+
sendPrompt: (text: string, images?: ImageContent[]) => Promise<void>;
|
|
89
|
+
sendSteer: (text: string, images?: ImageContent[]) => Promise<void>;
|
|
90
|
+
sendFollowUp: (text: string, images?: ImageContent[]) => Promise<void>;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const liveRuntimeBySessionFile = new Map<string, LiveSubagentRuntime>();
|
|
94
|
+
const inProcessSubagentDepthBySessionId = new Map<string, number>();
|
|
95
|
+
const inProcessSubagentAncestryBySessionId = new Map<string, string[]>();
|
|
96
|
+
let agentSessionLinkCounter = 0;
|
|
97
|
+
|
|
98
|
+
function listSessionFiles(sessionDir: string): string[] {
|
|
99
|
+
if (!fs.existsSync(sessionDir)) return [];
|
|
100
|
+
try {
|
|
101
|
+
return fs
|
|
102
|
+
.readdirSync(sessionDir)
|
|
103
|
+
.filter((name) => name.endsWith(".jsonl"))
|
|
104
|
+
.map((name) => path.join(sessionDir, name));
|
|
105
|
+
} catch {
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function registerAgentSessionLink(link: Omit<AgentSessionLink, "id" | "createdAt" | "updatedAt">): AgentSessionLink {
|
|
111
|
+
const now = Date.now();
|
|
112
|
+
const id = `agent-${++agentSessionLinkCounter}`;
|
|
113
|
+
const full: AgentSessionLink = { ...link, id, createdAt: now, updatedAt: now };
|
|
114
|
+
agentSessionLinksById.set(id, full);
|
|
115
|
+
const list = agentSessionIdsByParent.get(link.parentSessionFile) ?? [];
|
|
116
|
+
list.push(id);
|
|
117
|
+
agentSessionIdsByParent.set(link.parentSessionFile, list);
|
|
118
|
+
parentSessionByChild.set(link.subagentSessionFile, link.parentSessionFile);
|
|
119
|
+
return full;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function updateAgentSessionLinkState(subagentSessionFile: string, state: AgentSessionState): void {
|
|
123
|
+
for (const link of agentSessionLinksById.values()) {
|
|
124
|
+
if (link.subagentSessionFile === subagentSessionFile) {
|
|
125
|
+
link.state = state;
|
|
126
|
+
link.updatedAt = Date.now();
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function upsertAgentSessionLink(
|
|
133
|
+
agentName: string,
|
|
134
|
+
task: string,
|
|
135
|
+
parentSessionFile: string,
|
|
136
|
+
subagentSessionFile: string,
|
|
137
|
+
state: AgentSessionState,
|
|
138
|
+
): void {
|
|
139
|
+
const existingParent = parentSessionByChild.get(subagentSessionFile);
|
|
140
|
+
if (!existingParent) {
|
|
141
|
+
registerAgentSessionLink({
|
|
142
|
+
agentName,
|
|
143
|
+
task,
|
|
144
|
+
parentSessionFile,
|
|
145
|
+
subagentSessionFile,
|
|
146
|
+
state,
|
|
147
|
+
});
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
updateAgentSessionLinkState(subagentSessionFile, state);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function getAgentSessionLinksForParent(parentSessionFile: string): AgentSessionLink[] {
|
|
155
|
+
const ids = agentSessionIdsByParent.get(parentSessionFile) ?? [];
|
|
156
|
+
return ids
|
|
157
|
+
.map((id) => agentSessionLinksById.get(id))
|
|
158
|
+
.filter((entry): entry is AgentSessionLink => Boolean(entry))
|
|
159
|
+
.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function readSessionHeader(sessionFile: string): {
|
|
163
|
+
parentSession?: string;
|
|
164
|
+
subagentName?: string;
|
|
165
|
+
subagentTask?: string;
|
|
166
|
+
subagentSystemPrompt?: string;
|
|
167
|
+
subagentTools?: string[];
|
|
168
|
+
} | null {
|
|
169
|
+
try {
|
|
170
|
+
const content = fs.readFileSync(sessionFile, "utf-8");
|
|
171
|
+
const firstLine = content.split("\n").find((line) => line.trim().length > 0);
|
|
172
|
+
if (!firstLine) return null;
|
|
173
|
+
const parsed = JSON.parse(firstLine);
|
|
174
|
+
if (!parsed || parsed.type !== "session") return null;
|
|
175
|
+
return {
|
|
176
|
+
parentSession: typeof parsed.parentSession === "string" ? parsed.parentSession : undefined,
|
|
177
|
+
subagentName: typeof parsed.subagentName === "string" ? parsed.subagentName : undefined,
|
|
178
|
+
subagentTask: typeof parsed.subagentTask === "string" ? parsed.subagentTask : undefined,
|
|
179
|
+
subagentSystemPrompt: typeof parsed.subagentSystemPrompt === "string" ? parsed.subagentSystemPrompt : undefined,
|
|
180
|
+
subagentTools: Array.isArray(parsed.subagentTools)
|
|
181
|
+
? parsed.subagentTools.filter((tool: unknown): tool is string => typeof tool === "string")
|
|
182
|
+
: undefined,
|
|
183
|
+
};
|
|
184
|
+
} catch {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function backfillAgentSessionLinksForParent(parentSessionFile: string, sessionDir: string): AgentSessionLink[] {
|
|
190
|
+
for (const sessionFile of listSessionFiles(sessionDir)) {
|
|
191
|
+
if (sessionFile === parentSessionFile) continue;
|
|
192
|
+
const header = readSessionHeader(sessionFile);
|
|
193
|
+
if (header?.parentSession !== parentSessionFile) continue;
|
|
194
|
+
const existingParent = parentSessionByChild.get(sessionFile);
|
|
195
|
+
if (!existingParent) {
|
|
196
|
+
registerAgentSessionLink({
|
|
197
|
+
agentName: header.subagentName ?? "subagent",
|
|
198
|
+
task: header.subagentTask ?? "Recovered from persisted session lineage",
|
|
199
|
+
parentSessionFile,
|
|
200
|
+
subagentSessionFile: sessionFile,
|
|
201
|
+
state: "completed",
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return getAgentSessionLinksForParent(parentSessionFile);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function formatSwitchTargetSummary(target: AgentSwitchTarget): string {
|
|
209
|
+
const current = target.isCurrent ? " (current)" : "";
|
|
210
|
+
if (target.kind === "parent") {
|
|
211
|
+
return `● parent — main session${current}`;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const icon = target.state === "running" ? "▶" : target.state === "failed" ? "✗" : "✓";
|
|
215
|
+
return `${icon} ${target.agentName} — ${target.taskPreview}${current}`;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function buildSwitchTargetsForParent(
|
|
219
|
+
parentSessionFile: string,
|
|
220
|
+
currentSessionFile: string,
|
|
221
|
+
currentCwd: string,
|
|
222
|
+
trackedLinks: AgentSessionLink[],
|
|
223
|
+
runningJobs: BackgroundSubagentJob[],
|
|
224
|
+
): AgentSwitchTarget[] {
|
|
225
|
+
return buildAgentSwitchTargets({
|
|
226
|
+
currentSessionFile,
|
|
227
|
+
rootParentSessionFile: parentSessionFile,
|
|
228
|
+
currentCwd,
|
|
229
|
+
trackedLinks: trackedLinks.map((link) => ({
|
|
230
|
+
id: link.id,
|
|
231
|
+
agentName: link.agentName,
|
|
232
|
+
task: link.task,
|
|
233
|
+
parentSessionFile: link.parentSessionFile,
|
|
234
|
+
subagentSessionFile: link.subagentSessionFile,
|
|
235
|
+
updatedAt: link.updatedAt,
|
|
236
|
+
state: link.state,
|
|
237
|
+
})),
|
|
238
|
+
runningJobs: runningJobs.map((job) => ({
|
|
239
|
+
id: job.id,
|
|
240
|
+
agentName: job.agentName,
|
|
241
|
+
task: job.task,
|
|
242
|
+
startedAt: job.startedAt,
|
|
243
|
+
parentSessionFile: job.parentSessionFile,
|
|
244
|
+
sessionFile: job.sessionFile,
|
|
245
|
+
cwd: job.cwd,
|
|
246
|
+
})),
|
|
247
|
+
});
|
|
248
|
+
}
|
|
58
249
|
|
|
59
250
|
const AwaitSubagentParams = Type.Object({
|
|
60
251
|
jobs: Type.Optional(
|
|
@@ -72,40 +263,7 @@ const AwaitSubagentParams = Type.Object({
|
|
|
72
263
|
});
|
|
73
264
|
|
|
74
265
|
export async function stopLiveSubagents(): Promise<void> {
|
|
75
|
-
|
|
76
|
-
if (active.length === 0) return;
|
|
77
|
-
|
|
78
|
-
for (const proc of active) {
|
|
79
|
-
try {
|
|
80
|
-
proc.kill("SIGTERM");
|
|
81
|
-
} catch {
|
|
82
|
-
/* ignore */
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
await Promise.all(
|
|
87
|
-
active.map(
|
|
88
|
-
(proc) =>
|
|
89
|
-
new Promise<void>((resolve) => {
|
|
90
|
-
const done = () => resolve();
|
|
91
|
-
const timer = setTimeout(done, 500);
|
|
92
|
-
proc.once("exit", () => {
|
|
93
|
-
clearTimeout(timer);
|
|
94
|
-
resolve();
|
|
95
|
-
});
|
|
96
|
-
}),
|
|
97
|
-
),
|
|
98
|
-
);
|
|
99
|
-
|
|
100
|
-
for (const proc of active) {
|
|
101
|
-
if (proc.exitCode === null) {
|
|
102
|
-
try {
|
|
103
|
-
proc.kill("SIGKILL");
|
|
104
|
-
} catch {
|
|
105
|
-
/* ignore */
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
266
|
+
await stopLegacySubagents();
|
|
109
267
|
}
|
|
110
268
|
|
|
111
269
|
function formatBackgroundSubagentResults(jobs: BackgroundSubagentJob[]): string {
|
|
@@ -325,20 +483,8 @@ interface SingleResult {
|
|
|
325
483
|
errorMessage?: string;
|
|
326
484
|
step?: number;
|
|
327
485
|
backgroundJobId?: string;
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
interface ForegroundSingleRunControl {
|
|
331
|
-
agentName: string;
|
|
332
|
-
task: string;
|
|
333
|
-
cwd: string;
|
|
334
|
-
abortController: AbortController;
|
|
335
|
-
resultPromise: Promise<{ summary: string; stderr: string; exitCode: number; model?: string }>;
|
|
336
|
-
adoptToBackground: (jobId: string) => boolean;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
interface ForegroundSingleRunHooks {
|
|
340
|
-
onStart?: (control: ForegroundSingleRunControl) => void;
|
|
341
|
-
onFinish?: () => void;
|
|
486
|
+
sessionFile?: string;
|
|
487
|
+
parentSessionFile?: string;
|
|
342
488
|
}
|
|
343
489
|
|
|
344
490
|
interface SubagentDetails {
|
|
@@ -395,343 +541,8 @@ async function mapWithConcurrencyLimit<TIn, TOut>(
|
|
|
395
541
|
return results;
|
|
396
542
|
}
|
|
397
543
|
|
|
398
|
-
function writePromptToTempFile(agentName: string, prompt: string): { dir: string; filePath: string } {
|
|
399
|
-
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagent-"));
|
|
400
|
-
const safeName = agentName.replace(/[^\w.-]+/g, "_");
|
|
401
|
-
const filePath = path.join(tmpDir, `prompt-${safeName}.md`);
|
|
402
|
-
fs.writeFileSync(filePath, prompt, { encoding: "utf-8", mode: 0o600 });
|
|
403
|
-
return { dir: tmpDir, filePath };
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
function readBudgetSubagentModelFromSettings(): string | undefined {
|
|
407
|
-
try {
|
|
408
|
-
const settingsPath = path.join(getAgentDir(), "settings.json");
|
|
409
|
-
if (!fs.existsSync(settingsPath)) return undefined;
|
|
410
|
-
const raw = fs.readFileSync(settingsPath, "utf-8");
|
|
411
|
-
const parsed = JSON.parse(raw) as { budgetSubagentModel?: unknown };
|
|
412
|
-
return typeof parsed.budgetSubagentModel === "string"
|
|
413
|
-
? normalizeSubagentModel(parsed.budgetSubagentModel)
|
|
414
|
-
: undefined;
|
|
415
|
-
} catch {
|
|
416
|
-
return undefined;
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
function resolveSubagentCliPath(defaultCwd: string): string | null {
|
|
421
|
-
const candidates = [process.env.GSD_BIN_PATH, process.env.LSD_BIN_PATH, process.argv[1]]
|
|
422
|
-
.map((value) => value?.trim())
|
|
423
|
-
.filter((value): value is string => Boolean(value && value !== "undefined"));
|
|
424
|
-
|
|
425
|
-
for (const candidate of candidates) {
|
|
426
|
-
if (path.isAbsolute(candidate) && fs.existsSync(candidate)) return candidate;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
const cwdCandidates = [path.join(defaultCwd, "dist", "loader.js"), path.join(defaultCwd, "scripts", "dev-cli.js")];
|
|
430
|
-
for (const candidate of cwdCandidates) {
|
|
431
|
-
if (fs.existsSync(candidate)) return candidate;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
for (const binName of ["lsd", "gsd"]) {
|
|
435
|
-
try {
|
|
436
|
-
const resolved = execFileSync("which", [binName], { encoding: "utf-8" }).trim();
|
|
437
|
-
if (resolved) return resolved;
|
|
438
|
-
} catch {
|
|
439
|
-
/* ignore */
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
return null;
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
function processSubagentEventLine(
|
|
447
|
-
line: string,
|
|
448
|
-
currentResult: SingleResult,
|
|
449
|
-
emitUpdate: () => void,
|
|
450
|
-
proc?: ChildProcess,
|
|
451
|
-
): boolean {
|
|
452
|
-
if (!line.trim()) return false;
|
|
453
|
-
let event: any;
|
|
454
|
-
try {
|
|
455
|
-
event = JSON.parse(line);
|
|
456
|
-
} catch {
|
|
457
|
-
return false;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
if (proc && isSubagentPermissionRequest(event)) {
|
|
461
|
-
void handleSubagentPermissionRequest(event, proc);
|
|
462
|
-
return false;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
if ((event.type === "message_end" || event.type === "turn_end") && event.message) {
|
|
466
|
-
const msg = event.message as Message;
|
|
467
|
-
currentResult.messages.push(msg);
|
|
468
|
-
|
|
469
|
-
if (msg.role === "assistant") {
|
|
470
|
-
currentResult.usage.turns++;
|
|
471
|
-
const usage = msg.usage;
|
|
472
|
-
if (usage) {
|
|
473
|
-
currentResult.usage.input += usage.input || 0;
|
|
474
|
-
currentResult.usage.output += usage.output || 0;
|
|
475
|
-
currentResult.usage.cacheRead += usage.cacheRead || 0;
|
|
476
|
-
currentResult.usage.cacheWrite += usage.cacheWrite || 0;
|
|
477
|
-
currentResult.usage.cost += usage.cost?.total || 0;
|
|
478
|
-
currentResult.usage.contextTokens = usage.totalTokens || 0;
|
|
479
|
-
}
|
|
480
|
-
if (msg.model && (!currentResult.model || msg.model.includes("/"))) currentResult.model = msg.model;
|
|
481
|
-
if (msg.stopReason) currentResult.stopReason = msg.stopReason;
|
|
482
|
-
if (msg.errorMessage) currentResult.errorMessage = msg.errorMessage;
|
|
483
|
-
}
|
|
484
|
-
emitUpdate();
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
if (event.type === "tool_result_end" && event.message) {
|
|
488
|
-
currentResult.messages.push(event.message as Message);
|
|
489
|
-
emitUpdate();
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
return event.type === "agent_end";
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
async function waitForFile(filePath: string, signal: AbortSignal | undefined, timeoutMs = 30 * 60 * 1000): Promise<boolean> {
|
|
496
|
-
const started = Date.now();
|
|
497
|
-
while (Date.now() - started < timeoutMs) {
|
|
498
|
-
if (signal?.aborted) return false;
|
|
499
|
-
if (fs.existsSync(filePath)) return true;
|
|
500
|
-
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
501
|
-
}
|
|
502
|
-
return false;
|
|
503
|
-
}
|
|
504
|
-
|
|
505
544
|
type OnUpdateCallback = (partial: AgentToolResult<SubagentDetails>) => void;
|
|
506
545
|
|
|
507
|
-
async function runSingleAgent(
|
|
508
|
-
defaultCwd: string,
|
|
509
|
-
agents: AgentConfig[],
|
|
510
|
-
agentName: string,
|
|
511
|
-
task: string,
|
|
512
|
-
cwd: string | undefined,
|
|
513
|
-
step: number | undefined,
|
|
514
|
-
modelOverride: string | undefined,
|
|
515
|
-
parentModel: { provider: string; id: string } | undefined,
|
|
516
|
-
signal: AbortSignal | undefined,
|
|
517
|
-
onUpdate: OnUpdateCallback | undefined,
|
|
518
|
-
makeDetails: (results: SingleResult[]) => SubagentDetails,
|
|
519
|
-
foregroundHooks?: ForegroundSingleRunHooks,
|
|
520
|
-
): Promise<SingleResult> {
|
|
521
|
-
const agent = agents.find((a) => a.name === agentName);
|
|
522
|
-
|
|
523
|
-
if (!agent) {
|
|
524
|
-
const available = agents.map((a) => `"${a.name}"`).join(", ") || "none";
|
|
525
|
-
return {
|
|
526
|
-
agent: agentName,
|
|
527
|
-
agentSource: "unknown",
|
|
528
|
-
task,
|
|
529
|
-
exitCode: 1,
|
|
530
|
-
messages: [],
|
|
531
|
-
stderr: `Unknown agent: "${agentName}". Available agents: ${available}.`,
|
|
532
|
-
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 },
|
|
533
|
-
step,
|
|
534
|
-
};
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
let tmpPromptDir: string | null = null;
|
|
538
|
-
let tmpPromptPath: string | null = null;
|
|
539
|
-
|
|
540
|
-
const preferences = loadEffectivePreferences()?.preferences;
|
|
541
|
-
const settingsBudgetModel = readBudgetSubagentModelFromSettings();
|
|
542
|
-
const resolvedModel = resolveConfiguredSubagentModel(agent, preferences, settingsBudgetModel);
|
|
543
|
-
const inferredModel = resolveSubagentModel(
|
|
544
|
-
{ name: agent.name, model: resolvedModel },
|
|
545
|
-
{ overrideModel: modelOverride, parentModel },
|
|
546
|
-
);
|
|
547
|
-
|
|
548
|
-
const currentResult: SingleResult = {
|
|
549
|
-
agent: agentName,
|
|
550
|
-
agentSource: agent.source,
|
|
551
|
-
task,
|
|
552
|
-
exitCode: 0,
|
|
553
|
-
messages: [],
|
|
554
|
-
stderr: "",
|
|
555
|
-
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 },
|
|
556
|
-
model: inferredModel,
|
|
557
|
-
step,
|
|
558
|
-
};
|
|
559
|
-
|
|
560
|
-
const emitUpdate = () => {
|
|
561
|
-
if (onUpdate) {
|
|
562
|
-
onUpdate({
|
|
563
|
-
content: [{ type: "text", text: getFinalOutput(currentResult.messages) || "(running...)" }],
|
|
564
|
-
details: makeDetails([currentResult]),
|
|
565
|
-
});
|
|
566
|
-
}
|
|
567
|
-
};
|
|
568
|
-
|
|
569
|
-
let wasAborted = false;
|
|
570
|
-
let deferTempPromptCleanup = false;
|
|
571
|
-
let tempPromptCleanupDone = false;
|
|
572
|
-
|
|
573
|
-
const cleanupTempPromptFiles = () => {
|
|
574
|
-
if (tempPromptCleanupDone) return;
|
|
575
|
-
tempPromptCleanupDone = true;
|
|
576
|
-
if (tmpPromptPath)
|
|
577
|
-
try {
|
|
578
|
-
fs.unlinkSync(tmpPromptPath);
|
|
579
|
-
} catch {
|
|
580
|
-
/* ignore */
|
|
581
|
-
}
|
|
582
|
-
if (tmpPromptDir)
|
|
583
|
-
try {
|
|
584
|
-
fs.rmdirSync(tmpPromptDir);
|
|
585
|
-
} catch {
|
|
586
|
-
/* ignore */
|
|
587
|
-
}
|
|
588
|
-
};
|
|
589
|
-
|
|
590
|
-
try {
|
|
591
|
-
if (agent.systemPrompt.trim()) {
|
|
592
|
-
const tmp = writePromptToTempFile(agent.name, agent.systemPrompt);
|
|
593
|
-
tmpPromptDir = tmp.dir;
|
|
594
|
-
tmpPromptPath = tmp.filePath;
|
|
595
|
-
}
|
|
596
|
-
const args = buildSubagentProcessArgs(agent, task, tmpPromptPath, inferredModel);
|
|
597
|
-
|
|
598
|
-
const exitCode = await new Promise<number>((resolve) => {
|
|
599
|
-
const bundledPaths = getBundledExtensionPathsFromEnv();
|
|
600
|
-
const extensionArgs = bundledPaths.flatMap((p) => ["--extension", p]);
|
|
601
|
-
const cliPath = resolveSubagentCliPath(cwd ?? defaultCwd);
|
|
602
|
-
if (!cliPath) {
|
|
603
|
-
currentResult.stderr += "Unable to resolve LSD/GSD CLI path for subagent launch.";
|
|
604
|
-
resolve(1);
|
|
605
|
-
return;
|
|
606
|
-
}
|
|
607
|
-
const proc = spawn(
|
|
608
|
-
process.execPath,
|
|
609
|
-
[cliPath, ...extensionArgs, ...args],
|
|
610
|
-
{ cwd: cwd ?? defaultCwd, shell: false, stdio: ["pipe", "pipe", "pipe"] },
|
|
611
|
-
);
|
|
612
|
-
// Keep stdin open so approval/classifier responses can be proxied back
|
|
613
|
-
// into the child process. Closing it here can leave the subagent stuck
|
|
614
|
-
// after its first turn when it requests permission for a tool call.
|
|
615
|
-
liveSubagentProcesses.add(proc);
|
|
616
|
-
let buffer = "";
|
|
617
|
-
let completionSeen = false;
|
|
618
|
-
let resolved = false;
|
|
619
|
-
let foregroundReleased = false;
|
|
620
|
-
const procAbortController = new AbortController();
|
|
621
|
-
let resolveBackgroundResult: ((value: { summary: string; stderr: string; exitCode: number; model?: string }) => void) | undefined;
|
|
622
|
-
let rejectBackgroundResult: ((reason?: unknown) => void) | undefined;
|
|
623
|
-
const backgroundResultPromise = new Promise<{ summary: string; stderr: string; exitCode: number; model?: string }>((resolveBg, rejectBg) => {
|
|
624
|
-
resolveBackgroundResult = resolveBg;
|
|
625
|
-
rejectBackgroundResult = rejectBg;
|
|
626
|
-
});
|
|
627
|
-
|
|
628
|
-
const finishForeground = (code: number) => {
|
|
629
|
-
if (resolved) return;
|
|
630
|
-
resolved = true;
|
|
631
|
-
resolve(code);
|
|
632
|
-
};
|
|
633
|
-
|
|
634
|
-
const adoptToBackground = (jobId: string): boolean => {
|
|
635
|
-
if (resolved || foregroundReleased) return false;
|
|
636
|
-
foregroundReleased = true;
|
|
637
|
-
deferTempPromptCleanup = true;
|
|
638
|
-
currentResult.backgroundJobId = jobId;
|
|
639
|
-
finishForeground(0);
|
|
640
|
-
return true;
|
|
641
|
-
};
|
|
642
|
-
|
|
643
|
-
backgroundResultPromise.finally(() => {
|
|
644
|
-
if (deferTempPromptCleanup) cleanupTempPromptFiles();
|
|
645
|
-
});
|
|
646
|
-
|
|
647
|
-
foregroundHooks?.onStart?.({
|
|
648
|
-
agentName,
|
|
649
|
-
task,
|
|
650
|
-
cwd: cwd ?? defaultCwd,
|
|
651
|
-
abortController: procAbortController,
|
|
652
|
-
resultPromise: backgroundResultPromise,
|
|
653
|
-
adoptToBackground,
|
|
654
|
-
});
|
|
655
|
-
|
|
656
|
-
proc.stdout.on("data", (data) => {
|
|
657
|
-
buffer += data.toString();
|
|
658
|
-
const lines = buffer.split("\n");
|
|
659
|
-
buffer = lines.pop() || "";
|
|
660
|
-
for (const line of lines) {
|
|
661
|
-
if (processSubagentEventLine(line, currentResult, emitUpdate, proc)) {
|
|
662
|
-
completionSeen = true;
|
|
663
|
-
try {
|
|
664
|
-
proc.kill("SIGTERM");
|
|
665
|
-
} catch {
|
|
666
|
-
/* ignore */
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
});
|
|
671
|
-
|
|
672
|
-
proc.stderr.on("data", (data) => {
|
|
673
|
-
currentResult.stderr += data.toString();
|
|
674
|
-
});
|
|
675
|
-
|
|
676
|
-
proc.on("close", (code) => {
|
|
677
|
-
liveSubagentProcesses.delete(proc);
|
|
678
|
-
if (buffer.trim()) {
|
|
679
|
-
const completedOnFlush = processSubagentEventLine(buffer, currentResult, emitUpdate, proc);
|
|
680
|
-
completionSeen = completionSeen || completedOnFlush;
|
|
681
|
-
}
|
|
682
|
-
const finalExitCode = completionSeen && (code === null || code === 143 || code === 15) ? 0 : (code ?? 0);
|
|
683
|
-
currentResult.exitCode = finalExitCode;
|
|
684
|
-
resolveBackgroundResult?.({
|
|
685
|
-
summary: getFinalOutput(currentResult.messages),
|
|
686
|
-
stderr: currentResult.stderr,
|
|
687
|
-
exitCode: finalExitCode,
|
|
688
|
-
model: currentResult.model,
|
|
689
|
-
});
|
|
690
|
-
foregroundHooks?.onFinish?.();
|
|
691
|
-
finishForeground(finalExitCode);
|
|
692
|
-
});
|
|
693
|
-
|
|
694
|
-
proc.on("error", (error) => {
|
|
695
|
-
liveSubagentProcesses.delete(proc);
|
|
696
|
-
rejectBackgroundResult?.(error);
|
|
697
|
-
foregroundHooks?.onFinish?.();
|
|
698
|
-
finishForeground(1);
|
|
699
|
-
});
|
|
700
|
-
|
|
701
|
-
const killProc = () => {
|
|
702
|
-
wasAborted = true;
|
|
703
|
-
procAbortController.abort();
|
|
704
|
-
proc.kill("SIGTERM");
|
|
705
|
-
setTimeout(() => {
|
|
706
|
-
if (!proc.killed) proc.kill("SIGKILL");
|
|
707
|
-
}, 5000);
|
|
708
|
-
};
|
|
709
|
-
|
|
710
|
-
if (signal) {
|
|
711
|
-
if (signal.aborted) killProc();
|
|
712
|
-
else signal.addEventListener("abort", killProc, { once: true });
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
if (procAbortController.signal.aborted) {
|
|
716
|
-
killProc();
|
|
717
|
-
} else {
|
|
718
|
-
procAbortController.signal.addEventListener("abort", () => {
|
|
719
|
-
proc.kill("SIGTERM");
|
|
720
|
-
setTimeout(() => {
|
|
721
|
-
if (!proc.killed) proc.kill("SIGKILL");
|
|
722
|
-
}, 5000);
|
|
723
|
-
}, { once: true });
|
|
724
|
-
}
|
|
725
|
-
});
|
|
726
|
-
|
|
727
|
-
currentResult.exitCode = exitCode;
|
|
728
|
-
if (wasAborted) throw new Error("Subagent was aborted");
|
|
729
|
-
return currentResult;
|
|
730
|
-
} finally {
|
|
731
|
-
if (!deferTempPromptCleanup) cleanupTempPromptFiles();
|
|
732
|
-
}
|
|
733
|
-
}
|
|
734
|
-
|
|
735
546
|
const TaskItem = Type.Object({
|
|
736
547
|
agent: Type.String({ description: "Name of the agent to invoke" }),
|
|
737
548
|
task: Type.String({ description: "Task to delegate to the agent" }),
|
|
@@ -803,17 +614,91 @@ export default function(pi: ExtensionAPI) {
|
|
|
803
614
|
let bgManager: BackgroundJobManager | null = null;
|
|
804
615
|
const foregroundSubagentStatusKey = "foreground-subagent";
|
|
805
616
|
const foregroundSubagentHint = "Ctrl+B: move foreground subagent to background";
|
|
806
|
-
type ActiveForegroundSubagent =
|
|
617
|
+
type ActiveForegroundSubagent = {
|
|
618
|
+
claimed: boolean;
|
|
619
|
+
agentName: string;
|
|
620
|
+
task: string;
|
|
621
|
+
cwd: string;
|
|
622
|
+
parentSessionFile?: string;
|
|
623
|
+
resultPromise: Promise<{
|
|
624
|
+
summary: string;
|
|
625
|
+
stderr: string;
|
|
626
|
+
exitCode: number;
|
|
627
|
+
model?: string;
|
|
628
|
+
sessionFile?: string;
|
|
629
|
+
parentSessionFile?: string;
|
|
630
|
+
}>;
|
|
631
|
+
adoptToBackground: (jobId: string) => boolean;
|
|
632
|
+
abortController?: AbortController;
|
|
633
|
+
handle?: SubagentHandle;
|
|
634
|
+
sendPrompt?: (text: string, images?: ImageContent[]) => Promise<void>;
|
|
635
|
+
sendSteer?: (text: string, images?: ImageContent[]) => Promise<void>;
|
|
636
|
+
sendFollowUp?: (text: string, images?: ImageContent[]) => Promise<void>;
|
|
637
|
+
isBusy?: () => boolean;
|
|
638
|
+
};
|
|
807
639
|
let activeForegroundSubagent: ActiveForegroundSubagent | null = null;
|
|
640
|
+
let activeSessionFileForUi: string | undefined;
|
|
641
|
+
const liveStreamBufferBySession = new Map<string, string>();
|
|
642
|
+
|
|
643
|
+
function flushLiveStream(sessionFile: string): void {
|
|
644
|
+
const buffered = liveStreamBufferBySession.get(sessionFile);
|
|
645
|
+
if (!buffered || !buffered.trim()) return;
|
|
646
|
+
liveStreamBufferBySession.set(sessionFile, "");
|
|
647
|
+
pi.sendMessage(
|
|
648
|
+
{
|
|
649
|
+
customType: "live_subagent_stream",
|
|
650
|
+
content: buffered,
|
|
651
|
+
display: true,
|
|
652
|
+
},
|
|
653
|
+
{ deliverAs: "followUp" },
|
|
654
|
+
);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function pushLiveStreamDelta(sessionFile: string, delta: string): void {
|
|
658
|
+
const prev = liveStreamBufferBySession.get(sessionFile) ?? "";
|
|
659
|
+
const next = prev + delta;
|
|
660
|
+
liveStreamBufferBySession.set(sessionFile, next);
|
|
661
|
+
if (next.length >= 120 || next.includes("\n")) {
|
|
662
|
+
flushLiveStream(sessionFile);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
function getCurrentSessionSubagentMetadata(sessionFile: string | undefined) {
|
|
667
|
+
if (!sessionFile) return null;
|
|
668
|
+
return readSessionHeader(sessionFile);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
function applyCurrentSessionSubagentTools(ctx: any): void {
|
|
672
|
+
const metadata = getCurrentSessionSubagentMetadata(ctx.sessionManager.getSessionFile());
|
|
673
|
+
if (metadata?.subagentTools && metadata.subagentTools.length > 0) {
|
|
674
|
+
ctx.setActiveTools(metadata.subagentTools);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
808
677
|
|
|
809
678
|
function getBgManager(): BackgroundJobManager {
|
|
810
679
|
if (!bgManager) throw new Error("BackgroundJobManager not initialized.");
|
|
811
680
|
return bgManager;
|
|
812
681
|
}
|
|
813
682
|
|
|
814
|
-
pi.on("session_start", async () => {
|
|
683
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
684
|
+
activeSessionFileForUi = ctx.sessionManager.getSessionFile();
|
|
815
685
|
bgManager = new BackgroundJobManager({
|
|
816
686
|
onJobComplete: (job) => {
|
|
687
|
+
if (job.sessionFile && job.parentSessionFile) {
|
|
688
|
+
const existingParent = parentSessionByChild.get(job.sessionFile);
|
|
689
|
+
if (!existingParent) {
|
|
690
|
+
registerAgentSessionLink({
|
|
691
|
+
agentName: job.agentName,
|
|
692
|
+
task: job.task,
|
|
693
|
+
parentSessionFile: job.parentSessionFile,
|
|
694
|
+
subagentSessionFile: job.sessionFile,
|
|
695
|
+
state: job.status === "failed" ? "failed" : "completed",
|
|
696
|
+
});
|
|
697
|
+
} else {
|
|
698
|
+
updateAgentSessionLinkState(job.sessionFile, job.status === "failed" ? "failed" : "completed");
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
817
702
|
if (job.awaited) return;
|
|
818
703
|
const statusEmoji = job.status === "completed" ? "✓" : job.status === "cancelled" ? "✗ cancelled" : "✗ failed";
|
|
819
704
|
const elapsed = ((Date.now() - job.startedAt) / 1000).toFixed(1);
|
|
@@ -839,25 +724,94 @@ export default function(pi: ExtensionAPI) {
|
|
|
839
724
|
);
|
|
840
725
|
},
|
|
841
726
|
});
|
|
727
|
+
applyCurrentSessionSubagentTools(ctx);
|
|
842
728
|
});
|
|
843
729
|
|
|
730
|
+
pi.on("session_switch", async (_event, ctx) => {
|
|
731
|
+
activeSessionFileForUi = ctx.sessionManager.getSessionFile();
|
|
732
|
+
applyCurrentSessionSubagentTools(ctx);
|
|
733
|
+
});
|
|
844
734
|
|
|
845
|
-
pi.on("
|
|
846
|
-
|
|
847
|
-
if (
|
|
848
|
-
|
|
849
|
-
|
|
735
|
+
pi.on("before_agent_start", async (event, ctx) => {
|
|
736
|
+
const metadata = getCurrentSessionSubagentMetadata(ctx.sessionManager.getSessionFile());
|
|
737
|
+
if (!metadata?.subagentSystemPrompt) return;
|
|
738
|
+
const subagentName = metadata.subagentName ?? "subagent";
|
|
739
|
+
const taskNote = metadata.subagentTask
|
|
740
|
+
? `Original delegated task: ${metadata.subagentTask}`
|
|
741
|
+
: "Continue operating as the delegated subagent for this session.";
|
|
742
|
+
const antiRecursion = [
|
|
743
|
+
`You are already the ${subagentName} subagent for this session.`,
|
|
744
|
+
"Do not spawn or delegate to another subagent with the same name as yourself.",
|
|
745
|
+
`If the user asks you to continue ${subagentName} work, do that work directly in this session.`,
|
|
746
|
+
taskNote,
|
|
747
|
+
].join("\n");
|
|
748
|
+
return {
|
|
749
|
+
systemPrompt: `${event.systemPrompt}\n\n${antiRecursion}\n\n${metadata.subagentSystemPrompt}`,
|
|
750
|
+
};
|
|
751
|
+
});
|
|
752
|
+
pi.on("input", async (event, ctx) => {
|
|
753
|
+
const sessionFile = ctx.sessionManager.getSessionFile();
|
|
754
|
+
const runtimeFromSession = sessionFile ? liveRuntimeBySessionFile.get(sessionFile) : undefined;
|
|
755
|
+
const runtimeFromForeground = (!runtimeFromSession && activeForegroundSubagent && !activeForegroundSubagent.claimed &&
|
|
756
|
+
activeForegroundSubagent.sendPrompt && activeForegroundSubagent.sendSteer && activeForegroundSubagent.sendFollowUp && activeForegroundSubagent.isBusy &&
|
|
757
|
+
sessionFile && activeForegroundSubagent.parentSessionFile === sessionFile)
|
|
758
|
+
? {
|
|
759
|
+
agentName: activeForegroundSubagent.agentName,
|
|
760
|
+
isBusy: activeForegroundSubagent.isBusy,
|
|
761
|
+
sendPrompt: activeForegroundSubagent.sendPrompt,
|
|
762
|
+
sendSteer: activeForegroundSubagent.sendSteer,
|
|
763
|
+
sendFollowUp: activeForegroundSubagent.sendFollowUp,
|
|
764
|
+
}
|
|
765
|
+
: undefined;
|
|
766
|
+
const runtime = runtimeFromSession ?? runtimeFromForeground;
|
|
767
|
+
if (!runtime) return;
|
|
768
|
+
|
|
769
|
+
const text = event.text?.trim();
|
|
770
|
+
if (!text) return { action: "handled" as const };
|
|
771
|
+
|
|
772
|
+
const isSlashCommand = text.startsWith("/");
|
|
773
|
+
if (isSlashCommand) return;
|
|
774
|
+
|
|
775
|
+
try {
|
|
776
|
+
if (runtime.isBusy()) {
|
|
777
|
+
await runtime.sendSteer(text, event.images);
|
|
778
|
+
ctx.ui.notify(`Sent steer to running subagent ${runtime.agentName}.`, "info");
|
|
779
|
+
} else {
|
|
780
|
+
await runtime.sendPrompt(text, event.images);
|
|
781
|
+
ctx.ui.notify(`Sent prompt to live subagent ${runtime.agentName}.`, "info");
|
|
850
782
|
}
|
|
783
|
+
return { action: "handled" as const };
|
|
784
|
+
} catch (error) {
|
|
785
|
+
ctx.ui.notify(
|
|
786
|
+
`Failed to send input to live subagent ${runtime.agentName}: ${error instanceof Error ? error.message : String(error)}`,
|
|
787
|
+
"error",
|
|
788
|
+
);
|
|
789
|
+
return { action: "handled" as const };
|
|
851
790
|
}
|
|
852
791
|
});
|
|
853
792
|
|
|
793
|
+
|
|
794
|
+
pi.on("session_before_switch", async () => {
|
|
795
|
+
if (activeSessionFileForUi) flushLiveStream(activeSessionFileForUi);
|
|
796
|
+
activeForegroundSubagent = null;
|
|
797
|
+
});
|
|
798
|
+
|
|
854
799
|
pi.on("session_shutdown", async () => {
|
|
800
|
+
if (activeSessionFileForUi) flushLiveStream(activeSessionFileForUi);
|
|
801
|
+
activeSessionFileForUi = undefined;
|
|
855
802
|
activeForegroundSubagent = null;
|
|
856
803
|
await stopLiveSubagents();
|
|
857
804
|
if (bgManager) {
|
|
858
805
|
bgManager.shutdown();
|
|
859
806
|
bgManager = null;
|
|
860
807
|
}
|
|
808
|
+
agentSessionLinksById.clear();
|
|
809
|
+
agentSessionIdsByParent.clear();
|
|
810
|
+
parentSessionByChild.clear();
|
|
811
|
+
liveRuntimeBySessionFile.clear();
|
|
812
|
+
inProcessSubagentDepthBySessionId.clear();
|
|
813
|
+
inProcessSubagentAncestryBySessionId.clear();
|
|
814
|
+
liveStreamBufferBySession.clear();
|
|
861
815
|
});
|
|
862
816
|
|
|
863
817
|
// /subagents command
|
|
@@ -972,6 +926,189 @@ export default function(pi: ExtensionAPI) {
|
|
|
972
926
|
},
|
|
973
927
|
});
|
|
974
928
|
|
|
929
|
+
// /agent command - switch to the parent or a tracked subagent session
|
|
930
|
+
pi.registerCommand("agent", {
|
|
931
|
+
description: "Switch focus to parent/subagent sessions (/agent picker, /agent <id|index|name>, /agent parent)",
|
|
932
|
+
handler: async (args: string, ctx) => {
|
|
933
|
+
const currentSessionFile = ctx.sessionManager.getSessionFile();
|
|
934
|
+
if (!currentSessionFile) {
|
|
935
|
+
ctx.ui.notify("Current session is in-memory only; /agent requires a persisted session file.", "warning");
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
const arg = args.trim();
|
|
940
|
+
const parentSessionFile = parentSessionByChild.get(currentSessionFile);
|
|
941
|
+
const currentParent = parentSessionFile ?? currentSessionFile;
|
|
942
|
+
const currentSessionDir = path.dirname(currentParent);
|
|
943
|
+
|
|
944
|
+
let tracked = getAgentSessionLinksForParent(currentParent).filter((entry) => fs.existsSync(entry.subagentSessionFile));
|
|
945
|
+
if (tracked.length === 0) {
|
|
946
|
+
tracked = backfillAgentSessionLinksForParent(currentParent, currentSessionDir)
|
|
947
|
+
.filter((entry) => fs.existsSync(entry.subagentSessionFile));
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
const runningJobs = bgManager?.getRunningJobs() ?? [];
|
|
951
|
+
const switchTargets = buildSwitchTargetsForParent(
|
|
952
|
+
currentParent,
|
|
953
|
+
currentSessionFile,
|
|
954
|
+
ctx.cwd,
|
|
955
|
+
tracked,
|
|
956
|
+
runningJobs,
|
|
957
|
+
);
|
|
958
|
+
|
|
959
|
+
const applySwitchTarget = async (target: AgentSwitchTarget): Promise<void> => {
|
|
960
|
+
if (target.selectionAction === "blocked") {
|
|
961
|
+
ctx.ui.notify(target.blockedReason ?? "That target cannot be selected yet.", "warning");
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
if (target.selectionAction === "attach_live") {
|
|
966
|
+
if (!fs.existsSync(target.sessionFile)) {
|
|
967
|
+
ctx.ui.notify(`Live subagent session file is missing: ${target.sessionFile}`, "error");
|
|
968
|
+
return;
|
|
969
|
+
}
|
|
970
|
+
const liveRuntime = liveRuntimeBySessionFile.get(target.sessionFile);
|
|
971
|
+
if (!liveRuntime) {
|
|
972
|
+
ctx.ui.notify("Live runtime is no longer available for this subagent. It may have completed.", "warning");
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
const switched = await ctx.switchSession(target.sessionFile);
|
|
976
|
+
if (switched.cancelled) {
|
|
977
|
+
ctx.ui.notify("Session switch was cancelled.", "warning");
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
ctx.ui.notify(`Attached to running subagent ${target.agentName}. Prompts in this session are routed live (busy => steer, idle => prompt). Use /agent parent to return.`, "info");
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
if (target.kind === "parent") {
|
|
985
|
+
if (!fs.existsSync(target.sessionFile)) {
|
|
986
|
+
ctx.ui.notify(`Parent session file not found: ${target.sessionFile}`, "error");
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
const switched = await ctx.switchSession(target.sessionFile);
|
|
990
|
+
if (switched.cancelled) {
|
|
991
|
+
ctx.ui.notify("Session switch was cancelled.", "warning");
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
ctx.ui.notify("Switched to parent session.", "info");
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
if (!fs.existsSync(target.sessionFile)) {
|
|
999
|
+
ctx.ui.notify(`Subagent session file is missing: ${target.sessionFile}`, "error");
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
const switched = await ctx.switchSession(target.sessionFile);
|
|
1004
|
+
if (switched.cancelled) {
|
|
1005
|
+
ctx.ui.notify("Session switch was cancelled.", "warning");
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
updateAgentSessionLinkState(target.sessionFile, target.state === "failed" ? "failed" : "completed");
|
|
1009
|
+
ctx.ui.notify(`Switched to subagent ${target.agentName}. This resumes the saved subagent session; use /agent parent to return.`, "info");
|
|
1010
|
+
};
|
|
1011
|
+
|
|
1012
|
+
if (!arg) {
|
|
1013
|
+
const subagentTargets = switchTargets.filter((target) => target.kind === "subagent");
|
|
1014
|
+
if (ctx.hasUI) {
|
|
1015
|
+
if (subagentTargets.length === 0 && !parentSessionFile) {
|
|
1016
|
+
ctx.ui.notify("No tracked subagent sessions for this parent session yet. Run a single-mode subagent first (foreground or background).", "info");
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
const selected = await showAgentSwitcher(ctx, switchTargets);
|
|
1021
|
+
if (!selected) return;
|
|
1022
|
+
await applySwitchTarget(selected);
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
if (subagentTargets.length === 0 && !parentSessionFile) {
|
|
1027
|
+
ctx.ui.notify("No tracked subagent sessions for this parent session yet. Run a single-mode subagent first (foreground or background).", "info");
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
const lines = ["Agent switch targets:"];
|
|
1032
|
+
switchTargets.forEach((target, index) => {
|
|
1033
|
+
lines.push(`${index + 1}. ${formatSwitchTargetSummary(target)}`);
|
|
1034
|
+
});
|
|
1035
|
+
lines.push("", "Use `/agent <index|id|name>` for explicit targeting, or `/agent parent`.");
|
|
1036
|
+
ctx.ui.notify(lines.join("\n"), "info");
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
if (arg === "parent" || arg === "main") {
|
|
1041
|
+
if (!parentSessionFile) {
|
|
1042
|
+
ctx.ui.notify("You are already in the parent/main session.", "info");
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
if (!fs.existsSync(parentSessionFile)) {
|
|
1046
|
+
ctx.ui.notify(`Parent session file not found: ${parentSessionFile}`, "error");
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1049
|
+
const switched = await ctx.switchSession(parentSessionFile);
|
|
1050
|
+
if (switched.cancelled) {
|
|
1051
|
+
ctx.ui.notify("Session switch was cancelled.", "warning");
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
1054
|
+
ctx.ui.notify("Switched to parent session.", "info");
|
|
1055
|
+
return;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
let target: AgentSessionLink | undefined;
|
|
1059
|
+
if (/^\d+$/.test(arg)) {
|
|
1060
|
+
const index = Number.parseInt(arg, 10) - 1;
|
|
1061
|
+
target = tracked[index];
|
|
1062
|
+
}
|
|
1063
|
+
if (!target) {
|
|
1064
|
+
target = tracked.find((entry) => entry.id === arg);
|
|
1065
|
+
}
|
|
1066
|
+
if (!target) {
|
|
1067
|
+
target = tracked.find((entry) => entry.agentName === arg);
|
|
1068
|
+
}
|
|
1069
|
+
if (!target) {
|
|
1070
|
+
target = tracked.find((entry) => path.basename(entry.subagentSessionFile) === arg);
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
if (!target) {
|
|
1074
|
+
const runningTarget = switchTargets.find((entry) => entry.id === arg && entry.kind === "subagent");
|
|
1075
|
+
if (runningTarget?.state === "running") {
|
|
1076
|
+
ctx.ui.notify(runningTarget.blockedReason ?? "Selected subagent is still running. Live attach is not implemented yet.", "warning");
|
|
1077
|
+
return;
|
|
1078
|
+
}
|
|
1079
|
+
ctx.ui.notify(`Unknown subagent target: ${arg}. Run /agent to list available targets.`, "warning");
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
if (!fs.existsSync(target.subagentSessionFile)) {
|
|
1084
|
+
ctx.ui.notify(`Subagent session file is missing: ${target.subagentSessionFile}`, "error");
|
|
1085
|
+
return;
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
if (target.state === "running") {
|
|
1089
|
+
const liveRuntime = liveRuntimeBySessionFile.get(target.subagentSessionFile);
|
|
1090
|
+
if (!liveRuntime) {
|
|
1091
|
+
ctx.ui.notify("Live runtime is no longer available for this subagent. It may have completed.", "warning");
|
|
1092
|
+
return;
|
|
1093
|
+
}
|
|
1094
|
+
const switched = await ctx.switchSession(target.subagentSessionFile);
|
|
1095
|
+
if (switched.cancelled) {
|
|
1096
|
+
ctx.ui.notify("Session switch was cancelled.", "warning");
|
|
1097
|
+
return;
|
|
1098
|
+
}
|
|
1099
|
+
ctx.ui.notify(`Attached to running subagent ${target.agentName}. Prompts in this session are routed live (busy => steer, idle => prompt). Use /agent parent to return.`, "info");
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
const switched = await ctx.switchSession(target.subagentSessionFile);
|
|
1104
|
+
if (switched.cancelled) {
|
|
1105
|
+
ctx.ui.notify("Session switch was cancelled.", "warning");
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
updateAgentSessionLinkState(target.subagentSessionFile, target.state === "failed" ? "failed" : "completed");
|
|
1109
|
+
ctx.ui.notify(`Switched to subagent ${target.agentName}. This resumes the saved subagent session; use /agent parent to return.`, "info");
|
|
1110
|
+
},
|
|
1111
|
+
});
|
|
975
1112
|
pi.registerShortcut(Key.ctrl("b"), {
|
|
976
1113
|
description: shortcutDesc("Move foreground subagent to background", "/subagents list"),
|
|
977
1114
|
handler: async (ctx) => {
|
|
@@ -986,13 +1123,31 @@ export default function(pi: ExtensionAPI) {
|
|
|
986
1123
|
running.claimed = true;
|
|
987
1124
|
let jobId: string;
|
|
988
1125
|
try {
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
1126
|
+
if (running.handle) {
|
|
1127
|
+
jobId = manager.adoptHandle(
|
|
1128
|
+
running.agentName,
|
|
1129
|
+
running.task,
|
|
1130
|
+
running.cwd,
|
|
1131
|
+
running.handle,
|
|
1132
|
+
running.resultPromise,
|
|
1133
|
+
{
|
|
1134
|
+
parentSessionFile: running.parentSessionFile ?? ctx.sessionManager.getSessionFile(),
|
|
1135
|
+
},
|
|
1136
|
+
);
|
|
1137
|
+
} else if (running.abortController) {
|
|
1138
|
+
jobId = manager.adoptRunning(
|
|
1139
|
+
running.agentName,
|
|
1140
|
+
running.task,
|
|
1141
|
+
running.cwd,
|
|
1142
|
+
running.abortController,
|
|
1143
|
+
running.resultPromise,
|
|
1144
|
+
{
|
|
1145
|
+
parentSessionFile: running.parentSessionFile ?? ctx.sessionManager.getSessionFile(),
|
|
1146
|
+
},
|
|
1147
|
+
);
|
|
1148
|
+
} else {
|
|
1149
|
+
throw new Error("Foreground subagent cannot be moved to background (missing runtime handle).");
|
|
1150
|
+
}
|
|
996
1151
|
} catch (error) {
|
|
997
1152
|
running.claimed = false;
|
|
998
1153
|
ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
|
|
@@ -1075,6 +1230,18 @@ export default function(pi: ExtensionAPI) {
|
|
|
1075
1230
|
const confirmProjectAgents = params.confirmProjectAgents ?? false;
|
|
1076
1231
|
const cmuxClient = CmuxClient.fromPreferences(loadEffectivePreferences()?.preferences);
|
|
1077
1232
|
const cmuxSplitsEnabled = cmuxClient.getConfig().splits;
|
|
1233
|
+
const invokingSessionFile = ctx.sessionManager.getSessionFile();
|
|
1234
|
+
const invokingSessionId = ctx.sessionManager.getSessionId();
|
|
1235
|
+
const invokingMetadata = getCurrentSessionSubagentMetadata(invokingSessionFile);
|
|
1236
|
+
const currentSubagentName = invokingMetadata?.subagentName;
|
|
1237
|
+
const trackedAncestry = inProcessSubagentAncestryBySessionId.get(invokingSessionId);
|
|
1238
|
+
const currentAncestry = trackedAncestry ?? (currentSubagentName ? [currentSubagentName] : []);
|
|
1239
|
+
const inferredCurrentDepth = currentSubagentName ? Math.max(currentAncestry.length, 1) : 0;
|
|
1240
|
+
const currentSubagentDepth = Math.max(
|
|
1241
|
+
inProcessSubagentDepthBySessionId.get(invokingSessionId) ?? 0,
|
|
1242
|
+
inferredCurrentDepth,
|
|
1243
|
+
);
|
|
1244
|
+
const nextSubagentDepth = currentSubagentDepth + 1;
|
|
1078
1245
|
|
|
1079
1246
|
// Resolve isolation mode
|
|
1080
1247
|
const isolationMode = readIsolationMode();
|
|
@@ -1094,6 +1261,37 @@ export default function(pi: ExtensionAPI) {
|
|
|
1094
1261
|
results,
|
|
1095
1262
|
});
|
|
1096
1263
|
|
|
1264
|
+
const trackInProcessDepth = (
|
|
1265
|
+
started: { handle: SubagentHandle; resultPromise: Promise<SingleResult> },
|
|
1266
|
+
depth: number,
|
|
1267
|
+
ancestry: string[],
|
|
1268
|
+
) => {
|
|
1269
|
+
const sessionId = started.handle.sessionId;
|
|
1270
|
+
if (!sessionId) return;
|
|
1271
|
+
inProcessSubagentDepthBySessionId.set(sessionId, depth);
|
|
1272
|
+
inProcessSubagentAncestryBySessionId.set(sessionId, ancestry);
|
|
1273
|
+
void started.resultPromise.finally(() => {
|
|
1274
|
+
inProcessSubagentDepthBySessionId.delete(sessionId);
|
|
1275
|
+
inProcessSubagentAncestryBySessionId.delete(sessionId);
|
|
1276
|
+
});
|
|
1277
|
+
};
|
|
1278
|
+
|
|
1279
|
+
const buildChildAncestry = (childAgentName: string): string[] => [...currentAncestry, childAgentName];
|
|
1280
|
+
|
|
1281
|
+
const requestedAgentNames = hasSingle
|
|
1282
|
+
? [params.agent!]
|
|
1283
|
+
: hasChain
|
|
1284
|
+
? (params.chain ?? []).map((step) => step.agent)
|
|
1285
|
+
: (params.tasks ?? []).map((task) => task.agent);
|
|
1286
|
+
|
|
1287
|
+
if (currentSubagentName && requestedAgentNames.some((name) => name === currentSubagentName)) {
|
|
1288
|
+
return {
|
|
1289
|
+
content: [{ type: "text", text: `Subagent "${currentSubagentName}" cannot spawn another subagent with the same name as itself.` }],
|
|
1290
|
+
details: makeDetails(hasChain ? "chain" : hasTasks ? "parallel" : "single")([]),
|
|
1291
|
+
isError: true,
|
|
1292
|
+
};
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1097
1295
|
if (modeCount !== 1) {
|
|
1098
1296
|
const available = agents.map((a) => `${a.name} (${a.source})`).join(", ") || "none";
|
|
1099
1297
|
return {
|
|
@@ -1115,6 +1313,22 @@ export default function(pi: ExtensionAPI) {
|
|
|
1115
1313
|
};
|
|
1116
1314
|
}
|
|
1117
1315
|
|
|
1316
|
+
if (params.background && currentSubagentDepth > 0) {
|
|
1317
|
+
return {
|
|
1318
|
+
content: [{ type: "text", text: "Nested background subagent launches are not supported yet. Run the nested subagent in foreground mode." }],
|
|
1319
|
+
details: makeDetails("single")([]),
|
|
1320
|
+
isError: true,
|
|
1321
|
+
};
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
if (USE_IN_PROCESS_SUBAGENT && nextSubagentDepth > MAX_IN_PROCESS_SUBAGENT_DEPTH) {
|
|
1325
|
+
return {
|
|
1326
|
+
content: [{ type: "text", text: `Max subagent depth (${MAX_IN_PROCESS_SUBAGENT_DEPTH}) exceeded.` }],
|
|
1327
|
+
details: makeDetails(hasChain ? "chain" : hasTasks ? "parallel" : "single")([]),
|
|
1328
|
+
isError: true,
|
|
1329
|
+
};
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1118
1332
|
if ((agentScope === "project" || agentScope === "both") && confirmProjectAgents && ctx.hasUI) {
|
|
1119
1333
|
const requestedAgentNames = new Set<string>();
|
|
1120
1334
|
if (params.chain) for (const step of params.chain) requestedAgentNames.add(step.agent);
|
|
@@ -1163,19 +1377,45 @@ export default function(pi: ExtensionAPI) {
|
|
|
1163
1377
|
}
|
|
1164
1378
|
: undefined;
|
|
1165
1379
|
|
|
1166
|
-
const result =
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1380
|
+
const result = USE_IN_PROCESS_SUBAGENT
|
|
1381
|
+
? await (async () => {
|
|
1382
|
+
const started = await startInProcessSingleAgent(
|
|
1383
|
+
ctx.cwd,
|
|
1384
|
+
agents,
|
|
1385
|
+
step.agent,
|
|
1386
|
+
taskWithContext,
|
|
1387
|
+
step.cwd,
|
|
1388
|
+
i + 1,
|
|
1389
|
+
step.model,
|
|
1390
|
+
ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined,
|
|
1391
|
+
signal,
|
|
1392
|
+
chainUpdate,
|
|
1393
|
+
makeDetails("chain"),
|
|
1394
|
+
invokingSessionFile,
|
|
1395
|
+
nextSubagentDepth,
|
|
1396
|
+
invokingSessionId,
|
|
1397
|
+
currentAncestry,
|
|
1398
|
+
);
|
|
1399
|
+
trackInProcessDepth(started, nextSubagentDepth, buildChildAncestry(step.agent));
|
|
1400
|
+
return started.resultPromise;
|
|
1401
|
+
})()
|
|
1402
|
+
: await runLegacySingleAgent(
|
|
1403
|
+
ctx.cwd,
|
|
1404
|
+
agents,
|
|
1405
|
+
step.agent,
|
|
1406
|
+
taskWithContext,
|
|
1407
|
+
step.cwd,
|
|
1408
|
+
i + 1,
|
|
1409
|
+
step.model,
|
|
1410
|
+
ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined,
|
|
1411
|
+
signal,
|
|
1412
|
+
chainUpdate,
|
|
1413
|
+
makeDetails("chain"),
|
|
1414
|
+
invokingSessionFile,
|
|
1415
|
+
false,
|
|
1416
|
+
undefined,
|
|
1417
|
+
undefined,
|
|
1418
|
+
);
|
|
1179
1419
|
results.push(result);
|
|
1180
1420
|
|
|
1181
1421
|
const isError =
|
|
@@ -1246,24 +1486,54 @@ export default function(pi: ExtensionAPI) {
|
|
|
1246
1486
|
const results = await mapWithConcurrencyLimit(params.tasks, MAX_CONCURRENCY, async (t, index) => {
|
|
1247
1487
|
const workerId = registerWorker(t.agent, t.task, index, batchSize, batchId);
|
|
1248
1488
|
const runTask = () =>
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1489
|
+
USE_IN_PROCESS_SUBAGENT
|
|
1490
|
+
? (async () => {
|
|
1491
|
+
const started = await startInProcessSingleAgent(
|
|
1492
|
+
ctx.cwd,
|
|
1493
|
+
agents,
|
|
1494
|
+
t.agent,
|
|
1495
|
+
t.task,
|
|
1496
|
+
t.cwd,
|
|
1497
|
+
undefined,
|
|
1498
|
+
t.model,
|
|
1499
|
+
ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined,
|
|
1500
|
+
signal,
|
|
1501
|
+
(partial) => {
|
|
1502
|
+
if (partial.details?.results[0]) {
|
|
1503
|
+
allResults[index] = partial.details.results[0];
|
|
1504
|
+
emitParallelUpdate();
|
|
1505
|
+
}
|
|
1506
|
+
},
|
|
1507
|
+
makeDetails("parallel"),
|
|
1508
|
+
invokingSessionFile,
|
|
1509
|
+
nextSubagentDepth,
|
|
1510
|
+
invokingSessionId,
|
|
1511
|
+
currentAncestry,
|
|
1512
|
+
);
|
|
1513
|
+
trackInProcessDepth(started, nextSubagentDepth, buildChildAncestry(t.agent));
|
|
1514
|
+
return started.resultPromise;
|
|
1515
|
+
})()
|
|
1516
|
+
: runLegacySingleAgent(
|
|
1517
|
+
ctx.cwd,
|
|
1518
|
+
agents,
|
|
1519
|
+
t.agent,
|
|
1520
|
+
t.task,
|
|
1521
|
+
t.cwd,
|
|
1522
|
+
undefined,
|
|
1523
|
+
t.model,
|
|
1524
|
+
ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined,
|
|
1525
|
+
signal,
|
|
1526
|
+
(partial) => {
|
|
1527
|
+
if (partial.details?.results[0]) {
|
|
1528
|
+
allResults[index] = partial.details.results[0];
|
|
1529
|
+
emitParallelUpdate();
|
|
1530
|
+
}
|
|
1531
|
+
},
|
|
1532
|
+
makeDetails("parallel"),
|
|
1533
|
+
invokingSessionFile,
|
|
1534
|
+
false,
|
|
1535
|
+
undefined,
|
|
1536
|
+
);
|
|
1267
1537
|
let result = await runTask();
|
|
1268
1538
|
|
|
1269
1539
|
// Auto-retry failed tasks (likely API rate limit or transient error)
|
|
@@ -1330,36 +1600,130 @@ export default function(pi: ExtensionAPI) {
|
|
|
1330
1600
|
|
|
1331
1601
|
let jobId: string;
|
|
1332
1602
|
try {
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1603
|
+
if (USE_IN_PROCESS_SUBAGENT) {
|
|
1604
|
+
const started = await startInProcessSingleAgent(
|
|
1605
|
+
ctx.cwd,
|
|
1606
|
+
agents,
|
|
1607
|
+
params.agent,
|
|
1608
|
+
params.task,
|
|
1609
|
+
params.cwd,
|
|
1610
|
+
undefined,
|
|
1611
|
+
params.model,
|
|
1612
|
+
ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined,
|
|
1613
|
+
undefined,
|
|
1614
|
+
undefined,
|
|
1615
|
+
makeDetails("single"),
|
|
1616
|
+
invokingSessionFile,
|
|
1617
|
+
nextSubagentDepth,
|
|
1618
|
+
invokingSessionId,
|
|
1619
|
+
currentAncestry,
|
|
1620
|
+
);
|
|
1621
|
+
trackInProcessDepth(started, nextSubagentDepth, buildChildAncestry(params.agent));
|
|
1622
|
+
|
|
1623
|
+
const effectiveCwd = params.cwd ?? ctx.cwd;
|
|
1624
|
+
jobId = manager.adoptHandle(
|
|
1625
|
+
params.agent,
|
|
1626
|
+
params.task,
|
|
1627
|
+
effectiveCwd,
|
|
1628
|
+
started.handle,
|
|
1629
|
+
started.resultPromise.then((result) => ({
|
|
1630
|
+
summary: (getFinalOutput(result.messages) || "(no output)").slice(0, 300),
|
|
1358
1631
|
stderr: result.stderr,
|
|
1632
|
+
exitCode: result.exitCode,
|
|
1359
1633
|
model: result.model,
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1634
|
+
sessionFile: result.sessionFile,
|
|
1635
|
+
parentSessionFile: result.parentSessionFile,
|
|
1636
|
+
})),
|
|
1637
|
+
{
|
|
1638
|
+
parentSessionFile: invokingSessionFile,
|
|
1639
|
+
model: bgInferredModel,
|
|
1640
|
+
},
|
|
1641
|
+
);
|
|
1642
|
+
} else {
|
|
1643
|
+
jobId = runSubagentInBackground(
|
|
1644
|
+
manager,
|
|
1645
|
+
agents,
|
|
1646
|
+
params.agent,
|
|
1647
|
+
params.task,
|
|
1648
|
+
params.cwd,
|
|
1649
|
+
params.model,
|
|
1650
|
+
{ defaultCwd: ctx.cwd, model: ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined, parentSessionFile: invokingSessionFile },
|
|
1651
|
+
async (bgSignal) => {
|
|
1652
|
+
let liveSessionFile: string | undefined;
|
|
1653
|
+
let liveRuntime: LiveSubagentRuntime | undefined;
|
|
1654
|
+
const result = await runLegacySingleAgent(
|
|
1655
|
+
ctx.cwd,
|
|
1656
|
+
agents,
|
|
1657
|
+
params.agent!,
|
|
1658
|
+
params.task!,
|
|
1659
|
+
params.cwd,
|
|
1660
|
+
undefined,
|
|
1661
|
+
params.model,
|
|
1662
|
+
ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined,
|
|
1663
|
+
bgSignal,
|
|
1664
|
+
undefined, // no streaming updates for background jobs
|
|
1665
|
+
makeDetails("single"),
|
|
1666
|
+
invokingSessionFile,
|
|
1667
|
+
true,
|
|
1668
|
+
(info) => {
|
|
1669
|
+
if (!invokingSessionFile || !info.sessionFile) return;
|
|
1670
|
+
upsertAgentSessionLink(
|
|
1671
|
+
params.agent!,
|
|
1672
|
+
params.task!,
|
|
1673
|
+
invokingSessionFile,
|
|
1674
|
+
info.sessionFile,
|
|
1675
|
+
"running",
|
|
1676
|
+
);
|
|
1677
|
+
liveSessionFile = info.sessionFile;
|
|
1678
|
+
if (liveRuntime) {
|
|
1679
|
+
liveRuntime.sessionFile = info.sessionFile;
|
|
1680
|
+
liveRuntime.parentSessionFile = info.parentSessionFile ?? invokingSessionFile;
|
|
1681
|
+
liveRuntimeBySessionFile.set(info.sessionFile, liveRuntime);
|
|
1682
|
+
}
|
|
1683
|
+
},
|
|
1684
|
+
(event, partial) => {
|
|
1685
|
+
const sessionFile = partial.sessionFile;
|
|
1686
|
+
if (!sessionFile || activeSessionFileForUi !== sessionFile) return;
|
|
1687
|
+
if (event?.type === "message_update" && event.assistantMessageEvent?.type === "text_delta") {
|
|
1688
|
+
const delta = String(event.assistantMessageEvent.delta ?? "");
|
|
1689
|
+
if (delta) pushLiveStreamDelta(sessionFile, delta);
|
|
1690
|
+
}
|
|
1691
|
+
if (event?.type === "message_end") {
|
|
1692
|
+
flushLiveStream(sessionFile);
|
|
1693
|
+
}
|
|
1694
|
+
},
|
|
1695
|
+
{
|
|
1696
|
+
onStart: (control) => {
|
|
1697
|
+
if (!control.sendPrompt || !control.sendSteer || !control.sendFollowUp || !control.isBusy) return;
|
|
1698
|
+
liveRuntime = {
|
|
1699
|
+
sessionFile: liveSessionFile,
|
|
1700
|
+
parentSessionFile: invokingSessionFile,
|
|
1701
|
+
agentName: params.agent!,
|
|
1702
|
+
isBusy: control.isBusy,
|
|
1703
|
+
sendPrompt: control.sendPrompt,
|
|
1704
|
+
sendSteer: control.sendSteer,
|
|
1705
|
+
sendFollowUp: control.sendFollowUp,
|
|
1706
|
+
};
|
|
1707
|
+
if (liveSessionFile) {
|
|
1708
|
+
liveRuntimeBySessionFile.set(liveSessionFile, liveRuntime);
|
|
1709
|
+
}
|
|
1710
|
+
},
|
|
1711
|
+
onFinish: () => {
|
|
1712
|
+
if (liveSessionFile) liveRuntimeBySessionFile.delete(liveSessionFile);
|
|
1713
|
+
},
|
|
1714
|
+
},
|
|
1715
|
+
);
|
|
1716
|
+
return {
|
|
1717
|
+
exitCode: result.exitCode,
|
|
1718
|
+
finalOutput: getFinalOutput(result.messages),
|
|
1719
|
+
stderr: result.stderr,
|
|
1720
|
+
model: result.model,
|
|
1721
|
+
sessionFile: result.sessionFile,
|
|
1722
|
+
parentSessionFile: result.parentSessionFile,
|
|
1723
|
+
};
|
|
1724
|
+
},
|
|
1725
|
+
);
|
|
1726
|
+
}
|
|
1363
1727
|
} catch (err) {
|
|
1364
1728
|
return {
|
|
1365
1729
|
content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }],
|
|
@@ -1387,31 +1751,170 @@ export default function(pi: ExtensionAPI) {
|
|
|
1387
1751
|
isolation = await createIsolation(effectiveCwd, taskId, isolationMode);
|
|
1388
1752
|
}
|
|
1389
1753
|
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1754
|
+
let result: SingleResult;
|
|
1755
|
+
if (USE_IN_PROCESS_SUBAGENT && !isolation) {
|
|
1756
|
+
const started = await startInProcessSingleAgent(
|
|
1757
|
+
ctx.cwd,
|
|
1758
|
+
agents,
|
|
1759
|
+
params.agent,
|
|
1760
|
+
params.task,
|
|
1761
|
+
params.cwd,
|
|
1762
|
+
undefined,
|
|
1763
|
+
params.model,
|
|
1764
|
+
ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined,
|
|
1765
|
+
signal,
|
|
1766
|
+
onUpdate,
|
|
1767
|
+
makeDetails("single"),
|
|
1768
|
+
invokingSessionFile,
|
|
1769
|
+
nextSubagentDepth,
|
|
1770
|
+
invokingSessionId,
|
|
1771
|
+
currentAncestry,
|
|
1772
|
+
);
|
|
1773
|
+
trackInProcessDepth(started, nextSubagentDepth, buildChildAncestry(params.agent));
|
|
1774
|
+
|
|
1775
|
+
const effectiveRunCwd = params.cwd ?? ctx.cwd;
|
|
1776
|
+
let releaseToBackground: ((jobId: string) => void) | undefined;
|
|
1777
|
+
const movedToBackground = new Promise<string>((resolve) => {
|
|
1778
|
+
releaseToBackground = resolve;
|
|
1779
|
+
});
|
|
1780
|
+
|
|
1781
|
+
activeForegroundSubagent = {
|
|
1782
|
+
claimed: false,
|
|
1783
|
+
agentName: params.agent,
|
|
1784
|
+
task: params.task,
|
|
1785
|
+
cwd: effectiveRunCwd,
|
|
1786
|
+
parentSessionFile: invokingSessionFile,
|
|
1787
|
+
handle: started.handle,
|
|
1788
|
+
resultPromise: started.resultPromise.then((done) => ({
|
|
1789
|
+
summary: getFinalOutput(done.messages) || "(no output)",
|
|
1790
|
+
stderr: done.stderr,
|
|
1791
|
+
exitCode: done.exitCode,
|
|
1792
|
+
model: done.model,
|
|
1793
|
+
sessionFile: done.sessionFile,
|
|
1794
|
+
parentSessionFile: done.parentSessionFile,
|
|
1795
|
+
})),
|
|
1796
|
+
adoptToBackground: (jobId: string) => {
|
|
1797
|
+
if (!releaseToBackground) return false;
|
|
1798
|
+
releaseToBackground(jobId);
|
|
1799
|
+
releaseToBackground = undefined;
|
|
1800
|
+
return true;
|
|
1801
|
+
},
|
|
1802
|
+
sendPrompt: started.handle.prompt,
|
|
1803
|
+
sendSteer: started.handle.steer,
|
|
1804
|
+
sendFollowUp: started.handle.followUp,
|
|
1805
|
+
isBusy: started.handle.isBusy,
|
|
1806
|
+
};
|
|
1807
|
+
ctx.ui.setStatus(foregroundSubagentStatusKey, foregroundSubagentHint);
|
|
1808
|
+
|
|
1809
|
+
const winner = await Promise.race([
|
|
1810
|
+
started.resultPromise.then((done) => ({ type: "done" as const, done })),
|
|
1811
|
+
movedToBackground.then((jobId) => ({ type: "background" as const, jobId })),
|
|
1812
|
+
]);
|
|
1813
|
+
|
|
1814
|
+
if (winner.type === "background") {
|
|
1815
|
+
activeForegroundSubagent = null;
|
|
1816
|
+
ctx.ui.setStatus(foregroundSubagentStatusKey, undefined);
|
|
1817
|
+
result = {
|
|
1818
|
+
...started.currentResult,
|
|
1819
|
+
backgroundJobId: winner.jobId,
|
|
1820
|
+
};
|
|
1821
|
+
} else {
|
|
1822
|
+
activeForegroundSubagent = null;
|
|
1823
|
+
ctx.ui.setStatus(foregroundSubagentStatusKey, undefined);
|
|
1824
|
+
result = winner.done;
|
|
1825
|
+
}
|
|
1826
|
+
} else {
|
|
1827
|
+
let liveSessionFile: string | undefined;
|
|
1828
|
+
let liveRuntime: LiveSubagentRuntime | undefined;
|
|
1829
|
+
result = await runLegacySingleAgent(
|
|
1830
|
+
ctx.cwd,
|
|
1831
|
+
agents,
|
|
1832
|
+
params.agent,
|
|
1833
|
+
params.task,
|
|
1834
|
+
isolation ? isolation.workDir : params.cwd,
|
|
1835
|
+
undefined,
|
|
1836
|
+
params.model,
|
|
1837
|
+
ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined,
|
|
1838
|
+
signal,
|
|
1839
|
+
onUpdate,
|
|
1840
|
+
makeDetails("single"),
|
|
1841
|
+
invokingSessionFile,
|
|
1842
|
+
!isolation,
|
|
1843
|
+
!isolation
|
|
1844
|
+
? (info) => {
|
|
1845
|
+
if (!invokingSessionFile || !info.sessionFile) return;
|
|
1846
|
+
upsertAgentSessionLink(
|
|
1847
|
+
params.agent!,
|
|
1848
|
+
params.task!,
|
|
1849
|
+
invokingSessionFile,
|
|
1850
|
+
info.sessionFile,
|
|
1851
|
+
"running",
|
|
1852
|
+
);
|
|
1853
|
+
liveSessionFile = info.sessionFile;
|
|
1854
|
+
if (liveRuntime) {
|
|
1855
|
+
liveRuntime.sessionFile = info.sessionFile;
|
|
1856
|
+
liveRuntime.parentSessionFile = info.parentSessionFile ?? invokingSessionFile;
|
|
1857
|
+
liveRuntimeBySessionFile.set(info.sessionFile, liveRuntime);
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
: undefined,
|
|
1861
|
+
!isolation
|
|
1862
|
+
? (event, partial) => {
|
|
1863
|
+
const sessionFile = partial.sessionFile;
|
|
1864
|
+
if (!sessionFile || activeSessionFileForUi !== sessionFile) return;
|
|
1865
|
+
if (event?.type === "message_update" && event.assistantMessageEvent?.type === "text_delta") {
|
|
1866
|
+
const delta = String(event.assistantMessageEvent.delta ?? "");
|
|
1867
|
+
if (delta) pushLiveStreamDelta(sessionFile, delta);
|
|
1868
|
+
}
|
|
1869
|
+
if (event?.type === "message_end") {
|
|
1870
|
+
flushLiveStream(sessionFile);
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
: undefined,
|
|
1874
|
+
!isolation
|
|
1875
|
+
? {
|
|
1876
|
+
onStart: (control) => {
|
|
1877
|
+
activeForegroundSubagent = { ...control, claimed: false };
|
|
1878
|
+
ctx.ui.setStatus(foregroundSubagentStatusKey, foregroundSubagentHint);
|
|
1879
|
+
|
|
1880
|
+
if (!control.sendPrompt || !control.sendSteer || !control.sendFollowUp || !control.isBusy) return;
|
|
1881
|
+
liveRuntime = {
|
|
1882
|
+
sessionFile: liveSessionFile,
|
|
1883
|
+
parentSessionFile: invokingSessionFile,
|
|
1884
|
+
agentName: params.agent!,
|
|
1885
|
+
isBusy: control.isBusy,
|
|
1886
|
+
sendPrompt: control.sendPrompt,
|
|
1887
|
+
sendSteer: control.sendSteer,
|
|
1888
|
+
sendFollowUp: control.sendFollowUp,
|
|
1889
|
+
};
|
|
1890
|
+
if (liveSessionFile && liveRuntime) {
|
|
1891
|
+
liveRuntimeBySessionFile.set(liveSessionFile, liveRuntime);
|
|
1892
|
+
}
|
|
1893
|
+
},
|
|
1894
|
+
onFinish: () => {
|
|
1895
|
+
activeForegroundSubagent = null;
|
|
1896
|
+
ctx.ui.setStatus(foregroundSubagentStatusKey, undefined);
|
|
1897
|
+
if (liveSessionFile) liveRuntimeBySessionFile.delete(liveSessionFile);
|
|
1898
|
+
},
|
|
1899
|
+
}
|
|
1900
|
+
: undefined,
|
|
1901
|
+
);
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
if (result.sessionFile && invokingSessionFile) {
|
|
1905
|
+
const existingParent = parentSessionByChild.get(result.sessionFile);
|
|
1906
|
+
if (!existingParent) {
|
|
1907
|
+
registerAgentSessionLink({
|
|
1908
|
+
agentName: result.agent,
|
|
1909
|
+
task: result.task,
|
|
1910
|
+
parentSessionFile: invokingSessionFile,
|
|
1911
|
+
subagentSessionFile: result.sessionFile,
|
|
1912
|
+
state: result.exitCode === 0 ? "completed" : "failed",
|
|
1913
|
+
});
|
|
1914
|
+
} else {
|
|
1915
|
+
updateAgentSessionLinkState(result.sessionFile, result.exitCode === 0 ? "completed" : "failed");
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1415
1918
|
|
|
1416
1919
|
if (result.backgroundJobId) {
|
|
1417
1920
|
return {
|
|
@@ -1429,11 +1932,12 @@ export default function(pi: ExtensionAPI) {
|
|
|
1429
1932
|
}
|
|
1430
1933
|
|
|
1431
1934
|
const isError = result.exitCode !== 0 || result.stopReason === "error" || result.stopReason === "aborted";
|
|
1935
|
+
const agentSwitchHint = result.sessionFile ? "\n\nTip: run `/agent` to switch focus to this subagent session." : "";
|
|
1432
1936
|
if (isError) {
|
|
1433
1937
|
const errorMsg =
|
|
1434
1938
|
result.errorMessage || result.stderr || getFinalOutput(result.messages) || "(no output)";
|
|
1435
1939
|
return {
|
|
1436
|
-
content: [{ type: "text", text: `Agent ${result.stopReason || "failed"}: ${errorMsg}` }],
|
|
1940
|
+
content: [{ type: "text", text: `Agent ${result.stopReason || "failed"}: ${errorMsg}${agentSwitchHint}` }],
|
|
1437
1941
|
details: makeDetails("single")([result]),
|
|
1438
1942
|
isError: true,
|
|
1439
1943
|
};
|
|
@@ -1444,6 +1948,7 @@ export default function(pi: ExtensionAPI) {
|
|
|
1444
1948
|
if (mergeResult && !mergeResult.success) {
|
|
1445
1949
|
outputText += `\n\n⚠ Patch merge failed: ${mergeResult.error || "unknown error"}`;
|
|
1446
1950
|
}
|
|
1951
|
+
if (agentSwitchHint) outputText += agentSwitchHint;
|
|
1447
1952
|
return {
|
|
1448
1953
|
content: [{ type: "text", text: outputText }],
|
|
1449
1954
|
details: makeDetails("single")([result]),
|