Files
dsh-context/tests/host/fleet.spec.ts
T
bowenliang123 f0ee3195ac feat: rebuild the Fleet tab as the agent fleet cockpit
Split the Agent network card into graph + cursor-following inspector
(delegation prompt, latest reply, descriptor, owned tasks, comms), add
the members board and the status-grouped task board off the agentTeam
projection with inline dependency chains, a filterable/sortable roster,
and an on-demand comms timeline over relay/mailbox/settled records.

Data rides a new /api/dsh-context/fleet route (one observeSession read
per session through the shared cold-read gate); hover, pin, owner
filter, and task focus are lifted to the view so every panel steers the
same inspector.
2026-10-11 07:52:58 +08:00

351 lines
17 KiB
TypeScript

// The fleet route (src/host/fleet.ts): the per-session agent color extraction (descriptor,
// delegation prompt, inter-agent traffic) and the route's gating/transport behavior.
import assert from 'node:assert/strict'
import { describe, test } from 'vitest'
import type { Context } from '@deepseek-ai/cordis'
import { FLEET_ROUTE, fleetDetailOfEvents, watchFleetChannel } from '../../src/host/fleet'
import type { ColdReadGate } from '../../src/host/coldRead'
type RouteFetch = (request: Request) => Promise<Response>
interface CtxSpec {
connection?: unknown
sessionQuery?: unknown
}
/** A minimal host ctx double with cordis inject semantics (the detail route spec's own shape):
* the callback fires once its dependency list completes, and disposers are collected. */
function ctxOf(spec: CtxSpec): { ctx: Context; captured: { path?: string; fetch?: RouteFetch }; disposers: (() => void)[] } {
const captured: { path?: string; fetch?: RouteFetch } = {}
const disposers: (() => void)[] = []
const services = new Map<string, unknown>()
if ('connection' in spec) services.set('connection', spec.connection)
if ('sessionQuery' in spec) services.set('sessionQuery', spec.sessionQuery)
const ctx = {
get: (name: string) => services.get(name),
effect(fn: () => unknown, _label?: string) {
const d = fn()
if (typeof d === 'function') disposers.push(d as () => void)
return () => {}
},
inject(deps: string[], cb: (c: unknown) => unknown) {
if (!deps.every(d => services.has(d))) return
const d = cb(ctx)
if (typeof d === 'function') disposers.push(d as () => void)
},
}
if (spec.connection !== undefined && spec.connection !== null) {
const conn = spec.connection as { fetch?: { register?: unknown } }
if (typeof conn.fetch?.register === 'function') {
conn.fetch.register = (route: { path: string; fetch: RouteFetch }) => {
captured.path = route.path
captured.fetch = route.fetch
return () => {}
}
}
}
return { ctx: ctx as unknown as Context, captured, disposers }
}
const OPEN_GATE: ColdReadGate = { admit: read => read() }
/** A gate that refuses every read — the heap-pressure path. */
const SKIP_GATE: ColdReadGate = { admit: () => Promise.resolve(undefined) }
function armed(spec: CtxSpec = {}): { captured: { path?: string; fetch?: RouteFetch } } {
const { ctx, captured } = ctxOf({ connection: { fetch: { register: () => () => {} } }, ...spec })
watchFleetChannel(ctx, OPEN_GATE)
return { captured }
}
async function call(captured: { fetch?: RouteFetch }, body: unknown): Promise<Record<string, unknown>> {
const request = new Request(`http://dsh.test${FLEET_ROUTE}`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
...(body === undefined ? {} : { body: JSON.stringify(body) }),
})
const response = await captured.fetch!(request)
return await response.json() as Record<string, unknown>
}
function observing(events: unknown): { observeSession(id: string, options: unknown): Promise<unknown> } {
let disposed = false
return {
observeSession: () => Promise.resolve({
events,
get disposed() { return disposed },
[Symbol.dispose]() { disposed = true },
}),
}
}
describe('watchFleetChannel gating', () => {
test('no route without the connection service or its fetch registry', () => {
assert.equal(ctxOf({}).captured.path, undefined)
assert.equal(ctxOf({ connection: {} }).captured.path, undefined)
assert.equal(ctxOf({ connection: { fetch: {} } }).captured.path, undefined)
const { captured } = armed()
assert.equal(captured.path, FLEET_ROUTE)
})
test('a rejecting registry keeps the route absent without throwing', () => {
const ctx = {
get: (name: string) => name === 'connection'
? {
fetch: {
register: () => {
throw new Error('registration rejected')
},
},
}
: undefined,
inject(_deps: string[], cb: (c: unknown) => unknown) { cb(ctx) },
}
assert.doesNotThrow(() => { watchFleetChannel(ctx as unknown as Context, OPEN_GATE) })
})
test('bad bodies and missing session ids are typed failures', async () => {
const { captured } = armed()
const badJson = await captured.fetch!(new Request(`http://dsh.test${FLEET_ROUTE}`, { method: 'POST', body: '{' }))
assert.equal((await badJson.json() as { ok: boolean }).ok, false)
for (const body of [undefined, null, {}, { sessionId: 42 }, { sessionId: '' }]) {
const r = await call(captured, body)
assert.equal(r.ok, false, JSON.stringify(body))
}
})
test('no sessionQuery (or no observeSession verb) answers a typed null', async () => {
const { captured } = armed()
assert.deepEqual(await call(captured, { sessionId: 's1' }), { ok: true, value: null })
const withQuery = armed({ sessionQuery: {} })
assert.deepEqual(await call(withQuery.captured, { sessionId: 's1' }), { ok: true, value: null })
})
test('an observation without an events array answers a typed null', async () => {
const { captured } = armed({ sessionQuery: observing(null) })
assert.deepEqual(await call(captured, { sessionId: 's1' }), { ok: true, value: null })
})
test('a skipped cold read answers a typed null', async () => {
const { ctx, captured } = ctxOf({
connection: { fetch: { register: () => () => {} } },
sessionQuery: observing([]),
})
watchFleetChannel(ctx, SKIP_GATE)
assert.deepEqual(await call(captured, { sessionId: 's1' }), { ok: true, value: null })
})
test('events fold into the detail and the observation disposes', async () => {
const query = observing([
{ type: 'user/message', seq: 1, time: 100, data: { content: [{ type: 'text', text: 'do the thing' }] } },
])
const { captured } = armed({ sessionQuery: query })
const r = await call(captured, { sessionId: 's1' })
assert.equal(r.ok, true)
const value = r.value as { initialPrompt: string }
assert.equal(value.initialPrompt, 'do the thing')
})
test('an observation without a dispose method still serves', async () => {
const { captured } = armed({
sessionQuery: {
observeSession: () => Promise.resolve({
events: [{ type: 'user/message', seq: 1, time: 1, data: { content: [{ type: 'text', text: 'plain' }] } }],
}),
},
})
const r = await call(captured, { sessionId: 's1' })
assert.equal((r.value as { initialPrompt: string }).initialPrompt, 'plain')
})
test('a throwing observation surfaces as an internal failure', async () => {
const { captured } = armed({
sessionQuery: { observeSession: () => Promise.reject(new Error('log unreadable')) },
})
const r = await call(captured, { sessionId: 's1' })
assert.equal(r.ok, false)
assert.equal((r.error as { message: string }).message, 'log unreadable')
const nonError = armed({
sessionQuery: { observeSession: () => Promise.reject('nope') },
})
const r2 = await call(nonError.captured, { sessionId: 's1' })
assert.equal((r2.error as { message: string }).message, 'nope')
})
})
describe('fleetDetailOfEvents — the descriptor', () => {
test('last descriptor wins (the fork-seed replay order), optional fields ride along', () => {
const detail = fleetDetailOfEvents([
{ type: 'subagent/descriptor', seq: 1, time: 1, data: { version: 3, mode: 'one-shot', provider: 'spawn' } },
{
type: 'subagent/descriptor',
seq: 2,
time: 2,
data: { version: 3, mode: 'continuable', provider: 'fork', label: 'worker', agentProvider: 'deepseek', agentModel: 'v4', persona: 'reviewer' },
},
])
assert.deepEqual(detail.descriptor, {
mode: 'continuable', provider: 'fork', label: 'worker', agentProvider: 'deepseek', agentModel: 'v4', persona: 'reviewer',
})
})
test('malformed descriptors drop alone', () => {
const detail = fleetDetailOfEvents([
{ type: 'subagent/descriptor', seq: 1, time: 1, data: { mode: 'mystery' } },
{ type: 'subagent/descriptor', seq: 2, time: 2, data: { mode: 'one-shot', label: '', provider: 7 } },
{ type: 'subagent/descriptor', seq: 3, time: 3, data: null },
{ type: 'subagent/descriptor', seq: 4, time: 4 },
null,
42,
{ seq: 5, time: 5, data: {} },
{ type: 9, seq: 6, data: {} },
// A real but irrelevant event type folds to nothing.
{ type: 'assistant/message', seq: 7, time: 7, data: { message: { content: [{ type: 'text', text: 'hi' }] } } },
])
assert.deepEqual(detail.descriptor, { mode: 'one-shot' })
assert.deepEqual(detail.messages, [])
assert.equal(detail.truncated, false)
})
})
describe('fleetDetailOfEvents — the delegation prompt', () => {
test('the first plain user message is the prompt; injections and agent traffic are not', () => {
const detail = fleetDetailOfEvents([
{ type: 'user/message', seq: 1, time: 1, data: { source: { kind: 'plugin', form: 'notice' }, content: [{ type: 'text', text: 'injected' }] } },
{ type: 'user/message', seq: 2, time: 2, data: { source: { kind: 'user' }, content: [{ type: 'text', text: '' }] } },
{ type: 'user/message', seq: 3, time: 3, data: { source: { kind: 'user' }, content: [{ type: 'text', text: 'build it' }] } },
{ type: 'user/message', seq: 4, time: 4, data: { content: [{ type: 'text', text: 'second prompt never wins' }] } },
])
assert.equal(detail.initialPrompt, 'build it')
})
test('the teammate framing reminder strips off the excerpt', () => {
const detail = fleetDetailOfEvents([
{
type: 'user/message',
seq: 1,
time: 1,
data: { content: [{ type: 'text', text: '<system-reminder>\nYou are teammate "reviewer".\n</system-reminder>\n\nreview the diff' }] },
},
])
assert.equal(detail.initialPrompt, 'review the diff')
})
test('a long prompt clips and flags the payload', () => {
const detail = fleetDetailOfEvents([
{ type: 'user/message', seq: 1, time: 1, data: { content: [{ type: 'text', text: 'x'.repeat(2000) }] } },
])
assert.equal(detail.initialPrompt?.length, 1601)
assert.equal(detail.truncated, true)
})
})
describe('fleetDetailOfEvents — roster facts and latest reply', () => {
test('team/member records fold last-wins by name; malformed rows drop alone', () => {
const detail = fleetDetailOfEvents([
{ type: 'team/member', seq: 1, time: 1, data: { teamId: 't', member: { id: 'a', name: 'worker', description: 'old duty', provider: 'spawn', context: 'fresh', phase: 'provisioning' } } },
{ type: 'team/member', seq: 2, time: 2, data: { teamId: 't', member: { id: 'a', name: 'worker', description: 'duty', provider: 'spawn', context: 'fresh', phase: 'active' } } },
{ type: 'team/member', seq: 3, time: 3, data: { member: null } },
{ type: 'team/member', seq: 4, time: 4, data: { member: { name: '' } } },
{ type: 'team/member', seq: 5, time: 5, data: { member: { name: 'bare', description: 7, provider: 9, context: 'sideways' } } },
])
assert.deepEqual(detail.roster, [
{ name: 'worker', description: 'duty', provider: 'spawn', context: 'fresh' },
{ name: 'bare', description: '' },
])
})
test('the latest assistant text is the reply; non-text and empty replies are skipped', () => {
const detail = fleetDetailOfEvents([
{ type: 'assistant/message', seq: 1, time: 10, data: { message: { content: [{ type: 'text', text: 'first' }] } } },
{ type: 'assistant/message', seq: 2, time: 20, data: { message: { content: [{ type: 'tool-call' }] } } },
{ type: 'assistant/message', seq: 3, time: 30, data: { message: { content: [{ type: 'text', text: 'latest report' }] } } },
{ type: 'assistant/message', seq: 4, time: 40, data: { message: { content: [{ type: 'text', text: ' ' }] } } },
{ type: 'assistant/message', seq: 5, time: 50, data: { message: { content: 'nope' } } },
{ type: 'assistant/message', seq: 6, time: 60, data: {} },
// The reply scan walks the log newest-LAST-in-array first: hostile blocks precede the winner here.
{ type: 'assistant/message', seq: 7, time: 70, data: { message: { content: [null, { type: 'text', text: 7 }] } } },
])
assert.deepEqual(detail.lastReply, { time: 30, text: 'latest report' })
assert.equal(fleetDetailOfEvents([]).lastReply, undefined)
// A reply with no readable instant zeros the clock.
const timeless = fleetDetailOfEvents([
{ type: 'assistant/message', seq: 1, data: { message: { content: [{ type: 'text', text: 'hi' }] } } },
])
assert.deepEqual(timeless.lastReply, { time: 0, text: 'hi' })
})
test('a long reply clips and flags the payload', () => {
const detail = fleetDetailOfEvents([
{ type: 'assistant/message', seq: 1, time: 1, data: { message: { content: [{ type: 'text', text: 'z'.repeat(400) }] } } },
])
assert.equal(detail.lastReply?.text.length, 301)
assert.equal(detail.truncated, true)
})
test('the mailbox transport preamble strips off the excerpt', () => {
const detail = fleetDetailOfEvents([
{ type: 'user/message', seq: 1, time: 1, data: { source: { kind: 'team-message', senderId: 'a', senderName: 'lead' }, content: [{ type: 'text', text: 'Team message team-message-abc from lead: 请继续 task-3' }] } },
])
assert.equal(detail.messages[0].text, '请继续 task-3')
})
})
describe('fleetDetailOfEvents — inter-agent messages', () => {
test('relay, mailbox, and settled rows each carry their attribution', () => {
const detail = fleetDetailOfEvents([
{ type: 'user/message', seq: 3, time: 30, data: { source: { kind: 'agent-message', form: 'relay', senderSessionId: 'lead-1' }, content: [{ type: 'text', text: 'how far along?' }] } },
{ type: 'user/message', seq: 1, time: 10, data: { source: { kind: 'team-message', senderId: 'w-1', senderName: 'worker' }, content: [{ type: 'text', text: 'done' }] } },
{ type: 'user/message', seq: 2, time: 20, data: { source: { kind: 'subagent-settled', form: 'notice', summary: 'child finished', senderSessionId: 'w-2' }, content: [{ type: 'text', text: 'unused' }] } },
])
assert.deepEqual(detail.messages, [
{ seq: 3, time: 30, kind: 'relay', from: 'lead-1', text: 'how far along?' },
{ seq: 2, time: 20, kind: 'settled', from: 'w-2', text: 'child finished' },
{ seq: 1, time: 10, kind: 'mailbox', from: 'w-1', fromName: 'worker', text: 'done' },
])
})
test('a settled notice without a summary falls back to its content; empty text drops the row', () => {
const detail = fleetDetailOfEvents([
{ type: 'user/message', seq: 1, time: 10, data: { source: { kind: 'subagent-settled' }, content: [{ type: 'text', text: ' ' }] } },
{ type: 'user/message', seq: 2, time: 20, data: { source: { kind: 'subagent-settled' }, content: [{ type: 'text', text: 'content account' }] } },
{ type: 'user/message', seq: 3, time: 30, data: { source: { kind: 'agent-message', senderSessionId: 'x' }, content: [{ type: 'text', text: '' }] } },
{ type: 'user/message', seq: 4, time: 40, data: { source: { kind: 'team-message', senderId: '', senderName: 9 }, content: [{ type: 'text', text: 'hi' }] } },
])
assert.deepEqual(detail.messages, [
{ seq: 4, time: 40, kind: 'mailbox', text: 'hi' },
{ seq: 2, time: 20, kind: 'settled', text: 'content account' },
])
})
test('long texts clip, and the list caps at the newest 40', () => {
const events = Array.from({ length: 45 }, (_, i) => ({
type: 'user/message',
seq: i + 1,
time: i + 1,
data: { source: { kind: 'agent-message', senderSessionId: 'x' }, content: [{ type: 'text', text: `m${i}` }] },
}))
events.push({ type: 'user/message', seq: 99, time: 99, data: { source: { kind: 'agent-message', senderSessionId: 'x' }, content: [{ type: 'text', text: 'y'.repeat(500) }] } })
const detail = fleetDetailOfEvents(events)
assert.equal(detail.messages.length, 40)
assert.equal(detail.messages[0].seq, 99)
assert.equal(detail.messages[0].text.length, 401)
assert.equal(detail.truncated, true)
})
test('non-finite seq/time zero out rather than reject the row', () => {
const detail = fleetDetailOfEvents([
{ type: 'user/message', seq: Number.NaN, time: Number.POSITIVE_INFINITY, data: { source: { kind: 'agent-message' }, content: [{ type: 'text', text: 'hi' }] } },
])
assert.deepEqual(detail.messages, [{ seq: 0, time: 0, kind: 'relay', text: 'hi' }])
})
test('a message with no content array reads as empty and drops', () => {
const detail = fleetDetailOfEvents([
{ type: 'user/message', seq: 1, time: 1, data: { source: { kind: 'agent-message' }, content: 'not-an-array' } },
{ type: 'user/message', seq: 2, time: 2, data: { source: { kind: 'agent-message' }, content: [{ type: 'image' }, { type: 'text', text: 7 }] } },
])
assert.deepEqual(detail.messages, [])
})
})