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.
Files changed (51) hide show
  1. package/README.md +4 -4
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/crates/sks-core/src/main.rs +1 -1
  5. package/dist/.sks-build-stamp.json +4 -4
  6. package/dist/bin/sks.js +1 -1
  7. package/dist/build-manifest.json +28 -4
  8. package/dist/commands/image-ux-review.d.ts +2 -0
  9. package/dist/commands/ppt.d.ts +2 -0
  10. package/dist/core/agents/agent-codex-cockpit.d.ts +53 -0
  11. package/dist/core/agents/agent-codex-cockpit.js +168 -0
  12. package/dist/core/agents/agent-command-surface.d.ts +1 -0
  13. package/dist/core/agents/agent-command-surface.js +8 -4
  14. package/dist/core/agents/agent-gate.js +15 -1
  15. package/dist/core/agents/agent-janitor.d.ts +23 -0
  16. package/dist/core/agents/agent-janitor.js +147 -0
  17. package/dist/core/agents/agent-ledger-schemas.d.ts +178 -2
  18. package/dist/core/agents/agent-ledger-schemas.js +24 -1
  19. package/dist/core/agents/agent-orchestrator.d.ts +2 -0
  20. package/dist/core/agents/agent-orchestrator.js +44 -2
  21. package/dist/core/agents/agent-plan.js +2 -2
  22. package/dist/core/agents/agent-proof-evidence.d.ts +3 -0
  23. package/dist/core/agents/agent-proof-evidence.js +3 -0
  24. package/dist/core/agents/agent-session-rows.d.ts +2 -0
  25. package/dist/core/agents/agent-session-rows.js +14 -0
  26. package/dist/core/agents/agent-worker-pipeline.js +1 -1
  27. package/dist/core/agents/route-collaboration-ledger.d.ts +2 -0
  28. package/dist/core/commands/agent-command.js +13 -2
  29. package/dist/core/commands/image-ux-review-command.d.ts +2 -0
  30. package/dist/core/commands/ppt-command.d.ts +2 -0
  31. package/dist/core/fsx.d.ts +1 -1
  32. package/dist/core/fsx.js +1 -1
  33. package/dist/core/proof/auto-finalize.js +4 -0
  34. package/dist/core/proof/route-proof-gate.js +17 -4
  35. package/dist/core/proof/route-proof-policy.js +3 -0
  36. package/dist/core/session/project-namespace.d.ts +31 -0
  37. package/dist/core/session/project-namespace.js +52 -0
  38. package/dist/core/tmux-ui.js +3 -1
  39. package/dist/core/verification/verification-artifact-lock.d.ts +9 -0
  40. package/dist/core/verification/verification-artifact-lock.js +22 -0
  41. package/dist/core/verification/verification-dag.d.ts +10 -0
  42. package/dist/core/verification/verification-dag.js +70 -0
  43. package/dist/core/verification/verification-proof.d.ts +7 -0
  44. package/dist/core/verification/verification-proof.js +27 -0
  45. package/dist/core/verification/verification-result.d.ts +40 -0
  46. package/dist/core/verification/verification-result.js +20 -0
  47. package/dist/core/verification/verification-worker-pool.d.ts +13 -0
  48. package/dist/core/verification/verification-worker-pool.js +186 -0
  49. package/dist/core/version.d.ts +1 -1
  50. package/dist/core/version.js +1 -1
  51. 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.16.2** carries forward the native multi-session agent kernel and adds Codex App prompt-side Team width control: `$Team 20:agents ...` / `$Team 20:agent ...` can request up to 20 native Team sessions. SKS **1.16.1** closed the kernel itself: `sks agent` and `sks --agent` create bounded agent rosters, non-overlapping leases, append-only ledgers, session lifecycle proof, recursion guards, dynamic per-agent effort routing, and native agent proof evidence. The old multi-agent command surface has been removed; native agents are the only release-supported route collaboration backend.
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.mjs,src/core/routes.mjs
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.mjs
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 audit, changelog, syntax, feature-registry coverage, all-features contract selftest, selftest, size, and registry checks, then writes a source digest stamp under `.sneakoscope/reports/`. Generate the human-readable registry with `sks features inventory --write-docs`. `1.0.0` is a stable release, so 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.
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
 
@@ -76,7 +76,7 @@ dependencies = [
76
76
 
77
77
  [[package]]
78
78
  name = "sks-core"
79
- version = "1.16.2"
79
+ version = "1.17.0"
80
80
  dependencies = [
81
81
  "serde_json",
82
82
  ]
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "sks-core"
3
- version = "1.16.2"
3
+ version = "1.17.0"
4
4
  edition = "2021"
5
5
 
6
6
  [dependencies]
@@ -4,7 +4,7 @@ use std::io::{self, Read, Seek, SeekFrom};
4
4
  fn main() {
5
5
  let mut args = std::env::args().skip(1);
6
6
  match args.next().as_deref() {
7
- Some("--version") => println!("sks-rs 1.16.2"),
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.16.2",
5
- "source_digest": "38a14936b463c4d5f6254412dbaa186a6f351f637dd5e5db82a6eb6b5eff43b9",
6
- "source_file_count": 1475,
7
- "built_at_source_time": 1779679969693
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
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- const FAST_PACKAGE_VERSION = '1.16.2';
2
+ const FAST_PACKAGE_VERSION = '1.17.0';
3
3
  const args = process.argv.slice(2);
4
4
  try {
5
5
  if (args[0] === '--version' || args[0] === '-v' || args[0] === 'version') {
@@ -1,11 +1,17 @@
1
1
  {
2
2
  "schema": "sks.dist-build.v2",
3
- "version": "1.16.2",
4
- "package_version": "1.16.2",
3
+ "version": "1.17.0",
4
+ "package_version": "1.17.0",
5
5
  "typescript": true,
6
6
  "mjs_runtime_files": 0,
7
- "source_digest": "38a14936b463c4d5f6254412dbaa186a6f351f637dd5e5db82a6eb6b5eff43b9",
8
- "source_file_count": 1475,
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;
@@ -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
@@ -12,5 +12,6 @@ export declare function parseAgentCommandArgs(command: string, args?: string[]):
12
12
  json: boolean;
13
13
  missionId: string;
14
14
  lane: string;
15
+ codexApp: boolean;
15
16
  };
16
17
  //# sourceMappingURL=agent-command-surface.d.ts.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 missionId = String(readOption(args, '--mission', readOption(args, '--mission-id', missionDefault)));
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 prompt = positionalArgs(rest, new Set(['--agents', '--concurrency', '--backend', '--route', '--mission', '--mission-id', '--agent', '--lane'])).join(' ').trim() || 'Native agent run';
19
- return { command, action, prompt, route, agents, concurrency, backend, mock, real, readonly, json, missionId, lane };
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
- const sessionsClosed = proof?.all_sessions_closed !== false && gate?.all_sessions_closed !== false;
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