@oh-my-pi/pi-coding-agent 13.19.0 → 14.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +266 -1
- package/package.json +86 -20
- package/scripts/format-prompts.ts +2 -2
- package/src/autoresearch/apply-contract-to-state.ts +24 -0
- package/src/autoresearch/contract.ts +0 -44
- package/src/autoresearch/dashboard.ts +1 -2
- package/src/autoresearch/git.ts +91 -0
- package/src/autoresearch/helpers.ts +49 -0
- package/src/autoresearch/index.ts +28 -187
- package/src/autoresearch/prompt.md +26 -9
- package/src/autoresearch/state.ts +0 -6
- package/src/autoresearch/tools/init-experiment.ts +202 -117
- package/src/autoresearch/tools/log-experiment.ts +83 -125
- package/src/autoresearch/tools/run-experiment.ts +48 -10
- package/src/autoresearch/types.ts +2 -2
- package/src/capability/index.ts +4 -2
- package/src/cli/file-processor.ts +3 -3
- package/src/cli/grep-cli.ts +8 -8
- package/src/cli/grievances-cli.ts +78 -0
- package/src/cli/read-cli.ts +67 -0
- package/src/cli/setup-cli.ts +4 -4
- package/src/cli/update-cli.ts +3 -3
- package/src/cli.ts +2 -0
- package/src/commands/grep.ts +6 -1
- package/src/commands/grievances.ts +20 -0
- package/src/commands/read.ts +33 -0
- package/src/commit/agentic/agent.ts +5 -5
- package/src/commit/agentic/index.ts +3 -4
- package/src/commit/agentic/tools/analyze-file.ts +3 -3
- package/src/commit/agentic/validation.ts +1 -1
- package/src/commit/analysis/conventional.ts +4 -4
- package/src/commit/analysis/summary.ts +3 -3
- package/src/commit/changelog/generate.ts +4 -4
- package/src/commit/map-reduce/map-phase.ts +4 -4
- package/src/commit/map-reduce/reduce-phase.ts +4 -4
- package/src/commit/pipeline.ts +3 -4
- package/src/config/prompt-templates.ts +44 -226
- package/src/config/resolve-config-value.ts +4 -2
- package/src/config/settings-schema.ts +54 -2
- package/src/config/settings.ts +25 -26
- package/src/dap/client.ts +674 -0
- package/src/dap/config.ts +150 -0
- package/src/dap/defaults.json +211 -0
- package/src/dap/index.ts +4 -0
- package/src/dap/session.ts +1255 -0
- package/src/dap/types.ts +600 -0
- package/src/debug/log-viewer.ts +3 -2
- package/src/discovery/builtin.ts +1 -2
- package/src/discovery/codex.ts +2 -2
- package/src/discovery/github.ts +2 -1
- package/src/discovery/helpers.ts +2 -2
- package/src/discovery/opencode.ts +2 -2
- package/src/edit/diff.ts +818 -0
- package/src/edit/index.ts +309 -0
- package/src/edit/line-hash.ts +67 -0
- package/src/edit/modes/chunk.ts +454 -0
- package/src/{patch → edit/modes}/hashline.ts +741 -361
- package/src/{patch/applicator.ts → edit/modes/patch.ts} +420 -117
- package/src/{patch/fuzzy.ts → edit/modes/replace.ts} +519 -197
- package/src/{patch → edit}/normalize.ts +97 -76
- package/src/{patch/shared.ts → edit/renderer.ts} +181 -108
- package/src/exec/bash-executor.ts +4 -2
- package/src/exec/idle-timeout-watchdog.ts +126 -0
- package/src/exec/non-interactive-env.ts +5 -0
- package/src/extensibility/custom-commands/bundled/ci-green/index.ts +2 -2
- package/src/extensibility/custom-commands/bundled/review/index.ts +2 -2
- package/src/extensibility/custom-commands/loader.ts +1 -2
- package/src/extensibility/custom-tools/loader.ts +34 -11
- package/src/extensibility/extensions/loader.ts +9 -4
- package/src/extensibility/extensions/runner.ts +24 -1
- package/src/extensibility/extensions/types.ts +1 -1
- package/src/extensibility/hooks/loader.ts +5 -6
- package/src/extensibility/hooks/types.ts +1 -1
- package/src/extensibility/plugins/doctor.ts +2 -1
- package/src/extensibility/slash-commands.ts +3 -7
- package/src/index.ts +2 -1
- package/src/internal-urls/docs-index.generated.ts +11 -11
- package/src/ipy/executor.ts +58 -17
- package/src/ipy/gateway-coordinator.ts +6 -4
- package/src/ipy/kernel.ts +45 -22
- package/src/ipy/runtime.ts +2 -2
- package/src/lsp/client.ts +7 -4
- package/src/lsp/clients/lsp-linter-client.ts +4 -4
- package/src/lsp/config.ts +2 -2
- package/src/lsp/defaults.json +688 -154
- package/src/lsp/index.ts +234 -45
- package/src/lsp/lspmux.ts +2 -2
- package/src/lsp/startup-events.ts +13 -0
- package/src/lsp/types.ts +12 -1
- package/src/lsp/utils.ts +8 -1
- package/src/main.ts +102 -46
- package/src/memories/index.ts +4 -5
- package/src/modes/acp/acp-agent.ts +563 -163
- package/src/modes/acp/acp-event-mapper.ts +9 -1
- package/src/modes/acp/acp-mode.ts +4 -2
- package/src/modes/components/agent-dashboard.ts +3 -4
- package/src/modes/components/diff.ts +6 -7
- package/src/modes/components/read-tool-group.ts +6 -12
- package/src/modes/components/settings-defs.ts +5 -0
- package/src/modes/components/tool-execution.ts +1 -1
- package/src/modes/components/welcome.ts +1 -1
- package/src/modes/controllers/btw-controller.ts +2 -2
- package/src/modes/controllers/command-controller.ts +3 -2
- package/src/modes/controllers/input-controller.ts +12 -8
- package/src/modes/index.ts +20 -2
- package/src/modes/interactive-mode.ts +94 -37
- package/src/modes/rpc/host-tools.ts +186 -0
- package/src/modes/rpc/rpc-client.ts +178 -13
- package/src/modes/rpc/rpc-mode.ts +73 -3
- package/src/modes/rpc/rpc-types.ts +53 -1
- package/src/modes/theme/theme.ts +80 -8
- package/src/modes/types.ts +2 -2
- package/src/prompts/system/system-prompt.md +2 -1
- package/src/prompts/tools/chunk-edit.md +219 -0
- package/src/prompts/tools/debug.md +43 -0
- package/src/prompts/tools/grep.md +3 -0
- package/src/prompts/tools/lsp.md +5 -5
- package/src/prompts/tools/read-chunk.md +17 -0
- package/src/prompts/tools/read.md +19 -5
- package/src/sdk.ts +190 -154
- package/src/secrets/obfuscator.ts +1 -1
- package/src/session/agent-session.ts +306 -256
- package/src/session/agent-storage.ts +12 -12
- package/src/session/compaction/branch-summarization.ts +3 -3
- package/src/session/compaction/compaction.ts +5 -6
- package/src/session/compaction/utils.ts +3 -3
- package/src/session/history-storage.ts +62 -19
- package/src/session/messages.ts +3 -3
- package/src/session/session-dump-format.ts +203 -0
- package/src/session/session-storage.ts +4 -2
- package/src/session/streaming-output.ts +1 -1
- package/src/session/tool-choice-queue.ts +213 -0
- package/src/slash-commands/builtin-registry.ts +56 -8
- package/src/ssh/connection-manager.ts +2 -2
- package/src/ssh/sshfs-mount.ts +5 -5
- package/src/stt/downloader.ts +4 -4
- package/src/stt/recorder.ts +4 -4
- package/src/stt/transcriber.ts +2 -2
- package/src/system-prompt.ts +21 -13
- package/src/task/agents.ts +5 -6
- package/src/task/commands.ts +2 -5
- package/src/task/executor.ts +4 -4
- package/src/task/index.ts +3 -4
- package/src/task/template.ts +2 -2
- package/src/task/worktree.ts +4 -4
- package/src/tools/ask.ts +2 -3
- package/src/tools/ast-edit.ts +7 -7
- package/src/tools/ast-grep.ts +7 -7
- package/src/tools/auto-generated-guard.ts +36 -41
- package/src/tools/await-tool.ts +2 -2
- package/src/tools/bash.ts +5 -23
- package/src/tools/browser.ts +4 -5
- package/src/tools/calculator.ts +2 -3
- package/src/tools/cancel-job.ts +2 -2
- package/src/tools/checkpoint.ts +3 -3
- package/src/tools/debug.ts +1007 -0
- package/src/tools/exit-plan-mode.ts +2 -3
- package/src/tools/fetch.ts +67 -3
- package/src/tools/find.ts +4 -5
- package/src/tools/fs-cache-invalidation.ts +5 -0
- package/src/tools/gemini-image.ts +13 -5
- package/src/tools/gh.ts +10 -11
- package/src/tools/grep.ts +57 -9
- package/src/tools/index.ts +44 -22
- package/src/tools/inspect-image.ts +4 -4
- package/src/tools/output-meta.ts +1 -1
- package/src/tools/python.ts +19 -6
- package/src/tools/read.ts +198 -67
- package/src/tools/render-mermaid.ts +2 -3
- package/src/tools/render-utils.ts +20 -6
- package/src/tools/renderers.ts +3 -1
- package/src/tools/report-tool-issue.ts +80 -0
- package/src/tools/resolve.ts +70 -39
- package/src/tools/search-tool-bm25.ts +2 -2
- package/src/tools/ssh.ts +2 -2
- package/src/tools/todo-write.ts +2 -2
- package/src/tools/tool-timeouts.ts +1 -0
- package/src/tools/write.ts +5 -6
- package/src/tui/tree-list.ts +3 -1
- package/src/utils/clipboard.ts +80 -0
- package/src/utils/commit-message-generator.ts +2 -3
- package/src/utils/edit-mode.ts +49 -0
- package/src/utils/file-display-mode.ts +6 -5
- package/src/utils/file-mentions.ts +8 -7
- package/src/utils/git.ts +4 -4
- package/src/utils/image-loading.ts +98 -0
- package/src/utils/title-generator.ts +2 -3
- package/src/utils/tools-manager.ts +6 -6
- package/src/web/scrapers/choosealicense.ts +1 -1
- package/src/web/search/index.ts +3 -3
- package/src/autoresearch/command-initialize.md +0 -34
- package/src/patch/diff.ts +0 -433
- package/src/patch/index.ts +0 -888
- package/src/patch/parser.ts +0 -532
- package/src/patch/types.ts +0 -292
- package/src/prompts/agents/oracle.md +0 -77
- package/src/tools/pending-action.ts +0 -49
- package/src/utils/child-process.ts +0 -88
- package/src/utils/frontmatter.ts +0 -117
- package/src/utils/image-input.ts +0 -274
- package/src/utils/mime.ts +0 -53
- package/src/utils/prompt-format.ts +0 -170
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as nodePath from "node:path";
|
|
3
|
+
import type { AgentToolResult } from "@oh-my-pi/pi-agent-core";
|
|
4
|
+
import { StringEnum } from "@oh-my-pi/pi-coding-agent";
|
|
5
|
+
import {
|
|
6
|
+
ChunkAnchorStyle,
|
|
7
|
+
ChunkEditOp,
|
|
8
|
+
type ChunkInfo,
|
|
9
|
+
ChunkReadStatus,
|
|
10
|
+
type ChunkReadTarget,
|
|
11
|
+
ChunkState,
|
|
12
|
+
type EditOperation as NativeEditOperation,
|
|
13
|
+
} from "@oh-my-pi/pi-natives";
|
|
14
|
+
import { type Static, Type } from "@sinclair/typebox";
|
|
15
|
+
import type { BunFile } from "bun";
|
|
16
|
+
import { LRUCache } from "lru-cache";
|
|
17
|
+
import type { Settings } from "../../config/settings";
|
|
18
|
+
import type { WritethroughCallback, WritethroughDeferredHandle } from "../../lsp";
|
|
19
|
+
import { getLanguageFromPath } from "../../modes/theme/theme";
|
|
20
|
+
import type { ToolSession } from "../../tools";
|
|
21
|
+
import { assertEditableFileContent } from "../../tools/auto-generated-guard";
|
|
22
|
+
import { invalidateFsScanAfterWrite } from "../../tools/fs-cache-invalidation";
|
|
23
|
+
import { outputMeta } from "../../tools/output-meta";
|
|
24
|
+
import { enforcePlanModeWrite, resolvePlanPath } from "../../tools/plan-mode-guard";
|
|
25
|
+
import { generateUnifiedDiffString } from "../diff";
|
|
26
|
+
import { detectLineEnding, normalizeToLF, restoreLineEndings, stripBom } from "../normalize";
|
|
27
|
+
import type { EditToolDetails, LspBatchRequest } from "../renderer";
|
|
28
|
+
|
|
29
|
+
export type { ChunkReadTarget };
|
|
30
|
+
|
|
31
|
+
export type ChunkEditOperation =
|
|
32
|
+
| { op: "replace"; sel?: string; content: string }
|
|
33
|
+
| { op: "before"; sel?: string; content: string }
|
|
34
|
+
| { op: "after"; sel?: string; content: string }
|
|
35
|
+
| { op: "prepend"; sel?: string; content: string }
|
|
36
|
+
| { op: "append"; sel?: string; content: string };
|
|
37
|
+
|
|
38
|
+
type ChunkEditResult = {
|
|
39
|
+
diffSourceBefore: string;
|
|
40
|
+
diffSourceAfter: string;
|
|
41
|
+
responseText: string;
|
|
42
|
+
changed: boolean;
|
|
43
|
+
parseValid: boolean;
|
|
44
|
+
touchedPaths: string[];
|
|
45
|
+
warnings: string[];
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export type ParsedChunkReadPath = {
|
|
49
|
+
filePath: string;
|
|
50
|
+
selector?: string;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
type ChunkCacheEntry = {
|
|
54
|
+
mtimeMs: number;
|
|
55
|
+
size: number;
|
|
56
|
+
source: string;
|
|
57
|
+
state: ChunkState;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const validAnchorStyles: Record<string, ChunkAnchorStyle> = {
|
|
61
|
+
full: ChunkAnchorStyle.Full,
|
|
62
|
+
kind: ChunkAnchorStyle.Kind,
|
|
63
|
+
bare: ChunkAnchorStyle.Bare,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export function resolveAnchorStyle(settings?: Settings): ChunkAnchorStyle {
|
|
67
|
+
const envStyle = Bun.env.PI_ANCHOR_STYLE;
|
|
68
|
+
return (
|
|
69
|
+
(envStyle && validAnchorStyles[envStyle]) ||
|
|
70
|
+
(settings?.get("read.anchorstyle") as ChunkAnchorStyle | undefined) ||
|
|
71
|
+
ChunkAnchorStyle.Full
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const readEnvInt = (name: string, defaultValue: number): number => {
|
|
76
|
+
const value = Bun.env[name];
|
|
77
|
+
if (!value) return defaultValue;
|
|
78
|
+
const parsed = Number.parseInt(value, 10);
|
|
79
|
+
if (Number.isNaN(parsed) || parsed <= 0) return defaultValue;
|
|
80
|
+
return parsed;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const chunkStateCache = new LRUCache<string, ChunkCacheEntry>({
|
|
84
|
+
max: readEnvInt("PI_CHUNK_CACHE_MAX_ENTRIES", 200),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
export function invalidateChunkCache(filePath: string): void {
|
|
88
|
+
chunkStateCache.delete(filePath);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
type ChunkSourceContext = {
|
|
92
|
+
resolvedPath: string;
|
|
93
|
+
sourceFile: BunFile;
|
|
94
|
+
sourceExists: boolean;
|
|
95
|
+
rawContent: string;
|
|
96
|
+
chunkLanguage: string | undefined;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
function normalizeLanguage(language: string | undefined): string {
|
|
100
|
+
return language?.trim().toLowerCase() || "";
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function normalizeChunkSource(text: string): string {
|
|
104
|
+
return normalizeToLF(stripBom(text).text);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function displayPathForFile(filePath: string, cwd: string): string {
|
|
108
|
+
const relative = nodePath.relative(cwd, filePath).replace(/\\/g, "/");
|
|
109
|
+
return relative && !relative.startsWith("..") ? relative : filePath.replace(/\\/g, "/");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function fileLanguageTag(filePath: string, language?: string): string | undefined {
|
|
113
|
+
const normalizedLanguage = normalizeLanguage(language);
|
|
114
|
+
if (normalizedLanguage.length > 0) return normalizedLanguage;
|
|
115
|
+
const ext = nodePath.extname(filePath).replace(/^\./, "").toLowerCase();
|
|
116
|
+
return ext.length > 0 ? ext : undefined;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function resolveChunkSourceContext(session: ToolSession, path: string): Promise<ChunkSourceContext> {
|
|
120
|
+
const resolvedPath = resolvePlanPath(session, path);
|
|
121
|
+
const sourceFile = Bun.file(resolvedPath);
|
|
122
|
+
const sourceExists = await sourceFile.exists();
|
|
123
|
+
enforcePlanModeWrite(session, path, { op: sourceExists ? "update" : "create" });
|
|
124
|
+
|
|
125
|
+
let rawContent = "";
|
|
126
|
+
if (sourceExists) {
|
|
127
|
+
rawContent = await sourceFile.text();
|
|
128
|
+
assertEditableFileContent(rawContent, path);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
resolvedPath,
|
|
133
|
+
sourceFile,
|
|
134
|
+
sourceExists,
|
|
135
|
+
rawContent,
|
|
136
|
+
chunkLanguage: getLanguageFromPath(resolvedPath),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function buildChunkEditResult(result: {
|
|
141
|
+
diffBefore: string;
|
|
142
|
+
diffAfter: string;
|
|
143
|
+
responseText: string;
|
|
144
|
+
changed: boolean;
|
|
145
|
+
parseValid: boolean;
|
|
146
|
+
touchedPaths: string[];
|
|
147
|
+
warnings: string[];
|
|
148
|
+
}): ChunkEditResult {
|
|
149
|
+
return {
|
|
150
|
+
diffSourceBefore: result.diffBefore,
|
|
151
|
+
diffSourceAfter: result.diffAfter,
|
|
152
|
+
responseText: result.responseText,
|
|
153
|
+
changed: result.changed,
|
|
154
|
+
parseValid: result.parseValid,
|
|
155
|
+
touchedPaths: result.touchedPaths,
|
|
156
|
+
warnings: result.warnings,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function chunkReadPathSeparatorIndex(readPath: string): number {
|
|
161
|
+
if (/^[a-zA-Z]:[/\\]/.test(readPath)) {
|
|
162
|
+
return readPath.indexOf(":", 2);
|
|
163
|
+
}
|
|
164
|
+
return readPath.indexOf(":");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function parseChunkSelector(selector: string | undefined): { selector?: string } {
|
|
168
|
+
if (!selector || selector.length === 0) {
|
|
169
|
+
return {};
|
|
170
|
+
}
|
|
171
|
+
return { selector };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function parseChunkReadPath(readPath: string): ParsedChunkReadPath {
|
|
175
|
+
const colonIndex = chunkReadPathSeparatorIndex(readPath);
|
|
176
|
+
if (colonIndex === -1) {
|
|
177
|
+
return { filePath: readPath };
|
|
178
|
+
}
|
|
179
|
+
const parsedSelector = parseChunkSelector(readPath.slice(colonIndex + 1) || undefined);
|
|
180
|
+
return {
|
|
181
|
+
filePath: readPath.slice(0, colonIndex),
|
|
182
|
+
selector: parsedSelector.selector,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function isChunkReadablePath(readPath: string): boolean {
|
|
187
|
+
return parseChunkReadPath(readPath).selector !== undefined;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export async function loadChunkStateForFile(filePath: string, language: string | undefined): Promise<ChunkCacheEntry> {
|
|
191
|
+
const file = Bun.file(filePath);
|
|
192
|
+
const stat = await file.stat();
|
|
193
|
+
const cached = chunkStateCache.get(filePath);
|
|
194
|
+
if (cached && cached.mtimeMs === stat.mtimeMs && cached.size === stat.size) {
|
|
195
|
+
return cached;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const source = normalizeChunkSource(await file.text());
|
|
199
|
+
const state = ChunkState.parse(source, normalizeLanguage(language));
|
|
200
|
+
const entry = { mtimeMs: stat.mtimeMs, size: stat.size, source, state };
|
|
201
|
+
chunkStateCache.set(filePath, entry);
|
|
202
|
+
return entry;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export async function formatChunkedRead(params: {
|
|
206
|
+
filePath: string;
|
|
207
|
+
readPath: string;
|
|
208
|
+
cwd: string;
|
|
209
|
+
language?: string;
|
|
210
|
+
omitChecksum?: boolean;
|
|
211
|
+
anchorStyle?: ChunkAnchorStyle;
|
|
212
|
+
absoluteLineRange?: { startLine: number; endLine?: number };
|
|
213
|
+
}): Promise<{ text: string; resolvedPath?: string; chunk?: ChunkReadTarget }> {
|
|
214
|
+
const { filePath, readPath, cwd, language, omitChecksum = false, anchorStyle, absoluteLineRange } = params;
|
|
215
|
+
const normalizedLanguage = normalizeLanguage(language);
|
|
216
|
+
const { state } = await loadChunkStateForFile(filePath, normalizedLanguage);
|
|
217
|
+
const displayPath = displayPathForFile(filePath, cwd);
|
|
218
|
+
const result = state.renderRead({
|
|
219
|
+
readPath,
|
|
220
|
+
displayPath,
|
|
221
|
+
languageTag: fileLanguageTag(filePath, normalizedLanguage),
|
|
222
|
+
omitChecksum,
|
|
223
|
+
anchorStyle,
|
|
224
|
+
absoluteLineRange: absoluteLineRange
|
|
225
|
+
? { startLine: absoluteLineRange.startLine, endLine: absoluteLineRange.endLine ?? absoluteLineRange.startLine }
|
|
226
|
+
: undefined,
|
|
227
|
+
tabReplacement: " ",
|
|
228
|
+
normalizeIndent: true,
|
|
229
|
+
});
|
|
230
|
+
return { text: result.text, resolvedPath: filePath, chunk: result.chunk };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export async function formatChunkedGrepLine(params: {
|
|
234
|
+
filePath: string;
|
|
235
|
+
lineNumber: number;
|
|
236
|
+
line: string;
|
|
237
|
+
cwd: string;
|
|
238
|
+
language?: string;
|
|
239
|
+
}): Promise<string> {
|
|
240
|
+
const { filePath, lineNumber, line, cwd, language } = params;
|
|
241
|
+
const { state } = await loadChunkStateForFile(filePath, language);
|
|
242
|
+
return state.formatGrepLine(displayPathForFile(filePath, cwd), lineNumber, line);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function toNativeEditOperation(operation: ChunkEditOperation): NativeEditOperation {
|
|
246
|
+
switch (operation.op) {
|
|
247
|
+
case "replace":
|
|
248
|
+
return {
|
|
249
|
+
op: ChunkEditOp.Replace,
|
|
250
|
+
sel: operation.sel,
|
|
251
|
+
content: operation.content,
|
|
252
|
+
};
|
|
253
|
+
case "before":
|
|
254
|
+
return { op: ChunkEditOp.Before, sel: operation.sel, content: operation.content };
|
|
255
|
+
case "after":
|
|
256
|
+
return { op: ChunkEditOp.After, sel: operation.sel, content: operation.content };
|
|
257
|
+
case "prepend":
|
|
258
|
+
return { op: ChunkEditOp.Prepend, sel: operation.sel, content: operation.content };
|
|
259
|
+
case "append":
|
|
260
|
+
return { op: ChunkEditOp.Append, sel: operation.sel, content: operation.content };
|
|
261
|
+
default: {
|
|
262
|
+
const exhaustive: never = operation;
|
|
263
|
+
return exhaustive;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export function applyChunkEdits(params: {
|
|
269
|
+
source: string;
|
|
270
|
+
language?: string;
|
|
271
|
+
cwd: string;
|
|
272
|
+
filePath: string;
|
|
273
|
+
operations: ChunkEditOperation[];
|
|
274
|
+
defaultSelector?: string;
|
|
275
|
+
defaultCrc?: string;
|
|
276
|
+
anchorStyle?: ChunkAnchorStyle;
|
|
277
|
+
}): ChunkEditResult {
|
|
278
|
+
const normalizedSource = normalizeChunkSource(params.source);
|
|
279
|
+
const nativeOperations = params.operations.map(toNativeEditOperation);
|
|
280
|
+
const state = ChunkState.parse(normalizedSource, normalizeLanguage(params.language));
|
|
281
|
+
const result = state.applyEdits({
|
|
282
|
+
operations: nativeOperations,
|
|
283
|
+
defaultSelector: params.defaultSelector,
|
|
284
|
+
defaultCrc: params.defaultCrc,
|
|
285
|
+
anchorStyle: params.anchorStyle,
|
|
286
|
+
cwd: params.cwd,
|
|
287
|
+
filePath: params.filePath,
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
return buildChunkEditResult(result);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export async function getChunkInfoForFile(
|
|
294
|
+
filePath: string,
|
|
295
|
+
language: string | undefined,
|
|
296
|
+
chunkPath: string,
|
|
297
|
+
): Promise<ChunkInfo | undefined> {
|
|
298
|
+
const { state } = await loadChunkStateForFile(filePath, language);
|
|
299
|
+
return state.chunk(chunkPath) ?? undefined;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export function missingChunkReadTarget(selector: string): ChunkReadTarget {
|
|
303
|
+
return { status: ChunkReadStatus.NotFound, selector };
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const CHUNK_OP_VALUES = ["replace", "after", "before", "prepend", "append"] as const;
|
|
307
|
+
|
|
308
|
+
export const chunkToolEditSchema = Type.Object({
|
|
309
|
+
op: StringEnum(CHUNK_OP_VALUES),
|
|
310
|
+
sel: Type.String({
|
|
311
|
+
description:
|
|
312
|
+
"Chunk selector. Format: 'path@region' for insertions, 'path#CRC@region' for replace. Omit @region to target the full chunk. Valid regions: head, body, tail, decl.",
|
|
313
|
+
}),
|
|
314
|
+
content: Type.String({
|
|
315
|
+
description: "New content. Use one leading space per indent level; do not include the chunk's base padding.",
|
|
316
|
+
}),
|
|
317
|
+
});
|
|
318
|
+
export const chunkEditParamsSchema = Type.Object(
|
|
319
|
+
{
|
|
320
|
+
path: Type.String({ description: "File path" }),
|
|
321
|
+
edits: Type.Array(chunkToolEditSchema, {
|
|
322
|
+
description: "Chunk edits",
|
|
323
|
+
minItems: 1,
|
|
324
|
+
}),
|
|
325
|
+
},
|
|
326
|
+
{ additionalProperties: false },
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
export type ChunkToolEdit = Static<typeof chunkToolEditSchema>;
|
|
330
|
+
export type ChunkParams = Static<typeof chunkEditParamsSchema>;
|
|
331
|
+
|
|
332
|
+
interface ExecuteChunkModeOptions {
|
|
333
|
+
session: ToolSession;
|
|
334
|
+
params: ChunkParams;
|
|
335
|
+
signal?: AbortSignal;
|
|
336
|
+
batchRequest?: LspBatchRequest;
|
|
337
|
+
writethrough: WritethroughCallback;
|
|
338
|
+
beginDeferredDiagnosticsForPath: (path: string) => WritethroughDeferredHandle;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export function isChunkParams(params: unknown): params is ChunkParams {
|
|
342
|
+
return (
|
|
343
|
+
typeof params === "object" &&
|
|
344
|
+
params !== null &&
|
|
345
|
+
"edits" in params &&
|
|
346
|
+
Array.isArray(params.edits) &&
|
|
347
|
+
params.edits.length > 0 &&
|
|
348
|
+
typeof params.edits[0] === "object" &&
|
|
349
|
+
params.edits[0] !== null &&
|
|
350
|
+
"sel" in params.edits[0]
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function normalizeChunkEditOperations(edits: ChunkToolEdit[]): ChunkEditOperation[] {
|
|
355
|
+
return edits as ChunkEditOperation[];
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
async function writeChunkResult(params: {
|
|
359
|
+
result: ChunkEditResult;
|
|
360
|
+
resolvedPath: string;
|
|
361
|
+
sourceFile: BunFile;
|
|
362
|
+
sourceText: string;
|
|
363
|
+
sourceExists: boolean;
|
|
364
|
+
signal?: AbortSignal;
|
|
365
|
+
batchRequest?: LspBatchRequest;
|
|
366
|
+
writethrough: WritethroughCallback;
|
|
367
|
+
beginDeferredDiagnosticsForPath: (path: string) => WritethroughDeferredHandle;
|
|
368
|
+
}): Promise<AgentToolResult<EditToolDetails, typeof chunkEditParamsSchema>> {
|
|
369
|
+
const {
|
|
370
|
+
result,
|
|
371
|
+
resolvedPath,
|
|
372
|
+
sourceFile,
|
|
373
|
+
sourceText,
|
|
374
|
+
sourceExists,
|
|
375
|
+
signal,
|
|
376
|
+
batchRequest,
|
|
377
|
+
writethrough,
|
|
378
|
+
beginDeferredDiagnosticsForPath,
|
|
379
|
+
} = params;
|
|
380
|
+
|
|
381
|
+
const { bom, text } = stripBom(sourceText);
|
|
382
|
+
const originalEnding = detectLineEnding(text);
|
|
383
|
+
const finalContent = bom + restoreLineEndings(result.diffSourceAfter, originalEnding);
|
|
384
|
+
const diagnostics = await writethrough(resolvedPath, finalContent, signal, sourceFile, batchRequest, dst =>
|
|
385
|
+
dst === resolvedPath ? beginDeferredDiagnosticsForPath(resolvedPath) : undefined,
|
|
386
|
+
);
|
|
387
|
+
invalidateFsScanAfterWrite(resolvedPath);
|
|
388
|
+
|
|
389
|
+
const diffResult = generateUnifiedDiffString(result.diffSourceBefore, result.diffSourceAfter);
|
|
390
|
+
const warningsBlock = result.warnings.length > 0 ? `\n\n${result.warnings.join("\n")}` : "";
|
|
391
|
+
const meta = outputMeta()
|
|
392
|
+
.diagnostics(diagnostics?.summary ?? "", diagnostics?.messages ?? [])
|
|
393
|
+
.get();
|
|
394
|
+
|
|
395
|
+
return {
|
|
396
|
+
content: [{ type: "text", text: `${result.responseText}${warningsBlock}` }],
|
|
397
|
+
details: {
|
|
398
|
+
diff: diffResult.diff,
|
|
399
|
+
firstChangedLine: diffResult.firstChangedLine,
|
|
400
|
+
diagnostics,
|
|
401
|
+
op: sourceExists ? "update" : "create",
|
|
402
|
+
meta,
|
|
403
|
+
},
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
export async function executeChunkMode(
|
|
408
|
+
options: ExecuteChunkModeOptions,
|
|
409
|
+
): Promise<AgentToolResult<EditToolDetails, typeof chunkEditParamsSchema>> {
|
|
410
|
+
const { session, params, signal, batchRequest, writethrough, beginDeferredDiagnosticsForPath } = options;
|
|
411
|
+
const { path, edits } = params;
|
|
412
|
+
const { resolvedPath, sourceFile, sourceExists, rawContent, chunkLanguage } = await resolveChunkSourceContext(
|
|
413
|
+
session,
|
|
414
|
+
path,
|
|
415
|
+
);
|
|
416
|
+
const parentDir = nodePath.dirname(resolvedPath);
|
|
417
|
+
if (parentDir && parentDir !== ".") {
|
|
418
|
+
await fs.mkdir(parentDir, { recursive: true });
|
|
419
|
+
}
|
|
420
|
+
const normalizedOperations = normalizeChunkEditOperations(edits);
|
|
421
|
+
|
|
422
|
+
const chunkResult = applyChunkEdits({
|
|
423
|
+
source: rawContent,
|
|
424
|
+
language: chunkLanguage,
|
|
425
|
+
cwd: session.cwd,
|
|
426
|
+
filePath: resolvedPath,
|
|
427
|
+
operations: normalizedOperations,
|
|
428
|
+
anchorStyle: resolveAnchorStyle(session.settings),
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
if (!chunkResult.changed) {
|
|
432
|
+
const responseText = `[No changes needed — content already matches.]\n\n${chunkResult.responseText}`;
|
|
433
|
+
return {
|
|
434
|
+
content: [{ type: "text", text: responseText }],
|
|
435
|
+
details: {
|
|
436
|
+
diff: "",
|
|
437
|
+
op: sourceExists ? "update" : "create",
|
|
438
|
+
meta: outputMeta().get(),
|
|
439
|
+
},
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
return writeChunkResult({
|
|
444
|
+
result: chunkResult,
|
|
445
|
+
resolvedPath,
|
|
446
|
+
sourceFile,
|
|
447
|
+
sourceText: rawContent,
|
|
448
|
+
sourceExists,
|
|
449
|
+
signal,
|
|
450
|
+
batchRequest,
|
|
451
|
+
writethrough,
|
|
452
|
+
beginDeferredDiagnosticsForPath,
|
|
453
|
+
});
|
|
454
|
+
}
|