@zq-silk/yui 0.13.9 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (229) hide show
  1. package/ARCHITECTURE.md +69 -428
  2. package/README.md +108 -298
  3. package/dist/agent/managedRuntimeEnvironment.js +2 -2
  4. package/dist/cli/commandCatalog.js +39 -77
  5. package/dist/cli/interactionCandidates.js +4 -4
  6. package/dist/cli/interactionPolicy.js +7 -10
  7. package/dist/cli/invocationRouter.js +1 -1
  8. package/dist/cli/updateCommand.js +6 -27
  9. package/dist/cli/updateOrchestrator.js +63 -484
  10. package/dist/cli/updatePorts.js +15 -333
  11. package/dist/cli/upgradeCommand.js +8 -65
  12. package/dist/cli.js +57 -242
  13. package/dist/commands/configCommands.js +11 -62
  14. package/dist/commands/executionAuditCommands.js +23 -23
  15. package/dist/commands/globalRoleCommands.js +2 -7
  16. package/dist/commands/jobCommands.js +1 -4
  17. package/dist/commands/projectCommands.js +5 -5
  18. package/dist/commands/releaseCommands.js +3 -10
  19. package/dist/commands/resourcesCommands.js +2 -2
  20. package/dist/commands/sessionCommands.js +2 -2
  21. package/dist/commands/taskActor.js +31 -39
  22. package/dist/commands/taskChangeSetCommands.js +5 -12
  23. package/dist/commands/taskCommands.js +464 -1249
  24. package/dist/commands/taskContextCommand.js +35 -36
  25. package/dist/commands/taskExecutionCommands.js +33 -46
  26. package/dist/commands/taskInputCommands.js +20 -42
  27. package/dist/commands/taskNextActionCommand.js +9 -9
  28. package/dist/commands/taskOverviewCommand.js +56 -95
  29. package/dist/commands/taskRoleRuntimeStatus.js +77 -107
  30. package/dist/commands/taskUpstreamCommands.js +3 -3
  31. package/dist/commands/taskWorkspaceCommands.js +7 -79
  32. package/dist/commands/telemetryCommands.js +32 -124
  33. package/dist/commands/workflowCommands.js +3 -3
  34. package/dist/completion/fileCompletionManager.js +1 -1
  35. package/dist/config/configCatalog.js +4 -6
  36. package/dist/config/yuiConfig.js +6 -43
  37. package/dist/context/dispatchContext.js +14 -30
  38. package/dist/context/roleSessionContext.js +2 -2
  39. package/dist/context/sessionBootstrapManifest.js +2 -2
  40. package/dist/context/{runContextPack.js → turnContextPack.js} +58 -73
  41. package/dist/context/turnInputContract.js +204 -0
  42. package/dist/context/wakeNotification.js +2 -2
  43. package/dist/controller/agentRuntimeObserver.js +18 -18
  44. package/dist/controller/clientRuntime.js +13 -20
  45. package/dist/controller/controller.js +89 -116
  46. package/dist/controller/fileSchedulerStoreAdapter.js +511 -742
  47. package/dist/controller/jobControl.js +17 -17
  48. package/dist/controller/resourceInventory.js +6 -6
  49. package/dist/controller/resourceInventoryLinux.js +38 -59
  50. package/dist/controller/resourceInventoryRpc.js +3 -3
  51. package/dist/controller/resourceInventoryWorker.js +1 -1
  52. package/dist/controller/runtime.js +46 -53
  53. package/dist/controller/runtimeEventInbox.js +23 -31
  54. package/dist/controller/runtimeEventProcessor.js +15 -15
  55. package/dist/controller/{runtimeHookRunFence.js → runtimeHookTurnFence.js} +87 -81
  56. package/dist/controller/runtimeLaunchCoordinator.js +3 -3
  57. package/dist/controller/runtimeObservationHook.js +11 -8
  58. package/dist/controller/sessionNotify.js +11 -11
  59. package/dist/controller/structuredProviderObservation.js +89 -16
  60. package/dist/coordination/workMailbox.js +309 -579
  61. package/dist/coordination/workMailboxQueue.js +5 -6
  62. package/dist/core/controllerClient.js +1 -91
  63. package/dist/core/controllerServer.js +3 -6
  64. package/dist/doctor/doctor.js +51 -314
  65. package/dist/execution/candidateConvergence.js +3 -3
  66. package/dist/execution/executionGroup.js +32 -33
  67. package/dist/execution/executionHealth.js +36 -38
  68. package/dist/execution/resourceBroker.js +12 -12
  69. package/dist/executor/agentExecutor.js +30 -265
  70. package/dist/executor/effectiveLaunch.js +9 -19
  71. package/dist/executor/executorRegistry.js +43 -17
  72. package/dist/executor/fileRoleLaunchPlanner.js +56 -56
  73. package/dist/executor/launchPlan.js +3 -3
  74. package/dist/executor/workspacePreflightClassification.js +16 -16
  75. package/dist/grant/capabilityGrant.js +20 -25
  76. package/dist/input/inputRequest.js +12 -12
  77. package/dist/integration/changeSet.js +9 -13
  78. package/dist/integration/changeSetManifest.js +1 -1
  79. package/dist/integration/gitIntegrationService.js +2 -2
  80. package/dist/integration/integrationQueueService.js +5 -21
  81. package/dist/integration/overlapDiagnostics.js +2 -2
  82. package/dist/interaction/operatorPresentation.js +1 -1
  83. package/dist/job/jobRunner.js +1 -1
  84. package/dist/lifecycle/canonicalLifecycleEvent.js +25 -25
  85. package/dist/lifecycle/{exactRunTerminalization.js → exactTurnTerminalization.js} +116 -232
  86. package/dist/message/message.js +6 -6
  87. package/dist/observability/executionAudit.js +74 -86
  88. package/dist/observability/faultClassification.js +7 -24
  89. package/dist/observability/orchestrationMetrics.js +5 -5
  90. package/dist/observability/runtimeIdentity.js +34 -101
  91. package/dist/profile/agentProfile.js +1 -1
  92. package/dist/release/cliHomeReleaseFence.js +3 -3
  93. package/dist/release/releaseWorkflowEngine.js +6 -10
  94. package/dist/release/releaseWorkflowPorts.js +2 -2
  95. package/dist/release/workflowFileLock.js +1 -1
  96. package/dist/repository/checkoutSwap.js +1 -1
  97. package/dist/repository/gitWorkspace.js +0 -13
  98. package/dist/repository/project.js +1 -1
  99. package/dist/repository/projectMaintenanceLock.js +1 -2
  100. package/dist/repository/taskBaseFreshness.js +13 -13
  101. package/dist/repository/taskWorkspaceCoordinator.js +21 -25
  102. package/dist/repository/taskWorkspaceIdentity.js +8 -21
  103. package/dist/repository/taskWorkspacePreparer.js +63 -620
  104. package/dist/resources/autoResourceGc.js +3 -3
  105. package/dist/resources/liveReferences.js +1 -1
  106. package/dist/resources/resourceGc.js +1 -1
  107. package/dist/resources/sqliteResourceRegistry.js +1 -1
  108. package/dist/review/reviewDecision.js +7 -7
  109. package/dist/review/reviewFindingLedger.js +1 -1
  110. package/dist/review/reviewOutcomeClassifier.js +35 -50
  111. package/dist/review/reviewRound.js +14 -25
  112. package/dist/review/reviewerAvailability.js +4 -4
  113. package/dist/review/taskFinalReviewContractResolution.js +41 -0
  114. package/dist/role/role.js +6 -3
  115. package/dist/runtime/agentDriver.js +4 -0
  116. package/dist/runtime/agentHost.js +151 -36
  117. package/dist/runtime/builtinAgentDrivers.js +79 -2
  118. package/dist/runtime/codexAppServerRuntime.js +122 -0
  119. package/dist/runtime/continuationManager.js +3 -5
  120. package/dist/runtime/exactControlPlane.js +17 -42
  121. package/dist/runtime/firstProgressAdvisory.js +1 -1
  122. package/dist/runtime/index.js +2 -2
  123. package/dist/runtime/launchDiagnostics.js +2 -2
  124. package/dist/runtime/lifecycleReservation.js +4 -4
  125. package/dist/runtime/processExitObservation.js +1 -1
  126. package/dist/runtime/processExitOutbox.js +0 -32
  127. package/dist/runtime/promptEnvelope.js +9 -9
  128. package/dist/runtime/providerContinuation.js +2 -2
  129. package/dist/runtime/providerContinuationReconciliationService.js +2 -2
  130. package/dist/runtime/providerRuntimeIdentity.js +76 -24
  131. package/dist/runtime/recentTurnIds.js +38 -0
  132. package/dist/runtime/runtimeContinuationProjection.js +2 -2
  133. package/dist/runtime/runtimeHealthPolicy.js +1 -1
  134. package/dist/runtime/runtimeObservation.js +58 -21
  135. package/dist/runtime/runtimeProjection.js +19 -27
  136. package/dist/runtime/sessionLaunchRequest.js +2 -2
  137. package/dist/runtime/structuredProviderHost.js +177 -12
  138. package/dist/runtime/tmuxAdapters.js +46 -11
  139. package/dist/scheduler/activeRoleTurnDelivery.js +245 -0
  140. package/dist/scheduler/leaderWakeupProcessor.js +147 -445
  141. package/dist/scheduler/operatorEvent.js +4 -6
  142. package/dist/scheduler/resourceQueueProjection.js +71 -0
  143. package/dist/scheduler/{roleRunLiveness.js → roleTurnLiveness.js} +24 -28
  144. package/dist/scheduler/{roleRunStall.js → roleTurnStall.js} +162 -247
  145. package/dist/scheduler/taskExecutionProjection.js +99 -99
  146. package/dist/scheduler/taskObservabilityProjection.js +10 -10
  147. package/dist/scheduler/taskWake.js +4 -4
  148. package/dist/scheduler/wakeReason.js +6 -10
  149. package/dist/scheduler/wakeupQueue.js +0 -9
  150. package/dist/setup/setupCommand.js +2 -2
  151. package/dist/storage/currentTaskStore.js +46 -0
  152. package/dist/storage/persistenceWorker.js +1 -1
  153. package/dist/storage/sqliteSchema.js +81 -485
  154. package/dist/storage/sqliteStore.js +206 -365
  155. package/dist/storage/storageSchema.js +1 -15
  156. package/dist/storage/storageVersions.js +8 -9
  157. package/dist/storage/storeRpc.js +10 -34
  158. package/dist/storage/taskStore.js +63 -3415
  159. package/dist/storage/upgrade/recordVersions.js +55 -149
  160. package/dist/storage/upgrade/upgradeOrchestrator.js +89 -1752
  161. package/dist/task/completionReadiness.js +5 -5
  162. package/dist/task/deliveryGuard.js +10 -10
  163. package/dist/task/nextAction.js +74 -77
  164. package/dist/task/task.js +2 -4
  165. package/dist/task/taskRecordReference.js +3 -3
  166. package/dist/task/taskRecordRetirement.js +1 -1
  167. package/dist/telemetry/sqliteTelemetryStore.js +53 -53
  168. package/dist/telemetry/telemetryConfig.js +14 -14
  169. package/dist/telemetry/telemetryWiring.js +8 -14
  170. package/dist/turn/turn.js +356 -0
  171. package/dist/{run/runIdentity.js → turn/turnIdentity.js} +4 -4
  172. package/dist/verification/gateArtifactStore.js +2 -2
  173. package/dist/web/assets/client/app.js +1 -1
  174. package/dist/web/assets/client/components.js +29 -54
  175. package/dist/web/assets/client/i18n.js +17 -37
  176. package/dist/web/assets/client/view.js +37 -37
  177. package/dist/web/assets/shell.js +1 -1
  178. package/dist/web/assets/styles/cards.js +5 -5
  179. package/dist/web/assets/styles/widgets.js +1 -2
  180. package/dist/web/webSnapshot.js +65 -60
  181. package/dist/workItem/workItem.js +11 -11
  182. package/dist/workspace/workItemChangeSetManager.js +1 -1
  183. package/docs/task-local-identity.md +5 -5
  184. package/i18n/README.zh-CN.md +41 -54
  185. package/package.json +1 -1
  186. package/skills/yui-leader/SKILL.md +85 -83
  187. package/skills/yui-operator/SKILL.md +16 -16
  188. package/skills/yui-reviewer/SKILL.md +10 -10
  189. package/skills/yui-runtime/SKILL.md +17 -18
  190. package/skills/yui-worker/SKILL.md +16 -18
  191. package/dist/context/runContextContract.js +0 -162
  192. package/dist/executor/turnCompletion.js +0 -126
  193. package/dist/review/taskFinalReviewContractEvent.js +0 -1
  194. package/dist/review/taskFinalReviewContractRebind.js +0 -367
  195. package/dist/run/agentRun.js +0 -356
  196. package/dist/run/rejectedYieldAttempt.js +0 -221
  197. package/dist/run/runControlRequest.js +0 -50
  198. package/dist/run/yieldReceipt.js +0 -65
  199. package/dist/runtime/turnCompletion.js +0 -3
  200. package/dist/scheduler/actionability.js +0 -323
  201. package/dist/scheduler/activeRoleRunDelivery.js +0 -868
  202. package/dist/scheduler/activeTaskProgress.js +0 -126
  203. package/dist/storage/compatibleTaskStore.js +0 -248
  204. package/dist/storage/migration/baseline.js +0 -78
  205. package/dist/storage/migration/classifier.js +0 -51
  206. package/dist/storage/migration/compatibleCodec.js +0 -53
  207. package/dist/storage/migration/engine.js +0 -147
  208. package/dist/storage/migration/index.js +0 -33
  209. package/dist/storage/migration/planner.js +0 -154
  210. package/dist/storage/migration/productionRegistry.js +0 -3931
  211. package/dist/storage/migration/registry.js +0 -169
  212. package/dist/storage/migration/report.js +0 -54
  213. package/dist/storage/migration/types.js +0 -31
  214. package/dist/storage/upgrade/homeClassification.js +0 -353
  215. package/dist/storage/upgrade/homeMigrationTarget.js +0 -595
  216. package/dist/storage/upgrade/migrationReceipt.js +0 -67
  217. package/dist/storage/upgrade/offlineUpgradeInventory.js +0 -453
  218. package/dist/storage/upgrade/productionMigrationRegistry.js +0 -6
  219. package/dist/storage/upgrade/pseudoLayoutRepair.js +0 -241
  220. package/dist/storage/upgrade/recordVersionScan.js +0 -176
  221. package/dist/storage/upgrade/sqliteFileSet.js +0 -50
  222. package/dist/storage/upgrade/sqliteMigrationTarget.js +0 -375
  223. package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +0 -305
  224. package/dist/storage/upgrade/sqliteStateMigration.js +0 -918
  225. package/dist/storage/upgrade/switchProgress.js +0 -80
  226. package/dist/storage/upgrade/upgradeReceipt.js +0 -161
  227. package/dist/storage/upgradeCoordination.js +0 -187
  228. package/dist/storage/upgradeFence.js +0 -366
  229. package/dist/telemetry/telemetryCompaction.js +0 -252
@@ -8,7 +8,7 @@ import { callController, controllerCallMayHaveApplied, ControllerClientError } f
8
8
  import { readHomeFilesystemId } from "../core/homeFilesystemIdentity.js";
9
9
  import { callFileTaskController } from "../controller/clientRuntime.js";
10
10
  import { isForeignHandoverLockHeld } from "../release/runtimeRelease.js";
11
- import { publishStructuredProviderAccepted, publishStructuredProviderActivationTerminal, publishStructuredConversationRecoverability, publishStructuredProviderOpened, publishStructuredProviderTerminal } from "../controller/structuredProviderObservation.js";
11
+ import { publishStructuredProviderAccepted, publishStructuredProviderActivationTerminal, publishStructuredProviderGoal, publishStructuredConversationRecoverability, publishStructuredProviderOpened, publishStructuredProviderStarted, publishStructuredProviderTerminal } from "../controller/structuredProviderObservation.js";
12
12
  import { validateAgentHostLaunchPayload } from "./launchBroker.js";
13
13
  import { ProviderDeliveryUnknownError, ProviderConversationMissingError, ProviderTurnBusyError, ProviderTurnRejectedError, startStructuredProviderSession } from "./structuredProviderHost.js";
14
14
  import { sameProviderAuthorityFence, validateProviderAuthorityFence } from "./providerAuthorityFence.js";
@@ -42,6 +42,7 @@ export async function runAgentHost(input) {
42
42
  let snapshot = hostSnapshot("idle");
43
43
  let dispatchTail = Promise.resolve();
44
44
  let promptHuman = () => { };
45
+ const recentSteerAttempts = new Set();
45
46
  await replayExitOutbox(input.home);
46
47
  const updateSnapshot = (next) => {
47
48
  snapshot = validateSnapshot(next);
@@ -51,6 +52,29 @@ export async function runAgentHost(input) {
51
52
  authorityOwner: authority.owner,
52
53
  authorityHolderId: authority.holderId
53
54
  };
55
+ const handleStarted = (started, observedPayload = sessionPayload) => {
56
+ if (started.clientOwned || observedPayload === undefined)
57
+ return;
58
+ updateSnapshot(hostSnapshot("busy", {
59
+ launchId: observedPayload.launchId,
60
+ adapterId: observedPayload.providerControl?.adapterId,
61
+ processInstanceId: session?.processInstanceId,
62
+ nativeSessionId: started.nativeSessionId,
63
+ conversationId: started.conversationId,
64
+ nativeTurnId: started.nativeTurnId,
65
+ ...authorityFields()
66
+ }));
67
+ const directActivationId = activationId ?? observedPayload.launchId;
68
+ void enqueueSerialized(async () => {
69
+ await publishStructuredProviderStarted({
70
+ home: input.home,
71
+ environment: observedPayload.environment,
72
+ activationId: directActivationId,
73
+ started
74
+ });
75
+ signalRoleMailbox(input.home, observedPayload);
76
+ }).catch(() => { });
77
+ };
54
78
  const handleTerminal = (terminal) => {
55
79
  if (!terminal.clientOwned) {
56
80
  const busyPayload = sessionPayload;
@@ -58,19 +82,29 @@ export async function runAgentHost(input) {
58
82
  || session === undefined
59
83
  || terminal.conversationId !== session.conversationId)
60
84
  return;
61
- // Another ordinary client completed a Turn. It never enters Yui's Run
62
- // observation path; it only removes backpressure from retained work.
63
- if (snapshot.state === "busy") {
64
- updateSnapshot(hostSnapshot("idle", {
65
- launchId: busyPayload.launchId,
66
- adapterId: session.adapterId,
67
- processInstanceId: session.processInstanceId,
68
- nativeSessionId: session.nativeSessionId,
69
- conversationId: session.conversationId,
70
- ...authorityFields()
71
- }));
72
- }
73
- signalRoleMailbox(input.home, busyPayload);
85
+ // A direct Provider client shares the Session but still produces a Yui
86
+ // Turn audit record. It has no WorkItem ownership and no managed input
87
+ // fence; the runtime observer records only visible input/output.
88
+ const directActivationId = activationId ?? busyPayload.launchId;
89
+ void enqueueSerialized(async () => {
90
+ await publishStructuredProviderTerminal({
91
+ home: input.home,
92
+ environment: busyPayload.environment,
93
+ activationId: directActivationId,
94
+ terminal
95
+ });
96
+ if (session !== undefined && terminal.conversationId === session.conversationId) {
97
+ updateSnapshot(hostSnapshot(session.activeTurnId === undefined ? "idle" : "busy", {
98
+ launchId: busyPayload.launchId,
99
+ adapterId: session.adapterId,
100
+ processInstanceId: session.processInstanceId,
101
+ nativeSessionId: session.nativeSessionId,
102
+ conversationId: session.conversationId,
103
+ ...authorityFields()
104
+ }));
105
+ }
106
+ signalRoleMailbox(input.home, busyPayload);
107
+ }).catch(() => { });
74
108
  return;
75
109
  }
76
110
  const terminalPayload = activeTurnPayload;
@@ -130,6 +164,23 @@ export async function runAgentHost(input) {
130
164
  }
131
165
  }).catch(() => { });
132
166
  };
167
+ const handleGoal = (goal) => {
168
+ const currentPayload = activeTurnPayload ?? sessionPayload;
169
+ const currentSession = session;
170
+ if (currentPayload === undefined || currentSession === undefined)
171
+ return;
172
+ const currentActivationId = activationId ?? currentPayload.launchId;
173
+ void enqueueSerialized(async () => {
174
+ await publishStructuredProviderGoal({
175
+ home: input.home,
176
+ environment: currentPayload.environment,
177
+ activationId: currentActivationId,
178
+ conversationId: currentSession.conversationId,
179
+ goal
180
+ });
181
+ signalRoleMailbox(input.home, currentPayload);
182
+ }).catch(() => { });
183
+ };
133
184
  const reconnectCodexClient = async (disconnectedSession, currentPayload) => {
134
185
  const previousControl = currentPayload.providerControl;
135
186
  if (previousControl?.adapterId !== "codex" || authority === undefined) {
@@ -169,7 +220,9 @@ export async function runAgentHost(input) {
169
220
  return;
170
221
  try {
171
222
  const started = await startStructuredProviderSession(reconnectPayload, {
172
- onTerminal: handleTerminal
223
+ onStarted: (turn) => handleStarted(turn, currentPayload),
224
+ onTerminal: handleTerminal,
225
+ onGoal: handleGoal
173
226
  });
174
227
  session = started.session;
175
228
  sessionPayload = currentPayload;
@@ -197,6 +250,17 @@ export async function runAgentHost(input) {
197
250
  if (started.recoveredTerminal !== undefined) {
198
251
  handleTerminal(started.recoveredTerminal);
199
252
  }
253
+ if (ownedTurn === undefined && started.session.activeTurnId !== undefined) {
254
+ handleStarted({
255
+ conversationId: started.session.conversationId,
256
+ nativeSessionId: started.session.nativeSessionId,
257
+ nativeTurnId: started.session.activeTurnId,
258
+ clientOwned: false,
259
+ observedAt: new Date().toISOString()
260
+ }, currentPayload);
261
+ }
262
+ if (started.goal !== undefined)
263
+ handleGoal(started.goal);
200
264
  return;
201
265
  }
202
266
  catch (error) {
@@ -298,9 +362,9 @@ export async function runAgentHost(input) {
298
362
  ? {}
299
363
  : { taskId: currentPayload.environment.YUI_TASK_ID }),
300
364
  roleName: currentPayload.environment.YUI_ROLE ?? "unknown-role",
301
- ...(currentPayload.environment.YUI_RUN_ID === undefined
365
+ ...(currentPayload.environment.YUI_TURN_ID === undefined
302
366
  ? {}
303
- : { runId: currentPayload.environment.YUI_RUN_ID }),
367
+ : { turnId: currentPayload.environment.YUI_TURN_ID }),
304
368
  launchId: currentPayload.launchId,
305
369
  ...(providerSession.nativeSessionId.length === 0
306
370
  ? {}
@@ -372,6 +436,7 @@ export async function runAgentHost(input) {
372
436
  }),
373
437
  ...authorityFields()
374
438
  }));
439
+ let recoveredGoal;
375
440
  try {
376
441
  if (session !== undefined) {
377
442
  if (session.adapterId !== providerControl.adapterId
@@ -391,9 +456,14 @@ export async function runAgentHost(input) {
391
456
  activeTurnAttemptId = providerControl.ownedTurn.attemptId;
392
457
  activeNativeTurnId = providerControl.ownedTurn.turnId;
393
458
  }
394
- const started = await startStructuredProviderSession(next, { onTerminal: handleTerminal });
459
+ const started = await startStructuredProviderSession(next, {
460
+ onStarted: (turn) => handleStarted(turn, next),
461
+ onTerminal: handleTerminal,
462
+ onGoal: handleGoal
463
+ });
395
464
  session = started.session;
396
465
  sessionPayload = next;
466
+ recoveredGoal = started.goal;
397
467
  if (started.session.adapterId === "codex") {
398
468
  codexClientAttachedAt = Date.now();
399
469
  consecutiveCodexDisconnects = 0;
@@ -402,6 +472,16 @@ export async function runAgentHost(input) {
402
472
  if (started.recoveredTerminal !== undefined) {
403
473
  handleTerminal(started.recoveredTerminal);
404
474
  }
475
+ if ((providerControl.kind !== "restore" || providerControl.ownedTurn === undefined)
476
+ && started.session.activeTurnId !== undefined) {
477
+ handleStarted({
478
+ conversationId: started.session.conversationId,
479
+ nativeSessionId: started.session.nativeSessionId,
480
+ nativeTurnId: started.session.activeTurnId,
481
+ clientOwned: false,
482
+ observedAt: new Date().toISOString()
483
+ }, next);
484
+ }
405
485
  }
406
486
  await publishStructuredProviderOpened({
407
487
  home: input.home,
@@ -412,6 +492,15 @@ export async function runAgentHost(input) {
412
492
  recoverability: conversationRecoverability,
413
493
  observedAt: new Date().toISOString()
414
494
  });
495
+ if (recoveredGoal !== undefined) {
496
+ await publishStructuredProviderGoal({
497
+ home: input.home,
498
+ environment: next.environment,
499
+ activationId: activationId ?? next.launchId,
500
+ conversationId: session.conversationId,
501
+ goal: recoveredGoal
502
+ });
503
+ }
415
504
  const restoredOwnedTurn = providerControl.kind === "restore"
416
505
  ? providerControl.ownedTurn
417
506
  : undefined;
@@ -488,11 +577,12 @@ export async function runAgentHost(input) {
488
577
  if (activeTurnPayload !== undefined || snapshot.state === "settling") {
489
578
  throw new ProviderTurnBusyError("Agent Host still owns an unsettled Provider Turn.", request.turn.attemptId, activeNativeTurnId);
490
579
  }
580
+ const { YUI_TURN_ID: _launchTurnId, ...baseEnvironment } = sessionPayload.environment;
491
581
  activeTurnPayload = {
492
582
  ...sessionPayload,
493
583
  environment: {
494
- ...sessionPayload.environment,
495
- YUI_RUN_ID: request.runId
584
+ ...baseEnvironment,
585
+ ...(request.turnId === undefined ? {} : { YUI_TURN_ID: request.turnId })
496
586
  }
497
587
  };
498
588
  activeTurnAttemptId = request.turn.attemptId;
@@ -506,7 +596,7 @@ export async function runAgentHost(input) {
506
596
  attemptId: request.turn.attemptId,
507
597
  ...authorityFields()
508
598
  }));
509
- const durableTurn = hostTurnControlParams(sessionPayload, session.nativeSessionId, request.authority, request.turn.attemptId, request.runId);
599
+ const durableTurn = hostTurnControlParams(sessionPayload, session.nativeSessionId, request.authority, request.turn.attemptId, request.turnId);
510
600
  await beginDurableProviderTurn(input.home, durableTurn);
511
601
  let providerAccepted = false;
512
602
  try {
@@ -516,7 +606,7 @@ export async function runAgentHost(input) {
516
606
  try {
517
607
  await publishStructuredProviderAccepted({
518
608
  home: input.home,
519
- environment: sessionPayload.environment,
609
+ environment: activeTurnPayload.environment,
520
610
  activationId: activationId ?? sessionPayload.launchId,
521
611
  receipt
522
612
  });
@@ -569,6 +659,29 @@ export async function runAgentHost(input) {
569
659
  throw error;
570
660
  }
571
661
  };
662
+ const steerTurn = async (request) => {
663
+ if (session === undefined || sessionPayload === undefined || activeTurnPayload === undefined) {
664
+ throw new ProviderTurnRejectedError("Agent Host has no active Provider Turn to steer.", request.turn.attemptId);
665
+ }
666
+ if (request.nativeSessionId !== session.nativeSessionId
667
+ || request.nativeTurnId !== activeNativeTurnId) {
668
+ throw new ProviderTurnRejectedError("Agent Host steer targets a different Provider Turn.", request.turn.attemptId);
669
+ }
670
+ if (authority === undefined || !sameProviderAuthorityFence(authority, request.authority)) {
671
+ throw new Error("Agent Host rejected a stale Provider writer fence.");
672
+ }
673
+ if (recentSteerAttempts.has(request.turn.attemptId))
674
+ return snapshot;
675
+ const receipt = await session.steerTurn(request.turn);
676
+ if (receipt.nativeTurnId !== request.nativeTurnId) {
677
+ throw new ProviderDeliveryUnknownError("Provider accepted steer against an unexpected native Turn.", request.turn.attemptId);
678
+ }
679
+ recentSteerAttempts.add(request.turn.attemptId);
680
+ if (recentSteerAttempts.size > 256) {
681
+ recentSteerAttempts.delete(recentSteerAttempts.values().next().value);
682
+ }
683
+ return snapshot;
684
+ };
572
685
  const setAuthority = (request) => {
573
686
  if (session === undefined || request.nativeSessionId !== session.nativeSessionId) {
574
687
  throw new Error("Agent Host authority targets a different Provider Conversation.");
@@ -605,6 +718,10 @@ export async function runAgentHost(input) {
605
718
  const accepted = await enqueueSerialized(() => submitTurn(request));
606
719
  return controlResult("accepted", accepted);
607
720
  }
721
+ if (request.type === "steer-turn") {
722
+ const accepted = await enqueueSerialized(() => steerTurn(request));
723
+ return controlResult("accepted", accepted);
724
+ }
608
725
  if (request.type === "set-authority") {
609
726
  const accepted = await enqueueSerialized(async () => setAuthority(request));
610
727
  return controlResult("accepted", accepted);
@@ -652,22 +769,13 @@ export async function runAgentHost(input) {
652
769
  type: "submit-turn",
653
770
  launchId: currentPayload.launchId,
654
771
  nativeSessionId: currentSession.nativeSessionId,
655
- runId: requiredEnvironment(currentPayload.environment.YUI_RUN_ID, "Run id"),
656
772
  authority: currentAuthority,
657
773
  turn: {
658
774
  attemptId,
659
775
  boundedText
660
776
  }
661
777
  };
662
- const durableTurn = hostTurnControlParams(currentPayload, currentSession.nativeSessionId, currentAuthority, attemptId);
663
- await beginDurableProviderTurn(input.home, durableTurn);
664
- try {
665
- await submitTurn(turnControl);
666
- }
667
- catch (error) {
668
- await resolveProviderTurnSubmission(input.home, durableTurn, error);
669
- throw error;
670
- }
778
+ await submitTurn(turnControl);
671
779
  process.stdout.write("Provider accepted the human Turn; waiting for its terminal boundary.\n");
672
780
  }).catch((error) => {
673
781
  process.stderr.write(`Provider input failed: ${errorText(error)}\n`);
@@ -736,6 +844,9 @@ export async function sendAgentHostLaunchControl(input) {
736
844
  export async function sendAgentHostTurnControl(input) {
737
845
  return await sendAgentHostControl(input);
738
846
  }
847
+ export async function sendAgentHostSteerControl(input) {
848
+ return await sendAgentHostControl(input);
849
+ }
739
850
  export async function sendAgentHostAuthorityControl(input) {
740
851
  return await sendAgentHostControl(input);
741
852
  }
@@ -919,10 +1030,14 @@ function validateControl(control) {
919
1030
  }
920
1031
  if (control.type === "status")
921
1032
  return Object.freeze({ ...control });
922
- if (control.type === "submit-turn") {
1033
+ if (control.type === "submit-turn" || control.type === "steer-turn") {
923
1034
  validateLaunchId(control.launchId);
924
1035
  validateIdentity(control.nativeSessionId, "native Session id");
925
- validateIdentity(control.runId, "Run id");
1036
+ if (control.type === "submit-turn" && control.turnId !== undefined) {
1037
+ validateIdentity(control.turnId, "Turn id");
1038
+ }
1039
+ if (control.type === "steer-turn")
1040
+ validateIdentity(control.nativeTurnId, "native Turn id");
926
1041
  validateProviderAuthorityFence(control.authority);
927
1042
  validateIdentity(control.turn.attemptId, "Provider input attempt id");
928
1043
  if (typeof control.turn.boundedText !== "string"
@@ -1028,12 +1143,12 @@ async function delay(milliseconds) {
1028
1143
  timer.unref();
1029
1144
  });
1030
1145
  }
1031
- function hostTurnControlParams(payload, nativeSessionId, authority, attemptId, runId = requiredEnvironment(payload.environment.YUI_RUN_ID, "Run id")) {
1146
+ function hostTurnControlParams(payload, nativeSessionId, authority, attemptId, turnId) {
1032
1147
  const environment = payload.environment;
1033
1148
  return Object.freeze({
1034
1149
  taskId: requiredEnvironment(environment.YUI_TASK_ID, "Task id"),
1035
1150
  roleName: requiredEnvironment(environment.YUI_ROLE, "Role name"),
1036
- runId: requiredEnvironment(runId, "Run id"),
1151
+ ...(turnId === undefined ? {} : { turnId: requiredEnvironment(turnId, "Turn id") }),
1037
1152
  agentId: requiredEnvironment(environment.YUI_AGENT_ID, "Agent id"),
1038
1153
  launchId: payload.launchId,
1039
1154
  nativeSessionId: requiredEnvironment(nativeSessionId, "native Session id"),
@@ -35,7 +35,7 @@ const STRUCTURED_CLI_CAPABILITIES = Object.freeze({
35
35
  }),
36
36
  input: Object.freeze({
37
37
  startTurn: true,
38
- steer: "unavailable",
38
+ steer: "fenced",
39
39
  inject: "unavailable",
40
40
  acceptance: "exact",
41
41
  idempotency: "unavailable"
@@ -52,6 +52,7 @@ const STRUCTURED_CLI_CAPABILITIES = Object.freeze({
52
52
  preInputReadiness: "unavailable",
53
53
  promptAcceptance: "exact",
54
54
  turnLifecycle: "exact",
55
+ goalLifecycle: "partial",
55
56
  // Transcript sources may emit an explicit activity identity separately
56
57
  // from usage snapshots. Numeric token values never decide runtime health.
57
58
  operations: Object.freeze(["tool", "subagent"]),
@@ -129,6 +130,7 @@ export const BUILTIN_AGENT_DRIVERS = Object.freeze([
129
130
  // to manufacture tool/wait/usage observations.
130
131
  operations: Object.freeze([]),
131
132
  waiting: Object.freeze([]),
133
+ goalLifecycle: "exact",
132
134
  usage: "unavailable"
133
135
  })
134
136
  }),
@@ -161,6 +163,16 @@ export function builtinAgentDriverRegistry() {
161
163
  return registry;
162
164
  }
163
165
  function mapClaudeHook(name, payload, occurrenceId) {
166
+ const lifecycle = mapClaudeLifecycleHook(name, payload, occurrenceId);
167
+ const goal = claudeGoalObservation(payload, occurrenceId);
168
+ if (goal === undefined)
169
+ return lifecycle;
170
+ return Object.freeze([
171
+ ...(Array.isArray(lifecycle) ? lifecycle : [lifecycle]),
172
+ goal
173
+ ]);
174
+ }
175
+ function mapClaudeLifecycleHook(name, payload, occurrenceId) {
164
176
  switch (name) {
165
177
  case "SessionStart":
166
178
  return [
@@ -248,6 +260,71 @@ function mapClaudeHook(name, payload, occurrenceId) {
248
260
  throw new Error(`Claude Code Driver does not support Hook event: ${name}.`);
249
261
  }
250
262
  }
263
+ function claudeGoalObservation(payload, occurrenceId) {
264
+ if (!Object.hasOwn(payload, "active_goal"))
265
+ return undefined;
266
+ if (payload.active_goal === null)
267
+ return mapped("goal.cleared");
268
+ const goal = objectValue(payload.active_goal);
269
+ if (goal === undefined)
270
+ return undefined;
271
+ const objective = textValue(goal.objective);
272
+ const status = normalizedGoalStatus(goal.status);
273
+ const updatedAt = timestampValue(goal.updated_at ?? goal.updatedAt)
274
+ ?? timestampValue(payload.timestamp)
275
+ ?? occurrenceTimestamp(occurrenceId);
276
+ if (objective === undefined || status === undefined || updatedAt === undefined)
277
+ return undefined;
278
+ const tokenBudget = positiveIntegerValue(goal.token_budget ?? goal.tokenBudget);
279
+ return mapped("goal.updated", {
280
+ goalStatus: status,
281
+ goalObjective: objective,
282
+ goalUpdatedAt: updatedAt,
283
+ ...(optionalIdentityFrom(goal, ["turn_id", "turnId"]) === undefined
284
+ ? {}
285
+ : { goalNativeTurnId: optionalIdentityFrom(goal, ["turn_id", "turnId"]) }),
286
+ ...(tokenBudget === undefined ? {} : { goalTokenBudget: tokenBudget })
287
+ });
288
+ }
289
+ function normalizedGoalStatus(value) {
290
+ if (value === "active" || value === "paused" || value === "blocked" || value === "complete") {
291
+ return value;
292
+ }
293
+ if (value === "usageLimited" || value === "usage_limited" || value === "usage-limited") {
294
+ return "usage-limited";
295
+ }
296
+ if (value === "budgetLimited" || value === "budget_limited" || value === "budget-limited") {
297
+ return "budget-limited";
298
+ }
299
+ return undefined;
300
+ }
301
+ function objectValue(value) {
302
+ return value !== null && typeof value === "object" && !Array.isArray(value)
303
+ ? value
304
+ : undefined;
305
+ }
306
+ function textValue(value) {
307
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
308
+ }
309
+ function positiveIntegerValue(value) {
310
+ return Number.isSafeInteger(value) && value > 0 ? value : undefined;
311
+ }
312
+ function timestampValue(value) {
313
+ if (typeof value === "string" && Number.isFinite(Date.parse(value))) {
314
+ return new Date(value).toISOString();
315
+ }
316
+ if (Number.isSafeInteger(value) && value >= 0) {
317
+ const epoch = value;
318
+ return new Date(epoch < 1_000_000_000_000 ? epoch * 1_000 : epoch).toISOString();
319
+ }
320
+ return undefined;
321
+ }
322
+ function occurrenceTimestamp(value) {
323
+ const prefix = value?.slice(0, 24);
324
+ return prefix !== undefined && Number.isFinite(Date.parse(prefix))
325
+ ? new Date(prefix).toISOString()
326
+ : undefined;
327
+ }
251
328
  function mapCodexHook(name, payload, occurrenceId) {
252
329
  switch (name) {
253
330
  case "SessionStart":
@@ -402,7 +479,7 @@ function claudeFailure(payload) {
402
479
  }),
403
480
  ...(lastOutput === undefined ? {} : { lastOutput }),
404
481
  ...(payload.run_terminal === true || payload.unrecoverable === true
405
- ? { runTerminal: true }
482
+ ? { turnTerminal: true }
406
483
  : {})
407
484
  },
408
485
  summary: [
@@ -49,6 +49,12 @@ export class CodexAppServerRuntime {
49
49
  });
50
50
  return parseThreadSnapshot(result, id, "unknown");
51
51
  }
52
+ async readGoal(conversationId) {
53
+ const id = text(conversationId, "Codex thread id");
54
+ const result = await this.transport.request("thread/goal/get", { threadId: id });
55
+ const goal = objectMember(result, "goal");
56
+ return goal === null ? null : codexThreadGoal(goal, id);
57
+ }
52
58
  async inspectConversation(conversationId) {
53
59
  const id = text(conversationId, "Codex thread id");
54
60
  try {
@@ -235,12 +241,29 @@ export function codexNotificationBoundary(input) {
235
241
  ?? optionalId(objectMember(input.params, "turn")?.id);
236
242
  if (input.method === "thread/closed")
237
243
  return { kind: "activation-ended", conversationId };
244
+ if (input.method === "thread/goal/updated")
245
+ return { kind: "goal-updated", conversationId, turnId };
246
+ if (input.method === "thread/goal/cleared")
247
+ return { kind: "goal-cleared", conversationId };
238
248
  if (input.method === "turn/started")
239
249
  return { kind: "turn-started", conversationId, turnId };
240
250
  if (input.method === "turn/completed")
241
251
  return { kind: "turn-completed", conversationId, turnId };
242
252
  return { kind: "other", conversationId, turnId };
243
253
  }
254
+ export function codexGoalNotification(input) {
255
+ const conversationId = optionalId(input.params.threadId);
256
+ if (conversationId === undefined)
257
+ return undefined;
258
+ if (input.method === "thread/goal/cleared")
259
+ return null;
260
+ if (input.method !== "thread/goal/updated")
261
+ return undefined;
262
+ const goal = objectMember(input.params, "goal");
263
+ if (goal === null)
264
+ return undefined;
265
+ return codexThreadGoal(goal, conversationId);
266
+ }
244
267
  function parseThreadSnapshot(result, expectedThreadId, loaded) {
245
268
  const thread = objectMember(result, "thread") ?? result;
246
269
  const id = optionalId(thread.id) ?? expectedThreadId;
@@ -254,10 +277,12 @@ function parseThreadSnapshot(result, expectedThreadId, loaded) {
254
277
  if (turnId === undefined)
255
278
  return [];
256
279
  const status = optionalTurnStatus(turn.status);
280
+ const summary = codexTurnSummary(turn);
257
281
  const error = providerError(turn.error);
258
282
  return [{
259
283
  turnId,
260
284
  ...(status === undefined ? {} : { status }),
285
+ ...(summary === undefined ? {} : { summary }),
261
286
  ...(error === undefined ? {} : { error })
262
287
  }];
263
288
  });
@@ -275,6 +300,62 @@ function parseThreadSnapshot(result, expectedThreadId, loaded) {
275
300
  raw: result
276
301
  };
277
302
  }
303
+ /** Extract the last assistant message from one native Codex Turn. */
304
+ export function codexTurnSummary(turn) {
305
+ for (const value of [...arrayMember(turn, "items")].reverse()) {
306
+ const item = object(value);
307
+ if (item === null)
308
+ continue;
309
+ const type = item.type;
310
+ const message = object(item.message);
311
+ const agentMessage = type === "agentMessage"
312
+ || type === "agent_message"
313
+ || (type === "message" && (item.role === "assistant" || message?.role === "assistant"));
314
+ if (!agentMessage)
315
+ continue;
316
+ const text = messageText(item) ?? (message === null ? undefined : messageText(message));
317
+ if (text !== undefined)
318
+ return text;
319
+ }
320
+ return undefined;
321
+ }
322
+ /** Extract the provider-visible human input without copying tool or reasoning items. */
323
+ export function codexTurnInput(turn) {
324
+ for (const value of arrayMember(turn, "items")) {
325
+ const item = object(value);
326
+ if (item === null)
327
+ continue;
328
+ const type = item.type;
329
+ const message = object(item.message);
330
+ const userMessage = type === "userMessage"
331
+ || type === "user_message"
332
+ || (type === "message" && (item.role === "user" || message?.role === "user"));
333
+ if (!userMessage)
334
+ continue;
335
+ const text = messageText(item) ?? (message === null ? undefined : messageText(message));
336
+ if (text !== undefined)
337
+ return text;
338
+ }
339
+ return undefined;
340
+ }
341
+ function messageText(message) {
342
+ for (const value of [message.text, message.content]) {
343
+ if (typeof value === "string" && value.trim().length > 0)
344
+ return value.trim();
345
+ if (!Array.isArray(value))
346
+ continue;
347
+ const parts = value.flatMap((part) => {
348
+ if (typeof part === "string" && part.trim().length > 0)
349
+ return [part];
350
+ const record = object(part);
351
+ const text = record?.text ?? record?.content;
352
+ return typeof text === "string" && text.trim().length > 0 ? [text] : [];
353
+ });
354
+ if (parts.length > 0)
355
+ return parts.join("\n").trim();
356
+ }
357
+ return undefined;
358
+ }
278
359
  function providerError(value) {
279
360
  if (typeof value === "string" && value.trim().length > 0)
280
361
  return value.trim();
@@ -286,6 +367,47 @@ function providerError(value) {
286
367
  }
287
368
  return JSON.stringify(record);
288
369
  }
370
+ function codexThreadGoal(goal, expectedThreadId) {
371
+ const conversationId = text(goal.threadId, "Codex Goal thread id");
372
+ if (conversationId !== expectedThreadId) {
373
+ throw new Error("Codex Goal belongs to a different thread.");
374
+ }
375
+ const status = codexGoalStatus(goal.status);
376
+ const tokenBudget = goal.tokenBudget;
377
+ return Object.freeze({
378
+ conversationId,
379
+ status,
380
+ objective: text(goal.objective, "Codex Goal objective"),
381
+ updatedAt: codexEpochTimestamp(goal.updatedAt),
382
+ ...(tokenBudget === null || tokenBudget === undefined
383
+ ? {}
384
+ : { tokenBudget: positiveInteger(tokenBudget, "Codex Goal token budget") })
385
+ });
386
+ }
387
+ function codexGoalStatus(value) {
388
+ switch (value) {
389
+ case "active":
390
+ case "paused":
391
+ case "blocked":
392
+ case "complete":
393
+ return value;
394
+ case "usageLimited": return "usage-limited";
395
+ case "budgetLimited": return "budget-limited";
396
+ default: throw new Error("Codex Goal status is invalid.");
397
+ }
398
+ }
399
+ function codexEpochTimestamp(value) {
400
+ if (!Number.isSafeInteger(value) || value < 0) {
401
+ throw new Error("Codex Goal updatedAt is invalid.");
402
+ }
403
+ const epoch = value;
404
+ return new Date(epoch < 1_000_000_000_000 ? epoch * 1_000 : epoch).toISOString();
405
+ }
406
+ function positiveInteger(value, label) {
407
+ if (!Number.isSafeInteger(value) || value < 1)
408
+ throw new Error(`${label} is invalid.`);
409
+ return value;
410
+ }
289
411
  function codexContinuationState(snapshot) {
290
412
  if (snapshot.status === "active" || snapshot.latestTurnStatus === "inProgress") {
291
413
  return {
@@ -11,7 +11,7 @@ export function foldContinuationObservation(existing, raw) {
11
11
  const base = existing ?? createProviderContinuation({
12
12
  taskId: observation.fence.taskId,
13
13
  roleName: observation.fence.roleName,
14
- runId: observation.fence.runId,
14
+ turnId: observation.fence.turnId,
15
15
  identity,
16
16
  ...(observation.fence.parentContinuationId === undefined
17
17
  ? {}
@@ -107,9 +107,7 @@ export function applyContinuationObservationAtomically(store, raw, factRef) {
107
107
  refs: [factRef],
108
108
  occurredAt: observation.observedAt ?? observation.receivedAt,
109
109
  source: "continuation-manager",
110
- dedupeKey: observation.semanticKey,
111
- deliveryMode: "followup",
112
- lane: "normal"
110
+ dedupeKey: observation.semanticKey
113
111
  }
114
112
  });
115
113
  }
@@ -118,7 +116,7 @@ export function applyContinuationObservationAtomically(store, raw, factRef) {
118
116
  }
119
117
  function continuationIdentity(observation) {
120
118
  const fence = observation.fence;
121
- if (fence.taskId === undefined || fence.runId === undefined
119
+ if (fence.taskId === undefined || fence.turnId === undefined
122
120
  || fence.conversationId === undefined || fence.activationId === undefined
123
121
  || fence.continuationId === undefined) {
124
122
  throw new Error("Continuation observation fence is incomplete.");