@oh-my-pi/pi-coding-agent 15.10.10 → 15.10.12
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/CHANGELOG.md +142 -7
- package/dist/cli.js +23108 -0
- package/dist/tokenizers.linux-x64-gnu-xcjh3jwk.node +0 -0
- package/dist/types/async/job-manager.d.ts +18 -0
- package/dist/types/cli/args.d.ts +2 -1
- package/dist/types/cli/dry-balance-cli.d.ts +1 -1
- package/dist/types/cli/gallery-cli.d.ts +1 -1
- package/dist/types/cli/gallery-fixtures/types.d.ts +1 -1
- package/dist/types/cli/usage-cli.d.ts +72 -0
- package/dist/types/cli-commands.d.ts +12 -0
- package/dist/types/commands/launch.d.ts +5 -1
- package/dist/types/commands/read.d.ts +1 -1
- package/dist/types/commands/usage.d.ts +25 -0
- package/dist/types/config/api-key-resolver.d.ts +3 -0
- package/dist/types/config/append-only-context-mode.d.ts +2 -1
- package/dist/types/config/model-discovery.d.ts +55 -0
- package/dist/types/config/model-registry.d.ts +8 -219
- package/dist/types/config/model-resolver.d.ts +34 -10
- package/dist/types/config/model-roles.d.ts +28 -0
- package/dist/types/config/models-config-schema.d.ts +523 -42
- package/dist/types/config/models-config.d.ts +385 -0
- package/dist/types/config/settings-schema.d.ts +41 -8
- package/dist/types/config/settings.d.ts +8 -1
- package/dist/types/debug/log-viewer.d.ts +1 -1
- package/dist/types/debug/raw-sse.d.ts +1 -1
- package/dist/types/edit/hashline/noop-loop-guard.d.ts +72 -0
- package/dist/types/eval/backend.d.ts +0 -2
- package/dist/types/eval/idle-timeout.d.ts +0 -4
- package/dist/types/eval/js/shared/rewrite-imports.d.ts +6 -6
- package/dist/types/eval/py/executor.d.ts +5 -0
- package/dist/types/eval/py/kernel.d.ts +6 -1
- package/dist/types/eval/py/runtime.d.ts +9 -0
- package/dist/types/exec/bash-executor.d.ts +2 -0
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/extensibility/extensions/runner.d.ts +3 -2
- package/dist/types/extensibility/extensions/types.d.ts +6 -3
- package/dist/types/hindsight/mental-models.d.ts +17 -8
- package/dist/types/internal-urls/artifact-protocol.d.ts +2 -2
- package/dist/types/internal-urls/types.d.ts +1 -1
- package/dist/types/lsp/edits.d.ts +9 -0
- package/dist/types/lsp/index.d.ts +2 -2
- package/dist/types/lsp/types.d.ts +2 -0
- package/dist/types/lsp/utils.d.ts +3 -0
- package/dist/types/mcp/json-rpc.d.ts +5 -0
- package/dist/types/memory-backend/index.d.ts +1 -0
- package/dist/types/memory-backend/runtime.d.ts +4 -0
- package/dist/types/memory-backend/types.d.ts +66 -1
- package/dist/types/mnemopi/state.d.ts +11 -1
- package/dist/types/modes/components/agent-dashboard.d.ts +1 -1
- package/dist/types/modes/components/assistant-message.d.ts +3 -1
- package/dist/types/modes/components/bash-execution.d.ts +1 -1
- package/dist/types/modes/components/copy-selector.d.ts +1 -1
- package/dist/types/modes/components/dynamic-border.d.ts +1 -1
- package/dist/types/modes/components/extensions/extension-dashboard.d.ts +1 -1
- package/dist/types/modes/components/extensions/extension-list.d.ts +1 -1
- package/dist/types/modes/components/extensions/inspector-panel.d.ts +1 -1
- package/dist/types/modes/components/footer.d.ts +1 -1
- package/dist/types/modes/components/hook-editor.d.ts +5 -0
- package/dist/types/modes/components/hook-input.d.ts +4 -0
- package/dist/types/modes/components/hook-selector.d.ts +1 -1
- package/dist/types/modes/components/model-selector.d.ts +1 -1
- package/dist/types/modes/components/plan-review-overlay.d.ts +1 -1
- package/dist/types/modes/components/session-observer-overlay.d.ts +1 -1
- package/dist/types/modes/components/session-selector.d.ts +1 -1
- package/dist/types/modes/components/status-line/component.d.ts +1 -1
- package/dist/types/modes/components/tiny-title-download-progress.d.ts +1 -1
- package/dist/types/modes/components/transcript-container.d.ts +25 -6
- package/dist/types/modes/components/tree-selector.d.ts +1 -1
- package/dist/types/modes/components/user-message-selector.d.ts +1 -1
- package/dist/types/modes/components/user-message.d.ts +2 -1
- package/dist/types/modes/components/visual-truncate.d.ts +1 -1
- package/dist/types/modes/components/welcome.d.ts +19 -3
- package/dist/types/modes/controllers/mcp-command-controller.d.ts +1 -1
- package/dist/types/modes/controllers/streaming-reveal.d.ts +1 -1
- package/dist/types/modes/index.d.ts +3 -3
- package/dist/types/modes/interactive-mode.d.ts +8 -3
- package/dist/types/modes/oauth-manual-input.d.ts +7 -0
- package/dist/types/modes/rpc/rpc-client.d.ts +39 -2
- package/dist/types/modes/rpc/rpc-mode.d.ts +31 -2
- package/dist/types/modes/rpc/rpc-subagents.d.ts +24 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +75 -1
- package/dist/types/modes/setup-wizard/index.d.ts +5 -1
- package/dist/types/modes/setup-wizard/lazy.d.ts +2 -0
- package/dist/types/modes/setup-wizard/scenes/sign-in.d.ts +1 -1
- package/dist/types/modes/setup-wizard/scenes/types.d.ts +1 -1
- package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +1 -1
- package/dist/types/modes/setup-wizard/wizard-overlay.d.ts +1 -1
- package/dist/types/modes/types.d.ts +4 -1
- package/dist/types/secrets/index.d.ts +1 -1
- package/dist/types/secrets/obfuscator.d.ts +8 -2
- package/dist/types/session/agent-session.d.ts +15 -3
- package/dist/types/session/auth-broker-config.d.ts +4 -0
- package/dist/types/session/session-manager.d.ts +1 -1
- package/dist/types/session/streaming-output.d.ts +23 -0
- package/dist/types/slash-commands/acp-builtins.d.ts +16 -0
- package/dist/types/slash-commands/builtin-registry.d.ts +1 -0
- package/dist/types/slash-commands/helpers/stats-dashboard.d.ts +13 -0
- package/dist/types/slash-commands/types.d.ts +1 -1
- package/dist/types/ssh/connection-manager.d.ts +8 -0
- package/dist/types/system-prompt.d.ts +2 -0
- package/dist/types/task/executor.d.ts +1 -0
- package/dist/types/task/index.d.ts +2 -2
- package/dist/types/task/parallel.d.ts +2 -2
- package/dist/types/task/types.d.ts +8 -0
- package/dist/types/task/worktree.d.ts +2 -0
- package/dist/types/thinking.d.ts +4 -0
- package/dist/types/tiny/title-client.d.ts +11 -0
- package/dist/types/tiny/title-protocol.d.ts +1 -0
- package/dist/types/tools/ask.d.ts +4 -0
- package/dist/types/tools/conflict-detect.d.ts +16 -0
- package/dist/types/tools/github-cache.d.ts +7 -0
- package/dist/types/tools/index.d.ts +6 -0
- package/dist/types/tools/sqlite-reader.d.ts +3 -0
- package/dist/types/tui/output-block.d.ts +3 -3
- package/dist/types/utils/changelog.d.ts +8 -0
- package/dist/types/utils/git.d.ts +15 -2
- package/dist/types/utils/title-generator.d.ts +3 -2
- package/dist/types/web/scrapers/readthedocs.d.ts +3 -0
- package/dist/types/web/scrapers/types.d.ts +12 -0
- package/dist/types/web/search/providers/codex.d.ts +1 -1
- package/dist/types/web/search/providers/gemini.d.ts +1 -1
- package/examples/extensions/tools.ts +5 -4
- package/package.json +14 -11
- package/scripts/build-binary.ts +18 -23
- package/scripts/bundle-dist.ts +81 -0
- package/scripts/{dev-launch → omp} +1 -1
- package/scripts/{dev-launch-preload.ts → omp.ts} +1 -1
- package/src/async/job-manager.ts +57 -3
- package/src/auto-thinking/classifier.ts +1 -0
- package/src/autoresearch/dashboard.ts +1 -1
- package/src/autoresearch/prompt-setup.md +6 -6
- package/src/autoresearch/prompt.md +6 -6
- package/src/capability/fs.ts +10 -0
- package/src/cli/args.ts +4 -1
- package/src/cli/auth-gateway-cli.ts +1 -3
- package/src/cli/dry-balance-cli.ts +1 -1
- package/src/cli/gallery-cli.ts +1 -1
- package/src/cli/gallery-fixtures/fs.ts +1 -1
- package/src/cli/gallery-fixtures/types.ts +5 -1
- package/src/cli/list-models.ts +2 -1
- package/src/cli/usage-cli.ts +603 -0
- package/src/cli-commands.ts +30 -0
- package/src/cli.ts +76 -13
- package/src/commands/complete.ts +1 -1
- package/src/commands/launch.ts +5 -1
- package/src/commands/read.ts +6 -3
- package/src/commands/usage.ts +35 -0
- package/src/commit/agentic/agent.ts +1 -1
- package/src/commit/model-selection.ts +4 -3
- package/src/config/api-key-resolver.ts +8 -6
- package/src/config/append-only-context-mode.ts +6 -12
- package/src/config/model-discovery.ts +554 -0
- package/src/config/model-registry.ts +320 -1041
- package/src/config/model-resolver.ts +173 -156
- package/src/config/model-roles.ts +74 -0
- package/src/config/models-config-schema.ts +57 -8
- package/src/config/models-config.ts +129 -0
- package/src/config/settings-schema.ts +61 -19
- package/src/config/settings.ts +98 -4
- package/src/dap/client.ts +124 -37
- package/src/dap/session.ts +259 -158
- package/src/debug/log-viewer.ts +1 -1
- package/src/debug/raw-sse.ts +1 -1
- package/src/edit/diff.ts +47 -3
- package/src/edit/hashline/block-resolver.ts +20 -1
- package/src/edit/hashline/diff.ts +36 -1
- package/src/edit/hashline/execute.ts +47 -4
- package/src/edit/hashline/noop-loop-guard.ts +99 -0
- package/src/edit/index.ts +16 -1
- package/src/edit/modes/patch.ts +52 -0
- package/src/edit/modes/replace.ts +56 -22
- package/src/edit/notebook.ts +22 -2
- package/src/edit/renderer.ts +36 -10
- package/src/eval/__tests__/completion-bridge.test.ts +1 -1
- package/src/eval/backend.ts +0 -2
- package/src/eval/completion-bridge.ts +3 -1
- package/src/eval/idle-timeout.ts +2 -9
- package/src/eval/js/context-manager.ts +6 -8
- package/src/eval/js/executor.ts +6 -2
- package/src/eval/js/index.ts +0 -2
- package/src/eval/js/shared/helpers.ts +5 -6
- package/src/eval/js/shared/local-module-loader.ts +1 -1
- package/src/eval/js/shared/prelude.txt +62 -1
- package/src/eval/js/shared/rewrite-imports.ts +40 -22
- package/src/eval/js/shared/runtime.ts +1 -1
- package/src/eval/py/executor.ts +29 -7
- package/src/eval/py/index.ts +6 -3
- package/src/eval/py/kernel.ts +43 -4
- package/src/eval/py/runner.py +107 -3
- package/src/eval/py/runtime.ts +37 -0
- package/src/exec/bash-executor.ts +85 -4
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +3 -1
- package/src/extensibility/extensions/get-commands-handler.ts +2 -1
- package/src/extensibility/extensions/runner.ts +6 -1
- package/src/extensibility/extensions/types.ts +6 -2
- package/src/extensibility/plugins/legacy-pi-compat.ts +20 -3
- package/src/hindsight/bank.ts +17 -2
- package/src/hindsight/mental-models.ts +59 -12
- package/src/hindsight/state.ts +6 -1
- package/src/internal-urls/artifact-protocol.ts +11 -2
- package/src/internal-urls/docs-index.generated.ts +11 -11
- package/src/internal-urls/issue-pr-protocol.ts +12 -5
- package/src/internal-urls/router.ts +1 -1
- package/src/internal-urls/types.ts +1 -1
- package/src/lib/xai-http.ts +1 -1
- package/src/lsp/client.ts +118 -38
- package/src/lsp/clients/biome-client.ts +101 -39
- package/src/lsp/edits.ts +143 -95
- package/src/lsp/index.ts +31 -22
- package/src/lsp/render.ts +1 -1
- package/src/lsp/types.ts +2 -0
- package/src/lsp/utils.ts +28 -10
- package/src/main.ts +183 -23
- package/src/mcp/json-rpc.ts +35 -5
- package/src/mcp/transports/stdio.ts +7 -1
- package/src/memories/index.ts +4 -1
- package/src/memory-backend/index.ts +1 -0
- package/src/memory-backend/local-backend.ts +9 -0
- package/src/memory-backend/off-backend.ts +9 -0
- package/src/memory-backend/runtime.ts +66 -0
- package/src/memory-backend/types.ts +81 -1
- package/src/mnemopi/backend.ts +176 -7
- package/src/mnemopi/state.ts +38 -2
- package/src/modes/acp/acp-agent.ts +119 -11
- package/src/modes/components/agent-dashboard.ts +10 -7
- package/src/modes/components/assistant-message.ts +32 -28
- package/src/modes/components/bash-execution.ts +1 -1
- package/src/modes/components/copy-selector.ts +1 -1
- package/src/modes/components/diff.ts +13 -2
- package/src/modes/components/dynamic-border.ts +12 -3
- package/src/modes/components/extensions/extension-dashboard.ts +8 -5
- package/src/modes/components/extensions/extension-list.ts +1 -1
- package/src/modes/components/extensions/inspector-panel.ts +1 -1
- package/src/modes/components/footer.ts +4 -2
- package/src/modes/components/history-search.ts +1 -1
- package/src/modes/components/hook-editor.ts +8 -0
- package/src/modes/components/hook-input.ts +8 -0
- package/src/modes/components/hook-selector.ts +2 -2
- package/src/modes/components/model-selector.ts +4 -2
- package/src/modes/components/plan-review-overlay.ts +1 -1
- package/src/modes/components/session-observer-overlay.ts +2 -2
- package/src/modes/components/session-selector.ts +1 -1
- package/src/modes/components/settings-selector.ts +5 -1
- package/src/modes/components/status-line/component.ts +119 -35
- package/src/modes/components/tiny-title-download-progress.ts +1 -1
- package/src/modes/components/transcript-container.ts +258 -53
- package/src/modes/components/tree-selector.ts +3 -3
- package/src/modes/components/user-message-selector.ts +1 -1
- package/src/modes/components/user-message.ts +17 -5
- package/src/modes/components/visual-truncate.ts +1 -1
- package/src/modes/components/welcome.ts +108 -26
- package/src/modes/controllers/command-controller.ts +11 -4
- package/src/modes/controllers/event-controller.ts +73 -4
- package/src/modes/controllers/input-controller.ts +2 -1
- package/src/modes/controllers/mcp-command-controller.ts +39 -4
- package/src/modes/controllers/selector-controller.ts +1 -1
- package/src/modes/controllers/streaming-reveal.ts +85 -18
- package/src/modes/index.ts +3 -21
- package/src/modes/interactive-mode.ts +42 -18
- package/src/modes/oauth-manual-input.ts +30 -3
- package/src/modes/rpc/rpc-client.ts +154 -3
- package/src/modes/rpc/rpc-mode.ts +97 -12
- package/src/modes/rpc/rpc-subagents.ts +265 -0
- package/src/modes/rpc/rpc-types.ts +81 -1
- package/src/modes/setup-wizard/index.ts +12 -2
- package/src/modes/setup-wizard/lazy.ts +16 -0
- package/src/modes/setup-wizard/scenes/glyph.ts +1 -1
- package/src/modes/setup-wizard/scenes/providers.ts +1 -1
- package/src/modes/setup-wizard/scenes/sign-in.ts +1 -1
- package/src/modes/setup-wizard/scenes/theme.ts +1 -1
- package/src/modes/setup-wizard/scenes/types.ts +1 -1
- package/src/modes/setup-wizard/scenes/web-search.ts +1 -1
- package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
- package/src/modes/types.ts +4 -1
- package/src/prompts/agents/explore.md +2 -2
- package/src/prompts/agents/librarian.md +1 -2
- package/src/prompts/agents/oracle.md +1 -1
- package/src/prompts/agents/plan.md +5 -5
- package/src/prompts/agents/task.md +5 -5
- package/src/prompts/ci-green-request.md +5 -7
- package/src/prompts/goals/goal-budget-limit.md +2 -2
- package/src/prompts/goals/goal-continuation.md +4 -4
- package/src/prompts/goals/goal-mode-active.md +1 -1
- package/src/prompts/memories/read-path.md +1 -1
- package/src/prompts/memories/stage_one_system.md +2 -2
- package/src/prompts/review-custom-request.md +1 -1
- package/src/prompts/system/agent-creation-architect.md +2 -2
- package/src/prompts/system/auto-continue.md +1 -1
- package/src/prompts/system/background-tan-dispatch.md +1 -1
- package/src/prompts/system/btw-user.md +2 -2
- package/src/prompts/system/commit-message-system.md +13 -1
- package/src/prompts/system/custom-system-prompt.md +1 -1
- package/src/prompts/system/eager-todo.md +2 -2
- package/src/prompts/system/irc-incoming.md +1 -1
- package/src/prompts/system/manual-continue.md +1 -1
- package/src/prompts/system/omfg-user.md +3 -4
- package/src/prompts/system/orchestrate-notice.md +9 -9
- package/src/prompts/system/plan-mode-active.md +4 -4
- package/src/prompts/system/plan-mode-subagent.md +4 -5
- package/src/prompts/system/plan-mode-tool-decision-reminder.md +1 -1
- package/src/prompts/system/project-prompt.md +2 -2
- package/src/prompts/system/subagent-system-prompt.md +4 -4
- package/src/prompts/system/system-prompt.md +13 -24
- package/src/prompts/system/title-system.md +2 -2
- package/src/prompts/system/ttsr-tool-reminder.md +1 -1
- package/src/prompts/system/workflow-notice.md +1 -1
- package/src/prompts/tools/ast-edit.md +1 -1
- package/src/prompts/tools/ast-grep.md +2 -2
- package/src/prompts/tools/bash.md +5 -7
- package/src/prompts/tools/browser.md +7 -7
- package/src/prompts/tools/debug.md +1 -1
- package/src/prompts/tools/eval.md +3 -3
- package/src/prompts/tools/find.md +0 -1
- package/src/prompts/tools/github.md +8 -7
- package/src/prompts/tools/goal.md +1 -1
- package/src/prompts/tools/image-gen.md +1 -1
- package/src/prompts/tools/inspect-image-system.md +1 -1
- package/src/prompts/tools/irc.md +15 -15
- package/src/prompts/tools/lsp.md +2 -2
- package/src/prompts/tools/patch.md +2 -2
- package/src/prompts/tools/read.md +3 -4
- package/src/prompts/tools/recall.md +1 -1
- package/src/prompts/tools/reflect.md +1 -1
- package/src/prompts/tools/render-mermaid.md +2 -2
- package/src/prompts/tools/replace.md +4 -10
- package/src/prompts/tools/rewind.md +2 -2
- package/src/prompts/tools/search-tool-bm25.md +1 -9
- package/src/prompts/tools/search.md +0 -1
- package/src/prompts/tools/ssh.md +0 -4
- package/src/prompts/tools/task.md +2 -3
- package/src/prompts/tools/todo.md +1 -1
- package/src/sdk.ts +31 -11
- package/src/secrets/index.ts +8 -1
- package/src/secrets/obfuscator.ts +39 -18
- package/src/session/agent-session.ts +223 -64
- package/src/session/auth-broker-config.ts +30 -1
- package/src/session/session-manager.ts +2 -2
- package/src/session/streaming-output.ts +188 -11
- package/src/slash-commands/acp-builtins.ts +24 -0
- package/src/slash-commands/builtin-registry.ts +40 -0
- package/src/slash-commands/helpers/stats-dashboard.ts +85 -0
- package/src/slash-commands/types.ts +1 -1
- package/src/ssh/connection-manager.ts +27 -0
- package/src/system-prompt.ts +14 -0
- package/src/task/commands.ts +2 -1
- package/src/task/executor.ts +74 -65
- package/src/task/index.ts +146 -68
- package/src/task/parallel.ts +3 -3
- package/src/task/render.ts +20 -5
- package/src/task/types.ts +9 -0
- package/src/task/worktree.ts +64 -56
- package/src/thinking.ts +9 -1
- package/src/tiny/title-client.ts +60 -16
- package/src/tiny/title-protocol.ts +1 -1
- package/src/tiny/worker.ts +6 -4
- package/src/tools/archive-reader.ts +30 -2
- package/src/tools/ask.ts +104 -21
- package/src/tools/ast-edit.ts +25 -5
- package/src/tools/auto-generated-guard.ts +20 -3
- package/src/tools/bash-interactive.ts +27 -7
- package/src/tools/bash.ts +100 -18
- package/src/tools/browser/launch.ts +11 -2
- package/src/tools/browser/readable.ts +19 -2
- package/src/tools/browser/registry.ts +4 -1
- package/src/tools/browser/render.ts +2 -2
- package/src/tools/browser/tab-supervisor.ts +55 -16
- package/src/tools/conflict-detect.ts +50 -4
- package/src/tools/debug.ts +1 -1
- package/src/tools/eval-render.ts +5 -5
- package/src/tools/eval.ts +0 -2
- package/src/tools/fetch.ts +33 -10
- package/src/tools/gh-cache-invalidation.ts +63 -8
- package/src/tools/gh-renderer.ts +1 -1
- package/src/tools/gh.ts +172 -29
- package/src/tools/github-cache.ts +70 -6
- package/src/tools/image-gen.ts +14 -13
- package/src/tools/index.ts +13 -1
- package/src/tools/inspect-image.ts +1 -0
- package/src/tools/irc.ts +5 -1
- package/src/tools/job.ts +1 -1
- package/src/tools/read.ts +202 -61
- package/src/tools/render-utils.ts +3 -3
- package/src/tools/resolve.ts +1 -1
- package/src/tools/search.ts +92 -29
- package/src/tools/sqlite-reader.ts +17 -5
- package/src/tools/ssh.ts +8 -8
- package/src/tools/todo.ts +38 -8
- package/src/tools/write.ts +118 -18
- package/src/tui/output-block.ts +4 -4
- package/src/utils/changelog.ts +27 -1
- package/src/utils/commit-message-generator.ts +1 -0
- package/src/utils/file-mentions.ts +2 -1
- package/src/utils/git.ts +267 -13
- package/src/utils/title-generator.ts +24 -5
- package/src/web/scrapers/arxiv.ts +1 -1
- package/src/web/scrapers/go-pkg.ts +1 -1
- package/src/web/scrapers/iacr.ts +1 -1
- package/src/web/scrapers/readthedocs.ts +1 -1
- package/src/web/scrapers/twitter.ts +2 -1
- package/src/web/scrapers/types.ts +87 -8
- package/src/web/scrapers/wikipedia.ts +1 -1
- package/src/web/scrapers/youtube.ts +6 -1
- package/src/web/search/index.ts +1 -1
- package/src/web/search/providers/codex.ts +2 -1
- package/src/web/search/providers/gemini.ts +2 -3
- package/src/web/search/render.ts +8 -6
- package/dist/types/config/model-equivalence.d.ts +0 -24
- package/dist/types/config/model-id-affixes.d.ts +0 -12
- package/dist/types/config/model-provider-priority.d.ts +0 -1
- package/dist/types/exec/idle-timeout-watchdog.d.ts +0 -18
- package/src/config/model-equivalence.ts +0 -875
- package/src/config/model-id-affixes.ts +0 -81
- package/src/config/model-provider-priority.ts +0 -56
- package/src/exec/idle-timeout-watchdog.ts +0 -126
package/src/tools/read.ts
CHANGED
|
@@ -87,6 +87,7 @@ import {
|
|
|
87
87
|
getTableSchema,
|
|
88
88
|
isSqliteFile,
|
|
89
89
|
listTables,
|
|
90
|
+
MAX_RAW_QUERY_ROWS,
|
|
90
91
|
parseSqlitePathCandidates,
|
|
91
92
|
parseSqliteSelector,
|
|
92
93
|
queryRows,
|
|
@@ -334,6 +335,7 @@ async function streamLinesFromFile(
|
|
|
334
335
|
maxBytes: number,
|
|
335
336
|
selectedLineLimit: number | null,
|
|
336
337
|
signal?: AbortSignal,
|
|
338
|
+
stopScanAfterCollect = false,
|
|
337
339
|
): Promise<{
|
|
338
340
|
lines: string[];
|
|
339
341
|
totalFileLines: number;
|
|
@@ -342,6 +344,8 @@ async function streamLinesFromFile(
|
|
|
342
344
|
firstLinePreview?: { text: string; bytes: number };
|
|
343
345
|
firstLineByteLength?: number;
|
|
344
346
|
selectedBytesTotal: number;
|
|
347
|
+
/** False when `stopScanAfterCollect` cut the scan short — `totalFileLines` is then a lower bound. */
|
|
348
|
+
reachedEof: boolean;
|
|
345
349
|
}> {
|
|
346
350
|
const bufferChunk = Buffer.allocUnsafe(READ_CHUNK_SIZE);
|
|
347
351
|
const collectedLines: string[] = [];
|
|
@@ -349,6 +353,7 @@ async function streamLinesFromFile(
|
|
|
349
353
|
let collectedBytes = 0;
|
|
350
354
|
let stoppedByByteLimit = false;
|
|
351
355
|
let doneCollecting = false;
|
|
356
|
+
let reachedEof = true;
|
|
352
357
|
let fileHandle: fs.FileHandle | null = null;
|
|
353
358
|
let currentLineLength = 0;
|
|
354
359
|
let currentLineChunks: Buffer[] = [];
|
|
@@ -463,6 +468,30 @@ async function streamLinesFromFile(
|
|
|
463
468
|
const chunk = bufferChunk.subarray(0, bytesRead);
|
|
464
469
|
endedWithNewline = chunk[bytesRead - 1] === 0x0a;
|
|
465
470
|
|
|
471
|
+
// Once collection and selected-line accounting are both finished, the
|
|
472
|
+
// remaining scan only computes `totalFileLines` — count newlines with
|
|
473
|
+
// native indexOf instead of the per-byte JS loop (a multi-GB tail
|
|
474
|
+
// otherwise stalls the read for seconds to minutes).
|
|
475
|
+
if (doneCollecting && selectedLineLimit !== null && selectedLinesSeen >= selectedLineLimit) {
|
|
476
|
+
if (stopScanAfterCollect) {
|
|
477
|
+
reachedEof = false;
|
|
478
|
+
break;
|
|
479
|
+
}
|
|
480
|
+
let searchFrom = 0;
|
|
481
|
+
let newlineAt = chunk.indexOf(0x0a);
|
|
482
|
+
while (newlineAt !== -1) {
|
|
483
|
+
lineIndex++;
|
|
484
|
+
searchFrom = newlineAt + 1;
|
|
485
|
+
newlineAt = chunk.indexOf(0x0a, searchFrom);
|
|
486
|
+
}
|
|
487
|
+
if (searchFrom === 0) {
|
|
488
|
+
currentLineLength += chunk.length;
|
|
489
|
+
} else {
|
|
490
|
+
currentLineLength = chunk.length - searchFrom;
|
|
491
|
+
}
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
|
|
466
495
|
let start = 0;
|
|
467
496
|
for (let i = 0; i < chunk.length; i++) {
|
|
468
497
|
if (chunk[i] === 0x0a) {
|
|
@@ -485,7 +514,7 @@ async function streamLinesFromFile(
|
|
|
485
514
|
}
|
|
486
515
|
}
|
|
487
516
|
|
|
488
|
-
if (endedWithNewline || currentLineLength > 0 || !sawAnyByte) {
|
|
517
|
+
if (reachedEof && (endedWithNewline || currentLineLength > 0 || !sawAnyByte)) {
|
|
489
518
|
finalizeLine();
|
|
490
519
|
}
|
|
491
520
|
|
|
@@ -503,6 +532,7 @@ async function streamLinesFromFile(
|
|
|
503
532
|
firstLinePreview,
|
|
504
533
|
firstLineByteLength,
|
|
505
534
|
selectedBytesTotal,
|
|
535
|
+
reachedEof,
|
|
506
536
|
};
|
|
507
537
|
}
|
|
508
538
|
|
|
@@ -516,6 +546,17 @@ function isNotFoundError(error: unknown): boolean {
|
|
|
516
546
|
return code === "ENOENT" || code === "ENOTDIR";
|
|
517
547
|
}
|
|
518
548
|
|
|
549
|
+
/**
|
|
550
|
+
* Escape glob metacharacters so a literal path (e.g. `foo[1].ts`) interpolated
|
|
551
|
+
* into a suffix-glob pattern matches itself. Each metachar is wrapped in a
|
|
552
|
+
* character class (the native glob engine rewrites `\` to `/`, so backslash
|
|
553
|
+
* escaping is unavailable). `]`/`}` need no escaping once their openers are
|
|
554
|
+
* neutralized — unmatched closers are literal.
|
|
555
|
+
*/
|
|
556
|
+
function escapeGlobMetachars(value: string): string {
|
|
557
|
+
return value.replace(/[*?[{]/g, "[$&]");
|
|
558
|
+
}
|
|
559
|
+
|
|
519
560
|
/**
|
|
520
561
|
* Attempt to resolve a non-existent path by finding a unique suffix match within the workspace.
|
|
521
562
|
* Uses a glob suffix pattern so the native engine handles matching directly.
|
|
@@ -528,6 +569,7 @@ async function findUniqueSuffixMatch(
|
|
|
528
569
|
): Promise<{ absolutePath: string; displayPath: string } | null> {
|
|
529
570
|
const normalized = rawPath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+$/, "");
|
|
530
571
|
if (!normalized) return null;
|
|
572
|
+
const pattern = `**/${escapeGlobMetachars(normalized)}`;
|
|
531
573
|
|
|
532
574
|
const timeoutSignal = AbortSignal.timeout(GLOB_TIMEOUT_MS);
|
|
533
575
|
const combinedSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
|
|
@@ -536,7 +578,7 @@ async function findUniqueSuffixMatch(
|
|
|
536
578
|
try {
|
|
537
579
|
const result = await untilAborted(combinedSignal, () =>
|
|
538
580
|
glob({
|
|
539
|
-
pattern
|
|
581
|
+
pattern,
|
|
540
582
|
path: cwd,
|
|
541
583
|
// No fileType filter: matches both files and directories
|
|
542
584
|
hidden: true,
|
|
@@ -560,9 +602,7 @@ async function findUniqueSuffixMatch(
|
|
|
560
602
|
}
|
|
561
603
|
|
|
562
604
|
function decodeUtf8Text(bytes: Uint8Array): string | null {
|
|
563
|
-
|
|
564
|
-
if (byte === 0) return null;
|
|
565
|
-
}
|
|
605
|
+
if (bytes.indexOf(0) !== -1) return null;
|
|
566
606
|
|
|
567
607
|
try {
|
|
568
608
|
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
@@ -580,7 +620,11 @@ function prependSuffixResolutionNotice(text: string, suffixResolution?: { from:
|
|
|
580
620
|
|
|
581
621
|
const readSchema = z
|
|
582
622
|
.object({
|
|
583
|
-
path: z
|
|
623
|
+
path: z
|
|
624
|
+
.string()
|
|
625
|
+
.describe(
|
|
626
|
+
'Local path, internal URI (e.g. "omp://", "issue://123", "pr://123"), or URL; append :<sel> for line ranges or raw mode (e.g. "src/foo.ts:50-100")',
|
|
627
|
+
),
|
|
584
628
|
})
|
|
585
629
|
.strict();
|
|
586
630
|
|
|
@@ -689,6 +733,9 @@ interface ResolvedSqliteReadPath {
|
|
|
689
733
|
suffixResolution?: { from: string; to: string };
|
|
690
734
|
}
|
|
691
735
|
|
|
736
|
+
/** Per-execute memo of suffix-glob lookups; `null` records a confirmed miss. */
|
|
737
|
+
type SuffixMatchCache = Map<string, { absolutePath: string; displayPath: string } | null>;
|
|
738
|
+
|
|
692
739
|
/**
|
|
693
740
|
* Read tool implementation.
|
|
694
741
|
*
|
|
@@ -772,7 +819,30 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
772
819
|
return toolResult<ReadToolDetails>({ notes, displayReadTargets }).content(content).done();
|
|
773
820
|
}
|
|
774
821
|
|
|
775
|
-
|
|
822
|
+
/**
|
|
823
|
+
* Memoized {@link findUniqueSuffixMatch} for a single read call. A missing
|
|
824
|
+
* path with archive/sqlite extensions probes the workspace once per stage
|
|
825
|
+
* (archive candidates, sqlite candidates, plain path) — each glob carries a
|
|
826
|
+
* 5s timeout, so repeated lookups of the same string stack into a long
|
|
827
|
+
* stall before erroring. The cache collapses repeats within one execute().
|
|
828
|
+
*/
|
|
829
|
+
async #findSuffixMatchCached(
|
|
830
|
+
cache: SuffixMatchCache,
|
|
831
|
+
rawPath: string,
|
|
832
|
+
signal?: AbortSignal,
|
|
833
|
+
): Promise<{ absolutePath: string; displayPath: string } | null> {
|
|
834
|
+
const hit = cache.get(rawPath);
|
|
835
|
+
if (hit !== undefined) return hit;
|
|
836
|
+
const result = await findUniqueSuffixMatch(rawPath, this.session.cwd, signal);
|
|
837
|
+
cache.set(rawPath, result);
|
|
838
|
+
return result;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
async #resolveArchiveReadPath(
|
|
842
|
+
readPath: string,
|
|
843
|
+
suffixCache: SuffixMatchCache,
|
|
844
|
+
signal?: AbortSignal,
|
|
845
|
+
): Promise<ResolvedArchiveReadPath | null> {
|
|
776
846
|
const candidates = parseArchivePathCandidates(readPath);
|
|
777
847
|
for (const candidate of candidates) {
|
|
778
848
|
let absolutePath = resolveReadPath(candidate.archivePath, this.session.cwd);
|
|
@@ -789,7 +859,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
789
859
|
} catch (error) {
|
|
790
860
|
if (!isNotFoundError(error) || isRemoteMountPath(absolutePath)) continue;
|
|
791
861
|
|
|
792
|
-
const suffixMatch = await
|
|
862
|
+
const suffixMatch = await this.#findSuffixMatchCached(suffixCache, candidate.archivePath, signal);
|
|
793
863
|
if (!suffixMatch) continue;
|
|
794
864
|
|
|
795
865
|
try {
|
|
@@ -814,7 +884,11 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
814
884
|
return null;
|
|
815
885
|
}
|
|
816
886
|
|
|
817
|
-
async #resolveSqliteReadPath(
|
|
887
|
+
async #resolveSqliteReadPath(
|
|
888
|
+
readPath: string,
|
|
889
|
+
suffixCache: SuffixMatchCache,
|
|
890
|
+
signal?: AbortSignal,
|
|
891
|
+
): Promise<ResolvedSqliteReadPath | null> {
|
|
818
892
|
const candidates = parseSqlitePathCandidates(readPath);
|
|
819
893
|
for (const candidate of candidates) {
|
|
820
894
|
let absolutePath = resolveReadPath(candidate.sqlitePath, this.session.cwd);
|
|
@@ -834,7 +908,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
834
908
|
} catch (error) {
|
|
835
909
|
if (!isNotFoundError(error) || isRemoteMountPath(absolutePath)) continue;
|
|
836
910
|
|
|
837
|
-
const suffixMatch = await
|
|
911
|
+
const suffixMatch = await this.#findSuffixMatchCached(suffixCache, candidate.sqlitePath, signal);
|
|
838
912
|
if (!suffixMatch) continue;
|
|
839
913
|
|
|
840
914
|
try {
|
|
@@ -1169,17 +1243,29 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
1169
1243
|
const rangeStart = range.startLine - 1; // 0-indexed
|
|
1170
1244
|
const requestedLength = range.endLine !== undefined ? range.endLine - range.startLine + 1 : this.#defaultLimit;
|
|
1171
1245
|
const maxLines = Math.min(requestedLength, DEFAULT_MAX_LINES);
|
|
1172
|
-
const maxBytesForRead = Math.max(DEFAULT_MAX_BYTES, maxLines * 512);
|
|
1173
1246
|
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1247
|
+
// When the full file is already in memory (the common case for files
|
|
1248
|
+
// within the snapshot byte cap), slice ranges from it instead of
|
|
1249
|
+
// re-streaming the file once per range.
|
|
1250
|
+
let collectedLines: string[];
|
|
1251
|
+
let totalFileLines: number;
|
|
1252
|
+
if (fullLines) {
|
|
1253
|
+
totalFileLines = fullLines.length;
|
|
1254
|
+
collectedLines = fullLines.slice(rangeStart, rangeStart + maxLines);
|
|
1255
|
+
} else {
|
|
1256
|
+
const maxBytesForRead = Math.max(DEFAULT_MAX_BYTES, maxLines * 512);
|
|
1257
|
+
const streamResult = await streamLinesFromFile(
|
|
1258
|
+
absolutePath,
|
|
1259
|
+
rangeStart,
|
|
1260
|
+
maxLines,
|
|
1261
|
+
maxBytesForRead,
|
|
1262
|
+
maxLines,
|
|
1263
|
+
signal,
|
|
1264
|
+
fileSize > SNAPSHOT_MAX_BYTES, // giant file: collected ranges don't need an exact EOF line count
|
|
1265
|
+
);
|
|
1266
|
+
totalFileLines = streamResult.totalFileLines;
|
|
1267
|
+
collectedLines = streamResult.lines;
|
|
1268
|
+
}
|
|
1183
1269
|
|
|
1184
1270
|
if (rangeStart >= totalFileLines) {
|
|
1185
1271
|
const bound = range.endLine !== undefined ? `${range.startLine}-${range.endLine}` : `${range.startLine}`;
|
|
@@ -1187,7 +1273,6 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
1187
1273
|
continue;
|
|
1188
1274
|
}
|
|
1189
1275
|
|
|
1190
|
-
const collectedLines = streamResult.lines;
|
|
1191
1276
|
// Column truncation is display-only; clone before stamping ellipsis so
|
|
1192
1277
|
// the original on-disk lines stay intact for display reconstruction.
|
|
1193
1278
|
let displayLines: string[] = collectedLines;
|
|
@@ -1256,13 +1341,17 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
1256
1341
|
archive: ArchiveReader,
|
|
1257
1342
|
archivePath: string,
|
|
1258
1343
|
subPath: string,
|
|
1344
|
+
offset: number | undefined,
|
|
1259
1345
|
limit: number | undefined,
|
|
1260
1346
|
details: ReadToolDetails,
|
|
1261
1347
|
signal?: AbortSignal,
|
|
1262
1348
|
): Promise<AgentToolResult<ReadToolDetails>> {
|
|
1263
1349
|
const DEFAULT_LIMIT = 500;
|
|
1264
1350
|
const effectiveLimit = limit ?? DEFAULT_LIMIT;
|
|
1265
|
-
const
|
|
1351
|
+
const allEntries = archive.listDirectory(subPath);
|
|
1352
|
+
// `offset` is 1-indexed (line-selector semantics): `a.zip:dir:50` starts
|
|
1353
|
+
// the listing at the 50th entry instead of being silently ignored.
|
|
1354
|
+
const entries = offset !== undefined && offset > 1 ? allEntries.slice(offset - 1) : allEntries;
|
|
1266
1355
|
|
|
1267
1356
|
const listLimit = applyListLimit(entries, { limit: effectiveLimit });
|
|
1268
1357
|
const limitedEntries = listLimit.items;
|
|
@@ -1301,27 +1390,41 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
1301
1390
|
suffixResolution: resolvedArchivePath.suffixResolution,
|
|
1302
1391
|
};
|
|
1303
1392
|
|
|
1304
|
-
|
|
1393
|
+
let archiveSubPath = resolvedArchivePath.archiveSubPath;
|
|
1394
|
+
let sel = parsedSel;
|
|
1395
|
+
let node = archive.getNode(archiveSubPath);
|
|
1396
|
+
if (!node && archiveSubPath) {
|
|
1397
|
+
// `archive.zip:500` / `archive.zip:raw`: the whole subPath is a
|
|
1398
|
+
// selector on the archive root, not a member name. Member names take
|
|
1399
|
+
// precedence (getNode above); fall back to root + selector.
|
|
1400
|
+
const wholeSel = parseSel(archiveSubPath);
|
|
1401
|
+
if (wholeSel.kind !== "none") {
|
|
1402
|
+
node = archive.getNode("");
|
|
1403
|
+
archiveSubPath = "";
|
|
1404
|
+
sel = wholeSel;
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1305
1407
|
if (!node) {
|
|
1306
1408
|
throw new ToolError(`Path '${readPath}' not found inside archive`);
|
|
1307
1409
|
}
|
|
1308
1410
|
|
|
1309
1411
|
if (node.isDirectory) {
|
|
1310
|
-
if (isMultiRange(
|
|
1412
|
+
if (isMultiRange(sel)) {
|
|
1311
1413
|
throw new ToolError("Multi-range line selectors are not supported for archive directory listings.");
|
|
1312
1414
|
}
|
|
1313
|
-
const { limit } = selToOffsetLimit(
|
|
1415
|
+
const { offset, limit } = selToOffsetLimit(sel);
|
|
1314
1416
|
return this.#readArchiveDirectory(
|
|
1315
1417
|
archive,
|
|
1316
1418
|
resolvedArchivePath.absolutePath,
|
|
1317
|
-
|
|
1419
|
+
archiveSubPath,
|
|
1420
|
+
offset,
|
|
1318
1421
|
limit,
|
|
1319
1422
|
details,
|
|
1320
1423
|
signal,
|
|
1321
1424
|
);
|
|
1322
1425
|
}
|
|
1323
1426
|
|
|
1324
|
-
const entry = await archive.readFile(
|
|
1427
|
+
const entry = await archive.readFile(archiveSubPath);
|
|
1325
1428
|
const text = decodeUtf8Text(entry.bytes);
|
|
1326
1429
|
if (text === null) {
|
|
1327
1430
|
return toolResult<ReadToolDetails>(details)
|
|
@@ -1335,26 +1438,26 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
1335
1438
|
.done();
|
|
1336
1439
|
}
|
|
1337
1440
|
|
|
1338
|
-
|
|
1441
|
+
// Archive members are immutable: there is no edit path for bytes inside
|
|
1442
|
+
// an archive, and a hashline tag keyed to the archive file would invite
|
|
1443
|
+
// (and fail) edits while clobbering sibling members' snapshots.
|
|
1444
|
+
const raw = isRawSelector(sel);
|
|
1339
1445
|
const result =
|
|
1340
|
-
isMultiRange(
|
|
1341
|
-
? this.#buildInMemoryMultiRangeResult(text,
|
|
1446
|
+
isMultiRange(sel) && sel.kind === "lines"
|
|
1447
|
+
? this.#buildInMemoryMultiRangeResult(text, sel.ranges, {
|
|
1342
1448
|
details,
|
|
1343
1449
|
sourcePath: resolvedArchivePath.absolutePath,
|
|
1344
1450
|
entityLabel: "archive entry",
|
|
1345
1451
|
raw,
|
|
1452
|
+
immutable: true,
|
|
1346
1453
|
})
|
|
1347
|
-
: this.#buildInMemoryTextResult(
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
entityLabel: "archive entry",
|
|
1355
|
-
raw,
|
|
1356
|
-
},
|
|
1357
|
-
);
|
|
1454
|
+
: this.#buildInMemoryTextResult(text, selToOffsetLimit(sel).offset, selToOffsetLimit(sel).limit, {
|
|
1455
|
+
details,
|
|
1456
|
+
sourcePath: resolvedArchivePath.absolutePath,
|
|
1457
|
+
entityLabel: "archive entry",
|
|
1458
|
+
raw,
|
|
1459
|
+
immutable: true,
|
|
1460
|
+
});
|
|
1358
1461
|
const firstText = result.content.find((content): content is TextContent => content.type === "text");
|
|
1359
1462
|
if (firstText) {
|
|
1360
1463
|
firstText.text = prependSuffixResolutionNotice(firstText.text, resolvedArchivePath.suffixResolution);
|
|
@@ -1459,19 +1562,18 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
1459
1562
|
}
|
|
1460
1563
|
case "raw": {
|
|
1461
1564
|
const result = executeReadQuery(db, selector.sql);
|
|
1565
|
+
let output = renderTable(result.columns, result.rows, {
|
|
1566
|
+
totalCount: result.rows.length,
|
|
1567
|
+
offset: 0,
|
|
1568
|
+
limit: result.rows.length || DEFAULT_MAX_LINES,
|
|
1569
|
+
table: "query",
|
|
1570
|
+
dbPath: resolvedSqlitePath.absolutePath,
|
|
1571
|
+
});
|
|
1572
|
+
if (result.truncated) {
|
|
1573
|
+
output += `\n[Output capped at ${MAX_RAW_QUERY_ROWS} rows; add a LIMIT/OFFSET clause to the query to page through more]`;
|
|
1574
|
+
}
|
|
1462
1575
|
return toolResult<ReadToolDetails>(details)
|
|
1463
|
-
.text(
|
|
1464
|
-
prependSuffixResolutionNotice(
|
|
1465
|
-
renderTable(result.columns, result.rows, {
|
|
1466
|
-
totalCount: result.rows.length,
|
|
1467
|
-
offset: 0,
|
|
1468
|
-
limit: result.rows.length || DEFAULT_MAX_LINES,
|
|
1469
|
-
table: "query",
|
|
1470
|
-
dbPath: resolvedSqlitePath.absolutePath,
|
|
1471
|
-
}),
|
|
1472
|
-
resolvedSqlitePath.suffixResolution,
|
|
1473
|
-
),
|
|
1474
|
-
)
|
|
1576
|
+
.text(prependSuffixResolutionNotice(output, resolvedSqlitePath.suffixResolution))
|
|
1475
1577
|
.sourcePath(resolvedSqlitePath.absolutePath)
|
|
1476
1578
|
.done();
|
|
1477
1579
|
}
|
|
@@ -1696,10 +1798,19 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
1696
1798
|
if (internalRouter.canHandle(readPath)) {
|
|
1697
1799
|
const internalTarget = splitInternalUrlSel(readPath);
|
|
1698
1800
|
const parsed = parseSel(internalTarget.sel);
|
|
1801
|
+
if (internalTarget.sel !== undefined && parsed.kind === "none") {
|
|
1802
|
+
throw new ToolError(
|
|
1803
|
+
`Invalid selector ':${internalTarget.sel}' on '${internalTarget.path}'. Use :N, :N-M, :N+K, :N- (open-ended), a comma-separated list of ranges, :raw, or a range combined with raw (e.g. :raw:50-100).`,
|
|
1804
|
+
);
|
|
1805
|
+
}
|
|
1699
1806
|
return this.#handleInternalUrl(internalTarget.path, parsed, signal);
|
|
1700
1807
|
}
|
|
1701
1808
|
|
|
1702
|
-
|
|
1809
|
+
// One suffix-glob memo per read call — archive, sqlite, and plain-path
|
|
1810
|
+
// resolution share misses instead of re-globbing the workspace.
|
|
1811
|
+
const suffixCache: SuffixMatchCache = new Map();
|
|
1812
|
+
|
|
1813
|
+
const archivePath = await this.#resolveArchiveReadPath(readPath, suffixCache, signal);
|
|
1703
1814
|
if (archivePath) {
|
|
1704
1815
|
const archiveSubPath = splitPathAndSel(archivePath.archiveSubPath);
|
|
1705
1816
|
const archiveParsed = parseSel(archiveSubPath.sel);
|
|
@@ -1711,7 +1822,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
1711
1822
|
);
|
|
1712
1823
|
}
|
|
1713
1824
|
|
|
1714
|
-
const sqlitePath = await this.#resolveSqliteReadPath(readPath, signal);
|
|
1825
|
+
const sqlitePath = await this.#resolveSqliteReadPath(readPath, suffixCache, signal);
|
|
1715
1826
|
if (sqlitePath) {
|
|
1716
1827
|
return this.#readSqlite(sqlitePath, signal);
|
|
1717
1828
|
}
|
|
@@ -1733,7 +1844,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
1733
1844
|
if (isNotFoundError(error)) {
|
|
1734
1845
|
// Attempt unique suffix resolution before falling back to fuzzy suggestions
|
|
1735
1846
|
if (!isRemoteMountPath(absolutePath)) {
|
|
1736
|
-
const suffixMatch = await
|
|
1847
|
+
const suffixMatch = await this.#findSuffixMatchCached(suffixCache, localReadPath, signal);
|
|
1737
1848
|
if (suffixMatch) {
|
|
1738
1849
|
try {
|
|
1739
1850
|
const retryStat = await Bun.file(suffixMatch.absolutePath).stat();
|
|
@@ -1992,6 +2103,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
1992
2103
|
maxBytesForRead,
|
|
1993
2104
|
selectedLineLimit,
|
|
1994
2105
|
undefined, // plain-file read: deterministic and fast, never abort mid-read
|
|
2106
|
+
fileSize > SNAPSHOT_MAX_BYTES, // giant file: don't scan to EOF just for an exact line count
|
|
1995
2107
|
);
|
|
1996
2108
|
|
|
1997
2109
|
const {
|
|
@@ -2001,6 +2113,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
2001
2113
|
stoppedByByteLimit,
|
|
2002
2114
|
firstLinePreview,
|
|
2003
2115
|
firstLineByteLength,
|
|
2116
|
+
reachedEof,
|
|
2004
2117
|
} = streamResult;
|
|
2005
2118
|
|
|
2006
2119
|
// Check if offset is out of bounds - return graceful message instead of throwing
|
|
@@ -2021,6 +2134,25 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
2021
2134
|
// counts in `truncation` keep reflecting the source, not the trimmed
|
|
2022
2135
|
// view — column truncation surfaces separately via `.limits()`.
|
|
2023
2136
|
const rawSelector = isRawSelector(parsed);
|
|
2137
|
+
// Binary sniff: NUL bytes in the collected window mean the file is
|
|
2138
|
+
// not displayable text (binary, or UTF-16 which has NULs in the
|
|
2139
|
+
// ASCII range) — emit a notice instead of mojibake filling the
|
|
2140
|
+
// line budget. `:raw` stays an explicit escape hatch.
|
|
2141
|
+
if (!rawSelector) {
|
|
2142
|
+
for (const line of collectedLines) {
|
|
2143
|
+
if (line.includes("\u0000")) {
|
|
2144
|
+
return toolResult<ReadToolDetails>({ resolvedPath: absolutePath, suffixResolution })
|
|
2145
|
+
.text(
|
|
2146
|
+
prependSuffixResolutionNotice(
|
|
2147
|
+
`[Cannot read binary file '${formatPathRelativeToCwd(absolutePath, this.session.cwd)}' (${formatBytes(fileSize)}); content contains NUL bytes (binary or UTF-16 encoded)]`,
|
|
2148
|
+
suffixResolution,
|
|
2149
|
+
),
|
|
2150
|
+
)
|
|
2151
|
+
.sourcePath(absolutePath)
|
|
2152
|
+
.done();
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
}
|
|
2024
2156
|
const maxColumns = resolveOutputMaxColumns(this.session.settings);
|
|
2025
2157
|
// Column truncation is display-only. `collectedLines` MUST stay
|
|
2026
2158
|
// byte-for-byte with the on-disk content so the snapshot recorded
|
|
@@ -2149,7 +2281,11 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
2149
2281
|
sourcePath = absolutePath;
|
|
2150
2282
|
truncationInfo = {
|
|
2151
2283
|
result: truncation,
|
|
2152
|
-
options: {
|
|
2284
|
+
options: {
|
|
2285
|
+
direction: "head",
|
|
2286
|
+
startLine: startLineDisplay,
|
|
2287
|
+
totalFileLines: reachedEof ? totalFileLines : undefined,
|
|
2288
|
+
},
|
|
2153
2289
|
};
|
|
2154
2290
|
} else if (truncation.truncated) {
|
|
2155
2291
|
outputText = formatBracketAwareText() ?? formatText(truncation.content, startLineDisplay);
|
|
@@ -2157,14 +2293,19 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
2157
2293
|
sourcePath = absolutePath;
|
|
2158
2294
|
truncationInfo = {
|
|
2159
2295
|
result: truncation,
|
|
2160
|
-
options: {
|
|
2296
|
+
options: {
|
|
2297
|
+
direction: "head",
|
|
2298
|
+
startLine: startLineDisplay,
|
|
2299
|
+
totalFileLines: reachedEof ? totalFileLines : undefined,
|
|
2300
|
+
},
|
|
2161
2301
|
};
|
|
2162
|
-
} else if (startLine + userLimitedLines < totalFileLines) {
|
|
2163
|
-
const remaining = totalFileLines - (startLine + userLimitedLines);
|
|
2302
|
+
} else if (startLine + userLimitedLines < totalFileLines || !reachedEof) {
|
|
2164
2303
|
const nextOffset = startLine + userLimitedLines + 1;
|
|
2165
2304
|
|
|
2166
2305
|
outputText = formatBracketAwareText() ?? formatText(truncation.content, startLineDisplay);
|
|
2167
|
-
outputText +=
|
|
2306
|
+
outputText += reachedEof
|
|
2307
|
+
? `\n\n[${totalFileLines - (startLine + userLimitedLines)} more lines in file. Use :${nextOffset} to continue]`
|
|
2308
|
+
: `\n\n[More lines in file (${formatBytes(fileSize)} total; not scanned to EOF). Use :${nextOffset} to continue]`;
|
|
2168
2309
|
details = {};
|
|
2169
2310
|
sourcePath = absolutePath;
|
|
2170
2311
|
} else {
|
|
@@ -521,7 +521,7 @@ function parseDiffSegments(lines: string[]): DiffSegment[] {
|
|
|
521
521
|
|
|
522
522
|
for (const line of lines) {
|
|
523
523
|
const isChange = line.startsWith("+") || line.startsWith("-");
|
|
524
|
-
const isEllipsis = line.trimStart().startsWith("...");
|
|
524
|
+
const isEllipsis = line.trimStart().startsWith("...") || line.trim().length === 0;
|
|
525
525
|
|
|
526
526
|
if (isEllipsis) {
|
|
527
527
|
if (current) segments.push(current);
|
|
@@ -628,7 +628,7 @@ export function truncateDiffByHunk(
|
|
|
628
628
|
const half = Math.ceil(allowedLines / 2);
|
|
629
629
|
if (seg.lines.length > allowedLines) {
|
|
630
630
|
kept.push(...seg.lines.slice(0, half));
|
|
631
|
-
kept.push(
|
|
631
|
+
kept.push("");
|
|
632
632
|
kept.push(...seg.lines.slice(-half));
|
|
633
633
|
} else {
|
|
634
634
|
kept.push(...seg.lines);
|
|
@@ -761,7 +761,7 @@ export function createCachedComponent(
|
|
|
761
761
|
): Component {
|
|
762
762
|
let cached: { key: bigint; lines: string[] } | undefined;
|
|
763
763
|
return {
|
|
764
|
-
render(width: number): string[] {
|
|
764
|
+
render(width: number): readonly string[] {
|
|
765
765
|
const expanded = getExpanded();
|
|
766
766
|
const key = new Hasher().bool(expanded).u32(width).digest();
|
|
767
767
|
if (cached?.key === key) return cached.lines;
|
package/src/tools/resolve.ts
CHANGED
|
@@ -254,7 +254,7 @@ export const resolveToolRenderer = {
|
|
|
254
254
|
const lines = ["", headerLine, "", uiTheme.italic(reason), ""];
|
|
255
255
|
|
|
256
256
|
return {
|
|
257
|
-
render(width: number) {
|
|
257
|
+
render(width: number): readonly string[] {
|
|
258
258
|
const lineWidth = Math.max(3, width);
|
|
259
259
|
const innerWidth = Math.max(1, lineWidth - 2);
|
|
260
260
|
return lines.map(line => {
|