@zq-silk/yui 0.14.1 → 0.15.0

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 (185) hide show
  1. package/ARCHITECTURE.md +91 -47
  2. package/README.md +275 -145
  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/updateCommand.js +17 -9
  10. package/dist/cli/updateOrchestrator.js +87 -19
  11. package/dist/cli/updatePorts.js +79 -13
  12. package/dist/cli/upgradeCommand.js +105 -23
  13. package/dist/cli.js +268 -231
  14. package/dist/commands/agentCommands.js +13 -0
  15. package/dist/commands/configCommands.js +7 -19
  16. package/dist/commands/configOverview.js +3 -2
  17. package/dist/commands/controllerCommands.js +1 -1
  18. package/dist/commands/durableJobCommands.js +1 -1
  19. package/dist/commands/executionAuditCommands.js +4 -9
  20. package/dist/commands/globalRoleCommands.js +17 -0
  21. package/dist/commands/profileCommands.js +200 -30
  22. package/dist/commands/sessionCommands.js +1 -1
  23. package/dist/commands/taskActor.js +29 -127
  24. package/dist/commands/taskChangeSetCommands.js +1 -3
  25. package/dist/commands/taskCommands.js +1771 -2107
  26. package/dist/commands/taskCompletionGate.js +27 -103
  27. package/dist/commands/taskContextCommand.js +47 -80
  28. package/dist/commands/taskExecutionCommands.js +9 -27
  29. package/dist/commands/taskInputCommands.js +10 -10
  30. package/dist/commands/taskIntegrationCommands.js +69 -61
  31. package/dist/commands/taskIntegrationQueueCommands.js +1 -1
  32. package/dist/commands/taskNextActionCommand.js +8 -29
  33. package/dist/commands/taskOverviewCommand.js +25 -3
  34. package/dist/commands/taskPublicationCommands.js +151 -85
  35. package/dist/commands/taskPublicationVerifyCommand.js +181 -0
  36. package/dist/commands/taskRemoteDeliveryCommand.js +134 -0
  37. package/dist/commands/taskRoleRuntimeStatus.js +5 -6
  38. package/dist/commands/taskUpstreamCommands.js +79 -95
  39. package/dist/config/configCatalog.js +1 -1
  40. package/dist/context/contextSnapshot.js +17 -2
  41. package/dist/context/sourceTurnContext.js +30 -0
  42. package/dist/context/turnContextPack.js +107 -48
  43. package/dist/context/turnInputContract.js +11 -4
  44. package/dist/context/wakeNotification.js +84 -22
  45. package/dist/controller/agentRuntimeObserver.js +9 -12
  46. package/dist/controller/clientRuntime.js +13 -13
  47. package/dist/controller/controller.js +11 -11
  48. package/dist/controller/fileSchedulerStoreAdapter.js +262 -219
  49. package/dist/controller/jobControl.js +11 -10
  50. package/dist/controller/resourceInventory.js +6 -6
  51. package/dist/controller/resourceInventoryLinux.js +2 -2
  52. package/dist/controller/runtime.js +18 -61
  53. package/dist/controller/runtimeEventInbox.js +57 -34
  54. package/dist/controller/runtimeEventProcessor.js +36 -96
  55. package/dist/controller/runtimeHookTurnFence.js +33 -57
  56. package/dist/controller/runtimeLaunchCoordinator.js +44 -40
  57. package/dist/controller/runtimeObservationHook.js +18 -12
  58. package/dist/controller/sessionNotify.js +43 -28
  59. package/dist/controller/sessionOwnerReconciliation.js +21 -21
  60. package/dist/controller/structuredProviderObservation.js +13 -16
  61. package/dist/coordination/workMailboxQueue.js +132 -0
  62. package/dist/core/controllerServer.js +6 -5
  63. package/dist/doctor/doctor.js +37 -14
  64. package/dist/domain/agentResultTransport.js +60 -0
  65. package/dist/execution/executionHealth.js +111 -118
  66. package/dist/execution/reviewMainTurn.js +161 -0
  67. package/dist/execution/workItemExecution.js +454 -0
  68. package/dist/execution/workItemExecutionProjection.js +309 -0
  69. package/dist/execution/workItemMainTurn.js +164 -0
  70. package/dist/executor/agentExecutor.js +19 -19
  71. package/dist/executor/executorRegistry.js +7 -28
  72. package/dist/executor/fileRoleLaunchPlanner.js +16 -82
  73. package/dist/external/githubPublicationVerifier.js +114 -0
  74. package/dist/external/gitlabPublicationVerifier.js +158 -0
  75. package/dist/external/pinnedCommandRunner.js +73 -0
  76. package/dist/integration/changeSetManifest.js +0 -3
  77. package/dist/integration/deliveryObligation.js +43 -6
  78. package/dist/integration/gitIntegrationService.js +284 -101
  79. package/dist/integration/integrationAttempt.js +64 -14
  80. package/dist/integration/integrationQueueEntry.js +5 -35
  81. package/dist/integration/integrationQueueService.js +65 -194
  82. package/dist/lifecycle/canonicalLifecycleEvent.js +16 -15
  83. package/dist/lifecycle/exactTurnTerminalization.js +100 -97
  84. package/dist/message/message.js +16 -0
  85. package/dist/observability/executionAudit.js +4 -25
  86. package/dist/observability/faultClassification.js +58 -70
  87. package/dist/observability/orchestrationMetrics.js +8 -50
  88. package/dist/observability/runtimeIdentity.js +48 -50
  89. package/dist/profile/agentProfile.js +60 -15
  90. package/dist/profile/agentProfileRuntime.js +100 -0
  91. package/dist/release/releaseWorkflowPorts.js +5 -68
  92. package/dist/release/runtimeRelease.js +9 -1
  93. package/dist/repository/executionLaneGitSnapshot.js +80 -0
  94. package/dist/repository/gitWorkspace.js +9 -67
  95. package/dist/repository/taskBaseFreshness.js +3 -1
  96. package/dist/repository/taskWorkspaceCoordinator.js +32 -4
  97. package/dist/repository/taskWorkspacePreparer.js +252 -411
  98. package/dist/resources/liveReferences.js +6 -55
  99. package/dist/resources/resourceDiscovery.js +1 -30
  100. package/dist/resources/resourceRegistrar.js +0 -23
  101. package/dist/review/deltaRecheck.js +8 -65
  102. package/dist/review/reviewAcceptance.js +22 -19
  103. package/dist/review/reviewConfig.js +0 -10
  104. package/dist/review/reviewDecision.js +2 -2
  105. package/dist/review/reviewRound.js +147 -273
  106. package/dist/review/reviewerAvailability.js +13 -23
  107. package/dist/role/role.js +3 -0
  108. package/dist/runtime/agentDriverObservation.js +1 -1
  109. package/dist/runtime/agentHost.js +51 -51
  110. package/dist/runtime/builtinAgentDrivers.js +9 -2
  111. package/dist/runtime/codexAppServerRuntime.js +23 -5
  112. package/dist/runtime/exactControlPlane.js +22 -296
  113. package/dist/runtime/firstProgressAdvisory.js +1 -1
  114. package/dist/runtime/index.js +1 -1
  115. package/dist/runtime/launchBroker.js +11 -11
  116. package/dist/runtime/lifecycleReservation.js +2 -2
  117. package/dist/runtime/managedCaller.js +111 -0
  118. package/dist/runtime/ports.js +16 -3
  119. package/dist/runtime/preallocatedNativeSession.js +2 -2
  120. package/dist/runtime/processExitObservation.js +3 -2
  121. package/dist/runtime/providerContinuationReconciliationService.js +1 -1
  122. package/dist/runtime/runtimeBinding.js +1 -1
  123. package/dist/runtime/runtimeObservation.js +33 -16
  124. package/dist/runtime/runtimeSessionCandidate.js +2 -2
  125. package/dist/runtime/runtimeStopReceipt.js +11 -11
  126. package/dist/runtime/sessionLaunchRequest.js +2 -2
  127. package/dist/runtime/sessionOwnerIdentity.js +9 -9
  128. package/dist/runtime/sessionOwnerRegistry.js +13 -13
  129. package/dist/runtime/sessionReconciliation.js +4 -4
  130. package/dist/runtime/sessionTerminationGuard.js +9 -9
  131. package/dist/runtime/sessionTokenMetrics.js +5 -7
  132. package/dist/runtime/structuredProviderHost.js +5 -5
  133. package/dist/runtime/taskRuntimeIsolation.js +24 -24
  134. package/dist/runtime/tmuxAdapters.js +32 -30
  135. package/dist/scheduler/activeRoleTurnDelivery.js +37 -15
  136. package/dist/scheduler/leaderWakeupProcessor.js +78 -55
  137. package/dist/scheduler/roleTurnLiveness.js +6 -6
  138. package/dist/scheduler/roleTurnStall.js +8 -9
  139. package/dist/scheduler/taskExecutionProjection.js +19 -41
  140. package/dist/scheduler/taskObservabilityProjection.js +75 -122
  141. package/dist/storage/currentTaskStore.js +6 -4
  142. package/dist/storage/sqliteSchema.js +135 -80
  143. package/dist/storage/sqliteStore.js +69 -52
  144. package/dist/storage/storageSchema.js +92 -230
  145. package/dist/storage/storageVersions.js +12 -13
  146. package/dist/storage/taskStore.js +8 -8
  147. package/dist/storage/upgrade/upgradeOrchestrator.js +276 -85
  148. package/dist/task/completionReadiness.js +17 -98
  149. package/dist/task/deliveryGuard.js +9 -11
  150. package/dist/task/draftPlan.js +142 -0
  151. package/dist/task/nextAction.js +179 -477
  152. package/dist/task/publicationVerification.js +1 -0
  153. package/dist/task/remoteDelivery.js +233 -0
  154. package/dist/task/task.js +108 -4
  155. package/dist/task/taskRecordReference.js +0 -1
  156. package/dist/turn/turn.js +109 -16
  157. package/dist/verification/verificationGateService.js +0 -26
  158. package/dist/version.js +3 -3
  159. package/dist/web/assets/client/components.js +99 -121
  160. package/dist/web/assets/client/i18n.js +136 -58
  161. package/dist/web/assets/client/view.js +44 -0
  162. package/dist/web/assets/styles/cards.js +3 -15
  163. package/dist/web/assets/styles/widgets.js +4 -0
  164. package/dist/web/webSnapshot.js +29 -14
  165. package/dist/workItem/dependencyGate.js +34 -0
  166. package/dist/workItem/workItem.js +99 -266
  167. package/dist/workspace/workItemChangeSetManager.js +15 -59
  168. package/docs/task-local-identity.md +9 -9
  169. package/i18n/README.zh-CN.md +189 -74
  170. package/package.json +1 -1
  171. package/skills/yui-leader/SKILL.md +272 -782
  172. package/skills/yui-operator/SKILL.md +190 -365
  173. package/skills/yui-reviewer/SKILL.md +40 -33
  174. package/skills/yui-runtime/SKILL.md +22 -0
  175. package/skills/yui-worker/SKILL.md +19 -6
  176. package/dist/execution/candidateConvergence.js +0 -623
  177. package/dist/execution/executionGroup.js +0 -854
  178. package/dist/execution/resourceBroker.js +0 -426
  179. package/dist/executor/launchPlan.js +0 -16
  180. package/dist/review/reviewFinding.js +0 -240
  181. package/dist/review/reviewFindingLedger.js +0 -550
  182. package/dist/review/reviewOutcomeClassifier.js +0 -255
  183. package/dist/scheduler/resourceQueueProjection.js +0 -71
  184. package/dist/storage/upgrade/recordVersions.js +0 -82
  185. package/dist/task/repairWave.js +0 -150
@@ -1,15 +1,11 @@
1
1
  import { createHash } from "node:crypto";
2
- import { readFileSync, realpathSync } from "node:fs";
3
- import { dirname, join, resolve } from "node:path";
2
+ import { realpathSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
4
  import { callController as defaultCallController } from "../core/controllerClient.js";
5
5
  import { inspectStorageSchema } from "../storage/storageSchema.js";
6
6
  import { yuiVersionIdentity } from "../version.js";
7
- import { hasRuntimeCleanupObligation, isRuntimeLaunchReservation, runtimeLifecycleTarget } from "./lifecycleReservation.js";
8
- import { nativeSessionIdForLaunch } from "./preallocatedNativeSession.js";
9
- import { writeTextFileAtomically } from "../storage/durableFile.js";
10
7
  export const EXACT_CONTROL_ARGUMENT = "--yui-control";
11
8
  export const YUI_CONTROL_PLANE_DESCRIPTOR = "YUI_CONTROL_PLANE_DESCRIPTOR";
12
- export const YUI_TASK_RUNTIME_DESCRIPTOR = "YUI_TASK_RUNTIME_DESCRIPTOR";
13
9
  export function createExactControlPlaneDescriptor(input) {
14
10
  const identity = validateVersionIdentity(input.identity ?? yuiVersionIdentity());
15
11
  return Object.freeze({
@@ -25,25 +21,6 @@ export function createExactControlPlaneDescriptor(input) {
25
21
  : { activeReleaseDigest: input.activeReleaseDigest })
26
22
  });
27
23
  }
28
- export function createExactTaskRuntimeDescriptor(input) {
29
- return Object.freeze({
30
- schemaVersion: 1,
31
- kind: "yui-task-runtime",
32
- controlPlaneDigest: requireDigest(input.controlPlaneDigest),
33
- taskId: requireIdentity(input.taskId, "Task id"),
34
- roleName: requireIdentity(input.roleName, "Role name"),
35
- agentId: requireIdentity(input.agentId, "Agent id"),
36
- adapterId: requireAdapter(input.adapterId),
37
- workspace: canonicalPath(input.workspace),
38
- ...(input.turnId === undefined ? {} : { turnId: requireIdentity(input.turnId, "Turn id") }),
39
- ...(input.launchId === undefined
40
- ? {}
41
- : { launchId: requireIdentity(input.launchId, "Launch id") }),
42
- ...(input.nativeSessionId === undefined
43
- ? {}
44
- : { nativeSessionId: requireIdentity(input.nativeSessionId, "Native session id") })
45
- });
46
- }
47
24
  export function serializeExactDescriptor(descriptor) {
48
25
  return JSON.stringify(descriptor);
49
26
  }
@@ -64,47 +41,9 @@ export function parseExactControlPlaneDescriptor(value) {
64
41
  : { activeReleaseDigest: record.activeReleaseDigest })
65
42
  });
66
43
  }
67
- export function parseExactTaskRuntimeDescriptor(value) {
68
- const record = parseDescriptorRecord(value);
69
- assertDescriptorKind(record, "yui-task-runtime");
70
- if (record.schemaVersion !== 1) {
71
- throw new Error("Exact Task runtime descriptor schema version is invalid.");
72
- }
73
- return createExactTaskRuntimeDescriptor({
74
- controlPlaneDigest: requireText(record.controlPlaneDigest, "Control-plane digest"),
75
- taskId: requireText(record.taskId, "Task id"),
76
- roleName: requireText(record.roleName, "Role name"),
77
- agentId: requireText(record.agentId, "Agent id"),
78
- adapterId: requireAdapter(record.adapterId),
79
- workspace: requireText(record.workspace, "Task runtime workspace"),
80
- ...(record.turnId === undefined ? {} : { turnId: requireText(record.turnId, "Turn id") }),
81
- ...(record.launchId === undefined
82
- ? {}
83
- : { launchId: requireText(record.launchId, "Launch id") }),
84
- ...(record.nativeSessionId === undefined
85
- ? {}
86
- : { nativeSessionId: requireText(record.nativeSessionId, "Native session id") })
87
- });
88
- }
89
44
  export function exactControlPlaneDigest(descriptor) {
90
45
  return createHash("sha256").update(serializeExactDescriptor(descriptor)).digest("hex");
91
46
  }
92
- /** Stable per Task/Role/Agent location; volatile Turn/launch/native fields are content. */
93
- export function exactTaskRuntimeDescriptorPath(home, descriptor) {
94
- const validated = createExactTaskRuntimeDescriptor(descriptor);
95
- const digest = createHash("sha256").update(JSON.stringify([
96
- validated.controlPlaneDigest,
97
- validated.taskId,
98
- validated.roleName,
99
- validated.agentId,
100
- validated.adapterId,
101
- validated.workspace
102
- ])).digest("hex");
103
- return join(canonicalPath(home), "runtime", "exact-task-runtime", `${digest}.json`);
104
- }
105
- export function readExactTaskRuntimeDescriptorSource(source, home) {
106
- return readTaskRuntimeSource(source, home).descriptor;
107
- }
108
47
  export function exactControlPlaneCommandPrefix(descriptor) {
109
48
  return [
110
49
  descriptor.executable,
@@ -134,7 +73,7 @@ export function extractExactControlArgument(args) {
134
73
  }
135
74
  /**
136
75
  * One read-only gate shared by every managed Task control command. It verifies
137
- * the frozen executable/CLI/Home/digest, protocol and scalar-storage identity,
76
+ * the frozen executable/CLI/Home/digest, protocol and storage identity,
138
77
  * on-disk schema, and any live Controller before command routing may construct
139
78
  * a writable store. Package version alone may advance at the same managed path
140
79
  * so an existing Session can cross an explicitly compatible in-place update.
@@ -153,15 +92,10 @@ export async function assertExactControlPlanePreflight(input, options = {}) {
153
92
  if (storage.status !== "current") {
154
93
  throw new Error(`Exact control-plane storage is not current: ${storage.status}.`);
155
94
  }
156
- if (storage.currentLayoutVersion !== descriptor.identity.storageLayoutVersion) {
157
- throw new Error("Exact control-plane storage layout does not match its frozen descriptor "
158
- + `(expected ${descriptor.identity.storageLayoutVersion}, found `
159
- + `${storage.currentLayoutVersion ?? "unknown"}).`);
160
- }
161
- if (storage.currentAggregateSchemaVersion !== descriptor.identity.aggregateSchemaVersion) {
162
- throw new Error("Exact control-plane aggregate schema does not match its frozen descriptor "
163
- + `(expected ${descriptor.identity.aggregateSchemaVersion}, found `
164
- + `${storage.currentAggregateSchemaVersion ?? "unknown"}).`);
95
+ if (storage.currentVersion !== descriptor.identity.storageVersion) {
96
+ throw new Error("Exact control-plane storage version does not match its frozen descriptor "
97
+ + `(expected ${descriptor.identity.storageVersion}, found `
98
+ + `${storage.currentVersion ?? "unknown"}).`);
165
99
  }
166
100
  // A frozen descriptor authenticates the command that created it; it no
167
101
  // longer pins the Home's deployment pointer for the lifetime of a Session.
@@ -194,15 +128,10 @@ export async function assertCompatibleControlPlanePreflight(input, options = {})
194
128
  if (storage.status !== "current") {
195
129
  throw new Error(`Managed control-plane storage is not current: ${storage.status}.`);
196
130
  }
197
- if (storage.currentLayoutVersion !== identity.storageLayoutVersion) {
198
- throw new Error("Managed control-plane storage layout is incompatible "
199
- + `(expected ${identity.storageLayoutVersion}, found `
200
- + `${storage.currentLayoutVersion ?? "unknown"}).`);
201
- }
202
- if (storage.currentAggregateSchemaVersion !== identity.aggregateSchemaVersion) {
203
- throw new Error("Managed control-plane aggregate schema is incompatible "
204
- + `(expected ${identity.aggregateSchemaVersion}, found `
205
- + `${storage.currentAggregateSchemaVersion ?? "unknown"}).`);
131
+ if (storage.currentVersion !== identity.storageVersion) {
132
+ throw new Error("Managed control-plane storage version is incompatible "
133
+ + `(expected ${identity.storageVersion}, found `
134
+ + `${storage.currentVersion ?? "unknown"}).`);
206
135
  }
207
136
  if (options.checkController !== false) {
208
137
  const call = options.callController ?? defaultCallController;
@@ -223,210 +152,31 @@ export function assertControllerStatusIdentity(status, expected = yuiVersionIden
223
152
  }
224
153
  assertControllerField(status.protocolVersion, expected.controllerProtocolVersion, "protocol");
225
154
  assertControllerField(status.version, expected.version, "version");
226
- assertControllerField(status.storageLayoutVersion, expected.storageLayoutVersion, "storage layout");
227
- assertControllerField(status.aggregateSchemaVersion, expected.aggregateSchemaVersion, "aggregate schema");
228
- }
229
- export function assertExactTaskRuntimeEnvironment(runtimeSource, environment, expectedControlDigest, expectedHome = requireText(environment.YUI_HOME, "YUI_HOME")) {
230
- const resolved = readTaskRuntimeSource(runtimeSource, expectedHome);
231
- const runtime = resolved.descriptor;
232
- if (runtime.controlPlaneDigest !== requireDigest(expectedControlDigest)) {
233
- throw new Error("Task runtime descriptor belongs to another exact control plane.");
234
- }
235
- assertEnvironment(runtime.taskId, environment.YUI_TASK_ID, "Task id");
236
- assertEnvironment(runtime.roleName, environment.YUI_ROLE, "Role name");
237
- assertEnvironment(runtime.agentId, environment.YUI_AGENT_ID, "Agent id");
238
- assertEnvironment(runtime.adapterId, environment.YUI_ADAPTER_ID, "Agent adapter id");
239
- assertSamePath(runtime.workspace, requireText(environment.YUI_WORKSPACE, "YUI_WORKSPACE"), "Task runtime workspace");
240
- // A reused native pane retains its original process environment. Volatile
241
- // identity therefore comes only from the atomically published descriptor
242
- // plus durable state, never from stale ambient Turn/launch/native variables.
243
- if (!resolved.fromFile) {
244
- assertOptionalEnvironment(runtime.turnId, environment.YUI_TURN_ID, "Turn id");
245
- assertOptionalEnvironment(runtime.launchId, environment.YUI_LAUNCH_ID, "Launch id");
246
- assertOptionalEnvironment(runtime.nativeSessionId, environment.YUI_NATIVE_SESSION_ID, "Native session id");
247
- }
248
- return runtime;
249
- }
250
- /** Fences a descriptor to the one currently active durable Task runtime. */
251
- export function assertExactTaskRuntimeState(runtime, store, options = {}) {
252
- const task = store.getTask(runtime.taskId);
253
- if (task === null
254
- || task.status !== "active"
255
- || task.executionGate.state !== "enabled") {
256
- throw new Error("Exact Task runtime Task is not current and active.");
257
- }
258
- const role = store.getRole(runtime.taskId, runtime.roleName);
259
- if (role === null || role.activeAgentId !== runtime.agentId) {
260
- throw new Error("Exact Task runtime Role or Agent is not current.");
261
- }
262
- const run = store.getActiveTurn(runtime.taskId, runtime.roleName);
263
- if (runtime.turnId === undefined) {
264
- if (run !== null)
265
- throw new Error("Exact Task runtime Turn is not current.");
266
- }
267
- else if (run === null
268
- || run.id !== runtime.turnId
269
- || run.status !== "active"
270
- || run.effective.agentId !== runtime.agentId
271
- || run.effective.adapterId !== runtime.adapterId
272
- || canonicalPath(run.effective.workspace.root) !== runtime.workspace) {
273
- throw new Error("Exact Task runtime Turn is not current.");
274
- }
275
- const sessions = store.getTaskRoleSessionSet(runtime.taskId, runtime.roleName);
276
- const session = sessions?.sessions[runtime.agentId];
277
- if (sessions !== null && sessions.activeAgentId !== runtime.agentId) {
278
- throw new Error("Exact Task runtime Session Agent is not current.");
279
- }
280
- const lifecycleMailbox = store.getWorkMailbox(runtimeLifecycleTarget({
281
- scope: "task",
282
- taskId: runtime.taskId,
283
- roleName: runtime.roleName
284
- }));
285
- const reservation = runtime.launchId !== undefined
286
- && isRuntimeLaunchReservation(lifecycleMailbox?.processing, runtime.launchId);
287
- const sessionLaunch = runtime.launchId !== undefined
288
- && session?.launchId === runtime.launchId;
289
- const preallocated = options.preallocatedDriverSessionReservation;
290
- const exactHostLaunchReservation = runtime.launchId !== undefined
291
- && reservation
292
- && !hasRuntimeCleanupObligation(lifecycleMailbox);
293
- const terminalSessionReplacementReservation = exactHostLaunchReservation
294
- && session !== undefined
295
- && session.status === "ended";
296
- const exactPreallocatedReservation = preallocated !== undefined
297
- && runtime.adapterId === preallocated.adapterId
298
- && exactHostLaunchReservation
299
- && runtime.nativeSessionId !== undefined
300
- && (session === undefined || terminalSessionReplacementReservation)
301
- && runtime.nativeSessionId === nativeSessionIdForLaunch(preallocated.yuiHome, runtime.launchId, runtime.agentId, runtime.adapterId);
302
- if (runtime.launchId === undefined || (!reservation && !sessionLaunch)) {
303
- throw new Error("Exact Task runtime launch fence is not current.");
304
- }
305
- if (runtime.nativeSessionId === undefined) {
306
- if (session !== undefined && !terminalSessionReplacementReservation) {
307
- throw new Error("Exact Task runtime native Session fence is missing.");
308
- }
309
- }
310
- else if (session === undefined || terminalSessionReplacementReservation) {
311
- if (!exactPreallocatedReservation) {
312
- throw new Error("Exact Task runtime native Session fence is not current.");
313
- }
314
- }
315
- else if (session.agentId !== runtime.agentId
316
- || session.adapterId !== runtime.adapterId
317
- || session.nativeSessionId !== runtime.nativeSessionId
318
- || session.launchId !== runtime.launchId
319
- || session.status === "ended"
320
- || session.effective.agentId !== runtime.agentId
321
- || session.effective.adapterId !== runtime.adapterId
322
- || canonicalPath(session.effective.workspace.root) !== runtime.workspace) {
323
- throw new Error("Exact Task runtime native Session fence is not current.");
324
- }
325
- }
326
- /** Publishes provider-discovered native identity without changing the stable source path. */
327
- export function refreshExactTaskRuntimeDescriptorSource(source, home, store) {
328
- const resolved = readTaskRuntimeSource(source, home);
329
- if (!resolved.fromFile) {
330
- throw new Error("A managed Task runtime descriptor must use its stable file source.");
331
- }
332
- const current = resolved.descriptor;
333
- const session = store.getTaskRoleSessionSet(current.taskId, current.roleName)
334
- ?.sessions[current.agentId];
335
- if (session === undefined) {
336
- throw new Error("Exact Task runtime native Session is not available for refresh.");
337
- }
338
- const refreshed = createExactTaskRuntimeDescriptor({
339
- ...current,
340
- nativeSessionId: session.nativeSessionId
341
- });
342
- if (exactTaskRuntimeDescriptorPath(home, refreshed) !== resolve(source)) {
343
- throw new Error("Refreshed Task runtime descriptor changed its stable identity.");
344
- }
345
- assertExactTaskRuntimeState(refreshed, store);
346
- writeTextFileAtomically(resolve(source), `${serializeExactDescriptor(refreshed)}\n`);
347
- return refreshed;
348
- }
349
- /**
350
- * Advances a reused Hook's own exact source to the current durable Turn/launch
351
- * generation before the volatile fence. The stable identity (control digest,
352
- * Task, Role, Agent, adapter, workspace) and the native Session are preserved;
353
- * only the volatile Turn/launch fields advance. A source may never jump to a
354
- * replacement native Session.
355
- */
356
- export function refreshReusedTaskRuntimeDescriptorSource(source, home, store, current) {
357
- const resolved = readTaskRuntimeSource(source, home);
358
- if (!resolved.fromFile) {
359
- throw new Error("A managed Task runtime descriptor must use its stable file source.");
360
- }
361
- const previous = resolved.descriptor;
362
- if (previous.nativeSessionId === undefined) {
363
- if (previous.turnId !== current.turnId || previous.launchId !== current.launchId) {
364
- throw new Error("Task runtime descriptor source cannot bind a native Session from another generation.");
365
- }
366
- return refreshExactTaskRuntimeDescriptorSource(source, home, store);
367
- }
368
- if (previous.nativeSessionId !== current.nativeSessionId) {
369
- throw new Error("Task runtime descriptor source cannot jump to a replacement native Session.");
370
- }
371
- const refreshed = createExactTaskRuntimeDescriptor({
372
- ...previous,
373
- turnId: current.turnId,
374
- launchId: current.launchId,
375
- nativeSessionId: current.nativeSessionId
376
- });
377
- if (exactTaskRuntimeDescriptorPath(home, refreshed) !== resolve(source)) {
378
- throw new Error("Refreshed Task runtime descriptor changed its stable identity.");
379
- }
380
- assertExactTaskRuntimeState(refreshed, store);
381
- writeTextFileAtomically(resolve(source), `${serializeExactDescriptor(refreshed)}\n`);
382
- return refreshed;
383
- }
384
- function readTaskRuntimeSource(source, home) {
385
- const value = requireText(source, "Exact Task runtime descriptor source");
386
- if (value.trimStart().startsWith("{")) {
387
- return { descriptor: parseExactTaskRuntimeDescriptor(value), fromFile: false };
388
- }
389
- const requested = resolve(value);
390
- const root = resolve(canonicalPath(home), "runtime", "exact-task-runtime");
391
- if (dirname(requested) !== root) {
392
- throw new Error("Exact Task runtime descriptor path is outside its control plane.");
393
- }
394
- let actual;
395
- try {
396
- actual = realpathSync(requested);
397
- }
398
- catch (error) {
399
- throw new Error("Exact Task runtime descriptor file is unavailable.", { cause: error });
400
- }
401
- if (actual !== requested) {
402
- throw new Error("Exact Task runtime descriptor file must not be a symbolic link.");
403
- }
404
- const descriptor = parseExactTaskRuntimeDescriptor(readFileSync(actual, "utf8"));
405
- if (exactTaskRuntimeDescriptorPath(home, descriptor) !== requested) {
406
- throw new Error("Exact Task runtime descriptor path does not match its stable identity.");
407
- }
408
- return { descriptor, fromFile: true };
155
+ assertControllerField(status.storageVersion, expected.storageVersion, "storage version");
156
+ assertControllerField(status.minimumStorageVersion, expected.minimumStorageVersion, "minimum storage migration version");
409
157
  }
410
158
  function validateVersionIdentity(value) {
411
159
  if (!isRecord(value))
412
160
  throw new Error("Yui version identity is invalid.");
413
161
  const version = requireText(value.version, "Yui version");
414
162
  const controllerProtocolVersion = requireVersion(value.controllerProtocolVersion, "Controller protocol version");
415
- const storageLayoutVersion = requireVersion(value.storageLayoutVersion, "Storage layout version");
416
- const aggregateSchemaVersion = requireVersion(value.aggregateSchemaVersion, "Aggregate schema version");
163
+ const storageVersion = requireVersion(value.storageVersion, "Storage version");
164
+ const minimumStorageVersion = requireVersion(value.minimumStorageVersion, "Minimum storage migration version");
165
+ if (minimumStorageVersion > storageVersion) {
166
+ throw new Error("Minimum storage migration version cannot exceed the current storage version.");
167
+ }
417
168
  return {
418
169
  version,
419
170
  controllerProtocolVersion,
420
- storageLayoutVersion,
421
- aggregateSchemaVersion
171
+ storageVersion,
172
+ minimumStorageVersion
422
173
  };
423
174
  }
424
175
  /** Managed continuity is a protocol/storage contract, not a package pin. */
425
176
  function assertContinuityIdentity(label, expected, actual) {
426
177
  for (const field of [
427
178
  "controllerProtocolVersion",
428
- "storageLayoutVersion",
429
- "aggregateSchemaVersion"
179
+ "storageVersion"
430
180
  ]) {
431
181
  if (expected[field] !== actual[field]) {
432
182
  throw new Error(`${label} ${field} does not match the frozen control plane `
@@ -442,8 +192,7 @@ function assertControllerContinuityIdentity(status, expected) {
442
192
  throw new Error("Controller version is invalid at the managed continuity gate.");
443
193
  }
444
194
  assertControllerField(status.protocolVersion, expected.controllerProtocolVersion, "protocol");
445
- assertControllerField(status.storageLayoutVersion, expected.storageLayoutVersion, "storage layout");
446
- assertControllerField(status.aggregateSchemaVersion, expected.aggregateSchemaVersion, "aggregate schema");
195
+ assertControllerField(status.storageVersion, expected.storageVersion, "storage version");
447
196
  }
448
197
  function assertControllerField(actual, expected, label) {
449
198
  if (actual !== expected) {
@@ -459,16 +208,6 @@ function assertSamePath(expected, actual, label) {
459
208
  throw new Error(`${label} does not match the frozen control plane (expected ${expected}, found ${normalized}).`);
460
209
  }
461
210
  }
462
- function assertEnvironment(expected, actual, label) {
463
- if (actual !== expected) {
464
- throw new Error(`${label} does not match the exact Task runtime descriptor.`);
465
- }
466
- }
467
- function assertOptionalEnvironment(expected, actual, label) {
468
- if (actual !== expected) {
469
- throw new Error(`${label} does not match the exact Task runtime descriptor.`);
470
- }
471
- }
472
211
  function parseDescriptorRecord(value) {
473
212
  let parsed;
474
213
  try {
@@ -487,19 +226,6 @@ function assertDescriptorKind(value, expected) {
487
226
  + `${typeof value.kind === "string" ? value.kind : "unknown"}.`);
488
227
  }
489
228
  }
490
- function requireAdapter(value) {
491
- if (value !== "codex" && value !== "claude") {
492
- throw new Error("Agent adapter id is invalid.");
493
- }
494
- return value;
495
- }
496
- function requireIdentity(value, label) {
497
- const text = requireText(value, label).trim();
498
- if (text.length === 0 || text.length > 1_024 || text.includes("\0")) {
499
- throw new Error(`${label} is invalid.`);
500
- }
501
- return text;
502
- }
503
229
  function requireText(value, label) {
504
230
  if (typeof value !== "string" || value.length === 0 || value.includes("\0")) {
505
231
  throw new Error(`${label} is invalid.`);
@@ -4,7 +4,7 @@ export function projectFirstProgressAdvisory(input) {
4
4
  : [...(input.sessions.history ?? []), ...Object.values(input.sessions.sessions)]
5
5
  .sort((left, right) => left.createdAt.localeCompare(right.createdAt));
6
6
  const unique = [...new Map(sessions.map((session) => [
7
- `${session.nativeSessionId}\0${session.launchId ?? ""}`,
7
+ `${session.nativeSessionId}\0${session.runtimeGenerationId ?? ""}`,
8
8
  session
9
9
  ])).values()];
10
10
  const firstGenerationAt = unique[0]?.createdAt;
@@ -5,7 +5,7 @@ export { createRuntimeBinding } from "./runtimeBinding.js";
5
5
  export { normalizeRuntimeOwner } from "./runtimeOwner.js";
6
6
  export { createSessionLaunchRequest } from "./sessionLaunchRequest.js";
7
7
  export { DEFAULT_RECENT_TURN_ID_LIMIT, hasRecentTurnId, rememberRecentTurnId, validateRecentTurnIds } from "./recentTurnIds.js";
8
- export { RuntimeHostContentionError, RuntimeLaunchError } from "./ports.js";
8
+ export { RuntimeGenerationMismatchError, RuntimeHostContentionError, RuntimeLaunchError } from "./ports.js";
9
9
  export { AgentHostPromptPushAdapter, TmuxSessionHost } from "./tmuxAdapters.js";
10
10
  export { FileTaskRuntimeIsolation, YUI_TASK_RUNTIME_ISOLATION_DESCRIPTOR, YUI_TASK_RUNTIME_SERVICE_NAMESPACE, assertTaskRuntimeIsolationPreflight, createTaskRuntimeIsolationDescriptor, parseTaskRuntimeIsolationDescriptor, planTaskRuntimeCleanup, taskRuntimeIsolationEnvironment, taskRuntimeIsolationFingerprint } from "./taskRuntimeIsolation.js";
11
11
  export { createSessionOwnerIdentity, discoverProviderRootByLaunchEnv, isLinuxProcessLive, listLaunchFencedProcesses, listOwnedProcessTree, readLinuxProcessIdentity } from "./sessionOwnerIdentity.js";
@@ -17,30 +17,30 @@ export class LaunchBroker {
17
17
  #reservations = new Map();
18
18
  reserve(payload) {
19
19
  validatePayload(payload);
20
- if (this.#reservations.has(payload.launchId)) {
21
- throw new Error(`Launch payload is already reserved: ${payload.launchId}.`);
20
+ if (this.#reservations.has(payload.runtimeGenerationId)) {
21
+ throw new Error(`Launch payload is already reserved: ${payload.runtimeGenerationId}.`);
22
22
  }
23
23
  const ticket = randomBytes(32).toString("hex");
24
- this.#reservations.set(payload.launchId, Object.freeze({
24
+ this.#reservations.set(payload.runtimeGenerationId, Object.freeze({
25
25
  ticket,
26
26
  payload,
27
27
  createdAt: Date.now()
28
28
  }));
29
- return Object.freeze({ launchId: payload.launchId, ticket });
29
+ return Object.freeze({ runtimeGenerationId: payload.runtimeGenerationId, ticket });
30
30
  }
31
- redeem(launchId, ticket) {
32
- const reservation = this.#reservations.get(launchId);
31
+ redeem(runtimeGenerationId, ticket) {
32
+ const reservation = this.#reservations.get(runtimeGenerationId);
33
33
  if (reservation === undefined || reservation.ticket !== ticket) {
34
34
  throw new Error("Launch ticket is invalid or already consumed.");
35
35
  }
36
- this.#reservations.delete(launchId);
36
+ this.#reservations.delete(runtimeGenerationId);
37
37
  if (Date.now() - reservation.createdAt > AGENT_HOST_LAUNCH_TICKET_TTL_MS) {
38
38
  throw new Error("Launch ticket expired before redemption.");
39
39
  }
40
40
  return reservation.payload;
41
41
  }
42
- revoke(launchId) {
43
- this.#reservations.delete(launchId);
42
+ revoke(runtimeGenerationId) {
43
+ this.#reservations.delete(runtimeGenerationId);
44
44
  }
45
45
  pendingCount() {
46
46
  return this.#reservations.size;
@@ -53,9 +53,9 @@ export function validateAgentHostLaunchPayload(value) {
53
53
  return validatePayload(value);
54
54
  }
55
55
  function validatePayload(payload) {
56
- if (payload.schemaVersion !== 1)
56
+ if (payload.schemaVersion !== 2)
57
57
  throw new Error("Agent Host launch payload version is invalid.");
58
- text(payload.launchId, "launchId");
58
+ text(payload.runtimeGenerationId, "runtimeGenerationId");
59
59
  text(payload.command, "command");
60
60
  text(payload.cwd, "cwd");
61
61
  if (!Array.isArray(payload.args))
@@ -33,11 +33,11 @@ export function runtimeLifecycleSignalKey(owner) {
33
33
  ? `role:${encodeURIComponent(owner.taskId)}/${encodeURIComponent(owner.roleName)}`
34
34
  : `global-role:${encodeURIComponent(owner.roleName)}`;
35
35
  }
36
- export function isRuntimeLaunchReservation(processing, launchId) {
36
+ export function isRuntimeLaunchReservation(processing, runtimeGenerationId) {
37
37
  return processing?.owner === RUNTIME_LIFECYCLE_OWNER
38
38
  && processing.batch.reasons.length === 1
39
39
  && processing.batch.reasons[0] === RUNTIME_LAUNCH_RESERVED_REASON
40
- && (launchId === undefined || processing.batchId === launchId);
40
+ && (runtimeGenerationId === undefined || processing.batchId === runtimeGenerationId);
41
41
  }
42
42
  export function hasRuntimeLaunchReservation(mailbox) {
43
43
  return isRuntimeLaunchReservation(mailbox?.processing);
@@ -0,0 +1,111 @@
1
+ import { createHash } from "node:crypto";
2
+ import { activeRoleAgentBinding } from "../role/role.js";
3
+ /** Per-process credential injected at native Session launch. */
4
+ export const MANAGED_CALLER_KEY_ENV = "YUI_JOB_CALLER_KEY";
5
+ /**
6
+ * A managed Session presented valid self-identity but is no longer the current
7
+ * runtime of its Task Role. This is a bounded diagnosis rather than a bare
8
+ * denial: the Agent can still read current state and decide whether to re-read,
9
+ * hand back, or stop.
10
+ */
11
+ export class ManagedRuntimeDriftError extends Error {
12
+ name = "ManagedRuntimeDriftError";
13
+ constructor(message) {
14
+ super(message);
15
+ }
16
+ }
17
+ /** Reads the process's own immutable managed identity, or undefined when unmanaged. */
18
+ export function managedTaskSessionIdentity(environment) {
19
+ const env = environment ?? {};
20
+ if (env.YUI_SESSION_SCOPE !== "task")
21
+ return undefined;
22
+ const taskId = identity(env.YUI_TASK_ID);
23
+ const roleName = identity(env.YUI_ROLE);
24
+ if (taskId === undefined || roleName === undefined) {
25
+ throw new ManagedRuntimeDriftError("Managed Task Session identity is incomplete: YUI_TASK_ID and YUI_ROLE are required.");
26
+ }
27
+ const workspace = identity(env.YUI_WORKSPACE);
28
+ const callerKey = identity(env[MANAGED_CALLER_KEY_ENV]);
29
+ return Object.freeze({
30
+ taskId,
31
+ roleName,
32
+ ...(workspace === undefined ? {} : { workspace }),
33
+ ...(callerKey === undefined ? {} : { callerKey })
34
+ });
35
+ }
36
+ /**
37
+ * Resolves the current runtime authority for a managed Task Session, or
38
+ * undefined for an unmanaged (plain user) invocation. Throws only when the
39
+ * process claims managed identity that durable state no longer recognizes.
40
+ */
41
+ export function resolveManagedTaskCaller(store, environment) {
42
+ const self = managedTaskSessionIdentity(environment);
43
+ if (self === undefined)
44
+ return undefined;
45
+ return requireCurrentRuntime(store, self);
46
+ }
47
+ /** Same as resolveManagedTaskCaller, but requires a managed Task Session. */
48
+ export function requireManagedTaskCaller(store, environment) {
49
+ const caller = resolveManagedTaskCaller(store, environment);
50
+ if (caller === undefined) {
51
+ throw new ManagedRuntimeDriftError("This command requires a managed Task Session.");
52
+ }
53
+ return caller;
54
+ }
55
+ /**
56
+ * The current runtime authority for one Task Role, or undefined. Used by
57
+ * command authorization that must not fail the whole invocation, only decline
58
+ * one privileged effect.
59
+ */
60
+ export function currentManagedRuntime(store, environment, taskId, roleName) {
61
+ let caller;
62
+ try {
63
+ caller = resolveManagedTaskCaller(store, environment);
64
+ }
65
+ catch {
66
+ return undefined;
67
+ }
68
+ if (caller === undefined || caller.taskId !== taskId)
69
+ return undefined;
70
+ if (roleName !== undefined && caller.roleName !== roleName)
71
+ return undefined;
72
+ return caller;
73
+ }
74
+ function requireCurrentRuntime(store, self) {
75
+ const role = store.getRole(self.taskId, self.roleName);
76
+ if (role === null) {
77
+ throw new ManagedRuntimeDriftError(`This managed Session belongs to ${self.taskId}/${self.roleName}, which no longer exists. `
78
+ + "A new Session must be launched to act on this Task.");
79
+ }
80
+ const agentId = role.activeAgentId;
81
+ if (self.callerKey === undefined) {
82
+ throw new ManagedRuntimeDriftError(`This managed Session carries no ${MANAGED_CALLER_KEY_ENV}, so it cannot be recognized `
83
+ + `as the current runtime of ${self.taskId}/${self.roleName}.`);
84
+ }
85
+ const expectedHash = store.getJobCallerKeyHash(self.taskId, self.roleName, agentId);
86
+ if (expectedHash === null
87
+ || createHash("sha256").update(self.callerKey).digest("hex") !== expectedHash) {
88
+ throw new ManagedRuntimeDriftError(`This managed Session is no longer the current runtime of ${self.taskId}/${self.roleName} `
89
+ + `(the Role now runs Agent ${agentId}). Its native Session was replaced or its Role was `
90
+ + "rebound, so its durable caller key no longer matches. Nothing was changed. Read the "
91
+ + `current state with \`yui task show ${self.taskId}\`; acting requires the Session Yui `
92
+ + "launched for the current runtime.");
93
+ }
94
+ const activeTurn = store.getActiveTurn(self.taskId, self.roleName);
95
+ return Object.freeze({
96
+ taskId: self.taskId,
97
+ roleName: self.roleName,
98
+ agentId,
99
+ adapterId: activeRoleAgentBinding(role).adapterId,
100
+ ...(self.workspace === undefined ? {} : { workspace: self.workspace }),
101
+ ...(activeTurn === null || activeTurn.status !== "active"
102
+ ? {}
103
+ : { currentTurnId: activeTurn.id })
104
+ });
105
+ }
106
+ function identity(value) {
107
+ if (typeof value !== "string" || value.includes("\0"))
108
+ return undefined;
109
+ const normalized = value.trim();
110
+ return normalized.length === 0 || normalized !== value ? undefined : normalized;
111
+ }
@@ -1,13 +1,13 @@
1
1
  /** A persisted launch is either temporarily unavailable or permanently lost. */
2
2
  export class RuntimeLaunchError extends Error {
3
3
  retryable;
4
- launchId;
4
+ runtimeGenerationId;
5
5
  reason;
6
6
  name = "RuntimeLaunchError";
7
- constructor(retryable, launchId, message, reason) {
7
+ constructor(retryable, runtimeGenerationId, message, reason) {
8
8
  super(message);
9
9
  this.retryable = retryable;
10
- this.launchId = launchId;
10
+ this.runtimeGenerationId = runtimeGenerationId;
11
11
  this.reason = reason;
12
12
  }
13
13
  }
@@ -20,3 +20,16 @@ export class RuntimeHostContentionError extends Error {
20
20
  this.reason = reason;
21
21
  }
22
22
  }
23
+ /** A reused Agent Host acknowledged a different runtime generation. */
24
+ export class RuntimeGenerationMismatchError extends Error {
25
+ expectedRuntimeGenerationId;
26
+ observedRuntimeGenerationId;
27
+ hostState;
28
+ name = "RuntimeGenerationMismatchError";
29
+ constructor(expectedRuntimeGenerationId, observedRuntimeGenerationId, hostState, message) {
30
+ super(message);
31
+ this.expectedRuntimeGenerationId = expectedRuntimeGenerationId;
32
+ this.observedRuntimeGenerationId = observedRuntimeGenerationId;
33
+ this.hostState = hostState;
34
+ }
35
+ }
@@ -2,10 +2,10 @@ import { createHash } from "node:crypto";
2
2
  import { resolve } from "node:path";
3
3
  import { requireText } from "./validation.js";
4
4
  /** Deterministic native identity preallocated for one exact provider launch. */
5
- export function nativeSessionIdForLaunch(home, launchId, agentId, adapterId) {
5
+ export function nativeSessionIdForLaunch(home, runtimeGenerationId, agentId, adapterId) {
6
6
  const hex = createHash("sha256").update(JSON.stringify([
7
7
  resolve(requireText(home, "YUI_HOME")),
8
- requireText(launchId, "Launch id"),
8
+ requireText(runtimeGenerationId, "Runtime generation id"),
9
9
  requireText(agentId, "Agent id"),
10
10
  requireText(adapterId, "Agent adapter id")
11
11
  ])).digest("hex");