@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
@@ -0,0 +1,260 @@
1
+ /**
2
+ * Turning a Yui Role's requested run configuration into ACP config option
3
+ * calls, and judging what the Agent answered.
4
+ *
5
+ * Two rules shape this module.
6
+ *
7
+ * The first is that the protocol does the work. ACP describes a Session's
8
+ * configurable surface as a list of `select` options, each with an id, an
9
+ * optional category naming the axis it belongs to, a current value and the
10
+ * complete enumeration of values it accepts. Model, reasoning effort and
11
+ * permission mode are all just options on that list, so resolving them is one
12
+ * generic lookup by category rather than three product branches. An Agent Yui
13
+ * has never seen is configured by the same code as one it ships knowledge of.
14
+ *
15
+ * The second is that a product-specific fact must be named, never inferred. One
16
+ * such fact is needed here: which mode value means "act without asking". No ACP
17
+ * field marks it, and its name is the Agent's own choice, so a value called
18
+ * `bypassPermissions` proves nothing on its own — a different Agent could use
19
+ * the same word for something narrower, or a narrower word for the same power.
20
+ * Guessing from the string would convert an unverified reading into granted
21
+ * authority. So this module does not decide it: it asks the execution component
22
+ * catalog, where every product is described, and a component that names no
23
+ * bypass value has its request reported as unsupported instead. That is a worse
24
+ * user experience and the only honest one. Nothing here enumerates which
25
+ * components exist, so a new ACP product is added by describing it there rather
26
+ * than by editing this file.
27
+ *
28
+ * Nothing here performs I/O: the caller owns the transport, this module owns
29
+ * the decision. That keeps the mapping testable against recorded option lists
30
+ * without a live Agent, and keeps the Session free of product knowledge.
31
+ */
32
+ import { agentExecutionComponent } from "../agent/executionComponents.js";
33
+ import { ACP_MODE_CONFIG_ID } from "./acpProtocol.js";
34
+ /** ACP categories that name each configurable axis. */
35
+ const MODEL_CATEGORY = "model";
36
+ const EFFORT_CATEGORY = "thought_level";
37
+ const MODE_CATEGORY = "mode";
38
+ /** Only expose the axes Yui configures, never arbitrary peer credential/settings fields. */
39
+ export function acpRunConfigurationOptions(options) {
40
+ const selected = [
41
+ findOption(options, MODEL_CATEGORY, "model"),
42
+ findOption(options, EFFORT_CATEGORY, "effort"),
43
+ findOption(options, MODE_CATEGORY, ACP_MODE_CONFIG_ID)
44
+ ];
45
+ return options.filter((option) => selected.includes(option));
46
+ }
47
+ /**
48
+ * Find the option describing one axis.
49
+ *
50
+ * Category is the primary key because it is the field ACP defines for exactly
51
+ * this purpose and it does not depend on an Agent's choice of id. The id is
52
+ * consulted only as a fallback, for an Agent that omits the optional category
53
+ * but uses the conventional id anyway. An Agent doing neither leaves the axis
54
+ * unconfigurable, which is reported rather than worked around.
55
+ */
56
+ function findOption(options, category, fallbackId) {
57
+ return options.find((option) => option.category === category)
58
+ ?? options.find((option) => option.id === fallbackId);
59
+ }
60
+ /**
61
+ * Resolve one requested value against one axis.
62
+ *
63
+ * A value is sendable only when the Agent enumerated it. ACP requires a `select`
64
+ * option to carry its complete set of accepted values, so that list is
65
+ * authoritative: sending anything outside it would be sending a value the Agent
66
+ * has already said it does not take. Reporting the mismatch with the real
67
+ * enumeration is both more accurate and more useful than relaying whatever
68
+ * generic error the Agent would answer with.
69
+ */
70
+ function resolveValue(field, requested, option, missingAxisReason) {
71
+ if (option === undefined) {
72
+ return Object.freeze({ field, requested, reason: missingAxisReason, offered: Object.freeze([]) });
73
+ }
74
+ const offered = Object.freeze(option.options.map(({ value }) => value));
75
+ const match = option.options.find(({ value }) => value === requested);
76
+ if (match === undefined) {
77
+ return Object.freeze({
78
+ field,
79
+ requested,
80
+ reason: `ACP Agent option \`${option.id}\` does not offer the value \`${requested}\`.`,
81
+ offered
82
+ });
83
+ }
84
+ return Object.freeze({
85
+ field,
86
+ configId: option.id,
87
+ value: match.value,
88
+ previousValue: option.currentValue
89
+ });
90
+ }
91
+ function isRejection(value) {
92
+ return "reason" in value;
93
+ }
94
+ /**
95
+ * The order fields are applied in, and it is load-bearing.
96
+ *
97
+ * A model change is what redefines the other axes: selecting a model can reset
98
+ * the reasoning effort to that model's own default, and can change which effort
99
+ * values exist at all. So the model is settled first and everything that depends
100
+ * on it is resolved afterwards, against the list the Agent reports by then. The
101
+ * permission mode is last because it is the one axis whose wrong value grants
102
+ * authority rather than merely degrading quality — it is decided when the
103
+ * Session's shape has stopped moving.
104
+ */
105
+ export const ACP_CONFIGURATION_ORDER = Object.freeze(["model", "effort", "permission"]);
106
+ /**
107
+ * Resolve exactly one requested field against the options the Agent reports
108
+ * right now.
109
+ *
110
+ * Per-field and stateless by design. The Session applies one field at a time and
111
+ * re-reads the Agent's complete list in between, so every resolution must be
112
+ * computed from the newest list rather than from anything remembered: a value
113
+ * that was correct before a model change may have been reset by it, and a value
114
+ * that was unavailable may have become offered. A batch plan computed once
115
+ * cannot express either, which is why this replaced one.
116
+ */
117
+ export function resolveAcpConfigurationField(field, desired, options, component) {
118
+ const outcome = resolveRequest(field, desired, options, component);
119
+ if (outcome === undefined)
120
+ return UNREQUESTED;
121
+ if (isRejection(outcome))
122
+ return Object.freeze({ kind: "rejection", rejection: outcome });
123
+ // A value the Agent already holds needs no call. Keeping this distinct from a
124
+ // value Yui set means a launch never claims to have sent something it did not.
125
+ return outcome.previousValue === outcome.value
126
+ ? Object.freeze({ kind: "satisfied", step: outcome })
127
+ : Object.freeze({ kind: "step", step: outcome });
128
+ }
129
+ const UNREQUESTED = Object.freeze({ kind: "unrequested" });
130
+ function resolveRequest(field, desired, options, component) {
131
+ if (field === "model") {
132
+ if (desired.model === undefined)
133
+ return undefined;
134
+ return resolveValue("model", desired.model, findOption(options, MODEL_CATEGORY, "model"), "ACP Agent offers no model config option for this Session, so the requested model "
135
+ + "cannot be selected over the protocol; configure it in the Agent itself.");
136
+ }
137
+ if (field === "effort") {
138
+ if (desired.effort === undefined)
139
+ return undefined;
140
+ return resolveValue("effort", desired.effort, findOption(options, EFFORT_CATEGORY, "effort"), "ACP Agent offers no reasoning-effort config option for this Session, so the "
141
+ + "requested effort cannot be selected over the protocol.");
142
+ }
143
+ const permission = desired.permission;
144
+ // `default` deliberately produces nothing. Yui states no mode, so whatever the
145
+ // Agent chose for itself stands: an old Role's conservative default is never
146
+ // widened, and a permissive Agent is not narrowed behind the user's back.
147
+ if (permission.kind === "default")
148
+ return undefined;
149
+ const modeOption = findOption(options, MODE_CATEGORY, ACP_MODE_CONFIG_ID);
150
+ if (permission.kind === "mode") {
151
+ return resolveValue("permission", permission.modeId, modeOption, "ACP Agent offers no permission-mode config option for this Session, so the "
152
+ + "requested mode cannot be applied.");
153
+ }
154
+ const bypassValue = agentExecutionComponent(component).bypassPermissionMode;
155
+ if (bypassValue === undefined) {
156
+ // The user asked for real elevation and Yui cannot name the value that
157
+ // grants it for this product. Selecting a mode by how its name reads would
158
+ // be a guess with authority attached, so the request stops here.
159
+ return Object.freeze({
160
+ field: "permission",
161
+ requested: "bypass",
162
+ reason: `Yui cannot map the bypass permission strategy onto execution component `
163
+ + `${component}: no mode value is known to grant it, and Yui does not infer one `
164
+ + `from a mode's name. Select an exact mode this Agent offers instead.`,
165
+ offered: Object.freeze(modeOption?.options.map(({ value }) => value) ?? [])
166
+ });
167
+ }
168
+ return resolveValue("permission", bypassValue, modeOption, "ACP Agent offers no permission-mode config option for this Session, so the "
169
+ + "requested bypass strategy cannot be applied.");
170
+ }
171
+ /**
172
+ * Re-check every explicitly requested value against the Agent's final option
173
+ * list, after all calls have been made.
174
+ *
175
+ * This exists because confirming each step as it lands is not enough: a later
176
+ * call can reset an earlier axis. Selecting a model resets the reasoning effort
177
+ * on real Agents, so a Session can confirm `effort=high`, then confirm
178
+ * `model=b`, and end up running at that model's default effort with both
179
+ * per-step checks having passed. Only a check against the last complete list the
180
+ * Agent reported can see that, and it is the list the prompt would run under.
181
+ *
182
+ * Only stated values are checked. An unrequested axis has no expectation to
183
+ * violate, and `default` permission means Yui asked for nothing.
184
+ *
185
+ * Legacy `session/set_mode` peers answer with no option list, so their mode
186
+ * cannot be re-read; `acknowledged` outcomes carry that and are excluded here
187
+ * rather than being verified against a value Yui wrote into its own cache.
188
+ */
189
+ export function verifyAcpConfiguration(desired, options, component, outcomes) {
190
+ const failures = [];
191
+ for (const field of ACP_CONFIGURATION_ORDER) {
192
+ const outcome = outcomes.find((candidate) => candidate.field === field);
193
+ if (outcome !== undefined && outcome.confirmation === "acknowledged")
194
+ continue;
195
+ const resolution = resolveAcpConfigurationField(field, desired, options, component);
196
+ if (resolution.kind === "unrequested" || resolution.kind === "satisfied")
197
+ continue;
198
+ if (resolution.kind === "rejection") {
199
+ // The axis or the value disappeared from the Agent's final list, so the
200
+ // request cannot be shown to hold even though a call for it succeeded.
201
+ failures.push(`Requested ${field} could not be confirmed against the ACP Agent's `
202
+ + `final configuration. ${resolution.rejection.reason}`);
203
+ continue;
204
+ }
205
+ // The Agent reports a different current value than the one requested, which
206
+ // means something applied later moved this axis.
207
+ failures.push(`ACP Agent reports ${field} \`${resolution.step.previousValue}\` after `
208
+ + `Yui applied this Session's configuration, but the launch requested `
209
+ + `\`${resolution.step.value}\`.`);
210
+ }
211
+ return Object.freeze(failures);
212
+ }
213
+ /**
214
+ * Confirm that one applied step actually took effect.
215
+ *
216
+ * `session/set_config_option` answers with the Session's complete option list,
217
+ * so the proof is in that answer rather than in the call having not thrown. An
218
+ * Agent that accepts the call and reports a different current value has
219
+ * substituted something for what was asked, and that substitution must surface
220
+ * as a failure instead of passing as success.
221
+ */
222
+ export function confirmAcpConfigurationStep(step, options) {
223
+ const option = options.find(({ id }) => id === step.configId);
224
+ if (option === undefined) {
225
+ return `ACP Agent stopped reporting config option \`${step.configId}\` after Yui set it, `
226
+ + `so the requested ${step.field} cannot be confirmed.`;
227
+ }
228
+ if (option.currentValue !== step.value) {
229
+ return `ACP Agent accepted \`${step.configId}\` = \`${step.value}\` but reports `
230
+ + `\`${option.currentValue}\`, so the requested ${step.field} was not applied.`;
231
+ }
232
+ return undefined;
233
+ }
234
+ /** A single readable diagnostic for every request the Agent cannot honour. */
235
+ export function describeAcpConfigurationRejections(rejections) {
236
+ return rejections
237
+ .map((rejection) => rejection.offered.length === 0
238
+ ? rejection.reason
239
+ : `${rejection.reason} Offered values: ${rejection.offered.join(", ")}.`)
240
+ .join(" ");
241
+ }
242
+ /**
243
+ * Read a launch payload's requested configuration into the form this module
244
+ * plans from.
245
+ *
246
+ * The payload keeps the two permission requests as separate fields because they
247
+ * travel as JSON; this collapses them into the one decision they represent.
248
+ * Neither present means `default`, which sends no mode at all.
249
+ */
250
+ export function acpDesiredSessionConfiguration(options) {
251
+ return Object.freeze({
252
+ ...(options.model === undefined ? {} : { model: options.model }),
253
+ ...(options.effort === undefined ? {} : { effort: options.effort }),
254
+ permission: options.permissionMode !== undefined
255
+ ? Object.freeze({ kind: "mode", modeId: options.permissionMode })
256
+ : options.permissionBypass === true
257
+ ? Object.freeze({ kind: "bypass" })
258
+ : Object.freeze({ kind: "default" })
259
+ });
260
+ }
@@ -41,6 +41,9 @@ export function validateAgentDriverCapabilities(input) {
41
41
  throw new Error(`Agent Driver control capability ${name} must be boolean.`);
42
42
  }
43
43
  }
44
+ if (!["native", "owned-process"].includes(String(control.interruptDelivery))) {
45
+ throw new Error("Agent Driver interrupt delivery capability is invalid.");
46
+ }
44
47
  const lifecycle = input.lifecycle;
45
48
  if (lifecycle === null || typeof lifecycle !== "object" || Array.isArray(lifecycle)) {
46
49
  throw new Error("Agent Driver lifecycle capabilities must be an object.");
@@ -140,6 +143,7 @@ export function validateAgentDriverCapabilities(input) {
140
143
  resume: control.resume,
141
144
  sendTurn: control.sendTurn,
142
145
  interrupt: control.interrupt,
146
+ interruptDelivery: control.interruptDelivery,
143
147
  stop: control.stop
144
148
  }),
145
149
  conversation: Object.freeze({
@@ -190,9 +194,8 @@ export function managedRuntimeAdmission(capabilities) {
190
194
  if (actual.observation.sessionIdentity !== "exact"
191
195
  || actual.conversation.persistentIdentity !== "exact")
192
196
  missing.push("exact-session-identity");
193
- if (actual.observation.promptAcceptance !== "exact"
194
- || actual.input.acceptance !== "exact")
195
- missing.push("exact-prompt-acceptance");
197
+ if (!provesPromptAcceptance(actual))
198
+ missing.push("provable-prompt-acceptance");
196
199
  if (actual.lifecycle.host !== "persistent")
197
200
  missing.push("persistent-agent-host");
198
201
  if (actual.lifecycle.nativeConversationResume !== "exact") {
@@ -206,6 +209,30 @@ export function managedRuntimeAdmission(capabilities) {
206
209
  ? Object.freeze({ admitted: true })
207
210
  : Object.freeze({ admitted: false, missing: Object.freeze(missing) });
208
211
  }
212
+ /**
213
+ * Whether a Turn's input can be proven either accepted or not accepted.
214
+ *
215
+ * Two different protocol shapes satisfy this, and neither is weaker than the
216
+ * other:
217
+ *
218
+ * - the provider announces acceptance separately from the result, so a Turn
219
+ * is known to be running before it finishes (`acceptance: "exact"`);
220
+ * - the provider answers the request itself with an exact structured
221
+ * terminal, so the reply that settles the Turn is also the proof its input
222
+ * was accepted (`bounded.structuredTerminal` with an exact turn lifecycle).
223
+ *
224
+ * In the second shape a long wait is genuinely pending rather than unproven:
225
+ * the correlated response is still owed, and it will say which way the Turn
226
+ * went. What must never be admitted is a provider that can leave input in a
227
+ * state with no reply and no acceptance, because nothing there can ever settle
228
+ * the Turn.
229
+ */
230
+ function provesPromptAcceptance(actual) {
231
+ if (actual.observation.promptAcceptance === "exact" && actual.input.acceptance === "exact") {
232
+ return true;
233
+ }
234
+ return actual.bounded.structuredTerminal && actual.observation.turnLifecycle === "exact";
235
+ }
209
236
  export function boundedRuntimeAdmission(capabilities) {
210
237
  const actual = validateAgentDriverCapabilities(capabilities);
211
238
  const missing = [];
@@ -314,19 +341,11 @@ export function normalizeAgentDriverHookClassification(input) {
314
341
  }
315
342
  if (input.continuationId !== undefined) {
316
343
  requireText(input.continuationId, "Agent Driver Hook continuation id");
317
- if (!Number.isSafeInteger(input.continuationGeneration)
318
- || input.continuationGeneration < 1) {
319
- throw new Error("Agent Driver Hook continuation generation is invalid.");
320
- }
321
- }
322
- else if (input.continuationGeneration !== undefined) {
323
- throw new Error("Agent Driver Hook continuation generation requires an id.");
324
344
  }
325
345
  return Object.freeze({
326
346
  ...(input.startupSession === undefined ? {} : { startupSession: input.startupSession }),
327
347
  ...(input.continuationId === undefined ? {} : {
328
348
  continuationId: input.continuationId,
329
- continuationGeneration: input.continuationGeneration
330
349
  }),
331
350
  terminal: input.terminal ?? false
332
351
  });
@@ -0,0 +1,278 @@
1
+ import { builtinAgentDriverRegistry } from "./builtinAgentDrivers.js";
2
+ import { builtinAgentEndpointImplementation, requireBuiltinAgentEndpointImplementation } from "./agentEndpointIdentity.js";
3
+ import { CodexPreSubmissionError } from "./codexAppServerRuntime.js";
4
+ export { builtinAgentEndpointImplementation } from "./agentEndpointIdentity.js";
5
+ import { ProviderDeliveryUnknownError, ProviderTurnBusyError, ProviderTurnRejectedError, startStructuredProviderSession } from "./structuredProviderHost.js";
6
+ /**
7
+ * The only built-in managed execution factory. Product/protocol codecs remain
8
+ * private to runtime; the Host consumes the same boundary for both providers.
9
+ * An injected opener is useful for isolated protocol evidence, not a fallback.
10
+ */
11
+ export function createAgentEndpointFactory(start = startStructuredProviderSession) {
12
+ const connect = async (payload, mode) => {
13
+ const control = payload.providerControl;
14
+ if (control === undefined || control.mode !== mode) {
15
+ throw new Error(`AgentEndpoint ${mode === "new" ? "open" : "resume"} requires matching Session intent.`);
16
+ }
17
+ const pinned = control.endpointImplementation;
18
+ const implementation = pinned === undefined ? builtinAgentEndpointImplementation(control.adapterId)
19
+ : Object.freeze({ ...requireBuiltinAgentEndpointImplementation(control.adapterId, pinned) });
20
+ // Clone before asynchronous startup; callers must not mutate the Session's
21
+ // effective invocation when configuration changes for a subsequent AgentRun.
22
+ const configuration = Object.freeze({
23
+ implementation,
24
+ command: payload.command,
25
+ args: Object.freeze([...payload.args]),
26
+ cwd: payload.cwd,
27
+ ...(payload.executionEnvironment === undefined ? {} : {
28
+ executionEnvironment: freezeConfiguration(structuredClone(payload.executionEnvironment))
29
+ }),
30
+ ...(control.codexThread === undefined ? {} : {
31
+ threadOptions: freezeConfiguration(structuredClone(control.codexThread))
32
+ })
33
+ });
34
+ let endpoint;
35
+ const openingEvents = [];
36
+ const emit = (event) => {
37
+ if (endpoint === undefined)
38
+ openingEvents.push(event);
39
+ else
40
+ endpoint.observe(event);
41
+ };
42
+ const opened = await start(payload, {
43
+ onAccepted: (value) => emit({ type: "accepted", value }),
44
+ onActivity: (value) => emit({ type: "activity", value }),
45
+ onStarted: (value) => emit({ type: "started", value }),
46
+ onTerminal: (value) => emit({ type: "terminal", value }),
47
+ onInput: (value) => emit({ type: "input", value }),
48
+ onGoal: (value) => emit({ type: "goal", value })
49
+ });
50
+ endpoint = new BuiltinAgentEndpoint(opened.session, configuration, opened.recoveredTerminal);
51
+ for (const event of openingEvents)
52
+ endpoint.observe(event);
53
+ return Object.freeze({
54
+ session: endpoint,
55
+ ...(opened.recoveredTerminal === undefined ? {} : { recoveredTerminal: opened.recoveredTerminal }),
56
+ ...(opened.goal === undefined ? {} : { goal: opened.goal })
57
+ });
58
+ };
59
+ return Object.freeze({
60
+ open: (payload) => connect(payload, "new"),
61
+ resume: (payload) => connect(payload, "resume")
62
+ });
63
+ }
64
+ function freezeConfiguration(value) {
65
+ if (value !== null && typeof value === "object") {
66
+ for (const member of Object.values(value))
67
+ freezeConfiguration(member);
68
+ Object.freeze(value);
69
+ }
70
+ return value;
71
+ }
72
+ class BuiltinAgentEndpoint {
73
+ driver;
74
+ configuration;
75
+ get ownedProcessId() { return this.driver.ownedProcessId; }
76
+ get nativeAccountHome() { return this.driver.nativeAccountHome; }
77
+ capabilities;
78
+ conversationRecoverability;
79
+ #listeners = new Set();
80
+ #openingEvents = [];
81
+ #attempts = new Map();
82
+ #terminals = new Map();
83
+ #attachment = "attached";
84
+ #cancellation = "not-requested";
85
+ constructor(driver, configuration, recoveredTerminal) {
86
+ this.driver = driver;
87
+ this.configuration = configuration;
88
+ if (recoveredTerminal?.clientOwned && recoveredTerminal.attemptId !== undefined) {
89
+ this.#terminals.set(recoveredTerminal.attemptId, cancellationProof(recoveredTerminal));
90
+ }
91
+ // The registered Driver, not the adapter name, states which control
92
+ // affordances this Session really has: a Session that sends a native
93
+ // cancel message must not be reported as one that can only kill its
94
+ // process, and vice versa.
95
+ const capabilities = builtinAgentDriverRegistry()
96
+ .requireByAdapterId(driver.adapterId)
97
+ .capabilities;
98
+ this.capabilities = Object.freeze({
99
+ steer: capabilities.input.steer === "fenced" ? "native" : "unsupported",
100
+ cancel: capabilities.control.interruptDelivery === "native"
101
+ ? "native-interrupt"
102
+ : "owned-process"
103
+ });
104
+ // What the protocol permits is not always what this Agent agreed to. When
105
+ // the Session settled the question during its own handshake, that answer
106
+ // wins over the adapter-wide capability, which cannot see the difference
107
+ // between two Agents on the same adapter.
108
+ this.conversationRecoverability = driver.conversationRecoverability
109
+ ?? (capabilities.lifecycle.nativeConversationResume === "exact"
110
+ && capabilities.conversation.crossProcessResume
111
+ ? "recoverable"
112
+ : "unknown");
113
+ void driver.waitForExit().then(() => { this.#attachment = "exited"; });
114
+ }
115
+ get adapterId() { return this.driver.adapterId; }
116
+ get nativeSessionId() { return this.driver.nativeSessionId; }
117
+ get conversationId() { return this.driver.conversationId; }
118
+ get processInstanceId() { return this.driver.processInstanceId; }
119
+ /**
120
+ * Delegated rather than copied at construction: the Session reads its own
121
+ * current state, and a value captured here would freeze the configuration as
122
+ * it stood when the connection opened.
123
+ */
124
+ get runConfiguration() {
125
+ return this.driver.runConfiguration;
126
+ }
127
+ submit(input) {
128
+ return this.deliver(input, "submit");
129
+ }
130
+ steer(input) {
131
+ return this.deliver(input, "steer");
132
+ }
133
+ async deliver(input, operation) {
134
+ if (!input.attemptId || !input.inputRef)
135
+ throw new Error("Endpoint input requires attemptId and inputRef.");
136
+ const previous = this.#attempts.get(input.attemptId);
137
+ if (previous !== undefined) {
138
+ if (previous.input.inputRef !== input.inputRef || previous.input.boundedText !== input.boundedText
139
+ || previous.operation !== operation) {
140
+ throw new Error("Endpoint attempt identity cannot be reused for different input or operation.");
141
+ }
142
+ // Busy is a proven non-write: the same durable request may be tried
143
+ // explicitly later. Unknown, accepted and in-flight input are never resent.
144
+ if (previous.disposition.status !== "pending" || previous.disposition.reason !== "native-busy") {
145
+ return previous.disposition;
146
+ }
147
+ }
148
+ if (this.#attachment !== "attached") {
149
+ return { status: "not-submitted", error: new ProviderTurnRejectedError("Endpoint is detached.", input.attemptId) };
150
+ }
151
+ const attempt = {
152
+ input: Object.freeze({ ...input }),
153
+ operation,
154
+ disposition: { status: "pending", reason: "submitting" }
155
+ };
156
+ this.#attempts.set(input.attemptId, attempt);
157
+ try {
158
+ const receipt = await (operation === "submit"
159
+ ? this.driver.submitTurn(input) : this.driver.steerTurn(input));
160
+ if (receipt.attemptId !== input.attemptId || receipt.nativeSessionId !== this.nativeSessionId
161
+ || receipt.conversationId !== this.conversationId) {
162
+ throw new ProviderDeliveryUnknownError("Provider receipt does not match Endpoint input.", input.attemptId);
163
+ }
164
+ if (attempt.disposition.status !== "accepted") {
165
+ attempt.disposition = { status: "accepted", receipt };
166
+ }
167
+ }
168
+ catch (error) {
169
+ if (attempt.disposition.status === "accepted")
170
+ return attempt.disposition;
171
+ attempt.disposition = error instanceof ProviderTurnBusyError
172
+ ? { status: "pending", reason: "native-busy", error }
173
+ : error instanceof ProviderTurnRejectedError || error instanceof CodexPreSubmissionError
174
+ ? { status: "not-submitted", error }
175
+ : {
176
+ status: "unknown",
177
+ error: error instanceof ProviderDeliveryUnknownError ? error
178
+ : new ProviderDeliveryUnknownError("Provider submission outcome is unknown.", input.attemptId, { cause: error })
179
+ };
180
+ }
181
+ return attempt.disposition;
182
+ }
183
+ inspect() {
184
+ return Object.freeze({
185
+ ...(this.driver.activeTurnId === undefined ? {} : { activeNativeTurnId: this.driver.activeTurnId }),
186
+ submissions: Object.freeze([...this.#attempts].map(([attemptId, attempt]) => Object.freeze({
187
+ attemptId, inputRef: attempt.input.inputRef, disposition: attempt.disposition
188
+ }))),
189
+ attachment: this.#attachment,
190
+ cancellation: this.#cancellation,
191
+ resources: "unknown"
192
+ });
193
+ }
194
+ observe(value) {
195
+ if (value.type !== "goal"
196
+ && (value.value.nativeSessionId !== this.nativeSessionId || value.value.conversationId !== this.conversationId))
197
+ return;
198
+ if (value.type === "goal" && value.value !== null && value.value.conversationId !== this.conversationId)
199
+ return;
200
+ if (value.type === "accepted") {
201
+ const attempt = this.#attempts.get(value.value.attemptId);
202
+ if (attempt === undefined || value.value.acceptance !== "provider")
203
+ return;
204
+ attempt.disposition = { status: "accepted", receipt: value.value };
205
+ }
206
+ if (value.type === "terminal" && value.value.clientOwned && value.value.attemptId !== undefined) {
207
+ const attempt = this.#attempts.get(value.value.attemptId);
208
+ if (attempt !== undefined) {
209
+ const prior = attempt.disposition;
210
+ if (prior.status === "accepted" && prior.receipt.nativeTurnId !== undefined
211
+ && prior.receipt.nativeTurnId !== value.value.nativeTurnId)
212
+ return;
213
+ // An exactly correlated terminal is stronger evidence than pipe write
214
+ // or a lost acknowledgement. It never adopts the current attempt.
215
+ attempt.disposition = {
216
+ status: "accepted",
217
+ receipt: {
218
+ attemptId: value.value.attemptId,
219
+ nativeSessionId: this.nativeSessionId,
220
+ conversationId: this.conversationId,
221
+ ...(value.value.nativeTurnId === undefined ? {} : { nativeTurnId: value.value.nativeTurnId }),
222
+ acceptedAt: value.value.observedAt,
223
+ acceptance: "provider"
224
+ }
225
+ };
226
+ }
227
+ this.#terminals.set(value.value.attemptId, cancellationProof(value.value));
228
+ }
229
+ const event = Object.freeze({
230
+ ...value, implementation: this.configuration.implementation, processInstanceId: this.processInstanceId
231
+ });
232
+ if (this.#listeners.size === 0)
233
+ this.#openingEvents.push(event);
234
+ else
235
+ for (const listener of this.#listeners)
236
+ listener(event);
237
+ }
238
+ events(listener) {
239
+ this.#listeners.add(listener);
240
+ for (const event of this.#openingEvents.splice(0))
241
+ listener(event);
242
+ return () => { this.#listeners.delete(listener); };
243
+ }
244
+ async cancel(attemptId) {
245
+ this.#cancellation = "requested";
246
+ const status = await this.driver.cancelTurn(attemptId);
247
+ let terminal = this.#terminals.get(attemptId);
248
+ if (terminal === undefined && status !== "unknown") {
249
+ terminal = await new Promise(resolve => {
250
+ const timer = setTimeout(() => { this.#listeners.delete(listener); resolve(undefined); }, 8_000);
251
+ const listener = (event) => {
252
+ if (event.type !== "terminal" || !event.value.clientOwned || event.value.attemptId !== attemptId)
253
+ return;
254
+ clearTimeout(timer);
255
+ this.#listeners.delete(listener);
256
+ resolve(cancellationProof(event.value));
257
+ };
258
+ this.#listeners.add(listener);
259
+ });
260
+ }
261
+ if (terminal === undefined || this.driver.activeTurnId !== undefined) {
262
+ return { status: "unknown", resources: "unknown" };
263
+ }
264
+ return Object.freeze({ status, resources: "unknown", terminal });
265
+ }
266
+ detach(signal = "SIGTERM") {
267
+ if (this.#attachment === "exited")
268
+ return;
269
+ this.#attachment = "detach-requested";
270
+ // The driver terminates only its owned proxy/stream process group.
271
+ this.driver.terminate(signal);
272
+ }
273
+ waitForExit() { return this.driver.waitForExit(); }
274
+ }
275
+ function cancellationProof(terminal) {
276
+ const { input: _input, output: _output, error: _error, rawError: _rawError, ...proof } = terminal;
277
+ return Object.freeze(proof);
278
+ }