sneakoscope 1.18.4 → 1.18.6

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 (51) hide show
  1. package/README.md +8 -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 +15 -9
  8. package/dist/commands/image-ux-review.d.ts +20 -0
  9. package/dist/commands/ppt.d.ts +20 -0
  10. package/dist/core/agents/agent-cleanup-executor.d.ts +30 -1
  11. package/dist/core/agents/agent-cleanup-executor.js +186 -15
  12. package/dist/core/agents/agent-command-surface.d.ts +3 -0
  13. package/dist/core/agents/agent-command-surface.js +5 -2
  14. package/dist/core/agents/agent-orchestrator.d.ts +20 -0
  15. package/dist/core/agents/agent-orchestrator.js +7 -4
  16. package/dist/core/agents/agent-output-validator.d.ts +13 -0
  17. package/dist/core/agents/agent-output-validator.js +12 -1
  18. package/dist/core/agents/agent-proof-evidence.d.ts +7 -0
  19. package/dist/core/agents/agent-proof-evidence.js +34 -1
  20. package/dist/core/agents/agent-runner-codex-exec.js +6 -1
  21. package/dist/core/agents/agent-trust-report.d.ts +13 -0
  22. package/dist/core/agents/agent-trust-report.js +46 -0
  23. package/dist/core/agents/intelligent-work-graph.d.ts +14 -1
  24. package/dist/core/agents/intelligent-work-graph.js +254 -10
  25. package/dist/core/agents/route-collaboration-ledger.d.ts +20 -0
  26. package/dist/core/agents/tmux-physical-proof.d.ts +100 -8
  27. package/dist/core/agents/tmux-physical-proof.js +92 -16
  28. package/dist/core/agents/work-partition/dependency-graph.js +33 -11
  29. package/dist/core/commands/agent-command.js +14 -1
  30. package/dist/core/commands/image-ux-review-command.d.ts +20 -0
  31. package/dist/core/commands/mad-sks-command.js +7 -2
  32. package/dist/core/commands/ppt-command.d.ts +20 -0
  33. package/dist/core/fsx.d.ts +1 -1
  34. package/dist/core/fsx.js +1 -1
  35. package/dist/core/hooks-runtime.js +12 -1
  36. package/dist/core/image-ux-review/imagegen-adapter.js +210 -10
  37. package/dist/core/mad-sks/mad-tmux-lane-proof.d.ts +26 -0
  38. package/dist/core/mad-sks/mad-tmux-lane-proof.js +50 -0
  39. package/dist/core/proof/fake-real-proof-policy.d.ts +10 -2
  40. package/dist/core/proof/fake-real-proof-policy.js +67 -9
  41. package/dist/core/proof/runtime-truth-matrix.d.ts +38 -0
  42. package/dist/core/proof/runtime-truth-matrix.js +155 -0
  43. package/dist/core/routes.js +1 -1
  44. package/dist/core/source-intelligence/source-intelligence-proof.js +2 -1
  45. package/dist/core/source-intelligence/source-intelligence-runner.js +4 -1
  46. package/dist/core/version.d.ts +1 -1
  47. package/dist/core/version.js +1 -1
  48. package/dist/scripts/release-parallel-check.d.ts +3 -0
  49. package/dist/scripts/release-parallel-check.js +204 -0
  50. package/package.json +18 -10
  51. package/schemas/codex/agent-result.schema.json +53 -17
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.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.
13
+ SKS **1.18.6** closes the zero-gap runtime truth loop: proof policy now reports every runtime subsystem separately, trust reports surface those subsystem proof levels, runtime truth matrix evidence is generated from live release artifacts, cleanup verifies process/tree/tmux/temp/lock after-states, and MAD-SKS writes explicit Warp/tmux lane proof instead of implying UI visibility.
14
14
 
15
15
  ```bash
16
16
  sks mad-sks plan --target-root <path> --json
@@ -24,8 +24,12 @@ npm run source-intelligence:all-modes
24
24
  npm run agent:background-terminals
25
25
  npm run agent:tmux-lane-no-flicker
26
26
  npm run agent:cleanup-executor
27
+ npm run agent:cleanup-executor-v2
27
28
  npm run agent:intelligent-work-graph
29
+ npm run agent:ast-aware-work-graph
28
30
  npm run proof:fake-vs-real-policy
31
+ npm run proof:fake-real-policy-v2
32
+ npm run release:runtime-truth-matrix
29
33
  npm run route:blackbox-realism
30
34
  npm run release:real-check
31
35
  npm run agent:backfill-route-blackbox
@@ -68,7 +72,9 @@ Detailed release history lives in [CHANGELOG.md](CHANGELOG.md). Current release
68
72
  - Agent cleanup executor: [docs/agent-cleanup-executor.md](docs/agent-cleanup-executor.md)
69
73
  - Intelligent work graph: [docs/intelligent-work-graph.md](docs/intelligent-work-graph.md)
70
74
  - 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)
75
+ - Runtime truth matrix: [docs/runtime-truth-matrix.md](docs/runtime-truth-matrix.md)
76
+ - Warp MAD tmux lanes: [docs/warp-mad-tmux-lanes.md](docs/warp-mad-tmux-lanes.md)
77
+ - Migration 1.18.5 to 1.18.6: [docs/migration-1.18.5-to-1.18.6.md](docs/migration-1.18.5-to-1.18.6.md)
72
78
  - Codex official Goal mode: [docs/codex-official-goal-mode.md](docs/codex-official-goal-mode.md)
73
79
  - Release parallel full coverage: [docs/release-parallel-full-coverage.md](docs/release-parallel-full-coverage.md)
74
80
  - 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.4"
79
+ version = "1.18.6"
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.4"
3
+ version = "1.18.6"
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.4"),
7
+ Some("--version") => println!("sks-rs 1.18.6"),
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.4",
5
- "source_digest": "33109bbaf2af23979db84be40e35c9d47e2b35618270ab31547bfda9b59f590f",
6
- "source_file_count": 1405,
7
- "built_at_source_time": 1779777408842
4
+ "package_version": "1.18.6",
5
+ "source_digest": "d45147f0f01dfabdab71a2e03ea484173d6de7e64e58958d1b73d31f8719ae5c",
6
+ "source_file_count": 1435,
7
+ "built_at_source_time": 1779844415377
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.3';
2
+ const FAST_PACKAGE_VERSION = '1.18.6';
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.4",
4
- "package_version": "1.18.4",
3
+ "version": "1.18.6",
4
+ "package_version": "1.18.6",
5
5
  "typescript": true,
6
6
  "mjs_runtime_files": 0,
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",
7
+ "compiled_file_count": 878,
8
+ "compiled_js_count": 439,
9
+ "compiled_dts_count": 439,
10
+ "source_digest": "d45147f0f01dfabdab71a2e03ea484173d6de7e64e58958d1b73d31f8719ae5c",
11
+ "source_file_count": 1435,
12
+ "source_files_hash": "d1b0087b824861574256c5a1f9af243009a1ac33006649d562e5905cba8b829d",
13
+ "source_list_hash": "d1b0087b824861574256c5a1f9af243009a1ac33006649d562e5905cba8b829d",
14
14
  "src_mjs_runtime_files": 0,
15
15
  "dist_stamp_schema": "sks.dist-build-stamp.v1",
16
16
  "files": [
@@ -598,6 +598,8 @@
598
598
  "core/mad-sks/guard-middleware.js",
599
599
  "core/mad-sks/immutable-harness-guard.d.ts",
600
600
  "core/mad-sks/immutable-harness-guard.js",
601
+ "core/mad-sks/mad-tmux-lane-proof.d.ts",
602
+ "core/mad-sks/mad-tmux-lane-proof.js",
601
603
  "core/mad-sks/permission-model.d.ts",
602
604
  "core/mad-sks/permission-model.js",
603
605
  "core/mad-sks/proof-evidence.d.ts",
@@ -743,6 +745,8 @@
743
745
  "core/proof/route-proof-gate.js",
744
746
  "core/proof/route-proof-policy.d.ts",
745
747
  "core/proof/route-proof-policy.js",
748
+ "core/proof/runtime-truth-matrix.d.ts",
749
+ "core/proof/runtime-truth-matrix.js",
746
750
  "core/proof/selftest-proof-fixtures.d.ts",
747
751
  "core/proof/selftest-proof-fixtures.js",
748
752
  "core/proof/validation.d.ts",
@@ -887,6 +891,8 @@
887
891
  "core/wiki-image/visual-anchor.js",
888
892
  "core/work-order-ledger.d.ts",
889
893
  "core/work-order-ledger.js",
894
+ "scripts/release-parallel-check.d.ts",
895
+ "scripts/release-parallel-check.js",
890
896
  "vendor/openai-codex/latest/hooks/permission-request.command.input.schema.json",
891
897
  "vendor/openai-codex/latest/hooks/permission-request.command.output.schema.json",
892
898
  "vendor/openai-codex/latest/hooks/post-compact.command.input.schema.json",
@@ -575,6 +575,10 @@ export declare function run(command: any, args?: any): Promise<void | {
575
575
  lane_count: number;
576
576
  physical_tmux_verified: boolean;
577
577
  physical_proof_status: any;
578
+ physical_proof_summary: string | null;
579
+ before_drain_proof: string | null;
580
+ after_drain_proof: string | null;
581
+ final_proof: string | null;
578
582
  list_panes_artifact: any;
579
583
  capture_pane_artifacts: any;
580
584
  };
@@ -601,6 +605,15 @@ export declare function run(command: any, args?: any): Promise<void | {
601
605
  killed_timed_out_sessions: any;
602
606
  fake_backend_disclaimer: string | null;
603
607
  };
608
+ runtime_truth_groups: {
609
+ Fake: string[];
610
+ Optional: string[];
611
+ Proven: string[];
612
+ Blocked: string[];
613
+ };
614
+ runtime_truth_matrix: string | null;
615
+ proof_level_by_subsystem: any;
616
+ fake_real_policy: string | null;
604
617
  blockers: any;
605
618
  };
606
619
  wrongness: {
@@ -677,6 +690,13 @@ export declare function run(command: any, args?: any): Promise<void | {
677
690
  tmux_pane_launch_count: number;
678
691
  physical_tmux_verified: boolean;
679
692
  tmux_physical_proof: string;
693
+ tmux_physical_proof_summary: string;
694
+ tmux_physical_proof_before_drain: string;
695
+ tmux_physical_proof_after_drain: string;
696
+ tmux_physical_proof_final: string;
697
+ tmux_physical_before_drain_ok: boolean;
698
+ tmux_physical_after_drain_ok: boolean;
699
+ tmux_physical_final_ok: boolean;
680
700
  tmux_list_panes_artifact: any;
681
701
  tmux_capture_pane_artifacts: any;
682
702
  tmux_pane_id_reconciled: boolean;
@@ -427,6 +427,10 @@ export declare function run(command: any, args?: any): Promise<void | {
427
427
  lane_count: number;
428
428
  physical_tmux_verified: boolean;
429
429
  physical_proof_status: any;
430
+ physical_proof_summary: string | null;
431
+ before_drain_proof: string | null;
432
+ after_drain_proof: string | null;
433
+ final_proof: string | null;
430
434
  list_panes_artifact: any;
431
435
  capture_pane_artifacts: any;
432
436
  };
@@ -453,6 +457,15 @@ export declare function run(command: any, args?: any): Promise<void | {
453
457
  killed_timed_out_sessions: any;
454
458
  fake_backend_disclaimer: string | null;
455
459
  };
460
+ runtime_truth_groups: {
461
+ Fake: string[];
462
+ Optional: string[];
463
+ Proven: string[];
464
+ Blocked: string[];
465
+ };
466
+ runtime_truth_matrix: string | null;
467
+ proof_level_by_subsystem: any;
468
+ fake_real_policy: string | null;
456
469
  blockers: any;
457
470
  };
458
471
  wrongness: {
@@ -529,6 +542,13 @@ export declare function run(command: any, args?: any): Promise<void | {
529
542
  tmux_pane_launch_count: number;
530
543
  physical_tmux_verified: boolean;
531
544
  tmux_physical_proof: string;
545
+ tmux_physical_proof_summary: string;
546
+ tmux_physical_proof_before_drain: string;
547
+ tmux_physical_proof_after_drain: string;
548
+ tmux_physical_proof_final: string;
549
+ tmux_physical_before_drain_ok: boolean;
550
+ tmux_physical_after_drain_ok: boolean;
551
+ tmux_physical_final_ok: boolean;
532
552
  tmux_list_panes_artifact: any;
533
553
  tmux_capture_pane_artifacts: any;
534
554
  tmux_pane_id_reconciled: boolean;
@@ -1,4 +1,4 @@
1
- export declare const AGENT_CLEANUP_PROOF_SCHEMA = "sks.agent-cleanup-proof.v1";
1
+ export declare const AGENT_CLEANUP_PROOF_SCHEMA = "sks.agent-cleanup-proof.v2";
2
2
  export declare const AGENT_CLEANUP_ACTION_LEDGER_SCHEMA = "sks.agent-cleanup-action-ledger.v1";
3
3
  export interface AgentCleanupExecutorOptions {
4
4
  missionDir: string;
@@ -8,6 +8,8 @@ export interface AgentCleanupExecutorOptions {
8
8
  dryRun?: boolean;
9
9
  drain?: boolean;
10
10
  staleMs?: number;
11
+ graceMs?: number;
12
+ killEscalation?: boolean;
11
13
  }
12
14
  type CleanupActionKind = 'terminate_process' | 'close_tmux_pane' | 'remove_temp_dir' | 'remove_lock' | 'skip_active_session' | 'skip_foreign_namespace' | 'archive_transcript_keep';
13
15
  interface CleanupAction {
@@ -16,6 +18,18 @@ interface CleanupAction {
16
18
  status: 'planned' | 'applied' | 'skipped' | 'failed';
17
19
  reason: string;
18
20
  error?: string;
21
+ process_tree?: ProcessTreeEntry[];
22
+ before?: Record<string, unknown>;
23
+ after?: Record<string, unknown>;
24
+ signal_sequence?: string[];
25
+ grace_ms?: number;
26
+ verified_exited?: boolean;
27
+ escalated_to_sigkill?: boolean;
28
+ }
29
+ interface ProcessTreeEntry {
30
+ pid: number;
31
+ ppid: number;
32
+ command: string;
19
33
  }
20
34
  export declare function runAgentCleanupExecutor(opts: AgentCleanupExecutorOptions): Promise<{
21
35
  schema: string;
@@ -28,8 +42,23 @@ export declare function runAgentCleanupExecutor(opts: AgentCleanupExecutorOption
28
42
  apply: boolean;
29
43
  stale_processes_found: string[];
30
44
  stale_processes_killed: string[];
45
+ process_trees: {
46
+ target: string;
47
+ tree: ProcessTreeEntry[];
48
+ }[];
49
+ process_tree_count: number;
50
+ sigterm_planned: string[];
51
+ sigterm_sent: string[];
52
+ sigkill_escalations: string[];
53
+ process_exit_verified: string[];
54
+ sigterm_count: number;
55
+ sigkill_count: number;
56
+ verified_exited_count: number;
57
+ failed_to_kill_count: number;
31
58
  stale_tmux_panes_found: string[];
32
59
  stale_tmux_panes_closed: string[];
60
+ tmux_panes_verified_closed: string[];
61
+ tmux_close_failures: string[];
33
62
  orphan_temp_dirs_found: string[];
34
63
  orphan_temp_dirs_removed: string[];
35
64
  stale_locks_found: string[];
@@ -3,7 +3,7 @@ import path from 'node:path';
3
3
  import { appendJsonl, exists, nowIso, readJson, writeJsonAtomic } from '../fsx.js';
4
4
  import { drainTmuxLaneSupervisor } from './tmux-lane-supervisor.js';
5
5
  import { normalizeAgentSessionRows } from './agent-session-rows.js';
6
- export const AGENT_CLEANUP_PROOF_SCHEMA = 'sks.agent-cleanup-proof.v1';
6
+ export const AGENT_CLEANUP_PROOF_SCHEMA = 'sks.agent-cleanup-proof.v2';
7
7
  export const AGENT_CLEANUP_ACTION_LEDGER_SCHEMA = 'sks.agent-cleanup-action-ledger.v1';
8
8
  const TERMINAL_STATUSES = new Set(['closed', 'completed', 'done', 'failed', 'blocked', 'killed', 'timed_out']);
9
9
  export async function runAgentCleanupExecutor(opts) {
@@ -21,28 +21,33 @@ export async function runAgentCleanupExecutor(opts) {
21
21
  .filter(Boolean));
22
22
  const now = Date.now();
23
23
  const staleMs = opts.staleMs ?? 30 * 60 * 1000;
24
+ const projectHash = String(namespace?.root_hash || '');
24
25
  const actions = [];
26
+ const graceMs = opts.graceMs ?? Number(process.env.SKS_CLEANUP_GRACE_MS || 750);
27
+ const killEscalation = opts.killEscalation !== false && process.env.SKS_CLEANUP_KILL_ESCALATION !== '0';
25
28
  const processReports = await listNamedFiles(path.join(agentRoot, 'sessions'), 'agent-process-report.json');
26
29
  for (const file of processReports) {
27
30
  const report = await readJson(file, null);
28
31
  const pid = Number(report?.pid || 0);
29
32
  const sessionId = String(report?.session_id || '');
30
33
  const status = String(sessions.find((row) => String(row.session_id || '') === sessionId)?.status || '');
31
- const terminal = TERMINAL_STATUSES.has(status) || report?.exit_code !== null;
34
+ const terminal = TERMINAL_STATUSES.has(status) || (report?.exit_code !== null && report?.exit_code !== undefined);
32
35
  if (!pid || !processIsAlive(pid))
33
36
  continue;
34
37
  if (activeSessionIds.has(sessionId) && !terminal) {
35
38
  actions.push({ kind: 'skip_active_session', target: sessionId || String(pid), status: 'skipped', reason: 'session_active' });
36
39
  continue;
37
40
  }
38
- actions.push(await applyAction({
39
- kind: 'terminate_process',
40
- target: String(pid),
41
+ if (!processReportInNamespace(report, projectHash)) {
42
+ actions.push({ kind: 'skip_foreign_namespace', target: String(pid), status: 'skipped', reason: 'process_outside_project_namespace' });
43
+ continue;
44
+ }
45
+ actions.push(await terminateProcessTreeAction({
46
+ pid,
41
47
  reason: terminal ? 'terminal_session_process_alive' : 'stale_session_process',
42
48
  apply,
43
- run: async () => {
44
- process.kill(pid, 'SIGTERM');
45
- }
49
+ graceMs,
50
+ killEscalation
46
51
  }));
47
52
  }
48
53
  const tmuxReports = await listNamedFiles(path.join(agentRoot, 'sessions'), 'agent-tmux-report.json');
@@ -52,7 +57,11 @@ export async function runAgentCleanupExecutor(opts) {
52
57
  const sessionId = String(report?.session_id || '');
53
58
  if (!validTmuxPaneId(paneId))
54
59
  continue;
55
- if (activeSessionIds.has(sessionId)) {
60
+ if (!processReportInNamespace(report, projectHash)) {
61
+ actions.push({ kind: 'skip_foreign_namespace', target: paneId, status: 'skipped', reason: 'tmux_pane_outside_project_namespace' });
62
+ continue;
63
+ }
64
+ if (activeSessionIds.has(sessionId) && opts.drain !== true) {
56
65
  actions.push({ kind: 'skip_active_session', target: sessionId || paneId, status: 'skipped', reason: 'tmux_session_active' });
57
66
  continue;
58
67
  }
@@ -61,13 +70,16 @@ export async function runAgentCleanupExecutor(opts) {
61
70
  target: paneId,
62
71
  reason: 'stale_tmux_pane',
63
72
  apply,
73
+ before: async () => ({ listed: await tmuxPaneListed(paneId), pane_id: paneId }),
74
+ after: async () => ({ listed: await tmuxPaneListed(paneId), pane_id: paneId }),
64
75
  run: async () => {
65
76
  const { runProcess } = await import('../fsx.js');
66
77
  await runProcess('tmux', ['kill-pane', '-t', paneId], { timeoutMs: 3000, maxOutputBytes: 4096 });
78
+ if (await tmuxPaneListed(paneId))
79
+ throw new Error('tmux_pane_still_listed_after_kill');
67
80
  }
68
81
  }));
69
82
  }
70
- const projectHash = String(namespace?.root_hash || '');
71
83
  for (const dir of Array.isArray(namespace?.orphan_temp_dirs) ? namespace.orphan_temp_dirs.map(String) : []) {
72
84
  if (!namespaceOwnsPath(dir, projectHash)) {
73
85
  actions.push({ kind: 'skip_foreign_namespace', target: dir, status: 'skipped', reason: 'path_outside_project_namespace' });
@@ -80,7 +92,13 @@ export async function runAgentCleanupExecutor(opts) {
80
92
  target: dir,
81
93
  reason: 'orphan_temp_dir',
82
94
  apply,
83
- run: async () => fsp.rm(dir, { recursive: true, force: true })
95
+ before: async () => ({ exists: await exists(dir) }),
96
+ after: async () => ({ exists: await exists(dir) }),
97
+ run: async () => {
98
+ await fsp.rm(dir, { recursive: true, force: true });
99
+ if (await exists(dir))
100
+ throw new Error('temp_dir_still_exists_after_remove');
101
+ }
84
102
  }));
85
103
  }
86
104
  for (const lock of await staleLockFiles(String(namespace?.lock_dir || ''), projectHash, now, staleMs)) {
@@ -89,7 +107,13 @@ export async function runAgentCleanupExecutor(opts) {
89
107
  target: lock,
90
108
  reason: 'stale_lock_file',
91
109
  apply,
92
- run: async () => fsp.rm(lock, { force: true })
110
+ before: async () => ({ exists: await exists(lock) }),
111
+ after: async () => ({ exists: await exists(lock) }),
112
+ run: async () => {
113
+ await fsp.rm(lock, { force: true });
114
+ if (await exists(lock))
115
+ throw new Error('lock_file_still_exists_after_remove');
116
+ }
93
117
  }));
94
118
  }
95
119
  for (const transcript of await listNamedFiles(path.join(agentRoot, 'sessions'), 'agent-terminal-session.json')) {
@@ -139,8 +163,20 @@ function buildCleanupProof(input) {
139
163
  apply: input.apply,
140
164
  stale_processes_found: byKind('terminate_process').map((row) => row.target),
141
165
  stale_processes_killed: byKind('terminate_process', 'applied').map((row) => row.target),
166
+ process_trees: byKind('terminate_process').map((row) => ({ target: row.target, tree: row.process_tree || [] })),
167
+ process_tree_count: byKind('terminate_process').filter((row) => (row.process_tree || []).length > 0).length,
168
+ sigterm_planned: input.actions.filter((row) => row.status === 'planned' && row.signal_sequence?.includes('SIGTERM')).map((row) => row.target),
169
+ sigterm_sent: input.actions.filter((row) => row.status === 'applied' && row.signal_sequence?.includes('SIGTERM')).map((row) => row.target),
170
+ sigkill_escalations: input.actions.filter((row) => row.escalated_to_sigkill === true).map((row) => row.target),
171
+ process_exit_verified: input.actions.filter((row) => row.kind === 'terminate_process' && row.verified_exited === true).map((row) => row.target),
172
+ sigterm_count: input.actions.filter((row) => row.signal_sequence?.includes('SIGTERM')).length,
173
+ sigkill_count: input.actions.filter((row) => row.signal_sequence?.includes('SIGKILL')).length,
174
+ verified_exited_count: input.actions.filter((row) => row.kind === 'terminate_process' && row.verified_exited === true).length,
175
+ failed_to_kill_count: input.actions.filter((row) => row.kind === 'terminate_process' && row.status === 'failed').length,
142
176
  stale_tmux_panes_found: byKind('close_tmux_pane').map((row) => row.target),
143
177
  stale_tmux_panes_closed: byKind('close_tmux_pane', 'applied').map((row) => row.target),
178
+ tmux_panes_verified_closed: byKind('close_tmux_pane', 'applied').filter((row) => row.after?.listed === false).map((row) => row.target),
179
+ tmux_close_failures: byKind('close_tmux_pane', 'failed').map((row) => row.target),
144
180
  orphan_temp_dirs_found: byKind('remove_temp_dir').map((row) => row.target),
145
181
  orphan_temp_dirs_removed: byKind('remove_temp_dir', 'applied').map((row) => row.target),
146
182
  stale_locks_found: byKind('remove_lock').map((row) => row.target),
@@ -155,15 +191,84 @@ function buildCleanupProof(input) {
155
191
  blockers: failed.map((row) => `cleanup_action_failed:${row.kind}:${row.target}`)
156
192
  };
157
193
  }
194
+ async function terminateProcessTreeAction(input) {
195
+ const processTree = await readProcessTree(input.pid);
196
+ const targets = processTree.length ? processTree.map((row) => row.pid) : [input.pid];
197
+ if (!input.apply) {
198
+ return {
199
+ kind: 'terminate_process',
200
+ target: String(input.pid),
201
+ status: 'planned',
202
+ reason: input.reason,
203
+ process_tree: processTree,
204
+ before: { alive: targets.filter(processIsAlive) },
205
+ after: { alive: targets.filter(processIsAlive) },
206
+ signal_sequence: input.killEscalation ? ['SIGTERM', 'SIGKILL_IF_STILL_ALIVE'] : ['SIGTERM'],
207
+ grace_ms: input.graceMs,
208
+ verified_exited: false,
209
+ escalated_to_sigkill: false
210
+ };
211
+ }
212
+ const signalSequence = [];
213
+ try {
214
+ for (const pid of [...targets].reverse())
215
+ safeKill(pid, 'SIGTERM');
216
+ signalSequence.push('SIGTERM');
217
+ await waitForProcessesExited(targets, input.graceMs);
218
+ let alive = targets.filter(processIsAlive);
219
+ let escalated = false;
220
+ if (alive.length && input.killEscalation) {
221
+ for (const pid of [...alive].reverse())
222
+ safeKill(pid, 'SIGKILL');
223
+ signalSequence.push('SIGKILL');
224
+ escalated = true;
225
+ await waitForProcessesExited(targets, 500);
226
+ alive = targets.filter(processIsAlive);
227
+ }
228
+ return {
229
+ kind: 'terminate_process',
230
+ target: String(input.pid),
231
+ status: alive.length ? 'failed' : 'applied',
232
+ reason: input.reason,
233
+ process_tree: processTree,
234
+ before: { alive: targets },
235
+ after: { alive },
236
+ signal_sequence: signalSequence,
237
+ grace_ms: input.graceMs,
238
+ verified_exited: alive.length === 0,
239
+ escalated_to_sigkill: escalated,
240
+ ...(alive.length ? { error: `processes_still_alive:${alive.join(',')}` } : {})
241
+ };
242
+ }
243
+ catch (err) {
244
+ return {
245
+ kind: 'terminate_process',
246
+ target: String(input.pid),
247
+ status: 'failed',
248
+ reason: input.reason,
249
+ process_tree: processTree,
250
+ before: { alive: targets },
251
+ after: { alive: targets.filter(processIsAlive) },
252
+ signal_sequence: signalSequence,
253
+ grace_ms: input.graceMs,
254
+ verified_exited: false,
255
+ escalated_to_sigkill: signalSequence.includes('SIGKILL'),
256
+ error: err instanceof Error ? err.message : String(err)
257
+ };
258
+ }
259
+ }
158
260
  async function applyAction(input) {
261
+ const before = input.before ? await input.before().catch((err) => ({ error: err instanceof Error ? err.message : String(err) })) : undefined;
159
262
  if (!input.apply)
160
- return { kind: input.kind, target: input.target, status: 'planned', reason: input.reason };
263
+ return { kind: input.kind, target: input.target, status: 'planned', reason: input.reason, ...(before ? { before, after: before } : {}) };
161
264
  try {
162
265
  await input.run();
163
- return { kind: input.kind, target: input.target, status: 'applied', reason: input.reason };
266
+ const after = input.after ? await input.after().catch((err) => ({ error: err instanceof Error ? err.message : String(err) })) : undefined;
267
+ return { kind: input.kind, target: input.target, status: 'applied', reason: input.reason, ...(before ? { before } : {}), ...(after ? { after } : {}) };
164
268
  }
165
269
  catch (err) {
166
- return { kind: input.kind, target: input.target, status: 'failed', reason: input.reason, error: err instanceof Error ? err.message : String(err) };
270
+ const after = input.after ? await input.after().catch((afterErr) => ({ error: afterErr instanceof Error ? afterErr.message : String(afterErr) })) : undefined;
271
+ return { kind: input.kind, target: input.target, status: 'failed', reason: input.reason, error: err instanceof Error ? err.message : String(err), ...(before ? { before } : {}), ...(after ? { after } : {}) };
167
272
  }
168
273
  }
169
274
  async function staleLockFiles(lockDir, projectHash, now, staleMs) {
@@ -196,6 +301,19 @@ async function listFiles(dir) {
196
301
  function namespaceOwnsPath(candidate, projectHash) {
197
302
  return Boolean(candidate && (!projectHash || candidate.includes(projectHash)));
198
303
  }
304
+ function processReportInNamespace(report, projectHash) {
305
+ if (!projectHash)
306
+ return true;
307
+ const raw = JSON.stringify({
308
+ project_hash: report?.project_hash,
309
+ root_hash: report?.root_hash,
310
+ project_namespace: report?.project_namespace,
311
+ cwd: report?.cwd,
312
+ stdout_log: report?.stdout_log,
313
+ stderr_log: report?.stderr_log
314
+ });
315
+ return raw === '{}' || raw.includes(projectHash) || (!report?.project_hash && !report?.root_hash && !report?.project_namespace);
316
+ }
199
317
  function processIsAlive(pid) {
200
318
  try {
201
319
  process.kill(pid, 0);
@@ -205,7 +323,60 @@ function processIsAlive(pid) {
205
323
  return false;
206
324
  }
207
325
  }
326
+ async function readProcessTree(rootPid) {
327
+ try {
328
+ const { runProcess } = await import('../fsx.js');
329
+ const result = await runProcess('ps', ['-axo', 'pid=,ppid=,command='], { timeoutMs: 3000, maxOutputBytes: 512 * 1024 });
330
+ const rows = result.stdout.split(/\r?\n/).map((line) => {
331
+ const match = line.match(/^\s*(\d+)\s+(\d+)\s+(.+)$/);
332
+ if (!match)
333
+ return null;
334
+ return { pid: Number(match[1]), ppid: Number(match[2]), command: match[3] || '' };
335
+ }).filter(Boolean);
336
+ const byParent = new Map();
337
+ for (const row of rows)
338
+ byParent.set(row.ppid, [...(byParent.get(row.ppid) || []), row]);
339
+ const out = [];
340
+ const visit = (pid) => {
341
+ const current = rows.find((row) => row.pid === pid);
342
+ if (current && !out.some((row) => row.pid === current.pid))
343
+ out.push(current);
344
+ for (const child of byParent.get(pid) || [])
345
+ visit(child.pid);
346
+ };
347
+ visit(rootPid);
348
+ return out;
349
+ }
350
+ catch {
351
+ return processIsAlive(rootPid) ? [{ pid: rootPid, ppid: 0, command: 'unknown' }] : [];
352
+ }
353
+ }
354
+ function safeKill(pid, signal) {
355
+ try {
356
+ process.kill(pid, signal);
357
+ }
358
+ catch { }
359
+ }
360
+ async function waitForProcessesExited(pids, timeoutMs) {
361
+ const deadline = Date.now() + timeoutMs;
362
+ while (Date.now() < deadline) {
363
+ if (!pids.some(processIsAlive))
364
+ return true;
365
+ await new Promise((resolve) => setTimeout(resolve, 50));
366
+ }
367
+ return !pids.some(processIsAlive);
368
+ }
208
369
  function validTmuxPaneId(value) {
209
370
  return /^%\d+$/.test(value);
210
371
  }
372
+ async function tmuxPaneListed(paneId) {
373
+ try {
374
+ const { runProcess } = await import('../fsx.js');
375
+ const listed = await runProcess('tmux', ['list-panes', '-a', '-F', '#{pane_id}'], { timeoutMs: 3000, maxOutputBytes: 4096 });
376
+ return listed.stdout.split(/\r?\n/).includes(paneId);
377
+ }
378
+ catch {
379
+ return false;
380
+ }
381
+ }
211
382
  //# sourceMappingURL=agent-cleanup-executor.js.map
@@ -16,6 +16,9 @@ export declare function parseAgentCommandArgs(command: string, args?: string[]):
16
16
  apply: boolean;
17
17
  dryRun: boolean;
18
18
  drain: boolean;
19
+ staleMs: number;
20
+ graceMs: number;
21
+ killEscalation: boolean;
19
22
  json: boolean;
20
23
  missionId: string;
21
24
  lane: string;
@@ -19,15 +19,18 @@ export function parseAgentCommandArgs(command, args = []) {
19
19
  const apply = hasFlag(args, '--apply');
20
20
  const dryRun = hasFlag(args, '--dry-run') || hasFlag(args, '--dryrun');
21
21
  const drain = hasFlag(args, '--drain');
22
+ const staleMs = Number(readOption(args, '--stale-ms', 30 * 60 * 1000));
23
+ const graceMs = Number(readOption(args, '--grace-ms', 750));
24
+ const killEscalation = hasFlag(args, '--kill-escalation') || !hasFlag(args, '--no-kill-escalation');
22
25
  const codexApp = hasFlag(args, '--codex-app');
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']));
26
+ 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', '--stale-ms', '--grace-ms']));
24
27
  const missionDefault = action === 'run' || action === 'spawn' || action === 'plan' ? '' : 'latest';
25
28
  const positionalMission = action === 'run' || action === 'spawn' || action === 'plan' ? '' : (positionals[0] || '');
26
29
  const missionId = String(readOption(args, '--mission', readOption(args, '--mission-id', positionalMission || missionDefault)));
27
30
  const lane = String(readOption(args, '--agent', readOption(args, '--lane', '')));
28
31
  const promptPositionals = positionalMission ? positionals.slice(1) : positionals;
29
32
  const prompt = promptPositionals.join(' ').trim() || 'Native agent run';
30
- return { command, action, prompt, route, agents, targetActiveSlots, desiredWorkItemCount, minimumWorkItems, maxQueueExpansion, concurrency, backend, mock, real, readonly, apply, dryRun, drain, json, missionId, lane, codexApp };
33
+ return { command, action, prompt, route, agents, targetActiveSlots, desiredWorkItemCount, minimumWorkItems, maxQueueExpansion, concurrency, backend, mock, real, readonly, apply, dryRun, drain, staleMs, graceMs, killEscalation, json, missionId, lane, codexApp };
31
34
  }
32
35
  function hasFlag(args, flag) {
33
36
  return args.includes(flag);
@@ -194,6 +194,10 @@ export declare function runNativeAgentOrchestrator(opts?: AgentRunOptions): Prom
194
194
  lane_count: number;
195
195
  physical_tmux_verified: boolean;
196
196
  physical_proof_status: any;
197
+ physical_proof_summary: string | null;
198
+ before_drain_proof: string | null;
199
+ after_drain_proof: string | null;
200
+ final_proof: string | null;
197
201
  list_panes_artifact: any;
198
202
  capture_pane_artifacts: any;
199
203
  };
@@ -220,6 +224,15 @@ export declare function runNativeAgentOrchestrator(opts?: AgentRunOptions): Prom
220
224
  killed_timed_out_sessions: any;
221
225
  fake_backend_disclaimer: string | null;
222
226
  };
227
+ runtime_truth_groups: {
228
+ Fake: string[];
229
+ Optional: string[];
230
+ Proven: string[];
231
+ Blocked: string[];
232
+ };
233
+ runtime_truth_matrix: string | null;
234
+ proof_level_by_subsystem: any;
235
+ fake_real_policy: string | null;
223
236
  blockers: any;
224
237
  };
225
238
  wrongness: {
@@ -296,6 +309,13 @@ export declare function runNativeAgentOrchestrator(opts?: AgentRunOptions): Prom
296
309
  tmux_pane_launch_count: number;
297
310
  physical_tmux_verified: boolean;
298
311
  tmux_physical_proof: string;
312
+ tmux_physical_proof_summary: string;
313
+ tmux_physical_proof_before_drain: string;
314
+ tmux_physical_proof_after_drain: string;
315
+ tmux_physical_proof_final: string;
316
+ tmux_physical_before_drain_ok: boolean;
317
+ tmux_physical_after_drain_ok: boolean;
318
+ tmux_physical_final_ok: boolean;
299
319
  tmux_list_panes_artifact: any;
300
320
  tmux_capture_pane_artifacts: any;
301
321
  tmux_pane_id_reconciled: boolean;