diff --git a/package-lock.json b/package-lock.json index 85d1123..350c4c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "dsh-cctui", - "version": "0.3.9", + "version": "0.3.10", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "dsh-cctui", - "version": "0.3.9", + "version": "0.3.10", "license": "MIT", "dependencies": { "@dsh-cctui/ink": "file:./packages/dsh-cctui-ink", diff --git a/package.json b/package.json index 55630cb..c05319d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dsh-cctui", - "version": "0.3.9", + "version": "0.3.10", "description": "Claude-Code-style terminal UI for deepseek-harness, ported from clawcodex ui-tui", "license": "MIT", "repository": { diff --git a/scripts/tool-gallery.py b/scripts/tool-gallery.py index 47b1c3b..242832c 100755 --- a/scripts/tool-gallery.py +++ b/scripts/tool-gallery.py @@ -58,10 +58,13 @@ "glob": 'PROBE glob {"pattern": "src/components/*.tsx"}', "bash": 'PROBE bash {"command": "ls -1 src | head -12", "description": "list src"}', "bash_fail": 'PROBE bash {"command": "ls /no/such/dir", "description": "failing ls"}', - "write": 'PROBE write {"file_path": "e2e-scratch/gallery.txt", "content": "alpha\\nbeta\\ngamma\\n"}', + "write": 'PROBE write {"file_path": "e2e-scratch/gallery-write.txt", "content": "alpha\\nbeta\\ngamma\\n"}', + # the harness refuses to edit a file this session has not observed, so the + # scenario writes it, reads it, then edits it "edit": [ - 'PROBE write {"file_path": "e2e-scratch/gallery.txt", "content": "alpha\\nbeta\\ngamma\\n"}', - 'PROBE edit {"file_path": "e2e-scratch/gallery.txt", "old_string": "beta", "new_string": "BETA-changed"}', + 'PROBE write {"file_path": "e2e-scratch/gallery-edit.txt", "content": "alpha\\nbeta\\ngamma\\n"}', + 'PROBE read {"file_path": "e2e-scratch/gallery-edit.txt"}', + 'PROBE edit {"file_path": "e2e-scratch/gallery-edit.txt", "old_string": "beta", "new_string": "BETA-changed"}', ], # reaches the network on purpose — the fetch card is a retrieval summary "web_fetch": 'PROBE web_fetch {"url": "https://example.com"}', @@ -83,7 +86,8 @@ ), "skill": 'PROBE skill {"name": "nonexistent-skill"}', "read_image": 'PROBE read_image {"file_path": "e2e-scratch/gallery.png"}', - "str_replace_editor": 'PROBE str_replace_editor {"command": "view", "path": "src/domain/usage.ts"}', + # this one insists on an absolute path + "str_replace_editor": ('PROBE str_replace_editor {"command": "view", "path": "%s/src/domain/usage.ts"}' % ROOT), "job_output": 'PROBE job_output {"job_id": "no-such-job"}', "send_message": 'PROBE send_message {"subagent_id": "no-such-agent", "message": "hello"}', "get_goal": 'PROBE get_goal {}', @@ -111,6 +115,9 @@ def bootstrap() -> None: + # the mutation scenarios must CREATE their file, not find yesterday's + for leftover in (ROOT / "e2e-scratch").glob("gallery-*.txt"): + leftover.unlink() PROFILE.mkdir(parents=True, exist_ok=True) (PROFILE / "package.json").write_text( json.dumps( diff --git a/src/__tests__/harnessClient.test.ts b/src/__tests__/harnessClient.test.ts index fac9ed5..0fc4d45 100644 --- a/src/__tests__/harnessClient.test.ts +++ b/src/__tests__/harnessClient.test.ts @@ -51,7 +51,10 @@ const TOOL_CARDS: Record = { path: 'src/y.ts', totalLines: 40 }, - write: { card: 'diff', diffs: [{ newText: 'line one\nline two\n', oldText: null, path: 'notes.txt' }] } + write: { card: 'diff', diffs: [{ newText: 'line one\nline two\n', oldText: null, path: 'notes.txt' }] }, + // an APPLIED HUNK rather than a whole file: it stops mid-file, so neither + // side carries a trailing newline + write_hunk: { card: 'diff', diffs: [{ newText: 'beta\nBETA', oldText: 'beta\nbeta', path: 'notes.txt' }] } } function makeWorld() { @@ -1024,6 +1027,16 @@ describe('HarnessGatewayClient', () => { expect(usage.context_percent).toBe(5) }) + it('drops the no-newline marker a hunk-shaped diff always produces', async () => { + const done = await runTool('write_hunk', '{"file_path":"notes.txt"}') + const lines = done.payload.structured_diff?.hunks.flatMap(hunk => hunk.lines) ?? [] + + // the row renderer has no marker for `\ No newline at end of file`, so it + // came out as a phantom context line numbered one past the end + expect(lines.length).toBeGreaterThan(0) + expect(lines.some(line => line.startsWith('\\'))).toBe(false) + }) + it('converts diff presentation views into structured_diff payloads', async () => { const w = makeWorld() diff --git a/src/harness/client.ts b/src/harness/client.ts index 057ca37..3fd22d3 100644 --- a/src/harness/client.ts +++ b/src/harness/client.ts @@ -1092,7 +1092,13 @@ export class HarnessGatewayClient extends GatewayClient { const structuredDiff: StructuredDiffPayload = { filePath: first.path, hunks: patch.hunks.map(h => ({ - lines: h.lines, + // `\ No newline at end of file` is an artifact of diffing the + // tool's applied HUNK rather than the whole file: a fragment that + // stops mid-file has no trailing newline by construction, and the + // file it came from is usually fine. The row renderer has no marker + // for it either, so it came out as a phantom context line numbered + // one past the end. Upstream shows no such row. + lines: h.lines.filter(line => !line.startsWith('\\')), newLines: h.newLines, newStart: h.newStart, oldLines: h.oldLines,