sneakoscope 1.18.4 → 1.18.5

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 (42) hide show
  1. package/README.md +6 -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/build-manifest.json +11 -9
  7. package/dist/commands/image-ux-review.d.ts +17 -0
  8. package/dist/commands/ppt.d.ts +17 -0
  9. package/dist/core/agents/agent-cleanup-executor.d.ts +21 -1
  10. package/dist/core/agents/agent-cleanup-executor.js +141 -9
  11. package/dist/core/agents/agent-command-surface.d.ts +1 -0
  12. package/dist/core/agents/agent-command-surface.js +3 -2
  13. package/dist/core/agents/agent-orchestrator.d.ts +17 -0
  14. package/dist/core/agents/agent-orchestrator.js +7 -4
  15. package/dist/core/agents/agent-output-validator.d.ts +13 -0
  16. package/dist/core/agents/agent-output-validator.js +12 -1
  17. package/dist/core/agents/agent-proof-evidence.d.ts +7 -0
  18. package/dist/core/agents/agent-proof-evidence.js +15 -0
  19. package/dist/core/agents/agent-runner-codex-exec.js +6 -1
  20. package/dist/core/agents/agent-trust-report.d.ts +10 -0
  21. package/dist/core/agents/agent-trust-report.js +32 -0
  22. package/dist/core/agents/intelligent-work-graph.d.ts +12 -1
  23. package/dist/core/agents/intelligent-work-graph.js +162 -10
  24. package/dist/core/agents/route-collaboration-ledger.d.ts +17 -0
  25. package/dist/core/agents/tmux-physical-proof.d.ts +96 -8
  26. package/dist/core/agents/tmux-physical-proof.js +85 -16
  27. package/dist/core/commands/agent-command.js +12 -1
  28. package/dist/core/commands/image-ux-review-command.d.ts +17 -0
  29. package/dist/core/commands/ppt-command.d.ts +17 -0
  30. package/dist/core/fsx.d.ts +1 -1
  31. package/dist/core/fsx.js +1 -1
  32. package/dist/core/hooks-runtime.js +12 -1
  33. package/dist/core/image-ux-review/imagegen-adapter.js +210 -10
  34. package/dist/core/proof/fake-real-proof-policy.d.ts +3 -2
  35. package/dist/core/proof/fake-real-proof-policy.js +31 -8
  36. package/dist/core/routes.js +1 -1
  37. package/dist/core/version.d.ts +1 -1
  38. package/dist/core/version.js +1 -1
  39. package/dist/scripts/release-parallel-check.d.ts +3 -0
  40. package/dist/scripts/release-parallel-check.js +204 -0
  41. package/package.json +12 -4
  42. 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.5** wires the 1.18.4 real-proof tools into the runtime lifecycle: real tmux mode now writes before-drain, after-drain, and final physical pane proof artifacts; real Codex dynamic smoke validates output-schema/result-file/process cleanup and reports `fixture_instrumented_real` honestly; `sks agent close/cleanup` performs process-tree-aware SIGTERM/SIGKILL cleanup; and the task graph carries AST/import/test ownership, critical path, and runtime truth matrix evidence.
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,7 @@ 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
+ - Migration 1.18.4 to 1.18.5: [docs/migration-1.18.4-to-1.18.5.md](docs/migration-1.18.4-to-1.18.5.md)
72
76
  - Codex official Goal mode: [docs/codex-official-goal-mode.md](docs/codex-official-goal-mode.md)
73
77
  - Release parallel full coverage: [docs/release-parallel-full-coverage.md](docs/release-parallel-full-coverage.md)
74
78
  - 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.5"
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.5"
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.5"),
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.5",
5
+ "source_digest": "25698b68e8f9d6ff16ac94f404e60d99db9841e8e85a0f8c5831069a744434cc",
6
+ "source_file_count": 1430,
7
+ "built_at_source_time": 1779807721873
8
8
  }
@@ -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.5",
4
+ "package_version": "1.18.5",
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": 874,
8
+ "compiled_js_count": 437,
9
+ "compiled_dts_count": 437,
10
+ "source_digest": "25698b68e8f9d6ff16ac94f404e60d99db9841e8e85a0f8c5831069a744434cc",
11
+ "source_file_count": 1430,
12
+ "source_files_hash": "f8db0c06ebed73d7f4ee1f33117ef1dc633b59312f6d976ca6abf2e525d527fe",
13
+ "source_list_hash": "f8db0c06ebed73d7f4ee1f33117ef1dc633b59312f6d976ca6abf2e525d527fe",
14
14
  "src_mjs_runtime_files": 0,
15
15
  "dist_stamp_schema": "sks.dist-build-stamp.v1",
16
16
  "files": [
@@ -887,6 +887,8 @@
887
887
  "core/wiki-image/visual-anchor.js",
888
888
  "core/work-order-ledger.d.ts",
889
889
  "core/work-order-ledger.js",
890
+ "scripts/release-parallel-check.d.ts",
891
+ "scripts/release-parallel-check.js",
890
892
  "vendor/openai-codex/latest/hooks/permission-request.command.input.schema.json",
891
893
  "vendor/openai-codex/latest/hooks/permission-request.command.output.schema.json",
892
894
  "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,12 @@ 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
+ };
604
614
  blockers: any;
605
615
  };
606
616
  wrongness: {
@@ -677,6 +687,13 @@ export declare function run(command: any, args?: any): Promise<void | {
677
687
  tmux_pane_launch_count: number;
678
688
  physical_tmux_verified: boolean;
679
689
  tmux_physical_proof: string;
690
+ tmux_physical_proof_summary: string;
691
+ tmux_physical_proof_before_drain: string;
692
+ tmux_physical_proof_after_drain: string;
693
+ tmux_physical_proof_final: string;
694
+ tmux_physical_before_drain_ok: boolean;
695
+ tmux_physical_after_drain_ok: boolean;
696
+ tmux_physical_final_ok: boolean;
680
697
  tmux_list_panes_artifact: any;
681
698
  tmux_capture_pane_artifacts: any;
682
699
  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,12 @@ 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
+ };
456
466
  blockers: any;
457
467
  };
458
468
  wrongness: {
@@ -529,6 +539,13 @@ export declare function run(command: any, args?: any): Promise<void | {
529
539
  tmux_pane_launch_count: number;
530
540
  physical_tmux_verified: boolean;
531
541
  tmux_physical_proof: string;
542
+ tmux_physical_proof_summary: string;
543
+ tmux_physical_proof_before_drain: string;
544
+ tmux_physical_proof_after_drain: string;
545
+ tmux_physical_proof_final: string;
546
+ tmux_physical_before_drain_ok: boolean;
547
+ tmux_physical_after_drain_ok: boolean;
548
+ tmux_physical_final_ok: boolean;
532
549
  tmux_list_panes_artifact: any;
533
550
  tmux_capture_pane_artifacts: any;
534
551
  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;
@@ -16,6 +16,18 @@ interface CleanupAction {
16
16
  status: 'planned' | 'applied' | 'skipped' | 'failed';
17
17
  reason: string;
18
18
  error?: string;
19
+ process_tree?: ProcessTreeEntry[];
20
+ before?: Record<string, unknown>;
21
+ after?: Record<string, unknown>;
22
+ signal_sequence?: string[];
23
+ grace_ms?: number;
24
+ verified_exited?: boolean;
25
+ escalated_to_sigkill?: boolean;
26
+ }
27
+ interface ProcessTreeEntry {
28
+ pid: number;
29
+ ppid: number;
30
+ command: string;
19
31
  }
20
32
  export declare function runAgentCleanupExecutor(opts: AgentCleanupExecutorOptions): Promise<{
21
33
  schema: string;
@@ -28,6 +40,14 @@ export declare function runAgentCleanupExecutor(opts: AgentCleanupExecutorOption
28
40
  apply: boolean;
29
41
  stale_processes_found: string[];
30
42
  stale_processes_killed: string[];
43
+ process_trees: {
44
+ target: string;
45
+ tree: ProcessTreeEntry[];
46
+ }[];
47
+ sigterm_planned: string[];
48
+ sigterm_sent: string[];
49
+ sigkill_escalations: string[];
50
+ process_exit_verified: string[];
31
51
  stale_tmux_panes_found: string[];
32
52
  stale_tmux_panes_closed: string[];
33
53
  orphan_temp_dirs_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,31 @@ 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 = Number(process.env.SKS_CLEANUP_GRACE_MS || 750);
25
27
  const processReports = await listNamedFiles(path.join(agentRoot, 'sessions'), 'agent-process-report.json');
26
28
  for (const file of processReports) {
27
29
  const report = await readJson(file, null);
28
30
  const pid = Number(report?.pid || 0);
29
31
  const sessionId = String(report?.session_id || '');
30
32
  const status = String(sessions.find((row) => String(row.session_id || '') === sessionId)?.status || '');
31
- const terminal = TERMINAL_STATUSES.has(status) || report?.exit_code !== null;
33
+ const terminal = TERMINAL_STATUSES.has(status) || (report?.exit_code !== null && report?.exit_code !== undefined);
32
34
  if (!pid || !processIsAlive(pid))
33
35
  continue;
34
36
  if (activeSessionIds.has(sessionId) && !terminal) {
35
37
  actions.push({ kind: 'skip_active_session', target: sessionId || String(pid), status: 'skipped', reason: 'session_active' });
36
38
  continue;
37
39
  }
38
- actions.push(await applyAction({
39
- kind: 'terminate_process',
40
- target: String(pid),
40
+ if (!processReportInNamespace(report, projectHash)) {
41
+ actions.push({ kind: 'skip_foreign_namespace', target: String(pid), status: 'skipped', reason: 'process_outside_project_namespace' });
42
+ continue;
43
+ }
44
+ actions.push(await terminateProcessTreeAction({
45
+ pid,
41
46
  reason: terminal ? 'terminal_session_process_alive' : 'stale_session_process',
42
47
  apply,
43
- run: async () => {
44
- process.kill(pid, 'SIGTERM');
45
- }
48
+ graceMs
46
49
  }));
47
50
  }
48
51
  const tmuxReports = await listNamedFiles(path.join(agentRoot, 'sessions'), 'agent-tmux-report.json');
@@ -64,10 +67,12 @@ export async function runAgentCleanupExecutor(opts) {
64
67
  run: async () => {
65
68
  const { runProcess } = await import('../fsx.js');
66
69
  await runProcess('tmux', ['kill-pane', '-t', paneId], { timeoutMs: 3000, maxOutputBytes: 4096 });
70
+ const listed = await runProcess('tmux', ['list-panes', '-a', '-F', '#{pane_id}'], { timeoutMs: 3000, maxOutputBytes: 4096 });
71
+ if (listed.stdout.split(/\r?\n/).includes(paneId))
72
+ throw new Error('tmux_pane_still_listed_after_kill');
67
73
  }
68
74
  }));
69
75
  }
70
- const projectHash = String(namespace?.root_hash || '');
71
76
  for (const dir of Array.isArray(namespace?.orphan_temp_dirs) ? namespace.orphan_temp_dirs.map(String) : []) {
72
77
  if (!namespaceOwnsPath(dir, projectHash)) {
73
78
  actions.push({ kind: 'skip_foreign_namespace', target: dir, status: 'skipped', reason: 'path_outside_project_namespace' });
@@ -139,6 +144,11 @@ function buildCleanupProof(input) {
139
144
  apply: input.apply,
140
145
  stale_processes_found: byKind('terminate_process').map((row) => row.target),
141
146
  stale_processes_killed: byKind('terminate_process', 'applied').map((row) => row.target),
147
+ process_trees: byKind('terminate_process').map((row) => ({ target: row.target, tree: row.process_tree || [] })),
148
+ sigterm_planned: input.actions.filter((row) => row.status === 'planned' && row.signal_sequence?.includes('SIGTERM')).map((row) => row.target),
149
+ sigterm_sent: input.actions.filter((row) => row.status === 'applied' && row.signal_sequence?.includes('SIGTERM')).map((row) => row.target),
150
+ sigkill_escalations: input.actions.filter((row) => row.escalated_to_sigkill === true).map((row) => row.target),
151
+ process_exit_verified: input.actions.filter((row) => row.kind === 'terminate_process' && row.verified_exited === true).map((row) => row.target),
142
152
  stale_tmux_panes_found: byKind('close_tmux_pane').map((row) => row.target),
143
153
  stale_tmux_panes_closed: byKind('close_tmux_pane', 'applied').map((row) => row.target),
144
154
  orphan_temp_dirs_found: byKind('remove_temp_dir').map((row) => row.target),
@@ -155,6 +165,72 @@ function buildCleanupProof(input) {
155
165
  blockers: failed.map((row) => `cleanup_action_failed:${row.kind}:${row.target}`)
156
166
  };
157
167
  }
168
+ async function terminateProcessTreeAction(input) {
169
+ const processTree = await readProcessTree(input.pid);
170
+ const targets = processTree.length ? processTree.map((row) => row.pid) : [input.pid];
171
+ if (!input.apply) {
172
+ return {
173
+ kind: 'terminate_process',
174
+ target: String(input.pid),
175
+ status: 'planned',
176
+ reason: input.reason,
177
+ process_tree: processTree,
178
+ before: { alive: targets.filter(processIsAlive) },
179
+ after: { alive: targets.filter(processIsAlive) },
180
+ signal_sequence: ['SIGTERM', 'SIGKILL_IF_STILL_ALIVE'],
181
+ grace_ms: input.graceMs,
182
+ verified_exited: false,
183
+ escalated_to_sigkill: false
184
+ };
185
+ }
186
+ const signalSequence = [];
187
+ try {
188
+ for (const pid of [...targets].reverse())
189
+ safeKill(pid, 'SIGTERM');
190
+ signalSequence.push('SIGTERM');
191
+ await waitForProcessesExited(targets, input.graceMs);
192
+ let alive = targets.filter(processIsAlive);
193
+ let escalated = false;
194
+ if (alive.length) {
195
+ for (const pid of [...alive].reverse())
196
+ safeKill(pid, 'SIGKILL');
197
+ signalSequence.push('SIGKILL');
198
+ escalated = true;
199
+ await waitForProcessesExited(targets, 500);
200
+ alive = targets.filter(processIsAlive);
201
+ }
202
+ return {
203
+ kind: 'terminate_process',
204
+ target: String(input.pid),
205
+ status: alive.length ? 'failed' : 'applied',
206
+ reason: input.reason,
207
+ process_tree: processTree,
208
+ before: { alive: targets },
209
+ after: { alive },
210
+ signal_sequence: signalSequence,
211
+ grace_ms: input.graceMs,
212
+ verified_exited: alive.length === 0,
213
+ escalated_to_sigkill: escalated,
214
+ ...(alive.length ? { error: `processes_still_alive:${alive.join(',')}` } : {})
215
+ };
216
+ }
217
+ catch (err) {
218
+ return {
219
+ kind: 'terminate_process',
220
+ target: String(input.pid),
221
+ status: 'failed',
222
+ reason: input.reason,
223
+ process_tree: processTree,
224
+ before: { alive: targets },
225
+ after: { alive: targets.filter(processIsAlive) },
226
+ signal_sequence: signalSequence,
227
+ grace_ms: input.graceMs,
228
+ verified_exited: false,
229
+ escalated_to_sigkill: signalSequence.includes('SIGKILL'),
230
+ error: err instanceof Error ? err.message : String(err)
231
+ };
232
+ }
233
+ }
158
234
  async function applyAction(input) {
159
235
  if (!input.apply)
160
236
  return { kind: input.kind, target: input.target, status: 'planned', reason: input.reason };
@@ -196,6 +272,19 @@ async function listFiles(dir) {
196
272
  function namespaceOwnsPath(candidate, projectHash) {
197
273
  return Boolean(candidate && (!projectHash || candidate.includes(projectHash)));
198
274
  }
275
+ function processReportInNamespace(report, projectHash) {
276
+ if (!projectHash)
277
+ return true;
278
+ const raw = JSON.stringify({
279
+ project_hash: report?.project_hash,
280
+ root_hash: report?.root_hash,
281
+ project_namespace: report?.project_namespace,
282
+ cwd: report?.cwd,
283
+ stdout_log: report?.stdout_log,
284
+ stderr_log: report?.stderr_log
285
+ });
286
+ return raw === '{}' || raw.includes(projectHash) || (!report?.project_hash && !report?.root_hash && !report?.project_namespace);
287
+ }
199
288
  function processIsAlive(pid) {
200
289
  try {
201
290
  process.kill(pid, 0);
@@ -205,6 +294,49 @@ function processIsAlive(pid) {
205
294
  return false;
206
295
  }
207
296
  }
297
+ async function readProcessTree(rootPid) {
298
+ try {
299
+ const { runProcess } = await import('../fsx.js');
300
+ const result = await runProcess('ps', ['-axo', 'pid=,ppid=,command='], { timeoutMs: 3000, maxOutputBytes: 512 * 1024 });
301
+ const rows = result.stdout.split(/\r?\n/).map((line) => {
302
+ const match = line.match(/^\s*(\d+)\s+(\d+)\s+(.+)$/);
303
+ if (!match)
304
+ return null;
305
+ return { pid: Number(match[1]), ppid: Number(match[2]), command: match[3] || '' };
306
+ }).filter(Boolean);
307
+ const byParent = new Map();
308
+ for (const row of rows)
309
+ byParent.set(row.ppid, [...(byParent.get(row.ppid) || []), row]);
310
+ const out = [];
311
+ const visit = (pid) => {
312
+ const current = rows.find((row) => row.pid === pid);
313
+ if (current && !out.some((row) => row.pid === current.pid))
314
+ out.push(current);
315
+ for (const child of byParent.get(pid) || [])
316
+ visit(child.pid);
317
+ };
318
+ visit(rootPid);
319
+ return out;
320
+ }
321
+ catch {
322
+ return processIsAlive(rootPid) ? [{ pid: rootPid, ppid: 0, command: 'unknown' }] : [];
323
+ }
324
+ }
325
+ function safeKill(pid, signal) {
326
+ try {
327
+ process.kill(pid, signal);
328
+ }
329
+ catch { }
330
+ }
331
+ async function waitForProcessesExited(pids, timeoutMs) {
332
+ const deadline = Date.now() + timeoutMs;
333
+ while (Date.now() < deadline) {
334
+ if (!pids.some(processIsAlive))
335
+ return true;
336
+ await new Promise((resolve) => setTimeout(resolve, 50));
337
+ }
338
+ return !pids.some(processIsAlive);
339
+ }
208
340
  function validTmuxPaneId(value) {
209
341
  return /^%\d+$/.test(value);
210
342
  }
@@ -16,6 +16,7 @@ export declare function parseAgentCommandArgs(command: string, args?: string[]):
16
16
  apply: boolean;
17
17
  dryRun: boolean;
18
18
  drain: boolean;
19
+ staleMs: number;
19
20
  json: boolean;
20
21
  missionId: string;
21
22
  lane: string;
@@ -19,15 +19,16 @@ 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));
22
23
  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']));
24
+ 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']));
24
25
  const missionDefault = action === 'run' || action === 'spawn' || action === 'plan' ? '' : 'latest';
25
26
  const positionalMission = action === 'run' || action === 'spawn' || action === 'plan' ? '' : (positionals[0] || '');
26
27
  const missionId = String(readOption(args, '--mission', readOption(args, '--mission-id', positionalMission || missionDefault)));
27
28
  const lane = String(readOption(args, '--agent', readOption(args, '--lane', '')));
28
29
  const promptPositionals = positionalMission ? positionals.slice(1) : positionals;
29
30
  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 };
31
+ return { command, action, prompt, route, agents, targetActiveSlots, desiredWorkItemCount, minimumWorkItems, maxQueueExpansion, concurrency, backend, mock, real, readonly, apply, dryRun, drain, staleMs, json, missionId, lane, codexApp };
31
32
  }
32
33
  function hasFlag(args, flag) {
33
34
  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,12 @@ 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
+ };
223
233
  blockers: any;
224
234
  };
225
235
  wrongness: {
@@ -296,6 +306,13 @@ export declare function runNativeAgentOrchestrator(opts?: AgentRunOptions): Prom
296
306
  tmux_pane_launch_count: number;
297
307
  physical_tmux_verified: boolean;
298
308
  tmux_physical_proof: string;
309
+ tmux_physical_proof_summary: string;
310
+ tmux_physical_proof_before_drain: string;
311
+ tmux_physical_proof_after_drain: string;
312
+ tmux_physical_proof_final: string;
313
+ tmux_physical_before_drain_ok: boolean;
314
+ tmux_physical_after_drain_ok: boolean;
315
+ tmux_physical_final_ok: boolean;
299
316
  tmux_list_panes_artifact: any;
300
317
  tmux_capture_pane_artifacts: any;
301
318
  tmux_pane_id_reconciled: boolean;
@@ -36,6 +36,8 @@ export async function runNativeAgentOrchestrator(opts = {}) {
36
36
  const routeCommand = String(opts.routeCommand || defaultRouteCommand(route));
37
37
  const routeBlackboxKind = String(opts.routeBlackboxKind || defaultRouteBlackboxKind(route));
38
38
  const backend = normalizeAgentBackend(opts.backend || (opts.mock ? 'fake' : 'codex-exec'));
39
+ const realTmux = backend === 'tmux' && opts.real === true;
40
+ const realTmuxProofRequired = realTmux && process.env.SKS_REQUIRE_REAL_TMUX === '1';
39
41
  const created = opts.missionId
40
42
  ? { id: opts.missionId, dir: missionDir(root, opts.missionId), mission: { id: opts.missionId, mode: 'agent', prompt } }
41
43
  : await createMission(root, { mode: 'agent', prompt });
@@ -87,7 +89,8 @@ export async function runNativeAgentOrchestrator(opts = {}) {
87
89
  await writeIntelligentWorkGraphArtifacts(ledgerRoot, partition.intelligent_work_graph);
88
90
  await writeScoutPolicyArtifact(ledgerRoot);
89
91
  await writeTmuxRightLaneCockpit(ledgerRoot, { missionId, sessionName: `sks-${missionId}`, agents: roster.roster });
90
- await initializeTmuxLaneSupervisor(ledgerRoot, { missionId, sessionName: `sks-${missionId}`, targetActiveSlots, launchRealTmux: backend === 'tmux' && opts.real === true });
92
+ await initializeTmuxLaneSupervisor(ledgerRoot, { missionId, sessionName: `sks-${missionId}`, targetActiveSlots, launchRealTmux: realTmux });
93
+ await writeTmuxPhysicalProof(ledgerRoot, { missionId, realTmux, required: realTmuxProofRequired, phase: 'initial' });
91
94
  await writeAgentCodexCockpitArtifacts(dir, { missionId, projectHash: namespace.root_hash });
92
95
  await writeJsonAtomic(path.join(ledgerRoot, 'agent-no-overlap-proof.json'), partition.no_overlap_proof || { schema: 'sks.agent-no-overlap-proof.v1', ok: false, blockers: ['missing_no_overlap_proof'] });
93
96
  await writeAgentLifecyclePolicy(ledgerRoot);
@@ -188,12 +191,12 @@ export async function runNativeAgentOrchestrator(opts = {}) {
188
191
  }
189
192
  if (String(event.event_type) === 'scheduler_draining') {
190
193
  await verifyTmuxLaneSurvival(ledgerRoot);
191
- await writeTmuxPhysicalProof(ledgerRoot, { missionId, realTmux: backend === 'tmux' && opts.real === true, phase: 'before_drain' });
194
+ await writeTmuxPhysicalProof(ledgerRoot, { missionId, realTmux, required: realTmuxProofRequired, phase: 'before_drain' });
192
195
  }
193
196
  await updateTmuxLaneSupervisorFromSlots(ledgerRoot, { missionId, sessionName: `sks-${missionId}`, slots, state, event });
194
197
  if (String(event.event_type) === 'scheduler_drained') {
195
198
  await drainTmuxLaneSupervisor(ledgerRoot);
196
- await writeTmuxPhysicalProof(ledgerRoot, { missionId, realTmux: backend === 'tmux' && opts.real === true, phase: 'after_drain' });
199
+ await writeTmuxPhysicalProof(ledgerRoot, { missionId, realTmux, required: realTmuxProofRequired, phase: 'after_drain' });
197
200
  }
198
201
  }
199
202
  });
@@ -210,7 +213,7 @@ export async function runNativeAgentOrchestrator(opts = {}) {
210
213
  const finalPaneBySlot = await readTmuxPaneIdsBySlot(ledgerRoot);
211
214
  const finalTmuxSlots = scheduler.slots.map((slot) => ({ ...slot, pane_id: finalPaneBySlot.get(slot.slot_id) || null, launch_status: finalPaneBySlot.has(slot.slot_id) ? 'launched' : slot.status }));
212
215
  await writeTmuxRightLaneCockpit(ledgerRoot, { missionId, sessionName: `sks-${missionId}`, slots: finalTmuxSlots });
213
- await writeTmuxPhysicalProof(ledgerRoot, { missionId, realTmux: backend === 'tmux' && opts.real === true, phase: 'after_drain' });
216
+ await writeTmuxPhysicalProof(ledgerRoot, { missionId, realTmux, required: realTmuxProofRequired, phase: 'final' });
214
217
  await compactAgentLedger(ledgerRoot);
215
218
  const cleanup = await writeAgentCleanupReport(ledgerRoot);
216
219
  const janitor = await runAgentJanitor({ missionDir: dir, missionId, projectHash: namespace.root_hash });
@@ -161,6 +161,19 @@ export declare const AGENT_RESULT_RUNTIME_SCHEMA: {
161
161
  };
162
162
  readonly lease_requirements: {
163
163
  readonly type: "array";
164
+ readonly items: {
165
+ readonly type: "object";
166
+ readonly required: readonly ["kind", "path"];
167
+ readonly properties: {
168
+ readonly kind: {
169
+ readonly type: "string";
170
+ };
171
+ readonly path: {
172
+ readonly type: "string";
173
+ };
174
+ };
175
+ readonly additionalProperties: false;
176
+ };
164
177
  };
165
178
  readonly max_attempts: {
166
179
  readonly type: "integer";
@@ -93,7 +93,18 @@ export const AGENT_RESULT_RUNTIME_SCHEMA = {
93
93
  required_persona_category: { type: 'string', minLength: 1 },
94
94
  priority: { type: 'integer', minimum: 0 },
95
95
  dependencies: { type: 'array', items: { type: 'string' } },
96
- lease_requirements: { type: 'array' },
96
+ lease_requirements: {
97
+ type: 'array',
98
+ items: {
99
+ type: 'object',
100
+ required: ['kind', 'path'],
101
+ properties: {
102
+ kind: { type: 'string' },
103
+ path: { type: 'string' }
104
+ },
105
+ additionalProperties: false
106
+ }
107
+ },
97
108
  max_attempts: { type: 'integer', minimum: 1 },
98
109
  reason: { type: 'string', minLength: 1 },
99
110
  source_agent_session_id: { type: 'string' }
@@ -82,6 +82,13 @@ export declare function writeAgentProofEvidence(root: string, input: {
82
82
  tmux_pane_launch_count: number;
83
83
  physical_tmux_verified: boolean;
84
84
  tmux_physical_proof: string;
85
+ tmux_physical_proof_summary: string;
86
+ tmux_physical_proof_before_drain: string;
87
+ tmux_physical_proof_after_drain: string;
88
+ tmux_physical_proof_final: string;
89
+ tmux_physical_before_drain_ok: boolean;
90
+ tmux_physical_after_drain_ok: boolean;
91
+ tmux_physical_final_ok: boolean;
85
92
  tmux_list_panes_artifact: any;
86
93
  tmux_capture_pane_artifacts: any;
87
94
  tmux_pane_id_reconciled: boolean;
@@ -18,6 +18,10 @@ export async function writeAgentProofEvidence(root, input) {
18
18
  const scheduler = input.scheduler || await readJson(path.join(root, 'agent-scheduler-state.json'), null);
19
19
  const taskGraph = input.partition?.task_graph || await readJson(path.join(root, 'agent-task-graph.json'), null);
20
20
  const tmuxPhysicalProof = await readJson(path.join(root, 'agent-tmux-physical-proof.json'), null);
21
+ const tmuxPhysicalProofSummary = await readJson(path.join(root, 'agent-tmux-physical-proof-summary.json'), null);
22
+ const tmuxPhysicalBeforeDrain = await readJson(path.join(root, 'agent-tmux-physical-proof-before-drain.json'), null);
23
+ const tmuxPhysicalAfterDrain = await readJson(path.join(root, 'agent-tmux-physical-proof-after-drain.json'), null);
24
+ const tmuxPhysicalFinal = await readJson(path.join(root, 'agent-tmux-physical-proof-final.json'), null);
21
25
  const cleanupProof = await readJson(path.join(root, 'agent-cleanup-proof.json'), null);
22
26
  const intelligentWorkGraph = await readJson(path.join(root, 'agent-intelligent-work-graph.json'), null);
23
27
  const slots = await readJson(path.join(root, 'agent-worker-slots.json'), null);
@@ -81,6 +85,10 @@ export async function writeAgentProofEvidence(root, input) {
81
85
  ...(laneSupervisor && Number(laneSupervisor.unexpected_close_count || 0) > 0 ? ['tmux_lane_unexpected_close_before_drain'] : []),
82
86
  ...(laneSupervisor?.blockers || []),
83
87
  ...(input.backend === 'tmux' && tmuxPhysicalProof?.physical_tmux_verified !== true ? ['tmux_physical_pane_proof_missing'] : []),
88
+ ...(input.backend === 'tmux' && !tmuxPhysicalBeforeDrain ? ['tmux_physical_before_drain_proof_missing'] : []),
89
+ ...(input.backend === 'tmux' && !tmuxPhysicalAfterDrain ? ['tmux_physical_after_drain_proof_missing'] : []),
90
+ ...(input.backend === 'tmux' && !tmuxPhysicalFinal ? ['tmux_physical_final_proof_missing'] : []),
91
+ ...(input.backend === 'tmux' && Array.isArray(tmuxPhysicalProofSummary?.blockers) ? tmuxPhysicalProofSummary.blockers : []),
84
92
  ...(input.backend === 'tmux' && Array.isArray(tmuxPhysicalProof?.blockers) ? tmuxPhysicalProof.blockers : []),
85
93
  ...(input.backend === 'tmux' && tmuxLanes?.ok !== true ? ['tmux_right_lane_manifest_missing'] : []),
86
94
  ...(input.backend === 'tmux' && tmuxPaneLaunchCount === 0 ? ['tmux_pane_launch_evidence_missing'] : []),
@@ -155,6 +163,13 @@ export async function writeAgentProofEvidence(root, input) {
155
163
  tmux_pane_launch_count: tmuxPaneLaunchCount,
156
164
  physical_tmux_verified: tmuxPhysicalProof?.physical_tmux_verified === true,
157
165
  tmux_physical_proof: 'agent-tmux-physical-proof.json',
166
+ tmux_physical_proof_summary: 'agent-tmux-physical-proof-summary.json',
167
+ tmux_physical_proof_before_drain: 'agent-tmux-physical-proof-before-drain.json',
168
+ tmux_physical_proof_after_drain: 'agent-tmux-physical-proof-after-drain.json',
169
+ tmux_physical_proof_final: 'agent-tmux-physical-proof-final.json',
170
+ tmux_physical_before_drain_ok: tmuxPhysicalBeforeDrain?.ok === true,
171
+ tmux_physical_after_drain_ok: tmuxPhysicalAfterDrain?.ok === true,
172
+ tmux_physical_final_ok: tmuxPhysicalFinal?.ok === true,
158
173
  tmux_list_panes_artifact: tmuxPhysicalProof?.tmux_list_panes_artifact || null,
159
174
  tmux_capture_pane_artifacts: tmuxPhysicalProof?.tmux_capture_pane_artifacts || [],
160
175
  tmux_pane_id_reconciled: tmuxPhysicalProof?.tmux_pane_id_reconciled === true,