/** * Merge Worker Sidecar * * Starts the merge worker as a background loop alongside the fleet. * Only one merge worker runs per repo (enforced by Redis lock). * * The sidecar: * 1. Reads .agentfactory/config.yaml for merge queue settings * 2. Creates a dedicated worktree for merge operations * 3. Starts the MergeWorker poll loop * 4. Stops gracefully on AbortSignal * * If Redis is not configured or merge queue is not enabled, this is a no-op. */ import { type IssueTrackerClient } from '@renseiai/agentfactory'; /** Label used to opt a PR into the local merge queue (REN-503 handoff signal). */ export declare const APPROVED_FOR_MERGE_LABEL = "approved-for-merge"; export interface MergeWorkerSidecarConfig { /** Git repository root (default: auto-detect from cwd) */ gitRoot?: string; /** Override repoId (default: derived from git remote) */ repoId?: string; /** * Issue tracker for bubbling merge results back to the originating issue. * When omitted, the sidecar autodetects in this order: * 1. LINEAR_API_KEY env var → direct Linear client * 2. proxyConfig.apiUrl + apiKey → proxy through coordinator API * 3. neither → no bubble-up (failures land in Redis only) * Pass `null` explicitly to disable bubble-up regardless of env state. */ issueTracker?: IssueTrackerClient | null; /** * Coordinator API credentials for the proxy issue tracker. When the * fleet runs against a platform that proxies all Linear operations * (rather than each worker holding a Linear API key directly), pass * the same apiUrl/apiKey workers use. The sidecar constructs a * ProxyIssueTrackerAdapter from these when LINEAR_API_KEY is not set. */ proxyConfig?: { apiUrl: string; apiKey: string; }; } export interface MergeWorkerSidecarHandle { /** Stop the merge worker gracefully */ stop(): void; /** Promise that resolves when the worker exits */ done: Promise; } /** * Split a `owner/repo` repoId into components. Returns null for unparseable * values (e.g., the "default" fallback) so callers can skip label polling * without crashing. */ export declare function splitRepoId(repoId: string): { owner: string; repo: string; } | null; /** * Resolve which issue tracker the worker should use: * - explicit `null` → opt-out (no bubble-up) * - explicit instance → use it * - omitted (undefined) → autodetect: * 1. LINEAR_API_KEY env var → direct Linear client * 2. proxyConfig.apiUrl + apiKey → proxy through coordinator API * 3. neither → null (no bubble-up) * * The proxy path supports deployments where workers don't hold Linear * credentials directly and instead route all tracker operations through * a coordinator. Mirrors the worker-runner.ts resolution order so the * sidecar matches whatever the fleet's workers use. * * Exported for testing. */ export declare function resolveIssueTracker(fromConfig: IssueTrackerClient | null | undefined, proxyConfig?: { apiUrl: string; apiKey: string; }): IssueTrackerClient | null; /** Shape `gh pr list --json number` returns (we only use `number`). */ interface LabeledPR { number: number; } /** Minimal adapter surface the label poller needs. Exported for testing. */ export interface LabelPollerAdapter { canEnqueue(owner: string, repo: string, prNumber: number): Promise; enqueue(owner: string, repo: string, prNumber: number): Promise; } /** * Poll GitHub for PRs carrying the `approved-for-merge` label and hand any * that aren't already queued to the merge queue adapter. This is the * secondary REN-503 handoff path — complements the orchestrator's * synchronous enqueue on acceptance pass, covers human-initiated queueing * (someone labels a PR by hand), and recovers from any missed acceptance * event (agent crashed between pass and enqueue). * * Exported for testing. `gh` is invoked via execFile (no shell), with a * dedicated timeout so a stalled call doesn't wedge the sidecar. * * Returns the number of PRs that were newly enqueued this pass (already- * queued PRs are idempotently no-op and not counted). */ export declare function pollApprovedForMergeLabel(adapter: LabelPollerAdapter, owner: string, repo: string, options?: { /** Injectable for testing — defaults to `gh pr list` via execFile. */ listLabeledPRs?: (owner: string, repo: string, signal?: AbortSignal) => Promise; log?: (msg: string) => void; /** Abort signal; an in-flight `gh` call is killed and the poll returns 0 on abort. */ signal?: AbortSignal; }): Promise; /** * Remove the `approved-for-merge` label from a PR via the `gh` CLI. Best- * effort — logs on failure but never throws. Exported for testing. * * `gh pr edit --remove-label` is idempotent: if the label isn't present it * exits non-zero with "Unprocessable Entity" style output. We treat that as * success because the end state is what we want. */ export declare function removeApprovedForMergeLabel(owner: string, repo: string, prNumber: number): Promise; /** * Fetch a PR's state via `gh pr view`. Returns null when the call fails * (network, missing PR, gh not installed) so callers can fall through to * the normal merge path rather than skipping unintentionally. */ export declare function getPRState(owner: string, repo: string, prNumber: number): Promise<{ state: 'OPEN' | 'CLOSED' | 'MERGED'; mergedAt: string | null; } | null>; /** * Start the merge worker sidecar if merge queue is enabled. * * Returns a handle to stop the worker, or null if merge queue is not * configured or Redis is unavailable. Safe to call unconditionally — * it checks all preconditions before starting. */ export declare function startMergeWorkerSidecar(config?: MergeWorkerSidecarConfig, signal?: AbortSignal): MergeWorkerSidecarHandle | null; export {}; //# sourceMappingURL=merge-worker-sidecar.d.ts.map