Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dsh-cctui",
"version": "0.3.8",
"version": "0.3.9",
"description": "Claude-Code-style terminal UI for deepseek-harness, ported from clawcodex ui-tui",
"license": "MIT",
"repository": {
Expand Down
19 changes: 19 additions & 0 deletions scripts/tool-gallery.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,27 @@
'PROBE subagent {"description": "Audit the tests", "prompt": "Summarise the test layout and stop."}'
),
"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"}',
"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 {}',
# plan mode has to be on before the tool will run; pair with --live to catch
# the review prompt while it is up
"exit_plan_mode": [
'/plan',
'PROBE exit_plan_mode {"plan": "# Close the tool-result gaps\\n\\n- read cards\\n- search cards"}'
],
"job_list": 'PROBE job_list {}',
"list_agents": 'PROBE list_agents {}',
# a RUN of calls, which is the only way the collapsed brief reads right
"mixed_run": (
'PROBE read {"file_path": "src/domain/usage.ts"}'
' ;; PROBE read {"file_path": "src/domain/roles.ts"}'
' ;; PROBE glob {"pattern": "src/domain/*.ts"}'
' ;; PROBE bash {"command": "ls -1 src | head -3", "description": "peek"}'
' ;; PROBE bash {"command": "echo hi", "description": "echo"}'
),
"todo": (
'PROBE todo_write {"todos": [{"content": "first task", "status": "in_progress"},'
' {"content": "second task", "status": "pending"}]}'
Expand Down
16 changes: 12 additions & 4 deletions src/__tests__/toolArgs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,18 @@ describe('toolArgsPreview', () => {
expect(toolArgsPreview(raw)).toBe('Review the diff')
})

it('falls back to the unfiltered projection when every argument is bulk', () => {
expect(toolArgsPreview('{"todos":[{"content":"a","status":"pending"}]}')).toBe(
'todos: [{"content":"a","status":"pending"}]'
)
it('does not restate a plan the review box is already showing', () => {
const raw = '{"plan":"# Close the gaps\\n\\n- read cards\\n- search cards"}'

// the plan renders in full right below the row; squeezing it into 64
// columns of parens shows it twice, once badly
expect(toolArgsPreview(raw)).toBe('')
})

it('renders nothing when every argument is bulk, leaving a bare label', () => {
// `⏺ Todo Write` with the checklist below it, the way upstream renders it —
// formatToolCall drops the parens entirely for an empty preview
expect(toolArgsPreview('{"todos":[{"content":"a","status":"pending"}]}')).toBe('')
})

it('renders non-string values as JSON', () => {
Expand Down
16 changes: 9 additions & 7 deletions src/domain/toolArgs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@

/**
* Arguments that never identify a call. Bulk payloads (a file body, an edit's
* before/after, a checklist, a delegation's brief) and policy/qualifier fields
* before/after, a checklist, a delegation's brief, a plan under review) and
* policy/qualifier fields
* (why a command may escalate, which window of a file to read) both make the
* row longer and less distinguishable, which is the opposite of the point.
*/
Expand All @@ -41,6 +42,7 @@ const NOISE_KEYS: ReadonlySet<string> = new Set([
'new_string',
'old_str',
'old_string',
'plan',
'prompt',
'todos',
// policy / qualifiers
Expand Down Expand Up @@ -117,10 +119,10 @@ export const toolArgsPreview = (raw: string): string => {
return project(salient)
}

const labels = entries.filter(([key]) => LABEL_KEYS.has(key))

// A call whose every argument is bulk (a bare `todo_write`) still deserves a
// row that says something; falling back to the unfiltered projection beats
// rendering `⏺ Todo Write()`.
return project(labels.length ? labels : entries)
// A call whose every argument is bulk or policy has nothing to put in the
// parens, and that is the right answer, not a failure: `formatToolCall`
// renders a preview-less call as a bare label, which is exactly what the
// original shows for `⏺ TodoWrite` and `⏺ ExitPlanMode` — both of which draw
// their real content (the checklist, the plan under review) below the row.
return project(entries.filter(([key]) => LABEL_KEYS.has(key)))
}
26 changes: 18 additions & 8 deletions test/e2e/probe-llm.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,27 @@ class ProbeAdapter extends LlmAdapter {
.slice(lastUserIndex(msgs) + 1)
.some(m => Array.isArray(m.content) && m.content.some(b => b.type === 'tool-result'))

const m = /^PROBE\s+(\S+)\s+([\s\S]*)$/.exec(user.trim())
// `PROBE <tool> <json>`, or several separated by ` ;; ` to exercise the
// collapsed brief, which only reads right across a RUN of calls.
const calls = user
.trim()
.split(' ;; ')
.map(part => /^PROBE\s+(\S+)\s*([\s\S]*)$/.exec(part.trim()))
.filter(Boolean)

if (calls.length && !hasToolResult) {
for (const [index, m] of calls.entries()) {
const name = m[1]
const args = m[2].trim() || '{}'
const id = `probe-${++seq}`
yield { blockType: 'tool-call', index, type: 'block-start' }
yield { argumentsDelta: args, id, name, type: 'tool-call-delta', index }
yield { block: { arguments: args, id, name, type: 'tool-call' }, index, type: 'block-end' }
}

if (m && !hasToolResult) {
const name = m[1]
const args = m[2].trim() || '{}'
const id = `probe-${++seq}`
yield { blockType: 'tool-call', index: 0, type: 'block-start' }
yield { argumentsDelta: args, id, name, type: 'tool-call-delta', index: 0 }
yield { block: { arguments: args, id, name, type: 'tool-call' }, index: 0, type: 'block-end' }
yield { type: 'usage', usage: { inputTokens: 10, outputTokens: 5 } }
yield { reason: { kind: 'tool-calls' }, type: 'finish' }

return
}

Expand Down
Loading