@oh-my-pi/pi-coding-agent 17.2.3 → 17.2.5
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 +92 -0
- package/dist/{CHANGELOG-c5hpqt9r.md → CHANGELOG-q2w43aw3.md} +92 -0
- package/dist/cli.js +3979 -3548
- package/dist/{template-8vdv6xb4.js → template-c2hyaytt.js} +8 -26
- package/dist/types/advisor/advise-tool.d.ts +6 -0
- package/dist/types/advisor/runtime.d.ts +4 -4
- package/dist/types/capability/mcp.d.ts +9 -0
- package/dist/types/cli/args.d.ts +4 -1
- package/dist/types/cli/browser-relay-cli.d.ts +15 -0
- package/dist/types/cli/command-help.d.ts +99 -0
- package/dist/types/cli/help-extra.d.ts +2 -0
- package/dist/types/cli/models-cli.d.ts +1 -1
- package/dist/types/cli/thinking-levels.d.ts +5 -0
- package/dist/types/cli/update-cli.d.ts +13 -0
- package/dist/types/commands/browser-relay.d.ts +38 -0
- package/dist/types/commands/commit.d.ts +3 -0
- package/dist/types/commands/complete.d.ts +1 -1
- package/dist/types/commands/completions.d.ts +6 -0
- package/dist/types/commands/grep.d.ts +3 -0
- package/dist/types/commands/join.d.ts +4 -0
- package/dist/types/commands/launch-help.d.ts +171 -0
- package/dist/types/commands/launch.d.ts +5 -1
- package/dist/types/commands/models.d.ts +3 -0
- package/dist/types/commands/say.d.ts +10 -0
- package/dist/types/commands/worktree.d.ts +3 -0
- package/dist/types/config/keybindings.d.ts +6 -1
- package/dist/types/config/model-discovery.d.ts +2 -2
- package/dist/types/config/models-config-schema-bundle.d.ts +3 -0
- package/dist/types/config/models-config-schema.d.ts +2 -0
- package/dist/types/config/models-config.d.ts +1 -0
- package/dist/types/config/service-tier.d.ts +2 -0
- package/dist/types/config/settings-schema.d.ts +70 -22
- package/dist/types/cursor-bridge-tools.d.ts +1 -1
- package/dist/types/cursor.d.ts +7 -4
- package/dist/types/discovery/helpers.d.ts +6 -0
- package/dist/types/discovery/omp-extension-roots.d.ts +21 -2
- package/dist/types/edit/index.d.ts +3 -3
- package/dist/types/edit/modes/replace.d.ts +22 -15
- package/dist/types/edit/renderer.d.ts +1 -1
- package/dist/types/eval/py/kernel.d.ts +3 -1
- package/dist/types/eval/py/spawn-options.d.ts +12 -16
- package/dist/types/extensibility/extensions/loader.d.ts +6 -2
- package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +8 -1
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +1 -1
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +2 -0
- package/dist/types/extensibility/plugins/marketplace/registry.d.ts +2 -2
- package/dist/types/launch/broker.d.ts +1 -1
- package/dist/types/launch/client.d.ts +6 -3
- package/dist/types/launch/ensure.d.ts +8 -0
- package/dist/types/launch/paths.d.ts +3 -2
- package/dist/types/launch/protocol.d.ts +2 -2
- package/dist/types/lsp/client.d.ts +2 -0
- package/dist/types/lsp/mux/daemon.d.ts +15 -0
- package/dist/types/lsp/mux/protocol.d.ts +57 -0
- package/dist/types/lsp/mux/server.d.ts +18 -0
- package/dist/types/lsp/types.d.ts +22 -2
- package/dist/types/main.d.ts +2 -0
- package/dist/types/mcp/request-id.d.ts +19 -0
- package/dist/types/mcp/types.d.ts +14 -0
- package/dist/types/memory-backend/index.d.ts +1 -0
- package/dist/types/memory-backend/messages.d.ts +15 -0
- package/dist/types/mnemopi/embed-client.d.ts +1 -1
- package/dist/types/mnemopi/state.d.ts +0 -23
- package/dist/types/modes/components/assistant-message.d.ts +2 -0
- package/dist/types/modes/components/custom-editor.d.ts +2 -1
- package/dist/types/modes/components/model-browser.d.ts +7 -5
- package/dist/types/modes/components/model-picker.d.ts +9 -3
- package/dist/types/modes/components/read-tool-group.d.ts +11 -0
- package/dist/types/modes/components/stripped-tool-calls-placeholder.d.ts +12 -0
- package/dist/types/modes/components/tool-execution.d.ts +2 -0
- package/dist/types/modes/controllers/input-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/modes/types.d.ts +2 -1
- package/dist/types/modes/workflow.d.ts +2 -1
- package/dist/types/registry/agent-lifecycle.d.ts +14 -5
- package/dist/types/registry/agent-registry.d.ts +4 -3
- package/dist/types/sdk.d.ts +4 -2
- package/dist/types/secrets/index.d.ts +7 -9
- package/dist/types/session/agent-session-types.d.ts +3 -1
- package/dist/types/session/agent-session.d.ts +6 -1
- package/dist/types/session/indexed-session-storage.d.ts +1 -0
- package/dist/types/session/model-controls.d.ts +0 -1
- package/dist/types/session/session-advisors.d.ts +8 -1
- package/dist/types/session/session-context.d.ts +2 -1
- package/dist/types/session/session-maintenance.d.ts +2 -0
- package/dist/types/session/session-manager.d.ts +16 -5
- package/dist/types/session/session-stats.d.ts +10 -0
- package/dist/types/session/session-storage.d.ts +15 -3
- package/dist/types/system-prompt.d.ts +6 -1
- package/dist/types/task/index.d.ts +2 -1
- package/dist/types/task/spawn-policy.d.ts +5 -0
- package/dist/types/thinking.d.ts +1 -7
- package/dist/types/tools/ast-grep.d.ts +1 -1
- package/dist/types/tools/browser/registry.d.ts +2 -1
- package/dist/types/tools/browser/relay/bridge.d.ts +35 -0
- package/dist/types/tools/browser/relay/daemon.d.ts +16 -0
- package/dist/types/tools/browser/relay/kind.d.ts +19 -0
- package/dist/types/tools/browser/relay/protocol.d.ts +97 -0
- package/dist/types/tools/browser/relay/server.d.ts +35 -0
- package/dist/types/tools/browser/render.d.ts +1 -0
- package/dist/types/tools/browser.d.ts +3 -0
- package/dist/types/tools/computer/exposure.d.ts +9 -7
- package/dist/types/tools/computer/protocol.d.ts +70 -17
- package/dist/types/tools/computer/supervisor.d.ts +23 -10
- package/dist/types/tools/computer/worker-entry.d.ts +1 -0
- package/dist/types/tools/computer/worker.d.ts +42 -7
- package/dist/types/tools/computer-renderer.d.ts +7 -5
- package/dist/types/tools/computer.d.ts +25 -44
- package/dist/types/tools/context.d.ts +3 -0
- package/dist/types/tools/default-renderer.d.ts +3 -0
- package/dist/types/tools/glob.d.ts +1 -1
- package/dist/types/tools/grep.d.ts +1 -1
- package/dist/types/tools/render-utils.d.ts +2 -0
- package/dist/types/tools/{browser/run-cancellation.d.ts → run-scope.d.ts} +8 -8
- package/dist/types/tools/tool-timeouts.d.ts +5 -0
- package/dist/types/tools/xdev.d.ts +8 -0
- package/dist/types/utils/clipboard.d.ts +2 -2
- package/dist/types/web/kagi.d.ts +1 -0
- package/dist/types/web/search/index.d.ts +1 -1
- package/dist/types/web/search/providers/anthropic.d.ts +1 -0
- package/dist/types/web/search/providers/base.d.ts +2 -0
- package/dist/types/web/search/providers/brave.d.ts +1 -0
- package/dist/types/web/search/providers/browser-page.d.ts +1 -0
- package/dist/types/web/search/providers/codex.d.ts +1 -0
- package/dist/types/web/search/providers/exa.d.ts +1 -0
- package/dist/types/web/search/providers/firecrawl.d.ts +1 -0
- package/dist/types/web/search/providers/gemini.d.ts +1 -0
- package/dist/types/web/search/providers/jina.d.ts +1 -0
- package/dist/types/web/search/providers/kagi.d.ts +1 -0
- package/dist/types/web/search/providers/kimi.d.ts +1 -0
- package/dist/types/web/search/providers/parallel.d.ts +1 -0
- package/dist/types/web/search/providers/perplexity.d.ts +1 -0
- package/dist/types/web/search/providers/searxng.d.ts +1 -0
- package/dist/types/web/search/providers/tavily.d.ts +1 -0
- package/dist/types/web/search/providers/tinyfish.d.ts +1 -0
- package/dist/types/web/search/providers/utils.d.ts +6 -9
- package/dist/types/web/search/providers/zai.d.ts +1 -0
- package/dist/types/web/search/types.d.ts +4 -0
- package/package.json +12 -12
- package/scripts/legacy-pi-virtual-module.ts +12 -2
- package/src/advisor/advise-tool.ts +18 -0
- package/src/advisor/runtime.ts +6 -6
- package/src/capability/mcp.ts +7 -0
- package/src/cli/args.ts +7 -87
- package/src/cli/browser-relay-cli.ts +119 -0
- package/src/cli/command-help.ts +103 -0
- package/src/cli/completion-gen.ts +5 -5
- package/src/cli/flag-tables.ts +9 -0
- package/src/cli/help-extra.ts +89 -0
- package/src/cli/models-cli.ts +13 -16
- package/src/cli/setup-cli.ts +13 -21
- package/src/cli/thinking-levels.ts +7 -0
- package/src/cli/update-cli.ts +26 -7
- package/src/cli-commands.ts +170 -33
- package/src/cli.ts +17 -5
- package/src/collab/host.ts +1 -1
- package/src/commands/acp.ts +3 -1
- package/src/commands/agents.ts +3 -2
- package/src/commands/auth-broker.ts +3 -2
- package/src/commands/auth-gateway.ts +3 -2
- package/src/commands/bench.ts +2 -3
- package/src/commands/browser-relay.ts +53 -0
- package/src/commands/cleanse.ts +2 -2
- package/src/commands/commit.ts +3 -2
- package/src/commands/complete.ts +2 -1
- package/src/commands/completions.ts +3 -2
- package/src/commands/config.ts +3 -2
- package/src/commands/dry-balance.ts +2 -2
- package/src/commands/gallery.ts +3 -2
- package/src/commands/gc.ts +3 -2
- package/src/commands/grep.ts +3 -2
- package/src/commands/grievances.ts +3 -2
- package/src/commands/install.ts +2 -2
- package/src/commands/join.ts +3 -2
- package/src/commands/launch-help.ts +116 -0
- package/src/commands/launch.ts +7 -191
- package/src/commands/models.ts +3 -2
- package/src/commands/plugin.ts +3 -2
- package/src/commands/read.ts +3 -2
- package/src/commands/say.ts +3 -2
- package/src/commands/setup.ts +3 -2
- package/src/commands/shell.ts +3 -2
- package/src/commands/ssh.ts +3 -2
- package/src/commands/stats.ts +3 -2
- package/src/commands/tiny-models.ts +2 -2
- package/src/commands/token.ts +2 -2
- package/src/commands/ttsr.ts +2 -2
- package/src/commands/update.ts +3 -2
- package/src/commands/usage.ts +3 -2
- package/src/commands/web-search.ts +3 -2
- package/src/commands/worktree.ts +3 -2
- package/src/config/keybindings.ts +19 -9
- package/src/config/mcp-schema.json +5 -0
- package/src/config/model-discovery.ts +30 -13
- package/src/config/model-registry.ts +16 -6
- package/src/config/models-config-schema-bundle.ts +9 -0
- package/src/config/resolve-config-value.ts +2 -1
- package/src/config/service-tier.ts +5 -0
- package/src/config/settings-schema.ts +73 -21
- package/src/cursor-bridge-tools.ts +1 -1
- package/src/cursor.ts +10 -14
- package/src/discovery/builtin.ts +10 -0
- package/src/discovery/helpers.ts +10 -0
- package/src/discovery/mcp-json.ts +11 -1
- package/src/discovery/omp-extension-roots.ts +79 -20
- package/src/discovery/omp-plugins.ts +10 -1
- package/src/edit/diff.ts +17 -8
- package/src/edit/index.ts +20 -7
- package/src/edit/modes/replace.ts +93 -40
- package/src/edit/renderer.ts +1 -1
- package/src/edit/streaming.ts +14 -17
- package/src/eval/py/kernel.ts +2 -1
- package/src/eval/py/spawn-options.ts +24 -29
- package/src/export/html/template.js +8 -26
- package/src/extensibility/extensions/loader.ts +66 -28
- package/src/extensibility/extensions/wrapper.ts +1 -0
- package/src/extensibility/legacy-pi-ai-shim.ts +22 -1
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +7 -6
- package/src/extensibility/plugins/legacy-pi-compat.ts +124 -57
- package/src/extensibility/plugins/marketplace/registry.ts +4 -8
- package/src/extensibility/skills.ts +11 -0
- package/src/extensibility/utils.ts +10 -2
- package/src/launch/broker.ts +1 -1
- package/src/launch/client.ts +32 -13
- package/src/launch/ensure.ts +73 -0
- package/src/launch/paths.ts +3 -6
- package/src/launch/protocol.ts +2 -2
- package/src/lsp/client.ts +23 -2
- package/src/lsp/index.ts +8 -0
- package/src/lsp/mux/daemon.ts +348 -0
- package/src/lsp/mux/protocol.ts +96 -0
- package/src/lsp/mux/server.ts +797 -0
- package/src/lsp/types.ts +28 -2
- package/src/main.ts +31 -24
- package/src/mcp/config.ts +1 -0
- package/src/mcp/oauth-flow.ts +11 -1
- package/src/mcp/request-id.ts +24 -0
- package/src/mcp/transports/http.ts +4 -2
- package/src/mcp/transports/sse.ts +4 -2
- package/src/mcp/transports/stdio.ts +4 -2
- package/src/mcp/types.ts +15 -0
- package/src/memory-backend/index.ts +1 -0
- package/src/memory-backend/messages.ts +19 -0
- package/src/mnemopi/embed-client.ts +49 -2
- package/src/mnemopi/state.ts +57 -23
- package/src/modes/components/agent-hub.ts +1 -1
- package/src/modes/components/assistant-message.ts +11 -0
- package/src/modes/components/chat-transcript-builder.ts +4 -0
- package/src/modes/components/custom-editor.ts +10 -1
- package/src/modes/components/model-browser.ts +25 -17
- package/src/modes/components/model-picker.ts +10 -6
- package/src/modes/components/read-tool-group.ts +36 -0
- package/src/modes/components/status-line/component.ts +1 -1
- package/src/modes/components/stripped-tool-calls-placeholder.ts +35 -0
- package/src/modes/components/tool-execution.ts +85 -20
- package/src/modes/components/tree-selector.ts +46 -48
- package/src/modes/controllers/command-controller.ts +2 -2
- package/src/modes/controllers/event-controller.ts +199 -10
- package/src/modes/controllers/input-controller.ts +39 -1
- package/src/modes/controllers/mcp-command-controller.ts +11 -5
- package/src/modes/controllers/selector-controller.ts +47 -6
- package/src/modes/interactive-mode.ts +2 -0
- package/src/modes/types.ts +6 -1
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/modes/utils/interactive-context-helpers.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +6 -10
- package/src/modes/workflow.ts +8 -2
- package/src/prompts/agents/init.md +1 -1
- package/src/prompts/system/plan-mode-active.md +2 -2
- package/src/prompts/system/system-prompt.md +26 -52
- package/src/prompts/system/workflow-notice.md +3 -3
- package/src/prompts/tools/ast-grep.md +1 -1
- package/src/prompts/tools/bash.md +1 -1
- package/src/prompts/tools/browser.md +1 -0
- package/src/prompts/tools/computer.md +21 -21
- package/src/prompts/tools/glob.md +1 -1
- package/src/prompts/tools/grep.md +1 -1
- package/src/prompts/tools/replace.md +5 -4
- package/src/prompts/tools/task.md +4 -4
- package/src/registry/agent-lifecycle.ts +36 -11
- package/src/registry/agent-registry.ts +12 -5
- package/src/sdk.ts +102 -35
- package/src/secrets/index.ts +16 -18
- package/src/session/agent-session-types.ts +3 -1
- package/src/session/agent-session.ts +23 -2
- package/src/session/agent-storage.ts +6 -4
- package/src/session/history-storage.ts +4 -2
- package/src/session/indexed-session-storage.ts +9 -0
- package/src/session/messages.ts +31 -7
- package/src/session/model-controls.ts +0 -1
- package/src/session/prewalk.ts +24 -2
- package/src/session/session-advisors.ts +21 -3
- package/src/session/session-context.ts +16 -12
- package/src/session/session-dump-format.ts +1 -1
- package/src/session/session-maintenance.ts +41 -4
- package/src/session/session-manager.ts +181 -94
- package/src/session/session-paths.ts +118 -55
- package/src/session/session-stats.ts +48 -3
- package/src/session/session-storage.ts +30 -22
- package/src/session/session-tools.ts +25 -23
- package/src/session/stream-guards.ts +1 -1
- package/src/slash-commands/builtin-registry.ts +29 -26
- package/src/system-prompt.ts +14 -4
- package/src/task/executor.ts +8 -1
- package/src/task/index.ts +23 -8
- package/src/task/spawn-policy.ts +14 -0
- package/src/thinking.ts +2 -8
- package/src/tiny/text.ts +12 -0
- package/src/tools/ast-grep.ts +10 -4
- package/src/tools/browser/cmux/cmux-tab.ts +5 -10
- package/src/tools/browser/cmux/rpc.ts +2 -8
- package/src/tools/browser/registry.ts +53 -2
- package/src/tools/browser/relay/bridge.ts +945 -0
- package/src/tools/browser/relay/daemon.ts +117 -0
- package/src/tools/browser/relay/extension-assets/background.js.txt +242 -0
- package/src/tools/browser/relay/extension-assets/manifest.json.txt +14 -0
- package/src/tools/browser/relay/extension-assets/options.html.txt +53 -0
- package/src/tools/browser/relay/extension-assets/options.js.txt +23 -0
- package/src/tools/browser/relay/kind.ts +41 -0
- package/src/tools/browser/relay/protocol.ts +54 -0
- package/src/tools/browser/relay/server.ts +141 -0
- package/src/tools/browser/render.ts +4 -1
- package/src/tools/browser/shared-daemon.ts +7 -58
- package/src/tools/browser/tab-supervisor.ts +5 -4
- package/src/tools/browser/tab-worker.ts +33 -12
- package/src/tools/browser.ts +26 -1
- package/src/tools/computer/exposure.ts +11 -35
- package/src/tools/computer/protocol.ts +51 -10
- package/src/tools/computer/supervisor.ts +227 -124
- package/src/tools/computer/worker-entry.ts +11 -6
- package/src/tools/computer/worker.ts +695 -81
- package/src/tools/computer-renderer.ts +104 -65
- package/src/tools/computer.ts +134 -428
- package/src/tools/context.ts +3 -0
- package/src/tools/default-renderer.ts +19 -4
- package/src/tools/glob.ts +9 -2
- package/src/tools/grep.ts +12 -6
- package/src/tools/index.ts +10 -6
- package/src/tools/inspect-image.ts +32 -6
- package/src/tools/render-utils.ts +2 -0
- package/src/tools/{browser/run-cancellation.ts → run-scope.ts} +11 -11
- package/src/tools/tool-timeouts.ts +1 -0
- package/src/tools/xdev.ts +29 -2
- package/src/utils/clipboard.ts +40 -14
- package/src/utils/command-args.ts +26 -28
- package/src/utils/tool-choice.ts +0 -13
- package/src/web/kagi.ts +2 -1
- package/src/web/search/index.ts +18 -2
- package/src/web/search/providers/anthropic.ts +7 -2
- package/src/web/search/providers/base.ts +2 -0
- package/src/web/search/providers/brave.ts +3 -1
- package/src/web/search/providers/browser-page.ts +6 -4
- package/src/web/search/providers/codex.ts +4 -1
- package/src/web/search/providers/duckduckgo.ts +2 -1
- package/src/web/search/providers/ecosia.ts +2 -1
- package/src/web/search/providers/exa.ts +4 -2
- package/src/web/search/providers/firecrawl.ts +3 -1
- package/src/web/search/providers/gemini.ts +8 -2
- package/src/web/search/providers/google.ts +2 -1
- package/src/web/search/providers/jina.ts +12 -2
- package/src/web/search/providers/kagi.ts +3 -0
- package/src/web/search/providers/kimi.ts +5 -1
- package/src/web/search/providers/mojeek.ts +2 -1
- package/src/web/search/providers/parallel.ts +5 -1
- package/src/web/search/providers/perplexity.ts +6 -3
- package/src/web/search/providers/public.ts +1 -1
- package/src/web/search/providers/searxng.ts +11 -5
- package/src/web/search/providers/startpage.ts +10 -4
- package/src/web/search/providers/synthetic.ts +11 -5
- package/src/web/search/providers/tavily.ts +3 -1
- package/src/web/search/providers/tinyfish.ts +3 -1
- package/src/web/search/providers/utils.ts +12 -10
- package/src/web/search/providers/xai.ts +2 -2
- package/src/web/search/providers/zai.ts +18 -3
- package/src/web/search/types.ts +6 -0
package/src/discovery/builtin.ts
CHANGED
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
expandEnvVarsDeep,
|
|
32
32
|
getExtensionNameFromPath,
|
|
33
33
|
loadFilesFromDir,
|
|
34
|
+
parseRequestIdFormat,
|
|
34
35
|
SOURCE_PATHS,
|
|
35
36
|
scanSkillsFromDir,
|
|
36
37
|
} from "./helpers";
|
|
@@ -154,10 +155,19 @@ async function loadMCPServers(ctx: LoadContext): Promise<LoadResult<MCPServer>>
|
|
|
154
155
|
timeout = undefined;
|
|
155
156
|
}
|
|
156
157
|
|
|
158
|
+
// Validate requestIdFormat: only the two documented encodings
|
|
159
|
+
const requestIdFormat = parseRequestIdFormat(serverConfig.requestIdFormat);
|
|
160
|
+
if (requestIdFormat === undefined && serverConfig.requestIdFormat != null) {
|
|
161
|
+
logger.warn(
|
|
162
|
+
`MCP server "${serverName}": invalid requestIdFormat ${JSON.stringify(serverConfig.requestIdFormat)}, ignoring`,
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
157
166
|
result.push({
|
|
158
167
|
name: serverName,
|
|
159
168
|
enabled,
|
|
160
169
|
timeout,
|
|
170
|
+
requestIdFormat,
|
|
161
171
|
command: serverConfig.command as string | undefined,
|
|
162
172
|
args: serverConfig.args as string[] | undefined,
|
|
163
173
|
env: serverConfig.env as Record<string, string> | undefined,
|
package/src/discovery/helpers.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { invalidate as invalidateFsCache, readDirEntries, readFile } from "../ca
|
|
|
16
16
|
import { parseRuleConditionAndScope, type Rule, type RuleFrontmatter } from "../capability/rule";
|
|
17
17
|
import type { Skill, SkillFrontmatter } from "../capability/skill";
|
|
18
18
|
import type { LoadContext, LoadResult, SourceMeta } from "../capability/types";
|
|
19
|
+
import type { MCPRequestIdFormat } from "../mcp/types";
|
|
19
20
|
import { type ConfiguredThinkingLevel, parseConfiguredThinkingLevel } from "../thinking";
|
|
20
21
|
import { normalizeToolNames } from "../tools/builtin-names";
|
|
21
22
|
|
|
@@ -140,6 +141,15 @@ export function parseBoolean(value: unknown): boolean | undefined {
|
|
|
140
141
|
return undefined;
|
|
141
142
|
}
|
|
142
143
|
|
|
144
|
+
/**
|
|
145
|
+
* Parse an MCP `requestIdFormat` value. Unrecognized values are dropped so a typo
|
|
146
|
+
* degrades to the default integer ids rather than reaching a transport.
|
|
147
|
+
*/
|
|
148
|
+
export function parseRequestIdFormat(value: unknown): MCPRequestIdFormat | undefined {
|
|
149
|
+
if (value === "string" || value === "number") return value;
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
|
|
143
153
|
/**
|
|
144
154
|
* Parse a comma-separated string into an array of trimmed, non-empty strings.
|
|
145
155
|
*/
|
|
@@ -12,7 +12,7 @@ import { registerProvider } from "../capability";
|
|
|
12
12
|
import { readFile } from "../capability/fs";
|
|
13
13
|
import { type MCPServer, mcpCapability } from "../capability/mcp";
|
|
14
14
|
import type { LoadContext, LoadResult, SourceMeta } from "../capability/types";
|
|
15
|
-
import { createSourceMeta, expandEnvVarsDeep } from "./helpers";
|
|
15
|
+
import { createSourceMeta, expandEnvVarsDeep, parseRequestIdFormat } from "./helpers";
|
|
16
16
|
|
|
17
17
|
const PROVIDER_ID = "mcp-json";
|
|
18
18
|
const DISPLAY_NAME = "MCP Config";
|
|
@@ -26,6 +26,7 @@ interface MCPConfigFile {
|
|
|
26
26
|
{
|
|
27
27
|
enabled?: boolean;
|
|
28
28
|
timeout?: number;
|
|
29
|
+
requestIdFormat?: "string" | "number";
|
|
29
30
|
command?: string;
|
|
30
31
|
args?: string[];
|
|
31
32
|
env?: Record<string, string>;
|
|
@@ -83,10 +84,19 @@ function transformMCPConfig(config: MCPConfigFile, source: SourceMeta): MCPServe
|
|
|
83
84
|
}
|
|
84
85
|
}
|
|
85
86
|
|
|
87
|
+
const requestIdFormat = parseRequestIdFormat(serverConfig.requestIdFormat);
|
|
88
|
+
if (requestIdFormat === undefined && serverConfig.requestIdFormat !== undefined) {
|
|
89
|
+
logger.warn("MCP server has invalid 'requestIdFormat' value, ignoring", {
|
|
90
|
+
name,
|
|
91
|
+
value: serverConfig.requestIdFormat,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
86
95
|
const server: MCPServer = {
|
|
87
96
|
name,
|
|
88
97
|
enabled,
|
|
89
98
|
timeout,
|
|
99
|
+
requestIdFormat,
|
|
90
100
|
command: serverConfig.command,
|
|
91
101
|
args: serverConfig.args,
|
|
92
102
|
env: serverConfig.env,
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* @see ./omp-plugins.ts
|
|
16
16
|
* @see ./builtin.ts `loadExtensionModules`
|
|
17
17
|
*/
|
|
18
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
18
19
|
import * as fs from "node:fs/promises";
|
|
19
20
|
import * as path from "node:path";
|
|
20
21
|
import { getAgentDir, isEnoent, logger, tryParseJson } from "@oh-my-pi/pi-utils";
|
|
@@ -36,10 +37,48 @@ export interface OmpExtensionRoot {
|
|
|
36
37
|
|
|
37
38
|
interface InjectedRoot {
|
|
38
39
|
path: string;
|
|
40
|
+
/** Relative CLI spelling, rebound against the active project on discovery. */
|
|
41
|
+
relativePath?: string;
|
|
39
42
|
level: "user" | "project";
|
|
40
43
|
}
|
|
41
44
|
|
|
45
|
+
export type OmpExtensionRootMode = "merge" | "explicit-only";
|
|
46
|
+
|
|
47
|
+
interface InvocationRootScope {
|
|
48
|
+
/** Raw SDK spellings, resolved against the LoadContext that performs discovery. */
|
|
49
|
+
paths: readonly string[];
|
|
50
|
+
mode: OmpExtensionRootMode;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const invocationRootScope = new AsyncLocalStorage<InvocationRootScope>();
|
|
54
|
+
|
|
42
55
|
let injectedCliRoots: InjectedRoot[] = [];
|
|
56
|
+
let injectedCliRootMode: OmpExtensionRootMode = "merge";
|
|
57
|
+
|
|
58
|
+
export interface InjectOmpExtensionCliRootOptions {
|
|
59
|
+
/**
|
|
60
|
+
* `explicit-only` exposes only roots named by this CLI invocation. Use it
|
|
61
|
+
* with `--no-extensions` so configured and installed packages cannot
|
|
62
|
+
* contribute sibling capabilities through the `omp-plugins` provider.
|
|
63
|
+
*/
|
|
64
|
+
mode?: OmpExtensionRootMode;
|
|
65
|
+
/** Replace roots from an earlier invocation instead of extending them. */
|
|
66
|
+
replace?: boolean;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Run one SDK invocation with its own extension-package roots. Async resources
|
|
71
|
+
* started inside `callback` retain this scope, including discovery deliberately
|
|
72
|
+
* deferred until the end of session startup. Raw relative paths are resolved by
|
|
73
|
+
* {@link listOmpExtensionRoots} against that invocation's active cwd.
|
|
74
|
+
*/
|
|
75
|
+
export function withOmpExtensionRootScope<T>(
|
|
76
|
+
paths: readonly string[],
|
|
77
|
+
mode: OmpExtensionRootMode,
|
|
78
|
+
callback: () => T,
|
|
79
|
+
): T {
|
|
80
|
+
return invocationRootScope.run({ paths: [...paths], mode }, callback);
|
|
81
|
+
}
|
|
43
82
|
|
|
44
83
|
/**
|
|
45
84
|
* Register CLI-provided extension package paths (e.g. from `--extension`/`-e`)
|
|
@@ -50,17 +89,27 @@ let injectedCliRoots: InjectedRoot[] = [];
|
|
|
50
89
|
* Call once during startup before any capability load. Repeated calls extend
|
|
51
90
|
* the registered set; {@link clearOmpExtensionCliRoots} resets for tests.
|
|
52
91
|
*/
|
|
53
|
-
export function injectOmpExtensionCliRoots(
|
|
92
|
+
export function injectOmpExtensionCliRoots(
|
|
93
|
+
paths: readonly string[],
|
|
94
|
+
home: string,
|
|
95
|
+
cwd: string,
|
|
96
|
+
options: InjectOmpExtensionCliRootOptions = {},
|
|
97
|
+
): void {
|
|
98
|
+
if (options.mode) injectedCliRootMode = options.mode;
|
|
99
|
+
if (options.replace) injectedCliRoots = [];
|
|
54
100
|
if (paths.length === 0) return;
|
|
55
101
|
const expanded = paths.map(raw => {
|
|
56
102
|
const tilde = expandTilde(raw, home);
|
|
57
|
-
return
|
|
103
|
+
return {
|
|
104
|
+
path: path.isAbsolute(tilde) ? tilde : path.resolve(cwd, tilde),
|
|
105
|
+
relativePath: path.isAbsolute(tilde) ? undefined : tilde,
|
|
106
|
+
};
|
|
58
107
|
});
|
|
59
108
|
const merged = new Map<string, InjectedRoot>();
|
|
60
109
|
for (const root of injectedCliRoots) merged.set(root.path, root);
|
|
61
|
-
for (const resolved of expanded) {
|
|
110
|
+
for (const { path: resolved, relativePath } of expanded) {
|
|
62
111
|
// CLI scope mirrors how `--extension` is treated elsewhere — user-level overrides win.
|
|
63
|
-
if (!merged.has(resolved)) merged.set(resolved, { path: resolved, level: "user" });
|
|
112
|
+
if (!merged.has(resolved)) merged.set(resolved, { path: resolved, relativePath, level: "user" });
|
|
64
113
|
}
|
|
65
114
|
injectedCliRoots = [...merged.values()];
|
|
66
115
|
}
|
|
@@ -68,6 +117,7 @@ export function injectOmpExtensionCliRoots(paths: readonly string[], home: strin
|
|
|
68
117
|
/** Drop every CLI-injected root. Tests use this between cases. */
|
|
69
118
|
export function clearOmpExtensionCliRoots(): void {
|
|
70
119
|
injectedCliRoots = [];
|
|
120
|
+
injectedCliRootMode = "merge";
|
|
71
121
|
}
|
|
72
122
|
|
|
73
123
|
/** Inspect currently-injected CLI roots (read-only). Exposed for diagnostics + tests. */
|
|
@@ -121,7 +171,8 @@ async function isDirectory(p: string): Promise<boolean> {
|
|
|
121
171
|
* Sources, in order of precedence (later entries with the same absolute path
|
|
122
172
|
* are dropped):
|
|
123
173
|
*
|
|
124
|
-
* 1.
|
|
174
|
+
* 1. Invocation-scoped SDK roots, when present; otherwise CLI roots injected
|
|
175
|
+
* via {@link injectOmpExtensionCliRoots}
|
|
125
176
|
* 2. Project `<cwd>/.omp/settings.json#extensions`
|
|
126
177
|
* 3. User `~/.omp/agent/settings.json#extensions`
|
|
127
178
|
* 4. Enabled npm/link plugins installed under `<plugins>/node_modules/` (for
|
|
@@ -134,21 +185,29 @@ async function isDirectory(p: string): Promise<boolean> {
|
|
|
134
185
|
* other sources still surface.
|
|
135
186
|
*/
|
|
136
187
|
export async function listOmpExtensionRoots(ctx: LoadContext): Promise<OmpExtensionRoot[]> {
|
|
137
|
-
const
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
188
|
+
const scopedRoots = invocationRootScope.getStore();
|
|
189
|
+
const rootMode = scopedRoots?.mode ?? injectedCliRootMode;
|
|
190
|
+
let candidates: InjectedRoot[] = scopedRoots
|
|
191
|
+
? scopedRoots.paths.map(raw => ({ path: resolveAgainst(raw, ctx), level: "user" }))
|
|
192
|
+
: injectedCliRoots.map(root =>
|
|
193
|
+
root.relativePath ? { ...root, path: path.resolve(ctx.cwd, root.relativePath) } : root,
|
|
194
|
+
);
|
|
195
|
+
if (rootMode === "merge") {
|
|
196
|
+
const { project, user } = scopeDirs(ctx);
|
|
197
|
+
const [projectExtensions, userExtensions, installedPlugins] = await Promise.all([
|
|
198
|
+
readSettingsExtensions(path.join(project, "settings.json")),
|
|
199
|
+
readSettingsExtensions(path.join(user, "settings.json")),
|
|
200
|
+
listInstalledPluginRoots(ctx),
|
|
201
|
+
]);
|
|
202
|
+
candidates = [
|
|
203
|
+
...candidates,
|
|
204
|
+
...projectExtensions.map((raw): InjectedRoot => ({ path: resolveAgainst(raw, ctx), level: "project" })),
|
|
205
|
+
...userExtensions.map((raw): InjectedRoot => ({ path: resolveAgainst(raw, ctx), level: "user" })),
|
|
206
|
+
...installedPlugins,
|
|
207
|
+
];
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// First-seen-wins dedup preserves invocation/CLI > project-settings > user-settings > installed precedence.
|
|
152
211
|
const seen = new Set<string>();
|
|
153
212
|
const unique: InjectedRoot[] = [];
|
|
154
213
|
for (const candidate of candidates) {
|
|
@@ -28,7 +28,13 @@ import { type Skill, skillCapability } from "../capability/skill";
|
|
|
28
28
|
import { type SlashCommand, slashCommandCapability } from "../capability/slash-command";
|
|
29
29
|
import { type CustomTool, toolCapability } from "../capability/tool";
|
|
30
30
|
import type { LoadContext, LoadResult } from "../capability/types";
|
|
31
|
-
import {
|
|
31
|
+
import {
|
|
32
|
+
buildRuleFromMarkdown,
|
|
33
|
+
createSourceMeta,
|
|
34
|
+
loadFilesFromDir,
|
|
35
|
+
parseRequestIdFormat,
|
|
36
|
+
scanSkillsFromDir,
|
|
37
|
+
} from "./helpers";
|
|
32
38
|
import { listOmpExtensionRoots, type OmpExtensionRoot } from "./omp-extension-roots";
|
|
33
39
|
import { resolvePluginStdioPaths } from "./substitute-plugin-root";
|
|
34
40
|
|
|
@@ -257,6 +263,7 @@ const MCP_FILENAMES = [".mcp.json", "mcp.json"] as const;
|
|
|
257
263
|
interface RawMcpServer {
|
|
258
264
|
enabled?: boolean;
|
|
259
265
|
timeout?: number;
|
|
266
|
+
requestIdFormat?: unknown;
|
|
260
267
|
command?: string;
|
|
261
268
|
args?: string[];
|
|
262
269
|
env?: Record<string, string>;
|
|
@@ -305,10 +312,12 @@ async function loadMCPServers(ctx: LoadContext): Promise<LoadResult<MCPServer>>
|
|
|
305
312
|
// Root relative command/cwd at the plugin's config directory, not the
|
|
306
313
|
// session cwd (MCP stdio spawning resolves relative values there).
|
|
307
314
|
const rooted = resolvePluginStdioPaths({ command: cfg.command, cwd: cfg.cwd }, root.path);
|
|
315
|
+
const requestIdFormat = parseRequestIdFormat(cfg.requestIdFormat);
|
|
308
316
|
items.push({
|
|
309
317
|
name: serverName,
|
|
310
318
|
...(cfg.enabled !== undefined && { enabled: cfg.enabled }),
|
|
311
319
|
...(cfg.timeout !== undefined && { timeout: cfg.timeout }),
|
|
320
|
+
...(requestIdFormat !== undefined && { requestIdFormat }),
|
|
312
321
|
...(rooted.command !== undefined && { command: rooted.command }),
|
|
313
322
|
...(cfg.args !== undefined && { args: cfg.args }),
|
|
314
323
|
...(cfg.env !== undefined && { env: cfg.env }),
|
package/src/edit/diff.ts
CHANGED
|
@@ -855,7 +855,6 @@ export function replaceText(content: string, oldText: string, newText: string, o
|
|
|
855
855
|
let normalizedContent = normalizeToLF(content);
|
|
856
856
|
const normalizedOldText = normalizeToLF(oldText);
|
|
857
857
|
const normalizedNewText = normalizeToLF(newText);
|
|
858
|
-
let count = 0;
|
|
859
858
|
|
|
860
859
|
if (options.all) {
|
|
861
860
|
// Check for exact matches first
|
|
@@ -867,11 +866,13 @@ export function replaceText(content: string, oldText: string, newText: string, o
|
|
|
867
866
|
};
|
|
868
867
|
}
|
|
869
868
|
|
|
870
|
-
//
|
|
869
|
+
// Match against the immutable source so inserted replacement text cannot become a later candidate.
|
|
870
|
+
const replacements: Array<{ startIndex: number; endIndex: number; text: string }> = [];
|
|
871
871
|
while (true) {
|
|
872
872
|
const matchOutcome = findMatch(normalizedContent, normalizedOldText, {
|
|
873
873
|
allowFuzzy: options.fuzzy,
|
|
874
874
|
threshold,
|
|
875
|
+
excludedRanges: replacements,
|
|
875
876
|
});
|
|
876
877
|
|
|
877
878
|
const shouldUseClosest =
|
|
@@ -888,14 +889,22 @@ export function replaceText(content: string, oldText: string, newText: string, o
|
|
|
888
889
|
if (adjustedNewText === match.actualText) {
|
|
889
890
|
break;
|
|
890
891
|
}
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
892
|
+
replacements.push({
|
|
893
|
+
startIndex: match.startIndex,
|
|
894
|
+
endIndex: match.startIndex + Math.max(match.actualText.length, 1),
|
|
895
|
+
text: adjustedNewText,
|
|
896
|
+
});
|
|
896
897
|
}
|
|
897
898
|
|
|
898
|
-
|
|
899
|
+
replacements.sort((a, b) => a.startIndex - b.startIndex);
|
|
900
|
+
const parts: string[] = [];
|
|
901
|
+
let sourceIndex = 0;
|
|
902
|
+
for (const replacement of replacements) {
|
|
903
|
+
parts.push(normalizedContent.substring(sourceIndex, replacement.startIndex), replacement.text);
|
|
904
|
+
sourceIndex = replacement.endIndex;
|
|
905
|
+
}
|
|
906
|
+
parts.push(normalizedContent.substring(sourceIndex));
|
|
907
|
+
return { content: parts.join(""), count: replacements.length };
|
|
899
908
|
}
|
|
900
909
|
|
|
901
910
|
// Single replacement mode
|
package/src/edit/index.ts
CHANGED
|
@@ -19,7 +19,7 @@ import { executeHashlineSingle, hashlineEditParamsSchema } from "./hashline";
|
|
|
19
19
|
import { type ApplyPatchParams, applyPatchSchema, expandApplyPatchToEntries } from "./modes/apply-patch";
|
|
20
20
|
import applyPatchGrammar from "./modes/apply-patch.lark" with { type: "text" };
|
|
21
21
|
import { executePatchSingle, type PatchEditEntry, type PatchParams, patchEditSchema } from "./modes/patch";
|
|
22
|
-
import {
|
|
22
|
+
import { executeReplace, type ReplaceBatchParams, type ReplaceParams, replaceEditSchema } from "./modes/replace";
|
|
23
23
|
import { type EditToolDetails, type EditToolPerFileResult, getLspBatchRequest, type LspBatchRequest } from "./renderer";
|
|
24
24
|
import { pruneOversizedEditSnapshots } from "./snapshot-details";
|
|
25
25
|
import { EDIT_MODE_STRATEGIES } from "./streaming";
|
|
@@ -46,7 +46,7 @@ type TInput =
|
|
|
46
46
|
|
|
47
47
|
type HashlineParams = typeof hashlineEditParamsSchema.infer;
|
|
48
48
|
|
|
49
|
-
type EditParams = ReplaceParams | PatchParams | HashlineParams | ApplyPatchParams;
|
|
49
|
+
type EditParams = ReplaceParams | ReplaceBatchParams | PatchParams | HashlineParams | ApplyPatchParams;
|
|
50
50
|
|
|
51
51
|
type EditModeDefinition = {
|
|
52
52
|
description: (session: ToolSession) => string;
|
|
@@ -402,7 +402,7 @@ export class EditTool implements AgentTool<TInput> {
|
|
|
402
402
|
/**
|
|
403
403
|
* `mode` pins the edit variant for this instance, for callers whose protocol
|
|
404
404
|
* fixes the shape of an edit. The Cursor `pi_edit` frame carries
|
|
405
|
-
* `
|
|
405
|
+
* `old_string`/`new_string` args, which only `replace` accepts — under the
|
|
406
406
|
* default `hashline` mode those args do not match the schema at all. Left
|
|
407
407
|
* unset, the env/settings resolution applies as before.
|
|
408
408
|
*/
|
|
@@ -662,11 +662,24 @@ export class EditTool implements AgentTool<TInput> {
|
|
|
662
662
|
batchRequest: LspBatchRequest | undefined,
|
|
663
663
|
onUpdate?: (partialResult: AgentToolResult<EditToolDetails, TInput>) => void,
|
|
664
664
|
) => {
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
665
|
+
// `edits` is the internal `ReplaceBatchParams` form only the Cursor
|
|
666
|
+
// exec bridge produces (multi-replacement `pi_edit` frames run as one
|
|
667
|
+
// lifecycle); model calls always arrive in the single-edit schema shape.
|
|
668
|
+
const replaceParams = params as ReplaceParams | ReplaceBatchParams;
|
|
669
|
+
const entries =
|
|
670
|
+
"edits" in replaceParams
|
|
671
|
+
? replaceParams.edits
|
|
672
|
+
: [
|
|
673
|
+
{
|
|
674
|
+
old_string: replaceParams.old_string,
|
|
675
|
+
new_string: replaceParams.new_string,
|
|
676
|
+
replace_all: replaceParams.replace_all,
|
|
677
|
+
},
|
|
678
|
+
];
|
|
679
|
+
const targetPath = await resolveEditPath(tool.session, replaceParams.path, { mustExist: true, signal });
|
|
680
|
+
const runs = entries.map(
|
|
668
681
|
entry => (br: LspBatchRequest | undefined) =>
|
|
669
|
-
|
|
682
|
+
executeReplace({
|
|
670
683
|
session: tool.session,
|
|
671
684
|
path: targetPath,
|
|
672
685
|
params: entry,
|
|
@@ -258,23 +258,41 @@ function formatPreviewWindow(lines: string[], centerIndex: number, options: Prev
|
|
|
258
258
|
.join("\n");
|
|
259
259
|
}
|
|
260
260
|
|
|
261
|
-
function findExactMatchOutcome(
|
|
262
|
-
|
|
263
|
-
|
|
261
|
+
function findExactMatchOutcome(
|
|
262
|
+
content: string,
|
|
263
|
+
target: string,
|
|
264
|
+
excludedRanges: readonly { startIndex: number; endIndex: number }[],
|
|
265
|
+
): MatchOutcome | undefined {
|
|
266
|
+
let firstIndex: number | undefined;
|
|
267
|
+
let occurrences = 0;
|
|
268
|
+
const recordedIndices: number[] = [];
|
|
269
|
+
let searchStart = 0;
|
|
270
|
+
|
|
271
|
+
while (searchStart <= content.length - target.length) {
|
|
272
|
+
const index = content.indexOf(target, searchStart);
|
|
273
|
+
if (index === -1) break;
|
|
274
|
+
|
|
275
|
+
const endIndex = index + target.length;
|
|
276
|
+
if (!excludedRanges.some(range => index < range.endIndex && endIndex > range.startIndex)) {
|
|
277
|
+
firstIndex ??= index;
|
|
278
|
+
occurrences++;
|
|
279
|
+
if (recordedIndices.length < MAX_RECORDED_MATCHES) {
|
|
280
|
+
recordedIndices.push(index);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
searchStart = endIndex;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (firstIndex === undefined) {
|
|
264
287
|
return undefined;
|
|
265
288
|
}
|
|
266
289
|
|
|
267
|
-
const occurrences = content.split(target).length - 1;
|
|
268
290
|
if (occurrences > 1) {
|
|
269
291
|
const contentLines = content.split("\n");
|
|
270
292
|
const occurrenceLines: number[] = [];
|
|
271
293
|
const occurrencePreviews: string[] = [];
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
for (let i = 0; i < MAX_RECORDED_MATCHES; i++) {
|
|
275
|
-
const idx = content.indexOf(target, searchStart);
|
|
276
|
-
if (idx === -1) break;
|
|
277
|
-
const lineNumber = content.slice(0, idx).split("\n").length;
|
|
294
|
+
for (const index of recordedIndices) {
|
|
295
|
+
const lineNumber = content.slice(0, index).split("\n").length;
|
|
278
296
|
occurrenceLines.push(lineNumber);
|
|
279
297
|
occurrencePreviews.push(
|
|
280
298
|
formatPreviewWindow(contentLines, lineNumber - 1, {
|
|
@@ -282,17 +300,16 @@ function findExactMatchOutcome(content: string, target: string): MatchOutcome |
|
|
|
282
300
|
maxLen: OCCURRENCE_PREVIEW_MAX_LEN,
|
|
283
301
|
}),
|
|
284
302
|
);
|
|
285
|
-
searchStart = idx + 1;
|
|
286
303
|
}
|
|
287
304
|
|
|
288
305
|
return { occurrences, occurrenceLines, occurrencePreviews };
|
|
289
306
|
}
|
|
290
307
|
|
|
291
|
-
const startLine = content.slice(0,
|
|
308
|
+
const startLine = content.slice(0, firstIndex).split("\n").length;
|
|
292
309
|
return {
|
|
293
310
|
match: {
|
|
294
311
|
actualText: target,
|
|
295
|
-
startIndex:
|
|
312
|
+
startIndex: firstIndex,
|
|
296
313
|
startLine,
|
|
297
314
|
confidence: 1,
|
|
298
315
|
},
|
|
@@ -408,6 +425,7 @@ function findBestFuzzyMatchCore(
|
|
|
408
425
|
offsets: number[],
|
|
409
426
|
threshold: number,
|
|
410
427
|
includeDepth: boolean,
|
|
428
|
+
excludedRanges: readonly { startIndex: number; endIndex: number }[],
|
|
411
429
|
): BestFuzzyMatchResult {
|
|
412
430
|
const targetNormalized = normalizeLines(targetLines, includeDepth);
|
|
413
431
|
|
|
@@ -417,6 +435,12 @@ function findBestFuzzyMatchCore(
|
|
|
417
435
|
let aboveThresholdCount = 0;
|
|
418
436
|
|
|
419
437
|
for (let start = 0; start <= contentLines.length - targetLines.length; start++) {
|
|
438
|
+
const startIndex = offsets[start];
|
|
439
|
+
const endLine = start + targetLines.length - 1;
|
|
440
|
+
const endIndex = Math.max(startIndex + 1, offsets[endLine] + contentLines[endLine].length);
|
|
441
|
+
if (excludedRanges.some(range => startIndex < range.endIndex && endIndex > range.startIndex)) {
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
420
444
|
const windowLines = contentLines.slice(start, start + targetLines.length);
|
|
421
445
|
const windowNormalized = normalizeLines(windowLines, includeDepth);
|
|
422
446
|
let score = 0;
|
|
@@ -434,7 +458,7 @@ function findBestFuzzyMatchCore(
|
|
|
434
458
|
bestScore = score;
|
|
435
459
|
best = {
|
|
436
460
|
actualText: windowLines.join("\n"),
|
|
437
|
-
startIndex
|
|
461
|
+
startIndex,
|
|
438
462
|
startLine: start + 1,
|
|
439
463
|
confidence: score,
|
|
440
464
|
};
|
|
@@ -446,7 +470,12 @@ function findBestFuzzyMatchCore(
|
|
|
446
470
|
return { best, aboveThresholdCount, secondBestScore };
|
|
447
471
|
}
|
|
448
472
|
|
|
449
|
-
function findBestFuzzyMatch(
|
|
473
|
+
function findBestFuzzyMatch(
|
|
474
|
+
content: string,
|
|
475
|
+
target: string,
|
|
476
|
+
threshold: number,
|
|
477
|
+
excludedRanges: readonly { startIndex: number; endIndex: number }[],
|
|
478
|
+
): BestFuzzyMatchResult {
|
|
450
479
|
const contentLines = content.split("\n");
|
|
451
480
|
const targetLines = target.split("\n");
|
|
452
481
|
|
|
@@ -458,11 +487,18 @@ function findBestFuzzyMatch(content: string, target: string, threshold: number):
|
|
|
458
487
|
}
|
|
459
488
|
|
|
460
489
|
const offsets = computeLineOffsets(contentLines);
|
|
461
|
-
let result = findBestFuzzyMatchCore(contentLines, targetLines, offsets, threshold, true);
|
|
490
|
+
let result = findBestFuzzyMatchCore(contentLines, targetLines, offsets, threshold, true, excludedRanges);
|
|
462
491
|
|
|
463
492
|
// Retry without indent depth if match is close but below threshold
|
|
464
493
|
if (result.best && result.best.confidence < threshold && result.best.confidence >= FALLBACK_THRESHOLD) {
|
|
465
|
-
const noDepthResult = findBestFuzzyMatchCore(
|
|
494
|
+
const noDepthResult = findBestFuzzyMatchCore(
|
|
495
|
+
contentLines,
|
|
496
|
+
targetLines,
|
|
497
|
+
offsets,
|
|
498
|
+
threshold,
|
|
499
|
+
false,
|
|
500
|
+
excludedRanges,
|
|
501
|
+
);
|
|
466
502
|
if (noDepthResult.best && noDepthResult.best.confidence > result.best.confidence) {
|
|
467
503
|
result = noDepthResult;
|
|
468
504
|
}
|
|
@@ -473,25 +509,35 @@ function findBestFuzzyMatch(content: string, target: string, threshold: number):
|
|
|
473
509
|
|
|
474
510
|
/**
|
|
475
511
|
* Find a match for target text within content.
|
|
476
|
-
* Used primarily for replace-mode edits.
|
|
512
|
+
* Used primarily for replace-mode edits; excluded ranges remain invisible to exact and fuzzy matching.
|
|
477
513
|
*/
|
|
478
514
|
export function findMatch(
|
|
479
515
|
content: string,
|
|
480
516
|
target: string,
|
|
481
|
-
options: {
|
|
517
|
+
options: {
|
|
518
|
+
allowFuzzy: boolean;
|
|
519
|
+
threshold?: number;
|
|
520
|
+
excludedRanges?: readonly { startIndex: number; endIndex: number }[];
|
|
521
|
+
},
|
|
482
522
|
): MatchOutcome {
|
|
483
523
|
if (target.length === 0) {
|
|
484
524
|
return {};
|
|
485
525
|
}
|
|
526
|
+
const excludedRanges = options.excludedRanges ?? [];
|
|
486
527
|
|
|
487
|
-
const exactMatch = findExactMatchOutcome(content, target);
|
|
528
|
+
const exactMatch = findExactMatchOutcome(content, target, excludedRanges);
|
|
488
529
|
if (exactMatch) {
|
|
489
530
|
return exactMatch;
|
|
490
531
|
}
|
|
491
532
|
|
|
492
533
|
// Try fuzzy match
|
|
493
534
|
const threshold = options.threshold ?? DEFAULT_FUZZY_THRESHOLD;
|
|
494
|
-
const { best, aboveThresholdCount, secondBestScore } = findBestFuzzyMatch(
|
|
535
|
+
const { best, aboveThresholdCount, secondBestScore } = findBestFuzzyMatch(
|
|
536
|
+
content,
|
|
537
|
+
target,
|
|
538
|
+
threshold,
|
|
539
|
+
excludedRanges,
|
|
540
|
+
);
|
|
495
541
|
|
|
496
542
|
if (!best) {
|
|
497
543
|
return {};
|
|
@@ -1012,24 +1058,31 @@ export function findContextLine(
|
|
|
1012
1058
|
return { index: undefined, confidence: bestScore };
|
|
1013
1059
|
}
|
|
1014
1060
|
|
|
1015
|
-
export const replaceEditEntrySchema = type({
|
|
1016
|
-
old_text: "string",
|
|
1017
|
-
new_text: "string",
|
|
1018
|
-
"all?": "boolean",
|
|
1019
|
-
});
|
|
1020
|
-
|
|
1021
1061
|
export const replaceEditSchema = type({
|
|
1022
1062
|
path: "string",
|
|
1023
|
-
|
|
1063
|
+
old_string: "string",
|
|
1064
|
+
new_string: "string",
|
|
1065
|
+
"replace_all?": "boolean",
|
|
1024
1066
|
});
|
|
1025
1067
|
|
|
1026
|
-
export type ReplaceEditEntry = typeof replaceEditEntrySchema.infer;
|
|
1027
1068
|
export type ReplaceParams = typeof replaceEditSchema.infer;
|
|
1028
1069
|
|
|
1029
|
-
|
|
1070
|
+
/**
|
|
1071
|
+
* Internal batch form of {@link ReplaceParams}, produced only by the Cursor
|
|
1072
|
+
* exec bridge: a `pi_edit` frame carries several replacements against one
|
|
1073
|
+
* path but must run as a single tool lifecycle with one aggregate result.
|
|
1074
|
+
* Never advertised to the model — the agent loop validates model calls
|
|
1075
|
+
* against {@link replaceEditSchema}, which has no `edits` field.
|
|
1076
|
+
*/
|
|
1077
|
+
export interface ReplaceBatchParams {
|
|
1078
|
+
path: string;
|
|
1079
|
+
edits: Omit<ReplaceParams, "path">[];
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
export interface ExecuteReplaceOptions {
|
|
1030
1083
|
session: ToolSession;
|
|
1031
1084
|
path: string;
|
|
1032
|
-
params:
|
|
1085
|
+
params: Omit<ReplaceParams, "path">;
|
|
1033
1086
|
signal?: AbortSignal;
|
|
1034
1087
|
batchRequest?: LspBatchRequest;
|
|
1035
1088
|
allowFuzzy: boolean;
|
|
@@ -1038,9 +1091,9 @@ export interface ExecuteReplaceSingleOptions {
|
|
|
1038
1091
|
beginDeferredDiagnosticsForPath: (path: string) => WritethroughDeferredHandle;
|
|
1039
1092
|
}
|
|
1040
1093
|
|
|
1041
|
-
export async function
|
|
1042
|
-
options:
|
|
1043
|
-
): Promise<AgentToolResult<EditToolDetails,
|
|
1094
|
+
export async function executeReplace(
|
|
1095
|
+
options: ExecuteReplaceOptions,
|
|
1096
|
+
): Promise<AgentToolResult<EditToolDetails, ReplaceParams>> {
|
|
1044
1097
|
const {
|
|
1045
1098
|
session,
|
|
1046
1099
|
path,
|
|
@@ -1052,12 +1105,12 @@ export async function executeReplaceSingle(
|
|
|
1052
1105
|
writethrough,
|
|
1053
1106
|
beginDeferredDiagnosticsForPath,
|
|
1054
1107
|
} = options;
|
|
1055
|
-
const {
|
|
1108
|
+
const { old_string, new_string, replace_all } = params;
|
|
1056
1109
|
|
|
1057
1110
|
enforcePlanModeWrite(session, path);
|
|
1058
1111
|
|
|
1059
|
-
if (
|
|
1060
|
-
throw new Error("
|
|
1112
|
+
if (old_string.length === 0) {
|
|
1113
|
+
throw new Error("old_string must not be empty.");
|
|
1061
1114
|
}
|
|
1062
1115
|
|
|
1063
1116
|
const absolutePath = resolvePlanPath(session, path);
|
|
@@ -1065,12 +1118,12 @@ export async function executeReplaceSingle(
|
|
|
1065
1118
|
const { bom, text: content } = stripBom(rawContent);
|
|
1066
1119
|
const originalEnding = detectLineEnding(content);
|
|
1067
1120
|
const normalizedContent = normalizeToLF(content);
|
|
1068
|
-
const normalizedOldText = normalizeToLF(
|
|
1069
|
-
const normalizedNewText = normalizeToLF(
|
|
1121
|
+
const normalizedOldText = normalizeToLF(old_string);
|
|
1122
|
+
const normalizedNewText = normalizeToLF(new_string);
|
|
1070
1123
|
|
|
1071
1124
|
const result = replaceText(normalizedContent, normalizedOldText, normalizedNewText, {
|
|
1072
1125
|
fuzzy: allowFuzzy,
|
|
1073
|
-
all:
|
|
1126
|
+
all: replace_all ?? false,
|
|
1074
1127
|
threshold: fuzzyThreshold,
|
|
1075
1128
|
});
|
|
1076
1129
|
|