@sema-agent/client-core 0.94.0 → 0.95.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 (255) hide show
  1. package/CHANGELOG.md +341 -0
  2. package/README.md +70 -47
  3. package/dist/adapt/arms.js +5 -6
  4. package/dist/adapt/panelTasks.js +4 -10
  5. package/dist/adapt/toolCards.js +2 -2
  6. package/dist/adapt/wireShapes.js +3 -8
  7. package/dist/adapter/activeRunSelfHeal.d.ts +8 -3
  8. package/dist/adapter/activeRunSelfHeal.js +20 -12
  9. package/dist/adapter/downstream/eventToSdkMessage.js +24 -17
  10. package/dist/adapter/downstream/terminalToSdkResult.js +11 -3
  11. package/dist/adapter/runStream.js +2 -1
  12. package/dist/adapter/types.d.ts +7 -0
  13. package/dist/adapter/types.js +15 -0
  14. package/dist/agentSession/backgroundView.js +5 -1
  15. package/dist/agentSession/engineAgentRegistryRead.js +2 -1
  16. package/dist/agentSession/engineSessionBackgroundStop.js +3 -2
  17. package/dist/agentsWireCaps.d.ts +2 -0
  18. package/dist/approvalDecidedByCapability.d.ts +0 -1
  19. package/dist/approvalDecidedByCapability.js +2 -7
  20. package/dist/approvalsStreamLiveCapability.d.ts +0 -1
  21. package/dist/approvalsStreamLiveCapability.js +2 -6
  22. package/dist/atMostOnce.d.ts +8 -0
  23. package/dist/atMostOnce.js +36 -0
  24. package/dist/attachmentSeatCapability.d.ts +0 -1
  25. package/dist/attachmentSeatCapability.js +0 -3
  26. package/dist/attachmentsWireCaps.js +11 -6
  27. package/dist/bashExitReading.d.ts +10 -0
  28. package/dist/bashExitReading.js +12 -0
  29. package/dist/callerCwdCapability.d.ts +0 -1
  30. package/dist/callerCwdCapability.js +2 -7
  31. package/dist/centerWiringCapability.d.ts +1 -3
  32. package/dist/centerWiringCapability.js +18 -21
  33. package/dist/classifierStatus.js +4 -4
  34. package/dist/cloudConfigWireCaps.js +43 -21
  35. package/dist/compensations.js +1 -1
  36. package/dist/cycleOrder.d.ts +15 -0
  37. package/dist/cycleOrder.js +45 -0
  38. package/dist/decideFailureNote.js +4 -1
  39. package/dist/decideReceipt.d.ts +2 -1
  40. package/dist/decideReceipt.js +41 -13
  41. package/dist/declared/supportFloorDeclared.d.ts +1 -0
  42. package/dist/declared/supportFloorDeclared.js +5 -0
  43. package/dist/deviceExecutorManagementCapability.d.ts +0 -1
  44. package/dist/deviceExecutorManagementCapability.js +7 -16
  45. package/dist/deviceRebind.d.ts +88 -0
  46. package/dist/deviceRebind.js +242 -0
  47. package/dist/displayUntrusted.d.ts +1 -0
  48. package/dist/displayUntrusted.js +313 -11
  49. package/dist/engineAgentAbsence.d.ts +2 -2
  50. package/dist/engineAgentPanelStore.d.ts +8 -18
  51. package/dist/engineAgentPanelStore.js +19 -537
  52. package/dist/engineCapReader.d.ts +6 -6
  53. package/dist/engineCapReader.js +30 -43
  54. package/dist/engineCapsCache.d.ts +7 -2
  55. package/dist/engineCapsCache.js +64 -26
  56. package/dist/engineCredentialRequirement.d.ts +9 -0
  57. package/dist/engineCredentialRequirement.js +23 -0
  58. package/dist/engineEnvSourcing.d.ts +53 -0
  59. package/dist/engineEnvSourcing.js +81 -0
  60. package/dist/engineErrorCodes.d.ts +14 -3
  61. package/dist/engineErrorCodes.js +31 -10
  62. package/dist/engineIdentity.js +2 -0
  63. package/dist/engineWireSdk.js +2 -2
  64. package/dist/executionLaneCapability.d.ts +2 -3
  65. package/dist/executionLaneCapability.js +18 -23
  66. package/dist/fileHistoryCaptureCapability.d.ts +0 -1
  67. package/dist/fileHistoryCaptureCapability.js +2 -6
  68. package/dist/fileHistoryLegNotice.d.ts +3 -2
  69. package/dist/fileHistoryLegNotice.js +51 -2
  70. package/dist/fleet/fleetLedger.d.ts +2 -0
  71. package/dist/fleet/fleetLedger.js +69 -52
  72. package/dist/fleet/fleetProjection.d.ts +0 -3
  73. package/dist/fleet/fleetProjection.js +9 -16
  74. package/dist/fleetAgentPanelProjection.d.ts +4 -17
  75. package/dist/fleetAgentPanelProjection.js +7 -197
  76. package/dist/fleetAgentRowLike.d.ts +16 -0
  77. package/dist/fleetAgentRowLike.js +1 -0
  78. package/dist/gateOutcome.js +30 -18
  79. package/dist/generated/sdkFactTables.d.ts +2 -0
  80. package/dist/generated/sdkFactTables.js +17 -0
  81. package/dist/generated/settingsSchemaFns.d.ts +10 -0
  82. package/dist/generated/settingsSchemaFns.js +22 -0
  83. package/dist/headlessPermissionModeWire.d.ts +1 -1
  84. package/dist/headlessPermissionModeWire.js +1 -1
  85. package/dist/headlessReconnectWire.js +49 -20
  86. package/dist/hitl/allowRuleShadow.d.ts +1 -1
  87. package/dist/hitl/allowRuleShadow.js +49 -8
  88. package/dist/hitl/approvalCompleteParked.d.ts +1 -0
  89. package/dist/hitl/approvalCompleteParked.js +14 -0
  90. package/dist/hitl/approvalEndingCause.d.ts +5 -0
  91. package/dist/hitl/approvalEndingCause.js +61 -0
  92. package/dist/hitl/approvalHolds.d.ts +12 -2
  93. package/dist/hitl/approvalHolds.js +53 -2
  94. package/dist/hitl/approvalsFeed.js +23 -11
  95. package/dist/hitl/askGateWire.d.ts +2 -1
  96. package/dist/hitl/askGateWire.js +26 -1
  97. package/dist/hitl/automaticAnswerVerdict.js +2 -2
  98. package/dist/hitl/crashConverged.js +2 -0
  99. package/dist/hitl/decisionConflict.d.ts +3 -0
  100. package/dist/hitl/decisionConflict.js +43 -0
  101. package/dist/hitl/decisionRefusal.d.ts +4 -0
  102. package/dist/hitl/decisionRefusal.js +26 -1
  103. package/dist/hitl/frameRouter.js +26 -43
  104. package/dist/hitl/gateLedger.d.ts +5 -0
  105. package/dist/hitl/gateLedger.js +21 -0
  106. package/dist/hitl/hitlBridge.d.ts +12 -1
  107. package/dist/hitl/hitlBridge.js +68 -53
  108. package/dist/hitl/hitlHostSurface.js +3 -11
  109. package/dist/hitl/livePendingAsk.d.ts +16 -6
  110. package/dist/hitl/livePendingAsk.js +36 -4
  111. package/dist/hitl/parkExpiryJudgements.d.ts +19 -0
  112. package/dist/hitl/parkExpiryJudgements.js +46 -0
  113. package/dist/hitl/parkResolver.d.ts +3 -7
  114. package/dist/hitl/parkResolver.js +50 -26
  115. package/dist/hitl/persistedRulesWire.js +5 -1
  116. package/dist/hitl/planReviewWire.d.ts +2 -2
  117. package/dist/hitl/planReviewWire.js +56 -30
  118. package/dist/hitl/replayedApproval.d.ts +8 -0
  119. package/dist/hitl/replayedApproval.js +125 -10
  120. package/dist/hitl/resumeRunningCard.d.ts +2 -0
  121. package/dist/hitl/resumeRunningCard.js +15 -4
  122. package/dist/hitl/toolApprovalWire.d.ts +24 -4
  123. package/dist/hitl/toolApprovalWire.js +115 -38
  124. package/dist/hooksWireCaps.js +38 -6
  125. package/dist/host.d.ts +5 -0
  126. package/dist/host.js +3 -1
  127. package/dist/hostCallbackGuard.d.ts +1 -0
  128. package/dist/hostCallbackGuard.js +25 -0
  129. package/dist/index.d.ts +30 -11
  130. package/dist/index.js +20 -8
  131. package/dist/interactiveInterrupt.d.ts +8 -0
  132. package/dist/interactiveInterrupt.js +54 -0
  133. package/dist/limitsWire.d.ts +5 -0
  134. package/dist/limitsWire.js +49 -3
  135. package/dist/liveModelCatalog.d.ts +95 -0
  136. package/dist/liveModelCatalog.js +297 -32
  137. package/dist/mcpEngineLeg.d.ts +25 -0
  138. package/dist/mcpEngineLeg.js +71 -15
  139. package/dist/mcpPanel.js +2 -1
  140. package/dist/mcpProbeCapability.d.ts +0 -1
  141. package/dist/mcpProbeCapability.js +2 -6
  142. package/dist/mcpReconnect.d.ts +1 -3
  143. package/dist/mcpReconnect.js +10 -5
  144. package/dist/memoryComplianceCapability.d.ts +0 -1
  145. package/dist/memoryComplianceCapability.js +2 -6
  146. package/dist/memoryOriginCapability.d.ts +0 -1
  147. package/dist/memoryOriginCapability.js +2 -6
  148. package/dist/memoryVerbsWire.d.ts +1 -1
  149. package/dist/memoryVerbsWire.js +4 -0
  150. package/dist/model/liveCatalogShape.d.ts +14 -0
  151. package/dist/model/liveCatalogShape.js +71 -0
  152. package/dist/model/modelSupplyRules.d.ts +1 -0
  153. package/dist/model/modelSupplyRules.js +30 -9
  154. package/dist/model/modelsPolicy.d.ts +21 -0
  155. package/dist/model/modelsPolicy.js +90 -0
  156. package/dist/modelIdentityResolvability.d.ts +1 -1
  157. package/dist/modelIdentityResolvability.js +5 -12
  158. package/dist/modelSwitchFallback.d.ts +69 -0
  159. package/dist/modelSwitchFallback.js +206 -0
  160. package/dist/notifications.js +12 -11
  161. package/dist/outputStyleSeatCapability.d.ts +0 -1
  162. package/dist/outputStyleSeatCapability.js +2 -7
  163. package/dist/ownKey.d.ts +6 -0
  164. package/dist/ownKey.js +48 -0
  165. package/dist/panelReduce.d.ts +114 -0
  166. package/dist/panelReduce.js +1135 -0
  167. package/dist/panelStateHolder.d.ts +17 -0
  168. package/dist/panelStateHolder.js +201 -0
  169. package/dist/peerLaneCapability.d.ts +0 -1
  170. package/dist/peerLaneCapability.js +2 -6
  171. package/dist/permissionModesCapability.d.ts +0 -1
  172. package/dist/permissionModesCapability.js +5 -10
  173. package/dist/permissionRulesWriteCapability.d.ts +0 -1
  174. package/dist/permissionRulesWriteCapability.js +2 -6
  175. package/dist/permissionsAllowGrantCapability.d.ts +0 -1
  176. package/dist/permissionsAllowGrantCapability.js +3 -9
  177. package/dist/permissionsDeclaration.d.ts +6 -0
  178. package/dist/permissionsDeclaration.js +45 -0
  179. package/dist/printToolResultFrame.js +2 -10
  180. package/dist/promptDelivery.js +0 -8
  181. package/dist/resumeRefusalCopy.d.ts +18 -1
  182. package/dist/resumeRefusalCopy.js +126 -15
  183. package/dist/rewindArchiveCapability.d.ts +2 -1
  184. package/dist/rewindArchiveCapability.js +15 -7
  185. package/dist/rewindEngineAction.d.ts +21 -0
  186. package/dist/rewindEngineAction.js +36 -0
  187. package/dist/rewindWireCaps.d.ts +2 -1
  188. package/dist/rewindWireCaps.js +1 -0
  189. package/dist/runTerminal.d.ts +0 -11
  190. package/dist/runTerminal.js +4 -27
  191. package/dist/sdkRegistryTransit.d.ts +2 -1
  192. package/dist/sdkRegistryTransit.js +1 -0
  193. package/dist/sealedKeyCapability.d.ts +0 -1
  194. package/dist/sealedKeyCapability.js +3 -9
  195. package/dist/seatContract.d.ts +54 -25
  196. package/dist/seatContract.js +394 -171
  197. package/dist/sessionIdSegment.d.ts +1 -0
  198. package/dist/sessionIdSegment.js +11 -0
  199. package/dist/sessionMemoryErase.d.ts +1 -1
  200. package/dist/sessionMemoryErase.js +6 -1
  201. package/dist/sessionMemoryStatus.js +3 -2
  202. package/dist/sessionPolicyCapability.d.ts +0 -1
  203. package/dist/sessionPolicyCapability.js +2 -6
  204. package/dist/sessionReadRedactionCapability.d.ts +0 -1
  205. package/dist/sessionReadRedactionCapability.js +2 -7
  206. package/dist/sessionSyncFileHistoryCapability.d.ts +0 -1
  207. package/dist/sessionSyncFileHistoryCapability.js +2 -6
  208. package/dist/skillToolListsCapability.d.ts +0 -1
  209. package/dist/skillToolListsCapability.js +2 -6
  210. package/dist/skillsWireCaps.d.ts +4 -0
  211. package/dist/skillsWireCaps.js +66 -1
  212. package/dist/sqlEngineCapability.d.ts +0 -1
  213. package/dist/sqlEngineCapability.js +9 -14
  214. package/dist/steerPriorityCapability.d.ts +0 -1
  215. package/dist/steerPriorityCapability.js +3 -9
  216. package/dist/storePostureCapability.d.ts +1 -1
  217. package/dist/storePostureCapability.js +16 -16
  218. package/dist/streamDrainingLegs.d.ts +6 -0
  219. package/dist/streamDrainingLegs.js +23 -0
  220. package/dist/subagent/engineCompactWire.js +13 -38
  221. package/dist/subagent/engineDelegatedPrompt.js +10 -6
  222. package/dist/subagent/engineSubagentOutput.d.ts +0 -1
  223. package/dist/subagent/engineSubagentOutput.js +5 -20
  224. package/dist/subagent/engineSubagentTail.d.ts +0 -1
  225. package/dist/subagent/engineSubagentTail.js +7 -32
  226. package/dist/subagentContentStore.js +3 -3
  227. package/dist/supportFloor.js +2 -5
  228. package/dist/terminalStatuses.d.ts +30 -0
  229. package/dist/terminalStatuses.js +67 -0
  230. package/dist/toolResult.d.ts +5 -0
  231. package/dist/toolResult.js +88 -36
  232. package/dist/types/engineState.d.ts +2 -1
  233. package/dist/usageAnalytics.d.ts +146 -0
  234. package/dist/usageAnalytics.js +311 -0
  235. package/dist/userInvokedSkillsCapability.d.ts +0 -1
  236. package/dist/userInvokedSkillsCapability.js +2 -6
  237. package/dist/webSearchBackendCapability.d.ts +0 -1
  238. package/dist/webSearchBackendCapability.js +3 -7
  239. package/dist/webSearchWireCaps.js +3 -11
  240. package/dist/wireFailureShape.d.ts +4 -0
  241. package/dist/wireFailureShape.js +42 -2
  242. package/dist/wireRefusalCopy.js +2 -18
  243. package/dist/workflowClient.js +7 -11
  244. package/dist/workflowsJournalCapability.d.ts +0 -1
  245. package/dist/workflowsJournalCapability.js +2 -6
  246. package/dist/writeProtectionCapability.d.ts +0 -1
  247. package/dist/writeProtectionCapability.js +5 -10
  248. package/docs/INTEGRATION-CLIENTS.md +1033 -26
  249. package/package.json +5 -5
  250. package/dist/engineCapsGenerationGuard.d.ts +0 -4
  251. package/dist/engineCapsGenerationGuard.js +0 -19
  252. package/dist/panelRunningHistory.d.ts +0 -22
  253. package/dist/panelRunningHistory.js +0 -82
  254. package/dist/registryConflict.d.ts +0 -3
  255. package/dist/registryConflict.js +0 -44
@@ -0,0 +1,14 @@
1
+ function ownData(o, key) {
2
+ const d = Object.getOwnPropertyDescriptor(o, key);
3
+ return d !== undefined && 'value' in d ? d.value : undefined;
4
+ }
5
+ export function approvalCompleteParked(frame) {
6
+ try {
7
+ if (frame === null || typeof frame !== 'object')
8
+ return false;
9
+ return ownData(frame, 'outcome') === 'expired' && ownData(frame, 'parked') === true;
10
+ }
11
+ catch {
12
+ return false;
13
+ }
14
+ }
@@ -0,0 +1,5 @@
1
+ import type { ApprovalSettleCause, AskRowEndingCause } from '@sema-agent/sdk';
2
+ export declare const ASK_ROW_ENDING_CAUSE_WORDS: readonly ["routing_failure_fail_closed", "run_cancelled", "run_exited", "batch_aborted", "batch_sibling_withdrawn", "batch_bound_elsewhere", "adhoc_leg_no_durable_domain", "orphan_ttl_exceeded", "crashed_before_park", "shutdown_before_park"];
3
+ export declare function askRowEndingCauseFor(state: 'VOID' | 'DENIED' | undefined, word: unknown): AskRowEndingCause | undefined;
4
+ export declare function approvalCompleteCauseOf(frame: unknown): ApprovalSettleCause | undefined;
5
+ export declare function notPendingCauseSaysEnded(err: object): boolean;
@@ -0,0 +1,61 @@
1
+ import { ASK_GONE_DENIED_WORD } from '../engineErrorCodes.js';
2
+ import { SDK_ASK_ROW_ENDING_CAUSES } from '../generated/sdkFactTables.js';
3
+ export const ASK_ROW_ENDING_CAUSE_WORDS = SDK_ASK_ROW_ENDING_CAUSES;
4
+ const _askRowCausePin = true;
5
+ void _askRowCausePin;
6
+ const DENIED_GROUP_WORD = ASK_GONE_DENIED_WORD;
7
+ const WAIT_CLASS_SETTLE_CAUSE_WORDS = Object.freeze({
8
+ ttl_expired: true,
9
+ broadcast_lost: true,
10
+ stream_disconnected: true,
11
+ undeliverable: true,
12
+ });
13
+ function isAskRowEndingCause(v) {
14
+ return typeof v === 'string' && ASK_ROW_ENDING_CAUSE_WORDS.includes(v);
15
+ }
16
+ function isApprovalSettleCause(v) {
17
+ return isAskRowEndingCause(v) || (typeof v === 'string' && Object.hasOwn(WAIT_CLASS_SETTLE_CAUSE_WORDS, v));
18
+ }
19
+ export function askRowEndingCauseFor(state, word) {
20
+ if (!isAskRowEndingCause(word))
21
+ return undefined;
22
+ if (state === 'DENIED')
23
+ return word === DENIED_GROUP_WORD ? word : undefined;
24
+ if (state === 'VOID')
25
+ return word !== DENIED_GROUP_WORD ? word : undefined;
26
+ return undefined;
27
+ }
28
+ export function approvalCompleteCauseOf(frame) {
29
+ try {
30
+ if (typeof frame !== 'object' || frame === null)
31
+ return undefined;
32
+ const typeSlot = Object.getOwnPropertyDescriptor(frame, 'type');
33
+ if (typeSlot === undefined || !Object.hasOwn(typeSlot, 'value') || typeSlot.value !== 'tool_approval_complete')
34
+ return undefined;
35
+ const causeSlot = Object.getOwnPropertyDescriptor(frame, 'cause');
36
+ if (causeSlot === undefined || !Object.hasOwn(causeSlot, 'value'))
37
+ return undefined;
38
+ const word = causeSlot.value;
39
+ return isApprovalSettleCause(word) ? word : undefined;
40
+ }
41
+ catch {
42
+ return undefined;
43
+ }
44
+ }
45
+ const LEGACY_NOT_PENDING_ENDED_WORDS = new Set(['voided', 'denied']);
46
+ export function notPendingCauseSaysEnded(err) {
47
+ try {
48
+ const extraSlot = Object.getOwnPropertyDescriptor(err, 'extra');
49
+ const extra = extraSlot !== undefined && Object.hasOwn(extraSlot, 'value') ? extraSlot.value : undefined;
50
+ if (typeof extra !== 'object' || extra === null || Array.isArray(extra))
51
+ return false;
52
+ const causeSlot = Object.getOwnPropertyDescriptor(extra, 'cause');
53
+ if (causeSlot === undefined || !Object.hasOwn(causeSlot, 'value'))
54
+ return false;
55
+ const word = causeSlot.value;
56
+ return isAskRowEndingCause(word) || (typeof word === 'string' && LEGACY_NOT_PENDING_ENDED_WORDS.has(word));
57
+ }
58
+ catch {
59
+ return false;
60
+ }
61
+ }
@@ -1,4 +1,4 @@
1
- import type { FsApprovalOutcome, ToolApprovalFrameOutcome } from './toolApprovalWire.js';
1
+ import type { ApprovalHolderIdentity, FsApprovalOutcome, ToolApprovalFrameOutcome } from './toolApprovalWire.js';
2
2
  export interface DurableDecisionHold {
3
3
  readonly lane: 'durable';
4
4
  readonly outcome: Promise<FsApprovalOutcome>;
@@ -9,6 +9,7 @@ export interface DurableDecisionHold {
9
9
  }
10
10
  export interface LiveCardHold {
11
11
  readonly lane: 'live';
12
+ readonly identity: LiveCardIdentity;
12
13
  readonly outcome: Promise<ToolApprovalFrameOutcome>;
13
14
  phase: 'card' | 'respond';
14
15
  superseded?: {
@@ -19,6 +20,15 @@ export interface LiveCardHold {
19
20
  trailing?: Promise<void>;
20
21
  }
21
22
  export type ApprovalHold = DurableDecisionHold | LiveCardHold;
23
+ export interface LiveCardIdentity {
24
+ readonly approvalId: string;
25
+ readonly toolCallId?: string;
26
+ readonly taskId?: string;
27
+ readonly sessionId?: string;
28
+ }
29
+ export declare function liveCardHoldFor(who: ApprovalHolderIdentity): LiveCardHold | undefined;
30
+ export declare function liveCardAnsweredHow(o: ToolApprovalFrameOutcome): 'answered' | 'interrupted' | undefined;
31
+ export declare function awaitLiveCardHolder(hold: LiveCardHold, signal: AbortSignal | undefined): Promise<ToolApprovalFrameOutcome | undefined>;
22
32
  export interface ApprovalDecisionHoldTable {
23
33
  readonly inFlight: Map<string, ApprovalHold>;
24
34
  readonly settled: Map<string, DurableSettledHold>;
@@ -31,6 +41,6 @@ export interface DurableSettledHold {
31
41
  }
32
42
  export declare const approvalDecisionHolds: ApprovalDecisionHoldTable;
33
43
  export declare function liveCardHoldOf(key: string): LiveCardHold | undefined;
34
- export declare function holdLiveCard(key: string, callerSignal: AbortSignal | undefined, present: (hold: LiveCardHold) => Promise<ToolApprovalFrameOutcome>): Promise<ToolApprovalFrameOutcome>;
44
+ export declare function holdLiveCard(identity: LiveCardIdentity, callerSignal: AbortSignal | undefined, present: (hold: LiveCardHold) => Promise<ToolApprovalFrameOutcome>): Promise<ToolApprovalFrameOutcome>;
35
45
  export declare function joinLiveCard(hold: LiveCardHold, signal: AbortSignal | undefined): Promise<ToolApprovalFrameOutcome>;
36
46
  export declare function supersedeLiveCard(key: string, parked: boolean): boolean;
@@ -1,10 +1,61 @@
1
1
  import { hostLog } from '../host.js';
2
+ import { liveFrameCallKey } from './gateIdentity.js';
3
+ import { approvalResolutionOf } from './approvalResolution.js';
4
+ const nonEmpty = (v) => typeof v === 'string' && v !== '';
5
+ export function liveCardHoldFor(who) {
6
+ if (nonEmpty(who.approvalId))
7
+ return liveCardHoldOf(liveFrameCallKey(who.approvalId));
8
+ if (!nonEmpty(who.taskId) || !nonEmpty(who.toolCallId))
9
+ return undefined;
10
+ for (const h of approvalDecisionHolds.inFlight.values()) {
11
+ if (h.lane !== 'live' || h.phase !== 'card')
12
+ continue;
13
+ const id = h.identity;
14
+ if (id.taskId !== who.taskId || id.toolCallId !== who.toolCallId)
15
+ continue;
16
+ if (nonEmpty(who.sessionId) && nonEmpty(id.sessionId) && id.sessionId !== who.sessionId)
17
+ continue;
18
+ return h;
19
+ }
20
+ return undefined;
21
+ }
22
+ function liveCardSettledQuestion(hold, o) {
23
+ const r = approvalResolutionOf(o);
24
+ if (answeredHowOf(r) !== undefined)
25
+ return true;
26
+ if (r.kind === 'unsettled')
27
+ return false;
28
+ return hold.superseded !== undefined && !hold.superseded.parked;
29
+ }
30
+ export function liveCardAnsweredHow(o) {
31
+ return answeredHowOf(approvalResolutionOf(o));
32
+ }
33
+ function answeredHowOf(r) {
34
+ if (r.kind === 'decided')
35
+ return 'answered';
36
+ return r.kind === 'unsettled' && r.cause === 'interrupted' ? 'interrupted' : undefined;
37
+ }
38
+ export async function awaitLiveCardHolder(hold, signal) {
39
+ const unsubscribe = followCallerSignal(signal, hold.interrupt);
40
+ let o;
41
+ try {
42
+ o = await hold.outcome;
43
+ }
44
+ catch {
45
+ return undefined;
46
+ }
47
+ finally {
48
+ unsubscribe();
49
+ }
50
+ return liveCardSettledQuestion(hold, o) ? { ...o, joinedInFlight: true } : undefined;
51
+ }
2
52
  export const approvalDecisionHolds = { inFlight: new Map(), settled: new Map(), openReads: new Set(), clock: 0 };
3
53
  export function liveCardHoldOf(key) {
4
54
  const h = approvalDecisionHolds.inFlight.get(key);
5
55
  return h !== undefined && h.lane === 'live' ? h : undefined;
6
56
  }
7
- export function holdLiveCard(key, callerSignal, present) {
57
+ export function holdLiveCard(identity, callerSignal, present) {
58
+ const key = liveFrameCallKey(identity.approvalId);
8
59
  const h = approvalDecisionHolds;
9
60
  const cut = new AbortController();
10
61
  let settled = false;
@@ -19,7 +70,7 @@ export function holdLiveCard(key, callerSignal, present) {
19
70
  resolveOutcome = resolve;
20
71
  rejectOutcome = reject;
21
72
  });
22
- const hold = { lane: 'live', outcome, phase: 'card', cardSignal: cut.signal, interrupt };
73
+ const hold = { lane: 'live', identity: Object.freeze({ ...identity }), outcome, phase: 'card', cardSignal: cut.signal, interrupt };
23
74
  h.inFlight.set(key, hold);
24
75
  const release = () => {
25
76
  if (h.inFlight.get(key) === hold)
@@ -1,7 +1,7 @@
1
1
  import { hostLog } from '../host.js';
2
2
  import { unrefTimer } from '../unrefTimer.js';
3
3
  import { readLivePendingRows } from './livePendingAsk.js';
4
- import { isInStreamDrainingEnd } from '../wireErrorTriage.js';
4
+ import { createStreamDrainingLegs } from '../streamDrainingLegs.js';
5
5
  const MAX_CONSECUTIVE_POLL_ESCAPES = 5;
6
6
  function readDurablePendingRows(raw) {
7
7
  if (!Array.isArray(raw))
@@ -365,7 +365,7 @@ export function startApprovalsFeed(client, onSnapshot, opts) {
365
365
  .then(() => scheduleReconcile());
366
366
  }, reconcileDelayMs));
367
367
  }
368
- function scheduleStreamRetry() {
368
+ function scheduleStreamRetry(afterMs = streamRetryMs) {
369
369
  if (stopped || ac.signal.aborted || streamRetryMs <= 0)
370
370
  return;
371
371
  if (!client.approvals.stream)
@@ -378,9 +378,9 @@ export function startApprovalsFeed(client, onSnapshot, opts) {
378
378
  return;
379
379
  hostLog('debug', 'approvalsFeed: retrying push leg (降级是暂时的,服务端升级后不该还要重启客户端)');
380
380
  void runStream();
381
- }, streamRetryMs));
381
+ }, afterMs));
382
382
  }
383
- function degradeToPoll(why, real = true) {
383
+ function degradeToPoll(why, real = true, retryAfterMs) {
384
384
  if (stopped)
385
385
  return;
386
386
  if (real)
@@ -390,8 +390,9 @@ export function startApprovalsFeed(client, onSnapshot, opts) {
390
390
  }
391
391
  mode = 'poll';
392
392
  schedulePoll();
393
- scheduleStreamRetry();
393
+ scheduleStreamRetry(retryAfterMs);
394
394
  }
395
+ const drains = createStreamDrainingLegs();
395
396
  async function runStream() {
396
397
  const stream = client.approvals.stream;
397
398
  if (!stream) {
@@ -402,6 +403,10 @@ export function startApprovalsFeed(client, onSnapshot, opts) {
402
403
  for await (const ev of stream.call(client.approvals, { signal: ac.signal })) {
403
404
  if (stopped || ac.signal.aborted)
404
405
  return;
406
+ if (drains.see(ev)) {
407
+ hostLog('debug', 'approvalsFeed: push leg received the drain end frame — the engine is going down; this leg ends with it and reopens on the draining ladder');
408
+ continue;
409
+ }
405
410
  if (mode !== 'push') {
406
411
  mode = 'push';
407
412
  if (pollTimer !== undefined) {
@@ -416,17 +421,24 @@ export function startApprovalsFeed(client, onSnapshot, opts) {
416
421
  stats.heartbeats++;
417
422
  continue;
418
423
  }
419
- if (isInStreamDrainingEnd(ev)) {
420
- hostLog('debug', 'approvalsFeed: push leg received the drain end frame — the engine is going down; the stream reconnects and re-syncs on its own');
421
- continue;
422
- }
423
424
  stats.pushEvents++;
424
425
  await take('push');
425
426
  }
426
- if (!stopped && !ac.signal.aborted)
427
- degradeToPoll('stream ended without progress');
427
+ if (stopped || ac.signal.aborted)
428
+ return;
429
+ const drained = drains.legEnded('returned');
430
+ if (drained !== undefined) {
431
+ const decision = drained;
432
+ if ('reconnectAfterMs' in decision)
433
+ degradeToPoll(`engine draining — push leg closed; reopening in ${decision.reconnectAfterMs}ms`, false, decision.reconnectAfterMs);
434
+ else
435
+ degradeToPoll(`engine draining — reconnect attempts exhausted (${decision.giveUp})`);
436
+ return;
437
+ }
438
+ degradeToPoll('stream ended without progress');
428
439
  }
429
440
  catch (e) {
441
+ drains.legEnded('threw');
430
442
  if (stopped || ac.signal.aborted)
431
443
  return;
432
444
  degradeToPoll(String(e));
@@ -8,9 +8,10 @@ export type { AskAnsweredOutput } from './gateLedger.js';
8
8
  export { SEMA_COLLATERAL_ABORT_KEY } from './gateLedger.js';
9
9
  export { SEMA_DENIAL_KIND_KEY } from './gateLedger.js';
10
10
  export type { GateDenyAttribution } from './gateLedger.js';
11
- export { toAnsweredOutput, isAlreadyResolvedFailure, isAlreadyResolvedGateReason, isGateStandingErrorCode, isCodeClassifiedGateFailure, GATE_FAILURE_CODES, nextHopBudget, MAX_GATE_HOPS, MAX_TOTAL_PARKS } from './parkResolver.js';
11
+ export { toAnsweredOutput, isAlreadyResolvedFailure, isAlreadyResolvedGateReason, isGateStandingErrorCode, isCodeClassifiedGateFailure, nextHopBudget, MAX_GATE_HOPS, MAX_TOTAL_PARKS } from './parkResolver.js';
12
12
  export type { HopBudget, HopRound } from './parkResolver.js';
13
13
  export type { GateOutcome, GateFailureCode } from './parkResolver.js';
14
+ export { GATE_FAILURE_CODES } from './hitlBridge.js';
14
15
  export declare function bridgeAskUserQuestionGates(source: AsyncGenerator<AgentEvent>, deps: AskGateWireDeps, opts?: {
15
16
  signal?: AbortSignal;
16
17
  taskId?: string;
@@ -1,12 +1,16 @@
1
1
  import { createGateLedger } from './gateLedger.js';
2
2
  import { flushHeldWithInterruptRewrite, isHostProgressFrame, routeFrame, } from './frameRouter.js';
3
3
  import { resolvePark, nextHopBudget, stalledTerminal, MAX_GATE_HOPS } from './parkResolver.js';
4
+ import { createStreamDrainingLegs } from '../streamDrainingLegs.js';
5
+ import { abortableSleep } from '../abortableSleep.js';
6
+ import { hostLog } from '../host.js';
4
7
  export { HITL_REJECT_MESSAGE, ENGINE_ABORT_TOOL_RESULT } from './frameRouter.js';
5
8
  export { HITL_INTERRUPT_MESSAGE_FOR_TOOL_USE } from './frameRouter.js';
6
9
  export { HITL_POLICY_DENY_MESSAGE } from './frameRouter.js';
7
10
  export { SEMA_COLLATERAL_ABORT_KEY } from './gateLedger.js';
8
11
  export { SEMA_DENIAL_KIND_KEY } from './gateLedger.js';
9
- export { toAnsweredOutput, isAlreadyResolvedFailure, isAlreadyResolvedGateReason, isGateStandingErrorCode, isCodeClassifiedGateFailure, GATE_FAILURE_CODES, nextHopBudget, MAX_GATE_HOPS, MAX_TOTAL_PARKS } from './parkResolver.js';
12
+ export { toAnsweredOutput, isAlreadyResolvedFailure, isAlreadyResolvedGateReason, isGateStandingErrorCode, isCodeClassifiedGateFailure, nextHopBudget, MAX_GATE_HOPS, MAX_TOTAL_PARKS } from './parkResolver.js';
13
+ export { GATE_FAILURE_CODES } from './hitlBridge.js';
10
14
  export async function* bridgeAskUserQuestionGates(source, deps, opts) {
11
15
  const led = createGateLedger();
12
16
  const taskId = { current: opts?.taskId };
@@ -24,9 +28,12 @@ export async function* bridgeAskUserQuestionGates(source, deps, opts) {
24
28
  let lastResolvedGatedCallId;
25
29
  let lastRoundDecided = false;
26
30
  let unverifiableParks = 0;
31
+ const drains = createStreamDrainingLegs();
27
32
  while (true) {
28
33
  let park = null;
29
34
  for await (const ev of stream) {
35
+ if (drains.see(ev))
36
+ continue;
30
37
  const answering = led.answeringLiveCards();
31
38
  if (answering.length > 0)
32
39
  await Promise.all(answering);
@@ -57,6 +64,24 @@ export async function* bridgeAskUserQuestionGates(source, deps, opts) {
57
64
  break;
58
65
  }
59
66
  if (!park) {
67
+ const drained = drains.legEnded('returned');
68
+ if (drained !== undefined) {
69
+ const reopenAfterMs = 'reconnectAfterMs' in drained ? drained.reconnectAfterMs : undefined;
70
+ if (reopenAfterMs !== undefined && taskId.current !== undefined && !opts?.signal?.aborted) {
71
+ hostLog('debug', `liveHitlAskWire: runs.events(${taskId.current}) ended on the drain end frame — the engine is going down; reopening in ${reopenAfterMs}ms`);
72
+ await abortableSleep(reopenAfterMs, opts?.signal ?? new AbortController().signal);
73
+ if (!opts?.signal?.aborted) {
74
+ const seq = led.lastSeq();
75
+ led.beginStreamSegment();
76
+ stream = deps.runsEvents(taskId.current, {
77
+ ...(seq !== undefined ? { lastEventId: seq } : {}),
78
+ ...(opts?.signal ? { signal: opts.signal } : {}),
79
+ });
80
+ continue;
81
+ }
82
+ }
83
+ hostLog('debug', `liveHitlAskWire: runs.events ended on the drain end frame and is not reopened (${'giveUp' in drained ? drained.giveUp : taskId.current === undefined ? 'no run handle' : 'aborted'}) — ending this leg like a dropped stream`);
84
+ }
60
85
  yield* flushHeldWithInterruptRewrite(led, { signal: opts?.signal });
61
86
  return;
62
87
  }
@@ -1,7 +1,7 @@
1
1
  import { CORE_MODE_ARBITRATION, CORE_MODE_RELEASE_REASONS } from '../generated/engineFactTables.js';
2
2
  import { CORE_RELEASABLE_ASK_CLASSES_BY_FACE, CORE_SHELL_FACE_TOOL_NAMES } from '../generated/engineAskFaceTables.js';
3
3
  import { mandatedApprovalDetail } from '../gateVocabulary.js';
4
- import { readOwnNonEmptyString } from '../ownKey.js';
4
+ import { readAskOrigin, readOwnNonEmptyString } from '../ownKey.js';
5
5
  import { readAskClassPairState } from './askClassPair.js';
6
6
  import { approvalAllowRuleShadowed } from './allowRuleShadow.js';
7
7
  import { approvalIsMandated, readApprovalMandate } from './toolApprovalWire.js';
@@ -102,7 +102,7 @@ export function automaticAnswerVerdict(card, lane, facts) {
102
102
  if (typeof card !== 'object' || card === null)
103
103
  return UNKNOWN.card_unreadable;
104
104
  const face = lane === 'mode_posture' ? askFaceOfCard(card) : undefined;
105
- const origin = readOwnNonEmptyString(evidenceView(card, 'origin'), 'origin');
105
+ const origin = readAskOrigin(evidenceView(card, 'origin'));
106
106
  if (origin !== undefined && Object.hasOwn(ORIGIN_IMPLIES_CLASS, origin)) {
107
107
  const cls = ORIGIN_IMPLIES_CLASS[origin];
108
108
  if (classBlocks(lane, cls, face))
@@ -3,6 +3,8 @@ const CRASH_CONVERGED_CAUSE_WORDS = Object.freeze({
3
3
  shutdown_before_park: true,
4
4
  });
5
5
  export const isCrashConvergedCause = (v) => typeof v === 'string' && Object.hasOwn(CRASH_CONVERGED_CAUSE_WORDS, v);
6
+ const _crashConvergedCausePin = true;
7
+ void _crashConvergedCausePin;
6
8
  const isString = (v) => typeof v === 'string';
7
9
  const isNumber = (v) => typeof v === 'number';
8
10
  const nonEmptyString = (v) => typeof v === 'string' && v.length > 0;
@@ -0,0 +1,3 @@
1
+ export type DecisionRefusalConflict = 'binding_mismatch' | 'row_integrity' | 'format_version' | 'answer_invalid';
2
+ export declare function decisionConflictOf(err: unknown): DecisionRefusalConflict | undefined;
3
+ export declare const DECISION_ANSWER_INVALID_CONTENT = "The engine rejected the answer itself because it does not fit the question that was asked \u00B7 Nothing was decided and the question is still waiting \u00B7 Correct the answer and send it again";
@@ -0,0 +1,43 @@
1
+ import { APPROVAL_BINDING_MISMATCH, CHECKPOINT_UNSUPPORTED_VERSION, RESUME_OUTCOME_INVALID } from '../engineErrorCodes.js';
2
+ import { readRefusalField } from '../wireFailureShape.js';
3
+ const BINDING_SEAT_FIELDS = new Set(['boundCallId', 'boundInputHash']);
4
+ const ANSWER_FIELD = 'answer';
5
+ const BINDING_MISMATCH_ERROR_NAME = 'ApprovalBindingMismatchError';
6
+ export function decisionConflictOf(err) {
7
+ if (typeof err !== 'object' || err === null)
8
+ return undefined;
9
+ let code;
10
+ let name;
11
+ try {
12
+ code = err.errorCode;
13
+ }
14
+ catch {
15
+ code = undefined;
16
+ }
17
+ try {
18
+ name = err.name;
19
+ }
20
+ catch {
21
+ name = undefined;
22
+ }
23
+ if (code === CHECKPOINT_UNSUPPORTED_VERSION)
24
+ return 'format_version';
25
+ const bindingFamily = code === APPROVAL_BINDING_MISMATCH || (code === undefined && name === BINDING_MISMATCH_ERROR_NAME);
26
+ if (!bindingFamily && code !== RESUME_OUTCOME_INVALID)
27
+ return undefined;
28
+ let field;
29
+ try {
30
+ field = readRefusalField(err);
31
+ }
32
+ catch {
33
+ field = undefined;
34
+ }
35
+ if (code === RESUME_OUTCOME_INVALID)
36
+ return field === ANSWER_FIELD ? 'answer_invalid' : undefined;
37
+ if (field === ANSWER_FIELD)
38
+ return 'answer_invalid';
39
+ if (field === undefined)
40
+ return 'row_integrity';
41
+ return BINDING_SEAT_FIELDS.has(field) ? 'binding_mismatch' : undefined;
42
+ }
43
+ export const DECISION_ANSWER_INVALID_CONTENT = 'The engine rejected the answer itself because it does not fit the question that was asked · Nothing was decided and the question is still waiting · Correct the answer and send it again';
@@ -1,9 +1,13 @@
1
+ import { type DecisionRefusalConflict } from './decisionConflict.js';
1
2
  export type DecisionRefusalStanding = 'settled-elsewhere' | 'ambiguous' | 'refused-pending';
3
+ export type { DecisionRefusalConflict } from './decisionConflict.js';
4
+ export declare function decisionRefusalSaysAskGone(err: unknown): boolean;
2
5
  export interface DecisionRefusalClassified {
3
6
  readonly standing: DecisionRefusalStanding;
4
7
  readonly status: number;
5
8
  readonly errorCode?: string;
6
9
  readonly transient?: true;
10
+ readonly conflict?: DecisionRefusalConflict;
7
11
  }
8
12
  export declare function classifyDecisionRefusal(err: unknown): DecisionRefusalClassified | undefined;
9
13
  export type LiveAskPendingReading = 'pending' | 'not_pending' | 'unknown';
@@ -1,9 +1,27 @@
1
1
  import { refusalSaysStillPending } from '../engineErrorCodes.js';
2
+ import { notPendingCauseSaysEnded } from './approvalEndingCause.js';
3
+ import { decisionConflictOf } from './decisionConflict.js';
2
4
  import { readLivePendingRows } from './livePendingAsk.js';
3
5
  import { readToolApprovalRespondRefusal } from './toolApprovalWire.js';
4
6
  const ASK_DECIDED_CODE = 'conflict.ask_decided';
5
7
  const TRANSIENT_STATUSES = new Set([408, 425, 429, 502, 503, 504]);
6
8
  const DISPLAYABLE_CODE = /^[A-Za-z0-9._:-]{1,64}$/;
9
+ export function decisionRefusalSaysAskGone(err) {
10
+ try {
11
+ if (typeof err === 'object' && err !== null && 'code' in err) {
12
+ const code = err.code;
13
+ if (code === 'no_pending')
14
+ return true;
15
+ }
16
+ const c = classifyDecisionRefusal(err);
17
+ if (c?.standing !== 'settled-elsewhere')
18
+ return false;
19
+ return c.status !== 404 || (typeof err === 'object' && err !== null && notPendingCauseSaysEnded(err));
20
+ }
21
+ catch {
22
+ return false;
23
+ }
24
+ }
7
25
  export function classifyDecisionRefusal(err) {
8
26
  const r = readToolApprovalRespondRefusal(err);
9
27
  const status = r?.status;
@@ -17,7 +35,14 @@ export function classifyDecisionRefusal(err) {
17
35
  ? 'ambiguous'
18
36
  : 'refused-pending';
19
37
  const transient = TRANSIENT_STATUSES.has(status) || (typeof err === 'object' && err !== null && refusalSaysStillPending(err));
20
- return Object.freeze({ standing, status, ...(errorCode !== undefined ? { errorCode } : {}), ...(transient ? { transient: true } : {}) });
38
+ const conflict = status === 409 ? decisionConflictOf(err) : undefined;
39
+ return Object.freeze({
40
+ standing,
41
+ status,
42
+ ...(errorCode !== undefined ? { errorCode } : {}),
43
+ ...(transient ? { transient: true } : {}),
44
+ ...(conflict !== undefined ? { conflict } : {}),
45
+ });
21
46
  }
22
47
  export function liveAskPendingReading(envelope, approvalId) {
23
48
  if (typeof approvalId !== 'string' || approvalId === '')
@@ -1,13 +1,15 @@
1
1
  import { hostLog } from '../host.js';
2
+ import { invokeHostCallbackIsolated } from '../hostCallbackGuard.js';
2
3
  import { isToolApprovalGate, toolNameIsFsWrite, toolNameIsShellExec, isToolApprovalFrame, isFromSubagent, surfaceToolApprovalFrameAndRespond, } from './toolApprovalWire.js';
3
4
  import { classifierDenyFromToolEnd } from '../classifierVerdictWire.js';
4
5
  import { GATE_PARKED_ERROR_CODE } from '../engineErrorCodes.js';
5
6
  import { readRunTerminal, runTerminalCode, runTerminalGateKind, runTerminalGateToolName } from '../runTerminal.js';
6
7
  import { surfaceForCurrentSession, surfaceReplayedApprovalListUnreadable } from './hitlHostSurface.js';
7
- import { liveCardHoldOf, supersedeLiveCard } from './approvalHolds.js';
8
+ import { liveCardAnsweredHow, liveCardHoldOf, supersedeLiveCard } from './approvalHolds.js';
9
+ import { approvalCompleteParked } from './approvalCompleteParked.js';
8
10
  import { liveFrameCallKey } from './gateIdentity.js';
9
11
  import { SEMA_DENIAL_KIND_KEY, hasLedgerPositionKey } from './gateLedger.js';
10
- import { replayedApprovalDisposition } from './replayedApproval.js';
12
+ import { replayedApprovalDisposition, settledApprovalDisposition } from './replayedApproval.js';
11
13
  import { settleWithinDeadline } from '../abortableSleep.js';
12
14
  import { ccToolDenialKindForSettledBy } from '../gateVocabulary.js';
13
15
  import { REJECT_MESSAGE } from '../adapt/wireShapes.js';
@@ -266,48 +268,34 @@ const TOOL_END_ARMS = [
266
268
  '改顺序必须同批改 TOOL_END_ARM_ORDER 并说明为什么新顺序仍然对。');
267
269
  }
268
270
  }
269
- function completeFrameParked(ev) {
270
- try {
271
- const d = Object.getOwnPropertyDescriptor(ev, 'parked');
272
- return d !== undefined && 'value' in d && d.value === true;
273
- }
274
- catch {
275
- return false;
276
- }
277
- }
278
271
  async function routeToolApprovalFrame(ev, ctx) {
279
272
  const { led, deps } = ctx;
280
273
  if (ev.type === 'tool_approval_complete') {
281
274
  const id = ev.approvalId;
282
- if (typeof id === 'string' && id !== '' && supersedeLiveCard(liveFrameCallKey(id), completeFrameParked(ev))) {
275
+ if (typeof id === 'string' && id !== '' && supersedeLiveCard(liveFrameCallKey(id), approvalCompleteParked(ev))) {
283
276
  hostLog('debug', `liveHitlAskWire: tool_approval_complete for ${id} arrived while its card was waiting — card withdrawn (nothing will be sent from it)`);
284
277
  }
278
+ if (typeof id === 'string' && id !== '')
279
+ led.noteApprovalSettled(id, 'closed_by_engine');
285
280
  return { kind: 'skip' };
286
281
  }
287
282
  if (!deps.respondToolApproval) {
288
283
  hostLog('debug', `liveHitlAskWire: tool_approval frame ${ev.approvalId} but no respond wire — engine self-settles (TTL deny)`);
289
284
  return { kind: 'skip' };
290
285
  }
291
- if (hasLedgerPositionKey(ev) && !led.preambleApprovals().has(ev.approvalId)) {
292
- const disposition = await judgeLedgerApprovalFrame(ev, ctx);
286
+ const settledHere = settledApprovalDisposition(ev, led.settledApprovals());
287
+ if (settledHere !== undefined || (hasLedgerPositionKey(ev) && !led.preambleApprovals().has(ev.approvalId))) {
288
+ const disposition = settledHere ?? (await judgeLedgerApprovalFrame(ev, ctx));
293
289
  if (disposition.kind !== 'live') {
294
- hostLog('debug', `liveHitlAskWire: tool_approval frame ${ev.approvalId} read back from the run event log is ${disposition.kind}` +
295
- `${disposition.kind === 'unknown' ? ` (${disposition.why})` : disposition.cause !== undefined ? ` (${disposition.cause})` : ''}` +
296
- ' — not raised as a question, no decision sent');
290
+ hostLog('debug', disposition.kind === 'decided' && disposition.settled !== undefined
291
+ ? `liveHitlAskWire: tool_approval frame ${ev.approvalId} repeats a question already settled on this bridge (${disposition.settled}) — not raised again, no decision sent`
292
+ : `liveHitlAskWire: tool_approval frame ${ev.approvalId} read back from the run event log is ${disposition.kind}` +
293
+ `${disposition.kind === 'unknown' ? ` (${disposition.why})` : disposition.cause !== undefined ? ` (${disposition.cause})` : ''}` +
294
+ ' — not raised as a question, no decision sent');
297
295
  if (disposition.kind === 'unknown' && ctx.signal?.aborted !== true)
298
296
  surfaceReplayedApprovalListUnreadable();
299
297
  if (deps.onReplayedApproval !== undefined) {
300
- try {
301
- const out = deps.onReplayedApproval(ev, disposition);
302
- if (out && typeof out.then === 'function') {
303
- void Promise.resolve(out).catch((e) => {
304
- hostLog('debug', `liveHitlAskWire: onReplayedApproval rejected: ${String(e).slice(0, 160)}`);
305
- });
306
- }
307
- }
308
- catch (e) {
309
- hostLog('debug', `liveHitlAskWire: onReplayedApproval threw: ${String(e).slice(0, 160)}`);
310
- }
298
+ invokeHostCallbackIsolated('liveHitlAskWire: onReplayedApproval', () => deps.onReplayedApproval?.(ev, disposition));
311
299
  }
312
300
  return { kind: 'skip' };
313
301
  }
@@ -317,29 +305,24 @@ async function routeToolApprovalFrame(ev, ctx) {
317
305
  const frameCallId = !fromSubagent && typeof ev.toolCallId === 'string' && ev.toolCallId !== '' ? ev.toolCallId : undefined;
318
306
  const gatedCallId = fromSubagent ? undefined : frameCallId ?? led.lastPendingFsCall();
319
307
  const argsCallId = frameCallId === undefined ? gatedCallId : typeof ev.toolName === 'string' && toolNameIsFsWrite(ev.toolName) ? frameCallId : undefined;
320
- const launched = surfaceToolApprovalFrameAndRespond(ev, deps.respondToolApproval, argsOfGatedStart(led, argsCallId), ctx.signal, deps.approvalLane);
308
+ const runTaskId = ctx.taskId.current;
309
+ const approvalIdAtLaunch = ev.approvalId;
310
+ const launched = surfaceToolApprovalFrameAndRespond(ev, deps.respondToolApproval, argsOfGatedStart(led, argsCallId), ctx.signal, deps.approvalLane, typeof runTaskId === 'string' && runTaskId !== '' ? { taskId: runTaskId } : undefined);
321
311
  const hold = liveCardHoldOf(liveFrameCallKey(ev.approvalId));
322
- const handled = launched.then((outcome) => settleLiveCardOutcome(ev, outcome, ctx, fromSubagent, gatedCallId), (e) => {
312
+ const handled = launched.then((outcome) => settleLiveCardOutcome(ev, approvalIdAtLaunch, outcome, ctx, fromSubagent, gatedCallId), (e) => {
323
313
  hostLog('debug', `liveHitlAskWire: tool_approval card for ${ev.approvalId} failed: ${String(e).slice(0, 160)}`);
324
314
  });
325
315
  led.trackLiveCard(() => hold?.phase ?? 'card', handled);
326
316
  return { kind: 'skip' };
327
317
  }
328
- function settleLiveCardOutcome(ev, outcome, ctx, fromSubagent, gatedCallId) {
318
+ function settleLiveCardOutcome(ev, approvalId, outcome, ctx, fromSubagent, gatedCallId) {
329
319
  const { led, deps } = ctx;
330
320
  const { decision } = outcome;
321
+ const settledHow = liveCardAnsweredHow(outcome);
322
+ if (settledHow !== undefined)
323
+ led.noteApprovalSettled(approvalId, settledHow);
331
324
  if (deps.onToolApprovalOutcome !== undefined) {
332
- try {
333
- const out = deps.onToolApprovalOutcome(ev, outcome);
334
- if (out && typeof out.then === 'function') {
335
- void Promise.resolve(out).catch((e) => {
336
- hostLog('debug', `liveHitlAskWire: onToolApprovalOutcome rejected: ${String(e).slice(0, 160)}`);
337
- });
338
- }
339
- }
340
- catch (e) {
341
- hostLog('debug', `liveHitlAskWire: onToolApprovalOutcome threw: ${String(e).slice(0, 160)}`);
342
- }
325
+ invokeHostCallbackIsolated('liveHitlAskWire: onToolApprovalOutcome', () => deps.onToolApprovalOutcome?.(ev, outcome));
343
326
  }
344
327
  if (decision === 'deny' && !fromSubagent) {
345
328
  const attribution = {
@@ -374,7 +357,7 @@ async function judgeLedgerApprovalFrame(ev, ctx) {
374
357
  .finally(release);
375
358
  });
376
359
  const approvals = got.kind === 'value' ? { read: 'ok', body: got.value } : { read: 'failed' };
377
- return replayedApprovalDisposition(ev, { preamble: ctx.led.preambleApprovals(), approvals });
360
+ return replayedApprovalDisposition(ev, { preamble: ctx.led.preambleApprovals(), approvals, settled: ctx.led.settledApprovals() });
378
361
  }
379
362
  function routeToolStart(ev, callId, led) {
380
363
  led.noteStart(callId, ev);
@@ -16,6 +16,9 @@ export interface AskAnsweredOutput {
16
16
  export declare function durableSeqOf(ev: object): string | undefined;
17
17
  export declare function hasLedgerPositionKey(ev: object): boolean;
18
18
  export declare const PREAMBLE_APPROVALS_MAX_ENTRIES = 4096;
19
+ export declare const APPROVAL_SETTLED_HOWS: readonly ["answered", "interrupted", "closed_by_engine"];
20
+ export type ApprovalSettledHow = (typeof APPROVAL_SETTLED_HOWS)[number];
21
+ export declare const SETTLED_APPROVALS_MAX_ENTRIES = 4096;
19
22
  export interface GateLedger {
20
23
  noteSeq(ev: AgentEvent): void;
21
24
  lastSeq(): string | undefined;
@@ -23,6 +26,8 @@ export interface GateLedger {
23
26
  notePreambleApproval(approvalId: string): void;
24
27
  preambleApprovals(): ReadonlySet<string>;
25
28
  beginStreamSegment(): void;
29
+ noteApprovalSettled(approvalId: string, how: ApprovalSettledHow): void;
30
+ settledApprovals(): ReadonlyMap<string, ApprovalSettledHow>;
26
31
  trackLiveCard(phaseOf: () => 'card' | 'respond', handled: Promise<void>): void;
27
32
  answeringLiveCards(): Promise<void>[];
28
33
  liveCardsSettled(): Promise<void>;