@zq-silk/yui 0.13.10 → 0.14.1

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 (230) hide show
  1. package/ARCHITECTURE.md +69 -428
  2. package/README.md +113 -300
  3. package/dist/agent/argumentPolicy.js +2 -1
  4. package/dist/agent/managedRuntimeEnvironment.js +2 -2
  5. package/dist/cli/commandCatalog.js +39 -77
  6. package/dist/cli/interactionCandidates.js +4 -4
  7. package/dist/cli/interactionPolicy.js +7 -10
  8. package/dist/cli/invocationRouter.js +1 -1
  9. package/dist/cli/updateCommand.js +6 -27
  10. package/dist/cli/updateOrchestrator.js +63 -484
  11. package/dist/cli/updatePorts.js +15 -333
  12. package/dist/cli/upgradeCommand.js +8 -65
  13. package/dist/cli.js +57 -242
  14. package/dist/commands/configCommands.js +11 -62
  15. package/dist/commands/executionAuditCommands.js +23 -23
  16. package/dist/commands/globalRoleCommands.js +2 -7
  17. package/dist/commands/jobCommands.js +1 -4
  18. package/dist/commands/projectCommands.js +5 -5
  19. package/dist/commands/releaseCommands.js +3 -10
  20. package/dist/commands/resourcesCommands.js +2 -2
  21. package/dist/commands/sessionCommands.js +2 -2
  22. package/dist/commands/taskActor.js +31 -39
  23. package/dist/commands/taskChangeSetCommands.js +5 -12
  24. package/dist/commands/taskCommands.js +464 -1249
  25. package/dist/commands/taskContextCommand.js +35 -36
  26. package/dist/commands/taskExecutionCommands.js +33 -46
  27. package/dist/commands/taskInputCommands.js +20 -42
  28. package/dist/commands/taskNextActionCommand.js +9 -9
  29. package/dist/commands/taskOverviewCommand.js +56 -95
  30. package/dist/commands/taskRoleRuntimeStatus.js +77 -107
  31. package/dist/commands/taskUpstreamCommands.js +3 -3
  32. package/dist/commands/taskWorkspaceCommands.js +7 -79
  33. package/dist/commands/telemetryCommands.js +32 -124
  34. package/dist/commands/workflowCommands.js +3 -3
  35. package/dist/completion/fileCompletionManager.js +1 -1
  36. package/dist/config/configCatalog.js +4 -6
  37. package/dist/config/yuiConfig.js +6 -43
  38. package/dist/context/dispatchContext.js +14 -30
  39. package/dist/context/roleSessionContext.js +2 -2
  40. package/dist/context/sessionBootstrapManifest.js +20 -3
  41. package/dist/context/{runContextPack.js → turnContextPack.js} +58 -73
  42. package/dist/context/turnInputContract.js +204 -0
  43. package/dist/context/wakeNotification.js +2 -2
  44. package/dist/controller/agentRuntimeObserver.js +18 -18
  45. package/dist/controller/clientRuntime.js +13 -20
  46. package/dist/controller/controller.js +89 -116
  47. package/dist/controller/fileSchedulerStoreAdapter.js +511 -742
  48. package/dist/controller/jobControl.js +17 -17
  49. package/dist/controller/resourceInventory.js +6 -6
  50. package/dist/controller/resourceInventoryLinux.js +38 -59
  51. package/dist/controller/resourceInventoryRpc.js +3 -3
  52. package/dist/controller/resourceInventoryWorker.js +1 -1
  53. package/dist/controller/runtime.js +46 -53
  54. package/dist/controller/runtimeEventInbox.js +23 -31
  55. package/dist/controller/runtimeEventProcessor.js +15 -15
  56. package/dist/controller/{runtimeHookRunFence.js → runtimeHookTurnFence.js} +87 -81
  57. package/dist/controller/runtimeLaunchCoordinator.js +3 -3
  58. package/dist/controller/runtimeObservationHook.js +11 -8
  59. package/dist/controller/sessionNotify.js +11 -11
  60. package/dist/controller/structuredProviderObservation.js +89 -16
  61. package/dist/coordination/workMailbox.js +309 -579
  62. package/dist/coordination/workMailboxQueue.js +5 -6
  63. package/dist/core/controllerClient.js +1 -91
  64. package/dist/core/controllerServer.js +3 -6
  65. package/dist/doctor/doctor.js +51 -314
  66. package/dist/execution/candidateConvergence.js +3 -3
  67. package/dist/execution/executionGroup.js +32 -33
  68. package/dist/execution/executionHealth.js +36 -38
  69. package/dist/execution/resourceBroker.js +12 -12
  70. package/dist/executor/agentExecutor.js +30 -265
  71. package/dist/executor/effectiveLaunch.js +9 -19
  72. package/dist/executor/executorRegistry.js +43 -17
  73. package/dist/executor/fileRoleLaunchPlanner.js +91 -57
  74. package/dist/executor/launchPlan.js +3 -3
  75. package/dist/executor/workspacePreflightClassification.js +16 -16
  76. package/dist/grant/capabilityGrant.js +20 -25
  77. package/dist/input/inputRequest.js +12 -12
  78. package/dist/integration/changeSet.js +9 -13
  79. package/dist/integration/changeSetManifest.js +1 -1
  80. package/dist/integration/gitIntegrationService.js +2 -2
  81. package/dist/integration/integrationQueueService.js +5 -21
  82. package/dist/integration/overlapDiagnostics.js +2 -2
  83. package/dist/interaction/operatorPresentation.js +1 -1
  84. package/dist/job/jobRunner.js +1 -1
  85. package/dist/lifecycle/canonicalLifecycleEvent.js +25 -25
  86. package/dist/lifecycle/{exactRunTerminalization.js → exactTurnTerminalization.js} +116 -232
  87. package/dist/message/message.js +6 -6
  88. package/dist/observability/executionAudit.js +74 -86
  89. package/dist/observability/faultClassification.js +7 -24
  90. package/dist/observability/orchestrationMetrics.js +5 -5
  91. package/dist/observability/runtimeIdentity.js +34 -101
  92. package/dist/profile/agentProfile.js +1 -1
  93. package/dist/release/cliHomeReleaseFence.js +3 -3
  94. package/dist/release/releaseWorkflowEngine.js +6 -10
  95. package/dist/release/releaseWorkflowPorts.js +2 -2
  96. package/dist/release/workflowFileLock.js +1 -1
  97. package/dist/repository/checkoutSwap.js +1 -1
  98. package/dist/repository/gitWorkspace.js +0 -13
  99. package/dist/repository/project.js +1 -1
  100. package/dist/repository/projectMaintenanceLock.js +1 -2
  101. package/dist/repository/taskBaseFreshness.js +13 -13
  102. package/dist/repository/taskWorkspaceCoordinator.js +21 -25
  103. package/dist/repository/taskWorkspaceIdentity.js +8 -21
  104. package/dist/repository/taskWorkspacePreparer.js +63 -620
  105. package/dist/resources/autoResourceGc.js +3 -3
  106. package/dist/resources/liveReferences.js +1 -1
  107. package/dist/resources/resourceGc.js +1 -1
  108. package/dist/resources/sqliteResourceRegistry.js +1 -1
  109. package/dist/review/reviewDecision.js +7 -7
  110. package/dist/review/reviewFindingLedger.js +1 -1
  111. package/dist/review/reviewOutcomeClassifier.js +35 -50
  112. package/dist/review/reviewRound.js +14 -25
  113. package/dist/review/reviewerAvailability.js +4 -4
  114. package/dist/review/taskFinalReviewContractResolution.js +41 -0
  115. package/dist/role/role.js +6 -3
  116. package/dist/runtime/agentDriver.js +4 -0
  117. package/dist/runtime/agentHost.js +151 -36
  118. package/dist/runtime/builtinAgentDrivers.js +79 -2
  119. package/dist/runtime/codexAppServerRuntime.js +122 -0
  120. package/dist/runtime/continuationManager.js +3 -5
  121. package/dist/runtime/exactControlPlane.js +17 -42
  122. package/dist/runtime/firstProgressAdvisory.js +1 -1
  123. package/dist/runtime/index.js +2 -2
  124. package/dist/runtime/launchDiagnostics.js +2 -2
  125. package/dist/runtime/lifecycleReservation.js +4 -4
  126. package/dist/runtime/processExitObservation.js +1 -1
  127. package/dist/runtime/processExitOutbox.js +0 -32
  128. package/dist/runtime/promptEnvelope.js +9 -9
  129. package/dist/runtime/providerContinuation.js +2 -2
  130. package/dist/runtime/providerContinuationReconciliationService.js +2 -2
  131. package/dist/runtime/providerRuntimeIdentity.js +76 -24
  132. package/dist/runtime/recentTurnIds.js +38 -0
  133. package/dist/runtime/runtimeContinuationProjection.js +2 -2
  134. package/dist/runtime/runtimeHealthPolicy.js +1 -1
  135. package/dist/runtime/runtimeObservation.js +58 -21
  136. package/dist/runtime/runtimeProjection.js +19 -27
  137. package/dist/runtime/sessionLaunchRequest.js +2 -2
  138. package/dist/runtime/structuredProviderHost.js +177 -12
  139. package/dist/runtime/tmuxAdapters.js +46 -11
  140. package/dist/scheduler/activeRoleTurnDelivery.js +245 -0
  141. package/dist/scheduler/leaderWakeupProcessor.js +147 -445
  142. package/dist/scheduler/operatorEvent.js +4 -6
  143. package/dist/scheduler/resourceQueueProjection.js +71 -0
  144. package/dist/scheduler/{roleRunLiveness.js → roleTurnLiveness.js} +24 -28
  145. package/dist/scheduler/{roleRunStall.js → roleTurnStall.js} +162 -247
  146. package/dist/scheduler/taskExecutionProjection.js +99 -99
  147. package/dist/scheduler/taskObservabilityProjection.js +10 -10
  148. package/dist/scheduler/taskWake.js +4 -4
  149. package/dist/scheduler/wakeReason.js +6 -10
  150. package/dist/scheduler/wakeupQueue.js +0 -9
  151. package/dist/setup/setupCommand.js +2 -2
  152. package/dist/storage/currentTaskStore.js +46 -0
  153. package/dist/storage/persistenceWorker.js +1 -1
  154. package/dist/storage/sqliteSchema.js +81 -485
  155. package/dist/storage/sqliteStore.js +206 -365
  156. package/dist/storage/storageSchema.js +1 -15
  157. package/dist/storage/storageVersions.js +8 -9
  158. package/dist/storage/storeRpc.js +10 -34
  159. package/dist/storage/taskStore.js +63 -3415
  160. package/dist/storage/upgrade/recordVersions.js +55 -149
  161. package/dist/storage/upgrade/upgradeOrchestrator.js +89 -1752
  162. package/dist/task/completionReadiness.js +5 -5
  163. package/dist/task/deliveryGuard.js +10 -10
  164. package/dist/task/nextAction.js +74 -77
  165. package/dist/task/task.js +2 -4
  166. package/dist/task/taskRecordReference.js +3 -3
  167. package/dist/task/taskRecordRetirement.js +1 -1
  168. package/dist/telemetry/sqliteTelemetryStore.js +53 -53
  169. package/dist/telemetry/telemetryConfig.js +14 -14
  170. package/dist/telemetry/telemetryWiring.js +8 -14
  171. package/dist/turn/turn.js +356 -0
  172. package/dist/{run/runIdentity.js → turn/turnIdentity.js} +4 -4
  173. package/dist/verification/gateArtifactStore.js +2 -2
  174. package/dist/web/assets/client/app.js +1 -1
  175. package/dist/web/assets/client/components.js +29 -54
  176. package/dist/web/assets/client/i18n.js +17 -37
  177. package/dist/web/assets/client/view.js +37 -37
  178. package/dist/web/assets/shell.js +1 -1
  179. package/dist/web/assets/styles/cards.js +5 -5
  180. package/dist/web/assets/styles/widgets.js +1 -2
  181. package/dist/web/webSnapshot.js +65 -60
  182. package/dist/workItem/workItem.js +11 -11
  183. package/dist/workspace/workItemChangeSetManager.js +1 -1
  184. package/docs/task-local-identity.md +5 -5
  185. package/i18n/README.zh-CN.md +42 -55
  186. package/package.json +1 -1
  187. package/skills/yui-leader/SKILL.md +85 -83
  188. package/skills/yui-operator/SKILL.md +16 -16
  189. package/skills/yui-reviewer/SKILL.md +10 -10
  190. package/skills/yui-runtime/SKILL.md +22 -24
  191. package/skills/yui-worker/SKILL.md +16 -18
  192. package/dist/context/runContextContract.js +0 -162
  193. package/dist/executor/turnCompletion.js +0 -126
  194. package/dist/review/taskFinalReviewContractEvent.js +0 -1
  195. package/dist/review/taskFinalReviewContractRebind.js +0 -367
  196. package/dist/run/agentRun.js +0 -356
  197. package/dist/run/rejectedYieldAttempt.js +0 -221
  198. package/dist/run/runControlRequest.js +0 -50
  199. package/dist/run/yieldReceipt.js +0 -65
  200. package/dist/runtime/turnCompletion.js +0 -3
  201. package/dist/scheduler/actionability.js +0 -323
  202. package/dist/scheduler/activeRoleRunDelivery.js +0 -868
  203. package/dist/scheduler/activeTaskProgress.js +0 -126
  204. package/dist/storage/compatibleTaskStore.js +0 -248
  205. package/dist/storage/migration/baseline.js +0 -78
  206. package/dist/storage/migration/classifier.js +0 -51
  207. package/dist/storage/migration/compatibleCodec.js +0 -53
  208. package/dist/storage/migration/engine.js +0 -147
  209. package/dist/storage/migration/index.js +0 -33
  210. package/dist/storage/migration/planner.js +0 -154
  211. package/dist/storage/migration/productionRegistry.js +0 -3931
  212. package/dist/storage/migration/registry.js +0 -169
  213. package/dist/storage/migration/report.js +0 -54
  214. package/dist/storage/migration/types.js +0 -31
  215. package/dist/storage/upgrade/homeClassification.js +0 -353
  216. package/dist/storage/upgrade/homeMigrationTarget.js +0 -595
  217. package/dist/storage/upgrade/migrationReceipt.js +0 -67
  218. package/dist/storage/upgrade/offlineUpgradeInventory.js +0 -453
  219. package/dist/storage/upgrade/productionMigrationRegistry.js +0 -6
  220. package/dist/storage/upgrade/pseudoLayoutRepair.js +0 -241
  221. package/dist/storage/upgrade/recordVersionScan.js +0 -176
  222. package/dist/storage/upgrade/sqliteFileSet.js +0 -50
  223. package/dist/storage/upgrade/sqliteMigrationTarget.js +0 -375
  224. package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +0 -305
  225. package/dist/storage/upgrade/sqliteStateMigration.js +0 -926
  226. package/dist/storage/upgrade/switchProgress.js +0 -80
  227. package/dist/storage/upgrade/upgradeReceipt.js +0 -161
  228. package/dist/storage/upgradeCoordination.js +0 -187
  229. package/dist/storage/upgradeFence.js +0 -366
  230. package/dist/telemetry/telemetryCompaction.js +0 -252
@@ -5,7 +5,7 @@ const DEFAULT_MAX_CONCURRENT_SAMPLES = 8;
5
5
  const MAX_CONCURRENT_SAMPLES = 64;
6
6
  /**
7
7
  * Controller-owned, provider-independent sampler. Drivers own source parsing;
8
- * this component owns active-Run discovery, cursor lifetime, canonical event
8
+ * this component owns active-Turn discovery, cursor lifetime, canonical event
9
9
  * creation, and low-latency mailbox wakes.
10
10
  */
11
11
  export class AgentRuntimeObserver {
@@ -73,7 +73,7 @@ export class AgentRuntimeObserver {
73
73
  if (state.usage === undefined) {
74
74
  // Cumulative counters need a lower bound. An exact initial sample
75
75
  // proves the complete Session-generation history even when the
76
- // current Run resumed that Session, so preserve every occurrence and
76
+ // current Turn resumed that Session, so preserve every occurrence and
77
77
  // anchor it at zero. A partial sample cannot prove the omitted request
78
78
  // boundaries; retain its latest total as a partial baseline, plus the
79
79
  // smallest ordered witness if the visible counter already rolled back.
@@ -239,8 +239,8 @@ export class AgentRuntimeObserver {
239
239
  activeSources() {
240
240
  const result = [];
241
241
  const indexedTaskIds = this.store.listActiveTaskIds?.();
242
- // FileTaskStore remains a development/compatibility fallback. The normal
243
- // Controller store is SQLite and must discover only its indexed hot set.
242
+ // Stores without the indexed projection remain useful for deterministic
243
+ // tests. The production SQLite store discovers only its indexed hot set.
244
244
  const activeTasks = indexedTaskIds === undefined
245
245
  ? this.store.listTasks().filter((task) => (task.status === "active" && task.executionGate.state === "enabled"))
246
246
  : [...new Set(indexedTaskIds)]
@@ -251,43 +251,43 @@ export class AgentRuntimeObserver {
251
251
  && task.executionGate.state === "enabled"));
252
252
  for (const task of activeTasks) {
253
253
  // A Task still incurs one O(E) event projection. Group those observations
254
- // by Run and sort each group once so every active Run can reuse the same
254
+ // by Turn and sort each group once so every active Turn can reuse the same
255
255
  // ordered slice instead of repeatedly filtering/sorting all E events.
256
- const observationsByRunId = new Map();
256
+ const observationsByTurnId = new Map();
257
257
  const taskObservations = [];
258
258
  for (const event of this.store.listEvents(task.id)) {
259
259
  const observation = runtimeObservationFromTaskEvent(event);
260
- const runId = observation?.fence.runId;
261
- if (observation === null || runId === undefined)
260
+ const turnId = observation?.fence.turnId;
261
+ if (observation === null || turnId === undefined)
262
262
  continue;
263
263
  taskObservations.push(observation);
264
- const grouped = observationsByRunId.get(runId);
264
+ const grouped = observationsByTurnId.get(turnId);
265
265
  if (grouped === undefined) {
266
- observationsByRunId.set(runId, [observation]);
266
+ observationsByTurnId.set(turnId, [observation]);
267
267
  }
268
268
  else {
269
269
  grouped.push(observation);
270
270
  }
271
271
  }
272
- for (const observations of observationsByRunId.values()) {
272
+ for (const observations of observationsByTurnId.values()) {
273
273
  observations.sort(compareObservations);
274
274
  }
275
275
  taskObservations.sort(compareObservations);
276
- for (const run of this.store.listAgentRuns(task.id)) {
276
+ for (const run of this.store.listTurns(task.id)) {
277
277
  if (run.status !== "active"
278
- || this.store.getActiveAgentRun(task.id, run.roleName)?.id !== run.id)
278
+ || this.store.getActiveTurn(task.id, run.roleName)?.id !== run.id)
279
279
  continue;
280
- const observations = observationsByRunId.get(run.id) ?? [];
280
+ const observations = observationsByTurnId.get(run.id) ?? [];
281
281
  const accepted = observations
282
282
  .filter((observation) => observation.kind === "turn.accepted"
283
- && observation.fence.runId === run.id
283
+ && observation.fence.turnId === run.id
284
284
  && observation.fence.roleName === run.roleName
285
285
  && observation.fence.agentId === run.effective.agentId
286
286
  && observation.payload.observerSource !== undefined)
287
287
  .at(-1);
288
288
  const source = accepted?.payload.observerSource;
289
289
  if (accepted === undefined || source === undefined
290
- || accepted.fence.taskId === undefined || accepted.fence.runId === undefined)
290
+ || accepted.fence.taskId === undefined || accepted.fence.turnId === undefined)
291
291
  continue;
292
292
  try {
293
293
  if (this.drivers.require(accepted.fence.driverId).runtime.observer === undefined)
@@ -313,7 +313,7 @@ export class AgentRuntimeObserver {
313
313
  key: JSON.stringify([
314
314
  fence.taskId,
315
315
  fence.roleName,
316
- fence.runId,
316
+ fence.turnId,
317
317
  fence.agentId,
318
318
  fence.driverId,
319
319
  fence.launchId,
@@ -374,7 +374,7 @@ function sampleConcurrency(value) {
374
374
  if (!Number.isSafeInteger(resolved)
375
375
  || resolved < 1
376
376
  || resolved > MAX_CONCURRENT_SAMPLES) {
377
- throw new Error(`Agent runtime observer maxConcurrentSamples must be between 1 and ${MAX_CONCURRENT_SAMPLES}.`);
377
+ throw new Error(`AgentRuntime observer maxConcurrentSamples must be between 1 and ${MAX_CONCURRENT_SAMPLES}.`);
378
378
  }
379
379
  return resolved;
380
380
  }
@@ -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 })