@zq-silk/yui 0.2.0 → 0.4.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 (208) hide show
  1. package/ARCHITECTURE.md +603 -133
  2. package/README.md +806 -31
  3. package/dist/agent/agent.js +2 -1
  4. package/dist/agent/argumentPolicy.js +3 -1
  5. package/dist/agent/launchEnvironment.js +106 -0
  6. package/dist/agent/managedRuntimeEnvironment.js +34 -0
  7. package/dist/brief/taskBrief.js +11 -1
  8. package/dist/cli/agentConfigurationPicker.js +287 -0
  9. package/dist/cli/commandCatalog.js +488 -60
  10. package/dist/cli/completion.js +146 -22
  11. package/dist/cli/helpRenderer.js +3 -1
  12. package/dist/cli/interactionCandidates.js +53 -15
  13. package/dist/cli/interactionPolicy.js +267 -30
  14. package/dist/cli/interactiveSelection.js +6 -2
  15. package/dist/cli/invocationRouter.js +5 -1
  16. package/dist/cli/operatorWizard.js +87 -0
  17. package/dist/cli/roleOptionCatalog.js +1 -0
  18. package/dist/cli/roleWizard.js +185 -21
  19. package/dist/cli/updateCommand.js +62 -19
  20. package/dist/cli/updateOrchestrator.js +539 -0
  21. package/dist/cli/updatePorts.js +1119 -0
  22. package/dist/cli/upgradeCommand.js +112 -0
  23. package/dist/cli.js +1420 -86
  24. package/dist/commands/agentCommands.js +146 -3
  25. package/dist/commands/configCommands.js +126 -0
  26. package/dist/commands/controllerCommands.js +365 -0
  27. package/dist/commands/globalRoleCommands.js +168 -126
  28. package/dist/commands/jobCommands.js +18 -8
  29. package/dist/commands/operatorCommands.js +159 -9
  30. package/dist/commands/profileCommands.js +203 -0
  31. package/dist/commands/projectCommands.js +650 -0
  32. package/dist/commands/roleConfiguration.js +85 -24
  33. package/dist/commands/roleRuntimeGuard.js +12 -0
  34. package/dist/commands/roleSkillValidation.js +47 -0
  35. package/dist/commands/taskActor.js +127 -0
  36. package/dist/commands/taskCommands.js +4201 -313
  37. package/dist/commands/taskCompletionGate.js +131 -0
  38. package/dist/commands/taskContextCommand.js +244 -30
  39. package/dist/commands/taskInputCommands.js +177 -59
  40. package/dist/commands/taskIntegrationCommands.js +303 -0
  41. package/dist/commands/taskOverviewCommand.js +363 -0
  42. package/dist/commands/taskRoleRuntimeStatus.js +125 -19
  43. package/dist/commands/textInput.js +15 -0
  44. package/dist/completion/completionInstaller.js +26 -22
  45. package/dist/config/yuiConfig.js +4 -3
  46. package/dist/context/dispatchContext.js +90 -38
  47. package/dist/context/roleSessionContext.js +119 -0
  48. package/dist/controller/claudeLifecycleHook.js +203 -0
  49. package/dist/controller/clientRuntime.js +408 -56
  50. package/dist/controller/codexLifecycleHook.js +108 -0
  51. package/dist/controller/controller.js +1089 -32
  52. package/dist/controller/domainIdentity.js +505 -0
  53. package/dist/controller/ephemeralResourceReaper.js +131 -0
  54. package/dist/controller/fileSchedulerStoreAdapter.js +2153 -103
  55. package/dist/controller/providerHookRunFence.js +127 -0
  56. package/dist/controller/resourceCleanupLinux.js +286 -0
  57. package/dist/controller/resourceInventory.js +531 -0
  58. package/dist/controller/resourceInventoryLinux.js +610 -0
  59. package/dist/controller/runtime.js +629 -10
  60. package/dist/controller/runtimeEventInbox.js +564 -0
  61. package/dist/controller/runtimeEventProcessor.js +248 -0
  62. package/dist/controller/runtimeLaunchCoordinator.js +477 -0
  63. package/dist/controller/sessionNotify.js +121 -78
  64. package/dist/coordination/deadlineScheduler.js +15 -0
  65. package/dist/coordination/mailboxScheduler.js +108 -0
  66. package/dist/coordination/workMailbox.js +329 -0
  67. package/dist/coordination/workMailboxQueue.js +86 -0
  68. package/dist/core/controllerClient.js +19 -5
  69. package/dist/core/controllerEndpoint.js +37 -0
  70. package/dist/core/controllerServer.js +218 -10
  71. package/dist/core/protocol.js +6 -2
  72. package/dist/decision/decision.js +2 -1
  73. package/dist/doctor/doctor.js +681 -32
  74. package/dist/domain/validation.js +53 -0
  75. package/dist/errors/cliError.js +5 -3
  76. package/dist/event/taskEvent.js +7 -3
  77. package/dist/execution/codexThreadNaming.js +160 -0
  78. package/dist/execution/executionGroup.js +579 -0
  79. package/dist/executor/agentAdapter.js +255 -40
  80. package/dist/executor/agentConfigurationCatalog.js +326 -0
  81. package/dist/executor/agentConfigurationProbe.js +506 -0
  82. package/dist/executor/agentExecutor.js +625 -10
  83. package/dist/executor/codexConfigConflict.js +290 -0
  84. package/dist/executor/effectiveLaunch.js +340 -0
  85. package/dist/executor/executorRegistry.js +238 -36
  86. package/dist/executor/fileRoleLaunchPlanner.js +550 -40
  87. package/dist/executor/turnCompletion.js +126 -0
  88. package/dist/input/inputRequest.js +30 -9
  89. package/dist/integration/changeSet.js +36 -0
  90. package/dist/integration/checkResult.js +24 -0
  91. package/dist/integration/gitIntegrationService.js +695 -0
  92. package/dist/integration/integrationAttempt.js +142 -0
  93. package/dist/interaction/operatorPresentation.js +96 -0
  94. package/dist/lifecycle/canonicalLifecycleEvent.js +342 -0
  95. package/dist/lifecycle/exactRunTerminalization.js +572 -0
  96. package/dist/lifecycle/providerLifecycleMapping.js +190 -0
  97. package/dist/lifecycle/taskRoleSessionReset.js +124 -0
  98. package/dist/message/message.js +23 -7
  99. package/dist/milestone/milestone.js +2 -1
  100. package/dist/operator/operatorSessionHistory.js +124 -0
  101. package/dist/output/agentConfigurationPresentation.js +43 -0
  102. package/dist/output/rolePresentation.js +34 -10
  103. package/dist/output/terminal.js +8 -0
  104. package/dist/output/timePresentation.js +55 -0
  105. package/dist/profile/agentProfile.js +128 -0
  106. package/dist/repository/gitWorkspace.js +578 -24
  107. package/dist/repository/project.js +213 -0
  108. package/dist/repository/taskWorkspaceCoordinator.js +392 -0
  109. package/dist/repository/taskWorkspacePreparer.js +1688 -191
  110. package/dist/review/reviewConfig.js +11 -0
  111. package/dist/review/reviewRound.js +399 -0
  112. package/dist/review/taskFinalReviewContract.js +90 -0
  113. package/dist/role/role.js +124 -23
  114. package/dist/run/agentRun.js +155 -12
  115. package/dist/run/runIdentity.js +82 -0
  116. package/dist/runtime/exactControlPlane.js +472 -0
  117. package/dist/runtime/index.js +8 -0
  118. package/dist/runtime/lifecycleReservation.js +38 -0
  119. package/dist/runtime/ports.js +11 -0
  120. package/dist/runtime/preallocatedNativeSession.js +13 -0
  121. package/dist/runtime/promptEnvelope.js +30 -0
  122. package/dist/runtime/runtimeBinding.js +31 -0
  123. package/dist/runtime/runtimeOwner.js +14 -0
  124. package/dist/runtime/sessionLaunchRequest.js +62 -0
  125. package/dist/runtime/sessionTitle.js +54 -0
  126. package/dist/runtime/taskRuntimeIsolation.js +643 -0
  127. package/dist/runtime/tmuxAdapters.js +315 -0
  128. package/dist/runtime/turnCompletion.js +3 -0
  129. package/dist/runtime/validation.js +23 -0
  130. package/dist/scheduler/activeRoleRunDelivery.js +342 -32
  131. package/dist/scheduler/activeTaskProgress.js +63 -0
  132. package/dist/scheduler/leaderFailure.js +2 -1
  133. package/dist/scheduler/leaderWakeupProcessor.js +307 -66
  134. package/dist/scheduler/operatorInputNotificationProcessor.js +109 -46
  135. package/dist/scheduler/operatorNotification.js +44 -2
  136. package/dist/scheduler/ports.js +28 -1
  137. package/dist/scheduler/roleRunLiveness.js +131 -25
  138. package/dist/scheduler/roleRunStall.js +951 -0
  139. package/dist/scheduler/taskExecutionProjection.js +544 -0
  140. package/dist/scheduler/wakeupQueue.js +3 -0
  141. package/dist/setup/setupCommand.js +302 -52
  142. package/dist/storage/compatibleTaskStore.js +102 -0
  143. package/dist/storage/migration/baseline.js +78 -0
  144. package/dist/storage/migration/classifier.js +51 -0
  145. package/dist/storage/migration/compatibleCodec.js +53 -0
  146. package/dist/storage/migration/engine.js +147 -0
  147. package/dist/storage/migration/index.js +33 -0
  148. package/dist/storage/migration/planner.js +154 -0
  149. package/dist/storage/migration/productionRegistry.js +486 -0
  150. package/dist/storage/migration/registry.js +169 -0
  151. package/dist/storage/migration/report.js +54 -0
  152. package/dist/storage/migration/types.js +31 -0
  153. package/dist/storage/storageSchema.js +147 -123
  154. package/dist/storage/storageVersions.js +11 -0
  155. package/dist/storage/taskStore.js +1793 -197
  156. package/dist/storage/upgrade/homeClassification.js +156 -0
  157. package/dist/storage/upgrade/homeMigrationTarget.js +595 -0
  158. package/dist/storage/upgrade/offlineUpgradeInventory.js +315 -0
  159. package/dist/storage/upgrade/productionMigrationRegistry.js +6 -0
  160. package/dist/storage/upgrade/recordVersionScan.js +176 -0
  161. package/dist/storage/upgrade/recordVersions.js +159 -0
  162. package/dist/storage/upgrade/switchProgress.js +80 -0
  163. package/dist/storage/upgrade/upgradeOrchestrator.js +948 -0
  164. package/dist/storage/upgrade/upgradeReceipt.js +161 -0
  165. package/dist/storage/upgradeCoordination.js +186 -0
  166. package/dist/storage/upgradeFence.js +366 -0
  167. package/dist/task/task.js +132 -26
  168. package/dist/task/taskRecordReference.js +66 -0
  169. package/dist/tmux/commandExecutor.js +75 -2
  170. package/dist/tmux/tmuxManager.js +747 -49
  171. package/dist/version.js +23 -0
  172. package/dist/web/assets/assetManifest.js +62 -0
  173. package/dist/web/assets/client/app.js +631 -0
  174. package/dist/web/assets/client/components.js +605 -0
  175. package/dist/web/assets/client/dom.js +14 -0
  176. package/dist/web/assets/client/format.js +28 -0
  177. package/dist/web/assets/client/i18n.js +494 -0
  178. package/dist/web/assets/client/markdown.js +114 -0
  179. package/dist/web/assets/client/theme.js +32 -0
  180. package/dist/web/assets/client/view.js +458 -0
  181. package/dist/web/assets/fontData.js +12 -0
  182. package/dist/web/assets/fonts.js +12 -0
  183. package/dist/web/assets/shell.js +114 -0
  184. package/dist/web/assets/styles/cards.js +135 -0
  185. package/dist/web/assets/styles/layout.js +47 -0
  186. package/dist/web/assets/styles/markdown.js +29 -0
  187. package/dist/web/assets/styles/responsive.js +39 -0
  188. package/dist/web/assets/styles/tokens.js +101 -0
  189. package/dist/web/assets/styles/widgets.js +147 -0
  190. package/dist/web/tmuxWebTerminal.js +158 -0
  191. package/dist/web/webServer.js +463 -0
  192. package/dist/web/webSnapshot.js +148 -0
  193. package/dist/workItem/workItem.js +642 -23
  194. package/dist/workspace/gitChangeSetCapture.js +86 -0
  195. package/dist/workspace/workItemChangeSetManager.js +445 -0
  196. package/dist/worktree/managedWorkspace.js +202 -0
  197. package/docs/task-local-identity.md +62 -0
  198. package/i18n/README.zh-CN.md +406 -31
  199. package/package.json +10 -2
  200. package/skills/yui-leader/SKILL.md +601 -39
  201. package/skills/yui-operator/SKILL.md +255 -34
  202. package/skills/yui-reviewer/SKILL.md +57 -0
  203. package/skills/yui-worker/SKILL.md +214 -17
  204. package/dist/commands/repositoryCommands.js +0 -86
  205. package/dist/operator/operatorContext.js +0 -66
  206. package/dist/repository/repository.js +0 -55
  207. package/dist/scheduler/archivedTaskRuntime.js +0 -12
  208. package/dist/worktree/roleWorkspace.js +0 -62
@@ -0,0 +1,477 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
3
+ import { createRuntimeBinding, RuntimeLaunchError } from "../runtime/index.js";
4
+ import { effectiveLaunchSnapshotsCompatible, validateEffectiveLaunchSnapshot } from "../executor/effectiveLaunch.js";
5
+ class RuntimeBindingContractError extends Error {
6
+ constructor(message, options) {
7
+ super(message, options);
8
+ this.name = "RuntimeBindingContractError";
9
+ }
10
+ }
11
+ /**
12
+ * One application service owns the reservation -> host -> persistence
13
+ * protocol for both foreground enter and scheduler-driven Role launches.
14
+ */
15
+ export class RuntimeLaunchCoordinator {
16
+ reservations;
17
+ host;
18
+ #createGenerationId;
19
+ #now;
20
+ #assertCurrent;
21
+ #launchFingerprint;
22
+ #onCleanupRequired;
23
+ #runtimeIsolation;
24
+ constructor(reservations, host, options = {}) {
25
+ this.reservations = reservations;
26
+ this.host = host;
27
+ this.#createGenerationId = options.createGenerationId ?? randomUUID;
28
+ this.#now = options.now ?? (() => new Date());
29
+ this.#assertCurrent = options.assertCurrent;
30
+ this.#launchFingerprint = options.launchFingerprint
31
+ ?? defaultLaunchFingerprint;
32
+ this.#onCleanupRequired = options.onCleanupRequired;
33
+ this.#runtimeIsolation = options.runtimeIsolation;
34
+ }
35
+ async prepare(request, persistence, assertCurrent, beforeHostStart) {
36
+ const ownerKey = request.owner.scope === "task"
37
+ ? `task\0${request.owner.taskId}\0${request.owner.roleName}`
38
+ : `global\0${request.owner.roleName}`;
39
+ const previous = this.#launchTails.get(ownerKey) ?? Promise.resolve();
40
+ let release;
41
+ const current = new Promise((resolve) => { release = resolve; });
42
+ const tail = previous.then(() => current);
43
+ this.#launchTails.set(ownerKey, tail);
44
+ await previous;
45
+ try {
46
+ return await this.#prepareUnlocked(request, persistence, assertCurrent, beforeHostStart);
47
+ }
48
+ finally {
49
+ release();
50
+ if (this.#launchTails.get(ownerKey) === tail) {
51
+ this.#launchTails.delete(ownerKey);
52
+ }
53
+ }
54
+ }
55
+ #launchTails = new Map();
56
+ async #prepareUnlocked(request, persistence, assertCurrent, beforeHostStart) {
57
+ const effective = validateEffectiveLaunchSnapshot(request.effective);
58
+ if (effective.agentId !== request.agentId
59
+ || effective.adapterId !== request.adapterId
60
+ || effective.workspace.root !== request.workspace) {
61
+ throw new TypeError("Runtime launch request does not match its effective snapshot.");
62
+ }
63
+ if (this.#runtimeIsolation !== undefined && request.owner.scope === "task") {
64
+ if (request.managedWorkspace === undefined) {
65
+ throw new Error("An authoritative ManagedWorkspace owner is required for a Task runtime launch.");
66
+ }
67
+ if (request.managedWorkspace.owner.taskId !== request.owner.taskId
68
+ || request.managedWorkspace.root !== request.workspace) {
69
+ throw new Error("Task runtime launch does not match its authoritative ManagedWorkspace.");
70
+ }
71
+ }
72
+ if (request.owner.scope === "global" && request.managedWorkspace !== undefined) {
73
+ throw new Error("A global runtime cannot use a Task ManagedWorkspace.");
74
+ }
75
+ const expectedFingerprint = requireText(this.#launchFingerprint(request), "Launch fingerprint");
76
+ const assertLaunchCurrent = () => {
77
+ this.#assertCurrent?.(request);
78
+ assertCurrent?.();
79
+ if (this.#launchFingerprint(request) !== expectedFingerprint) {
80
+ throw new Error(`Role or Agent launch state changed: ${request.owner.roleName}.`);
81
+ }
82
+ };
83
+ const generationPrefix = `runtime-${expectedFingerprint}:generation:`;
84
+ const proposedGenerationId = requireText(this.#createGenerationId(), "Launch generation id");
85
+ const proposedLaunchId = `${generationPrefix}${proposedGenerationId}`;
86
+ let runtimeIsolation = this.#preflightRuntimeIsolation(request, proposedLaunchId, proposedGenerationId, false);
87
+ let reservation = this.reservations.reserveRuntimeLaunch({
88
+ owner: request.owner,
89
+ launchId: proposedLaunchId,
90
+ ...(request.runId === undefined ? {} : { runId: request.runId })
91
+ }, assertLaunchCurrent, this.#now());
92
+ let reusedConfirmedRunningHost = false;
93
+ let launchPromptAcknowledgementRequired = false;
94
+ if (reservation.status === "existing") {
95
+ if (!reservation.launchId.startsWith(generationPrefix)) {
96
+ this.#requireCleanup(request.owner);
97
+ throw new Error(`Runtime launch reservation belongs to stale Role or Agent state: ${request.owner.roleName}.`);
98
+ }
99
+ const sameRunReservation = request.runId !== undefined
100
+ && reservation.runId === request.runId;
101
+ const differentRunReservation = request.runId !== undefined
102
+ && reservation.runId !== undefined
103
+ && reservation.runId !== request.runId;
104
+ const inspection = await this.host.inspectOwner(request.owner);
105
+ if (inspection.state === "unavailable" || inspection.state === "starting") {
106
+ if (sameRunReservation) {
107
+ throw new RuntimeLaunchError(true, reservation.launchId, `Runtime is temporarily ${inspection.state}: ${request.runId}/${reservation.launchId}.`);
108
+ }
109
+ throw new Error(`Runtime launch reservation cannot yet be verified: ${request.owner.roleName}.`);
110
+ }
111
+ if (inspection.state === "stopped") {
112
+ if (sameRunReservation) {
113
+ throw new RuntimeLaunchError(false, reservation.launchId, `Exact Run launch generation stopped before delivery: ${request.runId}/${reservation.launchId}.`);
114
+ }
115
+ const taskOwner = request.owner.scope === "task"
116
+ ? request.owner
117
+ : undefined;
118
+ let exactCleanupAttempted = false;
119
+ let completed = false;
120
+ try {
121
+ completed = this.reservations.completeRuntimeLaunchReservation(request.owner, reservation.launchId, differentRunReservation ? reservation.runId : undefined, taskOwner !== undefined && this.#runtimeIsolation !== undefined
122
+ ? () => {
123
+ exactCleanupAttempted = true;
124
+ this.#cleanupTaskLaunch(taskOwner, reservation.launchId);
125
+ }
126
+ : undefined);
127
+ }
128
+ catch (error) {
129
+ if (exactCleanupAttempted)
130
+ this.#requireCleanup(request.owner);
131
+ throw error;
132
+ }
133
+ if (!completed) {
134
+ if (exactCleanupAttempted)
135
+ this.#requireCleanup(request.owner);
136
+ throw new Error("Runtime launch reservation changed during recovery.");
137
+ }
138
+ reservation = this.reservations.reserveRuntimeLaunch({
139
+ owner: request.owner,
140
+ launchId: proposedLaunchId,
141
+ ...(request.runId === undefined ? {} : { runId: request.runId })
142
+ }, assertLaunchCurrent, this.#now());
143
+ if (reservation.status !== "reserved") {
144
+ throw new Error("Runtime launch reservation could not be renewed.");
145
+ }
146
+ }
147
+ else {
148
+ if (differentRunReservation) {
149
+ throw new Error(`Runtime launch reservation belongs to another Run whose host is still running: ${reservation.runId}.`);
150
+ }
151
+ reusedConfirmedRunningHost = true;
152
+ // Only a fresh Codex generation can carry the exact Run prompt in its
153
+ // launch argv. A resume request targets an existing native Session and
154
+ // must use the normal receipt-backed prompt push, even when the
155
+ // Controller is recovering the same launch reservation.
156
+ launchPromptAcknowledgementRequired = sameRunReservation
157
+ && request.mode === "new"
158
+ && request.adapterId === "codex";
159
+ runtimeIsolation = this.#preflightRuntimeIsolation(request, reservation.launchId, reservation.launchId.slice(generationPrefix.length), true);
160
+ assertLaunchCurrent();
161
+ }
162
+ }
163
+ const launchId = reservation.launchId;
164
+ let preflightObserved = beforeHostStart === undefined;
165
+ const observePreflight = (preflight) => {
166
+ if (preflightObserved) {
167
+ throw new Error("Runtime host reported its pre-start launch fence more than once.");
168
+ }
169
+ validateRuntimeLaunchPreflight(preflight, request, launchId);
170
+ preflightObserved = true;
171
+ beforeHostStart?.(preflight);
172
+ };
173
+ let binding;
174
+ try {
175
+ if (!reusedConfirmedRunningHost && runtimeIsolation !== undefined) {
176
+ // Activation is the first runtime-resource side effect and can occur
177
+ // only after the centralized read-only preflight and durable launch
178
+ // reservation have both succeeded.
179
+ this.#runtimeIsolation.activate(runtimeIsolation);
180
+ }
181
+ const rawBinding = request.mode === "new"
182
+ ? await this.host.start({
183
+ mode: "new",
184
+ launchId,
185
+ owner: request.owner,
186
+ agentId: request.agentId,
187
+ adapterId: request.adapterId,
188
+ effective: request.effective,
189
+ workspace: request.workspace,
190
+ ...(runtimeIsolation === undefined
191
+ ? {}
192
+ : { runtimeIsolation: runtimeIsolation.descriptor }),
193
+ ...(request.runId === undefined ? {} : { runId: request.runId }),
194
+ ...(request.environment === undefined
195
+ ? {}
196
+ : { environment: request.environment })
197
+ }, beforeHostStart === undefined ? undefined : observePreflight)
198
+ : await this.host.resume({
199
+ mode: "resume",
200
+ launchId,
201
+ owner: request.owner,
202
+ agentId: request.agentId,
203
+ adapterId: request.adapterId,
204
+ effective: request.effective,
205
+ workspace: request.workspace,
206
+ ...(runtimeIsolation === undefined
207
+ ? {}
208
+ : { runtimeIsolation: runtimeIsolation.descriptor }),
209
+ ...(request.runId === undefined ? {} : { runId: request.runId }),
210
+ ...(request.environment === undefined
211
+ ? {}
212
+ : { environment: request.environment }),
213
+ nativeSessionId: requireText(request.nativeSessionId, "Native session id")
214
+ }, beforeHostStart === undefined ? undefined : observePreflight);
215
+ if (!preflightObserved) {
216
+ throw new Error("Runtime session host did not expose a pre-host-start launch fence.");
217
+ }
218
+ binding = requireMatchingRuntimeBinding(rawBinding, request, launchId, launchPromptAcknowledgementRequired, reusedConfirmedRunningHost);
219
+ }
220
+ catch (error) {
221
+ if (error instanceof RuntimeBindingContractError) {
222
+ // Never pass an untrusted hostRef to stop(). Reconcile the requested
223
+ // owner through the durable, owner-addressed cleanup lane instead.
224
+ this.#requireCleanup(request.owner);
225
+ throw error;
226
+ }
227
+ await this.#settleFailedStart(request, launchId, reusedConfirmedRunningHost, runtimeIsolation);
228
+ throw error;
229
+ }
230
+ if (reusedConfirmedRunningHost && binding.hostCreated === true) {
231
+ if (request.runId !== undefined && reservation.runId === request.runId) {
232
+ throw new RuntimeLaunchError(false, launchId, `Exact Run launch generation was unexpectedly recreated: ${request.runId}/${launchId}.`);
233
+ }
234
+ await this.#compensateStartedHost(request.owner, binding, launchId, runtimeIsolation, new Error(`Runtime host was recreated while recovering an existing generation: ${request.owner.roleName}.`));
235
+ }
236
+ let reservationConfirmation;
237
+ try {
238
+ assertLaunchCurrent();
239
+ if (persistence === "immediate" && binding.nativeSessionId !== undefined) {
240
+ this.reservations.recordReservedRuntimeNativeSession({
241
+ owner: request.owner,
242
+ launchId,
243
+ agentId: request.agentId,
244
+ adapterId: request.adapterId,
245
+ nativeSessionId: binding.nativeSessionId,
246
+ effective: request.effective
247
+ }, assertLaunchCurrent, this.#now());
248
+ }
249
+ else {
250
+ // Deferred scheduler persistence records a known native identity while
251
+ // retaining the reservation until exact Run delivery. Fresh Codex has
252
+ // no identity yet and keeps it until its matching generation Hook.
253
+ reservationConfirmation = this.reservations.confirmRuntimeLaunchReservation({
254
+ owner: request.owner,
255
+ launchId
256
+ }, assertLaunchCurrent);
257
+ }
258
+ }
259
+ catch (error) {
260
+ await this.#compensateStartedHost(request.owner, binding, launchId, runtimeIsolation, error);
261
+ }
262
+ if (request.owner.scope === "task"
263
+ && request.adapterId === "codex"
264
+ && request.runId !== undefined
265
+ && binding.hostCreated !== false
266
+ && reservationConfirmation !== "provider-bound"
267
+ && binding.initialPromptRunId !== request.runId) {
268
+ // Fresh Codex has no pre-input Provider event. It may return before its
269
+ // matching lifecycle Hook, but only an exact launch-carried Run marker
270
+ // can bridge that asynchronous interval. This is transport evidence,
271
+ // never Provider acceptance; the reservation remains fenced until the
272
+ // matching Hook binds the native Session.
273
+ this.#requireCleanup(request.owner);
274
+ throw new RuntimeBindingContractError(`Session host cannot acknowledge the exact launch-carried prompt: ${request.owner.roleName}.`);
275
+ }
276
+ return binding;
277
+ }
278
+ async #settleFailedStart(request, launchId, reusedConfirmedRunningHost, runtimeIsolation) {
279
+ let inspection;
280
+ try {
281
+ inspection = await this.host.inspectOwner(request.owner);
282
+ }
283
+ catch {
284
+ // A rebind/preflight failure did not create the already-confirmed host.
285
+ // Preserve its generation when the follow-up probe is merely unknown.
286
+ if (!reusedConfirmedRunningHost)
287
+ this.#requireCleanup(request.owner);
288
+ return;
289
+ }
290
+ if (inspection.state === "stopped") {
291
+ if (runtimeIsolation !== undefined) {
292
+ try {
293
+ this.#runtimeIsolation.cleanup(runtimeIsolation, "failure");
294
+ }
295
+ catch (cleanupError) {
296
+ this.#requireCleanup(request.owner);
297
+ throw new Error(`Task runtime launch failed and exact resource cleanup also failed: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`, { cause: cleanupError });
298
+ }
299
+ }
300
+ if (!this.#settleConfirmedStopped(request, launchId)) {
301
+ this.#requireCleanup(request.owner);
302
+ }
303
+ return;
304
+ }
305
+ if (reusedConfirmedRunningHost)
306
+ return;
307
+ this.#requireCleanup(request.owner);
308
+ }
309
+ async #compensateStartedHost(owner, binding, launchId, runtimeIsolation, cause) {
310
+ try {
311
+ await this.host.stop(binding);
312
+ }
313
+ catch (stopError) {
314
+ this.#requireCleanup(owner);
315
+ throw new Error(`Runtime launch state changed. Runtime cleanup also failed: ${stopError instanceof Error ? stopError.message : String(stopError)}`, { cause: stopError });
316
+ }
317
+ let stopped = false;
318
+ try {
319
+ stopped = (await this.host.inspectOwner(owner)).state === "stopped";
320
+ }
321
+ catch {
322
+ // Durable owner cleanup below is the only safe fallback.
323
+ }
324
+ if (stopped && runtimeIsolation !== undefined) {
325
+ try {
326
+ this.#runtimeIsolation.cleanup(runtimeIsolation, "failure");
327
+ }
328
+ catch (cleanupError) {
329
+ this.#requireCleanup(owner);
330
+ throw new Error(`Runtime host stopped but exact Task resource cleanup failed: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`, { cause: cleanupError });
331
+ }
332
+ }
333
+ if (!stopped
334
+ || !this.reservations.settleStoppedRuntimeLaunch({
335
+ owner,
336
+ launchId,
337
+ agentId: binding.agentId,
338
+ adapterId: binding.adapterId,
339
+ ...(binding.nativeSessionId === undefined
340
+ ? {}
341
+ : { nativeSessionId: binding.nativeSessionId })
342
+ }, this.#now())) {
343
+ this.#requireCleanup(owner);
344
+ }
345
+ throw cause;
346
+ }
347
+ #preflightRuntimeIsolation(request, launchId, generationId, allowExactActive) {
348
+ if (this.#runtimeIsolation === undefined || request.owner.scope !== "task") {
349
+ return undefined;
350
+ }
351
+ return this.#runtimeIsolation.preflight({
352
+ workspace: request.managedWorkspace,
353
+ ...(request.runId === undefined ? {} : { runId: request.runId }),
354
+ launchId,
355
+ generationId: requireText(generationId, "Launch generation id"),
356
+ ...(request.runtimePolicy === undefined ? {} : { policy: request.runtimePolicy }),
357
+ ...(allowExactActive ? { allowExactActive: true } : {})
358
+ });
359
+ }
360
+ #cleanupTaskLaunch(owner, launchId) {
361
+ const cleanupTaskLaunch = this.#runtimeIsolation?.cleanupTaskLaunch;
362
+ if (cleanupTaskLaunch === undefined) {
363
+ throw new Error("Exact Task runtime launch cleanup is unavailable.");
364
+ }
365
+ cleanupTaskLaunch.call(this.#runtimeIsolation, {
366
+ taskId: owner.taskId,
367
+ launchId,
368
+ reason: "interruption"
369
+ });
370
+ }
371
+ #settleConfirmedStopped(request, launchId) {
372
+ return this.reservations.settleStoppedRuntimeLaunch({
373
+ owner: request.owner,
374
+ launchId,
375
+ agentId: request.agentId,
376
+ adapterId: request.adapterId,
377
+ ...(request.nativeSessionId === undefined
378
+ ? {}
379
+ : { nativeSessionId: request.nativeSessionId })
380
+ }, this.#now());
381
+ }
382
+ #requireCleanup(owner) {
383
+ const target = this.reservations.enqueueRuntimeCleanup(owner, this.#now())
384
+ ?? runtimeLifecycleTarget(owner);
385
+ try {
386
+ this.#onCleanupRequired?.(target);
387
+ }
388
+ catch {
389
+ // The durable cleanup lane is authoritative.
390
+ }
391
+ }
392
+ }
393
+ function validateRuntimeLaunchPreflight(preflight, request, launchId) {
394
+ const ownerMatches = preflight.owner.scope === request.owner.scope
395
+ && preflight.owner.roleName === request.owner.roleName
396
+ && (preflight.owner.scope === "global"
397
+ || (request.owner.scope === "task"
398
+ && preflight.owner.taskId === request.owner.taskId));
399
+ if (preflight.launchId !== launchId
400
+ || !ownerMatches
401
+ || preflight.runId !== request.runId
402
+ || preflight.agentId !== request.agentId
403
+ || preflight.adapterId !== request.adapterId
404
+ || !effectiveLaunchSnapshotsCompatible(preflight.effective, request.effective)
405
+ || (request.mode === "resume"
406
+ && preflight.nativeSessionId !== request.nativeSessionId)
407
+ || (preflight.initialPromptRunId !== undefined
408
+ && preflight.initialPromptRunId !== request.runId)) {
409
+ throw new Error(`Session host pre-start launch fence does not match the requested runtime: ${request.owner.roleName}.`);
410
+ }
411
+ }
412
+ function requireMatchingRuntimeBinding(raw, request, launchId, launchPromptAcknowledgementRequired, launchPromptUncertaintyAllowed) {
413
+ let binding;
414
+ try {
415
+ binding = createRuntimeBinding(raw);
416
+ }
417
+ catch (error) {
418
+ throw new RuntimeBindingContractError("Session host returned an invalid runtime binding.", { cause: error });
419
+ }
420
+ const ownerMatches = binding.owner.scope === request.owner.scope
421
+ && binding.owner.roleName === request.owner.roleName
422
+ && (binding.owner.scope === "global"
423
+ || (request.owner.scope === "task"
424
+ && binding.owner.taskId === request.owner.taskId));
425
+ if (binding.initialPromptRunId !== undefined
426
+ && binding.initialPromptRunId !== request.runId) {
427
+ throw new RuntimeBindingContractError(`Session host returned a launch-carried prompt for another Run: ${request.owner.roleName}.`);
428
+ }
429
+ if (launchPromptAcknowledgementRequired
430
+ && binding.initialPromptRunId !== request.runId
431
+ && !(launchPromptUncertaintyAllowed && binding.hostCreated === false)) {
432
+ throw new RuntimeBindingContractError(`Session host cannot acknowledge the exact launch-carried prompt: ${request.owner.roleName}.`);
433
+ }
434
+ if (binding.launchId !== launchId
435
+ || !ownerMatches
436
+ || binding.agentId !== request.agentId
437
+ || binding.adapterId !== request.adapterId
438
+ || (request.mode === "resume"
439
+ && binding.nativeSessionId !== request.nativeSessionId)) {
440
+ throw new RuntimeBindingContractError(`Session host returned a binding that does not match the requested runtime: ${request.owner.roleName}.`);
441
+ }
442
+ if (launchPromptAcknowledgementRequired
443
+ && launchPromptUncertaintyAllowed
444
+ && binding.hostCreated === false
445
+ && request.runId !== undefined
446
+ && binding.initialPromptRunId !== request.runId) {
447
+ // A Controller restart can lose only the in-memory fact that a still-
448
+ // running Codex generation carried this Run in its launch argv. Keep the
449
+ // uncertainty explicitly tied to the exact reservation/Run; the matching
450
+ // Provider Hook remains the sole acceptance authority.
451
+ return {
452
+ ...binding,
453
+ launchPromptUncertainRunId: request.runId
454
+ };
455
+ }
456
+ return binding;
457
+ }
458
+ function defaultLaunchFingerprint(request) {
459
+ return createHash("sha256").update(JSON.stringify([
460
+ request.owner,
461
+ request.agentId,
462
+ request.adapterId,
463
+ request.effective,
464
+ request.workspace,
465
+ request.managedWorkspace,
466
+ request.runtimePolicy
467
+ ])).digest("hex");
468
+ }
469
+ function requireText(value, label) {
470
+ if (typeof value !== "string"
471
+ || value.length === 0
472
+ || value.trim() !== value
473
+ || value.includes("\0")) {
474
+ throw new Error(`${label} is invalid.`);
475
+ }
476
+ return value;
477
+ }