@oh-my-pi/pi-coding-agent 13.18.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.
Files changed (235) hide show
  1. package/CHANGELOG.md +316 -1
  2. package/package.json +86 -24
  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 +116 -30
  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 +123 -178
  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 -8
  28. package/src/commit/agentic/index.ts +22 -26
  29. package/src/commit/agentic/tools/analyze-file.ts +3 -3
  30. package/src/commit/agentic/tools/git-file-diff.ts +3 -6
  31. package/src/commit/agentic/tools/git-hunk.ts +3 -3
  32. package/src/commit/agentic/tools/git-overview.ts +6 -9
  33. package/src/commit/agentic/tools/index.ts +6 -8
  34. package/src/commit/agentic/tools/propose-commit.ts +4 -7
  35. package/src/commit/agentic/tools/recent-commits.ts +3 -3
  36. package/src/commit/agentic/tools/split-commit.ts +4 -4
  37. package/src/commit/agentic/validation.ts +1 -1
  38. package/src/commit/analysis/conventional.ts +4 -4
  39. package/src/commit/analysis/summary.ts +3 -3
  40. package/src/commit/changelog/generate.ts +4 -4
  41. package/src/commit/changelog/index.ts +5 -9
  42. package/src/commit/map-reduce/map-phase.ts +4 -4
  43. package/src/commit/map-reduce/reduce-phase.ts +4 -4
  44. package/src/commit/pipeline.ts +13 -16
  45. package/src/config/keybindings.ts +7 -6
  46. package/src/config/prompt-templates.ts +44 -226
  47. package/src/config/resolve-config-value.ts +4 -2
  48. package/src/config/settings-schema.ts +98 -2
  49. package/src/config/settings.ts +25 -26
  50. package/src/dap/client.ts +674 -0
  51. package/src/dap/config.ts +150 -0
  52. package/src/dap/defaults.json +211 -0
  53. package/src/dap/index.ts +4 -0
  54. package/src/dap/session.ts +1255 -0
  55. package/src/dap/types.ts +600 -0
  56. package/src/debug/log-viewer.ts +3 -2
  57. package/src/discovery/builtin.ts +1 -2
  58. package/src/discovery/codex.ts +2 -2
  59. package/src/discovery/github.ts +2 -1
  60. package/src/discovery/helpers.ts +2 -2
  61. package/src/discovery/opencode.ts +2 -2
  62. package/src/edit/diff.ts +818 -0
  63. package/src/edit/index.ts +309 -0
  64. package/src/edit/line-hash.ts +67 -0
  65. package/src/edit/modes/chunk.ts +454 -0
  66. package/src/{patch → edit/modes}/hashline.ts +741 -361
  67. package/src/{patch/applicator.ts → edit/modes/patch.ts} +420 -117
  68. package/src/{patch/fuzzy.ts → edit/modes/replace.ts} +519 -197
  69. package/src/{patch → edit}/normalize.ts +97 -76
  70. package/src/{patch/shared.ts → edit/renderer.ts} +181 -108
  71. package/src/exec/bash-executor.ts +4 -2
  72. package/src/exec/idle-timeout-watchdog.ts +126 -0
  73. package/src/exec/non-interactive-env.ts +5 -0
  74. package/src/extensibility/custom-commands/bundled/ci-green/index.ts +6 -18
  75. package/src/extensibility/custom-commands/bundled/review/index.ts +45 -43
  76. package/src/extensibility/custom-commands/loader.ts +1 -2
  77. package/src/extensibility/custom-tools/loader.ts +34 -11
  78. package/src/extensibility/custom-tools/types.ts +1 -1
  79. package/src/extensibility/extensions/loader.ts +9 -4
  80. package/src/extensibility/extensions/runner.ts +24 -1
  81. package/src/extensibility/extensions/types.ts +4 -2
  82. package/src/extensibility/hooks/loader.ts +5 -6
  83. package/src/extensibility/hooks/types.ts +2 -2
  84. package/src/extensibility/plugins/doctor.ts +2 -1
  85. package/src/extensibility/plugins/marketplace/fetcher.ts +2 -57
  86. package/src/extensibility/plugins/marketplace/source-resolver.ts +4 -4
  87. package/src/extensibility/slash-commands.ts +3 -7
  88. package/src/index.ts +3 -1
  89. package/src/internal-urls/docs-index.generated.ts +11 -11
  90. package/src/ipy/executor.ts +58 -17
  91. package/src/ipy/gateway-coordinator.ts +6 -4
  92. package/src/ipy/kernel.ts +45 -22
  93. package/src/ipy/runtime.ts +2 -2
  94. package/src/lsp/client.ts +7 -4
  95. package/src/lsp/clients/lsp-linter-client.ts +4 -4
  96. package/src/lsp/config.ts +2 -2
  97. package/src/lsp/defaults.json +688 -154
  98. package/src/lsp/index.ts +234 -45
  99. package/src/lsp/lspmux.ts +2 -2
  100. package/src/lsp/startup-events.ts +13 -0
  101. package/src/lsp/types.ts +12 -1
  102. package/src/lsp/utils.ts +8 -1
  103. package/src/main.ts +125 -47
  104. package/src/memories/index.ts +4 -5
  105. package/src/modes/acp/acp-agent.ts +563 -163
  106. package/src/modes/acp/acp-event-mapper.ts +9 -1
  107. package/src/modes/acp/acp-mode.ts +4 -2
  108. package/src/modes/components/agent-dashboard.ts +3 -4
  109. package/src/modes/components/diff.ts +6 -7
  110. package/src/modes/components/footer.ts +9 -29
  111. package/src/modes/components/hook-editor.ts +3 -3
  112. package/src/modes/components/hook-selector.ts +6 -1
  113. package/src/modes/components/read-tool-group.ts +6 -12
  114. package/src/modes/components/session-observer-overlay.ts +472 -0
  115. package/src/modes/components/settings-defs.ts +24 -0
  116. package/src/modes/components/status-line.ts +15 -61
  117. package/src/modes/components/tool-execution.ts +1 -1
  118. package/src/modes/components/welcome.ts +1 -1
  119. package/src/modes/controllers/btw-controller.ts +2 -2
  120. package/src/modes/controllers/command-controller.ts +4 -2
  121. package/src/modes/controllers/event-controller.ts +59 -2
  122. package/src/modes/controllers/extension-ui-controller.ts +1 -0
  123. package/src/modes/controllers/input-controller.ts +15 -8
  124. package/src/modes/controllers/selector-controller.ts +26 -0
  125. package/src/modes/index.ts +20 -2
  126. package/src/modes/interactive-mode.ts +278 -69
  127. package/src/modes/rpc/host-tools.ts +186 -0
  128. package/src/modes/rpc/rpc-client.ts +178 -13
  129. package/src/modes/rpc/rpc-mode.ts +73 -3
  130. package/src/modes/rpc/rpc-types.ts +53 -1
  131. package/src/modes/session-observer-registry.ts +146 -0
  132. package/src/modes/shared.ts +0 -42
  133. package/src/modes/theme/theme.ts +80 -8
  134. package/src/modes/types.ts +4 -2
  135. package/src/modes/utils/keybinding-matchers.ts +9 -0
  136. package/src/prompts/system/custom-system-prompt.md +5 -0
  137. package/src/prompts/system/system-prompt.md +8 -1
  138. package/src/prompts/tools/chunk-edit.md +219 -0
  139. package/src/prompts/tools/debug.md +43 -0
  140. package/src/prompts/tools/grep.md +3 -0
  141. package/src/prompts/tools/lsp.md +5 -5
  142. package/src/prompts/tools/read-chunk.md +17 -0
  143. package/src/prompts/tools/read.md +19 -5
  144. package/src/sdk.ts +216 -165
  145. package/src/secrets/index.ts +1 -1
  146. package/src/secrets/obfuscator.ts +25 -17
  147. package/src/session/agent-session.ts +381 -286
  148. package/src/session/agent-storage.ts +12 -12
  149. package/src/session/compaction/branch-summarization.ts +3 -3
  150. package/src/session/compaction/compaction.ts +5 -6
  151. package/src/session/compaction/utils.ts +3 -3
  152. package/src/session/history-storage.ts +62 -19
  153. package/src/session/messages.ts +3 -3
  154. package/src/session/session-dump-format.ts +203 -0
  155. package/src/session/session-manager.ts +15 -5
  156. package/src/session/session-storage.ts +4 -2
  157. package/src/session/streaming-output.ts +1 -1
  158. package/src/session/tool-choice-queue.ts +213 -0
  159. package/src/slash-commands/builtin-registry.ts +56 -8
  160. package/src/ssh/connection-manager.ts +2 -2
  161. package/src/ssh/sshfs-mount.ts +5 -5
  162. package/src/stt/downloader.ts +4 -4
  163. package/src/stt/recorder.ts +4 -4
  164. package/src/stt/transcriber.ts +2 -2
  165. package/src/system-prompt.ts +25 -13
  166. package/src/task/agents.ts +5 -6
  167. package/src/task/commands.ts +2 -5
  168. package/src/task/executor.ts +32 -4
  169. package/src/task/index.ts +91 -82
  170. package/src/task/template.ts +2 -2
  171. package/src/task/types.ts +25 -0
  172. package/src/task/worktree.ts +131 -149
  173. package/src/tools/ask.ts +2 -3
  174. package/src/tools/ast-edit.ts +7 -7
  175. package/src/tools/ast-grep.ts +7 -7
  176. package/src/tools/auto-generated-guard.ts +36 -41
  177. package/src/tools/await-tool.ts +2 -2
  178. package/src/tools/bash.ts +5 -23
  179. package/src/tools/browser.ts +4 -5
  180. package/src/tools/calculator.ts +2 -3
  181. package/src/tools/cancel-job.ts +2 -2
  182. package/src/tools/checkpoint.ts +3 -3
  183. package/src/tools/debug.ts +1007 -0
  184. package/src/tools/exit-plan-mode.ts +3 -3
  185. package/src/tools/fetch.ts +67 -3
  186. package/src/tools/find.ts +4 -5
  187. package/src/tools/fs-cache-invalidation.ts +5 -0
  188. package/src/tools/gemini-image.ts +13 -5
  189. package/src/tools/gh.ts +130 -308
  190. package/src/tools/grep.ts +57 -9
  191. package/src/tools/index.ts +44 -22
  192. package/src/tools/inspect-image.ts +4 -4
  193. package/src/tools/output-meta.ts +1 -1
  194. package/src/tools/python.ts +19 -6
  195. package/src/tools/read.ts +211 -146
  196. package/src/tools/render-mermaid.ts +2 -3
  197. package/src/tools/render-utils.ts +20 -6
  198. package/src/tools/renderers.ts +3 -1
  199. package/src/tools/report-tool-issue.ts +80 -0
  200. package/src/tools/resolve.ts +70 -39
  201. package/src/tools/search-tool-bm25.ts +2 -2
  202. package/src/tools/ssh.ts +2 -2
  203. package/src/tools/todo-write.ts +2 -2
  204. package/src/tools/tool-timeouts.ts +1 -0
  205. package/src/tools/write.ts +5 -6
  206. package/src/tui/tree-list.ts +3 -1
  207. package/src/utils/clipboard.ts +80 -0
  208. package/src/utils/commit-message-generator.ts +2 -3
  209. package/src/utils/edit-mode.ts +49 -0
  210. package/src/utils/external-editor.ts +11 -5
  211. package/src/utils/file-display-mode.ts +6 -5
  212. package/src/utils/file-mentions.ts +8 -7
  213. package/src/utils/git.ts +1400 -0
  214. package/src/utils/image-loading.ts +98 -0
  215. package/src/utils/title-generator.ts +2 -3
  216. package/src/utils/tools-manager.ts +6 -6
  217. package/src/web/scrapers/choosealicense.ts +1 -1
  218. package/src/web/search/index.ts +3 -3
  219. package/src/web/search/render.ts +6 -4
  220. package/src/autoresearch/command-initialize.md +0 -34
  221. package/src/commit/git/errors.ts +0 -9
  222. package/src/commit/git/index.ts +0 -210
  223. package/src/commit/git/operations.ts +0 -54
  224. package/src/patch/diff.ts +0 -433
  225. package/src/patch/index.ts +0 -888
  226. package/src/patch/parser.ts +0 -532
  227. package/src/patch/types.ts +0 -292
  228. package/src/prompts/agents/oracle.md +0 -77
  229. package/src/tools/gh-cli.ts +0 -125
  230. package/src/tools/pending-action.ts +0 -49
  231. package/src/utils/child-process.ts +0 -88
  232. package/src/utils/frontmatter.ts +0 -117
  233. package/src/utils/image-input.ts +0 -274
  234. package/src/utils/mime.ts +0 -53
  235. package/src/utils/prompt-format.ts +0 -170
@@ -0,0 +1,309 @@
1
+ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
2
+ import { prompt } from "@oh-my-pi/pi-utils";
3
+ import {
4
+ createLspWritethrough,
5
+ type FileDiagnosticsResult,
6
+ type WritethroughCallback,
7
+ type WritethroughDeferredHandle,
8
+ writethroughNoop,
9
+ } from "../lsp";
10
+ import chunkEditDescription from "../prompts/tools/chunk-edit.md" with { type: "text" };
11
+ import hashlineDescription from "../prompts/tools/hashline.md" with { type: "text" };
12
+ import patchDescription from "../prompts/tools/patch.md" with { type: "text" };
13
+ import replaceDescription from "../prompts/tools/replace.md" with { type: "text" };
14
+ import type { ToolSession } from "../tools";
15
+ import { type EditMode, normalizeEditMode, resolveEditMode } from "../utils/edit-mode";
16
+ import {
17
+ type ChunkParams,
18
+ chunkEditParamsSchema,
19
+ executeChunkMode,
20
+ isChunkParams,
21
+ resolveAnchorStyle,
22
+ } from "./modes/chunk";
23
+ import { executeHashlineMode, type HashlineParams, hashlineEditParamsSchema, isHashlineParams } from "./modes/hashline";
24
+ import { executePatchMode, isPatchParams, type PatchParams, patchEditSchema } from "./modes/patch";
25
+ import { executeReplaceMode, isReplaceParams, type ReplaceParams, replaceEditSchema } from "./modes/replace";
26
+ import { type EditToolDetails, getLspBatchRequest, type LspBatchRequest } from "./renderer";
27
+
28
+ export { DEFAULT_EDIT_MODE, type EditMode, normalizeEditMode } from "../utils/edit-mode";
29
+ export * from "./diff";
30
+ export * from "./line-hash";
31
+ export * from "./modes/chunk";
32
+ export * from "./modes/hashline";
33
+ export * from "./modes/patch";
34
+ export * from "./modes/replace";
35
+ export * from "./normalize";
36
+ export * from "./renderer";
37
+
38
+ type TInput =
39
+ | typeof replaceEditSchema
40
+ | typeof patchEditSchema
41
+ | typeof hashlineEditParamsSchema
42
+ | typeof chunkEditParamsSchema;
43
+
44
+ type EditParams = ReplaceParams | PatchParams | HashlineParams | ChunkParams;
45
+
46
+ type ModeExecutionArgs = {
47
+ params: EditParams;
48
+ signal: AbortSignal | undefined;
49
+ batchRequest: LspBatchRequest | undefined;
50
+ };
51
+
52
+ type EditModeDefinition = {
53
+ description: (session: ToolSession) => string;
54
+ parameters: TInput;
55
+ invalidParamsMessage: string;
56
+ validate: (params: EditParams) => boolean;
57
+ execute: (tool: EditTool, args: ModeExecutionArgs) => Promise<AgentToolResult<EditToolDetails, TInput>>;
58
+ };
59
+
60
+ function resolveConfiguredEditMode(rawEditMode: string): EditMode | undefined {
61
+ if (!rawEditMode || rawEditMode === "auto") {
62
+ return undefined;
63
+ }
64
+
65
+ const editMode = normalizeEditMode(rawEditMode);
66
+ if (!editMode) {
67
+ throw new Error(`Invalid PI_EDIT_VARIANT: ${rawEditMode}`);
68
+ }
69
+
70
+ return editMode;
71
+ }
72
+
73
+ function resolveAllowFuzzy(session: ToolSession, rawValue: string): boolean {
74
+ switch (rawValue) {
75
+ case "true":
76
+ case "1":
77
+ return true;
78
+ case "false":
79
+ case "0":
80
+ return false;
81
+ case "auto":
82
+ return session.settings.get("edit.fuzzyMatch");
83
+ default:
84
+ throw new Error(`Invalid PI_EDIT_FUZZY: ${rawValue}`);
85
+ }
86
+ }
87
+
88
+ function resolveFuzzyThreshold(session: ToolSession, rawValue: string): number {
89
+ if (rawValue === "auto") {
90
+ return session.settings.get("edit.fuzzyThreshold");
91
+ }
92
+
93
+ const threshold = Number.parseFloat(rawValue);
94
+ if (Number.isNaN(threshold) || threshold < 0 || threshold > 1) {
95
+ throw new Error(`Invalid PI_EDIT_FUZZY_THRESHOLD: ${rawValue}`);
96
+ }
97
+
98
+ return threshold;
99
+ }
100
+
101
+ function createEditWritethrough(session: ToolSession): WritethroughCallback {
102
+ const enableLsp = session.enableLsp ?? true;
103
+ const enableDiagnostics = enableLsp && session.settings.get("lsp.diagnosticsOnEdit");
104
+ const enableFormat = enableLsp && session.settings.get("lsp.formatOnWrite");
105
+ return enableLsp ? createLspWritethrough(session.cwd, { enableFormat, enableDiagnostics }) : writethroughNoop;
106
+ }
107
+
108
+ export class EditTool implements AgentTool<TInput> {
109
+ readonly name = "edit";
110
+ readonly label = "Edit";
111
+ readonly nonAbortable = true;
112
+ readonly concurrency = "exclusive";
113
+ readonly strict = true;
114
+
115
+ readonly #allowFuzzy: boolean;
116
+ readonly #fuzzyThreshold: number;
117
+ readonly #writethrough: WritethroughCallback;
118
+ readonly #editMode?: EditMode;
119
+ readonly #pendingDeferredFetches = new Map<string, AbortController>();
120
+
121
+ constructor(private readonly session: ToolSession) {
122
+ const {
123
+ PI_EDIT_FUZZY: editFuzzy = "auto",
124
+ PI_EDIT_FUZZY_THRESHOLD: editFuzzyThreshold = "auto",
125
+ PI_EDIT_VARIANT: envEditVariant = "auto",
126
+ } = Bun.env;
127
+
128
+ this.#editMode = resolveConfiguredEditMode(envEditVariant);
129
+ this.#allowFuzzy = resolveAllowFuzzy(session, editFuzzy);
130
+ this.#fuzzyThreshold = resolveFuzzyThreshold(session, editFuzzyThreshold);
131
+ this.#writethrough = createEditWritethrough(session);
132
+ }
133
+
134
+ get mode(): EditMode {
135
+ if (this.#editMode) return this.#editMode;
136
+ return resolveEditMode(this.session);
137
+ }
138
+
139
+ get description(): string {
140
+ return this.#getModeDefinition().description(this.session);
141
+ }
142
+
143
+ get parameters(): TInput {
144
+ return this.#getModeDefinition().parameters;
145
+ }
146
+
147
+ async execute(
148
+ _toolCallId: string,
149
+ params: ReplaceParams,
150
+ signal?: AbortSignal,
151
+ _onUpdate?: AgentToolUpdateCallback<EditToolDetails, TInput>,
152
+ context?: AgentToolContext,
153
+ ): Promise<AgentToolResult<EditToolDetails, TInput>>;
154
+ async execute(
155
+ _toolCallId: string,
156
+ params: PatchParams,
157
+ signal?: AbortSignal,
158
+ _onUpdate?: AgentToolUpdateCallback<EditToolDetails, TInput>,
159
+ context?: AgentToolContext,
160
+ ): Promise<AgentToolResult<EditToolDetails, TInput>>;
161
+ async execute(
162
+ _toolCallId: string,
163
+ params: HashlineParams,
164
+ signal?: AbortSignal,
165
+ _onUpdate?: AgentToolUpdateCallback<EditToolDetails, TInput>,
166
+ context?: AgentToolContext,
167
+ ): Promise<AgentToolResult<EditToolDetails, TInput>>;
168
+ async execute(
169
+ _toolCallId: string,
170
+ params: ChunkParams,
171
+ signal?: AbortSignal,
172
+ _onUpdate?: AgentToolUpdateCallback<EditToolDetails, TInput>,
173
+ context?: AgentToolContext,
174
+ ): Promise<AgentToolResult<EditToolDetails, TInput>>;
175
+ async execute(
176
+ _toolCallId: string,
177
+ params: EditParams,
178
+ signal?: AbortSignal,
179
+ _onUpdate?: AgentToolUpdateCallback<EditToolDetails, TInput>,
180
+ context?: AgentToolContext,
181
+ ): Promise<AgentToolResult<EditToolDetails, TInput>> {
182
+ const modeDefinition = this.#getModeDefinition();
183
+ if (!modeDefinition.validate(params)) {
184
+ throw new Error(modeDefinition.invalidParamsMessage);
185
+ }
186
+
187
+ return modeDefinition.execute(this, {
188
+ params,
189
+ signal,
190
+ batchRequest: getLspBatchRequest(context?.toolCall),
191
+ });
192
+ }
193
+
194
+ #getModeDefinition(): EditModeDefinition {
195
+ return {
196
+ chunk: {
197
+ description: (session: ToolSession) =>
198
+ prompt.render(chunkEditDescription, {
199
+ anchorStyle: resolveAnchorStyle(session.settings),
200
+ }),
201
+ parameters: chunkEditParamsSchema,
202
+ invalidParamsMessage: "Invalid edit parameters for chunk mode.",
203
+ validate: isChunkParams,
204
+ async execute(tool: EditTool, args: ModeExecutionArgs) {
205
+ return executeChunkMode({
206
+ session: tool.session,
207
+ params: args.params as ChunkParams,
208
+ signal: args.signal,
209
+ batchRequest: args.batchRequest,
210
+ writethrough: tool.#writethrough,
211
+ beginDeferredDiagnosticsForPath: path => tool.#beginDeferredDiagnosticsForPath(path),
212
+ });
213
+ },
214
+ },
215
+ patch: {
216
+ description: () => prompt.render(patchDescription),
217
+ parameters: patchEditSchema,
218
+ invalidParamsMessage: "Invalid edit parameters for patch mode.",
219
+ validate: isPatchParams,
220
+ async execute(tool: EditTool, args: ModeExecutionArgs) {
221
+ return executePatchMode({
222
+ session: tool.session,
223
+ params: args.params as PatchParams,
224
+ signal: args.signal,
225
+ batchRequest: args.batchRequest,
226
+ allowFuzzy: tool.#allowFuzzy,
227
+ fuzzyThreshold: tool.#fuzzyThreshold,
228
+ writethrough: tool.#writethrough,
229
+ beginDeferredDiagnosticsForPath: path => tool.#beginDeferredDiagnosticsForPath(path),
230
+ });
231
+ },
232
+ },
233
+ hashline: {
234
+ description: () => prompt.render(hashlineDescription),
235
+ parameters: hashlineEditParamsSchema,
236
+ invalidParamsMessage: "Invalid edit parameters for hashline mode.",
237
+ validate: isHashlineParams,
238
+ async execute(tool: EditTool, args: ModeExecutionArgs) {
239
+ return executeHashlineMode({
240
+ session: tool.session,
241
+ params: args.params as HashlineParams,
242
+ signal: args.signal,
243
+ batchRequest: args.batchRequest,
244
+ writethrough: tool.#writethrough,
245
+ beginDeferredDiagnosticsForPath: path => tool.#beginDeferredDiagnosticsForPath(path),
246
+ });
247
+ },
248
+ },
249
+ replace: {
250
+ description: () => prompt.render(replaceDescription),
251
+ parameters: replaceEditSchema,
252
+ invalidParamsMessage: "Invalid edit parameters for replace mode.",
253
+ validate: isReplaceParams,
254
+ async execute(tool: EditTool, args: ModeExecutionArgs) {
255
+ return executeReplaceMode({
256
+ session: tool.session,
257
+ params: args.params as ReplaceParams,
258
+ signal: args.signal,
259
+ batchRequest: args.batchRequest,
260
+ allowFuzzy: tool.#allowFuzzy,
261
+ fuzzyThreshold: tool.#fuzzyThreshold,
262
+ writethrough: tool.#writethrough,
263
+ beginDeferredDiagnosticsForPath: path => tool.#beginDeferredDiagnosticsForPath(path),
264
+ });
265
+ },
266
+ },
267
+ }[this.mode];
268
+ }
269
+
270
+ #beginDeferredDiagnosticsForPath(path: string): WritethroughDeferredHandle {
271
+ const existingDeferred = this.#pendingDeferredFetches.get(path);
272
+ if (existingDeferred) {
273
+ existingDeferred.abort();
274
+ this.#pendingDeferredFetches.delete(path);
275
+ }
276
+
277
+ const deferredController = new AbortController();
278
+ return {
279
+ onDeferredDiagnostics: (lateDiagnostics: FileDiagnosticsResult) => {
280
+ this.#pendingDeferredFetches.delete(path);
281
+ this.#injectLateDiagnostics(path, lateDiagnostics);
282
+ },
283
+ signal: deferredController.signal,
284
+ finalize: (diagnostics: FileDiagnosticsResult | undefined) => {
285
+ if (!diagnostics) {
286
+ this.#pendingDeferredFetches.set(path, deferredController);
287
+ } else {
288
+ deferredController.abort();
289
+ }
290
+ },
291
+ };
292
+ }
293
+
294
+ #injectLateDiagnostics(path: string, diagnostics: FileDiagnosticsResult): void {
295
+ const summary = diagnostics.summary ?? "";
296
+ const lines = diagnostics.messages ?? [];
297
+ const body = [`Late LSP diagnostics for ${path} (arrived after the edit tool returned):`, summary, ...lines]
298
+ .filter(Boolean)
299
+ .join("\n");
300
+
301
+ this.session.queueDeferredMessage?.({
302
+ role: "custom",
303
+ customType: "lsp-late-diagnostic",
304
+ content: body,
305
+ display: false,
306
+ timestamp: Date.now(),
307
+ });
308
+ }
309
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Lightweight line-hash utilities extracted from hashline.ts to avoid
3
+ * circular dependencies (prompt-templates → hashline → tools → edit).
4
+ */
5
+
6
+ /** 16-char nibble alphabet (no digits); shared with chunk checksum suffixes. */
7
+ export const HASHLINE_NIBBLE_ALPHABET = "ZPMQVRWSNKTXJBYH";
8
+
9
+ const NIBBLE_STR = HASHLINE_NIBBLE_ALPHABET;
10
+
11
+ const DICT = Array.from({ length: 256 }, (_, i) => {
12
+ const h = i >>> 4;
13
+ const l = i & 0x0f;
14
+ return `${NIBBLE_STR[h]}${NIBBLE_STR[l]}`;
15
+ });
16
+
17
+ const RE_SIGNIFICANT = /[\p{L}\p{N}]/u;
18
+
19
+ /**
20
+ * Compute a short hexadecimal hash of a single line.
21
+ *
22
+ * Uses xxHash32 on a trailing-whitespace-trimmed, CR-stripped line, truncated to 2 chars from
23
+ * {@link NIBBLE_STR}. For lines containing no alphanumeric characters (only
24
+ * punctuation/symbols/whitespace), the line number is mixed in to reduce hash collisions.
25
+ * The line input should not include a trailing newline.
26
+ */
27
+ export function computeLineHash(idx: number, line: string): string {
28
+ line = line.replace(/\r/g, "").trimEnd();
29
+
30
+ let seed = 0;
31
+ if (!RE_SIGNIFICANT.test(line)) {
32
+ seed = idx;
33
+ }
34
+ return DICT[Bun.hash.xxHash32(line, seed) & 0xff];
35
+ }
36
+
37
+ /**
38
+ * Formats a hash given the line number and text.
39
+ */
40
+ export function formatLineHash(line: number, lines: string): string {
41
+ return `${line}#${computeLineHash(line, lines)}`;
42
+ }
43
+
44
+ /**
45
+ * Format file text with hashline prefixes for display.
46
+ *
47
+ * Each line becomes `LINENUM#HASH:TEXT` where LINENUM is 1-indexed.
48
+ *
49
+ * @param text - Raw file text string
50
+ * @param startLine - First line number (1-indexed, defaults to 1)
51
+ * @returns Formatted string with one hashline-prefixed line per input line
52
+ *
53
+ * @example
54
+ * ```
55
+ * formatHashLines("function hi() {\n return;\n}")
56
+ * // "1#HH:function hi() {\n2#HH: return;\n3#HH:}"
57
+ * ```
58
+ */
59
+ export function formatHashLines(text: string, startLine = 1): string {
60
+ const lines = text.split("\n");
61
+ return lines
62
+ .map((line, i) => {
63
+ const num = startLine + i;
64
+ return `${formatLineHash(num, line)}:${line}`;
65
+ })
66
+ .join("\n");
67
+ }