@zq-silk/yui 0.15.7 → 0.15.8

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 (281) hide show
  1. package/ARCHITECTURE.md +192 -399
  2. package/README.md +127 -1149
  3. package/dist/agent/adapterCatalog.js +15 -2
  4. package/dist/agent/agent.js +23 -3
  5. package/dist/agent/argumentPolicy.js +7 -1
  6. package/dist/agent/connectionPlan.js +62 -0
  7. package/dist/agent/executionComponents.js +158 -0
  8. package/dist/agent/launchEnvironment.js +31 -3
  9. package/dist/agent/managedRuntimeEnvironment.js +3 -5
  10. package/dist/{turn/turn.js → agentRun/agentRun.js} +166 -109
  11. package/dist/{turn/turnIdentity.js → agentRun/runIdentity.js} +4 -4
  12. package/dist/brief/taskBrief.js +12 -0
  13. package/dist/cli/agentConfigurationPicker.js +13 -0
  14. package/dist/cli/commandCatalog.js +157 -70
  15. package/dist/cli/interactionCandidates.js +5 -5
  16. package/dist/cli/interactionPolicy.js +38 -8
  17. package/dist/cli/invocationRouter.js +1 -1
  18. package/dist/cli/managedDiagnostics.js +28 -0
  19. package/dist/cli/operatorWizard.js +1 -7
  20. package/dist/cli/roleOptionOrder.js +27 -0
  21. package/dist/cli/roleWizard.js +50 -14
  22. package/dist/cli/updateOrchestrator.js +1 -1
  23. package/dist/cli/updatePorts.js +3 -4
  24. package/dist/cli.js +178 -95
  25. package/dist/commands/agentCommands.js +72 -14
  26. package/dist/commands/capabilityCommands.js +9 -6
  27. package/dist/commands/configCommands.js +20 -20
  28. package/dist/commands/deliveryGuardPreflight.js +2 -2
  29. package/dist/commands/executionAuditCommands.js +24 -24
  30. package/dist/commands/globalRoleCommands.js +1 -1
  31. package/dist/commands/grantCommands.js +4 -4
  32. package/dist/commands/operatorCommands.js +1 -7
  33. package/dist/commands/projectCommands.js +4 -4
  34. package/dist/commands/resourcesCommands.js +2 -2
  35. package/dist/commands/roleConfiguration.js +25 -5
  36. package/dist/commands/roleRuntimeGuard.js +4 -5
  37. package/dist/commands/sessionCommands.js +3 -7
  38. package/dist/commands/taskActivationCommands.js +259 -0
  39. package/dist/commands/taskActor.js +28 -49
  40. package/dist/commands/taskCommands.js +1188 -710
  41. package/dist/commands/taskContextCommand.js +39 -583
  42. package/dist/commands/taskExecutionCommands.js +32 -32
  43. package/dist/commands/taskInputCommands.js +40 -104
  44. package/dist/commands/taskIntegrationCommands.js +3 -2
  45. package/dist/commands/taskIntegrationQueueCommands.js +1 -1
  46. package/dist/commands/taskNextActionCommand.js +8 -8
  47. package/dist/commands/taskOverviewCommand.js +33 -45
  48. package/dist/commands/taskRemoteDeliveryCommand.js +2 -2
  49. package/dist/commands/taskRoleRuntimeStatus.js +133 -102
  50. package/dist/commands/telemetryCommands.js +36 -38
  51. package/dist/config/configCatalog.js +4 -4
  52. package/dist/config/yuiConfig.js +8 -8
  53. package/dist/context/contextSnapshot.js +10 -10
  54. package/dist/context/dispatchContext.js +11 -11
  55. package/dist/context/roleSessionContext.js +6 -3
  56. package/dist/context/{turnContextPack.js → runContextPack.js} +146 -81
  57. package/dist/context/{turnInputContract.js → runInputContract.js} +73 -60
  58. package/dist/context/sessionBootstrapManifest.js +21 -2
  59. package/dist/context/sourceRunContext.js +30 -0
  60. package/dist/context/taskContext.js +458 -0
  61. package/dist/context/wakeNotification.js +27 -27
  62. package/dist/controller/agentRuntimeObserver.js +21 -24
  63. package/dist/controller/capabilityBridge.js +17 -6
  64. package/dist/controller/clientRuntime.js +65 -92
  65. package/dist/controller/controller.js +114 -188
  66. package/dist/controller/fileSchedulerStoreAdapter.js +787 -887
  67. package/dist/controller/jobControl.js +54 -85
  68. package/dist/controller/resourceInventory.js +8 -27
  69. package/dist/controller/resourceInventoryLinux.js +12 -13
  70. package/dist/controller/runtime.js +529 -479
  71. package/dist/controller/runtimeEventInbox.js +55 -25
  72. package/dist/controller/runtimeEventProcessor.js +22 -31
  73. package/dist/controller/{runtimeHookTurnFence.js → runtimeHookRunFence.js} +91 -115
  74. package/dist/controller/runtimeLaunchCoordinator.js +80 -426
  75. package/dist/controller/runtimeObservationHook.js +14 -18
  76. package/dist/controller/sessionNotify.js +16 -24
  77. package/dist/controller/sessionOwnerReconciliation.js +168 -50
  78. package/dist/controller/structuredProviderObservation.js +138 -99
  79. package/dist/coordination/workMailbox.js +3 -3
  80. package/dist/coordination/workMailboxQueue.js +36 -33
  81. package/dist/core/boundedRpc.js +8 -1
  82. package/dist/core/controllerClient.js +20 -1
  83. package/dist/core/controllerServer.js +4 -4
  84. package/dist/doctor/doctor.js +13 -2
  85. package/dist/domain/agentResultTransport.js +9 -9
  86. package/dist/execution/codexThreadNaming.js +2 -8
  87. package/dist/execution/executionHealth.js +51 -63
  88. package/dist/execution/reviewMainRun.js +137 -0
  89. package/dist/execution/workItemExecution.js +28 -29
  90. package/dist/execution/workItemExecutionProjection.js +99 -107
  91. package/dist/execution/workItemMainRun.js +141 -0
  92. package/dist/executor/agentAdapter.js +227 -20
  93. package/dist/executor/agentConfigurationCatalog.js +126 -4
  94. package/dist/executor/agentConfigurationProbe.js +162 -4
  95. package/dist/executor/agentExecutor.js +79 -78
  96. package/dist/executor/effectiveLaunch.js +105 -18
  97. package/dist/executor/executorRegistry.js +29 -44
  98. package/dist/executor/fileRoleLaunchPlanner.js +229 -154
  99. package/dist/executor/workspacePreflightClassification.js +16 -16
  100. package/dist/grant/capabilityGrant.js +6 -3
  101. package/dist/input/inputRequest.js +12 -10
  102. package/dist/integration/gitIntegrationService.js +4 -11
  103. package/dist/integration/integrationQueueService.js +4 -4
  104. package/dist/interaction/operatorPresentation.js +1 -1
  105. package/dist/kernel/builtinCapabilities.js +247 -12
  106. package/dist/kernel/capabilityRegistry.js +64 -18
  107. package/dist/kernel/instanceHost.js +12 -1
  108. package/dist/kernel/kernelPorts.js +2 -2
  109. package/dist/lifecycle/canonicalLifecycleEvent.js +44 -49
  110. package/dist/lifecycle/exactRunTerminalization.js +449 -0
  111. package/dist/message/message.js +62 -6
  112. package/dist/message/messageContinuation.js +204 -0
  113. package/dist/observability/executionAudit.js +70 -72
  114. package/dist/observability/faultClassification.js +2 -2
  115. package/dist/observability/orchestrationMetrics.js +8 -8
  116. package/dist/operator/operatorSessionHistory.js +1 -7
  117. package/dist/output/agentConfigurationPresentation.js +8 -3
  118. package/dist/output/agentRunConfigurationPresentation.js +128 -0
  119. package/dist/output/rolePresentation.js +54 -3
  120. package/dist/plugins/pluginChild.js +104 -0
  121. package/dist/plugins/pluginIntent.js +26 -0
  122. package/dist/plugins/pluginInterpreter.js +43 -0
  123. package/dist/plugins/pluginPackage.js +101 -0
  124. package/dist/plugins/pluginProcess.js +112 -0
  125. package/dist/plugins/pluginService.js +380 -0
  126. package/dist/profile/agentProfile.js +1 -1
  127. package/dist/repository/gitWorkspace.js +26 -4
  128. package/dist/repository/project.js +19 -4
  129. package/dist/repository/taskBaseFreshness.js +13 -13
  130. package/dist/repository/taskWorkspaceCoordinator.js +20 -27
  131. package/dist/repository/taskWorkspacePreparer.js +344 -83
  132. package/dist/resources/autoResourceGc.js +3 -3
  133. package/dist/resources/liveReferences.js +3 -3
  134. package/dist/resources/projectResource.js +123 -0
  135. package/dist/resources/projectResourceService.js +421 -0
  136. package/dist/resources/resourceDiscovery.js +6 -6
  137. package/dist/resources/resourceGc.js +1 -1
  138. package/dist/resources/resourceRegistrar.js +1 -1
  139. package/dist/resources/resourceTypes.js +1 -1
  140. package/dist/review/deltaRecheck.js +3 -3
  141. package/dist/review/reviewAcceptance.js +16 -16
  142. package/dist/review/reviewDecision.js +7 -7
  143. package/dist/review/reviewRound.js +21 -20
  144. package/dist/review/reviewerAvailability.js +2 -2
  145. package/dist/role/role.js +51 -7
  146. package/dist/role/taskRoleUpdate.js +30 -0
  147. package/dist/runtime/acpProtocol.js +425 -0
  148. package/dist/runtime/acpSession.js +731 -0
  149. package/dist/runtime/acpSessionConfiguration.js +260 -0
  150. package/dist/runtime/agentDriver.js +30 -11
  151. package/dist/runtime/agentEndpoint.js +278 -0
  152. package/dist/runtime/agentEndpointIdentity.js +86 -0
  153. package/dist/runtime/agentEndpointOwnership.js +239 -0
  154. package/dist/runtime/agentError.js +2 -10
  155. package/dist/runtime/agentHost.js +565 -314
  156. package/dist/runtime/agentRunConfiguration.js +258 -0
  157. package/dist/runtime/builtinAgentDrivers.js +134 -18
  158. package/dist/runtime/builtinAgentErrorMappers.js +55 -3
  159. package/dist/runtime/builtinTranscriptUsage.js +1 -1
  160. package/dist/runtime/claude-process-owner +0 -0
  161. package/dist/runtime/codexAppServerRuntime.js +38 -30
  162. package/dist/runtime/codexInteractiveHost.js +41 -6
  163. package/dist/runtime/continuationManager.js +2 -6
  164. package/dist/runtime/executionEnvironment.js +30 -0
  165. package/dist/runtime/firstProgressAdvisory.js +11 -11
  166. package/dist/runtime/index.js +4 -3
  167. package/dist/runtime/jsonLineChannel.js +109 -0
  168. package/dist/runtime/launchBroker.js +91 -16
  169. package/dist/runtime/launchDiagnostics.js +2 -2
  170. package/dist/runtime/lifecycleReservation.js +10 -18
  171. package/dist/runtime/managedCaller.js +61 -17
  172. package/dist/runtime/nativeSessionControl.js +102 -0
  173. package/dist/runtime/ports.js +6 -21
  174. package/dist/runtime/processExitObservation.js +8 -7
  175. package/dist/runtime/promptEnvelope.js +17 -6
  176. package/dist/runtime/providerContinuation.js +3 -9
  177. package/dist/runtime/providerContinuationReconciliationService.js +4 -13
  178. package/dist/runtime/providerControl.js +2 -7
  179. package/dist/runtime/providerRuntimeIdentity.js +110 -222
  180. package/dist/runtime/providerRuntimeReconciler.js +5 -9
  181. package/dist/runtime/runtimeBinding.js +0 -1
  182. package/dist/runtime/runtimeContinuationProjection.js +4 -7
  183. package/dist/runtime/runtimeDeadlines.js +9 -0
  184. package/dist/runtime/runtimeHealthPolicy.js +1 -1
  185. package/dist/runtime/runtimeObservation.js +29 -65
  186. package/dist/runtime/runtimeProjection.js +43 -51
  187. package/dist/runtime/runtimeSessionCandidate.js +1 -3
  188. package/dist/runtime/sessionLaunchRequest.js +3 -7
  189. package/dist/runtime/sessionOwnerIdentity.js +7 -54
  190. package/dist/runtime/sessionOwnerRegistry.js +22 -17
  191. package/dist/runtime/sessionReconciliation.js +4 -8
  192. package/dist/runtime/sessionTerminationGuard.js +70 -259
  193. package/dist/runtime/sessionTokenMetrics.js +5 -16
  194. package/dist/runtime/structuredProviderHost.js +237 -117
  195. package/dist/runtime/taskRuntimeIsolation.js +39 -122
  196. package/dist/runtime/tmuxAdapters.js +39 -86
  197. package/dist/scheduler/activeRoleRunDelivery.js +354 -0
  198. package/dist/scheduler/leaderWakeupProcessor.js +75 -266
  199. package/dist/scheduler/operatorInputNotificationProcessor.js +1 -1
  200. package/dist/scheduler/ports.js +80 -9
  201. package/dist/scheduler/{roleTurnLiveness.js → roleRunLiveness.js} +26 -30
  202. package/dist/scheduler/{roleTurnStall.js → roleRunStall.js} +128 -139
  203. package/dist/scheduler/taskExecutionProjection.js +120 -124
  204. package/dist/scheduler/taskObservabilityProjection.js +29 -29
  205. package/dist/scheduler/taskWake.js +11 -4
  206. package/dist/scheduler/wakeReason.js +9 -1
  207. package/dist/setup/setupCommand.js +1 -0
  208. package/dist/storage/migrations/agentRunContract.js +159 -0
  209. package/dist/storage/migrations/removeRuntimeGeneration.js +207 -0
  210. package/dist/storage/sqliteSchema.js +431 -5
  211. package/dist/storage/sqliteStore.js +375 -220
  212. package/dist/storage/storageVersions.js +1 -1
  213. package/dist/storage/storeRpc.js +10 -5
  214. package/dist/storage/taskStore.js +13 -11
  215. package/dist/storage/upgrade/upgradeOrchestrator.js +5 -7
  216. package/dist/surface/surfaceContributions.js +102 -0
  217. package/dist/task/completionReadiness.js +32 -6
  218. package/dist/task/deliveryGuard.js +16 -16
  219. package/dist/task/draftPlan.js +72 -12
  220. package/dist/task/nextAction.js +144 -128
  221. package/dist/task/remoteDelivery.js +6 -6
  222. package/dist/task/task.js +184 -18
  223. package/dist/task/taskActivation.js +301 -0
  224. package/dist/task/taskActivationService.js +392 -0
  225. package/dist/task/taskRecordReference.js +5 -4
  226. package/dist/task/taskRecordRetirement.js +1 -1
  227. package/dist/telemetry/sqliteTelemetryStore.js +55 -68
  228. package/dist/telemetry/telemetryConfig.js +14 -14
  229. package/dist/telemetry/telemetryWiring.js +2 -2
  230. package/dist/web/assets/assetManifest.js +2 -0
  231. package/dist/web/assets/client/app.js +120 -20
  232. package/dist/web/assets/client/components.js +87 -54
  233. package/dist/web/assets/client/i18n.js +83 -41
  234. package/dist/web/assets/client/markdown.js +1 -1
  235. package/dist/web/assets/client/taskSurface.js +353 -0
  236. package/dist/web/assets/client/view.js +49 -44
  237. package/dist/web/assets/shell.js +1 -1
  238. package/dist/web/assets/styles/cards.js +22 -4
  239. package/dist/web/controllerWeb.js +60 -0
  240. package/dist/web/webMutation.js +28 -0
  241. package/dist/web/webServer.js +118 -8
  242. package/dist/web/webSnapshot.js +81 -74
  243. package/dist/web/webTaskSurface.js +64 -0
  244. package/dist/workItem/dependencyGate.js +1 -1
  245. package/dist/workItem/workItem.js +80 -48
  246. package/dist/workspace/workItemChangeSetManager.js +16 -9
  247. package/docs/agent-result-consumption.md +94 -0
  248. package/docs/agent-runtime-drivers.md +91 -0
  249. package/docs/architecture/README.md +38 -0
  250. package/docs/architecture/capabilities-and-resources.md +79 -0
  251. package/docs/managed-turn-and-session-runtime.md +222 -0
  252. package/docs/observability/README.md +81 -0
  253. package/docs/plugin-sdk.md +290 -0
  254. package/docs/provider-runtime.md +163 -0
  255. package/docs/release-workflow.md +303 -0
  256. package/docs/roles-and-configuration.md +113 -0
  257. package/docs/sqlite-control-plane-design.md +76 -0
  258. package/docs/task-dag-semantics.md +57 -0
  259. package/docs/task-delivery.md +103 -0
  260. package/docs/task-local-identity.md +6 -6
  261. package/docs/testing/verification-levels.md +86 -0
  262. package/i18n/README.zh-CN.md +98 -739
  263. package/package.json +2 -2
  264. package/skills/yui-leader/SKILL.md +130 -103
  265. package/skills/yui-leader/references/integration.md +39 -0
  266. package/skills/yui-leader/references/replicated-execution.md +42 -0
  267. package/skills/yui-leader/references/task-plugins.md +33 -0
  268. package/skills/yui-operator/SKILL.md +30 -59
  269. package/skills/yui-reviewer/SKILL.md +35 -36
  270. package/skills/yui-runtime/SKILL.md +88 -24
  271. package/skills/yui-runtime/references/publication.md +22 -0
  272. package/skills/yui-runtime/references/recovery.md +64 -0
  273. package/skills/yui-worker/SKILL.md +37 -39
  274. package/dist/cli/roleOptionCatalog.js +0 -68
  275. package/dist/context/sourceTurnContext.js +0 -30
  276. package/dist/execution/reviewMainTurn.js +0 -161
  277. package/dist/execution/workItemMainTurn.js +0 -164
  278. package/dist/lifecycle/exactTurnTerminalization.js +0 -407
  279. package/dist/runtime/preallocatedNativeSession.js +0 -13
  280. package/dist/runtime/runtimeStopReceipt.js +0 -42
  281. package/dist/scheduler/activeRoleTurnDelivery.js +0 -315
@@ -1,24 +1,32 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
2
  import { chmodSync, mkdirSync, rmSync } from "node:fs";
3
- import { tmpdir } from "node:os";
3
+ import { tmpdir, homedir } from "node:os";
4
4
  import { dirname, join, resolve } from "node:path";
5
5
  import { createConnection, createServer } from "node:net";
6
6
  import { createInterface } from "node:readline";
7
+ import { assertAgentExecutionEnvironment } from "./executionEnvironment.js";
8
+ import { withSessionContextPointer } from "../context/sessionBootstrapManifest.js";
7
9
  import { callController, controllerCallMayHaveApplied, ControllerClientError } from "../core/controllerClient.js";
8
10
  import { readHomeFilesystemId } from "../core/homeFilesystemIdentity.js";
9
11
  import { callFileTaskController } from "../controller/clientRuntime.js";
10
12
  import { isForeignHandoverLockHeld } from "../release/runtimeRelease.js";
11
- import { publishStructuredProviderAccepted, publishStructuredProviderActivationTerminal, publishStructuredProviderGoal, publishStructuredConversationRecoverability, publishStructuredProviderOpened, publishStructuredProviderStarted, publishStructuredProviderTerminal } from "../controller/structuredProviderObservation.js";
13
+ import { publishStructuredProviderAccepted, publishStructuredProviderAttachmentExit, publishStructuredProviderActivity, publishStructuredProviderInputSettlement, publishStructuredProviderInputObserved, publishStructuredProviderGoal, publishStructuredConversationRecoverability, publishStructuredProviderOpened, publishStructuredProviderStarted, publishStructuredProviderTerminal } from "../controller/structuredProviderObservation.js";
12
14
  import { validateAgentHostLaunchPayload } from "./launchBroker.js";
13
- import { ProviderDeliveryUnknownError, ProviderConversationMissingError, ProviderTurnBusyError, ProviderTurnRejectedError, startStructuredProviderSession } from "./structuredProviderHost.js";
15
+ import { ProviderDeliveryUnknownError, ProviderConversationMissingError, ProviderTurnBusyError, ProviderTurnRejectedError } from "./structuredProviderHost.js";
16
+ import { createAgentEndpointOwner } from "./agentEndpointOwnership.js";
14
17
  import { sameProviderAuthorityFence, validateProviderAuthorityFence } from "./providerAuthorityFence.js";
15
18
  import { validateRuntimeProcessExitObservation } from "./processExitObservation.js";
16
19
  import { persistRuntimeProcessExitObservation, replayRuntimeProcessExitOutbox } from "./processExitOutbox.js";
17
- import { readRuntimeStopReceipt, removeRuntimeStopReceipt } from "./runtimeStopReceipt.js";
18
- import { AGENT_HOST_CONTROL_TIMEOUT_MS, AGENT_HOST_READY_TIMEOUT_MS } from "./runtimeDeadlines.js";
20
+ import { AGENT_HOST_CLIENT_EXIT_GRACE_MS, AGENT_HOST_CONTROL_TIMEOUT_MS, AGENT_HOST_READY_TIMEOUT_MS, ENDPOINT_DRAIN_TIMEOUT_MS, PROVIDER_ACCEPT_TIMEOUT_MS } from "./runtimeDeadlines.js";
19
21
  import { providerDeliveryFailure, providerDeliveryFailureFrom, redactAgentErrorText, serializeAgentErrorRaw } from "./agentError.js";
20
22
  import { runCodexInteractiveHost } from "./codexInteractiveHost.js";
21
- export const AGENT_HOST_CONTROL_PROTOCOL = "yui-agent-host/v4";
23
+ import { readAgentRunConfigurationObservation, unknownAgentRunConfiguration } from "./agentRunConfiguration.js";
24
+ import { openCurrentTaskStore } from "../storage/currentTaskStore.js";
25
+ import { createSessionOwnerIdentity, readLinuxProcessIdentity } from "./sessionOwnerIdentity.js";
26
+ import { yuiTmuxServerName, yuiTmuxSessionName } from "../tmux/tmuxManager.js";
27
+ import { tmuxSocketDirectory } from "../tmux/tmuxSocketEndpoint.js";
28
+ import { createTaskEvent } from "../event/taskEvent.js";
29
+ export const AGENT_HOST_CONTROL_PROTOCOL = "yui-agent-host/v5";
22
30
  const HOST_CONTROL_MAX_BYTES = 32 * 1024;
23
31
  const CODEX_CLIENT_STABLE_MS = 5_000;
24
32
  const MAX_CONSECUTIVE_CODEX_DISCONNECTS = 3;
@@ -28,20 +36,29 @@ export function serializeAgentHostLaunchControl(control) {
28
36
  export async function runAgentHost(input) {
29
37
  const hostInstanceId = randomUUID();
30
38
  let hostSequence = 0;
31
- let payload = await redeem(input.home, input.runtimeGenerationId, input.ticket);
39
+ let payload = await redeem(input.home, input.ticket);
32
40
  if (payload.environment.YUI_SESSION_SCOPE === "global"
33
41
  && payload.environment.YUI_ADAPTER_ID === "codex"
34
42
  && payload.providerControl === undefined) {
35
43
  return runCodexInteractiveHost(input.home, payload);
36
44
  }
37
45
  let session;
46
+ // This process really holds the Endpoint code its Session runs on. The lease
47
+ // spans the Session's Turns, so installing a newer generation does not change
48
+ // the implementation under a Session that is still using this one.
49
+ const endpointOwner = createAgentEndpointOwner();
50
+ let endpointLease;
51
+ /** The adapter this process actually pinned, so the drain below reports the
52
+ * implementation genuinely held rather than whichever one a later payload names. */
53
+ let endpointAdapterId;
38
54
  let sessionPayload;
39
- let activeTurnPayload;
40
- let activeTurnAttemptId;
55
+ let activeRunPayload;
56
+ let lastTerminalPayload;
57
+ let lastTerminal;
58
+ let activeRunAttemptId;
41
59
  let activeNativeTurnId;
42
60
  let codexClientAttachedAt;
43
61
  let consecutiveCodexDisconnects = 0;
44
- let activationId;
45
62
  let conversationRecoverability = "unknown";
46
63
  let authority;
47
64
  let hostStopRequested = false;
@@ -53,16 +70,55 @@ export async function runAgentHost(input) {
53
70
  const updateSnapshot = (next) => {
54
71
  snapshot = validateSnapshot(next);
55
72
  };
56
- const authorityFields = () => authority === undefined ? {} : {
57
- authorityEpoch: authority.epoch,
58
- authorityOwner: authority.owner,
59
- authorityHolderId: authority.holderId
73
+ const authorityFields = () => ({
74
+ ...(session === undefined ? {} : { endpointImplementation: session.configuration.implementation }),
75
+ ...(authority === undefined ? {} : {
76
+ authorityEpoch: authority.epoch,
77
+ authorityOwner: authority.owner,
78
+ authorityHolderId: authority.holderId
79
+ })
80
+ });
81
+ const handleAccepted = (receipt) => {
82
+ const observedPayload = activeRunPayload;
83
+ if (observedPayload === undefined || receipt.acceptance !== "provider"
84
+ || receipt.attemptId !== activeRunAttemptId
85
+ || receipt.nativeSessionId !== session?.nativeSessionId)
86
+ return;
87
+ void enqueueSerialized(async () => {
88
+ if (activeRunPayload !== observedPayload || activeRunAttemptId !== receipt.attemptId
89
+ || snapshot.attemptId === receipt.attemptId && snapshot.inputAcceptance === "provider")
90
+ return;
91
+ await publishStructuredProviderAccepted({
92
+ home: input.home, environment: observedPayload.environment, receipt
93
+ });
94
+ updateSnapshot(hostSnapshot("ready", {
95
+ adapterId: session.adapterId,
96
+ processInstanceId: session.processInstanceId,
97
+ nativeSessionId: receipt.nativeSessionId,
98
+ conversationId: receipt.conversationId,
99
+ attemptId: receipt.attemptId,
100
+ nativeTurnId: receipt.nativeTurnId,
101
+ inputAcceptance: "provider",
102
+ ...authorityFields()
103
+ }));
104
+ signalRoleMailbox(input.home, observedPayload);
105
+ }).catch(() => { });
106
+ };
107
+ const handleActivity = (activity) => {
108
+ const observedPayload = activeRunPayload;
109
+ if (observedPayload === undefined || activity.attemptId !== activeRunAttemptId
110
+ || activity.nativeSessionId !== session?.nativeSessionId)
111
+ return;
112
+ void enqueueSerialized(async () => {
113
+ if (activeRunPayload !== observedPayload || activity.attemptId !== activeRunAttemptId)
114
+ return;
115
+ await publishStructuredProviderActivity({ home: input.home, environment: observedPayload.environment, activity });
116
+ }).catch(() => { });
60
117
  };
61
118
  const handleStarted = (started, observedPayload = sessionPayload) => {
62
119
  if (started.clientOwned || observedPayload === undefined)
63
120
  return;
64
121
  updateSnapshot(hostSnapshot("busy", {
65
- runtimeGenerationId: observedPayload.runtimeGenerationId,
66
122
  adapterId: observedPayload.providerControl?.adapterId,
67
123
  processInstanceId: session?.processInstanceId,
68
124
  nativeSessionId: started.nativeSessionId,
@@ -70,12 +126,10 @@ export async function runAgentHost(input) {
70
126
  nativeTurnId: started.nativeTurnId,
71
127
  ...authorityFields()
72
128
  }));
73
- const directActivationId = activationId ?? observedPayload.runtimeGenerationId;
74
129
  void enqueueSerialized(async () => {
75
130
  await publishStructuredProviderStarted({
76
131
  home: input.home,
77
132
  environment: observedPayload.environment,
78
- activationId: directActivationId,
79
133
  started
80
134
  });
81
135
  signalRoleMailbox(input.home, observedPayload);
@@ -89,19 +143,16 @@ export async function runAgentHost(input) {
89
143
  || terminal.conversationId !== session.conversationId)
90
144
  return;
91
145
  // A direct Provider client shares the Session but still produces a Yui
92
- // Turn audit record. It has no WorkItem ownership and no managed input
146
+ // AgentRun audit record. It has no WorkItem ownership and no managed input
93
147
  // fence; the runtime observer records only visible input/output.
94
- const directActivationId = activationId ?? busyPayload.runtimeGenerationId;
95
148
  void enqueueSerialized(async () => {
96
149
  await publishStructuredProviderTerminal({
97
150
  home: input.home,
98
151
  environment: busyPayload.environment,
99
- activationId: directActivationId,
100
152
  terminal
101
153
  });
102
154
  if (session !== undefined && terminal.conversationId === session.conversationId) {
103
- updateSnapshot(hostSnapshot(session.activeTurnId === undefined ? "idle" : "busy", {
104
- runtimeGenerationId: busyPayload.runtimeGenerationId,
155
+ updateSnapshot(hostSnapshot(session.inspect().activeNativeTurnId === undefined ? "idle" : "busy", {
105
156
  adapterId: session.adapterId,
106
157
  processInstanceId: session.processInstanceId,
107
158
  nativeSessionId: session.nativeSessionId,
@@ -113,18 +164,17 @@ export async function runAgentHost(input) {
113
164
  }).catch(() => { });
114
165
  return;
115
166
  }
116
- const terminalPayload = activeTurnPayload;
117
- if (terminal.attemptId !== undefined && terminal.attemptId !== activeTurnAttemptId
167
+ const terminalPayload = activeRunPayload;
168
+ if (terminal.attemptId !== undefined && terminal.attemptId !== activeRunAttemptId
118
169
  && sessionPayload !== undefined && terminal.nativeSessionId === session?.nativeSessionId) {
119
170
  // A duplicate/late terminal retains the Driver's exact old attempt.
120
171
  // The durable observer validates that binding; it must not alter the
121
- // current Host occupancy or inherit the successor's managed Turn.
172
+ // current Host occupancy or inherit the successor's managed AgentRun.
122
173
  const observedPayload = sessionPayload;
123
174
  void enqueueSerialized(async () => {
124
175
  await publishStructuredProviderTerminal({
125
176
  home: input.home,
126
177
  environment: observedPayload.environment,
127
- activationId: activationId ?? observedPayload.runtimeGenerationId,
128
178
  terminal
129
179
  });
130
180
  signalRoleMailbox(input.home, observedPayload);
@@ -132,20 +182,20 @@ export async function runAgentHost(input) {
132
182
  return;
133
183
  }
134
184
  if (terminalPayload === undefined
135
- || terminal.attemptId !== activeTurnAttemptId
185
+ || terminal.attemptId !== activeRunAttemptId
136
186
  || terminal.nativeSessionId !== session?.nativeSessionId
137
187
  || (terminal.nativeTurnId !== undefined && activeNativeTurnId !== undefined
138
188
  && terminal.nativeTurnId !== activeNativeTurnId))
139
189
  return;
140
190
  const terminalAttemptId = terminal.attemptId;
141
- const terminalActivationId = activationId ?? terminalPayload.runtimeGenerationId;
191
+ lastTerminalPayload = terminalPayload;
192
+ lastTerminal = terminal;
142
193
  void enqueueSerialized(async () => {
143
- if (activeTurnPayload !== terminalPayload
144
- || activeTurnAttemptId !== terminalAttemptId)
194
+ if (activeRunPayload !== terminalPayload
195
+ || activeRunAttemptId !== terminalAttemptId)
145
196
  return;
146
197
  if (session !== undefined) {
147
198
  updateSnapshot(hostSnapshot("settling", {
148
- runtimeGenerationId: terminalPayload.runtimeGenerationId,
149
199
  adapterId: terminalPayload.providerControl.adapterId,
150
200
  processInstanceId: session.processInstanceId,
151
201
  nativeSessionId: terminal.nativeSessionId,
@@ -159,19 +209,17 @@ export async function runAgentHost(input) {
159
209
  await publishStructuredProviderTerminal({
160
210
  home: input.home,
161
211
  environment: terminalPayload.environment,
162
- activationId: terminalActivationId,
163
212
  terminal
164
213
  });
165
- if (activeTurnPayload !== terminalPayload)
214
+ if (activeRunPayload !== terminalPayload)
166
215
  return;
167
- activeTurnPayload = undefined;
168
- activeTurnAttemptId = undefined;
216
+ activeRunPayload = undefined;
217
+ activeRunAttemptId = undefined;
169
218
  activeNativeTurnId = undefined;
170
219
  if (session === undefined)
171
220
  return;
172
221
  const currentPayload = sessionPayload ?? terminalPayload;
173
- updateSnapshot(hostSnapshot(session.activeTurnId === undefined ? "idle" : "busy", {
174
- runtimeGenerationId: currentPayload.runtimeGenerationId,
222
+ updateSnapshot(hostSnapshot(session.inspect().activeNativeTurnId === undefined ? "idle" : "busy", {
175
223
  adapterId: currentPayload.providerControl.adapterId,
176
224
  processInstanceId: session.processInstanceId,
177
225
  nativeSessionId: terminal.nativeSessionId,
@@ -182,7 +230,6 @@ export async function runAgentHost(input) {
182
230
  }
183
231
  catch (error) {
184
232
  updateSnapshot(hostSnapshot("failed", {
185
- runtimeGenerationId: terminalPayload.runtimeGenerationId,
186
233
  adapterId: terminalPayload.providerControl.adapterId,
187
234
  processInstanceId: session?.processInstanceId,
188
235
  nativeSessionId: terminal.nativeSessionId,
@@ -195,16 +242,14 @@ export async function runAgentHost(input) {
195
242
  }).catch(() => { });
196
243
  };
197
244
  const handleGoal = (goal) => {
198
- const currentPayload = activeTurnPayload ?? sessionPayload;
245
+ const currentPayload = activeRunPayload ?? sessionPayload;
199
246
  const currentSession = session;
200
247
  if (currentPayload === undefined || currentSession === undefined)
201
248
  return;
202
- const currentActivationId = activationId ?? currentPayload.runtimeGenerationId;
203
249
  void enqueueSerialized(async () => {
204
250
  await publishStructuredProviderGoal({
205
251
  home: input.home,
206
252
  environment: currentPayload.environment,
207
- activationId: currentActivationId,
208
253
  conversationId: currentSession.conversationId,
209
254
  goal
210
255
  });
@@ -216,17 +261,26 @@ export async function runAgentHost(input) {
216
261
  if (previousControl?.adapterId !== "codex" || authority === undefined) {
217
262
  throw new Error("Codex client reconnect lost its Provider control identity.");
218
263
  }
219
- const ownedTurn = activeTurnPayload === undefined
220
- || activeTurnAttemptId === undefined
264
+ const ownedTurn = activeRunPayload === undefined
265
+ || activeRunAttemptId === undefined
221
266
  || activeNativeTurnId === undefined
222
267
  ? undefined
223
- : { attemptId: activeTurnAttemptId, turnId: activeNativeTurnId };
268
+ : { attemptId: activeRunAttemptId, turnId: activeNativeTurnId };
224
269
  const reconnectPayload = {
225
270
  ...currentPayload,
226
- environment: activeTurnPayload?.environment ?? currentPayload.environment,
271
+ command: disconnectedSession.configuration.command,
272
+ args: disconnectedSession.configuration.args,
273
+ cwd: disconnectedSession.configuration.cwd,
274
+ environment: activeRunPayload?.environment ?? currentPayload.environment,
227
275
  providerControl: {
228
276
  schemaVersion: 1,
229
277
  adapterId: "codex",
278
+ // Reconnecting reaches the same product the disconnected Session was
279
+ // pinned to. Rebuilding the control without the component would quietly
280
+ // drop that identity halfway through a Session's life.
281
+ ...(previousControl.component === undefined
282
+ ? {}
283
+ : { component: previousControl.component }),
230
284
  transport: "codex-app-server-proxy",
231
285
  kind: "restore",
232
286
  mode: "resume",
@@ -234,7 +288,8 @@ export async function runAgentHost(input) {
234
288
  ...(previousControl.sessionTitle === undefined
235
289
  ? {}
236
290
  : { sessionTitle: previousControl.sessionTitle }),
237
- codexThread: previousControl.codexThread,
291
+ codexThread: disconnectedSession.configuration.threadOptions,
292
+ endpointImplementation: disconnectedSession.configuration.implementation,
238
293
  ...(ownedTurn === undefined ? {} : { ownedTurn }),
239
294
  authority
240
295
  }
@@ -249,42 +304,61 @@ export async function runAgentHost(input) {
249
304
  if (hostStopRequested)
250
305
  return;
251
306
  try {
252
- const started = await startStructuredProviderSession(reconnectPayload, {
253
- onStarted: (turn) => handleStarted(turn, currentPayload),
254
- onTerminal: handleTerminal,
255
- onGoal: handleGoal
256
- });
307
+ assertAgentExecutionEnvironment(input.home, reconnectPayload);
308
+ // Reattaching a dropped client continues the same Session on the same
309
+ // pinned generation; it never re-selects an implementation.
310
+ if (endpointLease === undefined) {
311
+ throw new Error("Codex client reconnect lost its Endpoint implementation lease.");
312
+ }
313
+ const started = await endpointLease.resume(reconnectPayload);
257
314
  session = started.session;
258
315
  sessionPayload = currentPayload;
316
+ started.session.events((event) => {
317
+ if (session !== started.session && event.type !== "terminal")
318
+ return;
319
+ if (event.type === "accepted")
320
+ handleAccepted(event.value);
321
+ else if (event.type === "activity")
322
+ handleActivity(event.value);
323
+ else if (event.type === "started")
324
+ handleStarted(event.value, currentPayload);
325
+ else if (event.type === "terminal")
326
+ handleTerminal(event.value);
327
+ else if (event.type === "goal")
328
+ handleGoal(event.value);
329
+ else
330
+ void enqueueSerialized(() => publishStructuredProviderInputObserved({
331
+ home: input.home, environment: currentPayload.environment, observed: event.value
332
+ })).catch(() => { });
333
+ });
259
334
  conversationRecoverability = "recoverable";
260
335
  codexClientAttachedAt = Date.now();
261
336
  observeExit(started.session, currentPayload);
262
- const reconnectState = activeTurnPayload !== undefined
337
+ const reconnectState = activeRunPayload !== undefined
263
338
  ? ownedTurn === undefined ? "delivery-unknown" : "ready"
264
- : started.session.activeTurnId === undefined ? "idle" : "busy";
339
+ : started.session.inspect().activeNativeTurnId === undefined ? "idle" : "busy";
265
340
  updateSnapshot(hostSnapshot(reconnectState, {
266
- runtimeGenerationId: currentPayload.runtimeGenerationId,
267
341
  adapterId: "codex",
268
342
  processInstanceId: started.session.processInstanceId,
269
343
  nativeSessionId: started.session.nativeSessionId,
270
344
  conversationId: started.session.conversationId,
271
- ...(activeTurnAttemptId === undefined ? {} : { attemptId: activeTurnAttemptId }),
345
+ ...(activeRunAttemptId === undefined ? {} : { attemptId: activeRunAttemptId }),
272
346
  ...(activeNativeTurnId === undefined ? {} : { nativeTurnId: activeNativeTurnId }),
273
347
  ...authorityFields(),
274
- ...(ownedTurn !== undefined || activeTurnPayload === undefined
348
+ ...(ownedTurn !== undefined || activeRunPayload === undefined
275
349
  ? {}
276
350
  : {
277
- detail: "Codex client reattached, but the in-flight Turn has no exact native identity."
351
+ detail: "Codex client reattached, but the in-flight AgentRun has no exact native identity."
278
352
  })
279
353
  }));
280
354
  if (started.recoveredTerminal !== undefined) {
281
355
  handleTerminal(started.recoveredTerminal);
282
356
  }
283
- if (ownedTurn === undefined && started.session.activeTurnId !== undefined) {
357
+ if (ownedTurn === undefined && started.session.inspect().activeNativeTurnId !== undefined) {
284
358
  handleStarted({
285
359
  conversationId: started.session.conversationId,
286
360
  nativeSessionId: started.session.nativeSessionId,
287
- nativeTurnId: started.session.activeTurnId,
361
+ nativeTurnId: started.session.inspect().activeNativeTurnId,
288
362
  clientOwned: false,
289
363
  observedAt: new Date().toISOString()
290
364
  }, currentPayload);
@@ -298,12 +372,11 @@ export async function runAgentHost(input) {
298
372
  }
299
373
  }
300
374
  updateSnapshot(hostSnapshot("failed", {
301
- runtimeGenerationId: currentPayload.runtimeGenerationId,
302
375
  adapterId: "codex",
303
376
  processInstanceId: disconnectedSession.processInstanceId,
304
377
  nativeSessionId: disconnectedSession.nativeSessionId,
305
378
  conversationId: disconnectedSession.conversationId,
306
- ...(activeTurnAttemptId === undefined ? {} : { attemptId: activeTurnAttemptId }),
379
+ ...(activeRunAttemptId === undefined ? {} : { attemptId: activeRunAttemptId }),
307
380
  ...(activeNativeTurnId === undefined ? {} : { nativeTurnId: activeNativeTurnId }),
308
381
  ...authorityFields(),
309
382
  detail: `Codex client could not reattach after bounded retries: ${errorText(lastError)}`
@@ -312,16 +385,13 @@ export async function runAgentHost(input) {
312
385
  const observeExit = (providerSession, launched) => {
313
386
  void providerSession.waitForExit().then((result) => enqueueSerialized(async () => {
314
387
  const ownsCurrentSession = session === providerSession;
315
- const currentPayload = ownsCurrentSession ? sessionPayload ?? launched : launched;
316
- const currentActivationId = ownsCurrentSession
317
- ? activationId ?? launched.runtimeGenerationId
318
- : launched.runtimeGenerationId;
388
+ const currentPayload = ownsCurrentSession
389
+ ? activeRunPayload ?? lastTerminalPayload ?? sessionPayload ?? launched : launched;
319
390
  const exitAuthority = authorityFields();
320
- const stopReceipt = readRuntimeStopReceipt(input.home, currentPayload.runtimeGenerationId);
391
+ const stopRequested = hostStopRequested || providerSession.inspect().cancellation === "requested";
321
392
  const reconnectableCodexClient = ownsCurrentSession
322
393
  && providerSession.adapterId === "codex"
323
- && !hostStopRequested
324
- && stopReceipt === null;
394
+ && !hostStopRequested;
325
395
  if (reconnectableCodexClient) {
326
396
  session = undefined;
327
397
  if (codexClientAttachedAt !== undefined
@@ -331,12 +401,11 @@ export async function runAgentHost(input) {
331
401
  consecutiveCodexDisconnects += 1;
332
402
  if (consecutiveCodexDisconnects > MAX_CONSECUTIVE_CODEX_DISCONNECTS) {
333
403
  updateSnapshot(hostSnapshot("failed", {
334
- runtimeGenerationId: currentPayload.runtimeGenerationId,
335
404
  adapterId: "codex",
336
405
  processInstanceId: result.processInstanceId,
337
406
  nativeSessionId: providerSession.nativeSessionId,
338
407
  conversationId: providerSession.conversationId,
339
- ...(activeTurnAttemptId === undefined ? {} : { attemptId: activeTurnAttemptId }),
408
+ ...(activeRunAttemptId === undefined ? {} : { attemptId: activeRunAttemptId }),
340
409
  ...(activeNativeTurnId === undefined ? {} : { nativeTurnId: activeNativeTurnId }),
341
410
  ...exitAuthority,
342
411
  detail: "Codex client repeatedly disconnected before reaching a stable attachment."
@@ -344,12 +413,11 @@ export async function runAgentHost(input) {
344
413
  return;
345
414
  }
346
415
  updateSnapshot(hostSnapshot("starting", {
347
- runtimeGenerationId: currentPayload.runtimeGenerationId,
348
416
  adapterId: "codex",
349
417
  processInstanceId: result.processInstanceId,
350
418
  nativeSessionId: providerSession.nativeSessionId,
351
419
  conversationId: providerSession.conversationId,
352
- ...(activeTurnAttemptId === undefined ? {} : { attemptId: activeTurnAttemptId }),
420
+ ...(activeRunAttemptId === undefined ? {} : { attemptId: activeRunAttemptId }),
353
421
  ...(activeNativeTurnId === undefined ? {} : { nativeTurnId: activeNativeTurnId }),
354
422
  ...exitAuthority,
355
423
  detail: "Codex App Server proxy disconnected; attaching a replacement client."
@@ -359,28 +427,18 @@ export async function runAgentHost(input) {
359
427
  }
360
428
  if (ownsCurrentSession) {
361
429
  session = undefined;
362
- activationId = undefined;
363
430
  conversationRecoverability = "unknown";
364
431
  authority = undefined;
432
+ // The client is gone for good, so this Session stops holding the
433
+ // implementation. Keeping the reference would report work in progress
434
+ // that no longer exists; a later launch pins again explicitly.
435
+ const ending = endpointLease;
436
+ endpointLease = undefined;
437
+ await ending?.release().catch(() => undefined);
365
438
  }
366
439
  hostSequence += 1;
367
440
  const observedAt = new Date().toISOString();
368
441
  const failures = [];
369
- try {
370
- await publishStructuredProviderActivationTerminal({
371
- home: input.home,
372
- environment: currentPayload.environment,
373
- conversationId: providerSession.conversationId,
374
- nativeSessionId: providerSession.nativeSessionId,
375
- activationId: currentActivationId,
376
- status: stopReceipt !== null || hostStopRequested ? "ended" : "failed",
377
- observedAt
378
- });
379
- }
380
- catch (error) {
381
- failures.push(`activation terminal: ${errorText(error)}`);
382
- }
383
- let exitPersisted = false;
384
442
  try {
385
443
  await persistAndSubmitExit(input.home, validateRuntimeProcessExitObservation({
386
444
  schemaVersion: 2,
@@ -392,31 +450,39 @@ export async function runAgentHost(input) {
392
450
  ? {}
393
451
  : { taskId: currentPayload.environment.YUI_TASK_ID }),
394
452
  roleName: currentPayload.environment.YUI_ROLE ?? "unknown-role",
395
- ...(currentPayload.environment.YUI_TURN_ID === undefined
453
+ ...(currentPayload.environment.YUI_RUN_ID === undefined
396
454
  ? {}
397
- : { turnId: currentPayload.environment.YUI_TURN_ID }),
398
- runtimeGenerationId: currentPayload.runtimeGenerationId,
455
+ : { runId: currentPayload.environment.YUI_RUN_ID }),
399
456
  ...(providerSession.nativeSessionId.length === 0
400
457
  ? {}
401
458
  : { nativeSessionId: providerSession.nativeSessionId }),
402
459
  processKind: "provider-child",
403
460
  ...(result.code === null ? {} : { exitCode: result.code }),
404
461
  ...(result.signal === null ? {} : { signal: result.signal }),
405
- ...(stopReceipt === null ? {} : { stopReceiptId: stopReceipt.receiptId }),
462
+ ...(stopRequested ? { stopRequested: true } : {}),
406
463
  observedAt
407
464
  }));
408
- exitPersisted = true;
409
465
  }
410
466
  catch (error) {
411
467
  failures.push(`process exit: ${errorText(error)}`);
412
468
  }
413
- if (stopReceipt !== null && exitPersisted) {
414
- removeRuntimeStopReceipt(input.home, currentPayload.runtimeGenerationId);
469
+ if (ownsCurrentSession && currentPayload.environment.YUI_SESSION_SCOPE === "task") {
470
+ try {
471
+ await publishStructuredProviderAttachmentExit({
472
+ home: input.home, environment: currentPayload.environment,
473
+ nativeSessionId: providerSession.nativeSessionId,
474
+ attemptId: activeRunAttemptId ?? lastTerminal?.attemptId,
475
+ nativeTurnId: activeNativeTurnId ?? lastTerminal?.nativeTurnId,
476
+ failed: !stopRequested, observedAt
477
+ });
478
+ }
479
+ catch (error) {
480
+ failures.push(`attachment exit: ${errorText(error)}`);
481
+ }
415
482
  }
416
483
  if (hostStopRequested || !ownsCurrentSession)
417
484
  return;
418
485
  updateSnapshot(hostSnapshot(failures.length === 0 ? "exited" : "failed", {
419
- runtimeGenerationId: currentPayload.runtimeGenerationId,
420
486
  adapterId: currentPayload.providerControl?.adapterId,
421
487
  processInstanceId: result.processInstanceId,
422
488
  nativeSessionId: providerSession.nativeSessionId,
@@ -424,12 +490,11 @@ export async function runAgentHost(input) {
424
490
  ...exitAuthority,
425
491
  ...(failures.length !== 0
426
492
  ? { detail: failures.join("; ") }
427
- : activeTurnPayload === undefined
493
+ : activeRunPayload === undefined
428
494
  ? {}
429
- : { detail: "Provider process exited before the active Turn reached a terminal boundary." })
495
+ : { detail: "Provider process exited before the active AgentRun reached a terminal boundary." })
430
496
  }));
431
497
  })).catch((error) => updateSnapshot(hostSnapshot("failed", {
432
- runtimeGenerationId: launched.runtimeGenerationId,
433
498
  adapterId: launched.providerControl?.adapterId,
434
499
  processInstanceId: providerSession.processInstanceId,
435
500
  ...authorityFields(),
@@ -441,7 +506,7 @@ export async function runAgentHost(input) {
441
506
  if (providerControl === undefined) {
442
507
  throw new Error("Agent Host accepts only managed Provider control launches.");
443
508
  }
444
- if (activeTurnPayload !== undefined
509
+ if (activeRunPayload !== undefined
445
510
  || ["starting", "ready", "settling", "delivery-unknown"].includes(snapshot.state)) {
446
511
  throw new Error("Agent Host still owns an unsettled Provider Turn.");
447
512
  }
@@ -457,7 +522,6 @@ export async function runAgentHost(input) {
457
522
  throw new Error("Agent Host launch carries a stale Provider authority fence.");
458
523
  }
459
524
  updateSnapshot(hostSnapshot("starting", {
460
- runtimeGenerationId: next.runtimeGenerationId,
461
525
  adapterId: providerControl.adapterId,
462
526
  ...(session === undefined ? {} : {
463
527
  processInstanceId: session.processInstanceId,
@@ -471,28 +535,58 @@ export async function runAgentHost(input) {
471
535
  if (session !== undefined) {
472
536
  if (session.adapterId !== providerControl.adapterId
473
537
  || providerControl.mode !== "resume"
474
- || providerControl.nativeSessionId !== session.nativeSessionId) {
538
+ || providerControl.nativeSessionId !== session.nativeSessionId
539
+ || (providerControl.endpointImplementation !== undefined
540
+ && (providerControl.endpointImplementation.id !== session.configuration.implementation.id
541
+ || providerControl.endpointImplementation.generation !== session.configuration.implementation.generation))) {
475
542
  throw new Error("Agent Host launch does not match its live Provider Conversation.");
476
543
  }
477
544
  sessionPayload = next;
478
545
  }
479
546
  else {
480
- activationId = next.runtimeGenerationId;
481
- conversationRecoverability = providerControl.adapterId === "codex"
482
- ? "recoverable"
483
- : "unknown";
484
547
  if (providerControl.kind === "restore" && providerControl.ownedTurn !== undefined) {
485
- activeTurnPayload = next;
486
- activeTurnAttemptId = providerControl.ownedTurn.attemptId;
548
+ activeRunPayload = next;
549
+ activeRunAttemptId = providerControl.ownedTurn.attemptId;
487
550
  activeNativeTurnId = providerControl.ownedTurn.turnId;
488
551
  }
489
- const started = await startStructuredProviderSession(next, {
490
- onStarted: (turn) => handleStarted(turn, next),
491
- onTerminal: handleTerminal,
492
- onGoal: handleGoal
493
- });
552
+ assertAgentExecutionEnvironment(input.home, next);
553
+ // Pin before opening: the Session holds this exact generation for every
554
+ // later Turn. A pinned reference naming code this process is not running
555
+ // fails here, rather than silently starting on a different generation.
556
+ endpointLease ??= endpointOwner.pin(providerControl.adapterId, providerControl.endpointImplementation);
557
+ endpointAdapterId = providerControl.adapterId;
558
+ const started = await (providerControl.mode === "new"
559
+ ? endpointLease.open(next) : endpointLease.resume(next));
494
560
  session = started.session;
495
561
  sessionPayload = next;
562
+ recordOwnedProviderProcess(input.home, next.environment, started.session);
563
+ recordNativeConnection(input.home, next, started.session);
564
+ lastTerminalPayload = undefined;
565
+ lastTerminal = undefined;
566
+ // Recoverable means a later process can rebind this Conversation by
567
+ // its native id. The Endpoint answers that from the Driver capability
568
+ // or, where the protocol settles it per connection, from what this
569
+ // Agent actually negotiated — so it is read after the handshake, never
570
+ // predicted before it.
571
+ conversationRecoverability = started.session.conversationRecoverability;
572
+ started.session.events((event) => {
573
+ if (session !== started.session && event.type !== "terminal")
574
+ return;
575
+ if (event.type === "accepted")
576
+ handleAccepted(event.value);
577
+ else if (event.type === "activity")
578
+ handleActivity(event.value);
579
+ else if (event.type === "started")
580
+ handleStarted(event.value, next);
581
+ else if (event.type === "terminal")
582
+ handleTerminal(event.value);
583
+ else if (event.type === "goal")
584
+ handleGoal(event.value);
585
+ else
586
+ void enqueueSerialized(() => publishStructuredProviderInputObserved({
587
+ home: input.home, environment: next.environment, observed: event.value
588
+ })).catch(() => { });
589
+ });
496
590
  recoveredGoal = started.goal;
497
591
  if (started.session.adapterId === "codex") {
498
592
  codexClientAttachedAt = Date.now();
@@ -503,51 +597,49 @@ export async function runAgentHost(input) {
503
597
  handleTerminal(started.recoveredTerminal);
504
598
  }
505
599
  if ((providerControl.kind !== "restore" || providerControl.ownedTurn === undefined)
506
- && started.session.activeTurnId !== undefined) {
600
+ && started.session.inspect().activeNativeTurnId !== undefined) {
507
601
  handleStarted({
508
602
  conversationId: started.session.conversationId,
509
603
  nativeSessionId: started.session.nativeSessionId,
510
- nativeTurnId: started.session.activeTurnId,
604
+ nativeTurnId: started.session.inspect().activeNativeTurnId,
511
605
  clientOwned: false,
512
606
  observedAt: new Date().toISOString()
513
607
  }, next);
514
608
  }
515
609
  }
516
- await publishStructuredProviderOpened({
517
- home: input.home,
518
- environment: next.environment,
519
- conversationId: session.conversationId,
520
- nativeSessionId: session.nativeSessionId,
521
- activationId: activationId ?? next.runtimeGenerationId,
522
- recoverability: conversationRecoverability,
523
- observedAt: new Date().toISOString()
524
- });
525
- if (recoveredGoal !== undefined) {
610
+ if (!providerControl.sessionOnly)
611
+ await publishStructuredProviderOpened({
612
+ home: input.home,
613
+ environment: next.environment,
614
+ conversationId: session.conversationId,
615
+ nativeSessionId: session.nativeSessionId,
616
+ recoverability: conversationRecoverability,
617
+ observedAt: new Date().toISOString()
618
+ });
619
+ if (recoveredGoal !== undefined && !providerControl.sessionOnly) {
526
620
  await publishStructuredProviderGoal({
527
621
  home: input.home,
528
622
  environment: next.environment,
529
- activationId: activationId ?? next.runtimeGenerationId,
530
623
  conversationId: session.conversationId,
531
624
  goal: recoveredGoal
532
625
  });
533
626
  }
534
- const restoredOwnedTurn = providerControl.kind === "restore"
627
+ const restoredOwnedRun = providerControl.kind === "restore"
535
628
  ? providerControl.ownedTurn
536
629
  : undefined;
537
- const providerState = restoredOwnedTurn !== undefined
630
+ const providerState = restoredOwnedRun !== undefined
538
631
  ? "ready"
539
- : session.activeTurnId === undefined ? "idle" : "busy";
632
+ : session.inspect().activeNativeTurnId === undefined ? "idle" : "busy";
540
633
  updateSnapshot(hostSnapshot(providerState, {
541
- runtimeGenerationId: next.runtimeGenerationId,
542
634
  adapterId: providerControl.adapterId,
543
635
  processInstanceId: session.processInstanceId,
544
636
  nativeSessionId: session.nativeSessionId,
545
637
  conversationId: session.conversationId,
546
- ...(restoredOwnedTurn === undefined
638
+ ...(restoredOwnedRun === undefined
547
639
  ? {}
548
640
  : {
549
- attemptId: restoredOwnedTurn.attemptId,
550
- nativeTurnId: restoredOwnedTurn.turnId
641
+ attemptId: restoredOwnedRun.attemptId,
642
+ nativeTurnId: restoredOwnedRun.turnId
551
643
  }),
552
644
  ...authorityFields()
553
645
  }));
@@ -559,13 +651,11 @@ export async function runAgentHost(input) {
559
651
  home: input.home,
560
652
  environment: next.environment,
561
653
  conversationId: error.conversationId,
562
- activationId: activationId ?? next.runtimeGenerationId,
563
654
  recoverability: "unrecoverable",
564
655
  observedAt: new Date().toISOString()
565
656
  }).catch(() => { });
566
657
  }
567
658
  if (session === undefined) {
568
- activationId = undefined;
569
659
  conversationRecoverability = "unknown";
570
660
  authority = undefined;
571
661
  }
@@ -573,7 +663,6 @@ export async function runAgentHost(input) {
573
663
  ? "busy"
574
664
  : error instanceof ProviderTurnRejectedError ? "rejected" : "failed";
575
665
  updateSnapshot(hostSnapshot(state, {
576
- runtimeGenerationId: next.runtimeGenerationId,
577
666
  adapterId: providerControl.adapterId,
578
667
  processInstanceId: session?.processInstanceId,
579
668
  nativeSessionId: session?.nativeSessionId ?? providerControl.nativeSessionId,
@@ -581,8 +670,8 @@ export async function runAgentHost(input) {
581
670
  ...authorityFields(),
582
671
  detail: errorText(error)
583
672
  }));
584
- activeTurnPayload = undefined;
585
- activeTurnAttemptId = undefined;
673
+ activeRunPayload = undefined;
674
+ activeRunAttemptId = undefined;
586
675
  activeNativeTurnId = undefined;
587
676
  throw error;
588
677
  }
@@ -593,85 +682,61 @@ export async function runAgentHost(input) {
593
682
  return operation;
594
683
  };
595
684
  const enqueueDispatch = (next) => (enqueueSerialized(() => dispatch(next)));
596
- const submitTurn = async (request, operation) => {
685
+ const submitRun = async (request, operation) => {
597
686
  if (session === undefined || sessionPayload === undefined) {
598
687
  throw new Error("Agent Host has no live Provider Conversation.");
599
688
  }
600
689
  if (request.nativeSessionId !== session.nativeSessionId) {
601
- throw new Error("Agent Host Turn targets a different Provider Conversation.");
602
- }
603
- // A superseded generation is a real identity conflict, and the writer
604
- // fence does not catch it: a Turn can carry an old generation under a
605
- // fence that is still current. Without this the Host registered the stale
606
- // Turn, wrote it to the Provider, and adopted the old generation into its
607
- // own snapshot. Same generation is contention, which is decided below.
608
- if (request.runtimeGenerationId !== sessionPayload.runtimeGenerationId) {
609
- operation.failure = providerDeliveryFailure({
610
- detail: "Agent Host Turn targets a superseded runtime generation.",
611
- errorName: "ProviderGenerationConflictError",
612
- phase: "turn-submit",
613
- hostState: snapshot.state,
614
- expectedRuntimeGenerationId: sessionPayload.runtimeGenerationId,
615
- observedRuntimeGenerationId: request.runtimeGenerationId,
616
- attemptId: request.turn.attemptId,
617
- inputDisposition: "not-accepted",
618
- registrationDisposition: "not-committed",
619
- // A stale writer does not make the live Session unusable.
620
- sessionDisposition: "recoverable"
621
- });
622
- throw new Error("Agent Host Turn targets a superseded runtime generation: expected "
623
- + `${sessionPayload.runtimeGenerationId}, observed ${request.runtimeGenerationId}.`);
690
+ throw new Error("Agent Host AgentRun targets a different Provider Conversation.");
624
691
  }
625
692
  if (authority === undefined
626
693
  || !sameProviderAuthorityFence(authority, request.authority)) {
627
694
  throw new Error("Agent Host rejected a stale Provider writer fence.");
628
695
  }
629
- if (activeTurnPayload !== undefined || snapshot.state === "settling") {
696
+ if (activeRunPayload !== undefined || snapshot.state === "settling"
697
+ || session.inspect().activeNativeTurnId !== undefined) {
630
698
  operation.failure = providerDeliveryFailure({
631
699
  detail: "Agent Host still owns an unsettled Provider Turn.",
632
700
  errorName: "ProviderTurnBusyError",
633
701
  phase: "turn-submit",
634
702
  hostState: snapshot.state === "settling" ? "settling" : snapshot.state,
635
- expectedRuntimeGenerationId: sessionPayload.runtimeGenerationId,
636
- observedRuntimeGenerationId: request.runtimeGenerationId,
637
- attemptId: request.turn.attemptId,
703
+ attemptId: request.run.attemptId,
638
704
  inputDisposition: "not-accepted",
639
705
  registrationDisposition: "not-committed",
640
706
  // Busy is a healthy Session doing work, never a reason to stop it.
641
707
  sessionDisposition: "recoverable"
642
708
  });
643
- throw new ProviderTurnBusyError("Agent Host still owns an unsettled Provider Turn.", request.turn.attemptId, activeNativeTurnId);
709
+ throw new ProviderTurnBusyError("Agent Host still owns an unsettled Provider Turn.", request.run.attemptId, activeNativeTurnId);
644
710
  }
645
- const { YUI_TURN_ID: _launchTurnId, ...baseEnvironment } = sessionPayload.environment;
711
+ const { YUI_RUN_ID: _launchRunId, ...baseEnvironment } = sessionPayload.environment;
646
712
  // Steer attempts belong to the active native Turn. Never evict an unknown
647
- // attempt while that Turn remains active, but don't retain another Turn's
713
+ // attempt while that AgentRun remains active, but don't retain another AgentRun's
648
714
  // completed steering history after a new exact submission begins.
649
715
  recentSteerAttempts.clear();
650
- activeTurnPayload = {
716
+ activeRunPayload = {
651
717
  ...sessionPayload,
652
718
  environment: {
653
719
  ...baseEnvironment,
654
- ...(request.turnId === undefined ? {} : { YUI_TURN_ID: request.turnId })
720
+ ...(request.runId === undefined ? {} : { YUI_RUN_ID: request.runId })
655
721
  }
656
722
  };
657
- activeTurnAttemptId = request.turn.attemptId;
723
+ activeRunAttemptId = request.run.attemptId;
658
724
  activeNativeTurnId = undefined;
659
725
  updateSnapshot(hostSnapshot("starting", {
660
- runtimeGenerationId: request.runtimeGenerationId,
661
726
  adapterId: session.adapterId,
662
727
  processInstanceId: session.processInstanceId,
663
728
  nativeSessionId: session.nativeSessionId,
664
729
  conversationId: session.conversationId,
665
- attemptId: request.turn.attemptId,
730
+ attemptId: request.run.attemptId,
666
731
  ...authorityFields()
667
732
  }));
668
- const durableTurn = hostTurnControlParams(sessionPayload, session.nativeSessionId, request.authority, request.turn.attemptId, request.turnId);
733
+ const durableRun = hostRunControlParams(sessionPayload, session.nativeSessionId, request.authority, request.run.attemptId, request.runId);
669
734
  // Registration precedes the Provider write, so its failure modes are not
670
735
  // the Provider's. A definite failure means the Provider never saw this
671
736
  // input and the occupancy must be released; anything unconfirmed leaves
672
737
  // durable state ambiguous and keeps it held.
673
738
  try {
674
- await beginDurableProviderTurn(input.home, durableTurn);
739
+ await beginDurableProviderTurn(input.home, durableRun);
675
740
  }
676
741
  catch (error) {
677
742
  // Registration ends in one of three states and they are not
@@ -686,13 +751,13 @@ export async function runAgentHost(input) {
686
751
  const registrationUnknown = !registrationSettled
687
752
  && (error instanceof ControllerAcknowledgementUnknownError
688
753
  || error instanceof ProviderDeliveryUnknownError);
689
- updateSnapshot(hostSnapshot(registrationUnknown ? "delivery-unknown" : "failed", {
690
- runtimeGenerationId: request.runtimeGenerationId,
754
+ updateSnapshot(hostSnapshot(registrationUnknown ? "delivery-unknown"
755
+ : error instanceof ProviderTurnBusyError ? "busy" : "failed", {
691
756
  adapterId: session.adapterId,
692
757
  processInstanceId: session.processInstanceId,
693
758
  nativeSessionId: session.nativeSessionId,
694
759
  conversationId: session.conversationId,
695
- attemptId: request.turn.attemptId,
760
+ attemptId: request.run.attemptId,
696
761
  ...authorityFields(),
697
762
  detail: registrationUnknown
698
763
  ? `Provider Turn registration acknowledgement is unconfirmed; the Provider was not sent this input: ${errorText(error)}`
@@ -705,9 +770,7 @@ export async function runAgentHost(input) {
705
770
  operation.failure = providerDeliveryFailureFrom(error, {
706
771
  phase: "turn-submit",
707
772
  hostState: snapshot.state,
708
- expectedRuntimeGenerationId: sessionPayload.runtimeGenerationId,
709
- observedRuntimeGenerationId: request.runtimeGenerationId,
710
- attemptId: request.turn.attemptId,
773
+ attemptId: request.run.attemptId,
711
774
  inputDisposition: "not-accepted",
712
775
  registrationDisposition: registrationUnknown
713
776
  ? "unknown"
@@ -718,8 +781,8 @@ export async function runAgentHost(input) {
718
781
  // The Provider provably holds nothing, and the durable record either
719
782
  // never existed or is settled. Release the Session for the next
720
783
  // attempt instead of stranding it in a false `starting`.
721
- activeTurnPayload = undefined;
722
- activeTurnAttemptId = undefined;
784
+ activeRunPayload = undefined;
785
+ activeRunAttemptId = undefined;
723
786
  activeNativeTurnId = undefined;
724
787
  }
725
788
  throw error;
@@ -727,34 +790,38 @@ export async function runAgentHost(input) {
727
790
  let providerAccepted = false;
728
791
  let transportAccepted = false;
729
792
  try {
730
- const receipt = await session.submitTurn(request.turn);
793
+ assertAgentExecutionEnvironment(input.home, activeRunPayload);
794
+ const receipt = endpointReceipt(await session.submit({
795
+ ...request.run,
796
+ boundedText: withSessionContextPointer(request.run.boundedText, activeRunPayload.environment),
797
+ inputRef: request.runId ?? request.run.attemptId
798
+ }), request.run.attemptId);
731
799
  transportAccepted = true;
732
800
  providerAccepted = receipt.acceptance === "provider";
733
801
  activeNativeTurnId = receipt.nativeTurnId;
734
802
  try {
735
803
  await publishStructuredProviderAccepted({
736
804
  home: input.home,
737
- environment: activeTurnPayload.environment,
738
- activationId: activationId ?? sessionPayload.runtimeGenerationId,
805
+ environment: activeRunPayload.environment,
739
806
  receipt
740
807
  });
741
808
  }
742
809
  catch (error) {
743
- const unknown = new ProviderDeliveryUnknownError("Input submission returned a receipt but its durable acknowledgement could not be confirmed.", request.turn.attemptId, { cause: error });
810
+ const unknown = new ProviderDeliveryUnknownError("Input submission returned a receipt but its durable acknowledgement could not be confirmed.", request.run.attemptId, { cause: error });
744
811
  // A resolve failure here must not escape before the snapshot is
745
812
  // written: the Provider has already accepted, and losing that fact
746
- // would report a definite non-acceptance for a live Turn.
747
- await resolveProviderTurnSubmission(input.home, durableTurn, unknown);
813
+ // would report a definite non-acceptance for a live AgentRun.
814
+ await resolveProviderTurnSubmission(input.home, durableRun, unknown);
748
815
  throw unknown;
749
816
  }
750
817
  updateSnapshot(hostSnapshot("ready", {
751
- runtimeGenerationId: request.runtimeGenerationId,
752
818
  adapterId: session.adapterId,
753
819
  processInstanceId: session.processInstanceId,
754
820
  nativeSessionId: receipt.nativeSessionId,
755
821
  conversationId: receipt.conversationId,
756
822
  attemptId: receipt.attemptId,
757
823
  nativeTurnId: receipt.nativeTurnId,
824
+ inputAcceptance: receipt.acceptance,
758
825
  ...authorityFields()
759
826
  }));
760
827
  return snapshot;
@@ -764,7 +831,7 @@ export async function runAgentHost(input) {
764
831
  let settlementUnknown = false;
765
832
  if (!transportAccepted) {
766
833
  try {
767
- await resolveProviderTurnSubmission(input.home, durableTurn, error);
834
+ await resolveProviderTurnSubmission(input.home, durableRun, error);
768
835
  }
769
836
  catch (resolutionError) {
770
837
  settlementUnknown = true;
@@ -779,21 +846,18 @@ export async function runAgentHost(input) {
779
846
  ? "busy"
780
847
  : error instanceof ProviderTurnRejectedError ? "rejected" : "failed";
781
848
  updateSnapshot(hostSnapshot(state, {
782
- runtimeGenerationId: request.runtimeGenerationId,
783
849
  adapterId: session.adapterId,
784
850
  processInstanceId: session.processInstanceId,
785
851
  nativeSessionId: session.nativeSessionId,
786
852
  conversationId: session.conversationId,
787
- attemptId: request.turn.attemptId,
853
+ attemptId: request.run.attemptId,
788
854
  ...authorityFields(),
789
855
  detail: errorText(failureError)
790
856
  }));
791
857
  operation.failure = providerDeliveryFailureFrom(failureError, {
792
858
  phase: "turn-submit",
793
859
  hostState: state,
794
- expectedRuntimeGenerationId: sessionPayload.runtimeGenerationId,
795
- observedRuntimeGenerationId: request.runtimeGenerationId,
796
- attemptId: request.turn.attemptId,
860
+ attemptId: request.run.attemptId,
797
861
  // Acceptance is observed, never inferred from the error class.
798
862
  inputDisposition: providerAccepted
799
863
  ? "accepted"
@@ -802,50 +866,28 @@ export async function runAgentHost(input) {
802
866
  sessionDisposition: state === "failed" ? "unknown" : "recoverable"
803
867
  });
804
868
  if (state !== "delivery-unknown") {
805
- activeTurnPayload = undefined;
806
- activeTurnAttemptId = undefined;
869
+ activeRunPayload = undefined;
870
+ activeRunAttemptId = undefined;
807
871
  activeNativeTurnId = undefined;
808
872
  }
809
873
  if (deliveryUnknown && !(failureError instanceof ProviderDeliveryUnknownError)) {
810
- throw new ProviderDeliveryUnknownError("Provider accepted input but its durable acknowledgement could not be confirmed.", request.turn.attemptId, { cause: failureError });
874
+ throw new ProviderDeliveryUnknownError("Provider accepted input but its durable acknowledgement could not be confirmed.", request.run.attemptId, { cause: failureError });
811
875
  }
812
876
  throw failureError;
813
877
  }
814
878
  };
815
- const steerTurn = async (request, operation) => {
816
- if (session === undefined || sessionPayload === undefined || activeTurnPayload === undefined) {
879
+ const steerRun = async (request, operation) => {
880
+ if (session === undefined || sessionPayload === undefined || activeRunPayload === undefined) {
817
881
  operation.failure = providerDeliveryFailure({
818
882
  detail: "Agent Host has no active Provider Turn to steer.",
819
883
  errorName: "ProviderTurnRejectedError",
820
884
  phase: "turn-submit",
821
885
  hostState: snapshot.state,
822
- expectedRuntimeGenerationId: sessionPayload?.runtimeGenerationId
823
- ?? snapshot.runtimeGenerationId ?? "none",
824
- observedRuntimeGenerationId: request.runtimeGenerationId,
825
- attemptId: request.turn.attemptId,
826
- inputDisposition: "not-accepted",
827
- sessionDisposition: "recoverable"
828
- });
829
- throw new ProviderTurnRejectedError("Agent Host has no active Provider Turn to steer.", request.turn.attemptId);
830
- }
831
- // Steer carried no generation check, so a steer issued against a
832
- // superseded activation was written into the current Provider Turn. The
833
- // native Turn id alone does not catch it: a late steer can arrive while
834
- // the id it names is still the active one under a newer generation.
835
- if (request.runtimeGenerationId !== sessionPayload.runtimeGenerationId) {
836
- operation.failure = providerDeliveryFailure({
837
- detail: "Agent Host steer targets a superseded runtime generation.",
838
- errorName: "ProviderGenerationConflictError",
839
- phase: "turn-submit",
840
- hostState: snapshot.state,
841
- expectedRuntimeGenerationId: sessionPayload.runtimeGenerationId,
842
- observedRuntimeGenerationId: request.runtimeGenerationId,
843
- attemptId: request.turn.attemptId,
886
+ attemptId: request.run.attemptId,
844
887
  inputDisposition: "not-accepted",
845
888
  sessionDisposition: "recoverable"
846
889
  });
847
- throw new ProviderTurnRejectedError("Agent Host steer targets a superseded runtime generation: expected "
848
- + `${sessionPayload.runtimeGenerationId}, observed ${request.runtimeGenerationId}.`, request.turn.attemptId);
890
+ throw new ProviderTurnRejectedError("Agent Host has no active Provider Turn to steer.", request.run.attemptId);
849
891
  }
850
892
  if (request.nativeSessionId !== session.nativeSessionId
851
893
  || request.nativeTurnId !== activeNativeTurnId) {
@@ -854,44 +896,35 @@ export async function runAgentHost(input) {
854
896
  errorName: "ProviderTurnRejectedError",
855
897
  phase: "turn-submit",
856
898
  hostState: snapshot.state,
857
- expectedRuntimeGenerationId: sessionPayload.runtimeGenerationId,
858
- observedRuntimeGenerationId: request.runtimeGenerationId,
859
- attemptId: request.turn.attemptId,
899
+ attemptId: request.run.attemptId,
860
900
  inputDisposition: "not-accepted",
861
901
  sessionDisposition: "recoverable"
862
902
  });
863
- throw new ProviderTurnRejectedError("Agent Host steer targets a different Provider Turn.", request.turn.attemptId);
903
+ throw new ProviderTurnRejectedError("Agent Host steer targets a different Provider Turn.", request.run.attemptId);
864
904
  }
865
905
  if (authority === undefined || !sameProviderAuthorityFence(authority, request.authority)) {
866
906
  throw new Error("Agent Host rejected a stale Provider writer fence.");
867
907
  }
868
- if (recentSteerAttempts.has(request.turn.attemptId)) {
869
- const previous = recentSteerAttempts.get(request.turn.attemptId);
870
- if (previous === undefined)
871
- return snapshot;
872
- operation.failure = previous;
873
- throw new ProviderDeliveryUnknownError(previous.detail, request.turn.attemptId);
874
- }
875
908
  try {
876
- const receipt = await session.steerTurn(request.turn);
909
+ assertAgentExecutionEnvironment(input.home, sessionPayload);
910
+ const receipt = endpointReceipt(await session.steer({
911
+ ...request.run,
912
+ boundedText: withSessionContextPointer(request.run.boundedText, sessionPayload.environment),
913
+ inputRef: request.run.attemptId
914
+ }), request.run.attemptId);
877
915
  if (receipt.nativeTurnId !== request.nativeTurnId) {
878
- throw new ProviderDeliveryUnknownError("Provider accepted steer against an unexpected native Turn.", request.turn.attemptId);
916
+ throw new ProviderDeliveryUnknownError("Provider accepted steer against an unexpected native Turn.", request.run.attemptId);
879
917
  }
880
- recentSteerAttempts.set(request.turn.attemptId, undefined);
881
918
  }
882
919
  catch (error) {
883
920
  const unknown = error instanceof ProviderDeliveryUnknownError;
884
921
  operation.failure = providerDeliveryFailureFrom(error, {
885
922
  phase: "turn-submit",
886
923
  hostState: snapshot.state,
887
- expectedRuntimeGenerationId: sessionPayload.runtimeGenerationId,
888
- observedRuntimeGenerationId: request.runtimeGenerationId,
889
- attemptId: request.turn.attemptId,
924
+ attemptId: request.run.attemptId,
890
925
  inputDisposition: unknown ? "unknown" : "not-accepted",
891
926
  sessionDisposition: "unknown"
892
927
  });
893
- if (unknown)
894
- recentSteerAttempts.set(request.turn.attemptId, operation.failure);
895
928
  throw error;
896
929
  }
897
930
  return snapshot;
@@ -900,9 +933,9 @@ export async function runAgentHost(input) {
900
933
  if (session === undefined || request.nativeSessionId !== session.nativeSessionId) {
901
934
  throw new Error("Agent Host authority targets a different Provider Conversation.");
902
935
  }
903
- if (activeTurnPayload !== undefined
936
+ if (activeRunPayload !== undefined
904
937
  || ["starting", "ready", "settling", "delivery-unknown"].includes(snapshot.state)) {
905
- throw new Error("Agent Host authority cannot transfer while a Turn is unsettled.");
938
+ throw new Error("Agent Host authority cannot transfer while a AgentRun is unsettled.");
906
939
  }
907
940
  const next = validateProviderAuthorityFence(request.authority);
908
941
  if (authority !== undefined) {
@@ -914,7 +947,6 @@ export async function runAgentHost(input) {
914
947
  }
915
948
  authority = next;
916
949
  updateSnapshot(hostSnapshot("idle", {
917
- runtimeGenerationId: sessionPayload?.runtimeGenerationId ?? snapshot.runtimeGenerationId,
918
950
  adapterId: session.adapterId,
919
951
  processInstanceId: session.processInstanceId,
920
952
  nativeSessionId: session.nativeSessionId,
@@ -926,48 +958,119 @@ export async function runAgentHost(input) {
926
958
  };
927
959
  const control = await openAgentHostControl(input.home, payload, () => snapshot, async (request) => {
928
960
  if (request.type === "status") {
929
- return controlResult("status", snapshot);
961
+ // Read the Agent's configuration at answer time, not from the stored
962
+ // snapshot. The Agent may have changed it since the last state
963
+ // transition, and a status request is exactly where a caller expects the
964
+ // current reading rather than the one that was true at launch. Nothing is
965
+ // sent to the Agent to obtain it: this reads what the Session has already
966
+ // been told.
967
+ return controlResult("status", session === undefined
968
+ ? snapshot
969
+ : validateSnapshot({
970
+ ...snapshot,
971
+ runConfiguration: session.runConfiguration
972
+ }));
973
+ }
974
+ if (request.type === "cancel") {
975
+ if (session === undefined || request.nativeSessionId !== session.nativeSessionId
976
+ || request.attemptId !== activeRunAttemptId
977
+ || authority === undefined || !sameProviderAuthorityFence(authority, request.authority)) {
978
+ throw new Error("Endpoint cancellation does not match the current Session and authority.");
979
+ }
980
+ // Cancellation remains callable while submit is awaiting native ack.
981
+ // The response never declares the shared native resource stopped.
982
+ const cancellation = await session.cancel(request.attemptId);
983
+ return Object.freeze({ ...controlResult("cancel-requested", snapshot), cancellation });
930
984
  }
931
985
  if (request.type === "submit-turn") {
932
- const accepted = await enqueueSerialized(async () => {
986
+ const submission = enqueueSerialized(async () => {
933
987
  const operation = {};
934
988
  try {
935
- return await submitTurn(request, operation);
989
+ return await submitRun(request, operation);
936
990
  }
937
991
  catch (error) {
938
992
  throw new AgentHostOperationError(error, snapshot, operation.failure);
939
993
  }
940
994
  });
941
- return controlResult("accepted", accepted);
995
+ return await observePendingHostSubmission(submission, () => snapshot);
942
996
  }
943
997
  if (request.type === "steer-turn") {
944
- const accepted = await enqueueSerialized(async () => {
998
+ const previous = recentSteerAttempts.get(request.run.attemptId);
999
+ if (previous !== undefined) {
1000
+ if (previous.request.nativeSessionId !== request.nativeSessionId
1001
+ || previous.request.nativeTurnId !== request.nativeTurnId
1002
+ || previous.request.run.boundedText !== request.run.boundedText
1003
+ || !sameProviderAuthorityFence(previous.request.authority, request.authority)) {
1004
+ throw new Error("Steer attempt identity cannot name another input or Provider fence.");
1005
+ }
1006
+ return controlResult(previous.outcome.result === "delivered" ? "accepted"
1007
+ : previous.outcome.result === "pending" ? "pending" : "rejected", snapshot, previous.outcome.failure);
1008
+ }
1009
+ // Reserve before queueing, so inspection is meaningful even while an
1010
+ // earlier Host operation owns the serialized dispatch lane.
1011
+ const attempt = { request, outcome: { result: "pending" } };
1012
+ recentSteerAttempts.set(request.run.attemptId, attempt);
1013
+ const sourcePayload = activeRunPayload ?? sessionPayload ?? payload;
1014
+ const settlement = {
1015
+ home: input.home,
1016
+ environment: sourcePayload.environment,
1017
+ nativeSessionId: request.nativeSessionId,
1018
+ nativeTurnId: request.nativeTurnId,
1019
+ attemptId: request.run.attemptId,
1020
+ boundedText: request.run.boundedText
1021
+ };
1022
+ const submission = enqueueSerialized(async () => {
945
1023
  const operation = {};
1024
+ let providerAccepted = false;
946
1025
  try {
947
- return await steerTurn(request, operation);
1026
+ const accepted = await steerRun(request, operation);
1027
+ providerAccepted = true;
1028
+ await publishStructuredProviderInputSettlement({ ...settlement, status: "accepted" });
1029
+ attempt.outcome = { result: "delivered" };
1030
+ signalRoleMailbox(input.home, sessionPayload ?? payload);
1031
+ return accepted;
948
1032
  }
949
1033
  catch (error) {
950
- throw new AgentHostOperationError(error, snapshot, operation.failure);
1034
+ const unknown = providerAccepted || error instanceof ProviderDeliveryUnknownError;
1035
+ const failure = operation.failure ?? providerDeliveryFailureFrom(error, {
1036
+ phase: "turn-submit", attemptId: request.run.attemptId,
1037
+ inputDisposition: providerAccepted ? "accepted" : unknown ? "unknown" : "not-accepted"
1038
+ });
1039
+ attempt.outcome = {
1040
+ result: unknown ? "delivery-unknown" : "rejected",
1041
+ failure
1042
+ };
1043
+ // Persist a native disposition before replying. Accepted publication
1044
+ // already enqueued its fact before Controller apply; never replace
1045
+ // that fact with an "unknown" when only apply acknowledgement failed.
1046
+ if (!providerAccepted) {
1047
+ try {
1048
+ await publishStructuredProviderInputSettlement({
1049
+ ...settlement, status: unknown ? "unknown" : "rejected", failure
1050
+ });
1051
+ }
1052
+ catch (settlementError) {
1053
+ throw new AgentHostOperationError(new ProviderDeliveryUnknownError("Steer disposition could not be durably acknowledged.", request.run.attemptId, { cause: new AggregateError([error, settlementError]) }), snapshot, failure);
1054
+ }
1055
+ }
1056
+ signalRoleMailbox(input.home, sessionPayload ?? payload);
1057
+ throw new AgentHostOperationError(providerAccepted ? new ProviderDeliveryUnknownError("Steer acceptance was observed but its settlement acknowledgement is unknown.", request.run.attemptId, { cause: error }) : error, snapshot, failure);
951
1058
  }
952
1059
  });
953
- return controlResult("accepted", accepted);
1060
+ return await observePendingHostSubmission(submission, () => snapshot);
954
1061
  }
955
1062
  if (request.type === "set-authority") {
956
1063
  const accepted = await enqueueSerialized(async () => setAuthority(request));
957
1064
  return controlResult("accepted", accepted);
958
1065
  }
959
- // Same generation means this exact activation is already live here: the
960
- // control request was received and the identity matches. Whether the
961
- // Conversation is usable yet is a separate fact the caller reads from
962
- // `snapshot.state`; it is never an identity conflict.
963
- if (snapshot.runtimeGenerationId === request.runtimeGenerationId
964
- && ["starting", "ready", "settling", "delivery-unknown"].includes(snapshot.state)) {
965
- return controlResult("active-same-generation", snapshot);
966
- }
967
- const redeemed = await redeem(input.home, request.runtimeGenerationId, request.ticket);
968
- if (activeTurnPayload !== undefined
1066
+ const redeemed = await redeem(input.home, request.ticket);
1067
+ if (activeRunPayload !== undefined
969
1068
  || ["starting", "ready", "settling", "delivery-unknown"].includes(snapshot.state)) {
970
- return controlResult("active-other-generation", snapshot);
1069
+ if (redeemed.providerControl?.mode === "resume"
1070
+ && redeemed.providerControl.nativeSessionId === snapshot.nativeSessionId) {
1071
+ return controlResult("accepted", snapshot);
1072
+ }
1073
+ return controlResult("busy", snapshot);
971
1074
  }
972
1075
  const accepted = await enqueueDispatch(redeemed);
973
1076
  return controlResult("accepted", accepted);
@@ -977,7 +1080,7 @@ export async function runAgentHost(input) {
977
1080
  : undefined;
978
1081
  promptHuman = () => {
979
1082
  if (humanConsole !== undefined && authority?.owner === "human"
980
- && activeTurnPayload === undefined
1083
+ && activeRunPayload === undefined
981
1084
  && ["idle", "rejected", "failed"].includes(snapshot.state)) {
982
1085
  humanConsole.setPrompt("yui(provider)> ");
983
1086
  humanConsole.prompt();
@@ -998,19 +1101,18 @@ export async function runAgentHost(input) {
998
1101
  if (boundedText.length === 0)
999
1102
  return;
1000
1103
  const attemptId = `human:${currentAuthority.holderId}:${randomUUID()}`;
1001
- const turnControl = {
1104
+ const runControl = {
1002
1105
  protocol: AGENT_HOST_CONTROL_PROTOCOL,
1003
1106
  type: "submit-turn",
1004
- runtimeGenerationId: currentPayload.runtimeGenerationId,
1005
1107
  nativeSessionId: currentSession.nativeSessionId,
1006
1108
  authority: currentAuthority,
1007
- turn: {
1109
+ run: {
1008
1110
  attemptId,
1009
1111
  boundedText
1010
1112
  }
1011
1113
  };
1012
- await submitTurn(turnControl, {});
1013
- process.stdout.write("Provider accepted the human Turn; waiting for its terminal boundary.\n");
1114
+ await submitRun(runControl, {});
1115
+ process.stdout.write("Provider accepted the human AgentRun; waiting for its terminal boundary.\n");
1014
1116
  }).catch((error) => {
1015
1117
  process.stderr.write(`Provider input failed: ${errorText(error)}\n`);
1016
1118
  promptHuman();
@@ -1029,8 +1131,8 @@ export async function runAgentHost(input) {
1029
1131
  if (hostStopRequested)
1030
1132
  return;
1031
1133
  hostStopRequested = true;
1032
- session?.terminate(signal);
1033
- forceKillTimer = setTimeout(() => session?.terminate("SIGKILL"), 10_000);
1134
+ session?.detach(signal);
1135
+ forceKillTimer = setTimeout(() => session?.detach("SIGKILL"), AGENT_HOST_CLIENT_EXIT_GRACE_MS);
1034
1136
  forceKillTimer.unref();
1035
1137
  stopResolve();
1036
1138
  };
@@ -1050,11 +1152,111 @@ export async function runAgentHost(input) {
1050
1152
  if (forceKillTimer !== undefined)
1051
1153
  clearTimeout(forceKillTimer);
1052
1154
  humanConsole?.close();
1053
- session?.terminate("SIGTERM");
1155
+ // Ask the client to exit, then hand back the Session's hold. The reference
1156
+ // is only actually returned once the client proves it exited, so the bounded
1157
+ // stop below waits on the real dependency instead of an assumed one.
1158
+ session?.detach();
1159
+ const adapterId = endpointAdapterId;
1160
+ await endpointLease?.release();
1161
+ endpointLease = undefined;
1162
+ if (adapterId !== undefined) {
1163
+ // Stop new acquisition and wait a bounded time for what is already
1164
+ // running. A timeout reports the references and pending effects that are
1165
+ // genuinely still in use; it never escalates to killing a Provider this
1166
+ // Endpoint does not own, and it never reports quiescence it cannot prove.
1167
+ const drain = await endpointOwner.stop(adapterId, ENDPOINT_DRAIN_TIMEOUT_MS);
1168
+ if (!drain.quiescent) {
1169
+ const detail = `Endpoint stop ${drain.timedOut ? "timed out" : "returned"} after ${drain.waitedMs}ms`
1170
+ + ` (bound ${ENDPOINT_DRAIN_TIMEOUT_MS}ms); ${drain.references} reference(s), `
1171
+ + `${drain.opening} opening client(s) and `
1172
+ + `${drain.sessions.reduce((total, held) => total + held.pending.length, 0)} pending effect(s) `
1173
+ + "may still be in use. Owned client resources are unknown.";
1174
+ updateSnapshot(hostSnapshot(snapshot.state, {
1175
+ ...definedFields({
1176
+ adapterId,
1177
+ nativeSessionId: snapshot.nativeSessionId,
1178
+ conversationId: snapshot.conversationId
1179
+ }),
1180
+ ...authorityFields(),
1181
+ detail
1182
+ }));
1183
+ // The snapshot is about to stop being reachable, and a stop this process
1184
+ // could not prove quiescent must remain diagnosable afterwards. Written
1185
+ // to the Host's own stream, which its pane and logs retain.
1186
+ process.stderr.write(`${detail}\n`);
1187
+ }
1188
+ }
1189
+ // Final cleanup is bounded on its own deadline. The bounded stop above may
1190
+ // already have given up on a client that never proves exit, and waiting for
1191
+ // that same drain again would leave the control socket open and this function
1192
+ // never returning. Anything still held is reported, not silently released.
1193
+ const remaining = await endpointOwner.close(ENDPOINT_DRAIN_TIMEOUT_MS).catch(() => []);
1194
+ const held = remaining.filter((drain) => !drain.quiescent);
1195
+ if (held.length > 0) {
1196
+ const references = held.reduce((total, drain) => total + drain.references, 0);
1197
+ const opening = held.reduce((total, drain) => total + drain.opening, 0);
1198
+ process.stderr.write(`Endpoint cleanup returned with ${references} reference(s) and ${opening} opening client(s) `
1199
+ + `still held across ${held.length} implementation(s) (bound ${ENDPOINT_DRAIN_TIMEOUT_MS}ms); `
1200
+ + "those implementations stay detached and undisposed. Owned client resources are unknown.\n");
1201
+ }
1054
1202
  await control.close();
1055
1203
  void sessionPayload;
1056
1204
  }
1057
1205
  }
1206
+ /** Host-restart-independent OS custody. No secret or launch payload is stored. */
1207
+ function recordOwnedProviderProcess(home, environment, endpoint) {
1208
+ if (endpoint.ownedProcessId === undefined || environment.YUI_SESSION_SCOPE !== "task")
1209
+ return;
1210
+ const identity = readLinuxProcessIdentity(endpoint.ownedProcessId);
1211
+ if (identity === undefined)
1212
+ throw new Error("Dedicated Provider process identity was lost before registration.");
1213
+ const taskId = environment.YUI_TASK_ID;
1214
+ const roleName = environment.YUI_ROLE;
1215
+ const store = openCurrentTaskStore(home);
1216
+ try {
1217
+ store.saveSessionOwner(createSessionOwnerIdentity({
1218
+ owner: { scope: "task", taskId, roleName },
1219
+ agentId: environment.YUI_AGENT_ID, adapterId: endpoint.adapterId,
1220
+ nativeSessionId: endpoint.nativeSessionId,
1221
+ tmux: {
1222
+ serverName: yuiTmuxServerName(home),
1223
+ socketPath: join(tmuxSocketDirectory(environment), yuiTmuxServerName(home)),
1224
+ sessionName: yuiTmuxSessionName(home, taskId), windowName: roleName, panePid: process.pid
1225
+ },
1226
+ providerRoot: { pid: identity.pid, startIdentity: identity.startIdentity,
1227
+ processGroupId: identity.processGroupId, processSessionId: identity.processSessionId,
1228
+ attribution: "owned-child" },
1229
+ recordedAt: new Date()
1230
+ }));
1231
+ }
1232
+ finally {
1233
+ store.close();
1234
+ }
1235
+ }
1236
+ function recordNativeConnection(home, payload, endpoint) {
1237
+ if (endpoint.adapterId !== "codex" || payload.environment.YUI_SESSION_SCOPE !== "task")
1238
+ return;
1239
+ const taskId = payload.environment.YUI_TASK_ID;
1240
+ const store = openCurrentTaskStore(home);
1241
+ try {
1242
+ if (store.listEvents(taskId).some(event => event.type === "runtime.native-connection-bound"
1243
+ && event.payload.nativeSessionId === endpoint.nativeSessionId))
1244
+ return;
1245
+ const userHome = resolve(payload.cwd, payload.environment.HOME ?? homedir());
1246
+ store.saveEvent(taskId, createTaskEvent(store.nextEventId(taskId), taskId, "runtime.native-connection-bound", {
1247
+ roleName: payload.environment.YUI_ROLE, agentId: payload.environment.YUI_AGENT_ID,
1248
+ nativeSessionId: endpoint.nativeSessionId,
1249
+ // Account location is needed after Controller/Host loss. Never retain
1250
+ // resolved credentials or duplicate the full process environment.
1251
+ home: userHome,
1252
+ codexHome: resolve(payload.cwd, payload.environment.CODEX_HOME ?? join(userHome, ".codex")),
1253
+ ...(endpoint.nativeAccountHome === undefined ? {} : { nativeAccountHome: endpoint.nativeAccountHome })
1254
+ }, new Date()));
1255
+ }
1256
+ finally {
1257
+ store.close();
1258
+ }
1259
+ }
1058
1260
  export function agentHostControlSocketPath(input) {
1059
1261
  const owner = input.scope === "task" ? input.taskId ?? "missing-task" : "global";
1060
1262
  const homeDigest = createHash("sha256")
@@ -1075,7 +1277,7 @@ export function agentHostControlSocketPath(input) {
1075
1277
  export async function sendAgentHostLaunchControl(input) {
1076
1278
  return await sendAgentHostControl(input);
1077
1279
  }
1078
- export async function sendAgentHostTurnControl(input) {
1280
+ export async function sendAgentHostRunControl(input) {
1079
1281
  return await sendAgentHostControl(input);
1080
1282
  }
1081
1283
  export async function sendAgentHostSteerControl(input) {
@@ -1084,6 +1286,9 @@ export async function sendAgentHostSteerControl(input) {
1084
1286
  export async function sendAgentHostAuthorityControl(input) {
1085
1287
  return await sendAgentHostControl(input);
1086
1288
  }
1289
+ export async function sendAgentHostCancelControl(input) {
1290
+ return await sendAgentHostControl(input);
1291
+ }
1087
1292
  export async function inspectAgentHost(input) {
1088
1293
  const result = await sendAgentHostControl({
1089
1294
  ...input,
@@ -1097,9 +1302,9 @@ export async function waitForAgentHostLaunchAck(input) {
1097
1302
  while (Date.now() < deadline) {
1098
1303
  try {
1099
1304
  const snapshot = await inspectAgentHost(input);
1100
- if (snapshot.runtimeGenerationId === input.runtimeGenerationId) {
1305
+ {
1101
1306
  if (snapshot.state === "ready"
1102
- || (input.requireTurnAck !== true && snapshot.state === "idle"))
1307
+ || (input.requireRunAck !== true && snapshot.state === "idle"))
1103
1308
  return snapshot;
1104
1309
  if (snapshot.state === "delivery-unknown"
1105
1310
  || snapshot.state === "busy"
@@ -1120,7 +1325,7 @@ export async function waitForAgentHostLaunchAck(input) {
1120
1325
  }
1121
1326
  await new Promise((resolvePromise) => setTimeout(resolvePromise, 50));
1122
1327
  }
1123
- throw new Error(`Agent Host did not acknowledge Provider launch ${input.runtimeGenerationId}: ${errorText(lastError)}.`);
1328
+ throw new Error(`Agent Host did not acknowledge Provider launch: ${errorText(lastError)}.`);
1124
1329
  }
1125
1330
  async function sendAgentHostControl(input) {
1126
1331
  const path = agentHostControlSocketPath(input);
@@ -1158,9 +1363,8 @@ async function sendAgentHostControl(input) {
1158
1363
  });
1159
1364
  });
1160
1365
  }
1161
- async function redeem(home, runtimeGenerationId, ticket) {
1366
+ async function redeem(home, ticket) {
1162
1367
  const result = await callController(home, "runtime.launch-redeem", {
1163
- runtimeGenerationId,
1164
1368
  ticket,
1165
1369
  hostPid: process.pid
1166
1370
  });
@@ -1236,15 +1440,12 @@ export async function openAgentHostControl(home, payload, snapshot, dispatch) {
1236
1440
  || error instanceof ControllerAcknowledgementUnknownError;
1237
1441
  // The failing operation already recorded the exact facts. Prefer its
1238
1442
  // record; only synthesize one when the failure came from outside a
1239
- // Turn operation (a malformed control, say), where those facts do
1443
+ // AgentRun operation (a malformed control, say), where those facts do
1240
1444
  // not exist.
1241
1445
  const recorded = caught instanceof AgentHostOperationError ? caught.failure : undefined;
1242
1446
  const failure = recorded ?? providerDeliveryFailureFrom(error, {
1243
1447
  phase: controlRequestPhase(body),
1244
1448
  hostState: busy ? "busy" : unknown ? "delivery-unknown" : current.state,
1245
- ...(current.runtimeGenerationId === undefined
1246
- ? {}
1247
- : { observedRuntimeGenerationId: current.runtimeGenerationId }),
1248
1449
  ...(current.attemptId === undefined
1249
1450
  ? {}
1250
1451
  : { attemptId: current.attemptId }),
@@ -1256,7 +1457,7 @@ export async function openAgentHostControl(home, payload, snapshot, dispatch) {
1256
1457
  // A bare `rejected` here is indistinguishable from the Provider
1257
1458
  // refusing the input. Carry the real cause and an explicit input
1258
1459
  // disposition so no consumer has to guess from the message text.
1259
- socket.end(`${JSON.stringify(boundControlResponse(controlResult(busy ? "accepted" : "rejected", validateSnapshot({
1460
+ socket.end(`${JSON.stringify(boundControlResponse(controlResult(busy ? "busy" : "rejected", validateSnapshot({
1260
1461
  ...current,
1261
1462
  ...(busy ? { state: "busy" } : {}),
1262
1463
  ...(unknown ? { state: "delivery-unknown" } : {}),
@@ -1294,7 +1495,10 @@ function boundControlResponse(result) {
1294
1495
  // Every public response, including status and successful controls, crosses
1295
1496
  // the same redaction/size boundary. An earlier failed operation may remain
1296
1497
  // visible through an otherwise successful status request.
1297
- result = controlResult(result.outcome, validateSnapshot(result.snapshot), result.failure === undefined ? undefined : providerDeliveryFailure(result.failure));
1498
+ result = {
1499
+ ...controlResult(result.outcome, validateSnapshot(result.snapshot), result.failure === undefined ? undefined : providerDeliveryFailure(result.failure)),
1500
+ ...(result.cancellation === undefined ? {} : { cancellation: result.cancellation })
1501
+ };
1298
1502
  if (withinControlBound(result))
1299
1503
  return result;
1300
1504
  const clip = (text, chars) => {
@@ -1303,6 +1507,17 @@ function boundControlResponse(result) {
1303
1507
  const head = Math.floor(chars * 0.75);
1304
1508
  return `${text.slice(0, head)}…[truncated ${text.length - chars} chars: control byte bound]${text.slice(text.length - (chars - head))}`;
1305
1509
  };
1510
+ // Never make an omitted option list look like a complete empty enumeration.
1511
+ if (result.snapshot.runConfiguration?.status === "observed") {
1512
+ result = controlResult(result.outcome, validateSnapshot({
1513
+ ...result.snapshot,
1514
+ runConfiguration: unknownAgentRunConfiguration("The Agent's reported configuration is too large for the Agent Host "
1515
+ + "control response, so it could not be carried in full and none of it "
1516
+ + "is shown rather than part of it.")
1517
+ }), result.failure);
1518
+ if (withinControlBound(result))
1519
+ return result;
1520
+ }
1306
1521
  let bounded = controlResult(result.outcome, {
1307
1522
  ...result.snapshot,
1308
1523
  ...(result.snapshot.detail === undefined ? {} : { detail: clip(result.snapshot.detail, 1200) })
@@ -1349,24 +1564,29 @@ function validateControl(control) {
1349
1564
  }
1350
1565
  if (control.type === "status")
1351
1566
  return Object.freeze({ ...control });
1567
+ if (control.type === "cancel") {
1568
+ validateIdentity(control.nativeSessionId, "native Session id");
1569
+ validateIdentity(control.attemptId, "Provider input attempt id");
1570
+ validateProviderAuthorityFence(control.authority);
1571
+ return Object.freeze({ ...control });
1572
+ }
1352
1573
  if (control.type === "submit-turn" || control.type === "steer-turn") {
1353
- validateRuntimeGenerationId(control.runtimeGenerationId);
1354
1574
  validateIdentity(control.nativeSessionId, "native Session id");
1355
- if (control.type === "submit-turn" && control.turnId !== undefined) {
1356
- validateIdentity(control.turnId, "Turn id");
1575
+ if (control.type === "submit-turn" && control.runId !== undefined) {
1576
+ validateIdentity(control.runId, "AgentRun id");
1357
1577
  }
1358
1578
  if (control.type === "steer-turn")
1359
1579
  validateIdentity(control.nativeTurnId, "native Turn id");
1360
1580
  validateProviderAuthorityFence(control.authority);
1361
- validateIdentity(control.turn.attemptId, "Provider input attempt id");
1362
- if (typeof control.turn.boundedText !== "string"
1363
- || control.turn.boundedText.includes("\0")
1364
- || Buffer.byteLength(control.turn.boundedText, "utf8") > 32 * 1024) {
1581
+ validateIdentity(control.run.attemptId, "Provider input attempt id");
1582
+ if (typeof control.run.boundedText !== "string"
1583
+ || control.run.boundedText.includes("\0")
1584
+ || Buffer.byteLength(control.run.boundedText, "utf8") > 32 * 1024) {
1365
1585
  throw new Error("Agent Host Provider input is invalid.");
1366
1586
  }
1367
1587
  return Object.freeze({
1368
1588
  ...control,
1369
- turn: Object.freeze({ ...control.turn })
1589
+ run: Object.freeze({ ...control.run })
1370
1590
  });
1371
1591
  }
1372
1592
  if (control.type === "set-authority") {
@@ -1378,18 +1598,11 @@ function validateControl(control) {
1378
1598
  }
1379
1599
  if (control.type !== "launch")
1380
1600
  throw new Error("Agent Host control type is invalid.");
1381
- validateRuntimeGenerationId(control.runtimeGenerationId);
1382
1601
  if (typeof control.ticket !== "string" || !/^[a-f0-9]{64}$/u.test(control.ticket)) {
1383
1602
  throw new Error("Agent Host launch control ticket is invalid.");
1384
1603
  }
1385
1604
  return Object.freeze({ ...control });
1386
1605
  }
1387
- function validateRuntimeGenerationId(value) {
1388
- if (typeof value !== "string" || value.length === 0
1389
- || value.length > 256 || value.includes("\0")) {
1390
- throw new Error("Agent Host launch control identity is invalid.");
1391
- }
1392
- }
1393
1606
  function validateIdentity(value, label) {
1394
1607
  if (typeof value !== "string" || value.trim().length === 0 || value.includes("\0")) {
1395
1608
  throw new Error(`Agent Host ${label} is invalid.`);
@@ -1397,7 +1610,7 @@ function validateIdentity(value, label) {
1397
1610
  }
1398
1611
  function validateControlResult(result) {
1399
1612
  if (result.protocol !== AGENT_HOST_CONTROL_PROTOCOL
1400
- || !["status", "accepted", "rejected", "active-same-generation", "active-other-generation"].includes(result.outcome)) {
1613
+ || !["status", "accepted", "pending", "cancel-requested", "rejected", "busy"].includes(result.outcome)) {
1401
1614
  throw new Error("Agent Host control response is invalid.");
1402
1615
  }
1403
1616
  return Object.freeze({
@@ -1431,6 +1644,10 @@ function validateSnapshot(snapshot) {
1431
1644
  if (!Number.isFinite(Date.parse(snapshot.updatedAt))) {
1432
1645
  throw new Error("Agent Host snapshot timestamp is invalid.");
1433
1646
  }
1647
+ if (snapshot.inputAcceptance !== undefined
1648
+ && snapshot.inputAcceptance !== "provider" && snapshot.inputAcceptance !== "transport") {
1649
+ throw new Error("Agent Host input acceptance evidence is invalid.");
1650
+ }
1434
1651
  const authorityFields = [
1435
1652
  snapshot.authorityEpoch,
1436
1653
  snapshot.authorityOwner,
@@ -1451,6 +1668,12 @@ function validateSnapshot(snapshot) {
1451
1668
  // to expose the original exception on their normal success paths.
1452
1669
  return Object.freeze({
1453
1670
  ...snapshot,
1671
+ ...(snapshot.runConfiguration === undefined
1672
+ ? {}
1673
+ // Re-read through the same parser the client uses. A shape this build does
1674
+ // not recognize becomes an explicit `unknown` here rather than travelling
1675
+ // as a partial record that renders like a real answer.
1676
+ : { runConfiguration: readAgentRunConfigurationObservation(snapshot.runConfiguration) }),
1454
1677
  ...(snapshot.detail === undefined ? {} : { detail: redactAgentErrorText(snapshot.detail) })
1455
1678
  });
1456
1679
  }
@@ -1480,6 +1703,8 @@ function controlRequestPhase(body) {
1480
1703
  const type = JSON.parse(body.trim()).type;
1481
1704
  if (type === "submit-turn" || type === "steer-turn")
1482
1705
  return "turn-submit";
1706
+ if (type === "cancel")
1707
+ return "host-stop";
1483
1708
  if (type === "launch")
1484
1709
  return "session-restore";
1485
1710
  }
@@ -1494,6 +1719,30 @@ function definedFields(value) {
1494
1719
  function errorText(error) {
1495
1720
  return error instanceof Error ? error.message : String(error ?? "unknown error");
1496
1721
  }
1722
+ /** Preserve the existing durable publication/failure path at the Host boundary. */
1723
+ function endpointReceipt(result, attemptId) {
1724
+ if (result.status === "accepted")
1725
+ return result.receipt;
1726
+ if (result.status === "pending") {
1727
+ throw result.error ?? new ProviderTurnBusyError("The same Endpoint input is still pending.", attemptId);
1728
+ }
1729
+ throw result.error;
1730
+ }
1731
+ async function observePendingHostSubmission(submission, snapshot) {
1732
+ let timer;
1733
+ try {
1734
+ return await Promise.race([
1735
+ submission.then((accepted) => controlResult("accepted", accepted)),
1736
+ new Promise((resolvePromise) => {
1737
+ timer = setTimeout(() => resolvePromise(controlResult("pending", snapshot())), PROVIDER_ACCEPT_TIMEOUT_MS);
1738
+ })
1739
+ ]);
1740
+ }
1741
+ finally {
1742
+ if (timer !== undefined)
1743
+ clearTimeout(timer);
1744
+ }
1745
+ }
1497
1746
  function signalRoleMailbox(home, payload) {
1498
1747
  const taskId = payload.environment.YUI_TASK_ID;
1499
1748
  const roleName = payload.environment.YUI_ROLE;
@@ -1511,14 +1760,13 @@ async function delay(milliseconds) {
1511
1760
  timer.unref();
1512
1761
  });
1513
1762
  }
1514
- function hostTurnControlParams(payload, nativeSessionId, authority, attemptId, turnId) {
1763
+ function hostRunControlParams(payload, nativeSessionId, authority, attemptId, runId) {
1515
1764
  const environment = payload.environment;
1516
1765
  return Object.freeze({
1517
1766
  taskId: requiredEnvironment(environment.YUI_TASK_ID, "Task id"),
1518
1767
  roleName: requiredEnvironment(environment.YUI_ROLE, "Role name"),
1519
- ...(turnId === undefined ? {} : { turnId: requiredEnvironment(turnId, "Turn id") }),
1768
+ ...(runId === undefined ? {} : { runId: requiredEnvironment(runId, "AgentRun id") }),
1520
1769
  agentId: requiredEnvironment(environment.YUI_AGENT_ID, "Agent id"),
1521
- runtimeGenerationId: payload.runtimeGenerationId,
1522
1770
  nativeSessionId: requiredEnvironment(nativeSessionId, "native Session id"),
1523
1771
  attemptId,
1524
1772
  authorityEpoch: authority.epoch,
@@ -1527,14 +1775,17 @@ function hostTurnControlParams(payload, nativeSessionId, authority, attemptId, t
1527
1775
  observedAt: new Date().toISOString()
1528
1776
  });
1529
1777
  }
1530
- async function beginDurableProviderTurn(home, durableTurn) {
1778
+ async function beginDurableProviderTurn(home, durableRun) {
1531
1779
  try {
1532
- await callControllerIdempotently(home, "runtime.provider-turn-begin", durableTurn);
1780
+ await callControllerIdempotently(home, "runtime.provider-turn-begin", durableRun);
1533
1781
  }
1534
1782
  catch (error) {
1783
+ if (error instanceof ControllerClientError && error.code === "SESSION_BUSY") {
1784
+ throw new ProviderTurnBusyError(error.message, String(durableRun.attemptId));
1785
+ }
1535
1786
  if (!(error instanceof ControllerAcknowledgementUnknownError))
1536
1787
  throw error;
1537
- await resolveProviderTurnSubmission(home, durableTurn, new Error(`Provider Turn intent acknowledgement failed before Provider write: ${errorText(error)}`, { cause: error }));
1788
+ await resolveProviderTurnSubmission(home, durableRun, new Error(`Provider Turn intent acknowledgement failed before Provider write: ${errorText(error)}`, { cause: error }));
1538
1789
  // The resolve is fenced on this exact attempt id, so its success proves
1539
1790
  // the registration did commit and is now settled with nothing written to
1540
1791
  // the Provider. That is a known outcome; reporting it as unknown would
@@ -1577,16 +1828,16 @@ class ControllerAcknowledgementUnknownError extends Error {
1577
1828
  class ProviderTurnRegistrationSettledError extends Error {
1578
1829
  name = "ProviderTurnRegistrationSettledError";
1579
1830
  }
1580
- async function resolveProviderTurnSubmission(home, durableTurn, error) {
1581
- const attemptId = durableTurn.attemptId;
1831
+ async function resolveProviderTurnSubmission(home, durableRun, error) {
1832
+ const attemptId = durableRun.attemptId;
1582
1833
  if (typeof attemptId !== "string") {
1583
1834
  throw new Error("Provider Turn resolution has no attempt id.");
1584
1835
  }
1585
1836
  const request = {
1586
- ...durableTurn,
1837
+ ...durableRun,
1587
1838
  status: error instanceof ProviderDeliveryUnknownError
1588
1839
  ? "delivery-unknown"
1589
- : "rejected",
1840
+ : error instanceof ProviderTurnBusyError ? "deferred" : "rejected",
1590
1841
  reason: errorText(error),
1591
1842
  raw: serializeAgentErrorRaw(error),
1592
1843
  observedAt: new Date().toISOString()