sneakoscope 2.0.16 → 2.0.18
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 +23 -30
- 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/cli/command-registry.js +1 -1
- package/dist/commands/doctor.js +39 -1
- package/dist/commands/proof.js +21 -0
- package/dist/commands/zellij-slot-pane.js +7 -1
- package/dist/core/agents/agent-effort-policy.js +7 -1
- package/dist/core/agents/agent-orchestrator.js +3 -1
- package/dist/core/agents/agent-scheduler.js +14 -1
- package/dist/core/agents/native-cli-session-swarm.js +11 -7
- package/dist/core/agents/native-cli-worker.js +56 -7
- package/dist/core/agents/parallel-runtime-proof.js +68 -9
- package/dist/core/agents/runtime-proof-summary.js +75 -0
- package/dist/core/codex-app/codex-app-handoff.js +77 -0
- package/dist/core/codex-control/codex-0138-capability.js +64 -0
- package/dist/core/codex-control/codex-model-capabilities.js +41 -0
- package/dist/core/codex-control/codex-sdk-config-policy.js +1 -1
- package/dist/core/codex-control/codex-task-runner.js +1 -1
- package/dist/core/codex-plugins/codex-plugin-json.js +152 -0
- package/dist/core/commands/mad-sks-command.js +4 -0
- package/dist/core/commands/naruto-command.js +20 -4
- package/dist/core/commands/qa-loop-command.js +111 -4
- package/dist/core/commands/team-command.js +6 -311
- package/dist/core/commands/team-legacy-observe-command.js +182 -0
- package/dist/core/db-safety.js +15 -0
- package/dist/core/doctor/codex-0138-doctor.js +104 -0
- package/dist/core/doctor/doctor-readiness-matrix.js +11 -0
- package/dist/core/effort-orchestrator.js +9 -0
- package/dist/core/feature-registry.js +4 -2
- package/dist/core/fsx.js +1 -1
- package/dist/core/hooks-runtime.js +38 -4
- package/dist/core/image/image-artifact-path-contract.js +99 -0
- package/dist/core/image-ux-review/imagegen-adapter.js +24 -3
- package/dist/core/init.js +1 -0
- package/dist/core/mad-db/mad-db-capability.js +9 -1
- package/dist/core/mad-db/mad-db-result-lifecycle.js +207 -0
- package/dist/core/mcp/mcp-plugin-inventory.js +29 -0
- package/dist/core/mcp/mcp-server-policy.js +24 -0
- package/dist/core/qa-loop/qa-loop-budget-policy.js +37 -0
- package/dist/core/qa-loop.js +28 -2
- package/dist/core/release/release-gate-affected-selector.js +47 -5
- package/dist/core/release/release-gate-dag.js +5 -1
- package/dist/core/release/release-gate-scheduler.js +2 -1
- package/dist/core/routes.js +3 -1
- package/dist/core/usage/codex-account-usage.js +78 -0
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/zellij-slot-column-anchor.js +16 -7
- package/dist/core/zellij/zellij-slot-pane-renderer.js +92 -1
- package/dist/core/zellij/zellij-slot-telemetry.js +29 -6
- package/dist/core/zellij/zellij-ui-mode.js +12 -2
- package/dist/scripts/prepublish-release-check-or-fast.js +3 -3
- package/dist/scripts/release-gate-existence-audit.js +5 -1
- package/dist/scripts/release-speed-summary.js +22 -2
- package/package.json +38 -4
- package/schemas/agents/parallel-runtime-proof.schema.json +31 -0
- package/schemas/codex-app/codex-app-handoff.schema.json +20 -0
- package/schemas/codex-plugins/codex-plugin-inventory.schema.json +32 -0
- package/schemas/image/image-artifact-path-contract.schema.json +32 -0
- package/schemas/usage/codex-account-usage.schema.json +27 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import { appendJsonlBounded, ensureDir, nowIso, readText, writeJsonAtomic } from '../fsx.js';
|
|
2
|
+
import { appendJsonlBounded, ensureDir, nowIso, readJson, readText, writeJsonAtomic } from '../fsx.js';
|
|
3
3
|
export const PARALLEL_RUNTIME_EVENT_SCHEMA = 'sks.parallel-runtime-event.v1';
|
|
4
4
|
export const PARALLEL_RUNTIME_PROOF_SCHEMA = 'sks.parallel-runtime-proof.v1';
|
|
5
5
|
export function parallelRuntimeEventPath(root, missionId) {
|
|
@@ -92,6 +92,9 @@ export async function buildParallelRuntimeProof(root, missionId, opts = {}) {
|
|
|
92
92
|
}
|
|
93
93
|
const requestedWorkers = positiveInt(opts.requestedWorkers, workerStarts.size || workerPids.size || maxWorkers);
|
|
94
94
|
const targetActiveSlots = positiveInt(opts.targetActiveSlots, requestedWorkers);
|
|
95
|
+
const proofMode = opts.proofMode || 'production';
|
|
96
|
+
const allowMissingPids = proofMode === 'in-process-fixture' && opts.allowMissingPids === true;
|
|
97
|
+
const requireWorkerPids = opts.requireWorkerPids ?? (proofMode === 'production' && requestedWorkers >= 16);
|
|
95
98
|
const wallMs = Math.max(0, lastMs - firstMs);
|
|
96
99
|
const sequentialEstimateMs = workerDurations.length
|
|
97
100
|
? workerDurations.reduce((sum, value) => sum + value, 0)
|
|
@@ -107,17 +110,33 @@ export async function buildParallelRuntimeProof(root, missionId, opts = {}) {
|
|
|
107
110
|
const launchEvents = sorted.filter((event) => event.event_type === 'worker_launch_invoked' || event.event_type === 'worker_process_spawned');
|
|
108
111
|
const launchSpanMs = launchEvents.length ? Math.max(...launchEvents.map((event) => event.ms)) - Math.min(...launchEvents.map((event) => event.ms)) : 0;
|
|
109
112
|
const firstBatchLimit = positiveInt(opts.firstBatchLaunchSpanLimitMs, requestedWorkers >= 16 ? 2500 : 30000);
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
113
|
+
const schedulerState = await readJson(path.join(root, 'agent-scheduler-state.json'), null).catch(() => null);
|
|
114
|
+
const coalescedOverlapWindows = coalesceOverlapWindows(overlapWindows);
|
|
115
|
+
const utilizationProofConsistency = buildUtilizationProofConsistency(schedulerState, {
|
|
116
|
+
proofMaxActive: maxWorkers,
|
|
117
|
+
proofWallMs: wallMs,
|
|
118
|
+
proofActiveSlotTimeMs: activeSlotTimeMsFromWindows(coalescedOverlapWindows)
|
|
119
|
+
});
|
|
120
|
+
const blockers = [];
|
|
121
|
+
if (!sorted.length)
|
|
122
|
+
blockers.push('parallel_runtime_events_missing');
|
|
123
|
+
if (minActiveWorkers > 0 && maxWorkers < minActiveWorkers)
|
|
124
|
+
blockers.push('max_observed_active_workers_below_target');
|
|
125
|
+
if (requireWorkerPids && workerPids.size < minActiveWorkers)
|
|
126
|
+
blockers.push('unique_worker_pids_below_target');
|
|
127
|
+
if (requireWorkerPids && workerPids.size === 0)
|
|
128
|
+
blockers.push('unique_worker_pids_missing_in_production_proof');
|
|
129
|
+
if (speedupRatio < minSpeedup)
|
|
130
|
+
blockers.push('speedup_ratio_below_target');
|
|
131
|
+
if (firstBatchLaunchSpanMs > firstBatchLimit)
|
|
132
|
+
blockers.push('first_batch_launch_span_above_limit');
|
|
117
133
|
return {
|
|
118
134
|
schema: PARALLEL_RUNTIME_PROOF_SCHEMA,
|
|
119
135
|
mission_id: missionId,
|
|
120
136
|
generated_at: nowIso(),
|
|
137
|
+
proof_mode: proofMode,
|
|
138
|
+
require_worker_pids: requireWorkerPids,
|
|
139
|
+
allow_missing_pids: allowMissingPids,
|
|
121
140
|
requested_workers: requestedWorkers,
|
|
122
141
|
target_active_slots: targetActiveSlots,
|
|
123
142
|
max_observed_active_workers: maxWorkers,
|
|
@@ -130,9 +149,10 @@ export async function buildParallelRuntimeProof(root, missionId, opts = {}) {
|
|
|
130
149
|
wall_ms: wallMs,
|
|
131
150
|
sequential_estimate_ms: sequentialEstimateMs,
|
|
132
151
|
speedup_ratio: speedupRatio,
|
|
133
|
-
overlap_windows:
|
|
152
|
+
overlap_windows: coalescedOverlapWindows,
|
|
134
153
|
visible_panes: visiblePanes,
|
|
135
154
|
headless_workers: headlessWorkers,
|
|
155
|
+
utilization_proof_consistency: utilizationProofConsistency,
|
|
136
156
|
passed: blockers.length === 0,
|
|
137
157
|
blockers
|
|
138
158
|
};
|
|
@@ -196,6 +216,45 @@ function nonNegativeInt(value, fallback) {
|
|
|
196
216
|
return Math.max(0, Math.floor(fallback || 0));
|
|
197
217
|
return Math.floor(parsed);
|
|
198
218
|
}
|
|
219
|
+
function buildUtilizationProofConsistency(state, input) {
|
|
220
|
+
if (!state || typeof state !== 'object') {
|
|
221
|
+
return {
|
|
222
|
+
ok: true,
|
|
223
|
+
scheduler_max_active: 0,
|
|
224
|
+
proof_max_active: input.proofMaxActive,
|
|
225
|
+
wall_ms_delta: 0,
|
|
226
|
+
scheduler_active_slot_time_ms: 0,
|
|
227
|
+
proof_active_slot_time_ms: input.proofActiveSlotTimeMs,
|
|
228
|
+
active_slot_time_ms_delta: 0,
|
|
229
|
+
scheduler_observation_delay_tolerance_ms: 0
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
const schedulerMaxActive = nonNegativeInt(state.max_observed_active_slots, 0);
|
|
233
|
+
const schedulerWallMs = nonNegativeInt(state.wall_time_ms, 0);
|
|
234
|
+
const schedulerActiveSlotTimeMs = nonNegativeInt(state.active_slot_time_ms, 0);
|
|
235
|
+
const wallDelta = Math.abs(schedulerWallMs - input.proofWallMs);
|
|
236
|
+
const activeSlotDelta = Math.abs(schedulerActiveSlotTimeMs - input.proofActiveSlotTimeMs);
|
|
237
|
+
const maxActiveDelta = Math.abs(schedulerMaxActive - input.proofMaxActive);
|
|
238
|
+
const wallToleranceMs = Math.max(500, Math.round(Math.max(schedulerWallMs, input.proofWallMs) * 0.25));
|
|
239
|
+
const activeSlotToleranceMs = Math.max(500, Math.round(Math.max(schedulerActiveSlotTimeMs, input.proofActiveSlotTimeMs) * 0.25));
|
|
240
|
+
const observationDelayToleranceMs = Math.max(activeSlotToleranceMs, wallDelta * Math.max(1, schedulerMaxActive));
|
|
241
|
+
const wallConsistent = wallDelta <= wallToleranceMs;
|
|
242
|
+
const activeSlotConsistent = schedulerActiveSlotTimeMs > 0 && input.proofActiveSlotTimeMs > 0 && (activeSlotDelta <= activeSlotToleranceMs
|
|
243
|
+
|| (schedulerActiveSlotTimeMs >= input.proofActiveSlotTimeMs && activeSlotDelta <= observationDelayToleranceMs));
|
|
244
|
+
return {
|
|
245
|
+
ok: maxActiveDelta <= 1 && (wallConsistent || activeSlotConsistent),
|
|
246
|
+
scheduler_max_active: schedulerMaxActive,
|
|
247
|
+
proof_max_active: input.proofMaxActive,
|
|
248
|
+
wall_ms_delta: wallDelta,
|
|
249
|
+
scheduler_active_slot_time_ms: schedulerActiveSlotTimeMs,
|
|
250
|
+
proof_active_slot_time_ms: input.proofActiveSlotTimeMs,
|
|
251
|
+
active_slot_time_ms_delta: activeSlotDelta,
|
|
252
|
+
scheduler_observation_delay_tolerance_ms: observationDelayToleranceMs
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
function activeSlotTimeMsFromWindows(windows) {
|
|
256
|
+
return windows.reduce((sum, window) => sum + Math.max(0, window.end_ms - window.start_ms) * Math.max(0, window.active_workers), 0);
|
|
257
|
+
}
|
|
199
258
|
function coalesceOverlapWindows(windows) {
|
|
200
259
|
return windows
|
|
201
260
|
.filter((window) => window.end_ms > window.start_ms)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { findLatestMission, missionDir } from '../mission.js';
|
|
3
|
+
import { readJson, writeJsonAtomic } from '../fsx.js';
|
|
4
|
+
export const RUNTIME_PROOF_SUMMARY_SCHEMA = 'sks.runtime-proof-summary.v1';
|
|
5
|
+
export async function buildRuntimeProofSummary(root, missionIdInput = 'latest') {
|
|
6
|
+
const missionId = missionIdInput === 'latest' ? await findLatestMission(root) : missionIdInput;
|
|
7
|
+
if (!missionId)
|
|
8
|
+
throw new Error('runtime_proof_summary_mission_missing');
|
|
9
|
+
const dir = missionDir(root, missionId);
|
|
10
|
+
const agentsDir = path.join(dir, 'agents');
|
|
11
|
+
const parallel = await readJson(path.join(agentsDir, 'parallel-runtime-proof.json'), null);
|
|
12
|
+
const scheduler = await readJson(path.join(agentsDir, 'agent-scheduler-state.json'), null);
|
|
13
|
+
const swarm = await readJson(path.join(agentsDir, 'agent-native-cli-session-swarm.json'), null);
|
|
14
|
+
const telemetry = await readJson(path.join(dir, 'zellij', 'slot-telemetry.snapshot.json'), null);
|
|
15
|
+
const governor = await readJson(path.join(agentsDir, 'naruto-concurrency-governor.json'), null);
|
|
16
|
+
const telemetryAgeMs = telemetry?.updated_at ? Math.max(0, Date.now() - Date.parse(telemetry.updated_at)) : Number.MAX_SAFE_INTEGER;
|
|
17
|
+
const visiblePanes = Number(parallel?.visible_panes ?? swarm?.zellij_pane_worker_sessions ?? telemetryVisiblePaneCount(telemetry) ?? 0);
|
|
18
|
+
const targetActive = Number(scheduler?.target_active_slots ?? parallel?.target_active_slots ?? swarm?.target_active_slots ?? governor?.target_active_slots ?? 0);
|
|
19
|
+
const headlessWorkers = Number(parallel?.headless_workers ?? swarm?.headless_overflow_worker_count ?? Math.max(0, targetActive - visiblePanes));
|
|
20
|
+
const blockers = [
|
|
21
|
+
...(!parallel ? ['parallel_runtime_proof_missing'] : []),
|
|
22
|
+
...(!scheduler ? ['agent_scheduler_state_missing'] : []),
|
|
23
|
+
...(parallel?.passed === false ? parallel.blockers || ['parallel_runtime_proof_failed'] : []),
|
|
24
|
+
...(telemetryAgeMs > 3000 ? ['zellij_telemetry_stale'] : [])
|
|
25
|
+
].map(String);
|
|
26
|
+
const summary = {
|
|
27
|
+
schema: RUNTIME_PROOF_SUMMARY_SCHEMA,
|
|
28
|
+
ok: blockers.length === 0,
|
|
29
|
+
mission_id: missionId,
|
|
30
|
+
generated_at: new Date().toISOString(),
|
|
31
|
+
parallel: {
|
|
32
|
+
max_active_workers: Number(parallel?.max_observed_active_workers || scheduler?.max_observed_active_slots || 0),
|
|
33
|
+
unique_worker_pids: Number(parallel?.unique_worker_pids || uniqueNumbers(swarm?.process_ids).length || 0),
|
|
34
|
+
speedup_ratio: Number(parallel?.speedup_ratio || 0),
|
|
35
|
+
proof_passed: parallel?.passed === true
|
|
36
|
+
},
|
|
37
|
+
ui: {
|
|
38
|
+
visible_panes: visiblePanes,
|
|
39
|
+
headless_workers: headlessWorkers,
|
|
40
|
+
telemetry_age_ms: telemetryAgeMs,
|
|
41
|
+
stale: telemetryAgeMs > 3000
|
|
42
|
+
},
|
|
43
|
+
model_calls: {
|
|
44
|
+
max_observed: Number(parallel?.max_observed_model_calls || 0),
|
|
45
|
+
unique_model_call_ids: Number(parallel?.unique_model_call_ids || 0)
|
|
46
|
+
},
|
|
47
|
+
scheduler: {
|
|
48
|
+
largest_batch_size: Number(scheduler?.largest_batch_size || 0),
|
|
49
|
+
utilization: Number(scheduler?.scheduler_utilization || 0)
|
|
50
|
+
},
|
|
51
|
+
blockers
|
|
52
|
+
};
|
|
53
|
+
await writeJsonAtomic(path.join(agentsDir, 'runtime-proof-summary.json'), summary);
|
|
54
|
+
return summary;
|
|
55
|
+
}
|
|
56
|
+
export function renderRuntimeProofSummary(summary) {
|
|
57
|
+
return [
|
|
58
|
+
`Parallel proof: ${summary.parallel.proof_passed ? 'passed' : 'blocked'}`,
|
|
59
|
+
`Active workers: ${summary.parallel.max_active_workers}`,
|
|
60
|
+
`Unique PIDs: ${summary.parallel.unique_worker_pids}`,
|
|
61
|
+
`Speedup: ${summary.parallel.speedup_ratio}x`,
|
|
62
|
+
`Visible/headless: ${summary.ui.visible_panes} / ${summary.ui.headless_workers}`,
|
|
63
|
+
`Telemetry: ${summary.ui.stale ? `stale ${(summary.ui.telemetry_age_ms / 1000).toFixed(1)}s` : `fresh ${(summary.ui.telemetry_age_ms / 1000).toFixed(1)}s`}`,
|
|
64
|
+
`Model calls max: ${summary.model_calls.max_observed}`,
|
|
65
|
+
...(summary.blockers.length ? [`Blockers: ${summary.blockers.join(', ')}`] : [])
|
|
66
|
+
].join('\n');
|
|
67
|
+
}
|
|
68
|
+
function telemetryVisiblePaneCount(snapshot) {
|
|
69
|
+
const slots = snapshot?.slots && typeof snapshot.slots === 'object' ? Object.values(snapshot.slots) : [];
|
|
70
|
+
return slots.filter((row) => row?.status && row.status !== 'headless').length;
|
|
71
|
+
}
|
|
72
|
+
function uniqueNumbers(values) {
|
|
73
|
+
return [...new Set((Array.isArray(values) ? values : []).map((value) => Number(value)).filter((value) => Number.isFinite(value)))];
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=runtime-proof-summary.js.map
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { detectCodex0138Capability } from '../codex-control/codex-0138-capability.js';
|
|
3
|
+
import { nowIso, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
|
|
4
|
+
export function buildCodexAppHandoffPrompt(request) {
|
|
5
|
+
return [
|
|
6
|
+
'SKS Codex Desktop /app handoff request',
|
|
7
|
+
`Mission: ${request.mission_id}`,
|
|
8
|
+
`Route: ${request.route}`,
|
|
9
|
+
`Reason: ${request.reason}`,
|
|
10
|
+
`Workspace: ${request.workspace_path}`,
|
|
11
|
+
request.thread_ref ? `Thread: ${request.thread_ref}` : '',
|
|
12
|
+
'',
|
|
13
|
+
'Artifacts:',
|
|
14
|
+
...(request.artifacts || []).map((artifact) => `- ${artifact}`),
|
|
15
|
+
'',
|
|
16
|
+
'Prompt:',
|
|
17
|
+
request.prompt,
|
|
18
|
+
'',
|
|
19
|
+
'Operator instruction: open Codex Desktop with `codex /app` and continue this mission using the artifacts above. Do not treat this handoff artifact as web UI verification evidence.'
|
|
20
|
+
].filter((line) => line !== '').join('\n');
|
|
21
|
+
}
|
|
22
|
+
export async function runCodexAppHandoff(root, request) {
|
|
23
|
+
const capability = await detectCodex0138Capability();
|
|
24
|
+
const platformSupported = process.platform === 'darwin' || process.platform === 'win32';
|
|
25
|
+
const desktopSupported = capability.supports_app_handoff === true && platformSupported;
|
|
26
|
+
const dir = path.join(root, '.sneakoscope', 'missions', request.mission_id, 'qa-loop');
|
|
27
|
+
const artifactPath = path.join(dir, 'app-handoff.json');
|
|
28
|
+
const promptArtifactPath = path.join(dir, 'app-handoff-prompt.md');
|
|
29
|
+
const blockers = [
|
|
30
|
+
...(capability.supports_app_handoff ? [] : ['codex_0_138_app_handoff_unavailable']),
|
|
31
|
+
...(platformSupported ? [] : ['codex_app_handoff_platform_unsupported'])
|
|
32
|
+
];
|
|
33
|
+
const prompt = buildCodexAppHandoffPrompt(request);
|
|
34
|
+
await writeTextAtomic(promptArtifactPath, prompt);
|
|
35
|
+
const status = request.require_desktop && !desktopSupported
|
|
36
|
+
? 'blocked_for_desktop_review'
|
|
37
|
+
: desktopSupported
|
|
38
|
+
? 'pending'
|
|
39
|
+
: 'skipped';
|
|
40
|
+
const result = {
|
|
41
|
+
schema: 'sks.codex-app-handoff-result.v1',
|
|
42
|
+
ok: request.require_desktop ? desktopSupported : true,
|
|
43
|
+
attempted: false,
|
|
44
|
+
launched: false,
|
|
45
|
+
status,
|
|
46
|
+
codex_0138_capability: capability,
|
|
47
|
+
command_line: ['codex', '/app'],
|
|
48
|
+
desktop_handoff_supported: desktopSupported,
|
|
49
|
+
fallback_reason: desktopSupported
|
|
50
|
+
? 'interactive_tui_handoff_pending_operator'
|
|
51
|
+
: blockers.join(';') || null,
|
|
52
|
+
artifact_path: artifactPath,
|
|
53
|
+
prompt_artifact_path: promptArtifactPath,
|
|
54
|
+
blockers: request.require_desktop ? blockers : []
|
|
55
|
+
};
|
|
56
|
+
await writeJsonAtomic(artifactPath, {
|
|
57
|
+
...result,
|
|
58
|
+
request,
|
|
59
|
+
operator_instruction: {
|
|
60
|
+
open: 'codex /app',
|
|
61
|
+
prompt_artifact: path.relative(root, promptArtifactPath),
|
|
62
|
+
created_at: nowIso()
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
export function qaLoopShouldRequestAppHandoff(input = {}) {
|
|
68
|
+
const args = input.args || [];
|
|
69
|
+
return args.includes('--app-handoff')
|
|
70
|
+
|| process.env.SKS_QA_LOOP_APP_HANDOFF === '1'
|
|
71
|
+
|| input.visualArtifactsPresent === true
|
|
72
|
+
|| input.zellijUiBlocked === true
|
|
73
|
+
|| input.pluginAppTemplateUnavailable === true
|
|
74
|
+
|| input.userRequestedDesktopReview === true
|
|
75
|
+
|| input.uiRequired === true && process.env.SKS_QA_LOOP_APP_HANDOFF_FOR_VISUAL === '1';
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=codex-app-handoff.js.map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { findCodexBinary } from '../codex-adapter.js';
|
|
3
|
+
import { compareSemverLike, parseCodexVersionText } from '../codex-compat/codex-version-policy.js';
|
|
4
|
+
import { nowIso, runProcess, writeJsonAtomic } from '../fsx.js';
|
|
5
|
+
export async function detectCodex0138Capability(input = {}) {
|
|
6
|
+
const fake = process.env.SKS_CODEX_0138_FAKE === '1';
|
|
7
|
+
const codexBin = fake
|
|
8
|
+
? input.codexBin || process.env.CODEX_BIN || 'codex'
|
|
9
|
+
: input.codexBin || process.env.CODEX_BIN || await findCodexBinary();
|
|
10
|
+
const versionText = fake
|
|
11
|
+
? String(process.env.SKS_CODEX_VERSION_FAKE || 'codex-cli 0.138.0')
|
|
12
|
+
: await readCodexVersionText(codexBin);
|
|
13
|
+
const parsed = parseCodexVersion(versionText);
|
|
14
|
+
const atLeast138 = Boolean(parsed && semverGte(parsed, '0.138.0'));
|
|
15
|
+
const blockers = [
|
|
16
|
+
...(!codexBin ? ['codex_cli_missing'] : []),
|
|
17
|
+
...(atLeast138 ? [] : ['codex_0_138_required_for_app_plugin_features'])
|
|
18
|
+
];
|
|
19
|
+
return {
|
|
20
|
+
schema: 'sks.codex-0138-capability.v1',
|
|
21
|
+
ok: atLeast138,
|
|
22
|
+
codex_bin: codexBin || null,
|
|
23
|
+
version_text: versionText || null,
|
|
24
|
+
parsed_version: parsed,
|
|
25
|
+
supports_app_handoff: atLeast138,
|
|
26
|
+
supports_plugin_json: atLeast138,
|
|
27
|
+
supports_image_path_exposure: atLeast138,
|
|
28
|
+
supports_model_defined_efforts: atLeast138,
|
|
29
|
+
supports_app_server_token_usage: atLeast138,
|
|
30
|
+
supports_v2_pat_auth: atLeast138,
|
|
31
|
+
supports_oauth_mcp_prerefresh: atLeast138,
|
|
32
|
+
blockers
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export async function writeCodex0138CapabilityArtifacts(root, input = {}) {
|
|
36
|
+
const capability = await detectCodex0138Capability({ codexBin: input.codexBin || null });
|
|
37
|
+
const report = { ...capability, generated_at: nowIso() };
|
|
38
|
+
const rootArtifact = path.join(root, '.sneakoscope', 'codex-0138-capability.json');
|
|
39
|
+
await writeJsonAtomic(rootArtifact, report);
|
|
40
|
+
let missionArtifact = null;
|
|
41
|
+
if (input.missionId) {
|
|
42
|
+
missionArtifact = path.join(root, '.sneakoscope', 'missions', input.missionId, 'codex-0138-capability.json');
|
|
43
|
+
await writeJsonAtomic(missionArtifact, report);
|
|
44
|
+
}
|
|
45
|
+
return { report, root_artifact: rootArtifact, mission_artifact: missionArtifact };
|
|
46
|
+
}
|
|
47
|
+
export function parseCodexVersion(text) {
|
|
48
|
+
return parseCodexVersionText(text);
|
|
49
|
+
}
|
|
50
|
+
export function semverGte(actual, minimum) {
|
|
51
|
+
return compareSemverLike(actual, minimum) >= 0;
|
|
52
|
+
}
|
|
53
|
+
async function readCodexVersionText(codexBin) {
|
|
54
|
+
if (!codexBin)
|
|
55
|
+
return null;
|
|
56
|
+
const result = await runProcess(codexBin, ['--version'], { timeoutMs: 10_000, maxOutputBytes: 16 * 1024 }).catch((err) => ({
|
|
57
|
+
code: 1,
|
|
58
|
+
stdout: '',
|
|
59
|
+
stderr: err?.message || String(err)
|
|
60
|
+
}));
|
|
61
|
+
const text = `${result.stdout || ''}${result.stderr || ''}`.trim();
|
|
62
|
+
return result.code === 0 ? text : text || null;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=codex-0138-capability.js.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export const SKS_FALLBACK_EFFORT_ORDER = ['minimal', 'low', 'medium', 'high', 'xhigh'];
|
|
2
|
+
export function codexModelEffortCapability(input = {}) {
|
|
3
|
+
const advertised = normalizeAdvertisedEfforts(input.advertisedEfforts);
|
|
4
|
+
const order = advertised.length ? advertised : SKS_FALLBACK_EFFORT_ORDER;
|
|
5
|
+
const defaultEffort = order.includes(String(input.defaultEffort || '')) ? String(input.defaultEffort) : order.includes('medium') ? 'medium' : order[0] || 'medium';
|
|
6
|
+
return {
|
|
7
|
+
model: String(input.model || process.env.SKS_CODEX_MODEL || process.env.CODEX_MODEL || 'gpt-5.5'),
|
|
8
|
+
advertised_efforts: order,
|
|
9
|
+
default_effort: defaultEffort,
|
|
10
|
+
order_source: advertised.length ? 'model-advertised' : 'sks-fallback'
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export function normalizeAdvertisedEfforts(value) {
|
|
14
|
+
const rows = Array.isArray(value) ? value : String(value || '').split(',');
|
|
15
|
+
const seen = new Set();
|
|
16
|
+
const out = [];
|
|
17
|
+
for (const row of rows) {
|
|
18
|
+
const effort = String(row || '').trim().toLowerCase();
|
|
19
|
+
if (!effort || seen.has(effort))
|
|
20
|
+
continue;
|
|
21
|
+
seen.add(effort);
|
|
22
|
+
out.push(effort);
|
|
23
|
+
}
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
26
|
+
export function nextAdvertisedEffort(current, capability = codexModelEffortCapability()) {
|
|
27
|
+
const order = capability.advertised_efforts.length ? capability.advertised_efforts : SKS_FALLBACK_EFFORT_ORDER;
|
|
28
|
+
const index = Math.max(0, order.indexOf(current));
|
|
29
|
+
return order[Math.min(order.length - 1, index + 1)] || current || capability.default_effort;
|
|
30
|
+
}
|
|
31
|
+
export function modelEffortAtLeast(target, capability = codexModelEffortCapability()) {
|
|
32
|
+
const order = capability.advertised_efforts.length ? capability.advertised_efforts : SKS_FALLBACK_EFFORT_ORDER;
|
|
33
|
+
if (order.includes(target))
|
|
34
|
+
return target;
|
|
35
|
+
if (target === 'recovery')
|
|
36
|
+
return order.includes('high') ? 'high' : order[order.length - 1];
|
|
37
|
+
if (target === 'forensic_vision')
|
|
38
|
+
return order.includes('xhigh') ? 'xhigh' : order[order.length - 1];
|
|
39
|
+
return capability.default_effort;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=codex-model-capabilities.js.map
|
|
@@ -2,7 +2,7 @@ export function buildCodexSdkConfig(input) {
|
|
|
2
2
|
const config = {
|
|
3
3
|
model: String(process.env.SKS_CODEX_MODEL || process.env.CODEX_MODEL || 'gpt-5.5'),
|
|
4
4
|
service_tier: 'fast',
|
|
5
|
-
model_reasoning_effort: 'medium',
|
|
5
|
+
model_reasoning_effort: String(input.modelReasoningEffort || input.reasoningEffort || process.env.SKS_CODEX_REASONING || process.env.CODEX_MODEL_REASONING_EFFORT || 'medium'),
|
|
6
6
|
mcp_servers: {},
|
|
7
7
|
sks: {
|
|
8
8
|
route: input.route,
|
|
@@ -445,7 +445,7 @@ async function ensurePythonCodexLbConfig(env, config) {
|
|
|
445
445
|
`model = ${tomlQuote(model)}`,
|
|
446
446
|
'model_provider = "codex-lb"',
|
|
447
447
|
'service_tier = "fast"',
|
|
448
|
-
|
|
448
|
+
`model_reasoning_effort = ${tomlQuote(String(config.model_reasoning_effort || env.SKS_CODEX_REASONING || env.CODEX_MODEL_REASONING_EFFORT || 'minimal'))}`,
|
|
449
449
|
'approval_policy = "never"',
|
|
450
450
|
'',
|
|
451
451
|
'[model_providers.codex-lb]',
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { findCodexBinary } from '../codex-adapter.js';
|
|
3
|
+
import { detectCodex0138Capability } from '../codex-control/codex-0138-capability.js';
|
|
4
|
+
import { nowIso, runProcess, writeJsonAtomic } from '../fsx.js';
|
|
5
|
+
export async function runCodexPluginListJson() {
|
|
6
|
+
if (process.env.SKS_CODEX_PLUGIN_JSON_FAKE === '1')
|
|
7
|
+
return fakePluginList();
|
|
8
|
+
const bin = await findCodexBinary();
|
|
9
|
+
if (!bin)
|
|
10
|
+
return { plugins: [], blockers: ['codex_cli_missing'] };
|
|
11
|
+
return runCodexJson(bin, ['plugin', 'list', '--json']);
|
|
12
|
+
}
|
|
13
|
+
export async function runCodexPluginDetailJson(pluginId) {
|
|
14
|
+
if (process.env.SKS_CODEX_PLUGIN_JSON_FAKE === '1')
|
|
15
|
+
return fakePluginDetail(pluginId);
|
|
16
|
+
const bin = await findCodexBinary();
|
|
17
|
+
if (!bin)
|
|
18
|
+
return { blockers: ['codex_cli_missing'] };
|
|
19
|
+
return runCodexJson(bin, ['plugin', 'detail', pluginId, '--json']);
|
|
20
|
+
}
|
|
21
|
+
export async function buildCodexPluginInventory() {
|
|
22
|
+
const capability = await detectCodex0138Capability();
|
|
23
|
+
const listJson = await runCodexPluginListJson();
|
|
24
|
+
const summaries = normalizePluginList(listJson);
|
|
25
|
+
const plugins = [];
|
|
26
|
+
for (const summary of summaries) {
|
|
27
|
+
const detail = await runCodexPluginDetailJson(summary.id || summary.name).catch((err) => ({ error: err?.message || String(err) }));
|
|
28
|
+
plugins.push(normalizePlugin(summary, detail));
|
|
29
|
+
}
|
|
30
|
+
const blockers = [
|
|
31
|
+
...(capability.supports_plugin_json ? [] : ['codex_0_138_plugin_json_unavailable']),
|
|
32
|
+
...normalizeList(listJson?.blockers)
|
|
33
|
+
];
|
|
34
|
+
return {
|
|
35
|
+
schema: 'sks.codex-plugin-inventory.v1',
|
|
36
|
+
generated_at: nowIso(),
|
|
37
|
+
codex_0138_capability: capability,
|
|
38
|
+
plugins,
|
|
39
|
+
marketplace_available: plugins.some((plugin) => plugin.source === 'marketplace' || plugin.source === 'remote') || Boolean(listJson?.marketplace_available || listJson?.marketplaceAvailable),
|
|
40
|
+
blockers
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export async function writeCodexPluginInventoryArtifacts(root, inventory = null) {
|
|
44
|
+
const report = inventory || await buildCodexPluginInventory();
|
|
45
|
+
const artifact = path.join(root, '.sneakoscope', 'codex-plugin-inventory.json');
|
|
46
|
+
await writeJsonAtomic(artifact, report);
|
|
47
|
+
return { report, artifact };
|
|
48
|
+
}
|
|
49
|
+
export function pluginAppTemplatePolicy(inventory) {
|
|
50
|
+
const unavailable = inventory.plugins.flatMap((plugin) => plugin.unavailable_app_templates.map((template) => ({
|
|
51
|
+
plugin: plugin.id,
|
|
52
|
+
template
|
|
53
|
+
})));
|
|
54
|
+
return {
|
|
55
|
+
schema: 'sks.codex-plugin-app-template-policy.v1',
|
|
56
|
+
ok: true,
|
|
57
|
+
unavailable_app_templates: unavailable,
|
|
58
|
+
qa_loop_app_handoff_recommended: unavailable.length > 0,
|
|
59
|
+
doctor_warnings: unavailable.map((row) => `plugin_app_template_unavailable:${row.plugin}`)
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
async function runCodexJson(bin, args) {
|
|
63
|
+
const result = await runProcess(bin, args, { timeoutMs: 20_000, maxOutputBytes: 256 * 1024 }).catch((err) => ({
|
|
64
|
+
code: 1,
|
|
65
|
+
stdout: '',
|
|
66
|
+
stderr: err?.message || String(err)
|
|
67
|
+
}));
|
|
68
|
+
const text = `${result.stdout || ''}${result.stderr || ''}`.trim();
|
|
69
|
+
try {
|
|
70
|
+
return text ? JSON.parse(text) : {};
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return { raw_text: text, blockers: [`codex_plugin_json_parse_failed:${args.join(' ')}`] };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function normalizePluginList(value) {
|
|
77
|
+
if (Array.isArray(value))
|
|
78
|
+
return value;
|
|
79
|
+
for (const key of ['plugins', 'installed_plugins', 'installedPlugins', 'items']) {
|
|
80
|
+
if (Array.isArray(value?.[key]))
|
|
81
|
+
return value[key];
|
|
82
|
+
}
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
function normalizePlugin(summary, detail) {
|
|
86
|
+
const raw = { summary, detail };
|
|
87
|
+
const id = String(detail?.id || summary?.id || summary?.plugin_id || summary?.name || 'unknown');
|
|
88
|
+
const name = String(detail?.name || summary?.name || id);
|
|
89
|
+
const sourceText = String(detail?.source || detail?.marketplaceSource || summary?.source || summary?.marketplaceSource || '').toLowerCase();
|
|
90
|
+
const source = sourceText.includes('marketplace') ? 'marketplace'
|
|
91
|
+
: sourceText.includes('remote') ? 'remote'
|
|
92
|
+
: sourceText.includes('local') ? 'local'
|
|
93
|
+
: 'unknown';
|
|
94
|
+
return {
|
|
95
|
+
id,
|
|
96
|
+
name,
|
|
97
|
+
source,
|
|
98
|
+
installed: boolish(detail?.installed ?? summary?.installed, true),
|
|
99
|
+
enabled: boolish(detail?.enabled ?? summary?.enabled, true),
|
|
100
|
+
default_prompts: normalizeList(detail?.default_prompts || detail?.defaultPrompts || detail?.prompts),
|
|
101
|
+
remote_mcp_servers: normalizeMcpServers(detail?.remote_mcp_servers || detail?.remoteMcpServers || detail?.mcp_servers || detail?.mcpServers),
|
|
102
|
+
unavailable_app_templates: normalizeList(detail?.unavailable_app_templates || detail?.unavailableAppTemplates || detail?.app_templates_unavailable),
|
|
103
|
+
raw
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function normalizeMcpServers(value) {
|
|
107
|
+
const rows = Array.isArray(value) ? value : value && typeof value === 'object' ? Object.entries(value).map(([name, row]) => ({ name, ...(row || {}) })) : [];
|
|
108
|
+
return rows.map((row, index) => ({
|
|
109
|
+
name: String(row?.name || row?.id || `remote-mcp-${index + 1}`),
|
|
110
|
+
url: stringOrNull(row?.url || row?.endpoint),
|
|
111
|
+
auth_type: stringOrNull(row?.auth_type || row?.authType || row?.auth)
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
114
|
+
function normalizeList(value) {
|
|
115
|
+
return Array.isArray(value) ? value.filter(Boolean).map(String) : value ? [String(value)] : [];
|
|
116
|
+
}
|
|
117
|
+
function stringOrNull(value) {
|
|
118
|
+
const text = String(value || '').trim();
|
|
119
|
+
return text ? text : null;
|
|
120
|
+
}
|
|
121
|
+
function boolish(value, fallback = false) {
|
|
122
|
+
if (value === true || value === 'true')
|
|
123
|
+
return true;
|
|
124
|
+
if (value === false || value === 'false')
|
|
125
|
+
return false;
|
|
126
|
+
return fallback;
|
|
127
|
+
}
|
|
128
|
+
function fakePluginList() {
|
|
129
|
+
return {
|
|
130
|
+
marketplace_available: true,
|
|
131
|
+
plugins: [{
|
|
132
|
+
id: 'fixture-plugin',
|
|
133
|
+
name: 'Fixture Plugin',
|
|
134
|
+
source: 'marketplace',
|
|
135
|
+
installed: true,
|
|
136
|
+
enabled: true
|
|
137
|
+
}]
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function fakePluginDetail(pluginId) {
|
|
141
|
+
return {
|
|
142
|
+
id: pluginId,
|
|
143
|
+
name: pluginId,
|
|
144
|
+
source: 'marketplace',
|
|
145
|
+
installed: true,
|
|
146
|
+
enabled: true,
|
|
147
|
+
default_prompts: ['Use the fixture plugin safely.'],
|
|
148
|
+
remote_mcp_servers: [{ name: 'fixture-db-docs', url: 'https://mcp.example.test', auth_type: 'oauth' }],
|
|
149
|
+
unavailable_app_templates: ['fixture-desktop-template']
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=codex-plugin-json.js.map
|
|
@@ -20,6 +20,7 @@ import { runCodexLaunchPreflight } from '../preflight/parallel-preflight-engine.
|
|
|
20
20
|
import { diffCodexAppUiSnapshots, writeCodexAppUiSnapshot } from '../codex-app/codex-app-ui-state-snapshot.js';
|
|
21
21
|
import { checkSksUpdateNotice } from '../update/update-notice.js';
|
|
22
22
|
import { createMadDbCapability, MAD_DB_ACK } from '../mad-db/mad-db-capability.js';
|
|
23
|
+
import { writeCodex0138CapabilityArtifacts } from '../codex-control/codex-0138-capability.js';
|
|
23
24
|
export async function madHighCommand(args = [], deps = {}) {
|
|
24
25
|
const subcommand = firstSubcommand(args);
|
|
25
26
|
if (subcommand)
|
|
@@ -379,6 +380,7 @@ async function activateMadZellijPermissionState(cwd = process.cwd(), args = [])
|
|
|
379
380
|
const has = (scope) => allowedScopes.has(scope);
|
|
380
381
|
const dbWriteAllowed = has('db_write');
|
|
381
382
|
const { id, dir } = await createMission(root, { mode: 'mad-sks', prompt: 'sks --mad Zellij scoped high-power maintenance session' });
|
|
383
|
+
await writeCodex0138CapabilityArtifacts(root, { missionId: id }).catch(() => null);
|
|
382
384
|
const protectedCore = resolveProtectedCore({ packageRoot: packageRoot(), targetRoot: cwd });
|
|
383
385
|
// The interactive launch 'before' snapshot is only persisted (env + policy json)
|
|
384
386
|
// and is never compared against an 'after' snapshot during the session, so the
|
|
@@ -564,6 +566,7 @@ function codexLbImmediateLaunchOpts(args = [], lb = {}, opts = {}) {
|
|
|
564
566
|
}
|
|
565
567
|
export async function madSksFixture(root) {
|
|
566
568
|
const { id, dir } = await createMission(root, { mode: 'mad-sks', prompt: '$MAD-SKS fixture permission gate' });
|
|
569
|
+
await writeCodex0138CapabilityArtifacts(root, { missionId: id }).catch(() => null);
|
|
567
570
|
const gate = { schema_version: 1, passed: true, mad_sks_permission_active: true, permissions_deactivated: true, catastrophic_safety_guard_active: true, permission_profile: permissionGateSummary(), fixture: true };
|
|
568
571
|
await writeJsonAtomic(path.join(dir, 'mad-sks-gate.json'), gate);
|
|
569
572
|
return { mission_id: id, dir, gate };
|
|
@@ -738,6 +741,7 @@ async function materializeMadSksRun(root, targetRoot, permission, userIntent, js
|
|
|
738
741
|
if (!(await exists(path.join(root, '.sneakoscope'))))
|
|
739
742
|
await initProject(root, {});
|
|
740
743
|
const { id, dir } = await createMission(root, { mode: 'mad-sks', prompt: userIntent });
|
|
744
|
+
await writeCodex0138CapabilityArtifacts(root, { missionId: id }).catch(() => null);
|
|
741
745
|
const before = await snapshotProtectedCore(packageRoot(), 'before');
|
|
742
746
|
const authorization = opts.authorizationManifest || createMadSksAuthorizationManifest({ permission, userIntent });
|
|
743
747
|
const authorizationPath = opts.authorizationManifestPath || path.join(dir, 'mad-sks-authorization.json');
|
|
@@ -19,6 +19,8 @@ import { buildNarutoGptFinalPack } from '../naruto/naruto-gpt-final-pack.js';
|
|
|
19
19
|
import { planNarutoZellijDashboard } from '../zellij/zellij-naruto-dashboard.js';
|
|
20
20
|
import { checkPromptPlaceholders } from '../prompt/prompt-placeholder-guard.js';
|
|
21
21
|
import { evaluateGitWorktreeCapability } from '../git/git-worktree-capability.js';
|
|
22
|
+
import { buildRuntimeProofSummary, renderRuntimeProofSummary } from '../agents/runtime-proof-summary.js';
|
|
23
|
+
import { writeCodex0138CapabilityArtifacts } from '../codex-control/codex-0138-capability.js';
|
|
22
24
|
const NARUTO_RESULT_SCHEMA = 'sks.naruto-command-result.v1';
|
|
23
25
|
const NARUTO_ROUTE = '$Naruto';
|
|
24
26
|
// $Naruto — Shadow Clone Swarm (影分身 / Kage Bunshin no Jutsu).
|
|
@@ -37,6 +39,8 @@ export async function narutoCommand(commandOrArgs = 'naruto', maybeArgs = []) {
|
|
|
37
39
|
return narutoDashboard(parsed);
|
|
38
40
|
if (parsed.action === 'workers')
|
|
39
41
|
return narutoWorkers(parsed);
|
|
42
|
+
if (parsed.action === 'proof')
|
|
43
|
+
return narutoProof(parsed);
|
|
40
44
|
return narutoRun(parsed);
|
|
41
45
|
}
|
|
42
46
|
async function narutoRun(parsed) {
|
|
@@ -70,6 +74,7 @@ async function narutoRun(parsed) {
|
|
|
70
74
|
maxAgentCount: MAX_NARUTO_AGENT_COUNT
|
|
71
75
|
});
|
|
72
76
|
const mission = await createMission(root, { mode: 'naruto', prompt: parsed.prompt });
|
|
77
|
+
await writeCodex0138CapabilityArtifacts(root, { missionId: mission.id }).catch(() => null);
|
|
73
78
|
const gitWorktreeCapability = writeCapable
|
|
74
79
|
? await evaluateGitWorktreeCapability({ root, missionId: mission.id })
|
|
75
80
|
: null;
|
|
@@ -379,7 +384,7 @@ async function narutoRun(parsed) {
|
|
|
379
384
|
&& Number(parallelRuntime.max_observed_active_workers || 0) >= Math.min(16, activeSlots));
|
|
380
385
|
await writeJsonAtomic(path.join(mission.dir, 'naruto-gate.json'), {
|
|
381
386
|
schema: 'sks.naruto-gate.v1',
|
|
382
|
-
passed: result.ok === true && nativeProofOk && finalAccepted,
|
|
387
|
+
passed: result.ok === true && nativeProofOk && finalAccepted && parallelRuntimeOk,
|
|
383
388
|
mission_id: mission.id,
|
|
384
389
|
clone_roster_built: true,
|
|
385
390
|
clone_count: roster.agent_count,
|
|
@@ -702,6 +707,16 @@ async function narutoWorkers(parsed) {
|
|
|
702
707
|
console.log(`Active ${summary.active} · completed ${summary.completed} · failed ${summary.failed} · visible ${summary.visible_worker_panes.length} · headless ${summary.headless_workers.length}`);
|
|
703
708
|
});
|
|
704
709
|
}
|
|
710
|
+
async function narutoProof(parsed) {
|
|
711
|
+
const root = await sksRoot();
|
|
712
|
+
const id = parsed.missionId && parsed.missionId !== 'latest' ? parsed.missionId : await findLatestMission(root);
|
|
713
|
+
if (!id)
|
|
714
|
+
return emit(parsed, { schema: NARUTO_RESULT_SCHEMA, ok: false, action: 'proof', status: 'missing_mission' }, () => console.log('No Naruto mission found.'));
|
|
715
|
+
const summary = await buildRuntimeProofSummary(root, id);
|
|
716
|
+
return emit(parsed, { ...summary, action: 'proof' }, () => {
|
|
717
|
+
console.log(renderRuntimeProofSummary(summary));
|
|
718
|
+
});
|
|
719
|
+
}
|
|
705
720
|
async function narutoHelp(parsed) {
|
|
706
721
|
const help = {
|
|
707
722
|
schema: NARUTO_RESULT_SCHEMA,
|
|
@@ -711,7 +726,8 @@ async function narutoHelp(parsed) {
|
|
|
711
726
|
description: 'Shadow Clone Swarm: fan out up to ' + MAX_NARUTO_AGENT_COUNT + ' parallel clone sessions.',
|
|
712
727
|
usage: [
|
|
713
728
|
'sks naruto run "<task>" [--clones N] [--backend codex-sdk|fake|ollama] [--local-model|--no-ollama] [--work-items N] [--real] [--readonly] [--json]',
|
|
714
|
-
'sks naruto status [--mission <id>] [--json]'
|
|
729
|
+
'sks naruto status [--mission <id>] [--json]',
|
|
730
|
+
'sks naruto proof latest [--json]'
|
|
715
731
|
],
|
|
716
732
|
defaults: { clones: DEFAULT_NARUTO_CLONES, max_clones: MAX_NARUTO_AGENT_COUNT, backend: 'codex-sdk' }
|
|
717
733
|
};
|
|
@@ -726,7 +742,7 @@ function parseNarutoArgs(args = []) {
|
|
|
726
742
|
if (hasFlag(args, '--help') || hasFlag(args, '-h'))
|
|
727
743
|
args = ['help', ...args.filter((arg) => arg !== '--help' && arg !== '-h')];
|
|
728
744
|
const first = args[0] && !String(args[0]).startsWith('--') ? String(args[0]) : '';
|
|
729
|
-
const actions = new Set(['run', 'status', 'help', 'dashboard', 'workers']);
|
|
745
|
+
const actions = new Set(['run', 'status', 'help', 'dashboard', 'workers', 'proof']);
|
|
730
746
|
const action = (actions.has(first) ? first : 'run');
|
|
731
747
|
const rest = action === first ? args.slice(1) : args;
|
|
732
748
|
const json = hasFlag(args, '--json');
|
|
@@ -743,7 +759,7 @@ function parseNarutoArgs(args = []) {
|
|
|
743
759
|
const readonly = hasFlag(args, '--readonly') || hasFlag(args, '--read-only');
|
|
744
760
|
const writeModeRaw = String(readOption(args, '--write-mode', hasFlag(args, '--parallel-write') ? 'parallel' : '') || '');
|
|
745
761
|
const writeMode = (['proof-safe', 'parallel', 'serial', 'off'].includes(writeModeRaw) ? writeModeRaw : null);
|
|
746
|
-
const positionalMission = action === 'dashboard' || action === 'workers' || action === 'status'
|
|
762
|
+
const positionalMission = action === 'dashboard' || action === 'workers' || action === 'status' || action === 'proof'
|
|
747
763
|
? positionalArgs(rest, new Set()).find((arg) => /^latest$|^M-/.test(arg))
|
|
748
764
|
: null;
|
|
749
765
|
const missionId = String(readOption(args, '--mission', readOption(args, '--mission-id', positionalMission || 'latest')));
|