@zq-silk/yui 0.14.0 → 0.14.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/ARCHITECTURE.md +64 -35
  2. package/README.md +258 -125
  3. package/dist/agent/argumentPolicy.js +2 -1
  4. package/dist/agent/managedRuntimeEnvironment.js +5 -8
  5. package/dist/cli/commandCatalog.js +74 -109
  6. package/dist/cli/helpRenderer.js +1 -2
  7. package/dist/cli/interactionCandidates.js +10 -0
  8. package/dist/cli/interactionPolicy.js +76 -37
  9. package/dist/cli/roleWizard.js +5 -6
  10. package/dist/cli/updateOrchestrator.js +10 -8
  11. package/dist/cli/updatePorts.js +17 -13
  12. package/dist/cli/upgradeCommand.js +2 -5
  13. package/dist/cli.js +268 -231
  14. package/dist/commands/agentCommands.js +13 -0
  15. package/dist/commands/configCommands.js +7 -19
  16. package/dist/commands/configOverview.js +3 -2
  17. package/dist/commands/durableJobCommands.js +1 -1
  18. package/dist/commands/executionAuditCommands.js +4 -9
  19. package/dist/commands/globalRoleCommands.js +17 -0
  20. package/dist/commands/profileCommands.js +200 -30
  21. package/dist/commands/sessionCommands.js +1 -1
  22. package/dist/commands/taskActor.js +29 -127
  23. package/dist/commands/taskChangeSetCommands.js +1 -3
  24. package/dist/commands/taskCommands.js +1771 -2107
  25. package/dist/commands/taskCompletionGate.js +27 -103
  26. package/dist/commands/taskContextCommand.js +47 -80
  27. package/dist/commands/taskExecutionCommands.js +9 -27
  28. package/dist/commands/taskInputCommands.js +10 -10
  29. package/dist/commands/taskIntegrationCommands.js +69 -61
  30. package/dist/commands/taskIntegrationQueueCommands.js +1 -1
  31. package/dist/commands/taskNextActionCommand.js +8 -29
  32. package/dist/commands/taskOverviewCommand.js +25 -3
  33. package/dist/commands/taskPublicationCommands.js +151 -85
  34. package/dist/commands/taskPublicationVerifyCommand.js +181 -0
  35. package/dist/commands/taskRemoteDeliveryCommand.js +134 -0
  36. package/dist/commands/taskRoleRuntimeStatus.js +5 -6
  37. package/dist/commands/taskUpstreamCommands.js +79 -95
  38. package/dist/config/configCatalog.js +1 -1
  39. package/dist/context/contextSnapshot.js +17 -2
  40. package/dist/context/sessionBootstrapManifest.js +18 -1
  41. package/dist/context/sourceTurnContext.js +30 -0
  42. package/dist/context/turnContextPack.js +107 -48
  43. package/dist/context/turnInputContract.js +11 -4
  44. package/dist/context/wakeNotification.js +84 -22
  45. package/dist/controller/agentRuntimeObserver.js +9 -12
  46. package/dist/controller/clientRuntime.js +13 -13
  47. package/dist/controller/controller.js +11 -11
  48. package/dist/controller/fileSchedulerStoreAdapter.js +262 -219
  49. package/dist/controller/jobControl.js +11 -10
  50. package/dist/controller/resourceInventory.js +6 -6
  51. package/dist/controller/resourceInventoryLinux.js +2 -2
  52. package/dist/controller/runtime.js +18 -61
  53. package/dist/controller/runtimeEventInbox.js +57 -34
  54. package/dist/controller/runtimeEventProcessor.js +36 -96
  55. package/dist/controller/runtimeHookTurnFence.js +33 -57
  56. package/dist/controller/runtimeLaunchCoordinator.js +44 -40
  57. package/dist/controller/runtimeObservationHook.js +18 -12
  58. package/dist/controller/sessionNotify.js +43 -28
  59. package/dist/controller/sessionOwnerReconciliation.js +21 -21
  60. package/dist/controller/structuredProviderObservation.js +13 -16
  61. package/dist/coordination/workMailboxQueue.js +132 -0
  62. package/dist/core/controllerServer.js +1 -0
  63. package/dist/domain/agentResultTransport.js +60 -0
  64. package/dist/execution/executionHealth.js +111 -118
  65. package/dist/execution/reviewMainTurn.js +161 -0
  66. package/dist/execution/workItemExecution.js +454 -0
  67. package/dist/execution/workItemExecutionProjection.js +309 -0
  68. package/dist/execution/workItemMainTurn.js +164 -0
  69. package/dist/executor/agentExecutor.js +19 -19
  70. package/dist/executor/executorRegistry.js +7 -28
  71. package/dist/executor/fileRoleLaunchPlanner.js +51 -83
  72. package/dist/external/githubPublicationVerifier.js +114 -0
  73. package/dist/external/gitlabPublicationVerifier.js +158 -0
  74. package/dist/external/pinnedCommandRunner.js +73 -0
  75. package/dist/integration/changeSetManifest.js +0 -3
  76. package/dist/integration/deliveryObligation.js +43 -6
  77. package/dist/integration/gitIntegrationService.js +284 -101
  78. package/dist/integration/integrationAttempt.js +64 -14
  79. package/dist/integration/integrationQueueEntry.js +5 -35
  80. package/dist/integration/integrationQueueService.js +65 -194
  81. package/dist/lifecycle/canonicalLifecycleEvent.js +16 -15
  82. package/dist/lifecycle/exactTurnTerminalization.js +100 -97
  83. package/dist/message/message.js +16 -0
  84. package/dist/observability/executionAudit.js +4 -25
  85. package/dist/observability/faultClassification.js +58 -70
  86. package/dist/observability/orchestrationMetrics.js +8 -50
  87. package/dist/profile/agentProfile.js +60 -15
  88. package/dist/profile/agentProfileRuntime.js +100 -0
  89. package/dist/release/releaseWorkflowPorts.js +5 -68
  90. package/dist/repository/executionLaneGitSnapshot.js +80 -0
  91. package/dist/repository/gitWorkspace.js +9 -67
  92. package/dist/repository/taskBaseFreshness.js +3 -1
  93. package/dist/repository/taskWorkspaceCoordinator.js +32 -4
  94. package/dist/repository/taskWorkspacePreparer.js +252 -411
  95. package/dist/resources/liveReferences.js +6 -55
  96. package/dist/resources/resourceDiscovery.js +1 -30
  97. package/dist/resources/resourceRegistrar.js +0 -23
  98. package/dist/review/deltaRecheck.js +8 -65
  99. package/dist/review/reviewAcceptance.js +22 -19
  100. package/dist/review/reviewConfig.js +0 -10
  101. package/dist/review/reviewDecision.js +2 -2
  102. package/dist/review/reviewRound.js +147 -273
  103. package/dist/review/reviewerAvailability.js +13 -23
  104. package/dist/role/role.js +3 -0
  105. package/dist/runtime/agentDriverObservation.js +1 -1
  106. package/dist/runtime/agentHost.js +51 -51
  107. package/dist/runtime/builtinAgentDrivers.js +9 -2
  108. package/dist/runtime/codexAppServerRuntime.js +23 -5
  109. package/dist/runtime/exactControlPlane.js +2 -267
  110. package/dist/runtime/firstProgressAdvisory.js +1 -1
  111. package/dist/runtime/index.js +1 -1
  112. package/dist/runtime/launchBroker.js +11 -11
  113. package/dist/runtime/lifecycleReservation.js +2 -2
  114. package/dist/runtime/managedCaller.js +111 -0
  115. package/dist/runtime/ports.js +16 -3
  116. package/dist/runtime/preallocatedNativeSession.js +2 -2
  117. package/dist/runtime/processExitObservation.js +3 -2
  118. package/dist/runtime/providerContinuationReconciliationService.js +1 -1
  119. package/dist/runtime/runtimeBinding.js +1 -1
  120. package/dist/runtime/runtimeObservation.js +33 -16
  121. package/dist/runtime/runtimeSessionCandidate.js +2 -2
  122. package/dist/runtime/runtimeStopReceipt.js +11 -11
  123. package/dist/runtime/sessionLaunchRequest.js +2 -2
  124. package/dist/runtime/sessionOwnerIdentity.js +9 -9
  125. package/dist/runtime/sessionOwnerRegistry.js +13 -13
  126. package/dist/runtime/sessionReconciliation.js +4 -4
  127. package/dist/runtime/sessionTerminationGuard.js +9 -9
  128. package/dist/runtime/sessionTokenMetrics.js +5 -7
  129. package/dist/runtime/structuredProviderHost.js +5 -5
  130. package/dist/runtime/taskRuntimeIsolation.js +24 -24
  131. package/dist/runtime/tmuxAdapters.js +32 -30
  132. package/dist/scheduler/activeRoleTurnDelivery.js +37 -15
  133. package/dist/scheduler/leaderWakeupProcessor.js +78 -55
  134. package/dist/scheduler/roleTurnLiveness.js +6 -6
  135. package/dist/scheduler/roleTurnStall.js +8 -9
  136. package/dist/scheduler/taskExecutionProjection.js +19 -41
  137. package/dist/scheduler/taskObservabilityProjection.js +75 -122
  138. package/dist/storage/sqliteSchema.js +1 -21
  139. package/dist/storage/sqliteStore.js +62 -47
  140. package/dist/storage/storageSchema.js +1 -8
  141. package/dist/storage/storageVersions.js +7 -4
  142. package/dist/storage/taskStore.js +8 -8
  143. package/dist/storage/upgrade/upgradeOrchestrator.js +112 -83
  144. package/dist/task/completionReadiness.js +17 -98
  145. package/dist/task/deliveryGuard.js +9 -11
  146. package/dist/task/draftPlan.js +142 -0
  147. package/dist/task/nextAction.js +179 -477
  148. package/dist/task/publicationVerification.js +1 -0
  149. package/dist/task/remoteDelivery.js +233 -0
  150. package/dist/task/task.js +108 -4
  151. package/dist/task/taskRecordReference.js +0 -1
  152. package/dist/turn/turn.js +109 -16
  153. package/dist/verification/verificationGateService.js +0 -26
  154. package/dist/web/assets/client/components.js +99 -121
  155. package/dist/web/assets/client/i18n.js +136 -58
  156. package/dist/web/assets/client/view.js +44 -0
  157. package/dist/web/assets/styles/cards.js +3 -15
  158. package/dist/web/assets/styles/widgets.js +4 -0
  159. package/dist/web/webSnapshot.js +29 -14
  160. package/dist/workItem/dependencyGate.js +34 -0
  161. package/dist/workItem/workItem.js +99 -266
  162. package/dist/workspace/workItemChangeSetManager.js +15 -59
  163. package/docs/task-local-identity.md +1 -1
  164. package/i18n/README.zh-CN.md +165 -59
  165. package/package.json +1 -1
  166. package/skills/yui-leader/SKILL.md +272 -782
  167. package/skills/yui-operator/SKILL.md +190 -365
  168. package/skills/yui-reviewer/SKILL.md +40 -33
  169. package/skills/yui-runtime/SKILL.md +27 -6
  170. package/skills/yui-worker/SKILL.md +19 -6
  171. package/dist/execution/candidateConvergence.js +0 -623
  172. package/dist/execution/executionGroup.js +0 -854
  173. package/dist/execution/resourceBroker.js +0 -426
  174. package/dist/executor/launchPlan.js +0 -16
  175. package/dist/review/reviewFinding.js +0 -240
  176. package/dist/review/reviewFindingLedger.js +0 -550
  177. package/dist/review/reviewOutcomeClassifier.js +0 -255
  178. package/dist/scheduler/resourceQueueProjection.js +0 -71
  179. package/dist/task/repairWave.js +0 -150
@@ -141,7 +141,7 @@ export class FileTaskRuntimeIsolation {
141
141
  }
142
142
  cleanupTaskLaunch(input) {
143
143
  const taskId = requireIdentity(input.taskId, "Task id");
144
- const launchId = requireIdentity(input.launchId, "Launch id");
144
+ const runtimeGenerationId = requireIdentity(input.runtimeGenerationId, "Runtime generation id");
145
145
  requireCleanupReason(input.reason);
146
146
  const taskRoot = taskRuntimeInventoryRoot(this.#runtimeRoot, taskId, this.#pathLayout);
147
147
  let entries;
@@ -157,7 +157,7 @@ export class FileTaskRuntimeIsolation {
157
157
  return "absent";
158
158
  throw error;
159
159
  }
160
- const launchDigest = taskRuntimeLaunchDigest(taskId, launchId, this.#pathLayout);
160
+ const launchDigest = taskRuntimeLaunchDigest(taskId, runtimeGenerationId, this.#pathLayout);
161
161
  const matches = [];
162
162
  for (const entry of entries) {
163
163
  if (!entry.isDirectory() || entry.isSymbolicLink()) {
@@ -181,7 +181,7 @@ export class FileTaskRuntimeIsolation {
181
181
  marker = parseMarker(readFileSync(join(generationRoot, MARKER_FILE), "utf8"));
182
182
  }
183
183
  catch (error) {
184
- throw new Error("Task runtime launch generation is unmarked or invalid.", {
184
+ throw new Error("Task runtime generation is unmarked or invalid.", {
185
185
  cause: error
186
186
  });
187
187
  }
@@ -189,9 +189,9 @@ export class FileTaskRuntimeIsolation {
189
189
  const fingerprint = taskRuntimeIsolationFingerprint(descriptor);
190
190
  if (marker.fingerprint !== fingerprint
191
191
  || descriptor.taskId !== taskId
192
- || descriptor.generation.launchId !== launchId
192
+ || descriptor.generation.runtimeGenerationId !== runtimeGenerationId
193
193
  || descriptor.roots.generation !== generationRoot) {
194
- throw new Error("Task runtime launch generation ownership is mismatched.");
194
+ throw new Error("Task runtime generation ownership is mismatched.");
195
195
  }
196
196
  matches.push({
197
197
  descriptor,
@@ -202,7 +202,7 @@ export class FileTaskRuntimeIsolation {
202
202
  if (matches.length === 0)
203
203
  return "absent";
204
204
  if (matches.length !== 1) {
205
- throw new Error("Task runtime launch generation ownership is ambiguous.");
205
+ throw new Error("Task runtime generation ownership is ambiguous.");
206
206
  }
207
207
  this.cleanup(matches[0], input.reason);
208
208
  return "cleaned";
@@ -212,17 +212,17 @@ export function createTaskRuntimeIsolationDescriptor(input) {
212
212
  const workspace = validateManagedWorkspace(input.workspace);
213
213
  const owner = taskRuntimeWorkspaceOwner(workspace.owner);
214
214
  const taskId = requireIdentity(owner.taskId, "Task id");
215
- const launchId = requireIdentity(input.launchId, "Launch id");
215
+ const runtimeGenerationId = requireIdentity(input.runtimeGenerationId, "Runtime generation id");
216
216
  const generationId = requireIdentity(input.generationId, "Generation id");
217
217
  const runtimeRoot = canonicalPath(input.runtimeRoot, "Task runtime root");
218
218
  const pathLayout = taskRuntimePathLayout(input.pathLayout);
219
- const generation = taskRuntimeGenerationRoot(runtimeRoot, taskId, owner, launchId, pathLayout);
219
+ const generation = taskRuntimeGenerationRoot(runtimeRoot, taskId, owner, runtimeGenerationId, pathLayout);
220
220
  const declared = capabilities(input.policy?.declaredExternalCapabilities ?? [], "Declared external capability");
221
221
  const requested = capabilities(input.policy?.requestedExternalCapabilities ?? [], "Requested external capability");
222
222
  const portPreference = ports(input.policy?.portPreference ?? [], "Port preference");
223
223
  const portAllocations = allocations(input.policy?.portAllocations ?? []);
224
224
  return Object.freeze({
225
- schemaVersion: 1,
225
+ schemaVersion: 2,
226
226
  kind: "yui-task-runtime-isolation",
227
227
  taskId,
228
228
  workspace: Object.freeze({ owner, root: canonicalPath(workspace.root, "Workspace root") }),
@@ -232,12 +232,12 @@ export function createTaskRuntimeIsolationDescriptor(input) {
232
232
  cache: join(generation, "cache"),
233
233
  temporary: join(generation, "tmp")
234
234
  }),
235
- serviceNamespace: taskRuntimeServiceNamespace(taskId, owner, launchId, generationId),
235
+ serviceNamespace: taskRuntimeServiceNamespace(taskId, owner, runtimeGenerationId, generationId),
236
236
  portPreference,
237
237
  portAllocations,
238
238
  externalCapabilities: Object.freeze({ declared, requested }),
239
239
  generation: Object.freeze({
240
- launchId,
240
+ runtimeGenerationId,
241
241
  generationId
242
242
  })
243
243
  });
@@ -253,7 +253,7 @@ export function parseTaskRuntimeIsolationDescriptor(serialized) {
253
253
  if (!isRecord(value) || value.kind !== "yui-task-runtime-isolation") {
254
254
  throw new Error(`Expected yui-task-runtime-isolation descriptor; found ${isRecord(value) && typeof value.kind === "string" ? value.kind : "unknown"}.`);
255
255
  }
256
- if (value.schemaVersion !== 1) {
256
+ if (value.schemaVersion !== 2) {
257
257
  throw new Error("Task runtime isolation descriptor schema version is invalid.");
258
258
  }
259
259
  const workspace = requireRecord(value.workspace, "Task runtime workspace");
@@ -262,7 +262,7 @@ export function parseTaskRuntimeIsolationDescriptor(serialized) {
262
262
  const external = requireRecord(value.externalCapabilities, "Task runtime external capabilities");
263
263
  const owner = taskRuntimeWorkspaceOwner(requireRecord(workspace.owner, "Task runtime workspace owner"));
264
264
  const descriptor = {
265
- schemaVersion: 1,
265
+ schemaVersion: 2,
266
266
  kind: "yui-task-runtime-isolation",
267
267
  taskId: requireIdentity(value.taskId, "Task id"),
268
268
  workspace: {
@@ -283,7 +283,7 @@ export function parseTaskRuntimeIsolationDescriptor(serialized) {
283
283
  requested: capabilities(external.requested, "Requested external capability")
284
284
  },
285
285
  generation: {
286
- launchId: requireIdentity(generation.launchId, "Launch id"),
286
+ runtimeGenerationId: requireIdentity(generation.runtimeGenerationId, "Runtime generation id"),
287
287
  generationId: requireIdentity(generation.generationId, "Generation id")
288
288
  }
289
289
  };
@@ -315,15 +315,15 @@ export function assertTaskRuntimeIsolationPreflight(input) {
315
315
  }
316
316
  const runtimeRoot = canonicalPath(input.runtimeRoot, "Task runtime root");
317
317
  const pathLayout = taskRuntimePathLayout(input.pathLayout);
318
- const expectedGeneration = taskRuntimeGenerationRoot(runtimeRoot, descriptor.taskId, expectedOwner, descriptor.generation.launchId, pathLayout);
318
+ const expectedGeneration = taskRuntimeGenerationRoot(runtimeRoot, descriptor.taskId, expectedOwner, descriptor.generation.runtimeGenerationId, pathLayout);
319
319
  if (descriptor.roots.generation !== expectedGeneration
320
320
  || descriptor.roots.data !== join(expectedGeneration, "data")
321
321
  || descriptor.roots.cache !== join(expectedGeneration, "cache")
322
322
  || descriptor.roots.temporary !== join(expectedGeneration, "tmp")) {
323
- throw new Error("Task runtime roots do not match the exact Task, owner, and launch identity.");
323
+ throw new Error("Task runtime roots do not match the exact Task, owner, and runtime generation identity.");
324
324
  }
325
- if (descriptor.serviceNamespace !== taskRuntimeServiceNamespace(descriptor.taskId, expectedOwner, descriptor.generation.launchId, descriptor.generation.generationId)) {
326
- throw new Error("Task runtime service namespace does not match its exact Task launch generation.");
325
+ if (descriptor.serviceNamespace !== taskRuntimeServiceNamespace(descriptor.taskId, expectedOwner, descriptor.generation.runtimeGenerationId, descriptor.generation.generationId)) {
326
+ throw new Error("Task runtime service namespace does not match its exact Task runtime generation.");
327
327
  }
328
328
  if (!isWithin(runtimeRoot, descriptor.roots.generation)) {
329
329
  throw new Error("Task runtime generation is outside its runtime root.");
@@ -494,8 +494,8 @@ function validatePreparation(preparation) {
494
494
  function taskRuntimeWorkspaceOwner(owner) {
495
495
  return owner;
496
496
  }
497
- function taskRuntimeGenerationRoot(runtimeRoot, taskId, owner, launchId, pathLayout = "hierarchical") {
498
- return join(taskRuntimeInventoryRoot(runtimeRoot, taskId, pathLayout), taskRuntimeOwnerDigest(taskId, owner, pathLayout), taskRuntimeLaunchDigest(taskId, launchId, pathLayout));
497
+ function taskRuntimeGenerationRoot(runtimeRoot, taskId, owner, runtimeGenerationId, pathLayout = "hierarchical") {
498
+ return join(taskRuntimeInventoryRoot(runtimeRoot, taskId, pathLayout), taskRuntimeOwnerDigest(taskId, owner, pathLayout), taskRuntimeLaunchDigest(taskId, runtimeGenerationId, pathLayout));
499
499
  }
500
500
  function taskRuntimeInventoryRoot(runtimeRoot, taskId, pathLayout) {
501
501
  return pathLayout === "compact" ? runtimeRoot : join(runtimeRoot, taskId);
@@ -505,8 +505,8 @@ function taskRuntimeOwnerDigest(taskId, owner, pathLayout) {
505
505
  ? digest(JSON.stringify([taskId, owner])).slice(0, 20)
506
506
  : digest(JSON.stringify(owner)).slice(0, 24);
507
507
  }
508
- function taskRuntimeLaunchDigest(taskId, launchId, pathLayout) {
509
- return digest(JSON.stringify([taskId, launchId])).slice(0, pathLayout === "compact" ? 20 : 24);
508
+ function taskRuntimeLaunchDigest(taskId, runtimeGenerationId, pathLayout) {
509
+ return digest(JSON.stringify([taskId, runtimeGenerationId])).slice(0, pathLayout === "compact" ? 20 : 24);
510
510
  }
511
511
  function taskRuntimePathLayout(value) {
512
512
  const pathLayout = value ?? "hierarchical";
@@ -515,11 +515,11 @@ function taskRuntimePathLayout(value) {
515
515
  }
516
516
  return pathLayout;
517
517
  }
518
- function taskRuntimeServiceNamespace(taskId, owner, launchId, generationId) {
518
+ function taskRuntimeServiceNamespace(taskId, owner, runtimeGenerationId, generationId) {
519
519
  return `yui-task-${digest(JSON.stringify([
520
520
  taskId,
521
521
  owner,
522
- launchId,
522
+ runtimeGenerationId,
523
523
  generationId
524
524
  ])).slice(0, 24)}`;
525
525
  }
@@ -3,10 +3,10 @@ import { fileURLToPath } from "node:url";
3
3
  import { resolve } from "node:path";
4
4
  import { createRuntimeBinding } from "./runtimeBinding.js";
5
5
  import { normalizeRuntimeOwner } from "./runtimeOwner.js";
6
- import { RuntimeHostContentionError } from "./ports.js";
6
+ import { RuntimeGenerationMismatchError, RuntimeHostContentionError } from "./ports.js";
7
7
  import { toRuntimeLaunchFailure } from "./launchDiagnostics.js";
8
8
  import { requireSafeIdentity } from "./validation.js";
9
- import { YUI_CONTROL_PLANE_DESCRIPTOR, YUI_TASK_RUNTIME_DESCRIPTOR } from "./exactControlPlane.js";
9
+ import { YUI_CONTROL_PLANE_DESCRIPTOR } from "./exactControlPlane.js";
10
10
  import { launchBrokerForHome } from "./launchBroker.js";
11
11
  import { AGENT_HOST_CONTROL_PROTOCOL, sendAgentHostLaunchControl, sendAgentHostTurnControl, sendAgentHostSteerControl, waitForAgentHostLaunchAck } from "./agentHost.js";
12
12
  const DEFAULT_INACTIVITY_TIMEOUT_MS = 300_000;
@@ -172,7 +172,7 @@ export class TmuxSessionHost {
172
172
  agentId: request.agentId,
173
173
  adapterId: request.adapterId,
174
174
  effective: request.effective,
175
- launchId: request.launchId,
175
+ runtimeGenerationId: request.runtimeGenerationId,
176
176
  mode: request.mode,
177
177
  ...(request.turnId === undefined ? {} : { turnId: request.turnId }),
178
178
  ...(request.runtimeIsolation === undefined
@@ -220,7 +220,7 @@ export class TmuxSessionHost {
220
220
  ?? (request.mode === "resume" ? request.nativeSessionId : undefined);
221
221
  beforeHostStart?.({
222
222
  owner: request.owner,
223
- launchId: request.launchId,
223
+ runtimeGenerationId: request.runtimeGenerationId,
224
224
  ...(request.turnId === undefined ? {} : { turnId: request.turnId }),
225
225
  agentId: request.agentId,
226
226
  adapterId: request.adapterId,
@@ -251,7 +251,7 @@ export class TmuxSessionHost {
251
251
  }
252
252
  let binding = createRuntimeBinding({
253
253
  id: bindingId,
254
- launchId: request.launchId,
254
+ runtimeGenerationId: request.runtimeGenerationId,
255
255
  owner: request.owner,
256
256
  agentId: request.agentId,
257
257
  adapterId: request.adapterId,
@@ -285,16 +285,14 @@ export class TmuxSessionHost {
285
285
  const broker = launchBrokerForHome(yuiHome);
286
286
  const sessionManifest = planned.launch.env.YUI_SESSION_MANIFEST;
287
287
  const frozenControlPlane = planned.launch.env[YUI_CONTROL_PLANE_DESCRIPTOR];
288
- const frozenTaskRuntime = planned.launch.env[YUI_TASK_RUNTIME_DESCRIPTOR];
289
288
  if (request.owner.scope === "task" && request.turnId !== undefined
290
289
  && (sessionManifest === undefined
291
- || frozenControlPlane === undefined
292
- || frozenTaskRuntime === undefined)) {
293
- throw new Error("Managed Task Agent Host launch is missing its Session Manifest or frozen control descriptors.");
290
+ || frozenControlPlane === undefined)) {
291
+ throw new Error("Managed Task Agent Host launch is missing its Session Manifest or frozen control descriptor.");
294
292
  }
295
293
  const reservation = broker.reserve(Object.freeze({
296
- schemaVersion: 1,
297
- launchId: request.launchId,
294
+ schemaVersion: 2,
295
+ runtimeGenerationId: request.runtimeGenerationId,
298
296
  command: planned.launch.command,
299
297
  args: [...planned.launch.args],
300
298
  environment: { ...planned.launch.env },
@@ -311,7 +309,7 @@ export class TmuxSessionHost {
311
309
  fileURLToPath(new URL("../cli.js", import.meta.url)),
312
310
  "internal",
313
311
  "agent-host",
314
- reservation.launchId,
312
+ reservation.runtimeGenerationId,
315
313
  reservation.ticket
316
314
  ],
317
315
  env: {
@@ -319,7 +317,7 @@ export class TmuxSessionHost {
319
317
  YUI_SESSION_SCOPE: request.owner.scope,
320
318
  ...(request.owner.scope === "task" ? { YUI_TASK_ID: request.owner.taskId } : {}),
321
319
  YUI_ROLE: request.owner.roleName,
322
- YUI_LAUNCH_ID: request.launchId,
320
+ YUI_RUNTIME_GENERATION_ID: request.runtimeGenerationId,
323
321
  ...(planned.launch.env.YUI_AGENT_ID === undefined
324
322
  ? {}
325
323
  : { YUI_AGENT_ID: planned.launch.env.YUI_AGENT_ID }),
@@ -334,10 +332,7 @@ export class TmuxSessionHost {
334
332
  : { YUI_SESSION_MANIFEST: sessionManifest }),
335
333
  ...(frozenControlPlane === undefined
336
334
  ? {}
337
- : { [YUI_CONTROL_PLANE_DESCRIPTOR]: frozenControlPlane }),
338
- ...(frozenTaskRuntime === undefined
339
- ? {}
340
- : { [YUI_TASK_RUNTIME_DESCRIPTOR]: frozenTaskRuntime })
335
+ : { [YUI_CONTROL_PLANE_DESCRIPTOR]: frozenControlPlane })
341
336
  }
342
337
  };
343
338
  let hostCreated = false;
@@ -351,7 +346,7 @@ export class TmuxSessionHost {
351
346
  scope: request.owner.scope,
352
347
  ...(request.owner.scope === "task" ? { taskId: request.owner.taskId } : {}),
353
348
  roleName: request.owner.roleName,
354
- launchId: reservation.launchId,
349
+ runtimeGenerationId: reservation.runtimeGenerationId,
355
350
  requireTurnAck: false
356
351
  });
357
352
  providerDispatchObserved = true;
@@ -365,29 +360,36 @@ export class TmuxSessionHost {
365
360
  control: {
366
361
  protocol: AGENT_HOST_CONTROL_PROTOCOL,
367
362
  type: "launch",
368
- launchId: reservation.launchId,
363
+ runtimeGenerationId: reservation.runtimeGenerationId,
369
364
  ticket: reservation.ticket
370
365
  }
371
366
  });
372
- if (controlResult.outcome === "active-other-launch") {
373
- broker.revoke(request.launchId);
367
+ if (controlResult.outcome === "active-other-generation") {
368
+ broker.revoke(request.runtimeGenerationId);
374
369
  throw new RuntimeHostContentionError("provider-child-active", `The persistent Agent Host for ${request.owner.roleName} still owns another Provider Turn.`);
375
370
  }
376
- if (controlResult.outcome === "active-same-launch") {
377
- broker.revoke(request.launchId);
371
+ if (controlResult.outcome === "active-same-generation") {
372
+ broker.revoke(request.runtimeGenerationId);
378
373
  }
379
374
  const acceptableState = ["idle", "ready", "busy"].includes(controlResult.snapshot.state);
380
375
  if (!acceptableState
381
- || controlResult.snapshot.launchId !== reservation.launchId) {
382
- broker.revoke(request.launchId);
383
- throw new Error(`Agent Host did not return an exact Provider acknowledgement for ${reservation.launchId}.`);
376
+ || controlResult.snapshot.runtimeGenerationId !== reservation.runtimeGenerationId) {
377
+ broker.revoke(request.runtimeGenerationId);
378
+ try {
379
+ await stopExactRole(this.tmux, hostId, request.owner.roleName);
380
+ }
381
+ catch {
382
+ // The coordinator will enqueue durable owner cleanup.
383
+ }
384
+ throw new RuntimeGenerationMismatchError(reservation.runtimeGenerationId, controlResult.snapshot.runtimeGenerationId, controlResult.snapshot.state, `Agent Host acknowledgement generation mismatch for ${reservation.runtimeGenerationId}; observed=${controlResult.snapshot.runtimeGenerationId ?? "none"}; `
385
+ + `state=${controlResult.snapshot.state}.`);
384
386
  }
385
387
  providerSnapshot = controlResult.snapshot;
386
388
  providerDispatchObserved = true;
387
389
  }
388
390
  }
389
391
  catch (error) {
390
- broker.revoke(request.launchId);
392
+ broker.revoke(request.runtimeGenerationId);
391
393
  if (hostCreated && !providerDispatchObserved) {
392
394
  try {
393
395
  await stopExactRole(this.tmux, hostId, request.owner.roleName);
@@ -415,7 +417,7 @@ export class TmuxSessionHost {
415
417
  }
416
418
  const binding = createRuntimeBinding({
417
419
  id: bindingId,
418
- launchId: request.launchId,
420
+ runtimeGenerationId: request.runtimeGenerationId,
419
421
  owner: request.owner,
420
422
  agentId: request.agentId,
421
423
  adapterId: request.adapterId,
@@ -529,7 +531,7 @@ export class AgentHostPromptPushAdapter {
529
531
  control: {
530
532
  protocol: AGENT_HOST_CONTROL_PROTOCOL,
531
533
  type: "submit-turn",
532
- launchId: request.binding.launchId,
534
+ runtimeGenerationId: request.binding.runtimeGenerationId,
533
535
  nativeSessionId: request.binding.nativeSessionId,
534
536
  turnId: request.envelope.source.localId,
535
537
  authority: request.binding.providerAuthority,
@@ -573,7 +575,7 @@ export class AgentHostPromptPushAdapter {
573
575
  control: {
574
576
  protocol: AGENT_HOST_CONTROL_PROTOCOL,
575
577
  type: "steer-turn",
576
- launchId: request.launchId,
578
+ runtimeGenerationId: request.runtimeGenerationId,
577
579
  nativeSessionId: request.nativeSessionId,
578
580
  nativeTurnId: request.nativeTurnId,
579
581
  authority: request.providerAuthority,
@@ -7,6 +7,7 @@ import { RuntimeLaunchFailure } from "../runtime/launchDiagnostics.js";
7
7
  import { RuntimeLaunchError } from "../runtime/ports.js";
8
8
  import { formatTurnReceiptId } from "../task/taskRecordReference.js";
9
9
  import { turnInputEnvelope } from "../turn/turn.js";
10
+ import { captureRoleTurnDispatch } from "../coordination/workMailboxQueue.js";
10
11
  import { isSchedulerTaskWorkspaceReady, selectedActiveSchedulerTasks, selectedSchedulerRoles } from "./ports.js";
11
12
  /**
12
13
  * Sole managed Provider write path. A Turn is durable workflow intent;
@@ -31,6 +32,15 @@ async function deliverActiveTurn(store, delivery, task, role, turn, now) {
31
32
  if (!isSchedulerTaskWorkspaceReady(task, store.getTaskWorkspace(task.id))) {
32
33
  return { ...base, status: "skipped", reason: "workspace-not-ready" };
33
34
  }
35
+ const dispatchToken = captureRoleTurnDispatch(store.getWorkMailbox({
36
+ kind: "role",
37
+ taskId: task.id,
38
+ roleName: role.name
39
+ }), {
40
+ taskId: task.id,
41
+ roleName: role.name,
42
+ turnId: turn.id
43
+ });
34
44
  const sessionSet = store.getTaskRoleSessionSet?.(task.id, role.name) ?? null;
35
45
  const binding = sessionSet?.providerBinding ?? null;
36
46
  const observedTurn = binding?.turn ?? null;
@@ -40,8 +50,11 @@ async function deliverActiveTurn(store, delivery, task, role, turn, now) {
40
50
  || binding?.authority.owner === "unknown") {
41
51
  return { ...base, status: "skipped", reason: "writer-attached" };
42
52
  }
43
- if (currentProviderTurn !== null
44
- && ["submitting", "accepted"].includes(currentProviderTurn.status)) {
53
+ if (currentProviderTurn?.status === "accepted") {
54
+ settleAcceptedRoleTurnDispatch(store, turn, dispatchToken);
55
+ return { ...base, status: "skipped", reason: "not-ready" };
56
+ }
57
+ if (currentProviderTurn?.status === "submitting") {
45
58
  return { ...base, status: "skipped", reason: "not-ready" };
46
59
  }
47
60
  if (currentProviderTurn?.status === "delivery-unknown") {
@@ -72,9 +85,9 @@ async function deliverActiveTurn(store, delivery, task, role, turn, now) {
72
85
  mode,
73
86
  turnId: turn.id,
74
87
  ...(nativeSessionId === undefined ? {} : { nativeSessionId }),
75
- ...(mode !== "resume" || existingSession?.launchId === undefined
88
+ ...(mode !== "resume" || existingSession?.runtimeGenerationId === undefined
76
89
  ? {}
77
- : { hostActivationId: existingSession.launchId }),
90
+ : { hostActivationId: existingSession.runtimeGenerationId }),
78
91
  beforeHostStart: (preflight) => persistPreStartSession(store, task, role, turn, existingSession, mode, preflight, now)
79
92
  });
80
93
  const preparedSession = prepared.session === undefined
@@ -85,7 +98,7 @@ async function deliverActiveTurn(store, delivery, task, role, turn, now) {
85
98
  role,
86
99
  turn,
87
100
  session: preparedSession,
88
- ...(prepared.launchId === undefined ? {} : { launchId: prepared.launchId }),
101
+ ...(prepared.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: prepared.runtimeGenerationId }),
89
102
  now
90
103
  });
91
104
  const ready = await delivery.waitUntilReady(prepared);
@@ -95,7 +108,7 @@ async function deliverActiveTurn(store, delivery, task, role, turn, now) {
95
108
  role,
96
109
  turn,
97
110
  session: readySession,
98
- ...(ready.prepared.launchId === undefined ? {} : { launchId: ready.prepared.launchId }),
111
+ ...(ready.prepared.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: ready.prepared.runtimeGenerationId }),
99
112
  now
100
113
  });
101
114
  submitted = true;
@@ -105,7 +118,7 @@ async function deliverActiveTurn(store, delivery, task, role, turn, now) {
105
118
  text: serializeTurnInputEnvelope(turnInputEnvelope(turn))
106
119
  });
107
120
  if (outcome === "busy" || outcome === "unavailable") {
108
- forget(delivery, task.id, role.name, turn.id, ready.prepared.launchId);
121
+ forget(delivery, task.id, role.name, turn.id, ready.prepared.runtimeGenerationId);
109
122
  return {
110
123
  ...base,
111
124
  status: "skipped",
@@ -113,12 +126,13 @@ async function deliverActiveTurn(store, delivery, task, role, turn, now) {
113
126
  };
114
127
  }
115
128
  if (outcome === "rejected" || outcome === "delivery-unknown") {
116
- forget(delivery, task.id, role.name, turn.id, ready.prepared.launchId);
129
+ forget(delivery, task.id, role.name, turn.id, ready.prepared.runtimeGenerationId);
117
130
  return failTurnDelivery(store, turn, now, outcome === "delivery-unknown" ? "delivery-unknown" : "runtime-failed", outcome === "delivery-unknown"
118
131
  ? "Provider Turn delivery is ambiguous; Yui will not replay it automatically."
119
132
  : "Provider rejected the managed Turn.");
120
133
  }
121
- forget(delivery, task.id, role.name, turn.id, ready.prepared.launchId);
134
+ forget(delivery, task.id, role.name, turn.id, ready.prepared.runtimeGenerationId);
135
+ settleAcceptedRoleTurnDispatch(store, turn, dispatchToken);
122
136
  return {
123
137
  ...base,
124
138
  status: outcome === "sent" ? "delivered" : "already-delivered"
@@ -147,7 +161,7 @@ async function deliverActiveTurn(store, delivery, task, role, turn, now) {
147
161
  error: message
148
162
  };
149
163
  }
150
- forget(delivery, task.id, role.name, turn.id, prepared?.launchId);
164
+ forget(delivery, task.id, role.name, turn.id, prepared?.runtimeGenerationId);
151
165
  return failTurnDelivery(store, turn, now, submitted ? "delivery-unknown" : "startup-failed", message);
152
166
  }
153
167
  }
@@ -172,19 +186,27 @@ function failTurnDelivery(store, turn, now, failureReason, summary) {
172
186
  ...(disposition === "failed" ? { terminalized: true } : {})
173
187
  };
174
188
  }
189
+ function settleAcceptedRoleTurnDispatch(store, turn, expected) {
190
+ store.settleRoleTurnDispatch({
191
+ taskId: turn.taskId,
192
+ roleName: turn.roleName,
193
+ turnId: turn.id,
194
+ ...(expected === undefined ? {} : { expected })
195
+ });
196
+ }
175
197
  function persistPreStartSession(store, task, role, turn, existing, mode, preflight, now) {
176
198
  if (preflight.owner.scope !== "task"
177
199
  || preflight.owner.taskId !== task.id
178
200
  || preflight.owner.roleName !== role.name
179
201
  || preflight.turnId !== turn.id
180
- || preflight.launchId.trim().length === 0) {
202
+ || preflight.runtimeGenerationId.trim().length === 0) {
181
203
  throw new Error(`Pre-start launch fence changed the active Role Turn: ${task.id}/${role.name}.`);
182
204
  }
183
205
  const session = preflight.nativeSessionId === undefined ? null : {
184
206
  agentId: preflight.agentId,
185
207
  adapterId: preflight.adapterId,
186
208
  nativeSessionId: preflight.nativeSessionId,
187
- launchId: preflight.launchId,
209
+ runtimeGenerationId: preflight.runtimeGenerationId,
188
210
  ...(preflight.sessionTitle === undefined ? {} : { title: preflight.sessionTitle }),
189
211
  status: "active",
190
212
  effective: preflight.effective
@@ -194,7 +216,7 @@ function persistPreStartSession(store, task, role, turn, existing, mode, preflig
194
216
  role,
195
217
  turn,
196
218
  session: validateRoleSession(role, turn, existing, mode, session),
197
- launchId: preflight.launchId,
219
+ runtimeGenerationId: preflight.runtimeGenerationId,
198
220
  now
199
221
  });
200
222
  }
@@ -232,12 +254,12 @@ function requireResumeSession(role, turn, session) {
232
254
  }
233
255
  return session.nativeSessionId;
234
256
  }
235
- function forget(delivery, taskId, roleName, turnId, launchId) {
257
+ function forget(delivery, taskId, roleName, turnId, runtimeGenerationId) {
236
258
  delivery.forgetPrepared?.({
237
259
  taskId,
238
260
  roleName,
239
261
  turnId,
240
- ...(launchId === undefined ? {} : { launchId })
262
+ ...(runtimeGenerationId === undefined ? {} : { runtimeGenerationId })
241
263
  });
242
264
  }
243
265
  function hasText(value) {
@@ -81,15 +81,18 @@ export async function processLeaderWakeups(store, delivery, now, selection) {
81
81
  : "new"
82
82
  : roleAgentSessionResumeMode(sessionSet, role.effective.agentId, role.effective);
83
83
  const turnId = store.peekNextTurnId(task.id);
84
+ const envelope = store.getTaskWakeEnvelope?.(task.id) ?? null;
84
85
  const contextSnapshot = store.freezeLeaderContextSnapshot?.(task.id, role.name, now);
85
86
  const input = createTurnInput({
86
87
  source: leaderWakeInputSource(wakeup.reasons),
87
88
  directive: [
88
89
  `Wake reasons: ${wakeup.reasons.join(", ")}.`,
90
+ ...(envelope === null ? [] : [envelope.text.trim()]),
89
91
  `Load exact context for ${task.id}/${turnId}.`,
92
+ "Read every referenced completed Worker or Reviewer Turn in full before deciding its disposition.",
90
93
  "Before ending this Turn, judge the affected WorkItems and Task; persist any lifecycle or result changes you establish.",
91
94
  "The final Turn response is evidence only and never updates WorkItem or Task state by itself."
92
- ].join(" "),
95
+ ].join("\n"),
93
96
  ...(contextSnapshot === undefined ? {} : { contextSnapshotRef: contextSnapshot.ref }),
94
97
  deltaRefIds: contextSnapshot?.deltaRefIds ?? []
95
98
  });
@@ -97,7 +100,6 @@ export async function processLeaderWakeups(store, delivery, now, selection) {
97
100
  ...(role.managedWorkspace === undefined ? {} : { workspace: role.managedWorkspace }),
98
101
  effective: role.effective
99
102
  });
100
- const envelope = store.getTaskWakeEnvelope?.(task.id) ?? null;
101
103
  const claim = store.saveLeaderDispatch({
102
104
  task,
103
105
  role,
@@ -155,65 +157,86 @@ async function forceLeaderSteer(store, delivery, taskId, roleName, active, now)
155
157
  if (processing.batchId !== batchId || processing.owner !== owner) {
156
158
  return { taskId, turnId: active.id, status: "skipped", reason: "busy" };
157
159
  }
158
- const sessions = store.getTaskRoleSessionSet?.(taskId, roleName) ?? null;
159
- const session = sessions?.sessions[active.effective.agentId];
160
- const binding = sessions?.providerBinding;
161
- const providerTurn = binding?.turn;
162
- const authority = binding?.authority;
163
- if (session?.launchId === undefined || session.nativeSessionId === undefined
164
- || providerTurn === null || providerTurn === undefined
165
- || providerTurn.turnId !== active.id || providerTurn.nativeTurnId === undefined
166
- || providerTurn.status !== "accepted"
167
- || authority?.owner !== "controller" || authority.holderId === undefined) {
168
- store.releaseWorkMailbox(target, batchId);
169
- return { taskId, status: "failed", reason: "not-ready", error: "Active Leader Turn has no steerable Provider fence." };
170
- }
171
- const batch = processing.batch;
172
- const directive = [
173
- `Aggregated Leader events: ${batch.reasons.join(", ")}.`,
174
- `The first event arrived at ${batch.firstQueuedAt} and has waited at least 10 minutes while this Leader Turn remained active.`,
175
- "Process these events now and update durable Task or WorkItem facts when needed.",
176
- "After the events are handled, continue the work you were doing before this interruption.",
177
- `Load the current exact context for ${taskId}/${active.id}; the event batch may have grown while this input was delivered.`
178
- ].join(" ");
179
- const input = createTurnInput({
180
- source: { type: "yui", channel: "leader-forced-wakeup" },
181
- directive,
182
- deltaRefIds: []
183
- });
184
- const outcome = await delivery.steerOnce({
185
- taskId,
186
- roleName,
187
- agentId: active.effective.agentId,
188
- adapterId: active.effective.adapterId,
189
- launchId: session.launchId,
190
- nativeSessionId: session.nativeSessionId,
191
- nativeTurnId: providerTurn.nativeTurnId,
192
- authority: {
193
- epoch: authority.epoch,
194
- owner: "controller",
195
- holderId: authority.holderId
196
- },
197
- receiptId: `turn-input:${taskId}/${active.id}/${batchId}`,
198
- text: directive
199
- });
200
- if (outcome !== "sent" && outcome !== "already-sent") {
201
- if (outcome !== "delivery-unknown")
160
+ try {
161
+ const sessions = store.getTaskRoleSessionSet?.(taskId, roleName) ?? null;
162
+ const session = sessions?.sessions[active.effective.agentId];
163
+ const binding = sessions?.providerBinding;
164
+ const providerTurn = binding?.turn;
165
+ const authority = binding?.authority;
166
+ if (session?.runtimeGenerationId === undefined || session.nativeSessionId === undefined
167
+ || providerTurn === null || providerTurn === undefined
168
+ || providerTurn.turnId !== active.id || providerTurn.nativeTurnId === undefined
169
+ || providerTurn.status !== "accepted"
170
+ || authority?.owner !== "controller" || authority.holderId === undefined) {
202
171
  store.releaseWorkMailbox(target, batchId);
172
+ return { taskId, status: "failed", reason: "not-ready", error: "Active Leader Turn has no steerable Provider fence." };
173
+ }
174
+ const batch = processing.batch;
175
+ const envelope = store.getTaskWakeEnvelope?.(taskId) ?? null;
176
+ const referencedTurnIds = envelope?.referencedTurnIds ?? [];
177
+ const displayedTurnIds = referencedTurnIds.slice(0, 4);
178
+ const directive = [
179
+ `Aggregated Leader events: ${batch.reasons.join(", ")}.`,
180
+ `The first event arrived at ${batch.firstQueuedAt} and has waited at least 10 minutes while this Leader Turn remained active.`,
181
+ ...displayedTurnIds.map((turnId) => `Read the complete result: yui task turn show ${taskId}/${turnId}.`),
182
+ ...(referencedTurnIds.length > displayedTurnIds.length
183
+ ? [
184
+ `${referencedTurnIds.length - displayedTurnIds.length} additional result Turns are listed in yui task wake show ${taskId} ${envelope.wakeId}.`
185
+ ]
186
+ : []),
187
+ "Process these events now and update durable Task or WorkItem facts when needed.",
188
+ "After the events are handled, continue the work you were doing before this interruption.",
189
+ `Load the current exact context for ${taskId}/${active.id}; the event batch may have grown while this input was delivered.`
190
+ ].join("\n");
191
+ const input = createTurnInput({
192
+ source: { type: "yui", channel: "leader-forced-wakeup" },
193
+ directive,
194
+ deltaRefIds: []
195
+ });
196
+ const outcome = await delivery.steerOnce({
197
+ taskId,
198
+ roleName,
199
+ agentId: active.effective.agentId,
200
+ adapterId: active.effective.adapterId,
201
+ runtimeGenerationId: session.runtimeGenerationId,
202
+ nativeSessionId: session.nativeSessionId,
203
+ nativeTurnId: providerTurn.nativeTurnId,
204
+ authority: {
205
+ epoch: authority.epoch,
206
+ owner: "controller",
207
+ holderId: authority.holderId
208
+ },
209
+ receiptId: `turn-input:${taskId}/${active.id}/${batchId}`,
210
+ text: directive
211
+ });
212
+ if (outcome !== "sent" && outcome !== "already-sent") {
213
+ if (outcome !== "delivery-unknown")
214
+ store.releaseWorkMailbox(target, batchId);
215
+ return {
216
+ taskId,
217
+ turnId: active.id,
218
+ status: outcome === "busy" ? "skipped" : "failed",
219
+ reason: outcome === "busy" ? "busy" : "not-ready",
220
+ error: outcome
221
+ };
222
+ }
223
+ const saved = store.saveLeaderSteer({ taskId, turnId: active.id, batchId, input, now });
224
+ if (saved !== "claimed")
225
+ store.releaseWorkMailbox(target, batchId);
226
+ return saved === "claimed"
227
+ ? { taskId, turnId: active.id, status: "steered" }
228
+ : { taskId, turnId: active.id, status: "skipped", reason: saved };
229
+ }
230
+ catch (error) {
231
+ store.releaseWorkMailbox(target, batchId);
203
232
  return {
204
233
  taskId,
205
234
  turnId: active.id,
206
- status: outcome === "busy" ? "skipped" : "failed",
207
- reason: outcome === "busy" ? "busy" : "not-ready",
208
- error: outcome
235
+ status: "failed",
236
+ reason: "not-ready",
237
+ error: error instanceof Error ? error.message : String(error)
209
238
  };
210
239
  }
211
- const saved = store.saveLeaderSteer({ taskId, turnId: active.id, batchId, input, now });
212
- if (saved !== "claimed")
213
- store.releaseWorkMailbox(target, batchId);
214
- return saved === "claimed"
215
- ? { taskId, turnId: active.id, status: "steered" }
216
- : { taskId, turnId: active.id, status: "skipped", reason: saved };
217
240
  }
218
241
  function hasNativeSession(session) {
219
242
  return session !== null