@zq-silk/yui 0.8.1 → 0.8.2

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 (38) hide show
  1. package/ARCHITECTURE.md +27 -28
  2. package/README.md +35 -46
  3. package/dist/cli/commandCatalog.js +49 -14
  4. package/dist/cli/interactionPolicy.js +4 -10
  5. package/dist/cli/invocationRouter.js +2 -1
  6. package/dist/cli.js +73 -21
  7. package/dist/commands/taskCommands.js +108 -53
  8. package/dist/controller/fileSchedulerStoreAdapter.js +389 -70
  9. package/dist/controller/resourceInventory.js +9 -5
  10. package/dist/controller/runtime.js +80 -7
  11. package/dist/controller/runtimeLaunchCoordinator.js +18 -78
  12. package/dist/controller/structuredProviderObservation.js +273 -0
  13. package/dist/executor/agentAdapter.js +40 -0
  14. package/dist/executor/agentExecutor.js +31 -7
  15. package/dist/executor/executorRegistry.js +11 -49
  16. package/dist/executor/fileRoleLaunchPlanner.js +115 -37
  17. package/dist/lifecycle/canonicalLifecycleEvent.js +5 -2
  18. package/dist/run/agentRun.js +2 -2
  19. package/dist/runtime/agentHost.js +767 -158
  20. package/dist/runtime/builtinAgentDrivers.js +1 -5
  21. package/dist/runtime/codexAppServerRuntime.js +67 -60
  22. package/dist/runtime/exactControlPlane.js +7 -2
  23. package/dist/runtime/index.js +6 -2
  24. package/dist/runtime/launchBroker.js +30 -8
  25. package/dist/runtime/providerAuthorityFence.js +24 -0
  26. package/dist/runtime/providerControl.js +63 -0
  27. package/dist/runtime/providerRecoveryDecision.js +55 -0
  28. package/dist/runtime/providerRuntimeIdentity.js +269 -19
  29. package/dist/runtime/runtimeBinding.js +20 -11
  30. package/dist/runtime/structuredProviderHost.js +476 -0
  31. package/dist/runtime/tmuxAdapters.js +143 -42
  32. package/dist/scheduler/activeRoleRunDelivery.js +206 -120
  33. package/dist/scheduler/leaderWakeupProcessor.js +141 -16
  34. package/dist/storage/migration/productionRegistry.js +111 -0
  35. package/dist/storage/taskStore.js +1 -1
  36. package/dist/tmux/tmuxManager.js +1 -1
  37. package/i18n/README.zh-CN.md +11 -8
  38. package/package.json +1 -1
@@ -17,9 +17,9 @@ import { AsyncTaskStoreClient, resolveStoreWorkerEnabledForHome } from "../stora
17
17
  import { FileTaskWorkspacePreparer } from "../repository/taskWorkspacePreparer.js";
18
18
  import { NodeCommandExecutor } from "../tmux/commandExecutor.js";
19
19
  import { TmuxManager, yuiTmuxServerName } from "../tmux/tmuxManager.js";
20
- import { FileTaskRuntimeIsolation, TmuxPromptPushAdapter, TmuxSessionHost, ProviderContinuationReconciliationService } from "../runtime/index.js";
20
+ import { AgentHostPromptPushAdapter, FileTaskRuntimeIsolation, TmuxSessionHost, ProviderContinuationReconciliationService } from "../runtime/index.js";
21
21
  import { startFileTaskController } from "./controller.js";
22
- import { FileSchedulerStoreAdapter } from "./fileSchedulerStoreAdapter.js";
22
+ import { AgentHostProviderTurnFenceError, FileSchedulerStoreAdapter } from "./fileSchedulerStoreAdapter.js";
23
23
  import { openSchedulerTelemetry } from "../telemetry/telemetryWiring.js";
24
24
  import { createFileArtifactPort, createLinuxProcessPort, DurableJobSupervisor } from "./jobSupervisor.js";
25
25
  import { createDurableJobControl } from "./jobControl.js";
@@ -38,7 +38,7 @@ import { launchBrokerForHome } from "../runtime/launchBroker.js";
38
38
  import { classifyRuntimeProcessExit, validateRuntimeProcessExitObservation } from "../runtime/processExitObservation.js";
39
39
  import { appendGlobalProcessExitObservation } from "../runtime/globalProcessExitStore.js";
40
40
  import { builtinAgentDriverRegistry } from "../runtime/builtinAgentDrivers.js";
41
- import { runtimeObservationFromTaskEvent } from "../runtime/runtimeObservation.js";
41
+ import { createRuntimeObservation, runtimeObservationFromTaskEvent } from "../runtime/runtimeObservation.js";
42
42
  import { createTaskEvent } from "../event/taskEvent.js";
43
43
  /** Refreshes only the exact Task runtime generation folded by the event transaction. */
44
44
  export function refreshAppliedTaskRuntimeDescriptor(store, planner, input) {
@@ -198,7 +198,7 @@ export async function startFileTaskControllerRuntime(home, options = {}) {
198
198
  }
199
199
  });
200
200
  const promptPush = options.promptPush
201
- ?? new TmuxPromptPushAdapter(tmux, agentProcessReadinessProbe);
201
+ ?? new AgentHostPromptPushAdapter(home);
202
202
  const runtimeIsolation = options.runtimeIsolation
203
203
  ?? new FileTaskRuntimeIsolation({
204
204
  // A sibling of the exact control Home keeps provider data/cache/tmp out
@@ -283,9 +283,6 @@ export async function startFileTaskControllerRuntime(home, options = {}) {
283
283
  sessionHost,
284
284
  promptPush,
285
285
  launchCoordinator,
286
- ...(options.providerInputRouting === undefined
287
- ? {}
288
- : { providerInputRouting: options.providerInputRouting }),
289
286
  roleResourceInventory: async (panes, inputs) => {
290
287
  const inventory = await scanInventory(panes);
291
288
  return inventory.resources.flatMap((resource) => {
@@ -550,6 +547,55 @@ export function createRuntimeLifecycleDispatcher(store, schedulerStore, sessionH
550
547
  });
551
548
  const lifecycleTails = new Map();
552
549
  return async (method, params) => {
550
+ if (method === "runtime.observation-apply") {
551
+ return {
552
+ outcome: schedulerStore.observeRuntimeObservation(createRuntimeObservation(params), new Date())
553
+ };
554
+ }
555
+ if (method === "runtime.provider-turn-begin") {
556
+ const value = providerTurnControlParams(params);
557
+ try {
558
+ schedulerStore.beginAgentHostProviderTurn({
559
+ taskId: value.taskId,
560
+ roleName: value.roleName,
561
+ runId: value.runId,
562
+ agentId: value.agentId,
563
+ launchId: value.launchId,
564
+ nativeSessionId: value.nativeSessionId,
565
+ attemptId: value.attemptId,
566
+ authorityEpoch: value.authorityEpoch,
567
+ authorityOwner: value.authorityOwner,
568
+ holderId: value.holderId,
569
+ now: value.now
570
+ });
571
+ }
572
+ catch (error) {
573
+ if (error instanceof AgentHostProviderTurnFenceError) {
574
+ throw applicationError("INVALID_PARAMS", error.message);
575
+ }
576
+ throw error;
577
+ }
578
+ return { recorded: true };
579
+ }
580
+ if (method === "runtime.provider-turn-submission-resolve") {
581
+ const value = providerTurnControlParams(params);
582
+ const status = params.status;
583
+ const reason = params.reason;
584
+ if ((status !== "rejected" && status !== "delivery-unknown")
585
+ || typeof reason !== "string" || reason.trim().length === 0) {
586
+ throw applicationError("INVALID_PARAMS", "Provider Turn resolution is invalid.");
587
+ }
588
+ schedulerStore.resolveAgentHostProviderTurnSubmission({
589
+ taskId: value.taskId,
590
+ roleName: value.roleName,
591
+ runId: value.runId,
592
+ attemptId: value.attemptId,
593
+ status,
594
+ reason,
595
+ now: value.now
596
+ });
597
+ return { recorded: true };
598
+ }
553
599
  if (method === "runtime.process-exit-observe") {
554
600
  const observation = validateRuntimeProcessExitObservation(params);
555
601
  const run = observation.taskId === undefined || observation.runId === undefined
@@ -1032,6 +1078,33 @@ function requiredParam(value) {
1032
1078
  }
1033
1079
  return value;
1034
1080
  }
1081
+ function providerTurnControlParams(params) {
1082
+ if (typeof params !== "object" || params === null || Array.isArray(params)) {
1083
+ throw applicationError("INVALID_PARAMS", "Provider Turn control params are invalid.");
1084
+ }
1085
+ const value = params;
1086
+ const authorityEpoch = value.authorityEpoch;
1087
+ const authorityOwner = value.authorityOwner;
1088
+ const observedAt = requiredParam(value.observedAt);
1089
+ if (!Number.isSafeInteger(authorityEpoch) || authorityEpoch < 1
1090
+ || (authorityOwner !== "controller" && authorityOwner !== "human")
1091
+ || !Number.isFinite(Date.parse(observedAt))) {
1092
+ throw applicationError("INVALID_PARAMS", "Provider Turn control fence is invalid.");
1093
+ }
1094
+ return {
1095
+ taskId: requiredParam(value.taskId),
1096
+ roleName: requiredParam(value.roleName),
1097
+ runId: requiredParam(value.runId),
1098
+ agentId: requiredParam(value.agentId),
1099
+ launchId: requiredParam(value.launchId),
1100
+ nativeSessionId: requiredParam(value.nativeSessionId),
1101
+ attemptId: requiredParam(value.attemptId),
1102
+ authorityEpoch: authorityEpoch,
1103
+ authorityOwner,
1104
+ holderId: requiredParam(value.holderId),
1105
+ now: new Date(observedAt)
1106
+ };
1107
+ }
1035
1108
  function abortableDelay(milliseconds, signal) {
1036
1109
  return new Promise((resolve, reject) => {
1037
1110
  const timer = setTimeout(() => {
@@ -8,12 +8,6 @@ class RuntimeBindingContractError extends Error {
8
8
  this.name = "RuntimeBindingContractError";
9
9
  }
10
10
  }
11
- class LaunchSubmittedHostBusyError extends RuntimeBindingContractError {
12
- constructor(message) {
13
- super(message);
14
- this.name = "LaunchSubmittedHostBusyError";
15
- }
16
- }
17
11
  class RuntimeLaunchStateChangedError extends Error {
18
12
  constructor(message, options) {
19
13
  super(message, options);
@@ -103,12 +97,6 @@ export class RuntimeLaunchCoordinator {
103
97
  ...(request.runId === undefined ? {} : { runId: request.runId })
104
98
  }, assertLaunchCurrent, this.#now());
105
99
  let reusedConfirmedRunningHost = false;
106
- const launchCarriesExactRunPrompt = carriesExactRunPrompt(request);
107
- // Preserve the existing Codex recovery path; managed Claude is the one
108
- // finite-process protocol that must never fall back to tmux key delivery
109
- // when a newly-reserved Run encounters an older live pane.
110
- let launchPromptAcknowledgementRequired = request.adapterId === "claude"
111
- && launchCarriesExactRunPrompt;
112
100
  if (reservation.status === "existing") {
113
101
  if (!reservation.launchId.startsWith(generationPrefix)) {
114
102
  this.#requireCleanup(request.owner);
@@ -167,12 +155,6 @@ export class RuntimeLaunchCoordinator {
167
155
  throw new Error(`Runtime launch reservation belongs to another Run whose host is still running: ${reservation.runId}.`);
168
156
  }
169
157
  reusedConfirmedRunningHost = true;
170
- // A fresh Codex generation carries the prompt in its launch argv. A
171
- // managed Claude generation carries it through stream-json stdin for
172
- // both new and native-resume modes. Only recovery of the exact same
173
- // reserved Run may bridge a lost in-memory launch acknowledgement.
174
- launchPromptAcknowledgementRequired = sameRunReservation
175
- && launchCarriesExactRunPrompt;
176
158
  runtimeIsolation = this.#preflightRuntimeIsolation(request, reservation.launchId, reservation.launchId.slice(generationPrefix.length), true);
177
159
  assertLaunchCurrent();
178
160
  }
@@ -229,11 +211,8 @@ export class RuntimeLaunchCoordinator {
229
211
  : { environment: request.environment }),
230
212
  nativeSessionId: requireText(request.nativeSessionId, "Native session id")
231
213
  }, beforeHostStart === undefined ? undefined : observePreflight);
232
- binding = requireMatchingRuntimeBinding(rawBinding, request, launchId, launchPromptAcknowledgementRequired, reusedConfirmedRunningHost);
233
- if (!preflightObserved
234
- && !(binding.hostCreated === false
235
- && request.adapterId === "claude"
236
- && launchCarriesExactRunPrompt)) {
214
+ binding = requireMatchingRuntimeBinding(rawBinding, request, launchId);
215
+ if (!preflightObserved) {
237
216
  throw new Error("Runtime session host did not expose a pre-host-start launch fence.");
238
217
  }
239
218
  }
@@ -244,8 +223,7 @@ export class RuntimeLaunchCoordinator {
244
223
  // terminalize that reservation.
245
224
  throw new RuntimeLaunchError(true, launchId, error.message, error.reason);
246
225
  }
247
- if ((error instanceof LaunchSubmittedHostBusyError
248
- || error instanceof RuntimeHostContentionError)
226
+ if (error instanceof RuntimeHostContentionError
249
227
  && !reusedConfirmedRunningHost) {
250
228
  let exactCleanupAttempted = false;
251
229
  let completed = false;
@@ -266,12 +244,6 @@ export class RuntimeLaunchCoordinator {
266
244
  this.#requireCleanup(request.owner);
267
245
  throw new Error("Busy managed runtime launch reservation changed during retry release.");
268
246
  }
269
- if (error instanceof LaunchSubmittedHostBusyError) {
270
- // A terminal Run no longer owns its finite provider process. Move
271
- // that exact Role owner through the durable cleanup lane before a
272
- // later retry creates the successor generation.
273
- this.#requireCleanup(request.owner);
274
- }
275
247
  throw new RuntimeLaunchError(true, launchId, error.message, error instanceof RuntimeHostContentionError
276
248
  ? error.reason
277
249
  : "previous-process");
@@ -291,7 +263,6 @@ export class RuntimeLaunchCoordinator {
291
263
  }
292
264
  await this.#compensateStartedHost(request.owner, binding, launchId, runtimeIsolation, new Error(`Runtime host was recreated while recovering an existing generation: ${request.owner.roleName}.`));
293
265
  }
294
- let reservationConfirmation;
295
266
  try {
296
267
  assertLaunchCurrent();
297
268
  if (persistence === "immediate" && binding.nativeSessionId !== undefined) {
@@ -308,7 +279,7 @@ export class RuntimeLaunchCoordinator {
308
279
  // Deferred scheduler persistence records a known native identity while
309
280
  // retaining the reservation until exact Run delivery. Fresh Codex has
310
281
  // no identity yet and keeps it until its matching generation Hook.
311
- reservationConfirmation = this.reservations.confirmRuntimeLaunchReservation({
282
+ this.reservations.confirmRuntimeLaunchReservation({
312
283
  owner: request.owner,
313
284
  launchId
314
285
  }, assertLaunchCurrent);
@@ -317,18 +288,6 @@ export class RuntimeLaunchCoordinator {
317
288
  catch (error) {
318
289
  await this.#compensateStartedHost(request.owner, binding, launchId, runtimeIsolation, error);
319
290
  }
320
- if (launchCarriesExactRunPrompt
321
- && binding.hostCreated !== false
322
- && reservationConfirmation !== "provider-bound"
323
- && binding.initialPromptRunId !== request.runId) {
324
- // A launch-carried prompt may return before its matching lifecycle Hook,
325
- // but only an exact Run marker can bridge that asynchronous interval.
326
- // This is transport evidence, never Provider acceptance; the
327
- // reservation remains fenced until the matching Hook binds the native
328
- // Session.
329
- this.#requireCleanup(request.owner);
330
- throw new RuntimeBindingContractError(`Session host cannot acknowledge the exact launch-carried prompt: ${request.owner.roleName}.`);
331
- }
332
291
  return binding;
333
292
  }
334
293
  async #settleFailedStart(request, launchId, reusedConfirmedRunningHost, runtimeIsolation) {
@@ -461,12 +420,12 @@ function validateRuntimeLaunchPreflight(preflight, request, launchId) {
461
420
  || !effectiveLaunchSnapshotsCompatible(preflight.effective, request.effective)
462
421
  || (request.mode === "resume"
463
422
  && preflight.nativeSessionId !== request.nativeSessionId)
464
- || (preflight.initialPromptRunId !== undefined
465
- && preflight.initialPromptRunId !== request.runId)) {
423
+ || (preflight.initialTurnRunId !== undefined
424
+ && preflight.initialTurnRunId !== request.runId)) {
466
425
  throw new Error(`Session host pre-start launch fence does not match the requested runtime: ${request.owner.roleName}.`);
467
426
  }
468
427
  }
469
- function requireMatchingRuntimeBinding(raw, request, launchId, launchPromptAcknowledgementRequired, launchPromptUncertaintyAllowed) {
428
+ function requireMatchingRuntimeBinding(raw, request, launchId) {
470
429
  let binding;
471
430
  try {
472
431
  binding = createRuntimeBinding(raw);
@@ -479,9 +438,17 @@ function requireMatchingRuntimeBinding(raw, request, launchId, launchPromptAckno
479
438
  && (binding.owner.scope === "global"
480
439
  || (request.owner.scope === "task"
481
440
  && binding.owner.taskId === request.owner.taskId));
482
- if (binding.initialPromptRunId !== undefined
483
- && binding.initialPromptRunId !== request.runId) {
484
- throw new RuntimeBindingContractError(`Session host returned a launch-carried prompt for another Run: ${request.owner.roleName}.`);
441
+ if (binding.initialTurnRunId !== undefined
442
+ && binding.initialTurnRunId !== request.runId) {
443
+ throw new RuntimeBindingContractError(`Session host returned an initial structured Turn for another Run: ${request.owner.roleName}.`);
444
+ }
445
+ if (binding.initialTurnDeliveryUnknownRunId !== undefined
446
+ && binding.initialTurnDeliveryUnknownRunId !== request.runId) {
447
+ throw new RuntimeBindingContractError(`Session host returned a delivery-unknown initial structured Turn for another Run: ${request.owner.roleName}.`);
448
+ }
449
+ if (binding.initialTurnRejectedRunId !== undefined
450
+ && binding.initialTurnRejectedRunId !== request.runId) {
451
+ throw new RuntimeBindingContractError(`Session host returned a rejected initial structured Turn for another Run: ${request.owner.roleName}.`);
485
452
  }
486
453
  if (binding.launchId !== launchId
487
454
  || !ownerMatches
@@ -491,35 +458,8 @@ function requireMatchingRuntimeBinding(raw, request, launchId, launchPromptAckno
491
458
  && binding.nativeSessionId !== request.nativeSessionId)) {
492
459
  throw new RuntimeBindingContractError(`Session host returned a binding that does not match the requested runtime: ${request.owner.roleName}.`);
493
460
  }
494
- if (launchPromptAcknowledgementRequired
495
- && binding.initialPromptRunId !== request.runId
496
- && !(launchPromptUncertaintyAllowed && binding.hostCreated === false)) {
497
- throw binding.hostCreated === false
498
- ? new LaunchSubmittedHostBusyError(`An earlier managed runtime is still exiting: ${request.owner.roleName}.`)
499
- : new RuntimeBindingContractError(`Session host cannot acknowledge the exact launch-carried prompt: ${request.owner.roleName}.`);
500
- }
501
- if (launchPromptAcknowledgementRequired
502
- && launchPromptUncertaintyAllowed
503
- && binding.hostCreated === false
504
- && request.runId !== undefined
505
- && binding.initialPromptRunId !== request.runId) {
506
- // A Controller restart can lose only the in-memory fact that a still-
507
- // running generation carried this Run at process launch. Keep the
508
- // uncertainty explicitly tied to the exact reservation/Run; the matching
509
- // Provider Hook remains the sole acceptance authority.
510
- return {
511
- ...binding,
512
- launchPromptUncertainRunId: request.runId
513
- };
514
- }
515
461
  return binding;
516
462
  }
517
- function carriesExactRunPrompt(request) {
518
- if (request.owner.scope !== "task" || request.runId === undefined)
519
- return false;
520
- return request.adapterId === "claude"
521
- || (request.adapterId === "codex" && request.mode === "new");
522
- }
523
463
  function defaultLaunchFingerprint(request) {
524
464
  return createHash("sha256").update(JSON.stringify([
525
465
  request.owner,
@@ -0,0 +1,273 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { callController } from "../core/controllerClient.js";
3
+ import { builtinAgentDriverRegistry } from "../runtime/builtinAgentDrivers.js";
4
+ import { createRuntimeObservation, runtimeObservationSemanticKey } from "../runtime/runtimeObservation.js";
5
+ import { runtimeLifecycleSignalKey } from "../runtime/lifecycleReservation.js";
6
+ import { FileRuntimeEventInbox } from "./runtimeEventInbox.js";
7
+ import { resolveRuntimeHookRunFence } from "./runtimeHookRunFence.js";
8
+ let structuredSequence = 0;
9
+ export async function publishStructuredProviderAccepted(input) {
10
+ const adapterId = requireIdentity(input.environment.YUI_ADAPTER_ID, "Agent adapter id");
11
+ const driver = builtinAgentDriverRegistry().requireByAdapterId(adapterId);
12
+ const startupSession = driver.capabilities.observation.sessionBootstrap;
13
+ const fence = resolveRuntimeHookRunFence(input.environment, adapterId, input.receipt.nativeSessionId, {
14
+ startupSession,
15
+ nativeTurnId: input.receipt.nativeTurnId
16
+ });
17
+ const observedAt = input.receipt.acceptedAt;
18
+ const commonFence = {
19
+ taskId: fence.taskId,
20
+ roleName: fence.roleName,
21
+ runId: fence.runId,
22
+ agentId: fence.agentId,
23
+ driverId: driver.id,
24
+ launchId: fence.launchId,
25
+ sessionGenerationId: fence.launchId,
26
+ conversationId: input.receipt.conversationId,
27
+ activationId: requireIdentity(input.activationId, "Provider Activation id"),
28
+ nativeSessionId: input.receipt.nativeSessionId,
29
+ nativeTurnId: input.receipt.nativeTurnId,
30
+ // The structured Host owns the exact input attempt identity. Runtime
31
+ // descriptor receipts name the Run bootstrap and must not overwrite a
32
+ // continuation or human-takeover Turn.
33
+ receiptId: input.receipt.attemptId
34
+ };
35
+ const baseSequence = nextStructuredSequence();
36
+ const observations = [observation({
37
+ kind: startupSession === "preallocated" ? "session.ready" : "session.started",
38
+ observedAt,
39
+ sequence: baseSequence,
40
+ ordinal: 0,
41
+ fence: commonFence
42
+ }), observation({
43
+ kind: "conversation.observed",
44
+ observedAt,
45
+ sequence: baseSequence,
46
+ ordinal: 1,
47
+ fence: commonFence,
48
+ payload: { recoverability: "recoverable" }
49
+ }), observation({
50
+ kind: "activation.started",
51
+ observedAt,
52
+ sequence: baseSequence,
53
+ ordinal: 2,
54
+ fence: commonFence
55
+ }), observation({
56
+ kind: "turn.accepted",
57
+ observedAt,
58
+ sequence: baseSequence,
59
+ ordinal: 3,
60
+ fence: commonFence
61
+ })];
62
+ await persistAndApply(input.home, observations, fence.taskId, fence.roleName);
63
+ }
64
+ export async function publishStructuredProviderOpened(input) {
65
+ const adapterId = requireIdentity(input.environment.YUI_ADAPTER_ID, "Agent adapter id");
66
+ const driver = builtinAgentDriverRegistry().requireByAdapterId(adapterId);
67
+ const startupSession = driver.capabilities.observation.sessionBootstrap;
68
+ const fence = resolveRuntimeHookRunFence(input.environment, adapterId, input.nativeSessionId, { startupSession });
69
+ const commonFence = {
70
+ taskId: fence.taskId,
71
+ roleName: fence.roleName,
72
+ runId: fence.runId,
73
+ agentId: fence.agentId,
74
+ driverId: driver.id,
75
+ launchId: fence.launchId,
76
+ sessionGenerationId: fence.launchId,
77
+ conversationId: input.conversationId,
78
+ activationId: requireIdentity(input.activationId, "Provider Activation id"),
79
+ nativeSessionId: input.nativeSessionId,
80
+ ...(fence.receiptId === undefined ? {} : { receiptId: fence.receiptId })
81
+ };
82
+ const sequence = nextStructuredSequence();
83
+ const observations = [observation({
84
+ kind: startupSession === "preallocated" ? "session.ready" : "session.started",
85
+ observedAt: input.observedAt,
86
+ sequence,
87
+ ordinal: 0,
88
+ fence: commonFence
89
+ }), observation({
90
+ kind: "conversation.observed",
91
+ observedAt: input.observedAt,
92
+ sequence,
93
+ ordinal: 1,
94
+ fence: commonFence,
95
+ payload: { recoverability: input.recoverability }
96
+ }), observation({
97
+ kind: "activation.started",
98
+ observedAt: input.observedAt,
99
+ sequence,
100
+ ordinal: 2,
101
+ fence: commonFence
102
+ })];
103
+ await persistAndApply(input.home, observations, fence.taskId, fence.roleName);
104
+ }
105
+ export async function publishStructuredConversationRecoverability(input) {
106
+ const adapterId = requireIdentity(input.environment.YUI_ADAPTER_ID, "Agent adapter id");
107
+ const driver = builtinAgentDriverRegistry().requireByAdapterId(adapterId);
108
+ const fence = resolveRuntimeHookRunFence(input.environment, adapterId, input.conversationId);
109
+ const sequence = nextStructuredSequence();
110
+ const observationFence = {
111
+ taskId: fence.taskId,
112
+ roleName: fence.roleName,
113
+ runId: fence.runId,
114
+ agentId: fence.agentId,
115
+ driverId: driver.id,
116
+ launchId: fence.launchId,
117
+ sessionGenerationId: fence.launchId,
118
+ conversationId: input.conversationId,
119
+ activationId: requireIdentity(input.activationId, "Provider Activation id"),
120
+ nativeSessionId: input.conversationId,
121
+ ...(fence.receiptId === undefined ? {} : { receiptId: fence.receiptId })
122
+ };
123
+ const observations = [observation({
124
+ kind: "activation.started",
125
+ observedAt: input.observedAt,
126
+ sequence,
127
+ ordinal: 0,
128
+ fence: observationFence
129
+ }), observation({
130
+ kind: "conversation.observed",
131
+ observedAt: input.observedAt,
132
+ sequence,
133
+ ordinal: 1,
134
+ fence: observationFence,
135
+ payload: { recoverability: input.recoverability }
136
+ })];
137
+ if (input.recoverability === "unrecoverable") {
138
+ observations.push(observation({
139
+ kind: "activation.failed",
140
+ observedAt: input.observedAt,
141
+ sequence,
142
+ ordinal: 2,
143
+ fence: observationFence,
144
+ payload: {}
145
+ }));
146
+ }
147
+ await persistAndApply(input.home, observations, fence.taskId, fence.roleName);
148
+ }
149
+ export async function publishStructuredProviderTerminal(input) {
150
+ const adapterId = requireIdentity(input.environment.YUI_ADAPTER_ID, "Agent adapter id");
151
+ const driver = builtinAgentDriverRegistry().requireByAdapterId(adapterId);
152
+ const fence = resolveRuntimeHookRunFence(input.environment, adapterId, input.terminal.nativeSessionId, { terminal: true, nativeTurnId: input.terminal.nativeTurnId });
153
+ const kind = input.terminal.status === "completed"
154
+ ? "turn.completed"
155
+ : input.terminal.status === "cancelled" ? "turn.cancelled" : "turn.failed";
156
+ const payload = kind === "turn.completed"
157
+ ? { ...(input.terminal.summary === undefined ? {} : { summary: input.terminal.summary }) }
158
+ : kind === "turn.failed"
159
+ ? {
160
+ failure: {
161
+ code: "provider-structured-failure",
162
+ ...(input.terminal.error === undefined
163
+ ? {}
164
+ : { details: input.terminal.error })
165
+ }
166
+ }
167
+ : {};
168
+ const terminalObservation = observation({
169
+ kind,
170
+ observedAt: input.terminal.observedAt,
171
+ sequence: nextStructuredSequence(),
172
+ ordinal: 0,
173
+ fence: {
174
+ taskId: fence.taskId,
175
+ roleName: fence.roleName,
176
+ runId: fence.runId,
177
+ agentId: fence.agentId,
178
+ driverId: driver.id,
179
+ launchId: fence.launchId,
180
+ sessionGenerationId: fence.launchId,
181
+ conversationId: input.terminal.conversationId,
182
+ activationId: requireIdentity(input.activationId, "Provider Activation id"),
183
+ nativeSessionId: input.terminal.nativeSessionId,
184
+ nativeTurnId: input.terminal.nativeTurnId,
185
+ ...(fence.receiptId === undefined ? {} : { receiptId: fence.receiptId })
186
+ },
187
+ payload
188
+ });
189
+ await persistAndApply(input.home, [terminalObservation], fence.taskId, fence.roleName);
190
+ }
191
+ export async function publishStructuredProviderActivationTerminal(input) {
192
+ const adapterId = requireIdentity(input.environment.YUI_ADAPTER_ID, "Agent adapter id");
193
+ const driver = builtinAgentDriverRegistry().requireByAdapterId(adapterId);
194
+ const fence = resolveRuntimeHookRunFence(input.environment, adapterId, input.nativeSessionId);
195
+ const terminal = observation({
196
+ kind: input.status === "failed" ? "activation.failed" : "activation.ended",
197
+ observedAt: input.observedAt,
198
+ sequence: nextStructuredSequence(),
199
+ ordinal: 0,
200
+ fence: {
201
+ taskId: fence.taskId,
202
+ roleName: fence.roleName,
203
+ runId: fence.runId,
204
+ agentId: fence.agentId,
205
+ driverId: driver.id,
206
+ launchId: fence.launchId,
207
+ sessionGenerationId: fence.launchId,
208
+ conversationId: requireIdentity(input.conversationId, "Provider Conversation id"),
209
+ activationId: requireIdentity(input.activationId, "Provider Activation id"),
210
+ nativeSessionId: requireIdentity(input.nativeSessionId, "native Session id"),
211
+ ...(fence.receiptId === undefined ? {} : { receiptId: fence.receiptId })
212
+ },
213
+ payload: {}
214
+ });
215
+ await persistAndApply(input.home, [terminal], fence.taskId, fence.roleName);
216
+ }
217
+ function observation(input) {
218
+ const eventId = `agent-host-${randomUUID()}`;
219
+ const partial = {
220
+ eventId,
221
+ kind: input.kind,
222
+ fence: input.fence,
223
+ sequence: input.sequence,
224
+ payload: input.payload ?? {}
225
+ };
226
+ return createRuntimeObservation({
227
+ schemaVersion: 2,
228
+ eventId,
229
+ semanticKey: runtimeObservationSemanticKey(partial),
230
+ kind: input.kind,
231
+ authority: "provider-structured",
232
+ receivedAt: new Date().toISOString(),
233
+ observedAt: input.observedAt,
234
+ sequence: input.sequence,
235
+ ordinal: input.ordinal,
236
+ fence: input.fence,
237
+ payload: input.payload ?? {}
238
+ });
239
+ }
240
+ async function signalController(home, taskId, roleName) {
241
+ await callController(home, "scheduler.signal", {
242
+ key: runtimeLifecycleSignalKey({ scope: "task", taskId, roleName })
243
+ }, { timeoutMs: 100 }).catch(() => { });
244
+ }
245
+ function nextStructuredSequence() {
246
+ structuredSequence = (structuredSequence + 1) % Number.MAX_SAFE_INTEGER;
247
+ return structuredSequence;
248
+ }
249
+ function requireIdentity(value, label) {
250
+ if (typeof value !== "string" || value.trim().length === 0 || value.includes("\0")) {
251
+ throw new Error(`${label} is invalid.`);
252
+ }
253
+ return value.trim();
254
+ }
255
+ async function persistAndApply(home, observations, taskId, roleName) {
256
+ const inbox = new FileRuntimeEventInbox(home);
257
+ for (const entry of observations)
258
+ inbox.enqueueObservation(entry);
259
+ for (const entry of observations) {
260
+ const result = await callController(home, "runtime.observation-apply", entry, {
261
+ timeoutMs: 10_000
262
+ });
263
+ // A fast Provider can accept the initial Turn before the scheduler call
264
+ // that launched this Host has returned and committed `run.pushed`. The
265
+ // immutable inbox entry already makes that exact fenced fact durable;
266
+ // `deferred` therefore means "retained for replay", not delivery failure.
267
+ // The signal below schedules the replay after the transport transaction.
268
+ if (result.outcome !== "applied" && result.outcome !== "deferred") {
269
+ throw new Error(`Structured Provider observation was not applied: ${result.outcome ?? "unknown"}.`);
270
+ }
271
+ }
272
+ await signalController(home, taskId, roleName);
273
+ }
@@ -159,6 +159,24 @@ class CodexAdapter extends BaseAdapter {
159
159
  const launch = this.compileNew(input);
160
160
  return { ...launch, argv: [...launch.argv, "resume", nativeId(input.nativeSessionId)] };
161
161
  }
162
+ compileManagedControl(input, _mode, _nativeSessionId) {
163
+ const launch = this.compileNew(input);
164
+ const argv = [...launch.argv];
165
+ if (input.config.model !== undefined) {
166
+ const modelFlag = argv.findIndex((value, index) => (value === "--model" && argv[index + 1] === input.config.model));
167
+ if (modelFlag < 0)
168
+ throw new Error("Managed Codex launch lost its selected model.");
169
+ argv.splice(modelFlag, 2);
170
+ // App Server thread/start reads the model from resolved configuration;
171
+ // the interactive --model shortcut is not inherited by new threads.
172
+ argv.push("--config", `model=${tomlString(input.config.model)}`);
173
+ }
174
+ return {
175
+ ...launch,
176
+ argv: [...argv, "app-server", "--stdio"],
177
+ transport: "codex-app-server-stdio"
178
+ };
179
+ }
162
180
  }
163
181
  class ClaudeAdapter extends BaseAdapter {
164
182
  id = "claude";
@@ -261,6 +279,28 @@ class ClaudeAdapter extends BaseAdapter {
261
279
  const launch = super.compileNew(input);
262
280
  return { ...launch, argv: [...launch.argv, "--resume", nativeId(input.nativeSessionId)] };
263
281
  }
282
+ compileManagedControl(input, mode, nativeSessionId) {
283
+ if (nativeSessionId === undefined) {
284
+ throw new Error("Managed Claude control requires a preallocated native Session id.");
285
+ }
286
+ const sessionId = nativeId(nativeSessionId);
287
+ const launch = mode === "new"
288
+ ? super.compileNew(input)
289
+ : this.compileResume({ ...input, nativeSessionId: sessionId });
290
+ return {
291
+ ...launch,
292
+ argv: [
293
+ ...launch.argv,
294
+ ...(mode === "new" ? ["--session-id", sessionId] : []),
295
+ "-p",
296
+ "--output-format", "stream-json",
297
+ "--input-format", "stream-json",
298
+ "--verbose",
299
+ "--replay-user-messages"
300
+ ],
301
+ transport: "claude-stream-json"
302
+ };
303
+ }
264
304
  }
265
305
  function driverPreInputReadiness(adapterId) {
266
306
  const capability = builtinAgentDriverRegistry().requireByAdapterId(adapterId)