@zq-silk/yui 0.13.9 → 0.14.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/ARCHITECTURE.md +69 -428
- package/README.md +108 -298
- package/dist/agent/managedRuntimeEnvironment.js +2 -2
- package/dist/cli/commandCatalog.js +39 -77
- package/dist/cli/interactionCandidates.js +4 -4
- package/dist/cli/interactionPolicy.js +7 -10
- package/dist/cli/invocationRouter.js +1 -1
- package/dist/cli/updateCommand.js +6 -27
- package/dist/cli/updateOrchestrator.js +63 -484
- package/dist/cli/updatePorts.js +15 -333
- package/dist/cli/upgradeCommand.js +8 -65
- package/dist/cli.js +57 -242
- package/dist/commands/configCommands.js +11 -62
- package/dist/commands/executionAuditCommands.js +23 -23
- package/dist/commands/globalRoleCommands.js +2 -7
- package/dist/commands/jobCommands.js +1 -4
- package/dist/commands/projectCommands.js +5 -5
- package/dist/commands/releaseCommands.js +3 -10
- package/dist/commands/resourcesCommands.js +2 -2
- package/dist/commands/sessionCommands.js +2 -2
- package/dist/commands/taskActor.js +31 -39
- package/dist/commands/taskChangeSetCommands.js +5 -12
- package/dist/commands/taskCommands.js +464 -1249
- package/dist/commands/taskContextCommand.js +35 -36
- package/dist/commands/taskExecutionCommands.js +33 -46
- package/dist/commands/taskInputCommands.js +20 -42
- package/dist/commands/taskNextActionCommand.js +9 -9
- package/dist/commands/taskOverviewCommand.js +56 -95
- package/dist/commands/taskRoleRuntimeStatus.js +77 -107
- package/dist/commands/taskUpstreamCommands.js +3 -3
- package/dist/commands/taskWorkspaceCommands.js +7 -79
- package/dist/commands/telemetryCommands.js +32 -124
- package/dist/commands/workflowCommands.js +3 -3
- package/dist/completion/fileCompletionManager.js +1 -1
- package/dist/config/configCatalog.js +4 -6
- package/dist/config/yuiConfig.js +6 -43
- package/dist/context/dispatchContext.js +14 -30
- package/dist/context/roleSessionContext.js +2 -2
- package/dist/context/sessionBootstrapManifest.js +2 -2
- package/dist/context/{runContextPack.js → turnContextPack.js} +58 -73
- package/dist/context/turnInputContract.js +204 -0
- package/dist/context/wakeNotification.js +2 -2
- package/dist/controller/agentRuntimeObserver.js +18 -18
- package/dist/controller/clientRuntime.js +13 -20
- package/dist/controller/controller.js +89 -116
- package/dist/controller/fileSchedulerStoreAdapter.js +511 -742
- package/dist/controller/jobControl.js +17 -17
- package/dist/controller/resourceInventory.js +6 -6
- package/dist/controller/resourceInventoryLinux.js +38 -59
- package/dist/controller/resourceInventoryRpc.js +3 -3
- package/dist/controller/resourceInventoryWorker.js +1 -1
- package/dist/controller/runtime.js +46 -53
- package/dist/controller/runtimeEventInbox.js +23 -31
- package/dist/controller/runtimeEventProcessor.js +15 -15
- package/dist/controller/{runtimeHookRunFence.js → runtimeHookTurnFence.js} +87 -81
- package/dist/controller/runtimeLaunchCoordinator.js +3 -3
- package/dist/controller/runtimeObservationHook.js +11 -8
- package/dist/controller/sessionNotify.js +11 -11
- package/dist/controller/structuredProviderObservation.js +89 -16
- package/dist/coordination/workMailbox.js +309 -579
- package/dist/coordination/workMailboxQueue.js +5 -6
- package/dist/core/controllerClient.js +1 -91
- package/dist/core/controllerServer.js +3 -6
- package/dist/doctor/doctor.js +51 -314
- package/dist/execution/candidateConvergence.js +3 -3
- package/dist/execution/executionGroup.js +32 -33
- package/dist/execution/executionHealth.js +36 -38
- package/dist/execution/resourceBroker.js +12 -12
- package/dist/executor/agentExecutor.js +30 -265
- package/dist/executor/effectiveLaunch.js +9 -19
- package/dist/executor/executorRegistry.js +43 -17
- package/dist/executor/fileRoleLaunchPlanner.js +56 -56
- package/dist/executor/launchPlan.js +3 -3
- package/dist/executor/workspacePreflightClassification.js +16 -16
- package/dist/grant/capabilityGrant.js +20 -25
- package/dist/input/inputRequest.js +12 -12
- package/dist/integration/changeSet.js +9 -13
- package/dist/integration/changeSetManifest.js +1 -1
- package/dist/integration/gitIntegrationService.js +2 -2
- package/dist/integration/integrationQueueService.js +5 -21
- package/dist/integration/overlapDiagnostics.js +2 -2
- package/dist/interaction/operatorPresentation.js +1 -1
- package/dist/job/jobRunner.js +1 -1
- package/dist/lifecycle/canonicalLifecycleEvent.js +25 -25
- package/dist/lifecycle/{exactRunTerminalization.js → exactTurnTerminalization.js} +116 -232
- package/dist/message/message.js +6 -6
- package/dist/observability/executionAudit.js +74 -86
- package/dist/observability/faultClassification.js +7 -24
- package/dist/observability/orchestrationMetrics.js +5 -5
- package/dist/observability/runtimeIdentity.js +34 -101
- package/dist/profile/agentProfile.js +1 -1
- package/dist/release/cliHomeReleaseFence.js +3 -3
- package/dist/release/releaseWorkflowEngine.js +6 -10
- package/dist/release/releaseWorkflowPorts.js +2 -2
- package/dist/release/workflowFileLock.js +1 -1
- package/dist/repository/checkoutSwap.js +1 -1
- package/dist/repository/gitWorkspace.js +0 -13
- package/dist/repository/project.js +1 -1
- package/dist/repository/projectMaintenanceLock.js +1 -2
- package/dist/repository/taskBaseFreshness.js +13 -13
- package/dist/repository/taskWorkspaceCoordinator.js +21 -25
- package/dist/repository/taskWorkspaceIdentity.js +8 -21
- package/dist/repository/taskWorkspacePreparer.js +63 -620
- package/dist/resources/autoResourceGc.js +3 -3
- package/dist/resources/liveReferences.js +1 -1
- package/dist/resources/resourceGc.js +1 -1
- package/dist/resources/sqliteResourceRegistry.js +1 -1
- package/dist/review/reviewDecision.js +7 -7
- package/dist/review/reviewFindingLedger.js +1 -1
- package/dist/review/reviewOutcomeClassifier.js +35 -50
- package/dist/review/reviewRound.js +14 -25
- package/dist/review/reviewerAvailability.js +4 -4
- package/dist/review/taskFinalReviewContractResolution.js +41 -0
- package/dist/role/role.js +6 -3
- package/dist/runtime/agentDriver.js +4 -0
- package/dist/runtime/agentHost.js +151 -36
- package/dist/runtime/builtinAgentDrivers.js +79 -2
- package/dist/runtime/codexAppServerRuntime.js +122 -0
- package/dist/runtime/continuationManager.js +3 -5
- package/dist/runtime/exactControlPlane.js +17 -42
- package/dist/runtime/firstProgressAdvisory.js +1 -1
- package/dist/runtime/index.js +2 -2
- package/dist/runtime/launchDiagnostics.js +2 -2
- package/dist/runtime/lifecycleReservation.js +4 -4
- package/dist/runtime/processExitObservation.js +1 -1
- package/dist/runtime/processExitOutbox.js +0 -32
- package/dist/runtime/promptEnvelope.js +9 -9
- package/dist/runtime/providerContinuation.js +2 -2
- package/dist/runtime/providerContinuationReconciliationService.js +2 -2
- package/dist/runtime/providerRuntimeIdentity.js +76 -24
- package/dist/runtime/recentTurnIds.js +38 -0
- package/dist/runtime/runtimeContinuationProjection.js +2 -2
- package/dist/runtime/runtimeHealthPolicy.js +1 -1
- package/dist/runtime/runtimeObservation.js +58 -21
- package/dist/runtime/runtimeProjection.js +19 -27
- package/dist/runtime/sessionLaunchRequest.js +2 -2
- package/dist/runtime/structuredProviderHost.js +177 -12
- package/dist/runtime/tmuxAdapters.js +46 -11
- package/dist/scheduler/activeRoleTurnDelivery.js +245 -0
- package/dist/scheduler/leaderWakeupProcessor.js +147 -445
- package/dist/scheduler/operatorEvent.js +4 -6
- package/dist/scheduler/resourceQueueProjection.js +71 -0
- package/dist/scheduler/{roleRunLiveness.js → roleTurnLiveness.js} +24 -28
- package/dist/scheduler/{roleRunStall.js → roleTurnStall.js} +162 -247
- package/dist/scheduler/taskExecutionProjection.js +99 -99
- package/dist/scheduler/taskObservabilityProjection.js +10 -10
- package/dist/scheduler/taskWake.js +4 -4
- package/dist/scheduler/wakeReason.js +6 -10
- package/dist/scheduler/wakeupQueue.js +0 -9
- package/dist/setup/setupCommand.js +2 -2
- package/dist/storage/currentTaskStore.js +46 -0
- package/dist/storage/persistenceWorker.js +1 -1
- package/dist/storage/sqliteSchema.js +81 -485
- package/dist/storage/sqliteStore.js +206 -365
- package/dist/storage/storageSchema.js +1 -15
- package/dist/storage/storageVersions.js +8 -9
- package/dist/storage/storeRpc.js +10 -34
- package/dist/storage/taskStore.js +63 -3415
- package/dist/storage/upgrade/recordVersions.js +55 -149
- package/dist/storage/upgrade/upgradeOrchestrator.js +89 -1752
- package/dist/task/completionReadiness.js +5 -5
- package/dist/task/deliveryGuard.js +10 -10
- package/dist/task/nextAction.js +74 -77
- package/dist/task/task.js +2 -4
- package/dist/task/taskRecordReference.js +3 -3
- package/dist/task/taskRecordRetirement.js +1 -1
- package/dist/telemetry/sqliteTelemetryStore.js +53 -53
- package/dist/telemetry/telemetryConfig.js +14 -14
- package/dist/telemetry/telemetryWiring.js +8 -14
- package/dist/turn/turn.js +356 -0
- package/dist/{run/runIdentity.js → turn/turnIdentity.js} +4 -4
- package/dist/verification/gateArtifactStore.js +2 -2
- package/dist/web/assets/client/app.js +1 -1
- package/dist/web/assets/client/components.js +29 -54
- package/dist/web/assets/client/i18n.js +17 -37
- package/dist/web/assets/client/view.js +37 -37
- package/dist/web/assets/shell.js +1 -1
- package/dist/web/assets/styles/cards.js +5 -5
- package/dist/web/assets/styles/widgets.js +1 -2
- package/dist/web/webSnapshot.js +65 -60
- package/dist/workItem/workItem.js +11 -11
- package/dist/workspace/workItemChangeSetManager.js +1 -1
- package/docs/task-local-identity.md +5 -5
- package/i18n/README.zh-CN.md +41 -54
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +85 -83
- package/skills/yui-operator/SKILL.md +16 -16
- package/skills/yui-reviewer/SKILL.md +10 -10
- package/skills/yui-runtime/SKILL.md +17 -18
- package/skills/yui-worker/SKILL.md +16 -18
- package/dist/context/runContextContract.js +0 -162
- package/dist/executor/turnCompletion.js +0 -126
- package/dist/review/taskFinalReviewContractEvent.js +0 -1
- package/dist/review/taskFinalReviewContractRebind.js +0 -367
- package/dist/run/agentRun.js +0 -356
- package/dist/run/rejectedYieldAttempt.js +0 -221
- package/dist/run/runControlRequest.js +0 -50
- package/dist/run/yieldReceipt.js +0 -65
- package/dist/runtime/turnCompletion.js +0 -3
- package/dist/scheduler/actionability.js +0 -323
- package/dist/scheduler/activeRoleRunDelivery.js +0 -868
- package/dist/scheduler/activeTaskProgress.js +0 -126
- package/dist/storage/compatibleTaskStore.js +0 -248
- package/dist/storage/migration/baseline.js +0 -78
- package/dist/storage/migration/classifier.js +0 -51
- package/dist/storage/migration/compatibleCodec.js +0 -53
- package/dist/storage/migration/engine.js +0 -147
- package/dist/storage/migration/index.js +0 -33
- package/dist/storage/migration/planner.js +0 -154
- package/dist/storage/migration/productionRegistry.js +0 -3931
- package/dist/storage/migration/registry.js +0 -169
- package/dist/storage/migration/report.js +0 -54
- package/dist/storage/migration/types.js +0 -31
- package/dist/storage/upgrade/homeClassification.js +0 -353
- package/dist/storage/upgrade/homeMigrationTarget.js +0 -595
- package/dist/storage/upgrade/migrationReceipt.js +0 -67
- package/dist/storage/upgrade/offlineUpgradeInventory.js +0 -453
- package/dist/storage/upgrade/productionMigrationRegistry.js +0 -6
- package/dist/storage/upgrade/pseudoLayoutRepair.js +0 -241
- package/dist/storage/upgrade/recordVersionScan.js +0 -176
- package/dist/storage/upgrade/sqliteFileSet.js +0 -50
- package/dist/storage/upgrade/sqliteMigrationTarget.js +0 -375
- package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +0 -305
- package/dist/storage/upgrade/sqliteStateMigration.js +0 -918
- package/dist/storage/upgrade/switchProgress.js +0 -80
- package/dist/storage/upgrade/upgradeReceipt.js +0 -161
- package/dist/storage/upgradeCoordination.js +0 -187
- package/dist/storage/upgradeFence.js +0 -366
- package/dist/telemetry/telemetryCompaction.js +0 -252
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { usageError } from "../errors/cliError.js";
|
|
2
|
-
import {
|
|
2
|
+
import { isRoleTurnStalled, latestStallProgressAt } from "../scheduler/roleTurnStall.js";
|
|
3
3
|
import { formatTimestamp } from "../output/timePresentation.js";
|
|
4
4
|
import { pendingWakeupProjection } from "../storage/taskStore.js";
|
|
5
5
|
import { defaultTableWidth, renderTable } from "../output/table.js";
|
|
6
6
|
import { projectTaskExecutionFromFacts } from "../scheduler/taskExecutionProjection.js";
|
|
7
7
|
import { resolveRuntimeHealth } from "../config/yuiConfig.js";
|
|
8
|
+
import { projectNextAction } from "../task/nextAction.js";
|
|
8
9
|
export function parseTaskListOptions(args) {
|
|
9
10
|
const allowed = new Set(["--all", "--verbose"]);
|
|
10
11
|
if (args.some((argument) => !allowed.has(argument))
|
|
@@ -61,7 +62,7 @@ function buildTaskOverviewEntry(task, store, now, runtimeHealthPolicy) {
|
|
|
61
62
|
const workItems = store.listWorkItems(task.id);
|
|
62
63
|
const inputRequests = store.listInputRequests(task.id);
|
|
63
64
|
const openInputRequests = inputRequests.filter((request) => request.status === "open");
|
|
64
|
-
const
|
|
65
|
+
const turns = store.listTurns(task.id);
|
|
65
66
|
const events = store.listEvents(task.id);
|
|
66
67
|
const leaderFailure = store.getLeaderFailure(task.id);
|
|
67
68
|
const leaderMailbox = store.getWorkMailbox({ kind: "role", taskId: task.id, roleName: "leader" });
|
|
@@ -73,14 +74,13 @@ function buildTaskOverviewEntry(task, store, now, runtimeHealthPolicy) {
|
|
|
73
74
|
const session = store.getRoleSession(task.id, role.name);
|
|
74
75
|
return session === null ? [] : [{ roleName: role.name, ...session }];
|
|
75
76
|
});
|
|
76
|
-
const attention = collectAttention(task,
|
|
77
|
+
const attention = collectAttention(task, turns, events, leaderFailure);
|
|
77
78
|
const blockers = collectBlockers(workItems, openInputRequests, attention);
|
|
78
|
-
const legacyNext = deriveNextAction(task, brief, workItems, openInputRequests, blockers, pendingWakeup);
|
|
79
79
|
const leader = {
|
|
80
80
|
role: "leader",
|
|
81
81
|
roleStatus: leaderRole === null
|
|
82
82
|
? "missing"
|
|
83
|
-
:
|
|
83
|
+
: turns.some((run) => run.roleName === "leader" && run.status === "active")
|
|
84
84
|
? "running"
|
|
85
85
|
: roleSessions.find((session) => session.roleName === "leader")?.status ?? "idle",
|
|
86
86
|
summary: brief?.leaderSummary ?? null,
|
|
@@ -90,28 +90,29 @@ function buildTaskOverviewEntry(task, store, now, runtimeHealthPolicy) {
|
|
|
90
90
|
summaryStatus: brief === null ? "missing" : "available"
|
|
91
91
|
};
|
|
92
92
|
const counts = countWorkItems(workItems);
|
|
93
|
-
const
|
|
93
|
+
const runtimeTurns = turns
|
|
94
94
|
.filter((run) => run.status === "active")
|
|
95
95
|
.map((run) => ({
|
|
96
96
|
id: run.id,
|
|
97
97
|
roleName: run.roleName,
|
|
98
98
|
status: run.status,
|
|
99
|
-
|
|
100
|
-
|
|
99
|
+
runtime: roleSessions.some((session) => (session.roleName === run.roleName
|
|
100
|
+
&& session.agentId === run.effective.agentId
|
|
101
|
+
&& session.status !== "ended")) ? "session-active" : "starting",
|
|
101
102
|
createdAt: run.createdAt,
|
|
102
103
|
updatedAt: run.updatedAt
|
|
103
104
|
}));
|
|
104
105
|
const runtime = {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
pendingDeliveryCount:
|
|
106
|
+
activeTurns: runtimeTurns,
|
|
107
|
+
activeTurnCount: runtimeTurns.length,
|
|
108
|
+
pendingDeliveryCount: runtimeTurns.filter((run) => run.runtime === "starting").length
|
|
108
109
|
};
|
|
109
110
|
// Fold the execution projection from the facts already read above instead of
|
|
110
111
|
// reading the store a second time for the same unchanged revision.
|
|
111
112
|
const execution = projectTaskExecutionFromFacts({
|
|
112
113
|
task,
|
|
113
114
|
roles,
|
|
114
|
-
|
|
115
|
+
turns,
|
|
115
116
|
workItems,
|
|
116
117
|
inputRequests,
|
|
117
118
|
reviewRounds,
|
|
@@ -127,21 +128,15 @@ function buildTaskOverviewEntry(task, store, now, runtimeHealthPolicy) {
|
|
|
127
128
|
now,
|
|
128
129
|
runtimeHealthPolicy
|
|
129
130
|
});
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
: execution.
|
|
138
|
-
|
|
139
|
-
action: execution.action,
|
|
140
|
-
owner: execution.owner,
|
|
141
|
-
kind: "execution",
|
|
142
|
-
summary: execution.summary
|
|
143
|
-
}
|
|
144
|
-
: legacyNext;
|
|
131
|
+
const nextActionFacts = store.readNextActionFacts(task.id);
|
|
132
|
+
if (nextActionFacts === null) {
|
|
133
|
+
throw new Error(`Task disappeared while building its overview: ${task.id}.`);
|
|
134
|
+
}
|
|
135
|
+
const next = overviewNextAction(task, workItems, execution, projectNextAction({
|
|
136
|
+
...nextActionFacts,
|
|
137
|
+
currentTaskReviewCandidate: null,
|
|
138
|
+
executionGroups: execution.executionGroups
|
|
139
|
+
}));
|
|
145
140
|
return {
|
|
146
141
|
...task,
|
|
147
142
|
brief,
|
|
@@ -163,7 +158,7 @@ function buildTaskOverviewEntry(task, store, now, runtimeHealthPolicy) {
|
|
|
163
158
|
execution
|
|
164
159
|
};
|
|
165
160
|
}
|
|
166
|
-
function collectAttention(task,
|
|
161
|
+
function collectAttention(task, turns, events, failure) {
|
|
167
162
|
const items = [];
|
|
168
163
|
if (failure !== null) {
|
|
169
164
|
items.push({
|
|
@@ -175,21 +170,21 @@ function collectAttention(task, runs, events, failure) {
|
|
|
175
170
|
updatedAt: failure.lastFailedAt
|
|
176
171
|
});
|
|
177
172
|
}
|
|
178
|
-
for (const
|
|
179
|
-
if (
|
|
180
|
-
||
|
|
181
|
-
|| !
|
|
173
|
+
for (const turn of turns) {
|
|
174
|
+
if (turn.roleName !== "leader"
|
|
175
|
+
|| turn.status !== "active"
|
|
176
|
+
|| !isRoleTurnStalled(events, turn.id))
|
|
182
177
|
continue;
|
|
183
|
-
const progressAt = latestStallProgressAt(events,
|
|
178
|
+
const progressAt = latestStallProgressAt(events, turn.id) ?? turn.updatedAt;
|
|
184
179
|
items.push({
|
|
185
180
|
kind: "leader-stalled",
|
|
186
|
-
id: `leader-stall:${
|
|
181
|
+
id: `leader-stall:${turn.id}:${progressAt}`,
|
|
187
182
|
status: "needs-attention",
|
|
188
183
|
owner: "operator",
|
|
189
|
-
summary: `
|
|
184
|
+
summary: `Turn ${turn.id} for ${turn.roleName} has no durable progress after ${progressAt}.`,
|
|
190
185
|
updatedAt: progressAt,
|
|
191
|
-
|
|
192
|
-
roleName:
|
|
186
|
+
turnId: turn.id,
|
|
187
|
+
roleName: turn.roleName
|
|
193
188
|
});
|
|
194
189
|
}
|
|
195
190
|
return items.sort((left, right) => (Date.parse(left.updatedAt) - Date.parse(right.updatedAt)
|
|
@@ -266,64 +261,30 @@ function collectBlockers(workItems, openInputRequests, attention) {
|
|
|
266
261
|
}
|
|
267
262
|
return blockers;
|
|
268
263
|
}
|
|
269
|
-
function
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
action: workBlocker.action,
|
|
294
|
-
owner: workBlocker.owner,
|
|
295
|
-
kind: "work",
|
|
296
|
-
id: workBlocker.id,
|
|
297
|
-
summary: workBlocker.summary
|
|
298
|
-
};
|
|
299
|
-
}
|
|
300
|
-
if (pendingWakeup !== null) {
|
|
301
|
-
return {
|
|
302
|
-
action: "process-leader-wakeup",
|
|
303
|
-
owner: "leader",
|
|
304
|
-
kind: "wakeup",
|
|
305
|
-
summary: pendingWakeup.reasons.join(", ")
|
|
306
|
-
};
|
|
307
|
-
}
|
|
308
|
-
const pending = workItems.find((item) => item.status === "pending");
|
|
309
|
-
if (pending !== undefined) {
|
|
310
|
-
return {
|
|
311
|
-
action: "start-work-item",
|
|
312
|
-
owner: pending.assignee ?? "leader",
|
|
313
|
-
kind: "work",
|
|
314
|
-
id: pending.id,
|
|
315
|
-
summary: pending.title
|
|
316
|
-
};
|
|
317
|
-
}
|
|
318
|
-
if (task.status === "active" && brief === null) {
|
|
319
|
-
return {
|
|
320
|
-
action: "update-leader-summary",
|
|
321
|
-
owner: "leader",
|
|
322
|
-
kind: "summary",
|
|
323
|
-
summary: "No persisted Task Brief is available."
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
return null;
|
|
264
|
+
function overviewNextAction(task, workItems, execution, action) {
|
|
265
|
+
if (task.status !== "active" && task.status !== "draft")
|
|
266
|
+
return null;
|
|
267
|
+
const primary = action.refs[0];
|
|
268
|
+
const workItem = action.refs
|
|
269
|
+
.filter(({ kind }) => kind === "work-item")
|
|
270
|
+
.map(({ id }) => workItems.find((item) => item.id === id))
|
|
271
|
+
.find((item) => item !== undefined);
|
|
272
|
+
const kind = primary?.kind === "input-request"
|
|
273
|
+
? "input"
|
|
274
|
+
: primary?.kind === "work-item"
|
|
275
|
+
? "work"
|
|
276
|
+
: action.kind.includes("attention") || action.kind.includes("inconsistency")
|
|
277
|
+
? "attention"
|
|
278
|
+
: "execution";
|
|
279
|
+
return {
|
|
280
|
+
action: action.kind,
|
|
281
|
+
owner: action.kind === "resolve-input"
|
|
282
|
+
? "user"
|
|
283
|
+
: workItem?.assignee ?? execution.owner,
|
|
284
|
+
kind,
|
|
285
|
+
...(primary === undefined ? {} : { id: primary.id }),
|
|
286
|
+
summary: action.reason
|
|
287
|
+
};
|
|
327
288
|
}
|
|
328
289
|
function countWorkItems(items) {
|
|
329
290
|
const counts = {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { isDeepStrictEqual } from "node:util";
|
|
2
|
-
import { agentRunDeliveryReceiptId } from "../run/agentRun.js";
|
|
3
2
|
import { hasRuntimeCleanupObligation, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
|
|
4
|
-
import {
|
|
3
|
+
import { isRoleTurnStalled, latestStallProgressAt } from "../scheduler/roleTurnStall.js";
|
|
5
4
|
import { createRuntimeObservation, runtimeObservationFromTaskEvent } from "../runtime/runtimeObservation.js";
|
|
6
|
-
import { classifyRuntimeHealth,
|
|
7
|
-
import {
|
|
5
|
+
import { classifyRuntimeHealth, projectRuntimeObservation, projectRuntimeTaskEvents, runtimeDisplayStatus } from "../runtime/runtimeProjection.js";
|
|
6
|
+
import { latestTurnDurableProgressAt } from "../scheduler/roleTurnStall.js";
|
|
8
7
|
import { resolveRuntimeHealth } from "../config/yuiConfig.js";
|
|
9
8
|
import { builtinDriverIdForAdapter } from "../runtime/builtinAgentDrivers.js";
|
|
9
|
+
import { formatTurnReceiptId } from "../task/taskRecordReference.js";
|
|
10
10
|
import { operationalTaskRecords } from "../task/taskRecordRetirement.js";
|
|
11
11
|
import { projectSessionTokenMetrics, resolveSessionTokenIdentity } from "../runtime/sessionTokenMetrics.js";
|
|
12
12
|
export function inspectTaskRoleRuntimeStatuses(taskId, roles, store, panes, now = new Date()) {
|
|
@@ -21,12 +21,12 @@ export function inspectTaskRoleRuntimeStatuses(taskId, roles, store, panes, now
|
|
|
21
21
|
return roles.map((role) => inspectTaskRoleRuntimeStatus(taskId, role, store, panesByRole.get(role.name), role.name === "leader" ? taskOpenInputRequestCount : 0, now));
|
|
22
22
|
}
|
|
23
23
|
export function renderTaskRoleRuntimeStatus(status) {
|
|
24
|
-
const
|
|
24
|
+
const activeTurn = status.activeTurn === null
|
|
25
25
|
? "-"
|
|
26
|
-
: `${status.
|
|
27
|
-
const
|
|
26
|
+
: `${status.activeTurn.id} (${activeTurnDeliveryLabel(status.activeTurn)})`;
|
|
27
|
+
const lastTurn = status.activeTurn !== null || status.lastTurn === null
|
|
28
28
|
? undefined
|
|
29
|
-
: `${status.
|
|
29
|
+
: `${status.lastTurn.id} (${status.lastTurn.status}${status.lastTurn.result === undefined ? "" : ` at ${status.lastTurn.result.completedAt}`})`;
|
|
30
30
|
const activeWork = status.activeWork === null
|
|
31
31
|
? "-"
|
|
32
32
|
: `${status.activeWork.id} (${status.activeWork.status}) ${status.activeWork.title}`;
|
|
@@ -84,15 +84,15 @@ export function renderTaskRoleRuntimeStatus(status) {
|
|
|
84
84
|
` Desired drift ${status.effectiveLaunch === null
|
|
85
85
|
? "-"
|
|
86
86
|
: status.launchDrift ? "pending next launch" : "none"}`,
|
|
87
|
-
`
|
|
87
|
+
` Turn/session ${status.turnSessionDrift ? "snapshot mismatch" : "snapshot consistent"}`,
|
|
88
88
|
` Active work ${activeWork}`,
|
|
89
|
-
` Active run ${
|
|
90
|
-
...(
|
|
91
|
-
`
|
|
89
|
+
` Active run ${activeTurn}`,
|
|
90
|
+
...(lastTurn === undefined ? [] : [` Last turn ${lastTurn}`]),
|
|
91
|
+
` Turn attention ${status.stall.active
|
|
92
92
|
? `needs-attention (${status.stall.kind ?? "workflow-not-progressing"}; no workflow progress since ${status.stall.progressAt ?? "unknown"})`
|
|
93
93
|
: "none"}`,
|
|
94
94
|
` Native session ${nativeSession}`,
|
|
95
|
-
`
|
|
95
|
+
` AgentRuntime ${runtime}`,
|
|
96
96
|
` Session tokens ${sessionTokens}`,
|
|
97
97
|
` Runtime cleanup ${status.runtimeCleanupPending ? "pending" : "none"}`,
|
|
98
98
|
` tmux pane ${tmux}`,
|
|
@@ -103,15 +103,15 @@ export function renderTaskRoleRuntimeStatus(status) {
|
|
|
103
103
|
export function taskRoleActiveWorkLabel(status) {
|
|
104
104
|
if (status.activeWork !== null)
|
|
105
105
|
return `${status.activeWork.id}: ${status.activeWork.title}`;
|
|
106
|
-
return status.
|
|
106
|
+
return status.activeTurn === null ? "-" : status.activeTurn.id;
|
|
107
107
|
}
|
|
108
|
-
/** Issue 09: compact last-
|
|
109
|
-
export function
|
|
110
|
-
if (status.
|
|
111
|
-
return `${status.
|
|
112
|
-
if (status.
|
|
108
|
+
/** Issue 09: compact last-Turn outcome label for the Role list table. */
|
|
109
|
+
export function taskRoleLastTurnLabel(status) {
|
|
110
|
+
if (status.activeTurn !== null)
|
|
111
|
+
return `${status.activeTurn.id} ${status.activeTurn.status}`;
|
|
112
|
+
if (status.lastTurn === null)
|
|
113
113
|
return "-";
|
|
114
|
-
return `${status.
|
|
114
|
+
return `${status.lastTurn.id} ${status.lastTurn.status}`;
|
|
115
115
|
}
|
|
116
116
|
export function taskRoleNativeSessionLabel(status) {
|
|
117
117
|
if (status.runtimeCleanupPending && status.nativeSession === null)
|
|
@@ -140,26 +140,26 @@ export function taskRoleTmuxLabel(status) {
|
|
|
140
140
|
: `running (${status.tmux.currentCommand})`;
|
|
141
141
|
}
|
|
142
142
|
function inspectTaskRoleRuntimeStatus(taskId, role, store, pane, openInputRequestCount, now) {
|
|
143
|
-
const
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
const
|
|
143
|
+
const activeTurn = store.getActiveTurn(taskId, role.name);
|
|
144
|
+
// The last Turn outcome is a separate axis from the Session lifecycle. A
|
|
145
|
+
// Session that stops after its Turn completed must not retroactively turn
|
|
146
|
+
// that Turn into a failure; the status display keeps both visible.
|
|
147
|
+
const lastTurn = operationalTaskRecords(store.listTurns(taskId), store.listEvents(taskId), "turn")
|
|
148
148
|
.filter((candidate) => candidate.roleName === role.name)
|
|
149
149
|
.sort((left, right) => Date.parse(right.updatedAt) - Date.parse(left.updatedAt))[0]
|
|
150
150
|
?? null;
|
|
151
|
-
const activeWork =
|
|
151
|
+
const activeWork = activeTurn?.workItemId === undefined
|
|
152
152
|
? null
|
|
153
|
-
: store.getWorkItem(taskId,
|
|
153
|
+
: store.getWorkItem(taskId, activeTurn.workItemId);
|
|
154
154
|
const sessions = store.getTaskRoleSessionSet(taskId, role.name);
|
|
155
|
-
const effectiveAgentId =
|
|
155
|
+
const effectiveAgentId = activeTurn?.effective.agentId ?? sessions?.activeAgentId;
|
|
156
156
|
const nativeSession = effectiveAgentId === undefined
|
|
157
157
|
? null
|
|
158
158
|
: sessions?.sessions[effectiveAgentId] ?? null;
|
|
159
|
-
const effectiveLaunch =
|
|
160
|
-
const
|
|
159
|
+
const effectiveLaunch = activeTurn?.effective ?? nativeSession?.effective ?? null;
|
|
160
|
+
const turnSessionDrift = activeTurn !== null
|
|
161
161
|
&& nativeSession !== null
|
|
162
|
-
&& !isDeepStrictEqual(
|
|
162
|
+
&& !isDeepStrictEqual(activeTurn.effective, nativeSession.effective);
|
|
163
163
|
const recovery = inspectTaskRoleSessionRecovery(taskId, role.name, store);
|
|
164
164
|
const tmux = pane === undefined
|
|
165
165
|
? { state: "missing" }
|
|
@@ -170,13 +170,13 @@ function inspectTaskRoleRuntimeStatus(taskId, role, store, pane, openInputReques
|
|
|
170
170
|
...(pane.pid === undefined ? {} : { pid: pane.pid }),
|
|
171
171
|
currentCommand: pane.currentCommand
|
|
172
172
|
};
|
|
173
|
-
// The active
|
|
173
|
+
// The active Turn snapshot is authoritative for the live Role session. It
|
|
174
174
|
// may point at a ReviewRound-owned workspace, which is intentionally
|
|
175
175
|
// distinct from the WorkItem Develop workspace.
|
|
176
|
-
const managedWorkspace =
|
|
177
|
-
?? (
|
|
176
|
+
const managedWorkspace = activeTurn?.workspace
|
|
177
|
+
?? (activeTurn?.workItemId === undefined
|
|
178
178
|
? store.getTaskWorkspace(taskId)
|
|
179
|
-
: store.getWorkItemWorkspace(taskId,
|
|
179
|
+
: store.getWorkItemWorkspace(taskId, activeTurn.workItemId));
|
|
180
180
|
const workspace = managedWorkspace === null
|
|
181
181
|
? { managed: false, path: role.workspace }
|
|
182
182
|
: { ...managedWorkspace, managed: true };
|
|
@@ -184,16 +184,16 @@ function inspectTaskRoleRuntimeStatus(taskId, role, store, pane, openInputReques
|
|
|
184
184
|
const sessionTokens = projectSessionTokenMetrics(events, resolveSessionTokenIdentity(nativeSession === null
|
|
185
185
|
? null
|
|
186
186
|
: { taskId, roleName: role.name, ...nativeSession }));
|
|
187
|
-
const runtime = projectTaskRoleRuntime(
|
|
188
|
-
const stalled =
|
|
189
|
-
const stallProgressAt =
|
|
187
|
+
const runtime = projectTaskRoleRuntime(activeTurn, nativeSession, tmux, events, store.getWorkMailbox({ kind: "role", taskId, roleName: role.name }), store, taskId, role.name, now);
|
|
188
|
+
const stalled = activeTurn !== null && isRoleTurnStalled(events, activeTurn.id);
|
|
189
|
+
const stallProgressAt = activeTurn === null
|
|
190
190
|
? undefined
|
|
191
|
-
: latestStallProgressAt(events,
|
|
192
|
-
const stallKind =
|
|
191
|
+
: latestStallProgressAt(events, activeTurn.id);
|
|
192
|
+
const stallKind = activeTurn === null
|
|
193
193
|
? undefined
|
|
194
|
-
: latestStallKind(events,
|
|
195
|
-
const health = calculateHealth(role,
|
|
196
|
-
const stall =
|
|
194
|
+
: latestStallKind(events, activeTurn.id);
|
|
195
|
+
const health = calculateHealth(role, activeTurn, lastTurn, nativeSession, recovery.runtimeCleanupPending, tmux, openInputRequestCount, stalled, runtime);
|
|
196
|
+
const stall = activeTurn === null
|
|
197
197
|
? { active: false }
|
|
198
198
|
: {
|
|
199
199
|
active: stalled,
|
|
@@ -209,12 +209,12 @@ function inspectTaskRoleRuntimeStatus(taskId, role, store, pane, openInputReques
|
|
|
209
209
|
effectiveLaunch,
|
|
210
210
|
launchDrift: effectiveLaunch !== null
|
|
211
211
|
&& effectiveLaunch.sourceDesiredRevision !== role.launchRevision,
|
|
212
|
-
|
|
212
|
+
turnSessionDrift,
|
|
213
213
|
...health,
|
|
214
214
|
openInputRequestCount,
|
|
215
215
|
role,
|
|
216
|
-
|
|
217
|
-
|
|
216
|
+
activeTurn,
|
|
217
|
+
lastTurn,
|
|
218
218
|
activeWork,
|
|
219
219
|
nativeSession,
|
|
220
220
|
tmux,
|
|
@@ -236,17 +236,17 @@ function sessionCumulativeTokenLabel(metrics) {
|
|
|
236
236
|
];
|
|
237
237
|
return `${total.totalTokens} (${breakdown.join(", ")})`;
|
|
238
238
|
}
|
|
239
|
-
function latestStallKind(events,
|
|
239
|
+
function latestStallKind(events, turnId) {
|
|
240
240
|
const event = [...events]
|
|
241
|
-
.filter((candidate) => candidate.type === "
|
|
242
|
-
&& candidate.payload.
|
|
241
|
+
.filter((candidate) => candidate.type === "turn.stalled"
|
|
242
|
+
&& candidate.payload.turnId === turnId
|
|
243
243
|
&& candidate.payload.status !== "diagnostic-only")
|
|
244
244
|
.sort((left, right) => Date.parse(right.createdAt) - Date.parse(left.createdAt))[0];
|
|
245
245
|
return event?.payload.kind === "delivery-stalled" || event?.payload.kind === "workflow-not-progressing"
|
|
246
246
|
? event.payload.kind
|
|
247
247
|
: undefined;
|
|
248
248
|
}
|
|
249
|
-
function calculateHealth(role,
|
|
249
|
+
function calculateHealth(role, activeTurn, lastTurn, nativeSession, runtimeCleanupPending, tmux, openInputRequestCount, stalled, runtime) {
|
|
250
250
|
if (runtimeCleanupPending && nativeSession === null) {
|
|
251
251
|
return {
|
|
252
252
|
health: "needs-attention",
|
|
@@ -254,26 +254,17 @@ function calculateHealth(role, activeRun, lastRun, nativeSession, runtimeCleanup
|
|
|
254
254
|
};
|
|
255
255
|
}
|
|
256
256
|
if (nativeSession?.status === "ended" && nativeSession.endReason === "failed") {
|
|
257
|
-
//
|
|
258
|
-
//
|
|
259
|
-
//
|
|
260
|
-
if (
|
|
257
|
+
// A broken Session only fails a live Turn. When the last Turn already
|
|
258
|
+
// completed, the Session death is a lifecycle event, not a Turn failure —
|
|
259
|
+
// surface it as attention with the persisted Turn outcome.
|
|
260
|
+
if (activeTurn !== null) {
|
|
261
261
|
return { health: "failed", healthReason: "the active native session is broken" };
|
|
262
262
|
}
|
|
263
263
|
return {
|
|
264
264
|
health: "needs-attention",
|
|
265
|
-
healthReason:
|
|
265
|
+
healthReason: lastTurn === null
|
|
266
266
|
? "the native session is broken"
|
|
267
|
-
: `the native session is broken; last
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
|
-
const awaitingProviderAcceptance = activeRun?.pushedAt !== undefined
|
|
271
|
-
&& activeRun.deliveredAt === undefined;
|
|
272
|
-
if (awaitingProviderAcceptance
|
|
273
|
-
&& tmux.state !== "running") {
|
|
274
|
-
return {
|
|
275
|
-
health: "needs-attention",
|
|
276
|
-
healthReason: "the pushed active Run is awaiting provider acceptance and has no live tmux pane"
|
|
267
|
+
: `the native session is broken; last Turn ${lastTurn.id} ${lastTurn.status}`
|
|
277
268
|
};
|
|
278
269
|
}
|
|
279
270
|
if (tmux.state === "exited") {
|
|
@@ -282,23 +273,17 @@ function calculateHealth(role, activeRun, lastRun, nativeSession, runtimeCleanup
|
|
|
282
273
|
healthReason: "the tmux pane exited; Provider Conversation/continuation state is unobservable"
|
|
283
274
|
};
|
|
284
275
|
}
|
|
285
|
-
if (
|
|
286
|
-
if (
|
|
287
|
-
return { health: "needs-attention", healthReason: "the
|
|
276
|
+
if (activeTurn !== null) {
|
|
277
|
+
if (tmux.state !== "running") {
|
|
278
|
+
return { health: "needs-attention", healthReason: "the active Turn has no live tmux pane" };
|
|
288
279
|
}
|
|
289
280
|
if (stalled) {
|
|
290
281
|
return {
|
|
291
282
|
health: "needs-attention",
|
|
292
|
-
healthReason: "the live active
|
|
283
|
+
healthReason: "the live active Turn has no durable progress in the configured stall window"
|
|
293
284
|
};
|
|
294
285
|
}
|
|
295
|
-
if (
|
|
296
|
-
if (runtime === null) {
|
|
297
|
-
return {
|
|
298
|
-
health: "needs-attention",
|
|
299
|
-
healthReason: "the delivered Run has no authoritative Agent Driver state"
|
|
300
|
-
};
|
|
301
|
-
}
|
|
286
|
+
if (runtime !== null) {
|
|
302
287
|
switch (runtime.healthLayer) {
|
|
303
288
|
case "broken":
|
|
304
289
|
return { health: "failed", healthReason: runtime.healthReason };
|
|
@@ -328,6 +313,10 @@ function calculateHealth(role, activeRun, lastRun, nativeSession, runtimeCleanup
|
|
|
328
313
|
return { health: "running", healthReason: runtime.healthReason };
|
|
329
314
|
}
|
|
330
315
|
}
|
|
316
|
+
return {
|
|
317
|
+
health: "starting",
|
|
318
|
+
healthReason: "the active Turn is awaiting Provider runtime observations"
|
|
319
|
+
};
|
|
331
320
|
}
|
|
332
321
|
if (nativeSession?.status === "active" && tmux.state !== "running") {
|
|
333
322
|
return { health: "needs-attention", healthReason: "the native session is running without a live tmux pane" };
|
|
@@ -341,25 +330,11 @@ function calculateHealth(role, activeRun, lastRun, nativeSession, runtimeCleanup
|
|
|
341
330
|
healthReason: `${openInputRequestCount} open InputRequest${openInputRequestCount === 1 ? "" : "s"} require user input`
|
|
342
331
|
};
|
|
343
332
|
}
|
|
344
|
-
if (activeRun !== null) {
|
|
345
|
-
if (activeRun.deliveredAt !== undefined)
|
|
346
|
-
return {
|
|
347
|
-
health: "needs-attention",
|
|
348
|
-
healthReason: "the delivered Run has no authoritative Agent Driver state"
|
|
349
|
-
};
|
|
350
|
-
if (activeRun.pushedAt !== undefined) {
|
|
351
|
-
return {
|
|
352
|
-
health: "awaiting-provider-acceptance",
|
|
353
|
-
healthReason: "the pushed active Run is awaiting provider acceptance"
|
|
354
|
-
};
|
|
355
|
-
}
|
|
356
|
-
return { health: "starting", healthReason: "the active Run is awaiting tmux delivery" };
|
|
357
|
-
}
|
|
358
333
|
return tmux.state === "running"
|
|
359
334
|
? { health: "ready", healthReason: "the native Agent pane is ready without active work" }
|
|
360
335
|
: { health: "idle", healthReason: "there is no active work or live tmux pane" };
|
|
361
336
|
}
|
|
362
|
-
function projectTaskRoleRuntime(run, session, tmux, events,
|
|
337
|
+
function projectTaskRoleRuntime(run, session, tmux, events, _mailbox, store, taskId, roleName, now) {
|
|
363
338
|
if (run === null || session?.launchId === undefined)
|
|
364
339
|
return null;
|
|
365
340
|
let driverId;
|
|
@@ -372,7 +347,7 @@ function projectTaskRoleRuntime(run, session, tmux, events, mailbox, store, task
|
|
|
372
347
|
const fence = {
|
|
373
348
|
taskId: run.taskId,
|
|
374
349
|
roleName: run.roleName,
|
|
375
|
-
|
|
350
|
+
turnId: run.id,
|
|
376
351
|
agentId: run.effective.agentId,
|
|
377
352
|
driverId,
|
|
378
353
|
launchId: session.launchId,
|
|
@@ -381,17 +356,18 @@ function projectTaskRoleRuntime(run, session, tmux, events, mailbox, store, task
|
|
|
381
356
|
nativeTurnId: runtimeNativeTurnId(events, {
|
|
382
357
|
taskId: run.taskId,
|
|
383
358
|
roleName: run.roleName,
|
|
384
|
-
|
|
359
|
+
turnId: run.id,
|
|
385
360
|
agentId: run.effective.agentId,
|
|
386
361
|
driverId,
|
|
387
362
|
launchId: session.launchId,
|
|
388
363
|
nativeSessionId: session.nativeSessionId,
|
|
389
|
-
receiptId:
|
|
364
|
+
receiptId: store.getTaskRoleSessionSet(taskId, roleName)?.providerBinding?.turn?.attemptId
|
|
365
|
+
?? formatTurnReceiptId(run.taskId, run.id)
|
|
390
366
|
}) ?? run.id,
|
|
391
|
-
receiptId:
|
|
367
|
+
receiptId: store.getTaskRoleSessionSet(taskId, roleName)?.providerBinding?.turn?.attemptId
|
|
368
|
+
?? formatTurnReceiptId(run.taskId, run.id)
|
|
392
369
|
};
|
|
393
370
|
let projection = projectRuntimeTaskEvents(fence, run.createdAt, events);
|
|
394
|
-
projection = projectRuntimeMailbox(projection, mailbox);
|
|
395
371
|
projection = projectRuntimeObservation(projection, createRuntimeObservation({
|
|
396
372
|
schemaVersion: 2,
|
|
397
373
|
eventId: `runtime-host-${run.id}`,
|
|
@@ -404,10 +380,8 @@ function projectTaskRoleRuntime(run, session, tmux, events, mailbox, store, task
|
|
|
404
380
|
}));
|
|
405
381
|
// The semantic progress fence is the same durable fold the scheduler stall
|
|
406
382
|
// pass consumes, so CLI/Web/scheduler share one progress clock.
|
|
407
|
-
const semanticProgress = run.
|
|
408
|
-
|
|
409
|
-
: latestRunDurableProgressAt(store, taskId, roleName, run.id)
|
|
410
|
-
?? { progressAt: run.deliveredAt };
|
|
383
|
+
const semanticProgress = latestTurnDurableProgressAt(store, taskId, roleName, run.id)
|
|
384
|
+
?? { progressAt: run.createdAt };
|
|
411
385
|
const classification = classifyRuntimeHealth({
|
|
412
386
|
projection,
|
|
413
387
|
semanticProgressAt: semanticProgress.progressAt,
|
|
@@ -444,7 +418,7 @@ function runtimeNativeTurnId(events, expected) {
|
|
|
444
418
|
.filter((observation) => observation !== null
|
|
445
419
|
&& observation.fence.taskId === expected.taskId
|
|
446
420
|
&& observation.fence.roleName === expected.roleName
|
|
447
|
-
&& observation.fence.
|
|
421
|
+
&& observation.fence.turnId === expected.turnId
|
|
448
422
|
&& observation.fence.agentId === expected.agentId
|
|
449
423
|
&& observation.fence.driverId === expected.driverId
|
|
450
424
|
&& observation.fence.launchId === expected.launchId
|
|
@@ -459,10 +433,6 @@ function runtimeNativeTurnId(events, expected) {
|
|
|
459
433
|
?.fence.nativeTurnId
|
|
460
434
|
?? observations.at(-1)?.fence.nativeTurnId;
|
|
461
435
|
}
|
|
462
|
-
function
|
|
463
|
-
|
|
464
|
-
return "delivered";
|
|
465
|
-
if (run.pushedAt !== undefined)
|
|
466
|
-
return "pushed (awaiting provider acceptance)";
|
|
467
|
-
return "queued";
|
|
436
|
+
function activeTurnDeliveryLabel(run) {
|
|
437
|
+
return run.status === "active" ? "active" : run.status;
|
|
468
438
|
}
|
|
@@ -42,11 +42,11 @@ async function integrateUpstream(args, store, options) {
|
|
|
42
42
|
throw usageError(`Task must be active to integrate upstream: ${taskId}/${task.status}.`);
|
|
43
43
|
}
|
|
44
44
|
// RFC Phase 4: integrating upstream rewrites the Task worktree HEAD. Refuse
|
|
45
|
-
// while the Leader has an active
|
|
45
|
+
// while the Leader has an active Turn so the merge never lands under a
|
|
46
46
|
// running Session.
|
|
47
|
-
const activeLeaderRun = store.
|
|
47
|
+
const activeLeaderRun = store.getActiveTurn(taskId, "leader");
|
|
48
48
|
if (activeLeaderRun !== null) {
|
|
49
|
-
throw usageError(`Task has an active Leader
|
|
49
|
+
throw usageError(`Task has an active Leader Turn (${activeLeaderRun.id}); stop it before integrating upstream: ${taskId}.`);
|
|
50
50
|
}
|
|
51
51
|
const workspace = store.getTaskWorkspace(taskId);
|
|
52
52
|
if (workspace === null) {
|