@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.
Files changed (229) hide show
  1. package/ARCHITECTURE.md +69 -428
  2. package/README.md +108 -298
  3. package/dist/agent/managedRuntimeEnvironment.js +2 -2
  4. package/dist/cli/commandCatalog.js +39 -77
  5. package/dist/cli/interactionCandidates.js +4 -4
  6. package/dist/cli/interactionPolicy.js +7 -10
  7. package/dist/cli/invocationRouter.js +1 -1
  8. package/dist/cli/updateCommand.js +6 -27
  9. package/dist/cli/updateOrchestrator.js +63 -484
  10. package/dist/cli/updatePorts.js +15 -333
  11. package/dist/cli/upgradeCommand.js +8 -65
  12. package/dist/cli.js +57 -242
  13. package/dist/commands/configCommands.js +11 -62
  14. package/dist/commands/executionAuditCommands.js +23 -23
  15. package/dist/commands/globalRoleCommands.js +2 -7
  16. package/dist/commands/jobCommands.js +1 -4
  17. package/dist/commands/projectCommands.js +5 -5
  18. package/dist/commands/releaseCommands.js +3 -10
  19. package/dist/commands/resourcesCommands.js +2 -2
  20. package/dist/commands/sessionCommands.js +2 -2
  21. package/dist/commands/taskActor.js +31 -39
  22. package/dist/commands/taskChangeSetCommands.js +5 -12
  23. package/dist/commands/taskCommands.js +464 -1249
  24. package/dist/commands/taskContextCommand.js +35 -36
  25. package/dist/commands/taskExecutionCommands.js +33 -46
  26. package/dist/commands/taskInputCommands.js +20 -42
  27. package/dist/commands/taskNextActionCommand.js +9 -9
  28. package/dist/commands/taskOverviewCommand.js +56 -95
  29. package/dist/commands/taskRoleRuntimeStatus.js +77 -107
  30. package/dist/commands/taskUpstreamCommands.js +3 -3
  31. package/dist/commands/taskWorkspaceCommands.js +7 -79
  32. package/dist/commands/telemetryCommands.js +32 -124
  33. package/dist/commands/workflowCommands.js +3 -3
  34. package/dist/completion/fileCompletionManager.js +1 -1
  35. package/dist/config/configCatalog.js +4 -6
  36. package/dist/config/yuiConfig.js +6 -43
  37. package/dist/context/dispatchContext.js +14 -30
  38. package/dist/context/roleSessionContext.js +2 -2
  39. package/dist/context/sessionBootstrapManifest.js +2 -2
  40. package/dist/context/{runContextPack.js → turnContextPack.js} +58 -73
  41. package/dist/context/turnInputContract.js +204 -0
  42. package/dist/context/wakeNotification.js +2 -2
  43. package/dist/controller/agentRuntimeObserver.js +18 -18
  44. package/dist/controller/clientRuntime.js +13 -20
  45. package/dist/controller/controller.js +89 -116
  46. package/dist/controller/fileSchedulerStoreAdapter.js +511 -742
  47. package/dist/controller/jobControl.js +17 -17
  48. package/dist/controller/resourceInventory.js +6 -6
  49. package/dist/controller/resourceInventoryLinux.js +38 -59
  50. package/dist/controller/resourceInventoryRpc.js +3 -3
  51. package/dist/controller/resourceInventoryWorker.js +1 -1
  52. package/dist/controller/runtime.js +46 -53
  53. package/dist/controller/runtimeEventInbox.js +23 -31
  54. package/dist/controller/runtimeEventProcessor.js +15 -15
  55. package/dist/controller/{runtimeHookRunFence.js → runtimeHookTurnFence.js} +87 -81
  56. package/dist/controller/runtimeLaunchCoordinator.js +3 -3
  57. package/dist/controller/runtimeObservationHook.js +11 -8
  58. package/dist/controller/sessionNotify.js +11 -11
  59. package/dist/controller/structuredProviderObservation.js +89 -16
  60. package/dist/coordination/workMailbox.js +309 -579
  61. package/dist/coordination/workMailboxQueue.js +5 -6
  62. package/dist/core/controllerClient.js +1 -91
  63. package/dist/core/controllerServer.js +3 -6
  64. package/dist/doctor/doctor.js +51 -314
  65. package/dist/execution/candidateConvergence.js +3 -3
  66. package/dist/execution/executionGroup.js +32 -33
  67. package/dist/execution/executionHealth.js +36 -38
  68. package/dist/execution/resourceBroker.js +12 -12
  69. package/dist/executor/agentExecutor.js +30 -265
  70. package/dist/executor/effectiveLaunch.js +9 -19
  71. package/dist/executor/executorRegistry.js +43 -17
  72. package/dist/executor/fileRoleLaunchPlanner.js +56 -56
  73. package/dist/executor/launchPlan.js +3 -3
  74. package/dist/executor/workspacePreflightClassification.js +16 -16
  75. package/dist/grant/capabilityGrant.js +20 -25
  76. package/dist/input/inputRequest.js +12 -12
  77. package/dist/integration/changeSet.js +9 -13
  78. package/dist/integration/changeSetManifest.js +1 -1
  79. package/dist/integration/gitIntegrationService.js +2 -2
  80. package/dist/integration/integrationQueueService.js +5 -21
  81. package/dist/integration/overlapDiagnostics.js +2 -2
  82. package/dist/interaction/operatorPresentation.js +1 -1
  83. package/dist/job/jobRunner.js +1 -1
  84. package/dist/lifecycle/canonicalLifecycleEvent.js +25 -25
  85. package/dist/lifecycle/{exactRunTerminalization.js → exactTurnTerminalization.js} +116 -232
  86. package/dist/message/message.js +6 -6
  87. package/dist/observability/executionAudit.js +74 -86
  88. package/dist/observability/faultClassification.js +7 -24
  89. package/dist/observability/orchestrationMetrics.js +5 -5
  90. package/dist/observability/runtimeIdentity.js +34 -101
  91. package/dist/profile/agentProfile.js +1 -1
  92. package/dist/release/cliHomeReleaseFence.js +3 -3
  93. package/dist/release/releaseWorkflowEngine.js +6 -10
  94. package/dist/release/releaseWorkflowPorts.js +2 -2
  95. package/dist/release/workflowFileLock.js +1 -1
  96. package/dist/repository/checkoutSwap.js +1 -1
  97. package/dist/repository/gitWorkspace.js +0 -13
  98. package/dist/repository/project.js +1 -1
  99. package/dist/repository/projectMaintenanceLock.js +1 -2
  100. package/dist/repository/taskBaseFreshness.js +13 -13
  101. package/dist/repository/taskWorkspaceCoordinator.js +21 -25
  102. package/dist/repository/taskWorkspaceIdentity.js +8 -21
  103. package/dist/repository/taskWorkspacePreparer.js +63 -620
  104. package/dist/resources/autoResourceGc.js +3 -3
  105. package/dist/resources/liveReferences.js +1 -1
  106. package/dist/resources/resourceGc.js +1 -1
  107. package/dist/resources/sqliteResourceRegistry.js +1 -1
  108. package/dist/review/reviewDecision.js +7 -7
  109. package/dist/review/reviewFindingLedger.js +1 -1
  110. package/dist/review/reviewOutcomeClassifier.js +35 -50
  111. package/dist/review/reviewRound.js +14 -25
  112. package/dist/review/reviewerAvailability.js +4 -4
  113. package/dist/review/taskFinalReviewContractResolution.js +41 -0
  114. package/dist/role/role.js +6 -3
  115. package/dist/runtime/agentDriver.js +4 -0
  116. package/dist/runtime/agentHost.js +151 -36
  117. package/dist/runtime/builtinAgentDrivers.js +79 -2
  118. package/dist/runtime/codexAppServerRuntime.js +122 -0
  119. package/dist/runtime/continuationManager.js +3 -5
  120. package/dist/runtime/exactControlPlane.js +17 -42
  121. package/dist/runtime/firstProgressAdvisory.js +1 -1
  122. package/dist/runtime/index.js +2 -2
  123. package/dist/runtime/launchDiagnostics.js +2 -2
  124. package/dist/runtime/lifecycleReservation.js +4 -4
  125. package/dist/runtime/processExitObservation.js +1 -1
  126. package/dist/runtime/processExitOutbox.js +0 -32
  127. package/dist/runtime/promptEnvelope.js +9 -9
  128. package/dist/runtime/providerContinuation.js +2 -2
  129. package/dist/runtime/providerContinuationReconciliationService.js +2 -2
  130. package/dist/runtime/providerRuntimeIdentity.js +76 -24
  131. package/dist/runtime/recentTurnIds.js +38 -0
  132. package/dist/runtime/runtimeContinuationProjection.js +2 -2
  133. package/dist/runtime/runtimeHealthPolicy.js +1 -1
  134. package/dist/runtime/runtimeObservation.js +58 -21
  135. package/dist/runtime/runtimeProjection.js +19 -27
  136. package/dist/runtime/sessionLaunchRequest.js +2 -2
  137. package/dist/runtime/structuredProviderHost.js +177 -12
  138. package/dist/runtime/tmuxAdapters.js +46 -11
  139. package/dist/scheduler/activeRoleTurnDelivery.js +245 -0
  140. package/dist/scheduler/leaderWakeupProcessor.js +147 -445
  141. package/dist/scheduler/operatorEvent.js +4 -6
  142. package/dist/scheduler/resourceQueueProjection.js +71 -0
  143. package/dist/scheduler/{roleRunLiveness.js → roleTurnLiveness.js} +24 -28
  144. package/dist/scheduler/{roleRunStall.js → roleTurnStall.js} +162 -247
  145. package/dist/scheduler/taskExecutionProjection.js +99 -99
  146. package/dist/scheduler/taskObservabilityProjection.js +10 -10
  147. package/dist/scheduler/taskWake.js +4 -4
  148. package/dist/scheduler/wakeReason.js +6 -10
  149. package/dist/scheduler/wakeupQueue.js +0 -9
  150. package/dist/setup/setupCommand.js +2 -2
  151. package/dist/storage/currentTaskStore.js +46 -0
  152. package/dist/storage/persistenceWorker.js +1 -1
  153. package/dist/storage/sqliteSchema.js +81 -485
  154. package/dist/storage/sqliteStore.js +206 -365
  155. package/dist/storage/storageSchema.js +1 -15
  156. package/dist/storage/storageVersions.js +8 -9
  157. package/dist/storage/storeRpc.js +10 -34
  158. package/dist/storage/taskStore.js +63 -3415
  159. package/dist/storage/upgrade/recordVersions.js +55 -149
  160. package/dist/storage/upgrade/upgradeOrchestrator.js +89 -1752
  161. package/dist/task/completionReadiness.js +5 -5
  162. package/dist/task/deliveryGuard.js +10 -10
  163. package/dist/task/nextAction.js +74 -77
  164. package/dist/task/task.js +2 -4
  165. package/dist/task/taskRecordReference.js +3 -3
  166. package/dist/task/taskRecordRetirement.js +1 -1
  167. package/dist/telemetry/sqliteTelemetryStore.js +53 -53
  168. package/dist/telemetry/telemetryConfig.js +14 -14
  169. package/dist/telemetry/telemetryWiring.js +8 -14
  170. package/dist/turn/turn.js +356 -0
  171. package/dist/{run/runIdentity.js → turn/turnIdentity.js} +4 -4
  172. package/dist/verification/gateArtifactStore.js +2 -2
  173. package/dist/web/assets/client/app.js +1 -1
  174. package/dist/web/assets/client/components.js +29 -54
  175. package/dist/web/assets/client/i18n.js +17 -37
  176. package/dist/web/assets/client/view.js +37 -37
  177. package/dist/web/assets/shell.js +1 -1
  178. package/dist/web/assets/styles/cards.js +5 -5
  179. package/dist/web/assets/styles/widgets.js +1 -2
  180. package/dist/web/webSnapshot.js +65 -60
  181. package/dist/workItem/workItem.js +11 -11
  182. package/dist/workspace/workItemChangeSetManager.js +1 -1
  183. package/docs/task-local-identity.md +5 -5
  184. package/i18n/README.zh-CN.md +41 -54
  185. package/package.json +1 -1
  186. package/skills/yui-leader/SKILL.md +85 -83
  187. package/skills/yui-operator/SKILL.md +16 -16
  188. package/skills/yui-reviewer/SKILL.md +10 -10
  189. package/skills/yui-runtime/SKILL.md +17 -18
  190. package/skills/yui-worker/SKILL.md +16 -18
  191. package/dist/context/runContextContract.js +0 -162
  192. package/dist/executor/turnCompletion.js +0 -126
  193. package/dist/review/taskFinalReviewContractEvent.js +0 -1
  194. package/dist/review/taskFinalReviewContractRebind.js +0 -367
  195. package/dist/run/agentRun.js +0 -356
  196. package/dist/run/rejectedYieldAttempt.js +0 -221
  197. package/dist/run/runControlRequest.js +0 -50
  198. package/dist/run/yieldReceipt.js +0 -65
  199. package/dist/runtime/turnCompletion.js +0 -3
  200. package/dist/scheduler/actionability.js +0 -323
  201. package/dist/scheduler/activeRoleRunDelivery.js +0 -868
  202. package/dist/scheduler/activeTaskProgress.js +0 -126
  203. package/dist/storage/compatibleTaskStore.js +0 -248
  204. package/dist/storage/migration/baseline.js +0 -78
  205. package/dist/storage/migration/classifier.js +0 -51
  206. package/dist/storage/migration/compatibleCodec.js +0 -53
  207. package/dist/storage/migration/engine.js +0 -147
  208. package/dist/storage/migration/index.js +0 -33
  209. package/dist/storage/migration/planner.js +0 -154
  210. package/dist/storage/migration/productionRegistry.js +0 -3931
  211. package/dist/storage/migration/registry.js +0 -169
  212. package/dist/storage/migration/report.js +0 -54
  213. package/dist/storage/migration/types.js +0 -31
  214. package/dist/storage/upgrade/homeClassification.js +0 -353
  215. package/dist/storage/upgrade/homeMigrationTarget.js +0 -595
  216. package/dist/storage/upgrade/migrationReceipt.js +0 -67
  217. package/dist/storage/upgrade/offlineUpgradeInventory.js +0 -453
  218. package/dist/storage/upgrade/productionMigrationRegistry.js +0 -6
  219. package/dist/storage/upgrade/pseudoLayoutRepair.js +0 -241
  220. package/dist/storage/upgrade/recordVersionScan.js +0 -176
  221. package/dist/storage/upgrade/sqliteFileSet.js +0 -50
  222. package/dist/storage/upgrade/sqliteMigrationTarget.js +0 -375
  223. package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +0 -305
  224. package/dist/storage/upgrade/sqliteStateMigration.js +0 -918
  225. package/dist/storage/upgrade/switchProgress.js +0 -80
  226. package/dist/storage/upgrade/upgradeReceipt.js +0 -161
  227. package/dist/storage/upgradeCoordination.js +0 -187
  228. package/dist/storage/upgradeFence.js +0 -366
  229. package/dist/telemetry/telemetryCompaction.js +0 -252
@@ -1,10 +1,10 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { fileURLToPath } from "node:url";
3
- import { callController, ControllerClientError, readControllerDiscovery, stopOrphanedFileTaskController, stopPreviousFileTaskController } from "../core/controllerClient.js";
3
+ import { callController, ControllerClientError, readControllerDiscovery, stopOrphanedFileTaskController } from "../core/controllerClient.js";
4
4
  import { controllerSocketPath } from "../core/controllerEndpoint.js";
5
5
  import { FILE_TASK_CONTROLLER_PROTOCOL_VERSION } from "../core/protocol.js";
6
6
  import { AGENT_OPERATIONAL_ENVIRONMENT_NAMES, nativeAgentEnvironmentNames, operationalAgentEnvironment, selectEnvironment, YUI_MANAGED_RUNTIME_ENVIRONMENT_NAMES } from "../agent/launchEnvironment.js";
7
- import { openCompatibleFileTaskStore } from "../storage/compatibleTaskStore.js";
7
+ import { openCurrentTaskStore } from "../storage/currentTaskStore.js";
8
8
  import { yuiTmuxServerName } from "../tmux/tmuxManager.js";
9
9
  import { hasRuntimeLifecycleWork } from "../runtime/lifecycleReservation.js";
10
10
  import { assertControllerStatusIdentity } from "../runtime/exactControlPlane.js";
@@ -21,19 +21,15 @@ const ENVIRONMENT_REFRESH_TIMEOUT_MS = 500;
21
21
  const CONFIGURATION_REFRESH_TIMEOUT_MS = 500;
22
22
  const CONTROLLER_OPERATIONAL_ENVIRONMENT = [
23
23
  ...AGENT_OPERATIONAL_ENVIRONMENT_NAMES,
24
- // Issue 01: YUI_STORE_BACKEND/YUI_STORE_WORKER are reserved for tests and
25
- // explicit recovery commands. A Controller spawned by such a command must
26
- // inherit the forced backend; otherwise a layout-7 test Home silently opens
27
- // SQLite, creates yui.db, and diverges from the file store the test writes.
28
- "YUI_STORE_BACKEND",
24
+ // The worker toggle changes process placement, not storage authority.
29
25
  "YUI_STORE_WORKER",
30
26
  // rr13/test: Forward the liveness seam so an integration test's Controller
31
- // subprocess does not reap a saved active Leader Run without a real tmux role.
27
+ // subprocess does not reap a saved active Leader Turn without a real tmux role.
32
28
  "YUI_TEST_ROLE_LIVENESS_PRESENT",
33
29
  ...EPHEMERAL_DOMAIN_ENVIRONMENT_NAMES
34
30
  ];
35
31
  /**
36
- * Direct FileTaskStore writers must not extend state while an older Controller
32
+ * Direct TaskStore writers must not extend state while an older Controller
37
33
  * is still reading the same YUI_HOME.
38
34
  */
39
35
  export async function assertFileTaskControllerStorageCompatible(home, options = {}) {
@@ -241,11 +237,8 @@ export async function restartFileTaskController(home, options = {}) {
241
237
  catch (error) {
242
238
  if (options.call !== undefined || !isInvalidDiscovery(error))
243
239
  throw error;
244
- // Protocol v3 is the immediately previous released Controller. Keep its
245
- // parser and transport confined to this explicit replacement operation;
246
- // no ordinary request can fall back to it.
247
- const previous = await stopPreviousFileTaskController(home, shutdownTimeoutMs);
248
- previousPid = previous.pid;
240
+ // A malformed current discovery record cannot authorize a protocol call.
241
+ // Exact process identity below may still fence and stop an orphaned owner.
249
242
  }
250
243
  if (!controllerRunning(current) && options.call === undefined) {
251
244
  const orphan = await stopOrphanedFileTaskController(home, shutdownTimeoutMs);
@@ -278,7 +271,7 @@ export async function restartFileTaskController(home, options = {}) {
278
271
  function controllerSpawnEnvironment(home, source) {
279
272
  const allowed = new Set(CONTROLLER_OPERATIONAL_ENVIRONMENT);
280
273
  try {
281
- for (const agent of openCompatibleFileTaskStore(home).listConfiguredAgents()) {
274
+ for (const agent of openCurrentTaskStore(home).listConfiguredAgents()) {
282
275
  for (const name of nativeAgentEnvironmentNames(agent.adapterId))
283
276
  allowed.add(name);
284
277
  for (const binding of agent.environment)
@@ -291,7 +284,7 @@ function controllerSpawnEnvironment(home, source) {
291
284
  }
292
285
  // Merge with process.env so a partial source (e.g. a managed-Session env
293
286
  // that only carries YUI_SESSION_SCOPE/YUI_TASK_ID) still forwards
294
- // operational vars like YUI_STORE_BACKEND and PATH. The source wins on
287
+ // operational vars like YUI_STORE_WORKER and PATH. The source wins on
295
288
  // conflict; the whitelist still gates every forwarded name.
296
289
  const effectiveSource = { ...process.env, ...source };
297
290
  const environment = {};
@@ -440,8 +433,8 @@ export class FileTaskWorkflowRuntime {
440
433
  async stopTaskRoleSessions(taskId, roleNames) {
441
434
  const targets = [];
442
435
  for (const roleName of [...new Set(roleNames)]) {
443
- if (this.store.getActiveAgentRun(taskId, roleName) !== null) {
444
- throw new Error(`Role has an active Run: ${taskId}/${roleName}.`);
436
+ if (this.store.getActiveTurn(taskId, roleName) !== null) {
437
+ throw new Error(`Role has an active Turn: ${taskId}/${roleName}.`);
445
438
  }
446
439
  const target = this.schedulerStore.enqueueRuntimeCleanup({
447
440
  scope: "task",
@@ -473,8 +466,8 @@ export class FileTaskWorkflowRuntime {
473
466
  }
474
467
  /** Stops only the exact idle Session observed by an Agent command. */
475
468
  async stopExactTaskRoleSession(input) {
476
- if (this.store.getActiveAgentRun(input.taskId, input.roleName) !== null) {
477
- throw new Error(`Role has an active Run: ${input.taskId}/${input.roleName}.`);
469
+ if (this.store.getActiveTurn(input.taskId, input.roleName) !== null) {
470
+ throw new Error(`Role has an active Turn: ${input.taskId}/${input.roleName}.`);
478
471
  }
479
472
  const owner = {
480
473
  scope: "task",
@@ -1,11 +1,10 @@
1
1
  import { reconciliationIntervalMilliseconds } from "../config/yuiConfig.js";
2
- import { processLeaderWakeups } from "../scheduler/leaderWakeupProcessor.js";
2
+ import { LEADER_WAKE_AGGREGATION_MS, LEADER_WAKE_FORCE_MS, processLeaderWakeups } from "../scheduler/leaderWakeupProcessor.js";
3
3
  import { pendingWakeupsMatch } from "../scheduler/pendingWakeup.js";
4
- import { processActiveRoleRunDeliveries } from "../scheduler/activeRoleRunDelivery.js";
5
- import { selectedActiveSchedulerTasks, selectedSchedulerRoles, selectedSchedulerTasks } from "../scheduler/ports.js";
6
- import { reconcileExitedRoleRuns } from "../scheduler/roleRunLiveness.js";
7
- import { DEFAULT_STALL_WINDOW_MS, DEFAULT_WORKFLOW_STALL_CANDIDATE_AGE_MS, reconcileStalledRoleRuns } from "../scheduler/roleRunStall.js";
8
- import { repairOrphanedActiveTasks } from "../scheduler/activeTaskProgress.js";
4
+ import { processActiveRoleTurnDeliveries } from "../scheduler/activeRoleTurnDelivery.js";
5
+ import { selectedActiveSchedulerTasks } from "../scheduler/ports.js";
6
+ import { reconcileExitedRoleTurns } from "../scheduler/roleTurnLiveness.js";
7
+ import { DEFAULT_STALL_WINDOW_MS, DEFAULT_WORKFLOW_STALL_CANDIDATE_AGE_MS, reconcileStalledRoleTurns } from "../scheduler/roleTurnStall.js";
9
8
  import { processOperatorInputNotifications } from "../scheduler/operatorInputNotificationProcessor.js";
10
9
  import { startControllerServer } from "../core/controllerServer.js";
11
10
  import { monotonicMilliseconds } from "../core/controllerTelemetry.js";
@@ -15,7 +14,7 @@ import { KeyedWorkQueue } from "../coordination/keyedWorkQueue.js";
15
14
  import { MailboxScheduler } from "../coordination/mailboxScheduler.js";
16
15
  import { nearestDeadlineBatch } from "../coordination/deadlineScheduler.js";
17
16
  import { mailboxHasWork, nextPendingBatch } from "../coordination/workMailbox.js";
18
- import { hasRuntimeCleanupObligation, isRuntimeLaunchReservation, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
17
+ import { hasRuntimeCleanupObligation, isRuntimeLaunchReservation } from "../runtime/lifecycleReservation.js";
19
18
  import { formatTaskRecordReference } from "../task/taskRecordReference.js";
20
19
  import { parseDurableJobAcknowledgeParams, parseDurableJobCancelParams, parseDurableJobRefParams, parseDurableJobStartParams } from "./jobControl.js";
21
20
  const DEFAULT_RECONCILIATION_INTERVAL_MS = reconciliationIntervalMilliseconds();
@@ -46,19 +45,18 @@ const ZERO_DRAIN_METRICS = Object.freeze({
46
45
  * Runs one lean scheduler pass. Due native Turn completions are folded before
47
46
  * liveness, so a valid Hook boundary fences destructive process reconciliation.
48
47
  */
49
- export async function runControllerSchedulerPass(store, delivery, now, workspacePreparer, scope = { kind: "full" }, includeOperator = true, runtimeCleanupOutcomes = [], lifecycleHost, stallWindowMs = DEFAULT_STALL_WINDOW_MS, maintenanceFence, onMaintenanceFenceDefer, blockedTaskIds = new Set(), inputDeliveryRecoveryCutoff, diagnosticAfterMs = DEFAULT_WORKFLOW_STALL_CANDIDATE_AGE_MS, leaderWakeFence) {
48
+ export async function runControllerSchedulerPass(store, delivery, now, workspacePreparer, scope = { kind: "full" }, includeOperator = true, runtimeCleanupOutcomes = [], lifecycleHost, stallWindowMs = DEFAULT_STALL_WINDOW_MS, maintenanceFence, onMaintenanceFenceDefer, blockedTaskIds = new Set(), diagnosticAfterMs = DEFAULT_WORKFLOW_STALL_CANDIDATE_AGE_MS, leaderWakeFence) {
50
49
  const compiledSelection = compileReconcileSelection(scope);
51
50
  const selection = includeOperator
52
51
  ? { ...compiledSelection, blockedTaskIds }
53
52
  : { ...compiledSelection, operator: false, blockedTaskIds };
54
- queueSelectedCompletedTaskRuntimeCleanups(store, selection, now);
55
53
  // A full-state Task projection can be individually bounded yet still starve
56
54
  // control sockets when several scheduler phases repeat it in one native
57
55
  // event-loop turn. Give already-written requests a poll boundary before the
58
56
  // next durable phase; later phases retain their existing CAS fences.
59
57
  await controlEventLoopTurn();
60
58
  // A dormant Session may still name a Host that disappeared after its last
61
- // Provider Turn. Detach that disposable Host before claiming the next Run,
59
+ // Provider Turn. Detach that disposable Host before claiming the next Turn,
62
60
  // so retained Agent intent restores the same Session instead of first
63
61
  // connecting to a known-dead control socket.
64
62
  await reconcileDormantRuntimeOwners(store, delivery, lifecycleHost, scope, now, blockedTaskIds);
@@ -67,8 +65,6 @@ export async function runControllerSchedulerPass(store, delivery, now, workspace
67
65
  const availableWakeupSelection = () => (leaderWakeFence?.() === true
68
66
  ? exactTaskSelection(new Set())
69
67
  : selectionWithoutFailedLeaderCleanupTasks(store, selection, failedCleanupRoles));
70
- if (selection.full)
71
- repairOrphanedActiveTasks(store, now, selection);
72
68
  const claimedTaskMailboxes = claimSelectedTaskMailboxes(store, selection, now);
73
69
  try {
74
70
  // Durable Leader work that already has a ready Task workspace belongs to
@@ -81,13 +77,10 @@ export async function runControllerSchedulerPass(store, delivery, now, workspace
81
77
  await controlEventLoopTurn();
82
78
  const workspacePreparation = await prepareActiveWorkspaces(store, workspacePreparer, selection, maintenanceFence, onMaintenanceFenceDefer);
83
79
  await controlEventLoopTurn();
84
- // Issue 04: reopen due in-place Provider retries on their original
85
- // Sessions before delivery, so the existing delivery path re-pushes the
86
- // exact same input in this same pass.
87
- const activeRunDeliveries = await processActiveRoleRunDeliveries(store, delivery, now, roleSelection, inputDeliveryRecoveryCutoff);
80
+ const activeTurnDeliveries = await processActiveRoleTurnDeliveries(store, delivery, now, roleSelection);
88
81
  await controlEventLoopTurn();
89
- const unsettledRunRefs = new Set(activeRunDeliveries.flatMap((result) => (result.reason === "delivery-uncertain" || result.terminalFailure !== undefined
90
- ? [formatTaskRecordReference(result.taskId, result.runId, "agentRun")]
82
+ const unsettledTurnRefs = new Set(activeTurnDeliveries.flatMap((result) => (result.reason === "delivery-uncertain"
83
+ ? [formatTaskRecordReference(result.taskId, result.turnId, "turn")]
91
84
  : [])));
92
85
  // Every successful Task has completed the targeted phases owned by its
93
86
  // mailbox at this boundary. Settle that exact claim before advisory
@@ -99,16 +92,16 @@ export async function runControllerSchedulerPass(store, delivery, now, workspace
99
92
  }
100
93
  }
101
94
  const newlyIdleBusyTaskIds = new Set(initialWakeupResults.flatMap((result) => (result.reason === "busy"
102
- && store.getActiveAgentRun(result.taskId, "leader") === null
95
+ && store.getActiveTurn(result.taskId, "leader") === null
103
96
  ? [result.taskId]
104
97
  : [])));
105
- // Phase-one Leader Runs did not exist at the pass's liveness boundary.
106
- // Keep every newly claimed Run outside destructive absence decisions until
98
+ // Phase-one Leader Turns did not exist at the pass's liveness boundary.
99
+ // Keep every newly claimed Turn outside destructive absence decisions until
107
100
  // a later pass can observe its stable provider/session generation.
108
101
  for (const result of initialWakeupResults) {
109
- if (result.runId !== undefined
110
- && store.getActiveAgentRun(result.taskId, "leader")?.id === result.runId) {
111
- unsettledRunRefs.add(formatTaskRecordReference(result.taskId, result.runId, "agentRun"));
102
+ if (result.turnId !== undefined
103
+ && store.getActiveTurn(result.taskId, "leader")?.id === result.turnId) {
104
+ unsettledTurnRefs.add(formatTaskRecordReference(result.taskId, result.turnId, "turn"));
112
105
  }
113
106
  }
114
107
  // Let socket callbacks queued during the bounded scheduler phases run
@@ -116,9 +109,9 @@ export async function runControllerSchedulerPass(store, delivery, now, workspace
116
109
  await controlEventLoopTurn();
117
110
  const liveStatuses = new Map();
118
111
  const resourceEvidence = new Map();
119
- const failedRunRefs = await reconcileExitedRoleRuns(store, delivery, now, roleSelection, unsettledRunRefs, liveStatuses, resourceEvidence, scope.kind === "dirty");
112
+ const failedTurnRefs = await reconcileExitedRoleTurns(store, delivery, now, roleSelection, unsettledTurnRefs, liveStatuses, resourceEvidence, scope.kind === "dirty");
120
113
  await controlEventLoopTurn();
121
- await reconcileStalledRoleRuns(store, delivery, now, roleSelection, stallWindowMs, liveStatuses, resourceEvidence, diagnosticAfterMs);
114
+ await reconcileStalledRoleTurns(store, delivery, now, roleSelection, stallWindowMs, liveStatuses, resourceEvidence, diagnosticAfterMs);
122
115
  await controlEventLoopTurn();
123
116
  await reconcileDormantRuntimeOwners(store, delivery, lifecycleHost, scope, now, selection.blockedTaskIds);
124
117
  const selectedInputTaskIds = selectedTaskIdsForBoundedPass(store, selection);
@@ -155,8 +148,8 @@ export async function runControllerSchedulerPass(store, delivery, now, workspace
155
148
  ? await processOperatorInputNotifications(store, delivery, selection, now)
156
149
  : [];
157
150
  return {
158
- activeRunDeliveries,
159
- failedRunRefs,
151
+ activeTurnDeliveries,
152
+ failedTurnRefs,
160
153
  wakeups: mergeWakeupPhaseResults(initialWakeupResults, laterWakeups),
161
154
  inputNotifications,
162
155
  autoResolvedInputs
@@ -208,60 +201,6 @@ function mergeWakeupPhaseResults(initial, later) {
208
201
  }
209
202
  return merged;
210
203
  }
211
- function queueSelectedCompletedTaskRuntimeCleanups(store, selection, now) {
212
- if (store.enqueueRuntimeCleanup === undefined)
213
- return;
214
- if (selection.full && store.listRuntimeSessionCandidates !== undefined) {
215
- for (const candidate of store.listRuntimeSessionCandidates({
216
- cleanupRequiredOnly: true
217
- })) {
218
- if (candidate.owner.scope !== "task"
219
- || !candidate.cleanupRequired
220
- || selection.blockedTaskIds?.has(candidate.owner.taskId)) {
221
- continue;
222
- }
223
- const { taskId, roleName } = candidate.owner;
224
- if (store.getTask(taskId)?.status !== "completed")
225
- continue;
226
- if (store.getActiveAgentRun(taskId, roleName) !== null)
227
- continue;
228
- const target = runtimeLifecycleTarget(candidate.owner);
229
- if (hasRuntimeCleanupObligation(store.getWorkMailbox(target)))
230
- continue;
231
- store.enqueueRuntimeCleanup(candidate.owner, now);
232
- }
233
- return;
234
- }
235
- for (const task of selectedSchedulerTasks(store, selection)) {
236
- if (task.status !== "completed")
237
- continue;
238
- for (const role of selectedSchedulerRoles(store, task.id, selection)) {
239
- if (store.getActiveAgentRun(task.id, role.name) !== null)
240
- continue;
241
- const session = store.getRoleSession(task.id, role.name);
242
- if (!schedulerSessionRequiresRuntimeCleanup(session))
243
- continue;
244
- const target = runtimeLifecycleTarget({
245
- scope: "task",
246
- taskId: task.id,
247
- roleName: role.name
248
- });
249
- if (hasRuntimeCleanupObligation(store.getWorkMailbox(target)))
250
- continue;
251
- store.enqueueRuntimeCleanup({
252
- scope: "task",
253
- taskId: task.id,
254
- roleName: role.name
255
- }, now);
256
- }
257
- }
258
- }
259
- function schedulerSessionRequiresRuntimeCleanup(session) {
260
- if (session === null || session.status === "ended") {
261
- return false;
262
- }
263
- return session.launchId !== undefined;
264
- }
265
204
  async function processSelectedRoleRuntimeCleanups(store, delivery, lifecycleHost, scope, now, outcomes, blockedTaskIds = new Set()) {
266
205
  const targets = selectedRuntimeLifecycleTargets(store, scope, blockedTaskIds);
267
206
  const failedRoles = new Set();
@@ -750,8 +689,8 @@ function partitionDirtyScope(keys) {
750
689
  }
751
690
  function emptyControllerSchedulerResult() {
752
691
  return {
753
- activeRunDeliveries: [],
754
- failedRunRefs: [],
692
+ activeTurnDeliveries: [],
693
+ failedTurnRefs: [],
755
694
  wakeups: [],
756
695
  inputNotifications: [],
757
696
  autoResolvedInputs: []
@@ -768,15 +707,15 @@ function runtimeTaskFailureIds(result) {
768
707
  }
769
708
  function mergeControllerSchedulerResults(results) {
770
709
  return {
771
- activeRunDeliveries: results.flatMap((result) => result.activeRunDeliveries),
772
- failedRunRefs: results.flatMap((result) => result.failedRunRefs),
710
+ activeTurnDeliveries: results.flatMap((result) => result.activeTurnDeliveries),
711
+ failedTurnRefs: results.flatMap((result) => result.failedTurnRefs),
773
712
  wakeups: results.flatMap((result) => result.wakeups),
774
713
  inputNotifications: results.flatMap((result) => result.inputNotifications),
775
714
  autoResolvedInputs: results.flatMap((result) => result.autoResolvedInputs)
776
715
  };
777
716
  }
778
717
  /**
779
- * Single-owner periodic runtime for FileTaskStore-backed scheduling. Full pump
718
+ * Single-owner periodic runtime for TaskStore-backed scheduling. Full pump
780
719
  * requests remain exclusive, while dirty work is serialized per Task and may
781
720
  * progress concurrently across different Tasks up to the configured bound.
782
721
  */
@@ -809,6 +748,7 @@ export class FileTaskController {
809
748
  #passRetryAttempt = 0;
810
749
  #timer;
811
750
  #deadlineTimer;
751
+ #leaderWakeDeadlineTimer;
812
752
  #runtimeObserverTimer;
813
753
  #signalScheduler;
814
754
  #operatorSignalScheduler;
@@ -851,8 +791,8 @@ export class FileTaskController {
851
791
  : this.#deliveryRetryLimit;
852
792
  this.#taskOrchestrationRetryLimit = positiveInteger(options.taskOrchestrationRetryLimit, DEFAULT_TASK_ORCHESTRATION_RETRY_LIMIT, "Controller Task orchestration retry limit");
853
793
  this.#taskConcurrency = boundedPositiveInteger(options.taskConcurrency, DEFAULT_TASK_CONCURRENCY, MAX_TASK_CONCURRENCY, "Controller Task concurrency");
854
- this.#stallWindowMs = positiveInteger(options.stallWindowMs, DEFAULT_STALL_WINDOW_MS, "Controller Run stall window");
855
- this.#diagnosticAfterMs = positiveInteger(options.diagnosticAfterMs, DEFAULT_WORKFLOW_STALL_CANDIDATE_AGE_MS, "Controller Run diagnostic threshold");
794
+ this.#stallWindowMs = positiveInteger(options.stallWindowMs, DEFAULT_STALL_WINDOW_MS, "Controller Turn stall window");
795
+ this.#diagnosticAfterMs = positiveInteger(options.diagnosticAfterMs, DEFAULT_WORKFLOW_STALL_CANDIDATE_AGE_MS, "Controller Turn diagnostic threshold");
856
796
  this.#runtimeEventProcessor = options.runtimeEventProcessor;
857
797
  this.#runtimeObserver = options.runtimeObserver;
858
798
  this.#runtimeObserverIntervalMs = positiveInteger(options.runtimeObserverIntervalMs, DEFAULT_RUNTIME_OBSERVER_INTERVAL_MS, "Controller runtime observer interval");
@@ -969,6 +909,10 @@ export class FileTaskController {
969
909
  clearTimeout(this.#deadlineTimer);
970
910
  this.#deadlineTimer = undefined;
971
911
  }
912
+ if (this.#leaderWakeDeadlineTimer !== undefined) {
913
+ clearTimeout(this.#leaderWakeDeadlineTimer);
914
+ this.#leaderWakeDeadlineTimer = undefined;
915
+ }
972
916
  if (this.#timer !== undefined) {
973
917
  clearInterval(this.#timer);
974
918
  this.#timer = undefined;
@@ -1115,7 +1059,7 @@ export class FileTaskController {
1115
1059
  // processes Leader wakeups.
1116
1060
  this.#jobSupervisor?.reconcile(this.#now());
1117
1061
  if (scope.kind === "full") {
1118
- result = await runControllerSchedulerPass(this.store, this.delivery, this.#now(), this.#workspacePreparer, scope, false, runtimeCleanupOutcomes, this.#lifecycleHost, this.#stallWindowMs, this.#maintenanceFence, this.#onMaintenanceFenceDefer, runtimeFailedTaskIds, this.#startedAt, this.#diagnosticAfterMs, this.#leaderWakeFence);
1062
+ result = await runControllerSchedulerPass(this.store, this.delivery, this.#now(), this.#workspacePreparer, scope, false, runtimeCleanupOutcomes, this.#lifecycleHost, this.#stallWindowMs, this.#maintenanceFence, this.#onMaintenanceFenceDefer, runtimeFailedTaskIds, this.#diagnosticAfterMs, this.#leaderWakeFence);
1119
1063
  }
1120
1064
  else {
1121
1065
  const dirtyPass = await this.#runDirtySchedulerPass(scope, runtimeCleanupOutcomes, runtimeFailedTaskIds);
@@ -1207,6 +1151,7 @@ export class FileTaskController {
1207
1151
  }
1208
1152
  finally {
1209
1153
  this.#scheduleNextInputDeadline();
1154
+ this.#scheduleNextLeaderWakeDeadline();
1210
1155
  }
1211
1156
  }
1212
1157
  async #runDirtySchedulerPass(scope, runtimeCleanupOutcomes, blockedTaskIds = new Set()) {
@@ -1214,7 +1159,7 @@ export class FileTaskController {
1214
1159
  const taskScopes = partition.taskScopes.filter((taskScope) => (!blockedTaskIds.has(taskScope.taskId)));
1215
1160
  const orderedResults = [];
1216
1161
  if (partition.globalKeys.length > 0) {
1217
- orderedResults.push(await runControllerSchedulerPass(this.store, this.delivery, this.#now(), this.#workspacePreparer, { kind: "dirty", keys: partition.globalKeys }, false, runtimeCleanupOutcomes, this.#lifecycleHost, this.#stallWindowMs, this.#maintenanceFence, this.#onMaintenanceFenceDefer, blockedTaskIds, this.#startedAt, this.#diagnosticAfterMs, this.#leaderWakeFence));
1162
+ orderedResults.push(await runControllerSchedulerPass(this.store, this.delivery, this.#now(), this.#workspacePreparer, { kind: "dirty", keys: partition.globalKeys }, false, runtimeCleanupOutcomes, this.#lifecycleHost, this.#stallWindowMs, this.#maintenanceFence, this.#onMaintenanceFenceDefer, blockedTaskIds, this.#diagnosticAfterMs, this.#leaderWakeFence));
1218
1163
  }
1219
1164
  if (taskScopes.length === 0
1220
1165
  || this.#stopped
@@ -1247,7 +1192,7 @@ export class FileTaskController {
1247
1192
  if (this.#stopped || this.#pendingFull)
1248
1193
  continue;
1249
1194
  try {
1250
- taskResults[selected.index] = await runControllerSchedulerPass(this.store, this.delivery, this.#now(), this.#workspacePreparer, { kind: "dirty", keys: selected.taskScope.keys }, false, taskCleanupOutcomes[selected.index], this.#lifecycleHost, this.#stallWindowMs, this.#maintenanceFence, this.#onMaintenanceFenceDefer, blockedTaskIds, this.#startedAt, this.#diagnosticAfterMs, this.#leaderWakeFence);
1195
+ taskResults[selected.index] = await runControllerSchedulerPass(this.store, this.delivery, this.#now(), this.#workspacePreparer, { kind: "dirty", keys: selected.taskScope.keys }, false, taskCleanupOutcomes[selected.index], this.#lifecycleHost, this.#stallWindowMs, this.#maintenanceFence, this.#onMaintenanceFenceDefer, blockedTaskIds, this.#diagnosticAfterMs, this.#leaderWakeFence);
1251
1196
  this.#clearTaskPassRetry(selected.taskScope.taskId);
1252
1197
  }
1253
1198
  catch (error) {
@@ -1302,6 +1247,7 @@ export class FileTaskController {
1302
1247
  this.#scheduleDeliveryRetries(result);
1303
1248
  this.#clearEmptyOperatorRetry();
1304
1249
  this.#scheduleNextInputDeadline();
1250
+ this.#scheduleNextLeaderWakeDeadline();
1305
1251
  }
1306
1252
  #signalOperatorMailbox(retryReady = false) {
1307
1253
  if (this.#stopped)
@@ -1322,7 +1268,7 @@ export class FileTaskController {
1322
1268
  const processor = this.#runtimeEventProcessor;
1323
1269
  if (processor === undefined)
1324
1270
  return undefined;
1325
- // The worker backend exposes drainAsync; the file backend stays sync.
1271
+ // The persistence worker exposes drainAsync; the in-process store stays sync.
1326
1272
  const result = "drainAsync" in processor
1327
1273
  ? await processor.drainAsync(this.#now())
1328
1274
  : processor.drain(this.#now());
@@ -1459,12 +1405,51 @@ export class FileTaskController {
1459
1405
  }, delayMs);
1460
1406
  this.#deadlineTimer.unref();
1461
1407
  }
1408
+ #scheduleNextLeaderWakeDeadline() {
1409
+ if (this.#leaderWakeDeadlineTimer !== undefined) {
1410
+ clearTimeout(this.#leaderWakeDeadlineTimer);
1411
+ this.#leaderWakeDeadlineTimer = undefined;
1412
+ }
1413
+ if (this.#stopped)
1414
+ return;
1415
+ const now = this.#now().getTime();
1416
+ const deadlines = this.store.listPendingWakeups().flatMap((wakeup) => {
1417
+ const firstRequestedAt = Date.parse(wakeup.firstRequestedAt);
1418
+ if (!Number.isFinite(firstRequestedAt))
1419
+ return [];
1420
+ const aggregationAt = firstRequestedAt + LEADER_WAKE_AGGREGATION_MS;
1421
+ if (aggregationAt > now) {
1422
+ return [{
1423
+ key: `role:${encodeURIComponent(wakeup.taskId)}/leader`,
1424
+ at: aggregationAt
1425
+ }];
1426
+ }
1427
+ const forceAt = firstRequestedAt + LEADER_WAKE_FORCE_MS;
1428
+ return forceAt > now && this.store.getActiveTurn(wakeup.taskId, "leader") !== null
1429
+ ? [{
1430
+ key: `role:${encodeURIComponent(wakeup.taskId)}/leader`,
1431
+ at: forceAt
1432
+ }]
1433
+ : [];
1434
+ });
1435
+ const nearest = nearestDeadlineBatch(deadlines);
1436
+ if (nearest === null)
1437
+ return;
1438
+ const delayMs = Math.min(MAX_TIMER_DELAY_MS, Math.max(0, nearest.at - now));
1439
+ this.#leaderWakeDeadlineTimer = setTimeout(() => {
1440
+ this.#leaderWakeDeadlineTimer = undefined;
1441
+ for (const key of nearest.keys)
1442
+ this.#signalScheduler.signal(key);
1443
+ void this.#signalScheduler.drain().catch(this.#onError);
1444
+ }, delayMs);
1445
+ this.#leaderWakeDeadlineTimer.unref();
1446
+ }
1462
1447
  #scheduleDeliveryRetries(result) {
1463
1448
  const retry = new Map();
1464
1449
  const settled = new Set();
1465
1450
  const writerBlocked = new Set();
1466
1451
  const resignal = new Set();
1467
- for (const delivery of result.activeRunDeliveries) {
1452
+ for (const delivery of result.activeTurnDeliveries) {
1468
1453
  const key = `role:${encodeURIComponent(delivery.taskId)}/${encodeURIComponent(delivery.roleName)}`;
1469
1454
  if (delivery.terminalized === true) {
1470
1455
  settled.add(key);
@@ -1474,28 +1459,16 @@ export class FileTaskController {
1474
1459
  writerBlocked.add(key);
1475
1460
  else if (delivery.reason === "not-ready"
1476
1461
  || delivery.reason === "runtime-unavailable") {
1477
- retry.set(key, {
1478
- identity: delivery.runId,
1479
- ...(delivery.terminalFailure === undefined
1480
- ? {}
1481
- : { terminalFailure: delivery.terminalFailure })
1482
- });
1462
+ retry.set(key, { identity: delivery.turnId });
1483
1463
  }
1484
1464
  else if (delivery.status === "delivered" || delivery.status === "already-delivered")
1485
1465
  settled.add(key);
1486
1466
  }
1487
1467
  for (const wakeup of result.wakeups) {
1488
1468
  const key = `role:${encodeURIComponent(wakeup.taskId)}/leader`;
1489
- if (wakeup.terminalized === true) {
1490
- settled.add(key);
1491
- resignal.add(key);
1492
- }
1493
- else if (wakeup.reason === "writer-attached") {
1494
- writerBlocked.add(key);
1495
- }
1496
- else if (wakeup.reason === "not-ready"
1497
- || (wakeup.reason === "busy" && wakeup.runId !== undefined)) {
1498
- retry.set(key, { identity: wakeup.runId ?? key });
1469
+ if (wakeup.reason === "not-ready"
1470
+ || (wakeup.reason === "busy" && wakeup.turnId !== undefined)) {
1471
+ retry.set(key, { identity: wakeup.turnId ?? key });
1499
1472
  }
1500
1473
  else if (wakeup.status === "dispatched")
1501
1474
  settled.add(key);
@@ -1602,9 +1575,9 @@ export class FileTaskController {
1602
1575
  timer.unref();
1603
1576
  this.#deliveryRetryTimers.set(key, timer);
1604
1577
  }
1605
- #terminalizePreparedAfterRetryExhaustion(key, runId, failure) {
1578
+ #terminalizePreparedAfterRetryExhaustion(key, turnId, failure) {
1606
1579
  if (!key.startsWith("role:")
1607
- || runId.startsWith("runtime-cleanup:")
1580
+ || turnId.startsWith("runtime-cleanup:")
1608
1581
  || failure === undefined) {
1609
1582
  return;
1610
1583
  }
@@ -1613,10 +1586,10 @@ export class FileTaskController {
1613
1586
  return;
1614
1587
  if (target.taskId !== failure.taskId
1615
1588
  || target.roleName !== failure.roleName
1616
- || runId !== failure.runId) {
1617
- throw new Error(`Role delivery retry identity changed: ${key}/${runId}.`);
1589
+ || turnId !== failure.turnId) {
1590
+ throw new Error(`Role delivery retry identity changed: ${key}/${turnId}.`);
1618
1591
  }
1619
- const result = this.store.saveRoleRunDeliveryFailure({
1592
+ const result = this.store.saveRoleTurnDeliveryFailure({
1620
1593
  ...failure,
1621
1594
  now: this.#now()
1622
1595
  });
@@ -1626,7 +1599,7 @@ export class FileTaskController {
1626
1599
  this.delivery.forgetPrepared?.({
1627
1600
  taskId: failure.taskId,
1628
1601
  roleName: failure.roleName,
1629
- runId: failure.runId,
1602
+ turnId: failure.turnId,
1630
1603
  ...(failure.launchId === undefined
1631
1604
  ? {}
1632
1605
  : { launchId: failure.launchId })