sneakoscope 1.16.2 → 1.17.0
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 +4 -4
- 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 +28 -4
- package/dist/commands/image-ux-review.d.ts +2 -0
- package/dist/commands/ppt.d.ts +2 -0
- package/dist/core/agents/agent-codex-cockpit.d.ts +53 -0
- package/dist/core/agents/agent-codex-cockpit.js +168 -0
- package/dist/core/agents/agent-command-surface.d.ts +1 -0
- package/dist/core/agents/agent-command-surface.js +8 -4
- package/dist/core/agents/agent-gate.js +15 -1
- package/dist/core/agents/agent-janitor.d.ts +23 -0
- package/dist/core/agents/agent-janitor.js +147 -0
- package/dist/core/agents/agent-ledger-schemas.d.ts +178 -2
- package/dist/core/agents/agent-ledger-schemas.js +24 -1
- package/dist/core/agents/agent-orchestrator.d.ts +2 -0
- package/dist/core/agents/agent-orchestrator.js +44 -2
- package/dist/core/agents/agent-plan.js +2 -2
- package/dist/core/agents/agent-proof-evidence.d.ts +3 -0
- package/dist/core/agents/agent-proof-evidence.js +3 -0
- package/dist/core/agents/agent-session-rows.d.ts +2 -0
- package/dist/core/agents/agent-session-rows.js +14 -0
- package/dist/core/agents/agent-worker-pipeline.js +1 -1
- package/dist/core/agents/route-collaboration-ledger.d.ts +2 -0
- package/dist/core/commands/agent-command.js +13 -2
- package/dist/core/commands/image-ux-review-command.d.ts +2 -0
- package/dist/core/commands/ppt-command.d.ts +2 -0
- package/dist/core/fsx.d.ts +1 -1
- package/dist/core/fsx.js +1 -1
- package/dist/core/proof/auto-finalize.js +4 -0
- package/dist/core/proof/route-proof-gate.js +17 -4
- package/dist/core/proof/route-proof-policy.js +3 -0
- package/dist/core/session/project-namespace.d.ts +31 -0
- package/dist/core/session/project-namespace.js +52 -0
- package/dist/core/tmux-ui.js +3 -1
- package/dist/core/verification/verification-artifact-lock.d.ts +9 -0
- package/dist/core/verification/verification-artifact-lock.js +22 -0
- package/dist/core/verification/verification-dag.d.ts +10 -0
- package/dist/core/verification/verification-dag.js +70 -0
- package/dist/core/verification/verification-proof.d.ts +7 -0
- package/dist/core/verification/verification-proof.js +27 -0
- package/dist/core/verification/verification-result.d.ts +40 -0
- package/dist/core/verification/verification-result.js +20 -0
- package/dist/core/verification/verification-worker-pool.d.ts +13 -0
- package/dist/core/verification/verification-worker-pool.js +186 -0
- package/dist/core/version.d.ts +1 -1
- package/dist/core/version.js +1 -1
- package/package.json +15 -4
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.
|
|
13
|
+
SKS **1.17.0** makes TypeScript the only runtime source of truth, moves package execution to built `dist/**/*.js`, adds Codex App agent cockpit artifacts, parallelizes release verification with a dependency DAG, and namespaces agent sessions by project hash plus mission id.
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
16
|
sks mad-sks plan --target-root <path> --json
|
|
@@ -352,7 +352,7 @@ sks gx render homepage --format html
|
|
|
352
352
|
sks validate-artifacts latest --json
|
|
353
353
|
sks pipeline plan latest --proof-field --json
|
|
354
354
|
sks perf run --json
|
|
355
|
-
sks perf workflow --json --intent "small CLI change" --changed src/cli/main.
|
|
355
|
+
sks perf workflow --json --intent "small CLI change" --changed src/cli/main.ts,src/core/routes.ts
|
|
356
356
|
sks proof-field scan --json --intent "small CLI change"
|
|
357
357
|
sks skill-dream status
|
|
358
358
|
sks skill-dream run --json
|
|
@@ -451,7 +451,7 @@ SKS_OPENCLAW=1 sks root
|
|
|
451
451
|
SKS_OPENCLAW=1 sks commands
|
|
452
452
|
SKS_OPENCLAW=1 sks dollar-commands
|
|
453
453
|
SKS_OPENCLAW=1 sks deps check
|
|
454
|
-
SKS_OPENCLAW=1 sks proof-field scan --intent "small CLI change" --changed src/cli/main.
|
|
454
|
+
SKS_OPENCLAW=1 sks proof-field scan --intent "small CLI change" --changed src/cli/main.ts
|
|
455
455
|
```
|
|
456
456
|
|
|
457
457
|
If OpenClaw runs in a sandbox, grant shell execution only for trusted workspaces. Database, migration, and destructive work still follows SKS safety routes.
|
|
@@ -601,7 +601,7 @@ npm run release:check
|
|
|
601
601
|
npm run publish:dry
|
|
602
602
|
```
|
|
603
603
|
|
|
604
|
-
`release:check` runs
|
|
604
|
+
`release:check` runs the 1.17.0 parallel P0 DAG, writes a source digest stamp under `.sneakoscope/reports/`, then refreshes release readiness so publish commands can verify the same stamp. The DAG covers build, TS runtime source checks, dist parity, proof artifact structure, Codex App cockpit, janitor, multi-project isolation, parallel verification, typecheck, schema, release metadata, and release readiness. Broader live or historical gates remain explicit scripts such as `release:real-check`. Generate the human-readable registry with `sks features inventory --write-docs`. Plain `npm publish` uses the `latest` dist-tag. npm's `prepublishOnly` verifies the fresh release stamp instead of rerunning the full gate, and `prepack` only rebuilds `dist`; publish no longer repeats the expensive release suite during packaging. `npm run publish:dry` remains the explicit dry-run helper.
|
|
605
605
|
|
|
606
606
|
Version bumps are manual. Run `sks versioning bump` only when preparing release metadata; SKS will not create `.git/hooks/pre-commit` or auto-bump during ordinary commits.
|
|
607
607
|
|
|
@@ -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.
|
|
7
|
+
Some("--version") => println!("sks-rs 1.17.0"),
|
|
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.
|
|
5
|
-
"source_digest": "
|
|
6
|
-
"source_file_count":
|
|
7
|
-
"built_at_source_time":
|
|
4
|
+
"package_version": "1.17.0",
|
|
5
|
+
"source_digest": "527177fcd408d00042f7263b37de99dd135af1fd2fdd0a8d79a220e747de03cb",
|
|
6
|
+
"source_file_count": 1515,
|
|
7
|
+
"built_at_source_time": 1779685135168
|
|
8
8
|
}
|
package/dist/bin/sks.js
CHANGED
package/dist/build-manifest.json
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": "sks.dist-build.v2",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"package_version": "1.
|
|
3
|
+
"version": "1.17.0",
|
|
4
|
+
"package_version": "1.17.0",
|
|
5
5
|
"typescript": true,
|
|
6
6
|
"mjs_runtime_files": 0,
|
|
7
|
-
"
|
|
8
|
-
"
|
|
7
|
+
"compiled_file_count": 828,
|
|
8
|
+
"compiled_js_count": 414,
|
|
9
|
+
"compiled_dts_count": 414,
|
|
10
|
+
"source_digest": "527177fcd408d00042f7263b37de99dd135af1fd2fdd0a8d79a220e747de03cb",
|
|
11
|
+
"source_file_count": 1515,
|
|
12
|
+
"source_files_hash": "137bd4f96d0913bd90ab4e74364e959627dd8fd29576dae9d57c89c38b9b009f",
|
|
13
|
+
"source_list_hash": "137bd4f96d0913bd90ab4e74364e959627dd8fd29576dae9d57c89c38b9b009f",
|
|
14
|
+
"src_mjs_runtime_files": 0,
|
|
9
15
|
"dist_stamp_schema": "sks.dist-build-stamp.v1",
|
|
10
16
|
"files": [
|
|
11
17
|
"bin/sks.d.ts",
|
|
@@ -182,6 +188,8 @@
|
|
|
182
188
|
"core/agents/agent-central-ledger.js",
|
|
183
189
|
"core/agents/agent-cleanup.d.ts",
|
|
184
190
|
"core/agents/agent-cleanup.js",
|
|
191
|
+
"core/agents/agent-codex-cockpit.d.ts",
|
|
192
|
+
"core/agents/agent-codex-cockpit.js",
|
|
185
193
|
"core/agents/agent-command-surface.d.ts",
|
|
186
194
|
"core/agents/agent-command-surface.js",
|
|
187
195
|
"core/agents/agent-conflict-graph.d.ts",
|
|
@@ -194,6 +202,8 @@
|
|
|
194
202
|
"core/agents/agent-gate.js",
|
|
195
203
|
"core/agents/agent-heartbeat.d.ts",
|
|
196
204
|
"core/agents/agent-heartbeat.js",
|
|
205
|
+
"core/agents/agent-janitor.d.ts",
|
|
206
|
+
"core/agents/agent-janitor.js",
|
|
197
207
|
"core/agents/agent-lease.d.ts",
|
|
198
208
|
"core/agents/agent-lease.js",
|
|
199
209
|
"core/agents/agent-ledger-schemas.d.ts",
|
|
@@ -226,6 +236,8 @@
|
|
|
226
236
|
"core/agents/agent-runner-tmux.js",
|
|
227
237
|
"core/agents/agent-schema.d.ts",
|
|
228
238
|
"core/agents/agent-schema.js",
|
|
239
|
+
"core/agents/agent-session-rows.d.ts",
|
|
240
|
+
"core/agents/agent-session-rows.js",
|
|
229
241
|
"core/agents/agent-task-slicer.d.ts",
|
|
230
242
|
"core/agents/agent-task-slicer.js",
|
|
231
243
|
"core/agents/agent-trust-report.d.ts",
|
|
@@ -715,6 +727,8 @@
|
|
|
715
727
|
"core/rust-accelerator.js",
|
|
716
728
|
"core/secret-redaction.d.ts",
|
|
717
729
|
"core/secret-redaction.js",
|
|
730
|
+
"core/session/project-namespace.d.ts",
|
|
731
|
+
"core/session/project-namespace.js",
|
|
718
732
|
"core/skill-forge.d.ts",
|
|
719
733
|
"core/skill-forge.js",
|
|
720
734
|
"core/structured-output-adapter.d.ts",
|
|
@@ -779,6 +793,16 @@
|
|
|
779
793
|
"core/validators/trust-report-validator.js",
|
|
780
794
|
"core/validators/validation-error.d.ts",
|
|
781
795
|
"core/validators/validation-error.js",
|
|
796
|
+
"core/verification/verification-artifact-lock.d.ts",
|
|
797
|
+
"core/verification/verification-artifact-lock.js",
|
|
798
|
+
"core/verification/verification-dag.d.ts",
|
|
799
|
+
"core/verification/verification-dag.js",
|
|
800
|
+
"core/verification/verification-proof.d.ts",
|
|
801
|
+
"core/verification/verification-proof.js",
|
|
802
|
+
"core/verification/verification-result.d.ts",
|
|
803
|
+
"core/verification/verification-result.js",
|
|
804
|
+
"core/verification/verification-worker-pool.d.ts",
|
|
805
|
+
"core/verification/verification-worker-pool.js",
|
|
782
806
|
"core/version-manager.d.ts",
|
|
783
807
|
"core/version-manager.js",
|
|
784
808
|
"core/version.d.ts",
|
|
@@ -566,6 +566,8 @@ export declare function run(command: any, args?: any): Promise<void | {
|
|
|
566
566
|
timeout_kill_report: string;
|
|
567
567
|
timeout_killed_sessions: any;
|
|
568
568
|
cleanup_report: string;
|
|
569
|
+
janitor_report: string;
|
|
570
|
+
janitor_ok: boolean;
|
|
569
571
|
trust_report: string;
|
|
570
572
|
wrongness_records: string;
|
|
571
573
|
changed_files_lease_checked: boolean;
|
package/dist/commands/ppt.d.ts
CHANGED
|
@@ -418,6 +418,8 @@ export declare function run(command: any, args?: any): Promise<void | {
|
|
|
418
418
|
timeout_kill_report: string;
|
|
419
419
|
timeout_killed_sessions: any;
|
|
420
420
|
cleanup_report: string;
|
|
421
|
+
janitor_report: string;
|
|
422
|
+
janitor_ok: boolean;
|
|
421
423
|
trust_report: string;
|
|
422
424
|
wrongness_records: string;
|
|
423
425
|
changed_files_lease_checked: boolean;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export declare const AGENT_CODEX_DASHBOARD_MD = "agent-codex-dashboard.md";
|
|
2
|
+
export declare const AGENT_CODEX_DASHBOARD_JSON = "agent-codex-dashboard.json";
|
|
3
|
+
export declare const AGENT_SESSION_CARDS_MD = "agent-session-cards.md";
|
|
4
|
+
export declare const AGENT_LIVE_SUMMARY_JSON = "agent-live-summary.json";
|
|
5
|
+
export declare const AGENT_PROGRESS_TIMELINE_MD = "agent-progress-timeline.md";
|
|
6
|
+
export declare const AGENT_CODEX_COCKPIT_EVENTS = "agent-codex-cockpit-events.jsonl";
|
|
7
|
+
export type CodexCockpitHookPayload = {
|
|
8
|
+
hook_event_name: 'SubagentStart' | 'SubagentStop';
|
|
9
|
+
agent_id?: string;
|
|
10
|
+
agent_type?: string;
|
|
11
|
+
session_id?: string;
|
|
12
|
+
transcript_path?: string | null;
|
|
13
|
+
agent_transcript_path?: string | null;
|
|
14
|
+
last_assistant_message?: string | null;
|
|
15
|
+
stop_hook_active?: boolean;
|
|
16
|
+
turn_id?: string;
|
|
17
|
+
cwd?: string;
|
|
18
|
+
model?: string;
|
|
19
|
+
permission_mode?: string;
|
|
20
|
+
};
|
|
21
|
+
export interface AgentCodexCockpitState {
|
|
22
|
+
schema: 'sks.agent-codex-cockpit.v1';
|
|
23
|
+
generated_at: string;
|
|
24
|
+
mission_id: string | null;
|
|
25
|
+
project_hash: string | null;
|
|
26
|
+
backend: string | null;
|
|
27
|
+
agent_count: number;
|
|
28
|
+
concurrency: number | null;
|
|
29
|
+
all_sessions_closed: boolean | null;
|
|
30
|
+
janitor_ok: boolean | null;
|
|
31
|
+
proof_status: string | null;
|
|
32
|
+
blockers: string[];
|
|
33
|
+
agents: Array<Record<string, unknown>>;
|
|
34
|
+
recent_events: string[];
|
|
35
|
+
artifacts: Record<string, string>;
|
|
36
|
+
}
|
|
37
|
+
export declare function appendAgentCodexCockpitHookEvent(missionDir: string, payload: CodexCockpitHookPayload): Promise<void>;
|
|
38
|
+
export declare function writeAgentCodexCockpitArtifacts(missionDir: string, opts?: {
|
|
39
|
+
missionId?: string | null;
|
|
40
|
+
projectHash?: string | null;
|
|
41
|
+
}): Promise<{
|
|
42
|
+
ok: boolean;
|
|
43
|
+
issues: string[];
|
|
44
|
+
state: AgentCodexCockpitState;
|
|
45
|
+
}>;
|
|
46
|
+
export declare function buildAgentCodexCockpitState(missionDir: string, opts?: {
|
|
47
|
+
missionId?: string | null;
|
|
48
|
+
projectHash?: string | null;
|
|
49
|
+
}): Promise<AgentCodexCockpitState>;
|
|
50
|
+
export declare function renderAgentCodexDashboard(state: AgentCodexCockpitState): string;
|
|
51
|
+
export declare function renderAgentSessionCards(state: AgentCodexCockpitState): string;
|
|
52
|
+
export declare function renderAgentProgressTimeline(state: AgentCodexCockpitState): string;
|
|
53
|
+
//# sourceMappingURL=agent-codex-cockpit.d.ts.map
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { appendJsonl, exists, nowIso, readJson, readText, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
|
|
3
|
+
import { normalizeAgentSessionRows } from './agent-session-rows.js';
|
|
4
|
+
export const AGENT_CODEX_DASHBOARD_MD = 'agent-codex-dashboard.md';
|
|
5
|
+
export const AGENT_CODEX_DASHBOARD_JSON = 'agent-codex-dashboard.json';
|
|
6
|
+
export const AGENT_SESSION_CARDS_MD = 'agent-session-cards.md';
|
|
7
|
+
export const AGENT_LIVE_SUMMARY_JSON = 'agent-live-summary.json';
|
|
8
|
+
export const AGENT_PROGRESS_TIMELINE_MD = 'agent-progress-timeline.md';
|
|
9
|
+
export const AGENT_CODEX_COCKPIT_EVENTS = 'agent-codex-cockpit-events.jsonl';
|
|
10
|
+
export async function appendAgentCodexCockpitHookEvent(missionDir, payload) {
|
|
11
|
+
await appendJsonl(path.join(agentRoot(missionDir), AGENT_CODEX_COCKPIT_EVENTS), {
|
|
12
|
+
schema: 'sks.agent-codex-cockpit-event.v1',
|
|
13
|
+
ts: nowIso(),
|
|
14
|
+
...payload,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
export async function writeAgentCodexCockpitArtifacts(missionDir, opts = {}) {
|
|
18
|
+
const state = await buildAgentCodexCockpitState(missionDir, opts);
|
|
19
|
+
const root = agentRoot(missionDir);
|
|
20
|
+
await writeJsonAtomic(path.join(root, AGENT_CODEX_DASHBOARD_JSON), state);
|
|
21
|
+
await writeJsonAtomic(path.join(root, AGENT_LIVE_SUMMARY_JSON), summarizeLiveState(state));
|
|
22
|
+
await writeTextAtomic(path.join(root, AGENT_CODEX_DASHBOARD_MD), renderAgentCodexDashboard(state));
|
|
23
|
+
await writeTextAtomic(path.join(root, AGENT_SESSION_CARDS_MD), renderAgentSessionCards(state));
|
|
24
|
+
await writeTextAtomic(path.join(root, AGENT_PROGRESS_TIMELINE_MD), renderAgentProgressTimeline(state));
|
|
25
|
+
return { ok: state.blockers.length === 0, issues: state.blockers, state };
|
|
26
|
+
}
|
|
27
|
+
export async function buildAgentCodexCockpitState(missionDir, opts = {}) {
|
|
28
|
+
const root = agentRoot(missionDir);
|
|
29
|
+
const sessions = await readJson(path.join(root, 'agent-sessions.json'), null);
|
|
30
|
+
const roster = await readJson(path.join(root, 'agent-roster.json'), null);
|
|
31
|
+
const leases = await readJson(path.join(root, 'agent-leases.json'), null);
|
|
32
|
+
const proof = await readJson(path.join(root, 'agent-proof-evidence.json'), null);
|
|
33
|
+
const consensus = await readJson(path.join(root, 'agent-consensus.json'), null);
|
|
34
|
+
const cleanup = await readJson(path.join(root, 'agent-cleanup.json'), null);
|
|
35
|
+
const janitor = await readJson(path.join(root, 'agent-janitor-report.json'), null);
|
|
36
|
+
const namespace = await readJson(path.join(missionDir, 'project-session-namespace.json'), null);
|
|
37
|
+
const eventsTail = await readTailLines(path.join(root, 'agent-events.jsonl'), 8);
|
|
38
|
+
const cockpitEventsTail = await readTailLines(path.join(root, AGENT_CODEX_COCKPIT_EVENTS), 8);
|
|
39
|
+
const teamTail = await readTailLines(path.join(missionDir, 'team-transcript.jsonl'), 8);
|
|
40
|
+
const sessionRows = normalizeAgentSessionRows(sessions);
|
|
41
|
+
const rosterRows = Array.isArray(roster?.roster) ? roster.roster : [];
|
|
42
|
+
const leaseRows = Array.isArray(leases?.leases) ? leases.leases : [];
|
|
43
|
+
const agents = mergeAgentRows(sessionRows, rosterRows, leaseRows, [...eventsTail, ...cockpitEventsTail]);
|
|
44
|
+
const blockers = [
|
|
45
|
+
...(Array.isArray(proof?.blockers) ? proof.blockers : []),
|
|
46
|
+
...(!sessions ? ['agent_sessions_missing'] : []),
|
|
47
|
+
...(proof && proof.ok === false ? ['agent_proof_not_ok'] : []),
|
|
48
|
+
...(janitor && janitor.ok === false ? ['agent_janitor_not_ok'] : []),
|
|
49
|
+
].map(String);
|
|
50
|
+
return {
|
|
51
|
+
schema: 'sks.agent-codex-cockpit.v1',
|
|
52
|
+
generated_at: nowIso(),
|
|
53
|
+
mission_id: opts.missionId || namespace?.mission_id || proof?.mission_id || null,
|
|
54
|
+
project_hash: opts.projectHash || namespace?.root_hash || null,
|
|
55
|
+
backend: proof?.backend || null,
|
|
56
|
+
agent_count: Number(proof?.agent_count || roster?.agent_count || agents.length || 0),
|
|
57
|
+
concurrency: Number.isFinite(Number(roster?.concurrency)) ? Number(roster.concurrency) : null,
|
|
58
|
+
all_sessions_closed: proof?.all_sessions_closed ?? cleanup?.all_sessions_closed ?? null,
|
|
59
|
+
janitor_ok: janitor?.ok ?? null,
|
|
60
|
+
proof_status: proof?.status || (proof?.ok ? 'passed' : proof ? 'blocked' : null),
|
|
61
|
+
blockers,
|
|
62
|
+
agents,
|
|
63
|
+
recent_events: [...eventsTail, ...cockpitEventsTail, ...teamTail].slice(-12),
|
|
64
|
+
artifacts: {
|
|
65
|
+
markdown: path.join('agents', AGENT_CODEX_DASHBOARD_MD),
|
|
66
|
+
json: path.join('agents', AGENT_CODEX_DASHBOARD_JSON),
|
|
67
|
+
cards: path.join('agents', AGENT_SESSION_CARDS_MD),
|
|
68
|
+
live_summary: path.join('agents', AGENT_LIVE_SUMMARY_JSON),
|
|
69
|
+
timeline: path.join('agents', AGENT_PROGRESS_TIMELINE_MD),
|
|
70
|
+
event_stream: path.join('agents', AGENT_CODEX_COCKPIT_EVENTS),
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
export function renderAgentCodexDashboard(state) {
|
|
75
|
+
const header = [
|
|
76
|
+
'# Agent Codex Dashboard',
|
|
77
|
+
'',
|
|
78
|
+
`- Mission: ${state.mission_id || 'unknown'}`,
|
|
79
|
+
`- Project hash: ${state.project_hash || 'unknown'}`,
|
|
80
|
+
`- Backend: ${state.backend || 'unknown'}`,
|
|
81
|
+
`- Agents: ${state.agent_count}`,
|
|
82
|
+
`- Concurrency: ${state.concurrency ?? 'unknown'}`,
|
|
83
|
+
`- Proof: ${state.proof_status || 'unknown'}`,
|
|
84
|
+
`- All sessions closed: ${state.all_sessions_closed ?? 'unknown'}`,
|
|
85
|
+
'',
|
|
86
|
+
'| Agent | Persona | Task | State | Heartbeat age | Lease | Blockers | Artifact |',
|
|
87
|
+
'| --- | --- | --- | --- | --- | --- | --- | --- |',
|
|
88
|
+
];
|
|
89
|
+
const rows = state.agents.map((agent) => {
|
|
90
|
+
const id = cell(agent.id);
|
|
91
|
+
const persona = cell(agent.persona || agent.persona_id);
|
|
92
|
+
const task = cell(agent.task || agent.task_slice_id || agent.slice_id);
|
|
93
|
+
const status = cell(agent.status || agent.lifecycle_state);
|
|
94
|
+
const heartbeat = cell(agent.heartbeat_age_ms === undefined ? '' : `${agent.heartbeat_age_ms}ms`);
|
|
95
|
+
const lease = cell(agent.lease || agent.lease_id || agent.write_policy);
|
|
96
|
+
const blockers = Array.isArray(agent.blockers) ? cell(agent.blockers.join(', ')) : cell(agent.blockers);
|
|
97
|
+
const artifact = cell(agent.output_artifact || agent.artifact || '');
|
|
98
|
+
return `| ${id} | ${persona} | ${task} | ${status} | ${heartbeat} | ${lease} | ${blockers} | ${artifact} |`;
|
|
99
|
+
});
|
|
100
|
+
return `${[...header, ...rows].join('\n')}\n`;
|
|
101
|
+
}
|
|
102
|
+
export function renderAgentSessionCards(state) {
|
|
103
|
+
const blocks = state.agents.map((agent) => [
|
|
104
|
+
`## ${cell(agent.id)}`,
|
|
105
|
+
'',
|
|
106
|
+
`- Persona: ${cell(agent.persona || agent.persona_id)}`,
|
|
107
|
+
`- Task: ${cell(agent.task || agent.task_slice_id || agent.slice_id)}`,
|
|
108
|
+
`- State: ${cell(agent.status || agent.lifecycle_state)}`,
|
|
109
|
+
`- Lease: ${cell(agent.lease || agent.lease_id || agent.write_policy)}`,
|
|
110
|
+
`- Artifact: ${cell(agent.output_artifact || agent.artifact || '')}`,
|
|
111
|
+
].join('\n'));
|
|
112
|
+
return `# Agent Session Cards\n\n${blocks.join('\n\n')}\n`;
|
|
113
|
+
}
|
|
114
|
+
export function renderAgentProgressTimeline(state) {
|
|
115
|
+
return `# Agent Progress Timeline\n\n${state.recent_events.map((line) => `- ${line}`).join('\n')}\n`;
|
|
116
|
+
}
|
|
117
|
+
function summarizeLiveState(state) {
|
|
118
|
+
return {
|
|
119
|
+
schema: 'sks.agent-live-summary.v1',
|
|
120
|
+
generated_at: state.generated_at,
|
|
121
|
+
mission_id: state.mission_id,
|
|
122
|
+
project_hash: state.project_hash,
|
|
123
|
+
backend: state.backend,
|
|
124
|
+
agent_count: state.agent_count,
|
|
125
|
+
concurrency: state.concurrency,
|
|
126
|
+
active_agents: state.agents.filter((agent) => !['closed', 'done', 'completed'].includes(String(agent.status || ''))).length,
|
|
127
|
+
proof_status: state.proof_status,
|
|
128
|
+
blockers: state.blockers,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function mergeAgentRows(sessions, roster, leases, events) {
|
|
132
|
+
const byId = new Map();
|
|
133
|
+
for (const row of roster)
|
|
134
|
+
byId.set(String(row.id || row.agent_id), { ...row });
|
|
135
|
+
for (const row of sessions) {
|
|
136
|
+
const id = String(row.id || row.agent_id || row.session_id);
|
|
137
|
+
byId.set(id, { ...(byId.get(id) || {}), ...row, id });
|
|
138
|
+
}
|
|
139
|
+
for (const row of leases) {
|
|
140
|
+
const id = String(row.agent_id || row.id || '');
|
|
141
|
+
if (!id)
|
|
142
|
+
continue;
|
|
143
|
+
byId.set(id, { ...(byId.get(id) || {}), lease_id: row.lease_id || row.id, lease: row.scope || row.path || row.write_scope });
|
|
144
|
+
}
|
|
145
|
+
for (const [id, row] of byId) {
|
|
146
|
+
row.id = row.id || id;
|
|
147
|
+
row.recent_event_tail = events.filter((line) => line.includes(id)).slice(-3);
|
|
148
|
+
if (row.heartbeat_age_ms === undefined) {
|
|
149
|
+
const heartbeat = Date.parse(String(row.heartbeat_at || row.last_heartbeat_at || row.updated_at || ''));
|
|
150
|
+
if (Number.isFinite(heartbeat))
|
|
151
|
+
row.heartbeat_age_ms = Math.max(0, Date.now() - heartbeat);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return [...byId.values()];
|
|
155
|
+
}
|
|
156
|
+
async function readTailLines(file, count) {
|
|
157
|
+
if (!(await exists(file)))
|
|
158
|
+
return [];
|
|
159
|
+
const text = await readText(file, '');
|
|
160
|
+
return String(text).trim().split(/\n+/).filter(Boolean).slice(-count);
|
|
161
|
+
}
|
|
162
|
+
function agentRoot(missionDir) {
|
|
163
|
+
return path.join(missionDir, 'agents');
|
|
164
|
+
}
|
|
165
|
+
function cell(value) {
|
|
166
|
+
return String(value ?? '').replace(/\|/g, '\\|').replace(/\s+/g, ' ').trim();
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=agent-codex-cockpit.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { DEFAULT_AGENT_COUNT } from './agent-schema.js';
|
|
2
2
|
export function parseAgentCommandArgs(command, args = []) {
|
|
3
3
|
const first = args[0] && !String(args[0]).startsWith('--') ? String(args[0]) : '';
|
|
4
|
-
const actions = new Set(['run', 'status', 'plan', 'spawn', 'watch', 'lane', 'board', 'ledger', 'collect', 'consensus', 'close', 'cleanup', 'proof', 'explain']);
|
|
4
|
+
const actions = new Set(['run', 'status', 'plan', 'spawn', 'watch', 'dashboard', 'cockpit', 'lane', 'board', 'ledger', 'collect', 'consensus', 'close', 'cleanup', 'proof', 'explain']);
|
|
5
5
|
const action = actions.has(first) ? first : 'run';
|
|
6
6
|
const rest = action === first ? args.slice(1) : args;
|
|
7
7
|
const json = hasFlag(args, '--json');
|
|
@@ -12,11 +12,15 @@ export function parseAgentCommandArgs(command, args = []) {
|
|
|
12
12
|
const mock = hasFlag(args, '--mock') || backend === 'fake';
|
|
13
13
|
const real = hasFlag(args, '--real');
|
|
14
14
|
const readonly = hasFlag(args, '--readonly') || hasFlag(args, '--read-only');
|
|
15
|
+
const codexApp = hasFlag(args, '--codex-app');
|
|
16
|
+
const positionals = positionalArgs(rest, new Set(['--agents', '--concurrency', '--backend', '--route', '--mission', '--mission-id', '--agent', '--lane']));
|
|
15
17
|
const missionDefault = action === 'run' || action === 'spawn' || action === 'plan' ? '' : 'latest';
|
|
16
|
-
const
|
|
18
|
+
const positionalMission = action === 'run' || action === 'spawn' || action === 'plan' ? '' : (positionals[0] || '');
|
|
19
|
+
const missionId = String(readOption(args, '--mission', readOption(args, '--mission-id', positionalMission || missionDefault)));
|
|
17
20
|
const lane = String(readOption(args, '--agent', readOption(args, '--lane', '')));
|
|
18
|
-
const
|
|
19
|
-
|
|
21
|
+
const promptPositionals = positionalMission ? positionals.slice(1) : positionals;
|
|
22
|
+
const prompt = promptPositionals.join(' ').trim() || 'Native agent run';
|
|
23
|
+
return { command, action, prompt, route, agents, concurrency, backend, mock, real, readonly, json, missionId, lane, codexApp };
|
|
20
24
|
}
|
|
21
25
|
function hasFlag(args, flag) {
|
|
22
26
|
return args.includes(flag);
|
|
@@ -28,7 +28,21 @@ export async function readAgentGateStatus(root, missionId) {
|
|
|
28
28
|
if (!(await exists(proofPath)))
|
|
29
29
|
missing.push('agents/agent-proof-evidence.json');
|
|
30
30
|
const blockers = [...(Array.isArray(proof?.blockers) ? proof.blockers : []), ...(Array.isArray(gate?.blockers) ? gate.blockers : [])];
|
|
31
|
-
|
|
31
|
+
if (proof?.ok !== true)
|
|
32
|
+
blockers.push('agent_proof_not_ok');
|
|
33
|
+
if (proof?.status !== 'passed')
|
|
34
|
+
blockers.push('agent_proof_status_not_passed');
|
|
35
|
+
if (Number(proof?.agent_count || 0) < 5)
|
|
36
|
+
blockers.push('agent_count_below_5');
|
|
37
|
+
if (proof?.no_overlap_ok !== true)
|
|
38
|
+
blockers.push('agent_no_overlap_not_ok');
|
|
39
|
+
if (proof?.ledger_hash_chain_ok !== true)
|
|
40
|
+
blockers.push('agent_ledger_hash_chain_not_ok');
|
|
41
|
+
if (proof?.consensus_ok !== true)
|
|
42
|
+
blockers.push('agent_consensus_not_ok');
|
|
43
|
+
if (proof?.janitor_ok !== true)
|
|
44
|
+
blockers.push('agent_janitor_missing_or_not_ok');
|
|
45
|
+
const sessionsClosed = proof?.all_sessions_closed === true && gate?.all_sessions_closed !== false;
|
|
32
46
|
const ok = missing.length === 0 && blockers.length === 0 && sessionsClosed && proof?.schema === 'sks.agent-proof-evidence.v1';
|
|
33
47
|
return {
|
|
34
48
|
id: AGENT_INTAKE_STAGE_ID,
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface AgentJanitorReport {
|
|
2
|
+
schema: 'sks.agent-janitor-report.v1';
|
|
3
|
+
generated_at: string;
|
|
4
|
+
ok: boolean;
|
|
5
|
+
mission_id: string | null;
|
|
6
|
+
project_hash: string | null;
|
|
7
|
+
stale_heartbeat_sessions: string[];
|
|
8
|
+
zombie_process_sessions: string[];
|
|
9
|
+
stale_tmux_sessions: string[];
|
|
10
|
+
orphan_temp_dirs: string[];
|
|
11
|
+
stale_locks: string[];
|
|
12
|
+
cleaned: string[];
|
|
13
|
+
blockers: string[];
|
|
14
|
+
}
|
|
15
|
+
export declare function runAgentJanitor(input: {
|
|
16
|
+
missionDir: string;
|
|
17
|
+
missionId?: string | null;
|
|
18
|
+
projectHash?: string | null;
|
|
19
|
+
staleMs?: number;
|
|
20
|
+
cleanup?: boolean;
|
|
21
|
+
}): Promise<AgentJanitorReport>;
|
|
22
|
+
export declare function writeAgentJanitorReport(missionDir: string, report: AgentJanitorReport): Promise<void>;
|
|
23
|
+
//# sourceMappingURL=agent-janitor.d.ts.map
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import fsp from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { exists, nowIso, readJson, writeJsonAtomic } from '../fsx.js';
|
|
4
|
+
import { normalizeAgentSessionRows } from './agent-session-rows.js';
|
|
5
|
+
export async function runAgentJanitor(input) {
|
|
6
|
+
const staleMs = input.staleMs ?? 30 * 60 * 1000;
|
|
7
|
+
const agentRoot = path.join(input.missionDir, 'agents');
|
|
8
|
+
const sessions = await readJson(path.join(agentRoot, 'agent-sessions.json'), null);
|
|
9
|
+
const namespace = await readJson(path.join(input.missionDir, 'project-session-namespace.json'), null);
|
|
10
|
+
const projectHash = input.projectHash || namespace?.root_hash || null;
|
|
11
|
+
const rows = normalizeAgentSessionRows(sessions);
|
|
12
|
+
const now = Date.now();
|
|
13
|
+
const staleHeartbeat = rows
|
|
14
|
+
.filter((row) => {
|
|
15
|
+
const status = String(row.status || row.lifecycle_state || '');
|
|
16
|
+
if (['closed', 'completed', 'done'].includes(status))
|
|
17
|
+
return false;
|
|
18
|
+
const heartbeat = Date.parse(String(row.heartbeat_at || row.last_heartbeat_at || row.updated_at || ''));
|
|
19
|
+
return !Number.isFinite(heartbeat) || now - heartbeat > staleMs;
|
|
20
|
+
})
|
|
21
|
+
.map((row) => String(row.session_id || row.id || row.agent_id));
|
|
22
|
+
const statusByAgent = new Map();
|
|
23
|
+
const statusBySession = new Map();
|
|
24
|
+
for (const row of rows) {
|
|
25
|
+
const status = String(row.status || row.lifecycle_state || '');
|
|
26
|
+
if (row.agent_id || row.id)
|
|
27
|
+
statusByAgent.set(String(row.agent_id || row.id), status);
|
|
28
|
+
if (row.session_id)
|
|
29
|
+
statusBySession.set(String(row.session_id), status);
|
|
30
|
+
}
|
|
31
|
+
const zombieProcesses = await detectZombieProcessSessions(agentRoot, statusByAgent, statusBySession);
|
|
32
|
+
const staleTmuxSessions = await detectStaleTmuxSessions(agentRoot, staleMs);
|
|
33
|
+
const orphanTempDirs = await scopedExistingPaths(Array.isArray(namespace?.orphan_temp_dirs) ? namespace.orphan_temp_dirs : [], projectHash);
|
|
34
|
+
const staleLocks = await scopedStaleLockPaths(namespace?.lock_dir ? [namespace.lock_dir] : [], projectHash, staleMs);
|
|
35
|
+
const cleaned = [];
|
|
36
|
+
if (input.cleanup) {
|
|
37
|
+
for (const dir of orphanTempDirs) {
|
|
38
|
+
await fsp.rm(dir, { recursive: true, force: true }).catch(() => { });
|
|
39
|
+
cleaned.push(dir);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const blockers = [
|
|
43
|
+
...staleHeartbeat.map((id) => `stale_heartbeat:${id}`),
|
|
44
|
+
...zombieProcesses.map((id) => `zombie_process:${id}`),
|
|
45
|
+
...staleTmuxSessions.map((id) => `stale_tmux:${id}`),
|
|
46
|
+
...staleLocks.map((id) => `stale_lock:${id}`),
|
|
47
|
+
];
|
|
48
|
+
const report = {
|
|
49
|
+
schema: 'sks.agent-janitor-report.v1',
|
|
50
|
+
generated_at: nowIso(),
|
|
51
|
+
ok: blockers.length === 0,
|
|
52
|
+
mission_id: input.missionId || namespace?.mission_id || null,
|
|
53
|
+
project_hash: projectHash,
|
|
54
|
+
stale_heartbeat_sessions: staleHeartbeat,
|
|
55
|
+
zombie_process_sessions: zombieProcesses,
|
|
56
|
+
stale_tmux_sessions: staleTmuxSessions,
|
|
57
|
+
orphan_temp_dirs: orphanTempDirs,
|
|
58
|
+
stale_locks: staleLocks,
|
|
59
|
+
cleaned,
|
|
60
|
+
blockers,
|
|
61
|
+
};
|
|
62
|
+
await writeAgentJanitorReport(input.missionDir, report);
|
|
63
|
+
return report;
|
|
64
|
+
}
|
|
65
|
+
export async function writeAgentJanitorReport(missionDir, report) {
|
|
66
|
+
await writeJsonAtomic(path.join(missionDir, 'agents', 'agent-janitor-report.json'), report);
|
|
67
|
+
}
|
|
68
|
+
async function scopedExistingPaths(paths, projectHash) {
|
|
69
|
+
const out = [];
|
|
70
|
+
for (const candidate of paths) {
|
|
71
|
+
if (!candidate)
|
|
72
|
+
continue;
|
|
73
|
+
if (projectHash && !candidate.includes(projectHash))
|
|
74
|
+
continue;
|
|
75
|
+
if (await exists(candidate))
|
|
76
|
+
out.push(candidate);
|
|
77
|
+
}
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
async function scopedStaleLockPaths(paths, projectHash, staleMs) {
|
|
81
|
+
const out = [];
|
|
82
|
+
const now = Date.now();
|
|
83
|
+
for (const dir of paths) {
|
|
84
|
+
if (!dir || (projectHash && !dir.includes(projectHash)) || !(await exists(dir)))
|
|
85
|
+
continue;
|
|
86
|
+
for (const file of await listFiles(dir)) {
|
|
87
|
+
const stat = await fsp.stat(file).catch(() => null);
|
|
88
|
+
if (stat && now - stat.mtimeMs > staleMs)
|
|
89
|
+
out.push(file);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
async function detectZombieProcessSessions(agentRoot, statusByAgent, statusBySession) {
|
|
95
|
+
const out = [];
|
|
96
|
+
for (const file of await listNamedFiles(path.join(agentRoot, 'sessions'), 'agent-process-report.json')) {
|
|
97
|
+
const report = await readJson(file, null);
|
|
98
|
+
const pid = Number(report?.pid || 0);
|
|
99
|
+
if (!pid || report?.exit_code !== null)
|
|
100
|
+
continue;
|
|
101
|
+
const id = String(report?.session_id || report?.agent_id || path.basename(path.dirname(file)));
|
|
102
|
+
const status = statusBySession.get(String(report?.session_id || '')) || statusByAgent.get(String(report?.agent_id || '')) || '';
|
|
103
|
+
const alive = processIsAlive(pid);
|
|
104
|
+
if ((!alive && !['closed', 'completed', 'done'].includes(status)) || (alive && ['closed', 'completed', 'done'].includes(status)))
|
|
105
|
+
out.push(id);
|
|
106
|
+
}
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
async function detectStaleTmuxSessions(agentRoot, staleMs) {
|
|
110
|
+
const out = [];
|
|
111
|
+
const now = Date.now();
|
|
112
|
+
for (const file of await listNamedFiles(path.join(agentRoot, 'sessions'), 'agent-tmux-report.json')) {
|
|
113
|
+
const report = await readJson(file, null);
|
|
114
|
+
if (!report || report.launch_mode === 'optional_not_launched')
|
|
115
|
+
continue;
|
|
116
|
+
const stat = await fsp.stat(file).catch(() => null);
|
|
117
|
+
if (stat && now - stat.mtimeMs > staleMs)
|
|
118
|
+
out.push(String(report.session_id || report.agent_id || path.basename(path.dirname(file))));
|
|
119
|
+
}
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
async function listNamedFiles(dir, name) {
|
|
123
|
+
return (await listFiles(dir)).filter((file) => path.basename(file) === name);
|
|
124
|
+
}
|
|
125
|
+
async function listFiles(dir) {
|
|
126
|
+
const out = [];
|
|
127
|
+
if (!(await exists(dir)))
|
|
128
|
+
return out;
|
|
129
|
+
for (const entry of await fsp.readdir(dir, { withFileTypes: true })) {
|
|
130
|
+
const file = path.join(dir, entry.name);
|
|
131
|
+
if (entry.isDirectory())
|
|
132
|
+
out.push(...await listFiles(file));
|
|
133
|
+
else if (entry.isFile())
|
|
134
|
+
out.push(file);
|
|
135
|
+
}
|
|
136
|
+
return out;
|
|
137
|
+
}
|
|
138
|
+
function processIsAlive(pid) {
|
|
139
|
+
try {
|
|
140
|
+
process.kill(pid, 0);
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=agent-janitor.js.map
|