{"version":3,"file":"resume.mjs","names":[],"sources":["../../../../../../../ai/src/human/resume.ts"],"sourcesContent":["import type {\n  ApprovalDecision,\n  PendingInterrupt,\n  ResumeOptions,\n  ResumeResult,\n} from \"./contracts\";\nimport { clearSeededDecision, seedDecision } from \"./resume-seed\";\n\n/**\n * Validate that a decision is a well-formed {@link ApprovalDecision}.\n *\n * `ai.human.resume(...)` is a public, out-of-process entry point — a\n * webhook can hand it anything. Guard the closed `type` union (and the\n * per-variant required fields) before applying it, so a malformed payload\n * fails loudly here rather than silently mis-driving the re-run.\n */\nfunction assertDecision(decision: ApprovalDecision): void {\n  if (decision.type === \"approve\") {\n    return;\n  }\n\n  if (decision.type === \"reject\") {\n    if (typeof decision.reason !== \"string\") {\n      throw new TypeError(\n        \"ai.human.resume: a 'reject' decision requires a string 'reason'.\",\n      );\n    }\n\n    return;\n  }\n\n  if (decision.type === \"edit\") {\n    if (!(\"args\" in decision)) {\n      throw new TypeError(\n        \"ai.human.resume: an 'edit' decision requires replacement 'args'.\",\n      );\n    }\n\n    return;\n  }\n\n  throw new TypeError(\n    `ai.human.resume: unknown decision type '${(decision as { type: string }).type}'. Expected one of: approve, reject, edit.`,\n  );\n}\n\n/**\n * Re-run the agent for a resumed interrupt with the decision pre-seeded.\n *\n * Stages the decision in the process-local seed registry (keyed by agent\n * name), then re-executes the original prompt. The agent's\n * `ai.human.approval(...)` middleware consumes the seed on the gated tool\n * call — so this time it resolves to the human's ruling instead of pausing\n * again. The seed is cleared in a `finally` so a re-run that errors before\n * the gated call never leaks a stale seed into a later run.\n */\nasync function rerun<TOutput>(\n  pending: PendingInterrupt,\n  decision: ApprovalDecision,\n  options: ResumeOptions<TOutput>,\n): Promise<ResumeResult<TOutput>> {\n  const { agent } = options;\n\n  // `agent` is guaranteed by the caller (this is only reached on the\n  // re-run path), but narrow for the type system.\n  if (!agent) {\n    return { type: \"applied\", interruptId: pending.interruptId, decision };\n  }\n\n  const input = options.input ?? pending.request.context.originalInput ?? \"\";\n\n  seedDecision(agent.name, decision);\n\n  try {\n    const result = await agent.execute(input, options.executeOptions);\n\n    return {\n      type: \"applied\",\n      interruptId: pending.interruptId,\n      decision,\n      result,\n    };\n  } finally {\n    // If the seeded call never fired (the re-run errored early, or the\n    // policy no longer gates the tool), drop the stale seed so it cannot\n    // leak into an unrelated later run of the same agent.\n    clearSeededDecision(agent.name);\n  }\n}\n\n/**\n * Apply a human's decision to a persisted interrupt — the out-of-process\n * resume entry point behind `ai.human.resume(interruptId, decision, options)`.\n *\n * **Durable v1 model — re-run, not mid-supervisor suspend.** This loads the\n * {@link PendingInterrupt} from `options.store`, validates the decision,\n * deletes the pending record, and (when an `agent` is supplied) re-executes\n * the original turn with the decision **pre-seeded**, so the gated tool call\n * resolves to the ruling instead of pausing again. It does **not** rehydrate\n * an in-flight supervisor — that is the deferred v2 lift.\n *\n * **Idempotent.** A second resume of an already-resolved (deleted) or\n * never-raised interrupt is a no-op: it returns `{ type: \"already-resolved\" }`\n * without re-applying the decision or re-running the turn — mirroring the\n * orchestrator resume's drain idempotency. The record is deleted **before**\n * the re-run, so even a re-run that itself raises a fresh interrupt cannot\n * collide with the one being resolved.\n *\n * **Two shapes** (see {@link ResumeOptions}):\n * - **apply-only** — omit `agent`: load, validate, delete, return\n *   `{ type: \"applied\", decision }` for a caller-owned re-drive.\n * - **re-run** — pass `agent`: additionally re-execute the turn; the\n *   {@link import(\"@warlock.js/ai\").AgentResult} rides `result.result`.\n *\n * @param interruptId - Id of the persisted interrupt to resolve.\n * @param decision - The human's ruling (approve / reject / edit).\n * @param options - The durable `store` (required) plus optional re-run\n *   `agent` / `input` / `executeOptions`.\n * @returns A {@link ResumeResult} — `\"applied\"` or idempotent\n *   `\"already-resolved\"`.\n *\n * @example\n * // Process B (webhook, hours later) — apply-only:\n * const outcome = await ai.human.resume(interruptId, { type: \"reject\", reason: \"Out of policy\" }, {\n *   store,\n * });\n *\n * @example\n * // Re-run the turn with the decision pre-seeded:\n * const outcome = await ai.human.resume(interruptId, { type: \"edit\", args: { amount: 5 } }, {\n *   store,\n *   agent: support,\n * });\n * if (outcome.type === \"applied\" && outcome.result) {\n *   console.log(outcome.result.text);\n * }\n */\nexport async function resume<TOutput = unknown>(\n  interruptId: string,\n  decision: ApprovalDecision,\n  options: ResumeOptions<TOutput>,\n): Promise<ResumeResult<TOutput>> {\n  assertDecision(decision);\n\n  const { store } = options;\n  const pending = await store.load(interruptId);\n\n  // No live interrupt — already resolved + deleted, or never raised. Never\n  // double-apply; never re-run. Idempotent no-op.\n  if (pending === undefined || pending.status !== \"pending\") {\n    return { type: \"already-resolved\", interruptId };\n  }\n\n  // Resolve + delete BEFORE the re-run so a re-run that itself raises a new\n  // interrupt cannot collide with the one being resolved, and a concurrent\n  // resume of the same id sees it gone.\n  await store.delete(interruptId);\n\n  if (!options.agent) {\n    return { type: \"applied\", interruptId, decision };\n  }\n\n  return rerun(pending, decision, options);\n}\n"],"mappings":";;;;;;;;;;;AAgBA,SAAS,eAAe,UAAkC;CACxD,IAAI,SAAS,SAAS,WACpB;CAGF,IAAI,SAAS,SAAS,UAAU;EAC9B,IAAI,OAAO,SAAS,WAAW,UAC7B,MAAM,IAAI,UACR,kEACF;EAGF;CACF;CAEA,IAAI,SAAS,SAAS,QAAQ;EAC5B,IAAI,EAAE,UAAU,WACd,MAAM,IAAI,UACR,kEACF;EAGF;CACF;CAEA,MAAM,IAAI,UACR,2CAA4C,SAA8B,KAAK,2CACjF;AACF;;;;;;;;;;;AAYA,eAAe,MACb,SACA,UACA,SACgC;CAChC,MAAM,EAAE,UAAU;CAIlB,IAAI,CAAC,OACH,OAAO;EAAE,MAAM;EAAW,aAAa,QAAQ;EAAa;CAAS;CAGvE,MAAM,QAAQ,QAAQ,SAAS,QAAQ,QAAQ,QAAQ,iBAAiB;CAExE,aAAa,MAAM,MAAM,QAAQ;CAEjC,IAAI;EACF,MAAM,SAAS,MAAM,MAAM,QAAQ,OAAO,QAAQ,cAAc;EAEhE,OAAO;GACL,MAAM;GACN,aAAa,QAAQ;GACrB;GACA;EACF;CACF,UAAU;EAIR,oBAAoB,MAAM,IAAI;CAChC;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiDA,eAAsB,OACpB,aACA,UACA,SACgC;CAChC,eAAe,QAAQ;CAEvB,MAAM,EAAE,UAAU;CAClB,MAAM,UAAU,MAAM,MAAM,KAAK,WAAW;CAI5C,IAAI,YAAY,UAAa,QAAQ,WAAW,WAC9C,OAAO;EAAE,MAAM;EAAoB;CAAY;CAMjD,MAAM,MAAM,OAAO,WAAW;CAE9B,IAAI,CAAC,QAAQ,OACX,OAAO;EAAE,MAAM;EAAW;EAAa;CAAS;CAGlD,OAAO,MAAM,SAAS,UAAU,OAAO;AACzC"}