68 lines
2.8 KiB
TypeScript
68 lines
2.8 KiB
TypeScript
/**
|
|
* Pure derivation of the compact LIVE line shown on a running subagent card:
|
|
* the last text output and the last tool call of the child's history tail
|
|
* (`subagent.history` events, oldest → newest). Renders nothing itself — the
|
|
* SubagentLiveLine component turns this into the card's status lines.
|
|
* Kept framework-free so the parser is unit-testable in the node environment.
|
|
*/
|
|
import type { SidebarHistoryEntry } from '../context-types.ts'
|
|
|
|
/**
|
|
* Extract the concatenated plain text of a content-block list (the durable
|
|
* `ContentBlock[]` shape, structurally: blocks with `type: 'text'` carry
|
|
* `text`; anything else — tool_use, image, … — contributes nothing).
|
|
* @param content - the raw `content` field of a message event.
|
|
* @returns the joined text, or undefined when the message carries no text.
|
|
*/
|
|
export function contentText(content: unknown): string | undefined {
|
|
if (!Array.isArray(content)) return undefined
|
|
const parts: string[] = []
|
|
for (const block of content) {
|
|
if (block === null || typeof block !== 'object') continue
|
|
const candidate = block as { type?: unknown; text?: unknown }
|
|
if (candidate.type === 'text' && typeof candidate.text === 'string') {
|
|
parts.push(candidate.text)
|
|
}
|
|
}
|
|
return parts.length > 0 ? parts.join('\n') : undefined
|
|
}
|
|
|
|
/** The live status of one subagent card (both fields optional). */
|
|
export interface LastActivity {
|
|
/** The latest assembled assistant text output in the tail. */
|
|
text?: string
|
|
/** The latest tool call in the tail. */
|
|
tool?: { name: string; args: string }
|
|
}
|
|
|
|
/**
|
|
* Fold a history tail into the last text output + last tool call (each is
|
|
* the LAST occurrence in event order). Lifecycle events and raw
|
|
* `assistant/chunk` rows are ignored — the card shows what the subagent is
|
|
* doing right now, not its plumbing.
|
|
* @param entries - the tail page from `subagent.history` (oldest → newest).
|
|
* @returns the last text and/or tool call; an empty object when the tail has neither.
|
|
*/
|
|
export function lastActivity(entries: SidebarHistoryEntry[]): LastActivity {
|
|
let text: string | undefined
|
|
let tool: { name: string; args: string } | undefined
|
|
for (const entry of entries) {
|
|
const { type, data } = entry.event
|
|
if (type === 'assistant/message') {
|
|
const message = data.message as { content?: unknown } | undefined
|
|
const extracted = contentText(message?.content)
|
|
if (extracted !== undefined) text = extracted
|
|
} else if (type === 'tool/call') {
|
|
tool = {
|
|
name: typeof data.name === 'string' ? data.name : 'tool',
|
|
args: typeof data.arguments === 'string' ? data.arguments : '',
|
|
}
|
|
}
|
|
}
|
|
if (text === undefined && tool === undefined) return {}
|
|
return {
|
|
...(text === undefined ? {} : { text }),
|
|
...(tool === undefined ? {} : { tool }),
|
|
}
|
|
}
|