/** * Multi-session Orchestration, Cross-Session Task Registry * * Wraps SessionTaskGraph with persistence to a host-owned task graph path * and reconnect/resume hydration. * * The registry is the single authoritative source for the cross-session task * graph within a process. Command handlers and sync integrations receive an * owned instance from the runtime service graph. * * Housekeeping contract (the persisted graph is a recoverable store, so it * carries the full set of store obligations; the pure half lives in * registry-housekeeping.ts): * - REAP ON RECOVERY, hydration drops refs whose owning session no longer * exists (via the injected `sessionExists` predicate), drops edges left * dangling by that removal, and retires handoffs that have already fired. * - BOUND, every collection has BOTH a count cap and an age TTL. * - VALIDATE BY CONTENT, the file is parsed and shape-checked record by * record; a torn/zero-byte/truncated file is rejected, never served. * - SWEEP PERIODICALLY, the same reap runs on an interval, not only at * startup, because a daemon-hosted registry can stay up for weeks. * - DISCLOSE, every reap that removed anything logs its counts. */ import type { CrossSessionTaskRef, TaskHandoffRecord, CancellationRequest, CancellationResult, SessionTaskGraphSnapshot } from './types.js'; import type { TaskLifecycleState } from '../../runtime/store/domains/tasks.js'; import { type CrossSessionGraphReapSummary } from './registry-housekeeping.js'; export type { CrossSessionGraphReapSummary } from './registry-housekeeping.js'; /** Construction-time seams for {@link CrossSessionTaskRegistry}. */ export interface CrossSessionTaskRegistryOptions { /** * "Does this session still exist?", INJECTED so the registry can reap * records whose owning session is gone without growing a hard dependency on * the session store (and so tests can drive it directly). * * Omitted means owner-existence reaping is skipped entirely; the age TTL and * count caps still apply. Callers that can answer this question should pass * it, otherwise refs for vanished sessions survive until they age out. */ readonly sessionExists?: ((sessionId: string) => boolean) | undefined; /** Clock seam (tests). Defaults to `Date.now`. */ readonly now?: (() => number) | undefined; /** Periodic sweep interval in ms. `0` disables the timer (tests, short-lived processes). Defaults to one hour. */ readonly sweepIntervalMs?: number | undefined; } /** * CrossSessionTaskRegistry, persistent wrapper around `SessionTaskGraph`. * * Responsibilities: * - Load the graph from disk on construction (reconnect/resume hydration), * validating it by content and reaping records whose owner is gone. * - Flush the graph to disk after every mutation. * - Sweep the graph periodically, not only at startup. * - Expose a stable interface for command handlers and sync adapters. * - Generate unique handoff IDs. */ export declare class CrossSessionTaskRegistry { private _graph; private readonly _graphPath; private readonly _dir; private readonly _sessionExists; private readonly _now; private _dirEnsured; private _flushTimer; private _sweepTimer; private _lastReap; _exitHandler: (() => void) | null; /** * @param graphPath - Absolute host-owned task graph path. * @param options - Injected seams (session-existence predicate, clock, sweep interval). */ constructor(graphPath: string, options?: CrossSessionTaskRegistryOptions); /** * Link a task into the global graph, registers the task as a * cross-session ref and optionally adds a dependency edge. * * @param ref - The task ref to link. * @param dependsOn - Optional ref this task depends on. * @param reason - Optional reason for the dependency edge. * @returns Result of the link operation. */ linkTask(ref: CrossSessionTaskRef, dependsOn?: { sessionId: string; taskId: string; }, reason?: string): { ok: boolean; error?: string | undefined; }; /** * Update the status of a task ref. * * @param sessionId - Owning session. * @param taskId - Target task. * @param status - New lifecycle status. * @returns `true` if the status changed and was flushed. */ propagateStatus(sessionId: string, taskId: string, status: TaskLifecycleState): boolean; /** * Look up a ref by session + task ID. */ getRef(sessionId: string, taskId: string): CrossSessionTaskRef | undefined; /** * Return all refs in the graph. */ getAllRefs(): CrossSessionTaskRef[]; /** * Return all refs for a given session. */ getRefsBySession(sessionId: string): CrossSessionTaskRef[]; /** * Return all direct dependencies of a task. */ getDependencies(sessionId: string, taskId: string): CrossSessionTaskRef[]; /** * Return all direct dependents of a task. */ getDependents(sessionId: string, taskId: string): CrossSessionTaskRef[]; /** * Initiate a task handoff from one session to another. * * Both sessions must have their task refs registered before calling this. * The originating task ref status is updated to 'blocked' (awaiting handoff). * * @param taskRef - The task being handed off. * @param fromSessionId - Source session. * @param toSessionId - Destination session. * @param reason - Optional human-readable reason. * @returns Result of the handoff operation. */ initiateHandoff(taskRef: { sessionId: string; taskId: string; }, fromSessionId: string, toSessionId: string, reason?: string): { ok: boolean; handoffId?: string; error?: string | undefined; }; /** * Acknowledge a handoff from the destination session. * * @param handoffId - The handoff to acknowledge. * @returns `true` if the handoff was found and acknowledged. */ acknowledgeHandoff(handoffId: string): boolean; /** * Return all handoff records. */ getHandoffs(): TaskHandoffRecord[]; /** * Apply a scoped cancellation to the graph. * * @param request - The cancellation request. * @returns Result describing what was cancelled and what was skipped. */ cancel(request: CancellationRequest): CancellationResult; /** * Take a snapshot of the current graph state. * Suitable for display (e.g. `/session graph`). */ snapshot(): SessionTaskGraphSnapshot; /** * Run a housekeeping pass over the in-memory graph now: drop refs whose * owning session is gone, refs past their TTL, dangling edges, fired or * aged-out handoffs, and anything over the count caps. * * Safe to call at any time and idempotent, a second call immediately after * a first reclaims nothing. Concurrent processes are safe because the reap * is computed over this process's in-memory graph and persisted through the * ordinary flush path; the loser of a concurrent flush simply reaps again. * * @returns The counts reclaimed by this pass. */ reap(): CrossSessionGraphReapSummary; /** The counts reclaimed by the most recent reap pass (hydration or sweep). */ lastReapSummary(): CrossSessionGraphReapSummary; /** * Force a synchronous flush to disk. * Use on shutdown/dispose to ensure all pending data is written. */ flush(): void; /** * Release process-level resources held by the registry. * * Safe to call multiple times. Flushes pending state before detaching the * process exit handler and stopping the periodic sweep. */ dispose(): void; /** * Load the persisted graph from disk and hydrate the in-memory graph. * * The file is validated by CONTENT, a zero-byte, truncated or otherwise * torn file is rejected and preserved aside rather than partially trusted. * Surviving records are then reaped before hydration, so recovery never * re-imports records whose owning session is gone. */ private _load; /** Surface what a reap reclaimed. Counts only, graph contents are never logged. */ private _disclose; /** * Preserve an untrustworthy graph file aside instead of letting the next * flush overwrite it. The quarantine name is fixed, so at most one such file * ever exists; {@link _sweepQuarantine} ages it out. */ private _quarantine; /** Delete a preserved-aside graph file once it is past {@link QUARANTINE_RETENTION_MS}. ENOENT is success. */ private _sweepQuarantine; /** * Flush the current graph snapshot to disk. * If the write fails, logs a warning and continues with the in-memory graph. */ private _flush; /** Schedule a debounced async write (coalesces rapid successive mutations). */ private _scheduledFlush; /** Perform a synchronous write, used by shutdown/dispose and flush(). */ private _flushSync; /** Per-process temp path for atomic writes. */ private _tempPath; } //# sourceMappingURL=registry.d.ts.map