Files
dsh-context/tests/client/components/contextView.agentNetwork.spec.ts
T
bowenliang123 887615497d feat(client): show the agent cards' work state in the harness's own glyphs
The header popup marks each subagent with a rolling spinner while it
works and a green dot once it is done; the cards had no marker at all,
because their only candidate signal never existed. SessionSummary has
no completion field, so the row read was dead and always false — the
popup takes its answer from subagentTiming.lastTurnCompleted, and so
does a card now. A session that is not a subagent never carries that
projection, so its own folded steps against a stopped session stand in.

The glyph is the harness's own StateDot primitive at one fixed size, so
a card flipping between states never shifts its title.
2026-10-10 12:33:05 +08:00

44 lines
2.3 KiB
TypeScript

// ContextView → Agent network: the tab's OWN projections are the only source of the
// current agent's live stats. Its list row carries no subagent-timing projection (the
// harness writes that one on subagent sessions alone), so the card's duration and step
// count have to arrive from here.
import { createElement as h } from 'react'
import assert from 'node:assert/strict'
import { describe, test } from 'vitest'
import { TestClientCtx } from '../helpers/harness'
import { mount, query, text } from '../helpers/kit'
import { makeView, projectionsFor, richTimeline } from './contextViewHarness'
/** The outward sessions face the card reads: one row, the current session, no subagent.
* getSnapshot hands back the SAME object every call — the card reads it through
* useSyncExternalStore, which loops on a fresh snapshot per read. */
function selfOnly() {
const snapshot = { byId: { 'sv-self': { displayTitle: 'Main Agent', running: false, blank: false, updatedAt: 1 } } }
return { list: { getSnapshot: (): unknown => snapshot, subscribe: (): (() => void) => () => {} } }
}
const TIMING = { wallMs: 387_000, ttftMs: 0, genMs: 0, calls: 0, toolsMs: 0, toolCalls: 0, tools: {} }
describe('ContextView — the agent network card\'s own session', () => {
test("the tab's active time and step count fill the current card's footer", async () => {
const View = makeView(new TestClientCtx({ services: { sessions: selfOnly() } }))
const m = await mount(h(View, { sessionId: 'sv-self', useProjection: projectionsFor(richTimeline({ timing: TIMING })) }))
const self = query(m.container, '.lc-agent-card.lc-agent-self')
assert.equal(text(self).includes('Main Agent'), true)
// richTimeline carries three steps; the duration is the timing card's active time.
assert.equal(query(self, '.lc-agent-meta-text').textContent, '6m27s · 3 steps')
await m.unmount()
})
test('without timing totals the card drops the duration instead of claiming 0s', async () => {
const View = makeView(new TestClientCtx({ services: { sessions: selfOnly() } }))
const m = await mount(h(View, { sessionId: 'sv-self', useProjection: projectionsFor(richTimeline()) }))
const self = query(m.container, '.lc-agent-card.lc-agent-self')
assert.equal(query(self, '.lc-agent-meta-text').textContent, '3 steps')
await m.unmount()
})
})