@zq-silk/yui 0.13.8 → 0.13.10

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 (68) hide show
  1. package/README.md +9 -8
  2. package/dist/cli/commandCatalog.js +21 -2
  3. package/dist/cli.js +42 -13
  4. package/dist/commands/agentCommands.js +1 -1
  5. package/dist/commands/configCommands.js +1 -86
  6. package/dist/commands/executionAuditCommands.js +17 -16
  7. package/dist/commands/globalRoleCommands.js +4 -4
  8. package/dist/commands/sessionCommands.js +2 -6
  9. package/dist/commands/taskActor.js +1 -2
  10. package/dist/commands/taskCommands.js +92 -9
  11. package/dist/commands/taskRoleRuntimeStatus.js +3 -3
  12. package/dist/config/configCatalog.js +1 -6
  13. package/dist/config/yuiConfig.js +0 -79
  14. package/dist/controller/clientRuntime.js +40 -3
  15. package/dist/controller/controller.js +29 -52
  16. package/dist/controller/fileSchedulerStoreAdapter.js +305 -1056
  17. package/dist/controller/runtime.js +12 -5
  18. package/dist/controller/runtimeHookRunFence.js +3 -9
  19. package/dist/controller/runtimeLaunchCoordinator.js +44 -67
  20. package/dist/controller/structuredProviderObservation.js +18 -5
  21. package/dist/coordination/workMailbox.js +4 -4
  22. package/dist/execution/executionHealth.js +1 -1
  23. package/dist/executor/agentExecutor.js +92 -68
  24. package/dist/executor/executorRegistry.js +8 -20
  25. package/dist/executor/fileRoleLaunchPlanner.js +24 -90
  26. package/dist/executor/turnCompletion.js +5 -5
  27. package/dist/lifecycle/exactRunTerminalization.js +1 -1
  28. package/dist/observability/executionAudit.js +40 -94
  29. package/dist/operator/operatorSessionHistory.js +7 -5
  30. package/dist/role/role.js +1 -1
  31. package/dist/run/agentRun.js +4 -54
  32. package/dist/runtime/agentDriver.js +2 -0
  33. package/dist/runtime/agentError.js +114 -0
  34. package/dist/runtime/agentHost.js +55 -82
  35. package/dist/runtime/builtinAgentDrivers.js +21 -9
  36. package/dist/runtime/builtinAgentErrorMappers.js +150 -0
  37. package/dist/runtime/exactControlPlane.js +6 -12
  38. package/dist/runtime/index.js +0 -1
  39. package/dist/runtime/launchBroker.js +5 -19
  40. package/dist/runtime/lifecycleReservation.js +20 -4
  41. package/dist/runtime/providerRuntimeIdentity.js +3 -2
  42. package/dist/runtime/runtimeBinding.js +0 -27
  43. package/dist/runtime/runtimeObservation.js +7 -16
  44. package/dist/runtime/runtimeSessionCandidate.js +3 -10
  45. package/dist/runtime/sessionLaunchRequest.js +1 -2
  46. package/dist/runtime/sessionReconciliation.js +2 -2
  47. package/dist/runtime/structuredProviderHost.js +44 -79
  48. package/dist/runtime/taskRuntimeIsolation.js +0 -7
  49. package/dist/runtime/tmuxAdapters.js +6 -49
  50. package/dist/scheduler/activeRoleRunDelivery.js +218 -168
  51. package/dist/scheduler/leaderWakeupProcessor.js +126 -86
  52. package/dist/scheduler/roleRunLiveness.js +4 -1
  53. package/dist/scheduler/roleRunStall.js +7 -11
  54. package/dist/scheduler/wakeReason.js +4 -0
  55. package/dist/storage/migration/productionRegistry.js +332 -0
  56. package/dist/storage/sqliteSchema.js +54 -2
  57. package/dist/storage/sqliteStore.js +11 -44
  58. package/dist/storage/taskStore.js +7 -35
  59. package/dist/storage/upgrade/sqliteStateMigration.js +17 -9
  60. package/package.json +1 -1
  61. package/skills/yui-leader/SKILL.md +30 -8
  62. package/skills/yui-operator/SKILL.md +14 -6
  63. package/skills/yui-runtime/SKILL.md +6 -4
  64. package/dist/lifecycle/providerErrorClass.js +0 -152
  65. package/dist/run/providerRetry.js +0 -226
  66. package/dist/run/providerRetryConfig.js +0 -27
  67. package/dist/runtime/providerErrorCodes.js +0 -278
  68. package/dist/runtime/providerRecoveryDecision.js +0 -55
@@ -16,7 +16,8 @@ import { validateRuntimeProcessExitObservation } from "./processExitObservation.
16
16
  import { persistRuntimeProcessExitObservation, replayRuntimeProcessExitOutbox } from "./processExitOutbox.js";
17
17
  import { readRuntimeStopReceipt, removeRuntimeStopReceipt } from "./runtimeStopReceipt.js";
18
18
  import { AGENT_HOST_CONTROL_TIMEOUT_MS, AGENT_HOST_READY_TIMEOUT_MS } from "./runtimeDeadlines.js";
19
- export const AGENT_HOST_CONTROL_PROTOCOL = "yui-agent-host/v2";
19
+ import { serializeAgentErrorRaw } from "./agentError.js";
20
+ export const AGENT_HOST_CONTROL_PROTOCOL = "yui-agent-host/v3";
20
21
  const HOST_CONTROL_MAX_BYTES = 32 * 1024;
21
22
  const CODEX_CLIENT_STABLE_MS = 5_000;
22
23
  const MAX_CONSECUTIVE_CODEX_DISCONNECTS = 3;
@@ -146,7 +147,7 @@ export async function runAgentHost(input) {
146
147
  schemaVersion: 1,
147
148
  adapterId: "codex",
148
149
  transport: "codex-app-server-proxy",
149
- kind: "ensure",
150
+ kind: "restore",
150
151
  mode: "resume",
151
152
  nativeSessionId: disconnectedSession.nativeSessionId,
152
153
  ...(previousControl.sessionTitle === undefined
@@ -351,7 +352,7 @@ export async function runAgentHost(input) {
351
352
  throw new Error("Agent Host still owns an unsettled Provider Turn.");
352
353
  }
353
354
  const requestedAuthority = validateProviderAuthorityFence(providerControl.authority);
354
- const replacesCurrentConversation = session !== undefined && providerControl.kind === "new";
355
+ const replacesCurrentConversation = session !== undefined && providerControl.kind === "start";
355
356
  if (replacesCurrentConversation) {
356
357
  throw new Error("Agent Host cannot replace a live Provider Session; stop it before starting a fresh Session.");
357
358
  }
@@ -364,9 +365,6 @@ export async function runAgentHost(input) {
364
365
  updateSnapshot(hostSnapshot("starting", {
365
366
  launchId: next.launchId,
366
367
  adapterId: providerControl.adapterId,
367
- ...(providerControl.initialTurn === undefined
368
- ? {}
369
- : { attemptId: providerControl.initialTurn.attemptId }),
370
368
  ...(session === undefined ? {} : {
371
369
  processInstanceId: session.processInstanceId,
372
370
  nativeSessionId: session.nativeSessionId,
@@ -374,8 +372,6 @@ export async function runAgentHost(input) {
374
372
  }),
375
373
  ...authorityFields()
376
374
  }));
377
- let providerAcceptedAttemptId;
378
- let durableInitialTurn;
379
375
  try {
380
376
  if (session !== undefined) {
381
377
  if (session.adapterId !== providerControl.adapterId
@@ -390,7 +386,7 @@ export async function runAgentHost(input) {
390
386
  conversationRecoverability = providerControl.adapterId === "codex"
391
387
  ? "recoverable"
392
388
  : "unknown";
393
- if (providerControl.kind === "ensure" && providerControl.ownedTurn !== undefined) {
389
+ if (providerControl.kind === "restore" && providerControl.ownedTurn !== undefined) {
394
390
  activeTurnPayload = next;
395
391
  activeTurnAttemptId = providerControl.ownedTurn.attemptId;
396
392
  activeNativeTurnId = providerControl.ownedTurn.turnId;
@@ -416,58 +412,24 @@ export async function runAgentHost(input) {
416
412
  recoverability: conversationRecoverability,
417
413
  observedAt: new Date().toISOString()
418
414
  });
419
- let receipt;
420
- if (providerControl.initialTurn !== undefined) {
421
- durableInitialTurn = hostTurnControlParams(next, session.nativeSessionId, requestedAuthority, providerControl.initialTurn.attemptId);
422
- await beginDurableProviderTurn(input.home, durableInitialTurn);
423
- activeTurnPayload = next;
424
- activeTurnAttemptId = providerControl.initialTurn.attemptId;
425
- activeNativeTurnId = undefined;
426
- try {
427
- receipt = await session.submitTurn(providerControl.initialTurn);
428
- providerAcceptedAttemptId = receipt.attemptId;
429
- activeNativeTurnId = receipt.nativeTurnId;
430
- }
431
- catch (error) {
432
- await resolveProviderTurnSubmission(input.home, durableInitialTurn, error);
433
- throw error;
434
- }
435
- }
436
- if (receipt !== undefined) {
437
- conversationRecoverability = "recoverable";
438
- try {
439
- await publishStructuredProviderAccepted({
440
- home: input.home,
441
- environment: next.environment,
442
- activationId: activationId ?? next.launchId,
443
- receipt
444
- });
445
- }
446
- catch (error) {
447
- await resolveProviderTurnSubmission(input.home, durableInitialTurn, new ProviderDeliveryUnknownError(`Provider accepted input but its durable acknowledgement could not be confirmed: ${errorText(error)}`, receipt.attemptId));
448
- throw error;
449
- }
450
- }
451
- const ensuredOwnedTurn = providerControl.kind === "ensure"
415
+ const restoredOwnedTurn = providerControl.kind === "restore"
452
416
  ? providerControl.ownedTurn
453
417
  : undefined;
454
- const providerState = receipt !== undefined || ensuredOwnedTurn !== undefined
418
+ const providerState = restoredOwnedTurn !== undefined
455
419
  ? "ready"
456
420
  : session.activeTurnId === undefined ? "idle" : "busy";
457
421
  updateSnapshot(hostSnapshot(providerState, {
458
422
  launchId: next.launchId,
459
423
  adapterId: providerControl.adapterId,
460
424
  processInstanceId: session.processInstanceId,
461
- nativeSessionId: receipt?.nativeSessionId ?? session.nativeSessionId,
462
- conversationId: receipt?.conversationId ?? session.conversationId,
463
- ...(receipt !== undefined
464
- ? { attemptId: receipt.attemptId, nativeTurnId: receipt.nativeTurnId }
465
- : ensuredOwnedTurn === undefined
466
- ? {}
467
- : {
468
- attemptId: ensuredOwnedTurn.attemptId,
469
- nativeTurnId: ensuredOwnedTurn.turnId
470
- }),
425
+ nativeSessionId: session.nativeSessionId,
426
+ conversationId: session.conversationId,
427
+ ...(restoredOwnedTurn === undefined
428
+ ? {}
429
+ : {
430
+ attemptId: restoredOwnedTurn.attemptId,
431
+ nativeTurnId: restoredOwnedTurn.turnId
432
+ }),
471
433
  ...authorityFields()
472
434
  }));
473
435
  return snapshot;
@@ -488,33 +450,21 @@ export async function runAgentHost(input) {
488
450
  conversationRecoverability = "unknown";
489
451
  authority = undefined;
490
452
  }
491
- const deliveryUnknown = error instanceof ProviderDeliveryUnknownError
492
- || providerAcceptedAttemptId !== undefined;
493
- const state = deliveryUnknown
494
- ? "delivery-unknown"
495
- : error instanceof ProviderTurnBusyError
496
- ? "busy"
497
- : error instanceof ProviderTurnRejectedError ? "rejected" : "failed";
453
+ const state = error instanceof ProviderTurnBusyError
454
+ ? "busy"
455
+ : error instanceof ProviderTurnRejectedError ? "rejected" : "failed";
498
456
  updateSnapshot(hostSnapshot(state, {
499
457
  launchId: next.launchId,
500
458
  adapterId: providerControl.adapterId,
501
459
  processInstanceId: session?.processInstanceId,
502
460
  nativeSessionId: session?.nativeSessionId ?? providerControl.nativeSessionId,
503
461
  conversationId: session?.conversationId ?? providerControl.nativeSessionId,
504
- ...(providerControl.initialTurn === undefined
505
- ? {}
506
- : { attemptId: providerControl.initialTurn.attemptId }),
507
462
  ...authorityFields(),
508
463
  detail: errorText(error)
509
464
  }));
510
- if (state !== "delivery-unknown") {
511
- activeTurnPayload = undefined;
512
- activeTurnAttemptId = undefined;
513
- activeNativeTurnId = undefined;
514
- }
515
- if (deliveryUnknown && !(error instanceof ProviderDeliveryUnknownError)) {
516
- throw new ProviderDeliveryUnknownError(`Provider accepted input but its durable acknowledgement could not be confirmed: ${errorText(error)}`, providerAcceptedAttemptId);
517
- }
465
+ activeTurnPayload = undefined;
466
+ activeTurnAttemptId = undefined;
467
+ activeNativeTurnId = undefined;
518
468
  throw error;
519
469
  }
520
470
  };
@@ -536,9 +486,15 @@ export async function runAgentHost(input) {
536
486
  throw new Error("Agent Host rejected a stale Provider writer fence.");
537
487
  }
538
488
  if (activeTurnPayload !== undefined || snapshot.state === "settling") {
539
- throw new Error("Agent Host still owns an unsettled Provider Turn.");
489
+ throw new ProviderTurnBusyError("Agent Host still owns an unsettled Provider Turn.", request.turn.attemptId, activeNativeTurnId);
540
490
  }
541
- activeTurnPayload = sessionPayload;
491
+ activeTurnPayload = {
492
+ ...sessionPayload,
493
+ environment: {
494
+ ...sessionPayload.environment,
495
+ YUI_RUN_ID: request.runId
496
+ }
497
+ };
542
498
  activeTurnAttemptId = request.turn.attemptId;
543
499
  activeNativeTurnId = undefined;
544
500
  updateSnapshot(hostSnapshot("starting", {
@@ -550,17 +506,26 @@ export async function runAgentHost(input) {
550
506
  attemptId: request.turn.attemptId,
551
507
  ...authorityFields()
552
508
  }));
509
+ const durableTurn = hostTurnControlParams(sessionPayload, session.nativeSessionId, request.authority, request.turn.attemptId, request.runId);
510
+ await beginDurableProviderTurn(input.home, durableTurn);
553
511
  let providerAccepted = false;
554
512
  try {
555
513
  const receipt = await session.submitTurn(request.turn);
556
514
  providerAccepted = true;
557
515
  activeNativeTurnId = receipt.nativeTurnId;
558
- await publishStructuredProviderAccepted({
559
- home: input.home,
560
- environment: sessionPayload.environment,
561
- activationId: activationId ?? sessionPayload.launchId,
562
- receipt
563
- });
516
+ try {
517
+ await publishStructuredProviderAccepted({
518
+ home: input.home,
519
+ environment: sessionPayload.environment,
520
+ activationId: activationId ?? sessionPayload.launchId,
521
+ receipt
522
+ });
523
+ }
524
+ catch (error) {
525
+ const unknown = new ProviderDeliveryUnknownError(`Provider accepted input but its durable acknowledgement could not be confirmed: ${errorText(error)}`, request.turn.attemptId);
526
+ await resolveProviderTurnSubmission(input.home, durableTurn, unknown);
527
+ throw unknown;
528
+ }
564
529
  updateSnapshot(hostSnapshot("ready", {
565
530
  launchId: request.launchId,
566
531
  adapterId: session.adapterId,
@@ -574,6 +539,9 @@ export async function runAgentHost(input) {
574
539
  return snapshot;
575
540
  }
576
541
  catch (error) {
542
+ if (!providerAccepted) {
543
+ await resolveProviderTurnSubmission(input.home, durableTurn, error);
544
+ }
577
545
  const deliveryUnknown = error instanceof ProviderDeliveryUnknownError || providerAccepted;
578
546
  const state = deliveryUnknown
579
547
  ? "delivery-unknown"
@@ -684,6 +652,7 @@ export async function runAgentHost(input) {
684
652
  type: "submit-turn",
685
653
  launchId: currentPayload.launchId,
686
654
  nativeSessionId: currentSession.nativeSessionId,
655
+ runId: requiredEnvironment(currentPayload.environment.YUI_RUN_ID, "Run id"),
687
656
  authority: currentAuthority,
688
657
  turn: {
689
658
  attemptId,
@@ -902,8 +871,10 @@ export async function openAgentHostControl(home, payload, snapshot, dispatch) {
902
871
  }
903
872
  catch (error) {
904
873
  const current = snapshot();
905
- socket.end(`${JSON.stringify(controlResult("rejected", validateSnapshot({
874
+ const busy = error instanceof ProviderTurnBusyError;
875
+ socket.end(`${JSON.stringify(controlResult(busy ? "accepted" : "rejected", validateSnapshot({
906
876
  ...current,
877
+ ...(busy ? { state: "busy" } : {}),
907
878
  detail: errorText(error),
908
879
  updatedAt: new Date().toISOString()
909
880
  })))}\n`);
@@ -951,6 +922,7 @@ function validateControl(control) {
951
922
  if (control.type === "submit-turn") {
952
923
  validateLaunchId(control.launchId);
953
924
  validateIdentity(control.nativeSessionId, "native Session id");
925
+ validateIdentity(control.runId, "Run id");
954
926
  validateProviderAuthorityFence(control.authority);
955
927
  validateIdentity(control.turn.attemptId, "Provider input attempt id");
956
928
  if (typeof control.turn.boundedText !== "string"
@@ -1056,12 +1028,12 @@ async function delay(milliseconds) {
1056
1028
  timer.unref();
1057
1029
  });
1058
1030
  }
1059
- function hostTurnControlParams(payload, nativeSessionId, authority, attemptId) {
1031
+ function hostTurnControlParams(payload, nativeSessionId, authority, attemptId, runId = requiredEnvironment(payload.environment.YUI_RUN_ID, "Run id")) {
1060
1032
  const environment = payload.environment;
1061
1033
  return Object.freeze({
1062
1034
  taskId: requiredEnvironment(environment.YUI_TASK_ID, "Task id"),
1063
1035
  roleName: requiredEnvironment(environment.YUI_ROLE, "Role name"),
1064
- runId: requiredEnvironment(environment.YUI_RUN_ID, "Run id"),
1036
+ runId: requiredEnvironment(runId, "Run id"),
1065
1037
  agentId: requiredEnvironment(environment.YUI_AGENT_ID, "Agent id"),
1066
1038
  launchId: payload.launchId,
1067
1039
  nativeSessionId: requiredEnvironment(nativeSessionId, "native Session id"),
@@ -1120,6 +1092,7 @@ async function resolveProviderTurnSubmission(home, durableTurn, error) {
1120
1092
  ? "delivery-unknown"
1121
1093
  : "rejected",
1122
1094
  reason: errorText(error),
1095
+ raw: serializeAgentErrorRaw(error),
1123
1096
  observedAt: new Date().toISOString()
1124
1097
  };
1125
1098
  try {
@@ -1,6 +1,7 @@
1
1
  import { AgentDriverRegistry } from "./agentDriver.js";
2
2
  import { claudeTranscriptObserver, codexTranscriptObserver, transcriptObserverSource } from "./builtinTranscriptObserver.js";
3
- import { parseClaudeError } from "./providerErrorCodes.js";
3
+ import { mapClaudeAgentError, mapCodexAgentError } from "./builtinAgentErrorMappers.js";
4
+ import { serializeAgentErrorRaw, standardAgentError } from "./agentError.js";
4
5
  export const CODEX_DRIVER_ID = "openai/codex";
5
6
  export const CLAUDE_CODE_DRIVER_ID = "anthropic/claude-code";
6
7
  export function builtinDriverIdForAdapter(adapterId) {
@@ -77,6 +78,7 @@ export const BUILTIN_AGENT_DRIVERS = Object.freeze([
77
78
  runtime: Object.freeze({
78
79
  nativeSessionId: ({ payload }) => (optionalIdentityFrom(payload, ["session_id"])),
79
80
  nativeTurnId: ({ payload }) => (optionalIdentityFrom(payload, ["prompt_id", "turn_id"])),
81
+ mapError: mapClaudeAgentError,
80
82
  mapHook: ({ hookEventName, payload, occurrenceId }) => (mapClaudeHook(hookEventName, payload, occurrenceId)),
81
83
  classifyHook: ({ hookEventName, payload }) => Object.freeze({
82
84
  ...(hookEventName === "SessionStart" && payload.source === "startup"
@@ -133,6 +135,7 @@ export const BUILTIN_AGENT_DRIVERS = Object.freeze([
133
135
  runtime: Object.freeze({
134
136
  nativeSessionId: ({ payload }) => (optionalIdentityFrom(payload, ["session_id"])),
135
137
  nativeTurnId: ({ payload }) => (optionalIdentityFrom(payload, ["turn_id", "prompt_id"])),
138
+ mapError: mapCodexAgentError,
136
139
  mapHook: ({ hookEventName, payload, occurrenceId }) => (mapCodexHook(hookEventName, payload, occurrenceId)),
137
140
  classifyHook: ({ hookEventName, payload }) => Object.freeze({
138
141
  ...(hookEventName === "SessionStart"
@@ -237,7 +240,10 @@ function mapClaudeHook(name, payload, occurrenceId) {
237
240
  case "StopFailure":
238
241
  return mapped("turn.failed", claudeFailure(payload));
239
242
  case "SessionEnd":
240
- return [mapped("session.ended"), mapped("activation.ended")];
243
+ // Native CLI exit ends this local Provider attachment, not the
244
+ // resumable Conversation. Durable Session end is an explicit Yui
245
+ // mutation or a Provider fact that the Conversation is unrecoverable.
246
+ return mapped("activation.ended");
241
247
  default:
242
248
  throw new Error(`Claude Code Driver does not support Hook event: ${name}.`);
243
249
  }
@@ -291,7 +297,7 @@ function mapCodexHook(name, payload, occurrenceId) {
291
297
  case "Stop":
292
298
  return mapped("turn.completed", optionalSummary(payload));
293
299
  case "SessionEnd":
294
- return [mapped("session.ended"), mapped("activation.ended")];
300
+ return mapped("activation.ended");
295
301
  default:
296
302
  throw new Error(`Codex Driver does not support Hook event: ${name}.`);
297
303
  }
@@ -376,7 +382,8 @@ function claudeFailure(payload) {
376
382
  const code = firstIdentity(payload, ["error"], "Claude StopFailure error");
377
383
  const details = optionalText(payload.error_details);
378
384
  const lastOutput = optionalText(payload.last_assistant_message);
379
- const parsed = parseClaudeError(code, details);
385
+ const raw = serializeAgentErrorRaw(payload);
386
+ const classification = mapClaudeAgentError({ message: code, raw });
380
387
  const retryAfterMs = typeof payload.retry_after_ms === "number"
381
388
  && Number.isSafeInteger(payload.retry_after_ms)
382
389
  && payload.retry_after_ms > 0
@@ -384,14 +391,19 @@ function claudeFailure(payload) {
384
391
  : undefined;
385
392
  return {
386
393
  failure: {
387
- ...(parsed.code !== "unknown" ? { errorCode: parsed.code } : {}),
388
- code,
389
- ...(details === undefined ? {} : { details }),
394
+ error: standardAgentError({
395
+ source: "provider",
396
+ phase: "turn-execute",
397
+ classification,
398
+ message: code,
399
+ raw,
400
+ inputDisposition: "accepted",
401
+ ...(retryAfterMs === undefined ? {} : { retryAfterMs })
402
+ }),
390
403
  ...(lastOutput === undefined ? {} : { lastOutput }),
391
404
  ...(payload.run_terminal === true || payload.unrecoverable === true
392
405
  ? { runTerminal: true }
393
- : {}),
394
- ...(retryAfterMs === undefined ? {} : { retryAfterMs })
406
+ : {})
395
407
  },
396
408
  summary: [
397
409
  "Agent turn failed.",
@@ -0,0 +1,150 @@
1
+ /** Provider-specific Claude Code failure recognition. */
2
+ export function mapClaudeAgentError(input) {
3
+ const text = `${input.message}\n${input.raw}`;
4
+ if (/^server_error$/iu.test(input.message) || /\bapi_error\b/iu.test(text)) {
5
+ return recoverable("availability", "provider.server-error");
6
+ }
7
+ if (/^overloaded_error$/iu.test(input.message) || /\boverloaded\b/iu.test(text)) {
8
+ return recoverable("availability", "provider.overloaded");
9
+ }
10
+ if (/^rate_limit_error$/iu.test(input.message) || /rate[\s_-]?limit/iu.test(text)) {
11
+ return recoverable("rate-limit", "provider.rate-limit");
12
+ }
13
+ if (/^(authentication_error|permission_error)$/iu.test(input.message)) {
14
+ return classification("access", "provider.access-denied");
15
+ }
16
+ if (/^not_found_error$/iu.test(input.message)) {
17
+ return sessionUnavailable("provider.session-not-found");
18
+ }
19
+ return mapSharedAgentError(text, "claude-code");
20
+ }
21
+ /** Provider-specific Codex failure recognition. */
22
+ export function mapCodexAgentError(input) {
23
+ const text = `${input.message}\n${input.raw}`;
24
+ if (/^server_error$/iu.test(input.message)) {
25
+ return recoverable("availability", "provider.server-error");
26
+ }
27
+ if (/^overloaded_error$/iu.test(input.message)
28
+ || /selected model is at capacity/iu.test(text)
29
+ || /\bmodel\b.*\bat capacity\b/iu.test(text)) {
30
+ return recoverable("availability", "provider.model-capacity");
31
+ }
32
+ if (/^rate_limit_error$/iu.test(input.message)) {
33
+ return recoverable("rate-limit", "provider.rate-limit");
34
+ }
35
+ if (/^(authentication_error|permission_error)$/iu.test(input.message)) {
36
+ return classification("access", "provider.access-denied");
37
+ }
38
+ if (/\[codex:(unrecognized_model|invalid_model|model_not_found)\]/iu.test(text)
39
+ || /model.*not supported|invalid.*model|model.*not found/iu.test(text)) {
40
+ return classification("invalid-request", "provider.invalid-model");
41
+ }
42
+ if (/stream disconnected/iu.test(text)) {
43
+ return recoverable("transport", "transport.stream-disconnected");
44
+ }
45
+ return mapSharedAgentError(text, "codex");
46
+ }
47
+ function mapSharedAgentError(text, namespace) {
48
+ if (/stream error:.*INTERNAL_ERROR/iu.test(text)) {
49
+ return recoverable("transport", "transport.stream-internal-error");
50
+ }
51
+ if (/stream error:.*PROTOCOL_ERROR/iu.test(text)) {
52
+ return recoverable("transport", "transport.stream-protocol-error");
53
+ }
54
+ if (/stream error/iu.test(text))
55
+ return recoverable("transport", "transport.stream-error");
56
+ if (/\b429\b/u.test(text) || /rate[\s_-]?limit/iu.test(text)) {
57
+ return recoverable("rate-limit", "provider.rate-limit");
58
+ }
59
+ if (/\b40[0-9]\b/u.test(text))
60
+ return classification("invalid-request", "provider.http-4xx");
61
+ if (/\b50[0-9]\b/u.test(text)
62
+ || /bad gateway|service unavailable|temporarily unavailable/iu.test(text)) {
63
+ return recoverable("availability", "provider.http-5xx");
64
+ }
65
+ if (/server[\s_-]?error/iu.test(text)) {
66
+ return recoverable("availability", "provider.server-error");
67
+ }
68
+ if (/\boverloaded\b/iu.test(text)) {
69
+ return recoverable("availability", "provider.overloaded");
70
+ }
71
+ if (/gateway timeout|timed?[ -]?out|etimedout/iu.test(text)) {
72
+ return recoverable("transport", "transport.timeout");
73
+ }
74
+ if (/connection[\s_-]?reset|econnreset|socket hang up/iu.test(text)) {
75
+ return recoverable("transport", "transport.connection-reset");
76
+ }
77
+ if (/connection[\s_-]?lost/iu.test(text)) {
78
+ return recoverable("transport", "transport.connection-lost");
79
+ }
80
+ if (/\beconnrefused\b|connection refused|connect[^\n]*refused/iu.test(text)) {
81
+ return recoverable("transport", "transport.connection-refused");
82
+ }
83
+ if (/\bepipe\b|broken pipe/iu.test(text)) {
84
+ return recoverable("transport", "transport.broken-pipe");
85
+ }
86
+ if (/\b(?:enotfound|eai_again)\b|dns[^\n]*(?:failed|unavailable)/iu.test(text)) {
87
+ return recoverable("transport", "transport.name-resolution");
88
+ }
89
+ if (/\benoent\b[^\n]*(?:socket|agent-host)|no such file[^\n]*(?:socket|agent-host)/iu.test(text)) {
90
+ return recoverable("transport", "transport.endpoint-missing");
91
+ }
92
+ if (/delivery[^\n]*unknown|acknowledgement[^\n]*unknown/iu.test(text)) {
93
+ return Object.freeze({
94
+ category: "transport",
95
+ code: "transport.delivery-unknown",
96
+ inputDisposition: "unknown",
97
+ sessionDisposition: "recoverable"
98
+ });
99
+ }
100
+ if (/unsettled turn|turn[^\n]*busy|session[^\n]*busy|writer fence|writer authority|human writer/iu.test(text)) {
101
+ return Object.freeze({
102
+ category: "conflict",
103
+ code: "runtime.session-busy",
104
+ inputDisposition: "not-accepted",
105
+ sessionDisposition: "recoverable"
106
+ });
107
+ }
108
+ if (/cyber[_-]?policy|policy[\s_-]?violation|usage[\s_-]?policy|content[\s_-]?policy|safety[\s_-]?policy/iu.test(text)) {
109
+ return classification("access", "provider.policy-denied");
110
+ }
111
+ if (/maximum context length|context length exceeded|context window (?:is )?(?:full|exceeded)|prompt (?:is )?too long|too many tokens/iu.test(text)) {
112
+ return classification("context", "provider.context-capacity");
113
+ }
114
+ if (/session[\s_-]?not[\s_-]?found|no[\s_-]?such[\s_-]?(?:session|thread)|thread[\s_-]?not[\s_-]?found/iu.test(text)) {
115
+ return sessionUnavailable("provider.session-not-found");
116
+ }
117
+ if (/session[\s_-]?(?:has[\s_-]?)?expired/iu.test(text)) {
118
+ return sessionUnavailable("provider.session-expired");
119
+ }
120
+ if (/session[\s_-]?(?:has[\s_-]?)?ended/iu.test(text)) {
121
+ return sessionUnavailable("provider.session-ended");
122
+ }
123
+ if (/process[\s_-]?exited/iu.test(text)) {
124
+ return recoverable("runtime", "runtime.process-exited");
125
+ }
126
+ if (/invalid[\s_-]?request|validation[\s_-]?error|bad[\s_-]?request|unknown[\s_-]?(?:flag|tool|argument)|unexpected argument|invalid schema/iu.test(text)
127
+ || (namespace === "codex"
128
+ ? /\[codex:(?:unrecognized_model|invalid_model|model_not_found)\]/iu.test(text)
129
+ : /\[claude-code:(?:unrecognized_model|invalid_model|model_not_found)\]/iu.test(text))) {
130
+ return classification("invalid-request", "provider.invalid-request");
131
+ }
132
+ return classification("unknown", "unknown");
133
+ }
134
+ function classification(category, code) {
135
+ return Object.freeze({ category, code });
136
+ }
137
+ function recoverable(category, code) {
138
+ return Object.freeze({
139
+ category,
140
+ code,
141
+ sessionDisposition: "recoverable"
142
+ });
143
+ }
144
+ function sessionUnavailable(code) {
145
+ return Object.freeze({
146
+ category: "session",
147
+ code,
148
+ sessionDisposition: "unrecoverable"
149
+ });
150
+ }
@@ -304,21 +304,16 @@ export function assertExactTaskRuntimeState(runtime, store, options = {}) {
304
304
  && isRuntimeLaunchReservation(lifecycleMailbox?.processing, runtime.launchId);
305
305
  const sessionLaunch = runtime.launchId !== undefined
306
306
  && session?.launchId === runtime.launchId;
307
- const executionRef = lifecycleMailbox?.processing?.executionRef;
308
307
  const preallocated = options.preallocatedDriverSessionReservation;
309
- const exactRunLaunchReservation = runtime.runId !== undefined
310
- && runtime.launchId !== undefined
308
+ const exactHostLaunchReservation = runtime.launchId !== undefined
311
309
  && reservation
312
- && !hasRuntimeCleanupObligation(lifecycleMailbox)
313
- && executionRef?.type === "run"
314
- && executionRef.taskId === runtime.taskId
315
- && executionRef.id === runtime.runId;
316
- const terminalSessionReplacementReservation = exactRunLaunchReservation
310
+ && !hasRuntimeCleanupObligation(lifecycleMailbox);
311
+ const terminalSessionReplacementReservation = exactHostLaunchReservation
317
312
  && session !== undefined
318
- && (session.status === "stopped" || session.status === "broken");
313
+ && session.status === "ended";
319
314
  const exactPreallocatedReservation = preallocated !== undefined
320
315
  && runtime.adapterId === preallocated.adapterId
321
- && exactRunLaunchReservation
316
+ && exactHostLaunchReservation
322
317
  && runtime.nativeSessionId !== undefined
323
318
  && (session === undefined || terminalSessionReplacementReservation)
324
319
  && runtime.nativeSessionId === nativeSessionIdForLaunch(preallocated.yuiHome, runtime.launchId, runtime.agentId, runtime.adapterId);
@@ -346,8 +341,7 @@ export function assertExactTaskRuntimeState(runtime, store, options = {}) {
346
341
  || session.adapterId !== runtime.adapterId
347
342
  || session.nativeSessionId !== runtime.nativeSessionId
348
343
  || session.launchId !== runtime.launchId
349
- || session.status === "stopped"
350
- || session.status === "broken"
344
+ || session.status === "ended"
351
345
  || session.effective.agentId !== runtime.agentId
352
346
  || session.effective.adapterId !== runtime.adapterId
353
347
  || canonicalPath(session.effective.workspace.root) !== runtime.workspace) {
@@ -17,5 +17,4 @@ export { codexNotificationBoundary, codexAppServerErrorIsMissing, CodexAppServer
17
17
  export { createProviderRuntimeBinding, acceptProviderTurn, beginProviderTurn, currentProviderActivation, currentProviderAuthority, currentProviderConversation, endProviderActivation, markProviderTurnDeliveryUnknown, rejectProviderTurn, settleProviderTurnSubmission, settleProviderTurn, startProviderActivation, supersedeProviderConversation, transferProviderAuthority, updateProviderConversationRecoverability, validateProviderRuntimeBinding } from "./providerRuntimeIdentity.js";
18
18
  export { FencedProviderControl } from "./providerControl.js";
19
19
  export { sameProviderAuthorityFence, validateProviderAuthorityFence } from "./providerAuthorityFence.js";
20
- export { decideProviderRecovery } from "./providerRecoveryDecision.js";
21
20
  export { reconcileSessionOwners } from "./sessionReconciliation.js";
@@ -98,22 +98,16 @@ function validateProviderControl(control) {
98
98
  if (control.mode !== "new" && control.mode !== "resume") {
99
99
  throw new Error("Agent Host Provider control mode is invalid.");
100
100
  }
101
- if (control.kind !== "new" && control.kind !== "resume" && control.kind !== "ensure") {
101
+ if (control.kind !== "start" && control.kind !== "restore") {
102
102
  throw new Error("Agent Host Provider control kind is invalid.");
103
103
  }
104
- if ((control.kind === "new") !== (control.mode === "new")) {
104
+ if ((control.kind === "start") !== (control.mode === "new")) {
105
105
  throw new Error("Agent Host Provider control kind does not match its transport mode.");
106
106
  }
107
- if (control.kind !== "ensure" && control.initialTurn === undefined) {
108
- throw new Error("Managed Provider new/resume launch requires its initial Turn.");
107
+ if (control.kind !== "restore" && "ownedTurn" in control) {
108
+ throw new Error("Only Session restore can reconcile an owned Turn.");
109
109
  }
110
- if (control.kind === "ensure" && control.initialTurn !== undefined) {
111
- throw new Error("Managed Provider ensure launch cannot carry a new Turn.");
112
- }
113
- if (control.kind !== "ensure" && "ownedTurn" in control) {
114
- throw new Error("Only a managed Provider ensure launch can recover an owned Turn.");
115
- }
116
- if (control.kind === "ensure" && control.ownedTurn !== undefined) {
110
+ if (control.kind === "restore" && control.ownedTurn !== undefined) {
117
111
  if (control.adapterId !== "codex") {
118
112
  throw new Error("Only Managed Codex can recover an owned Turn across client attachment.");
119
113
  }
@@ -135,14 +129,6 @@ function validateProviderControl(control) {
135
129
  }
136
130
  }
137
131
  validateProviderAuthorityFence(control.authority);
138
- if (control.initialTurn !== undefined) {
139
- text(control.initialTurn.attemptId, "Provider input attemptId");
140
- if (typeof control.initialTurn.boundedText !== "string"
141
- || control.initialTurn.boundedText.includes("\0")
142
- || Buffer.byteLength(control.initialTurn.boundedText, "utf8") > 32 * 1024) {
143
- throw new Error("Agent Host Provider input must be bounded bootstrap text.");
144
- }
145
- }
146
132
  }
147
133
  function validateCodexThreadOptions(options) {
148
134
  if (options === null || typeof options !== "object" || Array.isArray(options)) {
@@ -2,6 +2,7 @@ import { mailboxHasWork } from "../coordination/workMailbox.js";
2
2
  export const RUNTIME_LIFECYCLE_OWNER = "runtime-lifecycle";
3
3
  export const RUNTIME_LAUNCH_RESERVED_REASON = "runtime-launch-reserved";
4
4
  export const RUNTIME_CLEANUP_REQUIRED_REASON = "runtime-cleanup-required";
5
+ export const RUNTIME_HOST_DETACH_REQUIRED_REASON = "runtime-host-detach-required";
5
6
  /**
6
7
  * A Role runtime lifecycle lane already holds an in-flight operation (a
7
8
  * launch reservation or a cleanup obligation). This is scheduler
@@ -42,10 +43,25 @@ export function hasRuntimeLaunchReservation(mailbox) {
42
43
  return isRuntimeLaunchReservation(mailbox?.processing);
43
44
  }
44
45
  export function hasRuntimeCleanupObligation(mailbox) {
45
- const pending = mailbox?.pending.normal;
46
- return pending?.reasons.includes(RUNTIME_CLEANUP_REQUIRED_REASON) === true
47
- || (!isRuntimeLaunchReservation(mailbox?.processing)
48
- && mailbox?.processing?.batch.reasons.includes(RUNTIME_CLEANUP_REQUIRED_REASON) === true);
46
+ return runtimeCleanupDisposition(mailbox) !== null;
47
+ }
48
+ /** Explicit Session end dominates a coalesced physical Host detach request. */
49
+ export function runtimeCleanupDisposition(mailbox) {
50
+ const reasons = [
51
+ ...(mailbox?.pending.normal?.reasons ?? []),
52
+ ...(!isRuntimeLaunchReservation(mailbox?.processing)
53
+ ? mailbox?.processing?.batch.reasons ?? []
54
+ : [])
55
+ ];
56
+ if (reasons.includes(RUNTIME_CLEANUP_REQUIRED_REASON))
57
+ return "end-session";
58
+ if (reasons.includes(RUNTIME_HOST_DETACH_REQUIRED_REASON))
59
+ return "detach-host";
60
+ return null;
61
+ }
62
+ export function isRuntimeCleanupReason(reason) {
63
+ return reason === RUNTIME_CLEANUP_REQUIRED_REASON
64
+ || reason === RUNTIME_HOST_DETACH_REQUIRED_REASON;
49
65
  }
50
66
  export function hasRuntimeLifecycleWork(mailbox) {
51
67
  return mailbox !== null && mailboxHasWork(mailbox);
@@ -227,7 +227,7 @@ export function rejectProviderTurn(raw, input) {
227
227
  }
228
228
  });
229
229
  }
230
- /** Resolves an Agent Host submission exactly once and accepts an exact acknowledgement replay. */
230
+ /** Resolves an Agent Host submission; an unknown delivery may later gain exact negative evidence. */
231
231
  export function settleProviderTurnSubmission(raw, input) {
232
232
  const binding = validateProviderRuntimeBinding(raw);
233
233
  const attemptId = identity(input.attemptId, "Provider input attempt id");
@@ -236,7 +236,8 @@ export function settleProviderTurnSubmission(raw, input) {
236
236
  }
237
237
  if (binding.turn.status === input.status)
238
238
  return binding;
239
- if (binding.turn.status !== "submitting") {
239
+ if (binding.turn.status !== "submitting"
240
+ && !(binding.turn.status === "delivery-unknown" && input.status === "rejected")) {
240
241
  throw new Error("Provider Turn does not match a resolvable delivery state.");
241
242
  }
242
243
  return input.status === "delivery-unknown"