Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -611,10 +611,17 @@ function extractLocations(rawInput, toolCallResult) {
if (toolCallResult && typeof toolCallResult === 'object') {
const display = toolCallResult.resultDisplay;
if (display && typeof display === 'object') {
if (typeof display.fileName === 'string' && display.fileName) {
// Prefer filePath (full path) over fileName (basename-only) when both are present.
const displayPath =
typeof display.filePath === 'string' && display.filePath
? display.filePath
: typeof display.fileName === 'string' && display.fileName
? display.fileName
: undefined;
if (displayPath) {
// Avoid duplicates
if (!locations.some((loc) => loc.path === display.fileName)) {
locations.push({ path: display.fileName });
if (!locations.some((loc) => loc.path === displayPath)) {
locations.push({ path: displayPath });
}
}
}
Expand All @@ -630,7 +637,11 @@ function extractDiffContent(resultDisplay) {
return [
{
type: 'diff',
path: display.fileName,
// Prefer filePath (full path) over fileName (basename-only) when both are present.
path:
typeof display.filePath === 'string' && display.filePath
? display.filePath
: display.fileName,
oldText: display.originalContent ?? '',
newText: display.newContent,
},
Expand Down
107 changes: 107 additions & 0 deletions packages/acp-bridge/src/transcript-replay.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1275,6 +1275,113 @@ describe('createTranscriptReplayMachine', () => {
expect(machine.snapshot().pendingToolCalls).toEqual([]);
});

it('prefers filePath over the fileName basename when replaying an edit diff', () => {
const machine = createTranscriptReplayMachine();
updates(
machine,
record('assistant-1', 'assistant', {
message: {
role: 'model',
parts: [
{ functionCall: { name: 'edit_file', args: {}, id: 'call-1' } },
],
},
}),
);
const result = updates(
machine,
record('result-1', 'tool_result', {
message: {
role: 'user',
parts: [
{
functionResponse: {
name: 'edit_file',
response: { output: 'edited' },
},
},
],
},
toolCallResult: {
callId: 'call-1',
resultDisplay: {
fileDiff: '--- a\n+++ b\n',
fileName: 'Foo.kt',
filePath: '/workspace/app/src/main/java/com/example/Foo.kt',
originalContent: 'old',
newContent: 'new',
},
},
}),
);

expect(result[0]).toMatchObject({
sessionUpdate: 'tool_call_update',
toolCallId: 'call-1',
content: [
{
type: 'diff',
path: '/workspace/app/src/main/java/com/example/Foo.kt',
oldText: 'old',
newText: 'new',
},
],
});
});

it('falls back to the fileName basename when filePath is absent (pre-fix persisted sessions)', () => {
const machine = createTranscriptReplayMachine();
updates(
machine,
record('assistant-1', 'assistant', {
message: {
role: 'model',
parts: [
{ functionCall: { name: 'edit_file', args: {}, id: 'call-1' } },
],
},
}),
);
const result = updates(
machine,
record('result-1', 'tool_result', {
message: {
role: 'user',
parts: [
{
functionResponse: {
name: 'edit_file',
response: { output: 'edited' },
},
},
],
},
toolCallResult: {
callId: 'call-1',
resultDisplay: {
fileDiff: '--- a\n+++ b\n',
fileName: 'Foo.kt',
originalContent: 'old',
newContent: 'new',
},
},
}),
);

expect(result[0]).toMatchObject({
sessionUpdate: 'tool_call_update',
toolCallId: 'call-1',
content: [
{
type: 'diff',
path: 'Foo.kt',
oldText: 'old',
newText: 'new',
},
],
});
});

it('reports ambiguous same-name result correlation', () => {
const onDiagnostic = vi.fn();
const machine = createTranscriptReplayMachine({ onDiagnostic });
Expand Down
8 changes: 5 additions & 3 deletions packages/acp-bridge/src/transcript-replay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1458,9 +1458,11 @@ function extractDiffContent(resultDisplay: unknown): ToolCallContent | null {
return {
type: 'diff',
path:
typeof resultDisplay['fileName'] === 'string'
? resultDisplay['fileName']
: '',
typeof resultDisplay['filePath'] === 'string'
? resultDisplay['filePath']
: typeof resultDisplay['fileName'] === 'string'
? resultDisplay['fileName']
: '',
oldText:
typeof resultDisplay['originalContent'] === 'string'
? resultDisplay['originalContent']
Expand Down
105 changes: 105 additions & 0 deletions packages/cli/src/ui/utils/export/normalize.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,111 @@ describe('normalizeSessionData', () => {
]);
});

it('exports the diff path from filePath rather than the fileName basename', () => {
const record: ChatRecord = {
uuid: 'tool-2',
parentUuid: null,
sessionId: 'session-1',
timestamp: '2025-01-01T00:00:00.000Z',
type: 'tool_result',
cwd: '',
version: '1.0.0',
message: {
role: 'user',
parts: [
{
functionResponse: {
id: 'call-2',
name: 'edit_file',
response: { output: 'ok' },
},
},
],
},
toolCallResult: {
callId: 'call-2',
resultDisplay: {
fileName: 'Foo.kt',
filePath: '/workspace/app/src/main/java/com/example/Foo.kt',
fileDiff: '--- Foo.kt\n+++ Foo.kt\n',
originalContent: 'old',
newContent: 'new',
},
},
};

const normalized = normalizeSessionData(
{
sessionId: 'session-1',
startTime: '2025-01-01T00:00:00.000Z',
messages: [],
},
[record],
config,
);

expect(normalized.messages[0].toolCall?.content).toEqual([
{
type: 'diff',
path: '/workspace/app/src/main/java/com/example/Foo.kt',
oldText: 'old',
newText: 'new',
},
]);
});

it('falls back to the fileName basename when filePath is absent (pre-fix persisted sessions)', () => {
const record: ChatRecord = {
uuid: 'tool-2b',
parentUuid: null,
sessionId: 'session-1',
timestamp: '2025-01-01T00:00:00.000Z',
type: 'tool_result',
cwd: '',
version: '1.0.0',
message: {
role: 'user',
parts: [
{
functionResponse: {
id: 'call-2b',
name: 'edit_file',
response: { output: 'ok' },
},
},
],
},
toolCallResult: {
callId: 'call-2b',
resultDisplay: {
fileName: 'Foo.kt',
fileDiff: '--- Foo.kt\n+++ Foo.kt\n',
originalContent: 'old',
newContent: 'new',
},
},
};

const normalized = normalizeSessionData(
{
sessionId: 'session-1',
startTime: '2025-01-01T00:00:00.000Z',
messages: [],
},
[record],
config,
);

expect(normalized.messages[0].toolCall?.content).toEqual([
{
type: 'diff',
path: 'Foo.kt',
oldText: 'old',
newText: 'new',
},
]);
});

it('accepts the minimal daemon export config shape', () => {
const minimalConfig: ExportConfig = {};
const record: ChatRecord = {
Expand Down
5 changes: 4 additions & 1 deletion packages/cli/src/ui/utils/export/normalize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,10 @@ function extractDiffContent(
return [
{
type: 'diff',
path: display['fileName'] as string,
path:
typeof display['filePath'] === 'string'
? display['filePath']
: (display['fileName'] as string),
oldText: (display['originalContent'] as string) ?? '',
newText: display['newContent'] as string,
},
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/core/coreToolScheduler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7673,6 +7673,7 @@ describe('CoreToolScheduler edit cancellation', () => {
'--- test.txt\n+++ test.txt\n@@ -1,1 +1,1 @@\n-old content\n+new content',
);
expect(cancelledCall.response.resultDisplay.fileName).toBe('test.txt');
expect(cancelledCall.response.resultDisplay.filePath).toBe('test.txt');
});
});

Expand Down
1 change: 1 addition & 0 deletions packages/core/src/core/coreToolScheduler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1646,6 +1646,7 @@ export class CoreToolScheduler {
resultDisplay = {
fileDiff: waitingCall.confirmationDetails.fileDiff,
fileName: waitingCall.confirmationDetails.fileName,
filePath: waitingCall.confirmationDetails.filePath,
originalContent:
waitingCall.confirmationDetails.originalContent,
newContent: waitingCall.confirmationDetails.newContent,
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/tools/edit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,10 @@ describe('EditTool', () => {
expect(display.fileDiff).toMatch(initialContent);
expect(display.fileDiff).toMatch(newContent);
expect(display.fileName).toBe(testFile);
// `filePath` must carry the full path: UI consumers (e.g. the VSCode
// companion) cannot resolve a clickable location from `fileName`
// alone once the file is outside the workspace root.
expect(display.filePath).toBe(filePath);
expect(writeSpy).toHaveBeenCalledWith(
expect.objectContaining({ toolWriteOrigin: 'edit' }),
);
Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/tools/edit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import * as fs from 'node:fs';
import * as path from 'node:path';
import type {
FileDiff,
ToolCallConfirmationDetails,
ToolEditConfirmationDetails,
ToolInvocation,
Expand Down Expand Up @@ -674,9 +675,10 @@ class EditToolInvocation implements ToolInvocation<EditToolParams, ToolResult> {
'Current',
'Proposed',
);
const displayResult = {
const displayResult: FileDiff = {
fileDiff,
fileName,
filePath: this.params.file_path,
originalContent: editData.currentContent,
newContent: editData.newContent,
diffStat,
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/tools/notebook-edit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,7 @@ class NotebookEditInvocation extends BaseToolInvocation<
const displayResult = {
fileDiff,
fileName,
filePath: this.params.notebook_path,
originalContent: prepared.originalContent,
newContent: prepared.updatedContent,
diffStat,
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/tools/shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1768,6 +1768,7 @@ export class ShellToolInvocation extends BaseToolInvocation<
'Proposed',
),
fileName: edit.fileName,
filePath: edit.filePath,
originalContent: edit.originalContent,
newContent: edit.newContent,
diffStat,
Expand Down
7 changes: 7 additions & 0 deletions packages/core/src/tools/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -813,6 +813,13 @@ export interface TaskListResultDisplay {
export interface FileDiff {
fileDiff: string;
fileName: string;
/**
* Full (project-relative or absolute) path to the edited file, as passed
* to the tool. UI consumers must prefer this over `fileName` when
* resolving a clickable/openable location — `fileName` is a basename and
* cannot be used to locate files outside the workspace root.
*/
filePath?: string;
originalContent: string | null;
newContent: string;
diffStat?: DiffStat;
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/tools/write-file.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,7 @@ describe('WriteFileTool', () => {
expect(writtenContent).toBe(proposedContent);
const display = result.returnDisplay as FileDiff;
expect(display.fileName).toBe('execute_new_file.txt');
expect(display.filePath).toBe(filePath);
expect(display.fileDiff).toMatch(/--- execute_new_file.txt\tOriginal/);
expect(display.fileDiff).toMatch(/\+\+\+ execute_new_file.txt\tWritten/);
expect(display.fileDiff).toMatch(
Expand Down Expand Up @@ -857,6 +858,7 @@ describe('WriteFileTool', () => {
expect(writtenContent).toBe(proposedContent);
const display = result.returnDisplay as FileDiff;
expect(display.fileName).toBe('execute_existing_file.txt');
expect(display.filePath).toBe(filePath);
expect(display.fileDiff).toMatch(
initialContent.replace(/[.*+?^${}()|[\\]\\]/g, '\\$&'),
);
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/tools/write-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,7 @@ class WriteFileToolInvocation extends BaseToolInvocation<
const displayResult: FileDiff = {
fileDiff,
fileName,
filePath: file_path,
originalContent,
newContent: content,
diffStat,
Expand Down
Loading