{"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/services/reconcile/index.ts"],"sourcesContent":["import { isDelegationActive, type Task, type TaskDocument } from '../../models/task';\nimport { isProcessAlive } from '../processLiveness';\n\nexport const ERR_ORPHANED_BY_RESTART = 'Delegation orphaned by harness restart';\nexport const ERR_ORPHANED_BY_SESSION = 'Delegation orphaned by session restart';\n\nexport interface ReconcileResult {\n  document: TaskDocument;\n  orphaned: Task[];\n}\n\n/** The delegations this process can still settle, used to spot its own leftovers. */\nexport interface ReconcileSelf {\n  pid: number;\n  liveRequestIds: ReadonlySet<string>;\n}\n\n/**\n * Recover tasks that no longer have anyone able to finish them.\n *\n * A subagent run dies with the harness that spawned it, but the store file\n * outlives both, so a crash leaves tasks stuck in `in_progress` forever. Two\n * kinds of delegation qualify as dead:\n *\n * - the owning pid is gone, so nothing is left to report a result;\n * - the owning pid is *this* process, yet the request is absent from the live\n *   set. Settling requires an in-memory record, so an in-process session\n *   restart (which clears that map without writing to the store) leaves rows\n *   nothing can ever resolve. Checking pid liveness alone misses these, because\n *   the pid is our own and very much alive.\n *\n * Delegations owned by a different, still-running pid belong to a parallel\n * session and are left untouched.\n */\nexport function reconcileOrphanedDelegations(\n  document: TaskDocument,\n  now: string = new Date().toISOString(),\n  isAlive: (pid: number) => boolean = isProcessAlive,\n  self?: ReconcileSelf,\n): ReconcileResult {\n  const orphaned: Task[] = [];\n\n  const tasks = document.tasks.map((task) => {\n    if (!isDelegationActive(task)) return task;\n    const pid = task.delegation?.pid;\n    if (pid === undefined) return task;\n\n    const ownedByThisSession = self !== undefined && pid === self.pid;\n    const abandonedHere = ownedByThisSession && !self.liveRequestIds.has(task.delegation!.requestId);\n    if (!abandonedHere && isAlive(pid)) return task;\n\n    const recovered: Task = {\n      ...task,\n      status: 'pending',\n      updatedAt: now,\n      delegation: {\n        ...task.delegation!,\n        state: 'failed',\n        endedAt: now,\n        result: {\n          status: 'failed',\n          error: abandonedHere ? ERR_ORPHANED_BY_SESSION : ERR_ORPHANED_BY_RESTART,\n        },\n      },\n    };\n    orphaned.push(recovered);\n    return recovered;\n  });\n\n  if (orphaned.length === 0) return { document, orphaned };\n  return { document: { ...document, tasks }, orphaned };\n}\n"],"mappings":";;;AAGA,MAAa,0BAA0B;AACvC,MAAa,0BAA0B;;;;;;;;;;;;;;;;;;AA8BvC,SAAgB,6BACd,UACA,uBAAc,IAAI,KAAK,EAAA,CAAE,YAAY,GACrC,UAAoC,gBACpC,MACiB;CACjB,MAAM,WAAmB,CAAC;CAE1B,MAAM,QAAQ,SAAS,MAAM,KAAK,SAAS;EACzC,IAAI,CAAC,mBAAmB,IAAI,GAAG,OAAO;EACtC,MAAM,MAAM,KAAK,YAAY;EAC7B,IAAI,QAAQ,KAAA,GAAW,OAAO;EAG9B,MAAM,gBADqB,SAAS,KAAA,KAAa,QAAQ,KAAK,OAClB,CAAC,KAAK,eAAe,IAAI,KAAK,WAAY,SAAS;EAC/F,IAAI,CAAC,iBAAiB,QAAQ,GAAG,GAAG,OAAO;EAE3C,MAAM,YAAkB;GACtB,GAAG;GACH,QAAQ;GACR,WAAW;GACX,YAAY;IACV,GAAG,KAAK;IACR,OAAO;IACP,SAAS;IACT,QAAQ;KACN,QAAQ;KACR,OAAO,gBAAgB,0BAA0B;IACnD;GACF;EACF;EACA,SAAS,KAAK,SAAS;EACvB,OAAO;CACT,CAAC;CAED,IAAI,SAAS,WAAW,GAAG,OAAO;EAAE;EAAU;CAAS;CACvD,OAAO;EAAE,UAAU;GAAE,GAAG;GAAU;EAAM;EAAG;CAAS;AACtD"}