@kolisachint/hoocode-agent 0.4.106 → 0.4.108
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 +4 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +4 -4
- package/dist/cli/args.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +0 -1
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -2
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session.d.ts +1 -3
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +2 -2
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +1 -1
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/extension-mcp-servers.d.ts +1 -1
- package/dist/core/extension-mcp-servers.d.ts.map +1 -1
- package/dist/core/extension-mcp-servers.js +1 -1
- package/dist/core/extension-mcp-servers.js.map +1 -1
- package/dist/core/extensions/plugins/index.d.ts.map +1 -1
- package/dist/core/extensions/plugins/index.js +1 -1
- package/dist/core/extensions/plugins/index.js.map +1 -1
- package/dist/core/extensions/types.d.ts +1 -3
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/mode-prompts.d.ts +1 -1
- package/dist/core/mode-prompts.d.ts.map +1 -1
- package/dist/core/mode-prompts.js +1 -1
- package/dist/core/mode-prompts.js.map +1 -1
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +1 -1
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +1 -2
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts +2 -97
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +4 -65
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +2 -3
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/telemetry.d.ts.map +1 -1
- package/dist/core/telemetry.js +1 -1
- package/dist/core/telemetry.js.map +1 -1
- package/dist/core/timings.d.ts +1 -1
- package/dist/core/timings.d.ts.map +1 -1
- package/dist/core/timings.js +2 -2
- package/dist/core/timings.js.map +1 -1
- package/dist/core/tools/{browser-continue.d.ts → browser/browser-continue.d.ts} +1 -1
- package/dist/core/tools/browser/browser-continue.d.ts.map +1 -0
- package/dist/core/tools/{browser-continue.js → browser/browser-continue.js} +2 -2
- package/dist/core/tools/browser/browser-continue.js.map +1 -0
- package/dist/core/tools/{browser-run.d.ts → browser/browser-run.d.ts} +2 -2
- package/dist/core/tools/browser/browser-run.d.ts.map +1 -0
- package/dist/core/tools/{browser-run.js → browser/browser-run.js} +2 -2
- package/dist/core/tools/browser/browser-run.js.map +1 -0
- package/dist/core/tools/browser/browsertools-shared.d.ts.map +1 -0
- package/dist/core/tools/{browsertools-shared.js → browser/browsertools-shared.js} +1 -1
- package/dist/core/tools/browser/browsertools-shared.js.map +1 -0
- package/dist/core/tools/browser/index.d.ts +8 -0
- package/dist/core/tools/browser/index.d.ts.map +1 -0
- package/dist/core/tools/browser/index.js +8 -0
- package/dist/core/tools/browser/index.js.map +1 -0
- package/dist/core/tools/{docedit.d.ts → doc/docedit.d.ts} +2 -2
- package/dist/core/tools/doc/docedit.d.ts.map +1 -0
- package/dist/core/tools/{docedit.js → doc/docedit.js} +7 -7
- package/dist/core/tools/doc/docedit.js.map +1 -0
- package/dist/core/tools/{docgrep.d.ts → doc/docgrep.d.ts} +2 -2
- package/dist/core/tools/doc/docgrep.d.ts.map +1 -0
- package/dist/core/tools/{docgrep.js → doc/docgrep.js} +6 -6
- package/dist/core/tools/doc/docgrep.js.map +1 -0
- package/dist/core/tools/{docpeek.d.ts → doc/docpeek.d.ts} +2 -2
- package/dist/core/tools/{docpeek.js.map → doc/docpeek.d.ts.map} +1 -1
- package/dist/core/tools/{docpeek.js → doc/docpeek.js} +6 -6
- package/dist/core/tools/doc/docpeek.js.map +1 -0
- package/dist/core/tools/{docread.d.ts → doc/docread.d.ts} +2 -2
- package/dist/core/tools/doc/docread.d.ts.map +1 -0
- package/dist/core/tools/{docread.js → doc/docread.js} +6 -6
- package/dist/core/tools/doc/docread.js.map +1 -0
- package/dist/core/tools/{docscan.d.ts → doc/docscan.d.ts} +2 -2
- package/dist/core/tools/doc/docscan.d.ts.map +1 -0
- package/dist/core/tools/{docscan.js → doc/docscan.js} +6 -6
- package/dist/core/tools/doc/docscan.js.map +1 -0
- package/dist/core/tools/{docwrite.d.ts → doc/docwrite.d.ts} +1 -1
- package/dist/core/tools/doc/docwrite.d.ts.map +1 -0
- package/dist/core/tools/{docwrite.js → doc/docwrite.js} +6 -6
- package/dist/core/tools/doc/docwrite.js.map +1 -0
- package/dist/core/tools/doc/index.d.ts +11 -0
- package/dist/core/tools/doc/index.d.ts.map +1 -0
- package/dist/core/tools/doc/index.js +11 -0
- package/dist/core/tools/doc/index.js.map +1 -0
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +1 -1
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/index.d.ts +42 -28
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +61 -199
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/output-accumulator.d.ts.map +1 -1
- package/dist/core/tools/output-accumulator.js +1 -1
- package/dist/core/tools/output-accumulator.js.map +1 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +1 -1
- package/dist/core/tools/read.js.map +1 -1
- package/dist/extensions/core/ask-options.d.ts +10 -0
- package/dist/extensions/core/ask-options.d.ts.map +1 -0
- package/dist/extensions/core/ask-options.js +83 -0
- package/dist/extensions/core/ask-options.js.map +1 -0
- package/dist/extensions/core/config.d.ts +80 -0
- package/dist/extensions/core/config.d.ts.map +1 -0
- package/dist/extensions/core/config.js +110 -0
- package/dist/extensions/core/config.js.map +1 -0
- package/dist/extensions/core/cost.d.ts +11 -0
- package/dist/extensions/core/cost.d.ts.map +1 -0
- package/dist/extensions/core/cost.js +64 -0
- package/dist/extensions/core/cost.js.map +1 -0
- package/dist/extensions/core/hoo-core.d.ts +16 -179
- package/dist/extensions/core/hoo-core.d.ts.map +1 -1
- package/dist/extensions/core/hoo-core.js +27 -1663
- package/dist/extensions/core/hoo-core.js.map +1 -1
- package/dist/extensions/core/loop.d.ts +15 -0
- package/dist/extensions/core/loop.d.ts.map +1 -0
- package/dist/extensions/core/loop.js +235 -0
- package/dist/extensions/core/loop.js.map +1 -0
- package/dist/extensions/core/marketplace.d.ts +17 -0
- package/dist/extensions/core/marketplace.d.ts.map +1 -0
- package/dist/extensions/core/marketplace.js +167 -0
- package/dist/extensions/core/marketplace.js.map +1 -0
- package/dist/extensions/core/mcp-loader.d.ts +26 -0
- package/dist/extensions/core/mcp-loader.d.ts.map +1 -0
- package/dist/extensions/core/mcp-loader.js +391 -0
- package/dist/extensions/core/mcp-loader.js.map +1 -0
- package/dist/extensions/core/modes.d.ts +56 -0
- package/dist/extensions/core/modes.d.ts.map +1 -0
- package/dist/extensions/core/modes.js +268 -0
- package/dist/extensions/core/modes.js.map +1 -0
- package/dist/extensions/core/permission-gate.d.ts +10 -0
- package/dist/extensions/core/permission-gate.d.ts.map +1 -0
- package/dist/extensions/core/permission-gate.js +167 -0
- package/dist/extensions/core/permission-gate.js.map +1 -0
- package/dist/extensions/core/scaffold.d.ts +9 -0
- package/dist/extensions/core/scaffold.d.ts.map +1 -0
- package/dist/extensions/core/scaffold.js +146 -0
- package/dist/extensions/core/scaffold.js.map +1 -0
- package/dist/extensions/core/thinking-escalation.d.ts +18 -0
- package/dist/extensions/core/thinking-escalation.d.ts.map +1 -0
- package/dist/extensions/core/thinking-escalation.js +70 -0
- package/dist/extensions/core/thinking-escalation.js.map +1 -0
- package/dist/index.d.ts +2 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -4
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +2 -2
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/bash-execution-controller.d.ts +34 -0
- package/dist/modes/interactive/bash-execution-controller.d.ts.map +1 -0
- package/dist/modes/interactive/bash-execution-controller.js +98 -0
- package/dist/modes/interactive/bash-execution-controller.js.map +1 -0
- package/dist/modes/interactive/command-executor.d.ts +0 -5
- package/dist/modes/interactive/command-executor.d.ts.map +1 -1
- package/dist/modes/interactive/command-executor.js +0 -7
- package/dist/modes/interactive/command-executor.js.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.d.ts +1 -1
- package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.d.ts +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/custom-message.d.ts +1 -1
- package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-message.js.map +1 -1
- package/dist/modes/interactive/components/index.d.ts +0 -2
- package/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/dist/modes/interactive/components/index.js +0 -2
- package/dist/modes/interactive/components/index.js.map +1 -1
- package/dist/modes/interactive/extension-chrome.d.ts +56 -0
- package/dist/modes/interactive/extension-chrome.d.ts.map +1 -0
- package/dist/modes/interactive/extension-chrome.js +172 -0
- package/dist/modes/interactive/extension-chrome.js.map +1 -0
- package/dist/modes/interactive/extension-dialogs.d.ts +82 -0
- package/dist/modes/interactive/extension-dialogs.d.ts.map +1 -0
- package/dist/modes/interactive/extension-dialogs.js +259 -0
- package/dist/modes/interactive/extension-dialogs.js.map +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts +16 -206
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +359 -2370
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/login-controller.d.ts +50 -0
- package/dist/modes/interactive/login-controller.d.ts.map +1 -0
- package/dist/modes/interactive/login-controller.js +352 -0
- package/dist/modes/interactive/login-controller.js.map +1 -0
- package/dist/modes/interactive/message-queue-controller.d.ts +49 -0
- package/dist/modes/interactive/message-queue-controller.d.ts.map +1 -0
- package/dist/modes/interactive/message-queue-controller.js +184 -0
- package/dist/modes/interactive/message-queue-controller.js.map +1 -0
- package/dist/modes/interactive/model-controller.d.ts +43 -0
- package/dist/modes/interactive/model-controller.d.ts.map +1 -0
- package/dist/modes/interactive/model-controller.js +184 -0
- package/dist/modes/interactive/model-controller.js.map +1 -0
- package/dist/modes/interactive/resource-display.d.ts +59 -0
- package/dist/modes/interactive/resource-display.d.ts.map +1 -0
- package/dist/modes/interactive/resource-display.js +499 -0
- package/dist/modes/interactive/resource-display.js.map +1 -0
- package/dist/modes/interactive/startup-checks.d.ts +17 -0
- package/dist/modes/interactive/startup-checks.d.ts.map +1 -0
- package/dist/modes/interactive/startup-checks.js +102 -0
- package/dist/modes/interactive/startup-checks.js.map +1 -0
- package/dist/modes/interactive/team-focus.d.ts +56 -0
- package/dist/modes/interactive/team-focus.d.ts.map +1 -0
- package/dist/modes/interactive/team-focus.js +167 -0
- package/dist/modes/interactive/team-focus.js.map +1 -0
- package/dist/modes/interactive/voice/voice-controller.d.ts +66 -0
- package/dist/modes/interactive/voice/voice-controller.d.ts.map +1 -0
- package/dist/modes/interactive/voice/voice-controller.js +312 -0
- package/dist/modes/interactive/voice/voice-controller.js.map +1 -0
- package/dist/modes/interactive/voice/voice-panel.d.ts.map +1 -0
- package/dist/modes/interactive/voice/voice-panel.js.map +1 -0
- package/dist/modes/interactive/voice/voice-transcribe.d.ts.map +1 -0
- package/dist/modes/interactive/voice/voice-transcribe.js.map +1 -0
- package/dist/modes/rpc/rpc-client.d.ts +1 -2
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +1 -2
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/resources-cli.d.ts.map +1 -1
- package/dist/resources-cli.js +1 -1
- package/dist/resources-cli.js.map +1 -1
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +1 -1
- package/dist/utils/tools-manager.js.map +1 -1
- package/dist/utils/version-check.d.ts.map +1 -1
- package/dist/utils/version-check.js +2 -2
- package/dist/utils/version-check.js.map +1 -1
- package/docs/project-local-resources.md +1 -1
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +4 -4
- package/dist/core/compaction/branch-summarization.d.ts +0 -88
- package/dist/core/compaction/branch-summarization.d.ts.map +0 -1
- package/dist/core/compaction/branch-summarization.js +0 -243
- package/dist/core/compaction/branch-summarization.js.map +0 -1
- package/dist/core/compaction/compaction.d.ts +0 -123
- package/dist/core/compaction/compaction.d.ts.map +0 -1
- package/dist/core/compaction/compaction.js +0 -635
- package/dist/core/compaction/compaction.js.map +0 -1
- package/dist/core/compaction/index.d.ts +0 -7
- package/dist/core/compaction/index.d.ts.map +0 -1
- package/dist/core/compaction/index.js +0 -7
- package/dist/core/compaction/index.js.map +0 -1
- package/dist/core/compaction/utils.d.ts +0 -38
- package/dist/core/compaction/utils.d.ts.map +0 -1
- package/dist/core/compaction/utils.js +0 -156
- package/dist/core/compaction/utils.js.map +0 -1
- package/dist/core/messages.d.ts +0 -124
- package/dist/core/messages.d.ts.map +0 -1
- package/dist/core/messages.js +0 -221
- package/dist/core/messages.js.map +0 -1
- package/dist/core/tools/browser-continue.d.ts.map +0 -1
- package/dist/core/tools/browser-continue.js.map +0 -1
- package/dist/core/tools/browser-run.d.ts.map +0 -1
- package/dist/core/tools/browser-run.js.map +0 -1
- package/dist/core/tools/browsertools-shared.d.ts.map +0 -1
- package/dist/core/tools/browsertools-shared.js.map +0 -1
- package/dist/core/tools/docedit.d.ts.map +0 -1
- package/dist/core/tools/docedit.js.map +0 -1
- package/dist/core/tools/docgrep.d.ts.map +0 -1
- package/dist/core/tools/docgrep.js.map +0 -1
- package/dist/core/tools/docpeek.d.ts.map +0 -1
- package/dist/core/tools/docread.d.ts.map +0 -1
- package/dist/core/tools/docread.js.map +0 -1
- package/dist/core/tools/docscan.d.ts.map +0 -1
- package/dist/core/tools/docscan.js.map +0 -1
- package/dist/core/tools/docwrite.d.ts.map +0 -1
- package/dist/core/tools/docwrite.js.map +0 -1
- package/dist/core/tools/output-compression.d.ts +0 -53
- package/dist/core/tools/output-compression.d.ts.map +0 -1
- package/dist/core/tools/output-compression.js +0 -392
- package/dist/core/tools/output-compression.js.map +0 -1
- package/dist/modes/interactive/components/armin.d.ts +0 -34
- package/dist/modes/interactive/components/armin.d.ts.map +0 -1
- package/dist/modes/interactive/components/armin.js +0 -333
- package/dist/modes/interactive/components/armin.js.map +0 -1
- package/dist/modes/interactive/components/daxnuts.d.ts +0 -23
- package/dist/modes/interactive/components/daxnuts.d.ts.map +0 -1
- package/dist/modes/interactive/components/daxnuts.js +0 -140
- package/dist/modes/interactive/components/daxnuts.js.map +0 -1
- package/dist/modes/interactive/components/voice-panel.d.ts.map +0 -1
- package/dist/modes/interactive/components/voice-panel.js.map +0 -1
- package/dist/modes/interactive/voice-transcribe.d.ts.map +0 -1
- package/dist/modes/interactive/voice-transcribe.js.map +0 -1
- /package/dist/core/tools/{browsertools-shared.d.ts → browser/browsertools-shared.d.ts} +0 -0
- /package/dist/modes/interactive/{components → voice}/voice-panel.d.ts +0 -0
- /package/dist/modes/interactive/{components → voice}/voice-panel.js +0 -0
- /package/dist/modes/interactive/{voice-transcribe.d.ts → voice/voice-transcribe.d.ts} +0 -0
- /package/dist/modes/interactive/{voice-transcribe.js → voice/voice-transcribe.js} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docread.d.ts","sourceRoot":"","sources":["../../../../src/core/tools/doc/docread.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAEjE,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAG5C,OAAO,KAAK,EAAE,cAAc,EAA2B,MAAM,2BAA2B,CAAC;AACzF,OAAO,EAGN,KAAK,QAAQ,EAIb,MAAM,wBAAwB,CAAC;AAKhC,QAAA,MAAM,aAAa;;;EAWjB,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,OAAO,aAAa,CAAC,CAAC;AAE5D,MAAM,WAAW,kBAAkB;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,kBAAkB;IAClC,sDAAsD;IACtD,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAUD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,CAiB5F;AA6BD,wBAAgB,2BAA2B,CAC1C,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,kBAAkB,GAC1B,cAAc,CAAC,OAAO,aAAa,EAAE,kBAAkB,GAAG,SAAS,CAAC,CAyCtE;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAC,OAAO,aAAa,CAAC,CAE5G","sourcesContent":["import type { AgentTool } from \"@kolisachint/hoocode-agent-core\";\nimport { Text } from \"@kolisachint/hoocode-tui\";\nimport { type Static, Type } from \"typebox\";\nimport { keyHint } from \"../../../modes/interactive/components/keybinding-hints.js\";\nimport { theme as appTheme } from \"../../../modes/interactive/theme/theme.js\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../../extensions/types.js\";\nimport {\n\tDOCREAD_MAX_RENDER_TOKENS,\n\ttype DocNode,\n\ttype Envelope,\n\textractDocument,\n\trenderDocNodeLines,\n\ttruncateRenderToTokenBudget,\n} from \"../filetools-shared.js\";\nimport { resolveReadPath } from \"../path-utils.js\";\nimport { getTextOutput, invalidArgText, shortenPath, str } from \"../render-utils.js\";\nimport { wrapToolDefinition } from \"../tool-definition-wrapper.js\";\n\nconst docReadSchema = Type.Object({\n\tpath: Type.String({\n\t\tdescription:\n\t\t\t\"Path to the document to extract (relative or absolute). XML, drawio, OOXML (docx/xlsx/pptx), or PDF.\",\n\t}),\n\treadonly: Type.Optional(\n\t\tType.Boolean({\n\t\t\tdescription:\n\t\t\t\t\"Analysis-only projection: strips node ids for a smaller view that CANNOT be edited. Omit (default false) when you intend to DocEdit/DocWrite afterwards.\",\n\t\t}),\n\t),\n});\n\nexport type DocReadToolInput = Static<typeof docReadSchema>;\n\nexport interface DocReadToolDetails {\n\ttype?: string;\n\tfidelity?: string;\n\twritable?: boolean;\n\tnodeCount?: number;\n}\n\nexport interface DocReadToolOptions {\n\t/** Timeout (seconds) for the filetools invocation. */\n\ttimeoutSecs?: number;\n}\n\nfunction countNodes(nodes: DocNode[]): number {\n\tlet total = 0;\n\tfor (const node of nodes) {\n\t\ttotal += 1 + (node.children ? countNodes(node.children) : 0);\n\t}\n\treturn total;\n}\n\n/**\n * Render the envelope as compact, id-addressed text the model edits against.\n * Each line carries the node id so DocEdit/DocWrite patches can target it.\n *\n * The filetools binary has no pagination, so a dense document can project into\n * a huge dump. We truncate the rendered view to a token budget and append an\n * actionable notice instead of flooding the model context.\n */\nexport function renderEnvelopeText(envelope: Envelope, readonly: boolean | undefined): string {\n\tconst header =\n\t\t`document ${envelope.source.path} [${envelope.source.type}, ${envelope.fidelity}, ` +\n\t\t`${envelope.writable ? \"writable\" : \"read-only\"}]`;\n\tconst lines: string[] = [header, \"\", ...renderDocNodeLines(envelope.structure)];\n\n\tconst { text, droppedLines } = truncateRenderToTokenBudget(lines);\n\tif (droppedLines === 0) return text;\n\n\tconst hint = readonly\n\t\t? \"narrow to a smaller or more targeted file\"\n\t\t: \"re-run with readonly:true for a smaller analysis-only view, or target a smaller file\";\n\treturn (\n\t\t`${text}\\n\\n[Truncated: document exceeds the ~${DOCREAD_MAX_RENDER_TOKENS} token render budget; ` +\n\t\t`${droppedLines} more node line${droppedLines === 1 ? \"\" : \"s\"} omitted. The ids shown are still valid for ` +\n\t\t`DocEdit/DocWrite — ${hint}.]`\n\t);\n}\n\nfunction formatDocReadCall(args: { path?: string; readonly?: boolean } | undefined): string {\n\tconst path = str(args?.path);\n\tconst pathDisplay =\n\t\tpath === null ? invalidArgText(appTheme) : path ? shortenPath(path) : appTheme.fg(\"toolOutput\", \"...\");\n\tlet text = appTheme.fg(\"toolTitle\", appTheme.bold(\"DocRead \")) + appTheme.fg(\"accent\", pathDisplay);\n\tif (args?.readonly) text += appTheme.fg(\"muted\", \" (readonly)\");\n\treturn text;\n}\n\nfunction formatDocReadResult(\n\tresult: { content: Array<{ type: string; text?: string }>; details?: DocReadToolDetails },\n\toptions: ToolRenderResultOptions,\n\tshowImages: boolean,\n): string {\n\tconst output = getTextOutput(result as any, showImages).trim();\n\tif (!output) return \"\";\n\tconst lines = output.split(\"\\n\");\n\tconst maxLines = options.expanded ? lines.length : 15;\n\tconst displayLines = lines.slice(0, maxLines);\n\tconst remaining = lines.length - maxLines;\n\tlet text = `\\n${displayLines.map((line) => appTheme.fg(\"toolOutput\", line)).join(\"\\n\")}`;\n\tif (remaining > 0) {\n\t\ttext += `${appTheme.fg(\"muted\", `\\n... (${remaining} more lines,`)} ${keyHint(\"app.tools.expand\", \"to expand\")})`;\n\t}\n\treturn text;\n}\n\nexport function createDocReadToolDefinition(\n\tcwd: string,\n\toptions?: DocReadToolOptions,\n): ToolDefinition<typeof docReadSchema, DocReadToolDetails | undefined> {\n\treturn {\n\t\tname: \"DocRead\",\n\t\tlabel: \"DocRead\",\n\t\tdescription:\n\t\t\t\"Extract a structured or binary document (XML, drawio, docx/xlsx/pptx, PDF) into editable, id-addressed JSON the agent can patch losslessly. Each node has a stable #id; edit with DocEdit (in place) or DocWrite (to a new path), passing a patch that targets those ids. This is the canonical way to read and edit these formats: never fall back to ad-hoc scripts (python/openpyxl, docx, PyPDF2, unzip, sed) to parse or rewrite them — that loses the lossless id-map and corrupts formatting. Off by default; enabled with --enable-filetools.\",\n\t\tpromptSnippet: \"Extract a structured/binary document into editable, id-addressed JSON\",\n\t\tpromptGuidelines: [\n\t\t\t\"Use DocRead to open structured/binary documents (XML, drawio, docx/xlsx/pptx, PDF) instead of read; it returns id-addressed nodes you can patch with DocEdit/DocWrite. Never fall back to ad-hoc scripts (python/openpyxl, docx, PyPDF2, unzip, sed) to parse or edit these formats — that loses the lossless id-map and corrupts the file.\",\n\t\t\t\"Flow: scan first, edit second. To understand a document, start with DocRead readonly:true — an analysis-only projection that strips node ids and is much cheaper in tokens. Only do a full (writable) DocRead when you actually intend to edit, since that view carries the whole id-map and is token-heavy.\",\n\t\t\t\"Read once, then edit. A writable DocRead establishes the id-map; DocEdit/DocWrite re-extract on their own, so do not re-run DocRead between edits. If a patch targets stale ids the edit fails and returns the current structure with fresh ids to re-issue against.\",\n\t\t],\n\t\tparameters: docReadSchema,\n\t\tasync execute(_toolCallId, { path, readonly }: DocReadToolInput, signal?: AbortSignal) {\n\t\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\t\t\tconst absolutePath = resolveReadPath(path, cwd);\n\t\t\tconst envelope = await extractDocument(absolutePath, cwd, signal, {\n\t\t\t\treadonly,\n\t\t\t\ttimeoutSecs: options?.timeoutSecs,\n\t\t\t});\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\" as const, text: renderEnvelopeText(envelope, readonly) }],\n\t\t\t\tdetails: {\n\t\t\t\t\ttype: envelope.source.type,\n\t\t\t\t\tfidelity: envelope.fidelity,\n\t\t\t\t\twritable: envelope.writable,\n\t\t\t\t\tnodeCount: countNodes(envelope.structure),\n\t\t\t\t},\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(formatDocReadCall(args));\n\t\t\treturn text;\n\t\t},\n\t\trenderResult(result, options, _theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatDocReadResult(result as any, options, context.showImages));\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport function createDocReadTool(cwd: string, options?: DocReadToolOptions): AgentTool<typeof docReadSchema> {\n\treturn wrapToolDefinition(createDocReadToolDefinition(cwd, options));\n}\n"]}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { Text } from "@kolisachint/hoocode-tui";
|
|
2
2
|
import { Type } from "typebox";
|
|
3
|
-
import { keyHint } from "
|
|
4
|
-
import { theme as appTheme } from "
|
|
5
|
-
import { DOCREAD_MAX_RENDER_TOKENS, extractDocument, renderDocNodeLines, truncateRenderToTokenBudget, } from "
|
|
6
|
-
import { resolveReadPath } from "
|
|
7
|
-
import { getTextOutput, invalidArgText, shortenPath, str } from "
|
|
8
|
-
import { wrapToolDefinition } from "
|
|
3
|
+
import { keyHint } from "../../../modes/interactive/components/keybinding-hints.js";
|
|
4
|
+
import { theme as appTheme } from "../../../modes/interactive/theme/theme.js";
|
|
5
|
+
import { DOCREAD_MAX_RENDER_TOKENS, extractDocument, renderDocNodeLines, truncateRenderToTokenBudget, } from "../filetools-shared.js";
|
|
6
|
+
import { resolveReadPath } from "../path-utils.js";
|
|
7
|
+
import { getTextOutput, invalidArgText, shortenPath, str } from "../render-utils.js";
|
|
8
|
+
import { wrapToolDefinition } from "../tool-definition-wrapper.js";
|
|
9
9
|
const docReadSchema = Type.Object({
|
|
10
10
|
path: Type.String({
|
|
11
11
|
description: "Path to the document to extract (relative or absolute). XML, drawio, OOXML (docx/xlsx/pptx), or PDF.",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docread.js","sourceRoot":"","sources":["../../../../src/core/tools/doc/docread.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentTool } from \"@kolisachint/hoocode-agent-core\";\nimport { Text } from \"@kolisachint/hoocode-tui\";\nimport { type Static, Type } from \"typebox\";\nimport { keyHint } from \"../../../modes/interactive/components/keybinding-hints.js\";\nimport { theme as appTheme } from \"../../../modes/interactive/theme/theme.js\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../../extensions/types.js\";\nimport {\n\tDOCREAD_MAX_RENDER_TOKENS,\n\ttype DocNode,\n\ttype Envelope,\n\textractDocument,\n\trenderDocNodeLines,\n\ttruncateRenderToTokenBudget,\n} from \"../filetools-shared.js\";\nimport { resolveReadPath } from \"../path-utils.js\";\nimport { getTextOutput, invalidArgText, shortenPath, str } from \"../render-utils.js\";\nimport { wrapToolDefinition } from \"../tool-definition-wrapper.js\";\n\nconst docReadSchema = Type.Object({\n\tpath: Type.String({\n\t\tdescription:\n\t\t\t\"Path to the document to extract (relative or absolute). XML, drawio, OOXML (docx/xlsx/pptx), or PDF.\",\n\t}),\n\treadonly: Type.Optional(\n\t\tType.Boolean({\n\t\t\tdescription:\n\t\t\t\t\"Analysis-only projection: strips node ids for a smaller view that CANNOT be edited. Omit (default false) when you intend to DocEdit/DocWrite afterwards.\",\n\t\t}),\n\t),\n});\n\nexport type DocReadToolInput = Static<typeof docReadSchema>;\n\nexport interface DocReadToolDetails {\n\ttype?: string;\n\tfidelity?: string;\n\twritable?: boolean;\n\tnodeCount?: number;\n}\n\nexport interface DocReadToolOptions {\n\t/** Timeout (seconds) for the filetools invocation. */\n\ttimeoutSecs?: number;\n}\n\nfunction countNodes(nodes: DocNode[]): number {\n\tlet total = 0;\n\tfor (const node of nodes) {\n\t\ttotal += 1 + (node.children ? countNodes(node.children) : 0);\n\t}\n\treturn total;\n}\n\n/**\n * Render the envelope as compact, id-addressed text the model edits against.\n * Each line carries the node id so DocEdit/DocWrite patches can target it.\n *\n * The filetools binary has no pagination, so a dense document can project into\n * a huge dump. We truncate the rendered view to a token budget and append an\n * actionable notice instead of flooding the model context.\n */\nexport function renderEnvelopeText(envelope: Envelope, readonly: boolean | undefined): string {\n\tconst header =\n\t\t`document ${envelope.source.path} [${envelope.source.type}, ${envelope.fidelity}, ` +\n\t\t`${envelope.writable ? \"writable\" : \"read-only\"}]`;\n\tconst lines: string[] = [header, \"\", ...renderDocNodeLines(envelope.structure)];\n\n\tconst { text, droppedLines } = truncateRenderToTokenBudget(lines);\n\tif (droppedLines === 0) return text;\n\n\tconst hint = readonly\n\t\t? \"narrow to a smaller or more targeted file\"\n\t\t: \"re-run with readonly:true for a smaller analysis-only view, or target a smaller file\";\n\treturn (\n\t\t`${text}\\n\\n[Truncated: document exceeds the ~${DOCREAD_MAX_RENDER_TOKENS} token render budget; ` +\n\t\t`${droppedLines} more node line${droppedLines === 1 ? \"\" : \"s\"} omitted. The ids shown are still valid for ` +\n\t\t`DocEdit/DocWrite — ${hint}.]`\n\t);\n}\n\nfunction formatDocReadCall(args: { path?: string; readonly?: boolean } | undefined): string {\n\tconst path = str(args?.path);\n\tconst pathDisplay =\n\t\tpath === null ? invalidArgText(appTheme) : path ? shortenPath(path) : appTheme.fg(\"toolOutput\", \"...\");\n\tlet text = appTheme.fg(\"toolTitle\", appTheme.bold(\"DocRead \")) + appTheme.fg(\"accent\", pathDisplay);\n\tif (args?.readonly) text += appTheme.fg(\"muted\", \" (readonly)\");\n\treturn text;\n}\n\nfunction formatDocReadResult(\n\tresult: { content: Array<{ type: string; text?: string }>; details?: DocReadToolDetails },\n\toptions: ToolRenderResultOptions,\n\tshowImages: boolean,\n): string {\n\tconst output = getTextOutput(result as any, showImages).trim();\n\tif (!output) return \"\";\n\tconst lines = output.split(\"\\n\");\n\tconst maxLines = options.expanded ? lines.length : 15;\n\tconst displayLines = lines.slice(0, maxLines);\n\tconst remaining = lines.length - maxLines;\n\tlet text = `\\n${displayLines.map((line) => appTheme.fg(\"toolOutput\", line)).join(\"\\n\")}`;\n\tif (remaining > 0) {\n\t\ttext += `${appTheme.fg(\"muted\", `\\n... (${remaining} more lines,`)} ${keyHint(\"app.tools.expand\", \"to expand\")})`;\n\t}\n\treturn text;\n}\n\nexport function createDocReadToolDefinition(\n\tcwd: string,\n\toptions?: DocReadToolOptions,\n): ToolDefinition<typeof docReadSchema, DocReadToolDetails | undefined> {\n\treturn {\n\t\tname: \"DocRead\",\n\t\tlabel: \"DocRead\",\n\t\tdescription:\n\t\t\t\"Extract a structured or binary document (XML, drawio, docx/xlsx/pptx, PDF) into editable, id-addressed JSON the agent can patch losslessly. Each node has a stable #id; edit with DocEdit (in place) or DocWrite (to a new path), passing a patch that targets those ids. This is the canonical way to read and edit these formats: never fall back to ad-hoc scripts (python/openpyxl, docx, PyPDF2, unzip, sed) to parse or rewrite them — that loses the lossless id-map and corrupts formatting. Off by default; enabled with --enable-filetools.\",\n\t\tpromptSnippet: \"Extract a structured/binary document into editable, id-addressed JSON\",\n\t\tpromptGuidelines: [\n\t\t\t\"Use DocRead to open structured/binary documents (XML, drawio, docx/xlsx/pptx, PDF) instead of read; it returns id-addressed nodes you can patch with DocEdit/DocWrite. Never fall back to ad-hoc scripts (python/openpyxl, docx, PyPDF2, unzip, sed) to parse or edit these formats — that loses the lossless id-map and corrupts the file.\",\n\t\t\t\"Flow: scan first, edit second. To understand a document, start with DocRead readonly:true — an analysis-only projection that strips node ids and is much cheaper in tokens. Only do a full (writable) DocRead when you actually intend to edit, since that view carries the whole id-map and is token-heavy.\",\n\t\t\t\"Read once, then edit. A writable DocRead establishes the id-map; DocEdit/DocWrite re-extract on their own, so do not re-run DocRead between edits. If a patch targets stale ids the edit fails and returns the current structure with fresh ids to re-issue against.\",\n\t\t],\n\t\tparameters: docReadSchema,\n\t\tasync execute(_toolCallId, { path, readonly }: DocReadToolInput, signal?: AbortSignal) {\n\t\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\t\t\tconst absolutePath = resolveReadPath(path, cwd);\n\t\t\tconst envelope = await extractDocument(absolutePath, cwd, signal, {\n\t\t\t\treadonly,\n\t\t\t\ttimeoutSecs: options?.timeoutSecs,\n\t\t\t});\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\" as const, text: renderEnvelopeText(envelope, readonly) }],\n\t\t\t\tdetails: {\n\t\t\t\t\ttype: envelope.source.type,\n\t\t\t\t\tfidelity: envelope.fidelity,\n\t\t\t\t\twritable: envelope.writable,\n\t\t\t\t\tnodeCount: countNodes(envelope.structure),\n\t\t\t\t},\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(formatDocReadCall(args));\n\t\t\treturn text;\n\t\t},\n\t\trenderResult(result, options, _theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatDocReadResult(result as any, options, context.showImages));\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport function createDocReadTool(cwd: string, options?: DocReadToolOptions): AgentTool<typeof docReadSchema> {\n\treturn wrapToolDefinition(createDocReadToolDefinition(cwd, options));\n}\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AgentTool } from "@kolisachint/hoocode-agent-core";
|
|
2
2
|
import { type Static, Type } from "typebox";
|
|
3
|
-
import type { ToolDefinition } from "
|
|
4
|
-
import { type ScanView } from "
|
|
3
|
+
import type { ToolDefinition } from "../../extensions/types.js";
|
|
4
|
+
import { type ScanView } from "../filetools-shared.js";
|
|
5
5
|
declare const docScanSchema: Type.TObject<{
|
|
6
6
|
path: Type.TString;
|
|
7
7
|
offset: Type.TOptional<Type.TNumber>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docscan.d.ts","sourceRoot":"","sources":["../../../../src/core/tools/doc/docscan.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAEjE,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAG5C,OAAO,KAAK,EAAE,cAAc,EAA2B,MAAM,2BAA2B,CAAC;AACzF,OAAO,EAAE,KAAK,QAAQ,EAA6C,MAAM,wBAAwB,CAAC;AAKlG,QAAA,MAAM,aAAa;;;;EAMjB,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,OAAO,aAAa,CAAC,CAAC;AAE5D,MAAM,WAAW,kBAAkB;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IAClC,sDAAsD;IACtD,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CA2BrD;AAgCD,wBAAgB,2BAA2B,CAC1C,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,kBAAkB,GAC1B,cAAc,CAAC,OAAO,aAAa,EAAE,kBAAkB,GAAG,SAAS,CAAC,CAoCtE;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAC,OAAO,aAAa,CAAC,CAE5G","sourcesContent":["import type { AgentTool } from \"@kolisachint/hoocode-agent-core\";\nimport { Text } from \"@kolisachint/hoocode-tui\";\nimport { type Static, Type } from \"typebox\";\nimport { keyHint } from \"../../../modes/interactive/components/keybinding-hints.js\";\nimport { theme as appTheme } from \"../../../modes/interactive/theme/theme.js\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../../extensions/types.js\";\nimport { type ScanView, scanDocument, truncateRenderToTokenBudget } from \"../filetools-shared.js\";\nimport { resolveReadPath } from \"../path-utils.js\";\nimport { getTextOutput, invalidArgText, shortenPath, str } from \"../render-utils.js\";\nimport { wrapToolDefinition } from \"../tool-definition-wrapper.js\";\n\nconst docScanSchema = Type.Object({\n\tpath: Type.String({\n\t\tdescription: \"Path to the document to scan (relative or absolute). XML, drawio, OOXML (docx/xlsx/pptx), or PDF.\",\n\t}),\n\toffset: Type.Optional(Type.Number({ description: \"Skip the first N blocks (pagination). Default 0.\" })),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of blocks to return. Default: all remaining.\" })),\n});\n\nexport type DocScanToolInput = Static<typeof docScanSchema>;\n\nexport interface DocScanToolDetails {\n\tfileType?: string;\n\treturned?: number;\n\ttotal?: number;\n}\n\nexport interface DocScanToolOptions {\n\t/** Timeout (seconds) for the filetools invocation. */\n\ttimeoutSecs?: number;\n}\n\n/**\n * Render a scan manifest as compact, id-addressed preview lines. Each line\n * carries the block id (feeds DocPeek/DocGrep) plus a section label, token\n * estimate, and a short preview — structure without hydrating full content.\n */\nexport function renderScanView(view: ScanView): string {\n\tconst header =\n\t\t`document ${view.file_type} — ${view.returned}/${view.total} blocks ` +\n\t\t`(offset ${view.offset}, ~${view.total_tokens} tok total)`;\n\tconst lines: string[] = [header, \"\"];\n\tfor (const block of view.blocks) {\n\t\tconst section = block.section_name ? ` ${block.section_name}#${block.section_number}` : \"\";\n\t\tconst preview = block.preview ? ` :: ${JSON.stringify(block.preview)}` : \"\";\n\t\tlines.push(`#${block.id} [${block.block_type}]${section} ~${block.token_estimate}tok${preview}`);\n\t}\n\n\tconst { text, droppedLines } = truncateRenderToTokenBudget(lines);\n\tconst seen = view.offset + view.returned;\n\tconst remaining = view.total - seen;\n\tconst parts = [text];\n\tif (droppedLines > 0) {\n\t\tparts.push(\n\t\t\t`\\n[Truncated: ${droppedLines} more block line${droppedLines === 1 ? \"\" : \"s\"} omitted from this render. ` +\n\t\t\t\t`Re-run with a tighter limit, or DocGrep/DocPeek to target.]`,\n\t\t);\n\t}\n\tif (remaining > 0) {\n\t\tparts.push(\n\t\t\t`\\n[${remaining} more block${remaining === 1 ? \"\" : \"s\"} not shown — re-run with offset:${seen} to continue.]`,\n\t\t);\n\t}\n\treturn parts.join(\"\\n\");\n}\n\nfunction formatDocScanCall(args: { path?: string; offset?: number; limit?: number } | undefined): string {\n\tconst path = str(args?.path);\n\tconst pathDisplay =\n\t\tpath === null ? invalidArgText(appTheme) : path ? shortenPath(path) : appTheme.fg(\"toolOutput\", \"...\");\n\tlet text = appTheme.fg(\"toolTitle\", appTheme.bold(\"DocScan \")) + appTheme.fg(\"accent\", pathDisplay);\n\tconst range: string[] = [];\n\tif (args?.offset !== undefined) range.push(`offset ${args.offset}`);\n\tif (args?.limit !== undefined) range.push(`limit ${args.limit}`);\n\tif (range.length > 0) text += appTheme.fg(\"muted\", ` (${range.join(\", \")})`);\n\treturn text;\n}\n\nfunction formatDocScanResult(\n\tresult: { content: Array<{ type: string; text?: string }> },\n\toptions: ToolRenderResultOptions,\n\tshowImages: boolean,\n): string {\n\tconst output = getTextOutput(result as any, showImages).trim();\n\tif (!output) return \"\";\n\tconst lines = output.split(\"\\n\");\n\tconst maxLines = options.expanded ? lines.length : 15;\n\tconst displayLines = lines.slice(0, maxLines);\n\tconst remaining = lines.length - maxLines;\n\tlet text = `\\n${displayLines.map((line) => appTheme.fg(\"toolOutput\", line)).join(\"\\n\")}`;\n\tif (remaining > 0) {\n\t\ttext += `${appTheme.fg(\"muted\", `\\n... (${remaining} more lines,`)} ${keyHint(\"app.tools.expand\", \"to expand\")})`;\n\t}\n\treturn text;\n}\n\nexport function createDocScanToolDefinition(\n\tcwd: string,\n\toptions?: DocScanToolOptions,\n): ToolDefinition<typeof docScanSchema, DocScanToolDetails | undefined> {\n\treturn {\n\t\tname: \"DocScan\",\n\t\tlabel: \"DocScan\",\n\t\tdescription:\n\t\t\t\"Cheaply outline a structured/binary document (XML, drawio, docx/xlsx/pptx, PDF) without hydrating it: returns a paginated manifest of blocks, each with a structural-path id, type, section label, token estimate, and a short preview. This is the cheap first step of the token-sensitive loop — far smaller than a full DocRead. Pass the path ids it returns to DocPeek to hydrate just those blocks (the hydrated nodes carry the editable el_ #ids for DocEdit). Off by default; enabled with --enable-filetools.\",\n\t\tpromptSnippet: \"Outline a structured/binary document into a cheap, paginated block manifest\",\n\t\tpromptGuidelines: [\n\t\t\t\"Start here for large structured/binary documents instead of a full DocRead: DocScan returns a paginated outline (structural-path block ids + previews) that is much cheaper in tokens. Page through it with offset/limit.\",\n\t\t\t\"Flow: DocScan to see structure → DocPeek the path ids you want (hydrates them and reveals their editable el_ #ids) → DocEdit. Or jump straight in with DocGrep, which finds blocks by text and returns editable el_ #ids directly.\",\n\t\t],\n\t\tparameters: docScanSchema,\n\t\tasync execute(_toolCallId, { path, offset, limit }: DocScanToolInput, signal?: AbortSignal) {\n\t\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\t\t\tconst absolutePath = resolveReadPath(path, cwd);\n\t\t\tconst view = await scanDocument(absolutePath, cwd, signal, {\n\t\t\t\toffset,\n\t\t\t\tlimit,\n\t\t\t\ttimeoutSecs: options?.timeoutSecs,\n\t\t\t});\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\" as const, text: renderScanView(view) }],\n\t\t\t\tdetails: { fileType: view.file_type, returned: view.returned, total: view.total },\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(formatDocScanCall(args));\n\t\t\treturn text;\n\t\t},\n\t\trenderResult(result, options, _theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatDocScanResult(result as any, options, context.showImages));\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport function createDocScanTool(cwd: string, options?: DocScanToolOptions): AgentTool<typeof docScanSchema> {\n\treturn wrapToolDefinition(createDocScanToolDefinition(cwd, options));\n}\n"]}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { Text } from "@kolisachint/hoocode-tui";
|
|
2
2
|
import { Type } from "typebox";
|
|
3
|
-
import { keyHint } from "
|
|
4
|
-
import { theme as appTheme } from "
|
|
5
|
-
import { scanDocument, truncateRenderToTokenBudget } from "
|
|
6
|
-
import { resolveReadPath } from "
|
|
7
|
-
import { getTextOutput, invalidArgText, shortenPath, str } from "
|
|
8
|
-
import { wrapToolDefinition } from "
|
|
3
|
+
import { keyHint } from "../../../modes/interactive/components/keybinding-hints.js";
|
|
4
|
+
import { theme as appTheme } from "../../../modes/interactive/theme/theme.js";
|
|
5
|
+
import { scanDocument, truncateRenderToTokenBudget } from "../filetools-shared.js";
|
|
6
|
+
import { resolveReadPath } from "../path-utils.js";
|
|
7
|
+
import { getTextOutput, invalidArgText, shortenPath, str } from "../render-utils.js";
|
|
8
|
+
import { wrapToolDefinition } from "../tool-definition-wrapper.js";
|
|
9
9
|
const docScanSchema = Type.Object({
|
|
10
10
|
path: Type.String({
|
|
11
11
|
description: "Path to the document to scan (relative or absolute). XML, drawio, OOXML (docx/xlsx/pptx), or PDF.",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docscan.js","sourceRoot":"","sources":["../../../../src/core/tools/doc/docscan.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentTool } from \"@kolisachint/hoocode-agent-core\";\nimport { Text } from \"@kolisachint/hoocode-tui\";\nimport { type Static, Type } from \"typebox\";\nimport { keyHint } from \"../../../modes/interactive/components/keybinding-hints.js\";\nimport { theme as appTheme } from \"../../../modes/interactive/theme/theme.js\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../../extensions/types.js\";\nimport { type ScanView, scanDocument, truncateRenderToTokenBudget } from \"../filetools-shared.js\";\nimport { resolveReadPath } from \"../path-utils.js\";\nimport { getTextOutput, invalidArgText, shortenPath, str } from \"../render-utils.js\";\nimport { wrapToolDefinition } from \"../tool-definition-wrapper.js\";\n\nconst docScanSchema = Type.Object({\n\tpath: Type.String({\n\t\tdescription: \"Path to the document to scan (relative or absolute). XML, drawio, OOXML (docx/xlsx/pptx), or PDF.\",\n\t}),\n\toffset: Type.Optional(Type.Number({ description: \"Skip the first N blocks (pagination). Default 0.\" })),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of blocks to return. Default: all remaining.\" })),\n});\n\nexport type DocScanToolInput = Static<typeof docScanSchema>;\n\nexport interface DocScanToolDetails {\n\tfileType?: string;\n\treturned?: number;\n\ttotal?: number;\n}\n\nexport interface DocScanToolOptions {\n\t/** Timeout (seconds) for the filetools invocation. */\n\ttimeoutSecs?: number;\n}\n\n/**\n * Render a scan manifest as compact, id-addressed preview lines. Each line\n * carries the block id (feeds DocPeek/DocGrep) plus a section label, token\n * estimate, and a short preview — structure without hydrating full content.\n */\nexport function renderScanView(view: ScanView): string {\n\tconst header =\n\t\t`document ${view.file_type} — ${view.returned}/${view.total} blocks ` +\n\t\t`(offset ${view.offset}, ~${view.total_tokens} tok total)`;\n\tconst lines: string[] = [header, \"\"];\n\tfor (const block of view.blocks) {\n\t\tconst section = block.section_name ? ` ${block.section_name}#${block.section_number}` : \"\";\n\t\tconst preview = block.preview ? ` :: ${JSON.stringify(block.preview)}` : \"\";\n\t\tlines.push(`#${block.id} [${block.block_type}]${section} ~${block.token_estimate}tok${preview}`);\n\t}\n\n\tconst { text, droppedLines } = truncateRenderToTokenBudget(lines);\n\tconst seen = view.offset + view.returned;\n\tconst remaining = view.total - seen;\n\tconst parts = [text];\n\tif (droppedLines > 0) {\n\t\tparts.push(\n\t\t\t`\\n[Truncated: ${droppedLines} more block line${droppedLines === 1 ? \"\" : \"s\"} omitted from this render. ` +\n\t\t\t\t`Re-run with a tighter limit, or DocGrep/DocPeek to target.]`,\n\t\t);\n\t}\n\tif (remaining > 0) {\n\t\tparts.push(\n\t\t\t`\\n[${remaining} more block${remaining === 1 ? \"\" : \"s\"} not shown — re-run with offset:${seen} to continue.]`,\n\t\t);\n\t}\n\treturn parts.join(\"\\n\");\n}\n\nfunction formatDocScanCall(args: { path?: string; offset?: number; limit?: number } | undefined): string {\n\tconst path = str(args?.path);\n\tconst pathDisplay =\n\t\tpath === null ? invalidArgText(appTheme) : path ? shortenPath(path) : appTheme.fg(\"toolOutput\", \"...\");\n\tlet text = appTheme.fg(\"toolTitle\", appTheme.bold(\"DocScan \")) + appTheme.fg(\"accent\", pathDisplay);\n\tconst range: string[] = [];\n\tif (args?.offset !== undefined) range.push(`offset ${args.offset}`);\n\tif (args?.limit !== undefined) range.push(`limit ${args.limit}`);\n\tif (range.length > 0) text += appTheme.fg(\"muted\", ` (${range.join(\", \")})`);\n\treturn text;\n}\n\nfunction formatDocScanResult(\n\tresult: { content: Array<{ type: string; text?: string }> },\n\toptions: ToolRenderResultOptions,\n\tshowImages: boolean,\n): string {\n\tconst output = getTextOutput(result as any, showImages).trim();\n\tif (!output) return \"\";\n\tconst lines = output.split(\"\\n\");\n\tconst maxLines = options.expanded ? lines.length : 15;\n\tconst displayLines = lines.slice(0, maxLines);\n\tconst remaining = lines.length - maxLines;\n\tlet text = `\\n${displayLines.map((line) => appTheme.fg(\"toolOutput\", line)).join(\"\\n\")}`;\n\tif (remaining > 0) {\n\t\ttext += `${appTheme.fg(\"muted\", `\\n... (${remaining} more lines,`)} ${keyHint(\"app.tools.expand\", \"to expand\")})`;\n\t}\n\treturn text;\n}\n\nexport function createDocScanToolDefinition(\n\tcwd: string,\n\toptions?: DocScanToolOptions,\n): ToolDefinition<typeof docScanSchema, DocScanToolDetails | undefined> {\n\treturn {\n\t\tname: \"DocScan\",\n\t\tlabel: \"DocScan\",\n\t\tdescription:\n\t\t\t\"Cheaply outline a structured/binary document (XML, drawio, docx/xlsx/pptx, PDF) without hydrating it: returns a paginated manifest of blocks, each with a structural-path id, type, section label, token estimate, and a short preview. This is the cheap first step of the token-sensitive loop — far smaller than a full DocRead. Pass the path ids it returns to DocPeek to hydrate just those blocks (the hydrated nodes carry the editable el_ #ids for DocEdit). Off by default; enabled with --enable-filetools.\",\n\t\tpromptSnippet: \"Outline a structured/binary document into a cheap, paginated block manifest\",\n\t\tpromptGuidelines: [\n\t\t\t\"Start here for large structured/binary documents instead of a full DocRead: DocScan returns a paginated outline (structural-path block ids + previews) that is much cheaper in tokens. Page through it with offset/limit.\",\n\t\t\t\"Flow: DocScan to see structure → DocPeek the path ids you want (hydrates them and reveals their editable el_ #ids) → DocEdit. Or jump straight in with DocGrep, which finds blocks by text and returns editable el_ #ids directly.\",\n\t\t],\n\t\tparameters: docScanSchema,\n\t\tasync execute(_toolCallId, { path, offset, limit }: DocScanToolInput, signal?: AbortSignal) {\n\t\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\t\t\tconst absolutePath = resolveReadPath(path, cwd);\n\t\t\tconst view = await scanDocument(absolutePath, cwd, signal, {\n\t\t\t\toffset,\n\t\t\t\tlimit,\n\t\t\t\ttimeoutSecs: options?.timeoutSecs,\n\t\t\t});\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\" as const, text: renderScanView(view) }],\n\t\t\t\tdetails: { fileType: view.file_type, returned: view.returned, total: view.total },\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(formatDocScanCall(args));\n\t\t\treturn text;\n\t\t},\n\t\trenderResult(result, options, _theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatDocScanResult(result as any, options, context.showImages));\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport function createDocScanTool(cwd: string, options?: DocScanToolOptions): AgentTool<typeof docScanSchema> {\n\treturn wrapToolDefinition(createDocScanToolDefinition(cwd, options));\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AgentTool } from "@kolisachint/hoocode-agent-core";
|
|
2
2
|
import { type Static, Type } from "typebox";
|
|
3
|
-
import type { ToolDefinition } from "
|
|
3
|
+
import type { ToolDefinition } from "../../extensions/types.js";
|
|
4
4
|
declare const docWriteSchema: Type.TObject<{
|
|
5
5
|
path: Type.TString;
|
|
6
6
|
out: Type.TString;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docwrite.d.ts","sourceRoot":"","sources":["../../../../src/core/tools/doc/docwrite.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAEjE,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAE5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAQhE,QAAA,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;EAQlB,CAAC;AAEH,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,OAAO,cAAc,CAAC,CAAC;AAE9D,MAAM,WAAW,mBAAmB;IACnC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,mBAAmB;IACnC,sDAAsD;IACtD,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAmBD,wBAAgB,4BAA4B,CAC3C,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,mBAAmB,GAC3B,cAAc,CAAC,OAAO,cAAc,EAAE,mBAAmB,GAAG,SAAS,CAAC,CA+DxE;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,SAAS,CAAC,OAAO,cAAc,CAAC,CAE/G","sourcesContent":["import { mkdir as fsMkdir } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport type { AgentTool } from \"@kolisachint/hoocode-agent-core\";\nimport { Container, Text } from \"@kolisachint/hoocode-tui\";\nimport { type Static, Type } from \"typebox\";\nimport { theme as appTheme } from \"../../../modes/interactive/theme/theme.js\";\nimport type { ToolDefinition } from \"../../extensions/types.js\";\nimport { withFileMutationQueue } from \"../file-mutation-queue.js\";\nimport { patchOpsSchema, reconstructDocument, StalePatchError, toPatch } from \"../filetools-shared.js\";\nimport { resolveReadPath, resolveToCwd } from \"../path-utils.js\";\nimport { invalidArgText, shortenPath, str } from \"../render-utils.js\";\nimport { wrapToolDefinition } from \"../tool-definition-wrapper.js\";\nimport { renderEnvelopeText } from \"./docread.js\";\n\nconst docWriteSchema = Type.Object({\n\tpath: Type.String({\n\t\tdescription: \"Path to the SOURCE document. Must have been opened with DocRead first. Left untouched.\",\n\t}),\n\tout: Type.String({\n\t\tdescription: \"Output path for the reconstructed document (relative or absolute). Created/overwritten.\",\n\t}),\n\tpatch: patchOpsSchema,\n});\n\nexport type DocWriteToolInput = Static<typeof docWriteSchema>;\n\nexport interface DocWriteToolDetails {\n\tops?: number;\n\tout?: string;\n}\n\nexport interface DocWriteToolOptions {\n\t/** Timeout (seconds) for the filetools invocation. */\n\ttimeoutSecs?: number;\n}\n\nfunction formatDocWriteCall(args: { path?: string; out?: string; patch?: unknown[] } | undefined): string {\n\tconst path = str(args?.path);\n\tconst out = str(args?.out);\n\tconst pathDisplay =\n\t\tpath === null ? invalidArgText(appTheme) : path ? shortenPath(path) : appTheme.fg(\"toolOutput\", \"...\");\n\tconst outDisplay =\n\t\tout === null ? invalidArgText(appTheme) : out ? shortenPath(out) : appTheme.fg(\"toolOutput\", \"...\");\n\tlet text =\n\t\tappTheme.fg(\"toolTitle\", appTheme.bold(\"DocWrite \")) +\n\t\tappTheme.fg(\"accent\", pathDisplay) +\n\t\tappTheme.fg(\"muted\", \" -> \") +\n\t\tappTheme.fg(\"accent\", outDisplay);\n\tconst ops = Array.isArray(args?.patch) ? args.patch.length : undefined;\n\tif (ops !== undefined) text += appTheme.fg(\"muted\", ` (${ops} op${ops === 1 ? \"\" : \"s\"})`);\n\treturn text;\n}\n\nexport function createDocWriteToolDefinition(\n\tcwd: string,\n\toptions?: DocWriteToolOptions,\n): ToolDefinition<typeof docWriteSchema, DocWriteToolDetails | undefined> {\n\treturn {\n\t\tname: \"DocWrite\",\n\t\tlabel: \"DocWrite\",\n\t\tdescription:\n\t\t\t\"Apply an id-based patch to a structured/binary document and write the result to a NEW path, leaving the source untouched (save-as). Targets node ids from a DocRead extract of the source; if the cache is missing or the source changed on disk (e.g. an external script rewrote it) it is re-extracted automatically, so a separate DocRead is not required. If the patch references ids that no longer exist, the call fails and returns the current structure with fresh ids so you can re-issue. This is the canonical way to rewrite these formats: never fall back to ad-hoc scripts (python/openpyxl, docx, PyPDF2, unzip, sed) to produce the output — that bypasses the lossless id-map and corrupts the file. Off by default; enabled with --enable-filetools.\",\n\t\tpromptSnippet: \"Reconstruct a patched structured/binary document to a new path\",\n\t\tpromptGuidelines: [\n\t\t\t\"Use DocWrite to save an edited document to a different file: pass the source path (opened with DocRead), an `out` path, and an id-based patch. The source is left unchanged.\",\n\t\t\t\"Keep the patch minimal and scan before you write: prefer a DocRead readonly:true glimpse to plan the change, and avoid re-running a full writable DocRead between writes — it auto-extracts the source and is token-heavy.\",\n\t\t],\n\t\tparameters: docWriteSchema,\n\t\tasync execute(_toolCallId, { path, out, patch }: DocWriteToolInput, signal?: AbortSignal) {\n\t\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\t\t\tconst absolutePath = resolveReadPath(path, cwd);\n\t\t\tconst outPath = resolveToCwd(out, cwd);\n\n\t\t\treturn withFileMutationQueue(outPath, async () => {\n\t\t\t\tawait fsMkdir(dirname(outPath), { recursive: true });\n\t\t\t\ttry {\n\t\t\t\t\tawait reconstructDocument(absolutePath, toPatch(patch), outPath, cwd, signal, {\n\t\t\t\t\t\ttimeoutSecs: options?.timeoutSecs,\n\t\t\t\t\t});\n\t\t\t\t} catch (err) {\n\t\t\t\t\tif (err instanceof StalePatchError) {\n\t\t\t\t\t\t// The source was rewritten out-of-band: the cache auto-refreshed\n\t\t\t\t\t\t// but the patch targets ids that no longer exist. Surface the\n\t\t\t\t\t\t// current structure so the agent can re-issue without a DocRead.\n\t\t\t\t\t\tthrow new Error(`${err.message}\\n\\n${renderEnvelopeText(err.envelope, false)}`);\n\t\t\t\t\t}\n\t\t\t\t\tthrow err;\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\" as const,\n\t\t\t\t\t\t\ttext: `Wrote ${patch.length} op${patch.length === 1 ? \"\" : \"s\"} from ${path} to ${out}.`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tdetails: { ops: patch.length, out },\n\t\t\t\t};\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(formatDocWriteCall(args));\n\t\t\treturn text;\n\t\t},\n\t\trenderResult(result, _options, _theme, context) {\n\t\t\tif (!context.isError) {\n\t\t\t\tconst component = (context.lastComponent as Container | undefined) ?? new Container();\n\t\t\t\tcomponent.clear();\n\t\t\t\treturn component;\n\t\t\t}\n\t\t\tconst output = (result.content as Array<{ type: string; text?: string }>)\n\t\t\t\t.filter((c) => c.type === \"text\")\n\t\t\t\t.map((c) => c.text || \"\")\n\t\t\t\t.join(\"\\n\");\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(output ? `\\n${appTheme.fg(\"error\", output)}` : \"\");\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport function createDocWriteTool(cwd: string, options?: DocWriteToolOptions): AgentTool<typeof docWriteSchema> {\n\treturn wrapToolDefinition(createDocWriteToolDefinition(cwd, options));\n}\n"]}
|
|
@@ -2,13 +2,13 @@ import { mkdir as fsMkdir } from "node:fs/promises";
|
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
3
|
import { Container, Text } from "@kolisachint/hoocode-tui";
|
|
4
4
|
import { Type } from "typebox";
|
|
5
|
-
import { theme as appTheme } from "
|
|
5
|
+
import { theme as appTheme } from "../../../modes/interactive/theme/theme.js";
|
|
6
|
+
import { withFileMutationQueue } from "../file-mutation-queue.js";
|
|
7
|
+
import { patchOpsSchema, reconstructDocument, StalePatchError, toPatch } from "../filetools-shared.js";
|
|
8
|
+
import { resolveReadPath, resolveToCwd } from "../path-utils.js";
|
|
9
|
+
import { invalidArgText, shortenPath, str } from "../render-utils.js";
|
|
10
|
+
import { wrapToolDefinition } from "../tool-definition-wrapper.js";
|
|
6
11
|
import { renderEnvelopeText } from "./docread.js";
|
|
7
|
-
import { withFileMutationQueue } from "./file-mutation-queue.js";
|
|
8
|
-
import { patchOpsSchema, reconstructDocument, StalePatchError, toPatch } from "./filetools-shared.js";
|
|
9
|
-
import { resolveReadPath, resolveToCwd } from "./path-utils.js";
|
|
10
|
-
import { invalidArgText, shortenPath, str } from "./render-utils.js";
|
|
11
|
-
import { wrapToolDefinition } from "./tool-definition-wrapper.js";
|
|
12
12
|
const docWriteSchema = Type.Object({
|
|
13
13
|
path: Type.String({
|
|
14
14
|
description: "Path to the SOURCE document. Must have been opened with DocRead first. Left untouched.",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docwrite.js","sourceRoot":"","sources":["../../../../src/core/tools/doc/docwrite.ts"],"names":[],"mappings":"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","sourcesContent":["import { mkdir as fsMkdir } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport type { AgentTool } from \"@kolisachint/hoocode-agent-core\";\nimport { Container, Text } from \"@kolisachint/hoocode-tui\";\nimport { type Static, Type } from \"typebox\";\nimport { theme as appTheme } from \"../../../modes/interactive/theme/theme.js\";\nimport type { ToolDefinition } from \"../../extensions/types.js\";\nimport { withFileMutationQueue } from \"../file-mutation-queue.js\";\nimport { patchOpsSchema, reconstructDocument, StalePatchError, toPatch } from \"../filetools-shared.js\";\nimport { resolveReadPath, resolveToCwd } from \"../path-utils.js\";\nimport { invalidArgText, shortenPath, str } from \"../render-utils.js\";\nimport { wrapToolDefinition } from \"../tool-definition-wrapper.js\";\nimport { renderEnvelopeText } from \"./docread.js\";\n\nconst docWriteSchema = Type.Object({\n\tpath: Type.String({\n\t\tdescription: \"Path to the SOURCE document. Must have been opened with DocRead first. Left untouched.\",\n\t}),\n\tout: Type.String({\n\t\tdescription: \"Output path for the reconstructed document (relative or absolute). Created/overwritten.\",\n\t}),\n\tpatch: patchOpsSchema,\n});\n\nexport type DocWriteToolInput = Static<typeof docWriteSchema>;\n\nexport interface DocWriteToolDetails {\n\tops?: number;\n\tout?: string;\n}\n\nexport interface DocWriteToolOptions {\n\t/** Timeout (seconds) for the filetools invocation. */\n\ttimeoutSecs?: number;\n}\n\nfunction formatDocWriteCall(args: { path?: string; out?: string; patch?: unknown[] } | undefined): string {\n\tconst path = str(args?.path);\n\tconst out = str(args?.out);\n\tconst pathDisplay =\n\t\tpath === null ? invalidArgText(appTheme) : path ? shortenPath(path) : appTheme.fg(\"toolOutput\", \"...\");\n\tconst outDisplay =\n\t\tout === null ? invalidArgText(appTheme) : out ? shortenPath(out) : appTheme.fg(\"toolOutput\", \"...\");\n\tlet text =\n\t\tappTheme.fg(\"toolTitle\", appTheme.bold(\"DocWrite \")) +\n\t\tappTheme.fg(\"accent\", pathDisplay) +\n\t\tappTheme.fg(\"muted\", \" -> \") +\n\t\tappTheme.fg(\"accent\", outDisplay);\n\tconst ops = Array.isArray(args?.patch) ? args.patch.length : undefined;\n\tif (ops !== undefined) text += appTheme.fg(\"muted\", ` (${ops} op${ops === 1 ? \"\" : \"s\"})`);\n\treturn text;\n}\n\nexport function createDocWriteToolDefinition(\n\tcwd: string,\n\toptions?: DocWriteToolOptions,\n): ToolDefinition<typeof docWriteSchema, DocWriteToolDetails | undefined> {\n\treturn {\n\t\tname: \"DocWrite\",\n\t\tlabel: \"DocWrite\",\n\t\tdescription:\n\t\t\t\"Apply an id-based patch to a structured/binary document and write the result to a NEW path, leaving the source untouched (save-as). Targets node ids from a DocRead extract of the source; if the cache is missing or the source changed on disk (e.g. an external script rewrote it) it is re-extracted automatically, so a separate DocRead is not required. If the patch references ids that no longer exist, the call fails and returns the current structure with fresh ids so you can re-issue. This is the canonical way to rewrite these formats: never fall back to ad-hoc scripts (python/openpyxl, docx, PyPDF2, unzip, sed) to produce the output — that bypasses the lossless id-map and corrupts the file. Off by default; enabled with --enable-filetools.\",\n\t\tpromptSnippet: \"Reconstruct a patched structured/binary document to a new path\",\n\t\tpromptGuidelines: [\n\t\t\t\"Use DocWrite to save an edited document to a different file: pass the source path (opened with DocRead), an `out` path, and an id-based patch. The source is left unchanged.\",\n\t\t\t\"Keep the patch minimal and scan before you write: prefer a DocRead readonly:true glimpse to plan the change, and avoid re-running a full writable DocRead between writes — it auto-extracts the source and is token-heavy.\",\n\t\t],\n\t\tparameters: docWriteSchema,\n\t\tasync execute(_toolCallId, { path, out, patch }: DocWriteToolInput, signal?: AbortSignal) {\n\t\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\t\t\tconst absolutePath = resolveReadPath(path, cwd);\n\t\t\tconst outPath = resolveToCwd(out, cwd);\n\n\t\t\treturn withFileMutationQueue(outPath, async () => {\n\t\t\t\tawait fsMkdir(dirname(outPath), { recursive: true });\n\t\t\t\ttry {\n\t\t\t\t\tawait reconstructDocument(absolutePath, toPatch(patch), outPath, cwd, signal, {\n\t\t\t\t\t\ttimeoutSecs: options?.timeoutSecs,\n\t\t\t\t\t});\n\t\t\t\t} catch (err) {\n\t\t\t\t\tif (err instanceof StalePatchError) {\n\t\t\t\t\t\t// The source was rewritten out-of-band: the cache auto-refreshed\n\t\t\t\t\t\t// but the patch targets ids that no longer exist. Surface the\n\t\t\t\t\t\t// current structure so the agent can re-issue without a DocRead.\n\t\t\t\t\t\tthrow new Error(`${err.message}\\n\\n${renderEnvelopeText(err.envelope, false)}`);\n\t\t\t\t\t}\n\t\t\t\t\tthrow err;\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\" as const,\n\t\t\t\t\t\t\ttext: `Wrote ${patch.length} op${patch.length === 1 ? \"\" : \"s\"} from ${path} to ${out}.`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tdetails: { ops: patch.length, out },\n\t\t\t\t};\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(formatDocWriteCall(args));\n\t\t\treturn text;\n\t\t},\n\t\trenderResult(result, _options, _theme, context) {\n\t\t\tif (!context.isError) {\n\t\t\t\tconst component = (context.lastComponent as Container | undefined) ?? new Container();\n\t\t\t\tcomponent.clear();\n\t\t\t\treturn component;\n\t\t\t}\n\t\t\tconst output = (result.content as Array<{ type: string; text?: string }>)\n\t\t\t\t.filter((c) => c.type === \"text\")\n\t\t\t\t.map((c) => c.text || \"\")\n\t\t\t\t.join(\"\\n\");\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(output ? `\\n${appTheme.fg(\"error\", output)}` : \"\");\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport function createDocWriteTool(cwd: string, options?: DocWriteToolOptions): AgentTool<typeof docWriteSchema> {\n\treturn wrapToolDefinition(createDocWriteToolDefinition(cwd, options));\n}\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured document tools (DocRead/DocEdit/DocWrite/DocScan/DocGrep/DocPeek),
|
|
3
|
+
* backed by the `filetools` document engine. See docs/doc-tools-flow.md.
|
|
4
|
+
*/
|
|
5
|
+
export { createDocEditTool, createDocEditToolDefinition, type DocEditToolDetails, type DocEditToolInput, type DocEditToolOptions, } from "./docedit.js";
|
|
6
|
+
export { createDocGrepTool, createDocGrepToolDefinition, type DocGrepToolDetails, type DocGrepToolInput, type DocGrepToolOptions, } from "./docgrep.js";
|
|
7
|
+
export { createDocPeekTool, createDocPeekToolDefinition, type DocPeekToolDetails, type DocPeekToolInput, type DocPeekToolOptions, } from "./docpeek.js";
|
|
8
|
+
export { createDocReadTool, createDocReadToolDefinition, type DocReadToolDetails, type DocReadToolInput, type DocReadToolOptions, } from "./docread.js";
|
|
9
|
+
export { createDocScanTool, createDocScanToolDefinition, type DocScanToolDetails, type DocScanToolInput, type DocScanToolOptions, } from "./docscan.js";
|
|
10
|
+
export { createDocWriteTool, createDocWriteToolDefinition, type DocWriteToolDetails, type DocWriteToolInput, type DocWriteToolOptions, } from "./docwrite.js";
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/core/tools/doc/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACN,iBAAiB,EACjB,2BAA2B,EAC3B,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,GACvB,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,iBAAiB,EACjB,2BAA2B,EAC3B,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,GACvB,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,iBAAiB,EACjB,2BAA2B,EAC3B,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,GACvB,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,iBAAiB,EACjB,2BAA2B,EAC3B,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,GACvB,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,iBAAiB,EACjB,2BAA2B,EAC3B,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,GACvB,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,kBAAkB,EAClB,4BAA4B,EAC5B,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,GACxB,MAAM,eAAe,CAAC","sourcesContent":["/**\n * Structured document tools (DocRead/DocEdit/DocWrite/DocScan/DocGrep/DocPeek),\n * backed by the `filetools` document engine. See docs/doc-tools-flow.md.\n */\n\nexport {\n\tcreateDocEditTool,\n\tcreateDocEditToolDefinition,\n\ttype DocEditToolDetails,\n\ttype DocEditToolInput,\n\ttype DocEditToolOptions,\n} from \"./docedit.js\";\nexport {\n\tcreateDocGrepTool,\n\tcreateDocGrepToolDefinition,\n\ttype DocGrepToolDetails,\n\ttype DocGrepToolInput,\n\ttype DocGrepToolOptions,\n} from \"./docgrep.js\";\nexport {\n\tcreateDocPeekTool,\n\tcreateDocPeekToolDefinition,\n\ttype DocPeekToolDetails,\n\ttype DocPeekToolInput,\n\ttype DocPeekToolOptions,\n} from \"./docpeek.js\";\nexport {\n\tcreateDocReadTool,\n\tcreateDocReadToolDefinition,\n\ttype DocReadToolDetails,\n\ttype DocReadToolInput,\n\ttype DocReadToolOptions,\n} from \"./docread.js\";\nexport {\n\tcreateDocScanTool,\n\tcreateDocScanToolDefinition,\n\ttype DocScanToolDetails,\n\ttype DocScanToolInput,\n\ttype DocScanToolOptions,\n} from \"./docscan.js\";\nexport {\n\tcreateDocWriteTool,\n\tcreateDocWriteToolDefinition,\n\ttype DocWriteToolDetails,\n\ttype DocWriteToolInput,\n\ttype DocWriteToolOptions,\n} from \"./docwrite.js\";\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured document tools (DocRead/DocEdit/DocWrite/DocScan/DocGrep/DocPeek),
|
|
3
|
+
* backed by the `filetools` document engine. See docs/doc-tools-flow.md.
|
|
4
|
+
*/
|
|
5
|
+
export { createDocEditTool, createDocEditToolDefinition, } from "./docedit.js";
|
|
6
|
+
export { createDocGrepTool, createDocGrepToolDefinition, } from "./docgrep.js";
|
|
7
|
+
export { createDocPeekTool, createDocPeekToolDefinition, } from "./docpeek.js";
|
|
8
|
+
export { createDocReadTool, createDocReadToolDefinition, } from "./docread.js";
|
|
9
|
+
export { createDocScanTool, createDocScanToolDefinition, } from "./docscan.js";
|
|
10
|
+
export { createDocWriteTool, createDocWriteToolDefinition, } from "./docwrite.js";
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/core/tools/doc/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACN,iBAAiB,EACjB,2BAA2B,GAI3B,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,iBAAiB,EACjB,2BAA2B,GAI3B,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,iBAAiB,EACjB,2BAA2B,GAI3B,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,iBAAiB,EACjB,2BAA2B,GAI3B,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,iBAAiB,EACjB,2BAA2B,GAI3B,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,kBAAkB,EAClB,4BAA4B,GAI5B,MAAM,eAAe,CAAC","sourcesContent":["/**\n * Structured document tools (DocRead/DocEdit/DocWrite/DocScan/DocGrep/DocPeek),\n * backed by the `filetools` document engine. See docs/doc-tools-flow.md.\n */\n\nexport {\n\tcreateDocEditTool,\n\tcreateDocEditToolDefinition,\n\ttype DocEditToolDetails,\n\ttype DocEditToolInput,\n\ttype DocEditToolOptions,\n} from \"./docedit.js\";\nexport {\n\tcreateDocGrepTool,\n\tcreateDocGrepToolDefinition,\n\ttype DocGrepToolDetails,\n\ttype DocGrepToolInput,\n\ttype DocGrepToolOptions,\n} from \"./docgrep.js\";\nexport {\n\tcreateDocPeekTool,\n\tcreateDocPeekToolDefinition,\n\ttype DocPeekToolDetails,\n\ttype DocPeekToolInput,\n\ttype DocPeekToolOptions,\n} from \"./docpeek.js\";\nexport {\n\tcreateDocReadTool,\n\tcreateDocReadToolDefinition,\n\ttype DocReadToolDetails,\n\ttype DocReadToolInput,\n\ttype DocReadToolOptions,\n} from \"./docread.js\";\nexport {\n\tcreateDocScanTool,\n\tcreateDocScanToolDefinition,\n\ttype DocScanToolDetails,\n\ttype DocScanToolInput,\n\ttype DocScanToolOptions,\n} from \"./docscan.js\";\nexport {\n\tcreateDocWriteTool,\n\tcreateDocWriteToolDefinition,\n\ttype DocWriteToolDetails,\n\ttype DocWriteToolInput,\n\ttype DocWriteToolOptions,\n} from \"./docwrite.js\";\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grep.d.ts","sourceRoot":"","sources":["../../../src/core/tools/grep.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAKjE,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAG5C,OAAO,KAAK,EAAE,cAAc,EAA2B,MAAM,wBAAwB,CAAC;AAKtF,OAAO,EAIN,KAAK,gBAAgB,EAGrB,MAAM,eAAe,CAAC;AAEvB,QAAA,MAAM,UAAU;;;;;;;;EAYd,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,UAAU,CAAC,CAAC;AAGtD,MAAM,WAAW,eAAe;IAC/B,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,cAAc,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC9B,mEAAmE;IACnE,WAAW,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IAClE,2CAA2C;IAC3C,QAAQ,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;CAC7D;AAOD,MAAM,WAAW,eAAe;IAC/B,yEAAyE;IACzE,UAAU,CAAC,EAAE,cAAc,CAAC;CAC5B;AAyDD,wBAAgB,wBAAwB,CACvC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,eAAe,GACvB,cAAc,CAAC,OAAO,UAAU,EAAE,eAAe,GAAG,SAAS,CAAC,CA0RhE;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC,OAAO,UAAU,CAAC,CAEnG","sourcesContent":["import { createInterface } from \"node:readline\";\nimport type { AgentTool } from \"@kolisachint/hoocode-agent-core\";\nimport { Text } from \"@kolisachint/hoocode-tui\";\nimport { spawn } from \"child_process\";\nimport { readFileSync, statSync } from \"fs\";\nimport path from \"path\";\nimport { type Static, Type } from \"typebox\";\nimport { keyHint } from \"../../modes/interactive/components/keybinding-hints.js\";\nimport { ensureTool } from \"../../utils/tools-manager.js\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../extensions/types.js\";\nimport { compressGrepOutput } from \"./output-compression.js\";\nimport { resolveToCwd } from \"./path-utils.js\";\nimport { getTextOutput, invalidArgText, shortenPath, str } from \"./render-utils.js\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.js\";\nimport {\n\tDEFAULT_MAX_BYTES,\n\tformatSize,\n\tGREP_MAX_LINE_LENGTH,\n\ttype TruncationResult,\n\ttruncateHead,\n\ttruncateLine,\n} from \"./truncate.js\";\n\nconst grepSchema = Type.Object({\n\tpattern: Type.String({ description: \"Search pattern (regex or literal string)\" }),\n\tpath: Type.Optional(Type.String({ description: \"Directory or file to search (default: current directory)\" })),\n\tglob: Type.Optional(Type.String({ description: \"Filter files by glob pattern, e.g. '*.ts' or '**/*.spec.ts'\" })),\n\tignoreCase: Type.Optional(Type.Boolean({ description: \"Case-insensitive search (default: false)\" })),\n\tliteral: Type.Optional(\n\t\tType.Boolean({ description: \"Treat pattern as literal string instead of regex (default: false)\" }),\n\t),\n\tcontext: Type.Optional(\n\t\tType.Number({ description: \"Number of lines to show before and after each match (default: 0)\" }),\n\t),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of matches to return (default: 100)\" })),\n});\n\nexport type GrepToolInput = Static<typeof grepSchema>;\nconst DEFAULT_LIMIT = 100;\n\nexport interface GrepToolDetails {\n\ttruncation?: TruncationResult;\n\tmatchLimitReached?: number;\n\tlinesTruncated?: boolean;\n}\n\n/**\n * Pluggable operations for the grep tool.\n * Override these to delegate search to remote systems (for example SSH).\n */\nexport interface GrepOperations {\n\t/** Check if path is a directory. Throws if path does not exist. */\n\tisDirectory: (absolutePath: string) => Promise<boolean> | boolean;\n\t/** Read file contents for context lines */\n\treadFile: (absolutePath: string) => Promise<string> | string;\n}\n\nconst defaultGrepOperations: GrepOperations = {\n\tisDirectory: (p) => statSync(p).isDirectory(),\n\treadFile: (p) => readFileSync(p, \"utf-8\"),\n};\n\nexport interface GrepToolOptions {\n\t/** Custom operations for grep. Default: local filesystem plus ripgrep */\n\toperations?: GrepOperations;\n}\n\nfunction formatGrepCall(\n\targs: { pattern: string; path?: string; glob?: string; limit?: number } | undefined,\n\ttheme: typeof import(\"../../modes/interactive/theme/theme.js\").theme,\n): string {\n\tconst pattern = str(args?.pattern);\n\tconst rawPath = str(args?.path);\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(\"grep\")) +\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\nfunction formatGrepResult(\n\tresult: {\n\t\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\t\tdetails?: GrepToolDetails;\n\t},\n\toptions: ToolRenderResultOptions,\n\ttheme: typeof import(\"../../modes/interactive/theme/theme.js\").theme,\n\tshowImages: boolean,\n): string {\n\tconst output = getTextOutput(result, showImages).trim();\n\tlet text = \"\";\n\tif (output) {\n\t\tconst lines = output.split(\"\\n\");\n\t\tconst maxLines = options.expanded ? lines.length : 15;\n\t\tconst displayLines = lines.slice(0, maxLines);\n\t\tconst remaining = lines.length - maxLines;\n\t\ttext += `\\n${displayLines.map((line) => theme.fg(\"toolOutput\", line)).join(\"\\n\")}`;\n\t\tif (remaining > 0) {\n\t\t\ttext += `${theme.fg(\"muted\", `\\n... (${remaining} more lines,`)} ${keyHint(\"app.tools.expand\", \"to expand\")})`;\n\t\t}\n\t}\n\n\tconst matchLimit = result.details?.matchLimitReached;\n\tconst truncation = result.details?.truncation;\n\tconst linesTruncated = result.details?.linesTruncated;\n\tif (matchLimit || truncation?.truncated || linesTruncated) {\n\t\tconst warnings: string[] = [];\n\t\tif (matchLimit) warnings.push(`${matchLimit} matches limit`);\n\t\tif (truncation?.truncated) warnings.push(`${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit`);\n\t\tif (linesTruncated) warnings.push(\"some lines truncated\");\n\t\ttext += `\\n${theme.fg(\"warning\", `[Truncated: ${warnings.join(\", \")}]`)}`;\n\t}\n\treturn text;\n}\n\nexport function createGrepToolDefinition(\n\tcwd: string,\n\toptions?: GrepToolOptions,\n): ToolDefinition<typeof grepSchema, GrepToolDetails | undefined> {\n\tconst customOps = options?.operations;\n\treturn {\n\t\tname: \"grep\",\n\t\tlabel: \"grep\",\n\t\tdescription: `Search file contents for a pattern. Returns matching lines with file paths and line numbers. Respects .gitignore. Output is truncated to ${DEFAULT_LIMIT} matches or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). Long lines are truncated to ${GREP_MAX_LINE_LENGTH} chars.`,\n\t\tpromptSnippet: \"Search file contents for patterns (respects .gitignore)\",\n\t\tparameters: grepSchema,\n\t\tasync execute(\n\t\t\t_toolCallId,\n\t\t\t{\n\t\t\t\tpattern,\n\t\t\t\tpath: searchDir,\n\t\t\t\tglob,\n\t\t\t\tignoreCase,\n\t\t\t\tliteral,\n\t\t\t\tcontext,\n\t\t\t\tlimit,\n\t\t\t}: {\n\t\t\t\tpattern: string;\n\t\t\t\tpath?: string;\n\t\t\t\tglob?: string;\n\t\t\t\tignoreCase?: boolean;\n\t\t\t\tliteral?: boolean;\n\t\t\t\tcontext?: number;\n\t\t\t\tlimit?: number;\n\t\t\t},\n\t\t\tsignal?: AbortSignal,\n\t\t\t_onUpdate?,\n\t\t\t_ctx?,\n\t\t) {\n\t\t\treturn new Promise((resolve, reject) => {\n\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlet settled = false;\n\t\t\t\tconst settle = (fn: () => void) => {\n\t\t\t\t\tif (!settled) {\n\t\t\t\t\t\tsettled = true;\n\t\t\t\t\t\tfn();\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\t(async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst rgPath = await ensureTool(\"rg\", true);\n\t\t\t\t\t\tif (!rgPath) {\n\t\t\t\t\t\t\tsettle(() =>\n\t\t\t\t\t\t\t\treject(\n\t\t\t\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t\t\t\t\"ripgrep (rg) unavailable and could not be downloaded — use the bash tool to run grep/rg instead\",\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst searchPath = resolveToCwd(searchDir || \".\", cwd);\n\t\t\t\t\t\tconst ops = customOps ?? defaultGrepOperations;\n\t\t\t\t\t\tlet isDirectory: boolean;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tisDirectory = await ops.isDirectory(searchPath);\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\tsettle(() => reject(new Error(`Path not found: ${searchPath}`)));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst contextValue = context && context > 0 ? context : 0;\n\t\t\t\t\t\tconst effectiveLimit = Math.max(1, limit ?? DEFAULT_LIMIT);\n\t\t\t\t\t\tconst formatPath = (filePath: string): string => {\n\t\t\t\t\t\t\tif (isDirectory) {\n\t\t\t\t\t\t\t\tconst relative = path.relative(searchPath, filePath);\n\t\t\t\t\t\t\t\tif (relative && !relative.startsWith(\"..\")) {\n\t\t\t\t\t\t\t\t\treturn relative.replace(/\\\\/g, \"/\");\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn path.basename(filePath);\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tconst fileCache = new Map<string, string[]>();\n\t\t\t\t\t\tconst getFileLines = async (filePath: string): Promise<string[]> => {\n\t\t\t\t\t\t\tlet lines = fileCache.get(filePath);\n\t\t\t\t\t\t\tif (!lines) {\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tconst content = await ops.readFile(filePath);\n\t\t\t\t\t\t\t\t\tlines = content.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\").split(\"\\n\");\n\t\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t\tlines = [];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tfileCache.set(filePath, lines);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn lines;\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tconst args: string[] = [\"--json\", \"--line-number\", \"--color=never\", \"--hidden\"];\n\t\t\t\t\t\tif (ignoreCase) args.push(\"--ignore-case\");\n\t\t\t\t\t\tif (literal) args.push(\"--fixed-strings\");\n\t\t\t\t\t\tif (glob) args.push(\"--glob\", glob);\n\t\t\t\t\t\targs.push(\"--\", pattern, searchPath);\n\n\t\t\t\t\t\tconst child = spawn(rgPath, args, { stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n\t\t\t\t\t\tconst rl = createInterface({ input: child.stdout });\n\t\t\t\t\t\tlet stderr = \"\";\n\t\t\t\t\t\tlet matchCount = 0;\n\t\t\t\t\t\tlet matchLimitReached = false;\n\t\t\t\t\t\tlet linesTruncated = false;\n\t\t\t\t\t\tlet aborted = false;\n\t\t\t\t\t\tlet killedDueToLimit = false;\n\t\t\t\t\t\tconst outputLines: string[] = [];\n\n\t\t\t\t\t\tconst cleanup = () => {\n\t\t\t\t\t\t\trl.close();\n\t\t\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst stopChild = (dueToLimit = false) => {\n\t\t\t\t\t\t\tif (!child.killed) {\n\t\t\t\t\t\t\t\tkilledDueToLimit = dueToLimit;\n\t\t\t\t\t\t\t\tchild.kill();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst onAbort = () => {\n\t\t\t\t\t\t\taborted = true;\n\t\t\t\t\t\t\tstopChild();\n\t\t\t\t\t\t};\n\t\t\t\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\t\t\t\tchild.stderr?.on(\"data\", (chunk) => {\n\t\t\t\t\t\t\tstderr += chunk.toString();\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\t// Format a context block around a match line. Path is omitted (emitted once per file).\n\t\t\t\t\t\tconst formatBlock = async (filePath: string, lineNumber: number): Promise<string[]> => {\n\t\t\t\t\t\t\tconst lines = await getFileLines(filePath);\n\t\t\t\t\t\t\tif (!lines.length) return [` ${lineNumber}: (unable to read file)`];\n\t\t\t\t\t\t\tconst block: string[] = [];\n\t\t\t\t\t\t\tconst start = contextValue > 0 ? Math.max(1, lineNumber - contextValue) : lineNumber;\n\t\t\t\t\t\t\tconst end = contextValue > 0 ? Math.min(lines.length, lineNumber + contextValue) : lineNumber;\n\t\t\t\t\t\t\tfor (let current = start; current <= end; current++) {\n\t\t\t\t\t\t\t\tconst lineText = lines[current - 1] ?? \"\";\n\t\t\t\t\t\t\t\tconst sanitized = lineText.replace(/\\r/g, \"\");\n\t\t\t\t\t\t\t\tconst isMatchLine = current === lineNumber;\n\t\t\t\t\t\t\t\t// Truncate long lines so grep output stays compact.\n\t\t\t\t\t\t\t\tconst { text: truncatedText, wasTruncated } = truncateLine(sanitized);\n\t\t\t\t\t\t\t\tif (wasTruncated) linesTruncated = true;\n\t\t\t\t\t\t\t\tif (isMatchLine) block.push(`${current}: ${truncatedText}`);\n\t\t\t\t\t\t\t\telse block.push(`${current}- ${truncatedText}`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn block;\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\t// Collect matches during streaming, then format them after rg exits.\n\t\t\t\t\t\tconst matches: Array<{ filePath: string; lineNumber: number; lineText?: string }> = [];\n\t\t\t\t\t\trl.on(\"line\", (line) => {\n\t\t\t\t\t\t\tif (!line.trim() || matchCount >= effectiveLimit) return;\n\t\t\t\t\t\t\tlet event: any;\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tevent = JSON.parse(line);\n\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (event.type === \"match\") {\n\t\t\t\t\t\t\t\tmatchCount++;\n\t\t\t\t\t\t\t\tconst filePath = event.data?.path?.text;\n\t\t\t\t\t\t\t\tconst lineNumber = event.data?.line_number;\n\t\t\t\t\t\t\t\tconst lineText = event.data?.lines?.text;\n\t\t\t\t\t\t\t\tif (filePath && typeof lineNumber === \"number\")\n\t\t\t\t\t\t\t\t\tmatches.push({ filePath, lineNumber, lineText });\n\t\t\t\t\t\t\t\tif (matchCount >= effectiveLimit) {\n\t\t\t\t\t\t\t\t\tmatchLimitReached = true;\n\t\t\t\t\t\t\t\t\tstopChild(true);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tchild.on(\"error\", (error) => {\n\t\t\t\t\t\t\tcleanup();\n\t\t\t\t\t\t\tsettle(() => reject(new Error(`Failed to run ripgrep: ${error.message}`)));\n\t\t\t\t\t\t});\n\t\t\t\t\t\tchild.on(\"close\", async (code) => {\n\t\t\t\t\t\t\tcleanup();\n\t\t\t\t\t\t\tif (aborted) {\n\t\t\t\t\t\t\t\tsettle(() => reject(new Error(\"Operation aborted\")));\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (!killedDueToLimit && code !== 0 && code !== 1) {\n\t\t\t\t\t\t\t\tconst trimmedStderr = stderr.trim();\n\t\t\t\t\t\t\t\tlet errorMsg = trimmedStderr || `ripgrep exited with code ${code}`;\n\t\t\t\t\t\t\t\t// A regex parse error means the pattern is not a valid regex. Point the\n\t\t\t\t\t\t\t\t// caller at the `literal` option so they can search the text verbatim.\n\t\t\t\t\t\t\t\tif (!literal && /regex parse error/i.test(trimmedStderr)) {\n\t\t\t\t\t\t\t\t\terrorMsg += `\\n\\nThe pattern is not a valid regex. To search for it as plain text, pass literal: true (or escape the regex metacharacters).`;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tsettle(() => reject(new Error(errorMsg)));\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (matchCount === 0) {\n\t\t\t\t\t\t\t\tsettle(() =>\n\t\t\t\t\t\t\t\t\tresolve({ content: [{ type: \"text\", text: \"No matches found\" }], details: undefined }),\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Group matches by file path, showing filename once per file.\n\t\t\t\t\t\t\tconst fileGroups = new Map<string, typeof matches>();\n\t\t\t\t\t\t\tfor (const match of matches) {\n\t\t\t\t\t\t\t\tlet group = fileGroups.get(match.filePath);\n\t\t\t\t\t\t\t\tif (!group) {\n\t\t\t\t\t\t\t\t\tgroup = [];\n\t\t\t\t\t\t\t\t\tfileGroups.set(match.filePath, group);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tgroup.push(match);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tfor (const [filePath, fileMatches] of fileGroups) {\n\t\t\t\t\t\t\t\tconst relativePath = formatPath(filePath);\n\t\t\t\t\t\t\t\toutputLines.push(relativePath);\n\t\t\t\t\t\t\t\tfor (const match of fileMatches) {\n\t\t\t\t\t\t\t\t\tif (contextValue === 0 && match.lineText !== undefined) {\n\t\t\t\t\t\t\t\t\t\tconst sanitized = match.lineText\n\t\t\t\t\t\t\t\t\t\t\t.replace(/\\r\\n/g, \"\\n\")\n\t\t\t\t\t\t\t\t\t\t\t.replace(/\\r/g, \"\")\n\t\t\t\t\t\t\t\t\t\t\t.replace(/\\n$/, \"\");\n\t\t\t\t\t\t\t\t\t\tconst { text: truncatedText, wasTruncated } = truncateLine(sanitized);\n\t\t\t\t\t\t\t\t\t\tif (wasTruncated) linesTruncated = true;\n\t\t\t\t\t\t\t\t\t\toutputLines.push(`${match.lineNumber}: ${truncatedText}`);\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tconst block = await formatBlock(match.filePath, match.lineNumber);\n\t\t\t\t\t\t\t\t\t\toutputLines.push(...block);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tconst rawOutput = outputLines.join(\"\\n\");\n\t\t\t\t\t\t\t// Apply lossless compression then byte truncation.\n\t\t\t\t\t\t\tconst compressedOutput = compressGrepOutput(rawOutput);\n\t\t\t\t\t\t\t// Apply byte truncation. There is no line limit here because the match limit already capped rows.\n\t\t\t\t\t\t\tconst truncation = truncateHead(compressedOutput, { maxLines: Number.MAX_SAFE_INTEGER });\n\t\t\t\t\t\t\tlet output = truncation.content;\n\t\t\t\t\t\t\tconst details: GrepToolDetails = {};\n\t\t\t\t\t\t\t// Build actionable notices for truncation and match limits.\n\t\t\t\t\t\t\tconst notices: string[] = [];\n\t\t\t\t\t\t\tif (matchLimitReached) {\n\t\t\t\t\t\t\t\tnotices.push(\n\t\t\t\t\t\t\t\t\t`${effectiveLimit} matches limit reached. Use limit=${effectiveLimit * 2} for more, or refine pattern`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tdetails.matchLimitReached = effectiveLimit;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (truncation.truncated) {\n\t\t\t\t\t\t\t\tnotices.push(`${formatSize(DEFAULT_MAX_BYTES)} limit reached`);\n\t\t\t\t\t\t\t\tdetails.truncation = truncation;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (linesTruncated) {\n\t\t\t\t\t\t\t\tnotices.push(\n\t\t\t\t\t\t\t\t\t`Some lines truncated to ${GREP_MAX_LINE_LENGTH} chars. Use read tool to see full lines`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tdetails.linesTruncated = true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (notices.length > 0) output += `\\n\\n[${notices.join(\". \")}]`;\n\t\t\t\t\t\t\tsettle(() =>\n\t\t\t\t\t\t\t\tresolve({\n\t\t\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: output }],\n\t\t\t\t\t\t\t\t\tdetails: Object.keys(details).length > 0 ? details : undefined,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t});\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tsettle(() => reject(err as Error));\n\t\t\t\t\t}\n\t\t\t\t})();\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(formatGrepCall(args, theme));\n\t\t\treturn text;\n\t\t},\n\t\trenderResult(result, options, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatGrepResult(result as any, options, theme, context.showImages));\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport function createGrepTool(cwd: string, options?: GrepToolOptions): AgentTool<typeof grepSchema> {\n\treturn wrapToolDefinition(createGrepToolDefinition(cwd, options));\n}\n"]}
|
|
1
|
+
{"version":3,"file":"grep.d.ts","sourceRoot":"","sources":["../../../src/core/tools/grep.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAMjE,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAG5C,OAAO,KAAK,EAAE,cAAc,EAA2B,MAAM,wBAAwB,CAAC;AAItF,OAAO,EAIN,KAAK,gBAAgB,EAGrB,MAAM,eAAe,CAAC;AAEvB,QAAA,MAAM,UAAU;;;;;;;;EAYd,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,UAAU,CAAC,CAAC;AAGtD,MAAM,WAAW,eAAe;IAC/B,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,cAAc,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC9B,mEAAmE;IACnE,WAAW,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IAClE,2CAA2C;IAC3C,QAAQ,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;CAC7D;AAOD,MAAM,WAAW,eAAe;IAC/B,yEAAyE;IACzE,UAAU,CAAC,EAAE,cAAc,CAAC;CAC5B;AAyDD,wBAAgB,wBAAwB,CACvC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,eAAe,GACvB,cAAc,CAAC,OAAO,UAAU,EAAE,eAAe,GAAG,SAAS,CAAC,CA0RhE;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC,OAAO,UAAU,CAAC,CAEnG","sourcesContent":["import { createInterface } from \"node:readline\";\nimport type { AgentTool } from \"@kolisachint/hoocode-agent-core\";\nimport { compressGrepOutput } from \"@kolisachint/hoocode-agent-core\";\nimport { Text } from \"@kolisachint/hoocode-tui\";\nimport { spawn } from \"child_process\";\nimport { readFileSync, statSync } from \"fs\";\nimport path from \"path\";\nimport { type Static, Type } from \"typebox\";\nimport { keyHint } from \"../../modes/interactive/components/keybinding-hints.js\";\nimport { ensureTool } from \"../../utils/tools-manager.js\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../extensions/types.js\";\nimport { resolveToCwd } from \"./path-utils.js\";\nimport { getTextOutput, invalidArgText, shortenPath, str } from \"./render-utils.js\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.js\";\nimport {\n\tDEFAULT_MAX_BYTES,\n\tformatSize,\n\tGREP_MAX_LINE_LENGTH,\n\ttype TruncationResult,\n\ttruncateHead,\n\ttruncateLine,\n} from \"./truncate.js\";\n\nconst grepSchema = Type.Object({\n\tpattern: Type.String({ description: \"Search pattern (regex or literal string)\" }),\n\tpath: Type.Optional(Type.String({ description: \"Directory or file to search (default: current directory)\" })),\n\tglob: Type.Optional(Type.String({ description: \"Filter files by glob pattern, e.g. '*.ts' or '**/*.spec.ts'\" })),\n\tignoreCase: Type.Optional(Type.Boolean({ description: \"Case-insensitive search (default: false)\" })),\n\tliteral: Type.Optional(\n\t\tType.Boolean({ description: \"Treat pattern as literal string instead of regex (default: false)\" }),\n\t),\n\tcontext: Type.Optional(\n\t\tType.Number({ description: \"Number of lines to show before and after each match (default: 0)\" }),\n\t),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of matches to return (default: 100)\" })),\n});\n\nexport type GrepToolInput = Static<typeof grepSchema>;\nconst DEFAULT_LIMIT = 100;\n\nexport interface GrepToolDetails {\n\ttruncation?: TruncationResult;\n\tmatchLimitReached?: number;\n\tlinesTruncated?: boolean;\n}\n\n/**\n * Pluggable operations for the grep tool.\n * Override these to delegate search to remote systems (for example SSH).\n */\nexport interface GrepOperations {\n\t/** Check if path is a directory. Throws if path does not exist. */\n\tisDirectory: (absolutePath: string) => Promise<boolean> | boolean;\n\t/** Read file contents for context lines */\n\treadFile: (absolutePath: string) => Promise<string> | string;\n}\n\nconst defaultGrepOperations: GrepOperations = {\n\tisDirectory: (p) => statSync(p).isDirectory(),\n\treadFile: (p) => readFileSync(p, \"utf-8\"),\n};\n\nexport interface GrepToolOptions {\n\t/** Custom operations for grep. Default: local filesystem plus ripgrep */\n\toperations?: GrepOperations;\n}\n\nfunction formatGrepCall(\n\targs: { pattern: string; path?: string; glob?: string; limit?: number } | undefined,\n\ttheme: typeof import(\"../../modes/interactive/theme/theme.js\").theme,\n): string {\n\tconst pattern = str(args?.pattern);\n\tconst rawPath = str(args?.path);\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(\"grep\")) +\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\nfunction formatGrepResult(\n\tresult: {\n\t\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\t\tdetails?: GrepToolDetails;\n\t},\n\toptions: ToolRenderResultOptions,\n\ttheme: typeof import(\"../../modes/interactive/theme/theme.js\").theme,\n\tshowImages: boolean,\n): string {\n\tconst output = getTextOutput(result, showImages).trim();\n\tlet text = \"\";\n\tif (output) {\n\t\tconst lines = output.split(\"\\n\");\n\t\tconst maxLines = options.expanded ? lines.length : 15;\n\t\tconst displayLines = lines.slice(0, maxLines);\n\t\tconst remaining = lines.length - maxLines;\n\t\ttext += `\\n${displayLines.map((line) => theme.fg(\"toolOutput\", line)).join(\"\\n\")}`;\n\t\tif (remaining > 0) {\n\t\t\ttext += `${theme.fg(\"muted\", `\\n... (${remaining} more lines,`)} ${keyHint(\"app.tools.expand\", \"to expand\")})`;\n\t\t}\n\t}\n\n\tconst matchLimit = result.details?.matchLimitReached;\n\tconst truncation = result.details?.truncation;\n\tconst linesTruncated = result.details?.linesTruncated;\n\tif (matchLimit || truncation?.truncated || linesTruncated) {\n\t\tconst warnings: string[] = [];\n\t\tif (matchLimit) warnings.push(`${matchLimit} matches limit`);\n\t\tif (truncation?.truncated) warnings.push(`${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit`);\n\t\tif (linesTruncated) warnings.push(\"some lines truncated\");\n\t\ttext += `\\n${theme.fg(\"warning\", `[Truncated: ${warnings.join(\", \")}]`)}`;\n\t}\n\treturn text;\n}\n\nexport function createGrepToolDefinition(\n\tcwd: string,\n\toptions?: GrepToolOptions,\n): ToolDefinition<typeof grepSchema, GrepToolDetails | undefined> {\n\tconst customOps = options?.operations;\n\treturn {\n\t\tname: \"grep\",\n\t\tlabel: \"grep\",\n\t\tdescription: `Search file contents for a pattern. Returns matching lines with file paths and line numbers. Respects .gitignore. Output is truncated to ${DEFAULT_LIMIT} matches or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). Long lines are truncated to ${GREP_MAX_LINE_LENGTH} chars.`,\n\t\tpromptSnippet: \"Search file contents for patterns (respects .gitignore)\",\n\t\tparameters: grepSchema,\n\t\tasync execute(\n\t\t\t_toolCallId,\n\t\t\t{\n\t\t\t\tpattern,\n\t\t\t\tpath: searchDir,\n\t\t\t\tglob,\n\t\t\t\tignoreCase,\n\t\t\t\tliteral,\n\t\t\t\tcontext,\n\t\t\t\tlimit,\n\t\t\t}: {\n\t\t\t\tpattern: string;\n\t\t\t\tpath?: string;\n\t\t\t\tglob?: string;\n\t\t\t\tignoreCase?: boolean;\n\t\t\t\tliteral?: boolean;\n\t\t\t\tcontext?: number;\n\t\t\t\tlimit?: number;\n\t\t\t},\n\t\t\tsignal?: AbortSignal,\n\t\t\t_onUpdate?,\n\t\t\t_ctx?,\n\t\t) {\n\t\t\treturn new Promise((resolve, reject) => {\n\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlet settled = false;\n\t\t\t\tconst settle = (fn: () => void) => {\n\t\t\t\t\tif (!settled) {\n\t\t\t\t\t\tsettled = true;\n\t\t\t\t\t\tfn();\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\t(async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst rgPath = await ensureTool(\"rg\", true);\n\t\t\t\t\t\tif (!rgPath) {\n\t\t\t\t\t\t\tsettle(() =>\n\t\t\t\t\t\t\t\treject(\n\t\t\t\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t\t\t\t\"ripgrep (rg) unavailable and could not be downloaded — use the bash tool to run grep/rg instead\",\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst searchPath = resolveToCwd(searchDir || \".\", cwd);\n\t\t\t\t\t\tconst ops = customOps ?? defaultGrepOperations;\n\t\t\t\t\t\tlet isDirectory: boolean;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tisDirectory = await ops.isDirectory(searchPath);\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\tsettle(() => reject(new Error(`Path not found: ${searchPath}`)));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst contextValue = context && context > 0 ? context : 0;\n\t\t\t\t\t\tconst effectiveLimit = Math.max(1, limit ?? DEFAULT_LIMIT);\n\t\t\t\t\t\tconst formatPath = (filePath: string): string => {\n\t\t\t\t\t\t\tif (isDirectory) {\n\t\t\t\t\t\t\t\tconst relative = path.relative(searchPath, filePath);\n\t\t\t\t\t\t\t\tif (relative && !relative.startsWith(\"..\")) {\n\t\t\t\t\t\t\t\t\treturn relative.replace(/\\\\/g, \"/\");\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn path.basename(filePath);\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tconst fileCache = new Map<string, string[]>();\n\t\t\t\t\t\tconst getFileLines = async (filePath: string): Promise<string[]> => {\n\t\t\t\t\t\t\tlet lines = fileCache.get(filePath);\n\t\t\t\t\t\t\tif (!lines) {\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tconst content = await ops.readFile(filePath);\n\t\t\t\t\t\t\t\t\tlines = content.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\").split(\"\\n\");\n\t\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t\tlines = [];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tfileCache.set(filePath, lines);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn lines;\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tconst args: string[] = [\"--json\", \"--line-number\", \"--color=never\", \"--hidden\"];\n\t\t\t\t\t\tif (ignoreCase) args.push(\"--ignore-case\");\n\t\t\t\t\t\tif (literal) args.push(\"--fixed-strings\");\n\t\t\t\t\t\tif (glob) args.push(\"--glob\", glob);\n\t\t\t\t\t\targs.push(\"--\", pattern, searchPath);\n\n\t\t\t\t\t\tconst child = spawn(rgPath, args, { stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n\t\t\t\t\t\tconst rl = createInterface({ input: child.stdout });\n\t\t\t\t\t\tlet stderr = \"\";\n\t\t\t\t\t\tlet matchCount = 0;\n\t\t\t\t\t\tlet matchLimitReached = false;\n\t\t\t\t\t\tlet linesTruncated = false;\n\t\t\t\t\t\tlet aborted = false;\n\t\t\t\t\t\tlet killedDueToLimit = false;\n\t\t\t\t\t\tconst outputLines: string[] = [];\n\n\t\t\t\t\t\tconst cleanup = () => {\n\t\t\t\t\t\t\trl.close();\n\t\t\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst stopChild = (dueToLimit = false) => {\n\t\t\t\t\t\t\tif (!child.killed) {\n\t\t\t\t\t\t\t\tkilledDueToLimit = dueToLimit;\n\t\t\t\t\t\t\t\tchild.kill();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst onAbort = () => {\n\t\t\t\t\t\t\taborted = true;\n\t\t\t\t\t\t\tstopChild();\n\t\t\t\t\t\t};\n\t\t\t\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\t\t\t\tchild.stderr?.on(\"data\", (chunk) => {\n\t\t\t\t\t\t\tstderr += chunk.toString();\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\t// Format a context block around a match line. Path is omitted (emitted once per file).\n\t\t\t\t\t\tconst formatBlock = async (filePath: string, lineNumber: number): Promise<string[]> => {\n\t\t\t\t\t\t\tconst lines = await getFileLines(filePath);\n\t\t\t\t\t\t\tif (!lines.length) return [` ${lineNumber}: (unable to read file)`];\n\t\t\t\t\t\t\tconst block: string[] = [];\n\t\t\t\t\t\t\tconst start = contextValue > 0 ? Math.max(1, lineNumber - contextValue) : lineNumber;\n\t\t\t\t\t\t\tconst end = contextValue > 0 ? Math.min(lines.length, lineNumber + contextValue) : lineNumber;\n\t\t\t\t\t\t\tfor (let current = start; current <= end; current++) {\n\t\t\t\t\t\t\t\tconst lineText = lines[current - 1] ?? \"\";\n\t\t\t\t\t\t\t\tconst sanitized = lineText.replace(/\\r/g, \"\");\n\t\t\t\t\t\t\t\tconst isMatchLine = current === lineNumber;\n\t\t\t\t\t\t\t\t// Truncate long lines so grep output stays compact.\n\t\t\t\t\t\t\t\tconst { text: truncatedText, wasTruncated } = truncateLine(sanitized);\n\t\t\t\t\t\t\t\tif (wasTruncated) linesTruncated = true;\n\t\t\t\t\t\t\t\tif (isMatchLine) block.push(`${current}: ${truncatedText}`);\n\t\t\t\t\t\t\t\telse block.push(`${current}- ${truncatedText}`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn block;\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\t// Collect matches during streaming, then format them after rg exits.\n\t\t\t\t\t\tconst matches: Array<{ filePath: string; lineNumber: number; lineText?: string }> = [];\n\t\t\t\t\t\trl.on(\"line\", (line) => {\n\t\t\t\t\t\t\tif (!line.trim() || matchCount >= effectiveLimit) return;\n\t\t\t\t\t\t\tlet event: any;\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tevent = JSON.parse(line);\n\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (event.type === \"match\") {\n\t\t\t\t\t\t\t\tmatchCount++;\n\t\t\t\t\t\t\t\tconst filePath = event.data?.path?.text;\n\t\t\t\t\t\t\t\tconst lineNumber = event.data?.line_number;\n\t\t\t\t\t\t\t\tconst lineText = event.data?.lines?.text;\n\t\t\t\t\t\t\t\tif (filePath && typeof lineNumber === \"number\")\n\t\t\t\t\t\t\t\t\tmatches.push({ filePath, lineNumber, lineText });\n\t\t\t\t\t\t\t\tif (matchCount >= effectiveLimit) {\n\t\t\t\t\t\t\t\t\tmatchLimitReached = true;\n\t\t\t\t\t\t\t\t\tstopChild(true);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tchild.on(\"error\", (error) => {\n\t\t\t\t\t\t\tcleanup();\n\t\t\t\t\t\t\tsettle(() => reject(new Error(`Failed to run ripgrep: ${error.message}`)));\n\t\t\t\t\t\t});\n\t\t\t\t\t\tchild.on(\"close\", async (code) => {\n\t\t\t\t\t\t\tcleanup();\n\t\t\t\t\t\t\tif (aborted) {\n\t\t\t\t\t\t\t\tsettle(() => reject(new Error(\"Operation aborted\")));\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (!killedDueToLimit && code !== 0 && code !== 1) {\n\t\t\t\t\t\t\t\tconst trimmedStderr = stderr.trim();\n\t\t\t\t\t\t\t\tlet errorMsg = trimmedStderr || `ripgrep exited with code ${code}`;\n\t\t\t\t\t\t\t\t// A regex parse error means the pattern is not a valid regex. Point the\n\t\t\t\t\t\t\t\t// caller at the `literal` option so they can search the text verbatim.\n\t\t\t\t\t\t\t\tif (!literal && /regex parse error/i.test(trimmedStderr)) {\n\t\t\t\t\t\t\t\t\terrorMsg += `\\n\\nThe pattern is not a valid regex. To search for it as plain text, pass literal: true (or escape the regex metacharacters).`;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tsettle(() => reject(new Error(errorMsg)));\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (matchCount === 0) {\n\t\t\t\t\t\t\t\tsettle(() =>\n\t\t\t\t\t\t\t\t\tresolve({ content: [{ type: \"text\", text: \"No matches found\" }], details: undefined }),\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Group matches by file path, showing filename once per file.\n\t\t\t\t\t\t\tconst fileGroups = new Map<string, typeof matches>();\n\t\t\t\t\t\t\tfor (const match of matches) {\n\t\t\t\t\t\t\t\tlet group = fileGroups.get(match.filePath);\n\t\t\t\t\t\t\t\tif (!group) {\n\t\t\t\t\t\t\t\t\tgroup = [];\n\t\t\t\t\t\t\t\t\tfileGroups.set(match.filePath, group);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tgroup.push(match);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tfor (const [filePath, fileMatches] of fileGroups) {\n\t\t\t\t\t\t\t\tconst relativePath = formatPath(filePath);\n\t\t\t\t\t\t\t\toutputLines.push(relativePath);\n\t\t\t\t\t\t\t\tfor (const match of fileMatches) {\n\t\t\t\t\t\t\t\t\tif (contextValue === 0 && match.lineText !== undefined) {\n\t\t\t\t\t\t\t\t\t\tconst sanitized = match.lineText\n\t\t\t\t\t\t\t\t\t\t\t.replace(/\\r\\n/g, \"\\n\")\n\t\t\t\t\t\t\t\t\t\t\t.replace(/\\r/g, \"\")\n\t\t\t\t\t\t\t\t\t\t\t.replace(/\\n$/, \"\");\n\t\t\t\t\t\t\t\t\t\tconst { text: truncatedText, wasTruncated } = truncateLine(sanitized);\n\t\t\t\t\t\t\t\t\t\tif (wasTruncated) linesTruncated = true;\n\t\t\t\t\t\t\t\t\t\toutputLines.push(`${match.lineNumber}: ${truncatedText}`);\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tconst block = await formatBlock(match.filePath, match.lineNumber);\n\t\t\t\t\t\t\t\t\t\toutputLines.push(...block);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tconst rawOutput = outputLines.join(\"\\n\");\n\t\t\t\t\t\t\t// Apply lossless compression then byte truncation.\n\t\t\t\t\t\t\tconst compressedOutput = compressGrepOutput(rawOutput);\n\t\t\t\t\t\t\t// Apply byte truncation. There is no line limit here because the match limit already capped rows.\n\t\t\t\t\t\t\tconst truncation = truncateHead(compressedOutput, { maxLines: Number.MAX_SAFE_INTEGER });\n\t\t\t\t\t\t\tlet output = truncation.content;\n\t\t\t\t\t\t\tconst details: GrepToolDetails = {};\n\t\t\t\t\t\t\t// Build actionable notices for truncation and match limits.\n\t\t\t\t\t\t\tconst notices: string[] = [];\n\t\t\t\t\t\t\tif (matchLimitReached) {\n\t\t\t\t\t\t\t\tnotices.push(\n\t\t\t\t\t\t\t\t\t`${effectiveLimit} matches limit reached. Use limit=${effectiveLimit * 2} for more, or refine pattern`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tdetails.matchLimitReached = effectiveLimit;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (truncation.truncated) {\n\t\t\t\t\t\t\t\tnotices.push(`${formatSize(DEFAULT_MAX_BYTES)} limit reached`);\n\t\t\t\t\t\t\t\tdetails.truncation = truncation;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (linesTruncated) {\n\t\t\t\t\t\t\t\tnotices.push(\n\t\t\t\t\t\t\t\t\t`Some lines truncated to ${GREP_MAX_LINE_LENGTH} chars. Use read tool to see full lines`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tdetails.linesTruncated = true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (notices.length > 0) output += `\\n\\n[${notices.join(\". \")}]`;\n\t\t\t\t\t\t\tsettle(() =>\n\t\t\t\t\t\t\t\tresolve({\n\t\t\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: output }],\n\t\t\t\t\t\t\t\t\tdetails: Object.keys(details).length > 0 ? details : undefined,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t});\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tsettle(() => reject(err as Error));\n\t\t\t\t\t}\n\t\t\t\t})();\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(formatGrepCall(args, theme));\n\t\t\treturn text;\n\t\t},\n\t\trenderResult(result, options, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatGrepResult(result as any, options, theme, context.showImages));\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport function createGrepTool(cwd: string, options?: GrepToolOptions): AgentTool<typeof grepSchema> {\n\treturn wrapToolDefinition(createGrepToolDefinition(cwd, options));\n}\n"]}
|
package/dist/core/tools/grep.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createInterface } from "node:readline";
|
|
2
|
+
import { compressGrepOutput } from "@kolisachint/hoocode-agent-core";
|
|
2
3
|
import { Text } from "@kolisachint/hoocode-tui";
|
|
3
4
|
import { spawn } from "child_process";
|
|
4
5
|
import { readFileSync, statSync } from "fs";
|
|
@@ -6,7 +7,6 @@ import path from "path";
|
|
|
6
7
|
import { Type } from "typebox";
|
|
7
8
|
import { keyHint } from "../../modes/interactive/components/keybinding-hints.js";
|
|
8
9
|
import { ensureTool } from "../../utils/tools-manager.js";
|
|
9
|
-
import { compressGrepOutput } from "./output-compression.js";
|
|
10
10
|
import { resolveToCwd } from "./path-utils.js";
|
|
11
11
|
import { getTextOutput, invalidArgText, shortenPath, str } from "./render-utils.js";
|
|
12
12
|
import { wrapToolDefinition } from "./tool-definition-wrapper.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grep.js","sourceRoot":"","sources":["../../../src/core/tools/grep.ts"],"names":[],"mappings":"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","sourcesContent":["import { createInterface } from \"node:readline\";\nimport type { AgentTool } from \"@kolisachint/hoocode-agent-core\";\nimport { Text } from \"@kolisachint/hoocode-tui\";\nimport { spawn } from \"child_process\";\nimport { readFileSync, statSync } from \"fs\";\nimport path from \"path\";\nimport { type Static, Type } from \"typebox\";\nimport { keyHint } from \"../../modes/interactive/components/keybinding-hints.js\";\nimport { ensureTool } from \"../../utils/tools-manager.js\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../extensions/types.js\";\nimport { compressGrepOutput } from \"./output-compression.js\";\nimport { resolveToCwd } from \"./path-utils.js\";\nimport { getTextOutput, invalidArgText, shortenPath, str } from \"./render-utils.js\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.js\";\nimport {\n\tDEFAULT_MAX_BYTES,\n\tformatSize,\n\tGREP_MAX_LINE_LENGTH,\n\ttype TruncationResult,\n\ttruncateHead,\n\ttruncateLine,\n} from \"./truncate.js\";\n\nconst grepSchema = Type.Object({\n\tpattern: Type.String({ description: \"Search pattern (regex or literal string)\" }),\n\tpath: Type.Optional(Type.String({ description: \"Directory or file to search (default: current directory)\" })),\n\tglob: Type.Optional(Type.String({ description: \"Filter files by glob pattern, e.g. '*.ts' or '**/*.spec.ts'\" })),\n\tignoreCase: Type.Optional(Type.Boolean({ description: \"Case-insensitive search (default: false)\" })),\n\tliteral: Type.Optional(\n\t\tType.Boolean({ description: \"Treat pattern as literal string instead of regex (default: false)\" }),\n\t),\n\tcontext: Type.Optional(\n\t\tType.Number({ description: \"Number of lines to show before and after each match (default: 0)\" }),\n\t),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of matches to return (default: 100)\" })),\n});\n\nexport type GrepToolInput = Static<typeof grepSchema>;\nconst DEFAULT_LIMIT = 100;\n\nexport interface GrepToolDetails {\n\ttruncation?: TruncationResult;\n\tmatchLimitReached?: number;\n\tlinesTruncated?: boolean;\n}\n\n/**\n * Pluggable operations for the grep tool.\n * Override these to delegate search to remote systems (for example SSH).\n */\nexport interface GrepOperations {\n\t/** Check if path is a directory. Throws if path does not exist. */\n\tisDirectory: (absolutePath: string) => Promise<boolean> | boolean;\n\t/** Read file contents for context lines */\n\treadFile: (absolutePath: string) => Promise<string> | string;\n}\n\nconst defaultGrepOperations: GrepOperations = {\n\tisDirectory: (p) => statSync(p).isDirectory(),\n\treadFile: (p) => readFileSync(p, \"utf-8\"),\n};\n\nexport interface GrepToolOptions {\n\t/** Custom operations for grep. Default: local filesystem plus ripgrep */\n\toperations?: GrepOperations;\n}\n\nfunction formatGrepCall(\n\targs: { pattern: string; path?: string; glob?: string; limit?: number } | undefined,\n\ttheme: typeof import(\"../../modes/interactive/theme/theme.js\").theme,\n): string {\n\tconst pattern = str(args?.pattern);\n\tconst rawPath = str(args?.path);\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(\"grep\")) +\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\nfunction formatGrepResult(\n\tresult: {\n\t\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\t\tdetails?: GrepToolDetails;\n\t},\n\toptions: ToolRenderResultOptions,\n\ttheme: typeof import(\"../../modes/interactive/theme/theme.js\").theme,\n\tshowImages: boolean,\n): string {\n\tconst output = getTextOutput(result, showImages).trim();\n\tlet text = \"\";\n\tif (output) {\n\t\tconst lines = output.split(\"\\n\");\n\t\tconst maxLines = options.expanded ? lines.length : 15;\n\t\tconst displayLines = lines.slice(0, maxLines);\n\t\tconst remaining = lines.length - maxLines;\n\t\ttext += `\\n${displayLines.map((line) => theme.fg(\"toolOutput\", line)).join(\"\\n\")}`;\n\t\tif (remaining > 0) {\n\t\t\ttext += `${theme.fg(\"muted\", `\\n... (${remaining} more lines,`)} ${keyHint(\"app.tools.expand\", \"to expand\")})`;\n\t\t}\n\t}\n\n\tconst matchLimit = result.details?.matchLimitReached;\n\tconst truncation = result.details?.truncation;\n\tconst linesTruncated = result.details?.linesTruncated;\n\tif (matchLimit || truncation?.truncated || linesTruncated) {\n\t\tconst warnings: string[] = [];\n\t\tif (matchLimit) warnings.push(`${matchLimit} matches limit`);\n\t\tif (truncation?.truncated) warnings.push(`${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit`);\n\t\tif (linesTruncated) warnings.push(\"some lines truncated\");\n\t\ttext += `\\n${theme.fg(\"warning\", `[Truncated: ${warnings.join(\", \")}]`)}`;\n\t}\n\treturn text;\n}\n\nexport function createGrepToolDefinition(\n\tcwd: string,\n\toptions?: GrepToolOptions,\n): ToolDefinition<typeof grepSchema, GrepToolDetails | undefined> {\n\tconst customOps = options?.operations;\n\treturn {\n\t\tname: \"grep\",\n\t\tlabel: \"grep\",\n\t\tdescription: `Search file contents for a pattern. Returns matching lines with file paths and line numbers. Respects .gitignore. Output is truncated to ${DEFAULT_LIMIT} matches or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). Long lines are truncated to ${GREP_MAX_LINE_LENGTH} chars.`,\n\t\tpromptSnippet: \"Search file contents for patterns (respects .gitignore)\",\n\t\tparameters: grepSchema,\n\t\tasync execute(\n\t\t\t_toolCallId,\n\t\t\t{\n\t\t\t\tpattern,\n\t\t\t\tpath: searchDir,\n\t\t\t\tglob,\n\t\t\t\tignoreCase,\n\t\t\t\tliteral,\n\t\t\t\tcontext,\n\t\t\t\tlimit,\n\t\t\t}: {\n\t\t\t\tpattern: string;\n\t\t\t\tpath?: string;\n\t\t\t\tglob?: string;\n\t\t\t\tignoreCase?: boolean;\n\t\t\t\tliteral?: boolean;\n\t\t\t\tcontext?: number;\n\t\t\t\tlimit?: number;\n\t\t\t},\n\t\t\tsignal?: AbortSignal,\n\t\t\t_onUpdate?,\n\t\t\t_ctx?,\n\t\t) {\n\t\t\treturn new Promise((resolve, reject) => {\n\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlet settled = false;\n\t\t\t\tconst settle = (fn: () => void) => {\n\t\t\t\t\tif (!settled) {\n\t\t\t\t\t\tsettled = true;\n\t\t\t\t\t\tfn();\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\t(async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst rgPath = await ensureTool(\"rg\", true);\n\t\t\t\t\t\tif (!rgPath) {\n\t\t\t\t\t\t\tsettle(() =>\n\t\t\t\t\t\t\t\treject(\n\t\t\t\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t\t\t\t\"ripgrep (rg) unavailable and could not be downloaded — use the bash tool to run grep/rg instead\",\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst searchPath = resolveToCwd(searchDir || \".\", cwd);\n\t\t\t\t\t\tconst ops = customOps ?? defaultGrepOperations;\n\t\t\t\t\t\tlet isDirectory: boolean;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tisDirectory = await ops.isDirectory(searchPath);\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\tsettle(() => reject(new Error(`Path not found: ${searchPath}`)));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst contextValue = context && context > 0 ? context : 0;\n\t\t\t\t\t\tconst effectiveLimit = Math.max(1, limit ?? DEFAULT_LIMIT);\n\t\t\t\t\t\tconst formatPath = (filePath: string): string => {\n\t\t\t\t\t\t\tif (isDirectory) {\n\t\t\t\t\t\t\t\tconst relative = path.relative(searchPath, filePath);\n\t\t\t\t\t\t\t\tif (relative && !relative.startsWith(\"..\")) {\n\t\t\t\t\t\t\t\t\treturn relative.replace(/\\\\/g, \"/\");\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn path.basename(filePath);\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tconst fileCache = new Map<string, string[]>();\n\t\t\t\t\t\tconst getFileLines = async (filePath: string): Promise<string[]> => {\n\t\t\t\t\t\t\tlet lines = fileCache.get(filePath);\n\t\t\t\t\t\t\tif (!lines) {\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tconst content = await ops.readFile(filePath);\n\t\t\t\t\t\t\t\t\tlines = content.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\").split(\"\\n\");\n\t\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t\tlines = [];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tfileCache.set(filePath, lines);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn lines;\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tconst args: string[] = [\"--json\", \"--line-number\", \"--color=never\", \"--hidden\"];\n\t\t\t\t\t\tif (ignoreCase) args.push(\"--ignore-case\");\n\t\t\t\t\t\tif (literal) args.push(\"--fixed-strings\");\n\t\t\t\t\t\tif (glob) args.push(\"--glob\", glob);\n\t\t\t\t\t\targs.push(\"--\", pattern, searchPath);\n\n\t\t\t\t\t\tconst child = spawn(rgPath, args, { stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n\t\t\t\t\t\tconst rl = createInterface({ input: child.stdout });\n\t\t\t\t\t\tlet stderr = \"\";\n\t\t\t\t\t\tlet matchCount = 0;\n\t\t\t\t\t\tlet matchLimitReached = false;\n\t\t\t\t\t\tlet linesTruncated = false;\n\t\t\t\t\t\tlet aborted = false;\n\t\t\t\t\t\tlet killedDueToLimit = false;\n\t\t\t\t\t\tconst outputLines: string[] = [];\n\n\t\t\t\t\t\tconst cleanup = () => {\n\t\t\t\t\t\t\trl.close();\n\t\t\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst stopChild = (dueToLimit = false) => {\n\t\t\t\t\t\t\tif (!child.killed) {\n\t\t\t\t\t\t\t\tkilledDueToLimit = dueToLimit;\n\t\t\t\t\t\t\t\tchild.kill();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst onAbort = () => {\n\t\t\t\t\t\t\taborted = true;\n\t\t\t\t\t\t\tstopChild();\n\t\t\t\t\t\t};\n\t\t\t\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\t\t\t\tchild.stderr?.on(\"data\", (chunk) => {\n\t\t\t\t\t\t\tstderr += chunk.toString();\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\t// Format a context block around a match line. Path is omitted (emitted once per file).\n\t\t\t\t\t\tconst formatBlock = async (filePath: string, lineNumber: number): Promise<string[]> => {\n\t\t\t\t\t\t\tconst lines = await getFileLines(filePath);\n\t\t\t\t\t\t\tif (!lines.length) return [` ${lineNumber}: (unable to read file)`];\n\t\t\t\t\t\t\tconst block: string[] = [];\n\t\t\t\t\t\t\tconst start = contextValue > 0 ? Math.max(1, lineNumber - contextValue) : lineNumber;\n\t\t\t\t\t\t\tconst end = contextValue > 0 ? Math.min(lines.length, lineNumber + contextValue) : lineNumber;\n\t\t\t\t\t\t\tfor (let current = start; current <= end; current++) {\n\t\t\t\t\t\t\t\tconst lineText = lines[current - 1] ?? \"\";\n\t\t\t\t\t\t\t\tconst sanitized = lineText.replace(/\\r/g, \"\");\n\t\t\t\t\t\t\t\tconst isMatchLine = current === lineNumber;\n\t\t\t\t\t\t\t\t// Truncate long lines so grep output stays compact.\n\t\t\t\t\t\t\t\tconst { text: truncatedText, wasTruncated } = truncateLine(sanitized);\n\t\t\t\t\t\t\t\tif (wasTruncated) linesTruncated = true;\n\t\t\t\t\t\t\t\tif (isMatchLine) block.push(`${current}: ${truncatedText}`);\n\t\t\t\t\t\t\t\telse block.push(`${current}- ${truncatedText}`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn block;\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\t// Collect matches during streaming, then format them after rg exits.\n\t\t\t\t\t\tconst matches: Array<{ filePath: string; lineNumber: number; lineText?: string }> = [];\n\t\t\t\t\t\trl.on(\"line\", (line) => {\n\t\t\t\t\t\t\tif (!line.trim() || matchCount >= effectiveLimit) return;\n\t\t\t\t\t\t\tlet event: any;\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tevent = JSON.parse(line);\n\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (event.type === \"match\") {\n\t\t\t\t\t\t\t\tmatchCount++;\n\t\t\t\t\t\t\t\tconst filePath = event.data?.path?.text;\n\t\t\t\t\t\t\t\tconst lineNumber = event.data?.line_number;\n\t\t\t\t\t\t\t\tconst lineText = event.data?.lines?.text;\n\t\t\t\t\t\t\t\tif (filePath && typeof lineNumber === \"number\")\n\t\t\t\t\t\t\t\t\tmatches.push({ filePath, lineNumber, lineText });\n\t\t\t\t\t\t\t\tif (matchCount >= effectiveLimit) {\n\t\t\t\t\t\t\t\t\tmatchLimitReached = true;\n\t\t\t\t\t\t\t\t\tstopChild(true);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tchild.on(\"error\", (error) => {\n\t\t\t\t\t\t\tcleanup();\n\t\t\t\t\t\t\tsettle(() => reject(new Error(`Failed to run ripgrep: ${error.message}`)));\n\t\t\t\t\t\t});\n\t\t\t\t\t\tchild.on(\"close\", async (code) => {\n\t\t\t\t\t\t\tcleanup();\n\t\t\t\t\t\t\tif (aborted) {\n\t\t\t\t\t\t\t\tsettle(() => reject(new Error(\"Operation aborted\")));\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (!killedDueToLimit && code !== 0 && code !== 1) {\n\t\t\t\t\t\t\t\tconst trimmedStderr = stderr.trim();\n\t\t\t\t\t\t\t\tlet errorMsg = trimmedStderr || `ripgrep exited with code ${code}`;\n\t\t\t\t\t\t\t\t// A regex parse error means the pattern is not a valid regex. Point the\n\t\t\t\t\t\t\t\t// caller at the `literal` option so they can search the text verbatim.\n\t\t\t\t\t\t\t\tif (!literal && /regex parse error/i.test(trimmedStderr)) {\n\t\t\t\t\t\t\t\t\terrorMsg += `\\n\\nThe pattern is not a valid regex. To search for it as plain text, pass literal: true (or escape the regex metacharacters).`;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tsettle(() => reject(new Error(errorMsg)));\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (matchCount === 0) {\n\t\t\t\t\t\t\t\tsettle(() =>\n\t\t\t\t\t\t\t\t\tresolve({ content: [{ type: \"text\", text: \"No matches found\" }], details: undefined }),\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Group matches by file path, showing filename once per file.\n\t\t\t\t\t\t\tconst fileGroups = new Map<string, typeof matches>();\n\t\t\t\t\t\t\tfor (const match of matches) {\n\t\t\t\t\t\t\t\tlet group = fileGroups.get(match.filePath);\n\t\t\t\t\t\t\t\tif (!group) {\n\t\t\t\t\t\t\t\t\tgroup = [];\n\t\t\t\t\t\t\t\t\tfileGroups.set(match.filePath, group);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tgroup.push(match);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tfor (const [filePath, fileMatches] of fileGroups) {\n\t\t\t\t\t\t\t\tconst relativePath = formatPath(filePath);\n\t\t\t\t\t\t\t\toutputLines.push(relativePath);\n\t\t\t\t\t\t\t\tfor (const match of fileMatches) {\n\t\t\t\t\t\t\t\t\tif (contextValue === 0 && match.lineText !== undefined) {\n\t\t\t\t\t\t\t\t\t\tconst sanitized = match.lineText\n\t\t\t\t\t\t\t\t\t\t\t.replace(/\\r\\n/g, \"\\n\")\n\t\t\t\t\t\t\t\t\t\t\t.replace(/\\r/g, \"\")\n\t\t\t\t\t\t\t\t\t\t\t.replace(/\\n$/, \"\");\n\t\t\t\t\t\t\t\t\t\tconst { text: truncatedText, wasTruncated } = truncateLine(sanitized);\n\t\t\t\t\t\t\t\t\t\tif (wasTruncated) linesTruncated = true;\n\t\t\t\t\t\t\t\t\t\toutputLines.push(`${match.lineNumber}: ${truncatedText}`);\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tconst block = await formatBlock(match.filePath, match.lineNumber);\n\t\t\t\t\t\t\t\t\t\toutputLines.push(...block);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tconst rawOutput = outputLines.join(\"\\n\");\n\t\t\t\t\t\t\t// Apply lossless compression then byte truncation.\n\t\t\t\t\t\t\tconst compressedOutput = compressGrepOutput(rawOutput);\n\t\t\t\t\t\t\t// Apply byte truncation. There is no line limit here because the match limit already capped rows.\n\t\t\t\t\t\t\tconst truncation = truncateHead(compressedOutput, { maxLines: Number.MAX_SAFE_INTEGER });\n\t\t\t\t\t\t\tlet output = truncation.content;\n\t\t\t\t\t\t\tconst details: GrepToolDetails = {};\n\t\t\t\t\t\t\t// Build actionable notices for truncation and match limits.\n\t\t\t\t\t\t\tconst notices: string[] = [];\n\t\t\t\t\t\t\tif (matchLimitReached) {\n\t\t\t\t\t\t\t\tnotices.push(\n\t\t\t\t\t\t\t\t\t`${effectiveLimit} matches limit reached. Use limit=${effectiveLimit * 2} for more, or refine pattern`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tdetails.matchLimitReached = effectiveLimit;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (truncation.truncated) {\n\t\t\t\t\t\t\t\tnotices.push(`${formatSize(DEFAULT_MAX_BYTES)} limit reached`);\n\t\t\t\t\t\t\t\tdetails.truncation = truncation;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (linesTruncated) {\n\t\t\t\t\t\t\t\tnotices.push(\n\t\t\t\t\t\t\t\t\t`Some lines truncated to ${GREP_MAX_LINE_LENGTH} chars. Use read tool to see full lines`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tdetails.linesTruncated = true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (notices.length > 0) output += `\\n\\n[${notices.join(\". \")}]`;\n\t\t\t\t\t\t\tsettle(() =>\n\t\t\t\t\t\t\t\tresolve({\n\t\t\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: output }],\n\t\t\t\t\t\t\t\t\tdetails: Object.keys(details).length > 0 ? details : undefined,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t});\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tsettle(() => reject(err as Error));\n\t\t\t\t\t}\n\t\t\t\t})();\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(formatGrepCall(args, theme));\n\t\t\treturn text;\n\t\t},\n\t\trenderResult(result, options, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatGrepResult(result as any, options, theme, context.showImages));\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport function createGrepTool(cwd: string, options?: GrepToolOptions): AgentTool<typeof grepSchema> {\n\treturn wrapToolDefinition(createGrepToolDefinition(cwd, options));\n}\n"]}
|
|
1
|
+
{"version":3,"file":"grep.js","sourceRoot":"","sources":["../../../src/core/tools/grep.ts"],"names":[],"mappings":"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","sourcesContent":["import { createInterface } from \"node:readline\";\nimport type { AgentTool } from \"@kolisachint/hoocode-agent-core\";\nimport { compressGrepOutput } from \"@kolisachint/hoocode-agent-core\";\nimport { Text } from \"@kolisachint/hoocode-tui\";\nimport { spawn } from \"child_process\";\nimport { readFileSync, statSync } from \"fs\";\nimport path from \"path\";\nimport { type Static, Type } from \"typebox\";\nimport { keyHint } from \"../../modes/interactive/components/keybinding-hints.js\";\nimport { ensureTool } from \"../../utils/tools-manager.js\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../extensions/types.js\";\nimport { resolveToCwd } from \"./path-utils.js\";\nimport { getTextOutput, invalidArgText, shortenPath, str } from \"./render-utils.js\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.js\";\nimport {\n\tDEFAULT_MAX_BYTES,\n\tformatSize,\n\tGREP_MAX_LINE_LENGTH,\n\ttype TruncationResult,\n\ttruncateHead,\n\ttruncateLine,\n} from \"./truncate.js\";\n\nconst grepSchema = Type.Object({\n\tpattern: Type.String({ description: \"Search pattern (regex or literal string)\" }),\n\tpath: Type.Optional(Type.String({ description: \"Directory or file to search (default: current directory)\" })),\n\tglob: Type.Optional(Type.String({ description: \"Filter files by glob pattern, e.g. '*.ts' or '**/*.spec.ts'\" })),\n\tignoreCase: Type.Optional(Type.Boolean({ description: \"Case-insensitive search (default: false)\" })),\n\tliteral: Type.Optional(\n\t\tType.Boolean({ description: \"Treat pattern as literal string instead of regex (default: false)\" }),\n\t),\n\tcontext: Type.Optional(\n\t\tType.Number({ description: \"Number of lines to show before and after each match (default: 0)\" }),\n\t),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of matches to return (default: 100)\" })),\n});\n\nexport type GrepToolInput = Static<typeof grepSchema>;\nconst DEFAULT_LIMIT = 100;\n\nexport interface GrepToolDetails {\n\ttruncation?: TruncationResult;\n\tmatchLimitReached?: number;\n\tlinesTruncated?: boolean;\n}\n\n/**\n * Pluggable operations for the grep tool.\n * Override these to delegate search to remote systems (for example SSH).\n */\nexport interface GrepOperations {\n\t/** Check if path is a directory. Throws if path does not exist. */\n\tisDirectory: (absolutePath: string) => Promise<boolean> | boolean;\n\t/** Read file contents for context lines */\n\treadFile: (absolutePath: string) => Promise<string> | string;\n}\n\nconst defaultGrepOperations: GrepOperations = {\n\tisDirectory: (p) => statSync(p).isDirectory(),\n\treadFile: (p) => readFileSync(p, \"utf-8\"),\n};\n\nexport interface GrepToolOptions {\n\t/** Custom operations for grep. Default: local filesystem plus ripgrep */\n\toperations?: GrepOperations;\n}\n\nfunction formatGrepCall(\n\targs: { pattern: string; path?: string; glob?: string; limit?: number } | undefined,\n\ttheme: typeof import(\"../../modes/interactive/theme/theme.js\").theme,\n): string {\n\tconst pattern = str(args?.pattern);\n\tconst rawPath = str(args?.path);\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(\"grep\")) +\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\nfunction formatGrepResult(\n\tresult: {\n\t\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\t\tdetails?: GrepToolDetails;\n\t},\n\toptions: ToolRenderResultOptions,\n\ttheme: typeof import(\"../../modes/interactive/theme/theme.js\").theme,\n\tshowImages: boolean,\n): string {\n\tconst output = getTextOutput(result, showImages).trim();\n\tlet text = \"\";\n\tif (output) {\n\t\tconst lines = output.split(\"\\n\");\n\t\tconst maxLines = options.expanded ? lines.length : 15;\n\t\tconst displayLines = lines.slice(0, maxLines);\n\t\tconst remaining = lines.length - maxLines;\n\t\ttext += `\\n${displayLines.map((line) => theme.fg(\"toolOutput\", line)).join(\"\\n\")}`;\n\t\tif (remaining > 0) {\n\t\t\ttext += `${theme.fg(\"muted\", `\\n... (${remaining} more lines,`)} ${keyHint(\"app.tools.expand\", \"to expand\")})`;\n\t\t}\n\t}\n\n\tconst matchLimit = result.details?.matchLimitReached;\n\tconst truncation = result.details?.truncation;\n\tconst linesTruncated = result.details?.linesTruncated;\n\tif (matchLimit || truncation?.truncated || linesTruncated) {\n\t\tconst warnings: string[] = [];\n\t\tif (matchLimit) warnings.push(`${matchLimit} matches limit`);\n\t\tif (truncation?.truncated) warnings.push(`${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit`);\n\t\tif (linesTruncated) warnings.push(\"some lines truncated\");\n\t\ttext += `\\n${theme.fg(\"warning\", `[Truncated: ${warnings.join(\", \")}]`)}`;\n\t}\n\treturn text;\n}\n\nexport function createGrepToolDefinition(\n\tcwd: string,\n\toptions?: GrepToolOptions,\n): ToolDefinition<typeof grepSchema, GrepToolDetails | undefined> {\n\tconst customOps = options?.operations;\n\treturn {\n\t\tname: \"grep\",\n\t\tlabel: \"grep\",\n\t\tdescription: `Search file contents for a pattern. Returns matching lines with file paths and line numbers. Respects .gitignore. Output is truncated to ${DEFAULT_LIMIT} matches or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). Long lines are truncated to ${GREP_MAX_LINE_LENGTH} chars.`,\n\t\tpromptSnippet: \"Search file contents for patterns (respects .gitignore)\",\n\t\tparameters: grepSchema,\n\t\tasync execute(\n\t\t\t_toolCallId,\n\t\t\t{\n\t\t\t\tpattern,\n\t\t\t\tpath: searchDir,\n\t\t\t\tglob,\n\t\t\t\tignoreCase,\n\t\t\t\tliteral,\n\t\t\t\tcontext,\n\t\t\t\tlimit,\n\t\t\t}: {\n\t\t\t\tpattern: string;\n\t\t\t\tpath?: string;\n\t\t\t\tglob?: string;\n\t\t\t\tignoreCase?: boolean;\n\t\t\t\tliteral?: boolean;\n\t\t\t\tcontext?: number;\n\t\t\t\tlimit?: number;\n\t\t\t},\n\t\t\tsignal?: AbortSignal,\n\t\t\t_onUpdate?,\n\t\t\t_ctx?,\n\t\t) {\n\t\t\treturn new Promise((resolve, reject) => {\n\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlet settled = false;\n\t\t\t\tconst settle = (fn: () => void) => {\n\t\t\t\t\tif (!settled) {\n\t\t\t\t\t\tsettled = true;\n\t\t\t\t\t\tfn();\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\t(async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst rgPath = await ensureTool(\"rg\", true);\n\t\t\t\t\t\tif (!rgPath) {\n\t\t\t\t\t\t\tsettle(() =>\n\t\t\t\t\t\t\t\treject(\n\t\t\t\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t\t\t\t\"ripgrep (rg) unavailable and could not be downloaded — use the bash tool to run grep/rg instead\",\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst searchPath = resolveToCwd(searchDir || \".\", cwd);\n\t\t\t\t\t\tconst ops = customOps ?? defaultGrepOperations;\n\t\t\t\t\t\tlet isDirectory: boolean;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tisDirectory = await ops.isDirectory(searchPath);\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\tsettle(() => reject(new Error(`Path not found: ${searchPath}`)));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst contextValue = context && context > 0 ? context : 0;\n\t\t\t\t\t\tconst effectiveLimit = Math.max(1, limit ?? DEFAULT_LIMIT);\n\t\t\t\t\t\tconst formatPath = (filePath: string): string => {\n\t\t\t\t\t\t\tif (isDirectory) {\n\t\t\t\t\t\t\t\tconst relative = path.relative(searchPath, filePath);\n\t\t\t\t\t\t\t\tif (relative && !relative.startsWith(\"..\")) {\n\t\t\t\t\t\t\t\t\treturn relative.replace(/\\\\/g, \"/\");\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn path.basename(filePath);\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tconst fileCache = new Map<string, string[]>();\n\t\t\t\t\t\tconst getFileLines = async (filePath: string): Promise<string[]> => {\n\t\t\t\t\t\t\tlet lines = fileCache.get(filePath);\n\t\t\t\t\t\t\tif (!lines) {\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tconst content = await ops.readFile(filePath);\n\t\t\t\t\t\t\t\t\tlines = content.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\").split(\"\\n\");\n\t\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t\tlines = [];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tfileCache.set(filePath, lines);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn lines;\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tconst args: string[] = [\"--json\", \"--line-number\", \"--color=never\", \"--hidden\"];\n\t\t\t\t\t\tif (ignoreCase) args.push(\"--ignore-case\");\n\t\t\t\t\t\tif (literal) args.push(\"--fixed-strings\");\n\t\t\t\t\t\tif (glob) args.push(\"--glob\", glob);\n\t\t\t\t\t\targs.push(\"--\", pattern, searchPath);\n\n\t\t\t\t\t\tconst child = spawn(rgPath, args, { stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n\t\t\t\t\t\tconst rl = createInterface({ input: child.stdout });\n\t\t\t\t\t\tlet stderr = \"\";\n\t\t\t\t\t\tlet matchCount = 0;\n\t\t\t\t\t\tlet matchLimitReached = false;\n\t\t\t\t\t\tlet linesTruncated = false;\n\t\t\t\t\t\tlet aborted = false;\n\t\t\t\t\t\tlet killedDueToLimit = false;\n\t\t\t\t\t\tconst outputLines: string[] = [];\n\n\t\t\t\t\t\tconst cleanup = () => {\n\t\t\t\t\t\t\trl.close();\n\t\t\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst stopChild = (dueToLimit = false) => {\n\t\t\t\t\t\t\tif (!child.killed) {\n\t\t\t\t\t\t\t\tkilledDueToLimit = dueToLimit;\n\t\t\t\t\t\t\t\tchild.kill();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst onAbort = () => {\n\t\t\t\t\t\t\taborted = true;\n\t\t\t\t\t\t\tstopChild();\n\t\t\t\t\t\t};\n\t\t\t\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\t\t\t\tchild.stderr?.on(\"data\", (chunk) => {\n\t\t\t\t\t\t\tstderr += chunk.toString();\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\t// Format a context block around a match line. Path is omitted (emitted once per file).\n\t\t\t\t\t\tconst formatBlock = async (filePath: string, lineNumber: number): Promise<string[]> => {\n\t\t\t\t\t\t\tconst lines = await getFileLines(filePath);\n\t\t\t\t\t\t\tif (!lines.length) return [` ${lineNumber}: (unable to read file)`];\n\t\t\t\t\t\t\tconst block: string[] = [];\n\t\t\t\t\t\t\tconst start = contextValue > 0 ? Math.max(1, lineNumber - contextValue) : lineNumber;\n\t\t\t\t\t\t\tconst end = contextValue > 0 ? Math.min(lines.length, lineNumber + contextValue) : lineNumber;\n\t\t\t\t\t\t\tfor (let current = start; current <= end; current++) {\n\t\t\t\t\t\t\t\tconst lineText = lines[current - 1] ?? \"\";\n\t\t\t\t\t\t\t\tconst sanitized = lineText.replace(/\\r/g, \"\");\n\t\t\t\t\t\t\t\tconst isMatchLine = current === lineNumber;\n\t\t\t\t\t\t\t\t// Truncate long lines so grep output stays compact.\n\t\t\t\t\t\t\t\tconst { text: truncatedText, wasTruncated } = truncateLine(sanitized);\n\t\t\t\t\t\t\t\tif (wasTruncated) linesTruncated = true;\n\t\t\t\t\t\t\t\tif (isMatchLine) block.push(`${current}: ${truncatedText}`);\n\t\t\t\t\t\t\t\telse block.push(`${current}- ${truncatedText}`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn block;\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\t// Collect matches during streaming, then format them after rg exits.\n\t\t\t\t\t\tconst matches: Array<{ filePath: string; lineNumber: number; lineText?: string }> = [];\n\t\t\t\t\t\trl.on(\"line\", (line) => {\n\t\t\t\t\t\t\tif (!line.trim() || matchCount >= effectiveLimit) return;\n\t\t\t\t\t\t\tlet event: any;\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tevent = JSON.parse(line);\n\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (event.type === \"match\") {\n\t\t\t\t\t\t\t\tmatchCount++;\n\t\t\t\t\t\t\t\tconst filePath = event.data?.path?.text;\n\t\t\t\t\t\t\t\tconst lineNumber = event.data?.line_number;\n\t\t\t\t\t\t\t\tconst lineText = event.data?.lines?.text;\n\t\t\t\t\t\t\t\tif (filePath && typeof lineNumber === \"number\")\n\t\t\t\t\t\t\t\t\tmatches.push({ filePath, lineNumber, lineText });\n\t\t\t\t\t\t\t\tif (matchCount >= effectiveLimit) {\n\t\t\t\t\t\t\t\t\tmatchLimitReached = true;\n\t\t\t\t\t\t\t\t\tstopChild(true);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tchild.on(\"error\", (error) => {\n\t\t\t\t\t\t\tcleanup();\n\t\t\t\t\t\t\tsettle(() => reject(new Error(`Failed to run ripgrep: ${error.message}`)));\n\t\t\t\t\t\t});\n\t\t\t\t\t\tchild.on(\"close\", async (code) => {\n\t\t\t\t\t\t\tcleanup();\n\t\t\t\t\t\t\tif (aborted) {\n\t\t\t\t\t\t\t\tsettle(() => reject(new Error(\"Operation aborted\")));\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (!killedDueToLimit && code !== 0 && code !== 1) {\n\t\t\t\t\t\t\t\tconst trimmedStderr = stderr.trim();\n\t\t\t\t\t\t\t\tlet errorMsg = trimmedStderr || `ripgrep exited with code ${code}`;\n\t\t\t\t\t\t\t\t// A regex parse error means the pattern is not a valid regex. Point the\n\t\t\t\t\t\t\t\t// caller at the `literal` option so they can search the text verbatim.\n\t\t\t\t\t\t\t\tif (!literal && /regex parse error/i.test(trimmedStderr)) {\n\t\t\t\t\t\t\t\t\terrorMsg += `\\n\\nThe pattern is not a valid regex. To search for it as plain text, pass literal: true (or escape the regex metacharacters).`;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tsettle(() => reject(new Error(errorMsg)));\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (matchCount === 0) {\n\t\t\t\t\t\t\t\tsettle(() =>\n\t\t\t\t\t\t\t\t\tresolve({ content: [{ type: \"text\", text: \"No matches found\" }], details: undefined }),\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Group matches by file path, showing filename once per file.\n\t\t\t\t\t\t\tconst fileGroups = new Map<string, typeof matches>();\n\t\t\t\t\t\t\tfor (const match of matches) {\n\t\t\t\t\t\t\t\tlet group = fileGroups.get(match.filePath);\n\t\t\t\t\t\t\t\tif (!group) {\n\t\t\t\t\t\t\t\t\tgroup = [];\n\t\t\t\t\t\t\t\t\tfileGroups.set(match.filePath, group);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tgroup.push(match);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tfor (const [filePath, fileMatches] of fileGroups) {\n\t\t\t\t\t\t\t\tconst relativePath = formatPath(filePath);\n\t\t\t\t\t\t\t\toutputLines.push(relativePath);\n\t\t\t\t\t\t\t\tfor (const match of fileMatches) {\n\t\t\t\t\t\t\t\t\tif (contextValue === 0 && match.lineText !== undefined) {\n\t\t\t\t\t\t\t\t\t\tconst sanitized = match.lineText\n\t\t\t\t\t\t\t\t\t\t\t.replace(/\\r\\n/g, \"\\n\")\n\t\t\t\t\t\t\t\t\t\t\t.replace(/\\r/g, \"\")\n\t\t\t\t\t\t\t\t\t\t\t.replace(/\\n$/, \"\");\n\t\t\t\t\t\t\t\t\t\tconst { text: truncatedText, wasTruncated } = truncateLine(sanitized);\n\t\t\t\t\t\t\t\t\t\tif (wasTruncated) linesTruncated = true;\n\t\t\t\t\t\t\t\t\t\toutputLines.push(`${match.lineNumber}: ${truncatedText}`);\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tconst block = await formatBlock(match.filePath, match.lineNumber);\n\t\t\t\t\t\t\t\t\t\toutputLines.push(...block);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tconst rawOutput = outputLines.join(\"\\n\");\n\t\t\t\t\t\t\t// Apply lossless compression then byte truncation.\n\t\t\t\t\t\t\tconst compressedOutput = compressGrepOutput(rawOutput);\n\t\t\t\t\t\t\t// Apply byte truncation. There is no line limit here because the match limit already capped rows.\n\t\t\t\t\t\t\tconst truncation = truncateHead(compressedOutput, { maxLines: Number.MAX_SAFE_INTEGER });\n\t\t\t\t\t\t\tlet output = truncation.content;\n\t\t\t\t\t\t\tconst details: GrepToolDetails = {};\n\t\t\t\t\t\t\t// Build actionable notices for truncation and match limits.\n\t\t\t\t\t\t\tconst notices: string[] = [];\n\t\t\t\t\t\t\tif (matchLimitReached) {\n\t\t\t\t\t\t\t\tnotices.push(\n\t\t\t\t\t\t\t\t\t`${effectiveLimit} matches limit reached. Use limit=${effectiveLimit * 2} for more, or refine pattern`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tdetails.matchLimitReached = effectiveLimit;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (truncation.truncated) {\n\t\t\t\t\t\t\t\tnotices.push(`${formatSize(DEFAULT_MAX_BYTES)} limit reached`);\n\t\t\t\t\t\t\t\tdetails.truncation = truncation;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (linesTruncated) {\n\t\t\t\t\t\t\t\tnotices.push(\n\t\t\t\t\t\t\t\t\t`Some lines truncated to ${GREP_MAX_LINE_LENGTH} chars. Use read tool to see full lines`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tdetails.linesTruncated = true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (notices.length > 0) output += `\\n\\n[${notices.join(\". \")}]`;\n\t\t\t\t\t\t\tsettle(() =>\n\t\t\t\t\t\t\t\tresolve({\n\t\t\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: output }],\n\t\t\t\t\t\t\t\t\tdetails: Object.keys(details).length > 0 ? details : undefined,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t});\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tsettle(() => reject(err as Error));\n\t\t\t\t\t}\n\t\t\t\t})();\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(formatGrepCall(args, theme));\n\t\t\treturn text;\n\t\t},\n\t\trenderResult(result, options, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatGrepResult(result as any, options, theme, context.showImages));\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport function createGrepTool(cwd: string, options?: GrepToolOptions): AgentTool<typeof grepSchema> {\n\treturn wrapToolDefinition(createGrepToolDefinition(cwd, options));\n}\n"]}
|