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.
@@ -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' && entry.processRef === undefined)).toBe(true);
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
- expect(final.generations.every((entry) => entry.state === 'terminated' && (entry.processRef === undefined || !refIsAlive(entry.processRef)))).toBe(true);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-workflow-manager",
3
- "version": "9.12.1",
3
+ "version": "9.12.3",
4
4
  "main": "dist/src/index.js",
5
5
  "bin": {
6
6
  "awm": "./dist/src/index.js"
Binary file
Binary file