@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,151 +1,28 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* adapter/activeRunSelfHeal.ts — 「会话被 active-run claim 锁死」的自愈决断层(A-028.1,
|
|
3
|
-
* #244 族A 包半场,2026-08-12;源形 = cli `src/sema/activeRunSelfHeal.ts` 的四路分诊树 +
|
|
4
|
-
* 结局文案层,判别半场 `activeRunBusySignal` 早已在隔壁 `runStream.ts` —— 本文件是它的
|
|
5
|
-
* 消费侧,两半从此同居包内单源)。
|
|
6
|
-
*
|
|
7
|
-
* ── 事故形态 ────────────────────────────────────────────────────────────────────────────────
|
|
8
|
-
* 引擎的 session claim 是会话级锁(createRun EEXIST ⇒ 409)。终态释放它;而 park 态
|
|
9
|
-
* (`suspended` / `needs_review`)保留它,且重启引擎救不回来(boot 期孤儿回收只捞 running)、
|
|
10
|
-
* 时间型 reap 默认整条腿不跑。于是「上一轮 turn 在审批/提问处 park 了,用户走开」之后,这个
|
|
11
|
-
* 会话的每一条新消息恒被 409 拒收。
|
|
12
|
-
*
|
|
13
|
-
* ── 🔴 处置是**分诊**,不是「见 409 就取消」────────────────────────────────────────────────
|
|
14
|
-
* 分诊真源两级(A-028.1 目标形):**`pendingGate.kind` 优先,status 表回退**。
|
|
15
|
-
* · kind 是 wire 指名的 gate 身份(引擎只在 parked 且真有待决 checkpoint 时带它),比 status
|
|
16
|
-
* 粒度准:run status `needs_review` ≠ gate kind `plan_review` —— dry-run 复核门(gate kind
|
|
17
|
-
* `needs_review`)同样落 status needs_review,按 status 猜臂会把它错路成 plan 审批卡。
|
|
18
|
-
* · kind 缺席(旧引擎 / 非 park / store 降级)才回退 status 表。
|
|
19
|
-
* 四路结局:
|
|
20
|
-
* ① plan_review 门 ⇒ **重开审批卡**,绝不 cancel(cancel = 替用户 reject 掉整个 plan)。
|
|
21
|
-
* ② 审批/提问门(human / irreversible_ask / tool_approval)⇒ **重开那张卡**(把决定权还给
|
|
22
|
-
* 用户;cancel+自动重发臂已整退役 —— 实证它是「新消息→cancel→新 run 同 ask 再 park」的
|
|
23
|
-
* 循环病根;重开失败也只如实告知,绝不回退 cancel)。
|
|
24
|
-
* ③ `running` ⇒ **呈 steer/cancel/wait 三选卡**(runningChoiceArm;offerRunningChoice 供给
|
|
25
|
-
* 缺席即回落 not-parked 现状行不动)。持有 claim 的可能是用户背景化的 durable run,正在替
|
|
26
|
-
* 他干活——绝不见 409 就自动 cancel,决定权给用户。新结局五个:running-steered /
|
|
27
|
-
* running-steer-failed / running-cancelled / running-cancel-timeout / running-cancel-failed;
|
|
28
|
-
* cancel 后走有界等 claim 释放腿(waitForClaimRelease,CANCEL_RELEASE_WAIT_MS 封顶)。
|
|
29
|
-
* ④ kind/status 两级都判不出(含今天还不存在的新门/新状态)⇒ 也什么都不做,如实告知。
|
|
30
|
-
* 不确定时不做破坏性动作;新词到货时最坏结果是保守,而不是拿没想过的词做破坏性动作。
|
|
31
|
-
*
|
|
32
|
-
* ── 出路文案纪律 ────────────────────────────────────────────────────────────────────────────
|
|
33
|
-
* 本模块产出的每一句「你可以做什么」都必须是**真的接了线**的动作。默认串承诺的是 cli 的
|
|
34
|
-
* `/clear`(regenerateSessionId ⇒ 全新引擎会话)与引擎自己的 cancel/decide 端点;没有 `/clear`
|
|
35
|
-
* 概念的宿主**必须**经 {@link ActiveRunSelfHealCopy} 换成自己的真出路 —— 绝不出现「按某个键」
|
|
36
|
-
* 而那个键没有注册。
|
|
37
|
-
*
|
|
38
|
-
* 🔴 portability:本文件的值级 import **只有**包内共享等待叶 `abortableSleep`(#265 起:cancel 后
|
|
39
|
-
* 的 claim 释放轮询要等一拍,而「怎么安全地睡一觉」全仓只许有一份实现;类型仍全部 type-only 借
|
|
40
|
-
* `runStream.ts`),不进内核/A 层闭包;index 闭包已登记。
|
|
41
|
-
*/
|
|
42
1
|
import { abortableSleep } from '../abortableSleep.js';
|
|
43
|
-
/** wire gate kind → plan_review 重开臂的 canonical 成员(core CheckpointGate 词表之一)。 */
|
|
44
2
|
export const PLAN_REVIEW_GATE_KIND = 'plan_review';
|
|
45
|
-
/**
|
|
46
|
-
* wire gate kind → plan 重开臂的**全表**(#265,2026-08-14 补齐 `dry_run_review`)。
|
|
47
|
-
*
|
|
48
|
-
* server 侧的 kind→决议入口映射(`resumeEntryForGate`)把 `plan_review` 与 `dry_run_review` 送去
|
|
49
|
-
* **同一个**入口 `/v1/assistant/tasks/{taskId}/plan_review`,两者 park 也都落 `needs_review`
|
|
50
|
-
* ⇒ 同臂。此前包内只认 canonical 那一个词,于是一条停在 dry-run 复核门上的 run 走结局④「不动 +
|
|
51
|
-
* 如实说」——话是诚实的,但那张本来可以重开的卡没被重开,会话仍旧锁着。
|
|
52
|
-
*
|
|
53
|
-
* ⚠️ 别把它和 run status 词 `needs_review` 混起来:那是 **status**(两跳间接量),gate kind 叫
|
|
54
|
-
* `dry_run_review`。gate kind `needs_review` 仍是表外词(结局④),这条边界由常驻门逐词钉。
|
|
55
|
-
*/
|
|
56
3
|
export const PLAN_REVIEW_GATE_KINDS = [PLAN_REVIEW_GATE_KIND, 'dry_run_review'];
|
|
57
|
-
/**
|
|
58
|
-
* wire gate kind → ask 重开臂的成员:`human` / `irreversible_ask` / `policy_ask` 是 core
|
|
59
|
-
* CheckpointGate 的审批/提问族(有 approvals.list pending 行可铸卡);`tool_approval` 是 park 判定
|
|
60
|
-
* (`toolApprovalWire.isToolApprovalGate`)已承认的一等 kind —— 两张表必须同集合
|
|
61
|
-
* ([paired-mechanisms-must-share-premise]:park 认得出、分诊路不进去 = 恒 reopen-failed)。
|
|
62
|
-
* 表外 kind(`resource_limit` / `needs_review` / `task_done` / 未来新词)一律走结局④:不动 +
|
|
63
|
-
* 如实说(wire 带的 decidePath 由文案层原样交给用户)。
|
|
64
|
-
*
|
|
65
|
-
* `policy_ask` 是 #265 补齐的第四个成员(server 全表逐名对读):它与 `human` 同族同入口
|
|
66
|
-
* (`/v1/approvals/{sessionId}/decide`),漏一个词的代价 = 那一类门恒走结局④,卡不重开。
|
|
67
|
-
*/
|
|
68
4
|
export const ASK_PARK_GATE_KINDS = ['human', 'irreversible_ask', 'policy_ask', 'tool_approval'];
|
|
69
|
-
/**
|
|
70
|
-
* 行上在场且**不属 ask 门族**的门种(`plan_review` / `resource_limit` / 未来新词);`null` = 该行可入
|
|
71
|
-
* ask/审批臂。kind 缺席(pre-`gate_kind` 历史行)与空串也归 `null` —— 旧行没有更强信号,各臂维持
|
|
72
|
-
* 自己的原判据(toolName 等),门种闸只拦「明说了自己不归这族」的行。
|
|
73
|
-
* 🔴 单源:`classifyAskParkRows` 的门种闸与 `findPendingForTask` 的行过滤必须共用这一只 ——
|
|
74
|
-
* 各写各的 includes 必漂(P-30:`findPendingForTask` 侧漏了这道闸,同 task 停着 plan_review 行时
|
|
75
|
-
* 「任意行」回落把它递给工具审批 wire,弹 toolName 空的卡、decide 撞 409 gate_not_tool_approval)。
|
|
76
|
-
*/
|
|
77
5
|
export function askParkForeignGateKind(row) {
|
|
78
6
|
const kind = typeof row.gateKind === 'string' && row.gateKind.length > 0 ? row.gateKind : null;
|
|
79
7
|
return kind !== null && !ASK_PARK_GATE_KINDS.includes(kind) ? kind : null;
|
|
80
8
|
}
|
|
81
|
-
/**
|
|
82
|
-
* status 回退表(kind 缺席时的粗粒度代理):plan_review park 的引擎 run status。
|
|
83
|
-
* 🔴 动作**不按状态名硬编码枚举**:两张表之外的一切状态(含今天还不存在的)一律「不动它 +
|
|
84
|
-
* 如实说」,新状态到货时最坏结果是保守。
|
|
85
|
-
*/
|
|
86
9
|
export const PLAN_REVIEW_STATES = ['needs_review'];
|
|
87
|
-
/** status 回退表:审批/提问 park(**只有 `suspended`**)。 */
|
|
88
10
|
export const ASK_PARK_STATES = ['suspended'];
|
|
89
|
-
/**
|
|
90
|
-
* 走三选卡臂的状态 —— **只有 `running`**(引擎 RunStatus 里「正在干活」的那一个词)。
|
|
91
|
-
*
|
|
92
|
-
* 🔴 为什么是白名单而不是「park 词表之外的一切」:三选卡上的两条动作路(steer / cancel)都只对
|
|
93
|
-
* 一条**真在跑**的 run 成立 —— steer 的语义是「注入在跑的那一轮」(非 running 会落 `queued` /
|
|
94
|
-
* `parked_for_wake` / 409),cancel 对 park 态则是替用户否掉待决项(禁区)。今天还不存在的新状态
|
|
95
|
-
* 落在表外 ⇒ 照旧「不动它 + 如实说」:不确定时不给用户递一把语义不明的把手。
|
|
96
|
-
*/
|
|
97
11
|
export const RUNNING_STATES = ['running'];
|
|
98
|
-
/**
|
|
99
|
-
* 「引擎确认这条 run **不再占着会话**」的状态词 —— 也就是 cancel 之后允许重发那条消息的**唯一**
|
|
100
|
-
* 判据。
|
|
101
|
-
*
|
|
102
|
-
* 🔴 为什么是白名单而不是「running 之外的一切」:这条会话锁的语义是 **park 态(`suspended` /
|
|
103
|
-
* `needs_review`)保留 claim**。用 `!RUNNING_STATES.includes(status)` 当判据时,一条正在收尾的 run
|
|
104
|
-
* 只要在这一拍被读成 park(cancel 是异步的,run 完全可能先走到一个 checkpoint),就被判成「已释放」
|
|
105
|
-
* ⇒ 上屏说「引擎确认它不再占着这个会话」(假话)+ 立刻重发 ⇒ 那条消息一头撞进还锁着的会话,再吃
|
|
106
|
-
* 一个 409。没想过的新状态词同理落在白名单外:我们不知道那个词是不是「释放了」,就不能替引擎下这个
|
|
107
|
-
* 断言(等到点如实说「还占着」是可收敛的诚实结局,零破坏性)。
|
|
108
|
-
*
|
|
109
|
-
* 词表锚 = SDK `RunStatus`(`running|completed|failed|suspended|needs_review|blocked`)的三个终态
|
|
110
|
-
* + `timeout`。真·404(引擎不认得这条 run 了)另有一条腿,不走这里(见 {@link waitForClaimRelease})。
|
|
111
|
-
*/
|
|
112
12
|
export const CLAIM_RELEASED_STATES = ['completed', 'failed', 'blocked', 'timeout'];
|
|
113
|
-
/**
|
|
114
|
-
* 「这条 run **确实还占着**会话」的状态词 —— 也就是文案敢说「that run still held this session」的
|
|
115
|
-
* **唯一**判据(二次评审 R5 [medium])。
|
|
116
|
-
*
|
|
117
|
-
* 🔴 为什么又是白名单:上面那张表管的是「敢不敢重发」,这张表管的是「敢不敢断言它还占着」,两件事
|
|
118
|
-
* **都**只能由正面证据得出,而它们**不是互补的** —— 两张表之外还有一整片「读到了一个我们不认识的
|
|
119
|
-
* 词」的地带(server 加一个新终态 `cancelled` 就是现成的例子)。用「lastStatus 非空」当持锁判据,
|
|
120
|
-
* 那个新词会被读成「还占着」并渲上屏,而会话其实早就释放了。表外非空词 ⇒ 既不算释放也不算持锁,
|
|
121
|
-
* 收口成「确认不了」。
|
|
122
|
-
*/
|
|
123
13
|
export const CLAIM_HELD_STATES = ['running', 'suspended', 'needs_review'];
|
|
124
|
-
/**
|
|
125
|
-
* 🔴 判据锚在「**有没有一张现在就能答、答了就放行的卡**」这个决定量上,不是锚在「是不是 park
|
|
126
|
-
* 态」这个前置条件上([anchor-on-the-deciding-quantity])。所以三张 reopen-failed / not-parked /
|
|
127
|
-
* state-unknown 全部落 `not-delivered`:它们同样是 park,但卡没能呈到用户面前,把注入件放回队列
|
|
128
|
-
* 只会在下一拍再撞一次同样的 409。`running-not-found`(幽灵 claim)同落 `not-delivered`:
|
|
129
|
-
* 「已解锁」证不出,自动重投可能撞回仍锁着的会话。
|
|
130
|
-
*/
|
|
131
14
|
export function selfHealSubmissionDisposition(outcome) {
|
|
132
15
|
switch (outcome.kind) {
|
|
133
16
|
case 'decision-pending':
|
|
134
17
|
return 'held-for-decision';
|
|
135
18
|
case 'ask-reopened':
|
|
136
19
|
case 'plan-review-reopened':
|
|
137
|
-
// 重开成功 = 卡真的呈上去了(`ReopenCardVerdict` 带宿主呈现回执,呈不出来会降级成
|
|
138
|
-
// reopen-failed 那一臂)⇒ 用户手上确有把手。
|
|
139
20
|
return 'held-for-decision';
|
|
140
21
|
case 'running-cancelled':
|
|
141
22
|
case 'stale-park-cancelled':
|
|
142
|
-
// L-93:与 running-cancelled 同一条重发腿 —— 前提也同一个(引擎**确认**那条 run 不再占着会话,
|
|
143
|
-
// 判据 = CLAIM_RELEASED_STATES;确认不到的两形落 timeout/failed ⇒ default 的 not-delivered)。
|
|
144
23
|
case 'ask-decided-without-card':
|
|
145
24
|
return 'resending';
|
|
146
25
|
case 'ask-decided-release-unknown':
|
|
147
|
-
// L-80(对抗复审 r4 [high]③):决断成功、释放未确认。**没有**释放驱动的重投机制就不许承诺「稍后投递」——
|
|
148
|
-
// 注入件按既有 not-delivered 处置(端:归因上屏 + 问一次待决队列,不回灌不盲发);用户消息文案说「再发一次」。
|
|
149
26
|
return 'not-delivered';
|
|
150
27
|
case 'running-steered':
|
|
151
28
|
return 'handed-off';
|
|
@@ -153,7 +30,6 @@ export function selfHealSubmissionDisposition(outcome) {
|
|
|
153
30
|
return 'not-delivered';
|
|
154
31
|
}
|
|
155
32
|
}
|
|
156
|
-
/** 结局 → 续听意图;非 `running-steered` 结局(含非对象入参)⇒ `null`。纯函数,不改 {@link SelfHealOutcome} 型面。 */
|
|
157
33
|
export function steerFollowIntent(outcome) {
|
|
158
34
|
if (typeof outcome !== 'object' || outcome === null || outcome.kind !== 'running-steered')
|
|
159
35
|
return null;
|
|
@@ -164,13 +40,10 @@ export function steerFollowIntent(outcome) {
|
|
|
164
40
|
return { kind: 'after-wake', taskId };
|
|
165
41
|
return { kind: 'tail', taskId };
|
|
166
42
|
}
|
|
167
|
-
/** 会话作用域位的透传口(缺席即不置键 —— `exactOptionalPropertyTypes` 下 `{session: undefined}`
|
|
168
|
-
* 与「没有这个键」不是一回事,而 SDK 那一侧读的正是「在不在场」)。 */
|
|
169
43
|
function sessionOpts(deps) {
|
|
170
44
|
const sessionId = deps?.sessionId;
|
|
171
45
|
return typeof sessionId === 'string' && sessionId.length > 0 ? { session: sessionId } : {};
|
|
172
46
|
}
|
|
173
|
-
/** 失败细节:只取 message 且截断 —— 错误对象整体 stringify 可能把请求上下文一起带上屏。 */
|
|
174
47
|
function describeFailure(e) {
|
|
175
48
|
if (typeof e === 'object' && e !== null && 'message' in e) {
|
|
176
49
|
const m = e.message;
|
|
@@ -179,28 +52,10 @@ function describeFailure(e) {
|
|
|
179
52
|
}
|
|
180
53
|
return 'the engine gave no detail';
|
|
181
54
|
}
|
|
182
|
-
/** run 记录里读 status —— 只认非空字符串,别的一律当「读不到」(诚实缺席不编造)。 */
|
|
183
55
|
function readStatus(record) {
|
|
184
56
|
const st = record?.status;
|
|
185
57
|
return typeof st === 'string' && st.length > 0 ? st : null;
|
|
186
58
|
}
|
|
187
|
-
/**
|
|
188
|
-
* 「这条 run 还占着会话吗」的**直接读数**(0.68.0 / L-230;server 7.73.0 S-122 P-45:
|
|
189
|
-
* `GET /v1/runs/:id` 上的 `heldBy`)。
|
|
190
|
-
*
|
|
191
|
-
* 三态,**刻意不是布尔**:
|
|
192
|
-
* · `'held'` —— `heldBy` 是一个非空串 = **有一条 run 名字在上面**,它确实还占着;
|
|
193
|
-
* · `'released'` —— `heldBy` 显式是 `null` = 一句**正面事实**:会话已经交出来了;
|
|
194
|
-
* · `'unknown'` —— 这一位**整键缺席**(老引擎 / 不发这一位的部署),或者形不对
|
|
195
|
-
* (非串非 null)⇒ 读不出,**绝不**当成 released。
|
|
196
|
-
*
|
|
197
|
-
* 🔴 `null` 与「键不在」必须分开,这是本读器存在的全部理由:
|
|
198
|
-
* 把两者都读成「释放了」,老引擎上每一次探测都会当场答「已释放」并立刻重发那条消息 ——
|
|
199
|
-
* 而它可能一头撞回一个还锁着的会话(再吃一个 409,正是这条腿存在的理由);
|
|
200
|
-
* 把两者都读成「还占着」,新引擎上那句正面事实白给了,窗要一直等到点。
|
|
201
|
-
* 🔴 判据用 `Object.hasOwn`:`{heldBy: undefined}` 与 `{}` 在这里是**同一件事**(都读不出),
|
|
202
|
-
* 而 `null` 必须与它们分开 —— 靠 `?? ` / `!= null` 一类写法分不出来。
|
|
203
|
-
*/
|
|
204
59
|
function readClaimHolder(record) {
|
|
205
60
|
if (record === null || typeof record !== 'object')
|
|
206
61
|
return 'unknown';
|
|
@@ -211,18 +66,8 @@ function readClaimHolder(record) {
|
|
|
211
66
|
return 'released';
|
|
212
67
|
if (typeof v === 'string' && v.length > 0)
|
|
213
68
|
return 'held';
|
|
214
|
-
// 空串 / 数 / 对象:形不对 ⇒ 读不出(失效方向在保守那一侧:不宣告释放)。
|
|
215
69
|
return 'unknown';
|
|
216
70
|
}
|
|
217
|
-
/**
|
|
218
|
-
* cancel 回执(202 体)/ run 行上的 `cancelRequested`(0.68.0 / L-230;server 7.73.0 S-122 P-45)。
|
|
219
|
-
*
|
|
220
|
-
* 🔴 **三态**同上:`true` / `false` 都是真读数,**读不出 ⇒ `undefined`**(老引擎不发这一位)。
|
|
221
|
-
* 绝不折 `false` —— 「没请求过取消」与「不知道有没有请求过」对下一步的含义不同:前者可以放心
|
|
222
|
-
* 再发一次 cancel,后者再发就可能是第二枪。
|
|
223
|
-
* 🔴 它**不是**「已经取消了」:上游逐字是 *requested*,一次**已受理的请求**,不是终局。
|
|
224
|
-
* 会话有没有交出来仍然只由 {@link readClaimHolder} 与 {@link CLAIM_RELEASED_STATES} 回答。
|
|
225
|
-
*/
|
|
226
71
|
export function readCancelRequested(body) {
|
|
227
72
|
if (typeof body !== 'object' || body === null || Array.isArray(body))
|
|
228
73
|
return undefined;
|
|
@@ -231,77 +76,35 @@ export function readCancelRequested(body) {
|
|
|
231
76
|
const v = body.cancelRequested;
|
|
232
77
|
return typeof v === 'boolean' ? v : undefined;
|
|
233
78
|
}
|
|
234
|
-
/** 重开判决 → 结局(真呈现回执消费点:`presented === false` 时 reopened 断言不成立)。 */
|
|
235
79
|
function reopenDelivered(verdict) {
|
|
236
80
|
return verdict.reopened === true && verdict.presented !== false;
|
|
237
81
|
}
|
|
238
|
-
/**
|
|
239
|
-
* 一次**至多一次**(non-idempotent)POST 失败之后:到底是「服务端明确拒了」还是「不知道有没有
|
|
240
|
-
* 落地」。steer 与 cancel 两条腿共用这一把尺 —— 它们同属「这一枪不能盲发第二次」的族,而两类
|
|
241
|
-
* 失败对用户的处置**完全不同**,各写一份必漂。
|
|
242
|
-
*
|
|
243
|
-
* 判据 = HTTP 状态码在不在、是不是 4xx:**4xx = 服务端收到了、解析了、明确拒绝**(steering 族的
|
|
244
|
-
* 拒收码全在 4xx:422 invalid_content / 409 not_running|queue_full|duplicate_input_id / 404 / 413),
|
|
245
|
-
* 那一枪确定没有产生效果;5xx 与「压根没有状态码」(fetch failed / 连接断 / 超时)都是**送达未知**
|
|
246
|
-
* —— 请求可能已经被受理,只是回执没回来。
|
|
247
|
-
* 🔴 方向:不确定时归 `unknown`(保守面在「不对用户断言一件证不出的事」这一侧)。
|
|
248
|
-
*/
|
|
249
82
|
export function atMostOnceFailureClass(e) {
|
|
250
83
|
const status = e?.status;
|
|
251
84
|
return typeof status === 'number' && status >= 400 && status < 500 ? 'rejected' : 'unknown';
|
|
252
85
|
}
|
|
253
|
-
/** SDK `SteerReceipt.delivery`(`applied` / `queued` / `parked_for_wake`)。读不出 = null —— 不编造
|
|
254
|
-
* 一个投递语义:三种 delivery 对用户的意思完全不同,猜错就是假承诺。 */
|
|
255
86
|
export function readSteerDelivery(receipt) {
|
|
256
87
|
if (typeof receipt !== 'object' || receipt === null || !('delivery' in receipt))
|
|
257
88
|
return null;
|
|
258
89
|
const d = receipt.delivery;
|
|
259
90
|
return typeof d === 'string' && d.length > 0 ? d : null;
|
|
260
91
|
}
|
|
261
|
-
/** SDK `SteerReceipt.status` —— 服务端在投递那一刻读到的**行状态词**(`running` /「park 词」/终态)。
|
|
262
|
-
* `delivery:'queued'` 时它是**唯一**能分出「在等哪一类门」的材料(契约逐字:`"suspended"` OR
|
|
263
|
-
* `"needs_review"`),读不出即 null(不猜一个门种)。 */
|
|
264
92
|
export function readSteerReceiptStatus(receipt) {
|
|
265
93
|
if (typeof receipt !== 'object' || receipt === null || !('status' in receipt))
|
|
266
94
|
return null;
|
|
267
95
|
const s = receipt.status;
|
|
268
96
|
return typeof s === 'string' && s.length > 0 ? s : null;
|
|
269
97
|
}
|
|
270
|
-
/** cancel 之后**有界**等那条 run 交出会话的缺省窗(`POST …/cancel` 是 202 异步 —— 收下 ≠ 已停)。 */
|
|
271
98
|
const CANCEL_RELEASE_WAIT_MS = 10_000;
|
|
272
|
-
/** 出卡前存活对账那一发 `runs.get` 的等待上界(Inkglow-1085 P0b②)——与假死锁复核读口同款
|
|
273
|
-
* 「慢网也回得来」的诚实预算;窗尽/传输错 = 读不到 ⇒ 保守维持出卡(见 runningChoiceArm 注)。 */
|
|
274
99
|
const RUNNING_LIVENESS_RECHECK_TIMEOUT_MS = 4_000;
|
|
275
|
-
// ── running 三选卡的「已呈现且用户选了 Do nothing」登记(Inkglow-1085 P0b①,2026-08-19)────────
|
|
276
|
-
//
|
|
277
|
-
// 病形(案卷车2 发现2):同一条幽灵/长跑 run 占着会话时,用户每发一条消息就被整卡打断一次 ——
|
|
278
|
-
// 呈现台账只有 per-call 局部量(offeringByTask / runningChoiceOffered),没有任何跨 turn 的
|
|
279
|
-
// 「用户已对 run X 说过『别动它』」记账。本登记 = 模块级 (sessionKey,taskId) 集合:
|
|
280
|
-
// · 只在**卡真呈现过且用户选了 wait** 时登记(呈卡腿抛错那次不登记 —— 用户没看到卡,压掉后续
|
|
281
|
-
// 等于把没呈过的卡当已呈);
|
|
282
|
-
// · 登记在场 ⇒ 同 (sessionKey,taskId) 再撞 409 不再整卡重弹,结局带 `alreadyOffered: true`
|
|
283
|
-
// 判别位(端据此降级渲一行;默认文案层已给降级行,含 cancel 端点与 wayOut 两条真出路);
|
|
284
|
-
// · 用户选 steer/cancel ⇒ 清登记(他重新对这条 run 表了态);宿主的「重新打开操作菜单」入口
|
|
285
|
-
// 走 {@link clearRunningChoiceOffer}。
|
|
286
|
-
// run 身份变化天然换键(taskId 不同);同 run 状态迁移到 park 态会走别的臂,登记不拦。
|
|
287
100
|
const runningChoiceDeclined = new Set();
|
|
288
|
-
/** 登记表上限(对抗复审轻形采纳):超限 FIFO 驱逐最老条目 —— 驱逐代价 = 卡重弹一次(修前行为)。 */
|
|
289
101
|
const RUNNING_CHOICE_LEDGER_CAP = 512;
|
|
290
102
|
function runningChoiceDeclineKey(sessionId, taskId) {
|
|
291
|
-
// NUL 分隔:两段都是外来串,可打印分隔符在「sessionId 里恰有它」时会串键。
|
|
292
103
|
return `${sessionId ?? ''}\u0000${taskId}`;
|
|
293
104
|
}
|
|
294
|
-
/**
|
|
295
|
-
* L-93 两选卡(陈旧 park)的登记键 —— 与三选卡**同一张表、不同键**。
|
|
296
|
-
* 🔴 为什么不共用同一个键:两张卡问的是不同的问题(「这条**在跑**的 run 怎么办」vs「这条 park 住、
|
|
297
|
-
* 而审批已经**不在引擎上**的 run 怎么办」),用户对其中一张说过「什么都不做」不构成对另一张的表态
|
|
298
|
-
* —— 共键会让一次旧表态把一张他从没见过的卡压掉。共用一张表只为共用那条 FIFO 上界与同一个清口。
|
|
299
|
-
*/
|
|
300
105
|
function staleParkDeclineKey(sessionId, taskId) {
|
|
301
106
|
return `${runningChoiceDeclineKey(sessionId, taskId)}\u0000stale-park`;
|
|
302
107
|
}
|
|
303
|
-
/** 登记一条「用户选了什么都不做」+ FIFO 有界驱逐(两张卡共用 —— 各写一份必漂,而漂的后果是
|
|
304
|
-
* 其中一张卡的登记表悄悄变成无界)。驱逐代价 = 那条 run 的卡重弹一次(= 修前行为,方向安全)。 */
|
|
305
108
|
function rememberDeclinedOffer(key) {
|
|
306
109
|
runningChoiceDeclined.add(key);
|
|
307
110
|
if (runningChoiceDeclined.size > RUNNING_CHOICE_LEDGER_CAP) {
|
|
@@ -310,44 +113,19 @@ function rememberDeclinedOffer(key) {
|
|
|
310
113
|
runningChoiceDeclined.delete(oldest);
|
|
311
114
|
}
|
|
312
115
|
}
|
|
313
|
-
/** 清掉某条 run 的「Do nothing」登记 —— 宿主「重新打开操作菜单」入口(下一次 409 重新整卡呈现)。
|
|
314
|
-
* `sessionId` 与当时喂给 {@link ActiveRunSelfHealDeps.sessionId} 的值同源(缺席 = 默认键)。
|
|
315
|
-
* 🔴 三选卡与两选卡(L-93)的登记**一起清**:这个入口的语义是「让我重新对这条 run 表态」,
|
|
316
|
-
* 只清其中一张会让另一张在宿主已经答应「重新问」之后仍旧被压掉(paired-mechanisms 同族)。 */
|
|
317
116
|
export function clearRunningChoiceOffer(taskId, sessionId) {
|
|
318
117
|
runningChoiceDeclined.delete(runningChoiceDeclineKey(sessionId, taskId));
|
|
319
118
|
runningChoiceDeclined.delete(staleParkDeclineKey(sessionId, taskId));
|
|
320
119
|
}
|
|
321
|
-
/** 测试钩:清空整张登记表(套件各组之间隔离用)。 */
|
|
322
120
|
export function __resetRunningChoiceLedgerForTests() {
|
|
323
121
|
runningChoiceDeclined.clear();
|
|
324
122
|
}
|
|
325
|
-
/** 假死锁复核读口(`deps.listOwnedPendingApprovals`)的等待上界:一发 approvals 列表读,4s 是
|
|
326
|
-
* 「慢网也回得来」的诚实预算;窗尽=分不出真卡与幽灵 ⇒ 保守 stand down(见调用点注)。 */
|
|
327
123
|
const OWNED_PENDING_RECHECK_TIMEOUT_MS = 4_000;
|
|
328
|
-
/** 轮询退避:200ms 起、×1.5、封顶 2s(短退避,别把一次 cancel 打成连发)。 */
|
|
329
124
|
const CANCEL_POLL_START_MS = 200;
|
|
330
125
|
const CANCEL_POLL_MAX_MS = 2_000;
|
|
331
|
-
/** 缺省退避等待 = 包内共享等待叶(中止即刻 settle 并清 timer;timer **不 unref** —— 等待窗里它
|
|
332
|
-
* 可能是事件循环里唯一的活,unref 会让 `-p` 车道在中途直接退出,连那句诚实的收口行都一起蒸发)。
|
|
333
|
-
* 🔴 就地再写一份「怎么安全地睡一觉」= 第三份中止语义,只会各自漂;无 signal 的调用方给一只
|
|
334
|
-
* 永不 abort 的 signal,照样走同一个叶。 */
|
|
335
126
|
function claimPollSleep(ms, signal) {
|
|
336
127
|
return abortableSleep(ms, signal ?? new AbortController().signal);
|
|
337
128
|
}
|
|
338
|
-
/**
|
|
339
|
-
* 这一发出站请求的中止口:**只有真把一个截止 signal 交到请求上,墙钟才是个闸** —— 只在下一轮循环
|
|
340
|
-
* 开头查 deadline 是事后统计,一发挂住的 `runs.get`/`runs.cancel` 会跑满 SDK 缺省超时(且 GET 属
|
|
341
|
-
* 可重试类,实际上界是它的数倍),预算形同虚设。合并调用方 signal(用户 Esc)⇒ Esc 之后这一发也
|
|
342
|
-
* 当场收,零后续出站。
|
|
343
|
-
*
|
|
344
|
-
* 🔴 **不用 `AbortSignal.timeout`**(二次评审 R6 实测逼出):它的内部定时器在 Node 上是 **unref 的**
|
|
345
|
-
* —— 等待窗里这只定时器可能是事件循环里唯一的活,那种情况下进程会在截止到点之前直接退出,连那句
|
|
346
|
-
* 诚实的收口行都一起蒸发(仓内 `abortableSleep` / 重查环同一条判据)。这里自己持一只**普通**定时器,
|
|
347
|
-
* 并把清理做成 `release()` 的义务:不清就会把进程按预算多拖活一拍。
|
|
348
|
-
* 🔴 合并也自己做,不走 `AbortSignal.any`:少一条「宿主运行期有没有这个静态面」的分支,行为在所有
|
|
349
|
-
* 宿主上逐字相同。
|
|
350
|
-
*/
|
|
351
129
|
function claimProbeLease(caller, remainingMs) {
|
|
352
130
|
const ctl = new AbortController();
|
|
353
131
|
const timer = setTimeout(() => {
|
|
@@ -368,36 +146,16 @@ function claimProbeLease(caller, remainingMs) {
|
|
|
368
146
|
},
|
|
369
147
|
};
|
|
370
148
|
}
|
|
371
|
-
/**
|
|
372
|
-
* 等到那条 run 不再占着会话 ⇒ `released`;窗口到点仍占着 / 读不到 / 调用方中止 ⇒ 不 released
|
|
373
|
-
* (如实说,**绝不重发**那条被拒的消息)。
|
|
374
|
-
*
|
|
375
|
-
* 🔴 判据锚在**决定量**本身:`runs.get` 读回的 status 在不在 {@link CLAIM_RELEASED_STATES} 白名单里
|
|
376
|
-
* (而不是「cancel 回了 202」那个前置条件,也不是「不是 running」那个更弱的反面)。
|
|
377
|
-
* 🔴 读失败(传输错)**不**当成「释放了」:那是「不知道」,而把不知道当成释放 = 把消息喂进一个可能
|
|
378
|
-
* 还锁着的会话(下一次 409 就是它的代价)。只有 404(引擎不认得这条 run 了)才算释放 —— 那种情况
|
|
379
|
-
* 下它连 claim 都不可能还占着。
|
|
380
|
-
* 🔴 有界性由**每一发都带截止 signal**保证(见 {@link claimProbeSignal}),不是靠循环顶那一次减法。
|
|
381
|
-
*/
|
|
382
149
|
export async function waitForClaimRelease(taskId, deps) {
|
|
383
150
|
const now = typeof deps.now === 'function' ? deps.now : () => Date.now();
|
|
384
151
|
const sleep = typeof deps.sleep === 'function' ? deps.sleep : claimPollSleep;
|
|
385
|
-
// 🔴 预算先规范化成**有限非负**数(二次评审 R6 [medium]):`NaN` 会让 `deadline`/`remaining` 全是
|
|
386
|
-
// NaN,`remaining <= 0` 恒假 ⇒ 环永不收口(而每一拍的 `AbortSignal.timeout(NaN)` 抛错又被 catch
|
|
387
|
-
// 吞掉,退化成零延时空转);`Infinity` 同样到不了截止条件。预算是从 env/配置解析来的量,坏值
|
|
388
|
-
// 必须在入口就被读成「不等」,而不是把 turn 卡死。
|
|
389
152
|
const budgetMs = Number.isFinite(deps.budgetMs) && deps.budgetMs > 0 ? deps.budgetMs : 0;
|
|
390
153
|
const startedAt = now();
|
|
391
154
|
const deadline = startedAt + budgetMs;
|
|
392
155
|
const waited = () => now() - startedAt;
|
|
393
|
-
// 🔴 经函数读,别把 `signal?.aborted` 直接写在多处条件里:`AbortSignal.aborted` 在类型面是只读
|
|
394
|
-
// 属性,tsc 的控制流分析会在第一次判过之后把它**窄成 `false | undefined`**,后面每一次重判都被
|
|
395
|
-
// 判成「不可能成立的比较」。而这个量恰恰**会在两次 await 之间变** —— 要终止一个循环,动手的那
|
|
396
|
-
// 一层必须在每一轮里重新求值。函数返回值走声明类型,不进那条窄化链。
|
|
397
156
|
const isAborted = () => deps.signal?.aborted === true;
|
|
398
157
|
let delay = CANCEL_POLL_START_MS;
|
|
399
158
|
let lastStatus = null;
|
|
400
|
-
// 🔴 0.68.0 / L-230:`heldBy` 的三态与 `lastStatus` **同律** —— 只记最近一次**有回答**的探测。
|
|
401
159
|
let lastHolder = 'unknown';
|
|
402
160
|
for (;;) {
|
|
403
161
|
if (isAborted())
|
|
@@ -424,58 +182,23 @@ export async function waitForClaimRelease(taskId, deps) {
|
|
|
424
182
|
lease.release();
|
|
425
183
|
if (isAborted())
|
|
426
184
|
return { released: false, waitedMs: waited(), aborted: true, lastStatus, lastHolder };
|
|
427
|
-
// 🔴 本发是被**窗自己的截止**掐掉的(lease 到点;caller 未中止)⇒ 没有读到任何回答 ——
|
|
428
|
-
// 既不是释放证据,也不是「读不出来」的新证据,上一发**完成了的**读数保持在座(#244 F1
|
|
429
|
-
// 换装批 G-c 尾竞态定谳:R2 [medium] 的「只记最近一次」指最近一次**有回答**的探测 ——
|
|
430
|
-
// 404/传输错是回答了「不知道」,截止掐断连回答都没有;不区分则 confirmedHeld 在窗尾随机
|
|
431
|
-
// 翻 false,同输入两跑两种文案)。回循环顶按墙钟收口。
|
|
432
185
|
if (lease.signal.aborted)
|
|
433
186
|
continue;
|
|
434
|
-
// 这一发**有回答但读不出**(404/传输错)⇒ 最新证据是「不知道」,陈旧的成功读数当场作废
|
|
435
|
-
// (见 lastStatus 头注)。
|
|
436
187
|
lastStatus = null;
|
|
437
188
|
lastHolder = 'unknown';
|
|
438
|
-
// 🔴 **404 也只是「读不到」,不是「释放了」**(二次评审 [high] 处置,2026-08-14):这个读口的
|
|
439
|
-
// 404 在 SDK 契约上是 `not_found.run` —— 「这条 run 不存在」与「它不是你的」**共用同一个码,
|
|
440
|
-
// 且是刻意设计的不可分辨**(跨租户不给存在性预言机)。凭它断言「那条 run 已经不占着会话」,
|
|
441
|
-
// 就是拿一个缺席信号当正面证据 —— 与本批到处在修的那个错同形。误判的代价也不对称:说成
|
|
442
|
-
// 「已释放」会让调用方立刻重发,而那条消息可能一头撞回还锁着的会话(再吃一个 409,正是这条腿
|
|
443
|
-
// 存在的理由);说成「读不出来」只是让用户自己再发一次。其余错误(传输错/本发的截止中止)同理。
|
|
444
189
|
continue;
|
|
445
190
|
}
|
|
446
|
-
lease.release();
|
|
447
|
-
lastStatus = status;
|
|
191
|
+
lease.release();
|
|
192
|
+
lastStatus = status;
|
|
448
193
|
lastHolder = holder;
|
|
449
|
-
// ── 🔴 0.68.0 / L-230(server 7.73.0 S-122 P-45 读面消费)—— **两条释放证据,取并** ────────
|
|
450
|
-
// ① `heldBy === null` —— 引擎**直说**会话交出来了。这是本批新接的那条,而且它比 ② 强:
|
|
451
|
-
// ② 是拿「run 走到了某个终态词」去**推断**锁没了,而锁与 run 的生命周期不是同一件事
|
|
452
|
-
// (park 态保留 claim 正是这条推断会踩的坑,也是 `CLAIM_RELEASED_STATES` 是白名单而不是
|
|
453
|
-
// 「running 之外的一切」的原因)。有直接证据就别再推断。
|
|
454
|
-
// ② `status ∈ CLAIM_RELEASED_STATES` —— 老引擎不发 `heldBy`(读出 `'unknown'`)时的**既有路**,
|
|
455
|
-
// 一字未动。⇒ 本件对老引擎**零行为变化**;对新引擎多了一条更早、更硬的收口证据。
|
|
456
|
-
// 🔴 `'unknown'` 绝不当成释放:缺席不是证据(与本文件 404 那一格逐字同一条纪律)。
|
|
457
194
|
if (holder === 'released') {
|
|
458
195
|
return { released: true, waitedMs: waited(), aborted: false, lastStatus: status, lastHolder: holder };
|
|
459
196
|
}
|
|
460
|
-
// status 读不出(奇形记录)/ 还在跑 / park / 没想过的新词 ⇒ 都不算释放,继续等到点(保守方向:
|
|
461
|
-
// 宁可不重发)。判据见 {@link CLAIM_RELEASED_STATES}。
|
|
462
197
|
if (status !== null && CLAIM_RELEASED_STATES.includes(status)) {
|
|
463
198
|
return { released: true, waitedMs: waited(), aborted: false, lastStatus: status, lastHolder: holder };
|
|
464
199
|
}
|
|
465
200
|
}
|
|
466
201
|
}
|
|
467
|
-
/**
|
|
468
|
-
* 有界问一次「引擎侧这个会话名下还有几行**属主**待决审批」(`deps.listOwnedPendingApprovals`)。
|
|
469
|
-
*
|
|
470
|
-
* 返回 `number` = 引擎**答了**这个数;返回 `null` = **没答上来**(抛错 / 窗尽 / 调用方中止)——
|
|
471
|
-
* 两者是完全不同的事实,调用方各自决定怎么用(本文件两个调用点都把 `null` 当「不知道」保守处置)。
|
|
472
|
-
*
|
|
473
|
-
* 🔴 有界且可回收(#244 F1 复审 [high]+[medium]×2 的成文形):probe 恒不落地不许把整棵分诊树挂死;
|
|
474
|
-
* 合流 signal **真交到 probe 上**(掐的是底层请求,不是只掐调用方的 await);竞速败方在 finally 里
|
|
475
|
-
* 统一 abort(快成功时那只 4s 普通计时器不清,`-p` 车道进程会被它多拖活一窗)。
|
|
476
|
-
* 🔴 单源:两个调用点(假死锁防御 / L-93 开枪前复证)共用这一只 —— 各写一份必漂,而这两处漂的
|
|
477
|
-
* 后果分别是「Esc 之后接着弹卡」与「拿一次没答上来的读当成前提成立」。
|
|
478
|
-
*/
|
|
479
202
|
async function readOwnedPendingCount(read, deps) {
|
|
480
203
|
const recheckCtl = new AbortController();
|
|
481
204
|
const onCallerAbort = () => {
|
|
@@ -487,47 +210,28 @@ async function readOwnedPendingCount(read, deps) {
|
|
|
487
210
|
read({ signal: recheckCtl.signal }),
|
|
488
211
|
claimPollSleep(OWNED_PENDING_RECHECK_TIMEOUT_MS, recheckCtl.signal).then(() => null),
|
|
489
212
|
]);
|
|
490
|
-
// 已中止(进门前/竞速期间)一律不采信这个数 —— Esc 之后拿它驱动任何动作都是「用户说停还在动」。
|
|
491
213
|
if (deps?.signal?.aborted === true)
|
|
492
214
|
return null;
|
|
493
215
|
return typeof count === 'number' ? count : null;
|
|
494
216
|
}
|
|
495
217
|
catch {
|
|
496
|
-
return null;
|
|
218
|
+
return null;
|
|
497
219
|
}
|
|
498
220
|
finally {
|
|
499
221
|
deps?.signal?.removeEventListener('abort', onCallerAbort);
|
|
500
222
|
recheckCtl.abort(new Error('owned-pending recheck settled'));
|
|
501
223
|
}
|
|
502
224
|
}
|
|
503
|
-
/**
|
|
504
|
-
* 自愈一次:定门(kind 优先 → status 表回退)→ 按门类重开对应的卡 → 回报结局。**绝不抛**
|
|
505
|
-
* (turn 收尾路径)。kind 在场时不再回查 runs.get —— wire 已指名门身份,省一次往返。
|
|
506
|
-
*/
|
|
507
225
|
export async function attemptActiveRunSelfHeal(signal, runs, deps) {
|
|
508
|
-
// 🔴 最先问的一句:用户手上有没有一张能按的卡?有就把决定权还给他(见 decision-pending 注)。
|
|
509
226
|
let pending = false;
|
|
510
227
|
try {
|
|
511
228
|
pending = deps?.hasPendingDecision?.() === true;
|
|
512
229
|
}
|
|
513
230
|
catch {
|
|
514
|
-
pending = false;
|
|
231
|
+
pending = false;
|
|
515
232
|
}
|
|
516
|
-
// 🔴 经函数读(waitForClaimRelease 的 isAborted 同注):`aborted` 会在 await 两侧变,直接读两次
|
|
517
|
-
// 会被 tsc 的控制流分析把第二次窄成恒假比较(TS2367)。
|
|
518
233
|
const callerAborted = () => deps?.signal?.aborted === true;
|
|
519
234
|
if (pending && typeof deps?.listOwnedPendingApprovals === 'function' && !callerAborted()) {
|
|
520
|
-
// [3892]-[3899] 假死锁防御(deps.listOwnedPendingApprovals 头注全案):镜像说有卡 ⇒ 向引擎
|
|
521
|
-
// 复核一次属主 pending 行数。恰 0 = 镜像脏值的**正面证据**(引擎侧没有任何等这个用户的决定)
|
|
522
|
-
// ⇒ 放行真分诊;抛错/非 0 ⇒ 保守维持(真卡在屏时 stand down 是对的)。
|
|
523
|
-
// 🔴 复核必须**有界且可回收**(复审 #244 F1 轮一 [high] + 轮二 [medium]×2):
|
|
524
|
-
// · probe 恒不落地(引擎挂死且读口无自带超时)不许把整棵分诊树挂死 —— 假死锁防御自己变成
|
|
525
|
-
// 真死锁;窗尽/调用方中止 = 分不出真卡与幽灵,与抛错同臂保守 stand down;
|
|
526
|
-
// · 合流 signal **真交到 probe 上**(超时/中止掐的是底层请求,不是只掐调用方的 await);
|
|
527
|
-
// · 竞速败方必须收口:快成功时 4s sleep 的普通计时器不清,-p 车道进程会被它多拖活一窗
|
|
528
|
-
// (finally 统一 abort ⇒ abortableSleep 清 timer、在飞 probe 被掐);
|
|
529
|
-
// · 已中止(进门前/竞速期间)一律不采信 count —— Esc 之后接着放行重呈 = 用户说停还弹卡。
|
|
530
|
-
// 这四条的实现在共享叶 {@link readOwnedPendingCount}(L-93 开枪前复证走同一只)。
|
|
531
235
|
const count = await readOwnedPendingCount(deps.listOwnedPendingApprovals, deps);
|
|
532
236
|
if (count === 0 && !callerAborted())
|
|
533
237
|
pending = false;
|
|
@@ -542,13 +246,11 @@ export async function attemptActiveRunSelfHeal(signal, runs, deps) {
|
|
|
542
246
|
detail: 'the engine did not name the run that holds this session',
|
|
543
247
|
};
|
|
544
248
|
}
|
|
545
|
-
// ── 一级分诊:pendingGate.kind(wire 指名的门身份;缺席才落 status 表)──────────────────────
|
|
546
249
|
const gateKind = signal.pendingGate?.kind ?? null;
|
|
547
250
|
if (gateKind !== null && PLAN_REVIEW_GATE_KINDS.includes(gateKind))
|
|
548
251
|
return planReviewArm(taskId, signal, deps);
|
|
549
252
|
if (gateKind !== null && ASK_PARK_GATE_KINDS.includes(gateKind))
|
|
550
253
|
return askParkArm(taskId, signal, runs, deps);
|
|
551
|
-
// ── 状态真源两级:wire 直供 > 自己查一趟 runs.get(kind 缺席/表外 kind 的诚实陈述都要它)──
|
|
552
254
|
let status = signal.activeTaskStatus;
|
|
553
255
|
if (status === null) {
|
|
554
256
|
if (typeof runs?.get !== 'function') {
|
|
@@ -566,71 +268,51 @@ export async function attemptActiveRunSelfHeal(signal, runs, deps) {
|
|
|
566
268
|
if (status === null) {
|
|
567
269
|
return { kind: 'state-unknown', taskId, detail: 'the engine reported no status for that run' };
|
|
568
270
|
}
|
|
569
|
-
// 表外 kind(结局④):wire 指名了一个本层不认得的门 —— 按 status 表猜臂正是 kind 优先要禁
|
|
570
|
-
// 的动作(把 dry-run 复核门错路成 plan 卡)。不动它 + 如实说;decidePath 由文案层原样交出。
|
|
571
271
|
if (gateKind !== null)
|
|
572
272
|
return { kind: 'not-parked', taskId, status };
|
|
573
|
-
// ── 二级分诊:status 表回退(kind 缺席 —— 旧引擎 / store 降级)────────────────────────────
|
|
574
273
|
if (PLAN_REVIEW_STATES.includes(status))
|
|
575
274
|
return planReviewArm(taskId, signal, deps);
|
|
576
275
|
if (ASK_PARK_STATES.includes(status))
|
|
577
276
|
return askParkArm(taskId, signal, runs, deps);
|
|
578
|
-
// 真在跑 ⇒ 把引擎给着的两条路(steer / cancel)+ 现状做成三选卡交给用户;表外的其它状态
|
|
579
|
-
// (含今天还不存在的)照旧「不动它 + 如实说」—— 语义不明的状态不配递把手(见 RUNNING_STATES 注)。
|
|
580
|
-
// Inkglow-1085 P0b②:`statusFromWire` = 这个 running 是 409 终帧直供的投影(best-effort,可陈旧
|
|
581
|
-
// ——claim/store 错时恒回送 running 的幽灵读数);出卡前 runningChoiceArm 会对它补一发 runs.get
|
|
582
|
-
// 真查。status 本就是上面刚 get 来的(wire 缺席回退)⇒ 已新鲜,不打第二发。
|
|
583
277
|
if (RUNNING_STATES.includes(status)) {
|
|
584
278
|
return runningChoiceArm(taskId, status, signal, signal.activeTaskStatus !== null, runs, deps);
|
|
585
279
|
}
|
|
586
280
|
return { kind: 'not-parked', taskId, status };
|
|
587
281
|
}
|
|
588
|
-
/** plan_review 重开口的判决(臂与判决分开:steer 的 `queued` 回执要的是**同一份**重开语义的判决值,
|
|
589
|
-
* 但收口成的是 running-steered 那一行,不是 park 臂的行 —— 各写一份必漂)。 */
|
|
590
282
|
async function planVerdict(taskId, deps) {
|
|
591
283
|
try {
|
|
592
284
|
return (await deps?.reopenPlanReview?.(taskId)) ?? { reopened: false };
|
|
593
285
|
}
|
|
594
286
|
catch {
|
|
595
|
-
return { reopened: false };
|
|
287
|
+
return { reopened: false };
|
|
596
288
|
}
|
|
597
289
|
}
|
|
598
|
-
/** ask 重开口的判决(同上)。 */
|
|
599
290
|
async function askVerdict(taskId, deps) {
|
|
600
291
|
try {
|
|
601
292
|
return (await deps?.reopenAskPark?.(taskId)) ?? { reopened: false };
|
|
602
293
|
}
|
|
603
294
|
catch {
|
|
604
|
-
return { reopened: false };
|
|
295
|
+
return { reopened: false };
|
|
605
296
|
}
|
|
606
297
|
}
|
|
607
|
-
/** plan_review 门:重开卡,把决定权还给用户。静默 cancel 在这里 = 替他 reject 掉整个 plan。 */
|
|
608
298
|
async function planReviewArm(taskId, signal, deps) {
|
|
609
299
|
const verdict = await planVerdict(taskId, deps);
|
|
610
300
|
return reopenDelivered(verdict)
|
|
611
301
|
? { kind: 'plan-review-reopened', taskId, firstSight: verdict.firstSight === true }
|
|
612
302
|
: { kind: 'plan-review-reopen-failed', taskId, decidePath: signal.pendingGate?.decidePath ?? null };
|
|
613
303
|
}
|
|
614
|
-
/** 审批/提问门:重开卡。cancel 在这里的语义 = 替用户否掉待决项,且实证是循环病根 —— 臂已退役。 */
|
|
615
304
|
async function askParkArm(taskId, signal, runs, deps) {
|
|
616
305
|
const verdict = await askVerdict(taskId, deps);
|
|
617
306
|
if (reopenDelivered(verdict))
|
|
618
307
|
return { kind: 'ask-reopened', taskId, firstSight: verdict.firstSight === true };
|
|
619
|
-
// L-80:无卡但链已成功决断(规则直决)—— 它只证明「决断受理」,不证明「run 交出了会话」。三分:
|
|
620
|
-
// · 有界等到 run 释放 ⇒ `ask-decided-without-card`(resending,与 running-cancelled 同一条重发腿的前提相同);
|
|
621
|
-
// · 没释放但 run 在跑 ⇒ 走既有 running 三选卡(steer / cancel / wait),绝不盲发;
|
|
622
|
-
// · 没释放且 run 仍 parked / 读不到 ⇒ 如实 reopen-failed(旧文案;plan 腿无此形,规则不决 plan)。
|
|
623
308
|
if (verdict.reopened === false && verdict.decidedWithoutCard === true) {
|
|
624
|
-
// 决断成功是正面事实,**不许**再落 reopen-failed(那句「could not reopen … NOT sent」会掩盖它)。
|
|
625
309
|
if (typeof runs?.get !== 'function')
|
|
626
310
|
return { kind: 'ask-decided-release-unknown', taskId, lastStatus: null };
|
|
627
|
-
// r5 [high]②:真 SDK 的 RunsResource 方法依赖 `this`(与 cancel 路径 `boundGet` 同款保留接收者;裸函数会恒 TypeError
|
|
628
|
-
// 被轮询器吞掉、10s 后错落 release-unknown)。
|
|
629
311
|
const durableGet = runs.get;
|
|
630
312
|
const get = (id, opts) => durableGet.call(runs, id, opts);
|
|
631
313
|
const release = await waitForClaimRelease(taskId, {
|
|
632
314
|
get,
|
|
633
|
-
budgetMs: deps?.cancelReleaseWaitMs ?? CANCEL_RELEASE_WAIT_MS,
|
|
315
|
+
budgetMs: deps?.cancelReleaseWaitMs ?? CANCEL_RELEASE_WAIT_MS,
|
|
634
316
|
...(deps?.signal !== undefined ? { signal: deps.signal } : {}),
|
|
635
317
|
...sessionOpts(deps),
|
|
636
318
|
});
|
|
@@ -641,28 +323,16 @@ async function askParkArm(taskId, signal, runs, deps) {
|
|
|
641
323
|
}
|
|
642
324
|
return { kind: 'ask-decided-release-unknown', taskId, lastStatus: release.lastStatus };
|
|
643
325
|
}
|
|
644
|
-
// L-93:重开链在有界窗内**读面答了**、而属主待决行**恰 0**(引擎重启丢行 / 审批被 reap),run
|
|
645
|
-
// 却仍 park ⇒ 卡不是「呈不出来」而是「没有东西可呈」,走陈旧 park 臂(见 {@link staleParkArm})。
|
|
646
|
-
// 🔴 位缺席/含糊值一律不进(只认严格 `true`):这是本层允许 cancel 一条 park run 的唯一前提,
|
|
647
|
-
// 松掉它就是把退役 cancel 臂的循环病根放回来。`decidedWithoutCard` 已在上面收口,此处再写一次
|
|
648
|
-
// 是把「两位同时在场时谁优先」钉在判据里,不靠上面那个 return 的顺序当默契。
|
|
649
326
|
if (verdict.reopened === false && verdict.decidedWithoutCard !== true && verdict.pendingRowGone === true) {
|
|
650
327
|
return staleParkArm(taskId, signal, runs, deps);
|
|
651
328
|
}
|
|
652
329
|
return { kind: 'ask-reopen-failed', taskId, decidePath: signal.pendingGate?.decidePath ?? null };
|
|
653
330
|
}
|
|
654
|
-
/**
|
|
655
|
-
* 有界打一发 `runs.get` 读真态(lease 带截止 + 合流调用方中止;404 单列,其余错误一律「没答上来」)。
|
|
656
|
-
* 🔴 单源:running 存活对账腿、陈旧 park 臂的**卡前**与**卡后**三处共用这一只 —— 各写一份必漂,
|
|
657
|
-
* 而漂的后果是其中一处忘了 404 / 本发截止 / 奇形记录三种缺席的处置。
|
|
658
|
-
* 🔴 接收者必保:写成 `runs.get(...)` 方法形(把方法摘下来裸调 = 真 SDK 上当场 TypeError)。
|
|
659
|
-
*/
|
|
660
331
|
async function probeRunStatusOnce(taskId, durable, durableGet, deps) {
|
|
661
332
|
let status = null;
|
|
662
333
|
let ghost = false;
|
|
663
334
|
const lease = claimProbeLease(deps?.signal, RUNNING_LIVENESS_RECHECK_TIMEOUT_MS);
|
|
664
335
|
try {
|
|
665
|
-
// 保接收者(`durableGet.call(durable, …)`):摘下来裸调在真 SDK 上当场 TypeError。
|
|
666
336
|
status = readStatus(await durableGet.call(durable, taskId, { signal: lease.signal, ...sessionOpts(deps) }));
|
|
667
337
|
}
|
|
668
338
|
catch (e) {
|
|
@@ -674,110 +344,57 @@ async function probeRunStatusOnce(taskId, durable, durableGet, deps) {
|
|
|
674
344
|
}
|
|
675
345
|
return { status, ghost };
|
|
676
346
|
}
|
|
677
|
-
/**
|
|
678
|
-
* L-93 陈旧 park 臂(0.53.0):run 仍停在 park 态,而它在等的那张审批**已经不在引擎上**
|
|
679
|
-
* (宿主经 {@link ReopenCardVerdict} 的 `pendingRowGone` 证出「读面答了 + 零属主待决行」)。
|
|
680
|
-
*
|
|
681
|
-
* ── 为什么这一形可以给 cancel,而退役的那条臂不行 ─────────────────────────────────────────────
|
|
682
|
-
* 退役 cancel 臂的循环病根 = 「**待决项仍在**」时 cancel 的语义是替用户 deny 掉它 ⇒ 模型重试 ⇒
|
|
683
|
-
* 新 run 停在同一道门上 ⇒ 再 park。本臂的前提恰恰是那个待决项**已经不存在**:没有任何东西可被
|
|
684
|
-
* 「否掉」,那条循环在结构上不成立。所以边界写在**判定**上,不写在文案上 ——
|
|
685
|
-
* ① `pendingRowGone` 缺席/含糊值 ⇒ 本臂根本进不来(调用点只认严格 `true`);
|
|
686
|
-
* ② 进来之后仍要用 `runs.get` **一手复核**(宿主的一句话不足以驱动破坏性动作);
|
|
687
|
-
* ③ 复核读回的必须是**审批 park 词**({@link ASK_PARK_STATES});plan park(`needs_review`)交给
|
|
688
|
-
* plan 重开臂 —— 对它 cancel = 替用户丢掉整个 plan,是另一条禁区;
|
|
689
|
-
* ④ 两选卡上用户必须**逐字**选 `cancel` 才动那一枪(Esc/空答/读不出一律 wait)。
|
|
690
|
-
* 任何一条不成立 ⇒ 退回既有 `ask-reopen-failed`(老宿主与不确定情形逐字节零行为差)。
|
|
691
|
-
*/
|
|
692
347
|
async function staleParkArm(taskId, busy, runs, deps) {
|
|
693
|
-
/** 本臂的**保守收口**:与修前逐字同一行(卡呈不出来/读不出真态/不确定 ⇒ 零动作 + 如实说)。 */
|
|
694
348
|
const reopenFailed = {
|
|
695
349
|
kind: 'ask-reopen-failed',
|
|
696
350
|
taskId,
|
|
697
351
|
decidePath: busy.pendingGate?.decidePath ?? null,
|
|
698
352
|
};
|
|
699
|
-
// 读口缺席 ⇒ 一手复核做不成 ⇒ 不猜(宿主的一句话不驱动破坏性动作)。
|
|
700
|
-
// 🔴 **只取来判在场,绝不裸调**(runningChoiceArm R7 同注):真 SDK 的 `RunsResource` 方法体走
|
|
701
|
-
// `this.t.request(...)`,摘下来单独调当场 TypeError —— 下面的真调用一律写成 `runs.get(...)` 方法形,
|
|
702
|
-
// 交给 cancel 腿的那一份也用它保接收者的包装。
|
|
703
353
|
if (runs === undefined || typeof runs.get !== 'function')
|
|
704
354
|
return reopenFailed;
|
|
705
355
|
const durableGet = runs.get;
|
|
706
|
-
// 🔴 经函数读 aborted(waitForClaimRelease.isAborted 同注):它在 await 两侧会变,直接读两次会被
|
|
707
|
-
// tsc 控制流分析把第二次窄成恒假比较(TS2367)。
|
|
708
356
|
const callerAborted = () => deps?.signal?.aborted === true;
|
|
709
|
-
// 进门前已中止 ⇒ 一发出站都不打(Esc 之后还去问引擎 = 用户说停还在动)。
|
|
710
357
|
if (callerAborted())
|
|
711
358
|
return reopenFailed;
|
|
712
|
-
// ── 一手复核(与 running 存活对账腿同款 lease/abort 处理)────────────────────────────────────
|
|
713
359
|
const first = await probeRunStatusOnce(taskId, runs, durableGet, deps);
|
|
714
|
-
// 幽灵 claim:引擎一边说这个会话被这条 run 占着、一边查无此 run(与 running-not-found 同律 ——
|
|
715
|
-
// 404 是「不存在」∪「不是你的」的不可分辨并集,所以只说已证的事实,绝不盲发那条消息)。
|
|
716
360
|
if (first.ghost)
|
|
717
361
|
return { kind: 'ask-run-not-found', taskId };
|
|
718
|
-
// 复核期间用户中止 ⇒ 后续一切动作(派臂/出卡/登记/cancel)都不做。
|
|
719
362
|
if (callerAborted())
|
|
720
363
|
return reopenFailed;
|
|
721
|
-
// 读不出(网络失败 / 本发截止 / 奇形记录)⇒ 「读不到」≠「行不在」⇒ 保守收口,不猜。
|
|
722
364
|
const fresh = first.status;
|
|
723
365
|
if (fresh === null)
|
|
724
366
|
return reopenFailed;
|
|
725
|
-
// 真态是「在跑」⇒ 交给既有三选卡臂(statusFromWire=false:这一发就是新鲜读数,不打第二发)。
|
|
726
367
|
if (RUNNING_STATES.includes(fresh))
|
|
727
368
|
return runningChoiceArm(taskId, fresh, busy, false, runs, deps);
|
|
728
|
-
// 🔴 plan park ⇒ 交给 plan 重开臂:对它 cancel 是替用户把整个 plan 丢掉(禁区),而 plan 卡
|
|
729
|
-
// 本身是能重开的 —— 递一条更好的路,而不是递一把更危险的把手。
|
|
730
369
|
if (PLAN_REVIEW_STATES.includes(fresh))
|
|
731
370
|
return planReviewArm(taskId, busy, deps);
|
|
732
|
-
// 表外的状态词(含今天还不存在的)⇒ 不认得就不给把手,保守收口。
|
|
733
371
|
if (!ASK_PARK_STATES.includes(fresh))
|
|
734
372
|
return reopenFailed;
|
|
735
|
-
// ── 两选卡:停掉它 / 什么都不做 ──────────────────────────────────────────────────────────────
|
|
736
373
|
const offer = deps?.offerStaleParkChoice;
|
|
737
374
|
if (typeof offer !== 'function')
|
|
738
|
-
return reopenFailed;
|
|
739
|
-
// 供给位 = ①cancel 动词在场、②有能确认释放的 `get`(上面的一手复核已证在场)、③有**开枪前
|
|
740
|
-
// 再证一次前提**的属主待决行读口(`deps.listOwnedPendingApprovals`)。
|
|
741
|
-
// 🔴 第③条是 异源对抗复审 R1 [high] 的采纳(见下面开枪前那一段):`pendingRowGone` 是**读那一刻**的
|
|
742
|
-
// 事实,而这张卡是人在看、可以看任意久;没有复证口就没法在开枪前把那个前提再证一次,
|
|
743
|
-
// 于是「停掉它」这条路在本层就**兑现不了它承诺的安全性** ⇒ 按供给位纪律整卡不呈(退回旧结局),
|
|
744
|
-
// 而不是先呈卡再在开枪时才发现证不了。宿主要拿到这张卡,就得把这个读口一起注入(见 §16)。
|
|
745
|
-
// 两条路只剩「什么都不做」时整卡不呈 —— 只有一个空动作的卡是纯噪音(与三选卡 G8②b 同律)。
|
|
375
|
+
return reopenFailed;
|
|
746
376
|
const recheckOwnedPending = deps?.listOwnedPendingApprovals;
|
|
747
377
|
const canCancel = typeof runs.cancel === 'function' && typeof recheckOwnedPending === 'function';
|
|
748
|
-
// 第二段是给 tsc 的收窄(`canCancel` 是布尔,narrow 不了 `recheckOwnedPending` 这只可选函数),
|
|
749
|
-
// 判据本身只有 `canCancel` 一条。
|
|
750
378
|
if (!canCancel || typeof recheckOwnedPending !== 'function')
|
|
751
379
|
return reopenFailed;
|
|
752
380
|
const declineKey = staleParkDeclineKey(deps?.sessionId, taskId);
|
|
753
|
-
// 同 (sessionKey,taskId) 已呈过且用户选了「什么都不做」⇒ 不再整卡重弹,给判别位让端渲一行。
|
|
754
381
|
if (runningChoiceDeclined.has(declineKey)) {
|
|
755
382
|
return { kind: 'stale-park-wait', taskId, status: fresh, alreadyOffered: true };
|
|
756
383
|
}
|
|
757
384
|
let choice = null;
|
|
758
385
|
try {
|
|
759
|
-
// 🔴 fail-closed:**只有逐字 `'cancel'`** 才武装那一枪;Esc/空答/表外词一律收口成 wait。
|
|
760
386
|
choice = (await offer({ taskId, status: fresh, canCancel })) === 'cancel' ? 'cancel' : 'wait';
|
|
761
387
|
}
|
|
762
388
|
catch {
|
|
763
|
-
choice = null;
|
|
389
|
+
choice = null;
|
|
764
390
|
}
|
|
765
391
|
if (choice === null)
|
|
766
392
|
return reopenFailed;
|
|
767
393
|
if (choice === 'wait') {
|
|
768
|
-
// 呈卡腿抛错那次**不**登记(用户没看到卡,压掉后续等于把没呈过的卡当已呈)—— 见上面 catch。
|
|
769
394
|
rememberDeclinedOffer(declineKey);
|
|
770
395
|
return { kind: 'stale-park-wait', taskId, status: fresh };
|
|
771
396
|
}
|
|
772
|
-
// 用户重新表了态 ⇒ 清登记(与三选卡 steer/cancel 同律)。
|
|
773
397
|
runningChoiceDeclined.delete(declineKey);
|
|
774
|
-
// ── 🔴 卡后**复证 run 真态**(异源对抗复审 R3 [high] 采纳)────────────────────────────────────────────
|
|
775
|
-
// 上面那一发 `runs.get` 打在**呈卡之前**,而卡是人在看、可以停留任意久 —— 这中间那条 run 完全
|
|
776
|
-
// 可能已经**恢复运行**(审批在别处被决断 / 会话重新附着 / 引擎自愈)。沿用旧卡上那句 cancel 去停
|
|
777
|
-
// 一条**正在干活**的 run,等于拿「清理一个死结」的授权丢掉正在进行的工作,而用户按下去时屏幕上
|
|
778
|
-
// 写的前提早就不成立了。所以先复证状态仍是审批 park 词,再谈开枪:
|
|
779
|
-
// · 仍 park ⇒ 继续;· 404 ⇒ 幽灵 claim 如实说;· 变成别的状态 / 读不出 / 已中止 ⇒ 一律
|
|
780
|
-
// fail-closed 退回旧结局(下一条消息重新走整棵分诊树,拿到与那时真态相符的那张卡)。
|
|
781
398
|
if (callerAborted())
|
|
782
399
|
return reopenFailed;
|
|
783
400
|
const afterCard = await probeRunStatusOnce(taskId, runs, durableGet, deps);
|
|
@@ -787,26 +404,9 @@ async function staleParkArm(taskId, busy, runs, deps) {
|
|
|
787
404
|
return reopenFailed;
|
|
788
405
|
if (afterCard.status === null || !ASK_PARK_STATES.includes(afterCard.status))
|
|
789
406
|
return reopenFailed;
|
|
790
|
-
// ── 🔴 开枪前**再证一次前提**(异源对抗复审 R1 [high] 采纳)──────────────────────────────────────────
|
|
791
|
-
// `pendingRowGone` 是宿主**读那一刻**的事实,而这张卡是人在看、可以看任意久;这中间待决行完全
|
|
792
|
-
// 可能重新出现(store 恢复 / 会话重新附着),宿主也可能把「读失败 / 找不到能用的那一行」误报成
|
|
793
|
-
// gone。而上面那一发 `runs.get` 只证得了「它仍 parked」—— **审批还在**与**审批已丢**两种情形读回
|
|
794
|
-
// 的都是 `suspended`,那一发从来就证不了这个前提。代价不对称:cancel 一条**其实还能被决断**的
|
|
795
|
-
// run = 用户在一句假前提(「那张审批已经不在了」)下把整轮工作丢掉,而他以为自己只是在清理一个
|
|
796
|
-
// 死结。所以在**离 runs.cancel 最近的那一刻**再问一次属主待决行数:
|
|
797
|
-
// · 恰 0(引擎答了)⇒ 前提仍成立,开枪;
|
|
798
|
-
// · 非 0 / 抛错 / 窗尽 / 调用方中止 / 读不出 ⇒ **一律不开枪**(fail-closed),退回旧结局
|
|
799
|
-
// —— 「不知道」和「行又回来了」都不构成销毁一条 run 的授权。
|
|
800
|
-
// 🔴 **在册边界(P-44,异源对抗复审 R2 [high] 如实登记)**:这是「查了再做」,**不是原子条件取消**。
|
|
801
|
-
// 复证与那一枪之间仍有毫秒级窗口,行恰在此间恢复的话那一枪照样落下去。客户端关不死它 ——
|
|
802
|
-
// 真正的关法是 **server 读写面**的条件取消(core [7006] 定谳:core 无席——`TaskSpec.signal` 无条件中止、
|
|
803
|
-
// `claimTerminal` 是 ask 行 CAS 非 run lease;server [7007] 认领:cancel 已是两臂状态 CAS,7.73.0 契约明写 + 可选
|
|
804
|
-
// 版本前置,S-122 车),那是 wire 能力不是壳能自造的语义。这里能做的是把窗口从「人看卡的任意长时间」压到最小,
|
|
805
|
-
// 并把剩余风险登记在册(docs/INTEGRATION-CLIENTS.md §7b P-44),不假装它不存在。
|
|
806
407
|
const stillGone = await readOwnedPendingCount(recheckOwnedPending, deps);
|
|
807
408
|
if (stillGone !== 0)
|
|
808
409
|
return reopenFailed;
|
|
809
|
-
// 🔴 **复用**同一条 cancel + 有界释放确认腿(不复制):失败分类与释放判据必须与三选卡同源。
|
|
810
410
|
const verdict = await cancelAndConfirmRelease(taskId, runs, durableGet, deps);
|
|
811
411
|
if (verdict.outcome === 'released')
|
|
812
412
|
return { kind: 'stale-park-cancelled', taskId };
|
|
@@ -821,19 +421,6 @@ async function staleParkArm(taskId, busy, runs, deps) {
|
|
|
821
421
|
confirmedHeld: verdict.confirmedHeld,
|
|
822
422
|
};
|
|
823
423
|
}
|
|
824
|
-
/**
|
|
825
|
-
* cancel 恰一枪 + 有界等那条 run 交出会话。
|
|
826
|
-
*
|
|
827
|
-
* 🔴 cancel 的失败分两类(二次评审 R4 [high]),与 steer 同一把尺:
|
|
828
|
-
* · `rejected`(4xx)—— 服务端答了且明确拒 ⇒ 那一枪确定没生效,立即如实说,不必再等确认腿;
|
|
829
|
-
* · `unknown`(5xx / 连接在回执路上断了)—— 请求**可能已经被受理并在异步执行**。此时直接收口成
|
|
830
|
-
* 「取消失败,那条 run 还占着会话」是对用户下一个证不出的断言 —— 而这条腿手上恰恰有一个能
|
|
831
|
-
* 回答这个问题的读口(`get`)。所以照走有界确认腿:真交出来了就算取消成了,确认不到才如实说
|
|
832
|
-
* 「没能确认」。判据锚在**决定量**(会话有没有被交出来)上,不锚它的一个前置条件(那一枪的
|
|
833
|
-
* 回执长什么样)。
|
|
834
|
-
* 🔴 接收者必保:真 SDK 的 `RunsResource` 方法体走 `this.t.request(...)`,把方法摘下来裸调当场
|
|
835
|
-
* TypeError —— 所以 cancel 写成方法形 `durable.cancel?.(…)`,交给等待器的 `get` 用保接收者的包装。
|
|
836
|
-
*/
|
|
837
424
|
async function cancelAndConfirmRelease(taskId, durable, durableGet, deps) {
|
|
838
425
|
const boundGet = (id, opts) => durableGet.call(durable, id, opts);
|
|
839
426
|
let cancelFailure = null;
|
|
@@ -841,33 +428,26 @@ async function cancelAndConfirmRelease(taskId, durable, durableGet, deps) {
|
|
|
841
428
|
const cancelStartedAt = Date.now();
|
|
842
429
|
const cancelLease = claimProbeLease(deps?.signal, cancelWaitMs);
|
|
843
430
|
try {
|
|
844
|
-
// 🔴 这一枪也必须**带中止口与截止**(二次评审 R5 [high]):`cancel` 的 promise 不落地,后面那条
|
|
845
|
-
// 有界确认腿就根本起不来 —— 用户按了 Esc 既停不掉等待也停不掉出站请求,整条腿零结局。有界性
|
|
846
|
-
// 的执行手段只有一个:把 signal 真交到请求上。
|
|
847
431
|
await durable.cancel?.(taskId, { signal: cancelLease.signal, ...sessionOpts(deps) });
|
|
848
432
|
}
|
|
849
433
|
catch (e) {
|
|
850
|
-
// 用户中止 ⇒ 当拍收口(绝不接着跑一个满窗的确认腿:「UI 说停了、后台还在问」正是禁区)。
|
|
851
434
|
if (deps?.signal?.aborted === true) {
|
|
852
435
|
return { outcome: 'timeout', waitedMs: Date.now() - cancelStartedAt, aborted: true, confirmedHeld: false };
|
|
853
436
|
}
|
|
854
437
|
const delivery = atMostOnceFailureClass(e);
|
|
855
438
|
if (delivery === 'rejected')
|
|
856
439
|
return { outcome: 'failed', detail: describeFailure(e), delivery };
|
|
857
|
-
// 本枪的截止到点(请求挂死被掐)同样是**送达未知** —— 服务端可能已经受理,照走确认腿。
|
|
858
440
|
cancelFailure = { detail: describeFailure(e), delivery };
|
|
859
441
|
}
|
|
860
442
|
finally {
|
|
861
443
|
cancelLease.release();
|
|
862
444
|
}
|
|
863
|
-
// 202 = 「收下了」不是「停了」⇒ 必须真等到那条 run 交出会话,否则重发只会再撞一次 409。
|
|
864
445
|
const verdict = await waitForClaimRelease(taskId, {
|
|
865
446
|
get: boundGet,
|
|
866
447
|
budgetMs: cancelWaitMs,
|
|
867
448
|
...(deps?.signal !== undefined ? { signal: deps.signal } : {}),
|
|
868
449
|
...sessionOpts(deps),
|
|
869
450
|
});
|
|
870
|
-
// 会话真被交出来了 ⇒ 就是取消成功,哪怕那一枪的回执丢在路上(决定量说了算)。
|
|
871
451
|
if (verdict.released)
|
|
872
452
|
return { outcome: 'released' };
|
|
873
453
|
if (cancelFailure !== null)
|
|
@@ -876,70 +456,29 @@ async function cancelAndConfirmRelease(taskId, durable, durableGet, deps) {
|
|
|
876
456
|
outcome: 'timeout',
|
|
877
457
|
waitedMs: verdict.waitedMs,
|
|
878
458
|
aborted: verdict.aborted,
|
|
879
|
-
// 🔴 0.68.0 / L-230:**两条持锁证据取并**,直接证据优先 ——
|
|
880
|
-
// ① `heldBy` 读出 `'held'` = 引擎**直说**那条 run 还占着(server 7.73.0 S-122 P-45 读面);
|
|
881
|
-
// ② 老引擎不发那一位(`'unknown'`)⇒ 回落既有的按状态词推断({@link CLAIM_HELD_STATES})。
|
|
882
|
-
// 🔴 `'released'` 在这里到不了(released 的判决在上面就 return 了),`'unknown'` 不算证据 ——
|
|
883
|
-
// 两条都答不出时仍然收口成 `false` = 「确认不了」,文案绝不说「它还占着」。
|
|
884
459
|
confirmedHeld: verdict.lastHolder === 'held' ||
|
|
885
460
|
(verdict.lastStatus !== null && CLAIM_HELD_STATES.includes(verdict.lastStatus)),
|
|
886
461
|
};
|
|
887
462
|
}
|
|
888
|
-
/**
|
|
889
|
-
* `running` 形:呈三选卡 → 按用户的决定动手。零决定 = 零动作(`not-parked` 现状行)。
|
|
890
|
-
*
|
|
891
|
-
* 🔴 三条纪律在这一个函数里同时承重:
|
|
892
|
-
* ① **cancel 只在用户显式选了它时打**,一枪(反钉:客户端自己一枪都不许打);
|
|
893
|
-
* ② **steer 只打一次**,失败不重试也不回退 cancel(非幂等,契约逐字);
|
|
894
|
-
* ③ 选项按**供给**渲(verb 缺席 = 那条路兑现不了 = 整个不渲),cancel 还额外要 `get` ——
|
|
895
|
-
* 没有它就确认不了 claim 真的释放,「取消并重发」就成了猜。
|
|
896
|
-
*/
|
|
897
463
|
async function runningChoiceArm(taskId, status, busy, statusFromWire, runs, deps) {
|
|
898
464
|
const notParked = { kind: 'not-parked', taskId, status };
|
|
899
465
|
const offer = deps?.offerRunningChoice;
|
|
900
|
-
// CC-44(0.72.8):系统注入件不问人 —— 不需要呈卡口;canSteer 不成立时零卡零动作。
|
|
901
466
|
const injected = deps?.submissionOrigin === 'injected';
|
|
902
467
|
if (typeof offer !== 'function' && !injected)
|
|
903
468
|
return notParked;
|
|
904
469
|
const text = typeof deps?.deniedMessage === 'string' ? deps.deniedMessage : '';
|
|
905
|
-
// 🔴 **只取来判在场,绝不裸调**(二次评审 R7 [high]):真 SDK 的 `RunsResource` 方法体走
|
|
906
|
-
// `this.t.request(...)`,把方法从对象上摘下来单独调会当场 TypeError —— 后果是 steer 恒报「送达
|
|
907
|
-
// 未知」、cancel 先失败再拿一个同样失了接收者的 get 轮询到预算耗尽,三选卡的两条路在生产上**全废**
|
|
908
|
-
// (而箭头函数 mock 恰好把这一形完全遮住)。下面所有真调用一律写成 `runs.xxx(...)` 的方法形,
|
|
909
|
-
// 交给别人的那一发也用**保接收者的包装**。
|
|
910
470
|
const canSteer = typeof runs?.steer === 'function' && text.length > 0;
|
|
911
471
|
const canCancel = typeof runs?.cancel === 'function' && typeof runs?.get === 'function';
|
|
912
472
|
if (!canSteer && !canCancel)
|
|
913
473
|
return notParked;
|
|
914
474
|
if (injected && !canSteer)
|
|
915
475
|
return notParked;
|
|
916
|
-
// ── 存活对账腿(Inkglow-1085 P0b②):出卡前对 wire 直供的 running 补一发 runs.get 真查 ────────
|
|
917
|
-
// 409 终帧的 `activeTaskStatus` 是引擎 claim/store 的 best-effort 投影 —— claim 指着一条早已终结
|
|
918
|
-
// 甚至已不存在的 run 时,它会**每次都**回送同一个错的 running(幽灵),用户按 wire 读数拿到的
|
|
919
|
-
// steer/cancel 两条路全是死路。
|
|
920
|
-
// 🔴 [4664] server 定谳(2026-08-19)把「别把 409 running 当绝对真值」写成了四条成文的洞:
|
|
921
|
-
// ①park 态不在 reapStale 射程;②`updated_at` 误杀窗;③409 与 poll 口径短暂相左;
|
|
922
|
-
// ④LOCAL(file)车道无周期自愈腿。SQL 车道死 run 的自愈上界 ≈180s(心跳 30s +
|
|
923
|
-
// REAP_RUN_STALE_SEC 120 + tick 60)——**LOCAL 车道无上界,勿假设 180s**,所以本腿不做
|
|
924
|
-
// 「等它自愈」的时间性假设,只做一手读面复核。
|
|
925
|
-
// 真源两级里它是二手货,出卡(把把手递给人)前用一手读面复核一次:
|
|
926
|
-
// · get 读回非 running 的**已知**词 ⇒ wire 读数过期,按真态走(park 词进对应重开臂,其余落
|
|
927
|
-
// not-parked 如实说)—— 与「kind 缺席 ⇒ status 表回退」同一张表,不新铸分臂;
|
|
928
|
-
// · get 撞 404 ⇒ 幽灵 claim(`running-not-found` 判别位;为什么不算「已释放」见该臂注);
|
|
929
|
-
// · get 网络失败 / 本发截止 / 奇形记录 ⇒ **读不到 ≠ 非 running**,保守维持出卡 —— 误关卡会把
|
|
930
|
-
// steer/cancel 两条真把手一起藏掉;
|
|
931
|
-
// · status 本就是本层刚 runs.get 查来的(statusFromWire=false)⇒ 已新鲜,不打第二发。
|
|
932
|
-
// 🔴 经函数读 aborted(waitForClaimRelease.isAborted 同注):它在 await 两侧会变,直接读两次会被
|
|
933
|
-
// tsc 控制流分析把第二次窄成恒假比较(TS2367)。
|
|
934
476
|
const probeCallerAborted = () => deps?.signal?.aborted === true;
|
|
935
477
|
if (statusFromWire && typeof runs?.get === 'function' && !probeCallerAborted()) {
|
|
936
|
-
// 与陈旧 park 臂共用同一只读叶(同形单源,见 {@link probeRunStatusOnce})。
|
|
937
478
|
const probe = await probeRunStatusOnce(taskId, runs, runs.get, deps);
|
|
938
479
|
const fresh = probe.status;
|
|
939
480
|
if (probe.ghost)
|
|
940
481
|
return { kind: 'running-not-found', taskId };
|
|
941
|
-
// (对抗复审 [medium] 采纳)对账期间用户中止 ⇒ 后续一切动作(派臂/出卡/登记)都不做 ——
|
|
942
|
-
// Esc 之后还弹卡/重开 = 用户说停还在动。零动作现状行收口(与呈卡腿抛错同一条诚实收口)。
|
|
943
482
|
if (probeCallerAborted())
|
|
944
483
|
return notParked;
|
|
945
484
|
if (fresh !== null && !RUNNING_STATES.includes(fresh)) {
|
|
@@ -947,16 +486,11 @@ async function runningChoiceArm(taskId, status, busy, statusFromWire, runs, deps
|
|
|
947
486
|
return planReviewArm(taskId, busy, deps);
|
|
948
487
|
if (ASK_PARK_STATES.includes(fresh))
|
|
949
488
|
return askParkArm(taskId, busy, runs, deps);
|
|
950
|
-
// (对抗复审 [high] 采纳)终态词 ⇒ 专属结局:not-parked 的「wait for it to finish」对一条
|
|
951
|
-
// 已终结的 run 是永远等不到的假话。表外的未知词仍落 not-parked 如实说(不替引擎断言终结)。
|
|
952
489
|
if (CLAIM_RELEASED_STATES.includes(fresh))
|
|
953
490
|
return { kind: 'running-settled', taskId, status: fresh };
|
|
954
491
|
return { kind: 'not-parked', taskId, status: fresh };
|
|
955
492
|
}
|
|
956
|
-
// 确认仍 running(或读不到)⇒ 按原判继续出卡。
|
|
957
493
|
}
|
|
958
|
-
// ── 已呈现登记(Inkglow-1085 P0b①):同 (sessionKey,taskId) 用户选过「Do nothing」⇒ 不再整卡
|
|
959
|
-
// 重弹,给 `alreadyOffered` 判别位让端降级渲一行(登记语义与清口见 runningChoiceDeclined 顶注)。
|
|
960
494
|
const declineKey = runningChoiceDeclineKey(deps?.sessionId, taskId);
|
|
961
495
|
if (!injected && runningChoiceDeclined.has(declineKey)) {
|
|
962
496
|
return { kind: 'not-parked', taskId, status, alreadyOffered: true };
|
|
@@ -964,9 +498,6 @@ async function runningChoiceArm(taskId, status, busy, statusFromWire, runs, deps
|
|
|
964
498
|
let choice = 'wait';
|
|
965
499
|
let offerDelivered = false;
|
|
966
500
|
if (injected) {
|
|
967
|
-
// CC-44:系统件不问人 —— canSteer 已在上面证过(动词在场 ∧ 有正文)。cancel 永不自动打(反钉纪律不变)。
|
|
968
|
-
// 🔴 CC-47(test 0.72.8 验收 #56 ②):用户已按 Esc(`deps.signal` 已 abort)⇒ **不发**这枪不可撤回的 steer,零动作现状行
|
|
969
|
-
// 收口 —— 用户说停之后系统件还往 run 里注入 = 「UI 显示已停、后台还在动」那一形。
|
|
970
501
|
if (probeCallerAborted())
|
|
971
502
|
return notParked;
|
|
972
503
|
choice = 'steer';
|
|
@@ -977,16 +508,11 @@ async function runningChoiceArm(taskId, status, busy, statusFromWire, runs, deps
|
|
|
977
508
|
offerDelivered = true;
|
|
978
509
|
}
|
|
979
510
|
catch {
|
|
980
|
-
choice = 'wait';
|
|
511
|
+
choice = 'wait';
|
|
981
512
|
}
|
|
982
513
|
}
|
|
983
|
-
// wait = 用户看过卡并选择留着它(呈现层把 Esc/空答也收口成 wait —— 同样是「这次不动它」的表态);
|
|
984
|
-
// steer/cancel = 用户重新表了态 ⇒ 清登记。呈卡腿抛错那次**不**登记(用户没看到卡)。
|
|
985
|
-
// 注入形不碰这张表(它记的是人的决定)。
|
|
986
514
|
if (!injected) {
|
|
987
515
|
if (choice === 'wait') {
|
|
988
|
-
// (对抗复审 [medium] 轻形采纳)FIFO 有界:长命桌面/多会话宿主不许无界长住(叶
|
|
989
|
-
// {@link rememberDeclinedOffer},与 L-93 两选卡共用同一条上界)。
|
|
990
516
|
if (offerDelivered)
|
|
991
517
|
rememberDeclinedOffer(declineKey);
|
|
992
518
|
}
|
|
@@ -996,19 +522,9 @@ async function runningChoiceArm(taskId, status, busy, statusFromWire, runs, deps
|
|
|
996
522
|
}
|
|
997
523
|
if (choice === 'steer' && canSteer && runs?.steer !== undefined) {
|
|
998
524
|
try {
|
|
999
|
-
// 🔴 正文原样送(untrusted DATA,server 侧围栏);恰一次,绝不重试。方法形调用保接收者。
|
|
1000
525
|
const receipt = await runs.steer(taskId, { text }, sessionOpts(deps));
|
|
1001
526
|
const delivery = readSteerDelivery(receipt);
|
|
1002
527
|
const receiptStatus = readSteerReceiptStatus(receipt);
|
|
1003
|
-
// 🔴 `queued` 是 wire 在**纠正分诊输入**(SDK `SteerReceipt` 头注逐字:run is durably SUSPENDED,
|
|
1004
|
-
// 消息被 PARKED 在它的待决 checkpoint 上,`status` = 服务端 park 前读到的那个行词 —— 契约写死
|
|
1005
|
-
// 只有两种:`"suspended"` OR `"needs_review"`,后者正是 server 7.16.0 起 plan-review park 的回声)。
|
|
1006
|
-
// 进这一臂的前提是 status 读作 running —— 那份读数已经过期。此刻真正的出路是**把那张门卡重开**:
|
|
1007
|
-
// 门没被答,run 就不 resume,排进去的消息也就永远注入不进去。
|
|
1008
|
-
// 🔴 这里按 status 表分臂**不违反**「kind 优先」:回执上**根本没有 gate kind 位**(SteerReceipt
|
|
1009
|
-
// 只有 taskId/status/messageId/priority/note),所以这就是那条成文的「kind 缺席 ⇒ status 表回退」
|
|
1010
|
-
// 路径本身,不是拿 status 去压一个在场的 kind。契约把那两个词分别绑死在两个 park 族上,回退表
|
|
1011
|
-
// 与它逐词同形。未知 park 词 ⇒ 两臂都不进(不拿没想过的状态驱动已知决断 API)。
|
|
1012
528
|
if (delivery === 'queued') {
|
|
1013
529
|
const verdict = receiptStatus !== null && PLAN_REVIEW_STATES.includes(receiptStatus)
|
|
1014
530
|
? await planVerdict(taskId, deps)
|
|
@@ -1020,14 +536,10 @@ async function runningChoiceArm(taskId, status, busy, statusFromWire, runs, deps
|
|
|
1020
536
|
return { kind: 'running-steered', taskId, delivery, status: receiptStatus, reopened: null };
|
|
1021
537
|
}
|
|
1022
538
|
catch (e) {
|
|
1023
|
-
// 🔴 两类失败必须分开报(见 running-steer-failed 注):明确拒收才可以说「没送到、请重发」。
|
|
1024
539
|
return { kind: 'running-steer-failed', taskId, detail: describeFailure(e), delivery: atMostOnceFailureClass(e) };
|
|
1025
540
|
}
|
|
1026
541
|
}
|
|
1027
542
|
if (choice === 'cancel' && canCancel && runs?.cancel !== undefined && runs.get !== undefined) {
|
|
1028
|
-
// 🔴 **复用**共享的 cancel + 有界释放确认腿(见 {@link cancelAndConfirmRelease});本臂只做
|
|
1029
|
-
// 「判决 → 自己这一族的结局名」的映射。L-93 的两选卡走同一条腿 —— 复制第二份的代价是两条腿
|
|
1030
|
-
// 的失败分类与释放判据各自漂,而它们说的是同一件事。
|
|
1031
543
|
const verdict = await cancelAndConfirmRelease(taskId, runs, runs.get, deps);
|
|
1032
544
|
if (verdict.outcome === 'released')
|
|
1033
545
|
return { kind: 'running-cancelled', taskId };
|
|
@@ -1042,27 +554,14 @@ async function runningChoiceArm(taskId, status, busy, statusFromWire, runs, deps
|
|
|
1042
554
|
confirmedHeld: verdict.confirmedHeld,
|
|
1043
555
|
};
|
|
1044
556
|
}
|
|
1045
|
-
// 用户选了 ③ / 卡没答上 / 选了一条本轮不可兑现的路 ⇒ 零动作,如实转述现状。
|
|
1046
557
|
return notParked;
|
|
1047
558
|
}
|
|
1048
|
-
/** cli 的唯一真出路(`/clear` = 新会话 id ⇒ 引擎侧全新 session ⇒ 不受旧 claim 影响)。
|
|
1049
|
-
* 导出(#244 F1):宿主的 resume 冷启动对账腿等失败半场要说**同一句**真出路 —— 各写一份必漂
|
|
1050
|
-
* (一句「按某个键」而那个键没注册,正是本文件头在骂的形)。覆盖语义照旧走
|
|
1051
|
-
* {@link ActiveRunSelfHealCopy.wayOut},本常量只是默认串的单源。 */
|
|
1052
559
|
export const INTERACTIVE_WAY_OUT = 'run /clear to keep working in a fresh session';
|
|
1053
560
|
const DEFAULT_WAY_OUT = INTERACTIVE_WAY_OUT;
|
|
1054
561
|
const DEFAULT_FRESH_SESSION = 'start a new session (drop --resume/--continue)';
|
|
1055
|
-
/**
|
|
1056
|
-
* 「在等哪一种决定」的人话 —— **只由 wire 给的 `pendingGate.kind` 铸**。引擎对未知 gate 种类
|
|
1057
|
-
* 自己返 null,这里照实说「一个决定」,绝不按 kind 猜一张表(猜错 = 又一句「按了没反应」)。
|
|
1058
|
-
*/
|
|
1059
562
|
function gateKindPhrase(kind) {
|
|
1060
563
|
return kind ? `a ${kind} decision` : 'a decision';
|
|
1061
564
|
}
|
|
1062
|
-
/**
|
|
1063
|
-
* 409 体材料 → 「怎么解」的那一句。🔴 只在 wire 真给了 `pendingGate.decidePath` 时才存在;
|
|
1064
|
-
* 材料缺席 = 空串,由基句自己承担 —— 绝不自造一条路由。
|
|
1065
|
-
*/
|
|
1066
565
|
function decidePathClause(signal) {
|
|
1067
566
|
const decidePath = signal?.pendingGate?.decidePath;
|
|
1068
567
|
if (!decidePath)
|
|
@@ -1070,23 +569,11 @@ function decidePathClause(signal) {
|
|
|
1070
569
|
return (` The engine says that run is waiting on ${gateKindPhrase(signal?.pendingGate?.kind)} and that it is` +
|
|
1071
570
|
` decided at POST ${decidePath} — deciding it releases the session.`);
|
|
1072
571
|
}
|
|
1073
|
-
/**
|
|
1074
|
-
* 出身归因(`pendingGate.governanceForced`,server ≥7.13 / SDK 6.15.0 同窗上 409 体)。
|
|
1075
|
-
* 治理强制的门不是用户自己配的规则 —— 「去 settings 里把它关掉」在这类门上是假出路;说清出身,
|
|
1076
|
-
* 用户才知道该找谁。🔴 只在位真为 true 时存在;缺席一个字都不说(缺席 ≠「这不是治理门」,
|
|
1077
|
-
* 渲一句否定 = 把证不出的断言当事实)。
|
|
1078
|
-
*/
|
|
1079
572
|
function governanceOriginClause(signal) {
|
|
1080
573
|
return signal?.pendingGate?.governanceForced === true
|
|
1081
574
|
? ' That gate is enforced by this deployment\'s governance policy, not by a permission rule you set.'
|
|
1082
575
|
: '';
|
|
1083
576
|
}
|
|
1084
|
-
/**
|
|
1085
|
-
* 活性证据(Inkglow-1085 P0b,[4664]:409 体内 `msSinceLastActivity` 是唯一可消费的活性证据位)。
|
|
1086
|
-
* 🔴 只在位真在场时说话(缺席一个字都不说 —— 缺席 = 无法证明,不是「没有活动」);它是**展示**
|
|
1087
|
-
* 判别,不改变任何动作。只挂在 running 形的 not-parked 行上:那正是「它到底还活着吗」这个问题
|
|
1088
|
-
* 被问出来的地方。
|
|
1089
|
-
*/
|
|
1090
577
|
function lastActivityClause(signal) {
|
|
1091
578
|
const ms = signal?.msSinceLastActivity;
|
|
1092
579
|
if (typeof ms !== 'number' || !Number.isFinite(ms) || ms < 0)
|
|
@@ -1094,62 +581,25 @@ function lastActivityClause(signal) {
|
|
|
1094
581
|
const human = ms < 120_000 ? `${String(Math.max(1, Math.round(ms / 1000)))}s` : `${String(Math.round(ms / 60_000))}m`;
|
|
1095
582
|
return ` The engine last recorded activity on that run ${human} ago.`;
|
|
1096
583
|
}
|
|
1097
|
-
/**
|
|
1098
|
-
* 每种结局的上屏整行。**每一句出路都真的接了线**;没有真出路的分支就直说「这个会话暂时无法
|
|
1099
|
-
* 继续」。重开成功那条也要上屏 —— 卡是替用户重新打开的,他得知道去答哪张、答完做什么。
|
|
1100
|
-
* wire 真到得了端的两个 additive 键是 `activeTaskStatus` 与 `pendingGate`,各自有真消费点;
|
|
1101
|
-
* 不为「说不定哪天上游会发」的键留悬空分支。
|
|
1102
|
-
*/
|
|
1103
584
|
export function activeRunSelfHealRow(outcome, signal, copy, origin, follow) {
|
|
1104
585
|
const override = copy?.rowFor?.(outcome, signal);
|
|
1105
586
|
if (typeof override === 'string')
|
|
1106
587
|
return override;
|
|
1107
|
-
// 0.72.6(L-379):宿主声明已挂续听 **且**本结局的续听意图确是「现在 tail」⇒ handed-off 句换成事实陈述;
|
|
1108
|
-
// 不声明 ⇒ 逐字节同旧。🔴 意图为 after-decision / after-wake 时**不换**(异源对抗复审 [medium]:那两形的续听是
|
|
1109
|
-
// 延后登记,不是正在续听;注入形此前没按 delivery 分臂,这里在入口统一收口,两种来源同一前提)。
|
|
1110
588
|
const following = follow?.following === true && steerFollowIntent(outcome)?.kind === 'tail';
|
|
1111
|
-
// #289 批面② —— 系统注入件的低噪形。整块**前置**于默认串:注入形与用户形是两句完全不同的话,
|
|
1112
|
-
// 不是在同一句上加定语(见 {@link injectedSubmissionRow} 头注)。缺省 'user' ⇒ 下面逐字节原样。
|
|
1113
589
|
if (origin === 'injected')
|
|
1114
590
|
return injectedSubmissionRow(outcome, following);
|
|
1115
591
|
const base = activeRunSelfHealBaseRow(outcome, signal, copy?.wayOut ?? DEFAULT_WAY_OUT, following);
|
|
1116
592
|
switch (outcome.kind) {
|
|
1117
|
-
// 这两条是「用户此刻卡住了、而且没有别的把手」的结局 —— wire 给的 decide 入口在这里才有用。
|
|
1118
|
-
// not-parked(running 形)另挂活性证据([4664] 判别位):「它到底还活着吗」在这一行被问出来。
|
|
1119
593
|
case 'not-parked':
|
|
1120
594
|
return base + lastActivityClause(signal) + decidePathClause(signal) + governanceOriginClause(signal);
|
|
1121
595
|
case 'state-unknown':
|
|
1122
596
|
return base + decidePathClause(signal) + governanceOriginClause(signal);
|
|
1123
|
-
// 其余结局手上都另有一张更好的把手(重开的卡 / 已开的卡 / 失败臂基句自己渲的 decidePath)
|
|
1124
|
-
// —— 再叠一条引擎路由只会稀释那一个动作。
|
|
1125
597
|
default:
|
|
1126
598
|
return base + governanceOriginClause(signal);
|
|
1127
599
|
}
|
|
1128
600
|
}
|
|
1129
|
-
/**
|
|
1130
|
-
* 系统注入件被拒时的**低噪**整行(#289 批面②;dogfood inject C1 文案半场)。
|
|
1131
|
-
*
|
|
1132
|
-
* ── 为什么不是「在原句上换个主语」──────────────────────────────────────────────────────────
|
|
1133
|
-
* 原来那 8 条臂全部以「Your message was NOT sent … send it again」收尾。对注入件这句里的每一个
|
|
1134
|
-
* 词都不成立:不是 your、不是 message、而且 send it again 这个动作**在结构上不存在**(注入件在
|
|
1135
|
-
* 队列里 isMeta ⇒ 不可编辑、不可 ↑ 召回、队列预览里都不渲)。所以这一形从头写,不复用出路半句。
|
|
1136
|
-
*
|
|
1137
|
-
* ── 与行为的对账(不许再出一句假承诺)────────────────────────────────────────────────────────
|
|
1138
|
-
* 「it will be delivered after you decide」这句**只在** {@link selfHealSubmissionDisposition} 判
|
|
1139
|
-
* `held-for-decision` 时才说 —— 那正是宿主真会把它按原优先级放回队列的那一档。判据同源,不是
|
|
1140
|
-
* 两边各写一份(记忆 `paired-mechanisms-must-share-premise`)。其余档一律如实说「这一条没有送达」,
|
|
1141
|
-
* 绝不承诺一次不会发生的重投。
|
|
1142
|
-
*
|
|
1143
|
-
* ── 噪音预算 ────────────────────────────────────────────────────────────────────────────────
|
|
1144
|
-
* 用户没写过这条消息,所以这一行的读者其实是**将来的自己**(为什么模型没收到 plan 的结果)。
|
|
1145
|
-
* 一句话说清三件事:是系统件、当时什么挡住了它、接下来会不会自己再来。不给出路半句 —— 用户此刻
|
|
1146
|
-
* 该做的事是答那张卡,而那张卡就在屏幕上。
|
|
1147
|
-
*/
|
|
1148
601
|
function injectedSubmissionRow(outcome, following = false) {
|
|
1149
602
|
const handle = 'taskId' in outcome && outcome.taskId ? ` (run ${outcome.taskId})` : '';
|
|
1150
|
-
// 0.72.9 CC-47(test 0.72.8 验收 #56):steer 失败**按 delivery 分句**,不落默认 not-delivered 句 —— 那句说「could not put a
|
|
1151
|
-
// decision on screen」与真因不符,且对 `unknown`(5xx / 断连,可能已注入)断言「未送达、模型不知道」是替引擎编一句安全声明;
|
|
1152
|
-
// 用户据此手动补发 = 重复注入。与用户形同一条律:明确拒收才说没送到;送达未知只说「未知、未重试、请观察」。
|
|
1153
603
|
if (outcome.kind === 'running-steer-failed') {
|
|
1154
604
|
return outcome.delivery === 'unknown'
|
|
1155
605
|
? `A system notification was handed to the run that is already working${handle}, but the engine did not confirm delivery ` +
|
|
@@ -1165,8 +615,6 @@ function injectedSubmissionRow(outcome, following = false) {
|
|
|
1165
615
|
case 'resending':
|
|
1166
616
|
return `A system notification was held while the earlier run${handle} was cancelled; sema is delivering it now.`;
|
|
1167
617
|
case 'handed-off':
|
|
1168
|
-
// 🔴 「watch that run」只在宿主**没有**挂续听时说(那时确实只能靠用户看);挂上了就说事实 ——
|
|
1169
|
-
// 那条 run 接下来要的东西(含决断卡)会由 sema 从它自己的流上端上来(L-379)。
|
|
1170
618
|
return following
|
|
1171
619
|
? `A system notification was handed to the run that is already working${handle} instead of starting ` +
|
|
1172
620
|
`a new turn — sema is following that run and will surface whatever it asks for next.`
|
|
@@ -1185,9 +633,6 @@ function activeRunSelfHealBaseRow(outcome, signal, wayOut, following = false) {
|
|
|
1185
633
|
`sema did NOT cancel it — answering that card is what releases the session, and cancelling ` +
|
|
1186
634
|
`would decide it for you. Your message was NOT sent; answer the open card and send it again.`);
|
|
1187
635
|
case 'ask-reopened':
|
|
1188
|
-
// 🔴 首见/复见分形:首见卡(本进程从未呈现过这张 pending 卡 —— 典型链:上一 turn 的
|
|
1189
|
-
// approve/answer 把引擎推进到了**新** gate,本次提交撞 409 时那张新卡还没呈现过)绝不
|
|
1190
|
-
// 说 closed/reopened —— 对没发生过的历史下断言 = 谎报面。动作两形一致,只有话不同。
|
|
1191
636
|
if (outcome.firstSight) {
|
|
1192
637
|
return (`The previous turn advanced to a decision it needs from you (run ${outcome.taskId}), which is ` +
|
|
1193
638
|
`why nothing new could start — sema has surfaced that decision card. Answer it and send your ` +
|
|
@@ -1198,7 +643,6 @@ function activeRunSelfHealBaseRow(outcome, signal, wayOut, following = false) {
|
|
|
1198
643
|
`Answer it and send your message again. (sema did NOT cancel the run: cancelling would have ` +
|
|
1199
644
|
`decided it for you.)`);
|
|
1200
645
|
case 'plan-review-reopened':
|
|
1201
|
-
// 同上分形:首见 plan 卡(如 resume 到一个在别处 park 的会话)不断言「被关过」。
|
|
1202
646
|
if (outcome.firstSight) {
|
|
1203
647
|
return (`The previous turn produced a plan that is waiting for your review (run ${outcome.taskId}), ` +
|
|
1204
648
|
`which is why nothing new could start — sema has surfaced the review card. Answer it and send ` +
|
|
@@ -1226,8 +670,6 @@ function activeRunSelfHealBaseRow(outcome, signal, wayOut, following = false) {
|
|
|
1226
670
|
`Your message was NOT sent; ${wayOut} if you no longer want it.${viaEngine}`);
|
|
1227
671
|
}
|
|
1228
672
|
case 'plan-review-reopen-failed': {
|
|
1229
|
-
// decidePath 来自 409 body(引擎给的真路由);拿不到就不提它 —— 宁可少给一条路,
|
|
1230
|
-
// 也不自己按 gate kind 猜一个入口(猜错 = 又一句「按了没反应」)。
|
|
1231
673
|
const viaEngine = outcome.decidePath
|
|
1232
674
|
? ` You can also decide it on the engine directly: POST ${outcome.decidePath}.`
|
|
1233
675
|
: '';
|
|
@@ -1235,11 +677,7 @@ function activeRunSelfHealBaseRow(outcome, signal, wayOut, following = false) {
|
|
|
1235
677
|
`reopen that approval card. It did NOT cancel the run — that would have discarded the plan for ` +
|
|
1236
678
|
`you. Your message was NOT sent; ${wayOut} if you no longer want that plan.${viaEngine}`);
|
|
1237
679
|
}
|
|
1238
|
-
// ── 三选卡的四种动作结局(选 ③ / 没答 / 没呈上都落 not-parked)────────────────────────────
|
|
1239
680
|
case 'running-steered': {
|
|
1240
|
-
// 🔴 三种 delivery 对用户是三件**完全不同**的事(SDK SteerReceipt 头注),所以整句分形。
|
|
1241
|
-
// 🔴 附件披露:wire 的 steer body 只有 `text`(SDK 签名无 images 位),同一次提交里的图片
|
|
1242
|
-
// **没有随行**。不说 = 对带图提交谎报「你的消息已投递」。
|
|
1243
681
|
const handle = `run ${outcome.taskId}`;
|
|
1244
682
|
const textOnly = `Only the TEXT of your message was handed over — a steer carries no attachments, so any images in that submission were NOT sent.`;
|
|
1245
683
|
if (outcome.delivery === 'applied') {
|
|
@@ -1261,15 +699,6 @@ function activeRunSelfHealBaseRow(outcome, signal, wayOut, following = false) {
|
|
|
1261
699
|
`Nothing was cancelled, and your message did NOT start a new turn.`);
|
|
1262
700
|
}
|
|
1263
701
|
if (outcome.delivery === 'parked_for_wake') {
|
|
1264
|
-
// run 已终结 ⇒ 它已经不占着会话了,所以「再发一次」是**真的**出路(与 rejected 臂同款)。
|
|
1265
|
-
// wake 是引擎侧的动作(POST /v1/sessions/:id/wake),客户端这一侧没有接线 —— 如实点名它、
|
|
1266
|
-
// 并说清 sema 不会去做,绝不让用户以为屏幕上会有什么在等他。
|
|
1267
|
-
// 🔴 **重复执行的披露**(二次评审 R8 [high] 的采纳半场):那条 parked 副本**没有被丢弃** ——
|
|
1268
|
-
// 将来任何人 wake 这个会话,同一条指令会再跑一次。带写/删/外部副作用的指令上这是真代价,
|
|
1269
|
-
// 必须说在同一句里。
|
|
1270
|
-
// 🔵 **驳回的那半**(「干脆别建议重发」):本客户端这一侧既没有 wake 也没有撤销 parked 消息的
|
|
1271
|
-
// 接线,不建议重发 = 用户手上一条路都没有 —— 那正是本文件存在的理由(死墙)所要消灭的形。
|
|
1272
|
-
// 取舍 = 给出唯一真出路 + 把它的代价说全,而不是把用户留在墙前。
|
|
1273
702
|
return (`${handle} had already finished, so the engine parked your message on that finished run instead of ` +
|
|
1274
703
|
`running it — a parked message is delivered only if this session is woken on the engine ` +
|
|
1275
704
|
`(POST /v1/sessions/:id/wake), which sema does not do here. That run is no longer holding this ` +
|
|
@@ -1285,8 +714,6 @@ function activeRunSelfHealBaseRow(outcome, signal, wayOut, following = false) {
|
|
|
1285
714
|
: `watch that run before sending it again.`));
|
|
1286
715
|
}
|
|
1287
716
|
case 'running-steer-failed':
|
|
1288
|
-
// 🔴 明确拒收 vs 送达未知,两句话不能互换(见 outcome 注):对一条**可能已经注入**的非幂等
|
|
1289
|
-
// 指令说「没发出去,再发一遍」,买单的是用户那边重复的副作用。
|
|
1290
717
|
if (outcome.delivery === 'rejected') {
|
|
1291
718
|
return (`The engine rejected handing your message to the running run (run ${outcome.taskId}): ${outcome.detail}. ` +
|
|
1292
719
|
`sema did NOT retry it (a steer is not safe to send twice) and it did NOT cancel that run. ` +
|
|
@@ -1300,7 +727,6 @@ function activeRunSelfHealBaseRow(outcome, signal, wayOut, following = false) {
|
|
|
1300
727
|
return (`You chose to cancel run ${outcome.taskId}. The engine confirmed it is no longer holding this session, ` +
|
|
1301
728
|
`so sema is sending your message now.`);
|
|
1302
729
|
case 'running-cancel-timeout': {
|
|
1303
|
-
// 秒数取**真等了多久**(不是预算):预算说的是打算等多久,这句话说的是已经发生的事。
|
|
1304
730
|
const waited = `${String(Math.max(1, Math.round(outcome.waitedMs / 1000)))}s`;
|
|
1305
731
|
if (outcome.aborted) {
|
|
1306
732
|
return (`sema asked the engine to cancel run ${outcome.taskId} and you interrupted while it was still ` +
|
|
@@ -1308,8 +734,6 @@ function activeRunSelfHealBaseRow(outcome, signal, wayOut, following = false) {
|
|
|
1308
734
|
`may still hold this session. Your message was NOT sent; send it again in a moment, or ${wayOut}.`);
|
|
1309
735
|
}
|
|
1310
736
|
if (!outcome.confirmedHeld) {
|
|
1311
|
-
// 🔴 一次都没读出那条 run 的状态(404 / 传输错)⇒ 只许说「确认不了」。说「它还占着」是替
|
|
1312
|
-
// 引擎下一个证不出的断言,而这条腿存在的全部理由就是不许再对用户下证不出的断言。
|
|
1313
737
|
return (`sema asked the engine to cancel run ${outcome.taskId} but could not read that run's state back ` +
|
|
1314
738
|
`within ${waited}, so it cannot confirm whether the session was released. Your message was NOT sent; ` +
|
|
1315
739
|
`send it again in a moment (if that run really is gone it will just run), or ${wayOut}.`);
|
|
@@ -1320,37 +744,26 @@ function activeRunSelfHealBaseRow(outcome, signal, wayOut, following = false) {
|
|
|
1320
744
|
}
|
|
1321
745
|
case 'running-cancel-failed':
|
|
1322
746
|
if (outcome.delivery === 'unknown') {
|
|
1323
|
-
// 🔴 送达未知 ⇒ 那条 run 可能已经在停了。说「它还占着会话」是替引擎下一个证不出的断言。
|
|
1324
747
|
return (`sema sent the cancel for run ${outcome.taskId} but could not confirm what happened to it ` +
|
|
1325
748
|
`(${outcome.detail}), and it could not read that run back as released either. That run may or may ` +
|
|
1326
749
|
`not still be holding this session. Your message was NOT sent; send it again in a moment, or ${wayOut}.`);
|
|
1327
750
|
}
|
|
1328
|
-
// 🔴 「引擎拒了这一枪」证明的只是**这一枪没生效**,证明不了那条 run 此刻还占着会话:拒收码里
|
|
1329
|
-
// 404 是 unknown-run ∪ non-owner 的不可分辨并集、409 是 checkpoint 并发决定/过期的竞态,两者都
|
|
1330
|
-
// 可能发生在会话**已经释放**之后。所以这一句只说被拒的事实,不再顺带下那个证不出的因果断言。
|
|
1331
751
|
return (`sema could not cancel run ${outcome.taskId} — the engine rejected that request (${outcome.detail}), so ` +
|
|
1332
752
|
`nothing was cancelled and sema cannot tell whether that run is still holding this session. Your message ` +
|
|
1333
753
|
`was NOT sent; send it again to find out, or ${wayOut}.`);
|
|
1334
|
-
// ── L-93 陈旧 park 的五种结局(0.53.0)────────────────────────────────────────────────────────
|
|
1335
754
|
case 'ask-run-not-found':
|
|
1336
|
-
// 待决行确已不在 + 一手复核撞 404:两个缺席证据指向同一件事,但「已解锁」仍然证不出
|
|
1337
|
-
// (该 404 是 unknown-run ∪ non-owner 的不可分辨并集)⇒ 只说已证的事实 + 「再发一次」这条
|
|
1338
|
-
// 真出路。🔴 不提 decidePath:那张卡已经不在引擎上,POST 过去只会再吃一个 404(假出路)。
|
|
1339
755
|
return (`The previous turn is recorded as parked on a decision (run ${outcome.taskId}), but the approval it ` +
|
|
1340
756
|
`was waiting on is no longer on the engine, and asking the engine about that run directly comes back ` +
|
|
1341
757
|
`not-found — a lookup that cannot separate a run that is really gone from one this session is simply ` +
|
|
1342
758
|
`not allowed to read. Either way sema has nothing it can act on here, so it did NOT cancel anything. ` +
|
|
1343
759
|
`Your message was NOT sent; send it again (if the session really is free it will just run), or ${wayOut}.`);
|
|
1344
760
|
case 'stale-park-cancelled':
|
|
1345
|
-
// 🔴 resending 形绝不说「NOT sent」:释放已被引擎确认(判据 = CLAIM_RELEASED_STATES),
|
|
1346
|
-
// 调用方下一步就是重发那条消息,说它没送出去是对着一个正在发生的动作谎报。
|
|
1347
761
|
return (`Run ${outcome.taskId} was parked on an approval that no longer exists on the engine, so there was ` +
|
|
1348
762
|
`nothing left for you to answer. You chose to cancel that run: the engine now reports it as finished, ` +
|
|
1349
763
|
`so it will not resume — whatever that turn had left to do is not coming back. sema is sending your ` +
|
|
1350
764
|
`message again now; if that run has not fully let go of the session yet, you may have to send it once ` +
|
|
1351
765
|
`more.`);
|
|
1352
766
|
case 'stale-park-cancel-timeout': {
|
|
1353
|
-
// 秒数取**真等了多久**(不是预算),与 running-cancel-timeout 同律。
|
|
1354
767
|
const waited = `${String(Math.max(1, Math.round(outcome.waitedMs / 1000)))}s`;
|
|
1355
768
|
if (outcome.aborted) {
|
|
1356
769
|
return (`sema asked the engine to cancel run ${outcome.taskId} (it was parked on an approval that no longer ` +
|
|
@@ -1359,7 +772,6 @@ function activeRunSelfHealBaseRow(outcome, signal, wayOut, following = false) {
|
|
|
1359
772
|
`was NOT sent; send it again in a moment, or ${wayOut}.`);
|
|
1360
773
|
}
|
|
1361
774
|
if (!outcome.confirmedHeld) {
|
|
1362
|
-
// 一次都没读出那条 run 的状态 ⇒ 只许说「确认不了」,绝不替引擎断言「它还占着」。
|
|
1363
775
|
return (`sema asked the engine to cancel run ${outcome.taskId} (parked on an approval that no longer exists ` +
|
|
1364
776
|
`there) but could not read that run's state back within ${waited}, so it cannot confirm whether the ` +
|
|
1365
777
|
`session was released. Your message was NOT sent; send it again in a moment (if that run really is ` +
|
|
@@ -1371,7 +783,6 @@ function activeRunSelfHealBaseRow(outcome, signal, wayOut, following = false) {
|
|
|
1371
783
|
}
|
|
1372
784
|
case 'stale-park-cancel-failed':
|
|
1373
785
|
if (outcome.delivery === 'unknown') {
|
|
1374
|
-
// 送达未知 ⇒ 那条 run 可能已经在停了;说「它还占着会话」是替引擎下一个证不出的断言。
|
|
1375
786
|
return (`sema sent the cancel for run ${outcome.taskId} (parked on an approval that no longer exists on the ` +
|
|
1376
787
|
`engine) but could not confirm what happened to it (${outcome.detail}), and it could not read that ` +
|
|
1377
788
|
`run back as released either. That run may or may not still be holding this session. Your message ` +
|
|
@@ -1381,8 +792,6 @@ function activeRunSelfHealBaseRow(outcome, signal, wayOut, following = false) {
|
|
|
1381
792
|
`so nothing was cancelled and sema cannot tell whether that run is still holding this session. Your ` +
|
|
1382
793
|
`message was NOT sent; send it again to find out, or ${wayOut}.`);
|
|
1383
794
|
case 'stale-park-wait':
|
|
1384
|
-
// 两条真出路都必须在这一行里(压噪不压把手):引擎侧 cancel 端点与宿主的 wayOut。
|
|
1385
|
-
// 🔴 这一行**不**说「答那张卡」—— 那张卡正是已经不在了的东西。
|
|
1386
795
|
if (outcome.alreadyOffered === true) {
|
|
1387
796
|
return (`Run ${outcome.taskId} is still parked (status ${outcome.status}) on an approval that no longer ` +
|
|
1388
797
|
`exists on the engine. sema already asked what to do with it and you chose to leave it alone, so it ` +
|
|
@@ -1394,33 +803,22 @@ function activeRunSelfHealBaseRow(outcome, signal, wayOut, following = false) {
|
|
|
1394
803
|
`NOT cancel it — you chose to leave it alone. Your message was NOT sent; cancel that run on the engine ` +
|
|
1395
804
|
`(POST /v1/runs/${outcome.taskId}/cancel — that ends it for good), or ${wayOut}.`);
|
|
1396
805
|
case 'running-not-found':
|
|
1397
|
-
// Inkglow-1085 P0b②:幽灵 claim —— 引擎一边说这条 run 占着会话、一边查无此 run。「已解锁」
|
|
1398
|
-
// 证不出(404 与 non-owner 同码刻意不可分辨),所以只说已证的事实 + 「再发一次」这条真出路
|
|
1399
|
-
// (与 running-cancel-timeout 读不出那格同款措辞方向:真没了就会直接跑起来)。
|
|
1400
806
|
return (`The engine reported this session as held by run ${outcome.taskId} (status running), but it no ` +
|
|
1401
807
|
`longer recognizes that run when asked directly — that claim looks stale (the run most likely no ` +
|
|
1402
808
|
`longer exists). sema did not offer to steer or cancel it: there is nothing left to act on. ` +
|
|
1403
809
|
`Your message was NOT sent; send it again (if the session really is free it will just run), or ${wayOut}.`);
|
|
1404
810
|
case 'running-settled':
|
|
1405
|
-
// 对抗复审 [high] 采纳:run 已终结而 claim 仍报 running([4664] 洞③的窗口形)——
|
|
1406
|
-
// 「wait for it to finish」对它是永远等不到的假话;真出路 = 再发一次(claim 落地即跑)。
|
|
1407
811
|
return (`Run ${outcome.taskId} has already finished (status ${outcome.status}), but the engine still ` +
|
|
1408
812
|
`reported it as holding this session — that claim looks stale or mid-release. sema did not offer ` +
|
|
1409
813
|
`to steer or cancel it: the run is over. Your message was NOT sent; send it again (once the claim ` +
|
|
1410
814
|
`clears it will just run), or ${wayOut}.`);
|
|
1411
815
|
case 'not-parked':
|
|
1412
|
-
// Inkglow-1085 P0b①:同一条 run 的三选卡已呈现过且用户选了「Do nothing」⇒ 降级成一行,
|
|
1413
|
-
// 不再整卡打断;两条真出路(引擎 cancel 端点 / wayOut)必须还在这一行里 —— 压噪不压把手。
|
|
1414
816
|
if (outcome.alreadyOffered === true) {
|
|
1415
817
|
return (`Run ${outcome.taskId} is still holding this session (status ${outcome.status}). sema already ` +
|
|
1416
818
|
`asked what to do with it and you chose to leave it alone, so it is not asking again. ` +
|
|
1417
819
|
`Your message was NOT sent; wait for that run, cancel it on the engine ` +
|
|
1418
820
|
`(POST /v1/runs/${outcome.taskId}/cancel), or ${wayOut}.`);
|
|
1419
821
|
}
|
|
1420
|
-
// 🔴 「等它跑完」这句只在**它真的在跑**的时候是真话。分诊表外的门/状态里也可能是 park
|
|
1421
|
-
// (表外 gate kind 正是走这条),而 wire 恰恰会在那种情况下带 pendingGate —— 对一个
|
|
1422
|
-
// parked run 说「wait for it to finish」是一句永远等不到的假出路。动作仍然一个字不改
|
|
1423
|
-
// (不动它),改的只是话:有待决材料时就说清它在等人、怎么答。
|
|
1424
822
|
if (signal?.pendingGate) {
|
|
1425
823
|
return (`This session is held by an earlier run (run ${outcome.taskId}, status ${outcome.status}) that is ` +
|
|
1426
824
|
`waiting on ${gateKindPhrase(signal.pendingGate.kind)} — it will not finish on its own. sema did ` +
|
|
@@ -1437,12 +835,6 @@ function activeRunSelfHealBaseRow(outcome, signal, wayOut, following = false) {
|
|
|
1437
835
|
`${wayOut}.`);
|
|
1438
836
|
}
|
|
1439
837
|
}
|
|
1440
|
-
/**
|
|
1441
|
-
* 非交互车道(headless `-p` 等)对同一事实的诚实说法:那里没有 `/clear`,默认串指的是「别再
|
|
1442
|
-
* `--resume` 到这个会话」和引擎自己的 cancel/decide 端点 —— 都是真的存在、真的能做的动作。
|
|
1443
|
-
* 这条车道没有自愈腿,材料被吞的代价是全额的:一个「答一下就解锁」的会话会被一句「去 cancel」
|
|
1444
|
-
* 打发走,而 cancel 恰恰是替用户否掉那个待决项 —— 所以 decide 入口在场时它才是首选出路。
|
|
1445
|
-
*/
|
|
1446
838
|
export function activeRunBusyHeadlessRow(signal, copy) {
|
|
1447
839
|
const override = copy?.headlessRowFor?.(signal);
|
|
1448
840
|
if (typeof override === 'string')
|
|
@@ -1453,26 +845,19 @@ export function activeRunBusyHeadlessRow(signal, copy) {
|
|
|
1453
845
|
const status = signal.activeTaskStatus;
|
|
1454
846
|
const decidePath = signal.pendingGate?.decidePath ?? null;
|
|
1455
847
|
const freshSession = copy?.freshSession ?? DEFAULT_FRESH_SESSION;
|
|
1456
|
-
// ① 引擎给了 decide 入口 ⇒ **那**才是首选出路,cancel 降为带价签的备选。
|
|
1457
848
|
if (decidePath) {
|
|
1458
849
|
return (`This session is held by an earlier run${handle}${status ? ` (status ${status})` : ''} that is parked ` +
|
|
1459
850
|
`waiting on ${gateKindPhrase(signal.pendingGate?.kind)}, so this message was NOT sent. Decide it and ` +
|
|
1460
851
|
`the session unlocks: POST ${decidePath}. Cancelling instead (${cancelPath}) also unlocks it, but it ` +
|
|
1461
852
|
`decides that item against you. Or ${freshSession}.` +
|
|
1462
|
-
// 出身归因同款(见 governanceOriginClause):这条车道更需要它 —— 没有卡可看,
|
|
1463
|
-
// 用户唯一能读到的就是这一段话。
|
|
1464
853
|
governanceOriginClause(signal));
|
|
1465
854
|
}
|
|
1466
|
-
// ② 状态在场但引擎明说无待决项 ⇒ 至少说清是谁占着、它是什么态。cancel 的代价按状态**不硬编码
|
|
1467
|
-
// 一张表**(动作/文案都不许按状态名枚举),两种可能都摆出来。
|
|
1468
855
|
if (status) {
|
|
1469
856
|
return (`This session is held by an earlier run${handle} with status ${status}, so this message was NOT sent. ` +
|
|
1470
857
|
`The engine reported no decision pending on that run: if it is still working, wait for it to finish; ` +
|
|
1471
858
|
`if it will never settle, release it with ${cancelPath} (that discards whatever it was doing). ` +
|
|
1472
859
|
`Or ${freshSession}.`);
|
|
1473
860
|
}
|
|
1474
|
-
// ③ 材料全缺(旧引擎 / store 降级)⇒ activeTaskId 与 cancel 是恒在的保底真路(SDK 6.3.0 契约)。
|
|
1475
|
-
// freshSession 覆盖在这条也生效(句首形);默认串首字母大写后与源形逐字节相同。
|
|
1476
861
|
const startOver = `${freshSession.charAt(0).toUpperCase()}${freshSession.slice(1)}`;
|
|
1477
862
|
return (`This session is locked by an earlier run${handle} that was never released, so this message was ` +
|
|
1478
863
|
`NOT sent. Nothing here clears on its own. ${startOver}, or ` +
|