@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.
- package/CHANGELOG.md +23 -1
- package/README.md +5 -4
- package/dist/abortableSleep.d.ts +0 -28
- package/dist/abortableSleep.js +0 -28
- package/dist/adapt/arms.d.ts +0 -46
- package/dist/adapt/arms.js +9 -544
- package/dist/adapt/ids.d.ts +0 -58
- package/dist/adapt/ids.js +0 -63
- package/dist/adapt/instanceLedger.d.ts +0 -25
- package/dist/adapt/instanceLedger.js +0 -20
- package/dist/adapt/panelTasks.d.ts +0 -69
- package/dist/adapt/panelTasks.js +0 -95
- package/dist/adapt/textSegmentAuthority.d.ts +1 -137
- package/dist/adapt/textSegmentAuthority.js +0 -54
- package/dist/adapt/textStream.d.ts +0 -252
- package/dist/adapt/textStream.js +2 -281
- package/dist/adapt/toolCards.d.ts +0 -46
- package/dist/adapt/toolCards.js +0 -23
- package/dist/adapt/turnFlags.d.ts +0 -54
- package/dist/adapt/turnFlags.js +1 -17
- package/dist/adapt/wireShapes.d.ts +0 -92
- package/dist/adapt/wireShapes.js +0 -83
- package/dist/adapt.d.ts +0 -55
- package/dist/adapt.js +1 -120
- package/dist/adapter/activeRunSelfHeal.d.ts +22 -515
- package/dist/adapter/activeRunSelfHeal.js +10 -625
- package/dist/adapter/downstream/eventToSdkMessage.d.ts +2 -305
- package/dist/adapter/downstream/eventToSdkMessage.js +2 -867
- package/dist/adapter/downstream/terminalToSdkResult.d.ts +2 -314
- package/dist/adapter/downstream/terminalToSdkResult.js +13 -560
- package/dist/adapter/downstream/turnUsageToModelUsage.d.ts +0 -95
- package/dist/adapter/downstream/turnUsageToModelUsage.js +2 -31
- package/dist/adapter/runStream.d.ts +0 -206
- package/dist/adapter/runStream.js +6 -596
- package/dist/adapter/types.d.ts +0 -111
- package/dist/adapter/types.js +0 -29
- package/dist/agentSession/backgroundView.d.ts +0 -106
- package/dist/agentSession/backgroundView.js +3 -49
- package/dist/agentSession/contract.d.ts +0 -98
- package/dist/agentSession/contract.js +0 -12
- package/dist/agentsWireCaps.d.ts +0 -72
- package/dist/agentsWireCaps.js +4 -72
- package/dist/approvalsStreamLiveCapability.d.ts +0 -25
- package/dist/approvalsStreamLiveCapability.js +0 -31
- package/dist/argvFlagValue.d.ts +0 -33
- package/dist/argvFlagValue.js +3 -35
- package/dist/attachmentsWireCaps.d.ts +0 -127
- package/dist/attachmentsWireCaps.js +0 -144
- package/dist/autoModeUnavailable.d.ts +0 -120
- package/dist/autoModeUnavailable.js +0 -144
- package/dist/classifierStatus.d.ts +0 -72
- package/dist/classifierStatus.js +0 -167
- package/dist/classifierVerdictWire.d.ts +0 -54
- package/dist/classifierVerdictWire.js +0 -133
- package/dist/clientContextWireCaps.d.ts +0 -37
- package/dist/clientContextWireCaps.js +0 -36
- package/dist/clientSlice.d.ts +0 -63
- package/dist/clientSlice.js +0 -45
- package/dist/cloudConfigWireCaps.d.ts +0 -86
- package/dist/cloudConfigWireCaps.js +2 -65
- package/dist/compensations.d.ts +0 -52
- package/dist/compensations.js +2 -63
- package/dist/controlRouter.d.ts +2 -208
- package/dist/controlRouter.js +1 -129
- package/dist/coreValuePorts.d.ts +0 -84
- package/dist/coreValuePorts.js +0 -29
- package/dist/decideReceipt.d.ts +0 -110
- package/dist/decideReceipt.js +0 -84
- package/dist/detachWire.d.ts +0 -130
- package/dist/detachWire.js +1 -130
- package/dist/deviceExecutorManagementCapability.d.ts +2 -35
- package/dist/deviceExecutorManagementCapability.js +1 -37
- package/dist/diagnostics.d.ts +0 -8
- package/dist/diagnostics.js +0 -8
- package/dist/diff/patch.d.ts +0 -17
- package/dist/diff/patch.js +0 -19
- package/dist/effectiveFacts.d.ts +0 -40
- package/dist/effectiveFacts.js +0 -22
- package/dist/effortWire.d.ts +0 -11
- package/dist/effortWire.js +0 -12
- package/dist/engineAgentPanelStore.d.ts +0 -169
- package/dist/engineAgentPanelStore.js +13 -293
- package/dist/engineCapReader.d.ts +0 -62
- package/dist/engineCapReader.js +1 -43
- package/dist/engineCapsCache.d.ts +0 -177
- package/dist/engineCapsCache.js +0 -199
- package/dist/engineCapsGenerationGuard.d.ts +0 -2
- package/dist/engineCapsGenerationGuard.js +0 -11
- package/dist/engineErrorCodes.d.ts +1 -316
- package/dist/engineErrorCodes.js +0 -427
- package/dist/engineHttpTools.d.ts +0 -29
- package/dist/engineHttpTools.js +0 -29
- package/dist/engineIdentity.d.ts +0 -83
- package/dist/engineIdentity.js +0 -88
- package/dist/engineInlineTaskStats.d.ts +0 -57
- package/dist/engineInlineTaskStats.js +1 -42
- package/dist/engineNoticeCodes.d.ts +0 -190
- package/dist/engineNoticeCodes.js +0 -182
- package/dist/engineSessionParam.d.ts +0 -30
- package/dist/engineSessionParam.js +0 -52
- package/dist/engineToolLabelStore.d.ts +0 -29
- package/dist/engineToolLabelStore.js +0 -31
- package/dist/engineWireSdk.d.ts +0 -90
- package/dist/engineWireSdk.js +0 -74
- package/dist/engineWireTarget.d.ts +0 -14
- package/dist/engineWireTarget.js +0 -39
- package/dist/env/localeGeo.d.ts +0 -12
- package/dist/env/localeGeo.js +2 -77
- package/dist/env/localeTag.d.ts +0 -34
- package/dist/env/localeTag.js +0 -32
- package/dist/env/uiLanguage.d.ts +0 -13
- package/dist/env/uiLanguage.js +0 -25
- package/dist/envFlag.d.ts +0 -37
- package/dist/envFlag.js +0 -40
- package/dist/executionLaneCapability.d.ts +0 -48
- package/dist/executionLaneCapability.js +0 -54
- package/dist/finalVerifyWire.d.ts +0 -67
- package/dist/finalVerifyWire.js +3 -43
- package/dist/fleet/fleetLedger.d.ts +0 -308
- package/dist/fleet/fleetLedger.js +10 -429
- package/dist/fleet/fleetProjection.d.ts +0 -240
- package/dist/fleet/fleetProjection.js +0 -189
- package/dist/fleet/fleetRowAgentType.d.ts +0 -6
- package/dist/fleet/fleetRowAgentType.js +1 -32
- package/dist/fleet/workflowSizeWarning.d.ts +0 -47
- package/dist/fleet/workflowSizeWarning.js +1 -46
- package/dist/fleetAgentPanelProjection.d.ts +0 -48
- package/dist/fleetAgentPanelProjection.js +8 -173
- package/dist/fleetTaskDesc.d.ts +0 -39
- package/dist/fleetTaskDesc.js +0 -69
- package/dist/forkWireCaps.d.ts +0 -23
- package/dist/forkWireCaps.js +1 -24
- package/dist/gateOutcome.d.ts +0 -140
- package/dist/gateOutcome.js +0 -85
- package/dist/gateVocabulary.d.ts +0 -114
- package/dist/gateVocabulary.js +1 -161
- package/dist/goalStopHook.d.ts +0 -127
- package/dist/goalStopHook.js +0 -178
- package/dist/headlessPermissionModeWire.d.ts +0 -76
- package/dist/headlessPermissionModeWire.js +1 -155
- package/dist/headlessReconnectWire.d.ts +0 -84
- package/dist/headlessReconnectWire.js +13 -67
- package/dist/hitl/approvalDecisionNoteAudit.d.ts +0 -49
- package/dist/hitl/approvalDecisionNoteAudit.js +0 -56
- package/dist/hitl/approvalOutcomeNote.d.ts +0 -2
- package/dist/hitl/approvalOutcomeNote.js +0 -20
- package/dist/hitl/approvalResolution.d.ts +0 -113
- package/dist/hitl/approvalResolution.js +0 -66
- package/dist/hitl/approvalsFeed.d.ts +0 -183
- package/dist/hitl/approvalsFeed.js +12 -243
- package/dist/hitl/armedGateRegistry.d.ts +0 -55
- package/dist/hitl/armedGateRegistry.js +0 -146
- package/dist/hitl/askGateWire.d.ts +0 -86
- package/dist/hitl/askGateWire.js +1 -95
- package/dist/hitl/askParkRowRouting.d.ts +0 -123
- package/dist/hitl/askParkRowRouting.js +1 -89
- package/dist/hitl/crashConverged.d.ts +0 -148
- package/dist/hitl/crashConverged.js +0 -214
- package/dist/hitl/editedRuleTextPrecheck.d.ts +0 -109
- package/dist/hitl/editedRuleTextPrecheck.js +0 -71
- package/dist/hitl/frameRouter.d.ts +0 -134
- package/dist/hitl/frameRouter.js +6 -378
- package/dist/hitl/gateIdentity.d.ts +0 -39
- package/dist/hitl/gateIdentity.js +0 -41
- package/dist/hitl/gateLedger.d.ts +0 -267
- package/dist/hitl/gateLedger.js +0 -121
- package/dist/hitl/hitlBridge.d.ts +7 -431
- package/dist/hitl/hitlBridge.js +7 -476
- package/dist/hitl/hitlHostSurface.d.ts +0 -150
- package/dist/hitl/hitlHostSurface.js +0 -169
- package/dist/hitl/livePendingAsk.d.ts +0 -91
- package/dist/hitl/livePendingAsk.js +0 -74
- package/dist/hitl/localAllowRule.d.ts +0 -62
- package/dist/hitl/localAllowRule.js +1 -33
- package/dist/hitl/parkOwnership.d.ts +0 -56
- package/dist/hitl/parkOwnership.js +0 -22
- package/dist/hitl/parkResolver.d.ts +3 -88
- package/dist/hitl/parkResolver.js +8 -349
- package/dist/hitl/parkRowBirthWait.d.ts +2 -26
- package/dist/hitl/parkRowBirthWait.js +3 -93
- package/dist/hitl/persistedRulesWire.d.ts +13 -358
- package/dist/hitl/persistedRulesWire.js +0 -310
- package/dist/hitl/planReviewWire.d.ts +4 -176
- package/dist/hitl/planReviewWire.js +9 -312
- package/dist/hitl/resumeRunningCard.d.ts +0 -105
- package/dist/hitl/resumeRunningCard.js +0 -105
- package/dist/hitl/sessionPolicyWire.d.ts +11 -191
- package/dist/hitl/sessionPolicyWire.js +0 -149
- package/dist/hitl/suspendedReopen.d.ts +3 -24
- package/dist/hitl/suspendedReopen.js +0 -14
- package/dist/hitl/toolApprovalWire.d.ts +5 -1308
- package/dist/hitl/toolApprovalWire.js +5 -945
- package/dist/hooksWireCaps.d.ts +0 -38
- package/dist/hooksWireCaps.js +0 -190
- package/dist/host.d.ts +0 -105
- package/dist/host.js +0 -33
- package/dist/hostEnv.d.ts +0 -14
- package/dist/hostEnv.js +0 -13
- package/dist/imagesWireCaps.d.ts +0 -21
- package/dist/imagesWireCaps.js +0 -22
- package/dist/index.d.ts +0 -134
- package/dist/index.js +0 -476
- package/dist/interactiveHalt.d.ts +5 -153
- package/dist/interactiveHalt.js +0 -111
- package/dist/interactiveToolsWire.d.ts +0 -62
- package/dist/interactiveToolsWire.js +1 -67
- package/dist/leaderConflict.d.ts +0 -59
- package/dist/leaderConflict.js +0 -48
- package/dist/limitsWire.d.ts +0 -125
- package/dist/limitsWire.js +2 -120
- package/dist/liveInitToolFace.d.ts +0 -77
- package/dist/liveInitToolFace.js +1 -109
- package/dist/liveModelCatalog.d.ts +0 -52
- package/dist/liveModelCatalog.js +0 -48
- package/dist/liveQuestionStore.d.ts +0 -87
- package/dist/liveQuestionStore.js +1 -56
- package/dist/mcpLiveness.d.ts +0 -151
- package/dist/mcpLiveness.js +0 -122
- package/dist/mcpPanel.d.ts +0 -104
- package/dist/mcpPanel.js +0 -49
- package/dist/mcpReconnect.d.ts +0 -58
- package/dist/mcpReconnect.js +1 -69
- package/dist/mcpWireCaps.d.ts +0 -55
- package/dist/mcpWireCaps.js +1 -13
- package/dist/memoryComplianceCapability.d.ts +3 -57
- package/dist/memoryComplianceCapability.js +0 -57
- package/dist/memoryEntriesWire.d.ts +6 -198
- package/dist/memoryEntriesWire.js +0 -144
- package/dist/memoryOriginCapability.d.ts +3 -53
- package/dist/memoryOriginCapability.js +0 -50
- package/dist/memorySpecWire.d.ts +0 -116
- package/dist/memorySpecWire.js +0 -150
- package/dist/model/catalog.d.ts +0 -111
- package/dist/model/catalog.js +0 -87
- package/dist/model/catalogLoader.d.ts +0 -114
- package/dist/model/catalogLoader.js +2 -139
- package/dist/model/modelSupplyRules.d.ts +1 -55
- package/dist/model/modelSupplyRules.js +0 -62
- package/dist/model/providerAuth.d.ts +0 -103
- package/dist/model/providerAuth.js +2 -38
- package/dist/model/providerCatalog.d.ts +0 -45
- package/dist/model/providerCatalog.js +0 -37
- package/dist/model/providerPresets.d.ts +0 -33
- package/dist/model/providerPresets.js +2 -91
- package/dist/model/tierVocabulary.d.ts +0 -31
- package/dist/model/tierVocabulary.js +0 -27
- package/dist/modelBudgetRule.d.ts +0 -39
- package/dist/modelBudgetRule.js +0 -39
- package/dist/modelCapabilityProbe.d.ts +0 -175
- package/dist/modelCapabilityProbe.js +0 -147
- package/dist/modelWireCaps.d.ts +0 -13
- package/dist/modelWireCaps.js +0 -13
- package/dist/notifications.d.ts +0 -233
- package/dist/notifications.js +14 -492
- package/dist/oneShotWireCaps.d.ts +0 -34
- package/dist/oneShotWireCaps.js +0 -35
- package/dist/ownKey.d.ts +0 -33
- package/dist/ownKey.js +0 -33
- package/dist/panelRunningHistory.d.ts +0 -28
- package/dist/panelRunningHistory.js +0 -43
- package/dist/peerFrames.d.ts +0 -71
- package/dist/peerFrames.js +0 -168
- package/dist/peerLaneCapability.d.ts +0 -44
- package/dist/peerLaneCapability.js +0 -52
- package/dist/permissionRuleIssue.d.ts +0 -30
- package/dist/permissionRuleIssue.js +0 -78
- package/dist/permissionRulesWriteCapability.d.ts +0 -46
- package/dist/permissionRulesWriteCapability.js +0 -54
- package/dist/permissionWireCaps.d.ts +0 -37
- package/dist/permissionWireCaps.js +0 -37
- package/dist/postureKnob.d.ts +0 -71
- package/dist/postureKnob.js +0 -86
- package/dist/principalWire.d.ts +0 -17
- package/dist/principalWire.js +0 -17
- package/dist/printToolResultFrame.d.ts +0 -100
- package/dist/printToolResultFrame.js +0 -33
- package/dist/promptProfileWireCaps.d.ts +0 -13
- package/dist/promptProfileWireCaps.js +0 -13
- package/dist/readFacePosture.d.ts +0 -43
- package/dist/readFacePosture.js +0 -46
- package/dist/request/printNotification.d.ts +0 -20
- package/dist/request/printNotification.js +0 -55
- package/dist/request/taskRequest.d.ts +0 -249
- package/dist/request/taskRequest.js +10 -496
- package/dist/resumeRefusalCopy.d.ts +0 -136
- package/dist/resumeRefusalCopy.js +1 -117
- package/dist/retainBackgroundWireCaps.d.ts +0 -48
- package/dist/retainBackgroundWireCaps.js +0 -48
- package/dist/retryStatus.d.ts +2 -221
- package/dist/retryStatus.js +0 -107
- package/dist/rewindWireCaps.d.ts +0 -27
- package/dist/rewindWireCaps.js +0 -24
- package/dist/runCancelContext.d.ts +1 -20
- package/dist/runCancelContext.js +0 -34
- package/dist/runTerminal.d.ts +0 -273
- package/dist/runTerminal.js +0 -177
- package/dist/sandboxWire.d.ts +0 -38
- package/dist/sandboxWire.js +0 -82
- package/dist/scenarioWire.d.ts +0 -60
- package/dist/scenarioWire.js +0 -68
- package/dist/scratchpadWireCaps.d.ts +0 -11
- package/dist/scratchpadWireCaps.js +0 -32
- package/dist/sdkWireTransit.d.ts +0 -38
- package/dist/sdkWireTransit.js +0 -30
- package/dist/seam.d.ts +18 -853
- package/dist/seam.js +0 -52
- package/dist/seatContract.d.ts +0 -555
- package/dist/seatContract.js +5 -304
- package/dist/selfOrchestrationDenial.d.ts +0 -189
- package/dist/selfOrchestrationDenial.js +0 -162
- package/dist/selfOrchestrationWireCaps.d.ts +0 -38
- package/dist/selfOrchestrationWireCaps.js +0 -38
- package/dist/sessionMap.d.ts +0 -60
- package/dist/sessionMap.js +0 -37
- package/dist/sessionMemoryStatus.d.ts +11 -114
- package/dist/sessionMemoryStatus.js +0 -112
- package/dist/sessionModelLatch.d.ts +0 -29
- package/dist/sessionModelLatch.js +0 -40
- package/dist/sessionPolicyCapability.d.ts +3 -53
- package/dist/sessionPolicyCapability.js +0 -52
- package/dist/sessionSlot.d.ts +0 -26
- package/dist/sessionSlot.js +0 -17
- package/dist/skillsWireCaps.d.ts +0 -87
- package/dist/skillsWireCaps.js +0 -38
- package/dist/sqlEngineCapability.d.ts +0 -118
- package/dist/sqlEngineCapability.js +0 -134
- package/dist/sseIdleTriage.d.ts +0 -79
- package/dist/sseIdleTriage.js +1 -80
- package/dist/steering.d.ts +0 -54
- package/dist/steering.js +0 -63
- package/dist/subagent/engineCompactWire.d.ts +0 -34
- package/dist/subagent/engineCompactWire.js +4 -134
- package/dist/subagent/engineDelegatedPrompt.d.ts +0 -31
- package/dist/subagent/engineDelegatedPrompt.js +1 -103
- package/dist/subagent/engineRowStopGate.d.ts +0 -20
- package/dist/subagent/engineRowStopGate.js +0 -49
- package/dist/subagent/engineSubagentOutput.d.ts +0 -7
- package/dist/subagent/engineSubagentOutput.js +1 -51
- package/dist/subagent/engineSubagentResume.d.ts +2 -279
- package/dist/subagent/engineSubagentResume.js +2 -129
- package/dist/subagent/engineSubagentSteer.d.ts +0 -26
- package/dist/subagent/engineSubagentSteer.js +0 -63
- package/dist/subagent/engineSubagentTail.d.ts +0 -38
- package/dist/subagent/engineSubagentTail.js +2 -110
- package/dist/subagent/engineTaskHandleWire.d.ts +11 -65
- package/dist/subagent/engineTaskHandleWire.js +20 -131
- package/dist/subagent/subagentOwnerAbsence.d.ts +0 -2
- package/dist/subagent/subagentOwnerAbsence.js +0 -25
- package/dist/subagentContentStore.d.ts +3 -260
- package/dist/subagentContentStore.js +7 -540
- package/dist/systemReminderTag.d.ts +0 -49
- package/dist/systemReminderTag.js +0 -61
- package/dist/toolResult.d.ts +1 -211
- package/dist/toolResult.js +1 -358
- package/dist/toolRoster.d.ts +0 -140
- package/dist/toolRoster.js +0 -66
- package/dist/typePins.d.ts +0 -46
- package/dist/types/engineState.d.ts +0 -71
- package/dist/types/engineState.js +0 -16
- package/dist/ultracodeWireCaps.d.ts +0 -75
- package/dist/ultracodeWireCaps.js +0 -92
- package/dist/unrefTimer.d.ts +0 -27
- package/dist/webSearchBackendCapability.d.ts +0 -72
- package/dist/webSearchBackendCapability.js +0 -79
- package/dist/webSearchWireCaps.d.ts +0 -37
- package/dist/webSearchWireCaps.js +0 -34
- package/dist/websearch/searchProviderPresets.d.ts +0 -81
- package/dist/websearch/searchProviderPresets.js +0 -27
- package/dist/wireErrorTriage.d.ts +4 -160
- package/dist/wireErrorTriage.js +0 -171
- package/dist/wireRefusalCopy.d.ts +0 -28
- package/dist/wireRefusalCopy.js +0 -28
- package/dist/workflow.d.ts +0 -40
- package/dist/workflow.js +0 -52
- package/dist/workflowClient.d.ts +0 -122
- package/dist/workflowClient.js +8 -360
- package/dist/workflowMonitor.d.ts +2 -72
- package/dist/workflowMonitor.js +0 -26
- package/dist/writeProtectionCapability.d.ts +0 -112
- package/dist/writeProtectionCapability.js +0 -109
- package/docs/INTEGRATION-CLIENTS.md +62 -1
- 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++;
|
|
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);
|
|
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);
|
|
314
|
-
raiseCycleWatermark(rowIdTail(row.id), wireCycleSeq(row));
|
|
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++;
|
|
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;
|
|
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)
|
|
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;
|
|
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;
|
|
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,
|