@sema-agent/client-core 0.77.0 → 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 +23 -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 +1 -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 +10 -496
  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 +11 -65
  346. package/dist/subagent/engineTaskHandleWire.js +20 -131
  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 +62 -1
  382. package/package.json +1 -1
@@ -1,19 +1,10 @@
1
- /**
2
- * 通知投影纯函数(抽自 sema-cli engineTaskNotification/upstreamBridge 通知臂;
3
- * result 段纪律=cli #49 C1:帧带 result 必入 XML,core renderTaskNotificationXml 同名 tag 同段序)。
4
- * 0.4.0(B2 批)起本模块还含:engineTaskNotification 全文(台账/watcher/入队合成)、
5
- * taskNotificationErrorSupplement 全文、hookNoticeStore 的判定半场 —— 见文件下半的 B2 分节。
6
- */
7
1
  import { hostEnv } from './hostEnv.js';
8
2
  import { unrefTimer } from './unrefTimer.js';
9
3
  import { clearEnginePanelTaskResidentByWire, publishEngineAgentPanelEvent, } from './engineAgentPanelStore.js';
10
- // L-215②(0.65.0;core [6908]):「非成功终局」的**单铸谓词** —— 面板 settle 的 `isError` 位与
11
- // 三端自己的后台白名单必须读同一个判据;内联一个 `=== 'failed' || === 'killed'` 就是第二份会漂的台账。
12
4
  import { isTaskNotificationTerminalStatus, isTerminalNotSuccess, isTerminalStatus } from './runTerminal.js';
13
5
  function escapeXml(v) {
14
6
  return v.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
15
7
  }
16
- /** 通知 user-turn XML 渲染(core task-notification 同形;external 帧开标签带 from= 属性)。 */
17
8
  export function renderTaskNotificationXml(n) {
18
9
  const openTag = n.taskType === 'external' && n.externalSource
19
10
  ? `<task-notification type="external" from="${escapeXml(n.externalSource.replace(/\s+/g, ' ').slice(0, 120)).replace(/"/g, '&quot;')}">`
@@ -27,13 +18,6 @@ export function renderTaskNotificationXml(n) {
27
18
  <summary>${escapeXml(n.summary)}</summary>${seg('result', n.result)}${linesBlock}${stepsBlock}${typeof n.resumable === 'boolean' ? `\n<resumable>${n.resumable}</resumable>` : ''}
28
19
  </task-notification>`;
29
20
  }
30
- /**
31
- * core 1.262.0 残局包([558]A/D)— 从 task_notification 帧派生残局显示行,与 core 进程内
32
- * renderResidual(core/task-notification.ts:75-86)**逐字节对齐**:先 `- tool target → outcome`
33
- * 步骤行,再 ONE `edited: path (×N), …` 行(editedFiles 折进 recent-steps,core 不铸独立
34
- * <edited-files> tag——别自创)。字段是 additive 的,老引擎没有 ⇒ []。返回裸串,转义由调用方按
35
- * 自己的面做(XML/Ink 行)。cli deriveNotificationResidualLines 的等价搬运。
36
- */
37
21
  export function deriveNotificationResidualLines(n) {
38
22
  const out = [];
39
23
  if (Array.isArray(n.recentSteps)) {
@@ -56,17 +40,10 @@ export function deriveNotificationResidualLines(n) {
56
40
  }
57
41
  return out;
58
42
  }
59
- /**
60
- * 引擎 task_notification 原始 payload → renderTaskNotificationXml 的入参(**含上面注释里那两条
61
- * 回填**:summary 缺省 ⇒ `Background task "<id>" <status>`;status 非串 ⇒ 'unknown',summary 改说 outcome unknown)。
62
- * task_id 空串/缺失 ⇒ null(RB-75 同族:空 id 会塌成 ':status' 共享桶互吞,无 id 的通知本就无意义)。
63
- * 字段名映射按 wire 真形:task_id/output_file/task_type/source 是 snake/wire 名,其余同名。
64
- */
65
43
  export function normalizeTaskNotification(n) {
66
44
  const taskId = typeof n.task_id === 'string' && n.task_id.length > 0 ? n.task_id : undefined;
67
45
  if (taskId === undefined)
68
46
  return null;
69
- // 0.72.12 CC-50(#59 A-08):status 非串 ⇒ 'unknown'(此前 'completed',并用它编 summary 与去重键)。
70
47
  const status = typeof n.status === 'string' ? n.status : 'unknown';
71
48
  const lines = Array.isArray(n.lines)
72
49
  ? n.lines.filter((l) => typeof l === 'string')
@@ -98,33 +75,14 @@ export function normalizeTaskNotification(n) {
98
75
  ...(typeof n.source === 'string' && n.source.length > 0 ? { externalSource: n.source } : {}),
99
76
  };
100
77
  }
101
- /** 通知去重键——cli engineTaskNotificationDedupKey **逐字等价**(对抗复审抓的
102
- * 等价性破坏,0.1.1 修):无 seq 不追加段(与 seq:1 是不同键);仅 external 用
103
- * `external:` 前缀;seq 收 number|string。格式:`[external:]taskId:status[:seq]`。 */
104
78
  export function taskNotificationDedupKey(n) {
105
79
  const seq = typeof n.seq === 'number' || typeof n.seq === 'string' ? String(n.seq) : '';
106
80
  const lane = n.taskType === 'external' ? 'external:' : '';
107
81
  return `${lane}${n.taskId}:${n.status}${seq ? `:${seq}` : ''}`;
108
82
  }
109
- /**
110
- * ⇄ B7(T2):同一把键的 **裸 wire 载荷** 入口 —— cli `upstreamBridge.engineTaskNotificationDedupKey`
111
- * 的等价物(入参是引擎 `task_notification` 帧的原始 payload:snake_case `task_id` / `task_type`)。
112
- *
113
- * 🔴 **不是第二份算法**:字段归一之后直接委托给上面那一个 `taskNotificationDedupKey`。
114
- * 两份手写算法正是这条键在 0.1.1 被 对抗复审抓到过的形(无 seq 追不追段的差异会让
115
- * 「重放去重」整条失效而两边代码看着都对);pure 门 B7 段对两个入口做**同键对拍**。
116
- *
117
- * 键形:`[external:]taskId:status[:seq]`。缺 `task_id` ⇒ 空 taskId 段(与 cli 逐字一致 ——
118
- * 空 id 的通知本就在 `normalizeTaskNotification` 那一层被丢弃,这里不额外加语义)。
119
- *
120
- * ⚠️ **P1-3(完成事件单一权威)未到货 ⇒ 只搬不删**:引擎给出 `completionId` 之后,这把
121
- * 「客户端按 taskId:status[:seq] 自己重算」的键才能换成「读引擎发的 id」。退役条件见
122
- * `compensations.ts` 的 T2 行。
123
- */
124
83
  export function taskNotificationDedupKeyFromWire(n) {
125
84
  return taskNotificationDedupKey({
126
85
  taskId: typeof n.task_id === 'string' ? n.task_id : '',
127
- // CC-50:与 normalizeTaskNotification 同判,缺席不与真 completed 同桶。
128
86
  status: typeof n.status === 'string' ? n.status : 'unknown',
129
87
  ...(typeof n.seq === 'number' || typeof n.seq === 'string' ? { seq: n.seq } : {}),
130
88
  ...(typeof n.task_type === 'string' ? { taskType: n.task_type } : {}),
@@ -172,11 +130,6 @@ export function parseTranscriptNotificationSeeds(messages) {
172
130
  continue;
173
131
  const external = /\btype="external"/.test(attrs);
174
132
  renderedKeys.push(`${external ? 'external:' : ''}${taskId}:${status}`);
175
- // L-215② 族扫(0.65.0):终局判据走单铸谓词 —— 修前这里手抄三词,core [6908] 的 `blocked`
176
- // 漏在外面 ⇒ 一条已经渲过的 `blocked` 通知不进 `notifiedKeys`,重开会话后**再投一次**。
177
- // 0.73.3(CC-60 A-D11):这条 XML 有**两个生产者**、两张终局词表 —— core task-notification(带 cancelled)与本包
178
- // enqueueBgChildNotification(status 来自 run 行状态面,带 blocked)⇒ 按**并集**判终局;`event` 是唯一非终局。
179
- // 两张冻结元组各自不动(动任一张 = 把另一个生产者的词丢掉)。
180
133
  if (!external && (isTerminalStatus(status) || isTaskNotificationTerminalStatus(status))) {
181
134
  notifiedKeys.push(taskId);
182
135
  }
@@ -186,21 +139,6 @@ export function parseTranscriptNotificationSeeds(messages) {
186
139
  }
187
140
  return { renderedKeys, notifiedKeys, seeded };
188
141
  }
189
- // ══════════════════════════════════════════════════════════════════════════════════════════════
190
- // ⇄ B2 批搬迁(2026-07-27,多端改造设计稿 §3 B2「通知族合并」):cli 三个文件并入本模块
191
- // ① src/sema/engineTaskNotification.ts(347 行,台账 + watcher + 入队合成)
192
- // ② src/sema/taskNotificationErrorSupplement.ts(58 行,纯函数)
193
- // ③ src/sema/hookNoticeStore.ts(177 行)的**判定半场**(store 写入半场留宿主)
194
- // 🔴 单实例纪律(B1 血泪同款):①带 11 个模块级台账(notifiedRunIds / cardEnqueuedRunIds /
195
- // outstandingRuns / outstandingBgTasks / ownWorkflowRuns / bgNotifiedKeys / 两个 probe 槽 /
196
- // listener 集 / watchTimer / tickInFlight)—— 写口(bridge 登记、probe 安装)与读口
197
- // (footer outstanding 计数、fleet own 判别、去重)必须解析到**同一个包实例**。
198
- // 🔴 队列口(messageQueueManager)是宿主件:本包不 import 壳的队列,改成**注入 port**
199
- // (设计稿原文「messageQueueManager 调用 ⇒ chrome 事件」的等价实现:先给最小注入口,
200
- // chrome 事件化留给 B3 的 AdapterContext 扩容)。**未注入 = 通知投递静默丢失**,所以
201
- // miss 有计数 + 一次性告警,绝不让它悄悄消失(a0110e88 零渲染案的教训:配对机制必须共享前提)。
202
- // ══════════════════════════════════════════════════════════════════════════════════════════════
203
- // cli src/constants/xml.ts 的四个 tag 常量(值逐字对齐;本包不引壳的 constants 桶)。
204
142
  const TASK_NOTIFICATION_TAG = 'task-notification';
205
143
  const TASK_ID_TAG = 'task-id';
206
144
  const STATUS_TAG = 'status';
@@ -208,23 +146,15 @@ const SUMMARY_TAG = 'summary';
208
146
  let queuePort = null;
209
147
  let queuePortMisses = 0;
210
148
  let queuePortWarned = false;
211
- /** 宿主装配:壳侧 shim / web 宿主在**模块加载期**装(装之前的调用会计入 miss)。 */
212
149
  export function installNotificationQueuePort(port) {
213
150
  queuePort = port;
214
151
  }
215
- /** 🔴 未注入队列口而发生的投递次数 —— 恒应为 0;>0 = 宿主装配漏了,通知已静默丢失。 */
216
152
  export function notificationQueuePortMisses() {
217
153
  return queuePortMisses;
218
154
  }
219
- /**
220
- * 本模块唯一的 SEMA_DEBUG 留痕出口(前缀逐字保持 `[sema][notif] `,与既有 drop 那行同形)。
221
- * 🔴 为什么要单口:C5「静默丢数据零容忍」的执行方式是**每条丢弃/放弃路径都能在同一个前缀下
222
- * grep 到**;散写 console.error 时新增的丢弃路径很容易漏掉留痕(notif-02/-03 就是这么漏的)。
223
- */
224
155
  function traceNotif(line) {
225
156
  if (!hostEnv().SEMA_DEBUG)
226
157
  return;
227
- // eslint-disable-next-line no-console
228
158
  console.error(`[sema][notif] ${line}`);
229
159
  }
230
160
  function port() {
@@ -233,44 +163,20 @@ function port() {
233
163
  queuePortMisses++;
234
164
  if (!queuePortWarned) {
235
165
  queuePortWarned = true;
236
- // eslint-disable-next-line no-console
237
166
  console.error('[client-core] notification queue port NOT installed — task-notification delivery is being dropped. ' +
238
167
  'Host must call installNotificationQueuePort() at startup (see notifications.ts B2 section).');
239
168
  }
240
169
  return null;
241
170
  }
242
- /** 测试钩:清 port + miss 计数(不动台账,台账各有自己的 reset)。 */
243
171
  export function _resetNotificationQueuePortForTest() {
244
172
  queuePort = null;
245
173
  queuePortMisses = 0;
246
174
  queuePortWarned = false;
247
175
  }
248
- // ══════════════════════════════════════════════════════════════════════════════════════════════
249
- // ① engineTaskNotification — engine-side background-run completion → CC `<task-notification>` injection.
250
- //
251
- // The service pushes a `workflow_complete` named SSE frame (service 1.75, `{runId, status, summary}` —
252
- // an explicit whitelist) onto the session's NEXT `/v1/tasks/stream` connection when a background
253
- // workflow it launched earlier finishes out-of-band. The shell turns that frame into CC's OWN
254
- // remote-task completion mechanism (the RemoteAgentTask `enqueueRemoteNotification` precedent,
255
- // CC 187 `Nco`): build the `<task-notification>` XML, enqueue it as a `task-notification` queued
256
- // command, and let CC's native drain / queue processor (idle → auto-submit) deliver it to the model.
257
- // No explicit priority — CC's RemoteAgentTask enqueues WITHOUT one, so the queue default ('later',
258
- // messageQueueManager: "so user input is never starved") applies; 'next' is the LocalShellTask/
259
- // MONITOR_TOOL precedent, not the remote-completion one (adversarial review w0zwpa251 §5).
260
- //
261
- // Dedup: CC marks the task registry entry `notified` — an engine workflow has no registry entry, so a
262
- // PROCESS-LIFETIME Set on runId does the same job (the service inbox is at-least-once; its enqueue is
263
- // runId-idempotent across restarts, this Set covers redelivery within one shell process).
264
- //
265
- // No `<output-file>` line: CC's output-file points at a LOCAL task output file (getTaskOutputPath);
266
- // an engine run has none — the SDK-exit regex reads tags independently, an absent tag is undefined
267
- // (the `<tool-use-id>` line is optional in CC's own template the same way).
268
- // ══════════════════════════════════════════════════════════════════════════════════════════════
269
176
  const notifiedRunIds = new Set();
270
177
  const notifiedBgCycleHigh = new Map();
271
178
  let ledgerOrder = 0;
272
179
  const nextLedgerOrder = () => ++ledgerOrder;
273
- /** 「已送达」两账的**唯一**写口(裸 id 维 + 周期维必须同时前进,否则收摊臂又回到没有周期证据)。 */
274
180
  function markRunNotified(runId, cycle = BG_FIRST_SEQ) {
275
181
  notifiedRunIds.add(runId);
276
182
  const prev = notifiedBgCycleHigh.get(runId);
@@ -278,31 +184,10 @@ function markRunNotified(runId, cycle = BG_FIRST_SEQ) {
278
184
  notifiedBgCycleHigh.set(runId, { cycle, order: nextLedgerOrder() });
279
185
  }
280
186
  }
281
- /** 「**这一个周期**已经送达过」——收摊臂唯一可用的删除证据。 */
282
187
  function notifiedBgCycleAtLeast(taskId, cycle) {
283
188
  const high = notifiedBgCycleHigh.get(taskId);
284
189
  return high !== undefined && high.cycle >= cycle ? high : undefined;
285
190
  }
286
- /**
287
- * B1(frame-lane-matrix 三节定谳)— Path A 回声到达时丢弃同 run 的 pending 队列条目。
288
- * 双投机理:活跃 turn 内 Path B(workflow_complete/bg_notification/probe feeder →
289
- * enqueuePendingNotification)先入队,Path A(引擎 server-side steer-inject 的
290
- * task_notification 回声帧)随流到达——引擎侧模型已收到一遍;turn 收口 idle 排水若再
291
- * auto-submit 队列条目 = 模型二收同一份 XML。壳撤不回引擎侧注入(跨进程),但能在
292
- * drain 前把已被回声证实送达的同 run 队列条目丢掉。键空间 = 裸 taskId/runId(与
293
- * notifiedRunIds/cardEnqueuedRunIds 同域);条目匹配按队列 XML 的 <task-id> 标签
294
- * (enqueue 两族与 bridge 渲染同用 escapeXml,键形字节对齐)。仅主线程 task-notification
295
- * 条目(agentId undefined);用户输入/其他模式绝不触碰。
296
- */
297
- /**
298
- * B7:plan_review 结局回植(`hitl/planReviewWire.ts` 的唯一调用点)。走队列口的
299
- * `enqueueMetaPrompt` 可选动词 —— 口或动词缺席 ⇒ 计 miss(与 `port()` 同一个计数器,宿主
300
- * 只看 `hostPortMisses()` 一个数)并**返回 false**,调用方 fail-soft。
301
- *
302
- * 🔴 为什么不复用 `enqueuePendingNotification`:那条走的是 `<task-notification>` 通道(队列
303
- * 的 `task-notification` 模式,drain 时按通知语义处理);plan_review 结局是一个 **isMeta 的
304
- * prompt turn**。混用 = 结局被当成后台任务完成通知渲染,是「看起来送到了」的假绑定。
305
- */
306
191
  export function enqueuePlanReviewOutcome(value) {
307
192
  const p = port();
308
193
  if (!p)
@@ -323,89 +208,35 @@ export function dropQueuedNotificationsForRun(taskId) {
323
208
  cmd.agentId === undefined &&
324
209
  typeof cmd.value === 'string' &&
325
210
  cmd.value.includes(needle));
326
- // 🔴 a0110e88 零渲染案(clay 1.0.55 真机):删掉的条目正是 cardEnqueuedRunIds
327
- // 记号所指的「会渲卡的队列条目」——条目死了记号还在,推送帧的渲染臂会按记号 break,两条渲染路
328
- // 互相踢皮球 = UI 永远没有这条通知。删除成功必须同时交还卡所有权(清记号),让帧臂渲染。
329
- // 模型面不受影响:core 文档化设计(task-notification.ts design/116 §7)保证 turn 边界 steer
330
- // 注入,引擎侧单投成立;这里只救 UI 半场。
331
211
  if (dropped.length > 0) {
332
212
  cardEnqueuedRunIds.delete(taskId);
333
213
  traceNotif(`dropped ${dropped.length} queued task-notification(s) for ${taskId} — engine echo already delivered (B1); card ownership returned to the frame lane`);
334
214
  }
335
215
  return dropped.length;
336
216
  }
337
- /**
338
- * Pre-seed the runId dedup WITHOUT enqueueing — the completion already reached the model in-band
339
- * (clay 2026-07-04 复审:模型用 TaskOutput 轮询拿到 workflow 终态并当轮总结完;引擎 completion inbox
340
- * 不知道"轮询已送达",下一次开流仍补发 workflow_complete —— 对模型是纯重复,还触发一轮"这是补发通知,
341
- * 无需重复"的空转)。终态在 turn 内经过壳的投影(TaskOutput workflow 终态卡 / 引擎 server-side 已
342
- * steer-inject 的 task_notification 帧)即预标记,Channel A 对同 runId 的补发直接丢弃。
343
- */
344
217
  export function markEngineWorkflowNotified(runId) {
345
- // [2393] F-1:外部 mark 只证「首周期已送达」(它没有周期号可带),周期维按首周期记 —— 与本函数
346
- // 既有的跨通道语义逐字一致(enqueueBgChildNotification 的跨通道臂同样只对首周期成立)。
347
218
  markRunNotified(runId);
348
219
  if (outstandingRuns.delete(runId))
349
220
  notifyOutstanding();
350
221
  }
351
- // ══════════════════════════════════════════════════════════════════════════════════════════════
352
- // B5 ①(0.7.0)—— **配对台账折回**([1857];记忆 paired-mechanisms-must-share-premise)。
353
- // 0.6.0 把 `notifiedRuns` / `cardEnqueuedRuns` 放在**适配器实例**上,而与它们配对的另一半全在
354
- // 本模块的 module 台账上:清账的 `dropQueuedNotificationsForRun`、记账的
355
- // `enqueueBgChildNotification` / `enqueueEngineWorkflowNotification`、以及 idle watcher 那条
356
- // (壳根本拦不到的调用点)。两半前提不共享 = 四个方向同时错:
357
- // ① module 记过账 ⇒ 实例不知道 ⇒ 推送帧渲第二张卡(双卡)
358
- // ② 宿主回钩写实例、drop 清 module ⇒ 实例只增不减 ⇒ 那一行永不上屏(a0110e88 复发)
359
- // ③④ 跨通道去重两向失效 ⇒ 同一完成二次喂模型
360
- // 修法 = 台账**整体**回到 module(与 cli 的 module 级 Set 逐字同域),适配器只经下面这四个口读写;
361
- // 实例上只留 `renderedNotifications`(渲染去重,天然 per-session)与 `firedSubagentStartHookTaskIds`
362
- // (DIVERGENCE-6 有意的实例级)。
363
- // 🔴 多 session 宿主(web 一个页面两个会话)上本对台账是**进程级共享**的 —— 这与 cli 一致,
364
- // 且必须如此:队列 port、idle watcher、outstanding 药丸也都是进程级的,台账单独 per-session
365
- // 就又造出一对「前提不共享」的机制。
366
- // ══════════════════════════════════════════════════════════════════════════════════════════════
367
- /** 跨通道去重的**读**口(适配器 workflow_complete 臂的发射门;cli 同域)。 */
368
222
  export function isEngineWorkflowNotified(runId) {
369
223
  return notifiedRunIds.has(runId);
370
224
  }
371
- /** 「这个 run 的完成卡已入队」的**写**口(cli 侧等价物 = 两个 enqueue 函数内的 `cardEnqueuedRunIds.add`)。 */
372
225
  export function noteWorkflowCompletionCardEnqueued(runId) {
373
226
  if (runId.length > 0)
374
227
  cardEnqueuedRunIds.add(runId);
375
228
  }
376
- /** 台账序列化面(`exportLedger`)—— 只读快照,顺序 = 插入序。 */
377
229
  export function listNotifiedRuns() {
378
230
  return [...notifiedRunIds];
379
231
  }
380
232
  export function listWorkflowCompletionCardsEnqueued() {
381
233
  return [...cardEnqueuedRunIds];
382
234
  }
383
- /** 双通道「完成卡」去重(clay dogfood 2026-07-19 双通知案):与 notifiedRunIds(模型通知去重,
384
- * external mark 也写它)**分键**——本集只由 probe 合成 enqueue 写入(workflow+bg agent 两族,
385
- * 对抗复审§1:bg 合成不写此集时 probe 先到→后到推送帧仍渲第二张卡)、只由推送帧渲染前读,
386
- * 故 external 先到的 mark 不会误吞后到的内部帧(dedup-lane 单测双向钉)。 */
387
235
  const cardEnqueuedRunIds = new Set();
388
236
  export function isWorkflowCompletionCardEnqueued(runId) {
389
237
  return cardEnqueuedRunIds.has(runId);
390
238
  }
391
- const outstandingRuns = new Map(); // runId → registeredAt(ms)
392
- // ── D4(212 对拍):outstanding 计数订阅面——REPL 空闲态渲「✻ Waiting for N dynamic
393
- // workflow(s) to finish」(CC 2.1.212 同形,wf-ui-cc2 ground truth)。仅通知计数变化 ——
394
- // notif-03 起「计数」含 `outstandingAbandonedCount()`,故 bg 半场的 TTL 放弃也发一次通知
395
- // (那一格变了,消费方要重渲「N 个后台任务已停止等待」)。
396
- //
397
- // 🔴 [2393] notif-F9(2026-08-03 全窗复审)**判据纠偏 + 契约写清**(裁定:机制不改,把口径写死)。
398
- // 复审的观察是「bg 半场信号不对称:放弃发通知、登记不发,而订阅面两个计数口都不含 bg ⇒ 一次 bg
399
- // 放弃叫醒全部订阅者却什么都没变(空转重渲)」。逐口核过之后这条**不成立**,但它指出的真缺口
400
- // (契约没写清)是真的,所以写在这里:
401
- // · 订阅契约 = 「**任一可读计数**发生变化」,可读计数恰好三个:`outstandingWorkflowCount()` /
402
- // `outstandingDeliverableWorkflowCount()` / `outstandingAbandonedCount()`;
403
- // · bg **放弃**必须发:它改变第三个(`abandonedCount++`)—— 不是空转;
404
- // · bg **登记**必须不发:bg 在飞数**没有**可读口(刻意:headless 退出门只认 workflow 半场,
405
- // 见 `outstandingDeliverableWorkflowCount` 的头注),不改变任何可读量的事件不许叫醒订阅者
406
- // (叫醒 = 每次 async_launched 回执触发一次全树重渲,而消费方一个字都读不到新东西)。
407
- // ⇒ 「不对称」是这条契约的**正确后果**,不是漏。谁想给 bg 在飞数开一个可读口,必须同批让
408
- // `registerOutstandingBgTask` 发通知(两半共享前提,[paired-mechanisms-must-share-premise])。
239
+ const outstandingRuns = new Map();
409
240
  const outstandingListeners = new Set();
410
241
  function notifyOutstanding() {
411
242
  for (const l of [...outstandingListeners]) {
@@ -413,16 +244,12 @@ function notifyOutstanding() {
413
244
  l();
414
245
  }
415
246
  catch {
416
- /* 订阅方异常不拖累通知链 */
417
247
  }
418
248
  }
419
249
  }
420
250
  export function outstandingWorkflowCount() {
421
251
  return outstandingRuns.size;
422
252
  }
423
- /** headless(-p)退出门用([1340] BGB 分化案):**可送达**的 outstanding 数——probe 未装
424
- * (mock/离线)恒 0,print 驱动器不为永远等不到的通知悬置;probe 在装则 watcher 5s 节拍
425
- * 必然送达或 TTL 放弃(2h 上界=run 蒸发极端形,与 CC 等本地 bg 任务的无界形同族)。 */
426
253
  export function outstandingDeliverableWorkflowCount() {
427
254
  return statusProbe ? outstandingRuns.size : 0;
428
255
  }
@@ -433,10 +260,6 @@ export function subscribeOutstandingWorkflows(listener) {
433
260
  };
434
261
  }
435
262
  let abandonedCount = 0;
436
- /**
437
- * 🔴 放弃观察的**唯一出口**。`id` 是各自台账的键(bg 侧 = `taskId:seq` 复合键,留痕里可直读)。
438
- * 未命中(键已不在台账)⇒ 不计数、不留痕:放弃必须是**真发生过**的事,别把「本来就没有」算成放弃。
439
- */
440
263
  function abandonOutstanding(kind, id, reason) {
441
264
  const removed = kind === 'bg-task' ? outstandingBgTasks.delete(id) : outstandingRuns.delete(id);
442
265
  if (!removed)
@@ -445,24 +268,8 @@ function abandonOutstanding(kind, id, reason) {
445
268
  const line = `abandoned ${kind} ${id} — reason=${reason}, waited > WATCH_TTL_MS(${WATCH_TTL_MS}ms); ` +
446
269
  'no completion notification will ever be delivered for it';
447
270
  traceNotif(line);
448
- // 🔴 [2393] notif-F2(2026-08-03 全窗复审)**边界定谳**:本条修复到库边界为止,用户可见那一端
449
- // 由宿主闭合,已挂提货单(`docs/refactor/README.md`「提货单 / 破坏性变更清单」节的**宿主侧待办**表,
450
- // 背景与「库侧为什么停在这里」见 `docs/refactor/WAVE2-RESIDUALS.md` [2393] 那一节第 2 条)。
451
- // 为什么不在库内补偿:
452
- // · `traceNotif` 受 SEMA_DEBUG 门控是**刻意**的 —— 那是诊断面;把放弃行为默认打进用户 stderr
453
- // 等于库替宿主决定 UI(headless `-p` 的 stdout 是协议面,污染它是真回归);
454
- // · 走 `hostLog` 口需要 `notifications.ts` import `host.ts`,实测会把 **A 层闭包 22→24 文件**
455
- // (`run-client-core-portability-test.mjs` 的 `MAX_ADAPT_CLOSURE_FILES` 只许降)—— 为一条诊断
456
- // 线把 A 层拽进宿主口体系,代价大于收益([no-hasty-compensation-final-fix-first] 三问);
457
- // · 用户可见那一端的**量**已经在场且可读:`outstandingAbandonedCount()`(+ `notificationDropCounters()`
458
- // 六格)。缺的是三端的渲染,那是宿主工单,不是库内补偿位。
459
271
  notifyOutstanding();
460
272
  }
461
- /**
462
- * 🔴 已**停止等待**的 outstanding 条目数(workflow + bg 合计,process-lifetime 只增)。
463
- * 消费方(footer / headless 退出前的收尾行)据此把「N 个后台任务已停止等待」与「都送达了」
464
- * 分开渲——H1:排队与放弃对消费方必须可分辨。additive:既有计数口的语义一字节不变。
465
- */
466
273
  export function outstandingAbandonedCount() {
467
274
  return abandonedCount;
468
275
  }
@@ -485,74 +292,33 @@ export function notificationDropCounters() {
485
292
  let statusProbe = null;
486
293
  let watchTimer = null;
487
294
  const WATCH_INTERVAL_MS = 5000;
488
- const WATCH_TTL_MS = 2 * 60 * 60 * 1000; // 2h 兜底放弃(run 蒸发/owner 不可读时不永轮)
489
- /** liveClient 在构造会话 client 时注册(带正确 baseUrl/token/principal 的 workflows.get)。 */
295
+ const WATCH_TTL_MS = 2 * 60 * 60 * 1000;
490
296
  export function installWorkflowStatusProbe(probe) {
491
297
  statusProbe = probe;
492
298
  }
493
- /** bg 子代生命周期号的首周期值(SendMessage 复活即 +1;wire 缺 seq ⇒ 按首周期解释)。 */
494
299
  const BG_FIRST_SEQ = 1;
495
- /**
496
- * 🔴 [2393] notif-F8(2026-08-03 全窗复审):seq 归一的**唯一铸点**。
497
- * 修前有两处各写各的:登记口 `Number.isFinite && >= BG_FIRST_SEQ` 才收(否则钳到首周期,再 floor),
498
- * enqueue 口只有 `n.seq ?? BG_FIRST_SEQ`(零校验),而帧臂(`fleet/fleetLedger.ts` 的
499
- * `applyBgNotification`)是**原样透传**。后果不是「多写一遍」而是两条通道的去重键会**错开**:
500
- * server 哪天发 0 基 seq(或小数)⇒ watcher 合成走已钳成 1 的键 `id:1:status`,帧通道走原值键
501
- * `id:0:status`,两键不撞 ⇒ 跨通道去重整体失效,同一次完成被喂给模型两遍。
502
- * notif-02 立的是「键必须含 seq」,归一没收成单口就等于键的**语义**没收口。
503
- */
504
300
  function normalizeBgSeq(seq) {
505
301
  return typeof seq === 'number' && Number.isFinite(seq) && seq >= BG_FIRST_SEQ ? Math.floor(seq) : BG_FIRST_SEQ;
506
302
  }
507
- /**
508
- * 🔴 notif-02:观察台账的键是 **(taskId, seq) 复合键**,不是裸 taskId。
509
- * 裸 taskId 时 seq≥2 的复活周期与首周期同键 ⇒ 复活周期结构性进不了台账,而 watcher 存在的
510
- * 全部理由就是「bg 完成通知 idle 期没有到达通道」—— 复活周期因此退回那条已判定不可靠的推送通道。
511
- */
512
303
  function bgOutstandingKey(taskId, seq) {
513
304
  return `${taskId}:${seq}`;
514
305
  }
515
306
  const outstandingBgTasks = new Map();
516
307
  let bgStatusProbe = null;
517
- /**
518
- * R4(clay 五报 07-31)委派 prompt 台账:`async_launched` 回执带全 {task_id, description, prompt}
519
- * 三件,但此前 prompt 只进消息 payload(ctrl+o expand 那条腿),任务行/详情页(↓ manage)取不到,
520
- * 只能绕道等引擎 running 期不发的 transcriptId 去转录读面捞 ⇒ Prompt 节恒空。
521
- * 这里按行 id 记一份(与 fleet 行 rowIdTail 同 keyspace),行消费端建行时取用。
522
- * 与 outstandingBgTasks 分开存:后者随通知摘除,prompt 在行的整个生命周期都要可取。
523
- * process-lifetime 有界:bg 子代数量 = 人手派发量级,不设逐出。
524
- */
525
308
  const bgTaskPrompts = new Map();
526
- /** 委派 prompt 取件口(行消费端建行/详情页兜底用)。未登记 ⇒ undefined(诚实缺席)。 */
527
309
  export function outstandingBgTaskPrompt(taskId) {
528
310
  return bgTaskPrompts.get(taskId);
529
311
  }
530
312
  export function installBgTaskStatusProbe(probe) {
531
313
  bgStatusProbe = probe;
532
314
  }
533
- /**
534
- * bridge 在 Agent async_launched 回执(structured type:'agent')经过时登记。按 (taskId,seq) 幂等。
535
- *
536
- * 🔴 notif-02(两账分离):登记闸**不再查 `notifiedRunIds`**。那个集合是「模型通知去重」账
537
- * (哪次完成已经喂给模型了),不是「是否还要观察」账;两账混用的后果是 seq≥2 的复活周期
538
- * 永远登记不进来(`notifiedRunIds` 全文无 delete 站点,首周期一通知就是终身黑名单)。
539
- * 首周期若确已送达,由 watcher 的收摊臂在下一拍摘除(见 `tickWatchInner` bg 半场),
540
- * 代价是一次 no-op 遍历,而不是一整个完成周期的结构性缺席。
541
- *
542
- * 🔴 [2393] F-1:`seq` 缺席时这里仍然**按首周期解释,不在登记口猜周期号** —— 猜出来的周期号会
543
- * 把「已终局任务的回执重放」也升成新周期,方向相反地造出重复通知。周期号只在 watcher 那侧、
544
- * 拿到 probe 的「它又在跑」这个证据之后才前进(见 `tickWatchInner` 的收摊臂三档)。
545
- *
546
- * @param seq bg 子代生命周期号;wire 未带 ⇒ 按首周期(BG_FIRST_SEQ)解释,复活周期由 watcher 补。
547
- */
548
315
  export function registerOutstandingBgTask(taskId, description, prompt, seq) {
549
316
  if (!taskId)
550
317
  return;
551
- // prompt 台账先记(与 watcher 登记的幂等早退解耦:重复回执/已通知任务的 prompt 仍要可取)。
552
318
  if (typeof prompt === 'string' && prompt.length > 0 && !bgTaskPrompts.has(taskId)) {
553
319
  bgTaskPrompts.set(taskId, prompt);
554
320
  }
555
- const cycle = normalizeBgSeq(seq); // notif-F8:唯一铸点
321
+ const cycle = normalizeBgSeq(seq);
556
322
  const key = bgOutstandingKey(taskId, cycle);
557
323
  if (outstandingBgTasks.has(key))
558
324
  return;
@@ -565,36 +331,22 @@ export function registerOutstandingBgTask(taskId, description, prompt, seq) {
565
331
  });
566
332
  ensureWatchTimer();
567
333
  }
568
- /** 本壳亲手启动过的 workflow run(process-lifetime,只增不摘——outstandingRuns 会随完成摘除,
569
- * 这个集合是 fleet 行渲染的 own 判别台账;clay 2026-07-23 双 session 串扰案:旧代际引擎对
570
- * fleet 行帧广播,workflow 行必须有壳侧行级判别兜底,与 bg_notification 的 isOwnEngineRun
571
- * 台账对称)。诚实边界同款:壳重启/resume 后旧 workflow 行在老引擎下会被藏(1.244+ server
572
- * 已按会话过滤=sessionScoped 让位,不受此限)。 */
573
334
  const ownWorkflowRuns = new Set();
574
335
  export function isOwnWorkflowRun(runId) {
575
336
  return ownWorkflowRuns.has(runId);
576
337
  }
577
- /** 本壳亲手启动过的 workflow run 列表(Set 插入序 = 启动序;/workflows 命令的目标 run 选择用,
578
- * cmd-workflows.tsx——fleet source 无行可选时的兜底 id 源)。 */
579
338
  export function listOwnWorkflowRuns() {
580
339
  return [...ownWorkflowRuns];
581
340
  }
582
- /**
583
- * CC-37(0.72.2,cli [7468] ②):只登 own 台账、不动 outstanding —— 给 adapter 的 workflow lane 门用:带 `workflowRunId`
584
- * 的 `task_progress` 帧**且**其父指向本会话见过的 Workflow 工具卡(own-run 正向证明)时,在为其铸行的那一拍就登记,
585
- * 不再等 run_workflow 回执;此前 fleet 行已渲 13 s 而 `listOwnWorkflowRuns()` 仍空。幂等;别的会话的 workflow 帧
586
- * (无本会话卡为父)**不登**。
587
- */
588
341
  export function noteOwnWorkflowRun(runId) {
589
342
  if (!runId)
590
343
  return;
591
344
  ownWorkflowRuns.add(runId);
592
345
  }
593
- /** bridge 在 run_workflow 回执(structured type:'workflow-run')经过时登记。幂等;已通知的不再登记。 */
594
346
  export function registerOutstandingWorkflowRun(runId) {
595
347
  if (!runId)
596
348
  return;
597
- ownWorkflowRuns.add(runId); // own 台账先记(与 outstanding 摘除解耦)
349
+ ownWorkflowRuns.add(runId);
598
350
  if (notifiedRunIds.has(runId) || outstandingRuns.has(runId))
599
351
  return;
600
352
  outstandingRuns.set(runId, Date.now());
@@ -605,46 +357,18 @@ function ensureWatchTimer() {
605
357
  if (watchTimer !== null)
606
358
  return;
607
359
  watchTimer = setInterval(() => {
608
- // 定时器回调不能 await:tick 的失败必须在这里落地,否则变成 unhandledRejection 打死宿主进程。
609
360
  tickWatch().catch(err => {
610
361
  traceNotif(`watcher tick threw: ${err instanceof Error ? err.message : String(err)}`);
611
362
  });
612
363
  }, WATCH_INTERVAL_MS);
613
- // 不阻止进程退出(域词表-14:unrefTimer 单一实现)
614
364
  unrefTimer(watchTimer);
615
365
  }
616
- // ── notif-01(D4 必有 settle 路径 + A5 界不得来自第三方默认值)────────────────────────────────
617
- // 病形:重入护栏是**不带超时的单飞**,而 tickWatchInner 里两个 `await probe(...)` 没有任何
618
- // deadline —— probe 由宿主注入,今天两处实装都只是 `await client.runs/workflows.get(...)`,
619
- // 界完全外包给了宿主的 HTTP 栈。probe 的 promise 若不 settle:护栏永久为真 ⇒ 此后每拍早退 ⇒
620
- // **住在 tick 里的 TTL 清扫也一并失效** ⇒ outstanding 永不清空 ⇒ watchTimer 永不停 ⇒
621
- // `outstandingDeliverableWorkflowCount()` 恒 >0 ⇒ headless(-p)的退出门恒真、进程永不退出。
622
- // 修法三件(缺一条链就还在):①每个 probe await 包 deadline;②护栏改时间戳 + 强制复位;
623
- // ③TTL 清扫提到护栏**之前**,让「探测卡死」不连坐「超时放弃」。
624
- /**
625
- * 单次探测的截止。取值理由:节拍是 WATCH_INTERVAL_MS(5s),本值 = **2 个节拍**——
626
- * · 下界:比一个节拍大,慢但活着的 probe(一次往返略超 5s)不会被切成「必失败」;
627
- * · 上界:小整数倍,单个条目最坏只压住 2 拍,而不是把整条链交给宿主 HTTP 栈的默认值。
628
- */
629
366
  const PROBE_TIMEOUT_MS = 2 * WATCH_INTERVAL_MS;
630
- /**
631
- * 在飞 tick 的强制复位门。取值理由:一拍最坏耗时 ≈ 条目数 × PROBE_TIMEOUT_MS,故门必须显著
632
- * 大于单条截止,否则条目一多就会误判卡死。6 倍截止 = 一拍里有 3 个条目全部吃满超时仍不算卡死。
633
- * ⚠️ 复位**不能**中止那个已在飞的 tick(probe promise 不在我们手里),只是允许下一拍照常起 ——
634
- * 代价是短时间内可能有两拍并发探测同一条目;相对「进程永不退出」这是明确划算的取舍,
635
- * 且 `stuckTickResets` 恒应为 0,>0 说明有 probe 连 deadline 都截不住,是要查的账。
636
- */
637
367
  const STUCK_TICK_RESET_MS = 6 * PROBE_TIMEOUT_MS;
638
- /** 测试可覆写的时序(仅测试钩写;生产恒 null ⇒ 用上面两个常量)。 */
639
368
  let probeTimeoutOverrideMs = null;
640
369
  let stuckTickResetOverrideMs = null;
641
370
  const probeTimeoutMs = () => probeTimeoutOverrideMs ?? PROBE_TIMEOUT_MS;
642
371
  const stuckTickResetMs = () => stuckTickResetOverrideMs ?? STUCK_TICK_RESET_MS;
643
- /**
644
- * probe 超时的**类型化**错误(错误判别一律 instanceof,禁按文案前缀判)。
645
- * 不导出:它只在本模块的 catch 臂之间流动,宿主注入的 probe 永远看不到它 —— 公面每多一个名字
646
- * 就是多一条对外承诺,没有消费者的错误类别不该上公面(要用时再导出并同批更新导出基线)。
647
- */
648
372
  class ProbeDeadlineError extends Error {
649
373
  timeoutMs;
650
374
  constructor(timeoutMs) {
@@ -653,24 +377,11 @@ class ProbeDeadlineError extends Error {
653
377
  this.timeoutMs = timeoutMs;
654
378
  }
655
379
  }
656
- /**
657
- * 给一个不受我们控制的 promise 加一道**本仓自己声明**的界(A5)。超时 ⇒ reject
658
- * `ProbeDeadlineError`,落进调用方既有的「单次探测失败」catch 臂,TTL 继续兜底。
659
- * 计时器 unref(存在则),绝不因为一次探测把宿主进程钉住。
660
- */
661
380
  function withProbeDeadline(p, timeoutMs) {
662
381
  return new Promise((resolve, reject) => {
663
382
  const timer = setTimeout(() => {
664
383
  reject(new ProbeDeadlineError(timeoutMs));
665
384
  }, timeoutMs);
666
- // 🔴 [2393] notif-F7(2026-08-03 全窗复审)**不 unref**:这正是 `unrefTimer.ts:6-9` 逐字写死的
667
- // 域词表-13 定谳所指的那一类 —— 「被 await 的 race/budget 臂上的定时器绝不能 unref(会让进程
668
- // 在它 settle 前提前退出,RB-447 同形)」。本 deadline 定时器是 `await withProbeDeadline(...)`
669
- // **唯一的 settle 来源**(probe 永不 settle 时):unref 之后若宿主此刻没有别的活句柄
670
- // (watchTimer 自己也是 unref 的),事件循环判定可退出,回调不再执行,那一拍 tick 永不 settle
671
- // —— notif-01① 「必有 settle 路径」的保证就不是自持的,而是押在「宿主别处总有活句柄」上。
672
- // 两条成对臂已经保证它不会拖住进程:①probe settle 时下面两个 `clearTimeout(timer)` 必执行;
673
- // ②它自己到点就 reject 并被 clear,寿命上界 = timeoutMs(默认 PROBE_TIMEOUT_MS)。
674
385
  p.then(v => {
675
386
  clearTimeout(timer);
676
387
  resolve(v);
@@ -680,34 +391,10 @@ function withProbeDeadline(p, timeoutMs) {
680
391
  });
681
392
  });
682
393
  }
683
- // 重入护栏(对抗复审§5):慢 probe(>5s)时多个 tick 并发走同一 outstanding 快照=同任务
684
- // 重复探测×N。单飞:在飞即跳过本 tick,下个节拍自然补上。
685
- // 🔴 布尔改**起始时间戳**(notif-01②):布尔形没有任何复位路径能对付「await 永不返回」。
686
394
  let tickStartedAtMs = null;
687
- /** 在飞 tick 的世代号:强制复位后旧 tick 收口时不许清掉**新** tick 的时间戳。 */
688
395
  let tickGeneration = 0;
689
- /**
690
- * TTL 清扫(notif-03 的单口收敛 + notif-01③ 的位置修正)。
691
- * 🔴 必须在重入护栏**之前**跑:清扫住在 tickWatchInner 里时,一个卡死的 probe 会连坐 TTL,
692
- * 于是「2h 上界」这个 `outstandingDeliverableWorkflowCount()` 正当性的另一半也一起失效。
693
- */
694
- /**
695
- * 🔴 [2393] notif-F10(2026-08-03 全窗复审):**正被在飞 probe 臂持有**的键集(bg 侧存复合键,
696
- * workflow 侧存 runId)。`abandonOutstanding` 自己的前提是「放弃必须是**真发生过**的事」,但它
697
- * 上一版只挡住了「键不在台账」,没挡住「键正被在飞臂持有」:上一拍 probe 还在飞(它手里握着这
698
- * 条目的快照),这一拍 sweep 判它超 TTL ⇒ 计数 +1 并打出「no completion notification will ever
699
- * be delivered for it」,几十毫秒后那个 probe 返回 terminal ⇒ 照常合成并投递通知。
700
- * 留痕是假话,`outstandingAbandonedCount()`(消费方据以渲「N 个后台任务已停止等待」的诚实计数)
701
- * 被灌一格假数 —— 正是 notif-03 立案要根治的那一类观测面撒谎。
702
- * 🔴 让位必须**自己有界**,否则它就变成 notif-01③ 刚修掉的那个形(「清扫住在 tick 体内 ⇒ 一个卡死的
703
- * probe 连坐 TTL ⇒ headless 退出门恒真、进程永不退出」)。所以记的是**在飞臂的起始时刻**,
704
- * 让位只在「这条臂还没超过它自己的 deadline」时成立:臂一旦活过 `probeTimeoutMs()`(= 它本该被
705
- * 截断的时刻),它就不再是「马上会给出答案的那条臂」,TTL 照常执行。
706
- * ⇒ 正常路径:多等一拍(5s),换来放弃计数不被灌假数;病理路径:与修前逐字同形,liveness 不变。
707
- */
708
396
  const bgProbeInFlight = new Map();
709
397
  const runProbeInFlight = new Map();
710
- /** 在飞臂是否仍在它自己的 deadline 之内(超了就不再让位 —— 见上方 notif-F10 头注)。 */
711
398
  function probeArmStillWithinDeadline(startedAt, now) {
712
399
  return startedAt !== undefined && now - startedAt <= probeTimeoutMs();
713
400
  }
@@ -716,8 +403,6 @@ function sweepExpiredOutstanding(now) {
716
403
  if (now - meta.registeredAt <= WATCH_TTL_MS)
717
404
  continue;
718
405
  if (probeArmStillWithinDeadline(bgProbeInFlight.get(key), now)) {
719
- // notif-F10:在飞臂正握着它、且还没超它自己的 deadline —— 这一拍不判放弃
720
- // (判了就可能马上被那条臂证伪:计数与留痕当场成假话)。
721
406
  traceNotif(`ttl sweep deferred for bg-task ${key}: a probe arm is still in flight within its deadline`);
722
407
  continue;
723
408
  }
@@ -733,7 +418,6 @@ function sweepExpiredOutstanding(now) {
733
418
  abandonOutstanding('workflow-run', runId, 'ttl');
734
419
  }
735
420
  }
736
- /** @param nowMs 时钟注入(仅测试钩用;生产走 Date.now())。 */
737
421
  async function tickWatch(nowMs) {
738
422
  const now = nowMs ?? Date.now();
739
423
  sweepExpiredOutstanding(now);
@@ -757,36 +441,17 @@ async function tickWatch(nowMs) {
757
441
  await tickWatchInner(now);
758
442
  }
759
443
  finally {
760
- // 被强制复位过就别清:那面时间戳已经属于后来的那一拍了。
761
444
  if (tickGeneration === myGeneration)
762
445
  tickStartedAtMs = null;
763
446
  }
764
447
  }
765
- /** @param now 本拍的时钟(与 `sweepExpiredOutstanding` **同一个来源** —— notif-F10 的让位判据比的是
766
- * 「在飞臂活了多久 vs 它自己的 deadline」,两边取不同的钟就会得出 3 小时前起飞的荒谬结论)。 */
767
448
  async function tickWatchInner(now) {
768
- // bg agent 半场(与 workflow 半场同节拍;TTL 已提到 tickWatch 的护栏之前统一清扫。
769
- // probe 未装=mock/离线,只等推送补发)
770
449
  const bgProbe = bgStatusProbe;
771
450
  for (const [key, meta] of [...outstandingBgTasks]) {
772
- // 🔴 [2393] F-1:收摊臂的删除必须**挣得**。此前的判据是裸 taskId 黑名单
773
- // (`meta.seq <= BG_FIRST_SEQ && notifiedRunIds.has(meta.taskId)`),它只证明「这个 id 曾经
774
- // 送达过某次完成」;而 `structured.seq` 缺席时复活周期的 cycle 塌回首周期,于是这条臂拿
775
- // **首周期的送达记录**把**第二周期**的观察条目在下一拍秒删,probe 一次不跑 —— 与 notif-02
776
- // 修前的静默丢通知逐字相同,且删在 enqueue 之前 ⇒ 三个既有计数器一格都摸不到。
777
- // 新判据分三档,每一档都拿得出理由:
778
- // ① 这一周期确已送达(周期键台账)⇒ 才谈得上收摊;
779
- // ①a 条目**登记在那次送达之前** ⇒ 它就是那次送达自己的观察条目,无歧义,零成本收摊
780
- // (= 旧行为的正当那一半,常态路径,不多花一次探测);
781
- // ①b 条目**登记在送达之后** ⇒ 有歧义:复活周期?还是已终局任务的回执重放?靠 probe 定夺:
782
- // ② probe 说它**真终局** ⇒ 就是那个已送达的周期,收摊(重放回执落这里,不会二次喂模型);
783
- // ③ probe 说它**又在跑** ⇒ 这按构造是一个**新的完成周期**(裸 id 黑名单对它零判别力),
784
- // 把条目升到下一个周期号继续观察,并留痕。probe 未装 ⇒ 没有可挣得的证据,沿用旧的盲摘
785
- // 但记账;probe 失败/答不上来 ⇒ 不删也不升(TTL 兜底),绝不拿探测失败当终局证据。
786
451
  const delivered = notifiedBgCycleAtLeast(meta.taskId, meta.seq);
787
452
  if (delivered !== undefined) {
788
453
  if (meta.registeredOrder < delivered.order) {
789
- outstandingBgTasks.delete(key); // ①a 推送/别的通道已送达本条目观察的那个周期 — 收摊
454
+ outstandingBgTasks.delete(key);
790
455
  continue;
791
456
  }
792
457
  if (!bgProbe) {
@@ -795,21 +460,21 @@ async function tickWatchInner(now) {
795
460
  continue;
796
461
  }
797
462
  let alive;
798
- bgProbeInFlight.set(key, now); // notif-F10:在飞期间(且未超 deadline)sweep 不许把它判成「放弃」
463
+ bgProbeInFlight.set(key, now);
799
464
  try {
800
465
  const res = await withProbeDeadline(bgProbe(meta.taskId), probeTimeoutMs());
801
466
  if (res === null || res === undefined)
802
- continue; // 答不上来 ≠ 终局,留给下一拍/TTL
467
+ continue;
803
468
  alive = !res.terminal;
804
469
  }
805
470
  catch {
806
- continue; // 探测失败(含 ProbeDeadlineError)不构成删除证据
471
+ continue;
807
472
  }
808
473
  finally {
809
474
  bgProbeInFlight.delete(key);
810
475
  }
811
476
  if (!alive) {
812
- outstandingBgTasks.delete(key); // 真终局 = 已送达的那个周期,收摊
477
+ outstandingBgTasks.delete(key);
813
478
  continue;
814
479
  }
815
480
  const promoted = Math.max(meta.seq, delivered.cycle) + 1;
@@ -826,15 +491,13 @@ async function tickWatchInner(now) {
826
491
  }
827
492
  if (!bgProbe)
828
493
  continue;
829
- bgProbeInFlight.set(key, now); // notif-F10:同上 —— 在飞臂持有期间(未超 deadline)TTL 让位
494
+ bgProbeInFlight.set(key, now);
830
495
  try {
831
496
  const res = await withProbeDeadline(bgProbe(meta.taskId), probeTimeoutMs());
832
497
  if (res?.terminal) {
833
498
  outstandingBgTasks.delete(key);
834
499
  enqueueBgChildNotification({
835
500
  taskId: meta.taskId,
836
- // 🔴 seq 必须透传:不带 seq 时键退化成 `taskId:1:status`,与首周期键碰撞 ⇒ 被去重臂
837
- // 静默吞掉,而台账条目已经先删了 ⇒ 该完成**永久丢失**(clay 图2 案在 watcher 车道的复刻)。
838
501
  seq: meta.seq,
839
502
  status: res.status,
840
503
  summary: `Background agent "${meta.description}" ${res.status}`,
@@ -842,7 +505,6 @@ async function tickWatchInner(now) {
842
505
  }
843
506
  }
844
507
  catch {
845
- // 单次探测失败(含 ProbeDeadlineError 截断)不放弃:网络抖动照旧,TTL 兜底
846
508
  }
847
509
  finally {
848
510
  bgProbeInFlight.delete(key);
@@ -852,12 +514,12 @@ async function tickWatchInner(now) {
852
514
  for (const runId of [...outstandingRuns.keys()]) {
853
515
  if (notifiedRunIds.has(runId)) {
854
516
  if (outstandingRuns.delete(runId))
855
- notifyOutstanding(); // 推送/终态轮询卡已送达 —— 收摊
517
+ notifyOutstanding();
856
518
  continue;
857
519
  }
858
520
  if (!probe)
859
- continue; // live client 未装(mock/离线)→ 只等推送
860
- runProbeInFlight.set(runId, now); // notif-F10:在飞期间(且未超 deadline)sweep 不许把它判成「放弃」
521
+ continue;
522
+ runProbeInFlight.set(runId, now);
861
523
  try {
862
524
  const res = await withProbeDeadline(probe(runId), probeTimeoutMs());
863
525
  if (res?.terminal) {
@@ -871,74 +533,43 @@ async function tickWatchInner(now) {
871
533
  }
872
534
  }
873
535
  catch {
874
- // 单次探测失败(含 ProbeDeadlineError 截断)不放弃:网络抖动照旧,TTL 兜底
875
536
  }
876
537
  finally {
877
538
  runProbeInFlight.delete(runId);
878
539
  }
879
540
  }
880
541
  }
881
- /**
882
- * bg_notification 帧桥([446] 帧契约,service 1.119 上线):session 级后台子代的
883
- * 完成/终态通知走常开 fleet lane → 这里转入 #21 的 pending-notification 注入链
884
- * (idle 自动注入,彻底关掉「bg 完成通知等下次输入才 drain」的死窗)。
885
- * (taskId,status) 去重=外圈皮带(durable park 腿重放/at-least-once 双投自免)。
886
- */
887
542
  const bgNotifiedKeys = new Set();
888
543
  export function enqueueBgChildNotification(n) {
889
- const cycle = normalizeBgSeq(n.seq); // notif-F8:与登记口同一个铸点(零校验的 `?? 1` 会让两条通道的键错开)
544
+ const cycle = normalizeBgSeq(n.seq);
890
545
  const key = `${n.taskId}:${cycle}:${n.status}`;
891
- // 🔴 notif-03 同族(C5):两条静默 return 都记账 + 留痕 —— 不记账时「按设计去重」与
892
- // 「seq 丢了导致完成被吞」在观测上完全等价,而后者是用户永远收不到通知的那一类。
893
546
  if (bgNotifiedKeys.has(key)) {
894
547
  bgDedupDropped++;
895
548
  traceNotif(`bg notification suppressed (same key already delivered) key=${key}`);
896
549
  return;
897
550
  }
898
- // cross-channel 去重(clay 2026-07-06 截图疑似双唤醒定谳):同一完成可能同时走
899
- // workflow_complete/probe 链(notifiedRunIds 键空间)与 bg_notification 帧——两空间互认,
900
- // 任一链注入过即跳过,并反向 seed。仅首周期查(revive 周期是新完成,probe 链只报首周期
901
- // 终态,不构成双投)。
902
551
  if (cycle <= BG_FIRST_SEQ && notifiedRunIds.has(n.taskId)) {
903
552
  bgCrossChannelDropped++;
904
553
  traceNotif(`bg notification suppressed (cross-channel: run already notified) taskId=${n.taskId} seq=${cycle} status=${n.status}`);
905
554
  return;
906
555
  }
907
556
  bgNotifiedKeys.add(key);
908
- // 🔴 完成三本账(完成去重 / 周期 / 完成卡)与面板 settle **只认通知自己的终态词**(completed / failed / killed / cancelled)。
909
- // 非终态(running / queued / event / unknown / 未来词)只过上面那本**逐条**去重账:它若占了完成账,同一任务随后的
910
- // 真终态会被跨通道去重吞掉 ⇒ 零 end、常驻标永不摘(异源对抗复审)。
911
557
  const terminal = isTaskNotificationTerminalStatus(n.status);
912
558
  if (terminal) {
913
- // [2393] F-1:周期维必须跟着前进 —— 收摊臂删条目的唯一证据就是这一格。
914
559
  markRunNotified(n.taskId, cycle);
915
560
  cardEnqueuedRunIds.add(n.taskId);
916
561
  }
917
- // #6 通知-settle 边(合成半场):bg 子代行不再被 turn sweep 假结(session 常驻台账),真终态
918
- // 唯二来源 = 推送帧(bridge task_notification 臂)与本合成链(probe/fleet bg_notification 收敛点)。
919
- // 消费端 settle 幂等(running 才动),推送帧先到时此发布为 no-op。
920
562
  if (terminal) {
921
563
  try {
922
- // 0.72.13(test 0.72.12 验收 ①;DS-06 同形存量):只对**通知自己的终态词**(completed / failed / killed / cancelled)settle 面板行。
923
- // 此前对任何 status 都铸 `end`,而 `isError: isTerminalNotSuccess(status)` 对 running / queued / event / 未来词答 false ⇒
924
- // 面板把一条还在跑 / 认不出状态的行结成 **completed** —— 该谓词的 JSDoc 明禁拿它的 false 当成功。非终态 ⇒ 不 settle、常驻标不摘。
925
- // 0.74.4 CC-83:按 wire 词清 —— 尾段若归先发布的 UUID 所有,那把键下的常驻标一起清(主通道与常驻账同一把键)
926
564
  clearEnginePanelTaskResidentByWire(n.taskId, typeof n.seq === 'number' && Number.isInteger(n.seq) && n.seq >= BG_FIRST_SEQ ? { cycleSeq: n.seq } : undefined);
927
565
  publishEngineAgentPanelEvent({
928
566
  kind: 'end',
929
567
  taskId: n.taskId,
930
- // L-215②(0.65.0):读**单铸谓词**而不是内联两词 —— 修前这里只认 `failed`/`killed`,
931
- // 而 core [6908] 的 `blocked` 是 **agent 自报的终态**(不是等人)⇒ 一条自报走不下去的
932
- // 后台 run 在面板上被 settle 成**成功**。🔴 `suspended`/`needs_review` 仍不在表里
933
- // (那两词是「等一次人的决定」,判成终局会把一条正等着你的 run 在面板上判死)。
934
568
  isError: isTerminalNotSuccess(n.status),
935
- // 0.74.0:周期身份 —— **wire 真给了 seq 才带**。上面的 `cycle` 是去重键用的归一值(缺席归一成首周期),
936
- // 那是键不是事实;把它上屏等于对一条没报代际的通知声称「这是第一代」。
937
569
  ...(typeof n.seq === 'number' && Number.isInteger(n.seq) && n.seq >= BG_FIRST_SEQ ? { cycleSeq: n.seq } : {}),
938
570
  });
939
571
  }
940
572
  catch {
941
- /* fail-soft — 面板 settle 失败绝不挡模型通知注入 */
942
573
  }
943
574
  }
944
575
  const message = `<${TASK_NOTIFICATION_TAG}>
@@ -952,18 +583,11 @@ export function enqueueBgChildNotification(n) {
952
583
  });
953
584
  }
954
585
  export function enqueueEngineWorkflowNotification(c) {
955
- // 🔴 [2393] notif-F1(2026-08-03 全窗复审):这条静默 return 是 C5 收口的**第三处漏网拼法**。
956
- // 同批把 bg 侧两条静默 return 都补了计数+留痕(上面 bgDedupDropped / bgCrossChannelDropped),
957
- // 而它的孪生体 workflow 侧一条都没有 —— 而 `traceNotif` 的头注把执行标准写成「每条丢弃/放弃
958
- // 路径都能在同一个前缀下 grep 到」。失败场景:`markEngineWorkflowNotified()`(外部通道 seed)
959
- // 或 probe 链先记账后,推送帧再送同一 runId 的完成 ⇒ 这里静默吞掉,而「按设计去重」与
960
- // 「一条真完成被误吞(runId 铸法变了 / 两条链 id 不同域)」在观测面上完全等价。
961
586
  if (notifiedRunIds.has(c.runId)) {
962
587
  workflowDedupDropped++;
963
588
  traceNotif(`workflow notification suppressed (run already notified) runId=${c.runId} status=${c.status}`);
964
589
  return;
965
590
  }
966
- // [2393] F-1:workflow 侧没有周期概念,周期维按首周期记(与 markEngineWorkflowNotified 同理)。
967
591
  markRunNotified(c.runId);
968
592
  cardEnqueuedRunIds.add(c.runId);
969
593
  const message = `<${TASK_NOTIFICATION_TAG}>
@@ -976,8 +600,6 @@ export function enqueueEngineWorkflowNotification(c) {
976
600
  mode: 'task-notification',
977
601
  });
978
602
  }
979
- /** 测试钩(B2 新增,壳侧原文件没有):清空全部通知台账 + probe 槽 + watcher。
980
- * 🔴 生产绝不调用 —— 台账是 process-lifetime 去重的唯一凭据,清了就会双投。 */
981
603
  export function _resetEngineTaskNotificationForTest() {
982
604
  notifiedRunIds.clear();
983
605
  notifiedBgCycleHigh.clear();
@@ -987,15 +609,6 @@ export function _resetEngineTaskNotificationForTest() {
987
609
  ownWorkflowRuns.clear();
988
610
  bgNotifiedKeys.clear();
989
611
  outstandingListeners.clear();
990
- // 🔴 [2393] notif-F11(2026-08-03 全窗复审):这三件此前漏清 —— 头注自称「清空**全部**通知台账」。
991
- // · `bgTaskPrompts`:首值粘性(已登记的 taskId 不覆写),门里两个用例复用同一 taskId 时
992
- // `outstandingBgTaskPrompt()` 会跨用例返回上一用例的 prompt,reset 拿不掉(既有钉侥幸没撞上,
993
- // 不是被挡住了)。它的 process-lifetime 无逐出是**生产**决定(bg 子代 = 人手派发量级),
994
- // 与测试钩要不要清是两件事。
995
- // · `tickGeneration`:世代号不复位,跨用例累加(今天只做相等比较,不是现症;但「清空全部」
996
- // 这句话要么成立要么改掉,不留半真)。
997
- // · `bgProbeInFlight` / `runProbeInFlight`(notif-F10 新增):上一个用例的 probe 若被用例
998
- // 提前收尾抛下,残留的键会让下一个用例的 TTL sweep 永远让位 = 静默失效的 TTL。
999
612
  bgTaskPrompts.clear();
1000
613
  bgProbeInFlight.clear();
1001
614
  runProbeInFlight.clear();
@@ -1017,52 +630,18 @@ export function _resetEngineTaskNotificationForTest() {
1017
630
  watchTimer = null;
1018
631
  }
1019
632
  }
1020
- /**
1021
- * 测试钩:**驱动一拍** watcher(生产由 setInterval 驱动,外部无入口 ⇒ 护栏/TTL/deadline
1022
- * 三条链在门里全不可达,notif-01/-02/-03 才会一路活到今天)。
1023
- * @param nowMs TTL 判据的时钟注入(WATCH_TTL_MS 是 2h,不注入时钟就只能真等两小时)。
1024
- * 🔴 生产绝不调用。
1025
- */
1026
633
  export async function _tickWatchOnceForTest(nowMs) {
1027
634
  await tickWatch(nowMs);
1028
635
  }
1029
- /**
1030
- * 测试钩:覆写 watcher 时序常量(默认是 10s/60s 量级,门里等不起)。传 null 恢复生产值。
1031
- * 🔴 生产绝不调用;覆写只影响 deadline 与强制复位门,不动 WATCH_INTERVAL_MS/WATCH_TTL_MS。
1032
- */
1033
636
  export function _setWatcherTimingForTest(o) {
1034
637
  probeTimeoutOverrideMs = o?.probeTimeoutMs ?? null;
1035
638
  stuckTickResetOverrideMs = o?.stuckTickResetMs ?? null;
1036
639
  }
1037
- // ══════════════════════════════════════════════════════════════════════════════════════════════
1038
- // ② taskNotificationErrorSupplement — S4-P3(clay 专项;取证 workflow-card-forensics,
1039
- // 2026-07-24「workflow completed 卡下挂 API Error: 503 · draining」案):task-notification
1040
- // 自动提交 turn 的 API-error 行 supplement。
1041
- //
1042
- // 场景:后台 workflow/bg agent 完成 → 完成通知入队自动提交新 turn(enqueueEngineWorkflowNotification/
1043
- // enqueueBgChildNotification → queueProcessor idle 排水)→ 该 turn 的引擎提交失败(典型:撞温切排水的
1044
- // 503 draining,S4-P0 有界重试打光)→ CC 转录形制里 assistant error 行恒以 ⎿ 臂贴在其 user 消息
1045
- // (= 通知卡)正下方,用户误读为「后台任务本身失败」。这里生成一句 supplement 澄清:任务本身已终态,
1046
- // 失败的只是「把结果通知投递给模型」这一轮;/tasks 可查结果。supplement, never mask
1047
- // (detachWire/sandboxDegradeHint 同款姿势):原始 error 行原样保留,只追加一行。
1048
- //
1049
- // 判别:通知 turn 的 objective 就是合成的 `<task-notification>` XML 整体(queueProcessor 自动提交,
1050
- // user 消息 origin kind 'task-notification')——以 trimStart() 后 startsWith 判型,用户手打正文里
1051
- // 【提到】该 tag 不误伤。
1052
- // 渲染:AssistantTextMessage 的 API-error 臂经 splitApiErrorSupplement 把本行拆出来渲 dim。
1053
- // ══════════════════════════════════════════════════════════════════════════════════════════════
1054
- /** 所有 supplement 变体共享的行首 —— 渲染侧拆行锚(splitApiErrorSupplement),两侧必须同步改。 */
1055
640
  const SUPPLEMENT_LINE_PREFIX = 'The background task itself ';
1056
- /** 该 objective 是否为 task-notification 自动提交 turn 的注入 XML(整体即通知,非提及)。 */
1057
641
  export function isTaskNotificationObjective(objective) {
1058
642
  return (typeof objective === 'string' &&
1059
643
  objective.trimStart().startsWith(`<${TASK_NOTIFICATION_TAG}>`));
1060
644
  }
1061
- /**
1062
- * 通知 turn → supplement 文案(非通知 turn → undefined,判别不扩散)。措辞英文、CC 报错语气
1063
- * (事实 + 出路,不惊悚)。status 从 XML `<status>` 读:completed 用定稿句;其余终态如实带
1064
- * status(通知可能报 failed/killed —— 谎称 completed 比不加更糟)。
1065
- */
1066
645
  export function taskNotificationDeliverySupplement(objective) {
1067
646
  if (!isTaskNotificationObjective(objective))
1068
647
  return undefined;
@@ -1072,20 +651,11 @@ export function taskNotificationDeliverySupplement(objective) {
1072
651
  return `${SUPPLEMENT_LINE_PREFIX}completed — only this notification delivery failed. Check /tasks for the result.`;
1073
652
  }
1074
653
  const statusNote = status && status.length > 0 ? ` (status: ${status})` : '';
1075
- // 0.72.12 CC-50(异源对抗复审 R2 [medium]):只对**明确终态**说 already finished。此前这个尾模板对任何词都断言「已结束」,
1076
- // 而同批 normalizeTaskNotification 开始对缺 status 的通知铸 `unknown` ⇒ 补充行重新造出终局确定性(DS-06:兜底位置落正向词,
1077
- // 且模板表达式逃过 AST 五形)。缺席 / unknown / 未来词 / 活跃词 ⇒ 只陈述投递失败,不推断任务结局。
1078
- // 终态词表对账**通知**自己的词(单源 `runTerminal.TASK_NOTIFICATION_TERMINAL_STATUSES`),不借 run 级 `isTerminalStatus`
1079
- // (那张表没有 cancelled、多一个 blocked,语义不同)。
1080
654
  if (isTaskNotificationTerminalStatus(status)) {
1081
655
  return `${SUPPLEMENT_LINE_PREFIX}already finished${statusNote} — only this notification delivery failed. Check /tasks for the result.`;
1082
656
  }
1083
657
  return `${SUPPLEMENT_LINE_PREFIX}is in a state this client cannot confirm${statusNote} — this notification delivery failed and the task's outcome is unknown here. Check /tasks for its current state.`;
1084
658
  }
1085
- /**
1086
- * API-error 行文拆分(渲染层消费):尾部的 supplement 行(若在)分离出来渲 dim,main 保留原始
1087
- * error 字面渲红。无 supplement 的行原样返回 —— 既有 API-error 渲染路径零改变。
1088
- */
1089
659
  export function splitApiErrorSupplement(text) {
1090
660
  const idx = text.lastIndexOf(`\n${SUPPLEMENT_LINE_PREFIX}`);
1091
661
  if (idx === -1)
@@ -1093,28 +663,19 @@ export function splitApiErrorSupplement(text) {
1093
663
  return { main: text.slice(0, idx), supplement: text.slice(idx + 1) };
1094
664
  }
1095
665
  const KNOWN_REASONS = new Set(['no_content', 'unparsed', 'skipped']);
1096
- /**
1097
- * hook_notice 帧的判定本体(壳 ingestHookNoticeFrame 的判定半场逐字等价)。
1098
- * 🔴 归属镜像在**这里**做而不是在 fleetClient 里做 —— #105 变异普查(F3)定谳过:分发条件留在
1099
- * fleetClient 时,离线门只能 grep 源码,而 grep 对「把 'hook_notice' 改成 'hook_noticeZZZ'」
1100
- * 这类变异因注释碰撞+子串命中恒绿。提到这里后离线门直接**行为测本函数**。
1101
- */
1102
666
  export function classifyHookNoticeFrame(frame, opts) {
1103
667
  const f = frame;
1104
668
  if (f?.type !== 'hook_notice')
1105
669
  return { ok: false, drop: 'not-hook-notice' };
1106
670
  if (f.kind !== 'hook_decision_unavailable') {
1107
- // 未来新 kind:纯 observe 道,未知即忽略(不猜语义)。
1108
671
  return { ok: false, drop: 'unknown-kind', kind: String(f.kind) };
1109
672
  }
1110
- // 🔴 归属镜像:server 只对 scoped 连接 fail-closed;未 scoped 连接上这帧可能是任何会话的。
1111
673
  if (!opts.sessionScoped)
1112
674
  return { ok: false, drop: 'not-session-scoped' };
1113
675
  const reason = typeof f.reason === 'string' && KNOWN_REASONS.has(f.reason)
1114
676
  ? f.reason
1115
677
  : 'skipped';
1116
678
  const event = typeof f.event === 'string' ? f.event : '';
1117
- // 非 Stop 事件今天没有承载面(PreToolUse 等等有承载面再接)。
1118
679
  if (event !== 'Stop')
1119
680
  return { ok: false, drop: 'non-stop-event', event, reason };
1120
681
  return {
@@ -1127,52 +688,25 @@ export function classifyHookNoticeFrame(frame, opts) {
1127
688
  },
1128
689
  };
1129
690
  }
1130
- /** 判别口:是不是 hook_notice 帧(宿主 fleet applyFrame 的分发条件;不是则继续走别的臂)。 */
1131
691
  export function isHookNoticeFrame(frame) {
1132
692
  return frame?.type === 'hook_notice';
1133
693
  }
1134
- /**
1135
- * 「Last check」行的诚实文案([1790] 措辞纪律:「本轮未能评估,已放行」方向;detail 是 server
1136
- * 结构化日志同源的机器话,dim 展示原文比意译更可追责)。
1137
- */
1138
694
  export function hookNoticeLastCheckText(n) {
1139
695
  return `couldn't evaluate this turn — proceeded (${n.reason}${n.detail ? `: ${n.detail}` : ''})`;
1140
696
  }
1141
- /** footer 通知条文案前缀(测试锁字面;宿主拼 ` (${reason})`)。 */
1142
697
  export const HOOK_NOTICE_WARN_PREFIX = 'Goal check: couldn\'t evaluate this turn — proceeded';
1143
698
  const KNOWN_FAILURE_REASONS = new Set(['exit_nonzero', 'timeout', 'spawn_failed', 'bad_json']);
1144
699
  const KNOWN_ENTRY_TYPES = new Set(['command', 'http', 'prompt', 'agent']);
1145
- /**
1146
- * 观测帧自由文本上限 —— 与 server `MAX_HOOK_NOTICE_TEXT_CHARS`(`fleet/fleet-bus.d.ts`)同值。
1147
- * 🔴 server 已截过一遍(那一遍拿得到原文,是**承重**的);本包这一遍是**边界兜底**,挡住忘了截断的
1148
- * 未来生产者把一条无界流灌进单行横幅。幂等:输出恒 ≤ 上限。
1149
- */
1150
700
  export const MAX_HOOK_NOTICE_TEXT_CHARS = 512;
1151
701
  function boundedNoticeText(s) {
1152
702
  return s.length <= MAX_HOOK_NOTICE_TEXT_CHARS ? s : s.slice(0, MAX_HOOK_NOTICE_TEXT_CHARS);
1153
703
  }
1154
- /**
1155
- * `hook_non_blocking_failure` 帧的判定本体(#281 的客户端半场)。
1156
- *
1157
- * 🔴 **与 {@link classifyHookNoticeFrame} 是两个口,不是一个口的两条臂**:两族的载荷形不同
1158
- * (那族出 {@link GoalHookNotice},本族出 {@link HookFailureNotice}),把它们塞进同一个返回联合会
1159
- * **破坏既有消费端**(现在 `v.ok` 之后直接读 `v.notice` 的写法要全部改窄化)。分口 ⇒ 姊妹口的行为
1160
- * **逐字节不变**(它对本族仍报 `unknown-kind`,常驻套对这一格有钉),宿主按 `kind` 分派一次即可。
1161
- *
1162
- * 🔴 **闭集位一律「不认就丢」,绝不折成某一档**:与姊妹族「表外 reason 归一到 `skipped`」刻意不同 ——
1163
- * 那一族的 `skipped` 有真兜底语义(「跳过了」),本族**没有**「兜底故障形」这种东西,把一个没见过的
1164
- * 词折成 `exit_nonzero` 就是替引擎编一个它没说过的故障。
1165
- *
1166
- * 🔴 `hookName` 缺席即丢:一条「某个 hook 坏了、但不知道是哪个」的横幅,用户拿它什么都做不了,
1167
- * 只会制造焦虑 —— 而 server 侧这一位是**必填**,缺席意味着这不是一帧合规的本族帧。
1168
- */
1169
704
  export function classifyHookFailureFrame(frame, opts) {
1170
705
  const f = frame;
1171
706
  if (f?.type !== 'hook_notice')
1172
707
  return { ok: false, drop: 'not-hook-notice' };
1173
708
  if (f.kind !== 'hook_non_blocking_failure')
1174
709
  return { ok: false, drop: 'unknown-kind', kind: String(f.kind) };
1175
- // 🔴 归属镜像(与姊妹族同一条 fail-CLOSED):server 只对 scoped 连接过滤。
1176
710
  if (!opts.sessionScoped)
1177
711
  return { ok: false, drop: 'not-session-scoped' };
1178
712
  if (typeof f.reason !== 'string' || !KNOWN_FAILURE_REASONS.has(f.reason))
@@ -1188,7 +722,6 @@ export function classifyHookFailureFrame(frame, opts) {
1188
722
  reason: f.reason,
1189
723
  hookName: boundedNoticeText(f.hookName),
1190
724
  entryType: f.entryType,
1191
- // 🔴 非整数/非数一律降缺席 —— 退出码是给人照着查的,一个 1.5 或 'x' 比没有更坏。
1192
725
  ...(typeof f.exitCode === 'number' && Number.isSafeInteger(f.exitCode) ? { exitCode: f.exitCode } : {}),
1193
726
  ...(typeof f.toolName === 'string' && f.toolName ? { toolName: boundedNoticeText(f.toolName) } : {}),
1194
727
  ...(typeof f.stderr === 'string' && f.stderr ? { stderr: boundedNoticeText(f.stderr) } : {}),
@@ -1197,14 +730,3 @@ export function classifyHookFailureFrame(frame, opts) {
1197
730
  },
1198
731
  };
1199
732
  }
1200
- // 🔴 本族的**横幅文案口** `hookFailureNoticeText` / `HOOK_FAILURE_WARN_PREFIX` 与能力位读口一样,
1201
- // 刻意**不在本文件**,而在 `hooksWireCaps.ts` —— 理由同 `fleetHookFailureNoticeCapable`:本文件在
1202
- // **A 层闭包**内(portability 门 ①b,只降棘轮),而文案口必须过展示消毒单源 `collapseLabel`
1203
- // (在 `fleetTaskDesc.ts`),把那个文件拉进闭包会让棘轮当场涨一格;在本文件手抄第二份字符集
1204
- // 则正是 `fleetTaskDesc` 顶注点名禁止的形(「改一处必须改另一处」)。
1205
- // ⇒ **判定留本文件、展示归那一层**,与包内既有分工一致(本文件只交出**原文** `hookName`/`stderr`,
1206
- // UNTRUSTED-for-display 的消毒责任在渲染侧)。
1207
- // 🔴 本族的**能力位读口** `fleetHookFailureNoticeCapable` 刻意**不放在本文件**,而在
1208
- // `hooksWireCaps.ts` —— 本文件在 **A 层闭包**内(portability 门 ①b,只降棘轮),而能力位要读
1209
- // `engineCapsCache`,把那个文件拉进闭包会让棘轮当场涨一格。能力位本就属 `*WireCaps` 那一层
1210
- // (与 `hooksForWire` 同域),放那里是归位不是绕道。