micro-models-agent 0.3.0 → 0.5.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/loop.d.ts +5 -5
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +422 -246
- package/dist/agent/loop.js.map +1 -1
- package/dist/agent/system-prompt.d.ts +3 -3
- package/dist/agent/system-prompt.d.ts.map +1 -1
- package/dist/agent/system-prompt.js +383 -278
- package/dist/agent/system-prompt.js.map +1 -1
- 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/config/config.d.ts +2 -1
- package/dist/config/config.d.ts.map +1 -1
- package/dist/config/config.js +75 -54
- package/dist/config/config.js.map +1 -1
- package/dist/config/types.d.ts +3 -1
- package/dist/config/types.d.ts.map +1 -1
- package/dist/logger.d.ts +10 -3
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +89 -38
- 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/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/subagent/chat.d.ts +7 -0
- package/dist/subagent/chat.d.ts.map +1 -0
- package/dist/subagent/chat.js +257 -0
- package/dist/subagent/chat.js.map +1 -0
- package/dist/subagent/context.d.ts +2 -0
- package/dist/subagent/context.d.ts.map +1 -0
- package/dist/subagent/context.js +64 -0
- package/dist/subagent/context.js.map +1 -0
- package/dist/subagent/definition.d.ts +3 -0
- package/dist/subagent/definition.d.ts.map +1 -0
- package/dist/subagent/definition.js +22 -0
- package/dist/subagent/definition.js.map +1 -0
- package/dist/subagent/errors.d.ts +7 -0
- package/dist/subagent/errors.d.ts.map +1 -0
- package/dist/subagent/errors.js +64 -0
- package/dist/subagent/errors.js.map +1 -0
- package/dist/subagent/validation.d.ts +11 -0
- package/dist/subagent/validation.d.ts.map +1 -0
- package/dist/subagent/validation.js +77 -0
- package/dist/subagent/validation.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/subagent.d.ts +14 -57
- package/dist/tools/subagent.d.ts.map +1 -1
- package/dist/tools/subagent.js +112 -559
- package/dist/tools/subagent.js.map +1 -1
- package/dist/tools/web-browse.js +7 -7
- 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/package.json +47 -47
- 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,55 +1,55 @@
|
|
|
1
|
-
import { readFileSync, existsSync } from
|
|
2
|
-
import { join, isAbsolute, relative } from
|
|
3
|
-
import { parseToolCalls } from
|
|
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 { generateToolCallId } from
|
|
15
|
-
import { checkFileSyntax } from
|
|
16
|
-
import { loadConfig, getLLMConfig, getGroundingConfig } from
|
|
17
|
-
import { buildSystemPrompt } from
|
|
18
|
-
import { logInfo, logWarn, logError, logDebug, logToolCall, setStatusWriter, writeStatus } from
|
|
19
|
-
import { buildSubagentContext, runIsolatedLoop, doChat } from
|
|
20
|
-
import { SubagentRenderer } from
|
|
21
|
-
import { ToolExecutor } from
|
|
22
|
-
import { ErrorEscalator } from
|
|
23
|
-
import { WriteEnforcer } from
|
|
24
|
-
import { PluginManager } from
|
|
25
|
-
import { loadAllPluginsWithInfo } from
|
|
26
|
-
import { handleToolAfter as lintHandleToolAfter } from
|
|
27
|
-
import { handleAgentDone, handleToolBlocked, handleToolAfter as notifyHandleToolAfter } from
|
|
28
|
-
import { planHandler } from
|
|
29
|
-
import { todoHandler } from
|
|
30
|
-
import { questionHandler } from
|
|
31
|
-
import { approveHandler } from
|
|
32
|
-
import { userProfileHandler } from
|
|
33
|
-
import { themeHandler } from
|
|
34
|
-
import { scaffoldHandler } from
|
|
35
|
-
import { projectMapHandler } from
|
|
36
|
-
import { bashHandler } from
|
|
37
|
-
import { readFileHandler } from
|
|
38
|
-
import { writeFileHandler } from
|
|
39
|
-
import { editFileHandler } from
|
|
40
|
-
import { listDirHandler } from
|
|
41
|
-
import { createDirHandler } from
|
|
42
|
-
import { deleteFileHandler } from
|
|
43
|
-
import { copyFileHandler } from
|
|
44
|
-
import { moveFileHandler } from
|
|
45
|
-
import { fileInfoHandler } from
|
|
46
|
-
import { globSearchHandler } from
|
|
47
|
-
import { grepSearchHandler } from
|
|
48
|
-
import { treeHandler } from
|
|
49
|
-
import { webSearchHandler } from
|
|
50
|
-
import { webFetchHandler } from
|
|
51
|
-
import { webBrowseHandler } from
|
|
52
|
-
import { truncateOutput } from
|
|
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, logToolOutput, 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";
|
|
53
53
|
/**
|
|
54
54
|
* Основной цикл ReAct-агента.
|
|
55
55
|
*
|
|
@@ -85,8 +85,12 @@ function extractToolCallFallback(response) {
|
|
|
85
85
|
const m = response.match(re);
|
|
86
86
|
if (m) {
|
|
87
87
|
let cmd = m[1];
|
|
88
|
-
cmd = cmd
|
|
89
|
-
|
|
88
|
+
cmd = cmd
|
|
89
|
+
.replace(/\\n/g, "\n")
|
|
90
|
+
.replace(/\\t/g, "\t")
|
|
91
|
+
.replace(/\\"/g, '"')
|
|
92
|
+
.replace(/\\\\/g, "\\");
|
|
93
|
+
return { name: "bash", cmd };
|
|
90
94
|
}
|
|
91
95
|
}
|
|
92
96
|
return null;
|
|
@@ -114,30 +118,39 @@ function extractToolCallFallback(response) {
|
|
|
114
118
|
* @param options - дополнительные опции (quiet, showThinking, mode)
|
|
115
119
|
*/
|
|
116
120
|
export async function runAgentLoop(provider, registry, prompt, contextManager, previousMessages, renderer, providerName, options) {
|
|
117
|
-
logInfo(
|
|
121
|
+
logInfo("LOOP", `runAgentLoop started: "${prompt.slice(0, 50)}"`);
|
|
118
122
|
renderer?.resetAbort?.();
|
|
119
123
|
// Парсинг ссылок на файлы в промпте (формат @path/to/file)
|
|
120
124
|
const { prompt: enrichedPrompt, files } = await parseFileRefs(prompt);
|
|
121
|
-
logDebug(
|
|
125
|
+
logDebug("LOOP", `parseFileRefs: ${files.length} files, prompt len: ${enrichedPrompt.length}`);
|
|
122
126
|
const quiet = options?.quiet ?? false;
|
|
123
127
|
// ─── ModeRegistry: конфигурация режима ──
|
|
124
128
|
const modeRegistry = new ModeRegistry();
|
|
125
|
-
const modeConfig = modeRegistry.getOrFallback(options?.mode ||
|
|
126
|
-
const applied = applyMode(modeConfig, {
|
|
129
|
+
const modeConfig = modeRegistry.getOrFallback(options?.mode || "base");
|
|
130
|
+
const applied = applyMode(modeConfig, {
|
|
131
|
+
quiet,
|
|
132
|
+
showThinking: options?.showThinking,
|
|
133
|
+
});
|
|
127
134
|
// ─── Behaviors из ModeConfig ──────────────────────────
|
|
128
135
|
const behaviors = modeConfig.behaviors;
|
|
129
136
|
const planEnforcement = behaviors.planEnforcement !== false;
|
|
130
|
-
const directWriteStreakMax = typeof behaviors.directWriteStreakLimit ===
|
|
131
|
-
? behaviors.directWriteStreakLimit
|
|
132
|
-
|
|
133
|
-
|
|
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;
|
|
134
147
|
const readConsolidation = behaviors.readConsolidation !== false;
|
|
135
148
|
// Автозагрузка скилов из промпта (если пользователь указал @skill-name)
|
|
136
149
|
const loadedSkills = loadSkillsFromRefs(prompt, process.cwd());
|
|
137
|
-
const loadedSkillNames = new Set(loadedSkills.map(s => s.name));
|
|
150
|
+
const loadedSkillNames = new Set(loadedSkills.map((s) => s.name));
|
|
138
151
|
if (loadedSkills.length > 0) {
|
|
139
|
-
logInfo(
|
|
140
|
-
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(", ")}`);
|
|
141
154
|
}
|
|
142
155
|
// Принудительная загрузка скилов из ModeConfig
|
|
143
156
|
for (const skillName of applied.forcedSkills) {
|
|
@@ -146,11 +159,11 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
146
159
|
if (skill) {
|
|
147
160
|
loadedSkills.push(skill);
|
|
148
161
|
loadedSkillNames.add(skillName);
|
|
149
|
-
logInfo(
|
|
162
|
+
logInfo("LOOP", `auto-loaded ${skillName} skill for ${modeConfig.id} mode`);
|
|
150
163
|
renderer?.writeInfo(` Auto-loaded @${skillName} skill for ${modeConfig.id} mode`);
|
|
151
164
|
}
|
|
152
165
|
else {
|
|
153
|
-
logWarn(
|
|
166
|
+
logWarn("LOOP", `${skillName} skill not found for ${modeConfig.id} mode`);
|
|
154
167
|
renderer?.writeWarning(` Warning: @${skillName} skill not found (required for ${modeConfig.id} mode)`);
|
|
155
168
|
}
|
|
156
169
|
}
|
|
@@ -162,8 +175,8 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
162
175
|
const budget = applied.skillTokenBudget;
|
|
163
176
|
const { allowed, dropped } = enforceSkillBudget(loadedSkills, budget);
|
|
164
177
|
if (dropped.length > 0) {
|
|
165
|
-
renderer?.writeWarning(` Skill limit (${maxSkills} max in ${modeConfig.id} mode): dropped ${dropped.join(
|
|
166
|
-
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)`);
|
|
167
180
|
loadedSkills.length = 0;
|
|
168
181
|
loadedSkills.push(...allowed);
|
|
169
182
|
for (const s of dropped)
|
|
@@ -171,33 +184,41 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
171
184
|
}
|
|
172
185
|
}
|
|
173
186
|
// Pre-load скиллов по ключевым словам (без language-specific хардкода)
|
|
174
|
-
if (loadedSkills.length === 0 && options?.mode !==
|
|
187
|
+
if (loadedSkills.length === 0 && options?.mode !== "fast") {
|
|
175
188
|
const keywordSkills = preloadSkillsByKeywords(prompt, process.cwd());
|
|
176
189
|
for (const skill of keywordSkills) {
|
|
177
190
|
if (!loadedSkillNames.has(skill.name)) {
|
|
178
191
|
loadedSkills.push(skill);
|
|
179
192
|
loadedSkillNames.add(skill.name);
|
|
180
|
-
logInfo(
|
|
193
|
+
logInfo("LOOP", `keyword preloaded skill: ${skill.name}`);
|
|
181
194
|
renderer?.writeInfo(` Auto-loaded @${skill.name} (keyword match)`);
|
|
182
195
|
}
|
|
183
196
|
}
|
|
184
197
|
// Если пользователь явно просит "используй скилл", но ничего не нашлось — подсказываем
|
|
185
198
|
if (keywordSkills.length === 0 && isSkillRequest(prompt)) {
|
|
186
199
|
const allSkills = listInstalledSkills(process.cwd());
|
|
187
|
-
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)`);
|
|
188
204
|
}
|
|
189
205
|
}
|
|
190
206
|
if (files.length > 0) {
|
|
191
207
|
const totalTokens = files.reduce((sum, f) => sum + f.tokens, 0);
|
|
192
|
-
const totalLines = files.reduce((sum, f) => sum + f.content.split(
|
|
193
|
-
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)`);
|
|
194
210
|
renderer?.writeInfo(` Loaded ${files.length} files (${totalLines} lines, ~${totalTokens} tokens)`);
|
|
195
211
|
}
|
|
196
212
|
setStatusWriter(renderer ? (msg) => renderer.writeDim(` ${msg}`) : null);
|
|
197
|
-
writeStatus(
|
|
213
|
+
writeStatus("Initializing...");
|
|
198
214
|
// ─── PluginManager: загрузка и активация плагинов ──
|
|
199
215
|
const pluginManager = new PluginManager();
|
|
200
|
-
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
|
+
};
|
|
201
222
|
const pluginCtx = { cwd: process.cwd(), config: {}, logger: pluginLogger };
|
|
202
223
|
// Загружаем все плагины (builtin + project + user)
|
|
203
224
|
const pluginInfos = await loadAllPluginsWithInfo(pluginManager);
|
|
@@ -206,7 +227,7 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
206
227
|
await pluginManager.activateAll(modeConfig.plugins, pluginCtx);
|
|
207
228
|
}
|
|
208
229
|
// Авто-активация builtin плагинов (все моды)
|
|
209
|
-
const builtinPlugins = pluginInfos.filter(p => p.source ===
|
|
230
|
+
const builtinPlugins = pluginInfos.filter((p) => p.source === "builtin");
|
|
210
231
|
for (const bp of builtinPlugins) {
|
|
211
232
|
if (!pluginManager.isActive(bp.name)) {
|
|
212
233
|
await pluginManager.activate(bp.name, pluginCtx);
|
|
@@ -220,15 +241,15 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
220
241
|
}
|
|
221
242
|
}
|
|
222
243
|
// Регистрация обработчиков событий для всех активных плагинов
|
|
223
|
-
if (pluginManager.isActive(
|
|
224
|
-
pluginManager.on(
|
|
225
|
-
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");
|
|
226
247
|
});
|
|
227
248
|
}
|
|
228
|
-
if (pluginManager.isActive(
|
|
229
|
-
pluginManager.on(
|
|
230
|
-
pluginManager.on(
|
|
231
|
-
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));
|
|
232
253
|
}
|
|
233
254
|
const pluginSections = await pluginManager.getPromptSections();
|
|
234
255
|
// Сборка system prompt (включает инструкции, информацию о проекте, скилы, память, плагины)
|
|
@@ -239,17 +260,19 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
239
260
|
setStatusWriter(null);
|
|
240
261
|
// Начальное состояние истории: system prompt + предыдущие сообщения + текущий промпт
|
|
241
262
|
const messages = [
|
|
242
|
-
{ role:
|
|
263
|
+
{ role: "system", content: systemPrompt },
|
|
243
264
|
...(previousMessages || []),
|
|
244
|
-
{ role:
|
|
265
|
+
{ role: "user", content: enrichedPrompt },
|
|
245
266
|
];
|
|
246
267
|
const ctx = contextManager ?? new ContextManager(provider.contextWindow);
|
|
247
268
|
const maxSteps = config.agent.maxSteps ?? 100;
|
|
248
269
|
let consecutiveEmpty = 0; // счётчик пустых ответов подряд
|
|
249
|
-
let pendingSyntaxError =
|
|
270
|
+
let pendingSyntaxError = "";
|
|
250
271
|
let aborted = false;
|
|
251
272
|
let continuationNudges = 0; // счётчик подсказок "продолжай работать"
|
|
252
273
|
let consecutiveMissingGrounding = 0; // счётчик пропущенных grounding-маркеров подряд
|
|
274
|
+
let groundingRetryCount = 0; // сколько раз был CRITICAL retry подряд
|
|
275
|
+
let consecutiveInvalidCalls = 0; // счётчик невалидных tool call подряд
|
|
253
276
|
/** Счётчик бесполезных действий (list_dir, glob_search, todo list без результата) */
|
|
254
277
|
let uselessActionCount = 0;
|
|
255
278
|
/** История последних чтений файлов для.detect повторных чтений */
|
|
@@ -258,16 +281,36 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
258
281
|
const recentGrepPatterns = [];
|
|
259
282
|
/** История последних вопросов для detect повторных вопросов */
|
|
260
283
|
const recentQuestions = [];
|
|
284
|
+
/** История последних bash-команд для detect циклических вызовов */
|
|
285
|
+
const recentBashCommands = [];
|
|
261
286
|
// ─── ToolExecutor: единый диспетчер инструментов ──
|
|
262
287
|
const toolExecutor = new ToolExecutor();
|
|
263
288
|
toolExecutor.setPluginManager(pluginManager);
|
|
264
289
|
toolExecutor.registerAll([
|
|
265
|
-
planHandler,
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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,
|
|
271
314
|
]);
|
|
272
315
|
const errorEscalator = new ErrorEscalator();
|
|
273
316
|
const writeEnforcer = new WriteEnforcer();
|
|
@@ -290,7 +333,7 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
290
333
|
}
|
|
291
334
|
let skillsAutoLoaded = false;
|
|
292
335
|
const createdFiles = []; // отслеживание созданных/изменённых файлов
|
|
293
|
-
logDebug(
|
|
336
|
+
logDebug("LOOP", `step ${step + 1}/${maxSteps} starting, ${messages.length} messages`);
|
|
294
337
|
// Инкрементальный подсчёт — считаем только новые сообщения (быстрее в ~100x на больших историях)
|
|
295
338
|
if (step === 0) {
|
|
296
339
|
await ctx.updateTokens(messages);
|
|
@@ -298,8 +341,8 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
298
341
|
else {
|
|
299
342
|
await ctx.updateTokensIncremental(messages);
|
|
300
343
|
}
|
|
301
|
-
const skillNames = loadedSkills.map(s => s.name).join(
|
|
302
|
-
const usageText = ctx.formatUsage() + (skillNames ? ` │ skills: ${skillNames}` :
|
|
344
|
+
const skillNames = loadedSkills.map((s) => s.name).join(", ");
|
|
345
|
+
const usageText = ctx.formatUsage() + (skillNames ? ` │ skills: ${skillNames}` : "");
|
|
303
346
|
renderer?.updateContextUsage(usageText);
|
|
304
347
|
renderer?.updateStatus({
|
|
305
348
|
provider: providerName,
|
|
@@ -315,7 +358,7 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
315
358
|
const before = messages.length;
|
|
316
359
|
messages.splice(0, messages.length, ...ctx.compact(messages));
|
|
317
360
|
if (!quiet) {
|
|
318
|
-
renderer?.writeBracketTag(
|
|
361
|
+
renderer?.writeBracketTag("compact", `${before} → ${messages.length} messages`);
|
|
319
362
|
}
|
|
320
363
|
await ctx.updateTokens(messages);
|
|
321
364
|
}
|
|
@@ -324,13 +367,13 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
324
367
|
const before = messages.length;
|
|
325
368
|
messages.splice(0, messages.length, ...ctx.emergencyCompact(messages));
|
|
326
369
|
if (!quiet) {
|
|
327
|
-
renderer?.writeBracketTag(
|
|
370
|
+
renderer?.writeBracketTag("compact", `EMERGENCY: ${before} → ${messages.length}`);
|
|
328
371
|
}
|
|
329
372
|
await ctx.updateTokens(messages);
|
|
330
373
|
}
|
|
331
374
|
// ─── Запрос к LLM ────────────────────────────────────
|
|
332
375
|
const llmCfg = getLLMConfig(config);
|
|
333
|
-
logInfo(
|
|
376
|
+
logInfo("LOOP", `calling doChat, messages: ${messages.length}, options.showThinking: ${options?.showThinking}, retries: ${llmCfg.retries}`);
|
|
334
377
|
const { response } = await doChat(provider, messages, renderer, renderer?.abortSignal, llmCfg.retries, options?.showThinking);
|
|
335
378
|
// Проверка прерывания во время генерации
|
|
336
379
|
if (renderer?.abortSignal?.aborted) {
|
|
@@ -339,12 +382,21 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
339
382
|
}
|
|
340
383
|
// ─── Автозагрузка скилов по @упоминаниям ─────────────
|
|
341
384
|
// Если ответ содержит @skill-name, загружаем скил и пересобираем system prompt
|
|
342
|
-
if (options?.mode !==
|
|
385
|
+
if (options?.mode !== "fast" && !skillsAutoLoaded) {
|
|
343
386
|
const suggestedSkillRefs = response.matchAll(/@(\w[\w-]*)/g);
|
|
344
387
|
const newSkillNames = [];
|
|
345
388
|
for (const match of suggestedSkillRefs) {
|
|
346
389
|
const name = match[1];
|
|
347
|
-
if ([
|
|
390
|
+
if ([
|
|
391
|
+
"email",
|
|
392
|
+
"api",
|
|
393
|
+
"id",
|
|
394
|
+
"json",
|
|
395
|
+
"url",
|
|
396
|
+
"http",
|
|
397
|
+
"https",
|
|
398
|
+
"localhost",
|
|
399
|
+
].includes(name))
|
|
348
400
|
continue;
|
|
349
401
|
if (!loadedSkillNames.has(name)) {
|
|
350
402
|
const skill = loadSkill(name, process.cwd());
|
|
@@ -362,16 +414,19 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
362
414
|
if (autoBudget > 0 && loadedSkills.length > 0) {
|
|
363
415
|
const { allowed, dropped } = enforceSkillBudget(loadedSkills, autoBudget);
|
|
364
416
|
if (dropped.length > 0) {
|
|
365
|
-
renderer?.writeWarning(` Auto-load budget: dropped ${dropped.join(
|
|
366
|
-
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)`);
|
|
367
419
|
loadedSkills.length = 0;
|
|
368
420
|
loadedSkills.push(...allowed);
|
|
369
421
|
for (const s of dropped)
|
|
370
422
|
loadedSkillNames.delete(s);
|
|
371
423
|
}
|
|
372
424
|
}
|
|
373
|
-
renderer?.writeInfo(` Auto-loaded ${loadedSkills.filter(s => newSkillNames.includes(s.name)).length} skill(s): ${newSkillNames.join(
|
|
374
|
-
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
|
+
};
|
|
375
430
|
// Не удаляем ответ ассистента — skills загружаются для следующих шагов цикла.
|
|
376
431
|
// Модель увидит обновлённый system prompt на следующей итерации и сможет
|
|
377
432
|
// использовать загруженные скилы без retry (экономит ~один LLM-запрос).
|
|
@@ -380,105 +435,124 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
380
435
|
// ─── Обработка пустых ответов ────────────────────────
|
|
381
436
|
// Маленькие модели иногда возвращают пустой ответ — повторяем с подсказкой
|
|
382
437
|
if (!response.trim()) {
|
|
383
|
-
logWarn(
|
|
438
|
+
logWarn("LOOP", `empty response #${consecutiveEmpty + 1}`);
|
|
384
439
|
consecutiveEmpty++;
|
|
385
440
|
// После компактинга — более информативное сообщение о пустом ответе
|
|
386
441
|
if (ctx.compactCount > 0 && consecutiveEmpty <= 3) {
|
|
387
442
|
let lastUserMsg;
|
|
388
443
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
389
|
-
if (messages[i].role ===
|
|
444
|
+
if (messages[i].role === "user") {
|
|
390
445
|
lastUserMsg = messages[i];
|
|
391
446
|
break;
|
|
392
447
|
}
|
|
393
448
|
}
|
|
394
449
|
const contextHint = lastUserMsg
|
|
395
|
-
? `[System: You returned an empty response. The conversation was compressed. Continue working on: "${(lastUserMsg.content ||
|
|
396
|
-
:
|
|
397
|
-
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 });
|
|
398
453
|
if (!quiet) {
|
|
399
454
|
renderer?.writeDim(` [empty response, retrying with context hint]`);
|
|
400
455
|
}
|
|
401
456
|
continue;
|
|
402
457
|
}
|
|
403
458
|
const lastMsg = messages[messages.length - 1];
|
|
404
|
-
if ((lastMsg?.role ===
|
|
459
|
+
if ((lastMsg?.role === "user" || lastMsg?.role === "system") &&
|
|
460
|
+
consecutiveEmpty <= 2) {
|
|
405
461
|
// После system-сообщений (например "Skills loaded") — более конкретная подсказка
|
|
406
|
-
const isAfterSystem = lastMsg?.role ===
|
|
462
|
+
const isAfterSystem = lastMsg?.role === "system";
|
|
407
463
|
const hint = isAfterSystem
|
|
408
|
-
?
|
|
409
|
-
:
|
|
410
|
-
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 });
|
|
411
467
|
if (!quiet) {
|
|
412
|
-
renderer?.writeDim(` [empty response, retrying${isAfterSystem ?
|
|
468
|
+
renderer?.writeDim(` [empty response, retrying${isAfterSystem ? " with skills hint" : ""}]`);
|
|
413
469
|
}
|
|
414
470
|
continue;
|
|
415
471
|
}
|
|
416
472
|
renderer?.writeError(consecutiveEmpty > 3
|
|
417
473
|
? `[model returned empty response ${consecutiveEmpty} times in a row — stopping]`
|
|
418
|
-
:
|
|
474
|
+
: "[model returned empty response]");
|
|
419
475
|
return { messages };
|
|
420
476
|
}
|
|
421
477
|
consecutiveEmpty = 0;
|
|
422
|
-
messages.push({ role:
|
|
478
|
+
messages.push({ role: "assistant", content: response });
|
|
423
479
|
const { validCalls, invalidCalls } = parseToolCalls(response);
|
|
424
480
|
if (validCalls.length === 0) {
|
|
425
481
|
if (invalidCalls.length > 0) {
|
|
426
482
|
// If the response has substantial text outside tool_call tags,
|
|
427
483
|
// it's a description with examples — not a failed tool call attempt.
|
|
428
|
-
const textOutside = response
|
|
484
|
+
const textOutside = response
|
|
485
|
+
.replace(/<tool_call>[\s\S]*?<\/tool_call>/g, "")
|
|
486
|
+
.trim();
|
|
429
487
|
if (textOutside.length < 100) {
|
|
430
|
-
|
|
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.`;
|
|
431
490
|
if (!quiet) {
|
|
432
491
|
renderer?.writeError(` [invalid tool call: ${invalidCalls[0]}]`);
|
|
433
492
|
}
|
|
434
|
-
messages.push({
|
|
493
|
+
messages.push({
|
|
494
|
+
role: "tool",
|
|
495
|
+
content: feedback,
|
|
496
|
+
tool_call_id: `invalid-${Date.now()}`,
|
|
497
|
+
});
|
|
435
498
|
continue;
|
|
436
499
|
}
|
|
437
500
|
// Text response with example tool calls — just log and proceed
|
|
438
|
-
logWarn(
|
|
501
|
+
logWarn("LOOP", `ignored ${invalidCalls.length} invalid tool call(s) in descriptive text`);
|
|
439
502
|
}
|
|
440
503
|
// Nudge model to continue working after a tool result — but only if
|
|
441
504
|
// the agent didn't already write a meaningful text response.
|
|
442
|
-
if (continuationNudgeMax > 0 &&
|
|
505
|
+
if (continuationNudgeMax > 0 &&
|
|
506
|
+
continuationNudges < continuationNudgeMax) {
|
|
443
507
|
const lastThree = messages.slice(-3);
|
|
444
|
-
const hasRecentToolResult = lastThree.some(m => m.role ===
|
|
445
|
-
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;
|
|
446
511
|
if (hasRecentToolResult && !alreadyResponded) {
|
|
447
512
|
continuationNudges++;
|
|
448
513
|
if (!quiet) {
|
|
449
514
|
renderer?.writeDim(` [nudge ${continuationNudges}/${continuationNudgeMax}: continue working]`);
|
|
450
515
|
}
|
|
451
|
-
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
|
+
});
|
|
452
520
|
continue;
|
|
453
521
|
}
|
|
454
522
|
}
|
|
455
523
|
if (!skillsAutoLoaded) {
|
|
456
|
-
const textOutside = response
|
|
524
|
+
const textOutside = response
|
|
525
|
+
.replace(/<tool_call>[\s\S]*?<\/tool_call>/g, "")
|
|
526
|
+
.trim();
|
|
457
527
|
const looksLikeDone = /done|готово|создан|выполн|completed|finished/i.test(textOutside);
|
|
458
528
|
// Проверка: план с незавершёнными шагами — не даём агенту остановиться
|
|
459
529
|
const pendingSteps = getPlanPendingCount();
|
|
460
530
|
if (pendingSteps > 0 && looksLikeDone) {
|
|
461
531
|
const planText = showPlan();
|
|
462
|
-
const pendingLines = planText
|
|
463
|
-
|
|
532
|
+
const pendingLines = planText
|
|
533
|
+
.split("\n")
|
|
534
|
+
.filter((l) => l.includes("○") || l.includes("►"));
|
|
535
|
+
const nextStep = pendingLines[0] || "";
|
|
464
536
|
const nextStepMatch = nextStep.match(/\d+\.\s*(.+)/);
|
|
465
|
-
const nextStepTitle = nextStepMatch ? nextStepMatch[1].trim() :
|
|
537
|
+
const nextStepTitle = nextStepMatch ? nextStepMatch[1].trim() : "";
|
|
466
538
|
if (!quiet) {
|
|
467
539
|
renderer?.writeWarning(` [plan has ${pendingSteps} incomplete steps — pushing agent to continue]`);
|
|
468
540
|
}
|
|
469
541
|
const nudge = nextStepTitle
|
|
470
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})`
|
|
471
|
-
: `Plan is NOT complete! ${pendingSteps} step(s) remaining:\n${pendingLines.join(
|
|
472
|
-
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 });
|
|
473
545
|
continuationNudges = 0;
|
|
474
546
|
continue;
|
|
475
547
|
}
|
|
476
548
|
// Phase 4.1: Аудит файлов при завершении плана
|
|
477
549
|
if (pendingSteps === 0 && looksLikeDone) {
|
|
478
550
|
const plan = showPlan();
|
|
479
|
-
if (!plan.startsWith(
|
|
551
|
+
if (!plan.startsWith("No active plan")) {
|
|
480
552
|
// Извлекаем заголовки шагов и проверяем наличие файлов
|
|
481
|
-
const stepLines = plan
|
|
553
|
+
const stepLines = plan
|
|
554
|
+
.split("\n")
|
|
555
|
+
.filter((l) => l.match(/\d+\.\s/));
|
|
482
556
|
const missingFiles = [];
|
|
483
557
|
for (const line of stepLines) {
|
|
484
558
|
const titleMatch = line.match(/\d+\.\s*(.+)/);
|
|
@@ -486,22 +560,23 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
486
560
|
continue;
|
|
487
561
|
const title = titleMatch[1].trim();
|
|
488
562
|
// Извлекаем имя файла из заголовка (паттерн: "Create file.ext" или "file.ext")
|
|
489
|
-
const fileMatch = title.match(/(?:Create|Создать|Add|Добавить)\s+(\S+\.(?:ts|tsx|js|jsx|json|html|css|md|yaml|yml))/i)
|
|
490
|
-
|
|
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);
|
|
491
565
|
if (fileMatch) {
|
|
492
566
|
const fileName = fileMatch[1];
|
|
493
|
-
if (!existsSync(fileName) &&
|
|
567
|
+
if (!existsSync(fileName) &&
|
|
568
|
+
!createdFiles.some((f) => f.endsWith(fileName))) {
|
|
494
569
|
missingFiles.push(fileName);
|
|
495
570
|
}
|
|
496
571
|
}
|
|
497
572
|
}
|
|
498
573
|
if (missingFiles.length > 0) {
|
|
499
574
|
if (!quiet) {
|
|
500
|
-
renderer?.writeWarning(` [plan complete but missing files: ${missingFiles.join(
|
|
575
|
+
renderer?.writeWarning(` [plan complete but missing files: ${missingFiles.join(", ")}]`);
|
|
501
576
|
}
|
|
502
577
|
messages.push({
|
|
503
|
-
role:
|
|
504
|
-
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.`,
|
|
505
580
|
});
|
|
506
581
|
continuationNudges = 0;
|
|
507
582
|
continue;
|
|
@@ -509,16 +584,19 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
509
584
|
}
|
|
510
585
|
}
|
|
511
586
|
// Stuck detection: если модель делает бесполезные действия
|
|
512
|
-
const isUselessAction = validCalls.some(c => c.parsed.name ===
|
|
513
|
-
|
|
514
|
-
(c.parsed.name ===
|
|
515
|
-
|
|
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
|
|
516
593
|
let isRepeatedRead = false;
|
|
517
594
|
let isRepeatedSearch = false;
|
|
518
595
|
let isRepeatedQuestion = false;
|
|
596
|
+
let isRepeatedBash = false;
|
|
519
597
|
for (const c of validCalls) {
|
|
520
|
-
if (c.parsed.name ===
|
|
521
|
-
const filePath = String(c.parsed.arguments?.path ||
|
|
598
|
+
if (c.parsed.name === "read_file") {
|
|
599
|
+
const filePath = String(c.parsed.arguments?.path || "");
|
|
522
600
|
if (recentReadFiles.includes(filePath)) {
|
|
523
601
|
isRepeatedRead = true;
|
|
524
602
|
}
|
|
@@ -526,8 +604,8 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
526
604
|
if (recentReadFiles.length > 5)
|
|
527
605
|
recentReadFiles.shift();
|
|
528
606
|
}
|
|
529
|
-
if (c.parsed.name ===
|
|
530
|
-
const pattern = String(c.parsed.arguments?.pattern ||
|
|
607
|
+
if (c.parsed.name === "grep_search") {
|
|
608
|
+
const pattern = String(c.parsed.arguments?.pattern || "");
|
|
531
609
|
if (recentGrepPatterns.includes(pattern)) {
|
|
532
610
|
isRepeatedSearch = true;
|
|
533
611
|
}
|
|
@@ -535,8 +613,8 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
535
613
|
if (recentGrepPatterns.length > 5)
|
|
536
614
|
recentGrepPatterns.shift();
|
|
537
615
|
}
|
|
538
|
-
if (c.parsed.name ===
|
|
539
|
-
const questionText = String(c.parsed.arguments?.question ||
|
|
616
|
+
if (c.parsed.name === "question") {
|
|
617
|
+
const questionText = String(c.parsed.arguments?.question || "");
|
|
540
618
|
if (recentQuestions.includes(questionText)) {
|
|
541
619
|
isRepeatedQuestion = true;
|
|
542
620
|
}
|
|
@@ -544,8 +622,22 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
544
622
|
if (recentQuestions.length > 3)
|
|
545
623
|
recentQuestions.shift();
|
|
546
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
|
+
}
|
|
547
635
|
}
|
|
548
|
-
const isStuckAction = isUselessAction ||
|
|
636
|
+
const isStuckAction = isUselessAction ||
|
|
637
|
+
isRepeatedRead ||
|
|
638
|
+
isRepeatedSearch ||
|
|
639
|
+
isRepeatedQuestion ||
|
|
640
|
+
isRepeatedBash;
|
|
549
641
|
if (isStuckAction) {
|
|
550
642
|
uselessActionCount++;
|
|
551
643
|
if (uselessActionCount >= 3) {
|
|
@@ -554,7 +646,7 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
554
646
|
}
|
|
555
647
|
clearPlan();
|
|
556
648
|
messages.push({
|
|
557
|
-
role:
|
|
649
|
+
role: "user",
|
|
558
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.]`,
|
|
559
651
|
});
|
|
560
652
|
uselessActionCount = 0;
|
|
@@ -562,17 +654,20 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
562
654
|
continue;
|
|
563
655
|
}
|
|
564
656
|
}
|
|
565
|
-
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")) {
|
|
566
660
|
uselessActionCount = 0;
|
|
567
661
|
recentReadFiles.length = 0;
|
|
568
662
|
recentGrepPatterns.length = 0;
|
|
569
663
|
recentQuestions.length = 0;
|
|
664
|
+
recentBashCommands.length = 0;
|
|
570
665
|
}
|
|
571
666
|
// Если модель claim-ит done но есть созданные файлы — напоминаем о полноте
|
|
572
667
|
if (createdFiles.length > 0 && looksLikeDone) {
|
|
573
668
|
messages.push({
|
|
574
|
-
role:
|
|
575
|
-
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.`,
|
|
576
671
|
});
|
|
577
672
|
continuationNudges = 0;
|
|
578
673
|
continue;
|
|
@@ -587,48 +682,75 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
587
682
|
const groundingCfg = getGroundingConfig(config);
|
|
588
683
|
if (groundingCfg.enabled) {
|
|
589
684
|
const groundMatches = Array.from(response.matchAll(/@grounding\[файл:\s*([^\]]+)\]/g));
|
|
590
|
-
const groundPaths = groundMatches.map(m => m[1].trim());
|
|
591
|
-
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);
|
|
592
687
|
if (pathCalls.length > groundPaths.length) {
|
|
593
688
|
consecutiveMissingGrounding++;
|
|
594
689
|
const maxMissing = groundingCfg.maxMissingGrounding ?? 3;
|
|
690
|
+
const maxRetries = groundingCfg.maxConsecutiveGroundingErrors ?? 5;
|
|
595
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) {
|
|
596
717
|
if (!quiet)
|
|
597
|
-
renderer?.writeError(` [grounding:
|
|
718
|
+
renderer?.writeError(` [grounding: strict mode, retrying]`);
|
|
598
719
|
messages.push({
|
|
599
|
-
role:
|
|
600
|
-
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>]`,
|
|
601
722
|
});
|
|
602
723
|
continue;
|
|
603
724
|
}
|
|
604
|
-
|
|
725
|
+
else {
|
|
605
726
|
if (!quiet)
|
|
606
|
-
renderer?.
|
|
727
|
+
renderer?.writeDim(` [grounding: missing marker, reminder ${consecutiveMissingGrounding}/${maxMissing}]`);
|
|
607
728
|
messages.push({
|
|
608
|
-
role:
|
|
609
|
-
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>]`,
|
|
610
731
|
});
|
|
611
732
|
continue;
|
|
612
733
|
}
|
|
613
|
-
if (!quiet)
|
|
614
|
-
renderer?.writeDim(` [grounding: missing marker, reminder ${consecutiveMissingGrounding}/${maxMissing}]`);
|
|
615
|
-
messages.push({
|
|
616
|
-
role: 'user',
|
|
617
|
-
content: `[Reminder: Include @grounding[файл: path] marker before each <tool_call> to prevent path hallucinations.]`,
|
|
618
|
-
});
|
|
619
|
-
continue;
|
|
620
734
|
}
|
|
621
735
|
else {
|
|
622
736
|
consecutiveMissingGrounding = 0;
|
|
737
|
+
groundingRetryCount = 0;
|
|
623
738
|
// Проверка соответствия пути в маркере и в аргументах tool_call
|
|
624
739
|
for (let i = 0; i < pathCalls.length && i < groundPaths.length; i++) {
|
|
625
|
-
const callPath = String(pathCalls[i].parsed.arguments.path ||
|
|
626
|
-
|
|
627
|
-
|
|
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)) {
|
|
628
750
|
if (!quiet)
|
|
629
751
|
renderer?.writeWarning(` [grounding: path mismatch — marker says "${groundPath}", tool says "${callPath}"]`);
|
|
630
752
|
messages.push({
|
|
631
|
-
role:
|
|
753
|
+
role: "user",
|
|
632
754
|
content: `[Warning: Grounding marker path "${groundPath}" doesn't match tool call path "${callPath}". Fix the marker to match the actual path.]`,
|
|
633
755
|
});
|
|
634
756
|
break;
|
|
@@ -646,9 +768,11 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
646
768
|
// ─── Исполнение tool calls ──────────────────────────
|
|
647
769
|
for (const call of validCalls) {
|
|
648
770
|
const { parsed, cmd } = call;
|
|
649
|
-
const isBash = parsed.name ===
|
|
771
|
+
const isBash = parsed.name === "bash";
|
|
650
772
|
const preview = isBash
|
|
651
|
-
?
|
|
773
|
+
? cmd.length > 200
|
|
774
|
+
? cmd.slice(0, 200) + "..."
|
|
775
|
+
: cmd
|
|
652
776
|
: JSON.stringify(parsed.arguments);
|
|
653
777
|
const toolId = generateToolCallId();
|
|
654
778
|
logToolCall(parsed.name, preview);
|
|
@@ -656,67 +780,79 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
656
780
|
renderer?.writeToolHeader(parsed.name);
|
|
657
781
|
renderer?.writeToolPreview(parsed.name, preview);
|
|
658
782
|
}
|
|
659
|
-
const hookCmd = isBash
|
|
783
|
+
const hookCmd = isBash
|
|
784
|
+
? cmd
|
|
785
|
+
: `${parsed.name} ${JSON.stringify(parsed.arguments)}`;
|
|
660
786
|
const preHook = runPreToolUseHooks(process.cwd(), parsed.name, hookCmd);
|
|
661
787
|
if (!preHook.continue) {
|
|
662
788
|
if (!quiet) {
|
|
663
|
-
renderer?.writeBracketTag(
|
|
789
|
+
renderer?.writeBracketTag("hook blocked", preHook.message || "");
|
|
664
790
|
}
|
|
665
|
-
messages.push({
|
|
791
|
+
messages.push({
|
|
792
|
+
role: "tool",
|
|
793
|
+
content: `Hook blocked: ${preHook.message || ""}`,
|
|
794
|
+
tool_call_id: toolId,
|
|
795
|
+
});
|
|
666
796
|
continue;
|
|
667
797
|
}
|
|
668
798
|
if (pendingSyntaxError && !isBash) {
|
|
669
|
-
const isWriteOp = parsed.name ===
|
|
670
|
-
const newPath = String(parsed.arguments.path ??
|
|
799
|
+
const isWriteOp = parsed.name === "write_file" || parsed.name === "edit_file";
|
|
800
|
+
const newPath = String(parsed.arguments.path ?? "");
|
|
671
801
|
// Allow editing config files (tsconfig.json, package.json) since they
|
|
672
802
|
// can fix syntax errors (e.g. missing jsx flag in tsconfig)
|
|
673
|
-
const isConfig = newPath ===
|
|
803
|
+
const isConfig = newPath === "tsconfig.json" || newPath === "package.json";
|
|
674
804
|
if (isWriteOp && newPath !== pendingSyntaxError && !isConfig) {
|
|
675
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).`;
|
|
676
806
|
if (!quiet) {
|
|
677
807
|
renderer?.writeError(` [blocked: fix syntax error in ${pendingSyntaxError} first]`);
|
|
678
808
|
}
|
|
679
|
-
messages.push({
|
|
809
|
+
messages.push({
|
|
810
|
+
role: "tool",
|
|
811
|
+
content: blocked,
|
|
812
|
+
tool_call_id: toolId,
|
|
813
|
+
});
|
|
680
814
|
continue;
|
|
681
815
|
}
|
|
682
816
|
}
|
|
683
|
-
let toolOutput =
|
|
817
|
+
let toolOutput = "";
|
|
684
818
|
let toolExitCode = 0;
|
|
685
819
|
const toolStart = performance.now();
|
|
686
820
|
// ─── Субагент: изолированный контекст для задачи ──
|
|
687
|
-
if (parsed.name ===
|
|
688
|
-
const task = String(parsed.arguments.task ||
|
|
821
|
+
if (parsed.name === "subagent") {
|
|
822
|
+
const task = String(parsed.arguments.task || "");
|
|
689
823
|
if (!task) {
|
|
690
|
-
toolOutput =
|
|
824
|
+
toolOutput = "Error: task is required";
|
|
691
825
|
toolExitCode = 1;
|
|
692
826
|
}
|
|
693
827
|
else {
|
|
694
828
|
// Авто-пометка следующего pending шага как in_progress
|
|
695
829
|
const nextPending = getNextPendingStep();
|
|
696
830
|
if (nextPending) {
|
|
697
|
-
updatePlanStep(nextPending.id,
|
|
831
|
+
updatePlanStep(nextPending.id, "in_progress");
|
|
698
832
|
}
|
|
699
833
|
// Phase 2.3: Авто-подстановка полного пути в задачу subagent
|
|
700
834
|
let finalTask = task;
|
|
701
835
|
if (nextPending) {
|
|
702
836
|
const expectedDir = extractDirFromStepTitle(nextPending.title);
|
|
703
|
-
if (expectedDir && !task.includes(expectedDir +
|
|
837
|
+
if (expectedDir && !task.includes(expectedDir + "/")) {
|
|
704
838
|
finalTask = `${task} (directory: ${expectedDir}/)`;
|
|
705
|
-
logInfo(
|
|
839
|
+
logInfo("AGENT", `injected dir path into subagent task: "${expectedDir}"`);
|
|
706
840
|
}
|
|
707
841
|
}
|
|
708
842
|
const autoContext = buildSubagentContext(process.cwd());
|
|
709
|
-
logInfo(
|
|
843
|
+
logInfo("AGENT", `spawning subagent | task: "${finalTask.slice(0, 150)}"`);
|
|
710
844
|
// Показываем спиннер на основном рендерере пока субагент работает
|
|
711
845
|
if (!quiet) {
|
|
712
|
-
const taskPreview = finalTask.length > 60
|
|
846
|
+
const taskPreview = finalTask.length > 60
|
|
847
|
+
? finalTask.slice(0, 57) + "..."
|
|
848
|
+
: finalTask;
|
|
713
849
|
renderer?.writeSpinner(` [subagent] ${taskPreview}`);
|
|
714
850
|
}
|
|
715
851
|
const subRenderer = new SubagentRenderer();
|
|
716
852
|
try {
|
|
717
853
|
const childResult = await runIsolatedLoop(provider, registry, finalTask, autoContext, process.cwd(), quiet, subRenderer, toolExecutor, modeConfig.subagentMode, llmCfg.toolModel);
|
|
718
854
|
toolOutput = childResult.summary;
|
|
719
|
-
logInfo(
|
|
855
|
+
logInfo("AGENT", `subagent returned | files: ${childResult.createdFiles.length} | turns: ${childResult.turns} | errors: ${childResult.errors.length}`);
|
|
720
856
|
if (childResult.createdFiles.length > 0) {
|
|
721
857
|
for (const f of childResult.createdFiles) {
|
|
722
858
|
if (!createdFiles.includes(f))
|
|
@@ -727,26 +863,30 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
727
863
|
if (nextPending && childResult.createdFiles.length > 0) {
|
|
728
864
|
const expectedDir = extractDirFromStepTitle(nextPending.title);
|
|
729
865
|
if (expectedDir) {
|
|
730
|
-
const wrongDirFiles = childResult.createdFiles.filter(f => !f.startsWith(expectedDir +
|
|
866
|
+
const wrongDirFiles = childResult.createdFiles.filter((f) => !f.startsWith(expectedDir + "/") &&
|
|
867
|
+
!f.startsWith(expectedDir + "\\"));
|
|
731
868
|
if (wrongDirFiles.length > 0) {
|
|
732
|
-
const correction = `⚠ Files created in wrong directory: ${wrongDirFiles.join(
|
|
869
|
+
const correction = `⚠ Files created in wrong directory: ${wrongDirFiles.join(", ")}. ` +
|
|
733
870
|
`They should be in ${expectedDir}/. Use write_file with full path: ${expectedDir}/${wrongDirFiles[0]}`;
|
|
734
|
-
toolOutput +=
|
|
871
|
+
toolOutput += "\n\n" + correction;
|
|
735
872
|
if (!quiet) {
|
|
736
|
-
renderer?.writeWarning(` [subagent created files in wrong directory: ${wrongDirFiles.join(
|
|
873
|
+
renderer?.writeWarning(` [subagent created files in wrong directory: ${wrongDirFiles.join(", ")}]`);
|
|
737
874
|
}
|
|
738
875
|
}
|
|
739
876
|
}
|
|
740
877
|
}
|
|
741
878
|
const buf = subRenderer.getBuffer();
|
|
742
879
|
if (!quiet && renderer?.writeToolBuffer) {
|
|
743
|
-
renderer.writeToolBuffer({
|
|
880
|
+
renderer.writeToolBuffer({
|
|
881
|
+
lines: buf.lines,
|
|
882
|
+
toolCalls: buf.toolCalls,
|
|
883
|
+
});
|
|
744
884
|
}
|
|
745
885
|
}
|
|
746
886
|
catch (e) {
|
|
747
887
|
toolOutput = `Subagent error: ${e.message}`;
|
|
748
888
|
toolExitCode = 1;
|
|
749
|
-
logError(
|
|
889
|
+
logError("AGENT", `subagent error: ${e.message}`);
|
|
750
890
|
}
|
|
751
891
|
}
|
|
752
892
|
const subTime = ((performance.now() - toolStart) / 1000).toFixed(1);
|
|
@@ -760,12 +900,13 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
760
900
|
const mcpCmd = parseMCPCommand(cmd);
|
|
761
901
|
if (mcpCmd) {
|
|
762
902
|
if (!quiet) {
|
|
763
|
-
renderer?.writeBracketTag(
|
|
903
|
+
renderer?.writeBracketTag("mcp", `${mcpCmd.server}/${mcpCmd.tool}`);
|
|
764
904
|
}
|
|
765
905
|
toolOutput = callMCPTool(process.cwd(), mcpCmd.server, mcpCmd.tool, mcpCmd.args);
|
|
766
906
|
}
|
|
767
907
|
else {
|
|
768
|
-
toolOutput =
|
|
908
|
+
toolOutput =
|
|
909
|
+
"Invalid MCP command. Format: mcp <server> <tool> [args_json]";
|
|
769
910
|
toolExitCode = 1;
|
|
770
911
|
}
|
|
771
912
|
// ─── Все инструменты через ToolExecutor ──────────────
|
|
@@ -776,19 +917,25 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
776
917
|
let executorArgs = parsed.arguments;
|
|
777
918
|
// Bash-команды
|
|
778
919
|
if (isBash) {
|
|
779
|
-
executorName =
|
|
920
|
+
executorName = "bash";
|
|
780
921
|
executorArgs = { cmd, timeout: parsed.arguments?.timeout ?? 120 };
|
|
781
922
|
}
|
|
782
|
-
const hookCmd = isBash
|
|
783
|
-
|
|
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
|
+
};
|
|
784
931
|
const toolCtx = { cwd: process.cwd(), renderer, quiet };
|
|
785
932
|
const { result, elapsed } = await toolExecutor.execute(executorCall, toolCtx, hookCmd);
|
|
786
|
-
toolOutput = result.stdout || result.stderr ||
|
|
933
|
+
toolOutput = result.stdout || result.stderr || "(no output)";
|
|
787
934
|
toolExitCode = result.exitCode;
|
|
788
935
|
// Обработка question/approve: приостанавливаем цикл
|
|
789
936
|
if (result.questionPending) {
|
|
790
937
|
messages.push({
|
|
791
|
-
role:
|
|
938
|
+
role: "tool",
|
|
792
939
|
content: toolOutput,
|
|
793
940
|
tool_call_id: toolId,
|
|
794
941
|
});
|
|
@@ -799,75 +946,100 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
799
946
|
}
|
|
800
947
|
}
|
|
801
948
|
const toolTime = ((performance.now() - toolStart) / 1000).toFixed(1);
|
|
802
|
-
|
|
949
|
+
logToolOutput(parsed.name, toolOutput, toolExitCode);
|
|
950
|
+
logToolCall(parsed.name, preview, toolExitCode !== 0
|
|
951
|
+
? `exit:${toolExitCode} ${toolOutput.slice(0, 100)}`
|
|
952
|
+
: `ok (${toolTime}s)`);
|
|
803
953
|
if (toolExitCode !== 0) {
|
|
804
954
|
const errText = toolOutput;
|
|
805
955
|
if (!quiet) {
|
|
806
|
-
for (const line of errText.split(
|
|
956
|
+
for (const line of errText.split("\n").slice(0, 5)) {
|
|
807
957
|
renderer?.writeError(` ${line}`);
|
|
808
958
|
}
|
|
809
959
|
}
|
|
810
|
-
autoLearnFromError(hookCmd, toolExitCode !== 0 ? toolOutput :
|
|
960
|
+
autoLearnFromError(hookCmd, toolExitCode !== 0 ? toolOutput : "", "");
|
|
811
961
|
}
|
|
812
962
|
runPostToolUseHooks(process.cwd(), parsed.name, hookCmd, toolOutput, toolExitCode);
|
|
813
963
|
// Track sequential read_file calls for consolidation
|
|
814
|
-
if (readConsolidation &&
|
|
815
|
-
|
|
964
|
+
if (readConsolidation &&
|
|
965
|
+
!isBash &&
|
|
966
|
+
parsed.name === "read_file" &&
|
|
967
|
+
toolExitCode === 0) {
|
|
968
|
+
const filePath = String(parsed.arguments.path ?? "");
|
|
816
969
|
const offset = Math.max(1, Number(parsed.arguments.offset ?? 1));
|
|
817
970
|
const limit = Math.max(1, Number(parsed.arguments.limit ?? 500));
|
|
818
|
-
if (lastReadFile &&
|
|
971
|
+
if (lastReadFile &&
|
|
972
|
+
lastReadFile.path === filePath &&
|
|
973
|
+
offset === lastReadFile.offset + lastReadFile.limit) {
|
|
819
974
|
// Sequential read — merge with previous result
|
|
820
|
-
lastReadFile.content +=
|
|
975
|
+
lastReadFile.content += "\n" + toolOutput;
|
|
821
976
|
lastReadFile.offset = offset;
|
|
822
977
|
lastReadFile.limit = limit;
|
|
823
978
|
// Replace the last tool message content with merged content
|
|
824
979
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
825
|
-
if (messages[i].role ===
|
|
980
|
+
if (messages[i].role === "tool" &&
|
|
981
|
+
messages[i].tool_call_id === lastReadFile.toolId) {
|
|
826
982
|
messages[i].content = lastReadFile.content;
|
|
827
983
|
break;
|
|
828
984
|
}
|
|
829
985
|
}
|
|
830
986
|
// Remove the trailing "Continue." nudge + the new tool result that would be pushed
|
|
831
|
-
while (messages.length > 0 &&
|
|
987
|
+
while (messages.length > 0 &&
|
|
988
|
+
messages[messages.length - 1].role === "user" &&
|
|
989
|
+
messages[messages.length - 1].content === "Continue.") {
|
|
832
990
|
messages.pop();
|
|
833
991
|
}
|
|
834
992
|
if (!quiet) {
|
|
835
|
-
renderer?.writeBracketTag(
|
|
993
|
+
renderer?.writeBracketTag("consolidate", `merged read_file chunks for ${filePath}`);
|
|
836
994
|
}
|
|
837
995
|
continue; // skip pushing this result separately
|
|
838
996
|
}
|
|
839
997
|
else {
|
|
840
998
|
// New file or non-sequential — start fresh
|
|
841
|
-
lastReadFile = {
|
|
999
|
+
lastReadFile = {
|
|
1000
|
+
path: filePath,
|
|
1001
|
+
offset,
|
|
1002
|
+
limit,
|
|
1003
|
+
content: toolOutput,
|
|
1004
|
+
toolId,
|
|
1005
|
+
};
|
|
842
1006
|
}
|
|
843
1007
|
}
|
|
844
|
-
else if (!isBash && parsed.name !==
|
|
1008
|
+
else if (!isBash && parsed.name !== "read_file") {
|
|
845
1009
|
lastReadFile = null;
|
|
846
1010
|
}
|
|
847
1011
|
// ─── Проверка синтаксиса после записи файлов ────────
|
|
848
1012
|
let hasSyntaxError = false;
|
|
849
1013
|
if (toolExitCode === 0) {
|
|
850
1014
|
const isWriteOp = isBash
|
|
851
|
-
?
|
|
852
|
-
:
|
|
1015
|
+
? cmd.match(/Set-Content|Add-Content/)
|
|
1016
|
+
: parsed.name === "write_file" || parsed.name === "edit_file";
|
|
853
1017
|
if (isWriteOp) {
|
|
854
1018
|
const rawPath = isBash
|
|
855
|
-
? cmd
|
|
856
|
-
|
|
1019
|
+
? cmd
|
|
1020
|
+
.match(/-Path\s+['"]?([^'"]+\.(?:ts|tsx|js|jsx|json))['"]?/i)?.[1]
|
|
1021
|
+
?.replace(/\\/g, "/")
|
|
1022
|
+
: String(parsed.arguments.path ?? "");
|
|
857
1023
|
if (rawPath && /\.(ts|tsx|js|jsx|json)$/.test(rawPath)) {
|
|
858
|
-
const filePath = isAbsolute(rawPath)
|
|
1024
|
+
const filePath = isAbsolute(rawPath)
|
|
1025
|
+
? relative(process.cwd(), rawPath).replace(/\\/g, "/")
|
|
1026
|
+
: rawPath;
|
|
859
1027
|
const syntaxResult = checkFileSyntax(filePath, process.cwd());
|
|
860
|
-
if (syntaxResult !==
|
|
861
|
-
const errorLines = syntaxResult
|
|
1028
|
+
if (syntaxResult !== "OK") {
|
|
1029
|
+
const errorLines = syntaxResult
|
|
1030
|
+
.split("\n")
|
|
1031
|
+
.filter((l) => l.trim());
|
|
862
1032
|
const shown = errorLines.slice(0, 3);
|
|
863
|
-
const extra = errorLines.length > 3
|
|
1033
|
+
const extra = errorLines.length > 3
|
|
1034
|
+
? ` (+${errorLines.length - 3} more)`
|
|
1035
|
+
: "";
|
|
864
1036
|
if (!quiet) {
|
|
865
|
-
renderer?.writeBracketTag(
|
|
1037
|
+
renderer?.writeBracketTag("syntax", `${shown.join("; ")}${extra}`);
|
|
866
1038
|
}
|
|
867
1039
|
const fullPath = join(process.cwd(), filePath);
|
|
868
|
-
let contextLines =
|
|
1040
|
+
let contextLines = "";
|
|
869
1041
|
try {
|
|
870
|
-
const fileContent = readFileSync(fullPath,
|
|
1042
|
+
const fileContent = readFileSync(fullPath, "utf-8").split("\n");
|
|
871
1043
|
for (const errLine of shown) {
|
|
872
1044
|
const lineMatch = errLine.match(/\((\d+),\d+\)/);
|
|
873
1045
|
if (lineMatch) {
|
|
@@ -877,21 +1049,23 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
877
1049
|
const end = Math.min(fileContent.length, lineNum + 1);
|
|
878
1050
|
contextLines += `\n--- Line ${lineNum} ---\n`;
|
|
879
1051
|
for (let i = start; i < end; i++) {
|
|
880
|
-
const marker = i === lineNum - 1 ?
|
|
1052
|
+
const marker = i === lineNum - 1 ? " >> " : " ";
|
|
881
1053
|
contextLines += `${marker}${i + 1}: ${fileContent[i]}\n`;
|
|
882
1054
|
}
|
|
883
1055
|
}
|
|
884
1056
|
}
|
|
885
1057
|
}
|
|
886
1058
|
}
|
|
887
|
-
catch {
|
|
1059
|
+
catch {
|
|
1060
|
+
/* ignore read errors */
|
|
1061
|
+
}
|
|
888
1062
|
toolOutput = `SYNTAX ERROR in ${filePath}:\n${syntaxResult}${contextLines}`;
|
|
889
1063
|
// Detect config-related TS errors (missing jsx, module issues, etc.)
|
|
890
|
-
const isConfigError = syntaxResult.includes(
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
1064
|
+
const isConfigError = syntaxResult.includes("TS17004") ||
|
|
1065
|
+
syntaxResult.includes("TS2307") ||
|
|
1066
|
+
syntaxResult.includes("Cannot find module") ||
|
|
1067
|
+
syntaxResult.includes("jsx") ||
|
|
1068
|
+
syntaxResult.includes("moduleResolution");
|
|
895
1069
|
if (isConfigError) {
|
|
896
1070
|
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.`;
|
|
897
1071
|
}
|
|
@@ -903,16 +1077,18 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
903
1077
|
}
|
|
904
1078
|
else {
|
|
905
1079
|
if (pendingSyntaxError === filePath) {
|
|
906
|
-
pendingSyntaxError =
|
|
1080
|
+
pendingSyntaxError = "";
|
|
907
1081
|
}
|
|
908
1082
|
}
|
|
909
1083
|
// Автодетекция стиля кодирования из содержимого файла
|
|
910
1084
|
if (toolExitCode === 0 && filePath) {
|
|
911
1085
|
try {
|
|
912
|
-
const fullContent = readFileSync(join(process.cwd(), filePath),
|
|
1086
|
+
const fullContent = readFileSync(join(process.cwd(), filePath), "utf-8");
|
|
913
1087
|
collectCodingStyle(filePath, fullContent);
|
|
914
1088
|
}
|
|
915
|
-
catch {
|
|
1089
|
+
catch {
|
|
1090
|
+
/* ignore */
|
|
1091
|
+
}
|
|
916
1092
|
// Track created/modified files for completion reporting
|
|
917
1093
|
if (!hasSyntaxError && !createdFiles.includes(filePath)) {
|
|
918
1094
|
createdFiles.push(filePath);
|
|
@@ -922,14 +1098,14 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
922
1098
|
}
|
|
923
1099
|
}
|
|
924
1100
|
messages.push({
|
|
925
|
-
role:
|
|
1101
|
+
role: "tool",
|
|
926
1102
|
content: truncateOutput(parsed.name, toolOutput, toolExitCode),
|
|
927
1103
|
tool_call_id: toolId,
|
|
928
1104
|
});
|
|
929
1105
|
// ─── Error escalation + specialist auto-delegation ──────────
|
|
930
1106
|
if (toolExitCode !== 0 && toolOutput) {
|
|
931
1107
|
const action = await errorEscalator.handleToolError(parsed.name, toolOutput, parsed.arguments, toolId, messages, createdFiles, enrichedPrompt, renderer, quiet, provider, registry, toolExecutor, modeConfig, llmCfg);
|
|
932
|
-
if (action ===
|
|
1108
|
+
if (action === "continue")
|
|
933
1109
|
continue;
|
|
934
1110
|
}
|
|
935
1111
|
else {
|
|
@@ -944,12 +1120,12 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
944
1120
|
}
|
|
945
1121
|
}
|
|
946
1122
|
if (aborted) {
|
|
947
|
-
renderer?.writeWarning(
|
|
1123
|
+
renderer?.writeWarning("Agent stopped (double ESC)");
|
|
948
1124
|
}
|
|
949
|
-
logInfo(
|
|
1125
|
+
logInfo("LOOP", `runAgentLoop done: ${messages.length} total messages, aborted=${aborted}`);
|
|
950
1126
|
// Agent:done event for notify plugin
|
|
951
|
-
if (pluginManager.isActive(
|
|
952
|
-
pluginManager.emit(
|
|
1127
|
+
if (pluginManager.isActive("notify")) {
|
|
1128
|
+
pluginManager.emit("agent:done").catch(() => { });
|
|
953
1129
|
}
|
|
954
1130
|
return { messages };
|
|
955
1131
|
}
|