@athenaintel/react 0.56.0 → 0.57.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/README.md +75 -1
  2. package/dist/agent-runtime/src/inbound-channel.cjs +96 -0
  3. package/dist/agent-runtime/src/inbound-channel.cjs.map +1 -0
  4. package/dist/agent-runtime/src/inbound-channel.d.ts +75 -0
  5. package/dist/agent-runtime/src/inbound-channel.js +96 -0
  6. package/dist/agent-runtime/src/inbound-channel.js.map +1 -0
  7. package/dist/agent-runtime/src/index.d.ts +2 -1
  8. package/dist/agent-runtime/src/interrupts.cjs +45 -3
  9. package/dist/agent-runtime/src/interrupts.cjs.map +1 -1
  10. package/dist/agent-runtime/src/interrupts.d.ts +51 -4
  11. package/dist/agent-runtime/src/interrupts.js +46 -4
  12. package/dist/agent-runtime/src/interrupts.js.map +1 -1
  13. package/dist/agent-runtime/src/offline-status.cjs.map +1 -1
  14. package/dist/agent-runtime/src/offline-status.d.ts +25 -0
  15. package/dist/agent-runtime/src/offline-status.js.map +1 -1
  16. package/dist/agent-runtime/src/thread-handoff-flow-machine.d.ts +8 -8
  17. package/dist/agent-runtime/src/tool-metadata.cjs +8 -0
  18. package/dist/agent-runtime/src/tool-metadata.cjs.map +1 -1
  19. package/dist/agent-runtime/src/tool-metadata.js +8 -0
  20. package/dist/agent-runtime/src/tool-metadata.js.map +1 -1
  21. package/dist/auth/AthenaSSOProvider.cjs +54 -12
  22. package/dist/auth/AthenaSSOProvider.cjs.map +1 -1
  23. package/dist/auth/AthenaSSOProvider.d.ts +23 -2
  24. package/dist/auth/AthenaSSOProvider.js +54 -12
  25. package/dist/auth/AthenaSSOProvider.js.map +1 -1
  26. package/dist/auth/index.cjs +2 -0
  27. package/dist/auth/index.cjs.map +1 -1
  28. package/dist/auth/index.d.ts +1 -0
  29. package/dist/auth/index.js +2 -0
  30. package/dist/auth/index.js.map +1 -1
  31. package/dist/auth/sign-back-in.cjs +141 -0
  32. package/dist/auth/sign-back-in.cjs.map +1 -0
  33. package/dist/auth/sign-back-in.d.ts +117 -0
  34. package/dist/auth/sign-back-in.js +141 -0
  35. package/dist/auth/sign-back-in.js.map +1 -0
  36. package/dist/chat/AthenaChat.cjs +21 -3
  37. package/dist/chat/AthenaChat.cjs.map +1 -1
  38. package/dist/chat/AthenaChat.js +21 -3
  39. package/dist/chat/AthenaChat.js.map +1 -1
  40. package/dist/chat/StatewireApprovalCard.cjs +6 -2
  41. package/dist/chat/StatewireApprovalCard.cjs.map +1 -1
  42. package/dist/chat/StatewireApprovalCard.d.ts +8 -1
  43. package/dist/chat/StatewireApprovalCard.js +7 -3
  44. package/dist/chat/StatewireApprovalCard.js.map +1 -1
  45. package/dist/chat-ui/index.cjs +5 -0
  46. package/dist/chat-ui/index.cjs.map +1 -1
  47. package/dist/chat-ui/index.d.ts +1 -0
  48. package/dist/chat-ui/index.js +6 -1
  49. package/dist/chat-ui/index.js.map +1 -1
  50. package/dist/chat-ui/messages/inbound-channel-message.cjs +242 -0
  51. package/dist/chat-ui/messages/inbound-channel-message.cjs.map +1 -0
  52. package/dist/chat-ui/messages/inbound-channel-message.d.ts +53 -0
  53. package/dist/chat-ui/messages/inbound-channel-message.js +242 -0
  54. package/dist/chat-ui/messages/inbound-channel-message.js.map +1 -0
  55. package/dist/chat-ui/messages/inbound-channel-reader.cjs +184 -0
  56. package/dist/chat-ui/messages/inbound-channel-reader.cjs.map +1 -0
  57. package/dist/chat-ui/messages/inbound-channel-reader.d.ts +65 -0
  58. package/dist/chat-ui/messages/inbound-channel-reader.js +184 -0
  59. package/dist/chat-ui/messages/inbound-channel-reader.js.map +1 -0
  60. package/dist/chat-ui/messages/message-components.cjs +32 -15
  61. package/dist/chat-ui/messages/message-components.cjs.map +1 -1
  62. package/dist/chat-ui/messages/message-components.d.ts +7 -0
  63. package/dist/chat-ui/messages/message-components.js +33 -16
  64. package/dist/chat-ui/messages/message-components.js.map +1 -1
  65. package/dist/composer/sign-back-in-draft.cjs +62 -0
  66. package/dist/composer/sign-back-in-draft.cjs.map +1 -0
  67. package/dist/composer/sign-back-in-draft.d.ts +34 -0
  68. package/dist/composer/sign-back-in-draft.js +62 -0
  69. package/dist/composer/sign-back-in-draft.js.map +1 -0
  70. package/dist/diagnostics/athena-diagnostics.cjs.map +1 -1
  71. package/dist/diagnostics/athena-diagnostics.d.ts +6 -1
  72. package/dist/diagnostics/athena-diagnostics.js.map +1 -1
  73. package/dist/index.cjs +9 -0
  74. package/dist/index.cjs.map +1 -1
  75. package/dist/index.d.ts +2 -0
  76. package/dist/index.js +9 -0
  77. package/dist/index.js.map +1 -1
  78. package/dist/provider/AthenaProvider.cjs +48 -3
  79. package/dist/provider/AthenaProvider.cjs.map +1 -1
  80. package/dist/provider/AthenaProvider.js +49 -4
  81. package/dist/provider/AthenaProvider.js.map +1 -1
  82. package/dist/react/package.json.cjs +1 -1
  83. package/dist/react/package.json.js +1 -1
  84. package/dist/runtime/auth.cjs +11 -0
  85. package/dist/runtime/auth.cjs.map +1 -1
  86. package/dist/runtime/auth.d.ts +18 -1
  87. package/dist/runtime/auth.js +11 -0
  88. package/dist/runtime/auth.js.map +1 -1
  89. package/dist/styles.css +1 -1
  90. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"interrupts.cjs","sources":["../../../../agent-runtime/src/interrupts.ts"],"sourcesContent":["/**\n * Human-in-the-loop interrupt primitives shared by every statewire chat\n * surface (Olympus, the React SDK, the Office add-in, the Chrome extension,\n * mobile).\n *\n * The agora deep-agent runtime parks a gated run on an `interrupt` input\n * request whose payload carries the Athena `hitl_approval` shape\n * (`athena_deep_agent/hitl_bridge.py`). A surface answers it with\n * `{ type: 'run/input', requestId, response: { type: 'resume', value } }` —\n * the exact command the backend's resume path consumes. This module holds the\n * payload contract (narrowing, resume values) and the shared resume-lock state\n * machine; rendering stays with each surface.\n *\n * Deliberately parameterized rather than self-wiring: these helpers never call\n * `useStatewireRuns`/`useAuiState` themselves. Each surface reads its own\n * statewire hooks (which its tests already mock) and passes the values in, so\n * the shared semantics cannot drift while per-surface wiring stays testable.\n */\n\nimport type { StatewireThread } from '@assistant-ui/react-statewire';\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport { hasClientToolInterruptSource } from './client-tools';\nimport { statewireTransportOutcome } from './errors';\nimport { readDeepAgentStreamValues } from './statewire-state';\n\n/** Human-in-the-loop approval payload the agora deep-agent runtime parks a run\n * on (shaped by `athena_deep_agent/hitl_bridge.py`). */\nexport interface HitlApprovalPayload {\n source: string;\n type: string;\n message: string;\n /** Risk headline/facts/warnings, when the host includes them. */\n approval_context?: unknown;\n context?: {\n tool_name?: string;\n tool_id?: string;\n tool_call_id?: string;\n tool_args?: Record<string, unknown>;\n pending_action_count?: number;\n approval_context?: unknown;\n /**\n * Every pending gated call (name + args), in decision order. Absent on\n * older backends — fall back to the single `tool_name`/`tool_args` pair.\n * Per-dispatch payloads also carry each lane's `tool_call_id` (the lane\n * card key) and `interrupt_id` (the lane resume key).\n */\n action_requests?: {\n name?: string;\n args?: Record<string, unknown>;\n tool_call_id?: string;\n interrupt_id?: string;\n /**\n * True when \"approve all for this session\" would take effect for this\n * tool (the workspace allows overriding its approval). Absent on older\n * backends and on non-overridable tools: the option is not offered.\n */\n session_approval_allowed?: boolean;\n /**\n * The decision types the parked gate accepts for this call (`approve`,\n * `reject`, `edit`). Absent on older backends: only approve / reject\n * are known to be accepted.\n */\n allowed_decisions?: string[];\n /** What the backend resolved about this call before asking (display-only). */\n approval_context?: unknown;\n }[];\n /**\n * True when each dispatch parked on its own interrupt: decisions submit\n * per lane via `lane_decisions` and each approved lane starts immediately,\n * instead of the legacy batch where one resume must answer every lane.\n */\n per_dispatch?: boolean;\n };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/** A pending `interrupt` input request; a present `response` key marks it answered. */\nexport function isPendingInterrupt(\n request: StatewireThread.InputRequestState,\n): request is StatewireThread.CustomInputRequest {\n return request.type === 'interrupt' && !('response' in request);\n}\n\n/**\n * Interrupt sources that were system pauses before the host stamped\n * `requires_user_action: false` on them (a background deep-research wait). Kept\n * only for the deploy skew between an older agora and a newer client; delete\n * once every deployment's raiser stamps the flag.\n */\nconst LEGACY_SYSTEM_PAUSE_INTERRUPT_SOURCES: ReadonlySet<string> = new Set(['deep_research_pending']);\n\n/**\n * A park nobody answers: the agent is still working (a background research\n * wait) and the backend resumes it itself. The raiser stamps\n * `requires_user_action: false`; older raisers are recognised by source.\n */\nexport function isSystemPauseInterrupt(payload: unknown): boolean {\n if (!isRecord(payload)) return false;\n if (payload.requires_user_action === false) return true;\n return (\n typeof payload.source === 'string' && LEGACY_SYSTEM_PAUSE_INTERRUPT_SOURCES.has(payload.source)\n );\n}\n\n/**\n * A pending interrupt an approval card may answer. Two kinds are excluded —\n * client-tool parks (the client-tool bridge owns those, and a generic resume\n * would corrupt the parked tool call) and system pauses (nothing to answer; the\n * backend resumes them). The source checks are deliberately loose so a skewed\n * deploy can never route one here.\n */\nexport function isApprovalCardInterrupt(\n request: StatewireThread.InputRequestState,\n): request is StatewireThread.CustomInputRequest {\n if (!isPendingInterrupt(request)) return false;\n const payload = (request as { payload?: unknown }).payload;\n return !hasClientToolInterruptSource(payload) && !isSystemPauseInterrupt(payload);\n}\n\n/** Narrow an interrupt payload to the Athena HITL approval shape. */\nexport function asHitlApproval(value: unknown): HitlApprovalPayload | null {\n if (!isRecord(value)) return null;\n return value.type === 'hitl' &&\n value.source === 'hitl_approval' &&\n typeof value.message === 'string'\n ? (value as unknown as HitlApprovalPayload)\n : null;\n}\n\n/** Default paused-run copy when an interrupt payload carries no message. */\nexport const DEFAULT_INTERRUPT_PAUSED_MESSAGE =\n 'The agent is paused and waiting for input.';\n\n/** Best-effort human-readable message from any interrupt payload. */\nexport function readInterruptMessage(\n value: unknown,\n fallback: string = DEFAULT_INTERRUPT_PAUSED_MESSAGE,\n): string {\n if (isRecord(value) && typeof value.message === 'string' && value.message.length > 0) {\n return value.message;\n }\n return fallback;\n}\n\n/** One label/value row of a call's resolved approval facts. */\nexport interface PendingApprovalFact {\n label: string;\n value: string;\n}\n\n/** One caution of a call's resolved approval facts. */\nexport interface PendingApprovalWarning {\n severity: 'info' | 'warning' | 'critical';\n message: string;\n}\n\nexport interface PendingApprovalSlackTarget {\n catalog_id: string;\n conversation_id: string | null;\n thread_ts: string | null;\n team_url: string | null;\n people: string[];\n}\n\n/**\n * What the backend resolved about a gated call before asking — e.g. where a\n * Slack write goes and as whom (agora `services/interrupts/approval_context.py`).\n * Display-only; the decision still answers the parked call.\n */\nexport interface PendingApprovalContext {\n facts: PendingApprovalFact[];\n warnings: PendingApprovalWarning[];\n /** The question the approval asks, in a line (\"Use Studio Mac in this conversation?\"). */\n headline?: string;\n /** What approving lets happen, a sentence or two under the headline. */\n summary?: string;\n slack_target?: PendingApprovalSlackTarget;\n}\n\nconst APPROVAL_WARNING_SEVERITIES = new Set(['info', 'warning', 'critical']);\n\nfunction isNullableString(value: unknown): value is string | null {\n return value === null || typeof value === 'string';\n}\n\n/** Display text: a string, trimmed; a blank or anything that is not a string is absent. */\nfunction displayText(value: unknown): string | undefined {\n if (typeof value !== 'string') return undefined;\n const text = value.trim();\n return text.length > 0 ? text : undefined;\n}\n\nfunction isStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.every((entry) => typeof entry === 'string');\n}\n\nfunction parsePendingApprovalSlackTarget(value: unknown): PendingApprovalSlackTarget | undefined {\n if (!isRecord(value) || typeof value.catalog_id !== 'string') return undefined;\n const conversationId = value.conversation_id;\n const threadTs = value.thread_ts;\n const teamUrl = value.team_url;\n const people = value.people === undefined ? [] : value.people;\n if (\n !isNullableString(conversationId) ||\n !isNullableString(threadTs) ||\n !isNullableString(teamUrl) ||\n !isStringArray(people)\n )\n return undefined;\n return {\n catalog_id: value.catalog_id,\n conversation_id: conversationId,\n thread_ts: threadTs,\n team_url: teamUrl,\n people,\n };\n}\n\n/** A call's `approval_context`, or `undefined` when absent or empty. */\nexport function parsePendingApprovalContext(value: unknown): PendingApprovalContext | undefined {\n if (!isRecord(value)) return undefined;\n const facts: PendingApprovalFact[] = Array.isArray(value.facts)\n ? value.facts.flatMap((fact) =>\n isRecord(fact) && typeof fact.label === 'string' && typeof fact.value === 'string'\n ? [{ label: fact.label, value: fact.value }]\n : [],\n )\n : [];\n const warnings: PendingApprovalWarning[] = Array.isArray(value.warnings)\n ? value.warnings.flatMap((warning) => {\n if (!isRecord(warning) || typeof warning.message !== 'string') return [];\n const severity =\n typeof warning.severity === 'string' && APPROVAL_WARNING_SEVERITIES.has(warning.severity)\n ? (warning.severity as PendingApprovalWarning['severity'])\n : 'info';\n return [{ severity, message: warning.message }];\n })\n : [];\n const headline = displayText(value.headline);\n const summary = displayText(value.summary);\n const slackTarget = parsePendingApprovalSlackTarget(value.slack_target);\n return facts.length > 0 || warnings.length > 0 || headline || summary || slackTarget\n ? {\n facts,\n warnings,\n ...(headline ? { headline } : {}),\n ...(summary ? { summary } : {}),\n ...(slackTarget ? { slack_target: slackTarget } : {}),\n }\n : undefined;\n}\n\nexport interface PendingActionRequest {\n name: string;\n args: Record<string, unknown>;\n /** The lane's `task` tool-call id (present on per-dispatch payloads). */\n toolCallId?: string;\n /** The lane's langgraph interrupt id — the `lane_decisions` resume key. */\n interruptId?: string;\n /** Present (true) when a session-wide approval would cover later calls of this tool. */\n sessionApprovalAllowed?: true;\n /** Present (true) when the gate accepts an `edit` decision for this call. */\n editAllowed?: true;\n /** Present when the backend resolved anything about this call before asking (facts, a headline). */\n approvalContext?: PendingApprovalContext;\n}\n\n/**\n * Every gated call this approval covers, in decision order. One resume answers\n * ALL of them, so every entry must stay reviewable somewhere. Falls back to\n * the single first-call fields on older backends without `action_requests`.\n */\nexport function pendingActionRequests(hitl: HitlApprovalPayload | null): PendingActionRequest[] {\n const context = hitl?.context;\n if (!context) return [];\n // The payload crosses the wire with only its top-level shape narrowed\n // (`asHitlApproval`), so the nested collection must be validated before any\n // array/property access: a malformed host value must degrade to \"no\n // reviewable entries\", never crash the approval surface.\n const requests: Record<string, unknown>[] = Array.isArray(context.action_requests)\n ? context.action_requests.filter(isRecord)\n : context.tool_name && context.tool_args\n ? [{ name: context.tool_name, args: context.tool_args, tool_call_id: context.tool_call_id }]\n : [];\n return requests.map((request) => {\n const approvalContext = parsePendingApprovalContext(request.approval_context);\n return {\n name: typeof request.name === 'string' ? request.name : 'tool',\n args: isRecord(request.args) ? request.args : {},\n ...(typeof request.tool_call_id === 'string' && { toolCallId: request.tool_call_id }),\n ...(typeof request.interrupt_id === 'string' && { interruptId: request.interrupt_id }),\n ...(request.session_approval_allowed === true && { sessionApprovalAllowed: true }),\n ...(Array.isArray(request.allowed_decisions) &&\n request.allowed_decisions.includes('edit') && { editAllowed: true }),\n ...(approvalContext && { approvalContext }),\n };\n });\n}\n\n/** The headline (and summary) that title a pending approval in place of the generic copy. */\nexport interface PendingApprovalTitle {\n headline: string;\n summary?: string;\n}\n\n/**\n * The title of a pending approval: the headline and summary of its one gated\n * call's resolved envelope (the call's own `approval_context`, else the copy\n * the host lifted to `context.approval_context`). A batch has none: one\n * decision answers every call, so no one call's headline may speak for it.\n * Display-only, like the envelope.\n */\nexport function pendingApprovalTitle({\n hitl,\n actionRequests,\n}: {\n hitl: HitlApprovalPayload | null;\n actionRequests: readonly PendingActionRequest[];\n}): PendingApprovalTitle | undefined {\n const [call, ...others] = actionRequests;\n if (hitl === null || call === undefined || others.length > 0) return undefined;\n // An older backend lists only the first call and counts the rest here; a\n // count that is not plainly one keeps the generic copy.\n const pendingCount = hitl.context?.pending_action_count;\n if (pendingCount !== undefined && pendingCount !== 1) return undefined;\n const envelope =\n call.approvalContext ?? parsePendingApprovalContext(hitl.context?.approval_context);\n if (envelope?.headline === undefined) return undefined;\n return envelope.summary === undefined\n ? { headline: envelope.headline }\n : { headline: envelope.headline, summary: envelope.summary };\n}\n\n/**\n * Whether the card may offer \"approve all for this session\": every pending\n * call must be one the backend stamped as session-approvable, so a batch that\n * mixes in a tool the workspace pinned as always-prompt only offers the\n * one-time answer.\n */\nexport function canApproveForSession(actionRequests: readonly PendingActionRequest[]): boolean {\n return (\n actionRequests.length > 0 &&\n actionRequests.every((request) => request.sessionApprovalAllowed === true)\n );\n}\n\n/** An interrupt a non-resume dispatch dropped, published by the host. */\nexport interface AbandonedInputRequest {\n id: string;\n type: string;\n}\n\n/**\n * `useStatewireState` selector body for the host-published abandoned input\n * requests. Reads through {@link readDeepAgentStreamValues} so the selector is\n * safe on the pre-attach (`undefined`) wire state, and returns the raw array\n * reference so the subscription stays shallow-stable — derive (`.find`) on the\n * result, never inside the selector.\n */\nexport function readAbandonedInputRequests(\n state: unknown,\n): readonly AbandonedInputRequest[] | undefined {\n const abandoned = readDeepAgentStreamValues(state).abandonedInputRequests;\n // Wire value: anything non-array (including a malformed host publication)\n // reads as \"none\" rather than crashing the consuming surface. Identity-\n // preserving for the valid case, so it stays selector-safe.\n return Array.isArray(abandoned)\n ? (abandoned as readonly AbandonedInputRequest[])\n : undefined;\n}\n\n/**\n * `useStatewireState` selector body for the host's mirrored interrupt\n * (`__interrupt__`, the LangGraph `{value, resumable}` wrapper). The mirror\n * is published the moment a lane parks on its gate — while sibling lanes are\n * still streaming and the run manager has NOT yet published an answerable\n * input request. Raw reference, selector-safe; unwrap with\n * {@link interruptMirrorPayload}.\n */\nexport function readInterruptMirror(state: unknown): unknown {\n return readDeepAgentStreamValues(state).__interrupt__;\n}\n\n/** The payload of a mirrored interrupt wrapper, or `undefined` for anything else. */\nexport function interruptMirrorPayload(mirror: unknown): unknown {\n return isRecord(mirror) ? mirror.value : undefined;\n}\n\n/**\n * `useStatewireState` selector body for the per-dispatch lane decisions the\n * host accepted through `run/lane-input` while the run was still streaming\n * (`queuedLaneDecisions`, keyed by langgraph interrupt id). Held until the\n * run parks, then answered as a native `lane_decisions` resume; every attached\n * client reads a queued lane as decided. Raw reference, selector-safe.\n */\nexport function readQueuedLaneDecisions(\n state: unknown,\n): Readonly<Record<string, unknown>> | undefined {\n const queued = readDeepAgentStreamValues(state).queuedLaneDecisions;\n return isRecord(queued) ? queued : undefined;\n}\n\n/**\n * The distinct langgraph interrupt ids a `lane_decisions` resume answers for\n * these gated calls, in first-seen order — or `null` when any call lacks its\n * stamp (a legacy batch payload only a positional resume can answer). A\n * nested HITL request gating several calls contributes one id: the lane is\n * the interrupt, and one decision fans across its calls server-side.\n */\nexport function laneInterruptIds(actionRequests: readonly PendingActionRequest[]): string[] | null {\n if (actionRequests.length === 0) return null;\n const ids: string[] = [];\n for (const request of actionRequests) {\n if (request.interruptId === undefined) return null;\n if (!ids.includes(request.interruptId)) ids.push(request.interruptId);\n }\n return ids;\n}\n\nconst NO_TOOL_CALL_IDS: ReadonlySet<string> = new Set();\n\n/**\n * The gated calls still waiting on a person, by `tool_call_id`: every call of\n * the approval on screen (the parked request's, or the host's mirror while\n * sibling lanes still stream), less the lanes whose decision the host already\n * holds (`queuedLaneDecisions`, keyed by interrupt id): a lane decided live\n * waits for the run to park, not for anyone. The id is the one join between an\n * approval and the call it gates (a payload names no lane), so a call without\n * one counts for nothing.\n */\nexport function toolCallIdsAwaitingApproval({\n actionRequests,\n queuedLaneDecisions,\n}: {\n actionRequests: readonly PendingActionRequest[];\n queuedLaneDecisions?: Readonly<Record<string, unknown>>;\n}): ReadonlySet<string> {\n let ids: Set<string> | undefined;\n for (const request of actionRequests) {\n if (request.toolCallId === undefined) continue;\n if (\n request.interruptId !== undefined &&\n queuedLaneDecisions !== undefined &&\n request.interruptId in queuedLaneDecisions\n ) {\n continue;\n }\n ids ??= new Set();\n ids.add(request.toolCallId);\n }\n return ids ?? NO_TOOL_CALL_IDS;\n}\n\n/** One decision for every lane — the `lane_decisions` map a run-wide approve or reject sends. */\nexport function fanLaneDecision<T>({\n interruptIds,\n decision,\n}: {\n interruptIds: readonly string[];\n decision: T;\n}): Record<string, T> {\n return Object.fromEntries(interruptIds.map((id) => [id, decision]));\n}\n\n/** The abandoned `interrupt` entry, if any — the \"approval was cancelled\" notice. */\nexport function findAbandonedInterrupt(\n abandoned: readonly AbandonedInputRequest[] | undefined,\n): AbandonedInputRequest | undefined {\n return abandoned?.find(\n (request) => isRecord(request) && request.type === 'interrupt' && typeof request.id === 'string',\n );\n}\n\n/** Canonical reason string carried by a user rejection. */\nexport const HITL_REJECT_REASON = 'User rejected the action.';\n\n/**\n * Default {@link UseApprovalResumeLockOptions.pendingResumeGraceMs} for every\n * approval surface: how long a clicked decision waits for its resume run to\n * start before the actions unlock again. Covers a `run/input` the host\n * rejected or a disconnected transport swallowed before any run began — the\n * one failure neither identity-change nor observed-run recovery can detect.\n */\nexport const HITL_PENDING_RESUME_GRACE_MS = 15_000;\n\n/**\n * Default {@link UseApprovalResumeLockOptions.maxTransportRetries}: how many\n * times a decision the transport lost (the websocket dropped or the statewire\n * session reset while the `run/input` was in flight) is re-sent on its own\n * before the user is asked to click again.\n */\nexport const HITL_RESUME_TRANSPORT_RETRIES = 3;\n\n/**\n * The dispatch died client-side without the host ever seeing it — a proven\n * session loss or a gone stream. Safe to re-send the same decision for the\n * same still-parked request. The complement (a server verdict, or the surface\n * unmounting) is not.\n */\nexport function isResumeTransportLoss(error: unknown): boolean {\n const outcome = statewireTransportOutcome(error);\n return outcome === 'session-lost' || outcome === 'stream-gone';\n}\n\n/** `run/input` resume value approving the gated call(s). */\nexport function hitlResumeApprove(): Record<string, string> {\n return { action: 'approve' };\n}\n\n/**\n * `run/input` resume value approving the gated call(s) AND every later call of\n * the same tool(s) on this thread. The backend records the tools from its own\n * parked interrupt (never from this payload), so the value carries no names.\n */\nexport function hitlResumeApproveForSession(): Record<string, string> {\n return { action: 'approve', scope: 'session' };\n}\n\n/** `run/input` resume value rejecting the gated call(s). */\nexport function hitlResumeReject(reason: string = HITL_REJECT_REASON): Record<string, string> {\n return { action: 'reject', reason };\n}\n\n/** One gated call's own answer inside a per-action resume ({@link hitlResumeActionDecisions}). */\nexport type HitlActionDecision =\n | { action: 'approve' }\n | { action: 'reject'; reason: string }\n | { action: 'edit'; edited_args: Record<string, unknown> };\n\n/**\n * The `tool_call_id` of every gated call, in decision order — or `null` when\n * any call lacks one or two calls share one, in which case no per-action\n * resume can be bound to the batch and only the batch answers above apply.\n */\nexport function actionRequestToolCallIds(\n actionRequests: readonly PendingActionRequest[],\n): string[] | null {\n if (actionRequests.length === 0) return null;\n const ids: string[] = [];\n for (const request of actionRequests) {\n if (request.toolCallId === undefined || ids.includes(request.toolCallId)) return null;\n ids.push(request.toolCallId);\n }\n return ids;\n}\n\n/**\n * `run/input` resume value deciding each gated call by its `tool_call_id` —\n * the only channel that carries an edited call. The backend binds every entry\n * to the parked request with that id, takes the tool NAME from its own\n * request (an edit can change a call's arguments, never which tool runs), and\n * refuses the whole answer — nothing runs — unless the map names exactly the\n * parked calls. `edited_args` is the complete argument set the call runs\n * with, not a patch. The envelope `action` mirrors the first entry for hosts\n * that only read the envelope; `action_decisions` is authoritative.\n */\nexport function hitlResumeActionDecisions(\n decisions: Readonly<Record<string, HitlActionDecision>>,\n): Record<string, unknown> {\n const [anchorId, anchor] = Object.entries(decisions)[0] ?? [];\n return {\n action: anchor?.action === 'reject' ? 'reject' : 'approve',\n ...(anchorId !== undefined && { tool_call_id: anchorId }),\n action_decisions: decisions,\n };\n}\n\n/** Answers that let nothing run: rejecting, or the timeout handler's answer. */\nconst NON_APPROVING_ACTIONS: ReadonlySet<string> = new Set(['reject', 'timeout']);\n\n/**\n * Whether a `run/input` resume value (or one lane's decision) lets anything\n * run: approving, approving for the session, an edit, or anything this cannot\n * read as a reject. Mirrors the host's `_decision_approves`, which fails\n * closed the same way, so a surface asks for a passkey check exactly where\n * the host will want one. Rejecting never needs a check (identity 0b.9,\n * call 9). A `lane_decisions` map approves when any of its lanes does.\n */\nexport function resumeValueApproves(value: unknown): boolean {\n if (!isRecord(value)) return true;\n const decisions = value.decisions;\n if (Array.isArray(decisions)) {\n return decisions.length === 0 || decisions.some(resumeValueApproves);\n }\n const perAction = value.action_decisions;\n if (perAction !== undefined) {\n if (!isRecord(perAction)) return true;\n const answers = Object.values(perAction);\n return answers.length === 0 || answers.some(resumeValueApproves);\n }\n const lanes = value.lane_decisions;\n if (lanes !== undefined) {\n if (!isRecord(lanes)) return true;\n const answers = Object.values(lanes);\n return answers.length === 0 || answers.some(resumeValueApproves);\n }\n const kind = value.type;\n if (typeof kind === 'string') return !NON_APPROVING_ACTIONS.has(kind);\n const action = value.action;\n return typeof action !== 'string' || !NON_APPROVING_ACTIONS.has(action);\n}\n\n/**\n * What rides beside a resume value on the `run/input` command. Today only the\n * passkey decision check (`decisionCheck`, identity 0b.9): the token a person\n * minted for exactly this answer, which the host classifies and spends.\n */\nexport interface ResumeExtras {\n readonly decisionCheck?: string;\n}\n\n/** Canonical reason string carried by a user declining a non-HITL park. */\nexport const INTERRUPT_DECLINE_REASON = 'User declined the request.';\n\n/**\n * `run/input` resume value declining a non-HITL park. `{ action: 'reject' }`\n * is the one value every typed raiser treats as a clean user decline\n * (`FormCancelledError` / `ToolRejectedError`); a generic `continue` would be\n * rejected by their resume schemas and surface as a tool error the agent then\n * improvises around.\n */\nexport function interruptResumeDecline(\n reason: string = INTERRUPT_DECLINE_REASON,\n): Record<string, string> {\n return { action: 'reject', reason };\n}\n\nexport interface UseApprovalResumeLockOptions<TValue> {\n /** The pending interrupt's request id (`undefined` when nothing is parked). */\n requestId: string | undefined;\n /** The surface's thread `isRunning` read (mockable per surface). */\n isRunning: boolean;\n /**\n * Dispatch the `run/input` resume for `requestId` on the surface's transport.\n * Return the dispatch promise so a refused or lost command releases the lock\n * at once and reaches `onResumeRejected`; a `void` return leaves the\n * recovery paths below as the only release.\n */\n sendResume: (\n requestId: string,\n value: TValue,\n extras?: ResumeExtras,\n ) => void | Promise<unknown>;\n /**\n * The dispatch `sendResume` returned rejected — the host refused the\n * `run/input` or the transport lost it. The lock is already released when\n * this fires; the surface tells the user, since the request is back on\n * screen with nothing else saying why.\n */\n onResumeRejected?: (error: unknown, requestId: string) => void;\n /**\n * A dispatch the transport lost is being re-sent for the still-parked\n * request; `attempt` counts the retries so far (1-based). Diagnostics only —\n * the lock stays held across the retry.\n */\n onResumeRetry?: (error: unknown, requestId: string, attempt: number) => void;\n /**\n * Automatic re-sends of a decision the transport lost\n * ({@link isResumeTransportLoss}) while the request is still parked. `0`\n * disables retries. Defaults to {@link HITL_RESUME_TRANSPORT_RETRIES}.\n */\n maxTransportRetries?: number;\n /**\n * Re-enable the actions when no resume run started within this window —\n * covers a `run/input` that was rejected or lost before any run began, so\n * neither reset path below can fire. Omit to keep the pre-existing surface\n * behavior of staying locked until a resume run is observed.\n */\n pendingResumeGraceMs?: number;\n}\n\nexport interface ApprovalResumeLock<TValue> {\n /** True after a resume until the request identity changes (or recovery fires). */\n pending: boolean;\n /**\n * Send the resume value, locking the actions. No-op (returns `undefined`)\n * while pending or unparked. Returns the dispatch promise when `sendResume`\n * produced one, so a surface can hold its own state until the host settles.\n */\n resume: (value: TValue, extras?: ResumeExtras) => Promise<unknown> | undefined;\n}\n\n/**\n * Optimistic action lock shared by every approval surface: disable the\n * decision buttons the moment one is clicked, until the request identity\n * changes (it clears after the resume, or a fresh interrupt arrives). This\n * closes the double-click → duplicate-resume window.\n *\n * Recovery paths, in order of specificity:\n * 0. The dispatch itself rejected (a `sendResume` that returned a promise) —\n * released at once, and reported through `onResumeRejected`. A rejection\n * that is a transport loss (the socket dropped mid-flight) is re-sent up to\n * `maxTransportRetries` times first, the lock held throughout, so a flaky\n * connection does not hand the card back to the user for another click.\n * The returned promise settles with the final attempt.\n * 1. Identity change — the resume landed (or a new interrupt replaced the\n * request). Reset during render via React's \"previous value\" pattern.\n * 2. A resume run was observed starting and then settling with the request\n * still pending (failed/cancelled resume) — clear the lock so the user can\n * answer again.\n * 3. Optional grace window ({@link UseApprovalResumeLockOptions.pendingResumeGraceMs})\n * for a `run/input` that never started a run at all.\n */\nexport function useApprovalResumeLock<TValue = Record<string, string>>(\n options: UseApprovalResumeLockOptions<TValue>,\n): ApprovalResumeLock<TValue> {\n const {\n requestId,\n isRunning,\n sendResume,\n pendingResumeGraceMs,\n onResumeRejected,\n onResumeRetry,\n maxTransportRetries = HITL_RESUME_TRANSPORT_RETRIES,\n } = options;\n\n const [pending, setPending] = useState(false);\n // Bumped per re-send so the grace window restarts with each attempt.\n const [attempt, setAttempt] = useState(0);\n const prevRequestIdRef = useRef(requestId);\n if (prevRequestIdRef.current !== requestId) {\n prevRequestIdRef.current = requestId;\n if (pending) setPending(false);\n }\n\n // Track the resume run we kicked off (isRunning observed true→false while\n // pending) and clear `pending` when it ends with the request unresolved.\n const sawResumeRunRef = useRef(false);\n useEffect(() => {\n if (!pending) {\n sawResumeRunRef.current = false;\n return;\n }\n if (isRunning) {\n sawResumeRunRef.current = true;\n return;\n }\n if (sawResumeRunRef.current) {\n sawResumeRunRef.current = false;\n setPending(false);\n }\n }, [pending, isRunning]);\n\n // Pre-start failure recovery: re-enable after the grace window when no\n // resume run began; answering the still-parked request again is exactly\n // what the user needs to be able to do.\n useEffect(() => {\n if (!pending || pendingResumeGraceMs === undefined) return;\n const timer = setTimeout(() => {\n if (!sawResumeRunRef.current) setPending(false);\n }, pendingResumeGraceMs);\n return () => clearTimeout(timer);\n }, [pending, requestId, pendingResumeGraceMs, attempt]);\n\n const sendResumeRef = useRef(sendResume);\n sendResumeRef.current = sendResume;\n const onResumeRejectedRef = useRef(onResumeRejected);\n onResumeRejectedRef.current = onResumeRejected;\n const onResumeRetryRef = useRef(onResumeRetry);\n onResumeRetryRef.current = onResumeRetry;\n const maxTransportRetriesRef = useRef(maxTransportRetries);\n maxTransportRetriesRef.current = maxTransportRetries;\n const currentRequestIdRef = useRef(requestId);\n currentRequestIdRef.current = requestId;\n const pendingRef = useRef(pending);\n pendingRef.current = pending;\n // One token per `resume` call: a settle from an earlier submission must not\n // retry over, or release, a newer one for the same request.\n const submissionRef = useRef(0);\n\n const resume = useCallback(\n (value: TValue, extras?: ResumeExtras): Promise<unknown> | undefined => {\n if (requestId === undefined || pendingRef.current) return undefined;\n const submission = ++submissionRef.current;\n setPending(true);\n pendingRef.current = true;\n setAttempt(0);\n const first: unknown = sendResumeRef.current(requestId, value, extras);\n if (!(first instanceof Promise)) return undefined;\n // The final failure is already reported through `onResumeRejected`;\n // callers that drop the promise must not get an unhandled rejection.\n const outcome = new Promise<unknown>((resolve, reject) => {\n const settle = (dispatched: Promise<unknown>, retries: number) => {\n dispatched.then(resolve, (error: unknown) => {\n // A late refusal for a request the lock has since moved past (the\n // park was re-keyed, or a newer answer is in flight) must neither\n // release that answer's lock nor be reported as a failure of it.\n // The optimistic fold hides the answered request until the fold\n // rolls back, so `undefined` here is not a departure.\n const current = currentRequestIdRef.current;\n if (\n submission !== submissionRef.current ||\n (current !== undefined && current !== requestId)\n ) {\n reject(error);\n return;\n }\n if (retries < maxTransportRetriesRef.current && isResumeTransportLoss(error)) {\n const next = retries + 1;\n onResumeRetryRef.current?.(error, requestId, next);\n setPending(true);\n pendingRef.current = true;\n setAttempt(next);\n const resent: unknown = sendResumeRef.current(requestId, value, extras);\n if (resent instanceof Promise) {\n settle(resent, next);\n return;\n }\n resolve(undefined);\n return;\n }\n setPending(false);\n pendingRef.current = false;\n onResumeRejectedRef.current?.(error, requestId);\n reject(error);\n });\n };\n settle(first, 0);\n });\n outcome.catch(() => {});\n return outcome;\n },\n [requestId],\n );\n\n return { pending, resume };\n}\n"],"names":["hasClientToolInterruptSource","readDeepAgentStreamValues","statewireTransportOutcome","useState","useRef","useEffect","useCallback"],"mappings":"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"}
1
+ {"version":3,"file":"interrupts.cjs","sources":["../../../../agent-runtime/src/interrupts.ts"],"sourcesContent":["/**\n * Human-in-the-loop interrupt primitives shared by every statewire chat\n * surface (Olympus, the React SDK, the Office add-in, the Chrome extension,\n * mobile).\n *\n * The agora deep-agent runtime parks a gated run on an `interrupt` input\n * request whose payload carries the Athena `hitl_approval` shape\n * (`athena_deep_agent/hitl_bridge.py`). A surface answers it with\n * `{ type: 'run/input', requestId, response: { type: 'resume', value } }` —\n * the exact command the backend's resume path consumes. This module holds the\n * payload contract (narrowing, resume values) and the shared resume-lock state\n * machine; rendering stays with each surface.\n *\n * Deliberately parameterized rather than self-wiring: these helpers never call\n * `useStatewireRuns`/`useAuiState` themselves. Each surface reads its own\n * statewire hooks (which its tests already mock) and passes the values in, so\n * the shared semantics cannot drift while per-surface wiring stays testable.\n */\n\nimport type { StatewireThread } from '@assistant-ui/react-statewire';\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport { hasClientToolInterruptSource } from './client-tools';\nimport { statewireTransportOutcome } from './errors';\nimport { readDeepAgentStreamValues } from './statewire-state';\n\n/** Human-in-the-loop approval payload the agora deep-agent runtime parks a run\n * on (shaped by `athena_deep_agent/hitl_bridge.py`). */\nexport interface HitlApprovalPayload {\n source: string;\n type: string;\n message: string;\n /** Risk headline/facts/warnings, when the host includes them. */\n approval_context?: unknown;\n context?: {\n tool_name?: string;\n tool_id?: string;\n tool_call_id?: string;\n tool_args?: Record<string, unknown>;\n pending_action_count?: number;\n approval_context?: unknown;\n /**\n * Every pending gated call (name + args), in decision order. Absent on\n * older backends — fall back to the single `tool_name`/`tool_args` pair.\n * Per-dispatch payloads also carry each lane's `tool_call_id` (the lane\n * card key) and `interrupt_id` (the lane resume key).\n */\n action_requests?: {\n name?: string;\n args?: Record<string, unknown>;\n tool_call_id?: string;\n interrupt_id?: string;\n /**\n * True when \"approve all for this session\" would take effect for this\n * tool (the workspace allows overriding its approval). Absent on older\n * backends and on non-overridable tools: the option is not offered.\n */\n session_approval_allowed?: boolean;\n /**\n * On a send with an act adapter: the people a session-wide approval of\n * it covers, and only them, by the name the card shows (the address or\n * number). Such a send carries `session_approval_allowed: false`, so a\n * client that predates this field never offers a tool-wide answer.\n */\n session_approval_recipients?: string[];\n /**\n * The decision types the parked gate accepts for this call (`approve`,\n * `reject`, `edit`). Absent on older backends: only approve / reject\n * are known to be accepted.\n */\n allowed_decisions?: string[];\n /** What the backend resolved about this call before asking (display-only). */\n approval_context?: unknown;\n }[];\n /**\n * True when each dispatch parked on its own interrupt: decisions submit\n * per lane via `lane_decisions` and each approved lane starts immediately,\n * instead of the legacy batch where one resume must answer every lane.\n */\n per_dispatch?: boolean;\n };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/** A pending `interrupt` input request; a present `response` key marks it answered. */\nexport function isPendingInterrupt(\n request: StatewireThread.InputRequestState,\n): request is StatewireThread.CustomInputRequest {\n return request.type === 'interrupt' && !('response' in request);\n}\n\n/**\n * Interrupt sources that were system pauses before the host stamped\n * `requires_user_action: false` on them (a background deep-research wait). Kept\n * only for the deploy skew between an older agora and a newer client; delete\n * once every deployment's raiser stamps the flag.\n */\nconst LEGACY_SYSTEM_PAUSE_INTERRUPT_SOURCES: ReadonlySet<string> = new Set(['deep_research_pending']);\n\n/**\n * A park nobody answers: the agent is still working (a background research\n * wait) and the backend resumes it itself. The raiser stamps\n * `requires_user_action: false`; older raisers are recognised by source.\n */\nexport function isSystemPauseInterrupt(payload: unknown): boolean {\n if (!isRecord(payload)) return false;\n if (payload.requires_user_action === false) return true;\n return (\n typeof payload.source === 'string' && LEGACY_SYSTEM_PAUSE_INTERRUPT_SOURCES.has(payload.source)\n );\n}\n\n/**\n * A pending interrupt an approval card may answer. Two kinds are excluded —\n * client-tool parks (the client-tool bridge owns those, and a generic resume\n * would corrupt the parked tool call) and system pauses (nothing to answer; the\n * backend resumes them). The source checks are deliberately loose so a skewed\n * deploy can never route one here.\n */\nexport function isApprovalCardInterrupt(\n request: StatewireThread.InputRequestState,\n): request is StatewireThread.CustomInputRequest {\n if (!isPendingInterrupt(request)) return false;\n const payload = (request as { payload?: unknown }).payload;\n return !hasClientToolInterruptSource(payload) && !isSystemPauseInterrupt(payload);\n}\n\n/** Narrow an interrupt payload to the Athena HITL approval shape. */\nexport function asHitlApproval(value: unknown): HitlApprovalPayload | null {\n if (!isRecord(value)) return null;\n return value.type === 'hitl' &&\n value.source === 'hitl_approval' &&\n typeof value.message === 'string'\n ? (value as unknown as HitlApprovalPayload)\n : null;\n}\n\n/** Default paused-run copy when an interrupt payload carries no message. */\nexport const DEFAULT_INTERRUPT_PAUSED_MESSAGE =\n 'The agent is paused and waiting for input.';\n\n/** Best-effort human-readable message from any interrupt payload. */\nexport function readInterruptMessage(\n value: unknown,\n fallback: string = DEFAULT_INTERRUPT_PAUSED_MESSAGE,\n): string {\n if (isRecord(value) && typeof value.message === 'string' && value.message.length > 0) {\n return value.message;\n }\n return fallback;\n}\n\n/** One label/value row of a call's resolved approval facts. */\nexport interface PendingApprovalFact {\n label: string;\n value: string;\n}\n\n/** One caution of a call's resolved approval facts. */\nexport interface PendingApprovalWarning {\n severity: 'info' | 'warning' | 'critical';\n message: string;\n}\n\nexport interface PendingApprovalSlackTarget {\n catalog_id: string;\n conversation_id: string | null;\n thread_ts: string | null;\n team_url: string | null;\n people: string[];\n}\n\n/**\n * What the backend resolved about a gated call before asking — e.g. where a\n * Slack write goes and as whom (agora `services/interrupts/approval_context.py`).\n * Display-only; the decision still answers the parked call.\n */\nexport interface PendingApprovalContext {\n facts: PendingApprovalFact[];\n warnings: PendingApprovalWarning[];\n /** The question the approval asks, in a line (\"Use Studio Mac in this conversation?\"). */\n headline?: string;\n /** What approving lets happen, a sentence or two under the headline. */\n summary?: string;\n slack_target?: PendingApprovalSlackTarget;\n}\n\nconst APPROVAL_WARNING_SEVERITIES = new Set(['info', 'warning', 'critical']);\n\nfunction isNullableString(value: unknown): value is string | null {\n return value === null || typeof value === 'string';\n}\n\n/** Display text: a string, trimmed; a blank or anything that is not a string is absent. */\nfunction displayText(value: unknown): string | undefined {\n if (typeof value !== 'string') return undefined;\n const text = value.trim();\n return text.length > 0 ? text : undefined;\n}\n\nfunction isStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.every((entry) => typeof entry === 'string');\n}\n\nfunction parsePendingApprovalSlackTarget(value: unknown): PendingApprovalSlackTarget | undefined {\n if (!isRecord(value) || typeof value.catalog_id !== 'string') return undefined;\n const conversationId = value.conversation_id;\n const threadTs = value.thread_ts;\n const teamUrl = value.team_url;\n const people = value.people === undefined ? [] : value.people;\n if (\n !isNullableString(conversationId) ||\n !isNullableString(threadTs) ||\n !isNullableString(teamUrl) ||\n !isStringArray(people)\n )\n return undefined;\n return {\n catalog_id: value.catalog_id,\n conversation_id: conversationId,\n thread_ts: threadTs,\n team_url: teamUrl,\n people,\n };\n}\n\n/** A call's `approval_context`, or `undefined` when absent or empty. */\nexport function parsePendingApprovalContext(value: unknown): PendingApprovalContext | undefined {\n if (!isRecord(value)) return undefined;\n const facts: PendingApprovalFact[] = Array.isArray(value.facts)\n ? value.facts.flatMap((fact) =>\n isRecord(fact) && typeof fact.label === 'string' && typeof fact.value === 'string'\n ? [{ label: fact.label, value: fact.value }]\n : [],\n )\n : [];\n const warnings: PendingApprovalWarning[] = Array.isArray(value.warnings)\n ? value.warnings.flatMap((warning) => {\n if (!isRecord(warning) || typeof warning.message !== 'string') return [];\n const severity =\n typeof warning.severity === 'string' && APPROVAL_WARNING_SEVERITIES.has(warning.severity)\n ? (warning.severity as PendingApprovalWarning['severity'])\n : 'info';\n return [{ severity, message: warning.message }];\n })\n : [];\n const headline = displayText(value.headline);\n const summary = displayText(value.summary);\n const slackTarget = parsePendingApprovalSlackTarget(value.slack_target);\n return facts.length > 0 || warnings.length > 0 || headline || summary || slackTarget\n ? {\n facts,\n warnings,\n ...(headline ? { headline } : {}),\n ...(summary ? { summary } : {}),\n ...(slackTarget ? { slack_target: slackTarget } : {}),\n }\n : undefined;\n}\n\nexport interface PendingActionRequest {\n name: string;\n args: Record<string, unknown>;\n /** The lane's `task` tool-call id (present on per-dispatch payloads). */\n toolCallId?: string;\n /** The lane's langgraph interrupt id — the `lane_decisions` resume key. */\n interruptId?: string;\n /** Present (true) when a session-wide approval would cover later calls of this tool. */\n sessionApprovalAllowed?: true;\n /**\n * Present on a send whose session-wide approval covers only the people it\n * reaches (`session_approval_recipients`): what the card names for each of\n * them, every hidden or control character written out as `<U+XXXX>`. Such a\n * send never carries `sessionApprovalAllowed`.\n */\n sessionApprovalRecipients?: string[];\n /** Present (true) when the gate accepts an `edit` decision for this call. */\n editAllowed?: true;\n /** Present when the backend resolved anything about this call before asking (facts, a headline). */\n approvalContext?: PendingApprovalContext;\n}\n\n/**\n * Every gated call this approval covers, in decision order. One resume answers\n * ALL of them, so every entry must stay reviewable somewhere. Falls back to\n * the single first-call fields on older backends without `action_requests`.\n */\nexport function pendingActionRequests(hitl: HitlApprovalPayload | null): PendingActionRequest[] {\n const context = hitl?.context;\n if (!context) return [];\n // The payload crosses the wire with only its top-level shape narrowed\n // (`asHitlApproval`), so the nested collection must be validated before any\n // array/property access: a malformed host value must degrade to \"no\n // reviewable entries\", never crash the approval surface.\n const requests: Record<string, unknown>[] = Array.isArray(context.action_requests)\n ? context.action_requests.filter(isRecord)\n : context.tool_name && context.tool_args\n ? [{ name: context.tool_name, args: context.tool_args, tool_call_id: context.tool_call_id }]\n : [];\n return requests.map((request) => {\n const approvalContext = parsePendingApprovalContext(request.approval_context);\n const sessionApprovalRecipients = parseSessionApprovalRecipients(\n request.session_approval_recipients,\n );\n return {\n name: typeof request.name === 'string' ? request.name : 'tool',\n args: isRecord(request.args) ? request.args : {},\n ...(typeof request.tool_call_id === 'string' && { toolCallId: request.tool_call_id }),\n ...(typeof request.interrupt_id === 'string' && { interruptId: request.interrupt_id }),\n ...(request.session_approval_allowed === true && { sessionApprovalAllowed: true }),\n ...(sessionApprovalRecipients && { sessionApprovalRecipients }),\n ...(Array.isArray(request.allowed_decisions) &&\n request.allowed_decisions.includes('edit') && { editAllowed: true }),\n ...(approvalContext && { approvalContext }),\n };\n });\n}\n\n/** The headline (and summary) that title a pending approval in place of the generic copy. */\nexport interface PendingApprovalTitle {\n headline: string;\n summary?: string;\n}\n\n/**\n * The title of a pending approval: the headline and summary of its one gated\n * call's resolved envelope (the call's own `approval_context`, else the copy\n * the host lifted to `context.approval_context`). A batch has none: one\n * decision answers every call, so no one call's headline may speak for it.\n * Display-only, like the envelope.\n */\nexport function pendingApprovalTitle({\n hitl,\n actionRequests,\n}: {\n hitl: HitlApprovalPayload | null;\n actionRequests: readonly PendingActionRequest[];\n}): PendingApprovalTitle | undefined {\n const [call, ...others] = actionRequests;\n if (hitl === null || call === undefined || others.length > 0) return undefined;\n // An older backend lists only the first call and counts the rest here; a\n // count that is not plainly one keeps the generic copy.\n const pendingCount = hitl.context?.pending_action_count;\n if (pendingCount !== undefined && pendingCount !== 1) return undefined;\n const envelope =\n call.approvalContext ?? parsePendingApprovalContext(hitl.context?.approval_context);\n if (envelope?.headline === undefined) return undefined;\n return envelope.summary === undefined\n ? { headline: envelope.headline }\n : { headline: envelope.headline, summary: envelope.summary };\n}\n\n/**\n * `text` with every character a person can't see (control and format\n * characters such as zero-width spaces, bidi overrides and Unicode tag\n * characters, private-use and unassigned code points, and any separator but\n * a plain space) written out as `<U+XXXX>`, so a name shows all of itself.\n */\nexport function visibleText(text: string): string {\n return text.replace(/[\\p{C}\\p{Z}]/gu, (character) =>\n character === ' '\n ? character\n : `<U+${(character.codePointAt(0) ?? 0).toString(16).toUpperCase().padStart(4, '0')}>`,\n );\n}\n\n/** A send's stamped recipient names: a non-empty list of non-empty strings, or nothing. */\nfunction parseSessionApprovalRecipients(value: unknown): string[] | undefined {\n if (!Array.isArray(value) || value.length === 0) return undefined;\n const names: string[] = [];\n for (const name of value) {\n if (typeof name !== 'string' || name.trim() === '') return undefined;\n names.push(visibleText(name));\n }\n return names;\n}\n\n/**\n * What \"approve for this session\" would cover for a pending approval:\n *\n * - `tools`: later calls of these tools (every call stamped\n * `sessionApprovalAllowed`).\n * - `recipients`: later sends to exactly these people, and nobody else\n * (every call is a send stamped `sessionApprovalRecipients`).\n */\nexport type SessionApprovalOffer =\n | { kind: 'tools' }\n | { kind: 'recipients'; recipients: readonly string[] };\n\n/**\n * The session-wide approval the card may offer, or `undefined` for the\n * one-time answer only: a batch that mixes in a tool the workspace pinned as\n * always-prompt, a call stamped neither way, or tools beside recipient-scoped\n * sends (no one label could say exactly what that answer covers).\n */\nexport function sessionApprovalOffer(\n actionRequests: readonly PendingActionRequest[],\n): SessionApprovalOffer | undefined {\n if (actionRequests.length === 0) return undefined;\n if (\n actionRequests.every(\n (request) =>\n request.sessionApprovalAllowed === true && request.sessionApprovalRecipients === undefined,\n )\n ) {\n return { kind: 'tools' };\n }\n const recipients: string[] = [];\n for (const request of actionRequests) {\n if (request.sessionApprovalRecipients === undefined) return undefined;\n for (const name of request.sessionApprovalRecipients) {\n if (!recipients.includes(name)) recipients.push(name);\n }\n }\n return { kind: 'recipients', recipients };\n}\n\n/** `a`, `a and b`, `a, b and c`. */\nfunction namesInProse(names: readonly string[]): string {\n if (names.length <= 1) return names.join('');\n return `${names.slice(0, -1).join(', ')} and ${names[names.length - 1]}`;\n}\n\n/**\n * What the session option's button says: exactly what the answer covers.\n * \"Approve all for this session\" for tools; for sends, every recipient by\n * name: \"Approve for this session: only kyle@partner.example and\n * sam@partner.example\".\n */\nexport function sessionApprovalLabel(offer: SessionApprovalOffer): string {\n if (offer.kind === 'tools') return 'Approve all for this session';\n return `Approve for this session: only ${namesInProse(offer.recipients)}`;\n}\n\n/**\n * Whether the card may offer a session-wide approval at all\n * ({@link sessionApprovalOffer}).\n */\nexport function canApproveForSession(actionRequests: readonly PendingActionRequest[]): boolean {\n return sessionApprovalOffer(actionRequests) !== undefined;\n}\n\n/** An interrupt a non-resume dispatch dropped, published by the host. */\nexport interface AbandonedInputRequest {\n id: string;\n type: string;\n}\n\n/**\n * `useStatewireState` selector body for the host-published abandoned input\n * requests. Reads through {@link readDeepAgentStreamValues} so the selector is\n * safe on the pre-attach (`undefined`) wire state, and returns the raw array\n * reference so the subscription stays shallow-stable — derive (`.find`) on the\n * result, never inside the selector.\n */\nexport function readAbandonedInputRequests(\n state: unknown,\n): readonly AbandonedInputRequest[] | undefined {\n const abandoned = readDeepAgentStreamValues(state).abandonedInputRequests;\n // Wire value: anything non-array (including a malformed host publication)\n // reads as \"none\" rather than crashing the consuming surface. Identity-\n // preserving for the valid case, so it stays selector-safe.\n return Array.isArray(abandoned)\n ? (abandoned as readonly AbandonedInputRequest[])\n : undefined;\n}\n\n/**\n * `useStatewireState` selector body for the host's mirrored interrupt\n * (`__interrupt__`, the LangGraph `{value, resumable}` wrapper). The mirror\n * is published the moment a lane parks on its gate — while sibling lanes are\n * still streaming and the run manager has NOT yet published an answerable\n * input request. Raw reference, selector-safe; unwrap with\n * {@link interruptMirrorPayload}.\n */\nexport function readInterruptMirror(state: unknown): unknown {\n return readDeepAgentStreamValues(state).__interrupt__;\n}\n\n/** The payload of a mirrored interrupt wrapper, or `undefined` for anything else. */\nexport function interruptMirrorPayload(mirror: unknown): unknown {\n return isRecord(mirror) ? mirror.value : undefined;\n}\n\n/**\n * `useStatewireState` selector body for the per-dispatch lane decisions the\n * host accepted through `run/lane-input` while the run was still streaming\n * (`queuedLaneDecisions`, keyed by langgraph interrupt id). Held until the\n * run parks, then answered as a native `lane_decisions` resume; every attached\n * client reads a queued lane as decided. Raw reference, selector-safe.\n */\nexport function readQueuedLaneDecisions(\n state: unknown,\n): Readonly<Record<string, unknown>> | undefined {\n const queued = readDeepAgentStreamValues(state).queuedLaneDecisions;\n return isRecord(queued) ? queued : undefined;\n}\n\n/**\n * The distinct langgraph interrupt ids a `lane_decisions` resume answers for\n * these gated calls, in first-seen order — or `null` when any call lacks its\n * stamp (a legacy batch payload only a positional resume can answer). A\n * nested HITL request gating several calls contributes one id: the lane is\n * the interrupt, and one decision fans across its calls server-side.\n */\nexport function laneInterruptIds(actionRequests: readonly PendingActionRequest[]): string[] | null {\n if (actionRequests.length === 0) return null;\n const ids: string[] = [];\n for (const request of actionRequests) {\n if (request.interruptId === undefined) return null;\n if (!ids.includes(request.interruptId)) ids.push(request.interruptId);\n }\n return ids;\n}\n\nconst NO_TOOL_CALL_IDS: ReadonlySet<string> = new Set();\n\n/**\n * The gated calls still waiting on a person, by `tool_call_id`: every call of\n * the approval on screen (the parked request's, or the host's mirror while\n * sibling lanes still stream), less the lanes whose decision the host already\n * holds (`queuedLaneDecisions`, keyed by interrupt id): a lane decided live\n * waits for the run to park, not for anyone. The id is the one join between an\n * approval and the call it gates (a payload names no lane), so a call without\n * one counts for nothing.\n */\nexport function toolCallIdsAwaitingApproval({\n actionRequests,\n queuedLaneDecisions,\n}: {\n actionRequests: readonly PendingActionRequest[];\n queuedLaneDecisions?: Readonly<Record<string, unknown>>;\n}): ReadonlySet<string> {\n let ids: Set<string> | undefined;\n for (const request of actionRequests) {\n if (request.toolCallId === undefined) continue;\n if (\n request.interruptId !== undefined &&\n queuedLaneDecisions !== undefined &&\n request.interruptId in queuedLaneDecisions\n ) {\n continue;\n }\n ids ??= new Set();\n ids.add(request.toolCallId);\n }\n return ids ?? NO_TOOL_CALL_IDS;\n}\n\n/** One decision for every lane — the `lane_decisions` map a run-wide approve or reject sends. */\nexport function fanLaneDecision<T>({\n interruptIds,\n decision,\n}: {\n interruptIds: readonly string[];\n decision: T;\n}): Record<string, T> {\n return Object.fromEntries(interruptIds.map((id) => [id, decision]));\n}\n\n/** The abandoned `interrupt` entry, if any — the \"approval was cancelled\" notice. */\nexport function findAbandonedInterrupt(\n abandoned: readonly AbandonedInputRequest[] | undefined,\n): AbandonedInputRequest | undefined {\n return abandoned?.find(\n (request) => isRecord(request) && request.type === 'interrupt' && typeof request.id === 'string',\n );\n}\n\n/** Canonical reason string carried by a user rejection. */\nexport const HITL_REJECT_REASON = 'User rejected the action.';\n\n/**\n * Default {@link UseApprovalResumeLockOptions.pendingResumeGraceMs} for every\n * approval surface: how long a clicked decision waits for its resume run to\n * start before the actions unlock again. Covers a `run/input` the host\n * rejected or a disconnected transport swallowed before any run began — the\n * one failure neither identity-change nor observed-run recovery can detect.\n */\nexport const HITL_PENDING_RESUME_GRACE_MS = 15_000;\n\n/**\n * Default {@link UseApprovalResumeLockOptions.maxTransportRetries}: how many\n * times a decision the transport lost (the websocket dropped or the statewire\n * session reset while the `run/input` was in flight) is re-sent on its own\n * before the user is asked to click again.\n */\nexport const HITL_RESUME_TRANSPORT_RETRIES = 3;\n\n/**\n * The dispatch died client-side without the host ever seeing it — a proven\n * session loss or a gone stream. Safe to re-send the same decision for the\n * same still-parked request. The complement (a server verdict, or the surface\n * unmounting) is not.\n */\nexport function isResumeTransportLoss(error: unknown): boolean {\n const outcome = statewireTransportOutcome(error);\n return outcome === 'session-lost' || outcome === 'stream-gone';\n}\n\n/** `run/input` resume value approving the gated call(s). */\nexport function hitlResumeApprove(): Record<string, string> {\n return { action: 'approve' };\n}\n\n/**\n * `run/input` resume value approving the gated call(s) AND every later call of\n * the same tool(s) on this thread. The backend records the tools from its own\n * parked interrupt (never from this payload), so the value carries no names.\n */\nexport function hitlResumeApproveForSession(): Record<string, string> {\n return { action: 'approve', scope: 'session' };\n}\n\n/** `run/input` resume value rejecting the gated call(s). */\nexport function hitlResumeReject(reason: string = HITL_REJECT_REASON): Record<string, string> {\n return { action: 'reject', reason };\n}\n\n/** One gated call's own answer inside a per-action resume ({@link hitlResumeActionDecisions}). */\nexport type HitlActionDecision =\n | { action: 'approve' }\n | { action: 'reject'; reason: string }\n | { action: 'edit'; edited_args: Record<string, unknown> };\n\n/**\n * The `tool_call_id` of every gated call, in decision order — or `null` when\n * any call lacks one or two calls share one, in which case no per-action\n * resume can be bound to the batch and only the batch answers above apply.\n */\nexport function actionRequestToolCallIds(\n actionRequests: readonly PendingActionRequest[],\n): string[] | null {\n if (actionRequests.length === 0) return null;\n const ids: string[] = [];\n for (const request of actionRequests) {\n if (request.toolCallId === undefined || ids.includes(request.toolCallId)) return null;\n ids.push(request.toolCallId);\n }\n return ids;\n}\n\n/**\n * `run/input` resume value deciding each gated call by its `tool_call_id` —\n * the only channel that carries an edited call. The backend binds every entry\n * to the parked request with that id, takes the tool NAME from its own\n * request (an edit can change a call's arguments, never which tool runs), and\n * refuses the whole answer — nothing runs — unless the map names exactly the\n * parked calls. `edited_args` is the complete argument set the call runs\n * with, not a patch. The envelope `action` mirrors the first entry for hosts\n * that only read the envelope; `action_decisions` is authoritative.\n */\nexport function hitlResumeActionDecisions(\n decisions: Readonly<Record<string, HitlActionDecision>>,\n): Record<string, unknown> {\n const [anchorId, anchor] = Object.entries(decisions)[0] ?? [];\n return {\n action: anchor?.action === 'reject' ? 'reject' : 'approve',\n ...(anchorId !== undefined && { tool_call_id: anchorId }),\n action_decisions: decisions,\n };\n}\n\n/** Answers that let nothing run: rejecting, or the timeout handler's answer. */\nconst NON_APPROVING_ACTIONS: ReadonlySet<string> = new Set(['reject', 'timeout']);\n\n/**\n * Whether a `run/input` resume value (or one lane's decision) lets anything\n * run: approving, approving for the session, an edit, or anything this cannot\n * read as a reject. Mirrors the host's `_decision_approves`, which fails\n * closed the same way, so a surface asks for a passkey check exactly where\n * the host will want one. Rejecting never needs a check (identity 0b.9,\n * call 9). A `lane_decisions` map approves when any of its lanes does.\n */\nexport function resumeValueApproves(value: unknown): boolean {\n if (!isRecord(value)) return true;\n const decisions = value.decisions;\n if (Array.isArray(decisions)) {\n return decisions.length === 0 || decisions.some(resumeValueApproves);\n }\n const perAction = value.action_decisions;\n if (perAction !== undefined) {\n if (!isRecord(perAction)) return true;\n const answers = Object.values(perAction);\n return answers.length === 0 || answers.some(resumeValueApproves);\n }\n const lanes = value.lane_decisions;\n if (lanes !== undefined) {\n if (!isRecord(lanes)) return true;\n const answers = Object.values(lanes);\n return answers.length === 0 || answers.some(resumeValueApproves);\n }\n const kind = value.type;\n if (typeof kind === 'string') return !NON_APPROVING_ACTIONS.has(kind);\n const action = value.action;\n return typeof action !== 'string' || !NON_APPROVING_ACTIONS.has(action);\n}\n\n/**\n * What rides beside a resume value on the `run/input` command. Today only the\n * passkey decision check (`decisionCheck`, identity 0b.9): the token a person\n * minted for exactly this answer, which the host classifies and spends.\n */\nexport interface ResumeExtras {\n readonly decisionCheck?: string;\n}\n\n/** Canonical reason string carried by a user declining a non-HITL park. */\nexport const INTERRUPT_DECLINE_REASON = 'User declined the request.';\n\n/**\n * `run/input` resume value declining a non-HITL park. `{ action: 'reject' }`\n * is the one value every typed raiser treats as a clean user decline\n * (`FormCancelledError` / `ToolRejectedError`); a generic `continue` would be\n * rejected by their resume schemas and surface as a tool error the agent then\n * improvises around.\n */\nexport function interruptResumeDecline(\n reason: string = INTERRUPT_DECLINE_REASON,\n): Record<string, string> {\n return { action: 'reject', reason };\n}\n\nexport interface UseApprovalResumeLockOptions<TValue> {\n /** The pending interrupt's request id (`undefined` when nothing is parked). */\n requestId: string | undefined;\n /** The surface's thread `isRunning` read (mockable per surface). */\n isRunning: boolean;\n /**\n * Dispatch the `run/input` resume for `requestId` on the surface's transport.\n * Return the dispatch promise so a refused or lost command releases the lock\n * at once and reaches `onResumeRejected`; a `void` return leaves the\n * recovery paths below as the only release.\n */\n sendResume: (\n requestId: string,\n value: TValue,\n extras?: ResumeExtras,\n ) => void | Promise<unknown>;\n /**\n * The dispatch `sendResume` returned rejected — the host refused the\n * `run/input` or the transport lost it. The lock is already released when\n * this fires; the surface tells the user, since the request is back on\n * screen with nothing else saying why.\n */\n onResumeRejected?: (error: unknown, requestId: string) => void;\n /**\n * A dispatch the transport lost is being re-sent for the still-parked\n * request; `attempt` counts the retries so far (1-based). Diagnostics only —\n * the lock stays held across the retry.\n */\n onResumeRetry?: (error: unknown, requestId: string, attempt: number) => void;\n /**\n * Automatic re-sends of a decision the transport lost\n * ({@link isResumeTransportLoss}) while the request is still parked. `0`\n * disables retries. Defaults to {@link HITL_RESUME_TRANSPORT_RETRIES}.\n */\n maxTransportRetries?: number;\n /**\n * Re-enable the actions when no resume run started within this window —\n * covers a `run/input` that was rejected or lost before any run began, so\n * neither reset path below can fire. Omit to keep the pre-existing surface\n * behavior of staying locked until a resume run is observed.\n */\n pendingResumeGraceMs?: number;\n}\n\nexport interface ApprovalResumeLock<TValue> {\n /** True after a resume until the request identity changes (or recovery fires). */\n pending: boolean;\n /**\n * Send the resume value, locking the actions. No-op (returns `undefined`)\n * while pending or unparked. Returns the dispatch promise when `sendResume`\n * produced one, so a surface can hold its own state until the host settles.\n */\n resume: (value: TValue, extras?: ResumeExtras) => Promise<unknown> | undefined;\n}\n\n/**\n * Optimistic action lock shared by every approval surface: disable the\n * decision buttons the moment one is clicked, until the request identity\n * changes (it clears after the resume, or a fresh interrupt arrives). This\n * closes the double-click → duplicate-resume window.\n *\n * Recovery paths, in order of specificity:\n * 0. The dispatch itself rejected (a `sendResume` that returned a promise) —\n * released at once, and reported through `onResumeRejected`. A rejection\n * that is a transport loss (the socket dropped mid-flight) is re-sent up to\n * `maxTransportRetries` times first, the lock held throughout, so a flaky\n * connection does not hand the card back to the user for another click.\n * The returned promise settles with the final attempt.\n * 1. Identity change — the resume landed (or a new interrupt replaced the\n * request). Reset during render via React's \"previous value\" pattern.\n * 2. A resume run was observed starting and then settling with the request\n * still pending (failed/cancelled resume) — clear the lock so the user can\n * answer again.\n * 3. Optional grace window ({@link UseApprovalResumeLockOptions.pendingResumeGraceMs})\n * for a `run/input` that never started a run at all.\n */\nexport function useApprovalResumeLock<TValue = Record<string, string>>(\n options: UseApprovalResumeLockOptions<TValue>,\n): ApprovalResumeLock<TValue> {\n const {\n requestId,\n isRunning,\n sendResume,\n pendingResumeGraceMs,\n onResumeRejected,\n onResumeRetry,\n maxTransportRetries = HITL_RESUME_TRANSPORT_RETRIES,\n } = options;\n\n const [pending, setPending] = useState(false);\n // Bumped per re-send so the grace window restarts with each attempt.\n const [attempt, setAttempt] = useState(0);\n const prevRequestIdRef = useRef(requestId);\n if (prevRequestIdRef.current !== requestId) {\n prevRequestIdRef.current = requestId;\n if (pending) setPending(false);\n }\n\n // Track the resume run we kicked off (isRunning observed true→false while\n // pending) and clear `pending` when it ends with the request unresolved.\n const sawResumeRunRef = useRef(false);\n useEffect(() => {\n if (!pending) {\n sawResumeRunRef.current = false;\n return;\n }\n if (isRunning) {\n sawResumeRunRef.current = true;\n return;\n }\n if (sawResumeRunRef.current) {\n sawResumeRunRef.current = false;\n setPending(false);\n }\n }, [pending, isRunning]);\n\n // Pre-start failure recovery: re-enable after the grace window when no\n // resume run began; answering the still-parked request again is exactly\n // what the user needs to be able to do.\n useEffect(() => {\n if (!pending || pendingResumeGraceMs === undefined) return;\n const timer = setTimeout(() => {\n if (!sawResumeRunRef.current) setPending(false);\n }, pendingResumeGraceMs);\n return () => clearTimeout(timer);\n }, [pending, requestId, pendingResumeGraceMs, attempt]);\n\n const sendResumeRef = useRef(sendResume);\n sendResumeRef.current = sendResume;\n const onResumeRejectedRef = useRef(onResumeRejected);\n onResumeRejectedRef.current = onResumeRejected;\n const onResumeRetryRef = useRef(onResumeRetry);\n onResumeRetryRef.current = onResumeRetry;\n const maxTransportRetriesRef = useRef(maxTransportRetries);\n maxTransportRetriesRef.current = maxTransportRetries;\n const currentRequestIdRef = useRef(requestId);\n currentRequestIdRef.current = requestId;\n const pendingRef = useRef(pending);\n pendingRef.current = pending;\n // One token per `resume` call: a settle from an earlier submission must not\n // retry over, or release, a newer one for the same request.\n const submissionRef = useRef(0);\n\n const resume = useCallback(\n (value: TValue, extras?: ResumeExtras): Promise<unknown> | undefined => {\n if (requestId === undefined || pendingRef.current) return undefined;\n const submission = ++submissionRef.current;\n setPending(true);\n pendingRef.current = true;\n setAttempt(0);\n const first: unknown = sendResumeRef.current(requestId, value, extras);\n if (!(first instanceof Promise)) return undefined;\n // The final failure is already reported through `onResumeRejected`;\n // callers that drop the promise must not get an unhandled rejection.\n const outcome = new Promise<unknown>((resolve, reject) => {\n const settle = (dispatched: Promise<unknown>, retries: number) => {\n dispatched.then(resolve, (error: unknown) => {\n // A late refusal for a request the lock has since moved past (the\n // park was re-keyed, or a newer answer is in flight) must neither\n // release that answer's lock nor be reported as a failure of it.\n // The optimistic fold hides the answered request until the fold\n // rolls back, so `undefined` here is not a departure.\n const current = currentRequestIdRef.current;\n if (\n submission !== submissionRef.current ||\n (current !== undefined && current !== requestId)\n ) {\n reject(error);\n return;\n }\n if (retries < maxTransportRetriesRef.current && isResumeTransportLoss(error)) {\n const next = retries + 1;\n onResumeRetryRef.current?.(error, requestId, next);\n setPending(true);\n pendingRef.current = true;\n setAttempt(next);\n const resent: unknown = sendResumeRef.current(requestId, value, extras);\n if (resent instanceof Promise) {\n settle(resent, next);\n return;\n }\n resolve(undefined);\n return;\n }\n setPending(false);\n pendingRef.current = false;\n onResumeRejectedRef.current?.(error, requestId);\n reject(error);\n });\n };\n settle(first, 0);\n });\n outcome.catch(() => {});\n return outcome;\n },\n [requestId],\n );\n\n return { pending, resume };\n}\n"],"names":["hasClientToolInterruptSource","readDeepAgentStreamValues","statewireTransportOutcome","useState","useRef","useEffect","useCallback"],"mappings":"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"}
@@ -49,6 +49,13 @@ export interface HitlApprovalPayload {
49
49
  * backends and on non-overridable tools: the option is not offered.
50
50
  */
51
51
  session_approval_allowed?: boolean;
52
+ /**
53
+ * On a send with an act adapter: the people a session-wide approval of
54
+ * it covers, and only them, by the name the card shows (the address or
55
+ * number). Such a send carries `session_approval_allowed: false`, so a
56
+ * client that predates this field never offers a tool-wide answer.
57
+ */
58
+ session_approval_recipients?: string[];
52
59
  /**
53
60
  * The decision types the parked gate accepts for this call (`approve`,
54
61
  * `reject`, `edit`). Absent on older backends: only approve / reject
@@ -130,6 +137,13 @@ export interface PendingActionRequest {
130
137
  interruptId?: string;
131
138
  /** Present (true) when a session-wide approval would cover later calls of this tool. */
132
139
  sessionApprovalAllowed?: true;
140
+ /**
141
+ * Present on a send whose session-wide approval covers only the people it
142
+ * reaches (`session_approval_recipients`): what the card names for each of
143
+ * them, every hidden or control character written out as `<U+XXXX>`. Such a
144
+ * send never carries `sessionApprovalAllowed`.
145
+ */
146
+ sessionApprovalRecipients?: string[];
133
147
  /** Present (true) when the gate accepts an `edit` decision for this call. */
134
148
  editAllowed?: true;
135
149
  /** Present when the backend resolved anything about this call before asking (facts, a headline). */
@@ -158,10 +172,43 @@ export declare function pendingApprovalTitle({ hitl, actionRequests, }: {
158
172
  actionRequests: readonly PendingActionRequest[];
159
173
  }): PendingApprovalTitle | undefined;
160
174
  /**
161
- * Whether the card may offer "approve all for this session": every pending
162
- * call must be one the backend stamped as session-approvable, so a batch that
163
- * mixes in a tool the workspace pinned as always-prompt only offers the
164
- * one-time answer.
175
+ * `text` with every character a person can't see (control and format
176
+ * characters such as zero-width spaces, bidi overrides and Unicode tag
177
+ * characters, private-use and unassigned code points, and any separator but
178
+ * a plain space) written out as `<U+XXXX>`, so a name shows all of itself.
179
+ */
180
+ export declare function visibleText(text: string): string;
181
+ /**
182
+ * What "approve for this session" would cover for a pending approval:
183
+ *
184
+ * - `tools`: later calls of these tools (every call stamped
185
+ * `sessionApprovalAllowed`).
186
+ * - `recipients`: later sends to exactly these people, and nobody else
187
+ * (every call is a send stamped `sessionApprovalRecipients`).
188
+ */
189
+ export type SessionApprovalOffer = {
190
+ kind: 'tools';
191
+ } | {
192
+ kind: 'recipients';
193
+ recipients: readonly string[];
194
+ };
195
+ /**
196
+ * The session-wide approval the card may offer, or `undefined` for the
197
+ * one-time answer only: a batch that mixes in a tool the workspace pinned as
198
+ * always-prompt, a call stamped neither way, or tools beside recipient-scoped
199
+ * sends (no one label could say exactly what that answer covers).
200
+ */
201
+ export declare function sessionApprovalOffer(actionRequests: readonly PendingActionRequest[]): SessionApprovalOffer | undefined;
202
+ /**
203
+ * What the session option's button says: exactly what the answer covers.
204
+ * "Approve all for this session" for tools; for sends, every recipient by
205
+ * name: "Approve for this session: only kyle@partner.example and
206
+ * sam@partner.example".
207
+ */
208
+ export declare function sessionApprovalLabel(offer: SessionApprovalOffer): string;
209
+ /**
210
+ * Whether the card may offer a session-wide approval at all
211
+ * ({@link sessionApprovalOffer}).
165
212
  */
166
213
  export declare function canApproveForSession(actionRequests: readonly PendingActionRequest[]): boolean;
167
214
  /** An interrupt a non-resume dispatch dropped, published by the host. */
@@ -85,12 +85,16 @@ function pendingActionRequests(hitl) {
85
85
  const requests = Array.isArray(context.action_requests) ? context.action_requests.filter(isRecord) : context.tool_name && context.tool_args ? [{ name: context.tool_name, args: context.tool_args, tool_call_id: context.tool_call_id }] : [];
86
86
  return requests.map((request) => {
87
87
  const approvalContext = parsePendingApprovalContext(request.approval_context);
88
+ const sessionApprovalRecipients = parseSessionApprovalRecipients(
89
+ request.session_approval_recipients
90
+ );
88
91
  return {
89
92
  name: typeof request.name === "string" ? request.name : "tool",
90
93
  args: isRecord(request.args) ? request.args : {},
91
94
  ...typeof request.tool_call_id === "string" && { toolCallId: request.tool_call_id },
92
95
  ...typeof request.interrupt_id === "string" && { interruptId: request.interrupt_id },
93
96
  ...request.session_approval_allowed === true && { sessionApprovalAllowed: true },
97
+ ...sessionApprovalRecipients && { sessionApprovalRecipients },
94
98
  ...Array.isArray(request.allowed_decisions) && request.allowed_decisions.includes("edit") && { editAllowed: true },
95
99
  ...approvalContext && { approvalContext }
96
100
  };
@@ -109,8 +113,44 @@ function pendingApprovalTitle({
109
113
  if ((envelope == null ? void 0 : envelope.headline) === void 0) return void 0;
110
114
  return envelope.summary === void 0 ? { headline: envelope.headline } : { headline: envelope.headline, summary: envelope.summary };
111
115
  }
112
- function canApproveForSession(actionRequests) {
113
- return actionRequests.length > 0 && actionRequests.every((request) => request.sessionApprovalAllowed === true);
116
+ function visibleText(text) {
117
+ return text.replace(
118
+ /[\p{C}\p{Z}]/gu,
119
+ (character) => character === " " ? character : `<U+${(character.codePointAt(0) ?? 0).toString(16).toUpperCase().padStart(4, "0")}>`
120
+ );
121
+ }
122
+ function parseSessionApprovalRecipients(value) {
123
+ if (!Array.isArray(value) || value.length === 0) return void 0;
124
+ const names = [];
125
+ for (const name of value) {
126
+ if (typeof name !== "string" || name.trim() === "") return void 0;
127
+ names.push(visibleText(name));
128
+ }
129
+ return names;
130
+ }
131
+ function sessionApprovalOffer(actionRequests) {
132
+ if (actionRequests.length === 0) return void 0;
133
+ if (actionRequests.every(
134
+ (request) => request.sessionApprovalAllowed === true && request.sessionApprovalRecipients === void 0
135
+ )) {
136
+ return { kind: "tools" };
137
+ }
138
+ const recipients = [];
139
+ for (const request of actionRequests) {
140
+ if (request.sessionApprovalRecipients === void 0) return void 0;
141
+ for (const name of request.sessionApprovalRecipients) {
142
+ if (!recipients.includes(name)) recipients.push(name);
143
+ }
144
+ }
145
+ return { kind: "recipients", recipients };
146
+ }
147
+ function namesInProse(names) {
148
+ if (names.length <= 1) return names.join("");
149
+ return `${names.slice(0, -1).join(", ")} and ${names[names.length - 1]}`;
150
+ }
151
+ function sessionApprovalLabel(offer) {
152
+ if (offer.kind === "tools") return "Approve all for this session";
153
+ return `Approve for this session: only ${namesInProse(offer.recipients)}`;
114
154
  }
115
155
  function readAbandonedInputRequests(state) {
116
156
  const abandoned = readDeepAgentStreamValues(state).abandonedInputRequests;
@@ -331,7 +371,6 @@ export {
331
371
  INTERRUPT_DECLINE_REASON,
332
372
  actionRequestToolCallIds,
333
373
  asHitlApproval,
334
- canApproveForSession,
335
374
  fanLaneDecision,
336
375
  findAbandonedInterrupt,
337
376
  hitlResumeActionDecisions,
@@ -353,7 +392,10 @@ export {
353
392
  readInterruptMirror,
354
393
  readQueuedLaneDecisions,
355
394
  resumeValueApproves,
395
+ sessionApprovalLabel,
396
+ sessionApprovalOffer,
356
397
  toolCallIdsAwaitingApproval,
357
- useApprovalResumeLock
398
+ useApprovalResumeLock,
399
+ visibleText
358
400
  };
359
401
  //# sourceMappingURL=interrupts.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"interrupts.js","sources":["../../../../agent-runtime/src/interrupts.ts"],"sourcesContent":["/**\n * Human-in-the-loop interrupt primitives shared by every statewire chat\n * surface (Olympus, the React SDK, the Office add-in, the Chrome extension,\n * mobile).\n *\n * The agora deep-agent runtime parks a gated run on an `interrupt` input\n * request whose payload carries the Athena `hitl_approval` shape\n * (`athena_deep_agent/hitl_bridge.py`). A surface answers it with\n * `{ type: 'run/input', requestId, response: { type: 'resume', value } }` —\n * the exact command the backend's resume path consumes. This module holds the\n * payload contract (narrowing, resume values) and the shared resume-lock state\n * machine; rendering stays with each surface.\n *\n * Deliberately parameterized rather than self-wiring: these helpers never call\n * `useStatewireRuns`/`useAuiState` themselves. Each surface reads its own\n * statewire hooks (which its tests already mock) and passes the values in, so\n * the shared semantics cannot drift while per-surface wiring stays testable.\n */\n\nimport type { StatewireThread } from '@assistant-ui/react-statewire';\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport { hasClientToolInterruptSource } from './client-tools';\nimport { statewireTransportOutcome } from './errors';\nimport { readDeepAgentStreamValues } from './statewire-state';\n\n/** Human-in-the-loop approval payload the agora deep-agent runtime parks a run\n * on (shaped by `athena_deep_agent/hitl_bridge.py`). */\nexport interface HitlApprovalPayload {\n source: string;\n type: string;\n message: string;\n /** Risk headline/facts/warnings, when the host includes them. */\n approval_context?: unknown;\n context?: {\n tool_name?: string;\n tool_id?: string;\n tool_call_id?: string;\n tool_args?: Record<string, unknown>;\n pending_action_count?: number;\n approval_context?: unknown;\n /**\n * Every pending gated call (name + args), in decision order. Absent on\n * older backends — fall back to the single `tool_name`/`tool_args` pair.\n * Per-dispatch payloads also carry each lane's `tool_call_id` (the lane\n * card key) and `interrupt_id` (the lane resume key).\n */\n action_requests?: {\n name?: string;\n args?: Record<string, unknown>;\n tool_call_id?: string;\n interrupt_id?: string;\n /**\n * True when \"approve all for this session\" would take effect for this\n * tool (the workspace allows overriding its approval). Absent on older\n * backends and on non-overridable tools: the option is not offered.\n */\n session_approval_allowed?: boolean;\n /**\n * The decision types the parked gate accepts for this call (`approve`,\n * `reject`, `edit`). Absent on older backends: only approve / reject\n * are known to be accepted.\n */\n allowed_decisions?: string[];\n /** What the backend resolved about this call before asking (display-only). */\n approval_context?: unknown;\n }[];\n /**\n * True when each dispatch parked on its own interrupt: decisions submit\n * per lane via `lane_decisions` and each approved lane starts immediately,\n * instead of the legacy batch where one resume must answer every lane.\n */\n per_dispatch?: boolean;\n };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/** A pending `interrupt` input request; a present `response` key marks it answered. */\nexport function isPendingInterrupt(\n request: StatewireThread.InputRequestState,\n): request is StatewireThread.CustomInputRequest {\n return request.type === 'interrupt' && !('response' in request);\n}\n\n/**\n * Interrupt sources that were system pauses before the host stamped\n * `requires_user_action: false` on them (a background deep-research wait). Kept\n * only for the deploy skew between an older agora and a newer client; delete\n * once every deployment's raiser stamps the flag.\n */\nconst LEGACY_SYSTEM_PAUSE_INTERRUPT_SOURCES: ReadonlySet<string> = new Set(['deep_research_pending']);\n\n/**\n * A park nobody answers: the agent is still working (a background research\n * wait) and the backend resumes it itself. The raiser stamps\n * `requires_user_action: false`; older raisers are recognised by source.\n */\nexport function isSystemPauseInterrupt(payload: unknown): boolean {\n if (!isRecord(payload)) return false;\n if (payload.requires_user_action === false) return true;\n return (\n typeof payload.source === 'string' && LEGACY_SYSTEM_PAUSE_INTERRUPT_SOURCES.has(payload.source)\n );\n}\n\n/**\n * A pending interrupt an approval card may answer. Two kinds are excluded —\n * client-tool parks (the client-tool bridge owns those, and a generic resume\n * would corrupt the parked tool call) and system pauses (nothing to answer; the\n * backend resumes them). The source checks are deliberately loose so a skewed\n * deploy can never route one here.\n */\nexport function isApprovalCardInterrupt(\n request: StatewireThread.InputRequestState,\n): request is StatewireThread.CustomInputRequest {\n if (!isPendingInterrupt(request)) return false;\n const payload = (request as { payload?: unknown }).payload;\n return !hasClientToolInterruptSource(payload) && !isSystemPauseInterrupt(payload);\n}\n\n/** Narrow an interrupt payload to the Athena HITL approval shape. */\nexport function asHitlApproval(value: unknown): HitlApprovalPayload | null {\n if (!isRecord(value)) return null;\n return value.type === 'hitl' &&\n value.source === 'hitl_approval' &&\n typeof value.message === 'string'\n ? (value as unknown as HitlApprovalPayload)\n : null;\n}\n\n/** Default paused-run copy when an interrupt payload carries no message. */\nexport const DEFAULT_INTERRUPT_PAUSED_MESSAGE =\n 'The agent is paused and waiting for input.';\n\n/** Best-effort human-readable message from any interrupt payload. */\nexport function readInterruptMessage(\n value: unknown,\n fallback: string = DEFAULT_INTERRUPT_PAUSED_MESSAGE,\n): string {\n if (isRecord(value) && typeof value.message === 'string' && value.message.length > 0) {\n return value.message;\n }\n return fallback;\n}\n\n/** One label/value row of a call's resolved approval facts. */\nexport interface PendingApprovalFact {\n label: string;\n value: string;\n}\n\n/** One caution of a call's resolved approval facts. */\nexport interface PendingApprovalWarning {\n severity: 'info' | 'warning' | 'critical';\n message: string;\n}\n\nexport interface PendingApprovalSlackTarget {\n catalog_id: string;\n conversation_id: string | null;\n thread_ts: string | null;\n team_url: string | null;\n people: string[];\n}\n\n/**\n * What the backend resolved about a gated call before asking — e.g. where a\n * Slack write goes and as whom (agora `services/interrupts/approval_context.py`).\n * Display-only; the decision still answers the parked call.\n */\nexport interface PendingApprovalContext {\n facts: PendingApprovalFact[];\n warnings: PendingApprovalWarning[];\n /** The question the approval asks, in a line (\"Use Studio Mac in this conversation?\"). */\n headline?: string;\n /** What approving lets happen, a sentence or two under the headline. */\n summary?: string;\n slack_target?: PendingApprovalSlackTarget;\n}\n\nconst APPROVAL_WARNING_SEVERITIES = new Set(['info', 'warning', 'critical']);\n\nfunction isNullableString(value: unknown): value is string | null {\n return value === null || typeof value === 'string';\n}\n\n/** Display text: a string, trimmed; a blank or anything that is not a string is absent. */\nfunction displayText(value: unknown): string | undefined {\n if (typeof value !== 'string') return undefined;\n const text = value.trim();\n return text.length > 0 ? text : undefined;\n}\n\nfunction isStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.every((entry) => typeof entry === 'string');\n}\n\nfunction parsePendingApprovalSlackTarget(value: unknown): PendingApprovalSlackTarget | undefined {\n if (!isRecord(value) || typeof value.catalog_id !== 'string') return undefined;\n const conversationId = value.conversation_id;\n const threadTs = value.thread_ts;\n const teamUrl = value.team_url;\n const people = value.people === undefined ? [] : value.people;\n if (\n !isNullableString(conversationId) ||\n !isNullableString(threadTs) ||\n !isNullableString(teamUrl) ||\n !isStringArray(people)\n )\n return undefined;\n return {\n catalog_id: value.catalog_id,\n conversation_id: conversationId,\n thread_ts: threadTs,\n team_url: teamUrl,\n people,\n };\n}\n\n/** A call's `approval_context`, or `undefined` when absent or empty. */\nexport function parsePendingApprovalContext(value: unknown): PendingApprovalContext | undefined {\n if (!isRecord(value)) return undefined;\n const facts: PendingApprovalFact[] = Array.isArray(value.facts)\n ? value.facts.flatMap((fact) =>\n isRecord(fact) && typeof fact.label === 'string' && typeof fact.value === 'string'\n ? [{ label: fact.label, value: fact.value }]\n : [],\n )\n : [];\n const warnings: PendingApprovalWarning[] = Array.isArray(value.warnings)\n ? value.warnings.flatMap((warning) => {\n if (!isRecord(warning) || typeof warning.message !== 'string') return [];\n const severity =\n typeof warning.severity === 'string' && APPROVAL_WARNING_SEVERITIES.has(warning.severity)\n ? (warning.severity as PendingApprovalWarning['severity'])\n : 'info';\n return [{ severity, message: warning.message }];\n })\n : [];\n const headline = displayText(value.headline);\n const summary = displayText(value.summary);\n const slackTarget = parsePendingApprovalSlackTarget(value.slack_target);\n return facts.length > 0 || warnings.length > 0 || headline || summary || slackTarget\n ? {\n facts,\n warnings,\n ...(headline ? { headline } : {}),\n ...(summary ? { summary } : {}),\n ...(slackTarget ? { slack_target: slackTarget } : {}),\n }\n : undefined;\n}\n\nexport interface PendingActionRequest {\n name: string;\n args: Record<string, unknown>;\n /** The lane's `task` tool-call id (present on per-dispatch payloads). */\n toolCallId?: string;\n /** The lane's langgraph interrupt id — the `lane_decisions` resume key. */\n interruptId?: string;\n /** Present (true) when a session-wide approval would cover later calls of this tool. */\n sessionApprovalAllowed?: true;\n /** Present (true) when the gate accepts an `edit` decision for this call. */\n editAllowed?: true;\n /** Present when the backend resolved anything about this call before asking (facts, a headline). */\n approvalContext?: PendingApprovalContext;\n}\n\n/**\n * Every gated call this approval covers, in decision order. One resume answers\n * ALL of them, so every entry must stay reviewable somewhere. Falls back to\n * the single first-call fields on older backends without `action_requests`.\n */\nexport function pendingActionRequests(hitl: HitlApprovalPayload | null): PendingActionRequest[] {\n const context = hitl?.context;\n if (!context) return [];\n // The payload crosses the wire with only its top-level shape narrowed\n // (`asHitlApproval`), so the nested collection must be validated before any\n // array/property access: a malformed host value must degrade to \"no\n // reviewable entries\", never crash the approval surface.\n const requests: Record<string, unknown>[] = Array.isArray(context.action_requests)\n ? context.action_requests.filter(isRecord)\n : context.tool_name && context.tool_args\n ? [{ name: context.tool_name, args: context.tool_args, tool_call_id: context.tool_call_id }]\n : [];\n return requests.map((request) => {\n const approvalContext = parsePendingApprovalContext(request.approval_context);\n return {\n name: typeof request.name === 'string' ? request.name : 'tool',\n args: isRecord(request.args) ? request.args : {},\n ...(typeof request.tool_call_id === 'string' && { toolCallId: request.tool_call_id }),\n ...(typeof request.interrupt_id === 'string' && { interruptId: request.interrupt_id }),\n ...(request.session_approval_allowed === true && { sessionApprovalAllowed: true }),\n ...(Array.isArray(request.allowed_decisions) &&\n request.allowed_decisions.includes('edit') && { editAllowed: true }),\n ...(approvalContext && { approvalContext }),\n };\n });\n}\n\n/** The headline (and summary) that title a pending approval in place of the generic copy. */\nexport interface PendingApprovalTitle {\n headline: string;\n summary?: string;\n}\n\n/**\n * The title of a pending approval: the headline and summary of its one gated\n * call's resolved envelope (the call's own `approval_context`, else the copy\n * the host lifted to `context.approval_context`). A batch has none: one\n * decision answers every call, so no one call's headline may speak for it.\n * Display-only, like the envelope.\n */\nexport function pendingApprovalTitle({\n hitl,\n actionRequests,\n}: {\n hitl: HitlApprovalPayload | null;\n actionRequests: readonly PendingActionRequest[];\n}): PendingApprovalTitle | undefined {\n const [call, ...others] = actionRequests;\n if (hitl === null || call === undefined || others.length > 0) return undefined;\n // An older backend lists only the first call and counts the rest here; a\n // count that is not plainly one keeps the generic copy.\n const pendingCount = hitl.context?.pending_action_count;\n if (pendingCount !== undefined && pendingCount !== 1) return undefined;\n const envelope =\n call.approvalContext ?? parsePendingApprovalContext(hitl.context?.approval_context);\n if (envelope?.headline === undefined) return undefined;\n return envelope.summary === undefined\n ? { headline: envelope.headline }\n : { headline: envelope.headline, summary: envelope.summary };\n}\n\n/**\n * Whether the card may offer \"approve all for this session\": every pending\n * call must be one the backend stamped as session-approvable, so a batch that\n * mixes in a tool the workspace pinned as always-prompt only offers the\n * one-time answer.\n */\nexport function canApproveForSession(actionRequests: readonly PendingActionRequest[]): boolean {\n return (\n actionRequests.length > 0 &&\n actionRequests.every((request) => request.sessionApprovalAllowed === true)\n );\n}\n\n/** An interrupt a non-resume dispatch dropped, published by the host. */\nexport interface AbandonedInputRequest {\n id: string;\n type: string;\n}\n\n/**\n * `useStatewireState` selector body for the host-published abandoned input\n * requests. Reads through {@link readDeepAgentStreamValues} so the selector is\n * safe on the pre-attach (`undefined`) wire state, and returns the raw array\n * reference so the subscription stays shallow-stable — derive (`.find`) on the\n * result, never inside the selector.\n */\nexport function readAbandonedInputRequests(\n state: unknown,\n): readonly AbandonedInputRequest[] | undefined {\n const abandoned = readDeepAgentStreamValues(state).abandonedInputRequests;\n // Wire value: anything non-array (including a malformed host publication)\n // reads as \"none\" rather than crashing the consuming surface. Identity-\n // preserving for the valid case, so it stays selector-safe.\n return Array.isArray(abandoned)\n ? (abandoned as readonly AbandonedInputRequest[])\n : undefined;\n}\n\n/**\n * `useStatewireState` selector body for the host's mirrored interrupt\n * (`__interrupt__`, the LangGraph `{value, resumable}` wrapper). The mirror\n * is published the moment a lane parks on its gate — while sibling lanes are\n * still streaming and the run manager has NOT yet published an answerable\n * input request. Raw reference, selector-safe; unwrap with\n * {@link interruptMirrorPayload}.\n */\nexport function readInterruptMirror(state: unknown): unknown {\n return readDeepAgentStreamValues(state).__interrupt__;\n}\n\n/** The payload of a mirrored interrupt wrapper, or `undefined` for anything else. */\nexport function interruptMirrorPayload(mirror: unknown): unknown {\n return isRecord(mirror) ? mirror.value : undefined;\n}\n\n/**\n * `useStatewireState` selector body for the per-dispatch lane decisions the\n * host accepted through `run/lane-input` while the run was still streaming\n * (`queuedLaneDecisions`, keyed by langgraph interrupt id). Held until the\n * run parks, then answered as a native `lane_decisions` resume; every attached\n * client reads a queued lane as decided. Raw reference, selector-safe.\n */\nexport function readQueuedLaneDecisions(\n state: unknown,\n): Readonly<Record<string, unknown>> | undefined {\n const queued = readDeepAgentStreamValues(state).queuedLaneDecisions;\n return isRecord(queued) ? queued : undefined;\n}\n\n/**\n * The distinct langgraph interrupt ids a `lane_decisions` resume answers for\n * these gated calls, in first-seen order — or `null` when any call lacks its\n * stamp (a legacy batch payload only a positional resume can answer). A\n * nested HITL request gating several calls contributes one id: the lane is\n * the interrupt, and one decision fans across its calls server-side.\n */\nexport function laneInterruptIds(actionRequests: readonly PendingActionRequest[]): string[] | null {\n if (actionRequests.length === 0) return null;\n const ids: string[] = [];\n for (const request of actionRequests) {\n if (request.interruptId === undefined) return null;\n if (!ids.includes(request.interruptId)) ids.push(request.interruptId);\n }\n return ids;\n}\n\nconst NO_TOOL_CALL_IDS: ReadonlySet<string> = new Set();\n\n/**\n * The gated calls still waiting on a person, by `tool_call_id`: every call of\n * the approval on screen (the parked request's, or the host's mirror while\n * sibling lanes still stream), less the lanes whose decision the host already\n * holds (`queuedLaneDecisions`, keyed by interrupt id): a lane decided live\n * waits for the run to park, not for anyone. The id is the one join between an\n * approval and the call it gates (a payload names no lane), so a call without\n * one counts for nothing.\n */\nexport function toolCallIdsAwaitingApproval({\n actionRequests,\n queuedLaneDecisions,\n}: {\n actionRequests: readonly PendingActionRequest[];\n queuedLaneDecisions?: Readonly<Record<string, unknown>>;\n}): ReadonlySet<string> {\n let ids: Set<string> | undefined;\n for (const request of actionRequests) {\n if (request.toolCallId === undefined) continue;\n if (\n request.interruptId !== undefined &&\n queuedLaneDecisions !== undefined &&\n request.interruptId in queuedLaneDecisions\n ) {\n continue;\n }\n ids ??= new Set();\n ids.add(request.toolCallId);\n }\n return ids ?? NO_TOOL_CALL_IDS;\n}\n\n/** One decision for every lane — the `lane_decisions` map a run-wide approve or reject sends. */\nexport function fanLaneDecision<T>({\n interruptIds,\n decision,\n}: {\n interruptIds: readonly string[];\n decision: T;\n}): Record<string, T> {\n return Object.fromEntries(interruptIds.map((id) => [id, decision]));\n}\n\n/** The abandoned `interrupt` entry, if any — the \"approval was cancelled\" notice. */\nexport function findAbandonedInterrupt(\n abandoned: readonly AbandonedInputRequest[] | undefined,\n): AbandonedInputRequest | undefined {\n return abandoned?.find(\n (request) => isRecord(request) && request.type === 'interrupt' && typeof request.id === 'string',\n );\n}\n\n/** Canonical reason string carried by a user rejection. */\nexport const HITL_REJECT_REASON = 'User rejected the action.';\n\n/**\n * Default {@link UseApprovalResumeLockOptions.pendingResumeGraceMs} for every\n * approval surface: how long a clicked decision waits for its resume run to\n * start before the actions unlock again. Covers a `run/input` the host\n * rejected or a disconnected transport swallowed before any run began — the\n * one failure neither identity-change nor observed-run recovery can detect.\n */\nexport const HITL_PENDING_RESUME_GRACE_MS = 15_000;\n\n/**\n * Default {@link UseApprovalResumeLockOptions.maxTransportRetries}: how many\n * times a decision the transport lost (the websocket dropped or the statewire\n * session reset while the `run/input` was in flight) is re-sent on its own\n * before the user is asked to click again.\n */\nexport const HITL_RESUME_TRANSPORT_RETRIES = 3;\n\n/**\n * The dispatch died client-side without the host ever seeing it — a proven\n * session loss or a gone stream. Safe to re-send the same decision for the\n * same still-parked request. The complement (a server verdict, or the surface\n * unmounting) is not.\n */\nexport function isResumeTransportLoss(error: unknown): boolean {\n const outcome = statewireTransportOutcome(error);\n return outcome === 'session-lost' || outcome === 'stream-gone';\n}\n\n/** `run/input` resume value approving the gated call(s). */\nexport function hitlResumeApprove(): Record<string, string> {\n return { action: 'approve' };\n}\n\n/**\n * `run/input` resume value approving the gated call(s) AND every later call of\n * the same tool(s) on this thread. The backend records the tools from its own\n * parked interrupt (never from this payload), so the value carries no names.\n */\nexport function hitlResumeApproveForSession(): Record<string, string> {\n return { action: 'approve', scope: 'session' };\n}\n\n/** `run/input` resume value rejecting the gated call(s). */\nexport function hitlResumeReject(reason: string = HITL_REJECT_REASON): Record<string, string> {\n return { action: 'reject', reason };\n}\n\n/** One gated call's own answer inside a per-action resume ({@link hitlResumeActionDecisions}). */\nexport type HitlActionDecision =\n | { action: 'approve' }\n | { action: 'reject'; reason: string }\n | { action: 'edit'; edited_args: Record<string, unknown> };\n\n/**\n * The `tool_call_id` of every gated call, in decision order — or `null` when\n * any call lacks one or two calls share one, in which case no per-action\n * resume can be bound to the batch and only the batch answers above apply.\n */\nexport function actionRequestToolCallIds(\n actionRequests: readonly PendingActionRequest[],\n): string[] | null {\n if (actionRequests.length === 0) return null;\n const ids: string[] = [];\n for (const request of actionRequests) {\n if (request.toolCallId === undefined || ids.includes(request.toolCallId)) return null;\n ids.push(request.toolCallId);\n }\n return ids;\n}\n\n/**\n * `run/input` resume value deciding each gated call by its `tool_call_id` —\n * the only channel that carries an edited call. The backend binds every entry\n * to the parked request with that id, takes the tool NAME from its own\n * request (an edit can change a call's arguments, never which tool runs), and\n * refuses the whole answer — nothing runs — unless the map names exactly the\n * parked calls. `edited_args` is the complete argument set the call runs\n * with, not a patch. The envelope `action` mirrors the first entry for hosts\n * that only read the envelope; `action_decisions` is authoritative.\n */\nexport function hitlResumeActionDecisions(\n decisions: Readonly<Record<string, HitlActionDecision>>,\n): Record<string, unknown> {\n const [anchorId, anchor] = Object.entries(decisions)[0] ?? [];\n return {\n action: anchor?.action === 'reject' ? 'reject' : 'approve',\n ...(anchorId !== undefined && { tool_call_id: anchorId }),\n action_decisions: decisions,\n };\n}\n\n/** Answers that let nothing run: rejecting, or the timeout handler's answer. */\nconst NON_APPROVING_ACTIONS: ReadonlySet<string> = new Set(['reject', 'timeout']);\n\n/**\n * Whether a `run/input` resume value (or one lane's decision) lets anything\n * run: approving, approving for the session, an edit, or anything this cannot\n * read as a reject. Mirrors the host's `_decision_approves`, which fails\n * closed the same way, so a surface asks for a passkey check exactly where\n * the host will want one. Rejecting never needs a check (identity 0b.9,\n * call 9). A `lane_decisions` map approves when any of its lanes does.\n */\nexport function resumeValueApproves(value: unknown): boolean {\n if (!isRecord(value)) return true;\n const decisions = value.decisions;\n if (Array.isArray(decisions)) {\n return decisions.length === 0 || decisions.some(resumeValueApproves);\n }\n const perAction = value.action_decisions;\n if (perAction !== undefined) {\n if (!isRecord(perAction)) return true;\n const answers = Object.values(perAction);\n return answers.length === 0 || answers.some(resumeValueApproves);\n }\n const lanes = value.lane_decisions;\n if (lanes !== undefined) {\n if (!isRecord(lanes)) return true;\n const answers = Object.values(lanes);\n return answers.length === 0 || answers.some(resumeValueApproves);\n }\n const kind = value.type;\n if (typeof kind === 'string') return !NON_APPROVING_ACTIONS.has(kind);\n const action = value.action;\n return typeof action !== 'string' || !NON_APPROVING_ACTIONS.has(action);\n}\n\n/**\n * What rides beside a resume value on the `run/input` command. Today only the\n * passkey decision check (`decisionCheck`, identity 0b.9): the token a person\n * minted for exactly this answer, which the host classifies and spends.\n */\nexport interface ResumeExtras {\n readonly decisionCheck?: string;\n}\n\n/** Canonical reason string carried by a user declining a non-HITL park. */\nexport const INTERRUPT_DECLINE_REASON = 'User declined the request.';\n\n/**\n * `run/input` resume value declining a non-HITL park. `{ action: 'reject' }`\n * is the one value every typed raiser treats as a clean user decline\n * (`FormCancelledError` / `ToolRejectedError`); a generic `continue` would be\n * rejected by their resume schemas and surface as a tool error the agent then\n * improvises around.\n */\nexport function interruptResumeDecline(\n reason: string = INTERRUPT_DECLINE_REASON,\n): Record<string, string> {\n return { action: 'reject', reason };\n}\n\nexport interface UseApprovalResumeLockOptions<TValue> {\n /** The pending interrupt's request id (`undefined` when nothing is parked). */\n requestId: string | undefined;\n /** The surface's thread `isRunning` read (mockable per surface). */\n isRunning: boolean;\n /**\n * Dispatch the `run/input` resume for `requestId` on the surface's transport.\n * Return the dispatch promise so a refused or lost command releases the lock\n * at once and reaches `onResumeRejected`; a `void` return leaves the\n * recovery paths below as the only release.\n */\n sendResume: (\n requestId: string,\n value: TValue,\n extras?: ResumeExtras,\n ) => void | Promise<unknown>;\n /**\n * The dispatch `sendResume` returned rejected — the host refused the\n * `run/input` or the transport lost it. The lock is already released when\n * this fires; the surface tells the user, since the request is back on\n * screen with nothing else saying why.\n */\n onResumeRejected?: (error: unknown, requestId: string) => void;\n /**\n * A dispatch the transport lost is being re-sent for the still-parked\n * request; `attempt` counts the retries so far (1-based). Diagnostics only —\n * the lock stays held across the retry.\n */\n onResumeRetry?: (error: unknown, requestId: string, attempt: number) => void;\n /**\n * Automatic re-sends of a decision the transport lost\n * ({@link isResumeTransportLoss}) while the request is still parked. `0`\n * disables retries. Defaults to {@link HITL_RESUME_TRANSPORT_RETRIES}.\n */\n maxTransportRetries?: number;\n /**\n * Re-enable the actions when no resume run started within this window —\n * covers a `run/input` that was rejected or lost before any run began, so\n * neither reset path below can fire. Omit to keep the pre-existing surface\n * behavior of staying locked until a resume run is observed.\n */\n pendingResumeGraceMs?: number;\n}\n\nexport interface ApprovalResumeLock<TValue> {\n /** True after a resume until the request identity changes (or recovery fires). */\n pending: boolean;\n /**\n * Send the resume value, locking the actions. No-op (returns `undefined`)\n * while pending or unparked. Returns the dispatch promise when `sendResume`\n * produced one, so a surface can hold its own state until the host settles.\n */\n resume: (value: TValue, extras?: ResumeExtras) => Promise<unknown> | undefined;\n}\n\n/**\n * Optimistic action lock shared by every approval surface: disable the\n * decision buttons the moment one is clicked, until the request identity\n * changes (it clears after the resume, or a fresh interrupt arrives). This\n * closes the double-click → duplicate-resume window.\n *\n * Recovery paths, in order of specificity:\n * 0. The dispatch itself rejected (a `sendResume` that returned a promise) —\n * released at once, and reported through `onResumeRejected`. A rejection\n * that is a transport loss (the socket dropped mid-flight) is re-sent up to\n * `maxTransportRetries` times first, the lock held throughout, so a flaky\n * connection does not hand the card back to the user for another click.\n * The returned promise settles with the final attempt.\n * 1. Identity change — the resume landed (or a new interrupt replaced the\n * request). Reset during render via React's \"previous value\" pattern.\n * 2. A resume run was observed starting and then settling with the request\n * still pending (failed/cancelled resume) — clear the lock so the user can\n * answer again.\n * 3. Optional grace window ({@link UseApprovalResumeLockOptions.pendingResumeGraceMs})\n * for a `run/input` that never started a run at all.\n */\nexport function useApprovalResumeLock<TValue = Record<string, string>>(\n options: UseApprovalResumeLockOptions<TValue>,\n): ApprovalResumeLock<TValue> {\n const {\n requestId,\n isRunning,\n sendResume,\n pendingResumeGraceMs,\n onResumeRejected,\n onResumeRetry,\n maxTransportRetries = HITL_RESUME_TRANSPORT_RETRIES,\n } = options;\n\n const [pending, setPending] = useState(false);\n // Bumped per re-send so the grace window restarts with each attempt.\n const [attempt, setAttempt] = useState(0);\n const prevRequestIdRef = useRef(requestId);\n if (prevRequestIdRef.current !== requestId) {\n prevRequestIdRef.current = requestId;\n if (pending) setPending(false);\n }\n\n // Track the resume run we kicked off (isRunning observed true→false while\n // pending) and clear `pending` when it ends with the request unresolved.\n const sawResumeRunRef = useRef(false);\n useEffect(() => {\n if (!pending) {\n sawResumeRunRef.current = false;\n return;\n }\n if (isRunning) {\n sawResumeRunRef.current = true;\n return;\n }\n if (sawResumeRunRef.current) {\n sawResumeRunRef.current = false;\n setPending(false);\n }\n }, [pending, isRunning]);\n\n // Pre-start failure recovery: re-enable after the grace window when no\n // resume run began; answering the still-parked request again is exactly\n // what the user needs to be able to do.\n useEffect(() => {\n if (!pending || pendingResumeGraceMs === undefined) return;\n const timer = setTimeout(() => {\n if (!sawResumeRunRef.current) setPending(false);\n }, pendingResumeGraceMs);\n return () => clearTimeout(timer);\n }, [pending, requestId, pendingResumeGraceMs, attempt]);\n\n const sendResumeRef = useRef(sendResume);\n sendResumeRef.current = sendResume;\n const onResumeRejectedRef = useRef(onResumeRejected);\n onResumeRejectedRef.current = onResumeRejected;\n const onResumeRetryRef = useRef(onResumeRetry);\n onResumeRetryRef.current = onResumeRetry;\n const maxTransportRetriesRef = useRef(maxTransportRetries);\n maxTransportRetriesRef.current = maxTransportRetries;\n const currentRequestIdRef = useRef(requestId);\n currentRequestIdRef.current = requestId;\n const pendingRef = useRef(pending);\n pendingRef.current = pending;\n // One token per `resume` call: a settle from an earlier submission must not\n // retry over, or release, a newer one for the same request.\n const submissionRef = useRef(0);\n\n const resume = useCallback(\n (value: TValue, extras?: ResumeExtras): Promise<unknown> | undefined => {\n if (requestId === undefined || pendingRef.current) return undefined;\n const submission = ++submissionRef.current;\n setPending(true);\n pendingRef.current = true;\n setAttempt(0);\n const first: unknown = sendResumeRef.current(requestId, value, extras);\n if (!(first instanceof Promise)) return undefined;\n // The final failure is already reported through `onResumeRejected`;\n // callers that drop the promise must not get an unhandled rejection.\n const outcome = new Promise<unknown>((resolve, reject) => {\n const settle = (dispatched: Promise<unknown>, retries: number) => {\n dispatched.then(resolve, (error: unknown) => {\n // A late refusal for a request the lock has since moved past (the\n // park was re-keyed, or a newer answer is in flight) must neither\n // release that answer's lock nor be reported as a failure of it.\n // The optimistic fold hides the answered request until the fold\n // rolls back, so `undefined` here is not a departure.\n const current = currentRequestIdRef.current;\n if (\n submission !== submissionRef.current ||\n (current !== undefined && current !== requestId)\n ) {\n reject(error);\n return;\n }\n if (retries < maxTransportRetriesRef.current && isResumeTransportLoss(error)) {\n const next = retries + 1;\n onResumeRetryRef.current?.(error, requestId, next);\n setPending(true);\n pendingRef.current = true;\n setAttempt(next);\n const resent: unknown = sendResumeRef.current(requestId, value, extras);\n if (resent instanceof Promise) {\n settle(resent, next);\n return;\n }\n resolve(undefined);\n return;\n }\n setPending(false);\n pendingRef.current = false;\n onResumeRejectedRef.current?.(error, requestId);\n reject(error);\n });\n };\n settle(first, 0);\n });\n outcome.catch(() => {});\n return outcome;\n },\n [requestId],\n );\n\n return { pending, resume };\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"interrupts.js","sources":["../../../../agent-runtime/src/interrupts.ts"],"sourcesContent":["/**\n * Human-in-the-loop interrupt primitives shared by every statewire chat\n * surface (Olympus, the React SDK, the Office add-in, the Chrome extension,\n * mobile).\n *\n * The agora deep-agent runtime parks a gated run on an `interrupt` input\n * request whose payload carries the Athena `hitl_approval` shape\n * (`athena_deep_agent/hitl_bridge.py`). A surface answers it with\n * `{ type: 'run/input', requestId, response: { type: 'resume', value } }` —\n * the exact command the backend's resume path consumes. This module holds the\n * payload contract (narrowing, resume values) and the shared resume-lock state\n * machine; rendering stays with each surface.\n *\n * Deliberately parameterized rather than self-wiring: these helpers never call\n * `useStatewireRuns`/`useAuiState` themselves. Each surface reads its own\n * statewire hooks (which its tests already mock) and passes the values in, so\n * the shared semantics cannot drift while per-surface wiring stays testable.\n */\n\nimport type { StatewireThread } from '@assistant-ui/react-statewire';\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport { hasClientToolInterruptSource } from './client-tools';\nimport { statewireTransportOutcome } from './errors';\nimport { readDeepAgentStreamValues } from './statewire-state';\n\n/** Human-in-the-loop approval payload the agora deep-agent runtime parks a run\n * on (shaped by `athena_deep_agent/hitl_bridge.py`). */\nexport interface HitlApprovalPayload {\n source: string;\n type: string;\n message: string;\n /** Risk headline/facts/warnings, when the host includes them. */\n approval_context?: unknown;\n context?: {\n tool_name?: string;\n tool_id?: string;\n tool_call_id?: string;\n tool_args?: Record<string, unknown>;\n pending_action_count?: number;\n approval_context?: unknown;\n /**\n * Every pending gated call (name + args), in decision order. Absent on\n * older backends — fall back to the single `tool_name`/`tool_args` pair.\n * Per-dispatch payloads also carry each lane's `tool_call_id` (the lane\n * card key) and `interrupt_id` (the lane resume key).\n */\n action_requests?: {\n name?: string;\n args?: Record<string, unknown>;\n tool_call_id?: string;\n interrupt_id?: string;\n /**\n * True when \"approve all for this session\" would take effect for this\n * tool (the workspace allows overriding its approval). Absent on older\n * backends and on non-overridable tools: the option is not offered.\n */\n session_approval_allowed?: boolean;\n /**\n * On a send with an act adapter: the people a session-wide approval of\n * it covers, and only them, by the name the card shows (the address or\n * number). Such a send carries `session_approval_allowed: false`, so a\n * client that predates this field never offers a tool-wide answer.\n */\n session_approval_recipients?: string[];\n /**\n * The decision types the parked gate accepts for this call (`approve`,\n * `reject`, `edit`). Absent on older backends: only approve / reject\n * are known to be accepted.\n */\n allowed_decisions?: string[];\n /** What the backend resolved about this call before asking (display-only). */\n approval_context?: unknown;\n }[];\n /**\n * True when each dispatch parked on its own interrupt: decisions submit\n * per lane via `lane_decisions` and each approved lane starts immediately,\n * instead of the legacy batch where one resume must answer every lane.\n */\n per_dispatch?: boolean;\n };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/** A pending `interrupt` input request; a present `response` key marks it answered. */\nexport function isPendingInterrupt(\n request: StatewireThread.InputRequestState,\n): request is StatewireThread.CustomInputRequest {\n return request.type === 'interrupt' && !('response' in request);\n}\n\n/**\n * Interrupt sources that were system pauses before the host stamped\n * `requires_user_action: false` on them (a background deep-research wait). Kept\n * only for the deploy skew between an older agora and a newer client; delete\n * once every deployment's raiser stamps the flag.\n */\nconst LEGACY_SYSTEM_PAUSE_INTERRUPT_SOURCES: ReadonlySet<string> = new Set(['deep_research_pending']);\n\n/**\n * A park nobody answers: the agent is still working (a background research\n * wait) and the backend resumes it itself. The raiser stamps\n * `requires_user_action: false`; older raisers are recognised by source.\n */\nexport function isSystemPauseInterrupt(payload: unknown): boolean {\n if (!isRecord(payload)) return false;\n if (payload.requires_user_action === false) return true;\n return (\n typeof payload.source === 'string' && LEGACY_SYSTEM_PAUSE_INTERRUPT_SOURCES.has(payload.source)\n );\n}\n\n/**\n * A pending interrupt an approval card may answer. Two kinds are excluded —\n * client-tool parks (the client-tool bridge owns those, and a generic resume\n * would corrupt the parked tool call) and system pauses (nothing to answer; the\n * backend resumes them). The source checks are deliberately loose so a skewed\n * deploy can never route one here.\n */\nexport function isApprovalCardInterrupt(\n request: StatewireThread.InputRequestState,\n): request is StatewireThread.CustomInputRequest {\n if (!isPendingInterrupt(request)) return false;\n const payload = (request as { payload?: unknown }).payload;\n return !hasClientToolInterruptSource(payload) && !isSystemPauseInterrupt(payload);\n}\n\n/** Narrow an interrupt payload to the Athena HITL approval shape. */\nexport function asHitlApproval(value: unknown): HitlApprovalPayload | null {\n if (!isRecord(value)) return null;\n return value.type === 'hitl' &&\n value.source === 'hitl_approval' &&\n typeof value.message === 'string'\n ? (value as unknown as HitlApprovalPayload)\n : null;\n}\n\n/** Default paused-run copy when an interrupt payload carries no message. */\nexport const DEFAULT_INTERRUPT_PAUSED_MESSAGE =\n 'The agent is paused and waiting for input.';\n\n/** Best-effort human-readable message from any interrupt payload. */\nexport function readInterruptMessage(\n value: unknown,\n fallback: string = DEFAULT_INTERRUPT_PAUSED_MESSAGE,\n): string {\n if (isRecord(value) && typeof value.message === 'string' && value.message.length > 0) {\n return value.message;\n }\n return fallback;\n}\n\n/** One label/value row of a call's resolved approval facts. */\nexport interface PendingApprovalFact {\n label: string;\n value: string;\n}\n\n/** One caution of a call's resolved approval facts. */\nexport interface PendingApprovalWarning {\n severity: 'info' | 'warning' | 'critical';\n message: string;\n}\n\nexport interface PendingApprovalSlackTarget {\n catalog_id: string;\n conversation_id: string | null;\n thread_ts: string | null;\n team_url: string | null;\n people: string[];\n}\n\n/**\n * What the backend resolved about a gated call before asking — e.g. where a\n * Slack write goes and as whom (agora `services/interrupts/approval_context.py`).\n * Display-only; the decision still answers the parked call.\n */\nexport interface PendingApprovalContext {\n facts: PendingApprovalFact[];\n warnings: PendingApprovalWarning[];\n /** The question the approval asks, in a line (\"Use Studio Mac in this conversation?\"). */\n headline?: string;\n /** What approving lets happen, a sentence or two under the headline. */\n summary?: string;\n slack_target?: PendingApprovalSlackTarget;\n}\n\nconst APPROVAL_WARNING_SEVERITIES = new Set(['info', 'warning', 'critical']);\n\nfunction isNullableString(value: unknown): value is string | null {\n return value === null || typeof value === 'string';\n}\n\n/** Display text: a string, trimmed; a blank or anything that is not a string is absent. */\nfunction displayText(value: unknown): string | undefined {\n if (typeof value !== 'string') return undefined;\n const text = value.trim();\n return text.length > 0 ? text : undefined;\n}\n\nfunction isStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.every((entry) => typeof entry === 'string');\n}\n\nfunction parsePendingApprovalSlackTarget(value: unknown): PendingApprovalSlackTarget | undefined {\n if (!isRecord(value) || typeof value.catalog_id !== 'string') return undefined;\n const conversationId = value.conversation_id;\n const threadTs = value.thread_ts;\n const teamUrl = value.team_url;\n const people = value.people === undefined ? [] : value.people;\n if (\n !isNullableString(conversationId) ||\n !isNullableString(threadTs) ||\n !isNullableString(teamUrl) ||\n !isStringArray(people)\n )\n return undefined;\n return {\n catalog_id: value.catalog_id,\n conversation_id: conversationId,\n thread_ts: threadTs,\n team_url: teamUrl,\n people,\n };\n}\n\n/** A call's `approval_context`, or `undefined` when absent or empty. */\nexport function parsePendingApprovalContext(value: unknown): PendingApprovalContext | undefined {\n if (!isRecord(value)) return undefined;\n const facts: PendingApprovalFact[] = Array.isArray(value.facts)\n ? value.facts.flatMap((fact) =>\n isRecord(fact) && typeof fact.label === 'string' && typeof fact.value === 'string'\n ? [{ label: fact.label, value: fact.value }]\n : [],\n )\n : [];\n const warnings: PendingApprovalWarning[] = Array.isArray(value.warnings)\n ? value.warnings.flatMap((warning) => {\n if (!isRecord(warning) || typeof warning.message !== 'string') return [];\n const severity =\n typeof warning.severity === 'string' && APPROVAL_WARNING_SEVERITIES.has(warning.severity)\n ? (warning.severity as PendingApprovalWarning['severity'])\n : 'info';\n return [{ severity, message: warning.message }];\n })\n : [];\n const headline = displayText(value.headline);\n const summary = displayText(value.summary);\n const slackTarget = parsePendingApprovalSlackTarget(value.slack_target);\n return facts.length > 0 || warnings.length > 0 || headline || summary || slackTarget\n ? {\n facts,\n warnings,\n ...(headline ? { headline } : {}),\n ...(summary ? { summary } : {}),\n ...(slackTarget ? { slack_target: slackTarget } : {}),\n }\n : undefined;\n}\n\nexport interface PendingActionRequest {\n name: string;\n args: Record<string, unknown>;\n /** The lane's `task` tool-call id (present on per-dispatch payloads). */\n toolCallId?: string;\n /** The lane's langgraph interrupt id — the `lane_decisions` resume key. */\n interruptId?: string;\n /** Present (true) when a session-wide approval would cover later calls of this tool. */\n sessionApprovalAllowed?: true;\n /**\n * Present on a send whose session-wide approval covers only the people it\n * reaches (`session_approval_recipients`): what the card names for each of\n * them, every hidden or control character written out as `<U+XXXX>`. Such a\n * send never carries `sessionApprovalAllowed`.\n */\n sessionApprovalRecipients?: string[];\n /** Present (true) when the gate accepts an `edit` decision for this call. */\n editAllowed?: true;\n /** Present when the backend resolved anything about this call before asking (facts, a headline). */\n approvalContext?: PendingApprovalContext;\n}\n\n/**\n * Every gated call this approval covers, in decision order. One resume answers\n * ALL of them, so every entry must stay reviewable somewhere. Falls back to\n * the single first-call fields on older backends without `action_requests`.\n */\nexport function pendingActionRequests(hitl: HitlApprovalPayload | null): PendingActionRequest[] {\n const context = hitl?.context;\n if (!context) return [];\n // The payload crosses the wire with only its top-level shape narrowed\n // (`asHitlApproval`), so the nested collection must be validated before any\n // array/property access: a malformed host value must degrade to \"no\n // reviewable entries\", never crash the approval surface.\n const requests: Record<string, unknown>[] = Array.isArray(context.action_requests)\n ? context.action_requests.filter(isRecord)\n : context.tool_name && context.tool_args\n ? [{ name: context.tool_name, args: context.tool_args, tool_call_id: context.tool_call_id }]\n : [];\n return requests.map((request) => {\n const approvalContext = parsePendingApprovalContext(request.approval_context);\n const sessionApprovalRecipients = parseSessionApprovalRecipients(\n request.session_approval_recipients,\n );\n return {\n name: typeof request.name === 'string' ? request.name : 'tool',\n args: isRecord(request.args) ? request.args : {},\n ...(typeof request.tool_call_id === 'string' && { toolCallId: request.tool_call_id }),\n ...(typeof request.interrupt_id === 'string' && { interruptId: request.interrupt_id }),\n ...(request.session_approval_allowed === true && { sessionApprovalAllowed: true }),\n ...(sessionApprovalRecipients && { sessionApprovalRecipients }),\n ...(Array.isArray(request.allowed_decisions) &&\n request.allowed_decisions.includes('edit') && { editAllowed: true }),\n ...(approvalContext && { approvalContext }),\n };\n });\n}\n\n/** The headline (and summary) that title a pending approval in place of the generic copy. */\nexport interface PendingApprovalTitle {\n headline: string;\n summary?: string;\n}\n\n/**\n * The title of a pending approval: the headline and summary of its one gated\n * call's resolved envelope (the call's own `approval_context`, else the copy\n * the host lifted to `context.approval_context`). A batch has none: one\n * decision answers every call, so no one call's headline may speak for it.\n * Display-only, like the envelope.\n */\nexport function pendingApprovalTitle({\n hitl,\n actionRequests,\n}: {\n hitl: HitlApprovalPayload | null;\n actionRequests: readonly PendingActionRequest[];\n}): PendingApprovalTitle | undefined {\n const [call, ...others] = actionRequests;\n if (hitl === null || call === undefined || others.length > 0) return undefined;\n // An older backend lists only the first call and counts the rest here; a\n // count that is not plainly one keeps the generic copy.\n const pendingCount = hitl.context?.pending_action_count;\n if (pendingCount !== undefined && pendingCount !== 1) return undefined;\n const envelope =\n call.approvalContext ?? parsePendingApprovalContext(hitl.context?.approval_context);\n if (envelope?.headline === undefined) return undefined;\n return envelope.summary === undefined\n ? { headline: envelope.headline }\n : { headline: envelope.headline, summary: envelope.summary };\n}\n\n/**\n * `text` with every character a person can't see (control and format\n * characters such as zero-width spaces, bidi overrides and Unicode tag\n * characters, private-use and unassigned code points, and any separator but\n * a plain space) written out as `<U+XXXX>`, so a name shows all of itself.\n */\nexport function visibleText(text: string): string {\n return text.replace(/[\\p{C}\\p{Z}]/gu, (character) =>\n character === ' '\n ? character\n : `<U+${(character.codePointAt(0) ?? 0).toString(16).toUpperCase().padStart(4, '0')}>`,\n );\n}\n\n/** A send's stamped recipient names: a non-empty list of non-empty strings, or nothing. */\nfunction parseSessionApprovalRecipients(value: unknown): string[] | undefined {\n if (!Array.isArray(value) || value.length === 0) return undefined;\n const names: string[] = [];\n for (const name of value) {\n if (typeof name !== 'string' || name.trim() === '') return undefined;\n names.push(visibleText(name));\n }\n return names;\n}\n\n/**\n * What \"approve for this session\" would cover for a pending approval:\n *\n * - `tools`: later calls of these tools (every call stamped\n * `sessionApprovalAllowed`).\n * - `recipients`: later sends to exactly these people, and nobody else\n * (every call is a send stamped `sessionApprovalRecipients`).\n */\nexport type SessionApprovalOffer =\n | { kind: 'tools' }\n | { kind: 'recipients'; recipients: readonly string[] };\n\n/**\n * The session-wide approval the card may offer, or `undefined` for the\n * one-time answer only: a batch that mixes in a tool the workspace pinned as\n * always-prompt, a call stamped neither way, or tools beside recipient-scoped\n * sends (no one label could say exactly what that answer covers).\n */\nexport function sessionApprovalOffer(\n actionRequests: readonly PendingActionRequest[],\n): SessionApprovalOffer | undefined {\n if (actionRequests.length === 0) return undefined;\n if (\n actionRequests.every(\n (request) =>\n request.sessionApprovalAllowed === true && request.sessionApprovalRecipients === undefined,\n )\n ) {\n return { kind: 'tools' };\n }\n const recipients: string[] = [];\n for (const request of actionRequests) {\n if (request.sessionApprovalRecipients === undefined) return undefined;\n for (const name of request.sessionApprovalRecipients) {\n if (!recipients.includes(name)) recipients.push(name);\n }\n }\n return { kind: 'recipients', recipients };\n}\n\n/** `a`, `a and b`, `a, b and c`. */\nfunction namesInProse(names: readonly string[]): string {\n if (names.length <= 1) return names.join('');\n return `${names.slice(0, -1).join(', ')} and ${names[names.length - 1]}`;\n}\n\n/**\n * What the session option's button says: exactly what the answer covers.\n * \"Approve all for this session\" for tools; for sends, every recipient by\n * name: \"Approve for this session: only kyle@partner.example and\n * sam@partner.example\".\n */\nexport function sessionApprovalLabel(offer: SessionApprovalOffer): string {\n if (offer.kind === 'tools') return 'Approve all for this session';\n return `Approve for this session: only ${namesInProse(offer.recipients)}`;\n}\n\n/**\n * Whether the card may offer a session-wide approval at all\n * ({@link sessionApprovalOffer}).\n */\nexport function canApproveForSession(actionRequests: readonly PendingActionRequest[]): boolean {\n return sessionApprovalOffer(actionRequests) !== undefined;\n}\n\n/** An interrupt a non-resume dispatch dropped, published by the host. */\nexport interface AbandonedInputRequest {\n id: string;\n type: string;\n}\n\n/**\n * `useStatewireState` selector body for the host-published abandoned input\n * requests. Reads through {@link readDeepAgentStreamValues} so the selector is\n * safe on the pre-attach (`undefined`) wire state, and returns the raw array\n * reference so the subscription stays shallow-stable — derive (`.find`) on the\n * result, never inside the selector.\n */\nexport function readAbandonedInputRequests(\n state: unknown,\n): readonly AbandonedInputRequest[] | undefined {\n const abandoned = readDeepAgentStreamValues(state).abandonedInputRequests;\n // Wire value: anything non-array (including a malformed host publication)\n // reads as \"none\" rather than crashing the consuming surface. Identity-\n // preserving for the valid case, so it stays selector-safe.\n return Array.isArray(abandoned)\n ? (abandoned as readonly AbandonedInputRequest[])\n : undefined;\n}\n\n/**\n * `useStatewireState` selector body for the host's mirrored interrupt\n * (`__interrupt__`, the LangGraph `{value, resumable}` wrapper). The mirror\n * is published the moment a lane parks on its gate — while sibling lanes are\n * still streaming and the run manager has NOT yet published an answerable\n * input request. Raw reference, selector-safe; unwrap with\n * {@link interruptMirrorPayload}.\n */\nexport function readInterruptMirror(state: unknown): unknown {\n return readDeepAgentStreamValues(state).__interrupt__;\n}\n\n/** The payload of a mirrored interrupt wrapper, or `undefined` for anything else. */\nexport function interruptMirrorPayload(mirror: unknown): unknown {\n return isRecord(mirror) ? mirror.value : undefined;\n}\n\n/**\n * `useStatewireState` selector body for the per-dispatch lane decisions the\n * host accepted through `run/lane-input` while the run was still streaming\n * (`queuedLaneDecisions`, keyed by langgraph interrupt id). Held until the\n * run parks, then answered as a native `lane_decisions` resume; every attached\n * client reads a queued lane as decided. Raw reference, selector-safe.\n */\nexport function readQueuedLaneDecisions(\n state: unknown,\n): Readonly<Record<string, unknown>> | undefined {\n const queued = readDeepAgentStreamValues(state).queuedLaneDecisions;\n return isRecord(queued) ? queued : undefined;\n}\n\n/**\n * The distinct langgraph interrupt ids a `lane_decisions` resume answers for\n * these gated calls, in first-seen order — or `null` when any call lacks its\n * stamp (a legacy batch payload only a positional resume can answer). A\n * nested HITL request gating several calls contributes one id: the lane is\n * the interrupt, and one decision fans across its calls server-side.\n */\nexport function laneInterruptIds(actionRequests: readonly PendingActionRequest[]): string[] | null {\n if (actionRequests.length === 0) return null;\n const ids: string[] = [];\n for (const request of actionRequests) {\n if (request.interruptId === undefined) return null;\n if (!ids.includes(request.interruptId)) ids.push(request.interruptId);\n }\n return ids;\n}\n\nconst NO_TOOL_CALL_IDS: ReadonlySet<string> = new Set();\n\n/**\n * The gated calls still waiting on a person, by `tool_call_id`: every call of\n * the approval on screen (the parked request's, or the host's mirror while\n * sibling lanes still stream), less the lanes whose decision the host already\n * holds (`queuedLaneDecisions`, keyed by interrupt id): a lane decided live\n * waits for the run to park, not for anyone. The id is the one join between an\n * approval and the call it gates (a payload names no lane), so a call without\n * one counts for nothing.\n */\nexport function toolCallIdsAwaitingApproval({\n actionRequests,\n queuedLaneDecisions,\n}: {\n actionRequests: readonly PendingActionRequest[];\n queuedLaneDecisions?: Readonly<Record<string, unknown>>;\n}): ReadonlySet<string> {\n let ids: Set<string> | undefined;\n for (const request of actionRequests) {\n if (request.toolCallId === undefined) continue;\n if (\n request.interruptId !== undefined &&\n queuedLaneDecisions !== undefined &&\n request.interruptId in queuedLaneDecisions\n ) {\n continue;\n }\n ids ??= new Set();\n ids.add(request.toolCallId);\n }\n return ids ?? NO_TOOL_CALL_IDS;\n}\n\n/** One decision for every lane — the `lane_decisions` map a run-wide approve or reject sends. */\nexport function fanLaneDecision<T>({\n interruptIds,\n decision,\n}: {\n interruptIds: readonly string[];\n decision: T;\n}): Record<string, T> {\n return Object.fromEntries(interruptIds.map((id) => [id, decision]));\n}\n\n/** The abandoned `interrupt` entry, if any — the \"approval was cancelled\" notice. */\nexport function findAbandonedInterrupt(\n abandoned: readonly AbandonedInputRequest[] | undefined,\n): AbandonedInputRequest | undefined {\n return abandoned?.find(\n (request) => isRecord(request) && request.type === 'interrupt' && typeof request.id === 'string',\n );\n}\n\n/** Canonical reason string carried by a user rejection. */\nexport const HITL_REJECT_REASON = 'User rejected the action.';\n\n/**\n * Default {@link UseApprovalResumeLockOptions.pendingResumeGraceMs} for every\n * approval surface: how long a clicked decision waits for its resume run to\n * start before the actions unlock again. Covers a `run/input` the host\n * rejected or a disconnected transport swallowed before any run began — the\n * one failure neither identity-change nor observed-run recovery can detect.\n */\nexport const HITL_PENDING_RESUME_GRACE_MS = 15_000;\n\n/**\n * Default {@link UseApprovalResumeLockOptions.maxTransportRetries}: how many\n * times a decision the transport lost (the websocket dropped or the statewire\n * session reset while the `run/input` was in flight) is re-sent on its own\n * before the user is asked to click again.\n */\nexport const HITL_RESUME_TRANSPORT_RETRIES = 3;\n\n/**\n * The dispatch died client-side without the host ever seeing it — a proven\n * session loss or a gone stream. Safe to re-send the same decision for the\n * same still-parked request. The complement (a server verdict, or the surface\n * unmounting) is not.\n */\nexport function isResumeTransportLoss(error: unknown): boolean {\n const outcome = statewireTransportOutcome(error);\n return outcome === 'session-lost' || outcome === 'stream-gone';\n}\n\n/** `run/input` resume value approving the gated call(s). */\nexport function hitlResumeApprove(): Record<string, string> {\n return { action: 'approve' };\n}\n\n/**\n * `run/input` resume value approving the gated call(s) AND every later call of\n * the same tool(s) on this thread. The backend records the tools from its own\n * parked interrupt (never from this payload), so the value carries no names.\n */\nexport function hitlResumeApproveForSession(): Record<string, string> {\n return { action: 'approve', scope: 'session' };\n}\n\n/** `run/input` resume value rejecting the gated call(s). */\nexport function hitlResumeReject(reason: string = HITL_REJECT_REASON): Record<string, string> {\n return { action: 'reject', reason };\n}\n\n/** One gated call's own answer inside a per-action resume ({@link hitlResumeActionDecisions}). */\nexport type HitlActionDecision =\n | { action: 'approve' }\n | { action: 'reject'; reason: string }\n | { action: 'edit'; edited_args: Record<string, unknown> };\n\n/**\n * The `tool_call_id` of every gated call, in decision order — or `null` when\n * any call lacks one or two calls share one, in which case no per-action\n * resume can be bound to the batch and only the batch answers above apply.\n */\nexport function actionRequestToolCallIds(\n actionRequests: readonly PendingActionRequest[],\n): string[] | null {\n if (actionRequests.length === 0) return null;\n const ids: string[] = [];\n for (const request of actionRequests) {\n if (request.toolCallId === undefined || ids.includes(request.toolCallId)) return null;\n ids.push(request.toolCallId);\n }\n return ids;\n}\n\n/**\n * `run/input` resume value deciding each gated call by its `tool_call_id` —\n * the only channel that carries an edited call. The backend binds every entry\n * to the parked request with that id, takes the tool NAME from its own\n * request (an edit can change a call's arguments, never which tool runs), and\n * refuses the whole answer — nothing runs — unless the map names exactly the\n * parked calls. `edited_args` is the complete argument set the call runs\n * with, not a patch. The envelope `action` mirrors the first entry for hosts\n * that only read the envelope; `action_decisions` is authoritative.\n */\nexport function hitlResumeActionDecisions(\n decisions: Readonly<Record<string, HitlActionDecision>>,\n): Record<string, unknown> {\n const [anchorId, anchor] = Object.entries(decisions)[0] ?? [];\n return {\n action: anchor?.action === 'reject' ? 'reject' : 'approve',\n ...(anchorId !== undefined && { tool_call_id: anchorId }),\n action_decisions: decisions,\n };\n}\n\n/** Answers that let nothing run: rejecting, or the timeout handler's answer. */\nconst NON_APPROVING_ACTIONS: ReadonlySet<string> = new Set(['reject', 'timeout']);\n\n/**\n * Whether a `run/input` resume value (or one lane's decision) lets anything\n * run: approving, approving for the session, an edit, or anything this cannot\n * read as a reject. Mirrors the host's `_decision_approves`, which fails\n * closed the same way, so a surface asks for a passkey check exactly where\n * the host will want one. Rejecting never needs a check (identity 0b.9,\n * call 9). A `lane_decisions` map approves when any of its lanes does.\n */\nexport function resumeValueApproves(value: unknown): boolean {\n if (!isRecord(value)) return true;\n const decisions = value.decisions;\n if (Array.isArray(decisions)) {\n return decisions.length === 0 || decisions.some(resumeValueApproves);\n }\n const perAction = value.action_decisions;\n if (perAction !== undefined) {\n if (!isRecord(perAction)) return true;\n const answers = Object.values(perAction);\n return answers.length === 0 || answers.some(resumeValueApproves);\n }\n const lanes = value.lane_decisions;\n if (lanes !== undefined) {\n if (!isRecord(lanes)) return true;\n const answers = Object.values(lanes);\n return answers.length === 0 || answers.some(resumeValueApproves);\n }\n const kind = value.type;\n if (typeof kind === 'string') return !NON_APPROVING_ACTIONS.has(kind);\n const action = value.action;\n return typeof action !== 'string' || !NON_APPROVING_ACTIONS.has(action);\n}\n\n/**\n * What rides beside a resume value on the `run/input` command. Today only the\n * passkey decision check (`decisionCheck`, identity 0b.9): the token a person\n * minted for exactly this answer, which the host classifies and spends.\n */\nexport interface ResumeExtras {\n readonly decisionCheck?: string;\n}\n\n/** Canonical reason string carried by a user declining a non-HITL park. */\nexport const INTERRUPT_DECLINE_REASON = 'User declined the request.';\n\n/**\n * `run/input` resume value declining a non-HITL park. `{ action: 'reject' }`\n * is the one value every typed raiser treats as a clean user decline\n * (`FormCancelledError` / `ToolRejectedError`); a generic `continue` would be\n * rejected by their resume schemas and surface as a tool error the agent then\n * improvises around.\n */\nexport function interruptResumeDecline(\n reason: string = INTERRUPT_DECLINE_REASON,\n): Record<string, string> {\n return { action: 'reject', reason };\n}\n\nexport interface UseApprovalResumeLockOptions<TValue> {\n /** The pending interrupt's request id (`undefined` when nothing is parked). */\n requestId: string | undefined;\n /** The surface's thread `isRunning` read (mockable per surface). */\n isRunning: boolean;\n /**\n * Dispatch the `run/input` resume for `requestId` on the surface's transport.\n * Return the dispatch promise so a refused or lost command releases the lock\n * at once and reaches `onResumeRejected`; a `void` return leaves the\n * recovery paths below as the only release.\n */\n sendResume: (\n requestId: string,\n value: TValue,\n extras?: ResumeExtras,\n ) => void | Promise<unknown>;\n /**\n * The dispatch `sendResume` returned rejected — the host refused the\n * `run/input` or the transport lost it. The lock is already released when\n * this fires; the surface tells the user, since the request is back on\n * screen with nothing else saying why.\n */\n onResumeRejected?: (error: unknown, requestId: string) => void;\n /**\n * A dispatch the transport lost is being re-sent for the still-parked\n * request; `attempt` counts the retries so far (1-based). Diagnostics only —\n * the lock stays held across the retry.\n */\n onResumeRetry?: (error: unknown, requestId: string, attempt: number) => void;\n /**\n * Automatic re-sends of a decision the transport lost\n * ({@link isResumeTransportLoss}) while the request is still parked. `0`\n * disables retries. Defaults to {@link HITL_RESUME_TRANSPORT_RETRIES}.\n */\n maxTransportRetries?: number;\n /**\n * Re-enable the actions when no resume run started within this window —\n * covers a `run/input` that was rejected or lost before any run began, so\n * neither reset path below can fire. Omit to keep the pre-existing surface\n * behavior of staying locked until a resume run is observed.\n */\n pendingResumeGraceMs?: number;\n}\n\nexport interface ApprovalResumeLock<TValue> {\n /** True after a resume until the request identity changes (or recovery fires). */\n pending: boolean;\n /**\n * Send the resume value, locking the actions. No-op (returns `undefined`)\n * while pending or unparked. Returns the dispatch promise when `sendResume`\n * produced one, so a surface can hold its own state until the host settles.\n */\n resume: (value: TValue, extras?: ResumeExtras) => Promise<unknown> | undefined;\n}\n\n/**\n * Optimistic action lock shared by every approval surface: disable the\n * decision buttons the moment one is clicked, until the request identity\n * changes (it clears after the resume, or a fresh interrupt arrives). This\n * closes the double-click → duplicate-resume window.\n *\n * Recovery paths, in order of specificity:\n * 0. The dispatch itself rejected (a `sendResume` that returned a promise) —\n * released at once, and reported through `onResumeRejected`. A rejection\n * that is a transport loss (the socket dropped mid-flight) is re-sent up to\n * `maxTransportRetries` times first, the lock held throughout, so a flaky\n * connection does not hand the card back to the user for another click.\n * The returned promise settles with the final attempt.\n * 1. Identity change — the resume landed (or a new interrupt replaced the\n * request). Reset during render via React's \"previous value\" pattern.\n * 2. A resume run was observed starting and then settling with the request\n * still pending (failed/cancelled resume) — clear the lock so the user can\n * answer again.\n * 3. Optional grace window ({@link UseApprovalResumeLockOptions.pendingResumeGraceMs})\n * for a `run/input` that never started a run at all.\n */\nexport function useApprovalResumeLock<TValue = Record<string, string>>(\n options: UseApprovalResumeLockOptions<TValue>,\n): ApprovalResumeLock<TValue> {\n const {\n requestId,\n isRunning,\n sendResume,\n pendingResumeGraceMs,\n onResumeRejected,\n onResumeRetry,\n maxTransportRetries = HITL_RESUME_TRANSPORT_RETRIES,\n } = options;\n\n const [pending, setPending] = useState(false);\n // Bumped per re-send so the grace window restarts with each attempt.\n const [attempt, setAttempt] = useState(0);\n const prevRequestIdRef = useRef(requestId);\n if (prevRequestIdRef.current !== requestId) {\n prevRequestIdRef.current = requestId;\n if (pending) setPending(false);\n }\n\n // Track the resume run we kicked off (isRunning observed true→false while\n // pending) and clear `pending` when it ends with the request unresolved.\n const sawResumeRunRef = useRef(false);\n useEffect(() => {\n if (!pending) {\n sawResumeRunRef.current = false;\n return;\n }\n if (isRunning) {\n sawResumeRunRef.current = true;\n return;\n }\n if (sawResumeRunRef.current) {\n sawResumeRunRef.current = false;\n setPending(false);\n }\n }, [pending, isRunning]);\n\n // Pre-start failure recovery: re-enable after the grace window when no\n // resume run began; answering the still-parked request again is exactly\n // what the user needs to be able to do.\n useEffect(() => {\n if (!pending || pendingResumeGraceMs === undefined) return;\n const timer = setTimeout(() => {\n if (!sawResumeRunRef.current) setPending(false);\n }, pendingResumeGraceMs);\n return () => clearTimeout(timer);\n }, [pending, requestId, pendingResumeGraceMs, attempt]);\n\n const sendResumeRef = useRef(sendResume);\n sendResumeRef.current = sendResume;\n const onResumeRejectedRef = useRef(onResumeRejected);\n onResumeRejectedRef.current = onResumeRejected;\n const onResumeRetryRef = useRef(onResumeRetry);\n onResumeRetryRef.current = onResumeRetry;\n const maxTransportRetriesRef = useRef(maxTransportRetries);\n maxTransportRetriesRef.current = maxTransportRetries;\n const currentRequestIdRef = useRef(requestId);\n currentRequestIdRef.current = requestId;\n const pendingRef = useRef(pending);\n pendingRef.current = pending;\n // One token per `resume` call: a settle from an earlier submission must not\n // retry over, or release, a newer one for the same request.\n const submissionRef = useRef(0);\n\n const resume = useCallback(\n (value: TValue, extras?: ResumeExtras): Promise<unknown> | undefined => {\n if (requestId === undefined || pendingRef.current) return undefined;\n const submission = ++submissionRef.current;\n setPending(true);\n pendingRef.current = true;\n setAttempt(0);\n const first: unknown = sendResumeRef.current(requestId, value, extras);\n if (!(first instanceof Promise)) return undefined;\n // The final failure is already reported through `onResumeRejected`;\n // callers that drop the promise must not get an unhandled rejection.\n const outcome = new Promise<unknown>((resolve, reject) => {\n const settle = (dispatched: Promise<unknown>, retries: number) => {\n dispatched.then(resolve, (error: unknown) => {\n // A late refusal for a request the lock has since moved past (the\n // park was re-keyed, or a newer answer is in flight) must neither\n // release that answer's lock nor be reported as a failure of it.\n // The optimistic fold hides the answered request until the fold\n // rolls back, so `undefined` here is not a departure.\n const current = currentRequestIdRef.current;\n if (\n submission !== submissionRef.current ||\n (current !== undefined && current !== requestId)\n ) {\n reject(error);\n return;\n }\n if (retries < maxTransportRetriesRef.current && isResumeTransportLoss(error)) {\n const next = retries + 1;\n onResumeRetryRef.current?.(error, requestId, next);\n setPending(true);\n pendingRef.current = true;\n setAttempt(next);\n const resent: unknown = sendResumeRef.current(requestId, value, extras);\n if (resent instanceof Promise) {\n settle(resent, next);\n return;\n }\n resolve(undefined);\n return;\n }\n setPending(false);\n pendingRef.current = false;\n onResumeRejectedRef.current?.(error, requestId);\n reject(error);\n });\n };\n settle(first, 0);\n });\n outcome.catch(() => {});\n return outcome;\n },\n [requestId],\n );\n\n return { pending, resume };\n}\n"],"names":[],"mappings":"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"}