@sema-agent/client-core 0.97.0 → 0.98.0

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 (281) hide show
  1. package/CHANGELOG.md +429 -0
  2. package/README.md +74 -36
  3. package/dist/adapt/arms.d.ts +1 -0
  4. package/dist/adapt/arms.js +128 -34
  5. package/dist/adapt/ids.d.ts +12 -1
  6. package/dist/adapt/ids.js +59 -8
  7. package/dist/adapt/instanceLedger.d.ts +50 -0
  8. package/dist/adapt/instanceLedger.js +147 -0
  9. package/dist/adapt/textStream.d.ts +1 -0
  10. package/dist/adapt/textStream.js +24 -7
  11. package/dist/adapt/toolCards.d.ts +27 -5
  12. package/dist/adapt/toolCards.js +39 -6
  13. package/dist/adapt/wireShapes.js +6 -2
  14. package/dist/adapt.d.ts +2 -0
  15. package/dist/adapt.js +8 -3
  16. package/dist/adapter/activeRunSelfHeal.d.ts +5 -0
  17. package/dist/adapter/activeRunSelfHeal.js +51 -28
  18. package/dist/adapter/downstream/eventToSdkMessage.d.ts +2 -5
  19. package/dist/adapter/downstream/eventToSdkMessage.js +27 -34
  20. package/dist/adapter/downstream/terminalToSdkResult.js +11 -4
  21. package/dist/adapter/downstream/turnUsageToModelUsage.d.ts +5 -2
  22. package/dist/adapter/downstream/turnUsageToModelUsage.js +2 -0
  23. package/dist/adapter/runStream.js +29 -13
  24. package/dist/adapter/types.d.ts +4 -4
  25. package/dist/agentSession/backgroundView.js +2 -1
  26. package/dist/agentSession/engineAgentRegistryRead.d.ts +2 -2
  27. package/dist/agentSession/engineAgentRegistryRead.js +12 -13
  28. package/dist/agentsWireCaps.d.ts +8 -6
  29. package/dist/agentsWireCaps.js +177 -162
  30. package/dist/approvalDecidedByCapability.d.ts +0 -4
  31. package/dist/approvalDecidedByCapability.js +0 -6
  32. package/dist/approvalsStreamLiveCapability.d.ts +0 -4
  33. package/dist/approvalsStreamLiveCapability.js +0 -6
  34. package/dist/assistantInbox.d.ts +34 -0
  35. package/dist/assistantInbox.js +118 -0
  36. package/dist/centerWiringCapability.js +9 -1
  37. package/dist/clientSlice.d.ts +0 -9
  38. package/dist/clientSlice.js +0 -3
  39. package/dist/cloudConfigWireCaps.d.ts +4 -2
  40. package/dist/cloudConfigWireCaps.js +81 -61
  41. package/dist/compensations.js +1 -1
  42. package/dist/connectorStatus.d.ts +21 -0
  43. package/dist/connectorStatus.js +112 -0
  44. package/dist/controlRouter.d.ts +14 -1
  45. package/dist/controlRouter.js +68 -17
  46. package/dist/coreValuePorts.d.ts +3 -3
  47. package/dist/decideReceipt.d.ts +2 -2
  48. package/dist/decideReceipt.js +5 -2
  49. package/dist/declared/supportFloorDeclared.js +3 -3
  50. package/dist/deviceExecutorManagementCapability.d.ts +0 -4
  51. package/dist/deviceExecutorManagementCapability.js +0 -6
  52. package/dist/deviceRebind.d.ts +49 -1
  53. package/dist/deviceRebind.js +221 -3
  54. package/dist/displayUntrusted.d.ts +1 -0
  55. package/dist/displayUntrusted.js +250 -76
  56. package/dist/engineAgentAbsence.d.ts +1 -1
  57. package/dist/engineAgentPanelStore.d.ts +5 -4
  58. package/dist/engineAgentPanelStore.js +3 -12
  59. package/dist/engineCapReader.d.ts +0 -4
  60. package/dist/engineCapReader.js +1 -5
  61. package/dist/engineCapsCache.d.ts +4 -0
  62. package/dist/engineCapsCache.js +17 -12
  63. package/dist/engineClosedSets.d.ts +1 -0
  64. package/dist/engineClosedSets.js +4 -1
  65. package/dist/engineErrorCodes.d.ts +9 -2
  66. package/dist/engineErrorCodes.js +9 -1
  67. package/dist/engineIdentity.d.ts +1 -0
  68. package/dist/engineIdentity.js +8 -0
  69. package/dist/engineRowRef.d.ts +28 -0
  70. package/dist/engineRowRef.js +111 -0
  71. package/dist/engineSessionMessages.d.ts +67 -0
  72. package/dist/engineSessionMessages.js +364 -0
  73. package/dist/engineToolDirectory.d.ts +18 -0
  74. package/dist/engineToolDirectory.js +85 -0
  75. package/dist/engineUrl.d.ts +18 -0
  76. package/dist/engineUrl.js +52 -0
  77. package/dist/env/uiLanguage.d.ts +9 -0
  78. package/dist/env/uiLanguage.js +35 -0
  79. package/dist/eventLeg.d.ts +92 -0
  80. package/dist/eventLeg.js +285 -0
  81. package/dist/executionLaneCapability.d.ts +0 -4
  82. package/dist/executionLaneCapability.js +3 -16
  83. package/dist/fileHistoryCaptureCapability.d.ts +0 -4
  84. package/dist/fileHistoryCaptureCapability.js +5 -7
  85. package/dist/fleet/fleetLedger.d.ts +0 -3
  86. package/dist/fleet/fleetLedger.js +21 -36
  87. package/dist/fleet/fleetProjection.d.ts +2 -2
  88. package/dist/fleet/fleetProjection.js +10 -8
  89. package/dist/frameBinding.d.ts +5 -0
  90. package/dist/frameBinding.js +23 -0
  91. package/dist/gateFailureCodes.d.ts +2 -0
  92. package/dist/gateFailureCodes.js +15 -0
  93. package/dist/gateOutcome.d.ts +3 -1
  94. package/dist/gateOutcome.js +25 -6
  95. package/dist/gateVocabulary.d.ts +5 -0
  96. package/dist/gateVocabulary.js +64 -2
  97. package/dist/generated/engineAskFaceTables.js +2 -2
  98. package/dist/generated/engineFactTables.d.ts +6 -3
  99. package/dist/generated/engineFactTables.js +22 -4
  100. package/dist/generated/engineNoticeTables.js +9 -0
  101. package/dist/generated/toolNameTables.d.ts +1 -0
  102. package/dist/generated/toolNameTables.js +1 -0
  103. package/dist/hitl/allowRuleShadow.d.ts +6 -1
  104. package/dist/hitl/allowRuleShadow.js +21 -17
  105. package/dist/hitl/approvalCardReading.d.ts +15 -0
  106. package/dist/hitl/approvalCardReading.js +154 -0
  107. package/dist/hitl/approvalCardReasons.d.ts +13 -0
  108. package/dist/hitl/approvalCardReasons.js +120 -0
  109. package/dist/hitl/approvalHolds.d.ts +47 -15
  110. package/dist/hitl/approvalHolds.js +134 -28
  111. package/dist/hitl/approvalResolution.d.ts +51 -26
  112. package/dist/hitl/approvalResolution.js +86 -122
  113. package/dist/hitl/approvalsFeed.js +6 -18
  114. package/dist/hitl/askClassPair.d.ts +2 -0
  115. package/dist/hitl/askClassPair.js +1 -0
  116. package/dist/hitl/askGateWire.d.ts +20 -2
  117. package/dist/hitl/askGateWire.js +49 -1
  118. package/dist/hitl/askParkRowRouting.d.ts +5 -0
  119. package/dist/hitl/askParkRowRouting.js +32 -1
  120. package/dist/hitl/askSettlementLine.d.ts +18 -0
  121. package/dist/hitl/askSettlementLine.js +79 -0
  122. package/dist/hitl/automaticAnswerVerdict.d.ts +1 -1
  123. package/dist/hitl/automaticAnswerVerdict.js +21 -5
  124. package/dist/hitl/crashConverged.d.ts +11 -20
  125. package/dist/hitl/crashConverged.js +56 -0
  126. package/dist/hitl/crashConvergedNotice.d.ts +3 -1
  127. package/dist/hitl/crashConvergedNotice.js +48 -11
  128. package/dist/hitl/decisionConflict.js +3 -10
  129. package/dist/hitl/decisionRefusal.d.ts +40 -1
  130. package/dist/hitl/decisionRefusal.js +199 -42
  131. package/dist/hitl/frameRouter.d.ts +5 -2
  132. package/dist/hitl/frameRouter.js +20 -16
  133. package/dist/hitl/gateLedger.d.ts +2 -0
  134. package/dist/hitl/gateLedger.js +50 -13
  135. package/dist/hitl/hitlBridge.d.ts +1 -2
  136. package/dist/hitl/hitlBridge.js +10 -7
  137. package/dist/hitl/livePendingAsk.d.ts +3 -3
  138. package/dist/hitl/livePendingAsk.js +7 -18
  139. package/dist/hitl/parkResolver.d.ts +2 -24
  140. package/dist/hitl/parkResolver.js +61 -97
  141. package/dist/hitl/persistedRulesWire.d.ts +24 -0
  142. package/dist/hitl/persistedRulesWire.js +122 -2
  143. package/dist/hitl/planReviewWire.d.ts +26 -13
  144. package/dist/hitl/planReviewWire.js +112 -78
  145. package/dist/hitl/refusedToolNameProse.d.ts +0 -1
  146. package/dist/hitl/refusedToolNameProse.js +0 -3
  147. package/dist/hitl/replayedApproval.d.ts +4 -3
  148. package/dist/hitl/replayedApproval.js +7 -4
  149. package/dist/hitl/sessionPolicyDeliverable.js +2 -4
  150. package/dist/hitl/sessionPolicyWire.d.ts +32 -20
  151. package/dist/hitl/sessionPolicyWire.js +162 -52
  152. package/dist/hitl/standingRows.d.ts +20 -2
  153. package/dist/hitl/standingRows.js +69 -8
  154. package/dist/hitl/toolApprovalWire.d.ts +87 -152
  155. package/dist/hitl/toolApprovalWire.js +591 -380
  156. package/dist/hookMatcherVerdict.d.ts +6 -1
  157. package/dist/hookMatcherVerdict.js +14 -7
  158. package/dist/hooksWireCaps.d.ts +2 -2
  159. package/dist/hooksWireCaps.js +59 -55
  160. package/dist/i18n/messages.en.d.ts +121 -0
  161. package/dist/i18n/messages.en.js +119 -0
  162. package/dist/i18n/messages.zh.d.ts +3 -0
  163. package/dist/i18n/messages.zh.js +120 -0
  164. package/dist/i18n/translate.d.ts +25 -0
  165. package/dist/i18n/translate.js +127 -0
  166. package/dist/index.d.ts +53 -18
  167. package/dist/index.js +38 -10
  168. package/dist/inheritEnvCapability.d.ts +31 -0
  169. package/dist/inheritEnvCapability.js +104 -0
  170. package/dist/liveModelCatalog.d.ts +1 -2
  171. package/dist/liveModelCatalog.js +2 -6
  172. package/dist/liveQuestionStore.d.ts +2 -21
  173. package/dist/mcpLiveness.d.ts +2 -1
  174. package/dist/mcpProbeWire.d.ts +6 -1
  175. package/dist/mcpProbeWire.js +1 -1
  176. package/dist/mcpWireCaps.d.ts +13 -2
  177. package/dist/mcpWireCaps.js +48 -0
  178. package/dist/memoryEntriesWire.d.ts +4 -9
  179. package/dist/model/modelSupplyRules.d.ts +0 -2
  180. package/dist/model/modelSupplyRules.js +3 -12
  181. package/dist/model/modelsPolicy.d.ts +0 -5
  182. package/dist/model/modelsPolicy.js +6 -36
  183. package/dist/model/subagentModelWord.d.ts +31 -0
  184. package/dist/model/subagentModelWord.js +261 -0
  185. package/dist/notifications.d.ts +6 -0
  186. package/dist/notifications.js +71 -23
  187. package/dist/outputStyleSeatCapability.d.ts +0 -3
  188. package/dist/outputStyleSeatCapability.js +0 -3
  189. package/dist/ownKey.d.ts +15 -0
  190. package/dist/ownKey.js +60 -3
  191. package/dist/panelReduce.d.ts +15 -9
  192. package/dist/panelReduce.js +680 -549
  193. package/dist/panelStateHolder.d.ts +3 -7
  194. package/dist/panelStateHolder.js +14 -130
  195. package/dist/peerLaneCapability.d.ts +0 -4
  196. package/dist/peerLaneCapability.js +0 -6
  197. package/dist/permissionRulesWriteCapability.d.ts +0 -4
  198. package/dist/permissionRulesWriteCapability.js +0 -6
  199. package/dist/permissionWireCaps.d.ts +2 -2
  200. package/dist/permissionWireCaps.js +2 -0
  201. package/dist/permissionsAllowGrantCapability.d.ts +0 -4
  202. package/dist/permissionsAllowGrantCapability.js +0 -6
  203. package/dist/permissionsDeclaration.d.ts +0 -11
  204. package/dist/permissionsDeclaration.js +60 -53
  205. package/dist/pluginHooksWire.d.ts +1 -1
  206. package/dist/pluginHooksWire.js +3 -6
  207. package/dist/preemptWire.d.ts +44 -0
  208. package/dist/preemptWire.js +83 -0
  209. package/dist/printToolResultFrame.js +15 -4
  210. package/dist/replayBody.d.ts +3 -0
  211. package/dist/replayBody.js +14 -0
  212. package/dist/request/taskRequest.d.ts +0 -1
  213. package/dist/request/taskRequest.js +0 -3
  214. package/dist/resumeRefusalCopy.d.ts +2 -1
  215. package/dist/resumeRefusalCopy.js +38 -2
  216. package/dist/retryStatus.d.ts +8 -0
  217. package/dist/retryStatus.js +106 -1
  218. package/dist/rewindArchiveCapability.d.ts +0 -4
  219. package/dist/rewindArchiveCapability.js +2 -10
  220. package/dist/rewindArchiveReading.d.ts +0 -1
  221. package/dist/rewindArchiveReading.js +0 -3
  222. package/dist/runCancelContext.d.ts +2 -1
  223. package/dist/runCancelContext.js +4 -2
  224. package/dist/runReattachPolicy.d.ts +31 -0
  225. package/dist/runReattachPolicy.js +46 -0
  226. package/dist/runTerminal.d.ts +3 -6
  227. package/dist/runTerminal.js +8 -9
  228. package/dist/sandboxWire.d.ts +3 -1
  229. package/dist/sandboxWire.js +76 -27
  230. package/dist/scenarioWire.js +1 -1
  231. package/dist/seam.d.ts +24 -2
  232. package/dist/seam.js +15 -3
  233. package/dist/seatContract.d.ts +53 -10
  234. package/dist/seatContract.js +132 -81
  235. package/dist/sessionPolicyCapability.d.ts +0 -4
  236. package/dist/sessionPolicyCapability.js +0 -6
  237. package/dist/sessionSyncFileHistoryCapability.d.ts +0 -3
  238. package/dist/sessionSyncFileHistoryCapability.js +0 -3
  239. package/dist/skillToolListsCapability.d.ts +0 -3
  240. package/dist/skillToolListsCapability.js +0 -3
  241. package/dist/sqlEngineCapability.d.ts +0 -4
  242. package/dist/sqlEngineCapability.js +0 -6
  243. package/dist/steerPriorityCapability.d.ts +0 -3
  244. package/dist/steerPriorityCapability.js +0 -3
  245. package/dist/streamDrainingLegs.d.ts +4 -0
  246. package/dist/streamDrainingLegs.js +6 -1
  247. package/dist/subagent/engineCompactWire.d.ts +0 -1
  248. package/dist/subagent/engineCompactWire.js +7 -5
  249. package/dist/subagent/engineTaskHandleWire.js +4 -7
  250. package/dist/subagentContentStore.js +1 -11
  251. package/dist/submitReopen.d.ts +24 -0
  252. package/dist/submitReopen.js +75 -0
  253. package/dist/terminalStatuses.d.ts +3 -0
  254. package/dist/terminalStatuses.js +10 -2
  255. package/dist/toolResult.d.ts +10 -1
  256. package/dist/toolResult.js +34 -6
  257. package/dist/toolRoster.d.ts +0 -2
  258. package/dist/toolRoster.js +0 -2
  259. package/dist/turnFailurePresentation.d.ts +28 -0
  260. package/dist/turnFailurePresentation.js +205 -0
  261. package/dist/userInvokedSkillsCapability.d.ts +0 -3
  262. package/dist/userInvokedSkillsCapability.js +0 -3
  263. package/dist/webSearchBackendCapability.d.ts +0 -4
  264. package/dist/webSearchBackendCapability.js +0 -6
  265. package/dist/wireErrorTriage.js +5 -24
  266. package/dist/wireFailureShape.d.ts +62 -5
  267. package/dist/wireFailureShape.js +105 -5
  268. package/dist/wireRefusalCopy.d.ts +12 -2
  269. package/dist/wireRefusalCopy.js +50 -52
  270. package/dist/workflowsJournalCapability.d.ts +0 -4
  271. package/dist/workflowsJournalCapability.js +0 -6
  272. package/dist/writeProtectionCapability.d.ts +0 -4
  273. package/dist/writeProtectionCapability.js +0 -6
  274. package/docs/INTEGRATION-CLIENTS.md +1305 -46
  275. package/package.json +5 -4
  276. package/dist/attachmentSeatCapability.d.ts +0 -12
  277. package/dist/attachmentSeatCapability.js +0 -23
  278. package/dist/hitl/approvalOutcomeNote.d.ts +0 -10
  279. package/dist/hitl/approvalOutcomeNote.js +0 -29
  280. package/dist/sessionReadRedactionCapability.d.ts +0 -13
  281. package/dist/sessionReadRedactionCapability.js +0 -35
@@ -1,27 +1,29 @@
1
- import { APPROVAL_CARD_PORT_KEY_MISMATCH_REASON, DEFAULT_DENY_REASON, DecideTransportRetryExhaustedError, HitlBridge, NO_APPROVAL_CARD_PORT_REASON, denyReasonForWire, findPendingForTask, hitlSafetyCodeOf, readDecideCurrentPending, readWireCodes, readWireErrorCodeReading, } from './hitlBridge.js';
1
+ import { APPROVAL_CARD_PORT_KEY_MISMATCH_REASON, DEFAULT_DENY_REASON, HitlBridge, NO_APPROVAL_CARD_PORT_REASON, denyReasonForWire, findPendingForTask, readWireErrorCodeReading, } from './hitlBridge.js';
2
2
  import { askParkRowArm } from './askParkRowRouting.js';
3
3
  import { parkGateArmOf } from './frameRouter.js';
4
4
  import { seatSessionRowOf } from './standingRows.js';
5
5
  import { hostLog } from '../host.js';
6
6
  import { guardedRead, readAskOrigin, readOwnDataNonEmptyString, readOwnDataValue, readOwnNonEmptyString } from '../ownKey.js';
7
- import { readAskClassPair } from './askClassPair.js';
8
- import { readAllowRuleShadowFromWire } from './allowRuleShadow.js';
7
+ import { DEPLOYMENT_LIST_ASK_CLASSES, readAskClassPair } from './askClassPair.js';
8
+ import { invokeHostCallbackIsolated } from '../hostCallbackGuard.js';
9
+ import { approvalWindowKeysOf, readRequiresRealApproval } from './approvalCardReading.js';
10
+ import { readAllowRuleShadowFromWire, retiredAllowRuleShadowKeyPresent, RETIRED_SHADOW_EVIDENCE_LABEL } from './allowRuleShadow.js';
9
11
  import { createSessionSlot, DEFAULT_SESSION_KEY } from '../sessionSlot.js';
10
12
  import { readEngineActiveBgTasks } from '../fleet/fleetLedger.js';
11
13
  import { CORE_SKILL_TOOL_NAME } from '../generated/toolNameTables.js';
12
14
  import { INTERRUPT_DENY_REASON, observeCancelByDeny, surfaceApprovalParking, surfaceRememberNotApplied, surfaceEditNotForwarded, surfaceEditRefusedOnBlindAsk, surfaceRuleArmNotSent, surfaceRuleArmRejected } from './hitlHostSurface.js';
13
15
  import { approvalCallKey, liveFrameCallKey } from './gateIdentity.js';
14
16
  import { displayUntrusted } from '../displayUntrusted.js';
15
- import { approvalDecisionHolds, awaitLiveCardHolder, holdLiveCard, joinLiveCard, liveCardHoldFor, liveCardHoldOf, clearSettledApprovals, settledApprovalOf, settledForDurableLane, settledLiveCardOutcome } from './approvalHolds.js';
16
- import { approvalResolutionOf } from './approvalResolution.js';
17
+ import { frameBindingOf } from '../frameBinding.js';
18
+ import { approvalDecisionHolds, approvalHoldFor, awaitApprovalHolder, claimApprovalCardFor, holdLiveCard, joinLiveCard, liveCardHoldOf, clearSettledApprovals, noteHolderSettledApproval, settledApprovalOf, settledForDurableLane, settledLiveCardOutcome } from './approvalHolds.js';
19
+ import { joinedResolution, resolutionOfDecisionFailure } from './approvalResolution.js';
20
+ import { parkRowDeadlineState } from './parkExpiryJudgements.js';
17
21
  import { isApprovalMandateWord } from '../gateVocabulary.js';
18
22
  import { denyAttributionRefusalFromError } from '../wireRefusalCopy.js';
19
23
  import { approvalDecidedByAdmitted } from '../approvalDecidedByCapability.js';
20
24
  import { decideFailureNote } from '../decideFailureNote.js';
21
- import { readRefusalField, thrownIsInstanceOf, wireFailureTextsOf } from '../wireFailureShape.js';
22
- import { refusalCauseWordOf } from './approvalEndingCause.js';
23
- import { readDecisionRefusalAsk } from './decisionRefusal.js';
24
- import { DECIDE_WORKFLOW_REMEMBER_UNSUPPORTED, refusalSaysStillPending } from '../engineErrorCodes.js';
25
+ import { wireFailureTextsOf } from '../wireFailureShape.js';
26
+ import { DECIDE_WORKFLOW_REMEMBER_UNSUPPORTED } from '../engineErrorCodes.js';
25
27
  import { readDecideReceipt } from '../decideReceipt.js';
26
28
  import { RULE_OFFER_MATCHES, RULE_OFFER_BATCH_MEMBER_KINDS, RULE_OFFER_UNCOVERED_REASONS, RULE_OFFERS_ABSENCE_REASONS, ASK_ORIGINS, TOOL_APPROVAL_CARD_WORDS, } from '@sema-agent/sdk';
27
29
  export function toolNameIsFsWrite(name) {
@@ -82,6 +84,14 @@ export function readApprovalDenySettledBy(decision) {
82
84
  : undefined;
83
85
  }
84
86
  export const RETRACTED_CARD_DECISION_KIND = 'retracted';
87
+ function parkRowOf(taskId, row, gatedCallId) {
88
+ const d = parkRowDeadlineState(row, Date.now());
89
+ return Object.freeze({
90
+ taskId,
91
+ ...(gatedCallId !== undefined && gatedCallId !== '' ? { gatedCallId } : {}),
92
+ ...(d.kind !== 'none' ? { deadlineMs: d.deadlineMs } : {}),
93
+ });
94
+ }
85
95
  const cardPortByKey = createSessionSlot();
86
96
  const cardPortMissesByKey = new Map();
87
97
  function bumpCardPortMiss(sessionKey) {
@@ -149,14 +159,6 @@ export function rememberRefusalOf(error) {
149
159
  function rememberRefusalIn(reading) {
150
160
  return reading.state === 'code' ? REMEMBER_REFUSAL_CODES.find((c) => c === reading.code) : undefined;
151
161
  }
152
- function rememberFallbackKind(e) {
153
- const reading = readWireErrorCodeReading(e);
154
- if (rememberRefusalIn(reading) !== undefined)
155
- return 'refused';
156
- if (reading.state !== 'absent')
157
- return undefined;
158
- return readToolApprovalRespondRefusal(e)?.status === 400 ? 'legacy' : undefined;
159
- }
160
162
  function logAfterDecided(msg) {
161
163
  try {
162
164
  hostLog('debug', msg);
@@ -223,7 +225,7 @@ function heldDurableDecision(holdKey, readMark) {
223
225
  const recent = approvalDecisionHolds.settled.get(holdKey);
224
226
  return recent !== undefined && recent.at > readMark ? recent.hold : undefined;
225
227
  }
226
- function holdDurableDecision(holdKey, callerSignal, decide) {
228
+ function holdDurableDecision(holdKey, identity, callerSignal, decide) {
227
229
  const h = approvalDecisionHolds;
228
230
  const interruption = new AbortController();
229
231
  let settled = false;
@@ -241,7 +243,7 @@ function holdDurableDecision(holdKey, callerSignal, decide) {
241
243
  resolveOutcome = resolve;
242
244
  rejectOutcome = reject;
243
245
  });
244
- const hold = { lane: 'durable', outcome, presented: false, interrupt, cardSignal: interruption.signal };
246
+ const hold = { lane: 'durable', identity: Object.freeze({ ...identity }), phase: 'card', outcome, presented: false, interrupt, cardSignal: interruption.signal };
245
247
  h.inFlight.set(holdKey, hold);
246
248
  const release = () => {
247
249
  if (h.inFlight.get(holdKey) === hold)
@@ -283,7 +285,7 @@ async function joinDurableDecision(hold, deps, signal) {
283
285
  catch {
284
286
  }
285
287
  }
286
- return { ...o, joinedInFlight: true };
288
+ return joinedResolution(o);
287
289
  }
288
290
  export async function surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signal, parkGatedCallId, parkToolName) {
289
291
  const decidedByReading = decidedByReadingSnapshotOf(deps);
@@ -299,19 +301,22 @@ export async function surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signa
299
301
  closeDurableRead(readMark);
300
302
  }
301
303
  if (!found.ok)
302
- return { kind: 'failed', stage: 'fetch', reason: found.reason, ...(found.code !== undefined ? { code: found.code } : {}) };
304
+ return { kind: 'not_sent', lane: 'decide', cause: found.code ?? 'fetch_failed', ...(found.reason !== '' ? { detail: found.reason } : {}) };
303
305
  if (held !== undefined)
304
306
  return joinDurableDecision(held, deps, signal);
305
- const liveHolder = liveCardHoldFor(parkRowHolderIdentity(found.pending, found.gatedCallId));
306
- if (liveHolder !== undefined) {
307
- const joined = await awaitLiveCardHolder(liveHolder, signal);
308
- if (joined !== undefined)
309
- return fsOutcomeOfJoinedLiveCard(joined, found.gatedCallId);
307
+ const rowIdentity = parkRowHolderIdentity(found.pending, found.gatedCallId);
308
+ const otherHolder = approvalHoldFor(rowIdentity, (h) => h.lane === 'durable');
309
+ if (otherHolder !== undefined) {
310
+ const wait = await awaitApprovalHolder(otherHolder, signal, 'decide');
311
+ if (wait.kind === 'settled')
312
+ return joinedResolution(wait.outcome, found.gatedCallId);
313
+ if (wait.kind === 'left')
314
+ return { kind: 'not_sent', lane: 'decide', cause: 'retracted', ...(found.gatedCallId !== undefined ? { gatedCallId: found.gatedCallId } : {}), joinedInFlight: true };
310
315
  return surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signal, parkGatedCallId, parkToolName);
311
316
  }
312
317
  const callKey = approvalCallKey(found.gatedCallId, taskId);
313
318
  const fetched = found;
314
- return holdDurableDecision(durableHoldKey(fetched.pending, callKey), signal, async (hold) => {
319
+ return holdDurableDecision(durableHoldKey(fetched.pending, callKey), rowIdentity, signal, async (hold) => {
315
320
  const slot = {};
316
321
  const out = await presentFsApprovalAndDecide(deps, taskId, argsByCall, signal, fetched, decidedByReading, callKey, hold, slot);
317
322
  noteCallOutcome(slot, out);
@@ -319,34 +324,17 @@ export async function surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signa
319
324
  });
320
325
  }
321
326
  function parkRowHolderIdentity(row, gatedCallId) {
327
+ const askSig = carrierAskSig(row);
322
328
  return {
323
329
  ...(typeof row.taskId === 'string' && row.taskId !== '' ? { taskId: row.taskId } : {}),
324
330
  ...(gatedCallId !== undefined && gatedCallId !== '' ? { toolCallId: gatedCallId } : {}),
325
331
  ...(typeof row.sessionId === 'string' && row.sessionId !== '' ? { sessionId: row.sessionId } : {}),
326
- };
327
- }
328
- function fsOutcomeOfJoinedLiveCard(o, gatedCallId) {
329
- if (o.aborted === true)
330
- return { kind: 'aborted', gatedCallId, joinedInFlight: true };
331
- if (o.decision === 'unresolved')
332
- return { kind: 'retracted', gatedCallId, joinedInFlight: true };
333
- return {
334
- kind: 'decided',
335
- gatedCallId,
336
- ...(o.decision === 'deny'
337
- ? {
338
- denied: true,
339
- ...(o.denySettledBy !== undefined ? { denySettledBy: o.denySettledBy } : {}),
340
- ...(o.denyReason !== undefined ? { denyReason: o.denyReason } : {}),
341
- }
342
- : {}),
343
- ...(o.denyAttributionDropped === true ? { denyAttributionDropped: true } : {}),
344
- ...(o.denyAttributionRenamed === true ? { denyAttributionRenamed: true } : {}),
345
- joinedInFlight: true,
332
+ ...(askSig !== undefined ? { askSig } : {}),
346
333
  };
347
334
  }
348
335
  async function presentFsApprovalAndDecide(deps, taskId, argsByCall, signal, found, decidedByReading, callKey, hold, ledgerSlot) {
349
336
  const { pending, gatedCallId } = found;
337
+ const at = { lane: 'decide', ...(gatedCallId !== undefined ? { gatedCallId } : {}) };
350
338
  const toolName = typeof pending.toolName === 'string' ? pending.toolName : '';
351
339
  const streamArgs = gatedCallId !== undefined ? argsByCall.get(gatedCallId) : undefined;
352
340
  const rowInput = pending.input;
@@ -359,7 +347,7 @@ async function presentFsApprovalAndDecide(deps, taskId, argsByCall, signal, foun
359
347
  : undefined;
360
348
  const args = streamArgs !== undefined && streamArgs !== null ? streamArgs : rowArgsGap !== undefined ? {} : rowInput;
361
349
  if (typeof args !== 'object' || args === null) {
362
- return { kind: 'failed', stage: 'input', gatedCallId, reason: 'gate has no tool input payload' };
350
+ return { kind: 'not_sent', ...at, cause: 'no_input', detail: 'gate has no tool input payload' };
363
351
  }
364
352
  const bridge = new HitlBridge(deps.client, taskId, deps.onDecideOperation ? { onDecideOperation: deps.onDecideOperation } : undefined);
365
353
  const ruleOffersReadOnly = readRuleOffers(pending.ruleOffers);
@@ -381,7 +369,7 @@ async function presentFsApprovalAndDecide(deps, taskId, argsByCall, signal, foun
381
369
  }
382
370
  : {}),
383
371
  ...(pending.governanceForced === true ? { governanceForced: true } : {}),
384
- ...(pending.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
372
+ ...(readRequiresRealApproval(pending) ? { requiresRealApproval: true } : {}),
385
373
  ...(hasOwnStrictTrue(pending, 'mandated') ? { mandated: true } : {}),
386
374
  ...((w) => (w !== undefined ? { mandate: w } : {}))(readApprovalMandate(pending)),
387
375
  ...((o) => (o !== undefined ? { origin: o } : {}))(readAskOrigin(pending)),
@@ -402,92 +390,81 @@ async function presentFsApprovalAndDecide(deps, taskId, argsByCall, signal, foun
402
390
  : {}),
403
391
  allowSessionCarried: true,
404
392
  grantBinding: readApprovalGrantBinding(pending),
393
+ parkRow: parkRowOf(taskId, pending, gatedCallId),
405
394
  }));
395
+ hold.phase = 'respond';
406
396
  if (rowArgsGap !== undefined && card.kind === 'allow' && card.updatedInput !== undefined) {
407
397
  hostLog('error', `liveToolApprovalWire: approval card for ${gatedCallId ?? `task ${taskId}`} returned an edited approval, but the tool input is ${rowArgsGap === 'truncated' ? 'a size marker on the pending row' : 'missing from both the stream and the pending row'} — nothing sent (the park stays pending)`);
408
398
  surfaceEditRefusedOnBlindAsk();
409
- return { kind: 'failed', stage: 'card', gatedCallId, reason: PARK_EDIT_REFUSED_REASON, editRefused: true };
399
+ return { kind: 'not_sent', ...at, cause: 'edit_refused', detail: PARK_EDIT_REFUSED_REASON };
410
400
  }
411
401
  const decideSpan = bridge.openDecideOperation();
412
402
  const decideOpts = { ...(signal ? { signal } : {}), operation: decideSpan };
413
403
  try {
414
404
  switch (card.kind) {
415
405
  case 'failed':
416
- return { kind: 'failed', stage: 'card', gatedCallId, reason: card.reason };
406
+ return { kind: 'not_sent', ...at, cause: 'card_unavailable', ...(typeof card.reason === 'string' && card.reason !== '' ? { detail: card.reason } : {}) };
417
407
  case 'retracted':
418
408
  hostLog('debug', `liveHitlAskWire: approval card retracted without a decision for ${gatedCallId ?? '(no call)'} — nothing sent`);
419
- return { kind: 'retracted', gatedCallId };
409
+ return { kind: 'not_sent', ...at, cause: 'retracted' };
420
410
  case 'aborted':
421
411
  {
422
412
  const cancelDeny = bridge.decideTool({ decision: 'deny', reason: INTERRUPT_DENY_REASON }, gatedCallId, undefined, pending);
423
413
  hold.trailing = cancelDeny;
424
414
  observeCancelByDeny(cancelDeny, taskId);
425
415
  }
426
- return { kind: 'aborted', gatedCallId };
416
+ return { kind: 'unsettled', ...at, cause: 'interrupted' };
427
417
  case 'allow':
428
418
  try {
429
419
  let rememberRefused = false;
430
- if (card.allowSession) {
420
+ const approveBody = answerPayloadFor(card, 'decide');
421
+ const { remember: _remember, ...plainApproveBody } = approveBody;
422
+ void _remember;
423
+ emitAnswerCommitted(callKey, 'decide', card, approveBody);
424
+ if (approveBody.remember === 'session') {
431
425
  let sessionReceipt;
432
426
  let sessionDecided = false;
433
427
  try {
434
- const raw = await bridge.decideTool({ decision: 'approve', remember: 'session', ...(card.updatedInput !== undefined ? { updatedInput: card.updatedInput } : {}) }, gatedCallId, decideOpts, pending);
428
+ const raw = await bridge.decideTool(approveBody, gatedCallId, decideOpts, pending);
435
429
  sessionReceipt = readDecideReceipt(raw);
436
430
  sessionDecided = true;
437
431
  }
438
432
  catch (e) {
439
- const fallbackKind = rememberFallbackKind(e);
440
- if (fallbackKind === undefined)
433
+ if (rememberRefusalOf(e) === undefined)
441
434
  throw e;
442
- rememberRefused = fallbackKind === 'refused';
435
+ rememberRefused = true;
443
436
  logAfterDecided(`liveToolApprovalWire: decide(approve+remember) refused the remember part (${String(e)}) — falling back to plain approve`);
444
437
  }
445
438
  if (sessionDecided) {
446
439
  if (sessionReceipt?.rememberApplied === false)
447
440
  surfaceRememberNotApplied();
448
- return { kind: 'decided', gatedCallId, ...(sessionReceipt !== undefined ? { receipt: sessionReceipt } : {}) };
441
+ return { kind: 'decided', ...at, decision: 'allow', ...(sessionReceipt !== undefined ? { receipt: sessionReceipt } : {}) };
449
442
  }
450
443
  }
451
- const raw = await bridge.decideTool({ decision: 'approve', ...(card.updatedInput !== undefined ? { updatedInput: card.updatedInput } : {}) }, gatedCallId, decideOpts, pending);
444
+ const raw = await bridge.decideTool(plainApproveBody, gatedCallId, decideOpts, pending);
452
445
  const receipt = readDecideReceipt(raw);
453
446
  if (rememberRefused)
454
447
  surfaceRememberNotApplied();
455
- return { kind: 'decided', gatedCallId, ...(receipt !== undefined ? { receipt } : {}) };
448
+ return { kind: 'decided', ...at, decision: 'allow', ...(receipt !== undefined ? { receipt } : {}) };
456
449
  }
457
450
  catch (e) {
458
- const currentPending = readDecideCurrentPending(e);
459
- const wireCodes = readWireCodes(e);
460
- const wireRetriable = typeof e === 'object' && e !== null && refusalSaysStillPending(e);
461
- const safetyCode = hitlSafetyCodeOf(e);
462
451
  const failureTexts = wireFailureTextsOf(e);
463
452
  hostLog('debug', `liveToolApprovalWire: decide(approve) failed: ${failureTexts.log}`);
464
- return {
465
- kind: 'failed',
466
- stage: 'decide',
467
- gatedCallId,
468
- reason: `decide(approve) failed: ${failureTexts.reason}${decideFailureNote(e, 'approve')}`,
469
- ...(thrownIsInstanceOf(e, DecideTransportRetryExhaustedError) ? { retryExhausted: true } : {}),
470
- ...(currentPending !== undefined ? { currentPending } : {}),
471
- ...wireCodes,
472
- ...(wireRetriable ? { retriable: true } : {}),
473
- ...(safetyCode !== undefined ? { safetyCode } : {}),
474
- };
453
+ return resolutionOfDecisionFailure(e, { lane: 'decide', route: 'approval_decide', gatedCallId, detail: `decide(approve) failed: ${failureTexts.reason}${decideFailureNote(e, 'approve')}` });
475
454
  }
476
455
  case 'deny': {
477
456
  const denySettledByForWire = readApprovalDenySettledBy(card);
478
457
  const attributionKeys = decisionAttributionKeyOf(decidedByReading, denySettledByForWire);
479
- const denyReasonSnapshot = typeof card.reason === 'string' ? card.reason : undefined;
458
+ const denyReasonSnapshot = ((r) => (typeof r === 'string' ? r : undefined))(card.reason);
480
459
  let lastSendCarriedAttribution = false;
481
460
  try {
482
- const denyBody = {
483
- decision: 'deny',
484
- reason: denyReasonForWire(denyReasonSnapshot, `task ${taskId}`) ?? DEFAULT_DENY_REASON,
485
- ...attributionKeys,
486
- };
461
+ const denyPayload = { decision: 'deny', reason: answerPayloadFor({ kind: 'deny', ...(denyReasonSnapshot !== undefined ? { reason: denyReasonSnapshot } : {}) }, 'decide').reason };
462
+ const denyBody = { ...denyPayload, ...attributionKeys };
487
463
  let attributionDropped = false;
488
464
  let attributionRenamed = false;
489
465
  let raw;
490
466
  lastSendCarriedAttribution = carriesAttributionKey(attributionKeys);
467
+ emitAnswerCommitted(callKey, 'decide', card, denyPayload);
491
468
  try {
492
469
  raw = await bridge.decideTool(denyBody, gatedCallId, decideOpts, pending);
493
470
  }
@@ -527,39 +504,25 @@ async function presentFsApprovalAndDecide(deps, taskId, argsByCall, signal, foun
527
504
  }
528
505
  }
529
506
  const receipt = readDecideReceipt(raw);
530
- const denySettledBy = denySettledByForWire;
531
- const denyReason = denyReasonSnapshot !== undefined && denyReasonSnapshot.trim() !== '' ? denyReasonSnapshot : undefined;
507
+ const deny = denyFactsOf(denySettledByForWire, denyReasonSnapshot, attributionDropped, attributionRenamed);
532
508
  return {
533
509
  kind: 'decided',
534
- gatedCallId,
535
- denied: true,
536
- ...(denySettledBy !== undefined ? { denySettledBy } : {}),
537
- ...(denyReason !== undefined ? { denyReason } : {}),
538
- ...(attributionDropped ? { denyAttributionDropped: true } : {}),
539
- ...(attributionRenamed ? { denyAttributionRenamed: true } : {}),
510
+ ...at,
511
+ decision: 'deny',
512
+ ...(deny !== undefined ? { deny } : {}),
540
513
  ...(receipt !== undefined ? { receipt } : {}),
541
514
  };
542
515
  }
543
516
  catch (e) {
544
- const currentPending = readDecideCurrentPending(e);
545
- const wireCodes = readWireCodes(e);
546
- const wireRetriable = typeof e === 'object' && e !== null && refusalSaysStillPending(e);
547
- const safetyCode = hitlSafetyCodeOf(e);
548
- const attributionRefusal = denyAttributionRefusalFromError(e, lastSendCarriedAttribution);
549
517
  const failureTexts = wireFailureTextsOf(e);
550
518
  hostLog('debug', `liveToolApprovalWire: decide(deny) failed: ${failureTexts.log}`);
551
- return {
552
- kind: 'failed',
553
- stage: 'decide',
519
+ return resolutionOfDecisionFailure(e, {
520
+ lane: 'decide',
521
+ route: 'approval_decide',
554
522
  gatedCallId,
555
- reason: `decide(deny) failed: ${failureTexts.reason}${decideFailureNote(e, 'deny')}`,
556
- ...(thrownIsInstanceOf(e, DecideTransportRetryExhaustedError) ? { retryExhausted: true } : {}),
557
- ...(currentPending !== undefined ? { currentPending } : {}),
558
- ...wireCodes,
559
- ...(wireRetriable ? { retriable: true } : {}),
560
- ...(safetyCode !== undefined ? { safetyCode } : {}),
561
- ...(attributionRefusal !== null ? { denyAttributionRefusal: attributionRefusal } : {}),
562
- };
523
+ detail: `decide(deny) failed: ${failureTexts.reason}${decideFailureNote(e, 'deny')}`,
524
+ attributionCarried: lastSendCarriedAttribution,
525
+ });
563
526
  }
564
527
  }
565
528
  }
@@ -610,6 +573,7 @@ export const TOOL_APPROVAL_FRAME_KEYS_MIRROR = [
610
573
  'probeReason',
611
574
  'rememberRefused',
612
575
  'boundGrantDigest',
576
+ 'boundGrantAbsence',
613
577
  'boundGrants',
614
578
  'classifierNoRuling',
615
579
  'ruleEvidence',
@@ -621,7 +585,7 @@ export const TOOL_APPROVAL_FRAME_KEYS_MIRROR = [
621
585
  'outcome',
622
586
  'cause',
623
587
  ];
624
- const _mirrorPin = [true, true];
588
+ const _mirrorPin = [true];
625
589
  void _mirrorPin;
626
590
  const MANDATED_ABSENCE_WORD = 'mandated';
627
591
  export function approvalIsMandated(v) {
@@ -686,20 +650,76 @@ function isToolApprovalDelegation(v) {
686
650
  return d.agentName === undefined || typeof d.agentName === 'string';
687
651
  }
688
652
  export function isFromSubagent(frame) {
689
- if (frame.fromSubagent === true)
690
- return true;
691
- return typeof frame.sourceTaskId === 'string' && frame.sourceTaskId.length > 0;
653
+ return frame.fromSubagent === true;
692
654
  }
693
- const RESPOND_DECISIONS = ['allow', 'allow_session', 'deny'];
655
+ export const RESPOND_DECISIONS = Object.freeze(['allow', 'allow_session', 'deny']);
694
656
  const _respondDecisionPins = [true, true];
695
657
  void _respondDecisionPins;
696
658
  function isRespondDecision(v) {
697
659
  return typeof v === 'string' && RESPOND_DECISIONS.includes(v);
698
660
  }
661
+ function respondDecisionOfAnswer(answer) {
662
+ if (answer.kind === 'allow')
663
+ return answer.allowSession ? 'allow_session' : 'allow';
664
+ return answer.kind === 'deny' ? 'deny' : undefined;
665
+ }
666
+ function respondNoteGate(reason, capable) {
667
+ if (reason === undefined || reason.trim() === '')
668
+ return { dropped: 'blank' };
669
+ if (!capable)
670
+ return { dropped: 'capability' };
671
+ if (reason.length > MAX_RESPOND_NOTE_CHARS)
672
+ return { dropped: 'oversize' };
673
+ return { note: reason };
674
+ }
675
+ export function answerPayloadFor(answer, lane, caps) {
676
+ const word = respondDecisionOfAnswer(answer);
677
+ if (word === undefined)
678
+ return undefined;
679
+ if (lane === 'decide') {
680
+ if (answer.kind !== 'allow') {
681
+ const reason = answer.kind === 'deny' && typeof answer.reason === 'string' ? answer.reason : undefined;
682
+ return Object.freeze({ decision: 'deny', reason: denyReasonForWire(reason, 'answer') ?? DEFAULT_DENY_REASON });
683
+ }
684
+ return Object.freeze({
685
+ decision: 'approve',
686
+ ...(word === 'allow_session' ? { remember: 'session' } : {}),
687
+ ...(answer.updatedInput !== undefined ? { updatedInput: answer.updatedInput } : {}),
688
+ });
689
+ }
690
+ if (answer.kind === 'allow')
691
+ return Object.freeze({ decision: word, ...(answer.updatedInput !== undefined ? { updatedInput: answer.updatedInput } : {}) });
692
+ const gate = respondNoteGate(answer.kind === 'deny' && typeof answer.reason === 'string' ? answer.reason : undefined, caps?.approvalDecisionNoteCapable === true);
693
+ return Object.freeze({ decision: word, ...('note' in gate ? { note: gate.note } : {}) });
694
+ }
695
+ const ANSWER_CARRIAGE_NONE = Object.freeze({ allowNote: 'none' });
696
+ export function answerCarriage(card) {
697
+ void card;
698
+ return ANSWER_CARRIAGE_NONE;
699
+ }
700
+ export function observeApprovalAnswerCommitted(listener) {
701
+ answerCommittedListeners.add(listener);
702
+ return () => {
703
+ answerCommittedListeners.delete(listener);
704
+ };
705
+ }
706
+ const answerCommittedListeners = new Set();
707
+ function emitAnswerCommitted(callKey, lane, answer, payload) {
708
+ if (answerCommittedListeners.size === 0)
709
+ return;
710
+ const event = Object.freeze({ callKey, lane, answer: Object.freeze({ ...answer }), payload });
711
+ for (const l of [...answerCommittedListeners])
712
+ invokeHostCallbackIsolated('liveToolApprovalWire: answer-committed listener', () => l(event));
713
+ }
714
+ export function isToolApprovalRespondReplayed(ack) {
715
+ return typeof ack === 'object' && ack !== null && ack.idempotent === true;
716
+ }
699
717
  export function readToolApprovalRespondAck(v) {
700
718
  if (v === null || typeof v !== 'object')
701
719
  return undefined;
702
720
  const o = v;
721
+ if (o.idempotent === true)
722
+ return o.delivery === undefined ? readReplayedAck(o) : undefined;
703
723
  if (o.delivery !== 'applied')
704
724
  return undefined;
705
725
  if (typeof o.approvalId !== 'string' || o.approvalId.length === 0)
@@ -773,6 +793,48 @@ export function readToolApprovalRespondParking(v) {
773
793
  },
774
794
  };
775
795
  }
796
+ function replayClaimOf(raw) {
797
+ const approvalId = guardedRead(() => readOwnDataNonEmptyString(raw, 'approvalId'));
798
+ const d = guardedRead(() => readOwnDataValue(raw, 'decision'));
799
+ let carriesDelivery = true;
800
+ try {
801
+ carriesDelivery = typeof raw === 'object' && raw !== null && Object.hasOwn(raw, 'delivery');
802
+ }
803
+ catch {
804
+ carriesDelivery = true;
805
+ }
806
+ return { ...(approvalId !== undefined ? { approvalId } : {}), ...(d === 'allow' || d === 'deny' ? { decision: d } : {}), carriesDelivery };
807
+ }
808
+ function ownIdempotentTrue(raw) {
809
+ if (typeof raw !== 'object' || raw === null)
810
+ return false;
811
+ try {
812
+ const d = Object.getOwnPropertyDescriptor(raw, 'idempotent');
813
+ return d !== undefined && Object.hasOwn(d, 'value') && d.value === true;
814
+ }
815
+ catch {
816
+ return false;
817
+ }
818
+ }
819
+ function readReplayedAck(o) {
820
+ if (typeof o.approvalId !== 'string' || o.approvalId.length === 0)
821
+ return undefined;
822
+ if (typeof o.askId !== 'string' || o.askId.length === 0)
823
+ return undefined;
824
+ if (o.decision !== 'allow' && o.decision !== 'deny')
825
+ return undefined;
826
+ if (typeof o.decidedAtMs !== 'number' || !Number.isFinite(o.decidedAtMs) || o.decidedAtMs < 0)
827
+ return undefined;
828
+ return {
829
+ approvalId: o.approvalId,
830
+ askId: o.askId,
831
+ idempotent: true,
832
+ decision: o.decision,
833
+ decidedAtMs: o.decidedAtMs,
834
+ ...(typeof o.decisionNote === 'string' && o.decisionNote !== '' ? { decisionNote: o.decisionNote } : {}),
835
+ ...(o.rememberApplied === false ? { rememberApplied: false } : {}),
836
+ };
837
+ }
776
838
  function isNonEmptyStringArray(v) {
777
839
  return Array.isArray(v) && v.length > 0 && v.every(x => typeof x === 'string' && x !== '');
778
840
  }
@@ -852,58 +914,16 @@ function readDecidedByReadingOf(holder) {
852
914
  function decidedByReadingSnapshotOf(holder) {
853
915
  return approvalDecidedByAdmitted(readDecidedByReadingOf(holder)) ? Object.freeze({ kind: 'present', value: true }) : undefined;
854
916
  }
855
- export function readToolApprovalRespondRefusal(err) {
856
- const pick = (key) => {
857
- try {
858
- return err?.[key];
859
- }
860
- catch {
861
- return undefined;
862
- }
863
- };
864
- const pickMachine = (key) => guardedRead(() => (typeof err === 'object' && err !== null ? readOwnDataValue(err, key) : undefined));
865
- const rawStatus = pickMachine('status');
866
- const rawCode = pickMachine('errorCode');
867
- const status = typeof rawStatus === 'number' && Number.isFinite(rawStatus) ? rawStatus : undefined;
868
- const errorCode = typeof rawCode === 'string' && rawCode.length > 0 ? rawCode : undefined;
869
- const message = refusalTextOf(err, pick('message'));
870
- if (status === undefined && errorCode === undefined && message === undefined)
917
+ function denyFactsOf(settledBy, reasonSnapshot, dropped, renamed) {
918
+ const reason = reasonSnapshot !== undefined && reasonSnapshot.trim() !== '' ? reasonSnapshot : undefined;
919
+ const attributionKey = dropped ? 'dropped' : renamed ? 'renamed' : undefined;
920
+ if (settledBy === undefined && reason === undefined && attributionKey === undefined)
871
921
  return undefined;
872
- const machine = typeof err === 'object' && err !== null ? err : undefined;
873
- const retriable = machine !== undefined && refusalSaysStillPending(machine);
874
- const cause = machine !== undefined ? refusalCauseWordOf(machine) : undefined;
875
- const field = machine !== undefined ? readRefusalField(machine) : undefined;
876
- const unreadable = machine !== undefined && readDecisionRefusalAsk(machine).reading === 'unreadable';
877
- const rest = {
878
- ...(status !== undefined ? { status } : {}),
879
- ...(errorCode !== undefined ? { errorCode } : {}),
880
- ...(message !== undefined ? { message } : {}),
881
- ...(retriable ? { retriable: true } : {}),
882
- ...(cause !== undefined || field !== undefined
883
- ? { extra: { ...(cause !== undefined ? { cause } : {}), ...(field !== undefined ? { field } : {}) } }
884
- : {}),
885
- ...(unreadable ? { unreadable: true } : {}),
922
+ return {
923
+ ...(settledBy !== undefined ? { settledBy } : {}),
924
+ ...(reason !== undefined ? { reason } : {}),
925
+ ...(attributionKey !== undefined ? { attributionKey } : {}),
886
926
  };
887
- if (status !== undefined)
888
- return { ...rest, status };
889
- if (errorCode !== undefined)
890
- return { ...rest, errorCode };
891
- return { ...rest, message: message };
892
- }
893
- const USELESS_REFUSAL_TEXTS = new Set(['[object Object]', 'null', 'undefined', '']);
894
- function refusalTextOf(err, message) {
895
- if (typeof message === 'string' && message.length > 0)
896
- return message;
897
- if (typeof err === 'string' && err.length > 0)
898
- return err;
899
- let coerced;
900
- try {
901
- coerced = String(err);
902
- }
903
- catch {
904
- return undefined;
905
- }
906
- return USELESS_REFUSAL_TEXTS.has(coerced) ? undefined : coerced;
907
927
  }
908
928
  const MACHINE_CODE_SHAPE = /^[A-Za-z0-9._:-]{1,64}$/;
909
929
  function logSafeErrorCode(code) {
@@ -1021,28 +1041,67 @@ export function readApprovalBoundGrants(v) {
1021
1041
  return undefined;
1022
1042
  return Object.freeze({ skill, allowedTools, disallowedTools });
1023
1043
  }
1044
+ const BOUND_GRANT_NOT_BINDABLE = 'not_bindable';
1045
+ const SERVER_CLASS_READ_CAP = 64;
1024
1046
  function readApprovalGrantBinding(carrier) {
1025
1047
  if (typeof carrier !== 'object' || carrier === null)
1026
1048
  return 'unreadable';
1027
1049
  try {
1028
- if (Object.hasOwn(carrier, 'boundGrantDigest')) {
1029
- const digest = ownDataValue(carrier, 'boundGrantDigest');
1030
- return typeof digest === 'string' && digest.length > 0 ? 'bound' : 'unreadable';
1031
- }
1032
- const tool = ownDataValue(carrier, 'toolName');
1033
- if (typeof tool !== 'string' || tool.length === 0)
1050
+ const card = cardGrantBindingOf(carrier);
1051
+ const mayCall = serverMayReadCallLaw(carrier);
1052
+ if (cardGrantBindingOf(carrier) !== card)
1034
1053
  return 'unreadable';
1035
- return tool === CORE_SKILL_TOOL_NAME ? 'unreadable' : 'none';
1054
+ if (!mayCall)
1055
+ return card;
1056
+ const call = callGrantBindingOf(carrier);
1057
+ if (callGrantBindingOf(carrier) !== call || cardGrantBindingOf(carrier) !== card)
1058
+ return 'unreadable';
1059
+ return card === 'none' && call === 'none' ? 'none' : 'unreadable';
1036
1060
  }
1037
1061
  catch {
1038
1062
  return 'unreadable';
1039
1063
  }
1040
1064
  }
1065
+ function cardGrantBindingOf(carrier) {
1066
+ if (Object.hasOwn(carrier, 'boundGrantDigest') || 'boundGrantDigest' in carrier) {
1067
+ const digest = ownDataValue(carrier, 'boundGrantDigest');
1068
+ return typeof digest === 'string' && digest.length > 0 ? 'bound' : 'unreadable';
1069
+ }
1070
+ const seen = carrier;
1071
+ return ownDataValue(carrier, 'boundGrantAbsence') === BOUND_GRANT_NOT_BINDABLE && seen.boundGrantAbsence === BOUND_GRANT_NOT_BINDABLE && seen.boundGrantDigest === undefined
1072
+ ? 'none'
1073
+ : 'unreadable';
1074
+ }
1075
+ function callGrantBindingOf(carrier) {
1076
+ const tool = ownDataValue(carrier, 'toolName');
1077
+ return typeof tool === 'string' && tool.length > 0 && tool !== CORE_SKILL_TOOL_NAME && carrier.toolName === tool ? 'none' : 'unreadable';
1078
+ }
1079
+ function serverMayReadCallLaw(carrier) {
1080
+ const seen = carrier;
1081
+ const classes = seen.classes;
1082
+ if (!Array.isArray(classes))
1083
+ return false;
1084
+ const listClass = (c) => DEPLOYMENT_LIST_ASK_CLASSES.includes(c);
1085
+ if (listClass(seen.askClass))
1086
+ return true;
1087
+ const length = classes.length;
1088
+ if (typeof length !== 'number' || !Number.isSafeInteger(length) || length > SERVER_CLASS_READ_CAP)
1089
+ return true;
1090
+ for (let i = 0; i < length; i++)
1091
+ if (listClass(classes[i]))
1092
+ return true;
1093
+ let n = 0;
1094
+ for (const c of classes) {
1095
+ if (listClass(c) || ++n > SERVER_CLASS_READ_CAP)
1096
+ return true;
1097
+ }
1098
+ return false;
1099
+ }
1041
1100
  const UNREADABLE_EVIDENCE_KEYS = Object.freeze({
1042
1101
  frame: Object.freeze([
1043
1102
  ['rememberRefused', 'rememberRefused'], ['governanceForced', 'governanceForced'], ['requiresRealApproval', 'requiresRealApproval'],
1044
1103
  ['mandated', 'mandated'], ['mandate', 'mandate'], ['origin', 'origin'], ['ruleOffersAbsence', 'ruleOffersAbsence'], ['ruleOffers', 'ruleOffers'],
1045
- ['allowRuleShadowed', 'allowRuleShadow'], ['persistedRuleShadowed', 'allowRuleShadow'], ['askClass', 'askClass'], ['classes', 'classes'],
1104
+ ['allowRuleShadowed', 'allowRuleShadow'], ['askClass', 'askClass'], ['classes', 'classes'],
1046
1105
  ]),
1047
1106
  row: Object.freeze([
1048
1107
  ['rememberRefused', 'rememberRefused'], ['governanceForced', 'governanceForced'], ['requiresRealApproval', 'requiresRealApproval'],
@@ -1051,8 +1110,9 @@ const UNREADABLE_EVIDENCE_KEYS = Object.freeze({
1051
1110
  ]),
1052
1111
  v1: Object.freeze([
1053
1112
  ['rememberRefused', 'rememberRefused'], ['governanceForced', 'governanceForced'], ['ruleOffersAbsence', 'ruleOffersAbsence'], ['ruleOffers', 'ruleOffersReadOnly'],
1054
- ['allowRuleShadowed', 'allowRuleShadow'], ['persistedRuleShadowed', 'allowRuleShadow'], ['askClass', 'askClass'], ['classes', 'classes'],
1113
+ ['allowRuleShadowed', 'allowRuleShadow'], ['askClass', 'askClass'], ['classes', 'classes'],
1055
1114
  ]),
1115
+ generic: Object.freeze([['ruleOffersAbsence', 'ruleOffersAbsence'], ['askClass', 'askClass'], ['classes', 'classes']]),
1056
1116
  });
1057
1117
  function carrierHas(carrier, key) {
1058
1118
  if (typeof carrier !== 'object' || carrier === null)
@@ -1075,17 +1135,21 @@ function v1RiskUnreadable(card) {
1075
1135
  return true;
1076
1136
  }
1077
1137
  }
1078
- function withUnreadableEvidence(carrier, leg, req) {
1079
- const keys = new Set();
1138
+ function withUnreadableEvidence(carrier, leg, req, reported = []) {
1139
+ const keys = new Set(reported);
1080
1140
  for (const [from, to] of UNREADABLE_EVIDENCE_KEYS[leg]) {
1081
1141
  if (carrierHas(carrier, from) && !Object.hasOwn(req, to))
1082
1142
  keys.add(from);
1083
1143
  }
1084
1144
  if (leg === 'row') {
1085
1145
  const rd = ownDataValue(carrier, 'riskDescriptor');
1086
- for (const k of ['allowRuleShadowed', 'shadowedRule'])
1087
- if (carrierHas(rd, k) && !Object.hasOwn(req, 'allowRuleShadow'))
1088
- keys.add(`riskDescriptor.${k}`);
1146
+ if (carrierHas(rd, 'allowRuleShadowed') && !Object.hasOwn(req, 'allowRuleShadow'))
1147
+ keys.add('riskDescriptor.allowRuleShadowed');
1148
+ }
1149
+ if (leg !== 'generic' && !Object.hasOwn(req, 'allowRuleShadow')) {
1150
+ const shadowLeg = leg === 'row' ? 'row' : 'frame';
1151
+ if (retiredAllowRuleShadowKeyPresent(carrier, shadowLeg))
1152
+ keys.add(RETIRED_SHADOW_EVIDENCE_LABEL[shadowLeg]);
1089
1153
  }
1090
1154
  if (leg === 'v1' && v1RiskUnreadable(carrier))
1091
1155
  keys.add('risk');
@@ -1130,7 +1194,18 @@ const CALL_LEDGER_MAX_CALLS = 256;
1130
1194
  const CALL_LEDGER_MAX_ASKS_PER_CALL = 8;
1131
1195
  const callCardLedger = new Map();
1132
1196
  function askSignatureOf(req) {
1133
- return JSON.stringify([req.classes !== undefined ? [...req.classes] : null, req.origin ?? null, req.mandate ?? null]);
1197
+ return askSigOf(req.classes, req.origin, req.mandate);
1198
+ }
1199
+ function askSigOf(classes, origin, mandate) {
1200
+ return JSON.stringify([classes !== undefined ? [...classes] : null, origin ?? null, mandate ?? null]);
1201
+ }
1202
+ function carrierAskSig(v) {
1203
+ try {
1204
+ return askSigOf(readAskClassPair(v)?.classes, readAskOrigin(v), readApprovalMandate(v));
1205
+ }
1206
+ catch {
1207
+ return undefined;
1208
+ }
1134
1209
  }
1135
1210
  function priorCardView(a) {
1136
1211
  return Object.freeze({
@@ -1154,33 +1229,65 @@ function noteCardOnCall(callId, approvalId, req) {
1154
1229
  }
1155
1230
  }
1156
1231
  let at = asks.findIndex((a) => a.sig === sig);
1232
+ let supersedes;
1157
1233
  if (at < 0) {
1158
1234
  asks.push({ sig, ...(approvalId !== undefined ? { approvalId } : {}), ...(req.origin !== undefined ? { origin: req.origin } : {}), ...(req.mandate !== undefined ? { mandate: req.mandate } : {}) });
1159
1235
  while (asks.length > CALL_LEDGER_MAX_ASKS_PER_CALL)
1160
1236
  asks.shift();
1161
1237
  at = asks.length - 1;
1162
1238
  }
1163
- else if (asks[at].approvalId === undefined && approvalId !== undefined) {
1164
- asks[at].approvalId = approvalId;
1239
+ else {
1240
+ supersedes = supersedesOf(asks[at]);
1241
+ delete asks[at].answered;
1242
+ delete asks[at].answerFate;
1243
+ if (asks[at].approvalId === undefined && approvalId !== undefined)
1244
+ asks[at].approvalId = approvalId;
1165
1245
  }
1166
1246
  const entry = asks[at];
1167
1247
  const prior = asks.slice(0, at);
1168
- return { entry, ...(prior.length > 0 ? { prior: Object.freeze(prior.map(priorCardView)) } : {}) };
1248
+ return { entry, ...(prior.length > 0 ? { prior: Object.freeze(prior.map(priorCardView)) } : {}), ...(supersedes !== undefined ? { supersedes } : {}) };
1249
+ }
1250
+ function supersedesOf(a) {
1251
+ const settled = settledApprovalOf(a.approvalId);
1252
+ return Object.freeze({
1253
+ ...(a.approvalId !== undefined ? { approvalId: a.approvalId } : {}),
1254
+ ...(settled?.parked === true ? { parked: true } : {}),
1255
+ ...(a.answered !== undefined && a.outcome === undefined ? { priorAnswer: a.answered, priorAnswerOutcome: a.answerFate ?? 'unconfirmed' } : {}),
1256
+ });
1169
1257
  }
1170
1258
  async function surfaceCallTrackedCard(callId, approvalId, slot, req) {
1171
1259
  if (callId === undefined || callId === '')
1172
1260
  return surfaceApprovalCard(req);
1173
- const { entry, prior } = noteCardOnCall(callId, approvalId, req);
1261
+ const { entry, prior, supersedes } = noteCardOnCall(callId, approvalId, req);
1174
1262
  slot.entry = entry;
1175
- return surfaceApprovalCard(prior !== undefined ? { ...req, priorCardsForCall: prior } : req);
1263
+ const decision = await surfaceApprovalCard(prior === undefined && supersedes === undefined ? req : { ...req, ...(prior !== undefined ? { priorCardsForCall: prior } : {}), ...(supersedes !== undefined ? { supersedes } : {}) });
1264
+ const answered = respondDecisionOfAnswer(decision);
1265
+ if (answered !== undefined)
1266
+ entry.answered = answered;
1267
+ return decision;
1176
1268
  }
1177
- function noteCallOutcome(slot, outcome) {
1269
+ function noteCallOutcome(slot, r) {
1178
1270
  const entry = slot.entry;
1179
1271
  if (entry === undefined)
1180
1272
  return;
1181
- const r = approvalResolutionOf(outcome);
1182
- if (r.kind === 'decided')
1273
+ if (r.kind === 'decided') {
1183
1274
  entry.outcome = r.decision === 'deny' ? 'denied' : 'allowed';
1275
+ delete entry.answerFate;
1276
+ return;
1277
+ }
1278
+ const unsettledFate = {
1279
+ engine_refused: 'not_accepted',
1280
+ parking_in_progress: 'not_accepted',
1281
+ engine_error: 'unconfirmed',
1282
+ no_answer: 'unconfirmed',
1283
+ transport_exhausted: 'unconfirmed',
1284
+ interrupted: 'unconfirmed',
1285
+ orchestration: 'unconfirmed',
1286
+ unreadable: 'unconfirmed',
1287
+ };
1288
+ const next = r.kind === 'not_sent' ? 'not_sent' : Object.hasOwn(unsettledFate, r.cause) ? unsettledFate[r.cause] : 'unconfirmed';
1289
+ const fateRank = { not_sent: 0, not_accepted: 1, unconfirmed: 2 };
1290
+ entry.answerFate = entry.answerFate !== undefined && fateRank[entry.answerFate] > fateRank[next] ? entry.answerFate : next;
1184
1291
  }
1185
1292
  export function approvalPriorCardsDetail(card) {
1186
1293
  const prior = ownDataValue(card, 'priorCardsForCall');
@@ -1208,6 +1315,30 @@ export function approvalPriorCardsDetail(card) {
1208
1315
  }
1209
1316
  return `This is a second approval for the same call: ${earlier}. This one is a separate check and needs its own answer.`;
1210
1317
  }
1318
+ const PRIOR_ANSWER_PHRASE = Object.freeze({
1319
+ allow: 'Yes',
1320
+ allow_session: 'Yes for this session',
1321
+ deny: 'No',
1322
+ });
1323
+ export function approvalSupersedesDetail(card) {
1324
+ const sup = ownDataValue(card, 'supersedes');
1325
+ if (typeof sup !== 'object' || sup === null)
1326
+ return undefined;
1327
+ const parked = ownDataValue(sup, 'parked') === true;
1328
+ const prior = ownDataValue(sup, 'priorAnswer');
1329
+ const head = parked
1330
+ ? 'This card replaces an earlier one for the same request, which moved to the engine\'s waiting queue.'
1331
+ : 'This card replaces an earlier one for the same request.';
1332
+ if (typeof prior !== 'string' || !Object.hasOwn(PRIOR_ANSWER_PHRASE, prior))
1333
+ return head;
1334
+ const said = PRIOR_ANSWER_PHRASE[prior];
1335
+ const fate = ownDataValue(sup, 'priorAnswerOutcome');
1336
+ if (fate === 'not_sent')
1337
+ return `${head} Your earlier answer (${said}) did not reach the engine, so answer again here.`;
1338
+ if (fate === 'not_accepted')
1339
+ return `${head} Your earlier answer (${said}) was not accepted by the engine, so answer again here.`;
1340
+ return `${head} Your earlier answer (${said}) was not confirmed by the engine — it may already have taken effect, so check the request before answering again here.`;
1341
+ }
1211
1342
  function isNonNegativeFinite(v) {
1212
1343
  return typeof v === 'number' && Number.isFinite(v) && v >= 0;
1213
1344
  }
@@ -1379,32 +1510,44 @@ function v1Bit(stamp) {
1379
1510
  catch {
1380
1511
  }
1381
1512
  }
1382
- export function approvalCardFactsOfV1(card) {
1383
- if (typeof card !== 'object' || card === null)
1384
- return undefined;
1385
- const c = card;
1386
- const asFrame = card;
1387
- const fromSubagent = (() => {
1388
- try {
1389
- return isFromSubagent(asFrame);
1390
- }
1391
- catch {
1392
- return false;
1393
- }
1394
- })();
1513
+ const SUSPENDED_ASK_ARGS_NOTE = "this request's tool arguments are not available on this surface — it was raised while no client was connected, " +
1514
+ 'so you are deciding without seeing them; deny it if you are not sure what it will do. ' +
1515
+ 'Edits are not accepted on this card: there is no original input to edit';
1516
+ function cardFactsOf(carrier, leg, opts) {
1517
+ const c = carrier;
1518
+ const full = leg !== 'generic';
1519
+ const asFrame = carrier;
1520
+ const fromSubagent = full
1521
+ ? (() => {
1522
+ try {
1523
+ return isFromSubagent(asFrame);
1524
+ }
1525
+ catch {
1526
+ return false;
1527
+ }
1528
+ })()
1529
+ : false;
1395
1530
  const argsPart = (() => {
1396
1531
  try {
1397
- const raw = c.args !== undefined && c.args !== null ? c.args : undefined;
1532
+ let stream;
1533
+ let blind = false;
1534
+ if (leg === 'frame' && typeof opts === 'object' && opts !== null) {
1535
+ stream = opts.streamArgs;
1536
+ blind = opts.argsUnavailable === true;
1537
+ }
1538
+ const raw = stream !== undefined && stream !== null ? stream : c.args !== undefined && c.args !== null ? c.args : undefined;
1398
1539
  if (typeof raw === 'object' && raw !== null)
1399
- return { args: raw };
1540
+ return blind ? { args: raw, argsUnavailable: true } : { args: raw };
1400
1541
  const fallback = argsFallbackOf(c.message, c.argsOmitted);
1401
- return { args: fallback.args, argsUnavailable: true, ...(fallback.omittedNote !== undefined ? { wireNote: fallback.omittedNote } : {}) };
1542
+ const note = blind ? SUSPENDED_ASK_ARGS_NOTE : fallback.omittedNote;
1543
+ return { args: fallback.args, argsUnavailable: true, ...(note !== undefined ? { wireNote: note } : {}) };
1402
1544
  }
1403
1545
  catch {
1404
1546
  return { args: {}, argsUnavailable: true };
1405
1547
  }
1406
1548
  })();
1407
- const out = { toolName: '', ...argsPart };
1549
+ const out = { toolName: '', args: argsPart.args };
1550
+ let realApprovalUnreadable = false;
1408
1551
  v1Bit(() => {
1409
1552
  if (typeof c.toolName === 'string')
1410
1553
  out.toolName = c.toolName;
@@ -1417,44 +1560,79 @@ export function approvalCardFactsOfV1(card) {
1417
1560
  if (fromSubagent)
1418
1561
  out.workerBadge = subagentBadgeFor(asFrame);
1419
1562
  });
1420
- v1Bit(() => {
1421
- if (c.governanceForced === true)
1422
- out.governanceForced = true;
1423
- });
1424
- v1Bit(() => {
1425
- const d = c.delegation;
1426
- if (fromSubagent && isToolApprovalDelegation(d))
1427
- out.delegation = d;
1428
- });
1429
- v1Bit(() => {
1430
- const offers = readRuleOffers(c.ruleOffers);
1431
- if (offers !== undefined)
1432
- out.ruleOffersReadOnly = offers;
1433
- });
1434
- v1Bit(() => {
1435
- const shadow = readAllowRuleShadowFromWire(card, 'frame');
1436
- if (shadow !== undefined)
1437
- out.allowRuleShadow = shadow;
1438
- });
1439
- v1Bit(() => {
1440
- const pc = c.probeCause;
1441
- if (isWireRecordCarrier(pc))
1442
- out.probeCause = pc;
1443
- });
1444
- v1Bit(() => {
1445
- if (readApprovalRememberRefused(card))
1446
- out.rememberRefused = true;
1447
- });
1448
- v1Bit(() => {
1449
- const re = c.ruleEvidence;
1450
- if (isWireRecordCarrier(re))
1451
- out.ruleEvidence = re;
1452
- });
1453
- v1Bit(() => {
1454
- const risk = c.risk;
1455
- if (typeof risk === 'object' && risk !== null && risk.requiresRealApproval === true)
1456
- out.requiresRealApproval = true;
1457
- });
1563
+ if (argsPart.wireNote !== undefined)
1564
+ out.wireNote = argsPart.wireNote;
1565
+ if (argsPart.argsUnavailable === true)
1566
+ out.argsUnavailable = true;
1567
+ if (full) {
1568
+ v1Bit(() => {
1569
+ if (c.governanceForced === true)
1570
+ out.governanceForced = true;
1571
+ });
1572
+ v1Bit(() => {
1573
+ const d = c.delegation;
1574
+ if (fromSubagent && isToolApprovalDelegation(d))
1575
+ out.delegation = d;
1576
+ });
1577
+ v1Bit(() => {
1578
+ const offers = readRuleOffers(c.ruleOffers);
1579
+ if (offers === undefined)
1580
+ return;
1581
+ if (leg === 'frame')
1582
+ out.ruleOffers = offers;
1583
+ else
1584
+ out.ruleOffersReadOnly = offers;
1585
+ });
1586
+ v1Bit(() => {
1587
+ const shadow = readAllowRuleShadowFromWire(carrier, 'frame');
1588
+ if (shadow !== undefined)
1589
+ out.allowRuleShadow = shadow;
1590
+ });
1591
+ v1Bit(() => {
1592
+ const pc = c.probeCause;
1593
+ if (isWireRecordCarrier(pc))
1594
+ out.probeCause = pc;
1595
+ });
1596
+ if (leg === 'frame') {
1597
+ v1Bit(() => {
1598
+ const r = readProbeReason(carrier);
1599
+ if (r !== undefined)
1600
+ out.probeReason = r;
1601
+ });
1602
+ }
1603
+ v1Bit(() => {
1604
+ if (readApprovalRememberRefused(carrier))
1605
+ out.rememberRefused = true;
1606
+ });
1607
+ v1Bit(() => {
1608
+ const re = c.ruleEvidence;
1609
+ if (isWireRecordCarrier(re))
1610
+ out.ruleEvidence = re;
1611
+ });
1612
+ try {
1613
+ if (readRequiresRealApproval(carrier))
1614
+ out.requiresRealApproval = true;
1615
+ }
1616
+ catch {
1617
+ realApprovalUnreadable = true;
1618
+ }
1619
+ if (leg === 'frame') {
1620
+ v1Bit(() => {
1621
+ if (hasOwnStrictTrue(carrier, 'mandated'))
1622
+ out.mandated = true;
1623
+ });
1624
+ v1Bit(() => {
1625
+ const w = readApprovalMandate(carrier);
1626
+ if (w !== undefined)
1627
+ out.mandate = w;
1628
+ });
1629
+ v1Bit(() => {
1630
+ const rc = readReadRootCandidate(c.readRootCandidate);
1631
+ if (rc !== undefined)
1632
+ out.readRootCandidate = rc;
1633
+ });
1634
+ }
1635
+ }
1458
1636
  v1Bit(() => {
1459
1637
  if (c.inputHasBidi === true)
1460
1638
  out.inputHasBidi = true;
@@ -1469,43 +1647,115 @@ export function approvalCardFactsOfV1(card) {
1469
1647
  if (dlf !== undefined)
1470
1648
  out.denialLimitFallback = dlf;
1471
1649
  });
1650
+ if (leg === 'frame') {
1651
+ v1Bit(() => {
1652
+ const o = readAskOrigin(carrier);
1653
+ if (o !== undefined)
1654
+ out.origin = o;
1655
+ });
1656
+ v1Bit(() => {
1657
+ const w = readApprovalCardWord(carrier);
1658
+ if (w !== undefined)
1659
+ out.approvalCard = w;
1660
+ });
1661
+ }
1472
1662
  v1Bit(() => {
1473
- const pair = readAskClassPair(card);
1663
+ const pair = readAskClassPair(carrier);
1474
1664
  if (pair !== undefined) {
1475
1665
  out.askClass = pair.askClass;
1476
1666
  out.classes = pair.classes;
1477
1667
  }
1478
1668
  });
1479
- out.grantBinding = readApprovalGrantBinding(card);
1480
- return Object.freeze(withUnreadableEvidence(card, 'v1', out));
1669
+ if (leg === 'frame') {
1670
+ v1Bit(() => {
1671
+ const g = readApprovalBoundGrants(carrier);
1672
+ if (g !== undefined)
1673
+ out.boundGrants = g;
1674
+ });
1675
+ v1Bit(() => {
1676
+ const w = readApprovalClassifierNoRuling(carrier);
1677
+ if (w !== undefined)
1678
+ out.classifierNoRuling = w;
1679
+ });
1680
+ }
1681
+ v1Bit(() => {
1682
+ const msg = ownDataValue(carrier, 'message');
1683
+ if (typeof msg === 'string' && msg.trim() !== '')
1684
+ out.message = msg;
1685
+ });
1686
+ if (leg === 'generic')
1687
+ out.cardPayloadUnreadable = true;
1688
+ if (leg === 'frame')
1689
+ out.allowSessionCarried = true;
1690
+ out.grantBinding = readApprovalGrantBinding(carrier);
1691
+ return withUnreadableEvidence(carrier, leg, out, realApprovalUnreadable ? ['requiresRealApproval'] : []);
1692
+ }
1693
+ export function approvalCardFactsOfV1(card) {
1694
+ if (typeof card !== 'object' || card === null)
1695
+ return undefined;
1696
+ return Object.freeze(cardFactsOf(card, 'v1'));
1697
+ }
1698
+ export function approvalCardFactsOfFrame(frame, opts) {
1699
+ if (typeof frame !== 'object' || frame === null)
1700
+ return undefined;
1701
+ return Object.freeze(cardFactsOf(frame, 'frame', opts));
1702
+ }
1703
+ export function approvalCardFactsOfGeneric(card) {
1704
+ if (typeof card !== 'object' || card === null)
1705
+ return undefined;
1706
+ return Object.freeze(cardFactsOf(card, 'generic'));
1481
1707
  }
1482
1708
  const MAX_RESPOND_NOTE_CHARS = 2048;
1483
1709
  export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamArgs, signal, lane, run) {
1710
+ const receivedAtMs = Date.now();
1484
1711
  const approvalId = frame.approvalId;
1712
+ const entry = { frame, approvalId, identity: liveCardIdentityOf(approvalId, frame, run), respond, streamArgs, signal, lane, receivedAtMs };
1713
+ return enterLiveCard(entry, 0);
1714
+ }
1715
+ const LIVE_ENTRY_MAX_ROUNDS = 8;
1716
+ function enterLiveCard(e, round) {
1717
+ const { approvalId, signal } = e;
1485
1718
  const held = liveCardHoldOf(liveFrameCallKey(approvalId));
1486
1719
  if (held !== undefined)
1487
1720
  return joinLiveCard(held, signal);
1488
1721
  const settled = settledLiveCardOutcome(approvalId);
1489
1722
  if (settled !== undefined)
1490
1723
  return Promise.resolve(settled);
1491
- return holdLiveCard(liveCardIdentityOf(approvalId, frame, run), signal, async (hold) => {
1724
+ const other = round < LIVE_ENTRY_MAX_ROUNDS ? approvalHoldFor(e.identity, (h) => h.lane === 'live') : undefined;
1725
+ if (other !== undefined)
1726
+ return joinOtherLaneHolder(e, other, round);
1727
+ return holdLiveCard(e.identity, signal, async (hold) => {
1492
1728
  const slot = {};
1493
- const out = await presentToolApprovalFrameAndRespond(frame, approvalId, respond, streamArgs, signal, lane, hold, slot);
1729
+ const out = await presentToolApprovalFrameAndRespond(e.frame, approvalId, e.respond, e.streamArgs, signal, e.lane, hold, slot, e.receivedAtMs);
1494
1730
  noteCallOutcome(slot, out);
1495
1731
  return out;
1496
1732
  });
1497
1733
  }
1734
+ async function joinOtherLaneHolder(e, other, round) {
1735
+ const wait = await awaitApprovalHolder(other, e.signal, 'respond');
1736
+ if (wait.kind === 'settled') {
1737
+ noteHolderSettledApproval(e.approvalId, wait.how, wait.outcome);
1738
+ return joinedResolution(wait.outcome);
1739
+ }
1740
+ if (wait.kind === 'left')
1741
+ return { kind: 'not_sent', lane: 'respond', cause: 'retracted', joinedInFlight: true };
1742
+ return enterLiveCard(e, round + 1);
1743
+ }
1498
1744
  function liveCardIdentityOf(approvalId, frame, run) {
1499
1745
  const toolCallId = typeof frame.toolCallId === 'string' && frame.toolCallId !== '' ? frame.toolCallId : undefined;
1500
1746
  const fromSubagent = isFromSubagent(frame);
1501
- const ownRun = fromSubagent ? frame.sourceTaskId : run?.taskId;
1747
+ const bound = fromSubagent ? undefined : frameBindingOf(frame);
1748
+ const source = bound ?? run;
1749
+ const ownRun = fromSubagent ? frame.sourceTaskId : source?.taskId;
1502
1750
  const taskId = typeof ownRun === 'string' && ownRun !== '' ? ownRun : undefined;
1503
- const sessionId = !fromSubagent && typeof run?.sessionId === 'string' && run.sessionId !== '' ? run.sessionId : undefined;
1751
+ const sessionId = !fromSubagent && typeof source?.sessionId === 'string' && source.sessionId !== '' ? source.sessionId : undefined;
1752
+ const askSig = carrierAskSig(frame);
1504
1753
  return {
1505
1754
  approvalId,
1506
1755
  ...(toolCallId !== undefined ? { toolCallId } : {}),
1507
1756
  ...(taskId !== undefined ? { taskId } : {}),
1508
1757
  ...(sessionId !== undefined ? { sessionId } : {}),
1758
+ ...(askSig !== undefined ? { askSig } : {}),
1509
1759
  };
1510
1760
  }
1511
1761
  function readApprovalHolderIdentity(v) {
@@ -1527,107 +1777,51 @@ function readApprovalHolderIdentity(v) {
1527
1777
  }
1528
1778
  export async function joinApprovalHolder(identity, signal) {
1529
1779
  const who = readApprovalHolderIdentity(identity);
1530
- const hold = liveCardHoldFor(who);
1780
+ const hold = approvalHoldFor(who);
1531
1781
  if (hold === undefined) {
1532
1782
  const entry = settledApprovalOf(who.approvalId);
1533
- return settledForDurableLane(entry) && entry !== undefined ? { kind: 'joined', outcome: { ...entry.outcome, joinedInFlight: true } } : { kind: 'free' };
1783
+ return settledForDurableLane(entry) && entry !== undefined ? { kind: 'joined', outcome: joinedResolution(entry.outcome) } : { kind: 'free' };
1534
1784
  }
1535
- const joined = await awaitLiveCardHolder(hold, signal);
1536
- return joined !== undefined ? { kind: 'joined', outcome: joined } : { kind: 'free' };
1785
+ const wait = await awaitApprovalHolder(hold, signal, 'decide');
1786
+ if (wait.kind === 'settled')
1787
+ return { kind: 'joined', outcome: joinedResolution(wait.outcome) };
1788
+ if (wait.kind === 'left')
1789
+ return { kind: 'joined', outcome: { kind: 'not_sent', lane: 'decide', cause: 'retracted', joinedInFlight: true } };
1790
+ return { kind: 'free' };
1791
+ }
1792
+ export function claimApprovalCard(identity, signal) {
1793
+ return claimApprovalCardFor(readApprovalHolderIdentity(identity), signal);
1537
1794
  }
1538
- async function presentToolApprovalFrameAndRespond(frame, approvalId, respond, streamArgs, signal, lane, hold, ledgerSlot) {
1795
+ async function presentToolApprovalFrameAndRespond(frame, approvalId, respond, streamArgs, signal, lane, hold, ledgerSlot, receivedAtMs) {
1539
1796
  const frameToolCallId = typeof frame.toolCallId === 'string' && frame.toolCallId !== '' ? frame.toolCallId : undefined;
1540
1797
  const decidedByReading = decidedByReadingSnapshotOf(lane);
1541
- const toolName = typeof frame.toolName === 'string' ? frame.toolName : 'Write';
1542
- let args = streamArgs !== undefined && streamArgs !== null
1543
- ? streamArgs
1544
- : frame.args !== undefined && frame.args !== null
1545
- ? frame.args
1546
- : undefined;
1547
- let wireNote;
1548
- let argsFallback = false;
1549
- if (typeof args !== 'object' || args === null) {
1550
- argsFallback = true;
1551
- const fallback = argsFallbackOf(frame.message, frame.argsOmitted);
1552
- args = fallback.args;
1553
- if (fallback.omittedNote !== undefined) {
1554
- wireNote = fallback.omittedNote;
1555
- hostLog('debug', `liveToolApprovalWire: frame ${approvalId} args omitted (>16KiB wire cap) — card falls back to ${fallback.pathRecovered ? 'the message-derived path' : 'an empty input (no path could be derived from the message)'}`);
1556
- }
1557
- if (lane?.argsUnavailable === true) {
1558
- wireNote =
1559
- "this request's tool arguments are not available on this surface — it was raised while no client was connected, " +
1560
- 'so you are deciding without seeing them; deny it if you are not sure what it will do. ' +
1561
- 'Edits are not accepted on this card: there is no original input to edit';
1562
- }
1563
- }
1564
- const argsNotRealInput = argsFallback || lane?.argsUnavailable === true;
1565
- const ruleOffers = readRuleOffers(frame.ruleOffers);
1566
- const denialLimitFallback = readDenialLimitFallback(frame.denialLimitFallback);
1567
- const frameShadow = readAllowRuleShadowFromWire(frame, 'frame');
1568
- const card = await surfaceCallTrackedCard(frameToolCallId, approvalId, ledgerSlot, withUnreadableEvidence(frame, 'frame', {
1569
- toolName,
1570
- args: args,
1798
+ const facts = cardFactsOf(frame, 'frame', { streamArgs, ...(lane?.argsUnavailable === true ? { argsUnavailable: true } : {}) });
1799
+ const argsNotRealInput = facts.argsUnavailable === true;
1800
+ const ruleOffers = facts.ruleOffers;
1801
+ if (argsNotRealInput && lane?.argsUnavailable !== true && facts.wireNote !== undefined) {
1802
+ hostLog('debug', `liveToolApprovalWire: frame ${approvalId} args omitted (>16KiB wire cap) — card falls back to ${Object.hasOwn(facts.args, 'file_path') ? 'the message-derived path' : 'an empty input (no path could be derived from the message)'}`);
1803
+ }
1804
+ const card = await surfaceCallTrackedCard(frameToolCallId, approvalId, ledgerSlot, {
1805
+ ...facts,
1571
1806
  callKey: liveFrameCallKey(approvalId),
1572
- ...(frameToolCallId !== undefined
1573
- ? { toolCallId: frameToolCallId }
1574
- : {}),
1575
1807
  signal: hold.cardSignal,
1576
- ...(isFromSubagent(frame) ? { workerBadge: subagentBadgeFor(frame) } : {}),
1577
- ...(wireNote !== undefined ? { wireNote } : {}),
1578
- ...(argsNotRealInput ? { argsUnavailable: true } : {}),
1579
- ...(frame.governanceForced === true ? { governanceForced: true } : {}),
1580
- ...(lane?.windowIsCurrent === true
1581
- ? {
1582
- ...(isNonNegativeFinite(frame.expiresInMs) ? { expiresInMs: frame.expiresInMs } : {}),
1583
- ...(isNonNegativeFinite(frame.expiresAtMs) ? { expiresAtMs: frame.expiresAtMs } : {}),
1584
- ...(isNonNegativeFinite(frame.serverNowMs) ? { serverNowMs: frame.serverNowMs } : {}),
1585
- ...(isNonNegativeFinite(frame.expiresInMs) || isNonNegativeFinite(frame.expiresAtMs)
1586
- ? { receivedAtMs: Date.now() }
1587
- : {}),
1588
- }
1589
- : {}),
1590
- ...(isFromSubagent(frame) && isToolApprovalDelegation(frame.delegation)
1591
- ? { delegation: frame.delegation }
1592
- : {}),
1593
- ...(ruleOffers !== undefined ? { ruleOffers } : {}),
1594
- ...((sh) => (sh !== undefined ? { allowRuleShadow: sh } : {}))(frameShadow),
1595
- ...(isWireRecordCarrier(frame.probeCause) ? { probeCause: frame.probeCause } : {}),
1596
- ...((r) => (r !== undefined ? { probeReason: r } : {}))(readProbeReason(frame)),
1597
- ...(readApprovalRememberRefused(frame) ? { rememberRefused: true } : {}),
1598
- ...(isWireRecordCarrier(frame.ruleEvidence) ? { ruleEvidence: frame.ruleEvidence } : {}),
1599
- ...(frame.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
1600
- ...(hasOwnStrictTrue(frame, 'mandated') ? { mandated: true } : {}),
1601
- ...((w) => (w !== undefined ? { mandate: w } : {}))(readApprovalMandate(frame)),
1602
- ...(() => { const rc = readReadRootCandidate(frame.readRootCandidate); return rc !== undefined ? { readRootCandidate: rc } : {}; })(),
1603
- ...(frame.inputHasBidi === true ? { inputHasBidi: true } : {}),
1604
- ...(typeof frame.ruleOffersAbsence === 'string' && frame.ruleOffersAbsence !== ''
1605
- ? { ruleOffersAbsence: frame.ruleOffersAbsence }
1606
- : {}),
1607
- ...(denialLimitFallback !== undefined ? { denialLimitFallback } : {}),
1608
- ...((o) => (o !== undefined ? { origin: o } : {}))(readAskOrigin(frame)),
1609
- ...((w) => (w !== undefined ? { approvalCard: w } : {}))(readApprovalCardWord(frame)),
1610
- ...(readAskClassPair(frame) ?? {}),
1611
- ...((g) => (g !== undefined ? { boundGrants: g } : {}))(readApprovalBoundGrants(frame)),
1612
- ...((w) => (w !== undefined ? { classifierNoRuling: w } : {}))(readApprovalClassifierNoRuling(frame)),
1613
- allowSessionCarried: true,
1614
- grantBinding: readApprovalGrantBinding(frame),
1615
- }));
1808
+ ...approvalWindowKeysOf(frame, { windowIsCurrent: lane?.windowIsCurrent === true, receivedAtMs }),
1809
+ });
1616
1810
  if (hold.superseded !== undefined) {
1617
1811
  hostLog('debug', `liveToolApprovalWire: ${approvalId} was settled by the engine while its card was up${hold.superseded.parked ? ' (parked)' : ''} — card withdrawn; ` +
1618
1812
  `the card came back ${card.kind}; nothing sent`);
1619
1813
  if (hold.superseded.parked && (card.kind === 'allow' || card.kind === 'deny'))
1620
1814
  surfaceApprovalParking();
1621
- return { decision: 'unresolved', retracted: true };
1815
+ return { kind: 'not_sent', lane: 'respond', cause: 'retracted' };
1622
1816
  }
1623
1817
  if (argsNotRealInput && card.kind === 'allow' && card.updatedInput !== undefined) {
1624
1818
  hostLog('error', `liveToolApprovalWire: ${approvalId} card returned an edited approval on a card whose arguments are not the tool's real input (${lane?.argsUnavailable === true ? 'args-unavailable ask' : 'arguments omitted or absent on the frame'}) — nothing sent (the ask stays pending)`);
1625
1819
  surfaceEditRefusedOnBlindAsk();
1626
- return { decision: 'unresolved', editRefused: true };
1820
+ return { kind: 'not_sent', lane: 'respond', cause: 'edit_refused' };
1627
1821
  }
1628
1822
  if (card.kind === RETRACTED_CARD_DECISION_KIND) {
1629
1823
  hostLog('debug', `liveToolApprovalWire: ${approvalId} card retracted without a decision${card.reason ? ` (${card.reason})` : ''} — nothing sent`);
1630
- return { decision: 'unresolved', retracted: true };
1824
+ return { kind: 'not_sent', lane: 'respond', cause: 'retracted' };
1631
1825
  }
1632
1826
  if (card.kind === 'aborted') {
1633
1827
  hold.phase = 'respond';
@@ -1642,33 +1836,40 @@ async function presentToolApprovalFrameAndRespond(frame, approvalId, respond, st
1642
1836
  hold.trailing = interruptDeny.then((body) => readToolApprovalRespondParking(body) === undefined, () => false);
1643
1837
  observeCancelByDeny(interruptDeny, approvalId);
1644
1838
  hostLog('debug', `liveToolApprovalWire: ${approvalId} card aborted — one tool-level deny issued to settle the pending ask`);
1645
- return { decision: 'unresolved', aborted: true };
1839
+ return { kind: 'unsettled', lane: 'respond', cause: 'interrupted' };
1646
1840
  }
1647
1841
  hold.phase = 'respond';
1648
- const decision = card.kind === 'allow' ? (card.allowSession ? 'allow_session' : 'allow') : 'deny';
1842
+ const noteCapable = lane?.approvalDecisionNoteCapable === true;
1843
+ const denyReasonRead = card.kind === 'deny' ? ((r) => (typeof r === 'string' ? r : undefined))(card.reason) : undefined;
1844
+ const answerSnapshot = card.kind === 'deny'
1845
+ ? ((by) => ({ kind: 'deny', ...(denyReasonRead !== undefined ? { reason: denyReasonRead } : {}), ...(by !== undefined ? { settledBy: by } : {}) }))(readApprovalDenySettledBy(card))
1846
+ : card;
1847
+ const basePayload = answerPayloadFor(answerSnapshot, 'respond', { approvalDecisionNoteCapable: noteCapable });
1848
+ const decision = basePayload?.decision ?? 'deny';
1849
+ if (card.kind === 'allow' && typeof card.note === 'string' && card.note.trim() !== '' && answerCarriage({ callKey: liveFrameCallKey(approvalId) }).allowNote === 'none') {
1850
+ hostLog('debug', `liveToolApprovalWire: ${approvalId} card carried a note on Yes (len=${card.note.length}) — this engine has no field that brings it to the model, so it was not sent`);
1851
+ }
1649
1852
  const denySettledByForWire = card.kind === 'deny' ? readApprovalDenySettledBy(card) : undefined;
1650
1853
  const attributionKeys = decision === 'deny' ? decisionAttributionKeyOf(decidedByReading, denySettledByForWire) : {};
1651
1854
  const sentAttributionKey = carriesAttributionKey(attributionKeys);
1652
1855
  let lastSendCarriedAttribution = sentAttributionKey;
1653
- const denyReasonSnapshot = card.kind === 'deny' && typeof card.reason === 'string' ? card.reason : undefined;
1856
+ const denyReasonSnapshot = denyReasonRead;
1654
1857
  if (card.kind === 'failed') {
1655
1858
  hostLog('debug', `liveToolApprovalWire: approval card unavailable (${card.reason}) — fail-closed deny for ${approvalId}`);
1656
1859
  }
1657
- let note;
1658
- if (denyReasonSnapshot !== undefined && denyReasonSnapshot.trim() !== '') {
1659
- if (lane?.approvalDecisionNoteCapable !== true) {
1860
+ const note = basePayload?.note;
1861
+ const noteGate = respondNoteGate(denyReasonSnapshot, noteCapable);
1862
+ if ('dropped' in noteGate && noteGate.dropped !== 'blank' && denyReasonSnapshot !== undefined) {
1863
+ if (noteGate.dropped === 'capability') {
1660
1864
  hostLog('debug', `liveToolApprovalWire: ${approvalId} card supplied a deny reason (len=${denyReasonSnapshot.length}) but the ` +
1661
1865
  'approvalDecisionNote capability is not confirmed for this engine — note not sent (an unknown key would be ' +
1662
1866
  'silently swallowed by an older server while still acking 200)');
1663
1867
  }
1664
- else if (denyReasonSnapshot.length > MAX_RESPOND_NOTE_CHARS) {
1868
+ else {
1665
1869
  hostLog('debug', `liveToolApprovalWire: ${approvalId} deny reason exceeds the decision_note cap ` +
1666
1870
  `(len=${denyReasonSnapshot.length} > ${MAX_RESPOND_NOTE_CHARS}) — note not sent at all (the server rejects the WHOLE ` +
1667
1871
  'respond with 400 over an oversize note, and a silently halved audit reason is worse than none)');
1668
1872
  }
1669
- else {
1670
- note = denyReasonSnapshot;
1671
- }
1672
1873
  }
1673
1874
  try {
1674
1875
  let persistRule;
@@ -1735,7 +1936,7 @@ async function presentToolApprovalFrameAndRespond(frame, approvalId, respond, st
1735
1936
  }
1736
1937
  const respondOpts = {
1737
1938
  ...(signal && !signal.aborted ? { signal } : {}),
1738
- ...(card.kind === 'allow' && card.updatedInput !== undefined ? { updatedInput: card.updatedInput } : {}),
1939
+ ...(basePayload?.updatedInput !== undefined ? { updatedInput: basePayload.updatedInput } : {}),
1739
1940
  ...(persistRule !== undefined ? { persistRule } : {}),
1740
1941
  ...(persistRuleEdited !== undefined ? { persistRuleEdited } : {}),
1741
1942
  ...(persistRuleBatchOfferIndex !== undefined ? { persistRuleBatchOfferIndex } : {}),
@@ -1745,6 +1946,8 @@ async function presentToolApprovalFrameAndRespond(frame, approvalId, respond, st
1745
1946
  let attributionDropped = false;
1746
1947
  let attributionRenamed = false;
1747
1948
  let raw;
1949
+ if (basePayload !== undefined)
1950
+ emitAnswerCommitted(liveFrameCallKey(approvalId), 'respond', answerSnapshot, basePayload);
1748
1951
  try {
1749
1952
  raw = await respond(approvalId, decision, respondOpts);
1750
1953
  }
@@ -1790,41 +1993,56 @@ async function presentToolApprovalFrameAndRespond(frame, approvalId, respond, st
1790
1993
  hostLog('debug', `liveToolApprovalWire: respond(${decision}) for ${approvalId} answered 202 parking_in_progress${parking.retryAfterMs !== undefined ? ` (retryAfterMs=${parking.retryAfterMs})` : ''} — ` +
1791
1994
  `the answer did NOT take effect${parking.decidePath !== undefined ? ' (the engine pointed at a decide path for the parked card)' : ''}; not re-sent, not reported as decided; the user is told to answer again on the parked card`);
1792
1995
  surfaceApprovalParking(parking.retryAfterMs);
1793
- return { decision: 'unresolved', parking };
1996
+ return { kind: 'unsettled', lane: 'respond', cause: 'parking_in_progress', parking };
1794
1997
  }
1795
1998
  hostLog('error', `liveToolApprovalWire: 202 parking body for ${approvalId} is ${parkingBody.kind === 'parking' ? `for another approval (${parkingBody.view.approvalId})` : 'unreadable (approvalId is not usable)'} — ` +
1796
1999
  'reporting the answer as NOT settled (never as decided, never as parked); not re-sent');
1797
- return { decision: 'unresolved' };
2000
+ return { kind: 'unsettled', lane: 'respond', cause: 'unreadable' };
1798
2001
  }
1799
2002
  const parsed = readToolApprovalRespondAck(raw);
2003
+ const replayedForThisCard = parsed !== undefined && isToolApprovalRespondReplayed(parsed) && parsed.approvalId === approvalId;
2004
+ const claimsReplay = ownIdempotentTrue(raw) || (parsed !== undefined && isToolApprovalRespondReplayed(parsed));
2005
+ if (claimsReplay && !replayedForThisCard) {
2006
+ const claimed = replayClaimOf(raw);
2007
+ if (claimed.approvalId === approvalId && claimed.decision !== undefined && !claimed.carriesDelivery) {
2008
+ hostLog('debug', `liveToolApprovalWire: respond(${decision}) for ${approvalId} answered with a replay (body not in the full replay shape) — the approval was already decided ` +
2009
+ `(${claimed.decision}); this answer was not delivered, reporting the earlier decision`);
2010
+ return { kind: 'decided', lane: 'respond', decision: claimed.decision };
2011
+ }
2012
+ hostLog('error', `liveToolApprovalWire: respond(${decision}) for ${approvalId} answered with a replay of an earlier decision that cannot be read or names another approval — ` +
2013
+ 'reporting the answer as NOT settled (never as the decision this client sent); not re-sent');
2014
+ return { kind: 'unsettled', lane: 'respond', cause: 'unreadable' };
2015
+ }
1800
2016
  let ack = parsed;
1801
- if (parsed !== undefined && (parsed.approvalId !== approvalId || parsed.decision !== decision)) {
2017
+ if (parsed !== undefined && (parsed.approvalId !== approvalId || (!isToolApprovalRespondReplayed(parsed) && parsed.decision !== decision))) {
1802
2018
  hostLog('error', `liveToolApprovalWire: DISCARDING respond ack for ${approvalId} — it does not correlate ` +
1803
2019
  `(ack.approvalId=${parsed.approvalId} ack.decision=${parsed.decision}, sent decision=${decision}); ` +
1804
2020
  'treating as "no ack" (unknown) — never surfacing a safety notice off an unrelated receipt');
1805
2021
  ack = undefined;
1806
2022
  }
1807
- if (ack !== undefined &&
1808
- (ack.persistedRule !== undefined || ack.persistedRules !== undefined || ack.persistedRuleAnchors !== undefined)) {
2023
+ const replayed = ack !== undefined && isToolApprovalRespondReplayed(ack) ? ack : undefined;
2024
+ let applied = ack !== undefined && replayed === undefined ? ack : undefined;
2025
+ if (applied !== undefined &&
2026
+ (applied.persistedRule !== undefined || applied.persistedRules !== undefined || applied.persistedRuleAnchors !== undefined)) {
1809
2027
  const sentEditedArm = persistRule !== undefined && persistRuleEdited === true;
1810
2028
  const expectedBatchCount = batchArmTarget?.rules.length;
1811
2029
  const anchorKeyOnWire = typeof raw === 'object' &&
1812
2030
  raw !== null &&
1813
2031
  Object.prototype.hasOwnProperty.call(raw, 'persistedRuleAnchors');
1814
- const anchors = ack.persistedRuleAnchors;
2032
+ const anchors = applied.persistedRuleAnchors;
1815
2033
  const anchorsOk = anchors !== undefined
1816
2034
  ? persistRuleBatchOfferIndex !== undefined &&
1817
2035
  expectedBatchCount !== undefined &&
1818
2036
  anchors.every(a => a.offerIndex === persistRuleBatchOfferIndex && a.memberIndex < expectedBatchCount)
1819
2037
  : !anchorKeyOnWire;
1820
- const batchEchoOk = ack.persistedRules !== undefined &&
2038
+ const batchEchoOk = applied.persistedRules !== undefined &&
1821
2039
  expectedBatchCount !== undefined &&
1822
- ack.persistedRules.length === expectedBatchCount &&
2040
+ applied.persistedRules.length === expectedBatchCount &&
1823
2041
  anchorsOk;
1824
2042
  const strayEchoes = [];
1825
- if (ack.persistedRule !== undefined && !sentEditedArm)
2043
+ if (applied.persistedRule !== undefined && !sentEditedArm)
1826
2044
  strayEchoes.push('persistedRule');
1827
- if (ack.persistedRules !== undefined && !batchEchoOk)
2045
+ if (applied.persistedRules !== undefined && !batchEchoOk)
1828
2046
  strayEchoes.push('persistedRules');
1829
2047
  if (anchors !== undefined && !(anchorsOk && batchEchoOk))
1830
2048
  strayEchoes.push('persistedRuleAnchors');
@@ -1833,60 +2051,53 @@ async function presentToolApprovalFrameAndRespond(frame, approvalId, respond, st
1833
2051
  `${approvalId} — they do not correlate with the persistence arm actually sent ` +
1834
2052
  `(editedArm=${String(sentEditedArm)} batchArmMembers=${String(expectedBatchCount ?? 'none')} ` +
1835
2053
  `sentOfferIndex=${String(persistRuleBatchOfferIndex ?? 'none')} ` +
1836
- `echoedRules=${String(ack.persistedRules?.length ?? 'none')} ` +
2054
+ `echoedRules=${String(applied.persistedRules?.length ?? 'none')} ` +
1837
2055
  `echoedAnchorOffer=${String(anchors?.[0]?.offerIndex ?? 'none')}); never telling the user a rule ` +
1838
2056
  'was stored off a receipt that does not line up with what this client actually sent');
1839
- const { persistedRule: _pr, persistedRules: _prs, persistedRuleAnchors: _pra, ...rest } = ack;
2057
+ const { persistedRule: _pr, persistedRules: _prs, persistedRuleAnchors: _pra, ...rest } = applied;
1840
2058
  void _pr;
1841
2059
  void _prs;
1842
2060
  void _pra;
1843
- ack = {
2061
+ applied = {
1844
2062
  ...rest,
1845
- ...(ack.persistedRule !== undefined && sentEditedArm ? { persistedRule: ack.persistedRule } : {}),
1846
- ...(ack.persistedRules !== undefined && batchEchoOk ? { persistedRules: ack.persistedRules } : {}),
2063
+ ...(applied.persistedRule !== undefined && sentEditedArm ? { persistedRule: applied.persistedRule } : {}),
2064
+ ...(applied.persistedRules !== undefined && batchEchoOk ? { persistedRules: applied.persistedRules } : {}),
1847
2065
  ...(anchors !== undefined && anchorsOk && batchEchoOk ? { persistedRuleAnchors: anchors } : {}),
1848
2066
  };
2067
+ ack = applied;
1849
2068
  }
1850
2069
  }
1851
- if (ruleArmDroppedForCaps)
2070
+ if (replayed === undefined && ruleArmDroppedForCaps)
1852
2071
  surfaceRuleArmNotSent();
1853
- if (ruleArmDroppedForCheck !== undefined)
2072
+ if (replayed === undefined && ruleArmDroppedForCheck !== undefined)
1854
2073
  surfaceRuleArmRejected(ruleArmDroppedForCheck);
1855
- if (decision === 'allow_session' && ack?.rememberApplied === false) {
2074
+ if (decision === 'allow_session' && applied?.rememberApplied === false) {
1856
2075
  logAfterDecided(`liveToolApprovalWire: ${approvalId} allow_session ack rememberApplied=false — grant not stored, surfacing honest notice`);
1857
2076
  surfaceRememberNotApplied();
1858
2077
  }
1859
- if (respondOpts.updatedInput !== undefined && ack?.updatedInputForwarded === false) {
2078
+ if (respondOpts.updatedInput !== undefined && applied?.updatedInputForwarded === false) {
1860
2079
  logAfterDecided(`liveToolApprovalWire: ${approvalId} edited args were NOT forwarded (ack.updatedInputForwarded=false) — the tool runs on the ORIGINAL input`);
1861
2080
  surfaceEditNotForwarded();
1862
2081
  }
1863
- if (note !== undefined && ack !== undefined && ack.noteRecorded !== true) {
2082
+ if (note !== undefined && applied !== undefined && applied.noteRecorded !== true) {
1864
2083
  hostLog('debug', `liveToolApprovalWire: ${approvalId} decision note was sent but not persisted ` +
1865
- `(ack.noteRecorded=${String(ack.noteRecorded)}) — decision stood; the audit note did not land on the ask row`);
2084
+ `(ack.noteRecorded=${String(applied.noteRecorded)}) — decision stood; the audit note did not land on the ask row`);
1866
2085
  }
1867
- const denyAttribution = decision === 'deny'
1868
- ? {
1869
- ...(attributionDropped ? { denyAttributionDropped: true } : {}),
1870
- ...(attributionRenamed ? { denyAttributionRenamed: true } : {}),
1871
- ...(denySettledByForWire !== undefined ? { denySettledBy: denySettledByForWire } : {}),
1872
- ...(denyReasonSnapshot !== undefined && denyReasonSnapshot.trim() !== ''
1873
- ? { denyReason: denyReasonSnapshot }
1874
- : {}),
1875
- }
1876
- : {};
1877
- return ack !== undefined ? { decision, ack, ...denyAttribution } : { decision, ...denyAttribution };
2086
+ if (replayed !== undefined) {
2087
+ hostLog('debug', `liveToolApprovalWire: respond(${decision}) for ${approvalId} answered with a replay — the approval was already decided (${replayed.decision}); ` +
2088
+ 'this answer was not delivered, reporting the earlier decision');
2089
+ return { kind: 'decided', lane: 'respond', decision: replayed.decision, ack: replayed };
2090
+ }
2091
+ const deny = decision === 'deny' ? denyFactsOf(denySettledByForWire, denyReasonSnapshot, attributionDropped, attributionRenamed) : undefined;
2092
+ return { kind: 'decided', lane: 'respond', decision, ...(ack !== undefined ? { ack } : {}), ...(deny !== undefined ? { deny } : {}) };
1878
2093
  }
1879
2094
  catch (e) {
1880
- const respondRefusal = readToolApprovalRespondRefusal(e);
1881
- const attributionRefusal = denyAttributionRefusalFromError(e, lastSendCarriedAttribution);
2095
+ const r = resolutionOfDecisionFailure(e, { lane: 'respond', route: 'live_respond', attributionCarried: lastSendCarriedAttribution });
2096
+ const refusal = r.kind === 'unsettled' ? r.refusal : undefined;
1882
2097
  hostLog('debug', `liveToolApprovalWire: respond(${decision}) failed for ${approvalId} ` +
1883
- `(status=${respondRefusal?.status ?? 'none'} errorCode=${logSafeErrorCode(respondRefusal?.errorCode)} ` +
1884
- `messageLen=${respondRefusal?.message?.length ?? 0}) — engine self-settles (TTL/abort); ` +
1885
- 'the refusal text is handed back on outcome.respondRefusal for the host to surface');
1886
- return {
1887
- decision: 'unresolved',
1888
- ...(respondRefusal !== undefined ? { respondRefusal } : {}),
1889
- ...(attributionRefusal !== null ? { denyAttributionRefusal: attributionRefusal } : {}),
1890
- };
2098
+ `(status=${refusal?.status ?? 'none'} errorCode=${logSafeErrorCode(refusal?.errorCode)} ` +
2099
+ `messageLen=${refusal?.message?.length ?? 0}) — engine self-settles (TTL/abort); ` +
2100
+ 'the refusal reading is handed back on resolution.refusal for the host to surface');
2101
+ return r;
1891
2102
  }
1892
2103
  }