fix(acp): prevent tool events from fragmenting assistant messages - #3181
fix(acp): prevent tool events from fragmenting assistant messages#3181kekubhai wants to merge 4 commits into
Conversation
Greptile SummaryThis PR currently adds an unrelated Git-exclusion test file rather than the described ACP transcript fix.
Confidence Score: 3/5The PR is not safe to merge because the added test cannot resolve its imported module and the intended ACP fragmentation fix is absent. The only changed file introduces a test-collection/typechecking failure while neither changing ACP production behavior nor adding the described ACP regression coverage. Files Needing Attention: apps/emdash-desktop/src/core/features/projects/node/ensure-emdash-excluded.test.ts
|
| Filename | Overview |
|---|---|
| apps/emdash-desktop/src/core/features/projects/node/ensure-emdash-excluded.test.ts | Adds Git-exclusion tests that import a missing module and do not cover the stated ACP behavior. |
Prompt To Fix All With AI
### Issue 1
apps/emdash-desktop/src/core/features/projects/node/ensure-emdash-excluded.test.ts:4
**Imported Module Is Missing**
This test imports `./ensure-emdash-excluded`, but that sibling module does not exist in the repository. Because the desktop Node test project includes this file, module resolution fails during typechecking or test collection and blocks the merge checks.
### Issue 2
apps/emdash-desktop/src/core/features/projects/node/ensure-emdash-excluded.test.ts:73-148
**ACP Fix Is Absent**
The PR is intended to prevent ACP transcript fragmentation, but every added test exercises Git exclusion behavior instead. No ACP reducer or parser behavior is changed or covered, so interleaved tool events retain the existing fragmentation behavior and the reported issue remains unfixed.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Reviews (1): Last reviewed commit: "Merge branch 'generalaction:main' into m..." | Re-trigger Greptile
| import { describe, expect, it, vi } from 'vitest'; | ||
| import { nativePathFromHost } from '@core/primitives/desktop-runtime/api'; | ||
| import { filesClientScope } from '@core/services/runtime-broker/node/files'; | ||
| import { ensureEmdashGitExcluded } from './ensure-emdash-excluded'; |
There was a problem hiding this comment.
This test imports ./ensure-emdash-excluded, but that sibling module does not exist in the repository. Because the desktop Node test project includes this file, module resolution fails during typechecking or test collection and blocks the merge checks.
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/emdash-desktop/src/core/features/projects/node/ensure-emdash-excluded.test.ts
Line: 4
Comment:
**Imported Module Is Missing**
This test imports `./ensure-emdash-excluded`, but that sibling module does not exist in the repository. Because the desktop Node test project includes this file, module resolution fails during typechecking or test collection and blocks the merge checks.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| it('skips repos without a real .git directory (linked worktree / submodule)', async () => { | ||
| const { files, writeFile } = makeFs({ gitType: 'file', excludeContent: '' }); | ||
| await ensureEmdashGitExcluded(files, '/repo'); | ||
| expect(writeFile).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it('skips when there is no .git at all', async () => { | ||
| const { files, writeFile } = makeFs({ excludeContent: '' }); | ||
| await ensureEmdashGitExcluded(files, '/repo'); | ||
| expect(writeFile).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it('creates the exclude entry when info/exclude is missing', async () => { | ||
| const { files, writeFile } = makeFs({ gitType: 'directory', excludeContent: null }); | ||
| await ensureEmdashGitExcluded(files, '/repo'); | ||
| expect(writeFile).toHaveBeenCalledWith(expect.objectContaining({ content: '.emdash/\n' })); | ||
| expect(relativeToRepo(writeFile.mock.calls[0]![0])).toBe('.git/info/exclude'); | ||
| }); | ||
|
|
||
| it('appends the entry, preserving existing exclude content', async () => { | ||
| const { files, writeFile } = makeFs({ | ||
| gitType: 'directory', | ||
| excludeContent: '# git ls-files\nbuild/\n', | ||
| }); | ||
| await ensureEmdashGitExcluded(files, '/repo'); | ||
| expect(writeFile).toHaveBeenCalledWith( | ||
| expect.objectContaining({ content: '# git ls-files\nbuild/\n.emdash/\n' }) | ||
| ); | ||
| expect(relativeToRepo(writeFile.mock.calls[0]![0])).toBe('.git/info/exclude'); | ||
| }); | ||
|
|
||
| it('does nothing when .emdash/ is already excluded', async () => { | ||
| const { files, writeFile } = makeFs({ | ||
| gitType: 'directory', | ||
| excludeContent: 'foo\n.emdash/\n', | ||
| }); | ||
| await ensureEmdashGitExcluded(files, '/repo'); | ||
| expect(writeFile).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it('treats a slashless .emdash entry as already excluded', async () => { | ||
| const { files, writeFile } = makeFs({ gitType: 'directory', excludeContent: '.emdash\n' }); | ||
| await ensureEmdashGitExcluded(files, '/repo'); | ||
| expect(writeFile).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it('treats a **/ prefixed .emdash/ entry as already excluded', async () => { | ||
| const { files, writeFile } = makeFs({ | ||
| gitType: 'directory', | ||
| excludeContent: '**/.emdash/\n', | ||
| }); | ||
| await ensureEmdashGitExcluded(files, '/repo'); | ||
| expect(writeFile).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it('treats a **/ prefixed .emdash entry (no trailing slash) as already excluded', async () => { | ||
| const { files, writeFile } = makeFs({ | ||
| gitType: 'directory', | ||
| excludeContent: '**/.emdash\n', | ||
| }); | ||
| await ensureEmdashGitExcluded(files, '/repo'); | ||
| expect(writeFile).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it('does not rewrite when the exclude read was truncated', async () => { | ||
| // A truncated view could miss an existing entry past the cut; rewriting it would | ||
| // drop the tail of the file, so bail instead. | ||
| const { files, writeFile } = makeFs({ | ||
| gitType: 'directory', | ||
| excludeContent: 'build/\n', | ||
| truncated: true, | ||
| }); | ||
| await ensureEmdashGitExcluded(files, '/repo'); | ||
| expect(writeFile).not.toHaveBeenCalled(); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
The PR is intended to prevent ACP transcript fragmentation, but every added test exercises Git exclusion behavior instead. No ACP reducer or parser behavior is changed or covered, so interleaved tool events retain the existing fragmentation behavior and the reported issue remains unfixed.
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/emdash-desktop/src/core/features/projects/node/ensure-emdash-excluded.test.ts
Line: 73-148
Comment:
**ACP Fix Is Absent**
The PR is intended to prevent ACP transcript fragmentation, but every added test exercises Git exclusion behavior instead. No ACP reducer or parser behavior is changed or covered, so interleaved tool events retain the existing fragmentation behavior and the reported issue remains unfixed.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Description
Fix ACP streamed assistant messages being incorrectly split into multiple transcript messages when tool events are interleaved between
agent_message_chunkevents without amessageId.Tool calls, tool updates, and plan events no longer close an open synthesized assistant segment. This keeps subsequent message chunks attached to the same logical assistant response while preserving existing behavior for explicit message IDs.
Added regression coverage for consecutive chunks, interleaved tool events, Markdown spanning chunks, and explicit message ID behavior.
Related issues
Fixes #3166.
Testing
messageIdassistant chunks remain a single transcript message across tool events.Screenshot/Recording (if applicable)
Not applicable — this is a backend/reducer behavior fix with regression tests.
Checklist