{"version":3,"file":"resume.mjs","names":[],"sources":["../../../../../../../ai/src/orchestrator/resume.ts"],"sourcesContent":["import type { OrchestratorResumeOptions } from \"../contracts/orchestrator/orchestrator-execute-options.type\";\nimport type {\n  OrchestratorReport,\n  OrchestratorReportStatus,\n  OrchestratorResult,\n  TurnSnapshot,\n} from \"../contracts/result/orchestrator-result.type\";\nimport type { BaseReport } from \"../contracts/result/base-report.type\";\nimport { summarizeRoute } from \"./checkpoint\";\nimport { deriveRunId, dispatchTurn } from \"./dispatch\";\nimport type { OrchestratorEngineContext } from \"./engine-context.type\";\nimport { loadSession } from \"./load\";\n\n/**\n * Hooks the resume protocol borrows from the execution module so it can\n * reuse the drift check, report assembly, status mapping, terminal\n * event emission, and checkpoint persistence without a circular import\n * (execution.ts owns those; resume.ts is called by it).\n */\nexport type ResumeHooks = {\n  assertNoDrift(loadedSignature: string | undefined): void;\n  buildReport(\n    turnIndex: number,\n    status: OrchestratorReportStatus,\n    turnSnapshot: TurnSnapshot | undefined,\n    childReport: BaseReport | undefined,\n  ): OrchestratorReport;\n  deriveStatus(childStatus: BaseReport[\"status\"]): OrchestratorReportStatus;\n  emitTerminal(turnIndex: number, status: OrchestratorReportStatus): void;\n  persist(\n    turnIndex: number,\n    state: unknown,\n    lastRoute: string | string[] | null,\n    summarizedThrough: number | null,\n  ): Promise<unknown>;\n};\n\n/**\n * §9 resume protocol. Detects and drains an interrupted `iterate: true`\n * turn:\n *\n * 1. Load the latest checkpoint (§9.1 step 1).\n * 2. Compute the candidate `runId` for the NEXT turn — the one that was\n *    in flight — `${sessionId}.${version}.${turn_index + 1}` (§9.1 step 2).\n * 3. Load the supervisor snapshot for that runId (§9.1 step 3).\n * 4. If a still-`running` snapshot exists, resume the supervisor, persist\n *    a fresh checkpoint for `turn_index + 1`, and return the result\n *    (§9.1 step 4).\n * 5. Otherwise return `null` — nothing in flight; the caller proceeds to\n *    `execute()` normally (§9.1 step 5, §9.3 drain idempotency).\n *\n * Runs the same Phase 2 drift check as `execute()` (§9.4). Resume is a\n * no-op for `iterate: false` orchestrators (no SnapshotStore, nothing to\n * resume) — it returns `null`.\n */\nexport async function resolveResume<TOutput, TState>(\n  ctx: OrchestratorEngineContext<TOutput, TState>,\n  sessionId: string,\n  options: OrchestratorResumeOptions | undefined,\n  hooks: ResumeHooks,\n): Promise<OrchestratorResult<TOutput> | null> {\n  if (!ctx.snapshotStore || ctx.config.iterate !== true) {\n    return null;\n  }\n\n  const loaded = await loadSession(ctx, sessionId);\n\n  // Drift guard — same as execute() (§9.4).\n  hooks.assertNoDrift(loaded.record?.signature);\n\n  // The in-flight turn is the one AFTER the last settled checkpoint.\n  const resumedTurnIndex = (loaded.record?.turn_index ?? -1) + 1;\n  const runId = deriveRunId(sessionId, ctx.config.version, resumedTurnIndex);\n\n  const snapshot = await ctx.snapshotStore.load(runId);\n\n  if (!snapshot || snapshot.status !== \"running\") {\n    return null;\n  }\n\n  ctx.emitter.emit(\"orchestrator.turn.starting\", {\n    sessionId,\n    turnIndex: resumedTurnIndex,\n  });\n\n  // Re-dispatch: dispatchTurn detects the in-flight snapshot for this\n  // runId and calls supervisor.resume() rather than execute() (§5 step 5).\n  const { result, state, turnSnapshot } = await dispatchTurn<TOutput, TState>({\n    ctx,\n    sessionId,\n    input: snapshot.input,\n    seedState: loaded.state,\n    turnIndex: resumedTurnIndex,\n    history: [],\n    context: options?.context,\n    signal: options?.signal,\n  });\n\n  const status = result.error\n    ? hooks.deriveStatus(result.report.status)\n    : \"awaiting-input\";\n\n  if (result.error) {\n    const report = hooks.buildReport(\n      resumedTurnIndex,\n      status,\n      turnSnapshot,\n      result.report,\n    );\n\n    hooks.emitTerminal(resumedTurnIndex, status);\n\n    return {\n      data: result.data,\n      error: result.error,\n      usage: result.usage,\n      report,\n      sessionId,\n      turnIndex: resumedTurnIndex,\n    };\n  }\n\n  // Finalize: persist a fresh checkpoint for the resumed turn (§9.1 step 4).\n  await hooks.persist(\n    resumedTurnIndex,\n    state,\n    summarizeRoute(turnSnapshot.decision.raw as never),\n    loaded.record?.summarized_through ?? null,\n  );\n\n  const report = hooks.buildReport(\n    resumedTurnIndex,\n    \"awaiting-input\",\n    turnSnapshot,\n    result.report,\n  );\n\n  hooks.emitTerminal(resumedTurnIndex, \"awaiting-input\");\n\n  return {\n    data: result.data,\n    error: undefined,\n    usage: result.usage,\n    report,\n    sessionId,\n    turnIndex: resumedTurnIndex,\n  };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAuDA,eAAsB,cACpB,KACA,WACA,SACA,OAC6C;CAC7C,IAAI,CAAC,IAAI,iBAAiB,IAAI,OAAO,YAAY,MAC/C,OAAO;CAGT,MAAM,SAAS,MAAM,YAAY,KAAK,SAAS;CAG/C,MAAM,cAAc,OAAO,QAAQ,SAAS;CAG5C,MAAM,oBAAoB,OAAO,QAAQ,cAAc,MAAM;CAC7D,MAAM,QAAQ,YAAY,WAAW,IAAI,OAAO,SAAS,gBAAgB;CAEzE,MAAM,WAAW,MAAM,IAAI,cAAc,KAAK,KAAK;CAEnD,IAAI,CAAC,YAAY,SAAS,WAAW,WACnC,OAAO;CAGT,IAAI,QAAQ,KAAK,8BAA8B;EAC7C;EACA,WAAW;CACb,CAAC;CAID,MAAM,EAAE,QAAQ,OAAO,iBAAiB,MAAM,aAA8B;EAC1E;EACA;EACA,OAAO,SAAS;EAChB,WAAW,OAAO;EAClB,WAAW;EACX,SAAS,CAAC;EACV,SAAS,SAAS;EAClB,QAAQ,SAAS;CACnB,CAAC;CAED,MAAM,SAAS,OAAO,QAClB,MAAM,aAAa,OAAO,OAAO,MAAM,IACvC;CAEJ,IAAI,OAAO,OAAO;EAChB,MAAM,SAAS,MAAM,YACnB,kBACA,QACA,cACA,OAAO,MACT;EAEA,MAAM,aAAa,kBAAkB,MAAM;EAE3C,OAAO;GACL,MAAM,OAAO;GACb,OAAO,OAAO;GACd,OAAO,OAAO;GACd;GACA;GACA,WAAW;EACb;CACF;CAGA,MAAM,MAAM,QACV,kBACA,OACA,eAAe,aAAa,SAAS,GAAY,GACjD,OAAO,QAAQ,sBAAsB,IACvC;CAEA,MAAM,SAAS,MAAM,YACnB,kBACA,kBACA,cACA,OAAO,MACT;CAEA,MAAM,aAAa,kBAAkB,gBAAgB;CAErD,OAAO;EACL,MAAM,OAAO;EACb,OAAO;EACP,OAAO,OAAO;EACd;EACA;EACA,WAAW;CACb;AACF"}