/** * PendingAgentDraftStore — durable store for agent-generated draft context * attached to a failed peer-runner task. * * Task 11: When a peer runner reaches a permanent-failure terminal state * (BasePeerRunner catch block — Task 12), it constructs an AgentDraftPayload * and writes it here via insertPendingDraft. The feedback-report pipeline * (Task 13) later reads the unconsumed draft for a given taskId and merges * it into the user-facing FeedbackReport so the maintainer sees the agent's * perspective alongside the user's description. After a successful merge, * markConsumed sets consumed_at so the draft cannot be re-attached. * * Idempotency contract: the schema's partial unique index * UNIQUE(task_id) WHERE consumed_at IS NULL * guarantees at most one unconsumed draft per task_id at the DB level. * insertPendingDraft honors this by first SELECTing for an existing * unconsumed row, then UPDATE-ing it in place (rather than INSERT-ing a new * row) when one exists. This matches the spec requirement: "如果 taskId 已有 * 未消费行,UPDATE 该行的 agent_draft / created_at / pain_id,不创建新行". * * ERR checklist: * - EP-01 / ERR-001, ERR-005: agent_draft JSON is parsed into `unknown` and * validated field-by-field via isAgentDraftPayload before being exposed as * AgentDraftPayload. No `as` casts on row data (rc-1, rc-2). * - EP-01 / ERR-013: Object.hasOwn (not `in`) is used to check payload keys * on the parsed-unknown object (rc-5). * - EP-03 / ERR-002: write failures return { ok: false, error } so callers * can log an observable reason (rc-9). * - EP-03 / ERR-009, ERR-010: corrupt agent_draft JSON fails loud — * getUnconsumedByTaskId / listPending throw a structured Error rather than * silently returning a raw string masquerading as the original object * (rc-3-fail-loud-missing). * - EP-05 / ERR-015: insertPendingDraft reads fresh state via * SELECT ... WHERE consumed_at IS NULL immediately before the * INSERT/UPDATE decision, so concurrent writers see the latest row. */ import type { SqliteConnection } from '../store/sqlite-connection.js'; /** Agent-authored draft context attached to a failed task. */ export interface AgentDraftPayload { summary: string; observedFailure?: string; commandSummary?: string; } /** A row in the pending_agent_drafts table. agentDraft is typed (validated). */ export interface PendingAgentDraftRow { id: string; taskId: string; painId: string | null; agentDraft: AgentDraftPayload; createdAt: string; consumedAt: string | null; } /** Result of an insert or markConsumed operation. */ export type PendingDraftOpResult = { ok: true; id: string; } | { ok: false; error: string; }; export declare class PendingAgentDraftStore { private readonly connection; constructor(connection: SqliteConnection); /** * Insert a pending agent draft for a task, or UPDATE the existing * unconsumed draft if one already exists for the same taskId (idempotent). * * On UPDATE, the row's `id` is preserved; agent_draft / created_at / pain_id * are overwritten with the new values. This honors the spec: "如果 taskId 已有 * 未消费行(consumed_at IS NULL),UPDATE 该行的 agent_draft / created_at / * pain_id,不创建新行". * * Returns `{ ok: true, id }` on success (id is the row's PRIMARY KEY, * either the existing one for an UPDATE or a fresh `pad-` id for an INSERT). */ insertPendingDraft(input: { taskId: string; painId?: string; agentDraft: AgentDraftPayload; }): PendingDraftOpResult; /** * Get the unconsumed draft for a task, or null if none. * Fails loud (rc-3) when agent_draft is corrupt JSON — throws a structured * Error so callers can observe the corruption instead of silently receiving * a raw string masquerading as the original object. */ getUnconsumedByTaskId(taskId: string): PendingAgentDraftRow | null; /** * Mark a draft as consumed (set consumed_at = now). * Returns `{ ok: true }` whether or not the row exists — this is the * explicit "ok: true 静默" behavior called out in the task spec for * markConsumed on a non-existent id. The reasoning: markConsumed is called * from the feedback-report success path as a cleanup step; a missing row * (already consumed, never existed, or concurrently deleted) is not an * error condition the caller can act on. */ markConsumed(id: string): PendingDraftOpResult; /** * List pending (unconsumed) drafts, most recent first. * Fails loud (rc-3) on corrupt agent_draft JSON. */ listPending(filter?: { limit?: number; }): PendingAgentDraftRow[]; } //# sourceMappingURL=pending-agent-draft-store.d.ts.map