lonny-agent 0.2.4 → 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/README.md +326 -326
- 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/llm.d.ts +4 -0
- package/dist/agent/llm.d.ts.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 +100 -33
- package/dist/agent/prompt-builder.js.map +1 -1
- package/dist/agent/providers/google.d.ts.map +1 -1
- package/dist/agent/providers/google.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 +59 -6
- 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 +19 -17
- package/dist/agent/session.d.ts.map +1 -1
- package/dist/agent/session.js +234 -578
- 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/config/index.js +7 -1
- package/dist/config/index.js.map +1 -1
- package/dist/index.js +24 -3
- package/dist/index.js.map +1 -1
- package/dist/tools/__tests__/bash.test.js +169 -2
- package/dist/tools/__tests__/bash.test.js.map +1 -1
- package/dist/tools/__tests__/edit.test.js +1091 -340
- 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 +33 -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__/sed.test.d.ts +2 -0
- package/dist/tools/__tests__/sed.test.d.ts.map +1 -0
- package/dist/tools/__tests__/sed.test.js +228 -0
- package/dist/tools/__tests__/sed.test.js.map +1 -0
- 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 +711 -18
- 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 +50 -9
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +394 -159
- 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 +56 -30
- 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 -8
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js +114 -46
- 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/sed.d.ts +4 -0
- package/dist/tools/sed.d.ts.map +1 -0
- package/dist/tools/sed.js +121 -0
- package/dist/tools/sed.js.map +1 -0
- 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 +62 -101
- package/dist/web/index.js.map +1 -1
- package/dist/web/public/app.js +5 -855
- package/dist/web/public/confirm.js +70 -0
- package/dist/web/public/index.html +121 -120
- package/dist/web/public/input.js +107 -0
- package/dist/web/public/messages.js +411 -0
- package/dist/web/public/sidebar.js +82 -0
- package/dist/web/public/state.js +72 -0
- package/dist/web/public/style.css +247 -70
- package/dist/web/public/utils.js +37 -0
- package/dist/web/public/websocket.js +267 -0
- package/dist/web/public/ws.js +19 -0
- package/dist/web/session-bridge.d.ts.map +1 -1
- package/dist/web/session-bridge.js +85 -10
- package/dist/web/session-bridge.js.map +1 -1
- package/docs/ast-tools.md +117 -0
- package/package.json +11 -5
- package/scripts/check-line-count.mjs +53 -0
- package/scripts/copy-ast-wasm.mjs +32 -0
- package/scripts/copy-native.mjs +24 -0
- package/scripts/copy-web.mjs +15 -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 +7 -2
- package/src/agent/llm.ts +4 -0
- package/src/agent/memory.ts +60 -0
- package/src/agent/prompt-builder.ts +111 -33
- package/src/agent/providers/google.ts +0 -1
- package/src/agent/providers/openai.ts +79 -8
- 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 +269 -670
- package/src/cli/index.ts +73 -4
- package/src/config/index.ts +14 -7
- package/src/index.ts +30 -3
- package/src/tools/__tests__/bash.test.ts +188 -2
- package/src/tools/__tests__/edit.test.ts +1216 -356
- 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 +36 -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} +61 -32
- 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 +121 -50
- 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 +110 -561
- package/src/tui/overlays.ts +203 -0
- package/src/tui/themes.ts +41 -0
- package/src/web/index.ts +66 -107
- package/src/web/public/app.js +5 -855
- package/src/web/public/confirm.js +70 -0
- package/src/web/public/index.html +121 -120
- package/src/web/public/input.js +107 -0
- package/src/web/public/messages.js +411 -0
- package/src/web/public/sidebar.js +82 -0
- package/src/web/public/state.js +72 -0
- package/src/web/public/style.css +247 -70
- package/src/web/public/utils.js +37 -0
- package/src/web/public/websocket.js +267 -0
- package/src/web/public/ws.js +19 -0
- package/src/web/session-bridge.ts +90 -12
- package/.claude/settings.local.json +0 -14
- package/.kilo/plans/1780064105789-mighty-pixel.md +0 -171
- package/.kilo/plans/1780293725888-quick-wizard.md +0 -62
- package/.lonny/plan-web-cwd-status.md +0 -38
- package/AGENT.md +0 -99
- package/src/tools/bash.ts +0 -39
- package/src/tools/edit.ts +0 -554
- package/src/tools/exec.ts +0 -348
- 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,195 +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) {
|
|
184
|
-
const bus = getGlobalEventBus();
|
|
185
|
-
bus.emit(EventChannels.TOKEN_STATS, { turnIn, turnOut, totalIn, totalOut, turnApi, totalApi });
|
|
186
|
-
// Skip terminal output in Web UI mode
|
|
187
|
-
if (output?.suppressToolOutput)
|
|
188
|
-
return;
|
|
189
|
-
const total = totalIn + totalOut;
|
|
190
|
-
const msg = ` ${GY}┃${RS} ${GY}${BLD}▴${RS}${GY}${turnIn}${RS} ${GY}${BLD}▾${RS}${GY}${turnOut}${RS} ${GY}total${RS} ${total} ${GY}calls${RS} ${turnApi}(${totalApi})`;
|
|
191
|
-
writeOut(`\n${msg}\n`, output);
|
|
192
|
-
}
|
|
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 ────────────────────────────────────────────────────────────
|
|
193
15
|
export class Session {
|
|
194
16
|
messages;
|
|
195
17
|
provider;
|
|
@@ -198,12 +20,9 @@ export class Session {
|
|
|
198
20
|
config;
|
|
199
21
|
output;
|
|
200
22
|
_onPlanWritten;
|
|
201
|
-
/** Set the plan-written callback and propagate to ToolRegistry */
|
|
202
23
|
set onPlanWritten(cb) {
|
|
203
24
|
this._onPlanWritten = cb;
|
|
204
|
-
// Update context so setMode() picks it up too
|
|
205
25
|
this.registry.updateContext({ onPlanWritten: cb });
|
|
206
|
-
// Re-register write_plan tool with the new callback
|
|
207
26
|
if (this.registry.has('write_plan')) {
|
|
208
27
|
this.registry.reRegisterWritePlan(this.config.cwd, cb);
|
|
209
28
|
}
|
|
@@ -217,6 +36,10 @@ export class Session {
|
|
|
217
36
|
turnOutputTokens = 0;
|
|
218
37
|
turnApiCalls = 0;
|
|
219
38
|
totalApiCalls = 0;
|
|
39
|
+
turnCacheHitTokens = 0;
|
|
40
|
+
turnCacheMissTokens = 0;
|
|
41
|
+
totalCacheHitTokens = 0;
|
|
42
|
+
totalCacheMissTokens = 0;
|
|
220
43
|
stopped = false;
|
|
221
44
|
abortController = null;
|
|
222
45
|
constructor(config, output) {
|
|
@@ -242,457 +65,290 @@ export class Session {
|
|
|
242
65
|
else {
|
|
243
66
|
this.provider = new AnthropicProvider(config.apiKey, config.baseUrl, config.model);
|
|
244
67
|
}
|
|
245
|
-
// Placeholder until initSystemPrompt() is called
|
|
246
68
|
this.messages = [{ role: 'system', content: '' }];
|
|
247
|
-
// Initialize system prompt asynchronously
|
|
248
69
|
this.initSystemPrompt(config);
|
|
249
70
|
}
|
|
250
|
-
/** Initialize the system prompt asynchronously */
|
|
251
71
|
initSystemPrompt(config) {
|
|
252
|
-
buildSystemPrompt(config).then(prompt => {
|
|
253
|
-
this.messages
|
|
72
|
+
buildSystemPrompt(config, this.registry.getDefinitions()).then(prompt => {
|
|
73
|
+
if (this.messages.length <= 1) {
|
|
74
|
+
this.messages = [{ role: 'system', content: prompt }];
|
|
75
|
+
}
|
|
254
76
|
});
|
|
255
77
|
}
|
|
256
|
-
|
|
78
|
+
sessionId = generateId();
|
|
79
|
+
sessionTitle = '';
|
|
80
|
+
sessionCreatedAt = new Date().toISOString();
|
|
257
81
|
save() {
|
|
258
82
|
const dir = getSessionDir();
|
|
259
83
|
ensureDir(dir);
|
|
260
|
-
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
|
+
}
|
|
261
93
|
const data = {
|
|
94
|
+
id: this.sessionId,
|
|
95
|
+
title: title || undefined,
|
|
262
96
|
cwd: path.resolve(this.config.cwd),
|
|
263
97
|
messages: this.messages,
|
|
264
98
|
totalInputTokens: this.totalInputTokens,
|
|
265
99
|
totalOutputTokens: this.totalOutputTokens,
|
|
266
100
|
totalApiCalls: this.totalApiCalls,
|
|
101
|
+
totalCacheHitTokens: this.totalCacheHitTokens || undefined,
|
|
102
|
+
totalCacheMissTokens: this.totalCacheMissTokens || undefined,
|
|
267
103
|
mode: this.config.mode,
|
|
268
104
|
model: this.config.model,
|
|
269
105
|
provider: this.config.provider,
|
|
270
|
-
|
|
106
|
+
createdAt: this.sessionCreatedAt,
|
|
107
|
+
updatedAt: now,
|
|
271
108
|
};
|
|
272
109
|
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
|
|
273
110
|
}
|
|
274
|
-
/** Try to load a saved session for the given cwd. Returns null if none exists. */
|
|
275
111
|
static async load(config, output) {
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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);
|
|
280
126
|
}
|
|
281
|
-
|
|
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)
|
|
282
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
|
+
}
|
|
283
165
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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);
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
288
172
|
return null;
|
|
289
173
|
}
|
|
174
|
+
}
|
|
175
|
+
static async loadFromData(data, config, output) {
|
|
290
176
|
const session = new Session(config, output);
|
|
291
|
-
// Restore messages (replace the default system prompt with the saved one)
|
|
292
177
|
session.messages = data.messages;
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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());
|
|
299
188
|
session.messages[0] = { role: 'system', content: prompt };
|
|
300
189
|
}
|
|
301
|
-
// Restore token stats
|
|
302
190
|
session.totalInputTokens = data.totalInputTokens;
|
|
303
191
|
session.totalOutputTokens = data.totalOutputTokens;
|
|
304
192
|
session.totalApiCalls = data.totalApiCalls;
|
|
193
|
+
session.totalCacheHitTokens = data.totalCacheHitTokens ?? 0;
|
|
194
|
+
session.totalCacheMissTokens = data.totalCacheMissTokens ?? 0;
|
|
305
195
|
return session;
|
|
306
196
|
}
|
|
307
|
-
/** Remove the saved session file for the given cwd. */
|
|
308
197
|
static clearSavedSession(cwd) {
|
|
309
|
-
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();
|
|
310
219
|
try {
|
|
311
|
-
fs.
|
|
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;
|
|
268
|
+
try {
|
|
269
|
+
fs.unlinkSync(path.join(getSessionDir(), target.fileName));
|
|
270
|
+
return true;
|
|
312
271
|
}
|
|
313
272
|
catch {
|
|
314
|
-
|
|
273
|
+
return false;
|
|
315
274
|
}
|
|
316
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
|
+
}
|
|
317
332
|
async setMode(mode) {
|
|
318
333
|
this.config.mode = mode;
|
|
319
|
-
const prompt = await buildSystemPrompt(this.config);
|
|
320
|
-
this.messages[0] = { role: 'system', content: prompt };
|
|
321
334
|
this.registry.setMode(mode);
|
|
335
|
+
const prompt = await buildSystemPrompt(this.config, this.registry.getDefinitions());
|
|
336
|
+
this.messages[0] = { role: 'system', content: prompt };
|
|
322
337
|
this.save();
|
|
323
338
|
}
|
|
324
|
-
/** Stop the current conversation gracefully */
|
|
325
339
|
stop() {
|
|
326
340
|
this.stopped = true;
|
|
327
|
-
// Abort any in-flight LLM stream to stop token consumption immediately
|
|
328
341
|
this.abortController?.abort();
|
|
329
342
|
}
|
|
330
|
-
/** Check if the session was stopped */
|
|
331
343
|
isStopped() {
|
|
332
344
|
return this.stopped;
|
|
333
345
|
}
|
|
334
|
-
/** Reset the stopped flag for a new conversation */
|
|
335
346
|
resetStopped() {
|
|
336
347
|
this.stopped = false;
|
|
337
|
-
// Create new AbortController for next conversation
|
|
338
348
|
this.abortController = new AbortController();
|
|
339
349
|
}
|
|
340
350
|
async chat(userPrompt) {
|
|
341
|
-
|
|
342
|
-
const out = this.output;
|
|
343
|
-
printUserMessage(userPrompt, out);
|
|
344
|
-
this.messages.push({ role: 'user', content: userPrompt });
|
|
345
|
-
// Save immediately after user message so page refresh doesn't lose it
|
|
346
|
-
this.save();
|
|
347
|
-
// Reset per-turn counters
|
|
348
|
-
this.turnInputTokens = 0;
|
|
349
|
-
this.turnOutputTokens = 0;
|
|
350
|
-
this.turnApiCalls = 0;
|
|
351
|
-
let iterations = 0;
|
|
352
|
-
const maxIterations = 30;
|
|
353
|
-
// Reset stopped flag for new conversation
|
|
354
|
-
this.resetStopped();
|
|
355
|
-
// Create a new AbortController for this chat invocation
|
|
356
|
-
// (a new controller is needed each time because abort() is one-shot)
|
|
357
|
-
this.abortController = new AbortController();
|
|
358
|
-
// Declare toolCalls outside the loop so we can reference it in stop check
|
|
359
|
-
let toolCalls = [];
|
|
360
|
-
while (iterations < maxIterations) {
|
|
361
|
-
// Check if stop was requested
|
|
362
|
-
if (this.isStopped()) {
|
|
363
|
-
bus.emit(EventChannels.TURN_END, { iterations, toolCallCount: toolCalls.length });
|
|
364
|
-
this.save();
|
|
365
|
-
return;
|
|
366
|
-
}
|
|
367
|
-
iterations++;
|
|
368
|
-
this.turnApiCalls++;
|
|
369
|
-
this.totalApiCalls++;
|
|
370
|
-
toolCalls = [];
|
|
371
|
-
let fullResponse = '';
|
|
372
|
-
let reasoningContent;
|
|
373
|
-
let reasoningOutput = false;
|
|
374
|
-
let reasoningLineStart = false;
|
|
375
|
-
bus.emit(EventChannels.TURN_START, { prompt: userPrompt, iteration: iterations });
|
|
376
|
-
bus.emit(EventChannels.LLM_STREAM_START, { iteration: iterations });
|
|
377
|
-
const stream = this.provider.chat(this.messages, this.registry.getDefinitions(), this.abortController.signal);
|
|
378
|
-
try {
|
|
379
|
-
for await (const chunk of stream) {
|
|
380
|
-
if (chunk.reasoning_content) {
|
|
381
|
-
reasoningContent = chunk.reasoning_content;
|
|
382
|
-
// Stream reasoning content in real-time (only when no text in same chunk)
|
|
383
|
-
if (!chunk.text) {
|
|
384
|
-
// Emit thinking via EventBus for Web UI
|
|
385
|
-
bus.emit(EventChannels.THINKING, { text: chunk.reasoning_content });
|
|
386
|
-
if (!reasoningOutput) {
|
|
387
|
-
reasoningOutput = true;
|
|
388
|
-
reasoningLineStart = true;
|
|
389
|
-
if (!out?.suppressToolOutput) {
|
|
390
|
-
writeOut(thinkTopBorder(), out);
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
// Terminal display with box drawing (skip in Web UI mode, handled by EventBus)
|
|
394
|
-
if (!out?.suppressToolOutput) {
|
|
395
|
-
// Track column position on current line for wrapping
|
|
396
|
-
let thinkCol = 0;
|
|
397
|
-
// Handle newlines in streamed content - add left border on each new line
|
|
398
|
-
// Also manually wrap long lines so wrapped lines keep the │ prefix.
|
|
399
|
-
let remaining = chunk.reasoning_content;
|
|
400
|
-
const maxContentWidth = termWidth() - THINK_PREFIX_WIDTH;
|
|
401
|
-
while (remaining.length > 0) {
|
|
402
|
-
if (reasoningLineStart) {
|
|
403
|
-
writeOut(` ${GY}│${RS}${TH}`, out);
|
|
404
|
-
reasoningLineStart = false;
|
|
405
|
-
thinkCol = 0;
|
|
406
|
-
}
|
|
407
|
-
const nlIdx = remaining.indexOf('\n');
|
|
408
|
-
if (nlIdx === -1) {
|
|
409
|
-
// No newline — write as much as fits on current line, wrap if needed
|
|
410
|
-
while (remaining.length > 0) {
|
|
411
|
-
const segWidth = visibleWidth(remaining);
|
|
412
|
-
const avail = maxContentWidth - thinkCol;
|
|
413
|
-
if (segWidth <= avail) {
|
|
414
|
-
// Fits entirely on current line
|
|
415
|
-
writeOut(remaining, out);
|
|
416
|
-
thinkCol += segWidth;
|
|
417
|
-
remaining = '';
|
|
418
|
-
}
|
|
419
|
-
else if (avail <= 0) {
|
|
420
|
-
// Current line is full, wrap to next
|
|
421
|
-
writeOut(`${RS}\n`, out);
|
|
422
|
-
writeOut(` ${GY}│${RS}${TH}`, out);
|
|
423
|
-
thinkCol = 0;
|
|
424
|
-
}
|
|
425
|
-
else {
|
|
426
|
-
// Write first part that fits, then wrap
|
|
427
|
-
// Find character boundary that fits within avail
|
|
428
|
-
let cut = avail;
|
|
429
|
-
while (cut > 0 && visibleWidth(remaining.slice(0, cut)) > avail)
|
|
430
|
-
cut--;
|
|
431
|
-
if (cut <= 0)
|
|
432
|
-
cut = 1;
|
|
433
|
-
writeOut(remaining.slice(0, cut), out);
|
|
434
|
-
writeOut(`${RS}\n`, out);
|
|
435
|
-
writeOut(` ${GY}│${RS}${TH}`, out);
|
|
436
|
-
thinkCol = 0;
|
|
437
|
-
remaining = remaining.slice(cut);
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
else {
|
|
442
|
-
// Has newline — process the segment up to newline
|
|
443
|
-
const segment = remaining.slice(0, nlIdx);
|
|
444
|
-
const segWidth = visibleWidth(segment);
|
|
445
|
-
const avail = maxContentWidth - thinkCol;
|
|
446
|
-
if (segWidth <= avail) {
|
|
447
|
-
// Segment fits on current line
|
|
448
|
-
writeOut(segment, out);
|
|
449
|
-
writeOut(`${RS}\n`, out);
|
|
450
|
-
reasoningLineStart = true;
|
|
451
|
-
thinkCol = 0;
|
|
452
|
-
}
|
|
453
|
-
else {
|
|
454
|
-
// Segment too long — write what fits, wrap, then rest
|
|
455
|
-
let rest = segment;
|
|
456
|
-
// Write remainder of current line
|
|
457
|
-
if (avail > 0) {
|
|
458
|
-
let cut = avail;
|
|
459
|
-
while (cut > 0 && visibleWidth(rest.slice(0, cut)) > avail)
|
|
460
|
-
cut--;
|
|
461
|
-
if (cut <= 0)
|
|
462
|
-
cut = 1;
|
|
463
|
-
writeOut(rest.slice(0, cut), out);
|
|
464
|
-
rest = rest.slice(cut);
|
|
465
|
-
}
|
|
466
|
-
writeOut(`${RS}\n`, out);
|
|
467
|
-
reasoningLineStart = true;
|
|
468
|
-
thinkCol = 0;
|
|
469
|
-
// Write rest of segment on continuation line(s)
|
|
470
|
-
if (rest.length > 0) {
|
|
471
|
-
writeOut(` ${GY}│${RS}${TH}`, out);
|
|
472
|
-
reasoningLineStart = false;
|
|
473
|
-
while (rest.length > 0) {
|
|
474
|
-
const rw = visibleWidth(rest);
|
|
475
|
-
if (rw <= maxContentWidth) {
|
|
476
|
-
writeOut(rest, out);
|
|
477
|
-
thinkCol = rw;
|
|
478
|
-
rest = '';
|
|
479
|
-
}
|
|
480
|
-
else {
|
|
481
|
-
let cut = maxContentWidth;
|
|
482
|
-
while (cut > 0 && visibleWidth(rest.slice(0, cut)) > maxContentWidth)
|
|
483
|
-
cut--;
|
|
484
|
-
if (cut <= 0)
|
|
485
|
-
cut = 1;
|
|
486
|
-
writeOut(rest.slice(0, cut), out);
|
|
487
|
-
writeOut(`${RS}\n`, out);
|
|
488
|
-
writeOut(` ${GY}│${RS}${TH}`, out);
|
|
489
|
-
rest = rest.slice(cut);
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
writeOut(`${RS}\n`, out);
|
|
494
|
-
reasoningLineStart = true;
|
|
495
|
-
thinkCol = 0;
|
|
496
|
-
}
|
|
497
|
-
remaining = remaining.slice(nlIdx + 1);
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
if (chunk.type === 'text' && chunk.text) {
|
|
504
|
-
if (reasoningOutput) {
|
|
505
|
-
bus.emit(EventChannels.THINKING_END, {});
|
|
506
|
-
if (!out?.suppressToolOutput) {
|
|
507
|
-
writeOut(`${RS}\n`, out);
|
|
508
|
-
writeOut(thinkBottomBorder(), out);
|
|
509
|
-
}
|
|
510
|
-
reasoningOutput = false;
|
|
511
|
-
reasoningLineStart = false;
|
|
512
|
-
}
|
|
513
|
-
fullResponse += chunk.text;
|
|
514
|
-
writeOut(chunk.text, out);
|
|
515
|
-
}
|
|
516
|
-
else if (chunk.type === 'tool_use' && chunk.tool_call) {
|
|
517
|
-
toolCalls.push(chunk.tool_call);
|
|
518
|
-
}
|
|
519
|
-
else if (chunk.type === 'complete') {
|
|
520
|
-
if (chunk.usage) {
|
|
521
|
-
this.turnInputTokens += chunk.usage.input_tokens;
|
|
522
|
-
this.turnOutputTokens += chunk.usage.output_tokens;
|
|
523
|
-
this.totalInputTokens += chunk.usage.input_tokens;
|
|
524
|
-
this.totalOutputTokens += chunk.usage.output_tokens;
|
|
525
|
-
}
|
|
526
|
-
if (chunk.finish_reason === 'stop' || chunk.finish_reason === 'end_turn') {
|
|
527
|
-
if (toolCalls.length === 0) {
|
|
528
|
-
const finalAssistantMsg = {
|
|
529
|
-
role: 'assistant',
|
|
530
|
-
content: fullResponse || null,
|
|
531
|
-
reasoning_content: reasoningContent,
|
|
532
|
-
};
|
|
533
|
-
this.messages.push(finalAssistantMsg);
|
|
534
|
-
printTokenStats(this.turnInputTokens, this.turnOutputTokens, this.totalInputTokens, this.totalOutputTokens, this.turnApiCalls, this.totalApiCalls, out);
|
|
535
|
-
writeOut('\n\n', out);
|
|
536
|
-
saveTokenUsage(this.config.cwd, this.turnInputTokens, this.turnOutputTokens, this.turnApiCalls);
|
|
537
|
-
bus.emit(EventChannels.TURN_END, { iterations, toolCallCount: 0 });
|
|
538
|
-
this.save();
|
|
539
|
-
return;
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
catch (e) {
|
|
546
|
-
const errMsg = fmtErr(e);
|
|
547
|
-
const partialContent = fullResponse ? fullResponse.slice(0, 500) : '(empty)';
|
|
548
|
-
if (!out?.suppressToolOutput) {
|
|
549
|
-
writeOut(`\n${RE}Stream error:${RS} ${errMsg}`, out);
|
|
550
|
-
writeOut(`\n ${GY}┃${RS} Partial response: ${partialContent}\n`, out);
|
|
551
|
-
}
|
|
552
|
-
console.error('[session] Stream error:', errMsg, '| Partial response:', partialContent);
|
|
553
|
-
if (reasoningOutput) {
|
|
554
|
-
bus.emit(EventChannels.THINKING_END, {});
|
|
555
|
-
if (!out?.suppressToolOutput) {
|
|
556
|
-
writeOut(`${RS}\n`, out);
|
|
557
|
-
writeOut(thinkBottomBorder(), out);
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
bus.emit(EventChannels.LLM_STREAM_END, { iteration: iterations, toolCallCount: 0 });
|
|
561
|
-
bus.emit(EventChannels.TURN_END, { iterations, toolCallCount: 0 });
|
|
562
|
-
// If there are pending toolCalls that weren't executed due to abort,
|
|
563
|
-
// add an assistant message so the model knows they weren't executed
|
|
564
|
-
if (toolCalls.length > 0) {
|
|
565
|
-
const interruptedMsg = {
|
|
566
|
-
role: 'assistant',
|
|
567
|
-
content: null,
|
|
568
|
-
tool_calls: toolCalls,
|
|
569
|
-
reasoning_content: reasoningContent,
|
|
570
|
-
};
|
|
571
|
-
this.messages.push(interruptedMsg);
|
|
572
|
-
}
|
|
573
|
-
saveTokenUsage(this.config.cwd, this.turnInputTokens, this.turnOutputTokens, this.turnApiCalls);
|
|
574
|
-
this.save();
|
|
575
|
-
return;
|
|
576
|
-
}
|
|
577
|
-
bus.emit(EventChannels.LLM_STREAM_END, {
|
|
578
|
-
iteration: iterations,
|
|
579
|
-
toolCallCount: toolCalls.length,
|
|
580
|
-
});
|
|
581
|
-
// Close reasoning display if still open (model ended with tool calls, no text)
|
|
582
|
-
if (reasoningOutput) {
|
|
583
|
-
bus.emit(EventChannels.THINKING_END, {});
|
|
584
|
-
if (!out?.suppressToolOutput) {
|
|
585
|
-
writeOut(`${RS}\n`, out);
|
|
586
|
-
writeOut(thinkBottomBorder(), out);
|
|
587
|
-
}
|
|
588
|
-
reasoningOutput = false;
|
|
589
|
-
reasoningLineStart = false;
|
|
590
|
-
}
|
|
591
|
-
if (toolCalls.length === 0) {
|
|
592
|
-
if (fullResponse) {
|
|
593
|
-
const finalAssistantMsg = {
|
|
594
|
-
role: 'assistant',
|
|
595
|
-
content: fullResponse,
|
|
596
|
-
reasoning_content: reasoningContent,
|
|
597
|
-
};
|
|
598
|
-
this.messages.push(finalAssistantMsg);
|
|
599
|
-
printTokenStats(this.turnInputTokens, this.turnOutputTokens, this.totalInputTokens, this.totalOutputTokens, this.turnApiCalls, this.totalApiCalls, out);
|
|
600
|
-
writeOut('\n\n', out);
|
|
601
|
-
}
|
|
602
|
-
saveTokenUsage(this.config.cwd, this.turnInputTokens, this.turnOutputTokens, this.turnApiCalls);
|
|
603
|
-
bus.emit(EventChannels.TURN_END, { iterations, toolCallCount: 0 });
|
|
604
|
-
this.save();
|
|
605
|
-
return;
|
|
606
|
-
}
|
|
607
|
-
const assistantMsg = {
|
|
608
|
-
role: 'assistant',
|
|
609
|
-
content: fullResponse || null,
|
|
610
|
-
tool_calls: toolCalls,
|
|
611
|
-
reasoning_content: reasoningContent,
|
|
612
|
-
};
|
|
613
|
-
this.messages.push(assistantMsg);
|
|
614
|
-
this.save();
|
|
615
|
-
// ── User confirmation for write-type tool calls ──
|
|
616
|
-
if (!this.config.autoApprove && this.output?.confirmTool && toolCalls.length > 0) {
|
|
617
|
-
const writeTools = ['edit', 'bash', 'write_plan', 'exec', 'install_skill'];
|
|
618
|
-
const needsConfirm = toolCalls.filter(tc => writeTools.includes(tc.name));
|
|
619
|
-
if (needsConfirm.length > 0) {
|
|
620
|
-
const approved = await this.output.confirmTool(needsConfirm);
|
|
621
|
-
if (!approved) {
|
|
622
|
-
const rejectMsg = {
|
|
623
|
-
role: 'tool',
|
|
624
|
-
content: 'USER_REJECTED: The user declined to execute the requested tool calls. Try a different approach.',
|
|
625
|
-
tool_call_id: needsConfirm[0].id,
|
|
626
|
-
name: 'user_feedback',
|
|
627
|
-
};
|
|
628
|
-
this.messages.push(rejectMsg);
|
|
629
|
-
bus.emit(EventChannels.TURN_END, { iterations, toolCallCount: toolCalls.length });
|
|
630
|
-
continue;
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
for (let i = 0; i < toolCalls.length; i++) {
|
|
635
|
-
const tc = toolCalls[i];
|
|
636
|
-
// Check if stop was requested after each tool call
|
|
637
|
-
if (this.isStopped()) {
|
|
638
|
-
// Add assistant message with remaining tool_calls so model knows they weren't executed
|
|
639
|
-
const remainingToolCalls = toolCalls.slice(i);
|
|
640
|
-
if (remainingToolCalls.length > 0) {
|
|
641
|
-
const interruptedMsg = {
|
|
642
|
-
role: 'assistant',
|
|
643
|
-
content: null,
|
|
644
|
-
tool_calls: remainingToolCalls,
|
|
645
|
-
};
|
|
646
|
-
this.messages.push(interruptedMsg);
|
|
647
|
-
}
|
|
648
|
-
bus.emit(EventChannels.TURN_END, { iterations, toolCallCount: toolCalls.length });
|
|
649
|
-
this.save();
|
|
650
|
-
return;
|
|
651
|
-
}
|
|
652
|
-
bus.emit(EventChannels.TOOL_CALL, { name: tc.name, input: tc.input, id: tc.id });
|
|
653
|
-
if (!out?.suppressToolOutput) {
|
|
654
|
-
printToolInvocation(tc, out);
|
|
655
|
-
}
|
|
656
|
-
const result = await this.registry.dispatch(tc);
|
|
657
|
-
if (result.success) {
|
|
658
|
-
bus.emit(EventChannels.TOOL_RESULT, { name: tc.name, id: tc.id, output: result.output });
|
|
659
|
-
}
|
|
660
|
-
else {
|
|
661
|
-
bus.emit(EventChannels.TOOL_ERROR, { name: tc.name, id: tc.id, error: result.error });
|
|
662
|
-
}
|
|
663
|
-
if (!out?.suppressToolOutput) {
|
|
664
|
-
printToolResult(tc, result, out);
|
|
665
|
-
}
|
|
666
|
-
const resultMsg = {
|
|
667
|
-
role: 'tool',
|
|
668
|
-
content: result.success ? result.output : `ERROR: ${result.error}`,
|
|
669
|
-
tool_call_id: tc.id,
|
|
670
|
-
name: tc.name,
|
|
671
|
-
};
|
|
672
|
-
this.messages.push(resultMsg);
|
|
673
|
-
}
|
|
674
|
-
this.save();
|
|
675
|
-
// End the current turn before next iteration — frontend creates a new message
|
|
676
|
-
bus.emit(EventChannels.TURN_END, { iterations, toolCallCount: toolCalls.length });
|
|
677
|
-
// Check if compaction is needed
|
|
678
|
-
if (shouldCompact(this.messages, this.config.contextWindow)) {
|
|
679
|
-
const before = this.messages.length;
|
|
680
|
-
const result = compact(this.messages, this.config.contextWindow);
|
|
681
|
-
if (result.compressed) {
|
|
682
|
-
this.messages = result.messages;
|
|
683
|
-
bus.emit(EventChannels.COMPACTION_TRIGGERED, { before, after: result.newCount });
|
|
684
|
-
if (out && !out.suppressToolOutput) {
|
|
685
|
-
out.write(`\n ${GY}┃${RS} ${GY}📦 Compressed context: ${before} → ${result.newCount} messages${RS}\n`);
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
if (iterations >= maxIterations) {
|
|
691
|
-
printTokenStats(this.turnInputTokens, this.turnOutputTokens, this.totalInputTokens, this.totalOutputTokens, this.turnApiCalls, this.totalApiCalls, out);
|
|
692
|
-
writeOut('\nAgent reached maximum iterations. Stopping.\n', out);
|
|
693
|
-
}
|
|
694
|
-
saveTokenUsage(this.config.cwd, this.turnInputTokens, this.turnOutputTokens, this.turnApiCalls);
|
|
695
|
-
this.save();
|
|
351
|
+
return runChat(this, userPrompt);
|
|
696
352
|
}
|
|
697
353
|
}
|
|
698
354
|
//# sourceMappingURL=session.js.map
|