@zq-silk/yui 0.15.11 → 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 (280) hide show
  1. package/ARCHITECTURE.md +11 -6
  2. package/ARCHITECTURE.zh-CN.md +8 -4
  3. package/README.md +13 -5
  4. package/dist/agentRun/agentRun.js +3 -0
  5. package/dist/artifacts/artifactCommitLock.js +16 -55
  6. package/dist/artifacts/taskArtifactRepository.js +3 -3
  7. package/dist/cli/agentConfigurationPicker.js +1 -1
  8. package/dist/cli/commandCatalog.js +92 -48
  9. package/dist/cli/completion.js +18 -18
  10. package/dist/cli/completionWizard.js +1 -1
  11. package/dist/cli/dynamicCompletion.js +1 -1
  12. package/dist/cli/interactionPolicy.js +11 -3
  13. package/dist/cli/invocationAuthority.js +64 -0
  14. package/dist/cli/managedDiagnostics.js +2 -2
  15. package/dist/cli/parseRepeatable.js +35 -0
  16. package/dist/cli/updatePorts.js +26 -5
  17. package/dist/cli.js +1303 -1235
  18. package/dist/commands/agentCommands.js +4 -4
  19. package/dist/commands/capabilityCommands.js +1 -1
  20. package/dist/commands/configCommands.js +6 -57
  21. package/dist/commands/configOverview.js +2 -2
  22. package/dist/commands/durableJobCommands.js +12 -6
  23. package/dist/commands/executionAuditCommands.js +10 -0
  24. package/dist/commands/globalRoleCommands.js +78 -29
  25. package/dist/commands/grantCommands.js +0 -3
  26. package/dist/commands/jobCommands.js +1 -4
  27. package/dist/commands/operatorCommands.js +3 -3
  28. package/dist/commands/projectCommands.js +45 -25
  29. package/dist/commands/resourcesCommands.js +10 -40
  30. package/dist/commands/roleConfiguration.js +2 -6
  31. package/dist/commands/taskActivationCommands.js +4 -8
  32. package/dist/commands/taskAgentCapabilities.js +9 -0
  33. package/dist/commands/taskCommandSupport.js +155 -0
  34. package/dist/commands/taskCommandTypes.js +1 -0
  35. package/dist/commands/taskCommands.js +355 -732
  36. package/dist/commands/taskCompletionGate.js +1 -1
  37. package/dist/commands/taskExecutionCommands.js +1 -1
  38. package/dist/commands/taskFactCommands.js +325 -0
  39. package/dist/commands/taskInputCommands.js +12 -42
  40. package/dist/commands/taskIntegrationCommands.js +20 -58
  41. package/dist/commands/taskNextActionCommand.js +7 -6
  42. package/dist/commands/taskPublicationAdoptCommand.js +127 -0
  43. package/dist/commands/taskPublicationCommands.js +12 -3
  44. package/dist/commands/taskPublicationVerifyCommand.js +24 -40
  45. package/dist/commands/taskRemoteDeliveryCommand.js +5 -86
  46. package/dist/commands/taskUpstreamCommands.js +19 -8
  47. package/dist/commands/workflowCommands.js +6 -1
  48. package/dist/completion/completionInstaller.js +26 -26
  49. package/dist/completion/completionState.js +26 -26
  50. package/dist/completion/fileCompletionManager.js +6 -11
  51. package/dist/config/configCatalog.js +0 -2
  52. package/dist/config/timeZone.js +14 -0
  53. package/dist/config/yuiConfig.js +0 -29
  54. package/dist/context/runContextPack.js +5 -3
  55. package/dist/context/taskCatalog.js +185 -0
  56. package/dist/context/taskContext.js +152 -151
  57. package/dist/context/wakeRunReferences.js +11 -0
  58. package/dist/controller/agentCapabilities.js +58 -0
  59. package/dist/controller/agentRuntimeObserver.js +6 -11
  60. package/dist/controller/clientRuntime.js +22 -14
  61. package/dist/controller/controller.js +20 -24
  62. package/dist/controller/fileSchedulerStoreAdapter.js +191 -225
  63. package/dist/controller/globalInputDelivery.js +13 -0
  64. package/dist/controller/jobClient.js +4 -4
  65. package/dist/controller/jobControl.js +60 -25
  66. package/dist/controller/jobSupervisor.js +4 -4
  67. package/dist/controller/providerRetryAdmission.js +100 -0
  68. package/dist/controller/providerRetryDelivery.js +218 -0
  69. package/dist/controller/runtime.js +85 -43
  70. package/dist/controller/runtimeEventProcessor.js +0 -5
  71. package/dist/controller/sessionOwnerReconciliation.js +5 -0
  72. package/dist/controller/taskAgentError.js +35 -0
  73. package/dist/coordination/workMailboxQueue.js +2 -24
  74. package/dist/core/controllerClient.js +2 -2
  75. package/dist/core/fileLockOwner.js +74 -0
  76. package/dist/decision/decision.js +17 -0
  77. package/dist/doctor/doctor.js +18 -9
  78. package/dist/event/taskEvent.js +12 -0
  79. package/dist/execution/workItemExecutionProjection.js +2 -2
  80. package/dist/executor/agentCapabilityConfig.js +43 -0
  81. package/dist/executor/agentConfigurationCatalog.js +49 -11
  82. package/dist/executor/agentExecutor.js +5 -8
  83. package/dist/executor/fileRoleLaunchPlanner.js +11 -53
  84. package/dist/executor/taskAgentCapabilities.js +64 -0
  85. package/dist/integration/deliveryObligation.js +1 -69
  86. package/dist/integration/gitIntegrationService.js +79 -90
  87. package/dist/integration/integrationAttempt.js +3 -14
  88. package/dist/integration/integrationSourceApplication.js +4 -12
  89. package/dist/integration/manifestTags.js +2 -2
  90. package/dist/job/durableJob.js +4 -25
  91. package/dist/job/jobAssignmentScope.js +22 -0
  92. package/dist/job/jobOperation.js +16 -0
  93. package/dist/job/jobRunner.js +2 -1
  94. package/dist/job/stepDirectory.js +15 -0
  95. package/dist/kernel/builtinCapabilities.js +15 -11
  96. package/dist/kernel/kernelPorts.js +1 -17
  97. package/dist/lifecycle/exactRunTerminalization.js +5 -10
  98. package/dist/message/globalProviderRetry.js +15 -0
  99. package/dist/message/inputControlResolution.js +3 -4
  100. package/dist/message/message.js +17 -20
  101. package/dist/message/messageContinuation.js +1 -1
  102. package/dist/milestone/milestone.js +11 -0
  103. package/dist/observability/executionAudit.js +19 -0
  104. package/dist/observability/orchestrationMetrics.js +24 -1
  105. package/dist/observability/runtimeIdentity.js +1 -17
  106. package/dist/output/agentConfigurationPresentation.js +2 -0
  107. package/dist/output/timePresentation.js +1 -14
  108. package/dist/plugins/pluginService.js +12 -4
  109. package/dist/release/releaseWorkflowPorts.js +45 -96
  110. package/dist/release/runtimeRelease.js +9 -15
  111. package/dist/repository/gitWorkspace.js +371 -105
  112. package/dist/repository/projectMaintenanceLock.js +98 -82
  113. package/dist/repository/taskBaseFreshness.js +1 -1
  114. package/dist/repository/taskWorkspaceCoordinator.js +82 -144
  115. package/dist/repository/taskWorkspacePreparer.js +113 -47
  116. package/dist/repository/workspaceCleanupInspection.js +187 -0
  117. package/dist/resources/autoResourceGc.js +4 -77
  118. package/dist/resources/liveReferences.js +7 -54
  119. package/dist/resources/resourceDiscovery.js +5 -118
  120. package/dist/resources/resourceGc.js +233 -181
  121. package/dist/resources/resourceRegistrar.js +5 -4
  122. package/dist/resources/resourceRegistry.js +3 -37
  123. package/dist/resources/resourceRegistryStore.js +13 -36
  124. package/dist/resources/sqliteResourceRegistry.js +43 -26
  125. package/dist/review/deltaRecheck.js +1 -1
  126. package/dist/review/reviewAcceptance.js +1 -1
  127. package/dist/review/reviewDecision.js +1 -1
  128. package/dist/review/reviewRound.js +8 -7
  129. package/dist/review/taskFinalReviewContractResolution.js +1 -1
  130. package/dist/runtime/acpProtocol.js +4 -51
  131. package/dist/runtime/acpSession.js +10 -75
  132. package/dist/runtime/acpSessionConfiguration.js +1 -8
  133. package/dist/runtime/agentEndpoint.js +3 -2
  134. package/dist/runtime/agentError.js +5 -3
  135. package/dist/runtime/agentFailureContext.js +43 -0
  136. package/dist/runtime/agentHost.js +42 -25
  137. package/dist/runtime/builtinAgentErrorMappers.js +91 -0
  138. package/dist/runtime/codexAppServerRuntime.js +34 -3
  139. package/dist/runtime/codexInteractiveHost.js +2 -2
  140. package/dist/runtime/index.js +0 -1
  141. package/dist/runtime/managedCaller.js +21 -1
  142. package/dist/runtime/providerControl.js +5 -1
  143. package/dist/runtime/providerErrors.js +38 -0
  144. package/dist/runtime/providerRetry.js +198 -0
  145. package/dist/runtime/providerRuntimeIdentity.js +28 -2
  146. package/dist/runtime/runtimeObservation.js +0 -5
  147. package/dist/runtime/runtimeSessionCandidate.js +2 -3
  148. package/dist/runtime/sessionOwnerIdentity.js +1 -1
  149. package/dist/runtime/sessionTokenMetrics.js +15 -5
  150. package/dist/runtime/structuredProviderHost.js +10 -43
  151. package/dist/runtime/taskUsageMetrics.js +275 -0
  152. package/dist/runtime/tmuxAdapters.js +6 -8
  153. package/dist/scheduler/activeRoleRunDelivery.js +19 -7
  154. package/dist/scheduler/leaderWakeupProcessor.js +24 -6
  155. package/dist/scheduler/operatorEvent.js +12 -20
  156. package/dist/scheduler/operatorInputNotificationProcessor.js +1 -1
  157. package/dist/scheduler/ports.js +5 -8
  158. package/dist/scheduler/roleRunLiveness.js +4 -4
  159. package/dist/scheduler/roleRunStall.js +16 -23
  160. package/dist/scheduler/taskExecutionProjection.js +49 -36
  161. package/dist/scheduler/taskObservabilityProjection.js +6 -45
  162. package/dist/scheduler/taskWake.js +5 -10
  163. package/dist/scheduler/wakeReason.js +2 -0
  164. package/dist/scheduler/wakeupQueue.js +2 -4
  165. package/dist/setup/setupCommand.js +1 -1
  166. package/dist/storage/contextRecords.js +106 -0
  167. package/dist/storage/currentTaskStore.js +1 -1
  168. package/dist/storage/homeLayout.js +13 -17
  169. package/dist/storage/migrations/agentFailureContext.js +22 -0
  170. package/dist/storage/migrations/currentInputContract.js +86 -0
  171. package/dist/storage/migrations/currentRuntimeContract.js +228 -0
  172. package/dist/storage/migrations/historicalVerificationPlan.js +35 -0
  173. package/dist/storage/migrations/integrationContinuation.js +5 -4
  174. package/dist/storage/migrations/narrowAgentFailureContext.js +65 -0
  175. package/dist/storage/migrations/notificationOnlyWakes.js +74 -0
  176. package/dist/storage/migrations/verificationPlanV1.js +162 -0
  177. package/dist/storage/migrations/verificationPolicy.js +74 -0
  178. package/dist/storage/migrations/workItemHistory.js +46 -0
  179. package/dist/storage/persistenceWorker.js +12 -4
  180. package/dist/storage/recordValidation.js +87 -0
  181. package/dist/storage/sqliteSchema.js +173 -1
  182. package/dist/storage/sqliteStore.js +172 -161
  183. package/dist/storage/storageSchema.js +2 -14
  184. package/dist/storage/storageVersions.js +1 -1
  185. package/dist/storage/storeRpc.js +16 -7
  186. package/dist/storage/taskCatalog.js +123 -0
  187. package/dist/storage/taskStore.js +5 -7
  188. package/dist/storage/upgrade/upgradeOrchestrator.js +25 -37
  189. package/dist/task/archiveDiagnostics.js +1 -0
  190. package/dist/task/archivePreflight.js +124 -0
  191. package/dist/task/completionReadiness.js +5 -24
  192. package/dist/task/draftPlan.js +0 -53
  193. package/dist/task/nextAction.js +7 -13
  194. package/dist/task/publicationAdoption.js +56 -0
  195. package/dist/task/publicationReference.js +10 -0
  196. package/dist/task/remoteDelivery.js +31 -16
  197. package/dist/task/remoteDeliveryService.js +89 -0
  198. package/dist/task/taskActivation.js +2 -25
  199. package/dist/task/taskActivationService.js +0 -2
  200. package/dist/task/taskRecordReference.js +0 -1
  201. package/dist/task/taskSubmission.js +3 -14
  202. package/dist/telemetry/sqliteTelemetryBatch.js +29 -0
  203. package/dist/telemetry/sqliteTelemetryStore.js +49 -49
  204. package/dist/telemetry/telemetryWiring.js +4 -3
  205. package/dist/tmux/tmuxManager.js +29 -20
  206. package/dist/verification/gateArtifact.js +15 -24
  207. package/dist/verification/gateArtifactStore.js +14 -7
  208. package/dist/verification/verificationGateService.js +29 -88
  209. package/dist/verification/verificationPlan.js +27 -72
  210. package/dist/web/assets/client/app.js +92 -17
  211. package/dist/web/assets/client/components.js +55 -13
  212. package/dist/web/assets/client/i18n.js +72 -4
  213. package/dist/web/assets/client/taskSurface.js +6 -5
  214. package/dist/web/assets/client/view.js +35 -7
  215. package/dist/web/assets/shell.js +6 -0
  216. package/dist/web/assets/styles/layout.js +7 -0
  217. package/dist/web/webServer.js +12 -3
  218. package/dist/web/webSnapshot.js +14 -85
  219. package/dist/web/webTaskSurface.js +17 -46
  220. package/dist/workItem/workItem.js +1 -12
  221. package/dist/workspace/cleanupInspection.js +63 -0
  222. package/dist/workspace/workItemChangeSetManager.js +110 -50
  223. package/docs/agent-result-consumption.md +4 -0
  224. package/docs/agent-result-consumption.zh-CN.md +3 -0
  225. package/docs/agent-runtime-drivers.md +7 -0
  226. package/docs/agent-runtime-drivers.zh-CN.md +5 -0
  227. package/docs/architecture/README.md +2 -0
  228. package/docs/architecture/README.zh-CN.md +3 -1
  229. package/docs/architecture/capabilities-and-resources.md +42 -5
  230. package/docs/architecture/capabilities-and-resources.zh-CN.md +33 -3
  231. package/docs/managed-turn-and-session-runtime.md +121 -1
  232. package/docs/managed-turn-and-session-runtime.zh-CN.md +96 -2
  233. package/docs/observability/README.md +62 -0
  234. package/docs/observability/README.zh-CN.md +47 -0
  235. package/docs/plugin-sdk.md +4 -2
  236. package/docs/project-refresh.md +77 -0
  237. package/docs/project-refresh.zh-CN.md +59 -0
  238. package/docs/provider-retry.md +70 -0
  239. package/docs/provider-runtime.md +3 -1
  240. package/docs/provider-runtime.zh-CN.md +4 -2
  241. package/docs/release-workflow.md +190 -7
  242. package/docs/release-workflow.zh-CN.md +139 -5
  243. package/docs/roles-and-configuration.md +29 -0
  244. package/docs/roles-and-configuration.zh-CN.md +21 -0
  245. package/docs/sqlite-control-plane-design.md +16 -3
  246. package/docs/sqlite-control-plane-design.zh-CN.md +13 -2
  247. package/docs/task-dag-semantics.md +3 -3
  248. package/docs/task-dag-semantics.zh-CN.md +2 -2
  249. package/docs/task-delivery.md +223 -17
  250. package/docs/task-delivery.zh-CN.md +171 -14
  251. package/docs/task-discovery.md +101 -0
  252. package/docs/task-discovery.zh-CN.md +85 -0
  253. package/docs/testing/verification-levels.md +161 -9
  254. package/docs/testing/verification-levels.zh-CN.md +116 -9
  255. package/i18n/README.zh-CN.md +13 -7
  256. package/package.json +1 -1
  257. package/skills/yui-leader/SKILL.md +5 -0
  258. package/skills/yui-leader/references/execution.md +3 -2
  259. package/skills/yui-leader/references/integration.md +9 -5
  260. package/skills/yui-leader/references/planning.md +9 -3
  261. package/skills/yui-operator/SKILL.md +24 -8
  262. package/skills/yui-reviewer/SKILL.md +4 -0
  263. package/skills/yui-runtime/SKILL.md +17 -2
  264. package/skills/yui-runtime/references/publication.md +26 -4
  265. package/skills/yui-runtime/references/recovery.md +71 -0
  266. package/dist/agent/agentRegistry.js +0 -10
  267. package/dist/agentRun/runIdentity.js +0 -22
  268. package/dist/commands/deliveryGuardPreflight.js +0 -30
  269. package/dist/commands/taskIntegrationQueueCommands.js +0 -226
  270. package/dist/commands/taskOverviewCommand.js +0 -376
  271. package/dist/completion/completionWizard.js +0 -125
  272. package/dist/context/dispatchContext.js +0 -110
  273. package/dist/context/wakeNotification.js +0 -144
  274. package/dist/integration/integrationCheckEvidenceReuse.js +0 -53
  275. package/dist/integration/integrationQueueEntry.js +0 -191
  276. package/dist/integration/integrationQueueService.js +0 -810
  277. package/dist/release/fakeReleasePorts.js +0 -55
  278. package/dist/runtime/sessionOwnerRegistry.js +0 -137
  279. package/dist/task/deliveryGuard.js +0 -226
  280. /package/dist/{commands/taskActor.js → task/taskAuthority.js} +0 -0
@@ -2,6 +2,8 @@ import { sendAgentHostRunControl, AGENT_HOST_CONTROL_PROTOCOL } from "../runtime
2
2
  import { currentProviderConversation } from "../runtime/providerRuntimeIdentity.js";
3
3
  import { hasRuntimeCleanupObligation, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
4
4
  import { markGlobalRoleMessageNotDelivered } from "../message/message.js";
5
+ import { providerRetryPending } from "../runtime/providerRetry.js";
6
+ import { settleGlobalRetryInput } from "../message/globalProviderRetry.js";
5
7
  /** Messages are the only queue. Host's atomic begin arbitrates native writers. */
6
8
  export async function deliverGlobalInputs(home, store, ensure, onError) {
7
9
  for (const role of store.listGlobalRoles()) {
@@ -11,6 +13,15 @@ export async function deliverGlobalInputs(home, store, ensure, onError) {
11
13
  if (pending.length === 0 || hasRuntimeCleanupObligation(store.getWorkMailbox(runtimeLifecycleTarget({ scope: "global", roleName: role.name }))))
12
14
  continue;
13
15
  let sessions = store.getGlobalRoleSessionSet(role.name);
16
+ if (sessions?.providerBinding?.retry !== undefined && !providerRetryPending(sessions.providerBinding)) {
17
+ store.transaction(tx => settleGlobalRetryInput(tx, role.name, tx.getGlobalRoleSessionSet(role.name)?.providerBinding, new Date()));
18
+ pending = store.listGlobalRoleMessages(role.name).filter(message => message.delivery === undefined && message.notDelivered === undefined
19
+ && (message.inputControl?.action === "queue" || message.interruptThen !== undefined));
20
+ if (pending.length === 0)
21
+ continue;
22
+ }
23
+ if (providerRetryPending(sessions?.providerBinding))
24
+ continue;
14
25
  const current = sessions?.providerBinding?.run;
15
26
  const obsolete = pending.filter(message => {
16
27
  const claim = message.interruptThen;
@@ -99,6 +110,8 @@ export async function deliverGlobalInputs(home, store, ensure, onError) {
99
110
  if (latest === undefined || latest.delivery !== undefined || latest.notDelivered !== undefined)
100
111
  return;
101
112
  const turn = tx.getGlobalRoleSessionSet(role.name)?.providerBinding?.run;
113
+ if (providerRetryPending(tx.getGlobalRoleSessionSet(role.name)?.providerBinding))
114
+ return;
102
115
  // A later canonical acceptance wins over transport diagnostics.
103
116
  if (turn?.attemptId === attemptId && ["accepted", "completed", "failed", "cancelled"].includes(turn.status))
104
117
  return;
@@ -1,4 +1,4 @@
1
- import { resolveJobCaller } from "../commands/taskActor.js";
1
+ import { resolveJobCaller } from "../task/taskAuthority.js";
2
2
  import { callFileTaskController } from "./clientRuntime.js";
3
3
  export async function startDurableJob(home, params, clientOptions = {}) {
4
4
  // rr6/f2: Route through callFileTaskController so an Integration-issued
@@ -7,8 +7,8 @@ export async function startDurableJob(home, params, clientOptions = {}) {
7
7
  const result = await callFileTaskController(home, "job.start", params, clientOptions);
8
8
  return parseStartResult(result);
9
9
  }
10
- export async function getDurableJob(home, taskId, jobId, clientOptions = {}) {
11
- const result = await callFileTaskController(home, "job.get", { taskId, jobId }, clientOptions);
10
+ export async function getDurableJob(home, taskId, jobId, caller, clientOptions = {}) {
11
+ const result = await callFileTaskController(home, "job.get", { taskId, jobId, caller }, clientOptions);
12
12
  return parseJobResult(result);
13
13
  }
14
14
  export async function acknowledgeDurableJob(home, taskId, jobId, caller, clientOptions = {}) {
@@ -89,7 +89,7 @@ export function createControllerIntegrationJobPort(home, clientOptions = {}) {
89
89
  return job;
90
90
  },
91
91
  async getJob(taskId, jobId) {
92
- return getDurableJob(home, taskId, jobId, clientOptions);
92
+ return getDurableJob(home, taskId, jobId, resolveJobCaller(clientOptions.environment, taskId), clientOptions);
93
93
  },
94
94
  async cancelJob(taskId, jobId) {
95
95
  // rr8/rr12: Bind the cancel request to the caller's managed identity.
@@ -3,18 +3,22 @@
3
3
  * record is created or cancel-requested. The Controller socket layer calls
4
4
  * this port for `job.*` requests; nothing else writes queued jobs.
5
5
  *
6
- * Creation is idempotent per (owner, project, head, steps, workspace, env):
7
- * a repeated `job.start` with the same inputs returns the existing job with
8
- * `created: false`, so a Leader retry can never spawn duplicate runners.
6
+ * Creation is idempotent per authenticated actor and explicit request ID.
7
+ * Repeating that request with identical inputs returns the original Job;
8
+ * changing its input is a conflict. Integration owner identity additionally
9
+ * preserves its admitted Job across authorized Session replacement.
9
10
  */
10
11
  import { execSync } from "node:child_process";
11
12
  import { createHash } from "node:crypto";
12
13
  import { resolve, sep } from "node:path";
13
- import { acknowledgeUnknownDurableJob, createDurableJob, durableJobIdempotencyKey, isDurableJobTerminal, requestDurableJobCancel, retryDurableJobIdempotencyKey } from "../job/durableJob.js";
14
14
  import { activeLiveRoleAgentSession } from "../executor/agentExecutor.js";
15
+ import { acknowledgeUnknownDurableJob, createDurableJob, durableJobIdempotencyKey, isDurableJobTerminal, requestDurableJobCancel, retryDurableJobIdempotencyKey } from "../job/durableJob.js";
15
16
  import { CallAuthority } from "../kernel/callAuthority.js";
16
17
  import { redactLaunchText } from "../runtime/launchDiagnostics.js";
17
- import { requireManagedTaskCaller } from "../runtime/managedCaller.js";
18
+ import { requireManagedTaskCaller, ManagedRuntimeDriftError } from "../runtime/managedCaller.js";
19
+ import { CliError } from "../errors/cliError.js";
20
+ import { assertJobAssignmentScope, JobAssignmentScopeError } from "../job/jobAssignmentScope.js";
21
+ import { assertContextRecordReadable } from "../context/taskContext.js";
18
22
  export function createDurableJobControl(store) {
19
23
  const authority = createJobCallAuthority(store);
20
24
  return {
@@ -25,6 +29,7 @@ export function createDurableJobControl(store) {
25
29
  // startJob with the same key create a duplicate job.
26
30
  return store.transaction((tx) => {
27
31
  const context = authority.authenticate(Object.freeze({ ...params.caller }), params.taskId);
32
+ requireJobAssignment(tx, params, params.caller);
28
33
  assertNonSecretJobInput(params);
29
34
  const baseKey = durableJobIdempotencyKey({
30
35
  owner: params.owner,
@@ -37,8 +42,7 @@ export function createDurableJobControl(store) {
37
42
  const inputDigest = params.retryOf === undefined
38
43
  ? baseKey
39
44
  : retryDurableJobIdempotencyKey(baseKey, params.retryOf);
40
- const requestId = params.requestId === undefined ? inputDigest
41
- : requiredId(params.requestId, "job.start requestId");
45
+ const requestId = requiredId(params.requestId, "job.start requestId");
42
46
  // An IntegrationAttempt already is a durable operation identity.
43
47
  // Recovery by another authorized Role must find its original Job,
44
48
  // including the window before Integration persisted the returned id.
@@ -68,12 +72,6 @@ export function createDurableJobControl(store) {
68
72
  }
69
73
  return { job: existing, created: false };
70
74
  }
71
- // A historical content-addressed request has no attributable caller.
72
- // Do not silently execute it again under a newly attributed identity.
73
- const historical = tx.findDurableJobByIdempotencyKey(params.taskId, inputDigest);
74
- if (params.requestId === undefined && historical !== null) {
75
- throw jobDomainError(`Historical request already exists: ${historical.id}; inspect it or select an explicit new requestId.`);
76
- }
77
75
  authority.authorize(context, params.taskId);
78
76
  validateStartParams(tx, params);
79
77
  const id = tx.nextDurableJobId(params.taskId);
@@ -97,7 +95,21 @@ export function createDurableJobControl(store) {
97
95
  return { job, created: true };
98
96
  });
99
97
  },
100
- getJob(taskId, jobId) {
98
+ getJob(taskId, jobId, caller) {
99
+ if (caller === undefined)
100
+ throw jobControlError("UNAUTHORIZED", "Job reads require an explicit caller.");
101
+ try {
102
+ assertContextRecordReadable(store, taskId, "job", jobId, caller.scope === "user" ? {} : {
103
+ YUI_SESSION_SCOPE: caller.scope, YUI_TASK_ID: caller.taskId, YUI_ROLE: caller.role,
104
+ YUI_AGENT_ID: caller.agentId, YUI_ADAPTER_ID: caller.adapterId, YUI_NATIVE_SESSION_ID: caller.nativeSessionId
105
+ });
106
+ }
107
+ catch (error) {
108
+ if (error instanceof ManagedRuntimeDriftError || (error instanceof CliError && error.code === "USAGE_ERROR")) {
109
+ throw jobControlError("UNAUTHORIZED", error.message);
110
+ }
111
+ throw error;
112
+ }
101
113
  return store.getDurableJob(taskId, jobId);
102
114
  },
103
115
  cancelJob(taskId, jobId, now, caller) {
@@ -108,6 +120,7 @@ export function createDurableJobControl(store) {
108
120
  // rr8: Bind the cancel request to the caller's managed identity. The
109
121
  // same rules as job.start apply, checked against the job's owner.
110
122
  assertCallerAuthorized(tx, caller, taskId);
123
+ requireJobAssignment(tx, current, caller);
111
124
  const next = requestDurableJobCancel(current, now);
112
125
  if (next !== current)
113
126
  tx.saveDurableJob(taskId, next);
@@ -120,6 +133,7 @@ export function createDurableJobControl(store) {
120
133
  if (current === null)
121
134
  return null;
122
135
  assertCallerAuthorized(tx, caller, taskId);
136
+ requireJobAssignment(tx, current, caller);
123
137
  const next = acknowledgeUnknownDurableJob(current, now);
124
138
  if (next !== current)
125
139
  tx.saveDurableJob(taskId, next);
@@ -152,8 +166,19 @@ export function authorizeJobStart(store, job) {
152
166
  if (jobAuthorityBinding(store, scope, role, job.taskId) !== job.operation.authorityRef) {
153
167
  throw jobDomainError("Job caller binding was revoked; no execution was started.");
154
168
  }
169
+ requireJobAssignment(store, job, { scope, role });
155
170
  validateJobTarget(store, job);
156
171
  }
172
+ function requireJobAssignment(store, target, caller) {
173
+ try {
174
+ assertJobAssignmentScope(store, target, caller);
175
+ }
176
+ catch (error) {
177
+ if (error instanceof JobAssignmentScopeError)
178
+ throw jobControlError("UNAUTHORIZED", error.message);
179
+ throw error;
180
+ }
181
+ }
157
182
  function jobAuthorityBinding(store, scope, roleName, taskId) {
158
183
  // A Host detach/reattach preserves the native Session and its queued work.
159
184
  // Authenticate the live launch at ingress, but bind accepted Jobs to the
@@ -328,8 +353,8 @@ function validateJobTarget(store, params) {
328
353
  * - `task` + mismatched taskId: rejected.
329
354
  * - A non-Leader Task caller requires its current active Assignment.
330
355
  * - A Leader Task caller requires its current, unrevoked native Session.
331
- * - `task`: authority inside the matching Task after current Session and
332
- * native Session verification; Role does not narrow it.
356
+ * - Non-Leader Task callers additionally pass the shared exact Assignment
357
+ * check for every Job operation and before a queued Job starts.
333
358
  */
334
359
  /**
335
360
  * Verify the caller's current native Session and Task scope. This local Home
@@ -470,28 +495,26 @@ export function parseDurableJobStartParams(value) {
470
495
  env,
471
496
  steps,
472
497
  caller,
473
- ...(record.requestId === undefined ? {} : {
474
- requestId: requiredId(record.requestId, "job.start requestId")
475
- }),
498
+ requestId: requiredId(record.requestId, "job.start requestId"),
476
499
  ...(retryOf === undefined ? {} : { retryOf })
477
500
  };
478
501
  }
479
502
  export function parseDurableJobRefParams(value) {
480
503
  if (typeof value !== "object" || value === null || Array.isArray(value)
481
- || Object.keys(value).length !== 2) {
504
+ || Object.keys(value).length !== 3) {
482
505
  throw jobControlError("INVALID_PARAMS", "DurableJob ref params are invalid.");
483
506
  }
484
507
  const record = value;
485
508
  return {
486
509
  taskId: requiredId(record.taskId, "DurableJob taskId"),
487
- jobId: requiredId(record.jobId, "DurableJob jobId")
510
+ jobId: requiredId(record.jobId, "DurableJob jobId"),
511
+ caller: parseCaller(record.caller)
488
512
  };
489
513
  }
490
514
  /**
491
515
  * rr8: `job.cancel` params carry the caller identity so the Controller can
492
- * bind the cancel request to the caller's managed scope. Distinct from
493
- * `parseDurableJobRefParams` (used by `job.get`) because cancel requires the
494
- * third `caller` key.
516
+ * bind the cancel request to the caller's managed scope. Reads also carry
517
+ * an explicit caller but use Context's read authority rather than write authority.
495
518
  */
496
519
  export function parseDurableJobCancelParams(value) {
497
520
  if (typeof value !== "object" || value === null || Array.isArray(value)
@@ -551,7 +574,7 @@ function parseSteps(value) {
551
574
  throw jobControlError("INVALID_PARAMS", "job.start steps are invalid.");
552
575
  }
553
576
  const record = entry;
554
- const allowed = new Set(["name", "command", "timeoutMs"]);
577
+ const allowed = new Set(["name", "command", "argv", "cwd", "env", "timeoutMs"]);
555
578
  for (const key of Object.keys(record)) {
556
579
  if (!allowed.has(key)) {
557
580
  throw jobControlError("INVALID_PARAMS", "job.start steps are invalid.");
@@ -559,6 +582,15 @@ function parseSteps(value) {
559
582
  }
560
583
  const name = requiredId(record.name, "job.start step name");
561
584
  const command = requiredId(record.command, "job.start step command");
585
+ const argv = record.argv === undefined ? undefined : record.argv;
586
+ if (argv !== undefined && (!Array.isArray(argv) || argv.length === 0
587
+ || argv.some(value => typeof value !== "string" || value.length === 0 || value.includes("\0")))) {
588
+ throw jobControlError("INVALID_PARAMS", `job.start step argv is invalid: ${name}.`);
589
+ }
590
+ const cwd = record.cwd === undefined ? undefined : requiredId(record.cwd, "job.start step cwd");
591
+ if (cwd !== undefined && !cwd.startsWith("/")) {
592
+ throw jobControlError("INVALID_PARAMS", `job.start step cwd must be absolute: ${name}.`);
593
+ }
562
594
  if (names.has(name)) {
563
595
  throw jobControlError("INVALID_PARAMS", `job.start step names must be unique: ${name}.`);
564
596
  }
@@ -572,6 +604,9 @@ function parseSteps(value) {
572
604
  const step = {
573
605
  name,
574
606
  command,
607
+ ...(argv === undefined ? {} : { argv: argv }),
608
+ ...(cwd === undefined ? {} : { cwd }),
609
+ ...(record.env === undefined ? {} : { env: parseStringMap(record.env, "job.start step env") }),
575
610
  ...(record.timeoutMs === undefined ? {} : { timeoutMs: record.timeoutMs })
576
611
  };
577
612
  steps.push(step);
@@ -12,14 +12,14 @@
12
12
  * cancelRequestedAt -> write cancel fence + SIGTERM
13
13
  */
14
14
  import { spawn } from "node:child_process";
15
- import { chmodSync, existsSync, mkdirSync, openSync, closeSync, readFileSync, statSync } from "node:fs";
15
+ import { chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, statSync } from "node:fs";
16
16
  import { join } from "node:path";
17
17
  import { fileURLToPath } from "node:url";
18
- import { writeTextFileAtomically } from "../storage/durableFile.js";
19
18
  import { cancelQueuedDurableJob, completeDurableJob, isDurableJobTerminal, markDurableJobUnknown, markDurableJobWakeupNotified, rejectQueuedDurableJob, startDurableJob, touchDurableJobHeartbeat } from "../job/durableJob.js";
20
- import { readLinuxProcessStartIdentity } from "./domainIdentity.js";
21
- import { wakeReason } from "../scheduler/wakeReason.js";
22
19
  import { recordOperationEvidence } from "../kernel/operationFacts.js";
20
+ import { wakeReason } from "../scheduler/wakeReason.js";
21
+ import { writeTextFileAtomically } from "../storage/durableFile.js";
22
+ import { readLinuxProcessStartIdentity } from "./domainIdentity.js";
23
23
  const DEFAULT_STEP_TIMEOUT_MS = 30 * 60_000;
24
24
  const HEARTBEAT_STALE_MS = 2 * 60_000;
25
25
  const SIGKILL_GRACE_MS = 30_000;
@@ -0,0 +1,100 @@
1
+ import { admitProviderRetry, cancelProviderRetry, providerRetryAttemptId, providerRetryPending } from "../runtime/providerRetry.js";
2
+ import { blockingProviderContinuations } from "../runtime/runtimeContinuationProjection.js";
3
+ import { validateExactRunReviewRound } from "../lifecycle/exactRunTerminalization.js";
4
+ import { isDeepStrictEqual } from "node:util";
5
+ export function ownedProviderInput(input) {
6
+ if (input.runId !== undefined && input.taskId !== undefined) {
7
+ return { kind: "run", taskId: input.taskId, runId: input.runId };
8
+ }
9
+ const wake = /^notification:([^/]+)\/([^/]+)\//u.exec(input.attemptId);
10
+ if (wake !== null && wake[1] === input.taskId) {
11
+ return { kind: "wake", taskId: wake[1], wakeId: wake[2] };
12
+ }
13
+ const message = /^global-input:([^/]+)\/([^/]+)$/u.exec(input.attemptId);
14
+ if (message !== null && message[1] === input.roleName && input.taskId === undefined) {
15
+ return { kind: "message", roleName: input.roleName, messageId: message[2] };
16
+ }
17
+ return input.boundedText === undefined ? undefined : { kind: "text", text: input.boundedText };
18
+ }
19
+ export function retryIntentBlocker(store, input, retry) {
20
+ if (input.taskId === undefined) {
21
+ const role = store.getGlobalRole(input.roleName);
22
+ const sessions = store.getGlobalRoleSessionSet(input.roleName);
23
+ if (role === null || sessions?.activeAgentId !== role.activeAgentId)
24
+ return "role-changed";
25
+ if (sessions.sessions[sessions.activeAgentId]?.effective.sourceDesiredRevision !== role.launchRevision)
26
+ return "role-configuration-changed";
27
+ if (store.listGlobalRoleMessages(input.roleName).some(message => Date.parse(message.createdAt) > Date.parse(retry.intentAt)
28
+ && message.delivery === undefined && message.notDelivered === undefined))
29
+ return "new-user-input";
30
+ return undefined;
31
+ }
32
+ const task = store.getTask(input.taskId);
33
+ if (task === null || !["active", "draft"].includes(task.status)
34
+ || task.executionGate.state !== "enabled")
35
+ return "task-execution-stopped";
36
+ const role = store.getRole(input.taskId, input.roleName);
37
+ const sessions = store.getTaskRoleSessionSet(input.taskId, input.roleName);
38
+ if (role === null || sessions?.activeAgentId !== role.activeAgentId)
39
+ return "role-changed";
40
+ if (sessions.sessions[sessions.activeAgentId]?.effective.sourceDesiredRevision !== role.launchRevision)
41
+ return "role-configuration-changed";
42
+ if (store.listMessages(input.taskId).some(message => (message.kind === "operator" || message.kind === "user")
43
+ && Date.parse(message.createdAt) > Date.parse(retry.intentAt)))
44
+ return "new-user-input";
45
+ if (blockingProviderContinuations(store.listEvents(input.taskId)).some(c => c.roleName === input.roleName && c.identity.conversationId === retry.nativeSessionId)) {
46
+ return "background-work-unsettled";
47
+ }
48
+ return undefined;
49
+ }
50
+ /** Shared by automatic recovery and every ordinary Host submit. The native
51
+ * writer CAS remains the only grant; a scheduled successor is not a grant. */
52
+ export function admitOwnedProviderInput(store, binding, input) {
53
+ const retry = binding.retry;
54
+ const sameReviewSuccessor = retry?.successorReviewRoundId !== undefined && input.taskId !== undefined && input.runId !== undefined
55
+ && store.getRun(input.taskId, input.runId)?.reviewRoundId === retry.successorReviewRoundId;
56
+ if (retry !== undefined && !providerRetryPending(binding)
57
+ && (input.attemptId.startsWith("provider-retry:") || input.runId !== undefined && input.runId === retry.successorRunId
58
+ || sameReviewSuccessor)) {
59
+ throw new Error(`Provider retry is ${retry.status}; this scheduled successor cannot regain admission.`);
60
+ }
61
+ if (!providerRetryPending(binding) || retry === undefined)
62
+ return binding;
63
+ const automatic = input.attemptId === providerRetryAttemptId(retry)
64
+ || input.runId !== undefined && input.runId === retry.successorRunId
65
+ || input.runId !== undefined && input.taskId !== undefined && retry.successorReviewRoundId !== undefined
66
+ && store.getRun(input.taskId, input.runId)?.reviewRoundId === retry.successorReviewRoundId
67
+ || retry.status === "in-flight" && input.attemptId === retry.currentAttemptId;
68
+ if (!automatic)
69
+ return cancelProviderRetry(binding, "superseded-by-explicit-input", input.now.getTime());
70
+ const blocker = retryIntentBlocker(store, input, retry);
71
+ if (blocker !== undefined)
72
+ throw new Error(`Provider retry admission stopped: ${blocker}.`);
73
+ if (input.retrySupported !== true)
74
+ throw new Error("This Host cannot prove safe same-Session recovery; use explicit recovery.");
75
+ if (binding.goal?.status === "active")
76
+ throw new Error("Provider still owns an autonomous Goal; inspect its exact continuation.");
77
+ if (input.taskId !== undefined && input.runId !== undefined) {
78
+ const run = store.getRun(input.taskId, input.runId);
79
+ const sessions = store.getTaskRoleSessionSet(input.taskId, input.roleName);
80
+ if (run === null || run.status !== "active"
81
+ || !isDeepStrictEqual(run.effective, sessions?.sessions[input.agentId]?.effective)) {
82
+ throw new Error("Retry execution no longer matches its fixed native Session.");
83
+ }
84
+ if (run.workspace !== undefined && !isDeepStrictEqual(store.getManagedWorkspace(run.workspace.owner), run.workspace)) {
85
+ throw new Error("Retry workspace authority changed.");
86
+ }
87
+ if (run.workItemId !== undefined) {
88
+ const item = store.getWorkItem(input.taskId, run.workItemId);
89
+ if (item?.status !== "open" || run.purpose !== "review" && run.executionLaneId === undefined
90
+ && item.assignee !== run.roleName)
91
+ throw new Error("Retry WorkItem responsibility changed.");
92
+ }
93
+ if (validateExactRunReviewRound(store, run).disposition !== "applied") {
94
+ throw new Error("Retry Review no longer matches its exact Round and frozen candidate.");
95
+ }
96
+ }
97
+ const admitted = admitProviderRetry(binding, { attemptId: input.attemptId, at: input.now.getTime() });
98
+ return input.runId === undefined ? admitted
99
+ : { ...admitted, retry: { ...admitted.retry, successorRunId: input.runId } };
100
+ }
@@ -0,0 +1,218 @@
1
+ import { performance } from "node:perf_hooks";
2
+ import { runTaskCommand } from "../commands/taskCommands.js";
3
+ import { enqueueWork } from "../coordination/workMailboxQueue.js";
4
+ import { createTaskEvent } from "../event/taskEvent.js";
5
+ import { settleGlobalRetryInput } from "../message/globalProviderRetry.js";
6
+ import { redactAgentErrorText } from "../runtime/agentError.js";
7
+ import { AGENT_HOST_CONTROL_PROTOCOL, agentHostControlSocketPath, inspectAgentHostSocket, sendAgentHostRunControl } from "../runtime/agentHost.js";
8
+ import { hasRuntimeCleanupObligation, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
9
+ import { cancelProviderRetry, providerRetryAttemptId, providerRetryPending } from "../runtime/providerRetry.js";
10
+ import { currentProviderConversation } from "../runtime/providerRuntimeIdentity.js";
11
+ import { retryIntentBlocker } from "./providerRetryAdmission.js";
12
+ /** Hooks in the existing Controller pass and nearest-deadline timer. SQLite's
13
+ * current Provider input is the only pending intent; nothing is queued here. */
14
+ export function createProviderRetryHooks(home, store, ports = {}) {
15
+ const anchors = new Map();
16
+ const inspectedUnknown = new Set();
17
+ const inspect = ports.inspect ?? ((target) => inspectAgentHostSocket(agentHostControlSocketPath(target), 250));
18
+ const now = (retry) => {
19
+ const wall = (ports.now ?? (() => new Date()))().getTime();
20
+ const monotonic = (ports.monotonicNow ?? (() => performance.now()))();
21
+ let anchor = anchors.get(retry.chainId);
22
+ if (anchor === undefined) {
23
+ anchor = { wall: Math.max(wall, Date.parse(retry.updatedAt)), monotonic };
24
+ anchors.set(retry.chainId, anchor);
25
+ }
26
+ return Math.max(wall, anchor.wall + Math.max(0, monotonic - anchor.monotonic));
27
+ };
28
+ const load = (owner) => owner.scope === "task"
29
+ ? store.getTaskRoleSessionSet(owner.taskId, owner.roleName) : store.getGlobalRoleSessionSet(owner.roleName);
30
+ const save = (set) => {
31
+ if (set.owner.scope === "task")
32
+ store.saveTaskRoleSessionSet(set);
33
+ else
34
+ store.saveGlobalRoleSessionSet(set);
35
+ };
36
+ const stop = (set, reason, at, exhausted = false) => {
37
+ store.transaction(() => {
38
+ const current = load(set.owner);
39
+ if (current === null || current.providerBinding?.retry?.chainId !== set.providerBinding?.retry?.chainId)
40
+ return;
41
+ const binding = current.providerBinding;
42
+ if (!providerRetryPending(binding))
43
+ return;
44
+ const cancelled = cancelProviderRetry(binding, reason, at);
45
+ save({ ...current, providerBinding: exhausted
46
+ ? { ...cancelled, retry: { ...cancelled.retry, status: "exhausted" } } : cancelled });
47
+ const owner = set.owner;
48
+ if (owner.scope === "global")
49
+ settleGlobalRetryInput(store, owner.roleName, load(owner)?.providerBinding, new Date(at));
50
+ if (owner.scope === "task") {
51
+ const event = createTaskEvent(store.nextEventId(owner.taskId), owner.taskId, "provider.retry-stopped", {
52
+ roleName: owner.roleName, chainId: binding.retry.chainId, reason
53
+ }, new Date(at));
54
+ store.saveEvent(owner.taskId, event);
55
+ enqueueWork(store, owner.roleName === "leader" ? { kind: "operator" }
56
+ : { kind: "role", taskId: owner.taskId, roleName: "leader" }, "provider-retry-needs-attention", new Date(at), [{ type: "event", taskId: owner.taskId, id: event.id }]);
57
+ }
58
+ });
59
+ };
60
+ const key = (set) => set.owner.scope === "task"
61
+ ? `role:${encodeURIComponent(set.owner.taskId)}/${encodeURIComponent(set.owner.roleName)}`
62
+ : `global-role:${encodeURIComponent(set.owner.roleName)}`;
63
+ return {
64
+ deadlines() {
65
+ return store.listProviderRetrySessions().flatMap(set => {
66
+ const retry = set.providerBinding.retry;
67
+ if (retry.status !== "waiting")
68
+ return [];
69
+ // Busy or already-queued work awaits the next ordinary pass without a
70
+ // zero-delay timer loop. Waiting never consumes an execution attempt.
71
+ const wall = (ports.now ?? (() => new Date()))().getTime();
72
+ const effective = now(retry);
73
+ return [{ key: key(set), at: wall + Math.max(250, (retry.successorAutomatic === false ? Date.parse(retry.nextEligibleAt)
74
+ : Math.min(Date.parse(retry.nextEligibleAt), Date.parse(retry.deadline))) - effective) }];
75
+ });
76
+ },
77
+ async reconcile() {
78
+ const current = store.listProviderRetrySessions();
79
+ const activeChains = new Set(current.map(set => set.providerBinding.retry.chainId));
80
+ for (const id of anchors.keys())
81
+ if (!activeChains.has(id))
82
+ anchors.delete(id);
83
+ const pendingAttempts = new Set(current.map(set => set.providerBinding.retry.currentAttemptId));
84
+ for (const id of inspectedUnknown)
85
+ if (!pendingAttempts.has(id))
86
+ inspectedUnknown.delete(id);
87
+ for (const set of current) {
88
+ const owner = set.owner;
89
+ const binding = set.providerBinding;
90
+ const retry = binding.retry;
91
+ const at = now(retry);
92
+ const scoped = { ...(owner.scope === "task" ? { taskId: owner.taskId } : {}), roleName: owner.roleName };
93
+ const session = set.sessions[set.activeAgentId];
94
+ const blocker = retryIntentBlocker(store, scoped, retry);
95
+ if (blocker !== undefined && blocker !== "background-work-unsettled") {
96
+ stop(set, blocker, at);
97
+ continue;
98
+ }
99
+ if (binding.retryDisabled && retry.successorAutomatic !== false || binding.authority.owner !== "controller"
100
+ || binding.authority.epoch !== retry.authorityEpoch
101
+ || currentProviderConversation(binding).conversationId !== retry.nativeSessionId
102
+ || session?.status !== "active" || session.nativeSessionId !== retry.nativeSessionId
103
+ || hasRuntimeCleanupObligation(store.getWorkMailbox(runtimeLifecycleTarget(owner)))) {
104
+ stop(set, "session-or-authority-changed", at);
105
+ continue;
106
+ }
107
+ // Never replay an admitted attempt. Existing exact runtime queries and
108
+ // terminal ingress resolve it, even across Controller restart.
109
+ if (retry.status === "in-flight") {
110
+ if (binding.run?.status === "delivery-unknown" && retry.currentAttemptId !== undefined
111
+ && !inspectedUnknown.has(retry.currentAttemptId)) {
112
+ inspectedUnknown.add(retry.currentAttemptId);
113
+ let detail;
114
+ try {
115
+ const host = await inspect({ home, ...scoped, scope: owner.scope });
116
+ detail = host.nativeSessionId === retry.nativeSessionId && host.attemptId === retry.currentAttemptId
117
+ ? `Exact Host query: ${host.state}; awaiting original canonical Provider evidence.`
118
+ : "Host query did not prove the original Session/input identity; no replay.";
119
+ }
120
+ catch (error) {
121
+ detail = `Original Host query unavailable; no replay: ${redactAgentErrorText(error instanceof Error ? error.message : String(error))}`;
122
+ }
123
+ store.transaction(() => {
124
+ const latest = load(owner);
125
+ if (latest === null || latest.providerBinding == null || latest.providerBinding.run === null
126
+ || latest.providerBinding.run.attemptId !== retry.currentAttemptId
127
+ || latest.providerBinding.run.status !== "delivery-unknown")
128
+ return;
129
+ save({ ...latest, providerBinding: { ...latest.providerBinding,
130
+ retry: { ...latest.providerBinding.retry, reason: detail } } });
131
+ if (owner.scope === "task") {
132
+ const event = createTaskEvent(store.nextEventId(owner.taskId), owner.taskId, "provider.retry-unconfirmed", {
133
+ roleName: owner.roleName, attemptId: retry.currentAttemptId, nativeSessionId: retry.nativeSessionId, detail
134
+ }, new Date(at));
135
+ store.saveEvent(owner.taskId, event);
136
+ enqueueWork(store, owner.roleName === "leader" ? { kind: "operator" }
137
+ : { kind: "role", taskId: owner.taskId, roleName: "leader" }, "provider-retry-needs-attention", new Date(at), [{ type: "event", taskId: owner.taskId, id: event.id }]);
138
+ }
139
+ });
140
+ }
141
+ continue;
142
+ }
143
+ if (retry.successorAutomatic !== false && at >= Date.parse(retry.deadline)) {
144
+ stop(set, "deadline-exhausted", at, true);
145
+ continue;
146
+ }
147
+ if (at < Date.parse(retry.nextEligibleAt) || blocker !== undefined || binding.goal?.status === "active")
148
+ continue;
149
+ if (binding.run?.attemptId !== retry.failedAttemptId) {
150
+ stop(set, "superseded-input", at);
151
+ continue;
152
+ }
153
+ try {
154
+ if (owner.scope === "task" && retry.previousRunId !== undefined) {
155
+ if (retry.successorRunId !== undefined || retry.successorReviewRoundId !== undefined)
156
+ continue;
157
+ const previous = store.getRun(owner.taskId, retry.previousRunId);
158
+ if (previous?.status === "active")
159
+ continue; // ordinary rejected-delivery terminal fold
160
+ if (previous?.status !== "failed") {
161
+ stop(set, "run-no-longer-failed", at);
162
+ continue;
163
+ }
164
+ const actualTaskReviewCandidate = await ports.snapshotTaskCandidate?.(previous);
165
+ store.transaction(() => {
166
+ const latest = load(owner)?.providerBinding?.retry;
167
+ if (latest?.chainId !== retry.chainId || latest.failedAttemptId !== retry.failedAttemptId
168
+ || latest.status !== "waiting")
169
+ return;
170
+ const reason = retryIntentBlocker(store, scoped, latest);
171
+ if (reason !== undefined)
172
+ throw new Error(reason);
173
+ runTaskCommand(["run", "retry", `${owner.taskId}/${previous.id}`], store, {
174
+ now: () => new Date(at), environment: {}, providerRetryChainId: retry.chainId,
175
+ ...(actualTaskReviewCandidate === undefined ? {} : { actualTaskReviewCandidate })
176
+ });
177
+ });
178
+ }
179
+ else {
180
+ const original = retry.input.kind === "text" ? retry.input.text
181
+ : retry.input.kind === "wake"
182
+ ? `Yui Task notification: task=${retry.input.taskId} wake=${retry.input.wakeId}.\nRead current Task context and that fixed wake window, including original Messages in full. This is not an execution assignment.`
183
+ : retry.input.kind === "message"
184
+ ? `Yui Global Message: role=${retry.input.roleName} message=${retry.input.messageId}.\nRead your Session Context and referenced original Message in full.`
185
+ : undefined;
186
+ if (original === undefined) {
187
+ stop(set, "original-input-unavailable", at);
188
+ continue;
189
+ }
190
+ const result = await (ports.submit ?? sendAgentHostRunControl)({
191
+ home, ...scoped, scope: owner.scope,
192
+ control: {
193
+ protocol: AGENT_HOST_CONTROL_PROTOCOL, type: "submit-turn", nativeSessionId: retry.nativeSessionId,
194
+ authority: { epoch: binding.authority.epoch, owner: "controller", holderId: binding.authority.holderId },
195
+ run: { attemptId: providerRetryAttemptId(retry), boundedText: original }
196
+ }
197
+ });
198
+ // Durable settlement belongs to Host/Controller ingress. In
199
+ // particular a lost response is never a reason to replay.
200
+ if (result.outcome === "rejected" && result.failure?.registrationDisposition === "not-committed"
201
+ && result.failure.errorName !== "ProviderTurnBusyError") {
202
+ stop(set, result.failure.detail, at);
203
+ }
204
+ }
205
+ }
206
+ catch (error) {
207
+ // A send failure may have applied. A durable submitting/unknown
208
+ // identity fences it; do not manufacture a successor to uncertainty.
209
+ const latest = load(owner);
210
+ if (latest?.providerBinding?.run?.attemptId === retry.failedAttemptId) {
211
+ stop(set, error instanceof Error ? error.message : String(error), at);
212
+ }
213
+ ports.onError?.(error);
214
+ }
215
+ }
216
+ }
217
+ };
218
+ }