@zq-silk/yui 0.2.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +603 -133
- package/README.md +806 -31
- package/dist/agent/agent.js +2 -1
- package/dist/agent/argumentPolicy.js +3 -1
- package/dist/agent/launchEnvironment.js +106 -0
- package/dist/agent/managedRuntimeEnvironment.js +34 -0
- package/dist/brief/taskBrief.js +11 -1
- package/dist/cli/agentConfigurationPicker.js +287 -0
- package/dist/cli/commandCatalog.js +488 -60
- package/dist/cli/completion.js +146 -22
- package/dist/cli/helpRenderer.js +3 -1
- package/dist/cli/interactionCandidates.js +53 -15
- package/dist/cli/interactionPolicy.js +267 -30
- package/dist/cli/interactiveSelection.js +6 -2
- package/dist/cli/invocationRouter.js +5 -1
- package/dist/cli/operatorWizard.js +87 -0
- package/dist/cli/roleOptionCatalog.js +1 -0
- package/dist/cli/roleWizard.js +185 -21
- package/dist/cli/updateCommand.js +62 -19
- package/dist/cli/updateOrchestrator.js +539 -0
- package/dist/cli/updatePorts.js +1119 -0
- package/dist/cli/upgradeCommand.js +112 -0
- package/dist/cli.js +1420 -86
- package/dist/commands/agentCommands.js +146 -3
- package/dist/commands/configCommands.js +126 -0
- package/dist/commands/controllerCommands.js +365 -0
- package/dist/commands/globalRoleCommands.js +168 -126
- package/dist/commands/jobCommands.js +18 -8
- package/dist/commands/operatorCommands.js +159 -9
- package/dist/commands/profileCommands.js +203 -0
- package/dist/commands/projectCommands.js +650 -0
- package/dist/commands/roleConfiguration.js +85 -24
- package/dist/commands/roleRuntimeGuard.js +12 -0
- package/dist/commands/roleSkillValidation.js +47 -0
- package/dist/commands/taskActor.js +127 -0
- package/dist/commands/taskCommands.js +4201 -313
- package/dist/commands/taskCompletionGate.js +131 -0
- package/dist/commands/taskContextCommand.js +244 -30
- package/dist/commands/taskInputCommands.js +177 -59
- package/dist/commands/taskIntegrationCommands.js +303 -0
- package/dist/commands/taskOverviewCommand.js +363 -0
- package/dist/commands/taskRoleRuntimeStatus.js +125 -19
- package/dist/commands/textInput.js +15 -0
- package/dist/completion/completionInstaller.js +26 -22
- package/dist/config/yuiConfig.js +4 -3
- package/dist/context/dispatchContext.js +90 -38
- package/dist/context/roleSessionContext.js +119 -0
- package/dist/controller/claudeLifecycleHook.js +203 -0
- package/dist/controller/clientRuntime.js +408 -56
- package/dist/controller/codexLifecycleHook.js +108 -0
- package/dist/controller/controller.js +1089 -32
- package/dist/controller/domainIdentity.js +505 -0
- package/dist/controller/ephemeralResourceReaper.js +131 -0
- package/dist/controller/fileSchedulerStoreAdapter.js +2153 -103
- package/dist/controller/providerHookRunFence.js +127 -0
- package/dist/controller/resourceCleanupLinux.js +286 -0
- package/dist/controller/resourceInventory.js +531 -0
- package/dist/controller/resourceInventoryLinux.js +610 -0
- package/dist/controller/runtime.js +629 -10
- package/dist/controller/runtimeEventInbox.js +564 -0
- package/dist/controller/runtimeEventProcessor.js +248 -0
- package/dist/controller/runtimeLaunchCoordinator.js +477 -0
- package/dist/controller/sessionNotify.js +121 -78
- package/dist/coordination/deadlineScheduler.js +15 -0
- package/dist/coordination/mailboxScheduler.js +108 -0
- package/dist/coordination/workMailbox.js +329 -0
- package/dist/coordination/workMailboxQueue.js +86 -0
- package/dist/core/controllerClient.js +19 -5
- package/dist/core/controllerEndpoint.js +37 -0
- package/dist/core/controllerServer.js +218 -10
- package/dist/core/protocol.js +6 -2
- package/dist/decision/decision.js +2 -1
- package/dist/doctor/doctor.js +681 -32
- package/dist/domain/validation.js +53 -0
- package/dist/errors/cliError.js +5 -3
- package/dist/event/taskEvent.js +7 -3
- package/dist/execution/codexThreadNaming.js +160 -0
- package/dist/execution/executionGroup.js +579 -0
- package/dist/executor/agentAdapter.js +255 -40
- package/dist/executor/agentConfigurationCatalog.js +326 -0
- package/dist/executor/agentConfigurationProbe.js +506 -0
- package/dist/executor/agentExecutor.js +625 -10
- package/dist/executor/codexConfigConflict.js +290 -0
- package/dist/executor/effectiveLaunch.js +340 -0
- package/dist/executor/executorRegistry.js +238 -36
- package/dist/executor/fileRoleLaunchPlanner.js +550 -40
- package/dist/executor/turnCompletion.js +126 -0
- package/dist/input/inputRequest.js +30 -9
- package/dist/integration/changeSet.js +36 -0
- package/dist/integration/checkResult.js +24 -0
- package/dist/integration/gitIntegrationService.js +695 -0
- package/dist/integration/integrationAttempt.js +142 -0
- package/dist/interaction/operatorPresentation.js +96 -0
- package/dist/lifecycle/canonicalLifecycleEvent.js +342 -0
- package/dist/lifecycle/exactRunTerminalization.js +572 -0
- package/dist/lifecycle/providerLifecycleMapping.js +190 -0
- package/dist/lifecycle/taskRoleSessionReset.js +124 -0
- package/dist/message/message.js +23 -7
- package/dist/milestone/milestone.js +2 -1
- package/dist/operator/operatorSessionHistory.js +124 -0
- package/dist/output/agentConfigurationPresentation.js +43 -0
- package/dist/output/rolePresentation.js +34 -10
- package/dist/output/terminal.js +8 -0
- package/dist/output/timePresentation.js +55 -0
- package/dist/profile/agentProfile.js +128 -0
- package/dist/repository/gitWorkspace.js +578 -24
- package/dist/repository/project.js +213 -0
- package/dist/repository/taskWorkspaceCoordinator.js +392 -0
- package/dist/repository/taskWorkspacePreparer.js +1688 -191
- package/dist/review/reviewConfig.js +11 -0
- package/dist/review/reviewRound.js +399 -0
- package/dist/review/taskFinalReviewContract.js +90 -0
- package/dist/role/role.js +124 -23
- package/dist/run/agentRun.js +155 -12
- package/dist/run/runIdentity.js +82 -0
- package/dist/runtime/exactControlPlane.js +472 -0
- package/dist/runtime/index.js +8 -0
- package/dist/runtime/lifecycleReservation.js +38 -0
- package/dist/runtime/ports.js +11 -0
- package/dist/runtime/preallocatedNativeSession.js +13 -0
- package/dist/runtime/promptEnvelope.js +30 -0
- package/dist/runtime/runtimeBinding.js +31 -0
- package/dist/runtime/runtimeOwner.js +14 -0
- package/dist/runtime/sessionLaunchRequest.js +62 -0
- package/dist/runtime/sessionTitle.js +54 -0
- package/dist/runtime/taskRuntimeIsolation.js +643 -0
- package/dist/runtime/tmuxAdapters.js +315 -0
- package/dist/runtime/turnCompletion.js +3 -0
- package/dist/runtime/validation.js +23 -0
- package/dist/scheduler/activeRoleRunDelivery.js +342 -32
- package/dist/scheduler/activeTaskProgress.js +63 -0
- package/dist/scheduler/leaderFailure.js +2 -1
- package/dist/scheduler/leaderWakeupProcessor.js +307 -66
- package/dist/scheduler/operatorInputNotificationProcessor.js +109 -46
- package/dist/scheduler/operatorNotification.js +44 -2
- package/dist/scheduler/ports.js +28 -1
- package/dist/scheduler/roleRunLiveness.js +131 -25
- package/dist/scheduler/roleRunStall.js +951 -0
- package/dist/scheduler/taskExecutionProjection.js +544 -0
- package/dist/scheduler/wakeupQueue.js +3 -0
- package/dist/setup/setupCommand.js +302 -52
- package/dist/storage/compatibleTaskStore.js +102 -0
- package/dist/storage/migration/baseline.js +78 -0
- package/dist/storage/migration/classifier.js +51 -0
- package/dist/storage/migration/compatibleCodec.js +53 -0
- package/dist/storage/migration/engine.js +147 -0
- package/dist/storage/migration/index.js +33 -0
- package/dist/storage/migration/planner.js +154 -0
- package/dist/storage/migration/productionRegistry.js +486 -0
- package/dist/storage/migration/registry.js +169 -0
- package/dist/storage/migration/report.js +54 -0
- package/dist/storage/migration/types.js +31 -0
- package/dist/storage/storageSchema.js +147 -123
- package/dist/storage/storageVersions.js +11 -0
- package/dist/storage/taskStore.js +1793 -197
- package/dist/storage/upgrade/homeClassification.js +156 -0
- package/dist/storage/upgrade/homeMigrationTarget.js +595 -0
- package/dist/storage/upgrade/offlineUpgradeInventory.js +315 -0
- package/dist/storage/upgrade/productionMigrationRegistry.js +6 -0
- package/dist/storage/upgrade/recordVersionScan.js +176 -0
- package/dist/storage/upgrade/recordVersions.js +159 -0
- package/dist/storage/upgrade/switchProgress.js +80 -0
- package/dist/storage/upgrade/upgradeOrchestrator.js +948 -0
- package/dist/storage/upgrade/upgradeReceipt.js +161 -0
- package/dist/storage/upgradeCoordination.js +186 -0
- package/dist/storage/upgradeFence.js +366 -0
- package/dist/task/task.js +132 -26
- package/dist/task/taskRecordReference.js +66 -0
- package/dist/tmux/commandExecutor.js +75 -2
- package/dist/tmux/tmuxManager.js +747 -49
- package/dist/version.js +23 -0
- package/dist/web/assets/assetManifest.js +62 -0
- package/dist/web/assets/client/app.js +631 -0
- package/dist/web/assets/client/components.js +605 -0
- package/dist/web/assets/client/dom.js +14 -0
- package/dist/web/assets/client/format.js +28 -0
- package/dist/web/assets/client/i18n.js +494 -0
- package/dist/web/assets/client/markdown.js +114 -0
- package/dist/web/assets/client/theme.js +32 -0
- package/dist/web/assets/client/view.js +458 -0
- package/dist/web/assets/fontData.js +12 -0
- package/dist/web/assets/fonts.js +12 -0
- package/dist/web/assets/shell.js +114 -0
- package/dist/web/assets/styles/cards.js +135 -0
- package/dist/web/assets/styles/layout.js +47 -0
- package/dist/web/assets/styles/markdown.js +29 -0
- package/dist/web/assets/styles/responsive.js +39 -0
- package/dist/web/assets/styles/tokens.js +101 -0
- package/dist/web/assets/styles/widgets.js +147 -0
- package/dist/web/tmuxWebTerminal.js +158 -0
- package/dist/web/webServer.js +463 -0
- package/dist/web/webSnapshot.js +148 -0
- package/dist/workItem/workItem.js +642 -23
- package/dist/workspace/gitChangeSetCapture.js +86 -0
- package/dist/workspace/workItemChangeSetManager.js +445 -0
- package/dist/worktree/managedWorkspace.js +202 -0
- package/docs/task-local-identity.md +62 -0
- package/i18n/README.zh-CN.md +406 -31
- package/package.json +10 -2
- package/skills/yui-leader/SKILL.md +601 -39
- package/skills/yui-operator/SKILL.md +255 -34
- package/skills/yui-reviewer/SKILL.md +57 -0
- package/skills/yui-worker/SKILL.md +214 -17
- package/dist/commands/repositoryCommands.js +0 -86
- package/dist/operator/operatorContext.js +0 -66
- package/dist/repository/repository.js +0 -55
- package/dist/scheduler/archivedTaskRuntime.js +0 -12
- package/dist/worktree/roleWorkspace.js +0 -62
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from "node:util";
|
|
2
|
+
import { completeProcessing } from "../coordination/workMailbox.js";
|
|
3
|
+
import { enqueueWork } from "../coordination/workMailboxQueue.js";
|
|
4
|
+
import { settleExactWorkExecution } from "../coordination/workMailboxQueue.js";
|
|
5
|
+
import { terminalizeTaskRoleRunSession } from "../executor/agentExecutor.js";
|
|
6
|
+
import { updateRoleStatus } from "../role/role.js";
|
|
7
|
+
import { createTaskEvent } from "../event/taskEvent.js";
|
|
8
|
+
import { finishReviewRound, updateReviewExecutionGroup } from "../review/reviewRound.js";
|
|
9
|
+
import { failAgentRun, yieldAgentRun } from "../run/agentRun.js";
|
|
10
|
+
import { recordExecutionLaneResult } from "../execution/executionGroup.js";
|
|
11
|
+
import { formatAgentRunReceiptId } from "../task/taskRecordReference.js";
|
|
12
|
+
import { isRuntimeLaunchReservation, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
|
|
13
|
+
import { clearMatchingLeaderStallAttention, latestRunDurableProgressAt, RUN_RECOVERY_APPLIED_EVENT, RUN_RECOVERY_REQUESTED_EVENT } from "../scheduler/roleRunStall.js";
|
|
14
|
+
import { workItemExecutionGroupById, updateWorkItemExecutionGroup, updateWorkItemStatus } from "../workItem/workItem.js";
|
|
15
|
+
/**
|
|
16
|
+
* Validate every immutable identity and frozen Project head needed before a
|
|
17
|
+
* review Run can settle any mailbox or Round state. This is deliberately
|
|
18
|
+
* read-only: callers use it as the compare-and-swap fence immediately before
|
|
19
|
+
* their aggregate mutation.
|
|
20
|
+
*/
|
|
21
|
+
export function validateExactRunReviewRound(store, run, options = {}) {
|
|
22
|
+
if (run.purpose !== "review")
|
|
23
|
+
return { disposition: "applied", round: null };
|
|
24
|
+
if (run.reviewRoundId === undefined) {
|
|
25
|
+
return { disposition: "obsolete", round: null, reason: "review-round-missing" };
|
|
26
|
+
}
|
|
27
|
+
const round = store.getReviewRound(run.taskId, run.reviewRoundId);
|
|
28
|
+
if (round === null) {
|
|
29
|
+
return { disposition: "obsolete", round: null, reason: "review-round-missing" };
|
|
30
|
+
}
|
|
31
|
+
if (!options.allowTerminal
|
|
32
|
+
&& round.status !== "pending" && round.status !== "running") {
|
|
33
|
+
return { disposition: "obsolete", round, reason: "review-round-terminal" };
|
|
34
|
+
}
|
|
35
|
+
const lane = round.executionGroup?.lanes.find(({ id }) => id === run.executionLaneId);
|
|
36
|
+
const exactReviewerRun = round.reviewerRunId === run.id || lane?.runId === run.id;
|
|
37
|
+
const exactReviewerRole = round.reviewerRoleName === run.roleName || lane?.roleName === run.roleName;
|
|
38
|
+
if (!exactReviewerRun
|
|
39
|
+
|| !exactReviewerRole
|
|
40
|
+
|| round.workItemId !== run.workItemId
|
|
41
|
+
|| round.reviewBaseCommit !== run.effective.reviewBaseCommit) {
|
|
42
|
+
return { disposition: "obsolete", round, reason: "review-round-mismatch" };
|
|
43
|
+
}
|
|
44
|
+
const laneWorkspaceRoot = lane?.workspace?.root;
|
|
45
|
+
if (run.workspace === undefined || round.workspace === undefined
|
|
46
|
+
|| (laneWorkspaceRoot === undefined && !isDeepStrictEqual(run.workspace, round.workspace))
|
|
47
|
+
|| (laneWorkspaceRoot !== undefined && run.workspace.root !== laneWorkspaceRoot)) {
|
|
48
|
+
return { disposition: "obsolete", round, reason: "review-workspace-mismatch" };
|
|
49
|
+
}
|
|
50
|
+
const storedWorkspace = run.workspace.owner.type === "execution-lane"
|
|
51
|
+
? store.getManagedWorkspace(run.workspace.owner)
|
|
52
|
+
: store.getReviewRoundWorkspace(run.taskId, round.id);
|
|
53
|
+
if (storedWorkspace === null
|
|
54
|
+
|| (run.workspace.owner.type !== "execution-lane"
|
|
55
|
+
&& !isDeepStrictEqual(storedWorkspace, round.workspace))
|
|
56
|
+
|| !isDeepStrictEqual(storedWorkspace, run.workspace)) {
|
|
57
|
+
return { disposition: "obsolete", round, reason: "review-workspace-drift" };
|
|
58
|
+
}
|
|
59
|
+
if (run.workspace.owner.type !== "execution-lane") {
|
|
60
|
+
if (storedWorkspace.owner.type !== "review-round"
|
|
61
|
+
|| storedWorkspace.owner.taskId !== run.taskId
|
|
62
|
+
|| storedWorkspace.owner.reviewRoundId !== round.id) {
|
|
63
|
+
return { disposition: "obsolete", round, reason: "review-workspace-owner-mismatch" };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (run.workspace.owner.type === "execution-lane"
|
|
67
|
+
&& (run.workspace.owner.purpose !== "review"
|
|
68
|
+
|| run.workspace.owner.executionGroupId !== run.executionGroupId
|
|
69
|
+
|| run.workspace.owner.executionLaneId !== run.executionLaneId
|
|
70
|
+
|| run.workspace.owner.reviewRoundId !== round.id)) {
|
|
71
|
+
return { disposition: "obsolete", round, reason: "review-lane-workspace-owner-mismatch" };
|
|
72
|
+
}
|
|
73
|
+
if (run.workspace.owner.type === "execution-lane") {
|
|
74
|
+
if (lane === undefined
|
|
75
|
+
|| lane.runId !== run.id
|
|
76
|
+
|| lane.roleName !== run.roleName
|
|
77
|
+
|| lane.workspace?.root !== run.workspace.root
|
|
78
|
+
|| lane.workspace.writableProjectIds.length !== run.workspace.entries.length
|
|
79
|
+
|| run.workspace.entries.some((entry) => (entry.access !== "write"
|
|
80
|
+
|| !lane.workspace.writableProjectIds.includes(entry.projectId)))) {
|
|
81
|
+
return { disposition: "obsolete", round, reason: "review-lane-workspace-lineage-mismatch" };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const item = store.getWorkItem(run.taskId, round.workItemId);
|
|
85
|
+
if (item === null) {
|
|
86
|
+
return { disposition: "obsolete", round, reason: "review-work-item-missing" };
|
|
87
|
+
}
|
|
88
|
+
const candidate = item.candidates.find(({ id }) => id === round.candidateId);
|
|
89
|
+
if (candidate === undefined) {
|
|
90
|
+
return { disposition: "obsolete", round, reason: "review-candidate-missing" };
|
|
91
|
+
}
|
|
92
|
+
const task = store.getTask(run.taskId);
|
|
93
|
+
const taskScope = (round.scope ?? "work-item") === "task";
|
|
94
|
+
const frozenProjects = taskScope
|
|
95
|
+
? round.taskCandidate?.projects
|
|
96
|
+
: candidate.gitSnapshot?.projects;
|
|
97
|
+
if (taskScope) {
|
|
98
|
+
if (task === null || round.taskCandidate === undefined) {
|
|
99
|
+
return { disposition: "obsolete", round, reason: "review-task-candidate-missing" };
|
|
100
|
+
}
|
|
101
|
+
if (frozenProjects === undefined
|
|
102
|
+
|| new Set(frozenProjects.map(({ projectId }) => projectId)).size
|
|
103
|
+
!== task.projectBindings.length
|
|
104
|
+
|| task.projectBindings.some(({ projectId }) => (!frozenProjects.some((project) => project.projectId === projectId)))) {
|
|
105
|
+
return { disposition: "obsolete", round, reason: "review-frozen-project-scope-drift" };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
else if (candidate.gitSnapshot !== undefined
|
|
109
|
+
&& candidate.gitSnapshot.reviewBaseCommit !== round.reviewBaseCommit) {
|
|
110
|
+
return { disposition: "obsolete", round, reason: "review-candidate-snapshot-drift" };
|
|
111
|
+
}
|
|
112
|
+
if (!taskScope && frozenProjects === undefined) {
|
|
113
|
+
return { disposition: "applied", round };
|
|
114
|
+
}
|
|
115
|
+
if (frozenProjects === undefined
|
|
116
|
+
|| storedWorkspace.entries.length !== frozenProjects.length) {
|
|
117
|
+
return { disposition: "obsolete", round, reason: "review-frozen-project-scope-drift" };
|
|
118
|
+
}
|
|
119
|
+
const frozenByProject = new Map(frozenProjects.map(({ projectId, commit }) => [projectId, commit]));
|
|
120
|
+
if (storedWorkspace.entries.some((entry) => (entry.access !== "write"
|
|
121
|
+
|| frozenByProject.get(entry.projectId) !== entry.baseCommit
|
|
122
|
+
|| entry.baseRef !== entry.baseCommit))) {
|
|
123
|
+
return { disposition: "obsolete", round, reason: "review-frozen-head-drift" };
|
|
124
|
+
}
|
|
125
|
+
return { disposition: "applied", round };
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Atomically terminalizes the exact ReviewRound bound to a review AgentRun.
|
|
129
|
+
* The round must still be pending/running and its reviewer identity must match
|
|
130
|
+
* the Run exactly. This is the sole review-round convergence primitive shared
|
|
131
|
+
* by the exact Run terminalization path and the pre-delivery launch-failure
|
|
132
|
+
* path: a failed review Run must never leave its Round stranded.
|
|
133
|
+
*/
|
|
134
|
+
export function terminalizeExactRunReviewRound(store, input, now) {
|
|
135
|
+
const validation = validateExactRunReviewRound(store, input.run);
|
|
136
|
+
if (validation.disposition !== "applied" || validation.round === null) {
|
|
137
|
+
return validation;
|
|
138
|
+
}
|
|
139
|
+
const reviewRound = validation.round;
|
|
140
|
+
const groupedRound = input.run.executionGroupId !== undefined
|
|
141
|
+
&& input.run.executionLaneId !== undefined
|
|
142
|
+
&& reviewRound.executionGroup !== undefined
|
|
143
|
+
? updateReviewExecutionGroup(reviewRound, recordExecutionLaneResult(reviewRound.executionGroup, input.run.executionLaneId, {
|
|
144
|
+
summary: input.outcome.summary,
|
|
145
|
+
...(input.reviewResult?.report === undefined ? {} : { report: input.reviewResult.report }),
|
|
146
|
+
...(input.reviewResult?.checks === undefined
|
|
147
|
+
? {}
|
|
148
|
+
: {
|
|
149
|
+
checks: input.reviewResult.checks.map(({ name, outcome, details }) => ({
|
|
150
|
+
name,
|
|
151
|
+
outcome,
|
|
152
|
+
...(details === undefined ? {} : { details })
|
|
153
|
+
}))
|
|
154
|
+
}),
|
|
155
|
+
...(input.reviewResult?.findings === undefined
|
|
156
|
+
? {}
|
|
157
|
+
: { findings: input.reviewResult.findings }),
|
|
158
|
+
...(input.reviewResult?.evidence === undefined
|
|
159
|
+
? {}
|
|
160
|
+
: { evidence: input.reviewResult.evidence }),
|
|
161
|
+
...(input.reviewResult?.evidenceCommit === undefined
|
|
162
|
+
? {}
|
|
163
|
+
: { evidenceCommit: input.reviewResult.evidenceCommit }),
|
|
164
|
+
...(input.reviewResult?.gitSnapshot === undefined
|
|
165
|
+
? {}
|
|
166
|
+
: { gitSnapshot: input.reviewResult.gitSnapshot })
|
|
167
|
+
}, input.outcome.status === "yielded" ? "completed" : "failed", now))
|
|
168
|
+
: reviewRound;
|
|
169
|
+
const groupedMultiLane = groupedRound.executionGroup !== undefined
|
|
170
|
+
&& (groupedRound.executionGroup.lanes.length > 1
|
|
171
|
+
|| groupedRound.executionGroup.strategy.mode === "adaptive");
|
|
172
|
+
if (groupedMultiLane && groupedRound.executionGroup !== undefined) {
|
|
173
|
+
// A panel Lane only contributes evidence. The Leader must see every
|
|
174
|
+
// terminal Lane and explicitly resolve the Group before this ReviewRound
|
|
175
|
+
// can become terminal.
|
|
176
|
+
store.saveReviewRound(input.taskId, groupedRound);
|
|
177
|
+
return { disposition: "applied", round: groupedRound };
|
|
178
|
+
}
|
|
179
|
+
const terminal = finishReviewRound(groupedRound, input.outcome.status === "yielded" ? "completed" : "failed", input.outcome.summary, now, input.reviewResult);
|
|
180
|
+
store.saveReviewRound(input.taskId, terminal);
|
|
181
|
+
return { disposition: "applied", round: terminal };
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Applies one exact application-level terminal fact inside the caller's
|
|
185
|
+
* FileTaskStore transaction. All caller-owned outcome records can therefore
|
|
186
|
+
* be saved in the same aggregate commit.
|
|
187
|
+
*/
|
|
188
|
+
export function terminalizeExactTaskRun(store, input, now) {
|
|
189
|
+
const run = store.getAgentRun(input.taskId, input.runId);
|
|
190
|
+
if (run === null)
|
|
191
|
+
return obsolete(null, "run-missing");
|
|
192
|
+
if (run.status !== "active")
|
|
193
|
+
return obsolete(run, "run-terminal");
|
|
194
|
+
if (run.taskId !== input.taskId || run.roleName !== input.roleName) {
|
|
195
|
+
return obsolete(run, "run-owner-mismatch");
|
|
196
|
+
}
|
|
197
|
+
if (run.effective.agentId !== input.agentId) {
|
|
198
|
+
return obsolete(run, "run-agent-mismatch");
|
|
199
|
+
}
|
|
200
|
+
const role = store.getRole(input.taskId, input.roleName);
|
|
201
|
+
if (role === null)
|
|
202
|
+
return obsolete(run, "role-missing");
|
|
203
|
+
const active = run.executionGroupId !== undefined && run.executionLaneId !== undefined
|
|
204
|
+
? store.getActiveExecutionLaneRun(input.taskId, run.executionGroupId, run.executionLaneId)
|
|
205
|
+
: store.getActiveAgentRun(input.taskId, input.roleName);
|
|
206
|
+
if (active?.id !== run.id)
|
|
207
|
+
return obsolete(run, "active-run-mismatch");
|
|
208
|
+
const sessions = store.getTaskRoleSessionSet(input.taskId, input.roleName);
|
|
209
|
+
if (!matchesSessionFence(sessions, input)) {
|
|
210
|
+
return obsolete(run, "session-fence-mismatch");
|
|
211
|
+
}
|
|
212
|
+
if (!matchesLaunchFence(store, sessions, input)) {
|
|
213
|
+
return obsolete(run, "launch-fence-mismatch");
|
|
214
|
+
}
|
|
215
|
+
// Validate the exact ReviewRound, Candidate, stored workspace, and frozen
|
|
216
|
+
// Project heads before any mailbox or Round write.
|
|
217
|
+
const reviewValidation = validateExactRunReviewRound(store, run);
|
|
218
|
+
if (reviewValidation.disposition !== "applied") {
|
|
219
|
+
return obsolete(run, reviewValidation.reason ?? "review-round-mismatch");
|
|
220
|
+
}
|
|
221
|
+
const roleTarget = {
|
|
222
|
+
kind: "role",
|
|
223
|
+
taskId: input.taskId,
|
|
224
|
+
roleName: input.roleName
|
|
225
|
+
};
|
|
226
|
+
if (input.mailboxDisposition === "discard") {
|
|
227
|
+
store.removeWorkMailbox(roleTarget);
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
// For review Runs the mailbox must carry an exact pending or processing
|
|
231
|
+
// entry for this Run: an absent or unrelated mailbox means the review
|
|
232
|
+
// dispatch was never recorded and the terminalization must fail closed.
|
|
233
|
+
// Execution/Leader Runs keep the historical absent-is-clean behavior so
|
|
234
|
+
// direct Leader completion and normal execution are unaffected.
|
|
235
|
+
const isReview = run.purpose === "review";
|
|
236
|
+
const mailbox = store.getWorkMailbox(roleTarget);
|
|
237
|
+
const processing = mailbox?.processing;
|
|
238
|
+
if (mailbox !== null && processing !== null && processing !== undefined) {
|
|
239
|
+
if (processing.executionRef === undefined
|
|
240
|
+
|| processing.executionRef.type !== "run"
|
|
241
|
+
|| processing.executionRef.taskId !== run.taskId
|
|
242
|
+
|| processing.executionRef.id !== run.id) {
|
|
243
|
+
return obsolete(run, "mailbox-busy");
|
|
244
|
+
}
|
|
245
|
+
store.saveWorkMailbox(completeProcessing(mailbox, processing.batchId));
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
const mailboxSettlement = settleExactWorkExecution(store, roleTarget, { type: "run", taskId: run.taskId, id: run.id });
|
|
249
|
+
if (mailboxSettlement === "absent" && isReview) {
|
|
250
|
+
return obsolete(run, "review-mailbox-missing");
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// All Run, active-pointer, Session, launch, and mailbox fences have passed.
|
|
255
|
+
// Only now may the exact ReviewRound be terminalized alongside the Run so a
|
|
256
|
+
// stale fence never leaves a Round written while the Run stays active.
|
|
257
|
+
const reviewRoundTerminalization = terminalizeExactRunReviewRound(store, {
|
|
258
|
+
taskId: input.taskId,
|
|
259
|
+
run,
|
|
260
|
+
outcome: input.outcome,
|
|
261
|
+
reviewResult: input.reviewResult
|
|
262
|
+
}, now);
|
|
263
|
+
if (reviewRoundTerminalization.disposition !== "applied") {
|
|
264
|
+
return obsolete(run, reviewRoundTerminalization.reason ?? "review-round-mismatch");
|
|
265
|
+
}
|
|
266
|
+
const terminal = input.outcome.status === "yielded"
|
|
267
|
+
? yieldAgentRun(run, input.outcome.summary, now)
|
|
268
|
+
: failAgentRun(run, input.outcome.summary, now);
|
|
269
|
+
if (run.executionGroupId !== undefined
|
|
270
|
+
&& run.executionLaneId !== undefined
|
|
271
|
+
&& run.purpose === "execution"
|
|
272
|
+
&& run.workItemId !== undefined) {
|
|
273
|
+
const item = store.getWorkItem(input.taskId, run.workItemId);
|
|
274
|
+
const group = item === null
|
|
275
|
+
? undefined
|
|
276
|
+
: workItemExecutionGroupById(item, run.executionGroupId);
|
|
277
|
+
if (item !== null && group !== undefined) {
|
|
278
|
+
const grouped = recordExecutionLaneResult(group, run.executionLaneId, {
|
|
279
|
+
summary: input.outcome.summary,
|
|
280
|
+
...(input.reviewResult?.report === undefined ? {} : { report: input.reviewResult.report }),
|
|
281
|
+
...(input.reviewResult?.checks === undefined ? {} : { checks: input.reviewResult.checks }),
|
|
282
|
+
...(input.reviewResult?.findings === undefined ? {} : { findings: input.reviewResult.findings }),
|
|
283
|
+
...(input.reviewResult?.evidence === undefined ? {} : { evidence: input.reviewResult.evidence }),
|
|
284
|
+
...(input.reviewResult?.evidenceCommit === undefined ? {} : { evidenceCommit: input.reviewResult.evidenceCommit }),
|
|
285
|
+
...(input.reviewResult?.gitSnapshot === undefined ? {} : { gitSnapshot: input.reviewResult.gitSnapshot })
|
|
286
|
+
}, input.outcome.status === "yielded" ? "completed" : "failed", now);
|
|
287
|
+
store.saveWorkItem(input.taskId, updateWorkItemExecutionGroup(item, grouped, now));
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
store.saveAgentRun(terminal);
|
|
291
|
+
if (terminal.executionGroupId !== undefined && terminal.executionLaneId !== undefined) {
|
|
292
|
+
store.clearActiveExecutionLaneRun(input.taskId, terminal.executionGroupId, terminal.executionLaneId);
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
store.clearActiveAgentRun(input.taskId, input.roleName);
|
|
296
|
+
}
|
|
297
|
+
store.saveRole(input.taskId, updateRoleStatus(role, "idle", now));
|
|
298
|
+
if (sessions !== null) {
|
|
299
|
+
store.saveTaskRoleSessionSet(terminalizeTaskRoleRunSession(sessions, {
|
|
300
|
+
agentId: input.agentId,
|
|
301
|
+
runId: input.runId,
|
|
302
|
+
receiptId: input.receiptId
|
|
303
|
+
}, now));
|
|
304
|
+
}
|
|
305
|
+
settleLaunchReservation(store, sessions, input);
|
|
306
|
+
return { disposition: "applied", run: terminal };
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Leader-controlled recovery boundary for one active AgentRun. This primitive
|
|
310
|
+
* validates every durable fence in one transaction and records only a
|
|
311
|
+
* structured request for retry/session replacement. It never writes terminal
|
|
312
|
+
* bytes, retries a provider input, kills a host, or silently rebinds a native
|
|
313
|
+
* generation. Explicit termination is the sole action that changes Run state.
|
|
314
|
+
*/
|
|
315
|
+
export function recoverExactAgentRun(store, input) {
|
|
316
|
+
return store.transaction((tx) => recoverExactAgentRunInTransaction(tx, input));
|
|
317
|
+
}
|
|
318
|
+
/** Alias named after the existing exact terminalization primitive. */
|
|
319
|
+
export const recoverExactTaskRun = recoverExactAgentRun;
|
|
320
|
+
function recoverExactAgentRunInTransaction(store, input) {
|
|
321
|
+
const current = store.getAgentRun(input.taskId, input.runId);
|
|
322
|
+
const stateChanged = (reason) => ({
|
|
323
|
+
disposition: "state-changed",
|
|
324
|
+
action: input.action,
|
|
325
|
+
run: current,
|
|
326
|
+
...(current === null ? {} : { progressAt: latestRunDurableProgressAt(store, input.taskId, input.roleName, input.runId)?.progressAt }),
|
|
327
|
+
reason
|
|
328
|
+
});
|
|
329
|
+
const task = store.getTask(input.taskId);
|
|
330
|
+
if (task === null)
|
|
331
|
+
return stateChanged("task-missing");
|
|
332
|
+
if (task.status !== "active")
|
|
333
|
+
return stateChanged("task-terminal");
|
|
334
|
+
if (current === null)
|
|
335
|
+
return stateChanged("run-missing");
|
|
336
|
+
if (current.status !== "active")
|
|
337
|
+
return stateChanged("run-terminal");
|
|
338
|
+
if (current.taskId !== input.taskId || current.roleName !== input.roleName) {
|
|
339
|
+
return stateChanged("run-owner-mismatch");
|
|
340
|
+
}
|
|
341
|
+
if (current.effective.agentId !== input.agentId
|
|
342
|
+
|| current.effective.adapterId !== input.adapterId) {
|
|
343
|
+
return stateChanged("run-launch-identity-mismatch");
|
|
344
|
+
}
|
|
345
|
+
const role = store.getRole(input.taskId, input.roleName);
|
|
346
|
+
if (role === null)
|
|
347
|
+
return stateChanged("role-missing");
|
|
348
|
+
const active = current.executionGroupId !== undefined && current.executionLaneId !== undefined
|
|
349
|
+
? store.getActiveExecutionLaneRun(input.taskId, current.executionGroupId, current.executionLaneId)
|
|
350
|
+
: store.getActiveAgentRun(input.taskId, input.roleName);
|
|
351
|
+
if (active?.id !== current.id) {
|
|
352
|
+
return stateChanged("active-run-mismatch");
|
|
353
|
+
}
|
|
354
|
+
const progress = latestRunDurableProgressAt(store, input.taskId, input.roleName, input.runId);
|
|
355
|
+
if (progress === null)
|
|
356
|
+
return stateChanged("progress-unavailable");
|
|
357
|
+
if (progress.progressAt !== input.expectedProgressAt) {
|
|
358
|
+
return {
|
|
359
|
+
...stateChanged("progress-fence-mismatch"),
|
|
360
|
+
progressAt: progress.progressAt
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
// Acceptance is a durable delivery boundary, not a Leader assertion. An
|
|
364
|
+
// accepted request must match the Run's persisted receipt; an already
|
|
365
|
+
// delivered Run cannot be reclassified as provider-rejected.
|
|
366
|
+
if ((input.providerAcceptance === "accepted" && current.deliveredAt === undefined)
|
|
367
|
+
|| (input.providerAcceptance === "rejected" && current.deliveredAt !== undefined)) {
|
|
368
|
+
return {
|
|
369
|
+
disposition: "blocked",
|
|
370
|
+
action: input.action,
|
|
371
|
+
run: current,
|
|
372
|
+
progressAt: progress.progressAt,
|
|
373
|
+
reason: "provider-acceptance-mismatch"
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
if (!matchesRecoverySessionFence(store, input)) {
|
|
377
|
+
return stateChanged("session-or-launch-fence-mismatch");
|
|
378
|
+
}
|
|
379
|
+
if (input.providerAcceptance === "ambiguous"
|
|
380
|
+
&& input.action !== "diagnose") {
|
|
381
|
+
return {
|
|
382
|
+
disposition: "blocked",
|
|
383
|
+
action: input.action,
|
|
384
|
+
run: current,
|
|
385
|
+
progressAt: progress.progressAt,
|
|
386
|
+
reason: "provider-acceptance-ambiguous"
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
if (!hasRecoveryReason(input.reason)) {
|
|
390
|
+
return {
|
|
391
|
+
disposition: "blocked",
|
|
392
|
+
action: input.action,
|
|
393
|
+
run: current,
|
|
394
|
+
progressAt: progress.progressAt,
|
|
395
|
+
reason: "recovery-reason-required"
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
const eventPayload = {
|
|
399
|
+
runId: current.id,
|
|
400
|
+
roleName: current.roleName,
|
|
401
|
+
action: input.action,
|
|
402
|
+
providerAcceptance: input.providerAcceptance,
|
|
403
|
+
progressAt: progress.progressAt,
|
|
404
|
+
...(input.nativeSessionId === undefined ? {} : { nativeSessionId: input.nativeSessionId }),
|
|
405
|
+
...(input.launchId === undefined ? {} : { launchId: input.launchId }),
|
|
406
|
+
reason: input.reason
|
|
407
|
+
};
|
|
408
|
+
const events = store.listEvents(input.taskId);
|
|
409
|
+
const alreadyRequested = events.some((event) => (event.type === RUN_RECOVERY_REQUESTED_EVENT
|
|
410
|
+
&& event.payload.runId === current.id
|
|
411
|
+
&& event.payload.action === input.action
|
|
412
|
+
&& event.payload.progressAt === progress.progressAt
|
|
413
|
+
&& event.payload.nativeSessionId === input.nativeSessionId
|
|
414
|
+
&& event.payload.launchId === input.launchId));
|
|
415
|
+
if (input.action !== "terminate") {
|
|
416
|
+
if (!alreadyRequested) {
|
|
417
|
+
store.saveEvent(input.taskId, createTaskEvent(store.nextEventId(input.taskId), input.taskId, RUN_RECOVERY_REQUESTED_EVENT, eventPayload, input.now));
|
|
418
|
+
// A non-Leader request is surfaced through the existing Leader mailbox;
|
|
419
|
+
// it is not a Task Message and is coalesced by the mailbox reason.
|
|
420
|
+
if (input.roleName !== "leader") {
|
|
421
|
+
enqueueWork(store, { kind: "role", taskId: input.taskId, roleName: "leader" }, "run-recovery-requested", input.now, [{ type: "run", taskId: input.taskId, id: current.id }]);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return {
|
|
425
|
+
disposition: "applied",
|
|
426
|
+
action: input.action,
|
|
427
|
+
run: current,
|
|
428
|
+
progressAt: progress.progressAt,
|
|
429
|
+
requiresExplicitFollowup: true
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
const terminalization = terminalizeExactTaskRun(store, {
|
|
433
|
+
taskId: input.taskId,
|
|
434
|
+
roleName: input.roleName,
|
|
435
|
+
agentId: input.agentId,
|
|
436
|
+
runId: input.runId,
|
|
437
|
+
receiptId: formatAgentRunReceiptId(input.taskId, input.runId),
|
|
438
|
+
...(input.nativeSessionId === undefined ? {} : { nativeSessionId: input.nativeSessionId }),
|
|
439
|
+
...(input.launchId === undefined ? {} : { launchId: input.launchId }),
|
|
440
|
+
outcome: { status: "failed", summary: input.reason }
|
|
441
|
+
}, input.now);
|
|
442
|
+
if (terminalization.disposition !== "applied" || terminalization.run === null) {
|
|
443
|
+
return stateChanged(terminalization.reason ?? "terminalization-fence-mismatch");
|
|
444
|
+
}
|
|
445
|
+
const terminal = terminalization.run;
|
|
446
|
+
if (terminal.purpose === "execution" && terminal.workItemId !== undefined) {
|
|
447
|
+
const item = store.getWorkItem(input.taskId, terminal.workItemId);
|
|
448
|
+
if (item !== null && !["completed", "failed", "retired"].includes(item.status)) {
|
|
449
|
+
store.saveWorkItem(input.taskId, updateWorkItemStatus(item, "failed", input.now, input.reason));
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
store.saveEvent(input.taskId, createTaskEvent(store.nextEventId(input.taskId), input.taskId, RUN_RECOVERY_APPLIED_EVENT, { ...eventPayload, status: "terminated" }, input.now));
|
|
453
|
+
clearMatchingLeaderStallAttention(store, input.taskId, input.runId);
|
|
454
|
+
if (input.roleName !== "leader") {
|
|
455
|
+
enqueueWork(store, { kind: "role", taskId: input.taskId, roleName: "leader" }, "run-recovery-terminated", input.now, [{ type: "run", taskId: input.taskId, id: input.runId }]);
|
|
456
|
+
}
|
|
457
|
+
return {
|
|
458
|
+
disposition: "applied",
|
|
459
|
+
action: input.action,
|
|
460
|
+
run: terminal,
|
|
461
|
+
progressAt: progress.progressAt
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
function matchesRecoverySessionFence(store, input) {
|
|
465
|
+
const sessions = store.getTaskRoleSessionSet(input.taskId, input.roleName);
|
|
466
|
+
// Recovery must never proceed without a durable Session fence. A missing
|
|
467
|
+
// nativeSessionId is supported for an opaque host, but its exact launchId
|
|
468
|
+
// is then the only identity that can fence the action.
|
|
469
|
+
if (sessions === null)
|
|
470
|
+
return false;
|
|
471
|
+
if (sessions.activeAgentId !== input.agentId)
|
|
472
|
+
return false;
|
|
473
|
+
const session = sessions.sessions[input.agentId];
|
|
474
|
+
if (session === undefined)
|
|
475
|
+
return false;
|
|
476
|
+
if (session.agentId !== input.agentId || session.adapterId !== input.adapterId)
|
|
477
|
+
return false;
|
|
478
|
+
if (session.status === "stopped" || session.status === "broken")
|
|
479
|
+
return false;
|
|
480
|
+
const sessionNativeSessionId = session.nativeSessionId;
|
|
481
|
+
if (sessionNativeSessionId === undefined) {
|
|
482
|
+
if (input.nativeSessionId !== undefined)
|
|
483
|
+
return false;
|
|
484
|
+
}
|
|
485
|
+
else if (input.nativeSessionId !== sessionNativeSessionId) {
|
|
486
|
+
return false;
|
|
487
|
+
}
|
|
488
|
+
let launchMatches = false;
|
|
489
|
+
if (input.launchId !== undefined && session.launchId === input.launchId) {
|
|
490
|
+
launchMatches = true;
|
|
491
|
+
}
|
|
492
|
+
const mailbox = store.getWorkMailbox(runtimeLifecycleTarget({
|
|
493
|
+
scope: "task",
|
|
494
|
+
taskId: input.taskId,
|
|
495
|
+
roleName: input.roleName
|
|
496
|
+
}));
|
|
497
|
+
if (!launchMatches
|
|
498
|
+
&& input.launchId !== undefined
|
|
499
|
+
&& isRuntimeLaunchReservation(mailbox?.processing, input.launchId)) {
|
|
500
|
+
launchMatches = true;
|
|
501
|
+
}
|
|
502
|
+
// An opaque Session has no native identity to compare, so an exact launch
|
|
503
|
+
// identity is mandatory. Native Sessions may retain the older no-launch
|
|
504
|
+
// shape; their exact native identity remains a valid fence.
|
|
505
|
+
if (sessionNativeSessionId === undefined)
|
|
506
|
+
return launchMatches;
|
|
507
|
+
if (input.nativeSessionId === undefined)
|
|
508
|
+
return launchMatches;
|
|
509
|
+
return session.launchId === undefined
|
|
510
|
+
? input.launchId === undefined || launchMatches
|
|
511
|
+
: launchMatches;
|
|
512
|
+
}
|
|
513
|
+
function hasRecoveryReason(value) {
|
|
514
|
+
return typeof value === "string" && value.includes("\0") === false && value.trim().length > 0;
|
|
515
|
+
}
|
|
516
|
+
function matchesSessionFence(sessions, input) {
|
|
517
|
+
if (sessions === null)
|
|
518
|
+
return input.nativeSessionId === undefined;
|
|
519
|
+
if (sessions.activeAgentId !== input.agentId)
|
|
520
|
+
return false;
|
|
521
|
+
const session = sessions.sessions[input.agentId];
|
|
522
|
+
if (input.nativeSessionId !== undefined) {
|
|
523
|
+
if (session?.nativeSessionId !== input.nativeSessionId)
|
|
524
|
+
return false;
|
|
525
|
+
}
|
|
526
|
+
const pending = sessions.pendingTurnCompletion;
|
|
527
|
+
if (pending !== null && (pending.agentId !== input.agentId
|
|
528
|
+
|| pending.runId !== input.runId
|
|
529
|
+
|| (input.nativeSessionId !== undefined
|
|
530
|
+
&& pending.nativeSessionId !== input.nativeSessionId)))
|
|
531
|
+
return false;
|
|
532
|
+
const inFlight = sessions.inFlight;
|
|
533
|
+
return inFlight === null || (inFlight.agentId === input.agentId
|
|
534
|
+
&& inFlight.runId === input.runId
|
|
535
|
+
&& inFlight.receiptId === input.receiptId);
|
|
536
|
+
}
|
|
537
|
+
function matchesLaunchFence(store, sessions, input) {
|
|
538
|
+
if (input.launchId === undefined)
|
|
539
|
+
return true;
|
|
540
|
+
const session = sessions?.sessions[input.agentId];
|
|
541
|
+
if (session?.launchId === input.launchId)
|
|
542
|
+
return true;
|
|
543
|
+
const mailbox = store.getWorkMailbox(runtimeLifecycleTarget({
|
|
544
|
+
scope: "task",
|
|
545
|
+
taskId: input.taskId,
|
|
546
|
+
roleName: input.roleName
|
|
547
|
+
}));
|
|
548
|
+
return isRuntimeLaunchReservation(mailbox?.processing, input.launchId);
|
|
549
|
+
}
|
|
550
|
+
function settleLaunchReservation(store, sessions, input) {
|
|
551
|
+
const target = runtimeLifecycleTarget({
|
|
552
|
+
scope: "task",
|
|
553
|
+
taskId: input.taskId,
|
|
554
|
+
roleName: input.roleName
|
|
555
|
+
});
|
|
556
|
+
const mailbox = store.getWorkMailbox(target);
|
|
557
|
+
const reservation = mailbox?.processing;
|
|
558
|
+
const session = sessions?.sessions[input.agentId];
|
|
559
|
+
const launchId = input.launchId ?? session?.launchId;
|
|
560
|
+
if (launchId === undefined || !isRuntimeLaunchReservation(reservation, launchId))
|
|
561
|
+
return;
|
|
562
|
+
const settled = completeProcessing(mailbox, reservation.batchId);
|
|
563
|
+
if (settled.processing === null && settled.pending === null) {
|
|
564
|
+
store.removeWorkMailbox(target);
|
|
565
|
+
}
|
|
566
|
+
else {
|
|
567
|
+
store.saveWorkMailbox(settled);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
function obsolete(run, reason) {
|
|
571
|
+
return { disposition: "obsolete", run, reason };
|
|
572
|
+
}
|