@zq-silk/yui 0.14.0 → 0.14.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/ARCHITECTURE.md +64 -35
  2. package/README.md +258 -125
  3. package/dist/agent/argumentPolicy.js +2 -1
  4. package/dist/agent/managedRuntimeEnvironment.js +5 -8
  5. package/dist/cli/commandCatalog.js +74 -109
  6. package/dist/cli/helpRenderer.js +1 -2
  7. package/dist/cli/interactionCandidates.js +10 -0
  8. package/dist/cli/interactionPolicy.js +76 -37
  9. package/dist/cli/roleWizard.js +5 -6
  10. package/dist/cli/updateOrchestrator.js +10 -8
  11. package/dist/cli/updatePorts.js +17 -13
  12. package/dist/cli/upgradeCommand.js +2 -5
  13. package/dist/cli.js +268 -231
  14. package/dist/commands/agentCommands.js +13 -0
  15. package/dist/commands/configCommands.js +7 -19
  16. package/dist/commands/configOverview.js +3 -2
  17. package/dist/commands/durableJobCommands.js +1 -1
  18. package/dist/commands/executionAuditCommands.js +4 -9
  19. package/dist/commands/globalRoleCommands.js +17 -0
  20. package/dist/commands/profileCommands.js +200 -30
  21. package/dist/commands/sessionCommands.js +1 -1
  22. package/dist/commands/taskActor.js +29 -127
  23. package/dist/commands/taskChangeSetCommands.js +1 -3
  24. package/dist/commands/taskCommands.js +1771 -2107
  25. package/dist/commands/taskCompletionGate.js +27 -103
  26. package/dist/commands/taskContextCommand.js +47 -80
  27. package/dist/commands/taskExecutionCommands.js +9 -27
  28. package/dist/commands/taskInputCommands.js +10 -10
  29. package/dist/commands/taskIntegrationCommands.js +69 -61
  30. package/dist/commands/taskIntegrationQueueCommands.js +1 -1
  31. package/dist/commands/taskNextActionCommand.js +8 -29
  32. package/dist/commands/taskOverviewCommand.js +25 -3
  33. package/dist/commands/taskPublicationCommands.js +151 -85
  34. package/dist/commands/taskPublicationVerifyCommand.js +181 -0
  35. package/dist/commands/taskRemoteDeliveryCommand.js +134 -0
  36. package/dist/commands/taskRoleRuntimeStatus.js +5 -6
  37. package/dist/commands/taskUpstreamCommands.js +79 -95
  38. package/dist/config/configCatalog.js +1 -1
  39. package/dist/context/contextSnapshot.js +17 -2
  40. package/dist/context/sessionBootstrapManifest.js +18 -1
  41. package/dist/context/sourceTurnContext.js +30 -0
  42. package/dist/context/turnContextPack.js +107 -48
  43. package/dist/context/turnInputContract.js +11 -4
  44. package/dist/context/wakeNotification.js +84 -22
  45. package/dist/controller/agentRuntimeObserver.js +9 -12
  46. package/dist/controller/clientRuntime.js +13 -13
  47. package/dist/controller/controller.js +11 -11
  48. package/dist/controller/fileSchedulerStoreAdapter.js +262 -219
  49. package/dist/controller/jobControl.js +11 -10
  50. package/dist/controller/resourceInventory.js +6 -6
  51. package/dist/controller/resourceInventoryLinux.js +2 -2
  52. package/dist/controller/runtime.js +18 -61
  53. package/dist/controller/runtimeEventInbox.js +57 -34
  54. package/dist/controller/runtimeEventProcessor.js +36 -96
  55. package/dist/controller/runtimeHookTurnFence.js +33 -57
  56. package/dist/controller/runtimeLaunchCoordinator.js +44 -40
  57. package/dist/controller/runtimeObservationHook.js +18 -12
  58. package/dist/controller/sessionNotify.js +43 -28
  59. package/dist/controller/sessionOwnerReconciliation.js +21 -21
  60. package/dist/controller/structuredProviderObservation.js +13 -16
  61. package/dist/coordination/workMailboxQueue.js +132 -0
  62. package/dist/core/controllerServer.js +1 -0
  63. package/dist/domain/agentResultTransport.js +60 -0
  64. package/dist/execution/executionHealth.js +111 -118
  65. package/dist/execution/reviewMainTurn.js +161 -0
  66. package/dist/execution/workItemExecution.js +454 -0
  67. package/dist/execution/workItemExecutionProjection.js +309 -0
  68. package/dist/execution/workItemMainTurn.js +164 -0
  69. package/dist/executor/agentExecutor.js +19 -19
  70. package/dist/executor/executorRegistry.js +7 -28
  71. package/dist/executor/fileRoleLaunchPlanner.js +51 -83
  72. package/dist/external/githubPublicationVerifier.js +114 -0
  73. package/dist/external/gitlabPublicationVerifier.js +158 -0
  74. package/dist/external/pinnedCommandRunner.js +73 -0
  75. package/dist/integration/changeSetManifest.js +0 -3
  76. package/dist/integration/deliveryObligation.js +43 -6
  77. package/dist/integration/gitIntegrationService.js +284 -101
  78. package/dist/integration/integrationAttempt.js +64 -14
  79. package/dist/integration/integrationQueueEntry.js +5 -35
  80. package/dist/integration/integrationQueueService.js +65 -194
  81. package/dist/lifecycle/canonicalLifecycleEvent.js +16 -15
  82. package/dist/lifecycle/exactTurnTerminalization.js +100 -97
  83. package/dist/message/message.js +16 -0
  84. package/dist/observability/executionAudit.js +4 -25
  85. package/dist/observability/faultClassification.js +58 -70
  86. package/dist/observability/orchestrationMetrics.js +8 -50
  87. package/dist/profile/agentProfile.js +60 -15
  88. package/dist/profile/agentProfileRuntime.js +100 -0
  89. package/dist/release/releaseWorkflowPorts.js +5 -68
  90. package/dist/repository/executionLaneGitSnapshot.js +80 -0
  91. package/dist/repository/gitWorkspace.js +9 -67
  92. package/dist/repository/taskBaseFreshness.js +3 -1
  93. package/dist/repository/taskWorkspaceCoordinator.js +32 -4
  94. package/dist/repository/taskWorkspacePreparer.js +252 -411
  95. package/dist/resources/liveReferences.js +6 -55
  96. package/dist/resources/resourceDiscovery.js +1 -30
  97. package/dist/resources/resourceRegistrar.js +0 -23
  98. package/dist/review/deltaRecheck.js +8 -65
  99. package/dist/review/reviewAcceptance.js +22 -19
  100. package/dist/review/reviewConfig.js +0 -10
  101. package/dist/review/reviewDecision.js +2 -2
  102. package/dist/review/reviewRound.js +147 -273
  103. package/dist/review/reviewerAvailability.js +13 -23
  104. package/dist/role/role.js +3 -0
  105. package/dist/runtime/agentDriverObservation.js +1 -1
  106. package/dist/runtime/agentHost.js +51 -51
  107. package/dist/runtime/builtinAgentDrivers.js +9 -2
  108. package/dist/runtime/codexAppServerRuntime.js +23 -5
  109. package/dist/runtime/exactControlPlane.js +2 -267
  110. package/dist/runtime/firstProgressAdvisory.js +1 -1
  111. package/dist/runtime/index.js +1 -1
  112. package/dist/runtime/launchBroker.js +11 -11
  113. package/dist/runtime/lifecycleReservation.js +2 -2
  114. package/dist/runtime/managedCaller.js +111 -0
  115. package/dist/runtime/ports.js +16 -3
  116. package/dist/runtime/preallocatedNativeSession.js +2 -2
  117. package/dist/runtime/processExitObservation.js +3 -2
  118. package/dist/runtime/providerContinuationReconciliationService.js +1 -1
  119. package/dist/runtime/runtimeBinding.js +1 -1
  120. package/dist/runtime/runtimeObservation.js +33 -16
  121. package/dist/runtime/runtimeSessionCandidate.js +2 -2
  122. package/dist/runtime/runtimeStopReceipt.js +11 -11
  123. package/dist/runtime/sessionLaunchRequest.js +2 -2
  124. package/dist/runtime/sessionOwnerIdentity.js +9 -9
  125. package/dist/runtime/sessionOwnerRegistry.js +13 -13
  126. package/dist/runtime/sessionReconciliation.js +4 -4
  127. package/dist/runtime/sessionTerminationGuard.js +9 -9
  128. package/dist/runtime/sessionTokenMetrics.js +5 -7
  129. package/dist/runtime/structuredProviderHost.js +5 -5
  130. package/dist/runtime/taskRuntimeIsolation.js +24 -24
  131. package/dist/runtime/tmuxAdapters.js +32 -30
  132. package/dist/scheduler/activeRoleTurnDelivery.js +37 -15
  133. package/dist/scheduler/leaderWakeupProcessor.js +78 -55
  134. package/dist/scheduler/roleTurnLiveness.js +6 -6
  135. package/dist/scheduler/roleTurnStall.js +8 -9
  136. package/dist/scheduler/taskExecutionProjection.js +19 -41
  137. package/dist/scheduler/taskObservabilityProjection.js +75 -122
  138. package/dist/storage/sqliteSchema.js +1 -21
  139. package/dist/storage/sqliteStore.js +62 -47
  140. package/dist/storage/storageSchema.js +1 -8
  141. package/dist/storage/storageVersions.js +7 -4
  142. package/dist/storage/taskStore.js +8 -8
  143. package/dist/storage/upgrade/upgradeOrchestrator.js +112 -83
  144. package/dist/task/completionReadiness.js +17 -98
  145. package/dist/task/deliveryGuard.js +9 -11
  146. package/dist/task/draftPlan.js +142 -0
  147. package/dist/task/nextAction.js +179 -477
  148. package/dist/task/publicationVerification.js +1 -0
  149. package/dist/task/remoteDelivery.js +233 -0
  150. package/dist/task/task.js +108 -4
  151. package/dist/task/taskRecordReference.js +0 -1
  152. package/dist/turn/turn.js +109 -16
  153. package/dist/verification/verificationGateService.js +0 -26
  154. package/dist/web/assets/client/components.js +99 -121
  155. package/dist/web/assets/client/i18n.js +136 -58
  156. package/dist/web/assets/client/view.js +44 -0
  157. package/dist/web/assets/styles/cards.js +3 -15
  158. package/dist/web/assets/styles/widgets.js +4 -0
  159. package/dist/web/webSnapshot.js +29 -14
  160. package/dist/workItem/dependencyGate.js +34 -0
  161. package/dist/workItem/workItem.js +99 -266
  162. package/dist/workspace/workItemChangeSetManager.js +15 -59
  163. package/docs/task-local-identity.md +1 -1
  164. package/i18n/README.zh-CN.md +165 -59
  165. package/package.json +1 -1
  166. package/skills/yui-leader/SKILL.md +272 -782
  167. package/skills/yui-operator/SKILL.md +190 -365
  168. package/skills/yui-reviewer/SKILL.md +40 -33
  169. package/skills/yui-runtime/SKILL.md +27 -6
  170. package/skills/yui-worker/SKILL.md +19 -6
  171. package/dist/execution/candidateConvergence.js +0 -623
  172. package/dist/execution/executionGroup.js +0 -854
  173. package/dist/execution/resourceBroker.js +0 -426
  174. package/dist/executor/launchPlan.js +0 -16
  175. package/dist/review/reviewFinding.js +0 -240
  176. package/dist/review/reviewFindingLedger.js +0 -550
  177. package/dist/review/reviewOutcomeClassifier.js +0 -255
  178. package/dist/scheduler/resourceQueueProjection.js +0 -71
  179. package/dist/task/repairWave.js +0 -150
@@ -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,
@@ -226,187 +165,6 @@ export function assertControllerStatusIdentity(status, expected = yuiVersionIden
226
165
  assertControllerField(status.storageLayoutVersion, expected.storageLayoutVersion, "storage layout");
227
166
  assertControllerField(status.aggregateSchemaVersion, expected.aggregateSchemaVersion, "aggregate schema");
228
167
  }
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 };
409
- }
410
168
  function validateVersionIdentity(value) {
411
169
  if (!isRecord(value))
412
170
  throw new Error("Yui version identity is invalid.");
@@ -459,16 +217,6 @@ function assertSamePath(expected, actual, label) {
459
217
  throw new Error(`${label} does not match the frozen control plane (expected ${expected}, found ${normalized}).`);
460
218
  }
461
219
  }
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
220
  function parseDescriptorRecord(value) {
473
221
  let parsed;
474
222
  try {
@@ -487,19 +235,6 @@ function assertDescriptorKind(value, expected) {
487
235
  + `${typeof value.kind === "string" ? value.kind : "unknown"}.`);
488
236
  }
489
237
  }
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
238
  function requireText(value, label) {
504
239
  if (typeof value !== "string" || value.length === 0 || value.includes("\0")) {
505
240
  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");
@@ -1,4 +1,5 @@
1
- export const RUNTIME_PROCESS_EXIT_SCHEMA_VERSION = 1;
1
+ export const RUNTIME_PROCESS_EXIT_SCHEMA_VERSION = 2;
2
+ export const RUNTIME_PROCESS_EXIT_TASK_EVENT = "runtime.process-exit-observed";
2
3
  export function validateRuntimeProcessExitObservation(observation) {
3
4
  if (observation.schemaVersion !== RUNTIME_PROCESS_EXIT_SCHEMA_VERSION) {
4
5
  throw new Error("Runtime process-exit observation version is invalid.");
@@ -9,7 +10,7 @@ export function validateRuntimeProcessExitObservation(observation) {
9
10
  optionalIdentity(observation.taskId, "taskId");
10
11
  identity(observation.roleName, "roleName");
11
12
  optionalIdentity(observation.turnId, "turnId");
12
- identity(observation.launchId, "launchId");
13
+ identity(observation.runtimeGenerationId, "runtimeGenerationId");
13
14
  optionalIdentity(observation.nativeSessionId, "nativeSessionId");
14
15
  if (observation.processKind !== "agent-host" && observation.processKind !== "provider-child") {
15
16
  throw new Error("Runtime process kind is invalid.");
@@ -135,7 +135,7 @@ function reconciliationObservation(continuation, sourceFence, now) {
135
135
  const settled = continuation.execution === "quiescent"
136
136
  && continuation.observation === "exact";
137
137
  return createRuntimeObservation({
138
- schemaVersion: 2,
138
+ schemaVersion: 4,
139
139
  eventId: `continuation-reconcile:${digest}`,
140
140
  semanticKey: `continuation-reconcile:${digest}`,
141
141
  kind: settled ? "continuation.settled" : "continuation.started",
@@ -7,7 +7,7 @@ export function createRuntimeBinding(input) {
7
7
  : requireBoolean(input.hostCreated, "Runtime host-created flag");
8
8
  return {
9
9
  id: requireSafeIdentity(input.id, "Runtime binding id"),
10
- launchId: requireSafeIdentity(input.launchId, "Launch id"),
10
+ runtimeGenerationId: requireSafeIdentity(input.runtimeGenerationId, "Runtime generation id"),
11
11
  owner: normalizeRuntimeOwner(input.owner),
12
12
  agentId: requireSafeIdentity(input.agentId, "Agent id"),
13
13
  adapterId: requireSafeIdentity(input.adapterId, "Agent adapter id"),