@sema-agent/client-core 0.77.1 → 0.77.2

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 (382) hide show
  1. package/CHANGELOG.md +13 -1
  2. package/README.md +5 -4
  3. package/dist/abortableSleep.d.ts +0 -28
  4. package/dist/abortableSleep.js +0 -28
  5. package/dist/adapt/arms.d.ts +0 -46
  6. package/dist/adapt/arms.js +9 -544
  7. package/dist/adapt/ids.d.ts +0 -58
  8. package/dist/adapt/ids.js +0 -63
  9. package/dist/adapt/instanceLedger.d.ts +0 -25
  10. package/dist/adapt/instanceLedger.js +0 -20
  11. package/dist/adapt/panelTasks.d.ts +0 -69
  12. package/dist/adapt/panelTasks.js +0 -95
  13. package/dist/adapt/textSegmentAuthority.d.ts +1 -137
  14. package/dist/adapt/textSegmentAuthority.js +0 -54
  15. package/dist/adapt/textStream.d.ts +0 -252
  16. package/dist/adapt/textStream.js +2 -281
  17. package/dist/adapt/toolCards.d.ts +0 -46
  18. package/dist/adapt/toolCards.js +0 -23
  19. package/dist/adapt/turnFlags.d.ts +0 -54
  20. package/dist/adapt/turnFlags.js +1 -17
  21. package/dist/adapt/wireShapes.d.ts +0 -92
  22. package/dist/adapt/wireShapes.js +0 -83
  23. package/dist/adapt.d.ts +0 -55
  24. package/dist/adapt.js +1 -120
  25. package/dist/adapter/activeRunSelfHeal.d.ts +22 -515
  26. package/dist/adapter/activeRunSelfHeal.js +10 -625
  27. package/dist/adapter/downstream/eventToSdkMessage.d.ts +2 -305
  28. package/dist/adapter/downstream/eventToSdkMessage.js +2 -867
  29. package/dist/adapter/downstream/terminalToSdkResult.d.ts +2 -314
  30. package/dist/adapter/downstream/terminalToSdkResult.js +13 -560
  31. package/dist/adapter/downstream/turnUsageToModelUsage.d.ts +0 -95
  32. package/dist/adapter/downstream/turnUsageToModelUsage.js +2 -31
  33. package/dist/adapter/runStream.d.ts +0 -206
  34. package/dist/adapter/runStream.js +6 -596
  35. package/dist/adapter/types.d.ts +0 -111
  36. package/dist/adapter/types.js +0 -29
  37. package/dist/agentSession/backgroundView.d.ts +0 -106
  38. package/dist/agentSession/backgroundView.js +3 -49
  39. package/dist/agentSession/contract.d.ts +0 -98
  40. package/dist/agentSession/contract.js +0 -12
  41. package/dist/agentsWireCaps.d.ts +0 -72
  42. package/dist/agentsWireCaps.js +4 -72
  43. package/dist/approvalsStreamLiveCapability.d.ts +0 -25
  44. package/dist/approvalsStreamLiveCapability.js +0 -31
  45. package/dist/argvFlagValue.d.ts +0 -33
  46. package/dist/argvFlagValue.js +3 -35
  47. package/dist/attachmentsWireCaps.d.ts +0 -127
  48. package/dist/attachmentsWireCaps.js +0 -144
  49. package/dist/autoModeUnavailable.d.ts +0 -120
  50. package/dist/autoModeUnavailable.js +0 -144
  51. package/dist/classifierStatus.d.ts +0 -72
  52. package/dist/classifierStatus.js +0 -167
  53. package/dist/classifierVerdictWire.d.ts +0 -54
  54. package/dist/classifierVerdictWire.js +0 -133
  55. package/dist/clientContextWireCaps.d.ts +0 -37
  56. package/dist/clientContextWireCaps.js +0 -36
  57. package/dist/clientSlice.d.ts +0 -63
  58. package/dist/clientSlice.js +0 -45
  59. package/dist/cloudConfigWireCaps.d.ts +0 -86
  60. package/dist/cloudConfigWireCaps.js +2 -65
  61. package/dist/compensations.d.ts +0 -52
  62. package/dist/compensations.js +2 -63
  63. package/dist/controlRouter.d.ts +2 -208
  64. package/dist/controlRouter.js +1 -129
  65. package/dist/coreValuePorts.d.ts +0 -84
  66. package/dist/coreValuePorts.js +0 -29
  67. package/dist/decideReceipt.d.ts +0 -110
  68. package/dist/decideReceipt.js +0 -84
  69. package/dist/detachWire.d.ts +0 -130
  70. package/dist/detachWire.js +1 -130
  71. package/dist/deviceExecutorManagementCapability.d.ts +2 -35
  72. package/dist/deviceExecutorManagementCapability.js +1 -37
  73. package/dist/diagnostics.d.ts +0 -8
  74. package/dist/diagnostics.js +0 -8
  75. package/dist/diff/patch.d.ts +0 -17
  76. package/dist/diff/patch.js +0 -19
  77. package/dist/effectiveFacts.d.ts +0 -40
  78. package/dist/effectiveFacts.js +0 -22
  79. package/dist/effortWire.d.ts +0 -11
  80. package/dist/effortWire.js +0 -12
  81. package/dist/engineAgentPanelStore.d.ts +0 -169
  82. package/dist/engineAgentPanelStore.js +13 -293
  83. package/dist/engineCapReader.d.ts +0 -62
  84. package/dist/engineCapReader.js +1 -43
  85. package/dist/engineCapsCache.d.ts +0 -177
  86. package/dist/engineCapsCache.js +0 -199
  87. package/dist/engineCapsGenerationGuard.d.ts +0 -2
  88. package/dist/engineCapsGenerationGuard.js +0 -11
  89. package/dist/engineErrorCodes.d.ts +1 -316
  90. package/dist/engineErrorCodes.js +0 -427
  91. package/dist/engineHttpTools.d.ts +0 -29
  92. package/dist/engineHttpTools.js +0 -29
  93. package/dist/engineIdentity.d.ts +0 -83
  94. package/dist/engineIdentity.js +0 -88
  95. package/dist/engineInlineTaskStats.d.ts +0 -57
  96. package/dist/engineInlineTaskStats.js +1 -42
  97. package/dist/engineNoticeCodes.d.ts +0 -190
  98. package/dist/engineNoticeCodes.js +0 -182
  99. package/dist/engineSessionParam.d.ts +0 -30
  100. package/dist/engineSessionParam.js +0 -52
  101. package/dist/engineToolLabelStore.d.ts +0 -29
  102. package/dist/engineToolLabelStore.js +0 -31
  103. package/dist/engineWireSdk.d.ts +0 -90
  104. package/dist/engineWireSdk.js +0 -74
  105. package/dist/engineWireTarget.d.ts +0 -14
  106. package/dist/engineWireTarget.js +0 -39
  107. package/dist/env/localeGeo.d.ts +0 -12
  108. package/dist/env/localeGeo.js +2 -77
  109. package/dist/env/localeTag.d.ts +0 -34
  110. package/dist/env/localeTag.js +0 -32
  111. package/dist/env/uiLanguage.d.ts +0 -13
  112. package/dist/env/uiLanguage.js +0 -25
  113. package/dist/envFlag.d.ts +0 -37
  114. package/dist/envFlag.js +0 -40
  115. package/dist/executionLaneCapability.d.ts +0 -48
  116. package/dist/executionLaneCapability.js +0 -54
  117. package/dist/finalVerifyWire.d.ts +0 -67
  118. package/dist/finalVerifyWire.js +3 -43
  119. package/dist/fleet/fleetLedger.d.ts +0 -308
  120. package/dist/fleet/fleetLedger.js +10 -429
  121. package/dist/fleet/fleetProjection.d.ts +0 -240
  122. package/dist/fleet/fleetProjection.js +0 -189
  123. package/dist/fleet/fleetRowAgentType.d.ts +0 -6
  124. package/dist/fleet/fleetRowAgentType.js +1 -32
  125. package/dist/fleet/workflowSizeWarning.d.ts +0 -47
  126. package/dist/fleet/workflowSizeWarning.js +1 -46
  127. package/dist/fleetAgentPanelProjection.d.ts +0 -48
  128. package/dist/fleetAgentPanelProjection.js +8 -173
  129. package/dist/fleetTaskDesc.d.ts +0 -39
  130. package/dist/fleetTaskDesc.js +0 -69
  131. package/dist/forkWireCaps.d.ts +0 -23
  132. package/dist/forkWireCaps.js +1 -24
  133. package/dist/gateOutcome.d.ts +0 -140
  134. package/dist/gateOutcome.js +0 -85
  135. package/dist/gateVocabulary.d.ts +0 -114
  136. package/dist/gateVocabulary.js +1 -161
  137. package/dist/goalStopHook.d.ts +0 -127
  138. package/dist/goalStopHook.js +0 -178
  139. package/dist/headlessPermissionModeWire.d.ts +0 -76
  140. package/dist/headlessPermissionModeWire.js +1 -155
  141. package/dist/headlessReconnectWire.d.ts +0 -84
  142. package/dist/headlessReconnectWire.js +13 -67
  143. package/dist/hitl/approvalDecisionNoteAudit.d.ts +0 -49
  144. package/dist/hitl/approvalDecisionNoteAudit.js +0 -56
  145. package/dist/hitl/approvalOutcomeNote.d.ts +0 -2
  146. package/dist/hitl/approvalOutcomeNote.js +0 -20
  147. package/dist/hitl/approvalResolution.d.ts +0 -113
  148. package/dist/hitl/approvalResolution.js +0 -66
  149. package/dist/hitl/approvalsFeed.d.ts +0 -183
  150. package/dist/hitl/approvalsFeed.js +12 -243
  151. package/dist/hitl/armedGateRegistry.d.ts +0 -55
  152. package/dist/hitl/armedGateRegistry.js +0 -146
  153. package/dist/hitl/askGateWire.d.ts +0 -86
  154. package/dist/hitl/askGateWire.js +1 -95
  155. package/dist/hitl/askParkRowRouting.d.ts +0 -123
  156. package/dist/hitl/askParkRowRouting.js +1 -89
  157. package/dist/hitl/crashConverged.d.ts +0 -148
  158. package/dist/hitl/crashConverged.js +0 -214
  159. package/dist/hitl/editedRuleTextPrecheck.d.ts +0 -109
  160. package/dist/hitl/editedRuleTextPrecheck.js +0 -71
  161. package/dist/hitl/frameRouter.d.ts +0 -134
  162. package/dist/hitl/frameRouter.js +6 -378
  163. package/dist/hitl/gateIdentity.d.ts +0 -39
  164. package/dist/hitl/gateIdentity.js +0 -41
  165. package/dist/hitl/gateLedger.d.ts +0 -267
  166. package/dist/hitl/gateLedger.js +0 -121
  167. package/dist/hitl/hitlBridge.d.ts +7 -431
  168. package/dist/hitl/hitlBridge.js +7 -476
  169. package/dist/hitl/hitlHostSurface.d.ts +0 -150
  170. package/dist/hitl/hitlHostSurface.js +0 -169
  171. package/dist/hitl/livePendingAsk.d.ts +0 -91
  172. package/dist/hitl/livePendingAsk.js +0 -74
  173. package/dist/hitl/localAllowRule.d.ts +0 -62
  174. package/dist/hitl/localAllowRule.js +1 -33
  175. package/dist/hitl/parkOwnership.d.ts +0 -56
  176. package/dist/hitl/parkOwnership.js +0 -22
  177. package/dist/hitl/parkResolver.d.ts +3 -88
  178. package/dist/hitl/parkResolver.js +8 -349
  179. package/dist/hitl/parkRowBirthWait.d.ts +2 -26
  180. package/dist/hitl/parkRowBirthWait.js +3 -93
  181. package/dist/hitl/persistedRulesWire.d.ts +13 -358
  182. package/dist/hitl/persistedRulesWire.js +0 -310
  183. package/dist/hitl/planReviewWire.d.ts +4 -176
  184. package/dist/hitl/planReviewWire.js +9 -312
  185. package/dist/hitl/resumeRunningCard.d.ts +0 -105
  186. package/dist/hitl/resumeRunningCard.js +0 -105
  187. package/dist/hitl/sessionPolicyWire.d.ts +11 -191
  188. package/dist/hitl/sessionPolicyWire.js +0 -149
  189. package/dist/hitl/suspendedReopen.d.ts +3 -24
  190. package/dist/hitl/suspendedReopen.js +0 -14
  191. package/dist/hitl/toolApprovalWire.d.ts +5 -1308
  192. package/dist/hitl/toolApprovalWire.js +5 -945
  193. package/dist/hooksWireCaps.d.ts +0 -38
  194. package/dist/hooksWireCaps.js +0 -190
  195. package/dist/host.d.ts +0 -105
  196. package/dist/host.js +0 -33
  197. package/dist/hostEnv.d.ts +0 -14
  198. package/dist/hostEnv.js +0 -13
  199. package/dist/imagesWireCaps.d.ts +0 -21
  200. package/dist/imagesWireCaps.js +0 -22
  201. package/dist/index.d.ts +0 -134
  202. package/dist/index.js +0 -476
  203. package/dist/interactiveHalt.d.ts +5 -153
  204. package/dist/interactiveHalt.js +0 -111
  205. package/dist/interactiveToolsWire.d.ts +0 -62
  206. package/dist/interactiveToolsWire.js +1 -67
  207. package/dist/leaderConflict.d.ts +0 -59
  208. package/dist/leaderConflict.js +0 -48
  209. package/dist/limitsWire.d.ts +0 -125
  210. package/dist/limitsWire.js +2 -120
  211. package/dist/liveInitToolFace.d.ts +0 -77
  212. package/dist/liveInitToolFace.js +1 -109
  213. package/dist/liveModelCatalog.d.ts +0 -52
  214. package/dist/liveModelCatalog.js +0 -48
  215. package/dist/liveQuestionStore.d.ts +0 -87
  216. package/dist/liveQuestionStore.js +1 -56
  217. package/dist/mcpLiveness.d.ts +0 -151
  218. package/dist/mcpLiveness.js +0 -122
  219. package/dist/mcpPanel.d.ts +0 -104
  220. package/dist/mcpPanel.js +0 -49
  221. package/dist/mcpReconnect.d.ts +0 -58
  222. package/dist/mcpReconnect.js +0 -69
  223. package/dist/mcpWireCaps.d.ts +0 -55
  224. package/dist/mcpWireCaps.js +1 -13
  225. package/dist/memoryComplianceCapability.d.ts +3 -57
  226. package/dist/memoryComplianceCapability.js +0 -57
  227. package/dist/memoryEntriesWire.d.ts +6 -198
  228. package/dist/memoryEntriesWire.js +0 -144
  229. package/dist/memoryOriginCapability.d.ts +3 -53
  230. package/dist/memoryOriginCapability.js +0 -50
  231. package/dist/memorySpecWire.d.ts +0 -116
  232. package/dist/memorySpecWire.js +0 -150
  233. package/dist/model/catalog.d.ts +0 -111
  234. package/dist/model/catalog.js +0 -87
  235. package/dist/model/catalogLoader.d.ts +0 -114
  236. package/dist/model/catalogLoader.js +2 -139
  237. package/dist/model/modelSupplyRules.d.ts +1 -55
  238. package/dist/model/modelSupplyRules.js +0 -62
  239. package/dist/model/providerAuth.d.ts +0 -103
  240. package/dist/model/providerAuth.js +2 -38
  241. package/dist/model/providerCatalog.d.ts +0 -45
  242. package/dist/model/providerCatalog.js +0 -37
  243. package/dist/model/providerPresets.d.ts +0 -33
  244. package/dist/model/providerPresets.js +2 -91
  245. package/dist/model/tierVocabulary.d.ts +0 -31
  246. package/dist/model/tierVocabulary.js +0 -27
  247. package/dist/modelBudgetRule.d.ts +0 -39
  248. package/dist/modelBudgetRule.js +0 -39
  249. package/dist/modelCapabilityProbe.d.ts +0 -175
  250. package/dist/modelCapabilityProbe.js +0 -147
  251. package/dist/modelWireCaps.d.ts +0 -13
  252. package/dist/modelWireCaps.js +0 -13
  253. package/dist/notifications.d.ts +0 -233
  254. package/dist/notifications.js +14 -492
  255. package/dist/oneShotWireCaps.d.ts +0 -34
  256. package/dist/oneShotWireCaps.js +0 -35
  257. package/dist/ownKey.d.ts +0 -33
  258. package/dist/ownKey.js +0 -33
  259. package/dist/panelRunningHistory.d.ts +0 -28
  260. package/dist/panelRunningHistory.js +0 -43
  261. package/dist/peerFrames.d.ts +0 -71
  262. package/dist/peerFrames.js +0 -168
  263. package/dist/peerLaneCapability.d.ts +0 -44
  264. package/dist/peerLaneCapability.js +0 -52
  265. package/dist/permissionRuleIssue.d.ts +0 -30
  266. package/dist/permissionRuleIssue.js +0 -78
  267. package/dist/permissionRulesWriteCapability.d.ts +0 -46
  268. package/dist/permissionRulesWriteCapability.js +0 -54
  269. package/dist/permissionWireCaps.d.ts +0 -37
  270. package/dist/permissionWireCaps.js +0 -37
  271. package/dist/postureKnob.d.ts +0 -71
  272. package/dist/postureKnob.js +0 -86
  273. package/dist/principalWire.d.ts +0 -17
  274. package/dist/principalWire.js +0 -17
  275. package/dist/printToolResultFrame.d.ts +0 -100
  276. package/dist/printToolResultFrame.js +0 -33
  277. package/dist/promptProfileWireCaps.d.ts +0 -13
  278. package/dist/promptProfileWireCaps.js +0 -13
  279. package/dist/readFacePosture.d.ts +0 -43
  280. package/dist/readFacePosture.js +0 -46
  281. package/dist/request/printNotification.d.ts +0 -20
  282. package/dist/request/printNotification.js +0 -55
  283. package/dist/request/taskRequest.d.ts +0 -249
  284. package/dist/request/taskRequest.js +6 -498
  285. package/dist/resumeRefusalCopy.d.ts +0 -136
  286. package/dist/resumeRefusalCopy.js +1 -117
  287. package/dist/retainBackgroundWireCaps.d.ts +0 -48
  288. package/dist/retainBackgroundWireCaps.js +0 -48
  289. package/dist/retryStatus.d.ts +2 -221
  290. package/dist/retryStatus.js +0 -107
  291. package/dist/rewindWireCaps.d.ts +0 -27
  292. package/dist/rewindWireCaps.js +0 -24
  293. package/dist/runCancelContext.d.ts +1 -20
  294. package/dist/runCancelContext.js +0 -34
  295. package/dist/runTerminal.d.ts +0 -273
  296. package/dist/runTerminal.js +0 -177
  297. package/dist/sandboxWire.d.ts +0 -38
  298. package/dist/sandboxWire.js +0 -82
  299. package/dist/scenarioWire.d.ts +0 -60
  300. package/dist/scenarioWire.js +0 -68
  301. package/dist/scratchpadWireCaps.d.ts +0 -11
  302. package/dist/scratchpadWireCaps.js +0 -32
  303. package/dist/sdkWireTransit.d.ts +0 -38
  304. package/dist/sdkWireTransit.js +0 -30
  305. package/dist/seam.d.ts +18 -853
  306. package/dist/seam.js +0 -52
  307. package/dist/seatContract.d.ts +0 -555
  308. package/dist/seatContract.js +5 -304
  309. package/dist/selfOrchestrationDenial.d.ts +0 -189
  310. package/dist/selfOrchestrationDenial.js +0 -162
  311. package/dist/selfOrchestrationWireCaps.d.ts +0 -38
  312. package/dist/selfOrchestrationWireCaps.js +0 -38
  313. package/dist/sessionMap.d.ts +0 -60
  314. package/dist/sessionMap.js +0 -37
  315. package/dist/sessionMemoryStatus.d.ts +11 -114
  316. package/dist/sessionMemoryStatus.js +0 -112
  317. package/dist/sessionModelLatch.d.ts +0 -29
  318. package/dist/sessionModelLatch.js +0 -40
  319. package/dist/sessionPolicyCapability.d.ts +3 -53
  320. package/dist/sessionPolicyCapability.js +0 -52
  321. package/dist/sessionSlot.d.ts +0 -26
  322. package/dist/sessionSlot.js +0 -17
  323. package/dist/skillsWireCaps.d.ts +0 -87
  324. package/dist/skillsWireCaps.js +0 -38
  325. package/dist/sqlEngineCapability.d.ts +0 -118
  326. package/dist/sqlEngineCapability.js +0 -134
  327. package/dist/sseIdleTriage.d.ts +0 -79
  328. package/dist/sseIdleTriage.js +1 -80
  329. package/dist/steering.d.ts +0 -54
  330. package/dist/steering.js +0 -63
  331. package/dist/subagent/engineCompactWire.d.ts +0 -34
  332. package/dist/subagent/engineCompactWire.js +4 -134
  333. package/dist/subagent/engineDelegatedPrompt.d.ts +0 -31
  334. package/dist/subagent/engineDelegatedPrompt.js +1 -103
  335. package/dist/subagent/engineRowStopGate.d.ts +0 -20
  336. package/dist/subagent/engineRowStopGate.js +0 -49
  337. package/dist/subagent/engineSubagentOutput.d.ts +0 -7
  338. package/dist/subagent/engineSubagentOutput.js +1 -51
  339. package/dist/subagent/engineSubagentResume.d.ts +2 -279
  340. package/dist/subagent/engineSubagentResume.js +2 -129
  341. package/dist/subagent/engineSubagentSteer.d.ts +0 -26
  342. package/dist/subagent/engineSubagentSteer.js +0 -63
  343. package/dist/subagent/engineSubagentTail.d.ts +0 -38
  344. package/dist/subagent/engineSubagentTail.js +2 -110
  345. package/dist/subagent/engineTaskHandleWire.d.ts +6 -67
  346. package/dist/subagent/engineTaskHandleWire.js +0 -126
  347. package/dist/subagent/subagentOwnerAbsence.d.ts +0 -2
  348. package/dist/subagent/subagentOwnerAbsence.js +0 -25
  349. package/dist/subagentContentStore.d.ts +3 -260
  350. package/dist/subagentContentStore.js +7 -540
  351. package/dist/systemReminderTag.d.ts +0 -49
  352. package/dist/systemReminderTag.js +0 -61
  353. package/dist/toolResult.d.ts +1 -211
  354. package/dist/toolResult.js +1 -358
  355. package/dist/toolRoster.d.ts +0 -140
  356. package/dist/toolRoster.js +0 -66
  357. package/dist/typePins.d.ts +0 -46
  358. package/dist/types/engineState.d.ts +0 -71
  359. package/dist/types/engineState.js +0 -16
  360. package/dist/ultracodeWireCaps.d.ts +0 -75
  361. package/dist/ultracodeWireCaps.js +0 -92
  362. package/dist/unrefTimer.d.ts +0 -27
  363. package/dist/webSearchBackendCapability.d.ts +0 -72
  364. package/dist/webSearchBackendCapability.js +0 -79
  365. package/dist/webSearchWireCaps.d.ts +0 -37
  366. package/dist/webSearchWireCaps.js +0 -34
  367. package/dist/websearch/searchProviderPresets.d.ts +0 -81
  368. package/dist/websearch/searchProviderPresets.js +0 -27
  369. package/dist/wireErrorTriage.d.ts +4 -160
  370. package/dist/wireErrorTriage.js +0 -171
  371. package/dist/wireRefusalCopy.d.ts +0 -28
  372. package/dist/wireRefusalCopy.js +0 -28
  373. package/dist/workflow.d.ts +0 -40
  374. package/dist/workflow.js +0 -52
  375. package/dist/workflowClient.d.ts +0 -122
  376. package/dist/workflowClient.js +8 -360
  377. package/dist/workflowMonitor.d.ts +2 -72
  378. package/dist/workflowMonitor.js +0 -26
  379. package/dist/writeProtectionCapability.d.ts +0 -112
  380. package/dist/writeProtectionCapability.js +0 -109
  381. package/docs/INTEGRATION-CLIENTS.md +30 -1
  382. package/package.json +1 -1
@@ -1,151 +1,28 @@
1
- /**
2
- * adapter/activeRunSelfHeal.ts — 「会话被 active-run claim 锁死」的自愈决断层(A-028.1,
3
- * #244 族A 包半场,2026-08-12;源形 = cli `src/sema/activeRunSelfHeal.ts` 的四路分诊树 +
4
- * 结局文案层,判别半场 `activeRunBusySignal` 早已在隔壁 `runStream.ts` —— 本文件是它的
5
- * 消费侧,两半从此同居包内单源)。
6
- *
7
- * ── 事故形态 ────────────────────────────────────────────────────────────────────────────────
8
- * 引擎的 session claim 是会话级锁(createRun EEXIST ⇒ 409)。终态释放它;而 park 态
9
- * (`suspended` / `needs_review`)保留它,且重启引擎救不回来(boot 期孤儿回收只捞 running)、
10
- * 时间型 reap 默认整条腿不跑。于是「上一轮 turn 在审批/提问处 park 了,用户走开」之后,这个
11
- * 会话的每一条新消息恒被 409 拒收。
12
- *
13
- * ── 🔴 处置是**分诊**,不是「见 409 就取消」────────────────────────────────────────────────
14
- * 分诊真源两级(A-028.1 目标形):**`pendingGate.kind` 优先,status 表回退**。
15
- * · kind 是 wire 指名的 gate 身份(引擎只在 parked 且真有待决 checkpoint 时带它),比 status
16
- * 粒度准:run status `needs_review` ≠ gate kind `plan_review` —— dry-run 复核门(gate kind
17
- * `needs_review`)同样落 status needs_review,按 status 猜臂会把它错路成 plan 审批卡。
18
- * · kind 缺席(旧引擎 / 非 park / store 降级)才回退 status 表。
19
- * 四路结局:
20
- * ① plan_review 门 ⇒ **重开审批卡**,绝不 cancel(cancel = 替用户 reject 掉整个 plan)。
21
- * ② 审批/提问门(human / irreversible_ask / tool_approval)⇒ **重开那张卡**(把决定权还给
22
- * 用户;cancel+自动重发臂已整退役 —— 实证它是「新消息→cancel→新 run 同 ask 再 park」的
23
- * 循环病根;重开失败也只如实告知,绝不回退 cancel)。
24
- * ③ `running` ⇒ **呈 steer/cancel/wait 三选卡**(runningChoiceArm;offerRunningChoice 供给
25
- * 缺席即回落 not-parked 现状行不动)。持有 claim 的可能是用户背景化的 durable run,正在替
26
- * 他干活——绝不见 409 就自动 cancel,决定权给用户。新结局五个:running-steered /
27
- * running-steer-failed / running-cancelled / running-cancel-timeout / running-cancel-failed;
28
- * cancel 后走有界等 claim 释放腿(waitForClaimRelease,CANCEL_RELEASE_WAIT_MS 封顶)。
29
- * ④ kind/status 两级都判不出(含今天还不存在的新门/新状态)⇒ 也什么都不做,如实告知。
30
- * 不确定时不做破坏性动作;新词到货时最坏结果是保守,而不是拿没想过的词做破坏性动作。
31
- *
32
- * ── 出路文案纪律 ────────────────────────────────────────────────────────────────────────────
33
- * 本模块产出的每一句「你可以做什么」都必须是**真的接了线**的动作。默认串承诺的是 cli 的
34
- * `/clear`(regenerateSessionId ⇒ 全新引擎会话)与引擎自己的 cancel/decide 端点;没有 `/clear`
35
- * 概念的宿主**必须**经 {@link ActiveRunSelfHealCopy} 换成自己的真出路 —— 绝不出现「按某个键」
36
- * 而那个键没有注册。
37
- *
38
- * 🔴 portability:本文件的值级 import **只有**包内共享等待叶 `abortableSleep`(#265 起:cancel 后
39
- * 的 claim 释放轮询要等一拍,而「怎么安全地睡一觉」全仓只许有一份实现;类型仍全部 type-only 借
40
- * `runStream.ts`),不进内核/A 层闭包;index 闭包已登记。
41
- */
42
1
  import { abortableSleep } from '../abortableSleep.js';
43
- /** wire gate kind → plan_review 重开臂的 canonical 成员(core CheckpointGate 词表之一)。 */
44
2
  export const PLAN_REVIEW_GATE_KIND = 'plan_review';
45
- /**
46
- * wire gate kind → plan 重开臂的**全表**(#265,2026-08-14 补齐 `dry_run_review`)。
47
- *
48
- * server 侧的 kind→决议入口映射(`resumeEntryForGate`)把 `plan_review` 与 `dry_run_review` 送去
49
- * **同一个**入口 `/v1/assistant/tasks/{taskId}/plan_review`,两者 park 也都落 `needs_review`
50
- * ⇒ 同臂。此前包内只认 canonical 那一个词,于是一条停在 dry-run 复核门上的 run 走结局④「不动 +
51
- * 如实说」——话是诚实的,但那张本来可以重开的卡没被重开,会话仍旧锁着。
52
- *
53
- * ⚠️ 别把它和 run status 词 `needs_review` 混起来:那是 **status**(两跳间接量),gate kind 叫
54
- * `dry_run_review`。gate kind `needs_review` 仍是表外词(结局④),这条边界由常驻门逐词钉。
55
- */
56
3
  export const PLAN_REVIEW_GATE_KINDS = [PLAN_REVIEW_GATE_KIND, 'dry_run_review'];
57
- /**
58
- * wire gate kind → ask 重开臂的成员:`human` / `irreversible_ask` / `policy_ask` 是 core
59
- * CheckpointGate 的审批/提问族(有 approvals.list pending 行可铸卡);`tool_approval` 是 park 判定
60
- * (`toolApprovalWire.isToolApprovalGate`)已承认的一等 kind —— 两张表必须同集合
61
- * ([paired-mechanisms-must-share-premise]:park 认得出、分诊路不进去 = 恒 reopen-failed)。
62
- * 表外 kind(`resource_limit` / `needs_review` / `task_done` / 未来新词)一律走结局④:不动 +
63
- * 如实说(wire 带的 decidePath 由文案层原样交给用户)。
64
- *
65
- * `policy_ask` 是 #265 补齐的第四个成员(server 全表逐名对读):它与 `human` 同族同入口
66
- * (`/v1/approvals/{sessionId}/decide`),漏一个词的代价 = 那一类门恒走结局④,卡不重开。
67
- */
68
4
  export const ASK_PARK_GATE_KINDS = ['human', 'irreversible_ask', 'policy_ask', 'tool_approval'];
69
- /**
70
- * 行上在场且**不属 ask 门族**的门种(`plan_review` / `resource_limit` / 未来新词);`null` = 该行可入
71
- * ask/审批臂。kind 缺席(pre-`gate_kind` 历史行)与空串也归 `null` —— 旧行没有更强信号,各臂维持
72
- * 自己的原判据(toolName 等),门种闸只拦「明说了自己不归这族」的行。
73
- * 🔴 单源:`classifyAskParkRows` 的门种闸与 `findPendingForTask` 的行过滤必须共用这一只 ——
74
- * 各写各的 includes 必漂(P-30:`findPendingForTask` 侧漏了这道闸,同 task 停着 plan_review 行时
75
- * 「任意行」回落把它递给工具审批 wire,弹 toolName 空的卡、decide 撞 409 gate_not_tool_approval)。
76
- */
77
5
  export function askParkForeignGateKind(row) {
78
6
  const kind = typeof row.gateKind === 'string' && row.gateKind.length > 0 ? row.gateKind : null;
79
7
  return kind !== null && !ASK_PARK_GATE_KINDS.includes(kind) ? kind : null;
80
8
  }
81
- /**
82
- * status 回退表(kind 缺席时的粗粒度代理):plan_review park 的引擎 run status。
83
- * 🔴 动作**不按状态名硬编码枚举**:两张表之外的一切状态(含今天还不存在的)一律「不动它 +
84
- * 如实说」,新状态到货时最坏结果是保守。
85
- */
86
9
  export const PLAN_REVIEW_STATES = ['needs_review'];
87
- /** status 回退表:审批/提问 park(**只有 `suspended`**)。 */
88
10
  export const ASK_PARK_STATES = ['suspended'];
89
- /**
90
- * 走三选卡臂的状态 —— **只有 `running`**(引擎 RunStatus 里「正在干活」的那一个词)。
91
- *
92
- * 🔴 为什么是白名单而不是「park 词表之外的一切」:三选卡上的两条动作路(steer / cancel)都只对
93
- * 一条**真在跑**的 run 成立 —— steer 的语义是「注入在跑的那一轮」(非 running 会落 `queued` /
94
- * `parked_for_wake` / 409),cancel 对 park 态则是替用户否掉待决项(禁区)。今天还不存在的新状态
95
- * 落在表外 ⇒ 照旧「不动它 + 如实说」:不确定时不给用户递一把语义不明的把手。
96
- */
97
11
  export const RUNNING_STATES = ['running'];
98
- /**
99
- * 「引擎确认这条 run **不再占着会话**」的状态词 —— 也就是 cancel 之后允许重发那条消息的**唯一**
100
- * 判据。
101
- *
102
- * 🔴 为什么是白名单而不是「running 之外的一切」:这条会话锁的语义是 **park 态(`suspended` /
103
- * `needs_review`)保留 claim**。用 `!RUNNING_STATES.includes(status)` 当判据时,一条正在收尾的 run
104
- * 只要在这一拍被读成 park(cancel 是异步的,run 完全可能先走到一个 checkpoint),就被判成「已释放」
105
- * ⇒ 上屏说「引擎确认它不再占着这个会话」(假话)+ 立刻重发 ⇒ 那条消息一头撞进还锁着的会话,再吃
106
- * 一个 409。没想过的新状态词同理落在白名单外:我们不知道那个词是不是「释放了」,就不能替引擎下这个
107
- * 断言(等到点如实说「还占着」是可收敛的诚实结局,零破坏性)。
108
- *
109
- * 词表锚 = SDK `RunStatus`(`running|completed|failed|suspended|needs_review|blocked`)的三个终态
110
- * + `timeout`。真·404(引擎不认得这条 run 了)另有一条腿,不走这里(见 {@link waitForClaimRelease})。
111
- */
112
12
  export const CLAIM_RELEASED_STATES = ['completed', 'failed', 'blocked', 'timeout'];
113
- /**
114
- * 「这条 run **确实还占着**会话」的状态词 —— 也就是文案敢说「that run still held this session」的
115
- * **唯一**判据(二次评审 R5 [medium])。
116
- *
117
- * 🔴 为什么又是白名单:上面那张表管的是「敢不敢重发」,这张表管的是「敢不敢断言它还占着」,两件事
118
- * **都**只能由正面证据得出,而它们**不是互补的** —— 两张表之外还有一整片「读到了一个我们不认识的
119
- * 词」的地带(server 加一个新终态 `cancelled` 就是现成的例子)。用「lastStatus 非空」当持锁判据,
120
- * 那个新词会被读成「还占着」并渲上屏,而会话其实早就释放了。表外非空词 ⇒ 既不算释放也不算持锁,
121
- * 收口成「确认不了」。
122
- */
123
13
  export const CLAIM_HELD_STATES = ['running', 'suspended', 'needs_review'];
124
- /**
125
- * 🔴 判据锚在「**有没有一张现在就能答、答了就放行的卡**」这个决定量上,不是锚在「是不是 park
126
- * 态」这个前置条件上([anchor-on-the-deciding-quantity])。所以三张 reopen-failed / not-parked /
127
- * state-unknown 全部落 `not-delivered`:它们同样是 park,但卡没能呈到用户面前,把注入件放回队列
128
- * 只会在下一拍再撞一次同样的 409。`running-not-found`(幽灵 claim)同落 `not-delivered`:
129
- * 「已解锁」证不出,自动重投可能撞回仍锁着的会话。
130
- */
131
14
  export function selfHealSubmissionDisposition(outcome) {
132
15
  switch (outcome.kind) {
133
16
  case 'decision-pending':
134
17
  return 'held-for-decision';
135
18
  case 'ask-reopened':
136
19
  case 'plan-review-reopened':
137
- // 重开成功 = 卡真的呈上去了(`ReopenCardVerdict` 带宿主呈现回执,呈不出来会降级成
138
- // reopen-failed 那一臂)⇒ 用户手上确有把手。
139
20
  return 'held-for-decision';
140
21
  case 'running-cancelled':
141
22
  case 'stale-park-cancelled':
142
- // L-93:与 running-cancelled 同一条重发腿 —— 前提也同一个(引擎**确认**那条 run 不再占着会话,
143
- // 判据 = CLAIM_RELEASED_STATES;确认不到的两形落 timeout/failed ⇒ default 的 not-delivered)。
144
23
  case 'ask-decided-without-card':
145
24
  return 'resending';
146
25
  case 'ask-decided-release-unknown':
147
- // L-80(对抗复审 r4 [high]③):决断成功、释放未确认。**没有**释放驱动的重投机制就不许承诺「稍后投递」——
148
- // 注入件按既有 not-delivered 处置(端:归因上屏 + 问一次待决队列,不回灌不盲发);用户消息文案说「再发一次」。
149
26
  return 'not-delivered';
150
27
  case 'running-steered':
151
28
  return 'handed-off';
@@ -153,7 +30,6 @@ export function selfHealSubmissionDisposition(outcome) {
153
30
  return 'not-delivered';
154
31
  }
155
32
  }
156
- /** 结局 → 续听意图;非 `running-steered` 结局(含非对象入参)⇒ `null`。纯函数,不改 {@link SelfHealOutcome} 型面。 */
157
33
  export function steerFollowIntent(outcome) {
158
34
  if (typeof outcome !== 'object' || outcome === null || outcome.kind !== 'running-steered')
159
35
  return null;
@@ -164,13 +40,10 @@ export function steerFollowIntent(outcome) {
164
40
  return { kind: 'after-wake', taskId };
165
41
  return { kind: 'tail', taskId };
166
42
  }
167
- /** 会话作用域位的透传口(缺席即不置键 —— `exactOptionalPropertyTypes` 下 `{session: undefined}`
168
- * 与「没有这个键」不是一回事,而 SDK 那一侧读的正是「在不在场」)。 */
169
43
  function sessionOpts(deps) {
170
44
  const sessionId = deps?.sessionId;
171
45
  return typeof sessionId === 'string' && sessionId.length > 0 ? { session: sessionId } : {};
172
46
  }
173
- /** 失败细节:只取 message 且截断 —— 错误对象整体 stringify 可能把请求上下文一起带上屏。 */
174
47
  function describeFailure(e) {
175
48
  if (typeof e === 'object' && e !== null && 'message' in e) {
176
49
  const m = e.message;
@@ -179,28 +52,10 @@ function describeFailure(e) {
179
52
  }
180
53
  return 'the engine gave no detail';
181
54
  }
182
- /** run 记录里读 status —— 只认非空字符串,别的一律当「读不到」(诚实缺席不编造)。 */
183
55
  function readStatus(record) {
184
56
  const st = record?.status;
185
57
  return typeof st === 'string' && st.length > 0 ? st : null;
186
58
  }
187
- /**
188
- * 「这条 run 还占着会话吗」的**直接读数**(0.68.0 / L-230;server 7.73.0 S-122 P-45:
189
- * `GET /v1/runs/:id` 上的 `heldBy`)。
190
- *
191
- * 三态,**刻意不是布尔**:
192
- * · `'held'` —— `heldBy` 是一个非空串 = **有一条 run 名字在上面**,它确实还占着;
193
- * · `'released'` —— `heldBy` 显式是 `null` = 一句**正面事实**:会话已经交出来了;
194
- * · `'unknown'` —— 这一位**整键缺席**(老引擎 / 不发这一位的部署),或者形不对
195
- * (非串非 null)⇒ 读不出,**绝不**当成 released。
196
- *
197
- * 🔴 `null` 与「键不在」必须分开,这是本读器存在的全部理由:
198
- * 把两者都读成「释放了」,老引擎上每一次探测都会当场答「已释放」并立刻重发那条消息 ——
199
- * 而它可能一头撞回一个还锁着的会话(再吃一个 409,正是这条腿存在的理由);
200
- * 把两者都读成「还占着」,新引擎上那句正面事实白给了,窗要一直等到点。
201
- * 🔴 判据用 `Object.hasOwn`:`{heldBy: undefined}` 与 `{}` 在这里是**同一件事**(都读不出),
202
- * 而 `null` 必须与它们分开 —— 靠 `?? ` / `!= null` 一类写法分不出来。
203
- */
204
59
  function readClaimHolder(record) {
205
60
  if (record === null || typeof record !== 'object')
206
61
  return 'unknown';
@@ -211,18 +66,8 @@ function readClaimHolder(record) {
211
66
  return 'released';
212
67
  if (typeof v === 'string' && v.length > 0)
213
68
  return 'held';
214
- // 空串 / 数 / 对象:形不对 ⇒ 读不出(失效方向在保守那一侧:不宣告释放)。
215
69
  return 'unknown';
216
70
  }
217
- /**
218
- * cancel 回执(202 体)/ run 行上的 `cancelRequested`(0.68.0 / L-230;server 7.73.0 S-122 P-45)。
219
- *
220
- * 🔴 **三态**同上:`true` / `false` 都是真读数,**读不出 ⇒ `undefined`**(老引擎不发这一位)。
221
- * 绝不折 `false` —— 「没请求过取消」与「不知道有没有请求过」对下一步的含义不同:前者可以放心
222
- * 再发一次 cancel,后者再发就可能是第二枪。
223
- * 🔴 它**不是**「已经取消了」:上游逐字是 *requested*,一次**已受理的请求**,不是终局。
224
- * 会话有没有交出来仍然只由 {@link readClaimHolder} 与 {@link CLAIM_RELEASED_STATES} 回答。
225
- */
226
71
  export function readCancelRequested(body) {
227
72
  if (typeof body !== 'object' || body === null || Array.isArray(body))
228
73
  return undefined;
@@ -231,77 +76,35 @@ export function readCancelRequested(body) {
231
76
  const v = body.cancelRequested;
232
77
  return typeof v === 'boolean' ? v : undefined;
233
78
  }
234
- /** 重开判决 → 结局(真呈现回执消费点:`presented === false` 时 reopened 断言不成立)。 */
235
79
  function reopenDelivered(verdict) {
236
80
  return verdict.reopened === true && verdict.presented !== false;
237
81
  }
238
- /**
239
- * 一次**至多一次**(non-idempotent)POST 失败之后:到底是「服务端明确拒了」还是「不知道有没有
240
- * 落地」。steer 与 cancel 两条腿共用这一把尺 —— 它们同属「这一枪不能盲发第二次」的族,而两类
241
- * 失败对用户的处置**完全不同**,各写一份必漂。
242
- *
243
- * 判据 = HTTP 状态码在不在、是不是 4xx:**4xx = 服务端收到了、解析了、明确拒绝**(steering 族的
244
- * 拒收码全在 4xx:422 invalid_content / 409 not_running|queue_full|duplicate_input_id / 404 / 413),
245
- * 那一枪确定没有产生效果;5xx 与「压根没有状态码」(fetch failed / 连接断 / 超时)都是**送达未知**
246
- * —— 请求可能已经被受理,只是回执没回来。
247
- * 🔴 方向:不确定时归 `unknown`(保守面在「不对用户断言一件证不出的事」这一侧)。
248
- */
249
82
  export function atMostOnceFailureClass(e) {
250
83
  const status = e?.status;
251
84
  return typeof status === 'number' && status >= 400 && status < 500 ? 'rejected' : 'unknown';
252
85
  }
253
- /** SDK `SteerReceipt.delivery`(`applied` / `queued` / `parked_for_wake`)。读不出 = null —— 不编造
254
- * 一个投递语义:三种 delivery 对用户的意思完全不同,猜错就是假承诺。 */
255
86
  export function readSteerDelivery(receipt) {
256
87
  if (typeof receipt !== 'object' || receipt === null || !('delivery' in receipt))
257
88
  return null;
258
89
  const d = receipt.delivery;
259
90
  return typeof d === 'string' && d.length > 0 ? d : null;
260
91
  }
261
- /** SDK `SteerReceipt.status` —— 服务端在投递那一刻读到的**行状态词**(`running` /「park 词」/终态)。
262
- * `delivery:'queued'` 时它是**唯一**能分出「在等哪一类门」的材料(契约逐字:`"suspended"` OR
263
- * `"needs_review"`),读不出即 null(不猜一个门种)。 */
264
92
  export function readSteerReceiptStatus(receipt) {
265
93
  if (typeof receipt !== 'object' || receipt === null || !('status' in receipt))
266
94
  return null;
267
95
  const s = receipt.status;
268
96
  return typeof s === 'string' && s.length > 0 ? s : null;
269
97
  }
270
- /** cancel 之后**有界**等那条 run 交出会话的缺省窗(`POST …/cancel` 是 202 异步 —— 收下 ≠ 已停)。 */
271
98
  const CANCEL_RELEASE_WAIT_MS = 10_000;
272
- /** 出卡前存活对账那一发 `runs.get` 的等待上界(Inkglow-1085 P0b②)——与假死锁复核读口同款
273
- * 「慢网也回得来」的诚实预算;窗尽/传输错 = 读不到 ⇒ 保守维持出卡(见 runningChoiceArm 注)。 */
274
99
  const RUNNING_LIVENESS_RECHECK_TIMEOUT_MS = 4_000;
275
- // ── running 三选卡的「已呈现且用户选了 Do nothing」登记(Inkglow-1085 P0b①,2026-08-19)────────
276
- //
277
- // 病形(案卷车2 发现2):同一条幽灵/长跑 run 占着会话时,用户每发一条消息就被整卡打断一次 ——
278
- // 呈现台账只有 per-call 局部量(offeringByTask / runningChoiceOffered),没有任何跨 turn 的
279
- // 「用户已对 run X 说过『别动它』」记账。本登记 = 模块级 (sessionKey,taskId) 集合:
280
- // · 只在**卡真呈现过且用户选了 wait** 时登记(呈卡腿抛错那次不登记 —— 用户没看到卡,压掉后续
281
- // 等于把没呈过的卡当已呈);
282
- // · 登记在场 ⇒ 同 (sessionKey,taskId) 再撞 409 不再整卡重弹,结局带 `alreadyOffered: true`
283
- // 判别位(端据此降级渲一行;默认文案层已给降级行,含 cancel 端点与 wayOut 两条真出路);
284
- // · 用户选 steer/cancel ⇒ 清登记(他重新对这条 run 表了态);宿主的「重新打开操作菜单」入口
285
- // 走 {@link clearRunningChoiceOffer}。
286
- // run 身份变化天然换键(taskId 不同);同 run 状态迁移到 park 态会走别的臂,登记不拦。
287
100
  const runningChoiceDeclined = new Set();
288
- /** 登记表上限(对抗复审轻形采纳):超限 FIFO 驱逐最老条目 —— 驱逐代价 = 卡重弹一次(修前行为)。 */
289
101
  const RUNNING_CHOICE_LEDGER_CAP = 512;
290
102
  function runningChoiceDeclineKey(sessionId, taskId) {
291
- // NUL 分隔:两段都是外来串,可打印分隔符在「sessionId 里恰有它」时会串键。
292
103
  return `${sessionId ?? ''}\u0000${taskId}`;
293
104
  }
294
- /**
295
- * L-93 两选卡(陈旧 park)的登记键 —— 与三选卡**同一张表、不同键**。
296
- * 🔴 为什么不共用同一个键:两张卡问的是不同的问题(「这条**在跑**的 run 怎么办」vs「这条 park 住、
297
- * 而审批已经**不在引擎上**的 run 怎么办」),用户对其中一张说过「什么都不做」不构成对另一张的表态
298
- * —— 共键会让一次旧表态把一张他从没见过的卡压掉。共用一张表只为共用那条 FIFO 上界与同一个清口。
299
- */
300
105
  function staleParkDeclineKey(sessionId, taskId) {
301
106
  return `${runningChoiceDeclineKey(sessionId, taskId)}\u0000stale-park`;
302
107
  }
303
- /** 登记一条「用户选了什么都不做」+ FIFO 有界驱逐(两张卡共用 —— 各写一份必漂,而漂的后果是
304
- * 其中一张卡的登记表悄悄变成无界)。驱逐代价 = 那条 run 的卡重弹一次(= 修前行为,方向安全)。 */
305
108
  function rememberDeclinedOffer(key) {
306
109
  runningChoiceDeclined.add(key);
307
110
  if (runningChoiceDeclined.size > RUNNING_CHOICE_LEDGER_CAP) {
@@ -310,44 +113,19 @@ function rememberDeclinedOffer(key) {
310
113
  runningChoiceDeclined.delete(oldest);
311
114
  }
312
115
  }
313
- /** 清掉某条 run 的「Do nothing」登记 —— 宿主「重新打开操作菜单」入口(下一次 409 重新整卡呈现)。
314
- * `sessionId` 与当时喂给 {@link ActiveRunSelfHealDeps.sessionId} 的值同源(缺席 = 默认键)。
315
- * 🔴 三选卡与两选卡(L-93)的登记**一起清**:这个入口的语义是「让我重新对这条 run 表态」,
316
- * 只清其中一张会让另一张在宿主已经答应「重新问」之后仍旧被压掉(paired-mechanisms 同族)。 */
317
116
  export function clearRunningChoiceOffer(taskId, sessionId) {
318
117
  runningChoiceDeclined.delete(runningChoiceDeclineKey(sessionId, taskId));
319
118
  runningChoiceDeclined.delete(staleParkDeclineKey(sessionId, taskId));
320
119
  }
321
- /** 测试钩:清空整张登记表(套件各组之间隔离用)。 */
322
120
  export function __resetRunningChoiceLedgerForTests() {
323
121
  runningChoiceDeclined.clear();
324
122
  }
325
- /** 假死锁复核读口(`deps.listOwnedPendingApprovals`)的等待上界:一发 approvals 列表读,4s 是
326
- * 「慢网也回得来」的诚实预算;窗尽=分不出真卡与幽灵 ⇒ 保守 stand down(见调用点注)。 */
327
123
  const OWNED_PENDING_RECHECK_TIMEOUT_MS = 4_000;
328
- /** 轮询退避:200ms 起、×1.5、封顶 2s(短退避,别把一次 cancel 打成连发)。 */
329
124
  const CANCEL_POLL_START_MS = 200;
330
125
  const CANCEL_POLL_MAX_MS = 2_000;
331
- /** 缺省退避等待 = 包内共享等待叶(中止即刻 settle 并清 timer;timer **不 unref** —— 等待窗里它
332
- * 可能是事件循环里唯一的活,unref 会让 `-p` 车道在中途直接退出,连那句诚实的收口行都一起蒸发)。
333
- * 🔴 就地再写一份「怎么安全地睡一觉」= 第三份中止语义,只会各自漂;无 signal 的调用方给一只
334
- * 永不 abort 的 signal,照样走同一个叶。 */
335
126
  function claimPollSleep(ms, signal) {
336
127
  return abortableSleep(ms, signal ?? new AbortController().signal);
337
128
  }
338
- /**
339
- * 这一发出站请求的中止口:**只有真把一个截止 signal 交到请求上,墙钟才是个闸** —— 只在下一轮循环
340
- * 开头查 deadline 是事后统计,一发挂住的 `runs.get`/`runs.cancel` 会跑满 SDK 缺省超时(且 GET 属
341
- * 可重试类,实际上界是它的数倍),预算形同虚设。合并调用方 signal(用户 Esc)⇒ Esc 之后这一发也
342
- * 当场收,零后续出站。
343
- *
344
- * 🔴 **不用 `AbortSignal.timeout`**(二次评审 R6 实测逼出):它的内部定时器在 Node 上是 **unref 的**
345
- * —— 等待窗里这只定时器可能是事件循环里唯一的活,那种情况下进程会在截止到点之前直接退出,连那句
346
- * 诚实的收口行都一起蒸发(仓内 `abortableSleep` / 重查环同一条判据)。这里自己持一只**普通**定时器,
347
- * 并把清理做成 `release()` 的义务:不清就会把进程按预算多拖活一拍。
348
- * 🔴 合并也自己做,不走 `AbortSignal.any`:少一条「宿主运行期有没有这个静态面」的分支,行为在所有
349
- * 宿主上逐字相同。
350
- */
351
129
  function claimProbeLease(caller, remainingMs) {
352
130
  const ctl = new AbortController();
353
131
  const timer = setTimeout(() => {
@@ -368,36 +146,16 @@ function claimProbeLease(caller, remainingMs) {
368
146
  },
369
147
  };
370
148
  }
371
- /**
372
- * 等到那条 run 不再占着会话 ⇒ `released`;窗口到点仍占着 / 读不到 / 调用方中止 ⇒ 不 released
373
- * (如实说,**绝不重发**那条被拒的消息)。
374
- *
375
- * 🔴 判据锚在**决定量**本身:`runs.get` 读回的 status 在不在 {@link CLAIM_RELEASED_STATES} 白名单里
376
- * (而不是「cancel 回了 202」那个前置条件,也不是「不是 running」那个更弱的反面)。
377
- * 🔴 读失败(传输错)**不**当成「释放了」:那是「不知道」,而把不知道当成释放 = 把消息喂进一个可能
378
- * 还锁着的会话(下一次 409 就是它的代价)。只有 404(引擎不认得这条 run 了)才算释放 —— 那种情况
379
- * 下它连 claim 都不可能还占着。
380
- * 🔴 有界性由**每一发都带截止 signal**保证(见 {@link claimProbeSignal}),不是靠循环顶那一次减法。
381
- */
382
149
  export async function waitForClaimRelease(taskId, deps) {
383
150
  const now = typeof deps.now === 'function' ? deps.now : () => Date.now();
384
151
  const sleep = typeof deps.sleep === 'function' ? deps.sleep : claimPollSleep;
385
- // 🔴 预算先规范化成**有限非负**数(二次评审 R6 [medium]):`NaN` 会让 `deadline`/`remaining` 全是
386
- // NaN,`remaining <= 0` 恒假 ⇒ 环永不收口(而每一拍的 `AbortSignal.timeout(NaN)` 抛错又被 catch
387
- // 吞掉,退化成零延时空转);`Infinity` 同样到不了截止条件。预算是从 env/配置解析来的量,坏值
388
- // 必须在入口就被读成「不等」,而不是把 turn 卡死。
389
152
  const budgetMs = Number.isFinite(deps.budgetMs) && deps.budgetMs > 0 ? deps.budgetMs : 0;
390
153
  const startedAt = now();
391
154
  const deadline = startedAt + budgetMs;
392
155
  const waited = () => now() - startedAt;
393
- // 🔴 经函数读,别把 `signal?.aborted` 直接写在多处条件里:`AbortSignal.aborted` 在类型面是只读
394
- // 属性,tsc 的控制流分析会在第一次判过之后把它**窄成 `false | undefined`**,后面每一次重判都被
395
- // 判成「不可能成立的比较」。而这个量恰恰**会在两次 await 之间变** —— 要终止一个循环,动手的那
396
- // 一层必须在每一轮里重新求值。函数返回值走声明类型,不进那条窄化链。
397
156
  const isAborted = () => deps.signal?.aborted === true;
398
157
  let delay = CANCEL_POLL_START_MS;
399
158
  let lastStatus = null;
400
- // 🔴 0.68.0 / L-230:`heldBy` 的三态与 `lastStatus` **同律** —— 只记最近一次**有回答**的探测。
401
159
  let lastHolder = 'unknown';
402
160
  for (;;) {
403
161
  if (isAborted())
@@ -424,58 +182,23 @@ export async function waitForClaimRelease(taskId, deps) {
424
182
  lease.release();
425
183
  if (isAborted())
426
184
  return { released: false, waitedMs: waited(), aborted: true, lastStatus, lastHolder };
427
- // 🔴 本发是被**窗自己的截止**掐掉的(lease 到点;caller 未中止)⇒ 没有读到任何回答 ——
428
- // 既不是释放证据,也不是「读不出来」的新证据,上一发**完成了的**读数保持在座(#244 F1
429
- // 换装批 G-c 尾竞态定谳:R2 [medium] 的「只记最近一次」指最近一次**有回答**的探测 ——
430
- // 404/传输错是回答了「不知道」,截止掐断连回答都没有;不区分则 confirmedHeld 在窗尾随机
431
- // 翻 false,同输入两跑两种文案)。回循环顶按墙钟收口。
432
185
  if (lease.signal.aborted)
433
186
  continue;
434
- // 这一发**有回答但读不出**(404/传输错)⇒ 最新证据是「不知道」,陈旧的成功读数当场作废
435
- // (见 lastStatus 头注)。
436
187
  lastStatus = null;
437
188
  lastHolder = 'unknown';
438
- // 🔴 **404 也只是「读不到」,不是「释放了」**(二次评审 [high] 处置,2026-08-14):这个读口的
439
- // 404 在 SDK 契约上是 `not_found.run` —— 「这条 run 不存在」与「它不是你的」**共用同一个码,
440
- // 且是刻意设计的不可分辨**(跨租户不给存在性预言机)。凭它断言「那条 run 已经不占着会话」,
441
- // 就是拿一个缺席信号当正面证据 —— 与本批到处在修的那个错同形。误判的代价也不对称:说成
442
- // 「已释放」会让调用方立刻重发,而那条消息可能一头撞回还锁着的会话(再吃一个 409,正是这条腿
443
- // 存在的理由);说成「读不出来」只是让用户自己再发一次。其余错误(传输错/本发的截止中止)同理。
444
189
  continue;
445
190
  }
446
- lease.release(); // 🔴 定时器必清:这只是**普通**定时器(见 claimProbeLease),不清就把进程多拖活一拍
447
- lastStatus = status; // 奇形记录(读不出 status)同样把陈旧读数清掉 —— 最新证据仍是「不知道」
191
+ lease.release();
192
+ lastStatus = status;
448
193
  lastHolder = holder;
449
- // ── 🔴 0.68.0 / L-230(server 7.73.0 S-122 P-45 读面消费)—— **两条释放证据,取并** ────────
450
- // ① `heldBy === null` —— 引擎**直说**会话交出来了。这是本批新接的那条,而且它比 ② 强:
451
- // ② 是拿「run 走到了某个终态词」去**推断**锁没了,而锁与 run 的生命周期不是同一件事
452
- // (park 态保留 claim 正是这条推断会踩的坑,也是 `CLAIM_RELEASED_STATES` 是白名单而不是
453
- // 「running 之外的一切」的原因)。有直接证据就别再推断。
454
- // ② `status ∈ CLAIM_RELEASED_STATES` —— 老引擎不发 `heldBy`(读出 `'unknown'`)时的**既有路**,
455
- // 一字未动。⇒ 本件对老引擎**零行为变化**;对新引擎多了一条更早、更硬的收口证据。
456
- // 🔴 `'unknown'` 绝不当成释放:缺席不是证据(与本文件 404 那一格逐字同一条纪律)。
457
194
  if (holder === 'released') {
458
195
  return { released: true, waitedMs: waited(), aborted: false, lastStatus: status, lastHolder: holder };
459
196
  }
460
- // status 读不出(奇形记录)/ 还在跑 / park / 没想过的新词 ⇒ 都不算释放,继续等到点(保守方向:
461
- // 宁可不重发)。判据见 {@link CLAIM_RELEASED_STATES}。
462
197
  if (status !== null && CLAIM_RELEASED_STATES.includes(status)) {
463
198
  return { released: true, waitedMs: waited(), aborted: false, lastStatus: status, lastHolder: holder };
464
199
  }
465
200
  }
466
201
  }
467
- /**
468
- * 有界问一次「引擎侧这个会话名下还有几行**属主**待决审批」(`deps.listOwnedPendingApprovals`)。
469
- *
470
- * 返回 `number` = 引擎**答了**这个数;返回 `null` = **没答上来**(抛错 / 窗尽 / 调用方中止)——
471
- * 两者是完全不同的事实,调用方各自决定怎么用(本文件两个调用点都把 `null` 当「不知道」保守处置)。
472
- *
473
- * 🔴 有界且可回收(#244 F1 复审 [high]+[medium]×2 的成文形):probe 恒不落地不许把整棵分诊树挂死;
474
- * 合流 signal **真交到 probe 上**(掐的是底层请求,不是只掐调用方的 await);竞速败方在 finally 里
475
- * 统一 abort(快成功时那只 4s 普通计时器不清,`-p` 车道进程会被它多拖活一窗)。
476
- * 🔴 单源:两个调用点(假死锁防御 / L-93 开枪前复证)共用这一只 —— 各写一份必漂,而这两处漂的
477
- * 后果分别是「Esc 之后接着弹卡」与「拿一次没答上来的读当成前提成立」。
478
- */
479
202
  async function readOwnedPendingCount(read, deps) {
480
203
  const recheckCtl = new AbortController();
481
204
  const onCallerAbort = () => {
@@ -487,47 +210,28 @@ async function readOwnedPendingCount(read, deps) {
487
210
  read({ signal: recheckCtl.signal }),
488
211
  claimPollSleep(OWNED_PENDING_RECHECK_TIMEOUT_MS, recheckCtl.signal).then(() => null),
489
212
  ]);
490
- // 已中止(进门前/竞速期间)一律不采信这个数 —— Esc 之后拿它驱动任何动作都是「用户说停还在动」。
491
213
  if (deps?.signal?.aborted === true)
492
214
  return null;
493
215
  return typeof count === 'number' ? count : null;
494
216
  }
495
217
  catch {
496
- return null; // 读失败 = 「不知道」,不是「零」
218
+ return null;
497
219
  }
498
220
  finally {
499
221
  deps?.signal?.removeEventListener('abort', onCallerAbort);
500
222
  recheckCtl.abort(new Error('owned-pending recheck settled'));
501
223
  }
502
224
  }
503
- /**
504
- * 自愈一次:定门(kind 优先 → status 表回退)→ 按门类重开对应的卡 → 回报结局。**绝不抛**
505
- * (turn 收尾路径)。kind 在场时不再回查 runs.get —— wire 已指名门身份,省一次往返。
506
- */
507
225
  export async function attemptActiveRunSelfHeal(signal, runs, deps) {
508
- // 🔴 最先问的一句:用户手上有没有一张能按的卡?有就把决定权还给他(见 decision-pending 注)。
509
226
  let pending = false;
510
227
  try {
511
228
  pending = deps?.hasPendingDecision?.() === true;
512
229
  }
513
230
  catch {
514
- pending = false; // 读不到就当没有 —— 这一项只用来**减少**破坏性动作,不该反过来卡住自愈
231
+ pending = false;
515
232
  }
516
- // 🔴 经函数读(waitForClaimRelease 的 isAborted 同注):`aborted` 会在 await 两侧变,直接读两次
517
- // 会被 tsc 的控制流分析把第二次窄成恒假比较(TS2367)。
518
233
  const callerAborted = () => deps?.signal?.aborted === true;
519
234
  if (pending && typeof deps?.listOwnedPendingApprovals === 'function' && !callerAborted()) {
520
- // [3892]-[3899] 假死锁防御(deps.listOwnedPendingApprovals 头注全案):镜像说有卡 ⇒ 向引擎
521
- // 复核一次属主 pending 行数。恰 0 = 镜像脏值的**正面证据**(引擎侧没有任何等这个用户的决定)
522
- // ⇒ 放行真分诊;抛错/非 0 ⇒ 保守维持(真卡在屏时 stand down 是对的)。
523
- // 🔴 复核必须**有界且可回收**(复审 #244 F1 轮一 [high] + 轮二 [medium]×2):
524
- // · probe 恒不落地(引擎挂死且读口无自带超时)不许把整棵分诊树挂死 —— 假死锁防御自己变成
525
- // 真死锁;窗尽/调用方中止 = 分不出真卡与幽灵,与抛错同臂保守 stand down;
526
- // · 合流 signal **真交到 probe 上**(超时/中止掐的是底层请求,不是只掐调用方的 await);
527
- // · 竞速败方必须收口:快成功时 4s sleep 的普通计时器不清,-p 车道进程会被它多拖活一窗
528
- // (finally 统一 abort ⇒ abortableSleep 清 timer、在飞 probe 被掐);
529
- // · 已中止(进门前/竞速期间)一律不采信 count —— Esc 之后接着放行重呈 = 用户说停还弹卡。
530
- // 这四条的实现在共享叶 {@link readOwnedPendingCount}(L-93 开枪前复证走同一只)。
531
235
  const count = await readOwnedPendingCount(deps.listOwnedPendingApprovals, deps);
532
236
  if (count === 0 && !callerAborted())
533
237
  pending = false;
@@ -542,13 +246,11 @@ export async function attemptActiveRunSelfHeal(signal, runs, deps) {
542
246
  detail: 'the engine did not name the run that holds this session',
543
247
  };
544
248
  }
545
- // ── 一级分诊:pendingGate.kind(wire 指名的门身份;缺席才落 status 表)──────────────────────
546
249
  const gateKind = signal.pendingGate?.kind ?? null;
547
250
  if (gateKind !== null && PLAN_REVIEW_GATE_KINDS.includes(gateKind))
548
251
  return planReviewArm(taskId, signal, deps);
549
252
  if (gateKind !== null && ASK_PARK_GATE_KINDS.includes(gateKind))
550
253
  return askParkArm(taskId, signal, runs, deps);
551
- // ── 状态真源两级:wire 直供 > 自己查一趟 runs.get(kind 缺席/表外 kind 的诚实陈述都要它)──
552
254
  let status = signal.activeTaskStatus;
553
255
  if (status === null) {
554
256
  if (typeof runs?.get !== 'function') {
@@ -566,71 +268,51 @@ export async function attemptActiveRunSelfHeal(signal, runs, deps) {
566
268
  if (status === null) {
567
269
  return { kind: 'state-unknown', taskId, detail: 'the engine reported no status for that run' };
568
270
  }
569
- // 表外 kind(结局④):wire 指名了一个本层不认得的门 —— 按 status 表猜臂正是 kind 优先要禁
570
- // 的动作(把 dry-run 复核门错路成 plan 卡)。不动它 + 如实说;decidePath 由文案层原样交出。
571
271
  if (gateKind !== null)
572
272
  return { kind: 'not-parked', taskId, status };
573
- // ── 二级分诊:status 表回退(kind 缺席 —— 旧引擎 / store 降级)────────────────────────────
574
273
  if (PLAN_REVIEW_STATES.includes(status))
575
274
  return planReviewArm(taskId, signal, deps);
576
275
  if (ASK_PARK_STATES.includes(status))
577
276
  return askParkArm(taskId, signal, runs, deps);
578
- // 真在跑 ⇒ 把引擎给着的两条路(steer / cancel)+ 现状做成三选卡交给用户;表外的其它状态
579
- // (含今天还不存在的)照旧「不动它 + 如实说」—— 语义不明的状态不配递把手(见 RUNNING_STATES 注)。
580
- // Inkglow-1085 P0b②:`statusFromWire` = 这个 running 是 409 终帧直供的投影(best-effort,可陈旧
581
- // ——claim/store 错时恒回送 running 的幽灵读数);出卡前 runningChoiceArm 会对它补一发 runs.get
582
- // 真查。status 本就是上面刚 get 来的(wire 缺席回退)⇒ 已新鲜,不打第二发。
583
277
  if (RUNNING_STATES.includes(status)) {
584
278
  return runningChoiceArm(taskId, status, signal, signal.activeTaskStatus !== null, runs, deps);
585
279
  }
586
280
  return { kind: 'not-parked', taskId, status };
587
281
  }
588
- /** plan_review 重开口的判决(臂与判决分开:steer 的 `queued` 回执要的是**同一份**重开语义的判决值,
589
- * 但收口成的是 running-steered 那一行,不是 park 臂的行 —— 各写一份必漂)。 */
590
282
  async function planVerdict(taskId, deps) {
591
283
  try {
592
284
  return (await deps?.reopenPlanReview?.(taskId)) ?? { reopened: false };
593
285
  }
594
286
  catch {
595
- return { reopened: false }; // 重开腿的意外 throw 不许炸 turn 收尾;按没能重开如实说
287
+ return { reopened: false };
596
288
  }
597
289
  }
598
- /** ask 重开口的判决(同上)。 */
599
290
  async function askVerdict(taskId, deps) {
600
291
  try {
601
292
  return (await deps?.reopenAskPark?.(taskId)) ?? { reopened: false };
602
293
  }
603
294
  catch {
604
- return { reopened: false }; // 同上:意外 throw 按没能重开如实说,绝不回退 cancel
295
+ return { reopened: false };
605
296
  }
606
297
  }
607
- /** plan_review 门:重开卡,把决定权还给用户。静默 cancel 在这里 = 替他 reject 掉整个 plan。 */
608
298
  async function planReviewArm(taskId, signal, deps) {
609
299
  const verdict = await planVerdict(taskId, deps);
610
300
  return reopenDelivered(verdict)
611
301
  ? { kind: 'plan-review-reopened', taskId, firstSight: verdict.firstSight === true }
612
302
  : { kind: 'plan-review-reopen-failed', taskId, decidePath: signal.pendingGate?.decidePath ?? null };
613
303
  }
614
- /** 审批/提问门:重开卡。cancel 在这里的语义 = 替用户否掉待决项,且实证是循环病根 —— 臂已退役。 */
615
304
  async function askParkArm(taskId, signal, runs, deps) {
616
305
  const verdict = await askVerdict(taskId, deps);
617
306
  if (reopenDelivered(verdict))
618
307
  return { kind: 'ask-reopened', taskId, firstSight: verdict.firstSight === true };
619
- // L-80:无卡但链已成功决断(规则直决)—— 它只证明「决断受理」,不证明「run 交出了会话」。三分:
620
- // · 有界等到 run 释放 ⇒ `ask-decided-without-card`(resending,与 running-cancelled 同一条重发腿的前提相同);
621
- // · 没释放但 run 在跑 ⇒ 走既有 running 三选卡(steer / cancel / wait),绝不盲发;
622
- // · 没释放且 run 仍 parked / 读不到 ⇒ 如实 reopen-failed(旧文案;plan 腿无此形,规则不决 plan)。
623
308
  if (verdict.reopened === false && verdict.decidedWithoutCard === true) {
624
- // 决断成功是正面事实,**不许**再落 reopen-failed(那句「could not reopen … NOT sent」会掩盖它)。
625
309
  if (typeof runs?.get !== 'function')
626
310
  return { kind: 'ask-decided-release-unknown', taskId, lastStatus: null };
627
- // r5 [high]②:真 SDK 的 RunsResource 方法依赖 `this`(与 cancel 路径 `boundGet` 同款保留接收者;裸函数会恒 TypeError
628
- // 被轮询器吞掉、10s 后错落 release-unknown)。
629
311
  const durableGet = runs.get;
630
312
  const get = (id, opts) => durableGet.call(runs, id, opts);
631
313
  const release = await waitForClaimRelease(taskId, {
632
314
  get,
633
- budgetMs: deps?.cancelReleaseWaitMs ?? CANCEL_RELEASE_WAIT_MS, // 与 cancel 释放窗同源(10s),慢恢复不被 4s 误判
315
+ budgetMs: deps?.cancelReleaseWaitMs ?? CANCEL_RELEASE_WAIT_MS,
634
316
  ...(deps?.signal !== undefined ? { signal: deps.signal } : {}),
635
317
  ...sessionOpts(deps),
636
318
  });
@@ -641,28 +323,16 @@ async function askParkArm(taskId, signal, runs, deps) {
641
323
  }
642
324
  return { kind: 'ask-decided-release-unknown', taskId, lastStatus: release.lastStatus };
643
325
  }
644
- // L-93:重开链在有界窗内**读面答了**、而属主待决行**恰 0**(引擎重启丢行 / 审批被 reap),run
645
- // 却仍 park ⇒ 卡不是「呈不出来」而是「没有东西可呈」,走陈旧 park 臂(见 {@link staleParkArm})。
646
- // 🔴 位缺席/含糊值一律不进(只认严格 `true`):这是本层允许 cancel 一条 park run 的唯一前提,
647
- // 松掉它就是把退役 cancel 臂的循环病根放回来。`decidedWithoutCard` 已在上面收口,此处再写一次
648
- // 是把「两位同时在场时谁优先」钉在判据里,不靠上面那个 return 的顺序当默契。
649
326
  if (verdict.reopened === false && verdict.decidedWithoutCard !== true && verdict.pendingRowGone === true) {
650
327
  return staleParkArm(taskId, signal, runs, deps);
651
328
  }
652
329
  return { kind: 'ask-reopen-failed', taskId, decidePath: signal.pendingGate?.decidePath ?? null };
653
330
  }
654
- /**
655
- * 有界打一发 `runs.get` 读真态(lease 带截止 + 合流调用方中止;404 单列,其余错误一律「没答上来」)。
656
- * 🔴 单源:running 存活对账腿、陈旧 park 臂的**卡前**与**卡后**三处共用这一只 —— 各写一份必漂,
657
- * 而漂的后果是其中一处忘了 404 / 本发截止 / 奇形记录三种缺席的处置。
658
- * 🔴 接收者必保:写成 `runs.get(...)` 方法形(把方法摘下来裸调 = 真 SDK 上当场 TypeError)。
659
- */
660
331
  async function probeRunStatusOnce(taskId, durable, durableGet, deps) {
661
332
  let status = null;
662
333
  let ghost = false;
663
334
  const lease = claimProbeLease(deps?.signal, RUNNING_LIVENESS_RECHECK_TIMEOUT_MS);
664
335
  try {
665
- // 保接收者(`durableGet.call(durable, …)`):摘下来裸调在真 SDK 上当场 TypeError。
666
336
  status = readStatus(await durableGet.call(durable, taskId, { signal: lease.signal, ...sessionOpts(deps) }));
667
337
  }
668
338
  catch (e) {
@@ -674,110 +344,57 @@ async function probeRunStatusOnce(taskId, durable, durableGet, deps) {
674
344
  }
675
345
  return { status, ghost };
676
346
  }
677
- /**
678
- * L-93 陈旧 park 臂(0.53.0):run 仍停在 park 态,而它在等的那张审批**已经不在引擎上**
679
- * (宿主经 {@link ReopenCardVerdict} 的 `pendingRowGone` 证出「读面答了 + 零属主待决行」)。
680
- *
681
- * ── 为什么这一形可以给 cancel,而退役的那条臂不行 ─────────────────────────────────────────────
682
- * 退役 cancel 臂的循环病根 = 「**待决项仍在**」时 cancel 的语义是替用户 deny 掉它 ⇒ 模型重试 ⇒
683
- * 新 run 停在同一道门上 ⇒ 再 park。本臂的前提恰恰是那个待决项**已经不存在**:没有任何东西可被
684
- * 「否掉」,那条循环在结构上不成立。所以边界写在**判定**上,不写在文案上 ——
685
- * ① `pendingRowGone` 缺席/含糊值 ⇒ 本臂根本进不来(调用点只认严格 `true`);
686
- * ② 进来之后仍要用 `runs.get` **一手复核**(宿主的一句话不足以驱动破坏性动作);
687
- * ③ 复核读回的必须是**审批 park 词**({@link ASK_PARK_STATES});plan park(`needs_review`)交给
688
- * plan 重开臂 —— 对它 cancel = 替用户丢掉整个 plan,是另一条禁区;
689
- * ④ 两选卡上用户必须**逐字**选 `cancel` 才动那一枪(Esc/空答/读不出一律 wait)。
690
- * 任何一条不成立 ⇒ 退回既有 `ask-reopen-failed`(老宿主与不确定情形逐字节零行为差)。
691
- */
692
347
  async function staleParkArm(taskId, busy, runs, deps) {
693
- /** 本臂的**保守收口**:与修前逐字同一行(卡呈不出来/读不出真态/不确定 ⇒ 零动作 + 如实说)。 */
694
348
  const reopenFailed = {
695
349
  kind: 'ask-reopen-failed',
696
350
  taskId,
697
351
  decidePath: busy.pendingGate?.decidePath ?? null,
698
352
  };
699
- // 读口缺席 ⇒ 一手复核做不成 ⇒ 不猜(宿主的一句话不驱动破坏性动作)。
700
- // 🔴 **只取来判在场,绝不裸调**(runningChoiceArm R7 同注):真 SDK 的 `RunsResource` 方法体走
701
- // `this.t.request(...)`,摘下来单独调当场 TypeError —— 下面的真调用一律写成 `runs.get(...)` 方法形,
702
- // 交给 cancel 腿的那一份也用它保接收者的包装。
703
353
  if (runs === undefined || typeof runs.get !== 'function')
704
354
  return reopenFailed;
705
355
  const durableGet = runs.get;
706
- // 🔴 经函数读 aborted(waitForClaimRelease.isAborted 同注):它在 await 两侧会变,直接读两次会被
707
- // tsc 控制流分析把第二次窄成恒假比较(TS2367)。
708
356
  const callerAborted = () => deps?.signal?.aborted === true;
709
- // 进门前已中止 ⇒ 一发出站都不打(Esc 之后还去问引擎 = 用户说停还在动)。
710
357
  if (callerAborted())
711
358
  return reopenFailed;
712
- // ── 一手复核(与 running 存活对账腿同款 lease/abort 处理)────────────────────────────────────
713
359
  const first = await probeRunStatusOnce(taskId, runs, durableGet, deps);
714
- // 幽灵 claim:引擎一边说这个会话被这条 run 占着、一边查无此 run(与 running-not-found 同律 ——
715
- // 404 是「不存在」∪「不是你的」的不可分辨并集,所以只说已证的事实,绝不盲发那条消息)。
716
360
  if (first.ghost)
717
361
  return { kind: 'ask-run-not-found', taskId };
718
- // 复核期间用户中止 ⇒ 后续一切动作(派臂/出卡/登记/cancel)都不做。
719
362
  if (callerAborted())
720
363
  return reopenFailed;
721
- // 读不出(网络失败 / 本发截止 / 奇形记录)⇒ 「读不到」≠「行不在」⇒ 保守收口,不猜。
722
364
  const fresh = first.status;
723
365
  if (fresh === null)
724
366
  return reopenFailed;
725
- // 真态是「在跑」⇒ 交给既有三选卡臂(statusFromWire=false:这一发就是新鲜读数,不打第二发)。
726
367
  if (RUNNING_STATES.includes(fresh))
727
368
  return runningChoiceArm(taskId, fresh, busy, false, runs, deps);
728
- // 🔴 plan park ⇒ 交给 plan 重开臂:对它 cancel 是替用户把整个 plan 丢掉(禁区),而 plan 卡
729
- // 本身是能重开的 —— 递一条更好的路,而不是递一把更危险的把手。
730
369
  if (PLAN_REVIEW_STATES.includes(fresh))
731
370
  return planReviewArm(taskId, busy, deps);
732
- // 表外的状态词(含今天还不存在的)⇒ 不认得就不给把手,保守收口。
733
371
  if (!ASK_PARK_STATES.includes(fresh))
734
372
  return reopenFailed;
735
- // ── 两选卡:停掉它 / 什么都不做 ──────────────────────────────────────────────────────────────
736
373
  const offer = deps?.offerStaleParkChoice;
737
374
  if (typeof offer !== 'function')
738
- return reopenFailed; // 老宿主:整臂不走,行为逐字节不变
739
- // 供给位 = ①cancel 动词在场、②有能确认释放的 `get`(上面的一手复核已证在场)、③有**开枪前
740
- // 再证一次前提**的属主待决行读口(`deps.listOwnedPendingApprovals`)。
741
- // 🔴 第③条是 异源对抗复审 R1 [high] 的采纳(见下面开枪前那一段):`pendingRowGone` 是**读那一刻**的
742
- // 事实,而这张卡是人在看、可以看任意久;没有复证口就没法在开枪前把那个前提再证一次,
743
- // 于是「停掉它」这条路在本层就**兑现不了它承诺的安全性** ⇒ 按供给位纪律整卡不呈(退回旧结局),
744
- // 而不是先呈卡再在开枪时才发现证不了。宿主要拿到这张卡,就得把这个读口一起注入(见 §16)。
745
- // 两条路只剩「什么都不做」时整卡不呈 —— 只有一个空动作的卡是纯噪音(与三选卡 G8②b 同律)。
375
+ return reopenFailed;
746
376
  const recheckOwnedPending = deps?.listOwnedPendingApprovals;
747
377
  const canCancel = typeof runs.cancel === 'function' && typeof recheckOwnedPending === 'function';
748
- // 第二段是给 tsc 的收窄(`canCancel` 是布尔,narrow 不了 `recheckOwnedPending` 这只可选函数),
749
- // 判据本身只有 `canCancel` 一条。
750
378
  if (!canCancel || typeof recheckOwnedPending !== 'function')
751
379
  return reopenFailed;
752
380
  const declineKey = staleParkDeclineKey(deps?.sessionId, taskId);
753
- // 同 (sessionKey,taskId) 已呈过且用户选了「什么都不做」⇒ 不再整卡重弹,给判别位让端渲一行。
754
381
  if (runningChoiceDeclined.has(declineKey)) {
755
382
  return { kind: 'stale-park-wait', taskId, status: fresh, alreadyOffered: true };
756
383
  }
757
384
  let choice = null;
758
385
  try {
759
- // 🔴 fail-closed:**只有逐字 `'cancel'`** 才武装那一枪;Esc/空答/表外词一律收口成 wait。
760
386
  choice = (await offer({ taskId, status: fresh, canCancel })) === 'cancel' ? 'cancel' : 'wait';
761
387
  }
762
388
  catch {
763
- choice = null; // 呈卡腿出意外 = 用户没看到卡、更没做决定 ⇒ 退回旧结局(绝不掉进破坏性分支)
389
+ choice = null;
764
390
  }
765
391
  if (choice === null)
766
392
  return reopenFailed;
767
393
  if (choice === 'wait') {
768
- // 呈卡腿抛错那次**不**登记(用户没看到卡,压掉后续等于把没呈过的卡当已呈)—— 见上面 catch。
769
394
  rememberDeclinedOffer(declineKey);
770
395
  return { kind: 'stale-park-wait', taskId, status: fresh };
771
396
  }
772
- // 用户重新表了态 ⇒ 清登记(与三选卡 steer/cancel 同律)。
773
397
  runningChoiceDeclined.delete(declineKey);
774
- // ── 🔴 卡后**复证 run 真态**(异源对抗复审 R3 [high] 采纳)────────────────────────────────────────────
775
- // 上面那一发 `runs.get` 打在**呈卡之前**,而卡是人在看、可以停留任意久 —— 这中间那条 run 完全
776
- // 可能已经**恢复运行**(审批在别处被决断 / 会话重新附着 / 引擎自愈)。沿用旧卡上那句 cancel 去停
777
- // 一条**正在干活**的 run,等于拿「清理一个死结」的授权丢掉正在进行的工作,而用户按下去时屏幕上
778
- // 写的前提早就不成立了。所以先复证状态仍是审批 park 词,再谈开枪:
779
- // · 仍 park ⇒ 继续;· 404 ⇒ 幽灵 claim 如实说;· 变成别的状态 / 读不出 / 已中止 ⇒ 一律
780
- // fail-closed 退回旧结局(下一条消息重新走整棵分诊树,拿到与那时真态相符的那张卡)。
781
398
  if (callerAborted())
782
399
  return reopenFailed;
783
400
  const afterCard = await probeRunStatusOnce(taskId, runs, durableGet, deps);
@@ -787,26 +404,9 @@ async function staleParkArm(taskId, busy, runs, deps) {
787
404
  return reopenFailed;
788
405
  if (afterCard.status === null || !ASK_PARK_STATES.includes(afterCard.status))
789
406
  return reopenFailed;
790
- // ── 🔴 开枪前**再证一次前提**(异源对抗复审 R1 [high] 采纳)──────────────────────────────────────────
791
- // `pendingRowGone` 是宿主**读那一刻**的事实,而这张卡是人在看、可以看任意久;这中间待决行完全
792
- // 可能重新出现(store 恢复 / 会话重新附着),宿主也可能把「读失败 / 找不到能用的那一行」误报成
793
- // gone。而上面那一发 `runs.get` 只证得了「它仍 parked」—— **审批还在**与**审批已丢**两种情形读回
794
- // 的都是 `suspended`,那一发从来就证不了这个前提。代价不对称:cancel 一条**其实还能被决断**的
795
- // run = 用户在一句假前提(「那张审批已经不在了」)下把整轮工作丢掉,而他以为自己只是在清理一个
796
- // 死结。所以在**离 runs.cancel 最近的那一刻**再问一次属主待决行数:
797
- // · 恰 0(引擎答了)⇒ 前提仍成立,开枪;
798
- // · 非 0 / 抛错 / 窗尽 / 调用方中止 / 读不出 ⇒ **一律不开枪**(fail-closed),退回旧结局
799
- // —— 「不知道」和「行又回来了」都不构成销毁一条 run 的授权。
800
- // 🔴 **在册边界(P-44,异源对抗复审 R2 [high] 如实登记)**:这是「查了再做」,**不是原子条件取消**。
801
- // 复证与那一枪之间仍有毫秒级窗口,行恰在此间恢复的话那一枪照样落下去。客户端关不死它 ——
802
- // 真正的关法是 **server 读写面**的条件取消(core [7006] 定谳:core 无席——`TaskSpec.signal` 无条件中止、
803
- // `claimTerminal` 是 ask 行 CAS 非 run lease;server [7007] 认领:cancel 已是两臂状态 CAS,7.73.0 契约明写 + 可选
804
- // 版本前置,S-122 车),那是 wire 能力不是壳能自造的语义。这里能做的是把窗口从「人看卡的任意长时间」压到最小,
805
- // 并把剩余风险登记在册(docs/INTEGRATION-CLIENTS.md §7b P-44),不假装它不存在。
806
407
  const stillGone = await readOwnedPendingCount(recheckOwnedPending, deps);
807
408
  if (stillGone !== 0)
808
409
  return reopenFailed;
809
- // 🔴 **复用**同一条 cancel + 有界释放确认腿(不复制):失败分类与释放判据必须与三选卡同源。
810
410
  const verdict = await cancelAndConfirmRelease(taskId, runs, durableGet, deps);
811
411
  if (verdict.outcome === 'released')
812
412
  return { kind: 'stale-park-cancelled', taskId };
@@ -821,19 +421,6 @@ async function staleParkArm(taskId, busy, runs, deps) {
821
421
  confirmedHeld: verdict.confirmedHeld,
822
422
  };
823
423
  }
824
- /**
825
- * cancel 恰一枪 + 有界等那条 run 交出会话。
826
- *
827
- * 🔴 cancel 的失败分两类(二次评审 R4 [high]),与 steer 同一把尺:
828
- * · `rejected`(4xx)—— 服务端答了且明确拒 ⇒ 那一枪确定没生效,立即如实说,不必再等确认腿;
829
- * · `unknown`(5xx / 连接在回执路上断了)—— 请求**可能已经被受理并在异步执行**。此时直接收口成
830
- * 「取消失败,那条 run 还占着会话」是对用户下一个证不出的断言 —— 而这条腿手上恰恰有一个能
831
- * 回答这个问题的读口(`get`)。所以照走有界确认腿:真交出来了就算取消成了,确认不到才如实说
832
- * 「没能确认」。判据锚在**决定量**(会话有没有被交出来)上,不锚它的一个前置条件(那一枪的
833
- * 回执长什么样)。
834
- * 🔴 接收者必保:真 SDK 的 `RunsResource` 方法体走 `this.t.request(...)`,把方法摘下来裸调当场
835
- * TypeError —— 所以 cancel 写成方法形 `durable.cancel?.(…)`,交给等待器的 `get` 用保接收者的包装。
836
- */
837
424
  async function cancelAndConfirmRelease(taskId, durable, durableGet, deps) {
838
425
  const boundGet = (id, opts) => durableGet.call(durable, id, opts);
839
426
  let cancelFailure = null;
@@ -841,33 +428,26 @@ async function cancelAndConfirmRelease(taskId, durable, durableGet, deps) {
841
428
  const cancelStartedAt = Date.now();
842
429
  const cancelLease = claimProbeLease(deps?.signal, cancelWaitMs);
843
430
  try {
844
- // 🔴 这一枪也必须**带中止口与截止**(二次评审 R5 [high]):`cancel` 的 promise 不落地,后面那条
845
- // 有界确认腿就根本起不来 —— 用户按了 Esc 既停不掉等待也停不掉出站请求,整条腿零结局。有界性
846
- // 的执行手段只有一个:把 signal 真交到请求上。
847
431
  await durable.cancel?.(taskId, { signal: cancelLease.signal, ...sessionOpts(deps) });
848
432
  }
849
433
  catch (e) {
850
- // 用户中止 ⇒ 当拍收口(绝不接着跑一个满窗的确认腿:「UI 说停了、后台还在问」正是禁区)。
851
434
  if (deps?.signal?.aborted === true) {
852
435
  return { outcome: 'timeout', waitedMs: Date.now() - cancelStartedAt, aborted: true, confirmedHeld: false };
853
436
  }
854
437
  const delivery = atMostOnceFailureClass(e);
855
438
  if (delivery === 'rejected')
856
439
  return { outcome: 'failed', detail: describeFailure(e), delivery };
857
- // 本枪的截止到点(请求挂死被掐)同样是**送达未知** —— 服务端可能已经受理,照走确认腿。
858
440
  cancelFailure = { detail: describeFailure(e), delivery };
859
441
  }
860
442
  finally {
861
443
  cancelLease.release();
862
444
  }
863
- // 202 = 「收下了」不是「停了」⇒ 必须真等到那条 run 交出会话,否则重发只会再撞一次 409。
864
445
  const verdict = await waitForClaimRelease(taskId, {
865
446
  get: boundGet,
866
447
  budgetMs: cancelWaitMs,
867
448
  ...(deps?.signal !== undefined ? { signal: deps.signal } : {}),
868
449
  ...sessionOpts(deps),
869
450
  });
870
- // 会话真被交出来了 ⇒ 就是取消成功,哪怕那一枪的回执丢在路上(决定量说了算)。
871
451
  if (verdict.released)
872
452
  return { outcome: 'released' };
873
453
  if (cancelFailure !== null)
@@ -876,70 +456,29 @@ async function cancelAndConfirmRelease(taskId, durable, durableGet, deps) {
876
456
  outcome: 'timeout',
877
457
  waitedMs: verdict.waitedMs,
878
458
  aborted: verdict.aborted,
879
- // 🔴 0.68.0 / L-230:**两条持锁证据取并**,直接证据优先 ——
880
- // ① `heldBy` 读出 `'held'` = 引擎**直说**那条 run 还占着(server 7.73.0 S-122 P-45 读面);
881
- // ② 老引擎不发那一位(`'unknown'`)⇒ 回落既有的按状态词推断({@link CLAIM_HELD_STATES})。
882
- // 🔴 `'released'` 在这里到不了(released 的判决在上面就 return 了),`'unknown'` 不算证据 ——
883
- // 两条都答不出时仍然收口成 `false` = 「确认不了」,文案绝不说「它还占着」。
884
459
  confirmedHeld: verdict.lastHolder === 'held' ||
885
460
  (verdict.lastStatus !== null && CLAIM_HELD_STATES.includes(verdict.lastStatus)),
886
461
  };
887
462
  }
888
- /**
889
- * `running` 形:呈三选卡 → 按用户的决定动手。零决定 = 零动作(`not-parked` 现状行)。
890
- *
891
- * 🔴 三条纪律在这一个函数里同时承重:
892
- * ① **cancel 只在用户显式选了它时打**,一枪(反钉:客户端自己一枪都不许打);
893
- * ② **steer 只打一次**,失败不重试也不回退 cancel(非幂等,契约逐字);
894
- * ③ 选项按**供给**渲(verb 缺席 = 那条路兑现不了 = 整个不渲),cancel 还额外要 `get` ——
895
- * 没有它就确认不了 claim 真的释放,「取消并重发」就成了猜。
896
- */
897
463
  async function runningChoiceArm(taskId, status, busy, statusFromWire, runs, deps) {
898
464
  const notParked = { kind: 'not-parked', taskId, status };
899
465
  const offer = deps?.offerRunningChoice;
900
- // CC-44(0.72.8):系统注入件不问人 —— 不需要呈卡口;canSteer 不成立时零卡零动作。
901
466
  const injected = deps?.submissionOrigin === 'injected';
902
467
  if (typeof offer !== 'function' && !injected)
903
468
  return notParked;
904
469
  const text = typeof deps?.deniedMessage === 'string' ? deps.deniedMessage : '';
905
- // 🔴 **只取来判在场,绝不裸调**(二次评审 R7 [high]):真 SDK 的 `RunsResource` 方法体走
906
- // `this.t.request(...)`,把方法从对象上摘下来单独调会当场 TypeError —— 后果是 steer 恒报「送达
907
- // 未知」、cancel 先失败再拿一个同样失了接收者的 get 轮询到预算耗尽,三选卡的两条路在生产上**全废**
908
- // (而箭头函数 mock 恰好把这一形完全遮住)。下面所有真调用一律写成 `runs.xxx(...)` 的方法形,
909
- // 交给别人的那一发也用**保接收者的包装**。
910
470
  const canSteer = typeof runs?.steer === 'function' && text.length > 0;
911
471
  const canCancel = typeof runs?.cancel === 'function' && typeof runs?.get === 'function';
912
472
  if (!canSteer && !canCancel)
913
473
  return notParked;
914
474
  if (injected && !canSteer)
915
475
  return notParked;
916
- // ── 存活对账腿(Inkglow-1085 P0b②):出卡前对 wire 直供的 running 补一发 runs.get 真查 ────────
917
- // 409 终帧的 `activeTaskStatus` 是引擎 claim/store 的 best-effort 投影 —— claim 指着一条早已终结
918
- // 甚至已不存在的 run 时,它会**每次都**回送同一个错的 running(幽灵),用户按 wire 读数拿到的
919
- // steer/cancel 两条路全是死路。
920
- // 🔴 [4664] server 定谳(2026-08-19)把「别把 409 running 当绝对真值」写成了四条成文的洞:
921
- // ①park 态不在 reapStale 射程;②`updated_at` 误杀窗;③409 与 poll 口径短暂相左;
922
- // ④LOCAL(file)车道无周期自愈腿。SQL 车道死 run 的自愈上界 ≈180s(心跳 30s +
923
- // REAP_RUN_STALE_SEC 120 + tick 60)——**LOCAL 车道无上界,勿假设 180s**,所以本腿不做
924
- // 「等它自愈」的时间性假设,只做一手读面复核。
925
- // 真源两级里它是二手货,出卡(把把手递给人)前用一手读面复核一次:
926
- // · get 读回非 running 的**已知**词 ⇒ wire 读数过期,按真态走(park 词进对应重开臂,其余落
927
- // not-parked 如实说)—— 与「kind 缺席 ⇒ status 表回退」同一张表,不新铸分臂;
928
- // · get 撞 404 ⇒ 幽灵 claim(`running-not-found` 判别位;为什么不算「已释放」见该臂注);
929
- // · get 网络失败 / 本发截止 / 奇形记录 ⇒ **读不到 ≠ 非 running**,保守维持出卡 —— 误关卡会把
930
- // steer/cancel 两条真把手一起藏掉;
931
- // · status 本就是本层刚 runs.get 查来的(statusFromWire=false)⇒ 已新鲜,不打第二发。
932
- // 🔴 经函数读 aborted(waitForClaimRelease.isAborted 同注):它在 await 两侧会变,直接读两次会被
933
- // tsc 控制流分析把第二次窄成恒假比较(TS2367)。
934
476
  const probeCallerAborted = () => deps?.signal?.aborted === true;
935
477
  if (statusFromWire && typeof runs?.get === 'function' && !probeCallerAborted()) {
936
- // 与陈旧 park 臂共用同一只读叶(同形单源,见 {@link probeRunStatusOnce})。
937
478
  const probe = await probeRunStatusOnce(taskId, runs, runs.get, deps);
938
479
  const fresh = probe.status;
939
480
  if (probe.ghost)
940
481
  return { kind: 'running-not-found', taskId };
941
- // (对抗复审 [medium] 采纳)对账期间用户中止 ⇒ 后续一切动作(派臂/出卡/登记)都不做 ——
942
- // Esc 之后还弹卡/重开 = 用户说停还在动。零动作现状行收口(与呈卡腿抛错同一条诚实收口)。
943
482
  if (probeCallerAborted())
944
483
  return notParked;
945
484
  if (fresh !== null && !RUNNING_STATES.includes(fresh)) {
@@ -947,16 +486,11 @@ async function runningChoiceArm(taskId, status, busy, statusFromWire, runs, deps
947
486
  return planReviewArm(taskId, busy, deps);
948
487
  if (ASK_PARK_STATES.includes(fresh))
949
488
  return askParkArm(taskId, busy, runs, deps);
950
- // (对抗复审 [high] 采纳)终态词 ⇒ 专属结局:not-parked 的「wait for it to finish」对一条
951
- // 已终结的 run 是永远等不到的假话。表外的未知词仍落 not-parked 如实说(不替引擎断言终结)。
952
489
  if (CLAIM_RELEASED_STATES.includes(fresh))
953
490
  return { kind: 'running-settled', taskId, status: fresh };
954
491
  return { kind: 'not-parked', taskId, status: fresh };
955
492
  }
956
- // 确认仍 running(或读不到)⇒ 按原判继续出卡。
957
493
  }
958
- // ── 已呈现登记(Inkglow-1085 P0b①):同 (sessionKey,taskId) 用户选过「Do nothing」⇒ 不再整卡
959
- // 重弹,给 `alreadyOffered` 判别位让端降级渲一行(登记语义与清口见 runningChoiceDeclined 顶注)。
960
494
  const declineKey = runningChoiceDeclineKey(deps?.sessionId, taskId);
961
495
  if (!injected && runningChoiceDeclined.has(declineKey)) {
962
496
  return { kind: 'not-parked', taskId, status, alreadyOffered: true };
@@ -964,9 +498,6 @@ async function runningChoiceArm(taskId, status, busy, statusFromWire, runs, deps
964
498
  let choice = 'wait';
965
499
  let offerDelivered = false;
966
500
  if (injected) {
967
- // CC-44:系统件不问人 —— canSteer 已在上面证过(动词在场 ∧ 有正文)。cancel 永不自动打(反钉纪律不变)。
968
- // 🔴 CC-47(test 0.72.8 验收 #56 ②):用户已按 Esc(`deps.signal` 已 abort)⇒ **不发**这枪不可撤回的 steer,零动作现状行
969
- // 收口 —— 用户说停之后系统件还往 run 里注入 = 「UI 显示已停、后台还在动」那一形。
970
501
  if (probeCallerAborted())
971
502
  return notParked;
972
503
  choice = 'steer';
@@ -977,16 +508,11 @@ async function runningChoiceArm(taskId, status, busy, statusFromWire, runs, deps
977
508
  offerDelivered = true;
978
509
  }
979
510
  catch {
980
- choice = 'wait'; // 呈卡腿出意外 = 用户没做决定 ⇒ 零动作(绝不掉进破坏性分支)
511
+ choice = 'wait';
981
512
  }
982
513
  }
983
- // wait = 用户看过卡并选择留着它(呈现层把 Esc/空答也收口成 wait —— 同样是「这次不动它」的表态);
984
- // steer/cancel = 用户重新表了态 ⇒ 清登记。呈卡腿抛错那次**不**登记(用户没看到卡)。
985
- // 注入形不碰这张表(它记的是人的决定)。
986
514
  if (!injected) {
987
515
  if (choice === 'wait') {
988
- // (对抗复审 [medium] 轻形采纳)FIFO 有界:长命桌面/多会话宿主不许无界长住(叶
989
- // {@link rememberDeclinedOffer},与 L-93 两选卡共用同一条上界)。
990
516
  if (offerDelivered)
991
517
  rememberDeclinedOffer(declineKey);
992
518
  }
@@ -996,19 +522,9 @@ async function runningChoiceArm(taskId, status, busy, statusFromWire, runs, deps
996
522
  }
997
523
  if (choice === 'steer' && canSteer && runs?.steer !== undefined) {
998
524
  try {
999
- // 🔴 正文原样送(untrusted DATA,server 侧围栏);恰一次,绝不重试。方法形调用保接收者。
1000
525
  const receipt = await runs.steer(taskId, { text }, sessionOpts(deps));
1001
526
  const delivery = readSteerDelivery(receipt);
1002
527
  const receiptStatus = readSteerReceiptStatus(receipt);
1003
- // 🔴 `queued` 是 wire 在**纠正分诊输入**(SDK `SteerReceipt` 头注逐字:run is durably SUSPENDED,
1004
- // 消息被 PARKED 在它的待决 checkpoint 上,`status` = 服务端 park 前读到的那个行词 —— 契约写死
1005
- // 只有两种:`"suspended"` OR `"needs_review"`,后者正是 server 7.16.0 起 plan-review park 的回声)。
1006
- // 进这一臂的前提是 status 读作 running —— 那份读数已经过期。此刻真正的出路是**把那张门卡重开**:
1007
- // 门没被答,run 就不 resume,排进去的消息也就永远注入不进去。
1008
- // 🔴 这里按 status 表分臂**不违反**「kind 优先」:回执上**根本没有 gate kind 位**(SteerReceipt
1009
- // 只有 taskId/status/messageId/priority/note),所以这就是那条成文的「kind 缺席 ⇒ status 表回退」
1010
- // 路径本身,不是拿 status 去压一个在场的 kind。契约把那两个词分别绑死在两个 park 族上,回退表
1011
- // 与它逐词同形。未知 park 词 ⇒ 两臂都不进(不拿没想过的状态驱动已知决断 API)。
1012
528
  if (delivery === 'queued') {
1013
529
  const verdict = receiptStatus !== null && PLAN_REVIEW_STATES.includes(receiptStatus)
1014
530
  ? await planVerdict(taskId, deps)
@@ -1020,14 +536,10 @@ async function runningChoiceArm(taskId, status, busy, statusFromWire, runs, deps
1020
536
  return { kind: 'running-steered', taskId, delivery, status: receiptStatus, reopened: null };
1021
537
  }
1022
538
  catch (e) {
1023
- // 🔴 两类失败必须分开报(见 running-steer-failed 注):明确拒收才可以说「没送到、请重发」。
1024
539
  return { kind: 'running-steer-failed', taskId, detail: describeFailure(e), delivery: atMostOnceFailureClass(e) };
1025
540
  }
1026
541
  }
1027
542
  if (choice === 'cancel' && canCancel && runs?.cancel !== undefined && runs.get !== undefined) {
1028
- // 🔴 **复用**共享的 cancel + 有界释放确认腿(见 {@link cancelAndConfirmRelease});本臂只做
1029
- // 「判决 → 自己这一族的结局名」的映射。L-93 的两选卡走同一条腿 —— 复制第二份的代价是两条腿
1030
- // 的失败分类与释放判据各自漂,而它们说的是同一件事。
1031
543
  const verdict = await cancelAndConfirmRelease(taskId, runs, runs.get, deps);
1032
544
  if (verdict.outcome === 'released')
1033
545
  return { kind: 'running-cancelled', taskId };
@@ -1042,27 +554,14 @@ async function runningChoiceArm(taskId, status, busy, statusFromWire, runs, deps
1042
554
  confirmedHeld: verdict.confirmedHeld,
1043
555
  };
1044
556
  }
1045
- // 用户选了 ③ / 卡没答上 / 选了一条本轮不可兑现的路 ⇒ 零动作,如实转述现状。
1046
557
  return notParked;
1047
558
  }
1048
- /** cli 的唯一真出路(`/clear` = 新会话 id ⇒ 引擎侧全新 session ⇒ 不受旧 claim 影响)。
1049
- * 导出(#244 F1):宿主的 resume 冷启动对账腿等失败半场要说**同一句**真出路 —— 各写一份必漂
1050
- * (一句「按某个键」而那个键没注册,正是本文件头在骂的形)。覆盖语义照旧走
1051
- * {@link ActiveRunSelfHealCopy.wayOut},本常量只是默认串的单源。 */
1052
559
  export const INTERACTIVE_WAY_OUT = 'run /clear to keep working in a fresh session';
1053
560
  const DEFAULT_WAY_OUT = INTERACTIVE_WAY_OUT;
1054
561
  const DEFAULT_FRESH_SESSION = 'start a new session (drop --resume/--continue)';
1055
- /**
1056
- * 「在等哪一种决定」的人话 —— **只由 wire 给的 `pendingGate.kind` 铸**。引擎对未知 gate 种类
1057
- * 自己返 null,这里照实说「一个决定」,绝不按 kind 猜一张表(猜错 = 又一句「按了没反应」)。
1058
- */
1059
562
  function gateKindPhrase(kind) {
1060
563
  return kind ? `a ${kind} decision` : 'a decision';
1061
564
  }
1062
- /**
1063
- * 409 体材料 → 「怎么解」的那一句。🔴 只在 wire 真给了 `pendingGate.decidePath` 时才存在;
1064
- * 材料缺席 = 空串,由基句自己承担 —— 绝不自造一条路由。
1065
- */
1066
565
  function decidePathClause(signal) {
1067
566
  const decidePath = signal?.pendingGate?.decidePath;
1068
567
  if (!decidePath)
@@ -1070,23 +569,11 @@ function decidePathClause(signal) {
1070
569
  return (` The engine says that run is waiting on ${gateKindPhrase(signal?.pendingGate?.kind)} and that it is` +
1071
570
  ` decided at POST ${decidePath} — deciding it releases the session.`);
1072
571
  }
1073
- /**
1074
- * 出身归因(`pendingGate.governanceForced`,server ≥7.13 / SDK 6.15.0 同窗上 409 体)。
1075
- * 治理强制的门不是用户自己配的规则 —— 「去 settings 里把它关掉」在这类门上是假出路;说清出身,
1076
- * 用户才知道该找谁。🔴 只在位真为 true 时存在;缺席一个字都不说(缺席 ≠「这不是治理门」,
1077
- * 渲一句否定 = 把证不出的断言当事实)。
1078
- */
1079
572
  function governanceOriginClause(signal) {
1080
573
  return signal?.pendingGate?.governanceForced === true
1081
574
  ? ' That gate is enforced by this deployment\'s governance policy, not by a permission rule you set.'
1082
575
  : '';
1083
576
  }
1084
- /**
1085
- * 活性证据(Inkglow-1085 P0b,[4664]:409 体内 `msSinceLastActivity` 是唯一可消费的活性证据位)。
1086
- * 🔴 只在位真在场时说话(缺席一个字都不说 —— 缺席 = 无法证明,不是「没有活动」);它是**展示**
1087
- * 判别,不改变任何动作。只挂在 running 形的 not-parked 行上:那正是「它到底还活着吗」这个问题
1088
- * 被问出来的地方。
1089
- */
1090
577
  function lastActivityClause(signal) {
1091
578
  const ms = signal?.msSinceLastActivity;
1092
579
  if (typeof ms !== 'number' || !Number.isFinite(ms) || ms < 0)
@@ -1094,62 +581,25 @@ function lastActivityClause(signal) {
1094
581
  const human = ms < 120_000 ? `${String(Math.max(1, Math.round(ms / 1000)))}s` : `${String(Math.round(ms / 60_000))}m`;
1095
582
  return ` The engine last recorded activity on that run ${human} ago.`;
1096
583
  }
1097
- /**
1098
- * 每种结局的上屏整行。**每一句出路都真的接了线**;没有真出路的分支就直说「这个会话暂时无法
1099
- * 继续」。重开成功那条也要上屏 —— 卡是替用户重新打开的,他得知道去答哪张、答完做什么。
1100
- * wire 真到得了端的两个 additive 键是 `activeTaskStatus` 与 `pendingGate`,各自有真消费点;
1101
- * 不为「说不定哪天上游会发」的键留悬空分支。
1102
- */
1103
584
  export function activeRunSelfHealRow(outcome, signal, copy, origin, follow) {
1104
585
  const override = copy?.rowFor?.(outcome, signal);
1105
586
  if (typeof override === 'string')
1106
587
  return override;
1107
- // 0.72.6(L-379):宿主声明已挂续听 **且**本结局的续听意图确是「现在 tail」⇒ handed-off 句换成事实陈述;
1108
- // 不声明 ⇒ 逐字节同旧。🔴 意图为 after-decision / after-wake 时**不换**(异源对抗复审 [medium]:那两形的续听是
1109
- // 延后登记,不是正在续听;注入形此前没按 delivery 分臂,这里在入口统一收口,两种来源同一前提)。
1110
588
  const following = follow?.following === true && steerFollowIntent(outcome)?.kind === 'tail';
1111
- // #289 批面② —— 系统注入件的低噪形。整块**前置**于默认串:注入形与用户形是两句完全不同的话,
1112
- // 不是在同一句上加定语(见 {@link injectedSubmissionRow} 头注)。缺省 'user' ⇒ 下面逐字节原样。
1113
589
  if (origin === 'injected')
1114
590
  return injectedSubmissionRow(outcome, following);
1115
591
  const base = activeRunSelfHealBaseRow(outcome, signal, copy?.wayOut ?? DEFAULT_WAY_OUT, following);
1116
592
  switch (outcome.kind) {
1117
- // 这两条是「用户此刻卡住了、而且没有别的把手」的结局 —— wire 给的 decide 入口在这里才有用。
1118
- // not-parked(running 形)另挂活性证据([4664] 判别位):「它到底还活着吗」在这一行被问出来。
1119
593
  case 'not-parked':
1120
594
  return base + lastActivityClause(signal) + decidePathClause(signal) + governanceOriginClause(signal);
1121
595
  case 'state-unknown':
1122
596
  return base + decidePathClause(signal) + governanceOriginClause(signal);
1123
- // 其余结局手上都另有一张更好的把手(重开的卡 / 已开的卡 / 失败臂基句自己渲的 decidePath)
1124
- // —— 再叠一条引擎路由只会稀释那一个动作。
1125
597
  default:
1126
598
  return base + governanceOriginClause(signal);
1127
599
  }
1128
600
  }
1129
- /**
1130
- * 系统注入件被拒时的**低噪**整行(#289 批面②;dogfood inject C1 文案半场)。
1131
- *
1132
- * ── 为什么不是「在原句上换个主语」──────────────────────────────────────────────────────────
1133
- * 原来那 8 条臂全部以「Your message was NOT sent … send it again」收尾。对注入件这句里的每一个
1134
- * 词都不成立:不是 your、不是 message、而且 send it again 这个动作**在结构上不存在**(注入件在
1135
- * 队列里 isMeta ⇒ 不可编辑、不可 ↑ 召回、队列预览里都不渲)。所以这一形从头写,不复用出路半句。
1136
- *
1137
- * ── 与行为的对账(不许再出一句假承诺)────────────────────────────────────────────────────────
1138
- * 「it will be delivered after you decide」这句**只在** {@link selfHealSubmissionDisposition} 判
1139
- * `held-for-decision` 时才说 —— 那正是宿主真会把它按原优先级放回队列的那一档。判据同源,不是
1140
- * 两边各写一份(记忆 `paired-mechanisms-must-share-premise`)。其余档一律如实说「这一条没有送达」,
1141
- * 绝不承诺一次不会发生的重投。
1142
- *
1143
- * ── 噪音预算 ────────────────────────────────────────────────────────────────────────────────
1144
- * 用户没写过这条消息,所以这一行的读者其实是**将来的自己**(为什么模型没收到 plan 的结果)。
1145
- * 一句话说清三件事:是系统件、当时什么挡住了它、接下来会不会自己再来。不给出路半句 —— 用户此刻
1146
- * 该做的事是答那张卡,而那张卡就在屏幕上。
1147
- */
1148
601
  function injectedSubmissionRow(outcome, following = false) {
1149
602
  const handle = 'taskId' in outcome && outcome.taskId ? ` (run ${outcome.taskId})` : '';
1150
- // 0.72.9 CC-47(test 0.72.8 验收 #56):steer 失败**按 delivery 分句**,不落默认 not-delivered 句 —— 那句说「could not put a
1151
- // decision on screen」与真因不符,且对 `unknown`(5xx / 断连,可能已注入)断言「未送达、模型不知道」是替引擎编一句安全声明;
1152
- // 用户据此手动补发 = 重复注入。与用户形同一条律:明确拒收才说没送到;送达未知只说「未知、未重试、请观察」。
1153
603
  if (outcome.kind === 'running-steer-failed') {
1154
604
  return outcome.delivery === 'unknown'
1155
605
  ? `A system notification was handed to the run that is already working${handle}, but the engine did not confirm delivery ` +
@@ -1165,8 +615,6 @@ function injectedSubmissionRow(outcome, following = false) {
1165
615
  case 'resending':
1166
616
  return `A system notification was held while the earlier run${handle} was cancelled; sema is delivering it now.`;
1167
617
  case 'handed-off':
1168
- // 🔴 「watch that run」只在宿主**没有**挂续听时说(那时确实只能靠用户看);挂上了就说事实 ——
1169
- // 那条 run 接下来要的东西(含决断卡)会由 sema 从它自己的流上端上来(L-379)。
1170
618
  return following
1171
619
  ? `A system notification was handed to the run that is already working${handle} instead of starting ` +
1172
620
  `a new turn — sema is following that run and will surface whatever it asks for next.`
@@ -1185,9 +633,6 @@ function activeRunSelfHealBaseRow(outcome, signal, wayOut, following = false) {
1185
633
  `sema did NOT cancel it — answering that card is what releases the session, and cancelling ` +
1186
634
  `would decide it for you. Your message was NOT sent; answer the open card and send it again.`);
1187
635
  case 'ask-reopened':
1188
- // 🔴 首见/复见分形:首见卡(本进程从未呈现过这张 pending 卡 —— 典型链:上一 turn 的
1189
- // approve/answer 把引擎推进到了**新** gate,本次提交撞 409 时那张新卡还没呈现过)绝不
1190
- // 说 closed/reopened —— 对没发生过的历史下断言 = 谎报面。动作两形一致,只有话不同。
1191
636
  if (outcome.firstSight) {
1192
637
  return (`The previous turn advanced to a decision it needs from you (run ${outcome.taskId}), which is ` +
1193
638
  `why nothing new could start — sema has surfaced that decision card. Answer it and send your ` +
@@ -1198,7 +643,6 @@ function activeRunSelfHealBaseRow(outcome, signal, wayOut, following = false) {
1198
643
  `Answer it and send your message again. (sema did NOT cancel the run: cancelling would have ` +
1199
644
  `decided it for you.)`);
1200
645
  case 'plan-review-reopened':
1201
- // 同上分形:首见 plan 卡(如 resume 到一个在别处 park 的会话)不断言「被关过」。
1202
646
  if (outcome.firstSight) {
1203
647
  return (`The previous turn produced a plan that is waiting for your review (run ${outcome.taskId}), ` +
1204
648
  `which is why nothing new could start — sema has surfaced the review card. Answer it and send ` +
@@ -1226,8 +670,6 @@ function activeRunSelfHealBaseRow(outcome, signal, wayOut, following = false) {
1226
670
  `Your message was NOT sent; ${wayOut} if you no longer want it.${viaEngine}`);
1227
671
  }
1228
672
  case 'plan-review-reopen-failed': {
1229
- // decidePath 来自 409 body(引擎给的真路由);拿不到就不提它 —— 宁可少给一条路,
1230
- // 也不自己按 gate kind 猜一个入口(猜错 = 又一句「按了没反应」)。
1231
673
  const viaEngine = outcome.decidePath
1232
674
  ? ` You can also decide it on the engine directly: POST ${outcome.decidePath}.`
1233
675
  : '';
@@ -1235,11 +677,7 @@ function activeRunSelfHealBaseRow(outcome, signal, wayOut, following = false) {
1235
677
  `reopen that approval card. It did NOT cancel the run — that would have discarded the plan for ` +
1236
678
  `you. Your message was NOT sent; ${wayOut} if you no longer want that plan.${viaEngine}`);
1237
679
  }
1238
- // ── 三选卡的四种动作结局(选 ③ / 没答 / 没呈上都落 not-parked)────────────────────────────
1239
680
  case 'running-steered': {
1240
- // 🔴 三种 delivery 对用户是三件**完全不同**的事(SDK SteerReceipt 头注),所以整句分形。
1241
- // 🔴 附件披露:wire 的 steer body 只有 `text`(SDK 签名无 images 位),同一次提交里的图片
1242
- // **没有随行**。不说 = 对带图提交谎报「你的消息已投递」。
1243
681
  const handle = `run ${outcome.taskId}`;
1244
682
  const textOnly = `Only the TEXT of your message was handed over — a steer carries no attachments, so any images in that submission were NOT sent.`;
1245
683
  if (outcome.delivery === 'applied') {
@@ -1261,15 +699,6 @@ function activeRunSelfHealBaseRow(outcome, signal, wayOut, following = false) {
1261
699
  `Nothing was cancelled, and your message did NOT start a new turn.`);
1262
700
  }
1263
701
  if (outcome.delivery === 'parked_for_wake') {
1264
- // run 已终结 ⇒ 它已经不占着会话了,所以「再发一次」是**真的**出路(与 rejected 臂同款)。
1265
- // wake 是引擎侧的动作(POST /v1/sessions/:id/wake),客户端这一侧没有接线 —— 如实点名它、
1266
- // 并说清 sema 不会去做,绝不让用户以为屏幕上会有什么在等他。
1267
- // 🔴 **重复执行的披露**(二次评审 R8 [high] 的采纳半场):那条 parked 副本**没有被丢弃** ——
1268
- // 将来任何人 wake 这个会话,同一条指令会再跑一次。带写/删/外部副作用的指令上这是真代价,
1269
- // 必须说在同一句里。
1270
- // 🔵 **驳回的那半**(「干脆别建议重发」):本客户端这一侧既没有 wake 也没有撤销 parked 消息的
1271
- // 接线,不建议重发 = 用户手上一条路都没有 —— 那正是本文件存在的理由(死墙)所要消灭的形。
1272
- // 取舍 = 给出唯一真出路 + 把它的代价说全,而不是把用户留在墙前。
1273
702
  return (`${handle} had already finished, so the engine parked your message on that finished run instead of ` +
1274
703
  `running it — a parked message is delivered only if this session is woken on the engine ` +
1275
704
  `(POST /v1/sessions/:id/wake), which sema does not do here. That run is no longer holding this ` +
@@ -1285,8 +714,6 @@ function activeRunSelfHealBaseRow(outcome, signal, wayOut, following = false) {
1285
714
  : `watch that run before sending it again.`));
1286
715
  }
1287
716
  case 'running-steer-failed':
1288
- // 🔴 明确拒收 vs 送达未知,两句话不能互换(见 outcome 注):对一条**可能已经注入**的非幂等
1289
- // 指令说「没发出去,再发一遍」,买单的是用户那边重复的副作用。
1290
717
  if (outcome.delivery === 'rejected') {
1291
718
  return (`The engine rejected handing your message to the running run (run ${outcome.taskId}): ${outcome.detail}. ` +
1292
719
  `sema did NOT retry it (a steer is not safe to send twice) and it did NOT cancel that run. ` +
@@ -1300,7 +727,6 @@ function activeRunSelfHealBaseRow(outcome, signal, wayOut, following = false) {
1300
727
  return (`You chose to cancel run ${outcome.taskId}. The engine confirmed it is no longer holding this session, ` +
1301
728
  `so sema is sending your message now.`);
1302
729
  case 'running-cancel-timeout': {
1303
- // 秒数取**真等了多久**(不是预算):预算说的是打算等多久,这句话说的是已经发生的事。
1304
730
  const waited = `${String(Math.max(1, Math.round(outcome.waitedMs / 1000)))}s`;
1305
731
  if (outcome.aborted) {
1306
732
  return (`sema asked the engine to cancel run ${outcome.taskId} and you interrupted while it was still ` +
@@ -1308,8 +734,6 @@ function activeRunSelfHealBaseRow(outcome, signal, wayOut, following = false) {
1308
734
  `may still hold this session. Your message was NOT sent; send it again in a moment, or ${wayOut}.`);
1309
735
  }
1310
736
  if (!outcome.confirmedHeld) {
1311
- // 🔴 一次都没读出那条 run 的状态(404 / 传输错)⇒ 只许说「确认不了」。说「它还占着」是替
1312
- // 引擎下一个证不出的断言,而这条腿存在的全部理由就是不许再对用户下证不出的断言。
1313
737
  return (`sema asked the engine to cancel run ${outcome.taskId} but could not read that run's state back ` +
1314
738
  `within ${waited}, so it cannot confirm whether the session was released. Your message was NOT sent; ` +
1315
739
  `send it again in a moment (if that run really is gone it will just run), or ${wayOut}.`);
@@ -1320,37 +744,26 @@ function activeRunSelfHealBaseRow(outcome, signal, wayOut, following = false) {
1320
744
  }
1321
745
  case 'running-cancel-failed':
1322
746
  if (outcome.delivery === 'unknown') {
1323
- // 🔴 送达未知 ⇒ 那条 run 可能已经在停了。说「它还占着会话」是替引擎下一个证不出的断言。
1324
747
  return (`sema sent the cancel for run ${outcome.taskId} but could not confirm what happened to it ` +
1325
748
  `(${outcome.detail}), and it could not read that run back as released either. That run may or may ` +
1326
749
  `not still be holding this session. Your message was NOT sent; send it again in a moment, or ${wayOut}.`);
1327
750
  }
1328
- // 🔴 「引擎拒了这一枪」证明的只是**这一枪没生效**,证明不了那条 run 此刻还占着会话:拒收码里
1329
- // 404 是 unknown-run ∪ non-owner 的不可分辨并集、409 是 checkpoint 并发决定/过期的竞态,两者都
1330
- // 可能发生在会话**已经释放**之后。所以这一句只说被拒的事实,不再顺带下那个证不出的因果断言。
1331
751
  return (`sema could not cancel run ${outcome.taskId} — the engine rejected that request (${outcome.detail}), so ` +
1332
752
  `nothing was cancelled and sema cannot tell whether that run is still holding this session. Your message ` +
1333
753
  `was NOT sent; send it again to find out, or ${wayOut}.`);
1334
- // ── L-93 陈旧 park 的五种结局(0.53.0)────────────────────────────────────────────────────────
1335
754
  case 'ask-run-not-found':
1336
- // 待决行确已不在 + 一手复核撞 404:两个缺席证据指向同一件事,但「已解锁」仍然证不出
1337
- // (该 404 是 unknown-run ∪ non-owner 的不可分辨并集)⇒ 只说已证的事实 + 「再发一次」这条
1338
- // 真出路。🔴 不提 decidePath:那张卡已经不在引擎上,POST 过去只会再吃一个 404(假出路)。
1339
755
  return (`The previous turn is recorded as parked on a decision (run ${outcome.taskId}), but the approval it ` +
1340
756
  `was waiting on is no longer on the engine, and asking the engine about that run directly comes back ` +
1341
757
  `not-found — a lookup that cannot separate a run that is really gone from one this session is simply ` +
1342
758
  `not allowed to read. Either way sema has nothing it can act on here, so it did NOT cancel anything. ` +
1343
759
  `Your message was NOT sent; send it again (if the session really is free it will just run), or ${wayOut}.`);
1344
760
  case 'stale-park-cancelled':
1345
- // 🔴 resending 形绝不说「NOT sent」:释放已被引擎确认(判据 = CLAIM_RELEASED_STATES),
1346
- // 调用方下一步就是重发那条消息,说它没送出去是对着一个正在发生的动作谎报。
1347
761
  return (`Run ${outcome.taskId} was parked on an approval that no longer exists on the engine, so there was ` +
1348
762
  `nothing left for you to answer. You chose to cancel that run: the engine now reports it as finished, ` +
1349
763
  `so it will not resume — whatever that turn had left to do is not coming back. sema is sending your ` +
1350
764
  `message again now; if that run has not fully let go of the session yet, you may have to send it once ` +
1351
765
  `more.`);
1352
766
  case 'stale-park-cancel-timeout': {
1353
- // 秒数取**真等了多久**(不是预算),与 running-cancel-timeout 同律。
1354
767
  const waited = `${String(Math.max(1, Math.round(outcome.waitedMs / 1000)))}s`;
1355
768
  if (outcome.aborted) {
1356
769
  return (`sema asked the engine to cancel run ${outcome.taskId} (it was parked on an approval that no longer ` +
@@ -1359,7 +772,6 @@ function activeRunSelfHealBaseRow(outcome, signal, wayOut, following = false) {
1359
772
  `was NOT sent; send it again in a moment, or ${wayOut}.`);
1360
773
  }
1361
774
  if (!outcome.confirmedHeld) {
1362
- // 一次都没读出那条 run 的状态 ⇒ 只许说「确认不了」,绝不替引擎断言「它还占着」。
1363
775
  return (`sema asked the engine to cancel run ${outcome.taskId} (parked on an approval that no longer exists ` +
1364
776
  `there) but could not read that run's state back within ${waited}, so it cannot confirm whether the ` +
1365
777
  `session was released. Your message was NOT sent; send it again in a moment (if that run really is ` +
@@ -1371,7 +783,6 @@ function activeRunSelfHealBaseRow(outcome, signal, wayOut, following = false) {
1371
783
  }
1372
784
  case 'stale-park-cancel-failed':
1373
785
  if (outcome.delivery === 'unknown') {
1374
- // 送达未知 ⇒ 那条 run 可能已经在停了;说「它还占着会话」是替引擎下一个证不出的断言。
1375
786
  return (`sema sent the cancel for run ${outcome.taskId} (parked on an approval that no longer exists on the ` +
1376
787
  `engine) but could not confirm what happened to it (${outcome.detail}), and it could not read that ` +
1377
788
  `run back as released either. That run may or may not still be holding this session. Your message ` +
@@ -1381,8 +792,6 @@ function activeRunSelfHealBaseRow(outcome, signal, wayOut, following = false) {
1381
792
  `so nothing was cancelled and sema cannot tell whether that run is still holding this session. Your ` +
1382
793
  `message was NOT sent; send it again to find out, or ${wayOut}.`);
1383
794
  case 'stale-park-wait':
1384
- // 两条真出路都必须在这一行里(压噪不压把手):引擎侧 cancel 端点与宿主的 wayOut。
1385
- // 🔴 这一行**不**说「答那张卡」—— 那张卡正是已经不在了的东西。
1386
795
  if (outcome.alreadyOffered === true) {
1387
796
  return (`Run ${outcome.taskId} is still parked (status ${outcome.status}) on an approval that no longer ` +
1388
797
  `exists on the engine. sema already asked what to do with it and you chose to leave it alone, so it ` +
@@ -1394,33 +803,22 @@ function activeRunSelfHealBaseRow(outcome, signal, wayOut, following = false) {
1394
803
  `NOT cancel it — you chose to leave it alone. Your message was NOT sent; cancel that run on the engine ` +
1395
804
  `(POST /v1/runs/${outcome.taskId}/cancel — that ends it for good), or ${wayOut}.`);
1396
805
  case 'running-not-found':
1397
- // Inkglow-1085 P0b②:幽灵 claim —— 引擎一边说这条 run 占着会话、一边查无此 run。「已解锁」
1398
- // 证不出(404 与 non-owner 同码刻意不可分辨),所以只说已证的事实 + 「再发一次」这条真出路
1399
- // (与 running-cancel-timeout 读不出那格同款措辞方向:真没了就会直接跑起来)。
1400
806
  return (`The engine reported this session as held by run ${outcome.taskId} (status running), but it no ` +
1401
807
  `longer recognizes that run when asked directly — that claim looks stale (the run most likely no ` +
1402
808
  `longer exists). sema did not offer to steer or cancel it: there is nothing left to act on. ` +
1403
809
  `Your message was NOT sent; send it again (if the session really is free it will just run), or ${wayOut}.`);
1404
810
  case 'running-settled':
1405
- // 对抗复审 [high] 采纳:run 已终结而 claim 仍报 running([4664] 洞③的窗口形)——
1406
- // 「wait for it to finish」对它是永远等不到的假话;真出路 = 再发一次(claim 落地即跑)。
1407
811
  return (`Run ${outcome.taskId} has already finished (status ${outcome.status}), but the engine still ` +
1408
812
  `reported it as holding this session — that claim looks stale or mid-release. sema did not offer ` +
1409
813
  `to steer or cancel it: the run is over. Your message was NOT sent; send it again (once the claim ` +
1410
814
  `clears it will just run), or ${wayOut}.`);
1411
815
  case 'not-parked':
1412
- // Inkglow-1085 P0b①:同一条 run 的三选卡已呈现过且用户选了「Do nothing」⇒ 降级成一行,
1413
- // 不再整卡打断;两条真出路(引擎 cancel 端点 / wayOut)必须还在这一行里 —— 压噪不压把手。
1414
816
  if (outcome.alreadyOffered === true) {
1415
817
  return (`Run ${outcome.taskId} is still holding this session (status ${outcome.status}). sema already ` +
1416
818
  `asked what to do with it and you chose to leave it alone, so it is not asking again. ` +
1417
819
  `Your message was NOT sent; wait for that run, cancel it on the engine ` +
1418
820
  `(POST /v1/runs/${outcome.taskId}/cancel), or ${wayOut}.`);
1419
821
  }
1420
- // 🔴 「等它跑完」这句只在**它真的在跑**的时候是真话。分诊表外的门/状态里也可能是 park
1421
- // (表外 gate kind 正是走这条),而 wire 恰恰会在那种情况下带 pendingGate —— 对一个
1422
- // parked run 说「wait for it to finish」是一句永远等不到的假出路。动作仍然一个字不改
1423
- // (不动它),改的只是话:有待决材料时就说清它在等人、怎么答。
1424
822
  if (signal?.pendingGate) {
1425
823
  return (`This session is held by an earlier run (run ${outcome.taskId}, status ${outcome.status}) that is ` +
1426
824
  `waiting on ${gateKindPhrase(signal.pendingGate.kind)} — it will not finish on its own. sema did ` +
@@ -1437,12 +835,6 @@ function activeRunSelfHealBaseRow(outcome, signal, wayOut, following = false) {
1437
835
  `${wayOut}.`);
1438
836
  }
1439
837
  }
1440
- /**
1441
- * 非交互车道(headless `-p` 等)对同一事实的诚实说法:那里没有 `/clear`,默认串指的是「别再
1442
- * `--resume` 到这个会话」和引擎自己的 cancel/decide 端点 —— 都是真的存在、真的能做的动作。
1443
- * 这条车道没有自愈腿,材料被吞的代价是全额的:一个「答一下就解锁」的会话会被一句「去 cancel」
1444
- * 打发走,而 cancel 恰恰是替用户否掉那个待决项 —— 所以 decide 入口在场时它才是首选出路。
1445
- */
1446
838
  export function activeRunBusyHeadlessRow(signal, copy) {
1447
839
  const override = copy?.headlessRowFor?.(signal);
1448
840
  if (typeof override === 'string')
@@ -1453,26 +845,19 @@ export function activeRunBusyHeadlessRow(signal, copy) {
1453
845
  const status = signal.activeTaskStatus;
1454
846
  const decidePath = signal.pendingGate?.decidePath ?? null;
1455
847
  const freshSession = copy?.freshSession ?? DEFAULT_FRESH_SESSION;
1456
- // ① 引擎给了 decide 入口 ⇒ **那**才是首选出路,cancel 降为带价签的备选。
1457
848
  if (decidePath) {
1458
849
  return (`This session is held by an earlier run${handle}${status ? ` (status ${status})` : ''} that is parked ` +
1459
850
  `waiting on ${gateKindPhrase(signal.pendingGate?.kind)}, so this message was NOT sent. Decide it and ` +
1460
851
  `the session unlocks: POST ${decidePath}. Cancelling instead (${cancelPath}) also unlocks it, but it ` +
1461
852
  `decides that item against you. Or ${freshSession}.` +
1462
- // 出身归因同款(见 governanceOriginClause):这条车道更需要它 —— 没有卡可看,
1463
- // 用户唯一能读到的就是这一段话。
1464
853
  governanceOriginClause(signal));
1465
854
  }
1466
- // ② 状态在场但引擎明说无待决项 ⇒ 至少说清是谁占着、它是什么态。cancel 的代价按状态**不硬编码
1467
- // 一张表**(动作/文案都不许按状态名枚举),两种可能都摆出来。
1468
855
  if (status) {
1469
856
  return (`This session is held by an earlier run${handle} with status ${status}, so this message was NOT sent. ` +
1470
857
  `The engine reported no decision pending on that run: if it is still working, wait for it to finish; ` +
1471
858
  `if it will never settle, release it with ${cancelPath} (that discards whatever it was doing). ` +
1472
859
  `Or ${freshSession}.`);
1473
860
  }
1474
- // ③ 材料全缺(旧引擎 / store 降级)⇒ activeTaskId 与 cancel 是恒在的保底真路(SDK 6.3.0 契约)。
1475
- // freshSession 覆盖在这条也生效(句首形);默认串首字母大写后与源形逐字节相同。
1476
861
  const startOver = `${freshSession.charAt(0).toUpperCase()}${freshSession.slice(1)}`;
1477
862
  return (`This session is locked by an earlier run${handle} that was never released, so this message was ` +
1478
863
  `NOT sent. Nothing here clears on its own. ${startOver}, or ` +