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/src/tools/edit.ts
DELETED
|
@@ -1,554 +0,0 @@
|
|
|
1
|
-
import * as fs from 'node:fs'
|
|
2
|
-
import * as path from 'node:path'
|
|
3
|
-
import type { FileReadTracker } from '../diff/apply.js'
|
|
4
|
-
import { fmtErr } from './errors.js'
|
|
5
|
-
import type { Tool, ToolResult } from './types.js'
|
|
6
|
-
|
|
7
|
-
// ── Diff types ────────────────────────────────────────────────────────────
|
|
8
|
-
export type DiffLineType = 'old' | 'new'
|
|
9
|
-
|
|
10
|
-
export interface DiffLine {
|
|
11
|
-
lineNum: number
|
|
12
|
-
type: DiffLineType
|
|
13
|
-
content: string
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// ── ANSI colors for terminal output ───────────────────────────────────────
|
|
17
|
-
const DIFF_RED = '\x1b[38;2;255;80;80m'
|
|
18
|
-
const DIFF_GREEN = '\x1b[38;2;0;200;100m'
|
|
19
|
-
const DIFF_RESET = '\x1b[0m'
|
|
20
|
-
|
|
21
|
-
// ── ANSI colors for HTML output ───────────────────────────────────────────
|
|
22
|
-
const HTML_RED = '#ff5050'
|
|
23
|
-
const HTML_GREEN = '#00c864'
|
|
24
|
-
|
|
25
|
-
/** Build diagnostic JSON for error messages */
|
|
26
|
-
function buildDiag(edit: SingleEdit): string {
|
|
27
|
-
return JSON.stringify({
|
|
28
|
-
file_path: edit.file_path,
|
|
29
|
-
old_string: edit.old_string,
|
|
30
|
-
new_string: edit.new_string,
|
|
31
|
-
})
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/** Compute diff lines (pure data, no rendering) */
|
|
35
|
-
export function computeDiff(oldStr: string, newStr: string, startLine = 0): DiffLine[] {
|
|
36
|
-
const oldLines = oldStr === '' ? [] : oldStr.split('\n')
|
|
37
|
-
const newLines = newStr.split('\n')
|
|
38
|
-
const lines: DiffLine[] = []
|
|
39
|
-
|
|
40
|
-
for (let i = 0; i < oldLines.length; i++) {
|
|
41
|
-
lines.push({ lineNum: startLine + i, type: 'old', content: oldLines[i] })
|
|
42
|
-
}
|
|
43
|
-
for (let i = 0; i < newLines.length; i++) {
|
|
44
|
-
lines.push({ lineNum: startLine + i, type: 'new', content: newLines[i] })
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return lines
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** Terminal renderer */
|
|
51
|
-
export function renderDiffTerminal(lines: DiffLine[]): string {
|
|
52
|
-
if (lines.length === 0) return ''
|
|
53
|
-
|
|
54
|
-
const maxLineNum = Math.max(...lines.map(l => l.lineNum))
|
|
55
|
-
const lineNumWidth = String(maxLineNum).length
|
|
56
|
-
const fmtLineNum = (n: number) => String(n).padStart(lineNumWidth, ' ')
|
|
57
|
-
|
|
58
|
-
const output: string[] = []
|
|
59
|
-
for (const line of lines) {
|
|
60
|
-
const color = line.type === 'old' ? DIFF_RED : DIFF_GREEN
|
|
61
|
-
const reset = DIFF_RESET
|
|
62
|
-
output.push(` ${fmtLineNum(line.lineNum)} ${color}${line.content}${reset}`)
|
|
63
|
-
}
|
|
64
|
-
return output.join('\n')
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/** HTML renderer for web output */
|
|
68
|
-
export function renderDiffHtml(lines: DiffLine[]): string {
|
|
69
|
-
if (lines.length === 0) return ''
|
|
70
|
-
|
|
71
|
-
const maxLineNum = Math.max(...lines.map(l => l.lineNum))
|
|
72
|
-
const lineNumWidth = String(maxLineNum).length
|
|
73
|
-
const fmtLineNum = (n: number) => String(n).padStart(lineNumWidth, ' ')
|
|
74
|
-
|
|
75
|
-
const output: string[] = []
|
|
76
|
-
for (const line of lines) {
|
|
77
|
-
const color = line.type === 'old' ? HTML_RED : HTML_GREEN
|
|
78
|
-
const style = `color: ${color};`
|
|
79
|
-
output.push(
|
|
80
|
-
` <span style="${style}">${fmtLineNum(line.lineNum)} ${escapeHtml(line.content)}</span>`,
|
|
81
|
-
)
|
|
82
|
-
}
|
|
83
|
-
return output.join('\n')
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/** Escape HTML special characters */
|
|
87
|
-
function escapeHtml(str: string): string {
|
|
88
|
-
return str
|
|
89
|
-
.replace(/&/g, '&')
|
|
90
|
-
.replace(/</g, '<')
|
|
91
|
-
.replace(/>/g, '>')
|
|
92
|
-
.replace(/"/g, '"')
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Generate diff output (backward compatible).
|
|
97
|
-
* Delegates to terminal renderer by default.
|
|
98
|
-
*/
|
|
99
|
-
export function generateDiff(oldStr: string, newStr: string, startLine = 0): string {
|
|
100
|
-
const lines = computeDiff(oldStr, newStr, startLine)
|
|
101
|
-
return renderDiffTerminal(lines)
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Normalize a line for whitespace-tolerant comparison:
|
|
106
|
-
* 1. Trim leading/trailing whitespace
|
|
107
|
-
* 2. Collapse runs of 2+ spaces/tabs into a single space
|
|
108
|
-
*
|
|
109
|
-
* This handles whitespace differences ANYWHERE on the line:
|
|
110
|
-
* - Leading/trailing spaces → `trim()` removes them
|
|
111
|
-
* - Extra internal spaces → `"foo bar"` → `"foo bar"`
|
|
112
|
-
* - Blank lines with spaces → `" "` → `""`
|
|
113
|
-
*/
|
|
114
|
-
function normalizeLine(s: string): string {
|
|
115
|
-
return s.trim().replace(/[ \t]{2,}/g, ' ')
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
interface MatchPos {
|
|
119
|
-
index: number
|
|
120
|
-
length: number
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Sliding-window line search that ignores whitespace differences
|
|
125
|
-
* (leading, trailing, and internal runs).
|
|
126
|
-
*
|
|
127
|
-
* Returns all match positions in the ORIGINAL (unnormalized) content,
|
|
128
|
-
* so the caller can do content.slice(match.index, match.index + match.length)
|
|
129
|
-
* to extract the actual matched text (with its original whitespace).
|
|
130
|
-
*/
|
|
131
|
-
function findAllLinesTolerant(content: string, oldString: string): MatchPos[] {
|
|
132
|
-
if (oldString === '') return []
|
|
133
|
-
|
|
134
|
-
const contentLines = content.split('\n')
|
|
135
|
-
const oldLines = oldString.split('\n')
|
|
136
|
-
|
|
137
|
-
if (oldLines.length > contentLines.length) return []
|
|
138
|
-
|
|
139
|
-
// Pre-normalize for speed
|
|
140
|
-
const normContent = contentLines.map(normalizeLine)
|
|
141
|
-
const normOld = oldLines.map(normalizeLine)
|
|
142
|
-
|
|
143
|
-
const matches: MatchPos[] = []
|
|
144
|
-
|
|
145
|
-
for (let start = 0; start <= normContent.length - normOld.length; start++) {
|
|
146
|
-
let match = true
|
|
147
|
-
for (let j = 0; j < normOld.length; j++) {
|
|
148
|
-
if (normContent[start + j] !== normOld[j]) {
|
|
149
|
-
match = false
|
|
150
|
-
break
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
if (match) {
|
|
154
|
-
// Compute byte position in ORIGINAL (unnormalized) content
|
|
155
|
-
let charPos = 0
|
|
156
|
-
for (let k = 0; k < start; k++) {
|
|
157
|
-
charPos += contentLines[k]!.length + 1 // +1 for the \n
|
|
158
|
-
}
|
|
159
|
-
// Compute matched text length in ORIGINAL content
|
|
160
|
-
let matchedLen = 0
|
|
161
|
-
for (let j = 0; j < oldLines.length; j++) {
|
|
162
|
-
matchedLen += contentLines[start + j]!.length
|
|
163
|
-
if (j < oldLines.length - 1) matchedLen += 1 // +1 for the \n
|
|
164
|
-
}
|
|
165
|
-
matches.push({ index: charPos, length: matchedLen })
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
return matches
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
interface SingleEdit {
|
|
173
|
-
file_path: string
|
|
174
|
-
old_string: string
|
|
175
|
-
new_string: string
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
export function createEditTool(applier: FileReadTracker, cwd: string): Tool {
|
|
179
|
-
return {
|
|
180
|
-
definition: {
|
|
181
|
-
name: 'edit',
|
|
182
|
-
description: `Replace exact text in files. Each edit object is a COMPLETE find-and-replace pair.
|
|
183
|
-
Parameter: {"edits": [{"file_path": "...", "old_string": "...", "new_string": "..."}]}
|
|
184
|
-
|
|
185
|
-
HOW TO USE:
|
|
186
|
-
1. Read the file first with \`read\`
|
|
187
|
-
2. Copy the EXACT text to replace — include 2-3 lines of context before/after
|
|
188
|
-
3. Call: edit({ edits: [{ file_path, old_string, new_string }] })
|
|
189
|
-
|
|
190
|
-
CRITICAL RULES:
|
|
191
|
-
- Each edit MUST have BOTH old_string AND new_string (a complete find-replace pair)
|
|
192
|
-
- old_string must match EXACTLY (whitespace, indentation, line breaks)
|
|
193
|
-
- old_string must be UNIQUE — include enough surrounding context
|
|
194
|
-
- Do NOT include the "<lineNumber>: " prefix from read output
|
|
195
|
-
- \`edits\` is always an array. Never pass file_path/old_string/new_string as top-level keys.
|
|
196
|
-
- To batch multiple independent edits, add multiple objects to the array
|
|
197
|
-
|
|
198
|
-
EXAMPLES:
|
|
199
|
-
Single edit: edit({ edits: [{ file_path: "src/config.ts", old_string: "mode: 'code'", new_string: "mode: 'plan'" }] })
|
|
200
|
-
|
|
201
|
-
Batch edits: edit({ edits: [
|
|
202
|
-
{ file_path: "src/a.ts", old_string: "foo", new_string: "bar" },
|
|
203
|
-
{ file_path: "src/b.ts", old_string: "x", new_string: "y" }
|
|
204
|
-
] })
|
|
205
|
-
|
|
206
|
-
Create file: edit({ edits: [{ file_path: "src/new.ts", old_string: "", new_string: "const x = 1" }] })`,
|
|
207
|
-
parameters: {
|
|
208
|
-
edits: {
|
|
209
|
-
type: 'array',
|
|
210
|
-
minItems: 1,
|
|
211
|
-
description:
|
|
212
|
-
'REQUIRED. Array of complete find-replace pairs. Each edit MUST have BOTH old_string AND new_string. Example: [{ file_path: "src/file.ts", old_string: "old", new_string: "new" }]',
|
|
213
|
-
required: true,
|
|
214
|
-
items: {
|
|
215
|
-
type: 'object',
|
|
216
|
-
properties: {
|
|
217
|
-
file_path: { type: 'string', description: 'Path to the file' },
|
|
218
|
-
old_string: {
|
|
219
|
-
type: 'string',
|
|
220
|
-
description:
|
|
221
|
-
'Text to FIND in the file. REQUIRED. Pair with new_string to form a complete find-replace. Pass "" to create a new file.',
|
|
222
|
-
},
|
|
223
|
-
new_string: {
|
|
224
|
-
type: 'string',
|
|
225
|
-
description:
|
|
226
|
-
'Replacement text. REQUIRED. Pair with old_string to form a complete find-replace. Pass "" to delete content.',
|
|
227
|
-
},
|
|
228
|
-
},
|
|
229
|
-
},
|
|
230
|
-
},
|
|
231
|
-
},
|
|
232
|
-
},
|
|
233
|
-
async execute(input): Promise<ToolResult> {
|
|
234
|
-
// ── Auto-correction: detect common misuse patterns ────────────────
|
|
235
|
-
// ── Debug: log raw input for diagnosing failures ─────────────
|
|
236
|
-
let rawInput: unknown = input
|
|
237
|
-
try {
|
|
238
|
-
rawInput = structuredClone(input)
|
|
239
|
-
} catch {
|
|
240
|
-
console.error('[edit] Failed to clone input:', input)
|
|
241
|
-
rawInput = input
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// Pattern 0: input is an array (edits passed directly instead of wrapped)
|
|
245
|
-
if (Array.isArray(input)) {
|
|
246
|
-
input = { edits: input }
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// Pattern 1: input has file_path, old_string, new_string at top level (missing edits array)
|
|
250
|
-
if (!Array.isArray(input.edits)) {
|
|
251
|
-
const keys = Object.keys(input)
|
|
252
|
-
|
|
253
|
-
// Check if the keys look like a single edit object (file_path + old_string + new_string)
|
|
254
|
-
const hasFilePath = typeof input.file_path === 'string'
|
|
255
|
-
const hasOldString = typeof input.old_string === 'string'
|
|
256
|
-
const hasNewString = typeof input.new_string === 'string'
|
|
257
|
-
|
|
258
|
-
if (hasFilePath && hasOldString && hasNewString) {
|
|
259
|
-
// Auto-correct: wrap into edits array
|
|
260
|
-
input = {
|
|
261
|
-
edits: [
|
|
262
|
-
{
|
|
263
|
-
file_path: input.file_path,
|
|
264
|
-
old_string: input.old_string,
|
|
265
|
-
new_string: input.new_string,
|
|
266
|
-
},
|
|
267
|
-
],
|
|
268
|
-
}
|
|
269
|
-
} else if (hasFilePath && hasOldString) {
|
|
270
|
-
// Only file_path + old_string (missing new_string) — still try
|
|
271
|
-
input = {
|
|
272
|
-
edits: [
|
|
273
|
-
{
|
|
274
|
-
file_path: input.file_path,
|
|
275
|
-
old_string: input.old_string,
|
|
276
|
-
new_string: input.new_string || '',
|
|
277
|
-
},
|
|
278
|
-
],
|
|
279
|
-
}
|
|
280
|
-
} else if (keys.length === 1 && hasFilePath) {
|
|
281
|
-
// Only file_path — maybe they meant create file with empty content?
|
|
282
|
-
input = { edits: [{ file_path: input.file_path, old_string: '', new_string: '' }] }
|
|
283
|
-
} else if (keys.length === 2 && hasFilePath && typeof input.new_string === 'string') {
|
|
284
|
-
// file_path + new_string but no old_string — treat as new file creation
|
|
285
|
-
input = {
|
|
286
|
-
edits: [{ file_path: input.file_path, old_string: '', new_string: input.new_string }],
|
|
287
|
-
}
|
|
288
|
-
} else {
|
|
289
|
-
// Can't auto-correct — give helpful error with examples
|
|
290
|
-
const example = hasFilePath
|
|
291
|
-
? `edit({ edits: [{ file_path: "${input.file_path}", old_string: "...", new_string: "..." }] })`
|
|
292
|
-
: `edit({ edits: [{ file_path: "src/file.ts", old_string: "old", new_string: "new" }] })`
|
|
293
|
-
return {
|
|
294
|
-
success: false,
|
|
295
|
-
output: '',
|
|
296
|
-
error: `edit requires "edits" array. Received: ${JSON.stringify(rawInput)}. Usage: ${example}`,
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
const edits = input.edits as SingleEdit[]
|
|
302
|
-
if (edits.length === 0) {
|
|
303
|
-
// Build a diagnostic: show what the AI received vs expected
|
|
304
|
-
const receivedKeys = Object.keys(rawInput as Record<string, unknown>)
|
|
305
|
-
const hint =
|
|
306
|
-
receivedKeys.length === 1 && receivedKeys[0] === 'edits'
|
|
307
|
-
? 'The edits array exists but is empty — include at least one edit object with file_path, old_string, and new_string.'
|
|
308
|
-
: receivedKeys.includes('file_path')
|
|
309
|
-
? 'Top-level file_path/old_string/new_string detected — wrap them in an edits array: { edits: [{ file_path, old_string, new_string }] }'
|
|
310
|
-
: 'No usable edit data found in the input. Provide an edits array with at least one edit.'
|
|
311
|
-
return {
|
|
312
|
-
success: false,
|
|
313
|
-
output: '',
|
|
314
|
-
error: `edit FAILED — no edits to apply. Raw input: ${JSON.stringify(rawInput)}. ${hint}`,
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// Validate each edit object — catch missing old_string/new_string early
|
|
319
|
-
const editErrors: string[] = []
|
|
320
|
-
for (let i = 0; i < edits.length; i++) {
|
|
321
|
-
const e = edits[i]
|
|
322
|
-
const missing: string[] = []
|
|
323
|
-
if (typeof e.file_path !== 'string' || !e.file_path) missing.push('file_path')
|
|
324
|
-
if (typeof e.old_string !== 'string') missing.push('old_string')
|
|
325
|
-
if (typeof e.new_string !== 'string') missing.push('new_string')
|
|
326
|
-
if (missing.length > 0) {
|
|
327
|
-
editErrors.push(
|
|
328
|
-
` edit #${i + 1}: missing ${missing.join(', ')} (has: ${Object.keys(e).join(', ')})`,
|
|
329
|
-
)
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
if (editErrors.length > 0) {
|
|
333
|
-
return {
|
|
334
|
-
success: false,
|
|
335
|
-
output: '',
|
|
336
|
-
error: `edit FAILED — ${editErrors.length} of ${edits.length} edit(s) have missing fields.\n${editErrors.join('\n')}\n\nReceived: ${JSON.stringify(rawInput)}\n\nEach edit object must be a COMPLETE find-replace pair with BOTH old_string AND new_string.
|
|
337
|
-
Do NOT split old_string (text to find) and new_string (replacement) across different edit objects.`,
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
const fileGroups = new Map<string, { edits: SingleEdit[]; originalContent: string | null }>()
|
|
342
|
-
for (const e of edits) {
|
|
343
|
-
const resolved = path.resolve(cwd, e.file_path)
|
|
344
|
-
if (!fileGroups.has(resolved)) {
|
|
345
|
-
let originalContent: string | null = null
|
|
346
|
-
try {
|
|
347
|
-
originalContent = fs.readFileSync(resolved, 'utf-8')
|
|
348
|
-
} catch {
|
|
349
|
-
/* file doesn't exist yet */
|
|
350
|
-
}
|
|
351
|
-
fileGroups.set(resolved, { edits: [], originalContent })
|
|
352
|
-
}
|
|
353
|
-
fileGroups.get(resolved)!.edits.push(e)
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
const results: string[] = []
|
|
357
|
-
let anyFailed = false
|
|
358
|
-
let firstError = ''
|
|
359
|
-
const modifiedFiles = new Map<string, string | null>()
|
|
360
|
-
|
|
361
|
-
for (const [resolved, group] of fileGroups) {
|
|
362
|
-
const relPath = path.relative(cwd, resolved).replace(/\\/g, '/')
|
|
363
|
-
let content =
|
|
364
|
-
group.originalContent !== null ? group.originalContent.replace(/\r\n/g, '\n') : null
|
|
365
|
-
|
|
366
|
-
// Check if file was read (for stale-content diagnostics)
|
|
367
|
-
const readWarning = content !== null ? applier.checkModified(resolved) : null
|
|
368
|
-
|
|
369
|
-
for (let i = group.edits.length - 1; i >= 0; i--) {
|
|
370
|
-
const e = group.edits[i]
|
|
371
|
-
// Normalize CRLF → LF in AI-provided strings (critical on Windows)
|
|
372
|
-
e.old_string = e.old_string.replace(/\r\n/g, '\n')
|
|
373
|
-
e.new_string = e.new_string.replace(/\r\n/g, '\n')
|
|
374
|
-
|
|
375
|
-
if (e.old_string === '') {
|
|
376
|
-
if (content !== null) {
|
|
377
|
-
const diag = buildDiag({ ...e, old_string: '' })
|
|
378
|
-
const suggestion = ` → Use edit with old_string to replace existing content, or choose a different path.`
|
|
379
|
-
results.push(` FAIL ${relPath}: File already exists — Raw input: ${JSON.stringify(rawInput)}
|
|
380
|
-
Edit: ${diag}
|
|
381
|
-
${suggestion}`)
|
|
382
|
-
if (!anyFailed) {
|
|
383
|
-
anyFailed = true
|
|
384
|
-
firstError = `File already exists: ${relPath}`
|
|
385
|
-
}
|
|
386
|
-
break
|
|
387
|
-
}
|
|
388
|
-
const newLines = e.new_string.split('\n').length
|
|
389
|
-
content = e.new_string
|
|
390
|
-
results.push(
|
|
391
|
-
` Created ${relPath} (${newLines} lines):\n${generateDiff('', e.new_string)}`,
|
|
392
|
-
)
|
|
393
|
-
continue
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
if (content === null) {
|
|
397
|
-
const diag = buildDiag(e)
|
|
398
|
-
const suggestion = ` → Check the file path, or create it first with old_string: "" (empty string).`
|
|
399
|
-
results.push(` FAIL ${relPath}: File not found — Raw input: ${JSON.stringify(rawInput)}
|
|
400
|
-
Edit: ${diag}
|
|
401
|
-
${suggestion}`)
|
|
402
|
-
if (!anyFailed) {
|
|
403
|
-
anyFailed = true
|
|
404
|
-
firstError = `File not found: ${relPath}`
|
|
405
|
-
}
|
|
406
|
-
break
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
// ── Tier 1: Exact match ──────────────────────────────────────
|
|
410
|
-
const exactIdx = content.indexOf(e.old_string)
|
|
411
|
-
|
|
412
|
-
let matchInfo: { index: number; length: number; strategy: 'exact' | 'tolerant' } | null =
|
|
413
|
-
null
|
|
414
|
-
|
|
415
|
-
if (exactIdx !== -1) {
|
|
416
|
-
// Exact match found — check for duplicates
|
|
417
|
-
const lastIdx = content.lastIndexOf(e.old_string)
|
|
418
|
-
if (exactIdx !== lastIdx) {
|
|
419
|
-
const diag = buildDiag(e)
|
|
420
|
-
const suggestion = ` → Include more surrounding context lines (2-3 lines before and after) in old_string to make the match unique.`
|
|
421
|
-
results.push(` FAIL ${relPath}: old_string appears MULTIPLE times — Raw input: ${JSON.stringify(rawInput)}
|
|
422
|
-
Edit: ${diag}
|
|
423
|
-
${suggestion}`)
|
|
424
|
-
if (!anyFailed) {
|
|
425
|
-
anyFailed = true
|
|
426
|
-
firstError = `old_string appears MULTIPLE times in ${relPath}`
|
|
427
|
-
}
|
|
428
|
-
break
|
|
429
|
-
}
|
|
430
|
-
matchInfo = { index: exactIdx, length: e.old_string.length, strategy: 'exact' }
|
|
431
|
-
} else {
|
|
432
|
-
// ── Tier 2: Whitespace-normalized fallback ─────────────────
|
|
433
|
-
const tolerant = findAllLinesTolerant(content, e.old_string)
|
|
434
|
-
if (tolerant.length === 0) {
|
|
435
|
-
// Not found by any strategy — include proximity hint
|
|
436
|
-
const contentLines = content.split('\n')
|
|
437
|
-
let hint = ''
|
|
438
|
-
const firstLine = (e.old_string.split('\n')[0] || '').trim()
|
|
439
|
-
if (firstLine) {
|
|
440
|
-
const normFirst = normalizeLine(firstLine)
|
|
441
|
-
// Try to find a line that contains the first line text
|
|
442
|
-
const similarIdx = contentLines.findIndex(
|
|
443
|
-
l => l.length > 0 && normFirst.length > 0 && normalizeLine(l).includes(normFirst),
|
|
444
|
-
)
|
|
445
|
-
if (similarIdx !== -1) {
|
|
446
|
-
const start = Math.max(0, similarIdx - 1)
|
|
447
|
-
const end = Math.min(contentLines.length, similarIdx + 2)
|
|
448
|
-
const snippet = contentLines.slice(start, end).join('\n')
|
|
449
|
-
hint = `\n Near line ${similarIdx + 1}:\n """\n${snippet}\n """`
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
// Always show top of file for context, unless already shown via match
|
|
453
|
-
if (!hint && contentLines.length > 0) {
|
|
454
|
-
const lines = contentLines.slice(0, Math.min(contentLines.length, 5))
|
|
455
|
-
hint = `\n File content (first ${lines.length} lines):\n """\n${lines.join('\n')}\n """`
|
|
456
|
-
}
|
|
457
|
-
const diag = buildDiag(e)
|
|
458
|
-
const readHint = readWarning ? `\n ${readWarning}` : ''
|
|
459
|
-
const suggestion = ` → Read the file again with read({ paths: ["${e.file_path}"] }) to get current content, then retry with exact matching text. Include 2-3 lines of surrounding context for uniqueness.${readHint}`
|
|
460
|
-
results.push(` FAIL ${relPath}: old_string not found — Raw input: ${JSON.stringify(rawInput)}
|
|
461
|
-
Edit: ${diag}${hint}
|
|
462
|
-
${suggestion}`)
|
|
463
|
-
if (!anyFailed) {
|
|
464
|
-
anyFailed = true
|
|
465
|
-
firstError = `old_string not found in ${relPath}${hint}`
|
|
466
|
-
}
|
|
467
|
-
break
|
|
468
|
-
}
|
|
469
|
-
if (tolerant.length > 1) {
|
|
470
|
-
const diag = buildDiag(e)
|
|
471
|
-
const suggestion = ` → Include more surrounding context lines (2-3 lines before and after) in old_string to make the match unique.`
|
|
472
|
-
results.push(
|
|
473
|
-
` FAIL ${relPath}: old_string appears MULTIPLE times (whitespace-normalized) — Raw input: ${JSON.stringify(rawInput)}\n Edit: ${diag}\n${suggestion}`,
|
|
474
|
-
)
|
|
475
|
-
if (!anyFailed) {
|
|
476
|
-
anyFailed = true
|
|
477
|
-
firstError = `old_string appears MULTIPLE times in ${relPath} (whitespace-normalized)`
|
|
478
|
-
}
|
|
479
|
-
break
|
|
480
|
-
}
|
|
481
|
-
matchInfo = {
|
|
482
|
-
index: tolerant[0]!.index,
|
|
483
|
-
length: tolerant[0]!.length,
|
|
484
|
-
strategy: 'tolerant',
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
const strategyLabel = matchInfo.strategy === 'tolerant' ? ' (whitespace-normalized)' : ''
|
|
489
|
-
const matchLineNum = content.substring(0, matchInfo.index).split('\n').length
|
|
490
|
-
const diff = generateDiff(e.old_string, e.new_string, matchLineNum - 1)
|
|
491
|
-
results.push(` Edited ${relPath}${strategyLabel}:\n${diff}`)
|
|
492
|
-
content =
|
|
493
|
-
content.slice(0, matchInfo.index) +
|
|
494
|
-
e.new_string +
|
|
495
|
-
content.slice(matchInfo.index + matchInfo.length)
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
if (anyFailed) break
|
|
499
|
-
modifiedFiles.set(resolved, content)
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
if (anyFailed) {
|
|
503
|
-
for (const [filePath, group] of fileGroups) {
|
|
504
|
-
const originalContent = group.originalContent
|
|
505
|
-
if (originalContent === null) {
|
|
506
|
-
try {
|
|
507
|
-
fs.unlinkSync(filePath)
|
|
508
|
-
} catch {
|
|
509
|
-
/* ok */
|
|
510
|
-
}
|
|
511
|
-
} else {
|
|
512
|
-
try {
|
|
513
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true })
|
|
514
|
-
fs.writeFileSync(filePath, originalContent, 'utf-8')
|
|
515
|
-
} catch {
|
|
516
|
-
/* ok */
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
applier.markRead(filePath)
|
|
520
|
-
}
|
|
521
|
-
// Only show FAIL lines in the error (successful edits were rolled back)
|
|
522
|
-
const failLines = results.filter(r => r.startsWith(' FAIL ')).join('\n')
|
|
523
|
-
return {
|
|
524
|
-
success: false,
|
|
525
|
-
output: '',
|
|
526
|
-
error: `Edit FAILED — all changes rolled back. Raw input: ${JSON.stringify(rawInput)}\n${failLines}\n\n${firstError}`,
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
for (const [resolved, content] of modifiedFiles) {
|
|
531
|
-
try {
|
|
532
|
-
if (content === null) {
|
|
533
|
-
fs.unlinkSync(resolved)
|
|
534
|
-
} else {
|
|
535
|
-
fs.mkdirSync(path.dirname(resolved), { recursive: true })
|
|
536
|
-
fs.writeFileSync(resolved, content, 'utf-8')
|
|
537
|
-
}
|
|
538
|
-
} catch (err) {
|
|
539
|
-
return {
|
|
540
|
-
success: false,
|
|
541
|
-
output: '',
|
|
542
|
-
error: `Failed to write ${path.relative(cwd, resolved).replace(/\\/g, '/')}: ${fmtErr(err)}. Input: ${JSON.stringify(rawInput)}`,
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
applier.markRead(resolved)
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
return {
|
|
549
|
-
success: true,
|
|
550
|
-
output: results.join('\n').replace(/^ {2}/gm, ''),
|
|
551
|
-
}
|
|
552
|
-
},
|
|
553
|
-
}
|
|
554
|
-
}
|