sneakoscope 1.18.0 → 1.18.1

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 (60) hide show
  1. package/README.md +2 -2
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/crates/sks-core/src/main.rs +1 -1
  5. package/dist/.sks-build-stamp.json +4 -4
  6. package/dist/bin/sks.js +1 -1
  7. package/dist/build-manifest.json +17 -9
  8. package/dist/commands/image-ux-review.d.ts +46 -1
  9. package/dist/commands/ppt.d.ts +46 -1
  10. package/dist/core/agents/agent-central-ledger.d.ts +1 -0
  11. package/dist/core/agents/agent-central-ledger.js +4 -3
  12. package/dist/core/agents/agent-codex-cockpit.d.ts +7 -0
  13. package/dist/core/agents/agent-codex-cockpit.js +31 -1
  14. package/dist/core/agents/agent-janitor.d.ts +3 -0
  15. package/dist/core/agents/agent-janitor.js +32 -0
  16. package/dist/core/agents/agent-ledger-schemas.d.ts +162 -0
  17. package/dist/core/agents/agent-ledger-schemas.js +23 -0
  18. package/dist/core/agents/agent-lifecycle.js +40 -16
  19. package/dist/core/agents/agent-orchestrator.d.ts +46 -1
  20. package/dist/core/agents/agent-orchestrator.js +75 -21
  21. package/dist/core/agents/agent-output-validator.d.ts +16 -0
  22. package/dist/core/agents/agent-output-validator.js +12 -0
  23. package/dist/core/agents/agent-proof-evidence.d.ts +19 -0
  24. package/dist/core/agents/agent-proof-evidence.js +42 -1
  25. package/dist/core/agents/agent-runner-codex-exec.d.ts +2 -0
  26. package/dist/core/agents/agent-runner-codex-exec.js +6 -6
  27. package/dist/core/agents/agent-runner-fake.js +2 -0
  28. package/dist/core/agents/agent-runner-process.js +10 -4
  29. package/dist/core/agents/agent-runner-tmux.d.ts +1 -32
  30. package/dist/core/agents/agent-runner-tmux.js +83 -12
  31. package/dist/core/agents/agent-scheduler.d.ts +77 -0
  32. package/dist/core/agents/agent-scheduler.js +295 -0
  33. package/dist/core/agents/agent-schema.d.ts +2 -0
  34. package/dist/core/agents/agent-session-generation.d.ts +55 -0
  35. package/dist/core/agents/agent-session-generation.js +110 -0
  36. package/dist/core/agents/agent-terminal-session.d.ts +10 -0
  37. package/dist/core/agents/agent-terminal-session.js +31 -8
  38. package/dist/core/agents/agent-trust-report.d.ts +5 -0
  39. package/dist/core/agents/agent-trust-report.js +9 -1
  40. package/dist/core/agents/agent-work-queue.d.ts +53 -0
  41. package/dist/core/agents/agent-work-queue.js +128 -0
  42. package/dist/core/agents/agent-worker-pipeline.d.ts +2 -0
  43. package/dist/core/agents/agent-worker-pipeline.js +4 -0
  44. package/dist/core/agents/agent-worker-slot.d.ts +35 -0
  45. package/dist/core/agents/agent-worker-slot.js +90 -0
  46. package/dist/core/agents/agent-wrongness.d.ts +1 -1
  47. package/dist/core/agents/agent-wrongness.js +2 -0
  48. package/dist/core/agents/route-collaboration-ledger.d.ts +46 -1
  49. package/dist/core/agents/tmux-right-lane-cockpit.d.ts +22 -0
  50. package/dist/core/agents/tmux-right-lane-cockpit.js +12 -2
  51. package/dist/core/commands/image-ux-review-command.d.ts +46 -1
  52. package/dist/core/commands/ppt-command.d.ts +46 -1
  53. package/dist/core/fsx.d.ts +1 -1
  54. package/dist/core/fsx.js +1 -1
  55. package/dist/core/mad-sks/immutable-harness-guard.js +2 -0
  56. package/dist/core/release-parallel-full-coverage.js +9 -0
  57. package/dist/core/version.d.ts +1 -1
  58. package/dist/core/version.js +1 -1
  59. package/package.json +10 -1
  60. package/schemas/codex/agent-result.schema.json +12 -0
package/README.md CHANGED
@@ -10,7 +10,7 @@ SKS does not try to clone every other harness. It focuses on one thing: making C
10
10
 
11
11
  ## Current Release
12
12
 
13
- SKS **1.18.0** adds Universal Source Intelligence for Context7 + Codex Web Search, optional X AI MCP search when configured, main no-Scout / worker Scout-limited proof policy, per-agent terminal close evidence, tmux right-lane cockpit manifests, Codex official Goal mode detection, and full P0-P4 release readiness tracking.
13
+ SKS **1.18.1** adds Dynamic Agent Pool replenishment: `agents=5` now means keep up to five active worker slots running until the work queue drains, with immutable session generations, generation-aware terminal close reports, real/fake-tmux pane launch evidence, and Source Intelligence / Goal mode refs propagated to every generation.
14
14
 
15
15
  ```bash
16
16
  sks mad-sks plan --target-root <path> --json
@@ -612,7 +612,7 @@ npm run release:check
612
612
  npm run publish:dry
613
613
  ```
614
614
 
615
- `release:check` runs the 1.18.0 parallel P0-P4 closure DAG, writes a source digest stamp under `.sneakoscope/reports/`, then refreshes release readiness so publish commands can verify the same stamp. The DAG preserves the 1.17 baseline gates and adds Source Intelligence, X AI/Codex Web policy, Goal mode, main no-Scout, worker Scout-limited, agent terminal, tmux right-lane, visual consistency, release full-coverage, and priority closure checks. Broader live or historical gates remain explicit scripts such as `release:real-check`. Generate the human-readable registry with `sks features inventory --write-docs`. Plain `npm publish` uses the `latest` dist-tag. npm's `prepublishOnly` verifies the fresh release stamp instead of rerunning the full gate, and `prepack` only rebuilds `dist`; publish no longer repeats the expensive release suite during packaging. `npm run publish:dry` remains the explicit dry-run helper.
615
+ `release:check` runs the 1.18.1 dynamic agent pool closure DAG, writes a source digest stamp under `.sneakoscope/reports/`, then refreshes release readiness so publish commands can verify the same stamp. The DAG preserves the 1.18 baseline gates and adds dynamic pool, backfill replenishment, scheduler proof, session generation, terminal generation, tmux real right-lane, dynamic cockpit, Source Intelligence propagation, and Goal mode propagation checks. Broader live or historical gates remain explicit scripts such as `release:real-check`. Generate the human-readable registry with `sks features inventory --write-docs`. Plain `npm publish` uses the `latest` dist-tag. npm's `prepublishOnly` verifies the fresh release stamp instead of rerunning the full gate, and `prepack` only rebuilds `dist`; publish no longer repeats the expensive release suite during packaging. `npm run publish:dry` remains the explicit dry-run helper.
616
616
 
617
617
  Version bumps are manual. Run `sks versioning bump` only when preparing release metadata; SKS will not create `.git/hooks/pre-commit` or auto-bump during ordinary commits.
618
618
 
@@ -76,7 +76,7 @@ dependencies = [
76
76
 
77
77
  [[package]]
78
78
  name = "sks-core"
79
- version = "1.18.0"
79
+ version = "1.18.1"
80
80
  dependencies = [
81
81
  "serde_json",
82
82
  ]
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "sks-core"
3
- version = "1.18.0"
3
+ version = "1.18.1"
4
4
  edition = "2021"
5
5
 
6
6
  [dependencies]
@@ -4,7 +4,7 @@ use std::io::{self, Read, Seek, SeekFrom};
4
4
  fn main() {
5
5
  let mut args = std::env::args().skip(1);
6
6
  match args.next().as_deref() {
7
- Some("--version") => println!("sks-rs 1.18.0"),
7
+ Some("--version") => println!("sks-rs 1.18.1"),
8
8
  Some("compact-info") => {
9
9
  let mut input = String::new();
10
10
  let _ = io::stdin().read_to_string(&mut input);
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schema": "sks.dist-build-stamp.v1",
3
3
  "package_name": "sneakoscope",
4
- "package_version": "1.18.0",
5
- "source_digest": "9593cf876b0f0b49ffb7aaa5e2ca1017bd7fd1508892f92cffbfc7db58fbfef0",
6
- "source_file_count": 1279,
7
- "built_at_source_time": 1779716030404
4
+ "package_version": "1.18.1",
5
+ "source_digest": "6df40e82722a56a4df2efafa956011204d7a96fd218b43d19aa10672bbd0842c",
6
+ "source_file_count": 1312,
7
+ "built_at_source_time": 1779722031788
8
8
  }
package/dist/bin/sks.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- const FAST_PACKAGE_VERSION = '1.18.0';
2
+ const FAST_PACKAGE_VERSION = '1.18.1';
3
3
  const args = process.argv.slice(2);
4
4
  try {
5
5
  if (args[0] === '--version' || args[0] === '-v' || args[0] === 'version') {
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "schema": "sks.dist-build.v2",
3
- "version": "1.18.0",
4
- "package_version": "1.18.0",
3
+ "version": "1.18.1",
4
+ "package_version": "1.18.1",
5
5
  "typescript": true,
6
6
  "mjs_runtime_files": 0,
7
- "compiled_file_count": 850,
8
- "compiled_js_count": 425,
9
- "compiled_dts_count": 425,
10
- "source_digest": "9593cf876b0f0b49ffb7aaa5e2ca1017bd7fd1508892f92cffbfc7db58fbfef0",
11
- "source_file_count": 1279,
12
- "source_files_hash": "29895436992640147aab36792cd376edbed9adc6226480db074ef9e9b5104c13",
13
- "source_list_hash": "29895436992640147aab36792cd376edbed9adc6226480db074ef9e9b5104c13",
7
+ "compiled_file_count": 858,
8
+ "compiled_js_count": 429,
9
+ "compiled_dts_count": 429,
10
+ "source_digest": "6df40e82722a56a4df2efafa956011204d7a96fd218b43d19aa10672bbd0842c",
11
+ "source_file_count": 1312,
12
+ "source_files_hash": "fbe2aa994acefbdab214a17074d539960fa99369b928c3679442019ace20e7c6",
13
+ "source_list_hash": "fbe2aa994acefbdab214a17074d539960fa99369b928c3679442019ace20e7c6",
14
14
  "src_mjs_runtime_files": 0,
15
15
  "dist_stamp_schema": "sks.dist-build-stamp.v1",
16
16
  "files": [
@@ -234,8 +234,12 @@
234
234
  "core/agents/agent-runner-process.js",
235
235
  "core/agents/agent-runner-tmux.d.ts",
236
236
  "core/agents/agent-runner-tmux.js",
237
+ "core/agents/agent-scheduler.d.ts",
238
+ "core/agents/agent-scheduler.js",
237
239
  "core/agents/agent-schema.d.ts",
238
240
  "core/agents/agent-schema.js",
241
+ "core/agents/agent-session-generation.d.ts",
242
+ "core/agents/agent-session-generation.js",
239
243
  "core/agents/agent-session-rows.d.ts",
240
244
  "core/agents/agent-session-rows.js",
241
245
  "core/agents/agent-task-slicer.d.ts",
@@ -246,8 +250,12 @@
246
250
  "core/agents/agent-trust-report.js",
247
251
  "core/agents/agent-work-partition.d.ts",
248
252
  "core/agents/agent-work-partition.js",
253
+ "core/agents/agent-work-queue.d.ts",
254
+ "core/agents/agent-work-queue.js",
249
255
  "core/agents/agent-worker-pipeline.d.ts",
250
256
  "core/agents/agent-worker-pipeline.js",
257
+ "core/agents/agent-worker-slot.d.ts",
258
+ "core/agents/agent-worker-slot.js",
251
259
  "core/agents/agent-wrongness.d.ts",
252
260
  "core/agents/agent-wrongness.js",
253
261
  "core/agents/route-collaboration-ledger.d.ts",
@@ -436,6 +436,28 @@ export declare function run(command: any, args?: any): Promise<void | {
436
436
  lease_count: number;
437
437
  blockers: string[];
438
438
  };
439
+ scheduler: {
440
+ schema: string;
441
+ ok: boolean;
442
+ state: import("../core/agents/agent-scheduler.js").AgentSchedulerState;
443
+ queue: import("../core/agents/agent-work-queue.js").AgentWorkQueue;
444
+ slots: import("../core/agents/agent-worker-slot.js").AgentWorkerSlot[];
445
+ results: any[];
446
+ };
447
+ source_intelligence: {
448
+ artifact: string;
449
+ ok: boolean;
450
+ mode: import("../core/source-intelligence/source-intelligence-policy.js").SourceIntelligenceMode;
451
+ cache_key: string;
452
+ proof_ok: boolean;
453
+ };
454
+ goal_mode: {
455
+ artifact: string;
456
+ ok: boolean;
457
+ mode: "official_goal_default" | "sks_goal_fallback";
458
+ official_goal_available: boolean;
459
+ default_enabled: boolean;
460
+ };
439
461
  results: any[];
440
462
  consensus: {
441
463
  schema: string;
@@ -526,6 +548,11 @@ export declare function run(command: any, args?: any): Promise<void | {
526
548
  all_sessions_closed: boolean;
527
549
  terminal_sessions_closed: any;
528
550
  terminal_close_report: string;
551
+ target_active_slots: any;
552
+ max_observed_active_slots: any;
553
+ backfill_count: any;
554
+ pending_queue_drained: any;
555
+ generation_count: any;
529
556
  tmux_attach_command: string | null;
530
557
  tmux_lane_manifest: string;
531
558
  output_schema_ok: boolean;
@@ -542,7 +569,7 @@ export declare function run(command: any, args?: any): Promise<void | {
542
569
  generated_at: string;
543
570
  records: {
544
571
  schema: string;
545
- kind: "xai_available_not_used" | "context7_missing" | "codex_web_search_missing" | "recursion_attempt" | "lease_conflict" | "session_not_closed" | "terminal_missing" | "terminal_not_closed" | "schema_invalid_output" | "stale_heartbeat" | "legacy_multiagent_runtime_usage_attempt";
572
+ kind: "xai_available_not_used" | "context7_missing" | "codex_web_search_missing" | "recursion_attempt" | "lease_conflict" | "session_not_closed" | "terminal_missing" | "terminal_not_closed" | "scheduler_starvation" | "session_generation_missing" | "schema_invalid_output" | "stale_heartbeat" | "legacy_multiagent_runtime_usage_attempt";
546
573
  blocker: string;
547
574
  created_at: string;
548
575
  status: string;
@@ -564,9 +591,27 @@ export declare function run(command: any, args?: any): Promise<void | {
564
591
  closed_session_count: number;
565
592
  terminal_sessions_closed: boolean;
566
593
  terminal_session_count: number;
594
+ terminal_generation_count: number;
595
+ terminal_close_report_count: number;
567
596
  terminal_close_report: string;
597
+ session_generation_count: number;
598
+ all_generations_closed: boolean;
599
+ scheduler_state: string;
600
+ target_active_slots: any;
601
+ max_observed_active_slots: any;
602
+ pending_queue_drained: boolean;
603
+ backfill_count: any;
604
+ expected_backfill_count: any;
605
+ slot_count: any;
606
+ generation_count: number;
607
+ all_slots_closed_after_drain: boolean;
608
+ generated_work_item_count: any;
609
+ source_intelligence_generation_refs_ok: boolean;
610
+ goal_mode_generation_refs_ok: boolean;
568
611
  tmux_lane_manifest: string;
569
612
  tmux_lane_manifest_ok: boolean;
613
+ tmux_pane_launch_ledger: string;
614
+ tmux_pane_launch_count: number;
570
615
  ledger_hash_chain_ok: boolean;
571
616
  no_overlap_ok: boolean;
572
617
  consensus_ok: boolean;
@@ -288,6 +288,28 @@ export declare function run(command: any, args?: any): Promise<void | {
288
288
  lease_count: number;
289
289
  blockers: string[];
290
290
  };
291
+ scheduler: {
292
+ schema: string;
293
+ ok: boolean;
294
+ state: import("../core/agents/agent-scheduler.js").AgentSchedulerState;
295
+ queue: import("../core/agents/agent-work-queue.js").AgentWorkQueue;
296
+ slots: import("../core/agents/agent-worker-slot.js").AgentWorkerSlot[];
297
+ results: any[];
298
+ };
299
+ source_intelligence: {
300
+ artifact: string;
301
+ ok: boolean;
302
+ mode: import("../core/source-intelligence/source-intelligence-policy.js").SourceIntelligenceMode;
303
+ cache_key: string;
304
+ proof_ok: boolean;
305
+ };
306
+ goal_mode: {
307
+ artifact: string;
308
+ ok: boolean;
309
+ mode: "official_goal_default" | "sks_goal_fallback";
310
+ official_goal_available: boolean;
311
+ default_enabled: boolean;
312
+ };
291
313
  results: any[];
292
314
  consensus: {
293
315
  schema: string;
@@ -378,6 +400,11 @@ export declare function run(command: any, args?: any): Promise<void | {
378
400
  all_sessions_closed: boolean;
379
401
  terminal_sessions_closed: any;
380
402
  terminal_close_report: string;
403
+ target_active_slots: any;
404
+ max_observed_active_slots: any;
405
+ backfill_count: any;
406
+ pending_queue_drained: any;
407
+ generation_count: any;
381
408
  tmux_attach_command: string | null;
382
409
  tmux_lane_manifest: string;
383
410
  output_schema_ok: boolean;
@@ -394,7 +421,7 @@ export declare function run(command: any, args?: any): Promise<void | {
394
421
  generated_at: string;
395
422
  records: {
396
423
  schema: string;
397
- kind: "xai_available_not_used" | "context7_missing" | "codex_web_search_missing" | "recursion_attempt" | "lease_conflict" | "session_not_closed" | "terminal_missing" | "terminal_not_closed" | "schema_invalid_output" | "stale_heartbeat" | "legacy_multiagent_runtime_usage_attempt";
424
+ kind: "xai_available_not_used" | "context7_missing" | "codex_web_search_missing" | "recursion_attempt" | "lease_conflict" | "session_not_closed" | "terminal_missing" | "terminal_not_closed" | "scheduler_starvation" | "session_generation_missing" | "schema_invalid_output" | "stale_heartbeat" | "legacy_multiagent_runtime_usage_attempt";
398
425
  blocker: string;
399
426
  created_at: string;
400
427
  status: string;
@@ -416,9 +443,27 @@ export declare function run(command: any, args?: any): Promise<void | {
416
443
  closed_session_count: number;
417
444
  terminal_sessions_closed: boolean;
418
445
  terminal_session_count: number;
446
+ terminal_generation_count: number;
447
+ terminal_close_report_count: number;
419
448
  terminal_close_report: string;
449
+ session_generation_count: number;
450
+ all_generations_closed: boolean;
451
+ scheduler_state: string;
452
+ target_active_slots: any;
453
+ max_observed_active_slots: any;
454
+ pending_queue_drained: boolean;
455
+ backfill_count: any;
456
+ expected_backfill_count: any;
457
+ slot_count: any;
458
+ generation_count: number;
459
+ all_slots_closed_after_drain: boolean;
460
+ generated_work_item_count: any;
461
+ source_intelligence_generation_refs_ok: boolean;
462
+ goal_mode_generation_refs_ok: boolean;
420
463
  tmux_lane_manifest: string;
421
464
  tmux_lane_manifest_ok: boolean;
465
+ tmux_pane_launch_ledger: string;
466
+ tmux_pane_launch_count: number;
422
467
  ledger_hash_chain_ok: boolean;
423
468
  no_overlap_ok: boolean;
424
469
  consensus_ok: boolean;
@@ -17,6 +17,7 @@ export declare function initializeAgentCentralLedger(missionDir: string, input:
17
17
  partition?: any;
18
18
  route?: string;
19
19
  prompt?: string;
20
+ dynamicScheduler?: boolean;
20
21
  }): Promise<string>;
21
22
  export declare function appendAgentLedgerEvent(root: string, event: {
22
23
  agent_id: string;
@@ -47,10 +47,11 @@ export function validateAgentLedgerWriteScope(input) {
47
47
  const mode = input.mode || 'write';
48
48
  const orchestrator = actor === 'orchestrator' || actor === 'parent_orchestrator';
49
49
  const sessionMatch = target.match(/^sessions\/([^/]+)\.json$/);
50
+ const generationSessionMatch = target.match(/^sessions\/([^/]+)\/gen-\d+\/agent-session-record\.json$/);
50
51
  const messageAppend = target === 'agent-messages.jsonl' && mode === 'append';
51
52
  const eventAppend = target === 'agent-events.jsonl' && mode === 'append';
52
53
  const handoffAppend = target === 'agent-handoffs.jsonl' && mode === 'append';
53
- const ownSessionWrite = Boolean(sessionMatch && sessionMatch[1] === actor);
54
+ const ownSessionWrite = Boolean((sessionMatch && sessionMatch[1] === actor) || (generationSessionMatch && generationSessionMatch[1] === actor));
54
55
  const orchestratorOnly = AGENT_ORCHESTRATOR_ONLY_FILES.includes(target) || target === 'agent-sessions.json';
55
56
  if (orchestrator)
56
57
  return { ok: true, reason: 'orchestrator_write_allowed', actor_agent_id: actor, target_path: target, mode };
@@ -58,7 +59,7 @@ export function validateAgentLedgerWriteScope(input) {
58
59
  return { ok: true, reason: 'own_session_record_allowed', actor_agent_id: actor, target_path: target, mode };
59
60
  if (messageAppend || eventAppend || handoffAppend)
60
61
  return { ok: true, reason: 'central_append_allowed', actor_agent_id: actor, target_path: target, mode };
61
- if (sessionMatch && sessionMatch[1] !== actor)
62
+ if ((sessionMatch && sessionMatch[1] !== actor) || (generationSessionMatch && generationSessionMatch[1] !== actor))
62
63
  return { ok: false, reason: 'agent_cannot_modify_other_session_record', actor_agent_id: actor, target_path: target, mode };
63
64
  if (orchestratorOnly)
64
65
  return { ok: false, reason: 'agent_cannot_modify_orchestrator_only_file', actor_agent_id: actor, target_path: target, mode };
@@ -71,7 +72,7 @@ export async function initializeAgentCentralLedger(missionDir, input) {
71
72
  await writeTextAtomic(path.join(root, 'agent-events.jsonl'), '');
72
73
  await writeTextAtomic(path.join(root, 'agent-messages.jsonl'), '');
73
74
  await writeTextAtomic(path.join(root, 'agent-handoffs.jsonl'), '');
74
- const sessions = Object.fromEntries((input.roster.roster || []).map((agent) => [agent.id, {
75
+ const sessions = input.dynamicScheduler ? {} : Object.fromEntries((input.roster.roster || []).map((agent) => [agent.id, {
75
76
  agent_id: agent.id,
76
77
  session_id: agent.session_id,
77
78
  status: 'pending',
@@ -35,6 +35,13 @@ export interface AgentCodexCockpitState {
35
35
  goal_mode_status: string | null;
36
36
  terminal_session_status: string | null;
37
37
  tmux_attach_command: string | null;
38
+ target_active_slots: number | null;
39
+ active_slot_count: number | null;
40
+ pending_queue_count: number | null;
41
+ backfill_count: number | null;
42
+ scheduler_status: string | null;
43
+ worker_slots: Array<Record<string, unknown>>;
44
+ session_generations: Array<Record<string, unknown>>;
38
45
  blockers: string[];
39
46
  agents: Array<Record<string, unknown>>;
40
47
  recent_events: string[];
@@ -37,6 +37,9 @@ export async function buildAgentCodexCockpitState(missionDir, opts = {}) {
37
37
  const sourceIntelligence = await readJson(path.join(missionDir, 'source-intelligence-evidence.json'), null);
38
38
  const goalMode = await readJson(path.join(missionDir, 'goal-mode-applied.json'), null);
39
39
  const tmuxLayout = await readJson(path.join(root, 'agent-tmux-layout.json'), null);
40
+ const scheduler = await readJson(path.join(root, 'agent-scheduler-state.json'), null);
41
+ const workerSlots = await readJson(path.join(root, 'agent-worker-slots.json'), null);
42
+ const generations = await readJson(path.join(root, 'agent-session-generations.json'), null);
40
43
  const terminalClosed = proof?.terminal_sessions_closed === true;
41
44
  const eventsTail = await readTailLines(path.join(root, 'agent-events.jsonl'), 8);
42
45
  const cockpitEventsTail = await readTailLines(path.join(root, AGENT_CODEX_COCKPIT_EVENTS), 8);
@@ -68,6 +71,13 @@ export async function buildAgentCodexCockpitState(missionDir, opts = {}) {
68
71
  goal_mode_status: goalMode?.mode || null,
69
72
  terminal_session_status: terminalClosed ? 'closed' : proof ? 'blocked_or_unverified' : null,
70
73
  tmux_attach_command: tmuxLayout?.attach_command || null,
74
+ target_active_slots: scheduler?.target_active_slots ?? null,
75
+ active_slot_count: scheduler?.active_slot_count ?? null,
76
+ pending_queue_count: scheduler?.pending_count ?? null,
77
+ backfill_count: scheduler?.backfill_count ?? null,
78
+ scheduler_status: scheduler?.status || null,
79
+ worker_slots: Array.isArray(workerSlots?.slots) ? workerSlots.slots : [],
80
+ session_generations: generations?.generations ? Object.values(generations.generations) : [],
71
81
  blockers,
72
82
  agents,
73
83
  recent_events: [...eventsTail, ...cockpitEventsTail, ...teamTail].slice(-12),
@@ -98,6 +108,11 @@ export function renderAgentCodexDashboard(state) {
98
108
  `- Terminal sessions: ${state.terminal_session_status || 'unknown'}`,
99
109
  `- tmux attach: ${state.tmux_attach_command || 'unknown'}`,
100
110
  `- All sessions closed: ${state.all_sessions_closed ?? 'unknown'}`,
111
+ `- Scheduler: ${state.scheduler_status || 'unknown'}`,
112
+ `- Target active slots: ${state.target_active_slots ?? 'unknown'}`,
113
+ `- Active slots: ${state.active_slot_count ?? 'unknown'}`,
114
+ `- Pending queue: ${state.pending_queue_count ?? 'unknown'}`,
115
+ `- Backfill events: ${state.backfill_count ?? 'unknown'}`,
101
116
  '',
102
117
  '| Agent | Persona | Task | State | Heartbeat age | Lease | Blockers | Artifact |',
103
118
  '| --- | --- | --- | --- | --- | --- | --- | --- |',
@@ -116,6 +131,14 @@ export function renderAgentCodexDashboard(state) {
116
131
  return `${[...header, ...rows].join('\n')}\n`;
117
132
  }
118
133
  export function renderAgentSessionCards(state) {
134
+ const slotBlocks = state.worker_slots.map((slot) => [
135
+ `## ${cell(slot.slot_id)}`,
136
+ '',
137
+ `- Status: ${cell(slot.status)}`,
138
+ `- Current generation: ${cell(slot.current_generation_index)}`,
139
+ `- Current session: ${cell(slot.current_session_id)}`,
140
+ `- Generation count: ${cell(slot.generation_count)}`,
141
+ ].join('\n'));
119
142
  const blocks = state.agents.map((agent) => [
120
143
  `## ${cell(agent.id)}`,
121
144
  '',
@@ -125,7 +148,7 @@ export function renderAgentSessionCards(state) {
125
148
  `- Lease: ${cell(agent.lease || agent.lease_id || agent.write_policy)}`,
126
149
  `- Artifact: ${cell(agent.output_artifact || agent.artifact || '')}`,
127
150
  ].join('\n'));
128
- return `# Agent Session Cards\n\n${blocks.join('\n\n')}\n`;
151
+ return `# Agent Session Cards\n\n${[...slotBlocks, ...blocks].join('\n\n')}\n`;
129
152
  }
130
153
  export function renderAgentProgressTimeline(state) {
131
154
  return `# Agent Progress Timeline\n\n${state.recent_events.map((line) => `- ${line}`).join('\n')}\n`;
@@ -140,6 +163,13 @@ function summarizeLiveState(state) {
140
163
  agent_count: state.agent_count,
141
164
  concurrency: state.concurrency,
142
165
  active_agents: state.agents.filter((agent) => !['closed', 'done', 'completed'].includes(String(agent.status || ''))).length,
166
+ target_active_slots: state.target_active_slots,
167
+ active_slot_count: state.active_slot_count,
168
+ pending_queue_count: state.pending_queue_count,
169
+ backfill_count: state.backfill_count,
170
+ scheduler_status: state.scheduler_status,
171
+ worker_slot_count: state.worker_slots.length,
172
+ session_generation_count: state.session_generations.length,
143
173
  proof_status: state.proof_status,
144
174
  source_intelligence_status: state.source_intelligence_status,
145
175
  xai_status: state.xai_status,
@@ -7,6 +7,9 @@ export interface AgentJanitorReport {
7
7
  stale_heartbeat_sessions: string[];
8
8
  zombie_process_sessions: string[];
9
9
  stale_tmux_sessions: string[];
10
+ active_generation_sessions: string[];
11
+ orphan_generation_dirs: string[];
12
+ slot_generation_cleanup: string[];
10
13
  orphan_temp_dirs: string[];
11
14
  stale_locks: string[];
12
15
  cleaned: string[];
@@ -9,6 +9,11 @@ export async function runAgentJanitor(input) {
9
9
  const namespace = await readJson(path.join(input.missionDir, 'project-session-namespace.json'), null);
10
10
  const projectHash = input.projectHash || namespace?.root_hash || null;
11
11
  const rows = normalizeAgentSessionRows(sessions);
12
+ const generations = await readJson(path.join(agentRoot, 'agent-session-generations.json'), null);
13
+ const generationRows = generations?.generations ? Object.values(generations.generations) : [];
14
+ const activeGenerationSessions = generationRows
15
+ .filter((row) => !row.closed_at && ['running', 'launching', 'collecting'].includes(String(row.status || 'running')))
16
+ .map((row) => String(row.session_id));
12
17
  const now = Date.now();
13
18
  const staleHeartbeat = rows
14
19
  .filter((row) => {
@@ -30,10 +35,15 @@ export async function runAgentJanitor(input) {
30
35
  }
31
36
  const zombieProcesses = await detectZombieProcessSessions(agentRoot, statusByAgent, statusBySession);
32
37
  const staleTmuxSessions = await detectStaleTmuxSessions(agentRoot, staleMs);
38
+ const orphanGenerationDirs = await detectOrphanGenerationDirs(agentRoot, new Set(generationRows.map((row) => String(row.artifact_dir || ''))));
33
39
  const orphanTempDirs = await scopedExistingPaths(Array.isArray(namespace?.orphan_temp_dirs) ? namespace.orphan_temp_dirs : [], projectHash);
34
40
  const staleLocks = await scopedStaleLockPaths(namespace?.lock_dir ? [namespace.lock_dir] : [], projectHash, staleMs);
35
41
  const cleaned = [];
36
42
  if (input.cleanup) {
43
+ for (const dir of orphanGenerationDirs) {
44
+ await fsp.rm(path.join(agentRoot, dir), { recursive: true, force: true }).catch(() => { });
45
+ cleaned.push(path.join(agentRoot, dir));
46
+ }
37
47
  for (const dir of orphanTempDirs) {
38
48
  await fsp.rm(dir, { recursive: true, force: true }).catch(() => { });
39
49
  cleaned.push(dir);
@@ -54,6 +64,9 @@ export async function runAgentJanitor(input) {
54
64
  stale_heartbeat_sessions: staleHeartbeat,
55
65
  zombie_process_sessions: zombieProcesses,
56
66
  stale_tmux_sessions: staleTmuxSessions,
67
+ active_generation_sessions: activeGenerationSessions,
68
+ orphan_generation_dirs: orphanGenerationDirs,
69
+ slot_generation_cleanup: cleaned.filter((entry) => entry.includes(`${path.sep}sessions${path.sep}`)),
57
70
  orphan_temp_dirs: orphanTempDirs,
58
71
  stale_locks: staleLocks,
59
72
  cleaned,
@@ -62,6 +75,25 @@ export async function runAgentJanitor(input) {
62
75
  await writeAgentJanitorReport(input.missionDir, report);
63
76
  return report;
64
77
  }
78
+ async function detectOrphanGenerationDirs(agentRoot, knownGenerationDirs) {
79
+ const sessionsDir = path.join(agentRoot, 'sessions');
80
+ const out = [];
81
+ if (!(await exists(sessionsDir)))
82
+ return out;
83
+ for (const slot of await fsp.readdir(sessionsDir, { withFileTypes: true }).catch(() => [])) {
84
+ if (!slot.isDirectory())
85
+ continue;
86
+ const slotDir = path.join(sessionsDir, slot.name);
87
+ for (const gen of await fsp.readdir(slotDir, { withFileTypes: true }).catch(() => [])) {
88
+ if (!gen.isDirectory() || !/^gen-\d+$/.test(gen.name))
89
+ continue;
90
+ const rel = path.join('sessions', slot.name, gen.name);
91
+ if (!knownGenerationDirs.has(rel))
92
+ out.push(rel);
93
+ }
94
+ }
95
+ return out;
96
+ }
65
97
  export async function writeAgentJanitorReport(missionDir, report) {
66
98
  await writeJsonAtomic(path.join(missionDir, 'agents', 'agent-janitor-report.json'), report);
67
99
  }