import { type ExecutionRecord, type TaskRecord } from './core/tasks.ts'; import { type TaskBoardAction, type TaskBoardSchedulerSnapshot } from './protocol.ts'; import { type TaskPermission } from './core/handover.ts'; export interface LedgerState { revision: number; tasks: TaskRecord[]; scheduler: TaskBoardSchedulerSnapshot; } export interface OpenedRun { task: TaskRecord; execution: ExecutionRecord; } /** Minimal value copy used by the Host session monitor. */ export interface OpenExecutionReference { readonly taskId: string; readonly executionId: string; readonly sessionId: string | undefined; readonly startedAt: number; } /** Minimal value copy used by the Host scheduler. */ export interface DueScheduleReference { readonly taskId: string; readonly cron: string; readonly nextRunAt: number; } /** Derived runtime data for one session-poll pass. */ export interface LedgerRuntimeView { readonly armedSchedules: number; readonly openExecutions: readonly OpenExecutionReference[]; } /** * Best-effort single-letter process state ('R','S','D','Z',...) or undefined * when no probe is available on this platform. Linux reads /proc//stat * directly (no subprocess); other POSIX shells out to `ps -o stat=`; Windows * has no zombie state, so it returns undefined and the kill(0) probe alone * is authoritative there. */ export declare function processState(pid: number): string | undefined; export declare function processIsAlive(pid: number): boolean; /** Runs one PowerShell script and returns its trimmed stdout. */ export type PowerShellProbe = (script: string, timeoutMs: number) => string | undefined; /** * Windows start time (Unix epoch ms) of a live process. * * `Get-Process` is the precise first choice, but an unprivileged caller cannot * read `.StartTime` for a protected process (System, svchost): the property is * empty, so the probe returns nothing. Without a fallback, a crash leftover * lock whose PID was reused by such a process could never be proven stale and * blocked every startup until the lock was deleted by hand (issue #1629). * Win32_Process through CIM reports the same CreationDate for those processes * at the same millisecond precision, so it is the second identity source. The * probe is injectable so the fallback chain is testable off Windows. */ export declare function win32StartTimeMs(pid: number, probe?: PowerShellProbe): number | undefined; export declare class HostTaskLedger { private readonly now; private document; private readonly listeners; private readonly requestCache; private readonly lockToken; private lockFd; readonly file: string; readonly lockFile: string; /** Small sidecar for the 30 s scheduler heartbeat (lastTickAt only). */ readonly schedulerFile: string; /** Session-default permission the confirmation gate compares against. */ readonly sessionDefaultPermission: TaskPermission; constructor(dir?: string, now?: () => number, options?: { sessionDefaultPermission?: TaskPermission; }); /** Remove leftover *.tmp-* files from previous crashes or interrupted writes. */ private cleanStaleTemporaryFiles; /** Revision + scheduler without any task cloning; feeds the SSE event frame. */ summary(): { revision: number; scheduler: TaskBoardSchedulerSnapshot; }; state(): LedgerState; /** * Runtime-only projection for the 5 s Host poll. It copies just primitive * identifiers and timestamps, never the complete task/execution history or * an authoritative mutable object from the ledger. */ runtimeView(): LedgerRuntimeView; /** Count armed, non-archived schedules without cloning task histories. */ armedScheduleCount(): number; /** Return value-only references for schedules due at the supplied Host time. */ dueSchedules(now: number): DueScheduleReference[]; subscribe(listener: () => void): () => void; dispose(): void; applyRequest(requestId: string, action: TaskBoardAction, initiator?: string): { state: LedgerState; run?: OpenedRun; }; openScheduled(taskId: string, nextRunAt: number | undefined, triggeredAt: number): OpenedRun | undefined; skipMissed(now: number): void; setScheduler(patch: Partial): void; attachSession(taskId: string, executionId: string, sessionId: string): void; settle(taskId: string, executionId: string, outcome: 'succeeded' | 'failed' | 'cancelled', error?: string): void; private apply; private repairSchedules; private reconcileInterruptedStarts; /** * Field-preserving v2 to v3 migration. v3 adds no fields yet, so the * migration reuses the v3 normalization, but it first proves every task * row is structurally valid: a v2 document that would silently drop or * coerce rows fails loudly instead (no quarantined-empty restart). */ private migrateLegacyDocument; private load; private normalizeDocument; /** Quarantine an unreadable document and start from an empty ledger. */ private recoverCorrupt; private syncRecentRequests; private readSchedulerSidecar; /** Atomic write of the scheduler heartbeat sidecar (0600, tmp + rename + fsync). */ private writeSchedulerSidecar; private commit; private notify; private acquireLock; }