lonny-agent 0.2.6 → 0.2.7
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/.github/workflows/ci.yml +21 -0
- package/AGENTS.md +106 -0
- package/CHANGELOG.md +24 -0
- package/dist/agent/__tests__/session-restore.test.d.ts +2 -0
- package/dist/agent/__tests__/session-restore.test.d.ts.map +1 -0
- package/dist/agent/__tests__/session-restore.test.js +260 -0
- package/dist/agent/__tests__/session-restore.test.js.map +1 -0
- package/dist/agent/commands.d.ts +26 -0
- package/dist/agent/commands.d.ts.map +1 -0
- package/dist/agent/commands.js +253 -0
- package/dist/agent/commands.js.map +1 -0
- package/dist/agent/compaction.js +8 -1
- package/dist/agent/compaction.js.map +1 -1
- package/dist/agent/index.d.ts +2 -1
- package/dist/agent/index.d.ts.map +1 -1
- package/dist/agent/index.js +2 -2
- package/dist/agent/index.js.map +1 -1
- package/dist/agent/memory.d.ts +12 -0
- package/dist/agent/memory.d.ts.map +1 -0
- package/dist/agent/memory.js +51 -0
- package/dist/agent/memory.js.map +1 -0
- package/dist/agent/prompt-builder.d.ts +6 -1
- package/dist/agent/prompt-builder.d.ts.map +1 -1
- package/dist/agent/prompt-builder.js +89 -26
- package/dist/agent/prompt-builder.js.map +1 -1
- package/dist/agent/providers/openai.d.ts +1 -0
- package/dist/agent/providers/openai.d.ts.map +1 -1
- package/dist/agent/providers/openai.js +16 -3
- package/dist/agent/providers/openai.js.map +1 -1
- package/dist/agent/session-chat.d.ts +3 -0
- package/dist/agent/session-chat.d.ts.map +1 -0
- package/dist/agent/session-chat.js +384 -0
- package/dist/agent/session-chat.js.map +1 -0
- package/dist/agent/session-display.d.ts +29 -0
- package/dist/agent/session-display.d.ts.map +1 -0
- package/dist/agent/session-display.js +179 -0
- package/dist/agent/session-display.js.map +1 -0
- package/dist/agent/session-persistence.d.ts +55 -0
- package/dist/agent/session-persistence.d.ts.map +1 -0
- package/dist/agent/session-persistence.js +101 -0
- package/dist/agent/session-persistence.js.map +1 -0
- package/dist/agent/session-utils.d.ts +5 -0
- package/dist/agent/session-utils.d.ts.map +1 -0
- package/dist/agent/session-utils.js +51 -0
- package/dist/agent/session-utils.js.map +1 -0
- package/dist/agent/session.d.ts +15 -17
- package/dist/agent/session.d.ts.map +1 -1
- package/dist/agent/session.js +226 -608
- package/dist/agent/session.js.map +1 -1
- package/dist/cli/index.d.ts +4 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +67 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/config/index.d.ts +2 -2
- package/dist/config/index.d.ts.map +1 -1
- package/dist/index.js +24 -3
- package/dist/index.js.map +1 -1
- package/dist/tools/__tests__/bash.test.js +168 -1
- package/dist/tools/__tests__/bash.test.js.map +1 -1
- package/dist/tools/__tests__/edit.test.js +253 -6
- package/dist/tools/__tests__/edit.test.js.map +1 -1
- package/dist/tools/__tests__/fetch.test.js +1 -1
- package/dist/tools/__tests__/fetch.test.js.map +1 -1
- package/dist/tools/__tests__/glob.test.js +1 -1
- package/dist/tools/__tests__/glob.test.js.map +1 -1
- package/dist/tools/__tests__/grep.test.js +1 -1
- package/dist/tools/__tests__/grep.test.js.map +1 -1
- package/dist/tools/__tests__/ls.test.js +1 -1
- package/dist/tools/__tests__/ls.test.js.map +1 -1
- package/dist/tools/__tests__/read.test.js +1 -1
- package/dist/tools/__tests__/read.test.js.map +1 -1
- package/dist/tools/__tests__/registry.test.js +313 -0
- package/dist/tools/__tests__/registry.test.js.map +1 -1
- package/dist/tools/__tests__/write_plan.test.js +1 -1
- package/dist/tools/__tests__/write_plan.test.js.map +1 -1
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +659 -39
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/codebase/ast/__tests__/ast-edit-demo.d.ts +2 -0
- package/dist/tools/codebase/ast/__tests__/ast-edit-demo.d.ts.map +1 -0
- package/dist/tools/codebase/ast/__tests__/ast-edit-demo.js +7 -0
- package/dist/tools/codebase/ast/__tests__/ast-edit-demo.js.map +1 -0
- package/dist/tools/codebase/ast/__tests__/ast-edit-final.d.ts +6 -0
- package/dist/tools/codebase/ast/__tests__/ast-edit-final.d.ts.map +1 -0
- package/dist/tools/codebase/ast/__tests__/ast-edit-final.js +11 -0
- package/dist/tools/codebase/ast/__tests__/ast-edit-final.js.map +1 -0
- package/dist/tools/codebase/ast/__tests__/ast-edit-test.d.ts +2 -0
- package/dist/tools/codebase/ast/__tests__/ast-edit-test.d.ts.map +1 -0
- package/dist/tools/codebase/ast/__tests__/ast-edit-test.js +9 -0
- package/dist/tools/codebase/ast/__tests__/ast-edit-test.js.map +1 -0
- package/dist/tools/codebase/ast/__tests__/integration.test.d.ts +2 -0
- package/dist/tools/codebase/ast/__tests__/integration.test.d.ts.map +1 -0
- package/dist/tools/codebase/ast/__tests__/integration.test.js +415 -0
- package/dist/tools/codebase/ast/__tests__/integration.test.js.map +1 -0
- package/dist/tools/codebase/ast/__tests__/tree-sitter.test.d.ts +2 -0
- package/dist/tools/codebase/ast/__tests__/tree-sitter.test.d.ts.map +1 -0
- package/dist/tools/codebase/ast/__tests__/tree-sitter.test.js +414 -0
- package/dist/tools/codebase/ast/__tests__/tree-sitter.test.js.map +1 -0
- package/dist/tools/codebase/ast/adapter.d.ts +23 -0
- package/dist/tools/codebase/ast/adapter.d.ts.map +1 -0
- package/dist/tools/codebase/ast/adapter.js +11 -0
- package/dist/tools/codebase/ast/adapter.js.map +1 -0
- package/dist/tools/codebase/ast/tools.d.ts +3 -0
- package/dist/tools/codebase/ast/tools.d.ts.map +1 -0
- package/dist/tools/codebase/ast/tools.js +286 -0
- package/dist/tools/codebase/ast/tools.js.map +1 -0
- package/dist/tools/codebase/ast/tree-sitter-adapter.d.ts +3 -0
- package/dist/tools/codebase/ast/tree-sitter-adapter.d.ts.map +1 -0
- package/dist/tools/codebase/ast/tree-sitter-adapter.js +412 -0
- package/dist/tools/codebase/ast/tree-sitter-adapter.js.map +1 -0
- package/dist/tools/codebase/ast/tree-sitter-converters.d.ts +4 -0
- package/dist/tools/codebase/ast/tree-sitter-converters.d.ts.map +1 -0
- package/dist/tools/codebase/ast/tree-sitter-converters.js +447 -0
- package/dist/tools/codebase/ast/tree-sitter-converters.js.map +1 -0
- package/dist/tools/codebase/ast/types.d.ts +103 -0
- package/dist/tools/codebase/ast/types.d.ts.map +1 -0
- package/dist/tools/codebase/ast/types.js +2 -0
- package/dist/tools/codebase/ast/types.js.map +1 -0
- package/dist/tools/codebase/ast/wasm/tree-sitter-python.wasm +0 -0
- package/dist/tools/codebase/ast/wasm/tree-sitter-tsx.wasm +0 -0
- package/dist/tools/codebase/ast/wasm/tree-sitter-typescript.wasm +0 -0
- package/dist/tools/codebase/ast/wasm/tree-sitter.wasm +0 -0
- package/dist/tools/codebase/ast/wasm/web-tree-sitter.wasm +0 -0
- package/dist/tools/codebase/find.d.ts +3 -0
- package/dist/tools/codebase/find.d.ts.map +1 -0
- package/dist/tools/codebase/find.js +61 -0
- package/dist/tools/codebase/find.js.map +1 -0
- package/dist/tools/codebase/glob.d.ts +3 -0
- package/dist/tools/codebase/glob.d.ts.map +1 -0
- package/dist/tools/codebase/glob.js +37 -0
- package/dist/tools/codebase/glob.js.map +1 -0
- package/dist/tools/codebase/grep.d.ts +3 -0
- package/dist/tools/codebase/grep.d.ts.map +1 -0
- package/dist/tools/codebase/grep.js +169 -0
- package/dist/tools/codebase/grep.js.map +1 -0
- package/dist/tools/codebase/ls.d.ts +3 -0
- package/dist/tools/codebase/ls.d.ts.map +1 -0
- package/dist/tools/codebase/ls.js +39 -0
- package/dist/tools/codebase/ls.js.map +1 -0
- package/dist/tools/codebase/read.d.ts +10 -0
- package/dist/tools/codebase/read.d.ts.map +1 -0
- package/dist/tools/codebase/read.js +81 -0
- package/dist/tools/codebase/read.js.map +1 -0
- package/dist/tools/delete_memory.d.ts +3 -0
- package/dist/tools/delete_memory.d.ts.map +1 -0
- package/dist/tools/delete_memory.js +46 -0
- package/dist/tools/delete_memory.js.map +1 -0
- package/dist/tools/edit/diff-compute.d.ts +4 -0
- package/dist/tools/edit/diff-compute.d.ts.map +1 -0
- package/dist/tools/edit/diff-compute.js +18 -0
- package/dist/tools/edit/diff-compute.js.map +1 -0
- package/dist/tools/edit/diff-render.d.ts +18 -0
- package/dist/tools/edit/diff-render.d.ts.map +1 -0
- package/dist/tools/edit/diff-render.js +128 -0
- package/dist/tools/edit/diff-render.js.map +1 -0
- package/dist/tools/edit/edit.d.ts +4 -0
- package/dist/tools/edit/edit.d.ts.map +1 -0
- package/dist/tools/edit/edit.js +440 -0
- package/dist/tools/edit/edit.js.map +1 -0
- package/dist/tools/edit/matcher.d.ts +24 -0
- package/dist/tools/edit/matcher.d.ts.map +1 -0
- package/dist/tools/edit/matcher.js +66 -0
- package/dist/tools/edit/matcher.js.map +1 -0
- package/dist/tools/edit/parser.d.ts +10 -0
- package/dist/tools/edit/parser.d.ts.map +1 -0
- package/dist/tools/edit/parser.js +120 -0
- package/dist/tools/edit/parser.js.map +1 -0
- package/dist/tools/edit/types.d.ts +16 -0
- package/dist/tools/edit/types.d.ts.map +1 -0
- package/dist/tools/edit/types.js +2 -0
- package/dist/tools/edit/types.js.map +1 -0
- package/dist/tools/edit/write_plan.d.ts +5 -0
- package/dist/tools/edit/write_plan.d.ts.map +1 -0
- package/dist/tools/edit/write_plan.js +98 -0
- package/dist/tools/edit/write_plan.js.map +1 -0
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +66 -1
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/execute/bash/constants.d.ts +24 -0
- package/dist/tools/execute/bash/constants.d.ts.map +1 -0
- package/dist/tools/execute/bash/constants.js +88 -0
- package/dist/tools/execute/bash/constants.js.map +1 -0
- package/dist/tools/execute/bash/errors.d.ts +39 -0
- package/dist/tools/execute/bash/errors.d.ts.map +1 -0
- package/dist/tools/execute/bash/errors.js +249 -0
- package/dist/tools/execute/bash/errors.js.map +1 -0
- package/dist/tools/execute/bash/execution.d.ts +18 -0
- package/dist/tools/execute/bash/execution.d.ts.map +1 -0
- package/dist/tools/execute/bash/execution.js +207 -0
- package/dist/tools/execute/bash/execution.js.map +1 -0
- package/dist/tools/execute/bash/index.d.ts +3 -0
- package/dist/tools/execute/bash/index.d.ts.map +1 -0
- package/dist/tools/execute/bash/index.js +117 -0
- package/dist/tools/execute/bash/index.js.map +1 -0
- package/dist/tools/execute/bash/platform.d.ts +6 -0
- package/dist/tools/execute/bash/platform.d.ts.map +1 -0
- package/dist/tools/execute/bash/platform.js +64 -0
- package/dist/tools/execute/bash/platform.js.map +1 -0
- package/dist/tools/execute/bash/security.d.ts +12 -0
- package/dist/tools/execute/bash/security.d.ts.map +1 -0
- package/dist/tools/execute/bash/security.js +26 -0
- package/dist/tools/execute/bash/security.js.map +1 -0
- package/dist/tools/execute/bash/validation.d.ts +16 -0
- package/dist/tools/execute/bash/validation.d.ts.map +1 -0
- package/dist/tools/execute/bash/validation.js +57 -0
- package/dist/tools/execute/bash/validation.js.map +1 -0
- package/dist/tools/execute/bash.d.ts +3 -0
- package/dist/tools/execute/bash.d.ts.map +1 -0
- package/dist/tools/execute/bash.js +728 -0
- package/dist/tools/execute/bash.js.map +1 -0
- package/dist/tools/execute/git.d.ts +3 -0
- package/dist/tools/execute/git.d.ts.map +1 -0
- package/dist/tools/execute/git.js +183 -0
- package/dist/tools/execute/git.js.map +1 -0
- package/dist/tools/execute/task_complete.d.ts +7 -0
- package/dist/tools/execute/task_complete.d.ts.map +1 -0
- package/dist/tools/execute/task_complete.js +38 -0
- package/dist/tools/execute/task_complete.js.map +1 -0
- package/dist/tools/fetch.d.ts.map +1 -1
- package/dist/tools/fetch.js +2 -0
- package/dist/tools/fetch.js.map +1 -1
- package/dist/tools/find.d.ts.map +1 -1
- package/dist/tools/find.js +2 -0
- package/dist/tools/find.js.map +1 -1
- package/dist/tools/git.d.ts.map +1 -1
- package/dist/tools/git.js +2 -0
- package/dist/tools/git.js.map +1 -1
- package/dist/tools/glob.d.ts.map +1 -1
- package/dist/tools/glob.js +2 -0
- package/dist/tools/glob.js.map +1 -1
- package/dist/tools/grep.d.ts.map +1 -1
- package/dist/tools/grep.js +2 -0
- package/dist/tools/grep.js.map +1 -1
- package/dist/tools/install/clawhub.d.ts +26 -0
- package/dist/tools/install/clawhub.d.ts.map +1 -0
- package/dist/tools/install/clawhub.js +182 -0
- package/dist/tools/install/clawhub.js.map +1 -0
- package/dist/tools/install/constants.d.ts +5 -0
- package/dist/tools/install/constants.d.ts.map +1 -0
- package/dist/tools/install/constants.js +5 -0
- package/dist/tools/install/constants.js.map +1 -0
- package/dist/tools/install/install_skill.d.ts +3 -0
- package/dist/tools/install/install_skill.d.ts.map +1 -0
- package/dist/tools/install/install_skill.js +132 -0
- package/dist/tools/install/install_skill.js.map +1 -0
- package/dist/tools/install/npm.d.ts +8 -0
- package/dist/tools/install/npm.d.ts.map +1 -0
- package/dist/tools/install/npm.js +52 -0
- package/dist/tools/install/npm.js.map +1 -0
- package/dist/tools/install/skill-content.d.ts +9 -0
- package/dist/tools/install/skill-content.d.ts.map +1 -0
- package/dist/tools/install/skill-content.js +89 -0
- package/dist/tools/install/skill-content.js.map +1 -0
- package/dist/tools/install_skill.d.ts.map +1 -1
- package/dist/tools/install_skill.js +2 -0
- package/dist/tools/install_skill.js.map +1 -1
- package/dist/tools/list_memory.d.ts +3 -0
- package/dist/tools/list_memory.d.ts.map +1 -0
- package/dist/tools/list_memory.js +55 -0
- package/dist/tools/list_memory.js.map +1 -0
- package/dist/tools/ls.d.ts.map +1 -1
- package/dist/tools/ls.js +2 -0
- package/dist/tools/ls.js.map +1 -1
- package/dist/tools/memory/delete_memory.d.ts +3 -0
- package/dist/tools/memory/delete_memory.d.ts.map +1 -0
- package/dist/tools/memory/delete_memory.js +46 -0
- package/dist/tools/memory/delete_memory.js.map +1 -0
- package/dist/tools/memory/list_memory.d.ts +3 -0
- package/dist/tools/memory/list_memory.d.ts.map +1 -0
- package/dist/tools/memory/list_memory.js +55 -0
- package/dist/tools/memory/list_memory.js.map +1 -0
- package/dist/tools/memory/save_memory.d.ts +3 -0
- package/dist/tools/memory/save_memory.d.ts.map +1 -0
- package/dist/tools/memory/save_memory.js +45 -0
- package/dist/tools/memory/save_memory.js.map +1 -0
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +2 -0
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/registry.d.ts +7 -2
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js +112 -17
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/save_memory.d.ts +3 -0
- package/dist/tools/save_memory.d.ts.map +1 -0
- package/dist/tools/save_memory.js +45 -0
- package/dist/tools/save_memory.js.map +1 -0
- package/dist/tools/search.d.ts.map +1 -1
- package/dist/tools/search.js +2 -0
- package/dist/tools/search.js.map +1 -1
- package/dist/tools/tree.d.ts +12 -0
- package/dist/tools/tree.d.ts.map +1 -0
- package/dist/tools/tree.js +87 -0
- package/dist/tools/tree.js.map +1 -0
- package/dist/tools/types.d.ts +12 -0
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/tools/web/fetch.d.ts +3 -0
- package/dist/tools/web/fetch.d.ts.map +1 -0
- package/dist/tools/web/fetch.js +49 -0
- package/dist/tools/web/fetch.js.map +1 -0
- package/dist/tools/web/search.d.ts +3 -0
- package/dist/tools/web/search.d.ts.map +1 -0
- package/dist/tools/web/search.js +134 -0
- package/dist/tools/web/search.js.map +1 -0
- package/dist/tools/write_plan.d.ts.map +1 -1
- package/dist/tools/write_plan.js +2 -0
- package/dist/tools/write_plan.js.map +1 -1
- package/dist/tui/commands.d.ts +4 -0
- package/dist/tui/commands.d.ts.map +1 -0
- package/dist/tui/commands.js +118 -0
- package/dist/tui/commands.js.map +1 -0
- package/dist/tui/components/colors.d.ts +36 -0
- package/dist/tui/components/colors.d.ts.map +1 -0
- package/dist/tui/components/colors.js +48 -0
- package/dist/tui/components/colors.js.map +1 -0
- package/dist/tui/components/header-bar.d.ts +26 -0
- package/dist/tui/components/header-bar.d.ts.map +1 -0
- package/dist/tui/components/header-bar.js +78 -0
- package/dist/tui/components/header-bar.js.map +1 -0
- package/dist/tui/components/index.d.ts +9 -0
- package/dist/tui/components/index.d.ts.map +1 -0
- package/dist/tui/components/index.js +9 -0
- package/dist/tui/components/index.js.map +1 -0
- package/dist/tui/components/landing-screen.d.ts +12 -0
- package/dist/tui/components/landing-screen.d.ts.map +1 -0
- package/dist/tui/components/landing-screen.js +53 -0
- package/dist/tui/components/landing-screen.js.map +1 -0
- package/dist/tui/components/pixel-logo.d.ts +5 -0
- package/dist/tui/components/pixel-logo.d.ts.map +1 -0
- package/dist/tui/components/pixel-logo.js +27 -0
- package/dist/tui/components/pixel-logo.js.map +1 -0
- package/dist/tui/components/plan-utils.d.ts +11 -0
- package/dist/tui/components/plan-utils.d.ts.map +1 -0
- package/dist/tui/components/plan-utils.js +62 -0
- package/dist/tui/components/plan-utils.js.map +1 -0
- package/dist/tui/components/plans-list.d.ts +18 -0
- package/dist/tui/components/plans-list.d.ts.map +1 -0
- package/dist/tui/components/plans-list.js +46 -0
- package/dist/tui/components/plans-list.js.map +1 -0
- package/dist/tui/components/rich-footer.d.ts +29 -0
- package/dist/tui/components/rich-footer.d.ts.map +1 -0
- package/dist/tui/components/rich-footer.js +124 -0
- package/dist/tui/components/rich-footer.js.map +1 -0
- package/dist/tui/components/todo-panel.d.ts +14 -0
- package/dist/tui/components/todo-panel.d.ts.map +1 -0
- package/dist/tui/components/todo-panel.js +83 -0
- package/dist/tui/components/todo-panel.js.map +1 -0
- package/dist/tui/components.d.ts.map +1 -1
- package/dist/tui/components.js +14 -7
- package/dist/tui/components.js.map +1 -1
- package/dist/tui/index.d.ts +2 -1
- package/dist/tui/index.d.ts.map +1 -1
- package/dist/tui/index.js +99 -505
- package/dist/tui/index.js.map +1 -1
- package/dist/tui/overlays.d.ts +28 -0
- package/dist/tui/overlays.d.ts.map +1 -0
- package/dist/tui/overlays.js +154 -0
- package/dist/tui/overlays.js.map +1 -0
- package/dist/tui/themes.d.ts +5 -0
- package/dist/tui/themes.d.ts.map +1 -0
- package/dist/tui/themes.js +38 -0
- package/dist/tui/themes.js.map +1 -0
- package/dist/web/index.d.ts.map +1 -1
- package/dist/web/index.js +60 -122
- package/dist/web/index.js.map +1 -1
- package/dist/web/public/index.html +1 -1
- package/dist/web/public/messages.js +41 -25
- package/dist/web/public/state.js +72 -72
- package/dist/web/public/style.css +1126 -996
- package/dist/web/public/websocket.js +267 -267
- package/dist/web/session-bridge.d.ts.map +1 -1
- package/dist/web/session-bridge.js +82 -3
- package/dist/web/session-bridge.js.map +1 -1
- package/docs/ast-tools.md +117 -0
- package/package.json +60 -55
- package/scripts/check-line-count.mjs +53 -0
- package/scripts/copy-ast-wasm.mjs +32 -0
- package/src/agent/__tests__/session-restore.test.ts +284 -0
- package/src/agent/commands.ts +274 -0
- package/src/agent/compaction.ts +9 -2
- package/src/agent/index.ts +30 -25
- package/src/agent/memory.ts +60 -0
- package/src/agent/prompt-builder.ts +100 -26
- package/src/agent/providers/anthropic.ts +179 -179
- package/src/agent/providers/google.ts +210 -210
- package/src/agent/providers/ollama.ts +186 -186
- package/src/agent/providers/openai.ts +18 -3
- package/src/agent/session-chat.ts +480 -0
- package/src/agent/session-display.ts +195 -0
- package/src/agent/session-persistence.ts +146 -0
- package/src/agent/session-utils.ts +54 -0
- package/src/agent/session.ts +261 -718
- package/src/cli/index.ts +73 -4
- package/src/config/index.ts +342 -342
- package/src/index.ts +30 -3
- package/src/tools/__tests__/bash.test.ts +187 -1
- package/src/tools/__tests__/edit.test.ts +275 -11
- package/src/tools/__tests__/fetch.test.ts +1 -1
- package/src/tools/__tests__/glob.test.ts +1 -1
- package/src/tools/__tests__/grep.test.ts +1 -1
- package/src/tools/__tests__/ls.test.ts +1 -1
- package/src/tools/__tests__/read.test.ts +1 -1
- package/src/tools/__tests__/registry.test.ts +332 -0
- package/src/tools/__tests__/write_plan.test.ts +1 -1
- package/src/tools/codebase/ast/__tests__/integration.test.ts +452 -0
- package/src/tools/codebase/ast/__tests__/tree-sitter.test.ts +466 -0
- package/src/tools/codebase/ast/adapter.ts +44 -0
- package/src/tools/codebase/ast/tools.ts +322 -0
- package/src/tools/codebase/ast/tree-sitter-adapter.ts +468 -0
- package/src/tools/codebase/ast/tree-sitter-converters.ts +480 -0
- package/src/tools/codebase/ast/types.ts +102 -0
- package/src/tools/codebase/ast/wasm/tree-sitter-python.wasm +0 -0
- package/src/tools/codebase/ast/wasm/tree-sitter-tsx.wasm +0 -0
- package/src/tools/codebase/ast/wasm/tree-sitter-typescript.wasm +0 -0
- package/src/tools/codebase/ast/wasm/tree-sitter.wasm +0 -0
- package/src/tools/codebase/ast/wasm/web-tree-sitter.wasm +0 -0
- package/src/tools/{find.ts → codebase/find.ts} +4 -2
- package/src/tools/{glob.ts → codebase/glob.ts} +4 -2
- package/src/tools/{grep.ts → codebase/grep.ts} +4 -2
- package/src/tools/{ls.ts → codebase/ls.ts} +4 -2
- package/src/tools/{read.ts → codebase/read.ts} +5 -3
- package/src/tools/edit/diff-compute.ts +21 -0
- package/src/tools/edit/diff-render.ts +145 -0
- package/src/tools/edit/edit.ts +468 -0
- package/src/tools/edit/matcher.ts +70 -0
- package/src/tools/edit/parser.ts +131 -0
- package/src/tools/edit/types.ts +19 -0
- package/src/tools/{write_plan.ts → edit/write_plan.ts} +4 -2
- package/src/tools/execute/bash/constants.ts +93 -0
- package/src/tools/execute/bash/errors.ts +304 -0
- package/src/tools/execute/bash/execution.ts +211 -0
- package/src/tools/execute/bash/index.ts +131 -0
- package/src/tools/execute/bash/platform.ts +65 -0
- package/src/tools/execute/bash/security.ts +28 -0
- package/src/tools/execute/bash/validation.ts +78 -0
- package/src/tools/execute/git.ts +209 -0
- package/src/tools/execute/task_complete.ts +40 -0
- package/src/tools/install/clawhub.ts +222 -0
- package/src/tools/install/constants.ts +4 -0
- package/src/tools/install/install_skill.ts +150 -0
- package/src/tools/install/npm.ts +60 -0
- package/src/tools/install/skill-content.ts +108 -0
- package/src/tools/memory/delete_memory.ts +46 -0
- package/src/tools/memory/list_memory.ts +53 -0
- package/src/tools/memory/save_memory.ts +45 -0
- package/src/tools/registry.ts +119 -19
- package/src/tools/tree.ts +103 -0
- package/src/tools/types.ts +13 -0
- package/src/tools/{fetch.ts → web/fetch.ts} +4 -2
- package/src/tools/{search.ts → web/search.ts} +4 -2
- package/src/tui/commands.ts +135 -0
- package/src/tui/components/colors.ts +52 -0
- package/src/tui/components/header-bar.ts +88 -0
- package/src/tui/components/index.ts +8 -0
- package/src/tui/components/landing-screen.ts +67 -0
- package/src/tui/components/pixel-logo.ts +33 -0
- package/src/tui/components/plan-utils.ts +69 -0
- package/src/tui/components/plans-list.ts +55 -0
- package/src/tui/components/rich-footer.ts +146 -0
- package/src/tui/components/todo-panel.ts +95 -0
- package/src/tui/index.ts +335 -786
- package/src/tui/overlays.ts +203 -0
- package/src/tui/themes.ts +41 -0
- package/src/web/index.ts +64 -127
- package/src/web/public/index.html +1 -1
- package/src/web/public/messages.js +41 -25
- package/src/web/public/state.js +72 -72
- package/src/web/public/style.css +1126 -996
- package/src/web/public/websocket.js +267 -267
- package/src/web/session-bridge.ts +272 -193
- package/AGENT.md +0 -99
- package/src/tools/bash.ts +0 -125
- package/src/tools/edit.ts +0 -747
- package/src/tools/git.ts +0 -76
- package/src/tools/install_skill.ts +0 -539
- package/src/tui/components.ts +0 -635
package/dist/agent/session.js
CHANGED
|
@@ -1,214 +1,17 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
2
1
|
import * as fs from 'node:fs';
|
|
3
|
-
import * as os from 'node:os';
|
|
4
2
|
import * as path from 'node:path';
|
|
5
|
-
import { saveTokenUsage } from '../config/tokens.js';
|
|
6
3
|
import { FileReadTracker } from '../diff/apply.js';
|
|
7
|
-
import { fmtErr } from '../tools/errors.js';
|
|
8
4
|
import { ToolRegistry } from '../tools/registry.js';
|
|
9
|
-
import { compact, shouldCompact } from './compaction.js';
|
|
10
|
-
import { EventChannels, getGlobalEventBus } from './event-bus.js';
|
|
11
5
|
import { buildSystemPrompt } from './prompt-builder.js';
|
|
12
6
|
import { AnthropicProvider } from './providers/anthropic.js';
|
|
13
7
|
import { GoogleProvider } from './providers/google.js';
|
|
14
8
|
import { OllamaProvider } from './providers/ollama.js';
|
|
15
9
|
import { OpenAIProvider } from './providers/openai.js';
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const hash = createHash('sha256').update(absPath, 'utf-8').digest('hex').slice(0, 12);
|
|
22
|
-
const dirName = path.basename(absPath);
|
|
23
|
-
const safeName = dirName.replace(/[<>:"/\\|?*]/g, '_');
|
|
24
|
-
return path.join(getSessionDir(), `${safeName}-${hash}.json`);
|
|
25
|
-
}
|
|
26
|
-
function ensureDir(dir) {
|
|
27
|
-
if (!fs.existsSync(dir)) {
|
|
28
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
// ── Colors ─────────────────────────────────────────────────────────────────
|
|
32
|
-
const CY = '\x1b[36m';
|
|
33
|
-
const GR = '\x1b[32m';
|
|
34
|
-
const YE = '\x1b[33m';
|
|
35
|
-
const RE = '\x1b[31m';
|
|
36
|
-
const MG = '\x1b[35m';
|
|
37
|
-
const GY = '\x1b[90m';
|
|
38
|
-
const RS = '\x1b[0m';
|
|
39
|
-
const BLD = '\x1b[1m';
|
|
40
|
-
const TH = '\x1b[48;2;22;22;32m\x1b[38;2;150;150;170m'; // dark bg + dim fg for thinking
|
|
41
|
-
/** Get terminal width (columns), default to 80. */
|
|
42
|
-
function termWidth() {
|
|
43
|
-
return process.stdout.columns ?? 80;
|
|
44
|
-
}
|
|
45
|
-
/** Visible width of a string (strip ANSI codes). ASCII=1, CJK/non-ASCII=2. */
|
|
46
|
-
function visibleWidth(s) {
|
|
47
|
-
let w = 0;
|
|
48
|
-
for (let i = 0; i < s.length; i++) {
|
|
49
|
-
if (s.charCodeAt(i) === 0x1b) {
|
|
50
|
-
// Skip past escape sequence
|
|
51
|
-
while (i < s.length && s[i] !== 'm')
|
|
52
|
-
i++;
|
|
53
|
-
continue;
|
|
54
|
-
}
|
|
55
|
-
w += s.charCodeAt(i) > 0x7e ? 2 : 1;
|
|
56
|
-
}
|
|
57
|
-
return w;
|
|
58
|
-
}
|
|
59
|
-
/** Visible prefix width for thinking box lines: " │" = 3 */
|
|
60
|
-
const THINK_PREFIX_WIDTH = 3;
|
|
61
|
-
/** Build the top border of the thinking box */
|
|
62
|
-
function thinkTopBorder() {
|
|
63
|
-
return `\n ${GY}╭───────${RS}${TH} Think ${GY}────────────────────${RS}\n`;
|
|
64
|
-
}
|
|
65
|
-
/** Build the bottom border of the thinking box */
|
|
66
|
-
function thinkBottomBorder() {
|
|
67
|
-
return ` ${GY}╰${'─'.repeat(42)}${RS}\n\n`;
|
|
68
|
-
}
|
|
69
|
-
function writeOut(text, output) {
|
|
70
|
-
if (output) {
|
|
71
|
-
output.write(text);
|
|
72
|
-
}
|
|
73
|
-
else {
|
|
74
|
-
process.stdout.write(text);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
function printUserMessage(prompt, output) {
|
|
78
|
-
// When suppressToolOutput is true (Web UI mode), the frontend already
|
|
79
|
-
// displays the user message, so skip sending it as a chunk.
|
|
80
|
-
if (output?.suppressToolOutput)
|
|
81
|
-
return;
|
|
82
|
-
const line = ` ${GY}┃${RS} ${BLD}${CY}You${RS}`;
|
|
83
|
-
writeOut(`\n${line} ${prompt}\n\n`, output);
|
|
84
|
-
}
|
|
85
|
-
function printToolInvocation(tc, output) {
|
|
86
|
-
const detail = formatToolInput(tc);
|
|
87
|
-
const isWrite = tc.name === 'write_plan' || tc.name === 'edit';
|
|
88
|
-
const icon = isWrite ? `${YE}◆${RS}` : `${GR}◇${RS}`;
|
|
89
|
-
const label = isWrite ? `${YE}${tc.name}${RS}` : `${GR}${tc.name}${RS}`;
|
|
90
|
-
writeOut(`\n ${GY}│${RS} ${icon} ${label}${detail ? ` ${GY}${detail}${RS}` : ''}\n`, output);
|
|
91
|
-
}
|
|
92
|
-
function printToolResult(tc, result, output) {
|
|
93
|
-
if (!result.success) {
|
|
94
|
-
writeOut(` ${GY}│${RS} ${RE}✖${RS} ${RE}${result.error}${RS}\n`, output);
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
if (tc.name === 'read') {
|
|
98
|
-
const fileCount = (result.output.match(/^=== /gm) || []).length;
|
|
99
|
-
writeOut(` ${GY}│${RS} ${GR}✔${RS} read ${fileCount} file(s)\n`, output);
|
|
100
|
-
for (const line of result.output.split('\n')) {
|
|
101
|
-
if (line.startsWith('=== ')) {
|
|
102
|
-
const fp = line.slice(4, line.includes(' ===') ? line.indexOf(' ===') + 4 : undefined);
|
|
103
|
-
writeOut(` ${GY}│${RS} ${GY}${fp}${RS}\n`, output);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
else if (tc.name === 'glob') {
|
|
108
|
-
const count = result.output.split('\n').filter(l => l && !l.startsWith('No')).length;
|
|
109
|
-
writeOut(` ${GY}│${RS} ${GR}✔${RS} glob ${count} match(es)\n`, output);
|
|
110
|
-
}
|
|
111
|
-
else if (tc.name === 'grep') {
|
|
112
|
-
const count = result.output.split('\n').filter(l => l && !l.startsWith('No')).length;
|
|
113
|
-
writeOut(` ${GY}│${RS} ${GR}✔${RS} grep ${count} match(es)\n`, output);
|
|
114
|
-
}
|
|
115
|
-
else if (tc.name === 'bash') {
|
|
116
|
-
const outLines = result.output.split('\n');
|
|
117
|
-
const summary = outLines.length > 1 ? `(${outLines.length} lines)` : '';
|
|
118
|
-
writeOut(` ${GY}│${RS} ${GR}✔${RS} bash ${summary}\n`, output);
|
|
119
|
-
}
|
|
120
|
-
else if (tc.name === 'edit') {
|
|
121
|
-
writeOut(` ${GY}│${RS} ${GR}✔${RS} edit\n`, output);
|
|
122
|
-
if (result.output) {
|
|
123
|
-
for (const l of result.output.split('\n')) {
|
|
124
|
-
if (l.trim())
|
|
125
|
-
writeOut(` ${GY}│${RS} ${l.trim()}\n`, output);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
else if (tc.name === 'write_plan') {
|
|
130
|
-
writeOut(` ${GY}│${RS} ${GR}✔${RS} ${result.output || tc.name}\n`, output);
|
|
131
|
-
}
|
|
132
|
-
else if (tc.name === 'search') {
|
|
133
|
-
writeOut(` ${GY}│${RS} ${GR}✔${RS} search: ${String(tc.input.query || '').slice(0, 80)}\n`, output);
|
|
134
|
-
}
|
|
135
|
-
else {
|
|
136
|
-
writeOut(` ${GY}│${RS} ${GR}✔${RS} ${tc.name}\n`, output);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
function isSingleEditShape(v) {
|
|
140
|
-
return (typeof v === 'object' &&
|
|
141
|
-
v !== null &&
|
|
142
|
-
'file_path' in v &&
|
|
143
|
-
'old_string' in v &&
|
|
144
|
-
'new_string' in v);
|
|
145
|
-
}
|
|
146
|
-
export function formatToolInput(tc) {
|
|
147
|
-
const parts = [];
|
|
148
|
-
if (tc.name === 'read' && Array.isArray(tc.input.paths)) {
|
|
149
|
-
parts.push(tc.input.paths.join(', '));
|
|
150
|
-
}
|
|
151
|
-
else if (tc.name === 'glob' && typeof tc.input.pattern === 'string') {
|
|
152
|
-
parts.push(tc.input.pattern);
|
|
153
|
-
}
|
|
154
|
-
else if (tc.name === 'grep') {
|
|
155
|
-
if (typeof tc.input.pattern === 'string')
|
|
156
|
-
parts.push(`/${tc.input.pattern}/`);
|
|
157
|
-
if (typeof tc.input.include === 'string')
|
|
158
|
-
parts.push(`in:${tc.input.include}`);
|
|
159
|
-
}
|
|
160
|
-
else if (tc.name === 'ls') {
|
|
161
|
-
parts.push(typeof tc.input.path === 'string' ? tc.input.path : '.');
|
|
162
|
-
}
|
|
163
|
-
else if (tc.name === 'bash') {
|
|
164
|
-
const cmd = typeof tc.input.command === 'string' ? tc.input.command : '';
|
|
165
|
-
parts.push(cmd.length > 80 ? `${cmd.slice(0, 80)}\u2026` : cmd);
|
|
166
|
-
}
|
|
167
|
-
else if (tc.name === 'search') {
|
|
168
|
-
if (typeof tc.input.query === 'string')
|
|
169
|
-
parts.push(tc.input.query.slice(0, 120));
|
|
170
|
-
}
|
|
171
|
-
else if (tc.name === 'write_plan') {
|
|
172
|
-
if (typeof tc.input.filename === 'string')
|
|
173
|
-
parts.push(tc.input.filename);
|
|
174
|
-
}
|
|
175
|
-
else if (tc.name === 'edit') {
|
|
176
|
-
if (Array.isArray(tc.input.edits) && tc.input.edits.every(isSingleEditShape)) {
|
|
177
|
-
const paths = tc.input.edits.map(e => e.file_path);
|
|
178
|
-
parts.push(paths.join(', '));
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
return parts.join(' \u2502 ');
|
|
182
|
-
}
|
|
183
|
-
function printTokenStats(turnIn, turnOut, totalIn, totalOut, turnApi, totalApi, output, turnCacheHit, turnCacheMiss, totalCacheHit, totalCacheMiss) {
|
|
184
|
-
const bus = getGlobalEventBus();
|
|
185
|
-
bus.emit(EventChannels.TOKEN_STATS, {
|
|
186
|
-
turnIn,
|
|
187
|
-
turnOut,
|
|
188
|
-
totalIn,
|
|
189
|
-
totalOut,
|
|
190
|
-
turnApi,
|
|
191
|
-
totalApi,
|
|
192
|
-
turnCacheHit,
|
|
193
|
-
turnCacheMiss,
|
|
194
|
-
totalCacheHit,
|
|
195
|
-
totalCacheMiss,
|
|
196
|
-
});
|
|
197
|
-
// Skip terminal output in Web UI mode
|
|
198
|
-
if (output?.suppressToolOutput)
|
|
199
|
-
return;
|
|
200
|
-
const total = totalIn + totalOut;
|
|
201
|
-
let msg = ` ${GY}┃${RS} ${GY}${BLD}▴${RS}${GY}${turnIn}${RS} ${GY}${BLD}▾${RS}${GY}${turnOut}${RS} ${GY}total${RS} ${total} ${GY}calls${RS} ${turnApi}(${totalApi})`;
|
|
202
|
-
// Show cache hit rate if available
|
|
203
|
-
const cacheHit = totalCacheHit ?? 0;
|
|
204
|
-
const cacheMiss = totalCacheMiss ?? 0;
|
|
205
|
-
const cacheTotal = cacheHit + cacheMiss;
|
|
206
|
-
if (cacheTotal > 0) {
|
|
207
|
-
const pct = Math.round((cacheHit / cacheTotal) * 100);
|
|
208
|
-
msg += ` ${GY}cached${RS} ${pct}%`;
|
|
209
|
-
}
|
|
210
|
-
writeOut(`\n${msg}\n`, output);
|
|
211
|
-
}
|
|
10
|
+
import { runChat } from './session-chat.js';
|
|
11
|
+
import { ensureDir, findLegacySessionFile, generateId, getSessionDir, getSessionFilePath, getSessionFilesForCwd, migrateSessionData, } from './session-persistence.js';
|
|
12
|
+
import { sanitizeMessages } from './session-utils.js';
|
|
13
|
+
export { formatToolInput } from './session-display.js';
|
|
14
|
+
// ── Session class ────────────────────────────────────────────────────────────
|
|
212
15
|
export class Session {
|
|
213
16
|
messages;
|
|
214
17
|
provider;
|
|
@@ -217,12 +20,9 @@ export class Session {
|
|
|
217
20
|
config;
|
|
218
21
|
output;
|
|
219
22
|
_onPlanWritten;
|
|
220
|
-
/** Set the plan-written callback and propagate to ToolRegistry */
|
|
221
23
|
set onPlanWritten(cb) {
|
|
222
24
|
this._onPlanWritten = cb;
|
|
223
|
-
// Update context so setMode() picks it up too
|
|
224
25
|
this.registry.updateContext({ onPlanWritten: cb });
|
|
225
|
-
// Re-register write_plan tool with the new callback
|
|
226
26
|
if (this.registry.has('write_plan')) {
|
|
227
27
|
this.registry.reRegisterWritePlan(this.config.cwd, cb);
|
|
228
28
|
}
|
|
@@ -265,23 +65,34 @@ export class Session {
|
|
|
265
65
|
else {
|
|
266
66
|
this.provider = new AnthropicProvider(config.apiKey, config.baseUrl, config.model);
|
|
267
67
|
}
|
|
268
|
-
// Placeholder until initSystemPrompt() is called
|
|
269
68
|
this.messages = [{ role: 'system', content: '' }];
|
|
270
|
-
// Initialize system prompt asynchronously
|
|
271
69
|
this.initSystemPrompt(config);
|
|
272
70
|
}
|
|
273
|
-
/** Initialize the system prompt asynchronously */
|
|
274
71
|
initSystemPrompt(config) {
|
|
275
|
-
buildSystemPrompt(config).then(prompt => {
|
|
276
|
-
this.messages
|
|
72
|
+
buildSystemPrompt(config, this.registry.getDefinitions()).then(prompt => {
|
|
73
|
+
if (this.messages.length <= 1) {
|
|
74
|
+
this.messages = [{ role: 'system', content: prompt }];
|
|
75
|
+
}
|
|
277
76
|
});
|
|
278
77
|
}
|
|
279
|
-
|
|
78
|
+
sessionId = generateId();
|
|
79
|
+
sessionTitle = '';
|
|
80
|
+
sessionCreatedAt = new Date().toISOString();
|
|
280
81
|
save() {
|
|
281
82
|
const dir = getSessionDir();
|
|
282
83
|
ensureDir(dir);
|
|
283
|
-
const filePath = getSessionFilePath(this.config.cwd);
|
|
84
|
+
const filePath = getSessionFilePath(this.config.cwd, this.sessionId);
|
|
85
|
+
const now = new Date().toISOString();
|
|
86
|
+
let title = this.sessionTitle;
|
|
87
|
+
if (!title) {
|
|
88
|
+
const firstUserMsg = this.messages.find(m => m.role === 'user');
|
|
89
|
+
if (firstUserMsg && typeof firstUserMsg.content === 'string') {
|
|
90
|
+
title = firstUserMsg.content.slice(0, 80).replace(/\n/g, ' ');
|
|
91
|
+
}
|
|
92
|
+
}
|
|
284
93
|
const data = {
|
|
94
|
+
id: this.sessionId,
|
|
95
|
+
title: title || undefined,
|
|
285
96
|
cwd: path.resolve(this.config.cwd),
|
|
286
97
|
messages: this.messages,
|
|
287
98
|
totalInputTokens: this.totalInputTokens,
|
|
@@ -292,38 +103,90 @@ export class Session {
|
|
|
292
103
|
mode: this.config.mode,
|
|
293
104
|
model: this.config.model,
|
|
294
105
|
provider: this.config.provider,
|
|
295
|
-
|
|
106
|
+
createdAt: this.sessionCreatedAt,
|
|
107
|
+
updatedAt: now,
|
|
296
108
|
};
|
|
297
109
|
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
|
|
298
110
|
}
|
|
299
|
-
/** Try to load a saved session for the given cwd. Returns null if none exists. */
|
|
300
111
|
static async load(config, output) {
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
112
|
+
const cwdSessions = getSessionFilesForCwd(config.cwd);
|
|
113
|
+
console.log(`[session] Found ${cwdSessions.length} session files for cwd "${config.cwd}":`, cwdSessions
|
|
114
|
+
.map(f => `${f.fileName} (updatedAt=${f.data.updatedAt?.slice(0, 19)}, messages=${f.data.messages?.length || 0})`)
|
|
115
|
+
.join(', '));
|
|
116
|
+
if (cwdSessions.length > 0) {
|
|
117
|
+
cwdSessions.sort((a, b) => {
|
|
118
|
+
const aLen = a.data.messages?.length || 0;
|
|
119
|
+
const bLen = b.data.messages?.length || 0;
|
|
120
|
+
if (aLen !== bLen)
|
|
121
|
+
return bLen - aLen;
|
|
122
|
+
return b.data.updatedAt.localeCompare(a.data.updatedAt);
|
|
123
|
+
});
|
|
124
|
+
console.log(`[session] Loading session file: ${cwdSessions[0].fileName}`);
|
|
125
|
+
return Session.loadFromData(cwdSessions[0].data, config, output);
|
|
305
126
|
}
|
|
306
|
-
|
|
127
|
+
const legacyPath = findLegacySessionFile(config.cwd);
|
|
128
|
+
if (legacyPath) {
|
|
129
|
+
try {
|
|
130
|
+
const raw = JSON.parse(fs.readFileSync(legacyPath, 'utf-8'));
|
|
131
|
+
const data = migrateSessionData(raw);
|
|
132
|
+
const session = await Session.loadFromData(data, config, output);
|
|
133
|
+
session.save();
|
|
134
|
+
try {
|
|
135
|
+
fs.unlinkSync(legacyPath);
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
/* ignore */
|
|
139
|
+
}
|
|
140
|
+
return session;
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
static async loadById(sessionId, config, output) {
|
|
149
|
+
const allSessions = Session.listSessions();
|
|
150
|
+
const target = allSessions.find(s => s.id === sessionId);
|
|
151
|
+
if (!target)
|
|
307
152
|
return null;
|
|
153
|
+
const legacyPath = findLegacySessionFile(config.cwd);
|
|
154
|
+
if (legacyPath) {
|
|
155
|
+
try {
|
|
156
|
+
const raw = JSON.parse(fs.readFileSync(legacyPath, 'utf-8'));
|
|
157
|
+
const data = migrateSessionData(raw);
|
|
158
|
+
if (data.id === sessionId) {
|
|
159
|
+
return Session.loadFromData(data, config, output);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
/* ignore */
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const filePath = getSessionFilePath(target.cwd, sessionId);
|
|
167
|
+
try {
|
|
168
|
+
const raw = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
169
|
+
return Session.loadFromData(migrateSessionData(raw), config, output);
|
|
308
170
|
}
|
|
309
|
-
|
|
310
|
-
const savedAbs = path.resolve(data.cwd);
|
|
311
|
-
const currentAbs = path.resolve(config.cwd);
|
|
312
|
-
if (savedAbs !== currentAbs) {
|
|
171
|
+
catch {
|
|
313
172
|
return null;
|
|
314
173
|
}
|
|
174
|
+
}
|
|
175
|
+
static async loadFromData(data, config, output) {
|
|
315
176
|
const session = new Session(config, output);
|
|
316
|
-
// Restore messages (replace the default system prompt with the saved one)
|
|
317
177
|
session.messages = data.messages;
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
178
|
+
console.log(`[session] Loading session ${data.id}: ${data.messages.length} messages from disk`);
|
|
179
|
+
session.messages = sanitizeMessages(session.messages);
|
|
180
|
+
console.log(`[session] After sanitize: ${session.messages.length} messages (removed ${data.messages.length - session.messages.length})`);
|
|
181
|
+
session.sessionId = data.id;
|
|
182
|
+
session.sessionTitle = data.title || '';
|
|
183
|
+
session.sessionCreatedAt = data.createdAt;
|
|
184
|
+
config.mode = data.mode;
|
|
185
|
+
session.registry.setMode(data.mode);
|
|
186
|
+
if (data.model !== config.model || data.provider !== config.provider) {
|
|
187
|
+
const prompt = await buildSystemPrompt(config, session.registry.getDefinitions());
|
|
324
188
|
session.messages[0] = { role: 'system', content: prompt };
|
|
325
189
|
}
|
|
326
|
-
// Restore token stats
|
|
327
190
|
session.totalInputTokens = data.totalInputTokens;
|
|
328
191
|
session.totalOutputTokens = data.totalOutputTokens;
|
|
329
192
|
session.totalApiCalls = data.totalApiCalls;
|
|
@@ -331,406 +194,161 @@ export class Session {
|
|
|
331
194
|
session.totalCacheMissTokens = data.totalCacheMissTokens ?? 0;
|
|
332
195
|
return session;
|
|
333
196
|
}
|
|
334
|
-
/** Remove the saved session file for the given cwd. */
|
|
335
197
|
static clearSavedSession(cwd) {
|
|
336
|
-
const
|
|
198
|
+
const cwdSessions = getSessionFilesForCwd(cwd);
|
|
199
|
+
for (const { fileName } of cwdSessions) {
|
|
200
|
+
try {
|
|
201
|
+
fs.unlinkSync(path.join(getSessionDir(), fileName));
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
/* ignore */
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const legacyPath = findLegacySessionFile(cwd);
|
|
208
|
+
if (legacyPath) {
|
|
209
|
+
try {
|
|
210
|
+
fs.unlinkSync(legacyPath);
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
/* ignore */
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
static listSessions(maxCount) {
|
|
218
|
+
const dir = getSessionDir();
|
|
219
|
+
try {
|
|
220
|
+
if (!fs.existsSync(dir))
|
|
221
|
+
return [];
|
|
222
|
+
const files = fs.readdirSync(dir).filter(f => f.endsWith('.json'));
|
|
223
|
+
const sessions = [];
|
|
224
|
+
for (const fileName of files) {
|
|
225
|
+
try {
|
|
226
|
+
const raw = JSON.parse(fs.readFileSync(path.join(dir, fileName), 'utf-8'));
|
|
227
|
+
const data = migrateSessionData(raw);
|
|
228
|
+
const firstUserMsg = data.messages.find(m => m.role === 'user');
|
|
229
|
+
let title = data.title || '';
|
|
230
|
+
if (!title && firstUserMsg && typeof firstUserMsg.content === 'string') {
|
|
231
|
+
title = firstUserMsg.content.slice(0, 80).replace(/\n/g, ' ');
|
|
232
|
+
}
|
|
233
|
+
sessions.push({
|
|
234
|
+
id: data.id,
|
|
235
|
+
cwd: data.cwd,
|
|
236
|
+
title,
|
|
237
|
+
messageCount: data.messages.length,
|
|
238
|
+
mode: data.mode,
|
|
239
|
+
model: data.model,
|
|
240
|
+
provider: data.provider,
|
|
241
|
+
totalInputTokens: data.totalInputTokens,
|
|
242
|
+
totalOutputTokens: data.totalOutputTokens,
|
|
243
|
+
totalApiCalls: data.totalApiCalls,
|
|
244
|
+
createdAt: data.createdAt,
|
|
245
|
+
updatedAt: data.updatedAt,
|
|
246
|
+
fileName,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
// Skip corrupted files
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
sessions.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
254
|
+
if (maxCount && maxCount > 0) {
|
|
255
|
+
return sessions.slice(0, maxCount);
|
|
256
|
+
}
|
|
257
|
+
return sessions;
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
return [];
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
static deleteSession(id) {
|
|
264
|
+
const sessions = Session.listSessions();
|
|
265
|
+
const target = sessions.find(s => s.id === id);
|
|
266
|
+
if (!target)
|
|
267
|
+
return false;
|
|
337
268
|
try {
|
|
338
|
-
fs.unlinkSync(
|
|
269
|
+
fs.unlinkSync(path.join(getSessionDir(), target.fileName));
|
|
270
|
+
return true;
|
|
339
271
|
}
|
|
340
272
|
catch {
|
|
341
|
-
|
|
273
|
+
return false;
|
|
342
274
|
}
|
|
343
275
|
}
|
|
276
|
+
clearSavedSession() {
|
|
277
|
+
Session.clearSavedSession(this.config.cwd);
|
|
278
|
+
}
|
|
279
|
+
fork() {
|
|
280
|
+
const forked = new Session(this.config, this.output);
|
|
281
|
+
forked.messages = [...this.messages];
|
|
282
|
+
forked.onPlanWritten = this.onPlanWritten;
|
|
283
|
+
const baseTitle = this.sessionTitle || 'forked session';
|
|
284
|
+
forked.sessionTitle = `${baseTitle} (fork)`;
|
|
285
|
+
forked.sessionId = generateId();
|
|
286
|
+
forked.sessionCreatedAt = new Date().toISOString();
|
|
287
|
+
forked.save();
|
|
288
|
+
return forked;
|
|
289
|
+
}
|
|
290
|
+
exportSession(filePath) {
|
|
291
|
+
const dir = filePath || path.join(getSessionDir(), `export-${this.sessionId}.json`);
|
|
292
|
+
const now = new Date().toISOString();
|
|
293
|
+
let title = this.sessionTitle;
|
|
294
|
+
if (!title) {
|
|
295
|
+
const firstUserMsg = this.messages.find(m => m.role === 'user');
|
|
296
|
+
if (firstUserMsg && typeof firstUserMsg.content === 'string') {
|
|
297
|
+
title = firstUserMsg.content.slice(0, 80).replace(/\n/g, ' ');
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
const exportData = {
|
|
301
|
+
id: this.sessionId,
|
|
302
|
+
title: title || '(untitled)',
|
|
303
|
+
cwd: path.resolve(this.config.cwd),
|
|
304
|
+
mode: this.config.mode,
|
|
305
|
+
model: this.config.model,
|
|
306
|
+
provider: this.config.provider,
|
|
307
|
+
totalInputTokens: this.totalInputTokens,
|
|
308
|
+
totalOutputTokens: this.totalOutputTokens,
|
|
309
|
+
totalApiCalls: this.totalApiCalls,
|
|
310
|
+
totalCacheHitTokens: this.totalCacheHitTokens,
|
|
311
|
+
totalCacheMissTokens: this.totalCacheMissTokens,
|
|
312
|
+
createdAt: this.sessionCreatedAt,
|
|
313
|
+
exportedAt: now,
|
|
314
|
+
messages: this.messages.map(m => ({
|
|
315
|
+
role: m.role,
|
|
316
|
+
content: typeof m.content === 'string' ? m.content : null,
|
|
317
|
+
tool_calls: m.tool_calls
|
|
318
|
+
? m.tool_calls.map(tc => ({
|
|
319
|
+
id: tc.id,
|
|
320
|
+
name: tc.name,
|
|
321
|
+
input: tc.input,
|
|
322
|
+
}))
|
|
323
|
+
: undefined,
|
|
324
|
+
reasoning_content: m.reasoning_content,
|
|
325
|
+
})),
|
|
326
|
+
};
|
|
327
|
+
const fullPath = path.resolve(dir);
|
|
328
|
+
ensureDir(path.dirname(fullPath));
|
|
329
|
+
fs.writeFileSync(fullPath, JSON.stringify(exportData, null, 2), 'utf-8');
|
|
330
|
+
return fullPath;
|
|
331
|
+
}
|
|
344
332
|
async setMode(mode) {
|
|
345
333
|
this.config.mode = mode;
|
|
346
|
-
const prompt = await buildSystemPrompt(this.config);
|
|
347
|
-
this.messages[0] = { role: 'system', content: prompt };
|
|
348
334
|
this.registry.setMode(mode);
|
|
335
|
+
const prompt = await buildSystemPrompt(this.config, this.registry.getDefinitions());
|
|
336
|
+
this.messages[0] = { role: 'system', content: prompt };
|
|
349
337
|
this.save();
|
|
350
338
|
}
|
|
351
|
-
/** Stop the current conversation gracefully */
|
|
352
339
|
stop() {
|
|
353
340
|
this.stopped = true;
|
|
354
|
-
// Abort any in-flight LLM stream to stop token consumption immediately
|
|
355
341
|
this.abortController?.abort();
|
|
356
342
|
}
|
|
357
|
-
/** Check if the session was stopped */
|
|
358
343
|
isStopped() {
|
|
359
344
|
return this.stopped;
|
|
360
345
|
}
|
|
361
|
-
/** Reset the stopped flag for a new conversation */
|
|
362
346
|
resetStopped() {
|
|
363
347
|
this.stopped = false;
|
|
364
|
-
// Create new AbortController for next conversation
|
|
365
348
|
this.abortController = new AbortController();
|
|
366
349
|
}
|
|
367
350
|
async chat(userPrompt) {
|
|
368
|
-
|
|
369
|
-
const out = this.output;
|
|
370
|
-
printUserMessage(userPrompt, out);
|
|
371
|
-
this.messages.push({ role: 'user', content: userPrompt });
|
|
372
|
-
// Save immediately after user message so page refresh doesn't lose it
|
|
373
|
-
this.save();
|
|
374
|
-
// Reset per-turn counters
|
|
375
|
-
this.turnInputTokens = 0;
|
|
376
|
-
this.turnOutputTokens = 0;
|
|
377
|
-
this.turnApiCalls = 0;
|
|
378
|
-
this.turnCacheHitTokens = 0;
|
|
379
|
-
this.turnCacheMissTokens = 0;
|
|
380
|
-
let iterations = 0;
|
|
381
|
-
const maxIterations = 30;
|
|
382
|
-
// Reset stopped flag for new conversation
|
|
383
|
-
this.resetStopped();
|
|
384
|
-
// Create a new AbortController for this chat invocation
|
|
385
|
-
// (a new controller is needed each time because abort() is one-shot)
|
|
386
|
-
this.abortController = new AbortController();
|
|
387
|
-
// Declare toolCalls outside the loop so we can reference it in stop check
|
|
388
|
-
let toolCalls = [];
|
|
389
|
-
while (iterations < maxIterations) {
|
|
390
|
-
// Check if stop was requested
|
|
391
|
-
if (this.isStopped()) {
|
|
392
|
-
bus.emit(EventChannels.TURN_END, { iterations, toolCallCount: toolCalls.length });
|
|
393
|
-
this.save();
|
|
394
|
-
return;
|
|
395
|
-
}
|
|
396
|
-
iterations++;
|
|
397
|
-
this.turnApiCalls++;
|
|
398
|
-
this.totalApiCalls++;
|
|
399
|
-
toolCalls = [];
|
|
400
|
-
let fullResponse = '';
|
|
401
|
-
let reasoningContent;
|
|
402
|
-
let reasoningOutput = false;
|
|
403
|
-
let reasoningLineStart = false;
|
|
404
|
-
bus.emit(EventChannels.TURN_START, { prompt: userPrompt, iteration: iterations });
|
|
405
|
-
bus.emit(EventChannels.LLM_STREAM_START, { iteration: iterations });
|
|
406
|
-
const stream = this.provider.chat(this.messages, this.registry.getDefinitions(), this.abortController.signal);
|
|
407
|
-
try {
|
|
408
|
-
for await (const chunk of stream) {
|
|
409
|
-
if (chunk.reasoning_content) {
|
|
410
|
-
reasoningContent = chunk.reasoning_content;
|
|
411
|
-
// Stream reasoning content in real-time (only when no text in same chunk)
|
|
412
|
-
if (!chunk.text) {
|
|
413
|
-
// Emit thinking via EventBus for Web UI
|
|
414
|
-
bus.emit(EventChannels.THINKING, { text: chunk.reasoning_content });
|
|
415
|
-
if (!reasoningOutput) {
|
|
416
|
-
reasoningOutput = true;
|
|
417
|
-
reasoningLineStart = true;
|
|
418
|
-
if (!out?.suppressToolOutput) {
|
|
419
|
-
writeOut(thinkTopBorder(), out);
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
// Terminal display with box drawing (skip in Web UI mode, handled by EventBus)
|
|
423
|
-
if (!out?.suppressToolOutput) {
|
|
424
|
-
// Track column position on current line for wrapping
|
|
425
|
-
let thinkCol = 0;
|
|
426
|
-
// Handle newlines in streamed content - add left border on each new line
|
|
427
|
-
// Also manually wrap long lines so wrapped lines keep the │ prefix.
|
|
428
|
-
let remaining = chunk.reasoning_content;
|
|
429
|
-
const maxContentWidth = termWidth() - THINK_PREFIX_WIDTH;
|
|
430
|
-
while (remaining.length > 0) {
|
|
431
|
-
if (reasoningLineStart) {
|
|
432
|
-
writeOut(` ${GY}│${RS}${TH}`, out);
|
|
433
|
-
reasoningLineStart = false;
|
|
434
|
-
thinkCol = 0;
|
|
435
|
-
}
|
|
436
|
-
const nlIdx = remaining.indexOf('\n');
|
|
437
|
-
if (nlIdx === -1) {
|
|
438
|
-
// No newline — write as much as fits on current line, wrap if needed
|
|
439
|
-
while (remaining.length > 0) {
|
|
440
|
-
const segWidth = visibleWidth(remaining);
|
|
441
|
-
const avail = maxContentWidth - thinkCol;
|
|
442
|
-
if (segWidth <= avail) {
|
|
443
|
-
// Fits entirely on current line
|
|
444
|
-
writeOut(remaining, out);
|
|
445
|
-
thinkCol += segWidth;
|
|
446
|
-
remaining = '';
|
|
447
|
-
}
|
|
448
|
-
else if (avail <= 0) {
|
|
449
|
-
// Current line is full, wrap to next
|
|
450
|
-
writeOut(`${RS}\n`, out);
|
|
451
|
-
writeOut(` ${GY}│${RS}${TH}`, out);
|
|
452
|
-
thinkCol = 0;
|
|
453
|
-
}
|
|
454
|
-
else {
|
|
455
|
-
// Write first part that fits, then wrap
|
|
456
|
-
// Find character boundary that fits within avail
|
|
457
|
-
let cut = avail;
|
|
458
|
-
while (cut > 0 && visibleWidth(remaining.slice(0, cut)) > avail)
|
|
459
|
-
cut--;
|
|
460
|
-
if (cut <= 0)
|
|
461
|
-
cut = 1;
|
|
462
|
-
writeOut(remaining.slice(0, cut), out);
|
|
463
|
-
writeOut(`${RS}\n`, out);
|
|
464
|
-
writeOut(` ${GY}│${RS}${TH}`, out);
|
|
465
|
-
thinkCol = 0;
|
|
466
|
-
remaining = remaining.slice(cut);
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
else {
|
|
471
|
-
// Has newline — process the segment up to newline
|
|
472
|
-
const segment = remaining.slice(0, nlIdx);
|
|
473
|
-
const segWidth = visibleWidth(segment);
|
|
474
|
-
const avail = maxContentWidth - thinkCol;
|
|
475
|
-
if (segWidth <= avail) {
|
|
476
|
-
// Segment fits on current line
|
|
477
|
-
writeOut(segment, out);
|
|
478
|
-
writeOut(`${RS}\n`, out);
|
|
479
|
-
reasoningLineStart = true;
|
|
480
|
-
thinkCol = 0;
|
|
481
|
-
}
|
|
482
|
-
else {
|
|
483
|
-
// Segment too long — write what fits, wrap, then rest
|
|
484
|
-
let rest = segment;
|
|
485
|
-
// Write remainder of current line
|
|
486
|
-
if (avail > 0) {
|
|
487
|
-
let cut = avail;
|
|
488
|
-
while (cut > 0 && visibleWidth(rest.slice(0, cut)) > avail)
|
|
489
|
-
cut--;
|
|
490
|
-
if (cut <= 0)
|
|
491
|
-
cut = 1;
|
|
492
|
-
writeOut(rest.slice(0, cut), out);
|
|
493
|
-
rest = rest.slice(cut);
|
|
494
|
-
}
|
|
495
|
-
writeOut(`${RS}\n`, out);
|
|
496
|
-
reasoningLineStart = true;
|
|
497
|
-
thinkCol = 0;
|
|
498
|
-
// Write rest of segment on continuation line(s)
|
|
499
|
-
if (rest.length > 0) {
|
|
500
|
-
writeOut(` ${GY}│${RS}${TH}`, out);
|
|
501
|
-
reasoningLineStart = false;
|
|
502
|
-
while (rest.length > 0) {
|
|
503
|
-
const rw = visibleWidth(rest);
|
|
504
|
-
if (rw <= maxContentWidth) {
|
|
505
|
-
writeOut(rest, out);
|
|
506
|
-
thinkCol = rw;
|
|
507
|
-
rest = '';
|
|
508
|
-
}
|
|
509
|
-
else {
|
|
510
|
-
let cut = maxContentWidth;
|
|
511
|
-
while (cut > 0 && visibleWidth(rest.slice(0, cut)) > maxContentWidth)
|
|
512
|
-
cut--;
|
|
513
|
-
if (cut <= 0)
|
|
514
|
-
cut = 1;
|
|
515
|
-
writeOut(rest.slice(0, cut), out);
|
|
516
|
-
writeOut(`${RS}\n`, out);
|
|
517
|
-
writeOut(` ${GY}│${RS}${TH}`, out);
|
|
518
|
-
rest = rest.slice(cut);
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
writeOut(`${RS}\n`, out);
|
|
523
|
-
reasoningLineStart = true;
|
|
524
|
-
thinkCol = 0;
|
|
525
|
-
}
|
|
526
|
-
remaining = remaining.slice(nlIdx + 1);
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
if (chunk.type === 'text' && chunk.text) {
|
|
533
|
-
if (reasoningOutput) {
|
|
534
|
-
bus.emit(EventChannels.THINKING_END, {});
|
|
535
|
-
if (!out?.suppressToolOutput) {
|
|
536
|
-
writeOut(`${RS}\n`, out);
|
|
537
|
-
writeOut(thinkBottomBorder(), out);
|
|
538
|
-
}
|
|
539
|
-
reasoningOutput = false;
|
|
540
|
-
reasoningLineStart = false;
|
|
541
|
-
}
|
|
542
|
-
fullResponse += chunk.text;
|
|
543
|
-
writeOut(chunk.text, out);
|
|
544
|
-
}
|
|
545
|
-
else if (chunk.type === 'tool_use' && chunk.tool_call) {
|
|
546
|
-
toolCalls.push(chunk.tool_call);
|
|
547
|
-
}
|
|
548
|
-
else if (chunk.type === 'complete') {
|
|
549
|
-
if (chunk.usage) {
|
|
550
|
-
this.turnInputTokens += chunk.usage.input_tokens;
|
|
551
|
-
this.turnOutputTokens += chunk.usage.output_tokens;
|
|
552
|
-
this.totalInputTokens += chunk.usage.input_tokens;
|
|
553
|
-
this.totalOutputTokens += chunk.usage.output_tokens;
|
|
554
|
-
// Accumulate DeepSeek cache hit/miss tokens
|
|
555
|
-
if (chunk.usage.prompt_cache_hit_tokens != null) {
|
|
556
|
-
this.turnCacheHitTokens += chunk.usage.prompt_cache_hit_tokens;
|
|
557
|
-
this.totalCacheHitTokens += chunk.usage.prompt_cache_hit_tokens;
|
|
558
|
-
}
|
|
559
|
-
if (chunk.usage.prompt_cache_miss_tokens != null) {
|
|
560
|
-
this.turnCacheMissTokens += chunk.usage.prompt_cache_miss_tokens;
|
|
561
|
-
this.totalCacheMissTokens += chunk.usage.prompt_cache_miss_tokens;
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
if (chunk.finish_reason === 'stop' || chunk.finish_reason === 'end_turn') {
|
|
565
|
-
if (toolCalls.length === 0) {
|
|
566
|
-
const finalAssistantMsg = {
|
|
567
|
-
role: 'assistant',
|
|
568
|
-
content: fullResponse || null,
|
|
569
|
-
reasoning_content: reasoningContent,
|
|
570
|
-
};
|
|
571
|
-
this.messages.push(finalAssistantMsg);
|
|
572
|
-
printTokenStats(this.turnInputTokens, this.turnOutputTokens, this.totalInputTokens, this.totalOutputTokens, this.turnApiCalls, this.totalApiCalls, out, this.turnCacheHitTokens, this.turnCacheMissTokens, this.totalCacheHitTokens, this.totalCacheMissTokens);
|
|
573
|
-
writeOut('\n\n', out);
|
|
574
|
-
saveTokenUsage(this.config.cwd, this.turnInputTokens, this.turnOutputTokens, this.turnApiCalls);
|
|
575
|
-
bus.emit(EventChannels.TURN_END, { iterations, toolCallCount: 0 });
|
|
576
|
-
this.save();
|
|
577
|
-
return;
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
catch (e) {
|
|
584
|
-
const errMsg = fmtErr(e);
|
|
585
|
-
const partialContent = fullResponse ? fullResponse.slice(0, 500) : '(empty)';
|
|
586
|
-
if (!out?.suppressToolOutput) {
|
|
587
|
-
writeOut(`\n${RE}Stream error:${RS} ${errMsg}`, out);
|
|
588
|
-
writeOut(`\n ${GY}┃${RS} Partial response: ${partialContent}\n`, out);
|
|
589
|
-
}
|
|
590
|
-
console.error('[session] Stream error:', errMsg, '| Partial response:', partialContent);
|
|
591
|
-
if (reasoningOutput) {
|
|
592
|
-
bus.emit(EventChannels.THINKING_END, {});
|
|
593
|
-
if (!out?.suppressToolOutput) {
|
|
594
|
-
writeOut(`${RS}\n`, out);
|
|
595
|
-
writeOut(thinkBottomBorder(), out);
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
bus.emit(EventChannels.LLM_STREAM_END, { iteration: iterations, toolCallCount: 0 });
|
|
599
|
-
bus.emit(EventChannels.TURN_END, { iterations, toolCallCount: 0 });
|
|
600
|
-
// If there are pending toolCalls that weren't executed due to abort,
|
|
601
|
-
// add an assistant message so the model knows they weren't executed
|
|
602
|
-
if (toolCalls.length > 0) {
|
|
603
|
-
const interruptedMsg = {
|
|
604
|
-
role: 'assistant',
|
|
605
|
-
content: null,
|
|
606
|
-
tool_calls: toolCalls,
|
|
607
|
-
reasoning_content: reasoningContent,
|
|
608
|
-
};
|
|
609
|
-
this.messages.push(interruptedMsg);
|
|
610
|
-
}
|
|
611
|
-
saveTokenUsage(this.config.cwd, this.turnInputTokens, this.turnOutputTokens, this.turnApiCalls);
|
|
612
|
-
this.save();
|
|
613
|
-
return;
|
|
614
|
-
}
|
|
615
|
-
bus.emit(EventChannels.LLM_STREAM_END, {
|
|
616
|
-
iteration: iterations,
|
|
617
|
-
toolCallCount: toolCalls.length,
|
|
618
|
-
});
|
|
619
|
-
// Close reasoning display if still open (model ended with tool calls, no text)
|
|
620
|
-
if (reasoningOutput) {
|
|
621
|
-
bus.emit(EventChannels.THINKING_END, {});
|
|
622
|
-
if (!out?.suppressToolOutput) {
|
|
623
|
-
writeOut(`${RS}\n`, out);
|
|
624
|
-
writeOut(thinkBottomBorder(), out);
|
|
625
|
-
}
|
|
626
|
-
reasoningOutput = false;
|
|
627
|
-
reasoningLineStart = false;
|
|
628
|
-
}
|
|
629
|
-
if (toolCalls.length === 0) {
|
|
630
|
-
if (fullResponse) {
|
|
631
|
-
const finalAssistantMsg = {
|
|
632
|
-
role: 'assistant',
|
|
633
|
-
content: fullResponse,
|
|
634
|
-
reasoning_content: reasoningContent,
|
|
635
|
-
};
|
|
636
|
-
this.messages.push(finalAssistantMsg);
|
|
637
|
-
printTokenStats(this.turnInputTokens, this.turnOutputTokens, this.totalInputTokens, this.totalOutputTokens, this.turnApiCalls, this.totalApiCalls, out, this.turnCacheHitTokens, this.turnCacheMissTokens, this.totalCacheHitTokens, this.totalCacheMissTokens);
|
|
638
|
-
writeOut('\n\n', out);
|
|
639
|
-
}
|
|
640
|
-
saveTokenUsage(this.config.cwd, this.turnInputTokens, this.turnOutputTokens, this.turnApiCalls);
|
|
641
|
-
bus.emit(EventChannels.TURN_END, { iterations, toolCallCount: 0 });
|
|
642
|
-
this.save();
|
|
643
|
-
return;
|
|
644
|
-
}
|
|
645
|
-
const assistantMsg = {
|
|
646
|
-
role: 'assistant',
|
|
647
|
-
content: fullResponse || null,
|
|
648
|
-
tool_calls: toolCalls,
|
|
649
|
-
reasoning_content: reasoningContent,
|
|
650
|
-
};
|
|
651
|
-
this.messages.push(assistantMsg);
|
|
652
|
-
this.save();
|
|
653
|
-
// ── User confirmation for write-type tool calls ──
|
|
654
|
-
if (!this.config.autoApprove && this.output?.confirmTool && toolCalls.length > 0) {
|
|
655
|
-
const writeTools = ['edit', 'bash', 'write_plan', 'install_skill'];
|
|
656
|
-
const needsConfirm = toolCalls.filter(tc => writeTools.includes(tc.name));
|
|
657
|
-
if (needsConfirm.length > 0) {
|
|
658
|
-
const approved = await this.output.confirmTool(needsConfirm);
|
|
659
|
-
if (!approved) {
|
|
660
|
-
const rejectMsg = {
|
|
661
|
-
role: 'tool',
|
|
662
|
-
content: 'USER_REJECTED: The user declined to execute the requested tool calls. Try a different approach.',
|
|
663
|
-
tool_call_id: needsConfirm[0].id,
|
|
664
|
-
name: 'user_feedback',
|
|
665
|
-
};
|
|
666
|
-
this.messages.push(rejectMsg);
|
|
667
|
-
bus.emit(EventChannels.TURN_END, { iterations, toolCallCount: toolCalls.length });
|
|
668
|
-
continue;
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
for (let i = 0; i < toolCalls.length; i++) {
|
|
673
|
-
const tc = toolCalls[i];
|
|
674
|
-
// Check if stop was requested after each tool call
|
|
675
|
-
if (this.isStopped()) {
|
|
676
|
-
// Add assistant message with remaining tool_calls so model knows they weren't executed
|
|
677
|
-
const remainingToolCalls = toolCalls.slice(i);
|
|
678
|
-
if (remainingToolCalls.length > 0) {
|
|
679
|
-
const interruptedMsg = {
|
|
680
|
-
role: 'assistant',
|
|
681
|
-
content: null,
|
|
682
|
-
tool_calls: remainingToolCalls,
|
|
683
|
-
};
|
|
684
|
-
this.messages.push(interruptedMsg);
|
|
685
|
-
}
|
|
686
|
-
bus.emit(EventChannels.TURN_END, { iterations, toolCallCount: toolCalls.length });
|
|
687
|
-
this.save();
|
|
688
|
-
return;
|
|
689
|
-
}
|
|
690
|
-
bus.emit(EventChannels.TOOL_CALL, { name: tc.name, input: tc.input, id: tc.id });
|
|
691
|
-
if (!out?.suppressToolOutput) {
|
|
692
|
-
printToolInvocation(tc, out);
|
|
693
|
-
}
|
|
694
|
-
const result = await this.registry.dispatch(tc);
|
|
695
|
-
if (result.success) {
|
|
696
|
-
bus.emit(EventChannels.TOOL_RESULT, { name: tc.name, id: tc.id, output: result.output });
|
|
697
|
-
}
|
|
698
|
-
else {
|
|
699
|
-
bus.emit(EventChannels.TOOL_ERROR, { name: tc.name, id: tc.id, error: result.error });
|
|
700
|
-
}
|
|
701
|
-
if (!out?.suppressToolOutput) {
|
|
702
|
-
printToolResult(tc, result, out);
|
|
703
|
-
}
|
|
704
|
-
const resultMsg = {
|
|
705
|
-
role: 'tool',
|
|
706
|
-
content: result.success ? result.output : `ERROR: ${result.error}`,
|
|
707
|
-
tool_call_id: tc.id,
|
|
708
|
-
name: tc.name,
|
|
709
|
-
};
|
|
710
|
-
this.messages.push(resultMsg);
|
|
711
|
-
}
|
|
712
|
-
this.save();
|
|
713
|
-
// End the current turn before next iteration — frontend creates a new message
|
|
714
|
-
bus.emit(EventChannels.TURN_END, { iterations, toolCallCount: toolCalls.length });
|
|
715
|
-
// Check if compaction is needed
|
|
716
|
-
if (shouldCompact(this.messages, this.config.contextWindow)) {
|
|
717
|
-
const before = this.messages.length;
|
|
718
|
-
const result = compact(this.messages, this.config.contextWindow);
|
|
719
|
-
if (result.compressed) {
|
|
720
|
-
this.messages = result.messages;
|
|
721
|
-
bus.emit(EventChannels.COMPACTION_TRIGGERED, { before, after: result.newCount });
|
|
722
|
-
if (out && !out.suppressToolOutput) {
|
|
723
|
-
out.write(`\n ${GY}┃${RS} ${GY}📦 Compressed context: ${before} → ${result.newCount} messages${RS}\n`);
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
if (iterations >= maxIterations) {
|
|
729
|
-
printTokenStats(this.turnInputTokens, this.turnOutputTokens, this.totalInputTokens, this.totalOutputTokens, this.turnApiCalls, this.totalApiCalls, out, this.turnCacheHitTokens, this.turnCacheMissTokens, this.totalCacheHitTokens, this.totalCacheMissTokens);
|
|
730
|
-
writeOut('\nAgent reached maximum iterations. Stopping.\n', out);
|
|
731
|
-
}
|
|
732
|
-
saveTokenUsage(this.config.cwd, this.turnInputTokens, this.turnOutputTokens, this.turnApiCalls);
|
|
733
|
-
this.save();
|
|
351
|
+
return runChat(this, userPrompt);
|
|
734
352
|
}
|
|
735
353
|
}
|
|
736
354
|
//# sourceMappingURL=session.js.map
|