@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
package/dist/config/yuiConfig.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { supportedAgentAdapterIds } from "../agent/adapterCatalog.js";
|
|
2
1
|
import { DEFAULT_RUN_CAP, DEFAULT_TERMINAL_KEEP, MAX_RUN_CAP } from "../telemetry/telemetryConfig.js";
|
|
3
2
|
import { DEFAULT_RUNTIME_HEALTH_POLICY } from "../runtime/runtimeHealthPolicy.js";
|
|
4
3
|
export const DEFAULT_RECONCILIATION_INTERVAL_SECONDS = 120;
|
|
@@ -75,84 +74,6 @@ export function resolveLeaderNextActionMode(value) {
|
|
|
75
74
|
}
|
|
76
75
|
return normalized;
|
|
77
76
|
}
|
|
78
|
-
// ── Issue 01: Provider retry ──────────────────────────────────────────────
|
|
79
|
-
export const PROVIDER_RETRY_MODES = ["off", "shadow", "enforce"];
|
|
80
|
-
export const DEFAULT_PROVIDER_RETRY_MODE = "enforce";
|
|
81
|
-
export const DEFAULT_PROVIDER_RETRY_DELAYS_SECONDS = Object.freeze([2, 5, 15]);
|
|
82
|
-
export const DEFAULT_PROVIDER_RETRY_MAX_WINDOW_SECONDS = 600;
|
|
83
|
-
export const MAX_PROVIDER_RETRY_ATTEMPTS = 10;
|
|
84
|
-
export function resolveProviderRetryMode(value) {
|
|
85
|
-
if (value === undefined || value === null)
|
|
86
|
-
return DEFAULT_PROVIDER_RETRY_MODE;
|
|
87
|
-
if (typeof value !== "string") {
|
|
88
|
-
throw new TypeError("providerRetryMode must be off, shadow, or enforce.");
|
|
89
|
-
}
|
|
90
|
-
const normalized = value.trim().toLowerCase();
|
|
91
|
-
if (normalized.length === 0)
|
|
92
|
-
return DEFAULT_PROVIDER_RETRY_MODE;
|
|
93
|
-
if (!PROVIDER_RETRY_MODES.includes(normalized)) {
|
|
94
|
-
throw new TypeError("providerRetryMode must be off, shadow, or enforce.");
|
|
95
|
-
}
|
|
96
|
-
return normalized;
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Resolves the adapter list. `["all"]` or undefined means every supported
|
|
100
|
-
* adapter; an empty array disables in-place retry.
|
|
101
|
-
*/
|
|
102
|
-
export function resolveProviderRetryAdapters(value) {
|
|
103
|
-
if (value === undefined || value === null) {
|
|
104
|
-
return [...supportedAgentAdapterIds()];
|
|
105
|
-
}
|
|
106
|
-
if (!Array.isArray(value)) {
|
|
107
|
-
throw new TypeError("providerRetryAdapters must be an array of adapter ids.");
|
|
108
|
-
}
|
|
109
|
-
const supported = new Set(supportedAgentAdapterIds());
|
|
110
|
-
const adapters = [];
|
|
111
|
-
for (const raw of value) {
|
|
112
|
-
if (typeof raw !== "string") {
|
|
113
|
-
throw new TypeError("providerRetryAdapters entries must be strings.");
|
|
114
|
-
}
|
|
115
|
-
const token = raw.trim().toLowerCase();
|
|
116
|
-
if (token === "all") {
|
|
117
|
-
for (const adapter of supportedAgentAdapterIds()) {
|
|
118
|
-
if (!adapters.includes(adapter))
|
|
119
|
-
adapters.push(adapter);
|
|
120
|
-
}
|
|
121
|
-
continue;
|
|
122
|
-
}
|
|
123
|
-
if (!/^[a-z0-9][a-z0-9._-]*$/u.test(token)) {
|
|
124
|
-
throw new TypeError(`Invalid Provider retry adapter: ${token}.`);
|
|
125
|
-
}
|
|
126
|
-
if (!supported.has(token)) {
|
|
127
|
-
throw new TypeError(`Unknown Provider retry adapter: ${token}.`);
|
|
128
|
-
}
|
|
129
|
-
if (!adapters.includes(token))
|
|
130
|
-
adapters.push(token);
|
|
131
|
-
}
|
|
132
|
-
return adapters;
|
|
133
|
-
}
|
|
134
|
-
export function resolveProviderRetryDelaysSeconds(value) {
|
|
135
|
-
if (value === undefined || value === null)
|
|
136
|
-
return [...DEFAULT_PROVIDER_RETRY_DELAYS_SECONDS];
|
|
137
|
-
if (!Array.isArray(value) || value.length < 1 || value.length > MAX_PROVIDER_RETRY_ATTEMPTS) {
|
|
138
|
-
throw new TypeError(`providerRetryDelaysSeconds must contain 1-${MAX_PROVIDER_RETRY_ATTEMPTS} positive integers.`);
|
|
139
|
-
}
|
|
140
|
-
const delays = value.map((entry) => {
|
|
141
|
-
if (typeof entry !== "number" || !Number.isSafeInteger(entry) || entry < 1 || entry > 600) {
|
|
142
|
-
throw new TypeError("providerRetryDelaysSeconds entries must be integers from 1 to 600.");
|
|
143
|
-
}
|
|
144
|
-
return entry;
|
|
145
|
-
});
|
|
146
|
-
for (let index = 1; index < delays.length; index += 1) {
|
|
147
|
-
if (delays[index] < delays[index - 1]) {
|
|
148
|
-
throw new TypeError("providerRetryDelaysSeconds must be ordered from shortest to longest.");
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
return delays;
|
|
152
|
-
}
|
|
153
|
-
export function resolveProviderRetryMaxWindowSeconds(value) {
|
|
154
|
-
return resolveBoundedPositiveInteger(value, DEFAULT_PROVIDER_RETRY_MAX_WINDOW_SECONDS, 1, Number.MAX_SAFE_INTEGER, "providerRetryMaxWindowSeconds");
|
|
155
|
-
}
|
|
156
77
|
// ── Executable paths ──────────────────────────────────────────────────────
|
|
157
78
|
export function resolveTmuxBin(value) {
|
|
158
79
|
if (value === undefined || value === null)
|
|
@@ -466,11 +466,48 @@ export class FileTaskWorkflowRuntime {
|
|
|
466
466
|
throw new Error(`Role runtime did not stop: ${target.taskId}/${target.roleName}.`);
|
|
467
467
|
}
|
|
468
468
|
const session = this.store.getRoleSession(target.taskId, target.roleName);
|
|
469
|
-
if (session !== null && session.status !== "
|
|
469
|
+
if (session !== null && session.status !== "ended") {
|
|
470
470
|
throw new Error(`Role runtime session is still active: ${target.taskId}/${target.roleName}.`);
|
|
471
471
|
}
|
|
472
472
|
}
|
|
473
473
|
}
|
|
474
|
+
/** Stops only the exact idle Session observed by an Agent command. */
|
|
475
|
+
async stopExactTaskRoleSession(input) {
|
|
476
|
+
if (this.store.getActiveAgentRun(input.taskId, input.roleName) !== null) {
|
|
477
|
+
throw new Error(`Role has an active Run: ${input.taskId}/${input.roleName}.`);
|
|
478
|
+
}
|
|
479
|
+
const owner = {
|
|
480
|
+
scope: "task",
|
|
481
|
+
taskId: input.taskId,
|
|
482
|
+
roleName: input.roleName
|
|
483
|
+
};
|
|
484
|
+
const target = this.schedulerStore.enqueueRuntimeCleanup(owner, new Date(), {
|
|
485
|
+
owner,
|
|
486
|
+
agentId: input.agentId,
|
|
487
|
+
adapterId: input.adapterId,
|
|
488
|
+
nativeSessionId: input.nativeSessionId,
|
|
489
|
+
...(input.launchId === undefined ? {} : { launchId: input.launchId }),
|
|
490
|
+
sessionUpdatedAt: input.sessionUpdatedAt
|
|
491
|
+
});
|
|
492
|
+
if (target === null) {
|
|
493
|
+
throw new Error(`Role Session changed before its exact stop was reserved: ${input.taskId}/${input.roleName}.`);
|
|
494
|
+
}
|
|
495
|
+
await callFileTaskController(this.home, "scheduler.scan", {}, {
|
|
496
|
+
...this.clientOptions,
|
|
497
|
+
requestTimeoutMs: LIFECYCLE_REQUEST_TIMEOUT_MS
|
|
498
|
+
});
|
|
499
|
+
if (hasRuntimeLifecycleWork(this.store.getWorkMailbox(target))) {
|
|
500
|
+
throw new Error(`Role runtime did not stop: ${input.taskId}/${input.roleName}.`);
|
|
501
|
+
}
|
|
502
|
+
const session = this.store.getRoleSession(input.taskId, input.roleName);
|
|
503
|
+
if (session !== null
|
|
504
|
+
&& session.status !== "ended"
|
|
505
|
+
&& session.agentId === input.agentId
|
|
506
|
+
&& session.adapterId === input.adapterId
|
|
507
|
+
&& session.nativeSessionId === input.nativeSessionId) {
|
|
508
|
+
throw new Error(`Role runtime session is still active: ${input.taskId}/${input.roleName}.`);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
474
511
|
/** Wait until every cancellation requested by Task execution stop is physically settled. */
|
|
475
512
|
async stopTaskDurableJobs(taskId) {
|
|
476
513
|
const deadline = Date.now() + LIFECYCLE_REQUEST_TIMEOUT_MS;
|
|
@@ -506,7 +543,7 @@ export class FileTaskWorkflowRuntime {
|
|
|
506
543
|
+ activeJobs.map(({ id }) => id).join(", "));
|
|
507
544
|
}
|
|
508
545
|
const liveSessions = this.store.listRoleSessionSets(taskId).flatMap((sessions) => (Object.values(sessions.sessions)
|
|
509
|
-
.filter((session) => session.status
|
|
546
|
+
.filter((session) => session.status === "active")
|
|
510
547
|
.map((session) => `${sessions.owner.roleName}/${session.agentId}/${session.status}`)));
|
|
511
548
|
if (liveSessions.length > 0) {
|
|
512
549
|
throw new WorkspaceCleanupBlockedError("physical-resource-live", `task:${taskId}`, true, `Task physical resources are not released: current Role Session state is still live: `
|
|
@@ -556,7 +593,7 @@ export class FileTaskWorkflowRuntime {
|
|
|
556
593
|
}
|
|
557
594
|
const sessions = this.store.getGlobalRoleSessionSet(roleName);
|
|
558
595
|
const active = sessions?.sessions[sessions.activeAgentId];
|
|
559
|
-
if (active !== undefined && active.status !== "
|
|
596
|
+
if (active !== undefined && active.status !== "ended") {
|
|
560
597
|
throw new Error(`Global Role runtime session is still active: ${roleName}.`);
|
|
561
598
|
}
|
|
562
599
|
}
|
|
@@ -57,6 +57,11 @@ export async function runControllerSchedulerPass(store, delivery, now, workspace
|
|
|
57
57
|
// event-loop turn. Give already-written requests a poll boundary before the
|
|
58
58
|
// next durable phase; later phases retain their existing CAS fences.
|
|
59
59
|
await controlEventLoopTurn();
|
|
60
|
+
// A dormant Session may still name a Host that disappeared after its last
|
|
61
|
+
// Provider Turn. Detach that disposable Host before claiming the next Run,
|
|
62
|
+
// so retained Agent intent restores the same Session instead of first
|
|
63
|
+
// connecting to a known-dead control socket.
|
|
64
|
+
await reconcileDormantRuntimeOwners(store, delivery, lifecycleHost, scope, now, blockedTaskIds);
|
|
60
65
|
const failedCleanupRoles = await processSelectedRoleRuntimeCleanups(store, delivery, lifecycleHost, scope, now, runtimeCleanupOutcomes, blockedTaskIds);
|
|
61
66
|
const roleSelection = selectionWithoutFailedCleanupRoles(store, selection, failedCleanupRoles);
|
|
62
67
|
const availableWakeupSelection = () => (leaderWakeFence?.() === true
|
|
@@ -79,7 +84,6 @@ export async function runControllerSchedulerPass(store, delivery, now, workspace
|
|
|
79
84
|
// Issue 04: reopen due in-place Provider retries on their original
|
|
80
85
|
// Sessions before delivery, so the existing delivery path re-pushes the
|
|
81
86
|
// exact same input in this same pass.
|
|
82
|
-
resolveDueProviderRetries(store, roleSelection, now);
|
|
83
87
|
const activeRunDeliveries = await processActiveRoleRunDeliveries(store, delivery, now, roleSelection, inputDeliveryRecoveryCutoff);
|
|
84
88
|
await controlEventLoopTurn();
|
|
85
89
|
const unsettledRunRefs = new Set(activeRunDeliveries.flatMap((result) => (result.reason === "delivery-uncertain" || result.terminalFailure !== undefined
|
|
@@ -253,10 +257,10 @@ function queueSelectedCompletedTaskRuntimeCleanups(store, selection, now) {
|
|
|
253
257
|
}
|
|
254
258
|
}
|
|
255
259
|
function schedulerSessionRequiresRuntimeCleanup(session) {
|
|
256
|
-
if (session === null || session.status === "
|
|
260
|
+
if (session === null || session.status === "ended") {
|
|
257
261
|
return false;
|
|
258
262
|
}
|
|
259
|
-
return session.
|
|
263
|
+
return session.launchId !== undefined;
|
|
260
264
|
}
|
|
261
265
|
async function processSelectedRoleRuntimeCleanups(store, delivery, lifecycleHost, scope, now, outcomes, blockedTaskIds = new Set()) {
|
|
262
266
|
const targets = selectedRuntimeLifecycleTargets(store, scope, blockedTaskIds);
|
|
@@ -351,14 +355,18 @@ async function processSelectedRoleRuntimeCleanups(store, delivery, lifecycleHost
|
|
|
351
355
|
return failedRoles;
|
|
352
356
|
}
|
|
353
357
|
async function reconcileDormantRuntimeOwners(store, delivery, lifecycleHost, scope, now, blockedTaskIds = new Set()) {
|
|
354
|
-
if (
|
|
355
|
-
||
|
|
356
|
-
|| store.listDormantRuntimeOwners === undefined
|
|
357
|
-
|| store.markRuntimeOwnerStopped === undefined) {
|
|
358
|
+
if (lifecycleHost === undefined
|
|
359
|
+
|| store.listDormantRuntimeOwners === undefined) {
|
|
358
360
|
return;
|
|
359
361
|
}
|
|
360
|
-
const
|
|
361
|
-
|
|
362
|
+
const selectedOwners = scope.kind === "full"
|
|
363
|
+
? undefined
|
|
364
|
+
: new Set(selectedRuntimeLifecycleTargets(store, scope, blockedTaskIds)
|
|
365
|
+
.map((target) => runtimeOwnerIdentity(runtimeOwner(target))));
|
|
366
|
+
const candidates = store.listDormantRuntimeOwners().filter((candidate) => ((candidate.owner.scope !== "task"
|
|
367
|
+
|| !blockedTaskIds.has(candidate.owner.taskId))
|
|
368
|
+
&& (selectedOwners === undefined
|
|
369
|
+
|| selectedOwners.has(runtimeOwnerIdentity(candidate.owner)))));
|
|
362
370
|
if (candidates.length === 0)
|
|
363
371
|
return;
|
|
364
372
|
const owners = candidates.map((candidate) => candidate.owner);
|
|
@@ -391,17 +399,12 @@ async function reconcileDormantRuntimeOwners(store, delivery, lifecycleHost, sco
|
|
|
391
399
|
for (const candidate of candidates) {
|
|
392
400
|
if (byOwner.get(runtimeOwnerIdentity(candidate.owner))?.state
|
|
393
401
|
=== "stopped") {
|
|
394
|
-
if (candidate.
|
|
395
|
-
&& candidate.launchId !== undefined) {
|
|
402
|
+
if (candidate.launchId !== undefined) {
|
|
396
403
|
// The exact launch-owned resources must settle through the durable
|
|
397
|
-
// cleanup lane before its
|
|
398
|
-
//
|
|
399
|
-
//
|
|
400
|
-
store.
|
|
401
|
-
continue;
|
|
402
|
-
}
|
|
403
|
-
if (store.markRuntimeOwnerStopped(candidate, now)) {
|
|
404
|
-
forgetPreparedRuntimeOwner(delivery, candidate.owner);
|
|
404
|
+
// cleanup lane before its Host fact is detached. The resumable Session
|
|
405
|
+
// remains active; the candidate is the CAS fence against a concurrent
|
|
406
|
+
// Hook or replacement launch.
|
|
407
|
+
store.enqueueRuntimeHostDetach?.(candidate.owner, now, candidate);
|
|
405
408
|
}
|
|
406
409
|
}
|
|
407
410
|
}
|
|
@@ -419,20 +422,6 @@ function runtimeOwnerIdentity(owner) {
|
|
|
419
422
|
? `task\0${owner.taskId}\0${owner.roleName}`
|
|
420
423
|
: `global\0${owner.roleName}`;
|
|
421
424
|
}
|
|
422
|
-
/**
|
|
423
|
-
* Issue 04: reopens due in-place Provider retries on their original Native
|
|
424
|
-
* Sessions before the active-run delivery pass, so the existing delivery
|
|
425
|
-
* path re-pushes the exact same input in the same pass. A Run whose Session
|
|
426
|
-
* is proven dead terminalizes with a replacement blocker instead.
|
|
427
|
-
*/
|
|
428
|
-
function resolveDueProviderRetries(store, selection, now) {
|
|
429
|
-
if (typeof store.resolveDueProviderRetries !== "function")
|
|
430
|
-
return;
|
|
431
|
-
const selectedTaskIds = selectedTaskIdsForBoundedPass(store, selection);
|
|
432
|
-
if (selectedTaskIds?.size === 0)
|
|
433
|
-
return;
|
|
434
|
-
store.resolveDueProviderRetries(now, selectedTaskIds);
|
|
435
|
-
}
|
|
436
425
|
function selectedTaskIdsForBoundedPass(store, selection) {
|
|
437
426
|
if (!selection.full) {
|
|
438
427
|
if ((selection.blockedTaskIds?.size ?? 0) === 0)
|
|
@@ -1445,25 +1434,13 @@ export class FileTaskController {
|
|
|
1445
1434
|
}
|
|
1446
1435
|
if (this.#stopped)
|
|
1447
1436
|
return;
|
|
1448
|
-
const deadlines =
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
: []),
|
|
1456
|
-
// Issue 04 durable in-place retry timer: arm the Controller wake at the
|
|
1457
|
-
// earliest dispatch/deadline wake. Scheduled retries re-push on their
|
|
1458
|
-
// original Session; in-flight retries only wake at the episode deadline.
|
|
1459
|
-
// The projection is durable, so a restart resumes the lineage.
|
|
1460
|
-
...(typeof this.store.listPendingProviderRetries === "function"
|
|
1461
|
-
? this.store.listPendingProviderRetries()
|
|
1462
|
-
: []).map((retry) => ({
|
|
1463
|
-
key: `role:${encodeURIComponent(retry.taskId)}/${encodeURIComponent(retry.roleName)}`,
|
|
1464
|
-
at: Date.parse(retry.dueAt)
|
|
1465
|
-
}))
|
|
1466
|
-
];
|
|
1437
|
+
const deadlines = this.store.listOpenInputRequests()
|
|
1438
|
+
.flatMap((request) => request.policy.kind === "recommended"
|
|
1439
|
+
? [{
|
|
1440
|
+
key: `task:${encodeURIComponent(request.taskId)}`,
|
|
1441
|
+
at: Date.parse(request.policy.timeoutAt)
|
|
1442
|
+
}]
|
|
1443
|
+
: []);
|
|
1467
1444
|
const nearest = nearestDeadlineBatch(deadlines);
|
|
1468
1445
|
if (nearest === null)
|
|
1469
1446
|
return;
|