@zq-silk/yui 0.13.9 → 0.14.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 (229) hide show
  1. package/ARCHITECTURE.md +69 -428
  2. package/README.md +108 -298
  3. package/dist/agent/managedRuntimeEnvironment.js +2 -2
  4. package/dist/cli/commandCatalog.js +39 -77
  5. package/dist/cli/interactionCandidates.js +4 -4
  6. package/dist/cli/interactionPolicy.js +7 -10
  7. package/dist/cli/invocationRouter.js +1 -1
  8. package/dist/cli/updateCommand.js +6 -27
  9. package/dist/cli/updateOrchestrator.js +63 -484
  10. package/dist/cli/updatePorts.js +15 -333
  11. package/dist/cli/upgradeCommand.js +8 -65
  12. package/dist/cli.js +57 -242
  13. package/dist/commands/configCommands.js +11 -62
  14. package/dist/commands/executionAuditCommands.js +23 -23
  15. package/dist/commands/globalRoleCommands.js +2 -7
  16. package/dist/commands/jobCommands.js +1 -4
  17. package/dist/commands/projectCommands.js +5 -5
  18. package/dist/commands/releaseCommands.js +3 -10
  19. package/dist/commands/resourcesCommands.js +2 -2
  20. package/dist/commands/sessionCommands.js +2 -2
  21. package/dist/commands/taskActor.js +31 -39
  22. package/dist/commands/taskChangeSetCommands.js +5 -12
  23. package/dist/commands/taskCommands.js +464 -1249
  24. package/dist/commands/taskContextCommand.js +35 -36
  25. package/dist/commands/taskExecutionCommands.js +33 -46
  26. package/dist/commands/taskInputCommands.js +20 -42
  27. package/dist/commands/taskNextActionCommand.js +9 -9
  28. package/dist/commands/taskOverviewCommand.js +56 -95
  29. package/dist/commands/taskRoleRuntimeStatus.js +77 -107
  30. package/dist/commands/taskUpstreamCommands.js +3 -3
  31. package/dist/commands/taskWorkspaceCommands.js +7 -79
  32. package/dist/commands/telemetryCommands.js +32 -124
  33. package/dist/commands/workflowCommands.js +3 -3
  34. package/dist/completion/fileCompletionManager.js +1 -1
  35. package/dist/config/configCatalog.js +4 -6
  36. package/dist/config/yuiConfig.js +6 -43
  37. package/dist/context/dispatchContext.js +14 -30
  38. package/dist/context/roleSessionContext.js +2 -2
  39. package/dist/context/sessionBootstrapManifest.js +2 -2
  40. package/dist/context/{runContextPack.js → turnContextPack.js} +58 -73
  41. package/dist/context/turnInputContract.js +204 -0
  42. package/dist/context/wakeNotification.js +2 -2
  43. package/dist/controller/agentRuntimeObserver.js +18 -18
  44. package/dist/controller/clientRuntime.js +13 -20
  45. package/dist/controller/controller.js +89 -116
  46. package/dist/controller/fileSchedulerStoreAdapter.js +511 -742
  47. package/dist/controller/jobControl.js +17 -17
  48. package/dist/controller/resourceInventory.js +6 -6
  49. package/dist/controller/resourceInventoryLinux.js +38 -59
  50. package/dist/controller/resourceInventoryRpc.js +3 -3
  51. package/dist/controller/resourceInventoryWorker.js +1 -1
  52. package/dist/controller/runtime.js +46 -53
  53. package/dist/controller/runtimeEventInbox.js +23 -31
  54. package/dist/controller/runtimeEventProcessor.js +15 -15
  55. package/dist/controller/{runtimeHookRunFence.js → runtimeHookTurnFence.js} +87 -81
  56. package/dist/controller/runtimeLaunchCoordinator.js +3 -3
  57. package/dist/controller/runtimeObservationHook.js +11 -8
  58. package/dist/controller/sessionNotify.js +11 -11
  59. package/dist/controller/structuredProviderObservation.js +89 -16
  60. package/dist/coordination/workMailbox.js +309 -579
  61. package/dist/coordination/workMailboxQueue.js +5 -6
  62. package/dist/core/controllerClient.js +1 -91
  63. package/dist/core/controllerServer.js +3 -6
  64. package/dist/doctor/doctor.js +51 -314
  65. package/dist/execution/candidateConvergence.js +3 -3
  66. package/dist/execution/executionGroup.js +32 -33
  67. package/dist/execution/executionHealth.js +36 -38
  68. package/dist/execution/resourceBroker.js +12 -12
  69. package/dist/executor/agentExecutor.js +30 -265
  70. package/dist/executor/effectiveLaunch.js +9 -19
  71. package/dist/executor/executorRegistry.js +43 -17
  72. package/dist/executor/fileRoleLaunchPlanner.js +56 -56
  73. package/dist/executor/launchPlan.js +3 -3
  74. package/dist/executor/workspacePreflightClassification.js +16 -16
  75. package/dist/grant/capabilityGrant.js +20 -25
  76. package/dist/input/inputRequest.js +12 -12
  77. package/dist/integration/changeSet.js +9 -13
  78. package/dist/integration/changeSetManifest.js +1 -1
  79. package/dist/integration/gitIntegrationService.js +2 -2
  80. package/dist/integration/integrationQueueService.js +5 -21
  81. package/dist/integration/overlapDiagnostics.js +2 -2
  82. package/dist/interaction/operatorPresentation.js +1 -1
  83. package/dist/job/jobRunner.js +1 -1
  84. package/dist/lifecycle/canonicalLifecycleEvent.js +25 -25
  85. package/dist/lifecycle/{exactRunTerminalization.js → exactTurnTerminalization.js} +116 -232
  86. package/dist/message/message.js +6 -6
  87. package/dist/observability/executionAudit.js +74 -86
  88. package/dist/observability/faultClassification.js +7 -24
  89. package/dist/observability/orchestrationMetrics.js +5 -5
  90. package/dist/observability/runtimeIdentity.js +34 -101
  91. package/dist/profile/agentProfile.js +1 -1
  92. package/dist/release/cliHomeReleaseFence.js +3 -3
  93. package/dist/release/releaseWorkflowEngine.js +6 -10
  94. package/dist/release/releaseWorkflowPorts.js +2 -2
  95. package/dist/release/workflowFileLock.js +1 -1
  96. package/dist/repository/checkoutSwap.js +1 -1
  97. package/dist/repository/gitWorkspace.js +0 -13
  98. package/dist/repository/project.js +1 -1
  99. package/dist/repository/projectMaintenanceLock.js +1 -2
  100. package/dist/repository/taskBaseFreshness.js +13 -13
  101. package/dist/repository/taskWorkspaceCoordinator.js +21 -25
  102. package/dist/repository/taskWorkspaceIdentity.js +8 -21
  103. package/dist/repository/taskWorkspacePreparer.js +63 -620
  104. package/dist/resources/autoResourceGc.js +3 -3
  105. package/dist/resources/liveReferences.js +1 -1
  106. package/dist/resources/resourceGc.js +1 -1
  107. package/dist/resources/sqliteResourceRegistry.js +1 -1
  108. package/dist/review/reviewDecision.js +7 -7
  109. package/dist/review/reviewFindingLedger.js +1 -1
  110. package/dist/review/reviewOutcomeClassifier.js +35 -50
  111. package/dist/review/reviewRound.js +14 -25
  112. package/dist/review/reviewerAvailability.js +4 -4
  113. package/dist/review/taskFinalReviewContractResolution.js +41 -0
  114. package/dist/role/role.js +6 -3
  115. package/dist/runtime/agentDriver.js +4 -0
  116. package/dist/runtime/agentHost.js +151 -36
  117. package/dist/runtime/builtinAgentDrivers.js +79 -2
  118. package/dist/runtime/codexAppServerRuntime.js +122 -0
  119. package/dist/runtime/continuationManager.js +3 -5
  120. package/dist/runtime/exactControlPlane.js +17 -42
  121. package/dist/runtime/firstProgressAdvisory.js +1 -1
  122. package/dist/runtime/index.js +2 -2
  123. package/dist/runtime/launchDiagnostics.js +2 -2
  124. package/dist/runtime/lifecycleReservation.js +4 -4
  125. package/dist/runtime/processExitObservation.js +1 -1
  126. package/dist/runtime/processExitOutbox.js +0 -32
  127. package/dist/runtime/promptEnvelope.js +9 -9
  128. package/dist/runtime/providerContinuation.js +2 -2
  129. package/dist/runtime/providerContinuationReconciliationService.js +2 -2
  130. package/dist/runtime/providerRuntimeIdentity.js +76 -24
  131. package/dist/runtime/recentTurnIds.js +38 -0
  132. package/dist/runtime/runtimeContinuationProjection.js +2 -2
  133. package/dist/runtime/runtimeHealthPolicy.js +1 -1
  134. package/dist/runtime/runtimeObservation.js +58 -21
  135. package/dist/runtime/runtimeProjection.js +19 -27
  136. package/dist/runtime/sessionLaunchRequest.js +2 -2
  137. package/dist/runtime/structuredProviderHost.js +177 -12
  138. package/dist/runtime/tmuxAdapters.js +46 -11
  139. package/dist/scheduler/activeRoleTurnDelivery.js +245 -0
  140. package/dist/scheduler/leaderWakeupProcessor.js +147 -445
  141. package/dist/scheduler/operatorEvent.js +4 -6
  142. package/dist/scheduler/resourceQueueProjection.js +71 -0
  143. package/dist/scheduler/{roleRunLiveness.js → roleTurnLiveness.js} +24 -28
  144. package/dist/scheduler/{roleRunStall.js → roleTurnStall.js} +162 -247
  145. package/dist/scheduler/taskExecutionProjection.js +99 -99
  146. package/dist/scheduler/taskObservabilityProjection.js +10 -10
  147. package/dist/scheduler/taskWake.js +4 -4
  148. package/dist/scheduler/wakeReason.js +6 -10
  149. package/dist/scheduler/wakeupQueue.js +0 -9
  150. package/dist/setup/setupCommand.js +2 -2
  151. package/dist/storage/currentTaskStore.js +46 -0
  152. package/dist/storage/persistenceWorker.js +1 -1
  153. package/dist/storage/sqliteSchema.js +81 -485
  154. package/dist/storage/sqliteStore.js +206 -365
  155. package/dist/storage/storageSchema.js +1 -15
  156. package/dist/storage/storageVersions.js +8 -9
  157. package/dist/storage/storeRpc.js +10 -34
  158. package/dist/storage/taskStore.js +63 -3415
  159. package/dist/storage/upgrade/recordVersions.js +55 -149
  160. package/dist/storage/upgrade/upgradeOrchestrator.js +89 -1752
  161. package/dist/task/completionReadiness.js +5 -5
  162. package/dist/task/deliveryGuard.js +10 -10
  163. package/dist/task/nextAction.js +74 -77
  164. package/dist/task/task.js +2 -4
  165. package/dist/task/taskRecordReference.js +3 -3
  166. package/dist/task/taskRecordRetirement.js +1 -1
  167. package/dist/telemetry/sqliteTelemetryStore.js +53 -53
  168. package/dist/telemetry/telemetryConfig.js +14 -14
  169. package/dist/telemetry/telemetryWiring.js +8 -14
  170. package/dist/turn/turn.js +356 -0
  171. package/dist/{run/runIdentity.js → turn/turnIdentity.js} +4 -4
  172. package/dist/verification/gateArtifactStore.js +2 -2
  173. package/dist/web/assets/client/app.js +1 -1
  174. package/dist/web/assets/client/components.js +29 -54
  175. package/dist/web/assets/client/i18n.js +17 -37
  176. package/dist/web/assets/client/view.js +37 -37
  177. package/dist/web/assets/shell.js +1 -1
  178. package/dist/web/assets/styles/cards.js +5 -5
  179. package/dist/web/assets/styles/widgets.js +1 -2
  180. package/dist/web/webSnapshot.js +65 -60
  181. package/dist/workItem/workItem.js +11 -11
  182. package/dist/workspace/workItemChangeSetManager.js +1 -1
  183. package/docs/task-local-identity.md +5 -5
  184. package/i18n/README.zh-CN.md +41 -54
  185. package/package.json +1 -1
  186. package/skills/yui-leader/SKILL.md +85 -83
  187. package/skills/yui-operator/SKILL.md +16 -16
  188. package/skills/yui-reviewer/SKILL.md +10 -10
  189. package/skills/yui-runtime/SKILL.md +17 -18
  190. package/skills/yui-worker/SKILL.md +16 -18
  191. package/dist/context/runContextContract.js +0 -162
  192. package/dist/executor/turnCompletion.js +0 -126
  193. package/dist/review/taskFinalReviewContractEvent.js +0 -1
  194. package/dist/review/taskFinalReviewContractRebind.js +0 -367
  195. package/dist/run/agentRun.js +0 -356
  196. package/dist/run/rejectedYieldAttempt.js +0 -221
  197. package/dist/run/runControlRequest.js +0 -50
  198. package/dist/run/yieldReceipt.js +0 -65
  199. package/dist/runtime/turnCompletion.js +0 -3
  200. package/dist/scheduler/actionability.js +0 -323
  201. package/dist/scheduler/activeRoleRunDelivery.js +0 -868
  202. package/dist/scheduler/activeTaskProgress.js +0 -126
  203. package/dist/storage/compatibleTaskStore.js +0 -248
  204. package/dist/storage/migration/baseline.js +0 -78
  205. package/dist/storage/migration/classifier.js +0 -51
  206. package/dist/storage/migration/compatibleCodec.js +0 -53
  207. package/dist/storage/migration/engine.js +0 -147
  208. package/dist/storage/migration/index.js +0 -33
  209. package/dist/storage/migration/planner.js +0 -154
  210. package/dist/storage/migration/productionRegistry.js +0 -3931
  211. package/dist/storage/migration/registry.js +0 -169
  212. package/dist/storage/migration/report.js +0 -54
  213. package/dist/storage/migration/types.js +0 -31
  214. package/dist/storage/upgrade/homeClassification.js +0 -353
  215. package/dist/storage/upgrade/homeMigrationTarget.js +0 -595
  216. package/dist/storage/upgrade/migrationReceipt.js +0 -67
  217. package/dist/storage/upgrade/offlineUpgradeInventory.js +0 -453
  218. package/dist/storage/upgrade/productionMigrationRegistry.js +0 -6
  219. package/dist/storage/upgrade/pseudoLayoutRepair.js +0 -241
  220. package/dist/storage/upgrade/recordVersionScan.js +0 -176
  221. package/dist/storage/upgrade/sqliteFileSet.js +0 -50
  222. package/dist/storage/upgrade/sqliteMigrationTarget.js +0 -375
  223. package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +0 -305
  224. package/dist/storage/upgrade/sqliteStateMigration.js +0 -918
  225. package/dist/storage/upgrade/switchProgress.js +0 -80
  226. package/dist/storage/upgrade/upgradeReceipt.js +0 -161
  227. package/dist/storage/upgradeCoordination.js +0 -187
  228. package/dist/storage/upgradeFence.js +0 -366
  229. package/dist/telemetry/telemetryCompaction.js +0 -252
@@ -1,16 +1,19 @@
1
- import { createAgentRun } from "../run/agentRun.js";
2
- import { createRunAssignment, serializeRunBootstrapEnvelope } from "../context/runContextContract.js";
3
- import { formatAgentRunReceiptId } from "../task/taskRecordReference.js";
1
+ import { createTurnInput } from "../context/turnInputContract.js";
4
2
  import { effectiveLaunchSnapshotsCompatible, effectiveLaunchSnapshotsCompatibleForTaskSession } from "../executor/effectiveLaunch.js";
5
3
  import { roleAgentSessionResumeMode } from "../executor/agentExecutor.js";
6
- import { RuntimeLifecycleBusyError } from "../runtime/lifecycleReservation.js";
4
+ import { createTurn } from "../turn/turn.js";
7
5
  import { isSchedulerTaskWorkspaceReady } from "./ports.js";
8
- import { RuntimeLaunchError } from "../runtime/ports.js";
9
- import { serializeAgentErrorRaw } from "../runtime/agentError.js";
6
+ export const LEADER_WAKE_AGGREGATION_MS = 60_000;
7
+ export const LEADER_WAKE_FORCE_MS = 10 * 60_000;
8
+ /**
9
+ * Materializes durable Leader intent as a Turn. Provider lifecycle and
10
+ * Turn submission are handled by processActiveRoleTurnDeliveries, so every
11
+ * Role follows one launch, serialization, and failure path.
12
+ */
10
13
  export async function processLeaderWakeups(store, delivery, now, selection) {
11
14
  const results = [];
12
15
  const wakeups = selection === undefined || selection.full
13
- ? store.listPendingWakeups().filter((wakeup) => (!selection?.blockedTaskIds?.has(wakeup.taskId)))
16
+ ? store.listPendingWakeups().filter((wakeup) => !selection?.blockedTaskIds?.has(wakeup.taskId))
14
17
  : [...selection.taskIds].flatMap((taskId) => {
15
18
  if (selection.blockedTaskIds?.has(taskId))
16
19
  return [];
@@ -27,8 +30,7 @@ export async function processLeaderWakeups(store, delivery, now, selection) {
27
30
  results.push({ taskId: wakeup.taskId, status: "skipped", reason: "unavailable" });
28
31
  continue;
29
32
  }
30
- const taskWorkspace = store.getTaskWorkspace(task.id);
31
- if (!isSchedulerTaskWorkspaceReady(task, taskWorkspace)) {
33
+ if (!isSchedulerTaskWorkspaceReady(task, store.getTaskWorkspace(task.id))) {
32
34
  results.push({ taskId: task.id, status: "skipped", reason: "workspace-not-ready" });
33
35
  continue;
34
36
  }
@@ -36,485 +38,185 @@ export async function processLeaderWakeups(store, delivery, now, selection) {
36
38
  results.push({ taskId: task.id, status: "skipped", reason: "waiting-input" });
37
39
  continue;
38
40
  }
39
- // This check deliberately precedes every tmux operation. A pending wake is
40
- // durable state, not text that may be injected into a busy Agent process.
41
- if (store.getActiveAgentRun(task.id, role.name) !== null) {
42
- results.push({ taskId: task.id, status: "skipped", reason: "busy" });
41
+ const waitedMs = Math.max(0, now.getTime() - Date.parse(wakeup.firstRequestedAt));
42
+ if (waitedMs < LEADER_WAKE_AGGREGATION_MS) {
43
+ results.push({ taskId: task.id, status: "skipped", reason: "aggregating" });
43
44
  continue;
44
45
  }
45
- // A durable wake is already the complete next intent. Do not materialize
46
- // another Run while the Provider still owns the previous Turn: doing so
47
- // would make its late terminal compete with a newer Run generation.
48
- if (providerTurnIsUnsettled(store, task.id, role.name)) {
46
+ const active = store.getActiveTurn(task.id, role.name);
47
+ const leaderTarget = { kind: "role", taskId: task.id, roleName: role.name };
48
+ const claimedSteer = store.getWorkMailbox(leaderTarget)?.processing;
49
+ if (claimedSteer?.owner.startsWith("leader-steer:") === true
50
+ && (active === null || claimedSteer.owner !== `leader-steer:${active.id}`)) {
51
+ store.releaseWorkMailbox(leaderTarget, claimedSteer.batchId);
52
+ results.push({ taskId: task.id, status: "skipped", reason: "state-changed" });
53
+ continue;
54
+ }
55
+ if (active !== null) {
56
+ if (waitedMs >= LEADER_WAKE_FORCE_MS && active !== null) {
57
+ results.push(await forceLeaderSteer(store, delivery, task.id, role.name, active, now));
58
+ continue;
59
+ }
49
60
  results.push({ taskId: task.id, status: "skipped", reason: "busy" });
50
61
  continue;
51
62
  }
52
- const reopening = wakeup.reasons.includes("task-reopened");
53
- let existingSession = store.getRoleSession(task.id, role.name, reopening ? undefined : role.effective.agentId);
54
- let effectiveSession = existingSession;
55
- let claimed = false;
56
- let deliveryAttempted = false;
57
- let run = null;
58
- let prepared;
59
- let preStartFencePersisted = false;
60
- let mode = "new";
61
63
  try {
62
- const compatibleSession = existingSession !== null
63
- && (reopening
64
- ? effectiveLaunchSnapshotsCompatible(existingSession.effective, role.effective)
65
- : effectiveLaunchSnapshotsCompatibleForTaskSession(existingSession.effective, role.effective));
66
- const reopenIdentityDrift = reopening
67
- && hasNativeSession(existingSession)
68
- && !compatibleSession;
69
- if (hasNativeSession(existingSession) && !compatibleSession
70
- && !reopenIdentityDrift
71
- && existingSession.status === "active") {
64
+ const reopening = wakeup.reasons.includes("task-reopened");
65
+ const existingSession = store.getRoleSession(task.id, role.name, reopening ? undefined : role.effective.agentId);
66
+ const compatible = existingSession !== null && (reopening
67
+ ? effectiveLaunchSnapshotsCompatible(existingSession.effective, role.effective)
68
+ : effectiveLaunchSnapshotsCompatibleForTaskSession(existingSession.effective, role.effective));
69
+ if (hasNativeSession(existingSession)
70
+ && existingSession.status === "active"
71
+ && !compatible
72
+ && !reopening) {
72
73
  throw new Error(`Leader Session is incompatible with desired effective launch: ${task.id}/${role.name}.`);
73
74
  }
74
- const resumableSession = hasNativeSession(existingSession)
75
- && existingSession.status === "active";
76
- // Reuse a healthy Session. A terminal Session is disposable and the next
77
- // Run starts fresh without consulting historical recovery records.
78
75
  const sessionSet = store.getTaskRoleSessionSet?.(task.id, role.name) ?? null;
79
- mode = reopenIdentityDrift
76
+ const mode = reopening && !compatible
80
77
  ? "new"
81
78
  : sessionSet === null
82
- ? resumableSession && compatibleSession ? "resume" : "new"
79
+ ? hasNativeSession(existingSession) && existingSession.status === "active" && compatible
80
+ ? "resume"
81
+ : "new"
83
82
  : roleAgentSessionResumeMode(sessionSet, role.effective.agentId, role.effective);
84
- const runId = store.peekNextAgentRunId(task.id);
85
- const wakeEnvelope = resolveLeaderWakeEnvelope(store, task.id);
83
+ const turnId = store.peekNextTurnId(task.id);
86
84
  const contextSnapshot = store.freezeLeaderContextSnapshot?.(task.id, role.name, now);
87
- const assignment = createRunAssignment({
88
- runId,
89
- roleName: role.name,
90
- purpose: "execution",
91
- action: "leader-wake",
92
- subject: { taskId: task.id },
93
- directive: leaderWakeupInput(task.id, runId, wakeup.reasons),
85
+ const input = createTurnInput({
86
+ source: leaderWakeInputSource(wakeup.reasons),
87
+ directive: [
88
+ `Wake reasons: ${wakeup.reasons.join(", ")}.`,
89
+ `Load exact context for ${task.id}/${turnId}.`,
90
+ "Before ending this Turn, judge the affected WorkItems and Task; persist any lifecycle or result changes you establish.",
91
+ "The final Turn response is evidence only and never updates WorkItem or Task state by itself."
92
+ ].join(" "),
94
93
  ...(contextSnapshot === undefined ? {} : { contextSnapshotRef: contextSnapshot.ref }),
95
94
  deltaRefIds: contextSnapshot?.deltaRefIds ?? []
96
95
  });
97
- run = createAgentRun(runId, task.id, role.name, mode, assignment, now, {
98
- ...(role.managedWorkspace === undefined
99
- ? {}
100
- : { workspace: role.managedWorkspace }),
96
+ const turn = createTurn(turnId, task.id, role.name, mode, input, now, {
97
+ ...(role.managedWorkspace === undefined ? {} : { workspace: role.managedWorkspace }),
101
98
  effective: role.effective
102
99
  });
100
+ const envelope = store.getTaskWakeEnvelope?.(task.id) ?? null;
103
101
  const claim = store.saveLeaderDispatch({
104
102
  task,
105
103
  role,
106
- run,
107
- // An incompatible reopened generation is intentionally not rebound to
108
- // a new native host. Keep the old fixed Session as evidence while
109
- // claiming a short-lived Run so the existing failure path can record a
110
- // durable, retryable recovery obligation before any provider call.
111
- session: reopenIdentityDrift ? null : existingSession,
104
+ turn,
105
+ session: reopening && !compatible ? null : existingSession,
112
106
  wakeup,
113
- ...(wakeEnvelope === null ? {} : {
114
- wakeId: wakeEnvelope.wakeId,
115
- wakeFromCursor: wakeEnvelope.fromCursor
107
+ ...(envelope === null ? {} : {
108
+ wakeId: envelope.wakeId,
109
+ wakeFromCursor: envelope.fromCursor
116
110
  }),
117
111
  now
118
112
  });
119
- if (claim !== "claimed") {
120
- results.push({ taskId: task.id, status: "skipped", reason: claim });
121
- continue;
122
- }
123
- claimed = true;
124
- if (reopenIdentityDrift) {
125
- throw new Error(`Leader reopen refused after effective launch identity drift: ${task.id}/${role.name}.`);
126
- }
127
- prepared = await delivery.prepareRoleSession({
128
- taskId: task.id,
129
- roleName: role.name,
130
- agentId: role.effective.agentId,
131
- adapterId: role.effective.adapterId,
132
- effective: role.effective,
133
- workspace: role.effective.workspace.root,
134
- ...(run.workspace === undefined
135
- ? {}
136
- : { managedWorkspace: run.workspace }),
137
- mode,
138
- runId: run.id,
139
- ...(mode === "resume" ? { nativeSessionId: existingSession.nativeSessionId } : {}),
140
- ...(mode !== "resume" || existingSession?.launchId === undefined
141
- ? {}
142
- : { hostActivationId: existingSession.launchId }),
143
- beforeHostStart: (preflight) => {
144
- persistPreStartFence(store, task, role, run, existingSession, mode, preflight, now);
145
- effectiveSession = preflightSession(role, run.effective, existingSession, mode, preflight);
146
- preStartFencePersisted = true;
147
- }
148
- });
149
- // Persist the Session identity before submitting the Leader Turn.
150
- // Starting/restoring a Session is independent from sending Run input.
151
- if (!preStartFencePersisted) {
152
- const preparedFenceSession = prepared.session === undefined
153
- ? existingSession
154
- : validateReadySession(role, run.effective, existingSession, mode, prepared.session);
155
- effectiveSession = preparedFenceSession;
156
- store.saveRoleRunPrepared({
157
- task,
158
- role,
159
- run,
160
- session: preparedFenceSession,
161
- ...(prepared.launchId === undefined ? {} : { launchId: prepared.launchId }),
162
- now
163
- });
164
- }
165
- const ready = await delivery.waitUntilReady(prepared);
166
- const latestTask = store.getTask(task.id);
167
- if (latestTask === null
168
- || latestTask.status !== "active"
169
- || latestTask.executionGate.state !== "enabled") {
170
- delivery.forgetPrepared?.({
171
- taskId: task.id,
172
- roleName: role.name,
173
- runId: run.id,
174
- ...(prepared.launchId === undefined
175
- ? {}
176
- : { launchId: prepared.launchId })
177
- });
178
- results.push({ taskId: task.id, status: "skipped", reason: "unavailable" });
179
- continue;
180
- }
181
- effectiveSession = validateReadySession(role, run.effective, existingSession, mode, ready.session);
182
- store.saveRoleRunPrepared({
183
- task,
184
- role,
185
- run,
186
- session: effectiveSession,
187
- ...(ready.prepared.launchId === undefined
188
- ? {}
189
- : { launchId: ready.prepared.launchId }),
190
- now
191
- });
192
- deliveryAttempted = true;
193
- const outcome = await delivery.sendOnce({
194
- delivery: ready,
195
- receiptId: formatAgentRunReceiptId(task.id, run.id),
196
- text: serializeRunBootstrapEnvelope(run.bootstrapEnvelope)
197
- });
198
- if (outcome === "busy" || outcome === "unavailable") {
199
- delivery.forgetPrepared?.({
200
- taskId: task.id,
201
- roleName: role.name,
202
- runId: run.id,
203
- ...(ready.prepared.launchId === undefined
204
- ? {}
205
- : { launchId: ready.prepared.launchId })
206
- });
207
- results.push({
208
- taskId: task.id,
209
- runId: run.id,
210
- status: "skipped",
211
- reason: outcome === "busy" ? "busy" : "not-ready"
212
- });
213
- continue;
214
- }
215
- if (outcome === "rejected") {
216
- const terminal = store.saveRoleRunDeliveryFailure({
217
- taskId: task.id,
218
- roleName: role.name,
219
- agentId: run.effective.agentId,
220
- adapterId: run.effective.adapterId,
221
- runId: run.id,
222
- mailboxBatchId: formatAgentRunReceiptId(task.id, run.id),
223
- ...(effectiveSession?.nativeSessionId === undefined
224
- ? {}
225
- : { nativeSessionId: effectiveSession.nativeSessionId }),
226
- ...(ready.prepared.launchId === undefined
227
- ? {}
228
- : { launchId: ready.prepared.launchId }),
229
- summary: "Provider rejected the Leader Turn before acceptance; Operator recovery is required.",
230
- cleanupRequired: false,
231
- now
232
- });
233
- delivery.forgetPrepared?.({
234
- taskId: task.id,
235
- roleName: role.name,
236
- runId: run.id,
237
- ...(ready.prepared.launchId === undefined
238
- ? {}
239
- : { launchId: ready.prepared.launchId })
240
- });
241
- results.push({
242
- taskId: task.id,
243
- runId: run.id,
244
- status: terminal === "failed" ? "failed" : "skipped",
245
- reason: "provider-rejected",
246
- ...(terminal === "failed" ? { terminalized: true } : {})
247
- });
248
- continue;
249
- }
250
- if (outcome === "delivery-unknown") {
251
- store.saveRoleRunDelivery({
252
- task,
253
- role,
254
- run,
255
- session: effectiveSession,
256
- ...(ready.prepared.launchId === undefined
257
- ? {}
258
- : { launchId: ready.prepared.launchId }),
259
- now
260
- });
261
- delivery.forgetPrepared?.({
262
- taskId: task.id,
263
- roleName: role.name,
264
- runId: run.id,
265
- ...(ready.prepared.launchId === undefined
266
- ? {}
267
- : { launchId: ready.prepared.launchId })
268
- });
269
- results.push({
270
- taskId: task.id,
271
- runId: run.id,
272
- status: "skipped",
273
- reason: "delivery-uncertain"
274
- });
275
- continue;
276
- }
277
- store.saveRoleRunDelivery({
278
- task,
279
- role,
280
- run,
281
- session: effectiveSession,
282
- ...(ready.prepared.launchId === undefined
283
- ? {}
284
- : { launchId: ready.prepared.launchId }),
285
- now
286
- });
287
- delivery.forgetPrepared?.({
288
- taskId: task.id,
289
- roleName: role.name,
290
- runId: run.id,
291
- ...(ready.prepared.launchId === undefined
292
- ? {}
293
- : { launchId: ready.prepared.launchId })
294
- });
295
- results.push({ taskId: task.id, runId: run.id, status: "dispatched" });
296
- continue;
113
+ results.push(claim === "claimed"
114
+ ? { taskId: task.id, turnId: turn.id, status: "dispatched" }
115
+ : { taskId: task.id, status: "skipped", reason: claim });
297
116
  }
298
117
  catch (error) {
299
- const detail = error instanceof Error ? error.message : String(error);
300
- const message = `Leader dispatch failed: ${detail}`;
301
- if (claimed && run !== null) {
302
- // Scheduler single-flight backpressure: the Role runtime lifecycle
303
- // lane was busy when the launch was reserved. The claimed Run stays
304
- // active-unpushed; the active-Run delivery path retries it after the
305
- // lane settles. This is contention before any semantic launch, not a
306
- // Run failure and not grounds for a Leader failure record.
307
- if (error instanceof RuntimeLifecycleBusyError) {
308
- delivery.forgetPrepared?.({
309
- taskId: task.id,
310
- roleName: role.name,
311
- runId: run.id
312
- });
313
- results.push({
314
- taskId: task.id,
315
- runId: run.id,
316
- status: "skipped",
317
- reason: "not-ready",
318
- error: message
319
- });
320
- continue;
321
- }
322
- store.recordAgentError?.({
323
- taskId: task.id,
324
- roleName: role.name,
325
- runId: run.id,
326
- source: error instanceof RuntimeLaunchError ? "host" : "yui",
327
- phase: deliveryAttempted
328
- ? "turn-submit"
329
- : mode === "new" ? "session-start" : "session-restore",
330
- message: detail,
331
- raw: serializeAgentErrorRaw(error),
332
- inputDisposition: deliveryAttempted ? "unknown" : "not-accepted"
333
- }, now);
334
- if (!deliveryAttempted) {
335
- const failedSession = store.getRoleSession(task.id, role.name, run.effective.agentId);
336
- const terminal = store.saveRoleRunDeliveryFailure({
337
- taskId: task.id,
338
- roleName: role.name,
339
- agentId: run.effective.agentId,
340
- adapterId: run.effective.adapterId,
341
- runId: run.id,
342
- mailboxBatchId: formatAgentRunReceiptId(task.id, run.id),
343
- ...(failedSession?.nativeSessionId === undefined
344
- ? {}
345
- : { nativeSessionId: failedSession.nativeSessionId }),
346
- ...(prepared?.launchId === undefined && failedSession?.launchId === undefined
347
- ? {}
348
- : { launchId: prepared?.launchId ?? failedSession.launchId }),
349
- summary: `Leader Session preparation failed before Turn acceptance: ${message}`,
350
- cleanupRequired: false,
351
- now
352
- });
353
- if (terminal === "failed") {
354
- delivery.forgetPrepared?.({
355
- taskId: task.id,
356
- roleName: role.name,
357
- runId: run.id,
358
- ...(prepared?.launchId === undefined
359
- ? {}
360
- : { launchId: prepared.launchId })
361
- });
362
- results.push({
363
- taskId: task.id,
364
- runId: run.id,
365
- status: "failed",
366
- reason: "unavailable",
367
- error: message,
368
- terminalized: true
369
- });
370
- continue;
371
- }
372
- }
373
- // A managed Provider lifecycle operation may still be settling. Keep
374
- // this newly-claimed Run as the sole mailbox owner; active-Run delivery
375
- // retries after the lifecycle lane clears.
376
- if (error instanceof RuntimeLaunchError && error.retryable) {
377
- results.push({
378
- taskId: task.id,
379
- runId: run.id,
380
- status: "skipped",
381
- reason: error.reason === "writable-client"
382
- ? "writer-attached"
383
- : "not-ready",
384
- error: message
385
- });
386
- continue;
387
- }
388
- // Once delivery begins, a send may have succeeded even when receipt
389
- // observation or the aggregate write failed. Preserve the exact
390
- // durable Run and let receipt-backed active delivery recover it.
391
- if (deliveryAttempted) {
392
- results.push({
393
- taskId: task.id,
394
- runId: run.id,
395
- status: "failed",
396
- reason: "delivery-uncertain",
397
- error: message
398
- });
399
- continue;
400
- }
401
- delivery.forgetPrepared?.({
402
- taskId: task.id,
403
- roleName: role.name,
404
- runId: run.id,
405
- ...(prepared?.launchId === undefined
406
- ? {}
407
- : { launchId: prepared.launchId })
408
- });
409
- results.push({
410
- taskId: task.id,
411
- runId: run.id,
412
- status: "skipped",
413
- reason: "not-ready",
414
- error: message
415
- });
416
- continue;
417
- }
418
118
  results.push({
419
119
  taskId: task.id,
420
- runId: run?.id,
421
120
  status: "failed",
422
121
  reason: "not-ready",
423
- error: message
122
+ error: error instanceof Error ? error.message : String(error)
424
123
  });
425
124
  }
426
125
  }
427
126
  return results;
428
127
  }
429
- function providerTurnIsUnsettled(store, taskId, roleName) {
430
- if (store.getRoleSession(taskId, roleName)?.status !== "active")
431
- return false;
432
- const status = store.getTaskRoleSessionSet?.(taskId, roleName)
433
- ?.providerBinding?.turn?.status;
434
- return status !== undefined
435
- && ["submitting", "accepted", "running", "delivery-unknown"].includes(status);
436
- }
437
- function validateReadySession(role, effective, existing, mode, session) {
438
- if (mode === "new" && session === null)
439
- return null;
440
- if (session === null)
441
- throw new Error("Leader resume returned no fixed native session.");
442
- if (session.agentId !== effective.agentId || session.adapterId !== effective.adapterId) {
443
- throw new Error(`Ready session belongs to another Agent: ${session.agentId}.`);
128
+ function leaderWakeInputSource(reasons) {
129
+ if (reasons.length > 0 && reasons.every((reason) => reason === "user-message")) {
130
+ return { type: "yui", channel: "user-message" };
444
131
  }
445
- const compatible = mode === "resume"
446
- ? effectiveLaunchSnapshotsCompatibleForTaskSession(session.effective, effective)
447
- : effectiveLaunchSnapshotsCompatible(session.effective, effective);
448
- if (!compatible) {
449
- throw new Error("Ready Leader session effective snapshot changed.");
132
+ if (reasons.length > 0 && reasons.every((reason) => reason.startsWith("input-answered:"))) {
133
+ return { type: "yui", channel: "input-response" };
450
134
  }
451
- if (!hasNativeSession(session)) {
452
- throw new Error("Ready Leader session has no native session id.");
135
+ return { type: "yui", channel: "leader-wakeup" };
136
+ }
137
+ async function forceLeaderSteer(store, delivery, taskId, roleName, active, now) {
138
+ const target = { kind: "role", taskId, roleName };
139
+ const mailbox = store.getWorkMailbox(target);
140
+ const pending = mailbox?.pending;
141
+ const existing = mailbox?.processing;
142
+ if (existing === null || existing === undefined) {
143
+ if (pending === null || pending === undefined) {
144
+ return { taskId, turnId: active.id, status: "skipped", reason: "state-changed" };
145
+ }
453
146
  }
454
- if (mode === "resume" && session.nativeSessionId !== existing?.nativeSessionId) {
455
- throw new Error("Leader resume changed the fixed native session id.");
147
+ const owner = `leader-steer:${active.id}`;
148
+ const batchId = existing?.batchId
149
+ ?? `leader-steer:${encodeURIComponent(taskId)}:${encodeURIComponent(active.id)}:${pending.fromSequence}-${pending.toSequence}`;
150
+ const claim = store.claimWorkMailbox({ target, batchId, owner, now });
151
+ if (claim.status === "empty") {
152
+ return { taskId, turnId: active.id, status: "skipped", reason: "state-changed" };
456
153
  }
457
- return {
458
- ...session,
459
- status: "active",
460
- ...(mode === "resume" && existing !== null
461
- ? { effective: existing.effective }
462
- : {})
463
- };
464
- }
465
- function persistPreStartFence(store, task, role, run, existingSession, mode, preflight, now) {
466
- if (preflight.owner.scope !== "task"
467
- || preflight.owner.taskId !== task.id
468
- || preflight.owner.roleName !== role.name
469
- || preflight.runId !== run.id
470
- || preflight.launchId.trim().length === 0) {
471
- throw new Error(`Pre-start launch fence changed the Leader Run: ${task.id}/${role.name}.`);
154
+ const processing = claim.processing;
155
+ if (processing.batchId !== batchId || processing.owner !== owner) {
156
+ return { taskId, turnId: active.id, status: "skipped", reason: "busy" };
472
157
  }
473
- const session = preflightSession(role, run.effective, existingSession, mode, preflight);
474
- store.saveRoleRunPrepared({
475
- task,
476
- role,
477
- run,
478
- session,
479
- launchId: preflight.launchId,
480
- now
158
+ const sessions = store.getTaskRoleSessionSet?.(taskId, roleName) ?? null;
159
+ const session = sessions?.sessions[active.effective.agentId];
160
+ const binding = sessions?.providerBinding;
161
+ const providerTurn = binding?.turn;
162
+ const authority = binding?.authority;
163
+ if (session?.launchId === undefined || session.nativeSessionId === undefined
164
+ || providerTurn === null || providerTurn === undefined
165
+ || providerTurn.turnId !== active.id || providerTurn.nativeTurnId === undefined
166
+ || providerTurn.status !== "accepted"
167
+ || authority?.owner !== "controller" || authority.holderId === undefined) {
168
+ store.releaseWorkMailbox(target, batchId);
169
+ return { taskId, status: "failed", reason: "not-ready", error: "Active Leader Turn has no steerable Provider fence." };
170
+ }
171
+ const batch = processing.batch;
172
+ const directive = [
173
+ `Aggregated Leader events: ${batch.reasons.join(", ")}.`,
174
+ `The first event arrived at ${batch.firstQueuedAt} and has waited at least 10 minutes while this Leader Turn remained active.`,
175
+ "Process these events now and update durable Task or WorkItem facts when needed.",
176
+ "After the events are handled, continue the work you were doing before this interruption.",
177
+ `Load the current exact context for ${taskId}/${active.id}; the event batch may have grown while this input was delivered.`
178
+ ].join(" ");
179
+ const input = createTurnInput({
180
+ source: { type: "yui", channel: "leader-forced-wakeup" },
181
+ directive,
182
+ deltaRefIds: []
481
183
  });
482
- }
483
- function preflightSession(role, effective, existing, mode, preflight) {
484
- const session = preflight.nativeSessionId === undefined
485
- ? null
486
- : {
487
- agentId: preflight.agentId,
488
- adapterId: preflight.adapterId,
489
- nativeSessionId: preflight.nativeSessionId,
490
- launchId: preflight.launchId,
491
- ...(preflight.sessionTitle === undefined ? {} : { title: preflight.sessionTitle }),
492
- status: "active",
493
- effective: preflight.effective
184
+ const outcome = await delivery.steerOnce({
185
+ taskId,
186
+ roleName,
187
+ agentId: active.effective.agentId,
188
+ adapterId: active.effective.adapterId,
189
+ launchId: session.launchId,
190
+ nativeSessionId: session.nativeSessionId,
191
+ nativeTurnId: providerTurn.nativeTurnId,
192
+ authority: {
193
+ epoch: authority.epoch,
194
+ owner: "controller",
195
+ holderId: authority.holderId
196
+ },
197
+ receiptId: `turn-input:${taskId}/${active.id}/${batchId}`,
198
+ text: directive
199
+ });
200
+ if (outcome !== "sent" && outcome !== "already-sent") {
201
+ if (outcome !== "delivery-unknown")
202
+ store.releaseWorkMailbox(target, batchId);
203
+ return {
204
+ taskId,
205
+ turnId: active.id,
206
+ status: outcome === "busy" ? "skipped" : "failed",
207
+ reason: outcome === "busy" ? "busy" : "not-ready",
208
+ error: outcome
494
209
  };
495
- return validateReadySession(role, effective, existing, mode, session);
210
+ }
211
+ const saved = store.saveLeaderSteer({ taskId, turnId: active.id, batchId, input, now });
212
+ if (saved !== "claimed")
213
+ store.releaseWorkMailbox(target, batchId);
214
+ return saved === "claimed"
215
+ ? { taskId, turnId: active.id, status: "steered" }
216
+ : { taskId, turnId: active.id, status: "skipped", reason: saved };
496
217
  }
497
218
  function hasNativeSession(session) {
498
- return session !== null &&
499
- typeof session.nativeSessionId === "string" &&
500
- session.nativeSessionId.trim().length > 0;
501
- }
502
- /**
503
- * Issue 04 (long-term design): resolves the minimal wake envelope for a
504
- * Leader wake. The envelope carries only the aggregated reason tags, the
505
- * delta window, and read pointers; the Agent reads delta content on demand
506
- * with `yui task wake show`. Returns null when the adapter lacks the
507
- * projection, so the wake falls back to the full `yui task context`
508
- * instruction.
509
- */
510
- function resolveLeaderWakeEnvelope(store, taskId) {
511
- if (typeof store.getTaskWakeEnvelope !== "function")
512
- return null;
513
- return store.getTaskWakeEnvelope(taskId);
514
- }
515
- function leaderWakeupInput(taskId, runId, reasons) {
516
- return [
517
- `For every Leader decision, milestone, or Work Item lifecycle command that is meaningful progress, carry this exact current-turn assertion on that command: YUI_LEADER_ACTION_RUN_ID=${runId} YUI_LEADER_ACTION_RECEIPT_ID=${formatAgentRunReceiptId(taskId, runId)}. The native Session environment may retain an older YUI_RUN_ID/launch; never copy those values, and never reuse this assertion after the turn changes.`,
518
- `Wake reasons: ${reasons.join(", ")}. Load exact context for ${taskId}/${runId}.`
519
- ].join("\n");
219
+ return session !== null
220
+ && typeof session.nativeSessionId === "string"
221
+ && session.nativeSessionId.trim().length > 0;
520
222
  }