@qduc/term2 0.4.0 → 0.6.0
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/dist/agent.d.ts +5 -0
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +61 -8
- package/dist/agent.js.map +1 -1
- package/dist/agent.test.js +122 -10
- package/dist/agent.test.js.map +1 -1
- package/dist/app.d.ts +13 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +71 -18
- package/dist/app.js.map +1 -1
- package/dist/app.startup-banner.test.js +48 -1
- package/dist/app.startup-banner.test.js.map +1 -1
- package/dist/cli.js +197 -28
- package/dist/cli.js.map +1 -1
- package/dist/components/ApprovalPrompt.d.ts.map +1 -1
- package/dist/components/ApprovalPrompt.js +11 -34
- package/dist/components/ApprovalPrompt.js.map +1 -1
- package/dist/components/Banner.d.ts.map +1 -1
- package/dist/components/Banner.js +2 -7
- package/dist/components/Banner.js.map +1 -1
- package/dist/components/BottomArea.d.ts +7 -1
- package/dist/components/BottomArea.d.ts.map +1 -1
- package/dist/components/BottomArea.js +2 -2
- package/dist/components/BottomArea.js.map +1 -1
- package/dist/components/ChatMessage.d.ts.map +1 -1
- package/dist/components/ChatMessage.js +2 -1
- package/dist/components/ChatMessage.js.map +1 -1
- package/dist/components/CommandMessage.d.ts.map +1 -1
- package/dist/components/CommandMessage.js +45 -48
- package/dist/components/CommandMessage.js.map +1 -1
- package/dist/components/CommandMessage.test.js +69 -0
- package/dist/components/CommandMessage.test.js.map +1 -1
- package/dist/components/DiffView.d.ts +7 -0
- package/dist/components/DiffView.d.ts.map +1 -0
- package/dist/components/DiffView.js +80 -0
- package/dist/components/DiffView.js.map +1 -0
- package/dist/components/DiffView.test.d.ts +2 -0
- package/dist/components/DiffView.test.d.ts.map +1 -0
- package/dist/components/DiffView.test.js +45 -0
- package/dist/components/DiffView.test.js.map +1 -0
- package/dist/components/Input/PopupManager.d.ts +7 -0
- package/dist/components/Input/PopupManager.d.ts.map +1 -1
- package/dist/components/Input/PopupManager.js +4 -2
- package/dist/components/Input/PopupManager.js.map +1 -1
- package/dist/components/Input/determine-active-menu.d.ts.map +1 -1
- package/dist/components/Input/determine-active-menu.js +4 -1
- package/dist/components/Input/determine-active-menu.js.map +1 -1
- package/dist/components/Input/popup-props.d.ts +3 -1
- package/dist/components/Input/popup-props.d.ts.map +1 -1
- package/dist/components/Input/popup-props.js +7 -1
- package/dist/components/Input/popup-props.js.map +1 -1
- package/dist/components/InputBox.d.ts +6 -0
- package/dist/components/InputBox.d.ts.map +1 -1
- package/dist/components/InputBox.js +74 -11
- package/dist/components/InputBox.js.map +1 -1
- package/dist/components/InputBox.menu-logic.test.js +16 -1
- package/dist/components/InputBox.menu-logic.test.js.map +1 -1
- package/dist/components/InputBox.test.js +1 -0
- package/dist/components/InputBox.test.js.map +1 -1
- package/dist/components/MarkdownRenderer.d.ts.map +1 -1
- package/dist/components/MarkdownRenderer.js +65 -7
- package/dist/components/MarkdownRenderer.js.map +1 -1
- package/dist/components/MessageList.d.ts.map +1 -1
- package/dist/components/MessageList.js +62 -9
- package/dist/components/MessageList.js.map +1 -1
- package/dist/components/MessageList.test.js +128 -0
- package/dist/components/MessageList.test.js.map +1 -1
- package/dist/components/SettingsValueSelectionMenu.d.ts.map +1 -1
- package/dist/components/SettingsValueSelectionMenu.js +4 -1
- package/dist/components/SettingsValueSelectionMenu.js.map +1 -1
- package/dist/components/StatusBar.d.ts.map +1 -1
- package/dist/components/StatusBar.js +8 -6
- package/dist/components/StatusBar.js.map +1 -1
- package/dist/components/StatusBar.test.js +24 -0
- package/dist/components/StatusBar.test.js.map +1 -1
- package/dist/components/SubagentActivityMessage.d.ts +12 -0
- package/dist/components/SubagentActivityMessage.d.ts.map +1 -0
- package/dist/components/SubagentActivityMessage.js +30 -0
- package/dist/components/SubagentActivityMessage.js.map +1 -0
- package/dist/components/UndoSelectionMenu.d.ts +12 -0
- package/dist/components/UndoSelectionMenu.d.ts.map +1 -0
- package/dist/components/UndoSelectionMenu.js +46 -0
- package/dist/components/UndoSelectionMenu.js.map +1 -0
- package/dist/components/UndoSelectionMenu.test.d.ts +2 -0
- package/dist/components/UndoSelectionMenu.test.d.ts.map +1 -0
- package/dist/components/UndoSelectionMenu.test.js +40 -0
- package/dist/components/UndoSelectionMenu.test.js.map +1 -0
- package/dist/components/theme.d.ts +8 -0
- package/dist/components/theme.d.ts.map +1 -0
- package/dist/components/theme.js +20 -0
- package/dist/components/theme.js.map +1 -0
- package/dist/context/InputContext.d.ts +1 -1
- package/dist/context/InputContext.d.ts.map +1 -1
- package/dist/context/InputContext.js.map +1 -1
- package/dist/hooks/use-app-commands.d.ts +20 -2
- package/dist/hooks/use-app-commands.d.ts.map +1 -1
- package/dist/hooks/use-app-commands.js +138 -52
- package/dist/hooks/use-app-commands.js.map +1 -1
- package/dist/hooks/use-app-commands.test.js +264 -1
- package/dist/hooks/use-app-commands.test.js.map +1 -1
- package/dist/hooks/use-conversation.d.ts +20 -2
- package/dist/hooks/use-conversation.d.ts.map +1 -1
- package/dist/hooks/use-conversation.js +114 -14
- package/dist/hooks/use-conversation.js.map +1 -1
- package/dist/hooks/use-conversation.resume.test.d.ts +2 -0
- package/dist/hooks/use-conversation.resume.test.d.ts.map +1 -0
- package/dist/hooks/use-conversation.resume.test.js +26 -0
- package/dist/hooks/use-conversation.resume.test.js.map +1 -0
- package/dist/hooks/use-escape-key.d.ts.map +1 -1
- package/dist/hooks/use-escape-key.js +14 -7
- package/dist/hooks/use-escape-key.js.map +1 -1
- package/dist/hooks/use-escape-key.test.js +168 -4
- package/dist/hooks/use-escape-key.test.js.map +1 -1
- package/dist/hooks/use-input-history.d.ts +4 -3
- package/dist/hooks/use-input-history.d.ts.map +1 -1
- package/dist/hooks/use-input-history.js +10 -8
- package/dist/hooks/use-input-history.js.map +1 -1
- package/dist/hooks/use-input-history.test.d.ts +2 -0
- package/dist/hooks/use-input-history.test.d.ts.map +1 -0
- package/dist/hooks/use-input-history.test.js +43 -0
- package/dist/hooks/use-input-history.test.js.map +1 -0
- package/dist/hooks/use-mode-handlers.d.ts +10 -2
- package/dist/hooks/use-mode-handlers.d.ts.map +1 -1
- package/dist/hooks/use-mode-handlers.js +21 -2
- package/dist/hooks/use-mode-handlers.js.map +1 -1
- package/dist/hooks/use-model-selection.d.ts +3 -0
- package/dist/hooks/use-model-selection.d.ts.map +1 -1
- package/dist/hooks/use-model-selection.js +3 -0
- package/dist/hooks/use-model-selection.js.map +1 -1
- package/dist/hooks/use-path-completion.test.d.ts +2 -0
- package/dist/hooks/use-path-completion.test.d.ts.map +1 -0
- package/dist/hooks/use-path-completion.test.js +54 -0
- package/dist/hooks/use-path-completion.test.js.map +1 -0
- package/dist/hooks/use-runtime-settings.d.ts.map +1 -1
- package/dist/hooks/use-runtime-settings.js +15 -6
- package/dist/hooks/use-runtime-settings.js.map +1 -1
- package/dist/hooks/use-settings-completion.d.ts.map +1 -1
- package/dist/hooks/use-settings-completion.js +0 -1
- package/dist/hooks/use-settings-completion.js.map +1 -1
- package/dist/hooks/use-settings-value-completion.d.ts +1 -0
- package/dist/hooks/use-settings-value-completion.d.ts.map +1 -1
- package/dist/hooks/use-settings-value-completion.js +25 -19
- package/dist/hooks/use-settings-value-completion.js.map +1 -1
- package/dist/hooks/use-shell-mode.d.ts.map +1 -1
- package/dist/hooks/use-shell-mode.js +2 -1
- package/dist/hooks/use-shell-mode.js.map +1 -1
- package/dist/hooks/use-slash-commands.d.ts +1 -0
- package/dist/hooks/use-slash-commands.d.ts.map +1 -1
- package/dist/hooks/use-slash-commands.js +10 -1
- package/dist/hooks/use-slash-commands.js.map +1 -1
- package/dist/hooks/use-undo-selection.d.ts +19 -0
- package/dist/hooks/use-undo-selection.d.ts.map +1 -0
- package/dist/hooks/use-undo-selection.js +76 -0
- package/dist/hooks/use-undo-selection.js.map +1 -0
- package/dist/lib/editor-impl.test.js +63 -50
- package/dist/lib/editor-impl.test.js.map +1 -1
- package/dist/lib/openai-agent-client.d.ts +9 -0
- package/dist/lib/openai-agent-client.d.ts.map +1 -1
- package/dist/lib/openai-agent-client.js +104 -134
- package/dist/lib/openai-agent-client.js.map +1 -1
- package/dist/lib/openai-agent-client.test.js +30 -0
- package/dist/lib/openai-agent-client.test.js.map +1 -1
- package/dist/non-interactive.d.ts +6 -1
- package/dist/non-interactive.d.ts.map +1 -1
- package/dist/non-interactive.js +70 -4
- package/dist/non-interactive.js.map +1 -1
- package/dist/non-interactive.test.js +176 -1
- package/dist/non-interactive.test.js.map +1 -1
- package/dist/prompts/orchestrator.md +46 -0
- package/dist/prompts/plan-mode-info.md +17 -0
- package/dist/prompts/prompt-selector.d.ts +2 -1
- package/dist/prompts/prompt-selector.d.ts.map +1 -1
- package/dist/prompts/prompt-selector.js +5 -1
- package/dist/prompts/prompt-selector.js.map +1 -1
- package/dist/prompts/prompt-selector.test.js +7 -0
- package/dist/prompts/prompt-selector.test.js.map +1 -1
- package/dist/prompts/prompt-selector.test.ts +18 -0
- package/dist/prompts/prompt-selector.ts +7 -1
- package/dist/prompts/reasoning-efficiency.d.ts +7 -0
- package/dist/prompts/reasoning-efficiency.d.ts.map +1 -0
- package/dist/prompts/reasoning-efficiency.js +19 -0
- package/dist/prompts/reasoning-efficiency.js.map +1 -0
- package/dist/prompts/reasoning-efficiency.ts +18 -0
- package/dist/prompts/search-via-shell.test.js +17 -17
- package/dist/prompts/search-via-shell.test.js.map +1 -1
- package/dist/prompts/search-via-shell.test.ts +17 -17
- package/dist/prompts/shell-auto-approval.d.ts +3 -0
- package/dist/prompts/shell-auto-approval.d.ts.map +1 -0
- package/dist/prompts/shell-auto-approval.js +23 -0
- package/dist/prompts/shell-auto-approval.js.map +1 -0
- package/dist/prompts/shell-auto-approval.ts +23 -0
- package/dist/prompts/subagent-delegation.d.ts +10 -0
- package/dist/prompts/subagent-delegation.d.ts.map +1 -0
- package/dist/prompts/subagent-delegation.js +33 -0
- package/dist/prompts/subagent-delegation.js.map +1 -0
- package/dist/prompts/subagent-delegation.test.d.ts +2 -0
- package/dist/prompts/subagent-delegation.test.d.ts.map +1 -0
- package/dist/prompts/subagent-delegation.test.js +17 -0
- package/dist/prompts/subagent-delegation.test.js.map +1 -0
- package/dist/prompts/subagent-delegation.test.ts +21 -0
- package/dist/prompts/subagent-delegation.ts +40 -0
- package/dist/prompts/subagents/explorer.md +41 -0
- package/dist/prompts/subagents/mentor.md +15 -0
- package/dist/prompts/subagents/researcher.md +39 -0
- package/dist/prompts/subagents/worker.md +40 -0
- package/dist/providers/ai-sdk-anthropic.provider.d.ts +1 -0
- package/dist/providers/ai-sdk-anthropic.provider.d.ts.map +1 -1
- package/dist/providers/ai-sdk-anthropic.provider.js +67 -1
- package/dist/providers/ai-sdk-anthropic.provider.js.map +1 -1
- package/dist/providers/ai-sdk-anthropic.provider.test.d.ts +2 -0
- package/dist/providers/ai-sdk-anthropic.provider.test.d.ts.map +1 -0
- package/dist/providers/ai-sdk-anthropic.provider.test.js +87 -0
- package/dist/providers/ai-sdk-anthropic.provider.test.js.map +1 -0
- package/dist/providers/common/openai-compatible-messages.d.ts +1 -1
- package/dist/providers/common/openai-compatible-messages.d.ts.map +1 -1
- package/dist/providers/common/openai-compatible-messages.js +47 -16
- package/dist/providers/common/openai-compatible-messages.js.map +1 -1
- package/dist/providers/common/openai-compatible-messages.test.js +77 -14
- package/dist/providers/common/openai-compatible-messages.test.js.map +1 -1
- package/dist/providers/custom-provider-adapter.test.js +39 -1
- package/dist/providers/custom-provider-adapter.test.js.map +1 -1
- package/dist/providers/openai-compatible.provider.d.ts +11 -1
- package/dist/providers/openai-compatible.provider.d.ts.map +1 -1
- package/dist/providers/openai-compatible.provider.js +124 -10
- package/dist/providers/openai-compatible.provider.js.map +1 -1
- package/dist/providers/openai-compatible.provider.test.js +62 -1
- package/dist/providers/openai-compatible.provider.test.js.map +1 -1
- package/dist/providers/openrouter.provider.js +1 -1
- package/dist/providers/openrouter.provider.js.map +1 -1
- package/dist/scripts/eval-auto-approval/dataset.d.ts +1 -1
- package/dist/services/agent-stream.d.ts +1 -0
- package/dist/services/agent-stream.d.ts.map +1 -1
- package/dist/services/approval-flow-coordinator.d.ts +2 -0
- package/dist/services/approval-flow-coordinator.d.ts.map +1 -1
- package/dist/services/approval-flow-coordinator.js +45 -18
- package/dist/services/approval-flow-coordinator.js.map +1 -1
- package/dist/services/approval-flow-coordinator.test.js +87 -0
- package/dist/services/approval-flow-coordinator.test.js.map +1 -1
- package/dist/services/approval-rejection-interceptor.test.js +5 -0
- package/dist/services/approval-rejection-interceptor.test.js.map +1 -1
- package/dist/services/approval-state.d.ts +4 -0
- package/dist/services/approval-state.d.ts.map +1 -1
- package/dist/services/approval-state.js +2 -0
- package/dist/services/approval-state.js.map +1 -1
- package/dist/services/approval-state.test.js +21 -0
- package/dist/services/approval-state.test.js.map +1 -1
- package/dist/services/conversation-events.d.ts +25 -1
- package/dist/services/conversation-events.d.ts.map +1 -1
- package/dist/services/conversation-persistence.d.ts +52 -0
- package/dist/services/conversation-persistence.d.ts.map +1 -0
- package/dist/services/conversation-persistence.js +163 -0
- package/dist/services/conversation-persistence.js.map +1 -0
- package/dist/services/conversation-persistence.test.d.ts +2 -0
- package/dist/services/conversation-persistence.test.d.ts.map +1 -0
- package/dist/services/conversation-persistence.test.js +418 -0
- package/dist/services/conversation-persistence.test.js.map +1 -0
- package/dist/services/conversation-result-builder.d.ts.map +1 -1
- package/dist/services/conversation-result-builder.js +13 -0
- package/dist/services/conversation-result-builder.js.map +1 -1
- package/dist/services/conversation-service.d.ts +22 -0
- package/dist/services/conversation-service.d.ts.map +1 -1
- package/dist/services/conversation-service.js +18 -0
- package/dist/services/conversation-service.js.map +1 -1
- package/dist/services/conversation-session.auto-approval.test.js +57 -9
- package/dist/services/conversation-session.auto-approval.test.js.map +1 -1
- package/dist/services/conversation-session.d.ts +25 -0
- package/dist/services/conversation-session.d.ts.map +1 -1
- package/dist/services/conversation-session.js +208 -40
- package/dist/services/conversation-session.js.map +1 -1
- package/dist/services/conversation-store.d.ts +42 -0
- package/dist/services/conversation-store.d.ts.map +1 -1
- package/dist/services/conversation-store.js +182 -1
- package/dist/services/conversation-store.js.map +1 -1
- package/dist/services/conversation-store.test.js +310 -1
- package/dist/services/conversation-store.test.js.map +1 -1
- package/dist/services/history-service.d.ts +6 -1
- package/dist/services/history-service.d.ts.map +1 -1
- package/dist/services/history-service.js +46 -6
- package/dist/services/history-service.js.map +1 -1
- package/dist/services/history-service.test.d.ts +2 -0
- package/dist/services/history-service.test.d.ts.map +1 -0
- package/dist/services/history-service.test.js +77 -0
- package/dist/services/history-service.test.js.map +1 -0
- package/dist/services/input-surge-guard.d.ts +37 -0
- package/dist/services/input-surge-guard.d.ts.map +1 -0
- package/dist/services/input-surge-guard.js +190 -0
- package/dist/services/input-surge-guard.js.map +1 -0
- package/dist/services/input-surge-guard.test.d.ts +2 -0
- package/dist/services/input-surge-guard.test.d.ts.map +1 -0
- package/dist/services/input-surge-guard.test.js +126 -0
- package/dist/services/input-surge-guard.test.js.map +1 -0
- package/dist/services/interruption-info.js +1 -1
- package/dist/services/interruption-info.js.map +1 -1
- package/dist/services/interruption-info.test.js +12 -0
- package/dist/services/interruption-info.test.js.map +1 -1
- package/dist/services/mode-notices.d.ts +11 -0
- package/dist/services/mode-notices.d.ts.map +1 -0
- package/dist/services/mode-notices.js +16 -0
- package/dist/services/mode-notices.js.map +1 -0
- package/dist/services/plan-mode-interceptor.d.ts +10 -0
- package/dist/services/plan-mode-interceptor.d.ts.map +1 -0
- package/dist/services/plan-mode-interceptor.js +38 -0
- package/dist/services/plan-mode-interceptor.js.map +1 -0
- package/dist/services/plan-mode-interceptor.test.d.ts +2 -0
- package/dist/services/plan-mode-interceptor.test.d.ts.map +1 -0
- package/dist/services/plan-mode-interceptor.test.js +55 -0
- package/dist/services/plan-mode-interceptor.test.js.map +1 -0
- package/dist/services/rtk-service.d.ts.map +1 -1
- package/dist/services/rtk-service.js +104 -9
- package/dist/services/rtk-service.js.map +1 -1
- package/dist/services/rtk-service.test.js +55 -20
- package/dist/services/rtk-service.test.js.map +1 -1
- package/dist/services/service-interfaces.d.ts +4 -1
- package/dist/services/service-interfaces.d.ts.map +1 -1
- package/dist/services/settings-persistence.js +1 -1
- package/dist/services/settings-schema.d.ts +107 -4
- package/dist/services/settings-schema.d.ts.map +1 -1
- package/dist/services/settings-schema.js +103 -5
- package/dist/services/settings-schema.js.map +1 -1
- package/dist/services/settings-schema.test.js +35 -1
- package/dist/services/settings-schema.test.js.map +1 -1
- package/dist/services/settings-service.d.ts +3 -1
- package/dist/services/settings-service.d.ts.map +1 -1
- package/dist/services/settings-service.js +40 -4
- package/dist/services/settings-service.js.map +1 -1
- package/dist/services/settings-sources.d.ts.map +1 -1
- package/dist/services/settings-sources.js +2 -1
- package/dist/services/settings-sources.js.map +1 -1
- package/dist/services/settings-sources.test.js +4 -0
- package/dist/services/settings-sources.test.js.map +1 -1
- package/dist/services/shell-auto-approval-evaluator.d.ts +2 -1
- package/dist/services/shell-auto-approval-evaluator.d.ts.map +1 -1
- package/dist/services/shell-auto-approval-evaluator.js +12 -29
- package/dist/services/shell-auto-approval-evaluator.js.map +1 -1
- package/dist/services/shell-auto-approval-evaluator.test.js +2 -4
- package/dist/services/shell-auto-approval-evaluator.test.js.map +1 -1
- package/dist/services/shell-auto-approval-resolver.test.js +1 -1
- package/dist/services/shell-auto-approval-resolver.test.js.map +1 -1
- package/dist/services/stream-event-processor.d.ts.map +1 -1
- package/dist/services/stream-event-processor.js +24 -8
- package/dist/services/stream-event-processor.js.map +1 -1
- package/dist/services/stream-event-processor.test.js +27 -0
- package/dist/services/stream-event-processor.test.js.map +1 -1
- package/dist/services/subagents/subagent-manager.d.ts +16 -0
- package/dist/services/subagents/subagent-manager.d.ts.map +1 -0
- package/dist/services/subagents/subagent-manager.js +670 -0
- package/dist/services/subagents/subagent-manager.js.map +1 -0
- package/dist/services/subagents/subagent-manager.test.d.ts +2 -0
- package/dist/services/subagents/subagent-manager.test.d.ts.map +1 -0
- package/dist/services/subagents/subagent-manager.test.js +880 -0
- package/dist/services/subagents/subagent-manager.test.js.map +1 -0
- package/dist/services/subagents/subagent-session.d.ts +26 -0
- package/dist/services/subagents/subagent-session.d.ts.map +1 -0
- package/dist/services/subagents/subagent-session.js +86 -0
- package/dist/services/subagents/subagent-session.js.map +1 -0
- package/dist/services/subagents/subagent-session.test.d.ts +2 -0
- package/dist/services/subagents/subagent-session.test.d.ts.map +1 -0
- package/dist/services/subagents/subagent-session.test.js +118 -0
- package/dist/services/subagents/subagent-session.test.js.map +1 -0
- package/dist/services/subagents/types.d.ts +41 -0
- package/dist/services/subagents/types.d.ts.map +1 -0
- package/dist/services/subagents/types.js +2 -0
- package/dist/services/subagents/types.js.map +1 -0
- package/dist/services/terminal-result-collector.d.ts.map +1 -1
- package/dist/services/terminal-result-collector.js +31 -5
- package/dist/services/terminal-result-collector.js.map +1 -1
- package/dist/services/terminal-result-collector.test.js +81 -0
- package/dist/services/terminal-result-collector.test.js.map +1 -1
- package/dist/slash-commands.d.ts +1 -0
- package/dist/slash-commands.d.ts.map +1 -1
- package/dist/slash-commands.js +8 -1
- package/dist/slash-commands.js.map +1 -1
- package/dist/slash-commands.test.d.ts +2 -0
- package/dist/slash-commands.test.d.ts.map +1 -0
- package/dist/slash-commands.test.js +22 -0
- package/dist/slash-commands.test.js.map +1 -0
- package/dist/tools/apply-patch.d.ts +2 -2
- package/dist/tools/apply-patch.d.ts.map +1 -1
- package/dist/tools/apply-patch.js +2 -6
- package/dist/tools/apply-patch.js.map +1 -1
- package/dist/tools/apply-patch.test.js +2 -13
- package/dist/tools/apply-patch.test.js.map +1 -1
- package/dist/tools/ask-mentor.d.ts +2 -2
- package/dist/tools/ask-mentor.d.ts.map +1 -1
- package/dist/tools/ask-mentor.js.map +1 -1
- package/dist/tools/code-context.d.ts +3 -3
- package/dist/tools/code-context.d.ts.map +1 -1
- package/dist/tools/code-context.js.map +1 -1
- package/dist/tools/code-context.test.js +2 -2
- package/dist/tools/code-context.test.js.map +1 -1
- package/dist/tools/command-message-formatters.d.ts +5 -0
- package/dist/tools/command-message-formatters.d.ts.map +1 -0
- package/dist/tools/command-message-formatters.js +13 -0
- package/dist/tools/command-message-formatters.js.map +1 -0
- package/dist/tools/command-message-formatters.test.d.ts +2 -0
- package/dist/tools/command-message-formatters.test.d.ts.map +1 -0
- package/dist/tools/command-message-formatters.test.js +23 -0
- package/dist/tools/command-message-formatters.test.js.map +1 -0
- package/dist/tools/create-file.d.ts +2 -2
- package/dist/tools/create-file.d.ts.map +1 -1
- package/dist/tools/create-file.js +14 -9
- package/dist/tools/create-file.js.map +1 -1
- package/dist/tools/create-file.test.js +13 -13
- package/dist/tools/create-file.test.js.map +1 -1
- package/dist/tools/edit-healing.d.ts +3 -3
- package/dist/tools/edit-healing.d.ts.map +1 -1
- package/dist/tools/edit-healing.js.map +1 -1
- package/dist/tools/edit-healing.test.js +0 -1
- package/dist/tools/edit-healing.test.js.map +1 -1
- package/dist/tools/file-locks.d.ts +1 -0
- package/dist/tools/file-locks.d.ts.map +1 -1
- package/dist/tools/file-locks.js +16 -0
- package/dist/tools/file-locks.js.map +1 -1
- package/dist/tools/find-files.d.ts +2 -2
- package/dist/tools/find-files.d.ts.map +1 -1
- package/dist/tools/find-files.js.map +1 -1
- package/dist/tools/find-files.test.js +1 -1
- package/dist/tools/find-files.test.js.map +1 -1
- package/dist/tools/grep.d.ts +3 -2
- package/dist/tools/grep.d.ts.map +1 -1
- package/dist/tools/grep.js +65 -4
- package/dist/tools/grep.js.map +1 -1
- package/dist/tools/grep.test.d.ts +2 -0
- package/dist/tools/grep.test.d.ts.map +1 -0
- package/dist/tools/grep.test.js +39 -0
- package/dist/tools/grep.test.js.map +1 -0
- package/dist/tools/read-file.d.ts +3 -2
- package/dist/tools/read-file.d.ts.map +1 -1
- package/dist/tools/read-file.js +7 -5
- package/dist/tools/read-file.js.map +1 -1
- package/dist/tools/read-file.test.js +2 -2
- package/dist/tools/read-file.test.js.map +1 -1
- package/dist/tools/run-subagent.d.ts +14 -0
- package/dist/tools/run-subagent.d.ts.map +1 -0
- package/dist/tools/run-subagent.js +200 -0
- package/dist/tools/run-subagent.js.map +1 -0
- package/dist/tools/run-subagent.test.d.ts +2 -0
- package/dist/tools/run-subagent.test.d.ts.map +1 -0
- package/dist/tools/run-subagent.test.js +206 -0
- package/dist/tools/run-subagent.test.js.map +1 -0
- package/dist/tools/search-replace.d.ts +15 -7
- package/dist/tools/search-replace.d.ts.map +1 -1
- package/dist/tools/search-replace.js +93 -119
- package/dist/tools/search-replace.js.map +1 -1
- package/dist/tools/search-replace.test.js +330 -217
- package/dist/tools/search-replace.test.js.map +1 -1
- package/dist/tools/search.d.ts.map +1 -1
- package/dist/tools/search.js.map +1 -1
- package/dist/tools/shell.d.ts +3 -2
- package/dist/tools/shell.d.ts.map +1 -1
- package/dist/tools/shell.js +14 -6
- package/dist/tools/shell.js.map +1 -1
- package/dist/tools/shell.test.js +189 -11
- package/dist/tools/shell.test.js.map +1 -1
- package/dist/tools/tool-parameter-schema.test.js +13 -4
- package/dist/tools/tool-parameter-schema.test.js.map +1 -1
- package/dist/tools/types.d.ts +3 -2
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/tools/utils.d.ts.map +1 -1
- package/dist/tools/utils.js +11 -1
- package/dist/tools/utils.js.map +1 -1
- package/dist/tools/utils.test.js +13 -0
- package/dist/tools/utils.test.js.map +1 -1
- package/dist/tools/web-fetch.d.ts +2 -2
- package/dist/tools/web-fetch.d.ts.map +1 -1
- package/dist/tools/web-fetch.js +29 -4
- package/dist/tools/web-fetch.js.map +1 -1
- package/dist/tools/web-fetch.test.js +64 -0
- package/dist/tools/web-fetch.test.js.map +1 -1
- package/dist/tools/web-search.d.ts +2 -2
- package/dist/tools/web-search.d.ts.map +1 -1
- package/dist/tools/web-search.js.map +1 -1
- package/dist/utils/clipboard.d.ts +2 -2
- package/dist/utils/clipboard.d.ts.map +1 -1
- package/dist/utils/clipboard.js +58 -22
- package/dist/utils/clipboard.js.map +1 -1
- package/dist/utils/clipboard.test.js +25 -6
- package/dist/utils/clipboard.test.js.map +1 -1
- package/dist/utils/command-safety/handlers/sed-handler.d.ts.map +1 -1
- package/dist/utils/command-safety/handlers/sed-handler.js +10 -7
- package/dist/utils/command-safety/handlers/sed-handler.js.map +1 -1
- package/dist/utils/command-safety/index.d.ts.map +1 -1
- package/dist/utils/command-safety/index.js +153 -117
- package/dist/utils/command-safety/index.js.map +1 -1
- package/dist/utils/command-safety/path-analysis.d.ts.map +1 -1
- package/dist/utils/command-safety/path-analysis.js +6 -2
- package/dist/utils/command-safety/path-analysis.js.map +1 -1
- package/dist/utils/command-safety/utils.js +2 -2
- package/dist/utils/command-safety/utils.js.map +1 -1
- package/dist/utils/command-safety.path.test.js +15 -1
- package/dist/utils/command-safety.path.test.js.map +1 -1
- package/dist/utils/conversation-event-handler.d.ts +15 -0
- package/dist/utils/conversation-event-handler.d.ts.map +1 -1
- package/dist/utils/conversation-event-handler.js +108 -8
- package/dist/utils/conversation-event-handler.js.map +1 -1
- package/dist/utils/conversation-event-handler.test.js +207 -2
- package/dist/utils/conversation-event-handler.test.js.map +1 -1
- package/dist/utils/conversation-utils.d.ts +1 -0
- package/dist/utils/conversation-utils.d.ts.map +1 -1
- package/dist/utils/conversation-utils.js +1 -0
- package/dist/utils/conversation-utils.js.map +1 -1
- package/dist/utils/execute-shell.d.ts +2 -0
- package/dist/utils/execute-shell.d.ts.map +1 -1
- package/dist/utils/execute-shell.js +25 -1
- package/dist/utils/execute-shell.js.map +1 -1
- package/dist/utils/execute-shell.test.js +22 -0
- package/dist/utils/execute-shell.test.js.map +1 -1
- package/dist/utils/extract-command-messages.d.ts.map +1 -1
- package/dist/utils/extract-command-messages.js +2 -21
- package/dist/utils/extract-command-messages.js.map +1 -1
- package/dist/utils/model-settings.d.ts.map +1 -1
- package/dist/utils/model-settings.js +18 -0
- package/dist/utils/model-settings.js.map +1 -1
- package/dist/utils/output-trim.d.ts.map +1 -1
- package/dist/utils/output-trim.js +14 -6
- package/dist/utils/output-trim.js.map +1 -1
- package/dist/utils/streaming-session-factory.d.ts.map +1 -1
- package/dist/utils/streaming-session-factory.js +16 -1
- package/dist/utils/streaming-session-factory.js.map +1 -1
- package/dist/utils/streaming-session-factory.test.js +42 -0
- package/dist/utils/streaming-session-factory.test.js.map +1 -1
- package/dist/utils/token-usage.d.ts +13 -0
- package/dist/utils/token-usage.d.ts.map +1 -1
- package/dist/utils/token-usage.js +92 -1
- package/dist/utils/token-usage.js.map +1 -1
- package/dist/utils/token-usage.test.js +93 -1
- package/dist/utils/token-usage.test.js.map +1 -1
- package/package.json +4 -4
- package/readme.md +271 -44
|
@@ -0,0 +1,670 @@
|
|
|
1
|
+
import { Agent, run, tool as createTool, RunState } from '@openai/agents';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import { getProvider } from '../../providers/index.js';
|
|
6
|
+
import { SubagentSession } from './subagent-session.js';
|
|
7
|
+
import { wrapToolInvoke } from '../../lib/tool-invoke.js';
|
|
8
|
+
import { wrapNeedsApproval } from '../../lib/openai-agent-client.js';
|
|
9
|
+
import { toOpenAIStrictToolSchema } from '../../lib/openai-strict-tool-schema.js';
|
|
10
|
+
import { shouldUseStrictToolSchema, shouldPreferPatchEditingModel } from '../../lib/tool-selection-policy.js';
|
|
11
|
+
import { createReadFileToolDefinition } from '../../tools/read-file.js';
|
|
12
|
+
import { createGrepToolDefinition } from '../../tools/grep.js';
|
|
13
|
+
import { createFindFilesToolDefinition } from '../../tools/find-files.js';
|
|
14
|
+
import { createReadCodeOutlineToolDefinition, createCodeContextSearchToolDefinition, } from '../../tools/code-context.js';
|
|
15
|
+
import { createWebSearchToolDefinition } from '../../tools/web-search.js';
|
|
16
|
+
import { createWebFetchToolDefinition } from '../../tools/web-fetch.js';
|
|
17
|
+
import { createApplyPatchToolDefinition } from '../../tools/apply-patch.js';
|
|
18
|
+
import { createSearchReplaceToolDefinition } from '../../tools/search-replace.js';
|
|
19
|
+
import { createCreateFileToolDefinition } from '../../tools/create-file.js';
|
|
20
|
+
import { createShellToolDefinition } from '../../tools/shell.js';
|
|
21
|
+
import { registerToolFormatters } from '../../tools/command-message-formatters.js';
|
|
22
|
+
import { trimToolOutput } from '../../utils/trim-tool-output.js';
|
|
23
|
+
import { extractUsage, normalizeAgentRunUsage } from '../../utils/token-usage.js';
|
|
24
|
+
import { getEnvInfo, getAgentsInstructions } from '../../agent.js';
|
|
25
|
+
import { tryAcquireFileLock } from '../../tools/file-locks.js';
|
|
26
|
+
import { classifyCommand, SafetyStatus } from '../../utils/command-safety/index.js';
|
|
27
|
+
const PROMPTS_DIR = path.join(import.meta.dirname, '../../prompts/subagents');
|
|
28
|
+
const ROLE_MAX_TURNS_DEFAULT = 20;
|
|
29
|
+
function parseFrontmatter(content) {
|
|
30
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
|
|
31
|
+
if (!match) {
|
|
32
|
+
return { frontmatter: {}, body: content };
|
|
33
|
+
}
|
|
34
|
+
const frontmatterText = match[1];
|
|
35
|
+
const body = match[2];
|
|
36
|
+
const frontmatter = {};
|
|
37
|
+
for (const line of frontmatterText.split('\n')) {
|
|
38
|
+
const colonIdx = line.indexOf(':');
|
|
39
|
+
if (colonIdx === -1)
|
|
40
|
+
continue;
|
|
41
|
+
const key = line.slice(0, colonIdx).trim();
|
|
42
|
+
let raw = line.slice(colonIdx + 1).trim();
|
|
43
|
+
const quoted = raw.length >= 2 && ((raw.startsWith('"') && raw.endsWith('"')) || (raw.startsWith("'") && raw.endsWith("'")));
|
|
44
|
+
if (quoted) {
|
|
45
|
+
frontmatter[key] = raw.slice(1, -1);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (raw === 'true') {
|
|
49
|
+
frontmatter[key] = true;
|
|
50
|
+
}
|
|
51
|
+
else if (raw === 'false') {
|
|
52
|
+
frontmatter[key] = false;
|
|
53
|
+
}
|
|
54
|
+
else if (!isNaN(Number(raw)) && raw !== '') {
|
|
55
|
+
frontmatter[key] = Number(raw);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
frontmatter[key] = raw;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return { frontmatter, body: body.trim() };
|
|
62
|
+
}
|
|
63
|
+
function loadRoleDefinition(role, settings) {
|
|
64
|
+
const filePath = path.join(PROMPTS_DIR, `${role}.md`);
|
|
65
|
+
if (!fs.existsSync(filePath)) {
|
|
66
|
+
throw new Error(`Unknown subagent role: "${role}". No definition found at ${filePath}`);
|
|
67
|
+
}
|
|
68
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
69
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
70
|
+
const subagentPrefix = role === 'mentor' ? 'agent.mentor' : `agent.subagent${role.charAt(0).toUpperCase() + role.slice(1)}`;
|
|
71
|
+
const resolve = (value, subagentKey, fallbackKey, defaultValue) => {
|
|
72
|
+
if (value === 'inherit' || value === undefined || value === null || value === '') {
|
|
73
|
+
const subagentVal = settings.get(subagentKey);
|
|
74
|
+
if (subagentVal !== undefined && subagentVal !== null) {
|
|
75
|
+
return subagentVal;
|
|
76
|
+
}
|
|
77
|
+
if (fallbackKey) {
|
|
78
|
+
return settings.get(fallbackKey) ?? defaultValue;
|
|
79
|
+
}
|
|
80
|
+
return defaultValue;
|
|
81
|
+
}
|
|
82
|
+
return value;
|
|
83
|
+
};
|
|
84
|
+
return {
|
|
85
|
+
role,
|
|
86
|
+
name: frontmatter.name ?? role,
|
|
87
|
+
instructions: body,
|
|
88
|
+
canRead: frontmatter.canRead ?? false,
|
|
89
|
+
canWrite: frontmatter.canWrite ?? false,
|
|
90
|
+
canSearchWeb: frontmatter.canSearchWeb ?? false,
|
|
91
|
+
canRunShell: frontmatter.canRunShell ?? false,
|
|
92
|
+
maxTurns: frontmatter.maxTurns ?? ROLE_MAX_TURNS_DEFAULT,
|
|
93
|
+
model: resolve(frontmatter.model, `${subagentPrefix}Model`, 'agent.model', 'gpt-4o'),
|
|
94
|
+
provider: resolve(frontmatter.provider, `${subagentPrefix}Provider`, 'agent.provider', 'openai'),
|
|
95
|
+
reasoningEffort: resolve(frontmatter.reasoningEffort, `${subagentPrefix}ReasoningEffort`, undefined, 'default'),
|
|
96
|
+
description: frontmatter.description ?? '',
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function buildAgentTools(toolDefinitions, options) {
|
|
100
|
+
const providerDef = getProvider(options.providerId);
|
|
101
|
+
const capabilities = {
|
|
102
|
+
supportsConversationChaining: providerDef?.capabilities?.supportsConversationChaining ?? false,
|
|
103
|
+
supportsTracingControl: providerDef?.capabilities?.supportsTracingControl ?? false,
|
|
104
|
+
usesStrictToolSchema: providerDef?.capabilities?.usesStrictToolSchema,
|
|
105
|
+
};
|
|
106
|
+
const useStrictSchema = shouldUseStrictToolSchema({
|
|
107
|
+
providerId: options.providerId,
|
|
108
|
+
capabilities,
|
|
109
|
+
});
|
|
110
|
+
return toolDefinitions.map((definition) => wrapToolInvoke(createTool({
|
|
111
|
+
name: definition.name,
|
|
112
|
+
description: definition.description,
|
|
113
|
+
parameters: useStrictSchema ? toOpenAIStrictToolSchema(definition.parameters) : definition.parameters,
|
|
114
|
+
needsApproval: wrapNeedsApproval(definition),
|
|
115
|
+
execute: async (params, _context, details) => {
|
|
116
|
+
options.onToolStart?.(definition.name, params, formatRunningCommandMessages(definition, params));
|
|
117
|
+
const maxOutputLength = options.settings.get('shell.maxOutputChars');
|
|
118
|
+
const result = await definition.execute(params, _context, details);
|
|
119
|
+
options.onToolComplete?.(definition.name);
|
|
120
|
+
return trimToolOutput(result, undefined, maxOutputLength ?? undefined);
|
|
121
|
+
},
|
|
122
|
+
})));
|
|
123
|
+
}
|
|
124
|
+
function formatRunningCommandMessages(definition, params) {
|
|
125
|
+
const callId = `subagent-tool-${randomUUID()}`;
|
|
126
|
+
const item = {
|
|
127
|
+
id: callId,
|
|
128
|
+
rawItem: {
|
|
129
|
+
id: callId,
|
|
130
|
+
callId,
|
|
131
|
+
arguments: params,
|
|
132
|
+
output: '',
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
try {
|
|
136
|
+
const messages = definition.formatCommandMessage(item, 0, new Map());
|
|
137
|
+
if (messages.length > 0) {
|
|
138
|
+
return messages.map((message, index) => ({
|
|
139
|
+
...message,
|
|
140
|
+
id: `${callId}-${index}`,
|
|
141
|
+
status: 'running',
|
|
142
|
+
output: '',
|
|
143
|
+
success: undefined,
|
|
144
|
+
failureReason: undefined,
|
|
145
|
+
toolName: message.toolName ?? definition.name,
|
|
146
|
+
toolArgs: message.toolArgs ?? params,
|
|
147
|
+
}));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
// Fall back to the tool name if a formatter cannot handle an in-flight item.
|
|
152
|
+
}
|
|
153
|
+
return [
|
|
154
|
+
{
|
|
155
|
+
id: `${callId}-0`,
|
|
156
|
+
sender: 'command',
|
|
157
|
+
status: 'running',
|
|
158
|
+
command: definition.name,
|
|
159
|
+
output: '',
|
|
160
|
+
toolName: definition.name,
|
|
161
|
+
toolArgs: params,
|
|
162
|
+
},
|
|
163
|
+
];
|
|
164
|
+
}
|
|
165
|
+
function runWithProvider(providerId, runner, agent, input, options) {
|
|
166
|
+
const providerDef = getProvider(providerId);
|
|
167
|
+
const supportsTracingControl = providerDef?.capabilities?.supportsTracingControl ?? false;
|
|
168
|
+
const effectiveOptions = { ...options };
|
|
169
|
+
if (!supportsTracingControl) {
|
|
170
|
+
effectiveOptions.tracingDisabled = true;
|
|
171
|
+
}
|
|
172
|
+
if (!runner && providerId !== 'openai') {
|
|
173
|
+
const label = providerDef?.label || providerId;
|
|
174
|
+
throw new Error(`${label} is configured but could not be initialized. ` +
|
|
175
|
+
`Please check that all required credentials and provider settings are set.`);
|
|
176
|
+
}
|
|
177
|
+
return runner ? runner.run(agent, input, effectiveOptions) : run(agent, input, effectiveOptions);
|
|
178
|
+
}
|
|
179
|
+
function isToolHistoryItem(raw) {
|
|
180
|
+
const type = typeof raw?.type === 'string' ? raw.type : '';
|
|
181
|
+
if (raw?.role === 'tool')
|
|
182
|
+
return true;
|
|
183
|
+
return /tool|function_call/i.test(type);
|
|
184
|
+
}
|
|
185
|
+
function assistantText(raw) {
|
|
186
|
+
if (raw?.role !== 'assistant')
|
|
187
|
+
return null;
|
|
188
|
+
const content = raw?.content;
|
|
189
|
+
if (typeof content === 'string')
|
|
190
|
+
return content;
|
|
191
|
+
if (Array.isArray(content)) {
|
|
192
|
+
return content
|
|
193
|
+
.filter((c) => typeof c?.text === 'string')
|
|
194
|
+
.map((c) => c.text)
|
|
195
|
+
.join('');
|
|
196
|
+
}
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Returns only the subagent's final answer. When the run used tools, the
|
|
201
|
+
* answer is the last assistant message that appears strictly after the last
|
|
202
|
+
* tool item, so intermediate narration emitted between tool calls does not
|
|
203
|
+
* leak into the parent agent context.
|
|
204
|
+
*
|
|
205
|
+
* When the run ends with a tool call and no trailing narration, falls back to
|
|
206
|
+
* the last assistant message found anywhere in the history (which may precede
|
|
207
|
+
* the last tool call) to avoid returning an empty string for runs that
|
|
208
|
+
* completed successfully via write operations.
|
|
209
|
+
*/
|
|
210
|
+
function extractFinalText(result) {
|
|
211
|
+
if (typeof result.finalOutput === 'string' && result.finalOutput) {
|
|
212
|
+
return result.finalOutput;
|
|
213
|
+
}
|
|
214
|
+
if (Array.isArray(result.history)) {
|
|
215
|
+
const history = result.history;
|
|
216
|
+
let lastToolIndex = -1;
|
|
217
|
+
for (let i = history.length - 1; i >= 0; i--) {
|
|
218
|
+
if (isToolHistoryItem(history[i]?.rawItem ?? history[i])) {
|
|
219
|
+
lastToolIndex = i;
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
// Look for an assistant message after the last tool item.
|
|
224
|
+
for (let i = history.length - 1; i > lastToolIndex; i--) {
|
|
225
|
+
const text = assistantText(history[i]?.rawItem ?? history[i]);
|
|
226
|
+
if (text !== null)
|
|
227
|
+
return text;
|
|
228
|
+
}
|
|
229
|
+
// Fallback: the run ended with a tool call and no narration. Return the
|
|
230
|
+
// last assistant message found anywhere so the caller gets a non-empty
|
|
231
|
+
// summary rather than an empty string.
|
|
232
|
+
for (let i = history.length - 1; i >= 0; i--) {
|
|
233
|
+
const text = assistantText(history[i]?.rawItem ?? history[i]);
|
|
234
|
+
if (text !== null)
|
|
235
|
+
return text;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return '';
|
|
239
|
+
}
|
|
240
|
+
function aggregateToolUsage(toolCounts) {
|
|
241
|
+
return Array.from(toolCounts.entries()).map(([toolName, count]) => ({ toolName, count }));
|
|
242
|
+
}
|
|
243
|
+
export class SubagentManager {
|
|
244
|
+
#logger;
|
|
245
|
+
#settings;
|
|
246
|
+
#executionContext;
|
|
247
|
+
#onEvent;
|
|
248
|
+
#mentorSession;
|
|
249
|
+
constructor(deps) {
|
|
250
|
+
this.#logger = deps.logger;
|
|
251
|
+
this.#settings = deps.settings;
|
|
252
|
+
this.#executionContext = deps.executionContext;
|
|
253
|
+
this.#onEvent = deps.onEvent;
|
|
254
|
+
this.#mentorSession = new SubagentSession(randomUUID(), 'mentor');
|
|
255
|
+
}
|
|
256
|
+
#emit(event) {
|
|
257
|
+
try {
|
|
258
|
+
this.#onEvent?.(event);
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
this.#logger.debug('Subagent event emit failed', { error: error?.message });
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
resetMentorSession() {
|
|
265
|
+
this.#mentorSession.reset();
|
|
266
|
+
}
|
|
267
|
+
async run(request) {
|
|
268
|
+
const agentId = randomUUID();
|
|
269
|
+
this.#logger.debug('SubagentManager.run', { agentId, role: request.role, taskLength: request.task.length });
|
|
270
|
+
this.#emit({ type: 'subagent_started', agentId, role: request.role, task: request.task });
|
|
271
|
+
try {
|
|
272
|
+
const result = request.role === 'mentor'
|
|
273
|
+
? await this.#runMentor(agentId, request.task, request.signal)
|
|
274
|
+
: await this.#runSubagent(agentId, request, loadRoleDefinition(request.role, this.#settings));
|
|
275
|
+
this.#emit({ type: 'subagent_completed', result });
|
|
276
|
+
return result;
|
|
277
|
+
}
|
|
278
|
+
catch (error) {
|
|
279
|
+
this.#logger.error('Subagent run failed', { agentId, role: request.role, error: error?.message });
|
|
280
|
+
// Detect abort-like errors (user cancellation, stream abort) and
|
|
281
|
+
// normalize to 'cancelled' status so the parent can distinguish
|
|
282
|
+
// intentional user-initiated cancellation from unexpected failures.
|
|
283
|
+
const isAbort = error?.name === 'AbortError' ||
|
|
284
|
+
error?.message?.includes('abort') ||
|
|
285
|
+
error?.message?.includes('cancel') ||
|
|
286
|
+
error?.code === 'ERR_ABORTED';
|
|
287
|
+
const result = {
|
|
288
|
+
agentId,
|
|
289
|
+
role: request.role,
|
|
290
|
+
status: isAbort ? 'cancelled' : 'failed',
|
|
291
|
+
finalText: '',
|
|
292
|
+
filesChanged: [],
|
|
293
|
+
toolsUsed: [],
|
|
294
|
+
error: error?.message || String(error),
|
|
295
|
+
};
|
|
296
|
+
this.#emit({ type: 'subagent_completed', result });
|
|
297
|
+
return result;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
async #runMentor(agentId, task, signal) {
|
|
301
|
+
const mentorModel = this.#settings.get('agent.mentorModel');
|
|
302
|
+
if (!mentorModel) {
|
|
303
|
+
throw new Error('Mentor model is not configured');
|
|
304
|
+
}
|
|
305
|
+
const mentorProvider = this.#settings.get('agent.mentorProvider') ?? this.#settings.get('agent.provider') ?? 'openai';
|
|
306
|
+
const mentorMode = this.#settings.get('app.mentorMode');
|
|
307
|
+
// model/provider/reasoning stay on the mentor-specific settings (compat
|
|
308
|
+
// bridge); the prompt body and maxTurns come from the mentor role markdown.
|
|
309
|
+
const definition = loadRoleDefinition('mentor', this.#settings);
|
|
310
|
+
const baseInstructions = mentorMode
|
|
311
|
+
? 'You are a senior architect acting as a peer reviewer. You have no codebase access—you rely on what the user reports.\n\n' +
|
|
312
|
+
'Your role is adversarial review, not rubber-stamping:\n' +
|
|
313
|
+
'- Challenge assumptions, even when reasoning sounds solid\n' +
|
|
314
|
+
'- Probe for gaps: what did they not check? What could go wrong?\n' +
|
|
315
|
+
'- Suggest alternatives they may have dismissed too quickly\n' +
|
|
316
|
+
'- Ask for evidence when confidence seems misplaced\n\n' +
|
|
317
|
+
'When satisfied, give clear approval with specific next steps. When not, say exactly what needs more investigation.\n\n' +
|
|
318
|
+
"Be concise. Push back hard, but don't block unnecessarily."
|
|
319
|
+
: definition.instructions;
|
|
320
|
+
const envInfo = getEnvInfo(this.#settings, this.#executionContext);
|
|
321
|
+
const cwd = this.#executionContext?.getCwd() ?? process.cwd();
|
|
322
|
+
const agentsInstructions = this.#executionContext?.isRemote() ? '' : getAgentsInstructions(cwd);
|
|
323
|
+
const instructions = `${baseInstructions}\n\nEnvironment: ${envInfo}${agentsInstructions}`;
|
|
324
|
+
this.#mentorSession.switchProvider(mentorProvider);
|
|
325
|
+
const mentorRunner = this.#mentorSession.ensureRunner(mentorProvider, (providerId) => {
|
|
326
|
+
const providerDef = getProvider(providerId);
|
|
327
|
+
return providerDef?.createRunner?.({ settingsService: this.#settings, loggingService: this.#logger }) ?? null;
|
|
328
|
+
});
|
|
329
|
+
const mentorAgent = this.#mentorSession.ensureAgent(() => {
|
|
330
|
+
const reasoningEffort = this.#settings.get('agent.mentorReasoningEffort');
|
|
331
|
+
const modelSettings = {};
|
|
332
|
+
if (reasoningEffort && reasoningEffort !== 'default') {
|
|
333
|
+
modelSettings.reasoning = { effort: reasoningEffort, summary: 'auto' };
|
|
334
|
+
}
|
|
335
|
+
return new Agent({
|
|
336
|
+
name: definition.name,
|
|
337
|
+
model: mentorModel,
|
|
338
|
+
...(Object.keys(modelSettings).length > 0 ? { modelSettings } : {}),
|
|
339
|
+
instructions,
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
this.#mentorSession.addUserMessage(task);
|
|
343
|
+
const providerDef = getProvider(mentorProvider);
|
|
344
|
+
const supportsChaining = providerDef?.capabilities?.supportsConversationChaining ?? false;
|
|
345
|
+
const input = this.#mentorSession.getInput(task, supportsChaining);
|
|
346
|
+
const runOptions = {
|
|
347
|
+
...this.#mentorSession.getRunOptions(supportsChaining, definition.maxTurns),
|
|
348
|
+
...(signal ? { signal } : {}),
|
|
349
|
+
};
|
|
350
|
+
const result = await runWithProvider(mentorProvider, mentorRunner, mentorAgent, input, runOptions);
|
|
351
|
+
this.#mentorSession.updateFromResult(result);
|
|
352
|
+
return {
|
|
353
|
+
agentId,
|
|
354
|
+
role: 'mentor',
|
|
355
|
+
status: 'completed',
|
|
356
|
+
finalText: extractFinalText(result),
|
|
357
|
+
filesChanged: [],
|
|
358
|
+
toolsUsed: [],
|
|
359
|
+
usage: normalizeAgentRunUsage(result?.state?.usage) ?? extractUsage(result),
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
async #runSubagent(agentId, request, definition) {
|
|
363
|
+
const toolCounts = new Map();
|
|
364
|
+
const filesChanged = [];
|
|
365
|
+
const toolDefinitions = this.#buildToolDefinitions(definition, request.writeBoundary, filesChanged);
|
|
366
|
+
const providerId = definition.provider;
|
|
367
|
+
const tools = buildAgentTools(toolDefinitions, {
|
|
368
|
+
providerId,
|
|
369
|
+
logger: this.#logger,
|
|
370
|
+
settings: this.#settings,
|
|
371
|
+
onToolStart: (name, _params, commandMessages) => {
|
|
372
|
+
toolCounts.set(name, (toolCounts.get(name) ?? 0) + 1);
|
|
373
|
+
this.#emit({
|
|
374
|
+
type: 'subagent_tool_started',
|
|
375
|
+
agentId,
|
|
376
|
+
role: request.role,
|
|
377
|
+
toolName: name,
|
|
378
|
+
commandMessages,
|
|
379
|
+
});
|
|
380
|
+
},
|
|
381
|
+
});
|
|
382
|
+
const providerDef = getProvider(providerId);
|
|
383
|
+
const runner = providerId !== 'openai'
|
|
384
|
+
? providerDef?.createRunner?.({ settingsService: this.#settings, loggingService: this.#logger }) ?? null
|
|
385
|
+
: null;
|
|
386
|
+
const modelSettings = {};
|
|
387
|
+
if (definition.reasoningEffort && definition.reasoningEffort !== 'default') {
|
|
388
|
+
modelSettings.reasoning = { effort: definition.reasoningEffort, summary: 'auto' };
|
|
389
|
+
}
|
|
390
|
+
const envInfo = getEnvInfo(this.#settings, this.#executionContext);
|
|
391
|
+
const cwd = this.#executionContext?.getCwd() ?? process.cwd();
|
|
392
|
+
const agentsInstructions = this.#executionContext?.isRemote() ? '' : getAgentsInstructions(cwd);
|
|
393
|
+
const fullInstructions = [
|
|
394
|
+
definition.instructions,
|
|
395
|
+
`Environment: ${envInfo}${agentsInstructions}`,
|
|
396
|
+
request.writeBoundary?.length
|
|
397
|
+
? `Write boundary: you may only write to paths within: ${request.writeBoundary.join(', ')}`
|
|
398
|
+
: '',
|
|
399
|
+
]
|
|
400
|
+
.filter(Boolean)
|
|
401
|
+
.join('\n\n');
|
|
402
|
+
const agent = new Agent({
|
|
403
|
+
name: definition.name,
|
|
404
|
+
model: definition.model,
|
|
405
|
+
...(Object.keys(modelSettings).length > 0 ? { modelSettings } : {}),
|
|
406
|
+
instructions: fullInstructions,
|
|
407
|
+
tools,
|
|
408
|
+
});
|
|
409
|
+
// When resuming from a nested agent-tool run (e.g. worker approvals),
|
|
410
|
+
// restore the SDK RunState from the serialized resumeState string.
|
|
411
|
+
// This is populated by the SDK in the Agent.asTool path. When
|
|
412
|
+
// run_subagent is registered as a plain function tool,
|
|
413
|
+
// details.resumeState is not populated by the SDK, so this branch is
|
|
414
|
+
// dead code until run_subagent switches to Agent.asTool registration.
|
|
415
|
+
const runInput = request.resumeState ? await RunState.fromString(agent, request.resumeState) : request.task;
|
|
416
|
+
const result = await runWithProvider(providerId, runner, agent, runInput, {
|
|
417
|
+
stream: false,
|
|
418
|
+
maxTurns: definition.maxTurns,
|
|
419
|
+
...(request.signal ? { signal: request.signal } : {}),
|
|
420
|
+
});
|
|
421
|
+
return {
|
|
422
|
+
agentId,
|
|
423
|
+
role: request.role,
|
|
424
|
+
status: 'completed',
|
|
425
|
+
finalText: extractFinalText(result),
|
|
426
|
+
filesChanged: [...new Set(filesChanged)],
|
|
427
|
+
toolsUsed: aggregateToolUsage(toolCounts),
|
|
428
|
+
usage: normalizeAgentRunUsage(result?.state?.usage) ?? extractUsage(result),
|
|
429
|
+
nestedRunResult: result,
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
#buildToolDefinitions(definition, writeBoundary, filesChanged) {
|
|
433
|
+
const tools = [];
|
|
434
|
+
const cwd = this.#executionContext?.getCwd() ?? process.cwd();
|
|
435
|
+
const isRemote = this.#executionContext?.isRemote() ?? false;
|
|
436
|
+
if (definition.canRead) {
|
|
437
|
+
tools.push(createReadFileToolDefinition({ executionContext: this.#executionContext, allowOutsideWorkspace: false }), createGrepToolDefinition({ executionContext: this.#executionContext }), createFindFilesToolDefinition({ executionContext: this.#executionContext }));
|
|
438
|
+
if (!isRemote) {
|
|
439
|
+
tools.push(createReadCodeOutlineToolDefinition({ executionContext: this.#executionContext }), createCodeContextSearchToolDefinition({ executionContext: this.#executionContext }));
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
if (definition.canSearchWeb) {
|
|
443
|
+
tools.push(createWebSearchToolDefinition({ settingsService: this.#settings, loggingService: this.#logger }), createWebFetchToolDefinition({ settingsService: this.#settings, loggingService: this.#logger }));
|
|
444
|
+
}
|
|
445
|
+
if (definition.canRunShell) {
|
|
446
|
+
tools.push(this.#wrapShellTool(createShellToolDefinition({
|
|
447
|
+
settingsService: this.#settings,
|
|
448
|
+
loggingService: this.#logger,
|
|
449
|
+
executionContext: this.#executionContext,
|
|
450
|
+
}), writeBoundary, cwd, filesChanged));
|
|
451
|
+
}
|
|
452
|
+
if (definition.canWrite) {
|
|
453
|
+
const isGpt5 = shouldPreferPatchEditingModel(definition.model);
|
|
454
|
+
if (isGpt5) {
|
|
455
|
+
tools.push(this.#wrapWriteTool(createApplyPatchToolDefinition({
|
|
456
|
+
settingsService: this.#settings,
|
|
457
|
+
loggingService: this.#logger,
|
|
458
|
+
executionContext: this.#executionContext,
|
|
459
|
+
}), writeBoundary, cwd, filesChanged, (params) => this.#extractPathsFromApplyPatch(params)));
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
tools.push(this.#wrapWriteTool(createSearchReplaceToolDefinition({
|
|
463
|
+
settingsService: this.#settings,
|
|
464
|
+
loggingService: this.#logger,
|
|
465
|
+
executionContext: this.#executionContext,
|
|
466
|
+
}), writeBoundary, cwd, filesChanged, (params) => this.#extractPathsFromSearchReplace(params)), this.#wrapWriteTool(createCreateFileToolDefinition({
|
|
467
|
+
settingsService: this.#settings,
|
|
468
|
+
loggingService: this.#logger,
|
|
469
|
+
executionContext: this.#executionContext,
|
|
470
|
+
}), writeBoundary, cwd, filesChanged, (params) => (params?.path ? [params.path] : [])));
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
registerToolFormatters(tools);
|
|
474
|
+
return tools;
|
|
475
|
+
}
|
|
476
|
+
#extractPathsFromApplyPatch(params) {
|
|
477
|
+
if (Array.isArray(params?.operations)) {
|
|
478
|
+
return params.operations.map((op) => op?.path).filter(Boolean);
|
|
479
|
+
}
|
|
480
|
+
return params?.path ? [params.path] : [];
|
|
481
|
+
}
|
|
482
|
+
#extractPathsFromSearchReplace(params) {
|
|
483
|
+
return params?.path ? [params.path] : [];
|
|
484
|
+
}
|
|
485
|
+
#extractSuccessfulWritePaths(result) {
|
|
486
|
+
if (typeof result !== 'string')
|
|
487
|
+
return [];
|
|
488
|
+
try {
|
|
489
|
+
const parsed = JSON.parse(result);
|
|
490
|
+
if (Array.isArray(parsed?.output)) {
|
|
491
|
+
return parsed.output
|
|
492
|
+
.filter((item) => item?.success === true && typeof item?.path === 'string')
|
|
493
|
+
.map((item) => item.path);
|
|
494
|
+
}
|
|
495
|
+
if (parsed?.success === true && typeof parsed?.path === 'string') {
|
|
496
|
+
return [parsed.path];
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
catch {
|
|
500
|
+
return [];
|
|
501
|
+
}
|
|
502
|
+
return [];
|
|
503
|
+
}
|
|
504
|
+
#tryAcquireWorkerWriteLocks(paths, cwd) {
|
|
505
|
+
const uniqueResolvedPaths = [...new Set(paths.map((filePath) => path.resolve(cwd, filePath)))];
|
|
506
|
+
const releases = [];
|
|
507
|
+
for (const resolvedPath of uniqueResolvedPaths) {
|
|
508
|
+
const release = tryAcquireFileLock(resolvedPath);
|
|
509
|
+
if (!release) {
|
|
510
|
+
for (const unlock of releases.reverse()) {
|
|
511
|
+
unlock();
|
|
512
|
+
}
|
|
513
|
+
return null;
|
|
514
|
+
}
|
|
515
|
+
releases.push(release);
|
|
516
|
+
}
|
|
517
|
+
return () => {
|
|
518
|
+
for (const unlock of releases.reverse()) {
|
|
519
|
+
unlock();
|
|
520
|
+
}
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Wraps the shell tool for worker subagents:
|
|
525
|
+
* - Sets needsApproval to always return false. Workers run synchronously
|
|
526
|
+
* inside a blocked parent tool call and have no foreground approval channel,
|
|
527
|
+
* so SDK interruptions (triggered by needsApproval returning true) would
|
|
528
|
+
* silently hang or fail.
|
|
529
|
+
* - Gates execution via command safety classification instead. Safe (GREEN)
|
|
530
|
+
* commands execute normally; dangerous (YELLOW/RED) commands are blocked
|
|
531
|
+
* and return an error string to the model without executing.
|
|
532
|
+
* - For GREEN commands that perform write operations (redirects to paths
|
|
533
|
+
* within the workspace), enforces the worker write boundary and acquires
|
|
534
|
+
* file locks so that shell-based writes and structured writes (apply_patch,
|
|
535
|
+
* create_file) share the same safety guarantees.
|
|
536
|
+
*/
|
|
537
|
+
/**
|
|
538
|
+
* Parse a shell command string and extract file paths used as redirect
|
|
539
|
+
* targets or explicit path arguments that look like write operations.
|
|
540
|
+
* Returns resolved absolute paths.
|
|
541
|
+
*/
|
|
542
|
+
#extractPathsFromCommand(command, cwd) {
|
|
543
|
+
const paths = [];
|
|
544
|
+
try {
|
|
545
|
+
classifyCommand(command, this.#logger);
|
|
546
|
+
// classifyCommand only returns a status, not parsed paths. For now
|
|
547
|
+
// we tokenize the command to find redirect targets and path-like
|
|
548
|
+
// arguments. This is a simple heuristic — comprehensive path
|
|
549
|
+
// extraction from AST is tracked as future work.
|
|
550
|
+
const tokens = command.match(/>\s*(\S+)|>>\s*(\S+)|tee\s+(\S+)/gi);
|
|
551
|
+
if (tokens) {
|
|
552
|
+
for (const token of tokens) {
|
|
553
|
+
const match = token.match(/>+\s*(\S+)|tee\s+(\S+)/);
|
|
554
|
+
if (match) {
|
|
555
|
+
const p = match[1] ?? match[2];
|
|
556
|
+
if (p) {
|
|
557
|
+
const resolved = path.resolve(cwd, p);
|
|
558
|
+
paths.push(resolved);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
catch {
|
|
565
|
+
// Parsing failure is non-fatal — proceed without path extraction
|
|
566
|
+
}
|
|
567
|
+
return [...new Set(paths)];
|
|
568
|
+
}
|
|
569
|
+
#wrapShellTool(definition, writeBoundary, cwd, filesChanged) {
|
|
570
|
+
const originalExecute = definition.execute.bind(definition);
|
|
571
|
+
return {
|
|
572
|
+
...definition,
|
|
573
|
+
needsApproval: () => false,
|
|
574
|
+
execute: async (params, context, details) => {
|
|
575
|
+
const command = typeof params?.command === 'string' ? params.command : '';
|
|
576
|
+
if (!command) {
|
|
577
|
+
return originalExecute(params, context, details);
|
|
578
|
+
}
|
|
579
|
+
const status = classifyCommand(command, this.#logger);
|
|
580
|
+
if (status === SafetyStatus.YELLOW || status === SafetyStatus.RED) {
|
|
581
|
+
return `Error: command blocked for safety (${status}). Workers cannot run commands that require interactive approval. Command: ${command}`;
|
|
582
|
+
}
|
|
583
|
+
// For GREEN commands, extract any file paths referenced in the
|
|
584
|
+
// command (redirect targets, arguments that look like paths) and
|
|
585
|
+
// enforce the write boundary and file locks.
|
|
586
|
+
const extractedPaths = this.#extractPathsFromCommand(command, cwd);
|
|
587
|
+
if (extractedPaths.length > 0) {
|
|
588
|
+
for (const filePath of extractedPaths) {
|
|
589
|
+
if (!this.#isWithinWriteBoundary(filePath, writeBoundary, cwd)) {
|
|
590
|
+
return `Error: command blocked — target path "${filePath}" is outside the allowed write boundary. Command: ${command}`;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
const releaseWorkerLocks = this.#tryAcquireWorkerWriteLocks(extractedPaths, cwd);
|
|
594
|
+
if (!releaseWorkerLocks) {
|
|
595
|
+
return 'Error: command blocked — one or more target files are already being modified by another worker.';
|
|
596
|
+
}
|
|
597
|
+
try {
|
|
598
|
+
const result = await originalExecute(params, context, details);
|
|
599
|
+
filesChanged.push(...extractedPaths);
|
|
600
|
+
return result;
|
|
601
|
+
}
|
|
602
|
+
finally {
|
|
603
|
+
releaseWorkerLocks();
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
return originalExecute(params, context, details);
|
|
607
|
+
},
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
// An explicit writeBoundary narrows where a worker may write. When omitted it
|
|
611
|
+
// defaults to the workspace root, so writes outside the workspace are always
|
|
612
|
+
// rejected even without an explicit boundary.
|
|
613
|
+
#isWithinWriteBoundary(filePath, writeBoundary, cwd) {
|
|
614
|
+
const boundaries = writeBoundary?.length ? writeBoundary : [cwd];
|
|
615
|
+
const resolved = path.resolve(cwd, filePath);
|
|
616
|
+
return boundaries.some((boundary) => {
|
|
617
|
+
const resolvedBoundary = path.resolve(cwd, boundary);
|
|
618
|
+
return resolved === resolvedBoundary || resolved.startsWith(resolvedBoundary + path.sep);
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
#wrapWriteTool(definition, writeBoundary, cwd, filesChanged, extractPaths) {
|
|
622
|
+
const originalExecute = definition.execute.bind(definition);
|
|
623
|
+
return {
|
|
624
|
+
...definition,
|
|
625
|
+
// The writeBoundary is the worker's permission grant: in-boundary writes
|
|
626
|
+
// are auto-approved (there is no foreground approval channel for a
|
|
627
|
+
// subagent running inside a blocked parent tool call), and out-of-boundary
|
|
628
|
+
// writes are rejected deterministically by execute() below. Either way no
|
|
629
|
+
// interactive approval is required, so this never returns true.
|
|
630
|
+
needsApproval: () => false,
|
|
631
|
+
execute: async (params, context) => {
|
|
632
|
+
const paths = extractPaths(params);
|
|
633
|
+
for (const filePath of paths) {
|
|
634
|
+
if (!this.#isWithinWriteBoundary(filePath, writeBoundary, cwd)) {
|
|
635
|
+
return JSON.stringify({
|
|
636
|
+
output: [
|
|
637
|
+
{
|
|
638
|
+
success: false,
|
|
639
|
+
error: `Write rejected: path "${filePath}" is outside the allowed write boundary.`,
|
|
640
|
+
},
|
|
641
|
+
],
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
const releaseWorkerLocks = this.#tryAcquireWorkerWriteLocks(paths, cwd);
|
|
646
|
+
if (!releaseWorkerLocks) {
|
|
647
|
+
return JSON.stringify({
|
|
648
|
+
output: [
|
|
649
|
+
{
|
|
650
|
+
success: false,
|
|
651
|
+
error: 'Write rejected: one or more target files are already being modified by another worker.',
|
|
652
|
+
},
|
|
653
|
+
],
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
try {
|
|
657
|
+
const result = await originalExecute(params, context);
|
|
658
|
+
for (const successfulPath of this.#extractSuccessfulWritePaths(result)) {
|
|
659
|
+
filesChanged.push(successfulPath);
|
|
660
|
+
}
|
|
661
|
+
return result;
|
|
662
|
+
}
|
|
663
|
+
finally {
|
|
664
|
+
releaseWorkerLocks();
|
|
665
|
+
}
|
|
666
|
+
},
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
//# sourceMappingURL=subagent-manager.js.map
|