micro-models-agent 0.2.92 → 0.4.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/README.md +436 -436
- package/dist/agent/error-escalation.d.ts +18 -0
- package/dist/agent/error-escalation.d.ts.map +1 -0
- package/dist/agent/error-escalation.js +122 -0
- package/dist/agent/error-escalation.js.map +1 -0
- package/dist/agent/loop.d.ts +6 -5
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +433 -510
- package/dist/agent/loop.js.map +1 -1
- package/dist/agent/modes/applier.d.ts +2 -2
- package/dist/agent/modes/applier.d.ts.map +1 -1
- package/dist/agent/modes/registry.d.ts.map +1 -1
- package/dist/agent/modes/registry.js.map +1 -1
- package/dist/agent/modes/types.d.ts +12 -2
- package/dist/agent/modes/types.d.ts.map +1 -1
- package/dist/agent/react-loop.d.ts +4 -0
- package/dist/agent/react-loop.d.ts.map +1 -1
- package/dist/agent/react-loop.js +5 -23
- package/dist/agent/react-loop.js.map +1 -1
- package/dist/agent/system-prompt.d.ts +4 -3
- package/dist/agent/system-prompt.d.ts.map +1 -1
- package/dist/agent/system-prompt.js +392 -278
- package/dist/agent/system-prompt.js.map +1 -1
- package/dist/agent/tool-executor.d.ts +2 -0
- package/dist/agent/tool-executor.d.ts.map +1 -1
- package/dist/agent/tool-executor.js +1 -1
- package/dist/agent/tool-executor.js.map +1 -1
- package/dist/agent/write-enforcer.d.ts +11 -0
- package/dist/agent/write-enforcer.d.ts.map +1 -0
- package/dist/agent/write-enforcer.js +92 -0
- package/dist/agent/write-enforcer.js.map +1 -0
- package/dist/cli/config.d.ts +1 -1
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +53 -28
- package/dist/cli/config.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +19 -16
- package/dist/cli.js.map +1 -1
- package/dist/config/config.d.ts +2 -1
- package/dist/config/config.d.ts.map +1 -1
- package/dist/config/config.js +78 -54
- package/dist/config/config.js.map +1 -1
- package/dist/config/types.d.ts +8 -1
- package/dist/config/types.d.ts.map +1 -1
- package/dist/logger.d.ts +1 -0
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +5 -0
- package/dist/logger.js.map +1 -1
- package/dist/plugins/builtin/lint-on-write/index.ts +96 -96
- package/dist/plugins/builtin/notify/index.js +6 -6
- package/dist/plugins/builtin/notify/index.ts +104 -104
- package/dist/repl/commands.js +0 -5
- package/dist/repl/commands.js.map +1 -1
- package/dist/repl/history.d.ts +2 -1
- package/dist/repl/history.d.ts.map +1 -1
- package/dist/repl/history.js.map +1 -1
- package/dist/repl/index.d.ts +2 -0
- package/dist/repl/index.d.ts.map +1 -1
- package/dist/repl/index.js +2 -2
- package/dist/repl/index.js.map +1 -1
- package/dist/setup-wizard.d.ts +1 -1
- package/dist/setup-wizard.d.ts.map +1 -1
- package/dist/setup-wizard.js +137 -105
- package/dist/setup-wizard.js.map +1 -1
- package/dist/skills/builtin/agent-test/SKILL.md +68 -68
- package/dist/skills/builtin/grill-me/SKILL.md +46 -46
- package/dist/skills/builtin/web-scraping/SKILL.md +422 -422
- package/dist/skills/loader.d.ts +16 -0
- package/dist/skills/loader.d.ts.map +1 -1
- package/dist/skills/loader.js +110 -9
- package/dist/skills/loader.js.map +1 -1
- package/dist/tools/id-gen.d.ts +4 -0
- package/dist/tools/id-gen.d.ts.map +1 -0
- package/dist/tools/id-gen.js +12 -0
- package/dist/tools/id-gen.js.map +1 -0
- package/dist/tools/native/project-map.d.ts +2 -2
- package/dist/tools/native/project-map.d.ts.map +1 -1
- package/dist/tools/native/project-map.js +85 -57
- package/dist/tools/native/project-map.js.map +1 -1
- package/dist/tools/native/scaffold.js +113 -113
- package/dist/tools/native/theme.d.ts.map +1 -1
- package/dist/tools/native/theme.js +0 -5
- package/dist/tools/native/theme.js.map +1 -1
- package/dist/tools/parse-tool-call.d.ts +11 -0
- package/dist/tools/parse-tool-call.d.ts.map +1 -0
- package/dist/tools/parse-tool-call.js +85 -0
- package/dist/tools/parse-tool-call.js.map +1 -0
- package/dist/tools/plan.d.ts +2 -0
- package/dist/tools/plan.d.ts.map +1 -1
- package/dist/tools/plan.js +8 -2
- package/dist/tools/plan.js.map +1 -1
- package/dist/tools/subagent.d.ts +0 -8
- package/dist/tools/subagent.d.ts.map +1 -1
- package/dist/tools/subagent.js +15 -94
- package/dist/tools/subagent.js.map +1 -1
- package/dist/tools/todo.d.ts +2 -0
- package/dist/tools/todo.d.ts.map +1 -1
- package/dist/tools/todo.js +8 -2
- package/dist/tools/todo.js.map +1 -1
- package/dist/tools/web-browse.js +7 -7
- package/dist/ui/index.d.ts +1 -15
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +1 -12
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/ink/components/App.d.ts +0 -1
- package/dist/ui/ink/components/App.d.ts.map +1 -1
- package/dist/ui/ink/components/App.js +2 -8
- package/dist/ui/ink/components/App.js.map +1 -1
- package/dist/ui/ink/components/Conversation.d.ts +1 -2
- package/dist/ui/ink/components/Conversation.d.ts.map +1 -1
- package/dist/ui/ink/components/Conversation.js +8 -2
- package/dist/ui/ink/components/Conversation.js.map +1 -1
- package/dist/ui/ink/components/MessageBlock.d.ts +13 -1
- package/dist/ui/ink/components/MessageBlock.d.ts.map +1 -1
- package/dist/ui/ink/components/MessageBlock.js +15 -27
- package/dist/ui/ink/components/MessageBlock.js.map +1 -1
- package/dist/ui/ink/components/PromptInput.d.ts +11 -0
- package/dist/ui/ink/components/PromptInput.d.ts.map +1 -1
- package/dist/ui/ink/components/PromptInput.js +18 -141
- package/dist/ui/ink/components/PromptInput.js.map +1 -1
- package/dist/ui/ink/components/SelectInput.d.ts.map +1 -1
- package/dist/ui/ink/components/SelectInput.js +3 -2
- package/dist/ui/ink/components/SelectInput.js.map +1 -1
- package/dist/ui/ink/components/StatusBar.d.ts.map +1 -1
- package/dist/ui/ink/components/StatusBar.js +14 -6
- package/dist/ui/ink/components/StatusBar.js.map +1 -1
- package/dist/ui/ink/components/StreamBlock.d.ts.map +1 -1
- package/dist/ui/ink/components/StreamBlock.js +3 -3
- package/dist/ui/ink/components/StreamBlock.js.map +1 -1
- package/dist/ui/ink/components/Tags.d.ts +8 -11
- package/dist/ui/ink/components/Tags.d.ts.map +1 -1
- package/dist/ui/ink/components/Tags.js +4 -16
- package/dist/ui/ink/components/Tags.js.map +1 -1
- package/dist/ui/ink/components/ToolCallBlock.d.ts +12 -0
- package/dist/ui/ink/components/ToolCallBlock.d.ts.map +1 -1
- package/dist/ui/ink/components/ToolCallBlock.js +8 -24
- package/dist/ui/ink/components/ToolCallBlock.js.map +1 -1
- package/dist/ui/ink/renderer.d.ts +0 -1
- package/dist/ui/ink/renderer.d.ts.map +1 -1
- package/dist/ui/ink/renderer.js +1 -4
- package/dist/ui/ink/renderer.js.map +1 -1
- package/dist/ui/renderer.d.ts +2 -2
- package/dist/ui/renderer.d.ts.map +1 -1
- package/dist/ui/renderer.js +1 -1
- package/package.json +4 -8
- package/dist/async-map.d.ts +0 -6
- package/dist/async-map.d.ts.map +0 -1
- package/dist/async-map.js +0 -16
- package/dist/async-map.js.map +0 -1
- package/dist/blog-api/types/index.d.ts +0 -2
- package/dist/blog-api/types/index.d.ts.map +0 -1
- package/dist/blog-api/types/index.js +0 -2
- package/dist/blog-api/types/index.js.map +0 -1
- package/dist/examples.d.ts +0 -22
- package/dist/examples.d.ts.map +0 -1
- package/dist/examples.js +0 -45
- package/dist/examples.js.map +0 -1
- package/dist/fibonacci.d.ts +0 -19
- package/dist/fibonacci.d.ts.map +0 -1
- package/dist/fibonacci.js +0 -61
- package/dist/fibonacci.js.map +0 -1
- package/dist/parfymerie-landing/src/App.d.ts +0 -4
- package/dist/parfymerie-landing/src/App.d.ts.map +0 -1
- package/dist/parfymerie-landing/src/App.js +0 -12
- package/dist/parfymerie-landing/src/App.js.map +0 -1
- package/dist/parfymerie-landing/src/main.d.ts +0 -2
- package/dist/parfymerie-landing/src/main.d.ts.map +0 -1
- package/dist/parfymerie-landing/src/main.js +0 -7
- package/dist/parfymerie-landing/src/main.js.map +0 -1
- package/dist/parfymerie-landing/vite.config.d.ts +0 -3
- package/dist/parfymerie-landing/vite.config.d.ts.map +0 -1
- package/dist/parfymerie-landing/vite.config.js +0 -7
- package/dist/parfymerie-landing/vite.config.js.map +0 -1
- package/dist/repl.d.ts +0 -5
- package/dist/repl.d.ts.map +0 -1
- package/dist/repl.js +0 -472
- package/dist/repl.js.map +0 -1
- package/dist/sort.d.ts +0 -8
- package/dist/sort.d.ts.map +0 -1
- package/dist/sort.js +0 -36
- package/dist/sort.js.map +0 -1
- package/dist/stack.d.ts +0 -9
- package/dist/stack.d.ts.map +0 -1
- package/dist/stack.js +0 -22
- package/dist/stack.js.map +0 -1
- package/dist/test-run.d.ts +0 -2
- package/dist/test-run.d.ts.map +0 -1
- package/dist/test-run.js +0 -13
- package/dist/test-run.js.map +0 -1
- package/dist/ui/components/diff-view.d.ts +0 -8
- package/dist/ui/components/diff-view.d.ts.map +0 -1
- package/dist/ui/components/diff-view.js +0 -25
- package/dist/ui/components/diff-view.js.map +0 -1
- package/dist/ui/double-press.d.ts +0 -9
- package/dist/ui/double-press.d.ts.map +0 -1
- package/dist/ui/double-press.js +0 -18
- package/dist/ui/double-press.js.map +0 -1
- package/dist/ui/ink/components/ActivityLog.d.ts +0 -12
- package/dist/ui/ink/components/ActivityLog.d.ts.map +0 -1
- package/dist/ui/ink/components/ActivityLog.js +0 -11
- package/dist/ui/ink/components/ActivityLog.js.map +0 -1
- package/dist/ui/ink/components/AgentStatus.d.ts +0 -13
- package/dist/ui/ink/components/AgentStatus.d.ts.map +0 -1
- package/dist/ui/ink/components/AgentStatus.js +0 -20
- package/dist/ui/ink/components/AgentStatus.js.map +0 -1
- package/dist/ui/ink/components/BottomBar.d.ts +0 -12
- package/dist/ui/ink/components/BottomBar.d.ts.map +0 -1
- package/dist/ui/ink/components/BottomBar.js +0 -22
- package/dist/ui/ink/components/BottomBar.js.map +0 -1
- package/dist/ui/ink/components/CheckboxInput.d.ts +0 -21
- package/dist/ui/ink/components/CheckboxInput.d.ts.map +0 -1
- package/dist/ui/ink/components/CheckboxInput.js +0 -36
- package/dist/ui/ink/components/CheckboxInput.js.map +0 -1
- package/dist/ui/ink/components/InputBox.d.ts +0 -25
- package/dist/ui/ink/components/InputBox.d.ts.map +0 -1
- package/dist/ui/ink/components/InputBox.js +0 -91
- package/dist/ui/ink/components/InputBox.js.map +0 -1
- package/dist/ui/ink/components/ModeSelector.d.ts +0 -8
- package/dist/ui/ink/components/ModeSelector.d.ts.map +0 -1
- package/dist/ui/ink/components/ModeSelector.js +0 -13
- package/dist/ui/ink/components/ModeSelector.js.map +0 -1
- package/dist/ui/ink/components/QuestionBox.d.ts +0 -14
- package/dist/ui/ink/components/QuestionBox.d.ts.map +0 -1
- package/dist/ui/ink/components/QuestionBox.js +0 -35
- package/dist/ui/ink/components/QuestionBox.js.map +0 -1
- package/dist/ui/ink/components/RadioInput.d.ts +0 -17
- package/dist/ui/ink/components/RadioInput.d.ts.map +0 -1
- package/dist/ui/ink/components/RadioInput.js +0 -30
- package/dist/ui/ink/components/RadioInput.js.map +0 -1
- package/dist/ui/ink/components/SliderInput.d.ts +0 -20
- package/dist/ui/ink/components/SliderInput.d.ts.map +0 -1
- package/dist/ui/ink/components/SliderInput.js +0 -40
- package/dist/ui/ink/components/SliderInput.js.map +0 -1
- package/dist/ui/ink/components/StartupScreen.d.ts +0 -19
- package/dist/ui/ink/components/StartupScreen.d.ts.map +0 -1
- package/dist/ui/ink/components/StartupScreen.js +0 -9
- package/dist/ui/ink/components/StartupScreen.js.map +0 -1
- package/dist/ui/ink/components/WelcomeScreen.d.ts +0 -10
- package/dist/ui/ink/components/WelcomeScreen.d.ts.map +0 -1
- package/dist/ui/ink/components/WelcomeScreen.js +0 -8
- package/dist/ui/ink/components/WelcomeScreen.js.map +0 -1
- package/dist/ui/ink/components/Window.d.ts +0 -30
- package/dist/ui/ink/components/Window.d.ts.map +0 -1
- package/dist/ui/ink/components/Window.js +0 -9
- package/dist/ui/ink/components/Window.js.map +0 -1
- package/dist/ui/ink/components/ui/_core.d.ts +0 -113
- package/dist/ui/ink/components/ui/_core.d.ts.map +0 -1
- package/dist/ui/ink/components/ui/_core.js +0 -83
- package/dist/ui/ink/components/ui/_core.js.map +0 -1
- package/dist/ui/ink/components/ui/autocomplete/Autocomplete.d.ts +0 -23
- package/dist/ui/ink/components/ui/autocomplete/Autocomplete.d.ts.map +0 -1
- package/dist/ui/ink/components/ui/autocomplete/Autocomplete.js +0 -56
- package/dist/ui/ink/components/ui/autocomplete/Autocomplete.js.map +0 -1
- package/dist/ui/ink/components/ui/autocomplete/index.d.ts +0 -3
- package/dist/ui/ink/components/ui/autocomplete/index.d.ts.map +0 -1
- package/dist/ui/ink/components/ui/autocomplete/index.js +0 -2
- package/dist/ui/ink/components/ui/autocomplete/index.js.map +0 -1
- package/dist/ui/ink/components/ui/badge/Badge.d.ts +0 -13
- package/dist/ui/ink/components/ui/badge/Badge.d.ts.map +0 -1
- package/dist/ui/ink/components/ui/badge/Badge.js +0 -17
- package/dist/ui/ink/components/ui/badge/Badge.js.map +0 -1
- package/dist/ui/ink/components/ui/badge/index.d.ts +0 -3
- package/dist/ui/ink/components/ui/badge/index.d.ts.map +0 -1
- package/dist/ui/ink/components/ui/badge/index.js +0 -2
- package/dist/ui/ink/components/ui/badge/index.js.map +0 -1
- package/dist/ui/ink/components/ui/divider/Divider.d.ts +0 -12
- package/dist/ui/ink/components/ui/divider/Divider.d.ts.map +0 -1
- package/dist/ui/ink/components/ui/divider/Divider.js +0 -27
- package/dist/ui/ink/components/ui/divider/Divider.js.map +0 -1
- package/dist/ui/ink/components/ui/divider/index.d.ts +0 -3
- package/dist/ui/ink/components/ui/divider/index.d.ts.map +0 -1
- package/dist/ui/ink/components/ui/divider/index.js +0 -2
- package/dist/ui/ink/components/ui/divider/index.js.map +0 -1
- package/dist/ui/ink/components/ui/header/Header.d.ts +0 -13
- package/dist/ui/ink/components/ui/header/Header.d.ts.map +0 -1
- package/dist/ui/ink/components/ui/header/Header.js +0 -33
- package/dist/ui/ink/components/ui/header/Header.js.map +0 -1
- package/dist/ui/ink/components/ui/header/index.d.ts +0 -3
- package/dist/ui/ink/components/ui/header/index.d.ts.map +0 -1
- package/dist/ui/ink/components/ui/header/index.js +0 -2
- package/dist/ui/ink/components/ui/header/index.js.map +0 -1
- package/dist/ui/ink/components/ui/json-viewer/JSONViewer.d.ts +0 -14
- package/dist/ui/ink/components/ui/json-viewer/JSONViewer.d.ts.map +0 -1
- package/dist/ui/ink/components/ui/json-viewer/JSONViewer.js +0 -144
- package/dist/ui/ink/components/ui/json-viewer/JSONViewer.js.map +0 -1
- package/dist/ui/ink/components/ui/json-viewer/index.d.ts +0 -3
- package/dist/ui/ink/components/ui/json-viewer/index.d.ts.map +0 -1
- package/dist/ui/ink/components/ui/json-viewer/index.js +0 -2
- package/dist/ui/ink/components/ui/json-viewer/index.js.map +0 -1
- package/dist/ui/ink/components/ui/key-hint/KeyHint.d.ts +0 -14
- package/dist/ui/ink/components/ui/key-hint/KeyHint.d.ts.map +0 -1
- package/dist/ui/ink/components/ui/key-hint/KeyHint.js +0 -5
- package/dist/ui/ink/components/ui/key-hint/KeyHint.js.map +0 -1
- package/dist/ui/ink/components/ui/key-hint/index.d.ts +0 -3
- package/dist/ui/ink/components/ui/key-hint/index.d.ts.map +0 -1
- package/dist/ui/ink/components/ui/key-hint/index.js +0 -2
- package/dist/ui/ink/components/ui/key-hint/index.js.map +0 -1
- package/dist/ui/ink/components/ui/markdown/Markdown.d.ts +0 -11
- package/dist/ui/ink/components/ui/markdown/Markdown.d.ts.map +0 -1
- package/dist/ui/ink/components/ui/markdown/Markdown.js +0 -129
- package/dist/ui/ink/components/ui/markdown/Markdown.js.map +0 -1
- package/dist/ui/ink/components/ui/markdown/index.d.ts +0 -3
- package/dist/ui/ink/components/ui/markdown/index.d.ts.map +0 -1
- package/dist/ui/ink/components/ui/markdown/index.js +0 -2
- package/dist/ui/ink/components/ui/markdown/index.js.map +0 -1
- package/dist/ui/ink/components/ui/scroll-area/ScrollArea.d.ts +0 -21
- package/dist/ui/ink/components/ui/scroll-area/ScrollArea.d.ts.map +0 -1
- package/dist/ui/ink/components/ui/scroll-area/ScrollArea.js +0 -42
- package/dist/ui/ink/components/ui/scroll-area/ScrollArea.js.map +0 -1
- package/dist/ui/ink/components/ui/scroll-area/index.d.ts +0 -3
- package/dist/ui/ink/components/ui/scroll-area/index.d.ts.map +0 -1
- package/dist/ui/ink/components/ui/scroll-area/index.js +0 -2
- package/dist/ui/ink/components/ui/scroll-area/index.js.map +0 -1
- package/dist/ui/ink/components/ui/select/Select.d.ts +0 -19
- package/dist/ui/ink/components/ui/select/Select.d.ts.map +0 -1
- package/dist/ui/ink/components/ui/select/Select.js +0 -73
- package/dist/ui/ink/components/ui/select/Select.js.map +0 -1
- package/dist/ui/ink/components/ui/select/index.d.ts +0 -3
- package/dist/ui/ink/components/ui/select/index.d.ts.map +0 -1
- package/dist/ui/ink/components/ui/select/index.js +0 -2
- package/dist/ui/ink/components/ui/select/index.js.map +0 -1
- package/dist/ui/ink/components/ui/spinner/Spinner.d.ts +0 -14
- package/dist/ui/ink/components/ui/spinner/Spinner.d.ts.map +0 -1
- package/dist/ui/ink/components/ui/spinner/Spinner.js +0 -16
- package/dist/ui/ink/components/ui/spinner/Spinner.js.map +0 -1
- package/dist/ui/ink/components/ui/spinner/index.d.ts +0 -3
- package/dist/ui/ink/components/ui/spinner/index.d.ts.map +0 -1
- package/dist/ui/ink/components/ui/spinner/index.js +0 -2
- package/dist/ui/ink/components/ui/spinner/index.js.map +0 -1
- package/dist/ui/ink/components/ui/status-indicator/StatusIndicator.d.ts +0 -12
- package/dist/ui/ink/components/ui/status-indicator/StatusIndicator.d.ts.map +0 -1
- package/dist/ui/ink/components/ui/status-indicator/StatusIndicator.js +0 -39
- package/dist/ui/ink/components/ui/status-indicator/StatusIndicator.js.map +0 -1
- package/dist/ui/ink/components/ui/status-indicator/index.d.ts +0 -3
- package/dist/ui/ink/components/ui/status-indicator/index.d.ts.map +0 -1
- package/dist/ui/ink/components/ui/status-indicator/index.js +0 -2
- package/dist/ui/ink/components/ui/status-indicator/index.js.map +0 -1
- package/dist/ui/ink/components/ui/streaming-text/StreamingText.d.ts +0 -13
- package/dist/ui/ink/components/ui/streaming-text/StreamingText.d.ts.map +0 -1
- package/dist/ui/ink/components/ui/streaming-text/StreamingText.js +0 -22
- package/dist/ui/ink/components/ui/streaming-text/StreamingText.js.map +0 -1
- package/dist/ui/ink/components/ui/streaming-text/index.d.ts +0 -3
- package/dist/ui/ink/components/ui/streaming-text/index.d.ts.map +0 -1
- package/dist/ui/ink/components/ui/streaming-text/index.js +0 -2
- package/dist/ui/ink/components/ui/streaming-text/index.js.map +0 -1
- package/dist/ui/ink/components/ui/tabs/Tabs.d.ts +0 -20
- package/dist/ui/ink/components/ui/tabs/Tabs.d.ts.map +0 -1
- package/dist/ui/ink/components/ui/tabs/Tabs.js +0 -53
- package/dist/ui/ink/components/ui/tabs/Tabs.js.map +0 -1
- package/dist/ui/ink/components/ui/tabs/index.d.ts +0 -3
- package/dist/ui/ink/components/ui/tabs/index.d.ts.map +0 -1
- package/dist/ui/ink/components/ui/tabs/index.js +0 -2
- package/dist/ui/ink/components/ui/tabs/index.js.map +0 -1
- package/dist/ui/ink/components/ui/text-input/TextInput.d.ts +0 -22
- package/dist/ui/ink/components/ui/text-input/TextInput.d.ts.map +0 -1
- package/dist/ui/ink/components/ui/text-input/TextInput.js +0 -57
- package/dist/ui/ink/components/ui/text-input/TextInput.js.map +0 -1
- package/dist/ui/ink/components/ui/text-input/index.d.ts +0 -3
- package/dist/ui/ink/components/ui/text-input/index.d.ts.map +0 -1
- package/dist/ui/ink/components/ui/text-input/index.js +0 -2
- package/dist/ui/ink/components/ui/text-input/index.js.map +0 -1
- package/dist/ui/ink/components/ui/token-counter/TokenCounter.d.ts +0 -17
- package/dist/ui/ink/components/ui/token-counter/TokenCounter.d.ts.map +0 -1
- package/dist/ui/ink/components/ui/token-counter/TokenCounter.js +0 -38
- package/dist/ui/ink/components/ui/token-counter/TokenCounter.js.map +0 -1
- package/dist/ui/ink/components/ui/token-counter/index.d.ts +0 -3
- package/dist/ui/ink/components/ui/token-counter/index.d.ts.map +0 -1
- package/dist/ui/ink/components/ui/token-counter/index.js +0 -2
- package/dist/ui/ink/components/ui/token-counter/index.js.map +0 -1
- package/dist/ui/ink/components/ui/typewriter/Typewriter.d.ts +0 -14
- package/dist/ui/ink/components/ui/typewriter/Typewriter.d.ts.map +0 -1
- package/dist/ui/ink/components/ui/typewriter/Typewriter.js +0 -57
- package/dist/ui/ink/components/ui/typewriter/Typewriter.js.map +0 -1
- package/dist/ui/ink/components/ui/typewriter/index.d.ts +0 -3
- package/dist/ui/ink/components/ui/typewriter/index.d.ts.map +0 -1
- package/dist/ui/ink/components/ui/typewriter/index.js +0 -2
- package/dist/ui/ink/components/ui/typewriter/index.js.map +0 -1
- package/dist/ui/opentui-renderer.d.ts +0 -53
- package/dist/ui/opentui-renderer.d.ts.map +0 -1
- package/dist/ui/opentui-renderer.js +0 -463
- package/dist/ui/opentui-renderer.js.map +0 -1
- package/dist/utils/memoize.d.ts +0 -14
- package/dist/utils/memoize.d.ts.map +0 -1
- package/dist/utils/memoize.js +0 -51
- package/dist/utils/memoize.js.map +0 -1
- package/dist/utils/types.d.ts +0 -6
- package/dist/utils/types.d.ts.map +0 -1
- package/dist/utils/types.js +0 -2
- package/dist/utils/types.js.map +0 -1
- package/dist/utils.d.ts +0 -22
- package/dist/utils.d.ts.map +0 -1
- package/dist/utils.js +0 -81
- package/dist/utils.js.map +0 -1
package/dist/agent/loop.js
CHANGED
|
@@ -1,85 +1,55 @@
|
|
|
1
|
-
import { readFileSync, existsSync } from
|
|
2
|
-
import { join } from
|
|
3
|
-
import {
|
|
4
|
-
import { parseFileRefs } from
|
|
5
|
-
import { ContextManager } from
|
|
6
|
-
import { autoLearnFromError } from
|
|
7
|
-
import { collectCodingStyle } from
|
|
8
|
-
import { ModeRegistry } from
|
|
9
|
-
import { applyMode } from
|
|
10
|
-
import { loadSkill, loadSkillsFromRefs, enforceSkillBudget, preloadSkillsByKeywords, isSkillRequest, listInstalledSkills } from
|
|
11
|
-
import { runPreToolUseHooks, runPostToolUseHooks } from
|
|
12
|
-
import { isMCPCommand, parseMCPCommand, callMCPTool } from
|
|
13
|
-
import { showPlan, getPlanPendingCount, clearPlan, getNextPendingStep, updatePlanStep, extractDirFromStepTitle } from
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
36
|
-
import {
|
|
37
|
-
import {
|
|
38
|
-
import {
|
|
39
|
-
import {
|
|
40
|
-
import {
|
|
41
|
-
import {
|
|
42
|
-
import {
|
|
43
|
-
import {
|
|
44
|
-
import {
|
|
45
|
-
import {
|
|
46
|
-
import {
|
|
47
|
-
import {
|
|
48
|
-
import {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
* достаточно информации для модели чтобы продолжить работу.
|
|
54
|
-
*
|
|
55
|
-
* Лимиты подобраны эмпирически: достаточно для маленьких моделей
|
|
56
|
-
* понять что произошло, но не занимают весь контекст.
|
|
57
|
-
*/
|
|
58
|
-
function truncateToolOutput(toolName, output, exitCode) {
|
|
59
|
-
// Ошибки обрезаем мягче — модели нужен контекст для диагностики
|
|
60
|
-
if (exitCode !== 0) {
|
|
61
|
-
return output.length > 3000 ? output.slice(0, 3000) + '\n...[truncated]' : output;
|
|
62
|
-
}
|
|
63
|
-
// Лимиты для успешных операций (в символах)
|
|
64
|
-
const LIMITS = {
|
|
65
|
-
read_file: 2000, // модель может ссылаться на строки
|
|
66
|
-
write_file: 200, // достаточно "Written X bytes"
|
|
67
|
-
edit_file: 300, // нужно знать что изменилось
|
|
68
|
-
bash: 1500, // build output, git status
|
|
69
|
-
list_dir: 1500, // структура обычно компактна
|
|
70
|
-
glob_search: 1000, // список файлов
|
|
71
|
-
grep_search: 1500, // результаты поиска
|
|
72
|
-
tree: 1500, // дерево файлов
|
|
73
|
-
subagent: 500, // краткое резюме от субагента
|
|
74
|
-
web_search: 1000, // результаты поиска
|
|
75
|
-
web_fetch: 1500, // контент страницы
|
|
76
|
-
web_browse: 2000, // browser interaction results
|
|
77
|
-
};
|
|
78
|
-
const limit = LIMITS[toolName] ?? 800;
|
|
79
|
-
if (output.length <= limit)
|
|
80
|
-
return output;
|
|
81
|
-
return output.slice(0, limit) + '\n...[truncated]';
|
|
82
|
-
}
|
|
1
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
2
|
+
import { join, isAbsolute, relative } from "node:path";
|
|
3
|
+
import { parseToolCalls } from "../tools/parse-tool-call.js";
|
|
4
|
+
import { parseFileRefs } from "../file-loader.js";
|
|
5
|
+
import { ContextManager } from "./context.js";
|
|
6
|
+
import { autoLearnFromError } from "./memory.js";
|
|
7
|
+
import { collectCodingStyle } from "./user-profile.js";
|
|
8
|
+
import { ModeRegistry } from "./modes/registry.js";
|
|
9
|
+
import { applyMode } from "./modes/applier.js";
|
|
10
|
+
import { loadSkill, loadSkillsFromRefs, enforceSkillBudget, preloadSkillsByKeywords, isSkillRequest, listInstalledSkills, } from "../skills/loader.js";
|
|
11
|
+
import { runPreToolUseHooks, runPostToolUseHooks } from "../tools/hooks.js";
|
|
12
|
+
import { isMCPCommand, parseMCPCommand, callMCPTool } from "../tools/mcp.js";
|
|
13
|
+
import { showPlan, getPlanPendingCount, clearPlan, getNextPendingStep, updatePlanStep, extractDirFromStepTitle, } from "../tools/plan.js";
|
|
14
|
+
import { generateToolCallId } from "../tools/id-gen.js";
|
|
15
|
+
import { checkFileSyntax } from "../tools/syntax-check.js";
|
|
16
|
+
import { loadConfig, getLLMConfig, getGroundingConfig, } from "../config/config.js";
|
|
17
|
+
import { buildSystemPrompt } from "./system-prompt.js";
|
|
18
|
+
import { logInfo, logWarn, logError, logDebug, logToolCall, setStatusWriter, writeStatus, } from "../logger.js";
|
|
19
|
+
import { buildSubagentContext, runIsolatedLoop, doChat, } from "../tools/subagent.js";
|
|
20
|
+
import { SubagentRenderer } from "../ui/subagent-renderer.js";
|
|
21
|
+
import { ToolExecutor } from "./tool-executor.js";
|
|
22
|
+
import { ErrorEscalator } from "./error-escalation.js";
|
|
23
|
+
import { WriteEnforcer } from "./write-enforcer.js";
|
|
24
|
+
import { PluginManager } from "../plugins/manager.js";
|
|
25
|
+
import { loadAllPluginsWithInfo } from "../plugins/loader.js";
|
|
26
|
+
import { handleToolAfter as lintHandleToolAfter } from "../plugins/builtin/lint-on-write/index.js";
|
|
27
|
+
import { handleAgentDone, handleToolBlocked, handleToolAfter as notifyHandleToolAfter, } from "../plugins/builtin/notify/index.js";
|
|
28
|
+
import { planHandler } from "../tools/native/plan.js";
|
|
29
|
+
import { todoHandler } from "../tools/native/todo.js";
|
|
30
|
+
import { questionHandler } from "../tools/native/question.js";
|
|
31
|
+
import { approveHandler } from "../tools/native/approve.js";
|
|
32
|
+
import { userProfileHandler } from "../tools/native/user-profile.js";
|
|
33
|
+
import { themeHandler } from "../tools/native/theme.js";
|
|
34
|
+
import { scaffoldHandler } from "../tools/native/scaffold.js";
|
|
35
|
+
import { projectMapHandler } from "../tools/native/project-map.js";
|
|
36
|
+
import { bashHandler } from "../tools/bash.js";
|
|
37
|
+
import { readFileHandler } from "../tools/fs/read-file.js";
|
|
38
|
+
import { writeFileHandler } from "../tools/fs/write-file.js";
|
|
39
|
+
import { editFileHandler } from "../tools/fs/edit-file.js";
|
|
40
|
+
import { listDirHandler } from "../tools/fs/list-dir.js";
|
|
41
|
+
import { createDirHandler } from "../tools/fs/create-dir.js";
|
|
42
|
+
import { deleteFileHandler } from "../tools/fs/delete-file.js";
|
|
43
|
+
import { copyFileHandler } from "../tools/fs/copy-file.js";
|
|
44
|
+
import { moveFileHandler } from "../tools/fs/move-file.js";
|
|
45
|
+
import { fileInfoHandler } from "../tools/fs/file-info.js";
|
|
46
|
+
import { globSearchHandler } from "../tools/fs/glob-search.js";
|
|
47
|
+
import { grepSearchHandler } from "../tools/fs/grep-search.js";
|
|
48
|
+
import { treeHandler } from "../tools/fs/tree.js";
|
|
49
|
+
import { webSearchHandler } from "../tools/web-search.js";
|
|
50
|
+
import { webFetchHandler } from "../tools/web-fetch.js";
|
|
51
|
+
import { webBrowseHandler } from "../tools/web-browse.js";
|
|
52
|
+
import { truncateOutput } from "./tool-executor.js";
|
|
83
53
|
/**
|
|
84
54
|
* Основной цикл ReAct-агента.
|
|
85
55
|
*
|
|
@@ -115,64 +85,16 @@ function extractToolCallFallback(response) {
|
|
|
115
85
|
const m = response.match(re);
|
|
116
86
|
if (m) {
|
|
117
87
|
let cmd = m[1];
|
|
118
|
-
cmd = cmd
|
|
119
|
-
|
|
88
|
+
cmd = cmd
|
|
89
|
+
.replace(/\\n/g, "\n")
|
|
90
|
+
.replace(/\\t/g, "\t")
|
|
91
|
+
.replace(/\\"/g, '"')
|
|
92
|
+
.replace(/\\\\/g, "\\");
|
|
93
|
+
return { name: "bash", cmd };
|
|
120
94
|
}
|
|
121
95
|
}
|
|
122
96
|
return null;
|
|
123
97
|
}
|
|
124
|
-
/**
|
|
125
|
-
* Запускает specialist'а для решения повторяющейся ошибки.
|
|
126
|
-
*
|
|
127
|
-
* Specialist — это изолированный суб-агент со свежим контекстом,
|
|
128
|
-
* который получает задачу "диагностируй и исправь ошибку инструмента X".
|
|
129
|
-
*
|
|
130
|
-
* Алгоритм:
|
|
131
|
-
* 1. Формирует задачу через buildSpecialistTask()
|
|
132
|
-
* 2. Собирает контекст проекта через buildSubagentContext()
|
|
133
|
-
* 3. Запускает runIsolatedLoop() с ограничением maxSteps
|
|
134
|
-
* 4. Заменяет результат ошибки в messages[] на результат specialist'а
|
|
135
|
-
* 5. Трекает созданные specialist'ом файлы
|
|
136
|
-
*
|
|
137
|
-
* @returns true если specialist отработал (основной цикл должен continue),
|
|
138
|
-
* false если specialist упал (ошибка остаётся в messages)
|
|
139
|
-
*/
|
|
140
|
-
async function spawnSpecialist(p) {
|
|
141
|
-
if (!p.quiet) {
|
|
142
|
-
p.renderer?.writeBracketTag('specialist', `solving '${p.errorOutput.slice(0, 60).replace(/\n/g, ' ')}' error...`);
|
|
143
|
-
}
|
|
144
|
-
const task = buildSpecialistTask(p.toolName, p.toolArgs, p.errorOutput, p.enrichedPrompt, p.errorCount);
|
|
145
|
-
const ctx = buildSubagentContext(process.cwd());
|
|
146
|
-
const sub = new SubagentRenderer();
|
|
147
|
-
try {
|
|
148
|
-
logInfo('LOOP', `spawning specialist for ${p.toolName} (attempt #${p.errorCount})`);
|
|
149
|
-
const r = await runIsolatedLoop(p.provider, p.registry, task, ctx, process.cwd(), p.quiet, sub, p.toolExecutor, p.modeConfig.subagentMode, p.llmCfg.toolModel, p.specialistCfg.maxSteps);
|
|
150
|
-
for (let i = p.messages.length - 1; i >= 0; i--) {
|
|
151
|
-
if (p.messages[i].role === 'tool' && p.messages[i].tool_call_id === p.toolId) {
|
|
152
|
-
const text = r.errors.length > 0
|
|
153
|
-
? `[Specialist attempted to fix "${p.toolName}" but failed:\n${r.errors.join('\n')}\n${r.summary}]\nTry a different approach.`
|
|
154
|
-
: `[Specialist resolved "${p.toolName}" error.\n${r.summary}]\nContinue with the task.`;
|
|
155
|
-
p.messages[i].content = truncateToolOutput(p.toolName, text, 0);
|
|
156
|
-
break;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
for (const f of r.createdFiles) {
|
|
160
|
-
if (!p.createdFiles.includes(f))
|
|
161
|
-
p.createdFiles.push(f);
|
|
162
|
-
}
|
|
163
|
-
const buf = sub.getBuffer();
|
|
164
|
-
if (!p.quiet && p.renderer?.writeToolBuffer)
|
|
165
|
-
p.renderer.writeToolBuffer({ lines: buf.lines, toolCalls: buf.toolCalls });
|
|
166
|
-
logInfo('LOOP', `specialist done | files: ${r.createdFiles.length} | turns: ${r.turns} | errors: ${r.errors.length}`);
|
|
167
|
-
return true;
|
|
168
|
-
}
|
|
169
|
-
catch (e) {
|
|
170
|
-
logError('LOOP', `specialist error: ${e.message}`);
|
|
171
|
-
if (!p.quiet)
|
|
172
|
-
p.renderer?.writeError(` [specialist failed: ${e.message}]`);
|
|
173
|
-
return false;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
98
|
/**
|
|
177
99
|
* Запускает основной цикл ReAct-агента.
|
|
178
100
|
*
|
|
@@ -196,30 +118,39 @@ async function spawnSpecialist(p) {
|
|
|
196
118
|
* @param options - дополнительные опции (quiet, showThinking, mode)
|
|
197
119
|
*/
|
|
198
120
|
export async function runAgentLoop(provider, registry, prompt, contextManager, previousMessages, renderer, providerName, options) {
|
|
199
|
-
logInfo(
|
|
121
|
+
logInfo("LOOP", `runAgentLoop started: "${prompt.slice(0, 50)}"`);
|
|
200
122
|
renderer?.resetAbort?.();
|
|
201
123
|
// Парсинг ссылок на файлы в промпте (формат @path/to/file)
|
|
202
124
|
const { prompt: enrichedPrompt, files } = await parseFileRefs(prompt);
|
|
203
|
-
logDebug(
|
|
125
|
+
logDebug("LOOP", `parseFileRefs: ${files.length} files, prompt len: ${enrichedPrompt.length}`);
|
|
204
126
|
const quiet = options?.quiet ?? false;
|
|
205
127
|
// ─── ModeRegistry: конфигурация режима ──
|
|
206
128
|
const modeRegistry = new ModeRegistry();
|
|
207
|
-
const modeConfig = modeRegistry.getOrFallback(options?.mode ||
|
|
208
|
-
const applied = applyMode(modeConfig, {
|
|
129
|
+
const modeConfig = modeRegistry.getOrFallback(options?.mode || "base");
|
|
130
|
+
const applied = applyMode(modeConfig, {
|
|
131
|
+
quiet,
|
|
132
|
+
showThinking: options?.showThinking,
|
|
133
|
+
});
|
|
209
134
|
// ─── Behaviors из ModeConfig ──────────────────────────
|
|
210
135
|
const behaviors = modeConfig.behaviors;
|
|
211
136
|
const planEnforcement = behaviors.planEnforcement !== false;
|
|
212
|
-
const directWriteStreakMax = typeof behaviors.directWriteStreakLimit ===
|
|
213
|
-
? behaviors.directWriteStreakLimit
|
|
214
|
-
|
|
215
|
-
|
|
137
|
+
const directWriteStreakMax = typeof behaviors.directWriteStreakLimit === "number"
|
|
138
|
+
? behaviors.directWriteStreakLimit
|
|
139
|
+
: behaviors.directWriteStreak !== false
|
|
140
|
+
? 3
|
|
141
|
+
: Infinity;
|
|
142
|
+
const continuationNudgeMax = typeof behaviors.continuationNudgeLimit === "number"
|
|
143
|
+
? behaviors.continuationNudgeLimit
|
|
144
|
+
: behaviors.continuationNudge !== false
|
|
145
|
+
? 2
|
|
146
|
+
: 0;
|
|
216
147
|
const readConsolidation = behaviors.readConsolidation !== false;
|
|
217
148
|
// Автозагрузка скилов из промпта (если пользователь указал @skill-name)
|
|
218
149
|
const loadedSkills = loadSkillsFromRefs(prompt, process.cwd());
|
|
219
|
-
const loadedSkillNames = new Set(loadedSkills.map(s => s.name));
|
|
150
|
+
const loadedSkillNames = new Set(loadedSkills.map((s) => s.name));
|
|
220
151
|
if (loadedSkills.length > 0) {
|
|
221
|
-
logInfo(
|
|
222
|
-
renderer?.writeInfo(` Loaded ${loadedSkills.length} skill(s): ${loadedSkills.map(s => s.name).join(
|
|
152
|
+
logInfo("LOOP", `loaded ${loadedSkills.length} skills: ${loadedSkills.map((s) => s.name).join(", ")}`);
|
|
153
|
+
renderer?.writeInfo(` Loaded ${loadedSkills.length} skill(s): ${loadedSkills.map((s) => s.name).join(", ")}`);
|
|
223
154
|
}
|
|
224
155
|
// Принудительная загрузка скилов из ModeConfig
|
|
225
156
|
for (const skillName of applied.forcedSkills) {
|
|
@@ -228,11 +159,11 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
228
159
|
if (skill) {
|
|
229
160
|
loadedSkills.push(skill);
|
|
230
161
|
loadedSkillNames.add(skillName);
|
|
231
|
-
logInfo(
|
|
162
|
+
logInfo("LOOP", `auto-loaded ${skillName} skill for ${modeConfig.id} mode`);
|
|
232
163
|
renderer?.writeInfo(` Auto-loaded @${skillName} skill for ${modeConfig.id} mode`);
|
|
233
164
|
}
|
|
234
165
|
else {
|
|
235
|
-
logWarn(
|
|
166
|
+
logWarn("LOOP", `${skillName} skill not found for ${modeConfig.id} mode`);
|
|
236
167
|
renderer?.writeWarning(` Warning: @${skillName} skill not found (required for ${modeConfig.id} mode)`);
|
|
237
168
|
}
|
|
238
169
|
}
|
|
@@ -244,8 +175,8 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
244
175
|
const budget = applied.skillTokenBudget;
|
|
245
176
|
const { allowed, dropped } = enforceSkillBudget(loadedSkills, budget);
|
|
246
177
|
if (dropped.length > 0) {
|
|
247
|
-
renderer?.writeWarning(` Skill limit (${maxSkills} max in ${modeConfig.id} mode): dropped ${dropped.join(
|
|
248
|
-
logInfo(
|
|
178
|
+
renderer?.writeWarning(` Skill limit (${maxSkills} max in ${modeConfig.id} mode): dropped ${dropped.join(", ")}`);
|
|
179
|
+
logInfo("LOOP", `skill limit: dropped ${dropped.join(", ")} (budget: ${budget} tokens)`);
|
|
249
180
|
loadedSkills.length = 0;
|
|
250
181
|
loadedSkills.push(...allowed);
|
|
251
182
|
for (const s of dropped)
|
|
@@ -253,33 +184,41 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
253
184
|
}
|
|
254
185
|
}
|
|
255
186
|
// Pre-load скиллов по ключевым словам (без language-specific хардкода)
|
|
256
|
-
if (loadedSkills.length === 0 && options?.mode !==
|
|
187
|
+
if (loadedSkills.length === 0 && options?.mode !== "fast") {
|
|
257
188
|
const keywordSkills = preloadSkillsByKeywords(prompt, process.cwd());
|
|
258
189
|
for (const skill of keywordSkills) {
|
|
259
190
|
if (!loadedSkillNames.has(skill.name)) {
|
|
260
191
|
loadedSkills.push(skill);
|
|
261
192
|
loadedSkillNames.add(skill.name);
|
|
262
|
-
logInfo(
|
|
193
|
+
logInfo("LOOP", `keyword preloaded skill: ${skill.name}`);
|
|
263
194
|
renderer?.writeInfo(` Auto-loaded @${skill.name} (keyword match)`);
|
|
264
195
|
}
|
|
265
196
|
}
|
|
266
197
|
// Если пользователь явно просит "используй скилл", но ничего не нашлось — подсказываем
|
|
267
198
|
if (keywordSkills.length === 0 && isSkillRequest(prompt)) {
|
|
268
199
|
const allSkills = listInstalledSkills(process.cwd());
|
|
269
|
-
renderer?.writeWarning(` You asked for a skill but none matched. Use @skill-name in your prompt to load one. Installed: ${allSkills
|
|
200
|
+
renderer?.writeWarning(` You asked for a skill but none matched. Use @skill-name in your prompt to load one. Installed: ${allSkills
|
|
201
|
+
.slice(0, 10)
|
|
202
|
+
.map((s) => "@" + s)
|
|
203
|
+
.join(", ")}... (${allSkills.length} total)`);
|
|
270
204
|
}
|
|
271
205
|
}
|
|
272
206
|
if (files.length > 0) {
|
|
273
207
|
const totalTokens = files.reduce((sum, f) => sum + f.tokens, 0);
|
|
274
|
-
const totalLines = files.reduce((sum, f) => sum + f.content.split(
|
|
275
|
-
logInfo(
|
|
208
|
+
const totalLines = files.reduce((sum, f) => sum + f.content.split("\n").length, 0);
|
|
209
|
+
logInfo("LOOP", `loaded ${files.length} files (${totalLines} lines, ~${totalTokens} tokens)`);
|
|
276
210
|
renderer?.writeInfo(` Loaded ${files.length} files (${totalLines} lines, ~${totalTokens} tokens)`);
|
|
277
211
|
}
|
|
278
212
|
setStatusWriter(renderer ? (msg) => renderer.writeDim(` ${msg}`) : null);
|
|
279
|
-
writeStatus(
|
|
213
|
+
writeStatus("Initializing...");
|
|
280
214
|
// ─── PluginManager: загрузка и активация плагинов ──
|
|
281
215
|
const pluginManager = new PluginManager();
|
|
282
|
-
const pluginLogger = {
|
|
216
|
+
const pluginLogger = {
|
|
217
|
+
info: logInfo.bind(null, "PLUGIN"),
|
|
218
|
+
warn: logWarn.bind(null, "PLUGIN"),
|
|
219
|
+
error: logError.bind(null, "PLUGIN"),
|
|
220
|
+
debug: logDebug.bind(null, "PLUGIN"),
|
|
221
|
+
};
|
|
283
222
|
const pluginCtx = { cwd: process.cwd(), config: {}, logger: pluginLogger };
|
|
284
223
|
// Загружаем все плагины (builtin + project + user)
|
|
285
224
|
const pluginInfos = await loadAllPluginsWithInfo(pluginManager);
|
|
@@ -288,7 +227,7 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
288
227
|
await pluginManager.activateAll(modeConfig.plugins, pluginCtx);
|
|
289
228
|
}
|
|
290
229
|
// Авто-активация builtin плагинов (все моды)
|
|
291
|
-
const builtinPlugins = pluginInfos.filter(p => p.source ===
|
|
230
|
+
const builtinPlugins = pluginInfos.filter((p) => p.source === "builtin");
|
|
292
231
|
for (const bp of builtinPlugins) {
|
|
293
232
|
if (!pluginManager.isActive(bp.name)) {
|
|
294
233
|
await pluginManager.activate(bp.name, pluginCtx);
|
|
@@ -302,45 +241,38 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
302
241
|
}
|
|
303
242
|
}
|
|
304
243
|
// Регистрация обработчиков событий для всех активных плагинов
|
|
305
|
-
if (pluginManager.isActive(
|
|
306
|
-
pluginManager.on(
|
|
307
|
-
return lintHandleToolAfter(toolName, args, result, process.cwd(),
|
|
244
|
+
if (pluginManager.isActive("lint-on-write")) {
|
|
245
|
+
pluginManager.on("tool:after", "lint-on-write", (toolName, args, result) => {
|
|
246
|
+
return lintHandleToolAfter(toolName, args, result, process.cwd(), ".lint-detected");
|
|
308
247
|
});
|
|
309
248
|
}
|
|
310
|
-
if (pluginManager.isActive(
|
|
311
|
-
pluginManager.on(
|
|
312
|
-
pluginManager.on(
|
|
313
|
-
pluginManager.on(
|
|
249
|
+
if (pluginManager.isActive("notify")) {
|
|
250
|
+
pluginManager.on("agent:done", "notify", () => handleAgentDone());
|
|
251
|
+
pluginManager.on("tool:blocked", "notify", (toolName, args) => handleToolBlocked(toolName, args));
|
|
252
|
+
pluginManager.on("tool:after", "notify", (toolName, args, result) => notifyHandleToolAfter(toolName, args, result));
|
|
314
253
|
}
|
|
315
254
|
const pluginSections = await pluginManager.getPromptSections();
|
|
316
255
|
// Сборка system prompt (включает инструкции, информацию о проекте, скилы, память, плагины)
|
|
317
256
|
const sysStart = performance.now();
|
|
318
|
-
const systemPrompt = buildSystemPrompt(loadedSkills.length > 0 ? loadedSkills : undefined, options?.mode, pluginSections);
|
|
257
|
+
const systemPrompt = buildSystemPrompt(loadedSkills.length > 0 ? loadedSkills : undefined, options?.mode, pluginSections, options?.flags);
|
|
319
258
|
const sysElapsed = ((performance.now() - sysStart) / 1000).toFixed(1);
|
|
320
259
|
writeStatus(`Done (${sysElapsed}s)`);
|
|
321
260
|
setStatusWriter(null);
|
|
322
261
|
// Начальное состояние истории: system prompt + предыдущие сообщения + текущий промпт
|
|
323
262
|
const messages = [
|
|
324
|
-
{ role:
|
|
263
|
+
{ role: "system", content: systemPrompt },
|
|
325
264
|
...(previousMessages || []),
|
|
326
|
-
{ role:
|
|
265
|
+
{ role: "user", content: enrichedPrompt },
|
|
327
266
|
];
|
|
328
267
|
const ctx = contextManager ?? new ContextManager(provider.contextWindow);
|
|
329
268
|
const maxSteps = config.agent.maxSteps ?? 100;
|
|
330
269
|
let consecutiveEmpty = 0; // счётчик пустых ответов подряд
|
|
331
|
-
let pendingSyntaxError =
|
|
270
|
+
let pendingSyntaxError = "";
|
|
332
271
|
let aborted = false;
|
|
333
272
|
let continuationNudges = 0; // счётчик подсказок "продолжай работать"
|
|
334
273
|
let consecutiveMissingGrounding = 0; // счётчик пропущенных grounding-маркеров подряд
|
|
335
|
-
let
|
|
336
|
-
|
|
337
|
-
const consecutiveToolErrors = new Map();
|
|
338
|
-
/** Общий счётчик ошибок за сессию */
|
|
339
|
-
let totalToolErrors = 0;
|
|
340
|
-
/** Стратегия обработки ошибок: 'normal' | 'subagent-hint' | 'force-subagent' */
|
|
341
|
-
let errorStrategy = 'normal';
|
|
342
|
-
/** Флаг: specialist уже был запущен для текущей ошибки (чтобы не дублировать) */
|
|
343
|
-
let specialistSpawnedFor = null;
|
|
274
|
+
let groundingRetryCount = 0; // сколько раз был CRITICAL retry подряд
|
|
275
|
+
let consecutiveInvalidCalls = 0; // счётчик невалидных tool call подряд
|
|
344
276
|
/** Счётчик бесполезных действий (list_dir, glob_search, todo list без результата) */
|
|
345
277
|
let uselessActionCount = 0;
|
|
346
278
|
/** История последних чтений файлов для.detect повторных чтений */
|
|
@@ -349,17 +281,39 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
349
281
|
const recentGrepPatterns = [];
|
|
350
282
|
/** История последних вопросов для detect повторных вопросов */
|
|
351
283
|
const recentQuestions = [];
|
|
284
|
+
/** История последних bash-команд для detect циклических вызовов */
|
|
285
|
+
const recentBashCommands = [];
|
|
352
286
|
// ─── ToolExecutor: единый диспетчер инструментов ──
|
|
353
287
|
const toolExecutor = new ToolExecutor();
|
|
354
288
|
toolExecutor.setPluginManager(pluginManager);
|
|
355
289
|
toolExecutor.registerAll([
|
|
356
|
-
planHandler,
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
290
|
+
planHandler,
|
|
291
|
+
todoHandler,
|
|
292
|
+
questionHandler,
|
|
293
|
+
approveHandler,
|
|
294
|
+
userProfileHandler,
|
|
295
|
+
themeHandler,
|
|
296
|
+
scaffoldHandler,
|
|
297
|
+
projectMapHandler,
|
|
298
|
+
bashHandler,
|
|
299
|
+
readFileHandler,
|
|
300
|
+
writeFileHandler,
|
|
301
|
+
editFileHandler,
|
|
302
|
+
listDirHandler,
|
|
303
|
+
createDirHandler,
|
|
304
|
+
deleteFileHandler,
|
|
305
|
+
copyFileHandler,
|
|
306
|
+
moveFileHandler,
|
|
307
|
+
fileInfoHandler,
|
|
308
|
+
globSearchHandler,
|
|
309
|
+
grepSearchHandler,
|
|
310
|
+
treeHandler,
|
|
311
|
+
webSearchHandler,
|
|
312
|
+
webFetchHandler,
|
|
313
|
+
webBrowseHandler,
|
|
362
314
|
]);
|
|
315
|
+
const errorEscalator = new ErrorEscalator();
|
|
316
|
+
const writeEnforcer = new WriteEnforcer();
|
|
363
317
|
// Reset abort signal for this run
|
|
364
318
|
if (renderer?.abortSignal === undefined) {
|
|
365
319
|
// Renderer doesn't support abort — fine
|
|
@@ -379,7 +333,7 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
379
333
|
}
|
|
380
334
|
let skillsAutoLoaded = false;
|
|
381
335
|
const createdFiles = []; // отслеживание созданных/изменённых файлов
|
|
382
|
-
logDebug(
|
|
336
|
+
logDebug("LOOP", `step ${step + 1}/${maxSteps} starting, ${messages.length} messages`);
|
|
383
337
|
// Инкрементальный подсчёт — считаем только новые сообщения (быстрее в ~100x на больших историях)
|
|
384
338
|
if (step === 0) {
|
|
385
339
|
await ctx.updateTokens(messages);
|
|
@@ -387,8 +341,8 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
387
341
|
else {
|
|
388
342
|
await ctx.updateTokensIncremental(messages);
|
|
389
343
|
}
|
|
390
|
-
const skillNames = loadedSkills.map(s => s.name).join(
|
|
391
|
-
const usageText = ctx.formatUsage() + (skillNames ? ` │ skills: ${skillNames}` :
|
|
344
|
+
const skillNames = loadedSkills.map((s) => s.name).join(", ");
|
|
345
|
+
const usageText = ctx.formatUsage() + (skillNames ? ` │ skills: ${skillNames}` : "");
|
|
392
346
|
renderer?.updateContextUsage(usageText);
|
|
393
347
|
renderer?.updateStatus({
|
|
394
348
|
provider: providerName,
|
|
@@ -404,7 +358,7 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
404
358
|
const before = messages.length;
|
|
405
359
|
messages.splice(0, messages.length, ...ctx.compact(messages));
|
|
406
360
|
if (!quiet) {
|
|
407
|
-
renderer?.writeBracketTag(
|
|
361
|
+
renderer?.writeBracketTag("compact", `${before} → ${messages.length} messages`);
|
|
408
362
|
}
|
|
409
363
|
await ctx.updateTokens(messages);
|
|
410
364
|
}
|
|
@@ -413,13 +367,13 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
413
367
|
const before = messages.length;
|
|
414
368
|
messages.splice(0, messages.length, ...ctx.emergencyCompact(messages));
|
|
415
369
|
if (!quiet) {
|
|
416
|
-
renderer?.writeBracketTag(
|
|
370
|
+
renderer?.writeBracketTag("compact", `EMERGENCY: ${before} → ${messages.length}`);
|
|
417
371
|
}
|
|
418
372
|
await ctx.updateTokens(messages);
|
|
419
373
|
}
|
|
420
374
|
// ─── Запрос к LLM ────────────────────────────────────
|
|
421
375
|
const llmCfg = getLLMConfig(config);
|
|
422
|
-
logInfo(
|
|
376
|
+
logInfo("LOOP", `calling doChat, messages: ${messages.length}, options.showThinking: ${options?.showThinking}, retries: ${llmCfg.retries}`);
|
|
423
377
|
const { response } = await doChat(provider, messages, renderer, renderer?.abortSignal, llmCfg.retries, options?.showThinking);
|
|
424
378
|
// Проверка прерывания во время генерации
|
|
425
379
|
if (renderer?.abortSignal?.aborted) {
|
|
@@ -428,12 +382,21 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
428
382
|
}
|
|
429
383
|
// ─── Автозагрузка скилов по @упоминаниям ─────────────
|
|
430
384
|
// Если ответ содержит @skill-name, загружаем скил и пересобираем system prompt
|
|
431
|
-
if (options?.mode !==
|
|
385
|
+
if (options?.mode !== "fast" && !skillsAutoLoaded) {
|
|
432
386
|
const suggestedSkillRefs = response.matchAll(/@(\w[\w-]*)/g);
|
|
433
387
|
const newSkillNames = [];
|
|
434
388
|
for (const match of suggestedSkillRefs) {
|
|
435
389
|
const name = match[1];
|
|
436
|
-
if ([
|
|
390
|
+
if ([
|
|
391
|
+
"email",
|
|
392
|
+
"api",
|
|
393
|
+
"id",
|
|
394
|
+
"json",
|
|
395
|
+
"url",
|
|
396
|
+
"http",
|
|
397
|
+
"https",
|
|
398
|
+
"localhost",
|
|
399
|
+
].includes(name))
|
|
437
400
|
continue;
|
|
438
401
|
if (!loadedSkillNames.has(name)) {
|
|
439
402
|
const skill = loadSkill(name, process.cwd());
|
|
@@ -451,16 +414,19 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
451
414
|
if (autoBudget > 0 && loadedSkills.length > 0) {
|
|
452
415
|
const { allowed, dropped } = enforceSkillBudget(loadedSkills, autoBudget);
|
|
453
416
|
if (dropped.length > 0) {
|
|
454
|
-
renderer?.writeWarning(` Auto-load budget: dropped ${dropped.join(
|
|
455
|
-
logInfo(
|
|
417
|
+
renderer?.writeWarning(` Auto-load budget: dropped ${dropped.join(", ")} (budget: ${autoBudget} tokens)`);
|
|
418
|
+
logInfo("LOOP", `auto-load budget: dropped ${dropped.join(", ")} (budget: ${autoBudget} tokens)`);
|
|
456
419
|
loadedSkills.length = 0;
|
|
457
420
|
loadedSkills.push(...allowed);
|
|
458
421
|
for (const s of dropped)
|
|
459
422
|
loadedSkillNames.delete(s);
|
|
460
423
|
}
|
|
461
424
|
}
|
|
462
|
-
renderer?.writeInfo(` Auto-loaded ${loadedSkills.filter(s => newSkillNames.includes(s.name)).length} skill(s): ${newSkillNames.join(
|
|
463
|
-
messages[0] = {
|
|
425
|
+
renderer?.writeInfo(` Auto-loaded ${loadedSkills.filter((s) => newSkillNames.includes(s.name)).length} skill(s): ${newSkillNames.join(", ")}`);
|
|
426
|
+
messages[0] = {
|
|
427
|
+
role: "system",
|
|
428
|
+
content: buildSystemPrompt(loadedSkills, options?.mode, pluginSections, options?.flags),
|
|
429
|
+
};
|
|
464
430
|
// Не удаляем ответ ассистента — skills загружаются для следующих шагов цикла.
|
|
465
431
|
// Модель увидит обновлённый system prompt на следующей итерации и сможет
|
|
466
432
|
// использовать загруженные скилы без retry (экономит ~один LLM-запрос).
|
|
@@ -469,140 +435,124 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
469
435
|
// ─── Обработка пустых ответов ────────────────────────
|
|
470
436
|
// Маленькие модели иногда возвращают пустой ответ — повторяем с подсказкой
|
|
471
437
|
if (!response.trim()) {
|
|
472
|
-
logWarn(
|
|
438
|
+
logWarn("LOOP", `empty response #${consecutiveEmpty + 1}`);
|
|
473
439
|
consecutiveEmpty++;
|
|
474
440
|
// После компактинга — более информативное сообщение о пустом ответе
|
|
475
441
|
if (ctx.compactCount > 0 && consecutiveEmpty <= 3) {
|
|
476
442
|
let lastUserMsg;
|
|
477
443
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
478
|
-
if (messages[i].role ===
|
|
444
|
+
if (messages[i].role === "user") {
|
|
479
445
|
lastUserMsg = messages[i];
|
|
480
446
|
break;
|
|
481
447
|
}
|
|
482
448
|
}
|
|
483
449
|
const contextHint = lastUserMsg
|
|
484
|
-
? `[System: You returned an empty response. The conversation was compressed. Continue working on: "${(lastUserMsg.content ||
|
|
485
|
-
:
|
|
486
|
-
messages.push({ role:
|
|
450
|
+
? `[System: You returned an empty response. The conversation was compressed. Continue working on: "${(lastUserMsg.content || "").slice(0, 150)}..."]`
|
|
451
|
+
: "[System: You returned an empty response. Continue with the task.]";
|
|
452
|
+
messages.push({ role: "user", content: contextHint });
|
|
487
453
|
if (!quiet) {
|
|
488
454
|
renderer?.writeDim(` [empty response, retrying with context hint]`);
|
|
489
455
|
}
|
|
490
456
|
continue;
|
|
491
457
|
}
|
|
492
458
|
const lastMsg = messages[messages.length - 1];
|
|
493
|
-
if ((lastMsg?.role ===
|
|
459
|
+
if ((lastMsg?.role === "user" || lastMsg?.role === "system") &&
|
|
460
|
+
consecutiveEmpty <= 2) {
|
|
494
461
|
// После system-сообщений (например "Skills loaded") — более конкретная подсказка
|
|
495
|
-
const isAfterSystem = lastMsg?.role ===
|
|
462
|
+
const isAfterSystem = lastMsg?.role === "system";
|
|
496
463
|
const hint = isAfterSystem
|
|
497
|
-
?
|
|
498
|
-
:
|
|
499
|
-
messages.push({ role:
|
|
464
|
+
? "[System: You returned an empty response. The skills are loaded. Now create a plan and start working on the task. Do NOT wait for further instructions.]"
|
|
465
|
+
: "Continue.";
|
|
466
|
+
messages.push({ role: "user", content: hint });
|
|
500
467
|
if (!quiet) {
|
|
501
|
-
renderer?.writeDim(` [empty response, retrying${isAfterSystem ?
|
|
468
|
+
renderer?.writeDim(` [empty response, retrying${isAfterSystem ? " with skills hint" : ""}]`);
|
|
502
469
|
}
|
|
503
470
|
continue;
|
|
504
471
|
}
|
|
505
472
|
renderer?.writeError(consecutiveEmpty > 3
|
|
506
473
|
? `[model returned empty response ${consecutiveEmpty} times in a row — stopping]`
|
|
507
|
-
:
|
|
474
|
+
: "[model returned empty response]");
|
|
508
475
|
return { messages };
|
|
509
476
|
}
|
|
510
477
|
consecutiveEmpty = 0;
|
|
511
|
-
messages.push({ role:
|
|
512
|
-
|
|
513
|
-
// Извлекаем JSON из тегов <tool_call>...</tool_call> и валидируем
|
|
514
|
-
const allJsonRaw = extractAllToolCalls(response);
|
|
515
|
-
const validCalls = [];
|
|
516
|
-
const invalidCalls = [];
|
|
517
|
-
for (const jsonRaw of allJsonRaw) {
|
|
518
|
-
let p = null;
|
|
519
|
-
try {
|
|
520
|
-
p = JSON.parse(jsonrepair(jsonRaw));
|
|
521
|
-
}
|
|
522
|
-
catch {
|
|
523
|
-
try {
|
|
524
|
-
p = JSON.parse(jsonRaw);
|
|
525
|
-
}
|
|
526
|
-
catch { /* continue */ }
|
|
527
|
-
}
|
|
528
|
-
if (p) {
|
|
529
|
-
const v = validateToolCall(p);
|
|
530
|
-
if (v.valid) {
|
|
531
|
-
validCalls.push({ parsed: p, cmd: v.cmd });
|
|
532
|
-
}
|
|
533
|
-
else if (v.error) {
|
|
534
|
-
invalidCalls.push(v.error);
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
// ─── Fallback: regex-извлечение при невалидном JSON ───
|
|
539
|
-
if (validCalls.length === 0) {
|
|
540
|
-
const fallback = extractToolCallFallback(response);
|
|
541
|
-
if (fallback) {
|
|
542
|
-
validCalls.push({ parsed: { name: 'bash', arguments: { cmd: fallback.cmd } }, cmd: fallback.cmd });
|
|
543
|
-
if (!quiet) {
|
|
544
|
-
renderer?.writeBracketTag('fallback', 'regex extracted command');
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
}
|
|
478
|
+
messages.push({ role: "assistant", content: response });
|
|
479
|
+
const { validCalls, invalidCalls } = parseToolCalls(response);
|
|
548
480
|
if (validCalls.length === 0) {
|
|
549
481
|
if (invalidCalls.length > 0) {
|
|
550
482
|
// If the response has substantial text outside tool_call tags,
|
|
551
483
|
// it's a description with examples — not a failed tool call attempt.
|
|
552
|
-
const textOutside = response
|
|
484
|
+
const textOutside = response
|
|
485
|
+
.replace(/<tool_call>[\s\S]*?<\/tool_call>/g, "")
|
|
486
|
+
.trim();
|
|
553
487
|
if (textOutside.length < 100) {
|
|
554
|
-
|
|
488
|
+
consecutiveInvalidCalls++;
|
|
489
|
+
const feedback = `[Tool call error(s)]\n${invalidCalls.join("\n")}\n\nFix the tool call format and try again. Use ONLY valid tool names: bash, read_file, write_file, edit_file, list_dir, create_dir, delete_file, copy_file, move_file, file_info, glob_search, grep_search, tree, web_search, web_fetch, web_browse, plan, todo, question, approve, subagent, scaffold.`;
|
|
555
490
|
if (!quiet) {
|
|
556
491
|
renderer?.writeError(` [invalid tool call: ${invalidCalls[0]}]`);
|
|
557
492
|
}
|
|
558
|
-
messages.push({
|
|
493
|
+
messages.push({
|
|
494
|
+
role: "tool",
|
|
495
|
+
content: feedback,
|
|
496
|
+
tool_call_id: `invalid-${Date.now()}`,
|
|
497
|
+
});
|
|
559
498
|
continue;
|
|
560
499
|
}
|
|
561
500
|
// Text response with example tool calls — just log and proceed
|
|
562
|
-
logWarn(
|
|
501
|
+
logWarn("LOOP", `ignored ${invalidCalls.length} invalid tool call(s) in descriptive text`);
|
|
563
502
|
}
|
|
564
503
|
// Nudge model to continue working after a tool result — but only if
|
|
565
504
|
// the agent didn't already write a meaningful text response.
|
|
566
|
-
if (continuationNudgeMax > 0 &&
|
|
505
|
+
if (continuationNudgeMax > 0 &&
|
|
506
|
+
continuationNudges < continuationNudgeMax) {
|
|
567
507
|
const lastThree = messages.slice(-3);
|
|
568
|
-
const hasRecentToolResult = lastThree.some(m => m.role ===
|
|
569
|
-
const alreadyResponded = response.replace(/<tool_call>[\s\S]*?<\/tool_call>/g,
|
|
508
|
+
const hasRecentToolResult = lastThree.some((m) => m.role === "tool");
|
|
509
|
+
const alreadyResponded = response.replace(/<tool_call>[\s\S]*?<\/tool_call>/g, "").trim()
|
|
510
|
+
.length > 20;
|
|
570
511
|
if (hasRecentToolResult && !alreadyResponded) {
|
|
571
512
|
continuationNudges++;
|
|
572
513
|
if (!quiet) {
|
|
573
514
|
renderer?.writeDim(` [nudge ${continuationNudges}/${continuationNudgeMax}: continue working]`);
|
|
574
515
|
}
|
|
575
|
-
messages.push({
|
|
516
|
+
messages.push({
|
|
517
|
+
role: "user",
|
|
518
|
+
content: "[Tool result received. Write a response to the user. Do NOT call additional tools unless there is actual work to do.]",
|
|
519
|
+
});
|
|
576
520
|
continue;
|
|
577
521
|
}
|
|
578
522
|
}
|
|
579
523
|
if (!skillsAutoLoaded) {
|
|
580
|
-
const textOutside = response
|
|
524
|
+
const textOutside = response
|
|
525
|
+
.replace(/<tool_call>[\s\S]*?<\/tool_call>/g, "")
|
|
526
|
+
.trim();
|
|
581
527
|
const looksLikeDone = /done|готово|создан|выполн|completed|finished/i.test(textOutside);
|
|
582
528
|
// Проверка: план с незавершёнными шагами — не даём агенту остановиться
|
|
583
529
|
const pendingSteps = getPlanPendingCount();
|
|
584
530
|
if (pendingSteps > 0 && looksLikeDone) {
|
|
585
531
|
const planText = showPlan();
|
|
586
|
-
const pendingLines = planText
|
|
587
|
-
|
|
532
|
+
const pendingLines = planText
|
|
533
|
+
.split("\n")
|
|
534
|
+
.filter((l) => l.includes("○") || l.includes("►"));
|
|
535
|
+
const nextStep = pendingLines[0] || "";
|
|
588
536
|
const nextStepMatch = nextStep.match(/\d+\.\s*(.+)/);
|
|
589
|
-
const nextStepTitle = nextStepMatch ? nextStepMatch[1].trim() :
|
|
537
|
+
const nextStepTitle = nextStepMatch ? nextStepMatch[1].trim() : "";
|
|
590
538
|
if (!quiet) {
|
|
591
539
|
renderer?.writeWarning(` [plan has ${pendingSteps} incomplete steps — pushing agent to continue]`);
|
|
592
540
|
}
|
|
593
541
|
const nudge = nextStepTitle
|
|
594
542
|
? `Plan is NOT complete! ${pendingSteps} step(s) remaining.\nNext step: ${nextStepTitle}\nUse subagent to execute this step, then mark done: plan({action:"done", step:N})`
|
|
595
|
-
: `Plan is NOT complete! ${pendingSteps} step(s) remaining:\n${pendingLines.join(
|
|
596
|
-
messages.push({ role:
|
|
543
|
+
: `Plan is NOT complete! ${pendingSteps} step(s) remaining:\n${pendingLines.join("\n")}\n\nContinue with the next step using subagent.`;
|
|
544
|
+
messages.push({ role: "user", content: nudge });
|
|
597
545
|
continuationNudges = 0;
|
|
598
546
|
continue;
|
|
599
547
|
}
|
|
600
548
|
// Phase 4.1: Аудит файлов при завершении плана
|
|
601
549
|
if (pendingSteps === 0 && looksLikeDone) {
|
|
602
550
|
const plan = showPlan();
|
|
603
|
-
if (!plan.startsWith(
|
|
551
|
+
if (!plan.startsWith("No active plan")) {
|
|
604
552
|
// Извлекаем заголовки шагов и проверяем наличие файлов
|
|
605
|
-
const stepLines = plan
|
|
553
|
+
const stepLines = plan
|
|
554
|
+
.split("\n")
|
|
555
|
+
.filter((l) => l.match(/\d+\.\s/));
|
|
606
556
|
const missingFiles = [];
|
|
607
557
|
for (const line of stepLines) {
|
|
608
558
|
const titleMatch = line.match(/\d+\.\s*(.+)/);
|
|
@@ -610,22 +560,23 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
610
560
|
continue;
|
|
611
561
|
const title = titleMatch[1].trim();
|
|
612
562
|
// Извлекаем имя файла из заголовка (паттерн: "Create file.ext" или "file.ext")
|
|
613
|
-
const fileMatch = title.match(/(?:Create|Создать|Add|Добавить)\s+(\S+\.(?:ts|tsx|js|jsx|json|html|css|md|yaml|yml))/i)
|
|
614
|
-
|
|
563
|
+
const fileMatch = title.match(/(?:Create|Создать|Add|Добавить)\s+(\S+\.(?:ts|tsx|js|jsx|json|html|css|md|yaml|yml))/i) ||
|
|
564
|
+
title.match(/(\w+\.(?:ts|tsx|js|jsx|json|html|css|md|yaml|yml))/i);
|
|
615
565
|
if (fileMatch) {
|
|
616
566
|
const fileName = fileMatch[1];
|
|
617
|
-
if (!existsSync(fileName) &&
|
|
567
|
+
if (!existsSync(fileName) &&
|
|
568
|
+
!createdFiles.some((f) => f.endsWith(fileName))) {
|
|
618
569
|
missingFiles.push(fileName);
|
|
619
570
|
}
|
|
620
571
|
}
|
|
621
572
|
}
|
|
622
573
|
if (missingFiles.length > 0) {
|
|
623
574
|
if (!quiet) {
|
|
624
|
-
renderer?.writeWarning(` [plan complete but missing files: ${missingFiles.join(
|
|
575
|
+
renderer?.writeWarning(` [plan complete but missing files: ${missingFiles.join(", ")}]`);
|
|
625
576
|
}
|
|
626
577
|
messages.push({
|
|
627
|
-
role:
|
|
628
|
-
content: `Plan claims all steps done, but these files are missing: ${missingFiles.join(
|
|
578
|
+
role: "user",
|
|
579
|
+
content: `Plan claims all steps done, but these files are missing: ${missingFiles.join(", ")}. Create them before finishing.`,
|
|
629
580
|
});
|
|
630
581
|
continuationNudges = 0;
|
|
631
582
|
continue;
|
|
@@ -633,16 +584,19 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
633
584
|
}
|
|
634
585
|
}
|
|
635
586
|
// Stuck detection: если модель делает бесполезные действия
|
|
636
|
-
const isUselessAction = validCalls.some(c => c.parsed.name ===
|
|
637
|
-
|
|
638
|
-
(c.parsed.name ===
|
|
639
|
-
|
|
587
|
+
const isUselessAction = validCalls.some((c) => c.parsed.name === "list_dir" ||
|
|
588
|
+
c.parsed.name === "tree" ||
|
|
589
|
+
(c.parsed.name === "todo" &&
|
|
590
|
+
c.parsed.arguments?.action === "list") ||
|
|
591
|
+
(c.parsed.name === "question" && !c.parsed.arguments?.question));
|
|
592
|
+
// Phase 5.1: Расширенное detect — повторные чтения, поиски, вопросы и bash
|
|
640
593
|
let isRepeatedRead = false;
|
|
641
594
|
let isRepeatedSearch = false;
|
|
642
595
|
let isRepeatedQuestion = false;
|
|
596
|
+
let isRepeatedBash = false;
|
|
643
597
|
for (const c of validCalls) {
|
|
644
|
-
if (c.parsed.name ===
|
|
645
|
-
const filePath = String(c.parsed.arguments?.path ||
|
|
598
|
+
if (c.parsed.name === "read_file") {
|
|
599
|
+
const filePath = String(c.parsed.arguments?.path || "");
|
|
646
600
|
if (recentReadFiles.includes(filePath)) {
|
|
647
601
|
isRepeatedRead = true;
|
|
648
602
|
}
|
|
@@ -650,8 +604,8 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
650
604
|
if (recentReadFiles.length > 5)
|
|
651
605
|
recentReadFiles.shift();
|
|
652
606
|
}
|
|
653
|
-
if (c.parsed.name ===
|
|
654
|
-
const pattern = String(c.parsed.arguments?.pattern ||
|
|
607
|
+
if (c.parsed.name === "grep_search") {
|
|
608
|
+
const pattern = String(c.parsed.arguments?.pattern || "");
|
|
655
609
|
if (recentGrepPatterns.includes(pattern)) {
|
|
656
610
|
isRepeatedSearch = true;
|
|
657
611
|
}
|
|
@@ -659,8 +613,8 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
659
613
|
if (recentGrepPatterns.length > 5)
|
|
660
614
|
recentGrepPatterns.shift();
|
|
661
615
|
}
|
|
662
|
-
if (c.parsed.name ===
|
|
663
|
-
const questionText = String(c.parsed.arguments?.question ||
|
|
616
|
+
if (c.parsed.name === "question") {
|
|
617
|
+
const questionText = String(c.parsed.arguments?.question || "");
|
|
664
618
|
if (recentQuestions.includes(questionText)) {
|
|
665
619
|
isRepeatedQuestion = true;
|
|
666
620
|
}
|
|
@@ -668,8 +622,22 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
668
622
|
if (recentQuestions.length > 3)
|
|
669
623
|
recentQuestions.shift();
|
|
670
624
|
}
|
|
625
|
+
if (c.parsed.name === "bash") {
|
|
626
|
+
const cmdText = String(c.parsed.arguments?.cmd || "");
|
|
627
|
+
const normalized = cmdText.trim().replace(/\s+/g, " ");
|
|
628
|
+
if (recentBashCommands.includes(normalized)) {
|
|
629
|
+
isRepeatedBash = true;
|
|
630
|
+
}
|
|
631
|
+
recentBashCommands.push(normalized);
|
|
632
|
+
if (recentBashCommands.length > 5)
|
|
633
|
+
recentBashCommands.shift();
|
|
634
|
+
}
|
|
671
635
|
}
|
|
672
|
-
const isStuckAction = isUselessAction ||
|
|
636
|
+
const isStuckAction = isUselessAction ||
|
|
637
|
+
isRepeatedRead ||
|
|
638
|
+
isRepeatedSearch ||
|
|
639
|
+
isRepeatedQuestion ||
|
|
640
|
+
isRepeatedBash;
|
|
673
641
|
if (isStuckAction) {
|
|
674
642
|
uselessActionCount++;
|
|
675
643
|
if (uselessActionCount >= 3) {
|
|
@@ -678,7 +646,7 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
678
646
|
}
|
|
679
647
|
clearPlan();
|
|
680
648
|
messages.push({
|
|
681
|
-
role:
|
|
649
|
+
role: "user",
|
|
682
650
|
content: `[System: You are stuck in a loop. STOP calling the same tool. The user already answered your question — use their answer and proceed with the task. Do NOT ask the same question again.]`,
|
|
683
651
|
});
|
|
684
652
|
uselessActionCount = 0;
|
|
@@ -686,17 +654,20 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
686
654
|
continue;
|
|
687
655
|
}
|
|
688
656
|
}
|
|
689
|
-
else if (validCalls.some(c => c.parsed.name ===
|
|
657
|
+
else if (validCalls.some((c) => c.parsed.name === "subagent" ||
|
|
658
|
+
c.parsed.name === "write_file" ||
|
|
659
|
+
c.parsed.name === "bash")) {
|
|
690
660
|
uselessActionCount = 0;
|
|
691
661
|
recentReadFiles.length = 0;
|
|
692
662
|
recentGrepPatterns.length = 0;
|
|
693
663
|
recentQuestions.length = 0;
|
|
664
|
+
recentBashCommands.length = 0;
|
|
694
665
|
}
|
|
695
666
|
// Если модель claim-ит done но есть созданные файлы — напоминаем о полноте
|
|
696
667
|
if (createdFiles.length > 0 && looksLikeDone) {
|
|
697
668
|
messages.push({
|
|
698
|
-
role:
|
|
699
|
-
content: `Files created: ${createdFiles.join(
|
|
669
|
+
role: "user",
|
|
670
|
+
content: `Files created: ${createdFiles.join(", ")}. Before finishing — verify all files are complete and connected to each other. If any file is missing or not imported, continue working.`,
|
|
700
671
|
});
|
|
701
672
|
continuationNudges = 0;
|
|
702
673
|
continue;
|
|
@@ -711,48 +682,75 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
711
682
|
const groundingCfg = getGroundingConfig(config);
|
|
712
683
|
if (groundingCfg.enabled) {
|
|
713
684
|
const groundMatches = Array.from(response.matchAll(/@grounding\[файл:\s*([^\]]+)\]/g));
|
|
714
|
-
const groundPaths = groundMatches.map(m => m[1].trim());
|
|
715
|
-
const pathCalls = validCalls.filter(c => c.parsed.arguments?.path);
|
|
685
|
+
const groundPaths = groundMatches.map((m) => m[1].trim());
|
|
686
|
+
const pathCalls = validCalls.filter((c) => c.parsed.arguments?.path);
|
|
716
687
|
if (pathCalls.length > groundPaths.length) {
|
|
717
688
|
consecutiveMissingGrounding++;
|
|
718
689
|
const maxMissing = groundingCfg.maxMissingGrounding ?? 3;
|
|
690
|
+
const maxRetries = groundingCfg.maxConsecutiveGroundingErrors ?? 5;
|
|
719
691
|
if (consecutiveMissingGrounding >= maxMissing) {
|
|
692
|
+
// CRITICAL — сброс счётчика + учёт retry
|
|
693
|
+
consecutiveMissingGrounding = 0;
|
|
694
|
+
groundingRetryCount++;
|
|
695
|
+
if (groundingRetryCount > maxRetries) {
|
|
696
|
+
// Полный bypass — выполняем tool_call без маркера
|
|
697
|
+
if (!quiet)
|
|
698
|
+
renderer?.writeWarning(` [grounding: exceeded limit, bypassing]`);
|
|
699
|
+
groundingRetryCount = 0;
|
|
700
|
+
messages.push({
|
|
701
|
+
role: "user",
|
|
702
|
+
content: `[Note: Grounding check disabled after repeated failures. Proceeding without marker. To re-enable, reset the agent.]`,
|
|
703
|
+
});
|
|
704
|
+
// fall through — tool call будет выполнен
|
|
705
|
+
}
|
|
706
|
+
else {
|
|
707
|
+
if (!quiet)
|
|
708
|
+
renderer?.writeError(` [grounding: ${groundingRetryCount}/${maxRetries} retries]`);
|
|
709
|
+
messages.push({
|
|
710
|
+
role: "user",
|
|
711
|
+
content: `[CRITICAL: Output @grounding[файл: <path>] BEFORE each <tool_call>.\nExample:\n@grounding[файл: package.json]\n<tool_call>{"name":"read_file","arguments":{"path":"package.json"}}</tool_call>]`,
|
|
712
|
+
});
|
|
713
|
+
continue;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
else if (groundingCfg.strictMode) {
|
|
720
717
|
if (!quiet)
|
|
721
|
-
renderer?.writeError(` [grounding:
|
|
718
|
+
renderer?.writeError(` [grounding: strict mode, retrying]`);
|
|
722
719
|
messages.push({
|
|
723
|
-
role:
|
|
724
|
-
content: `[Grounding Error: Missing @grounding[файл: path] marker
|
|
720
|
+
role: "user",
|
|
721
|
+
content: `[Grounding Error: Missing @grounding[файл: path] marker. Output the marker BEFORE each <tool_call>. Example:\n@grounding[файл: package.json]\n<tool_call>{"name":"read_file","arguments":{"path":"package.json"}}</tool_call>]`,
|
|
725
722
|
});
|
|
726
723
|
continue;
|
|
727
724
|
}
|
|
728
|
-
|
|
725
|
+
else {
|
|
729
726
|
if (!quiet)
|
|
730
|
-
renderer?.
|
|
727
|
+
renderer?.writeDim(` [grounding: missing marker, reminder ${consecutiveMissingGrounding}/${maxMissing}]`);
|
|
731
728
|
messages.push({
|
|
732
|
-
role:
|
|
733
|
-
content: `[
|
|
729
|
+
role: "user",
|
|
730
|
+
content: `[Reminder: Include @grounding[файл: path] marker before each <tool_call> to prevent path hallucinations. Example:\n@grounding[файл: package.json]\n<tool_call>{"name":"read_file","arguments":{"path":"package.json"}}</tool_call>]`,
|
|
734
731
|
});
|
|
735
732
|
continue;
|
|
736
733
|
}
|
|
737
|
-
if (!quiet)
|
|
738
|
-
renderer?.writeDim(` [grounding: missing marker, reminder ${consecutiveMissingGrounding}/${maxMissing}]`);
|
|
739
|
-
messages.push({
|
|
740
|
-
role: 'user',
|
|
741
|
-
content: `[Reminder: Include @grounding[файл: path] marker before each <tool_call> to prevent path hallucinations.]`,
|
|
742
|
-
});
|
|
743
|
-
continue;
|
|
744
734
|
}
|
|
745
735
|
else {
|
|
746
736
|
consecutiveMissingGrounding = 0;
|
|
737
|
+
groundingRetryCount = 0;
|
|
747
738
|
// Проверка соответствия пути в маркере и в аргументах tool_call
|
|
748
739
|
for (let i = 0; i < pathCalls.length && i < groundPaths.length; i++) {
|
|
749
|
-
const callPath = String(pathCalls[i].parsed.arguments.path ||
|
|
750
|
-
|
|
751
|
-
|
|
740
|
+
const callPath = String(pathCalls[i].parsed.arguments.path || "")
|
|
741
|
+
.replace(/\\/g, "/")
|
|
742
|
+
.replace(/^\.\//, "");
|
|
743
|
+
const groundPath = groundPaths[i]
|
|
744
|
+
.replace(/\\/g, "/")
|
|
745
|
+
.replace(/^\.\//, "");
|
|
746
|
+
if (callPath &&
|
|
747
|
+
groundPath &&
|
|
748
|
+
!callPath.endsWith(groundPath) &&
|
|
749
|
+
!groundPath.endsWith(callPath)) {
|
|
752
750
|
if (!quiet)
|
|
753
751
|
renderer?.writeWarning(` [grounding: path mismatch — marker says "${groundPath}", tool says "${callPath}"]`);
|
|
754
752
|
messages.push({
|
|
755
|
-
role:
|
|
753
|
+
role: "user",
|
|
756
754
|
content: `[Warning: Grounding marker path "${groundPath}" doesn't match tool call path "${callPath}". Fix the marker to match the actual path.]`,
|
|
757
755
|
});
|
|
758
756
|
break;
|
|
@@ -764,155 +762,97 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
764
762
|
// ─── Enforce: лимиты на запись файлов ────────────────
|
|
765
763
|
// Отслеживание повторных чтений одного файла для консолидации
|
|
766
764
|
let lastReadFile = null;
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
if (hasMultiStepPlan) {
|
|
771
|
-
const directWrites = validCalls.filter(c => c.parsed.name === 'write_file' || c.parsed.name === 'edit_file');
|
|
772
|
-
const hasSubagent = validCalls.some(c => c.parsed.name === 'subagent');
|
|
773
|
-
const hasBash = validCalls.some(c => c.parsed.name === 'bash');
|
|
774
|
-
if (directWrites.length > 0 && !hasSubagent) {
|
|
775
|
-
// Block direct writes — model must use subagent
|
|
776
|
-
for (const wc of directWrites) {
|
|
777
|
-
const idx = validCalls.indexOf(wc);
|
|
778
|
-
if (idx !== -1)
|
|
779
|
-
validCalls.splice(idx, 1);
|
|
780
|
-
const blockId = `call-${Date.now()}-${step}-${Math.random().toString(36).slice(2, 6)}`;
|
|
781
|
-
messages.push({
|
|
782
|
-
role: 'tool',
|
|
783
|
-
content: `BLOCKED: Multi-step plan active. Use subagent for each step. Example: subagent({task:"Create file X", existingFiles:"Y.ts"}). After subagent completes, mark step done: plan({action:"done", step:N})`,
|
|
784
|
-
tool_call_id: blockId,
|
|
785
|
-
});
|
|
786
|
-
}
|
|
787
|
-
if (!quiet) {
|
|
788
|
-
renderer?.writeWarning(` [blocked ${directWrites.length} direct writes — use subagent for multi-step plans]`);
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
// Allow bash for verification (tsc --noEmit, npm run build) — don't block these
|
|
792
|
-
}
|
|
793
|
-
// ESCALATION: block write_file when creating N+ files without subagent (no plan case)
|
|
794
|
-
if (directWriteStreakMax < Infinity && !hasMultiStepPlan) {
|
|
795
|
-
const hasWrite = validCalls.some(c => c.parsed.name === 'write_file');
|
|
796
|
-
const hasSubagentNow = validCalls.some(c => c.parsed.name === 'subagent');
|
|
797
|
-
if (hasSubagentNow) {
|
|
798
|
-
directWriteStreak = 0;
|
|
799
|
-
}
|
|
800
|
-
else if (hasWrite) {
|
|
801
|
-
const writeCount = validCalls.filter(c => c.parsed.name === 'write_file').length;
|
|
802
|
-
directWriteStreak += writeCount;
|
|
803
|
-
if (directWriteStreak >= directWriteStreakMax) {
|
|
804
|
-
const directWrites = validCalls.filter(c => c.parsed.name === 'write_file');
|
|
805
|
-
for (const wc of directWrites) {
|
|
806
|
-
const idx = validCalls.indexOf(wc);
|
|
807
|
-
if (idx !== -1)
|
|
808
|
-
validCalls.splice(idx, 1);
|
|
809
|
-
}
|
|
810
|
-
if (directWrites.length > 0) {
|
|
811
|
-
const blockId = `call-${Date.now()}-${step}-${Math.random().toString(36).slice(2, 6)}`;
|
|
812
|
-
messages.push({
|
|
813
|
-
role: 'tool',
|
|
814
|
-
content: `BLOCKED: You're creating file #${directWriteStreak} without subagent. For 3+ new files, use subagent for each one. Example: subagent("Create file X. Context: Y already exists.")`,
|
|
815
|
-
tool_call_id: blockId,
|
|
816
|
-
});
|
|
817
|
-
}
|
|
818
|
-
if (!quiet) {
|
|
819
|
-
renderer?.writeWarning(` [blocked ${directWrites.length} writes: ${directWriteStreak} files without subagent — use subagent for multi-file projects]`);
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
// Блокировка нескольких записей файлов за один ответ — максимум одна
|
|
825
|
-
const writeCalls = validCalls.filter(c => c.parsed.name === 'write_file' || c.parsed.name === 'edit_file');
|
|
826
|
-
if (writeCalls.length > 1) {
|
|
827
|
-
// Keep only the first write call, block the rest
|
|
828
|
-
const blocked = writeCalls.slice(1);
|
|
829
|
-
validCalls.splice(validCalls.indexOf(blocked[0]), blocked.length);
|
|
830
|
-
for (const bc of blocked) {
|
|
831
|
-
const blockId = `call-${Date.now()}-${step}-${Math.random().toString(36).slice(2, 6)}`;
|
|
832
|
-
messages.push({
|
|
833
|
-
role: 'tool',
|
|
834
|
-
content: `BLOCKED: Only one file write per response is allowed. You wrote ${writeCalls.length} files at once. Complete the first file, then write the next one.`,
|
|
835
|
-
tool_call_id: blockId,
|
|
836
|
-
});
|
|
837
|
-
}
|
|
838
|
-
if (!quiet) {
|
|
839
|
-
renderer?.writeWarning(` [blocked ${blocked.length} extra file writes — one at a time]`);
|
|
840
|
-
}
|
|
841
|
-
}
|
|
765
|
+
writeEnforcer.enforcePlan(validCalls, messages, planEnforcement, quiet, renderer);
|
|
766
|
+
writeEnforcer.enforceWriteStreak(validCalls, messages, directWriteStreakMax, quiet, renderer);
|
|
767
|
+
writeEnforcer.enforceSingleWrite(validCalls, messages, quiet, renderer);
|
|
842
768
|
// ─── Исполнение tool calls ──────────────────────────
|
|
843
769
|
for (const call of validCalls) {
|
|
844
770
|
const { parsed, cmd } = call;
|
|
845
|
-
const isBash = parsed.name ===
|
|
771
|
+
const isBash = parsed.name === "bash";
|
|
846
772
|
const preview = isBash
|
|
847
|
-
?
|
|
773
|
+
? cmd.length > 200
|
|
774
|
+
? cmd.slice(0, 200) + "..."
|
|
775
|
+
: cmd
|
|
848
776
|
: JSON.stringify(parsed.arguments);
|
|
849
|
-
const toolId =
|
|
777
|
+
const toolId = generateToolCallId();
|
|
850
778
|
logToolCall(parsed.name, preview);
|
|
851
779
|
if (!quiet) {
|
|
852
780
|
renderer?.writeToolHeader(parsed.name);
|
|
853
781
|
renderer?.writeToolPreview(parsed.name, preview);
|
|
854
782
|
}
|
|
855
|
-
const hookCmd = isBash
|
|
783
|
+
const hookCmd = isBash
|
|
784
|
+
? cmd
|
|
785
|
+
: `${parsed.name} ${JSON.stringify(parsed.arguments)}`;
|
|
856
786
|
const preHook = runPreToolUseHooks(process.cwd(), parsed.name, hookCmd);
|
|
857
787
|
if (!preHook.continue) {
|
|
858
788
|
if (!quiet) {
|
|
859
|
-
renderer?.writeBracketTag(
|
|
789
|
+
renderer?.writeBracketTag("hook blocked", preHook.message || "");
|
|
860
790
|
}
|
|
861
|
-
messages.push({
|
|
791
|
+
messages.push({
|
|
792
|
+
role: "tool",
|
|
793
|
+
content: `Hook blocked: ${preHook.message || ""}`,
|
|
794
|
+
tool_call_id: toolId,
|
|
795
|
+
});
|
|
862
796
|
continue;
|
|
863
797
|
}
|
|
864
798
|
if (pendingSyntaxError && !isBash) {
|
|
865
|
-
const isWriteOp = parsed.name ===
|
|
866
|
-
const newPath = String(parsed.arguments.path ??
|
|
799
|
+
const isWriteOp = parsed.name === "write_file" || parsed.name === "edit_file";
|
|
800
|
+
const newPath = String(parsed.arguments.path ?? "");
|
|
867
801
|
// Allow editing config files (tsconfig.json, package.json) since they
|
|
868
802
|
// can fix syntax errors (e.g. missing jsx flag in tsconfig)
|
|
869
|
-
const isConfig = newPath ===
|
|
803
|
+
const isConfig = newPath === "tsconfig.json" || newPath === "package.json";
|
|
870
804
|
if (isWriteOp && newPath !== pendingSyntaxError && !isConfig) {
|
|
871
805
|
const blocked = `BLOCKED: You have a syntax error in ${pendingSyntaxError}. Fix it first before writing new files. If the error is about jsx, module resolution, or missing types — edit tsconfig.json (it is exempt from this block).`;
|
|
872
806
|
if (!quiet) {
|
|
873
807
|
renderer?.writeError(` [blocked: fix syntax error in ${pendingSyntaxError} first]`);
|
|
874
808
|
}
|
|
875
|
-
messages.push({
|
|
809
|
+
messages.push({
|
|
810
|
+
role: "tool",
|
|
811
|
+
content: blocked,
|
|
812
|
+
tool_call_id: toolId,
|
|
813
|
+
});
|
|
876
814
|
continue;
|
|
877
815
|
}
|
|
878
816
|
}
|
|
879
|
-
let toolOutput =
|
|
817
|
+
let toolOutput = "";
|
|
880
818
|
let toolExitCode = 0;
|
|
881
819
|
const toolStart = performance.now();
|
|
882
820
|
// ─── Субагент: изолированный контекст для задачи ──
|
|
883
|
-
if (parsed.name ===
|
|
884
|
-
const task = String(parsed.arguments.task ||
|
|
821
|
+
if (parsed.name === "subagent") {
|
|
822
|
+
const task = String(parsed.arguments.task || "");
|
|
885
823
|
if (!task) {
|
|
886
|
-
toolOutput =
|
|
824
|
+
toolOutput = "Error: task is required";
|
|
887
825
|
toolExitCode = 1;
|
|
888
826
|
}
|
|
889
827
|
else {
|
|
890
828
|
// Авто-пометка следующего pending шага как in_progress
|
|
891
829
|
const nextPending = getNextPendingStep();
|
|
892
830
|
if (nextPending) {
|
|
893
|
-
updatePlanStep(nextPending.id,
|
|
831
|
+
updatePlanStep(nextPending.id, "in_progress");
|
|
894
832
|
}
|
|
895
833
|
// Phase 2.3: Авто-подстановка полного пути в задачу subagent
|
|
896
834
|
let finalTask = task;
|
|
897
835
|
if (nextPending) {
|
|
898
836
|
const expectedDir = extractDirFromStepTitle(nextPending.title);
|
|
899
|
-
if (expectedDir && !task.includes(expectedDir +
|
|
837
|
+
if (expectedDir && !task.includes(expectedDir + "/")) {
|
|
900
838
|
finalTask = `${task} (directory: ${expectedDir}/)`;
|
|
901
|
-
logInfo(
|
|
839
|
+
logInfo("AGENT", `injected dir path into subagent task: "${expectedDir}"`);
|
|
902
840
|
}
|
|
903
841
|
}
|
|
904
842
|
const autoContext = buildSubagentContext(process.cwd());
|
|
905
|
-
logInfo(
|
|
843
|
+
logInfo("AGENT", `spawning subagent | task: "${finalTask.slice(0, 150)}"`);
|
|
906
844
|
// Показываем спиннер на основном рендерере пока субагент работает
|
|
907
845
|
if (!quiet) {
|
|
908
|
-
const taskPreview = finalTask.length > 60
|
|
846
|
+
const taskPreview = finalTask.length > 60
|
|
847
|
+
? finalTask.slice(0, 57) + "..."
|
|
848
|
+
: finalTask;
|
|
909
849
|
renderer?.writeSpinner(` [subagent] ${taskPreview}`);
|
|
910
850
|
}
|
|
911
851
|
const subRenderer = new SubagentRenderer();
|
|
912
852
|
try {
|
|
913
853
|
const childResult = await runIsolatedLoop(provider, registry, finalTask, autoContext, process.cwd(), quiet, subRenderer, toolExecutor, modeConfig.subagentMode, llmCfg.toolModel);
|
|
914
854
|
toolOutput = childResult.summary;
|
|
915
|
-
logInfo(
|
|
855
|
+
logInfo("AGENT", `subagent returned | files: ${childResult.createdFiles.length} | turns: ${childResult.turns} | errors: ${childResult.errors.length}`);
|
|
916
856
|
if (childResult.createdFiles.length > 0) {
|
|
917
857
|
for (const f of childResult.createdFiles) {
|
|
918
858
|
if (!createdFiles.includes(f))
|
|
@@ -923,26 +863,30 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
923
863
|
if (nextPending && childResult.createdFiles.length > 0) {
|
|
924
864
|
const expectedDir = extractDirFromStepTitle(nextPending.title);
|
|
925
865
|
if (expectedDir) {
|
|
926
|
-
const wrongDirFiles = childResult.createdFiles.filter(f => !f.startsWith(expectedDir +
|
|
866
|
+
const wrongDirFiles = childResult.createdFiles.filter((f) => !f.startsWith(expectedDir + "/") &&
|
|
867
|
+
!f.startsWith(expectedDir + "\\"));
|
|
927
868
|
if (wrongDirFiles.length > 0) {
|
|
928
|
-
const correction = `⚠ Files created in wrong directory: ${wrongDirFiles.join(
|
|
869
|
+
const correction = `⚠ Files created in wrong directory: ${wrongDirFiles.join(", ")}. ` +
|
|
929
870
|
`They should be in ${expectedDir}/. Use write_file with full path: ${expectedDir}/${wrongDirFiles[0]}`;
|
|
930
|
-
toolOutput +=
|
|
871
|
+
toolOutput += "\n\n" + correction;
|
|
931
872
|
if (!quiet) {
|
|
932
|
-
renderer?.writeWarning(` [subagent created files in wrong directory: ${wrongDirFiles.join(
|
|
873
|
+
renderer?.writeWarning(` [subagent created files in wrong directory: ${wrongDirFiles.join(", ")}]`);
|
|
933
874
|
}
|
|
934
875
|
}
|
|
935
876
|
}
|
|
936
877
|
}
|
|
937
878
|
const buf = subRenderer.getBuffer();
|
|
938
879
|
if (!quiet && renderer?.writeToolBuffer) {
|
|
939
|
-
renderer.writeToolBuffer({
|
|
880
|
+
renderer.writeToolBuffer({
|
|
881
|
+
lines: buf.lines,
|
|
882
|
+
toolCalls: buf.toolCalls,
|
|
883
|
+
});
|
|
940
884
|
}
|
|
941
885
|
}
|
|
942
886
|
catch (e) {
|
|
943
887
|
toolOutput = `Subagent error: ${e.message}`;
|
|
944
888
|
toolExitCode = 1;
|
|
945
|
-
logError(
|
|
889
|
+
logError("AGENT", `subagent error: ${e.message}`);
|
|
946
890
|
}
|
|
947
891
|
}
|
|
948
892
|
const subTime = ((performance.now() - toolStart) / 1000).toFixed(1);
|
|
@@ -956,12 +900,13 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
956
900
|
const mcpCmd = parseMCPCommand(cmd);
|
|
957
901
|
if (mcpCmd) {
|
|
958
902
|
if (!quiet) {
|
|
959
|
-
renderer?.writeBracketTag(
|
|
903
|
+
renderer?.writeBracketTag("mcp", `${mcpCmd.server}/${mcpCmd.tool}`);
|
|
960
904
|
}
|
|
961
905
|
toolOutput = callMCPTool(process.cwd(), mcpCmd.server, mcpCmd.tool, mcpCmd.args);
|
|
962
906
|
}
|
|
963
907
|
else {
|
|
964
|
-
toolOutput =
|
|
908
|
+
toolOutput =
|
|
909
|
+
"Invalid MCP command. Format: mcp <server> <tool> [args_json]";
|
|
965
910
|
toolExitCode = 1;
|
|
966
911
|
}
|
|
967
912
|
// ─── Все инструменты через ToolExecutor ──────────────
|
|
@@ -972,19 +917,25 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
972
917
|
let executorArgs = parsed.arguments;
|
|
973
918
|
// Bash-команды
|
|
974
919
|
if (isBash) {
|
|
975
|
-
executorName =
|
|
920
|
+
executorName = "bash";
|
|
976
921
|
executorArgs = { cmd, timeout: parsed.arguments?.timeout ?? 120 };
|
|
977
922
|
}
|
|
978
|
-
const hookCmd = isBash
|
|
979
|
-
|
|
923
|
+
const hookCmd = isBash
|
|
924
|
+
? cmd
|
|
925
|
+
: `${parsed.name} ${JSON.stringify(parsed.arguments)}`;
|
|
926
|
+
const executorCall = {
|
|
927
|
+
id: toolId,
|
|
928
|
+
name: executorName,
|
|
929
|
+
arguments: executorArgs,
|
|
930
|
+
};
|
|
980
931
|
const toolCtx = { cwd: process.cwd(), renderer, quiet };
|
|
981
932
|
const { result, elapsed } = await toolExecutor.execute(executorCall, toolCtx, hookCmd);
|
|
982
|
-
toolOutput = result.stdout || result.stderr ||
|
|
933
|
+
toolOutput = result.stdout || result.stderr || "(no output)";
|
|
983
934
|
toolExitCode = result.exitCode;
|
|
984
935
|
// Обработка question/approve: приостанавливаем цикл
|
|
985
936
|
if (result.questionPending) {
|
|
986
937
|
messages.push({
|
|
987
|
-
role:
|
|
938
|
+
role: "tool",
|
|
988
939
|
content: toolOutput,
|
|
989
940
|
tool_call_id: toolId,
|
|
990
941
|
});
|
|
@@ -995,76 +946,99 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
995
946
|
}
|
|
996
947
|
}
|
|
997
948
|
const toolTime = ((performance.now() - toolStart) / 1000).toFixed(1);
|
|
998
|
-
logToolCall(parsed.name, preview, toolExitCode !== 0
|
|
949
|
+
logToolCall(parsed.name, preview, toolExitCode !== 0
|
|
950
|
+
? `exit:${toolExitCode} ${toolOutput.slice(0, 100)}`
|
|
951
|
+
: `ok (${toolTime}s)`);
|
|
999
952
|
if (toolExitCode !== 0) {
|
|
1000
953
|
const errText = toolOutput;
|
|
1001
954
|
if (!quiet) {
|
|
1002
|
-
for (const line of errText.split(
|
|
955
|
+
for (const line of errText.split("\n").slice(0, 5)) {
|
|
1003
956
|
renderer?.writeError(` ${line}`);
|
|
1004
957
|
}
|
|
1005
958
|
}
|
|
1006
|
-
autoLearnFromError(hookCmd, toolExitCode !== 0 ? toolOutput :
|
|
959
|
+
autoLearnFromError(hookCmd, toolExitCode !== 0 ? toolOutput : "", "");
|
|
1007
960
|
}
|
|
1008
961
|
runPostToolUseHooks(process.cwd(), parsed.name, hookCmd, toolOutput, toolExitCode);
|
|
1009
962
|
// Track sequential read_file calls for consolidation
|
|
1010
|
-
if (readConsolidation &&
|
|
1011
|
-
|
|
963
|
+
if (readConsolidation &&
|
|
964
|
+
!isBash &&
|
|
965
|
+
parsed.name === "read_file" &&
|
|
966
|
+
toolExitCode === 0) {
|
|
967
|
+
const filePath = String(parsed.arguments.path ?? "");
|
|
1012
968
|
const offset = Math.max(1, Number(parsed.arguments.offset ?? 1));
|
|
1013
969
|
const limit = Math.max(1, Number(parsed.arguments.limit ?? 500));
|
|
1014
|
-
if (lastReadFile &&
|
|
970
|
+
if (lastReadFile &&
|
|
971
|
+
lastReadFile.path === filePath &&
|
|
972
|
+
offset === lastReadFile.offset + lastReadFile.limit) {
|
|
1015
973
|
// Sequential read — merge with previous result
|
|
1016
|
-
lastReadFile.content +=
|
|
974
|
+
lastReadFile.content += "\n" + toolOutput;
|
|
1017
975
|
lastReadFile.offset = offset;
|
|
1018
976
|
lastReadFile.limit = limit;
|
|
1019
977
|
// Replace the last tool message content with merged content
|
|
1020
978
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1021
|
-
if (messages[i].role ===
|
|
979
|
+
if (messages[i].role === "tool" &&
|
|
980
|
+
messages[i].tool_call_id === lastReadFile.toolId) {
|
|
1022
981
|
messages[i].content = lastReadFile.content;
|
|
1023
982
|
break;
|
|
1024
983
|
}
|
|
1025
984
|
}
|
|
1026
985
|
// Remove the trailing "Continue." nudge + the new tool result that would be pushed
|
|
1027
|
-
while (messages.length > 0 &&
|
|
986
|
+
while (messages.length > 0 &&
|
|
987
|
+
messages[messages.length - 1].role === "user" &&
|
|
988
|
+
messages[messages.length - 1].content === "Continue.") {
|
|
1028
989
|
messages.pop();
|
|
1029
990
|
}
|
|
1030
991
|
if (!quiet) {
|
|
1031
|
-
renderer?.writeBracketTag(
|
|
992
|
+
renderer?.writeBracketTag("consolidate", `merged read_file chunks for ${filePath}`);
|
|
1032
993
|
}
|
|
1033
994
|
continue; // skip pushing this result separately
|
|
1034
995
|
}
|
|
1035
996
|
else {
|
|
1036
997
|
// New file or non-sequential — start fresh
|
|
1037
|
-
lastReadFile = {
|
|
998
|
+
lastReadFile = {
|
|
999
|
+
path: filePath,
|
|
1000
|
+
offset,
|
|
1001
|
+
limit,
|
|
1002
|
+
content: toolOutput,
|
|
1003
|
+
toolId,
|
|
1004
|
+
};
|
|
1038
1005
|
}
|
|
1039
1006
|
}
|
|
1040
|
-
else if (!isBash && parsed.name !==
|
|
1007
|
+
else if (!isBash && parsed.name !== "read_file") {
|
|
1041
1008
|
lastReadFile = null;
|
|
1042
1009
|
}
|
|
1043
1010
|
// ─── Проверка синтаксиса после записи файлов ────────
|
|
1044
1011
|
let hasSyntaxError = false;
|
|
1045
1012
|
if (toolExitCode === 0) {
|
|
1046
1013
|
const isWriteOp = isBash
|
|
1047
|
-
?
|
|
1048
|
-
:
|
|
1014
|
+
? cmd.match(/Set-Content|Add-Content/)
|
|
1015
|
+
: parsed.name === "write_file" || parsed.name === "edit_file";
|
|
1049
1016
|
if (isWriteOp) {
|
|
1050
1017
|
const rawPath = isBash
|
|
1051
|
-
? cmd
|
|
1052
|
-
|
|
1018
|
+
? cmd
|
|
1019
|
+
.match(/-Path\s+['"]?([^'"]+\.(?:ts|tsx|js|jsx|json))['"]?/i)?.[1]
|
|
1020
|
+
?.replace(/\\/g, "/")
|
|
1021
|
+
: String(parsed.arguments.path ?? "");
|
|
1053
1022
|
if (rawPath && /\.(ts|tsx|js|jsx|json)$/.test(rawPath)) {
|
|
1054
|
-
const
|
|
1055
|
-
|
|
1023
|
+
const filePath = isAbsolute(rawPath)
|
|
1024
|
+
? relative(process.cwd(), rawPath).replace(/\\/g, "/")
|
|
1025
|
+
: rawPath;
|
|
1056
1026
|
const syntaxResult = checkFileSyntax(filePath, process.cwd());
|
|
1057
|
-
if (syntaxResult !==
|
|
1058
|
-
const errorLines = syntaxResult
|
|
1027
|
+
if (syntaxResult !== "OK") {
|
|
1028
|
+
const errorLines = syntaxResult
|
|
1029
|
+
.split("\n")
|
|
1030
|
+
.filter((l) => l.trim());
|
|
1059
1031
|
const shown = errorLines.slice(0, 3);
|
|
1060
|
-
const extra = errorLines.length > 3
|
|
1032
|
+
const extra = errorLines.length > 3
|
|
1033
|
+
? ` (+${errorLines.length - 3} more)`
|
|
1034
|
+
: "";
|
|
1061
1035
|
if (!quiet) {
|
|
1062
|
-
renderer?.writeBracketTag(
|
|
1036
|
+
renderer?.writeBracketTag("syntax", `${shown.join("; ")}${extra}`);
|
|
1063
1037
|
}
|
|
1064
1038
|
const fullPath = join(process.cwd(), filePath);
|
|
1065
|
-
let contextLines =
|
|
1039
|
+
let contextLines = "";
|
|
1066
1040
|
try {
|
|
1067
|
-
const fileContent = readFileSync(fullPath,
|
|
1041
|
+
const fileContent = readFileSync(fullPath, "utf-8").split("\n");
|
|
1068
1042
|
for (const errLine of shown) {
|
|
1069
1043
|
const lineMatch = errLine.match(/\((\d+),\d+\)/);
|
|
1070
1044
|
if (lineMatch) {
|
|
@@ -1074,21 +1048,23 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
1074
1048
|
const end = Math.min(fileContent.length, lineNum + 1);
|
|
1075
1049
|
contextLines += `\n--- Line ${lineNum} ---\n`;
|
|
1076
1050
|
for (let i = start; i < end; i++) {
|
|
1077
|
-
const marker = i === lineNum - 1 ?
|
|
1051
|
+
const marker = i === lineNum - 1 ? " >> " : " ";
|
|
1078
1052
|
contextLines += `${marker}${i + 1}: ${fileContent[i]}\n`;
|
|
1079
1053
|
}
|
|
1080
1054
|
}
|
|
1081
1055
|
}
|
|
1082
1056
|
}
|
|
1083
1057
|
}
|
|
1084
|
-
catch {
|
|
1058
|
+
catch {
|
|
1059
|
+
/* ignore read errors */
|
|
1060
|
+
}
|
|
1085
1061
|
toolOutput = `SYNTAX ERROR in ${filePath}:\n${syntaxResult}${contextLines}`;
|
|
1086
1062
|
// Detect config-related TS errors (missing jsx, module issues, etc.)
|
|
1087
|
-
const isConfigError = syntaxResult.includes(
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1063
|
+
const isConfigError = syntaxResult.includes("TS17004") ||
|
|
1064
|
+
syntaxResult.includes("TS2307") ||
|
|
1065
|
+
syntaxResult.includes("Cannot find module") ||
|
|
1066
|
+
syntaxResult.includes("jsx") ||
|
|
1067
|
+
syntaxResult.includes("moduleResolution");
|
|
1092
1068
|
if (isConfigError) {
|
|
1093
1069
|
toolOutput += `\n\nThis looks like a tsconfig.json configuration issue, NOT a code syntax error.\nCheck tsconfig.json — you may need to add "jsx": "react-jsx" or fix module settings.\nDo NOT modify the source code to work around config errors. Edit tsconfig.json instead.`;
|
|
1094
1070
|
}
|
|
@@ -1100,16 +1076,18 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
1100
1076
|
}
|
|
1101
1077
|
else {
|
|
1102
1078
|
if (pendingSyntaxError === filePath) {
|
|
1103
|
-
pendingSyntaxError =
|
|
1079
|
+
pendingSyntaxError = "";
|
|
1104
1080
|
}
|
|
1105
1081
|
}
|
|
1106
1082
|
// Автодетекция стиля кодирования из содержимого файла
|
|
1107
1083
|
if (toolExitCode === 0 && filePath) {
|
|
1108
1084
|
try {
|
|
1109
|
-
const fullContent = readFileSync(join(process.cwd(), filePath),
|
|
1085
|
+
const fullContent = readFileSync(join(process.cwd(), filePath), "utf-8");
|
|
1110
1086
|
collectCodingStyle(filePath, fullContent);
|
|
1111
1087
|
}
|
|
1112
|
-
catch {
|
|
1088
|
+
catch {
|
|
1089
|
+
/* ignore */
|
|
1090
|
+
}
|
|
1113
1091
|
// Track created/modified files for completion reporting
|
|
1114
1092
|
if (!hasSyntaxError && !createdFiles.includes(filePath)) {
|
|
1115
1093
|
createdFiles.push(filePath);
|
|
@@ -1119,73 +1097,18 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
1119
1097
|
}
|
|
1120
1098
|
}
|
|
1121
1099
|
messages.push({
|
|
1122
|
-
role:
|
|
1123
|
-
content:
|
|
1100
|
+
role: "tool",
|
|
1101
|
+
content: truncateOutput(parsed.name, toolOutput, toolExitCode),
|
|
1124
1102
|
tool_call_id: toolId,
|
|
1125
1103
|
});
|
|
1126
1104
|
// ─── Error escalation + specialist auto-delegation ──────────
|
|
1127
1105
|
if (toolExitCode !== 0 && toolOutput) {
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
consecutiveToolErrors.set(errKey, prevCount + 1);
|
|
1132
|
-
const errorCount = prevCount + 1;
|
|
1133
|
-
// ── Specialist: авто-делегация при повторяющихся ошибках ──
|
|
1134
|
-
const specialistCfg = getSpecialistConfig(config);
|
|
1135
|
-
if (specialistCfg.enabled &&
|
|
1136
|
-
errorCount === specialistCfg.triggerThreshold &&
|
|
1137
|
-
specialistSpawnedFor !== errKey &&
|
|
1138
|
-
!isTrivialError(parsed.name, toolOutput) &&
|
|
1139
|
-
toolOutput.length >= specialistCfg.minErrorLength) {
|
|
1140
|
-
specialistSpawnedFor = errKey;
|
|
1141
|
-
const spawned = await spawnSpecialist({
|
|
1142
|
-
provider, registry, toolExecutor, modeConfig, llmCfg, specialistCfg,
|
|
1143
|
-
quiet, renderer, messages, toolId, createdFiles,
|
|
1144
|
-
toolName: parsed.name, toolArgs: parsed.arguments,
|
|
1145
|
-
errorOutput: toolOutput, errorCount, enrichedPrompt,
|
|
1146
|
-
});
|
|
1147
|
-
if (spawned)
|
|
1148
|
-
continue;
|
|
1149
|
-
}
|
|
1150
|
-
// ── Escalation: hint / strategy switch / block ──
|
|
1151
|
-
if (errorCount > specialistCfg.triggerThreshold) {
|
|
1152
|
-
// Веб-специфичные подсказки
|
|
1153
|
-
let webHint = '';
|
|
1154
|
-
if (parsed.name === 'web_browse' && String(parsed.arguments?.action) === 'evaluate') {
|
|
1155
|
-
webHint = ' For web scraping: use web_browse(action:"discover") to find real CSS selectors, or web_browse(action:"get_links") to see all links, or web_browse(action:"extract", format:"html") to inspect the page structure.';
|
|
1156
|
-
}
|
|
1157
|
-
else if (parsed.name === 'web_browse' && String(parsed.arguments?.action) === 'navigate' && toolOutput.includes('Status: 40')) {
|
|
1158
|
-
webHint = ' Page returned HTTP error. Try navigating to the site homepage first, then use web_browse(action:"get_links") or web_browse(action:"discover") to explore available categories and sections instead of guessing URLs.';
|
|
1159
|
-
}
|
|
1160
|
-
const msg = errorCount === specialistCfg.triggerThreshold + 1
|
|
1161
|
-
? `[System: Tool "${parsed.name}" failed with the SAME error ${errorCount} times. Read the error message carefully. The input is likely wrong — try a completely different approach, or use subagent for a fresh context.]${webHint}`
|
|
1162
|
-
: `[System: Tool "${parsed.name}" failed ${errorCount} times with the same error. STOP. Do NOT retry the same call. Use subagent(task="...") for a fresh context, or tell the user you're stuck.]${webHint}`;
|
|
1163
|
-
messages.push({ role: 'user', content: msg });
|
|
1164
|
-
if (!quiet)
|
|
1165
|
-
renderer?.writeWarning(` [error escalation: ${parsed.name} failed ${errorCount} times]`);
|
|
1166
|
-
}
|
|
1167
|
-
if (totalToolErrors >= 5 && errorStrategy === 'normal') {
|
|
1168
|
-
errorStrategy = 'subagent-hint';
|
|
1169
|
-
messages.push({ role: 'user', content: `[System: ${totalToolErrors} tool errors. Use subagent(task="...") for a fresh context.]` });
|
|
1170
|
-
if (!quiet)
|
|
1171
|
-
renderer?.writeWarning(` [strategy: subagent-hint after ${totalToolErrors} errors]`);
|
|
1172
|
-
}
|
|
1173
|
-
if (errorCount >= 4) {
|
|
1174
|
-
const blockId = `call-${Date.now()}-${step}-${Math.random().toString(36).slice(2, 6)}`;
|
|
1175
|
-
messages.push({ role: 'tool', content: `BLOCKED: Tool "${parsed.name}" failed ${errorCount} times. Temporarily blocked.`, tool_call_id: blockId });
|
|
1176
|
-
const idx = validCalls.indexOf(call);
|
|
1177
|
-
if (idx !== -1)
|
|
1178
|
-
validCalls.splice(idx, 1);
|
|
1179
|
-
if (!quiet)
|
|
1180
|
-
renderer?.writeWarning(` [blocked ${parsed.name}: too many repeated errors]`);
|
|
1181
|
-
}
|
|
1106
|
+
const action = await errorEscalator.handleToolError(parsed.name, toolOutput, parsed.arguments, toolId, messages, createdFiles, enrichedPrompt, renderer, quiet, provider, registry, toolExecutor, modeConfig, llmCfg);
|
|
1107
|
+
if (action === "continue")
|
|
1108
|
+
continue;
|
|
1182
1109
|
}
|
|
1183
1110
|
else {
|
|
1184
|
-
|
|
1185
|
-
if (key.startsWith(parsed.name + '::'))
|
|
1186
|
-
consecutiveToolErrors.delete(key);
|
|
1187
|
-
}
|
|
1188
|
-
specialistSpawnedFor = null;
|
|
1111
|
+
errorEscalator.resetForTool(parsed.name);
|
|
1189
1112
|
}
|
|
1190
1113
|
// НЕ добавляем "Continue." после каждого tool-вызова — это нарушает
|
|
1191
1114
|
// структуру диалога. Bounded nudge (строка 418) уже покрывает
|
|
@@ -1196,12 +1119,12 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
1196
1119
|
}
|
|
1197
1120
|
}
|
|
1198
1121
|
if (aborted) {
|
|
1199
|
-
renderer?.writeWarning(
|
|
1122
|
+
renderer?.writeWarning("Agent stopped (double ESC)");
|
|
1200
1123
|
}
|
|
1201
|
-
logInfo(
|
|
1124
|
+
logInfo("LOOP", `runAgentLoop done: ${messages.length} total messages, aborted=${aborted}`);
|
|
1202
1125
|
// Agent:done event for notify plugin
|
|
1203
|
-
if (pluginManager.isActive(
|
|
1204
|
-
pluginManager.emit(
|
|
1126
|
+
if (pluginManager.isActive("notify")) {
|
|
1127
|
+
pluginManager.emit("agent:done").catch(() => { });
|
|
1205
1128
|
}
|
|
1206
1129
|
return { messages };
|
|
1207
1130
|
}
|