@zq-silk/yui 2.21.0 → 2.23.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 (145) hide show
  1. package/README.md +5 -0
  2. package/dist/cli/commandCatalog.js +87 -27
  3. package/dist/cli/dynamicCompletion.js +4 -1
  4. package/dist/cli/helpRenderer.js +12 -1
  5. package/dist/cli/interactionPolicy.js +2 -0
  6. package/dist/cli/invocationRouter.js +5 -5
  7. package/dist/cli/roleWizard.js +5 -1
  8. package/dist/cli/updateCommand.js +20 -3
  9. package/dist/cli/updateOrchestrator.js +13 -0
  10. package/dist/cli/updatePorts.js +27 -0
  11. package/dist/commands/globalRoleCommands.js +33 -8
  12. package/dist/commands/inboxCommands.js +39 -12
  13. package/dist/commands/operatorCommands.js +2 -5
  14. package/dist/commands/releaseCommands.js +16 -2
  15. package/dist/commands/roleConfiguration.js +25 -2
  16. package/dist/commands/sessionCommands.js +5 -2
  17. package/dist/commands/taskCommands.js +217 -64
  18. package/dist/commands/taskContextCommand.js +26 -4
  19. package/dist/commands/taskFactCommands.js +15 -2
  20. package/dist/commands/taskIntegrationCommands.js +3 -3
  21. package/dist/commands/taskRoleRuntimeStatus.js +19 -5
  22. package/dist/commands/textInput.js +2 -2
  23. package/dist/context/contextFamilies.js +10 -0
  24. package/dist/context/currentSkills.js +37 -4
  25. package/dist/context/runContextPack.js +44 -5
  26. package/dist/context/runInputContract.js +1 -1
  27. package/dist/context/sessionBootstrapManifest.js +21 -6
  28. package/dist/context/sessionSkillNotice.js +74 -0
  29. package/dist/context/taskContext.js +100 -16
  30. package/dist/controlPlaneCli.js +17 -7
  31. package/dist/controller/agentRuntimeObserver.js +57 -12
  32. package/dist/controller/clientRuntime.js +66 -18
  33. package/dist/controller/controller.js +19 -2
  34. package/dist/controller/diagnosticJournal.js +10 -2
  35. package/dist/controller/fileSchedulerStoreAdapter.js +74 -24
  36. package/dist/controller/hostLifecycle.js +153 -0
  37. package/dist/controller/hostLifecycleChecks.js +27 -0
  38. package/dist/controller/hostUpgrade.js +344 -0
  39. package/dist/controller/messageHealth.js +4 -3
  40. package/dist/controller/providerRetryAdmission.js +15 -1
  41. package/dist/controller/providerRetryDelivery.js +18 -2
  42. package/dist/controller/runtime.js +82 -21
  43. package/dist/controller/runtimeEventInbox.js +107 -13
  44. package/dist/controller/runtimeEventProcessor.js +3 -3
  45. package/dist/controller/sessionNotify.js +5 -46
  46. package/dist/controller/taskRunRecovery.js +153 -0
  47. package/dist/core/controllerClient.js +16 -2
  48. package/dist/core/controllerServer.js +14 -5
  49. package/dist/core/controllerTelemetry.js +51 -5
  50. package/dist/errors/cliFailure.js +14 -5
  51. package/dist/execution/codexThreadNaming.js +54 -43
  52. package/dist/executor/agentAdapter.js +32 -4
  53. package/dist/executor/agentConfigurationFields.js +2 -0
  54. package/dist/executor/fileRoleLaunchPlanner.js +18 -3
  55. package/dist/integration/gitIntegrationService.js +3 -2
  56. package/dist/integration/integrationSourceApplication.js +12 -1
  57. package/dist/interaction/notificationPresentation.js +11 -16
  58. package/dist/interaction/operatorPresentation.js +1 -1
  59. package/dist/message/messageContinuation.js +3 -1
  60. package/dist/output/boundedRead.js +36 -7
  61. package/dist/output/rolePresentation.js +3 -1
  62. package/dist/runtime/agentEndpoint.js +11 -1
  63. package/dist/runtime/agentEndpointIdentity.js +2 -0
  64. package/dist/runtime/agentHost.js +174 -40
  65. package/dist/runtime/agentHostImplementation.js +3 -1
  66. package/dist/runtime/codexAppServerRuntime.js +113 -24
  67. package/dist/runtime/codexInteractiveHost.js +102 -11
  68. package/dist/runtime/codexNativeAccess.js +1 -1
  69. package/dist/runtime/contextRecovery.js +15 -0
  70. package/dist/runtime/hostSessionLifecycle.js +83 -0
  71. package/dist/runtime/hostUpgradeGate.js +43 -0
  72. package/dist/runtime/lifecycleChecks.js +47 -0
  73. package/dist/runtime/lifecycleReservation.js +9 -1
  74. package/dist/runtime/nativeTitleReceipt.js +31 -0
  75. package/dist/runtime/nativeTitleSync.js +192 -0
  76. package/dist/runtime/providerRetry.js +9 -6
  77. package/dist/runtime/providerRuntimeIdentity.js +1 -1
  78. package/dist/runtime/structuredProviderHost.js +65 -35
  79. package/dist/scheduler/wakeupQueue.js +10 -4
  80. package/dist/storage/sqliteSchema.js +29 -0
  81. package/dist/storage/storageVersions.js +1 -1
  82. package/dist/tmux/tmuxManager.js +4 -2
  83. package/dist/web/webSettings.js +2 -0
  84. package/docs/authorization.md +18 -3
  85. package/docs/cli-information-contract.md +228 -6
  86. package/docs/cli-information-contract.zh-CN.md +95 -5
  87. package/docs/managed-turn-and-session-runtime.md +177 -21
  88. package/docs/managed-turn-and-session-runtime.zh-CN.md +126 -14
  89. package/docs/provider-retry.md +11 -0
  90. package/docs/release-workflow.md +65 -0
  91. package/docs/release-workflow.zh-CN.md +48 -0
  92. package/docs/roles-and-configuration.md +28 -0
  93. package/docs/roles-and-configuration.zh-CN.md +21 -0
  94. package/docs/storage-baseline.md +13 -1
  95. package/docs/storage-baseline.zh-CN.md +5 -1
  96. package/docs/task-delivery.md +7 -0
  97. package/docs/task-delivery.zh-CN.md +5 -0
  98. package/docs/testing/review-evaluation.md +7 -7
  99. package/docs/testing/review-evaluation.zh-CN.md +7 -7
  100. package/docs/testing/verification-levels.md +45 -2
  101. package/docs/testing/verification-levels.zh-CN.md +21 -2
  102. package/i18n/README.zh-CN.md +6 -0
  103. package/package.json +1 -1
  104. package/skills/yui-leader/SKILL.md +98 -193
  105. package/skills/yui-leader/VERSION +1 -1
  106. package/skills/yui-leader/references/artifacts.md +41 -0
  107. package/skills/yui-leader/references/assignment-execution.md +140 -0
  108. package/skills/yui-leader/references/completion.md +79 -0
  109. package/skills/yui-leader/references/delivery-scope.md +26 -0
  110. package/skills/yui-leader/references/execution.md +16 -487
  111. package/skills/yui-leader/references/implementation.md +81 -0
  112. package/skills/yui-leader/references/planning.md +7 -45
  113. package/skills/yui-leader/references/review.md +90 -0
  114. package/skills/yui-leader/references/task-progress.md +23 -0
  115. package/skills/yui-leader/references/work-allocation.md +118 -0
  116. package/skills/yui-operator/SKILL.md +77 -333
  117. package/skills/yui-operator/VERSION +1 -1
  118. package/skills/yui-operator/references/body-transport.md +62 -0
  119. package/skills/yui-operator/references/configuration.md +47 -0
  120. package/skills/yui-operator/references/lifecycle.md +41 -0
  121. package/skills/yui-operator/references/progress.md +39 -0
  122. package/skills/yui-operator/references/routing.md +57 -0
  123. package/skills/yui-operator/references/submission.md +56 -0
  124. package/skills/yui-operator/references/task-delivery.md +1 -1
  125. package/skills/yui-operator/references/web-lifecycle.md +19 -0
  126. package/skills/yui-operator/scripts/verify-submission.mjs +37 -0
  127. package/skills/yui-runtime/SKILL.md +182 -531
  128. package/skills/yui-runtime/VERSION +1 -1
  129. package/skills/yui-runtime/references/archive.md +51 -0
  130. package/skills/yui-runtime/references/command-discovery.md +41 -0
  131. package/skills/yui-runtime/references/completed-followup.md +65 -0
  132. package/skills/yui-runtime/references/context-ordering.md +3 -34
  133. package/skills/yui-runtime/references/context-reads.md +61 -0
  134. package/skills/yui-runtime/references/dispatched-run.md +61 -0
  135. package/skills/yui-runtime/references/execution-failure.md +105 -0
  136. package/skills/yui-runtime/references/global-context.md +18 -0
  137. package/skills/yui-runtime/references/global-input-controls.md +44 -0
  138. package/skills/yui-runtime/references/host-custody.md +30 -0
  139. package/skills/yui-runtime/references/inbox-recovery.md +77 -0
  140. package/skills/yui-runtime/references/incremental-context.md +40 -0
  141. package/skills/yui-runtime/references/publication.md +4 -111
  142. package/skills/yui-runtime/references/recovery.md +32 -192
  143. package/skills/yui-runtime/references/session-identity.md +18 -0
  144. package/skills/yui-runtime/references/session-replacement.md +45 -0
  145. package/skills/yui-runtime/references/uncertain-execution.md +29 -0
@@ -4,7 +4,7 @@ import { createConnection, createServer } from "node:net";
4
4
  import { homedir, tmpdir } from "node:os";
5
5
  import { dirname, join, resolve } from "node:path";
6
6
  import { createInterface } from "node:readline";
7
- import { withSessionContextPointer } from "../context/sessionBootstrapManifest.js";
7
+ import { createSessionSkillNotice, createSessionSkillLifecycleCheck } from "../context/sessionSkillNotice.js";
8
8
  import { publishStructuredConversationRecoverability, publishStructuredProviderAccepted, publishStructuredProviderActivity, publishStructuredProviderAttachmentExit, publishStructuredProviderConnection, publishStructuredProviderDiagnostic, publishStructuredProviderGoal, publishStructuredProviderInputObserved, publishStructuredProviderInputSettlement, publishStructuredProviderOpened, publishStructuredProviderStarted, publishStructuredProviderTerminal, replayStructuredProviderPersistence, structuredProviderEventDelivery } from "../controller/structuredProviderObservation.js";
9
9
  import { callController, controllerCallMayHaveApplied, ControllerClientError } from "../core/controllerClient.js";
10
10
  import { readHomeFilesystemId } from "../core/homeFilesystemIdentity.js";
@@ -16,6 +16,9 @@ import { providerDeliveryFailure, providerDeliveryFailureFrom, redactAgentErrorT
16
16
  import { AGENT_HOST_CONTROL_PROTOCOL } from "./agentHostProtocol.js";
17
17
  import { AGENT_HOST_IMPLEMENTATION } from "./agentHostImplementation.js";
18
18
  import { closeAgentHostEndpoints } from "./agentHostCleanup.js";
19
+ import { createHostUpgradeGate } from "./hostUpgradeGate.js";
20
+ import { createHostSessionLifecycle } from "./hostSessionLifecycle.js";
21
+ import { createNonBlockingChecks } from "./lifecycleChecks.js";
19
22
  import { readAgentRunConfigurationObservation, unknownAgentRunConfiguration } from "./agentRunConfiguration.js";
20
23
  import { CodexPreSubmissionError } from "./codexAppServerRuntime.js";
21
24
  import { runCodexInteractiveHost } from "./codexInteractiveHost.js";
@@ -82,6 +85,9 @@ export async function runAgentHost(input) {
82
85
  return runCodexInteractiveHost(input.home, payload, openAgentHostControl);
83
86
  }
84
87
  let session;
88
+ const skillNotice = createSessionSkillNotice(input.home);
89
+ const upgrade = createHostUpgradeGate();
90
+ let upgradeDetaching = false;
85
91
  // This process really holds the Endpoint code its Session runs on. The lease
86
92
  // spans the Session's Turns, so installing a newer generation does not change
87
93
  // the implementation under a Session that is still using this one.
@@ -111,6 +117,16 @@ export async function runAgentHost(input) {
111
117
  });
112
118
  };
113
119
  let snapshot = hostSnapshot("idle");
120
+ const lifecycleChecks = createNonBlockingChecks([
121
+ createSessionSkillLifecycleCheck(input.home, () => (sessionPayload ?? payload).environment), {
122
+ id: "controller-lifecycle-hint",
123
+ run: (_transition, signal) => {
124
+ if (!signal.aborted)
125
+ return signalRoleMailbox(input.home, sessionPayload ?? payload);
126
+ }
127
+ }
128
+ ], error => process.stderr.write(`Session lifecycle check failed: ${errorText(error)}\n`));
129
+ const lifecycle = createHostSessionLifecycle({ after: lifecycleChecks });
114
130
  let dispatchTail = Promise.resolve();
115
131
  let promptHuman = () => { };
116
132
  let humanConsole;
@@ -119,10 +135,23 @@ export async function runAgentHost(input) {
119
135
  let modelChanging = false;
120
136
  const recentSteerAttempts = new Map();
121
137
  await replayExitOutbox(input.home);
138
+ const hostEvent = (event, fields) => ({
139
+ event: { ...event, processInstanceId: fields.processInstanceId, nativeSessionId: fields.nativeSessionId,
140
+ attemptId: fields.attemptId, nativeTurnId: fields.nativeTurnId },
141
+ fields
142
+ });
122
143
  const updateSnapshot = (next) => {
123
- const { appliedGeneratedTitle: _oldTitle, ...rest } = next;
144
+ // Validate the payload before committing the FSM; an invalid observation
145
+ // must not leave the public snapshot and lifecycle on different states.
146
+ validateSnapshot(hostSnapshot(lifecycle.state, next.fields));
147
+ const result = lifecycle.send(next.event);
148
+ if (result.status === "ignored")
149
+ return;
150
+ if (result.status === "invalid")
151
+ throw new Error(result.reason);
152
+ const { appliedGeneratedTitle: _oldTitle, ...rest } = next.fields;
124
153
  snapshot = validateSnapshot({
125
- ...rest,
154
+ ...hostSnapshot(result.state, rest),
126
155
  ...(session?.appliedGeneratedTitle === undefined ? {} : {
127
156
  appliedGeneratedTitle: session.appliedGeneratedTitle
128
157
  })
@@ -149,7 +178,7 @@ export async function runAgentHost(input) {
149
178
  await publishStructuredProviderAccepted({
150
179
  home: input.home, environment: observedPayload.environment, receipt
151
180
  });
152
- updateSnapshot(hostSnapshot("ready", {
181
+ updateSnapshot(hostEvent({ type: "accepted" }, {
153
182
  adapterId: session.adapterId,
154
183
  processInstanceId: session.processInstanceId,
155
184
  nativeSessionId: receipt.nativeSessionId,
@@ -177,7 +206,7 @@ export async function runAgentHost(input) {
177
206
  if (started.clientOwned || observedPayload === undefined)
178
207
  return;
179
208
  const observedAuthority = authority === undefined ? undefined : { ...authority };
180
- updateSnapshot(hostSnapshot("busy", {
209
+ updateSnapshot(hostEvent({ type: "native-started" }, {
181
210
  adapterId: observedPayload.providerControl?.adapterId,
182
211
  processInstanceId: session?.processInstanceId,
183
212
  nativeSessionId: started.nativeSessionId,
@@ -238,7 +267,7 @@ export async function runAgentHost(input) {
238
267
  terminal
239
268
  });
240
269
  if (session !== undefined && terminal.conversationId === session.conversationId) {
241
- updateSnapshot(hostSnapshot(session.inspect().activeNativeTurnId === undefined ? "idle" : "busy", {
270
+ updateSnapshot(hostEvent({ type: "settled", occupancy: session.inspect().activeNativeTurnId === undefined ? "idle" : "busy" }, {
242
271
  adapterId: session.adapterId,
243
272
  processInstanceId: session.processInstanceId,
244
273
  nativeSessionId: session.nativeSessionId,
@@ -281,7 +310,7 @@ export async function runAgentHost(input) {
281
310
  || activeRunAttemptId !== terminalAttemptId)
282
311
  return;
283
312
  if (session !== undefined) {
284
- updateSnapshot(hostSnapshot("settling", {
313
+ updateSnapshot(hostEvent({ type: "terminal" }, {
285
314
  adapterId: terminalPayload.providerControl.adapterId,
286
315
  processInstanceId: session.processInstanceId,
287
316
  nativeSessionId: terminal.nativeSessionId,
@@ -305,7 +334,7 @@ export async function runAgentHost(input) {
305
334
  if (session === undefined)
306
335
  return;
307
336
  const currentPayload = sessionPayload ?? terminalPayload;
308
- updateSnapshot(hostSnapshot(session.inspect().activeNativeTurnId === undefined ? "idle" : "busy", {
337
+ updateSnapshot(hostEvent({ type: "settled", occupancy: session.inspect().activeNativeTurnId === undefined ? "idle" : "busy" }, {
309
338
  adapterId: currentPayload.providerControl.adapterId,
310
339
  processInstanceId: session.processInstanceId,
311
340
  nativeSessionId: terminal.nativeSessionId,
@@ -316,7 +345,7 @@ export async function runAgentHost(input) {
316
345
  promptHuman();
317
346
  }
318
347
  catch (error) {
319
- updateSnapshot(hostSnapshot("failed", {
348
+ updateSnapshot(hostEvent({ type: "failure", outcome: "failed" }, {
320
349
  adapterId: terminalPayload.providerControl.adapterId,
321
350
  processInstanceId: session?.processInstanceId,
322
351
  nativeSessionId: terminal.nativeSessionId,
@@ -405,6 +434,7 @@ export async function runAgentHost(input) {
405
434
  throw new Error("Codex client reconnect lost its Endpoint implementation lease.");
406
435
  }
407
436
  const started = await endpointLease.resume(reconnectPayload);
437
+ skillNotice.invalidate();
408
438
  session = started.session;
409
439
  sessionPayload = currentPayload;
410
440
  started.session.events((event) => {
@@ -431,10 +461,10 @@ export async function runAgentHost(input) {
431
461
  conversationRecoverability = "recoverable";
432
462
  codexClientAttachedAt = Date.now();
433
463
  observeExit(started.session, currentPayload);
434
- const reconnectState = activeRunPayload !== undefined
435
- ? ownedTurn === undefined ? "delivery-unknown" : "ready"
464
+ const occupancy = activeRunPayload !== undefined
465
+ ? ownedTurn === undefined ? "unknown" : "owned"
436
466
  : started.session.inspect().activeNativeTurnId === undefined ? "idle" : "busy";
437
- updateSnapshot(hostSnapshot(reconnectState, {
467
+ updateSnapshot(hostEvent({ type: "connected", occupancy }, {
438
468
  adapterId: "codex",
439
469
  processInstanceId: started.session.processInstanceId,
440
470
  nativeSessionId: started.session.nativeSessionId,
@@ -468,7 +498,7 @@ export async function runAgentHost(input) {
468
498
  lastError = error;
469
499
  }
470
500
  }
471
- updateSnapshot(hostSnapshot("failed", {
501
+ updateSnapshot(hostEvent({ type: "failure", outcome: "failed" }, {
472
502
  adapterId: "codex",
473
503
  processInstanceId: disconnectedSession.processInstanceId,
474
504
  nativeSessionId: disconnectedSession.nativeSessionId,
@@ -481,6 +511,10 @@ export async function runAgentHost(input) {
481
511
  };
482
512
  const observeExit = (providerSession, launched) => {
483
513
  void providerSession.waitForExit().then((result) => enqueueSerialized(async () => {
514
+ // A drained attachment is not an ended native Session and must not
515
+ // trigger reconnect, cancellation, or session lifecycle replacement.
516
+ if (upgradeDetaching)
517
+ return;
484
518
  const ownsCurrentSession = session === providerSession;
485
519
  const currentPayload = ownsCurrentSession
486
520
  ? activeRunPayload ?? lastTerminalPayload ?? sessionPayload ?? launched : launched;
@@ -499,7 +533,7 @@ export async function runAgentHost(input) {
499
533
  }
500
534
  consecutiveCodexDisconnects += 1;
501
535
  if (consecutiveCodexDisconnects > MAX_CONSECUTIVE_CODEX_DISCONNECTS) {
502
- updateSnapshot(hostSnapshot("failed", {
536
+ updateSnapshot(hostEvent({ type: "failure", outcome: "failed" }, {
503
537
  adapterId: "codex",
504
538
  processInstanceId: result.processInstanceId,
505
539
  nativeSessionId: providerSession.nativeSessionId,
@@ -511,7 +545,7 @@ export async function runAgentHost(input) {
511
545
  }));
512
546
  return;
513
547
  }
514
- updateSnapshot(hostSnapshot("starting", {
548
+ updateSnapshot(hostEvent({ type: "disconnected" }, {
515
549
  adapterId: "codex",
516
550
  processInstanceId: result.processInstanceId,
517
551
  nativeSessionId: providerSession.nativeSessionId,
@@ -581,7 +615,7 @@ export async function runAgentHost(input) {
581
615
  }
582
616
  if (hostStopRequested || !ownsCurrentSession)
583
617
  return;
584
- updateSnapshot(hostSnapshot(failures.length === 0 ? "exited" : "failed", {
618
+ updateSnapshot(hostEvent(failures.length === 0 ? { type: "exit" } : { type: "failure", outcome: "failed" }, {
585
619
  adapterId: currentPayload.providerControl?.adapterId,
586
620
  processInstanceId: result.processInstanceId,
587
621
  nativeSessionId: providerSession.nativeSessionId,
@@ -593,7 +627,7 @@ export async function runAgentHost(input) {
593
627
  ? {}
594
628
  : { detail: "Provider process exited before the active AgentRun reached a terminal boundary." })
595
629
  }));
596
- })).catch((error) => updateSnapshot(hostSnapshot("failed", {
630
+ })).catch((error) => updateSnapshot(hostEvent({ type: "failure", outcome: "failed" }, {
597
631
  adapterId: launched.providerControl?.adapterId,
598
632
  processInstanceId: providerSession.processInstanceId,
599
633
  ...authorityFields(),
@@ -608,7 +642,7 @@ export async function runAgentHost(input) {
608
642
  if (activeRunPayload !== undefined
609
643
  || sessionPayload !== undefined
610
644
  && (structuredProviderEventDelivery(input.home, sessionPayload.environment).unpersisted ?? 0) > 0
611
- || ["starting", "ready", "settling", "delivery-unknown"].includes(snapshot.state)) {
645
+ || !lifecycle.can({ type: "launch" })) {
612
646
  throw new Error("Agent Host still owns an unsettled Provider Turn.");
613
647
  }
614
648
  const requestedAuthority = validateProviderAuthorityFence(providerControl.authority);
@@ -622,7 +656,7 @@ export async function runAgentHost(input) {
622
656
  && !sameProviderAuthorityFence(authority, requestedAuthority)) {
623
657
  throw new Error("Agent Host launch carries a stale Provider authority fence.");
624
658
  }
625
- updateSnapshot(hostSnapshot("starting", {
659
+ updateSnapshot(hostEvent({ type: "launch" }, {
626
660
  adapterId: providerControl.adapterId,
627
661
  ...(session === undefined ? {} : {
628
662
  processInstanceId: session.processInstanceId,
@@ -660,6 +694,7 @@ export async function runAgentHost(input) {
660
694
  session = started.session;
661
695
  sessionPayload = next;
662
696
  await recordProviderConnection(input.home, next, started.session);
697
+ skillNotice.invalidate();
663
698
  lastTerminalPayload = undefined;
664
699
  lastTerminal = undefined;
665
700
  directObservation = undefined;
@@ -731,10 +766,10 @@ export async function runAgentHost(input) {
731
766
  const restoredOwnedRun = providerControl.kind === "restore"
732
767
  ? providerControl.ownedTurn
733
768
  : undefined;
734
- const providerState = restoredOwnedRun !== undefined
735
- ? "ready"
769
+ const occupancy = restoredOwnedRun !== undefined
770
+ ? "owned"
736
771
  : session.inspect().activeNativeTurnId === undefined ? "idle" : "busy";
737
- updateSnapshot(hostSnapshot(providerState, {
772
+ updateSnapshot(hostEvent({ type: "connected", occupancy }, {
738
773
  adapterId: providerControl.adapterId,
739
774
  processInstanceId: session.processInstanceId,
740
775
  nativeSessionId: session.nativeSessionId,
@@ -766,7 +801,7 @@ export async function runAgentHost(input) {
766
801
  const state = error instanceof ProviderTurnBusyError
767
802
  ? "busy"
768
803
  : error instanceof ProviderTurnRejectedError ? "rejected" : "failed";
769
- updateSnapshot(hostSnapshot(state, {
804
+ updateSnapshot(hostEvent({ type: "failure", outcome: state }, {
770
805
  adapterId: providerControl.adapterId,
771
806
  processInstanceId: session?.processInstanceId,
772
807
  nativeSessionId: session?.nativeSessionId ?? providerControl.nativeSessionId,
@@ -787,6 +822,8 @@ export async function runAgentHost(input) {
787
822
  };
788
823
  const enqueueDispatch = (next) => (enqueueSerialized(() => dispatch(next)));
789
824
  const submitRun = async (request, operation) => {
825
+ if (upgrade.fenced)
826
+ throw new ProviderTurnBusyError("Host upgrade admission is fenced.", request.run.attemptId);
790
827
  if (session === undefined || sessionPayload === undefined) {
791
828
  throw new Error("Agent Host has no live Provider Conversation.");
792
829
  }
@@ -797,7 +834,7 @@ export async function runAgentHost(input) {
797
834
  || !sameProviderAuthorityFence(authority, request.authority)) {
798
835
  throw new Error("Agent Host rejected a stale Provider writer fence.");
799
836
  }
800
- if (activeRunPayload !== undefined || snapshot.state === "settling"
837
+ if (activeRunPayload !== undefined || !lifecycle.can({ type: "submit" })
801
838
  || (structuredProviderEventDelivery(input.home, sessionPayload.environment).unpersisted ?? 0) > 0
802
839
  || session.inspect().activeNativeTurnId !== undefined) {
803
840
  operation.failure = providerDeliveryFailure({
@@ -827,7 +864,7 @@ export async function runAgentHost(input) {
827
864
  };
828
865
  activeRunAttemptId = request.run.attemptId;
829
866
  activeNativeTurnId = undefined;
830
- updateSnapshot(hostSnapshot("starting", {
867
+ updateSnapshot(hostEvent({ type: "submit" }, {
831
868
  adapterId: session.adapterId,
832
869
  processInstanceId: session.processInstanceId,
833
870
  nativeSessionId: session.nativeSessionId,
@@ -850,6 +887,7 @@ export async function runAgentHost(input) {
850
887
  && typeof retry.text === "string")
851
888
  recoveryInput = {
852
889
  text: retry.text,
890
+ ...(typeof retry.context === "string" ? { context: retry.context } : {}),
853
891
  ...(typeof retry.expectedFailedNativeTurnId === "string"
854
892
  ? { expectedFailedNativeTurnId: retry.expectedFailedNativeTurnId } : {})
855
893
  };
@@ -868,8 +906,8 @@ export async function runAgentHost(input) {
868
906
  const registrationUnknown = !registrationSettled
869
907
  && (error instanceof ControllerAcknowledgementUnknownError
870
908
  || error instanceof ProviderDeliveryUnknownError);
871
- updateSnapshot(hostSnapshot(registrationUnknown ? "delivery-unknown"
872
- : error instanceof ProviderTurnBusyError ? "busy" : "failed", {
909
+ updateSnapshot(hostEvent({ type: "failure", outcome: registrationUnknown ? "delivery-unknown"
910
+ : error instanceof ProviderTurnBusyError ? "busy" : "failed" }, {
873
911
  adapterId: session.adapterId,
874
912
  processInstanceId: session.processInstanceId,
875
913
  nativeSessionId: session.nativeSessionId,
@@ -909,9 +947,20 @@ export async function runAgentHost(input) {
909
947
  try {
910
948
  // beginDurableProviderTurn validated current execution-environment,
911
949
  // authority and Session identity before this native write.
950
+ const notice = skillNotice.prepare(session.adapterId === "codex" ? request.run.boundedText : recoveryInput?.text ?? request.run.boundedText, activeRunPayload.environment, session.nativeSessionId, { contextEpoch: session.contextEpoch });
912
951
  const receipt = endpointReceipt(await session.submit({
913
952
  ...request.run,
914
- boundedText: withSessionContextPointer(recoveryInput?.text ?? request.run.boundedText, activeRunPayload.environment),
953
+ boundedText: session.adapterId === "codex" ? notice.inputText : notice.text,
954
+ ...(session.adapterId !== "codex" || notice.context === undefined && recoveryInput?.context === undefined ? {} : {
955
+ additionalContext: {
956
+ ...(notice.context === undefined ? {} : {
957
+ "yui.session-skills": { kind: "application", value: notice.context }
958
+ }),
959
+ ...(recoveryInput?.context === undefined ? {} : {
960
+ "yui.recovery": { kind: "application", value: recoveryInput.context }
961
+ })
962
+ }
963
+ }),
915
964
  ...(recoveryInput === undefined ? {} : { requireQuiescent: true }),
916
965
  ...(recoveryInput?.expectedFailedNativeTurnId === undefined ? {} : {
917
966
  expectedFailedNativeTurnId: recoveryInput.expectedFailedNativeTurnId
@@ -920,6 +969,8 @@ export async function runAgentHost(input) {
920
969
  }), request.run.attemptId);
921
970
  transportAccepted = true;
922
971
  providerAccepted = receipt.acceptance === "provider";
972
+ if (providerAccepted)
973
+ notice.commit();
923
974
  activeNativeTurnId = receipt.nativeTurnId;
924
975
  try {
925
976
  await publishStructuredProviderAccepted({
@@ -936,7 +987,7 @@ export async function runAgentHost(input) {
936
987
  await resolveProviderTurnSubmission(input.home, durableRun, unknown);
937
988
  throw unknown;
938
989
  }
939
- updateSnapshot(hostSnapshot("ready", {
990
+ updateSnapshot(hostEvent({ type: "accepted" }, {
940
991
  adapterId: session.adapterId,
941
992
  processInstanceId: session.processInstanceId,
942
993
  nativeSessionId: receipt.nativeSessionId,
@@ -967,7 +1018,7 @@ export async function runAgentHost(input) {
967
1018
  : error instanceof ProviderTurnBusyError
968
1019
  ? "busy"
969
1020
  : error instanceof ProviderTurnRejectedError ? "rejected" : "failed";
970
- updateSnapshot(hostSnapshot(state, {
1021
+ updateSnapshot(hostEvent({ type: "failure", outcome: state }, {
971
1022
  adapterId: session.adapterId,
972
1023
  processInstanceId: session.processInstanceId,
973
1024
  nativeSessionId: session.nativeSessionId,
@@ -1029,14 +1080,20 @@ export async function runAgentHost(input) {
1029
1080
  }
1030
1081
  try {
1031
1082
  await assertHostExecutionEnvironment(input.home, sessionPayload, session.nativeSessionId);
1083
+ const notice = skillNotice.prepare(request.run.boundedText, sessionPayload.environment, session.nativeSessionId, { contextEpoch: session.contextEpoch });
1032
1084
  const receipt = endpointReceipt(await session.steer({
1033
1085
  ...request.run,
1034
- boundedText: withSessionContextPointer(request.run.boundedText, sessionPayload.environment),
1086
+ boundedText: session.adapterId === "codex" ? notice.inputText : notice.text,
1087
+ ...(session.adapterId !== "codex" || notice.context === undefined ? {} : {
1088
+ additionalContext: { "yui.session-skills": { kind: "application", value: notice.context } }
1089
+ }),
1035
1090
  inputRef: request.run.attemptId
1036
1091
  }), request.run.attemptId);
1037
1092
  if (receipt.nativeTurnId !== request.nativeTurnId) {
1038
1093
  throw new ProviderDeliveryUnknownError("Provider accepted steer against an unexpected native Turn.", request.run.attemptId);
1039
1094
  }
1095
+ if (receipt.acceptance === "provider")
1096
+ notice.commit();
1040
1097
  }
1041
1098
  catch (error) {
1042
1099
  const unknown = error instanceof ProviderDeliveryUnknownError;
@@ -1058,7 +1115,7 @@ export async function runAgentHost(input) {
1058
1115
  if (activeRunPayload !== undefined || session.inspect().activeNativeTurnId !== undefined
1059
1116
  || sessionPayload !== undefined
1060
1117
  && (structuredProviderEventDelivery(input.home, sessionPayload.environment).unpersisted ?? 0) > 0
1061
- || ["starting", "ready", "settling", "delivery-unknown"].includes(snapshot.state)) {
1118
+ || !lifecycle.can({ type: "authority-changed" })) {
1062
1119
  throw new Error("Agent Host authority cannot transfer while a AgentRun is unsettled.");
1063
1120
  }
1064
1121
  const next = validateProviderAuthorityFence(request.authority);
@@ -1070,7 +1127,7 @@ export async function runAgentHost(input) {
1070
1127
  }
1071
1128
  }
1072
1129
  authority = next;
1073
- updateSnapshot(hostSnapshot("idle", {
1130
+ updateSnapshot(hostEvent({ type: "authority-changed" }, {
1074
1131
  adapterId: session.adapterId,
1075
1132
  processInstanceId: session.processInstanceId,
1076
1133
  nativeSessionId: session.nativeSessionId,
@@ -1129,6 +1186,64 @@ export async function runAgentHost(input) {
1129
1186
  return { turn: { id: accepted.nativeTurnId, items: [], status: "inProgress", error: null } };
1130
1187
  };
1131
1188
  const handleControl = async (request) => {
1189
+ if (request.type === "prepare-upgrade" || request.type === "finish-upgrade") {
1190
+ upgrade.request(request, {
1191
+ nativeSessionId: snapshot.nativeSessionId ?? "",
1192
+ processInstanceId: snapshot.processInstanceId ?? "",
1193
+ hostGeneration: AGENT_HOST_IMPLEMENTATION.generation
1194
+ });
1195
+ if (request.type === "prepare-upgrade") {
1196
+ // Fence synchronously, then join the same lane as submission and
1197
+ // terminal publication. No check-then-submit gap on Host-owned paths.
1198
+ await enqueueSerialized(async () => {
1199
+ upgrade.request(request, {
1200
+ nativeSessionId: snapshot.nativeSessionId ?? "", processInstanceId: snapshot.processInstanceId ?? "",
1201
+ hostGeneration: AGENT_HOST_IMPLEMENTATION.generation
1202
+ });
1203
+ await upgrade.drain({
1204
+ busy: session === undefined || activeRunPayload !== undefined || activeNativeTurnId !== undefined
1205
+ || snapshot.state !== "idle" || nativeTui !== undefined || nativeOpening !== undefined
1206
+ || modelChanging || session.inspect().activeNativeTurnId !== undefined
1207
+ || session.inspect().submissions.some(s => s.disposition.status === "unknown"
1208
+ || s.disposition.status === "pending" && s.disposition.reason === "submitting"),
1209
+ events: structuredProviderEventDelivery(input.home, sessionPayload?.environment ?? payload.environment, snapshot.nativeSessionId),
1210
+ checkQuiescent: async () => {
1211
+ if (authority?.owner !== "controller" || conversationRecoverability !== "recoverable") {
1212
+ throw new Error("Upgrade requires an exactly resumable controller-owned native Session.");
1213
+ }
1214
+ return await session.checkQuiescent()
1215
+ && session.inspect().activeNativeTurnId === undefined;
1216
+ },
1217
+ release: async () => {
1218
+ const events = structuredProviderEventDelivery(input.home, sessionPayload.environment, snapshot.nativeSessionId);
1219
+ if (events.pending !== 0 || events.unpersisted !== 0 || events.pendingTerminals !== 0 || events.failure
1220
+ || session.inspect().activeNativeTurnId !== undefined
1221
+ || session.nativeSessionId !== request.nativeSessionId
1222
+ || session.processInstanceId !== request.processInstanceId) {
1223
+ throw new Error("Native execution or event delivery changed before Endpoint release.");
1224
+ }
1225
+ upgradeDetaching = true;
1226
+ session.detach();
1227
+ await endpointLease?.release();
1228
+ const drain = await endpointOwner.stop(endpointAdapterId, ENDPOINT_DRAIN_TIMEOUT_MS);
1229
+ if (!drain.quiescent)
1230
+ throw new Error("Endpoint release did not prove owned client/implementation drain.");
1231
+ }
1232
+ });
1233
+ });
1234
+ }
1235
+ else {
1236
+ if (upgrade.observation?.state !== "drained")
1237
+ throw new Error("Host has not drained.");
1238
+ // The Controller persists the drain receipt before this request.
1239
+ // Delay close until this connection has had a chance to receive its ACK.
1240
+ setImmediate(() => { hostStopRequested = true; stopResolve(); });
1241
+ }
1242
+ return controlResult("accepted", { ...snapshot, hostUpgrade: upgrade.observation });
1243
+ }
1244
+ if (upgrade.fenced && ["launch", "submit-turn", "steer-turn", "set-authority", "native-open", "native-model"].includes(request.type)) {
1245
+ return controlResult("busy", { ...snapshot, hostUpgrade: upgrade.observation });
1246
+ }
1132
1247
  if (request.type === "native-model") {
1133
1248
  return enqueueSerialized(async () => {
1134
1249
  const current = session;
@@ -1217,7 +1332,7 @@ export async function runAgentHost(input) {
1217
1332
  const persisted = await replayStructuredProviderPersistence(input.home, sessionPayload.environment);
1218
1333
  if (persisted.length > 0 && activeRunPayload === undefined) {
1219
1334
  const active = session.inspect().activeNativeTurnId;
1220
- updateSnapshot(hostSnapshot(active === undefined ? "idle" : "busy", {
1335
+ updateSnapshot(hostEvent({ type: "settled", occupancy: active === undefined ? "idle" : "busy" }, {
1221
1336
  adapterId: session.adapterId, processInstanceId: session.processInstanceId,
1222
1337
  nativeSessionId: session.nativeSessionId, conversationId: session.conversationId,
1223
1338
  nativeTurnId: active, ...authorityFields()
@@ -1261,7 +1376,7 @@ export async function runAgentHost(input) {
1261
1376
  activeRunPayload = undefined;
1262
1377
  activeRunAttemptId = undefined;
1263
1378
  activeNativeTurnId = undefined;
1264
- updateSnapshot(hostSnapshot(session.inspect().activeNativeTurnId === undefined ? "idle" : "busy", {
1379
+ updateSnapshot(hostEvent({ type: "settled", occupancy: session.inspect().activeNativeTurnId === undefined ? "idle" : "busy" }, {
1265
1380
  adapterId: session.adapterId, processInstanceId: session.processInstanceId,
1266
1381
  nativeSessionId: session.nativeSessionId, conversationId: session.conversationId,
1267
1382
  ...authorityFields()
@@ -1284,9 +1399,10 @@ export async function runAgentHost(input) {
1284
1399
  return requestBytes <= 8000;
1285
1400
  }).slice(0, 8);
1286
1401
  return controlResult("status", session === undefined
1287
- ? snapshot
1402
+ ? { ...snapshot, hostUpgrade: upgrade.observation }
1288
1403
  : validateSnapshot({
1289
1404
  ...snapshot,
1405
+ hostUpgrade: upgrade.observation,
1290
1406
  nativeTui: !!nativeTui,
1291
1407
  ...(nativeRequests === undefined ? {} : { nativeRequests,
1292
1408
  nativeRequestsOmitted: nativeRequests.length !== session.nativeAccess.requests.length }),
@@ -1394,7 +1510,7 @@ export async function runAgentHost(input) {
1394
1510
  throw new Error("Unsupported Host request.");
1395
1511
  const redeemed = await redeem(input.home, request.ticket);
1396
1512
  if (activeRunPayload !== undefined
1397
- || ["starting", "ready", "settling", "delivery-unknown"].includes(snapshot.state)) {
1513
+ || !lifecycle.can({ type: "launch" })) {
1398
1514
  if (redeemed.providerControl?.mode === "resume"
1399
1515
  && redeemed.providerControl.nativeSessionId === snapshot.nativeSessionId) {
1400
1516
  return controlResult("accepted", snapshot);
@@ -1481,6 +1597,7 @@ export async function runAgentHost(input) {
1481
1597
  return 0;
1482
1598
  }
1483
1599
  finally {
1600
+ lifecycleChecks.close();
1484
1601
  for (const [signal, handler] of handlers)
1485
1602
  process.removeListener(signal, handler);
1486
1603
  if (forceKillTimer !== undefined)
@@ -1568,6 +1685,9 @@ export async function sendAgentHostCancelControl(input) {
1568
1685
  export async function sendAgentHostNativeControl(input) {
1569
1686
  return sendAgentHostControl(input);
1570
1687
  }
1688
+ export async function sendAgentHostUpgradeControl(input) {
1689
+ return sendAgentHostControl(input);
1690
+ }
1571
1691
  export async function inspectAgentHost(input) {
1572
1692
  const result = await sendAgentHostControl({
1573
1693
  ...input,
@@ -1749,7 +1869,8 @@ export async function openAgentHostControl(home, payload, snapshot, dispatch) {
1749
1869
  hostImplementation: AGENT_HOST_IMPLEMENTATION,
1750
1870
  controlCapabilities: payload.providerControl === undefined ? ["status"] : [
1751
1871
  "status", "launch", "submit-turn", "steer-turn", "cancel", "set-authority",
1752
- "native-open", "native-respond", "native-model", "reconcile-turn"
1872
+ "native-open", "native-respond", "native-model", "reconcile-turn",
1873
+ ...(payload.providerControl.adapterId === "codex" ? ["prepare-upgrade", "finish-upgrade"] : [])
1753
1874
  ],
1754
1875
  ...(!["task", "global"].includes(payload.environment.YUI_SESSION_SCOPE ?? "") ? {} : {
1755
1876
  eventDelivery: structuredProviderEventDelivery(home, payload.environment, result.snapshot.nativeSessionId)
@@ -1900,6 +2021,12 @@ function validateControl(control) {
1900
2021
  }
1901
2022
  if (control.type === "status")
1902
2023
  return Object.freeze({ ...control });
2024
+ if (control.type === "prepare-upgrade" || control.type === "finish-upgrade") {
2025
+ for (const key of ["requestId", "nativeSessionId", "processInstanceId", "hostGeneration"]) {
2026
+ validateIdentity(control[key], key);
2027
+ }
2028
+ return Object.freeze({ ...control });
2029
+ }
1903
2030
  if (control.type === "reconcile-turn") {
1904
2031
  validateIdentity(control.nativeSessionId, "native Session id");
1905
2032
  validateIdentity(control.attemptId, "Provider input attempt id");
@@ -2016,6 +2143,13 @@ function validateSnapshot(snapshot) {
2016
2143
  || value.length === 0 || value.length > 64 || value.includes("\0")))) {
2017
2144
  throw new Error("Agent Host control capability advertisement is invalid.");
2018
2145
  }
2146
+ if (snapshot.hostUpgrade !== undefined) {
2147
+ for (const key of ["requestId", "nativeSessionId", "processInstanceId", "hostGeneration"]) {
2148
+ validateIdentity(snapshot.hostUpgrade[key], key);
2149
+ }
2150
+ if (!["waiting", "drained", "failed"].includes(snapshot.hostUpgrade.state))
2151
+ throw new Error("Invalid Host drain state.");
2152
+ }
2019
2153
  const authorityFields = [
2020
2154
  snapshot.authorityEpoch,
2021
2155
  snapshot.authorityOwner,
@@ -2115,11 +2249,11 @@ function signalRoleMailbox(home, payload) {
2115
2249
  const taskId = payload.environment.YUI_TASK_ID;
2116
2250
  const roleName = payload.environment.YUI_ROLE;
2117
2251
  if (roleName === undefined)
2118
- return;
2252
+ return Promise.resolve();
2119
2253
  const key = taskId === undefined
2120
2254
  ? `global-role:${encodeURIComponent(roleName)}`
2121
2255
  : `role:${encodeURIComponent(taskId)}/${encodeURIComponent(roleName)}`;
2122
- void callController(home, "scheduler.signal", { key }).catch(() => {
2256
+ return callController(home, "scheduler.signal", { key }).then(() => undefined).catch(() => {
2123
2257
  // This is a low-latency hint. Durable mailbox state and periodic
2124
2258
  // reconciliation remain the recovery path across a Controller handover.
2125
2259
  });
@@ -8,7 +8,9 @@ const release = detectRunningRelease(fileURLToPath(import.meta.url));
8
8
  const modules = [
9
9
  "agentHost.js", "agentHostProtocol.js", "agentHostImplementation.js",
10
10
  "codexInteractiveHost.js", "../controller/structuredProviderObservation.js",
11
- "../controller/runtimeEventInbox.js"
11
+ "../controller/runtimeEventInbox.js", "../context/sessionSkillNotice.js",
12
+ "../context/sessionBootstrapManifest.js", "../context/currentSkills.js", "contextRecovery.js", "hostUpgradeGate.js",
13
+ "hostSessionLifecycle.js", "lifecycleChecks.js"
12
14
  ];
13
15
  export const AGENT_HOST_IMPLEMENTATION = Object.freeze({
14
16
  id: "yui.agent-host",