@oh-my-pi/pi-coding-agent 13.19.0 → 14.0.3

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.
Files changed (205) hide show
  1. package/CHANGELOG.md +277 -2
  2. package/package.json +86 -20
  3. package/scripts/format-prompts.ts +2 -2
  4. package/src/autoresearch/apply-contract-to-state.ts +24 -0
  5. package/src/autoresearch/contract.ts +0 -44
  6. package/src/autoresearch/dashboard.ts +1 -2
  7. package/src/autoresearch/git.ts +91 -0
  8. package/src/autoresearch/helpers.ts +49 -0
  9. package/src/autoresearch/index.ts +28 -187
  10. package/src/autoresearch/prompt.md +26 -9
  11. package/src/autoresearch/state.ts +0 -6
  12. package/src/autoresearch/tools/init-experiment.ts +202 -117
  13. package/src/autoresearch/tools/log-experiment.ts +83 -125
  14. package/src/autoresearch/tools/run-experiment.ts +48 -10
  15. package/src/autoresearch/types.ts +2 -2
  16. package/src/capability/index.ts +4 -2
  17. package/src/cli/file-processor.ts +3 -3
  18. package/src/cli/grep-cli.ts +8 -8
  19. package/src/cli/grievances-cli.ts +78 -0
  20. package/src/cli/read-cli.ts +67 -0
  21. package/src/cli/setup-cli.ts +4 -4
  22. package/src/cli/update-cli.ts +3 -3
  23. package/src/cli.ts +2 -0
  24. package/src/commands/grep.ts +6 -1
  25. package/src/commands/grievances.ts +20 -0
  26. package/src/commands/read.ts +33 -0
  27. package/src/commit/agentic/agent.ts +5 -5
  28. package/src/commit/agentic/index.ts +3 -4
  29. package/src/commit/agentic/tools/analyze-file.ts +3 -3
  30. package/src/commit/agentic/validation.ts +1 -1
  31. package/src/commit/analysis/conventional.ts +4 -4
  32. package/src/commit/analysis/summary.ts +3 -3
  33. package/src/commit/changelog/generate.ts +4 -4
  34. package/src/commit/map-reduce/map-phase.ts +4 -4
  35. package/src/commit/map-reduce/reduce-phase.ts +4 -4
  36. package/src/commit/pipeline.ts +3 -4
  37. package/src/config/model-registry.ts +17 -3
  38. package/src/config/prompt-templates.ts +44 -226
  39. package/src/config/resolve-config-value.ts +4 -2
  40. package/src/config/settings-schema.ts +54 -2
  41. package/src/config/settings.ts +25 -26
  42. package/src/dap/client.ts +674 -0
  43. package/src/dap/config.ts +150 -0
  44. package/src/dap/defaults.json +211 -0
  45. package/src/dap/index.ts +4 -0
  46. package/src/dap/session.ts +1255 -0
  47. package/src/dap/types.ts +600 -0
  48. package/src/debug/log-viewer.ts +3 -2
  49. package/src/discovery/builtin.ts +1 -2
  50. package/src/discovery/codex.ts +2 -2
  51. package/src/discovery/github.ts +2 -1
  52. package/src/discovery/helpers.ts +2 -2
  53. package/src/discovery/opencode.ts +2 -2
  54. package/src/edit/diff.ts +818 -0
  55. package/src/edit/index.ts +309 -0
  56. package/src/edit/line-hash.ts +67 -0
  57. package/src/edit/modes/chunk.ts +454 -0
  58. package/src/{patch → edit/modes}/hashline.ts +741 -361
  59. package/src/{patch/applicator.ts → edit/modes/patch.ts} +420 -117
  60. package/src/{patch/fuzzy.ts → edit/modes/replace.ts} +519 -197
  61. package/src/{patch → edit}/normalize.ts +97 -76
  62. package/src/{patch/shared.ts → edit/renderer.ts} +181 -108
  63. package/src/exec/bash-executor.ts +4 -2
  64. package/src/exec/idle-timeout-watchdog.ts +126 -0
  65. package/src/exec/non-interactive-env.ts +5 -0
  66. package/src/extensibility/custom-commands/bundled/ci-green/index.ts +2 -2
  67. package/src/extensibility/custom-commands/bundled/review/index.ts +36 -15
  68. package/src/extensibility/custom-commands/loader.ts +1 -2
  69. package/src/extensibility/custom-tools/loader.ts +34 -11
  70. package/src/extensibility/extensions/loader.ts +9 -4
  71. package/src/extensibility/extensions/runner.ts +24 -1
  72. package/src/extensibility/extensions/types.ts +1 -1
  73. package/src/extensibility/hooks/loader.ts +5 -6
  74. package/src/extensibility/hooks/types.ts +1 -1
  75. package/src/extensibility/plugins/doctor.ts +2 -1
  76. package/src/extensibility/slash-commands.ts +3 -7
  77. package/src/index.ts +2 -1
  78. package/src/internal-urls/docs-index.generated.ts +11 -11
  79. package/src/ipy/executor.ts +58 -17
  80. package/src/ipy/gateway-coordinator.ts +6 -4
  81. package/src/ipy/kernel.ts +45 -22
  82. package/src/ipy/runtime.ts +2 -2
  83. package/src/lsp/client.ts +7 -4
  84. package/src/lsp/clients/lsp-linter-client.ts +4 -4
  85. package/src/lsp/config.ts +20 -4
  86. package/src/lsp/defaults.json +688 -154
  87. package/src/lsp/index.ts +234 -45
  88. package/src/lsp/lspmux.ts +2 -2
  89. package/src/lsp/startup-events.ts +13 -0
  90. package/src/lsp/types.ts +12 -1
  91. package/src/lsp/utils.ts +8 -1
  92. package/src/main.ts +102 -46
  93. package/src/memories/index.ts +4 -5
  94. package/src/modes/acp/acp-agent.ts +563 -163
  95. package/src/modes/acp/acp-event-mapper.ts +9 -1
  96. package/src/modes/acp/acp-mode.ts +4 -2
  97. package/src/modes/components/agent-dashboard.ts +3 -4
  98. package/src/modes/components/diff.ts +6 -7
  99. package/src/modes/components/read-tool-group.ts +6 -12
  100. package/src/modes/components/session-observer-overlay.ts +21 -12
  101. package/src/modes/components/settings-defs.ts +5 -0
  102. package/src/modes/components/tool-execution.ts +1 -1
  103. package/src/modes/components/welcome.ts +1 -1
  104. package/src/modes/controllers/btw-controller.ts +2 -2
  105. package/src/modes/controllers/command-controller.ts +3 -2
  106. package/src/modes/controllers/input-controller.ts +12 -8
  107. package/src/modes/index.ts +20 -2
  108. package/src/modes/interactive-mode.ts +94 -37
  109. package/src/modes/rpc/host-tools.ts +186 -0
  110. package/src/modes/rpc/rpc-client.ts +178 -13
  111. package/src/modes/rpc/rpc-mode.ts +73 -3
  112. package/src/modes/rpc/rpc-types.ts +53 -1
  113. package/src/modes/theme/theme.ts +80 -8
  114. package/src/modes/types.ts +2 -2
  115. package/src/prompts/review-request.md +6 -0
  116. package/src/prompts/system/system-prompt.md +2 -1
  117. package/src/prompts/tools/chunk-edit.md +223 -0
  118. package/src/prompts/tools/debug.md +43 -0
  119. package/src/prompts/tools/grep.md +3 -0
  120. package/src/prompts/tools/lsp.md +5 -5
  121. package/src/prompts/tools/read-chunk.md +17 -0
  122. package/src/prompts/tools/read.md +19 -5
  123. package/src/sdk.ts +190 -154
  124. package/src/secrets/obfuscator.ts +1 -1
  125. package/src/session/agent-session.ts +306 -256
  126. package/src/session/agent-storage.ts +12 -12
  127. package/src/session/compaction/branch-summarization.ts +3 -3
  128. package/src/session/compaction/compaction.ts +5 -6
  129. package/src/session/compaction/utils.ts +3 -3
  130. package/src/session/history-storage.ts +62 -19
  131. package/src/session/messages.ts +3 -3
  132. package/src/session/session-dump-format.ts +203 -0
  133. package/src/session/session-storage.ts +4 -2
  134. package/src/session/streaming-output.ts +1 -1
  135. package/src/session/tool-choice-queue.ts +213 -0
  136. package/src/slash-commands/builtin-registry.ts +56 -8
  137. package/src/ssh/connection-manager.ts +2 -2
  138. package/src/ssh/sshfs-mount.ts +5 -5
  139. package/src/stt/downloader.ts +4 -4
  140. package/src/stt/recorder.ts +4 -4
  141. package/src/stt/transcriber.ts +2 -2
  142. package/src/system-prompt.ts +21 -13
  143. package/src/task/agents.ts +5 -6
  144. package/src/task/commands.ts +2 -5
  145. package/src/task/executor.ts +4 -4
  146. package/src/task/index.ts +3 -4
  147. package/src/task/template.ts +2 -2
  148. package/src/task/worktree.ts +4 -4
  149. package/src/tools/ask.ts +2 -3
  150. package/src/tools/ast-edit.ts +7 -7
  151. package/src/tools/ast-grep.ts +7 -7
  152. package/src/tools/auto-generated-guard.ts +36 -41
  153. package/src/tools/await-tool.ts +2 -2
  154. package/src/tools/bash.ts +5 -23
  155. package/src/tools/browser.ts +4 -5
  156. package/src/tools/calculator.ts +2 -3
  157. package/src/tools/cancel-job.ts +2 -2
  158. package/src/tools/checkpoint.ts +3 -3
  159. package/src/tools/debug.ts +1007 -0
  160. package/src/tools/exit-plan-mode.ts +2 -3
  161. package/src/tools/fetch.ts +67 -3
  162. package/src/tools/find.ts +4 -5
  163. package/src/tools/fs-cache-invalidation.ts +5 -0
  164. package/src/tools/gemini-image.ts +13 -5
  165. package/src/tools/gh.ts +10 -11
  166. package/src/tools/grep.ts +57 -9
  167. package/src/tools/index.ts +44 -22
  168. package/src/tools/inspect-image.ts +4 -4
  169. package/src/tools/output-meta.ts +1 -1
  170. package/src/tools/python.ts +19 -6
  171. package/src/tools/read.ts +198 -67
  172. package/src/tools/render-mermaid.ts +2 -3
  173. package/src/tools/render-utils.ts +20 -6
  174. package/src/tools/renderers.ts +3 -1
  175. package/src/tools/report-tool-issue.ts +80 -0
  176. package/src/tools/resolve.ts +70 -39
  177. package/src/tools/search-tool-bm25.ts +2 -2
  178. package/src/tools/ssh.ts +2 -2
  179. package/src/tools/todo-write.ts +2 -2
  180. package/src/tools/tool-timeouts.ts +1 -0
  181. package/src/tools/write.ts +5 -6
  182. package/src/tui/tree-list.ts +3 -1
  183. package/src/utils/clipboard.ts +80 -0
  184. package/src/utils/commit-message-generator.ts +2 -3
  185. package/src/utils/edit-mode.ts +49 -0
  186. package/src/utils/file-display-mode.ts +6 -5
  187. package/src/utils/file-mentions.ts +8 -7
  188. package/src/utils/git.ts +4 -4
  189. package/src/utils/image-loading.ts +98 -0
  190. package/src/utils/title-generator.ts +2 -3
  191. package/src/utils/tools-manager.ts +6 -6
  192. package/src/web/scrapers/choosealicense.ts +1 -1
  193. package/src/web/search/index.ts +3 -3
  194. package/src/autoresearch/command-initialize.md +0 -34
  195. package/src/patch/diff.ts +0 -433
  196. package/src/patch/index.ts +0 -888
  197. package/src/patch/parser.ts +0 -532
  198. package/src/patch/types.ts +0 -292
  199. package/src/prompts/agents/oracle.md +0 -77
  200. package/src/tools/pending-action.ts +0 -49
  201. package/src/utils/child-process.ts +0 -88
  202. package/src/utils/frontmatter.ts +0 -117
  203. package/src/utils/image-input.ts +0 -274
  204. package/src/utils/mime.ts +0 -53
  205. 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 \\t for indentation. 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
+ }