mirror of
https://github.com/stablyai/orca.git
synced 2026-09-25 16:02:38 +00:00
* feat(agents): distinguish Claude background monitoring Adds an optional `workingMode: 'monitoring'` discriminator for a Claude session whose lead turn finished but which still has background shell tasks or session crons registered. The wire state stays `working`, so older peers that never read the field keep rendering Working. (cherry picked from commitd5d54b4bdd) Rebased onto current main (554 commits of drift) by Brennan Benson; conflicts resolved by keeping both sides where main and this branch made independent additions to the same construct. * fix(sidebar): keep monitoring status visible (cherry picked from commitfd6b38654d) * test(agents): cover Claude monitoring drain (cherry picked from commitce4d61ebf8) * test(mobile): avoid unresolved renderer test type (cherry picked from commitbbcfa35ff9) * feat(agents): render Claude monitoring as a static turquoise dot Replaces the yellow Radio glyph from #14205 with a static dot in a new --agent-monitoring token (#8abeb7), defined once for light and once for dark like --workspace-status-done, so the status keeps its identity when the theme flips. Deliberately a fixed UI value: it never reads terminal theme state at runtime. Adds the turn-boundary notification pins. The monitoring predicate and the turnCompletedAt stamp are computed from the same "lead said done but the pane resolves to working" expression, so a rename can silently drop the stamp and kill a completion notification that works today with nothing else going red. * revert(agents): restore the yellow Radio glyph for monitoring Brennan chose #14205's original treatment over the turquoise dot, so the visual goes back to nwparker's: lucide Radio in text-yellow-500 across the sidebar, dashboard dot, cmd-j palette and agent-map ring. Reverts only the visual surface. The turn-boundary notification pins stay — the monitoring predicate and the turnCompletedAt stamp share an expression, so a rename can silently drop the stamp and kill a completion that works today with nothing else going red. The --agent-monitoring token is removed with its last consumer rather than left dead in main.css. --------- Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
237 lines
9.4 KiB
TypeScript
237 lines
9.4 KiB
TypeScript
import type { AgentType, AgentWorkingMode } from './agent-status-types'
|
|
import type { ExecutionHostId } from './execution-host'
|
|
import type { RepoIcon } from './repo-icon'
|
|
import type { TuiAgent } from './tui-agent'
|
|
|
|
/**
|
|
* Serializable contract for the pop-out agent dashboard. The main renderer owns
|
|
* the live store, derives this snapshot, and relays it (through the main
|
|
* process) to the separate pop-out renderer, which renders it presentationally.
|
|
* Every field must be structured-clone-safe (no functions / class instances).
|
|
*/
|
|
|
|
/** Agent lifecycle columns; idle is optional while completed agents remain visible. */
|
|
export type DashboardBucket = 'attention' | 'working' | 'done' | 'idle'
|
|
|
|
/** Column order shared by producer and pop-out so they never drift. */
|
|
export const DASHBOARD_BUCKET_ORDER: readonly DashboardBucket[] = [
|
|
'attention',
|
|
'working',
|
|
'done',
|
|
'idle'
|
|
]
|
|
|
|
/** Max length of a card's display labels. The producer truncates to this and
|
|
* the main-process validator enforces it, so an unbounded name (a long
|
|
* `terminal rename`, an OSC title) cannot cost the card its place on the board. */
|
|
export const DASHBOARD_MAX_LABEL_LENGTH = 1_024
|
|
|
|
/** The validator drops a whole snapshot that exceeds this, so the builder caps
|
|
* the launcher's entries rather than letting a huge fleet blank the pop-out. */
|
|
export const DASHBOARD_MAX_LAUNCH_WORKTREES = 500
|
|
|
|
/** Keeps optional map-only workspace metadata bounded across the renderer bridge. */
|
|
export const DASHBOARD_MAX_MAP_WORKSPACES = 2_000
|
|
|
|
/** Kept distinct from `bucket` so attention cards retain their precise dot state. */
|
|
export type DashboardCardDotState = 'working' | 'blocked' | 'waiting' | 'done' | 'idle'
|
|
export type DashboardCardDisplayState = DashboardCardDotState | 'monitoring'
|
|
|
|
/** Completed agents stay green until acknowledged, then settle into gray idle. */
|
|
export function dashboardCardDisplayState(
|
|
card: Pick<DashboardCard, 'dotState' | 'workingMode' | 'unseen'>
|
|
): DashboardCardDisplayState {
|
|
if (card.dotState === 'working' && card.workingMode === 'monitoring') {
|
|
return 'monitoring'
|
|
}
|
|
return card.dotState === 'done' && !card.unseen ? 'idle' : card.dotState
|
|
}
|
|
|
|
export type DashboardCardReview = {
|
|
number: number
|
|
state: 'open' | 'closed' | 'merged' | 'draft'
|
|
}
|
|
|
|
export type DashboardCardSubagent = {
|
|
id: string
|
|
name: string
|
|
dotState: DashboardCardDotState
|
|
}
|
|
|
|
export type DashboardCardHostKind = 'local' | 'ssh' | 'wsl' | 'remote'
|
|
export type DashboardCardWorkspaceKind = 'worktree' | 'folder'
|
|
|
|
export type DashboardWorkspace = {
|
|
repoId: string
|
|
worktreeId: string
|
|
repoName: string
|
|
worktreeName: string
|
|
parentWorktreeId?: string
|
|
hostKind: DashboardCardHostKind
|
|
executionHostId: ExecutionHostId
|
|
/** Friendly saved-host name for compact host tooltips. */
|
|
hostLabel?: string
|
|
workspaceKind: DashboardCardWorkspaceKind
|
|
workspaceStatusId?: string
|
|
workspaceStatusLabel?: string
|
|
workspaceStatusColor?: string
|
|
hasReview?: boolean
|
|
review?: DashboardCardReview
|
|
}
|
|
|
|
export type DashboardCard = {
|
|
/** Stable identity for React keys. */
|
|
paneKey: string
|
|
/** Resolved live PTY id for the terminal preview, or null when the agent has
|
|
* no live pane (e.g. a retained/done row whose pane is gone). */
|
|
ptyId: string | null
|
|
agentType: AgentType
|
|
bucket: DashboardBucket
|
|
dotState: DashboardCardDotState
|
|
/** Additive discriminator; older pop-outs render this as ordinary working. */
|
|
workingMode?: AgentWorkingMode
|
|
/** One-line task/prompt text shown on the card. */
|
|
task: string
|
|
/** The most recent message the user sent this agent (its current prompt). */
|
|
lastUserMessage?: string
|
|
/** The most recent message the agent sent back. */
|
|
lastAgentMessage?: string
|
|
/** Routing target for click-to-focus. leafId is null when unresolved. */
|
|
repoId: string
|
|
worktreeId: string
|
|
tabId: string
|
|
leafId: string | null
|
|
/** Agent pane that spawned this agent, when both are visible. */
|
|
parentPaneKey?: string
|
|
/** Direct workspace parent. The map uses it only when both workspace rings are visible. */
|
|
parentWorktreeId?: string
|
|
repoName: string
|
|
worktreeName: string
|
|
/** Optional for preload compatibility with snapshots produced before Agent Map. */
|
|
hostKind?: DashboardCardHostKind
|
|
/** Exact owner used by in-window workspace actions when IDs collide across hosts. */
|
|
executionHostId?: ExecutionHostId
|
|
/** Friendly saved-host name for compact host tooltips. */
|
|
hostLabel?: string
|
|
/** Folder workspaces share the ring hierarchy without pretending to be git worktrees. */
|
|
workspaceKind?: DashboardCardWorkspaceKind
|
|
workspaceStatusId?: string
|
|
workspaceStatusLabel?: string
|
|
workspaceStatusColor?: string
|
|
/** True when the workspace links a review whose live state is not cached yet. */
|
|
hasReview?: boolean
|
|
review?: DashboardCardReview
|
|
subagents?: DashboardCardSubagent[]
|
|
/** "Started … ago" display. */
|
|
startedAt: number
|
|
/** When the agent last entered `done`, or null if it never finished. Drives
|
|
* the card's time column: finished cards read time-since-finish (parity with
|
|
* the left worktree sidebar), active cards fall back to startedAt. */
|
|
finishedAt: number | null
|
|
/** When the agent entered its current state — column ordering key (cards
|
|
* that moved into a bucket most recently sort first). 0 when unknown. */
|
|
stateChangedAt: number
|
|
/** Last accepted hook update. Optional for mixed-version snapshots; the
|
|
* pop-out uses it to request one refresh when a live state becomes stale. */
|
|
statusUpdatedAt?: number
|
|
/** Mirrors the sidebar's unvisited signal: the agent changed state since the
|
|
* user last acknowledged it (visited its tab / opened its dashboard dialog).
|
|
* Derived from the app-wide ack map so both surfaces mute in lockstep. */
|
|
unseen: boolean
|
|
/** Short summary of the pending question when bucket === 'attention'. */
|
|
askSummary?: string
|
|
/** The tab's conversation name, resolved exactly as the sidebar's agent rows
|
|
* resolve it. Undefined when no usable name exists (status-only titles). */
|
|
conversationName?: string
|
|
/** Host-dependent input facts the preview terminal needs to encode keys the
|
|
* way this agent's real pane does. Null when the card has no live pty. Only
|
|
* the main renderer owns the store these derive from, so they ride the
|
|
* snapshot to reach the pop-out. */
|
|
terminalInput?: DashboardCardTerminalInput
|
|
}
|
|
|
|
/**
|
|
* Per-pty input contract shared by a pane and its dashboard preview. Byte
|
|
* protocols follow the PTY's execution host, not the client OS — they differ
|
|
* for a macOS client driving a Windows runtime.
|
|
*/
|
|
export type DashboardCardTerminalInput = {
|
|
/** Platform executing the pty; picks the host-side byte encodings. */
|
|
hostPlatform: NodeJS.Platform
|
|
/** Local native Windows ConPTY, where PSReadLine binds Ctrl+←/→ itself. */
|
|
localWindowsConpty: boolean
|
|
/** OS release of a local Windows client, for xterm's ConPTY wrap-marker compat. */
|
|
osRelease?: string
|
|
/** Shift+Enter encoding resolved from this pane's agent evidence. */
|
|
windowsShiftEnterEncoding: 'alt-enter' | 'csi-u'
|
|
/** Force protected multiline paste when the live agent requires paste frames. */
|
|
forceBracketedMultilineTextPaste?: true
|
|
/** Newline encoding for Windows TUIs that consume console input records. */
|
|
windowsInputRecordPasteNewline?: 'alt-enter' | 'csi-u'
|
|
/** Trusted query-only consumer accepts Ctrl+Enter CSI-u without active flags. */
|
|
ctrlEnterCsiU: boolean
|
|
/** False withholds the kitty (CSI-u) advertisement, as ConPTY panes do. */
|
|
kittyKeyboardAdvertised: boolean
|
|
}
|
|
|
|
export type DashboardFilterOption = {
|
|
id: string
|
|
label: string
|
|
color?: string
|
|
}
|
|
|
|
export type DashboardFilterOptions = {
|
|
projects: DashboardFilterOption[]
|
|
workspaceStatuses: DashboardFilterOption[]
|
|
}
|
|
|
|
export type DashboardSnapshot = {
|
|
generatedAt: number
|
|
cards: DashboardCard[]
|
|
/** Active workspaces, including those without an agent card. Map-only and
|
|
* optional for preload compatibility with older snapshot producers. */
|
|
workspaces?: DashboardWorkspace[]
|
|
showIdle?: boolean
|
|
/** Available filter dimensions are store-derived so zero-card projects and
|
|
* statuses remain selectable. Optional for preload-version compatibility. */
|
|
filterOptions?: DashboardFilterOptions
|
|
/** Launch choices resolved on each workspace's execution host. */
|
|
launchableAgentsByWorktreeId?: Record<string, TuiAgent[]>
|
|
/** Icons for the repos the cards belong to. Keyed by repoId rather than
|
|
* carried per card: image icons are data URLs up to 400KB, and the snapshot
|
|
* is republished several times a second. Optional so a pop-out running
|
|
* pre-upgrade code still accepts the payload. */
|
|
repoIconsByRepoId?: Record<string, RepoIcon | null>
|
|
}
|
|
|
|
export const EMPTY_DASHBOARD_SNAPSHOT: DashboardSnapshot = {
|
|
generatedAt: 0,
|
|
cards: [],
|
|
workspaces: [],
|
|
filterOptions: { projects: [], workspaceStatuses: [] },
|
|
launchableAgentsByWorktreeId: {},
|
|
repoIconsByRepoId: {}
|
|
}
|
|
|
|
/** Routing payload for click-to-focus: reveal this agent's pane in the main
|
|
* window. leafId is null when the pane could not be resolved (best-effort:
|
|
* the worktree is still activated). */
|
|
export type DashboardRevealAgentArgs = {
|
|
repoId: string
|
|
worktreeId: string
|
|
executionHostId?: ExecutionHostId
|
|
tabId: string
|
|
leafId: string | null
|
|
}
|
|
|
|
export type DashboardSpawnAgentArgs = {
|
|
worktreeId: string
|
|
agent: TuiAgent
|
|
}
|
|
|
|
/** Puts a workspace to sleep from the dashboard: the main renderer owns the
|
|
* teardown sequence, so the pop-out only names the target. */
|
|
export type DashboardSleepWorkspaceArgs = {
|
|
worktreeId: string
|
|
}
|