agentic-workflow-manager 9.12.1 → 9.12.3
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/dist/src/commands/job/index.js +61 -0
- package/dist/src/commands/watch/admission-recovery.js +161 -0
- package/dist/src/commands/watch/apply.js +51 -2
- package/dist/src/commands/watch/generations.js +1 -1
- package/dist/src/commands/watch/index.js +43 -2
- package/dist/src/commands/watch/supervisor.js +202 -64
- package/dist/src/core/journal/requests.js +6 -0
- package/dist/src/core/journal/types.js +78 -4
- package/dist/tests/commands/watch/admission-recovery.test.js +278 -0
- package/dist/tests/commands/watch/plan-lifecycle.test.js +47 -1
- package/dist/tests/commands/watch/reopened-196.test.js +749 -0
- package/dist/tests/commands/watch/supervisor-loop.test.js +111 -2
- package/package.json +1 -1
- package/prebuilds/darwin-arm64/secure_fs.node +0 -0
- package/prebuilds/darwin-x64/secure_fs.node +0 -0
- package/prebuilds/win32-arm64/secure_fs.node +0 -0
- package/prebuilds/win32-x64/secure_fs.node +0 -0
|
@@ -58,6 +58,7 @@ const currentnessCheck = __importStar(require("../../../src/core/currentness/che
|
|
|
58
58
|
const sensorCommands = __importStar(require("../../../src/commands/sensors/run"));
|
|
59
59
|
const preferencesConfig = __importStar(require("../../../src/utils/config"));
|
|
60
60
|
const providers_1 = require("../../../src/providers");
|
|
61
|
+
const adapterModule = __importStar(require("../../../src/core/journal/adapter"));
|
|
61
62
|
jest.setTimeout(60000);
|
|
62
63
|
// runSupervisorLoop's full external-controller lifecycle (spawn stub codex ->
|
|
63
64
|
// identity captured by the wrapper -> adopted via collectControllerGeneration's
|
|
@@ -93,6 +94,18 @@ const admitted = async () => ({
|
|
|
93
94
|
state: 'admitted', planState: 'valid', executionMode: 'desatendido',
|
|
94
95
|
journal: 'current', currentness: 'current', sensors: 'pass', diagnostics: [],
|
|
95
96
|
});
|
|
97
|
+
/** Lifecycle fixtures do not spawn a real controller. Explicitly adopt a
|
|
98
|
+
* simulated native identity before expecting job dispatch. */
|
|
99
|
+
function adoptFixtureController(repo) {
|
|
100
|
+
const state = (0, store_1.readJournal)(repo, 'main').state;
|
|
101
|
+
const gen = (0, generations_1.activeGeneration)(state);
|
|
102
|
+
gen.processRef = { pid: process.pid, processGroup: process.pid, startTime: 'fixture',
|
|
103
|
+
spawnNonce: gen.spawnNonce, argvDigest: gen.launchArgvDigest, psArgsDigest: 'b'.repeat(64) };
|
|
104
|
+
(0, store_1.writeJournal)(repo, 'main', state);
|
|
105
|
+
const adapter = adapterModule.adapterFor('codex');
|
|
106
|
+
jest.spyOn(adapterModule, 'adapterFor').mockReturnValue({ ...adapter,
|
|
107
|
+
activity: () => ({ cpuTime: '0', groupSize: 1 }), safeToReplace: () => 'indeterminate' });
|
|
108
|
+
}
|
|
96
109
|
/** Legacy fixtures predate compact-only admission; make their execution contract explicit. */
|
|
97
110
|
function initUnattendedFixture(repo) {
|
|
98
111
|
(0, init_1.initWatch)(repo, 'main');
|
|
@@ -266,6 +279,8 @@ describe('supervisor loop', () => {
|
|
|
266
279
|
try {
|
|
267
280
|
let sawContinueWithLiveJob = false;
|
|
268
281
|
let outcome = 'continue';
|
|
282
|
+
expect(await sup.tick()).toBe('continue');
|
|
283
|
+
adoptFixtureController(repo);
|
|
269
284
|
for (let i = 0; i < 400 && outcome !== 'complete'; i++) {
|
|
270
285
|
outcome = await sup.tick();
|
|
271
286
|
const s = (0, store_1.readJournal)(repo, 'main').state;
|
|
@@ -280,7 +295,8 @@ describe('supervisor loop', () => {
|
|
|
280
295
|
expect(final.cycle.status).toBe('COMPLETE');
|
|
281
296
|
expect(typeof final.cycle.completedAt).toBe('string');
|
|
282
297
|
expect(Object.values(final.jobs).every((j) => j.executionState === 'exited' && j.verdict === 'pass')).toBe(true);
|
|
283
|
-
expect(final.generations.every((entry) => entry.state === 'terminated'
|
|
298
|
+
expect(final.generations.every((entry) => entry.state === 'terminated'
|
|
299
|
+
&& (entry.processRef === undefined || entry.processRef.pid === process.pid))).toBe(true);
|
|
284
300
|
expect(controllerSpawnAttempts).toBeGreaterThan(0);
|
|
285
301
|
}
|
|
286
302
|
finally {
|
|
@@ -356,6 +372,8 @@ describe('supervisor loop', () => {
|
|
|
356
372
|
};
|
|
357
373
|
const sup = new supervisor_1.Supervisor(repo, 'main', cfg, lifecycleSpawner, undefined, admitted);
|
|
358
374
|
let outcome = 'continue';
|
|
375
|
+
expect(await sup.tick()).toBe('continue');
|
|
376
|
+
adoptFixtureController(repo);
|
|
359
377
|
for (let i = 0; i < 400 && outcome !== 'complete'; i++) {
|
|
360
378
|
outcome = await sup.tick();
|
|
361
379
|
await new Promise((r) => setTimeout(r, 50));
|
|
@@ -367,7 +385,10 @@ describe('supervisor loop', () => {
|
|
|
367
385
|
for (const t of final.tracks)
|
|
368
386
|
expect(['REMOVED', 'DECLARED']).toContain(t.phase);
|
|
369
387
|
const { refIsAlive } = require('../../../src/core/journal/process');
|
|
370
|
-
|
|
388
|
+
// The fixture adopts this Jest process as its controller; it is
|
|
389
|
+
// intentionally not killed on COMPLETE and remains alive on Windows.
|
|
390
|
+
expect(final.generations.every((entry) => entry.state === 'terminated'
|
|
391
|
+
&& (entry.processRef === undefined || entry.processRef.pid === process.pid || !refIsAlive(entry.processRef)))).toBe(true);
|
|
371
392
|
}
|
|
372
393
|
finally {
|
|
373
394
|
fs_1.default.rmSync(tracksRoot, { recursive: true, force: true });
|
|
@@ -416,9 +437,12 @@ describe('supervisor loop', () => {
|
|
|
416
437
|
});
|
|
417
438
|
let spawned = 0;
|
|
418
439
|
const sup = new supervisor_1.Supervisor(repo, 'main', supervisor_1.DEFAULT_SUPERVISOR_CONFIG, () => { spawned += 1; }, undefined, notCertified);
|
|
440
|
+
let now = Date.now();
|
|
441
|
+
jest.spyOn(Date, 'now').mockImplementation(() => now);
|
|
419
442
|
for (let i = 0; i < supervisor_1.MAX_SENSOR_DEFERRALS; i += 1) {
|
|
420
443
|
expect(await sup.tick()).toBe('continue');
|
|
421
444
|
expect((0, store_1.readJournal)(repo, 'main').state.cycle.status).toBe('IN_PROGRESS');
|
|
445
|
+
now += 5000;
|
|
422
446
|
}
|
|
423
447
|
// The verdict never settled, so the supervisor must stop pretending it might.
|
|
424
448
|
expect(await sup.tick()).toBe('custody');
|
|
@@ -443,6 +467,91 @@ describe('supervisor loop', () => {
|
|
|
443
467
|
// Deferring dispatch was right throughout, and stays right.
|
|
444
468
|
expect(spawned).toBe(0);
|
|
445
469
|
});
|
|
470
|
+
test('unverifiable currentness retries durably across supervisor restart and does not spawn before admitted', async () => {
|
|
471
|
+
initUnattendedFixture(repo);
|
|
472
|
+
let now = Date.now();
|
|
473
|
+
jest.spyOn(Date, 'now').mockImplementation(() => now);
|
|
474
|
+
let verdict = 'unverifiable';
|
|
475
|
+
const observe = async () => verdict === 'unverifiable'
|
|
476
|
+
? { state: 'blocked', planState: 'valid', executionMode: 'desatendido', journal: 'not-required', currentness: 'unverifiable', sensors: 'not-required', diagnostics: [{ code: 'ADMISSION_CURRENTNESS_BLOCKED', message: 'registry:baseline' }] }
|
|
477
|
+
: admitted();
|
|
478
|
+
let spawned = 0;
|
|
479
|
+
const spawn = () => { spawned += 1; };
|
|
480
|
+
expect(await new supervisor_1.Supervisor(repo, 'main', supervisor_1.DEFAULT_SUPERVISOR_CONFIG, spawn, undefined, observe, true).tick()).toBe('continue');
|
|
481
|
+
expect((0, store_1.readJournal)(repo, 'main').state).toHaveProperty('admissionRetry.attempts', 1);
|
|
482
|
+
expect((0, store_1.readJournal)(repo, 'main').state.generations).toHaveLength(0);
|
|
483
|
+
expect(spawned).toBe(0);
|
|
484
|
+
// New supervisor instance represents a process restart. Repeated early
|
|
485
|
+
// ticks cannot spend or reset the durable retry budget.
|
|
486
|
+
const restarted = new supervisor_1.Supervisor(repo, 'main', supervisor_1.DEFAULT_SUPERVISOR_CONFIG, spawn, undefined, observe, true);
|
|
487
|
+
expect(await restarted.tick()).toBe('continue');
|
|
488
|
+
expect((0, store_1.readJournal)(repo, 'main').state).toHaveProperty('admissionRetry.attempts', 1);
|
|
489
|
+
now += 5000;
|
|
490
|
+
verdict = 'current';
|
|
491
|
+
expect(await restarted.tick()).toBe('continue');
|
|
492
|
+
expect((0, store_1.readJournal)(repo, 'main').state).not.toHaveProperty('admissionRetry');
|
|
493
|
+
expect((0, store_1.readJournal)(repo, 'main').state.cycle.status).toBe('IN_PROGRESS');
|
|
494
|
+
expect((0, store_1.readJournal)(repo, 'main').state.generations).toHaveLength(1);
|
|
495
|
+
expect(spawned).toBeGreaterThan(0);
|
|
496
|
+
});
|
|
497
|
+
test('stale currentness is conclusive and never receives transient retry budget', async () => {
|
|
498
|
+
initUnattendedFixture(repo);
|
|
499
|
+
const blocked = async () => ({ state: 'blocked', planState: 'valid', executionMode: 'desatendido', journal: 'current', currentness: 'stale', sensors: 'not-required', diagnostics: [{ code: 'ADMISSION_CURRENTNESS_BLOCKED', message: 'stale' }] });
|
|
500
|
+
let spawned = 0;
|
|
501
|
+
expect(await new supervisor_1.Supervisor(repo, 'main', supervisor_1.DEFAULT_SUPERVISOR_CONFIG, () => { spawned++; }, undefined, blocked).tick()).toBe('custody');
|
|
502
|
+
const state = (0, store_1.readJournal)(repo, 'main').state;
|
|
503
|
+
expect(state.cycle.status).toBe('BLOCKED');
|
|
504
|
+
expect(state).not.toHaveProperty('admissionRetry');
|
|
505
|
+
expect(state.generations).toHaveLength(0);
|
|
506
|
+
expect(spawned).toBe(0);
|
|
507
|
+
});
|
|
508
|
+
test('first admission observation binds provider, runtime and autonomy; changed restart identity never dispatches', async () => {
|
|
509
|
+
initUnattendedFixture(repo);
|
|
510
|
+
const identity = { kind: 'native', version: '1.0.0', accountScopeDigest: 'a'.repeat(64) };
|
|
511
|
+
const cfg = { ...supervisor_1.DEFAULT_SUPERVISOR_CONFIG, routingIdentity: identity, controllerAutonomy: 'approval-free' };
|
|
512
|
+
const notCurrent = async () => ({ state: 'blocked', planState: 'valid', executionMode: 'desatendido', journal: 'not-required', currentness: 'unverifiable', sensors: 'not-required', diagnostics: [{ code: 'ADMISSION_CURRENTNESS_BLOCKED', message: 'temporary' }] });
|
|
513
|
+
let spawns = 0;
|
|
514
|
+
expect(await new supervisor_1.Supervisor(repo, 'main', cfg, () => { spawns++; }, undefined, notCurrent).tick()).toBe('continue');
|
|
515
|
+
expect((0, store_1.readJournal)(repo, 'main').state).toHaveProperty('admissionContext', { provider: 'codex', runtime: identity, controllerAutonomy: 'approval-free' });
|
|
516
|
+
const changed = { ...cfg, routingIdentity: { ...identity, accountScopeDigest: 'b'.repeat(64) } };
|
|
517
|
+
expect(await new supervisor_1.Supervisor(repo, 'main', changed, () => { spawns++; }, undefined, admitted).tick()).toBe('custody');
|
|
518
|
+
expect((0, store_1.readJournal)(repo, 'main').state.cycle.status).toBe('BLOCKED');
|
|
519
|
+
expect(spawns).toBe(0);
|
|
520
|
+
});
|
|
521
|
+
test('alternating transient causes share one durable retry budget', async () => {
|
|
522
|
+
initUnattendedFixture(repo);
|
|
523
|
+
let now = Date.now();
|
|
524
|
+
jest.spyOn(Date, 'now').mockImplementation(() => now);
|
|
525
|
+
let reads = 0;
|
|
526
|
+
const alternating = async () => {
|
|
527
|
+
const sensor = reads++ % 2 === 1;
|
|
528
|
+
return sensor
|
|
529
|
+
? { state: 'blocked', planState: 'valid', executionMode: 'desatendido', journal: 'not-required', currentness: 'current', sensors: 'not-certified', diagnostics: [{ code: 'ADMISSION_SENSORS_BLOCKED', message: 'temporary' }] }
|
|
530
|
+
: { state: 'blocked', planState: 'valid', executionMode: 'desatendido', journal: 'not-required', currentness: 'unverifiable', sensors: 'not-required', diagnostics: [{ code: 'ADMISSION_CURRENTNESS_BLOCKED', message: 'temporary' }] };
|
|
531
|
+
};
|
|
532
|
+
let spawns = 0;
|
|
533
|
+
for (let i = 0; i < supervisor_1.MAX_SENSOR_DEFERRALS + 1; i++) {
|
|
534
|
+
const outcome = await new supervisor_1.Supervisor(repo, 'main', supervisor_1.DEFAULT_SUPERVISOR_CONFIG, () => { spawns++; }, undefined, alternating).tick();
|
|
535
|
+
expect(outcome).toBe(i < supervisor_1.MAX_SENSOR_DEFERRALS ? 'continue' : 'custody');
|
|
536
|
+
now += 5000;
|
|
537
|
+
}
|
|
538
|
+
expect(reads).toBe(supervisor_1.MAX_SENSOR_DEFERRALS + 1);
|
|
539
|
+
expect(spawns).toBe(0);
|
|
540
|
+
expect((0, store_1.readJournal)(repo, 'main').state.generations).toHaveLength(0);
|
|
541
|
+
});
|
|
542
|
+
test('an incomplete first configuration can be corrected before dispatch without trapping the cycle', async () => {
|
|
543
|
+
initUnattendedFixture(repo);
|
|
544
|
+
const notCurrent = async () => ({ state: 'blocked', planState: 'valid', executionMode: 'desatendido', journal: 'not-required', currentness: 'unverifiable', sensors: 'not-required', diagnostics: [{ code: 'ADMISSION_CURRENTNESS_BLOCKED', message: 'temporary' }] });
|
|
545
|
+
let spawned = 0;
|
|
546
|
+
expect(await new supervisor_1.Supervisor(repo, 'main', supervisor_1.DEFAULT_SUPERVISOR_CONFIG, () => { spawned++; }, undefined, notCurrent).tick()).toBe('continue');
|
|
547
|
+
expect((0, store_1.readJournal)(repo, 'main').state).not.toHaveProperty('admissionContext');
|
|
548
|
+
const cfg = { ...supervisor_1.DEFAULT_SUPERVISOR_CONFIG, routingIdentity: { kind: 'native', version: '1.0.0', accountScopeDigest: 'a'.repeat(64) }, controllerAutonomy: 'approval-free' };
|
|
549
|
+
const retry = (0, store_1.readJournal)(repo, 'main').state.admissionRetry;
|
|
550
|
+
jest.spyOn(Date, 'now').mockReturnValue(Date.parse(retry.nextRetryAt));
|
|
551
|
+
expect(await new supervisor_1.Supervisor(repo, 'main', cfg, () => { spawned++; }, undefined, admitted, true).tick()).toBe('continue');
|
|
552
|
+
expect((0, store_1.readJournal)(repo, 'main').state).toHaveProperty('admissionContext', { provider: 'codex', runtime: cfg.routingIdentity, controllerAutonomy: 'approval-free' });
|
|
553
|
+
expect(spawned).toBeGreaterThan(0);
|
|
554
|
+
});
|
|
446
555
|
test('una admisión compacta bloqueada no lanza controlador ni wrappers', async () => {
|
|
447
556
|
initUnattendedFixture(repo);
|
|
448
557
|
const state = (0, store_1.readJournal)(repo, 'main').state;
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|