@sema-agent/client-core 0.77.1 → 0.78.0
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 +42 -1
- package/README.md +6 -5
- 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 +3 -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 +23 -371
- package/dist/hitl/persistedRulesWire.js +62 -329
- 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 +0 -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 +1 -249
- package/dist/request/taskRequest.js +20 -498
- 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 +6 -67
- package/dist/subagent/engineTaskHandleWire.js +0 -126
- 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 +78 -5
- package/package.json +3 -3
|
@@ -1,79 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ⇄ B1 批搬迁(2026-07-27,多端改造设计稿 §2.4.1-B):cli src/sema/subagentContentStore.ts 逐字搬入(零依赖;壳侧改 re-export)。
|
|
3
|
-
* 🔴 module 级台账(tasks/parentToTask/ownEngineRuns/bgFacts)= 单实例语义:写方(seam adapter
|
|
4
|
-
* runStream 的子代内容分流 + liveClient 的 run_started own 台账)与读方(查看态/帧级 own 判别)
|
|
5
|
-
* 必须走**同一个模块实例**,否则 own 判别恒 false(帧级校验静默失效)。
|
|
6
|
-
*/
|
|
7
|
-
/**
|
|
8
|
-
* subagentContentStore — dep-free side-channel store for ENGINE subagent CONTENT events (C1 consumption,
|
|
9
|
-
* service 1.89 `forwardSubagentEvents` + core 1.219; blackboard [381]⑤/[383]③).
|
|
10
|
-
*
|
|
11
|
-
* WIRE FACTS: with `forwardSubagentEvents: true` on the task request, the live stream carries the
|
|
12
|
-
* subagent's text_delta / reasoning_delta / tool_start / tool_end stamped with EventIdentity
|
|
13
|
-
* —— 🔴 **durable 腿(重放)送的是同一段的聚合形** `text` / `reasoning`(整段全文,同样带
|
|
14
|
-
* EventIdentity)。两类都要收:只收增量的话,续听重放尾的宿主整条子代转录都是空的
|
|
15
|
-
* (0.62.0 补;两类的处置**不同**,见 {@link publishSubagentContentEvent} 的吸收规则)
|
|
16
|
-
* 🔴 **重放的幂等闸覆盖四个臂**(`text` / `reasoning` / `tool_start` / `tool_end`):重连 = 新的一条
|
|
17
|
-
* 流,缝前那道事件序号去重只在同一条流内有效,所以「整轮重放」拦得住的只能是这本账。判据形相同
|
|
18
|
-
* (先问身份、再问账本),身份与账本各按臂取:散文两臂 = 聚合帧 `eventId` + 已上屏的整段正文;
|
|
19
|
-
* 工具两臂 = `toolCallId` + `items` 上那张卡,收口另带一本**自己额度**的收口身份账(同一次调用
|
|
20
|
-
* 会有**第二发**真收口 —— 门 park 之后恢复执行,见 {@link publishSubagentContentEvent})。
|
|
21
|
-
* (§E1 redact upstream; §E2 identity). 🔴 EventIdentity 的键是 `eventId` / `parentToolCallId`
|
|
22
|
-
* (+ LIVE 白名单四臂上的 `sourceTaskId` / `bgAgentId`)—— **没有 `taskId`**(sdk `events.d.ts` 的
|
|
23
|
-
* `interface EventIdentity` 直证;`taskId` 只长在 `meta` 首帧上)。所以内容帧到不了「自带引擎
|
|
24
|
-
* taskId」那一步,归账键靠 `parentToTask`,见 `canonicalKey`。The seam adapter (runStream.ts)
|
|
25
|
-
* DIVERTS those events here — they must never reach the main-transcript pipeline (a subagent's tokens
|
|
26
|
-
* rendering as the leader's text is the pollution failure mode).
|
|
27
|
-
*
|
|
28
|
-
* WHAT IT KEEPS (bounded, per task):
|
|
29
|
-
* - a NEUTRAL item log: {kind:'text'|'thinking',text} | {kind:'tool',name,input,output,isError,id}
|
|
30
|
-
* (ring-capped) — the 查看态 transcript backlog, so entering a running agent mid-flight still shows
|
|
31
|
-
* history. Message SHAPES are built by the consumers (engineAgentView/chrome-agentprogress own the
|
|
32
|
-
* CC message creators; this store stays dep-free like engineAgentPanelStore).
|
|
33
|
-
* - live accumulation buffers (streaming text/thinking not yet segment-closed).
|
|
34
|
-
* - taskId → parentToolCallId (the C2 steer target — service routes subagent steer by parentToolCallId).
|
|
35
|
-
*
|
|
36
|
-
* NOTIFY: coalesced (~250ms) per-task notify so a per-token delta storm never turns into per-token
|
|
37
|
-
* AppState writes; consumers pull a snapshot on notify.
|
|
38
|
-
*/
|
|
39
|
-
/**
|
|
40
|
-
* `tool_end.output` → 本 store 的 `output` 位(**唯一**字符串化口)。
|
|
41
|
-
*
|
|
42
|
-
* 为什么住在这里(#158 移交① / [3674](d) 姊妹病,2026-08-12 搬入):这个函数原本长在
|
|
43
|
-
* `subagent/engineSubagentTail.ts`(tail 腿)里。而喂本 store 的是**两条**腿 —— tail 腿与
|
|
44
|
-
* seam 适配器的 C1 分流臂(`adapter/runStream.ts`)—— C1 那条当时自己写了
|
|
45
|
-
* `typeof sub.output === 'string' ? sub.output : undefined`,于是同一个 wire 形经不同腿进同一个
|
|
46
|
-
* 账本得到两种结果(块数组走 C1 恒空)。字符串化口跟着**它服务的那个位**走,两条腿一律 import
|
|
47
|
-
* 这一份:第二份实现在类型面就没有立足点了。
|
|
48
|
-
* 🔴 住这里的另一个硬理由:tail 腿的传递闭包里有 SDK/host(engineWireSdk 等),而 C1 所在的
|
|
49
|
-
* `runStream.ts` 有一道**可移植性闭包门**(零 Node 内建 / 零 react / 文件数棘轮 ≤7)——
|
|
50
|
-
* 从 tail 腿 import 会当场把整条 wire 依赖拖进内核。本 store 已在那 7 件之内且零 import,
|
|
51
|
-
* 所以搬到这里的代价是 0 个新闭包文件。
|
|
52
|
-
*
|
|
53
|
-
* 语义:`undefined`/`null` ⇒ `undefined`(诚实缺席,不铸空串);`string` ⇒ 原样直通;
|
|
54
|
-
* **§E1 的 `(TextContent|ImageContent)[]` 块数组 ⇒ 规范展平**(text 块贡献 `.text`、image 块贡献
|
|
55
|
-
* 中性占位 `[image]`);其余(server `redactDeep` 之后原型不保的对象形等)⇒ `JSON.stringify`,
|
|
56
|
-
* 序列化失败退 `String(v)`。
|
|
57
|
-
*
|
|
58
|
-
* 🔴 块数组这一支为什么不是 `JSON.stringify`(0.30.0 发包扫描修,2026-08-14):`output` 是**展示位**
|
|
59
|
-
* (投进 {@link planSubagentViewSlots} 的结果栏),而同一个 wire 形在主 lane 走的是
|
|
60
|
-
* `adapt/wireShapes.ts` 的 `flattenWireOutput`(工具卡体 / print 帧 / 面板 settle 三处)。修前这里
|
|
61
|
-
* 出的是 `[{"type":"text","text":"…"}]` 这种机器形 —— 同一份素材主 lane 渲人话、子代查看态渲转义
|
|
62
|
-
* JSON;带 image 块时整段 base64(events.d.ts 的 `totalChars` 注释逐字:image/document = base64 真实
|
|
63
|
-
* 体量)会被原样灌进内容账本并上屏(item 数有 200 上限、**单 item 体量无上限**)。
|
|
64
|
-
* 🔴 为什么这里还有第二份展平实现、而不是 import 那一份:本 store 是 `runStream.ts` 内核可移植闭包
|
|
65
|
-
* (`run-client-core-portability-test.mjs` 的 `MAX_CLOSURE_FILES`,只许降)里的一件,且本文件头注
|
|
66
|
-
* 三处以「dep-free / 零 import」为立身之本;从 `adapt/wireShapes.ts` 拉一条边会同时破这两条。
|
|
67
|
-
* ⇒ 两份实现由 **pure 门 ⑨b 段的等值断言**逐形对拍钉住(字符串 / 纯 text 块 / 含 image 块 / 混合 /
|
|
68
|
-
* 空数组 / 未知块形六形,`coerceOutput(x) === flattenWireOutput(x)` + 逐字面值),任一侧改语义当场红。
|
|
69
|
-
* 动本函数的块数组分支 ⇒ 同批看那条断言。
|
|
70
|
-
*/
|
|
71
1
|
export function coerceOutput(v) {
|
|
72
2
|
if (v === undefined || v === null)
|
|
73
3
|
return undefined;
|
|
74
4
|
if (typeof v === 'string')
|
|
75
5
|
return v;
|
|
76
|
-
// §E1 块数组 ⇒ 规范展平(与 adapt/wireShapes.ts 的 flattenWireOutput 逐字同语义;等值由 pure ⑨b 钉)
|
|
77
6
|
if (Array.isArray(v)) {
|
|
78
7
|
const parts = [];
|
|
79
8
|
for (const block of v) {
|
|
@@ -97,19 +26,11 @@ export function coerceOutput(v) {
|
|
|
97
26
|
const MAX_ITEMS_PER_TASK = 200;
|
|
98
27
|
const MAX_TASKS = 32;
|
|
99
28
|
const NOTIFY_COALESCE_MS = 250;
|
|
100
|
-
/** 不配时生效的两位(理由见 {@link SubagentContentStoreConfig} 顶注)。 */
|
|
101
29
|
export const SUBAGENT_CONTENT_STORE_DEFAULTS = Object.freeze({
|
|
102
30
|
maxBytesPerTask: 2 * 1024 * 1024,
|
|
103
31
|
maxBytesTotal: 16 * 1024 * 1024,
|
|
104
32
|
});
|
|
105
33
|
let storeConfig = { ...SUBAGENT_CONTENT_STORE_DEFAULTS };
|
|
106
|
-
/**
|
|
107
|
-
* 配字节预算(宿主启动时调一次;两位可分开配,只给一位时另一位保持现值)。
|
|
108
|
-
*
|
|
109
|
-
* 🔴 **fail-loud**:0 / 负数 / 非有限数 / 非整数一律 `throw` —— 一个被**静默忽略**的预算配置正是
|
|
110
|
-
* 这本账本要消灭的那类失败(宿主以为配上了,而账本还在按缺省无声地丢或不丢)。
|
|
111
|
-
* 🔴 拒绝是**原子**的:任何一位不合法就整只拒,既有配置一个字节不动(半套配置比不配更坏)。
|
|
112
|
-
*/
|
|
113
34
|
export function configureSubagentContentStore(next) {
|
|
114
35
|
const check = (name, v) => {
|
|
115
36
|
if (v === undefined)
|
|
@@ -121,10 +42,6 @@ export function configureSubagentContentStore(next) {
|
|
|
121
42
|
};
|
|
122
43
|
const perTask = check('maxBytesPerTask', next.maxBytesPerTask);
|
|
123
44
|
const total = check('maxBytesTotal', next.maxBytesTotal);
|
|
124
|
-
// 🔴 两位的**关系**也要验(车内异源复审逼出:两位各自合法、合起来自相矛盾)。每子代帽大于总帽时,
|
|
125
|
-
// 单条子代可以合法地涨到每子代帽 —— 而总帽淘汰**不动当期那条**(它正在写),于是那一条就整只
|
|
126
|
-
// 越过了总帽,总帽从此形同虚设。判的是**落值之后**的那一对,不是这次传进来的那一位:只配一位
|
|
127
|
-
// 时另一位保持现值,矛盾同样可能是这次配出来的。
|
|
128
45
|
const nextPerTask = perTask ?? storeConfig.maxBytesPerTask;
|
|
129
46
|
const nextTotal = total ?? storeConfig.maxBytesTotal;
|
|
130
47
|
if (nextPerTask > nextTotal) {
|
|
@@ -132,29 +49,17 @@ export function configureSubagentContentStore(next) {
|
|
|
132
49
|
`\`maxBytesTotal\` (${String(nextTotal)}) — a per-task budget above the total is unreachable by ` +
|
|
133
50
|
'construction, and the total stops bounding anything.');
|
|
134
51
|
}
|
|
135
|
-
// 两位都验完才落 —— 第二位不合法时第一位也不许生效。
|
|
136
52
|
storeConfig.maxBytesPerTask = nextPerTask;
|
|
137
53
|
storeConfig.maxBytesTotal = nextTotal;
|
|
138
54
|
}
|
|
139
|
-
/** 当前生效的两位(**快照**:改返回值不影响账本)。 */
|
|
140
55
|
export function subagentContentStoreConfig() {
|
|
141
56
|
return { ...storeConfig };
|
|
142
57
|
}
|
|
143
|
-
/** 测试钩:把预算复位到缺省。 */
|
|
144
58
|
export function __resetSubagentContentStoreConfigForTests() {
|
|
145
59
|
storeConfig = { ...SUBAGENT_CONTENT_STORE_DEFAULTS };
|
|
146
60
|
}
|
|
147
61
|
const tasks = new Map();
|
|
148
|
-
/** 整本账本当下保留的内容字节(逐次增删维护;总帽读它)。 */
|
|
149
62
|
let totalBytes = 0;
|
|
150
|
-
/**
|
|
151
|
-
* 一段文本的 **UTF-8 字节数**,不做编码分配。
|
|
152
|
-
*
|
|
153
|
-
* 🔴 为什么不用 `TextEncoder`:它每次都要**分配**一个字节数组,而这条路是逐 token 的热路径
|
|
154
|
-
* (实测 ~23 帧/s 的增量流)。算术形零分配、结果逐字节相同。
|
|
155
|
-
* 🔴 为什么不按 `str.length`(UTF-16 单元)算:账本里装的是引擎送来的文本,一段 CJK 的真实占用
|
|
156
|
-
* 是串长度的三倍 —— 按串长度算等于把预算对中文内容放宽两倍。
|
|
157
|
-
*/
|
|
158
63
|
function utf8Len(str) {
|
|
159
64
|
let n = 0;
|
|
160
65
|
for (let i = 0; i < str.length; i++) {
|
|
@@ -164,7 +69,6 @@ function utf8Len(str) {
|
|
|
164
69
|
else if (c < 0x800)
|
|
165
70
|
n += 2;
|
|
166
71
|
else if (c >= 0xd800 && c <= 0xdbff && i + 1 < str.length) {
|
|
167
|
-
// 合法代理对 = 一个 4 字节码位(高位后面不是低位时按 3 字节的孤代理项算,与编码器同)
|
|
168
72
|
const next = str.charCodeAt(i + 1);
|
|
169
73
|
if (next >= 0xdc00 && next <= 0xdfff) {
|
|
170
74
|
n += 4;
|
|
@@ -178,15 +82,6 @@ function utf8Len(str) {
|
|
|
178
82
|
}
|
|
179
83
|
return n;
|
|
180
84
|
}
|
|
181
|
-
/**
|
|
182
|
-
* 把 `chunk` 追加到 `prev` 之后,**字节账真正增加了多少**。
|
|
183
|
-
*
|
|
184
|
-
* 🔴 不能直接用 `utf8Len(chunk)`(车内异源复审逼出的真病):一对代理对被上游拆成**两帧**送来时,
|
|
185
|
-
* 前一帧尾部那枚高位代理项按孤代理项算 3 字节、后一帧首部那枚低位代理项也算 3 字节,合计 6;
|
|
186
|
-
* 而拼起来之后它是一个 4 字节码位。每拆一次就虚高 2 字节,而段闭合时是按**拼接后的串**扣账
|
|
187
|
-
* —— 加 6 扣 4,差额永久留在账上。长流里这个漂移会一路把总账推高,最后把还在用的账本淘汰掉。
|
|
188
|
-
* ⇒ 只在**拼接边界**上修正一次(O(1)),不重扫整条缓冲(缓冲能有 MB 级,而这是逐 token 的热路径)。
|
|
189
|
-
*/
|
|
190
85
|
function appendBytes(prev, chunk) {
|
|
191
86
|
const add = utf8Len(chunk);
|
|
192
87
|
if (prev.length === 0 || chunk.length === 0)
|
|
@@ -196,11 +91,6 @@ function appendBytes(prev, chunk) {
|
|
|
196
91
|
const joinsPair = lastPrev >= 0xd800 && lastPrev <= 0xdbff && firstNew >= 0xdc00 && firstNew <= 0xdfff;
|
|
197
92
|
return joinsPair ? add - 2 : add;
|
|
198
93
|
}
|
|
199
|
-
/**
|
|
200
|
-
* 工具参数(开集 `unknown`)的**规范序列化** —— 字节账({@link itemBytesOf})与「这一发到底改了
|
|
201
|
-
* 没有」({@link startContentKey})共用这**一口**,两处对同一份参数的看法因此不可能分叉。
|
|
202
|
-
* 不可序列化(循环引用等)⇒ 空串:它进不了展示面,也就占不了这本账的预算、也不算内容变化。
|
|
203
|
-
*/
|
|
204
94
|
function serializeToolInput(input) {
|
|
205
95
|
if (input === undefined)
|
|
206
96
|
return '';
|
|
@@ -208,11 +98,9 @@ function serializeToolInput(input) {
|
|
|
208
98
|
return JSON.stringify(input) ?? '';
|
|
209
99
|
}
|
|
210
100
|
catch {
|
|
211
|
-
// 循环引用/不可序列化 ⇒ 这一位按空串计(见本函数头注)
|
|
212
101
|
return '';
|
|
213
102
|
}
|
|
214
103
|
}
|
|
215
|
-
/** 一条 item 的字节账(`truncated` 是关于预算的元信息,自身不计)。 */
|
|
216
104
|
function itemBytesOf(it) {
|
|
217
105
|
switch (it.kind) {
|
|
218
106
|
case 'text':
|
|
@@ -220,13 +108,11 @@ function itemBytesOf(it) {
|
|
|
220
108
|
case 'echo':
|
|
221
109
|
return utf8Len(it.text);
|
|
222
110
|
case 'tool':
|
|
223
|
-
// 工具参数序列化一次算体量(每张卡只算一次,不在增量热路径上)。
|
|
224
111
|
return utf8Len(it.name) + utf8Len(it.output ?? '') + utf8Len(serializeToolInput(it.input));
|
|
225
112
|
case 'truncated':
|
|
226
113
|
return 0;
|
|
227
114
|
}
|
|
228
115
|
}
|
|
229
|
-
/** 从串**头部**裁掉至少 `want` 字节;返回剩下的尾巴与真正裁掉的字节数(不劈开合法代理对)。 */
|
|
230
116
|
function trimHeadBytes(str, want) {
|
|
231
117
|
let removed = 0;
|
|
232
118
|
let i = 0;
|
|
@@ -245,12 +131,10 @@ function trimHeadBytes(str, want) {
|
|
|
245
131
|
}
|
|
246
132
|
return { rest: str.slice(i), removed };
|
|
247
133
|
}
|
|
248
|
-
/** 字节账的唯一增减口(两本账同拍走,永不分叉)。 */
|
|
249
134
|
function addBytes(s, n) {
|
|
250
135
|
s.bytes += n;
|
|
251
136
|
totalBytes += n;
|
|
252
137
|
}
|
|
253
|
-
/** 记一次丢弃:更新/建立留痕记录(每条子代恒一条,恒排在最前)。 */
|
|
254
138
|
function noteDropped(s, dropped) {
|
|
255
139
|
if (dropped <= 0)
|
|
256
140
|
return;
|
|
@@ -265,18 +149,7 @@ function noteDropped(s, dropped) {
|
|
|
265
149
|
s.itemBytes.unshift(0);
|
|
266
150
|
pruneCardPhases(s);
|
|
267
151
|
}
|
|
268
|
-
/**
|
|
269
|
-
* items 被增删之后,把**卡已不在 items 上**的阶段账清掉。
|
|
270
|
-
*
|
|
271
|
-
* 🔴 这里此前还维护第二张表(`toolCallId` → 开着的卡下标)。删掉它是本批「按类修」的一部分:
|
|
272
|
-
* 一次工具调用的卡**在不在、在哪里**只有 `items` 说了算(本文件 {@link toolCardIndexOf} 头注
|
|
273
|
-
* 逐字写过这条),而下标表是同一件事的第二处真源 —— 它与 items 脱节的方式是可预期的
|
|
274
|
-
* (splice/unshift 之后下标整体漂移),而本批四条的病根正是「同一件事记了两本各自记的账」。
|
|
275
|
-
* 收口那一支改成每次按 `toolCallId` 现查(至多 200 条,一次线性扫),两处真源就只剩一处。
|
|
276
|
-
*/
|
|
277
152
|
function pruneCardPhases(s) {
|
|
278
|
-
// 🔴 只清**卡的**阶段账。段边界不在这里 —— 它压在缓冲上(TaskContentState.pendingCuts),卡被丢掉
|
|
279
|
-
// 时缓冲里那截正文一个字都没少,边界跟着卡消失就等于把「这里有过一次工具」这件事丢了。
|
|
280
153
|
const live = new Set();
|
|
281
154
|
for (const it of s.items) {
|
|
282
155
|
if (it.kind === 'tool')
|
|
@@ -287,7 +160,6 @@ function pruneCardPhases(s) {
|
|
|
287
160
|
s.toolPhase.delete(id);
|
|
288
161
|
}
|
|
289
162
|
}
|
|
290
|
-
/** 取(必要时新建)一张卡的阶段账 —— 卡与账同生:建卡/改卡的每一支都经这一口。 */
|
|
291
163
|
function phaseSlot(s, id) {
|
|
292
164
|
const cur = s.toolPhase.get(id);
|
|
293
165
|
if (cur !== undefined)
|
|
@@ -296,24 +168,11 @@ function phaseSlot(s, id) {
|
|
|
296
168
|
s.toolPhase.set(id, next);
|
|
297
169
|
return next;
|
|
298
170
|
}
|
|
299
|
-
/**
|
|
300
|
-
* 每子代帽:超出就**从最早的内容开始丢**,丢不动了再从缓冲头部裁。
|
|
301
|
-
*
|
|
302
|
-
* 🔴 顺序是「先 item 后缓冲」而不是反过来:缓冲是**活体尾巴**(用户正在看的那一段),item 是更早
|
|
303
|
-
* 的历史。反过来做会把用户眼前正在流的字丢掉,而屏上更早那一段完好无损 —— 那是错的一头。
|
|
304
|
-
* 🔴 留痕记录自身不参与丢弃(它是关于预算的元信息;丢掉它等于把「有东西被丢了」这件事也丢了)。
|
|
305
|
-
*/
|
|
306
171
|
function enforceTaskCap(s) {
|
|
307
172
|
const cap = storeConfig.maxBytesPerTask;
|
|
308
173
|
if (s.bytes <= cap)
|
|
309
174
|
return false;
|
|
310
175
|
let dropped = 0;
|
|
311
|
-
// 「有没有卡离开 items」按**条数**量,不拿 `dropped`(字节)代答。
|
|
312
|
-
// 📋 如实(变异自证):这一改**今天零行为差异** —— 每子代帽恒 ≥ 1(configureSubagentContentStore
|
|
313
|
-
// 的 fail-loud 挡住 0 与负数),而循环只在「还装不下」时继续,所以有卡离开就一定有字节离开,
|
|
314
|
-
// 两个条件当下等价;门对它没有判别力是**正常**的,别拿门当它的证明。这样写的理由是:
|
|
315
|
-
// 「卡走了、它的阶段账同批清」是一条**行为面**不变量(陈旧的 `end` 会把重建那一轮的收口整帧
|
|
316
|
-
// 拦下 —— 卡建回来却永远显示在飞,G9 那一格量的就是它),它不该靠一条关于**字节**的推论维持。
|
|
317
176
|
let removedItems = 0;
|
|
318
177
|
const first = () => (s.items[0]?.kind === 'truncated' ? 1 : 0);
|
|
319
178
|
while (s.bytes > cap && s.items.length > first()) {
|
|
@@ -325,9 +184,6 @@ function enforceTaskCap(s) {
|
|
|
325
184
|
dropped += b;
|
|
326
185
|
removedItems += 1;
|
|
327
186
|
}
|
|
328
|
-
// 🔴 裁的是缓冲**头部** ⇒ 压在这条缓冲上的段边界同拍左移(边界是位置,前缀短了它就得跟着短;
|
|
329
|
-
// 不移的话那一刀会切在错的字上)。段边界活得比卡长,所以这一步是真会走到的:上面那个循环
|
|
330
|
-
// 可以只丢掉卡而缓冲一个字没少,反过来这里裁了缓冲而边界还压着。
|
|
331
187
|
if (s.bytes > cap) {
|
|
332
188
|
const cut = trimHeadBytes(s.thinkBuf, s.bytes - cap);
|
|
333
189
|
shiftPendingCuts(s, 'think', s.thinkBuf.length - cut.rest.length);
|
|
@@ -350,10 +206,6 @@ function enforceTaskCap(s) {
|
|
|
350
206
|
}
|
|
351
207
|
return removedItems > 0;
|
|
352
208
|
}
|
|
353
|
-
/**
|
|
354
|
-
* 总帽:整条**最久未用**的子代账本被清掉(`tasks` 的迭代序就是 LRU 序,活跃项每次访问都会重插)。
|
|
355
|
-
* 🔴 刻意不动**当期**那条(它正在写),也刻意不做「每条各裁一点」——半截的转录比一条整齐的缺席更难读。
|
|
356
|
-
*/
|
|
357
209
|
function enforceTotalCap(currentKey) {
|
|
358
210
|
while (totalBytes > storeConfig.maxBytesTotal) {
|
|
359
211
|
let victim;
|
|
@@ -364,11 +216,10 @@ function enforceTotalCap(currentKey) {
|
|
|
364
216
|
}
|
|
365
217
|
}
|
|
366
218
|
if (victim === undefined)
|
|
367
|
-
return;
|
|
219
|
+
return;
|
|
368
220
|
dropTask(victim);
|
|
369
221
|
}
|
|
370
222
|
}
|
|
371
|
-
/** 删一条子代账本并把它的字节从总账里扣掉(唯一删除口)。 */
|
|
372
223
|
function dropTask(key) {
|
|
373
224
|
const s = tasks.get(key);
|
|
374
225
|
if (!s)
|
|
@@ -376,17 +227,11 @@ function dropTask(key) {
|
|
|
376
227
|
totalBytes -= s.bytes;
|
|
377
228
|
tasks.delete(key);
|
|
378
229
|
}
|
|
379
|
-
// parentToolCallId ↔ engine taskId aliasing: CONTENT events carry ONLY {eventId, parentToolCallId}
|
|
380
|
-
// (EventIdentity — no taskId on the wire), while the panel rows / 查看态 lookups key by the ENGINE task id
|
|
381
|
-
// (task_progress carries BOTH). Content arriving before the first tick parks under the parent key and is
|
|
382
|
-
// RE-KEYED when the alias registers.
|
|
383
230
|
const parentToTask = new Map();
|
|
384
231
|
const taskToParent = new Map();
|
|
385
232
|
let notifyListener = null;
|
|
386
233
|
const pendingNotify = new Set();
|
|
387
234
|
let notifyTimer = null;
|
|
388
|
-
/** LRU touch:Map 迭代序=插入序,容量淘汰取「最老」——不刷新的话是纯 FIFO,长会话里第 33 个
|
|
389
|
-
* 子代会把【仍在流式/正被查看】的最早行挤掉(扩散面复审 F11)。访问即 re-insert,活跃项恒新。 */
|
|
390
235
|
function touchLru(map, key) {
|
|
391
236
|
const v = map.get(key);
|
|
392
237
|
if (v !== undefined) {
|
|
@@ -397,11 +242,10 @@ function touchLru(map, key) {
|
|
|
397
242
|
function stateFor(taskId, parentToolCallId) {
|
|
398
243
|
let s = tasks.get(taskId);
|
|
399
244
|
if (!s) {
|
|
400
|
-
// Bound total task footprint: evict the LEAST-RECENTLY-USED task's log (reads/writes touch below).
|
|
401
245
|
if (tasks.size >= MAX_TASKS) {
|
|
402
246
|
const oldest = tasks.keys().next().value;
|
|
403
247
|
if (oldest !== undefined)
|
|
404
|
-
dropTask(oldest);
|
|
248
|
+
dropTask(oldest);
|
|
405
249
|
}
|
|
406
250
|
s = {
|
|
407
251
|
items: [],
|
|
@@ -436,30 +280,14 @@ function pushItem(s, item) {
|
|
|
436
280
|
s.items.splice(0, cut);
|
|
437
281
|
s.itemBytes.splice(0, cut);
|
|
438
282
|
addBytes(s, -freed);
|
|
439
|
-
// 条数帽丢掉的同样是**内容**,同样要留痕(此前这条路是无声的)。
|
|
440
283
|
noteDropped(s, freed);
|
|
441
|
-
// 卡被裁掉了 ⇒ 它的阶段账同批作废(卡没了,那本账就没有指涉对象)
|
|
442
284
|
pruneCardPhases(s);
|
|
443
285
|
}
|
|
444
286
|
}
|
|
445
|
-
/**
|
|
446
|
-
* 段闭合:把两条缓冲收成 item(段边界 = 工具起头,或这一轮 settle)。
|
|
447
|
-
*
|
|
448
|
-
* 🔴 **在所有还没兑现的段边界处一起切**({@link TaskContentState.pendingCuts};收口先于开始帧
|
|
449
|
-
* 到达时记下的位置)。
|
|
450
|
-
* 为什么不是「只切触发这一次闭合的那张卡的那一条」:边界记的是**缓冲上的位置**,不是某张卡的
|
|
451
|
-
* 私产 —— 一条缓冲上可以同时压着好几条(两张 orphan 卡交错),而闭合一旦发生缓冲就整只清空,
|
|
452
|
-
* 没被兑现的那些位置从此无处可切。异源复审 R5 的两个反例都是这一形:①兑现者是 `settle` 或
|
|
453
|
-
* 别的工具的 `tool_start`(它们此前根本不看这些位置)⇒ 两段黏成一段;②两张 orphan 交错时第一发
|
|
454
|
-
* 迟到的 start 只兑现自己那条、把另一条丢掉 ⇒ 后两段黏成一段。黏成一段的代价是整轮重放时那一段
|
|
455
|
-
* 的内容闸不命中,历史与缓冲各留一份。
|
|
456
|
-
*/
|
|
457
287
|
function flushBuffers(s) {
|
|
458
288
|
const asc = (a, b) => a - b;
|
|
459
289
|
const textCuts = [...new Set(s.pendingCuts.map((c) => c.text))].sort(asc);
|
|
460
290
|
const thinkCuts = [...new Set(s.pendingCuts.map((c) => c.think))].sort(asc);
|
|
461
|
-
// 🔴 缓冲的字节此前已经计过账;搬进 item 时先扣掉缓冲那一份,再由 pushItem 计 item 那一份
|
|
462
|
-
// (纯空白段被丢弃时同样要扣 —— 少扣一次,总账就会一路虚高到把别人的账本淘汰掉)。
|
|
463
291
|
addBytes(s, -utf8Len(s.thinkBuf));
|
|
464
292
|
const think = s.thinkBuf;
|
|
465
293
|
s.thinkBuf = '';
|
|
@@ -468,15 +296,10 @@ function flushBuffers(s) {
|
|
|
468
296
|
const text = s.textBuf;
|
|
469
297
|
s.textBuf = '';
|
|
470
298
|
closeSegments(s, 'text', text, textCuts);
|
|
471
|
-
// 缓冲已空 ⇒ 压在它上面的位置一条不剩地兑现完了(指涉对象没有了)
|
|
472
299
|
s.pendingCuts.length = 0;
|
|
473
|
-
// 🔴 段闭合发生过了 ⇒ 在场每张卡都不再欠一次段闭合。这一位问的是「自这张卡出现以来发生过段闭合
|
|
474
|
-
// 没有」而不是「它自己切过没有」:兑现的可以是别人,而那一发迟到的 `tool_start` 若还当自己欠着,
|
|
475
|
-
// 就会在别人切完之后**再切一刀** —— 切掉的是当前那条与它无关的活体尾段。
|
|
476
300
|
for (const p of s.toolPhase.values())
|
|
477
301
|
p.boundary = true;
|
|
478
302
|
}
|
|
479
|
-
/** 一条缓冲收成 item:在每一条**落在中间**的边界上切一刀(空白段照旧丢弃、不铸空 item)。 */
|
|
480
303
|
function closeSegments(s, kind, buf, cuts) {
|
|
481
304
|
const push = (part) => {
|
|
482
305
|
if (!part.trim())
|
|
@@ -494,14 +317,6 @@ function closeSegments(s, kind, buf, cuts) {
|
|
|
494
317
|
}
|
|
495
318
|
push(buf.slice(from));
|
|
496
319
|
}
|
|
497
|
-
/**
|
|
498
|
-
* 把段边界夹进 `[0, buf.length]`,并且**绝不劈开合法代理对**。
|
|
499
|
-
*
|
|
500
|
-
* 🔴 为什么会劈到:边界是「记下它那一刻的缓冲长度」,而上游真的会把一对代理对拆成**两帧**送
|
|
501
|
-
* (本文件 {@link appendBytes} 头注就是为这一形写的)—— 记边界那一刻缓冲尾部可能正停在高位
|
|
502
|
-
* 代理项上。从那里切会切出两个孤代理项:屏上两段各挂半个字符,两段的字节账合计也比整条多 2。
|
|
503
|
-
* ⇒ 撞上就把边界**退**到那一对之前(宁可把这半个字符归给后一段)。
|
|
504
|
-
*/
|
|
505
320
|
function safeCut(buf, at) {
|
|
506
321
|
let n = at < 0 ? 0 : at > buf.length ? buf.length : at;
|
|
507
322
|
if (n > 0 && n < buf.length) {
|
|
@@ -512,9 +327,7 @@ function safeCut(buf, at) {
|
|
|
512
327
|
}
|
|
513
328
|
return n;
|
|
514
329
|
}
|
|
515
|
-
/** 两次段闭合之间最多记多少条段边界(见 {@link TaskContentState.pendingCuts} 的有界那一笔)。 */
|
|
516
330
|
const MAX_PENDING_CUTS = 64;
|
|
517
|
-
/** 有界 FIFO 记号(两本小账共用;只用来做幂等判定,不参与预算)。 */
|
|
518
331
|
const MAX_REPLAY_KEYS = 64;
|
|
519
332
|
function remember(set, key) {
|
|
520
333
|
if (set.has(key))
|
|
@@ -544,77 +357,32 @@ function scheduleNotify(taskId) {
|
|
|
544
357
|
notifyListener(id);
|
|
545
358
|
}
|
|
546
359
|
catch {
|
|
547
|
-
// a consumer render error must never tear down the stream drain
|
|
548
360
|
}
|
|
549
361
|
}
|
|
550
362
|
}, NOTIFY_COALESCE_MS);
|
|
551
363
|
}
|
|
552
|
-
/**
|
|
553
|
-
* **内容归账那一半**(`parentToolCallId → taskId`,多对一):把停在 parent 键下的内容迁到 canonical
|
|
554
|
-
* task 键,并让后续内容帧的 `canonicalKey` 解析到 task。**不写** `taskToParent`。
|
|
555
|
-
*
|
|
556
|
-
* 为什么要与 {@link registerSubagentAlias} 分开(#158② 复审收货,2026-08-12):这张表其实承载
|
|
557
|
-
* **两件不同的事**,方向与基数都不同 ——
|
|
558
|
-
* · `parentToTask`:内容归账,**多对一**(同一个子代可能被多次调用宣告:SendMessage 复活会为
|
|
559
|
-
* 同一 taskId 再发一条 `async_launched` 回执,骑的是复活那次调用的 toolCallId);旧键不删,
|
|
560
|
-
* 所以两次宣告的内容都归到同一个 taskId,加一条只会更全。
|
|
561
|
-
* · `taskToParent`:**单值**,而它的读口 {@link parentToolCallIdOf} 是**对外**的 C2 steer 投递
|
|
562
|
-
* 目标(service 按「委派工具调用的 id」路由子代 steer)。这一位的**权威来源是 wire 的内容帧**
|
|
563
|
-
* (`publishSubagentContentEvent` 记在内容态上的 `parentToolCallId`,读口对它做兜底),
|
|
564
|
-
* 一条启动回执**不足以**改写它:复活回执带的是复活调用,拿它盖掉真正的委派调用 = 用户插话
|
|
565
|
-
* 投到错的目标,而「复活后该按哪个 target 投」要 server 语义作证,本包无从判定。
|
|
566
|
-
* ⇒ 回执类喂点只用本函数(只加内容归账,零 steer 面影响);`task_progress` tick 那条喂点两键都带、
|
|
567
|
-
* 且是引擎对「这条 tick 属谁」的当期陈述,继续用 {@link registerSubagentAlias}。
|
|
568
|
-
*/
|
|
569
364
|
export function registerSubagentContentAlias(parentToolCallId, taskId) {
|
|
570
365
|
aliasContentKey(parentToolCallId, taskId);
|
|
571
366
|
}
|
|
572
|
-
/** 两个登记口共用的内容归账实现(单源)。返回 false = 退化对被拒(自指/空值),调用方别再往下写。 */
|
|
573
367
|
function aliasContentKey(parentToolCallId, taskId) {
|
|
574
368
|
if (!parentToolCallId || !taskId || parentToolCallId === taskId)
|
|
575
369
|
return false;
|
|
576
370
|
parentToTask.set(parentToolCallId, taskId);
|
|
577
|
-
// a notify queued under the parent key before the alias landed must fire under the canonical key
|
|
578
371
|
if (pendingNotify.delete(parentToolCallId))
|
|
579
372
|
pendingNotify.add(taskId);
|
|
580
373
|
const parked = tasks.get(parentToolCallId);
|
|
581
374
|
if (parked && !tasks.has(taskId)) {
|
|
582
|
-
// 🔴 这是**换键**不是删除:同一本账换个键继续用,内容一个字节都没走 ⇒ 这里**不许**走
|
|
583
|
-
// {@link dropTask}(那一口会把它的字节从总账里扣掉,而字节还在)。扣了之后总账会一路虚低,
|
|
584
|
-
// 总帽从此形同虚设。删除只有三个口:clearSubagentContent / 条数淘汰 / 总帽淘汰。
|
|
585
375
|
tasks.delete(parentToolCallId);
|
|
586
376
|
tasks.set(taskId, parked);
|
|
587
377
|
scheduleNotify(taskId);
|
|
588
378
|
}
|
|
589
379
|
return true;
|
|
590
380
|
}
|
|
591
|
-
/** Register the parentToolCallId ↔ engine-taskId pair (from task_progress, which carries both).
|
|
592
|
-
* Content parked under the parent key migrates to the canonical task key.
|
|
593
|
-
* ⚠️ 这一口**同时**写单值的 `taskToParent`(= 对外 C2 steer 目标,见
|
|
594
|
-
* {@link registerSubagentContentAlias} 头注的两件事分家说明)。只想补内容归账的喂点用那一口。 */
|
|
595
381
|
export function registerSubagentAlias(parentToolCallId, taskId) {
|
|
596
382
|
if (!aliasContentKey(parentToolCallId, taskId))
|
|
597
383
|
return;
|
|
598
384
|
taskToParent.set(taskId, parentToolCallId);
|
|
599
385
|
}
|
|
600
|
-
/**
|
|
601
|
-
* 这一条**聚合帧**是不是「已经收下过的那一条」(durable 重放的两道闸;命中 ⇒ 整帧丢弃)。
|
|
602
|
-
*
|
|
603
|
-
* 🔴 工具两臂**同一条判据形、不同的身份**:它们不进这一口(见 publish 的工具两臂),因为
|
|
604
|
-
* `toolCallId` 就是那次调用的身份、账本就是 `items` 自己 —— 比一本有界 FIFO 强,也不占它的额度。
|
|
605
|
-
*
|
|
606
|
-
* 为什么光靠缓冲前缀吸收不够(异源复审逼出的真病):吸收规则只认**当前缓冲**那一段。一条
|
|
607
|
-
* 跨过工具边界的 turn,早先那些段早就段闭合成 item、缓冲已空 —— 重放时它们的聚合帧再来一遍,
|
|
608
|
-
* `''` 是任何串的前缀,于是整段被当成新内容塞回缓冲,而且塞在**后面那段**之后(顺序也是错的)。
|
|
609
|
-
*
|
|
610
|
-
* 两道闸,顺序刻意如此:
|
|
611
|
-
* ① **事件身份**(`eventId`)—— 精确、无损:同一条账本帧重放多少次都只收一次。
|
|
612
|
-
* ② **整段内容**(`recordedSegments`)—— 兜底:上游没给身份、或本进程这条流是重连后新起的
|
|
613
|
-
* (身份没在这本账上留过),内容判据仍拦得住「这一段已经上过屏」。
|
|
614
|
-
* 📋 **如实留白**:闸 ② 的代价是——一条 turn 里出现**两段逐字节相同**的正文时,durable 腿上只会
|
|
615
|
-
* 留下一段。取舍是明写的:每次重连都把整轮正文再渲一遍是**必然**发生的用户可见损坏,而
|
|
616
|
-
* 「同一条 turn 里说了两遍一模一样的话」是罕见形,且活体腿上两段都在。
|
|
617
|
-
*/
|
|
618
386
|
function replaySeen(s, ev) {
|
|
619
387
|
const id = ev.eventId;
|
|
620
388
|
if (typeof id === 'string' && id.length > 0) {
|
|
@@ -627,13 +395,6 @@ function replaySeen(s, ev) {
|
|
|
627
395
|
return true;
|
|
628
396
|
return false;
|
|
629
397
|
}
|
|
630
|
-
/**
|
|
631
|
-
* 这一发工具帧比卡上现在承载的那一阶段**旧或一样**吗(旧 ⇒ 整帧丢弃)。
|
|
632
|
-
*
|
|
633
|
-
* 判据是 `eventId` 的字典序 —— 前提是它**时间可排序**(core 铸的 uuidv7,sdk `EventIdentity` 逐字)。
|
|
634
|
-
* 两侧任一缺席 ⇒ 判不出新旧,答 `false`(退回「后到的落账」:重放送的是同一份值,落账是空操作)。
|
|
635
|
-
* 逐字相等 ⇒ 同一条帧又来一遍 ⇒ 同样答 `true`(整帧丢弃,一次也不重复落账)。
|
|
636
|
-
*/
|
|
637
398
|
function notNewer(eventId, mark) {
|
|
638
399
|
if (typeof eventId !== 'string' || eventId.length === 0)
|
|
639
400
|
return false;
|
|
@@ -641,23 +402,9 @@ function notNewer(eventId, mark) {
|
|
|
641
402
|
return false;
|
|
642
403
|
return eventId <= mark;
|
|
643
404
|
}
|
|
644
|
-
/** 读一张卡的阶段账(没有 ⇒ 这张卡还没落过任何一半的帧)。 */
|
|
645
405
|
function phaseOf(s, id) {
|
|
646
406
|
return id === undefined ? undefined : s.toolPhase.get(id);
|
|
647
407
|
}
|
|
648
|
-
/**
|
|
649
|
-
* 内容落账之后给这张卡盖章:**水位与卡上现在这份内容同源**。三形:
|
|
650
|
-
* · 身份在场 ⇒ 抬到这一发;
|
|
651
|
-
* · 身份缺席**且这一发真的改了卡上那一半的内容** ⇒ 把这一半**清掉**。此前缺席时一律「不抬」,
|
|
652
|
-
* 于是无身份的那一发把内容换掉了、水位还停在**换掉之前**那一阶段上 —— 随后正确的那一发原样
|
|
653
|
-
* 重放被自己的水位 `notNewer` 整帧拦掉,卡**永久**停在旧内容上;
|
|
654
|
-
* · 身份缺席**而内容一个字节没变**(重放送来的是同一份值)⇒ **原样保留**。水位仍然如实描述卡上
|
|
655
|
-
* 这份内容,清掉它等于顺手把「旧一阶段不许盖新一阶段」那道闸一起卸了 —— 异源复审 R5 的反例:
|
|
656
|
-
* 一发无身份的**同值**重放之后,旧一阶段的 start 就能把改后参数盖回原参,而更新的结果还留在
|
|
657
|
-
* 卡上,屏上成了「这个参数产生了那个结果」。喂点真的会混来源(tail 腿的工具帧不带 `eventId`)。
|
|
658
|
-
*
|
|
659
|
-
* 一句话:**这一半的水位,永远描述卡上这一半现在这份内容**——内容没动它就没理由动。
|
|
660
|
-
*/
|
|
661
408
|
function stampPhase(s, id, half, eventId, changed) {
|
|
662
409
|
if (id === undefined)
|
|
663
410
|
return;
|
|
@@ -667,21 +414,17 @@ function stampPhase(s, id, half, eventId, changed) {
|
|
|
667
414
|
else if (changed)
|
|
668
415
|
delete p[half];
|
|
669
416
|
}
|
|
670
|
-
/** 卡上「开始那一半」的内容指纹(名 + 参数的规范序列化)—— 只用来回答「这一发真的改了吗」。 */
|
|
671
417
|
function startContentKey(it) {
|
|
672
418
|
return `${it.name}\u0000${serializeToolInput(it.input)}`;
|
|
673
419
|
}
|
|
674
|
-
/** 这张卡不欠段闭合了(建卡那一支刚刚无条件闭合过一次)。 */
|
|
675
420
|
function markBoundary(s, id) {
|
|
676
421
|
phaseSlot(s, id).boundary = true;
|
|
677
422
|
}
|
|
678
|
-
/** 收口先到 ⇒ 把**此刻**两条缓冲的长度记成这条缓冲上的一个段边界(见 TaskContentState.pendingCuts)。 */
|
|
679
423
|
function recordCut(s) {
|
|
680
424
|
if (s.pendingCuts.length >= MAX_PENDING_CUTS)
|
|
681
425
|
return;
|
|
682
426
|
s.pendingCuts.push({ text: s.textBuf.length, think: s.thinkBuf.length });
|
|
683
427
|
}
|
|
684
|
-
/** 缓冲头部被裁掉 `removed` 个 UTF-16 单元 ⇒ 压在这条缓冲上的段边界一律左移(夹到 0)。 */
|
|
685
428
|
function shiftPendingCuts(s, half, removed) {
|
|
686
429
|
if (removed <= 0)
|
|
687
430
|
return;
|
|
@@ -690,28 +433,9 @@ function shiftPendingCuts(s, half, removed) {
|
|
|
690
433
|
c[half] = next > 0 ? next : 0;
|
|
691
434
|
}
|
|
692
435
|
}
|
|
693
|
-
/**
|
|
694
|
-
* 这一发**收口**比卡上现在承载的那一阶段旧吗(旧 ⇒ 整帧丢弃)。
|
|
695
|
-
*
|
|
696
|
-
* 🔴 **两半都要问**,这是 0.62.2 修的第二条:只问 `end` 的话,两条独立的流交错时(恢复阶段的
|
|
697
|
-
* `start` 已经到、旧一阶段的 `end` 才姗姗来迟)旧收口会落到新阶段那张开着的卡上 —— 新参数
|
|
698
|
-
* 配上旧阶段的失败结果,新流若中断这个错误状态就一直留在屏上。更新的 `start` 已经**建立了
|
|
699
|
-
* 新的一阶段**,旧收口不属于它;「避免永远在飞」不构成接受一份已知过期结果的理由(那张卡
|
|
700
|
-
* 确实还在飞,显示在飞是**如实**的)。
|
|
701
|
-
*/
|
|
702
436
|
function staleEnd(phase, eventId) {
|
|
703
437
|
return notNewer(eventId, phase?.start) || notNewer(eventId, phase?.end);
|
|
704
438
|
}
|
|
705
|
-
/**
|
|
706
|
-
* 这条 `toolCallId` 在本账本里已经有卡了吗(有 ⇒ 返回下标,没有 ⇒ `-1`)。
|
|
707
|
-
*
|
|
708
|
-
* 🔴 为什么账本就是 `items` 自己、不另铸第三本小账:一次工具调用的卡**在不在屏上**只有 `items`
|
|
709
|
-
* 说了算。另开一本记号账就会有两处真源,而它们脱节的方式是可预期的 —— 条数帽/字节帽把卡丢掉
|
|
710
|
-
* 之后记号还在,重放于是被永久挡住,屏上那一格从此空着且无人知道。这里逐条问 `items`,
|
|
711
|
-
* 答案与屏上看到的恒等(上限 200 条,每帧一次线性扫,量级可忽略)。
|
|
712
|
-
* 🔴 从尾往前找:同一 id 的卡至多一张(本闸的作用就是维持这条),从尾找只是让常见形(刚落的那张)
|
|
713
|
-
* 第一下命中。
|
|
714
|
-
*/
|
|
715
439
|
function toolCardIndexOf(s, id) {
|
|
716
440
|
for (let i = s.items.length - 1; i >= 0; i--) {
|
|
717
441
|
const it = s.items[i];
|
|
@@ -720,17 +444,6 @@ function toolCardIndexOf(s, id) {
|
|
|
720
444
|
}
|
|
721
445
|
return -1;
|
|
722
446
|
}
|
|
723
|
-
/**
|
|
724
|
-
* 聚合帧对当前缓冲的**吸收判定**(两个聚合臂共用一份;`null` = 这一帧整只丢弃)。
|
|
725
|
-
*
|
|
726
|
-
* 三形,顺序刻意如此:
|
|
727
|
-
* ① 缓冲是全文的**前缀**(缓冲为空时同样成立)⇒ 换成全文 —— 活体已流过的那半段被这一份接管,
|
|
728
|
-
* 同一条帧重复送达因此**幂等**(全文以自己为前缀)。
|
|
729
|
-
* ② 缓冲是全文被**预算裁过**的尾巴(全文以缓冲结尾)⇒ **丢弃这一帧**。没有这一条,一段已经被
|
|
730
|
-
* 裁掉头部的正文,每重放一次就会被整只追加回来:内容重复、顺序错乱,而且截尾统计会一路虚涨
|
|
731
|
-
* (裁掉的字节被反复重算)——「丢了多少」那个数会变成一句假话。
|
|
732
|
-
* ③ 两者都不是 ⇒ 真分岔,退回**追加**、两段都留下(少渲一段比替引擎判定「哪一段才算数」更坏)。
|
|
733
|
-
*/
|
|
734
447
|
function absorbAggregate(buf, agg) {
|
|
735
448
|
if (agg.startsWith(buf))
|
|
736
449
|
return agg;
|
|
@@ -739,7 +452,6 @@ function absorbAggregate(buf, agg) {
|
|
|
739
452
|
return buf + agg;
|
|
740
453
|
}
|
|
741
454
|
function canonicalKey(ev) {
|
|
742
|
-
// the wire's content events carry no taskId — runStream passes parentToolCallId in both slots
|
|
743
455
|
if (ev.taskId !== ev.parentToolCallId)
|
|
744
456
|
return ev.taskId;
|
|
745
457
|
return parentToTask.get(ev.parentToolCallId) ?? ev.parentToolCallId;
|
|
@@ -750,7 +462,6 @@ export function publishSubagentContentEvent(ev) {
|
|
|
750
462
|
switch (ev.type) {
|
|
751
463
|
case 'text_delta':
|
|
752
464
|
if (typeof ev.delta === 'string') {
|
|
753
|
-
// 🔴 增量按**拼接边界**记账(见 appendBytes:代理对被拆成两帧时按 chunk 单算会永久虚高)。
|
|
754
465
|
addBytes(s, appendBytes(s.textBuf, ev.delta));
|
|
755
466
|
s.textBuf += ev.delta;
|
|
756
467
|
}
|
|
@@ -761,20 +472,10 @@ export function publishSubagentContentEvent(ev) {
|
|
|
761
472
|
s.thinkBuf += ev.delta;
|
|
762
473
|
}
|
|
763
474
|
break;
|
|
764
|
-
// ── 聚合两臂(durable 重放腿:一段的权威全文)──────────────────────────────────────────
|
|
765
|
-
// 🔴 **吸收**而不是追加:活体已经流过的那一段是这一段全文的**前缀**,追加会让同一段内容
|
|
766
|
-
// 上屏两遍(缝前的事件序号去重管不到这一形——聚合帧与那串增量帧是不同的事件身份)。
|
|
767
|
-
// · 全文以当前缓冲为前缀 ⇒ 缓冲整体换成全文(活体流过 0 字节时同样成立,`''` 是任何串的前缀);
|
|
768
|
-
// 同一条聚合帧重复送达时因此是**幂等**的(全文以自己为前缀)。
|
|
769
|
-
// · 前缀对不上(理论上不该发生:同段的两次不同陈述)⇒ 退回**追加**的老语义并把两段都留下,
|
|
770
|
-
// 少渲一段比替引擎判定「哪一段才算数」更诚实。
|
|
771
|
-
// 🔴 不在这里段闭合:段边界仍由 tool_start / settle 给,与活体腿逐字同一条 —— 在这里 flush
|
|
772
|
-
// 会让「同一段的第二次重放」落成第二个 item(缓冲已空,吸收规则就没有前缀可比了)。
|
|
773
475
|
case 'text':
|
|
774
476
|
if (typeof ev.text === 'string' && ev.text.length > 0 && !replaySeen(s, ev)) {
|
|
775
477
|
const next = absorbAggregate(s.textBuf, ev.text);
|
|
776
478
|
if (next !== null) {
|
|
777
|
-
// 吸收/追加两形都可能改变缓冲长度 ⇒ 按**差额**记账(不是按帧长度加)。
|
|
778
479
|
const before = utf8Len(s.textBuf);
|
|
779
480
|
s.textBuf = next;
|
|
780
481
|
addBytes(s, utf8Len(s.textBuf) - before);
|
|
@@ -791,44 +492,16 @@ export function publishSubagentContentEvent(ev) {
|
|
|
791
492
|
}
|
|
792
493
|
}
|
|
793
494
|
break;
|
|
794
|
-
// ── 工具两臂(活体与 durable 重放送的是同一形)────────────────────────────────────────
|
|
795
|
-
// 🔴 与聚合两臂**同一条判据形**(先问身份、再问账本),但身份与账本都换成工具自己的那一套:
|
|
796
|
-
// 身份 = `toolCallId`(wire 给的那次调用的 id),账本 = `items` 自己(见 toolCardIndexOf)。
|
|
797
|
-
// 刻意**不写** `seenAggregateIds`:那本记号账是有界 FIFO,额度按「聚合帧只在重放腿上来」定的
|
|
798
|
-
// (活体走 `*_delta`,一个字都不写);工具帧**活体也来**,跟着写会把还在屏上的散文身份整窗
|
|
799
|
-
// 挤掉 —— 修一个臂的重放去重、弄坏另一个臂的,不是修。
|
|
800
|
-
// 🔴 收口那一支**不按「卡开着没开着」分岔**(0.62.2 合并):卡在不在只按 `toolCallId` 现查
|
|
801
|
-
// `items`,在账就过同一道闸、走同一条落账。此前开着那一支另有一张下标表、且无条件落结果
|
|
802
|
-
// (理由写的是「在飞的卡被拦掉收口就永远在飞」)—— 那条理由在**旧一阶段的收口迟到**这一形上
|
|
803
|
-
// 是错的:更新的 `start` 已经建立了新的一阶段,那张卡确实还在跑,显示在飞是**如实**的,而
|
|
804
|
-
// 把旧阶段的失败结果配到新参数上并关掉卡才是用户看得见的损坏。
|
|
805
495
|
case 'tool_start': {
|
|
806
496
|
const startId = ev.toolCallId;
|
|
807
497
|
const seen = startId !== undefined && toolCardIndexOf(s, startId) >= 0;
|
|
808
498
|
const known = phaseOf(s, startId);
|
|
809
499
|
if (seen && notNewer(ev.eventId, known?.start))
|
|
810
500
|
break;
|
|
811
|
-
// 段闭合可能触发条数裁剪(pushItem 从头 splice + unshift 留痕)⇒ 裁剪**前**取的下标与引用一律
|
|
812
|
-
// 作废:flush 之后按 toolCallId **重新**找卡。找不到 = 这张卡刚被裁掉 ⇒ 落到下面「新的一次」
|
|
813
|
-
// 建卡那一支(§26b 留白①),绝不对着已删的引用改名补参、也绝不把差额记到顶上那条留痕上。
|
|
814
|
-
// 这张卡还欠一次段闭合 ⇒ 现在补做(段闭合会在**所有**还没兑现的段边界上一起切,并把在场
|
|
815
|
-
// 每张卡的这一位置上 —— 兑现者不限于「欠着的那一张」)。
|
|
816
501
|
if (seen && known?.boundary !== true)
|
|
817
502
|
flushBuffers(s);
|
|
818
503
|
const at = startId !== undefined ? toolCardIndexOf(s, startId) : -1;
|
|
819
504
|
if (at >= 0) {
|
|
820
|
-
// 这次调用的卡已在账 ⇒ **不铸第二张**。剩下的两件按这张卡的**阶段账**判:
|
|
821
|
-
// 🔴 ① 这一发比卡上现在承载的那一阶段旧(或就是同一条帧又来一遍)⇒ 整帧丢弃。少了这一条,
|
|
822
|
-
// 门 park 那一阶段的 start 重放会把批复时**改过的执行参数**盖回原参数 —— 屏上就成了
|
|
823
|
-
// 「这个参数产生了那个结果」,而那个结果根本不是它跑出来的。
|
|
824
|
-
// 🔴 ② 这张卡的**首个段边界**给过没有(收口先到时下面那支会合成一张占位卡:名字 `Tool`、
|
|
825
|
-
// 参数 `{}`、边界还没给)—— 没给过 ⇒ 迟到的这一发就是它的第一个段边界,要补做一次段闭合,
|
|
826
|
-
// 并且按合成那一刻记下的边界把「工具之前 / 之后」两段**分开**收(不分开的话两段黏成
|
|
827
|
-
// 一段、只进一条记号,随后整轮重放时两段的内容闸都不命中)。给过了 ⇒ 这一发是重放或
|
|
828
|
-
// 下一阶段,**不**段闭合:边界在活体那一轮已经给过,在这里切会把还在流的那一段提前截断。
|
|
829
|
-
// 🔴 判据是卡上那一位,**不是**「start 水位在不在」:水位是可选位,上游不给 `eventId` 时
|
|
830
|
-
// 它永远抬不起来 ⇒ 每一次重放都会再切一刀(0.62.2 修的第三条)。
|
|
831
|
-
// 名字与参数就地补齐/更新(重放送来的是同一份值,补齐是空操作;字节按**差额**记账)。
|
|
832
505
|
const it = s.items[at];
|
|
833
506
|
const before = itemBytesOf(it);
|
|
834
507
|
const keyBefore = startContentKey(it);
|
|
@@ -845,28 +518,14 @@ export function publishSubagentContentEvent(ev) {
|
|
|
845
518
|
flushBuffers(s);
|
|
846
519
|
const id = startId ?? `${ev.taskId}-tool-${s.items.length}`;
|
|
847
520
|
pushItem(s, { kind: 'tool', id, name: ev.toolName ?? 'Tool', input: ev.args ?? {} });
|
|
848
|
-
// 建卡这一支刚刚无条件段闭合过 ⇒ 边界就是此刻,同拍记在卡上
|
|
849
521
|
markBoundary(s, id);
|
|
850
|
-
// 卡是这一刻新铸的 ⇒ 这一半的内容当然是「变了」(此前没有任何一份)
|
|
851
522
|
stampPhase(s, id, 'start', ev.eventId, true);
|
|
852
523
|
break;
|
|
853
524
|
}
|
|
854
525
|
case 'tool_end': {
|
|
855
526
|
const id = ev.toolCallId;
|
|
856
|
-
// 卡在不在、在哪里,只按 `toolCallId` 现查 items —— **开着**与**已收口**两支从此共用同一条
|
|
857
|
-
// 定位与同一道闸(0.62.2 合并):此前开着那一支走的是另一张「开着的卡 → 下标」表、且**无条件**
|
|
858
|
-
// 落结果,于是同一件事有两处真源、两套规矩,而两条独立的流交错时旧收口正是从那一支进来的。
|
|
859
527
|
const at = id !== undefined ? toolCardIndexOf(s, id) : -1;
|
|
860
528
|
if (at >= 0) {
|
|
861
|
-
// 🔴 一道闸管两支:比卡上现在承载的那一阶段**旧、或就是同一条帧** ⇒ 整帧丢弃。
|
|
862
|
-
// · 同一条帧重放 ⇒ 再落一次会把结果字节重复计进预算,走到下面那支还会另铸一张已完成的卡;
|
|
863
|
-
// · 旧一阶段的收口(park 的 end 在恢复阶段之后才姗姗来迟)⇒ 会把屏上的真结果盖回去,
|
|
864
|
-
// 卡还开着时更会**关掉**一张属于新阶段、其实还在跑的卡(见 {@link staleEnd} 头注);
|
|
865
|
-
// · 反过来,**更新的一发**收口(门 park 掉这次调用、批复后恢复执行,上游按同一个
|
|
866
|
-
// `toolCallId` 再发一对 start/end)⇒ 后到的才是真结果,就地替换/落账。把「已 settled」
|
|
867
|
-
// 当成永久完成会把真结果吞掉,屏上停在那句「被门拦下」上。
|
|
868
|
-
// 🔴 身份缺席(判不出新旧)⇒ 闸不拦,照落(重放送的是同一份值,落账是空操作),同拍把
|
|
869
|
-
// `end` 那一半的水位清掉(见 {@link stampPhase})。
|
|
870
529
|
if (staleEnd(phaseOf(s, id), ev.eventId))
|
|
871
530
|
break;
|
|
872
531
|
const it = s.items[at];
|
|
@@ -875,17 +534,12 @@ export function publishSubagentContentEvent(ev) {
|
|
|
875
534
|
const errBefore = it.isError;
|
|
876
535
|
it.output = ev.output ?? '';
|
|
877
536
|
it.isError = ev.isError === true;
|
|
878
|
-
// 结果正文可能是**这一刻**才落到已在账的那条 item 上的 ⇒ 按差额补记(替换时差额可正可负)。
|
|
879
537
|
const delta = itemBytesOf(it) - before;
|
|
880
538
|
s.itemBytes[at] = (s.itemBytes[at] ?? 0) + delta;
|
|
881
539
|
addBytes(s, delta);
|
|
882
540
|
stampPhase(s, it.id, 'end', ev.eventId, it.output !== outBefore || it.isError !== errBefore);
|
|
883
541
|
break;
|
|
884
542
|
}
|
|
885
|
-
// orphan close (start lost to the ring / arrived first) — synthesize a resolved card.
|
|
886
|
-
// 🔴 这里**不**段闭合:段边界由 `tool_start` 给(收口先到时那一发迟到的 start 会补做,见上)。
|
|
887
|
-
// 在这里闭合会把**还在流的**那一段提前切成 item —— 每子代帽配得很紧时,那一段随即变成
|
|
888
|
-
// 「最早的内容」而被帽整只丢掉,而它其实是用户眼前正在看的字。
|
|
889
543
|
const synthId = id ?? `${ev.taskId}-tool-${s.items.length}`;
|
|
890
544
|
pushItem(s, {
|
|
891
545
|
kind: 'tool',
|
|
@@ -895,24 +549,15 @@ export function publishSubagentContentEvent(ev) {
|
|
|
895
549
|
output: ev.output ?? '',
|
|
896
550
|
isError: ev.isError === true,
|
|
897
551
|
});
|
|
898
|
-
// 🔴 边界虽然不在这里切,**位置**要在这里记下来:此刻缓冲里的那一截是「工具之前」那一段,
|
|
899
|
-
// 之后再流进来的是「工具之后」那一段。迟到的 `tool_start` 按这个位置切开两段(见
|
|
900
|
-
// {@link TaskContentState.pendingCuts})—— 不记的话它只能把整条缓冲当成一段闭合,
|
|
901
|
-
// 两段就此黏死。"
|
|
902
552
|
recordCut(s);
|
|
903
|
-
// 卡是这一刻新铸的 ⇒ 结果那一半当然是「变了」
|
|
904
553
|
stampPhase(s, synthId, 'end', ev.eventId, true);
|
|
905
554
|
break;
|
|
906
555
|
}
|
|
907
556
|
}
|
|
908
|
-
// 两道帽在**每一次写入之后**执行(不是定时扫):越限那一刻就丢,账本永远不会先胀起来再回落。
|
|
909
557
|
enforceTaskCap(s);
|
|
910
558
|
enforceTotalCap(key);
|
|
911
559
|
scheduleNotify(key);
|
|
912
560
|
}
|
|
913
|
-
/** 查看态 composer echo (C2 steer optimistic display) — segment-closes the buffers first so the echo
|
|
914
|
-
* lands AFTER the text streamed before it. taskId may be unseen (row entered before any content):
|
|
915
|
-
* parentToolCallId may then be unknown — pass '' and the steer path resolves the target separately. */
|
|
916
561
|
export function pushSubagentLocalEcho(taskId, text) {
|
|
917
562
|
const s = stateFor(taskId, tasks.get(taskId)?.parentToolCallId ?? '');
|
|
918
563
|
flushBuffers(s);
|
|
@@ -926,17 +571,9 @@ export function getSubagentContentSnapshot(taskId) {
|
|
|
926
571
|
const s = tasks.get(key);
|
|
927
572
|
if (!s)
|
|
928
573
|
return null;
|
|
929
|
-
touchLru(tasks, key);
|
|
574
|
+
touchLru(tasks, key);
|
|
930
575
|
return { items: [...s.items], textBuf: s.textBuf, thinkBuf: s.thinkBuf };
|
|
931
576
|
}
|
|
932
|
-
/**
|
|
933
|
-
* 把一个子代的内容账本投影成查看态渲染计划。
|
|
934
|
-
*
|
|
935
|
-
* [1568] 之前的形(壳侧 engineAgentView 里)有两处「等段闭合」:①遇到第一个未闭合工具就 break;
|
|
936
|
-
* ②textBuf/thinkBuf 完全不投影。实测(engine 1.275.0 真跑):数据面 ~23 帧/s 的 delta 连续到壳,
|
|
937
|
-
* 查看态却只在 tool 边界更新(进查看态后 6.0s 全空,之后每 5–15s 才动一次)。现形:在飞工具照投,
|
|
938
|
-
* 流式尾巴作为 live 槽投 —— 屏面粒度 = wire 粒度(store notify 已按 250ms 合并)。
|
|
939
|
-
*/
|
|
940
577
|
export function planSubagentViewSlots(taskId) {
|
|
941
578
|
const snap = getSubagentContentSnapshot(taskId);
|
|
942
579
|
if (!snap)
|
|
@@ -967,7 +604,6 @@ export function planSubagentViewSlots(taskId) {
|
|
|
967
604
|
out.push({ kind: 'echo', slot: `e${i}`, text: it.text });
|
|
968
605
|
}
|
|
969
606
|
});
|
|
970
|
-
// 尾巴顺序与 flushBuffers 落项顺序一致(thinking 先、text 后)——段闭合时屏面内容连续不跳。
|
|
971
607
|
if (snap.thinkBuf.trim()) {
|
|
972
608
|
out.push({ kind: 'thinking', slot: 'live-think', text: snap.thinkBuf, live: true });
|
|
973
609
|
}
|
|
@@ -976,11 +612,9 @@ export function planSubagentViewSlots(taskId) {
|
|
|
976
612
|
}
|
|
977
613
|
return out;
|
|
978
614
|
}
|
|
979
|
-
/** C2 steer target: the service routes subagent steer by the delegating tool call's id. */
|
|
980
615
|
export function parentToolCallIdOf(taskId) {
|
|
981
616
|
return taskToParent.get(taskId) ?? tasks.get(taskId)?.parentToolCallId;
|
|
982
617
|
}
|
|
983
|
-
/** Settle a task's streaming buffers (turn ended / run settled) so the snapshot is complete. */
|
|
984
618
|
export function settleSubagentContent(taskId) {
|
|
985
619
|
const s = tasks.get(taskId);
|
|
986
620
|
if (!s)
|
|
@@ -989,7 +623,7 @@ export function settleSubagentContent(taskId) {
|
|
|
989
623
|
scheduleNotify(taskId);
|
|
990
624
|
}
|
|
991
625
|
export function clearSubagentContent(taskId) {
|
|
992
|
-
dropTask(taskId);
|
|
626
|
+
dropTask(taskId);
|
|
993
627
|
}
|
|
994
628
|
const bgFacts = new Map();
|
|
995
629
|
export function recordBgTerminalFacts(taskId, facts) {
|
|
@@ -1000,14 +634,10 @@ export function recordBgTerminalFacts(taskId, facts) {
|
|
|
1000
634
|
if (oldest !== undefined)
|
|
1001
635
|
bgFacts.delete(oldest);
|
|
1002
636
|
}
|
|
1003
|
-
bgFacts.delete(taskId);
|
|
637
|
+
bgFacts.delete(taskId);
|
|
1004
638
|
bgFacts.set(taskId, facts);
|
|
1005
|
-
scheduleNotify(taskId);
|
|
639
|
+
scheduleNotify(taskId);
|
|
1006
640
|
}
|
|
1007
|
-
/** 复活清账(#242 批 3 / 0.31.0 扫码 P2):非终态行帧/snapshot 复活 = 新周期开启,旧周期的
|
|
1008
|
-
* 终态事实作废 —— 不清账时 factsAccepted 的方向核会拿上周期的 killed/failed 当先例,把
|
|
1009
|
-
* 第二周期的合法 completed 整条拒收(status 与 summary 都不入账)。只删本 taskId 键;
|
|
1010
|
-
* taskToParent 映射不动(寻址面与事实面分离)。 */
|
|
1011
641
|
export function clearBgTerminalFacts(taskId) {
|
|
1012
642
|
bgFacts.delete(taskId);
|
|
1013
643
|
}
|
|
@@ -1016,106 +646,21 @@ export function getBgTerminalFacts(taskId) {
|
|
|
1016
646
|
touchLru(bgFacts, key);
|
|
1017
647
|
return bgFacts.get(key);
|
|
1018
648
|
}
|
|
1019
|
-
// ── bg 子代 → spawn 它的 leader run(server 1.244 subagentOutput 读面的寻址元组)──────────────
|
|
1020
|
-
// GET /v1/runs/:runId/subagents/:handle/output 的 runId = spawn 该子代的 run(registry access 从
|
|
1021
|
-
// run 行推导,[1491]①)。来源三路:fleet 行 parentId(出生帧即带)+ bg_notification.parentTaskId
|
|
1022
|
-
// + `task_progress` tick(#242 批 2 新腿,见下)。
|
|
1023
|
-
//
|
|
1024
|
-
// 🔴 #242 批 2(A-028.6 收编,2026-08-15):本表**收编壳侧 `subagentOwnerLedger`**,成为
|
|
1025
|
-
// 「这一行子代属于哪一条引擎 run」的**唯一台账**。收编前同一个问题有两份表、两个相反答案:
|
|
1026
|
-
// · 壳表(cli `src/sema/subagentOwnerLedger.ts`,cap 256)由 `task_progress` tick 喂,
|
|
1027
|
-
// 取址缺席时**诚实缺席**(指名了行就绝不回落到「此刻在飞的那条」);
|
|
1028
|
-
// · 本表由 fleet 行帧(`fleet/fleetLedger.ts:394`)+ bg 通知(`:496`)喂,三处消费方一律
|
|
1029
|
-
// `getBgParentRun(x) ?? activeEngineRunId()` 无条件回落。
|
|
1030
|
-
// 两表覆盖面**互补**(壳表只覆盖发 tick 的子代;fleet-row lane 子代零 tick,只有本表覆盖),
|
|
1031
|
-
// 所以合表不是二选一,是把两条 lane 的事实并进同一个答案。
|
|
1032
|
-
// 键域两侧本就同域,合表零改键:fleet 腿写 `rowIdTail(row.id)` = 裸引擎 taskId、通知腿写
|
|
1033
|
-
// `n.taskId` = registry handle(同一裸 id)、tick 腿写 `task_progress.taskId`。
|
|
1034
|
-
// ✅ #242 批 3([4000] Q3=B 裁定):三处 `?? activeEngineRunId()` 回落已全部翻面 —— tail /
|
|
1035
|
-
// taskOutput·taskStop / subagentOutput 三腿一律「台账缺席即诚实缺席,绝不回落」,缺席
|
|
1036
|
-
// warn 留痕(`subagent/subagentOwnerAbsence.ts`),与 resume 腿口径统一。
|
|
1037
|
-
//
|
|
1038
|
-
// 🔴 写入纪律(壳表头注 :20-22 的教训**原文搬**):宿主 run **必须由持有 stream-local 值的
|
|
1039
|
-
// 调用方显式传入**,本模块绝不从进程级 `activeEngineRunId()` 推断 —— 那个值被并发的
|
|
1040
|
-
// main/fork 流互相覆写、又被每个 query 的 finally 无条件清空,推断出来的宿主可能是**别的
|
|
1041
|
-
// run**,而**错值比缺席更坏**(消费端的门只查「有没有值」,分辨不出错值:continue 会打到
|
|
1042
|
-
// 不相干的 run,甚至叫醒那条 run 里的同名子代)。取不到 ⇒ 什么都不记。
|
|
1043
|
-
//
|
|
1044
|
-
// 🔴 有界:键来自 wire(不可信),无界表 = 引擎乱发 taskId 就能吃掉宿主内存。上限取**两表
|
|
1045
|
-
// 上限之和**(壳表 256 + 本表原 `MAX_TASKS*2`=64)—— 合表**不许缩窄既有保障**:收编前两条
|
|
1046
|
-
// lane 各有各的池子,能同时记 256 条 tick + 64 条 fleet;若按 256 钉,「tick 池已满 + 再来一条
|
|
1047
|
-
// fleet 行」当场淘汰一条 tick,那条子代的 resume 入口凭空消失,而收编前它还在(对抗复审
|
|
1048
|
-
// medium 实证形)。两读口的 LRU touch 也会让高频 fleet 行持续挤压 tick 历史,同一条道理。
|
|
1049
|
-
// 满了按 LRU 淘汰,淘汰后果只是那一行退回「不知道宿主 run」= 诚实缺席,不是错值。
|
|
1050
649
|
const MAX_BG_PARENT_RUNS = 320;
|
|
1051
650
|
const bgParentRun = new Map();
|
|
1052
|
-
/**
|
|
1053
|
-
* 缺席 `sessionKey` 的**有效槽身份**。
|
|
1054
|
-
*
|
|
1055
|
-
* 🔴 值必须与 `sessionSlot.DEFAULT_SESSION_KEY` 逐字相同,但**刻意不 import**:本 store 是
|
|
1056
|
-
* `runStream.ts` 可移植闭包里的零 import 件(文件头三处以此立身),拉一条边就破了那道门。
|
|
1057
|
-
* 两处同值由 pure 门的 `B3-P31/R6h` 钉住(读侧 `resolveOwnerContext` 的回落键必须 === 本常量),
|
|
1058
|
-
* 任一侧改字面当场红 —— 手抄一个常量而没有对账钉,才是真正会漂的形。
|
|
1059
|
-
*/
|
|
1060
651
|
const DEFAULT_OWNER_SLOT = '__default__';
|
|
1061
652
|
let ownerSessionConflictCount = 0;
|
|
1062
653
|
let lastOwnerSessionConflict;
|
|
1063
|
-
/**
|
|
1064
|
-
* 🔴 **跨槽冲突的隔离台账**(异源对抗复审第六轮 [high] 采纳):`taskId` → 出事的那个 `runId`。
|
|
1065
|
-
*
|
|
1066
|
-
* 为什么光删记录不够(复审的可证伪点,实测成立):删完之后 `prior` 为空 ⇒ **任一条**后续帧都会
|
|
1067
|
-
* 立刻把记录重建出来 —— 序列 `A 写 → B 写(冲突删)→ A 再写` 当场恢复 `{sessionKey:'A'}`,反向亦然。
|
|
1068
|
-
* 两个来源**持续产帧**时,这条行会在两个槽之间来回翻,每翻一次就开一个错误出站窗;而读侧拿它做的
|
|
1069
|
-
* 是 stop / resume 这类**有副作用**的动作。「后一帧自愈」只是**假设**另一条流已经停了,那不是证据。
|
|
1070
|
-
*
|
|
1071
|
-
* 隔离的**解除规则**(与复审给的口径一致,取其中不需要代际管道的那一条):
|
|
1072
|
-
* · `runId` **变了** ⇒ 那是另一条宿主 run,与出事的那一条无关,隔离随之作废;
|
|
1073
|
-
* · 测试钩 `__resetSubagentOwnerLedgerForTests()`。
|
|
1074
|
-
* 🔴 隔离期间**记录保持缺席** ⇒ 五个读点走成文的诚实缺席(`noteBgOwnerAbsence` + null / no-run),
|
|
1075
|
-
* 零读侧改动、零出站。⚠️ 计数恒应为 0 ⇒ 对接线正确的宿主本机制逐字节不可达。
|
|
1076
|
-
* 有界:与 `bgParentRun` 同上限、同 LRU 淘汰口径(键同样来自 wire,不许无界)。
|
|
1077
|
-
*/
|
|
1078
654
|
const conflictQuarantine = new Map();
|
|
1079
|
-
/** R6 读口:`count` = 累计冲突次数(恒应为 0);`last` = 最后一次的四元组(诚实缺席用 undefined)。 */
|
|
1080
655
|
export function subagentOwnerSessionConflicts() {
|
|
1081
656
|
return {
|
|
1082
657
|
count: ownerSessionConflictCount,
|
|
1083
658
|
...(lastOwnerSessionConflict !== undefined ? { last: lastOwnerSessionConflict } : {}),
|
|
1084
659
|
};
|
|
1085
660
|
}
|
|
1086
|
-
/**
|
|
1087
|
-
* fleet 行帧 / bg 通知腿的登记口(既有两写点)。
|
|
1088
|
-
* `sessionId` 是 #242 批 2 additive 补位:缺席则不落键(读面据此判「不带 `?session=`」)。
|
|
1089
|
-
* `sessionKey` 是 design/285 批 2 additive 补位,同款「缺席则不落键」。
|
|
1090
|
-
*
|
|
1091
|
-
* 🔴 **两个新位的值必须来自 ingress 的不可变捕获**(`ledger.issueStream()` 那一刻解析一次),
|
|
1092
|
-
* **不是**每帧现读某个可变槽 —— 逐帧现读会让重连后旧流的迟到帧被登记成 `{旧 runId, 新会话}`,
|
|
1093
|
-
* 正是本文件 `recordSubagentOwnerFromProgress` 头注明令禁止的错组合。ingress 的 epoch 闸让旧代际
|
|
1094
|
-
* 的帧根本进不来,这才是「原子」的落点。
|
|
1095
|
-
*/
|
|
1096
661
|
export function recordBgParentRun(taskId, runId, sessionId, sessionKey) {
|
|
1097
662
|
recordSubagentOwner(taskId, runId, sessionId, sessionKey);
|
|
1098
663
|
}
|
|
1099
|
-
/**
|
|
1100
|
-
* 「这一行子代的宿主 run」统一登记口(**显式传值**,零推断,见本段头注)。
|
|
1101
|
-
* `runId` 缺席/空串 ⇒ 什么都不记(宁可「不知道」也不记一个错的宿主);`taskId === runId`
|
|
1102
|
-
* 的退化自指映射同样拒收(顶层 run 行不是任何人的子代)。
|
|
1103
|
-
*
|
|
1104
|
-
* 🔴 **`sessionId` 的缺席不覆盖在场**(合表的承重口径,#242 批 2 自查抓出):喂本表的三条腿里
|
|
1105
|
-
* **只有 tick 腿带 session**(它在开流时一次性捕获),fleet 行帧腿与 bg 通知腿都不带。合表前
|
|
1106
|
-
* 两表各记各的,合表后若让「后到的无 session 写」直接盖掉整条记录,tick 捕获的会话就会被抹掉,
|
|
1107
|
-
* resume 退回「此刻的会话」—— 那正是对抗复审轮5 M1 要防的错组合(旧 runId + 新 sessionId ⇒
|
|
1108
|
-
* session-bound run 上 fail-closed 404)。
|
|
1109
|
-
* 口径:**同一 `runId` 上缺席不覆盖在场**(带值的写照常赢);`runId` 真变了 ⇒ 旧 session 必须丢
|
|
1110
|
-
* (那是另一条宿主 run 的会话,留着就是错组合)。
|
|
1111
|
-
*
|
|
1112
|
-
* 🔴 **`sessionId` 与 `sessionKey` 是一个「出身对」,整对进出**(design/285 批 2,异源对抗复审
|
|
1113
|
-
* [high] 收紧):**这一写带了任一位就整对赢**;**两位都缺席**才走上面那条「缺席不覆盖在场」。
|
|
1114
|
-
* 分开 merge(各留各的)会合成一条从来没被同一次捕获产生过的元组 —— 而读侧正是拿这一对去挑
|
|
1115
|
-
* 服务器 / 凭证 / 会话,详见函数体里那段头注的两条可达路径。`runId` 变了 ⇒ 整对丢。
|
|
1116
|
-
* 🔴 **同 runId 上换出身不静默**(R6):出身对(会话**或**槽键)变了就计 `ownerSessionConflictCount`
|
|
1117
|
-
* 并留下两对(读口 {@link subagentOwnerSessionConflicts})。值仍按「带出身的写赢」,变的是它不再无声。
|
|
1118
|
-
*/
|
|
1119
664
|
export function recordSubagentOwner(taskId, runId, sessionId, sessionKey) {
|
|
1120
665
|
if (!taskId || typeof runId !== 'string' || runId === '' || taskId === runId)
|
|
1121
666
|
return;
|
|
@@ -1124,29 +669,13 @@ export function recordSubagentOwner(taskId, runId, sessionId, sessionKey) {
|
|
|
1124
669
|
if (oldest !== undefined)
|
|
1125
670
|
bgParentRun.delete(oldest);
|
|
1126
671
|
}
|
|
1127
|
-
// 🔴 隔离闸(异源对抗复审第六轮):这条 `(taskId, runId)` 已被判过「两个槽同时认领」⇒ 记录保持
|
|
1128
|
-
// 缺席,任何后续帧都不许把它重建出来(「后一帧自愈」是假设不是证据)。`runId` 变了即解除。
|
|
1129
672
|
const quarantinedRun = conflictQuarantine.get(taskId);
|
|
1130
|
-
// 🔴 **墓碑,不是可解除的锁**(异源对抗复审第七轮 [high] 采纳,推翻前一版的「runId 变了就删条目」):
|
|
1131
|
-
// `runId` 是**无序字符串**,「不相等」证明不了前进方向。前一版的序列可复现:`(C,R)` 冲突 ⇒ 隔离;
|
|
1132
|
-
// `(C,R2)` 把条目**删掉**并登记 R2;此时原冲突源**迟到**的 `(C,R)` 因为条目已删、`sameRun` 又为假,
|
|
1133
|
-
// 直接把映射覆盖回旧 R 与旧槽 —— 跨会话误路由窗当场重开。
|
|
1134
|
-
// ⇒ 条目**留着不删**:别的 `runId` 照常放行(另一条宿主 run 与出事的那条无关),而**那一条**
|
|
1135
|
-
// 出事的 run 从此不许再回来。有界性不变(与 owner 表同上限、同 LRU 淘汰)。
|
|
1136
673
|
if (quarantinedRun !== undefined && quarantinedRun === runId)
|
|
1137
674
|
return;
|
|
1138
675
|
const prior = bgParentRun.get(taskId);
|
|
1139
676
|
const sameRun = prior?.runId === runId;
|
|
1140
677
|
const incoming = typeof sessionId === 'string' && sessionId !== '' ? sessionId : undefined;
|
|
1141
678
|
const incomingKey = typeof sessionKey === 'string' && sessionKey !== '' ? sessionKey : undefined;
|
|
1142
|
-
// 🔴 **出身对整对进出**(异源对抗复审 [high] 采纳):`sessionId` 与 `sessionKey` **不许各merge各的**。
|
|
1143
|
-
// 分开 merge 会合成一条**从来没有被同一次捕获产生过**的元组,而读侧拿它挑服务器/凭证/会话:
|
|
1144
|
-
// · ingress 写 `{S1,'A'}` 之后,另一本 keyed 到 `'B'` 的 ledger(其 SessionPort 无 id)经 ingress
|
|
1145
|
-
// 写 `{undefined,'B'}` ⇒ 分开 merge 得 `{S1,'B'}` = 拿 A 的会话去打 B 的服务器;
|
|
1146
|
-
// · ingress 写 `{S1,'A'}` 之后,tick 腿(只带会话、不带槽键)写 `{S2,undefined}` ⇒ 得 `{S2,'A'}`
|
|
1147
|
-
// = 拿默认槽捕获的会话去打 A 的服务器。两条都是本设计要结构性堵死的错组合。
|
|
1148
|
-
// 规矩:**这一写带了任一位就整对赢**(带的那一对就是它的出身,另一位缺席就是「这条出身上没有」);
|
|
1149
|
-
// **两位都缺席**才走「缺席不覆盖在场」(= 兼容入口/无出身的写,它没有资格改出身)。
|
|
1150
679
|
const carriesProvenance = incoming !== undefined || incomingKey !== undefined;
|
|
1151
680
|
const nextPair = carriesProvenance
|
|
1152
681
|
? {
|
|
@@ -1159,8 +688,6 @@ export function recordSubagentOwner(taskId, runId, sessionId, sessionKey) {
|
|
|
1159
688
|
...(prior?.sessionKey !== undefined ? { sessionKey: prior.sessionKey } : {}),
|
|
1160
689
|
}
|
|
1161
690
|
: {};
|
|
1162
|
-
// R6:同一条宿主 run 上**换出身**(会话变了、或槽键变了)—— 留痕在前(赢家仍是带出身的这一写),
|
|
1163
|
-
// 绝不静默 last-write-wins。🔴 槽键变化同样计:换槽键 = 换服务器与凭证,比换会话更严重。
|
|
1164
691
|
const priorHasProvenance = prior?.sessionId !== undefined || prior?.sessionKey !== undefined;
|
|
1165
692
|
if (sameRun &&
|
|
1166
693
|
carriesProvenance &&
|
|
@@ -1176,37 +703,6 @@ export function recordSubagentOwner(taskId, runId, sessionId, sessionKey) {
|
|
|
1176
703
|
},
|
|
1177
704
|
next: { ...nextPair },
|
|
1178
705
|
};
|
|
1179
|
-
// 🔴 **两个槽同时认领同一条 run ⇒ 出身对整对作废**(异源对抗复审第二轮 [high] 的**可采半场**)。
|
|
1180
|
-
//
|
|
1181
|
-
// 复审的原议是「任何出身冲突都 fail-closed」,**只采窄的那一格**,理由是两条判据不同命:
|
|
1182
|
-
// · **只换会话、槽键不变**(或两边都没有槽键)= #242 批 2 **已裁并钉死**的一格
|
|
1183
|
-
// (常驻钉 `#242-R3c3`「带 session 的写照常覆盖,新值赢」)—— 那是 tick 腿重新捕获的正常形,
|
|
1184
|
-
// 本批无权在施工轮把一条别的批次的裁定翻面;它的信号面是下面那个计数(R6 的原意)。
|
|
1185
|
-
// · **两边都带槽键且不相等** = 本批**新引入**的一格,没有任何既有裁定,而且它不可能有合法读法:
|
|
1186
|
-
// 两个不同的槽(= 两台引擎、两份凭证)同时声称同一条宿主 run 是谁的。此时「哪一份对」
|
|
1187
|
-
// **不可知**,而读侧拿它挑的正是**服务器 / 凭证 / 会话** —— 留痕在库内零消费者,
|
|
1188
|
-
// 拿后到的那份继续路由就是把不可知说成已知。按「错值比缺席更坏」整对丢:读侧回落到
|
|
1189
|
-
// 「无通道证据」那条**成文的**降级路径,而不是一个编出来的元组。
|
|
1190
|
-
// 🔴 **整条记录删掉,`runId` 也不留**(异源对抗复审第五轮 [high] 采纳,推翻了本批前一版的
|
|
1191
|
-
// 「留 runId」)。前一版的理由是「`runId` 那一位没有争议」,**那条理由不成立**:`runId`
|
|
1192
|
-
// 要能用,读侧必须给它配一个槽,而读侧对缺席的 `sessionKey` 的解释是 `DEFAULT_SESSION_KEY`
|
|
1193
|
-
// —— 于是「留 runId、清出身」实际等于**断言这条 run 属于默认槽**,而冲突恰恰是「有一个 keyed
|
|
1194
|
-
// 槽也认领了它」的正面证据。⇒ 那不是「不知道」,是一个已经被证否的猜测。
|
|
1195
|
-
// 与兼容入口的无槽记录**不同命**:对纯兼容宿主来说默认槽就是它唯一的槽(那条记录是对的);
|
|
1196
|
-
// 在冲突这一格上默认槽是**被证否**的候选之一。
|
|
1197
|
-
// 🔴 删记录**不需要任何读侧改动**:五个读点对「台账缺席」早已是成文的诚实缺席
|
|
1198
|
-
// (`noteBgOwnerAbsence` 留痕 + null / no-run,#242 批 3 [4000] Q3=B 裁定),本分支直接复用它。
|
|
1199
|
-
// 🔴 **不设粘性 quarantine**:删完之后 `prior` 为空 ⇒ 下一条带出身的帧照常重建记录 —— 那正是
|
|
1200
|
-
// 复审要的「唯一槽证据」恢复规则:真正还活着的那条流会继续写,停掉的那条不再写。粘性会把一条
|
|
1201
|
-
// 本可自愈的行永久钉死;而「冲突到下一帧之间的窗」与「冲突帧就是终帧」两种情形下,记录不在
|
|
1202
|
-
// = 读面诚实缺席,**没有错误出站**,那正是 fail-closed 要的结局。
|
|
1203
|
-
// ⚠️ 冲突计数**恒应为 0** ⇒ 对任何接线正确的宿主,本分支逐字节不可达。
|
|
1204
|
-
// 🔴 **按「有效槽」比,不按「键在不在」比**(异源对抗复审第三轮 [high] 采纳):读侧的
|
|
1205
|
-
// `resolveOwnerContext` 把**缺席的 sessionKey 读成 `DEFAULT_SESSION_KEY`** —— 所以「不带键的
|
|
1206
|
-
// 带出身写」不是「没有槽」,它就是**默认槽**在认领这条 run。只比显式值会漏掉最常见的那一格:
|
|
1207
|
-
// keyed ingress 写 `{S1,'A'}` 之后,一个仍按三参老签名调用的 tick(`{S2, 无键}`)会把记录改成
|
|
1208
|
-
// `{sessionId:S2}` ⇒ 读侧拿 **A 的 runId** 去打**默认槽**的服务器与凭证。两向都要挡。
|
|
1209
|
-
// ⚠️ 单会话宿主上两边的有效槽都是 `DEFAULT_SESSION_KEY` ⇒ 恒不触发,#242-R3c3 那条钉一字未动。
|
|
1210
706
|
const priorSlot = prior?.sessionKey ?? DEFAULT_OWNER_SLOT;
|
|
1211
707
|
const nextSlot = nextPair.sessionKey ?? DEFAULT_OWNER_SLOT;
|
|
1212
708
|
if (priorSlot !== nextSlot) {
|
|
@@ -1220,24 +716,9 @@ export function recordSubagentOwner(taskId, runId, sessionId, sessionKey) {
|
|
|
1220
716
|
return;
|
|
1221
717
|
}
|
|
1222
718
|
}
|
|
1223
|
-
bgParentRun.delete(taskId);
|
|
719
|
+
bgParentRun.delete(taskId);
|
|
1224
720
|
bgParentRun.set(taskId, { runId, ...nextPair });
|
|
1225
721
|
}
|
|
1226
|
-
/**
|
|
1227
|
-
* `task_progress` 腿的登记口(#242 批 2 ①:收编前本表**零 task_progress 腿**,已直证)。
|
|
1228
|
-
*
|
|
1229
|
-
* 一条帧进,认得就记,认不得就什么都不做(**绝不改帧**)。只认 `task_progress` —— 引擎侧子代的
|
|
1230
|
-
* 累计 tick,带子代自己的 `taskId`。
|
|
1231
|
-
*
|
|
1232
|
-
* 🔴 **`ownerRunId` / `ownerSessionId` 必须由调用方显式喂**,本函数**不许**从
|
|
1233
|
-
* `activeEngineRunId()` 推断(壳 `subagentOwnerLedger.ts:20-22` 对抗复审轮3 H2 实撞的教训:
|
|
1234
|
-
* 进程级值被并发 main/fork 流互相覆写 + 每个 query 的 finally 清空 ⇒ run A 的 tick 可能被
|
|
1235
|
-
* 登记到 run B 名下,而错值比缺席更坏)。登记点必须在**持有 stream-local run id 的那个闭包**
|
|
1236
|
-
* 里(壳 = `liveClient.ts` 的 `stampSubagentOwners`),把不可变的值传进来。
|
|
1237
|
-
* 🔴 `ownerSessionId` 同理**开流时一次性捕获**,不逐帧现读:逐帧现读会让 `/clear`、resume、
|
|
1238
|
-
* 会话切换之后晚到的旧流 tick 被登记成 `{旧 runId, 新 sessionId}`,这对组合去 resume 会带着
|
|
1239
|
-
* 错 session 打旧 run,fail-closed 404。
|
|
1240
|
-
*/
|
|
1241
722
|
export function recordSubagentOwnerFromProgress(ev, ownerRunId, ownerSessionId, ownerSessionKey) {
|
|
1242
723
|
if (typeof ev !== 'object' || ev === null)
|
|
1243
724
|
return;
|
|
@@ -1254,34 +735,22 @@ export function getBgParentRun(taskId) {
|
|
|
1254
735
|
touchLru(bgParentRun, taskId);
|
|
1255
736
|
return bgParentRun.get(taskId)?.runId;
|
|
1256
737
|
}
|
|
1257
|
-
/**
|
|
1258
|
-
* 整条宿主记录(runId + 登记时会话)。undefined = **诚实不知道**,调用方渲 no-run 出路 /
|
|
1259
|
-
* 不渲入口 —— 绝不在这里回落成「此刻在飞的那条 run」([anchor-on-the-deciding-quantity])。
|
|
1260
|
-
*/
|
|
1261
738
|
export function getBgParentRunOwner(taskId) {
|
|
1262
739
|
if (typeof taskId !== 'string' || taskId === '')
|
|
1263
740
|
return undefined;
|
|
1264
741
|
touchLru(bgParentRun, taskId);
|
|
1265
742
|
return bgParentRun.get(taskId);
|
|
1266
743
|
}
|
|
1267
|
-
/** 测试钩:清宿主台账(module 级单例,同进程多组断言必须能清)。R6 冲突台账一并清。 */
|
|
1268
744
|
export function __resetSubagentOwnerLedgerForTests() {
|
|
1269
745
|
bgParentRun.clear();
|
|
1270
746
|
conflictQuarantine.clear();
|
|
1271
747
|
ownerSessionConflictCount = 0;
|
|
1272
748
|
lastOwnerSessionConflict = undefined;
|
|
1273
749
|
}
|
|
1274
|
-
// ── 本壳自己发起过的引擎 run 台账([1498]④ 帧级校验换锚)────────────────────────────────────
|
|
1275
|
-
// 喂给:engineToolDetach.setActiveEngineTaskId(liveClient 从 run_started 帧绑定,本壳每个交互
|
|
1276
|
-
// turn 的 run 必经)。与 bgParentRun 的关键区别:bgParentRun 由 fleet 行/通知帧喂(≤1.244 事实
|
|
1277
|
-
// 广播下可能混入别的壳的行),这份台账**只**记本进程亲手驱动的 run,是帧级 own/foreign 判别的
|
|
1278
|
-
// 唯一可信锚。进程内存态,壳重启即空(跨壳 resume 的历史 run 判不了 own——诚实边界,见
|
|
1279
|
-
// fleetClient 帧级校验注释)。
|
|
1280
750
|
const ownEngineRuns = new Set();
|
|
1281
751
|
export function recordOwnEngineRun(runId) {
|
|
1282
752
|
if (!runId)
|
|
1283
753
|
return;
|
|
1284
|
-
// 防御性上限:一个壳进程生命周期内 turn 数远小于此;溢出丢最老(Set 迭代序=插入序)。
|
|
1285
754
|
if (ownEngineRuns.size >= 8192 && !ownEngineRuns.has(runId)) {
|
|
1286
755
|
const oldest = ownEngineRuns.values().next().value;
|
|
1287
756
|
if (oldest !== undefined)
|
|
@@ -1292,10 +761,8 @@ export function recordOwnEngineRun(runId) {
|
|
|
1292
761
|
export function isOwnEngineRun(runId) {
|
|
1293
762
|
return ownEngineRuns.has(runId);
|
|
1294
763
|
}
|
|
1295
|
-
/** Single consumer (the panel hook). Returns an unsubscribe. */
|
|
1296
764
|
export function subscribeSubagentContent(fn) {
|
|
1297
765
|
notifyListener = fn;
|
|
1298
|
-
// replay: anything already accumulated gets one notify so a late-mounting consumer catches up
|
|
1299
766
|
for (const id of tasks.keys())
|
|
1300
767
|
pendingNotify.add(id);
|
|
1301
768
|
if (pendingNotify.size > 0)
|