Merge remote-tracking branch 'origin/master' into worktree/fix-multi-select-custom-answer
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
terminal 100x40 buffer=normal length=40 base=0 viewport=0
|
||||
terminal 100x40 buffer=normal length=41 base=1 viewport=1
|
||||
lifecycle started=1 stopped=0 progress=inactive
|
||||
title "DSH snapshot"
|
||||
cursor hidden column=7 viewportRow=34 bufferRow=34
|
||||
cursor hidden column=7 viewportRow=39 bufferRow=40
|
||||
buffer
|
||||
0| " DEEPSEEK HARNESS"
|
||||
style 1-8 fg=bright-magenta bold
|
||||
@@ -31,9 +31,9 @@ buffer
|
||||
style 0-10 bold
|
||||
14| "- old line "
|
||||
style 0-9 fg=red
|
||||
15| "… +3 lines (Ctrl+O to expand) "
|
||||
15| "… +2 lines (Ctrl+O to expand) "
|
||||
style 0-28 dim
|
||||
16| "└ +2 -2 · 1 file "
|
||||
16| "└ +1 -1 · 1 file "
|
||||
style 0-15 dim
|
||||
17| <blank>
|
||||
18| "● Tool / subagent"
|
||||
@@ -58,17 +58,27 @@ buffer
|
||||
style 0-99 dim
|
||||
30| "Loaded review instructions. "
|
||||
style 0-99 dim
|
||||
31| "Model wait 0.0s "
|
||||
31| <blank>
|
||||
32| "● Tool / large_edit"
|
||||
style 0-18 fg=green
|
||||
33| "src/large.ts "
|
||||
style 0-11 bold
|
||||
34| "[exact line diff omitted: >2 changed lines] "
|
||||
style 0-42 dim
|
||||
35| "… +6 lines (Ctrl+O to expand) "
|
||||
style 0-28 dim
|
||||
36| "└ +3 -3 · 1 file · approximate "
|
||||
style 0-29 dim
|
||||
37| "Model wait 0.0s "
|
||||
style 0-14 dim
|
||||
32| <blank>
|
||||
33| "/workspace/project (tui-staging) deepseek-v4-flash ↑0 ↓0 0% context"
|
||||
38| <blank>
|
||||
39| "/workspace/project (tui-staging) deepseek-v4-flash ↑0 ↓0 0% context"
|
||||
style 0-17 fg=bright-magenta bold
|
||||
style 18-31 dim
|
||||
style 34-50 dim
|
||||
style 53-57 dim
|
||||
style 60-69 dim
|
||||
34| " dsh > "
|
||||
40| " dsh > "
|
||||
style 1-3 fg=bright-magenta bold
|
||||
style 5-6 dim
|
||||
style 7-7 inverse
|
||||
35-39| <blank>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
terminal 100x40 buffer=normal length=43 base=3 viewport=3
|
||||
terminal 100x40 buffer=normal length=53 base=13 viewport=13
|
||||
lifecycle started=1 stopped=0 progress=inactive
|
||||
title "DSH snapshot"
|
||||
cursor hidden column=7 viewportRow=39 bufferRow=42
|
||||
cursor hidden column=7 viewportRow=39 bufferRow=52
|
||||
buffer
|
||||
0| " DEEPSEEK HARNESS"
|
||||
style 1-8 fg=bright-magenta bold
|
||||
@@ -37,54 +37,73 @@ buffer
|
||||
style 0-10 bold
|
||||
17| "- old line "
|
||||
style 0-9 fg=red
|
||||
18| "- keep "
|
||||
style 0-5 fg=red
|
||||
19| "+ new line "
|
||||
18| "+ new line "
|
||||
style 0-9 fg=green
|
||||
20| "+ keep "
|
||||
style 0-5 fg=green
|
||||
21| "└ +2 -2 · 1 file "
|
||||
19| " keep "
|
||||
style 0-5 dim
|
||||
20| "└ +1 -1 · 1 file "
|
||||
style 0-15 dim
|
||||
22| <blank>
|
||||
23| "● Tool / subagent"
|
||||
21| <blank>
|
||||
22| "● Tool / subagent"
|
||||
style 0-16 fg=green
|
||||
24| "Delegate renderer audit "
|
||||
23| "Delegate renderer audit "
|
||||
style 0-99 dim
|
||||
25| "The renderer has explicit lifecycle ownership. "
|
||||
24| "The renderer has explicit lifecycle ownership. "
|
||||
style 0-99 dim
|
||||
26| <blank>
|
||||
27| "● Tool / task_output"
|
||||
25| <blank>
|
||||
26| "● Tool / task_output"
|
||||
style 0-19 fg=green
|
||||
28| "Read output from background task subagent-7 "
|
||||
27| "Read output from background task subagent-7 "
|
||||
style 0-99 dim
|
||||
29| " "
|
||||
30| "console "
|
||||
28| " "
|
||||
29| "console "
|
||||
style 0-6 dim
|
||||
31| " started background task bash-5 "
|
||||
30| " started background task bash-5 "
|
||||
style 0-1 dim
|
||||
style 2-31 fg=cyan dim
|
||||
style 32-99 dim
|
||||
32| " "
|
||||
33| <blank>
|
||||
34| "● Tool / skill"
|
||||
31| " "
|
||||
32| <blank>
|
||||
33| "● Tool / skill"
|
||||
style 0-13 fg=green
|
||||
35| "Load skill dsh-code-review "
|
||||
34| "Load skill dsh-code-review "
|
||||
style 0-99 dim
|
||||
36| "Loaded review instructions. "
|
||||
35| "Loaded review instructions. "
|
||||
style 0-99 dim
|
||||
37| "Model wait 0.0s "
|
||||
36| <blank>
|
||||
37| "● Tool / large_edit"
|
||||
style 0-18 fg=green
|
||||
38| "src/large.ts "
|
||||
style 0-11 bold
|
||||
39| "[exact line diff omitted: >2 changed lines] "
|
||||
style 0-42 dim
|
||||
40| "- old one "
|
||||
style 0-8 fg=red
|
||||
41| "- old two "
|
||||
style 0-8 fg=red
|
||||
42| "- old three "
|
||||
style 0-10 fg=red
|
||||
43| "+ new one "
|
||||
style 0-8 fg=green
|
||||
44| "+ new two "
|
||||
style 0-8 fg=green
|
||||
45| "+ new three "
|
||||
style 0-10 fg=green
|
||||
46| "└ +3 -3 · 1 file · approximate "
|
||||
style 0-29 dim
|
||||
47| "Model wait 0.0s "
|
||||
style 0-14 dim
|
||||
38| <blank>
|
||||
39| "Tool and context cards expanded. "
|
||||
48| <blank>
|
||||
49| "Tool and context cards expanded. "
|
||||
style 0-31 dim
|
||||
40| <blank>
|
||||
41| "/workspace/project (tui-staging) deepseek-v4-flash ↑0 ↓0 0% context"
|
||||
50| <blank>
|
||||
51| "/workspace/project (tui-staging) deepseek-v4-flash ↑0 ↓0 0% context"
|
||||
style 0-17 fg=bright-magenta bold
|
||||
style 18-31 dim
|
||||
style 34-50 dim
|
||||
style 53-57 dim
|
||||
style 60-69 dim
|
||||
42| " dsh > "
|
||||
52| " dsh > "
|
||||
style 1-3 fg=bright-magenta bold
|
||||
style 5-6 dim
|
||||
style 7-7 inverse
|
||||
|
||||
@@ -272,13 +272,32 @@ const ADVANCED_CARD_TOOLS: Record<string, ToolDefinition> = {
|
||||
edit: visualTool(
|
||||
'edit',
|
||||
() => ({ card: 'diff', title: 'Edit src/view.ts', diffs: [{ path: 'src/view.ts', oldText: 'old line', newText: 'new line' }] }),
|
||||
// The real edit/write tools produce exactly one diff whose path the title
|
||||
// already names, so the card omits the redundant per-file header.
|
||||
// The fixed tool header never names a path, so the hunk retains its path.
|
||||
(): ToolResultView => ({
|
||||
card: 'diff',
|
||||
diffs: [{ path: 'src/view.ts', oldText: 'old line\nkeep', newText: 'new line\nkeep' }],
|
||||
}),
|
||||
),
|
||||
large_edit: visualTool(
|
||||
'large_edit',
|
||||
() => ({
|
||||
card: 'diff',
|
||||
title: 'Edit src/large.ts',
|
||||
diffs: [{
|
||||
path: 'src/large.ts',
|
||||
oldText: 'old one\nold two\nold three',
|
||||
newText: 'new one\nnew two\nnew three',
|
||||
}],
|
||||
}),
|
||||
(): ToolResultView => ({
|
||||
card: 'diff',
|
||||
diffs: [{
|
||||
path: 'src/large.ts',
|
||||
oldText: 'old one\nold two\nold three',
|
||||
newText: 'new one\nnew two\nnew three',
|
||||
}],
|
||||
}),
|
||||
),
|
||||
subagent: visualTool('subagent', args => ({
|
||||
card: 'generic',
|
||||
title: 'Delegate renderer audit',
|
||||
@@ -588,7 +607,7 @@ describe('TUI terminal-state snapshots', () => {
|
||||
it('pins terminal, diff, subagent, task, skill, collapsed, and expanded cards', async () => {
|
||||
const harness = await setupSnapshot({
|
||||
tools: ADVANCED_CARD_TOOLS,
|
||||
config: { maxToolOutputLines: 3 },
|
||||
config: { maxToolOutputLines: 3, maxDiffEditLength: 2 },
|
||||
}, { columns: 100, rows: 40 })
|
||||
const calls = [
|
||||
{ id: 'advanced-1', name: 'bash', arguments: { command: 'pnpm run test:coverage' } },
|
||||
@@ -596,6 +615,7 @@ describe('TUI terminal-state snapshots', () => {
|
||||
{ id: 'advanced-3', name: 'subagent', arguments: { prompt: 'Review renderer ownership and report only gaps.' } },
|
||||
{ id: 'advanced-4', name: 'task_output', arguments: { task_id: 'subagent-7', wait: true } },
|
||||
{ id: 'advanced-5', name: 'skill', arguments: { name: 'dsh-code-review' } },
|
||||
{ id: 'advanced-6', name: 'large_edit', arguments: { file_path: 'src/large.ts' } },
|
||||
]
|
||||
await renderAfter(harness, () => {
|
||||
appendToolCalls(harness.session, calls)
|
||||
@@ -604,6 +624,7 @@ describe('TUI terminal-state snapshots', () => {
|
||||
appendToolResult(harness.session, 'advanced-3', [{ type: 'text', text: 'The renderer has explicit lifecycle ownership.' }])
|
||||
appendToolResult(harness.session, 'advanced-4', [{ type: 'text', text: 'audit complete\n[status: completed]' }])
|
||||
appendToolResult(harness.session, 'advanced-5', [{ type: 'text', text: 'Loaded review instructions.' }])
|
||||
appendToolResult(harness.session, 'advanced-6', [{ type: 'text', text: 'large edit complete' }])
|
||||
})
|
||||
await checkpoint('advanced-cards-collapsed', harness.terminal, { includeScrollback: true })
|
||||
|
||||
|
||||
@@ -185,6 +185,7 @@ describe('TUI config', () => {
|
||||
expect(resolveTuiConfig(undefined)).toEqual({
|
||||
showReasoning: true,
|
||||
maxToolOutputLines: 6,
|
||||
maxDiffEditLength: 1000,
|
||||
maxQuestionOptions: 8,
|
||||
maxModelOptions: 8,
|
||||
maxResumeOptions: 8,
|
||||
@@ -210,6 +211,7 @@ describe('TUI config', () => {
|
||||
expect(resolveTuiConfig({
|
||||
showReasoning: false,
|
||||
maxToolOutputLines: 2,
|
||||
maxDiffEditLength: 12,
|
||||
maxQuestionOptions: 3,
|
||||
maxModelOptions: 4,
|
||||
maxResumeOptions: 5,
|
||||
@@ -227,6 +229,7 @@ describe('TUI config', () => {
|
||||
})).toEqual({
|
||||
showReasoning: false,
|
||||
maxToolOutputLines: 2,
|
||||
maxDiffEditLength: 12,
|
||||
maxQuestionOptions: 3,
|
||||
maxModelOptions: 4,
|
||||
maxResumeOptions: 5,
|
||||
@@ -4675,7 +4678,11 @@ describe('tool cards and surface replay', () => {
|
||||
presentCall: () => ({
|
||||
card: 'diff',
|
||||
title: 'Edit src/only.ts',
|
||||
diffs: [{ path: 'src/only.ts', oldText: 'old', newText: 'new' }],
|
||||
diffs: [{
|
||||
path: 'src/only.ts',
|
||||
oldText: 'my: my-MM\nne: ne-NP\nnl: nl-NL\nnb: no-NO\npa: pa-Guru-IN\npl: pl-PL\npt_pt: pt-PT',
|
||||
newText: 'my: my-MM\nne: ne-NP\nnl: nl-NL\nnb: nb-NO\npa: pa-Guru-IN\npl: pl-PL\npt_pt: pt-PT',
|
||||
}],
|
||||
}),
|
||||
},
|
||||
scatteredDiff: {
|
||||
@@ -5029,7 +5036,7 @@ describe('tool cards and surface replay', () => {
|
||||
})
|
||||
|
||||
it('names a single-file diff in the body once, under a fixed Tool header', async () => {
|
||||
const result = await setup({ tools })
|
||||
const result = await setup({ tools, config: { maxToolOutputLines: 20 } })
|
||||
appendUser(result.session, 'edit one file')
|
||||
appendAssistant(result.session, [
|
||||
{ type: 'text', text: 'Editing' },
|
||||
@@ -5045,9 +5052,123 @@ describe('tool cards and surface replay', () => {
|
||||
expect(output).toContain('Tool / singleDiff')
|
||||
expect(output).not.toContain('Edit src/only.ts')
|
||||
expect(output.split('src/only.ts').length - 1).toBe(1)
|
||||
expect(output).toContain('- old')
|
||||
expect(output).toContain('+ new')
|
||||
expect(output).toContain('· 1 file')
|
||||
expect(output).toContain(' my: my-MM')
|
||||
expect(output).not.toContain('- my: my-MM')
|
||||
expect(output).not.toContain('+ my: my-MM')
|
||||
expect(output).toContain('- nb: no-NO')
|
||||
expect(output).toContain('+ nb: nb-NO')
|
||||
expect(output).toContain('└ +1 -1 · 1 file')
|
||||
await dispose(result)
|
||||
})
|
||||
|
||||
it('renders an empty create without a synthetic added row', async () => {
|
||||
const emptyCreate: Record<string, ToolDefinition> = {
|
||||
emptyCreate: {
|
||||
name: 'emptyCreate',
|
||||
description: '',
|
||||
parameters: {},
|
||||
output: UNUSED_TOOL_OUTPUT,
|
||||
execute: async () => [],
|
||||
presentCall: () => ({
|
||||
card: 'diff',
|
||||
title: 'Write empty.txt',
|
||||
diffs: [{ path: 'empty.txt', oldText: null, newText: '' }],
|
||||
}),
|
||||
},
|
||||
}
|
||||
const result = await setup({
|
||||
tools: emptyCreate,
|
||||
config: { maxToolOutputLines: 20, theme: { color: false } },
|
||||
})
|
||||
appendAssistant(result.session, [
|
||||
{ type: 'tool-call', id: 'empty-create' as never, name: 'emptyCreate', arguments: '{}' },
|
||||
])
|
||||
result.session.append('tool/call', {
|
||||
turn: 1,
|
||||
step: 1,
|
||||
callId: 'empty-create' as never,
|
||||
name: 'emptyCreate',
|
||||
arguments: '{}',
|
||||
})
|
||||
await tick()
|
||||
const rows = result.terminal.output.split('\n').map(row => row.trim())
|
||||
expect(result.terminal.output).toContain('empty.txt')
|
||||
expect(result.terminal.output).toContain('└ +0 -0 · 1 file')
|
||||
expect(rows).not.toContain('+')
|
||||
await dispose(result)
|
||||
})
|
||||
|
||||
it('bounds and caches exact diff comparison before whole-side fallback', async () => {
|
||||
let oldTextReads = 0
|
||||
let newText = 'new one\nnew two'
|
||||
const boundedDiff = {
|
||||
path: 'bounded.txt',
|
||||
get oldText() {
|
||||
oldTextReads += 1
|
||||
return 'old one\nold two'
|
||||
},
|
||||
get newText() { return newText },
|
||||
}
|
||||
const boundedView = {
|
||||
card: 'diff' as const,
|
||||
title: 'Edit bounded.txt',
|
||||
diffs: [boundedDiff],
|
||||
}
|
||||
const bounded: Record<string, ToolDefinition> = {
|
||||
bounded: {
|
||||
name: 'bounded',
|
||||
description: '',
|
||||
parameters: {},
|
||||
output: UNUSED_TOOL_OUTPUT,
|
||||
execute: async () => [],
|
||||
presentCall: () => boundedView,
|
||||
presentResult: () => {
|
||||
newText = 'settled one\nsettled two'
|
||||
return boundedView
|
||||
},
|
||||
},
|
||||
}
|
||||
const result = await setup({
|
||||
tools: bounded,
|
||||
config: {
|
||||
maxToolOutputLines: 20,
|
||||
maxDiffEditLength: 1,
|
||||
theme: { color: false },
|
||||
},
|
||||
})
|
||||
appendAssistant(result.session, [
|
||||
{ type: 'tool-call', id: 'bounded-diff' as never, name: 'bounded', arguments: '{}' },
|
||||
])
|
||||
result.session.append('tool/call', {
|
||||
turn: 1,
|
||||
step: 1,
|
||||
callId: 'bounded-diff' as never,
|
||||
name: 'bounded',
|
||||
arguments: '{}',
|
||||
})
|
||||
await tick()
|
||||
expect(result.terminal.output).toContain('[exact line diff omitted: >1 changed lines]')
|
||||
expect(result.terminal.output).toContain('- old one')
|
||||
expect(result.terminal.output).toContain('+ new one')
|
||||
expect(result.terminal.output).toContain('└ +2 -2 · 1 file · approximate')
|
||||
const readsAfterFirstRender = oldTextReads
|
||||
expect(readsAfterFirstRender).toBeGreaterThan(0)
|
||||
result.session.append('tool/result', {
|
||||
turn: 1,
|
||||
step: 1,
|
||||
message: createToolResultMessage({
|
||||
callId: 'bounded-diff' as never,
|
||||
content: [{ type: 'text', text: 'done' }],
|
||||
isError: false,
|
||||
}),
|
||||
}, { surfaceOp: 'append' })
|
||||
await tick()
|
||||
expect(result.terminal.output).toContain('+ settled one')
|
||||
expect(oldTextReads).toBeGreaterThan(readsAfterFirstRender)
|
||||
const readsAfterResult = oldTextReads
|
||||
result.terminal.resize(87)
|
||||
await tick()
|
||||
expect(oldTextReads).toBe(readsAfterResult)
|
||||
await dispose(result)
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user