@bastani/atomic 0.9.2 → 0.9.3-alpha.2
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 +66 -0
- package/README.md +2 -2
- package/dist/builtin/cursor/CHANGELOG.md +15 -0
- package/dist/builtin/cursor/README.md +2 -1
- package/dist/builtin/cursor/package.json +2 -2
- package/dist/builtin/cursor/src/cursor-models-raw.json +2 -9
- package/dist/builtin/cursor/src/model-mapper.ts +14 -3
- package/dist/builtin/cursor/src/proto/protobuf-codec-base64.ts +22 -0
- package/dist/builtin/cursor/src/proto/protobuf-codec-request.ts +53 -13
- package/dist/builtin/cursor/src/proto/protobuf-codec-wire.ts +24 -7
- package/dist/builtin/cursor/src/proto/protobuf-codec.ts +3 -2
- package/dist/builtin/cursor/src/stream.ts +5 -11
- package/dist/builtin/cursor/src/transport-types.ts +3 -0
- package/dist/builtin/cursor/src/transport.ts +1 -0
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/CHANGELOG.md +6 -0
- package/dist/builtin/mcp/direct-tools.ts +4 -2
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/mcp/proxy-call.ts +3 -1
- package/dist/builtin/mcp/utils.ts +18 -7
- package/dist/builtin/subagents/CHANGELOG.md +20 -0
- package/dist/builtin/subagents/README.md +6 -6
- package/dist/builtin/subagents/agents/code-simplifier.md +7 -6
- package/dist/builtin/subagents/agents/codebase-analyzer.md +5 -4
- package/dist/builtin/subagents/agents/codebase-locator.md +3 -3
- package/dist/builtin/subagents/agents/codebase-online-researcher.md +10 -10
- package/dist/builtin/subagents/agents/codebase-pattern-finder.md +4 -4
- package/dist/builtin/subagents/agents/codebase-research-analyzer.md +3 -3
- package/dist/builtin/subagents/agents/codebase-research-locator.md +4 -4
- package/dist/builtin/subagents/agents/debugger.md +5 -5
- package/dist/builtin/subagents/agents/worker.md +56 -0
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/subagents/skills/subagent/SKILL.md +11 -11
- package/dist/builtin/subagents/src/agents/agent-loaders.ts +3 -5
- package/dist/builtin/subagents/src/agents/agent-management-helpers.ts +3 -3
- package/dist/builtin/subagents/src/extension/fanout-child.ts +1 -0
- package/dist/builtin/subagents/src/extension/index.ts +6 -3
- package/dist/builtin/subagents/src/extension/schemas.ts +2 -7
- package/dist/builtin/subagents/src/intercom/result-intercom.ts +4 -3
- package/dist/builtin/subagents/src/runs/background/async-job-tracker.ts +1 -4
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-single.ts +15 -1
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor.ts +35 -1
- package/dist/builtin/subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +1 -1
- package/dist/builtin/subagents/src/runs/shared/nested-render.ts +2 -2
- package/dist/builtin/subagents/src/runs/shared/pi-args.ts +2 -1
- package/dist/builtin/subagents/src/runs/shared/subagent-prompt-runtime.ts +4 -2
- package/dist/builtin/subagents/src/shared/types-async.ts +1 -0
- package/dist/builtin/subagents/src/shared/types-depth.ts +5 -5
- package/dist/builtin/subagents/src/shared/types-runtime.ts +2 -1
- package/dist/builtin/subagents/src/slash/prompt-template-bridge.ts +27 -5
- package/dist/builtin/subagents/src/tui/render-event-formatting.ts +2 -2
- package/dist/builtin/subagents/src/tui/render-layout.ts +27 -4
- package/dist/builtin/subagents/src/tui/render-result-animation.ts +22 -31
- package/dist/builtin/subagents/src/tui/render-result-compact.ts +6 -6
- package/dist/builtin/subagents/src/tui/render-result.ts +20 -19
- package/dist/builtin/subagents/src/tui/render-status-progress.ts +3 -3
- package/dist/builtin/subagents/src/tui/render-widget.ts +46 -7
- package/dist/builtin/subagents/src/tui/render.ts +2 -2
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +56 -0
- package/dist/builtin/workflows/README.md +3 -3
- package/dist/builtin/workflows/builtin/goal-artifacts.ts +11 -6
- package/dist/builtin/workflows/builtin/goal-ledger.ts +33 -1
- package/dist/builtin/workflows/builtin/goal-prompts.ts +23 -28
- package/dist/builtin/workflows/builtin/goal-reducer.ts +2 -2
- package/dist/builtin/workflows/builtin/goal-reports.ts +2 -5
- package/dist/builtin/workflows/builtin/goal-review.ts +1 -1
- package/dist/builtin/workflows/builtin/goal-runner.ts +10 -17
- package/dist/builtin/workflows/builtin/open-claude-design-feedback.ts +3 -3
- package/dist/builtin/workflows/builtin/open-claude-design-phases.ts +1 -3
- package/dist/builtin/workflows/builtin/open-claude-design-setup.ts +1 -1
- package/dist/builtin/workflows/builtin/ralph-core.ts +7 -17
- package/dist/builtin/workflows/builtin/ralph-runner.ts +11 -18
- package/dist/builtin/workflows/builtin/shared-prompts.ts +1 -1
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/src/authoring.d.ts +1 -1
- package/dist/builtin/workflows/src/durable/backend.ts +343 -0
- package/dist/builtin/workflows/src/durable/child-primitive.ts +79 -0
- package/dist/builtin/workflows/src/durable/dbos-backend.ts +421 -0
- package/dist/builtin/workflows/src/durable/dbos-envelope.ts +171 -0
- package/dist/builtin/workflows/src/durable/factory.ts +96 -0
- package/dist/builtin/workflows/src/durable/file-backend.ts +433 -0
- package/dist/builtin/workflows/src/durable/index.ts +73 -0
- package/dist/builtin/workflows/src/durable/resume-catalog.ts +217 -0
- package/dist/builtin/workflows/src/durable/resume-runtime.ts +299 -0
- package/dist/builtin/workflows/src/durable/scoped-backend.ts +171 -0
- package/dist/builtin/workflows/src/durable/stage-primitive.ts +284 -0
- package/dist/builtin/workflows/src/durable/tool-primitive.ts +180 -0
- package/dist/builtin/workflows/src/durable/types.ts +168 -0
- package/dist/builtin/workflows/src/durable/ui-primitive.ts +96 -0
- package/dist/builtin/workflows/src/engine/options.ts +3 -0
- package/dist/builtin/workflows/src/engine/primitives/parallel.ts +2 -2
- package/dist/builtin/workflows/src/engine/primitives/task.ts +4 -4
- package/dist/builtin/workflows/src/engine/primitives/ui.ts +22 -8
- package/dist/builtin/workflows/src/engine/primitives/workflow.ts +8 -0
- package/dist/builtin/workflows/src/engine/run-durable-finalize.ts +69 -0
- package/dist/builtin/workflows/src/engine/run-durable-stage-session.ts +31 -0
- package/dist/builtin/workflows/src/engine/run.ts +148 -6
- package/dist/builtin/workflows/src/engine/runtime.ts +8 -2
- package/dist/builtin/workflows/src/extension/config-loader.ts +35 -15
- package/dist/builtin/workflows/src/extension/discovery.ts +20 -8
- package/dist/builtin/workflows/src/extension/extension-factory.ts +6 -12
- package/dist/builtin/workflows/src/extension/extension-lifecycle.ts +5 -1
- package/dist/builtin/workflows/src/extension/extension-runtime-state.ts +4 -2
- package/dist/builtin/workflows/src/extension/runtime.ts +48 -9
- package/dist/builtin/workflows/src/extension/wiring.ts +1 -1
- package/dist/builtin/workflows/src/extension/workflow-run-control-command.ts +143 -4
- package/dist/builtin/workflows/src/runs/background/quit.ts +61 -0
- package/dist/builtin/workflows/src/runs/background/status.ts +1 -0
- package/dist/builtin/workflows/src/runs/foreground/executor-direct-helpers.ts +5 -5
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-call.ts +74 -33
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-context.ts +20 -1
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-factory.ts +8 -7
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-replay.ts +1 -0
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-types.ts +1 -1
- package/dist/builtin/workflows/src/runs/foreground/executor-types.ts +19 -2
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-context.ts +4 -0
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-controller.ts +10 -10
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-options.ts +5 -1
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-send-user-message.ts +25 -0
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-types.ts +3 -0
- package/dist/builtin/workflows/src/shared/authoring-contract-stage.d.ts +16 -0
- package/dist/builtin/workflows/src/shared/authoring-contract-stage.ts +20 -0
- package/dist/builtin/workflows/src/shared/authoring-contract-ui.d.ts +23 -1
- package/dist/builtin/workflows/src/shared/authoring-contract-ui.ts +30 -1
- package/dist/builtin/workflows/src/shared/store-public-types.ts +6 -2
- package/dist/builtin/workflows/src/shared/store-run-methods.ts +12 -6
- package/dist/builtin/workflows/src/shared/types.ts +55 -0
- package/dist/builtin/workflows/src/tui/dispatch-confirm.ts +11 -10
- package/dist/builtin/workflows/src/tui/graph-view-constants.ts +1 -1
- package/dist/builtin/workflows/src/tui/graph-view-graph-render.ts +41 -0
- package/dist/builtin/workflows/src/tui/graph-view-input.ts +82 -24
- package/dist/builtin/workflows/src/tui/graph-view-render.ts +7 -0
- package/dist/builtin/workflows/src/tui/graph-view-state.ts +22 -2
- package/dist/builtin/workflows/src/tui/graph-view-types.ts +4 -5
- package/dist/builtin/workflows/src/tui/overlay-adapter.ts +9 -11
- package/dist/builtin/workflows/src/tui/stage-chat-view-footer-status.ts +9 -3
- package/dist/builtin/workflows/src/tui/stage-chat-view-input.ts +11 -2
- package/dist/builtin/workflows/src/tui/stage-chat-view-live-events.ts +35 -0
- package/dist/builtin/workflows/src/tui/stage-chat-view-state.ts +51 -17
- package/dist/builtin/workflows/src/tui/stage-chat-view-status.ts +36 -0
- package/dist/builtin/workflows/src/tui/stage-chat-view-types.ts +5 -1
- package/dist/builtin/workflows/src/tui/stage-chat-view.ts +3 -1
- package/dist/builtin/workflows/src/tui/status-list.ts +14 -2
- package/dist/builtin/workflows/src/tui/widget.ts +23 -8
- package/dist/builtin/workflows/src/tui/workflow-attach-pane-types.ts +5 -4
- package/dist/builtin/workflows/src/tui/workflow-attach-pane.ts +8 -8
- package/dist/builtin/workflows/src/tui/workflow-resume-selector.ts +151 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +9 -9
- package/dist/cli/args.js.map +1 -1
- package/dist/config-self-update.d.ts.map +1 -1
- package/dist/config-self-update.js +3 -4
- package/dist/config-self-update.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +4 -5
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session-bash.d.ts +1 -0
- package/dist/core/agent-session-bash.d.ts.map +1 -1
- package/dist/core/agent-session-bash.js +1 -0
- package/dist/core/agent-session-bash.js.map +1 -1
- package/dist/core/agent-session-tool-registry.d.ts.map +1 -1
- package/dist/core/agent-session-tool-registry.js +23 -0
- package/dist/core/agent-session-tool-registry.js.map +1 -1
- package/dist/core/bash-executor.d.ts +2 -0
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +1 -0
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts +29 -0
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +36 -1
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/compaction/context-compaction-metrics.d.ts +14 -2
- package/dist/core/compaction/context-compaction-metrics.d.ts.map +1 -1
- package/dist/core/compaction/context-compaction-metrics.js +50 -1
- package/dist/core/compaction/context-compaction-metrics.js.map +1 -1
- package/dist/core/compaction/context-compaction-prompt.d.ts.map +1 -1
- package/dist/core/compaction/context-compaction-prompt.js +2 -0
- package/dist/core/compaction/context-compaction-prompt.js.map +1 -1
- package/dist/core/compaction/context-compaction-runner.d.ts.map +1 -1
- package/dist/core/compaction/context-compaction-runner.js +1 -1
- package/dist/core/compaction/context-compaction-runner.js.map +1 -1
- package/dist/core/compaction/context-deletion-application.d.ts.map +1 -1
- package/dist/core/compaction/context-deletion-application.js +5 -5
- package/dist/core/compaction/context-deletion-application.js.map +1 -1
- package/dist/core/compaction/context-deletion-targets.d.ts +2 -0
- package/dist/core/compaction/context-deletion-targets.d.ts.map +1 -1
- package/dist/core/compaction/context-deletion-targets.js +23 -3
- package/dist/core/compaction/context-deletion-targets.js.map +1 -1
- package/dist/core/compaction/context-deletion-tool-definitions.d.ts +6 -0
- package/dist/core/compaction/context-deletion-tool-definitions.d.ts.map +1 -1
- package/dist/core/compaction/context-deletion-tool-definitions.js.map +1 -1
- package/dist/core/compaction/context-deletion-tools.d.ts.map +1 -1
- package/dist/core/compaction/context-deletion-tools.js +18 -10
- package/dist/core/compaction/context-deletion-tools.js.map +1 -1
- package/dist/core/compaction/context-transcript-analysis.d.ts.map +1 -1
- package/dist/core/compaction/context-transcript-analysis.js +2 -4
- package/dist/core/compaction/context-transcript-analysis.js.map +1 -1
- package/dist/core/copilot-gemini-tool-arguments.d.ts.map +1 -1
- package/dist/core/copilot-gemini-tool-arguments.js +2 -60
- package/dist/core/copilot-gemini-tool-arguments.js.map +1 -1
- package/dist/core/extensions/context-types.d.ts +2 -0
- package/dist/core/extensions/context-types.d.ts.map +1 -1
- package/dist/core/extensions/context-types.js.map +1 -1
- package/dist/core/extensions/index.d.ts +2 -2
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/loader-virtual-modules.d.ts.map +1 -1
- package/dist/core/extensions/loader-virtual-modules.js +57 -32
- package/dist/core/extensions/loader-virtual-modules.js.map +1 -1
- package/dist/core/extensions/runner-context.d.ts.map +1 -1
- package/dist/core/extensions/runner-context.js +11 -0
- package/dist/core/extensions/runner-context.js.map +1 -1
- package/dist/core/extensions/tool-events.d.ts +13 -13
- package/dist/core/extensions/tool-events.d.ts.map +1 -1
- package/dist/core/extensions/tool-events.js +3 -3
- package/dist/core/extensions/tool-events.js.map +1 -1
- package/dist/core/extensions/types.d.ts +1 -1
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/flattened-tool-arguments.d.ts +18 -0
- package/dist/core/flattened-tool-arguments.d.ts.map +1 -1
- package/dist/core/flattened-tool-arguments.js +104 -0
- package/dist/core/flattened-tool-arguments.js.map +1 -1
- package/dist/core/messages.d.ts +1 -0
- package/dist/core/messages.d.ts.map +1 -1
- package/dist/core/messages.js +46 -1
- package/dist/core/messages.js.map +1 -1
- package/dist/core/sdk-exports.d.ts +1 -1
- package/dist/core/sdk-exports.d.ts.map +1 -1
- package/dist/core/sdk-exports.js +1 -1
- package/dist/core/sdk-exports.js.map +1 -1
- package/dist/core/sdk-types.d.ts +2 -2
- package/dist/core/sdk-types.d.ts.map +1 -1
- package/dist/core/sdk-types.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +12 -0
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager-core.d.ts +15 -7
- package/dist/core/session-manager-core.d.ts.map +1 -1
- package/dist/core/session-manager-core.js +20 -9
- package/dist/core/session-manager-core.js.map +1 -1
- package/dist/core/session-manager-entries.d.ts +2 -2
- package/dist/core/session-manager-entries.d.ts.map +1 -1
- package/dist/core/session-manager-entries.js +9 -3
- package/dist/core/session-manager-entries.js.map +1 -1
- package/dist/core/session-manager-history.d.ts.map +1 -1
- package/dist/core/session-manager-history.js +2 -1
- package/dist/core/session-manager-history.js.map +1 -1
- package/dist/core/session-manager-list.d.ts +3 -3
- package/dist/core/session-manager-list.d.ts.map +1 -1
- package/dist/core/session-manager-list.js +27 -8
- package/dist/core/session-manager-list.js.map +1 -1
- package/dist/core/session-manager-storage.d.ts +3 -1
- package/dist/core/session-manager-storage.d.ts.map +1 -1
- package/dist/core/session-manager-storage.js +55 -12
- package/dist/core/session-manager-storage.js.map +1 -1
- package/dist/core/session-manager-tool-dependencies.d.ts +10 -0
- package/dist/core/session-manager-tool-dependencies.d.ts.map +1 -0
- package/dist/core/session-manager-tool-dependencies.js +133 -0
- package/dist/core/session-manager-tool-dependencies.js.map +1 -0
- package/dist/core/session-manager-types.d.ts +22 -0
- package/dist/core/session-manager-types.d.ts.map +1 -1
- package/dist/core/session-manager-types.js.map +1 -1
- package/dist/core/session-manager.d.ts +2 -2
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +1 -1
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager-basic-accessors.d.ts +4 -0
- package/dist/core/settings-manager-basic-accessors.d.ts.map +1 -1
- package/dist/core/settings-manager-basic-accessors.js +18 -0
- package/dist/core/settings-manager-basic-accessors.js.map +1 -1
- package/dist/core/settings-manager-resource-accessors.d.ts +4 -0
- package/dist/core/settings-manager-resource-accessors.d.ts.map +1 -1
- package/dist/core/settings-manager-resource-accessors.js +15 -0
- package/dist/core/settings-manager-resource-accessors.js.map +1 -1
- package/dist/core/settings-types.d.ts +11 -0
- package/dist/core/settings-types.d.ts.map +1 -1
- package/dist/core/settings-types.js.map +1 -1
- package/dist/core/system-prompt.d.ts +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +3 -2
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/artifact-protocol.d.ts +11 -0
- package/dist/core/tools/artifact-protocol.d.ts.map +1 -0
- package/dist/core/tools/artifact-protocol.js +76 -0
- package/dist/core/tools/artifact-protocol.js.map +1 -0
- package/dist/core/tools/artifacts.d.ts +18 -0
- package/dist/core/tools/artifacts.d.ts.map +1 -0
- package/dist/core/tools/artifacts.js +90 -0
- package/dist/core/tools/artifacts.js.map +1 -0
- package/dist/core/tools/bash-async-jobs.d.ts +20 -0
- package/dist/core/tools/bash-async-jobs.d.ts.map +1 -0
- package/dist/core/tools/bash-async-jobs.js +59 -0
- package/dist/core/tools/bash-async-jobs.js.map +1 -0
- package/dist/core/tools/bash-async-output.d.ts +10 -0
- package/dist/core/tools/bash-async-output.d.ts.map +1 -0
- package/dist/core/tools/bash-async-output.js +80 -0
- package/dist/core/tools/bash-async-output.js.map +1 -0
- package/dist/core/tools/bash-interceptor.d.ts +10 -0
- package/dist/core/tools/bash-interceptor.d.ts.map +1 -0
- package/dist/core/tools/bash-interceptor.js +39 -0
- package/dist/core/tools/bash-interceptor.js.map +1 -0
- package/dist/core/tools/bash-leading-cd.d.ts +7 -0
- package/dist/core/tools/bash-leading-cd.d.ts.map +1 -0
- package/dist/core/tools/bash-leading-cd.js +59 -0
- package/dist/core/tools/bash-leading-cd.js.map +1 -0
- package/dist/core/tools/bash-pty-native.d.ts +14 -0
- package/dist/core/tools/bash-pty-native.d.ts.map +1 -0
- package/dist/core/tools/bash-pty-native.js +71 -0
- package/dist/core/tools/bash-pty-native.js.map +1 -0
- package/dist/core/tools/bash.d.ts +28 -17
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +152 -35
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/block-resolver.d.ts +16 -0
- package/dist/core/tools/block-resolver.d.ts.map +1 -0
- package/dist/core/tools/block-resolver.js +74 -0
- package/dist/core/tools/block-resolver.js.map +1 -0
- package/dist/core/tools/conflict-registry.d.ts +16 -0
- package/dist/core/tools/conflict-registry.d.ts.map +1 -0
- package/dist/core/tools/conflict-registry.js +44 -0
- package/dist/core/tools/conflict-registry.js.map +1 -0
- package/dist/core/tools/directory-tree.d.ts +13 -0
- package/dist/core/tools/directory-tree.d.ts.map +1 -0
- package/dist/core/tools/directory-tree.js +81 -0
- package/dist/core/tools/directory-tree.js.map +1 -0
- package/dist/core/tools/edit.d.ts +4 -29
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +136 -228
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/fetch-url.d.ts +74 -0
- package/dist/core/tools/fetch-url.d.ts.map +1 -0
- package/dist/core/tools/fetch-url.js +518 -0
- package/dist/core/tools/fetch-url.js.map +1 -0
- package/dist/core/tools/find.d.ts +27 -9
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +400 -176
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/glob-path-utils.d.ts +8 -0
- package/dist/core/tools/glob-path-utils.d.ts.map +1 -0
- package/dist/core/tools/glob-path-utils.js +26 -0
- package/dist/core/tools/glob-path-utils.js.map +1 -0
- package/dist/core/tools/grep.d.ts +12 -0
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +141 -17
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/hashline-engine/apply.d.ts +11 -0
- package/dist/core/tools/hashline-engine/apply.d.ts.map +1 -0
- package/dist/core/tools/hashline-engine/apply.js +752 -0
- package/dist/core/tools/hashline-engine/apply.js.map +1 -0
- package/dist/core/tools/hashline-engine/block.d.ts +40 -0
- package/dist/core/tools/hashline-engine/block.d.ts.map +1 -0
- package/dist/core/tools/hashline-engine/block.js +117 -0
- package/dist/core/tools/hashline-engine/block.js.map +1 -0
- package/dist/core/tools/hashline-engine/diff-preview.d.ts +15 -0
- package/dist/core/tools/hashline-engine/diff-preview.d.ts.map +1 -0
- package/dist/core/tools/hashline-engine/diff-preview.js +98 -0
- package/dist/core/tools/hashline-engine/diff-preview.js.map +1 -0
- package/dist/core/tools/hashline-engine/format.d.ts +71 -0
- package/dist/core/tools/hashline-engine/format.d.ts.map +1 -0
- package/dist/core/tools/hashline-engine/format.js +178 -0
- package/dist/core/tools/hashline-engine/format.js.map +1 -0
- package/dist/core/tools/hashline-engine/fs.d.ts +81 -0
- package/dist/core/tools/hashline-engine/fs.d.ts.map +1 -0
- package/dist/core/tools/hashline-engine/fs.js +143 -0
- package/dist/core/tools/hashline-engine/fs.js.map +1 -0
- package/dist/core/tools/hashline-engine/index.d.ts +18 -0
- package/dist/core/tools/hashline-engine/index.d.ts.map +1 -0
- package/dist/core/tools/hashline-engine/index.js +20 -0
- package/dist/core/tools/hashline-engine/index.js.map +1 -0
- package/dist/core/tools/hashline-engine/input.d.ts +101 -0
- package/dist/core/tools/hashline-engine/input.d.ts.map +1 -0
- package/dist/core/tools/hashline-engine/input.js +398 -0
- package/dist/core/tools/hashline-engine/input.js.map +1 -0
- package/dist/core/tools/hashline-engine/messages.d.ts +99 -0
- package/dist/core/tools/hashline-engine/messages.d.ts.map +1 -0
- package/dist/core/tools/hashline-engine/messages.js +144 -0
- package/dist/core/tools/hashline-engine/messages.js.map +1 -0
- package/dist/core/tools/hashline-engine/mismatch.d.ts +45 -0
- package/dist/core/tools/hashline-engine/mismatch.d.ts.map +1 -0
- package/dist/core/tools/hashline-engine/mismatch.js +90 -0
- package/dist/core/tools/hashline-engine/mismatch.js.map +1 -0
- package/dist/core/tools/hashline-engine/normalize.d.ts +21 -0
- package/dist/core/tools/hashline-engine/normalize.d.ts.map +1 -0
- package/dist/core/tools/hashline-engine/normalize.js +33 -0
- package/dist/core/tools/hashline-engine/normalize.js.map +1 -0
- package/dist/core/tools/hashline-engine/parser.d.ts +24 -0
- package/dist/core/tools/hashline-engine/parser.d.ts.map +1 -0
- package/dist/core/tools/hashline-engine/parser.js +381 -0
- package/dist/core/tools/hashline-engine/parser.js.map +1 -0
- package/dist/core/tools/hashline-engine/patcher.d.ts +118 -0
- package/dist/core/tools/hashline-engine/patcher.d.ts.map +1 -0
- package/dist/core/tools/hashline-engine/patcher.js +341 -0
- package/dist/core/tools/hashline-engine/patcher.js.map +1 -0
- package/dist/core/tools/hashline-engine/prefixes.d.ts +43 -0
- package/dist/core/tools/hashline-engine/prefixes.d.ts.map +1 -0
- package/dist/core/tools/hashline-engine/prefixes.js +135 -0
- package/dist/core/tools/hashline-engine/prefixes.js.map +1 -0
- package/dist/core/tools/hashline-engine/recovery.d.ts +41 -0
- package/dist/core/tools/hashline-engine/recovery.d.ts.map +1 -0
- package/dist/core/tools/hashline-engine/recovery.js +168 -0
- package/dist/core/tools/hashline-engine/recovery.js.map +1 -0
- package/dist/core/tools/hashline-engine/snapshots.d.ts +65 -0
- package/dist/core/tools/hashline-engine/snapshots.d.ts.map +1 -0
- package/dist/core/tools/hashline-engine/snapshots.js +108 -0
- package/dist/core/tools/hashline-engine/snapshots.js.map +1 -0
- package/dist/core/tools/hashline-engine/stream.d.ts +3 -0
- package/dist/core/tools/hashline-engine/stream.d.ts.map +1 -0
- package/dist/core/tools/hashline-engine/stream.js +111 -0
- package/dist/core/tools/hashline-engine/stream.js.map +1 -0
- package/dist/core/tools/hashline-engine/tokenizer.d.ts +69 -0
- package/dist/core/tools/hashline-engine/tokenizer.d.ts.map +1 -0
- package/dist/core/tools/hashline-engine/tokenizer.js +430 -0
- package/dist/core/tools/hashline-engine/tokenizer.js.map +1 -0
- package/dist/core/tools/hashline-engine/types.d.ts +166 -0
- package/dist/core/tools/hashline-engine/types.d.ts.map +1 -0
- package/dist/core/tools/hashline-engine/types.js +9 -0
- package/dist/core/tools/hashline-engine/types.js.map +1 -0
- package/dist/core/tools/hashline.d.ts +29 -0
- package/dist/core/tools/hashline.d.ts.map +1 -0
- package/dist/core/tools/hashline.js +110 -0
- package/dist/core/tools/hashline.js.map +1 -0
- package/dist/core/tools/index.d.ts +6 -4
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +52 -35
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/notebook.d.ts +38 -0
- package/dist/core/tools/notebook.d.ts.map +1 -0
- package/dist/core/tools/notebook.js +125 -0
- package/dist/core/tools/notebook.js.map +1 -0
- package/dist/core/tools/read-document-extract.d.ts +9 -0
- package/dist/core/tools/read-document-extract.d.ts.map +1 -0
- package/dist/core/tools/read-document-extract.js +212 -0
- package/dist/core/tools/read-document-extract.js.map +1 -0
- package/dist/core/tools/read-selectors.d.ts +24 -0
- package/dist/core/tools/read-selectors.d.ts.map +1 -0
- package/dist/core/tools/read-selectors.js +277 -0
- package/dist/core/tools/read-selectors.js.map +1 -0
- package/dist/core/tools/read-url.d.ts +37 -0
- package/dist/core/tools/read-url.d.ts.map +1 -0
- package/dist/core/tools/read-url.js +39 -0
- package/dist/core/tools/read-url.js.map +1 -0
- package/dist/core/tools/read.d.ts +11 -11
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +224 -94
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/resource-selectors.d.ts +44 -0
- package/dist/core/tools/resource-selectors.d.ts.map +1 -0
- package/dist/core/tools/resource-selectors.js +808 -0
- package/dist/core/tools/resource-selectors.js.map +1 -0
- package/dist/core/tools/search-details.d.ts +26 -0
- package/dist/core/tools/search-details.d.ts.map +1 -0
- package/dist/core/tools/search-details.js +24 -0
- package/dist/core/tools/search-details.js.map +1 -0
- package/dist/core/tools/search-line-ranges.d.ts +11 -0
- package/dist/core/tools/search-line-ranges.d.ts.map +1 -0
- package/dist/core/tools/search-line-ranges.js +65 -0
- package/dist/core/tools/search-line-ranges.js.map +1 -0
- package/dist/core/tools/search-native.d.ts +97 -0
- package/dist/core/tools/search-native.d.ts.map +1 -0
- package/dist/core/tools/search-native.js +27 -0
- package/dist/core/tools/search-native.js.map +1 -0
- package/dist/core/tools/search.d.ts +24 -0
- package/dist/core/tools/search.d.ts.map +1 -0
- package/dist/core/tools/search.js +573 -0
- package/dist/core/tools/search.js.map +1 -0
- package/dist/core/tools/truncate.d.ts +4 -4
- package/dist/core/tools/truncate.d.ts.map +1 -1
- package/dist/core/tools/truncate.js +3 -3
- package/dist/core/tools/truncate.js.map +1 -1
- package/dist/core/tools/url-ip-guards.d.ts +4 -0
- package/dist/core/tools/url-ip-guards.d.ts.map +1 -0
- package/dist/core/tools/url-ip-guards.js +126 -0
- package/dist/core/tools/url-ip-guards.js.map +1 -0
- package/dist/core/tools/write.d.ts +12 -2
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +166 -14
- package/dist/core/tools/write.js.map +1 -1
- package/dist/core/trust-manager.d.ts.map +1 -1
- package/dist/core/trust-manager.js +2 -3
- package/dist/core/trust-manager.js.map +1 -1
- package/dist/index-extensions.d.ts +2 -2
- package/dist/index-extensions.d.ts.map +1 -1
- package/dist/index-extensions.js +1 -1
- package/dist/index-extensions.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-runtime.d.ts +1 -0
- package/dist/modes/interactive/components/chat-session-host-runtime.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-runtime.js +12 -0
- package/dist/modes/interactive/components/chat-session-host-runtime.js.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-terminal-cleanup.d.ts +4 -0
- package/dist/modes/interactive/components/chat-session-host-terminal-cleanup.d.ts.map +1 -0
- package/dist/modes/interactive/components/chat-session-host-terminal-cleanup.js +131 -0
- package/dist/modes/interactive/components/chat-session-host-terminal-cleanup.js.map +1 -0
- package/dist/modes/interactive/components/chat-session-host.d.ts +2 -0
- package/dist/modes/interactive/components/chat-session-host.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-session-host.js +7 -1
- package/dist/modes/interactive/components/chat-session-host.js.map +1 -1
- package/dist/modes/interactive/components/chat-transcript.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-transcript.js +15 -4
- package/dist/modes/interactive/components/chat-transcript.js.map +1 -1
- package/dist/modes/interactive/components/custom-editor.d.ts +1 -0
- package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-editor.js +9 -2
- package/dist/modes/interactive/components/custom-editor.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector-handlers.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector-handlers.js +3 -0
- package/dist/modes/interactive/components/settings-selector-handlers.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector-items.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector-items.js +7 -0
- package/dist/modes/interactive/components/settings-selector-items.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector-types.d.ts +2 -0
- package/dist/modes/interactive/components/settings-selector-types.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector-types.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +3 -0
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +26 -0
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/tree-selector-content.d.ts.map +1 -1
- package/dist/modes/interactive/components/tree-selector-content.js +0 -5
- package/dist/modes/interactive/components/tree-selector-content.js.map +1 -1
- package/dist/modes/interactive/interactive-auth-login.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-auth-login.js +1 -0
- package/dist/modes/interactive/interactive-auth-login.js.map +1 -1
- package/dist/modes/interactive/interactive-autocomplete.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-autocomplete.js +80 -2
- package/dist/modes/interactive/interactive-autocomplete.js.map +1 -1
- package/dist/modes/interactive/interactive-hotkeys-debug.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-hotkeys-debug.js +3 -0
- package/dist/modes/interactive/interactive-hotkeys-debug.js.map +1 -1
- package/dist/modes/interactive/interactive-input-handling.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-input-handling.js +51 -0
- package/dist/modes/interactive/interactive-input-handling.js.map +1 -1
- package/dist/modes/interactive/interactive-mode-base.d.ts +5 -0
- package/dist/modes/interactive/interactive-mode-base.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode-base.js +5 -0
- package/dist/modes/interactive/interactive-mode-base.js.map +1 -1
- package/dist/modes/interactive/interactive-mode-deps.d.ts +1 -1
- package/dist/modes/interactive/interactive-mode-deps.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode-deps.js.map +1 -1
- package/dist/modes/interactive/interactive-mode-surface.d.ts +12 -0
- package/dist/modes/interactive/interactive-mode-surface.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode-surface.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +1 -0
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/interactive-model-routing.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-model-routing.js +4 -1
- package/dist/modes/interactive/interactive-model-routing.js.map +1 -1
- package/dist/modes/interactive/interactive-onboarding.d.ts +11 -0
- package/dist/modes/interactive/interactive-onboarding.d.ts.map +1 -0
- package/dist/modes/interactive/interactive-onboarding.js +220 -0
- package/dist/modes/interactive/interactive-onboarding.js.map +1 -0
- package/dist/modes/interactive/interactive-selectors.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-selectors.js +4 -0
- package/dist/modes/interactive/interactive-selectors.js.map +1 -1
- package/dist/modes/interactive/interactive-session-routing.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-session-routing.js +6 -0
- package/dist/modes/interactive/interactive-session-routing.js.map +1 -1
- package/dist/modes/interactive/interactive-slash-commands.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-slash-commands.js +9 -4
- package/dist/modes/interactive/interactive-slash-commands.js.map +1 -1
- package/dist/modes/interactive/interactive-startup.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-startup.js +28 -0
- package/dist/modes/interactive/interactive-startup.js.map +1 -1
- package/dist/utils/child-process.d.ts.map +1 -1
- package/dist/utils/child-process.js +21 -1
- package/dist/utils/child-process.js.map +1 -1
- package/dist/utils/markit.d.ts +8 -0
- package/dist/utils/markit.d.ts.map +1 -0
- package/dist/utils/markit.js +53 -0
- package/dist/utils/markit.js.map +1 -0
- package/dist/utils/paths.d.ts +2 -1
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +14 -1
- package/dist/utils/paths.js.map +1 -1
- package/docs/compaction.md +18 -1
- package/docs/containerization.md +1 -1
- package/docs/docs.json +1 -0
- package/docs/extensions.md +25 -36
- package/docs/models.md +1 -1
- package/docs/providers.md +2 -1
- package/docs/quickstart.md +11 -6
- package/docs/sdk.md +5 -5
- package/docs/session-format.md +6 -0
- package/docs/sessions.md +6 -0
- package/docs/settings.md +7 -0
- package/docs/subagents.md +3 -2
- package/docs/tools.md +49 -0
- package/docs/usage.md +3 -3
- package/docs/workflows.md +112 -8
- package/examples/extensions/subagent/README.md +5 -5
- package/examples/extensions/subagent/agents/planner.md +1 -1
- package/examples/extensions/subagent/agents/reviewer.md +1 -1
- package/examples/extensions/subagent/agents/scout.md +2 -2
- package/examples/extensions/subagent/display.ts +3 -3
- package/examples/sdk/05-tools.ts +3 -3
- package/examples/sdk/README.md +1 -1
- package/package.json +5 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../src/core/tools/search.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAE/D,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,KAAK,EAAE,cAAc,EAA2B,MAAM,wBAAwB,CAAC;AACtF,OAAO,EAEN,KAAK,eAAe,EACpB,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAuD,KAAK,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAKhH,OAAO,EAAsB,KAAK,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAGjF,YAAY,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,QAAA,MAAM,YAAY;;;;;;;EAYiB,CAAC;AACpC,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,OAAO,YAAY,CAAC,CAAC;AAC1D,MAAM,MAAM,iBAAiB,GAAG,eAAe,GAAG;IAAE,aAAa,CAAC,EAAE,qBAAqB,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AA2N3I,wBAAgB,0BAA0B,CACzC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,iBAAiB,GACzB,cAAc,CAAC,OAAO,YAAY,EAAE,iBAAiB,GAAG,SAAS,CAAC,CAyMpE;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAC,OAAO,YAAY,CAAC,CAEzG","sourcesContent":["import { existsSync } from \"node:fs\";\nimport { readFile as fsReadFile, stat as fsStat } from \"node:fs/promises\";\nimport { dirname, join, resolve as resolvePath } from \"node:path\";\nimport type { AgentTool } from \"@earendil-works/pi-agent-core\";\nimport { Text } from \"@earendil-works/pi-tui\";\nimport { type Static, Type } from \"typebox\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../extensions/types.ts\";\nimport {\n\tcreateGrepToolDefinition,\n\ttype GrepToolOptions,\n} from \"./grep.ts\";\nimport { normalizePathLikeInput, splitPathLikeGlob } from \"./glob-path-utils.ts\";\nimport { createHashlineSnapshotStore, recordHashlineSnapshot, type HashlineSnapshotStore } from \"./hashline.ts\";\nimport { invalidArgText, shortenPath, str } from \"./render-utils.ts\";\nimport { resolveToCwd } from \"./path-utils.ts\";\nimport { parseArchiveSelector, readArchiveSelector, resolveArchiveSelector, resolveInternalSelector, searchArchiveSelector, searchInternalSelector, searchSqliteSelector, sqliteSelectorForPath, type InternalResourceContext, type SqliteSelector } from \"./resource-selectors.ts\";\nimport { DEFAULT_MAX_BYTES, formatSize, truncateHead } from \"./truncate.ts\";\nimport { buildSearchDetails, type SearchToolDetails } from \"./search-details.ts\";\nimport { filterSearchOutputByLineRange, splitLineRangeSelector, type SearchLineRange } from \"./search-line-ranges.ts\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.ts\";\nexport type { SearchToolDetails } from \"./search-details.ts\";\nconst searchSchema = Type.Object({\n\tpattern: Type.String({ description: \"Regex pattern. Whitespace-only patterns are rejected; otherwise the pattern is preserved verbatim.\" }),\n\tpaths: Type.Optional(\n\t\tType.Union([\n\t\t\tType.String({ description: \"File, directory, glob, internal URL, or <file>:<lines> selector to search. Omitted or empty searches the workspace root.\" }),\n\t\t\tType.Array(Type.String({ description: \"File, directory, glob, internal URL, or <file>:<lines> selector to search.\" })),\n\t\t]),\n\t),\n\ti: Type.Optional(Type.Boolean({ description: \"Case-insensitive search.\" })),\n\tgitignore: Type.Optional(Type.Boolean({ description: \"Respect gitignore.\" })),\n\tcase: Type.Optional(Type.Boolean({ description: \"Set false for case-insensitive search (oh-my-pi compatibility).\" })),\n\tskip: Type.Optional(Type.Union([Type.Number({ description: \"Files to skip before collecting results; use to paginate when the previous call hit the file limit.\" }), Type.Null()])),\n}, { additionalProperties: false });\nexport type SearchToolInput = Static<typeof searchSchema>;\nexport type SearchToolOptions = GrepToolOptions & { hashlineStore?: HashlineSnapshotStore; contextBefore?: number; contextAfter?: number };\nfunction delimiterInExistingSearchGlobRoot(value: string, cwd: string): boolean { const selector = splitLineRangeSelector(value); const parsed = splitPathLikeGlob(selector.path); return !!parsed.glob && /[;,\\s]/.test(parsed.basePath) && existsSync(resolveToCwd(parsed.basePath, cwd)); }\nfunction archiveSelectorExists(value: string, cwd: string): boolean { const archive = parseArchiveSelector(value); if (!archive) return false; const resolved = resolveArchiveSelector(archive, cwd); if (!existsSync(resolved.archivePath)) return false; if (!resolved.memberPath) return true; try { readArchiveSelector(resolved); return true; } catch { return false; } }\nfunction searchPathResolvable(value: string, cwd: string): boolean { const selector = splitLineRangeSelector(value), archive = parseArchiveSelector(selector.path); if (archive) return archiveSelectorExists(selector.path, cwd); const sqlite = sqliteSelectorForPath(selector.path, cwd); return !!sqlite || /^(?:skill|agent|artifact|history|issue|local|memory|pr|conflict|omp|rule|mcp|vault):\\/\\//.test(selector.path) || existsSync(resolveToCwd(splitPathLikeGlob(selector.path).basePath, cwd)); }\n\nfunction normalizePaths(pathsValue: string | string[] | undefined, cwd: string): string[] {\n\tconst inputs = Array.isArray(pathsValue) ? (pathsValue.length > 0 ? pathsValue : [\".\"]) : pathsValue === undefined ? [\".\"] : [pathsValue];\n\tconst expanded: string[] = [];\n\tfor (const input of inputs) {\n\t\tconst raw = normalizePathLikeInput(input);\n\t\tif (raw === \"\") { expanded.push(\".\"); continue; }\n\t\tconst resourceLike = /^[a-z]+:\\/\\//i.test(raw) || /^[^:]+\\.(?:zip|jar|tar|tgz|gz|sqlite|db):/i.test(raw);\n\t\tif (existsSync(resolveToCwd(splitLineRangeSelector(raw).path, cwd)) || delimiterInExistingSearchGlobRoot(raw, cwd) || archiveSelectorExists(raw, cwd)) { expanded.push(raw); continue; }\n\t\tconst chunks = raw.split(/[;\\s]+/).map(normalizePathLikeInput).filter(Boolean);\n\t\tconst parts = chunks.flatMap((chunk) => searchPathResolvable(chunk, cwd) ? [chunk] : chunk.split(\",\").map(normalizePathLikeInput).filter(Boolean));\n\t\tconst commaOrSemicolon = /[;,]/.test(raw), canSplit = commaOrSemicolon ? parts.some((part) => searchPathResolvable(part, cwd)) : parts.every((part) => searchPathResolvable(part, cwd));\n\t\tif (parts.length > 1 && canSplit) expanded.push(...parts);\n\t\telse if (resourceLike) expanded.push(raw);\n\t\telse expanded.push(raw);\n\t}\n\treturn expanded;\n}\n\ninterface SearchTarget {\n\tpath: string;\n\tglob?: string;\n\tlineRanges?: SearchLineRange[];\n\tarchive?: ReturnType<typeof parseArchiveSelector>;\n\tsqlite?: SqliteSelector;\n\tinternal?: string;\n}\n\ninterface SearchOutputGroup {\n\tpath: string;\n\tlines: string[];\n}\n\ninterface PagedSearchOutputGroup {\n\ttargetPath: string;\n\tvirtual: boolean;\n\tgroup: SearchOutputGroup;\n}\n\nconst DEFAULT_LIMIT = 20, INTERNAL_SKIP_LIMIT = 2000, MULTI_FILE_PER_FILE_MATCHES = 20, SINGLE_FILE_MATCHES = 200;\n\nfunction normalizeSkip(skip: number | null | undefined): number {\n\tif (skip === undefined || skip === null) return 0;\n\tif (!Number.isFinite(skip) || skip < 0) throw new Error(\"Skip must be a non-negative number\");\n\treturn Math.floor(skip);\n}\nfunction markFileLimit(details: SearchToolDetails, limit: number): void {\n\tdetails.fileLimitReached = true;\n\tdetails.meta = { ...(details.meta ?? {}), limits: { ...(details.meta?.limits ?? {}), fileLimit: limit } };\n}\nfunction fileLimitDetails(limit: number): SearchToolDetails { const details: SearchToolDetails = {}; markFileLimit(details, limit); return details; }\nfunction continuationHint(limit: number, skip: number): string { return `[${limit} matching files shown. Use skip=${skip + limit} to view more.]`; }\nfunction internalCapHint(): string { return `[Search collected the first ${INTERNAL_SKIP_LIMIT} matches before pagination; refine the pattern or path for later results.]`; }\nfunction hasFileLimit(details: unknown): boolean { return typeof details === \"object\" && details !== null && (details as { fileLimitReached?: unknown }).fileLimitReached === true; }\n\nfunction isResourceSelector(value: string): boolean {\n\treturn /^[^:]+\\.(zip|jar|tar|tgz|gz|sqlite|db):/i.test(value) || /^[a-z]+:\\/\\//i.test(value) || value.startsWith(\"skill://\");\n}\n\nfunction normalizeSearchTargets(pathsValue: string | string[] | undefined, cwd: string): SearchTarget[] {\n\treturn normalizePaths(pathsValue, cwd).map((searchPath) => {\n\t\tconst directSqlite = sqliteSelectorForPath(searchPath, cwd);\n\t\tif (directSqlite?.rowId) return { path: searchPath, sqlite: directSqlite };\n\t\tif (archiveSelectorExists(searchPath, cwd)) { const archive = parseArchiveSelector(searchPath)!; return { path: searchPath, archive }; }\n\t\tconst selector = splitLineRangeSelector(searchPath);\n\t\tconst sqliteDirect = sqliteSelectorForPath(selector.path, cwd);\n\t\tif (sqliteDirect) return { path: selector.path, lineRanges: selector.lineRanges, sqlite: sqliteDirect };\n\t\tconst archive = parseArchiveSelector(selector.path);\n\t\tif (archive) return { path: selector.path, lineRanges: selector.lineRanges, archive };\n\t\tif (selector.path.startsWith(\"local://\")) { const sourcePath = resolveInternalSelector(selector.path, cwd); if (sourcePath) { const parsed = splitPathLikeGlob(sourcePath); return { path: parsed.basePath, glob: parsed.glob, lineRanges: selector.lineRanges }; } }\n\t\tif (/^(?:skill|agent|artifact|history|issue|local|memory|pr|conflict|omp|rule|mcp|vault):\\/\\//.test(selector.path)) return { path: selector.path, lineRanges: selector.lineRanges, internal: selector.path };\n\t\tif (isResourceSelector(selector.path)) throw new Error(`Search resource selectors are not supported by this filesystem backend: ${searchPath}`);\n\t\tconst parsed = splitPathLikeGlob(selector.path);\n\t\treturn { path: parsed.basePath, glob: parsed.glob, lineRanges: selector.lineRanges };\n\t});\n}\n\n\nfunction stripExtendedRegexWhitespace(pattern: string): string { let out = \"\", escaped = false, inClass = false, inComment = false; for (const ch of pattern) { if (inComment) { if (ch === \"\\n\" || ch === \"\\r\") inComment = false; continue; } if (escaped) { out += ch; escaped = false; continue; } if (ch === \"\\\\\") { out += ch; escaped = true; continue; } if (ch === \"[\") inClass = true; else if (ch === \"]\") inClass = false; if (!inClass && ch === \"#\") { inComment = true; continue; } if (!inClass && /\\s/.test(ch)) continue; out += ch; } return out; }\nfunction normalizeInlineSearchPattern(pattern: string, ignoreCase: boolean): { pattern: string; flags: string } { const match = pattern.match(/^\\(\\?([imsUx-]+)\\)([\\s\\S]*)$/), flags = new Set<string>(); if (ignoreCase) flags.add(\"i\"); if (match) { const inline = match[1] ?? \"\"; for (const flag of inline) if (flag === \"i\" || flag === \"m\" || flag === \"s\") flags.add(flag); return { pattern: inline.includes(\"x\") ? stripExtendedRegexWhitespace(match[2] ?? \"\") : match[2] ?? \"\", flags: [...flags].join(\"\") }; } return { pattern, flags: [...flags].join(\"\") }; }\n\nasync function searchFileLineRanges(target: SearchTarget, cwd: string, pattern: string, ignoreCase: boolean, contextBefore = 1, contextAfter = 3): Promise<string | undefined> {\n\tif (!target.lineRanges?.length || target.glob || target.archive || target.sqlite || target.internal) return undefined;\n\tconst absolutePath = resolveToCwd(target.path, cwd);\n\tif (!await fsStat(absolutePath).then((stat) => stat.isFile()).catch(() => false)) return undefined;\n\tconst source = (await fsReadFile(absolutePath, \"utf8\")).split(\"\\n\");\n\tconst normalized = normalizeInlineSearchPattern(pattern, ignoreCase);\n\tlet regex: RegExp;\n\ttry { regex = new RegExp(normalized.pattern, normalized.flags); } catch { return undefined; }\n\tconst matchLines = new Set<number>();\n\tsource.forEach((line, index) => { regex.lastIndex = 0; if (regex.test(line)) matchLines.add(index + 1); });\n\tconst outputLines = new Set<number>();\n\tfor (const line of matchLines) for (let n = Math.max(1, line - contextBefore); n <= Math.min(source.length, line + contextAfter); n++) outputLines.add(n);\n\tconst text = [...outputLines].sort((a, b) => a - b).map((n) => `${target.path}${matchLines.has(n) ? \":\" : \"-\"}${n}${matchLines.has(n) ? \":\" : \"-\"} ${source[n - 1] ?? \"\"}`).join(\"\\n\") || \"No matches found\";\n\treturn filterSearchOutputByLineRange(text, target.lineRanges, contextBefore, contextAfter);\n}\n\nfunction groupSearchOutput(text: string): SearchOutputGroup[] {\n\tconst groups: SearchOutputGroup[] = [];\n\tconst byPath = new Map<string, SearchOutputGroup>();\n\tfor (const line of text.split(\"\\n\")) {\n\t\tconst match = line.match(/^(.+?)(?::[^:]+: |:\\d+: |-\\d+- )/);\n\t\tif (!match) continue;\n\t\tconst filePath = match[1]!;\n\t\tlet current = byPath.get(filePath);\n\t\tif (!current) { current = { path: filePath, lines: [] }; byPath.set(filePath, current); groups.push(current); }\n\t\tif (!current.lines.includes(line)) current.lines.push(line);\n\t}\n\treturn groups;\n}\nfunction isVirtualSearchTarget(target: SearchTarget | undefined): boolean { return !!(target?.archive || target?.sqlite || target?.internal); }\n\n\nasync function addHashlineHeadersToSearchOutput(text: string, cwd: string, targetPath: string, hashlineStore: HashlineSnapshotStore): Promise<string> {\n\tconst groups = groupSearchOutput(text);\n\tif (groups.length === 0) return text;\n\tconst searchRoot = resolveToCwd(targetPath, cwd);\n\tconst targetIsFile = await fsStat(searchRoot).then((stat) => stat.isFile()).catch(() => false);\n\tconst rendered: string[] = [];\n\tlet lastDir = \"\";\n\tfor (const group of groups) {\n\t\tlet absolutePath = targetIsFile ? searchRoot : join(searchRoot, group.path);\n\t\ttry {\n\t\t\tawait fsReadFile(absolutePath);\n\t\t} catch {\n\t\t\tabsolutePath = resolvePath(cwd, group.path);\n\t\t}\n\t\ttry {\n\t\t\tconst content = await fsReadFile(absolutePath, \"utf-8\");\n\t\t\tconst snapshot = recordHashlineSnapshot(absolutePath, cwd, content, hashlineStore);\n\t\t\tconst dir = dirname(snapshot.displayPath);\n\t\t\tif (dir !== \".\" && dir !== lastDir) {\n\t\t\t\trendered.push(`# ${dir}/`);\n\t\t\t\tlastDir = dir;\n\t\t\t}\n\t\t\trendered.push(`[${snapshot.displayPath}#${snapshot.tag}]`);\n\t\t\tfor (const line of group.lines) {\n\t\t\t\tconst match = line.match(/^.+?(?::(\\d+): |-(\\d+)- )(.*)$/);\n\t\t\t\tif (match) rendered.push(`${match[1] ? \"*\" : \" \"}${match[1] ?? match[2]}:${match[3] ?? \"\"}`);\n\t\t\t}\n\t\t} catch {\n\t\t\trendered.push(...group.lines);\n\t\t}\n\t}\n\treturn rendered.join(\"\\n\");\n}\n\nfunction applyDefaultSearchContext(text: string, before = 1, after = 3): string {\n\tconst groups = groupSearchOutput(text);\n\tif (groups.length === 0) return text;\n\tconst output: string[] = [];\n\tfor (const group of groups) {\n\t\tconst byLine = new Map<number, { line: string; isMatch: boolean }>();\n\t\tfor (const line of group.lines) {\n\t\t\tconst match = line.match(/^(.+?)(?::(\\d+): |-(\\d+)- )(.*)$/);\n\t\t\tif (!match) continue;\n\t\t\tconst number = Number.parseInt(match[2] ?? match[3] ?? \"0\", 10);\n\t\t\tconst isMatch = match[2] !== undefined;\n\t\t\tconst existing = byLine.get(number);\n\t\t\tif (!existing || (isMatch && !existing.isMatch)) byLine.set(number, { line, isMatch });\n\t\t}\n\t\tconst matchNumbers = [...byLine.values()].filter((item) => item.isMatch).map((item) => Number.parseInt(item.line.match(/(?::(\\d+): |-(\\d+)- )/)?.[1] ?? item.line.match(/-(\\d+)- /)?.[1] ?? \"0\", 10));\n\t\tfor (const [number, item] of [...byLine.entries()].sort((a, b) => a[0] - b[0])) {\n\t\t\tif (item.isMatch || matchNumbers.some((lineNumber) => number >= lineNumber - before && number <= lineNumber + after)) output.push(item.line);\n\t\t}\n\t}\n\treturn output.join(\"\\n\");\n}\n\nfunction isSearchMatchLine(line: string): boolean { return /:\\d+: /.test(line); }\nfunction sliceGroupLinesByMatchCount(lines: readonly string[], matchLimit: number): string[] {\n\tif (matchLimit <= 0) return [];\n\tlet matches = 0; const output: string[] = [];\n\tfor (const line of lines) {\n\t\tif (isSearchMatchLine(line)) { if (matches >= matchLimit) break; matches++; }\n\t\toutput.push(line);\n\t}\n\treturn output;\n}\nfunction formatSearchGroups(groups: SearchOutputGroup[], limit: number, perFileLimit = 20): string {\n\treturn groups.slice(0, Math.max(0, limit)).flatMap((group) => sliceGroupLinesByMatchCount(group.lines, perFileLimit)).join(\"\\n\");\n}\n\nfunction dedupeRenderedSearchOutput(content: string): string {\n\tconst out: string[] = [], seen = new Set<string>(); let header = \"\", sawContinuationHint = false, sawCapHint = false;\n\tfor (const line of content.split(\"\\n\")) {\n\t\tconst h = line.match(/^\\[([^\\]#]+)#[0-9A-F]{4}\\]$/); if (h) header = h[1]!;\n\t\tif (/^\\[\\d+ matching files shown\\. Use skip=\\d+ to view more\\.\\]$/.test(line)) { if (sawContinuationHint) continue; sawContinuationHint = true; }\n\t\tif (line.startsWith(\"[Search collected the first \")) { if (sawCapHint) continue; sawCapHint = true; }\n\t\tconst m = header ? line.match(/^[* ]?(\\d+):/) : undefined;\n\t\tconst key = m ? `${header}:${m[1]}` : \"\"; if (key && seen.has(key)) continue; if (key) seen.add(key); out.push(line);\n\t}\n\treturn out.join(\"\\n\");\n}\nfunction formatSearchCall(\n\targs: { pattern: string; path?: string; paths?: string | string[]; glob?: string; limit?: number } | undefined,\n\ttheme: typeof import(\"../../modes/interactive/theme/theme.ts\").theme,\n): string {\n\tconst pattern = str(args?.pattern);\n\tconst rawPaths = Array.isArray(args?.paths) ? args.paths.join(\", \") : args?.paths;\n\tconst rawPath = str(args?.path ?? rawPaths);\n\tconst path = rawPath !== null ? shortenPath(rawPath || \".\") : null;\n\tconst glob = str(args?.glob);\n\tconst limit = args?.limit;\n\tconst invalidArg = invalidArgText(theme);\n\tlet text =\n\t\ttheme.fg(\"toolTitle\", theme.bold(\"search\")) +\n\t\t\" \" +\n\t\t(pattern === null ? invalidArg : theme.fg(\"accent\", `/${pattern || \"\"}/`)) +\n\t\ttheme.fg(\"toolOutput\", ` in ${path === null ? invalidArg : path}`);\n\tif (glob) text += theme.fg(\"toolOutput\", ` (${glob})`);\n\tif (limit !== undefined) text += theme.fg(\"toolOutput\", ` limit ${limit}`);\n\treturn text;\n}\n\nexport function createSearchToolDefinition(\n\tcwd: string,\n\toptions?: SearchToolOptions,\n): ToolDefinition<typeof searchSchema, SearchToolDetails | undefined> {\n\tconst grepDefinition = createGrepToolDefinition(cwd, { ...options, nativeCache: false });\n\tconst hashlineStore = options?.hashlineStore ?? createHashlineSnapshotStore();\n\treturn {\n\t\t...grepDefinition,\n\t\tname: \"search\",\n\t\tlabel: \"search\",\n\t\tdescription: \"Search file contents with a regex across files, directories, globs, and internal URLs.\",\n\t\tpromptSnippet: \"Search file contents with regex patterns.\",\n\t\tparameters: searchSchema,\n\t\tasync execute(toolCallId, params, signal, onUpdate, ctx) {\n\t\t\tconst resourceCtx = ctx as InternalResourceContext | undefined;\n\t\t\tif (params.pattern.trim() === \"\") throw new Error(\"Pattern must not be empty\");\n\t\t\tconst targets = normalizeSearchTargets(params.paths, cwd);\n\t\t\tconst ignoreCase = params.i === true || params.case === false;\n\t\t\tconst contextBefore = options?.contextBefore ?? 1, contextAfter = options?.contextAfter ?? 3, searchContext = Math.max(contextBefore, contextAfter);\n\t\t\tconst isSingleFileSearch = targets.length === 1 && await fsStat(resolveToCwd(targets[0]!.path, cwd)).then((stat) => stat.isFile()).catch(() => false);\n\t\t\tconst limit = isSingleFileSearch ? SINGLE_FILE_MATCHES : DEFAULT_LIMIT;\n\t\t\tlet skip = normalizeSkip(params.skip);\n\t\t\tconst singleFileSkipIgnored = skip > 0 && isSingleFileSearch;\n\t\t\tif (singleFileSkipIgnored) skip = 0;\n\t\t\tfor (const target of targets) {\n\t\t\t\tif (target.archive || target.sqlite || target.internal) continue;\n\t\t\t\tif (target.lineRanges && !await fsStat(resolveToCwd(target.path, cwd)).then((stat) => stat.isFile()).catch(() => false)) {\n\t\t\t\t\tthrow new Error(\"Line-range search selectors are only supported for single files\");\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst results: Awaited<ReturnType<typeof grepDefinition.execute>>[] = [];\n\t\t\tconst groups: PagedSearchOutputGroup[] = [];\n\t\t\tconst skippedMissingPaths: string[] = [];\n\t\t\tconst scopePath = targets.map((target) => target.path).join(\", \") || \".\";\n\t\t\tlet pageFullWithMore = false, internalSearchCapped = false;\n\t\t\tlet remaining = limit;\n\t\t\tconst targetHasMatch = async (target: SearchTarget): Promise<boolean> => {\n\t\t\t\tif (target.archive || target.sqlite || target.internal) {\n\t\t\t\t\tconst archive = target.archive ? resolveArchiveSelector(target.archive, cwd) : undefined;\n\t\t\t\t\tconst text = archive ? searchArchiveSelector(archive, params.pattern, ignoreCase, false, contextBefore, contextAfter) : target.sqlite ? searchSqliteSelector(target.sqlite, params.pattern, ignoreCase, contextBefore, contextAfter) : await searchInternalSelector(target.internal!, cwd, params.pattern, ignoreCase, false, resourceCtx, contextBefore, contextAfter);\n\t\t\t\t\treturn groupSearchOutput(filterSearchOutputByLineRange(text, target.lineRanges, contextBefore, contextAfter)).length > 0;\n\t\t\t\t}\n\t\t\t\tconst ranged = await searchFileLineRanges(target, cwd, params.pattern, ignoreCase, contextBefore, contextAfter);\n\t\t\t\tif (ranged !== undefined) return groupSearchOutput(ranged).length > 0;\n\t\t\t\tconst probe = await grepDefinition.execute(toolCallId, { pattern: params.pattern, path: target.path, glob: target.glob, ignoreCase, literal: false, context: 0, limit: 1, gitignore: params.gitignore }, signal, undefined, ctx);\n\t\t\t\treturn probe.content.some((item) => item.type === \"text\" && item.text && item.text !== \"No matches found\");\n\t\t\t};\n\t\t\tfor (const target of targets) {\n\t\t\t\tif (skip === 0 && remaining <= 0) { if (!target.archive && !target.sqlite && !target.internal && targets.length > 1 && await fsStat(resolveToCwd(target.path, cwd)).then(() => false).catch(() => true)) { skippedMissingPaths.push(target.path); continue; } if (await targetHasMatch(target)) { pageFullWithMore = true; break; } continue; }\n\t\t\t\tif (target.archive || target.sqlite || target.internal) {\n\t\t\t\t\tconst archive = target.archive ? resolveArchiveSelector(target.archive, cwd) : undefined;\n\t\t\t\t\tlet text = archive ? searchArchiveSelector(archive, params.pattern, ignoreCase, false, contextBefore, contextAfter) : target.sqlite ? searchSqliteSelector(target.sqlite, params.pattern, ignoreCase, contextBefore, contextAfter) : await searchInternalSelector(target.internal!, cwd, params.pattern, ignoreCase, false, resourceCtx, contextBefore, contextAfter);\n\t\t\t\t\tif (archive && target.archive) text = text.split(archive.archivePath).join(target.archive.archivePath); if (target.sqlite) text = text.split(target.sqlite.databasePath).join(target.path.match(/^(.+?\\.(?:sqlite3?|db3?))/i)?.[1] ?? target.sqlite.databasePath); text = text || \"No matches found\";\n\t\t\t\t\ttext = filterSearchOutputByLineRange(text, target.lineRanges, contextBefore, contextAfter);\n\t\t\t\t\tconst outputGroups = groupSearchOutput(text);\n\t\t\t\t\tif (skip > 0) groups.push(...outputGroups.map((group) => ({ targetPath: target.path, virtual: isVirtualSearchTarget(target), group })) );\n\t\t\t\t\telse if (outputGroups.length > 0) {\n\t\t\t\t\t\tconst hadMore = outputGroups.length > remaining;\n\t\t\t\t\t\ttext = formatSearchGroups(outputGroups, remaining);\n\t\t\t\t\t\tif (hadMore) { text += `\\n\\n${continuationHint(limit, skip)}`; pageFullWithMore = true; }\n\t\t\t\t\t\tremaining -= Math.min(remaining, outputGroups.length);\n\t\t\t\t\t} else if (text && text !== \"No matches found\") {\n\t\t\t\t\t\tremaining--;\n\t\t\t\t\t} else text = \"No matches found\";\n\t\t\t\t\tif (skip === 0) results.push({ content: [{ type: \"text\" as const, text }], details: undefined });\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (targets.length > 1 && await fsStat(resolveToCwd(target.path, cwd)).then(() => false).catch(() => true)) {\n\t\t\t\t\tskippedMissingPaths.push(target.path);\n\t\t\t\t\tresults.push({ content: [{ type: \"text\" as const, text: \"No matches found\" }], details: undefined });\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst rangedText = await searchFileLineRanges(target, cwd, params.pattern, ignoreCase, contextBefore, contextAfter);\n\t\t\t\tif (rangedText !== undefined) {\n\t\t\t\t\tlet text = applyDefaultSearchContext(rangedText, contextBefore, contextAfter) || \"No matches found\";\n\t\t\t\t\tconst result = { content: [{ type: \"text\" as const, text }], details: undefined as SearchToolDetails | undefined };\n\t\t\t\t\tresults.push(result);\n\t\t\t\t\tconst outputGroups = groupSearchOutput(text);\n\t\t\t\t\tif (skip > 0) groups.push(...outputGroups.map((group) => ({ targetPath: target.path, virtual: false, group })));\n\t\t\t\t\telse if (!isSingleFileSearch && outputGroups.length > 0) { const hadMore = outputGroups.length > remaining; text = formatSearchGroups(outputGroups, remaining); if (hadMore) { result.details = fileLimitDetails(limit); pageFullWithMore = true; } remaining -= Math.min(remaining, outputGroups.length); result.content = [{ type: \"text\", text }]; }\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst result = await grepDefinition.execute(\n\t\t\t\t\ttoolCallId,\n\t\t\t\t\t{\n\t\t\t\t\t\tpattern: params.pattern,\n\t\t\t\t\t\tpath: target.path,\n\t\t\t\t\t\tglob: target.glob,\n\t\t\t\t\t\tignoreCase,\n\t\t\t\t\t\tliteral: false,\n\t\t\t\t\t\tcontext: searchContext,\n\t\t\t\t\t\tmaxCountPerFile: isSingleFileSearch ? SINGLE_FILE_MATCHES : MULTI_FILE_PER_FILE_MATCHES,\n\t\t\t\t\t\tlimit: INTERNAL_SKIP_LIMIT,\n\t\t\t\t\t\tgitignore: params.gitignore,\n\t\t\t\t\t},\n\t\t\t\t\tsignal,\n\t\t\t\t\tonUpdate,\n\t\t\t\t\tctx,\n\t\t\t\t);\n\t\t\t\tresults.push(result);\n\t\t\t\tif (result.details?.matchLimitReached) internalSearchCapped = true;\n\t\t\t\tlet text = result.content\n\t\t\t\t\t.map((item) => (item.type === \"text\" ? item.text : undefined))\n\t\t\t\t\t.filter((text): text is string => typeof text === \"string\")\n\t\t\t\t\t.join(\"\\n\");\n\t\t\t\ttext = filterSearchOutputByLineRange(text, target.lineRanges, contextBefore, contextAfter);\n\t\t\t\tif (text !== \"No matches found\") text = applyDefaultSearchContext(text, contextBefore, contextAfter) || \"No matches found\";\n\t\t\t\tconst outputGroups = groupSearchOutput(text);\n\t\t\t\tif (skip > 0) groups.push(...outputGroups.map((group) => ({ targetPath: target.path, virtual: false, group })) );\n\t\t\t\telse if (!isSingleFileSearch && outputGroups.length > 0) {\n\t\t\t\t\tconst hadMore = outputGroups.length > remaining;\n\t\t\t\t\ttext = formatSearchGroups(outputGroups, remaining);\n\t\t\t\t\tif (hadMore) { result.details = { ...(result.details ?? {}), ...fileLimitDetails(limit) }; pageFullWithMore = true; }\n\t\t\t\t\tremaining -= Math.min(remaining, outputGroups.length);\n\t\t\t\t}\n\t\t\t\tresult.content = [{ type: \"text\", text }];\n\t\t\t}\n\n\t\t\tconst details: SearchToolDetails = {};\n\t\t\tfor (const result of results) {\n\t\t\t\tif (result.details?.truncation) details.truncation = result.details.truncation;\n\t\t\t\tif (result.details?.matchLimitReached) details.matchLimitReached = result.details.matchLimitReached;\n\t\t\t\tif (hasFileLimit(result.details)) markFileLimit(details, limit);\n\t\t\t\tif (result.details?.linesTruncated) details.linesTruncated = true;\n\t\t\t}\n\n\t\t\tif (skip > 0) {\n\t\t\t\tconst renderedPages: string[] = [];\n\t\t\t\tlet remainingMatches = limit;\n\t\t\t\tfor (const item of groups.slice(skip)) {\n\t\t\t\t\tif (remainingMatches <= 0) break;\n\t\t\t\t\tconst raw = formatSearchGroups([item.group], 1);\n\t\t\t\t\tremainingMatches--;\n\t\t\t\t\tif (raw) renderedPages.push(item.virtual ? raw : await addHashlineHeadersToSearchOutput(raw, cwd, item.targetPath, hashlineStore));\n\t\t\t\t}\n\t\t\t\tconst hasMorePages = groups.slice(skip + limit).length > 0;\n\t\t\t\tconst cappedBeforeRequestedPage = internalSearchCapped && skip >= groups.length;\n\t\t\t\tconst capNotice = internalSearchCapped && !hasMorePages ? `\\n\\n${internalCapHint()}` : \"\";\n\t\t\t\tconst output = `${renderedPages.join(\"\\n\\n\") || `No more results (skip=${skip})`}${hasMorePages ? `\\n\\n${continuationHint(limit, skip)}` : \"\"}${capNotice}`;\n\t\t\t\tif (hasMorePages || cappedBeforeRequestedPage || capNotice) markFileLimit(details, limit);\n\t\t\t\tconst truncation = truncateHead(output, { maxLines: Number.MAX_SAFE_INTEGER });\n\t\t\t\tlet content = truncation.content;\n\t\t\t\tif (truncation.truncated) {\n\t\t\t\t\tdetails.truncation = truncation;\n\t\t\t\t\tcontent += `\\n\\n[${formatSize(DEFAULT_MAX_BYTES)} combined output limit reached]`;\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: content }],\n\t\t\t\t\tdetails: buildSearchDetails(details, content, cwd, scopePath, skippedMissingPaths),\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (results.length === 1) {\n\t\t\t\tconst result = results[0]!;\n\t\t\t\tlet text = result.content\n\t\t\t\t\t.map((item) => (item.type === \"text\" ? item.text : undefined))\n\t\t\t\t\t.filter((value): value is string => typeof value === \"string\")\n\t\t\t\t\t.join(\"\\n\");\n\t\t\t\ttext = filterSearchOutputByLineRange(text, targets[0]?.lineRanges, contextBefore, contextAfter);\n\t\t\t\tif (!isVirtualSearchTarget(targets[0]) && text !== \"No matches found\") text = applyDefaultSearchContext(text, contextBefore, contextAfter) || \"No matches found\";\n\t\t\t\tconst skipNotice = singleFileSkipIgnored ? \"\\n\\n[skip is ignored for single-file search; refine the pattern/path or search a directory to page matching files.]\" : \"\";\n\t\t\t\tif (isSingleFileSearch && text !== \"No matches found\") text = formatSearchGroups(groupSearchOutput(text), limit, limit);\n\t\t\t\tif (text && text !== \"No matches found\") {\n\t\t\t\t\tconst renderedText = isVirtualSearchTarget(targets[0]) ? text : await addHashlineHeadersToSearchOutput(text, cwd, targets[0]?.path ?? \".\", hashlineStore);\n\t\t\t\t\tconst pagedText = `${hasFileLimit(result.details) ? `${renderedText}\\n\\n${continuationHint(limit, skip)}` : renderedText}${skipNotice}`;\n\t\t\t\t\tconst truncation = truncateHead(pagedText, { maxLines: Number.MAX_SAFE_INTEGER });\n\t\t\t\t\tif (truncation.truncated) return { ...result, content: [{ type: \"text\", text: `${truncation.content}\\n\\n[${formatSize(DEFAULT_MAX_BYTES)} combined output limit reached]` }], details: buildSearchDetails({ ...(result.details ?? {}), truncation }, truncation.content, cwd, scopePath, skippedMissingPaths) };\n\t\t\t\t\treturn { ...result, content: [{ type: \"text\", text: pagedText }], details: buildSearchDetails(result.details, pagedText, cwd, scopePath, skippedMissingPaths) };\n\t\t\t\t}\n\t\t\t\treturn { ...result, details: buildSearchDetails(result.details, text || \"No matches found\", cwd, scopePath, skippedMissingPaths) };\n\t\t\t}\n\n\t\t\tconst renderedResults = await Promise.all(results.map(async (result, index) => {\n\t\t\t\tlet text = result.content\n\t\t\t\t\t.map((item) => (item.type === \"text\" ? item.text : undefined))\n\t\t\t\t\t.filter((value): value is string => typeof value === \"string\" && value.length > 0)\n\t\t\t\t\t.join(\"\\n\");\n\t\t\t\ttext = filterSearchOutputByLineRange(text, targets[index]?.lineRanges, contextBefore, contextAfter);\n\t\t\t\tif (!isVirtualSearchTarget(targets[index]) && text !== \"No matches found\") text = applyDefaultSearchContext(text, contextBefore, contextAfter);\n\t\t\t\tif (!text || text === \"No matches found\") return `# ${targets[index]?.path ?? \".\"}\\n${text}`;\n\t\t\t\treturn isVirtualSearchTarget(targets[index]) ? text : await addHashlineHeadersToSearchOutput(text, cwd, targets[index]?.path ?? \".\", hashlineStore);\n\t\t\t}));\n\t\t\tconst content = dedupeRenderedSearchOutput(`${renderedResults.join(\"\\n\\n\")}${skippedMissingPaths.length > 0 ? `\\n\\n[Skipped missing paths: ${skippedMissingPaths.join(\", \")}]` : \"\"}${pageFullWithMore ? `\\n\\n${continuationHint(limit, skip)}` : \"\"}${internalSearchCapped && !pageFullWithMore ? `\\n\\n${internalCapHint()}` : \"\"}`);\n\t\t\tif (pageFullWithMore || internalSearchCapped) markFileLimit(details, limit);\n\t\t\tconst truncation = truncateHead(content, { maxLines: Number.MAX_SAFE_INTEGER });\n\t\t\tlet output = truncation.content;\n\t\t\tif (truncation.truncated) {\n\t\t\t\tdetails.truncation = truncation;\n\t\t\t\toutput += `\\n\\n[${formatSize(DEFAULT_MAX_BYTES)} combined output limit reached]`;\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\", text: output }],\n\t\t\t\tdetails: buildSearchDetails(details, output, cwd, scopePath, skippedMissingPaths),\n\t\t\t};\n\t\t},\n\t\trenderCall(args, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatSearchCall(args, theme));\n\t\t\treturn text;\n\t\t},\n\t\trenderResult(result, options: ToolRenderResultOptions, theme, context) {\n\t\t\treturn grepDefinition.renderResult?.(result, options, theme, context) ?? new Text(\"\", 0, 0);\n\t\t},\n\t};\n}\n\nexport function createSearchTool(cwd: string, options?: SearchToolOptions): AgentTool<typeof searchSchema> {\n\treturn wrapToolDefinition(createSearchToolDefinition(cwd, options));\n}\n"]}
|
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { readFile as fsReadFile, stat as fsStat } from "node:fs/promises";
|
|
3
|
+
import { dirname, join, resolve as resolvePath } from "node:path";
|
|
4
|
+
import { Text } from "@earendil-works/pi-tui";
|
|
5
|
+
import { Type } from "typebox";
|
|
6
|
+
import { createGrepToolDefinition, } from "./grep.js";
|
|
7
|
+
import { normalizePathLikeInput, splitPathLikeGlob } from "./glob-path-utils.js";
|
|
8
|
+
import { createHashlineSnapshotStore, recordHashlineSnapshot } from "./hashline.js";
|
|
9
|
+
import { invalidArgText, shortenPath, str } from "./render-utils.js";
|
|
10
|
+
import { resolveToCwd } from "./path-utils.js";
|
|
11
|
+
import { parseArchiveSelector, readArchiveSelector, resolveArchiveSelector, resolveInternalSelector, searchArchiveSelector, searchInternalSelector, searchSqliteSelector, sqliteSelectorForPath } from "./resource-selectors.js";
|
|
12
|
+
import { DEFAULT_MAX_BYTES, formatSize, truncateHead } from "./truncate.js";
|
|
13
|
+
import { buildSearchDetails } from "./search-details.js";
|
|
14
|
+
import { filterSearchOutputByLineRange, splitLineRangeSelector } from "./search-line-ranges.js";
|
|
15
|
+
import { wrapToolDefinition } from "./tool-definition-wrapper.js";
|
|
16
|
+
const searchSchema = Type.Object({
|
|
17
|
+
pattern: Type.String({ description: "Regex pattern. Whitespace-only patterns are rejected; otherwise the pattern is preserved verbatim." }),
|
|
18
|
+
paths: Type.Optional(Type.Union([
|
|
19
|
+
Type.String({ description: "File, directory, glob, internal URL, or <file>:<lines> selector to search. Omitted or empty searches the workspace root." }),
|
|
20
|
+
Type.Array(Type.String({ description: "File, directory, glob, internal URL, or <file>:<lines> selector to search." })),
|
|
21
|
+
])),
|
|
22
|
+
i: Type.Optional(Type.Boolean({ description: "Case-insensitive search." })),
|
|
23
|
+
gitignore: Type.Optional(Type.Boolean({ description: "Respect gitignore." })),
|
|
24
|
+
case: Type.Optional(Type.Boolean({ description: "Set false for case-insensitive search (oh-my-pi compatibility)." })),
|
|
25
|
+
skip: Type.Optional(Type.Union([Type.Number({ description: "Files to skip before collecting results; use to paginate when the previous call hit the file limit." }), Type.Null()])),
|
|
26
|
+
}, { additionalProperties: false });
|
|
27
|
+
function delimiterInExistingSearchGlobRoot(value, cwd) { const selector = splitLineRangeSelector(value); const parsed = splitPathLikeGlob(selector.path); return !!parsed.glob && /[;,\s]/.test(parsed.basePath) && existsSync(resolveToCwd(parsed.basePath, cwd)); }
|
|
28
|
+
function archiveSelectorExists(value, cwd) { const archive = parseArchiveSelector(value); if (!archive)
|
|
29
|
+
return false; const resolved = resolveArchiveSelector(archive, cwd); if (!existsSync(resolved.archivePath))
|
|
30
|
+
return false; if (!resolved.memberPath)
|
|
31
|
+
return true; try {
|
|
32
|
+
readArchiveSelector(resolved);
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return false;
|
|
37
|
+
} }
|
|
38
|
+
function searchPathResolvable(value, cwd) { const selector = splitLineRangeSelector(value), archive = parseArchiveSelector(selector.path); if (archive)
|
|
39
|
+
return archiveSelectorExists(selector.path, cwd); const sqlite = sqliteSelectorForPath(selector.path, cwd); return !!sqlite || /^(?:skill|agent|artifact|history|issue|local|memory|pr|conflict|omp|rule|mcp|vault):\/\//.test(selector.path) || existsSync(resolveToCwd(splitPathLikeGlob(selector.path).basePath, cwd)); }
|
|
40
|
+
function normalizePaths(pathsValue, cwd) {
|
|
41
|
+
const inputs = Array.isArray(pathsValue) ? (pathsValue.length > 0 ? pathsValue : ["."]) : pathsValue === undefined ? ["."] : [pathsValue];
|
|
42
|
+
const expanded = [];
|
|
43
|
+
for (const input of inputs) {
|
|
44
|
+
const raw = normalizePathLikeInput(input);
|
|
45
|
+
if (raw === "") {
|
|
46
|
+
expanded.push(".");
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const resourceLike = /^[a-z]+:\/\//i.test(raw) || /^[^:]+\.(?:zip|jar|tar|tgz|gz|sqlite|db):/i.test(raw);
|
|
50
|
+
if (existsSync(resolveToCwd(splitLineRangeSelector(raw).path, cwd)) || delimiterInExistingSearchGlobRoot(raw, cwd) || archiveSelectorExists(raw, cwd)) {
|
|
51
|
+
expanded.push(raw);
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const chunks = raw.split(/[;\s]+/).map(normalizePathLikeInput).filter(Boolean);
|
|
55
|
+
const parts = chunks.flatMap((chunk) => searchPathResolvable(chunk, cwd) ? [chunk] : chunk.split(",").map(normalizePathLikeInput).filter(Boolean));
|
|
56
|
+
const commaOrSemicolon = /[;,]/.test(raw), canSplit = commaOrSemicolon ? parts.some((part) => searchPathResolvable(part, cwd)) : parts.every((part) => searchPathResolvable(part, cwd));
|
|
57
|
+
if (parts.length > 1 && canSplit)
|
|
58
|
+
expanded.push(...parts);
|
|
59
|
+
else if (resourceLike)
|
|
60
|
+
expanded.push(raw);
|
|
61
|
+
else
|
|
62
|
+
expanded.push(raw);
|
|
63
|
+
}
|
|
64
|
+
return expanded;
|
|
65
|
+
}
|
|
66
|
+
const DEFAULT_LIMIT = 20, INTERNAL_SKIP_LIMIT = 2000, MULTI_FILE_PER_FILE_MATCHES = 20, SINGLE_FILE_MATCHES = 200;
|
|
67
|
+
function normalizeSkip(skip) {
|
|
68
|
+
if (skip === undefined || skip === null)
|
|
69
|
+
return 0;
|
|
70
|
+
if (!Number.isFinite(skip) || skip < 0)
|
|
71
|
+
throw new Error("Skip must be a non-negative number");
|
|
72
|
+
return Math.floor(skip);
|
|
73
|
+
}
|
|
74
|
+
function markFileLimit(details, limit) {
|
|
75
|
+
details.fileLimitReached = true;
|
|
76
|
+
details.meta = { ...(details.meta ?? {}), limits: { ...(details.meta?.limits ?? {}), fileLimit: limit } };
|
|
77
|
+
}
|
|
78
|
+
function fileLimitDetails(limit) { const details = {}; markFileLimit(details, limit); return details; }
|
|
79
|
+
function continuationHint(limit, skip) { return `[${limit} matching files shown. Use skip=${skip + limit} to view more.]`; }
|
|
80
|
+
function internalCapHint() { return `[Search collected the first ${INTERNAL_SKIP_LIMIT} matches before pagination; refine the pattern or path for later results.]`; }
|
|
81
|
+
function hasFileLimit(details) { return typeof details === "object" && details !== null && details.fileLimitReached === true; }
|
|
82
|
+
function isResourceSelector(value) {
|
|
83
|
+
return /^[^:]+\.(zip|jar|tar|tgz|gz|sqlite|db):/i.test(value) || /^[a-z]+:\/\//i.test(value) || value.startsWith("skill://");
|
|
84
|
+
}
|
|
85
|
+
function normalizeSearchTargets(pathsValue, cwd) {
|
|
86
|
+
return normalizePaths(pathsValue, cwd).map((searchPath) => {
|
|
87
|
+
const directSqlite = sqliteSelectorForPath(searchPath, cwd);
|
|
88
|
+
if (directSqlite?.rowId)
|
|
89
|
+
return { path: searchPath, sqlite: directSqlite };
|
|
90
|
+
if (archiveSelectorExists(searchPath, cwd)) {
|
|
91
|
+
const archive = parseArchiveSelector(searchPath);
|
|
92
|
+
return { path: searchPath, archive };
|
|
93
|
+
}
|
|
94
|
+
const selector = splitLineRangeSelector(searchPath);
|
|
95
|
+
const sqliteDirect = sqliteSelectorForPath(selector.path, cwd);
|
|
96
|
+
if (sqliteDirect)
|
|
97
|
+
return { path: selector.path, lineRanges: selector.lineRanges, sqlite: sqliteDirect };
|
|
98
|
+
const archive = parseArchiveSelector(selector.path);
|
|
99
|
+
if (archive)
|
|
100
|
+
return { path: selector.path, lineRanges: selector.lineRanges, archive };
|
|
101
|
+
if (selector.path.startsWith("local://")) {
|
|
102
|
+
const sourcePath = resolveInternalSelector(selector.path, cwd);
|
|
103
|
+
if (sourcePath) {
|
|
104
|
+
const parsed = splitPathLikeGlob(sourcePath);
|
|
105
|
+
return { path: parsed.basePath, glob: parsed.glob, lineRanges: selector.lineRanges };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (/^(?:skill|agent|artifact|history|issue|local|memory|pr|conflict|omp|rule|mcp|vault):\/\//.test(selector.path))
|
|
109
|
+
return { path: selector.path, lineRanges: selector.lineRanges, internal: selector.path };
|
|
110
|
+
if (isResourceSelector(selector.path))
|
|
111
|
+
throw new Error(`Search resource selectors are not supported by this filesystem backend: ${searchPath}`);
|
|
112
|
+
const parsed = splitPathLikeGlob(selector.path);
|
|
113
|
+
return { path: parsed.basePath, glob: parsed.glob, lineRanges: selector.lineRanges };
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
function stripExtendedRegexWhitespace(pattern) { let out = "", escaped = false, inClass = false, inComment = false; for (const ch of pattern) {
|
|
117
|
+
if (inComment) {
|
|
118
|
+
if (ch === "\n" || ch === "\r")
|
|
119
|
+
inComment = false;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (escaped) {
|
|
123
|
+
out += ch;
|
|
124
|
+
escaped = false;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (ch === "\\") {
|
|
128
|
+
out += ch;
|
|
129
|
+
escaped = true;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (ch === "[")
|
|
133
|
+
inClass = true;
|
|
134
|
+
else if (ch === "]")
|
|
135
|
+
inClass = false;
|
|
136
|
+
if (!inClass && ch === "#") {
|
|
137
|
+
inComment = true;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (!inClass && /\s/.test(ch))
|
|
141
|
+
continue;
|
|
142
|
+
out += ch;
|
|
143
|
+
} return out; }
|
|
144
|
+
function normalizeInlineSearchPattern(pattern, ignoreCase) { const match = pattern.match(/^\(\?([imsUx-]+)\)([\s\S]*)$/), flags = new Set(); if (ignoreCase)
|
|
145
|
+
flags.add("i"); if (match) {
|
|
146
|
+
const inline = match[1] ?? "";
|
|
147
|
+
for (const flag of inline)
|
|
148
|
+
if (flag === "i" || flag === "m" || flag === "s")
|
|
149
|
+
flags.add(flag);
|
|
150
|
+
return { pattern: inline.includes("x") ? stripExtendedRegexWhitespace(match[2] ?? "") : match[2] ?? "", flags: [...flags].join("") };
|
|
151
|
+
} return { pattern, flags: [...flags].join("") }; }
|
|
152
|
+
async function searchFileLineRanges(target, cwd, pattern, ignoreCase, contextBefore = 1, contextAfter = 3) {
|
|
153
|
+
if (!target.lineRanges?.length || target.glob || target.archive || target.sqlite || target.internal)
|
|
154
|
+
return undefined;
|
|
155
|
+
const absolutePath = resolveToCwd(target.path, cwd);
|
|
156
|
+
if (!await fsStat(absolutePath).then((stat) => stat.isFile()).catch(() => false))
|
|
157
|
+
return undefined;
|
|
158
|
+
const source = (await fsReadFile(absolutePath, "utf8")).split("\n");
|
|
159
|
+
const normalized = normalizeInlineSearchPattern(pattern, ignoreCase);
|
|
160
|
+
let regex;
|
|
161
|
+
try {
|
|
162
|
+
regex = new RegExp(normalized.pattern, normalized.flags);
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
167
|
+
const matchLines = new Set();
|
|
168
|
+
source.forEach((line, index) => { regex.lastIndex = 0; if (regex.test(line))
|
|
169
|
+
matchLines.add(index + 1); });
|
|
170
|
+
const outputLines = new Set();
|
|
171
|
+
for (const line of matchLines)
|
|
172
|
+
for (let n = Math.max(1, line - contextBefore); n <= Math.min(source.length, line + contextAfter); n++)
|
|
173
|
+
outputLines.add(n);
|
|
174
|
+
const text = [...outputLines].sort((a, b) => a - b).map((n) => `${target.path}${matchLines.has(n) ? ":" : "-"}${n}${matchLines.has(n) ? ":" : "-"} ${source[n - 1] ?? ""}`).join("\n") || "No matches found";
|
|
175
|
+
return filterSearchOutputByLineRange(text, target.lineRanges, contextBefore, contextAfter);
|
|
176
|
+
}
|
|
177
|
+
function groupSearchOutput(text) {
|
|
178
|
+
const groups = [];
|
|
179
|
+
const byPath = new Map();
|
|
180
|
+
for (const line of text.split("\n")) {
|
|
181
|
+
const match = line.match(/^(.+?)(?::[^:]+: |:\d+: |-\d+- )/);
|
|
182
|
+
if (!match)
|
|
183
|
+
continue;
|
|
184
|
+
const filePath = match[1];
|
|
185
|
+
let current = byPath.get(filePath);
|
|
186
|
+
if (!current) {
|
|
187
|
+
current = { path: filePath, lines: [] };
|
|
188
|
+
byPath.set(filePath, current);
|
|
189
|
+
groups.push(current);
|
|
190
|
+
}
|
|
191
|
+
if (!current.lines.includes(line))
|
|
192
|
+
current.lines.push(line);
|
|
193
|
+
}
|
|
194
|
+
return groups;
|
|
195
|
+
}
|
|
196
|
+
function isVirtualSearchTarget(target) { return !!(target?.archive || target?.sqlite || target?.internal); }
|
|
197
|
+
async function addHashlineHeadersToSearchOutput(text, cwd, targetPath, hashlineStore) {
|
|
198
|
+
const groups = groupSearchOutput(text);
|
|
199
|
+
if (groups.length === 0)
|
|
200
|
+
return text;
|
|
201
|
+
const searchRoot = resolveToCwd(targetPath, cwd);
|
|
202
|
+
const targetIsFile = await fsStat(searchRoot).then((stat) => stat.isFile()).catch(() => false);
|
|
203
|
+
const rendered = [];
|
|
204
|
+
let lastDir = "";
|
|
205
|
+
for (const group of groups) {
|
|
206
|
+
let absolutePath = targetIsFile ? searchRoot : join(searchRoot, group.path);
|
|
207
|
+
try {
|
|
208
|
+
await fsReadFile(absolutePath);
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
absolutePath = resolvePath(cwd, group.path);
|
|
212
|
+
}
|
|
213
|
+
try {
|
|
214
|
+
const content = await fsReadFile(absolutePath, "utf-8");
|
|
215
|
+
const snapshot = recordHashlineSnapshot(absolutePath, cwd, content, hashlineStore);
|
|
216
|
+
const dir = dirname(snapshot.displayPath);
|
|
217
|
+
if (dir !== "." && dir !== lastDir) {
|
|
218
|
+
rendered.push(`# ${dir}/`);
|
|
219
|
+
lastDir = dir;
|
|
220
|
+
}
|
|
221
|
+
rendered.push(`[${snapshot.displayPath}#${snapshot.tag}]`);
|
|
222
|
+
for (const line of group.lines) {
|
|
223
|
+
const match = line.match(/^.+?(?::(\d+): |-(\d+)- )(.*)$/);
|
|
224
|
+
if (match)
|
|
225
|
+
rendered.push(`${match[1] ? "*" : " "}${match[1] ?? match[2]}:${match[3] ?? ""}`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
rendered.push(...group.lines);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return rendered.join("\n");
|
|
233
|
+
}
|
|
234
|
+
function applyDefaultSearchContext(text, before = 1, after = 3) {
|
|
235
|
+
const groups = groupSearchOutput(text);
|
|
236
|
+
if (groups.length === 0)
|
|
237
|
+
return text;
|
|
238
|
+
const output = [];
|
|
239
|
+
for (const group of groups) {
|
|
240
|
+
const byLine = new Map();
|
|
241
|
+
for (const line of group.lines) {
|
|
242
|
+
const match = line.match(/^(.+?)(?::(\d+): |-(\d+)- )(.*)$/);
|
|
243
|
+
if (!match)
|
|
244
|
+
continue;
|
|
245
|
+
const number = Number.parseInt(match[2] ?? match[3] ?? "0", 10);
|
|
246
|
+
const isMatch = match[2] !== undefined;
|
|
247
|
+
const existing = byLine.get(number);
|
|
248
|
+
if (!existing || (isMatch && !existing.isMatch))
|
|
249
|
+
byLine.set(number, { line, isMatch });
|
|
250
|
+
}
|
|
251
|
+
const matchNumbers = [...byLine.values()].filter((item) => item.isMatch).map((item) => Number.parseInt(item.line.match(/(?::(\d+): |-(\d+)- )/)?.[1] ?? item.line.match(/-(\d+)- /)?.[1] ?? "0", 10));
|
|
252
|
+
for (const [number, item] of [...byLine.entries()].sort((a, b) => a[0] - b[0])) {
|
|
253
|
+
if (item.isMatch || matchNumbers.some((lineNumber) => number >= lineNumber - before && number <= lineNumber + after))
|
|
254
|
+
output.push(item.line);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return output.join("\n");
|
|
258
|
+
}
|
|
259
|
+
function isSearchMatchLine(line) { return /:\d+: /.test(line); }
|
|
260
|
+
function sliceGroupLinesByMatchCount(lines, matchLimit) {
|
|
261
|
+
if (matchLimit <= 0)
|
|
262
|
+
return [];
|
|
263
|
+
let matches = 0;
|
|
264
|
+
const output = [];
|
|
265
|
+
for (const line of lines) {
|
|
266
|
+
if (isSearchMatchLine(line)) {
|
|
267
|
+
if (matches >= matchLimit)
|
|
268
|
+
break;
|
|
269
|
+
matches++;
|
|
270
|
+
}
|
|
271
|
+
output.push(line);
|
|
272
|
+
}
|
|
273
|
+
return output;
|
|
274
|
+
}
|
|
275
|
+
function formatSearchGroups(groups, limit, perFileLimit = 20) {
|
|
276
|
+
return groups.slice(0, Math.max(0, limit)).flatMap((group) => sliceGroupLinesByMatchCount(group.lines, perFileLimit)).join("\n");
|
|
277
|
+
}
|
|
278
|
+
function dedupeRenderedSearchOutput(content) {
|
|
279
|
+
const out = [], seen = new Set();
|
|
280
|
+
let header = "", sawContinuationHint = false, sawCapHint = false;
|
|
281
|
+
for (const line of content.split("\n")) {
|
|
282
|
+
const h = line.match(/^\[([^\]#]+)#[0-9A-F]{4}\]$/);
|
|
283
|
+
if (h)
|
|
284
|
+
header = h[1];
|
|
285
|
+
if (/^\[\d+ matching files shown\. Use skip=\d+ to view more\.\]$/.test(line)) {
|
|
286
|
+
if (sawContinuationHint)
|
|
287
|
+
continue;
|
|
288
|
+
sawContinuationHint = true;
|
|
289
|
+
}
|
|
290
|
+
if (line.startsWith("[Search collected the first ")) {
|
|
291
|
+
if (sawCapHint)
|
|
292
|
+
continue;
|
|
293
|
+
sawCapHint = true;
|
|
294
|
+
}
|
|
295
|
+
const m = header ? line.match(/^[* ]?(\d+):/) : undefined;
|
|
296
|
+
const key = m ? `${header}:${m[1]}` : "";
|
|
297
|
+
if (key && seen.has(key))
|
|
298
|
+
continue;
|
|
299
|
+
if (key)
|
|
300
|
+
seen.add(key);
|
|
301
|
+
out.push(line);
|
|
302
|
+
}
|
|
303
|
+
return out.join("\n");
|
|
304
|
+
}
|
|
305
|
+
function formatSearchCall(args, theme) {
|
|
306
|
+
const pattern = str(args?.pattern);
|
|
307
|
+
const rawPaths = Array.isArray(args?.paths) ? args.paths.join(", ") : args?.paths;
|
|
308
|
+
const rawPath = str(args?.path ?? rawPaths);
|
|
309
|
+
const path = rawPath !== null ? shortenPath(rawPath || ".") : null;
|
|
310
|
+
const glob = str(args?.glob);
|
|
311
|
+
const limit = args?.limit;
|
|
312
|
+
const invalidArg = invalidArgText(theme);
|
|
313
|
+
let text = theme.fg("toolTitle", theme.bold("search")) +
|
|
314
|
+
" " +
|
|
315
|
+
(pattern === null ? invalidArg : theme.fg("accent", `/${pattern || ""}/`)) +
|
|
316
|
+
theme.fg("toolOutput", ` in ${path === null ? invalidArg : path}`);
|
|
317
|
+
if (glob)
|
|
318
|
+
text += theme.fg("toolOutput", ` (${glob})`);
|
|
319
|
+
if (limit !== undefined)
|
|
320
|
+
text += theme.fg("toolOutput", ` limit ${limit}`);
|
|
321
|
+
return text;
|
|
322
|
+
}
|
|
323
|
+
export function createSearchToolDefinition(cwd, options) {
|
|
324
|
+
const grepDefinition = createGrepToolDefinition(cwd, { ...options, nativeCache: false });
|
|
325
|
+
const hashlineStore = options?.hashlineStore ?? createHashlineSnapshotStore();
|
|
326
|
+
return {
|
|
327
|
+
...grepDefinition,
|
|
328
|
+
name: "search",
|
|
329
|
+
label: "search",
|
|
330
|
+
description: "Search file contents with a regex across files, directories, globs, and internal URLs.",
|
|
331
|
+
promptSnippet: "Search file contents with regex patterns.",
|
|
332
|
+
parameters: searchSchema,
|
|
333
|
+
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
334
|
+
const resourceCtx = ctx;
|
|
335
|
+
if (params.pattern.trim() === "")
|
|
336
|
+
throw new Error("Pattern must not be empty");
|
|
337
|
+
const targets = normalizeSearchTargets(params.paths, cwd);
|
|
338
|
+
const ignoreCase = params.i === true || params.case === false;
|
|
339
|
+
const contextBefore = options?.contextBefore ?? 1, contextAfter = options?.contextAfter ?? 3, searchContext = Math.max(contextBefore, contextAfter);
|
|
340
|
+
const isSingleFileSearch = targets.length === 1 && await fsStat(resolveToCwd(targets[0].path, cwd)).then((stat) => stat.isFile()).catch(() => false);
|
|
341
|
+
const limit = isSingleFileSearch ? SINGLE_FILE_MATCHES : DEFAULT_LIMIT;
|
|
342
|
+
let skip = normalizeSkip(params.skip);
|
|
343
|
+
const singleFileSkipIgnored = skip > 0 && isSingleFileSearch;
|
|
344
|
+
if (singleFileSkipIgnored)
|
|
345
|
+
skip = 0;
|
|
346
|
+
for (const target of targets) {
|
|
347
|
+
if (target.archive || target.sqlite || target.internal)
|
|
348
|
+
continue;
|
|
349
|
+
if (target.lineRanges && !await fsStat(resolveToCwd(target.path, cwd)).then((stat) => stat.isFile()).catch(() => false)) {
|
|
350
|
+
throw new Error("Line-range search selectors are only supported for single files");
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
const results = [];
|
|
354
|
+
const groups = [];
|
|
355
|
+
const skippedMissingPaths = [];
|
|
356
|
+
const scopePath = targets.map((target) => target.path).join(", ") || ".";
|
|
357
|
+
let pageFullWithMore = false, internalSearchCapped = false;
|
|
358
|
+
let remaining = limit;
|
|
359
|
+
const targetHasMatch = async (target) => {
|
|
360
|
+
if (target.archive || target.sqlite || target.internal) {
|
|
361
|
+
const archive = target.archive ? resolveArchiveSelector(target.archive, cwd) : undefined;
|
|
362
|
+
const text = archive ? searchArchiveSelector(archive, params.pattern, ignoreCase, false, contextBefore, contextAfter) : target.sqlite ? searchSqliteSelector(target.sqlite, params.pattern, ignoreCase, contextBefore, contextAfter) : await searchInternalSelector(target.internal, cwd, params.pattern, ignoreCase, false, resourceCtx, contextBefore, contextAfter);
|
|
363
|
+
return groupSearchOutput(filterSearchOutputByLineRange(text, target.lineRanges, contextBefore, contextAfter)).length > 0;
|
|
364
|
+
}
|
|
365
|
+
const ranged = await searchFileLineRanges(target, cwd, params.pattern, ignoreCase, contextBefore, contextAfter);
|
|
366
|
+
if (ranged !== undefined)
|
|
367
|
+
return groupSearchOutput(ranged).length > 0;
|
|
368
|
+
const probe = await grepDefinition.execute(toolCallId, { pattern: params.pattern, path: target.path, glob: target.glob, ignoreCase, literal: false, context: 0, limit: 1, gitignore: params.gitignore }, signal, undefined, ctx);
|
|
369
|
+
return probe.content.some((item) => item.type === "text" && item.text && item.text !== "No matches found");
|
|
370
|
+
};
|
|
371
|
+
for (const target of targets) {
|
|
372
|
+
if (skip === 0 && remaining <= 0) {
|
|
373
|
+
if (!target.archive && !target.sqlite && !target.internal && targets.length > 1 && await fsStat(resolveToCwd(target.path, cwd)).then(() => false).catch(() => true)) {
|
|
374
|
+
skippedMissingPaths.push(target.path);
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
if (await targetHasMatch(target)) {
|
|
378
|
+
pageFullWithMore = true;
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
if (target.archive || target.sqlite || target.internal) {
|
|
384
|
+
const archive = target.archive ? resolveArchiveSelector(target.archive, cwd) : undefined;
|
|
385
|
+
let text = archive ? searchArchiveSelector(archive, params.pattern, ignoreCase, false, contextBefore, contextAfter) : target.sqlite ? searchSqliteSelector(target.sqlite, params.pattern, ignoreCase, contextBefore, contextAfter) : await searchInternalSelector(target.internal, cwd, params.pattern, ignoreCase, false, resourceCtx, contextBefore, contextAfter);
|
|
386
|
+
if (archive && target.archive)
|
|
387
|
+
text = text.split(archive.archivePath).join(target.archive.archivePath);
|
|
388
|
+
if (target.sqlite)
|
|
389
|
+
text = text.split(target.sqlite.databasePath).join(target.path.match(/^(.+?\.(?:sqlite3?|db3?))/i)?.[1] ?? target.sqlite.databasePath);
|
|
390
|
+
text = text || "No matches found";
|
|
391
|
+
text = filterSearchOutputByLineRange(text, target.lineRanges, contextBefore, contextAfter);
|
|
392
|
+
const outputGroups = groupSearchOutput(text);
|
|
393
|
+
if (skip > 0)
|
|
394
|
+
groups.push(...outputGroups.map((group) => ({ targetPath: target.path, virtual: isVirtualSearchTarget(target), group })));
|
|
395
|
+
else if (outputGroups.length > 0) {
|
|
396
|
+
const hadMore = outputGroups.length > remaining;
|
|
397
|
+
text = formatSearchGroups(outputGroups, remaining);
|
|
398
|
+
if (hadMore) {
|
|
399
|
+
text += `\n\n${continuationHint(limit, skip)}`;
|
|
400
|
+
pageFullWithMore = true;
|
|
401
|
+
}
|
|
402
|
+
remaining -= Math.min(remaining, outputGroups.length);
|
|
403
|
+
}
|
|
404
|
+
else if (text && text !== "No matches found") {
|
|
405
|
+
remaining--;
|
|
406
|
+
}
|
|
407
|
+
else
|
|
408
|
+
text = "No matches found";
|
|
409
|
+
if (skip === 0)
|
|
410
|
+
results.push({ content: [{ type: "text", text }], details: undefined });
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
if (targets.length > 1 && await fsStat(resolveToCwd(target.path, cwd)).then(() => false).catch(() => true)) {
|
|
414
|
+
skippedMissingPaths.push(target.path);
|
|
415
|
+
results.push({ content: [{ type: "text", text: "No matches found" }], details: undefined });
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
const rangedText = await searchFileLineRanges(target, cwd, params.pattern, ignoreCase, contextBefore, contextAfter);
|
|
419
|
+
if (rangedText !== undefined) {
|
|
420
|
+
let text = applyDefaultSearchContext(rangedText, contextBefore, contextAfter) || "No matches found";
|
|
421
|
+
const result = { content: [{ type: "text", text }], details: undefined };
|
|
422
|
+
results.push(result);
|
|
423
|
+
const outputGroups = groupSearchOutput(text);
|
|
424
|
+
if (skip > 0)
|
|
425
|
+
groups.push(...outputGroups.map((group) => ({ targetPath: target.path, virtual: false, group })));
|
|
426
|
+
else if (!isSingleFileSearch && outputGroups.length > 0) {
|
|
427
|
+
const hadMore = outputGroups.length > remaining;
|
|
428
|
+
text = formatSearchGroups(outputGroups, remaining);
|
|
429
|
+
if (hadMore) {
|
|
430
|
+
result.details = fileLimitDetails(limit);
|
|
431
|
+
pageFullWithMore = true;
|
|
432
|
+
}
|
|
433
|
+
remaining -= Math.min(remaining, outputGroups.length);
|
|
434
|
+
result.content = [{ type: "text", text }];
|
|
435
|
+
}
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
const result = await grepDefinition.execute(toolCallId, {
|
|
439
|
+
pattern: params.pattern,
|
|
440
|
+
path: target.path,
|
|
441
|
+
glob: target.glob,
|
|
442
|
+
ignoreCase,
|
|
443
|
+
literal: false,
|
|
444
|
+
context: searchContext,
|
|
445
|
+
maxCountPerFile: isSingleFileSearch ? SINGLE_FILE_MATCHES : MULTI_FILE_PER_FILE_MATCHES,
|
|
446
|
+
limit: INTERNAL_SKIP_LIMIT,
|
|
447
|
+
gitignore: params.gitignore,
|
|
448
|
+
}, signal, onUpdate, ctx);
|
|
449
|
+
results.push(result);
|
|
450
|
+
if (result.details?.matchLimitReached)
|
|
451
|
+
internalSearchCapped = true;
|
|
452
|
+
let text = result.content
|
|
453
|
+
.map((item) => (item.type === "text" ? item.text : undefined))
|
|
454
|
+
.filter((text) => typeof text === "string")
|
|
455
|
+
.join("\n");
|
|
456
|
+
text = filterSearchOutputByLineRange(text, target.lineRanges, contextBefore, contextAfter);
|
|
457
|
+
if (text !== "No matches found")
|
|
458
|
+
text = applyDefaultSearchContext(text, contextBefore, contextAfter) || "No matches found";
|
|
459
|
+
const outputGroups = groupSearchOutput(text);
|
|
460
|
+
if (skip > 0)
|
|
461
|
+
groups.push(...outputGroups.map((group) => ({ targetPath: target.path, virtual: false, group })));
|
|
462
|
+
else if (!isSingleFileSearch && outputGroups.length > 0) {
|
|
463
|
+
const hadMore = outputGroups.length > remaining;
|
|
464
|
+
text = formatSearchGroups(outputGroups, remaining);
|
|
465
|
+
if (hadMore) {
|
|
466
|
+
result.details = { ...(result.details ?? {}), ...fileLimitDetails(limit) };
|
|
467
|
+
pageFullWithMore = true;
|
|
468
|
+
}
|
|
469
|
+
remaining -= Math.min(remaining, outputGroups.length);
|
|
470
|
+
}
|
|
471
|
+
result.content = [{ type: "text", text }];
|
|
472
|
+
}
|
|
473
|
+
const details = {};
|
|
474
|
+
for (const result of results) {
|
|
475
|
+
if (result.details?.truncation)
|
|
476
|
+
details.truncation = result.details.truncation;
|
|
477
|
+
if (result.details?.matchLimitReached)
|
|
478
|
+
details.matchLimitReached = result.details.matchLimitReached;
|
|
479
|
+
if (hasFileLimit(result.details))
|
|
480
|
+
markFileLimit(details, limit);
|
|
481
|
+
if (result.details?.linesTruncated)
|
|
482
|
+
details.linesTruncated = true;
|
|
483
|
+
}
|
|
484
|
+
if (skip > 0) {
|
|
485
|
+
const renderedPages = [];
|
|
486
|
+
let remainingMatches = limit;
|
|
487
|
+
for (const item of groups.slice(skip)) {
|
|
488
|
+
if (remainingMatches <= 0)
|
|
489
|
+
break;
|
|
490
|
+
const raw = formatSearchGroups([item.group], 1);
|
|
491
|
+
remainingMatches--;
|
|
492
|
+
if (raw)
|
|
493
|
+
renderedPages.push(item.virtual ? raw : await addHashlineHeadersToSearchOutput(raw, cwd, item.targetPath, hashlineStore));
|
|
494
|
+
}
|
|
495
|
+
const hasMorePages = groups.slice(skip + limit).length > 0;
|
|
496
|
+
const cappedBeforeRequestedPage = internalSearchCapped && skip >= groups.length;
|
|
497
|
+
const capNotice = internalSearchCapped && !hasMorePages ? `\n\n${internalCapHint()}` : "";
|
|
498
|
+
const output = `${renderedPages.join("\n\n") || `No more results (skip=${skip})`}${hasMorePages ? `\n\n${continuationHint(limit, skip)}` : ""}${capNotice}`;
|
|
499
|
+
if (hasMorePages || cappedBeforeRequestedPage || capNotice)
|
|
500
|
+
markFileLimit(details, limit);
|
|
501
|
+
const truncation = truncateHead(output, { maxLines: Number.MAX_SAFE_INTEGER });
|
|
502
|
+
let content = truncation.content;
|
|
503
|
+
if (truncation.truncated) {
|
|
504
|
+
details.truncation = truncation;
|
|
505
|
+
content += `\n\n[${formatSize(DEFAULT_MAX_BYTES)} combined output limit reached]`;
|
|
506
|
+
}
|
|
507
|
+
return {
|
|
508
|
+
content: [{ type: "text", text: content }],
|
|
509
|
+
details: buildSearchDetails(details, content, cwd, scopePath, skippedMissingPaths),
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
if (results.length === 1) {
|
|
513
|
+
const result = results[0];
|
|
514
|
+
let text = result.content
|
|
515
|
+
.map((item) => (item.type === "text" ? item.text : undefined))
|
|
516
|
+
.filter((value) => typeof value === "string")
|
|
517
|
+
.join("\n");
|
|
518
|
+
text = filterSearchOutputByLineRange(text, targets[0]?.lineRanges, contextBefore, contextAfter);
|
|
519
|
+
if (!isVirtualSearchTarget(targets[0]) && text !== "No matches found")
|
|
520
|
+
text = applyDefaultSearchContext(text, contextBefore, contextAfter) || "No matches found";
|
|
521
|
+
const skipNotice = singleFileSkipIgnored ? "\n\n[skip is ignored for single-file search; refine the pattern/path or search a directory to page matching files.]" : "";
|
|
522
|
+
if (isSingleFileSearch && text !== "No matches found")
|
|
523
|
+
text = formatSearchGroups(groupSearchOutput(text), limit, limit);
|
|
524
|
+
if (text && text !== "No matches found") {
|
|
525
|
+
const renderedText = isVirtualSearchTarget(targets[0]) ? text : await addHashlineHeadersToSearchOutput(text, cwd, targets[0]?.path ?? ".", hashlineStore);
|
|
526
|
+
const pagedText = `${hasFileLimit(result.details) ? `${renderedText}\n\n${continuationHint(limit, skip)}` : renderedText}${skipNotice}`;
|
|
527
|
+
const truncation = truncateHead(pagedText, { maxLines: Number.MAX_SAFE_INTEGER });
|
|
528
|
+
if (truncation.truncated)
|
|
529
|
+
return { ...result, content: [{ type: "text", text: `${truncation.content}\n\n[${formatSize(DEFAULT_MAX_BYTES)} combined output limit reached]` }], details: buildSearchDetails({ ...(result.details ?? {}), truncation }, truncation.content, cwd, scopePath, skippedMissingPaths) };
|
|
530
|
+
return { ...result, content: [{ type: "text", text: pagedText }], details: buildSearchDetails(result.details, pagedText, cwd, scopePath, skippedMissingPaths) };
|
|
531
|
+
}
|
|
532
|
+
return { ...result, details: buildSearchDetails(result.details, text || "No matches found", cwd, scopePath, skippedMissingPaths) };
|
|
533
|
+
}
|
|
534
|
+
const renderedResults = await Promise.all(results.map(async (result, index) => {
|
|
535
|
+
let text = result.content
|
|
536
|
+
.map((item) => (item.type === "text" ? item.text : undefined))
|
|
537
|
+
.filter((value) => typeof value === "string" && value.length > 0)
|
|
538
|
+
.join("\n");
|
|
539
|
+
text = filterSearchOutputByLineRange(text, targets[index]?.lineRanges, contextBefore, contextAfter);
|
|
540
|
+
if (!isVirtualSearchTarget(targets[index]) && text !== "No matches found")
|
|
541
|
+
text = applyDefaultSearchContext(text, contextBefore, contextAfter);
|
|
542
|
+
if (!text || text === "No matches found")
|
|
543
|
+
return `# ${targets[index]?.path ?? "."}\n${text}`;
|
|
544
|
+
return isVirtualSearchTarget(targets[index]) ? text : await addHashlineHeadersToSearchOutput(text, cwd, targets[index]?.path ?? ".", hashlineStore);
|
|
545
|
+
}));
|
|
546
|
+
const content = dedupeRenderedSearchOutput(`${renderedResults.join("\n\n")}${skippedMissingPaths.length > 0 ? `\n\n[Skipped missing paths: ${skippedMissingPaths.join(", ")}]` : ""}${pageFullWithMore ? `\n\n${continuationHint(limit, skip)}` : ""}${internalSearchCapped && !pageFullWithMore ? `\n\n${internalCapHint()}` : ""}`);
|
|
547
|
+
if (pageFullWithMore || internalSearchCapped)
|
|
548
|
+
markFileLimit(details, limit);
|
|
549
|
+
const truncation = truncateHead(content, { maxLines: Number.MAX_SAFE_INTEGER });
|
|
550
|
+
let output = truncation.content;
|
|
551
|
+
if (truncation.truncated) {
|
|
552
|
+
details.truncation = truncation;
|
|
553
|
+
output += `\n\n[${formatSize(DEFAULT_MAX_BYTES)} combined output limit reached]`;
|
|
554
|
+
}
|
|
555
|
+
return {
|
|
556
|
+
content: [{ type: "text", text: output }],
|
|
557
|
+
details: buildSearchDetails(details, output, cwd, scopePath, skippedMissingPaths),
|
|
558
|
+
};
|
|
559
|
+
},
|
|
560
|
+
renderCall(args, theme, context) {
|
|
561
|
+
const text = context.lastComponent ?? new Text("", 0, 0);
|
|
562
|
+
text.setText(formatSearchCall(args, theme));
|
|
563
|
+
return text;
|
|
564
|
+
},
|
|
565
|
+
renderResult(result, options, theme, context) {
|
|
566
|
+
return grepDefinition.renderResult?.(result, options, theme, context) ?? new Text("", 0, 0);
|
|
567
|
+
},
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
export function createSearchTool(cwd, options) {
|
|
571
|
+
return wrapToolDefinition(createSearchToolDefinition(cwd, options));
|
|
572
|
+
}
|
|
573
|
+
//# sourceMappingURL=search.js.map
|