fix(tui): merge tools into contextual shelves

This commit is contained in:
Brooklyn Nicholson 2026-04-26 16:00:38 -05:00
parent 4d3e3a738d
commit 4943ea2a7c
2 changed files with 25 additions and 3 deletions

View file

@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest' import { describe, expect, it } from 'vitest'
import { appendToolShelfMessage, isTodoDone } from './liveProgress.js' import { appendToolShelfMessage, canHoldToolShelf, isTodoDone, mergeToolShelfInto } from './liveProgress.js'
describe('isTodoDone', () => { describe('isTodoDone', () => {
it('only treats non-empty all-completed/cancelled lists as done', () => { it('only treats non-empty all-completed/cancelled lists as done', () => {
@ -16,6 +16,23 @@ describe('isTodoDone', () => {
}) })
}) })
describe('tool shelf helpers', () => {
it('recognizes contextual thinking shelves as holders', () => {
expect(canHoldToolShelf({ kind: 'trail', role: 'system', text: '', thinking: 'plan' })).toBe(true)
expect(canHoldToolShelf({ kind: 'trail', role: 'system', text: '', tools: ['one ✓'] })).toBe(true)
expect(canHoldToolShelf({ role: 'assistant', text: 'done' })).toBe(false)
})
it('merges source rows into an existing shelf', () => {
expect(
mergeToolShelfInto(
{ kind: 'trail', role: 'system', text: '', thinking: 'plan', tools: ['one ✓'] },
{ kind: 'trail', role: 'system', text: '', tools: ['two ✓'] }
)
).toEqual({ kind: 'trail', role: 'system', text: '', thinking: 'plan', tools: ['one ✓', 'two ✓'] })
})
})
describe('appendToolShelfMessage', () => { describe('appendToolShelfMessage', () => {
it('merges adjacent tool shelves into one contextual shelf', () => { it('merges adjacent tool shelves into one contextual shelf', () => {
const merged = appendToolShelfMessage([{ kind: 'trail', role: 'system', text: '', tools: ['one ✓'] }], { const merged = appendToolShelfMessage([{ kind: 'trail', role: 'system', text: '', tools: ['one ✓'] }], {

View file

@ -6,9 +6,14 @@ export const isTodoDone = (todos: readonly TodoItem[]) =>
export const isToolShelfMessage = (msg: Msg | undefined) => export const isToolShelfMessage = (msg: Msg | undefined) =>
Boolean(msg?.kind === 'trail' && !msg.text && !msg.thinking?.trim() && msg.tools?.length) Boolean(msg?.kind === 'trail' && !msg.text && !msg.thinking?.trim() && msg.tools?.length)
const canHoldToolShelf = (msg: Msg | undefined) => export const canHoldToolShelf = (msg: Msg | undefined) =>
Boolean(msg?.kind === 'trail' && !msg.text && (msg.thinking?.trim() || msg.tools?.length)) Boolean(msg?.kind === 'trail' && !msg.text && (msg.thinking?.trim() || msg.tools?.length))
export const mergeToolShelfInto = (target: Msg, source: Msg): Msg => ({
...target,
tools: [...(target.tools ?? []), ...(source.tools ?? [])]
})
export const appendToolShelfMessage = (prev: readonly Msg[], msg: Msg): Msg[] => { export const appendToolShelfMessage = (prev: readonly Msg[], msg: Msg): Msg[] => {
if (!isToolShelfMessage(msg)) { if (!isToolShelfMessage(msg)) {
return [...prev, msg] return [...prev, msg]
@ -20,7 +25,7 @@ export const appendToolShelfMessage = (prev: readonly Msg[], msg: Msg): Msg[] =>
if (canHoldToolShelf(candidate)) { if (canHoldToolShelf(candidate)) {
const next = [...prev] const next = [...prev]
next[index] = { ...candidate!, tools: [...(candidate!.tools ?? []), ...(msg.tools ?? [])] } next[index] = mergeToolShelfInto(candidate!, msg)
return next return next
} }