@zq-silk/yui 0.14.1 → 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 (177) hide show
  1. package/ARCHITECTURE.md +64 -35
  2. package/README.md +254 -124
  3. package/dist/agent/managedRuntimeEnvironment.js +5 -8
  4. package/dist/cli/commandCatalog.js +74 -109
  5. package/dist/cli/helpRenderer.js +1 -2
  6. package/dist/cli/interactionCandidates.js +10 -0
  7. package/dist/cli/interactionPolicy.js +76 -37
  8. package/dist/cli/roleWizard.js +5 -6
  9. package/dist/cli/updateOrchestrator.js +10 -8
  10. package/dist/cli/updatePorts.js +17 -13
  11. package/dist/cli/upgradeCommand.js +2 -5
  12. package/dist/cli.js +268 -231
  13. package/dist/commands/agentCommands.js +13 -0
  14. package/dist/commands/configCommands.js +7 -19
  15. package/dist/commands/configOverview.js +3 -2
  16. package/dist/commands/durableJobCommands.js +1 -1
  17. package/dist/commands/executionAuditCommands.js +4 -9
  18. package/dist/commands/globalRoleCommands.js +17 -0
  19. package/dist/commands/profileCommands.js +200 -30
  20. package/dist/commands/sessionCommands.js +1 -1
  21. package/dist/commands/taskActor.js +29 -127
  22. package/dist/commands/taskChangeSetCommands.js +1 -3
  23. package/dist/commands/taskCommands.js +1771 -2107
  24. package/dist/commands/taskCompletionGate.js +27 -103
  25. package/dist/commands/taskContextCommand.js +47 -80
  26. package/dist/commands/taskExecutionCommands.js +9 -27
  27. package/dist/commands/taskInputCommands.js +10 -10
  28. package/dist/commands/taskIntegrationCommands.js +69 -61
  29. package/dist/commands/taskIntegrationQueueCommands.js +1 -1
  30. package/dist/commands/taskNextActionCommand.js +8 -29
  31. package/dist/commands/taskOverviewCommand.js +25 -3
  32. package/dist/commands/taskPublicationCommands.js +151 -85
  33. package/dist/commands/taskPublicationVerifyCommand.js +181 -0
  34. package/dist/commands/taskRemoteDeliveryCommand.js +134 -0
  35. package/dist/commands/taskRoleRuntimeStatus.js +5 -6
  36. package/dist/commands/taskUpstreamCommands.js +79 -95
  37. package/dist/config/configCatalog.js +1 -1
  38. package/dist/context/contextSnapshot.js +17 -2
  39. package/dist/context/sourceTurnContext.js +30 -0
  40. package/dist/context/turnContextPack.js +107 -48
  41. package/dist/context/turnInputContract.js +11 -4
  42. package/dist/context/wakeNotification.js +84 -22
  43. package/dist/controller/agentRuntimeObserver.js +9 -12
  44. package/dist/controller/clientRuntime.js +13 -13
  45. package/dist/controller/controller.js +11 -11
  46. package/dist/controller/fileSchedulerStoreAdapter.js +262 -219
  47. package/dist/controller/jobControl.js +11 -10
  48. package/dist/controller/resourceInventory.js +6 -6
  49. package/dist/controller/resourceInventoryLinux.js +2 -2
  50. package/dist/controller/runtime.js +18 -61
  51. package/dist/controller/runtimeEventInbox.js +57 -34
  52. package/dist/controller/runtimeEventProcessor.js +36 -96
  53. package/dist/controller/runtimeHookTurnFence.js +33 -57
  54. package/dist/controller/runtimeLaunchCoordinator.js +44 -40
  55. package/dist/controller/runtimeObservationHook.js +18 -12
  56. package/dist/controller/sessionNotify.js +43 -28
  57. package/dist/controller/sessionOwnerReconciliation.js +21 -21
  58. package/dist/controller/structuredProviderObservation.js +13 -16
  59. package/dist/coordination/workMailboxQueue.js +132 -0
  60. package/dist/core/controllerServer.js +1 -0
  61. package/dist/domain/agentResultTransport.js +60 -0
  62. package/dist/execution/executionHealth.js +111 -118
  63. package/dist/execution/reviewMainTurn.js +161 -0
  64. package/dist/execution/workItemExecution.js +454 -0
  65. package/dist/execution/workItemExecutionProjection.js +309 -0
  66. package/dist/execution/workItemMainTurn.js +164 -0
  67. package/dist/executor/agentExecutor.js +19 -19
  68. package/dist/executor/executorRegistry.js +7 -28
  69. package/dist/executor/fileRoleLaunchPlanner.js +16 -82
  70. package/dist/external/githubPublicationVerifier.js +114 -0
  71. package/dist/external/gitlabPublicationVerifier.js +158 -0
  72. package/dist/external/pinnedCommandRunner.js +73 -0
  73. package/dist/integration/changeSetManifest.js +0 -3
  74. package/dist/integration/deliveryObligation.js +43 -6
  75. package/dist/integration/gitIntegrationService.js +284 -101
  76. package/dist/integration/integrationAttempt.js +64 -14
  77. package/dist/integration/integrationQueueEntry.js +5 -35
  78. package/dist/integration/integrationQueueService.js +65 -194
  79. package/dist/lifecycle/canonicalLifecycleEvent.js +16 -15
  80. package/dist/lifecycle/exactTurnTerminalization.js +100 -97
  81. package/dist/message/message.js +16 -0
  82. package/dist/observability/executionAudit.js +4 -25
  83. package/dist/observability/faultClassification.js +58 -70
  84. package/dist/observability/orchestrationMetrics.js +8 -50
  85. package/dist/profile/agentProfile.js +60 -15
  86. package/dist/profile/agentProfileRuntime.js +100 -0
  87. package/dist/release/releaseWorkflowPorts.js +5 -68
  88. package/dist/repository/executionLaneGitSnapshot.js +80 -0
  89. package/dist/repository/gitWorkspace.js +9 -67
  90. package/dist/repository/taskBaseFreshness.js +3 -1
  91. package/dist/repository/taskWorkspaceCoordinator.js +32 -4
  92. package/dist/repository/taskWorkspacePreparer.js +252 -411
  93. package/dist/resources/liveReferences.js +6 -55
  94. package/dist/resources/resourceDiscovery.js +1 -30
  95. package/dist/resources/resourceRegistrar.js +0 -23
  96. package/dist/review/deltaRecheck.js +8 -65
  97. package/dist/review/reviewAcceptance.js +22 -19
  98. package/dist/review/reviewConfig.js +0 -10
  99. package/dist/review/reviewDecision.js +2 -2
  100. package/dist/review/reviewRound.js +147 -273
  101. package/dist/review/reviewerAvailability.js +13 -23
  102. package/dist/role/role.js +3 -0
  103. package/dist/runtime/agentDriverObservation.js +1 -1
  104. package/dist/runtime/agentHost.js +51 -51
  105. package/dist/runtime/builtinAgentDrivers.js +9 -2
  106. package/dist/runtime/codexAppServerRuntime.js +23 -5
  107. package/dist/runtime/exactControlPlane.js +2 -267
  108. package/dist/runtime/firstProgressAdvisory.js +1 -1
  109. package/dist/runtime/index.js +1 -1
  110. package/dist/runtime/launchBroker.js +11 -11
  111. package/dist/runtime/lifecycleReservation.js +2 -2
  112. package/dist/runtime/managedCaller.js +111 -0
  113. package/dist/runtime/ports.js +16 -3
  114. package/dist/runtime/preallocatedNativeSession.js +2 -2
  115. package/dist/runtime/processExitObservation.js +3 -2
  116. package/dist/runtime/providerContinuationReconciliationService.js +1 -1
  117. package/dist/runtime/runtimeBinding.js +1 -1
  118. package/dist/runtime/runtimeObservation.js +33 -16
  119. package/dist/runtime/runtimeSessionCandidate.js +2 -2
  120. package/dist/runtime/runtimeStopReceipt.js +11 -11
  121. package/dist/runtime/sessionLaunchRequest.js +2 -2
  122. package/dist/runtime/sessionOwnerIdentity.js +9 -9
  123. package/dist/runtime/sessionOwnerRegistry.js +13 -13
  124. package/dist/runtime/sessionReconciliation.js +4 -4
  125. package/dist/runtime/sessionTerminationGuard.js +9 -9
  126. package/dist/runtime/sessionTokenMetrics.js +5 -7
  127. package/dist/runtime/structuredProviderHost.js +5 -5
  128. package/dist/runtime/taskRuntimeIsolation.js +24 -24
  129. package/dist/runtime/tmuxAdapters.js +32 -30
  130. package/dist/scheduler/activeRoleTurnDelivery.js +37 -15
  131. package/dist/scheduler/leaderWakeupProcessor.js +78 -55
  132. package/dist/scheduler/roleTurnLiveness.js +6 -6
  133. package/dist/scheduler/roleTurnStall.js +8 -9
  134. package/dist/scheduler/taskExecutionProjection.js +19 -41
  135. package/dist/scheduler/taskObservabilityProjection.js +75 -122
  136. package/dist/storage/sqliteSchema.js +1 -21
  137. package/dist/storage/sqliteStore.js +62 -47
  138. package/dist/storage/storageSchema.js +1 -8
  139. package/dist/storage/storageVersions.js +7 -4
  140. package/dist/storage/taskStore.js +8 -8
  141. package/dist/storage/upgrade/upgradeOrchestrator.js +112 -83
  142. package/dist/task/completionReadiness.js +17 -98
  143. package/dist/task/deliveryGuard.js +9 -11
  144. package/dist/task/draftPlan.js +142 -0
  145. package/dist/task/nextAction.js +179 -477
  146. package/dist/task/publicationVerification.js +1 -0
  147. package/dist/task/remoteDelivery.js +233 -0
  148. package/dist/task/task.js +108 -4
  149. package/dist/task/taskRecordReference.js +0 -1
  150. package/dist/turn/turn.js +109 -16
  151. package/dist/verification/verificationGateService.js +0 -26
  152. package/dist/web/assets/client/components.js +99 -121
  153. package/dist/web/assets/client/i18n.js +136 -58
  154. package/dist/web/assets/client/view.js +44 -0
  155. package/dist/web/assets/styles/cards.js +3 -15
  156. package/dist/web/assets/styles/widgets.js +4 -0
  157. package/dist/web/webSnapshot.js +29 -14
  158. package/dist/workItem/dependencyGate.js +34 -0
  159. package/dist/workItem/workItem.js +99 -266
  160. package/dist/workspace/workItemChangeSetManager.js +15 -59
  161. package/docs/task-local-identity.md +1 -1
  162. package/i18n/README.zh-CN.md +164 -58
  163. package/package.json +1 -1
  164. package/skills/yui-leader/SKILL.md +272 -782
  165. package/skills/yui-operator/SKILL.md +190 -365
  166. package/skills/yui-reviewer/SKILL.md +40 -33
  167. package/skills/yui-runtime/SKILL.md +22 -0
  168. package/skills/yui-worker/SKILL.md +19 -6
  169. package/dist/execution/candidateConvergence.js +0 -623
  170. package/dist/execution/executionGroup.js +0 -854
  171. package/dist/execution/resourceBroker.js +0 -426
  172. package/dist/executor/launchPlan.js +0 -16
  173. package/dist/review/reviewFinding.js +0 -240
  174. package/dist/review/reviewFindingLedger.js +0 -550
  175. package/dist/review/reviewOutcomeClassifier.js +0 -255
  176. package/dist/scheduler/resourceQueueProjection.js +0 -71
  177. package/dist/task/repairWave.js +0 -150
@@ -1,6 +1,6 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
2
  import { runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
3
- import { createRuntimeBinding, RuntimeHostContentionError, RuntimeLaunchError } from "../runtime/index.js";
3
+ import { createRuntimeBinding, RuntimeGenerationMismatchError, RuntimeHostContentionError, RuntimeLaunchError } from "../runtime/index.js";
4
4
  import { effectiveLaunchSnapshotsCompatible, validateEffectiveLaunchSnapshot } from "../executor/effectiveLaunch.js";
5
5
  class RuntimeBindingContractError extends Error {
6
6
  constructor(message, options) {
@@ -89,7 +89,7 @@ export class RuntimeLaunchCoordinator {
89
89
  };
90
90
  const generationPrefix = `runtime-${expectedFingerprint}:generation:`;
91
91
  const proposedGenerationId = requireText(this.#createGenerationId(), "Launch generation id");
92
- let proposedLaunchId = `${generationPrefix}${proposedGenerationId}`;
92
+ let proposedRuntimeGenerationId = `${generationPrefix}${proposedGenerationId}`;
93
93
  let reusedConfirmedRunningHost = false;
94
94
  if (request.mode === "resume" && request.hostActivationId !== undefined) {
95
95
  if (!request.hostActivationId.startsWith(generationPrefix)) {
@@ -100,25 +100,25 @@ export class RuntimeLaunchCoordinator {
100
100
  throw new RuntimeLaunchError(true, request.hostActivationId, `Host activation is temporarily ${inspection.state}: ${request.owner.roleName}.`);
101
101
  }
102
102
  if (inspection.state === "running") {
103
- proposedLaunchId = request.hostActivationId;
103
+ proposedRuntimeGenerationId = request.hostActivationId;
104
104
  reusedConfirmedRunningHost = true;
105
105
  }
106
106
  }
107
107
  let runtimeIsolation = reusedConfirmedRunningHost
108
108
  ? undefined
109
- : this.#preflightRuntimeIsolation(request, proposedLaunchId, proposedGenerationId, false);
109
+ : this.#preflightRuntimeIsolation(request, proposedRuntimeGenerationId, proposedGenerationId, false);
110
110
  let reservation = this.reservations.reserveRuntimeLaunch({
111
111
  owner: request.owner,
112
- launchId: proposedLaunchId
112
+ runtimeGenerationId: proposedRuntimeGenerationId
113
113
  }, assertLaunchCurrent, this.#now());
114
114
  if (reservation.status === "existing") {
115
- if (!reservation.launchId.startsWith(generationPrefix)) {
115
+ if (!reservation.runtimeGenerationId.startsWith(generationPrefix)) {
116
116
  this.#requireCleanup(request.owner);
117
117
  throw new Error(`Runtime launch reservation belongs to stale Role or Agent state: ${request.owner.roleName}.`);
118
118
  }
119
119
  const inspection = await this.host.inspectOwner(request.owner);
120
120
  if (inspection.state === "unavailable" || inspection.state === "starting") {
121
- throw new RuntimeLaunchError(true, reservation.launchId, `Runtime is temporarily ${inspection.state}: ${request.owner.roleName}/${reservation.launchId}.`);
121
+ throw new RuntimeLaunchError(true, reservation.runtimeGenerationId, `Runtime is temporarily ${inspection.state}: ${request.owner.roleName}/${reservation.runtimeGenerationId}.`);
122
122
  }
123
123
  if (inspection.state === "stopped") {
124
124
  const taskOwner = request.owner.scope === "task"
@@ -127,10 +127,10 @@ export class RuntimeLaunchCoordinator {
127
127
  let exactCleanupAttempted = false;
128
128
  let completed = false;
129
129
  try {
130
- completed = this.reservations.completeRuntimeLaunchReservation(request.owner, reservation.launchId, taskOwner !== undefined && this.#runtimeIsolation !== undefined
130
+ completed = this.reservations.completeRuntimeLaunchReservation(request.owner, reservation.runtimeGenerationId, taskOwner !== undefined && this.#runtimeIsolation !== undefined
131
131
  ? () => {
132
132
  exactCleanupAttempted = true;
133
- this.#cleanupTaskLaunch(taskOwner, reservation.launchId);
133
+ this.#cleanupTaskLaunch(taskOwner, reservation.runtimeGenerationId);
134
134
  }
135
135
  : undefined);
136
136
  }
@@ -146,7 +146,7 @@ export class RuntimeLaunchCoordinator {
146
146
  }
147
147
  reservation = this.reservations.reserveRuntimeLaunch({
148
148
  owner: request.owner,
149
- launchId: proposedLaunchId
149
+ runtimeGenerationId: proposedRuntimeGenerationId
150
150
  }, assertLaunchCurrent, this.#now());
151
151
  if (reservation.status !== "reserved") {
152
152
  throw new Error("Runtime launch reservation could not be renewed.");
@@ -158,13 +158,13 @@ export class RuntimeLaunchCoordinator {
158
158
  assertLaunchCurrent();
159
159
  }
160
160
  }
161
- const launchId = reservation.launchId;
161
+ const runtimeGenerationId = reservation.runtimeGenerationId;
162
162
  let preflightObserved = beforeHostStart === undefined;
163
163
  const observePreflight = (preflight) => {
164
164
  if (preflightObserved) {
165
165
  throw new Error("Runtime host reported its pre-start launch fence more than once.");
166
166
  }
167
- validateRuntimeLaunchPreflight(preflight, request, launchId);
167
+ validateRuntimeLaunchPreflight(preflight, request, runtimeGenerationId);
168
168
  preflightObserved = true;
169
169
  beforeHostStart?.(preflight);
170
170
  };
@@ -179,7 +179,7 @@ export class RuntimeLaunchCoordinator {
179
179
  const rawBinding = request.mode === "new"
180
180
  ? await this.host.start({
181
181
  mode: "new",
182
- launchId,
182
+ runtimeGenerationId,
183
183
  owner: request.owner,
184
184
  agentId: request.agentId,
185
185
  adapterId: request.adapterId,
@@ -195,7 +195,7 @@ export class RuntimeLaunchCoordinator {
195
195
  }, beforeHostStart === undefined ? undefined : observePreflight)
196
196
  : await this.host.restore({
197
197
  mode: "resume",
198
- launchId,
198
+ runtimeGenerationId,
199
199
  owner: request.owner,
200
200
  agentId: request.agentId,
201
201
  adapterId: request.adapterId,
@@ -210,24 +210,28 @@ export class RuntimeLaunchCoordinator {
210
210
  : { environment: request.environment }),
211
211
  nativeSessionId: requireText(request.nativeSessionId, "Native session id")
212
212
  }, beforeHostStart === undefined ? undefined : observePreflight);
213
- binding = requireMatchingRuntimeBinding(rawBinding, request, launchId);
213
+ binding = requireMatchingRuntimeBinding(rawBinding, request, runtimeGenerationId);
214
214
  if (!preflightObserved) {
215
215
  throw new Error("Runtime session host did not expose a pre-host-start launch fence.");
216
216
  }
217
217
  }
218
218
  catch (error) {
219
+ if (error instanceof RuntimeGenerationMismatchError) {
220
+ await this.#settleFailedStart(request, runtimeGenerationId, reusedConfirmedRunningHost, runtimeIsolation, true);
221
+ throw new RuntimeLaunchError(false, runtimeGenerationId, error.message, "generation-mismatch");
222
+ }
219
223
  if (error instanceof RuntimeHostContentionError && reusedConfirmedRunningHost) {
220
224
  // The exact recovered generation remains authoritative. A late human
221
225
  // writer is transient backpressure and must not settle, clean, or
222
226
  // terminalize that reservation.
223
- throw new RuntimeLaunchError(true, launchId, error.message, error.reason);
227
+ throw new RuntimeLaunchError(true, runtimeGenerationId, error.message, error.reason);
224
228
  }
225
229
  if (error instanceof RuntimeHostContentionError
226
230
  && !reusedConfirmedRunningHost) {
227
231
  let exactCleanupAttempted = false;
228
232
  let completed = false;
229
233
  try {
230
- completed = this.reservations.completeRuntimeLaunchReservation(request.owner, launchId, runtimeIsolation === undefined
234
+ completed = this.reservations.completeRuntimeLaunchReservation(request.owner, runtimeGenerationId, runtimeIsolation === undefined
231
235
  ? undefined
232
236
  : () => {
233
237
  exactCleanupAttempted = true;
@@ -243,7 +247,7 @@ export class RuntimeLaunchCoordinator {
243
247
  this.#requireCleanup(request.owner);
244
248
  throw new Error("Busy managed runtime launch reservation changed during retry release.");
245
249
  }
246
- throw new RuntimeLaunchError(true, launchId, error.message, error instanceof RuntimeHostContentionError
250
+ throw new RuntimeLaunchError(true, runtimeGenerationId, error.message, error instanceof RuntimeHostContentionError
247
251
  ? error.reason
248
252
  : "previous-process");
249
253
  }
@@ -253,40 +257,40 @@ export class RuntimeLaunchCoordinator {
253
257
  this.#requireCleanup(request.owner);
254
258
  throw error;
255
259
  }
256
- await this.#settleFailedStart(request, launchId, reusedConfirmedRunningHost, runtimeIsolation);
260
+ await this.#settleFailedStart(request, runtimeGenerationId, reusedConfirmedRunningHost, runtimeIsolation);
257
261
  throw error;
258
262
  }
259
263
  if (reusedConfirmedRunningHost && binding.hostCreated === true) {
260
- await this.#compensateStartedHost(request.owner, binding, launchId, runtimeIsolation, new Error(`Runtime host was recreated while recovering an existing generation: ${request.owner.roleName}.`));
264
+ await this.#compensateStartedHost(request.owner, binding, runtimeGenerationId, runtimeIsolation, new Error(`Runtime host was recreated while recovering an existing generation: ${request.owner.roleName}.`));
261
265
  }
262
266
  try {
263
267
  assertLaunchCurrent();
264
268
  const reservationState = this.reservations.confirmRuntimeLaunchReservation({
265
269
  owner: request.owner,
266
- launchId
270
+ runtimeGenerationId
267
271
  }, assertLaunchCurrent);
268
272
  if (reservationState === "reserved") {
269
273
  if (binding.nativeSessionId !== undefined) {
270
274
  this.reservations.recordReservedRuntimeNativeSession({
271
275
  owner: request.owner,
272
- launchId,
276
+ runtimeGenerationId,
273
277
  agentId: request.agentId,
274
278
  adapterId: request.adapterId,
275
279
  nativeSessionId: binding.nativeSessionId,
276
280
  effective: request.effective
277
281
  }, assertLaunchCurrent, this.#now());
278
282
  }
279
- else if (!this.reservations.completeRuntimeLaunchReservation(request.owner, launchId)) {
283
+ else if (!this.reservations.completeRuntimeLaunchReservation(request.owner, runtimeGenerationId)) {
280
284
  throw new Error("Runtime Host-start reservation changed before completion.");
281
285
  }
282
286
  }
283
287
  }
284
288
  catch (error) {
285
- await this.#compensateStartedHost(request.owner, binding, launchId, runtimeIsolation, error);
289
+ await this.#compensateStartedHost(request.owner, binding, runtimeGenerationId, runtimeIsolation, error);
286
290
  }
287
291
  return binding;
288
292
  }
289
- async #settleFailedStart(request, launchId, reusedConfirmedRunningHost, runtimeIsolation) {
293
+ async #settleFailedStart(request, runtimeGenerationId, reusedConfirmedRunningHost, runtimeIsolation, forceCleanup = false) {
290
294
  let inspection;
291
295
  try {
292
296
  inspection = await this.host.inspectOwner(request.owner);
@@ -294,7 +298,7 @@ export class RuntimeLaunchCoordinator {
294
298
  catch {
295
299
  // A rebind/preflight failure did not create the already-confirmed host.
296
300
  // Preserve its generation when the follow-up probe is merely unknown.
297
- if (!reusedConfirmedRunningHost)
301
+ if (!reusedConfirmedRunningHost || forceCleanup)
298
302
  this.#requireCleanup(request.owner);
299
303
  return;
300
304
  }
@@ -308,16 +312,16 @@ export class RuntimeLaunchCoordinator {
308
312
  throw new Error(`Task runtime launch failed and exact resource cleanup also failed: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`, { cause: cleanupError });
309
313
  }
310
314
  }
311
- if (!this.#settleConfirmedStopped(request, launchId)) {
315
+ if (!this.#settleConfirmedStopped(request, runtimeGenerationId)) {
312
316
  this.#requireCleanup(request.owner);
313
317
  }
314
318
  return;
315
319
  }
316
- if (reusedConfirmedRunningHost)
320
+ if (reusedConfirmedRunningHost && !forceCleanup)
317
321
  return;
318
322
  this.#requireCleanup(request.owner);
319
323
  }
320
- async #compensateStartedHost(owner, binding, launchId, runtimeIsolation, cause) {
324
+ async #compensateStartedHost(owner, binding, runtimeGenerationId, runtimeIsolation, cause) {
321
325
  try {
322
326
  await this.host.stop(binding);
323
327
  }
@@ -344,7 +348,7 @@ export class RuntimeLaunchCoordinator {
344
348
  if (!stopped
345
349
  || !this.reservations.settleStoppedRuntimeLaunch({
346
350
  owner,
347
- launchId,
351
+ runtimeGenerationId,
348
352
  agentId: binding.agentId,
349
353
  adapterId: binding.adapterId,
350
354
  ...(binding.nativeSessionId === undefined
@@ -356,33 +360,33 @@ export class RuntimeLaunchCoordinator {
356
360
  const detail = cause instanceof Error ? cause.message : String(cause);
357
361
  throw new RuntimeLaunchStateChangedError(`Runtime launch was compensated after state changed: ${detail}`, { cause });
358
362
  }
359
- #preflightRuntimeIsolation(request, launchId, generationId, allowExactActive) {
363
+ #preflightRuntimeIsolation(request, runtimeGenerationId, generationId, allowExactActive) {
360
364
  if (this.#runtimeIsolation === undefined || request.owner.scope !== "task") {
361
365
  return undefined;
362
366
  }
363
367
  return this.#runtimeIsolation.preflight({
364
368
  workspace: request.managedWorkspace,
365
- launchId,
369
+ runtimeGenerationId,
366
370
  generationId: requireText(generationId, "Launch generation id"),
367
371
  ...(request.runtimePolicy === undefined ? {} : { policy: request.runtimePolicy }),
368
372
  ...(allowExactActive ? { allowExactActive: true } : {})
369
373
  });
370
374
  }
371
- #cleanupTaskLaunch(owner, launchId) {
375
+ #cleanupTaskLaunch(owner, runtimeGenerationId) {
372
376
  const cleanupTaskLaunch = this.#runtimeIsolation?.cleanupTaskLaunch;
373
377
  if (cleanupTaskLaunch === undefined) {
374
378
  throw new Error("Exact Task runtime launch cleanup is unavailable.");
375
379
  }
376
380
  cleanupTaskLaunch.call(this.#runtimeIsolation, {
377
381
  taskId: owner.taskId,
378
- launchId,
382
+ runtimeGenerationId,
379
383
  reason: "interruption"
380
384
  });
381
385
  }
382
- #settleConfirmedStopped(request, launchId) {
386
+ #settleConfirmedStopped(request, runtimeGenerationId) {
383
387
  return this.reservations.settleStoppedRuntimeLaunch({
384
388
  owner: request.owner,
385
- launchId,
389
+ runtimeGenerationId,
386
390
  agentId: request.agentId,
387
391
  adapterId: request.adapterId,
388
392
  ...(request.nativeSessionId === undefined
@@ -401,13 +405,13 @@ export class RuntimeLaunchCoordinator {
401
405
  }
402
406
  }
403
407
  }
404
- function validateRuntimeLaunchPreflight(preflight, request, launchId) {
408
+ function validateRuntimeLaunchPreflight(preflight, request, runtimeGenerationId) {
405
409
  const ownerMatches = preflight.owner.scope === request.owner.scope
406
410
  && preflight.owner.roleName === request.owner.roleName
407
411
  && (preflight.owner.scope === "global"
408
412
  || (request.owner.scope === "task"
409
413
  && preflight.owner.taskId === request.owner.taskId));
410
- if (preflight.launchId !== launchId
414
+ if (preflight.runtimeGenerationId !== runtimeGenerationId
411
415
  || !ownerMatches
412
416
  || preflight.turnId !== request.turnId
413
417
  || preflight.agentId !== request.agentId
@@ -418,7 +422,7 @@ function validateRuntimeLaunchPreflight(preflight, request, launchId) {
418
422
  throw new Error(`Session host pre-start launch fence does not match the requested runtime: ${request.owner.roleName}.`);
419
423
  }
420
424
  }
421
- function requireMatchingRuntimeBinding(raw, request, launchId) {
425
+ function requireMatchingRuntimeBinding(raw, request, runtimeGenerationId) {
422
426
  let binding;
423
427
  try {
424
428
  binding = createRuntimeBinding(raw);
@@ -431,7 +435,7 @@ function requireMatchingRuntimeBinding(raw, request, launchId) {
431
435
  && (binding.owner.scope === "global"
432
436
  || (request.owner.scope === "task"
433
437
  && binding.owner.taskId === request.owner.taskId));
434
- if (binding.launchId !== launchId
438
+ if (binding.runtimeGenerationId !== runtimeGenerationId
435
439
  || !ownerMatches
436
440
  || binding.agentId !== request.agentId
437
441
  || binding.adapterId !== request.adapterId
@@ -4,6 +4,7 @@ import { normalizeAgentDriverHookClassification } from "../runtime/agentDriver.j
4
4
  import { mapAgentDriverHooks } from "../runtime/agentDriverObservation.js";
5
5
  import { runtimeLifecycleSignalKey } from "../runtime/lifecycleReservation.js";
6
6
  import { formatTurnReceiptId } from "../task/taskRecordReference.js";
7
+ import { boundedTurnFailureDiagnostic, transportAgentResult } from "../domain/agentResultTransport.js";
7
8
  import { FileRuntimeEventInbox, MAX_RUNTIME_EVENT_FILE_BYTES } from "./runtimeEventInbox.js";
8
9
  import { resolveRuntimeHookTurnFence } from "./runtimeHookTurnFence.js";
9
10
  /**
@@ -48,28 +49,34 @@ async function runGlobalRuntimeTurnHook(stdinJson, environment, call, now, depen
48
49
  }
49
50
  const home = requireIdentity(environment.YUI_HOME, "YUI_HOME");
50
51
  const roleName = requireIdentity(environment.YUI_ROLE, "Role name");
51
- new FileRuntimeEventInbox(home, () => now).enqueueTurnCompleted({
52
+ new FileRuntimeEventInbox(home, () => now).enqueueTurnTerminal({
52
53
  scope: "global",
53
54
  roleName,
54
55
  agentId: requireIdentity(environment.YUI_AGENT_ID, "Agent id"),
55
56
  adapterId: "claude",
56
- launchId: requireIdentity(environment.YUI_LAUNCH_ID, "Launch id"),
57
+ runtimeGenerationId: requireIdentity(environment.YUI_RUNTIME_GENERATION_ID, "Runtime generation id"),
57
58
  nativeSessionId,
58
59
  nativeTurnId: optionalIdentity(driver.runtime.nativeTurnId(nativeHook)) ?? occurrenceId,
59
60
  ...(environment.YUI_SESSION_TITLE === undefined
60
61
  ? {}
61
62
  : { title: requireIdentity(environment.YUI_SESSION_TITLE, "Session title") }),
62
- summary: globalHookSummary(payload, hookEventName)
63
+ providerStatus: hookEventName === "Stop" ? "completed" : "failed",
64
+ outcome: globalHookOutcome(payload, hookEventName)
63
65
  });
64
66
  await call(home, "scheduler.signal", { key: runtimeLifecycleSignalKey({ scope: "global", roleName }) }, { timeoutMs: 100 }).catch(() => { });
65
67
  }
66
- function globalHookSummary(payload, hookEventName) {
67
- for (const field of ["last_assistant_message", "summary", "message", "error"]) {
68
- const value = payload[field];
69
- if (typeof value === "string" && value.trim().length > 0)
70
- return value.trim();
68
+ function globalHookOutcome(payload, hookEventName) {
69
+ if (hookEventName === "Stop") {
70
+ return transportAgentResult(payload.last_assistant_message);
71
71
  }
72
- return hookEventName === "Stop" ? "Native Turn completed." : "Native Turn failed.";
72
+ const diagnostic = [payload.error, payload.error_details]
73
+ .filter((value) => typeof value === "string" && value.trim().length > 0)
74
+ .join("\n");
75
+ return {
76
+ status: "failed",
77
+ failureReason: "runtime-failed",
78
+ diagnostic: boundedTurnFailureDiagnostic(diagnostic)
79
+ };
73
80
  }
74
81
  export function parseRuntimeObservationHook(stdinJson, environment, now = new Date(), dependencies = {}) {
75
82
  const payload = parseObject(stdinJson);
@@ -113,10 +120,9 @@ export function parseRuntimeObservationHook(stdinJson, environment, now = new Da
113
120
  turnId: fence.turnId,
114
121
  agentId: fence.agentId,
115
122
  driverId,
116
- launchId: fence.launchId,
117
- sessionGenerationId: fence.launchId,
123
+ runtimeGenerationId: fence.runtimeGenerationId,
118
124
  conversationId: fence.nativeSessionId,
119
- activationId: fence.launchId,
125
+ activationId: fence.runtimeGenerationId,
120
126
  nativeSessionId: fence.nativeSessionId,
121
127
  nativeTurnId: nativeTurnId ?? fence.turnId,
122
128
  receiptId: fence.receiptId ?? formatTurnReceiptId(fence.taskId, fence.turnId)
@@ -1,25 +1,30 @@
1
1
  import { callController } from "../core/controllerClient.js";
2
2
  import { FileRuntimeEventInbox } from "./runtimeEventInbox.js";
3
+ import { transportAgentResult } from "../domain/agentResultTransport.js";
3
4
  import { runtimeLifecycleSignalKey } from "../runtime/lifecycleReservation.js";
4
5
  import { setCodexThreadName } from "../execution/codexThreadNaming.js";
5
6
  import { openCurrentTaskStore } from "../storage/currentTaskStore.js";
6
- const NO_FINAL_ASSISTANT_MESSAGE_SUMMARY = "Native Turn completed without a final assistant message.";
7
7
  /** Hidden CLI entrypoint used by Codex's structured notify hook. */
8
8
  export async function runSessionNotifyCommand(payloadArgument, environment = process.env, call, setThreadName = setCodexThreadName) {
9
9
  const params = parseCodexSessionNotification(payloadArgument, environment);
10
10
  const home = requireText(environment.YUI_HOME, "YUI_HOME");
11
- const enqueued = new FileRuntimeEventInbox(home).enqueueTurnCompleted({
11
+ // A Codex process outlives its Turn, so the notify envelope cannot say which
12
+ // Turn or runtime generation is current. Durable Session state answers both;
13
+ // the envelope's runtime generation id is used only before a Session has been projected.
14
+ const current = currentNotifyGeneration(home, params);
15
+ const enqueued = new FileRuntimeEventInbox(home).enqueueTurnTerminal({
12
16
  scope: params.scope,
13
17
  ...(params.scope === "task" ? { taskId: params.taskId } : {}),
14
18
  roleName: params.roleName,
15
19
  agentId: params.agentId,
16
20
  adapterId: params.adapterId,
17
- launchId: params.launchId,
21
+ ...(current.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: current.runtimeGenerationId }),
18
22
  nativeSessionId: params.nativeSessionId,
19
23
  nativeTurnId: params.nativeTurnId,
20
- ...(params.turnId === undefined ? {} : { turnId: params.turnId }),
24
+ ...(current.turnId === undefined ? {} : { turnId: current.turnId }),
21
25
  ...(params.title === undefined ? {} : { title: params.title }),
22
- summary: params.lastAssistantMessage
26
+ providerStatus: "completed",
27
+ outcome: params.outcome
23
28
  });
24
29
  // The durable write is authoritative. This short socket call is only a
25
30
  // wake-up hint and never starts or waits for a Controller process.
@@ -35,12 +40,36 @@ export async function runSessionNotifyCommand(payloadArgument, environment = pro
35
40
  roleName: params.roleName
36
41
  })
37
42
  }, { timeoutMs: 100 }).catch(() => { });
38
- if (enqueued.created && shouldSetThreadName(home, params)) {
43
+ if (enqueued.created && shouldSetThreadName(home, params, current.turnId)) {
39
44
  const request = threadNameRequest(params, environment);
40
45
  if (request !== null)
41
46
  await setThreadName(request).catch(() => { });
42
47
  }
43
48
  }
49
+ /**
50
+ * Reads the durable runtime generation and active Turn for the notifying
51
+ * Session. The envelope's runtime generation id only applies before Yui has projected a
52
+ * Session for this Role, which is the one moment durable state cannot answer.
53
+ */
54
+ function currentNotifyGeneration(home, params) {
55
+ if (params.scope !== "task" || params.taskId === undefined) {
56
+ return params.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: params.runtimeGenerationId };
57
+ }
58
+ try {
59
+ const store = openCurrentTaskStore(home);
60
+ const session = store.getTaskRoleSessionSet(params.taskId, params.roleName)
61
+ ?.sessions[params.agentId];
62
+ const activeTurn = store.getActiveTurn(params.taskId, params.roleName);
63
+ const runtimeGenerationId = session?.runtimeGenerationId ?? params.runtimeGenerationId;
64
+ return {
65
+ ...(runtimeGenerationId === undefined ? {} : { runtimeGenerationId }),
66
+ ...(activeTurn === null ? {} : { turnId: activeTurn.id })
67
+ };
68
+ }
69
+ catch {
70
+ return params.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: params.runtimeGenerationId };
71
+ }
72
+ }
44
73
  export function parseCodexSessionNotification(payloadArgument, environment) {
45
74
  const payload = parseObject(payloadArgument, "Codex notify payload");
46
75
  if (payload.type !== "agent-turn-complete") {
@@ -48,10 +77,7 @@ export function parseCodexSessionNotification(payloadArgument, environment) {
48
77
  }
49
78
  const nativeSessionId = requireText(payload["thread-id"], "Codex thread-id");
50
79
  const nativeTurnId = requireText(payload["turn-id"], "Codex turn-id");
51
- const lastAssistantMessage = requireAssistantMessage(payload["last-assistant-message"]);
52
- const turnId = environment.YUI_TURN_ID === undefined
53
- ? undefined
54
- : requireText(environment.YUI_TURN_ID, "YUI_TURN_ID");
80
+ const outcome = transportAgentResult(payload["last-assistant-message"]);
55
81
  const title = environment.YUI_SESSION_TITLE === undefined
56
82
  ? undefined
57
83
  : requireText(environment.YUI_SESSION_TITLE, "YUI_SESSION_TITLE");
@@ -64,12 +90,13 @@ export function parseCodexSessionNotification(payloadArgument, environment) {
64
90
  roleName: requireText(environment.YUI_ROLE, "YUI_ROLE"),
65
91
  agentId: requireText(environment.YUI_AGENT_ID, "YUI_AGENT_ID"),
66
92
  adapterId: requireCodexAdapter(environment.YUI_ADAPTER_ID),
67
- launchId: requireText(environment.YUI_LAUNCH_ID, "YUI_LAUNCH_ID"),
93
+ ...(environment.YUI_RUNTIME_GENERATION_ID === undefined
94
+ ? {}
95
+ : { runtimeGenerationId: requireText(environment.YUI_RUNTIME_GENERATION_ID, "YUI_RUNTIME_GENERATION_ID") }),
68
96
  nativeSessionId,
69
97
  nativeTurnId,
70
- ...(turnId === undefined ? {} : { turnId }),
71
98
  ...(title === undefined ? {} : { title }),
72
- lastAssistantMessage
99
+ outcome
73
100
  };
74
101
  return scope === "task"
75
102
  ? {
@@ -106,26 +133,14 @@ function requireText(value, label) {
106
133
  throw new Error(`${label} is invalid.`);
107
134
  return text;
108
135
  }
109
- function requireAssistantMessage(value) {
110
- if (value === null)
111
- return NO_FINAL_ASSISTANT_MESSAGE_SUMMARY;
112
- if (typeof value !== "string" || value.includes("\0")) {
113
- throw new Error("Codex last assistant message is required.");
114
- }
115
- const text = value.trim();
116
- if (text.length === 0 || Buffer.byteLength(text) > 524_288) {
117
- throw new Error("Codex last assistant message is invalid.");
118
- }
119
- return text;
120
- }
121
- function shouldSetThreadName(home, params) {
136
+ function shouldSetThreadName(home, params, turnId) {
122
137
  if (params.scope !== "task"
123
138
  || params.title === undefined
124
- || params.turnId === undefined)
139
+ || turnId === undefined)
125
140
  return false;
126
141
  try {
127
142
  const store = openCurrentTaskStore(home);
128
- return store.getTurn(params.taskId, params.turnId)?.mode === "new";
143
+ return store.getTurn(params.taskId, turnId)?.mode === "new";
129
144
  }
130
145
  catch {
131
146
  return false;
@@ -26,11 +26,11 @@ export class SessionOwnerReconciliation {
26
26
  /**
27
27
  * Persists the exact physical owner identity after a host created a new
28
28
  * Provider process. The Provider root is attributed by the exact
29
- * YUI_LAUNCH_ID environment fence; the tmux pane PID is only a weaker
29
+ * YUI_RUNTIME_GENERATION_ID environment fence; the tmux pane PID is only a weaker
30
30
  * fallback when the fence cannot be read.
31
31
  */
32
32
  recordHostOwner(input) {
33
- const discovered = discoverProviderRootByLaunchEnv(input.launchId);
33
+ const discovered = discoverProviderRootByLaunchEnv(input.runtimeGenerationId);
34
34
  const fallback = discovered === undefined && input.panePid !== undefined
35
35
  ? readLinuxProcessIdentity(input.panePid)
36
36
  : undefined;
@@ -38,7 +38,7 @@ export class SessionOwnerReconciliation {
38
38
  ? undefined
39
39
  : { pid: fallback.pid, identity: fallback });
40
40
  if (root === undefined) {
41
- this.#onWarning(`Session owner identity could not attribute a Provider root for launch ${input.launchId}; `
41
+ this.#onWarning(`Session owner identity could not attribute a Provider root for launch ${input.runtimeGenerationId}; `
42
42
  + "no owner record was written.");
43
43
  return;
44
44
  }
@@ -52,7 +52,7 @@ export class SessionOwnerReconciliation {
52
52
  },
53
53
  agentId: input.agentId,
54
54
  adapterId: input.adapterId,
55
- launchId: input.launchId,
55
+ runtimeGenerationId: input.runtimeGenerationId,
56
56
  ...(input.nativeSessionId === undefined
57
57
  ? {}
58
58
  : { nativeSessionId: input.nativeSessionId }),
@@ -98,7 +98,7 @@ export class SessionOwnerReconciliation {
98
98
  return undefined;
99
99
  }
100
100
  },
101
- lastStopOutcome: (taskId, roleName, launchId) => (lastSessionTerminationOutcome(this.#store, taskId, roleName, launchId)),
101
+ lastStopOutcome: (taskId, roleName, runtimeGenerationId) => (lastSessionTerminationOutcome(this.#store, taskId, roleName, runtimeGenerationId)),
102
102
  now: new Date()
103
103
  });
104
104
  }
@@ -130,7 +130,7 @@ export class SessionOwnerReconciliation {
130
130
  return false;
131
131
  }
132
132
  },
133
- listLaunchFencedProcesses: (launchId) => listLaunchFencedProcesses(launchId),
133
+ listLaunchFencedProcesses: (runtimeGenerationId) => listLaunchFencedProcesses(runtimeGenerationId),
134
134
  signalProcess: (pid, signal) => process.kill(pid, signal),
135
135
  signalProcessGroup: (processGroupId, signal) => {
136
136
  try {
@@ -148,23 +148,23 @@ export class SessionOwnerReconciliation {
148
148
  const result = await terminateSessionOwners(owner, records, ports, options);
149
149
  if (result.outcome === "stop-confirmed") {
150
150
  for (const record of result.confirmed) {
151
- this.#store.removeSessionOwner(record.launchId);
152
- removeRuntimeStopReceipt(this.#home, record.launchId);
151
+ this.#store.removeSessionOwner(record.runtimeGenerationId);
152
+ removeRuntimeStopReceipt(this.#home, record.runtimeGenerationId);
153
153
  }
154
154
  }
155
155
  return result;
156
156
  }
157
157
  #recordTerminationEvent(event) {
158
158
  const owner = event.owner;
159
- if (event.stage === "stop-requested" && event.launchId !== undefined) {
160
- writeRuntimeStopReceipt(this.#home, event.launchId, event.at);
159
+ if (event.stage === "stop-requested" && event.runtimeGenerationId !== undefined) {
160
+ writeRuntimeStopReceipt(this.#home, event.runtimeGenerationId, event.at);
161
161
  }
162
162
  if (owner.scope !== "task")
163
163
  return;
164
164
  try {
165
165
  this.#store.saveEvent(owner.taskId, createTaskEvent(this.#store.nextEventId(owner.taskId), owner.taskId, "runtime.session-termination", {
166
166
  roleName: owner.roleName,
167
- launchId: event.launchId ?? "",
167
+ runtimeGenerationId: event.runtimeGenerationId ?? "",
168
168
  nativeSessionId: event.nativeSessionId ?? "",
169
169
  outcome: event.stage,
170
170
  ...(event.detail === undefined ? {} : { detail: event.detail })
@@ -175,7 +175,7 @@ export class SessionOwnerReconciliation {
175
175
  }
176
176
  }
177
177
  }
178
- /** Projects every durable Role session generation for reconciliation. */
178
+ /** Projects every durable Role runtime generation for reconciliation. */
179
179
  export function durableSessionFacts(store) {
180
180
  const facts = [];
181
181
  for (const task of store.listTasks()) {
@@ -187,7 +187,7 @@ export function durableSessionFacts(store) {
187
187
  roleName: set.owner.roleName,
188
188
  agentId,
189
189
  adapterId: session.adapterId,
190
- ...(session.launchId === undefined ? {} : { launchId: session.launchId }),
190
+ ...(session.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: session.runtimeGenerationId }),
191
191
  ...(session.nativeSessionId === undefined
192
192
  ? {}
193
193
  : { nativeSessionId: session.nativeSessionId }),
@@ -202,7 +202,7 @@ export function durableSessionFacts(store) {
202
202
  roleName: set.owner.roleName,
203
203
  agentId: history.agentId,
204
204
  adapterId: history.adapterId,
205
- ...(history.launchId === undefined ? {} : { launchId: history.launchId }),
205
+ ...(history.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: history.runtimeGenerationId }),
206
206
  ...(history.nativeSessionId === undefined
207
207
  ? {}
208
208
  : { nativeSessionId: history.nativeSessionId }),
@@ -219,7 +219,7 @@ export function durableSessionFacts(store) {
219
219
  roleName: set.owner.roleName,
220
220
  agentId,
221
221
  adapterId: session.adapterId,
222
- ...(session.launchId === undefined ? {} : { launchId: session.launchId }),
222
+ ...(session.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: session.runtimeGenerationId }),
223
223
  ...(session.nativeSessionId === undefined
224
224
  ? {}
225
225
  : { nativeSessionId: session.nativeSessionId }),
@@ -234,7 +234,7 @@ export function durableSessionFacts(store) {
234
234
  roleName: set.owner.roleName,
235
235
  agentId: session.agentId,
236
236
  adapterId: session.adapterId,
237
- ...(session.launchId === undefined ? {} : { launchId: session.launchId }),
237
+ ...(session.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: session.runtimeGenerationId }),
238
238
  ...(session.nativeSessionId === undefined
239
239
  ? {}
240
240
  : { nativeSessionId: session.nativeSessionId }),
@@ -297,7 +297,7 @@ export function observeSessionOwnerPhysical(record) {
297
297
  };
298
298
  }
299
299
  /** Reads the latest termination outcome recorded for one generation. */
300
- export function lastSessionTerminationOutcome(store, taskId, roleName, launchId) {
300
+ export function lastSessionTerminationOutcome(store, taskId, roleName, runtimeGenerationId) {
301
301
  if (taskId === undefined)
302
302
  return undefined;
303
303
  let latest;
@@ -306,10 +306,10 @@ export function lastSessionTerminationOutcome(store, taskId, roleName, launchId)
306
306
  continue;
307
307
  if (event.payload.roleName !== roleName)
308
308
  continue;
309
- if (launchId !== ""
310
- && event.payload.launchId !== undefined
311
- && event.payload.launchId !== ""
312
- && event.payload.launchId !== launchId) {
309
+ if (runtimeGenerationId !== ""
310
+ && event.payload.runtimeGenerationId !== undefined
311
+ && event.payload.runtimeGenerationId !== ""
312
+ && event.payload.runtimeGenerationId !== runtimeGenerationId) {
313
313
  continue;
314
314
  }
315
315
  latest = event.payload.outcome;