@zq-silk/yui 0.15.12 → 0.16.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 (236) hide show
  1. package/ARCHITECTURE.md +3 -2
  2. package/ARCHITECTURE.zh-CN.md +3 -2
  3. package/dist/artifacts/artifactCommitLock.js +16 -55
  4. package/dist/artifacts/taskArtifactRepository.js +3 -3
  5. package/dist/cli/agentConfigurationPicker.js +1 -1
  6. package/dist/cli/commandCatalog.js +57 -41
  7. package/dist/cli/completion.js +18 -18
  8. package/dist/cli/completionWizard.js +1 -1
  9. package/dist/cli/dynamicCompletion.js +1 -1
  10. package/dist/cli/interactionPolicy.js +7 -3
  11. package/dist/cli/invocationAuthority.js +64 -0
  12. package/dist/cli/managedDiagnostics.js +1 -1
  13. package/dist/cli/parseRepeatable.js +35 -0
  14. package/dist/cli/updatePorts.js +26 -5
  15. package/dist/cli.js +1280 -1243
  16. package/dist/commands/agentCommands.js +4 -4
  17. package/dist/commands/capabilityCommands.js +1 -1
  18. package/dist/commands/configCommands.js +6 -57
  19. package/dist/commands/configOverview.js +2 -2
  20. package/dist/commands/durableJobCommands.js +12 -6
  21. package/dist/commands/globalRoleCommands.js +53 -29
  22. package/dist/commands/grantCommands.js +0 -3
  23. package/dist/commands/jobCommands.js +1 -4
  24. package/dist/commands/operatorCommands.js +3 -3
  25. package/dist/commands/projectCommands.js +1 -10
  26. package/dist/commands/resourcesCommands.js +10 -40
  27. package/dist/commands/roleConfiguration.js +2 -6
  28. package/dist/commands/taskActivationCommands.js +4 -8
  29. package/dist/commands/taskAgentCapabilities.js +9 -0
  30. package/dist/commands/taskCommandSupport.js +155 -0
  31. package/dist/commands/taskCommandTypes.js +1 -0
  32. package/dist/commands/taskCommands.js +271 -710
  33. package/dist/commands/taskCompletionGate.js +1 -1
  34. package/dist/commands/taskExecutionCommands.js +1 -1
  35. package/dist/commands/taskFactCommands.js +325 -0
  36. package/dist/commands/taskInputCommands.js +12 -42
  37. package/dist/commands/taskIntegrationCommands.js +17 -57
  38. package/dist/commands/taskNextActionCommand.js +7 -6
  39. package/dist/commands/taskPublicationAdoptCommand.js +3 -3
  40. package/dist/commands/taskPublicationCommands.js +1 -1
  41. package/dist/commands/taskPublicationVerifyCommand.js +1 -1
  42. package/dist/commands/taskRemoteDeliveryCommand.js +2 -94
  43. package/dist/commands/taskUpstreamCommands.js +19 -8
  44. package/dist/commands/workflowCommands.js +6 -1
  45. package/dist/completion/completionInstaller.js +26 -26
  46. package/dist/completion/completionState.js +26 -26
  47. package/dist/completion/fileCompletionManager.js +6 -11
  48. package/dist/config/configCatalog.js +0 -2
  49. package/dist/config/timeZone.js +14 -0
  50. package/dist/config/yuiConfig.js +0 -29
  51. package/dist/context/runContextPack.js +2 -3
  52. package/dist/context/taskCatalog.js +3 -5
  53. package/dist/context/taskContext.js +139 -151
  54. package/dist/context/wakeRunReferences.js +11 -0
  55. package/dist/controller/agentCapabilities.js +58 -0
  56. package/dist/controller/agentRuntimeObserver.js +6 -11
  57. package/dist/controller/clientRuntime.js +22 -14
  58. package/dist/controller/controller.js +9 -22
  59. package/dist/controller/fileSchedulerStoreAdapter.js +117 -212
  60. package/dist/controller/jobClient.js +4 -4
  61. package/dist/controller/jobControl.js +60 -25
  62. package/dist/controller/jobSupervisor.js +4 -4
  63. package/dist/controller/providerRetryDelivery.js +7 -7
  64. package/dist/controller/runtime.js +51 -44
  65. package/dist/controller/runtimeEventProcessor.js +0 -5
  66. package/dist/controller/sessionOwnerReconciliation.js +5 -0
  67. package/dist/controller/taskAgentError.js +35 -0
  68. package/dist/coordination/workMailboxQueue.js +2 -24
  69. package/dist/core/controllerClient.js +2 -2
  70. package/dist/core/fileLockOwner.js +74 -0
  71. package/dist/decision/decision.js +17 -0
  72. package/dist/doctor/doctor.js +18 -9
  73. package/dist/event/taskEvent.js +12 -0
  74. package/dist/execution/workItemExecutionProjection.js +2 -2
  75. package/dist/executor/agentCapabilityConfig.js +43 -0
  76. package/dist/executor/agentConfigurationCatalog.js +49 -11
  77. package/dist/executor/agentExecutor.js +5 -8
  78. package/dist/executor/fileRoleLaunchPlanner.js +11 -53
  79. package/dist/executor/taskAgentCapabilities.js +64 -0
  80. package/dist/integration/deliveryObligation.js +1 -69
  81. package/dist/integration/gitIntegrationService.js +60 -84
  82. package/dist/integration/integrationAttempt.js +3 -14
  83. package/dist/integration/integrationSourceApplication.js +4 -12
  84. package/dist/integration/manifestTags.js +2 -2
  85. package/dist/job/durableJob.js +4 -25
  86. package/dist/job/jobAssignmentScope.js +22 -0
  87. package/dist/job/jobOperation.js +16 -0
  88. package/dist/job/jobRunner.js +2 -1
  89. package/dist/job/stepDirectory.js +15 -0
  90. package/dist/kernel/builtinCapabilities.js +15 -11
  91. package/dist/kernel/kernelPorts.js +1 -17
  92. package/dist/lifecycle/exactRunTerminalization.js +1 -9
  93. package/dist/message/inputControlResolution.js +3 -4
  94. package/dist/message/message.js +17 -20
  95. package/dist/message/messageContinuation.js +1 -1
  96. package/dist/milestone/milestone.js +11 -0
  97. package/dist/observability/orchestrationMetrics.js +24 -1
  98. package/dist/observability/runtimeIdentity.js +1 -17
  99. package/dist/output/agentConfigurationPresentation.js +2 -0
  100. package/dist/output/timePresentation.js +1 -14
  101. package/dist/plugins/pluginService.js +12 -4
  102. package/dist/release/releaseWorkflowPorts.js +45 -96
  103. package/dist/release/runtimeRelease.js +9 -15
  104. package/dist/repository/projectMaintenanceLock.js +23 -64
  105. package/dist/repository/taskBaseFreshness.js +1 -1
  106. package/dist/repository/taskWorkspaceCoordinator.js +13 -22
  107. package/dist/repository/taskWorkspacePreparer.js +29 -25
  108. package/dist/repository/workspaceCleanupInspection.js +5 -5
  109. package/dist/resources/autoResourceGc.js +4 -77
  110. package/dist/resources/liveReferences.js +7 -54
  111. package/dist/resources/resourceDiscovery.js +5 -118
  112. package/dist/resources/resourceGc.js +233 -181
  113. package/dist/resources/resourceRegistrar.js +5 -4
  114. package/dist/resources/resourceRegistry.js +3 -37
  115. package/dist/resources/resourceRegistryStore.js +13 -36
  116. package/dist/resources/sqliteResourceRegistry.js +43 -26
  117. package/dist/review/deltaRecheck.js +1 -1
  118. package/dist/review/reviewAcceptance.js +1 -1
  119. package/dist/review/reviewDecision.js +1 -1
  120. package/dist/review/reviewRound.js +8 -7
  121. package/dist/review/taskFinalReviewContractResolution.js +1 -1
  122. package/dist/runtime/acpProtocol.js +4 -51
  123. package/dist/runtime/acpSession.js +10 -75
  124. package/dist/runtime/acpSessionConfiguration.js +1 -8
  125. package/dist/runtime/agentEndpoint.js +3 -2
  126. package/dist/runtime/agentFailureContext.js +43 -0
  127. package/dist/runtime/agentHost.js +15 -15
  128. package/dist/runtime/codexInteractiveHost.js +2 -2
  129. package/dist/runtime/index.js +0 -1
  130. package/dist/runtime/managedCaller.js +21 -1
  131. package/dist/runtime/providerErrors.js +38 -0
  132. package/dist/runtime/runtimeObservation.js +0 -5
  133. package/dist/runtime/runtimeSessionCandidate.js +2 -3
  134. package/dist/runtime/sessionOwnerIdentity.js +1 -1
  135. package/dist/runtime/structuredProviderHost.js +4 -41
  136. package/dist/runtime/tmuxAdapters.js +6 -8
  137. package/dist/scheduler/activeRoleRunDelivery.js +7 -7
  138. package/dist/scheduler/leaderWakeupProcessor.js +19 -6
  139. package/dist/scheduler/operatorEvent.js +12 -20
  140. package/dist/scheduler/operatorInputNotificationProcessor.js +1 -1
  141. package/dist/scheduler/ports.js +5 -8
  142. package/dist/scheduler/roleRunLiveness.js +4 -4
  143. package/dist/scheduler/roleRunStall.js +16 -23
  144. package/dist/scheduler/taskExecutionProjection.js +25 -33
  145. package/dist/scheduler/taskObservabilityProjection.js +0 -1
  146. package/dist/scheduler/taskWake.js +5 -10
  147. package/dist/scheduler/wakeReason.js +2 -0
  148. package/dist/scheduler/wakeupQueue.js +2 -4
  149. package/dist/setup/setupCommand.js +1 -1
  150. package/dist/storage/contextRecords.js +106 -0
  151. package/dist/storage/currentTaskStore.js +1 -1
  152. package/dist/storage/homeLayout.js +13 -17
  153. package/dist/storage/migrations/agentFailureContext.js +22 -0
  154. package/dist/storage/migrations/currentInputContract.js +86 -0
  155. package/dist/storage/migrations/currentRuntimeContract.js +228 -0
  156. package/dist/storage/migrations/historicalVerificationPlan.js +35 -0
  157. package/dist/storage/migrations/integrationContinuation.js +5 -4
  158. package/dist/storage/migrations/narrowAgentFailureContext.js +65 -0
  159. package/dist/storage/migrations/notificationOnlyWakes.js +74 -0
  160. package/dist/storage/migrations/verificationPlanV1.js +162 -0
  161. package/dist/storage/migrations/verificationPolicy.js +74 -0
  162. package/dist/storage/migrations/workItemHistory.js +46 -0
  163. package/dist/storage/persistenceWorker.js +12 -4
  164. package/dist/storage/recordValidation.js +87 -0
  165. package/dist/storage/sqliteSchema.js +142 -1
  166. package/dist/storage/sqliteStore.js +156 -162
  167. package/dist/storage/storageSchema.js +2 -14
  168. package/dist/storage/storageVersions.js +1 -1
  169. package/dist/storage/storeRpc.js +15 -7
  170. package/dist/storage/taskStore.js +3 -7
  171. package/dist/storage/upgrade/upgradeOrchestrator.js +25 -37
  172. package/dist/task/completionReadiness.js +5 -24
  173. package/dist/task/draftPlan.js +0 -53
  174. package/dist/task/nextAction.js +7 -13
  175. package/dist/task/remoteDeliveryService.js +89 -0
  176. package/dist/task/taskActivation.js +2 -25
  177. package/dist/task/taskActivationService.js +0 -2
  178. package/dist/task/taskRecordReference.js +0 -1
  179. package/dist/task/taskSubmission.js +3 -14
  180. package/dist/telemetry/sqliteTelemetryBatch.js +29 -0
  181. package/dist/telemetry/sqliteTelemetryStore.js +49 -49
  182. package/dist/telemetry/telemetryWiring.js +4 -3
  183. package/dist/tmux/tmuxManager.js +29 -20
  184. package/dist/verification/gateArtifact.js +15 -24
  185. package/dist/verification/gateArtifactStore.js +14 -7
  186. package/dist/verification/verificationGateService.js +29 -88
  187. package/dist/verification/verificationPlan.js +27 -72
  188. package/dist/web/assets/client/app.js +1 -1
  189. package/dist/web/assets/client/taskSurface.js +4 -4
  190. package/dist/web/webServer.js +4 -6
  191. package/dist/web/webSnapshot.js +3 -83
  192. package/dist/web/webTaskSurface.js +17 -46
  193. package/dist/workItem/workItem.js +1 -12
  194. package/docs/architecture/capabilities-and-resources.md +14 -2
  195. package/docs/architecture/capabilities-and-resources.zh-CN.md +11 -1
  196. package/docs/managed-turn-and-session-runtime.md +83 -10
  197. package/docs/managed-turn-and-session-runtime.zh-CN.md +65 -11
  198. package/docs/plugin-sdk.md +4 -2
  199. package/docs/provider-runtime.md +3 -1
  200. package/docs/provider-runtime.zh-CN.md +4 -2
  201. package/docs/release-workflow.md +190 -7
  202. package/docs/release-workflow.zh-CN.md +139 -5
  203. package/docs/roles-and-configuration.md +29 -0
  204. package/docs/roles-and-configuration.zh-CN.md +21 -0
  205. package/docs/sqlite-control-plane-design.md +16 -3
  206. package/docs/sqlite-control-plane-design.zh-CN.md +13 -2
  207. package/docs/task-dag-semantics.md +3 -3
  208. package/docs/task-dag-semantics.zh-CN.md +2 -2
  209. package/docs/task-delivery.md +91 -5
  210. package/docs/task-delivery.zh-CN.md +72 -4
  211. package/docs/task-discovery.md +10 -11
  212. package/docs/task-discovery.zh-CN.md +8 -9
  213. package/docs/testing/verification-levels.md +145 -9
  214. package/docs/testing/verification-levels.zh-CN.md +103 -8
  215. package/package.json +1 -1
  216. package/skills/yui-leader/SKILL.md +5 -0
  217. package/skills/yui-leader/references/integration.md +9 -5
  218. package/skills/yui-leader/references/planning.md +9 -3
  219. package/skills/yui-operator/SKILL.md +15 -10
  220. package/skills/yui-runtime/SKILL.md +6 -6
  221. package/skills/yui-runtime/references/recovery.md +47 -0
  222. package/dist/agent/agentRegistry.js +0 -10
  223. package/dist/agentRun/runIdentity.js +0 -22
  224. package/dist/commands/deliveryGuardPreflight.js +0 -30
  225. package/dist/commands/taskIntegrationQueueCommands.js +0 -226
  226. package/dist/commands/taskOverviewCommand.js +0 -377
  227. package/dist/completion/completionWizard.js +0 -125
  228. package/dist/context/dispatchContext.js +0 -110
  229. package/dist/context/wakeNotification.js +0 -144
  230. package/dist/integration/integrationCheckEvidenceReuse.js +0 -53
  231. package/dist/integration/integrationQueueEntry.js +0 -191
  232. package/dist/integration/integrationQueueService.js +0 -810
  233. package/dist/release/fakeReleasePorts.js +0 -55
  234. package/dist/runtime/sessionOwnerRegistry.js +0 -137
  235. package/dist/task/deliveryGuard.js +0 -226
  236. /package/dist/{commands/taskActor.js → task/taskAuthority.js} +0 -0
@@ -2,18 +2,18 @@ import { spawn } from "node:child_process";
2
2
  import { fileURLToPath } from "node:url";
3
3
  import { findLiveControllerProcessForHome, inspectLiveControllerProcess } from "../core/controllerProcessIdentity.js";
4
4
  import { readHomeFilesystemId } from "../core/homeFilesystemIdentity.js";
5
+ import { AGENT_OPERATIONAL_ENVIRONMENT_NAMES, nativeAgentEnvironmentNames, operationalAgentEnvironment, selectEnvironment, YUI_MANAGED_RUNTIME_ENVIRONMENT_NAMES } from "../agent/launchEnvironment.js";
5
6
  import { callController, ControllerClientError, readControllerDiscovery, stopOrphanedFileTaskController } from "../core/controllerClient.js";
6
7
  import { FILE_TASK_CONTROLLER_PROTOCOL_VERSION } from "../core/protocol.js";
7
- import { AGENT_OPERATIONAL_ENVIRONMENT_NAMES, nativeAgentEnvironmentNames, operationalAgentEnvironment, selectEnvironment, YUI_MANAGED_RUNTIME_ENVIRONMENT_NAMES } from "../agent/launchEnvironment.js";
8
- import { openCurrentTaskStore } from "../storage/currentTaskStore.js";
8
+ import { isForeignHandoverLockHeld } from "../release/runtimeRelease.js";
9
+ import { WorkspaceCleanupBlockedError } from "../repository/taskWorkspacePreparer.js";
9
10
  import { hasRuntimeLifecycleWork } from "../runtime/lifecycleReservation.js";
10
11
  import { assertControllerStatusIdentity } from "../runtime/runtimeCoherence.js";
11
- import { EPHEMERAL_DOMAIN_ENVIRONMENT_NAMES } from "./domainIdentity.js";
12
+ import { CONTROLLER_SHUTDOWN_TIMEOUT_MS, LIFECYCLE_REQUEST_TIMEOUT_MS } from "../runtime/runtimeDeadlines.js";
13
+ import { openCurrentTaskStore } from "../storage/currentTaskStore.js";
12
14
  import { yuiVersionIdentity } from "../version.js";
15
+ import { EPHEMERAL_DOMAIN_ENVIRONMENT_NAMES } from "./domainIdentity.js";
13
16
  import { SessionOwnerReconciliation } from "./sessionOwnerReconciliation.js";
14
- import { WorkspaceCleanupBlockedError } from "../repository/taskWorkspacePreparer.js";
15
- import { CONTROLLER_SHUTDOWN_TIMEOUT_MS, LIFECYCLE_REQUEST_TIMEOUT_MS } from "../runtime/runtimeDeadlines.js";
16
- import { isForeignHandoverLockHeld } from "../release/runtimeRelease.js";
17
17
  const STARTUP_TIMEOUT_MS = 5_000;
18
18
  const POLL_INTERVAL_MS = 50;
19
19
  const ENVIRONMENT_REFRESH_TIMEOUT_MS = 500;
@@ -166,7 +166,7 @@ function assertCompatibleControllerStatus(status, expectedVersion) {
166
166
  });
167
167
  }
168
168
  }
169
- function spawnDetachedFileTaskController(home, environment) {
169
+ function spawnDetachedFileTaskController(_home, environment) {
170
170
  const child = spawn(process.execPath, [fileURLToPath(new URL("./controllerMain.js", import.meta.url))], {
171
171
  env: environment,
172
172
  detached: true,
@@ -439,9 +439,6 @@ export class FileTaskWorkflowRuntime {
439
439
  this.workspacePreparer = workspacePreparer;
440
440
  this.clientOptions = clientOptions;
441
441
  }
442
- notifyStateChanged(taskId) {
443
- void this.notifyMailboxChanged({ kind: "task", taskId });
444
- }
445
442
  notifyMailboxChanged(target) {
446
443
  const pending = callFileTaskController(this.home, "scheduler.signal", { key: controllerMailboxKey(target) }, this.clientOptions).then(() => { });
447
444
  void pending.catch(this.clientOptions.onError ?? (() => { }));
@@ -577,6 +574,17 @@ export class FileTaskWorkflowRuntime {
577
574
  + blockers.map((entry) => (`${entry.owner.roleName}/${entry.nativeSessionId ?? "unknown-session"}`
578
575
  + ` (pid ${entry.physical?.alive === true ? entry.physical.pid : "?"})`)).join("; "));
579
576
  }
577
+ /** Archive cleanup is stronger than a read-only proof that no process is live. */
578
+ async releaseTaskTerminals(taskId) {
579
+ await this.assertTaskPhysicalResourcesReleased(taskId);
580
+ // A Home's server is shared with other Tasks and the Operator. Remove only
581
+ // this Task's session group; exited panes still own scrollback memory.
582
+ await this.tmux.stopTaskAsync(taskId);
583
+ const remaining = this.tmux.inspectTaskRolePanes(taskId);
584
+ if (remaining.length > 0) {
585
+ throw new WorkspaceCleanupBlockedError("physical-resource-live", `task:${taskId}`, true, `Task terminals remain after cleanup: ${remaining.map(pane => pane.roleName).join(", ")}.`);
586
+ }
587
+ }
580
588
  async stopGlobalRoleSession(roleName) {
581
589
  if (this.store.getGlobalRole(roleName) === null) {
582
590
  throw new Error(`Global Role not found: ${roleName}.`);
@@ -678,11 +686,11 @@ function runtimeOwnerLabel(owner) {
678
686
  }
679
687
  const MANAGED_RUNTIME_ENVIRONMENT = new Set(YUI_MANAGED_RUNTIME_ENVIRONMENT_NAMES);
680
688
  function foregroundGlobalRoleEnvironment(store, roleName, source) {
681
- const role = store.getGlobalRole?.(roleName);
682
- if (role === null || role === undefined)
689
+ const role = store.getGlobalRole(roleName);
690
+ if (role === null)
683
691
  return undefined;
684
- const agent = store.getConfiguredAgent?.(role.activeAgentId);
685
- if (agent === null || agent === undefined)
692
+ const agent = store.getConfiguredAgent(role.activeAgentId);
693
+ if (agent === null)
686
694
  return undefined;
687
695
  const declaredSources = new Set(agent.environment.map((binding) => binding.sourceName));
688
696
  for (const name of MANAGED_RUNTIME_ENVIRONMENT)
@@ -16,7 +16,6 @@ import { nearestDeadlineBatch } from "../coordination/deadlineScheduler.js";
16
16
  import { mailboxHasWork, nextPendingBatch } from "../coordination/workMailbox.js";
17
17
  import { hasRuntimeCleanupObligation } from "../runtime/lifecycleReservation.js";
18
18
  import { formatTaskRecordReference } from "../task/taskRecordReference.js";
19
- import { activationRequestIsControllerAdoptable } from "../task/taskActivation.js";
20
19
  import { parseDurableJobAcknowledgeParams, parseDurableJobCancelParams, parseDurableJobRefParams, parseDurableJobStartParams } from "./jobControl.js";
21
20
  const DEFAULT_RECONCILIATION_INTERVAL_MS = reconciliationIntervalMilliseconds();
22
21
  const DEFAULT_SIGNAL_WINDOW_MS = 100;
@@ -217,8 +216,7 @@ async function processSelectedRoleRuntimeCleanups(store, delivery, lifecycleHost
217
216
  if (!await lifecycleHost.stopOwner(owner)) {
218
217
  throw new Error(`Role runtime cleanup could not confirm the host stopped: ${runtimeOwnerLabel(owner)}.`);
219
218
  }
220
- if (store.completeRuntimeCleanup === undefined
221
- || !store.completeRuntimeCleanup(target, now)) {
219
+ if (!store.completeRuntimeCleanup(target, now)) {
222
220
  throw new Error(`Role runtime cleanup mailbox changed: ${runtimeOwnerLabel(owner)}.`);
223
221
  }
224
222
  forgetPreparedRuntimeOwner(delivery, owner);
@@ -252,7 +250,7 @@ function selectedTaskIdsForBoundedPass(store, selection) {
252
250
  return new Set(selectedActiveSchedulerTasks(store, selection).map((task) => task.id));
253
251
  }
254
252
  function selectedReadyWorkMailboxes(store) {
255
- return store.listReadyWorkMailboxes?.() ?? store.listWorkMailboxes();
253
+ return store.listReadyWorkMailboxes();
256
254
  }
257
255
  function selectedRuntimeLifecycleTargets(store, scope, blockedTaskIds = new Set()) {
258
256
  if (scope.kind === "full") {
@@ -456,9 +454,9 @@ export function compileReconcileSelection(scope) {
456
454
  };
457
455
  }
458
456
  /**
459
- * Adopts activation requests whose deferral has been released.
457
+ * Continues admitted activation requests once their execution boundaries allow it.
460
458
  *
461
- * The deferral is only ever released by the planning Turn ending, so this phase
459
+ * A planning deferral is released only by its Turn ending. This phase therefore
462
460
  * re-reads the request instead of trusting the signal that woke it: a request
463
461
  * cancelled, or a Task retired or stopped, while the Turn was still running is
464
462
  * left alone rather than replayed as historical intent. Adoption itself belongs
@@ -475,10 +473,7 @@ async function adoptReleasedTaskActivations(store, workspace, selection, onError
475
473
  if (workspace === undefined)
476
474
  return;
477
475
  const candidates = selection.full
478
- ? (store.listPendingActivationRequestTaskIds?.()
479
- ?? store.listTasks().flatMap((task) => (task.status === "draft" && task.activationRequest?.disposition === "pending"
480
- ? [task.id]
481
- : [])))
476
+ ? store.listPendingActivationRequestTaskIds()
482
477
  : selection.taskIds;
483
478
  for (const taskId of candidates) {
484
479
  if (selection.blockedTaskIds?.has(taskId))
@@ -489,20 +484,14 @@ async function adoptReleasedTaskActivations(store, workspace, selection, onError
489
484
  const request = task.activationRequest;
490
485
  if (request?.disposition !== "pending")
491
486
  continue;
492
- // Continue only requests with a provable source: a released deferral, or an
493
- // immediate request carrying a recognised origin (explicit action, or a
494
- // develop submission accepted while unplanned). This admits legal
495
- // Operator/user immediate requests the old actor filter dropped, while
496
- // still refusing origin-less historical requests, which stay behind the
497
- // explicit `yui task activate` boundary.
498
- if (!activationRequestIsControllerAdoptable(request))
499
- continue;
487
+ // The request was authorized at admission. Recheck its current execution
488
+ // and Session boundaries without a second provenance-based workflow gate.
500
489
  // Later Draft discussion is a Session notification, not another AgentRun.
501
490
  // Its durable activation intent is sufficient once the exact native input
502
491
  // is settled. Never create a synthetic Run merely to release that intent.
503
492
  if (store.getActiveRun(taskId, "leader") !== null)
504
493
  continue;
505
- const provider = store.getTaskRoleSessionSet?.(taskId, "leader")?.providerBinding;
494
+ const provider = store.getTaskRoleSessionSet(taskId, "leader")?.providerBinding;
506
495
  if (provider?.run != null
507
496
  && ["submitting", "accepted", "delivery-unknown"].includes(provider.run.status))
508
497
  continue;
@@ -663,7 +652,6 @@ function mergeControllerSchedulerResults(results) {
663
652
  export class FileTaskController {
664
653
  store;
665
654
  delivery;
666
- #startedAt;
667
655
  #intervalMs;
668
656
  #now;
669
657
  #onError;
@@ -724,7 +712,6 @@ export class FileTaskController {
724
712
  this.delivery = delivery;
725
713
  this.#intervalMs = positiveInteger(options.intervalMs, DEFAULT_RECONCILIATION_INTERVAL_MS, "Controller reconciliation interval");
726
714
  this.#now = options.now ?? (() => new Date());
727
- this.#startedAt = this.#now();
728
715
  this.#onError = options.onError ?? (() => { });
729
716
  const workspacePreparer = options.workspacePreparer;
730
717
  this.#workspacePreparer = workspacePreparer === undefined ? undefined : {
@@ -1649,7 +1636,7 @@ export async function startFileTaskController(home, store, delivery, dispatcher,
1649
1636
  }
1650
1637
  if (method === "job.get") {
1651
1638
  const ref = parseDurableJobRefParams(params);
1652
- const job = control.getJob(ref.taskId, ref.jobId);
1639
+ const job = control.getJob(ref.taskId, ref.jobId, ref.caller);
1653
1640
  if (job === null) {
1654
1641
  throw controllerApplicationError("NOT_FOUND", `DurableJob not found: ${ref.taskId}/${ref.jobId}.`);
1655
1642
  }
@@ -1,47 +1,46 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
- import { prepareMessageContinuations, interruptThenTerminalState } from "../message/messageContinuation.js";
3
- import { freezeRunContextSnapshot } from "../context/runContextPack.js";
4
- import { contextSnapshotRef } from "../context/contextSnapshot.js";
5
2
  import { isDeepStrictEqual } from "node:util";
3
+ import { contextSnapshotRef } from "../context/contextSnapshot.js";
4
+ import { freezeRunContextSnapshot } from "../context/runContextPack.js";
5
+ import { settleGlobalRetryInput } from "../message/globalProviderRetry.js";
6
+ import { interruptThenTerminalState, prepareMessageContinuations } from "../message/messageContinuation.js";
6
7
  import { assertExecutionEnvironmentCurrent } from "../runtime/executionEnvironment.js";
7
- import { resolveAgentHostObservation, recordAgentHostConnection, AgentHostObservationDeferred } from "./agentHostObservation.js";
8
- import { RuntimeHookRunFenceError } from "./runtimeHookRunFence.js";
8
+ import { deferProviderRetry, providerRetryPending, recordProviderFailure } from "../runtime/providerRetry.js";
9
+ import { AgentHostObservationDeferred, recordAgentHostConnection, resolveAgentHostObservation } from "./agentHostObservation.js";
9
10
  import { admitOwnedProviderInput, ownedProviderInput } from "./providerRetryAdmission.js";
10
- import { recordProviderFailure, providerRetryPending, deferProviderRetry } from "../runtime/providerRetry.js";
11
- import { settleGlobalRetryInput } from "../message/globalProviderRetry.js";
12
- import { activeLiveRoleAgentSession, bindTaskRoleProviderRuntime, bindGlobalRoleProviderRuntime, createRoleSessionSet, recordRoleAgentSession, replaceTaskRoleAgentSession, recordTaskRoleNativeTurnBoundary, rememberRoleAgentCompletedTurn, detachRoleAgentSessionHost, updateRoleAgentSessionStatus, updateTaskRoleProviderRuntime, updateGlobalRoleProviderRuntime, selectNewTaskRoleSession, taskRoleControlTarget } from "../executor/agentExecutor.js";
13
- import { acceptProviderTurn, cancelQuiescentProviderInput, beginProviderTurn, createProviderRuntimeBinding, currentProviderConversation, managedProviderTurnId, clearProviderGoal, settleProviderTurnSubmission, settleProviderTurn, transferProviderAuthority, supersedeProviderConversation, updateProviderConversationRecoverability, updateProviderGoal } from "../runtime/providerRuntimeIdentity.js";
11
+ import { RuntimeHookRunFenceError } from "./runtimeHookRunFence.js";
12
+ import { appendRunInput, createRun, runPurposeAdmitsTaskState, withRunContextSnapshot } from "../agentRun/agentRun.js";
13
+ import { createRunInput } from "../context/runInputContract.js";
14
+ import { bindExecution, claimPending, completeProcessing, consumePendingBatch, mailboxHasWork, releaseProcessing } from "../coordination/workMailbox.js";
15
+ import { enqueueWork, settleRoleRunDispatch as settleRoleRunDispatchMailbox } from "../coordination/workMailboxQueue.js";
16
+ import { transportAgentResult } from "../domain/agentResultTransport.js";
14
17
  import { createTaskEvent } from "../event/taskEvent.js";
15
- import { operationalTaskRecords } from "../task/taskRecordRetirement.js";
16
- import { buildTaskWakeEnvelope } from "../context/wakeNotification.js";
17
- import { createTaskWake, fallbackWakeCursor, latestTaskWake, markTaskWakeConsumed } from "../scheduler/taskWake.js";
18
+ import { activeLiveRoleAgentSession, bindGlobalRoleProviderRuntime, bindTaskRoleProviderRuntime, createRoleSessionSet, detachRoleAgentSessionHost, recordRoleAgentSession, recordTaskRoleNativeTurnBoundary, rememberRoleAgentCompletedTurn, replaceTaskRoleAgentSession, selectNewTaskRoleSession, taskRoleControlTarget, updateGlobalRoleProviderRuntime, updateRoleAgentSessionStatus, updateTaskRoleProviderRuntime } from "../executor/agentExecutor.js";
19
+ import { effectiveLaunchWithTaskMainWorkspace, resolveEffectiveLaunch, roleSessionMayContinue, validateEffectiveLaunchSnapshot } from "../executor/effectiveLaunch.js";
18
20
  import { answerInputRequest } from "../input/inputRequest.js";
21
+ import { createCanonicalLifecycleEvent, foldCanonicalLifecycleEvent } from "../lifecycle/canonicalLifecycleEvent.js";
22
+ import { cancelQuiescentRoleRuns, terminalizeExactTaskRun } from "../lifecycle/exactRunTerminalization.js";
23
+ import { markGlobalRoleMessageDelivered, markGlobalRoleMessageNotDelivered, recordTaskMessageControlOutcome } from "../message/message.js";
24
+ import { snapshotExecutionLaneWorkspaceSync } from "../repository/executionLaneGitSnapshot.js";
19
25
  import { activeRoleAgentBinding } from "../role/role.js";
20
- import { effectiveLaunchWithTaskMainWorkspace, roleSessionMayContinue, resolveEffectiveLaunch, validateEffectiveLaunchSnapshot } from "../executor/effectiveLaunch.js";
21
26
  import { SYSTEM_OPERATOR_ROLE } from "../role/systemRoles.js";
22
- import { appendRunInput, createRun, withRunContextSnapshot, runPurposeAdmitsTaskState } from "../agentRun/agentRun.js";
23
- import { transportAgentResult } from "../domain/agentResultTransport.js";
24
- import { createRunInput } from "../context/runInputContract.js";
25
- import { recordTaskMessageControlOutcome, markGlobalRoleMessageDelivered, markGlobalRoleMessageNotDelivered } from "../message/message.js";
27
+ import { standardAgentError } from "../runtime/agentError.js";
28
+ import { recordTaskAgentError } from "./taskAgentError.js";
29
+ import { builtinAgentDriverRegistry } from "../runtime/builtinAgentDrivers.js";
30
+ import { RUNTIME_CLEANUP_REQUIRED_REASON, RUNTIME_HOST_DETACH_REQUIRED_REASON, RUNTIME_LIFECYCLE_OWNER, hasRuntimeCleanupObligation, hasRuntimeLifecycleWork, isRuntimeCleanupReason, runtimeCleanupDisposition, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
26
31
  import { classifyRuntimeProcessExit, validateRuntimeProcessExitObservation } from "../runtime/processExitObservation.js";
27
- import { terminalizeExactTaskRun, cancelQuiescentRoleRuns } from "../lifecycle/exactRunTerminalization.js";
28
- import { createCanonicalLifecycleEvent, foldCanonicalLifecycleEvent } from "../lifecycle/canonicalLifecycleEvent.js";
32
+ import { providerContinuationKey } from "../runtime/providerContinuation.js";
33
+ import { acceptProviderTurn, beginProviderTurn, cancelQuiescentProviderInput, clearProviderGoal, createProviderRuntimeBinding, currentProviderConversation, managedProviderTurnId, settleProviderTurn, settleProviderTurnSubmission, supersedeProviderConversation, transferProviderAuthority, updateProviderConversationRecoverability, updateProviderGoal } from "../runtime/providerRuntimeIdentity.js";
34
+ import { projectProviderContinuations } from "../runtime/runtimeContinuationProjection.js";
35
+ import { RUNTIME_OBSERVATION_TASK_EVENT, createRuntimeObservation, isRuntimeTokenEvidence, runtimeObservationFenceMatches, runtimeObservationFromTaskEvent, runtimeObservationRunFenceMatches, runtimeObservationTaskEventPayload } from "../runtime/runtimeObservation.js";
29
36
  import { recordLeaderFailure } from "../scheduler/leaderFailure.js";
30
- import { recordLeaderAttentionRequired, routeRoleEvent } from "../scheduler/operatorEvent.js";
31
- import { queueLeaderWakeup } from "../scheduler/wakeupQueue.js";
37
+ import { enqueueSupervisorEvent, recordLeaderAttentionRequired, routeRoleEvent } from "../scheduler/operatorEvent.js";
38
+ import { RUN_DIAGNOSTIC_FINISHED_EVENT, RUN_PROGRESS_EVENT, RUN_RECOVERED_EVENT, RUN_STALLED_EVENT, foldRunProgressFacts, isRoleRunStalled, latestRunDurableProgressAt, latestRunEventTime, latestStallEvidenceKey } from "../scheduler/roleRunStall.js";
39
+ import { createTaskWake, latestTaskWake, markTaskWakeConsumed } from "../scheduler/taskWake.js";
32
40
  import { wakeReason } from "../scheduler/wakeReason.js";
33
- import { foldRunProgressFacts, latestRunDurableProgressAt, latestRunEventTime, latestStallEvidenceKey, isRoleRunStalled, RUN_PROGRESS_EVENT, RUN_DIAGNOSTIC_FINISHED_EVENT, RUN_RECOVERED_EVENT, RUN_STALLED_EVENT } from "../scheduler/roleRunStall.js";
41
+ import { queueLeaderWakeup } from "../scheduler/wakeupQueue.js";
34
42
  import { pendingWakeupProjection } from "../storage/taskStore.js";
35
- import { projectProviderContinuations } from "../runtime/runtimeContinuationProjection.js";
36
- import { providerContinuationKey } from "../runtime/providerContinuation.js";
37
43
  import { formatRunReceiptId } from "../task/taskRecordReference.js";
38
- import { bindExecution, claimPending, completeProcessing, consumePendingBatch, mailboxHasWork, releaseProcessing } from "../coordination/workMailbox.js";
39
- import { enqueueWork, settleRoleRunDispatch as settleRoleRunDispatchMailbox } from "../coordination/workMailboxQueue.js";
40
- import { RUNTIME_CLEANUP_REQUIRED_REASON, RUNTIME_HOST_DETACH_REQUIRED_REASON, RUNTIME_LIFECYCLE_OWNER, hasRuntimeCleanupObligation, hasRuntimeLifecycleWork, isRuntimeCleanupReason, runtimeCleanupDisposition, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
41
- import { builtinAgentDriverRegistry } from "../runtime/builtinAgentDrivers.js";
42
- import { standardAgentError } from "../runtime/agentError.js";
43
- import { RUNTIME_OBSERVATION_TASK_EVENT, createRuntimeObservation, isRuntimeTokenEvidence, runtimeObservationFenceMatches, runtimeObservationFromTaskEvent, runtimeObservationRunFenceMatches, runtimeObservationTaskEventPayload } from "../runtime/runtimeObservation.js";
44
- import { snapshotExecutionLaneWorkspaceSync } from "../repository/executionLaneGitSnapshot.js";
45
44
  export class AgentHostProviderTurnFenceError extends Error {
46
45
  constructor(message) {
47
46
  super(message);
@@ -740,31 +739,24 @@ export class FileSchedulerStoreAdapter {
740
739
  const run = input.fence.runId === undefined
741
740
  ? null
742
741
  : store.getRun(taskId, input.fence.runId);
743
- const errorEvent = createTaskEvent(store.nextEventId(taskId), taskId, "runtime.agent-error", {
744
- sourceEventId: input.eventId,
745
- observationEventId,
746
- runId: input.fence.runId ?? "",
747
- roleName: input.fence.roleName,
748
- agentId: input.fence.agentId,
749
- adapterId: run?.effective.adapterId ?? "unknown",
750
- driverId: input.fence.driverId,
751
- nativeSessionId: input.fence.nativeSessionId ?? "",
752
- nativeTurnId: input.fence.nativeTurnId ?? "",
753
- source: error.source,
754
- phase: error.phase,
755
- category: error.category,
756
- code: error.code,
757
- message: error.message,
758
- raw: error.raw,
759
- inputDisposition: error.inputDisposition,
760
- sessionDisposition: error.sessionDisposition,
761
- ...(error.retryAfterMs === undefined
762
- ? {}
763
- : { retryAfterMs: String(error.retryAfterMs) }),
764
- ...(failure.lastOutput === undefined ? {} : { lastOutput: failure.lastOutput })
742
+ const errorSessions = store.getTaskRoleSessionSet(taskId, input.fence.roleName);
743
+ const errorSession = [...Object.values(errorSessions?.sessions ?? {}), ...(errorSessions?.history ?? [])]
744
+ .find(session => session.nativeSessionId === input.fence.nativeSessionId
745
+ && session.agentId === input.fence.agentId);
746
+ const effective = run?.effective ?? errorSession?.effective;
747
+ const { event: errorEvent, created } = recordTaskAgentError(store, {
748
+ taskId, roleName: input.fence.roleName, sourceEventId: input.eventId,
749
+ agentId: input.fence.agentId, adapterId: effective?.adapterId ?? "unknown",
750
+ driverId: input.fence.driverId, error, effective,
751
+ evidence: {
752
+ observationEventId,
753
+ runId: input.fence.runId,
754
+ nativeSessionId: input.fence.nativeSessionId,
755
+ nativeTurnId: input.fence.nativeTurnId,
756
+ lastOutput: failure.lastOutput
757
+ },
765
758
  }, now);
766
- store.saveEvent(taskId, errorEvent);
767
- if (!providerRetryPending(store.getTaskRoleSessionSet(taskId, input.fence.roleName)?.providerBinding))
759
+ if (created && !providerRetryPending(store.getTaskRoleSessionSet(taskId, input.fence.roleName)?.providerBinding))
768
760
  routeRoleEvent(store, errorEvent, input.fence.roleName, wakeReason("agent-error", errorEvent.id), now);
769
761
  }
770
762
  if ((input.kind === "operation.completed" || input.kind === "operation.failed")
@@ -855,29 +847,6 @@ export class FileSchedulerStoreAdapter {
855
847
  getTaskBrief(taskId) { return this.store.getTaskBrief(taskId); }
856
848
  listDecisions(taskId) { return this.store.listDecisions(taskId); }
857
849
  listMilestones(taskId) { return this.store.listMilestones(taskId); }
858
- getTaskWakeEnvelope(taskId) {
859
- return this.store.transaction((reader) => {
860
- const pending = reader.getPendingWakeup(taskId);
861
- if (pending === null)
862
- return null;
863
- const task = reader.getTask(taskId);
864
- if (task === null)
865
- return null;
866
- const latest = latestTaskWake(reader.listTaskWakes(taskId));
867
- const fromCursor = latest?.toCursor ?? fallbackWakeCursor({
868
- taskCreatedAt: task.createdAt,
869
- leaderRunCreatedAt: operationalTaskRecords(reader.listRuns(taskId), reader.listEvents(taskId), "run")
870
- .filter((run) => run.roleName === "leader")
871
- .at(-1)?.createdAt
872
- });
873
- return buildTaskWakeEnvelope(reader, {
874
- taskId,
875
- wakeId: reader.peekNextTaskWakeId(taskId),
876
- reasons: pending.reasons,
877
- fromCursor
878
- });
879
- });
880
- }
881
850
  listRoles(taskId) {
882
851
  return this.store.listRoles(taskId).map((role) => mapRole(this.store, role));
883
852
  }
@@ -1305,41 +1274,22 @@ export class FileSchedulerStoreAdapter {
1305
1274
  raw: input.raw,
1306
1275
  inputDisposition: input.status === "delivery-unknown" ? "unknown" : "not-accepted"
1307
1276
  });
1308
- const errorEvent = createTaskEvent(store.nextEventId(input.taskId), input.taskId, "runtime.agent-error", {
1309
- sourceEventId: input.attemptId,
1310
- runId: input.runId ?? "",
1311
- roleName: input.roleName,
1277
+ const { event: errorEvent, created } = recordTaskAgentError(store, {
1278
+ taskId: input.taskId, roleName: input.roleName, sourceEventId: input.attemptId,
1312
1279
  agentId: run?.effective.agentId ?? sessions.activeAgentId,
1313
- adapterId: run?.effective.adapterId ?? session?.adapterId ?? "unknown",
1314
- driverId: driver?.id ?? "unknown",
1315
- // No native Turn exists at submission resolution, and the Session
1316
- // facts may be absent. An unknown fact is an absent key: an empty
1317
- // string reads back as a real value and cannot be told apart from
1318
- // one the Provider genuinely reported.
1319
- ...optionalEventFields({
1280
+ adapterId: adapterId ?? "unknown", driverId: driver?.id ?? "unknown",
1281
+ error, effective: run?.effective ?? session?.effective,
1282
+ evidence: {
1283
+ attemptId: input.attemptId,
1284
+ runId: input.runId,
1320
1285
  nativeSessionId: session?.nativeSessionId
1321
- }),
1322
- source: error.source,
1323
- phase: error.phase,
1324
- category: error.category,
1325
- code: error.code,
1326
- message: error.message,
1327
- raw: error.raw,
1328
- inputDisposition: error.inputDisposition,
1329
- sessionDisposition: error.sessionDisposition
1286
+ }
1330
1287
  }, input.now);
1331
- store.saveEvent(input.taskId, errorEvent);
1332
- if (input.status === "deferred" || providerRetryPending(updated))
1288
+ if (!created || input.status === "deferred" || providerRetryPending(updated))
1333
1289
  return;
1334
- const route = input.roleName === "leader"
1335
- ? { kind: "operator" }
1336
- : { kind: "role", taskId: input.taskId, roleName: "leader" };
1337
- enqueueWork(store, route, input.roleName === "leader"
1290
+ enqueueSupervisorEvent(store, errorEvent, input.roleName === "leader" ? "operator" : "leader", input.roleName === "leader"
1338
1291
  ? "leader-turn-submission-error"
1339
- : wakeReason("agent-error", errorEvent.id), input.now, [{ type: "event", taskId: input.taskId, id: errorEvent.id }], {
1340
- source: driver?.id ?? "agent-host",
1341
- dedupeKey: `agent-error:${input.taskId}:${input.attemptId}`
1342
- });
1292
+ : wakeReason("agent-error", errorEvent.id), input.now);
1343
1293
  });
1344
1294
  }
1345
1295
  retrySubmissionResolution(binding, input, adapterId) {
@@ -1403,15 +1353,19 @@ export class FileSchedulerStoreAdapter {
1403
1353
  }
1404
1354
  recordAgentError(input, now) {
1405
1355
  return this.store.transaction((store) => {
1406
- const run = store.getRun(input.taskId, input.runId);
1356
+ if (input.runId === undefined && input.attemptId === undefined) {
1357
+ throw new Error("An Agent error needs an exact Run or notification attempt.");
1358
+ }
1359
+ const run = input.runId === undefined ? null : store.getRun(input.taskId, input.runId);
1407
1360
  const sessionSet = store.getTaskRoleSessionSet(input.taskId, input.roleName);
1408
- const sessionAgentId = run?.effective.agentId ?? sessionSet?.activeAgentId;
1361
+ const effective = input.effective ?? run?.effective;
1362
+ const sessionAgentId = effective?.agentId ?? sessionSet?.activeAgentId;
1409
1363
  const session = sessionAgentId === undefined
1410
1364
  ? undefined
1411
1365
  : sessionSet?.sessions[sessionAgentId];
1412
- const driver = run === null
1366
+ const driver = effective === undefined
1413
1367
  ? null
1414
- : this.drivers.findByAdapterId(run.effective.adapterId);
1368
+ : this.drivers.findByAdapterId(effective.adapterId);
1415
1369
  const error = standardAgentError({
1416
1370
  source: input.source,
1417
1371
  phase: input.phase,
@@ -1428,57 +1382,22 @@ export class FileSchedulerStoreAdapter {
1428
1382
  ? {}
1429
1383
  : { sessionDisposition: input.sessionDisposition })
1430
1384
  });
1431
- const duplicate = [...store.listEvents(input.taskId)].reverse().find((event) => (event.type === "runtime.agent-error"
1432
- && event.payload.runId === input.runId
1433
- && event.payload.roleName === input.roleName
1434
- && event.payload.phase === input.phase
1435
- && event.payload.attemptId === input.attemptId
1436
- && event.payload.source === error.source
1437
- && event.payload.inputDisposition === error.inputDisposition
1438
- && event.payload.sessionDisposition === error.sessionDisposition
1439
- && event.payload.registrationDisposition === input.registrationDisposition
1440
- && event.payload.hostState === input.hostState
1441
- && (input.attemptId === undefined
1442
- ? event.payload.raw === error.raw
1443
- : event.payload.message === error.message
1444
- && event.payload.errorName === input.errorName
1445
- && event.payload.causeName === input.causeName)));
1446
- // Re-reading one failed attempt may add a different caller stack, not a
1447
- // new execution fact. Preserve its first full cause without multiplying
1448
- // notifications; changed disposition/identity/diagnostic remains visible.
1449
- if (duplicate !== undefined)
1450
- return duplicate.id;
1451
- const event = createTaskEvent(store.nextEventId(input.taskId), input.taskId, "runtime.agent-error", {
1452
- sourceEventId: `${input.runId}:${input.phase}${input.attemptId === undefined ? "" : `:${input.attemptId}`}`,
1453
- runId: input.runId,
1454
- roleName: input.roleName,
1455
- agentId: run?.effective.agentId ?? session?.agentId ?? "unknown",
1456
- adapterId: run?.effective.adapterId ?? session?.adapterId ?? "unknown",
1457
- driverId: driver?.id ?? "unknown",
1458
- source: error.source,
1459
- phase: error.phase,
1460
- category: error.category,
1461
- code: error.code,
1462
- message: error.message,
1463
- raw: error.raw,
1464
- inputDisposition: error.inputDisposition,
1465
- sessionDisposition: error.sessionDisposition,
1466
- // Structured facts from the failing operation. Absent keys stay
1467
- // absent rather than becoming an empty string, so a reader can tell
1468
- // "the Host did not report this" from "the Host reported nothing".
1469
- // The Session identities follow the same rule: this failure can
1470
- // happen before any Session record exists, and there is no native
1471
- // AgentRun to name at all.
1472
- ...optionalEventFields({
1473
- nativeSessionId: session?.nativeSessionId,
1474
- errorName: input.errorName,
1475
- causeName: input.causeName,
1476
- hostState: input.hostState,
1477
- attemptId: input.attemptId,
1385
+ const { event, created } = recordTaskAgentError(store, {
1386
+ taskId: input.taskId, roleName: input.roleName,
1387
+ sourceEventId: `${input.runId ?? input.attemptId}:${input.phase}${input.runId === undefined || input.attemptId === undefined ? "" : `:${input.attemptId}`}`,
1388
+ agentId: effective?.agentId ?? session?.agentId ?? "unknown",
1389
+ adapterId: effective?.adapterId ?? session?.adapterId ?? "unknown",
1390
+ driverId: driver?.id ?? "unknown", error, effective,
1391
+ evidence: {
1392
+ runId: input.runId, attemptId: input.attemptId, nativeSessionId: session?.nativeSessionId,
1393
+ errorName: input.errorName, causeName: input.causeName, hostState: input.hostState,
1478
1394
  registrationDisposition: input.registrationDisposition
1479
- })
1395
+ }
1480
1396
  }, now);
1481
- store.saveEvent(input.taskId, event);
1397
+ if (!created)
1398
+ return event.id;
1399
+ if (store.getTask(input.taskId)?.status === "archived")
1400
+ return event.id;
1482
1401
  if (input.phase === "turn-submit" && error.inputDisposition === "unknown"
1483
1402
  && input.attemptId !== undefined && sessionSet?.providerBinding?.run?.attemptId === input.attemptId
1484
1403
  && sessionSet.providerBinding.run.status === "submitting") {
@@ -1492,18 +1411,7 @@ export class FileSchedulerStoreAdapter {
1492
1411
  return event.id;
1493
1412
  const leaderCannotReceive = input.roleName === "leader"
1494
1413
  && ["host-start", "session-start", "session-restore", "turn-submit"].includes(input.phase);
1495
- if (!leaderCannotReceive) {
1496
- enqueueWork(store, { kind: "role", taskId: input.taskId, roleName: "leader" }, wakeReason("agent-error", event.id), now, [{ type: "event", taskId: input.taskId, id: event.id }], {
1497
- source: driver?.id ?? input.source,
1498
- dedupeKey: `agent-error:${input.taskId}:${input.runId}:${input.phase}:${event.id}`
1499
- });
1500
- }
1501
- if (leaderCannotReceive) {
1502
- enqueueWork(store, { kind: "operator" }, "leader-agent-error", now, [{ type: "event", taskId: input.taskId, id: event.id }], {
1503
- source: driver?.id ?? input.source,
1504
- dedupeKey: `leader-agent-error:${input.taskId}:${input.runId}:${event.id}`
1505
- });
1506
- }
1414
+ enqueueSupervisorEvent(store, event, leaderCannotReceive ? "operator" : "leader", leaderCannotReceive ? "leader-agent-error" : wakeReason("agent-error", event.id), now);
1507
1415
  return event.id;
1508
1416
  });
1509
1417
  }
@@ -1797,6 +1705,13 @@ export class FileSchedulerStoreAdapter {
1797
1705
  const task = store.getTask(taskId);
1798
1706
  if (task == null || !["active", "draft"].includes(task.status) || task.executionGate.state !== "enabled")
1799
1707
  return null;
1708
+ // Admission refuses input while this Session is being cleaned up.
1709
+ // Leave the original batch pending for its successor instead of
1710
+ // claiming a notification that cannot legally reach the Provider.
1711
+ if (hasRuntimeCleanupObligation(store.getWorkMailbox(runtimeLifecycleTarget({
1712
+ scope: "task", taskId, roleName: "leader"
1713
+ }))))
1714
+ return null;
1800
1715
  const target = { kind: "role", taskId, roleName: "leader" };
1801
1716
  let mailbox = store.getWorkMailbox(target);
1802
1717
  if (mailbox === null)
@@ -1822,8 +1737,10 @@ export class FileSchedulerStoreAdapter {
1822
1737
  return null;
1823
1738
  }
1824
1739
  if (native?.status === "rejected" || previous?.payload.outcome === "rejected") {
1825
- this.settleLeaderNotification(taskId, attemptId, "rejected", now);
1826
- return null;
1740
+ if (previous?.payload.outcome !== "rejected") {
1741
+ this.settleLeaderNotification(taskId, attemptId, "rejected", now);
1742
+ }
1743
+ return { wakeId, attemptId, disposition: "rejected" };
1827
1744
  }
1828
1745
  if (native?.status === "submitting")
1829
1746
  return { wakeId, attemptId, disposition: "pending" };
@@ -1898,7 +1815,7 @@ export class FileSchedulerStoreAdapter {
1898
1815
  return { wakeId, attemptId, disposition: "submit" };
1899
1816
  });
1900
1817
  }
1901
- settleLeaderNotification(taskId, attemptId, outcome, now, detail) {
1818
+ settleLeaderNotification(taskId, attemptId, outcome, now, detail, failure) {
1902
1819
  this.store.transaction((store) => {
1903
1820
  const target = { kind: "role", taskId, roleName: "leader" };
1904
1821
  const mailbox = store.getWorkMailbox(target);
@@ -1908,19 +1825,35 @@ export class FileSchedulerStoreAdapter {
1908
1825
  return;
1909
1826
  if (!store.listEvents(taskId).some((event) => event.type === "notification.delivery"
1910
1827
  && event.payload.attemptId === attemptId && event.payload.outcome === outcome)) {
1911
- store.saveEvent(taskId, createTaskEvent(store.nextEventId(taskId), taskId, "notification.delivery", { attemptId, outcome, ...(detail === undefined ? {} : { detail }) }, now));
1828
+ // Native ingress may already have persisted the same rejection. Reuse
1829
+ // that original failure rather than reporting it twice from the caller.
1830
+ const existing = store.listEventsByType(taskId, ["runtime.agent-error"])
1831
+ .find(event => event.payload.roleName === "leader"
1832
+ && (event.payload.attemptId === attemptId || event.payload.sourceEventId === attemptId));
1833
+ const errorEventId = existing?.id ?? (failure !== undefined
1834
+ && (outcome === "rejected" || outcome === "unknown")
1835
+ ? this.recordAgentError({
1836
+ taskId, roleName: "leader", attemptId, effective: failure.effective,
1837
+ source: "host", phase: failure.phase,
1838
+ message: detail ?? `Notification ${outcome}.`, raw: failure.raw,
1839
+ inputDisposition: outcome === "unknown" ? "unknown" : "not-accepted",
1840
+ ...(failure.phase === "session-start" ? { registrationDisposition: "not-committed" } : {})
1841
+ }, now) : undefined);
1842
+ store.saveEvent(taskId, createTaskEvent(store.nextEventId(taskId), taskId, "notification.delivery", { attemptId, outcome, ...(detail === undefined ? {} : { detail }),
1843
+ ...(errorEventId === undefined ? {} : { errorEventId }) }, now));
1912
1844
  }
1913
1845
  if (store.getTask(taskId)?.status === "archived")
1914
1846
  return;
1915
- if (outcome !== "accepted" && outcome !== "rejected")
1847
+ // Rejection proves nondelivery, not consumption of the original input.
1848
+ // Keep the exact claim as the existing recovery/completion boundary.
1849
+ // An explicit retry or Session replacement can release it later.
1850
+ if (outcome !== "accepted")
1916
1851
  return;
1917
1852
  store.saveWorkMailbox(completeProcessing(mailbox, attemptId));
1918
- if (outcome === "accepted") {
1919
- const wakeId = processing.owner.slice("leader-notification:".length);
1920
- const wake = store.listTaskWakes(taskId).find((entry) => entry.id === wakeId);
1921
- if (wake !== undefined)
1922
- store.saveTaskWake(taskId, markTaskWakeConsumed(wake, now));
1923
- }
1853
+ const wakeId = processing.owner.slice("leader-notification:".length);
1854
+ const wake = store.listTaskWakes(taskId).find((entry) => entry.id === wakeId);
1855
+ if (wake !== undefined)
1856
+ store.saveTaskWake(taskId, markTaskWakeConsumed(wake, now));
1924
1857
  });
1925
1858
  }
1926
1859
  saveRoleRunPrepared(input) {
@@ -2132,12 +2065,6 @@ export class FileSchedulerStoreAdapter {
2132
2065
  let sessions = store.getTaskRoleSessionSet(input.taskId, input.roleName)
2133
2066
  ?? createRoleSessionSet({ scope: "task", taskId: input.taskId, roleName: input.roleName }, input.agentId, now);
2134
2067
  const canonicalRunId = input.nativeTurnId ?? resolved.nativeTurnId;
2135
- const existing = sessions.sessions[input.agentId];
2136
- const owner = {
2137
- scope: "task",
2138
- taskId: input.taskId,
2139
- roleName: input.roleName
2140
- };
2141
2068
  assertConsistentTerminal(store, input, observedRun);
2142
2069
  // A historical operation failure stays failed. The immutable terminal
2143
2070
  // observation retains the late report for explicit Leader adoption.
@@ -3210,28 +3137,6 @@ function requireRole(store, taskId, roleName) {
3210
3137
  throw new Error(`Role not found: ${taskId}/${roleName}.`);
3211
3138
  return role;
3212
3139
  }
3213
- function runLaunchEventPayload(run) {
3214
- return {
3215
- runId: run.id,
3216
- role: run.roleName,
3217
- purpose: run.purpose,
3218
- mode: run.mode,
3219
- agent: `${run.effective.agentId}/${run.effective.adapterId}`,
3220
- component: run.effective.component,
3221
- effectiveRevision: String(run.effective.sourceDesiredRevision),
3222
- profileAccess: run.effective.profileAccess,
3223
- effectivePermission: run.effective.permission.strategy,
3224
- writeProjectIds: run.effective.writeProjectIds.join(",") || "none"
3225
- };
3226
- }
3227
- /**
3228
- * Keeps only the fields the caller actually knew. Event payloads are a
3229
- * string map, so an unknown fact has to be an absent key: writing `""` would
3230
- * claim the Host reported an empty value.
3231
- */
3232
- function optionalEventFields(fields) {
3233
- return Object.fromEntries(Object.entries(fields).filter((entry) => typeof entry[1] === "string" && entry[1].trim().length > 0));
3234
- }
3235
3140
  function runtimeObservationTelemetryEntry(input) {
3236
3141
  return {
3237
3142
  taskId: input.fence.taskId,