Files
orca/src/shared/agent-child-row-model.ts
T
OrcaWinandOrcaWin 7d6603aa17 [comment audit] Complete repository-wide comment cleanup (#33249)
* [comment audit] Align legacy failure-read counts with cached display

* [comment audit] Condense shared provider lifecycle and browser recovery comments [skip ci]

* [comment audit] Condense shared generation, path and preview comments [skip ci]

* [comment audit] Condense host, file and Git contract rationale [skip ci]

* [comment audit] Tighten Git lifecycle and provider contract comments [skip ci]

* [comment audit] Clarify shared host authority and launch isolation [skip ci]

* [comment audit] simplify local access mobile and native chat contracts [skip ci]

* [comment audit] simplify native chat presentation and state contracts [skip ci]

* [comment audit] simplify native runtime and provider contracts [skip ci]

* [comment audit] simplify migration and orchestration contracts [skip ci]

* [comment audit] simplify plugin trust and process capture rationale [skip ci]

* [comment audit] simplify terminal delivery and remote compatibility rationale [skip ci]

* [comment audit] clarify remote recovery and launch wire rationale [skip ci]

* [comment audit] shorten shared RPC compatibility and runtime evidence comments [skip ci]

* [comment audit] simplify secure storage shell startup and sidebar rationale [skip ci]

* [comment audit] clarify sidebar, skill and SSH invariants [skip ci]

* [comment audit] condense structured chat delivery and timing rationale [skip ci]

* [comment audit] condense terminal replay and telemetry invariants [skip ci]

* [comment audit] clarify launch permission and terminal delivery rationale [skip ci]

* [comment audit] clarify workspace ownership and layout contracts [skip ci]

* [comment audit] wait for fitted and painted SSH terminal links

* [comment audit] clarify layout persistence and session ownership [skip ci]

* [comment audit] clarify host routing and workspace lifecycle contracts [skip ci]

* [comment audit] clarify startup persistence and app lifetime comments [skip ci]

* [comment audit] clarify recovery and activity boundaries [skip ci]

* [comment audit] condense activity and artifact invariants [skip ci]

* [comment audit] condense automation authority contracts [skip ci]

* [comment audit] simplify automation scheduling and history contracts [skip ci]

* [comment audit] clarify browser chrome and annotation lifecycles [skip ci]

* [comment audit] clarify browser routing and retained guest ownership [skip ci]

* [comment audit] clarify browser recovery and document ownership comments [skip ci]

* [comment audit] restore hosted runners required by native IME fixtures

* [comment audit] shorten preview, palette and dashboard invariants [skip ci]

* [comment audit] clarify terminal preview, dashboard and retention comments [skip ci]

* [comment audit] clarify diff-note recovery and editor lifecycle comments [skip ci]

* [comment audit] shorten rich-text and combined-diff ownership comments [skip ci]

* [comment audit] shorten autosave, draft recovery and model ownership comments [skip ci]

* [comment audit] shorten save-queue, notebook and Markdown constraints [skip ci]

* [comment audit] shorten Monaco, PDF and rich Markdown safeguards [skip ci]

* [comment audit] clarify rich Markdown source and selection safeguards [skip ci]

* [comment audit] clarify editor loading and emulator stream lifetimes [skip ci]

* [comment audit] trim emulator and feature tour narration [skip ci]

* [comment audit] clarify floating workspace and GitHub cache ownership [skip ci]

* [comment audit] trim provider controls and Linear filter explanations [skip ci]

* [comment audit] clarify pairing and chat resume lifetimes [skip ci]

* [comment audit] clarify chat delivery and transcript lifetimes [skip ci]

* [comment audit] clarify session options and host-owned status [skip ci]

* [comment audit] clarify draft durability and live-status precedence [skip ci]

* [comment audit] Trim chat send and model ownership explanations [skip ci]

* [comment audit] Shorten transcript ownership and send recovery comments [skip ci]

* [comment audit] Simplify native chat composer and transcript hook comments [skip ci]

* [comment audit] Shorten native chat scroll and structured session explanations [skip ci]

* [comment audit] Trim workspace picker and onboarding commentary [skip ci]

* [comment audit] Simplify pet rendering, review cache and quick open comments [skip ci]

* [comment audit] Trim sidebar and session history explanations [skip ci]

* [comment audit] Condense session resume and review checks comments [skip ci]

* [comment audit] Shorten checks state and file explorer refresh comments [skip ci]

* [comment audit] Trim plugin bridge and source-control action rationale [skip ci]

* [comment audit] Trim source-control listing and review rationale [skip ci]

* [comment audit] Shorten sidebar lifecycle and explorer safeguards [skip ci]

* [comment audit] Condense Settings account and appearance rationale [skip ci]

* [comment audit] Trim pairing and repository Settings explanations [skip ci]

* [comment audit] Condense Settings search SSH and terminal safeguards [skip ci]

* [comment audit] Shorten Settings voice and provider ownership comments [skip ci]

* [comment audit] Condense Settings draft persistence and host-state rationale [skip ci]

* [comment audit] Trim Settings hooks and sidebar setup rationale [skip ci]

* [comment audit] Condense sidebar deletion and board-filter safeguards [skip ci]

* [comment audit] Condense worktree cards, host ownership and Markdown contracts [skip ci]

* [comment audit] Condense sidebar drag, folder and filtering contracts [skip ci]

* [comment audit] Condense sidebar parity, sleep and host ownership contracts [skip ci]

* [comment audit] Prune focus-read baseline after comment cleanup

* [comment audit] Clarify sidebar visibility, board drops and agent evidence [skip ci]

* [comment audit] Tighten sidebar listing, drag and reveal contracts [skip ci]

* [comment audit] Clarify viewport geometry and skill update safeguards [skip ci]

* [comment audit] Tighten skill inventory, usage and host status comments [skip ci]

* [comment audit] Clarify usage ownership and resource kill safeguards [skip ci]

* [comment audit] Tighten tab selection, rename and search contracts [skip ci]

* [comment audit] Tighten tab-strip geometry and drag ownership comments [skip ci]

* [comment audit] Tighten Tasks recovery, pagination and mutation comments [skip ci]

* [comment audit] Tighten provider loading and terminal recovery comments [skip ci]

* [comment audit] Tighten completion, cadence and PTY ownership comments [skip ci]

* [comment audit] Clarify terminal restore and parked-pane boundaries [skip ci]

* [comment audit] Tighten PTY startup and replay explanations [skip ci]

* [comment audit] Clarify PTY identity and delivery ownership [skip ci]

* [comment audit] Shorten remote reconnect and input replay rationale [skip ci]

* [comment audit] Condense terminal parking clipboard and file link comments [skip ci]

* [comment audit] Clarify IME ownership and trim trace history [skip ci]

* [comment audit] Shorten pane recovery and parked watcher explanations [skip ci]

* [comment audit] Condense snapshot replay and visibility recovery rationale [skip ci]

* [comment audit] Shorten terminal parking and keyboard ownership rationale [skip ci]

* [comment audit] Trim renderer lifecycle and menu rationale [skip ci]

* [comment audit] Condense workspace cleanup ownership and settlement notes [skip ci]

* [comment audit] Clarify renderer hook evidence and source ownership [skip ci]

* [comment audit] Tighten IPC ownership and reconnect explanations [skip ci]

* [comment audit] Trim renderer focus, paste and attention comments [skip ci]

* [comment audit] Shorten scroll, locale and agent lifecycle comments [skip ci]

* [comment audit] Trim agent launch, resume and ownership commentary [skip ci]

* [comment audit] Shorten account, browser and editor comments [skip ci]

* [comment audit] Clarify host-mirror waits, previews and input ownership [skip ci]

* [comment audit] Condense launch recovery and editor tokenization comments [skip ci]

* [comment audit] Condense workspace selection and terminal pane rationale [skip ci]

* [comment audit] Condense terminal fitting and rendering recovery comments [skip ci]

* [comment audit] Condense terminal input and host routing explanations [skip ci]

* [comment audit] Condense ownership and sleeping-session recovery rationale [skip ci]

* [comment audit] Condense structured launch and terminal link explanations [skip ci]

* [comment audit] Condense terminal ownership and workspace file routing rationale [skip ci]

* [comment audit] Condense session persistence and workspace activation rationale [skip ci]

* [comment audit] Condense workspace routing and browser ownership rationale [skip ci]

* [comment audit] Condense mirror recovery and layout authority rationale [skip ci]

* [comment audit] Condense runtime transport and file safety rationale [skip ci]

* [comment audit] Condense runtime graph and structured session rationale [skip ci]

* [comment audit] Condense browser creation and remote tab recovery rationale [skip ci]

* [comment audit] Condense snapshot authority and publisher recovery rationale [skip ci]

* [comment audit] Condense frame, startup and provider cache rationale [skip ci]

* [comment audit] Condense host twin and agent lifecycle rationale [skip ci]

* [comment audit] Condense browser authority and editor cleanup rationale [skip ci]

* [comment audit] Condense editor buffer identity and review cache rationale [skip ci]

* [comment audit] Condense provider lifetime and repository ownership rationale [skip ci]

* [comment audit] Condense reconnect, hydration and terminal retirement rationale [skip ci]

* [comment audit] Condense recovery, reconciliation and persistence rationale [skip ci]

* [comment audit] Condense cleanup ownership and listing admission rationale [skip ci]

* [comment audit] Condense teardown ordering and terminal ownership rationale [skip ci]

* [comment audit] Condense recovery ledger and web host-boundary rationale [skip ci]

* [comment audit] Condense web reconnect and credential lifecycle rationale [skip ci]

* [comment audit] Condense cloud rollout and assignment safety rationale [skip ci]

* [comment audit] Condense relay session and database recovery rationale [skip ci]

* [comment audit] Condense relay placement and protocol compatibility rationale [skip ci]

* [comment audit] Condense build and packaging constraints [skip ci]

* [comment audit] Condense CI gates and validation isolation rationale [skip ci]

* [comment audit] Condense catalog identity and browser validation rationale [skip ci]

* [comment audit] Condense mobile browser and frame-budget rationale [skip ci]

* [comment audit] Shorten mobile route and browser test explanations [skip ci]

* [comment audit] Condense editor and session seam test rationale [skip ci]

* [comment audit] Trim session and terminal closure history [skip ci]

* [comment audit] Shorten terminal browser and census explanations [skip ci]

* [comment audit] Preserve root-only E2E diagnostic baseline

* [comment audit] Condense mobile seam and HTML preview test rationale [skip ci]

* [comment audit] Condense preview instrumentation and render harness rationale [skip ci]

* [comment audit] Condense bundle contracts and native-build constraints [skip ci]

* [comment audit] Shorten native packaging and CI routing rationale [skip ci]

* [comment audit] Condense release tooling and benchmark constraints [skip ci]

* [comment audit] Condense packaging and platform gate rationale [skip ci]

* [comment audit] Shorten mobile route and history rationale [skip ci]

* [comment audit] Condense mobile browser and component rationale [skip ci]

* [comment audit] Shorten drawer and workspace-form rationale [skip ci]

* [comment audit] Trim mobile review and editor-document explanations [skip ci]

* [comment audit] Condense editor lifecycle and Markdown explanations [skip ci]

* [comment audit] Trim mobile file, diagnostics and dictation commentary [skip ci]

* [comment audit] Simplify mobile host-list and workspace-view explanations [skip ci]

* [comment audit] Clarify mobile shell lifetime and bridge delivery constraints [skip ci]

* [comment audit] Condense bridge stream, native request and audio rationale [skip ci]

* [comment audit] Reduce bridge schema and page-client commentary [skip ci]

* [comment audit] Tighten mobile page bridge contracts [skip ci]

* [comment audit] Clarify mobile cache and session constraints [skip ci]

* [comment audit] Condense mobile route and lifecycle rationale [skip ci]

* [comment audit] Tighten mobile shell hook timing rationale [skip ci]

* [comment audit] Clarify route handoff and notification compatibility [skip ci]

* [comment audit] Clarify push registration and dictation cleanup constraints [skip ci]

* [comment audit] Condense keyboard geometry and native media ownership rationale [skip ci]

* [comment audit] Tighten mobile chat scroll and composer concurrency rationale [skip ci]

* [comment audit] Condense mobile reply compatibility and upload ownership rationale [skip ci]

* [comment audit] Condense optimistic echo and scroll ownership rationale [skip ci]

* [comment audit] Tighten stream replay and queued session ownership rationale [skip ci]

* [comment audit] Condense mobile composer and reply-schema constraints [skip ci]

* [comment audit] Clarify mobile transcript recovery and session ownership [skip ci]

* [comment audit] Clarify mobile queue and source-control compatibility [skip ci]

* [comment audit] Condense storage and task launch constraints [skip ci]

* [comment audit] Clarify task schemas and terminal document ownership [skip ci]

* [comment audit] Tighten terminal input and viewport rationale [skip ci]

* [comment audit] Shorten terminal and RPC recorder contracts [skip ci]

* [comment audit] Condense recorder engine and connection contracts [skip ci]

* [comment audit] Shorten host credential and relay recovery comments [skip ci]

* [comment audit] Tighten pairing recovery and bundle transport rationale [skip ci]

* [comment audit] Clarify RPC delivery and workspace catalog boundaries [skip ci]

* [comment audit] Condense catalog and relay entry contracts [skip ci]

* [comment audit] Shorten shutdown and SSH relay delivery contracts [skip ci]

* [comment audit] Condense WSL relay and E2E regression rationale [skip ci]

* [comment audit] Shorten cross-version harness and renderer fixture explanations [skip ci]

* [comment audit] Condense paired-host and E2E lifecycle rationale [skip ci]

* [comment audit] Shorten layout oracle and regression explanations [skip ci]

* [comment audit] Condense paired recovery and ownership regression contracts [skip ci]

* [comment audit] Shorten restart, sidebar and SSH lifecycle contracts [skip ci]

* [comment audit] Condense terminal rendering and IME regression rationale [skip ci]

* [comment audit] Shorten terminal recovery and benchmark contracts [skip ci]

* [comment audit] Condense tooling and Windows lifecycle proof rationale [skip ci]

* [comment audit] Condense workflow routing and release rationale [skip ci]

* [comment audit] Condense mobile security and installer contracts [skip ci]

* [comment audit] Shorten native rationale and stylesheet workarounds [skip ci]

* [comment audit] Shorten Windows visibility probe rationale [skip ci]

* [comment audit] Condense embedded release and signing rationale [skip ci]

* [comment audit] Retain current main test behavior in final reconciliation [skip ci]

* [comment audit] Complete final comment review across current source inventory

* [comment audit] Handle consolidated audit reports before final CI

* [comment audit] Preserve credential storage census detail [skip ci]

* [comment audit] Scale broad changed E2E selections with verified timing shards

* [comment audit] Align E2E assertions with browser close and settled session metadata

* [comment audit] Match existing E2Es to current resource and windowing contracts

* [comment audit] Observe the shared server outage notice in reconnect coverage

* [comment audit] Make existing renderer probes measure settled tab and PTY work

* [comment audit] Align palette E2E selector with task search

* [comment audit] Refresh remaining palette search selectors

* [comment audit] Keep selector correction within selected audit specs

* [comment audit] Await emoji fixture settings inside renderer evaluation

* [comment audit] Cancel fixture fit retries between terminal tests

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-25 04:05:46 -07:00

354 lines
13 KiB
TypeScript

/** Share child labels/details across sidebar and chat. Prefer host children views; retain subagents/task-roster builders until no supported publisher/paired host requires each legacy shape. Delete each legacy builder when its last publisher is gone. */
import type { AgentSessionBackgroundTask } from './agent-session-background-task-wire'
import type { AgentChildWorkKind } from './agent-status-child-work'
import { resolveAgentChildWorkFreshness } from './agent-status-child-work-freshness'
import {
agentChildWorkOwnedLiveness,
deriveAgentChildDisplayState,
type AgentChildDisplayState
} from './agent-status-child-work-display'
import { agentChildWorkViewOffersStop } from './agent-child-work-stop-targets'
import type { AgentChildWorkView } from './agent-status-child-work-view'
import {
agentStatusAuthorityObservedAt,
agentStatusEvidenceObservedAt
} from './agent-status-freshness'
import type { AgentStatusEntry, AgentSubagentSnapshot } from './agent-status-types'
/** What a child row says beside its name. Surfaces format it; they never decide it. */
export type AgentChildRowDetail =
/** The tool the child is running now, as `tool: input` — the line a CLI agent row shows. */
| { kind: 'operation'; toolName: string; input?: string }
/** Its own work is over and only work it owns still runs; the tool line is stale. */
| { kind: 'monitoring' }
| { kind: 'message'; text: string }
/** Settled, and the lane cannot say how. */
| { kind: 'ended' }
/** How long since the last evidence for this child (see `recencyAt`). */
| { kind: 'no-update' }
/** The child's role, when nothing more specific is known. */
| { kind: 'role'; agentType: string }
/** A host that publishes only a run state: that state's reason word. */
| { kind: 'reason'; state: 'waiting' | 'blocked' | 'unverifiable' }
/** One child row, for every surface that lists child work (sidebar and chat strip). */
export type AgentChildRowModel = {
/** Row identity: the host's child id, or the provider id an old host names it by. */
id: string
/** The id a targeted stop names; absent when the host holds none. */
providerId?: string
kind: AgentChildWorkKind
displayState: AgentChildDisplayState
/** '' when the child reported no label; a surface then names the row by its state. */
name: string
agentType?: string
model?: string
detail: AgentChildRowDetail | null
/** When the child was first seen: the elapsed anchor. */
firstObservedAt: number
/** Last evidence for THIS child. Absent from a host that reports none. */
observedAt?: number
/** Reader-clock anchor for no-update age: child evidence, otherwise parent evidence. */
recencyAt: number
settledAt?: number
totalTokens?: number
canStop: boolean
settled: boolean
/** Work this child owns (a nested agent, a shell it launched), in host order. */
owned: AgentChildRowModel[]
}
export type AgentChildRowContext = {
/** A stale parent makes every live claim beneath it unverifiable. */
parentEvidenceFresh: boolean
transportObservation: 'live' | 'unverifiable'
/** Parent evidence on the reader clock; fallback recency when the child has no clock. */
parentObservedAt: number
/** Reader minus host clock, or zero locally; convert child stamps before computing age. */
hostClockOffsetMs: number
}
/** Derive every surface's child context from the same parent evidence. */
export function agentChildRowContextForParent(
parent: Pick<
AgentStatusEntry,
'updatedAt' | 'evidenceObservedAt' | 'mirroredEvidenceReceivedAt' | 'subagentObservation'
>,
parentEvidenceFresh: boolean
): AgentChildRowContext {
// The parent row decays on this same clock, so a child's silence reads the same as its parent's.
const parentObservedAt = agentStatusEvidenceObservedAt(parent)
return {
parentEvidenceFresh,
transportObservation: parent.subagentObservation ?? 'live',
parentObservedAt,
hostClockOffsetMs: parentObservedAt - agentStatusAuthorityObservedAt(parent)
}
}
/** Provider strings that carry no identity; the next label wins. */
const PLACEHOLDER_LABELS = new Set(['unknown', 'untitled', 'task', 'subagent'])
/** A provider label that names the child, or undefined for an empty or placeholder one. */
export function usableAgentChildLabel(value: string | undefined): string | undefined {
const trimmed = value?.trim()
return trimmed && !PLACEHOLDER_LABELS.has(trimmed.toLowerCase()) ? trimmed : undefined
}
// A live claim beneath a parent Orca no longer hears from asserts nothing; settled history stands.
function withFreshness(
displayState: AgentChildDisplayState,
context: AgentChildRowContext
): AgentChildDisplayState {
if (
displayState === 'done' ||
displayState === 'failed' ||
displayState === 'interrupted' ||
displayState === 'idle'
) {
return displayState
}
return resolveAgentChildWorkFreshness({
state: displayState,
membership: 'live',
parentEvidenceFresh: context.parentEvidenceFresh,
transportObservation: context.transportObservation
})
}
type AgentChildRowDetailSource = {
/** `run-state`: the host reported only the child's run state (the legacy task roster). */
evidence: 'child' | 'run-state'
kind: AgentChildWorkKind
name: string
agentType?: string
operation?: { toolName: string; input?: string }
lastMessage?: string
settled: boolean
}
// A default role or one that restates the name adds nothing.
function roleDetail(source: AgentChildRowDetailSource): AgentChildRowDetail | null {
if (source.kind !== 'agent') {
return null
}
const agentType = source.agentType ?? 'unknown'
return agentType.trim() === 'default' || source.name.trim() === agentType.trim()
? null
: { kind: 'role', agentType }
}
function messageOrRole(source: AgentChildRowDetailSource): AgentChildRowDetail | null {
const text = source.lastMessage?.trim()
return text ? { kind: 'message', text } : roleDetail(source)
}
/** The one rule for what a child row says, in the order a CLI agent row decides it. */
function agentChildRowDetail(
source: AgentChildRowDetailSource,
displayState: AgentChildDisplayState
): AgentChildRowDetail | null {
if (source.evidence === 'run-state') {
return displayState === 'waiting' ||
displayState === 'blocked' ||
displayState === 'unverifiable'
? { kind: 'reason', state: displayState }
: null
}
switch (displayState) {
case 'unverifiable':
return { kind: 'no-update' }
case 'monitoring':
// A shell or monitor names itself; only an agent's own tool line goes stale.
return source.kind === 'agent' ? { kind: 'monitoring' } : null
case 'working':
case 'waiting':
return source.operation
? {
kind: 'operation',
toolName: source.operation.toolName,
...(source.operation.input !== undefined ? { input: source.operation.input } : {})
}
: messageOrRole(source)
case 'blocked':
case 'done':
case 'failed':
return messageOrRole(source)
case 'interrupted':
return roleDetail(source)
case 'idle':
return source.settled ? { kind: 'ended' } : roleDetail(source)
}
}
function viewName(view: AgentChildWorkView): string {
return (
usableAgentChildLabel(view.description) ??
usableAgentChildLabel(view.name) ??
usableAgentChildLabel(view.agentType) ??
''
)
}
function rowFromView(
view: AgentChildWorkView,
views: readonly AgentChildWorkView[],
ownedByOwner: ReadonlyMap<string, AgentChildWorkView[]>,
context: AgentChildRowContext,
path: ReadonlySet<string>
): AgentChildRowModel {
const displayState = withFreshness(
deriveAgentChildDisplayState(view, agentChildWorkOwnedLiveness(views, view.id)),
context
)
const settled = view.membership === 'settled'
const name = viewName(view)
const nextPath = new Set(path).add(view.id)
return {
id: view.id,
...(view.providerId !== undefined ? { providerId: view.providerId } : {}),
kind: view.kind,
displayState,
name,
...(view.agentType !== undefined ? { agentType: view.agentType } : {}),
...(view.model !== undefined ? { model: view.model } : {}),
detail: agentChildRowDetail(
{
evidence: 'child',
kind: view.kind,
name,
agentType: view.agentType,
operation: view.operation,
lastMessage: view.lastMessage,
settled
},
displayState
),
firstObservedAt: view.firstObservedAt,
observedAt: view.observedAt,
recencyAt: view.observedAt + context.hostClockOffsetMs,
...(view.settledAt !== undefined ? { settledAt: view.settledAt } : {}),
...(view.totalTokens !== undefined ? { totalTokens: view.totalTokens } : {}),
canStop: agentChildWorkViewOffersStop(view),
settled,
owned: (ownedByOwner.get(view.id) ?? [])
.filter((owned) => !nextPath.has(owned.id))
.map((owned) => rowFromView(owned, views, ownedByOwner, context, nextPath))
}
}
/** Nest host views by ownership; the shared fold keeps finished agents monitoring their live work. */
export function buildAgentChildRowModels(
views: readonly AgentChildWorkView[],
context: AgentChildRowContext
): AgentChildRowModel[] {
const ownedByOwner = new Map<string, AgentChildWorkView[]>()
for (const view of views) {
if (view.parentChildWorkId !== undefined) {
ownedByOwner.set(view.parentChildWorkId, [
...(ownedByOwner.get(view.parentChildWorkId) ?? []),
view
])
}
}
return views
.filter((view) => view.parentChildWorkId === undefined)
.map((view) => rowFromView(view, views, ownedByOwner, context, new Set()))
}
/** Legacy subagents from older hosts/CLI panes have lifecycle/spawn times but no child evidence clock. */
export function buildLegacyAgentChildRowModels(
subagents: readonly AgentSubagentSnapshot[],
context: AgentChildRowContext
): AgentChildRowModel[] {
return subagents.map((subagent) => {
const displayState = withFreshness(
deriveAgentChildDisplayState({ state: subagent.state, membership: 'live' }, null),
context
)
const name =
usableAgentChildLabel(subagent.description) ?? usableAgentChildLabel(subagent.agentType) ?? ''
return {
id: subagent.id,
providerId: subagent.id,
kind: 'agent',
displayState,
name,
...(subagent.agentType !== undefined ? { agentType: subagent.agentType } : {}),
...(subagent.model !== undefined ? { model: subagent.model } : {}),
detail: agentChildRowDetail(
{ evidence: 'child', kind: 'agent', name, agentType: subagent.agentType, settled: false },
displayState
),
firstObservedAt: subagent.startedAt,
recencyAt: context.parentObservedAt,
canStop: false,
settled: false,
owned: []
}
})
}
function legacyTaskDisplayState(
task: AgentSessionBackgroundTask,
settled: boolean
): AgentChildDisplayState {
if (task.state) {
return task.state
}
if (settled) {
return 'done'
}
return task.kind === 'monitor' ? 'monitoring' : 'working'
}
// The host decided each row's state; the row keeps it.
function legacyTaskRow(task: AgentSessionBackgroundTask, settled: boolean): AgentChildRowModel {
const displayState = legacyTaskDisplayState(task, settled)
const name = usableAgentChildLabel(task.description) ?? usableAgentChildLabel(task.name) ?? ''
const startedAt = task.startedAt ?? 0
return {
id: task.id,
providerId: task.id,
kind: task.kind,
displayState,
name,
detail: agentChildRowDetail(
{ evidence: 'run-state', kind: task.kind, name, settled },
displayState
),
firstObservedAt: startedAt,
recencyAt: startedAt,
...(task.totalTokens !== undefined ? { totalTokens: task.totalTokens } : {}),
// Absent means stoppable: a host predating the field published only rows its stop could act on.
canStop: task.stoppable !== false,
settled,
owned: []
}
}
/** Legacy session task rosters are flat, retain host-decided state and have only start times. */
export function buildLegacyTaskRowModels(
tasks: readonly AgentSessionBackgroundTask[],
settledTasks: readonly AgentSessionBackgroundTask[]
): AgentChildRowModel[] {
// Older hosts can retain a previous turn beside its resumed live task; the live row wins.
const rows = new Map<string, AgentChildRowModel>()
for (const [roster, settled] of [
[settledTasks, true],
[tasks, false]
] as const) {
for (const task of roster) {
rows.set(task.id, legacyTaskRow(task, settled))
}
}
return [...rows.values()]
}
/** Every row of a tree, owners before what they own. */
export function flattenAgentChildRowModels(
rows: readonly AgentChildRowModel[]
): AgentChildRowModel[] {
return rows.flatMap((row) => [row, ...flattenAgentChildRowModels(row.owned)])
}