@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,41 +1,3 @@
1
- /**
2
- * ⇄ B7 批搬迁(2026-07-27,设计稿 §3 B7):cli `src/sema/planReviewWire.ts`(142 行)**整条进包**。
3
- *
4
- * 🔴 **`wireConfig()` 第七份收编闭合**:B6 的 `engineWireTarget.ts` 头注记着「本批搬入的是前六份;
5
- * 第七份 `planReviewWire` 不在 B6 范围,仍留壳内 —— 它是下一批的收编候选,别以为已经收干净了」。
6
- * 本批就是那一批:这里改用 `engineWireTarget()`,壳树 `grep -rn 'function wireConfig' src/sema/`
7
- * 的七份手抄至此**归零**(pure 门 B7 段有断言:壳树在场时命中数 = 0;缺席 ⇒ DEGRADED)。
8
- *
9
- * 🔴 其余搬迁差分(零行为变化):
10
- * 1. `logForDebugging` → `hostLog`。
11
- * 2. `await import('../utils/messageQueueManager.js').enqueuePendingNotification({...})`
12
- * → `NotificationQueuePort.enqueueMetaPrompt`(B7 新增的**可选**方法 —— 既有宿主实现不破)。
13
- * 🔴 缺席后果不是「少个装饰」:模型永远不知道 plan 被批准/驳回后引擎跑出了什么结果,
14
- * 于是它在 approve 之后**原地不动**。所以缺席计 miss(`notificationQueuePortMisses()`),
15
- * 并保留壳原文的 try/catch fail-soft(投递失败绝不炸流)。
16
- * 3. `registerLocalQuestionResponder`/`publishQuestionFrame` → 包内 `liveQuestionStore`
17
- * (B1 已搬,**同一个 module 台账**;两份实例 ⇒ 卡弹出来没人收答)。
18
- *
19
- * ── 以下为原文件的领域说明(逐字保留)────────────────────────────────────────────────────────
20
- *
21
- * planReviewWire — plan_review park 的壳侧审批闭环(core [508]③ 形状拍板 + [511]② 实证)。
22
- *
23
- * WIRE 全链(server 1.138 × core 1.257.4 探针逐字实证,probe-plan-final-frame.mjs):
24
- * 1. plan-mode turn 里模型调 ExitPlanMode → 引擎 park:done 帧 `status:"needs_review"` +
25
- * `checkpointGate:{kind:"plan_review",reason:"ExitPlanMode"}` + `errorCode:"review.pending"`;
26
- * plan 文本已由 ExitPlanMode tool_end 渲染在转录(fence 块),checkpoint 落 FileCheckpointStore。
27
- * 2. 壳(本模块,runStream done 分支调用)合成一个 QuestionFrame 借 AskUserQuestion overlay 弹
28
- * 审批卡(core 拍板:不新造 live question 帧,壳自绘)——合成 id `plan-review:<taskId>` 经
29
- * liveQuestionStore 的 LOCAL responder 拦截,绝不会打到引擎的 /v1/questions(那里 404)。
30
- * 3. 决断 → POST /v1/assistant/tasks/:taskId/plan_review `{decision:"approve"|"reject"}`——
31
- * resume 是 SYNC 驱动到终态的(实证 200 `{taskId,sessionId,status:"completed"}`,可能分钟级),
32
- * 所以 fire 后卡片即关,完成后经 enqueuePendingNotification 入队一个 isMeta turn:模型收到
33
- * resume 终态,自然接续汇报(approve 后"继续干活"的 CC 体验由引擎侧 resume 已完成,这里是
34
- * 结果呈现)。reject 同构(引擎丢 plan 保持 plan 语境)。
35
- *
36
- * 挂账(诚实边界):approve 后壳本地的 plan-mode footer 徽章不自动退(CC 会退)——退 mode 的
37
- * app-state seam 不在本模块可达面,记 rc.47 接 REPL 层;engine 侧 handsReadOnly 已由 resume 处理。
38
- */
39
1
  import { publishQuestionFrame, publishQuestionFrameFor, registerLocalQuestionResponder, unregisterLocalQuestionResponder, hasLocalQuestionResponder, hasQuestionOverlayFor, } from '../liveQuestionStore.js';
40
2
  import { hostLog } from '../host.js';
41
3
  import { makeEngineWireClient } from '../engineWireSdk.js';
@@ -47,32 +9,14 @@ import { planReviewQuestionId, REOPEN_ID_TAIL } from './gateIdentity.js';
47
9
  import { notePlanReviewAnswered, notePlanReviewAnsweredFor, planReviewArmedKeyFor, registerArmedGateFor, waitForGateArmedFor, wasGateArmedFor, } from './armedGateRegistry.js';
48
10
  import { DEFAULT_SESSION_KEY } from '../sessionSlot.js';
49
11
  import { PLAN_REVIEW_GATE_KIND } from '../adapter/activeRunSelfHeal.js';
50
- // 0.60.0(engine ≥7.64.0 / sdk 8.4.0):终局/park 读数的单一读器。
51
12
  import { readRunTerminal, runTerminalGateKind } from '../runTerminal.js';
52
- /** resume 是 SYNC 驱动到终态的(可能分钟级)——原 raw fetch 无超时;SDK Transport 恒有每请求
53
- * 超时,取 6h 宽上界(诚实预算:超过它基本是引擎僵死,超时文案与网络失败同走 outcome 通知)。 */
54
13
  const PLAN_REVIEW_RESUME_TIMEOUT_MS = 6 * 60 * 60_000;
55
- /**
56
- * REF-CC-026:此前是 module-private 常量,唯一另一个消费者(cli `planReviewReopen.ts`)只能靠
57
- * 人眼手抄同步(头注写着「与 client-core planReviewWire 的原卡逐字一致」)——漂一个字节,判决就
58
- * 落到危险的默认臂(见下方 `planReviewDecisionFromAnswer` 的头注)。现在导出成唯一真源;cli 侧改
59
- * import 是后续跟车工单(壳仓文件域,不在本卡范围)。
60
- */
61
14
  export const PLAN_REVIEW_APPROVE_LABEL = 'Yes, approve and run the plan';
62
15
  export const PLAN_REVIEW_REJECT_LABEL = 'No, reject it (keep planning)';
63
- /**
64
- * 0.72.13 CC-46(engine ≥7.86.0 S-433;契约 §4c):三选卡的两个 Yes —— 对齐 CC `ExitPlanMode` 卡
65
- * 「Yes, and auto-accept edits」/「Yes, and manually approve edits」。只在**双闸**都过时才出三选卡(见 {@link planReviewCardOptions}
66
- * 的调用点):宿主声明这条任务是它自己用 `permissionMode:"plan"` 提交的(适用面读不到 wire)∧ 引擎版本证据 ≥7.86.0。
67
- */
68
16
  export const PLAN_REVIEW_APPROVE_AUTO_LABEL = 'Yes, and auto-accept edits';
69
17
  export const PLAN_REVIEW_APPROVE_MANUAL_LABEL = 'Yes, and manually approve edits';
70
- /** `permissionModeAfter` 的引擎地板(server 7.86.0;更老的 server 静默丢这个键 = 承诺了却不兑现)。 */
71
18
  export const PLAN_REVIEW_MODE_AFTER_MIN_ENGINE = Object.freeze([7, 86, 0]);
72
- /** 引擎版本串够不够新:三态 —— 读不出(缺席 / 非 x.y.z)⇒ `unknown`,**不折 supported**(没证据不许出三选卡)。 */
73
19
  export function versionSupportsPlanReviewModeAfter(v) {
74
- // 🔴 **整串**匹配正式版 x.y.z(无前导零):预发布(7.86.0-rc.1 可能还没实现这一位)/ 尾随垃圾 / v 前缀 / 空白 一律 unknown ——
75
- // 它们不是支持证据(异源对抗复审:前缀匹配曾把这些都放行,宿主声明为 true 时三选卡就出了,而老引擎静默丢键)。
76
20
  const mm = v ? /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.exec(v) : null;
77
21
  if (!mm)
78
22
  return 'unknown';
@@ -87,39 +31,20 @@ export function versionSupportsPlanReviewModeAfter(v) {
87
31
  }
88
32
  const APPROVE_LABEL = PLAN_REVIEW_APPROVE_LABEL;
89
33
  const REJECT_LABEL = PLAN_REVIEW_REJECT_LABEL;
90
- /** 老两选卡的标签集 = 判读口不给展示标签时的缺省读域。 */
91
34
  const LEGACY_TWO_CHOICE_LABELS = Object.freeze([PLAN_REVIEW_APPROVE_LABEL, PLAN_REVIEW_REJECT_LABEL]);
92
- /** 四个已知标签(只供**记账**口判「是不是一次决断性作答」,不驱动任何决断)。 */
93
35
  const ALL_KNOWN_LABELS = Object.freeze([
94
36
  PLAN_REVIEW_APPROVE_AUTO_LABEL,
95
37
  PLAN_REVIEW_APPROVE_MANUAL_LABEL,
96
38
  PLAN_REVIEW_APPROVE_LABEL,
97
39
  PLAN_REVIEW_REJECT_LABEL,
98
40
  ]);
99
- /**
100
- * REF-CC-026:三值判决(此前 `selected === APPROVE_LABEL ? 'approve' : 'reject'` 把「没读懂/没
101
- * 选」的第三种输入静默塌缩进危险的 reject 臂——overlay 的 dismiss/拒答语义是「送空 answers」,
102
- * 与用户真的点了 REJECT_LABEL 长得一模一样)。两个标签都做**显式**等值比对,任何第三种输入
103
- * (含 undefined)落 `'dismissed'`,由调用方决定是保持 park 还是显式当 reject 处理——本函数只
104
- * 负责如实分类,不替调用方做选择。
105
- */
106
- export function planReviewDecisionFromAnswer(answer,
107
- /** 本次实际展示的那张卡的标签;不给 = 老两选(见 {@link planReviewChoiceFromAnswer})。 */
108
- shownLabels) {
41
+ export function planReviewDecisionFromAnswer(answer, shownLabels) {
109
42
  const choice = planReviewChoiceFromAnswer(answer, shownLabels);
110
43
  if (choice === 'dismissed')
111
44
  return 'dismissed';
112
45
  return choice.decision;
113
46
  }
114
- export function planReviewChoiceFromAnswer(answer,
115
- /**
116
- * 本次**实际展示**的那张卡的标签(`planReviewCardOptions(offer).map(o => o.label)`);只认其中的标签。
117
- * 🔴 **不给 = 老两选**:三选卡的两个 Yes 只有调用方显式说「我展示的是三选卡」才读得成批准 —— 缺省不许替老调用方
118
- * 悄悄扩大批准的输入域(异源对抗复审:重开腿恒两选,缺省放行时一个没展示过的 auto 标签会被投递成 approve)。
119
- */
120
- shownLabels = LEGACY_TWO_CHOICE_LABELS) {
121
- // 🔴 这是一张**单选**卡,而其中一项在安全轴上(批准后放宽写征询):恰一题、恰一项才算作答。矛盾多选(`[auto, reject]`)/
122
- // 多题 / 空 selected 一律 dismissed —— 此前只取 `selected[0]`,拒绝意图会被丢弃、放宽被执行(异源对抗复审 [high])。
47
+ export function planReviewChoiceFromAnswer(answer, shownLabels = LEGACY_TWO_CHOICE_LABELS) {
123
48
  const answers = answer?.answers;
124
49
  if (!Array.isArray(answers) || answers.length !== 1)
125
50
  return 'dismissed';
@@ -127,7 +52,6 @@ shownLabels = LEGACY_TWO_CHOICE_LABELS) {
127
52
  if (!Array.isArray(picked) || picked.length !== 1)
128
53
  return 'dismissed';
129
54
  const selected = picked[0];
130
- // 标签必须属于这次展示的卡:两选卡上不存在「auto-accept edits」,收到它 = 答的不是这张卡。
131
55
  if (typeof selected !== 'string' || !shownLabels.includes(selected))
132
56
  return 'dismissed';
133
57
  if (selected === PLAN_REVIEW_APPROVE_AUTO_LABEL)
@@ -140,7 +64,6 @@ shownLabels = LEGACY_TWO_CHOICE_LABELS) {
140
64
  return { decision: 'reject' };
141
65
  return 'dismissed';
142
66
  }
143
- /** 卡选项的**单源**:`offerModeAfter=false` ⇒ 老两选(逐字同旧);`true` ⇒ 三选(两个 Yes + No)。 */
144
67
  export function planReviewCardOptions(offerModeAfter) {
145
68
  const reject = { label: REJECT_LABEL, description: 'Discard this plan and stay in plan mode to refine it' };
146
69
  if (!offerModeAfter) {
@@ -152,34 +75,14 @@ export function planReviewCardOptions(offerModeAfter) {
152
75
  reject,
153
76
  ];
154
77
  }
155
- /**
156
- * 推代的**门控口**(A-024.4;#244 F1 上收,语义照壳现实现):只有**决断性**作答(approve/
157
- * reject 标签命中 —— {@link planReviewDecisionFromAnswer} 的官方三值分类)才消费当代 plan 门。
158
- * Esc/空答对原卡是 dismissal(arm 臂的 dismissed 分支不驱动 decidePlanReview,checkpoint 仍
159
- * pending)—— 那时推代会把「被关过」翻成「首见」,恰是 A-024.4 在最常见路径(Esc 关卡)上要
160
- * 保住的区分。消费方 = 宿主 overlay 的 answerAndRelease(respond 成功后调 —— 它是 responder
161
- * 之外**第二条**决断可见点,原卡与重开卡两条通路对同一张卡各报一次,`notePlanReviewAnswered`
162
- * 按完整 questionId 去重恰推一代);包内 responder 走 {@link notePlanReviewAnswered} 直报
163
- * (它们没有 dismissed 臂会到达记账行)。
164
- */
165
78
  export function notePlanReviewAnsweredIfDecisive(questionId, answer) {
166
79
  notePlanReviewAnsweredIfDecisiveFor(DEFAULT_SESSION_KEY, questionId, answer);
167
80
  }
168
- /** W1 带 key 变体。 */
169
81
  function notePlanReviewAnsweredIfDecisiveFor(sessionKey, questionId, answer) {
170
- // 记账口:两种卡的任一已知标签都算决断性作答(这里不投递决断;投递侧各自按**自己展示的卡**校验标签)。
171
82
  if (planReviewDecisionFromAnswer(answer ?? { answers: [] }, ALL_KNOWN_LABELS) === 'dismissed')
172
83
  return;
173
84
  notePlanReviewAnsweredFor(sessionKey, questionId);
174
85
  }
175
- /**
176
- * done 帧的 plan_review park 形状(结构性读,别的终局一律 false)。
177
- *
178
- * 🔴 0.60.0(engine ≥7.64.0 / sdk 8.4.0):判据从退役的两个平面键(`status:"needs_review"` +
179
- * `checkpointGate.kind`)换成带标因由 `terminal.paused.gate.kind`,读法单源
180
- * {@link readRunTerminal}。门种仍**逐字只认 `plan_review`**(不顺手放宽到 `dry_run_review` /
181
- * `needs_review`):本臂驱动的是那一个 resume verb,放宽等于把别的门送进一个它答不了的入口。
182
- */
183
86
  export function isPlanReviewPark(result) {
184
87
  const read = readRunTerminal(result);
185
88
  if (read === null || read.kind !== 'paused')
@@ -189,45 +92,24 @@ export function isPlanReviewPark(result) {
189
92
  const taskId = result?.taskId;
190
93
  return typeof taskId === 'string' && taskId.length > 0;
191
94
  }
192
- /**
193
- * REF-CC-027:已武装未消解的 taskId 去重集(与 `HitlBridge`/`liveQuestionStore` 同款「进程内单例
194
- * 承重不变量」——见 `docs/refactor/p1-scan/singleton-manifest.json` 登记)。durable re-attach 会把
195
- * 同一个 `done{needs_review}` 帧再送一遍(与 askGateWire 的 `decidedGates` 同一族问题);此前
196
- * `armPlanReviewApproval` 对同一 taskId 的重复调用会**再注册一次** responder(旧的注销函数被整个
197
- * 丢掉),第二次注册会覆盖 `localResponders` 里的条目——若第一张卡此刻还没答,它的 unregister
198
- * 闭包会变成一个指向"已被覆盖"槽位的悬空引用。这个集合让重复 arm 直接幂等短路,不再产生第二次
199
- * `registerLocalQuestionResponder` 调用。
200
- */
201
95
  const armedPlanReviewTaskIds = new Set();
202
- /**
203
- * 0.72.13 CC-46:这张已武装的卡**出的是不是三选卡**(两个 Yes 各带一档批准后模式)。与 {@link armedPlanReviewTaskIds}
204
- * 同生命周期(arm 写、决断性作答 / arm 失败清);重放重呈读它,保证同一张卡前后两次呈现选项一致。缺席 = 老两选。
205
- */
206
96
  const offerModeAfterByTask = new Map();
207
- /** 测试钩:清掉武装态(不动 liveQuestionStore 本身,那边有自己的 reset)。 */
208
97
  export function _resetArmedPlanReviewsForTest() {
209
98
  armedPlanReviewTaskIds.clear();
210
99
  offerModeAfterByTask.clear();
211
100
  }
212
- /* 合成 questionId 的唯一铸口:A-028.3 起收编进 `gateIdentity.planReviewQuestionId`(公面导出,
213
- * 壳侧重开腿从此 import 同一铸口,不再手抄字面)。本文件只 import,不再自铸。 */
214
- /** 首次 arm 的题面正句。 */
215
101
  const ARM_QUESTION = 'The plan is ready for review. Approve it and start the implementation?';
216
- /** 重开腿的题面正句(点明「这张卡就是占住会话的那件事」;标签/选项与首呈同源)。 */
217
102
  const REOPEN_QUESTION = 'This plan is still waiting for your review — it is what is holding this session. Approve it and start the implementation?';
218
- /** 题面唯一构造点 —— 首次 arm、重放重呈、重开三处共用,标签/选项描述不许各写一份。 */
219
103
  function planReviewQuestions(question, offerModeAfter = false) {
220
104
  return [
221
105
  {
222
106
  header: 'Plan review',
223
107
  question,
224
- // 选项单源 `planReviewCardOptions`:老两选逐字同旧;三选只在 arm 的双闸都过时出(重开腿恒老两选 = 批准不带键 ⇒ default)。
225
108
  options: planReviewCardOptions(offerModeAfter),
226
109
  multiSelect: false,
227
110
  },
228
111
  ];
229
112
  }
230
- /** 审批卡的发布口 —— 首次 arm 与「重放 arm 重新弹卡」共用同一份(canonical 身份)。 */
231
113
  function publishPlanReviewCard(taskId) {
232
114
  publishQuestionFrame({
233
115
  type: 'question',
@@ -235,87 +117,32 @@ function publishPlanReviewCard(taskId) {
235
117
  questions: planReviewQuestions(ARM_QUESTION, offerModeAfterByTask.get(taskId) === true),
236
118
  });
237
119
  }
238
- /**
239
- * Arm the approval card for a parked plan_review (call with the RAW done.result). Returns true when
240
- * armed (live path + shape matched, INCLUDING the idempotent "already armed" short-circuit — see
241
- * REF-CC-027 above); false = caller renders the terminal as usual. Fail-soft: any error inside must
242
- * never break the stream drain.
243
- */
244
- export function armPlanReviewApproval(result, sessionKey,
245
- /**
246
- * 0.72.13 CC-46:`submittedInPlanMode: true` = 宿主声明「这条任务是我用 `permissionMode:"plan"` 提交的」(只读起步)。
247
- * 🔴 这件事**读不到 wire 上**(契约 §4c:两种任务产出同一张卡),所以只能由提交方声明;不声明 / false ⇒ 老两选卡、决断体不带键。
248
- * 声明了还要过第二道闸:本进程 caps 缓存里的引擎版本 ≥7.86.0(更老的 server 静默丢这个键 = 卡上承诺了却不兑现)。
249
- */
250
- opts) {
120
+ export function armPlanReviewApproval(result, sessionKey, opts) {
251
121
  let armedTaskId;
252
122
  try {
253
123
  if (!isPlanReviewPark(result))
254
124
  return false;
255
- // 🔴 **非默认槽 ⇒ 运行时 fail-closed**(异源对抗复审第六轮 [high] 采纳:「文档登记不能代替运行时
256
- // 隔离」)。additive 第二参让 keyed 宿主**说得出**这条 park 属于哪个槽;库这边诚实回答
257
- // 「这条链今天服务不了它」,而不是**默默按默认槽立卡、再按默认槽把决断发出去** ——
258
- // 后者在「默认槽 + keyed 槽同时装配」的宿主上会把 B 会话的 approve/reject 带着**默认槽**的
259
- // baseUrl/token/principal 发出去(对端有同名任务就改错会话的状态,没有就 404 而原 run 继续
260
- // 停在审批门)。整条链(卡注册 / 退役 / responder 台账 / decide)同批转 keyed 是 additive
261
- // 公开面改动,归后续工单;在那之前**明确拒绝**比静默回落默认槽诚实。
262
- // ⚠️ 不传 / 传默认槽 ⇒ 与 0.44.0 逐字同(单会话宿主零受迫)。
263
125
  if (sessionKey !== undefined && sessionKey !== DEFAULT_SESSION_KEY) {
264
126
  hostLog('error', `planReviewWire: plan_review 卡未 arm —— 本链今天只服务默认会话槽,收到 sessionKey=${JSON.stringify(sessionKey)}。` +
265
127
  '多会话宿主的 plan-mode 归后续工单(卡台账/responder/decide 需同批转 keyed);' +
266
128
  '此处拒绝而不是回落默认槽 —— 回落会把这条会话的决断带着别的槽的 baseUrl/token/principal 发出去。');
267
129
  return false;
268
130
  }
269
- // 🔴 design/285 批 3 的**显式裁定:plan 决策链两处 `engineWireTarget()` 本批豁免**(D7b 全量
270
- // 清单里唯二不换的两处;设计稿 §1 明令「施工时显式裁入射程或豁免 + 理由」,不许静默漏)。
271
- //
272
- // 豁免的理由**不是**「它没有这个病」——它有:keyed 宿主上 plan 决断会走默认槽的 baseUrl /
273
- // token / principal。理由是**只换这一跳会造出一个新的自相矛盾**:整条 plan_review HITL 链
274
- // 今天是默认槽单会话形 —— 卡的注册与退役按 `DEFAULT_SESSION_KEY`(下一行 `retireActiveReopen`
275
- // 与 `liveQuestionStore` 的 responder 台账同键),`decidePlanReview` 又是从那张卡的 responder
276
- // 里回调的。把 wire target 单独换成 keyed = 「按默认槽立卡、按 keyed 槽发决断」,比现状更坏
277
- // (现状至少两头一致地错在同一个槽上,一眼可见;半 keyed 形是两头不一致,只在多会话下现形)。
278
- // ⇒ 正确的做法是整条链(卡台账 + responder + wire)**同批**转 keyed,那是 additive 公开面
279
- // 改动(`armPlanReviewApproval` 要收 sessionKey 并把它捎进 responder 闭包),归后续工单;
280
- // 与本行下方那条「多会话宿主的 arm 路径归后续工单」是同一条欠账,不是两笔。
281
- // 现状由常驻钉 `B3-P31/G18b` 钉住(装了 keyed 槽而默认槽为空 ⇒ 本腿仍判 offline),
282
- // 防「悄悄换成 keyed」与「悄悄以为已经 keyed 了」两向。
283
131
  if (!engineWireTarget())
284
132
  return false;
285
133
  const { taskId } = result;
286
- // 单活纪律(复审二轮,与 reopen 侧对偶):arm/重呈要发的 canonical 卡从此是这个 gate 唯一的
287
- // 作答入口 —— 名下还在场的重开卡先退役(注销 responder + 撤帧)。不退役的话,durable 重放的
288
- // arm 会在 fresh 重开卡还开着时再立一张 canonical 卡,双活双投递面从 arm 侧复发。
289
- // (arm 臂是默认会话装配,退役也按默认键;多会话宿主的 arm 路径归后续工单。)
290
134
  retireActiveReopen(DEFAULT_SESSION_KEY, planReviewQuestionId(taskId));
291
135
  if (armedPlanReviewTaskIds.has(taskId)) {
292
- // REF-CC-027:同一张卡还没消解就被重放的 arm 撞上——幂等,**不二次注册** responder。
293
- //
294
- // [2393] hitl-F3(2026-08-02):但「不二次注册」不等于「什么都不做」。旧码在这里只打一行
295
- // debug 就 `return true`,于是这条短路成了一堵挡死重开路径的墙:
296
- // · `liveQuestionStore.ts` 的契约明写「dialog dismissed without answering ⇒ caller cleans
297
- // up」,而本模块**没有** dismiss/abort 腿 —— 用户 Esc 关掉卡(不作答)之后,武装态
298
- // 一直是 true,responder 也一直挂着;
299
- // · 此后每一次 arm(durable re-attach 重放 / 409 撞上再开)都走这条短路:**一张卡都不弹**,
300
- // 调用方拿到 true 于是也不渲终帧 —— 用户面静默挂住,plan 卡在 park 里没有任何出口。
301
- // 修法按「还在不在」分两支,两支都不产生第二次 `registerLocalQuestionResponder`:
302
136
  if (hasLocalQuestionResponder(planReviewQuestionId(taskId))) {
303
- // responder 还绑着 ⇒ 这张卡仍然可作答,只是可能已经不在屏上了。**重新弹一次**
304
- // (同一个 questionId、同一份题面、同一个 responder):卡还开着的宿主只是收到一帧同形
305
- // 的重绘,卡已经被 dismiss 的宿主则拿回了它的重开路径。
306
137
  hostLog('debug', `planReviewWire: arm replayed — re-presenting the still-armed card for task ${taskId}`);
307
138
  publishPlanReviewCard(taskId);
308
139
  return true;
309
140
  }
310
- // responder 不在了(被别的注册顶掉后注销 / 被单方面清理)⇒ 武装态是**陈旧**的。
311
- // 复用它 = 弹一张没有任何人能收答的卡(比不弹更坏:它看起来能点)。丢掉陈旧记号,
312
- // 往下走整条重新 arm —— 这正是 REF-CC-027 头注里那个「悬空引用」形的收尾。
313
141
  hostLog('debug', `planReviewWire: stale armed state for task ${taskId} (no local responder) — re-arming from scratch`);
314
142
  armedPlanReviewTaskIds.delete(taskId);
315
143
  }
316
144
  armedPlanReviewTaskIds.add(taskId);
317
145
  armedTaskId = taskId;
318
- // CC-46 双闸:宿主声明 ∧ 引擎版本证据(读不出 ⇒ unknown ⇒ 不出三选卡)。
319
146
  const offerModeAfter = opts?.submittedInPlanMode === true &&
320
147
  versionSupportsPlanReviewModeAfter(engineCapString(engineWireTarget()?.baseUrl, 'version')) === 'supported';
321
148
  if (offerModeAfter)
@@ -324,28 +151,17 @@ opts) {
324
151
  offerModeAfterByTask.delete(taskId);
325
152
  const questionId = planReviewQuestionId(taskId);
326
153
  const unregister = registerLocalQuestionResponder(questionId, async (_id, answer) => {
327
- // REF-CC-027:一次性 —— 首句就注销 + 清武装态(照 askGateWire 的 cleanup() 形)。
328
- // liveQuestionStore.respondToQuestion 自身也是一次性取件(delete-before-invoke),这里是
329
- // 第二道钉:武装态一清,同 taskId 才能重新正常 arm(证明 responder 真的落定了)。
330
154
  unregister();
331
155
  armedPlanReviewTaskIds.delete(taskId);
332
156
  const shown = planReviewCardOptions(offerModeAfterByTask.get(taskId) === true).map(o => o.label);
333
157
  const choice = planReviewChoiceFromAnswer(answer, shown);
334
158
  if (choice === 'dismissed') {
335
- // dismiss/无法判读:诚实缺席优先于编造默认值(REF-CC-026)——不静默驱动 decidePlanReview,
336
- // 卡的重开路径把决定权还给用户。呈现台账的键**不清**:同一张卡再被重开就是真「reopened」。
337
159
  return { ok: true };
338
160
  }
339
161
  const decided = choice.decision;
340
- // CC-46:只有三选卡的两个 Yes 带批准后模式;老两选的 Yes / No 不带 ⇒ 决断体不落键。
341
162
  const modeAfter = choice.decision === 'approve' ? choice.permissionModeAfter : undefined;
342
163
  offerModeAfterByTask.delete(taskId);
343
- // A-024.4(#244 F1 起走分代):决断一经递交,这个门的呈现史就消费掉(当代键清 + 推代);
344
- // 同 run 的**下一个** plan gate(approve 推进后引擎可再 park 一个新 plan)必须读回首见,
345
- // 否则文案对一张从未呈现过的新卡说「reopened」。递交后决定未生效的形(RB-471 族)读回
346
- // 首见只损失「reopened」一词 —— 首见文案零历史断言,诚实方向安全。
347
164
  notePlanReviewAnswered(questionId);
348
- // fire-and-forget:resume 同步驱动到终态可能分钟级,不能挂住 overlay;结果经 queue 通知回来
349
165
  hostLog('debug', `planReviewWire: arm responder delivering ${decided} for task ${taskId} (path=arm)`);
350
166
  void decidePlanReview(taskId, decided, modeAfter);
351
167
  return { ok: true };
@@ -355,8 +171,6 @@ opts) {
355
171
  return true;
356
172
  }
357
173
  catch (e) {
358
- // REF-CC-027:arm 本身失败(register/publish 抛)绝不能把 taskId 永久锁在武装态里
359
- // ——那样这张卡再也无法被正常 arm 一次。
360
174
  if (armedTaskId !== undefined) {
361
175
  armedPlanReviewTaskIds.delete(armedTaskId);
362
176
  offerModeAfterByTask.delete(armedTaskId);
@@ -365,10 +179,6 @@ opts) {
365
179
  return false;
366
180
  }
367
181
  }
368
- /**
369
- * outcome 尾句的**唯一铸点**(给模型的下一步指令)。🔴 只有 `took_effect` 才许说「已批准并汇报做了什么 / 已拒绝」;
370
- * 其余三态一律不许出现把决断当成已生效的措辞 —— 那会让模型向用户汇报一件没发生的事。
371
- */
372
182
  export function planReviewOutcomeTail(taskId, decision, effect) {
373
183
  switch (effect) {
374
184
  case 'took_effect':
@@ -387,40 +197,16 @@ export function planReviewOutcomeTail(taskId, decision, effect) {
387
197
  return `Do not state the result as fact: tell the user the ${decision} was submitted but its effect could not be confirmed; TaskOutput("${taskId}") shows where the task stands.`;
388
198
  }
389
199
  }
390
- /** POST the decision; on settle enqueue an isMeta turn so the model reports the resume outcome.
391
- * SDK 宪法迁移批:SDK 0.0.52 assistant.planReview verb(同 wire `POST /v1/assistant/tasks/:id/
392
- * plan_review` 同 body {decision})——raw fetch 退役。错误模型:非 2xx 抛 APIError(message =
393
- * server body.error 原文)→ 同款「HTTP <status> <error>」outcome 文案;网络失败走原「could not
394
- * reach the engine」臂。 */
395
- /**
396
- * 投递口在飞集(0.73.1):同 taskId 的第二次 decide 在第一次 **POST 落地之前**到达 = 重复投递(下游定谳的 0.9 s 双 approve 形)。
397
- * 🔴 窗只到 POST 落地,**不盖**其后的状态回拉(最长 15 s)与 outcome 投递:引擎收下 approve 后可以立刻续跑又停一张新门,
398
- * 那张新门的作答是合法的第二次决断,回拉期间到达不许被吞(异源对抗复审 [high])。
399
- * 被闩住的那次:错误级留痕、不发第二条 meta prompt(第二条会让模型对用户汇报两次)。
400
- */
401
200
  const decideInFlight = new Map();
402
201
  let decideDispatchSeq = 0;
403
- /** 测试钩:清投递口闩。 */
404
202
  export function __resetPlanReviewDecisionLatchForTests() {
405
203
  decideInFlight.clear();
406
204
  }
407
- export async function decidePlanReview(taskId, decision,
408
- /**
409
- * 0.72.13 CC-46(engine ≥7.86.0;契约 §4c):批准之后用哪一档。缺席 ⇒ 体逐字节同旧 `{decision}`。只配 approve、闭集两词 ——
410
- * 坏形本地拒(零 POST,outcome 如实说没送出去,不回显值)。引擎答 400 `request.field_conflict`(这条任务不是只读起步):
411
- * `default` 与缺席同义 ⇒ 去键重发恰一次;`acceptEdits` **不**重发(不静默降成逐次征询),outcome 说批准没生效。
412
- */
413
- permissionModeAfter) {
414
- // REF-CC-025:此前 `cfg`/`client` 缺席各自 `return` 静默——卡已经对用户回了「收到」
415
- // (armPlanReviewApproval 的 responder 早就 `return {ok:true}` 过了),但决断本身连一次网络
416
- // 请求都没发出去,而模型/用户没有任何回程信号。两条早退现在都汇进同一条 outcome 管道(下方
417
- // 唯一的 enqueue 调用点),不再是两条独立的静默 return。
418
- // 0.73.1 投递口闩:在飞中的第二次 decide 直接拒(错误级留痕;不发第二条 meta prompt —— 那会让模型汇报两次)。
205
+ export async function decidePlanReview(taskId, decision, permissionModeAfter) {
419
206
  if (decideInFlight.has(taskId)) {
420
207
  hostLog('error', `planReviewWire: decidePlanReview(${decision}) for task ${taskId} REFUSED — a decision for this task is already in flight (duplicate delivery latched)`);
421
208
  return;
422
209
  }
423
- // 所有权 token:只有持闩的那次调用能放闩(异源对抗复审:旧调用回拉完成后的 finally 曾无条件 delete,把新调用刚取的闩删掉)。
424
210
  const token = {};
425
211
  decideInFlight.set(taskId, token);
426
212
  const releaseLatch = () => {
@@ -433,19 +219,12 @@ permissionModeAfter) {
433
219
  await decidePlanReviewInner(taskId, decision, permissionModeAfter, dispatchNo, releaseLatch);
434
220
  }
435
221
  finally {
436
- releaseLatch(); // 没走到 POST 的早退路径(本地拒 / 无连接)也要放闩;幂等,持闩者才真放
222
+ releaseLatch();
437
223
  }
438
224
  }
439
- async function decidePlanReviewInner(taskId, decision, permissionModeAfter, dispatchNo,
440
- /** **最终**一次 POST 落地(成功或失败;含 default 去键重发)那一刻调:闩窗到此为止。幂等。 */
441
- releaseLatch) {
225
+ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, dispatchNo, releaseLatch) {
442
226
  let outcome;
443
- // 0.73.1:决断**有没有生效** —— 尾句按它选,不按 `decision` 无条件追加。此前 approve 恒追加「取续跑结果并汇报做了什么」、
444
- // reject 恒追加「告知用户计划已拒绝」,于是「could NOT be sent」「did NOT take effect」的正文后面跟着一句让模型把没生效的
445
- // 决断当成已生效去汇报的指令(同一段 meta prompt 自相矛盾)。缺省 = 未确认(任何一条路径忘了归类,都落在最保守的那一句上)。
446
227
  let effect = 'unconfirmed';
447
- // 🔴 design/285 批 3 显式裁定:本处与 `armPlanReviewApproval` 同一笔豁免(理由逐字见那边)——
448
- // 决断是从 arm 立的那张卡的 responder 回调进来的,槽键必须与立卡时同源,单换这一跳即两头不一致。
449
228
  const cfg = engineWireTarget();
450
229
  const badMode = permissionModeAfter !== undefined && (decision !== 'approve' || !isPlanReviewModeAfter(permissionModeAfter));
451
230
  if (badMode) {
@@ -472,7 +251,6 @@ releaseLatch) {
472
251
  }
473
252
  else {
474
253
  try {
475
- // sdk 9.6.0 的 PlanReviewRequest 未声明 permissionModeAfter ⇒ 交集型加一可选位;缺席 = 键不落(体逐字节同旧)。
476
254
  const mode = isPlanReviewModeAfter(permissionModeAfter) ? permissionModeAfter : undefined;
477
255
  const reqBody = { decision };
478
256
  if (mode !== undefined)
@@ -482,12 +260,9 @@ releaseLatch) {
482
260
  body = await client.assistant.planReview(taskId, reqBody);
483
261
  }
484
262
  catch (e1) {
485
- // 400 request.field_conflict + 带了 default:default ≡ 缺席(契约:缺席 = 这一档)⇒ 去键重发恰一次,零语义损失。
486
- // 被这道门拒掉的请求一个字节的状态都不动(契约),同一次批准可原样重试。acceptEdits 不走这里(见下方 catch)。
487
263
  const conflict = e1;
488
264
  if (mode === 'default' && conflict?.status === 400 && conflict?.errorCode === 'request.field_conflict') {
489
265
  hostLog('debug', 'planReviewWire: permissionModeAfter "default" refused as not applicable — re-sending the approval without the key (same meaning)');
490
- // 去键重发仍属**原决断**(400 明确没改状态)⇒ 闩不放,直到重发落地(异源对抗复审 [medium])。
491
266
  body = await client.assistant.planReview(taskId, { decision });
492
267
  }
493
268
  else {
@@ -495,14 +270,9 @@ releaseLatch) {
495
270
  }
496
271
  }
497
272
  finally {
498
- releaseLatch(); // 最终一次 POST 落地(成功 / 400 / 重发成败)⇒ 闩窗到此为止;其后的回拉与 outcome 不在窗内
273
+ releaseLatch();
499
274
  }
500
275
  hostLog('debug', `planReviewWire: ${decision} → ok (dispatch #${dispatchNo}) ${JSON.stringify(body).slice(0, 200)}`);
501
- // [2315]/[2316](#109,2026-08-02):decide 的 **2xx 不当终态** —— test 黑盒实测 reject 9/9
502
- // 返回 200 而会话仍锁在同一 gate(core RB-471:重开兜底对 reject 腿恒真误触发)。这里回拉
503
- // 一次任务状态,按真形分三路措辞;根因归 core/server,本腿是「对外动作回读验证」在产品面的
504
- // 同款,上游修后仍有价值(approve 合法推进新 gate 的形也靠它说真话)。
505
- // 回拉用**短超时**独立 client(15s):verify 腿挂住不该占用 decide 的 6h 宽预算。
506
276
  let postStatus;
507
277
  try {
508
278
  const probe = makeEngineWireClient({
@@ -517,13 +287,9 @@ releaseLatch) {
517
287
  catch (e) {
518
288
  hostLog('debug', `planReviewWire: post-decide status re-pull failed (fail-soft): ${String(e)}`);
519
289
  }
520
- // 回拉失败时回落 decide 200 体自带的 status(次级来源;两者都缺=unverified,措辞如实降级)。
521
290
  const effective = postStatus ?? (typeof body?.status === 'string' ? body.status : undefined);
522
291
  if (effective === 'needs_review') {
523
292
  effect = 'still_parked';
524
- // 决定没生效,仍锁原 gate。不渲「已处理」;真出路只有 approve 或 cancel(下一次提交撞 409
525
- // 时 activeRunSelfHeal 会把审批卡重开——这里不自动重弹卡,避免「刚拒绝又弹卡」的突袭感,
526
- // 决定权经卡的重开路径还给用户)。
527
293
  outcome =
528
294
  `The plan_review ${decision} returned HTTP 200 but the session is STILL locked on the same review gate ` +
529
295
  `(post-decide status: needs_review). The decision did NOT take effect (known engine issue, RB-471 family). ` +
@@ -531,7 +297,6 @@ releaseLatch) {
531
297
  }
532
298
  else if (effective === 'suspended') {
533
299
  effect = 'advanced';
534
- // 合法推进到新 gate(test [2315] ②形):不是「完成」,如实说下一张审批卡会跟上。
535
300
  outcome =
536
301
  `The plan was ${decision === 'approve' ? 'approved' : 'rejected'} and the task advanced to a NEW approval gate ` +
537
302
  `(post-decide status: suspended) — the next approval card will surface it; this is not a completion yet.`;
@@ -541,27 +306,20 @@ releaseLatch) {
541
306
  outcome = `The plan was ${decision === 'approve' ? 'approved and the parked task resumed to completion' : 'rejected (plan discarded)'} — final status: ${effective} (post-decide re-checked: task left the review gate).`;
542
307
  }
543
308
  else {
544
- // 复审 R1:正文也不许断言成功 —— 200 只证明引擎受理了请求,效果是什么本包此刻不知道。
545
309
  outcome = `The plan_review ${decision} was accepted by the engine (HTTP 200) but its effect is unconfirmed — final status: unknown (post-decide verification unavailable). Do not treat this as ${decision === 'approve' ? 'an approval that resumed the task' : 'a discarded plan'}.`;
546
310
  }
547
311
  }
548
312
  catch (e) {
549
- // APIError 判型走 status duck-check(sseIdleTriage 同款纪律:双包时 instanceof 会分叉)。
550
313
  const status = e?.status;
551
314
  const refusedCode = e?.errorCode;
552
315
  if (status === 400 && refusedCode === 'request.field_conflict' && permissionModeAfter === 'acceptEdits') {
553
- // CC-46:这条任务不是只读起步的(模型自选 plan 模式那种形)⇒ 引擎拒收「批准后自动接受编辑」。**不**静默降档重发:
554
- // 用户选的是「不再逐次征询」,悄悄换成「逐次征询」= 替他改了决定。引擎一个字节的状态都没动,计划仍在等。
555
316
  hostLog('debug', 'planReviewWire: approve + acceptEdits refused (request.field_conflict) — NOT re-sent with a different mode');
556
- effect = 'still_parked'; // 契约:被这道门拒掉的请求一个字节的状态都不动 ⇒ 计划仍停在同一张门上,这一形能确定
317
+ effect = 'still_parked';
557
318
  outcome =
558
319
  'The plan approval was NOT applied: the engine refused "auto-accept edits" for this task (it was not started read-only in plan mode, so that option does not apply). ' +
559
320
  'Nothing changed engine-side — the plan is still waiting for review. Tell the user plainly, and that approving with "manually approve edits" will go through.';
560
321
  }
561
322
  else if (status === 404 && refusedCode === 'not_found.plan_review') {
562
- // 0.73.5:这道门已经不在了(早先的决断已生效,或已过期)。现场形:用户在一张**陈旧的重开卡**上作答 ⇒ 404;
563
- // 通用句「decision failed: HTTP 404 …」被模型读成「计划被拒 / 计划失败」,于是对用户说计划被拒并重新规划 ——
564
- // 而那份计划其实已批准并跑完。⇒ 明说这不是拒绝也不是失败,只是这次作答没有对象;效果态仍是 not_applied。
565
323
  hostLog('debug', `planReviewWire: ${decision} → 404 not_found.plan_review (no pending plan review any more — an earlier decision likely settled it)`);
566
324
  effect = 'not_applied';
567
325
  outcome =
@@ -571,7 +329,6 @@ releaseLatch) {
571
329
  else if (typeof status === 'number') {
572
330
  const msg = e instanceof Error ? e.message : String(e);
573
331
  hostLog('debug', `planReviewWire: ${decision} → ${status} ${msg.slice(0, 200)}`);
574
- // 4xx = 引擎明确拒收(状态没动);5xx / 其它 = 不知道引擎那头做到了哪一步 ⇒ 未确认
575
332
  effect = status >= 400 && status < 500 ? 'not_applied' : 'unconfirmed';
576
333
  outcome = `The plan_review decision failed: HTTP ${status} ${msg}`.trim();
577
334
  }
@@ -581,10 +338,6 @@ releaseLatch) {
581
338
  }
582
339
  }
583
340
  }
584
- // REF-CC-025:唯一的 enqueue 调用点(三条上游路径——cfg 缺席 / client 缺席 / 正常决断—— 都汇到
585
- // 这里)。返回值此前被丢弃:`enqueuePlanReviewOutcome` 返回 `false` 意味着队列口没装或没实现
586
- // `enqueueMetaPrompt`(notifications.ts 头注:「模型永远不知道 plan 被批准/驳回后引擎跑出了
587
- // 什么结果」),这是与 HTTP 失败同等重量的静默丢失,必须留痕。
588
341
  try {
589
342
  const delivered = enqueuePlanReviewOutcome(`<plan-review-outcome>\n${outcome}\n${planReviewOutcomeTail(taskId, decision, effect)}\n</plan-review-outcome>`);
590
343
  if (!delivered) {
@@ -595,21 +348,10 @@ releaseLatch) {
595
348
  hostLog('debug', `planReviewWire: outcome enqueue threw: ${String(e)}`);
596
349
  }
597
350
  }
598
- /** 重开身份的进程内唯一尾缀(时基 + 随机段:两份包实例同时铸也不撞键 —— 撞键会被宿主 overlay
599
- * 的去重集吃掉帧,重开静默变 no-op;所以刻意不用模块级计数器)。 */
600
351
  function reopenIdSuffix() {
601
352
  return `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;
602
353
  }
603
- /**
604
- * 每个 `(sessionKey, canonicalId)` **至多一个在场的重开 responder**(复审 [high]:此前每次重开
605
- * 都新注册一个 responder 而不退役上一个 —— 重试/并发 409 会留下两张都能投递决断的活卡,
606
- * 一张 approve 一张 reject 就是两次相互冲突的状态变更请求)。铸新卡前:退役上一张的 responder +
607
- * 对它的帧 id 发 `question_complete` 撤卡;作答/退役后清账。
608
- * 🔴 模块级单例(singleton-manifest 登记):两份实例 ⇒ 各退各的,跨份的旧卡退役不掉 ——
609
- * 退化回修复前的重复活卡形。
610
- */
611
354
  const activeReopenResponders = new Map();
612
- /** 退役 `(sessionKey, canonicalId)` 名下还在场的重开 responder(撤帧 + 注销 + 清账)。 */
613
355
  function retireActiveReopen(sessionKey, canonicalId) {
614
356
  const key = `${sessionKey}\u0000${canonicalId}`;
615
357
  const prev = activeReopenResponders.get(key);
@@ -620,36 +362,23 @@ function retireActiveReopen(sessionKey, canonicalId) {
620
362
  prev.unregister();
621
363
  }
622
364
  catch {
623
- // 注销钩自身的意外 throw 不许挡住新卡铸造(退役面 fail-soft,新卡才是用户的把手)
624
365
  }
625
366
  publishQuestionFrameFor(sessionKey, { type: 'question_complete', questionId: prev.questionId });
626
367
  }
627
- /** 测试钩:清空重开 responder 台账(跨用例状态)。 */
628
368
  export function _resetActiveReopenRespondersForTest() {
629
369
  activeReopenResponders.clear();
630
370
  }
631
371
  export function reopenPlanReviewCard(taskId, opts) {
632
372
  const receiptMs = opts?.presentationReceiptMs;
633
- /** 回执模式下发布之后的收口(订阅必须**先于** publish:总线 handler 同步跑,快 overlay 的
634
- * 登记可能发生在 publish 返回之前 —— 后订阅会错过已发生的回执)。 */
635
373
  const settleOnReceipt = (receipt, firstSight) => receipt.then((presented) => (presented ? { reopened: true, firstSight } : { reopened: false }), () => ({ reopened: false }));
636
374
  try {
637
375
  if (typeof taskId !== 'string' || taskId.length === 0) {
638
376
  return receiptMs === undefined ? { reopened: false } : Promise.resolve({ reopened: false });
639
377
  }
640
378
  const sessionKey = opts?.sessionKey ?? DEFAULT_SESSION_KEY;
641
- // 诚实边界:请求方会话没有 overlay 订阅者(headless / 尚未挂载)⇒ 返回 false,调用方如实说
642
- // 「没能重开」。overlay 在场答的是「有没有一个 UI 会收到这一帧」;检查按 sessionKey 走 ——
643
- // 别的会话挂着 overlay 不等于本会话的卡有地方渲(复审 [high]:默认键硬走会把帧投进别人的面)。
644
379
  if (!hasQuestionOverlayFor(sessionKey)) {
645
380
  return receiptMs === undefined ? { reopened: false } : Promise.resolve({ reopened: false });
646
381
  }
647
- // 🔴 **非默认槽 + 没有自带投递口 ⇒ 拒绝重开**(异源对抗复审第七轮 [high] 采纳)。本函数按传入的
648
- // `sessionKey` 在 B 槽发卡与记账,但**缺省 responder 投递走 `decidePlanReview`,而那一处固定读
649
- // 默认槽的 `engineWireTarget()`** ⇒ 用户在 B 看到并点下去的 approve/reject 会带着 **A 的**
650
- // baseUrl/token/principal 出站(对端有同名任务就改错会话的状态,没有就 404 而 B 的任务继续锁死)。
651
- // arm 臂那道 fail-closed 覆盖不到这条 409/自愈重开入口 —— 两个入口必须同命。
652
- // ⚠️ 宿主自带 `deliverDecision`(它自己按槽投递)⇒ 放行,那时投递口不再是库内的默认槽管道。
653
382
  if (sessionKey !== DEFAULT_SESSION_KEY && opts?.deliverDecision === undefined) {
654
383
  hostLog('error', `planReviewWire: plan_review 卡未重开 —— sessionKey=${JSON.stringify(sessionKey)} 是非默认槽,` +
655
384
  '而缺省决断投递口(decidePlanReview)今天固定打默认槽的 baseUrl/token/principal。' +
@@ -658,22 +387,14 @@ export function reopenPlanReviewCard(taskId, opts) {
658
387
  return receiptMs === undefined ? { reopened: false } : Promise.resolve({ reopened: false });
659
388
  }
660
389
  const canonicalId = planReviewQuestionId(taskId);
661
- // 先查(firstSight)后记 —— 顺序决定判决正确性;台账按 sessionKey 分账,键 = **当代** plan 键
662
- // (A-024.4 分代:0 代与 canonical 字节同形,决断消费后落新代 —— 恒定键会对同 run 的下一只
663
- // plan 门谎报「被关过」)。
664
390
  const armedKey = planReviewArmedKeyFor(sessionKey, taskId);
665
391
  const firstSight = !wasGateArmedFor(sessionKey, armedKey);
666
392
  const mintFresh = opts?.mintFreshQuestionId !== false;
667
393
  if (!mintFresh && hasLocalQuestionResponder(canonicalId)) {
668
394
  hostLog('debug', `planReviewWire: reopen for task ${taskId} reusing the canonical responder (path=reopen-canonical)`);
669
- // 重呈短路(与 arm 臂的重放短路同形):同一个 canonical 身份、同一份题面、同一个 responder。
670
- // 卡还开着的宿主只是收到一帧同形重绘,卡已被 dismiss 的宿主拿回重开路径。
671
- // 0.29.0 发包扫描门:本臂沿用 arm responder ⇒ opts.deliverDecision 不生效(JSDoc 成文例外),
672
- // 传了就留痕 —— 静默吞注入口是契约不一致的形。
673
395
  if (opts?.deliverDecision !== undefined) {
674
396
  hostLog('debug', `planReviewWire: reopen canonical-reuse branch for task ${taskId} ignores opts.deliverDecision (answers route through the existing arm responder — documented exception)`);
675
397
  }
676
- // 先退役本会话名下还在场的**重开**卡(如此前走过 mintFresh 臂)—— 同一 gate 不留两张活卡。
677
398
  retireActiveReopen(sessionKey, canonicalId);
678
399
  if (receiptMs !== undefined) {
679
400
  const receipt = waitForGateArmedFor(sessionKey, [canonicalId], receiptMs);
@@ -687,19 +408,11 @@ export function reopenPlanReviewCard(taskId, opts) {
687
408
  publishQuestionFrameFor(sessionKey, {
688
409
  type: 'question',
689
410
  questionId: canonicalId,
690
- // 🔴 沿用 arm responder ⇒ 题面必须是**那张卡**的选项(三选 arm 后重绘成两选 = 屏上的 Yes 不在 responder 的读域里,点了不投递)。
691
411
  questions: planReviewQuestions(ARM_QUESTION, offerModeAfterByTask.get(taskId) === true),
692
412
  });
693
413
  registerArmedGateFor(sessionKey, armedKey);
694
414
  return { reopened: true, firstSight };
695
415
  }
696
- // 复审 [high] 单活纪律(两轮收紧):铸新卡前退役**全部**旧作答入口 —— 同一个 gate 至多一张
697
- // 能投递决断的卡,两张都活着时一张 approve 一张 reject = 两次相互冲突的状态变更请求。
698
- // · 上一张重开卡:注销 responder + question_complete 撤帧(单活台账);
699
- // · canonical(arm)卡:question_complete 撤帧**且按 id 注销 responder**(二轮 [high]:
700
- // 撤帧只是 UI 动作,表里的 responder 不死,在途的迟到作答仍能经它投递)。canonical
701
- // responder 死后,arm 重放走它自己的「陈旧武装态 ⇒ 整条重新 arm」自愈臂([2393] hitl-F3),
702
- // 不会留下弹不出卡的死短路。
703
416
  retireActiveReopen(sessionKey, canonicalId);
704
417
  if (unregisterLocalQuestionResponder(canonicalId)) {
705
418
  hostLog('debug', `planReviewWire: reopen retired the canonical responder for task ${taskId} (single-active discipline)`);
@@ -709,45 +422,30 @@ export function reopenPlanReviewCard(taskId, opts) {
709
422
  hostLog('debug', `planReviewWire: reopen for task ${taskId} minting a fresh card ${questionId} (path=reopen-fresh)`);
710
423
  const deliver = opts?.deliverDecision ??
711
424
  ((tid, decision) => {
712
- // fire-and-forget:resume 同步驱动到终态可能分钟级,不能挂住 overlay(arm 臂同姿势);
713
- // 失败处置/结果回植由 decidePlanReview 自带的 outcome 管道承担。
714
425
  hostLog('debug', `planReviewWire: reopen responder delivering ${decision} for task ${tid} (path=reopen-default)`);
715
426
  void decidePlanReview(tid, decision);
716
427
  });
717
428
  const activeKey = `${sessionKey}\u0000${canonicalId}`;
718
429
  const unregister = registerLocalQuestionResponder(questionId, async (_id, answer) => {
719
- unregister(); // 一次性(arm 臂同款):respond 侧 delete-before-invoke 之外的第二道钉
720
- // 单活台账清账(只清**自己**的条目 —— 自己被更新的重开取代后,旧 responder 的迟到作答
721
- // 不许把新卡的条目清掉)。
430
+ unregister();
722
431
  if (activeReopenResponders.get(activeKey)?.questionId === questionId) {
723
432
  activeReopenResponders.delete(activeKey);
724
433
  }
725
- // 本腿展示的恒是老两选 ⇒ 显式按两选标签校验(未展示的 auto / manual 标签 = 答的不是这张卡 ⇒ dismissed)。
726
434
  const decided = planReviewDecisionFromAnswer(answer, LEGACY_TWO_CHOICE_LABELS);
727
435
  if (decided === 'dismissed') {
728
- // 三态(REF-CC-026):dismiss/读不出不驱动 decidePlanReview;呈现台账不清 ——
729
- // 这张卡再被重开就是真「reopened」。
730
436
  return { ok: true };
731
437
  }
732
- // A-024.4(#244 F1 起走分代):决断递交即消费呈现史(与 arm 臂 responder 同点;overlay 的
733
- // answerAndRelease 对同一张卡也会经 notePlanReviewAnsweredIfDecisive 报一次,按完整
734
- // questionId 去重,不双推)。
735
438
  notePlanReviewAnsweredFor(sessionKey, questionId);
736
439
  try {
737
440
  await deliver(taskId, decided);
738
441
  }
739
442
  catch (e) {
740
- // 投递编排是注入方的职责面(缺省臂自带 outcome 管道不会走到这);这里只留痕,
741
- // 绝不把宿主投递臂的 throw 冒进 overlay 的 respond 链。
742
443
  hostLog('error', `planReviewWire: reopen decision delivery threw for task ${taskId}: ${String(e)}`);
743
444
  }
744
445
  return { ok: true };
745
446
  });
746
447
  activeReopenResponders.set(activeKey, { questionId, unregister });
747
448
  if (receiptMs !== undefined) {
748
- // 回执模式(#250 件1):零登记(登记时点 = 宿主 overlay 真入队点)、订阅先于 publish、
749
- // 回执键 = 原始帧 id(尝试级回执 —— 同 gate 并发重开不同键互唤);窗尽如实 false,
750
- // responder 故意保留(迟到卡必须仍可答,决断腿照走)。
751
449
  const receipt = waitForGateArmedFor(sessionKey, [questionId], receiptMs);
752
450
  publishQuestionFrameFor(sessionKey, {
753
451
  type: 'question',
@@ -765,7 +463,6 @@ export function reopenPlanReviewCard(taskId, opts) {
765
463
  return { reopened: true, firstSight };
766
464
  }
767
465
  catch {
768
- // 重开失败绝不炸 turn —— 调用方会如实说「没能重开」(回执模式回同形的 resolved promise)。
769
466
  return receiptMs === undefined ? { reopened: false } : Promise.resolve({ reopened: false });
770
467
  }
771
468
  }