/** * Minimal in-process task store. * * Tracks short-lived tasks (e.g. subagent delegations) so the TUI task panel can * display active work. It is a process-level singleton because the tool that * creates tasks and the footer that renders them live in the same process and * there is no cross-process boundary to cross. */ /** * `cancelled` is a user-initiated stop (Esc/abort mid-run) — deliberately * distinct from `failed` so the panel doesn't paint an intentional interrupt * as an error. */ export type TaskStatus = "pending" | "in_progress" | "done" | "failed" | "cancelled"; /** * What kind of background work owns a task, surfaced as a source glyph in the * pane. Unset means the main agent. A "team" origin is reserved for the * hooteams integration and stays unwired until that lands. */ export type TaskSource = "subagent" | "mcp"; /** * Kind of agent that can own tasks in the pane's grouped views: * the main session (orchestrator), a spawned subagent, or a named * team role-agent (e.g. fed by a hooteams bridge). */ export type TaskAgentKind = "main" | "subagent" | "role"; /** Lifecycle word shown as the agent's `[state]` tag in grouped views. */ export type TaskAgentState = "active" | "running" | "done" | "queued" | "idle" | "waiting" | "failed" | "cancelled"; /** * An agent that owns tasks, rendered as a group header in the pane's * `subagents` / `teams` views. Subagent dispatches register themselves here; * external orchestrators (hooteams) can upsert role-agents with handoffs. */ export interface TaskAgent { readonly id: string; name: string; /** Short descriptor after the name: "orchestrator", "subagent", or a team role like "architect". */ role?: string; kind: TaskAgentKind; state?: TaskAgentState; /** Handoff arrow text for team views (e.g. "→ reviewer", "← builder"). */ handoff?: string; /** * Live activity descriptor for a running subagent (e.g. the tool it is * currently executing), fed by the pool's `task_progress` events. Empty string * means "no current activity" — `applyAgentPatch` skips `undefined`, so callers * clear it by patching `""`, not `undefined`. */ activity?: string; /** Per-agent token + cost totals, shown right-aligned on the group header. */ stats?: { input: number; output: number; cost: number; }; } export interface Task { readonly id: number; title: string; status: TaskStatus; /** Origin of the task (subagent delegation vs MCP tool call; unset = main agent); drives the pane's source glyph. */ source?: TaskSource; /** * Origin label shown as the row's `[tag]` in the task pane: the subagent * type for delegations (e.g. "explore"), the MCP server name for MCP tasks * (e.g. "github"). */ subagentMode?: string; /** Id of the owning TaskAgent; drives grouping in the pane's subagents/teams views. */ agent?: string; /** * Id of the task that spawned this one, linking a dispatched subagent (and its * own delegations) back to the Task call that created it. Root tasks omit it. * Drives the subagents lens's recursive task tree: a node's children are the * tasks whose `parentTaskId` is its id, so nesting deeper than one level (a * subagent that spawns a subagent) is visible. Set when a child subagent's task * subtree is merged into the parent (see finalizeDispatchResult). */ parentTaskId?: number; /** * Id of the main-agent (TodoWrite) task this run is working on, set at * dispatch time when exactly one plan item is in_progress. Purely a display * link: the flat ("tasks") lens nests the run under its plan item so the * plan and the agents executing it read as one picture. Deliberately NOT * `parentTaskId` — that field drives the subagents tree and the * cross-process subtree merge, and overloading it would move runs between * lenses. A dangling link (the todo was removed/replaced) is simply not * rendered. */ linkedTaskId?: number; /** * Short warning note surfaced as a ⚠ cue in the task pane (e.g. the subagent * fell back to the inherited model, or was skipped because the provider was * exhausted). Kept terse so it fits the row's right column. */ note?: string; /** * Canonical TodoWrite item content for main-plan tasks. `title` is the * *display* form and legitimately changes between `content` and `activeForm` * as the item's status moves, so it cannot identify an item across TodoWrite * calls. This field can: the tool reconciles incoming items against it by * identity first (position only as a fallback), keeping task ids — and the * subagent runs linked to them — attached to the same plan item when the * list is reordered or shrunk. */ todoContent?: string; readonly createdAt: number; updatedAt: number; /** Token and cost usage attributed to this task (e.g. from a subagent session). */ usage?: { input: number; output: number; cacheRead: number; cacheWrite: number; cost: number; }; } interface CreateTaskOptions { source?: TaskSource; subagentMode?: string; agent?: string; parentTaskId?: number; linkedTaskId?: number; } type TaskPatch = Partial>; type TaskAgentPatch = Partial>; /** * Owner group for a task when no explicit agent is set: subagent-sourced work * falls into a generic "subagent" group, everything else belongs to main. * Shared by the store's reset() and the pane's grouped views so the two never * disagree about which agents still own live tasks. */ export declare function taskOwnerId(task: Pick): string; type Listener = () => void; declare class TaskStore { private tasks; private taskAgents; private nextId; private readonly listeners; private batchDepth; private mutationCount; /** * Monotonic counter bumped on every mutation (including those inside a * batch). Lets renderers cache derived state and invalidate it cheaply by * comparing versions instead of deep-diffing tasks/agents. */ version(): number; /** * Run a series of mutations without notifying listeners until the batch * completes, so the TUI renders once instead of per-item. Nested batches are * supported: only the outermost batch flushes. */ batch(fn: () => void): void; create(title: string, options?: CreateTaskOptions): Task; update(id: number, patch: TaskPatch): void; /** * Register or update an agent for the grouped task views. Merges into an * existing entry with the same id (accumulated stats survive a re-dispatch); * creates it otherwise. */ upsertAgent(agent: { id: string; } & TaskAgentPatch & Pick): TaskAgent; /** Patch an existing agent (state/handoff/stats…). Unknown ids are ignored. */ patchAgent(id: string, patch: TaskAgentPatch): void; /** Add a usage delta to an agent's running totals (creating them at zero). */ addAgentStats(id: string, delta: { input?: number; output?: number; cost?: number; }): void; agents(): readonly TaskAgent[]; private applyAgentPatch; remove(id: number): void; /** * Arrange the given tasks (by id) into the specified relative order, keeping * every other task fixed: the matched tasks permute among their existing * array slots. Lets TodoWrite render the plan in list order while ids stay * pinned to their items (its identity-based reconcile). Ignored unless every * id resolves to a task. */ arrange(ids: readonly number[]): void; /** * Drop finished tasks and restart numbering from #1 once the pane is empty. * * Called when a new user message arrives: finished tasks from the previous turn * stay visible (with their final status) for the whole turn and are wiped only * when the user starts the next turn, so the next turn opens with an empty pane * and its first task is #1 again. Active (pending/in_progress) tasks are kept — * a follow-up/steer message can arrive while a subagent is still running, and * dropping its task here would orphan the live work (its later status update * would target a removed id and silently vanish). Numbering only restarts once * no active task survives, so ids never collide with a kept task. * Agents with accumulated stats are preserved across resets so cross-turn cost * accounting survives. */ reset(): void; list(): readonly Task[]; /** Wipe all tasks and restart numbering. Intended for test isolation only. */ clear(): void; subscribe(listener: Listener): () => void; private emit; } /** Shared, process-wide task store. */ export declare const taskStore: TaskStore; export {}; //# sourceMappingURL=task-store.d.ts.map