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/src/index.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { execSync } from 'node:child_process'
|
|
3
3
|
import * as os from 'node:os'
|
|
4
4
|
import { runAgent } from './agent/index.js'
|
|
5
|
+
import { Session } from './agent/session.js'
|
|
5
6
|
import { parseArgs } from './cli/index.js'
|
|
6
7
|
import { fmtErr } from './tools/errors.js'
|
|
7
8
|
import { startTui } from './tui/index.js'
|
|
@@ -27,7 +28,9 @@ function tryEnableUtf8(): void {
|
|
|
27
28
|
|
|
28
29
|
async function main() {
|
|
29
30
|
tryEnableUtf8()
|
|
30
|
-
const { config, prompt, web, port, init } = await parseArgs(
|
|
31
|
+
const { config, prompt, web, port, init, continueSession, sessionId } = await parseArgs(
|
|
32
|
+
process.argv,
|
|
33
|
+
)
|
|
31
34
|
|
|
32
35
|
// init command exits after completion
|
|
33
36
|
if (init) {
|
|
@@ -41,12 +44,36 @@ async function main() {
|
|
|
41
44
|
process.exit(1)
|
|
42
45
|
}
|
|
43
46
|
|
|
47
|
+
let loadedSession: Session | null = null
|
|
48
|
+
|
|
49
|
+
// Handle --continue and --session flags
|
|
50
|
+
if (continueSession || sessionId) {
|
|
51
|
+
if (sessionId) {
|
|
52
|
+
loadedSession = await Session.loadById(sessionId, config)
|
|
53
|
+
if (loadedSession) {
|
|
54
|
+
console.log(
|
|
55
|
+
`${BLD}Resuming session:${RS} ${loadedSession.sessionTitle || loadedSession.sessionId}`,
|
|
56
|
+
)
|
|
57
|
+
} else {
|
|
58
|
+
console.error(`${RE}Error:${RS} Session not found: ${sessionId}`)
|
|
59
|
+
process.exit(1)
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
loadedSession = await Session.load(config)
|
|
63
|
+
if (loadedSession) {
|
|
64
|
+
console.log(
|
|
65
|
+
`${BLD}Continuing last session:${RS} ${loadedSession.sessionTitle || loadedSession.sessionId}`,
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
44
71
|
if (web) {
|
|
45
72
|
await startWebUi(config, port || 15090)
|
|
46
73
|
} else if (prompt) {
|
|
47
|
-
await runAgent(prompt, config)
|
|
74
|
+
await runAgent(prompt, config, loadedSession ?? undefined)
|
|
48
75
|
} else {
|
|
49
|
-
await startTui(config)
|
|
76
|
+
await startTui(config, loadedSession ?? undefined)
|
|
50
77
|
}
|
|
51
78
|
}
|
|
52
79
|
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
import * as fs from 'node:fs'
|
|
2
|
+
import * as os from 'node:os'
|
|
3
|
+
import * as path from 'node:path'
|
|
1
4
|
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import { bashTool } from '../bash.js'
|
|
5
|
+
import { bashTool } from '../execute/bash/index.js'
|
|
6
|
+
|
|
7
|
+
function makeTempDir(): string {
|
|
8
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), 'lonny-test-'))
|
|
9
|
+
}
|
|
3
10
|
|
|
4
11
|
describe('bash tool', () => {
|
|
5
12
|
it('executes a command successfully', async () => {
|
|
@@ -25,4 +32,183 @@ describe('bash tool', () => {
|
|
|
25
32
|
expect(result.success).toBe(true)
|
|
26
33
|
expect(result.output).toContain('timed')
|
|
27
34
|
})
|
|
35
|
+
|
|
36
|
+
it('includes description in output when provided', async () => {
|
|
37
|
+
const result = await bashTool.execute({
|
|
38
|
+
command: 'echo hello',
|
|
39
|
+
description: 'testing description',
|
|
40
|
+
})
|
|
41
|
+
expect(result.success).toBe(true)
|
|
42
|
+
expect(result.output).toContain('[bash]')
|
|
43
|
+
expect(result.output).toContain('testing description')
|
|
44
|
+
expect(result.output).toContain('hello')
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('runs command in specified cwd', async () => {
|
|
48
|
+
const tmpDir = makeTempDir()
|
|
49
|
+
try {
|
|
50
|
+
// Create a marker file so we can verify cwd
|
|
51
|
+
const result = await bashTool.execute({ command: 'pwd', cwd: tmpDir })
|
|
52
|
+
expect(result.success).toBe(true)
|
|
53
|
+
// On Windows, PowerShell's Get-Location returns the path
|
|
54
|
+
// We just need to check the command executed without error
|
|
55
|
+
if (os.platform() === 'win32') {
|
|
56
|
+
// PowerShell: use Get-Location instead of pwd
|
|
57
|
+
const result2 = await bashTool.execute({
|
|
58
|
+
command: `Get-Location | Select-Object -ExpandProperty Path`,
|
|
59
|
+
cwd: tmpDir,
|
|
60
|
+
})
|
|
61
|
+
expect(result2.success).toBe(true)
|
|
62
|
+
const normalizedOutput = result2.output.replace(/\\/g, '/')
|
|
63
|
+
const normalizedTmp = tmpDir.replace(/\\/g, '/')
|
|
64
|
+
expect(normalizedOutput).toContain(normalizedTmp)
|
|
65
|
+
} else {
|
|
66
|
+
expect(result.output).toContain(tmpDir)
|
|
67
|
+
}
|
|
68
|
+
} finally {
|
|
69
|
+
fs.rmSync(tmpDir, { recursive: true, force: true })
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('handles stderr output separately', async () => {
|
|
74
|
+
// PowerShell: write to stderr using Write-Error
|
|
75
|
+
const result = await bashTool.execute({
|
|
76
|
+
command: 'Write-Error "test error" 2>&1 | Out-Null; Write-Output "stdout ok"',
|
|
77
|
+
})
|
|
78
|
+
// 2>&1 redirects stderr to stdout in PowerShell, so it should succeed
|
|
79
|
+
expect(result.success).toBe(true)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('shows (no output) for empty output', async () => {
|
|
83
|
+
// A command that produces no output
|
|
84
|
+
const result = await bashTool.execute({ command: 'Write-Output ""' })
|
|
85
|
+
expect(result.success).toBe(true)
|
|
86
|
+
expect(result.output).toContain('(no output)')
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('truncates very large output', async () => {
|
|
90
|
+
// Generate a very large output to trigger truncation
|
|
91
|
+
const result = await bashTool.execute({ command: '1..5000 | ForEach-Object { "Line $_" }' })
|
|
92
|
+
expect(result.success).toBe(true)
|
|
93
|
+
// Check if truncation warning is present (output > 10k chars)
|
|
94
|
+
if (result.output.length > 10000) {
|
|
95
|
+
expect(result.output).toContain('truncated at')
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('rejects whitespace-only command', async () => {
|
|
100
|
+
const result = await bashTool.execute({ command: ' ' })
|
|
101
|
+
expect(result.success).toBe(false)
|
|
102
|
+
expect(result.error).toContain('whitespace')
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('rejects non-string command type', async () => {
|
|
106
|
+
const result = await bashTool.execute({ command: 123 })
|
|
107
|
+
expect(result.success).toBe(false)
|
|
108
|
+
expect(result.error).toContain('must be a string')
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it('rejects non-existent cwd', async () => {
|
|
112
|
+
const result = await bashTool.execute({
|
|
113
|
+
command: 'echo test',
|
|
114
|
+
cwd: 'C:\\NonExistent_Directory_XYZ_123',
|
|
115
|
+
})
|
|
116
|
+
expect(result.success).toBe(false)
|
|
117
|
+
expect(result.error).toContain('does not exist')
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('blocks destructive rm -rf / command', async () => {
|
|
121
|
+
const result = await bashTool.execute({ command: 'rm -rf /' })
|
|
122
|
+
expect(result.success).toBe(false)
|
|
123
|
+
expect(result.error).toContain('Destructive')
|
|
124
|
+
expect(result.error).toContain('destroy')
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('blocks destructive format command', async () => {
|
|
128
|
+
const result = await bashTool.execute({ command: 'format D: /Q' })
|
|
129
|
+
expect(result.success).toBe(false)
|
|
130
|
+
expect(result.error).toContain('Destructive')
|
|
131
|
+
expect(result.error).toContain('destroy')
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it('clamps timeout to valid range', async () => {
|
|
135
|
+
// Very large timeout should be clamped, not cause error
|
|
136
|
+
const result = await bashTool.execute({ command: 'echo ok', timeout: 999999999 })
|
|
137
|
+
expect(result.success).toBe(true)
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it('rejects filename-like command (no space before flag)', async () => {
|
|
141
|
+
// This test verifies that the tool handles edge case input gracefully
|
|
142
|
+
const result = await bashTool.execute({ command: '--version' })
|
|
143
|
+
// Should fail with exit code 1, not crash
|
|
144
|
+
expect(result.success).toBe(false)
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it('blocks Remove-Item -Recurse -Force command', async () => {
|
|
148
|
+
const result = await bashTool.execute({ command: 'Remove-Item C:\\Windows -Recurse -Force' })
|
|
149
|
+
expect(result.success).toBe(false)
|
|
150
|
+
expect(result.error).toContain('Destructive')
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it('blocks diskpart clean-all command', async () => {
|
|
154
|
+
const result = await bashTool.execute({ command: 'diskpart clean-all' })
|
|
155
|
+
expect(result.success).toBe(false)
|
|
156
|
+
expect(result.error).toContain('Destructive')
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
it('blocks reg delete command', async () => {
|
|
160
|
+
const result = await bashTool.execute({ command: 'reg delete HKLM\\Software' })
|
|
161
|
+
expect(result.success).toBe(false)
|
|
162
|
+
expect(result.error).toContain('Destructive')
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it('rejects cwd with path traversal', async () => {
|
|
166
|
+
const result = await bashTool.execute({
|
|
167
|
+
command: 'echo test',
|
|
168
|
+
cwd: 'C:\\Users\\..\\Windows',
|
|
169
|
+
})
|
|
170
|
+
expect(result.success).toBe(false)
|
|
171
|
+
expect(result.error).toContain('path traversal')
|
|
172
|
+
expect(result.error).toContain('..')
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
it('rejects cwd with path traversal using forward slashes', async () => {
|
|
176
|
+
const result = await bashTool.execute({
|
|
177
|
+
command: 'echo test',
|
|
178
|
+
cwd: '/Users/../etc',
|
|
179
|
+
})
|
|
180
|
+
expect(result.success).toBe(false)
|
|
181
|
+
expect(result.error).toContain('path traversal')
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
it('redacts sensitive data from output', async () => {
|
|
185
|
+
// Test redaction of API key pattern in output
|
|
186
|
+
const result = await bashTool.execute({
|
|
187
|
+
command: "Write-Output 'api_key=sk-abc123def456ghi789jkl012mno345pqr678stu'",
|
|
188
|
+
})
|
|
189
|
+
expect(result.success).toBe(true)
|
|
190
|
+
// The API key should be redacted
|
|
191
|
+
expect(result.output).not.toContain('sk-abc123def456ghi789jkl012mno345pqr678stu')
|
|
192
|
+
expect(result.output).toContain('[REDACTED]')
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
it('redacts sensitive data from stderr', async () => {
|
|
196
|
+
const result = await bashTool.execute({
|
|
197
|
+
command: "Write-Error 'The token ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx is invalid'",
|
|
198
|
+
})
|
|
199
|
+
// Should not crash; the redacted marker should appear in the output/error
|
|
200
|
+
if (!result.success && result.error) {
|
|
201
|
+
// The token may be split across lines in PowerShell error formatting,
|
|
202
|
+
// but [REDACTED] should appear at least once
|
|
203
|
+
expect(result.error).toContain('[REDACTED]')
|
|
204
|
+
}
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
it('shows platform-specific tip for grep on Windows', async () => {
|
|
208
|
+
if (os.platform() === 'win32') {
|
|
209
|
+
const result = await bashTool.execute({ command: 'grep "pattern" file.txt' })
|
|
210
|
+
expect(result.success).toBe(false)
|
|
211
|
+
expect(result.error).toContain('findstr')
|
|
212
|
+
}
|
|
213
|
+
})
|
|
28
214
|
})
|
|
@@ -3,16 +3,16 @@ import * as os from 'node:os'
|
|
|
3
3
|
import * as path from 'node:path'
|
|
4
4
|
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
|
5
5
|
import { FileReadTracker } from '../../diff/apply.js'
|
|
6
|
+
import { computeDiff } from '../edit/diff-compute.js'
|
|
6
7
|
import {
|
|
7
|
-
computeDiff,
|
|
8
|
-
createEditTool,
|
|
9
8
|
escapeHtml,
|
|
10
|
-
findAllLinesTolerant,
|
|
11
9
|
generateDiff,
|
|
12
|
-
|
|
13
|
-
parseMarkdownEdit,
|
|
10
|
+
generateDiffWithContext,
|
|
14
11
|
renderDiffTerminal,
|
|
15
|
-
} from '../edit.js'
|
|
12
|
+
} from '../edit/diff-render.js'
|
|
13
|
+
import { createEditTool } from '../edit/edit.js'
|
|
14
|
+
import { findAllLinesTolerant, normalizeLine } from '../edit/matcher.js'
|
|
15
|
+
import { parseMarkdownEdit } from '../edit/parser.js'
|
|
16
16
|
import { makeTempDir } from './helpers.js'
|
|
17
17
|
|
|
18
18
|
// ── Minimal mock for FileReadTracker ─────────────────────────────────────
|
|
@@ -119,6 +119,34 @@ describe('findAllLinesTolerant', () => {
|
|
|
119
119
|
const match = contentSlice('hello \nworld\n', result[0]!)
|
|
120
120
|
expect(match).toBe('hello \nworld')
|
|
121
121
|
})
|
|
122
|
+
|
|
123
|
+
it('handles CRLF content - correct position computation', () => {
|
|
124
|
+
const result = findAllLinesTolerant('hello\r\nworld\r\nfoo\r\n', 'hello\nworld')
|
|
125
|
+
expect(result).toHaveLength(1)
|
|
126
|
+
expect(result[0]!.index).toBe(0)
|
|
127
|
+
// 12 = 'hello\r' (6) + '\n' (1) + 'world' (5, trailing \r excluded)
|
|
128
|
+
expect(result[0]!.length).toBe(12)
|
|
129
|
+
const match = contentSlice('hello\r\nworld\r\nfoo\r\n', result[0]!)
|
|
130
|
+
expect(match).toBe('hello\r\nworld')
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it('handles mixed CRLF/LF content', () => {
|
|
134
|
+
const result = findAllLinesTolerant('hello\r\nworld\nfoo\r\n', 'hello\nworld')
|
|
135
|
+
expect(result).toHaveLength(1)
|
|
136
|
+
expect(result[0]!.index).toBe(0)
|
|
137
|
+
expect(result[0]!.length).toBe(12)
|
|
138
|
+
const match = contentSlice('hello\r\nworld\nfoo\r\n', result[0]!)
|
|
139
|
+
expect(match).toBe('hello\r\nworld')
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('handles CRLF with whitespace normalization', () => {
|
|
143
|
+
const result = findAllLinesTolerant('hello \r\nworld\r\n', 'hello\nworld')
|
|
144
|
+
expect(result).toHaveLength(1)
|
|
145
|
+
// 13 = 'hello \r' (7) + '\n' (1) + 'world' (5, trailing \r excluded)
|
|
146
|
+
expect(result[0]!.length).toBe(13)
|
|
147
|
+
const match = contentSlice('hello \r\nworld\r\n', result[0]!)
|
|
148
|
+
expect(match).toBe('hello \r\nworld')
|
|
149
|
+
})
|
|
122
150
|
})
|
|
123
151
|
|
|
124
152
|
function contentSlice(s: string, m: { index: number; length: number }): string {
|
|
@@ -207,6 +235,59 @@ describe('generateDiff', () => {
|
|
|
207
235
|
})
|
|
208
236
|
})
|
|
209
237
|
|
|
238
|
+
describe('generateDiffWithContext', () => {
|
|
239
|
+
it('returns context line before match', () => {
|
|
240
|
+
const output = generateDiffWithContext('keep\nreplace\nkeep', 'replace', 'REPLACE', 5, 7)
|
|
241
|
+
expect(output).toContain('keep')
|
|
242
|
+
expect(output).toContain('REPLACE')
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
it('returns no context before when match starts at first line', () => {
|
|
246
|
+
const output = generateDiffWithContext('line1\nline2', 'line1', 'LINE1', 0, 5)
|
|
247
|
+
expect(output).toContain('LINE1')
|
|
248
|
+
const dimCount = (output.match(/\x1b\[38;2;100;100;100m/g) || []).length
|
|
249
|
+
expect(dimCount).toBe(1)
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
it('returns no context after when match ends at last line', () => {
|
|
253
|
+
const output = generateDiffWithContext('line1\nline2', 'line2', 'LINE2', 6, 5)
|
|
254
|
+
expect(output).toContain('LINE2')
|
|
255
|
+
const dimCount = (output.match(/\x1b\[38;2;100;100;100m/g) || []).length
|
|
256
|
+
expect(dimCount).toBe(1)
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
it('handles matchIndex at exact end of content (append behavior)', () => {
|
|
260
|
+
const output = generateDiffWithContext('line1\nline2', '', 'appended', 11, 0)
|
|
261
|
+
expect(output).toContain('appended')
|
|
262
|
+
// Should show line 2 context (the last line)
|
|
263
|
+
expect(output).toContain('line2')
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
it('handles match spanning only the last line with no trailing newline', () => {
|
|
267
|
+
const output = generateDiffWithContext('first\nlast', 'last', 'LAST', 6, 4)
|
|
268
|
+
expect(output).toContain('LAST')
|
|
269
|
+
expect(output).toContain('first')
|
|
270
|
+
// No context after since match is on the last line
|
|
271
|
+
const dimCount = (output.match(/\x1b\[38;2;100;100;100m/g) || []).length
|
|
272
|
+
expect(dimCount).toBe(1)
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
it('handles fullContent with no trailing newline', () => {
|
|
276
|
+
const output = generateDiffWithContext('line1\nline2\nline3', 'line2', 'LINE2', 6, 5)
|
|
277
|
+
expect(output).toContain('LINE2')
|
|
278
|
+
expect(output).toContain('line1')
|
|
279
|
+
expect(output).toContain('line3')
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
it('handles match on single-line content with no trailing newline', () => {
|
|
283
|
+
const output = generateDiffWithContext('onlyline', 'onlyline', 'REPLACED', 0, 8)
|
|
284
|
+
expect(output).toContain('REPLACED')
|
|
285
|
+
// No context dim lines, only the diff
|
|
286
|
+
const dimCount = (output.match(/\x1b\[38;2;100;100;100m/g) || []).length
|
|
287
|
+
expect(dimCount).toBe(0)
|
|
288
|
+
})
|
|
289
|
+
})
|
|
290
|
+
|
|
210
291
|
describe('escapeHtml', () => {
|
|
211
292
|
it('escapes ampersands', () => {
|
|
212
293
|
expect(escapeHtml('a&b')).toBe('a&b')
|
|
@@ -312,7 +393,7 @@ describe('parseMarkdownEdit', () => {
|
|
|
312
393
|
const input = '```edit\nfile: a.ts\nold: hello\nnew: world\n```'
|
|
313
394
|
const edits = parseMarkdownEdit(input)
|
|
314
395
|
expect(edits).toHaveLength(1)
|
|
315
|
-
// Non-pipe format: "old: hello"
|
|
396
|
+
// Non-pipe format: "old: hello" �?content after "old: " includes the leading space
|
|
316
397
|
expect(edits[0]!.old_string).toBe(' hello')
|
|
317
398
|
expect(edits[0]!.new_string).toBe(' world')
|
|
318
399
|
})
|
|
@@ -357,7 +438,7 @@ describe('parseMarkdownEdit', () => {
|
|
|
357
438
|
})
|
|
358
439
|
|
|
359
440
|
it('handles early-``` closing (new: outside the block)', () => {
|
|
360
|
-
// Model may close ``` before new:
|
|
441
|
+
// Model may close ``` before new: �?common mistake
|
|
361
442
|
const input = [
|
|
362
443
|
'```edit',
|
|
363
444
|
'file: a.ts',
|
|
@@ -393,10 +474,51 @@ describe('parseMarkdownEdit', () => {
|
|
|
393
474
|
expect(edits[0]!.old_string).toBe(' hello')
|
|
394
475
|
expect(edits[0]!.new_string).toBe(' world')
|
|
395
476
|
})
|
|
477
|
+
|
|
478
|
+
it('handles old:| then new: (no pipe on new)', () => {
|
|
479
|
+
const input = '```edit\nfile: a.ts\nold: |\n hello\nnew:\n world\n```'
|
|
480
|
+
const edits = parseMarkdownEdit(input)
|
|
481
|
+
expect(edits).toHaveLength(1)
|
|
482
|
+
expect(edits[0]!.file_path).toBe('a.ts')
|
|
483
|
+
expect(edits[0]!.new_string).toBe(' world')
|
|
484
|
+
})
|
|
485
|
+
|
|
486
|
+
it('handles |2 pipe-with-digit syntax', () => {
|
|
487
|
+
const input = '```edit\nfile: a.ts\nold: |2\n hello\n world\nnew: |1\n hi\n```'
|
|
488
|
+
const edits = parseMarkdownEdit(input)
|
|
489
|
+
expect(edits).toHaveLength(1)
|
|
490
|
+
expect(edits[0]!.file_path).toBe('a.ts')
|
|
491
|
+
expect(edits[0]!.old_string).toBe(' hello\n world')
|
|
492
|
+
expect(edits[0]!.new_string).toBe(' hi')
|
|
493
|
+
})
|
|
494
|
+
|
|
495
|
+
it('handles backticks inside edit content', () => {
|
|
496
|
+
const input = '```edit\nfile: a.ts\nold: |\n some `code` here\nnew: |\n replaced `code`\n```'
|
|
497
|
+
const edits = parseMarkdownEdit(input)
|
|
498
|
+
expect(edits).toHaveLength(1)
|
|
499
|
+
expect(edits[0]!.file_path).toBe('a.ts')
|
|
500
|
+
expect(edits[0]!.old_string).toBe(' some `code` here')
|
|
501
|
+
expect(edits[0]!.new_string).toBe(' replaced `code`')
|
|
502
|
+
})
|
|
503
|
+
|
|
504
|
+
it('handles old_string with trailing whitespace line', () => {
|
|
505
|
+
const input = '```edit\nfile: a.ts\nold: |\n hello\n \nnew: |\n hi\n```'
|
|
506
|
+
const edits = parseMarkdownEdit(input)
|
|
507
|
+
expect(edits).toHaveLength(1)
|
|
508
|
+
expect(edits[0]!.old_string).toBe(' hello\n ')
|
|
509
|
+
})
|
|
510
|
+
|
|
511
|
+
it('handles new_string with empty content (deletion)', () => {
|
|
512
|
+
const input = '```edit\nfile: a.ts\nold: |\n remove me\nnew:\n```'
|
|
513
|
+
const edits = parseMarkdownEdit(input)
|
|
514
|
+
expect(edits).toHaveLength(1)
|
|
515
|
+
expect(edits[0]!.old_string).toBe(' remove me')
|
|
516
|
+
expect(edits[0]!.new_string).toBe('')
|
|
517
|
+
})
|
|
396
518
|
})
|
|
397
519
|
|
|
398
520
|
// ── Markdown format integration tests ────────────────────────────────────
|
|
399
|
-
describe('edit tool
|
|
521
|
+
describe('edit tool �?markdown format', () => {
|
|
400
522
|
let tmpDir: string
|
|
401
523
|
let applier: FileReadTracker
|
|
402
524
|
|
|
@@ -491,7 +613,7 @@ describe('edit tool — markdown format', () => {
|
|
|
491
613
|
})
|
|
492
614
|
|
|
493
615
|
// ── Advanced edge cases ──────────────────────────────────────────────────
|
|
494
|
-
describe('edit tool
|
|
616
|
+
describe('edit tool �?edge cases', () => {
|
|
495
617
|
let tmpDir: string
|
|
496
618
|
let applier: FileReadTracker
|
|
497
619
|
|
|
@@ -610,6 +732,39 @@ describe('edit tool — edge cases', () => {
|
|
|
610
732
|
expect(r.success).toBe(true)
|
|
611
733
|
expect(fs.readFileSync(file, 'utf8')).toBe('brand new content\n')
|
|
612
734
|
})
|
|
735
|
+
it('rejects empty old_string on existing non-empty file', async () => {
|
|
736
|
+
const file = path.join(tmpDir, 'empty-old-on-existing.txt')
|
|
737
|
+
fs.writeFileSync(file, 'some content\n')
|
|
738
|
+
applier.markRead(file)
|
|
739
|
+
const r = await tool().execute({
|
|
740
|
+
edits: [
|
|
741
|
+
{
|
|
742
|
+
file_path: 'empty-old-on-existing.txt',
|
|
743
|
+
old_string: '',
|
|
744
|
+
new_string: 'should not replace',
|
|
745
|
+
},
|
|
746
|
+
],
|
|
747
|
+
})
|
|
748
|
+
expect(r.success).toBe(false)
|
|
749
|
+
expect(r.error).toContain('already exists')
|
|
750
|
+
})
|
|
751
|
+
|
|
752
|
+
it('rejects create file when file already exists with empty content', async () => {
|
|
753
|
+
const file = path.join(tmpDir, 'empty-existing-file.txt')
|
|
754
|
+
fs.writeFileSync(file, '')
|
|
755
|
+
applier.markRead(file)
|
|
756
|
+
const r = await tool().execute({
|
|
757
|
+
edits: [
|
|
758
|
+
{
|
|
759
|
+
file_path: 'empty-existing-file.txt',
|
|
760
|
+
old_string: '',
|
|
761
|
+
new_string: 'add content',
|
|
762
|
+
},
|
|
763
|
+
],
|
|
764
|
+
})
|
|
765
|
+
expect(r.success).toBe(false)
|
|
766
|
+
expect(r.error).toContain('already exists')
|
|
767
|
+
})
|
|
613
768
|
})
|
|
614
769
|
|
|
615
770
|
describe('file path edge cases', () => {
|
|
@@ -644,8 +799,117 @@ describe('edit tool — edge cases', () => {
|
|
|
644
799
|
fs.readFileSync(path.join(tmpDir, 'file-with-dashes_and_underscores.txt'), 'utf8'),
|
|
645
800
|
).toBe('special path')
|
|
646
801
|
})
|
|
802
|
+
|
|
803
|
+
it('rejects path traversal with Windows drive letter', async () => {
|
|
804
|
+
const r = await tool().execute({
|
|
805
|
+
edits: [
|
|
806
|
+
{
|
|
807
|
+
file_path: 'C:\\Windows\\system32\\drivers\\etc\\hosts',
|
|
808
|
+
old_string: '',
|
|
809
|
+
new_string: 'should not write',
|
|
810
|
+
},
|
|
811
|
+
],
|
|
812
|
+
})
|
|
813
|
+
expect(r.success).toBe(false)
|
|
814
|
+
expect(r.error).toContain('traversal')
|
|
815
|
+
})
|
|
647
816
|
})
|
|
648
817
|
|
|
818
|
+
describe('path traversal security', () => {
|
|
819
|
+
it('rejects path traversal with ../', async () => {
|
|
820
|
+
const r = await tool().execute({
|
|
821
|
+
edits: [
|
|
822
|
+
{
|
|
823
|
+
file_path: '../../outside-cwd.txt',
|
|
824
|
+
old_string: 'hello',
|
|
825
|
+
new_string: 'world',
|
|
826
|
+
},
|
|
827
|
+
],
|
|
828
|
+
})
|
|
829
|
+
expect(r.success).toBe(false)
|
|
830
|
+
expect(r.error).toContain('traversal')
|
|
831
|
+
})
|
|
832
|
+
|
|
833
|
+
it('rejects path traversal with deep nested ../', async () => {
|
|
834
|
+
const r = await tool().execute({
|
|
835
|
+
edits: [
|
|
836
|
+
{
|
|
837
|
+
file_path: 'subdir/../../../outside-cwd.txt',
|
|
838
|
+
old_string: '',
|
|
839
|
+
new_string: 'should not create',
|
|
840
|
+
},
|
|
841
|
+
],
|
|
842
|
+
})
|
|
843
|
+
expect(r.success).toBe(false)
|
|
844
|
+
expect(r.error).toContain('traversal')
|
|
845
|
+
})
|
|
846
|
+
|
|
847
|
+
it('rejects absolute path', async () => {
|
|
848
|
+
const r = await tool().execute({
|
|
849
|
+
edits: [
|
|
850
|
+
{
|
|
851
|
+
file_path: '/etc/passwd',
|
|
852
|
+
old_string: '',
|
|
853
|
+
new_string: 'should not write',
|
|
854
|
+
},
|
|
855
|
+
],
|
|
856
|
+
})
|
|
857
|
+
expect(r.success).toBe(false)
|
|
858
|
+
expect(r.error).toContain('traversal')
|
|
859
|
+
})
|
|
860
|
+
|
|
861
|
+
it('allows normal paths inside cwd', async () => {
|
|
862
|
+
const r = await tool().execute({
|
|
863
|
+
edits: [
|
|
864
|
+
{
|
|
865
|
+
file_path: 'safe-file.txt',
|
|
866
|
+
old_string: '',
|
|
867
|
+
new_string: 'safe content',
|
|
868
|
+
},
|
|
869
|
+
],
|
|
870
|
+
})
|
|
871
|
+
expect(r.success).toBe(true)
|
|
872
|
+
})
|
|
873
|
+
|
|
874
|
+
it('rejects path traversal via markdown format', async () => {
|
|
875
|
+
const input = '```edit\nfile: ../../escape.txt\nold:\nnew: |\n hacked\n```'
|
|
876
|
+
const r = await tool().execute({ content: input })
|
|
877
|
+
expect(r.success).toBe(false)
|
|
878
|
+
expect(r.error).toContain('traversal')
|
|
879
|
+
})
|
|
880
|
+
|
|
881
|
+
it('rejects path traversal via symlink (if supported)', async () => {
|
|
882
|
+
let canSymlink = false
|
|
883
|
+
const externalDir = path.join(os.tmpdir(), 'lonny-symlink-target-' + Date.now())
|
|
884
|
+
const symLink = path.join(tmpDir, 'symlink-inside')
|
|
885
|
+
try {
|
|
886
|
+
fs.mkdirSync(externalDir, { recursive: true })
|
|
887
|
+
fs.writeFileSync(path.join(externalDir, 'malicious.txt'), 'evil')
|
|
888
|
+
fs.symlinkSync(externalDir, symLink, 'junction')
|
|
889
|
+
canSymlink = fs.existsSync(symLink)
|
|
890
|
+
} catch {
|
|
891
|
+
canSymlink = false
|
|
892
|
+
}
|
|
893
|
+
if (!canSymlink) return
|
|
894
|
+
|
|
895
|
+
const r = await tool().execute({
|
|
896
|
+
edits: [
|
|
897
|
+
{
|
|
898
|
+
file_path: 'symlink-inside/malicious.txt',
|
|
899
|
+
old_string: 'evil',
|
|
900
|
+
new_string: 'hacked',
|
|
901
|
+
},
|
|
902
|
+
],
|
|
903
|
+
})
|
|
904
|
+
expect(r.success).toBe(false)
|
|
905
|
+
expect(r.error).toContain('traversal')
|
|
906
|
+
|
|
907
|
+
// Cleanup
|
|
908
|
+
try {
|
|
909
|
+
fs.rmSync(externalDir, { recursive: true, force: true })
|
|
910
|
+
} catch {}
|
|
911
|
+
})
|
|
912
|
+
})
|
|
649
913
|
describe('error message quality', () => {
|
|
650
914
|
it('includes proximity hint when old_string not found', async () => {
|
|
651
915
|
const file = path.join(tmpDir, 'proximity.txt')
|
|
@@ -687,7 +951,7 @@ describe('edit tool — edge cases', () => {
|
|
|
687
951
|
})
|
|
688
952
|
|
|
689
953
|
it('includes read warning when file not read first', async () => {
|
|
690
|
-
// Don't call markRead
|
|
954
|
+
// Don't call markRead �?simulate stale content
|
|
691
955
|
const file = path.join(tmpDir, 'no-read.txt')
|
|
692
956
|
fs.writeFileSync(file, 'content\n')
|
|
693
957
|
const r = await tool().execute({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from 'node:fs'
|
|
2
2
|
import * as path from 'node:path'
|
|
3
3
|
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
|
4
|
-
import { globTool } from '../glob.js'
|
|
4
|
+
import { globTool } from '../codebase/glob.js'
|
|
5
5
|
import { makeTempDir } from './helpers.js'
|
|
6
6
|
|
|
7
7
|
describe('glob tool', () => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from 'node:fs'
|
|
2
2
|
import * as path from 'node:path'
|
|
3
3
|
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
|
4
|
-
import { createGrepTool } from '../grep.js'
|
|
4
|
+
import { createGrepTool } from '../codebase/grep.js'
|
|
5
5
|
import { makeTempDir } from './helpers.js'
|
|
6
6
|
|
|
7
7
|
describe('grep tool', () => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from 'node:fs'
|
|
2
2
|
import * as path from 'node:path'
|
|
3
3
|
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
|
4
|
-
import { createLsTool } from '../ls.js'
|
|
4
|
+
import { createLsTool } from '../codebase/ls.js'
|
|
5
5
|
import { makeTempDir } from './helpers.js'
|
|
6
6
|
|
|
7
7
|
describe('ls tool', () => {
|
|
@@ -2,7 +2,7 @@ import * as fs from 'node:fs'
|
|
|
2
2
|
import * as path from 'node:path'
|
|
3
3
|
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
|
4
4
|
import { FileReadTracker } from '../../diff/apply.js'
|
|
5
|
-
import { createReadTool } from '../read.js'
|
|
5
|
+
import { createReadTool } from '../codebase/read.js'
|
|
6
6
|
import { makeTempDir } from './helpers.js'
|
|
7
7
|
|
|
8
8
|
describe('read tool', () => {
|