sneakoscope 1.18.3 → 1.18.4

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 (43) hide show
  1. package/README.md +12 -1
  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/build-manifest.json +17 -9
  7. package/dist/cli/feature-commands.js +16 -5
  8. package/dist/commands/image-ux-review.d.ts +41 -7
  9. package/dist/commands/ppt.d.ts +41 -7
  10. package/dist/core/agents/agent-cleanup-executor.d.ts +47 -0
  11. package/dist/core/agents/agent-cleanup-executor.js +211 -0
  12. package/dist/core/agents/agent-command-surface.d.ts +3 -0
  13. package/dist/core/agents/agent-command-surface.js +4 -1
  14. package/dist/core/agents/agent-orchestrator.d.ts +41 -7
  15. package/dist/core/agents/agent-orchestrator.js +10 -2
  16. package/dist/core/agents/agent-proof-evidence.d.ts +19 -0
  17. package/dist/core/agents/agent-proof-evidence.js +27 -0
  18. package/dist/core/agents/agent-runner-codex-exec.js +10 -0
  19. package/dist/core/agents/agent-trust-report.d.ts +20 -0
  20. package/dist/core/agents/agent-trust-report.js +28 -2
  21. package/dist/core/agents/agent-work-partition.d.ts +3 -7
  22. package/dist/core/agents/agent-work-partition.js +11 -2
  23. package/dist/core/agents/intelligent-work-graph.d.ts +40 -0
  24. package/dist/core/agents/intelligent-work-graph.js +217 -0
  25. package/dist/core/agents/route-collaboration-ledger.d.ts +41 -7
  26. package/dist/core/agents/tmux-physical-proof.d.ts +184 -0
  27. package/dist/core/agents/tmux-physical-proof.js +269 -0
  28. package/dist/core/agents/work-partition/repo-inventory.js +1 -1
  29. package/dist/core/codex-hooks/codex-hook-actual-discovery.d.ts +1 -0
  30. package/dist/core/codex-hooks/codex-hook-actual-discovery.js +6 -2
  31. package/dist/core/codex-hooks/codex-hook-trust-doctor.d.ts +29 -0
  32. package/dist/core/codex-hooks/codex-hook-trust-doctor.js +12 -2
  33. package/dist/core/commands/agent-command.js +12 -5
  34. package/dist/core/commands/image-ux-review-command.d.ts +41 -7
  35. package/dist/core/commands/ppt-command.d.ts +41 -7
  36. package/dist/core/fsx.d.ts +1 -1
  37. package/dist/core/fsx.js +1 -1
  38. package/dist/core/proof/fake-real-proof-policy.d.ts +15 -0
  39. package/dist/core/proof/fake-real-proof-policy.js +52 -0
  40. package/dist/core/routes.js +1 -1
  41. package/dist/core/version.d.ts +1 -1
  42. package/dist/core/version.js +1 -1
  43. package/package.json +10 -2
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.3** closes the route-truth Dynamic Worker Pool runtime: `agents=5` means five target active worker slots, while `--work-items N` controls the route work queue. Pending work backfills empty slots immediately, tmux lanes persist as worker-slot UI until scheduler drain, follow-up work items are schema-bound, and Agent/Team/Research/QA blackboxes now execute the actual route commands while proving task graph, work queue, scheduler, terminal, Source Intelligence, Goal, and tmux supervisor evidence.
13
+ SKS **1.18.4** closes the runtime-truth gap left after the route-truth Dynamic Worker Pool work: real tmux mode now requires physical pane evidence from `tmux list-panes`, `tmux capture-pane`, pane-id reconciliation, and drain-close checks; real Codex dynamic smoke is opt-in through `SKS_TEST_REAL_DYNAMIC_AGENTS=1`; `sks agent close/cleanup` now writes executor proof for stale process, tmux, temp, and lock cleanup; and the task graph carries an intelligent work graph score, test ownership, critical path, and integration bottleneck artifacts. Fake fixture evidence remains useful for hermetic release gates, but it is explicitly separated from real runtime proof.
14
14
 
15
15
  ```bash
16
16
  sks mad-sks plan --target-root <path> --json
@@ -23,6 +23,11 @@ sks agent run "release review" --agents 8 --work-items 16 --concurrency 4 --mock
23
23
  npm run source-intelligence:all-modes
24
24
  npm run agent:background-terminals
25
25
  npm run agent:tmux-lane-no-flicker
26
+ npm run agent:cleanup-executor
27
+ npm run agent:intelligent-work-graph
28
+ npm run proof:fake-vs-real-policy
29
+ npm run route:blackbox-realism
30
+ npm run release:real-check
26
31
  npm run agent:backfill-route-blackbox
27
32
  npm run team:actual-route-backfill
28
33
  npm run release:readiness
@@ -58,6 +63,12 @@ Detailed release history lives in [CHANGELOG.md](CHANGELOG.md). Current release
58
63
  - Main no-Scout / worker Scout policy: [docs/main-no-scout-worker-scout-policy.md](docs/main-no-scout-worker-scout-policy.md)
59
64
  - Agent terminal lanes: [docs/agent-terminal-lanes.md](docs/agent-terminal-lanes.md)
60
65
  - tmux right-lane cockpit: [docs/tmux-right-lane-cockpit.md](docs/tmux-right-lane-cockpit.md)
66
+ - Real tmux pane proof: [docs/real-tmux-pane-proof.md](docs/real-tmux-pane-proof.md)
67
+ - Real Codex dynamic smoke: [docs/real-codex-dynamic-smoke.md](docs/real-codex-dynamic-smoke.md)
68
+ - Agent cleanup executor: [docs/agent-cleanup-executor.md](docs/agent-cleanup-executor.md)
69
+ - Intelligent work graph: [docs/intelligent-work-graph.md](docs/intelligent-work-graph.md)
70
+ - Fake vs real proof policy: [docs/fake-vs-real-proof-policy.md](docs/fake-vs-real-proof-policy.md)
71
+ - Migration 1.18.3 to 1.18.4: [docs/migration-1.18.3-to-1.18.4.md](docs/migration-1.18.3-to-1.18.4.md)
61
72
  - Codex official Goal mode: [docs/codex-official-goal-mode.md](docs/codex-official-goal-mode.md)
62
73
  - Release parallel full coverage: [docs/release-parallel-full-coverage.md](docs/release-parallel-full-coverage.md)
63
74
  - Priority closure P0-P4: [docs/priority-closure-p0-p4.md](docs/priority-closure-p0-p4.md)
@@ -76,7 +76,7 @@ dependencies = [
76
76
 
77
77
  [[package]]
78
78
  name = "sks-core"
79
- version = "1.18.3"
79
+ version = "1.18.4"
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.3"
3
+ version = "1.18.4"
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.3"),
7
+ Some("--version") => println!("sks-rs 1.18.4"),
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.3",
5
- "source_digest": "5aefcff7c0119b0c22dc3ca2d50edd2dbd5f069ea1a9838cee14700f0ede28de",
6
- "source_file_count": 1370,
7
- "built_at_source_time": 1779767703495
4
+ "package_version": "1.18.4",
5
+ "source_digest": "33109bbaf2af23979db84be40e35c9d47e2b35618270ab31547bfda9b59f590f",
6
+ "source_file_count": 1405,
7
+ "built_at_source_time": 1779777408842
8
8
  }
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "schema": "sks.dist-build.v2",
3
- "version": "1.18.3",
4
- "package_version": "1.18.3",
3
+ "version": "1.18.4",
4
+ "package_version": "1.18.4",
5
5
  "typescript": true,
6
6
  "mjs_runtime_files": 0,
7
- "compiled_file_count": 864,
8
- "compiled_js_count": 432,
9
- "compiled_dts_count": 432,
10
- "source_digest": "5aefcff7c0119b0c22dc3ca2d50edd2dbd5f069ea1a9838cee14700f0ede28de",
11
- "source_file_count": 1370,
12
- "source_files_hash": "1807c675876cb62160e1f8e2fb69a846610ba2a34fa01154263805337a98c149",
13
- "source_list_hash": "1807c675876cb62160e1f8e2fb69a846610ba2a34fa01154263805337a98c149",
7
+ "compiled_file_count": 872,
8
+ "compiled_js_count": 436,
9
+ "compiled_dts_count": 436,
10
+ "source_digest": "33109bbaf2af23979db84be40e35c9d47e2b35618270ab31547bfda9b59f590f",
11
+ "source_file_count": 1405,
12
+ "source_files_hash": "eef20a281a31010636ae8b25a6e03acd3ac4564fb9870beed34653150250e3fe",
13
+ "source_list_hash": "eef20a281a31010636ae8b25a6e03acd3ac4564fb9870beed34653150250e3fe",
14
14
  "src_mjs_runtime_files": 0,
15
15
  "dist_stamp_schema": "sks.dist-build-stamp.v1",
16
16
  "files": [
@@ -186,6 +186,8 @@
186
186
  "commands/wiki.js",
187
187
  "core/agents/agent-central-ledger.d.ts",
188
188
  "core/agents/agent-central-ledger.js",
189
+ "core/agents/agent-cleanup-executor.d.ts",
190
+ "core/agents/agent-cleanup-executor.js",
189
191
  "core/agents/agent-cleanup.d.ts",
190
192
  "core/agents/agent-cleanup.js",
191
193
  "core/agents/agent-codex-cockpit.d.ts",
@@ -262,12 +264,16 @@
262
264
  "core/agents/agent-worker-slot.js",
263
265
  "core/agents/agent-wrongness.d.ts",
264
266
  "core/agents/agent-wrongness.js",
267
+ "core/agents/intelligent-work-graph.d.ts",
268
+ "core/agents/intelligent-work-graph.js",
265
269
  "core/agents/route-collaboration-ledger.d.ts",
266
270
  "core/agents/route-collaboration-ledger.js",
267
271
  "core/agents/scout-policy.d.ts",
268
272
  "core/agents/scout-policy.js",
269
273
  "core/agents/tmux-lane-supervisor.d.ts",
270
274
  "core/agents/tmux-lane-supervisor.js",
275
+ "core/agents/tmux-physical-proof.d.ts",
276
+ "core/agents/tmux-physical-proof.js",
271
277
  "core/agents/tmux-right-lane-cockpit.d.ts",
272
278
  "core/agents/tmux-right-lane-cockpit.js",
273
279
  "core/agents/work-partition/conflict-detector.d.ts",
@@ -713,6 +719,8 @@
713
719
  "core/proof/command-ledger.js",
714
720
  "core/proof/evidence-collector.d.ts",
715
721
  "core/proof/evidence-collector.js",
722
+ "core/proof/fake-real-proof-policy.d.ts",
723
+ "core/proof/fake-real-proof-policy.js",
716
724
  "core/proof/file-change-ledger.d.ts",
717
725
  "core/proof/file-change-ledger.js",
718
726
  "core/proof/proof-reader.d.ts",
@@ -171,18 +171,29 @@ export async function hooksCommand(sub = 'explain', args = []) {
171
171
  return;
172
172
  }
173
173
  }
174
- const report = await installManagedCodexHooks(root);
174
+ const projectReport = await installManagedCodexHooks(root);
175
+ const userReport = await installManagedCodexHooks(root, {
176
+ requirementsPath: path.join(os.homedir(), '.codex', 'requirements.toml'),
177
+ managedDir: path.join(os.homedir(), '.codex', 'managed-hooks')
178
+ });
179
+ const actual = await codexHookTrustDoctor(root, { actual: true });
175
180
  const result = {
176
- ...report,
177
181
  schema: 'sks.codex-hooks-repair.v1',
182
+ ok: Boolean(projectReport.ok && userReport.ok && actual.ok),
178
183
  mode: 'managed',
184
+ root,
185
+ project_install: projectReport,
186
+ user_install: userReport,
187
+ actual_trust: actual.trust,
188
+ managed_dirs: actual.managed_dirs || [],
189
+ blockers: actual.blockers || [],
179
190
  next_command: 'sks hooks trust-doctor --actual --json',
180
- actions: ['requirements_toml_managed_install_default']
191
+ actions: ['project_requirements_toml_managed_install', 'user_requirements_toml_managed_install']
181
192
  };
182
193
  if (flag(args, '--json'))
183
194
  return console.log(JSON.stringify(result, null, 2));
184
- console.log(`Hooks managed repair: ${report.ok ? 'ok' : 'blocked'}`);
185
- if (!report.ok)
195
+ console.log(`Hooks managed repair: ${result.ok ? 'ok' : 'blocked'}`);
196
+ if (!result.ok)
186
197
  process.exitCode = 1;
187
198
  return;
188
199
  }
@@ -438,14 +438,9 @@ export declare function run(command: any, args?: any): Promise<void | {
438
438
  lease_count: number;
439
439
  blockers: string[];
440
440
  };
441
- task_graph: {
442
- target_active_slots: number;
443
- total_work_items: number;
444
- generated_from_route: string;
445
- work_items_exceed_active_slots: boolean;
446
- };
441
+ task_graph: any;
447
442
  requested_work_items: number;
448
- actual_total_work_items: number;
443
+ actual_total_work_items: any;
449
444
  target_active_slots: number;
450
445
  minimum_work_items: number;
451
446
  scheduler: {
@@ -578,6 +573,26 @@ export declare function run(command: any, args?: any): Promise<void | {
578
573
  pane_survival_checked: boolean;
579
574
  unexpected_close_count: number;
580
575
  lane_count: number;
576
+ physical_tmux_verified: boolean;
577
+ physical_proof_status: any;
578
+ list_panes_artifact: any;
579
+ capture_pane_artifacts: any;
580
+ };
581
+ cleanup_executor: {
582
+ status: string;
583
+ proof: string | null;
584
+ stale_processes_killed: any;
585
+ stale_tmux_panes_closed: any;
586
+ orphan_temp_dirs_removed: any;
587
+ stale_locks_removed: any;
588
+ skipped_active_sessions: any;
589
+ };
590
+ intelligent_work_graph: {
591
+ status: string;
592
+ score: any;
593
+ test_ownership: string;
594
+ critical_path: string;
595
+ integration_bottlenecks: string;
581
596
  };
582
597
  output_schema_ok: boolean;
583
598
  output_tail_report: string;
@@ -660,6 +675,25 @@ export declare function run(command: any, args?: any): Promise<void | {
660
675
  tmux_lane_auto_reopen_count: number;
661
676
  tmux_pane_launch_ledger: string;
662
677
  tmux_pane_launch_count: number;
678
+ physical_tmux_verified: boolean;
679
+ tmux_physical_proof: string;
680
+ tmux_list_panes_artifact: any;
681
+ tmux_capture_pane_artifacts: any;
682
+ tmux_pane_id_reconciled: boolean;
683
+ real_truth_summary: {
684
+ fake_backend: boolean;
685
+ physical_tmux_verified: boolean;
686
+ real_tmux_status: any;
687
+ cleanup_executor_status: string;
688
+ work_graph_quality_score: number;
689
+ fake_vs_real_policy: string;
690
+ };
691
+ intelligent_work_graph: string;
692
+ test_ownership_map: string;
693
+ critical_path: string;
694
+ integration_bottlenecks: string;
695
+ work_graph_quality_score: number;
696
+ work_graph_quality_partial: boolean;
663
697
  terminal_reports_match_generations: boolean;
664
698
  ledger_hash_chain_ok: boolean;
665
699
  no_overlap_ok: boolean;
@@ -290,14 +290,9 @@ export declare function run(command: any, args?: any): Promise<void | {
290
290
  lease_count: number;
291
291
  blockers: string[];
292
292
  };
293
- task_graph: {
294
- target_active_slots: number;
295
- total_work_items: number;
296
- generated_from_route: string;
297
- work_items_exceed_active_slots: boolean;
298
- };
293
+ task_graph: any;
299
294
  requested_work_items: number;
300
- actual_total_work_items: number;
295
+ actual_total_work_items: any;
301
296
  target_active_slots: number;
302
297
  minimum_work_items: number;
303
298
  scheduler: {
@@ -430,6 +425,26 @@ export declare function run(command: any, args?: any): Promise<void | {
430
425
  pane_survival_checked: boolean;
431
426
  unexpected_close_count: number;
432
427
  lane_count: number;
428
+ physical_tmux_verified: boolean;
429
+ physical_proof_status: any;
430
+ list_panes_artifact: any;
431
+ capture_pane_artifacts: any;
432
+ };
433
+ cleanup_executor: {
434
+ status: string;
435
+ proof: string | null;
436
+ stale_processes_killed: any;
437
+ stale_tmux_panes_closed: any;
438
+ orphan_temp_dirs_removed: any;
439
+ stale_locks_removed: any;
440
+ skipped_active_sessions: any;
441
+ };
442
+ intelligent_work_graph: {
443
+ status: string;
444
+ score: any;
445
+ test_ownership: string;
446
+ critical_path: string;
447
+ integration_bottlenecks: string;
433
448
  };
434
449
  output_schema_ok: boolean;
435
450
  output_tail_report: string;
@@ -512,6 +527,25 @@ export declare function run(command: any, args?: any): Promise<void | {
512
527
  tmux_lane_auto_reopen_count: number;
513
528
  tmux_pane_launch_ledger: string;
514
529
  tmux_pane_launch_count: number;
530
+ physical_tmux_verified: boolean;
531
+ tmux_physical_proof: string;
532
+ tmux_list_panes_artifact: any;
533
+ tmux_capture_pane_artifacts: any;
534
+ tmux_pane_id_reconciled: boolean;
535
+ real_truth_summary: {
536
+ fake_backend: boolean;
537
+ physical_tmux_verified: boolean;
538
+ real_tmux_status: any;
539
+ cleanup_executor_status: string;
540
+ work_graph_quality_score: number;
541
+ fake_vs_real_policy: string;
542
+ };
543
+ intelligent_work_graph: string;
544
+ test_ownership_map: string;
545
+ critical_path: string;
546
+ integration_bottlenecks: string;
547
+ work_graph_quality_score: number;
548
+ work_graph_quality_partial: boolean;
515
549
  terminal_reports_match_generations: boolean;
516
550
  ledger_hash_chain_ok: boolean;
517
551
  no_overlap_ok: boolean;
@@ -0,0 +1,47 @@
1
+ export declare const AGENT_CLEANUP_PROOF_SCHEMA = "sks.agent-cleanup-proof.v1";
2
+ export declare const AGENT_CLEANUP_ACTION_LEDGER_SCHEMA = "sks.agent-cleanup-action-ledger.v1";
3
+ export interface AgentCleanupExecutorOptions {
4
+ missionDir: string;
5
+ missionId?: string | null;
6
+ action?: 'cleanup' | 'close';
7
+ apply?: boolean;
8
+ dryRun?: boolean;
9
+ drain?: boolean;
10
+ staleMs?: number;
11
+ }
12
+ type CleanupActionKind = 'terminate_process' | 'close_tmux_pane' | 'remove_temp_dir' | 'remove_lock' | 'skip_active_session' | 'skip_foreign_namespace' | 'archive_transcript_keep';
13
+ interface CleanupAction {
14
+ kind: CleanupActionKind;
15
+ target: string;
16
+ status: 'planned' | 'applied' | 'skipped' | 'failed';
17
+ reason: string;
18
+ error?: string;
19
+ }
20
+ export declare function runAgentCleanupExecutor(opts: AgentCleanupExecutorOptions): Promise<{
21
+ schema: string;
22
+ generated_at: string;
23
+ ok: boolean;
24
+ mission_id: string | null;
25
+ project_namespace: string | null;
26
+ action: string;
27
+ dry_run: boolean;
28
+ apply: boolean;
29
+ stale_processes_found: string[];
30
+ stale_processes_killed: string[];
31
+ stale_tmux_panes_found: string[];
32
+ stale_tmux_panes_closed: string[];
33
+ orphan_temp_dirs_found: string[];
34
+ orphan_temp_dirs_removed: string[];
35
+ stale_locks_found: string[];
36
+ stale_locks_removed: string[];
37
+ skipped_active_sessions: string[];
38
+ skipped_foreign_namespace: string[];
39
+ terminal_transcripts_preserved: string[];
40
+ action_count: number;
41
+ applied_count: number;
42
+ failed_count: number;
43
+ actions: CleanupAction[];
44
+ blockers: string[];
45
+ }>;
46
+ export {};
47
+ //# sourceMappingURL=agent-cleanup-executor.d.ts.map
@@ -0,0 +1,211 @@
1
+ import fsp from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { appendJsonl, exists, nowIso, readJson, writeJsonAtomic } from '../fsx.js';
4
+ import { drainTmuxLaneSupervisor } from './tmux-lane-supervisor.js';
5
+ import { normalizeAgentSessionRows } from './agent-session-rows.js';
6
+ export const AGENT_CLEANUP_PROOF_SCHEMA = 'sks.agent-cleanup-proof.v1';
7
+ export const AGENT_CLEANUP_ACTION_LEDGER_SCHEMA = 'sks.agent-cleanup-action-ledger.v1';
8
+ const TERMINAL_STATUSES = new Set(['closed', 'completed', 'done', 'failed', 'blocked', 'killed', 'timed_out']);
9
+ export async function runAgentCleanupExecutor(opts) {
10
+ const agentRoot = path.join(opts.missionDir, 'agents');
11
+ const action = opts.action || 'cleanup';
12
+ const apply = opts.apply === true && opts.dryRun !== true;
13
+ if (action === 'close' && opts.drain === true)
14
+ await drainTmuxLaneSupervisor(agentRoot).catch(() => null);
15
+ const namespace = await readJson(path.join(opts.missionDir, 'project-session-namespace.json'), null);
16
+ const sessionsRaw = await readJson(path.join(agentRoot, 'agent-sessions.json'), { sessions: {} });
17
+ const sessions = normalizeAgentSessionRows(sessionsRaw);
18
+ const activeSessionIds = new Set(sessions
19
+ .filter((row) => !TERMINAL_STATUSES.has(String(row.status || row.lifecycle_state || '')))
20
+ .map((row) => String(row.session_id || row.id || row.agent_id || ''))
21
+ .filter(Boolean));
22
+ const now = Date.now();
23
+ const staleMs = opts.staleMs ?? 30 * 60 * 1000;
24
+ const actions = [];
25
+ const processReports = await listNamedFiles(path.join(agentRoot, 'sessions'), 'agent-process-report.json');
26
+ for (const file of processReports) {
27
+ const report = await readJson(file, null);
28
+ const pid = Number(report?.pid || 0);
29
+ const sessionId = String(report?.session_id || '');
30
+ const status = String(sessions.find((row) => String(row.session_id || '') === sessionId)?.status || '');
31
+ const terminal = TERMINAL_STATUSES.has(status) || report?.exit_code !== null;
32
+ if (!pid || !processIsAlive(pid))
33
+ continue;
34
+ if (activeSessionIds.has(sessionId) && !terminal) {
35
+ actions.push({ kind: 'skip_active_session', target: sessionId || String(pid), status: 'skipped', reason: 'session_active' });
36
+ continue;
37
+ }
38
+ actions.push(await applyAction({
39
+ kind: 'terminate_process',
40
+ target: String(pid),
41
+ reason: terminal ? 'terminal_session_process_alive' : 'stale_session_process',
42
+ apply,
43
+ run: async () => {
44
+ process.kill(pid, 'SIGTERM');
45
+ }
46
+ }));
47
+ }
48
+ const tmuxReports = await listNamedFiles(path.join(agentRoot, 'sessions'), 'agent-tmux-report.json');
49
+ for (const file of tmuxReports) {
50
+ const report = await readJson(file, null);
51
+ const paneId = String(report?.pane_id || '');
52
+ const sessionId = String(report?.session_id || '');
53
+ if (!validTmuxPaneId(paneId))
54
+ continue;
55
+ if (activeSessionIds.has(sessionId)) {
56
+ actions.push({ kind: 'skip_active_session', target: sessionId || paneId, status: 'skipped', reason: 'tmux_session_active' });
57
+ continue;
58
+ }
59
+ actions.push(await applyAction({
60
+ kind: 'close_tmux_pane',
61
+ target: paneId,
62
+ reason: 'stale_tmux_pane',
63
+ apply,
64
+ run: async () => {
65
+ const { runProcess } = await import('../fsx.js');
66
+ await runProcess('tmux', ['kill-pane', '-t', paneId], { timeoutMs: 3000, maxOutputBytes: 4096 });
67
+ }
68
+ }));
69
+ }
70
+ const projectHash = String(namespace?.root_hash || '');
71
+ for (const dir of Array.isArray(namespace?.orphan_temp_dirs) ? namespace.orphan_temp_dirs.map(String) : []) {
72
+ if (!namespaceOwnsPath(dir, projectHash)) {
73
+ actions.push({ kind: 'skip_foreign_namespace', target: dir, status: 'skipped', reason: 'path_outside_project_namespace' });
74
+ continue;
75
+ }
76
+ if (!(await exists(dir)))
77
+ continue;
78
+ actions.push(await applyAction({
79
+ kind: 'remove_temp_dir',
80
+ target: dir,
81
+ reason: 'orphan_temp_dir',
82
+ apply,
83
+ run: async () => fsp.rm(dir, { recursive: true, force: true })
84
+ }));
85
+ }
86
+ for (const lock of await staleLockFiles(String(namespace?.lock_dir || ''), projectHash, now, staleMs)) {
87
+ actions.push(await applyAction({
88
+ kind: 'remove_lock',
89
+ target: lock,
90
+ reason: 'stale_lock_file',
91
+ apply,
92
+ run: async () => fsp.rm(lock, { force: true })
93
+ }));
94
+ }
95
+ for (const transcript of await listNamedFiles(path.join(agentRoot, 'sessions'), 'agent-terminal-session.json')) {
96
+ actions.push({ kind: 'archive_transcript_keep', target: transcript, status: 'skipped', reason: 'terminal_transcripts_are_preserved' });
97
+ }
98
+ const proof = buildCleanupProof({
99
+ generatedAt: nowIso(),
100
+ missionId: opts.missionId || namespace?.mission_id || null,
101
+ projectNamespace: projectHash || null,
102
+ action,
103
+ apply,
104
+ dryRun: !apply,
105
+ actions,
106
+ activeSessionIds: [...activeSessionIds]
107
+ });
108
+ await writeJsonAtomic(path.join(agentRoot, 'agent-cleanup-proof.json'), proof);
109
+ await writeJsonAtomic(path.join(agentRoot, 'agent-command-cleanup.json'), {
110
+ schema: 'sks.agent-command-cleanup.v2',
111
+ ok: proof.ok,
112
+ mission_id: proof.mission_id,
113
+ action,
114
+ cleanup_executor: 'agent-cleanup-proof.json',
115
+ cleanup_action_ledger: 'agent-cleanup-action-ledger.jsonl',
116
+ applied: apply,
117
+ blockers: proof.blockers
118
+ });
119
+ for (const row of actions) {
120
+ await appendJsonl(path.join(agentRoot, 'agent-cleanup-action-ledger.jsonl'), {
121
+ schema: AGENT_CLEANUP_ACTION_LEDGER_SCHEMA,
122
+ generated_at: proof.generated_at,
123
+ action: row
124
+ });
125
+ }
126
+ return proof;
127
+ }
128
+ function buildCleanupProof(input) {
129
+ const byKind = (kind, status) => input.actions.filter((row) => row.kind === kind && (!status || row.status === status));
130
+ const failed = input.actions.filter((row) => row.status === 'failed');
131
+ return {
132
+ schema: AGENT_CLEANUP_PROOF_SCHEMA,
133
+ generated_at: input.generatedAt,
134
+ ok: failed.length === 0,
135
+ mission_id: input.missionId,
136
+ project_namespace: input.projectNamespace,
137
+ action: input.action,
138
+ dry_run: input.dryRun,
139
+ apply: input.apply,
140
+ stale_processes_found: byKind('terminate_process').map((row) => row.target),
141
+ stale_processes_killed: byKind('terminate_process', 'applied').map((row) => row.target),
142
+ stale_tmux_panes_found: byKind('close_tmux_pane').map((row) => row.target),
143
+ stale_tmux_panes_closed: byKind('close_tmux_pane', 'applied').map((row) => row.target),
144
+ orphan_temp_dirs_found: byKind('remove_temp_dir').map((row) => row.target),
145
+ orphan_temp_dirs_removed: byKind('remove_temp_dir', 'applied').map((row) => row.target),
146
+ stale_locks_found: byKind('remove_lock').map((row) => row.target),
147
+ stale_locks_removed: byKind('remove_lock', 'applied').map((row) => row.target),
148
+ skipped_active_sessions: [...new Set([...input.activeSessionIds, ...byKind('skip_active_session').map((row) => row.target)])].filter(Boolean),
149
+ skipped_foreign_namespace: byKind('skip_foreign_namespace').map((row) => row.target),
150
+ terminal_transcripts_preserved: byKind('archive_transcript_keep').map((row) => row.target),
151
+ action_count: input.actions.length,
152
+ applied_count: input.actions.filter((row) => row.status === 'applied').length,
153
+ failed_count: failed.length,
154
+ actions: input.actions,
155
+ blockers: failed.map((row) => `cleanup_action_failed:${row.kind}:${row.target}`)
156
+ };
157
+ }
158
+ async function applyAction(input) {
159
+ if (!input.apply)
160
+ return { kind: input.kind, target: input.target, status: 'planned', reason: input.reason };
161
+ try {
162
+ await input.run();
163
+ return { kind: input.kind, target: input.target, status: 'applied', reason: input.reason };
164
+ }
165
+ catch (err) {
166
+ return { kind: input.kind, target: input.target, status: 'failed', reason: input.reason, error: err instanceof Error ? err.message : String(err) };
167
+ }
168
+ }
169
+ async function staleLockFiles(lockDir, projectHash, now, staleMs) {
170
+ const out = [];
171
+ if (!lockDir || !namespaceOwnsPath(lockDir, projectHash) || !(await exists(lockDir)))
172
+ return out;
173
+ for (const file of await listFiles(lockDir)) {
174
+ const stat = await fsp.stat(file).catch(() => null);
175
+ if (stat && now - stat.mtimeMs > staleMs)
176
+ out.push(file);
177
+ }
178
+ return out;
179
+ }
180
+ async function listNamedFiles(dir, name) {
181
+ return (await listFiles(dir)).filter((file) => path.basename(file) === name);
182
+ }
183
+ async function listFiles(dir) {
184
+ const out = [];
185
+ if (!(await exists(dir)))
186
+ return out;
187
+ for (const entry of await fsp.readdir(dir, { withFileTypes: true }).catch(() => [])) {
188
+ const file = path.join(dir, entry.name);
189
+ if (entry.isDirectory())
190
+ out.push(...await listFiles(file));
191
+ else if (entry.isFile())
192
+ out.push(file);
193
+ }
194
+ return out;
195
+ }
196
+ function namespaceOwnsPath(candidate, projectHash) {
197
+ return Boolean(candidate && (!projectHash || candidate.includes(projectHash)));
198
+ }
199
+ function processIsAlive(pid) {
200
+ try {
201
+ process.kill(pid, 0);
202
+ return true;
203
+ }
204
+ catch {
205
+ return false;
206
+ }
207
+ }
208
+ function validTmuxPaneId(value) {
209
+ return /^%\d+$/.test(value);
210
+ }
211
+ //# sourceMappingURL=agent-cleanup-executor.js.map
@@ -13,6 +13,9 @@ export declare function parseAgentCommandArgs(command: string, args?: string[]):
13
13
  mock: boolean;
14
14
  real: boolean;
15
15
  readonly: boolean;
16
+ apply: boolean;
17
+ dryRun: boolean;
18
+ drain: boolean;
16
19
  json: boolean;
17
20
  missionId: string;
18
21
  lane: string;
@@ -16,6 +16,9 @@ export function parseAgentCommandArgs(command, args = []) {
16
16
  const mock = hasFlag(args, '--mock') || backend === 'fake';
17
17
  const real = hasFlag(args, '--real');
18
18
  const readonly = hasFlag(args, '--readonly') || hasFlag(args, '--read-only');
19
+ const apply = hasFlag(args, '--apply');
20
+ const dryRun = hasFlag(args, '--dry-run') || hasFlag(args, '--dryrun');
21
+ const drain = hasFlag(args, '--drain');
19
22
  const codexApp = hasFlag(args, '--codex-app');
20
23
  const positionals = positionalArgs(rest, new Set(['--agents', '--target-active-slots', '--work-items', '--minimum-work-items', '--max-queue-expansion', '--concurrency', '--backend', '--route', '--mission', '--mission-id', '--agent', '--lane']));
21
24
  const missionDefault = action === 'run' || action === 'spawn' || action === 'plan' ? '' : 'latest';
@@ -24,7 +27,7 @@ export function parseAgentCommandArgs(command, args = []) {
24
27
  const lane = String(readOption(args, '--agent', readOption(args, '--lane', '')));
25
28
  const promptPositionals = positionalMission ? positionals.slice(1) : positionals;
26
29
  const prompt = promptPositionals.join(' ').trim() || 'Native agent run';
27
- return { command, action, prompt, route, agents, targetActiveSlots, desiredWorkItemCount, minimumWorkItems, maxQueueExpansion, concurrency, backend, mock, real, readonly, json, missionId, lane, codexApp };
30
+ return { command, action, prompt, route, agents, targetActiveSlots, desiredWorkItemCount, minimumWorkItems, maxQueueExpansion, concurrency, backend, mock, real, readonly, apply, dryRun, drain, json, missionId, lane, codexApp };
28
31
  }
29
32
  function hasFlag(args, flag) {
30
33
  return args.includes(flag);