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