/** * Lead watch. In parallel-sweep-2 the resource governor deferred two roles * forever and the lead never noticed that they had never started, so their * tasks were never reassigned to the idle workers. This watch tells the lead, * through its mailbox, when a role that holds an open task * (i) has not started a session `not_started_s` seconds after the task was * dispatched (deferred by the governor, queued, crashed at start), or * (ii) has produced no bus event for `silent_s` seconds. * * It only ever fires for a role with an open ('running') task, so a healthy * run, where every assignee starts and keeps emitting events, is untouched. * One message per role per episode: an episode ends the moment the role * starts or emits; inside an episode a reminder follows only after a doubling * gap, at most MAX_NOTICES in all. A lead that cancels the task, re-creates it * for another role, or messages the role (acknowledges) is not reminded again * about those tasks. * * Work handed out by org_send message instead of org_task (the sweep orgs) * counts too: a lead (or reports_to) message to a role that is an actionable * assignment (not an acknowledgement, see `isActionableAssignment`) is open work * for that role from the moment it was sent, with the same not-started and * silent clocks, episodes, backoff and cap. It ends when the role messages * anyone after it (a reply or a report), when the lead messages the role after * a notice (acknowledge), or when it is reassigned. A role hung in one long * Bash call emits no events and is exactly the silent case. * * Config: run_config.lead_watch = { not_started_s?: 90, silent_s?: 180 }; * `false` turns it off. The decision is a pure function of a snapshot * (`LeadWatch.tick`), so it is table-testable without a daemon. */ import type { OrgDaemon } from './daemon.js'; import type { RunningOrg } from './daemon-types.js'; export declare const DEFAULT_NOT_STARTED_S = 90; export declare const DEFAULT_SILENT_S = 180; /** Notices per episode, first one included. */ export declare const MAX_NOTICES = 3; export interface LeadWatchConfig { notStartedMs: number; silentMs: number; } /** The effective config, or null when the org turned the watch off. */ export declare function leadWatchConfig(run: { lead_watch?: false | { not_started_s?: number; silent_s?: number; }; }): LeadWatchConfig | null; export interface WatchedTask { id: string; title: string; /** When it was dispatched (status 'running'), or when the assignment message was sent. */ since: number; /** An assignment sent by org_send rather than an org_task. */ via?: 'message'; } export interface RoleSnapshot { id: string; /** The lead to tell, or undefined when there is nobody to tell. */ lead?: string; /** A live session exists. */ started: boolean; /** Last bus event the role emitted, 0 if none. */ lastActivity: number; /** Legitimately waiting (pending approval/gate/blocking question). */ waiting: boolean; /** Sections orgs (P3.16c): a consumer whose only open work is waiting for documents; never silent. Absent otherwise. */ awaitingDocuments?: boolean; openTasks: WatchedTask[]; } export interface Notice { lead: string; role: string; kind: 'not-started' | 'silent'; taskIds: string[]; text: string; } /** A message that hands work over: not empty and not a bare acknowledgement or thanks. */ export declare function isActionableAssignment(_subject: string | undefined, body: string | undefined): boolean; export declare class LeadWatch { private cfg; private episodes; /** Assignment messages not yet answered, by role. */ private msgWork; /** Tasks the lead has dealt with or acknowledged: never mentioned again. */ private settled; constructor(cfg: LeadWatchConfig); /** The lead messaged `role`: acknowledge every task it was told about. True when there was one. */ acknowledge(role: string): boolean; /** The lead sent `role` an assignment by message. */ messageAssigned(role: string, work: WatchedTask): void; /** `role` messaged someone after its assignment (a reply or a report): that work is answered. */ messageReplied(role: string): void; /** Roles that hold unanswered assignment messages. */ messageRoles(): string[]; /** A task was re-created for another role: its twin is settled. */ reassigned(taskId: string): void; tick(roles: RoleSnapshot[], now: number): Notice[]; } /** Start the watch for one org; returns a stop function. */ export declare function startLeadWatch(daemon: OrgDaemon, name: string, running: RunningOrg, roleActivity: Map): (() => void) | undefined; //# sourceMappingURL=lead-watch.d.ts.map