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
|
@@ -11,32 +11,170 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Uses JSON mode to capture structured output from subagents.
|
|
13
13
|
*/
|
|
14
|
-
import { spawn, execFileSync } from "node:child_process";
|
|
15
14
|
import * as crypto from "node:crypto";
|
|
16
15
|
import * as fs from "node:fs";
|
|
17
16
|
import * as os from "node:os";
|
|
18
17
|
import * as path from "node:path";
|
|
19
18
|
import { StringEnum } from "@gsd/pi-ai";
|
|
20
|
-
import {
|
|
19
|
+
import { getMarkdownTheme, } from "@gsd/pi-coding-agent";
|
|
21
20
|
import { Container, Key, Markdown, Spacer, Text } from "@gsd/pi-tui";
|
|
22
21
|
import { Type } from "@sinclair/typebox";
|
|
23
22
|
import { formatTokenCount, shortcutDesc } from "../shared/mod.js";
|
|
24
23
|
import { discoverAgents } from "./agents.js";
|
|
25
|
-
import { buildSubagentProcessArgs, getBundledExtensionPathsFromEnv } from "./launch-helpers.js";
|
|
26
24
|
import { createIsolation, mergeDeltaPatches, readIsolationMode, } from "./isolation.js";
|
|
27
25
|
import { registerWorker, updateWorker } from "./worker-registry.js";
|
|
28
|
-
import { handleSubagentPermissionRequest, isSubagentPermissionRequest } from "./approval-proxy.js";
|
|
29
26
|
import { resolveConfiguredSubagentModel } from "./configured-model.js";
|
|
30
|
-
import {
|
|
27
|
+
import { resolveSubagentModel } from "./model-resolution.js";
|
|
31
28
|
import { loadEffectivePreferences } from "../shared/preferences.js";
|
|
29
|
+
import { readBudgetSubagentModelFromSettings, runLegacySingleAgent, stopLegacySubagents, } from "./legacy-runner.js";
|
|
30
|
+
import { MAX_IN_PROCESS_SUBAGENT_DEPTH, startInProcessSingleAgent, } from "./in-process-runner.js";
|
|
32
31
|
import { CmuxClient } from "../cmux/index.js";
|
|
33
32
|
import { BackgroundJobManager } from "./background-job-manager.js";
|
|
34
33
|
import { runSubagentInBackground } from "./background-runner.js";
|
|
34
|
+
import { showAgentSwitcher } from "./agent-switcher-component.js";
|
|
35
|
+
import { buildAgentSwitchTargets, } from "./agent-switcher-model.js";
|
|
35
36
|
const MAX_PARALLEL_TASKS = 8;
|
|
36
37
|
const MAX_CONCURRENCY = 4;
|
|
37
38
|
const COLLAPSED_ITEM_COUNT = 10;
|
|
38
39
|
const DEFAULT_AWAIT_SUBAGENT_TIMEOUT_SECONDS = 120;
|
|
39
|
-
const
|
|
40
|
+
const USE_IN_PROCESS_SUBAGENT = process.env.LSD_SUBAGENT_IN_PROCESS !== "0";
|
|
41
|
+
const agentSessionLinksById = new Map();
|
|
42
|
+
const agentSessionIdsByParent = new Map();
|
|
43
|
+
const parentSessionByChild = new Map();
|
|
44
|
+
const liveRuntimeBySessionFile = new Map();
|
|
45
|
+
const inProcessSubagentDepthBySessionId = new Map();
|
|
46
|
+
const inProcessSubagentAncestryBySessionId = new Map();
|
|
47
|
+
let agentSessionLinkCounter = 0;
|
|
48
|
+
function listSessionFiles(sessionDir) {
|
|
49
|
+
if (!fs.existsSync(sessionDir))
|
|
50
|
+
return [];
|
|
51
|
+
try {
|
|
52
|
+
return fs
|
|
53
|
+
.readdirSync(sessionDir)
|
|
54
|
+
.filter((name) => name.endsWith(".jsonl"))
|
|
55
|
+
.map((name) => path.join(sessionDir, name));
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function registerAgentSessionLink(link) {
|
|
62
|
+
const now = Date.now();
|
|
63
|
+
const id = `agent-${++agentSessionLinkCounter}`;
|
|
64
|
+
const full = { ...link, id, createdAt: now, updatedAt: now };
|
|
65
|
+
agentSessionLinksById.set(id, full);
|
|
66
|
+
const list = agentSessionIdsByParent.get(link.parentSessionFile) ?? [];
|
|
67
|
+
list.push(id);
|
|
68
|
+
agentSessionIdsByParent.set(link.parentSessionFile, list);
|
|
69
|
+
parentSessionByChild.set(link.subagentSessionFile, link.parentSessionFile);
|
|
70
|
+
return full;
|
|
71
|
+
}
|
|
72
|
+
function updateAgentSessionLinkState(subagentSessionFile, state) {
|
|
73
|
+
for (const link of agentSessionLinksById.values()) {
|
|
74
|
+
if (link.subagentSessionFile === subagentSessionFile) {
|
|
75
|
+
link.state = state;
|
|
76
|
+
link.updatedAt = Date.now();
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function upsertAgentSessionLink(agentName, task, parentSessionFile, subagentSessionFile, state) {
|
|
82
|
+
const existingParent = parentSessionByChild.get(subagentSessionFile);
|
|
83
|
+
if (!existingParent) {
|
|
84
|
+
registerAgentSessionLink({
|
|
85
|
+
agentName,
|
|
86
|
+
task,
|
|
87
|
+
parentSessionFile,
|
|
88
|
+
subagentSessionFile,
|
|
89
|
+
state,
|
|
90
|
+
});
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
updateAgentSessionLinkState(subagentSessionFile, state);
|
|
94
|
+
}
|
|
95
|
+
function getAgentSessionLinksForParent(parentSessionFile) {
|
|
96
|
+
const ids = agentSessionIdsByParent.get(parentSessionFile) ?? [];
|
|
97
|
+
return ids
|
|
98
|
+
.map((id) => agentSessionLinksById.get(id))
|
|
99
|
+
.filter((entry) => Boolean(entry))
|
|
100
|
+
.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
101
|
+
}
|
|
102
|
+
function readSessionHeader(sessionFile) {
|
|
103
|
+
try {
|
|
104
|
+
const content = fs.readFileSync(sessionFile, "utf-8");
|
|
105
|
+
const firstLine = content.split("\n").find((line) => line.trim().length > 0);
|
|
106
|
+
if (!firstLine)
|
|
107
|
+
return null;
|
|
108
|
+
const parsed = JSON.parse(firstLine);
|
|
109
|
+
if (!parsed || parsed.type !== "session")
|
|
110
|
+
return null;
|
|
111
|
+
return {
|
|
112
|
+
parentSession: typeof parsed.parentSession === "string" ? parsed.parentSession : undefined,
|
|
113
|
+
subagentName: typeof parsed.subagentName === "string" ? parsed.subagentName : undefined,
|
|
114
|
+
subagentTask: typeof parsed.subagentTask === "string" ? parsed.subagentTask : undefined,
|
|
115
|
+
subagentSystemPrompt: typeof parsed.subagentSystemPrompt === "string" ? parsed.subagentSystemPrompt : undefined,
|
|
116
|
+
subagentTools: Array.isArray(parsed.subagentTools)
|
|
117
|
+
? parsed.subagentTools.filter((tool) => typeof tool === "string")
|
|
118
|
+
: undefined,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function backfillAgentSessionLinksForParent(parentSessionFile, sessionDir) {
|
|
126
|
+
for (const sessionFile of listSessionFiles(sessionDir)) {
|
|
127
|
+
if (sessionFile === parentSessionFile)
|
|
128
|
+
continue;
|
|
129
|
+
const header = readSessionHeader(sessionFile);
|
|
130
|
+
if (header?.parentSession !== parentSessionFile)
|
|
131
|
+
continue;
|
|
132
|
+
const existingParent = parentSessionByChild.get(sessionFile);
|
|
133
|
+
if (!existingParent) {
|
|
134
|
+
registerAgentSessionLink({
|
|
135
|
+
agentName: header.subagentName ?? "subagent",
|
|
136
|
+
task: header.subagentTask ?? "Recovered from persisted session lineage",
|
|
137
|
+
parentSessionFile,
|
|
138
|
+
subagentSessionFile: sessionFile,
|
|
139
|
+
state: "completed",
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return getAgentSessionLinksForParent(parentSessionFile);
|
|
144
|
+
}
|
|
145
|
+
function formatSwitchTargetSummary(target) {
|
|
146
|
+
const current = target.isCurrent ? " (current)" : "";
|
|
147
|
+
if (target.kind === "parent") {
|
|
148
|
+
return `● parent — main session${current}`;
|
|
149
|
+
}
|
|
150
|
+
const icon = target.state === "running" ? "▶" : target.state === "failed" ? "✗" : "✓";
|
|
151
|
+
return `${icon} ${target.agentName} — ${target.taskPreview}${current}`;
|
|
152
|
+
}
|
|
153
|
+
function buildSwitchTargetsForParent(parentSessionFile, currentSessionFile, currentCwd, trackedLinks, runningJobs) {
|
|
154
|
+
return buildAgentSwitchTargets({
|
|
155
|
+
currentSessionFile,
|
|
156
|
+
rootParentSessionFile: parentSessionFile,
|
|
157
|
+
currentCwd,
|
|
158
|
+
trackedLinks: trackedLinks.map((link) => ({
|
|
159
|
+
id: link.id,
|
|
160
|
+
agentName: link.agentName,
|
|
161
|
+
task: link.task,
|
|
162
|
+
parentSessionFile: link.parentSessionFile,
|
|
163
|
+
subagentSessionFile: link.subagentSessionFile,
|
|
164
|
+
updatedAt: link.updatedAt,
|
|
165
|
+
state: link.state,
|
|
166
|
+
})),
|
|
167
|
+
runningJobs: runningJobs.map((job) => ({
|
|
168
|
+
id: job.id,
|
|
169
|
+
agentName: job.agentName,
|
|
170
|
+
task: job.task,
|
|
171
|
+
startedAt: job.startedAt,
|
|
172
|
+
parentSessionFile: job.parentSessionFile,
|
|
173
|
+
sessionFile: job.sessionFile,
|
|
174
|
+
cwd: job.cwd,
|
|
175
|
+
})),
|
|
176
|
+
});
|
|
177
|
+
}
|
|
40
178
|
const AwaitSubagentParams = Type.Object({
|
|
41
179
|
jobs: Type.Optional(Type.Array(Type.String(), {
|
|
42
180
|
description: "Subagent job IDs to wait for. Omit to wait for the next running background subagent.",
|
|
@@ -47,35 +185,7 @@ const AwaitSubagentParams = Type.Object({
|
|
|
47
185
|
})),
|
|
48
186
|
});
|
|
49
187
|
export async function stopLiveSubagents() {
|
|
50
|
-
|
|
51
|
-
if (active.length === 0)
|
|
52
|
-
return;
|
|
53
|
-
for (const proc of active) {
|
|
54
|
-
try {
|
|
55
|
-
proc.kill("SIGTERM");
|
|
56
|
-
}
|
|
57
|
-
catch {
|
|
58
|
-
/* ignore */
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
await Promise.all(active.map((proc) => new Promise((resolve) => {
|
|
62
|
-
const done = () => resolve();
|
|
63
|
-
const timer = setTimeout(done, 500);
|
|
64
|
-
proc.once("exit", () => {
|
|
65
|
-
clearTimeout(timer);
|
|
66
|
-
resolve();
|
|
67
|
-
});
|
|
68
|
-
})));
|
|
69
|
-
for (const proc of active) {
|
|
70
|
-
if (proc.exitCode === null) {
|
|
71
|
-
try {
|
|
72
|
-
proc.kill("SIGKILL");
|
|
73
|
-
}
|
|
74
|
-
catch {
|
|
75
|
-
/* ignore */
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
188
|
+
await stopLegacySubagents();
|
|
79
189
|
}
|
|
80
190
|
function formatBackgroundSubagentResults(jobs) {
|
|
81
191
|
if (jobs.length === 0)
|
|
@@ -293,308 +403,6 @@ async function mapWithConcurrencyLimit(items, concurrency, fn) {
|
|
|
293
403
|
await Promise.all(workers);
|
|
294
404
|
return results;
|
|
295
405
|
}
|
|
296
|
-
function writePromptToTempFile(agentName, prompt) {
|
|
297
|
-
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagent-"));
|
|
298
|
-
const safeName = agentName.replace(/[^\w.-]+/g, "_");
|
|
299
|
-
const filePath = path.join(tmpDir, `prompt-${safeName}.md`);
|
|
300
|
-
fs.writeFileSync(filePath, prompt, { encoding: "utf-8", mode: 0o600 });
|
|
301
|
-
return { dir: tmpDir, filePath };
|
|
302
|
-
}
|
|
303
|
-
function readBudgetSubagentModelFromSettings() {
|
|
304
|
-
try {
|
|
305
|
-
const settingsPath = path.join(getAgentDir(), "settings.json");
|
|
306
|
-
if (!fs.existsSync(settingsPath))
|
|
307
|
-
return undefined;
|
|
308
|
-
const raw = fs.readFileSync(settingsPath, "utf-8");
|
|
309
|
-
const parsed = JSON.parse(raw);
|
|
310
|
-
return typeof parsed.budgetSubagentModel === "string"
|
|
311
|
-
? normalizeSubagentModel(parsed.budgetSubagentModel)
|
|
312
|
-
: undefined;
|
|
313
|
-
}
|
|
314
|
-
catch {
|
|
315
|
-
return undefined;
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
function resolveSubagentCliPath(defaultCwd) {
|
|
319
|
-
const candidates = [process.env.GSD_BIN_PATH, process.env.LSD_BIN_PATH, process.argv[1]]
|
|
320
|
-
.map((value) => value?.trim())
|
|
321
|
-
.filter((value) => Boolean(value && value !== "undefined"));
|
|
322
|
-
for (const candidate of candidates) {
|
|
323
|
-
if (path.isAbsolute(candidate) && fs.existsSync(candidate))
|
|
324
|
-
return candidate;
|
|
325
|
-
}
|
|
326
|
-
const cwdCandidates = [path.join(defaultCwd, "dist", "loader.js"), path.join(defaultCwd, "scripts", "dev-cli.js")];
|
|
327
|
-
for (const candidate of cwdCandidates) {
|
|
328
|
-
if (fs.existsSync(candidate))
|
|
329
|
-
return candidate;
|
|
330
|
-
}
|
|
331
|
-
for (const binName of ["lsd", "gsd"]) {
|
|
332
|
-
try {
|
|
333
|
-
const resolved = execFileSync("which", [binName], { encoding: "utf-8" }).trim();
|
|
334
|
-
if (resolved)
|
|
335
|
-
return resolved;
|
|
336
|
-
}
|
|
337
|
-
catch {
|
|
338
|
-
/* ignore */
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
return null;
|
|
342
|
-
}
|
|
343
|
-
function processSubagentEventLine(line, currentResult, emitUpdate, proc) {
|
|
344
|
-
if (!line.trim())
|
|
345
|
-
return false;
|
|
346
|
-
let event;
|
|
347
|
-
try {
|
|
348
|
-
event = JSON.parse(line);
|
|
349
|
-
}
|
|
350
|
-
catch {
|
|
351
|
-
return false;
|
|
352
|
-
}
|
|
353
|
-
if (proc && isSubagentPermissionRequest(event)) {
|
|
354
|
-
void handleSubagentPermissionRequest(event, proc);
|
|
355
|
-
return false;
|
|
356
|
-
}
|
|
357
|
-
if ((event.type === "message_end" || event.type === "turn_end") && event.message) {
|
|
358
|
-
const msg = event.message;
|
|
359
|
-
currentResult.messages.push(msg);
|
|
360
|
-
if (msg.role === "assistant") {
|
|
361
|
-
currentResult.usage.turns++;
|
|
362
|
-
const usage = msg.usage;
|
|
363
|
-
if (usage) {
|
|
364
|
-
currentResult.usage.input += usage.input || 0;
|
|
365
|
-
currentResult.usage.output += usage.output || 0;
|
|
366
|
-
currentResult.usage.cacheRead += usage.cacheRead || 0;
|
|
367
|
-
currentResult.usage.cacheWrite += usage.cacheWrite || 0;
|
|
368
|
-
currentResult.usage.cost += usage.cost?.total || 0;
|
|
369
|
-
currentResult.usage.contextTokens = usage.totalTokens || 0;
|
|
370
|
-
}
|
|
371
|
-
if (msg.model && (!currentResult.model || msg.model.includes("/")))
|
|
372
|
-
currentResult.model = msg.model;
|
|
373
|
-
if (msg.stopReason)
|
|
374
|
-
currentResult.stopReason = msg.stopReason;
|
|
375
|
-
if (msg.errorMessage)
|
|
376
|
-
currentResult.errorMessage = msg.errorMessage;
|
|
377
|
-
}
|
|
378
|
-
emitUpdate();
|
|
379
|
-
}
|
|
380
|
-
if (event.type === "tool_result_end" && event.message) {
|
|
381
|
-
currentResult.messages.push(event.message);
|
|
382
|
-
emitUpdate();
|
|
383
|
-
}
|
|
384
|
-
return event.type === "agent_end";
|
|
385
|
-
}
|
|
386
|
-
async function waitForFile(filePath, signal, timeoutMs = 30 * 60 * 1000) {
|
|
387
|
-
const started = Date.now();
|
|
388
|
-
while (Date.now() - started < timeoutMs) {
|
|
389
|
-
if (signal?.aborted)
|
|
390
|
-
return false;
|
|
391
|
-
if (fs.existsSync(filePath))
|
|
392
|
-
return true;
|
|
393
|
-
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
394
|
-
}
|
|
395
|
-
return false;
|
|
396
|
-
}
|
|
397
|
-
async function runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, modelOverride, parentModel, signal, onUpdate, makeDetails, foregroundHooks) {
|
|
398
|
-
const agent = agents.find((a) => a.name === agentName);
|
|
399
|
-
if (!agent) {
|
|
400
|
-
const available = agents.map((a) => `"${a.name}"`).join(", ") || "none";
|
|
401
|
-
return {
|
|
402
|
-
agent: agentName,
|
|
403
|
-
agentSource: "unknown",
|
|
404
|
-
task,
|
|
405
|
-
exitCode: 1,
|
|
406
|
-
messages: [],
|
|
407
|
-
stderr: `Unknown agent: "${agentName}". Available agents: ${available}.`,
|
|
408
|
-
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 },
|
|
409
|
-
step,
|
|
410
|
-
};
|
|
411
|
-
}
|
|
412
|
-
let tmpPromptDir = null;
|
|
413
|
-
let tmpPromptPath = null;
|
|
414
|
-
const preferences = loadEffectivePreferences()?.preferences;
|
|
415
|
-
const settingsBudgetModel = readBudgetSubagentModelFromSettings();
|
|
416
|
-
const resolvedModel = resolveConfiguredSubagentModel(agent, preferences, settingsBudgetModel);
|
|
417
|
-
const inferredModel = resolveSubagentModel({ name: agent.name, model: resolvedModel }, { overrideModel: modelOverride, parentModel });
|
|
418
|
-
const currentResult = {
|
|
419
|
-
agent: agentName,
|
|
420
|
-
agentSource: agent.source,
|
|
421
|
-
task,
|
|
422
|
-
exitCode: 0,
|
|
423
|
-
messages: [],
|
|
424
|
-
stderr: "",
|
|
425
|
-
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 },
|
|
426
|
-
model: inferredModel,
|
|
427
|
-
step,
|
|
428
|
-
};
|
|
429
|
-
const emitUpdate = () => {
|
|
430
|
-
if (onUpdate) {
|
|
431
|
-
onUpdate({
|
|
432
|
-
content: [{ type: "text", text: getFinalOutput(currentResult.messages) || "(running...)" }],
|
|
433
|
-
details: makeDetails([currentResult]),
|
|
434
|
-
});
|
|
435
|
-
}
|
|
436
|
-
};
|
|
437
|
-
let wasAborted = false;
|
|
438
|
-
let deferTempPromptCleanup = false;
|
|
439
|
-
let tempPromptCleanupDone = false;
|
|
440
|
-
const cleanupTempPromptFiles = () => {
|
|
441
|
-
if (tempPromptCleanupDone)
|
|
442
|
-
return;
|
|
443
|
-
tempPromptCleanupDone = true;
|
|
444
|
-
if (tmpPromptPath)
|
|
445
|
-
try {
|
|
446
|
-
fs.unlinkSync(tmpPromptPath);
|
|
447
|
-
}
|
|
448
|
-
catch {
|
|
449
|
-
/* ignore */
|
|
450
|
-
}
|
|
451
|
-
if (tmpPromptDir)
|
|
452
|
-
try {
|
|
453
|
-
fs.rmdirSync(tmpPromptDir);
|
|
454
|
-
}
|
|
455
|
-
catch {
|
|
456
|
-
/* ignore */
|
|
457
|
-
}
|
|
458
|
-
};
|
|
459
|
-
try {
|
|
460
|
-
if (agent.systemPrompt.trim()) {
|
|
461
|
-
const tmp = writePromptToTempFile(agent.name, agent.systemPrompt);
|
|
462
|
-
tmpPromptDir = tmp.dir;
|
|
463
|
-
tmpPromptPath = tmp.filePath;
|
|
464
|
-
}
|
|
465
|
-
const args = buildSubagentProcessArgs(agent, task, tmpPromptPath, inferredModel);
|
|
466
|
-
const exitCode = await new Promise((resolve) => {
|
|
467
|
-
const bundledPaths = getBundledExtensionPathsFromEnv();
|
|
468
|
-
const extensionArgs = bundledPaths.flatMap((p) => ["--extension", p]);
|
|
469
|
-
const cliPath = resolveSubagentCliPath(cwd ?? defaultCwd);
|
|
470
|
-
if (!cliPath) {
|
|
471
|
-
currentResult.stderr += "Unable to resolve LSD/GSD CLI path for subagent launch.";
|
|
472
|
-
resolve(1);
|
|
473
|
-
return;
|
|
474
|
-
}
|
|
475
|
-
const proc = spawn(process.execPath, [cliPath, ...extensionArgs, ...args], { cwd: cwd ?? defaultCwd, shell: false, stdio: ["pipe", "pipe", "pipe"] });
|
|
476
|
-
// Keep stdin open so approval/classifier responses can be proxied back
|
|
477
|
-
// into the child process. Closing it here can leave the subagent stuck
|
|
478
|
-
// after its first turn when it requests permission for a tool call.
|
|
479
|
-
liveSubagentProcesses.add(proc);
|
|
480
|
-
let buffer = "";
|
|
481
|
-
let completionSeen = false;
|
|
482
|
-
let resolved = false;
|
|
483
|
-
let foregroundReleased = false;
|
|
484
|
-
const procAbortController = new AbortController();
|
|
485
|
-
let resolveBackgroundResult;
|
|
486
|
-
let rejectBackgroundResult;
|
|
487
|
-
const backgroundResultPromise = new Promise((resolveBg, rejectBg) => {
|
|
488
|
-
resolveBackgroundResult = resolveBg;
|
|
489
|
-
rejectBackgroundResult = rejectBg;
|
|
490
|
-
});
|
|
491
|
-
const finishForeground = (code) => {
|
|
492
|
-
if (resolved)
|
|
493
|
-
return;
|
|
494
|
-
resolved = true;
|
|
495
|
-
resolve(code);
|
|
496
|
-
};
|
|
497
|
-
const adoptToBackground = (jobId) => {
|
|
498
|
-
if (resolved || foregroundReleased)
|
|
499
|
-
return false;
|
|
500
|
-
foregroundReleased = true;
|
|
501
|
-
deferTempPromptCleanup = true;
|
|
502
|
-
currentResult.backgroundJobId = jobId;
|
|
503
|
-
finishForeground(0);
|
|
504
|
-
return true;
|
|
505
|
-
};
|
|
506
|
-
backgroundResultPromise.finally(() => {
|
|
507
|
-
if (deferTempPromptCleanup)
|
|
508
|
-
cleanupTempPromptFiles();
|
|
509
|
-
});
|
|
510
|
-
foregroundHooks?.onStart?.({
|
|
511
|
-
agentName,
|
|
512
|
-
task,
|
|
513
|
-
cwd: cwd ?? defaultCwd,
|
|
514
|
-
abortController: procAbortController,
|
|
515
|
-
resultPromise: backgroundResultPromise,
|
|
516
|
-
adoptToBackground,
|
|
517
|
-
});
|
|
518
|
-
proc.stdout.on("data", (data) => {
|
|
519
|
-
buffer += data.toString();
|
|
520
|
-
const lines = buffer.split("\n");
|
|
521
|
-
buffer = lines.pop() || "";
|
|
522
|
-
for (const line of lines) {
|
|
523
|
-
if (processSubagentEventLine(line, currentResult, emitUpdate, proc)) {
|
|
524
|
-
completionSeen = true;
|
|
525
|
-
try {
|
|
526
|
-
proc.kill("SIGTERM");
|
|
527
|
-
}
|
|
528
|
-
catch {
|
|
529
|
-
/* ignore */
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
});
|
|
534
|
-
proc.stderr.on("data", (data) => {
|
|
535
|
-
currentResult.stderr += data.toString();
|
|
536
|
-
});
|
|
537
|
-
proc.on("close", (code) => {
|
|
538
|
-
liveSubagentProcesses.delete(proc);
|
|
539
|
-
if (buffer.trim()) {
|
|
540
|
-
const completedOnFlush = processSubagentEventLine(buffer, currentResult, emitUpdate, proc);
|
|
541
|
-
completionSeen = completionSeen || completedOnFlush;
|
|
542
|
-
}
|
|
543
|
-
const finalExitCode = completionSeen && (code === null || code === 143 || code === 15) ? 0 : (code ?? 0);
|
|
544
|
-
currentResult.exitCode = finalExitCode;
|
|
545
|
-
resolveBackgroundResult?.({
|
|
546
|
-
summary: getFinalOutput(currentResult.messages),
|
|
547
|
-
stderr: currentResult.stderr,
|
|
548
|
-
exitCode: finalExitCode,
|
|
549
|
-
model: currentResult.model,
|
|
550
|
-
});
|
|
551
|
-
foregroundHooks?.onFinish?.();
|
|
552
|
-
finishForeground(finalExitCode);
|
|
553
|
-
});
|
|
554
|
-
proc.on("error", (error) => {
|
|
555
|
-
liveSubagentProcesses.delete(proc);
|
|
556
|
-
rejectBackgroundResult?.(error);
|
|
557
|
-
foregroundHooks?.onFinish?.();
|
|
558
|
-
finishForeground(1);
|
|
559
|
-
});
|
|
560
|
-
const killProc = () => {
|
|
561
|
-
wasAborted = true;
|
|
562
|
-
procAbortController.abort();
|
|
563
|
-
proc.kill("SIGTERM");
|
|
564
|
-
setTimeout(() => {
|
|
565
|
-
if (!proc.killed)
|
|
566
|
-
proc.kill("SIGKILL");
|
|
567
|
-
}, 5000);
|
|
568
|
-
};
|
|
569
|
-
if (signal) {
|
|
570
|
-
if (signal.aborted)
|
|
571
|
-
killProc();
|
|
572
|
-
else
|
|
573
|
-
signal.addEventListener("abort", killProc, { once: true });
|
|
574
|
-
}
|
|
575
|
-
if (procAbortController.signal.aborted) {
|
|
576
|
-
killProc();
|
|
577
|
-
}
|
|
578
|
-
else {
|
|
579
|
-
procAbortController.signal.addEventListener("abort", () => {
|
|
580
|
-
proc.kill("SIGTERM");
|
|
581
|
-
setTimeout(() => {
|
|
582
|
-
if (!proc.killed)
|
|
583
|
-
proc.kill("SIGKILL");
|
|
584
|
-
}, 5000);
|
|
585
|
-
}, { once: true });
|
|
586
|
-
}
|
|
587
|
-
});
|
|
588
|
-
currentResult.exitCode = exitCode;
|
|
589
|
-
if (wasAborted)
|
|
590
|
-
throw new Error("Subagent was aborted");
|
|
591
|
-
return currentResult;
|
|
592
|
-
}
|
|
593
|
-
finally {
|
|
594
|
-
if (!deferTempPromptCleanup)
|
|
595
|
-
cleanupTempPromptFiles();
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
406
|
const TaskItem = Type.Object({
|
|
599
407
|
agent: Type.String({ description: "Name of the agent to invoke" }),
|
|
600
408
|
task: Type.String({ description: "Task to delegate to the agent" }),
|
|
@@ -646,14 +454,62 @@ export default function (pi) {
|
|
|
646
454
|
const foregroundSubagentStatusKey = "foreground-subagent";
|
|
647
455
|
const foregroundSubagentHint = "Ctrl+B: move foreground subagent to background";
|
|
648
456
|
let activeForegroundSubagent = null;
|
|
457
|
+
let activeSessionFileForUi;
|
|
458
|
+
const liveStreamBufferBySession = new Map();
|
|
459
|
+
function flushLiveStream(sessionFile) {
|
|
460
|
+
const buffered = liveStreamBufferBySession.get(sessionFile);
|
|
461
|
+
if (!buffered || !buffered.trim())
|
|
462
|
+
return;
|
|
463
|
+
liveStreamBufferBySession.set(sessionFile, "");
|
|
464
|
+
pi.sendMessage({
|
|
465
|
+
customType: "live_subagent_stream",
|
|
466
|
+
content: buffered,
|
|
467
|
+
display: true,
|
|
468
|
+
}, { deliverAs: "followUp" });
|
|
469
|
+
}
|
|
470
|
+
function pushLiveStreamDelta(sessionFile, delta) {
|
|
471
|
+
const prev = liveStreamBufferBySession.get(sessionFile) ?? "";
|
|
472
|
+
const next = prev + delta;
|
|
473
|
+
liveStreamBufferBySession.set(sessionFile, next);
|
|
474
|
+
if (next.length >= 120 || next.includes("\n")) {
|
|
475
|
+
flushLiveStream(sessionFile);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
function getCurrentSessionSubagentMetadata(sessionFile) {
|
|
479
|
+
if (!sessionFile)
|
|
480
|
+
return null;
|
|
481
|
+
return readSessionHeader(sessionFile);
|
|
482
|
+
}
|
|
483
|
+
function applyCurrentSessionSubagentTools(ctx) {
|
|
484
|
+
const metadata = getCurrentSessionSubagentMetadata(ctx.sessionManager.getSessionFile());
|
|
485
|
+
if (metadata?.subagentTools && metadata.subagentTools.length > 0) {
|
|
486
|
+
ctx.setActiveTools(metadata.subagentTools);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
649
489
|
function getBgManager() {
|
|
650
490
|
if (!bgManager)
|
|
651
491
|
throw new Error("BackgroundJobManager not initialized.");
|
|
652
492
|
return bgManager;
|
|
653
493
|
}
|
|
654
|
-
pi.on("session_start", async () => {
|
|
494
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
495
|
+
activeSessionFileForUi = ctx.sessionManager.getSessionFile();
|
|
655
496
|
bgManager = new BackgroundJobManager({
|
|
656
497
|
onJobComplete: (job) => {
|
|
498
|
+
if (job.sessionFile && job.parentSessionFile) {
|
|
499
|
+
const existingParent = parentSessionByChild.get(job.sessionFile);
|
|
500
|
+
if (!existingParent) {
|
|
501
|
+
registerAgentSessionLink({
|
|
502
|
+
agentName: job.agentName,
|
|
503
|
+
task: job.task,
|
|
504
|
+
parentSessionFile: job.parentSessionFile,
|
|
505
|
+
subagentSessionFile: job.sessionFile,
|
|
506
|
+
state: job.status === "failed" ? "failed" : "completed",
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
updateAgentSessionLinkState(job.sessionFile, job.status === "failed" ? "failed" : "completed");
|
|
511
|
+
}
|
|
512
|
+
}
|
|
657
513
|
if (job.awaited)
|
|
658
514
|
return;
|
|
659
515
|
const statusEmoji = job.status === "completed" ? "✓" : job.status === "cancelled" ? "✗ cancelled" : "✗ failed";
|
|
@@ -676,22 +532,91 @@ export default function (pi) {
|
|
|
676
532
|
}, { deliverAs: "followUp" });
|
|
677
533
|
},
|
|
678
534
|
});
|
|
535
|
+
applyCurrentSessionSubagentTools(ctx);
|
|
679
536
|
});
|
|
680
|
-
pi.on("
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
537
|
+
pi.on("session_switch", async (_event, ctx) => {
|
|
538
|
+
activeSessionFileForUi = ctx.sessionManager.getSessionFile();
|
|
539
|
+
applyCurrentSessionSubagentTools(ctx);
|
|
540
|
+
});
|
|
541
|
+
pi.on("before_agent_start", async (event, ctx) => {
|
|
542
|
+
const metadata = getCurrentSessionSubagentMetadata(ctx.sessionManager.getSessionFile());
|
|
543
|
+
if (!metadata?.subagentSystemPrompt)
|
|
544
|
+
return;
|
|
545
|
+
const subagentName = metadata.subagentName ?? "subagent";
|
|
546
|
+
const taskNote = metadata.subagentTask
|
|
547
|
+
? `Original delegated task: ${metadata.subagentTask}`
|
|
548
|
+
: "Continue operating as the delegated subagent for this session.";
|
|
549
|
+
const antiRecursion = [
|
|
550
|
+
`You are already the ${subagentName} subagent for this session.`,
|
|
551
|
+
"Do not spawn or delegate to another subagent with the same name as yourself.",
|
|
552
|
+
`If the user asks you to continue ${subagentName} work, do that work directly in this session.`,
|
|
553
|
+
taskNote,
|
|
554
|
+
].join("\n");
|
|
555
|
+
return {
|
|
556
|
+
systemPrompt: `${event.systemPrompt}\n\n${antiRecursion}\n\n${metadata.subagentSystemPrompt}`,
|
|
557
|
+
};
|
|
558
|
+
});
|
|
559
|
+
pi.on("input", async (event, ctx) => {
|
|
560
|
+
const sessionFile = ctx.sessionManager.getSessionFile();
|
|
561
|
+
const runtimeFromSession = sessionFile ? liveRuntimeBySessionFile.get(sessionFile) : undefined;
|
|
562
|
+
const runtimeFromForeground = (!runtimeFromSession && activeForegroundSubagent && !activeForegroundSubagent.claimed &&
|
|
563
|
+
activeForegroundSubagent.sendPrompt && activeForegroundSubagent.sendSteer && activeForegroundSubagent.sendFollowUp && activeForegroundSubagent.isBusy &&
|
|
564
|
+
sessionFile && activeForegroundSubagent.parentSessionFile === sessionFile)
|
|
565
|
+
? {
|
|
566
|
+
agentName: activeForegroundSubagent.agentName,
|
|
567
|
+
isBusy: activeForegroundSubagent.isBusy,
|
|
568
|
+
sendPrompt: activeForegroundSubagent.sendPrompt,
|
|
569
|
+
sendSteer: activeForegroundSubagent.sendSteer,
|
|
570
|
+
sendFollowUp: activeForegroundSubagent.sendFollowUp,
|
|
685
571
|
}
|
|
572
|
+
: undefined;
|
|
573
|
+
const runtime = runtimeFromSession ?? runtimeFromForeground;
|
|
574
|
+
if (!runtime)
|
|
575
|
+
return;
|
|
576
|
+
const text = event.text?.trim();
|
|
577
|
+
if (!text)
|
|
578
|
+
return { action: "handled" };
|
|
579
|
+
const isSlashCommand = text.startsWith("/");
|
|
580
|
+
if (isSlashCommand)
|
|
581
|
+
return;
|
|
582
|
+
try {
|
|
583
|
+
if (runtime.isBusy()) {
|
|
584
|
+
await runtime.sendSteer(text, event.images);
|
|
585
|
+
ctx.ui.notify(`Sent steer to running subagent ${runtime.agentName}.`, "info");
|
|
586
|
+
}
|
|
587
|
+
else {
|
|
588
|
+
await runtime.sendPrompt(text, event.images);
|
|
589
|
+
ctx.ui.notify(`Sent prompt to live subagent ${runtime.agentName}.`, "info");
|
|
590
|
+
}
|
|
591
|
+
return { action: "handled" };
|
|
686
592
|
}
|
|
593
|
+
catch (error) {
|
|
594
|
+
ctx.ui.notify(`Failed to send input to live subagent ${runtime.agentName}: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
595
|
+
return { action: "handled" };
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
pi.on("session_before_switch", async () => {
|
|
599
|
+
if (activeSessionFileForUi)
|
|
600
|
+
flushLiveStream(activeSessionFileForUi);
|
|
601
|
+
activeForegroundSubagent = null;
|
|
687
602
|
});
|
|
688
603
|
pi.on("session_shutdown", async () => {
|
|
604
|
+
if (activeSessionFileForUi)
|
|
605
|
+
flushLiveStream(activeSessionFileForUi);
|
|
606
|
+
activeSessionFileForUi = undefined;
|
|
689
607
|
activeForegroundSubagent = null;
|
|
690
608
|
await stopLiveSubagents();
|
|
691
609
|
if (bgManager) {
|
|
692
610
|
bgManager.shutdown();
|
|
693
611
|
bgManager = null;
|
|
694
612
|
}
|
|
613
|
+
agentSessionLinksById.clear();
|
|
614
|
+
agentSessionIdsByParent.clear();
|
|
615
|
+
parentSessionByChild.clear();
|
|
616
|
+
liveRuntimeBySessionFile.clear();
|
|
617
|
+
inProcessSubagentDepthBySessionId.clear();
|
|
618
|
+
inProcessSubagentAncestryBySessionId.clear();
|
|
619
|
+
liveStreamBufferBySession.clear();
|
|
695
620
|
});
|
|
696
621
|
// /subagents command
|
|
697
622
|
pi.registerCommand("subagents", {
|
|
@@ -795,6 +720,166 @@ export default function (pi) {
|
|
|
795
720
|
ctx.ui.notify(`Available agents (${discovery.agents.length}):\n${lines.join("\n")}`, "info");
|
|
796
721
|
},
|
|
797
722
|
});
|
|
723
|
+
// /agent command - switch to the parent or a tracked subagent session
|
|
724
|
+
pi.registerCommand("agent", {
|
|
725
|
+
description: "Switch focus to parent/subagent sessions (/agent picker, /agent <id|index|name>, /agent parent)",
|
|
726
|
+
handler: async (args, ctx) => {
|
|
727
|
+
const currentSessionFile = ctx.sessionManager.getSessionFile();
|
|
728
|
+
if (!currentSessionFile) {
|
|
729
|
+
ctx.ui.notify("Current session is in-memory only; /agent requires a persisted session file.", "warning");
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
const arg = args.trim();
|
|
733
|
+
const parentSessionFile = parentSessionByChild.get(currentSessionFile);
|
|
734
|
+
const currentParent = parentSessionFile ?? currentSessionFile;
|
|
735
|
+
const currentSessionDir = path.dirname(currentParent);
|
|
736
|
+
let tracked = getAgentSessionLinksForParent(currentParent).filter((entry) => fs.existsSync(entry.subagentSessionFile));
|
|
737
|
+
if (tracked.length === 0) {
|
|
738
|
+
tracked = backfillAgentSessionLinksForParent(currentParent, currentSessionDir)
|
|
739
|
+
.filter((entry) => fs.existsSync(entry.subagentSessionFile));
|
|
740
|
+
}
|
|
741
|
+
const runningJobs = bgManager?.getRunningJobs() ?? [];
|
|
742
|
+
const switchTargets = buildSwitchTargetsForParent(currentParent, currentSessionFile, ctx.cwd, tracked, runningJobs);
|
|
743
|
+
const applySwitchTarget = async (target) => {
|
|
744
|
+
if (target.selectionAction === "blocked") {
|
|
745
|
+
ctx.ui.notify(target.blockedReason ?? "That target cannot be selected yet.", "warning");
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
if (target.selectionAction === "attach_live") {
|
|
749
|
+
if (!fs.existsSync(target.sessionFile)) {
|
|
750
|
+
ctx.ui.notify(`Live subagent session file is missing: ${target.sessionFile}`, "error");
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
const liveRuntime = liveRuntimeBySessionFile.get(target.sessionFile);
|
|
754
|
+
if (!liveRuntime) {
|
|
755
|
+
ctx.ui.notify("Live runtime is no longer available for this subagent. It may have completed.", "warning");
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
const switched = await ctx.switchSession(target.sessionFile);
|
|
759
|
+
if (switched.cancelled) {
|
|
760
|
+
ctx.ui.notify("Session switch was cancelled.", "warning");
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
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");
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
if (target.kind === "parent") {
|
|
767
|
+
if (!fs.existsSync(target.sessionFile)) {
|
|
768
|
+
ctx.ui.notify(`Parent session file not found: ${target.sessionFile}`, "error");
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
const switched = await ctx.switchSession(target.sessionFile);
|
|
772
|
+
if (switched.cancelled) {
|
|
773
|
+
ctx.ui.notify("Session switch was cancelled.", "warning");
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
ctx.ui.notify("Switched to parent session.", "info");
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
if (!fs.existsSync(target.sessionFile)) {
|
|
780
|
+
ctx.ui.notify(`Subagent session file is missing: ${target.sessionFile}`, "error");
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
const switched = await ctx.switchSession(target.sessionFile);
|
|
784
|
+
if (switched.cancelled) {
|
|
785
|
+
ctx.ui.notify("Session switch was cancelled.", "warning");
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
updateAgentSessionLinkState(target.sessionFile, target.state === "failed" ? "failed" : "completed");
|
|
789
|
+
ctx.ui.notify(`Switched to subagent ${target.agentName}. This resumes the saved subagent session; use /agent parent to return.`, "info");
|
|
790
|
+
};
|
|
791
|
+
if (!arg) {
|
|
792
|
+
const subagentTargets = switchTargets.filter((target) => target.kind === "subagent");
|
|
793
|
+
if (ctx.hasUI) {
|
|
794
|
+
if (subagentTargets.length === 0 && !parentSessionFile) {
|
|
795
|
+
ctx.ui.notify("No tracked subagent sessions for this parent session yet. Run a single-mode subagent first (foreground or background).", "info");
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
const selected = await showAgentSwitcher(ctx, switchTargets);
|
|
799
|
+
if (!selected)
|
|
800
|
+
return;
|
|
801
|
+
await applySwitchTarget(selected);
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
if (subagentTargets.length === 0 && !parentSessionFile) {
|
|
805
|
+
ctx.ui.notify("No tracked subagent sessions for this parent session yet. Run a single-mode subagent first (foreground or background).", "info");
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
const lines = ["Agent switch targets:"];
|
|
809
|
+
switchTargets.forEach((target, index) => {
|
|
810
|
+
lines.push(`${index + 1}. ${formatSwitchTargetSummary(target)}`);
|
|
811
|
+
});
|
|
812
|
+
lines.push("", "Use `/agent <index|id|name>` for explicit targeting, or `/agent parent`.");
|
|
813
|
+
ctx.ui.notify(lines.join("\n"), "info");
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
if (arg === "parent" || arg === "main") {
|
|
817
|
+
if (!parentSessionFile) {
|
|
818
|
+
ctx.ui.notify("You are already in the parent/main session.", "info");
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
if (!fs.existsSync(parentSessionFile)) {
|
|
822
|
+
ctx.ui.notify(`Parent session file not found: ${parentSessionFile}`, "error");
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
const switched = await ctx.switchSession(parentSessionFile);
|
|
826
|
+
if (switched.cancelled) {
|
|
827
|
+
ctx.ui.notify("Session switch was cancelled.", "warning");
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
ctx.ui.notify("Switched to parent session.", "info");
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
let target;
|
|
834
|
+
if (/^\d+$/.test(arg)) {
|
|
835
|
+
const index = Number.parseInt(arg, 10) - 1;
|
|
836
|
+
target = tracked[index];
|
|
837
|
+
}
|
|
838
|
+
if (!target) {
|
|
839
|
+
target = tracked.find((entry) => entry.id === arg);
|
|
840
|
+
}
|
|
841
|
+
if (!target) {
|
|
842
|
+
target = tracked.find((entry) => entry.agentName === arg);
|
|
843
|
+
}
|
|
844
|
+
if (!target) {
|
|
845
|
+
target = tracked.find((entry) => path.basename(entry.subagentSessionFile) === arg);
|
|
846
|
+
}
|
|
847
|
+
if (!target) {
|
|
848
|
+
const runningTarget = switchTargets.find((entry) => entry.id === arg && entry.kind === "subagent");
|
|
849
|
+
if (runningTarget?.state === "running") {
|
|
850
|
+
ctx.ui.notify(runningTarget.blockedReason ?? "Selected subagent is still running. Live attach is not implemented yet.", "warning");
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
ctx.ui.notify(`Unknown subagent target: ${arg}. Run /agent to list available targets.`, "warning");
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
if (!fs.existsSync(target.subagentSessionFile)) {
|
|
857
|
+
ctx.ui.notify(`Subagent session file is missing: ${target.subagentSessionFile}`, "error");
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
if (target.state === "running") {
|
|
861
|
+
const liveRuntime = liveRuntimeBySessionFile.get(target.subagentSessionFile);
|
|
862
|
+
if (!liveRuntime) {
|
|
863
|
+
ctx.ui.notify("Live runtime is no longer available for this subagent. It may have completed.", "warning");
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
const switched = await ctx.switchSession(target.subagentSessionFile);
|
|
867
|
+
if (switched.cancelled) {
|
|
868
|
+
ctx.ui.notify("Session switch was cancelled.", "warning");
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
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");
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
const switched = await ctx.switchSession(target.subagentSessionFile);
|
|
875
|
+
if (switched.cancelled) {
|
|
876
|
+
ctx.ui.notify("Session switch was cancelled.", "warning");
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
updateAgentSessionLinkState(target.subagentSessionFile, target.state === "failed" ? "failed" : "completed");
|
|
880
|
+
ctx.ui.notify(`Switched to subagent ${target.agentName}. This resumes the saved subagent session; use /agent parent to return.`, "info");
|
|
881
|
+
},
|
|
882
|
+
});
|
|
798
883
|
pi.registerShortcut(Key.ctrl("b"), {
|
|
799
884
|
description: shortcutDesc("Move foreground subagent to background", "/subagents list"),
|
|
800
885
|
handler: async (ctx) => {
|
|
@@ -809,7 +894,19 @@ export default function (pi) {
|
|
|
809
894
|
running.claimed = true;
|
|
810
895
|
let jobId;
|
|
811
896
|
try {
|
|
812
|
-
|
|
897
|
+
if (running.handle) {
|
|
898
|
+
jobId = manager.adoptHandle(running.agentName, running.task, running.cwd, running.handle, running.resultPromise, {
|
|
899
|
+
parentSessionFile: running.parentSessionFile ?? ctx.sessionManager.getSessionFile(),
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
else if (running.abortController) {
|
|
903
|
+
jobId = manager.adoptRunning(running.agentName, running.task, running.cwd, running.abortController, running.resultPromise, {
|
|
904
|
+
parentSessionFile: running.parentSessionFile ?? ctx.sessionManager.getSessionFile(),
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
else {
|
|
908
|
+
throw new Error("Foreground subagent cannot be moved to background (missing runtime handle).");
|
|
909
|
+
}
|
|
813
910
|
}
|
|
814
911
|
catch (error) {
|
|
815
912
|
running.claimed = false;
|
|
@@ -884,6 +981,15 @@ export default function (pi) {
|
|
|
884
981
|
const confirmProjectAgents = params.confirmProjectAgents ?? false;
|
|
885
982
|
const cmuxClient = CmuxClient.fromPreferences(loadEffectivePreferences()?.preferences);
|
|
886
983
|
const cmuxSplitsEnabled = cmuxClient.getConfig().splits;
|
|
984
|
+
const invokingSessionFile = ctx.sessionManager.getSessionFile();
|
|
985
|
+
const invokingSessionId = ctx.sessionManager.getSessionId();
|
|
986
|
+
const invokingMetadata = getCurrentSessionSubagentMetadata(invokingSessionFile);
|
|
987
|
+
const currentSubagentName = invokingMetadata?.subagentName;
|
|
988
|
+
const trackedAncestry = inProcessSubagentAncestryBySessionId.get(invokingSessionId);
|
|
989
|
+
const currentAncestry = trackedAncestry ?? (currentSubagentName ? [currentSubagentName] : []);
|
|
990
|
+
const inferredCurrentDepth = currentSubagentName ? Math.max(currentAncestry.length, 1) : 0;
|
|
991
|
+
const currentSubagentDepth = Math.max(inProcessSubagentDepthBySessionId.get(invokingSessionId) ?? 0, inferredCurrentDepth);
|
|
992
|
+
const nextSubagentDepth = currentSubagentDepth + 1;
|
|
887
993
|
// Resolve isolation mode
|
|
888
994
|
const isolationMode = readIsolationMode();
|
|
889
995
|
const useIsolation = Boolean(params.isolated) && isolationMode !== "none";
|
|
@@ -897,6 +1003,30 @@ export default function (pi) {
|
|
|
897
1003
|
projectAgentsDir: discovery.projectAgentsDir,
|
|
898
1004
|
results,
|
|
899
1005
|
});
|
|
1006
|
+
const trackInProcessDepth = (started, depth, ancestry) => {
|
|
1007
|
+
const sessionId = started.handle.sessionId;
|
|
1008
|
+
if (!sessionId)
|
|
1009
|
+
return;
|
|
1010
|
+
inProcessSubagentDepthBySessionId.set(sessionId, depth);
|
|
1011
|
+
inProcessSubagentAncestryBySessionId.set(sessionId, ancestry);
|
|
1012
|
+
void started.resultPromise.finally(() => {
|
|
1013
|
+
inProcessSubagentDepthBySessionId.delete(sessionId);
|
|
1014
|
+
inProcessSubagentAncestryBySessionId.delete(sessionId);
|
|
1015
|
+
});
|
|
1016
|
+
};
|
|
1017
|
+
const buildChildAncestry = (childAgentName) => [...currentAncestry, childAgentName];
|
|
1018
|
+
const requestedAgentNames = hasSingle
|
|
1019
|
+
? [params.agent]
|
|
1020
|
+
: hasChain
|
|
1021
|
+
? (params.chain ?? []).map((step) => step.agent)
|
|
1022
|
+
: (params.tasks ?? []).map((task) => task.agent);
|
|
1023
|
+
if (currentSubagentName && requestedAgentNames.some((name) => name === currentSubagentName)) {
|
|
1024
|
+
return {
|
|
1025
|
+
content: [{ type: "text", text: `Subagent "${currentSubagentName}" cannot spawn another subagent with the same name as itself.` }],
|
|
1026
|
+
details: makeDetails(hasChain ? "chain" : hasTasks ? "parallel" : "single")([]),
|
|
1027
|
+
isError: true,
|
|
1028
|
+
};
|
|
1029
|
+
}
|
|
900
1030
|
if (modeCount !== 1) {
|
|
901
1031
|
const available = agents.map((a) => `${a.name} (${a.source})`).join(", ") || "none";
|
|
902
1032
|
return {
|
|
@@ -916,6 +1046,20 @@ export default function (pi) {
|
|
|
916
1046
|
isError: true,
|
|
917
1047
|
};
|
|
918
1048
|
}
|
|
1049
|
+
if (params.background && currentSubagentDepth > 0) {
|
|
1050
|
+
return {
|
|
1051
|
+
content: [{ type: "text", text: "Nested background subagent launches are not supported yet. Run the nested subagent in foreground mode." }],
|
|
1052
|
+
details: makeDetails("single")([]),
|
|
1053
|
+
isError: true,
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
if (USE_IN_PROCESS_SUBAGENT && nextSubagentDepth > MAX_IN_PROCESS_SUBAGENT_DEPTH) {
|
|
1057
|
+
return {
|
|
1058
|
+
content: [{ type: "text", text: `Max subagent depth (${MAX_IN_PROCESS_SUBAGENT_DEPTH}) exceeded.` }],
|
|
1059
|
+
details: makeDetails(hasChain ? "chain" : hasTasks ? "parallel" : "single")([]),
|
|
1060
|
+
isError: true,
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
919
1063
|
if ((agentScope === "project" || agentScope === "both") && confirmProjectAgents && ctx.hasUI) {
|
|
920
1064
|
const requestedAgentNames = new Set();
|
|
921
1065
|
if (params.chain)
|
|
@@ -960,7 +1104,13 @@ export default function (pi) {
|
|
|
960
1104
|
}
|
|
961
1105
|
}
|
|
962
1106
|
: undefined;
|
|
963
|
-
const result =
|
|
1107
|
+
const result = USE_IN_PROCESS_SUBAGENT
|
|
1108
|
+
? await (async () => {
|
|
1109
|
+
const started = await startInProcessSingleAgent(ctx.cwd, agents, step.agent, taskWithContext, step.cwd, i + 1, step.model, ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined, signal, chainUpdate, makeDetails("chain"), invokingSessionFile, nextSubagentDepth, invokingSessionId, currentAncestry);
|
|
1110
|
+
trackInProcessDepth(started, nextSubagentDepth, buildChildAncestry(step.agent));
|
|
1111
|
+
return started.resultPromise;
|
|
1112
|
+
})()
|
|
1113
|
+
: await runLegacySingleAgent(ctx.cwd, agents, step.agent, taskWithContext, step.cwd, i + 1, step.model, ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined, signal, chainUpdate, makeDetails("chain"), invokingSessionFile, false, undefined, undefined);
|
|
964
1114
|
results.push(result);
|
|
965
1115
|
const isError = result.exitCode !== 0 || result.stopReason === "error" || result.stopReason === "aborted";
|
|
966
1116
|
if (isError) {
|
|
@@ -1022,12 +1172,23 @@ export default function (pi) {
|
|
|
1022
1172
|
const gridSurfaces = [];
|
|
1023
1173
|
const results = await mapWithConcurrencyLimit(params.tasks, MAX_CONCURRENCY, async (t, index) => {
|
|
1024
1174
|
const workerId = registerWorker(t.agent, t.task, index, batchSize, batchId);
|
|
1025
|
-
const runTask = () =>
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1175
|
+
const runTask = () => USE_IN_PROCESS_SUBAGENT
|
|
1176
|
+
? (async () => {
|
|
1177
|
+
const started = await startInProcessSingleAgent(ctx.cwd, agents, t.agent, t.task, t.cwd, undefined, t.model, ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined, signal, (partial) => {
|
|
1178
|
+
if (partial.details?.results[0]) {
|
|
1179
|
+
allResults[index] = partial.details.results[0];
|
|
1180
|
+
emitParallelUpdate();
|
|
1181
|
+
}
|
|
1182
|
+
}, makeDetails("parallel"), invokingSessionFile, nextSubagentDepth, invokingSessionId, currentAncestry);
|
|
1183
|
+
trackInProcessDepth(started, nextSubagentDepth, buildChildAncestry(t.agent));
|
|
1184
|
+
return started.resultPromise;
|
|
1185
|
+
})()
|
|
1186
|
+
: runLegacySingleAgent(ctx.cwd, agents, t.agent, t.task, t.cwd, undefined, t.model, ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined, signal, (partial) => {
|
|
1187
|
+
if (partial.details?.results[0]) {
|
|
1188
|
+
allResults[index] = partial.details.results[0];
|
|
1189
|
+
emitParallelUpdate();
|
|
1190
|
+
}
|
|
1191
|
+
}, makeDetails("parallel"), invokingSessionFile, false, undefined);
|
|
1031
1192
|
let result = await runTask();
|
|
1032
1193
|
// Auto-retry failed tasks (likely API rate limit or transient error)
|
|
1033
1194
|
const isFailed = result.exitCode !== 0 || (result.messages.length === 0 && !signal?.aborted);
|
|
@@ -1084,16 +1245,81 @@ export default function (pi) {
|
|
|
1084
1245
|
const bgInferredModel = resolveSubagentModel({ name: agentForBg.name, model: bgResolvedModelCfg }, { overrideModel: params.model, parentModel: ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined });
|
|
1085
1246
|
let jobId;
|
|
1086
1247
|
try {
|
|
1087
|
-
|
|
1088
|
-
const
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1248
|
+
if (USE_IN_PROCESS_SUBAGENT) {
|
|
1249
|
+
const started = await startInProcessSingleAgent(ctx.cwd, agents, params.agent, params.task, params.cwd, undefined, params.model, ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined, undefined, undefined, makeDetails("single"), invokingSessionFile, nextSubagentDepth, invokingSessionId, currentAncestry);
|
|
1250
|
+
trackInProcessDepth(started, nextSubagentDepth, buildChildAncestry(params.agent));
|
|
1251
|
+
const effectiveCwd = params.cwd ?? ctx.cwd;
|
|
1252
|
+
jobId = manager.adoptHandle(params.agent, params.task, effectiveCwd, started.handle, started.resultPromise.then((result) => ({
|
|
1253
|
+
summary: (getFinalOutput(result.messages) || "(no output)").slice(0, 300),
|
|
1093
1254
|
stderr: result.stderr,
|
|
1255
|
+
exitCode: result.exitCode,
|
|
1094
1256
|
model: result.model,
|
|
1095
|
-
|
|
1096
|
-
|
|
1257
|
+
sessionFile: result.sessionFile,
|
|
1258
|
+
parentSessionFile: result.parentSessionFile,
|
|
1259
|
+
})), {
|
|
1260
|
+
parentSessionFile: invokingSessionFile,
|
|
1261
|
+
model: bgInferredModel,
|
|
1262
|
+
});
|
|
1263
|
+
}
|
|
1264
|
+
else {
|
|
1265
|
+
jobId = runSubagentInBackground(manager, agents, params.agent, params.task, params.cwd, params.model, { defaultCwd: ctx.cwd, model: ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined, parentSessionFile: invokingSessionFile }, async (bgSignal) => {
|
|
1266
|
+
let liveSessionFile;
|
|
1267
|
+
let liveRuntime;
|
|
1268
|
+
const result = await runLegacySingleAgent(ctx.cwd, agents, params.agent, params.task, params.cwd, undefined, params.model, ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined, bgSignal, undefined, // no streaming updates for background jobs
|
|
1269
|
+
makeDetails("single"), invokingSessionFile, true, (info) => {
|
|
1270
|
+
if (!invokingSessionFile || !info.sessionFile)
|
|
1271
|
+
return;
|
|
1272
|
+
upsertAgentSessionLink(params.agent, params.task, invokingSessionFile, info.sessionFile, "running");
|
|
1273
|
+
liveSessionFile = info.sessionFile;
|
|
1274
|
+
if (liveRuntime) {
|
|
1275
|
+
liveRuntime.sessionFile = info.sessionFile;
|
|
1276
|
+
liveRuntime.parentSessionFile = info.parentSessionFile ?? invokingSessionFile;
|
|
1277
|
+
liveRuntimeBySessionFile.set(info.sessionFile, liveRuntime);
|
|
1278
|
+
}
|
|
1279
|
+
}, (event, partial) => {
|
|
1280
|
+
const sessionFile = partial.sessionFile;
|
|
1281
|
+
if (!sessionFile || activeSessionFileForUi !== sessionFile)
|
|
1282
|
+
return;
|
|
1283
|
+
if (event?.type === "message_update" && event.assistantMessageEvent?.type === "text_delta") {
|
|
1284
|
+
const delta = String(event.assistantMessageEvent.delta ?? "");
|
|
1285
|
+
if (delta)
|
|
1286
|
+
pushLiveStreamDelta(sessionFile, delta);
|
|
1287
|
+
}
|
|
1288
|
+
if (event?.type === "message_end") {
|
|
1289
|
+
flushLiveStream(sessionFile);
|
|
1290
|
+
}
|
|
1291
|
+
}, {
|
|
1292
|
+
onStart: (control) => {
|
|
1293
|
+
if (!control.sendPrompt || !control.sendSteer || !control.sendFollowUp || !control.isBusy)
|
|
1294
|
+
return;
|
|
1295
|
+
liveRuntime = {
|
|
1296
|
+
sessionFile: liveSessionFile,
|
|
1297
|
+
parentSessionFile: invokingSessionFile,
|
|
1298
|
+
agentName: params.agent,
|
|
1299
|
+
isBusy: control.isBusy,
|
|
1300
|
+
sendPrompt: control.sendPrompt,
|
|
1301
|
+
sendSteer: control.sendSteer,
|
|
1302
|
+
sendFollowUp: control.sendFollowUp,
|
|
1303
|
+
};
|
|
1304
|
+
if (liveSessionFile) {
|
|
1305
|
+
liveRuntimeBySessionFile.set(liveSessionFile, liveRuntime);
|
|
1306
|
+
}
|
|
1307
|
+
},
|
|
1308
|
+
onFinish: () => {
|
|
1309
|
+
if (liveSessionFile)
|
|
1310
|
+
liveRuntimeBySessionFile.delete(liveSessionFile);
|
|
1311
|
+
},
|
|
1312
|
+
});
|
|
1313
|
+
return {
|
|
1314
|
+
exitCode: result.exitCode,
|
|
1315
|
+
finalOutput: getFinalOutput(result.messages),
|
|
1316
|
+
stderr: result.stderr,
|
|
1317
|
+
model: result.model,
|
|
1318
|
+
sessionFile: result.sessionFile,
|
|
1319
|
+
parentSessionFile: result.parentSessionFile,
|
|
1320
|
+
};
|
|
1321
|
+
});
|
|
1322
|
+
}
|
|
1097
1323
|
}
|
|
1098
1324
|
catch (err) {
|
|
1099
1325
|
return {
|
|
@@ -1117,18 +1343,134 @@ export default function (pi) {
|
|
|
1117
1343
|
const taskId = crypto.randomUUID();
|
|
1118
1344
|
isolation = await createIsolation(effectiveCwd, taskId, isolationMode);
|
|
1119
1345
|
}
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1346
|
+
let result;
|
|
1347
|
+
if (USE_IN_PROCESS_SUBAGENT && !isolation) {
|
|
1348
|
+
const started = await startInProcessSingleAgent(ctx.cwd, agents, params.agent, params.task, params.cwd, undefined, params.model, ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined, signal, onUpdate, makeDetails("single"), invokingSessionFile, nextSubagentDepth, invokingSessionId, currentAncestry);
|
|
1349
|
+
trackInProcessDepth(started, nextSubagentDepth, buildChildAncestry(params.agent));
|
|
1350
|
+
const effectiveRunCwd = params.cwd ?? ctx.cwd;
|
|
1351
|
+
let releaseToBackground;
|
|
1352
|
+
const movedToBackground = new Promise((resolve) => {
|
|
1353
|
+
releaseToBackground = resolve;
|
|
1354
|
+
});
|
|
1355
|
+
activeForegroundSubagent = {
|
|
1356
|
+
claimed: false,
|
|
1357
|
+
agentName: params.agent,
|
|
1358
|
+
task: params.task,
|
|
1359
|
+
cwd: effectiveRunCwd,
|
|
1360
|
+
parentSessionFile: invokingSessionFile,
|
|
1361
|
+
handle: started.handle,
|
|
1362
|
+
resultPromise: started.resultPromise.then((done) => ({
|
|
1363
|
+
summary: getFinalOutput(done.messages) || "(no output)",
|
|
1364
|
+
stderr: done.stderr,
|
|
1365
|
+
exitCode: done.exitCode,
|
|
1366
|
+
model: done.model,
|
|
1367
|
+
sessionFile: done.sessionFile,
|
|
1368
|
+
parentSessionFile: done.parentSessionFile,
|
|
1369
|
+
})),
|
|
1370
|
+
adoptToBackground: (jobId) => {
|
|
1371
|
+
if (!releaseToBackground)
|
|
1372
|
+
return false;
|
|
1373
|
+
releaseToBackground(jobId);
|
|
1374
|
+
releaseToBackground = undefined;
|
|
1375
|
+
return true;
|
|
1129
1376
|
},
|
|
1377
|
+
sendPrompt: started.handle.prompt,
|
|
1378
|
+
sendSteer: started.handle.steer,
|
|
1379
|
+
sendFollowUp: started.handle.followUp,
|
|
1380
|
+
isBusy: started.handle.isBusy,
|
|
1381
|
+
};
|
|
1382
|
+
ctx.ui.setStatus(foregroundSubagentStatusKey, foregroundSubagentHint);
|
|
1383
|
+
const winner = await Promise.race([
|
|
1384
|
+
started.resultPromise.then((done) => ({ type: "done", done })),
|
|
1385
|
+
movedToBackground.then((jobId) => ({ type: "background", jobId })),
|
|
1386
|
+
]);
|
|
1387
|
+
if (winner.type === "background") {
|
|
1388
|
+
activeForegroundSubagent = null;
|
|
1389
|
+
ctx.ui.setStatus(foregroundSubagentStatusKey, undefined);
|
|
1390
|
+
result = {
|
|
1391
|
+
...started.currentResult,
|
|
1392
|
+
backgroundJobId: winner.jobId,
|
|
1393
|
+
};
|
|
1394
|
+
}
|
|
1395
|
+
else {
|
|
1396
|
+
activeForegroundSubagent = null;
|
|
1397
|
+
ctx.ui.setStatus(foregroundSubagentStatusKey, undefined);
|
|
1398
|
+
result = winner.done;
|
|
1130
1399
|
}
|
|
1131
|
-
|
|
1400
|
+
}
|
|
1401
|
+
else {
|
|
1402
|
+
let liveSessionFile;
|
|
1403
|
+
let liveRuntime;
|
|
1404
|
+
result = await runLegacySingleAgent(ctx.cwd, agents, params.agent, params.task, isolation ? isolation.workDir : params.cwd, undefined, params.model, ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined, signal, onUpdate, makeDetails("single"), invokingSessionFile, !isolation, !isolation
|
|
1405
|
+
? (info) => {
|
|
1406
|
+
if (!invokingSessionFile || !info.sessionFile)
|
|
1407
|
+
return;
|
|
1408
|
+
upsertAgentSessionLink(params.agent, params.task, invokingSessionFile, info.sessionFile, "running");
|
|
1409
|
+
liveSessionFile = info.sessionFile;
|
|
1410
|
+
if (liveRuntime) {
|
|
1411
|
+
liveRuntime.sessionFile = info.sessionFile;
|
|
1412
|
+
liveRuntime.parentSessionFile = info.parentSessionFile ?? invokingSessionFile;
|
|
1413
|
+
liveRuntimeBySessionFile.set(info.sessionFile, liveRuntime);
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
: undefined, !isolation
|
|
1417
|
+
? (event, partial) => {
|
|
1418
|
+
const sessionFile = partial.sessionFile;
|
|
1419
|
+
if (!sessionFile || activeSessionFileForUi !== sessionFile)
|
|
1420
|
+
return;
|
|
1421
|
+
if (event?.type === "message_update" && event.assistantMessageEvent?.type === "text_delta") {
|
|
1422
|
+
const delta = String(event.assistantMessageEvent.delta ?? "");
|
|
1423
|
+
if (delta)
|
|
1424
|
+
pushLiveStreamDelta(sessionFile, delta);
|
|
1425
|
+
}
|
|
1426
|
+
if (event?.type === "message_end") {
|
|
1427
|
+
flushLiveStream(sessionFile);
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
: undefined, !isolation
|
|
1431
|
+
? {
|
|
1432
|
+
onStart: (control) => {
|
|
1433
|
+
activeForegroundSubagent = { ...control, claimed: false };
|
|
1434
|
+
ctx.ui.setStatus(foregroundSubagentStatusKey, foregroundSubagentHint);
|
|
1435
|
+
if (!control.sendPrompt || !control.sendSteer || !control.sendFollowUp || !control.isBusy)
|
|
1436
|
+
return;
|
|
1437
|
+
liveRuntime = {
|
|
1438
|
+
sessionFile: liveSessionFile,
|
|
1439
|
+
parentSessionFile: invokingSessionFile,
|
|
1440
|
+
agentName: params.agent,
|
|
1441
|
+
isBusy: control.isBusy,
|
|
1442
|
+
sendPrompt: control.sendPrompt,
|
|
1443
|
+
sendSteer: control.sendSteer,
|
|
1444
|
+
sendFollowUp: control.sendFollowUp,
|
|
1445
|
+
};
|
|
1446
|
+
if (liveSessionFile && liveRuntime) {
|
|
1447
|
+
liveRuntimeBySessionFile.set(liveSessionFile, liveRuntime);
|
|
1448
|
+
}
|
|
1449
|
+
},
|
|
1450
|
+
onFinish: () => {
|
|
1451
|
+
activeForegroundSubagent = null;
|
|
1452
|
+
ctx.ui.setStatus(foregroundSubagentStatusKey, undefined);
|
|
1453
|
+
if (liveSessionFile)
|
|
1454
|
+
liveRuntimeBySessionFile.delete(liveSessionFile);
|
|
1455
|
+
},
|
|
1456
|
+
}
|
|
1457
|
+
: undefined);
|
|
1458
|
+
}
|
|
1459
|
+
if (result.sessionFile && invokingSessionFile) {
|
|
1460
|
+
const existingParent = parentSessionByChild.get(result.sessionFile);
|
|
1461
|
+
if (!existingParent) {
|
|
1462
|
+
registerAgentSessionLink({
|
|
1463
|
+
agentName: result.agent,
|
|
1464
|
+
task: result.task,
|
|
1465
|
+
parentSessionFile: invokingSessionFile,
|
|
1466
|
+
subagentSessionFile: result.sessionFile,
|
|
1467
|
+
state: result.exitCode === 0 ? "completed" : "failed",
|
|
1468
|
+
});
|
|
1469
|
+
}
|
|
1470
|
+
else {
|
|
1471
|
+
updateAgentSessionLinkState(result.sessionFile, result.exitCode === 0 ? "completed" : "failed");
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1132
1474
|
if (result.backgroundJobId) {
|
|
1133
1475
|
return {
|
|
1134
1476
|
content: [{ type: "text", text: `Moved ${result.agent} to background as **${result.backgroundJobId}**. Use \`await_subagent\`, \`/subagents wait ${result.backgroundJobId}\`, or \`/subagents output ${result.backgroundJobId}\`.` }],
|
|
@@ -1143,10 +1485,11 @@ export default function (pi) {
|
|
|
1143
1485
|
}
|
|
1144
1486
|
}
|
|
1145
1487
|
const isError = result.exitCode !== 0 || result.stopReason === "error" || result.stopReason === "aborted";
|
|
1488
|
+
const agentSwitchHint = result.sessionFile ? "\n\nTip: run `/agent` to switch focus to this subagent session." : "";
|
|
1146
1489
|
if (isError) {
|
|
1147
1490
|
const errorMsg = result.errorMessage || result.stderr || getFinalOutput(result.messages) || "(no output)";
|
|
1148
1491
|
return {
|
|
1149
|
-
content: [{ type: "text", text: `Agent ${result.stopReason || "failed"}: ${errorMsg}` }],
|
|
1492
|
+
content: [{ type: "text", text: `Agent ${result.stopReason || "failed"}: ${errorMsg}${agentSwitchHint}` }],
|
|
1150
1493
|
details: makeDetails("single")([result]),
|
|
1151
1494
|
isError: true,
|
|
1152
1495
|
};
|
|
@@ -1155,6 +1498,8 @@ export default function (pi) {
|
|
|
1155
1498
|
if (mergeResult && !mergeResult.success) {
|
|
1156
1499
|
outputText += `\n\n⚠ Patch merge failed: ${mergeResult.error || "unknown error"}`;
|
|
1157
1500
|
}
|
|
1501
|
+
if (agentSwitchHint)
|
|
1502
|
+
outputText += agentSwitchHint;
|
|
1158
1503
|
return {
|
|
1159
1504
|
content: [{ type: "text", text: outputText }],
|
|
1160
1505
|
details: makeDetails("single")([result]),
|