@sema-agent/client-core 0.94.0 → 0.96.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 (332) hide show
  1. package/CHANGELOG.md +521 -0
  2. package/README.md +104 -57
  3. package/dist/adapt/arms.js +12 -7
  4. package/dist/adapt/panelTasks.js +4 -10
  5. package/dist/adapt/toolCards.d.ts +1 -0
  6. package/dist/adapt/toolCards.js +18 -5
  7. package/dist/adapt/wireShapes.js +5 -24
  8. package/dist/adapter/activeRunSelfHeal.d.ts +12 -3
  9. package/dist/adapter/activeRunSelfHeal.js +50 -16
  10. package/dist/adapter/downstream/eventToSdkMessage.js +33 -18
  11. package/dist/adapter/downstream/terminalToSdkResult.js +19 -12
  12. package/dist/adapter/runStream.js +30 -46
  13. package/dist/adapter/types.d.ts +7 -0
  14. package/dist/adapter/types.js +15 -0
  15. package/dist/agentSession/backgroundView.js +5 -1
  16. package/dist/agentSession/engineAgentRegistryRead.js +7 -5
  17. package/dist/agentSession/engineSessionBackgroundStop.js +25 -17
  18. package/dist/agentsWireCaps.d.ts +2 -0
  19. package/dist/agentsWireCaps.js +13 -2
  20. package/dist/approvalDecidedByCapability.d.ts +0 -1
  21. package/dist/approvalDecidedByCapability.js +2 -7
  22. package/dist/approvalsStreamLiveCapability.d.ts +0 -1
  23. package/dist/approvalsStreamLiveCapability.js +2 -6
  24. package/dist/atMostOnce.d.ts +8 -0
  25. package/dist/atMostOnce.js +36 -0
  26. package/dist/attachmentSeatCapability.d.ts +0 -1
  27. package/dist/attachmentSeatCapability.js +0 -3
  28. package/dist/attachmentsWireCaps.js +11 -6
  29. package/dist/bashExitReading.d.ts +10 -0
  30. package/dist/bashExitReading.js +12 -0
  31. package/dist/callerCwdCapability.d.ts +0 -5
  32. package/dist/callerCwdCapability.js +2 -13
  33. package/dist/centerWiringCapability.d.ts +1 -7
  34. package/dist/centerWiringCapability.js +18 -27
  35. package/dist/classifierStatus.js +4 -4
  36. package/dist/classifierVerdictWire.js +4 -16
  37. package/dist/clientSlice.js +1 -1
  38. package/dist/cloudConfigWireCaps.d.ts +3 -3
  39. package/dist/cloudConfigWireCaps.js +85 -43
  40. package/dist/compensations.js +1 -1
  41. package/dist/coreValuePorts.d.ts +11 -7
  42. package/dist/coreValuePorts.js +5 -8
  43. package/dist/credentialProvenance.d.ts +28 -0
  44. package/dist/credentialProvenance.js +76 -0
  45. package/dist/cycleOrder.d.ts +15 -0
  46. package/dist/cycleOrder.js +45 -0
  47. package/dist/decideFailureNote.js +7 -2
  48. package/dist/decideReceipt.d.ts +2 -1
  49. package/dist/decideReceipt.js +39 -14
  50. package/dist/declared/supportFloorDeclared.d.ts +1 -0
  51. package/dist/declared/supportFloorDeclared.js +5 -0
  52. package/dist/deviceExecutorManagementCapability.d.ts +0 -1
  53. package/dist/deviceExecutorManagementCapability.js +7 -16
  54. package/dist/deviceRebind.d.ts +88 -0
  55. package/dist/deviceRebind.js +242 -0
  56. package/dist/displayUntrusted.d.ts +8 -3
  57. package/dist/displayUntrusted.js +661 -63
  58. package/dist/engineAgentAbsence.d.ts +2 -2
  59. package/dist/engineAgentPanelStore.d.ts +8 -18
  60. package/dist/engineAgentPanelStore.js +19 -537
  61. package/dist/engineCapReader.d.ts +6 -6
  62. package/dist/engineCapReader.js +30 -43
  63. package/dist/engineCapsCache.d.ts +13 -2
  64. package/dist/engineCapsCache.js +276 -44
  65. package/dist/engineCredentialRequirement.d.ts +9 -0
  66. package/dist/engineCredentialRequirement.js +23 -0
  67. package/dist/engineDirectory.d.ts +79 -0
  68. package/dist/engineDirectory.js +186 -0
  69. package/dist/engineEnvSourcing.d.ts +53 -0
  70. package/dist/engineEnvSourcing.js +81 -0
  71. package/dist/engineErrorCodes.d.ts +28 -4
  72. package/dist/engineErrorCodes.js +68 -25
  73. package/dist/engineIdentity.js +2 -0
  74. package/dist/engineNoticeCodes.d.ts +17 -0
  75. package/dist/engineNoticeCodes.js +57 -3
  76. package/dist/engineWireSdk.js +2 -2
  77. package/dist/engineWordRenames.d.ts +8 -0
  78. package/dist/engineWordRenames.js +13 -0
  79. package/dist/executionLaneCapability.d.ts +2 -3
  80. package/dist/executionLaneCapability.js +18 -23
  81. package/dist/fileHistoryCaptureCapability.d.ts +14 -1
  82. package/dist/fileHistoryCaptureCapability.js +72 -6
  83. package/dist/fileHistoryLegNotice.d.ts +3 -2
  84. package/dist/fileHistoryLegNotice.js +51 -2
  85. package/dist/fleet/bgSubagentRow.d.ts +15 -0
  86. package/dist/fleet/bgSubagentRow.js +67 -0
  87. package/dist/fleet/fleetLedger.d.ts +3 -0
  88. package/dist/fleet/fleetLedger.js +153 -122
  89. package/dist/fleet/fleetProjection.d.ts +1 -3
  90. package/dist/fleet/fleetProjection.js +19 -19
  91. package/dist/fleetAgentPanelProjection.d.ts +4 -17
  92. package/dist/fleetAgentPanelProjection.js +7 -197
  93. package/dist/fleetAgentRowLike.d.ts +16 -0
  94. package/dist/fleetAgentRowLike.js +1 -0
  95. package/dist/fleetTaskDesc.js +4 -17
  96. package/dist/gateOutcome.d.ts +12 -1
  97. package/dist/gateOutcome.js +87 -18
  98. package/dist/gateVocabulary.d.ts +7 -2
  99. package/dist/gateVocabulary.js +5 -1
  100. package/dist/generated/engineAskFaceTables.js +1 -1
  101. package/dist/generated/engineFactTables.d.ts +5 -3
  102. package/dist/generated/engineFactTables.js +17 -4
  103. package/dist/generated/engineNoticeTables.js +12 -3
  104. package/dist/generated/sdkFactTables.d.ts +2 -0
  105. package/dist/generated/sdkFactTables.js +17 -0
  106. package/dist/generated/settingsSchemaFns.d.ts +13 -0
  107. package/dist/generated/settingsSchemaFns.js +75 -0
  108. package/dist/generated/toolNameTables.d.ts +1 -0
  109. package/dist/generated/toolNameTables.js +1 -0
  110. package/dist/headlessPermissionModeWire.d.ts +1 -1
  111. package/dist/headlessPermissionModeWire.js +1 -1
  112. package/dist/headlessReconnectWire.js +49 -20
  113. package/dist/hitl/allowRuleShadow.d.ts +1 -1
  114. package/dist/hitl/allowRuleShadow.js +49 -8
  115. package/dist/hitl/approvalCompleteParked.d.ts +1 -0
  116. package/dist/hitl/approvalCompleteParked.js +14 -0
  117. package/dist/hitl/approvalEndingCause.d.ts +7 -0
  118. package/dist/hitl/approvalEndingCause.js +71 -0
  119. package/dist/hitl/approvalHolds.d.ts +12 -2
  120. package/dist/hitl/approvalHolds.js +53 -2
  121. package/dist/hitl/approvalsFeed.js +23 -11
  122. package/dist/hitl/askGateWire.d.ts +2 -1
  123. package/dist/hitl/askGateWire.js +26 -1
  124. package/dist/hitl/askParkRowRouting.js +1 -1
  125. package/dist/hitl/automaticAnswerVerdict.d.ts +1 -1
  126. package/dist/hitl/automaticAnswerVerdict.js +15 -10
  127. package/dist/hitl/crashConverged.js +2 -0
  128. package/dist/hitl/decisionConflict.d.ts +3 -0
  129. package/dist/hitl/decisionConflict.js +41 -0
  130. package/dist/hitl/decisionRefusal.d.ts +12 -0
  131. package/dist/hitl/decisionRefusal.js +63 -4
  132. package/dist/hitl/frameRouter.d.ts +0 -1
  133. package/dist/hitl/frameRouter.js +32 -57
  134. package/dist/hitl/gateLedger.d.ts +5 -0
  135. package/dist/hitl/gateLedger.js +21 -0
  136. package/dist/hitl/hitlBridge.d.ts +17 -2
  137. package/dist/hitl/hitlBridge.js +91 -55
  138. package/dist/hitl/hitlHostSurface.js +3 -11
  139. package/dist/hitl/livePendingAsk.d.ts +16 -6
  140. package/dist/hitl/livePendingAsk.js +36 -4
  141. package/dist/hitl/localAllowRule.d.ts +1 -0
  142. package/dist/hitl/localAllowRule.js +2 -2
  143. package/dist/hitl/noAllowCause.d.ts +2 -1
  144. package/dist/hitl/noAllowCause.js +3 -2
  145. package/dist/hitl/parkExpiryJudgements.d.ts +19 -0
  146. package/dist/hitl/parkExpiryJudgements.js +46 -0
  147. package/dist/hitl/parkResolver.d.ts +4 -7
  148. package/dist/hitl/parkResolver.js +12 -34
  149. package/dist/hitl/parkRowBirthWait.d.ts +1 -0
  150. package/dist/hitl/parkRowBirthWait.js +2 -2
  151. package/dist/hitl/persistedRulesWire.js +5 -1
  152. package/dist/hitl/planReviewWire.d.ts +3 -2
  153. package/dist/hitl/planReviewWire.js +154 -50
  154. package/dist/hitl/replayedApproval.d.ts +9 -0
  155. package/dist/hitl/replayedApproval.js +139 -10
  156. package/dist/hitl/resumeRunningCard.d.ts +2 -0
  157. package/dist/hitl/resumeRunningCard.js +15 -4
  158. package/dist/hitl/sessionPolicyDeliverable.js +8 -6
  159. package/dist/hitl/standingRows.d.ts +18 -0
  160. package/dist/hitl/standingRows.js +127 -0
  161. package/dist/hitl/toolApprovalWire.d.ts +39 -7
  162. package/dist/hitl/toolApprovalWire.js +351 -61
  163. package/dist/hookMatcherVerdict.d.ts +1 -9
  164. package/dist/hookMatcherVerdict.js +21 -61
  165. package/dist/hooksWireCaps.js +40 -8
  166. package/dist/host.d.ts +5 -0
  167. package/dist/host.js +3 -1
  168. package/dist/hostCallbackGuard.d.ts +2 -0
  169. package/dist/hostCallbackGuard.js +31 -0
  170. package/dist/index.d.ts +48 -17
  171. package/dist/index.js +36 -15
  172. package/dist/interactiveInterrupt.d.ts +8 -0
  173. package/dist/interactiveInterrupt.js +54 -0
  174. package/dist/limitsWire.d.ts +5 -0
  175. package/dist/limitsWire.js +49 -3
  176. package/dist/liveModelCatalog.d.ts +95 -0
  177. package/dist/liveModelCatalog.js +297 -32
  178. package/dist/mcpEngineLeg.d.ts +25 -0
  179. package/dist/mcpEngineLeg.js +71 -15
  180. package/dist/mcpPanel.js +2 -1
  181. package/dist/mcpProbeCapability.d.ts +0 -5
  182. package/dist/mcpProbeCapability.js +2 -12
  183. package/dist/mcpReconnect.d.ts +1 -3
  184. package/dist/mcpReconnect.js +10 -5
  185. package/dist/memoryComplianceCapability.d.ts +0 -5
  186. package/dist/memoryComplianceCapability.js +2 -12
  187. package/dist/memoryOriginCapability.d.ts +0 -5
  188. package/dist/memoryOriginCapability.js +2 -12
  189. package/dist/memorySpecWire.js +2 -2
  190. package/dist/memoryVerbsWire.d.ts +1 -1
  191. package/dist/memoryVerbsWire.js +4 -0
  192. package/dist/model/liveCatalogShape.d.ts +14 -0
  193. package/dist/model/liveCatalogShape.js +71 -0
  194. package/dist/model/modelSupplyRules.d.ts +1 -0
  195. package/dist/model/modelSupplyRules.js +30 -9
  196. package/dist/model/modelsPolicy.d.ts +21 -0
  197. package/dist/model/modelsPolicy.js +90 -0
  198. package/dist/modelIdentityResolvability.d.ts +2 -2
  199. package/dist/modelIdentityResolvability.js +14 -12
  200. package/dist/modelSwitchFallback.d.ts +69 -0
  201. package/dist/modelSwitchFallback.js +206 -0
  202. package/dist/noticePresentation.d.ts +68 -0
  203. package/dist/noticePresentation.js +247 -0
  204. package/dist/notifications.js +12 -11
  205. package/dist/outputStyleSeatCapability.d.ts +0 -2
  206. package/dist/outputStyleSeatCapability.js +2 -10
  207. package/dist/ownKey.d.ts +8 -0
  208. package/dist/ownKey.js +66 -0
  209. package/dist/panelReduce.d.ts +114 -0
  210. package/dist/panelReduce.js +1135 -0
  211. package/dist/panelStateHolder.d.ts +17 -0
  212. package/dist/panelStateHolder.js +201 -0
  213. package/dist/peerFrames.js +13 -9
  214. package/dist/peerLaneCapability.d.ts +0 -1
  215. package/dist/peerLaneCapability.js +2 -6
  216. package/dist/permissionModesCapability.d.ts +0 -5
  217. package/dist/permissionModesCapability.js +5 -16
  218. package/dist/permissionRuleIssue.js +0 -3
  219. package/dist/permissionRulesWriteCapability.d.ts +0 -1
  220. package/dist/permissionRulesWriteCapability.js +2 -6
  221. package/dist/permissionsAllowGrantCapability.d.ts +0 -1
  222. package/dist/permissionsAllowGrantCapability.js +3 -9
  223. package/dist/permissionsDeclaration.d.ts +8 -0
  224. package/dist/permissionsDeclaration.js +101 -41
  225. package/dist/printPermissionOutcome.d.ts +12 -0
  226. package/dist/printPermissionOutcome.js +50 -0
  227. package/dist/printToolResultFrame.d.ts +1 -0
  228. package/dist/printToolResultFrame.js +5 -17
  229. package/dist/promptBlockedCopy.d.ts +5 -0
  230. package/dist/promptBlockedCopy.js +35 -0
  231. package/dist/promptDelivery.js +0 -8
  232. package/dist/protocolSegment.d.ts +9 -6
  233. package/dist/protocolSegment.js +31 -21
  234. package/dist/providerRejection.d.ts +4 -0
  235. package/dist/providerRejection.js +75 -14
  236. package/dist/replayBody.d.ts +12 -0
  237. package/dist/replayBody.js +25 -0
  238. package/dist/resumeRefusalCopy.d.ts +18 -1
  239. package/dist/resumeRefusalCopy.js +127 -32
  240. package/dist/retryStatus.d.ts +9 -1
  241. package/dist/retryStatus.js +9 -2
  242. package/dist/rewindArchiveCapability.d.ts +5 -14
  243. package/dist/rewindArchiveCapability.js +18 -61
  244. package/dist/rewindArchiveReading.d.ts +15 -0
  245. package/dist/rewindArchiveReading.js +56 -0
  246. package/dist/rewindEngineAction.d.ts +21 -0
  247. package/dist/rewindEngineAction.js +36 -0
  248. package/dist/rewindWireCaps.d.ts +2 -1
  249. package/dist/rewindWireCaps.js +1 -0
  250. package/dist/ruleCompileVerdict.d.ts +9 -1
  251. package/dist/ruleCompileVerdict.js +29 -4
  252. package/dist/runTerminal.d.ts +15 -12
  253. package/dist/runTerminal.js +60 -35
  254. package/dist/sandboxWire.js +13 -0
  255. package/dist/sdkRegistryTransit.d.ts +2 -1
  256. package/dist/sdkRegistryTransit.js +1 -0
  257. package/dist/sealedKeyCapability.d.ts +0 -5
  258. package/dist/sealedKeyCapability.js +3 -15
  259. package/dist/seam.js +4 -2
  260. package/dist/seatContract.d.ts +125 -34
  261. package/dist/seatContract.js +583 -179
  262. package/dist/sessionIdSegment.d.ts +1 -0
  263. package/dist/sessionIdSegment.js +11 -0
  264. package/dist/sessionMemoryErase.d.ts +1 -1
  265. package/dist/sessionMemoryErase.js +6 -1
  266. package/dist/sessionMemoryStatus.js +3 -2
  267. package/dist/sessionPolicyCapability.d.ts +0 -1
  268. package/dist/sessionPolicyCapability.js +2 -6
  269. package/dist/sessionReadRedactionCapability.d.ts +0 -5
  270. package/dist/sessionReadRedactionCapability.js +2 -13
  271. package/dist/sessionSyncFileHistory.d.ts +4 -0
  272. package/dist/sessionSyncFileHistory.js +66 -0
  273. package/dist/sessionSyncFileHistoryCapability.d.ts +0 -2
  274. package/dist/sessionSyncFileHistoryCapability.js +2 -9
  275. package/dist/skillToolListsCapability.d.ts +0 -2
  276. package/dist/skillToolListsCapability.js +2 -9
  277. package/dist/skillsWireCaps.d.ts +4 -0
  278. package/dist/skillsWireCaps.js +66 -1
  279. package/dist/sqlEngineCapability.d.ts +0 -1
  280. package/dist/sqlEngineCapability.js +9 -14
  281. package/dist/steerPriorityCapability.d.ts +0 -2
  282. package/dist/steerPriorityCapability.js +3 -12
  283. package/dist/storePostureCapability.d.ts +1 -5
  284. package/dist/storePostureCapability.js +16 -22
  285. package/dist/streamDrainingLegs.d.ts +6 -0
  286. package/dist/streamDrainingLegs.js +23 -0
  287. package/dist/subagent/engineCompactWire.js +13 -38
  288. package/dist/subagent/engineDelegatedPrompt.js +10 -6
  289. package/dist/subagent/engineSubagentOutput.d.ts +0 -1
  290. package/dist/subagent/engineSubagentOutput.js +5 -20
  291. package/dist/subagent/engineSubagentSteer.js +7 -5
  292. package/dist/subagent/engineSubagentTail.d.ts +0 -1
  293. package/dist/subagent/engineSubagentTail.js +7 -32
  294. package/dist/subagentContentStore.js +7 -18
  295. package/dist/supportFloor.d.ts +1 -0
  296. package/dist/supportFloor.js +15 -14
  297. package/dist/systemReminderTag.d.ts +1 -0
  298. package/dist/systemReminderTag.js +8 -0
  299. package/dist/terminalStatuses.d.ts +30 -0
  300. package/dist/terminalStatuses.js +67 -0
  301. package/dist/toolHistoryMismatch.d.ts +0 -3
  302. package/dist/toolHistoryMismatch.js +3 -7
  303. package/dist/toolResult.d.ts +6 -1
  304. package/dist/toolResult.js +90 -37
  305. package/dist/toolResultContent.d.ts +2 -0
  306. package/dist/toolResultContent.js +63 -0
  307. package/dist/types/engineState.d.ts +2 -1
  308. package/dist/usageAnalytics.d.ts +146 -0
  309. package/dist/usageAnalytics.js +335 -0
  310. package/dist/userInvokedSkillsCapability.d.ts +0 -2
  311. package/dist/userInvokedSkillsCapability.js +2 -9
  312. package/dist/webSearchBackendCapability.d.ts +0 -1
  313. package/dist/webSearchBackendCapability.js +3 -7
  314. package/dist/webSearchWireCaps.js +3 -11
  315. package/dist/wireErrorTriage.d.ts +2 -0
  316. package/dist/wireErrorTriage.js +34 -5
  317. package/dist/wireFailureShape.d.ts +23 -2
  318. package/dist/wireFailureShape.js +88 -43
  319. package/dist/wireRefusalCopy.js +2 -18
  320. package/dist/workflowClient.js +7 -11
  321. package/dist/workflowsJournalCapability.d.ts +0 -1
  322. package/dist/workflowsJournalCapability.js +2 -6
  323. package/dist/writeProtectionCapability.d.ts +0 -1
  324. package/dist/writeProtectionCapability.js +5 -10
  325. package/docs/INTEGRATION-CLIENTS.md +2279 -27
  326. package/package.json +5 -5
  327. package/dist/engineCapsGenerationGuard.d.ts +0 -4
  328. package/dist/engineCapsGenerationGuard.js +0 -19
  329. package/dist/panelRunningHistory.d.ts +0 -22
  330. package/dist/panelRunningHistory.js +0 -82
  331. package/dist/registryConflict.d.ts +0 -3
  332. package/dist/registryConflict.js +0 -44
@@ -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
  }
@@ -50,7 +50,7 @@ export function askParkRowArm(row) {
50
50
  const rowHasToolName = typeof row.toolName === 'string' && row.toolName.trim().length > 0;
51
51
  return askQuestionsFromPending(row) !== null || !rowHasToolName ? 'question' : 'tool-gate';
52
52
  }
53
- const GENERIC_DECISION_GATE_KINDS = ['human', 'policy_ask'];
53
+ const GENERIC_DECISION_GATE_KINDS = ['human'];
54
54
  export function askParkRowStillPending(rows, identity) {
55
55
  return rows.some(r => identity.gatedCallId !== undefined
56
56
  ? (r.toolCallId ?? r.boundCallId ?? undefined) === identity.gatedCallId
@@ -19,7 +19,7 @@ export type AutomaticAnswerVerdict = {
19
19
  readonly kind: 'unknown';
20
20
  readonly why: AutomaticAnswerUnknownWhy;
21
21
  };
22
- export declare const MODE_ARBITRATION: Readonly<Record<"hook" | "write_protection" | "mode" | "rule_unreadable" | "write_protection_unresolved" | "content_question" | "ancestor" | "dangerous_delete" | "unverifiable_delete" | "transcript_write" | "org_rule" | "governance_unavailable" | "ask_rule" | "denial_limit_fallback" | "classifier_no_ruling" | "requires_user_interaction" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "unstated" | "ordinary", CoreModeArbitrationRow>>;
22
+ export declare const MODE_ARBITRATION: Readonly<Record<"hook" | "write_protection" | "mode" | "rule_unreadable" | "target_unresolved" | "content_question" | "ancestor" | "dangerous_delete" | "unverifiable_delete" | "unverifiable_shell_text" | "transcript_write" | "org_rule" | "governance_unavailable" | "ask_rule" | "denial_limit_fallback" | "classifier_no_ruling" | "requires_user_interaction" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "unstated" | "ordinary", CoreModeArbitrationRow>>;
23
23
  export declare function foldModeArbitrationRow(classes: Iterable<string>, askFace?: AutomaticAnswerAskFace): CoreModeArbitrationRow;
24
24
  export declare function automaticAnswerVerdict(card: object | null | undefined, lane: AutomaticAnswerLane, facts?: AutomaticAnswerFacts | null): AutomaticAnswerVerdict;
25
25
  export declare function mandatedApprovalDetailOf(card: object | null | undefined): string;
@@ -1,8 +1,9 @@
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
+ import { currentEngineWord } from '../engineWordRenames.js';
6
7
  import { approvalAllowRuleShadowed } from './allowRuleShadow.js';
7
8
  import { approvalIsMandated, readApprovalMandate } from './toolApprovalWire.js';
8
9
  export const MODE_ARBITRATION = CORE_MODE_ARBITRATION;
@@ -15,9 +16,10 @@ const ORIGIN_IMPLIES_CLASS = Object.freeze({
15
16
  ask_rule: 'ask_rule',
16
17
  denial_limit_fallback: 'denial_limit_fallback',
17
18
  });
18
- function mandateWordClass(word) {
19
- if (word === 'operator_always')
19
+ function mandateWordClass(raw) {
20
+ if (raw === 'operator_always')
20
21
  return 'shell_floor';
22
+ const word = currentEngineWord(raw);
21
23
  return Object.hasOwn(TABLE, word) ? word : undefined;
22
24
  }
23
25
  const ORDINARY_ROW = CORE_MODE_ARBITRATION.ordinary;
@@ -41,7 +43,8 @@ export function foldModeArbitrationRow(classes, askFace) {
41
43
  let sandboxAdmission = true;
42
44
  let ruleMayClear = true;
43
45
  let hookAskFloor = false;
44
- for (const c of classes) {
46
+ for (const raw of classes) {
47
+ const c = typeof raw === 'string' ? currentEngineWord(raw) : raw;
45
48
  if (!Object.hasOwn(TABLE, c))
46
49
  continue;
47
50
  const row = TABLE[c];
@@ -102,7 +105,7 @@ export function automaticAnswerVerdict(card, lane, facts) {
102
105
  if (typeof card !== 'object' || card === null)
103
106
  return UNKNOWN.card_unreadable;
104
107
  const face = lane === 'mode_posture' ? askFaceOfCard(card) : undefined;
105
- const origin = readOwnNonEmptyString(evidenceView(card, 'origin'), 'origin');
108
+ const origin = readAskOrigin(evidenceView(card, 'origin'));
106
109
  if (origin !== undefined && Object.hasOwn(ORIGIN_IMPLIES_CLASS, origin)) {
107
110
  const cls = ORIGIN_IMPLIES_CLASS[origin];
108
111
  if (classBlocks(lane, cls, face))
@@ -130,11 +133,12 @@ export function automaticAnswerVerdict(card, lane, facts) {
130
133
  return UNKNOWN.classes_malformed;
131
134
  if (pair.state === 'absent')
132
135
  return UNKNOWN.classes_absent;
133
- const folded = foldModeArbitrationRow(pair.classes, face);
136
+ const classes = pair.classes.map(currentEngineWord);
137
+ const folded = foldModeArbitrationRow(classes, face);
134
138
  let anyKnown = false;
135
139
  let firstBlocking;
136
140
  let anyUnknown = false;
137
- for (const c of pair.classes) {
141
+ for (const c of classes) {
138
142
  if (!Object.hasOwn(TABLE, c)) {
139
143
  anyUnknown = true;
140
144
  continue;
@@ -154,7 +158,7 @@ export function automaticAnswerVerdict(card, lane, facts) {
154
158
  return mustAsk('settings_ask_rule');
155
159
  if (matched !== false)
156
160
  return UNKNOWN.host_not_reported;
157
- const releaseReason = CORE_MODE_RELEASE_REASONS.find((r) => pair.classes.includes(r));
161
+ const releaseReason = CORE_MODE_RELEASE_REASONS.find((r) => classes.includes(r));
158
162
  return releaseReason !== undefined ? Object.freeze({ kind: 'may_answer', releaseReason }) : MAY_ANSWER;
159
163
  }
160
164
  catch {
@@ -168,9 +172,10 @@ export function mandatedApprovalDetailOf(card) {
168
172
  const pair = readAskClassPairState(card);
169
173
  if (pair.state !== 'pair')
170
174
  return mandatedApprovalDetail();
171
- if (pair.classes.some((c) => !Object.hasOwn(TABLE, c)))
175
+ const classes = pair.classes.map(currentEngineWord);
176
+ if (classes.some((c) => !Object.hasOwn(TABLE, c)))
172
177
  return mandatedApprovalDetail();
173
- const row = foldModeArbitrationRow(pair.classes);
178
+ const row = foldModeArbitrationRow(classes);
174
179
  if (row.classifierEligible || row.sandboxAdmission)
175
180
  return mandatedApprovalDetail();
176
181
  return MANDATED_NO_AUTOMATIC_CHECK_DETAIL;
@@ -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,41 @@
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 === undefined)
38
+ return 'row_integrity';
39
+ return BINDING_SEAT_FIELDS.has(field) ? 'binding_mismatch' : undefined;
40
+ }
41
+ 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,21 @@
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;
5
+ export type DecisionRefusalAskReading = 'settled' | 'not_settled' | 'unreadable';
6
+ interface DecisionRefusalAskFacts {
7
+ readonly reading: DecisionRefusalAskReading;
8
+ readonly gateMoved: boolean;
9
+ readonly wordingHint: boolean;
10
+ }
11
+ export declare function readDecisionRefusalAsk(failure: unknown): DecisionRefusalAskFacts;
12
+ export declare function decisionRefusalAskReading(failure: unknown): DecisionRefusalAskReading;
2
13
  export interface DecisionRefusalClassified {
3
14
  readonly standing: DecisionRefusalStanding;
4
15
  readonly status: number;
5
16
  readonly errorCode?: string;
6
17
  readonly transient?: true;
18
+ readonly conflict?: DecisionRefusalConflict;
7
19
  }
8
20
  export declare function classifyDecisionRefusal(err: unknown): DecisionRefusalClassified | undefined;
9
21
  export type LiveAskPendingReading = 'pending' | 'not_pending' | 'unknown';
@@ -1,9 +1,60 @@
1
- import { refusalSaysStillPending } from '../engineErrorCodes.js';
1
+ import { ApprovalStaleError, AskDecidedError, ParkedResumeStartupError } from '@sema-agent/sdk';
2
+ import { ASK_GONE, ASK_GONE_PARKED, isDecideAlreadyResolvedCode, isDecideParkMovedCode, refusalRetriableReading, refusalSaysStillPending } from '../engineErrorCodes.js';
3
+ import { notPendingCauseReading } from './approvalEndingCause.js';
4
+ import { GATE_FAILURE_CODES } from './hitlBridge.js';
5
+ import { isAlreadyResolvedGateReason, isCodeClassifiedGateFailure } from './parkResolver.js';
6
+ import { decisionConflictOf } from './decisionConflict.js';
2
7
  import { readLivePendingRows } from './livePendingAsk.js';
3
8
  import { readToolApprovalRespondRefusal } from './toolApprovalWire.js';
4
9
  const ASK_DECIDED_CODE = 'conflict.ask_decided';
5
10
  const TRANSIENT_STATUSES = new Set([408, 425, 429, 502, 503, 504]);
6
11
  const DISPLAYABLE_CODE = /^[A-Za-z0-9._:-]{1,64}$/;
12
+ export function decisionRefusalSaysAskGone(err) {
13
+ return readDecisionRefusalAsk(err).reading === 'settled';
14
+ }
15
+ const facts = (reading, gateMoved = false, wordingHint = false) => ({ reading, gateMoved, wordingHint });
16
+ export function readDecisionRefusalAsk(failure) {
17
+ if (typeof failure !== 'object' || failure === null)
18
+ return facts('not_settled');
19
+ let flattened, localCode, name, errorCode, status, retriable, text;
20
+ try {
21
+ const o = failure;
22
+ if (Object.hasOwn(failure, 'unreadable') && failure.unreadable === true)
23
+ return facts('unreadable');
24
+ flattened = Object.hasOwn(failure, 'kind') && o.kind === 'failed';
25
+ const closed = (v) => (typeof v === 'string' && GATE_FAILURE_CODES.includes(v) ? v : undefined);
26
+ localCode = flattened ? (closed(o.code) ?? closed(o.safetyCode)) : closed(o.code);
27
+ name = flattened ? undefined : o.name;
28
+ errorCode = o.errorCode;
29
+ status = o.status;
30
+ retriable = refusalRetriableReading(failure);
31
+ text = flattened ? o.reason : String(failure);
32
+ }
33
+ catch {
34
+ return facts('unreadable');
35
+ }
36
+ if (retriable === undefined)
37
+ return facts('unreadable');
38
+ if (localCode !== undefined)
39
+ return facts(localCode === 'no_pending' ? 'settled' : 'not_settled');
40
+ if (name === 'HitlSafetyError')
41
+ return facts('not_settled');
42
+ const code = typeof errorCode === 'string' && errorCode.length > 0 ? errorCode : undefined;
43
+ const st = typeof status === 'number' && Number.isInteger(status) && status >= 400 && status <= 599 ? status : undefined;
44
+ if (code === ASK_DECIDED_CODE || code === ASK_GONE || (st === 410 && code !== ASK_GONE_PARKED))
45
+ return facts('settled');
46
+ if (st === 404 && notPendingCauseReading(failure) === 'ended')
47
+ return facts('settled');
48
+ if (code === undefined && name === AskDecidedError.name)
49
+ return facts('settled');
50
+ const gateMoved = isDecideAlreadyResolvedCode(code) || isDecideParkMovedCode(code) || code === ASK_GONE_PARKED || (code === undefined && name === ApprovalStaleError.name);
51
+ if (gateMoved || st !== undefined || name === ParkedResumeStartupError.name || isCodeClassifiedGateFailure(code, retriable))
52
+ return facts('not_settled', gateMoved);
53
+ return facts('not_settled', false, isAlreadyResolvedGateReason(text));
54
+ }
55
+ export function decisionRefusalAskReading(failure) {
56
+ return readDecisionRefusalAsk(failure).reading;
57
+ }
7
58
  export function classifyDecisionRefusal(err) {
8
59
  const r = readToolApprovalRespondRefusal(err);
9
60
  const status = r?.status;
@@ -11,13 +62,21 @@ export function classifyDecisionRefusal(err) {
11
62
  return undefined;
12
63
  const code = r?.errorCode;
13
64
  const errorCode = code !== undefined && DISPLAYABLE_CODE.test(code) ? code : undefined;
14
- const standing = status === 404 || status === 410 || (status === 409 && code === ASK_DECIDED_CODE)
65
+ const standing = readDecisionRefusalAsk(err).reading === 'settled'
15
66
  ? 'settled-elsewhere'
16
67
  : status >= 500 && status !== 501
17
68
  ? 'ambiguous'
18
69
  : 'refused-pending';
19
- 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 } : {}) });
70
+ const transient = TRANSIENT_STATUSES.has(status) ||
71
+ (typeof err === 'object' && err !== null && (refusalSaysStillPending(err) || (status === 404 && notPendingCauseReading(err) === 'other_replica')));
72
+ const conflict = status === 409 ? decisionConflictOf(err) : undefined;
73
+ return Object.freeze({
74
+ standing,
75
+ status,
76
+ ...(errorCode !== undefined ? { errorCode } : {}),
77
+ ...(transient ? { transient: true } : {}),
78
+ ...(conflict !== undefined ? { conflict } : {}),
79
+ });
21
80
  }
22
81
  export function liveAskPendingReading(envelope, approvalId) {
23
82
  if (typeof approvalId !== 'string' || approvalId === '')
@@ -17,7 +17,6 @@ export interface PolicyDenyReasonFacts {
17
17
  export declare function policyDenyReason(facts: PolicyDenyReasonFacts): string;
18
18
  export declare const HITL_INTERRUPT_MESSAGE_FOR_TOOL_USE = "[Request interrupted by user for tool use]";
19
19
  export declare const ENGINE_ABORT_TOOL_RESULT = "Operation aborted";
20
- export declare function toolEndOutputText(output: unknown): string | undefined;
21
20
  export interface ParkReattachNotice {
22
21
  attempt: number;
23
22
  max: number;
@@ -1,13 +1,16 @@
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';
5
+ import { toolEndOutputText } from '../toolResultContent.js';
4
6
  import { GATE_PARKED_ERROR_CODE } from '../engineErrorCodes.js';
5
7
  import { readRunTerminal, runTerminalCode, runTerminalGateKind, runTerminalGateToolName } from '../runTerminal.js';
6
8
  import { surfaceForCurrentSession, surfaceReplayedApprovalListUnreadable } from './hitlHostSurface.js';
7
- import { liveCardHoldOf, supersedeLiveCard } from './approvalHolds.js';
9
+ import { liveCardAnsweredHow, liveCardHoldOf, supersedeLiveCard } from './approvalHolds.js';
10
+ import { approvalCompleteParked } from './approvalCompleteParked.js';
8
11
  import { liveFrameCallKey } from './gateIdentity.js';
9
12
  import { SEMA_DENIAL_KIND_KEY, hasLedgerPositionKey } from './gateLedger.js';
10
- import { replayedApprovalDisposition } from './replayedApproval.js';
13
+ import { replayedApprovalDisposition, settledApprovalDisposition } from './replayedApproval.js';
11
14
  import { settleWithinDeadline } from '../abortableSleep.js';
12
15
  import { ccToolDenialKindForSettledBy } from '../gateVocabulary.js';
13
16
  import { REJECT_MESSAGE } from '../adapt/wireShapes.js';
@@ -21,7 +24,7 @@ export function policyDenyReason(facts) {
21
24
  const one = typeof facts.toolName === 'string' && SENTENCE_TOOL_NAME.test(facts.toolName) ? `this one "${facts.toolName}" call` : 'this one call';
22
25
  const flag = typeof facts.allowRuleFlag === 'string' && SENTENCE_TOOL_NAME.test(facts.allowRuleFlag.replace(/^--?/, '')) ? ` (or ${facts.allowRuleFlag})` : '';
23
26
  const budgetKnob = typeof facts.budgetSetting === 'string' && SENTENCE_TOOL_NAME.test(facts.budgetSetting) ? facts.budgetSetting : undefined;
24
- const cause = facts.cause === 'no_allow_rule' || facts.cause === 'ask_rule' || facts.cause === 'budget' ? facts.cause : 'other';
27
+ const cause = facts.cause === 'no_allow_rule' || facts.cause === 'ask_rule' || facts.cause === 'engine_ask_rule' || facts.cause === 'budget' ? facts.cause : 'other';
25
28
  if (facts.lane === 'dont_ask') {
26
29
  const mode = 'denied by the dontAsk permission mode';
27
30
  switch (cause) {
@@ -29,6 +32,8 @@ export function policyDenyReason(facts) {
29
32
  return `${mode}: ${one} needed approval, no permission rule allowed it, and this mode refuses anything that would ask — calls that need no approval still run; an allow rule in settings${flag} can approve calls like this ahead of time`;
30
33
  case 'ask_rule':
31
34
  return `${mode}: an ask rule matched ${one}, and this mode refuses anything that would ask (an ask rule outranks allow rules) — narrow or remove that ask rule, or use another permission mode to answer it`;
35
+ case 'engine_ask_rule':
36
+ return `${mode}: a rule on the engine side, not one of the user's permission rules, asks about ${one}, and this mode refuses anything that would ask — the user's settings cannot approve it; use another permission mode to answer it`;
32
37
  case 'budget':
33
38
  return `${mode}: ${one} needed approval, and this session's automatic-approval budget is used up, so saved rules no longer approve calls${budgetKnob !== undefined ? ` — raise ${budgetKnob} before launch to change the cap` : ''}`;
34
39
  default:
@@ -42,6 +47,8 @@ export function policyDenyReason(facts) {
42
47
  return `${lane}: ${one} needed a person's approval, no permission rule answered it, and ${tail}; a matching allow rule in settings${flag} can approve calls like this ahead of time`;
43
48
  case 'ask_rule':
44
49
  return `${lane}: an ask rule matched ${one}, so it needed a person's answer, and ${tail}; an ask rule outranks allow rules, so narrow or remove that ask rule, or run the task interactively to answer it`;
50
+ case 'engine_ask_rule':
51
+ return `${lane}: a rule on the engine side, not one of the user's permission rules, asks about ${one}, so it needed a person's answer, and ${tail}; the user's settings cannot approve it - run the task interactively to answer it`;
45
52
  case 'budget':
46
53
  return `${lane}: ${one} needed a person's approval, and this session's automatic-approval budget is used up, so saved rules no longer approve calls; ${tail}${budgetKnob !== undefined ? `; raise ${budgetKnob} before launch to change the cap` : ''}`;
47
54
  default:
@@ -71,19 +78,6 @@ function denyStampedEnd(ev, attribution) {
71
78
  export const HITL_INTERRUPT_MESSAGE_FOR_TOOL_USE = '[Request interrupted by user for tool use]';
72
79
  const RUN_CANCELLED_ERROR_CODE = RUN_CANCELLED_CODE;
73
80
  export const ENGINE_ABORT_TOOL_RESULT = 'Operation aborted';
74
- export function toolEndOutputText(output) {
75
- if (typeof output === 'string')
76
- return output;
77
- if (!Array.isArray(output))
78
- return undefined;
79
- const parts = [];
80
- for (const b of output) {
81
- const bb = b;
82
- if (bb?.type === 'text' && typeof bb.text === 'string')
83
- parts.push(bb.text);
84
- }
85
- return parts.length > 0 ? parts.join('') : undefined;
86
- }
87
81
  function isAskToolLoose(name) {
88
82
  return typeof name === 'string' && name.replace(/[\s_-]+/g, '').toLowerCase() === 'askuserquestion';
89
83
  }
@@ -266,48 +260,34 @@ const TOOL_END_ARMS = [
266
260
  '改顺序必须同批改 TOOL_END_ARM_ORDER 并说明为什么新顺序仍然对。');
267
261
  }
268
262
  }
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
263
  async function routeToolApprovalFrame(ev, ctx) {
279
264
  const { led, deps } = ctx;
280
265
  if (ev.type === 'tool_approval_complete') {
281
266
  const id = ev.approvalId;
282
- if (typeof id === 'string' && id !== '' && supersedeLiveCard(liveFrameCallKey(id), completeFrameParked(ev))) {
267
+ if (typeof id === 'string' && id !== '' && supersedeLiveCard(liveFrameCallKey(id), approvalCompleteParked(ev))) {
283
268
  hostLog('debug', `liveHitlAskWire: tool_approval_complete for ${id} arrived while its card was waiting — card withdrawn (nothing will be sent from it)`);
284
269
  }
270
+ if (typeof id === 'string' && id !== '')
271
+ led.noteApprovalSettled(id, 'closed_by_engine');
285
272
  return { kind: 'skip' };
286
273
  }
287
274
  if (!deps.respondToolApproval) {
288
275
  hostLog('debug', `liveHitlAskWire: tool_approval frame ${ev.approvalId} but no respond wire — engine self-settles (TTL deny)`);
289
276
  return { kind: 'skip' };
290
277
  }
291
- if (hasLedgerPositionKey(ev) && !led.preambleApprovals().has(ev.approvalId)) {
292
- const disposition = await judgeLedgerApprovalFrame(ev, ctx);
278
+ const settledHere = settledApprovalDisposition(ev, led.settledApprovals());
279
+ if (settledHere !== undefined || (hasLedgerPositionKey(ev) && !led.preambleApprovals().has(ev.approvalId))) {
280
+ const disposition = settledHere ?? (await judgeLedgerApprovalFrame(ev, ctx));
293
281
  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');
282
+ hostLog('debug', disposition.kind === 'decided' && disposition.settled !== undefined
283
+ ? `liveHitlAskWire: tool_approval frame ${ev.approvalId} repeats a question already settled on this bridge (${disposition.settled}) — not raised again, no decision sent`
284
+ : `liveHitlAskWire: tool_approval frame ${ev.approvalId} read back from the run event log is ${disposition.kind}` +
285
+ `${disposition.kind === 'unknown' ? ` (${disposition.why})` : disposition.cause !== undefined ? ` (${disposition.cause})` : ''}` +
286
+ ' — not raised as a question, no decision sent');
297
287
  if (disposition.kind === 'unknown' && ctx.signal?.aborted !== true)
298
288
  surfaceReplayedApprovalListUnreadable();
299
289
  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
- }
290
+ invokeHostCallbackIsolated('liveHitlAskWire: onReplayedApproval', () => deps.onReplayedApproval?.(ev, disposition));
311
291
  }
312
292
  return { kind: 'skip' };
313
293
  }
@@ -317,29 +297,24 @@ async function routeToolApprovalFrame(ev, ctx) {
317
297
  const frameCallId = !fromSubagent && typeof ev.toolCallId === 'string' && ev.toolCallId !== '' ? ev.toolCallId : undefined;
318
298
  const gatedCallId = fromSubagent ? undefined : frameCallId ?? led.lastPendingFsCall();
319
299
  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);
300
+ const runTaskId = ctx.taskId.current;
301
+ const approvalIdAtLaunch = ev.approvalId;
302
+ const launched = surfaceToolApprovalFrameAndRespond(ev, deps.respondToolApproval, argsOfGatedStart(led, argsCallId), ctx.signal, deps.approvalLane, typeof runTaskId === 'string' && runTaskId !== '' ? { taskId: runTaskId } : undefined);
321
303
  const hold = liveCardHoldOf(liveFrameCallKey(ev.approvalId));
322
- const handled = launched.then((outcome) => settleLiveCardOutcome(ev, outcome, ctx, fromSubagent, gatedCallId), (e) => {
304
+ const handled = launched.then((outcome) => settleLiveCardOutcome(ev, approvalIdAtLaunch, outcome, ctx, fromSubagent, gatedCallId), (e) => {
323
305
  hostLog('debug', `liveHitlAskWire: tool_approval card for ${ev.approvalId} failed: ${String(e).slice(0, 160)}`);
324
306
  });
325
307
  led.trackLiveCard(() => hold?.phase ?? 'card', handled);
326
308
  return { kind: 'skip' };
327
309
  }
328
- function settleLiveCardOutcome(ev, outcome, ctx, fromSubagent, gatedCallId) {
310
+ function settleLiveCardOutcome(ev, approvalId, outcome, ctx, fromSubagent, gatedCallId) {
329
311
  const { led, deps } = ctx;
330
312
  const { decision } = outcome;
313
+ const settledHow = liveCardAnsweredHow(outcome);
314
+ if (settledHow !== undefined)
315
+ led.noteApprovalSettled(approvalId, settledHow);
331
316
  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
- }
317
+ invokeHostCallbackIsolated('liveHitlAskWire: onToolApprovalOutcome', () => deps.onToolApprovalOutcome?.(ev, outcome));
343
318
  }
344
319
  if (decision === 'deny' && !fromSubagent) {
345
320
  const attribution = {
@@ -374,7 +349,7 @@ async function judgeLedgerApprovalFrame(ev, ctx) {
374
349
  .finally(release);
375
350
  });
376
351
  const approvals = got.kind === 'value' ? { read: 'ok', body: got.value } : { read: 'failed' };
377
- return replayedApprovalDisposition(ev, { preamble: ctx.led.preambleApprovals(), approvals });
352
+ return replayedApprovalDisposition(ev, { preamble: ctx.led.preambleApprovals(), approvals, settled: ctx.led.settledApprovals() });
378
353
  }
379
354
  function routeToolStart(ev, callId, led) {
380
355
  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>;