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.
- package/README.md +8 -2
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/crates/sks-core/src/main.rs +1 -1
- package/dist/.sks-build-stamp.json +4 -4
- package/dist/bin/sks.js +1 -1
- package/dist/build-manifest.json +15 -9
- package/dist/commands/image-ux-review.d.ts +20 -0
- package/dist/commands/ppt.d.ts +20 -0
- package/dist/core/agents/agent-cleanup-executor.d.ts +30 -1
- package/dist/core/agents/agent-cleanup-executor.js +186 -15
- package/dist/core/agents/agent-command-surface.d.ts +3 -0
- package/dist/core/agents/agent-command-surface.js +5 -2
- package/dist/core/agents/agent-orchestrator.d.ts +20 -0
- package/dist/core/agents/agent-orchestrator.js +7 -4
- package/dist/core/agents/agent-output-validator.d.ts +13 -0
- package/dist/core/agents/agent-output-validator.js +12 -1
- package/dist/core/agents/agent-proof-evidence.d.ts +7 -0
- package/dist/core/agents/agent-proof-evidence.js +34 -1
- package/dist/core/agents/agent-runner-codex-exec.js +6 -1
- package/dist/core/agents/agent-trust-report.d.ts +13 -0
- package/dist/core/agents/agent-trust-report.js +46 -0
- package/dist/core/agents/intelligent-work-graph.d.ts +14 -1
- package/dist/core/agents/intelligent-work-graph.js +254 -10
- package/dist/core/agents/route-collaboration-ledger.d.ts +20 -0
- package/dist/core/agents/tmux-physical-proof.d.ts +100 -8
- package/dist/core/agents/tmux-physical-proof.js +92 -16
- package/dist/core/agents/work-partition/dependency-graph.js +33 -11
- package/dist/core/commands/agent-command.js +14 -1
- package/dist/core/commands/image-ux-review-command.d.ts +20 -0
- package/dist/core/commands/mad-sks-command.js +7 -2
- package/dist/core/commands/ppt-command.d.ts +20 -0
- package/dist/core/fsx.d.ts +1 -1
- package/dist/core/fsx.js +1 -1
- package/dist/core/hooks-runtime.js +12 -1
- package/dist/core/image-ux-review/imagegen-adapter.js +210 -10
- package/dist/core/mad-sks/mad-tmux-lane-proof.d.ts +26 -0
- package/dist/core/mad-sks/mad-tmux-lane-proof.js +50 -0
- package/dist/core/proof/fake-real-proof-policy.d.ts +10 -2
- package/dist/core/proof/fake-real-proof-policy.js +67 -9
- package/dist/core/proof/runtime-truth-matrix.d.ts +38 -0
- package/dist/core/proof/runtime-truth-matrix.js +155 -0
- package/dist/core/routes.js +1 -1
- package/dist/core/source-intelligence/source-intelligence-proof.js +2 -1
- package/dist/core/source-intelligence/source-intelligence-runner.js +4 -1
- package/dist/core/version.d.ts +1 -1
- package/dist/core/version.js +1 -1
- package/dist/scripts/release-parallel-check.d.ts +3 -0
- package/dist/scripts/release-parallel-check.js +204 -0
- package/package.json +18 -10
- 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.
|
|
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
|
-
-
|
|
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)
|
|
@@ -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.
|
|
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.
|
|
5
|
-
"source_digest": "
|
|
6
|
-
"source_file_count":
|
|
7
|
-
"built_at_source_time":
|
|
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
package/dist/build-manifest.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": "sks.dist-build.v2",
|
|
3
|
-
"version": "1.18.
|
|
4
|
-
"package_version": "1.18.
|
|
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":
|
|
8
|
-
"compiled_js_count":
|
|
9
|
-
"compiled_dts_count":
|
|
10
|
-
"source_digest": "
|
|
11
|
-
"source_file_count":
|
|
12
|
-
"source_files_hash": "
|
|
13
|
-
"source_list_hash": "
|
|
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;
|
package/dist/commands/ppt.d.ts
CHANGED
|
@@ -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.
|
|
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.
|
|
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
|
-
|
|
39
|
-
kind: '
|
|
40
|
-
|
|
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
|
-
|
|
44
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|