@zq-silk/yui 0.7.1 → 0.8.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 (73) hide show
  1. package/ARCHITECTURE.md +27 -28
  2. package/README.md +79 -71
  3. package/dist/cli/commandCatalog.js +283 -136
  4. package/dist/cli/completion.js +3 -3
  5. package/dist/cli/helpRenderer.js +3 -0
  6. package/dist/cli/interactionPolicy.js +48 -33
  7. package/dist/cli/interactiveSelection.js +1 -1
  8. package/dist/cli/invocationRouter.js +3 -2
  9. package/dist/cli/roleWizard.js +8 -8
  10. package/dist/cli.js +189 -93
  11. package/dist/commands/agentCommands.js +5 -5
  12. package/dist/commands/configCommands.js +351 -104
  13. package/dist/commands/configOverview.js +60 -0
  14. package/dist/commands/deliveryGuardPreflight.js +2 -2
  15. package/dist/commands/globalRoleCommands.js +9 -9
  16. package/dist/commands/profileCommands.js +8 -8
  17. package/dist/commands/resourcesCommands.js +6 -5
  18. package/dist/commands/taskCommands.js +111 -59
  19. package/dist/commands/taskRoleRuntimeStatus.js +3 -1
  20. package/dist/commands/telemetryCommands.js +11 -6
  21. package/dist/config/configCatalog.js +42 -0
  22. package/dist/config/yuiConfig.js +80 -35
  23. package/dist/context/sessionBootstrapManifest.js +1 -1
  24. package/dist/controller/clientRuntime.js +0 -2
  25. package/dist/controller/controller.js +21 -9
  26. package/dist/controller/fileSchedulerStoreAdapter.js +409 -79
  27. package/dist/controller/resourceInventory.js +9 -5
  28. package/dist/controller/runtime.js +112 -25
  29. package/dist/controller/runtimeLaunchCoordinator.js +18 -78
  30. package/dist/controller/structuredProviderObservation.js +273 -0
  31. package/dist/doctor/doctor.js +2 -2
  32. package/dist/executor/agentAdapter.js +40 -0
  33. package/dist/executor/agentExecutor.js +31 -7
  34. package/dist/executor/executorRegistry.js +11 -49
  35. package/dist/executor/fileRoleLaunchPlanner.js +115 -37
  36. package/dist/lifecycle/canonicalLifecycleEvent.js +5 -2
  37. package/dist/resources/autoResourceGc.js +3 -1
  38. package/dist/review/reviewConfig.js +0 -2
  39. package/dist/run/agentRun.js +2 -2
  40. package/dist/run/providerRetry.js +29 -16
  41. package/dist/run/providerRetryConfig.js +5 -3
  42. package/dist/runtime/agentHost.js +767 -158
  43. package/dist/runtime/builtinAgentDrivers.js +1 -5
  44. package/dist/runtime/codexAppServerRuntime.js +67 -60
  45. package/dist/runtime/exactControlPlane.js +7 -2
  46. package/dist/runtime/index.js +6 -2
  47. package/dist/runtime/launchBroker.js +30 -8
  48. package/dist/runtime/launchDiagnostics.js +1 -1
  49. package/dist/runtime/providerAuthorityFence.js +24 -0
  50. package/dist/runtime/providerControl.js +63 -0
  51. package/dist/runtime/providerRecoveryDecision.js +55 -0
  52. package/dist/runtime/providerRuntimeIdentity.js +269 -19
  53. package/dist/runtime/runtimeBinding.js +20 -11
  54. package/dist/runtime/structuredProviderHost.js +476 -0
  55. package/dist/runtime/tmuxAdapters.js +143 -42
  56. package/dist/scheduler/activeRoleRunDelivery.js +206 -120
  57. package/dist/scheduler/leaderWakeupProcessor.js +141 -16
  58. package/dist/scheduler/roleRunStall.js +12 -9
  59. package/dist/setup/setupCommand.js +153 -492
  60. package/dist/storage/compatibleTaskStore.js +9 -5
  61. package/dist/storage/migration/productionRegistry.js +169 -0
  62. package/dist/storage/taskStore.js +22 -3
  63. package/dist/telemetry/sqliteTelemetryStore.js +9 -1
  64. package/dist/telemetry/telemetryConfig.js +1 -18
  65. package/dist/telemetry/telemetryStore.js +2 -2
  66. package/dist/telemetry/telemetryWiring.js +6 -5
  67. package/dist/tmux/tmuxManager.js +1 -1
  68. package/dist/web/webSnapshot.js +5 -3
  69. package/i18n/README.zh-CN.md +48 -40
  70. package/package.json +1 -1
  71. package/skills/yui-leader/SKILL.md +12 -5
  72. package/skills/yui-operator/SKILL.md +44 -6
  73. package/skills/yui-runtime/SKILL.md +1 -1
@@ -113,16 +113,17 @@ export function buildControllerResourceInventory(facts) {
113
113
  for (const process of paneProcesses)
114
114
  claimed.add(process.pid);
115
115
  const role = findRole(homeFact.roles, pane);
116
- const archived = role?.taskStatus === "archived";
116
+ const terminalIsolation = role?.taskStatus === "retired"
117
+ || role?.taskStatus === "archived";
117
118
  resources.push(processResource({
118
119
  kind: "agent-session",
119
120
  state: pane.dead ? "dead" : role === undefined ? "orphaned" : "running",
120
121
  disposition: role === undefined
121
122
  ? pane.dead ? "safe" : "review"
122
- : archived ? "safe" : "protected",
123
+ : terminalIsolation ? "safe" : "protected",
123
124
  reasonCode: role === undefined
124
125
  ? pane.dead ? "dead-orphan-pane" : "orphan-pane"
125
- : archived ? "archived-task-pane" : "owned-role-pane",
126
+ : terminalIsolation ? `${role.taskStatus}-task-pane` : "owned-role-pane",
126
127
  yuiHome,
127
128
  owner: role === undefined ? { kind: "none" } : roleOwner(role),
128
129
  processes: paneProcesses,
@@ -359,7 +360,10 @@ function artifactResource(artifact, yuiHome, domain, homeId) {
359
360
  ...(domain === undefined ? {} : { domain })
360
361
  };
361
362
  }
362
- function domainDisposition(base, _reason, domain, target) {
363
+ function domainDisposition(base, reason, domain, target) {
364
+ if (reason === "retired-task-pane" || reason === "archived-task-pane") {
365
+ return "safe";
366
+ }
363
367
  if (domain === undefined)
364
368
  return base;
365
369
  if (domain.disposition === "safe") {
@@ -377,7 +381,7 @@ function domainDisposition(base, _reason, domain, target) {
377
381
  // A tmux server with panes is protected until the pane resources have
378
382
  // been removed and a later bounded pass can revalidate the empty
379
383
  // server. This avoids killing a server while a target race is in flight.
380
- if (base === "protected" && _reason === "owned-tmux-server")
384
+ if (base === "protected" && reason === "owned-tmux-server")
381
385
  return "protected";
382
386
  // `report-only` is deliberately reserved for an unrecognized process;
383
387
  // the YUI_HOME environment alone is not a cleanup ownership proof.
@@ -1,4 +1,4 @@
1
- import { reconciliationIntervalMilliseconds, resolveTmuxBin } from "../config/yuiConfig.js";
1
+ import { reconciliationIntervalMilliseconds, resolveAgentLaunchInactivityTimeoutSeconds, resolveControllerTaskConcurrency, resolveDeliveryTimeoutSeconds, resolveRuntimeHealth, resolveTmuxBin, resolveTmuxHistoryLimit } from "../config/yuiConfig.js";
2
2
  import { createHash } from "node:crypto";
3
3
  import { resolve } from "node:path";
4
4
  import { isDeepStrictEqual } from "node:util";
@@ -17,9 +17,9 @@ import { AsyncTaskStoreClient, resolveStoreWorkerEnabledForHome } from "../stora
17
17
  import { FileTaskWorkspacePreparer } from "../repository/taskWorkspacePreparer.js";
18
18
  import { NodeCommandExecutor } from "../tmux/commandExecutor.js";
19
19
  import { TmuxManager, yuiTmuxServerName } from "../tmux/tmuxManager.js";
20
- import { FileTaskRuntimeIsolation, TmuxPromptPushAdapter, TmuxSessionHost, ProviderContinuationReconciliationService } from "../runtime/index.js";
20
+ import { AgentHostPromptPushAdapter, FileTaskRuntimeIsolation, TmuxSessionHost, ProviderContinuationReconciliationService } from "../runtime/index.js";
21
21
  import { startFileTaskController } from "./controller.js";
22
- import { FileSchedulerStoreAdapter } from "./fileSchedulerStoreAdapter.js";
22
+ import { AgentHostProviderTurnFenceError, FileSchedulerStoreAdapter } from "./fileSchedulerStoreAdapter.js";
23
23
  import { openSchedulerTelemetry } from "../telemetry/telemetryWiring.js";
24
24
  import { createFileArtifactPort, createLinuxProcessPort, DurableJobSupervisor } from "./jobSupervisor.js";
25
25
  import { createDurableJobControl } from "./jobControl.js";
@@ -38,7 +38,7 @@ import { launchBrokerForHome } from "../runtime/launchBroker.js";
38
38
  import { classifyRuntimeProcessExit, validateRuntimeProcessExitObservation } from "../runtime/processExitObservation.js";
39
39
  import { appendGlobalProcessExitObservation } from "../runtime/globalProcessExitStore.js";
40
40
  import { builtinAgentDriverRegistry } from "../runtime/builtinAgentDrivers.js";
41
- import { runtimeObservationFromTaskEvent } from "../runtime/runtimeObservation.js";
41
+ import { createRuntimeObservation, runtimeObservationFromTaskEvent } from "../runtime/runtimeObservation.js";
42
42
  import { createTaskEvent } from "../event/taskEvent.js";
43
43
  /** Refreshes only the exact Task runtime generation folded by the event transaction. */
44
44
  export function refreshAppliedTaskRuntimeDescriptor(store, planner, input) {
@@ -92,6 +92,7 @@ export async function startFileTaskControllerRuntime(home, options = {}) {
92
92
  ? new SqliteTaskStore(home)
93
93
  : openCompatibleFileTaskStore(home));
94
94
  const homeId = store.getHomeIdentity().homeId;
95
+ const durableConfig = store.getConfig();
95
96
  // When the worker backend is active, the db-touching observer folds run in
96
97
  // the worker (off the main event loop). The client is closed on shutdown.
97
98
  const asyncStoreClient = useWorker
@@ -113,8 +114,9 @@ export async function startFileTaskControllerRuntime(home, options = {}) {
113
114
  const planner = options.planner ?? new FileRoleLaunchPlanner(home, store, {
114
115
  environment: options.environment
115
116
  });
116
- const tmux = options.tmux ?? new TmuxManager(resolveTmuxBin(store.getConfig().tmuxBin), new NodeCommandExecutor(), {
117
+ const tmux = options.tmux ?? new TmuxManager(resolveTmuxBin(durableConfig.tmuxBin), new NodeCommandExecutor(), {
117
118
  yuiHome: home,
119
+ historyLimit: resolveTmuxHistoryLimit(durableConfig.tmuxHistoryLimit),
118
120
  ...(domainIdentity === undefined
119
121
  ? {}
120
122
  : {
@@ -181,8 +183,7 @@ export async function startFileTaskControllerRuntime(home, options = {}) {
181
183
  }
182
184
  throw new Error("Native session discovery was aborted.");
183
185
  },
184
- inactivityTimeoutMs: positiveIntegerOption(options.environment?.YUI_LAUNCH_INACTIVITY_TIMEOUT_MS
185
- ?? process.env.YUI_LAUNCH_INACTIVITY_TIMEOUT_MS, 300_000),
186
+ inactivityTimeoutMs: resolveAgentLaunchInactivityTimeoutSeconds(durableConfig.agentLaunchInactivityTimeoutSeconds) * 1_000,
186
187
  onHostCreated: ({ binding, pane }) => {
187
188
  sessionOwners.recordHostOwner({
188
189
  owner: binding.owner,
@@ -197,7 +198,7 @@ export async function startFileTaskControllerRuntime(home, options = {}) {
197
198
  }
198
199
  });
199
200
  const promptPush = options.promptPush
200
- ?? new TmuxPromptPushAdapter(tmux, agentProcessReadinessProbe);
201
+ ?? new AgentHostPromptPushAdapter(home);
201
202
  const runtimeIsolation = options.runtimeIsolation
202
203
  ?? new FileTaskRuntimeIsolation({
203
204
  // A sibling of the exact control Home keeps provider data/cache/tmp out
@@ -282,9 +283,6 @@ export async function startFileTaskControllerRuntime(home, options = {}) {
282
283
  sessionHost,
283
284
  promptPush,
284
285
  launchCoordinator,
285
- ...(options.providerInputRouting === undefined
286
- ? {}
287
- : { providerInputRouting: options.providerInputRouting }),
288
286
  roleResourceInventory: async (panes, inputs) => {
289
287
  const inventory = await scanInventory(panes);
290
288
  return inventory.resources.flatMap((resource) => {
@@ -438,9 +436,16 @@ export async function startFileTaskControllerRuntime(home, options = {}) {
438
436
  intervalMs: options.intervalMs
439
437
  ?? reconciliationIntervalMilliseconds(store.getConfig().reconciliationIntervalSeconds),
440
438
  signalWindowMs: options.signalWindowMs,
441
- taskConcurrency: options.taskConcurrency,
439
+ taskConcurrency: options.taskConcurrency
440
+ ?? resolveControllerTaskConcurrency(durableConfig.controllerTaskConcurrency),
442
441
  deliveryRetryMs: options.deliveryRetryMs,
443
442
  deliveryRetryLimit: options.deliveryRetryLimit,
443
+ deliveryTimeoutMs: options.deliveryTimeoutMs
444
+ ?? resolveDeliveryTimeoutSeconds(durableConfig.deliveryTimeoutSeconds) * 1_000,
445
+ stallWindowMs: options.stallWindowMs
446
+ ?? resolveRuntimeHealth(durableConfig.runtimeHealth).stallWindowMs,
447
+ diagnosticAfterMs: options.diagnosticAfterMs
448
+ ?? resolveRuntimeHealth(durableConfig.runtimeHealth).diagnosticAfterMs,
444
449
  now: options.now,
445
450
  onError: options.onError,
446
451
  lifecycleHost,
@@ -497,13 +502,28 @@ export async function startFileTaskControllerRuntime(home, options = {}) {
497
502
  catch (error) {
498
503
  (options.onError ?? (() => undefined))(error);
499
504
  }
505
+ let resourceClose;
506
+ const closeResources = () => {
507
+ resourceClose ??= Promise.all([
508
+ asyncStoreClient?.close() ?? Promise.resolve(),
509
+ inventoryClient?.close() ?? Promise.resolve()
510
+ ]).then(() => undefined);
511
+ return resourceClose;
512
+ };
513
+ const closed = running.closed.then(closeResources);
500
514
  return {
501
515
  ...running,
516
+ closed,
502
517
  close: async () => {
503
- await running.close();
504
- // Release the worker's database connections when the worker backend is active.
505
- await asyncStoreClient?.close();
506
- await inventoryClient?.close();
518
+ try {
519
+ await running.close();
520
+ }
521
+ finally {
522
+ // RPC-driven Controller stops resolve `running.closed` without calling
523
+ // this wrapper. Share one cleanup promise so both lifecycle paths
524
+ // release the worker connections before the process can linger.
525
+ await closeResources();
526
+ }
507
527
  },
508
528
  store,
509
529
  schedulerStore,
@@ -527,6 +547,55 @@ export function createRuntimeLifecycleDispatcher(store, schedulerStore, sessionH
527
547
  });
528
548
  const lifecycleTails = new Map();
529
549
  return async (method, params) => {
550
+ if (method === "runtime.observation-apply") {
551
+ return {
552
+ outcome: schedulerStore.observeRuntimeObservation(createRuntimeObservation(params), new Date())
553
+ };
554
+ }
555
+ if (method === "runtime.provider-turn-begin") {
556
+ const value = providerTurnControlParams(params);
557
+ try {
558
+ schedulerStore.beginAgentHostProviderTurn({
559
+ taskId: value.taskId,
560
+ roleName: value.roleName,
561
+ runId: value.runId,
562
+ agentId: value.agentId,
563
+ launchId: value.launchId,
564
+ nativeSessionId: value.nativeSessionId,
565
+ attemptId: value.attemptId,
566
+ authorityEpoch: value.authorityEpoch,
567
+ authorityOwner: value.authorityOwner,
568
+ holderId: value.holderId,
569
+ now: value.now
570
+ });
571
+ }
572
+ catch (error) {
573
+ if (error instanceof AgentHostProviderTurnFenceError) {
574
+ throw applicationError("INVALID_PARAMS", error.message);
575
+ }
576
+ throw error;
577
+ }
578
+ return { recorded: true };
579
+ }
580
+ if (method === "runtime.provider-turn-submission-resolve") {
581
+ const value = providerTurnControlParams(params);
582
+ const status = params.status;
583
+ const reason = params.reason;
584
+ if ((status !== "rejected" && status !== "delivery-unknown")
585
+ || typeof reason !== "string" || reason.trim().length === 0) {
586
+ throw applicationError("INVALID_PARAMS", "Provider Turn resolution is invalid.");
587
+ }
588
+ schedulerStore.resolveAgentHostProviderTurnSubmission({
589
+ taskId: value.taskId,
590
+ roleName: value.roleName,
591
+ runId: value.runId,
592
+ attemptId: value.attemptId,
593
+ status,
594
+ reason,
595
+ now: value.now
596
+ });
597
+ return { recorded: true };
598
+ }
530
599
  if (method === "runtime.process-exit-observe") {
531
600
  const observation = validateRuntimeProcessExitObservation(params);
532
601
  const run = observation.taskId === undefined || observation.runId === undefined
@@ -1009,6 +1078,33 @@ function requiredParam(value) {
1009
1078
  }
1010
1079
  return value;
1011
1080
  }
1081
+ function providerTurnControlParams(params) {
1082
+ if (typeof params !== "object" || params === null || Array.isArray(params)) {
1083
+ throw applicationError("INVALID_PARAMS", "Provider Turn control params are invalid.");
1084
+ }
1085
+ const value = params;
1086
+ const authorityEpoch = value.authorityEpoch;
1087
+ const authorityOwner = value.authorityOwner;
1088
+ const observedAt = requiredParam(value.observedAt);
1089
+ if (!Number.isSafeInteger(authorityEpoch) || authorityEpoch < 1
1090
+ || (authorityOwner !== "controller" && authorityOwner !== "human")
1091
+ || !Number.isFinite(Date.parse(observedAt))) {
1092
+ throw applicationError("INVALID_PARAMS", "Provider Turn control fence is invalid.");
1093
+ }
1094
+ return {
1095
+ taskId: requiredParam(value.taskId),
1096
+ roleName: requiredParam(value.roleName),
1097
+ runId: requiredParam(value.runId),
1098
+ agentId: requiredParam(value.agentId),
1099
+ launchId: requiredParam(value.launchId),
1100
+ nativeSessionId: requiredParam(value.nativeSessionId),
1101
+ attemptId: requiredParam(value.attemptId),
1102
+ authorityEpoch: authorityEpoch,
1103
+ authorityOwner,
1104
+ holderId: requiredParam(value.holderId),
1105
+ now: new Date(observedAt)
1106
+ };
1107
+ }
1012
1108
  function abortableDelay(milliseconds, signal) {
1013
1109
  return new Promise((resolve, reject) => {
1014
1110
  const timer = setTimeout(() => {
@@ -1026,15 +1122,6 @@ function abortableDelay(milliseconds, signal) {
1026
1122
  signal.addEventListener("abort", onAbort, { once: true });
1027
1123
  });
1028
1124
  }
1029
- function positiveIntegerOption(value, fallback) {
1030
- if (value === undefined || value.trim().length === 0)
1031
- return fallback;
1032
- const parsed = Number(value);
1033
- if (!Number.isSafeInteger(parsed) || parsed <= 0) {
1034
- throw new Error(`Invalid native session discovery timeout: ${value}`);
1035
- }
1036
- return parsed;
1037
- }
1038
1125
  function applicationError(code, message) {
1039
1126
  const error = Object.assign(new Error(message), { code });
1040
1127
  error.name = "CoreApplicationError";
@@ -8,12 +8,6 @@ class RuntimeBindingContractError extends Error {
8
8
  this.name = "RuntimeBindingContractError";
9
9
  }
10
10
  }
11
- class LaunchSubmittedHostBusyError extends RuntimeBindingContractError {
12
- constructor(message) {
13
- super(message);
14
- this.name = "LaunchSubmittedHostBusyError";
15
- }
16
- }
17
11
  class RuntimeLaunchStateChangedError extends Error {
18
12
  constructor(message, options) {
19
13
  super(message, options);
@@ -103,12 +97,6 @@ export class RuntimeLaunchCoordinator {
103
97
  ...(request.runId === undefined ? {} : { runId: request.runId })
104
98
  }, assertLaunchCurrent, this.#now());
105
99
  let reusedConfirmedRunningHost = false;
106
- const launchCarriesExactRunPrompt = carriesExactRunPrompt(request);
107
- // Preserve the existing Codex recovery path; managed Claude is the one
108
- // finite-process protocol that must never fall back to tmux key delivery
109
- // when a newly-reserved Run encounters an older live pane.
110
- let launchPromptAcknowledgementRequired = request.adapterId === "claude"
111
- && launchCarriesExactRunPrompt;
112
100
  if (reservation.status === "existing") {
113
101
  if (!reservation.launchId.startsWith(generationPrefix)) {
114
102
  this.#requireCleanup(request.owner);
@@ -167,12 +155,6 @@ export class RuntimeLaunchCoordinator {
167
155
  throw new Error(`Runtime launch reservation belongs to another Run whose host is still running: ${reservation.runId}.`);
168
156
  }
169
157
  reusedConfirmedRunningHost = true;
170
- // A fresh Codex generation carries the prompt in its launch argv. A
171
- // managed Claude generation carries it through stream-json stdin for
172
- // both new and native-resume modes. Only recovery of the exact same
173
- // reserved Run may bridge a lost in-memory launch acknowledgement.
174
- launchPromptAcknowledgementRequired = sameRunReservation
175
- && launchCarriesExactRunPrompt;
176
158
  runtimeIsolation = this.#preflightRuntimeIsolation(request, reservation.launchId, reservation.launchId.slice(generationPrefix.length), true);
177
159
  assertLaunchCurrent();
178
160
  }
@@ -229,11 +211,8 @@ export class RuntimeLaunchCoordinator {
229
211
  : { environment: request.environment }),
230
212
  nativeSessionId: requireText(request.nativeSessionId, "Native session id")
231
213
  }, beforeHostStart === undefined ? undefined : observePreflight);
232
- binding = requireMatchingRuntimeBinding(rawBinding, request, launchId, launchPromptAcknowledgementRequired, reusedConfirmedRunningHost);
233
- if (!preflightObserved
234
- && !(binding.hostCreated === false
235
- && request.adapterId === "claude"
236
- && launchCarriesExactRunPrompt)) {
214
+ binding = requireMatchingRuntimeBinding(rawBinding, request, launchId);
215
+ if (!preflightObserved) {
237
216
  throw new Error("Runtime session host did not expose a pre-host-start launch fence.");
238
217
  }
239
218
  }
@@ -244,8 +223,7 @@ export class RuntimeLaunchCoordinator {
244
223
  // terminalize that reservation.
245
224
  throw new RuntimeLaunchError(true, launchId, error.message, error.reason);
246
225
  }
247
- if ((error instanceof LaunchSubmittedHostBusyError
248
- || error instanceof RuntimeHostContentionError)
226
+ if (error instanceof RuntimeHostContentionError
249
227
  && !reusedConfirmedRunningHost) {
250
228
  let exactCleanupAttempted = false;
251
229
  let completed = false;
@@ -266,12 +244,6 @@ export class RuntimeLaunchCoordinator {
266
244
  this.#requireCleanup(request.owner);
267
245
  throw new Error("Busy managed runtime launch reservation changed during retry release.");
268
246
  }
269
- if (error instanceof LaunchSubmittedHostBusyError) {
270
- // A terminal Run no longer owns its finite provider process. Move
271
- // that exact Role owner through the durable cleanup lane before a
272
- // later retry creates the successor generation.
273
- this.#requireCleanup(request.owner);
274
- }
275
247
  throw new RuntimeLaunchError(true, launchId, error.message, error instanceof RuntimeHostContentionError
276
248
  ? error.reason
277
249
  : "previous-process");
@@ -291,7 +263,6 @@ export class RuntimeLaunchCoordinator {
291
263
  }
292
264
  await this.#compensateStartedHost(request.owner, binding, launchId, runtimeIsolation, new Error(`Runtime host was recreated while recovering an existing generation: ${request.owner.roleName}.`));
293
265
  }
294
- let reservationConfirmation;
295
266
  try {
296
267
  assertLaunchCurrent();
297
268
  if (persistence === "immediate" && binding.nativeSessionId !== undefined) {
@@ -308,7 +279,7 @@ export class RuntimeLaunchCoordinator {
308
279
  // Deferred scheduler persistence records a known native identity while
309
280
  // retaining the reservation until exact Run delivery. Fresh Codex has
310
281
  // no identity yet and keeps it until its matching generation Hook.
311
- reservationConfirmation = this.reservations.confirmRuntimeLaunchReservation({
282
+ this.reservations.confirmRuntimeLaunchReservation({
312
283
  owner: request.owner,
313
284
  launchId
314
285
  }, assertLaunchCurrent);
@@ -317,18 +288,6 @@ export class RuntimeLaunchCoordinator {
317
288
  catch (error) {
318
289
  await this.#compensateStartedHost(request.owner, binding, launchId, runtimeIsolation, error);
319
290
  }
320
- if (launchCarriesExactRunPrompt
321
- && binding.hostCreated !== false
322
- && reservationConfirmation !== "provider-bound"
323
- && binding.initialPromptRunId !== request.runId) {
324
- // A launch-carried prompt may return before its matching lifecycle Hook,
325
- // but only an exact Run marker can bridge that asynchronous interval.
326
- // This is transport evidence, never Provider acceptance; the
327
- // reservation remains fenced until the matching Hook binds the native
328
- // Session.
329
- this.#requireCleanup(request.owner);
330
- throw new RuntimeBindingContractError(`Session host cannot acknowledge the exact launch-carried prompt: ${request.owner.roleName}.`);
331
- }
332
291
  return binding;
333
292
  }
334
293
  async #settleFailedStart(request, launchId, reusedConfirmedRunningHost, runtimeIsolation) {
@@ -461,12 +420,12 @@ function validateRuntimeLaunchPreflight(preflight, request, launchId) {
461
420
  || !effectiveLaunchSnapshotsCompatible(preflight.effective, request.effective)
462
421
  || (request.mode === "resume"
463
422
  && preflight.nativeSessionId !== request.nativeSessionId)
464
- || (preflight.initialPromptRunId !== undefined
465
- && preflight.initialPromptRunId !== request.runId)) {
423
+ || (preflight.initialTurnRunId !== undefined
424
+ && preflight.initialTurnRunId !== request.runId)) {
466
425
  throw new Error(`Session host pre-start launch fence does not match the requested runtime: ${request.owner.roleName}.`);
467
426
  }
468
427
  }
469
- function requireMatchingRuntimeBinding(raw, request, launchId, launchPromptAcknowledgementRequired, launchPromptUncertaintyAllowed) {
428
+ function requireMatchingRuntimeBinding(raw, request, launchId) {
470
429
  let binding;
471
430
  try {
472
431
  binding = createRuntimeBinding(raw);
@@ -479,9 +438,17 @@ function requireMatchingRuntimeBinding(raw, request, launchId, launchPromptAckno
479
438
  && (binding.owner.scope === "global"
480
439
  || (request.owner.scope === "task"
481
440
  && binding.owner.taskId === request.owner.taskId));
482
- if (binding.initialPromptRunId !== undefined
483
- && binding.initialPromptRunId !== request.runId) {
484
- throw new RuntimeBindingContractError(`Session host returned a launch-carried prompt for another Run: ${request.owner.roleName}.`);
441
+ if (binding.initialTurnRunId !== undefined
442
+ && binding.initialTurnRunId !== request.runId) {
443
+ throw new RuntimeBindingContractError(`Session host returned an initial structured Turn for another Run: ${request.owner.roleName}.`);
444
+ }
445
+ if (binding.initialTurnDeliveryUnknownRunId !== undefined
446
+ && binding.initialTurnDeliveryUnknownRunId !== request.runId) {
447
+ throw new RuntimeBindingContractError(`Session host returned a delivery-unknown initial structured Turn for another Run: ${request.owner.roleName}.`);
448
+ }
449
+ if (binding.initialTurnRejectedRunId !== undefined
450
+ && binding.initialTurnRejectedRunId !== request.runId) {
451
+ throw new RuntimeBindingContractError(`Session host returned a rejected initial structured Turn for another Run: ${request.owner.roleName}.`);
485
452
  }
486
453
  if (binding.launchId !== launchId
487
454
  || !ownerMatches
@@ -491,35 +458,8 @@ function requireMatchingRuntimeBinding(raw, request, launchId, launchPromptAckno
491
458
  && binding.nativeSessionId !== request.nativeSessionId)) {
492
459
  throw new RuntimeBindingContractError(`Session host returned a binding that does not match the requested runtime: ${request.owner.roleName}.`);
493
460
  }
494
- if (launchPromptAcknowledgementRequired
495
- && binding.initialPromptRunId !== request.runId
496
- && !(launchPromptUncertaintyAllowed && binding.hostCreated === false)) {
497
- throw binding.hostCreated === false
498
- ? new LaunchSubmittedHostBusyError(`An earlier managed runtime is still exiting: ${request.owner.roleName}.`)
499
- : new RuntimeBindingContractError(`Session host cannot acknowledge the exact launch-carried prompt: ${request.owner.roleName}.`);
500
- }
501
- if (launchPromptAcknowledgementRequired
502
- && launchPromptUncertaintyAllowed
503
- && binding.hostCreated === false
504
- && request.runId !== undefined
505
- && binding.initialPromptRunId !== request.runId) {
506
- // A Controller restart can lose only the in-memory fact that a still-
507
- // running generation carried this Run at process launch. Keep the
508
- // uncertainty explicitly tied to the exact reservation/Run; the matching
509
- // Provider Hook remains the sole acceptance authority.
510
- return {
511
- ...binding,
512
- launchPromptUncertainRunId: request.runId
513
- };
514
- }
515
461
  return binding;
516
462
  }
517
- function carriesExactRunPrompt(request) {
518
- if (request.owner.scope !== "task" || request.runId === undefined)
519
- return false;
520
- return request.adapterId === "claude"
521
- || (request.adapterId === "codex" && request.mode === "new");
522
- }
523
463
  function defaultLaunchFingerprint(request) {
524
464
  return createHash("sha256").update(JSON.stringify([
525
465
  request.owner,