import { type StageLabel } from './stage-enum.js'; import type { AppendHeartbeatInput, DispatchRecord, Heartbeat, LifecycleInput, WriteInitialDispatchInput } from './dispatch-record-types.js'; export type { AppendHeartbeatInput, DispatchOutcome, DispatchRecord, DispatchRecordStatus, Heartbeat, HeartbeatStatus, LifecycleInput, WriteInitialDispatchInput } from './dispatch-record-types.js'; export { isDispatchStatus, isOutcome } from './dispatch-record-upgrade.js'; /** Write a new dispatch record (G2 + G5 + G6). Returns the absolute path. */ export declare function writeInitialDispatchRecord(input: WriteInitialDispatchInput): { path: string; record: DispatchRecord; }; /** * Bound the dispatch ledger before a new record registers: index rows left * `queued` past the bound, whose record is still `queued`, are finalized by the * tooling rather than by a remembered `peaks sub-agent finalize` call (measured: * 39/39, 2/2, 9/9, 23/23 rows across four sessions had never been finalized). * * The RECORD decides, not the index row. Nothing writes a mid-flight status into * a row — `registerActiveDispatch` hardcodes `queued` and the only unregister * caller deletes the row — so a row reads `queued` for a sub-agent that is * heartbeating right now, and a row-keyed guard would reap a live dispatch. * `no-execution` is terminal for the heartbeat pollers, so that would stop a * watcher waiting on work that is still running. * * The clock passed in is the SAME one that stamps this dispatch's `createdAt`, * so the bound is judged on one clock and a caller that injects a clock (every * test, and any replay) sees a consistent frame. */ /** * The record's own status, or `null` when the record file is absent. An * unparseable file reports `unreadable` — a real terminal status — rather than * `null`, so a caller can tell "no record here" from "a record I may not * rewrite"; the reaper drops a row only for the first. * * Exported because the reaper takes it as its reader, so production and its * tests judge on the same implementation rather than on two copies of it. */ export declare function readRecordStatus(recordPath: string): string | null; /** * The active-dispatches index lives in `./active-dispatch-index.ts` (split out * to keep this file under its size cap). Re-exported here so every existing * importer keeps its path. */ export { readActiveDispatchIndex, type ActiveDispatchEntry } from './active-dispatch-index.js'; export declare const DISPATCH_RECORD_TTL_DAYS = 30; /** * (older than DISPATCH_RECORD_TTL_DAYS or already GC'd)? Mirrors * `isOrphanChannel` in shared-channel.ts so a future * `peaks sub-agent cleanup` umbrella can run all three sweeps * (shared channel + dispatch record + contract) in one pass. */ export declare function isOrphanDispatchRecord(opts: { projectRoot: string; sid: string; rid: string; recordPath: string; now?: Date; }): boolean; /** Append a heartbeat (G6). Idempotent on (at, status) — append-only. */ export declare function appendHeartbeat(input: AppendHeartbeatInput): { record: DispatchRecord; truncated: boolean; }; /** Apply truncation: keep most recent 100, mark truncated flag. */ export declare function applyTruncation(entries: readonly Heartbeat[]): { heartbeats: Heartbeat[]; truncated: boolean; }; /** * auto-release for the lease owned by a dispatch. Called from * `markCompleted` (terminal status) and from the heartbeat CLI * (`--status done`). * * Design: * - The release subprocess is spawned ASYNC and detached. The * finalize-time caller (heartbeat / share / dispatch reducer) * does NOT await it; the caller's job is to record the * finalization, not to wait for the lease cleanup. * - Failures are swallowed (best-effort, same as `git worktree * remove` inside `peaks worktree release` itself). The next * `peaks worktree gc` pass is the safety net. * - Idempotent: re-calling with the same leaseId is a no-op on * the release side (the CLI refuses to re-release an already- * released lease; see Part 1 release command). * - The leaseId MUST be 16-hex (same regex the gate uses). Any * other value is silently ignored — we never shell out to * `peaks worktree release` with attacker-controlled input. */ export declare function tryAutoReleaseLease(args: { projectRoot: string; sessionId: string; leaseId: string; /** Best-effort logging hook (e.g. logger.writeLogEntry). Returns null on failure. */ logger?: (line: string) => void; }): void; /** Mark a record as completed (success / failed / cancelled / no-execution). */ export declare function markCompleted(input: LifecycleInput): { record: DispatchRecord; }; /** Mark a record as disposed (reducer ran). */ export declare function markDisposed(recordPath: string, now?: () => Date): { record: DispatchRecord; }; /** * the record's `stage` field. Rejects unknown values with * `INVALID_STAGE`; the LLM-side runner surfaces the error so the * sub-agent can pick from the bounded enum in ./stage-enum.ts. * * Atomic via the same `withFileLockSync` lock as `appendHeartbeat` / * `markCompleted`. `null` is a valid argument ("clear the stage") * but unknown strings are not. */ export declare function setStage(input: { recordPath: string; stage: StageLabel | null; now?: () => Date; }): { record: DispatchRecord; }; /** * Read a dispatch record with backward-compat defaults. Old records * missing G5 / G6 fields are upgraded on read (no error, no overwrite). */ export declare function readRecord(recordPath: string): DispatchRecord; /** Read multiple records from a list of paths. Tolerates missing files. */ export declare function readRecords(paths: readonly string[]): DispatchRecord[];