mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 00:57:45 +00:00
* fix(mobile): use desktop's sort, colour, count and agent-row rules in the workspace list Recent ignores terminal output, repo colours fall back to desktop's neutral, Home counts skip archived rows, folder workspaces trail each lane, and a stale agent row on a live terminal reads "No update in …" like desktop. * fix(mobile): keep Delete disabled on a primary checkout, as desktop does * fix(mobile): keep the task text on a silent agent row * fix(mobile): show a silent agent row's time since its last update * test(mobile): drop agent-row tests that replay the shared verdict * refactor(mobile): check host PTY liveness once and keep the delete guard on the row flag * test(mobile): re-record the home catalog goldens for the list's active count
123 lines
4.8 KiB
TypeScript
123 lines
4.8 KiB
TypeScript
import { isFreshNonDoneAgentStatus } from '../../shared/agent-status-types'
|
|
import { isAgentStatusFresh } from '../../shared/agent-status-freshness'
|
|
import type { RuntimeWorktreeAgentRow, RuntimeWorktreePsSummary } from '../../shared/runtime-types'
|
|
import { mergeWorktreeSummaryStatus } from './runtime-worktree-status-projection'
|
|
import {
|
|
resolveRuntimeWorktreeSidebarStatus,
|
|
terminalTitleEvidenceMatchesSource
|
|
} from './runtime-worktree-sidebar-status'
|
|
import type { RuntimeWorktreePsSummaryLookup } from './runtime-worktree-summary-paths'
|
|
import type { RuntimeTerminalTitleEvidence } from './runtime-worktree-ps-activity'
|
|
import type { RuntimeWorktreeAgentSource } from './runtime-worktree-agent-source'
|
|
export type { RuntimeAgentRowSnapshot } from './runtime-hook-agent-row-selection'
|
|
|
|
type OrchestrationDisplay = {
|
|
taskTitle?: string | null
|
|
displayName?: string | null
|
|
parentPaneKey?: string | null
|
|
}
|
|
|
|
export function attachRuntimeWorktreeAgentRows(args: {
|
|
summaries: Map<string, RuntimeWorktreePsSummary>
|
|
rowSources: ReadonlyMap<string, RuntimeWorktreeAgentSource>
|
|
terminalTitleEvidenceByWorktreeId: ReadonlyMap<string, readonly RuntimeTerminalTitleEvidence[]>
|
|
orchestrationByPaneKey: Record<string, OrchestrationDisplay> | null | undefined
|
|
getSummary: RuntimeWorktreePsSummaryLookup
|
|
}): void {
|
|
const { rowSources } = args
|
|
const now = Date.now()
|
|
const rowsByWorktree = new Map<string, RuntimeWorktreeAgentRow[]>()
|
|
for (const source of rowSources.values()) {
|
|
const { worktreeId } = source
|
|
if (!worktreeId) {
|
|
continue
|
|
}
|
|
const summary = args.getSummary(worktreeId)
|
|
if (!summary) {
|
|
continue
|
|
}
|
|
const orchestration = args.orchestrationByPaneKey?.[source.paneKey]
|
|
const row: RuntimeWorktreeAgentRow = {
|
|
paneKey: source.paneKey,
|
|
parentPaneKey: orchestration?.parentPaneKey ?? null,
|
|
state: source.state,
|
|
...(source.workingMode ? { workingMode: source.workingMode } : {}),
|
|
agentType: source.agentType,
|
|
prompt: source.prompt,
|
|
taskTitle: orchestration?.taskTitle ?? null,
|
|
displayName: orchestration?.displayName ?? null,
|
|
lastAssistantMessage: source.lastAssistantMessage,
|
|
toolName: source.toolName,
|
|
toolInput: source.toolInput,
|
|
interrupted: source.interrupted,
|
|
...(source.mainAgent ? { mainAgent: source.mainAgent } : {}),
|
|
stateStartedAt: source.stateStartedAt,
|
|
updatedAt: source.updatedAt,
|
|
...(source.children !== undefined ? { children: source.children } : {}),
|
|
...(source.subagents !== undefined ? { subagents: source.subagents } : {}),
|
|
...(source.structuredHost === 'owned' ? { structuredHostOwned: true as const } : {}),
|
|
...(source.hasLivePty ? { hasLivePty: true as const } : {}),
|
|
...(source.programStatus ? { programStatus: source.programStatus } : {})
|
|
}
|
|
if (source.children !== undefined || source.subagents !== undefined) {
|
|
row.subagentObservation = isAgentStatusFresh(row, now) ? 'live' : 'unverifiable'
|
|
}
|
|
const rows = rowsByWorktree.get(summary.worktreeId)
|
|
if (rows) {
|
|
rows.push(row)
|
|
} else {
|
|
rowsByWorktree.set(summary.worktreeId, [row])
|
|
}
|
|
}
|
|
for (const [worktreeId, rows] of rowsByWorktree) {
|
|
rows.sort((a, b) => a.stateStartedAt - b.stateStartedAt)
|
|
const summary = args.summaries.get(worktreeId)
|
|
if (!summary) {
|
|
continue
|
|
}
|
|
summary.agents = rows
|
|
let hasForegroundWorkingAgent = false
|
|
const monitoringSources: RuntimeWorktreeAgentSource[] = []
|
|
for (const row of rows) {
|
|
if (!isFreshNonDoneAgentStatus(row, now)) {
|
|
continue
|
|
}
|
|
summary.hasHostSidebarActivity = true
|
|
if (row.state === 'working') {
|
|
if (row.workingMode === 'monitoring') {
|
|
const source = rowSources.get(row.paneKey)
|
|
if (source) {
|
|
monitoringSources.push(source)
|
|
}
|
|
} else {
|
|
hasForegroundWorkingAgent = true
|
|
}
|
|
} else {
|
|
mergeWorktreeSummaryStatus(summary, 'permission')
|
|
}
|
|
}
|
|
if (hasForegroundWorkingAgent || monitoringSources.length > 0) {
|
|
const hasIndependentWorkingTerminal = (
|
|
args.terminalTitleEvidenceByWorktreeId.get(worktreeId) ?? []
|
|
).some(
|
|
(evidence) =>
|
|
evidence.status === 'working' &&
|
|
monitoringSources.every((source) => !terminalTitleEvidenceMatchesSource(evidence, source))
|
|
)
|
|
mergeWorktreeSummaryStatus(
|
|
summary,
|
|
'working',
|
|
hasForegroundWorkingAgent || hasIndependentWorkingTerminal ? undefined : 'monitoring'
|
|
)
|
|
}
|
|
}
|
|
for (const summary of args.summaries.values()) {
|
|
summary.sidebarStatus = resolveRuntimeWorktreeSidebarStatus(
|
|
summary,
|
|
now,
|
|
args.terminalTitleEvidenceByWorktreeId.get(summary.worktreeId) ?? [],
|
|
(paneKey) => rowSources.get(paneKey)
|
|
)
|
|
}
|
|
}
|