@caupulican/pi-adaptative 0.80.85 → 0.80.88
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 +160 -1
- package/dist/core/agent-session.d.ts +394 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +1862 -46
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/autonomy/approval-gate.d.ts +4 -0
- package/dist/core/autonomy/approval-gate.d.ts.map +1 -0
- package/dist/core/autonomy/approval-gate.js +27 -0
- package/dist/core/autonomy/approval-gate.js.map +1 -0
- package/dist/core/autonomy/bounded-completion.d.ts +27 -0
- package/dist/core/autonomy/bounded-completion.d.ts.map +1 -0
- package/dist/core/autonomy/bounded-completion.js +44 -0
- package/dist/core/autonomy/bounded-completion.js.map +1 -0
- package/dist/core/autonomy/contracts.d.ts +129 -0
- package/dist/core/autonomy/contracts.d.ts.map +1 -0
- package/dist/core/autonomy/contracts.js +2 -0
- package/dist/core/autonomy/contracts.js.map +1 -0
- package/dist/core/autonomy/gates.d.ts +15 -0
- package/dist/core/autonomy/gates.d.ts.map +1 -0
- package/dist/core/autonomy/gates.js +205 -0
- package/dist/core/autonomy/gates.js.map +1 -0
- package/dist/core/autonomy/lane-tracker.d.ts +48 -0
- package/dist/core/autonomy/lane-tracker.d.ts.map +1 -0
- package/dist/core/autonomy/lane-tracker.js +125 -0
- package/dist/core/autonomy/lane-tracker.js.map +1 -0
- package/dist/core/autonomy/path-scope.d.ts +9 -0
- package/dist/core/autonomy/path-scope.d.ts.map +1 -0
- package/dist/core/autonomy/path-scope.js +122 -0
- package/dist/core/autonomy/path-scope.js.map +1 -0
- package/dist/core/autonomy/risk-assessment.d.ts +3 -0
- package/dist/core/autonomy/risk-assessment.d.ts.map +1 -0
- package/dist/core/autonomy/risk-assessment.js +122 -0
- package/dist/core/autonomy/risk-assessment.js.map +1 -0
- package/dist/core/autonomy/session-lane-record.d.ts +10 -0
- package/dist/core/autonomy/session-lane-record.d.ts.map +1 -0
- package/dist/core/autonomy/session-lane-record.js +36 -0
- package/dist/core/autonomy/session-lane-record.js.map +1 -0
- package/dist/core/autonomy/status.d.ts +40 -0
- package/dist/core/autonomy/status.d.ts.map +1 -0
- package/dist/core/autonomy/status.js +107 -0
- package/dist/core/autonomy/status.js.map +1 -0
- package/dist/core/autonomy/subagent-prompt.d.ts +21 -0
- package/dist/core/autonomy/subagent-prompt.d.ts.map +1 -0
- package/dist/core/autonomy/subagent-prompt.js +28 -0
- package/dist/core/autonomy/subagent-prompt.js.map +1 -0
- package/dist/core/autonomy/telemetry-events.d.ts +18 -0
- package/dist/core/autonomy/telemetry-events.d.ts.map +1 -0
- package/dist/core/autonomy/telemetry-events.js +60 -0
- package/dist/core/autonomy/telemetry-events.js.map +1 -0
- package/dist/core/context/artifact-retrieval.d.ts +49 -0
- package/dist/core/context/artifact-retrieval.d.ts.map +1 -0
- package/dist/core/context/artifact-retrieval.js +49 -0
- package/dist/core/context/artifact-retrieval.js.map +1 -0
- package/dist/core/context/context-artifacts.d.ts +94 -0
- package/dist/core/context/context-artifacts.d.ts.map +1 -0
- package/dist/core/context/context-artifacts.js +307 -0
- package/dist/core/context/context-artifacts.js.map +1 -0
- package/dist/core/context/context-audit.d.ts +66 -0
- package/dist/core/context/context-audit.d.ts.map +1 -0
- package/dist/core/context/context-audit.js +173 -0
- package/dist/core/context/context-audit.js.map +1 -0
- package/dist/core/context/context-item.d.ts +117 -0
- package/dist/core/context/context-item.d.ts.map +1 -0
- package/dist/core/context/context-item.js +36 -0
- package/dist/core/context/context-item.js.map +1 -0
- package/dist/core/context/context-prompt-enforcement.d.ts +73 -0
- package/dist/core/context/context-prompt-enforcement.d.ts.map +1 -0
- package/dist/core/context/context-prompt-enforcement.js +153 -0
- package/dist/core/context/context-prompt-enforcement.js.map +1 -0
- package/dist/core/context/context-prompt-policy.d.ts +90 -0
- package/dist/core/context/context-prompt-policy.d.ts.map +1 -0
- package/dist/core/context/context-prompt-policy.js +73 -0
- package/dist/core/context/context-prompt-policy.js.map +1 -0
- package/dist/core/context/context-retention.d.ts +36 -0
- package/dist/core/context/context-retention.d.ts.map +1 -0
- package/dist/core/context/context-retention.js +108 -0
- package/dist/core/context/context-retention.js.map +1 -0
- package/dist/core/context/context-store.d.ts +37 -0
- package/dist/core/context/context-store.d.ts.map +1 -0
- package/dist/core/context/context-store.js +45 -0
- package/dist/core/context/context-store.js.map +1 -0
- package/dist/core/context/memory-diagnostics.d.ts +50 -0
- package/dist/core/context/memory-diagnostics.d.ts.map +1 -0
- package/dist/core/context/memory-diagnostics.js +43 -0
- package/dist/core/context/memory-diagnostics.js.map +1 -0
- package/dist/core/context/memory-index-store.d.ts +28 -0
- package/dist/core/context/memory-index-store.d.ts.map +1 -0
- package/dist/core/context/memory-index-store.js +38 -0
- package/dist/core/context/memory-index-store.js.map +1 -0
- package/dist/core/context/memory-prompt-block.d.ts +34 -0
- package/dist/core/context/memory-prompt-block.d.ts.map +1 -0
- package/dist/core/context/memory-prompt-block.js +58 -0
- package/dist/core/context/memory-prompt-block.js.map +1 -0
- package/dist/core/context/memory-provider-contract.d.ts +114 -0
- package/dist/core/context/memory-provider-contract.d.ts.map +1 -0
- package/dist/core/context/memory-provider-contract.js +121 -0
- package/dist/core/context/memory-provider-contract.js.map +1 -0
- package/dist/core/context/memory-retrieval.d.ts +27 -0
- package/dist/core/context/memory-retrieval.d.ts.map +1 -0
- package/dist/core/context/memory-retrieval.js +91 -0
- package/dist/core/context/memory-retrieval.js.map +1 -0
- package/dist/core/context/okf-memory-provider.d.ts +26 -0
- package/dist/core/context/okf-memory-provider.d.ts.map +1 -0
- package/dist/core/context/okf-memory-provider.js +154 -0
- package/dist/core/context/okf-memory-provider.js.map +1 -0
- package/dist/core/context/okf-memory.d.ts +42 -0
- package/dist/core/context/okf-memory.d.ts.map +1 -0
- package/dist/core/context/okf-memory.js +175 -0
- package/dist/core/context/okf-memory.js.map +1 -0
- package/dist/core/context/policy-engine.d.ts +66 -0
- package/dist/core/context/policy-engine.d.ts.map +1 -0
- package/dist/core/context/policy-engine.js +171 -0
- package/dist/core/context/policy-engine.js.map +1 -0
- package/dist/core/context/policy-types.d.ts +102 -0
- package/dist/core/context/policy-types.d.ts.map +1 -0
- package/dist/core/context/policy-types.js +7 -0
- package/dist/core/context/policy-types.js.map +1 -0
- package/dist/core/context/sqlite-runtime-index.d.ts +19 -0
- package/dist/core/context/sqlite-runtime-index.d.ts.map +1 -0
- package/dist/core/context/sqlite-runtime-index.js +344 -0
- package/dist/core/context/sqlite-runtime-index.js.map +1 -0
- package/dist/core/context/storage-authority.d.ts +20 -0
- package/dist/core/context/storage-authority.d.ts.map +1 -0
- package/dist/core/context/storage-authority.js +51 -0
- package/dist/core/context/storage-authority.js.map +1 -0
- package/dist/core/context/tool-output-packer.d.ts +75 -0
- package/dist/core/context/tool-output-packer.d.ts.map +1 -0
- package/dist/core/context/tool-output-packer.js +77 -0
- package/dist/core/context/tool-output-packer.js.map +1 -0
- package/dist/core/cost/session-usage.d.ts +20 -0
- package/dist/core/cost/session-usage.d.ts.map +1 -0
- package/dist/core/cost/session-usage.js +164 -0
- package/dist/core/cost/session-usage.js.map +1 -0
- package/dist/core/delegation/session-worker-result.d.ts +10 -0
- package/dist/core/delegation/session-worker-result.d.ts.map +1 -0
- package/dist/core/delegation/session-worker-result.js +36 -0
- package/dist/core/delegation/session-worker-result.js.map +1 -0
- package/dist/core/delegation/worker-result.d.ts +9 -0
- package/dist/core/delegation/worker-result.d.ts.map +1 -0
- package/dist/core/delegation/worker-result.js +152 -0
- package/dist/core/delegation/worker-result.js.map +1 -0
- package/dist/core/delegation/worker-runner.d.ts +58 -0
- package/dist/core/delegation/worker-runner.d.ts.map +1 -0
- package/dist/core/delegation/worker-runner.js +188 -0
- package/dist/core/delegation/worker-runner.js.map +1 -0
- package/dist/core/extensions/builtin.d.ts +5 -1
- package/dist/core/extensions/builtin.d.ts.map +1 -1
- package/dist/core/extensions/builtin.js +23 -1
- package/dist/core/extensions/builtin.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts +5 -1
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +13 -0
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/goals/goal-continuation-controller.d.ts +22 -0
- package/dist/core/goals/goal-continuation-controller.d.ts.map +1 -0
- package/dist/core/goals/goal-continuation-controller.js +88 -0
- package/dist/core/goals/goal-continuation-controller.js.map +1 -0
- package/dist/core/goals/goal-continuation-defaults.d.ts +10 -0
- package/dist/core/goals/goal-continuation-defaults.d.ts.map +1 -0
- package/dist/core/goals/goal-continuation-defaults.js +10 -0
- package/dist/core/goals/goal-continuation-defaults.js.map +1 -0
- package/dist/core/goals/goal-continuation-prompt.d.ts +18 -0
- package/dist/core/goals/goal-continuation-prompt.d.ts.map +1 -0
- package/dist/core/goals/goal-continuation-prompt.js +141 -0
- package/dist/core/goals/goal-continuation-prompt.js.map +1 -0
- package/dist/core/goals/goal-runtime-snapshot.d.ts +19 -0
- package/dist/core/goals/goal-runtime-snapshot.d.ts.map +1 -0
- package/dist/core/goals/goal-runtime-snapshot.js +23 -0
- package/dist/core/goals/goal-runtime-snapshot.js.map +1 -0
- package/dist/core/goals/goal-state.d.ts +87 -0
- package/dist/core/goals/goal-state.d.ts.map +1 -0
- package/dist/core/goals/goal-state.js +259 -0
- package/dist/core/goals/goal-state.js.map +1 -0
- package/dist/core/goals/goal-tool-core.d.ts +66 -0
- package/dist/core/goals/goal-tool-core.d.ts.map +1 -0
- package/dist/core/goals/goal-tool-core.js +146 -0
- package/dist/core/goals/goal-tool-core.js.map +1 -0
- package/dist/core/goals/session-goal-state.d.ts +10 -0
- package/dist/core/goals/session-goal-state.d.ts.map +1 -0
- package/dist/core/goals/session-goal-state.js +35 -0
- package/dist/core/goals/session-goal-state.js.map +1 -0
- package/dist/core/learning/learning-audit.d.ts +45 -0
- package/dist/core/learning/learning-audit.d.ts.map +1 -0
- package/dist/core/learning/learning-audit.js +139 -0
- package/dist/core/learning/learning-audit.js.map +1 -0
- package/dist/core/learning/learning-gate.d.ts +29 -0
- package/dist/core/learning/learning-gate.d.ts.map +1 -0
- package/dist/core/learning/learning-gate.js +150 -0
- package/dist/core/learning/learning-gate.js.map +1 -0
- package/dist/core/learning/session-learning-decision.d.ts +10 -0
- package/dist/core/learning/session-learning-decision.d.ts.map +1 -0
- package/dist/core/learning/session-learning-decision.js +36 -0
- package/dist/core/learning/session-learning-decision.js.map +1 -0
- package/dist/core/model-capability.d.ts +41 -0
- package/dist/core/model-capability.d.ts.map +1 -0
- package/dist/core/model-capability.js +101 -0
- package/dist/core/model-capability.js.map +1 -0
- package/dist/core/model-router/config-diagnostics.d.ts.map +1 -1
- package/dist/core/model-router/config-diagnostics.js +1 -0
- package/dist/core/model-router/config-diagnostics.js.map +1 -1
- package/dist/core/model-router/intent-classifier.d.ts +2 -0
- package/dist/core/model-router/intent-classifier.d.ts.map +1 -1
- package/dist/core/model-router/intent-classifier.js +154 -9
- package/dist/core/model-router/intent-classifier.js.map +1 -1
- package/dist/core/model-router/route-judge.d.ts +54 -0
- package/dist/core/model-router/route-judge.d.ts.map +1 -0
- package/dist/core/model-router/route-judge.js +128 -0
- package/dist/core/model-router/route-judge.js.map +1 -0
- package/dist/core/model-router/status.d.ts +4 -1
- package/dist/core/model-router/status.d.ts.map +1 -1
- package/dist/core/model-router/status.js +30 -6
- package/dist/core/model-router/status.js.map +1 -1
- package/dist/core/model-router/tool-escalation.d.ts +4 -6
- package/dist/core/model-router/tool-escalation.d.ts.map +1 -1
- package/dist/core/model-router/tool-escalation.js +1 -1
- package/dist/core/model-router/tool-escalation.js.map +1 -1
- package/dist/core/models/fitness-store.d.ts +40 -0
- package/dist/core/models/fitness-store.d.ts.map +1 -0
- package/dist/core/models/fitness-store.js +61 -0
- package/dist/core/models/fitness-store.js.map +1 -0
- package/dist/core/profile-registry.d.ts.map +1 -1
- package/dist/core/profile-registry.js +1 -1
- package/dist/core/profile-registry.js.map +1 -1
- package/dist/core/prompt-templates.d.ts +2 -0
- package/dist/core/prompt-templates.d.ts.map +1 -1
- package/dist/core/prompt-templates.js +12 -4
- package/dist/core/prompt-templates.js.map +1 -1
- package/dist/core/research/automata-provider.d.ts +5 -0
- package/dist/core/research/automata-provider.d.ts.map +1 -0
- package/dist/core/research/automata-provider.js +15 -0
- package/dist/core/research/automata-provider.js.map +1 -0
- package/dist/core/research/evidence-bundle.d.ts +10 -0
- package/dist/core/research/evidence-bundle.d.ts.map +1 -0
- package/dist/core/research/evidence-bundle.js +116 -0
- package/dist/core/research/evidence-bundle.js.map +1 -0
- package/dist/core/research/model-fitness.d.ts +79 -0
- package/dist/core/research/model-fitness.d.ts.map +1 -0
- package/dist/core/research/model-fitness.js +257 -0
- package/dist/core/research/model-fitness.js.map +1 -0
- package/dist/core/research/research-gate.d.ts +11 -0
- package/dist/core/research/research-gate.d.ts.map +1 -0
- package/dist/core/research/research-gate.js +82 -0
- package/dist/core/research/research-gate.js.map +1 -0
- package/dist/core/research/research-runner.d.ts +59 -0
- package/dist/core/research/research-runner.d.ts.map +1 -0
- package/dist/core/research/research-runner.js +155 -0
- package/dist/core/research/research-runner.js.map +1 -0
- package/dist/core/research/session-evidence-bundle.d.ts +11 -0
- package/dist/core/research/session-evidence-bundle.d.ts.map +1 -0
- package/dist/core/research/session-evidence-bundle.js +55 -0
- package/dist/core/research/session-evidence-bundle.js.map +1 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +7 -1
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/settings-manager.d.ts +147 -4
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +285 -9
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/skills.d.ts +4 -0
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +18 -6
- package/dist/core/skills.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +4 -0
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/toolkit/script-registry.d.ts +34 -0
- package/dist/core/toolkit/script-registry.d.ts.map +1 -0
- package/dist/core/toolkit/script-registry.js +71 -0
- package/dist/core/toolkit/script-registry.js.map +1 -0
- package/dist/core/toolkit/script-runner.d.ts +28 -0
- package/dist/core/toolkit/script-runner.d.ts.map +1 -0
- package/dist/core/toolkit/script-runner.js +48 -0
- package/dist/core/toolkit/script-runner.js.map +1 -0
- package/dist/core/tools/artifact-retrieve.d.ts +23 -0
- package/dist/core/tools/artifact-retrieve.d.ts.map +1 -0
- package/dist/core/tools/artifact-retrieve.js +110 -0
- package/dist/core/tools/artifact-retrieve.js.map +1 -0
- package/dist/core/tools/delegate.d.ts +32 -0
- package/dist/core/tools/delegate.d.ts.map +1 -0
- package/dist/core/tools/delegate.js +60 -0
- package/dist/core/tools/delegate.js.map +1 -0
- package/dist/core/tools/fff-search-backend.d.ts +103 -0
- package/dist/core/tools/fff-search-backend.d.ts.map +1 -0
- package/dist/core/tools/fff-search-backend.js +151 -0
- package/dist/core/tools/fff-search-backend.js.map +1 -0
- package/dist/core/tools/find.d.ts +21 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +183 -10
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/goal.d.ts +35 -0
- package/dist/core/tools/goal.d.ts.map +1 -0
- package/dist/core/tools/goal.js +122 -0
- package/dist/core/tools/goal.js.map +1 -0
- package/dist/core/tools/grep.d.ts +21 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +272 -27
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/index.d.ts +4 -1
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +9 -0
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/model-fitness.d.ts +30 -0
- package/dist/core/tools/model-fitness.d.ts.map +1 -0
- package/dist/core/tools/model-fitness.js +38 -0
- package/dist/core/tools/model-fitness.js.map +1 -0
- package/dist/core/tools/run-toolkit-script.d.ts +24 -0
- package/dist/core/tools/run-toolkit-script.d.ts.map +1 -0
- package/dist/core/tools/run-toolkit-script.js +103 -0
- package/dist/core/tools/run-toolkit-script.js.map +1 -0
- package/dist/core/tools/search-router.d.ts +75 -0
- package/dist/core/tools/search-router.d.ts.map +1 -0
- package/dist/core/tools/search-router.js +85 -0
- package/dist/core/tools/search-router.js.map +1 -0
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +18 -16
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +13 -1
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +471 -11
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +4 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +220 -39
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +3 -0
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/utils/tools-manager.d.ts +2 -0
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +154 -2
- package/dist/utils/tools-manager.js.map +1 -1
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- 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-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +368 -12
- package/package.json +5 -4
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { readdir } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { ensureFffNodePackage, loadAvailableFffNodePackage } from "../../utils/tools-manager.js";
|
|
4
|
+
const DEFAULT_WAIT_FOR_SCAN_MS = 15_000;
|
|
5
|
+
const MAX_FINDER_CACHE_SIZE = 8;
|
|
6
|
+
const FFF_GITIGNORE_SKIP_DIRS = new Set([".git", "node_modules"]);
|
|
7
|
+
let loadedFffModule;
|
|
8
|
+
function isRecord(value) {
|
|
9
|
+
return Boolean(value) && typeof value === "object";
|
|
10
|
+
}
|
|
11
|
+
function hasProperties(value) {
|
|
12
|
+
return Boolean(value) && (typeof value === "object" || typeof value === "function");
|
|
13
|
+
}
|
|
14
|
+
function isFffResult(value) {
|
|
15
|
+
if (!isRecord(value))
|
|
16
|
+
return false;
|
|
17
|
+
return value.ok === true || value.ok === false;
|
|
18
|
+
}
|
|
19
|
+
function isFffModule(value) {
|
|
20
|
+
if (!isRecord(value))
|
|
21
|
+
return false;
|
|
22
|
+
const fileFinder = value.FileFinder;
|
|
23
|
+
return hasProperties(fileFinder) && typeof fileFinder.create === "function";
|
|
24
|
+
}
|
|
25
|
+
export function loadFffModule(requires) {
|
|
26
|
+
if (requires) {
|
|
27
|
+
for (const requireFff of requires) {
|
|
28
|
+
try {
|
|
29
|
+
const loaded = requireFff("@ff-labs/fff-node");
|
|
30
|
+
if (isFffModule(loaded))
|
|
31
|
+
return loaded;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// Try the next resolution root.
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
if (loadedFffModule !== undefined)
|
|
40
|
+
return loadedFffModule;
|
|
41
|
+
const loaded = loadAvailableFffNodePackage();
|
|
42
|
+
loadedFffModule = isFffModule(loaded) ? loaded : null;
|
|
43
|
+
return loadedFffModule;
|
|
44
|
+
}
|
|
45
|
+
async function ensureFffModule() {
|
|
46
|
+
const loaded = loadFffModule();
|
|
47
|
+
if (loaded)
|
|
48
|
+
return loaded;
|
|
49
|
+
const installed = await ensureFffNodePackage(true);
|
|
50
|
+
loadedFffModule = isFffModule(installed) ? installed : null;
|
|
51
|
+
return loadedFffModule;
|
|
52
|
+
}
|
|
53
|
+
function isFffRuntimeDisabled() {
|
|
54
|
+
const value = process.env.PI_FFF_DISABLED ?? process.env.PI_SEARCH_BACKEND;
|
|
55
|
+
if (!value)
|
|
56
|
+
return false;
|
|
57
|
+
return value === "1" || value.toLowerCase() === "true" || value.toLowerCase() === "disabled";
|
|
58
|
+
}
|
|
59
|
+
function isRootScanningEnabled() {
|
|
60
|
+
const value = process.env.PI_FFF_ENABLE_ROOT_SCAN;
|
|
61
|
+
return value === "1" || value?.toLowerCase() === "true";
|
|
62
|
+
}
|
|
63
|
+
function destroyFinder(finder) {
|
|
64
|
+
if (finder && !finder.isDestroyed) {
|
|
65
|
+
finder.destroy();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export function relativePathInside(basePath, targetPath) {
|
|
69
|
+
const relative = path.relative(path.resolve(basePath), path.resolve(targetPath));
|
|
70
|
+
if (relative === "")
|
|
71
|
+
return "";
|
|
72
|
+
if (relative.startsWith("..") || path.isAbsolute(relative))
|
|
73
|
+
return undefined;
|
|
74
|
+
return relative.split(path.sep).join("/");
|
|
75
|
+
}
|
|
76
|
+
export async function hasGitignoreInTree(rootPath) {
|
|
77
|
+
const stack = [path.resolve(rootPath)];
|
|
78
|
+
while (stack.length > 0) {
|
|
79
|
+
const current = stack.pop();
|
|
80
|
+
if (!current)
|
|
81
|
+
continue;
|
|
82
|
+
let entries;
|
|
83
|
+
try {
|
|
84
|
+
entries = await readdir(current, { withFileTypes: true });
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
for (const entry of entries) {
|
|
90
|
+
if (entry.isFile() && entry.name === ".gitignore")
|
|
91
|
+
return true;
|
|
92
|
+
if (entry.isDirectory() && !FFF_GITIGNORE_SKIP_DIRS.has(entry.name)) {
|
|
93
|
+
stack.push(path.join(current, entry.name));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
class DefaultFffSearchBackend {
|
|
100
|
+
finders = new Map();
|
|
101
|
+
async getFinder(basePath) {
|
|
102
|
+
if (isFffRuntimeDisabled())
|
|
103
|
+
return undefined;
|
|
104
|
+
const normalizedBasePath = path.resolve(basePath);
|
|
105
|
+
const cached = this.finders.get(normalizedBasePath);
|
|
106
|
+
if (cached)
|
|
107
|
+
return cached;
|
|
108
|
+
const created = this.createFinder(normalizedBasePath);
|
|
109
|
+
this.finders.set(normalizedBasePath, created);
|
|
110
|
+
this.evictIfNeeded();
|
|
111
|
+
return created;
|
|
112
|
+
}
|
|
113
|
+
evictIfNeeded() {
|
|
114
|
+
while (this.finders.size > MAX_FINDER_CACHE_SIZE) {
|
|
115
|
+
const firstKey = this.finders.keys().next().value;
|
|
116
|
+
if (!firstKey)
|
|
117
|
+
return;
|
|
118
|
+
const first = this.finders.get(firstKey);
|
|
119
|
+
this.finders.delete(firstKey);
|
|
120
|
+
void first?.then(destroyFinder, () => undefined);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
async createFinder(basePath) {
|
|
124
|
+
let fff = await ensureFffModule();
|
|
125
|
+
if (!fff)
|
|
126
|
+
return undefined;
|
|
127
|
+
if (fff.FileFinder.isAvailable && !fff.FileFinder.isAvailable()) {
|
|
128
|
+
const installed = await ensureFffNodePackage(true, true);
|
|
129
|
+
loadedFffModule = isFffModule(installed) ? installed : null;
|
|
130
|
+
fff = loadedFffModule;
|
|
131
|
+
if (!fff || (fff.FileFinder.isAvailable && !fff.FileFinder.isAvailable()))
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
const created = fff.FileFinder.create({
|
|
135
|
+
basePath,
|
|
136
|
+
aiMode: true,
|
|
137
|
+
enableHomeDirScanning: true,
|
|
138
|
+
enableFsRootScanning: isRootScanningEnabled(),
|
|
139
|
+
});
|
|
140
|
+
if (!isFffResult(created) || !created.ok)
|
|
141
|
+
return undefined;
|
|
142
|
+
const scan = await created.value.waitForScan(DEFAULT_WAIT_FOR_SCAN_MS);
|
|
143
|
+
if (!scan.ok) {
|
|
144
|
+
destroyFinder(created.value);
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
return created.value;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
export const defaultFffSearchBackend = new DefaultFffSearchBackend();
|
|
151
|
+
//# sourceMappingURL=fff-search-backend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fff-search-backend.js","sourceRoot":"","sources":["../../../src/core/tools/fff-search-backend.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Dirent } from \"node:fs\";\nimport { readdir } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { ensureFffNodePackage, loadAvailableFffNodePackage } from \"../../utils/tools-manager.ts\";\n\nexport type FffResult<T> = { ok: true; value: T } | { ok: false; error: string };\nexport type FffGrepMode = \"plain\" | \"regex\" | \"fuzzy\";\n\nexport interface FffFileItem {\n\trelativePath: string;\n\tfileName: string;\n\tsize: number;\n\tmodified: number;\n\tgitStatus: string;\n\taccessFrecencyScore?: number;\n\tmodificationFrecencyScore?: number;\n\ttotalFrecencyScore?: number;\n}\n\nexport interface FffScore {\n\ttotal: number;\n\tbaseScore: number;\n\tmatchType: string;\n}\n\nexport interface FffSearchResult {\n\titems: FffFileItem[];\n\tscores: FffScore[];\n\ttotalMatched: number;\n\ttotalFiles: number;\n}\n\nexport interface FffSearchOptions {\n\tpageIndex?: number;\n\tpageSize?: number;\n}\n\nexport interface FffGlobOptions {\n\tpageIndex?: number;\n\tpageSize?: number;\n}\n\nexport interface FffGrepOptions {\n\tmaxMatchesPerFile?: number;\n\tsmartCase?: boolean;\n\tmode?: FffGrepMode;\n\tbeforeContext?: number;\n\tafterContext?: number;\n\tpageSize?: number;\n}\n\nexport interface FffGrepMatch {\n\trelativePath: string;\n\tfileName: string;\n\tgitStatus: string;\n\tsize: number;\n\tmodified: number;\n\tisBinary: boolean;\n\ttotalFrecencyScore: number;\n\taccessFrecencyScore: number;\n\tmodificationFrecencyScore: number;\n\tlineNumber: number;\n\tcol: number;\n\tbyteOffset: number;\n\tlineContent: string;\n\tmatchRanges: [number, number][];\n\tcontextBefore?: string[];\n\tcontextAfter?: string[];\n}\n\nexport interface FffGrepResult {\n\titems: FffGrepMatch[];\n\ttotalMatched: number;\n\ttotalFilesSearched: number;\n\ttotalFiles: number;\n\tfilteredFileCount: number;\n\tnextCursor: unknown | null;\n\tregexFallbackError?: string;\n}\n\nexport interface FffFileFinder {\n\treadonly isDestroyed: boolean;\n\tdestroy(): void;\n\tfileSearch(query: string, options?: FffSearchOptions): FffResult<FffSearchResult>;\n\tglob(pattern: string, options?: FffGlobOptions): FffResult<FffSearchResult>;\n\tgrep(query: string, options?: FffGrepOptions): FffResult<FffGrepResult>;\n\twaitForScan(timeoutMs?: number): Promise<FffResult<boolean>>;\n}\n\ninterface FffInitOptions {\n\tbasePath: string;\n\taiMode?: boolean;\n\tenableHomeDirScanning?: boolean;\n\tenableFsRootScanning?: boolean;\n}\n\ninterface FffFileFinderConstructor {\n\tcreate(options: FffInitOptions): FffResult<FffFileFinder>;\n\tisAvailable?: () => boolean;\n}\n\ninterface FffModule {\n\tFileFinder: FffFileFinderConstructor;\n}\n\nexport interface FffSearchBackend {\n\tgetFinder(basePath: string): Promise<FffFileFinder | undefined>;\n}\n\ntype ModuleRequire = (id: string) => unknown;\n\nconst DEFAULT_WAIT_FOR_SCAN_MS = 15_000;\nconst MAX_FINDER_CACHE_SIZE = 8;\nconst FFF_GITIGNORE_SKIP_DIRS = new Set([\".git\", \"node_modules\"]);\n\nlet loadedFffModule: FffModule | null | undefined;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn Boolean(value) && typeof value === \"object\";\n}\n\nfunction hasProperties(value: unknown): value is Record<string, unknown> {\n\treturn Boolean(value) && (typeof value === \"object\" || typeof value === \"function\");\n}\n\nfunction isFffResult<T>(value: unknown): value is FffResult<T> {\n\tif (!isRecord(value)) return false;\n\treturn value.ok === true || value.ok === false;\n}\n\nfunction isFffModule(value: unknown): value is FffModule {\n\tif (!isRecord(value)) return false;\n\tconst fileFinder = value.FileFinder;\n\treturn hasProperties(fileFinder) && typeof fileFinder.create === \"function\";\n}\n\nexport function loadFffModule(requires?: readonly ModuleRequire[]): FffModule | null {\n\tif (requires) {\n\t\tfor (const requireFff of requires) {\n\t\t\ttry {\n\t\t\t\tconst loaded = requireFff(\"@ff-labs/fff-node\");\n\t\t\t\tif (isFffModule(loaded)) return loaded;\n\t\t\t} catch {\n\t\t\t\t// Try the next resolution root.\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\tif (loadedFffModule !== undefined) return loadedFffModule;\n\tconst loaded = loadAvailableFffNodePackage();\n\tloadedFffModule = isFffModule(loaded) ? loaded : null;\n\treturn loadedFffModule;\n}\n\nasync function ensureFffModule(): Promise<FffModule | null> {\n\tconst loaded = loadFffModule();\n\tif (loaded) return loaded;\n\tconst installed = await ensureFffNodePackage(true);\n\tloadedFffModule = isFffModule(installed) ? installed : null;\n\treturn loadedFffModule;\n}\n\nfunction isFffRuntimeDisabled(): boolean {\n\tconst value = process.env.PI_FFF_DISABLED ?? process.env.PI_SEARCH_BACKEND;\n\tif (!value) return false;\n\treturn value === \"1\" || value.toLowerCase() === \"true\" || value.toLowerCase() === \"disabled\";\n}\n\nfunction isRootScanningEnabled(): boolean {\n\tconst value = process.env.PI_FFF_ENABLE_ROOT_SCAN;\n\treturn value === \"1\" || value?.toLowerCase() === \"true\";\n}\n\nfunction destroyFinder(finder: FffFileFinder | undefined): void {\n\tif (finder && !finder.isDestroyed) {\n\t\tfinder.destroy();\n\t}\n}\n\nexport function relativePathInside(basePath: string, targetPath: string): string | undefined {\n\tconst relative = path.relative(path.resolve(basePath), path.resolve(targetPath));\n\tif (relative === \"\") return \"\";\n\tif (relative.startsWith(\"..\") || path.isAbsolute(relative)) return undefined;\n\treturn relative.split(path.sep).join(\"/\");\n}\n\nexport async function hasGitignoreInTree(rootPath: string): Promise<boolean> {\n\tconst stack = [path.resolve(rootPath)];\n\twhile (stack.length > 0) {\n\t\tconst current = stack.pop();\n\t\tif (!current) continue;\n\n\t\tlet entries: Dirent[];\n\t\ttry {\n\t\t\tentries = await readdir(current, { withFileTypes: true });\n\t\t} catch {\n\t\t\treturn true;\n\t\t}\n\n\t\tfor (const entry of entries) {\n\t\t\tif (entry.isFile() && entry.name === \".gitignore\") return true;\n\t\t\tif (entry.isDirectory() && !FFF_GITIGNORE_SKIP_DIRS.has(entry.name)) {\n\t\t\t\tstack.push(path.join(current, entry.name));\n\t\t\t}\n\t\t}\n\t}\n\treturn false;\n}\n\nclass DefaultFffSearchBackend implements FffSearchBackend {\n\tprivate readonly finders = new Map<string, Promise<FffFileFinder | undefined>>();\n\n\tasync getFinder(basePath: string): Promise<FffFileFinder | undefined> {\n\t\tif (isFffRuntimeDisabled()) return undefined;\n\n\t\tconst normalizedBasePath = path.resolve(basePath);\n\t\tconst cached = this.finders.get(normalizedBasePath);\n\t\tif (cached) return cached;\n\n\t\tconst created = this.createFinder(normalizedBasePath);\n\t\tthis.finders.set(normalizedBasePath, created);\n\t\tthis.evictIfNeeded();\n\t\treturn created;\n\t}\n\n\tprivate evictIfNeeded(): void {\n\t\twhile (this.finders.size > MAX_FINDER_CACHE_SIZE) {\n\t\t\tconst firstKey = this.finders.keys().next().value;\n\t\t\tif (!firstKey) return;\n\t\t\tconst first = this.finders.get(firstKey);\n\t\t\tthis.finders.delete(firstKey);\n\t\t\tvoid first?.then(destroyFinder, () => undefined);\n\t\t}\n\t}\n\n\tprivate async createFinder(basePath: string): Promise<FffFileFinder | undefined> {\n\t\tlet fff = await ensureFffModule();\n\t\tif (!fff) return undefined;\n\t\tif (fff.FileFinder.isAvailable && !fff.FileFinder.isAvailable()) {\n\t\t\tconst installed = await ensureFffNodePackage(true, true);\n\t\t\tloadedFffModule = isFffModule(installed) ? installed : null;\n\t\t\tfff = loadedFffModule;\n\t\t\tif (!fff || (fff.FileFinder.isAvailable && !fff.FileFinder.isAvailable())) return undefined;\n\t\t}\n\n\t\tconst created = fff.FileFinder.create({\n\t\t\tbasePath,\n\t\t\taiMode: true,\n\t\t\tenableHomeDirScanning: true,\n\t\t\tenableFsRootScanning: isRootScanningEnabled(),\n\t\t});\n\t\tif (!isFffResult<FffFileFinder>(created) || !created.ok) return undefined;\n\n\t\tconst scan = await created.value.waitForScan(DEFAULT_WAIT_FOR_SCAN_MS);\n\t\tif (!scan.ok) {\n\t\t\tdestroyFinder(created.value);\n\t\t\treturn undefined;\n\t\t}\n\t\treturn created.value;\n\t}\n}\n\nexport const defaultFffSearchBackend: FffSearchBackend = new DefaultFffSearchBackend();\n"]}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { AgentTool } from "@caupulican/pi-agent-core";
|
|
2
2
|
import { type Static, Type } from "typebox";
|
|
3
|
+
import type { ArtifactStore } from "../context/context-artifacts.ts";
|
|
4
|
+
import { type BroadQueryTracker } from "../context/tool-output-packer.ts";
|
|
3
5
|
import type { ToolDefinition } from "../extensions/types.ts";
|
|
6
|
+
import { type FffSearchBackend } from "./fff-search-backend.ts";
|
|
7
|
+
import { type SearchRouter } from "./search-router.ts";
|
|
4
8
|
import { type TruncationResult } from "./truncate.ts";
|
|
5
9
|
declare const findSchema: Type.TObject<{
|
|
6
10
|
pattern: Type.TString;
|
|
@@ -12,6 +16,10 @@ export type FindToolInput = Static<typeof findSchema>;
|
|
|
12
16
|
export interface FindToolDetails {
|
|
13
17
|
truncation?: TruncationResult;
|
|
14
18
|
resultLimitReached?: number;
|
|
19
|
+
/** Set only when output was packed to an artifact; see tool-output-packer.ts. */
|
|
20
|
+
artifactId?: string;
|
|
21
|
+
/** Set when this exact query has repeatedly produced broad/truncated results. */
|
|
22
|
+
invalidationCandidate?: boolean;
|
|
15
23
|
}
|
|
16
24
|
/**
|
|
17
25
|
* Pluggable operations for the find tool.
|
|
@@ -27,8 +35,20 @@ export interface FindOperations {
|
|
|
27
35
|
}) => Promise<string[]> | string[];
|
|
28
36
|
}
|
|
29
37
|
export interface FindToolOptions {
|
|
30
|
-
/** Custom operations for find. Default: local filesystem plus fd */
|
|
38
|
+
/** Custom operations for find. Default: local filesystem plus routed FFF/fd search */
|
|
31
39
|
operations?: FindOperations;
|
|
40
|
+
/** FFF backend for resident indexed search. Set false to force fd fallback. */
|
|
41
|
+
fff?: FffSearchBackend | false;
|
|
42
|
+
/** Pure router that selects FFF or fd from request filters and environment facts. */
|
|
43
|
+
searchRouter?: SearchRouter;
|
|
44
|
+
/**
|
|
45
|
+
* Opt-in artifact store for first-capture-then-bound output packing (Phase 3). When
|
|
46
|
+
* omitted (the default), behavior is byte-for-byte unchanged from before this option
|
|
47
|
+
* existed: output is truncated the same way, just never artifact-backed.
|
|
48
|
+
*/
|
|
49
|
+
artifactStore?: ArtifactStore;
|
|
50
|
+
/** Opt-in tracker for repeated-broad-query "do not repeat" signals. Also default-off. */
|
|
51
|
+
broadQueryTracker?: BroadQueryTracker;
|
|
32
52
|
}
|
|
33
53
|
export declare function createFindToolDefinition(cwd: string, options?: FindToolOptions): ToolDefinition<typeof findSchema, FindToolDetails | undefined>;
|
|
34
54
|
export declare function createFindTool(cwd: string, options?: FindToolOptions): AgentTool<typeof findSchema>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"find.d.ts","sourceRoot":"","sources":["../../../src/core/tools/find.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAI3D,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAI5C,OAAO,KAAK,EAAE,cAAc,EAA2B,MAAM,wBAAwB,CAAC;AAItF,OAAO,EAAiC,KAAK,gBAAgB,EAAgB,MAAM,eAAe,CAAC;AAMnG,QAAA,MAAM,UAAU;;;;;EAQd,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,UAAU,CAAC,CAAC;AAItD,MAAM,WAAW,eAAe;IAC/B,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC9B,2BAA2B;IAC3B,MAAM,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IAC7D,4EAA4E;IAC5E,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC;CACnH;AAQD,MAAM,WAAW,eAAe;IAC/B,oEAAoE;IACpE,UAAU,CAAC,EAAE,cAAc,CAAC;CAC5B;AAoHD,wBAAgB,wBAAwB,CACvC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,eAAe,GACvB,cAAc,CAAC,OAAO,UAAU,EAAE,eAAe,GAAG,SAAS,CAAC,CA0NhE;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 \"@caupulican/pi-agent-core\";\nimport { Text } from \"@caupulican/pi-tui\";\nimport { spawn } from \"child_process\";\nimport path from \"path\";\nimport { type Static, Type } from \"typebox\";\nimport { keyHint } from \"../../modes/interactive/components/keybinding-hints.ts\";\nimport type { Theme } from \"../../modes/interactive/theme/theme.ts\";\nimport { ensureTool } from \"../../utils/tools-manager.ts\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../extensions/types.ts\";\nimport { pathExists, resolveToCwd } from \"./path-utils.ts\";\nimport { getTextOutput, invalidArgText, shortenPath, str } from \"./render-utils.ts\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.ts\";\nimport { DEFAULT_MAX_BYTES, formatSize, type TruncationResult, truncateHead } from \"./truncate.ts\";\n\nfunction toPosixPath(value: string): string {\n\treturn value.split(path.sep).join(\"/\");\n}\n\nconst findSchema = Type.Object({\n\tpattern: Type.String({\n\t\tdescription:\n\t\t\t\"Glob pattern to match files, e.g. '*.ts', '**/*.json', or 'src/**/*.spec.ts'. Use '.' to match all files.\",\n\t}),\n\tpath: Type.Optional(Type.String({ description: \"Directory to search in (default: current directory)\" })),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of results (default: 1000)\" })),\n\tignoreCase: Type.Optional(Type.Boolean({ description: \"Case-insensitive matching (default: false)\" })),\n});\n\nexport type FindToolInput = Static<typeof findSchema>;\n\nconst DEFAULT_LIMIT = 1000;\n\nexport interface FindToolDetails {\n\ttruncation?: TruncationResult;\n\tresultLimitReached?: number;\n}\n\n/**\n * Pluggable operations for the find tool.\n * Override these to delegate file search to remote systems (for example SSH).\n */\nexport interface FindOperations {\n\t/** Check if path exists */\n\texists: (absolutePath: string) => Promise<boolean> | boolean;\n\t/** Find files matching glob pattern. Returns relative or absolute paths. */\n\tglob: (pattern: string, cwd: string, options: { ignore: string[]; limit: number }) => Promise<string[]> | string[];\n}\n\nconst defaultFindOperations: FindOperations = {\n\texists: pathExists,\n\t// This is a placeholder. Actual fd execution happens in execute() when no custom glob is provided.\n\tglob: () => [],\n};\n\nexport interface FindToolOptions {\n\t/** Custom operations for find. Default: local filesystem plus fd */\n\toperations?: FindOperations;\n}\n\nfunction formatFindCall(\n\targs: { pattern: string; path?: string; limit?: number } | undefined,\n\ttheme: Theme,\n\tcwd: string,\n): string {\n\tconst pattern = str(args?.pattern);\n\tconst rawPath = str(args?.path);\n\tconst path = rawPath !== null ? shortenPath(rawPath || \".\", cwd) : null;\n\tconst limit = args?.limit;\n\tconst invalidArg = invalidArgText(theme);\n\tlet text =\n\t\ttheme.fg(\"toolTitle\", theme.bold(\"find\")) +\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 (limit !== undefined) {\n\t\ttext += theme.fg(\"toolOutput\", ` (limit ${limit})`);\n\t}\n\treturn text;\n}\n\nfunction formatFindResult(\n\tresult: {\n\t\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\t\tdetails?: FindToolDetails;\n\t},\n\toptions: ToolRenderResultOptions,\n\ttheme: 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 : 20;\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 resultLimit = result.details?.resultLimitReached;\n\tconst truncation = result.details?.truncation;\n\tif (resultLimit || truncation?.truncated) {\n\t\tconst warnings: string[] = [];\n\t\tif (resultLimit) warnings.push(`${resultLimit} results limit`);\n\t\tif (truncation?.truncated) warnings.push(`${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit`);\n\t\ttext += `\\n${theme.fg(\"warning\", `[Truncated: ${warnings.join(\", \")}]`)}`;\n\t}\n\treturn text;\n}\n\nfunction formatFindResults(relativized: string[], effectiveLimit: number): { text: string; details: FindToolDetails } {\n\tif (relativized.length === 0) {\n\t\treturn { text: \"No files found matching pattern\", details: {} };\n\t}\n\n\tconst dirGroups = new Map<string, string[]>();\n\tconst extCounts = new Map<string, number>();\n\n\tfor (const p of relativized) {\n\t\tconst dir = path.dirname(p);\n\t\tconst base = path.basename(p);\n\t\tconst dirKey = dir === \".\" ? \"./\" : `${dir}/`;\n\t\tif (!dirGroups.has(dirKey)) {\n\t\t\tdirGroups.set(dirKey, []);\n\t\t}\n\t\tdirGroups.get(dirKey)!.push(base);\n\n\t\tconst ext = path.extname(p).toLowerCase() || \"(no extension)\";\n\t\textCounts.set(ext, (extCounts.get(ext) || 0) + 1);\n\t}\n\n\tconst sortedDirs = Array.from(dirGroups.keys()).sort((a, b) => a.localeCompare(b));\n\tconst formattedLines: string[] = [];\n\tfor (const dir of sortedDirs) {\n\t\tformattedLines.push(dir);\n\t\tconst files = dirGroups.get(dir)!;\n\t\tfiles.sort((a, b) => a.localeCompare(b));\n\t\tfor (const file of files) {\n\t\t\tformattedLines.push(` ${file}`);\n\t\t}\n\t}\n\n\tconst extSummaryParts = Array.from(extCounts.entries())\n\t\t.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))\n\t\t.map(([ext, count]) => `${ext}: ${count}`);\n\tconst extSummary = `Extensions: ${extSummaryParts.join(\", \")}`;\n\n\tconst resultLimitReached = relativized.length >= effectiveLimit;\n\tconst rawOutput = formattedLines.join(\"\\n\");\n\tconst truncation = truncateHead(rawOutput, { maxLines: Number.MAX_SAFE_INTEGER });\n\tlet resultOutput = truncation.content;\n\tconst details: FindToolDetails = {};\n\tconst notices: string[] = [];\n\tif (resultLimitReached) {\n\t\tnotices.push(`${effectiveLimit} results limit reached`);\n\t\tdetails.resultLimitReached = effectiveLimit;\n\t}\n\tif (truncation.truncated) {\n\t\tnotices.push(`${formatSize(DEFAULT_MAX_BYTES)} limit reached`);\n\t\tdetails.truncation = truncation;\n\t}\n\tif (relativized.length > 0) {\n\t\tresultOutput += `\\n\\n[Summary - ${extSummary}]`;\n\t}\n\tif (notices.length > 0) {\n\t\tresultOutput += `\\n\\n[${notices.join(\". \")}]`;\n\t}\n\treturn { text: resultOutput, details };\n}\n\nexport function createFindToolDefinition(\n\tcwd: string,\n\toptions?: FindToolOptions,\n): ToolDefinition<typeof findSchema, FindToolDetails | undefined> {\n\tconst customOps = options?.operations;\n\treturn {\n\t\tname: \"find\",\n\t\tlabel: \"find\",\n\t\tdescription: `Search for files by glob pattern. Returns matching file paths relative to the search directory. Respects .gitignore. Output is truncated to ${DEFAULT_LIMIT} results or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first).`,\n\t\tpromptSnippet: \"Find files by glob pattern (respects .gitignore)\",\n\t\tparameters: findSchema,\n\t\ttoolGroup: \"explore\",\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\tlimit,\n\t\t\t\tignoreCase,\n\t\t\t}: { pattern: string; path?: string; limit?: number; ignoreCase?: boolean },\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\n\t\t\t\tlet settled = false;\n\t\t\t\tlet stopChild: (() => void) | undefined;\n\t\t\t\tconst settle = (fn: () => void) => {\n\t\t\t\t\tif (settled) return;\n\t\t\t\t\tsettled = true;\n\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\tstopChild = undefined;\n\t\t\t\t\tfn();\n\t\t\t\t};\n\t\t\t\tconst onAbort = () => {\n\t\t\t\t\tstopChild?.();\n\t\t\t\t\tsettle(() => reject(new Error(\"Operation aborted\")));\n\t\t\t\t};\n\t\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\n\t\t\t\t(async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst searchPath = resolveToCwd(searchDir || \".\", cwd);\n\t\t\t\t\t\tconst effectiveLimit = limit ?? DEFAULT_LIMIT;\n\t\t\t\t\t\tconst ops = customOps ?? defaultFindOperations;\n\n\t\t\t\t\t\tlet effectivePattern = pattern;\n\t\t\t\t\t\tif (pattern === \".\") {\n\t\t\t\t\t\t\teffectivePattern = \"**/*\";\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// If custom operations provide glob(), use that instead of fd.\n\t\t\t\t\t\tif (customOps?.glob) {\n\t\t\t\t\t\t\tif (!(await ops.exists(searchPath))) {\n\t\t\t\t\t\t\t\tsettle(() => reject(new Error(`Path not found: ${searchPath}`)));\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 (signal?.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\tconst results = await ops.glob(effectivePattern, searchPath, {\n\t\t\t\t\t\t\t\tignore: [\"**/node_modules/**\", \"**/.git/**\"],\n\t\t\t\t\t\t\t\tlimit: effectiveLimit,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tif (signal?.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\n\t\t\t\t\t\t\t// Relativize paths against the search root for stable output.\n\t\t\t\t\t\t\tconst relativized = results.map((p) => {\n\t\t\t\t\t\t\t\tif (p.startsWith(searchPath)) return toPosixPath(p.slice(searchPath.length + 1));\n\t\t\t\t\t\t\t\treturn toPosixPath(path.relative(searchPath, p));\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\tconst formatted = formatFindResults(relativized, effectiveLimit);\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: formatted.text }],\n\t\t\t\t\t\t\t\t\tdetails: Object.keys(formatted.details).length > 0 ? formatted.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\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Default implementation uses fd.\n\t\t\t\t\t\tconst fdPath = await ensureTool(\"fd\", true);\n\t\t\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\t\t\tsettle(() => reject(new Error(\"Operation aborted\")));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!fdPath) {\n\t\t\t\t\t\t\tsettle(() => reject(new Error(\"fd is not available and could not be downloaded\")));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Build fd arguments. --no-require-git makes fd apply hierarchical .gitignore\n\t\t\t\t\t\t// semantics whether or not the search path is inside a git repository, without\n\t\t\t\t\t\t// leaking sibling-directory rules the way --ignore-file (a global source) would.\n\t\t\t\t\t\tconst args: string[] = [\n\t\t\t\t\t\t\t\"--glob\",\n\t\t\t\t\t\t\t\"--color=never\",\n\t\t\t\t\t\t\t\"--hidden\",\n\t\t\t\t\t\t\t\"--no-require-git\",\n\t\t\t\t\t\t\t\"--max-results\",\n\t\t\t\t\t\t\tString(effectiveLimit),\n\t\t\t\t\t\t];\n\t\t\t\t\t\tif (ignoreCase) {\n\t\t\t\t\t\t\targs.push(\"--ignore-case\");\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// fd --glob matches against the basename unless --full-path is set; in --full-path\n\t\t\t\t\t\t// mode it matches against the absolute candidate path, so a path-containing\n\t\t\t\t\t\t// pattern like 'src/**/*.spec.ts' needs a leading '**/' to match anything.\n\t\t\t\t\t\tlet finalPattern = effectivePattern;\n\t\t\t\t\t\tif (effectivePattern.includes(\"/\")) {\n\t\t\t\t\t\t\targs.push(\"--full-path\");\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t!effectivePattern.startsWith(\"/\") &&\n\t\t\t\t\t\t\t\t!effectivePattern.startsWith(\"**/\") &&\n\t\t\t\t\t\t\t\teffectivePattern !== \"**\"\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tfinalPattern = `**/${effectivePattern}`;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\targs.push(\"--\", finalPattern, searchPath);\n\n\t\t\t\t\t\tconst child = spawn(fdPath, 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\tconst lines: string[] = [];\n\n\t\t\t\t\t\tstopChild = () => {\n\t\t\t\t\t\t\tif (!child.killed) {\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\n\t\t\t\t\t\tconst cleanup = () => {\n\t\t\t\t\t\t\trl.close();\n\t\t\t\t\t\t};\n\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\trl.on(\"line\", (line) => {\n\t\t\t\t\t\t\tlines.push(line);\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 fd: ${error.message}`)));\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tchild.on(\"close\", (code) => {\n\t\t\t\t\t\t\tcleanup();\n\t\t\t\t\t\t\tif (signal?.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\tconst output = lines.join(\"\\n\");\n\t\t\t\t\t\t\tif (code !== 0) {\n\t\t\t\t\t\t\t\tconst errorMsg = stderr.trim() || `fd exited with code ${code}`;\n\t\t\t\t\t\t\t\tif (!output) {\n\t\t\t\t\t\t\t\t\tsettle(() => reject(new Error(errorMsg)));\n\t\t\t\t\t\t\t\t\treturn;\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 relativized: string[] = [];\n\t\t\t\t\t\t\tfor (const rawLine of lines) {\n\t\t\t\t\t\t\t\tconst line = rawLine.replace(/\\r$/, \"\").trim();\n\t\t\t\t\t\t\t\tif (!line) continue;\n\t\t\t\t\t\t\t\tconst hadTrailingSlash = line.endsWith(\"/\") || line.endsWith(\"\\\\\");\n\t\t\t\t\t\t\t\tlet relativePath = line;\n\t\t\t\t\t\t\t\tif (line.startsWith(searchPath)) {\n\t\t\t\t\t\t\t\t\trelativePath = line.slice(searchPath.length + 1);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\trelativePath = path.relative(searchPath, line);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (hadTrailingSlash && !relativePath.endsWith(\"/\")) relativePath += \"/\";\n\t\t\t\t\t\t\t\trelativized.push(toPosixPath(relativePath));\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tconst formatted = formatFindResults(relativized, effectiveLimit);\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: formatted.text }],\n\t\t\t\t\t\t\t\t\tdetails: Object.keys(formatted.details).length > 0 ? formatted.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 (e) {\n\t\t\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\t\t\tsettle(() => reject(new Error(\"Operation aborted\")));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst error = e instanceof Error ? e : new Error(String(e));\n\t\t\t\t\t\tsettle(() => reject(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(formatFindCall(args, theme, context.cwd));\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(formatFindResult(result as any, options, theme, context.showImages));\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport function createFindTool(cwd: string, options?: FindToolOptions): AgentTool<typeof findSchema> {\n\treturn wrapToolDefinition(createFindToolDefinition(cwd, options));\n}\n"]}
|
|
1
|
+
{"version":3,"file":"find.d.ts","sourceRoot":"","sources":["../../../src/core/tools/find.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAI3D,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAI5C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EACN,KAAK,iBAAiB,EAKtB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,KAAK,EAAE,cAAc,EAA2B,MAAM,wBAAwB,CAAC;AACtF,OAAO,EAEN,KAAK,gBAAgB,EAIrB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAE5E,OAAO,EAAiC,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAMrF,QAAA,MAAM,UAAU;;;;;EAQd,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,UAAU,CAAC,CAAC;AAItD,MAAM,WAAW,eAAe;IAC/B,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,iFAAiF;IACjF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iFAAiF;IACjF,qBAAqB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC9B,2BAA2B;IAC3B,MAAM,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IAC7D,4EAA4E;IAC5E,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC;CACnH;AAQD,MAAM,WAAW,eAAe;IAC/B,sFAAsF;IACtF,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,+EAA+E;IAC/E,GAAG,CAAC,EAAE,gBAAgB,GAAG,KAAK,CAAC;IAC/B,qFAAqF;IACrF,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B;;;;OAIG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,yFAAyF;IACzF,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;CACtC;AA6RD,wBAAgB,wBAAwB,CACvC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,eAAe,GACvB,cAAc,CAAC,OAAO,UAAU,EAAE,eAAe,GAAG,SAAS,CAAC,CAuQhE;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 \"@caupulican/pi-agent-core\";\nimport { Text } from \"@caupulican/pi-tui\";\nimport { spawn } from \"child_process\";\nimport path from \"path\";\nimport { type Static, Type } from \"typebox\";\nimport { keyHint } from \"../../modes/interactive/components/keybinding-hints.ts\";\nimport type { Theme } from \"../../modes/interactive/theme/theme.ts\";\nimport { ensureTool } from \"../../utils/tools-manager.ts\";\nimport type { ArtifactStore } from \"../context/context-artifacts.ts\";\nimport {\n\ttype BroadQueryTracker,\n\tbroadQueryInvalidationNote,\n\tformatArtifactNotice,\n\tnormalizeBroadQueryKey,\n\tpackToolOutput,\n} from \"../context/tool-output-packer.ts\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../extensions/types.ts\";\nimport {\n\tdefaultFffSearchBackend,\n\ttype FffSearchBackend,\n\ttype FffSearchResult,\n\thasGitignoreInTree,\n\trelativePathInside,\n} from \"./fff-search-backend.ts\";\nimport { pathExists, resolveToCwd } from \"./path-utils.ts\";\nimport { getTextOutput, invalidArgText, shortenPath, str } from \"./render-utils.ts\";\nimport { defaultSearchRouter, type SearchRouter } from \"./search-router.ts\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.ts\";\nimport { DEFAULT_MAX_BYTES, formatSize, type TruncationResult } from \"./truncate.ts\";\n\nfunction toPosixPath(value: string): string {\n\treturn value.split(path.sep).join(\"/\");\n}\n\nconst findSchema = Type.Object({\n\tpattern: Type.String({\n\t\tdescription:\n\t\t\t\"Glob pattern to match files, e.g. '*.ts', '**/*.json', or 'src/**/*.spec.ts'. Use '.' to match all files.\",\n\t}),\n\tpath: Type.Optional(Type.String({ description: \"Directory to search in (default: current directory)\" })),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of results (default: 1000)\" })),\n\tignoreCase: Type.Optional(Type.Boolean({ description: \"Case-insensitive matching (default: false)\" })),\n});\n\nexport type FindToolInput = Static<typeof findSchema>;\n\nconst DEFAULT_LIMIT = 1000;\n\nexport interface FindToolDetails {\n\ttruncation?: TruncationResult;\n\tresultLimitReached?: number;\n\t/** Set only when output was packed to an artifact; see tool-output-packer.ts. */\n\tartifactId?: string;\n\t/** Set when this exact query has repeatedly produced broad/truncated results. */\n\tinvalidationCandidate?: boolean;\n}\n\n/**\n * Pluggable operations for the find tool.\n * Override these to delegate file search to remote systems (for example SSH).\n */\nexport interface FindOperations {\n\t/** Check if path exists */\n\texists: (absolutePath: string) => Promise<boolean> | boolean;\n\t/** Find files matching glob pattern. Returns relative or absolute paths. */\n\tglob: (pattern: string, cwd: string, options: { ignore: string[]; limit: number }) => Promise<string[]> | string[];\n}\n\nconst defaultFindOperations: FindOperations = {\n\texists: pathExists,\n\t// This is a placeholder. Actual fd execution happens in execute() when no custom glob is provided.\n\tglob: () => [],\n};\n\nexport interface FindToolOptions {\n\t/** Custom operations for find. Default: local filesystem plus routed FFF/fd search */\n\toperations?: FindOperations;\n\t/** FFF backend for resident indexed search. Set false to force fd fallback. */\n\tfff?: FffSearchBackend | false;\n\t/** Pure router that selects FFF or fd from request filters and environment facts. */\n\tsearchRouter?: SearchRouter;\n\t/**\n\t * Opt-in artifact store for first-capture-then-bound output packing (Phase 3). When\n\t * omitted (the default), behavior is byte-for-byte unchanged from before this option\n\t * existed: output is truncated the same way, just never artifact-backed.\n\t */\n\tartifactStore?: ArtifactStore;\n\t/** Opt-in tracker for repeated-broad-query \"do not repeat\" signals. Also default-off. */\n\tbroadQueryTracker?: BroadQueryTracker;\n}\n\nfunction formatFindCall(\n\targs: { pattern: string; path?: string; limit?: number } | undefined,\n\ttheme: Theme,\n\tcwd: string,\n): string {\n\tconst pattern = str(args?.pattern);\n\tconst rawPath = str(args?.path);\n\tconst path = rawPath !== null ? shortenPath(rawPath || \".\", cwd) : null;\n\tconst limit = args?.limit;\n\tconst invalidArg = invalidArgText(theme);\n\tlet text =\n\t\ttheme.fg(\"toolTitle\", theme.bold(\"find\")) +\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 (limit !== undefined) {\n\t\ttext += theme.fg(\"toolOutput\", ` (limit ${limit})`);\n\t}\n\treturn text;\n}\n\nfunction formatFindResult(\n\tresult: {\n\t\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\t\tdetails?: FindToolDetails;\n\t},\n\toptions: ToolRenderResultOptions,\n\ttheme: 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 : 20;\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 resultLimit = result.details?.resultLimitReached;\n\tconst truncation = result.details?.truncation;\n\tif (resultLimit || truncation?.truncated) {\n\t\tconst warnings: string[] = [];\n\t\tif (resultLimit) warnings.push(`${resultLimit} results limit`);\n\t\tif (truncation?.truncated) warnings.push(`${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit`);\n\t\ttext += `\\n${theme.fg(\"warning\", `[Truncated: ${warnings.join(\", \")}]`)}`;\n\t}\n\treturn text;\n}\n\nfunction hasGlobSyntax(pattern: string): boolean {\n\treturn pattern === \".\" || /[*?[{]/.test(pattern);\n}\n\nfunction fffQueryParts(parts: string[]): string {\n\treturn parts.filter(Boolean).join(\" \");\n}\n\nfunction toSearchRelative(repoRelativePath: string, searchPathRelativeToCwd: string): string | undefined {\n\tif (!searchPathRelativeToCwd) return repoRelativePath;\n\tconst prefix = `${searchPathRelativeToCwd}/`;\n\tif (!repoRelativePath.startsWith(prefix)) return undefined;\n\treturn repoRelativePath.slice(prefix.length);\n}\n\nfunction fffGlobPattern(pattern: string, searchPathRelativeToCwd: string): string {\n\tconst effectivePattern = pattern === \".\" ? \"**/*\" : pattern;\n\tif (!searchPathRelativeToCwd) {\n\t\tif (effectivePattern.includes(\"/\") || effectivePattern.startsWith(\"**/\")) return effectivePattern;\n\t\treturn `**/${effectivePattern}`;\n\t}\n\tif (effectivePattern === \"**\" || effectivePattern === \"**/*\") return `${searchPathRelativeToCwd}/**/*`;\n\tif (effectivePattern.includes(\"/\")) return `${searchPathRelativeToCwd}/${effectivePattern}`;\n\treturn `${searchPathRelativeToCwd}/**/${effectivePattern}`;\n}\n\ninterface FindPackingOptions {\n\ttoolCallId: string;\n\tartifactStore?: ArtifactStore;\n\tbroadQueryTracker?: BroadQueryTracker;\n\tpattern: string;\n\trawPath?: string;\n}\n\nfunction fffSearchOutput(\n\tresult: FffSearchResult,\n\tsearchPathRelativeToCwd: string,\n\teffectiveLimit: number,\n\tpacking: FindPackingOptions,\n) {\n\tconst relativized = result.items\n\t\t.map((item) => toSearchRelative(item.relativePath, searchPathRelativeToCwd))\n\t\t.filter((item): item is string => Boolean(item));\n\treturn formatFindResults(relativized, effectiveLimit, packing);\n}\n\nasync function tryFffFind(options: {\n\tbackend: FffSearchBackend;\n\trouter: SearchRouter;\n\tcwd: string;\n\tsearchPath: string;\n\tpattern: string;\n\tignoreCase?: boolean;\n\teffectiveLimit: number;\n\ttoolCallId: string;\n\tartifactStore?: ArtifactStore;\n\tbroadQueryTracker?: BroadQueryTracker;\n\trawPath?: string;\n}): Promise<{ text: string; details: FindToolDetails } | undefined> {\n\tif (!(await pathExists(options.searchPath))) return undefined;\n\n\tconst searchPathRelativeToCwd = relativePathInside(options.cwd, options.searchPath);\n\tconst glob = hasGlobSyntax(options.pattern);\n\tconst baseRoute = options.router.route({\n\t\ttool: \"find\",\n\t\tglob,\n\t\tignoreCase: Boolean(options.ignoreCase),\n\t\tlimit: options.effectiveLimit,\n\t\tfinderAvailable: true,\n\t\tpathResolvable: searchPathRelativeToCwd !== undefined,\n\t\tgitignoreInTree: false,\n\t});\n\tif (baseRoute.backend !== \"fff\") return undefined;\n\tif (searchPathRelativeToCwd === undefined) return undefined;\n\n\tconst gitignoreInTree = await hasGitignoreInTree(options.searchPath);\n\tconst semanticRoute = options.router.route({\n\t\ttool: \"find\",\n\t\tglob,\n\t\tignoreCase: Boolean(options.ignoreCase),\n\t\tlimit: options.effectiveLimit,\n\t\tfinderAvailable: true,\n\t\tpathResolvable: true,\n\t\tgitignoreInTree,\n\t});\n\tif (semanticRoute.backend !== \"fff\") return undefined;\n\n\tconst finder = await options.backend.getFinder(options.cwd);\n\tconst finderRoute = options.router.route({\n\t\ttool: \"find\",\n\t\tglob,\n\t\tignoreCase: Boolean(options.ignoreCase),\n\t\tlimit: options.effectiveLimit,\n\t\tfinderAvailable: Boolean(finder),\n\t\tpathResolvable: true,\n\t\tgitignoreInTree: false,\n\t});\n\tif (!finder || finderRoute.backend !== \"fff\") return undefined;\n\n\tconst packing: FindPackingOptions = {\n\t\ttoolCallId: options.toolCallId,\n\t\tartifactStore: options.artifactStore,\n\t\tbroadQueryTracker: options.broadQueryTracker,\n\t\tpattern: options.pattern,\n\t\trawPath: options.rawPath,\n\t};\n\n\tif (glob) {\n\t\tconst result = finder.glob(fffGlobPattern(options.pattern, searchPathRelativeToCwd), {\n\t\t\tpageSize: options.effectiveLimit,\n\t\t});\n\t\treturn result.ok\n\t\t\t? fffSearchOutput(result.value, searchPathRelativeToCwd, options.effectiveLimit, packing)\n\t\t\t: undefined;\n\t}\n\n\tconst pathConstraint = searchPathRelativeToCwd ? `${searchPathRelativeToCwd}/` : \"\";\n\tconst result = finder.fileSearch(fffQueryParts([pathConstraint, options.pattern]), {\n\t\tpageSize: options.effectiveLimit,\n\t});\n\treturn result.ok\n\t\t? fffSearchOutput(result.value, searchPathRelativeToCwd, options.effectiveLimit, packing)\n\t\t: undefined;\n}\n\nfunction formatFindResults(\n\trelativized: string[],\n\teffectiveLimit: number,\n\tpacking: FindPackingOptions,\n): { text: string; details: FindToolDetails } {\n\tif (relativized.length === 0) {\n\t\treturn { text: \"No files found matching pattern\", details: {} };\n\t}\n\n\tconst dirGroups = new Map<string, string[]>();\n\tconst extCounts = new Map<string, number>();\n\n\tfor (const p of relativized) {\n\t\tconst dir = path.dirname(p);\n\t\tconst base = path.basename(p);\n\t\tconst dirKey = dir === \".\" ? \"./\" : `${dir}/`;\n\t\tif (!dirGroups.has(dirKey)) {\n\t\t\tdirGroups.set(dirKey, []);\n\t\t}\n\t\tdirGroups.get(dirKey)!.push(base);\n\n\t\tconst ext = path.extname(p).toLowerCase() || \"(no extension)\";\n\t\textCounts.set(ext, (extCounts.get(ext) || 0) + 1);\n\t}\n\n\tconst sortedDirs = Array.from(dirGroups.keys()).sort((a, b) => a.localeCompare(b));\n\tconst formattedLines: string[] = [];\n\tfor (const dir of sortedDirs) {\n\t\tformattedLines.push(dir);\n\t\tconst files = dirGroups.get(dir)!;\n\t\tfiles.sort((a, b) => a.localeCompare(b));\n\t\tfor (const file of files) {\n\t\t\tformattedLines.push(` ${file}`);\n\t\t}\n\t}\n\n\tconst extSummaryParts = Array.from(extCounts.entries())\n\t\t.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))\n\t\t.map(([ext, count]) => `${ext}: ${count}`);\n\tconst extSummary = `Extensions: ${extSummaryParts.join(\", \")}`;\n\n\tconst resultLimitReached = relativized.length >= effectiveLimit;\n\tconst rawOutput = formattedLines.join(\"\\n\");\n\t// Measure -> pack (artifact-backed if oversized and a store was provided) -> notices.\n\tconst packed = packToolOutput(\n\t\t{\n\t\t\ttoolName: \"find\",\n\t\t\tpath: packing.rawPath,\n\t\t\trawContent: rawOutput,\n\t\t\t// No line limit here because the result limit already caps rows; only the byte\n\t\t\t// cap should apply, matching the pre-Slice-B truncateHead call exactly.\n\t\t\ttruncation: { maxLines: Number.MAX_SAFE_INTEGER },\n\t\t},\n\t\tpacking.artifactStore,\n\t\tpacking.toolCallId,\n\t);\n\tlet resultOutput = packed.content;\n\tconst details: FindToolDetails = {};\n\tconst notices: string[] = [];\n\tif (packed.artifactId) {\n\t\tnotices.push(formatArtifactNotice(packed.artifactId));\n\t\tdetails.artifactId = packed.artifactId;\n\t}\n\tif (resultLimitReached) {\n\t\tnotices.push(\n\t\t\t`${effectiveLimit} results limit reached. Use limit=${effectiveLimit * 2} for more, or narrow path/pattern`,\n\t\t);\n\t\tdetails.resultLimitReached = effectiveLimit;\n\t}\n\tif (packed.truncation.truncated) {\n\t\tnotices.push(`${formatSize(DEFAULT_MAX_BYTES)} limit reached`);\n\t\t// Drop the duplicated bounded-preview text: it's already in the message's own\n\t\t// content, and re-including it here can push `details` past\n\t\t// MAX_RETAINED_TOOL_RESULT_DETAILS_BYTES (message-retention.ts), which replaces\n\t\t// the *entire* details object with a stub -- silently losing artifactId and every\n\t\t// other field alongside it. This is load-bearing beyond just the retention budget:\n\t\t// agent-session.ts's _releaseGcPackedArtifactReferences() reads artifactId back off\n\t\t// this same canonical message at eviction time (potentially many turns later), so\n\t\t// keeping `details` small here is what keeps that release path working at all. If\n\t\t// this field ever grows a large addition again, add a regression proving artifactId\n\t\t// survives compactToolResultDetailsForRetention (see\n\t\t// test/suite/agent-session-artifact-lifecycle.test.ts), not just a details-size check.\n\t\tdetails.truncation = { ...packed.truncation, content: \"\" };\n\t}\n\tif (resultLimitReached || packed.truncation.truncated) {\n\t\tconst note = broadQueryInvalidationNote(\n\t\t\tpacking.broadQueryTracker,\n\t\t\tnormalizeBroadQueryKey({ toolName: \"find\", pattern: packing.pattern, path: packing.rawPath }),\n\t\t\t`find \"${packing.pattern}\" in ${packing.rawPath ?? \".\"}`,\n\t\t);\n\t\tif (note) {\n\t\t\tnotices.push(note);\n\t\t\tdetails.invalidationCandidate = true;\n\t\t}\n\t}\n\tif (relativized.length > 0) {\n\t\tresultOutput += `\\n\\n[Summary - ${extSummary}]`;\n\t}\n\tif (notices.length > 0) {\n\t\tresultOutput += `\\n\\n[${notices.join(\". \")}]`;\n\t}\n\treturn { text: resultOutput, details };\n}\n\nexport function createFindToolDefinition(\n\tcwd: string,\n\toptions?: FindToolOptions,\n): ToolDefinition<typeof findSchema, FindToolDetails | undefined> {\n\tconst customOps = options?.operations;\n\tconst fffBackend = options?.fff === false ? undefined : (options?.fff ?? defaultFffSearchBackend);\n\tconst searchRouter = options?.searchRouter ?? defaultSearchRouter;\n\tconst artifactStore = options?.artifactStore;\n\tconst broadQueryTracker = options?.broadQueryTracker;\n\treturn {\n\t\tname: \"find\",\n\t\tlabel: \"find\",\n\t\tdescription: `Search for files by glob pattern. Returns matching file paths relative to the search directory. Respects .gitignore. Output is truncated to ${DEFAULT_LIMIT} results or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first).`,\n\t\tpromptSnippet: \"Find files by glob pattern (respects .gitignore)\",\n\t\tparameters: findSchema,\n\t\ttoolGroup: \"explore\",\n\t\tasync execute(\n\t\t\ttoolCallId,\n\t\t\t{\n\t\t\t\tpattern,\n\t\t\t\tpath: searchDir,\n\t\t\t\tlimit,\n\t\t\t\tignoreCase,\n\t\t\t}: { pattern: string; path?: string; limit?: number; ignoreCase?: boolean },\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\n\t\t\t\tlet settled = false;\n\t\t\t\tlet stopChild: (() => void) | undefined;\n\t\t\t\tconst settle = (fn: () => void) => {\n\t\t\t\t\tif (settled) return;\n\t\t\t\t\tsettled = true;\n\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\tstopChild = undefined;\n\t\t\t\t\tfn();\n\t\t\t\t};\n\t\t\t\tconst onAbort = () => {\n\t\t\t\t\tstopChild?.();\n\t\t\t\t\tsettle(() => reject(new Error(\"Operation aborted\")));\n\t\t\t\t};\n\t\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\n\t\t\t\t(async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst searchPath = resolveToCwd(searchDir || \".\", cwd);\n\t\t\t\t\t\tconst effectiveLimit = limit ?? DEFAULT_LIMIT;\n\t\t\t\t\t\tconst ops = customOps ?? defaultFindOperations;\n\n\t\t\t\t\t\tlet effectivePattern = pattern;\n\t\t\t\t\t\tif (pattern === \".\") {\n\t\t\t\t\t\t\teffectivePattern = \"**/*\";\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (!customOps && fffBackend) {\n\t\t\t\t\t\t\tconst fffResult = await tryFffFind({\n\t\t\t\t\t\t\t\tbackend: fffBackend,\n\t\t\t\t\t\t\t\trouter: searchRouter,\n\t\t\t\t\t\t\t\tcwd,\n\t\t\t\t\t\t\t\tsearchPath,\n\t\t\t\t\t\t\t\tpattern: effectivePattern,\n\t\t\t\t\t\t\t\tignoreCase,\n\t\t\t\t\t\t\t\teffectiveLimit,\n\t\t\t\t\t\t\t\ttoolCallId,\n\t\t\t\t\t\t\t\tartifactStore,\n\t\t\t\t\t\t\t\tbroadQueryTracker,\n\t\t\t\t\t\t\t\trawPath: searchDir,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tif (signal?.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 (fffResult) {\n\t\t\t\t\t\t\t\tsettle(() =>\n\t\t\t\t\t\t\t\t\tresolve({\n\t\t\t\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: fffResult.text }],\n\t\t\t\t\t\t\t\t\t\tdetails: Object.keys(fffResult.details).length > 0 ? fffResult.details : undefined,\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\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// If custom operations provide glob(), use that instead of fd.\n\t\t\t\t\t\tif (customOps?.glob) {\n\t\t\t\t\t\t\tif (!(await ops.exists(searchPath))) {\n\t\t\t\t\t\t\t\tsettle(() => reject(new Error(`Path not found: ${searchPath}`)));\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 (signal?.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\tconst results = await ops.glob(effectivePattern, searchPath, {\n\t\t\t\t\t\t\t\tignore: [\"**/node_modules/**\", \"**/.git/**\"],\n\t\t\t\t\t\t\t\tlimit: effectiveLimit,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tif (signal?.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\n\t\t\t\t\t\t\t// Relativize paths against the search root for stable output.\n\t\t\t\t\t\t\tconst relativized = results.map((p) => {\n\t\t\t\t\t\t\t\tif (p.startsWith(searchPath)) return toPosixPath(p.slice(searchPath.length + 1));\n\t\t\t\t\t\t\t\treturn toPosixPath(path.relative(searchPath, p));\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\tconst formatted = formatFindResults(relativized, effectiveLimit, {\n\t\t\t\t\t\t\t\ttoolCallId,\n\t\t\t\t\t\t\t\tartifactStore,\n\t\t\t\t\t\t\t\tbroadQueryTracker,\n\t\t\t\t\t\t\t\tpattern: effectivePattern,\n\t\t\t\t\t\t\t\trawPath: searchDir,\n\t\t\t\t\t\t\t});\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: formatted.text }],\n\t\t\t\t\t\t\t\t\tdetails: Object.keys(formatted.details).length > 0 ? formatted.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\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Default implementation uses fd.\n\t\t\t\t\t\tconst fdPath = await ensureTool(\"fd\", true);\n\t\t\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\t\t\tsettle(() => reject(new Error(\"Operation aborted\")));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!fdPath) {\n\t\t\t\t\t\t\tsettle(() => reject(new Error(\"fd is not available and could not be downloaded\")));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Build fd arguments. --no-require-git makes fd apply hierarchical .gitignore\n\t\t\t\t\t\t// semantics whether or not the search path is inside a git repository, without\n\t\t\t\t\t\t// leaking sibling-directory rules the way --ignore-file (a global source) would.\n\t\t\t\t\t\tconst args: string[] = [\n\t\t\t\t\t\t\t\"--glob\",\n\t\t\t\t\t\t\t\"--color=never\",\n\t\t\t\t\t\t\t\"--hidden\",\n\t\t\t\t\t\t\t\"--no-require-git\",\n\t\t\t\t\t\t\t\"--max-results\",\n\t\t\t\t\t\t\tString(effectiveLimit),\n\t\t\t\t\t\t];\n\t\t\t\t\t\tif (ignoreCase) {\n\t\t\t\t\t\t\targs.push(\"--ignore-case\");\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// fd --glob matches against the basename unless --full-path is set; in --full-path\n\t\t\t\t\t\t// mode it matches against the absolute candidate path, so a path-containing\n\t\t\t\t\t\t// pattern like 'src/**/*.spec.ts' needs a leading '**/' to match anything.\n\t\t\t\t\t\tlet finalPattern = effectivePattern;\n\t\t\t\t\t\tif (effectivePattern.includes(\"/\")) {\n\t\t\t\t\t\t\targs.push(\"--full-path\");\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t!effectivePattern.startsWith(\"/\") &&\n\t\t\t\t\t\t\t\t!effectivePattern.startsWith(\"**/\") &&\n\t\t\t\t\t\t\t\teffectivePattern !== \"**\"\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tfinalPattern = `**/${effectivePattern}`;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\targs.push(\"--\", finalPattern, searchPath);\n\n\t\t\t\t\t\tconst child = spawn(fdPath, 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\tconst lines: string[] = [];\n\n\t\t\t\t\t\tstopChild = () => {\n\t\t\t\t\t\t\tif (!child.killed) {\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\n\t\t\t\t\t\tconst cleanup = () => {\n\t\t\t\t\t\t\trl.close();\n\t\t\t\t\t\t};\n\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\trl.on(\"line\", (line) => {\n\t\t\t\t\t\t\tlines.push(line);\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 fd: ${error.message}`)));\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tchild.on(\"close\", (code) => {\n\t\t\t\t\t\t\tcleanup();\n\t\t\t\t\t\t\tif (signal?.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\tconst output = lines.join(\"\\n\");\n\t\t\t\t\t\t\tif (code !== 0) {\n\t\t\t\t\t\t\t\tconst errorMsg = stderr.trim() || `fd exited with code ${code}`;\n\t\t\t\t\t\t\t\tif (!output) {\n\t\t\t\t\t\t\t\t\tsettle(() => reject(new Error(errorMsg)));\n\t\t\t\t\t\t\t\t\treturn;\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 relativized: string[] = [];\n\t\t\t\t\t\t\tfor (const rawLine of lines) {\n\t\t\t\t\t\t\t\tconst line = rawLine.replace(/\\r$/, \"\").trim();\n\t\t\t\t\t\t\t\tif (!line) continue;\n\t\t\t\t\t\t\t\tconst hadTrailingSlash = line.endsWith(\"/\") || line.endsWith(\"\\\\\");\n\t\t\t\t\t\t\t\tlet relativePath = line;\n\t\t\t\t\t\t\t\tif (line.startsWith(searchPath)) {\n\t\t\t\t\t\t\t\t\trelativePath = line.slice(searchPath.length + 1);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\trelativePath = path.relative(searchPath, line);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (hadTrailingSlash && !relativePath.endsWith(\"/\")) relativePath += \"/\";\n\t\t\t\t\t\t\t\trelativized.push(toPosixPath(relativePath));\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tconst formatted = formatFindResults(relativized, effectiveLimit, {\n\t\t\t\t\t\t\t\ttoolCallId,\n\t\t\t\t\t\t\t\tartifactStore,\n\t\t\t\t\t\t\t\tbroadQueryTracker,\n\t\t\t\t\t\t\t\tpattern: effectivePattern,\n\t\t\t\t\t\t\t\trawPath: searchDir,\n\t\t\t\t\t\t\t});\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: formatted.text }],\n\t\t\t\t\t\t\t\t\tdetails: Object.keys(formatted.details).length > 0 ? formatted.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 (e) {\n\t\t\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\t\t\tsettle(() => reject(new Error(\"Operation aborted\")));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst error = e instanceof Error ? e : new Error(String(e));\n\t\t\t\t\t\tsettle(() => reject(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(formatFindCall(args, theme, context.cwd));\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(formatFindResult(result as any, options, theme, context.showImages));\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport function createFindTool(cwd: string, options?: FindToolOptions): AgentTool<typeof findSchema> {\n\treturn wrapToolDefinition(createFindToolDefinition(cwd, options));\n}\n"]}
|
package/dist/core/tools/find.js
CHANGED
|
@@ -5,10 +5,13 @@ import path from "path";
|
|
|
5
5
|
import { Type } from "typebox";
|
|
6
6
|
import { keyHint } from "../../modes/interactive/components/keybinding-hints.js";
|
|
7
7
|
import { ensureTool } from "../../utils/tools-manager.js";
|
|
8
|
+
import { broadQueryInvalidationNote, formatArtifactNotice, normalizeBroadQueryKey, packToolOutput, } from "../context/tool-output-packer.js";
|
|
9
|
+
import { defaultFffSearchBackend, hasGitignoreInTree, relativePathInside, } from "./fff-search-backend.js";
|
|
8
10
|
import { pathExists, resolveToCwd } from "./path-utils.js";
|
|
9
11
|
import { getTextOutput, invalidArgText, shortenPath, str } from "./render-utils.js";
|
|
12
|
+
import { defaultSearchRouter } from "./search-router.js";
|
|
10
13
|
import { wrapToolDefinition } from "./tool-definition-wrapper.js";
|
|
11
|
-
import { DEFAULT_MAX_BYTES, formatSize
|
|
14
|
+
import { DEFAULT_MAX_BYTES, formatSize } from "./truncate.js";
|
|
12
15
|
function toPosixPath(value) {
|
|
13
16
|
return value.split(path.sep).join("/");
|
|
14
17
|
}
|
|
@@ -66,7 +69,105 @@ function formatFindResult(result, options, theme, showImages) {
|
|
|
66
69
|
}
|
|
67
70
|
return text;
|
|
68
71
|
}
|
|
69
|
-
function
|
|
72
|
+
function hasGlobSyntax(pattern) {
|
|
73
|
+
return pattern === "." || /[*?[{]/.test(pattern);
|
|
74
|
+
}
|
|
75
|
+
function fffQueryParts(parts) {
|
|
76
|
+
return parts.filter(Boolean).join(" ");
|
|
77
|
+
}
|
|
78
|
+
function toSearchRelative(repoRelativePath, searchPathRelativeToCwd) {
|
|
79
|
+
if (!searchPathRelativeToCwd)
|
|
80
|
+
return repoRelativePath;
|
|
81
|
+
const prefix = `${searchPathRelativeToCwd}/`;
|
|
82
|
+
if (!repoRelativePath.startsWith(prefix))
|
|
83
|
+
return undefined;
|
|
84
|
+
return repoRelativePath.slice(prefix.length);
|
|
85
|
+
}
|
|
86
|
+
function fffGlobPattern(pattern, searchPathRelativeToCwd) {
|
|
87
|
+
const effectivePattern = pattern === "." ? "**/*" : pattern;
|
|
88
|
+
if (!searchPathRelativeToCwd) {
|
|
89
|
+
if (effectivePattern.includes("/") || effectivePattern.startsWith("**/"))
|
|
90
|
+
return effectivePattern;
|
|
91
|
+
return `**/${effectivePattern}`;
|
|
92
|
+
}
|
|
93
|
+
if (effectivePattern === "**" || effectivePattern === "**/*")
|
|
94
|
+
return `${searchPathRelativeToCwd}/**/*`;
|
|
95
|
+
if (effectivePattern.includes("/"))
|
|
96
|
+
return `${searchPathRelativeToCwd}/${effectivePattern}`;
|
|
97
|
+
return `${searchPathRelativeToCwd}/**/${effectivePattern}`;
|
|
98
|
+
}
|
|
99
|
+
function fffSearchOutput(result, searchPathRelativeToCwd, effectiveLimit, packing) {
|
|
100
|
+
const relativized = result.items
|
|
101
|
+
.map((item) => toSearchRelative(item.relativePath, searchPathRelativeToCwd))
|
|
102
|
+
.filter((item) => Boolean(item));
|
|
103
|
+
return formatFindResults(relativized, effectiveLimit, packing);
|
|
104
|
+
}
|
|
105
|
+
async function tryFffFind(options) {
|
|
106
|
+
if (!(await pathExists(options.searchPath)))
|
|
107
|
+
return undefined;
|
|
108
|
+
const searchPathRelativeToCwd = relativePathInside(options.cwd, options.searchPath);
|
|
109
|
+
const glob = hasGlobSyntax(options.pattern);
|
|
110
|
+
const baseRoute = options.router.route({
|
|
111
|
+
tool: "find",
|
|
112
|
+
glob,
|
|
113
|
+
ignoreCase: Boolean(options.ignoreCase),
|
|
114
|
+
limit: options.effectiveLimit,
|
|
115
|
+
finderAvailable: true,
|
|
116
|
+
pathResolvable: searchPathRelativeToCwd !== undefined,
|
|
117
|
+
gitignoreInTree: false,
|
|
118
|
+
});
|
|
119
|
+
if (baseRoute.backend !== "fff")
|
|
120
|
+
return undefined;
|
|
121
|
+
if (searchPathRelativeToCwd === undefined)
|
|
122
|
+
return undefined;
|
|
123
|
+
const gitignoreInTree = await hasGitignoreInTree(options.searchPath);
|
|
124
|
+
const semanticRoute = options.router.route({
|
|
125
|
+
tool: "find",
|
|
126
|
+
glob,
|
|
127
|
+
ignoreCase: Boolean(options.ignoreCase),
|
|
128
|
+
limit: options.effectiveLimit,
|
|
129
|
+
finderAvailable: true,
|
|
130
|
+
pathResolvable: true,
|
|
131
|
+
gitignoreInTree,
|
|
132
|
+
});
|
|
133
|
+
if (semanticRoute.backend !== "fff")
|
|
134
|
+
return undefined;
|
|
135
|
+
const finder = await options.backend.getFinder(options.cwd);
|
|
136
|
+
const finderRoute = options.router.route({
|
|
137
|
+
tool: "find",
|
|
138
|
+
glob,
|
|
139
|
+
ignoreCase: Boolean(options.ignoreCase),
|
|
140
|
+
limit: options.effectiveLimit,
|
|
141
|
+
finderAvailable: Boolean(finder),
|
|
142
|
+
pathResolvable: true,
|
|
143
|
+
gitignoreInTree: false,
|
|
144
|
+
});
|
|
145
|
+
if (!finder || finderRoute.backend !== "fff")
|
|
146
|
+
return undefined;
|
|
147
|
+
const packing = {
|
|
148
|
+
toolCallId: options.toolCallId,
|
|
149
|
+
artifactStore: options.artifactStore,
|
|
150
|
+
broadQueryTracker: options.broadQueryTracker,
|
|
151
|
+
pattern: options.pattern,
|
|
152
|
+
rawPath: options.rawPath,
|
|
153
|
+
};
|
|
154
|
+
if (glob) {
|
|
155
|
+
const result = finder.glob(fffGlobPattern(options.pattern, searchPathRelativeToCwd), {
|
|
156
|
+
pageSize: options.effectiveLimit,
|
|
157
|
+
});
|
|
158
|
+
return result.ok
|
|
159
|
+
? fffSearchOutput(result.value, searchPathRelativeToCwd, options.effectiveLimit, packing)
|
|
160
|
+
: undefined;
|
|
161
|
+
}
|
|
162
|
+
const pathConstraint = searchPathRelativeToCwd ? `${searchPathRelativeToCwd}/` : "";
|
|
163
|
+
const result = finder.fileSearch(fffQueryParts([pathConstraint, options.pattern]), {
|
|
164
|
+
pageSize: options.effectiveLimit,
|
|
165
|
+
});
|
|
166
|
+
return result.ok
|
|
167
|
+
? fffSearchOutput(result.value, searchPathRelativeToCwd, options.effectiveLimit, packing)
|
|
168
|
+
: undefined;
|
|
169
|
+
}
|
|
170
|
+
function formatFindResults(relativized, effectiveLimit, packing) {
|
|
70
171
|
if (relativized.length === 0) {
|
|
71
172
|
return { text: "No files found matching pattern", details: {} };
|
|
72
173
|
}
|
|
@@ -99,17 +200,47 @@ function formatFindResults(relativized, effectiveLimit) {
|
|
|
99
200
|
const extSummary = `Extensions: ${extSummaryParts.join(", ")}`;
|
|
100
201
|
const resultLimitReached = relativized.length >= effectiveLimit;
|
|
101
202
|
const rawOutput = formattedLines.join("\n");
|
|
102
|
-
|
|
103
|
-
|
|
203
|
+
// Measure -> pack (artifact-backed if oversized and a store was provided) -> notices.
|
|
204
|
+
const packed = packToolOutput({
|
|
205
|
+
toolName: "find",
|
|
206
|
+
path: packing.rawPath,
|
|
207
|
+
rawContent: rawOutput,
|
|
208
|
+
// No line limit here because the result limit already caps rows; only the byte
|
|
209
|
+
// cap should apply, matching the pre-Slice-B truncateHead call exactly.
|
|
210
|
+
truncation: { maxLines: Number.MAX_SAFE_INTEGER },
|
|
211
|
+
}, packing.artifactStore, packing.toolCallId);
|
|
212
|
+
let resultOutput = packed.content;
|
|
104
213
|
const details = {};
|
|
105
214
|
const notices = [];
|
|
215
|
+
if (packed.artifactId) {
|
|
216
|
+
notices.push(formatArtifactNotice(packed.artifactId));
|
|
217
|
+
details.artifactId = packed.artifactId;
|
|
218
|
+
}
|
|
106
219
|
if (resultLimitReached) {
|
|
107
|
-
notices.push(`${effectiveLimit} results limit reached`);
|
|
220
|
+
notices.push(`${effectiveLimit} results limit reached. Use limit=${effectiveLimit * 2} for more, or narrow path/pattern`);
|
|
108
221
|
details.resultLimitReached = effectiveLimit;
|
|
109
222
|
}
|
|
110
|
-
if (truncation.truncated) {
|
|
223
|
+
if (packed.truncation.truncated) {
|
|
111
224
|
notices.push(`${formatSize(DEFAULT_MAX_BYTES)} limit reached`);
|
|
112
|
-
|
|
225
|
+
// Drop the duplicated bounded-preview text: it's already in the message's own
|
|
226
|
+
// content, and re-including it here can push `details` past
|
|
227
|
+
// MAX_RETAINED_TOOL_RESULT_DETAILS_BYTES (message-retention.ts), which replaces
|
|
228
|
+
// the *entire* details object with a stub -- silently losing artifactId and every
|
|
229
|
+
// other field alongside it. This is load-bearing beyond just the retention budget:
|
|
230
|
+
// agent-session.ts's _releaseGcPackedArtifactReferences() reads artifactId back off
|
|
231
|
+
// this same canonical message at eviction time (potentially many turns later), so
|
|
232
|
+
// keeping `details` small here is what keeps that release path working at all. If
|
|
233
|
+
// this field ever grows a large addition again, add a regression proving artifactId
|
|
234
|
+
// survives compactToolResultDetailsForRetention (see
|
|
235
|
+
// test/suite/agent-session-artifact-lifecycle.test.ts), not just a details-size check.
|
|
236
|
+
details.truncation = { ...packed.truncation, content: "" };
|
|
237
|
+
}
|
|
238
|
+
if (resultLimitReached || packed.truncation.truncated) {
|
|
239
|
+
const note = broadQueryInvalidationNote(packing.broadQueryTracker, normalizeBroadQueryKey({ toolName: "find", pattern: packing.pattern, path: packing.rawPath }), `find "${packing.pattern}" in ${packing.rawPath ?? "."}`);
|
|
240
|
+
if (note) {
|
|
241
|
+
notices.push(note);
|
|
242
|
+
details.invalidationCandidate = true;
|
|
243
|
+
}
|
|
113
244
|
}
|
|
114
245
|
if (relativized.length > 0) {
|
|
115
246
|
resultOutput += `\n\n[Summary - ${extSummary}]`;
|
|
@@ -121,6 +252,10 @@ function formatFindResults(relativized, effectiveLimit) {
|
|
|
121
252
|
}
|
|
122
253
|
export function createFindToolDefinition(cwd, options) {
|
|
123
254
|
const customOps = options?.operations;
|
|
255
|
+
const fffBackend = options?.fff === false ? undefined : (options?.fff ?? defaultFffSearchBackend);
|
|
256
|
+
const searchRouter = options?.searchRouter ?? defaultSearchRouter;
|
|
257
|
+
const artifactStore = options?.artifactStore;
|
|
258
|
+
const broadQueryTracker = options?.broadQueryTracker;
|
|
124
259
|
return {
|
|
125
260
|
name: "find",
|
|
126
261
|
label: "find",
|
|
@@ -128,7 +263,7 @@ export function createFindToolDefinition(cwd, options) {
|
|
|
128
263
|
promptSnippet: "Find files by glob pattern (respects .gitignore)",
|
|
129
264
|
parameters: findSchema,
|
|
130
265
|
toolGroup: "explore",
|
|
131
|
-
async execute(
|
|
266
|
+
async execute(toolCallId, { pattern, path: searchDir, limit, ignoreCase, }, signal, _onUpdate, _ctx) {
|
|
132
267
|
return new Promise((resolve, reject) => {
|
|
133
268
|
if (signal?.aborted) {
|
|
134
269
|
reject(new Error("Operation aborted"));
|
|
@@ -158,6 +293,32 @@ export function createFindToolDefinition(cwd, options) {
|
|
|
158
293
|
if (pattern === ".") {
|
|
159
294
|
effectivePattern = "**/*";
|
|
160
295
|
}
|
|
296
|
+
if (!customOps && fffBackend) {
|
|
297
|
+
const fffResult = await tryFffFind({
|
|
298
|
+
backend: fffBackend,
|
|
299
|
+
router: searchRouter,
|
|
300
|
+
cwd,
|
|
301
|
+
searchPath,
|
|
302
|
+
pattern: effectivePattern,
|
|
303
|
+
ignoreCase,
|
|
304
|
+
effectiveLimit,
|
|
305
|
+
toolCallId,
|
|
306
|
+
artifactStore,
|
|
307
|
+
broadQueryTracker,
|
|
308
|
+
rawPath: searchDir,
|
|
309
|
+
});
|
|
310
|
+
if (signal?.aborted) {
|
|
311
|
+
settle(() => reject(new Error("Operation aborted")));
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
if (fffResult) {
|
|
315
|
+
settle(() => resolve({
|
|
316
|
+
content: [{ type: "text", text: fffResult.text }],
|
|
317
|
+
details: Object.keys(fffResult.details).length > 0 ? fffResult.details : undefined,
|
|
318
|
+
}));
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
161
322
|
// If custom operations provide glob(), use that instead of fd.
|
|
162
323
|
if (customOps?.glob) {
|
|
163
324
|
if (!(await ops.exists(searchPath))) {
|
|
@@ -182,7 +343,13 @@ export function createFindToolDefinition(cwd, options) {
|
|
|
182
343
|
return toPosixPath(p.slice(searchPath.length + 1));
|
|
183
344
|
return toPosixPath(path.relative(searchPath, p));
|
|
184
345
|
});
|
|
185
|
-
const formatted = formatFindResults(relativized, effectiveLimit
|
|
346
|
+
const formatted = formatFindResults(relativized, effectiveLimit, {
|
|
347
|
+
toolCallId,
|
|
348
|
+
artifactStore,
|
|
349
|
+
broadQueryTracker,
|
|
350
|
+
pattern: effectivePattern,
|
|
351
|
+
rawPath: searchDir,
|
|
352
|
+
});
|
|
186
353
|
settle(() => resolve({
|
|
187
354
|
content: [{ type: "text", text: formatted.text }],
|
|
188
355
|
details: Object.keys(formatted.details).length > 0 ? formatted.details : undefined,
|
|
@@ -279,7 +446,13 @@ export function createFindToolDefinition(cwd, options) {
|
|
|
279
446
|
relativePath += "/";
|
|
280
447
|
relativized.push(toPosixPath(relativePath));
|
|
281
448
|
}
|
|
282
|
-
const formatted = formatFindResults(relativized, effectiveLimit
|
|
449
|
+
const formatted = formatFindResults(relativized, effectiveLimit, {
|
|
450
|
+
toolCallId,
|
|
451
|
+
artifactStore,
|
|
452
|
+
broadQueryTracker,
|
|
453
|
+
pattern: effectivePattern,
|
|
454
|
+
rawPath: searchDir,
|
|
455
|
+
});
|
|
283
456
|
settle(() => resolve({
|
|
284
457
|
content: [{ type: "text", text: formatted.text }],
|
|
285
458
|
details: Object.keys(formatted.details).length > 0 ? formatted.details : undefined,
|