@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,8 +1,6 @@
1
1
  import { hostLog } from '../host.js';
2
2
  import { engineWireDebugEnabled } from '../engineWireTarget.js';
3
3
  import { engineSessionParam, engineSessionParamFor } from '../engineSessionParam.js';
4
- // `isHookNoticeFrame` 不在这里 re-export —— 它已由 index.ts 的 `export * from './notifications.js'`
5
- // 出口(重复 re-export 只会让同名绑定在包根变含糊)。端要先分诊再喂台账,从包根 import 即可。
6
4
  import { enqueueBgChildNotification, isOwnWorkflowRun } from '../notifications.js';
7
5
  import { clearBgTerminalFacts, getBgTerminalFacts, isOwnEngineRun, recordBgParentRun, recordBgTerminalFacts, recordOwnEngineRun, } from '../subagentContentStore.js';
8
6
  import { rowIdTail } from '../workflow.js';
@@ -10,23 +8,14 @@ import { recordEngineTranscriptId } from '../subagent/engineDelegatedPrompt.js';
10
8
  import { recordFleetRowAgentType } from './fleetRowAgentType.js';
11
9
  import { DEFAULT_SESSION_KEY } from '../sessionSlot.js';
12
10
  import { TERMINAL_FLEET_TASK_STATUSES, projectTasks, projectWorkflows, wireCycleSeq, wireParentId, } from './fleetProjection.js';
13
- /** 终态行留存宽限窗(187 的 agents 页把完成会话留在列表;窗过即净,防长会话堆行)。 */
14
11
  export const TERMINAL_RETAIN_MS = 60_000;
15
- /** REF-CC-044(fleet2-06):per-sessionKey 注册表(W1 同款 `Map<sessionKey, T>` 形)。 */
16
12
  const liveBgViews = new Map();
17
13
  function viewsFor(sessionKey) {
18
14
  return liveBgViews.get(sessionKey) ?? new Set();
19
15
  }
20
- /**
21
- * server 代际证据读面:任一 live fleet 源的 meta 报过 `sessionScoped=true`。
22
- * true ⇒ 到达本连接的**行帧**已经过 server 会话过滤,端侧行级 own 判别可以让位。
23
- * 🔴 只覆盖【行帧】:通知帧的 own-run 台账判别不看本标,恒开(1.244 对缺 ownerSessionId 的通知
24
- * 仍广播,fail-closed 是 1.245+)。
25
- */
26
16
  export function isFleetSessionScoped() {
27
17
  return isFleetSessionScopedFor(DEFAULT_SESSION_KEY);
28
18
  }
29
- /** W1 keyed variant:只看**这一个会话**登记的视图(REF-CC-044)。 */
30
19
  export function isFleetSessionScopedFor(sessionKey) {
31
20
  for (const view of viewsFor(sessionKey)) {
32
21
  try {
@@ -34,16 +23,13 @@ export function isFleetSessionScopedFor(sessionKey) {
34
23
  return true;
35
24
  }
36
25
  catch {
37
- /* 单视图炸不拖累读面 */
38
26
  }
39
27
  }
40
28
  return false;
41
29
  }
42
- /** 引擎侧在飞 bg 子代读数(union 所有未 dispose 的源,按 row.id 去重)。无 live 源 ⇒ `{[], false}`。 */
43
30
  export function readEngineActiveBgTasks() {
44
31
  return readEngineActiveBgTasksFor(DEFAULT_SESSION_KEY);
45
32
  }
46
- /** W1 keyed variant(REF-CC-044):只 union **这一个会话**登记的视图,不跨会话泄漏。 */
47
33
  export function readEngineActiveBgTasksFor(sessionKey) {
48
34
  const byId = new Map();
49
35
  let fresh = false;
@@ -56,61 +42,35 @@ export function readEngineActiveBgTasksFor(sessionKey) {
56
42
  byId.set(row.id, row);
57
43
  }
58
44
  catch {
59
- /* 单视图炸不拖累读面 */
60
45
  }
61
46
  }
62
47
  return { tasks: [...byId.values()], fresh };
63
48
  }
64
- /**
65
- * 清空**这一个会话**的所有 live 源的终态留存池 —— 会话切换(/clear、REPL 内 resume、fork)信号
66
- * 一到就调。R7:留存池是渲染层宽限窗,快照臂刻意不清它(重连不该抽走行);但换了会话继续渲上一
67
- * 会话的终态行(名字 = objective 派生)就是跨会话残影。
68
- */
69
49
  export function clearAllRetainedFleetRows() {
70
50
  clearAllRetainedFleetRowsFor(DEFAULT_SESSION_KEY);
71
51
  }
72
- /** W1 keyed variant(REF-CC-044):只清**这一个会话**的池,不广播到别的会话。 */
73
52
  export function clearAllRetainedFleetRowsFor(sessionKey) {
74
53
  for (const view of viewsFor(sessionKey)) {
75
54
  try {
76
55
  view.clearRetained();
77
56
  }
78
57
  catch {
79
- /* 单视图炸不拖累信号 */
80
58
  }
81
59
  }
82
60
  }
83
- /** 测试钩:摘掉所有登记的视图(同进程多组断言之间互不污染)。 */
84
61
  export function __resetFleetLedgerRegistryForTests() {
85
62
  liveBgViews.clear();
86
63
  }
87
- // ── P1-4:会话过滤参数(取代壳侧 makeSessionScopingFetch)───────────────────────────────────────
88
- /**
89
- * `client.fleet.stream(...)` 的会话过滤参数。缺席(端未装 SessionPort / 还没有会话 id)⇒ `{}`
90
- * = principal 级视图,**与 1.37 之前逐字同行为**(不是错误态)。
91
- *
92
- * 🔴 **默认槽形**(design/285 §2.1):读的是 `DEFAULT_SESSION_KEY` 槽的 `SessionPort`。单会话宿主
93
- * (cli 及今天的三端)用它**正确**;**keyed 多会话宿主别用它开流** —— 用
94
- * `createFleetLedger(hooks, { sessionKey }).issueStream()` 拿 `ingress.options`,那是本包对
95
- * keyed 开流参数的唯一承诺形(理由见文件头 design/285 段)。
96
- */
97
64
  export function fleetStreamOptions() {
98
65
  const s = engineSessionParam();
99
66
  return s ? { session: s } : {};
100
67
  }
101
- /** G19:`client.fleet.snapshot(...)` 的参数(同上,同为默认槽形;首屏/温切探针用)。
102
- * keyed 宿主走 `ledger.issueSnapshot(opts)`。 */
103
68
  export function fleetSnapshotOptions(opts) {
104
69
  return {
105
70
  ...fleetStreamOptions(),
106
71
  ...(opts?.timeoutMs !== undefined ? { timeoutMs: opts.timeoutMs } : {}),
107
72
  };
108
73
  }
109
- /**
110
- * 建一个 fleet 帧台账。**不开流、不构造 client** —— 宿主的 consume 循环把解码后的帧喂进来。
111
- * (这条切缝就是设计稿 §2.5 给 fleetClient 划的那一刀:帧体归库、连接归端。)
112
- */
113
- /** 补偿链的基数判据:非负有限数才算(先验再算 —— 负基数加上空窗会被洗成一个看着合法的正数;异源对抗复审)。 */
114
74
  function isElapsedBase(v) {
115
75
  return typeof v === 'number' && Number.isFinite(v) && v >= 0;
116
76
  }
@@ -119,18 +79,8 @@ export function createFleetLedger(hooks = {}, opts = {}) {
119
79
  const nowFn = opts.nowFn ?? Date.now;
120
80
  const taskMap = new Map();
121
81
  const wfMap = new Map();
122
- /** [1481]①「elapsed 恒 0s」补偿:记每行**本地接收**时刻(引擎只在活动边界重发行)。
123
- * REF-CC-039:字段标域改名 `receivedAtMs` —— 装的是 `nowFn()`,绝不是 wire 上的 `frame.ts`。 */
124
82
  const rowMeta = new Map();
125
- /** [1481]②「完成即消失」补偿:终态行进留存池,宽限窗内继续投影。`expireAt` 同样锚本地钟。
126
- * #242 批 3:`retiredByNotification` 标 = 该条目由 belt(终态通知)退休写入,行帧车道没为它
127
- * settle 过 —— 在飞读面(`bgView.read`)对带标且未过窗的条目**继续按在飞计**(E4-7 解耦,
128
- * 见 `EngineActiveBgTask.retiredByNotification` 顶注);行帧路径写入的条目从不带标。 */
129
83
  const retained = new Map();
130
- /**
131
- * 0.74.0(异源对抗复审 R3)—— 每个 task 见过的最高代际(按行 id 尾段);**行退场后仍留**(有界 LRU):零命中的
132
- * 前代通知(行已退场、留存已过窗)没有别的判据可挡,靠它挡在事实台账之外。
133
- */
134
84
  const cycleWatermark = new Map();
135
85
  const CYCLE_WATERMARK_MAX = 512;
136
86
  function raiseCycleWatermark(tail, gen) {
@@ -147,12 +97,9 @@ export function createFleetLedger(hooks = {}, opts = {}) {
147
97
  }
148
98
  cycleWatermark.set(tail, gen);
149
99
  }
150
- /** REF-CC-051(fleet2-13):畸形帧/通知早退计数(与同文件的策略性丢弃 debug 行对称留痕)。 */
151
100
  let droppedMalformed = 0;
152
- /** [2393] notif-F12:另两条丢弃路径的计数(未知帧型 / 隔离优先丢弃)。 */
153
101
  let droppedUnknownFrame = 0;
154
102
  let droppedForeignBgNotification = 0;
155
- /** [2393] notif-F12 姊妹位(design/285 批 1):迟到 ingress(旧 epoch / 已关)上的帧丢弃计数。 */
156
103
  let droppedStaleIngress = 0;
157
104
  let connected = false;
158
105
  let version = null;
@@ -160,59 +107,24 @@ export function createFleetLedger(hooks = {}, opts = {}) {
160
107
  let sessionScopedMeta;
161
108
  let bgNotifyFailClosedMeta = false;
162
109
  let disposed = false;
163
- // ── design/285 批 1:ingress 发放与代际 ───────────────────────────────────────────────────────
164
- /** ingress 发放序号(stream 与 snapshot 共用一条单调序列,值本身只用于「是不是当前那一只」)。 */
165
110
  let epochSeq = 0;
166
- /** 当前 live stream ingress 的 epoch;**0 = 从未 `issueStream()`**(宿主还在用兼容入口)。 */
167
111
  let liveStreamEpoch = 0;
168
- /** 当前 live snapshot ingress 的 epoch(与 stream 分开记 —— 快照绝不作废正在跑的流)。 */
169
112
  let liveSnapshotEpoch = 0;
170
- /** 当前 stream ingress 开流那一刻捕获的会话锚(`liveStreamEpoch === 0` 时无意义)。 */
171
113
  let streamAnchor;
172
- /**
173
- * 本 ledger **有没有采用过 ingress**(对抗复审第九轮 [medium])。
174
- * 用途:判「兼容入口是不是有一条活流」时,`connected` 只对**纯兼容入口宿主**有权威性 —— 采用了
175
- * ingress 的宿主,流的存活由 `liveStreamEpoch` 说了算,`connected` 可能是上一条流留下的陈旧值
176
- * (`close()` 只退流模式,不替宿主改它)。不分开的话,合法的降级序列「开流 → setConnected(true) →
177
- * close → 取一次性快照兜底」会把那只快照**永久**判过期,SSE 断掉之后的快照恢复路径静默失效。
178
- */
179
114
  let everIssuedStream = false;
180
- /**
181
- * 🔴 **内容归属锚**(对抗复审第二轮 [high]):本 ledger 里现有的行/留存/连接级 meta **是哪个会话的**。
182
- * 只由**经 ingress 到达**的帧写(兼容入口刻意不写:它每帧现读槽会话,再为它加一次端口读只会给
183
- * cli 热路径多计一份 miss,而兼容入口按契约本来就只承诺默认槽单会话)。
184
- * 用途:`issueStream()` 发现新捕获的会话与它不同 ⇒ 这是**换会话**不是重连,旧会话的账必须先清 ——
185
- * 否则 `status().sessionAnchor` 报 B 而 `project()` 还在渲 A 的行,又是一本读面上看不出来的混合账。
186
- */
187
115
  let contentAnchor;
188
116
  let contentAnchorSet = false;
189
- /**
190
- * 🔴 **账本写序号**(对抗复审第五轮 [high]):**任何**入口(ingress 或兼容 `ledger.applyFrame`)成功
191
- * 落一帧就 +1。快照 ingress 在发放那一刻记下它,落账时变了即判过期。
192
- * 为什么不能只看 `liveStreamEpoch`:兼容入口消费活流时那个值**恒为 0**,于是「兼容流写 running →
193
- * 取快照 → 兼容流写 completed → 过期快照 REPLACE 回 running」这条路能整条绕过快照闸。
194
- * 副产品(有意,已成文):一只快照 ingress 是**一次性**的 —— 落账一次之后序号即变,要再取一次快照
195
- * 就再 `issueSnapshot()`。
196
- */
197
117
  let ledgerWrites = 0;
198
118
  const debug = (line) => {
199
119
  if (engineWireDebugEnabled())
200
120
  hostLog('debug', line);
201
121
  };
202
- /** wire 行 → 在飞读面行的单一投影点(live 臂与 belt 退休臂共用;两臂各写一份就会在 name/status
203
- * 的三态处理上漂开)。 */
204
122
  const toBgTask = (r, retired) => {
205
123
  const parentId = wireParentId(r);
206
124
  return {
207
125
  id: r.id,
208
- // REF-CC-046(fleet2-08,含 TYPESHAPE-06):与 parentId 同款条件展开 —— `name` 是三态位
209
- // (server ≥3.5.1 起真的会缺席),无条件落键会让「键在不在」判三态在序列化边界翻面。
210
126
  ...(typeof r.name === 'string' && r.name.length > 0 ? { name: r.name } : {}),
211
- // REF-CC-052(fleet2-14):`status` 是 SDK 必填闭集,但脏值/缺席仍可能到达 wire —— 不知道
212
- // 就说不知道,`'unknown'` 是自描述哨兵,绝不编造一个具体的、看似活跃的 `'running'`。
213
127
  status: r.status ?? 'unknown',
214
- // REF-CC-049(fleet2-11):经 wireParentId 单一铸点(悬空父脏值空串按缺席处理)。eopt:
215
- // 局部变量先取一次(两次裸调用之间 TS 不共享窄化,双调用会重新变宽成 string|undefined)。
216
128
  ...(parentId !== undefined ? { parentId } : {}),
217
129
  ...(retired ? { retiredByNotification: true } : {}),
218
130
  };
@@ -222,13 +134,6 @@ export function createFleetLedger(hooks = {}, opts = {}) {
222
134
  const rows = [...taskMap.values()]
223
135
  .filter(r => !TERMINAL_FLEET_TASK_STATUSES.has(r.status ?? ''))
224
136
  .map(r => toBgTask(r, false));
225
- // 🔴 #242 批 3(E4-7 解耦,design-242 §2.2-5):belt 退休、仍在留存窗内的行**继续出现在
226
- // 在飞读面**(带 `retiredByNotification` 标,消费方自裁;不消费该标 = 保守按在飞计)。
227
- // 为什么不能直接消失:这条读面的下游是引擎温切门(`readEngineActiveBgTasks` →
228
- // `engineSwapGate` 空集即 SIGTERM 旧引擎)—— 通知车道无代际号,误退休(旧周期回声)
229
- // 不可排除;从读面瞬间消失 = 一条回声就能杀掉真在跑的子代,不可恢复且无留痕。
230
- // 窗界:`expireAt`(= 退休时刻 + TERMINAL_RETAIN_MS)自查,不依赖 `project()` 的惰性清扫
231
- // (headless 车道可能整窗不投影);live 行回场(复活/终态行帧)即让位(taskMap 优先)。
232
137
  const nowMs = nowFn();
233
138
  for (const [id, r] of retained) {
234
139
  if (r.retiredByNotification !== true)
@@ -255,46 +160,31 @@ export function createFleetLedger(hooks = {}, opts = {}) {
255
160
  views.add(bgView);
256
161
  }
257
162
  function applyFrame(frame, ing) {
258
- ledgerWrites++; // 账本写序号:全入口通用(见 ledgerWrites 顶注 —— 只看 liveStreamEpoch 会漏掉兼容入口)
163
+ ledgerWrites++;
259
164
  switch (frame.type) {
260
165
  case 'meta': {
261
166
  version = frame.version;
262
167
  scoped = frame.scoped;
263
- // 🔴 B6 typed 直读(SDK 0.0.117 `FleetFrame.meta` 已 declare 这两键;此前是
264
- // `(frame as {sessionScoped?: unknown})` 兜读)。server 1.291.0 实发
265
- // `{version:1, scoped, sessionScoped: callerSession!==null, bgNotifyFailClosed:true}`。
266
168
  sessionScopedMeta = typeof frame.sessionScoped === 'boolean' ? frame.sessionScoped : undefined;
267
- // [1510] 代际标:fail-closed 语义只在 session-bound 连接生效(CHANGELOG 钉「仅 session-bound
268
- // 连接适用」),所以让位判定必须叠加 sessionScoped —— 亲拆 1.247 dist:meta 恒带 true。
269
169
  bgNotifyFailClosedMeta = frame.bgNotifyFailClosed === true;
270
- // [1521]F1 教训:让位路径此前是死代码而围栏全绿(未断言路径本身)——数据面日志让围栏能锚
271
- // 「代际标真的到达且被消费」,防中继剥键类回归再度静默。
272
170
  debug(`[fleet-frame] meta sessionScoped=${String(sessionScopedMeta)} bgNotifyFailClosed=${String(bgNotifyFailClosedMeta)}`);
273
171
  break;
274
172
  }
275
173
  case 'snapshot': {
276
- // 新快照 REPLACE 整个活跃集(重连间隙里被删掉的行不该 linger)。
277
174
  taskMap.clear();
278
175
  wfMap.clear();
279
176
  rowMeta.clear();
280
- // REF-CC-039(fleet2-01):单一铸点 —— 整批快照行共享**同一次**本地接收时刻,绝不用
281
- // wire 上的 `frame.ts`(server 时刻,server 省略该键时兜成 0,与本地钟不同域)。
282
177
  const receivedAtMs = nowFn();
283
178
  for (const row of frame.tasks) {
284
179
  taskMap.set(row.id, row);
285
180
  rowMeta.set(row.id, { receivedAtMs });
286
- // #242 批 3(复审 P-F1,红先绿后=B3-G4b):snapshot(重连 REPLACE)腿与增量 task 帧腿
287
- // 的复活语义**同权** —— snapshot 带回的非终态行,其全部别名(同尾段)的 belt 退休条目
288
- // 一并清,否则重连别名切换(裸 id 退休 → 复合 id running 回归)会产出「复合 running +
289
- // 裸 completed(带标)」双影。终态行不清(与增量腿同判);snapshot 里**没有**的任务,
290
- // 留存条目照旧保留(R7 反钉:重连不抽走已完成行)。
291
181
  if (!TERMINAL_FLEET_TASK_STATUSES.has(row.status ?? '')) {
292
182
  const tail = rowIdTail(row.id);
293
183
  for (const id of [...retained.keys()]) {
294
184
  if (rowIdTail(id) === tail)
295
185
  retained.delete(id);
296
186
  }
297
- clearBgTerminalFacts(tail); // 复活=新周期,与增量腿同权(B3-N3)
187
+ clearBgTerminalFacts(tail);
298
188
  }
299
189
  }
300
190
  for (const row of frame.workflows)
@@ -308,22 +198,13 @@ export function createFleetLedger(hooks = {}, opts = {}) {
308
198
  debug('[fleet-frame] MALFORMED task frame dropped(row 或 row.id 缺席)');
309
199
  break;
310
200
  }
311
- // REF-CC-039(fleet2-01):同一个字段只有一个铸点 —— 本地接收时刻,不是 `frame.ts`。
312
201
  const receivedAtMs = nowFn();
313
- taskMap.set(row.id, row); // POST-MERGE FULL row → replace by id
314
- raiseCycleWatermark(rowIdTail(row.id), wireCycleSeq(row)); // 0.74.0:周期水位(退场后仍留)
202
+ taskMap.set(row.id, row);
203
+ raiseCycleWatermark(rowIdTail(row.id), wireCycleSeq(row));
315
204
  rowMeta.set(row.id, { receivedAtMs });
316
205
  debug(`[fleet-row] id=${row.id} parent=${row.parentId ?? '-'} agentType=${row.agentType ?? '-'}` +
317
206
  ` agentName=${row.agentName ?? '-'} name=${row.name ?? '-'} status=${row.status ?? '-'}` +
318
207
  ` tokens=${row.tokens ?? '-'} parentToolCallId=${row.parentToolCallId ?? '-'}`);
319
- // [1491] subagentOutput 寻址:子代行出生帧带 parentId(spawn run)——记映射供查看态终报读面。
320
- // 归属判别与通知臂同锚(R1/F1,2026-07-22):
321
- // · parent ∈ own-run 台账 → own:登映射 + 把本行 taskId 也登进台账(F1 闭包 —— 嵌套孙代的
322
- // bg_notification.parentTaskId 是**直接父**,不闭包的话孙代完成通知会被当 foreign 丢);
323
- // · parent ∉ 台账但 meta.sessionScoped===true → server 已按会话过滤行帧,到达即本会话
324
- // (或无标行)→ 登映射**不登台账**(台账只收硬证据,防无标外来行污染台账后反过来给
325
- // 通知臂放行);这也保住「壳重启+resume 接回会话」的终报 hydrate;
326
- // · 两者都不是(老代际广播窗)→ 不登映射(隔离优先)。
327
208
  const parentId = wireParentId(row);
328
209
  if (parentId !== undefined) {
329
210
  const childTid = rowIdTail(row.id);
@@ -332,33 +213,18 @@ export function createFleetLedger(hooks = {}, opts = {}) {
332
213
  if (parentOwned)
333
214
  recordOwnEngineRun(childTid);
334
215
  if (parentOwned || sessionScopedMeta === true) {
335
- // 🔴 design/285 批 2(D5):三值原子写 —— 会话与槽键取**本 ingress 开流那一刻的不可变
336
- // 捕获**,不是现读任何可变槽。兼容入口(`ing === null`)两个新参都是 `undefined`
337
- // ⇒ 与 0.44.0 逐字同(它每帧现读槽会话、库里不留锚,归属不可知就不替它背书)。
338
216
  recordBgParentRun(childTid, parentTid, ing?.session, ing?.sessionKey);
339
217
  }
340
218
  else {
341
219
  debug(`[fleet-frame] task row parent-map SKIPPED foreign/unowned(row=${row.id} parent=${parentTid})`);
342
220
  }
343
221
  }
344
- // [1839]① 终态行帧四键:transcriptId 现在**行上也可能有**(此前只在通知帧),有就登锚 ——
345
- // 委派 prompt 取件腿据此在「只看行卡」的场景也能补齐详情页 Prompt 段。
346
222
  if (typeof row.transcriptId === 'string' && row.transcriptId.length > 0) {
347
223
  recordEngineTranscriptId(rowIdTail(row.id), row.transcriptId);
348
224
  }
349
- // 0.75.1:`agentType`(§K-7 短类型 label)在 `projectTasks` 里会被**折进** `name`
350
- // (`deriveAgentLabel` = agentName ?? agentType),折完就与显示名分不开了 —— 而 `subagent_lifecycle`
351
- // 的 `agentType` 需要的正是折之前那一位。所以在这里(折之前)按行 id 尾段留一份;缺席 / 坏形不入账
352
- // (读口答缺席,tick 臂让键缺席,绝不拿展示名冒充)。写方只有这一处,与转录锚台账同族同作用域。
353
225
  recordFleetRowAgentType(rowIdTail(row.id), row.agentType);
354
- // 终态帧 → 留存池(remove 同毫秒尾随,probe 实证;留存让 completed band 数得到它)。
355
- // REF-CC-040(fleet2-02):非终态分支必须显式 delete —— 否则 bg 复活(SendMessage 后
356
- // seq+1)时,`project()` 因 `taskMap.has(id)` 恒 continue 而永不清理过期留存条目,belt
357
- // 的 `!retained.has()` 前提失守,第二周期的终态被首周期的陈旧快照挡在门外。
358
226
  if (TERMINAL_FLEET_TASK_STATUSES.has(row.status ?? '')) {
359
227
  retained.set(row.id, { row, expireAt: receivedAtMs + TERMINAL_RETAIN_MS });
360
- // 终态行帧到达=行帧车道开口(顶注契约:本标即撤)——双生行**另一别名**的 belt 退休
361
- // 条目一并删(0.31.0 扫码 P3,B3-N2),否则同一任务整 60s 窗按在飞多算。
362
228
  const tailT = rowIdTail(row.id);
363
229
  for (const id of [...retained.keys()]) {
364
230
  if (id !== row.id && rowIdTail(id) === tailT)
@@ -367,24 +233,16 @@ export function createFleetLedger(hooks = {}, opts = {}) {
367
233
  }
368
234
  else {
369
235
  retained.delete(row.id);
370
- // #242 批 3(G4 双生行半场):复活 = 这个任务活着,**全部别名**(裸 id ↔ `runId childId`
371
- // 复合形共享同一尾段 = 引擎 taskId 键域)的陈旧终态快照一并清 —— 只清整 id 会留下另一
372
- // 别名的 belt 退休条目,project 会把它当第二条行渲出来(一活一死的双影),读面也会把
373
- // 一条真在跑的任务同时按「退休中」计。尾段全局唯一(引擎 handle 域),不会误清邻行。
374
236
  const tail = rowIdTail(row.id);
375
237
  for (const id of [...retained.keys()]) {
376
238
  if (id !== row.id && rowIdTail(id) === tail)
377
239
  retained.delete(id);
378
240
  }
379
- // 复活=新周期:旧周期终态事实作废(0.31.0 扫码 P2,B3-N3)——不清则 factsAccepted
380
- // 的方向核把上周期 killed 当先例,第二周期合法 completed 被当洗绿回声拒收。
381
241
  clearBgTerminalFacts(tail);
382
242
  }
383
243
  break;
384
244
  }
385
245
  case 'task_remove': {
386
- // 0.74.0:退场帧带**被退那一代**的代际号。行已经是更新的一代(两边都报了且帧上更小)⇒ 这是前代迟到帧,
387
- // 删了就把复活后的行抹掉 ⇒ 不删。任一边没报 ⇒ 判不出 ⇒ 现行为(删)。
388
246
  const fr = frame;
389
247
  const removedGen = typeof fr.cycleSeq === 'number' && Number.isInteger(fr.cycleSeq) && fr.cycleSeq >= 1 ? fr.cycleSeq : undefined;
390
248
  const held = taskMap.get(frame.id);
@@ -404,7 +262,6 @@ export function createFleetLedger(hooks = {}, opts = {}) {
404
262
  ...(stale ? { stale: true } : {}),
405
263
  ...(held === undefined ? { unknownRow: true } : {}),
406
264
  });
407
- // 宿主给的可能是 async 回调:reject 同样要接住(与 onBgNotificationAccepted 同款),否则是 unhandledRejection。
408
265
  if (out && typeof out.then === 'function') {
409
266
  void Promise.resolve(out).catch((e) => {
410
267
  hostLog('debug', `[fleet-frame] onTaskRemoved hook rejected: ${String(e).slice(0, 160)}`);
@@ -412,7 +269,6 @@ export function createFleetLedger(hooks = {}, opts = {}) {
412
269
  }
413
270
  }
414
271
  catch (e) {
415
- // 宿主读口的异常不许拆帧消费循环(与 onBgNotificationAccepted 同款);留痕走 hostLog(端的 debug 汇集面)。
416
272
  hostLog('debug', `[fleet-frame] onTaskRemoved hook threw: ${String(e).slice(0, 160)}`);
417
273
  }
418
274
  }
@@ -431,82 +287,30 @@ export function createFleetLedger(hooks = {}, opts = {}) {
431
287
  wfMap.delete(frame.id);
432
288
  break;
433
289
  case 'hook_notice':
434
- // hook_notice(server 1.280.0,[1790]):「本轮 Stop 守卫未能评估」纯 observe 帧。
435
- // 🔴 **在册残余 P-31 的第三格**(对抗复审第九轮 [high] 登记):第二参交出去的就是那个**未经会话值
436
- // 校验**的连接级信任位 `sessionScopedMeta` —— 与通知臂的 `server_fail_closed`、行帧车道的
437
- // `sessionScoped` 让位**同一个根因**(连接级零内容自校)。keyed ledger 上一条被误接的 A 流
438
- // 报 sessionScoped=true 时,A 的 hook_notice 会带着「已按会话过滤」这句话进 B 的宿主面,而本
439
- // 回调既不带锚也不带 evidence,端无从自裁。本批**不改签名**(additive 第三参属另一批的公开面
440
- // 决定),按残余登记 + 钉现状(B3-P31/R9a);根治与另两格同一个 = 上游在 meta 帧回显本连接
441
- // session id ⇒ 一次修好三条通道。
442
- // 判定本体在 notifications.classifyHookNoticeFrame(#105 F3:判别不许留在分发处,否则
443
- // 离线门只能 grep);写 store 归宿主。SDK 0.0.117 起本帧**已在 FleetFrame 联合里**,
444
- // 不再走 default 臂的 `type` 字面判别。
445
290
  hooks.onHookNotice?.(frame, sessionScopedMeta === true);
446
291
  break;
447
292
  case 'bg_notification':
448
293
  applyBgNotification(frame.notification, ing);
449
294
  break;
450
295
  default:
451
- // 未知未来帧 —— 开集,忽略。**但要留痕**([2393] notif-F12):忽略也是丢数据,
452
- // 与同文件另两类丢弃(畸形 / 策略性)对称记账,否则「上游发了新帧型」在本端完全不可观察。
453
296
  droppedUnknownFrame++;
454
297
  debug(`[fleet-frame] UNKNOWN frame type ignored(type=${String(frame.type)})`);
455
- // ⚠️ 老 SDK 时代这里还兜着「平铺形 bg_notification / 字面 hook_notice」两条 belt;
456
- // 0.0.117 的 FleetFrame 两者都是具名臂,belt 随之退役(旧回放素材由端自己升级)。
457
298
  break;
458
299
  }
459
300
  }
460
- /** bg 子代终态通知帧([446] 帧契约,service 1.119):常开 lane 即时到达 → 转注入链。
461
- * REF-CC-048(fleet2-10):参数类型此前写成 `FleetFrame extends never ? never : BgNotificationWire`
462
- * —— 条件类型只对**裸类型参数**分发,`FleetFrame` 是具体 union,整个表达式恒等于
463
- * `BgNotificationWire`,伪装成了一个从不成立的守卫。直写目标类型,不留误导性的仪式感。 */
464
301
  function applyBgNotification(n, ing) {
465
302
  if (typeof n?.taskId !== 'string' || typeof n?.status !== 'string') {
466
303
  droppedMalformed++;
467
304
  debug('[fleet-frame] MALFORMED bg_notification dropped(taskId 或 status 缺席/非串)');
468
305
  return;
469
306
  }
470
- // 🔴 #284 尾件(对抗复审第四轮 high,红先绿后=B3-DIRTY):`parentTaskId` 是**隔离判据的锚**,
471
- // 它的脏形必须先于判据处置。下面那道 own/foreign 门写成
472
- // `typeof === 'string' && length > 0 && !isOwnEngineRun(…)` —— 对 number/object/boolean
473
- // 这类脏值**整条不成立** ⇒ 门不响、通知照收,而新的 evidence 还会把它标成 `absent_parent`
474
- // (「老引擎没带这个键」的兼容臂):键**带了**,只是坏的,标成缺席是谎报。帧从 wire 上来、
475
- // SDK 只 JSON.parse + rest-spread 不校型(同 `recentSteps` 处的威胁模型),这条路真实可达。
476
- // 三态分开,只收紧真正的脏形:
477
- // · 键缺席 / `undefined` / `null` / 空串 ⇒ absent-放行臂**照旧**(与 `wireParentId`
478
- // 「悬空父脏值空串按缺席处理」同一规矩;零新增 drop —— 老引擎与「本来就没有父」不受伤);
479
- // · 键在场却**不是串**(含 `null`,0.32.0 扫码收紧)⇒ 与 taskId/status 同档 fail-closed:
480
- // 计 `droppedMalformed`、不写状态面、不回调 accepted 钩子、不入通知队列(隔离优先,
481
- // 坏值不许买路)。`null` 不入兼容臂的理由:wire schema 只允许**缺席或 string**——
482
- // 老引擎是不带这个键,不是带 `null`;把 null 归入缺席等于给「外来 parent 锚被置 null」
483
- // 这一腐败/漂移形留一条绕过 foreign 检查的路(空串是 string 的退化形,真老形,保留)。
484
307
  if (n.parentTaskId !== undefined && typeof n.parentTaskId !== 'string') {
485
308
  droppedMalformed++;
486
309
  debug(`[fleet-frame] MALFORMED bg_notification dropped(parentTaskId 键在场但非串:${typeof n.parentTaskId}` +
487
310
  ` —— 隔离判据的锚不许被坏值旁路;taskId=${n.taskId})`);
488
311
  return;
489
312
  }
490
- // 🔴 own/foreign 判别(双开泄漏案帧级防线;[1498]④ 换锚重写):payload `sessionId` 是**子代
491
- // 自身** session uuid,宿主会话在 ownerSessionId 上且出 wire 前被剥 —— 所以判别锚
492
- // `parentTaskId`(spawn 该子代的 leader run id)对 own-run 台账:
493
- // · ∈ 台账 → own,放行;
494
- // · 在但不在台账 → 隔离优先丢弃;
495
- // · 缺席 → 放行(absent-放行姿势)。
496
- // [1510] 代际让位:meta 报 bgNotifyFailClosed 且本连接 session-bound ⇒ server 侧注入路已
497
- // fail-closed,到达即本会话 → 端侧台账判别整体让位。
498
313
  const serverFailClosed = bgNotifyFailClosedMeta && sessionScopedMeta === true;
499
- // [1518] δ 投影:通知带 `rootSessionId`(委托树 root 宿主会话,固定点语义)=== 本会话即 own
500
- // 硬证据。🔴 与行帧的 `rowSessionTag` 不同 —— server `streamFleet` 只从**行**上剥
501
- // scope/sessionId,通知帧只剥 ownerScope/ownerSessionId,`rootSessionId` **照发**
502
- // (1.291.0 dist 实读)⇒ 这条腿是活的,不在 G12 删除范围内。
503
- // 🔴 design/285 批 1(P-31 正位解的判据半场):比对锚从**默认槽**换成**本 ledger 的会话锚** ——
504
- // 经 ingress 到达 ⇒ 该 ingress 开流那一刻的不可变捕获(逐帧现读会让重连后旧流的迟到帧配上
505
- // 新会话,那正是 owner 台账明禁的错组合);兼容入口 ⇒ `engineSessionParamFor(sessionKey)` 现读。
506
- // 默认槽 + 兼容入口 ⇒ `engineSessionParamFor(DEFAULT_SESSION_KEY)` ≡ `engineSessionParam()`,
507
- // 与 0.32.0 同一条取值链、逐字节等价。
508
- // ⚠️ keyed 车道这是**真行为改变**:`ownByRoot` 是下面那道丢弃门的放行腿之一,换锚会改放行/
509
- // 丢弃集 —— 带**别的会话** `rootSessionId` 的通知对本键 ledger 本就该判 foreign(方向更正确)。
510
314
  const anchor = ing !== null ? ing.session : engineSessionParamFor(sessionKey);
511
315
  const ownByRoot = typeof n.rootSessionId === 'string' &&
512
316
  n.rootSessionId.length > 0 &&
@@ -516,42 +320,14 @@ export function createFleetLedger(hooks = {}, opts = {}) {
516
320
  typeof n.parentTaskId === 'string' &&
517
321
  n.parentTaskId.length > 0 &&
518
322
  !isOwnEngineRun(n.parentTaskId)) {
519
- droppedForeignBgNotification++; // [2393] notif-F12:策略性丢弃也要有可读的量,不只 debug 行
323
+ droppedForeignBgNotification++;
520
324
  debug(`[fleet-frame] bg_notification DROPPED foreign/unowned parentRun(taskId=${n.taskId} parentRun=${n.parentTaskId})`);
521
325
  return;
522
326
  }
523
- // 🔴 #284:放行**凭据**定级(additive 第三参喂 accepted 钩子)。臂序 = 上面那道放行判据的
524
- // 求值序,逐臂对位:`serverFailClosed` 先短路 → `ownByRoot` 次之 → 剩下两格由
525
- // `parentTaskId` 在不在分开(走到这里且带非空 parentTaskId ⇒ 它必然 ∈ own-run 台账,
526
- // 否则上面已丢弃)。多臂同时成立报第一条 —— 报的是「这一帧**实际**靠哪条过的门」,
527
- // 不是「最强的那条理论上也成立」。
528
- // 🔴 可信谓词**按臂拆分**(design/285 §2.3.3,P-31 正位解;取代 0.32.0「非默认 key 一律封顶」
529
- // 那一刀切)。两臂不同命的理由 = **自校能力不同**,不是版本先后:
530
- // · `own_root` 拿通知内容里的 `rootSessionId` 与本锚**逐值比对** —— 宿主把 A 流喂进 B 的
531
- // ingress 时,A 的通知带 `rootSessionId = sid-A ≠ sid-B`,自动拒。⇒ 锚换对了这一臂就真可信:
532
- // 默认槽恒可信(0.32.0 逐字同);keyed 上**经 ingress 到达**即可信(通道证据),走兼容入口
533
- // 仍封顶(没有通道证据,ledger 无从知道帧来自哪条连接)。
534
- // · `serverFailClosed` **零内容自校**:只读 meta 两个布尔位,不读任何会话、不比对任何值,
535
- // 而任何一条 session-bound 流的 meta 都长这样 ⇒ 跨-key 误接时它会替别的会话作证,ingress
536
- // 对此证明不了任何东西。⇒ **keyed 上恒封顶**,待上游在 meta 帧回显本连接 session id 后再议。
537
- // 封顶词仍是 `session_anchor_untrusted`(不降到 own_parent:经会话级臂放行的帧其 parentTaskId
538
- // 可能是 foreign,标进程成员比错标会话更糟;也不标 absent_parent:键可能在场)。
539
327
  const defaultSlot = sessionKey === DEFAULT_SESSION_KEY;
540
328
  const viaIngress = ing !== null;
541
- /** 内容自校臂:锚对了就可信(空锚时 `ownByRoot` 结构性恒 false —— 没有锚就没法逐值比对,
542
- * 所以不需要第二道 anchorOk;这也是为什么空锚只挡得住这一臂,挡不住零自校的那一臂)。 */
543
329
  const ownRootTrusted = defaultSlot || viaIngress;
544
- /** 零内容自校臂:只有「前提可成立」的默认槽那一格可信。
545
- * ⚠️ **刻意不叠 anchorOk**:这一臂的前提是「这条流是替谁开的」,与本端有没有装 `SessionPort`
546
- * 无关 —— 默认槽 + 空锚 + 生产 meta 仍报本词,与 0.32.0 逐字同(钉:B3-P31/R5c)。 */
547
330
  const serverFailClosedTrusted = defaultSlot;
548
- // 🔴 臂序 = 求值序,但**只在「成立且可信」的臂之间**排序(对抗复审第一轮 [high] 采纳,红先绿后)。
549
- // 旧写法「无条件先报 serverFailClosed」在**生产 meta 组合**(两位恒 true)下会让 keyed 车道
550
- // 永远走不到 own_root —— 哪怕 `rootSessionId` 与本锚精确相等、内容自校已经成立。那等于把一条
551
- // **真的**会话级证明扔掉、改报一个封顶的非证据词,本批的核心净收益在生产配置下完全不可达。
552
- // 新规矩:**第一条「成立 ∧ 可信」的会话级臂**胜出;一条都没有而至少有一条成立 ⇒ 封顶词。
553
- // 🔴 绝不因此下探到 `own_parent`:会话级臂放行的帧根本没验过 `parentTaskId ∈ own-run 台账`
554
- // (放行门在 serverFailClosed/ownByRoot 上短路),报进程成员就是谎报。
555
331
  const trustedSessionArm = serverFailClosed && serverFailClosedTrusted
556
332
  ? 'server_fail_closed'
557
333
  : ownByRoot && ownRootTrusted
@@ -564,10 +340,6 @@ export function createFleetLedger(hooks = {}, opts = {}) {
564
340
  ? 'own_parent'
565
341
  : 'absent_parent');
566
342
  debug(`[fleet-frame] bg_notification taskId=${n.taskId} status=${n.status} seq=${n.seq ?? '-'} accept=${evidence}`);
567
- // 键域(#242 批 3):`n.taskId` = registry handle = 行 id 的**尾段**。live 集按「整行 id 或
568
- // 尾段命中」收齐**全部**别名(server 双生行:裸 id 与 `runId childId` 复合形同时在场时两条
569
- // 一起处理)—— 快照取在任何退休动作之前,原样交给 accepted 钩子。首段(runId)绝不作键
570
- // (同 run 兄弟行共享首段,按它匹配 = 一条通知误退休全部并发兄弟)。
571
343
  const rowIds = [];
572
344
  for (const id of taskMap.keys()) {
573
345
  if (id === n.taskId || rowIdTail(id) === n.taskId)
@@ -575,45 +347,19 @@ export function createFleetLedger(hooks = {}, opts = {}) {
575
347
  }
576
348
  if (TERMINAL_FLEET_TASK_STATUSES.has(n.status)) {
577
349
  if (typeof n.parentTaskId === 'string' && n.parentTaskId) {
578
- // design/285 批 2(D5,通知腿):与行帧腿同一口径 —— 三值取本 ingress 的不可变捕获。
579
350
  recordBgParentRun(n.taskId, n.parentTaskId, ing?.session, ing?.sessionKey);
580
351
  }
581
- // `transcriptId`(server 1.278.0)= 委派 prompt 的取件锚。现势:通知帧是它**今天真的在场**
582
- // 的主要位置(行帧那条腿已同款接好,引擎一发即亮 —— 见 case 'task')。
583
- // (寻址/取件锚不随下方状态面判决走:回声通知携带的 id 映射仍是真映射。)
584
352
  if (typeof n.transcriptId === 'string' && n.transcriptId.length > 0) {
585
353
  recordEngineTranscriptId(n.taskId, n.transcriptId);
586
354
  }
587
- // ── #242 批 3 belt 根修(design-242 §4.1 判 bug;E4-1/2/6/7 覆盖,零代际账)────────────
588
- // 旧形的配对失配:belt 只在 `taskMap.get(n.taskId)` 命中时写 `retained`,而 `project()` 对
589
- // 留存条目一律 `taskMap.has(id) ⇒ continue` —— 两条件互斥,belt 写进去的那条当拍必被跳过,
590
- // 唯一显形路径是随后的 `task_remove`,而 belt 存在的全部理由恰是「终态行帧(与它同毫秒的
591
- // remove)丢了」⇒ 对主症(行永恒 running)结构性零效果。
592
- // 新形:通知命中 live 行 ⇒ **退休**(`taskMap.delete` + `retained.set`),与「终态行帧 +
593
- // 同毫秒 task_remove」那条路逐段同构;定位按尾段且全部命中(双生行一起退休)。
594
- //
595
- // 🔴 状态面单一判决点(两读面 —— 留存池行 status 与 `recordBgTerminalFacts` 台账 —— 共用
596
- // 同一份判决,绝不各判各的;REF-CC-040 腿三「不分叉」不变量的延续):
597
- // · **洗绿拦截**(E4-2/G3 的可执行形):已有终态真值 ∈ {failed, killed} 时,`completed`
598
- // 通知不得改写它 —— 把失败洗成成功是伤害型谎报。「已终态行不覆盖 retained」守卫按
599
- // 方向落地而非按字面:字面全禁会与 REF-CC-040 腿三(行帧 completed 在先、通知 killed
600
- // 在后 ⇒ 通知是 settle 权威,两读面必须同说 killed)直接矛盾 —— 同为「行车道已终态 +
601
- // 通知异词」,升级(completed→killed/failed)是新知识,降级洗绿是回声。
602
- // · **不引入任何代际账**(E4-1/3/4/5 的共同宿主整套不复活):回声保护 = 方向规则(无状态)
603
- // + `enqueueBgChildNotification` 既有 (taskId,status,seq) 去重;复活保护 = 非终态行帧
604
- // 即清账(case 'task' else 臂,含双生行别名清扫)。`droppedStaleCycle*` 计数不复活。
605
- // · **无早退**(E4-8/G8):状态面判决无论落哪支,通知面(`enqueueBgChildNotification`)
606
- // 与 accepted 钩子照常执行 —— 通知面与状态面分开裁。
607
355
  const isWashGreen = (prior, next) => next === 'completed' && (prior === 'failed' || prior === 'killed');
608
356
  const coerced = n.status;
609
357
  const nowMs = nowFn();
610
358
  const writtenIds = new Set();
611
359
  let applied = false;
612
360
  let washBlocked = false;
613
- /** 0.74.0:通知的 seq 早于行 / 留存条目的代际 ⇒ 前代通知;挡在状态面之外,也不入事实台账(与洗绿同一档)。 */
614
361
  let staleBlocked = false;
615
362
  const notifSeq = typeof n.seq === 'number' && Number.isInteger(n.seq) && n.seq >= 1 ? n.seq : undefined;
616
- // 零命中路径(行已退场、留存已过窗)的前代判据 = 本 task 的周期水位;通知本身也抬水位(它是同一条轴上的读数)。
617
363
  {
618
364
  const mark = cycleWatermark.get(n.taskId);
619
365
  if (notifSeq !== undefined && mark !== undefined && notifSeq < mark) {
@@ -625,8 +371,6 @@ export function createFleetLedger(hooks = {}, opts = {}) {
625
371
  const known = taskMap.get(id);
626
372
  if (!known)
627
373
  continue;
628
- // 0.74.0(异源对抗复审 R1):通知带的是**前一代**的 seq、而 live 行已是更新的一代 ⇒ 前代迟到通知,不退休这一行
629
- // (退休会把行上的 cycleSeq 借给终态,消费端据此把仍在跑的新一轮结掉)。任一边没报 ⇒ 判不出 ⇒ 现行为。
630
374
  const heldGen = wireCycleSeq(known);
631
375
  if (notifSeq !== undefined && heldGen !== undefined && notifSeq < heldGen) {
632
376
  staleBlocked = true;
@@ -635,41 +379,26 @@ export function createFleetLedger(hooks = {}, opts = {}) {
635
379
  }
636
380
  const rowStatus = known.status ?? '';
637
381
  if (TERMINAL_FLEET_TASK_STATUSES.has(rowStatus)) {
638
- // 行车道已 settle 本行(终态行帧在先):retained 已由 case 'task' 写入行帧真值。
639
382
  if (isWashGreen(rowStatus, n.status)) {
640
383
  washBlocked = true;
641
384
  debug(`[fleet-frame] bg_notification WASH-GREEN blocked(row=${id} row-status=${rowStatus} notif=${n.status})`);
642
- continue; // G3:`task_remove` 之后投影仍是行帧真值(failed),不被通知洗绿
385
+ continue;
643
386
  }
644
387
  if (rowStatus !== n.status) {
645
- // 升级(REF-CC-040 腿三):通知是 settle 权威,两读面同步改写,不分叉。
646
- // 🔴 #281(0.31.0 扫码 P3 的真窗,红先绿后=B3-P3):`retained` **和** live 行一起改。
647
- // 只写 retained 的旧形有一个 remove 之前的分叉窗 —— `project()` 对留存条目一律
648
- // `taskMap.has(id) ⇒ continue`,于是面板读的是 live 行的**旧终态词**(completed)
649
- // 而事实台账已是 killed:正是 E4-6 注里点名不许出现的「面板绿而详情页 killed」。
650
- // 常态下同毫秒的 `task_remove` 会把窗压到不可见,但 remove 单独丢失且不重连时它
651
- // **长驻**(自愈只能等重连 REPLACE 或整窗过期)。live 行改写后两读面当拍同说;
652
- // 在飞读面不受影响(升级后仍是终态词,照旧不进在飞集),`task_remove` 语义不变。
653
388
  const upgraded = { ...known, status: coerced };
654
389
  retained.set(id, { row: upgraded, expireAt: nowMs + TERMINAL_RETAIN_MS });
655
390
  taskMap.set(id, upgraded);
656
391
  }
657
392
  writtenIds.add(id);
658
393
  applied = true;
659
- // live 终态行不退休(remove 帧随后清;终态行不进在飞读面)——只在升级时同步它的状态词。
660
394
  }
661
395
  else {
662
- // 退休:live 行离开在飞集,进留存池,带 E4-7 解耦标(见 bgView.read)。
663
- // elapsed 在退休时刻**铸一次冻结值**(project() 对 live 行的同一公式):行帧丢失场景
664
- // wire 上最后一帧的 elapsedMs 是陈旧值(常为 0)——不铸的话完成行的计时器渲 0,
665
- // 与 A-024.8「翻 status 与冻 elapsed 必须同一刀」同病。诚实边界:冻的是通知到达时刻,
666
- // 是上界不是精确完成时刻(通知不带可信完成时钟)。
667
396
  const started = known.startedAt;
668
397
  const meta = rowMeta.get(id);
669
398
  const frozenElapsed = typeof started === 'number' && started > 0 && started <= nowMs
670
399
  ? nowMs - started
671
400
  : meta !== undefined && isElapsedBase(known.elapsedMs)
672
- ? known.elapsedMs + Math.max(0, nowMs - meta.receivedAtMs) // 0.73.0:无基数不铸(同 project() 的补偿链)
401
+ ? known.elapsedMs + Math.max(0, nowMs - meta.receivedAtMs)
673
402
  : known.elapsedMs;
674
403
  taskMap.delete(id);
675
404
  rowMeta.delete(id);
@@ -686,14 +415,11 @@ export function createFleetLedger(hooks = {}, opts = {}) {
686
415
  applied = true;
687
416
  }
688
417
  }
689
- // E4-6(G7):留存池更新**不以 live 行在场为前提**,按尾段独立成路 —— 退休后同 task 的
690
- // 第二条终态通知(completed→killed)必须仍能改写留存池,否则面板绿而详情页 killed。
691
418
  for (const [id, entry] of retained) {
692
419
  if (writtenIds.has(id))
693
420
  continue;
694
421
  if (id !== n.taskId && rowIdTail(id) !== n.taskId)
695
422
  continue;
696
- // 0.74.0(异源对抗复审 R2):留存池这条独立成路的更新同样按周期判 —— 否则被 live 路挡掉的前代通知从这里改写新一代的结局。
697
423
  const retainedGen = wireCycleSeq(entry.row);
698
424
  if (notifSeq !== undefined && retainedGen !== undefined && notifSeq < retainedGen) {
699
425
  staleBlocked = true;
@@ -702,7 +428,7 @@ export function createFleetLedger(hooks = {}, opts = {}) {
702
428
  }
703
429
  const prior = entry.row.status ?? '';
704
430
  if (prior === n.status) {
705
- applied = true; // 幂等重投(G5):同值不动、**不续窗**(回声风暴不得无限延长读面在飞窗)
431
+ applied = true;
706
432
  continue;
707
433
  }
708
434
  if (isWashGreen(prior, n.status)) {
@@ -712,19 +438,11 @@ export function createFleetLedger(hooks = {}, opts = {}) {
712
438
  }
713
439
  retained.set(id, {
714
440
  row: { ...entry.row, status: coerced },
715
- expireAt: nowMs + TERMINAL_RETAIN_MS, // 状态真变 = 新知识,续窗让修正可见
441
+ expireAt: nowMs + TERMINAL_RETAIN_MS,
716
442
  ...(entry.retiredByNotification === true ? { retiredByNotification: true } : {}),
717
443
  });
718
444
  applied = true;
719
445
  }
720
- // [1481]① 查看态事实喂给:终态通知带 summary+recentSteps,这是端当前能拿到的关于后台子代
721
- // 结局的【全部】内容(终报正文不在 wire 上)。查看态对无终报的终态行渲染这些真实事实。
722
- // 🔴 与留存池共用上方同一份判决:状态面收下才记账;洗绿回声整条不入事实台账(status 记
723
- // completed 而面板渲 failed = 同一帧两写分叉,REF-CC-040 腿三的反面)。零命中且零拦截
724
- // (行帧车道整条丢失、留存已过窗)⇒ 按既有事实台账自身做同方向核(killed/failed 在账,
725
- // completed 回声不得洗绿它)。
726
- // 0.74.0:事实台账带周期 —— 洗绿保护只在**同周期**内生效(第 1 代 failed 在账、第 2 代 completed 是新事实不是洗绿);
727
- // 在账的周期比来的通知新 ⇒ 前代回声,不记。
728
446
  const factsAccepted = !staleBlocked &&
729
447
  (applied ||
730
448
  (!washBlocked &&
@@ -747,11 +465,6 @@ export function createFleetLedger(hooks = {}, opts = {}) {
747
465
  status: n.status,
748
466
  ...(notifSeq !== undefined ? { cycleSeq: notifSeq } : {}),
749
467
  ...(typeof n.summary === 'string' ? { summary: n.summary } : {}),
750
- // 🔴 SDK 0.0.117 把 `recentSteps` 的三键 declare 成**必填** string,但那是**声称**不是保证:
751
- // 帧从 wire 上来,脏项(null / 缺键 / 非串)在类型面之外仍可能到达,而 BgTerminalFacts
752
- // 那侧三键**全可选** ⇒ 这里的逐键 typeof 不是「防御读没退干净」,是 wire 脏值过滤(与
753
- // fleetProjection 的 `wire*` 值域校验同一档次)。谓词类型必须是元素型本身,不能是
754
- // `Record<string, unknown>`(那不是参数型的子型 ⇒ TS2677)。
755
468
  ...(Array.isArray(n.recentSteps)
756
469
  ? {
757
470
  recentSteps: n.recentSteps
@@ -769,12 +482,7 @@ export function createFleetLedger(hooks = {}, opts = {}) {
769
482
  debug(`[fleet-frame] bg_notification state-plane rejected as wash-green echo(taskId=${n.taskId} notif=${n.status})—— 通知面照常入队`);
770
483
  }
771
484
  }
772
- // accepted 钩子(#242 批 3):归属判别已通过;终态/非终态都回调,状态筛选归消费方。
773
- // 钩子抛错不拆帧消费(留痕即可)。
774
485
  try {
775
- // 钩子类型虽 void,宿主传 async 函数合法(P-F3):返回 thenable 时必须承接拒绝,
776
- // 否则 async 消费方一炸就是 unhandledRejection = Node 缺省整进程崩(TUI 最坏形,
777
- // engineSubagentTail meta sink 同族先例)。同步抛与异步拒同一条留痕出口。
778
486
  const out = hooks.onBgNotificationAccepted?.(n, rowIds, evidence);
779
487
  if (out && typeof out.then === 'function') {
780
488
  void Promise.resolve(out).catch((e) => {
@@ -783,25 +491,20 @@ export function createFleetLedger(hooks = {}, opts = {}) {
783
491
  }
784
492
  }
785
493
  catch (e) {
786
- // 消费端钩子的异常不许拆帧消费循环;留痕走 hostLog(端的 debug 汇集面)。
787
494
  hostLog('debug', `[fleet-frame] onBgNotificationAccepted hook threw: ${String(e).slice(0, 160)}`);
788
495
  }
789
496
  enqueueBgChildNotification({
790
497
  taskId: n.taskId,
791
498
  status: n.status,
792
- // seq = 周期号(SendMessage 复活即 +1)—— 去重键必须带,否则 revive 周期的完成通知被首周期
793
- // 同 (taskId,status) 键吞掉(clay 图2 案根因,1.355 复验 seq=2 帧实锤)。
794
499
  ...(typeof n.seq === 'number' ? { seq: n.seq } : {}),
795
500
  ...(typeof n.summary === 'string' ? { summary: n.summary } : {}),
796
501
  });
797
502
  }
798
503
  function project(nowMs, opts) {
799
504
  const viewingTaskId = opts.viewingTaskId;
800
- // ① 帧间时钟推进(非终态行):elapsedMs 基值 + 帧后空窗。终态行冻结在 wire 终值(诚实)。
801
505
  const liveRows = [...taskMap.values()].map(r => {
802
506
  if (TERMINAL_FLEET_TASK_STATUSES.has(r.status ?? ''))
803
507
  return r;
804
- // [1518] δ:server 1.250 起行带 startedAt(launch 真锚)⇒ elapsed 自算,不再依赖补偿链。
805
508
  const started = r.startedAt;
806
509
  if (typeof started === 'number' && started > 0 && started <= nowMs) {
807
510
  return { ...r, elapsedMs: nowMs - started };
@@ -810,13 +513,10 @@ export function createFleetLedger(hooks = {}, opts = {}) {
810
513
  if (!meta)
811
514
  return r;
812
515
  const idle = Math.max(0, nowMs - meta.receivedAtMs);
813
- // 0.73.0:补偿链只给**真有基数**的行推进(基数 = 引擎快照 elapsedMs)。基数缺席时 `?? 0` 会把「本端收到这一行多久了」
814
- // 渲成「它跑了多久」—— 那是本端的钟,不是这一行的时长 ⇒ 保持缺席。
815
516
  if (!isElapsedBase(r.elapsedMs))
816
517
  return r;
817
518
  return idle > 0 ? { ...r, elapsedMs: r.elapsedMs + idle } : r;
818
519
  });
819
- // ② 终态留存池:live 集没有的行在宽限窗内继续投影;被查看中的行不过期。
820
520
  const retainedRows = [];
821
521
  for (const [id, r] of retained) {
822
522
  if (taskMap.has(id))
@@ -828,24 +528,12 @@ export function createFleetLedger(hooks = {}, opts = {}) {
828
528
  }
829
529
  retainedRows.push(r.row);
830
530
  }
831
- // ③ 行级 own 判别(clay 2026-07-23 双 session 串扰案,1.251 引擎对行帧广播,B 壳 FleetView
832
- // 直接看到 A 壳的子代行)。与通知帧的台账判别对称 —— 行帧此前只有连接级 `?session=`
833
- // 一道防线,server 旁路(≤1.252 无标行广播 / 无 auth 部署)即裸奔。
834
- // 🔴 B6:第三腿 `rowSessionTag`(rootSessionId/ownerSessionId/sessionId)已删 —— 见文件头
835
- // G12 直证(server 1.291.0 `stripT`/`stripW` 把 sessionId 与 scope 双双剥掉,另两键
836
- // 在行类型上从来不存在)。剩下的两腿一字未动。
837
- // 诚实边界(码注声明):老引擎 + 壳重启/resume 场景,自己的历史行会被藏 —— 宁藏勿串;
838
- // 1.244+ server 过滤在场时不受此限。
839
531
  const allRowsById = new Map();
840
532
  for (const r of [...liveRows, ...retainedRows])
841
533
  allRowsById.set(r.id, r);
842
534
  const ownTaskRow = (r) => {
843
535
  if (sessionScopedMeta === true)
844
536
  return true;
845
- // 祖链闭包:孙代行 parentId=直接父(a… handle)而非 spawn run([1502] wire 事实)——
846
- // 沿 parentId 链上溯,任一环(或环的 parentId)∈ 本端 run 台账即 own。
847
- // REF-CC-049(fleet2-11):`wireParentId` 单一铸点(空串按缺席),不再裸读 `cur.parentId`
848
- // 做 `!== undefined` 判断 —— 那种写法对空串脏值判「有父」,与 case 'task' 那半支相反。
849
537
  const seen = new Set();
850
538
  let cur = r;
851
539
  while (cur && !seen.has(cur.id)) {
@@ -868,16 +556,6 @@ export function createFleetLedger(hooks = {}, opts = {}) {
868
556
  workflows: projectWorkflows([...wfMap.values()].filter(ownWorkflowRow)),
869
557
  };
870
558
  }
871
- /**
872
- * ingress 工厂(stream/snapshot 共用)。`isLive` 让两条 ingress 各查各的代际位。
873
- *
874
- * 🔴 **单锚不变量**(对抗复审第一轮 [high] 采纳):一本 ledger 在**任一时刻只有一个会话锚**。
875
- * 两条 ingress 的代际位分开记,但两者**不许同时 live 且锚不同** —— 否则同一个 `taskMap` /
876
- * `retained` / 连接级 meta 会被两个会话代际同写(snapshot 的 REPLACE 还会抹掉 stream 那一代的行),
877
- * 而 `status().sessionAnchor` 只报 stream 那一个,混合态从读面上完全看不出来。
878
- * 落地:`issueStream()` **作废在飞的 snapshot ingress**;`issueSnapshot()` 在 stream 模式下
879
- * **继承** stream 的捕获而不自己现读(见各自实现)。
880
- */
881
559
  const makeIngress = (kind, epoch, session, isLive) => {
882
560
  let closed = false;
883
561
  return {
@@ -888,9 +566,6 @@ export function createFleetLedger(hooks = {}, opts = {}) {
888
566
  if (disposed)
889
567
  return;
890
568
  if (closed || !isLive()) {
891
- // 迟到帧(重连后旧 ingress / 已关的 ingress / 被流超车的快照):丢弃 + 计数。结构性堵死
892
- // 「旧流的帧配上新会话」这一错组合 —— 那正是 owner 台账「开流时一次性捕获、不逐帧现读」
893
- // 要防的形。
894
569
  droppedStaleIngress++;
895
570
  debug(`[fleet-frame] STALE ingress frame dropped(kind=${kind} epoch=${epoch} closed=${String(closed)}` +
896
571
  ` type=${String(frame.type)})`);
@@ -902,12 +577,6 @@ export function createFleetLedger(hooks = {}, opts = {}) {
902
577
  },
903
578
  close() {
904
579
  closed = true;
905
- // 🔴 收流要**真的退出流模式**(对抗复审第二轮 [medium]):只翻闭包位的话,`liveStreamEpoch`
906
- // 仍非 0 ⇒ 此后每次 `issueSnapshot()` 都把一条**已关**的流当 live、继承它的陈旧锚,于是
907
- // 「关流 → 换会话 → 取快照」会拿旧会话去打服务器,而 `status()` 还报旧会话。
908
- // ⚠️ 退出流模式**不等于**锚回落现读:账里还有上一会话的内容时 `status().sessionAnchor`
909
- // 照实报**内容归属锚**(见 `FleetLedgerStatus.sessionAnchor` 三态)。
910
- // epoch 相等才动:关一只**过期**的 ingress 绝不许影响更新的那一只。
911
580
  if (kind === 'stream' && liveStreamEpoch === epoch)
912
581
  liveStreamEpoch = 0;
913
582
  if (kind === 'snapshot' && liveSnapshotEpoch === epoch)
@@ -915,18 +584,6 @@ export function createFleetLedger(hooks = {}, opts = {}) {
915
584
  },
916
585
  };
917
586
  };
918
- /**
919
- * 🔴 **连接级 meta 复位** —— 每开一条新连接就清(对抗复审第三轮 [high])。
920
- *
921
- * `version`/`scoped`/`sessionScoped`/`bgNotifyFailClosed` 是「**那条连接**」的事实,不是会话内容:
922
- * `sessionScoped` 决定行级 own 判别整条**让位**,`bgNotifyFailClosed` 决定通知的台账判别整条**让位**
923
- * —— 两个都是 fail-**open** 方向的信任位。重连若落到老版本 / 降级节点 / 忽略 `?session=` 的节点,
924
- * 或新 `meta` 缺席/迟到,沿用上一条连接的这两位 = 广播行被投影、foreign 通知绕过本地归属门去改状态
925
- * 和触发钩子。**保留行与留存是对的(R7),保留连接信任位不是。**
926
- * 复位后到新 `meta` 到达之间那一小段:`sessionScoped=undefined` ⇒ 行级 own 判别回到本地台账
927
- * (成文的「宁藏勿串」),`bgNotifyFailClosed=false` ⇒ 通知回到 parentTaskId 判别 —— 两边都更严,方向对。
928
- * ⚠️ SDK 的发帧序是 `meta` **第一帧**,所以这个窗在真实连接上就是「首帧之前」。
929
- */
930
587
  const resetConnectionMeta = (why) => {
931
588
  version = null;
932
589
  scoped = null;
@@ -934,23 +591,7 @@ export function createFleetLedger(hooks = {}, opts = {}) {
934
591
  bgNotifyFailClosedMeta = false;
935
592
  debug(`[fleet-frame] 连接级 meta 复位(${why})—— 新连接自报之前不许沿用上一条连接的让位/信任位`);
936
593
  };
937
- /**
938
- * 🔴 换会话时把**上一会话的内容**整体清掉(对抗复审第二/三轮 [high])。
939
- * 射程 = 行 / 留存 / 行时刻 + 内容归属锚本身。计数器是生命期量,不清;连接级 meta 由
940
- * {@link resetConnectionMeta} 单独管(两件事拆开:换会话必清内容,换连接必清 meta)。
941
- * ⚠️ 与「重连」严格分开:**同一会话**重开流不清内容(留存池是渲染层宽限窗,R7 反钉:重连不该
942
- * 抽走已完成行)。判据是**捕获的会话变没变**,不是「有没有重开流」。
943
- */
944
- /** 账里有没有内容(行 / 工作流 / 留存)—— 换会话清账判据里「来路不明的存量」那一格用。 */
945
594
  const hasLedgerContent = () => taskMap.size > 0 || wfMap.size > 0 || retained.size > 0;
946
- /**
947
- * 🔴 发放 ingress 前的清账判据(对抗复审第六轮 [high] 补齐兼容入口那一格):
948
- * · 账里的内容**经 ingress 落的** ⇒ 有确切的归属锚,按「捕获值变没变」判(同会话重连 ⇒ 不清,R7);
949
- * · 账里的内容**经兼容入口落的** ⇒ 归属**不可知**(兼容入口每帧现读槽会话,库里不留锚;为它加一次
950
- * per-frame 端口读只会给 cli 热路径白白多计一份 miss)。不可知 = 不能替它背书 ⇒ **清**。
951
- * 实际影响面只有「从兼容入口迁到 ingress」那一次(清完 `hasLedgerContent()` 即假);
952
- * 而 cli 这类**纯兼容入口**宿主从不调 `issue*`,这条判据对它恒不触发 ⇒ 默认槽逐字节零变化。
953
- */
954
595
  const shouldResetContentFor = (session) => contentAnchorSet ? contentAnchor !== session : hasLedgerContent();
955
596
  const resetLedgerContentOnSessionChange = (from, to) => {
956
597
  taskMap.clear();
@@ -966,26 +607,18 @@ export function createFleetLedger(hooks = {}, opts = {}) {
966
607
  applyFrame(frame) {
967
608
  if (disposed)
968
609
  return;
969
- // 兼容入口:没有通道证据 ⇒ 会话锚现读本键槽(默认槽逐字节等价于 0.32.0)。
970
610
  applyFrame(frame, null);
971
611
  },
972
612
  issueStream() {
973
613
  const session = engineSessionParamFor(sessionKey);
974
- // 🔴 换会话 ≠ 重连:新捕获的会话与**账里现有内容所属**的会话不同 ⇒ 先清旧账再发新 ingress,
975
- // 否则 status() 报新会话而 project()/在飞读面还在渲旧会话的行(混合账,读面上看不出来)。
976
- // 同会话重开流(真正的重连)走不到这里,留存池原样保留(R7)。
977
614
  if (shouldResetContentFor(session)) {
978
615
  resetLedgerContentOnSessionChange(contentAnchorSet ? contentAnchor : '<兼容入口,归属不可知>', session);
979
616
  }
980
- // 🔴 连接级 meta **每开一条新连接就清**(与内容保留策略拆开):那两位是 fail-open 方向的信任位,
981
- // 沿用上一条连接的会让「重连落到降级/老版本/忽略 ?session= 的节点」直接绕过本地归属门。
982
617
  resetConnectionMeta('新 stream ingress');
983
618
  const epoch = ++epochSeq;
984
619
  liveStreamEpoch = epoch;
985
620
  streamAnchor = session;
986
621
  everIssuedStream = true;
987
- // 🔴 单锚不变量:开真流即作废在飞的一次性快照 ingress —— 快照是时点探针,流一开它就被取代了;
988
- // 留着它 = 允许「快照那一代的会话」与「本流的会话」并发写同一本账。
989
622
  liveSnapshotEpoch = 0;
990
623
  debug(`[fleet-frame] ingress issued(kind=stream key=${sessionKey} epoch=${epoch} session=${session ?? '-'})`);
991
624
  return {
@@ -994,41 +627,14 @@ export function createFleetLedger(hooks = {}, opts = {}) {
994
627
  };
995
628
  },
996
629
  issueSnapshot(snapOpts) {
997
- // 🔴 单锚不变量:stream 模式下**继承** stream 的不可变捕获,绝不自己现读。现读的形在「槽内会话
998
- // 已轮换、宿主还没重开流」时会铸出第二个锚,于是两条 live ingress 各按各的会话给同一本账
999
- // 背书。继承同时满足 design/285「snapshot 绝不改写 stream 锚」——它只读不写。
1000
- // 真换了会话的正确姿势是先 `issueStream()`(= 重连),那一步会把锚整体推进。
1001
630
  const inStreamMode = liveStreamEpoch !== 0;
1002
- // 🔴 **兼容入口消费的活流也是活流**(对抗复审第八轮 [high]):那条车道不设 `liveStreamEpoch`,
1003
- // 只有宿主的 `setConnected(true)` 说得出「现在有一条流连着」。漏判它 = 把一条**正活着**的
1004
- // 连接当 snapshot-only 处理,当场清掉它的连接级 meta —— 而那条流的 `meta` 只在连接首帧出现过,
1005
- // 于是 `sessionScoped`/`bgNotifyFailClosed` 会在**剩余整个连接生命期**保持关闭(行投影退回本地
1006
- // own-run 判别而静默藏行)。⇒ 活流期间签发的快照:**不清内容、不清 meta、永久判过期**。
1007
- // 🔴 但 `connected` 只对**纯兼容入口宿主**有权威性(第九轮 [medium]):采用了 ingress 的宿主,
1008
- // 流的存活由 `liveStreamEpoch` 说了算 —— `close()` 只退流模式、不替宿主改 `connected`,拿一个
1009
- // 陈旧的 true 当「活流」会把合法降级序列「开流 → close → 取一次性快照兜底」的快照永久判废。
1010
631
  const compatStreamLive = !inStreamMode && connected && !everIssuedStream;
1011
632
  const duringLiveStream = inStreamMode || compatStreamLive;
1012
- // 🔴 **活流分支绝不现读端口**(对抗复审第十轮 [medium]):无条件先 `engineSessionParamFor()` 的写法
1013
- // 与本批「stream 模式下继承捕获、不自己现读」那句承诺自相矛盾 —— 端口热重载期间它会铸出**虚假的
1014
- // session miss**(实测:开流后撤端口再签发快照,`hostPortMissesFor(key).session` 0→1),端口实现
1015
- // 抛错时还会让 `issueSnapshot()` 整条失败。取值只在 snapshot-only 分支发生。
1016
633
  const session = inStreamMode ? streamAnchor : engineSessionParamFor(sessionKey);
1017
- // 🔴 **snapshot-only 车道的换会话也要清账**(对抗复审第三轮 [high]):没有 live stream 时快照
1018
- // 自己现读,而「A 流写完终态留存 + meta 后 close → 槽切到 B → 只取快照」这条真实时序里,
1019
- // snapshot 的 REPLACE **刻意不清留存**(R7)、也带不来新 meta ⇒ B 的活跃行会与 A 的留存行同账,
1020
- // 且 A 那条连接的 `sessionScoped=true` 还在替整本账放行。走与 issueStream 同一套清账。
1021
- // ⚠️ 只在**真的没有活流**时做:活流期间签发的快照本来就永久判过期,清账/清 meta 只会破坏
1022
- // 那条还活着的连接(第八轮 [high])。
1023
634
  if (!duringLiveStream) {
1024
635
  if (shouldResetContentFor(session)) {
1025
636
  resetLedgerContentOnSessionChange(contentAnchorSet ? contentAnchor : '<兼容入口,归属不可知>', session);
1026
637
  }
1027
- // 🔴 **快照连接自带不了 meta**(对抗复审第四轮 [high]):SDK 的 `fleet.snapshot()` 内部开流→取
1028
- // snapshot 帧→关流,`meta` 在封装里被吞 ⇒ 这条连接的行**没有任何 scoping 断言**。若沿用上一条
1029
- // (已关的)stream 连接的 `sessionScoped=true`,快照落在降级/不按 `?session=` 过滤的节点上时,
1030
- // 它带回的外来行会被一条**不相干连接**的信任位无条件放行投影,而且此后**没有任何 meta 帧**能
1031
- // 纠正它。⇒ 快照车道一律复位连接级 meta,行回到本地台账判别(fail-closed 方向)。
1032
638
  resetConnectionMeta('快照连接:SDK 封装吞掉了它自己的 meta,不许沿用别条连接的信任位');
1033
639
  }
1034
640
  if (inStreamMode) {
@@ -1036,33 +642,13 @@ export function createFleetLedger(hooks = {}, opts = {}) {
1036
642
  ' 要切会话请先 issueStream())');
1037
643
  }
1038
644
  const epoch = ++epochSeq;
1039
- liveSnapshotEpoch = epoch; // 🔴 不碰 liveStreamEpoch / streamAnchor
645
+ liveSnapshotEpoch = epoch;
1040
646
  debug(`[fleet-frame] ingress issued(kind=snapshot key=${sessionKey} epoch=${epoch} session=${session ?? '-'})`);
1041
- // 🔴 **有 live stream 时快照一律不落账**(对抗复审第二/四轮 [high] 的合流解):
1042
- // ①两条连接两个写者,而 `applySnapshot` 是无条件 REPLACE —— 「服务端取样之后、落账之前」流
1043
- // 又推了一帧时,照落会把**更新的**行覆盖回旧值,而 fleet 是 latest-state 总线,没有任何
1044
- // 后续帧保证修回来(只能等那一行下次 transition);
1045
- // ②快照连接的 `meta` 被 SDK 封装吞掉 ⇒ 它的行**自带零 scoping 断言**,落进一本正持着别的
1046
- // 连接 `sessionScoped=true` 的账里,就会被那条**不相干连接**的信任位无条件放行投影。
1047
- // 而 live stream 本身就是「snapshot 先行的 latest-state 总线」—— 它一开就带全量快照并持续
1048
- // 刷新,此时再落一份一次性快照**零收益**。⇒ 丢弃 + 计 `droppedStaleIngress`。
1049
- // 一次性快照的正当场景(首屏/温切探针)本来就发生在**没有 live stream**的时候。
1050
- // 写序号闸(对抗复审第五轮 [high]):「有 live stream 时不落账」挡不住**兼容入口**消费的活流
1051
- // (那条路上 `liveStreamEpoch` 恒为 0)⇒ 再叠一道全入口通用的账本写序号。
1052
647
  const writesAtIssue = ledgerWrites;
1053
- // 🔴 「有没有 live stream」必须**固化在发放那一刻**(对抗复审第七轮 [high] 的 close-before-result
1054
- // 竞态):落账时才现查 `liveStreamEpoch === 0` 的话,「流活着时签发快照 → 快照还在路上、流先
1055
- // `close()` → 快照返回落账」会因为 close 把 epoch 清零而**反而通过闸**;而这只快照当初正是在
1056
- // stream 模式下签发的 —— 它跳过了 `resetConnectionMeta()`,于是会沿用一条**已关连接**的
1057
- // `sessionScoped=true` 去给自己带回来的行背书。⇒ 流存活期间签发的快照**永久判过期**。
1058
- // (发放时无流的那只不必再查 `liveStreamEpoch`:此后一旦 `issueStream()`,它会把
1059
- // `liveSnapshotEpoch` 置 0,epoch 那一条已经把它挡下了;兼容车道则由 `ledgerWrites` 挡。)
1060
648
  const issuedDuringStream = duringLiveStream;
1061
649
  const base = makeIngress('snapshot', epoch, session, () => liveSnapshotEpoch === epoch && !issuedDuringStream && ledgerWrites === writesAtIssue);
1062
650
  return {
1063
651
  ...base,
1064
- // SDK 的 `fleet.snapshot()` 回的是 `{tasks, workflows}`(不是帧)—— 帧形在库内合成一处,
1065
- // 端不再手抄。`ts` 走本地钟只是占位:台账对 snapshot 帧的时刻恒用 `nowFn()`,不读 `frame.ts`。
1066
652
  applySnapshot(snapshot) {
1067
653
  base.applyFrame({
1068
654
  type: 'snapshot',
@@ -1092,11 +678,6 @@ export function createFleetLedger(hooks = {}, opts = {}) {
1092
678
  droppedMalformed,
1093
679
  droppedUnknownFrame,
1094
680
  droppedForeignBgNotification,
1095
- // 「此刻的判据/内容所属锚」三态(对抗复审第五轮 [high] 收紧):
1096
- // ①有 live stream ⇒ 它开流那一刻的捕获(它的帧就按这个判);
1097
- // ②没有 live stream 但**账里有内容** ⇒ 内容归属锚 —— 绝不改报一个还没被用过的现读值,
1098
- // 否则「close 之后槽会话轮换」这一刻 status 就会报新会话而账里装的还是旧会话的行;
1099
- // ③空账无流 ⇒ 兼容入口下一帧会现读的那个值。
1100
681
  sessionAnchor: liveStreamEpoch !== 0 ? streamAnchor : contentAnchorSet ? contentAnchor : engineSessionParamFor(sessionKey),
1101
682
  sessionAnchorEpoch: liveStreamEpoch,
1102
683
  droppedStaleIngress,