@duckmind/dm-windows-x64 0.61.4 → 0.61.9
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/dm.exe +0 -0
- package/extensions/.dm-extensions.json +211 -67
- package/extensions/dm-subagents/agents/claude-code-writer.md +15 -0
- package/extensions/dm-subagents/agents/claude-code.md +15 -0
- package/extensions/dm-subagents/agents/codex-exec-writer.md +15 -0
- package/extensions/dm-subagents/agents/codex-exec.md +15 -0
- package/extensions/dm-subagents/agents/cursor-agent-writer.md +14 -0
- package/extensions/dm-subagents/agents/cursor-agent.md +14 -0
- package/extensions/dm-subagents/agents/delegate.md +3 -2
- package/extensions/dm-subagents/agents/oracle.md +10 -5
- package/extensions/dm-subagents/agents/researcher.md +2 -2
- package/extensions/dm-subagents/agents/reviewer.md +17 -7
- package/extensions/dm-subagents/agents/scout.md +5 -5
- package/extensions/dm-subagents/agents/worker.md +6 -2
- package/extensions/dm-subagents/async-retention-discovery-worker.mjs +167 -0
- package/extensions/dm-subagents/index.js +4 -0
- package/extensions/dm-subagents/inspector-runner.mjs +10 -0
- package/extensions/dm-subagents/install.mjs +3 -2
- package/extensions/dm-subagents/package.json +2 -2
- package/extensions/dm-subagents/prompts/council.md +60 -0
- package/extensions/dm-subagents/prompts/parallel-review.md +5 -1
- package/extensions/dm-subagents/prompts/review-loop.md +13 -7
- package/extensions/dm-subagents/skills/council-mode/SKILL.md +59 -0
- package/extensions/dm-subagents/skills/council-mode/references/pass-contracts.md +150 -0
- package/extensions/dm-subagents/skills/dm-subagents/SKILL.md +96 -911
- package/extensions/dm-subagents/skills/dm-subagents/references/constraints-and-recipes.md +70 -0
- package/extensions/dm-subagents/skills/dm-subagents/references/execution-controls.md +539 -0
- package/extensions/dm-subagents/skills/dm-subagents/references/management-authoring-rpc.md +161 -0
- package/extensions/dm-subagents/skills/dm-subagents/references/multi-lane-orchestration.md +51 -0
- package/extensions/dm-subagents/skills/dm-subagents/references/prompting-and-roles.md +295 -0
- package/extensions/dm-subagents/skills/dm-subagents/references/review-and-validation.md +73 -0
- package/extensions/dm-subagents/src/agents/agent-management.js +716 -484
- package/extensions/dm-subagents/src/agents/agent-refinements.js +563 -0
- package/extensions/dm-subagents/src/agents/agent-serializer.js +70 -5
- package/extensions/dm-subagents/src/agents/agents.js +1447 -299
- package/extensions/dm-subagents/src/agents/builtin-names.js +15 -0
- package/extensions/dm-subagents/src/agents/chain-serializer.js +12 -7
- package/extensions/dm-subagents/src/agents/frontmatter.js +64 -14
- package/extensions/dm-subagents/src/agents/identity.js +1 -1
- package/extensions/dm-subagents/src/agents/proactive-skills.js +14 -11
- package/extensions/dm-subagents/src/agents/runtime-agent-events.js +49 -0
- package/extensions/dm-subagents/src/agents/runtime-agent-registry.js +412 -0
- package/extensions/dm-subagents/src/agents/skills.js +52 -35
- package/extensions/dm-subagents/src/api/agents.js +6 -0
- package/extensions/dm-subagents/src/api/background-work.js +151 -0
- package/extensions/dm-subagents/src/api/capability-ceiling.js +12 -0
- package/extensions/dm-subagents/src/api/control-channel.js +3 -0
- package/extensions/dm-subagents/src/api/delegation.js +5 -0
- package/extensions/dm-subagents/src/api/dm-args.js +3 -0
- package/extensions/dm-subagents/src/api/external-job-provider.js +137 -0
- package/extensions/dm-subagents/src/api/external-runs.js +233 -0
- package/extensions/dm-subagents/src/api/intercom-bridge.js +3 -0
- package/extensions/dm-subagents/src/api/preflight.js +322 -0
- package/extensions/dm-subagents/src/api/project-panes.js +11 -0
- package/extensions/dm-subagents/src/api/shared-types.js +4 -0
- package/extensions/dm-subagents/src/extension/config.js +175 -0
- package/extensions/dm-subagents/src/extension/control-notices.js +4 -43
- package/extensions/dm-subagents/src/extension/doctor.js +71 -17
- package/extensions/dm-subagents/src/extension/fanout-child.js +49 -32
- package/extensions/dm-subagents/src/extension/index.js +711 -265
- package/extensions/dm-subagents/src/extension/public-execution.js +114 -0
- package/extensions/dm-subagents/src/extension/rpc.js +427 -22
- package/extensions/dm-subagents/src/extension/schemas.js +158 -65
- package/extensions/dm-subagents/src/extension/steering-notices.js +23 -0
- package/extensions/dm-subagents/src/extension/subagent-guide.js +31 -0
- package/extensions/dm-subagents/src/extension/tool-description.js +92 -74
- package/extensions/dm-subagents/src/extension/tool-result.js +7 -0
- package/extensions/dm-subagents/src/inspectors/herdr/actions.js +218 -0
- package/extensions/dm-subagents/src/inspectors/herdr/client.js +123 -0
- package/extensions/dm-subagents/src/inspectors/herdr/focus.js +47 -0
- package/extensions/dm-subagents/src/inspectors/herdr/inspector-runner.js +160 -0
- package/extensions/dm-subagents/src/inspectors/herdr/project-panes.js +618 -0
- package/extensions/dm-subagents/src/inspectors/herdr/session-roots-codec.js +21 -0
- package/extensions/dm-subagents/src/inspectors/herdr/shell-command.js +15 -0
- package/extensions/dm-subagents/src/integrations/herdr-status.js +377 -0
- package/extensions/dm-subagents/src/intercom/intercom-bridge.js +17 -13
- package/extensions/dm-subagents/src/intercom/native-supervisor-channel.js +371 -79
- package/extensions/dm-subagents/src/intercom/result-intercom.js +47 -7
- package/extensions/dm-subagents/src/missions/actions.js +394 -0
- package/extensions/dm-subagents/src/missions/goal-driver.js +149 -0
- package/extensions/dm-subagents/src/missions/lifecycle.js +331 -0
- package/extensions/dm-subagents/src/missions/store.js +548 -0
- package/extensions/dm-subagents/src/missions/types.js +9 -0
- package/extensions/dm-subagents/src/missions/workflow-state.js +245 -0
- package/extensions/dm-subagents/src/policy/authority.js +37 -0
- package/extensions/dm-subagents/src/profiles/profiles.js +36 -18
- package/extensions/dm-subagents/src/runs/background/active-async-capacity.js +427 -0
- package/extensions/dm-subagents/src/runs/background/active-run-index.js +122 -0
- package/extensions/dm-subagents/src/runs/background/async-execution.js +925 -137
- package/extensions/dm-subagents/src/runs/background/async-job-tracker.js +515 -148
- package/extensions/dm-subagents/src/runs/background/async-resume.js +378 -51
- package/extensions/dm-subagents/src/runs/background/async-retention.js +828 -0
- package/extensions/dm-subagents/src/runs/background/async-status-snapshot.js +31 -0
- package/extensions/dm-subagents/src/runs/background/async-status.js +259 -22
- package/extensions/dm-subagents/src/runs/background/auto-drain.js +46 -0
- package/extensions/dm-subagents/src/runs/background/chain-append.js +50 -15
- package/extensions/dm-subagents/src/runs/background/chain-root-attachment.js +67 -12
- package/extensions/dm-subagents/src/runs/background/completion-batcher.js +5 -1
- package/extensions/dm-subagents/src/runs/background/completion-dedupe.js +3 -11
- package/extensions/dm-subagents/src/runs/background/completion-replay.js +245 -0
- package/extensions/dm-subagents/src/runs/background/control-channel.js +423 -36
- package/extensions/dm-subagents/src/runs/background/fleet-view.js +132 -59
- package/extensions/dm-subagents/src/runs/background/index-segment.js +38 -0
- package/extensions/dm-subagents/src/runs/background/inspect-rpc.js +373 -0
- package/extensions/dm-subagents/src/runs/background/notify.js +357 -57
- package/extensions/dm-subagents/src/runs/background/owned-process-tree.js +86 -0
- package/extensions/dm-subagents/src/runs/background/process-terminal.js +269 -0
- package/extensions/dm-subagents/src/runs/background/result-delivery-ownership.js +34 -0
- package/extensions/dm-subagents/src/runs/background/result-files.js +469 -0
- package/extensions/dm-subagents/src/runs/background/result-watcher.js +540 -75
- package/extensions/dm-subagents/src/runs/background/resume-guidance.js +44 -0
- package/extensions/dm-subagents/src/runs/background/retained-children.js +119 -0
- package/extensions/dm-subagents/src/runs/background/run-id-query.js +5 -0
- package/extensions/dm-subagents/src/runs/background/run-id-resolver.js +93 -9
- package/extensions/dm-subagents/src/runs/background/run-status.js +303 -32
- package/extensions/dm-subagents/src/runs/background/scheduled-runs.js +784 -376
- package/extensions/dm-subagents/src/runs/background/stale-run-reconciler.js +75 -34
- package/extensions/dm-subagents/src/runs/background/steering.js +221 -0
- package/extensions/dm-subagents/src/runs/background/subagent-runner.js +3106 -844
- package/extensions/dm-subagents/src/runs/background/subagent-wait.js +529 -0
- package/extensions/dm-subagents/src/runs/background/terminal-run-index.js +106 -0
- package/extensions/dm-subagents/src/runs/background/top-level-async.js +1 -1
- package/extensions/dm-subagents/src/runs/background/wait-completions.js +155 -0
- package/extensions/dm-subagents/src/runs/background/wait-config.js +46 -0
- package/extensions/dm-subagents/src/runs/background/wait-subscriptions.js +278 -0
- package/extensions/dm-subagents/src/runs/background/wait-tool.js +47 -0
- package/extensions/dm-subagents/src/runs/foreground/async-dismiss-action.js +81 -0
- package/extensions/dm-subagents/src/runs/foreground/async-steering-action.js +245 -0
- package/extensions/dm-subagents/src/runs/foreground/async-stop-action.js +74 -0
- package/extensions/dm-subagents/src/runs/foreground/execution.js +1401 -342
- package/extensions/dm-subagents/src/runs/foreground/foreground-control.js +133 -0
- package/extensions/dm-subagents/src/runs/foreground/foreground-history.js +148 -0
- package/extensions/dm-subagents/src/runs/foreground/prompt-audit.js +139 -0
- package/extensions/dm-subagents/src/runs/foreground/subagent-executor.js +4278 -1441
- package/extensions/dm-subagents/src/runs/foreground/workflow-detach-reconcile.js +278 -0
- package/extensions/dm-subagents/src/runs/foreground/workflow-foreground-steering.js +155 -0
- package/extensions/dm-subagents/src/runs/shared/abort-recovery.js +97 -0
- package/extensions/dm-subagents/src/runs/shared/acceptance.js +597 -148
- package/extensions/dm-subagents/src/runs/shared/agent-contract.js +35 -0
- package/extensions/dm-subagents/src/runs/shared/async-status-projection.js +472 -0
- package/extensions/dm-subagents/src/runs/shared/background-process-options.js +6 -0
- package/extensions/dm-subagents/src/runs/shared/capability-ceiling.js +175 -0
- package/extensions/dm-subagents/src/runs/shared/child-identity.js +32 -0
- package/extensions/dm-subagents/src/runs/shared/child-launch-plan.js +65 -0
- package/extensions/dm-subagents/src/runs/shared/child-protocol.js +447 -0
- package/extensions/dm-subagents/src/runs/shared/claude-code-adapter.js +120 -0
- package/extensions/dm-subagents/src/runs/shared/codex-exec-adapter.js +129 -0
- package/extensions/dm-subagents/src/runs/shared/completion-evidence.js +40 -0
- package/extensions/dm-subagents/src/runs/shared/completion-guard.js +140 -83
- package/extensions/dm-subagents/src/runs/shared/context-mode.js +38 -0
- package/extensions/dm-subagents/src/runs/shared/cursor-agent-adapter.js +101 -0
- package/extensions/dm-subagents/src/runs/shared/dm-args.js +445 -72
- package/extensions/dm-subagents/src/runs/shared/dm-spawn.js +27 -16
- package/extensions/dm-subagents/src/runs/shared/dynamic-fanout.js +19 -6
- package/extensions/dm-subagents/src/runs/shared/extension-bindings.js +81 -0
- package/extensions/dm-subagents/src/runs/shared/external-cli-contract.js +134 -0
- package/extensions/dm-subagents/src/runs/shared/external-cli-preflight.js +98 -0
- package/extensions/dm-subagents/src/runs/shared/external-cli-runner.js +419 -0
- package/extensions/dm-subagents/src/runs/shared/external-job-bridge.js +404 -0
- package/extensions/dm-subagents/src/runs/shared/external-job-runner.js +334 -0
- package/extensions/dm-subagents/src/runs/shared/fast-mode-extension.js +8 -0
- package/extensions/dm-subagents/src/runs/shared/host-step-status.js +228 -0
- package/extensions/dm-subagents/src/runs/shared/lane-metadata.js +104 -0
- package/extensions/dm-subagents/src/runs/shared/launch-cwd.js +17 -0
- package/extensions/dm-subagents/src/runs/shared/llm-intent-arbiter.js +190 -0
- package/extensions/dm-subagents/src/runs/shared/long-running-guard.js +48 -3
- package/extensions/dm-subagents/src/runs/shared/mcp-config-sources.js +387 -0
- package/extensions/dm-subagents/src/runs/shared/mcp-direct-tool-allowlist.js +212 -137
- package/extensions/dm-subagents/src/runs/shared/mcp-direct-tool-grant.js +131 -0
- package/extensions/dm-subagents/src/runs/shared/model-exclusions.js +207 -0
- package/extensions/dm-subagents/src/runs/shared/model-fallback.js +225 -55
- package/extensions/dm-subagents/src/runs/shared/model-scope.js +85 -28
- package/extensions/dm-subagents/src/runs/shared/mutation-evidence.js +182 -0
- package/extensions/dm-subagents/src/runs/shared/nested-events.js +264 -102
- package/extensions/dm-subagents/src/runs/shared/nested-render.js +15 -5
- package/extensions/dm-subagents/src/runs/shared/orca-progress-tabs.js +505 -0
- package/extensions/dm-subagents/src/runs/shared/parallel-handoff.js +653 -0
- package/extensions/dm-subagents/src/runs/shared/parallel-utils.js +29 -12
- package/extensions/dm-subagents/src/runs/shared/permissions.js +108 -0
- package/extensions/dm-subagents/src/runs/shared/process-signal.js +13 -0
- package/extensions/dm-subagents/src/runs/shared/run-fanout-budget.js +257 -0
- package/extensions/dm-subagents/src/runs/shared/run-history.js +133 -13
- package/extensions/dm-subagents/src/runs/shared/runtime-acknowledged-extensions.js +62 -0
- package/extensions/dm-subagents/src/runs/shared/session-lease.js +225 -0
- package/extensions/dm-subagents/src/runs/shared/single-output.js +129 -27
- package/extensions/dm-subagents/src/runs/shared/spawn-budget.js +95 -0
- package/extensions/dm-subagents/src/runs/shared/structured-output.js +129 -10
- package/extensions/dm-subagents/src/runs/shared/subagent-control.js +66 -11
- package/extensions/dm-subagents/src/runs/shared/subagent-prompt-runtime.js +548 -70
- package/extensions/dm-subagents/src/runs/shared/subagent-startup-retry.js +50 -0
- package/extensions/dm-subagents/src/runs/shared/task-intent.js +130 -0
- package/extensions/dm-subagents/src/runs/shared/tool-availability.js +59 -0
- package/extensions/dm-subagents/src/runs/shared/tool-budget.js +7 -5
- package/extensions/dm-subagents/src/runs/shared/tool-timeout.js +64 -0
- package/extensions/dm-subagents/src/runs/shared/usage-budget.js +74 -0
- package/extensions/dm-subagents/src/runs/shared/workflow-graph.js +22 -0
- package/extensions/dm-subagents/src/runs/shared/worktree-cleanup-plan.js +721 -0
- package/extensions/dm-subagents/src/runs/shared/worktree.js +168 -19
- package/extensions/dm-subagents/src/shared/accessible-dir.js +35 -0
- package/extensions/dm-subagents/src/shared/agent-stream-options.js +3 -0
- package/extensions/dm-subagents/src/shared/artifacts.js +170 -11
- package/extensions/dm-subagents/src/shared/atomic-json.js +36 -38
- package/extensions/dm-subagents/src/shared/capacity-resilient-json.js +77 -0
- package/extensions/dm-subagents/src/shared/child-session-name.js +15 -0
- package/extensions/dm-subagents/src/shared/child-transcript.js +57 -2
- package/extensions/dm-subagents/src/shared/completion-owner.js +7 -0
- package/extensions/dm-subagents/src/shared/display-text.js +142 -0
- package/extensions/dm-subagents/src/shared/extension-context.js +17 -0
- package/extensions/dm-subagents/src/shared/file-coalescer.js +9 -0
- package/extensions/dm-subagents/src/shared/file-system-retry.js +56 -0
- package/extensions/dm-subagents/src/shared/fork-context.js +96 -33
- package/extensions/dm-subagents/src/shared/formatters.js +21 -7
- package/extensions/dm-subagents/src/shared/launch-contract.js +94 -0
- package/extensions/dm-subagents/src/shared/model-info.js +10 -5
- package/extensions/dm-subagents/src/shared/node-executable.js +19 -0
- package/extensions/dm-subagents/src/shared/prompt-resources.js +10 -0
- package/extensions/dm-subagents/src/shared/pruned-fork.js +427 -0
- package/extensions/dm-subagents/src/shared/session-file-trust.js +19 -0
- package/extensions/dm-subagents/src/shared/session-tokens.js +14 -3
- package/extensions/dm-subagents/src/shared/settings.js +39 -47
- package/extensions/dm-subagents/src/shared/shortcuts.js +16 -0
- package/extensions/dm-subagents/src/shared/status-format.js +9 -2
- package/extensions/dm-subagents/src/shared/thinking-ceiling.js +41 -0
- package/extensions/dm-subagents/src/shared/types.js +47 -7
- package/extensions/dm-subagents/src/shared/utf8.js +12 -0
- package/extensions/dm-subagents/src/shared/utils.js +151 -131
- package/extensions/dm-subagents/src/shared/watch-strategy.js +3 -0
- package/extensions/dm-subagents/src/shared/workflow-child-permit.js +84 -0
- package/extensions/dm-subagents/src/slash/delegation-adapters.js +274 -0
- package/extensions/dm-subagents/src/slash/delegation-json.js +113 -0
- package/extensions/dm-subagents/src/slash/delegation-request.js +152 -0
- package/extensions/dm-subagents/src/slash/prompt-template-bridge.js +294 -243
- package/extensions/dm-subagents/src/slash/prompt-workflows.js +35 -73
- package/extensions/dm-subagents/src/slash/selector.js +101 -0
- package/extensions/dm-subagents/src/slash/slash-bridge.js +17 -1
- package/extensions/dm-subagents/src/slash/slash-commands.js +722 -732
- package/extensions/dm-subagents/src/slash/slash-live-state.js +37 -19
- package/extensions/dm-subagents/src/slash/subagents-admin.js +410 -0
- package/extensions/dm-subagents/src/tui/fleet-status.js +824 -0
- package/extensions/dm-subagents/src/tui/fleet-transcript.js +479 -0
- package/extensions/dm-subagents/src/tui/fleet.js +1326 -0
- package/extensions/dm-subagents/src/tui/render-helpers.js +22 -0
- package/extensions/dm-subagents/src/tui/render.js +1511 -261
- package/extensions/dm-subagents/src/watchdog/change-signature.js +220 -0
- package/extensions/dm-subagents/src/watchdog/child-status.js +151 -0
- package/extensions/dm-subagents/src/watchdog/emission-guard.js +90 -0
- package/extensions/dm-subagents/src/watchdog/lsp-diagnostics.js +484 -0
- package/extensions/dm-subagents/src/watchdog/model-selection.js +154 -0
- package/extensions/dm-subagents/src/watchdog/permission-arbiter.js +138 -0
- package/extensions/dm-subagents/src/watchdog/register-child.js +112 -0
- package/extensions/dm-subagents/src/watchdog/register-main.js +419 -0
- package/extensions/dm-subagents/src/watchdog/render.js +54 -0
- package/extensions/dm-subagents/src/watchdog/review.js +251 -0
- package/extensions/dm-subagents/src/watchdog/runtime.js +803 -0
- package/extensions/dm-subagents/src/watchdog/scope.js +56 -0
- package/extensions/dm-subagents/src/watchdog/settings.js +515 -0
- package/extensions/dm-subagents/src/watchdog/tool-actions.js +151 -0
- package/extensions/dm-subagents/src/watchdog/turn-delta.js +169 -0
- package/extensions/dm-subagents/src/watchdog/types.js +29 -0
- package/extensions/dm-subagents/src/watchdog/warning-format.js +58 -0
- package/extensions/dm-subagents/src/workflows/chat-progress.js +116 -0
- package/extensions/dm-subagents/src/workflows/host-command.js +227 -0
- package/extensions/dm-subagents/src/workflows/scripted-workflow.js +2011 -0
- package/extensions/dm-subagents/src/workflows/workflow-child-summary.js +116 -0
- package/extensions/dm-subagents/src/workflows/workflow-preflight.js +243 -0
- package/extensions/dm-subagents/src/workflows/workflow-receipt.js +387 -0
- package/extensions/dm-subagents/src/workflows/workflow-settlement.js +189 -0
- package/package.json +4 -3
- package/extensions/dm-fff/package.json +0 -21
- package/extensions/dm-fff/src/index.js +0 -691
- package/extensions/dm-fff/src/query.js +0 -60
- package/extensions/dm-subagents/agents/context-builder.md +0 -46
- package/extensions/dm-subagents/agents/planner.md +0 -55
- package/extensions/dm-subagents/prompts/parallel-context-build.md +0 -55
- package/extensions/dm-subagents/prompts/parallel-handoff-plan.md +0 -61
- package/extensions/dm-subagents/src/runs/background/wait.js +0 -206
- package/extensions/dm-subagents/src/runs/foreground/chain-clarify.js +0 -1013
- package/extensions/dm-subagents/src/runs/foreground/chain-execution.js +0 -981
- package/extensions/dm-subagents/src/runs/shared/turn-budget.js +0 -50
|
@@ -1,691 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Text
|
|
3
|
-
} from "@duckmind/dm-tui";
|
|
4
|
-
import { FileFinder } from "@ff-labs/fff-node";
|
|
5
|
-
import { Type } from "@sinclair/typebox";
|
|
6
|
-
import { buildQuery } from "./query";
|
|
7
|
-
const DEFAULT_GREP_LIMIT = 20;
|
|
8
|
-
const DEFAULT_FIND_LIMIT = 30;
|
|
9
|
-
const GREP_MAX_LINE_LENGTH = 500;
|
|
10
|
-
const MENTION_MAX_RESULTS = 20;
|
|
11
|
-
const VALID_MODES = ["tools-and-ui", "tools-only", "override"];
|
|
12
|
-
const FFF_TOOL_NAMES = {
|
|
13
|
-
grep: "ffgrep",
|
|
14
|
-
find: "fffind",
|
|
15
|
-
multiGrep: "fff-multi-grep"
|
|
16
|
-
};
|
|
17
|
-
const OVERRIDE_TOOL_NAMES = {
|
|
18
|
-
grep: "grep",
|
|
19
|
-
find: "find",
|
|
20
|
-
multiGrep: "multi_grep"
|
|
21
|
-
};
|
|
22
|
-
function resolveToolNames(mode) {
|
|
23
|
-
return mode === "override" ? OVERRIDE_TOOL_NAMES : FFF_TOOL_NAMES;
|
|
24
|
-
}
|
|
25
|
-
const cursorCache = new Map;
|
|
26
|
-
let cursorCounter = 0;
|
|
27
|
-
function storeCursor(cursor) {
|
|
28
|
-
const id = `fff_c${++cursorCounter}`;
|
|
29
|
-
cursorCache.set(id, cursor);
|
|
30
|
-
if (cursorCache.size > 200) {
|
|
31
|
-
const first = cursorCache.keys().next().value;
|
|
32
|
-
if (first)
|
|
33
|
-
cursorCache.delete(first);
|
|
34
|
-
}
|
|
35
|
-
return id;
|
|
36
|
-
}
|
|
37
|
-
function getCursor(id) {
|
|
38
|
-
return cursorCache.get(id);
|
|
39
|
-
}
|
|
40
|
-
const findCursorCache = new Map;
|
|
41
|
-
let findCursorCounter = 0;
|
|
42
|
-
function storeFindCursor(cursor) {
|
|
43
|
-
const id = `${++findCursorCounter}`;
|
|
44
|
-
findCursorCache.set(id, cursor);
|
|
45
|
-
if (findCursorCache.size > 200) {
|
|
46
|
-
const first = findCursorCache.keys().next().value;
|
|
47
|
-
if (first)
|
|
48
|
-
findCursorCache.delete(first);
|
|
49
|
-
}
|
|
50
|
-
return id;
|
|
51
|
-
}
|
|
52
|
-
function getFindCursor(id) {
|
|
53
|
-
return findCursorCache.get(id);
|
|
54
|
-
}
|
|
55
|
-
function truncateLine(line, max = GREP_MAX_LINE_LENGTH) {
|
|
56
|
-
const trimmed = line.trim();
|
|
57
|
-
return trimmed.length <= max ? trimmed : `${trimmed.slice(0, max)}...`;
|
|
58
|
-
}
|
|
59
|
-
const HOT_FRECENCY = 25;
|
|
60
|
-
const WARM_FRECENCY = 20;
|
|
61
|
-
export function fffFileAnnotation(item) {
|
|
62
|
-
const git = item.gitStatus;
|
|
63
|
-
if (git && git !== "clean" && git !== "unknown" && git !== "") {
|
|
64
|
-
return ` [${git} in git]`;
|
|
65
|
-
}
|
|
66
|
-
const frecency = item.totalFrecencyScore ?? item.accessFrecencyScore ?? 0;
|
|
67
|
-
if (frecency >= HOT_FRECENCY)
|
|
68
|
-
return " [VERY often touched file]";
|
|
69
|
-
if (frecency >= WARM_FRECENCY)
|
|
70
|
-
return " [often touched file]";
|
|
71
|
-
return "";
|
|
72
|
-
}
|
|
73
|
-
function formatGrepOutput(result) {
|
|
74
|
-
if (result.items.length === 0)
|
|
75
|
-
return "No matches found";
|
|
76
|
-
const lines = [];
|
|
77
|
-
let currentFile = "";
|
|
78
|
-
let shown = 0;
|
|
79
|
-
for (const match of result.items) {
|
|
80
|
-
if (match.relativePath !== currentFile) {
|
|
81
|
-
if (lines.length > 0)
|
|
82
|
-
lines.push("");
|
|
83
|
-
currentFile = match.relativePath;
|
|
84
|
-
lines.push(`${currentFile}${fffFileAnnotation(match)}`);
|
|
85
|
-
}
|
|
86
|
-
match.contextBefore?.forEach((line, i) => {
|
|
87
|
-
const lineNum = match.lineNumber - match.contextBefore.length + i;
|
|
88
|
-
lines.push(` ${lineNum}- ${truncateLine(line)}`);
|
|
89
|
-
});
|
|
90
|
-
lines.push(` ${match.lineNumber}: ${truncateLine(match.lineContent)}`);
|
|
91
|
-
shown++;
|
|
92
|
-
match.contextAfter?.forEach((line, i) => {
|
|
93
|
-
const lineNum = match.lineNumber + 1 + i;
|
|
94
|
-
lines.push(` ${lineNum}- ${truncateLine(line)}`);
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
return lines.join(`
|
|
98
|
-
`);
|
|
99
|
-
}
|
|
100
|
-
const FIND_WEAK_SAMPLE_SIZE = 5;
|
|
101
|
-
function weakScoreThreshold(pattern) {
|
|
102
|
-
const perfect = pattern.length * 12;
|
|
103
|
-
return Math.floor(perfect * 50 / 100);
|
|
104
|
-
}
|
|
105
|
-
function formatFindOutput(result, limit, pattern) {
|
|
106
|
-
if (result.items.length === 0) {
|
|
107
|
-
return {
|
|
108
|
-
output: "No files found matching pattern",
|
|
109
|
-
weak: false,
|
|
110
|
-
shownCount: 0
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
const reordered = result.items.map((item) => ({ item }));
|
|
114
|
-
const topScore = result.scores[0]?.total ?? 0;
|
|
115
|
-
const weak = topScore < weakScoreThreshold(pattern);
|
|
116
|
-
const effective = weak ? Math.min(FIND_WEAK_SAMPLE_SIZE, limit) : limit;
|
|
117
|
-
const shown = reordered.slice(0, effective);
|
|
118
|
-
return {
|
|
119
|
-
output: shown.map((p) => `${p.item.relativePath}${fffFileAnnotation(p.item)}`).join(`
|
|
120
|
-
`),
|
|
121
|
-
weak,
|
|
122
|
-
shownCount: shown.length
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
function extractAtPrefix(textBeforeCursor) {
|
|
126
|
-
const match = textBeforeCursor.match(/(?:^|[ \t])(@(?:"[^"]*|[^\s]*))$/);
|
|
127
|
-
return match?.[1] ?? null;
|
|
128
|
-
}
|
|
129
|
-
function buildAtCompletionValue(path) {
|
|
130
|
-
return path.includes(" ") ? `@"${path}"` : `@${path}`;
|
|
131
|
-
}
|
|
132
|
-
function createFffMentionProvider(getItems) {
|
|
133
|
-
return {
|
|
134
|
-
async getSuggestions(lines, cursorLine, cursorCol, options) {
|
|
135
|
-
const currentLine = lines[cursorLine] || "";
|
|
136
|
-
const prefix = extractAtPrefix(currentLine.slice(0, cursorCol));
|
|
137
|
-
if (!prefix || options.signal.aborted)
|
|
138
|
-
return null;
|
|
139
|
-
const query = prefix.startsWith('@"') ? prefix.slice(2) : prefix.slice(1);
|
|
140
|
-
const items = await getItems(query, options.signal);
|
|
141
|
-
return options.signal.aborted || items.length === 0 ? null : { items, prefix };
|
|
142
|
-
},
|
|
143
|
-
applyCompletion(_lines, cursorLine, cursorCol, item, prefix) {
|
|
144
|
-
const currentLine = _lines[cursorLine] || "";
|
|
145
|
-
const before = currentLine.slice(0, cursorCol - prefix.length);
|
|
146
|
-
const after = currentLine.slice(cursorCol);
|
|
147
|
-
const newLine = before + item.value + after;
|
|
148
|
-
const newCursorCol = cursorCol - prefix.length + item.value.length;
|
|
149
|
-
return {
|
|
150
|
-
lines: [..._lines.slice(0, cursorLine), newLine, ..._lines.slice(cursorLine + 1)],
|
|
151
|
-
cursorLine,
|
|
152
|
-
cursorCol: newCursorCol
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
export default function fffExtension(dm) {
|
|
158
|
-
let finder = null;
|
|
159
|
-
let finderCwd = null;
|
|
160
|
-
let finderPromise = null;
|
|
161
|
-
let activeCwd = process.cwd();
|
|
162
|
-
let currentMode = dm.getFlag("fff-mode") ?? process.env.DM_FFF_MODE ?? "tools-and-ui";
|
|
163
|
-
const toolNames = resolveToolNames(currentMode);
|
|
164
|
-
const frecencyDbPath = dm.getFlag("fff-frecency-db") ?? process.env.FFF_FRECENCY_DB ?? undefined;
|
|
165
|
-
const historyDbPath = dm.getFlag("fff-history-db") ?? process.env.FFF_HISTORY_DB ?? undefined;
|
|
166
|
-
function resolveBoolOpt(flagName, envName) {
|
|
167
|
-
const flag = dm.getFlag(flagName);
|
|
168
|
-
if (typeof flag === "boolean")
|
|
169
|
-
return flag;
|
|
170
|
-
if (typeof flag === "string")
|
|
171
|
-
return flag === "true" || flag === "1";
|
|
172
|
-
const env = process.env[envName];
|
|
173
|
-
return env === "1" || env === "true";
|
|
174
|
-
}
|
|
175
|
-
const enableFsRootScanning = resolveBoolOpt("fff-enable-root-scan", "FFF_ENABLE_ROOT_SCAN");
|
|
176
|
-
function getMode() {
|
|
177
|
-
return currentMode;
|
|
178
|
-
}
|
|
179
|
-
function setMode(mode) {
|
|
180
|
-
currentMode = mode;
|
|
181
|
-
}
|
|
182
|
-
function shouldEnableMentions() {
|
|
183
|
-
return currentMode !== "tools-only";
|
|
184
|
-
}
|
|
185
|
-
function ensureFinder(cwd) {
|
|
186
|
-
if (finder && !finder.isDestroyed && finderCwd === cwd)
|
|
187
|
-
return Promise.resolve(finder);
|
|
188
|
-
if (finderPromise)
|
|
189
|
-
return finderPromise;
|
|
190
|
-
finderPromise = (async () => {
|
|
191
|
-
if (finder && !finder.isDestroyed) {
|
|
192
|
-
finder.destroy();
|
|
193
|
-
finder = null;
|
|
194
|
-
finderCwd = null;
|
|
195
|
-
}
|
|
196
|
-
const result = FileFinder.create({
|
|
197
|
-
basePath: cwd,
|
|
198
|
-
frecencyDbPath,
|
|
199
|
-
historyDbPath,
|
|
200
|
-
aiMode: true,
|
|
201
|
-
enableHomeDirScanning: true,
|
|
202
|
-
enableFsRootScanning
|
|
203
|
-
});
|
|
204
|
-
if (!result.ok)
|
|
205
|
-
throw new Error(`Failed to create FFF file finder: ${result.error}`);
|
|
206
|
-
finder = result.value;
|
|
207
|
-
finderCwd = cwd;
|
|
208
|
-
await finder.waitForScan(15000);
|
|
209
|
-
return finder;
|
|
210
|
-
})().finally(() => {
|
|
211
|
-
finderPromise = null;
|
|
212
|
-
});
|
|
213
|
-
return finderPromise;
|
|
214
|
-
}
|
|
215
|
-
function destroyFinder() {
|
|
216
|
-
if (finder && !finder.isDestroyed) {
|
|
217
|
-
finder.destroy();
|
|
218
|
-
finder = null;
|
|
219
|
-
finderCwd = null;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
async function getMentionItems(query, signal) {
|
|
223
|
-
if (signal.aborted)
|
|
224
|
-
return [];
|
|
225
|
-
const f = await ensureFinder(activeCwd);
|
|
226
|
-
if (signal.aborted)
|
|
227
|
-
return [];
|
|
228
|
-
const result = f.mixedSearch(query, { pageSize: MENTION_MAX_RESULTS });
|
|
229
|
-
if (!result.ok)
|
|
230
|
-
return [];
|
|
231
|
-
return result.value.items.slice(0, MENTION_MAX_RESULTS).map((mixed) => {
|
|
232
|
-
if (mixed.type === "directory") {
|
|
233
|
-
return {
|
|
234
|
-
value: buildAtCompletionValue(mixed.item.relativePath),
|
|
235
|
-
label: mixed.item.dirName,
|
|
236
|
-
description: mixed.item.relativePath
|
|
237
|
-
};
|
|
238
|
-
}
|
|
239
|
-
return {
|
|
240
|
-
value: buildAtCompletionValue(mixed.item.relativePath),
|
|
241
|
-
label: mixed.item.fileName,
|
|
242
|
-
description: mixed.item.relativePath
|
|
243
|
-
};
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
function registerAutocompleteProvider(ctx) {
|
|
247
|
-
ctx.ui.addAutocompleteProvider((current) => {
|
|
248
|
-
const mentionProvider = createFffMentionProvider(getMentionItems);
|
|
249
|
-
return {
|
|
250
|
-
async getSuggestions(lines, cursorLine, cursorCol, options) {
|
|
251
|
-
if (shouldEnableMentions()) {
|
|
252
|
-
try {
|
|
253
|
-
const mentionResult = await mentionProvider.getSuggestions(lines, cursorLine, cursorCol, options);
|
|
254
|
-
if (mentionResult)
|
|
255
|
-
return mentionResult;
|
|
256
|
-
} catch {}
|
|
257
|
-
}
|
|
258
|
-
return current.getSuggestions(lines, cursorLine, cursorCol, options);
|
|
259
|
-
},
|
|
260
|
-
applyCompletion(lines, cursorLine, cursorCol, item, prefix) {
|
|
261
|
-
return current.applyCompletion(lines, cursorLine, cursorCol, item, prefix);
|
|
262
|
-
},
|
|
263
|
-
shouldTriggerFileCompletion(lines, cursorLine, cursorCol) {
|
|
264
|
-
return current.shouldTriggerFileCompletion?.(lines, cursorLine, cursorCol) ?? true;
|
|
265
|
-
}
|
|
266
|
-
};
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
dm.registerFlag("fff-mode", {
|
|
270
|
-
description: "FFF mode: tools-and-ui | tools-only | override",
|
|
271
|
-
type: "string"
|
|
272
|
-
});
|
|
273
|
-
dm.registerFlag("fff-frecency-db", {
|
|
274
|
-
description: "Path to the frecency database (overrides FFF_FRECENCY_DB env)",
|
|
275
|
-
type: "string"
|
|
276
|
-
});
|
|
277
|
-
dm.registerFlag("fff-history-db", {
|
|
278
|
-
description: "Path to the query history database (overrides FFF_HISTORY_DB env)",
|
|
279
|
-
type: "string"
|
|
280
|
-
});
|
|
281
|
-
dm.registerFlag("fff-enable-root-scan", {
|
|
282
|
-
description: "Allow indexing when launched from the filesystem root (also: FFF_ENABLE_ROOT_SCAN env)",
|
|
283
|
-
type: "boolean"
|
|
284
|
-
});
|
|
285
|
-
dm.on("session_start", async (_event, ctx) => {
|
|
286
|
-
try {
|
|
287
|
-
activeCwd = ctx.cwd;
|
|
288
|
-
const entries = ctx.sessionManager?.getEntries();
|
|
289
|
-
if (entries) {
|
|
290
|
-
const modeEntry = [...entries].reverse().find((e) => e.type === "custom" && e.customType === "fff-mode");
|
|
291
|
-
if (modeEntry && typeof modeEntry.data?.mode === "string" && VALID_MODES.includes(modeEntry.data.mode)) {
|
|
292
|
-
const restored = modeEntry.data.mode;
|
|
293
|
-
if (restored !== currentMode) {
|
|
294
|
-
currentMode = restored;
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
registerAutocompleteProvider(ctx);
|
|
299
|
-
await ensureFinder(activeCwd);
|
|
300
|
-
} catch (e) {
|
|
301
|
-
ctx.ui.notify(`FFF init failed: ${e instanceof Error ? e.message : String(e)}`, "error");
|
|
302
|
-
}
|
|
303
|
-
});
|
|
304
|
-
dm.on("session_shutdown", async () => {
|
|
305
|
-
destroyFinder();
|
|
306
|
-
});
|
|
307
|
-
const renderTextResult = (result, options, theme, context, maxLines = 15) => {
|
|
308
|
-
const text = context.lastComponent ?? new Text("", 0, 0);
|
|
309
|
-
const output = result.content?.find((c) => c.type === "text")?.text?.trim() ?? "";
|
|
310
|
-
if (!output) {
|
|
311
|
-
text.setText(theme.fg("muted", "No output"));
|
|
312
|
-
return text;
|
|
313
|
-
}
|
|
314
|
-
const lines = output.split(`
|
|
315
|
-
`);
|
|
316
|
-
const displayLines = lines.slice(0, options.expanded ? lines.length : maxLines);
|
|
317
|
-
let content = `
|
|
318
|
-
${displayLines.map((line) => theme.fg("toolOutput", line)).join(`
|
|
319
|
-
`)}`;
|
|
320
|
-
if (lines.length > displayLines.length) {
|
|
321
|
-
content += theme.fg("muted", `
|
|
322
|
-
... (${lines.length - displayLines.length} more lines)`);
|
|
323
|
-
}
|
|
324
|
-
text.setText(content);
|
|
325
|
-
return text;
|
|
326
|
-
};
|
|
327
|
-
const grepSchema = Type.Object({
|
|
328
|
-
pattern: Type.String({
|
|
329
|
-
description: "Search pattern (literal text or regex)"
|
|
330
|
-
}),
|
|
331
|
-
path: Type.Optional(Type.String({
|
|
332
|
-
description: "Repo-relative path constraint. Directory prefix (src/ or src/foo/), bare filename with extension (main.rs), or glob (*.ts, src/**/*.cc, {src,lib}/**). Applied to the full repo-relative path."
|
|
333
|
-
})),
|
|
334
|
-
exclude: Type.Optional(Type.Union([Type.String(), Type.Array(Type.String())], {
|
|
335
|
-
description: "Exclude paths (comma/space-separated or array). Same syntax as path: directory prefix ('test/'), filename with extension ('config.json'), or glob ('*.min.js', '**/*.{rs,go}'). A leading '!' is optional and ignored — both 'test/' and '!test/' work. Example: 'test/,*.min.js,!vendor/'."
|
|
336
|
-
})),
|
|
337
|
-
caseSensitive: Type.Optional(Type.Boolean({
|
|
338
|
-
description: "Force case-sensitive matching. Default uses smart-case (case-insensitive when pattern is all lowercase)."
|
|
339
|
-
})),
|
|
340
|
-
context: Type.Optional(Type.Number({ description: "Context lines before+after each match" })),
|
|
341
|
-
limit: Type.Optional(Type.Number({
|
|
342
|
-
description: `Max matches (default ${DEFAULT_GREP_LIMIT})`
|
|
343
|
-
})),
|
|
344
|
-
cursor: Type.Optional(Type.String({ description: "Pagination cursor from previous result" }))
|
|
345
|
-
});
|
|
346
|
-
dm.registerTool({
|
|
347
|
-
name: toolNames.grep,
|
|
348
|
-
label: toolNames.grep,
|
|
349
|
-
description: `Grep file contents. Smart-case, auto-detects regex vs literal, git-aware. Results are ranked by frecency (most-accessed files first); matches within a file stay in source order. Default limit ${DEFAULT_GREP_LIMIT}.`,
|
|
350
|
-
promptSnippet: "Grep contents",
|
|
351
|
-
promptGuidelines: [
|
|
352
|
-
"Prefer bare identifiers as patterns. Literal queries are most efficient.",
|
|
353
|
-
"Use path for include ('src/', '*.ts') and exclude for noise ('test/,*.min.js').",
|
|
354
|
-
"caseSensitive: true when you need exact case (smart-case otherwise).",
|
|
355
|
-
"After 1-2 greps, read the top match instead of more greps."
|
|
356
|
-
],
|
|
357
|
-
parameters: grepSchema,
|
|
358
|
-
async execute(_toolCallId, params, signal) {
|
|
359
|
-
if (signal?.aborted)
|
|
360
|
-
throw new Error("Operation aborted");
|
|
361
|
-
const f = await ensureFinder(activeCwd);
|
|
362
|
-
const effectiveLimit = Math.max(1, params.limit ?? DEFAULT_GREP_LIMIT);
|
|
363
|
-
const query = buildQuery(params.path, params.pattern, params.exclude, activeCwd);
|
|
364
|
-
const hasRegexSyntax = params.pattern !== params.pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
365
|
-
let mode = hasRegexSyntax ? "regex" : "plain";
|
|
366
|
-
if (mode === "regex") {
|
|
367
|
-
try {
|
|
368
|
-
new RegExp(params.pattern);
|
|
369
|
-
} catch {
|
|
370
|
-
mode = "plain";
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
const p = params.pattern.trim();
|
|
374
|
-
const isWildcardOnly = hasRegexSyntax && /^(?:[.^$]*(?:[.][*+?]|\*|\+)[.^$]*|[.^$\s]*|\.\*\??|\.\*[+?]?|\.\+\??|\.|\*|\?)$/.test(p);
|
|
375
|
-
if (isWildcardOnly) {
|
|
376
|
-
return {
|
|
377
|
-
content: [
|
|
378
|
-
{
|
|
379
|
-
type: "text",
|
|
380
|
-
text: `Pattern '${params.pattern}' matches everything — grep needs a concrete substring or identifier. Example: \`pattern: 'MyClass'\` or \`pattern: 'export function'\`.`
|
|
381
|
-
}
|
|
382
|
-
],
|
|
383
|
-
details: { totalMatched: 0, totalFiles: 0 }
|
|
384
|
-
};
|
|
385
|
-
}
|
|
386
|
-
const smartCase = params.caseSensitive !== true;
|
|
387
|
-
const grepResult = f.grep(query, {
|
|
388
|
-
mode,
|
|
389
|
-
smartCase,
|
|
390
|
-
maxMatchesPerFile: Math.min(effectiveLimit, 50),
|
|
391
|
-
cursor: (params.cursor ? getCursor(params.cursor) : null) ?? null,
|
|
392
|
-
beforeContext: params.context ?? 0,
|
|
393
|
-
afterContext: params.context ?? 0,
|
|
394
|
-
classifyDefinitions: true
|
|
395
|
-
});
|
|
396
|
-
if (!grepResult.ok)
|
|
397
|
-
throw new Error(grepResult.error);
|
|
398
|
-
let result = grepResult.value;
|
|
399
|
-
let fuzzyNotice = null;
|
|
400
|
-
if (result.items.length === 0 && !params.cursor && mode !== "regex") {
|
|
401
|
-
const fuzzy = f.grep(params.pattern, {
|
|
402
|
-
mode: "fuzzy",
|
|
403
|
-
smartCase,
|
|
404
|
-
maxMatchesPerFile: Math.min(effectiveLimit, 50),
|
|
405
|
-
cursor: null,
|
|
406
|
-
beforeContext: 0,
|
|
407
|
-
afterContext: 0,
|
|
408
|
-
classifyDefinitions: true
|
|
409
|
-
});
|
|
410
|
-
if (fuzzy.ok && fuzzy.value.items.length > 0) {
|
|
411
|
-
fuzzyNotice = `0 exact matches. Maybe you meant this?`;
|
|
412
|
-
result = fuzzy.value;
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
let output = formatGrepOutput(result);
|
|
416
|
-
const notices = [];
|
|
417
|
-
if (result.regexFallbackError) {
|
|
418
|
-
notices.push(`Invalid regex: ${result.regexFallbackError}, used literal match`);
|
|
419
|
-
}
|
|
420
|
-
if (result.nextCursor) {
|
|
421
|
-
notices.push(`Continue with cursor="${storeCursor(result.nextCursor)}"`);
|
|
422
|
-
}
|
|
423
|
-
if (notices.length > 0)
|
|
424
|
-
output += `
|
|
425
|
-
|
|
426
|
-
[${notices.join(". ")}]`;
|
|
427
|
-
if (fuzzyNotice)
|
|
428
|
-
output = `[${fuzzyNotice}]
|
|
429
|
-
${output}`;
|
|
430
|
-
return {
|
|
431
|
-
content: [{ type: "text", text: output }],
|
|
432
|
-
details: {
|
|
433
|
-
totalMatched: result.totalMatched,
|
|
434
|
-
totalFiles: result.totalFiles
|
|
435
|
-
}
|
|
436
|
-
};
|
|
437
|
-
},
|
|
438
|
-
renderCall(args, theme, context) {
|
|
439
|
-
const text = context.lastComponent ?? new Text("", 0, 0);
|
|
440
|
-
const pattern = args?.pattern ?? "";
|
|
441
|
-
const path = args?.path ?? ".";
|
|
442
|
-
let content = theme.fg("toolTitle", theme.bold(toolNames.grep)) + " " + theme.fg("accent", `/${pattern}/`) + theme.fg("toolOutput", ` in ${path}`);
|
|
443
|
-
if (args?.limit !== undefined)
|
|
444
|
-
content += theme.fg("toolOutput", ` limit ${args.limit}`);
|
|
445
|
-
if (args?.cursor)
|
|
446
|
-
content += theme.fg("muted", ` (page)`);
|
|
447
|
-
text.setText(content);
|
|
448
|
-
return text;
|
|
449
|
-
},
|
|
450
|
-
renderResult(result, options, theme, context) {
|
|
451
|
-
return renderTextResult(result, options, theme, context, 15);
|
|
452
|
-
}
|
|
453
|
-
});
|
|
454
|
-
const findSchema = Type.Object({
|
|
455
|
-
pattern: Type.String({
|
|
456
|
-
description: "Fuzzy filename search and glob search. Frecency-ranked, git-aware. Multi-word = narrower (AND) not bound to order, use for multi word related concept search. Prefer this over ls/find/bash as the first exploration step whenever the user names a concept, feature, or symbol — it surfaces the relevant files in one call. Only use ls/read on a directory when you specifically need the alphabetical layout of an unknown repo, or when a concept search returned nothing."
|
|
457
|
-
}),
|
|
458
|
-
path: Type.Optional(Type.String({
|
|
459
|
-
description: "Repo-relative path constraint. Directory prefix (src/ or src/foo/), bare filename with extension (main.rs), or glob (*.ts, src/**/*.cc, {src,lib}/**). Applied to the full repo-relative path."
|
|
460
|
-
})),
|
|
461
|
-
exclude: Type.Optional(Type.Union([Type.String(), Type.Array(Type.String())], {
|
|
462
|
-
description: "Exclude paths (comma/space-separated or array). Same syntax as path: directory prefix ('test/'), filename with extension ('config.json'), or glob ('*.min.js', '**/*.{rs,go}'). A leading '!' is optional and ignored — both 'test/' and '!test/' work. Example: 'test/,*.min.js,!vendor/'."
|
|
463
|
-
})),
|
|
464
|
-
limit: Type.Optional(Type.Number({
|
|
465
|
-
description: `Max results per page (default ${DEFAULT_FIND_LIMIT})`
|
|
466
|
-
})),
|
|
467
|
-
cursor: Type.Optional(Type.String({ description: "Pagination cursor from previous result" }))
|
|
468
|
-
});
|
|
469
|
-
dm.registerTool({
|
|
470
|
-
name: toolNames.find,
|
|
471
|
-
label: toolNames.find,
|
|
472
|
-
description: `Fuzzy path search and glob search. Matches against the whole repo-relative path, not just the filename. Frecency-ranked, git-aware. Multi-word = narrower (AND). Default limit ${DEFAULT_FIND_LIMIT}.`,
|
|
473
|
-
promptSnippet: "Find files by path or glob",
|
|
474
|
-
promptGuidelines: [
|
|
475
|
-
"Matches the WHOLE path, not just the filename — `profile` hits `chrome/browser/profiles/x.cc` too.",
|
|
476
|
-
"Keep queries to 1-2 terms; extra words narrow.",
|
|
477
|
-
"Use for paths, not content. Use grep for content.",
|
|
478
|
-
"For exact path matches use a glob in `path` — e.g. path: '**/profile.h' for exact filename, or path: 'src/**/profile.h' scoped to a subtree. Bare patterns are fuzzy.",
|
|
479
|
-
"To list everything inside a directory, pass path: 'dir/**' with an empty or wildcard pattern instead of using pattern alone.",
|
|
480
|
-
"Use exclude: 'test/,*.min.js' to cut noise in large repos."
|
|
481
|
-
],
|
|
482
|
-
parameters: findSchema,
|
|
483
|
-
async execute(_toolCallId, params, signal) {
|
|
484
|
-
if (signal?.aborted)
|
|
485
|
-
throw new Error("Operation aborted");
|
|
486
|
-
const f = await ensureFinder(activeCwd);
|
|
487
|
-
const resumed = params.cursor ? getFindCursor(params.cursor) : undefined;
|
|
488
|
-
const effectiveLimit = resumed ? resumed.pageSize : Math.max(1, params.limit ?? DEFAULT_FIND_LIMIT);
|
|
489
|
-
const query = resumed ? resumed.query : buildQuery(params.path, params.pattern, params.exclude, activeCwd);
|
|
490
|
-
const pattern = resumed ? resumed.pattern : params.pattern;
|
|
491
|
-
const pageIndex = resumed?.nextPageIndex ?? 0;
|
|
492
|
-
const searchResult = f.fileSearch(query, {
|
|
493
|
-
pageIndex,
|
|
494
|
-
pageSize: effectiveLimit
|
|
495
|
-
});
|
|
496
|
-
if (!searchResult.ok)
|
|
497
|
-
throw new Error(searchResult.error);
|
|
498
|
-
const result = searchResult.value;
|
|
499
|
-
const formatted = formatFindOutput(result, effectiveLimit, pattern);
|
|
500
|
-
let output = formatted.output;
|
|
501
|
-
const shownSoFar = pageIndex * effectiveLimit + result.items.length;
|
|
502
|
-
const hasMore = result.items.length >= effectiveLimit && result.totalMatched > shownSoFar;
|
|
503
|
-
const notices = [];
|
|
504
|
-
if (formatted.weak && formatted.shownCount > 0)
|
|
505
|
-
notices.push(`Query "${pattern}" produced only weak scattered fuzzy matches. Output capped at ${formatted.shownCount}/${result.totalMatched}.`);
|
|
506
|
-
if (!formatted.weak && hasMore) {
|
|
507
|
-
const remaining = result.totalMatched - shownSoFar;
|
|
508
|
-
const cursorId = storeFindCursor({
|
|
509
|
-
query,
|
|
510
|
-
pattern,
|
|
511
|
-
pageSize: effectiveLimit,
|
|
512
|
-
nextPageIndex: pageIndex + 1
|
|
513
|
-
});
|
|
514
|
-
notices.push(`${remaining} more match${remaining === 1 ? "" : "es"} available. cursor="${cursorId}" to continue`);
|
|
515
|
-
}
|
|
516
|
-
if (notices.length > 0)
|
|
517
|
-
output += `
|
|
518
|
-
|
|
519
|
-
[${notices.join(". ")}]`;
|
|
520
|
-
return {
|
|
521
|
-
content: [{ type: "text", text: output }],
|
|
522
|
-
details: {
|
|
523
|
-
totalMatched: result.totalMatched,
|
|
524
|
-
totalFiles: result.totalFiles,
|
|
525
|
-
pageIndex,
|
|
526
|
-
hasMore
|
|
527
|
-
}
|
|
528
|
-
};
|
|
529
|
-
},
|
|
530
|
-
renderCall(args, theme, context) {
|
|
531
|
-
const text = context.lastComponent ?? new Text("", 0, 0);
|
|
532
|
-
const pattern = args?.pattern ?? "";
|
|
533
|
-
const path = args?.path ?? ".";
|
|
534
|
-
let content = theme.fg("toolTitle", theme.bold(toolNames.find)) + " " + theme.fg("accent", pattern) + theme.fg("toolOutput", ` in ${path}`);
|
|
535
|
-
if (args?.limit !== undefined)
|
|
536
|
-
content += theme.fg("toolOutput", ` (limit ${args.limit})`);
|
|
537
|
-
if (args?.cursor)
|
|
538
|
-
content += theme.fg("muted", ` (page)`);
|
|
539
|
-
text.setText(content);
|
|
540
|
-
return text;
|
|
541
|
-
},
|
|
542
|
-
renderResult(result, options, theme, context) {
|
|
543
|
-
return renderTextResult(result, options, theme, context, 20);
|
|
544
|
-
}
|
|
545
|
-
});
|
|
546
|
-
const enableMultiGrep = process.env.DM_FFF_MULTIGREP === "1";
|
|
547
|
-
if (enableMultiGrep) {
|
|
548
|
-
const multiGrepSchema = Type.Object({
|
|
549
|
-
patterns: Type.Array(Type.String(), {
|
|
550
|
-
description: "Literal patterns (OR). Include snake_case/camelCase/PascalCase variants."
|
|
551
|
-
}),
|
|
552
|
-
constraints: Type.Optional(Type.String({ description: "File filter, e.g. '*.{ts,tsx} !test/'" })),
|
|
553
|
-
context: Type.Optional(Type.Number({ description: "Context lines before+after" })),
|
|
554
|
-
limit: Type.Optional(Type.Number({
|
|
555
|
-
description: `Max matches (default ${DEFAULT_GREP_LIMIT})`
|
|
556
|
-
})),
|
|
557
|
-
cursor: Type.Optional(Type.String({ description: "Pagination cursor" }))
|
|
558
|
-
});
|
|
559
|
-
dm.registerTool({
|
|
560
|
-
name: toolNames.multiGrep,
|
|
561
|
-
label: toolNames.multiGrep,
|
|
562
|
-
description: "Search file contents for ANY of multiple literal patterns (OR, SIMD Aho-Corasick). Faster than regex alternation.",
|
|
563
|
-
promptSnippet: "Multi-pattern OR content search",
|
|
564
|
-
promptGuidelines: [
|
|
565
|
-
"Use when searching for several identifiers at once.",
|
|
566
|
-
"Include all naming-convention variants (snake/camel/Pascal).",
|
|
567
|
-
"Patterns are literal. Use constraints for file filters."
|
|
568
|
-
],
|
|
569
|
-
parameters: multiGrepSchema,
|
|
570
|
-
async execute(_toolCallId, params, signal) {
|
|
571
|
-
if (signal?.aborted)
|
|
572
|
-
throw new Error("Operation aborted");
|
|
573
|
-
if (!params.patterns?.length)
|
|
574
|
-
throw new Error("patterns array must have at least 1 element");
|
|
575
|
-
const f = await ensureFinder(activeCwd);
|
|
576
|
-
const effectiveLimit = Math.max(1, params.limit ?? DEFAULT_GREP_LIMIT);
|
|
577
|
-
const grepResult = f.multiGrep({
|
|
578
|
-
patterns: params.patterns,
|
|
579
|
-
constraints: params.constraints,
|
|
580
|
-
maxMatchesPerFile: Math.min(effectiveLimit, 50),
|
|
581
|
-
smartCase: true,
|
|
582
|
-
cursor: (params.cursor ? getCursor(params.cursor) : null) ?? null,
|
|
583
|
-
beforeContext: params.context ?? 0,
|
|
584
|
-
afterContext: params.context ?? 0
|
|
585
|
-
});
|
|
586
|
-
if (!grepResult.ok)
|
|
587
|
-
throw new Error(grepResult.error);
|
|
588
|
-
const result = grepResult.value;
|
|
589
|
-
let output = formatGrepOutput(result);
|
|
590
|
-
const notices = [];
|
|
591
|
-
if (result.items.length >= effectiveLimit)
|
|
592
|
-
notices.push(`${effectiveLimit}+ matches (refine patterns)`);
|
|
593
|
-
if (result.nextCursor)
|
|
594
|
-
notices.push(`More available. cursor="${storeCursor(result.nextCursor)}" to continue`);
|
|
595
|
-
if (notices.length > 0)
|
|
596
|
-
output += `
|
|
597
|
-
|
|
598
|
-
[${notices.join(". ")}]`;
|
|
599
|
-
return {
|
|
600
|
-
content: [{ type: "text", text: output }],
|
|
601
|
-
details: {
|
|
602
|
-
totalMatched: result.totalMatched,
|
|
603
|
-
totalFiles: result.totalFiles,
|
|
604
|
-
patterns: params.patterns
|
|
605
|
-
}
|
|
606
|
-
};
|
|
607
|
-
},
|
|
608
|
-
renderCall(args, theme, context) {
|
|
609
|
-
const text = context.lastComponent ?? new Text("", 0, 0);
|
|
610
|
-
const patterns = args?.patterns ?? [];
|
|
611
|
-
const constraints = args?.constraints;
|
|
612
|
-
let content = theme.fg("toolTitle", theme.bold(toolNames.multiGrep)) + " " + theme.fg("accent", patterns.map((p) => `"${p}"`).join(", "));
|
|
613
|
-
if (constraints)
|
|
614
|
-
content += theme.fg("toolOutput", ` (${constraints})`);
|
|
615
|
-
if (args?.cursor)
|
|
616
|
-
content += theme.fg("muted", ` (page)`);
|
|
617
|
-
text.setText(content);
|
|
618
|
-
return text;
|
|
619
|
-
},
|
|
620
|
-
renderResult(result, options, theme, context) {
|
|
621
|
-
return renderTextResult(result, options, theme, context, 15);
|
|
622
|
-
}
|
|
623
|
-
});
|
|
624
|
-
}
|
|
625
|
-
dm.registerCommand("fff-mode", {
|
|
626
|
-
description: "Show or set FFF mode: /fff-mode [tools-and-ui | tools-only | override]",
|
|
627
|
-
handler: async (args, ctx) => {
|
|
628
|
-
const arg = (args || "").trim();
|
|
629
|
-
if (!arg) {
|
|
630
|
-
const mode = getMode();
|
|
631
|
-
const flag = dm.getFlag("fff-mode") ?? "unset";
|
|
632
|
-
ctx.ui.notify(`Current mode: '${mode}' (flag: ${flag})`, "info");
|
|
633
|
-
return;
|
|
634
|
-
}
|
|
635
|
-
if (!VALID_MODES.includes(arg)) {
|
|
636
|
-
ctx.ui.notify(`Usage: /fff-mode [${VALID_MODES.join(" | ")}]`, "warning");
|
|
637
|
-
return;
|
|
638
|
-
}
|
|
639
|
-
const newMode = arg;
|
|
640
|
-
const oldMode = getMode();
|
|
641
|
-
setMode(newMode);
|
|
642
|
-
dm.appendEntry("fff-mode", { mode: newMode });
|
|
643
|
-
const note = oldMode === "override" !== (newMode === "override") ? " (tool name change requires /reload)" : "";
|
|
644
|
-
ctx.ui.notify(`Mode changed: '${oldMode}' → '${newMode}'${note}`, "info");
|
|
645
|
-
}
|
|
646
|
-
});
|
|
647
|
-
dm.registerCommand("fff-health", {
|
|
648
|
-
description: "Show FFF file finder health and status",
|
|
649
|
-
handler: async (_args, ctx) => {
|
|
650
|
-
if (!finder || finder.isDestroyed) {
|
|
651
|
-
ctx.ui.notify("FFF not initialized", "warning");
|
|
652
|
-
return;
|
|
653
|
-
}
|
|
654
|
-
const health = finder.healthCheck();
|
|
655
|
-
if (!health.ok) {
|
|
656
|
-
ctx.ui.notify(`Health check failed: ${health.error}`, "error");
|
|
657
|
-
return;
|
|
658
|
-
}
|
|
659
|
-
const h = health.value;
|
|
660
|
-
const lines = [
|
|
661
|
-
`FFF v${h.version}`,
|
|
662
|
-
`Mode: ${getMode()}`,
|
|
663
|
-
`Git: ${h.git.repositoryFound ? `yes (${h.git.workdir ?? "unknown"})` : "no"}`,
|
|
664
|
-
`Picker: ${h.filePicker.initialized ? `${h.filePicker.indexedFiles ?? 0} files` : "not initialized"}`,
|
|
665
|
-
`Frecency: ${h.frecency.initialized ? "active" : "disabled"}`,
|
|
666
|
-
`Query tracker: ${h.queryTracker.initialized ? "active" : "disabled"}`
|
|
667
|
-
];
|
|
668
|
-
const progress = finder.getScanProgress();
|
|
669
|
-
if (progress.ok) {
|
|
670
|
-
lines.push(`Scanning: ${progress.value.isScanning ? "yes" : "no"} (${progress.value.scannedFilesCount} files)`);
|
|
671
|
-
}
|
|
672
|
-
ctx.ui.notify(lines.join(`
|
|
673
|
-
`), "info");
|
|
674
|
-
}
|
|
675
|
-
});
|
|
676
|
-
dm.registerCommand("fff-rescan", {
|
|
677
|
-
description: "Trigger FFF to rescan files",
|
|
678
|
-
handler: async (_args, ctx) => {
|
|
679
|
-
if (!finder || finder.isDestroyed) {
|
|
680
|
-
ctx.ui.notify("FFF not initialized", "warning");
|
|
681
|
-
return;
|
|
682
|
-
}
|
|
683
|
-
const result = finder.scanFiles();
|
|
684
|
-
if (!result.ok) {
|
|
685
|
-
ctx.ui.notify(`Rescan failed: ${result.error}`, "error");
|
|
686
|
-
return;
|
|
687
|
-
}
|
|
688
|
-
ctx.ui.notify("FFF rescan triggered", "info");
|
|
689
|
-
}
|
|
690
|
-
});
|
|
691
|
-
}
|