@pentoshi/clai 2.0.62 → 3.5.1
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 +262 -368
- package/dist/agent/classic-renderer.js +9 -1
- package/dist/agent/classic-renderer.js.map +1 -1
- package/dist/agent/context-manager.d.ts +5 -0
- package/dist/agent/context-manager.js +14 -2
- package/dist/agent/context-manager.js.map +1 -1
- package/dist/agent/events.d.ts +9 -1
- package/dist/agent/plan-tool.js +80 -8
- package/dist/agent/plan-tool.js.map +1 -1
- package/dist/agent/runner.js +261 -122
- package/dist/agent/runner.js.map +1 -1
- package/dist/agent/tool-call-parser.d.ts +20 -0
- package/dist/agent/tool-call-parser.js +186 -2
- package/dist/agent/tool-call-parser.js.map +1 -1
- package/dist/app/adapters/agent-event-adapter.d.ts +29 -0
- package/dist/app/adapters/agent-event-adapter.js +148 -0
- package/dist/app/adapters/agent-event-adapter.js.map +1 -0
- package/dist/app/adapters/current-agent-adapter.d.ts +7 -0
- package/dist/app/adapters/current-agent-adapter.js +27 -0
- package/dist/app/adapters/current-agent-adapter.js.map +1 -0
- package/dist/app/adapters/current-jobs-adapter.d.ts +4 -0
- package/dist/app/adapters/current-jobs-adapter.js +13 -0
- package/dist/app/adapters/current-jobs-adapter.js.map +1 -0
- package/dist/app/adapters/current-store-adapter.d.ts +3 -0
- package/dist/app/adapters/current-store-adapter.js +22 -0
- package/dist/app/adapters/current-store-adapter.js.map +1 -0
- package/dist/app/adapters/current-terminal-adapter.d.ts +9 -0
- package/dist/app/adapters/current-terminal-adapter.js +31 -0
- package/dist/app/adapters/current-terminal-adapter.js.map +1 -0
- package/dist/app/adapters/current-updates-adapter.d.ts +7 -0
- package/dist/app/adapters/current-updates-adapter.js +31 -0
- package/dist/app/adapters/current-updates-adapter.js.map +1 -0
- package/dist/app/adapters/in-memory-clipboard-adapter.d.ts +10 -0
- package/dist/app/adapters/in-memory-clipboard-adapter.js +20 -0
- package/dist/app/adapters/in-memory-clipboard-adapter.js.map +1 -0
- package/dist/app/commands/command.d.ts +24 -0
- package/dist/app/commands/command.js +4 -0
- package/dist/app/commands/command.js.map +1 -0
- package/dist/app/commands/registry.d.ts +45 -0
- package/dist/app/commands/registry.js +164 -0
- package/dist/app/commands/registry.js.map +1 -0
- package/dist/app/controllers/disposable.d.ts +15 -0
- package/dist/app/controllers/disposable.js +38 -0
- package/dist/app/controllers/disposable.js.map +1 -0
- package/dist/app/controllers/job-controller.d.ts +22 -0
- package/dist/app/controllers/job-controller.js +35 -0
- package/dist/app/controllers/job-controller.js.map +1 -0
- package/dist/app/controllers/plan-controller.d.ts +31 -0
- package/dist/app/controllers/plan-controller.js +69 -0
- package/dist/app/controllers/plan-controller.js.map +1 -0
- package/dist/app/controllers/session-controller.d.ts +167 -0
- package/dist/app/controllers/session-controller.js +467 -0
- package/dist/app/controllers/session-controller.js.map +1 -0
- package/dist/app/controllers/turn-controller.d.ts +52 -0
- package/dist/app/controllers/turn-controller.js +118 -0
- package/dist/app/controllers/turn-controller.js.map +1 -0
- package/dist/app/events/app-event.d.ts +122 -0
- package/dist/app/events/app-event.js +25 -0
- package/dist/app/events/app-event.js.map +1 -0
- package/dist/app/events/event-buffer.d.ts +51 -0
- package/dist/app/events/event-buffer.js +109 -0
- package/dist/app/events/event-buffer.js.map +1 -0
- package/dist/app/events/sequencer.d.ts +32 -0
- package/dist/app/events/sequencer.js +55 -0
- package/dist/app/events/sequencer.js.map +1 -0
- package/dist/app/ports/agent-port.d.ts +29 -0
- package/dist/app/ports/agent-port.js +2 -0
- package/dist/app/ports/agent-port.js.map +1 -0
- package/dist/app/ports/clipboard-port.d.ts +9 -0
- package/dist/app/ports/clipboard-port.js +2 -0
- package/dist/app/ports/clipboard-port.js.map +1 -0
- package/dist/app/ports/confirm-port.d.ts +17 -0
- package/dist/app/ports/confirm-port.js +2 -0
- package/dist/app/ports/confirm-port.js.map +1 -0
- package/dist/app/ports/jobs-port.d.ts +18 -0
- package/dist/app/ports/jobs-port.js +2 -0
- package/dist/app/ports/jobs-port.js.map +1 -0
- package/dist/app/ports/persistence-port.d.ts +21 -0
- package/dist/app/ports/persistence-port.js +2 -0
- package/dist/app/ports/persistence-port.js.map +1 -0
- package/dist/app/ports/secret-port.d.ts +12 -0
- package/dist/app/ports/secret-port.js +2 -0
- package/dist/app/ports/secret-port.js.map +1 -0
- package/dist/app/ports/terminal-port.d.ts +18 -0
- package/dist/app/ports/terminal-port.js +2 -0
- package/dist/app/ports/terminal-port.js.map +1 -0
- package/dist/app/ports/updates-port.d.ts +12 -0
- package/dist/app/ports/updates-port.js +2 -0
- package/dist/app/ports/updates-port.js.map +1 -0
- package/dist/commands/doctor.js +19 -0
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/update.js +1 -1
- package/dist/commands/update.js.map +1 -1
- package/dist/index.js +47 -12
- package/dist/index.js.map +1 -1
- package/dist/llm/router.js +4 -2
- package/dist/llm/router.js.map +1 -1
- package/dist/prompts/index.d.ts +1 -1
- package/dist/prompts/index.js +11 -8
- package/dist/prompts/index.js.map +1 -1
- package/dist/repl/slash-commands.js +1 -1
- package/dist/repl/slash-commands.js.map +1 -1
- package/dist/repl.js +35 -107
- package/dist/repl.js.map +1 -1
- package/dist/store/config.js +3 -1
- package/dist/store/config.js.map +1 -1
- package/dist/store/history.d.ts +26 -2
- package/dist/store/history.js +383 -72
- package/dist/store/history.js.map +1 -1
- package/dist/store/logs.js +3 -3
- package/dist/store/logs.js.map +1 -1
- package/dist/store/paths.d.ts +6 -0
- package/dist/store/paths.js +38 -0
- package/dist/store/paths.js.map +1 -0
- package/dist/store/plan.js +8 -5
- package/dist/store/plan.js.map +1 -1
- package/dist/tools/fs.d.ts +6 -0
- package/dist/tools/fs.js +56 -6
- package/dist/tools/fs.js.map +1 -1
- package/dist/tools/jobs.js +2 -2
- package/dist/tools/jobs.js.map +1 -1
- package/dist/tools/registry.js +189 -67
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/shell.js +2 -2
- package/dist/tools/shell.js.map +1 -1
- package/dist/tui/runtime.d.ts +19 -0
- package/dist/tui/runtime.js +92 -0
- package/dist/tui/runtime.js.map +1 -0
- package/dist/tui/state.js +22 -17
- package/dist/tui/state.js.map +1 -1
- package/dist/tui-v2/actions/action-id.d.ts +14 -0
- package/dist/tui-v2/actions/action-id.js +109 -0
- package/dist/tui-v2/actions/action-id.js.map +1 -0
- package/dist/tui-v2/actions/action-router.d.ts +18 -0
- package/dist/tui-v2/actions/action-router.js +62 -0
- package/dist/tui-v2/actions/action-router.js.map +1 -0
- package/dist/tui-v2/actions/chord-from-key.d.ts +26 -0
- package/dist/tui-v2/actions/chord-from-key.js +45 -0
- package/dist/tui-v2/actions/chord-from-key.js.map +1 -0
- package/dist/tui-v2/actions/keymap.d.ts +24 -0
- package/dist/tui-v2/actions/keymap.js +134 -0
- package/dist/tui-v2/actions/keymap.js.map +1 -0
- package/dist/tui-v2/app/App.d.ts +13 -0
- package/dist/tui-v2/app/App.js +332 -0
- package/dist/tui-v2/app/App.js.map +1 -0
- package/dist/tui-v2/app/command-handlers.d.ts +8 -0
- package/dist/tui-v2/app/command-handlers.js +66 -0
- package/dist/tui-v2/app/command-handlers.js.map +1 -0
- package/dist/tui-v2/app/commands/config-commands.d.ts +14 -0
- package/dist/tui-v2/app/commands/config-commands.js +161 -0
- package/dist/tui-v2/app/commands/config-commands.js.map +1 -0
- package/dist/tui-v2/app/commands/key-commands.d.ts +9 -0
- package/dist/tui-v2/app/commands/key-commands.js +263 -0
- package/dist/tui-v2/app/commands/key-commands.js.map +1 -0
- package/dist/tui-v2/app/commands/picker-commands.d.ts +24 -0
- package/dist/tui-v2/app/commands/picker-commands.js +423 -0
- package/dist/tui-v2/app/commands/picker-commands.js.map +1 -0
- package/dist/tui-v2/app/commands/session-commands.d.ts +19 -0
- package/dist/tui-v2/app/commands/session-commands.js +162 -0
- package/dist/tui-v2/app/commands/session-commands.js.map +1 -0
- package/dist/tui-v2/app/plan-lifecycle.d.ts +22 -0
- package/dist/tui-v2/app/plan-lifecycle.js +95 -0
- package/dist/tui-v2/app/plan-lifecycle.js.map +1 -0
- package/dist/tui-v2/app/providers.d.ts +17 -0
- package/dist/tui-v2/app/providers.js +32 -0
- package/dist/tui-v2/app/providers.js.map +1 -0
- package/dist/tui-v2/bootstrap/capabilities.d.ts +35 -0
- package/dist/tui-v2/bootstrap/capabilities.js +93 -0
- package/dist/tui-v2/bootstrap/capabilities.js.map +1 -0
- package/dist/tui-v2/bootstrap/composition-root.d.ts +85 -0
- package/dist/tui-v2/bootstrap/composition-root.js +131 -0
- package/dist/tui-v2/bootstrap/composition-root.js.map +1 -0
- package/dist/tui-v2/bootstrap/disable-native-selection.d.ts +11 -0
- package/dist/tui-v2/bootstrap/disable-native-selection.js +13 -0
- package/dist/tui-v2/bootstrap/disable-native-selection.js.map +1 -0
- package/dist/tui-v2/bootstrap/lifecycle.d.ts +62 -0
- package/dist/tui-v2/bootstrap/lifecycle.js +123 -0
- package/dist/tui-v2/bootstrap/lifecycle.js.map +1 -0
- package/dist/tui-v2/bootstrap/osc52-clipboard.d.ts +28 -0
- package/dist/tui-v2/bootstrap/osc52-clipboard.js +44 -0
- package/dist/tui-v2/bootstrap/osc52-clipboard.js.map +1 -0
- package/dist/tui-v2/bootstrap/overlay-ports.d.ts +12 -0
- package/dist/{tui/confirm.js → tui-v2/bootstrap/overlay-ports.js} +17 -25
- package/dist/tui-v2/bootstrap/overlay-ports.js.map +1 -0
- package/dist/tui-v2/bootstrap/pager-export.d.ts +24 -0
- package/dist/tui-v2/bootstrap/pager-export.js +87 -0
- package/dist/tui-v2/bootstrap/pager-export.js.map +1 -0
- package/dist/tui-v2/bootstrap/start-tui-v2.d.ts +15 -0
- package/dist/tui-v2/bootstrap/start-tui-v2.js +95 -0
- package/dist/tui-v2/bootstrap/start-tui-v2.js.map +1 -0
- package/dist/tui-v2/bootstrap/ui-selection.d.ts +30 -0
- package/dist/tui-v2/bootstrap/ui-selection.js +59 -0
- package/dist/tui-v2/bootstrap/ui-selection.js.map +1 -0
- package/dist/tui-v2/components/completion/completion-menu.d.ts +23 -0
- package/dist/tui-v2/components/completion/completion-menu.js +84 -0
- package/dist/tui-v2/components/completion/completion-menu.js.map +1 -0
- package/dist/tui-v2/components/jobs/jobs-panel.d.ts +16 -0
- package/dist/tui-v2/components/jobs/jobs-panel.js +92 -0
- package/dist/tui-v2/components/jobs/jobs-panel.js.map +1 -0
- package/dist/tui-v2/components/modal/confirm-modal.d.ts +20 -0
- package/dist/tui-v2/components/modal/confirm-modal.js +110 -0
- package/dist/tui-v2/components/modal/confirm-modal.js.map +1 -0
- package/dist/tui-v2/components/modal/prompt-actions-modal.d.ts +15 -0
- package/dist/tui-v2/components/modal/prompt-actions-modal.js +80 -0
- package/dist/tui-v2/components/modal/prompt-actions-modal.js.map +1 -0
- package/dist/tui-v2/components/modal/secret-modal.d.ts +18 -0
- package/dist/tui-v2/components/modal/secret-modal.js +50 -0
- package/dist/tui-v2/components/modal/secret-modal.js.map +1 -0
- package/dist/tui-v2/components/overlay/overlay-host.d.ts +26 -0
- package/dist/tui-v2/components/overlay/overlay-host.js +43 -0
- package/dist/tui-v2/components/overlay/overlay-host.js.map +1 -0
- package/dist/tui-v2/components/pager/pager.d.ts +20 -0
- package/dist/tui-v2/components/pager/pager.js +396 -0
- package/dist/tui-v2/components/pager/pager.js.map +1 -0
- package/dist/tui-v2/components/picker/picker.d.ts +17 -0
- package/dist/tui-v2/components/picker/picker.js +222 -0
- package/dist/tui-v2/components/picker/picker.js.map +1 -0
- package/dist/tui-v2/components/plan/plan-view.d.ts +20 -0
- package/dist/tui-v2/components/plan/plan-view.js +120 -0
- package/dist/tui-v2/components/plan/plan-view.js.map +1 -0
- package/dist/tui-v2/components/queue/queue-panel.d.ts +23 -0
- package/dist/tui-v2/components/queue/queue-panel.js +58 -0
- package/dist/tui-v2/components/queue/queue-panel.js.map +1 -0
- package/dist/tui-v2/components/status/status-line.d.ts +27 -0
- package/dist/tui-v2/components/status/status-line.js +167 -0
- package/dist/tui-v2/components/status/status-line.js.map +1 -0
- package/dist/tui-v2/components/toast/toast-host.d.ts +15 -0
- package/dist/tui-v2/components/toast/toast-host.js +48 -0
- package/dist/tui-v2/components/toast/toast-host.js.map +1 -0
- package/dist/tui-v2/components/transcript/assistant-message.d.ts +20 -0
- package/dist/tui-v2/components/transcript/assistant-message.js +41 -0
- package/dist/tui-v2/components/transcript/assistant-message.js.map +1 -0
- package/dist/tui-v2/components/transcript/compacted-row.d.ts +19 -0
- package/dist/tui-v2/components/transcript/compacted-row.js +69 -0
- package/dist/tui-v2/components/transcript/compacted-row.js.map +1 -0
- package/dist/tui-v2/components/transcript/intro-card.d.ts +19 -0
- package/dist/tui-v2/components/transcript/intro-card.js +50 -0
- package/dist/tui-v2/components/transcript/intro-card.js.map +1 -0
- package/dist/tui-v2/components/transcript/linkable-text.d.ts +21 -0
- package/dist/tui-v2/components/transcript/linkable-text.js +46 -0
- package/dist/tui-v2/components/transcript/linkable-text.js.map +1 -0
- package/dist/tui-v2/components/transcript/notice-row.d.ts +14 -0
- package/dist/tui-v2/components/transcript/notice-row.js +40 -0
- package/dist/tui-v2/components/transcript/notice-row.js.map +1 -0
- package/dist/tui-v2/components/transcript/search-bar.d.ts +19 -0
- package/dist/tui-v2/components/transcript/search-bar.js +7 -0
- package/dist/tui-v2/components/transcript/search-bar.js.map +1 -0
- package/dist/tui-v2/components/transcript/thinking-block.d.ts +22 -0
- package/dist/tui-v2/components/transcript/thinking-block.js +27 -0
- package/dist/tui-v2/components/transcript/thinking-block.js.map +1 -0
- package/dist/tui-v2/components/transcript/tool-card.d.ts +26 -0
- package/dist/tui-v2/components/transcript/tool-card.js +205 -0
- package/dist/tui-v2/components/transcript/tool-card.js.map +1 -0
- package/dist/tui-v2/components/transcript/transcript-row.d.ts +25 -0
- package/dist/tui-v2/components/transcript/transcript-row.js +37 -0
- package/dist/tui-v2/components/transcript/transcript-row.js.map +1 -0
- package/dist/tui-v2/components/transcript/transcript-scroll-port.d.ts +52 -0
- package/dist/tui-v2/components/transcript/transcript-scroll-port.js +86 -0
- package/dist/tui-v2/components/transcript/transcript-scroll-port.js.map +1 -0
- package/dist/tui-v2/components/transcript/transcript-view.d.ts +24 -0
- package/dist/tui-v2/components/transcript/transcript-view.js +403 -0
- package/dist/tui-v2/components/transcript/transcript-view.js.map +1 -0
- package/dist/tui-v2/components/transcript/use-click-without-drag.d.ts +13 -0
- package/dist/tui-v2/components/transcript/use-click-without-drag.js +35 -0
- package/dist/tui-v2/components/transcript/use-click-without-drag.js.map +1 -0
- package/dist/tui-v2/components/transcript/use-native-selection-copy.d.ts +10 -0
- package/dist/tui-v2/components/transcript/use-native-selection-copy.js +37 -0
- package/dist/tui-v2/components/transcript/use-native-selection-copy.js.map +1 -0
- package/dist/tui-v2/components/transcript/use-transcript-selection.d.ts +23 -0
- package/dist/tui-v2/components/transcript/use-transcript-selection.js +136 -0
- package/dist/tui-v2/components/transcript/use-transcript-selection.js.map +1 -0
- package/dist/tui-v2/components/transcript/user-message.d.ts +10 -0
- package/dist/tui-v2/components/transcript/user-message.js +23 -0
- package/dist/tui-v2/components/transcript/user-message.js.map +1 -0
- package/dist/tui-v2/composer/arrow-intent.d.ts +23 -0
- package/dist/tui-v2/composer/arrow-intent.js +35 -0
- package/dist/tui-v2/composer/arrow-intent.js.map +1 -0
- package/dist/tui-v2/composer/completion.d.ts +37 -0
- package/dist/tui-v2/composer/completion.js +67 -0
- package/dist/tui-v2/composer/completion.js.map +1 -0
- package/dist/tui-v2/composer/composer-editor.d.ts +33 -0
- package/dist/tui-v2/composer/composer-editor.js +620 -0
- package/dist/tui-v2/composer/composer-editor.js.map +1 -0
- package/dist/tui-v2/composer/composer-height.d.ts +21 -0
- package/dist/tui-v2/composer/composer-height.js +29 -0
- package/dist/tui-v2/composer/composer-height.js.map +1 -0
- package/dist/tui-v2/composer/composer-meta.d.ts +9 -0
- package/dist/tui-v2/composer/composer-meta.js +38 -0
- package/dist/tui-v2/composer/composer-meta.js.map +1 -0
- package/dist/tui-v2/composer/history-nav.d.ts +15 -0
- package/dist/tui-v2/composer/history-nav.js +16 -0
- package/dist/tui-v2/composer/history-nav.js.map +1 -0
- package/dist/tui-v2/composer/newline-hint.d.ts +13 -0
- package/dist/tui-v2/composer/newline-hint.js +13 -0
- package/dist/tui-v2/composer/newline-hint.js.map +1 -0
- package/dist/tui-v2/composer/paste-placeholder.d.ts +35 -0
- package/dist/tui-v2/composer/paste-placeholder.js +55 -0
- package/dist/tui-v2/composer/paste-placeholder.js.map +1 -0
- package/dist/tui-v2/composer/prompt-history.d.ts +22 -0
- package/dist/tui-v2/composer/prompt-history.js +51 -0
- package/dist/tui-v2/composer/prompt-history.js.map +1 -0
- package/dist/tui-v2/composer/secret-buffer.d.ts +20 -0
- package/dist/tui-v2/composer/secret-buffer.js +42 -0
- package/dist/tui-v2/composer/secret-buffer.js.map +1 -0
- package/dist/tui-v2/composer/textarea-keybindings.d.ts +22 -0
- package/dist/tui-v2/composer/textarea-keybindings.js +27 -0
- package/dist/tui-v2/composer/textarea-keybindings.js.map +1 -0
- package/dist/tui-v2/controllers/focus-controller.d.ts +30 -0
- package/dist/tui-v2/controllers/focus-controller.js +80 -0
- package/dist/tui-v2/controllers/focus-controller.js.map +1 -0
- package/dist/tui-v2/controllers/overlay-controller.d.ts +99 -0
- package/dist/tui-v2/controllers/overlay-controller.js +195 -0
- package/dist/tui-v2/controllers/overlay-controller.js.map +1 -0
- package/dist/tui-v2/controllers/selection-controller.d.ts +76 -0
- package/dist/tui-v2/controllers/selection-controller.js +238 -0
- package/dist/tui-v2/controllers/selection-controller.js.map +1 -0
- package/dist/tui-v2/controllers/toast-controller.d.ts +35 -0
- package/dist/tui-v2/controllers/toast-controller.js +88 -0
- package/dist/tui-v2/controllers/toast-controller.js.map +1 -0
- package/dist/tui-v2/layout/compute-layout.d.ts +59 -0
- package/dist/tui-v2/layout/compute-layout.js +170 -0
- package/dist/tui-v2/layout/compute-layout.js.map +1 -0
- package/dist/tui-v2/rendering/ansi-to-styled.d.ts +17 -0
- package/dist/tui-v2/rendering/ansi-to-styled.js +217 -0
- package/dist/tui-v2/rendering/ansi-to-styled.js.map +1 -0
- package/dist/tui-v2/rendering/batch-sections.d.ts +57 -0
- package/dist/tui-v2/rendering/batch-sections.js +177 -0
- package/dist/tui-v2/rendering/batch-sections.js.map +1 -0
- package/dist/tui-v2/rendering/link-detector.d.ts +16 -0
- package/dist/tui-v2/rendering/link-detector.js +43 -0
- package/dist/tui-v2/rendering/link-detector.js.map +1 -0
- package/dist/tui-v2/rendering/open-tool-output.d.ts +28 -0
- package/dist/tui-v2/rendering/open-tool-output.js +138 -0
- package/dist/tui-v2/rendering/open-tool-output.js.map +1 -0
- package/dist/tui-v2/rendering/picker-filter.d.ts +22 -0
- package/dist/tui-v2/rendering/picker-filter.js +68 -0
- package/dist/tui-v2/rendering/picker-filter.js.map +1 -0
- package/dist/tui-v2/rendering/plan-view.d.ts +32 -0
- package/dist/tui-v2/rendering/plan-view.js +196 -0
- package/dist/tui-v2/rendering/plan-view.js.map +1 -0
- package/dist/tui-v2/rendering/render-markdown-lines.d.ts +29 -0
- package/dist/tui-v2/rendering/render-markdown-lines.js +46 -0
- package/dist/tui-v2/rendering/render-markdown-lines.js.map +1 -0
- package/dist/tui-v2/rendering/sanitize-display.d.ts +13 -0
- package/dist/tui-v2/rendering/sanitize-display.js +24 -0
- package/dist/tui-v2/rendering/sanitize-display.js.map +1 -0
- package/dist/tui-v2/rendering/strip-tool-surfaces.d.ts +10 -0
- package/dist/tui-v2/rendering/strip-tool-surfaces.js +32 -0
- package/dist/tui-v2/rendering/strip-tool-surfaces.js.map +1 -0
- package/dist/tui-v2/rendering/theme.d.ts +67 -0
- package/dist/tui-v2/rendering/theme.js +74 -0
- package/dist/tui-v2/rendering/theme.js.map +1 -0
- package/dist/tui-v2/rendering/tool-presenter.d.ts +39 -0
- package/dist/tui-v2/rendering/tool-presenter.js +209 -0
- package/dist/tui-v2/rendering/tool-presenter.js.map +1 -0
- package/dist/tui-v2/rendering/transcript-export.d.ts +11 -0
- package/dist/tui-v2/rendering/transcript-export.js +18 -0
- package/dist/tui-v2/rendering/transcript-export.js.map +1 -0
- package/dist/tui-v2/rendering/transcript-semantic.d.ts +17 -0
- package/dist/tui-v2/rendering/transcript-semantic.js +67 -0
- package/dist/tui-v2/rendering/transcript-semantic.js.map +1 -0
- package/dist/tui-v2/state/pager-search.d.ts +22 -0
- package/dist/tui-v2/state/pager-search.js +78 -0
- package/dist/tui-v2/state/pager-search.js.map +1 -0
- package/dist/tui-v2/state/semantic-document.d.ts +34 -0
- package/dist/tui-v2/state/semantic-document.js +201 -0
- package/dist/tui-v2/state/semantic-document.js.map +1 -0
- package/dist/tui-v2/state/transcript-compaction.d.ts +24 -0
- package/dist/tui-v2/state/transcript-compaction.js +79 -0
- package/dist/tui-v2/state/transcript-compaction.js.map +1 -0
- package/dist/tui-v2/state/transcript-hydrate.d.ts +27 -0
- package/dist/tui-v2/state/transcript-hydrate.js +276 -0
- package/dist/tui-v2/state/transcript-hydrate.js.map +1 -0
- package/dist/tui-v2/state/transcript-reducer.d.ts +10 -0
- package/dist/tui-v2/state/transcript-reducer.js +339 -0
- package/dist/tui-v2/state/transcript-reducer.js.map +1 -0
- package/dist/tui-v2/state/transcript-search.d.ts +17 -0
- package/dist/tui-v2/state/transcript-search.js +38 -0
- package/dist/tui-v2/state/transcript-search.js.map +1 -0
- package/dist/tui-v2/state/transcript-store.d.ts +32 -0
- package/dist/tui-v2/state/transcript-store.js +66 -0
- package/dist/tui-v2/state/transcript-store.js.map +1 -0
- package/dist/tui-v2/state/transcript-struct.d.ts +15 -0
- package/dist/tui-v2/state/transcript-struct.js +133 -0
- package/dist/tui-v2/state/transcript-struct.js.map +1 -0
- package/dist/tui-v2/state/transcript-types.d.ts +76 -0
- package/dist/tui-v2/state/transcript-types.js +65 -0
- package/dist/tui-v2/state/transcript-types.js.map +1 -0
- package/dist/tui-v2/state/use-overlay.d.ts +3 -0
- package/dist/tui-v2/state/use-overlay.js +6 -0
- package/dist/tui-v2/state/use-overlay.js.map +1 -0
- package/dist/tui-v2/state/use-plan.d.ts +4 -0
- package/dist/tui-v2/state/use-plan.js +6 -0
- package/dist/tui-v2/state/use-plan.js.map +1 -0
- package/dist/tui-v2/state/use-session-state.d.ts +2 -0
- package/dist/tui-v2/state/use-session-state.js +7 -0
- package/dist/tui-v2/state/use-session-state.js.map +1 -0
- package/dist/tui-v2/state/use-toast.d.ts +3 -0
- package/dist/tui-v2/state/use-toast.js +15 -0
- package/dist/tui-v2/state/use-toast.js.map +1 -0
- package/dist/tui-v2/state/use-transcript-store.d.ts +10 -0
- package/dist/tui-v2/state/use-transcript-store.js +12 -0
- package/dist/tui-v2/state/use-transcript-store.js.map +1 -0
- package/dist/ui/intro-card.js +3 -3
- package/dist/ui/intro-card.js.map +1 -1
- package/dist/ui/intro-header.d.ts +23 -0
- package/dist/ui/intro-header.js +232 -0
- package/dist/ui/intro-header.js.map +1 -0
- package/dist/ui/markdown.js +0 -1
- package/dist/ui/markdown.js.map +1 -1
- package/dist/ui/mentions.d.ts +9 -2
- package/dist/ui/mentions.js +102 -4
- package/dist/ui/mentions.js.map +1 -1
- package/dist/ui/output-pane.js +1 -1
- package/dist/ui/output-pane.js.map +1 -1
- package/dist/ui/plan-pane.d.ts +8 -0
- package/dist/ui/plan-pane.js +38 -0
- package/dist/ui/plan-pane.js.map +1 -1
- package/dist/ui/wordmark.d.ts +5 -0
- package/dist/ui/wordmark.js +6 -1
- package/dist/ui/wordmark.js.map +1 -1
- package/package.json +25 -12
- package/dist/tui/App.d.ts +0 -12
- package/dist/tui/App.js +0 -2174
- package/dist/tui/App.js.map +0 -1
- package/dist/tui/components/ConfirmModal.d.ts +0 -9
- package/dist/tui/components/ConfirmModal.js +0 -55
- package/dist/tui/components/ConfirmModal.js.map +0 -1
- package/dist/tui/components/JobsPanel.d.ts +0 -11
- package/dist/tui/components/JobsPanel.js +0 -55
- package/dist/tui/components/JobsPanel.js.map +0 -1
- package/dist/tui/components/Pager.d.ts +0 -14
- package/dist/tui/components/Pager.js +0 -56
- package/dist/tui/components/Pager.js.map +0 -1
- package/dist/tui/components/PickerPanel.d.ts +0 -15
- package/dist/tui/components/PickerPanel.js +0 -126
- package/dist/tui/components/PickerPanel.js.map +0 -1
- package/dist/tui/components/PlanSidebar.d.ts +0 -6
- package/dist/tui/components/PlanSidebar.js +0 -99
- package/dist/tui/components/PlanSidebar.js.map +0 -1
- package/dist/tui/components/SecretInputPanel.d.ts +0 -7
- package/dist/tui/components/SecretInputPanel.js +0 -38
- package/dist/tui/components/SecretInputPanel.js.map +0 -1
- package/dist/tui/confirm.d.ts +0 -16
- package/dist/tui/confirm.js.map +0 -1
- package/dist/tui/default.d.ts +0 -6
- package/dist/tui/default.js +0 -11
- package/dist/tui/default.js.map +0 -1
- package/dist/tui/hooks/useAgentRunner.d.ts +0 -73
- package/dist/tui/hooks/useAgentRunner.js +0 -277
- package/dist/tui/hooks/useAgentRunner.js.map +0 -1
- package/dist/tui/hooks/useJobs.d.ts +0 -7
- package/dist/tui/hooks/useJobs.js +0 -39
- package/dist/tui/hooks/useJobs.js.map +0 -1
- package/dist/tui/hooks/useSpinner.d.ts +0 -5
- package/dist/tui/hooks/useSpinner.js +0 -21
- package/dist/tui/hooks/useSpinner.js.map +0 -1
- package/dist/tui/hooks/useTerminalSize.d.ts +0 -9
- package/dist/tui/hooks/useTerminalSize.js +0 -26
- package/dist/tui/hooks/useTerminalSize.js.map +0 -1
- package/dist/tui/index.d.ts +0 -10
- package/dist/tui/index.js +0 -37
- package/dist/tui/index.js.map +0 -1
- package/dist/tui/key-intent.d.ts +0 -36
- package/dist/tui/key-intent.js +0 -54
- package/dist/tui/key-intent.js.map +0 -1
- package/dist/tui/mouse.d.ts +0 -8
- package/dist/tui/mouse.js +0 -34
- package/dist/tui/mouse.js.map +0 -1
- package/dist/tui/plan-sidebar-lines.d.ts +0 -2
- package/dist/tui/plan-sidebar-lines.js +0 -140
- package/dist/tui/plan-sidebar-lines.js.map +0 -1
- package/dist/tui/render-lines.d.ts +0 -22
- package/dist/tui/render-lines.js +0 -611
- package/dist/tui/render-lines.js.map +0 -1
package/dist/agent/runner.js
CHANGED
|
@@ -11,7 +11,7 @@ import { classifyToolCall, isPentestToolCall, scopeHint, scopeTargetForToolCall,
|
|
|
11
11
|
import { availableToolNames, normalizeToolCall, runToolCall, BATCH_SAFE_TOOLS, } from "../tools/registry.js";
|
|
12
12
|
import { looksInteractiveStdin } from "../tools/shell.js";
|
|
13
13
|
import { formatViewportHint, registerViewport } from "../ui/output-pane.js";
|
|
14
|
-
import { compactMessagesWithSummary, estimateTokens, estimateMessagesTokens, COMPACTION_MEMORY_PREFIX, } from "./context-manager.js";
|
|
14
|
+
import { compactMessagesWithSummary, estimateTokens, estimateMessagesTokens, AUTO_COMPACT_TOKEN_BUDGET, COMPACTION_MEMORY_PREFIX, } from "./context-manager.js";
|
|
15
15
|
import { auditLog } from "../store/logs.js";
|
|
16
16
|
import { loadProjectContext } from "../store/project.js";
|
|
17
17
|
import { loadScope, isScopeActive, targetInScope } from "../store/scope.js";
|
|
@@ -24,7 +24,7 @@ import { analyzeTask } from "./task-analyzer.js";
|
|
|
24
24
|
import { LoopGuard } from "./loop-guard.js";
|
|
25
25
|
import { loadPlan } from "../store/plan.js";
|
|
26
26
|
import { pathInsideSandbox, fsWrite } from "../tools/fs.js";
|
|
27
|
-
import { stripSentinelTokens, parseToolCall, recognizeBareToolJson, looksLikeTruncatedToolCall, salvageTruncatedWrite, countToolFences, parseAllToolCalls, groupToolCallsForExecution, buildTurnHistory, collapseRepeatedText, textBeforeToolCall, formatToolArgs, looksLikePentestTask, looksLikeBuildTask, looksLikeInformationalQuery, looksLikeActionNarration, looksLikePlanNarration, requiresFreshWebSearch, freshnessGuardMessage, buildWorkflowDirective, pentestWorkflowDirective, shouldDimToolChatter, looksLikePromptLeak, } from "./tool-call-parser.js";
|
|
27
|
+
import { stripSentinelTokens, parseToolCall, recognizeBareToolJson, looksLikeTruncatedToolCall, salvageTruncatedWrite, countToolFences, parseAllToolCalls, groupToolCallsForExecution, buildTurnHistory, collapseRepeatedText, textBeforeToolCall, formatToolArgs, looksLikePentestTask, looksLikeBuildTask, looksLikeInformationalQuery, looksLikeIdleOrSocialPrompt, looksLikeActionNarration, looksLikeWebActionNarration, looksLikePlanNarration, requiresFreshWebSearch, freshnessGuardMessage, buildWorkflowDirective, pentestWorkflowDirective, pentestNoLocalServerDirective, shouldDimToolChatter, looksLikePromptLeak, } from "./tool-call-parser.js";
|
|
28
28
|
import { createSessionPolicy, isPreApprovalAllowedTool, isPlanApprovedByStatus, planHasOpenWork, isAbortError, shouldEnableImageOcr, } from "./session-policy.js";
|
|
29
29
|
import { saveToolOutput, summarizeOutput, formatToolContext, } from "./tool-output-formatting.js";
|
|
30
30
|
import { planContextMessage, handlePlanTool, } from "./plan-tool.js";
|
|
@@ -128,7 +128,17 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
128
128
|
stop: () => { },
|
|
129
129
|
};
|
|
130
130
|
const writeStatus = (text, rendered = chalk.dim(text)) => {
|
|
131
|
-
|
|
131
|
+
// Footer activity is single-line; strip classic stdout newlines/indents.
|
|
132
|
+
// Never surface /output path hints as activity (garbles the status bar).
|
|
133
|
+
let cleaned = text.replace(/\s+/g, " ").trim();
|
|
134
|
+
if (/\/output\b|open full output|Ctrl\+O or|\.clai\/outputs/i.test(cleaned)) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (cleaned.length > 64) {
|
|
138
|
+
const short = cleaned.match(/^[\w./-]+/);
|
|
139
|
+
cleaned = short ? short[0] : cleaned.slice(0, 61) + "…";
|
|
140
|
+
}
|
|
141
|
+
emit({ type: "status", text: cleaned || "working" });
|
|
132
142
|
if (writesDirectly)
|
|
133
143
|
process.stdout.write(rendered);
|
|
134
144
|
};
|
|
@@ -154,9 +164,14 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
154
164
|
if (writesDirectly)
|
|
155
165
|
process.stdout.write(`${renderThinkingSummary(content)}\n`);
|
|
156
166
|
};
|
|
157
|
-
const writeToolOutput = (id, chunk, rendered) => {
|
|
158
|
-
emit({
|
|
159
|
-
|
|
167
|
+
const writeToolOutput = (id, chunk, rendered, options) => {
|
|
168
|
+
emit({
|
|
169
|
+
type: "tool-output",
|
|
170
|
+
id,
|
|
171
|
+
chunk,
|
|
172
|
+
...(options?.replace ? { replace: true } : {}),
|
|
173
|
+
});
|
|
174
|
+
if (writesDirectly && rendered)
|
|
160
175
|
process.stdout.write(rendered);
|
|
161
176
|
};
|
|
162
177
|
const writeToolCall = (id, call, rendered) => {
|
|
@@ -257,8 +272,13 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
257
272
|
// brand-new plan (the exact failure where "what do u know till now"
|
|
258
273
|
// triggered explore→plan and created an unrelated "Enhance clai" plan).
|
|
259
274
|
const informationalQuery = looksLikeInformationalQuery(prompt);
|
|
275
|
+
// Greetings / thanks / short acks must never force tools, plans, or
|
|
276
|
+
// freshness retries — a false "act don't narrate" path burned tokens on
|
|
277
|
+
// web.search recovery loops after a simple "hi".
|
|
278
|
+
const idleOrSocialPrompt = looksLikeIdleOrSocialPrompt(prompt);
|
|
260
279
|
const freshWebSearchRequired = !buildLikeTurn &&
|
|
261
280
|
!pentestLikeTurn &&
|
|
281
|
+
!idleOrSocialPrompt &&
|
|
262
282
|
toolNames.includes("web.search") &&
|
|
263
283
|
requiresFreshWebSearch(prompt);
|
|
264
284
|
let provider = options.provider ?? config.defaultProvider;
|
|
@@ -307,7 +327,10 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
307
327
|
// what's already there, create a comprehensive multi-task plan, then
|
|
308
328
|
// implement task by task until the goal is met. This mirrors how a careful
|
|
309
329
|
// coding agent (Claude Code) operates.
|
|
310
|
-
if (buildLikeTurn &&
|
|
330
|
+
if (buildLikeTurn &&
|
|
331
|
+
!activePlan &&
|
|
332
|
+
!informationalQuery &&
|
|
333
|
+
!idleOrSocialPrompt) {
|
|
311
334
|
systemSections.push(buildWorkflowDirective());
|
|
312
335
|
}
|
|
313
336
|
// Pentest / security engagements need a different shape than a coding
|
|
@@ -316,9 +339,22 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
316
339
|
// directive is only injected before a plan exists; once a plan is in
|
|
317
340
|
// place (or being refined), the ACTIVE PLAN block already carries the
|
|
318
341
|
// current task state and recon-vs-active-tool guidance.
|
|
319
|
-
if (pentestLikeTurn &&
|
|
342
|
+
if (pentestLikeTurn &&
|
|
343
|
+
!activePlan &&
|
|
344
|
+
!informationalQuery &&
|
|
345
|
+
!idleOrSocialPrompt) {
|
|
320
346
|
systemSections.push(pentestWorkflowDirective());
|
|
321
347
|
}
|
|
348
|
+
// Any remote/security engagement (even with an existing or completed plan)
|
|
349
|
+
// must never fall into the coding "start localhost dev server" habit.
|
|
350
|
+
const pentestSession = pentestLikeTurn ||
|
|
351
|
+
activePlan?.kind === "pentest" ||
|
|
352
|
+
(activePlan?.kind !== "coding" &&
|
|
353
|
+
Boolean(activePlan?.goal &&
|
|
354
|
+
/pentest|vulnerab|recon|security assess|attack surface|red team/i.test(activePlan.goal)));
|
|
355
|
+
if (pentestSession && !idleOrSocialPrompt) {
|
|
356
|
+
systemSections.push(pentestNoLocalServerDirective());
|
|
357
|
+
}
|
|
322
358
|
const renderedSystemPrompt = systemSections.join("\n\n");
|
|
323
359
|
// Reserve most of a constrained model's input budget for the user message,
|
|
324
360
|
// recent conversation, tool results, and provider framing. Dynamic project
|
|
@@ -711,6 +747,9 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
711
747
|
}
|
|
712
748
|
parentSignal.throwIfAborted();
|
|
713
749
|
options.onToolStart?.(call);
|
|
750
|
+
// Card was "queued" since writeToolCall; flip to running only when work starts.
|
|
751
|
+
emit({ type: "tool-start", id: toolEventId });
|
|
752
|
+
writeStatus(call.name, chalk.dim(` → ${call.name}\n`));
|
|
714
753
|
const interactiveCommand = (call.name === "shell.exec" &&
|
|
715
754
|
typeof call.args.command === "string" &&
|
|
716
755
|
looksInteractiveStdin(call.args.command)) ||
|
|
@@ -724,38 +763,20 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
724
763
|
parentSignal.addEventListener("abort", onParentAbort);
|
|
725
764
|
let result;
|
|
726
765
|
let liveBytes = 0;
|
|
727
|
-
const liveCap = 16_000;
|
|
728
|
-
let liveTruncatedNotified = false;
|
|
729
|
-
let lastProgressAt = 0;
|
|
730
766
|
const shouldDimLive = !interactiveCommand;
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
};
|
|
767
|
+
// Stream every live byte — never drop mid-run. After the tool finishes we
|
|
768
|
+
// still replace the spool with the authoritative full `result.output`.
|
|
734
769
|
const printLive = (chunk) => {
|
|
735
770
|
if (call.name === "fs.read" ||
|
|
736
771
|
call.name === "fs.list" ||
|
|
737
772
|
call.name === "fs.search")
|
|
738
773
|
return;
|
|
739
|
-
if (
|
|
740
|
-
if (!liveTruncatedNotified) {
|
|
741
|
-
liveTruncatedNotified = true;
|
|
742
|
-
writeToolInfo("... live preview truncated, full output saved");
|
|
743
|
-
writeToolInfo("(tool still running — ESC or Ctrl+C to abort)");
|
|
744
|
-
lastProgressAt = Date.now();
|
|
745
|
-
}
|
|
746
|
-
const now = Date.now();
|
|
747
|
-
if (now - lastProgressAt > 5_000) {
|
|
748
|
-
lastProgressAt = now;
|
|
749
|
-
writeToolOutput(toolEventId, ".", chalk.dim("."));
|
|
750
|
-
}
|
|
774
|
+
if (!chunk)
|
|
751
775
|
return;
|
|
752
|
-
|
|
753
|
-
const
|
|
754
|
-
const slice = chunk.length > remaining ? chunk.slice(0, remaining) : chunk;
|
|
755
|
-
liveBytes += slice.length;
|
|
756
|
-
const indented = slice.replace(/\r/g, "").replace(/\n(?!$)/g, "\n ");
|
|
776
|
+
liveBytes += chunk.length;
|
|
777
|
+
const indented = chunk.replace(/\r/g, "").replace(/\n(?!$)/g, "\n ");
|
|
757
778
|
const body = indented.startsWith("\n") ? indented : ` ${indented}`;
|
|
758
|
-
writeToolOutput(toolEventId,
|
|
779
|
+
writeToolOutput(toolEventId, chunk, shouldDimLive ? chalk.dim(body) : body);
|
|
759
780
|
};
|
|
760
781
|
const jobId = randomUUID().slice(0, 8);
|
|
761
782
|
const backgroundJob = {
|
|
@@ -798,7 +819,7 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
798
819
|
confirmed: true,
|
|
799
820
|
userPrompt: prompt,
|
|
800
821
|
});
|
|
801
|
-
if (liveBytes > 0
|
|
822
|
+
if (liveBytes > 0) {
|
|
802
823
|
writeToolOutput(toolEventId, "\n", "\n");
|
|
803
824
|
}
|
|
804
825
|
jobManager.updateJobStatus(jobId, result.ok ? "exited" : "failed", result.exitCode);
|
|
@@ -839,11 +860,10 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
839
860
|
parentSignal.removeEventListener("abort", onParentAbort);
|
|
840
861
|
}
|
|
841
862
|
const output = result.output.trim();
|
|
842
|
-
|
|
863
|
+
// Always keep a full on-disk copy of tool output (any size) so the
|
|
864
|
+
// pager never depends on a truncated in-memory preview.
|
|
843
865
|
const savedOutputPath = result.outputPath ??
|
|
844
|
-
(output
|
|
845
|
-
? await saveToolOutput(call, output)
|
|
846
|
-
: undefined);
|
|
866
|
+
(output ? await saveToolOutput(call, output) : undefined);
|
|
847
867
|
const resultWithArtifact = {
|
|
848
868
|
...result,
|
|
849
869
|
outputPath: savedOutputPath,
|
|
@@ -878,20 +898,30 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
878
898
|
}
|
|
879
899
|
}
|
|
880
900
|
const statusIcon = result.ok ? chalk.green(" ✓") : chalk.red(" ✗");
|
|
881
|
-
|
|
901
|
+
// Classic stdout gets a short status glyph; the event stream / spool
|
|
902
|
+
// must NOT be polluted with "ok"/"failed" (the card already shows status).
|
|
903
|
+
if (writesDirectly) {
|
|
904
|
+
process.stdout.write(statusIcon + "\n");
|
|
905
|
+
}
|
|
882
906
|
if (output) {
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
if (savedOutputPath) {
|
|
889
|
-
writeToolInfo(`full output saved to ${savedOutputPath}`);
|
|
890
|
-
}
|
|
907
|
+
// Authoritative FULL body — replace any live stream so the pager
|
|
908
|
+
// never shows a truncated mid-run preview. Never cap for the UI.
|
|
909
|
+
const fullChunk = output.endsWith("\n") ? output : `${output}\n`;
|
|
910
|
+
if (!writesDirectly) {
|
|
911
|
+
writeToolOutput(toolEventId, fullChunk, "", { replace: true });
|
|
891
912
|
}
|
|
892
913
|
else {
|
|
914
|
+
// Classic stdout: short preview only; full text is on disk.
|
|
915
|
+
const displayMax = 6_000;
|
|
916
|
+
const displaySummary = summarizeOutput(output, displayMax);
|
|
917
|
+
const displayText = displaySummary.truncated
|
|
918
|
+
? `${displaySummary.text}${savedOutputPath ? chalk.dim(`\n ... full output saved to ${savedOutputPath}`) : ""}`
|
|
919
|
+
: displaySummary.text;
|
|
893
920
|
const renderedOutput = indentAndWrapText(displayText);
|
|
894
|
-
|
|
921
|
+
process.stdout.write(styleToolChatter(call, renderedOutput) + "\n");
|
|
922
|
+
if (savedOutputPath && displaySummary.truncated) {
|
|
923
|
+
process.stdout.write(chalk.dim(` full output saved to ${savedOutputPath}\n`));
|
|
924
|
+
}
|
|
895
925
|
}
|
|
896
926
|
}
|
|
897
927
|
if (output) {
|
|
@@ -901,9 +931,16 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
901
931
|
artifactPath: savedOutputPath,
|
|
902
932
|
summary: contextOutput,
|
|
903
933
|
});
|
|
904
|
-
|
|
934
|
+
// Classic REPL only: short path notice. Never push long
|
|
935
|
+
// "Ctrl+O or /output last … (path)" strings into the TUI status
|
|
936
|
+
// footer — they collide with activity and garble the chrome.
|
|
937
|
+
if (writesDirectly && savedOutputPath) {
|
|
938
|
+
const short = chalk.dim(` saved ${savedOutputPath}\n`);
|
|
939
|
+
process.stdout.write(short);
|
|
940
|
+
}
|
|
941
|
+
else if (writesDirectly) {
|
|
905
942
|
const viewportHint = `${formatViewportHint(viewport)}\n`;
|
|
906
|
-
|
|
943
|
+
process.stdout.write(viewportHint);
|
|
907
944
|
}
|
|
908
945
|
}
|
|
909
946
|
return { ok: result.ok, call, result, contextOutput };
|
|
@@ -917,7 +954,6 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
917
954
|
// what is done, and what remains. The estimate is chars/4; the budget is
|
|
918
955
|
// deliberately conservative so we compact a little early rather than hit a
|
|
919
956
|
// provider context-window error mid-task.
|
|
920
|
-
const AUTO_COMPACT_TOKEN_BUDGET = 150_000;
|
|
921
957
|
const AUTO_COMPACT_KEEP_RECENT = 6;
|
|
922
958
|
let lastCompactionMsgCount = 0;
|
|
923
959
|
const summarizeForCompaction = async (summaryPrompt) => {
|
|
@@ -953,6 +989,8 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
953
989
|
return;
|
|
954
990
|
messages.splice(0, messages.length, ...result.messages);
|
|
955
991
|
loopGuard.resetReadOnly();
|
|
992
|
+
// Token stats BEFORE plan re-injection so the reduction is accurate.
|
|
993
|
+
const compactedTokens = estimateMessagesTokens(messages);
|
|
956
994
|
// Re-inject the live plan so the model keeps full plan awareness even
|
|
957
995
|
// after older turns (which carried the plan context) were summarized.
|
|
958
996
|
const livePlan = await loadPlan(session.sessionId).catch(() => undefined);
|
|
@@ -963,13 +1001,11 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
963
1001
|
});
|
|
964
1002
|
}
|
|
965
1003
|
lastCompactionMsgCount = messages.length;
|
|
966
|
-
//
|
|
967
|
-
|
|
968
|
-
// final count (which is what the model actually receives) separately.
|
|
969
|
-
const compactedTokens = estimateMessagesTokens(messages);
|
|
1004
|
+
// Final count the model actually receives (may include re-injected plan).
|
|
1005
|
+
const afterTokens = estimateMessagesTokens(messages);
|
|
970
1006
|
await auditLog("agent.compact", {
|
|
971
1007
|
newLength: messages.length,
|
|
972
|
-
estimatedTokens:
|
|
1008
|
+
estimatedTokens: afterTokens,
|
|
973
1009
|
reason,
|
|
974
1010
|
});
|
|
975
1011
|
// Extract the inserted compaction memory so we can surface the
|
|
@@ -981,12 +1017,12 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
981
1017
|
const summaryText = insertedSummary.startsWith(`${COMPACTION_MEMORY_PREFIX}\n\n`)
|
|
982
1018
|
? insertedText(insertedSummary, `${COMPACTION_MEMORY_PREFIX}\n\n`)
|
|
983
1019
|
: insertedText(insertedSummary, COMPACTION_MEMORY_PREFIX);
|
|
984
|
-
|
|
985
|
-
writeCompacted(summaryText, beforeTokens,
|
|
1020
|
+
// Card shows pre/post of the summarization; plan re-injection is noted.
|
|
1021
|
+
writeCompacted(summaryText, beforeTokens, compactedTokens);
|
|
986
1022
|
const planNote = afterTokens > compactedTokens
|
|
987
1023
|
? ` (compacted to ~${compactedTokens.toLocaleString()}, +plan → ~${afterTokens.toLocaleString()})`
|
|
988
1024
|
: "";
|
|
989
|
-
writeNotice("info", `context auto-compacted to fit the window (~${beforeTokens.toLocaleString()} → ~${
|
|
1025
|
+
writeNotice("info", `context auto-compacted to fit the window (~${beforeTokens.toLocaleString()} → ~${compactedTokens.toLocaleString()} tokens${planNote})`, chalk.dim(` ℹ context auto-compacted (~${beforeTokens.toLocaleString()} → ~${compactedTokens.toLocaleString()} tokens${planNote})\n`));
|
|
990
1026
|
}
|
|
991
1027
|
catch (error) {
|
|
992
1028
|
if (error instanceof Error &&
|
|
@@ -1111,10 +1147,11 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
1111
1147
|
completion = await streamWithProvider({
|
|
1112
1148
|
provider,
|
|
1113
1149
|
model,
|
|
1114
|
-
//
|
|
1115
|
-
//
|
|
1116
|
-
//
|
|
1117
|
-
|
|
1150
|
+
// Stay on the user's selected provider/model. Cross-provider
|
|
1151
|
+
// hops (e.g. bynara → gemini on a 429) only happen when the
|
|
1152
|
+
// user has explicitly enabled `providerFallback` AND this is
|
|
1153
|
+
// the provider's default model. Never force fallback mid-turn.
|
|
1154
|
+
allowModelFallback: false,
|
|
1118
1155
|
messages,
|
|
1119
1156
|
// MiniMax M3 degenerates at the generic agent temperature. The
|
|
1120
1157
|
// HTTP layer also applies its `top_p` override for both the
|
|
@@ -1379,15 +1416,19 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
1379
1416
|
const lineCount = salvaged.content.split("\n").length;
|
|
1380
1417
|
writeNotice("info", `tool call was truncated — salvaged ${lineCount} lines and wrote to ${salvaged.path}`, chalk.cyan(` ℹ tool call was truncated — salvaged ${lineCount} lines to ${salvaged.path}\n`));
|
|
1381
1418
|
pushAssistantHistory(stripThinking(assistantText.visible).visible);
|
|
1419
|
+
const priorBytes = Buffer.byteLength(salvaged.content, "utf8");
|
|
1382
1420
|
messages.push({
|
|
1383
1421
|
role: "user",
|
|
1384
|
-
content: `Your fs.write tool call was cut off at the token limit, but the system salvaged the partial content and wrote ${lineCount} lines to ${salvaged.path}. ` +
|
|
1385
|
-
`The file
|
|
1386
|
-
`CONTINUE
|
|
1387
|
-
'```tool\n{"name":"fs.append","args":{"path":
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1422
|
+
content: `Your fs.write tool call was cut off at the token limit, but the system salvaged the partial content and wrote ${lineCount} lines (${priorBytes} bytes) to ${salvaged.path}. ` +
|
|
1423
|
+
`The file ends with: ${JSON.stringify(salvaged.lastLine)}\n\n` +
|
|
1424
|
+
`CONTINUE with ONE large fs.append of the remaining content (prefer hundreds of lines per call — do NOT use tiny ~100-line chunks):\n` +
|
|
1425
|
+
'```tool\n{"name":"fs.append","args":{"path":' +
|
|
1426
|
+
JSON.stringify(salvaged.path) +
|
|
1427
|
+
',"expectedPriorBytes":' +
|
|
1428
|
+
priorBytes +
|
|
1429
|
+
',"content":"...ONLY the remaining content not already on disk..."}}\n```\n' +
|
|
1430
|
+
`expectedPriorBytes must match the receipt so append cannot double-write. ` +
|
|
1431
|
+
`Do NOT re-read the full file; do NOT re-send content already saved.`,
|
|
1391
1432
|
});
|
|
1392
1433
|
continue;
|
|
1393
1434
|
}
|
|
@@ -1402,12 +1443,12 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
1402
1443
|
messages.push({
|
|
1403
1444
|
role: "user",
|
|
1404
1445
|
content: "Your previous tool call was cut off before it finished — the JSON was incomplete, so NOTHING ran. " +
|
|
1405
|
-
"
|
|
1406
|
-
"
|
|
1407
|
-
"
|
|
1408
|
-
"
|
|
1409
|
-
"
|
|
1410
|
-
"
|
|
1446
|
+
"Prefer ONE complete fs.write when it fits (~32k output tokens is a lot of file content if reasoning stays short). " +
|
|
1447
|
+
"If the file is too large for one call:\n" +
|
|
1448
|
+
"1. fs.write the first large section (as much as fits — hundreds+ of lines)\n" +
|
|
1449
|
+
"2. fs.append the rest with expectedPriorBytes from the write receipt\n" +
|
|
1450
|
+
"3. Repeat append only if still incomplete — large chunks, not ~100-line drips\n" +
|
|
1451
|
+
"Keep reasoning SHORT — emit the ```tool block early. Do NOT claim a file was written until a tool call succeeds.",
|
|
1411
1452
|
});
|
|
1412
1453
|
continue;
|
|
1413
1454
|
}
|
|
@@ -1442,7 +1483,7 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
1442
1483
|
content: `The system extracted and wrote ${lineCount} lines to ${salvaged.path} from your malformed tool call. ` +
|
|
1443
1484
|
`The file content ends at: "${salvaged.lastLine}"\n\n` +
|
|
1444
1485
|
`If the file is complete, proceed with the next step. ` +
|
|
1445
|
-
`If more content is needed, use fs.append
|
|
1486
|
+
`If more content is needed, use one large fs.append with expectedPriorBytes from the write receipt (not tiny chunks).`,
|
|
1446
1487
|
});
|
|
1447
1488
|
continue;
|
|
1448
1489
|
}
|
|
@@ -1460,10 +1501,8 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
1460
1501
|
content: "Your previous message contained a ```tool block, but its JSON was INVALID, so NOTHING ran. " +
|
|
1461
1502
|
"Common causes: unescaped newlines or quotes inside a string value, an extra or missing `}` / `]`, or content too large for the output window. " +
|
|
1462
1503
|
'Re-emit ONE valid ```tool block of the exact form {"name":"<tool>","args":{...}} with balanced braces. ' +
|
|
1463
|
-
"IMPORTANT:
|
|
1464
|
-
"
|
|
1465
|
-
"2. fs.append for each subsequent ~100-line section\n" +
|
|
1466
|
-
"Keep reasoning SHORT to maximize output space for the tool call JSON. " +
|
|
1504
|
+
"IMPORTANT: Prefer ONE complete fs.write when it fits. Keep reasoning SHORT. " +
|
|
1505
|
+
"Only if the output window cuts you off, continue with large fs.append chunks + expectedPriorBytes. " +
|
|
1467
1506
|
"Do NOT claim any file was written until a tool call actually succeeds.",
|
|
1468
1507
|
});
|
|
1469
1508
|
continue;
|
|
@@ -1481,6 +1520,7 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
1481
1520
|
// final answer ends the turn with nothing done and no real plan saved.
|
|
1482
1521
|
// Nudge it to emit a real tool call, with a concrete example.
|
|
1483
1522
|
const narratedAction = looksLikeActionNarration(cleaned);
|
|
1523
|
+
const narratedWebAction = looksLikeWebActionNarration(cleaned);
|
|
1484
1524
|
// `activePlan` is the snapshot loaded at the start of this turn.
|
|
1485
1525
|
// A final task.update mutates and persists a separate plan instance,
|
|
1486
1526
|
// so re-read it here before deciding whether a final response should
|
|
@@ -1493,15 +1533,24 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
1493
1533
|
const completedPlanDuringThisTurn = activePlan?.status !== "completed" &&
|
|
1494
1534
|
planStatusAtCompletion === "completed";
|
|
1495
1535
|
const planHasOpenWorkNow = planHasOpenWork(planStatusAtCompletion);
|
|
1536
|
+
// User-driven reasons to require tools this turn (build/pentest/
|
|
1537
|
+
// approved plan / freshness). Idle greetings never qualify.
|
|
1538
|
+
const userExpectsWork = freshWebSearchRequired ||
|
|
1539
|
+
(planHasOpenWorkNow && session.planApproved.value) ||
|
|
1540
|
+
(!informationalQuery &&
|
|
1541
|
+
!idleOrSocialPrompt &&
|
|
1542
|
+
(buildLikeTurn || pentestLikeTurn));
|
|
1496
1543
|
// History-inherited build/pentest intent only forces action when
|
|
1497
|
-
// THIS prompt is not itself a plain question
|
|
1498
|
-
//
|
|
1499
|
-
//
|
|
1544
|
+
// THIS prompt is not itself a plain question/idle turn. Narration
|
|
1545
|
+
// stalls ("I'll list the files") still force a tool when the user
|
|
1546
|
+
// is not asking an informational question — but capability menus
|
|
1547
|
+
// and greetings are filtered out by looksLikeActionNarration /
|
|
1548
|
+
// idleOrSocialPrompt so they cannot burn recovery turns.
|
|
1500
1549
|
const wantsAction = !completedPlanDuringThisTurn &&
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
(
|
|
1504
|
-
(
|
|
1550
|
+
!idleOrSocialPrompt &&
|
|
1551
|
+
(userExpectsWork ||
|
|
1552
|
+
(narratedAction && !informationalQuery) ||
|
|
1553
|
+
(narratedWebAction && !informationalQuery));
|
|
1505
1554
|
const planNarrated = (buildLikeTurn || pentestLikeTurn) &&
|
|
1506
1555
|
!activePlan &&
|
|
1507
1556
|
looksLikePlanNarration(cleaned);
|
|
@@ -1528,14 +1577,20 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
1528
1577
|
"Every turn MUST contain a ```tool block until the task is done.";
|
|
1529
1578
|
writeNotice("warn", "described a security/pentest action but emitted no tool call — nudging it to run one", chalk.yellow(" ⚠ described a security/pentest action but emitted no tool call — nudging it to run one\n"));
|
|
1530
1579
|
}
|
|
1531
|
-
else if (
|
|
1580
|
+
else if (freshWebSearchRequired || narratedWebAction) {
|
|
1581
|
+
// Web-specific recovery ONLY when the user asked for current
|
|
1582
|
+
// info or the model explicitly claimed a fetch/search step.
|
|
1583
|
+
// Previously every non-build stall used this path, so a "hi"
|
|
1584
|
+
// greeting that said "I'll start executing" was forced into
|
|
1585
|
+
// pointless web.search recovery loops.
|
|
1532
1586
|
nudge =
|
|
1533
1587
|
"You wrote that you would fetch/search/read something but emitted NO ```tool block, so NOTHING ran. Do NOT narrate the next browsing step — DO it. Emit exactly one valid ```tool block now. If you know the exact page, use:\n" +
|
|
1534
1588
|
'```tool\n{"name":"web.fetch","args":{"url":"https://example.com/page","responseMode":"readable"}}\n```\n' +
|
|
1535
1589
|
"If you do not know the exact page URL, use web.search first. After the tool output, answer from the fetched page content.";
|
|
1536
1590
|
writeNotice("warn", "described a web action but emitted no tool call — nudging it to run one", chalk.yellow(" ⚠ described a web action but emitted no tool call — nudging it to run one\n"));
|
|
1537
1591
|
}
|
|
1538
|
-
else if (
|
|
1592
|
+
else if (buildLikeTurn &&
|
|
1593
|
+
(planNarrated || productiveSteps > 0)) {
|
|
1539
1594
|
const kind = pentestLikeTurn ? "pentest" : "coding";
|
|
1540
1595
|
nudge =
|
|
1541
1596
|
"You wrote the plan as PROSE but did NOT call plan.create, so no plan was saved and the user cannot /implement it. Emit it as a real tool call NOW — exactly one ```tool block:\n" +
|
|
@@ -1543,17 +1598,17 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
1543
1598
|
"Do not describe the plan again in prose — just emit the plan.create tool block.";
|
|
1544
1599
|
writeNotice("warn", "plan was written as text, not created — nudging it to call plan.create", chalk.yellow(" ⚠ plan was written as text, not created — nudging it to call plan.create\n"));
|
|
1545
1600
|
}
|
|
1601
|
+
else if (buildLikeTurn) {
|
|
1602
|
+
nudge =
|
|
1603
|
+
"You described what you will do but emitted NO ```tool block, so NOTHING actually happened — narration is not action. Emit a real tool call NOW. For this build task, explore first like this:\n" +
|
|
1604
|
+
'```tool\n{"name":"fs.list","args":{"path":"."}}\n```\n' +
|
|
1605
|
+
"Then read key files, and once you understand the directory, call plan.create. Every turn MUST contain a ```tool block until the task is done.";
|
|
1606
|
+
writeNotice("warn", "described an action but emitted no tool call — nudging it to run one", chalk.yellow(" ⚠ described an action but emitted no tool call — nudging it to run one\n"));
|
|
1607
|
+
}
|
|
1546
1608
|
else {
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
}
|
|
1551
|
-
else {
|
|
1552
|
-
nudge =
|
|
1553
|
-
"You described what you will do but emitted NO ```tool block, so NOTHING actually happened — narration is not action. Emit a real tool call NOW. For this build task, explore first like this:\n" +
|
|
1554
|
-
'```tool\n{"name":"fs.list","args":{"path":"."}}\n```\n' +
|
|
1555
|
-
"Then read key files, and once you understand the directory, call plan.create. Every turn MUST contain a ```tool block until the task is done.";
|
|
1556
|
-
}
|
|
1609
|
+
// Generic non-build, non-web stall (e.g. "I'll list the files").
|
|
1610
|
+
nudge =
|
|
1611
|
+
"You described what you will do but emitted NO ```tool block, so NOTHING actually happened — narration is not action. Emit a real tool call NOW for the step you just described. Every turn that claims an action MUST contain a ```tool block.";
|
|
1557
1612
|
writeNotice("warn", "described an action but emitted no tool call — nudging it to run one", chalk.yellow(" ⚠ described an action but emitted no tool call — nudging it to run one\n"));
|
|
1558
1613
|
}
|
|
1559
1614
|
pushAssistantHistory(assistantText.visible);
|
|
@@ -1574,29 +1629,28 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
1574
1629
|
continue;
|
|
1575
1630
|
}
|
|
1576
1631
|
// A passing build is not evidence that an app is serving requests.
|
|
1577
|
-
// On completed build plans, require start → logs → HTTP
|
|
1578
|
-
//
|
|
1632
|
+
// On completed CODING build plans only, require start → logs → HTTP
|
|
1633
|
+
// verification. NEVER apply this to pentest/remote engagements — that
|
|
1634
|
+
// forced "npm run dev on the clai repo" after a finished web assessment.
|
|
1579
1635
|
if (buildLike &&
|
|
1636
|
+
!pentestLike &&
|
|
1637
|
+
!pentestSession &&
|
|
1580
1638
|
session.planApproved.value &&
|
|
1581
1639
|
(!sawServerStart || !sawServerTail || !sawLocalHttpProbe) &&
|
|
1582
1640
|
runtimeVerificationRetries < 2) {
|
|
1583
1641
|
const runtimePlan = await loadPlan(session.sessionId).catch(() => undefined);
|
|
1584
|
-
const
|
|
1642
|
+
const codingPlanFinished = Boolean(runtimePlan &&
|
|
1643
|
+
runtimePlan.kind !== "pentest" &&
|
|
1585
1644
|
runtimePlan.tasks.length > 0 &&
|
|
1586
1645
|
runtimePlan.tasks.every((task) => task.state === "done" || task.state === "skipped"));
|
|
1587
|
-
if (
|
|
1646
|
+
if (codingPlanFinished) {
|
|
1588
1647
|
runtimeVerificationRetries += 1;
|
|
1589
1648
|
pushAssistantHistory(assistantText.visible);
|
|
1590
|
-
const missing = [
|
|
1591
|
-
!sawServerStart ? "shell.start" : "",
|
|
1592
|
-
!sawServerTail ? "shell.tail" : "",
|
|
1593
|
-
!sawLocalHttpProbe
|
|
1594
|
-
? "a successful bounded localhost HTTP probe"
|
|
1595
|
-
: "",
|
|
1596
|
-
].filter(Boolean);
|
|
1597
1649
|
messages.push({
|
|
1598
1650
|
role: "user",
|
|
1599
|
-
content: "
|
|
1651
|
+
content: "This is a CODING project only: run the missing local checks (shell.start + shell.tail + localhost HTTP probe). " +
|
|
1652
|
+
"Keep the dev server running and print the localhost link. " +
|
|
1653
|
+
"If this was a remote pentest, ignore this and finalize the report with no local server.",
|
|
1600
1654
|
});
|
|
1601
1655
|
continue;
|
|
1602
1656
|
}
|
|
@@ -1615,20 +1669,26 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
1615
1669
|
const next = unfinished[0];
|
|
1616
1670
|
writeNotice("warn", `${unfinished.length} plan task(s) still unfinished — not accepting a "done" claim; resuming execution`, chalk.yellow(` ⚠ ${unfinished.length} plan task(s) still unfinished — not accepting a "done" claim; resuming execution\n`));
|
|
1617
1671
|
pushAssistantHistory(assistantText.visible);
|
|
1672
|
+
const isPentestPlan = livePlan.kind === "pentest" || pentestSession;
|
|
1618
1673
|
let instruction = `Resume now with the NEXT task ${next.id} ("${next.title}"): `;
|
|
1619
|
-
if (
|
|
1620
|
-
instruction +=
|
|
1674
|
+
if (isPentestPlan) {
|
|
1675
|
+
instruction +=
|
|
1676
|
+
`call task.update {taskId:"${next.id}", state:"in_progress"}, then do the recon/testing work ` +
|
|
1677
|
+
`(dns/http/net.scan/http.fetch/tool.batch — NOT a local dev server), VERIFY with real tool output, ` +
|
|
1678
|
+
`then task.update done. Do NOT shell.start / npm run dev / explore the clai workspace. `;
|
|
1679
|
+
}
|
|
1680
|
+
else if (next.state === "pending") {
|
|
1681
|
+
instruction += `call task.update {taskId:"${next.id}", state:"in_progress"}, then do the real work with a tool call (fs.writeMany / shell.exec / shell.start when building a local app), VERIFY it, and mark it done. `;
|
|
1621
1682
|
}
|
|
1622
1683
|
else {
|
|
1623
|
-
instruction += `do the real work with a tool call (fs.writeMany / shell.exec / shell.start) to complete it, VERIFY it, and mark it done (call task.update {taskId:"${next.id}", state:"done"}). `;
|
|
1684
|
+
instruction += `do the real work with a tool call (fs.writeMany / shell.exec / shell.start when building a local app) to complete it, VERIFY it, and mark it done (call task.update {taskId:"${next.id}", state:"done"}). `;
|
|
1624
1685
|
}
|
|
1625
1686
|
instruction += `Continue task by task until EVERY task is actually finished.`;
|
|
1626
1687
|
messages.push({
|
|
1627
1688
|
role: "user",
|
|
1628
1689
|
content: `You have NOT finished the approved plan: ${unfinished.length} task(s) remain ` +
|
|
1629
1690
|
`(${unfinished.map((t) => `[${t.id}] ${t.title}`).join("; ")}). ` +
|
|
1630
|
-
`Do NOT claim the work is complete
|
|
1631
|
-
`unless a tool call actually succeeded and you saw the output. ` +
|
|
1691
|
+
`Do NOT claim the work is complete unless a tool call actually succeeded and you saw the output. ` +
|
|
1632
1692
|
instruction,
|
|
1633
1693
|
});
|
|
1634
1694
|
continue;
|
|
@@ -1740,7 +1800,12 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
1740
1800
|
`${omittedCallCount} were not run. After reviewing results, issue a small, prioritized next batch.`,
|
|
1741
1801
|
});
|
|
1742
1802
|
}
|
|
1743
|
-
//
|
|
1803
|
+
// Notice BEFORE tool cards so the transcript reads:
|
|
1804
|
+
// thinking → response → "N tool calls…" → tool cards (not tools then info).
|
|
1805
|
+
if (allCalls.length > 1) {
|
|
1806
|
+
writeNotice("info", `${allCalls.length} tool calls in this message — running scoped (independent read-only lookups in parallel, everything else in order)`, chalk.dim(` ℹ ${allCalls.length} tool calls — read-only lookups in parallel, the rest in order\n`));
|
|
1807
|
+
}
|
|
1808
|
+
// Emit only the calls that will actually execute, after thinking
|
|
1744
1809
|
// + assistant text so transcript order remains correct.
|
|
1745
1810
|
for (const deferred of activeDeferredToolCalls.slice(0, allCalls.length)) {
|
|
1746
1811
|
writeToolCall(deferred.eventId, deferred.call, deferred.rendered);
|
|
@@ -1750,9 +1815,6 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
1750
1815
|
.map((c) => `\`\`\`tool\n${JSON.stringify(c)}\n\`\`\``)
|
|
1751
1816
|
.join("\n\n");
|
|
1752
1817
|
pushAssistantHistory(standardizedContent);
|
|
1753
|
-
if (allCalls.length > 1) {
|
|
1754
|
-
writeNotice("info", `${allCalls.length} tool calls in this message — running scoped (independent read-only lookups in parallel, everything else in order)`, chalk.dim(` ℹ ${allCalls.length} tool calls — read-only lookups in parallel, the rest in order\n`));
|
|
1755
|
-
}
|
|
1756
1818
|
// Scoped-parallel batch execution
|
|
1757
1819
|
// The model may emit several calls in one message. We partition them,
|
|
1758
1820
|
// IN DOCUMENT ORDER, into segments:
|
|
@@ -1772,7 +1834,22 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
1772
1834
|
// later recon from running; aborts, blocks, and sequential-barrier
|
|
1773
1835
|
// failures still stop the batch so the model can react safely.
|
|
1774
1836
|
const scopeForBatch = await loadScope().catch(() => undefined);
|
|
1837
|
+
/**
|
|
1838
|
+
* Tools that may share a concurrent wave. Pure read-only BATCH_SAFE
|
|
1839
|
+
* tools always qualify when classified safe. Heavy discovery wrappers
|
|
1840
|
+
* (pentest.recon, net.context) also run concurrent with dns/http so
|
|
1841
|
+
* nmap does not serialize the entire recon wave.
|
|
1842
|
+
* net.scan stays a barrier when it needs confirm/sudo UX.
|
|
1843
|
+
*/
|
|
1775
1844
|
const isParallelSafe = (c) => {
|
|
1845
|
+
if (c.name === "pentest.recon" ||
|
|
1846
|
+
c.name === "net.context" ||
|
|
1847
|
+
c.name === "tool.batch" ||
|
|
1848
|
+
c.name === "tool.check" ||
|
|
1849
|
+
c.name === "shell.jobs" ||
|
|
1850
|
+
c.name === "shell.tail") {
|
|
1851
|
+
return true;
|
|
1852
|
+
}
|
|
1776
1853
|
if (!BATCH_SAFE_TOOLS.has(c.name))
|
|
1777
1854
|
return false;
|
|
1778
1855
|
try {
|
|
@@ -1782,13 +1859,36 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
1782
1859
|
return false;
|
|
1783
1860
|
}
|
|
1784
1861
|
};
|
|
1785
|
-
|
|
1862
|
+
/** Tools whose failure must NOT cancel sibling calls in this turn. */
|
|
1863
|
+
const shouldSoftFailTool = (name) => {
|
|
1864
|
+
if (name === "tool.batch")
|
|
1865
|
+
return true;
|
|
1866
|
+
if (BATCH_SAFE_TOOLS.has(name))
|
|
1867
|
+
return true;
|
|
1868
|
+
if (name === "pentest.recon" ||
|
|
1869
|
+
name === "net.scan" ||
|
|
1870
|
+
name === "net.pingSweep" ||
|
|
1871
|
+
name === "net.context" ||
|
|
1872
|
+
name === "shell.jobs" ||
|
|
1873
|
+
name === "shell.tail" ||
|
|
1874
|
+
name === "tool.check") {
|
|
1875
|
+
return true;
|
|
1876
|
+
}
|
|
1877
|
+
return false;
|
|
1878
|
+
};
|
|
1879
|
+
// Recon waves often emit 6–10 lookups; 4 forced a second sequential wave.
|
|
1880
|
+
const PARALLEL_LIMIT = 8;
|
|
1786
1881
|
let aborted = false;
|
|
1787
1882
|
let blocked = false;
|
|
1788
1883
|
let blockedResult = null;
|
|
1789
1884
|
let failed = false;
|
|
1790
1885
|
let awaitingPlanApproval = false;
|
|
1886
|
+
/** Indices into allCalls that actually ran (got a tool-result). */
|
|
1887
|
+
const executedIndices = new Set();
|
|
1791
1888
|
const recordResult = (res, continueAfterFailure = false) => {
|
|
1889
|
+
const idx = allCalls.indexOf(res.call);
|
|
1890
|
+
if (idx >= 0)
|
|
1891
|
+
executedIndices.add(idx);
|
|
1792
1892
|
messages.push({
|
|
1793
1893
|
role: "tool",
|
|
1794
1894
|
content: `Tool ${res.call.name} result (exit=${res.result.exitCode ?? 0}, ok=${res.result.ok}):\n${res.contextOutput}`,
|
|
@@ -1833,7 +1933,11 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
1833
1933
|
}
|
|
1834
1934
|
const id = (idx >= 0 ? callIds[idx] : undefined) ?? `tool-${++nextToolEventId}`;
|
|
1835
1935
|
const res = await executeSingleTool(call, id, options.signal || new AbortController().signal);
|
|
1836
|
-
|
|
1936
|
+
// Soft-fail recon / read-only / discovery tools so a stalled
|
|
1937
|
+
// whois or failed lookup never cancels the rest of the turn with
|
|
1938
|
+
// "Cancelled — earlier tool in this batch failed."
|
|
1939
|
+
const softFail = shouldSoftFailTool(call.name);
|
|
1940
|
+
recordResult(res, softFail);
|
|
1837
1941
|
}
|
|
1838
1942
|
else {
|
|
1839
1943
|
// Concurrent group — assign ids in document order, then push their
|
|
@@ -1853,6 +1957,41 @@ export async function runAgentLoop(prompt, options = {}) {
|
|
|
1853
1957
|
recordResult(res, true);
|
|
1854
1958
|
}
|
|
1855
1959
|
}
|
|
1960
|
+
// Cards were emitted for every call up front. If the batch stopped
|
|
1961
|
+
// early (failure / abort / plan gate), any card still on "running"
|
|
1962
|
+
// must get a terminal result so the TUI never spins forever.
|
|
1963
|
+
for (let i = 0; i < allCalls.length; i += 1) {
|
|
1964
|
+
if (executedIndices.has(i))
|
|
1965
|
+
continue;
|
|
1966
|
+
const call = allCalls[i];
|
|
1967
|
+
if (i >= 0 && !callIds[i]) {
|
|
1968
|
+
callIds[i] = `tool-${++nextToolEventId}`;
|
|
1969
|
+
}
|
|
1970
|
+
const id = callIds[i];
|
|
1971
|
+
if (!alreadyPrintedIds.has(id)) {
|
|
1972
|
+
// Never shown in the transcript — skip.
|
|
1973
|
+
continue;
|
|
1974
|
+
}
|
|
1975
|
+
const reason = aborted
|
|
1976
|
+
? "Cancelled — turn aborted before this call ran."
|
|
1977
|
+
: blocked
|
|
1978
|
+
? "Cancelled — earlier tool was blocked or declined."
|
|
1979
|
+
: awaitingPlanApproval
|
|
1980
|
+
? "Deferred — waiting for plan approval."
|
|
1981
|
+
: failed
|
|
1982
|
+
? "Cancelled — earlier tool in this batch failed."
|
|
1983
|
+
: "Cancelled — not executed.";
|
|
1984
|
+
const result = {
|
|
1985
|
+
ok: false,
|
|
1986
|
+
output: reason,
|
|
1987
|
+
exitCode: 130,
|
|
1988
|
+
};
|
|
1989
|
+
emitToolResult(id, result, reason);
|
|
1990
|
+
messages.push({
|
|
1991
|
+
role: "tool",
|
|
1992
|
+
content: `Tool ${call.name} result (exit=130, ok=false):\n${reason}`,
|
|
1993
|
+
});
|
|
1994
|
+
}
|
|
1856
1995
|
// plan.create is a hard transaction boundary. Its successful handler
|
|
1857
1996
|
// persists and displays the plan; returning immediately prevents a
|
|
1858
1997
|
// stale pre-loop activePlan snapshot from nudging a duplicate plan and
|