/** * On-disk layout for the engineer daemon (epic #2098, child #4): * * ~/.agent-cards/engineer// * daemon.json 0600 — the ek_ relay token + engagement context * state.json 0600 — crash-safe runtime state (pending instructions, session ids) * outbox/ 0700 — spooled issue reports awaiting the daemon * * Deliberately SEPARATE from ~/.agent-cards/config.json: `logout`/`clearAuth` * must never touch the daemon credential, and no ordinary command should load * it. The ek_ token lives only in daemon.json (0600) — never argv, never env. * Writes are atomic (tmp + rename), the config.ts pattern. */ export declare function engineerBaseDir(): string; export interface DaemonConfig { engagementId: string; orgId: string; repoRoot: string; slackChannelId: string | null; slackChannelName?: string | null; /** The ek_ relay bearer — shown once by the backend, stored only here. */ daemonToken: string; /** Preferred adapter; connect --adapter overrides per run. */ adapter?: 'claude-code' | 'codex' | 'paste'; /** CLI version at init — the pinned-release check compares against this. */ pinnedVersion: string; createdAt: string; } export interface PendingInstruction { /** EngagementMessage id — the ack key. */ id: string; /** The proposal envelope as delivered by the relay. */ payload: Record; deliveredAt: string; /** Slack ts of the daemon's own Accept/Decline message, once posted. */ buttonTs?: string; /** * Shared (relay-only) mode: the durable "applied" marker, persisted the * moment the adapter chain finishes and BEFORE the `injected` ack goes out. * A restart that finds this set retries the ACK only — it never re-applies * the instruction. */ injectedOutcome?: { result: 'completed' | 'handed_off' | 'error'; summary: string; }; } export interface DaemonState { pendingInstructions: PendingInstruction[]; /** Instruction ids fully handled (accepted+injected or declined) — idempotence. */ handledInstructionIds: string[]; claudeSessionId?: string; codexThreadId?: string; lastPollAt?: string; /** Slack ts of the engagement's conversation root (the first handshake). Persisted so a * daemon restart reuses the SAME thread instead of splitting the conversation. */ conversationRootTs?: string; } export declare function saveDaemonConfig(cfg: DaemonConfig, baseDir?: string): string; export declare function loadDaemonConfig(engagementId: string, baseDir?: string): DaemonConfig | null; export declare function listDaemonConfigs(baseDir?: string): DaemonConfig[]; /** Resolve the engagement bound to a repo (realpath comparison). */ export declare function findDaemonConfigByRepo(repoRoot: string, baseDir?: string): DaemonConfig | null; export declare function loadDaemonState(engagementId: string, baseDir?: string): DaemonState; export declare function saveDaemonState(engagementId: string, state: DaemonState, baseDir?: string): void; /** * Fresh state.json lastPollAt within this window ⇒ `engineer connect` is * alive (the daemon persists lastPollAt on every relay poll tick). Local-only * liveness signal shared by `engineer ask` and the wizard's --engineer * preflight — no IPC, no token. */ export declare const DAEMON_LIVENESS_WINDOW_MS = 90000; export declare function isDaemonLive(lastPollAt: string | undefined, now?: number): boolean; export declare function outboxDir(engagementId: string, baseDir?: string): string; /** Path to the detached daemon's log file (child stdout/stderr). */ export declare function daemonLogPath(engagementId: string, baseDir?: string): string; /** Path to the detached daemon's pidfile. */ export declare function daemonPidPath(engagementId: string, baseDir?: string): string; /** Path to the handshake-outcome marker the detached child writes. */ export declare function daemonHandshakePath(engagementId: string, baseDir?: string): string; export declare function ensureEngagementDir(engagementId: string, baseDir?: string): string; /** Read the detached daemon's pid, or null if unset/garbage. */ export declare function readDaemonPid(engagementId: string, baseDir?: string): number | null; export declare function writeDaemonPid(engagementId: string, pid: number, baseDir?: string): void; export declare function clearDaemonPid(engagementId: string, baseDir?: string): void; /** Liveness probe: `kill(pid, 0)` throws ESRCH when the process is gone. */ export declare function isProcessAlive(pid: number): boolean; /** * True only when the live pid is actually one of our engineer daemons. A * pidfile records just a number; if a detached daemon exits without clearing it * and the OS recycles that PID, signaling it blindly would hit an unrelated * process. Verifying the command line closes that hole. */ export declare function isEngineerDaemonProcess(pid: number): boolean; /** Whether a background daemon is running for this engagement (pidfile + liveness). */ export declare function backgroundDaemonStatus(engagementId: string, baseDir?: string, isDaemon?: (pid: number) => boolean): { running: boolean; pid: number | null; }; /** * Answers delivered by the relay land here (T7): the daemon writes each * `answer` envelope as a file so `engineer ask` (and the wizard's child agent * behind it) can block on the reply without ever touching the ek_ token — * the inbound mirror of the outbox spool. */ export declare function inboxDir(engagementId: string, baseDir?: string): string; /** * Remove an engagement's local footprint. Best-effort shred of daemon.json * (overwrite the token bytes before unlink) — not a security boundary on * modern filesystems, just avoids leaving the credential in the clear. */ export declare function removeEngagementDir(engagementId: string, baseDir?: string): void; //# sourceMappingURL=store.d.ts.map