/** * Pure eligibility classifier — takes the per-iteration board snapshot plus * derived state (worktrees, live workspaces, usage, slot count) and returns * a verdict per Todo task. No logging, no Linear calls, no shell-outs. * * The Dispatcher consumes the verdict list to drive logging and side * effects. */ import { type ResolvedConfig } from "../lib/config.ts"; import { type GroundcrewIssue } from "../lib/taskSource.ts"; import type { UsageByAgent } from "../lib/usage.ts"; import type { WorkspaceProbe } from "../lib/workspaces.ts"; import type { WorktreeEntry } from "../lib/worktrees.ts"; type SkipReason = "blocked" | "blockers_paginated" | "agent_any_capacity" | "agent_exhausted" | "workspace_list_unavailable" | "workspace_missing"; export interface StartVerdict { kind: "start"; issue: GroundcrewIssue; recovery: boolean; /** Set when the verdict resolved an `agent-any` label to a concrete agent. */ resolvedFromAny: boolean; } export interface SkipVerdict { kind: "skip"; issue: GroundcrewIssue; /** Human log line. */ message: string; /** Stable kebab-case enum surfaced as `logEvent.reason`. */ eventReason: SkipReason; /** Set for `blocked` and `blockers_paginated`. */ blockers?: string[]; /** * Set when the skip event should carry the resolved agent (i.e. the * verdict knew which agent would have run). Omitted for blocker skips * and `agent_any_capacity` where the agent was either unresolved or * irrelevant. */ agent?: string; } type Verdict = StartVerdict | SkipVerdict; export type AgentUsageExhaustion = { kind: "unavailable"; agent: string; reason: string; } | { kind: "session"; agent: string; usedPercentage: number; limitPercentage: number; resetMinutes: number | null; } | { kind: "weekly"; agent: string; usedPercentage: number; allowedPercentage: number; resetMinutes: number; }; export interface ClassifyArguments { config: ResolvedConfig; /** * Issues already filtered through `classifyBlockers` — the blocker * pre-pass runs on a separate path so dispatcher can short-circuit * (skipping the codexbar usage HTTP call and the cmux/tmux shell-out) * when every Todo is blocked. */ unblocked: readonly GroundcrewIssue[]; worktreeEntries: readonly WorktreeEntry[]; workspaceProbe: WorkspaceProbe; usage: UsageByAgent; /** Agents flagged over `sessionLimitPercentage`. */ exhausted: Set; /** Maximum number of `start` verdicts to produce. */ slots: number; dryRun: boolean; } interface BlockerClassification { unblocked: GroundcrewIssue[]; skips: SkipVerdict[]; } /** * Pick the configured agent with the most available session capacity. * Agents flagged exhausted (over `sessionLimitPercentage`) are excluded. * Score is `usage[agent].session` with `null`/missing treated as 0 * (maximum headroom), so when no usage data is available every agent * ties at 0 and the default agent wins the tiebreak — `agent-any` then * falls back to the default predictably. */ export declare function pickBestAgent(config: ResolvedConfig, usage: UsageByAgent, exhausted: Set): string | undefined; export declare function classifyUsageExhaustion(config: ResolvedConfig, usage: UsageByAgent): AgentUsageExhaustion[]; /** * Cheap pre-pass — partitions Todo into unblocked issues and blocker * skip verdicts. Runs before the dispatcher fetches usage or probes the * workspace adapter, so a board where every Todo is blocked short-circuits * without paying for either. */ export declare function classifyBlockers(todo: readonly GroundcrewIssue[]): BlockerClassification; /** * Eligibility verdicts for already-unblocked Todo issues — handles * agent-any resolution, session exhaustion, worktree/workspace recovery, * and slot capping. Pure: caller pre-fetches usage + workspaces and passes * the snapshots in. */ export declare function classifyEligibility(arguments_: ClassifyArguments): Verdict[]; export {}; //# sourceMappingURL=eligibility.d.ts.map