import type { NativeSession } from "../../vendor/paperclip-runner/index.js"; type CredentialTurn = { /** Legacy turn copy-back; event-backed bindings need no per-turn read. */ copyBack: () => Promise; /** Remove launch credentials after the provider owner has stopped. */ remove: () => Promise; /** Save the final refresh directly through durable authority before removal. */ retire?: () => Promise; retirement?: Promise; detached: boolean; closing?: boolean; completed: boolean; completion?: Promise; removed?: boolean; }; const credentialTurns = new WeakMap(); /** A warm session outlives an invocation's temporary credential destination. */ export function bindManagedNativeCredentialTurn( session: NativeSession, callbacks: Pick, ): NativeSession { const previous = credentialTurns.get(session); // A successor controller must attach a new handle. Reusing a detached or // closing handle would let old references close the successor's provider. if (previous?.detached || previous?.closing) { throw new Error("managed_native_credential_session_retired"); } if (previous && !previous.completed) { throw new Error("managed_native_credential_turn_still_owned"); } const turn: CredentialTurn = { ...callbacks, detached: false, completed: false }; credentialTurns.set(session, turn); if (previous) return session; const close = session.close.bind(session); const detach = session.detachControllerForRestart?.bind(session); if (detach) { session.detachControllerForRestart = async () => { // Set before yielding: a late close from this controller cannot touch // credentials retained by the replacement controller. const owner = credentialTurns.get(session); if (owner) owner.detached = true; await detach(); }; } session.close = async (input) => { const owner = credentialTurns.get(session); if (owner?.detached) return; if (owner) owner.closing = true; await close(input); if (!owner || owner.detached || credentialTurns.get(session) !== owner) return; if (owner.retire && !owner.removed) { owner.retirement ??= owner.retire().catch(error => { owner.retirement = undefined; throw error; }); await owner.retirement; } try { await completeCredentialTurn(owner); } finally { // Idle expiry removes the provider credential, but never writes back to // the already-deleted temporary home of a completed invocation. if (!owner.detached && !owner.removed && credentialTurns.get(session) === owner) { owner.removed = true; await owner.remove(); } } }; return session; } async function completeCredentialTurn(turn: CredentialTurn) { if (turn.detached || turn.completed) return; turn.completion ??= turn.copyBack().finally(() => { turn.completed = true; }); await turn.completion; } /** Complete a turn without closing the provider; event-backed bindings flush through PRP. */ export async function completeManagedNativeCredentialTurn(session: NativeSession | undefined) { if (!session) return; const turn = credentialTurns.get(session); if (turn) await completeCredentialTurn(turn); }