import { type EngineerDaemonApi, type DeliveredInstruction } from './api.js'; import type { EngineerSlackHandle, BlockActionPayload, ChannelMessageEvent } from './slack.js'; import type { AgentAdapter } from '../adapters/types.js'; import { type DaemonConfig, type DaemonState } from './store.js'; export interface DaemonHooks { log(line: string): void; sleep?(ms: number): Promise; /** Test seam — skip fs.watch wiring. */ disableFsWatch?: boolean; } export interface EngineerDaemonOptions { config: DaemonConfig; api: EngineerDaemonApi; /** null = shared (relay-only) mode: no Slack client-side at all. */ slack: EngineerSlackHandle | null; adapter: AgentAdapter; /** Fallback when the primary adapter errors or is unavailable (paste). */ fallbackAdapter: AgentAdapter; ourBotUserId: string; customerBotUserId: string; version: string; pollWaitSeconds?: number; /** Delay between polls while frozen (test seam; default 5s). */ frozenDelayMs?: number; baseDir?: string; hooks: DaemonHooks; } export declare class EngineerDaemon { private readonly opts; readonly state: DaemonState; private stopped; private frozen; private stopReason; private wake; private injectChain; private adapterStarted; private backoffAttempt; private watcher; private draining; constructor(opts: EngineerDaemonOptions); private log; private sleep; private persist; private adapterContext; /** * Mirror a daemon envelope to Slack, then ledger it on the relay. * * Slack-first (for initiating messages) so we can anchor the conversation * thread on the top-level message: the engineer's reply/ack must carry * thread_ref = a REAL Slack ts to thread under it, and that ts only exists * after the post. We stamp thread_ref onto the copy we ledger so the * engineer-agent (which reads envelopes off the relay) replies in-thread. * A Slack failure never drops the message — it's still ledgered (that relay * path is how content flows even when Slack posting is unavailable). */ private publishEnvelope; start(): Promise; /** * Connectivity handshake (run once, right after start()): POST a `handshake` * envelope on the customer's behalf — same relay-ledger + Slack-mirror path * an issue report uses — then wait a bounded window for the engineer's * `handshake_ack`, delivered like any other approved outbound over the relay * long-poll. Any other instruction that arrives during the wait is handled * normally (never dropped). Returns true iff the ack arrived in time; the * daemon keeps running either way. */ performHandshake(opts?: { companyLabel?: string; timeoutMs?: number; }): Promise; /** Main loop. Resolves when the daemon stops (freeze holds, close exits). */ run(): Promise; /** * One iteration: long-poll the relay, dispatch deliveries, drain the spool. * Exposed for tests. */ tick(): Promise; /** Sleep that a Slack wake signal or stop() can cut short. */ private interruptibleSleep; shutdown(reason: string): Promise; handleDelivered(message: DeliveredInstruction): Promise; /** * Shared-mode execution path: run the injection chain for a pre-accepted * proposal. Skips the `accepted` ack — the Authorize press was validated and * recorded server-side; the daemon only reports `injected`. * * Crash/failure safety (Greptile P1): the proposal STAYS in * pendingInstructions (the in-progress record) until injection reaches a * terminal outcome — only then is it claimed into handledInstructionIds. An * adapter throw or a crash mid-injection leaves it pending, so a restart's * rehydrate (or a relay redelivery, which no-ops on pending ids) re-injects * instead of silently dropping an already-accepted instruction. */ private injectPreAccepted; private postProposalButtons; onChannelMessage(event: ChannelMessageEvent): void; handleAction(actionId: string, payload: BlockActionPayload): Promise; /** presser=null → shared mode: the Accept was recorded server-side (Ford DM). */ private runInjection; drainSpool(): Promise; } //# sourceMappingURL=daemon.d.ts.map