@zq-silk/yui 0.13.8 → 0.13.9
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.
- package/README.md +9 -8
- package/dist/cli/commandCatalog.js +21 -2
- package/dist/cli.js +42 -13
- package/dist/commands/agentCommands.js +1 -1
- package/dist/commands/configCommands.js +1 -86
- package/dist/commands/executionAuditCommands.js +17 -16
- package/dist/commands/globalRoleCommands.js +4 -4
- package/dist/commands/sessionCommands.js +2 -6
- package/dist/commands/taskActor.js +1 -2
- package/dist/commands/taskCommands.js +92 -9
- package/dist/commands/taskRoleRuntimeStatus.js +3 -3
- package/dist/config/configCatalog.js +1 -6
- package/dist/config/yuiConfig.js +0 -79
- package/dist/controller/clientRuntime.js +40 -3
- package/dist/controller/controller.js +29 -52
- package/dist/controller/fileSchedulerStoreAdapter.js +305 -1056
- package/dist/controller/runtime.js +12 -5
- package/dist/controller/runtimeHookRunFence.js +3 -9
- package/dist/controller/runtimeLaunchCoordinator.js +44 -67
- package/dist/controller/structuredProviderObservation.js +18 -5
- package/dist/coordination/workMailbox.js +4 -4
- package/dist/execution/executionHealth.js +1 -1
- package/dist/executor/agentExecutor.js +92 -68
- package/dist/executor/executorRegistry.js +8 -20
- package/dist/executor/fileRoleLaunchPlanner.js +24 -90
- package/dist/executor/turnCompletion.js +5 -5
- package/dist/lifecycle/exactRunTerminalization.js +1 -1
- package/dist/observability/executionAudit.js +40 -94
- package/dist/operator/operatorSessionHistory.js +7 -5
- package/dist/role/role.js +1 -1
- package/dist/run/agentRun.js +4 -54
- package/dist/runtime/agentDriver.js +2 -0
- package/dist/runtime/agentError.js +114 -0
- package/dist/runtime/agentHost.js +55 -82
- package/dist/runtime/builtinAgentDrivers.js +21 -9
- package/dist/runtime/builtinAgentErrorMappers.js +150 -0
- package/dist/runtime/exactControlPlane.js +6 -12
- package/dist/runtime/index.js +0 -1
- package/dist/runtime/launchBroker.js +5 -19
- package/dist/runtime/lifecycleReservation.js +20 -4
- package/dist/runtime/providerRuntimeIdentity.js +3 -2
- package/dist/runtime/runtimeBinding.js +0 -27
- package/dist/runtime/runtimeObservation.js +7 -16
- package/dist/runtime/runtimeSessionCandidate.js +3 -10
- package/dist/runtime/sessionLaunchRequest.js +1 -2
- package/dist/runtime/sessionReconciliation.js +2 -2
- package/dist/runtime/structuredProviderHost.js +44 -79
- package/dist/runtime/taskRuntimeIsolation.js +0 -7
- package/dist/runtime/tmuxAdapters.js +6 -49
- package/dist/scheduler/activeRoleRunDelivery.js +218 -168
- package/dist/scheduler/leaderWakeupProcessor.js +126 -86
- package/dist/scheduler/roleRunLiveness.js +4 -1
- package/dist/scheduler/roleRunStall.js +7 -11
- package/dist/scheduler/wakeReason.js +4 -0
- package/dist/storage/migration/productionRegistry.js +332 -0
- package/dist/storage/sqliteSchema.js +54 -2
- package/dist/storage/sqliteStore.js +11 -44
- package/dist/storage/taskStore.js +7 -35
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +30 -8
- package/skills/yui-operator/SKILL.md +14 -6
- package/skills/yui-runtime/SKILL.md +6 -4
- package/dist/lifecycle/providerErrorClass.js +0 -152
- package/dist/run/providerRetry.js +0 -226
- package/dist/run/providerRetryConfig.js +0 -27
- package/dist/runtime/providerErrorCodes.js +0 -278
- 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
|
-
|
|
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: "
|
|
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 === "
|
|
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 === "
|
|
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
|
-
|
|
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 =
|
|
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:
|
|
462
|
-
conversationId:
|
|
463
|
-
...(
|
|
464
|
-
? {
|
|
465
|
-
:
|
|
466
|
-
|
|
467
|
-
:
|
|
468
|
-
|
|
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
|
|
492
|
-
|
|
493
|
-
|
|
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
|
-
|
|
511
|
-
|
|
512
|
-
|
|
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
|
|
489
|
+
throw new ProviderTurnBusyError("Agent Host still owns an unsettled Provider Turn.", request.turn.attemptId, activeNativeTurnId);
|
|
540
490
|
}
|
|
541
|
-
activeTurnPayload =
|
|
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
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
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
|
-
|
|
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(
|
|
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 {
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
388
|
-
|
|
389
|
-
|
|
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
|
|
310
|
-
&& runtime.launchId !== undefined
|
|
308
|
+
const exactHostLaunchReservation = runtime.launchId !== undefined
|
|
311
309
|
&& reservation
|
|
312
|
-
&& !hasRuntimeCleanupObligation(lifecycleMailbox)
|
|
313
|
-
|
|
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
|
-
&&
|
|
313
|
+
&& session.status === "ended";
|
|
319
314
|
const exactPreallocatedReservation = preallocated !== undefined
|
|
320
315
|
&& runtime.adapterId === preallocated.adapterId
|
|
321
|
-
&&
|
|
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 === "
|
|
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) {
|
package/dist/runtime/index.js
CHANGED
|
@@ -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 !== "
|
|
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 === "
|
|
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 !== "
|
|
108
|
-
throw new Error("
|
|
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 === "
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
|
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"
|