@gajae-code/coding-agent 0.12.21 → 0.13.0
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 +67 -2
- package/dist/types/cli/args.d.ts +5 -2
- package/dist/types/cli/root-flags.d.ts +246 -0
- package/dist/types/cli.d.ts +11 -19
- package/dist/types/commands/daemon.d.ts +0 -30
- package/dist/types/commands/launch.d.ts +11 -19
- package/dist/types/commands/quick-lane.d.ts +32 -0
- package/dist/types/commands/sdk.d.ts +21 -15
- package/dist/types/config/file-lock-gc.d.ts +2 -0
- package/dist/types/config/model-equivalence.d.ts +20 -0
- package/dist/types/config/model-profile-activation.d.ts +62 -17
- package/dist/types/config/model-profiles.d.ts +10 -0
- package/dist/types/config/model-registry.d.ts +68 -1
- package/dist/types/config/model-resolver.d.ts +15 -4
- package/dist/types/config/models-config-schema.d.ts +7 -2
- package/dist/types/config/provider-selection-policy.d.ts +62 -0
- package/dist/types/config/settings-schema.d.ts +36 -78
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/edit/renderer.d.ts +5 -2
- package/dist/types/edit/streaming.d.ts +13 -0
- package/dist/types/extensibility/custom-tools/types.d.ts +2 -0
- package/dist/types/extensibility/extensions/compact-handler.d.ts +2 -1
- package/dist/types/extensibility/extensions/types.d.ts +3 -0
- package/dist/types/gjc-runtime/gc-runtime.d.ts +19 -5
- package/dist/types/internal-urls/local-protocol.d.ts +1 -0
- package/dist/types/modes/components/model-selector.d.ts +0 -2
- package/dist/types/modes/components/provider-order-context.d.ts +45 -0
- package/dist/types/modes/components/provider-order-editor.d.ts +24 -0
- package/dist/types/modes/components/settings-defs.d.ts +5 -1
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/quick-lane/classify.d.ts +40 -0
- package/dist/types/runtime/stt-settings-setup.d.ts +18 -0
- package/dist/types/sdk/broker/broker.d.ts +56 -2
- package/dist/types/sdk/broker/operation-receipt-ledger.d.ts +25 -0
- package/dist/types/sdk/broker/session-index.d.ts +83 -2
- package/dist/types/sdk/broker/state-version.d.ts +1 -1
- package/dist/types/sdk/bus/notification-service.d.ts +6 -0
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +8 -1
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +4 -1
- package/dist/types/sdk/bus/telegram-daemon.d.ts +26 -0
- package/dist/types/sdk/cli/rows.d.ts +98 -0
- package/dist/types/sdk/cli/session-cli.d.ts +35 -3
- package/dist/types/sdk/elevation/allowlist.d.ts +20 -0
- package/dist/types/sdk/elevation/audit.d.ts +93 -0
- package/dist/types/sdk/elevation/capability.d.ts +3 -0
- package/dist/types/sdk/elevation/digest.d.ts +72 -0
- package/dist/types/sdk/elevation/dispatch-receipt.d.ts +85 -0
- package/dist/types/sdk/elevation/grant-ledger.d.ts +163 -0
- package/dist/types/sdk/elevation/owner.d.ts +48 -0
- package/dist/types/sdk/guides/cache.d.ts +98 -0
- package/dist/types/sdk/guides/catalog.d.ts +156 -0
- package/dist/types/sdk/guides/cli.d.ts +39 -0
- package/dist/types/sdk/guides/manifest.d.ts +66 -0
- package/dist/types/sdk/guides/verify.d.ts +66 -0
- package/dist/types/sdk/host/control/dispatch.d.ts +8 -0
- package/dist/types/sdk/host/control/index.d.ts +1 -1
- package/dist/types/sdk/host/control/operations.d.ts +3 -2
- package/dist/types/sdk/host/query/cursor.d.ts +10 -0
- package/dist/types/sdk/host/query/handlers.d.ts +21 -0
- package/dist/types/sdk/host/session-runtime.d.ts +9 -0
- package/dist/types/sdk/mcp/server.d.ts +2 -2
- package/dist/types/sdk/prompt-status.d.ts +11 -0
- package/dist/types/sdk/session.d.ts +4 -0
- package/dist/types/sdk/transport/index.d.ts +1 -0
- package/dist/types/sdk/transport/relay.d.ts +1 -0
- package/dist/types/sdk/transport/serve-cli.d.ts +20 -1
- package/dist/types/session/agent-session.d.ts +32 -18
- package/dist/types/session/blob-store.d.ts +13 -7
- package/dist/types/session/fallback-chain-controller.d.ts +15 -0
- package/dist/types/session/internal/managed-session-storage.d.ts +25 -1
- package/dist/types/session/internal/session-memory-sidecar.d.ts +5 -5
- package/dist/types/session/session-manager.d.ts +139 -10
- package/dist/types/session/session-storage.d.ts +48 -0
- package/dist/types/setup/provider-onboarding.d.ts +7 -1
- package/dist/types/slash-commands/builtin-registry.d.ts +14 -0
- package/dist/types/stt/setup.d.ts +2 -0
- package/dist/types/task/executor.d.ts +10 -2
- package/dist/types/tools/browser/runtime-diagnostics.d.ts +65 -0
- package/dist/types/tools/browser/tab-protocol.d.ts +4 -0
- package/dist/types/tools/browser/tab-supervisor.d.ts +2 -0
- package/dist/types/tools/browser/tab-worker.d.ts +2 -0
- package/dist/types/tools/browser.d.ts +2 -0
- package/dist/types/tools/image-gen.d.ts +14 -18
- package/dist/types/tools/index.d.ts +2 -0
- package/dist/types/tools/resource-gc.d.ts +2 -0
- package/dist/types/utils/pasted-image-path.d.ts +6 -0
- package/package.json +9 -9
- package/scripts/generate-sdk-operation-inventory.ts +8 -0
- package/scripts/run-test-manifest.ts +61 -21
- package/scripts/verify-gjc-sdk-canonicalization.ts +9 -4
- package/src/cli/args.ts +62 -9
- package/src/cli/completion-cli.ts +1 -0
- package/src/cli/fast-help.ts +1 -0
- package/src/cli/root-flags.ts +100 -0
- package/src/cli/setup-cli.ts +6 -2
- package/src/cli.ts +42 -65
- package/src/commands/acp.ts +1 -1
- package/src/commands/daemon.ts +2 -31
- package/src/commands/gc.ts +1 -1
- package/src/commands/launch.ts +8 -129
- package/src/commands/quick-lane.ts +51 -0
- package/src/commands/sdk.ts +200 -26
- package/src/commands/setup.ts +4 -2
- package/src/config/file-lock-gc.ts +4 -1
- package/src/config/model-equivalence.ts +76 -7
- package/src/config/model-profile-activation.ts +1115 -265
- package/src/config/model-profiles.ts +134 -2
- package/src/config/model-registry.ts +352 -50
- package/src/config/model-resolver.ts +289 -38
- package/src/config/models-config-schema.ts +11 -4
- package/src/config/provider-selection-policy.ts +146 -0
- package/src/config/settings-schema.ts +50 -77
- package/src/config/settings.ts +40 -0
- package/src/coordinator-mcp/server.ts +32 -2
- package/src/edit/renderer.ts +175 -223
- package/src/edit/streaming.ts +108 -1
- package/src/export/html/index.ts +57 -7
- package/src/extensibility/custom-tools/types.ts +2 -0
- package/src/extensibility/extensions/compact-handler.ts +3 -2
- package/src/extensibility/extensions/runner.ts +6 -0
- package/src/extensibility/extensions/types.ts +3 -0
- package/src/gjc-runtime/gc-runtime.ts +379 -20
- package/src/gjc-runtime/launch-worktree.ts +5 -1
- package/src/gjc-runtime/team-runtime.ts +59 -2
- package/src/gjc-runtime/tmux-owner-isolation.ts +19 -0
- package/src/gjc-runtime/ultragoal-receipt-freshness.ts +16 -1
- package/src/internal-urls/docs-index.generated.ts +17 -13
- package/src/internal-urls/local-protocol.ts +1 -0
- package/src/main.ts +40 -3
- package/src/memories/index.ts +4 -2
- package/src/modes/acp/acp-agent.ts +235 -91
- package/src/modes/components/model-selector.ts +206 -42
- package/src/modes/components/provider-order-context.ts +197 -0
- package/src/modes/components/provider-order-editor.ts +441 -0
- package/src/modes/components/settings-defs.ts +21 -1
- package/src/modes/components/settings-selector.ts +45 -2
- package/src/modes/components/tool-execution.ts +47 -15
- package/src/modes/controllers/command-controller.ts +6 -3
- package/src/modes/controllers/extension-ui-controller.ts +2 -0
- package/src/modes/controllers/input-controller.ts +73 -2
- package/src/modes/controllers/selector-controller.ts +260 -153
- package/src/modes/prompt-suggestion-controller.ts +1 -1
- package/src/modes/runtime-init.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +18 -0
- package/src/prompts/tools/browser.md +1 -0
- package/src/quick-lane/classify.ts +257 -0
- package/src/rlm/index.ts +1 -1
- package/src/runtime/stt-settings-setup.ts +57 -0
- package/src/sdk/acp/adapter.ts +2 -1
- package/src/sdk/broker/broker.ts +780 -12
- package/src/sdk/broker/lifecycle.ts +70 -29
- package/src/sdk/broker/operation-receipt-ledger.ts +144 -0
- package/src/sdk/broker/session-index.ts +463 -48
- package/src/sdk/broker/state-version.ts +13 -9
- package/src/sdk/broker/transport.ts +14 -1
- package/src/sdk/bus/index.ts +34 -13
- package/src/sdk/bus/kind-aware-reconciliation.ts +21 -2
- package/src/sdk/bus/notification-service.ts +47 -1
- package/src/sdk/bus/prompt-reconciliation.ts +22 -3
- package/src/sdk/bus/telegram-daemon-contract.ts +4 -1
- package/src/sdk/bus/telegram-daemon.ts +54 -3
- package/src/sdk/cli/rows.ts +247 -0
- package/src/sdk/cli/session-cli.ts +930 -79
- package/src/sdk/elevation/allowlist.ts +54 -0
- package/src/sdk/elevation/audit.ts +208 -0
- package/src/sdk/elevation/capability.ts +41 -0
- package/src/sdk/elevation/digest.ts +129 -0
- package/src/sdk/elevation/dispatch-receipt.ts +184 -0
- package/src/sdk/elevation/grant-ledger.ts +1142 -0
- package/src/sdk/elevation/owner.ts +109 -0
- package/src/sdk/guides/cache.ts +363 -0
- package/src/sdk/guides/catalog.ts +554 -0
- package/src/sdk/guides/cli.ts +235 -0
- package/src/sdk/guides/manifest.ts +152 -0
- package/src/sdk/guides/verify.ts +178 -0
- package/src/sdk/host/control/dispatch.ts +49 -1
- package/src/sdk/host/control/index.ts +1 -0
- package/src/sdk/host/control/operations.ts +8 -1
- package/src/sdk/host/query/cursor.ts +47 -3
- package/src/sdk/host/query/handlers.ts +155 -2
- package/src/sdk/host/sdk-surface-parity.test.ts +1 -0
- package/src/sdk/host/session-runtime.test.ts +391 -1
- package/src/sdk/host/session-runtime.ts +308 -73
- package/src/sdk/host/surface-policy.ts +3 -0
- package/src/sdk/host/websocket-transport.ts +7 -1
- package/src/sdk/mcp/server.ts +88 -15
- package/src/sdk/prompt-status.ts +8 -0
- package/src/sdk/protocol/operation-inventory.generated.json +62 -0
- package/src/sdk/protocol/operation-registry.ts +3 -2
- package/src/sdk/session.ts +87 -39
- package/src/sdk/transport/index.ts +1 -0
- package/src/sdk/transport/relay.ts +3 -0
- package/src/sdk/transport/serve-cli.ts +111 -24
- package/src/secrets/obfuscator.ts +92 -12
- package/src/session/agent-session.ts +322 -93
- package/src/session/blob-store.ts +60 -8
- package/src/session/fallback-chain-controller.ts +35 -0
- package/src/session/internal/managed-session-scope.ts +25 -3
- package/src/session/internal/managed-session-storage.ts +574 -73
- package/src/session/internal/session-memory-sidecar.ts +19 -29
- package/src/session/session-manager.ts +2166 -743
- package/src/session/session-storage.ts +715 -48
- package/src/setup/provider-onboarding.ts +19 -3
- package/src/setup/provider-presets.json +3 -1
- package/src/slash-commands/builtin-registry.ts +85 -3
- package/src/stt/downloader.ts +2 -3
- package/src/stt/setup.ts +29 -4
- package/src/task/executor.ts +22 -18
- package/src/task/index.ts +24 -1
- package/src/tools/archive-reader.ts +11 -2
- package/src/tools/browser/runtime-diagnostics.ts +249 -0
- package/src/tools/browser/tab-protocol.ts +4 -0
- package/src/tools/browser/tab-supervisor.ts +5 -0
- package/src/tools/browser/tab-worker.ts +36 -1
- package/src/tools/browser.ts +7 -0
- package/src/tools/image-gen.ts +110 -166
- package/src/tools/index.ts +2 -0
- package/src/tools/read.ts +1 -1
- package/src/tools/resource-gc.ts +6 -1
- package/src/tools/tool-catalog.generated.ts +5 -1
- package/src/utils/pasted-image-path.ts +93 -0
- package/src/web/search/index.ts +3 -3
package/src/edit/renderer.ts
CHANGED
|
@@ -6,7 +6,6 @@ import type { Component } from "@gajae-code/tui";
|
|
|
6
6
|
import { Text, visibleWidth, wrapTextWithAnsi } from "@gajae-code/tui";
|
|
7
7
|
import { sanitizeText } from "@gajae-code/utils";
|
|
8
8
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
9
|
-
import { HL_FILE_PREFIX } from "../hashline/hash";
|
|
10
9
|
import type { FileDiagnosticsResult } from "../lsp";
|
|
11
10
|
import { renderDiff as renderDiffColored } from "../modes/components/diff";
|
|
12
11
|
import { getLanguageFromPath, type Theme } from "../modes/theme/theme";
|
|
@@ -30,9 +29,9 @@ import { fileHyperlink, Hasher, type RenderCache, renderStatusLine, truncateToWi
|
|
|
30
29
|
import type { EditMode } from "../utils/edit-mode";
|
|
31
30
|
import type { VimToolDetails } from "../vim/types";
|
|
32
31
|
import type { DiffError, DiffResult } from "./diff";
|
|
33
|
-
import {
|
|
32
|
+
import { expandApplyPatchToPreviewEntries } from "./modes/apply-patch";
|
|
34
33
|
import type { Operation } from "./modes/patch";
|
|
35
|
-
import type
|
|
34
|
+
import { getEditRequestTargetInventory, orderedDistinctPaths, type PerFileDiffPreview } from "./streaming";
|
|
36
35
|
|
|
37
36
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
38
37
|
// LSP Batching
|
|
@@ -90,7 +89,7 @@ export interface EditToolDetails {
|
|
|
90
89
|
// TUI Renderer
|
|
91
90
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
92
91
|
|
|
93
|
-
interface EditRenderArgs {
|
|
92
|
+
export interface EditRenderArgs {
|
|
94
93
|
path?: string;
|
|
95
94
|
file_path?: string;
|
|
96
95
|
oldText?: string;
|
|
@@ -116,17 +115,9 @@ type EditRenderEntry = {
|
|
|
116
115
|
rename?: string;
|
|
117
116
|
move?: string;
|
|
118
117
|
op?: Operation;
|
|
118
|
+
firstChangedLine?: number;
|
|
119
119
|
};
|
|
120
120
|
|
|
121
|
-
interface HashlineInputRenderSummary {
|
|
122
|
-
entries: Array<{ path: string }>;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
interface ApplyPatchRenderSummary {
|
|
126
|
-
entries: ApplyPatchEntry[];
|
|
127
|
-
error?: string;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
121
|
function isVimRenderArgs(args: EditRenderArgs | VimRenderArgs): args is VimRenderArgs {
|
|
131
122
|
return (
|
|
132
123
|
typeof args === "object" &&
|
|
@@ -170,58 +161,22 @@ const EDIT_STREAMING_PREVIEW_LINES = 12;
|
|
|
170
161
|
const CALL_TEXT_PREVIEW_LINES = 6;
|
|
171
162
|
const CALL_TEXT_PREVIEW_WIDTH = 80;
|
|
172
163
|
|
|
173
|
-
/** Extract file path from an edit entry. */
|
|
174
|
-
function filePathFromEditEntry(p: string | undefined): string | undefined {
|
|
175
|
-
return p ?? undefined;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function decodePartialJsonStringFragment(fragment: string): string {
|
|
179
|
-
// Trim a trailing partial escape so JSON.parse sees a well-formed string.
|
|
180
|
-
let text = fragment.replace(/\\u[0-9a-fA-F]{0,3}$/, "");
|
|
181
|
-
const trailingBackslashes = text.match(/\\+$/)?.[0].length ?? 0;
|
|
182
|
-
if (trailingBackslashes % 2 === 1) text = text.slice(0, -1);
|
|
183
|
-
try {
|
|
184
|
-
return JSON.parse(`"${text}"`) as string;
|
|
185
|
-
} catch {
|
|
186
|
-
// Streaming fragment isn't a valid JSON string yet; surface it raw rather
|
|
187
|
-
// than ad-hoc unescaping that mishandles surrogates and partial escapes.
|
|
188
|
-
return text;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function extractPartialJsonString(partialJson: string | undefined, key: string): string | undefined {
|
|
193
|
-
if (!partialJson) return undefined;
|
|
194
|
-
const pattern = new RegExp(`"${key}"\\s*:\\s*"((?:\\\\.|[^"\\\\])*)`, "u");
|
|
195
|
-
const match = pattern.exec(partialJson);
|
|
196
|
-
if (!match) return undefined;
|
|
197
|
-
return decodePartialJsonStringFragment(match[1]);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function getPartialJsonEditPath(args: EditRenderArgs): string | undefined {
|
|
201
|
-
return filePathFromEditEntry(extractPartialJsonString(args.__partialJson, "path"));
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/** Count distinct file paths in an edits array. */
|
|
205
|
-
function countEditFiles(edits: EditRenderEntry[]): number {
|
|
206
|
-
return new Set(edits.map(edit => filePathFromEditEntry(edit.path)).filter(Boolean)).size;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function countLines(text: string): number {
|
|
210
|
-
if (!text) return 0;
|
|
211
|
-
return text.split("\n").length;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
164
|
function getOperationTitle(op: Operation | undefined): string {
|
|
215
165
|
return op === "create" ? "Create" : op === "delete" ? "Delete" : "Edit";
|
|
216
166
|
}
|
|
217
167
|
|
|
168
|
+
function sanitizeDisplayPath(path: string): string {
|
|
169
|
+
return replaceTabs(sanitizeText(path).replace(/[\r\n]/g, " "));
|
|
170
|
+
}
|
|
171
|
+
|
|
218
172
|
function formatEditPathDisplay(
|
|
219
173
|
rawPath: string,
|
|
220
174
|
uiTheme: Theme,
|
|
221
175
|
options?: { rename?: string; firstChangedLine?: number },
|
|
222
176
|
): string {
|
|
223
|
-
|
|
224
|
-
|
|
177
|
+
const displayPath = sanitizeDisplayPath(rawPath);
|
|
178
|
+
let pathDisplay = displayPath
|
|
179
|
+
? fileHyperlink(displayPath, uiTheme.fg("accent", shortenPath(displayPath)))
|
|
225
180
|
: uiTheme.fg("toolOutput", "…");
|
|
226
181
|
|
|
227
182
|
if (options?.firstChangedLine) {
|
|
@@ -229,7 +184,8 @@ function formatEditPathDisplay(
|
|
|
229
184
|
}
|
|
230
185
|
|
|
231
186
|
if (options?.rename) {
|
|
232
|
-
|
|
187
|
+
const rename = sanitizeDisplayPath(options.rename);
|
|
188
|
+
pathDisplay += ` ${uiTheme.fg("dim", "→")} ${fileHyperlink(rename, uiTheme.fg("accent", shortenPath(rename)))}`;
|
|
233
189
|
}
|
|
234
190
|
|
|
235
191
|
return pathDisplay;
|
|
@@ -276,21 +232,13 @@ function formatStreamingDiff(diff: string, rawPath: string, uiTheme: Theme, labe
|
|
|
276
232
|
return text;
|
|
277
233
|
}
|
|
278
234
|
|
|
279
|
-
function formatMetadataLine(lineCount: number | null, language: string | undefined, uiTheme: Theme): string {
|
|
280
|
-
const icon = uiTheme.getLangIcon(language);
|
|
281
|
-
if (lineCount !== null) {
|
|
282
|
-
return uiTheme.fg("dim", `${icon} ${lineCount} lines`);
|
|
283
|
-
}
|
|
284
|
-
return uiTheme.fg("dim", `${icon}`);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
235
|
function formatMultiFileStreamingDiff(previews: PerFileDiffPreview[], uiTheme: Theme): string {
|
|
288
236
|
const parts: string[] = [];
|
|
289
237
|
for (const preview of previews) {
|
|
290
238
|
if (!preview.diff && !preview.error) continue;
|
|
291
|
-
const header = uiTheme.fg("dim", `\n\n── ${shortenPath(preview.path)} ──`);
|
|
239
|
+
const header = uiTheme.fg("dim", `\n\n── ${shortenPath(sanitizeDisplayPath(preview.path))} ──`);
|
|
292
240
|
if (preview.error) {
|
|
293
|
-
parts.push(`${header}\n${uiTheme.fg("error", replaceTabs(preview.error, preview.path))}`);
|
|
241
|
+
parts.push(`${header}\n${uiTheme.fg("error", replaceTabs(sanitizeText(preview.error), preview.path))}`);
|
|
294
242
|
continue;
|
|
295
243
|
}
|
|
296
244
|
if (preview.diff) {
|
|
@@ -328,75 +276,6 @@ function getCallPreview(
|
|
|
328
276
|
return "";
|
|
329
277
|
}
|
|
330
278
|
|
|
331
|
-
const MISSING_APPLY_PATCH_END_ERROR = "The last line of the patch must be '*** End Patch'";
|
|
332
|
-
|
|
333
|
-
function normalizeHashlineInputPreviewPath(rawPath: string): string {
|
|
334
|
-
const trimmed = rawPath.trim();
|
|
335
|
-
if (trimmed.length < 2) return trimmed;
|
|
336
|
-
const first = trimmed[0];
|
|
337
|
-
const last = trimmed[trimmed.length - 1];
|
|
338
|
-
if ((first === '"' || first === "'") && first === last) {
|
|
339
|
-
return trimmed.slice(1, -1);
|
|
340
|
-
}
|
|
341
|
-
return trimmed;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
function parseHashlineInputPreviewHeader(line: string): string | null {
|
|
345
|
-
if (!line.startsWith(HL_FILE_PREFIX)) return null;
|
|
346
|
-
// Mirror hashline/input.ts: strip every leading file marker so canonical
|
|
347
|
-
// `§ PATH` headers and stray `§§ PATH` / `§§§PATH` runs render clean paths.
|
|
348
|
-
let prefixEnd = 0;
|
|
349
|
-
while (prefixEnd < line.length && line[prefixEnd] === HL_FILE_PREFIX) prefixEnd++;
|
|
350
|
-
const body = line.slice(prefixEnd).trim();
|
|
351
|
-
const previewPath = normalizeHashlineInputPreviewPath(body);
|
|
352
|
-
return previewPath.length > 0 ? previewPath : null;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
function getHashlineInputPaths(input: string): string[] {
|
|
356
|
-
const stripped = input.startsWith("\uFEFF") ? input.slice(1) : input;
|
|
357
|
-
const paths: string[] = [];
|
|
358
|
-
for (const rawLine of stripped.split("\n")) {
|
|
359
|
-
const line = rawLine.replace(/\r$/, "");
|
|
360
|
-
const path = parseHashlineInputPreviewHeader(line);
|
|
361
|
-
if (path) paths.push(path);
|
|
362
|
-
}
|
|
363
|
-
return paths;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
function getHashlineInputRenderSummary(
|
|
367
|
-
args: EditRenderArgs,
|
|
368
|
-
editMode: EditMode | undefined,
|
|
369
|
-
): HashlineInputRenderSummary | undefined {
|
|
370
|
-
if (editMode !== "hashline" || typeof args.input !== "string") {
|
|
371
|
-
return undefined;
|
|
372
|
-
}
|
|
373
|
-
return { entries: getHashlineInputPaths(args.input).map(path => ({ path })) };
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
function getApplyPatchRenderSummary(
|
|
377
|
-
args: EditRenderArgs,
|
|
378
|
-
isPartial: boolean,
|
|
379
|
-
editMode: EditMode | undefined,
|
|
380
|
-
): ApplyPatchRenderSummary | undefined {
|
|
381
|
-
if (editMode !== undefined && editMode !== "apply_patch") {
|
|
382
|
-
return undefined;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
if (typeof args.input !== "string") {
|
|
386
|
-
return undefined;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
try {
|
|
390
|
-
return { entries: expandApplyPatchToEntries({ input: args.input }) };
|
|
391
|
-
} catch (err) {
|
|
392
|
-
const error = err instanceof Error ? err.message : String(err);
|
|
393
|
-
if (isPartial && error === MISSING_APPLY_PATCH_END_ERROR) {
|
|
394
|
-
return { entries: expandApplyPatchToPreviewEntries({ input: args.input }) };
|
|
395
|
-
}
|
|
396
|
-
return { entries: [], error };
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
|
|
400
279
|
function renderDiffSection(
|
|
401
280
|
diff: string,
|
|
402
281
|
rawPath: string,
|
|
@@ -472,39 +351,26 @@ export const editToolRenderer = {
|
|
|
472
351
|
}
|
|
473
352
|
|
|
474
353
|
const editArgs = args as EditRenderArgs;
|
|
475
|
-
const
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
const firstHashlineInputEntry = hashlineInputSummary?.entries[0];
|
|
479
|
-
// Extract path from first edit entry when top-level path is absent (new schema)
|
|
354
|
+
const inventory = getEditRequestTargetInventory(editArgs, renderContext?.editMode, {
|
|
355
|
+
isPartial: options.isPartial,
|
|
356
|
+
});
|
|
480
357
|
const firstEdit = Array.isArray(editArgs.edits) && editArgs.edits.length > 0 ? editArgs.edits[0] : undefined;
|
|
481
|
-
const rawPath =
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
getPartialJsonEditPath(editArgs) ||
|
|
486
|
-
firstHashlineInputEntry?.path ||
|
|
487
|
-
firstApplyPatchEntry?.path ||
|
|
488
|
-
"";
|
|
489
|
-
const rename = editArgs.rename || firstEdit?.rename || firstEdit?.move || firstApplyPatchEntry?.rename;
|
|
490
|
-
const op = editArgs.op || firstEdit?.op || firstApplyPatchEntry?.op;
|
|
491
|
-
const { description } = formatEditDescription(rawPath, uiTheme, { rename });
|
|
358
|
+
const rawPath = inventory.paths[0] ?? "";
|
|
359
|
+
const rename = editArgs.rename || firstEdit?.rename || firstEdit?.move;
|
|
360
|
+
const op = editArgs.op || firstEdit?.op || inventory.firstOp;
|
|
361
|
+
const { description } = formatEditDescription(rawPath, uiTheme, options.expanded ? { rename } : undefined);
|
|
492
362
|
const spinner =
|
|
493
363
|
options?.spinnerFrame !== undefined ? formatStatusIcon("running", uiTheme, options.spinnerFrame) : "";
|
|
494
364
|
let text = `${formatTitle(getOperationTitle(op), uiTheme)} ${spinner ? `${spinner} ` : ""}${description}`;
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
if (Array.isArray(editArgs.edits)) {
|
|
498
|
-
fileCount = countEditFiles(editArgs.edits);
|
|
499
|
-
}
|
|
500
|
-
if (fileCount > 1) {
|
|
501
|
-
text += uiTheme.fg("dim", ` (+${fileCount - 1} more)`);
|
|
365
|
+
if (inventory.paths.length > 1) {
|
|
366
|
+
text += uiTheme.fg("dim", ` (+${inventory.paths.length - 1} more)`);
|
|
502
367
|
}
|
|
368
|
+
if (!options.expanded) return new Text(text, 0, 0);
|
|
369
|
+
|
|
503
370
|
text += getCallPreview(editArgs, rawPath, uiTheme, renderContext);
|
|
504
|
-
if (
|
|
505
|
-
text += `\n\n${uiTheme.fg("error", truncateToWidth(replaceTabs(
|
|
371
|
+
if (inventory.parseError) {
|
|
372
|
+
text += `\n\n${uiTheme.fg("error", truncateToWidth(replaceTabs(sanitizeText(inventory.parseError), rawPath), CALL_TEXT_PREVIEW_WIDTH))}`;
|
|
506
373
|
}
|
|
507
|
-
|
|
508
374
|
return new Text(text, 0, 0);
|
|
509
375
|
},
|
|
510
376
|
|
|
@@ -523,14 +389,91 @@ export const editToolRenderer = {
|
|
|
523
389
|
}
|
|
524
390
|
|
|
525
391
|
const perFileResults = result.details?.perFileResults;
|
|
526
|
-
const
|
|
527
|
-
|
|
528
|
-
|
|
392
|
+
const inventory = getEditRequestTargetInventory(args, options.renderContext?.editMode, {
|
|
393
|
+
isPartial: options.isPartial,
|
|
394
|
+
});
|
|
395
|
+
const representedPaths = orderedDistinctPaths(perFileResults?.map(file => file.path) ?? []);
|
|
396
|
+
if (perFileResults && (perFileResults.length > 1 || inventory.paths.length > 1)) {
|
|
397
|
+
return renderMultiFileResult(
|
|
398
|
+
perFileResults,
|
|
399
|
+
inventory.paths.length,
|
|
400
|
+
representedPaths.length,
|
|
401
|
+
options,
|
|
402
|
+
uiTheme,
|
|
403
|
+
args,
|
|
404
|
+
);
|
|
529
405
|
}
|
|
530
406
|
return renderSingleFileResult(result, options, uiTheme, args);
|
|
531
407
|
},
|
|
532
408
|
};
|
|
533
409
|
|
|
410
|
+
function resolveCompletedEditIdentity(
|
|
411
|
+
details: EditToolDetails | EditToolPerFileResult | undefined,
|
|
412
|
+
args: EditRenderArgs | undefined,
|
|
413
|
+
editMode: EditMode | undefined,
|
|
414
|
+
isPartial: boolean,
|
|
415
|
+
): { path: string; op: Operation | undefined; move: string | undefined; firstChangedLine: number | undefined } {
|
|
416
|
+
const firstEdit = args?.edits?.[0];
|
|
417
|
+
const inventory = getEditRequestTargetInventory(args, editMode, { isPartial });
|
|
418
|
+
const detailsPath = details && "path" in details ? details.path : undefined;
|
|
419
|
+
return {
|
|
420
|
+
path: detailsPath ?? args?.file_path ?? args?.path ?? firstEdit?.path ?? inventory.paths[0] ?? "",
|
|
421
|
+
op: details?.op ?? args?.op ?? firstEdit?.op,
|
|
422
|
+
move: details?.move ?? args?.rename ?? firstEdit?.rename ?? firstEdit?.move,
|
|
423
|
+
firstChangedLine: details?.firstChangedLine ?? firstEdit?.firstChangedLine,
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
export function getPerFileEditRenderArgs(
|
|
428
|
+
args: EditRenderArgs | undefined,
|
|
429
|
+
path: string,
|
|
430
|
+
editMode: EditMode | undefined,
|
|
431
|
+
): EditRenderArgs | undefined {
|
|
432
|
+
if (!args) return undefined;
|
|
433
|
+
const matchingEdit = args.edits?.find(edit => edit.path === path);
|
|
434
|
+
if (matchingEdit) {
|
|
435
|
+
return {
|
|
436
|
+
...args,
|
|
437
|
+
path: matchingEdit.path,
|
|
438
|
+
op: matchingEdit.op,
|
|
439
|
+
rename: matchingEdit.rename ?? matchingEdit.move,
|
|
440
|
+
edits: [matchingEdit],
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
if (args.path === path || args.file_path === path) return args;
|
|
444
|
+
if (editMode === "apply_patch" && typeof args.input === "string") {
|
|
445
|
+
try {
|
|
446
|
+
const entry = expandApplyPatchToPreviewEntries({ input: args.input }).find(
|
|
447
|
+
candidate => candidate.path === path,
|
|
448
|
+
);
|
|
449
|
+
if (entry) return { path: entry.path, op: entry.op, rename: entry.rename, edits: [entry] };
|
|
450
|
+
} catch {
|
|
451
|
+
// Malformed free-form input has no safe per-file request metadata fallback.
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
return undefined;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
export function getPerFileEditRenderContext(
|
|
458
|
+
renderContext: EditRenderContext | undefined,
|
|
459
|
+
path: string,
|
|
460
|
+
): EditRenderContext | undefined {
|
|
461
|
+
if (!renderContext) return undefined;
|
|
462
|
+
const matchingPreview = renderContext.perFileDiffPreview?.find(preview => preview.path === path);
|
|
463
|
+
const editDiffPreview = matchingPreview?.error
|
|
464
|
+
? { error: matchingPreview.error }
|
|
465
|
+
: matchingPreview
|
|
466
|
+
? { diff: matchingPreview.diff ?? "", firstChangedLine: matchingPreview.firstChangedLine }
|
|
467
|
+
: undefined;
|
|
468
|
+
return { ...renderContext, editDiffPreview };
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function hasDiagnostics(
|
|
472
|
+
details: EditToolDetails | EditToolPerFileResult | undefined,
|
|
473
|
+
): details is (EditToolDetails | EditToolPerFileResult) & { diagnostics: FileDiagnosticsResult } {
|
|
474
|
+
return details !== undefined && details.diagnostics !== undefined;
|
|
475
|
+
}
|
|
476
|
+
|
|
534
477
|
function renderSingleFileResult(
|
|
535
478
|
result: {
|
|
536
479
|
content: Array<{ type: string; text?: string }>;
|
|
@@ -543,75 +486,74 @@ function renderSingleFileResult(
|
|
|
543
486
|
): Component {
|
|
544
487
|
const details = result.details;
|
|
545
488
|
const isError = result.isError ?? (details && "isError" in details ? details.isError : false);
|
|
546
|
-
const firstEdit = args?.edits?.[0];
|
|
547
|
-
const hashlineInputSummary = getHashlineInputRenderSummary(args ?? {}, options.renderContext?.editMode);
|
|
548
|
-
const firstHashlineInputEntry = hashlineInputSummary?.entries[0];
|
|
549
|
-
const rawPath =
|
|
550
|
-
args?.file_path ||
|
|
551
|
-
args?.path ||
|
|
552
|
-
filePathFromEditEntry(firstEdit?.path) ||
|
|
553
|
-
(details && "path" in details ? details.path : "") ||
|
|
554
|
-
firstHashlineInputEntry?.path ||
|
|
555
|
-
"";
|
|
556
|
-
const op = args?.op || firstEdit?.op || details?.op;
|
|
557
|
-
const rename = args?.rename || firstEdit?.rename || firstEdit?.move || details?.move;
|
|
558
|
-
const { language } = formatEditDescription(rawPath, uiTheme, { rename });
|
|
559
|
-
|
|
560
|
-
const editTextSource = args?.newText ?? args?.oldText ?? args?.diff ?? args?.patch;
|
|
561
|
-
const metadataLineCount = editTextSource ? countLines(editTextSource) : null;
|
|
562
|
-
const metadataLine = op !== "delete" ? `\n${formatMetadataLine(metadataLineCount, language, uiTheme)}` : "";
|
|
563
|
-
|
|
564
489
|
const displayErrorText = isError && details && "displayErrorText" in details ? details.displayErrorText : undefined;
|
|
565
490
|
const errorText = isError
|
|
566
491
|
? displayErrorText ||
|
|
567
492
|
(details && "errorText" in details && details.errorText) ||
|
|
568
493
|
(result.content?.find(c => c.type === "text")?.text ?? "")
|
|
569
494
|
: "";
|
|
570
|
-
|
|
495
|
+
const baseIdentity = resolveCompletedEditIdentity(details, args, options.renderContext?.editMode, options.isPartial);
|
|
571
496
|
let cached: RenderCache | undefined;
|
|
572
497
|
|
|
573
498
|
return {
|
|
574
499
|
render(width) {
|
|
575
500
|
const { expanded, renderContext } = options;
|
|
576
|
-
const
|
|
577
|
-
const
|
|
578
|
-
const
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
501
|
+
const preview = renderContext?.editDiffPreview;
|
|
502
|
+
const previewLine = preview && "firstChangedLine" in preview ? preview.firstChangedLine : undefined;
|
|
503
|
+
const identity = {
|
|
504
|
+
...baseIdentity,
|
|
505
|
+
firstChangedLine: baseIdentity.firstChangedLine ?? previewLine,
|
|
506
|
+
};
|
|
507
|
+
const { description } = formatEditDescription(identity.path, uiTheme, {
|
|
508
|
+
rename: identity.move,
|
|
509
|
+
firstChangedLine: identity.firstChangedLine,
|
|
510
|
+
});
|
|
586
511
|
const header = renderStatusLine(
|
|
587
|
-
{
|
|
588
|
-
icon: isError ? "error" : "success",
|
|
589
|
-
title: getOperationTitle(op),
|
|
590
|
-
description,
|
|
591
|
-
},
|
|
512
|
+
{ icon: isError ? "error" : "success", title: getOperationTitle(identity.op), description },
|
|
592
513
|
uiTheme,
|
|
593
514
|
);
|
|
594
|
-
|
|
595
|
-
|
|
515
|
+
const key = new Hasher().bool(expanded).u32(width).str(header).digest();
|
|
516
|
+
if (cached?.key === key) return cached.lines;
|
|
596
517
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
518
|
+
let text = header;
|
|
519
|
+
if (!expanded) {
|
|
520
|
+
if (isError) {
|
|
521
|
+
const cause = sanitizeText(errorText)
|
|
522
|
+
.split("\n")
|
|
523
|
+
.map(line => line.trim())
|
|
524
|
+
.find(Boolean);
|
|
525
|
+
if (cause) {
|
|
526
|
+
const availableWidth = Math.max(1, Math.min(80, width > 0 ? width : 80));
|
|
527
|
+
text += `\n${uiTheme.fg("error", truncateToWidth(replaceTabs(cause, identity.path), availableWidth))}`;
|
|
528
|
+
}
|
|
529
|
+
} else if (details?.diff) {
|
|
530
|
+
const stats = getDiffStats(details.diff);
|
|
531
|
+
text += `\n${uiTheme.fg("dim", uiTheme.format.bracketLeft)}${formatDiffStats(
|
|
532
|
+
stats.added,
|
|
533
|
+
stats.removed,
|
|
534
|
+
stats.hunks,
|
|
535
|
+
uiTheme,
|
|
536
|
+
)}${uiTheme.fg("dim", uiTheme.format.bracketRight)}`;
|
|
600
537
|
}
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
if (
|
|
605
|
-
text += `\n\n${uiTheme.fg("error", replaceTabs(
|
|
606
|
-
} else if (
|
|
607
|
-
text += renderDiffSection(
|
|
538
|
+
if (hasDiagnostics(details)) text += `\n${uiTheme.fg("dim", "Diagnostics available")}`;
|
|
539
|
+
} else {
|
|
540
|
+
const renderDiffFn = renderContext?.renderDiff ?? ((diff: string) => diff);
|
|
541
|
+
if (isError) {
|
|
542
|
+
if (errorText) text += `\n\n${uiTheme.fg("error", replaceTabs(sanitizeText(errorText), identity.path))}`;
|
|
543
|
+
} else if (details?.diff) {
|
|
544
|
+
text += renderDiffSection(details.diff, identity.path, true, uiTheme, renderDiffFn);
|
|
545
|
+
} else if (preview) {
|
|
546
|
+
if ("error" in preview) {
|
|
547
|
+
text += `\n\n${uiTheme.fg("error", replaceTabs(sanitizeText(preview.error), identity.path))}`;
|
|
548
|
+
} else if (preview.diff) {
|
|
549
|
+
text += renderDiffSection(preview.diff, identity.path, true, uiTheme, renderDiffFn);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
if (hasDiagnostics(details)) {
|
|
553
|
+
text += formatDiagnostics(details.diagnostics, true, uiTheme, (fp: string) =>
|
|
554
|
+
uiTheme.getLangIcon(getLanguageFromPath(fp)),
|
|
555
|
+
);
|
|
608
556
|
}
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
if (details?.diagnostics) {
|
|
612
|
-
text += formatDiagnostics(details.diagnostics, expanded, uiTheme, (fp: string) =>
|
|
613
|
-
uiTheme.getLangIcon(getLanguageFromPath(fp)),
|
|
614
|
-
);
|
|
615
557
|
}
|
|
616
558
|
|
|
617
559
|
const lines =
|
|
@@ -628,13 +570,25 @@ function renderSingleFileResult(
|
|
|
628
570
|
function renderMultiFileResult(
|
|
629
571
|
perFileResults: EditToolPerFileResult[],
|
|
630
572
|
totalFiles: number,
|
|
573
|
+
representedFiles: number,
|
|
631
574
|
options: RenderResultOptions & { renderContext?: EditRenderContext },
|
|
632
575
|
uiTheme: Theme,
|
|
576
|
+
args: EditRenderArgs | undefined,
|
|
633
577
|
): Component {
|
|
634
|
-
const fileComponents = perFileResults.map(fileResult =>
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
578
|
+
const fileComponents = perFileResults.map(fileResult => {
|
|
579
|
+
const fileOptions: RenderResultOptions & { renderContext?: EditRenderContext } = {
|
|
580
|
+
...options,
|
|
581
|
+
renderContext: getPerFileEditRenderContext(options.renderContext, fileResult.path),
|
|
582
|
+
};
|
|
583
|
+
const fileArgs = getPerFileEditRenderArgs(args, fileResult.path, options.renderContext?.editMode);
|
|
584
|
+
return renderSingleFileResult(
|
|
585
|
+
{ content: [], details: fileResult, isError: fileResult.isError },
|
|
586
|
+
fileOptions,
|
|
587
|
+
uiTheme,
|
|
588
|
+
fileArgs,
|
|
589
|
+
);
|
|
590
|
+
});
|
|
591
|
+
const remaining = options.isPartial ? Math.max(0, totalFiles - representedFiles) : 0;
|
|
638
592
|
|
|
639
593
|
let cached: RenderCache | undefined;
|
|
640
594
|
|
|
@@ -651,7 +605,6 @@ function renderMultiFileResult(
|
|
|
651
605
|
allLines.push(...fileComponents[i].render(width));
|
|
652
606
|
}
|
|
653
607
|
|
|
654
|
-
// Show pending indicator for files still being processed
|
|
655
608
|
if (remaining > 0) {
|
|
656
609
|
if (allLines.length > 0) allLines.push("");
|
|
657
610
|
const spinnerFrame = options.spinnerFrame;
|
|
@@ -667,7 +620,6 @@ function renderMultiFileResult(
|
|
|
667
620
|
),
|
|
668
621
|
);
|
|
669
622
|
if (spinner) {
|
|
670
|
-
// Replace the pending icon with spinner on the last line
|
|
671
623
|
allLines[allLines.length - 1] = allLines[allLines.length - 1].replace(/^(?:\x1b\[[^m]*m)*./u, spinner);
|
|
672
624
|
}
|
|
673
625
|
}
|
package/src/edit/streaming.ts
CHANGED
|
@@ -31,7 +31,7 @@ import { replaceTabs, truncateToWidth } from "../tools/render-utils";
|
|
|
31
31
|
import { type EditMode, resolveEditMode } from "../utils/edit-mode";
|
|
32
32
|
import { computeEditDiff, type DiffError, type DiffResult } from "./diff";
|
|
33
33
|
import { type ApplyPatchEntry, expandApplyPatchToEntries, expandApplyPatchToPreviewEntries } from "./modes/apply-patch";
|
|
34
|
-
import { computePatchDiff, type PatchEditEntry } from "./modes/patch";
|
|
34
|
+
import { computePatchDiff, type Operation, type PatchEditEntry } from "./modes/patch";
|
|
35
35
|
import type { ReplaceEditEntry } from "./modes/replace";
|
|
36
36
|
|
|
37
37
|
export interface PerFileDiffPreview {
|
|
@@ -74,6 +74,113 @@ export interface EditStreamingStrategy<Args = unknown> {
|
|
|
74
74
|
*/
|
|
75
75
|
renderStreamingFallback(args: Args, uiTheme: Theme): string;
|
|
76
76
|
}
|
|
77
|
+
export interface EditRequestTargetInventory {
|
|
78
|
+
paths: string[];
|
|
79
|
+
/** Operation of the first target (Create/Delete) when derivable from the
|
|
80
|
+
* request payload (apply_patch/hashline free-form envelopes carry op on the
|
|
81
|
+
* parsed entry, not on the top-level args). */
|
|
82
|
+
firstOp?: Operation;
|
|
83
|
+
parseError?: string;
|
|
84
|
+
}
|
|
85
|
+
const MISSING_APPLY_PATCH_END_ERROR = `The last line of the patch must be '${END_PATCH_MARKER}'`;
|
|
86
|
+
|
|
87
|
+
export function orderedDistinctPaths(paths: readonly (string | undefined)[]): string[] {
|
|
88
|
+
const seen = new Set<string>();
|
|
89
|
+
const distinct: string[] = [];
|
|
90
|
+
for (const path of paths) {
|
|
91
|
+
if (!path || seen.has(path)) continue;
|
|
92
|
+
seen.add(path);
|
|
93
|
+
distinct.push(path);
|
|
94
|
+
}
|
|
95
|
+
return distinct;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function decodePartialJsonStringFragment(fragment: string): string {
|
|
99
|
+
let text = fragment.replace(/\\u[0-9a-fA-F]{0,3}$/, "");
|
|
100
|
+
const trailingBackslashes = text.match(/\\+$/)?.[0].length ?? 0;
|
|
101
|
+
if (trailingBackslashes % 2 === 1) text = text.slice(0, -1);
|
|
102
|
+
try {
|
|
103
|
+
return JSON.parse(`"${text}"`) as string;
|
|
104
|
+
} catch {
|
|
105
|
+
return text;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function extractTrailingPartialJsonPath(partialJson: string | undefined): string | undefined {
|
|
110
|
+
if (!partialJson) return undefined;
|
|
111
|
+
const pattern = /"(?:file_path|path)"\s*:\s*"((?:\\.|[^"\\])*)/gu;
|
|
112
|
+
let trailingPath: string | undefined;
|
|
113
|
+
for (const match of partialJson.matchAll(pattern)) {
|
|
114
|
+
trailingPath = decodePartialJsonStringFragment(match[1]);
|
|
115
|
+
}
|
|
116
|
+
return trailingPath;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function normalizeHashlineTargetPath(rawPath: string): string {
|
|
120
|
+
const trimmed = rawPath.trim();
|
|
121
|
+
if (trimmed.length < 2) return trimmed;
|
|
122
|
+
const first = trimmed[0];
|
|
123
|
+
const last = trimmed[trimmed.length - 1];
|
|
124
|
+
return (first === '"' || first === "'") && first === last ? trimmed.slice(1, -1) : trimmed;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function getHashlineTargetPaths(input: string): string[] {
|
|
128
|
+
const stripped = input.startsWith("\uFEFF") ? input.slice(1) : input;
|
|
129
|
+
const paths: string[] = [];
|
|
130
|
+
for (const rawLine of stripped.split("\n")) {
|
|
131
|
+
const trimmed = rawLine.replace(/\r$/, "").trimEnd();
|
|
132
|
+
if (trimmed === END_PATCH_MARKER || trimmed === ABORT_MARKER) break;
|
|
133
|
+
if (isHashlineHeaderLine(trimmed)) paths.push(normalizeHashlineTargetPath(parseHashlineHeaderPath(trimmed)));
|
|
134
|
+
}
|
|
135
|
+
return paths.filter(Boolean);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function getEditRequestTargetInventory(
|
|
139
|
+
args: unknown,
|
|
140
|
+
editMode: EditMode | undefined,
|
|
141
|
+
options: { isPartial: boolean },
|
|
142
|
+
): EditRequestTargetInventory {
|
|
143
|
+
if (editMode === "vim") return { paths: [] };
|
|
144
|
+
const values = args && typeof args === "object" ? (args as Record<string, unknown>) : {};
|
|
145
|
+
const partialJson = typeof values.__partialJson === "string" ? values.__partialJson : undefined;
|
|
146
|
+
const topLevelPath =
|
|
147
|
+
typeof values.file_path === "string"
|
|
148
|
+
? values.file_path
|
|
149
|
+
: typeof values.path === "string"
|
|
150
|
+
? values.path
|
|
151
|
+
: undefined;
|
|
152
|
+
const edits = Array.isArray(values.edits) ? values.edits : [];
|
|
153
|
+
|
|
154
|
+
if (editMode === "hashline") {
|
|
155
|
+
const input = typeof values.input === "string" ? values.input : "";
|
|
156
|
+
const headerPaths = getHashlineTargetPaths(input);
|
|
157
|
+
return { paths: orderedDistinctPaths(headerPaths.length > 0 ? headerPaths : [topLevelPath]) };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (editMode === "apply_patch") {
|
|
161
|
+
const input = typeof values.input === "string" ? values.input : "";
|
|
162
|
+
try {
|
|
163
|
+
const entries = expandApplyPatchToEntries({ input });
|
|
164
|
+
return { paths: orderedDistinctPaths(entries.map(entry => entry.path)), firstOp: entries[0]?.op };
|
|
165
|
+
} catch (err) {
|
|
166
|
+
const parseError = err instanceof Error ? err.message : String(err);
|
|
167
|
+
const previewEntries = expandApplyPatchToPreviewEntries({ input });
|
|
168
|
+
const paths = orderedDistinctPaths(previewEntries.map(entry => entry.path));
|
|
169
|
+
if (options.isPartial && parseError === MISSING_APPLY_PATCH_END_ERROR) {
|
|
170
|
+
return { paths, firstOp: previewEntries[0]?.op };
|
|
171
|
+
}
|
|
172
|
+
return { paths, firstOp: previewEntries[0]?.op, parseError };
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const editPaths = edits.map(edit => {
|
|
177
|
+
if (!edit || typeof edit !== "object") return undefined;
|
|
178
|
+
const entry = edit as Record<string, unknown>;
|
|
179
|
+
return typeof entry.path === "string" ? entry.path : undefined;
|
|
180
|
+
});
|
|
181
|
+
const partialPath = options.isPartial ? extractTrailingPartialJsonPath(partialJson) : undefined;
|
|
182
|
+
return { paths: orderedDistinctPaths([topLevelPath, ...editPaths, partialPath]) };
|
|
183
|
+
}
|
|
77
184
|
|
|
78
185
|
const STREAMING_FALLBACK_LINES = 12;
|
|
79
186
|
const STREAMING_FALLBACK_WIDTH = 80;
|