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.
- package/README.md +6 -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/build-manifest.json +11 -9
- package/dist/commands/image-ux-review.d.ts +17 -0
- package/dist/commands/ppt.d.ts +17 -0
- package/dist/core/agents/agent-cleanup-executor.d.ts +21 -1
- package/dist/core/agents/agent-cleanup-executor.js +141 -9
- package/dist/core/agents/agent-command-surface.d.ts +1 -0
- package/dist/core/agents/agent-command-surface.js +3 -2
- package/dist/core/agents/agent-orchestrator.d.ts +17 -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 +15 -0
- package/dist/core/agents/agent-runner-codex-exec.js +6 -1
- package/dist/core/agents/agent-trust-report.d.ts +10 -0
- package/dist/core/agents/agent-trust-report.js +32 -0
- package/dist/core/agents/intelligent-work-graph.d.ts +12 -1
- package/dist/core/agents/intelligent-work-graph.js +162 -10
- package/dist/core/agents/route-collaboration-ledger.d.ts +17 -0
- package/dist/core/agents/tmux-physical-proof.d.ts +96 -8
- package/dist/core/agents/tmux-physical-proof.js +85 -16
- package/dist/core/commands/agent-command.js +12 -1
- package/dist/core/commands/image-ux-review-command.d.ts +17 -0
- package/dist/core/commands/ppt-command.d.ts +17 -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/proof/fake-real-proof-policy.d.ts +3 -2
- package/dist/core/proof/fake-real-proof-policy.js +31 -8
- package/dist/core/routes.js +1 -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 +12 -4
- 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.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.
|
|
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)
|
|
@@ -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.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.
|
|
5
|
-
"source_digest": "
|
|
6
|
-
"source_file_count":
|
|
7
|
-
"built_at_source_time":
|
|
4
|
+
"package_version": "1.18.5",
|
|
5
|
+
"source_digest": "25698b68e8f9d6ff16ac94f404e60d99db9841e8e85a0f8c5831069a744434cc",
|
|
6
|
+
"source_file_count": 1430,
|
|
7
|
+
"built_at_source_time": 1779807721873
|
|
8
8
|
}
|
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.5",
|
|
4
|
+
"package_version": "1.18.5",
|
|
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": 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;
|
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,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.
|
|
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.
|
|
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
|
-
|
|
39
|
-
kind: '
|
|
40
|
-
|
|
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
|
-
|
|
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
|
}
|
|
@@ -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:
|
|
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:
|
|
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:
|
|
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:
|
|
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: {
|
|
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,
|