sneakoscope 3.0.0 → 3.0.2
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 -2
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/crates/sks-core/src/main.rs +1 -1
- package/dist/.sks-build-stamp.json +4 -4
- package/dist/bin/sks.js +1 -1
- package/dist/core/agents/agent-message-bus.js +89 -2
- package/dist/core/agents/runtime-proof-summary.js +29 -1
- package/dist/core/codex-control/codex-0139-capability.js +102 -0
- package/dist/core/commands/mad-sks-command.js +4 -0
- package/dist/core/commands/naruto-command.js +11 -4
- package/dist/core/fsx.js +1 -1
- package/dist/core/pipeline-internals/runtime-core.js +4 -2
- package/dist/core/release/release-cache-key.js +128 -0
- package/dist/core/release/release-gate-cache-v2.js +6 -7
- package/dist/core/release/release-proof-truth.js +63 -0
- package/dist/core/safety/side-effect-runtime-report.js +19 -4
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/zellij-capability.js +41 -0
- package/dist/core/zellij/zellij-command.js +2 -1
- package/dist/core/zellij/zellij-update.js +28 -3
- package/dist/core/zellij/zellij-worker-pane-manager.js +116 -5
- package/dist/scripts/github-release-body-helper.js +43 -0
- package/dist/scripts/release-speed-summary.js +11 -0
- package/package.json +20 -2
package/README.md
CHANGED
|
@@ -35,9 +35,11 @@ Set up this agent project with Sneakoscope Codex. Use [[mandarange/Sneakoscope-C
|
|
|
35
35
|
|
|
36
36
|
## 🚀 Current Release
|
|
37
37
|
|
|
38
|
-
SKS **3.0.
|
|
38
|
+
SKS **3.0.2** tracks Codex CLI `rust-v0.139.0` and hardens the 3.0.0 swarm release: capability detection for code-mode web search, preserved `oneOf`/`allOf` tool schemas, plugin marketplace `source`/cached catalog, the `-P` sandbox profile alias, the multi-agent v2 `interrupt_agent` rename, Zellij stacked-pane version gates, pane-lock concurrency proof, release cache safety fixtures, agent message proof summaries, and release proof source-truth artifacts. See [docs/codex-0.139-compat.md](docs/codex-0.139-compat.md).
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
SKS 3.0.0 was the parallel-runtime stabilization release. The whole live-swarm experience — what you actually *see* while 5, 20, or 100 workers run — was rebuilt and proven end-to-end.
|
|
41
|
+
|
|
42
|
+
What changed in 3.0.0:
|
|
41
43
|
|
|
42
44
|
- **Slot panes are finally alive.** The watch renderer froze for entire missions because the telemetry snapshot cache never invalidated; snapshot reads are now mtime-aware, multi-process flushes merge instead of clobbering each other, and the disk `updated_at` stays authoritative for stale detection.
|
|
43
45
|
- **One SLOTS column, vertical stack.** Concurrent workers used to race anchor creation and split the screen into N side-by-side columns. Anchor + worker pane creation is serialized per session, and workers join a native Zellij stacked-pane group (`new-pane --stacked`, opt out with `SKS_ZELLIJ_WORKER_STACKED=0`).
|
|
@@ -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 3.0.
|
|
7
|
+
Some("--version") => println!("sks-rs 3.0.2"),
|
|
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": "3.0.
|
|
5
|
-
"source_digest": "
|
|
6
|
-
"source_file_count":
|
|
7
|
-
"built_at_source_time":
|
|
4
|
+
"package_version": "3.0.2",
|
|
5
|
+
"source_digest": "5d782324cdb5c0cb66ce53c5733ebdd754313abc22b579907de282270a0e6178",
|
|
6
|
+
"source_file_count": 2284,
|
|
7
|
+
"built_at_source_time": 1781100033829
|
|
8
8
|
}
|
package/dist/bin/sks.js
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import { appendJsonl, nowIso } from '../fsx.js';
|
|
3
4
|
import { appendAgentLedgerEvent } from './agent-central-ledger.js';
|
|
4
5
|
export async function appendAgentMessage(root, message) {
|
|
6
|
+
const eventType = normalizeAgentMessageEventType(message.event_type || message.type);
|
|
5
7
|
const entry = {
|
|
6
8
|
schema: 'sks.agent-message.v1',
|
|
9
|
+
ts: nowIso(),
|
|
10
|
+
mission_id: message.mission_id || inferMissionIdFromAgentRoot(root),
|
|
11
|
+
worker_id: message.worker_id || message.from,
|
|
12
|
+
slot_id: message.slot_id ?? message.from ?? null,
|
|
13
|
+
session_id: message.session_id || null,
|
|
14
|
+
level: message.level || (eventType === 'worker_failed' || eventType === 'blocker' ? 'error' : 'info'),
|
|
15
|
+
event_type: eventType,
|
|
16
|
+
message: message.body,
|
|
17
|
+
artifact_paths: message.artifact_paths || [],
|
|
7
18
|
from: message.from,
|
|
8
|
-
session_id: message.session_id,
|
|
9
19
|
to: message.to || 'orchestrator',
|
|
10
20
|
type: message.type || 'note',
|
|
11
21
|
body: message.body
|
|
@@ -14,4 +24,81 @@ export async function appendAgentMessage(root, message) {
|
|
|
14
24
|
await appendAgentLedgerEvent(root, { agent_id: message.from, session_id: message.session_id, event_type: 'message_appended', payload: { to: entry.to, type: entry.type } });
|
|
15
25
|
return entry;
|
|
16
26
|
}
|
|
27
|
+
export async function readAgentMessageBus(root, missionId, opts = {}) {
|
|
28
|
+
const file = agentMessageBusPath(root, missionId);
|
|
29
|
+
let text = '';
|
|
30
|
+
try {
|
|
31
|
+
text = await fs.readFile(file, 'utf8');
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
const levels = new Set((opts.levels || []).map((level) => String(level)));
|
|
37
|
+
const rows = text.split(/\n+/)
|
|
38
|
+
.map((line) => line.trim())
|
|
39
|
+
.filter(Boolean)
|
|
40
|
+
.map((line) => {
|
|
41
|
+
try {
|
|
42
|
+
return normalizeAgentMessageBusEntry(JSON.parse(line), missionId);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
.filter((row) => Boolean(row))
|
|
49
|
+
.filter((row) => !levels.size || levels.has(row.level));
|
|
50
|
+
const max = Math.max(0, Math.floor(Number(opts.max || rows.length)));
|
|
51
|
+
return max > 0 ? rows.slice(-max) : rows;
|
|
52
|
+
}
|
|
53
|
+
export function agentMessageBusPath(root, missionId) {
|
|
54
|
+
const resolved = path.resolve(root);
|
|
55
|
+
if (path.basename(resolved) === 'agents')
|
|
56
|
+
return path.join(resolved, 'agent-messages.jsonl');
|
|
57
|
+
if (path.basename(resolved) === missionId)
|
|
58
|
+
return path.join(resolved, 'agents', 'agent-messages.jsonl');
|
|
59
|
+
return path.join(resolved, '.sneakoscope', 'missions', missionId, 'agents', 'agent-messages.jsonl');
|
|
60
|
+
}
|
|
61
|
+
function normalizeAgentMessageBusEntry(value, missionId) {
|
|
62
|
+
const eventType = normalizeAgentMessageEventType(value.event_type || value.type);
|
|
63
|
+
return {
|
|
64
|
+
schema: 'sks.agent-message.v1',
|
|
65
|
+
ts: String(value.ts || value.generated_at || nowIso()),
|
|
66
|
+
mission_id: String(value.mission_id || missionId),
|
|
67
|
+
worker_id: String(value.worker_id || value.from || value.slot_id || 'worker'),
|
|
68
|
+
slot_id: value.slot_id == null ? value.from == null ? null : String(value.from) : String(value.slot_id),
|
|
69
|
+
session_id: value.session_id == null ? null : String(value.session_id),
|
|
70
|
+
level: normalizeAgentMessageLevel(value.level, eventType),
|
|
71
|
+
event_type: eventType,
|
|
72
|
+
message: String(value.message || value.body || ''),
|
|
73
|
+
artifact_paths: Array.isArray(value.artifact_paths) ? value.artifact_paths.map(String) : [],
|
|
74
|
+
from: value.from == null ? undefined : String(value.from),
|
|
75
|
+
to: value.to == null ? undefined : String(value.to),
|
|
76
|
+
type: value.type == null ? undefined : String(value.type),
|
|
77
|
+
body: value.body == null ? undefined : String(value.body)
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function normalizeAgentMessageEventType(value) {
|
|
81
|
+
const text = String(value || '').toLowerCase();
|
|
82
|
+
if (text === 'worker_completed' || text === 'completed' || text === 'done')
|
|
83
|
+
return 'worker_completed';
|
|
84
|
+
if (text === 'worker_failed' || text === 'failed' || text === 'error')
|
|
85
|
+
return 'worker_failed';
|
|
86
|
+
if (text === 'blocker')
|
|
87
|
+
return 'blocker';
|
|
88
|
+
if (text === 'handoff')
|
|
89
|
+
return 'handoff';
|
|
90
|
+
return 'status';
|
|
91
|
+
}
|
|
92
|
+
function normalizeAgentMessageLevel(value, eventType) {
|
|
93
|
+
const text = String(value || '').toLowerCase();
|
|
94
|
+
if (text === 'info' || text === 'warning' || text === 'error')
|
|
95
|
+
return text;
|
|
96
|
+
if (eventType === 'worker_failed' || eventType === 'blocker')
|
|
97
|
+
return 'error';
|
|
98
|
+
return 'info';
|
|
99
|
+
}
|
|
100
|
+
function inferMissionIdFromAgentRoot(root) {
|
|
101
|
+
const resolved = path.resolve(root);
|
|
102
|
+
return path.basename(resolved) === 'agents' ? path.basename(path.dirname(resolved)) : 'unknown';
|
|
103
|
+
}
|
|
17
104
|
//# sourceMappingURL=agent-message-bus.js.map
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { findLatestMission, missionDir } from '../mission.js';
|
|
3
3
|
import { readJson, writeJsonAtomic } from '../fsx.js';
|
|
4
|
+
import { readAgentMessageBus } from './agent-message-bus.js';
|
|
4
5
|
export const RUNTIME_PROOF_SUMMARY_SCHEMA = 'sks.runtime-proof-summary.v1';
|
|
5
|
-
export async function buildRuntimeProofSummary(root, missionIdInput = 'latest') {
|
|
6
|
+
export async function buildRuntimeProofSummary(root, missionIdInput = 'latest', opts = {}) {
|
|
6
7
|
const missionId = missionIdInput === 'latest' ? await findLatestMission(root) : missionIdInput;
|
|
7
8
|
if (!missionId)
|
|
8
9
|
throw new Error('runtime_proof_summary_mission_missing');
|
|
@@ -13,6 +14,10 @@ export async function buildRuntimeProofSummary(root, missionIdInput = 'latest')
|
|
|
13
14
|
const swarm = await readJson(path.join(agentsDir, 'agent-native-cli-session-swarm.json'), null);
|
|
14
15
|
const telemetry = await readJson(path.join(dir, 'zellij', 'slot-telemetry.snapshot.json'), null);
|
|
15
16
|
const governor = await readJson(path.join(agentsDir, 'naruto-concurrency-governor.json'), null);
|
|
17
|
+
const messagesAll = await readAgentMessageBus(root, missionId, { max: 500 });
|
|
18
|
+
const recentMessages = await readAgentMessageBus(root, missionId, { max: opts.maxMessages || 8 });
|
|
19
|
+
const failedMessages = messagesAll.filter((row) => row.event_type === 'worker_failed');
|
|
20
|
+
const errorMessages = messagesAll.filter((row) => row.level === 'error');
|
|
16
21
|
const telemetryAgeMs = telemetry?.updated_at ? Math.max(0, Date.now() - Date.parse(telemetry.updated_at)) : Number.MAX_SAFE_INTEGER;
|
|
17
22
|
const visiblePanes = Number(parallel?.visible_panes ?? swarm?.zellij_pane_worker_sessions ?? telemetryVisiblePaneCount(telemetry) ?? 0);
|
|
18
23
|
const targetActive = Number(scheduler?.target_active_slots ?? parallel?.target_active_slots ?? swarm?.target_active_slots ?? governor?.target_active_slots ?? 0);
|
|
@@ -21,6 +26,7 @@ export async function buildRuntimeProofSummary(root, missionIdInput = 'latest')
|
|
|
21
26
|
...(!parallel ? ['parallel_runtime_proof_missing'] : []),
|
|
22
27
|
...(!scheduler ? ['agent_scheduler_state_missing'] : []),
|
|
23
28
|
...(parallel?.passed === false ? parallel.blockers || ['parallel_runtime_proof_failed'] : []),
|
|
29
|
+
...(errorMessages.length ? ['agent_message_bus_error_blockers'] : []),
|
|
24
30
|
...(telemetryAgeMs > 3000 ? ['zellij_telemetry_stale'] : [])
|
|
25
31
|
].map(String);
|
|
26
32
|
const summary = {
|
|
@@ -48,6 +54,13 @@ export async function buildRuntimeProofSummary(root, missionIdInput = 'latest')
|
|
|
48
54
|
largest_batch_size: Number(scheduler?.largest_batch_size || 0),
|
|
49
55
|
utilization: Number(scheduler?.scheduler_utilization || 0)
|
|
50
56
|
},
|
|
57
|
+
messages: {
|
|
58
|
+
recent: recentMessages,
|
|
59
|
+
completed_count: messagesAll.filter((row) => row.event_type === 'worker_completed').length,
|
|
60
|
+
failed_count: failedMessages.length,
|
|
61
|
+
warning_count: messagesAll.filter((row) => row.level === 'warning').length,
|
|
62
|
+
error_count: errorMessages.length
|
|
63
|
+
},
|
|
51
64
|
blockers
|
|
52
65
|
};
|
|
53
66
|
await writeJsonAtomic(path.join(agentsDir, 'runtime-proof-summary.json'), summary);
|
|
@@ -62,9 +75,24 @@ export function renderRuntimeProofSummary(summary) {
|
|
|
62
75
|
`Visible/headless: ${summary.ui.visible_panes} / ${summary.ui.headless_workers}`,
|
|
63
76
|
`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
77
|
`Model calls max: ${summary.model_calls.max_observed}`,
|
|
78
|
+
...(summary.messages.recent.length ? [
|
|
79
|
+
'Recent worker messages:',
|
|
80
|
+
...summary.messages.recent.map((row) => ` ${messageStatusLabel(row)} ${row.slot_id || row.worker_id}: ${row.message}`)
|
|
81
|
+
] : []),
|
|
65
82
|
...(summary.blockers.length ? [`Blockers: ${summary.blockers.join(', ')}`] : [])
|
|
66
83
|
].join('\n');
|
|
67
84
|
}
|
|
85
|
+
function messageStatusLabel(row) {
|
|
86
|
+
if (row.event_type === 'worker_completed')
|
|
87
|
+
return '[done]';
|
|
88
|
+
if (row.event_type === 'worker_failed')
|
|
89
|
+
return '[fail]';
|
|
90
|
+
if (row.level === 'warning')
|
|
91
|
+
return '[warn]';
|
|
92
|
+
if (row.level === 'error')
|
|
93
|
+
return '[err]';
|
|
94
|
+
return '[info]';
|
|
95
|
+
}
|
|
68
96
|
function telemetryVisiblePaneCount(snapshot) {
|
|
69
97
|
const slots = snapshot?.slots && typeof snapshot.slots === 'object' ? Object.values(snapshot.slots) : [];
|
|
70
98
|
return slots.filter((row) => row?.status && row.status !== 'headless').length;
|
|
@@ -0,0 +1,102 @@
|
|
|
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 detectCodex0139Capability(input = {}) {
|
|
6
|
+
const fake = process.env.SKS_CODEX_0139_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.139.0')
|
|
12
|
+
: await readCodexVersionText(codexBin);
|
|
13
|
+
const parsed = parseCodexVersionText(versionText);
|
|
14
|
+
const atLeast139 = Boolean(parsed && compareSemverLike(parsed, '0.139.0') >= 0);
|
|
15
|
+
const probeMode = process.env.SKS_CODEX_0139_PROBE === '1' ? 'feature-probe' : 'version-only';
|
|
16
|
+
const featureProbeResults = probeMode === 'feature-probe'
|
|
17
|
+
? await probeCodex0139Features(codexBin, { fake })
|
|
18
|
+
: {
|
|
19
|
+
marketplace_list_json: 'skipped',
|
|
20
|
+
sandbox_profile_alias: 'skipped'
|
|
21
|
+
};
|
|
22
|
+
const marketplaceOk = atLeast139 && (probeMode === 'version-only' || featureProbeResults.marketplace_list_json !== 'failed');
|
|
23
|
+
const profileAliasOk = atLeast139 && (probeMode === 'version-only' || featureProbeResults.sandbox_profile_alias !== 'failed');
|
|
24
|
+
const blockers = [
|
|
25
|
+
...(!codexBin ? ['codex_cli_missing'] : []),
|
|
26
|
+
...(atLeast139 ? [] : ['codex_0_139_required_for_search_schema_marketplace_features']),
|
|
27
|
+
...(probeMode === 'feature-probe' && featureProbeResults.marketplace_list_json === 'failed' ? ['codex_marketplace_list_json_probe_failed'] : [])
|
|
28
|
+
];
|
|
29
|
+
return {
|
|
30
|
+
schema: 'sks.codex-0139-capability.v1',
|
|
31
|
+
ok: atLeast139 && blockers.length === 0,
|
|
32
|
+
probe_mode: probeMode,
|
|
33
|
+
codex_bin: codexBin || null,
|
|
34
|
+
version_text: versionText || null,
|
|
35
|
+
parsed_version: parsed,
|
|
36
|
+
supports_code_mode_web_search: atLeast139,
|
|
37
|
+
supports_rich_tool_schemas: atLeast139,
|
|
38
|
+
supports_doctor_env_details: atLeast139,
|
|
39
|
+
supports_marketplace_source_field: marketplaceOk,
|
|
40
|
+
supports_plugin_catalog_cache: atLeast139,
|
|
41
|
+
supports_sandbox_profile_alias: profileAliasOk,
|
|
42
|
+
supports_interrupt_agent_rename: atLeast139,
|
|
43
|
+
feature_probe_results: featureProbeResults,
|
|
44
|
+
blockers
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export async function writeCodex0139CapabilityArtifacts(root, input = {}) {
|
|
48
|
+
const capability = await detectCodex0139Capability({ codexBin: input.codexBin || null });
|
|
49
|
+
const report = { ...capability, generated_at: nowIso() };
|
|
50
|
+
const rootArtifact = path.join(root, '.sneakoscope', 'codex-0139-capability.json');
|
|
51
|
+
await writeJsonAtomic(rootArtifact, report);
|
|
52
|
+
let missionArtifact = null;
|
|
53
|
+
if (input.missionId) {
|
|
54
|
+
missionArtifact = path.join(root, '.sneakoscope', 'missions', input.missionId, 'codex-0139-capability.json');
|
|
55
|
+
await writeJsonAtomic(missionArtifact, report);
|
|
56
|
+
}
|
|
57
|
+
return { report, root_artifact: rootArtifact, mission_artifact: missionArtifact };
|
|
58
|
+
}
|
|
59
|
+
async function readCodexVersionText(codexBin) {
|
|
60
|
+
if (!codexBin)
|
|
61
|
+
return null;
|
|
62
|
+
const result = await runProcess(codexBin, ['--version'], { timeoutMs: 10_000, maxOutputBytes: 16 * 1024 }).catch((err) => ({
|
|
63
|
+
code: 1,
|
|
64
|
+
stdout: '',
|
|
65
|
+
stderr: err?.message || String(err)
|
|
66
|
+
}));
|
|
67
|
+
const text = `${result.stdout || ''}${result.stderr || ''}`.trim();
|
|
68
|
+
return result.code === 0 ? text : text || null;
|
|
69
|
+
}
|
|
70
|
+
async function probeCodex0139Features(codexBin, opts = {}) {
|
|
71
|
+
if (opts.fake) {
|
|
72
|
+
return {
|
|
73
|
+
marketplace_list_json: process.env.SKS_CODEX_0139_FAKE_MARKETPLACE_FAIL === '1' ? 'failed' : 'passed',
|
|
74
|
+
sandbox_profile_alias: 'passed'
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
const timeoutMs = Math.max(1, Number(process.env.SKS_CODEX_0139_PROBE_TIMEOUT_MS || 3000) || 3000);
|
|
78
|
+
if (!codexBin) {
|
|
79
|
+
return { marketplace_list_json: 'failed', sandbox_profile_alias: 'failed' };
|
|
80
|
+
}
|
|
81
|
+
const marketplace = await runProcess(codexBin, ['plugin', 'marketplace', 'list', '--json'], { timeoutMs, maxOutputBytes: 256 * 1024 }).catch(() => ({ code: 1, stdout: '' }));
|
|
82
|
+
const marketplaceListJson = marketplace.code === 0 && marketplaceSourcesPresent(marketplace.stdout) ? 'passed' : 'failed';
|
|
83
|
+
const help = await runProcess(codexBin, ['--help'], { timeoutMs, maxOutputBytes: 256 * 1024 }).catch(() => ({ code: 1, stdout: '' }));
|
|
84
|
+
const aliasOk = help.code === 0 && /(^|\s)-P[,\s]/m.test(String(help.stdout || ''));
|
|
85
|
+
return {
|
|
86
|
+
marketplace_list_json: marketplaceListJson,
|
|
87
|
+
sandbox_profile_alias: aliasOk ? 'passed' : 'failed'
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
export function marketplaceSourcesPresent(stdout) {
|
|
91
|
+
try {
|
|
92
|
+
const parsed = JSON.parse(String(stdout || ''));
|
|
93
|
+
const rows = Array.isArray(parsed) ? parsed : Array.isArray(parsed?.marketplaces) ? parsed.marketplaces : Array.isArray(parsed?.items) ? parsed.items : [];
|
|
94
|
+
if (!rows.length)
|
|
95
|
+
return true;
|
|
96
|
+
return rows.some((row) => typeof row?.source === 'string' && row.source.length > 0);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=codex-0139-capability.js.map
|
|
@@ -21,6 +21,7 @@ import { diffCodexAppUiSnapshots, writeCodexAppUiSnapshot } from '../codex-app/c
|
|
|
21
21
|
import { checkSksUpdateNotice } from '../update/update-notice.js';
|
|
22
22
|
import { createMadDbCapability, MAD_DB_ACK } from '../mad-db/mad-db-capability.js';
|
|
23
23
|
import { writeCodex0138CapabilityArtifacts } from '../codex-control/codex-0138-capability.js';
|
|
24
|
+
import { writeCodex0139CapabilityArtifacts } from '../codex-control/codex-0139-capability.js';
|
|
24
25
|
export async function madHighCommand(args = [], deps = {}) {
|
|
25
26
|
const subcommand = firstSubcommand(args);
|
|
26
27
|
if (subcommand)
|
|
@@ -385,6 +386,7 @@ async function activateMadZellijPermissionState(cwd = process.cwd(), args = [])
|
|
|
385
386
|
const dbWriteAllowed = has('db_write');
|
|
386
387
|
const { id, dir } = await createMission(root, { mode: 'mad-sks', prompt: 'sks --mad Zellij scoped high-power maintenance session' });
|
|
387
388
|
await writeCodex0138CapabilityArtifacts(root, { missionId: id }).catch(() => null);
|
|
389
|
+
await writeCodex0139CapabilityArtifacts(root, { missionId: id }).catch(() => null);
|
|
388
390
|
const protectedCore = resolveProtectedCore({ packageRoot: packageRoot(), targetRoot: cwd });
|
|
389
391
|
// The interactive launch 'before' snapshot is only persisted (env + policy json)
|
|
390
392
|
// and is never compared against an 'after' snapshot during the session, so the
|
|
@@ -571,6 +573,7 @@ function codexLbImmediateLaunchOpts(args = [], lb = {}, opts = {}) {
|
|
|
571
573
|
export async function madSksFixture(root) {
|
|
572
574
|
const { id, dir } = await createMission(root, { mode: 'mad-sks', prompt: '$MAD-SKS fixture permission gate' });
|
|
573
575
|
await writeCodex0138CapabilityArtifacts(root, { missionId: id }).catch(() => null);
|
|
576
|
+
await writeCodex0139CapabilityArtifacts(root, { missionId: id }).catch(() => null);
|
|
574
577
|
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 };
|
|
575
578
|
await writeJsonAtomic(path.join(dir, 'mad-sks-gate.json'), gate);
|
|
576
579
|
return { mission_id: id, dir, gate };
|
|
@@ -746,6 +749,7 @@ async function materializeMadSksRun(root, targetRoot, permission, userIntent, js
|
|
|
746
749
|
await initProject(root, {});
|
|
747
750
|
const { id, dir } = await createMission(root, { mode: 'mad-sks', prompt: userIntent });
|
|
748
751
|
await writeCodex0138CapabilityArtifacts(root, { missionId: id }).catch(() => null);
|
|
752
|
+
await writeCodex0139CapabilityArtifacts(root, { missionId: id }).catch(() => null);
|
|
749
753
|
const before = await snapshotProtectedCore(packageRoot(), 'before');
|
|
750
754
|
const authorization = opts.authorizationManifest || createMadSksAuthorizationManifest({ permission, userIntent });
|
|
751
755
|
const authorizationPath = opts.authorizationManifestPath || path.join(dir, 'mad-sks-authorization.json');
|
|
@@ -23,6 +23,7 @@ import { checkPromptPlaceholders } from '../prompt/prompt-placeholder-guard.js';
|
|
|
23
23
|
import { evaluateGitWorktreeCapability } from '../git/git-worktree-capability.js';
|
|
24
24
|
import { buildRuntimeProofSummary, renderRuntimeProofSummary } from '../agents/runtime-proof-summary.js';
|
|
25
25
|
import { writeCodex0138CapabilityArtifacts } from '../codex-control/codex-0138-capability.js';
|
|
26
|
+
import { writeCodex0139CapabilityArtifacts } from '../codex-control/codex-0139-capability.js';
|
|
26
27
|
const NARUTO_RESULT_SCHEMA = 'sks.naruto-command-result.v1';
|
|
27
28
|
const NARUTO_ROUTE = '$Naruto';
|
|
28
29
|
// $Naruto — Shadow Clone Swarm (影分身 / Kage Bunshin no Jutsu).
|
|
@@ -83,6 +84,7 @@ async function narutoRun(parsed) {
|
|
|
83
84
|
});
|
|
84
85
|
const mission = await createMission(root, { mode: 'naruto', prompt: parsed.prompt });
|
|
85
86
|
await writeCodex0138CapabilityArtifacts(root, { missionId: mission.id }).catch(() => null);
|
|
87
|
+
await writeCodex0139CapabilityArtifacts(root, { missionId: mission.id }).catch(() => null);
|
|
86
88
|
const gitWorktreeCapability = writeCapable
|
|
87
89
|
? await evaluateGitWorktreeCapability({ root, missionId: mission.id })
|
|
88
90
|
: null;
|
|
@@ -739,7 +741,7 @@ async function narutoProof(parsed) {
|
|
|
739
741
|
const id = parsed.missionId && parsed.missionId !== 'latest' ? parsed.missionId : await findLatestMission(root);
|
|
740
742
|
if (!id)
|
|
741
743
|
return emit(parsed, { schema: NARUTO_RESULT_SCHEMA, ok: false, action: 'proof', status: 'missing_mission' }, () => console.log('No Naruto mission found.'));
|
|
742
|
-
const summary = await buildRuntimeProofSummary(root, id);
|
|
744
|
+
const summary = await buildRuntimeProofSummary(root, id, { maxMessages: parsed.messages });
|
|
743
745
|
return emit(parsed, { ...summary, action: 'proof' }, () => {
|
|
744
746
|
console.log(renderRuntimeProofSummary(summary));
|
|
745
747
|
});
|
|
@@ -754,7 +756,7 @@ async function narutoHelp(parsed) {
|
|
|
754
756
|
usage: [
|
|
755
757
|
'sks naruto run "<task>" [--clones N] [--backend codex-sdk|fake|ollama] [--local-model|--no-ollama] [--work-items N] [--real] [--readonly] [--json]',
|
|
756
758
|
'sks naruto status [--mission <id>] [--json]',
|
|
757
|
-
'sks naruto proof latest [--json]'
|
|
759
|
+
'sks naruto proof latest [--messages 20] [--json]'
|
|
758
760
|
],
|
|
759
761
|
defaults: { clones: DEFAULT_NARUTO_CLONES, max_clones: MAX_NARUTO_AGENT_COUNT, backend: 'codex-sdk' }
|
|
760
762
|
};
|
|
@@ -796,9 +798,10 @@ function parseNarutoArgs(args = []) {
|
|
|
796
798
|
const attach = hasFlag(args, '--attach');
|
|
797
799
|
const smoke = hasFlag(args, '--smoke');
|
|
798
800
|
const parallelism = normalizeParallelism(readOption(args, '--parallelism', 'extreme'));
|
|
799
|
-
const
|
|
801
|
+
const messages = normalizeMessages(readOption(args, '--messages', '8'));
|
|
802
|
+
const valueFlags = new Set(['--clones', '--agents', '--work-items', '--concurrency', '--target-active-slots', '--backend', '--write-mode', '--mission', '--mission-id', '--ollama-model', '--local-model-model', '--ollama-base-url', '--local-model-base-url', '--parallelism', '--messages']);
|
|
800
803
|
const prompt = positionalArgs(rest, valueFlags).join(' ').trim() || 'Naruto shadow clone swarm run';
|
|
801
|
-
return { action, prompt, clones, workItems, concurrency, backend, backendExplicit, mock, real, readonly, ollamaEnabled: useOllama && !noOllama, noOllama, ollamaModel, ollamaBaseUrl, writeMode, json, missionId, noOpenZellij, attach, smoke, parallelism };
|
|
804
|
+
return { action, prompt, clones, workItems, concurrency, backend, backendExplicit, mock, real, readonly, ollamaEnabled: useOllama && !noOllama, noOllama, ollamaModel, ollamaBaseUrl, writeMode, json, missionId, noOpenZellij, attach, smoke, parallelism, messages };
|
|
802
805
|
}
|
|
803
806
|
function normalizeParallelism(value) {
|
|
804
807
|
const text = String(value || 'extreme').toLowerCase();
|
|
@@ -806,6 +809,10 @@ function normalizeParallelism(value) {
|
|
|
806
809
|
return text;
|
|
807
810
|
return 'extreme';
|
|
808
811
|
}
|
|
812
|
+
function normalizeMessages(value) {
|
|
813
|
+
const parsed = Number(value);
|
|
814
|
+
return Math.max(0, Math.min(100, Math.floor(Number.isFinite(parsed) ? parsed : 8)));
|
|
815
|
+
}
|
|
809
816
|
async function writeNarutoArtifacts(ledgerRoot, artifacts) {
|
|
810
817
|
await writeJsonAtomic(path.join(ledgerRoot, 'naruto-work-graph.json'), artifacts.workGraph);
|
|
811
818
|
await writeJsonAtomic(path.join(ledgerRoot, 'naruto-role-distribution.json'), artifacts.roleDistribution);
|
package/dist/core/fsx.js
CHANGED
|
@@ -5,7 +5,7 @@ import os from 'node:os';
|
|
|
5
5
|
import crypto from 'node:crypto';
|
|
6
6
|
import { spawn } from 'node:child_process';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
|
-
export const PACKAGE_VERSION = '3.0.
|
|
8
|
+
export const PACKAGE_VERSION = '3.0.2';
|
|
9
9
|
export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
|
|
10
10
|
export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
|
|
11
11
|
export function nowIso() {
|
|
@@ -1249,13 +1249,15 @@ function subagentToolName(payload) {
|
|
|
1249
1249
|
}
|
|
1250
1250
|
function subagentStage(payload) {
|
|
1251
1251
|
const hay = JSON.stringify(payload || {});
|
|
1252
|
-
|
|
1252
|
+
// Codex 0.139 renamed multi-agent v2 `close_agent` to `interrupt_agent`;
|
|
1253
|
+
// accept both so cockpit evidence keeps classifying on newer CLIs.
|
|
1254
|
+
if (!/(spawn_agent|send_input|wait_agent|close_agent|interrupt_agent|subagent|worker|explorer)/i.test(hay))
|
|
1253
1255
|
return null;
|
|
1254
1256
|
if (/subagent[_ -]?unavailable|subagents unavailable|unsafe to split|unsplittable|cannot safely split/i.test(hay))
|
|
1255
1257
|
return 'exception';
|
|
1256
1258
|
if (/spawn_agent/i.test(hay))
|
|
1257
1259
|
return 'spawn_agent';
|
|
1258
|
-
if (/wait_agent|close_agent|completed|final/i.test(hay))
|
|
1260
|
+
if (/wait_agent|close_agent|interrupt_agent|completed|final/i.test(hay))
|
|
1259
1261
|
return 'result';
|
|
1260
1262
|
return 'subagent';
|
|
1261
1263
|
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
export function classifyReleaseCacheInputChange(input) {
|
|
2
|
+
if (input.before === input.after) {
|
|
3
|
+
return { neutralizable: true, reason: 'unchanged', behavior_affecting: false };
|
|
4
|
+
}
|
|
5
|
+
const before = normalizeReleaseCacheInputForBehavior(input.file, input.before);
|
|
6
|
+
const after = normalizeReleaseCacheInputForBehavior(input.file, input.after);
|
|
7
|
+
if (before === after) {
|
|
8
|
+
return {
|
|
9
|
+
neutralizable: true,
|
|
10
|
+
reason: neutralReason(input.file),
|
|
11
|
+
behavior_affecting: false
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
return {
|
|
15
|
+
neutralizable: false,
|
|
16
|
+
reason: behaviorReason(input.file, input.before, input.after),
|
|
17
|
+
behavior_affecting: true
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export function normalizeReleaseCacheInputForBehavior(file, text) {
|
|
21
|
+
const rel = normalizeRel(file);
|
|
22
|
+
if (rel === 'package.json')
|
|
23
|
+
return normalizePackageJson(text);
|
|
24
|
+
if (rel === 'package-lock.json')
|
|
25
|
+
return normalizePackageLock(text);
|
|
26
|
+
if (rel === 'src/core/version.ts' || rel === 'src/core/fsx.ts') {
|
|
27
|
+
return text.replace(/(PACKAGE_VERSION\s*=\s*['"])([^'"]+)(['"])/, '$1__SKS_RELEASE_VERSION__$3');
|
|
28
|
+
}
|
|
29
|
+
if (rel === 'src/bin/sks.ts') {
|
|
30
|
+
return text.replace(/(FAST_PACKAGE_VERSION\s*=\s*['"])([^'"]+)(['"])/, '$1__SKS_RELEASE_VERSION__$3');
|
|
31
|
+
}
|
|
32
|
+
if (rel === 'dist/build-manifest.json')
|
|
33
|
+
return normalizeBuildManifest(text);
|
|
34
|
+
return text;
|
|
35
|
+
}
|
|
36
|
+
function normalizePackageJson(text) {
|
|
37
|
+
return normalizeJson(text, (value) => {
|
|
38
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
39
|
+
value.version = '__SKS_RELEASE_VERSION__';
|
|
40
|
+
}
|
|
41
|
+
return value;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function normalizePackageLock(text) {
|
|
45
|
+
return normalizeJson(text, (value) => {
|
|
46
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
47
|
+
value.version = '__SKS_RELEASE_VERSION__';
|
|
48
|
+
if (value.packages?.[''] && typeof value.packages[''] === 'object') {
|
|
49
|
+
value.packages[''].version = '__SKS_RELEASE_VERSION__';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return value;
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function normalizeBuildManifest(text) {
|
|
56
|
+
return normalizeJson(text, (value) => {
|
|
57
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
58
|
+
if ('version' in value)
|
|
59
|
+
value.version = '__SKS_RELEASE_VERSION__';
|
|
60
|
+
if ('package_version' in value)
|
|
61
|
+
value.package_version = '__SKS_RELEASE_VERSION__';
|
|
62
|
+
}
|
|
63
|
+
return value;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
function normalizeJson(text, mutate) {
|
|
67
|
+
try {
|
|
68
|
+
const parsed = JSON.parse(text);
|
|
69
|
+
return stableJson(mutate(parsed));
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return text;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function stableJson(value) {
|
|
76
|
+
if (Array.isArray(value))
|
|
77
|
+
return `[${value.map(stableJson).join(',')}]`;
|
|
78
|
+
if (value && typeof value === 'object') {
|
|
79
|
+
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableJson(value[key])}`).join(',')}}`;
|
|
80
|
+
}
|
|
81
|
+
return JSON.stringify(value);
|
|
82
|
+
}
|
|
83
|
+
function neutralReason(file) {
|
|
84
|
+
const rel = normalizeRel(file);
|
|
85
|
+
if (rel === 'package.json')
|
|
86
|
+
return 'package_json_version_only';
|
|
87
|
+
if (rel === 'package-lock.json')
|
|
88
|
+
return 'package_lock_root_version_only';
|
|
89
|
+
if (rel === 'src/bin/sks.ts')
|
|
90
|
+
return 'fast_package_version_only';
|
|
91
|
+
if (rel === 'src/core/version.ts' || rel === 'src/core/fsx.ts')
|
|
92
|
+
return 'package_version_constant_only';
|
|
93
|
+
if (rel === 'dist/build-manifest.json')
|
|
94
|
+
return 'build_manifest_version_only';
|
|
95
|
+
return 'version_surface_only';
|
|
96
|
+
}
|
|
97
|
+
function behaviorReason(file, before, after) {
|
|
98
|
+
const rel = normalizeRel(file);
|
|
99
|
+
if (rel === 'package.json') {
|
|
100
|
+
const changed = changedTopLevelJsonKeys(before, after);
|
|
101
|
+
const behaviorKeys = changed.filter((key) => ['scripts', 'dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies', 'files', 'engines', 'publishConfig'].includes(key));
|
|
102
|
+
return behaviorKeys.length ? `package_json_behavior_keys:${behaviorKeys.join(',')}` : `package_json_non_version_keys:${changed.join(',') || 'unknown'}`;
|
|
103
|
+
}
|
|
104
|
+
if (rel === 'package-lock.json')
|
|
105
|
+
return 'package_lock_dependency_graph_changed';
|
|
106
|
+
if (rel === 'dist/build-manifest.json')
|
|
107
|
+
return 'build_manifest_artifact_hash_or_behavior_changed';
|
|
108
|
+
if (rel.startsWith('src/'))
|
|
109
|
+
return 'source_behavior_changed';
|
|
110
|
+
if (rel.startsWith('schemas/'))
|
|
111
|
+
return 'schema_behavior_changed';
|
|
112
|
+
return 'release_cache_input_behavior_changed';
|
|
113
|
+
}
|
|
114
|
+
function changedTopLevelJsonKeys(before, after) {
|
|
115
|
+
try {
|
|
116
|
+
const left = JSON.parse(before);
|
|
117
|
+
const right = JSON.parse(after);
|
|
118
|
+
const keys = [...new Set([...Object.keys(left || {}), ...Object.keys(right || {})])];
|
|
119
|
+
return keys.filter((key) => stableJson(left?.[key]) !== stableJson(right?.[key])).sort();
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return [];
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function normalizeRel(file) {
|
|
126
|
+
return String(file || '').replace(/\\/g, '/').replace(/^\.?\//, '');
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=release-cache-key.js.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import crypto from 'node:crypto';
|
|
4
|
+
import { normalizeReleaseCacheInputForBehavior } from './release-cache-key.js';
|
|
4
5
|
export const RELEASE_GATE_CACHE_V2_SCHEMA = 'sks.release-gate-cache.v2';
|
|
5
6
|
export function releaseGateCacheFile(root) {
|
|
6
7
|
return path.join(root, '.sneakoscope', 'reports', 'release-gates', 'cache-v2.json');
|
|
@@ -21,7 +22,8 @@ const VERSION_NEUTRAL_CACHE_FILES = new Set([
|
|
|
21
22
|
'package-lock.json',
|
|
22
23
|
'src/core/version.ts',
|
|
23
24
|
'src/core/fsx.ts',
|
|
24
|
-
'src/bin/sks.ts'
|
|
25
|
+
'src/bin/sks.ts',
|
|
26
|
+
'dist/build-manifest.json'
|
|
25
27
|
]);
|
|
26
28
|
export function releaseGateCacheKey(root, gate) {
|
|
27
29
|
const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
|
|
@@ -54,14 +56,14 @@ export function releaseGateCacheKey(root, gate) {
|
|
|
54
56
|
const rel = path.relative(root, file);
|
|
55
57
|
hash.update(rel);
|
|
56
58
|
if (!versionSensitive && VERSION_NEUTRAL_CACHE_FILES.has(rel))
|
|
57
|
-
hashVersionNeutralFile(hash, file, releaseVersion);
|
|
59
|
+
hashVersionNeutralFile(hash, rel, file, releaseVersion);
|
|
58
60
|
else
|
|
59
61
|
hashFileIfPresent(hash, file);
|
|
60
62
|
}
|
|
61
63
|
}
|
|
62
64
|
return hash.digest('hex');
|
|
63
65
|
}
|
|
64
|
-
function hashVersionNeutralFile(hash, file, releaseVersion) {
|
|
66
|
+
function hashVersionNeutralFile(hash, rel, file, releaseVersion) {
|
|
65
67
|
if (!fs.existsSync(file) || !fs.statSync(file).isFile())
|
|
66
68
|
return;
|
|
67
69
|
const text = fs.readFileSync(file, 'utf8');
|
|
@@ -69,10 +71,7 @@ function hashVersionNeutralFile(hash, file, releaseVersion) {
|
|
|
69
71
|
hash.update(text);
|
|
70
72
|
return;
|
|
71
73
|
}
|
|
72
|
-
|
|
73
|
-
// version-only bump hashes identically. Any other content change in these
|
|
74
|
-
// files still alters the key.
|
|
75
|
-
hash.update(text.split(releaseVersion).join('__SKS_RELEASE_VERSION__'));
|
|
74
|
+
hash.update(normalizeReleaseCacheInputForBehavior(rel, text));
|
|
76
75
|
}
|
|
77
76
|
export function expandGlob(root, input) {
|
|
78
77
|
const absolute = path.join(root, input);
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { nowIso, readJson, runProcess, sha256, writeJsonAtomic } from '../fsx.js';
|
|
4
|
+
export async function buildReleaseProofTruth(root) {
|
|
5
|
+
const pkg = await readJson(path.join(root, 'package.json'));
|
|
6
|
+
const gitCommit = await gitOutput(root, ['rev-parse', 'HEAD']);
|
|
7
|
+
const gitBranch = await gitOutput(root, ['rev-parse', '--abbrev-ref', 'HEAD']);
|
|
8
|
+
const gitStatus = await gitOutput(root, ['status', '--porcelain']);
|
|
9
|
+
const packlist = await readNpmPacklist(root);
|
|
10
|
+
return {
|
|
11
|
+
schema: 'sks.release-proof-truth.v1',
|
|
12
|
+
generated_at: nowIso(),
|
|
13
|
+
package_version: String(pkg.version || ''),
|
|
14
|
+
git_commit_sha: gitCommit || null,
|
|
15
|
+
git_branch: gitBranch || null,
|
|
16
|
+
git_status_clean: gitStatus === '',
|
|
17
|
+
package_json_sha256: await shaFile(root, 'package.json'),
|
|
18
|
+
package_lock_sha256: await shaFile(root, 'package-lock.json'),
|
|
19
|
+
version_ts_sha256: await shaFile(root, 'src/core/version.ts'),
|
|
20
|
+
changelog_sha256: await shaFile(root, 'CHANGELOG.md'),
|
|
21
|
+
release_gates_sha256: await shaFile(root, 'release-gates.v2.json'),
|
|
22
|
+
...(packlist ? {
|
|
23
|
+
npm_packlist_count: packlist.count,
|
|
24
|
+
npm_packlist_bytes: packlist.bytes
|
|
25
|
+
} : {})
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export async function writeReleaseProofTruth(root) {
|
|
29
|
+
const truth = await buildReleaseProofTruth(root);
|
|
30
|
+
await writeJsonAtomic(path.join(root, '.sneakoscope', 'release-proof-truth.json'), truth);
|
|
31
|
+
await writeJsonAtomic(path.join(root, 'dist', 'release-proof-truth.json'), truth);
|
|
32
|
+
return truth;
|
|
33
|
+
}
|
|
34
|
+
async function shaFile(root, rel) {
|
|
35
|
+
return sha256(await fs.readFile(path.join(root, rel)));
|
|
36
|
+
}
|
|
37
|
+
async function gitOutput(root, args) {
|
|
38
|
+
const result = await runProcess('git', args, { cwd: root, timeoutMs: 10000, maxOutputBytes: 64 * 1024 }).catch(() => null);
|
|
39
|
+
if (!result || result.code !== 0)
|
|
40
|
+
return null;
|
|
41
|
+
return String(result.stdout || '').trim();
|
|
42
|
+
}
|
|
43
|
+
async function readNpmPacklist(root) {
|
|
44
|
+
const result = await runProcess('npm', ['pack', '--dry-run', '--json', '--ignore-scripts'], {
|
|
45
|
+
cwd: root,
|
|
46
|
+
timeoutMs: 60000,
|
|
47
|
+
maxOutputBytes: 1024 * 1024
|
|
48
|
+
}).catch(() => null);
|
|
49
|
+
if (!result || result.code !== 0)
|
|
50
|
+
return null;
|
|
51
|
+
try {
|
|
52
|
+
const parsed = JSON.parse(String(result.stdout || '[]'));
|
|
53
|
+
const files = Array.isArray(parsed?.[0]?.files) ? parsed[0].files : [];
|
|
54
|
+
return {
|
|
55
|
+
count: files.length,
|
|
56
|
+
bytes: files.reduce((sum, file) => sum + Number(file.size || 0), 0)
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=release-proof-truth.js.map
|
|
@@ -34,10 +34,18 @@ export async function buildSideEffectRuntimeReport(root) {
|
|
|
34
34
|
async function discoverLedgerPaths(root) {
|
|
35
35
|
const found = new Set();
|
|
36
36
|
await addIfExists(found, mutationLedgerPath(root));
|
|
37
|
-
await walkForLedgers(path.join(root, '.sneakoscope', 'missions'), found
|
|
37
|
+
await walkForLedgers(path.join(root, '.sneakoscope', 'missions'), found, {
|
|
38
|
+
depth: 0,
|
|
39
|
+
maxDepth: positiveInt(process.env.SKS_SIDE_EFFECT_LEDGER_SCAN_MAX_DEPTH, 6),
|
|
40
|
+
visitedDirs: 0,
|
|
41
|
+
maxDirs: positiveInt(process.env.SKS_SIDE_EFFECT_LEDGER_SCAN_MAX_DIRS, 20000)
|
|
42
|
+
});
|
|
38
43
|
return [...found].sort();
|
|
39
44
|
}
|
|
40
|
-
async function walkForLedgers(dir, found) {
|
|
45
|
+
async function walkForLedgers(dir, found, budget) {
|
|
46
|
+
if (budget.depth > budget.maxDepth || budget.visitedDirs >= budget.maxDirs)
|
|
47
|
+
return;
|
|
48
|
+
budget.visitedDirs += 1;
|
|
41
49
|
let entries;
|
|
42
50
|
try {
|
|
43
51
|
entries = await fsp.readdir(dir, { withFileTypes: true });
|
|
@@ -47,12 +55,19 @@ async function walkForLedgers(dir, found) {
|
|
|
47
55
|
}
|
|
48
56
|
for (const entry of entries) {
|
|
49
57
|
const file = path.join(dir, entry.name);
|
|
50
|
-
if (entry.isDirectory())
|
|
51
|
-
await walkForLedgers(file, found);
|
|
58
|
+
if (entry.isDirectory() && !shouldSkipLedgerScanDir(entry.name))
|
|
59
|
+
await walkForLedgers(file, found, { ...budget, depth: budget.depth + 1 });
|
|
52
60
|
else if (entry.isFile() && entry.name === 'mutation-ledger.jsonl')
|
|
53
61
|
found.add(file);
|
|
54
62
|
}
|
|
55
63
|
}
|
|
64
|
+
function shouldSkipLedgerScanDir(name) {
|
|
65
|
+
return new Set(['node_modules', '.git', 'dist', 'vendor', '.next', 'coverage']).has(name);
|
|
66
|
+
}
|
|
67
|
+
function positiveInt(value, fallback) {
|
|
68
|
+
const parsed = Number(value);
|
|
69
|
+
return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : fallback;
|
|
70
|
+
}
|
|
56
71
|
async function addIfExists(found, file) {
|
|
57
72
|
try {
|
|
58
73
|
await fsp.access(file);
|
package/dist/core/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const PACKAGE_VERSION = '3.0.
|
|
1
|
+
export const PACKAGE_VERSION = '3.0.2';
|
|
2
2
|
//# sourceMappingURL=version.js.map
|
|
@@ -3,6 +3,47 @@ import { nowIso, writeJsonAtomic } from '../fsx.js';
|
|
|
3
3
|
import { compareVersionLike, parseZellijVersionText, runZellij } from './zellij-command.js';
|
|
4
4
|
export const ZELLIJ_CAPABILITY_SCHEMA = 'sks.zellij-capability.v1';
|
|
5
5
|
export const ZELLIJ_MIN_VERSION = '0.41.0';
|
|
6
|
+
export const ZELLIJ_STACKED_PANE_CAPABILITY_SCHEMA = 'sks.zellij-stacked-pane-capability.v1';
|
|
7
|
+
export const ZELLIJ_STACKED_PANE_MIN_VERSION = '0.43.0';
|
|
8
|
+
export function zellijSupportsStackedPanes(version) {
|
|
9
|
+
const parsed = parseZellijVersionText(version);
|
|
10
|
+
return Boolean(parsed && compareVersionLike(parsed, ZELLIJ_STACKED_PANE_MIN_VERSION) >= 0);
|
|
11
|
+
}
|
|
12
|
+
export function resolveZellijStackedPaneCapability(input = {}) {
|
|
13
|
+
const versionText = input.versionText == null ? null : String(input.versionText);
|
|
14
|
+
const parsedVersion = parseZellijVersionText(versionText);
|
|
15
|
+
const supports = zellijSupportsStackedPanes(parsedVersion);
|
|
16
|
+
const blockers = [...(input.blockers || [])].map(String);
|
|
17
|
+
const zellijMissing = blockers.includes('zellij_missing') || blockers.includes('zellij_missing_required');
|
|
18
|
+
if (!parsedVersion && !zellijMissing && input.ok === false)
|
|
19
|
+
blockers.push('zellij_version_unparsed');
|
|
20
|
+
return {
|
|
21
|
+
schema: ZELLIJ_STACKED_PANE_CAPABILITY_SCHEMA,
|
|
22
|
+
ok: blockers.length === 0 && supports,
|
|
23
|
+
zellij_bin: input.zellijBin === undefined ? 'zellij' : input.zellijBin,
|
|
24
|
+
version_text: versionText,
|
|
25
|
+
parsed_version: parsedVersion,
|
|
26
|
+
supports_stacked_panes: supports,
|
|
27
|
+
requires_update: Boolean(parsedVersion && !supports),
|
|
28
|
+
fallback_mode: supports ? 'native-stacked' : zellijMissing ? 'headless-only' : 'down-split-stack-emulation',
|
|
29
|
+
blockers
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export async function checkZellijStackedPaneCapability(opts = {}) {
|
|
33
|
+
const versionRun = await runZellij(['--version'], { optional: true, timeoutMs: 5000 });
|
|
34
|
+
const versionText = `${versionRun.stdout_tail}\n${versionRun.stderr_tail}`.trim();
|
|
35
|
+
const report = resolveZellijStackedPaneCapability({
|
|
36
|
+
ok: versionRun.ok,
|
|
37
|
+
zellijBin: 'zellij',
|
|
38
|
+
versionText,
|
|
39
|
+
blockers: versionRun.ok ? [] : versionRun.blockers
|
|
40
|
+
});
|
|
41
|
+
if (opts.writeReport !== false) {
|
|
42
|
+
const root = opts.root || process.cwd();
|
|
43
|
+
await writeJsonAtomic(path.join(root, '.sneakoscope', 'reports', 'zellij-stacked-pane-capability.json'), report);
|
|
44
|
+
}
|
|
45
|
+
return report;
|
|
46
|
+
}
|
|
6
47
|
export async function checkZellijCapability(opts = {}) {
|
|
7
48
|
const requireZellij = opts.require === true || process.env.SKS_REQUIRE_ZELLIJ === '1';
|
|
8
49
|
const versionRun = await runZellij(['--version'], { optional: !requireZellij, timeoutMs: 5000 });
|
|
@@ -88,9 +88,10 @@ export function isZellijSocketPathTooLong(text) {
|
|
|
88
88
|
return /IPC socket path is too long|socket path is too long/i.test(String(text || ''));
|
|
89
89
|
}
|
|
90
90
|
export function parseZellijVersionText(text) {
|
|
91
|
-
const match = String(text || '').match(
|
|
91
|
+
const match = String(text || '').match(/(?:^|[^0-9A-Za-z])v?(\d+\.\d+\.\d+)(?:[-+][0-9A-Za-z.-]+)?(?:$|[^0-9A-Za-z])/i);
|
|
92
92
|
return match?.[1] ?? null;
|
|
93
93
|
}
|
|
94
|
+
export const parseZellijVersion = parseZellijVersionText;
|
|
94
95
|
export function compareVersionLike(a, b) {
|
|
95
96
|
const pa = versionParts(a);
|
|
96
97
|
const pb = versionParts(b);
|
|
@@ -8,6 +8,18 @@ import { createRequestedScopeContract } from '../safety/requested-scope-contract
|
|
|
8
8
|
import { checkZellijCapability } from './zellij-capability.js';
|
|
9
9
|
import { compareVersionLike, parseZellijVersionText } from './zellij-command.js';
|
|
10
10
|
export const ZELLIJ_UPDATE_NOTICE_SCHEMA = 'sks.zellij-update-notice.v1';
|
|
11
|
+
export function resolveZellijUpdatePromptMode(input) {
|
|
12
|
+
const env = input.env || process.env;
|
|
13
|
+
if (input.skipFlag === true || env.SKS_SKIP_ZELLIJ_UPDATE === '1')
|
|
14
|
+
return 'skip';
|
|
15
|
+
if (input.ci === true || env.CI === '1' || /^true$/i.test(String(env.CI || '')))
|
|
16
|
+
return 'nonblocking-notice';
|
|
17
|
+
if (input.noQuestion === true || env.SKS_NO_QUESTION === '1' || /^true$/i.test(String(env.SKS_NO_QUESTION || '')))
|
|
18
|
+
return 'nonblocking-notice';
|
|
19
|
+
if (input.headless === true)
|
|
20
|
+
return 'nonblocking-notice';
|
|
21
|
+
return 'interactive-prompt';
|
|
22
|
+
}
|
|
11
23
|
const ZELLIJ_RELEASES_API_PATH = '/repos/zellij-org/zellij/releases/latest';
|
|
12
24
|
export function zellijUpgradeCommandHint(missing = false) {
|
|
13
25
|
if (process.platform === 'darwin')
|
|
@@ -194,10 +206,19 @@ export async function upgradeZellijToLatest(input = {}) {
|
|
|
194
206
|
export async function maybePromptZellijUpdateForLaunch(args = [], opts = {}) {
|
|
195
207
|
const env = opts.env || process.env;
|
|
196
208
|
const list = (args || []).map((arg) => String(arg));
|
|
197
|
-
|
|
209
|
+
const mode = resolveZellijUpdatePromptMode({
|
|
210
|
+
env,
|
|
211
|
+
skipFlag: list.includes('--json') || list.includes('--skip-cli-tools') || list.includes('--skip-zellij-update'),
|
|
212
|
+
noQuestion: list.includes('--no-question') || list.includes('--no-questions'),
|
|
213
|
+
headless: !(process.stdin.isTTY && process.stdout.isTTY)
|
|
214
|
+
});
|
|
215
|
+
if (mode === 'skip') {
|
|
198
216
|
return { status: 'skipped', current: null, latest: null, command: null };
|
|
199
217
|
}
|
|
200
|
-
const
|
|
218
|
+
const noticeInput = { env };
|
|
219
|
+
if (opts.missionDir !== undefined)
|
|
220
|
+
noticeInput.missionDir = opts.missionDir;
|
|
221
|
+
const notice = await checkZellijUpdateNotice(noticeInput).catch(() => null);
|
|
201
222
|
if (!notice)
|
|
202
223
|
return { status: 'skipped', current: null, latest: null, command: null };
|
|
203
224
|
if (notice.zellij_missing) {
|
|
@@ -211,6 +232,10 @@ export async function maybePromptZellijUpdateForLaunch(args = [], opts = {}) {
|
|
|
211
232
|
}
|
|
212
233
|
const label = opts.label || 'Zellij launch';
|
|
213
234
|
const autoYes = list.includes('--yes') || list.includes('-y');
|
|
235
|
+
if (mode === 'nonblocking-notice') {
|
|
236
|
+
console.log(`Zellij update available: ${notice.current_version} -> ${notice.latest_version}. Run: ${notice.upgrade_command}`);
|
|
237
|
+
return { status: 'available', current: notice.current_version, latest: notice.latest_version, command: notice.upgrade_command };
|
|
238
|
+
}
|
|
214
239
|
if (!autoYes && !canAskYesNo(env)) {
|
|
215
240
|
console.log(`Zellij update available: ${notice.current_version} -> ${notice.latest_version}. Run: ${notice.upgrade_command}`);
|
|
216
241
|
return { status: 'available', current: notice.current_version, latest: notice.latest_version, command: notice.upgrade_command };
|
|
@@ -284,7 +309,7 @@ function githubLatestTag(timeoutMs) {
|
|
|
284
309
|
});
|
|
285
310
|
}
|
|
286
311
|
function canAskYesNo(env) {
|
|
287
|
-
return
|
|
312
|
+
return resolveZellijUpdatePromptMode({ env, headless: !(process.stdin.isTTY && process.stdout.isTTY) }) === 'interactive-prompt';
|
|
288
313
|
}
|
|
289
314
|
async function askYesNoDefaultYes(question) {
|
|
290
315
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { appendJsonl, ensureDir, nowIso, packageRoot, readJson, writeJsonAtomic } from '../fsx.js';
|
|
3
|
+
import { appendParallelRuntimeEvent } from '../agents/parallel-runtime-proof.js';
|
|
3
4
|
import { providerPaneLabel } from '../provider/provider-badge.js';
|
|
4
5
|
import { resolveProviderContext } from '../provider/provider-context.js';
|
|
6
|
+
import { checkZellijStackedPaneCapability } from './zellij-capability.js';
|
|
5
7
|
import { runZellij } from './zellij-command.js';
|
|
6
8
|
import { extractZellijPaneIdFromOutput } from './zellij-lane-runtime.js';
|
|
7
9
|
import { buildZellijSlotColumnAnchorCommand } from './zellij-slot-column-anchor.js';
|
|
8
10
|
import { closeWorkerInRightColumn, prepareWorkerInRightColumn, readRightColumnState, recordSlotColumnAnchorInRightColumn, recordWorkerPaneInRightColumn } from './zellij-right-column-manager.js';
|
|
9
11
|
export const ZELLIJ_WORKER_PANE_SCHEMA = 'sks.zellij-worker-pane.v1';
|
|
10
12
|
export const ZELLIJ_WORKER_PANE_EVENT_SCHEMA = 'sks.zellij-worker-pane-event.v1';
|
|
13
|
+
export const ZELLIJ_PANE_CREATION_LOCK_METRICS_SCHEMA = 'sks.zellij-pane-creation-lock-metrics.v1';
|
|
11
14
|
export function buildWorkerPaneName(slotId, generationIndex) {
|
|
12
15
|
return `${slotId}/gen-${Math.max(1, Math.floor(Number(generationIndex) || 1))}`;
|
|
13
16
|
}
|
|
@@ -77,12 +80,15 @@ export function buildWorkerPaneArtifact(input) {
|
|
|
77
80
|
worker_direction_applied: input.workerDirectionApplied || (directionApplied === 'down' ? 'down' : directionApplied === 'unknown' ? 'unknown' : 'not_applied'),
|
|
78
81
|
worker_stacked_requested: input.stackedRequested === true,
|
|
79
82
|
worker_stacked_applied: input.stackedApplied === true,
|
|
83
|
+
worker_stacked_fallback_mode: input.stackedFallbackMode || null,
|
|
84
|
+
worker_stacked_capability: input.stackedCapability || null,
|
|
80
85
|
slot_column_anchor_pane_id: input.slotColumnAnchorPaneId || null,
|
|
81
86
|
right_column: input.rightColumn || null,
|
|
82
87
|
sdk_thread_id: input.sdkThreadId || null,
|
|
83
88
|
sdk_run_id: input.sdkRunId || null,
|
|
84
89
|
stream_event_count: Number(input.streamEventCount || 0),
|
|
85
90
|
structured_output_valid: input.structuredOutputValid === true,
|
|
91
|
+
warnings: input.warnings || [],
|
|
86
92
|
blockers
|
|
87
93
|
};
|
|
88
94
|
}
|
|
@@ -163,7 +169,7 @@ export async function openWorkerPane(input) {
|
|
|
163
169
|
// --direction right — splitting the screen into N side-by-side columns
|
|
164
170
|
// (observed in agent-zellij-pane-launch-ledger: terminal_1..terminal_5 all
|
|
165
171
|
// recorded as separate slot_column_anchor_pane_id values in one mission).
|
|
166
|
-
return withZellijPaneCreationLock(input
|
|
172
|
+
return withZellijPaneCreationLock(input, async () => {
|
|
167
173
|
// Re-read the right-column state now that we hold the lock: a previously
|
|
168
174
|
// serialized worker may have created the anchor and/or its own pane.
|
|
169
175
|
const freshState = rightColumn
|
|
@@ -221,9 +227,23 @@ export async function openWorkerPane(input) {
|
|
|
221
227
|
// worker (`new-pane --stacked`, zellij >= 0.43) so the right column stays a
|
|
222
228
|
// clean vertical stack instead of fragmenting the screen. Opt out with
|
|
223
229
|
// SKS_ZELLIJ_WORKER_STACKED=0.
|
|
224
|
-
const
|
|
230
|
+
const stackIntent = process.env.SKS_ZELLIJ_WORKER_STACKED !== '0'
|
|
225
231
|
&& Boolean(lastVisibleWorkerPaneId)
|
|
226
232
|
&& focus?.ok === true;
|
|
233
|
+
const stackedCapability = stackIntent
|
|
234
|
+
? await checkZellijStackedPaneCapability({ writeReport: false }).catch((err) => ({
|
|
235
|
+
schema: 'sks.zellij-stacked-pane-capability.v1',
|
|
236
|
+
ok: false,
|
|
237
|
+
zellij_bin: 'zellij',
|
|
238
|
+
version_text: null,
|
|
239
|
+
parsed_version: null,
|
|
240
|
+
supports_stacked_panes: false,
|
|
241
|
+
requires_update: false,
|
|
242
|
+
fallback_mode: 'headless-only',
|
|
243
|
+
blockers: [`zellij_stacked_capability_check_failed:${err?.code || err?.message || String(err)}`]
|
|
244
|
+
}))
|
|
245
|
+
: null;
|
|
246
|
+
const stackRequested = stackIntent && stackedCapability?.supports_stacked_panes === true;
|
|
227
247
|
const newPaneArgs = stackRequested
|
|
228
248
|
? ['--session', input.sessionName, 'action', 'new-pane', '--stacked', '--name', paneName, '--', 'sh', '-lc', input.workerCommand]
|
|
229
249
|
: ['--session', input.sessionName, 'action', 'new-pane', '--direction', directionRequested, '--near-current-pane', '--name', paneName, '--', 'sh', '-lc', input.workerCommand];
|
|
@@ -236,6 +256,7 @@ export async function openWorkerPane(input) {
|
|
|
236
256
|
: null;
|
|
237
257
|
let stackApplied = Boolean(stackRequested && launch?.ok);
|
|
238
258
|
let directionApplied = launch?.ok ? directionRequested : 'not_applied';
|
|
259
|
+
let stackedRejectedFallback = false;
|
|
239
260
|
if (createSession.ok && launch && !launch.ok) {
|
|
240
261
|
const fallbackArgs = ['--session', input.sessionName, 'action', 'new-pane', '--direction', directionRequested, '--name', paneName, '--', 'sh', '-lc', input.workerCommand];
|
|
241
262
|
const fallback = await runZellij(fallbackArgs, {
|
|
@@ -246,6 +267,7 @@ export async function openWorkerPane(input) {
|
|
|
246
267
|
if (fallback.ok) {
|
|
247
268
|
launch = fallback;
|
|
248
269
|
stackApplied = false;
|
|
270
|
+
stackedRejectedFallback = stackRequested;
|
|
249
271
|
directionApplied = rightColumn ? 'down' : 'unknown';
|
|
250
272
|
}
|
|
251
273
|
}
|
|
@@ -268,6 +290,10 @@ export async function openWorkerPane(input) {
|
|
|
268
290
|
...(launch && !launch.ok ? launch.blockers.map((blocker) => `zellij_worker_pane_${blocker}`) : []),
|
|
269
291
|
...(launch?.ok && !isRealZellijWorkerPaneIdSource(paneIdSource) ? ['zellij_worker_pane_id_real_source_missing'] : [])
|
|
270
292
|
];
|
|
293
|
+
const warnings = [
|
|
294
|
+
...(stackIntent && stackedCapability && !stackedCapability.supports_stacked_panes ? [`zellij_stacked_pane_fallback:${stackedCapability.fallback_mode}`] : []),
|
|
295
|
+
...(stackedRejectedFallback ? ['zellij_stacked_pane_rejected_fallback_down'] : [])
|
|
296
|
+
];
|
|
271
297
|
const record = buildWorkerPaneArtifact({
|
|
272
298
|
...input,
|
|
273
299
|
paneId,
|
|
@@ -288,13 +314,16 @@ export async function openWorkerPane(input) {
|
|
|
288
314
|
columnCreationDirectionApplied,
|
|
289
315
|
workerDirectionRequested: 'down',
|
|
290
316
|
workerDirectionApplied: directionApplied === 'down' ? 'down' : directionApplied === 'unknown' ? 'unknown' : 'not_applied',
|
|
291
|
-
stackedRequested:
|
|
317
|
+
stackedRequested: stackIntent,
|
|
292
318
|
stackedApplied: stackApplied,
|
|
319
|
+
stackedFallbackMode: stackIntent ? (stackApplied ? 'native-stacked' : stackedCapability?.fallback_mode || 'down-split-stack-emulation') : null,
|
|
320
|
+
stackedCapability,
|
|
293
321
|
slotColumnAnchorPaneId,
|
|
294
322
|
rightColumn: rightColumn ? { mode: 'spawn-on-first-worker', focus_pane_id: focusPaneId, y_order: rightColumn.yOrder, slot_column_anchor_pane_id: slotColumnAnchorPaneId } : null,
|
|
295
323
|
status: blockers.length ? 'failed' : 'running',
|
|
296
324
|
providerContext,
|
|
297
325
|
serviceTier: input.serviceTier || providerContext.service_tier,
|
|
326
|
+
warnings,
|
|
298
327
|
blockers
|
|
299
328
|
});
|
|
300
329
|
await writeWorkerPaneArtifact(root, record);
|
|
@@ -311,8 +340,27 @@ export async function openWorkerPane(input) {
|
|
|
311
340
|
ok: record.ok,
|
|
312
341
|
pane_id: record.pane_id,
|
|
313
342
|
pane_id_source: record.pane_id_source,
|
|
343
|
+
worker_stacked_requested: record.worker_stacked_requested === true,
|
|
344
|
+
worker_stacked_applied: record.worker_stacked_applied === true,
|
|
345
|
+
worker_stacked_fallback_mode: record.worker_stacked_fallback_mode || null,
|
|
314
346
|
blockers
|
|
315
347
|
});
|
|
348
|
+
await appendParallelRuntimeEvent(root, input.missionId, {
|
|
349
|
+
event_type: 'zellij_pane_created',
|
|
350
|
+
slot_id: input.slotId,
|
|
351
|
+
generation_index: input.generationIndex,
|
|
352
|
+
session_id: input.sessionId,
|
|
353
|
+
pid: null,
|
|
354
|
+
backend: String(input.backend || 'zellij'),
|
|
355
|
+
placement: 'zellij-pane',
|
|
356
|
+
meta: {
|
|
357
|
+
ok: record.ok,
|
|
358
|
+
pane_id: record.pane_id,
|
|
359
|
+
worker_stacked_requested: record.worker_stacked_requested === true,
|
|
360
|
+
worker_stacked_applied: record.worker_stacked_applied === true,
|
|
361
|
+
worker_stacked_fallback_mode: record.worker_stacked_fallback_mode || null
|
|
362
|
+
}
|
|
363
|
+
}).catch(() => undefined);
|
|
316
364
|
await appendJsonl(path.join(root, 'agent-zellij-pane-launch-ledger.jsonl'), {
|
|
317
365
|
schema: 'sks.agent-zellij-pane-launch.v1',
|
|
318
366
|
generated_at: nowIso(),
|
|
@@ -338,6 +386,7 @@ export async function openWorkerPane(input) {
|
|
|
338
386
|
worker_direction_applied: record.worker_direction_applied,
|
|
339
387
|
worker_stacked_requested: record.worker_stacked_requested === true,
|
|
340
388
|
worker_stacked_applied: record.worker_stacked_applied === true,
|
|
389
|
+
worker_stacked_fallback_mode: record.worker_stacked_fallback_mode || null,
|
|
341
390
|
slot_column_anchor_pane_id: record.slot_column_anchor_pane_id || null,
|
|
342
391
|
command: record.command,
|
|
343
392
|
worker_artifact_dir: input.workerArtifactDir,
|
|
@@ -627,8 +676,20 @@ function sleep(ms) {
|
|
|
627
676
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
628
677
|
}
|
|
629
678
|
const zellijPaneCreationLocks = new Map();
|
|
630
|
-
async function withZellijPaneCreationLock(
|
|
631
|
-
const key = String(sessionName || 'default');
|
|
679
|
+
async function withZellijPaneCreationLock(input, fn) {
|
|
680
|
+
const key = String(input.sessionName || 'default');
|
|
681
|
+
const requestedAt = nowIso();
|
|
682
|
+
const requestedMs = Date.now();
|
|
683
|
+
await appendWorkerPaneEvent(path.resolve(input.root), 'zellij_pane_creation_lock_requested', input, {}).catch(() => undefined);
|
|
684
|
+
await appendParallelRuntimeEvent(path.resolve(input.root), input.missionId, {
|
|
685
|
+
event_type: 'zellij_pane_creation_lock_requested',
|
|
686
|
+
slot_id: input.slotId,
|
|
687
|
+
generation_index: input.generationIndex,
|
|
688
|
+
session_id: input.sessionId,
|
|
689
|
+
pid: null,
|
|
690
|
+
backend: String(input.backend || 'zellij'),
|
|
691
|
+
placement: 'zellij-pane'
|
|
692
|
+
}).catch(() => undefined);
|
|
632
693
|
const previous = zellijPaneCreationLocks.get(key) || Promise.resolve();
|
|
633
694
|
let release;
|
|
634
695
|
const current = new Promise((resolve) => {
|
|
@@ -636,15 +697,65 @@ async function withZellijPaneCreationLock(sessionName, fn) {
|
|
|
636
697
|
});
|
|
637
698
|
zellijPaneCreationLocks.set(key, previous.then(() => current, () => current));
|
|
638
699
|
await previous.catch(() => undefined);
|
|
700
|
+
const acquiredAt = nowIso();
|
|
701
|
+
const acquiredMs = Date.now();
|
|
702
|
+
await appendWorkerPaneEvent(path.resolve(input.root), 'zellij_pane_creation_lock_acquired', input, { wait_ms: acquiredMs - requestedMs }).catch(() => undefined);
|
|
703
|
+
await appendParallelRuntimeEvent(path.resolve(input.root), input.missionId, {
|
|
704
|
+
event_type: 'zellij_pane_creation_lock_acquired',
|
|
705
|
+
slot_id: input.slotId,
|
|
706
|
+
generation_index: input.generationIndex,
|
|
707
|
+
session_id: input.sessionId,
|
|
708
|
+
pid: null,
|
|
709
|
+
backend: String(input.backend || 'zellij'),
|
|
710
|
+
placement: 'zellij-pane',
|
|
711
|
+
meta: { wait_ms: acquiredMs - requestedMs }
|
|
712
|
+
}).catch(() => undefined);
|
|
639
713
|
try {
|
|
640
714
|
return await fn();
|
|
641
715
|
}
|
|
642
716
|
finally {
|
|
717
|
+
const releasedAt = nowIso();
|
|
718
|
+
const releasedMs = Date.now();
|
|
719
|
+
const metrics = {
|
|
720
|
+
schema: ZELLIJ_PANE_CREATION_LOCK_METRICS_SCHEMA,
|
|
721
|
+
mission_id: input.missionId,
|
|
722
|
+
session_name: input.sessionName,
|
|
723
|
+
slot_id: input.slotId,
|
|
724
|
+
generation_index: input.generationIndex,
|
|
725
|
+
requested_at: requestedAt,
|
|
726
|
+
acquired_at: acquiredAt,
|
|
727
|
+
released_at: releasedAt,
|
|
728
|
+
wait_ms: acquiredMs - requestedMs,
|
|
729
|
+
held_ms: releasedMs - acquiredMs
|
|
730
|
+
};
|
|
731
|
+
await appendJsonl(paneCreationLockMetricsPath(path.resolve(input.root), input.missionId), metrics).catch(() => undefined);
|
|
732
|
+
await appendWorkerPaneEvent(path.resolve(input.root), 'zellij_pane_creation_lock_released', input, { wait_ms: metrics.wait_ms, held_ms: metrics.held_ms }).catch(() => undefined);
|
|
733
|
+
await appendParallelRuntimeEvent(path.resolve(input.root), input.missionId, {
|
|
734
|
+
event_type: 'zellij_pane_creation_lock_released',
|
|
735
|
+
slot_id: input.slotId,
|
|
736
|
+
generation_index: input.generationIndex,
|
|
737
|
+
session_id: input.sessionId,
|
|
738
|
+
pid: null,
|
|
739
|
+
backend: String(input.backend || 'zellij'),
|
|
740
|
+
placement: 'zellij-pane',
|
|
741
|
+
meta: { wait_ms: metrics.wait_ms, held_ms: metrics.held_ms }
|
|
742
|
+
}).catch(() => undefined);
|
|
643
743
|
release();
|
|
644
744
|
if (zellijPaneCreationLocks.get(key) === current)
|
|
645
745
|
zellijPaneCreationLocks.delete(key);
|
|
646
746
|
}
|
|
647
747
|
}
|
|
748
|
+
function paneCreationLockMetricsPath(root, missionId) {
|
|
749
|
+
return path.join(missionArtifactRoot(root, missionId), 'zellij', 'pane-creation-lock-events.jsonl');
|
|
750
|
+
}
|
|
751
|
+
function missionArtifactRoot(root, missionId) {
|
|
752
|
+
const resolved = path.resolve(root);
|
|
753
|
+
if (path.basename(resolved) === 'agents')
|
|
754
|
+
return path.dirname(resolved);
|
|
755
|
+
if (path.basename(resolved) === missionId)
|
|
756
|
+
return resolved;
|
|
757
|
+
return path.join(resolved, '.sneakoscope', 'missions', missionId);
|
|
758
|
+
}
|
|
648
759
|
function normalizeExistingZellijSession(sessionName, result) {
|
|
649
760
|
if (result.ok)
|
|
650
761
|
return result;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { assertGate, root } from './sks-1-18-gate-lib.js';
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
|
|
8
|
+
const truth = readJson('.sneakoscope/release-proof-truth.json') || readJson('dist/release-proof-truth.json');
|
|
9
|
+
const changelog = fs.readFileSync(path.join(root, 'CHANGELOG.md'), 'utf8');
|
|
10
|
+
const latest = latestChangelogSection(changelog);
|
|
11
|
+
const body = [
|
|
12
|
+
`Version: ${pkg.version}`,
|
|
13
|
+
`Commit: ${truth?.git_commit_sha || 'unknown'}`,
|
|
14
|
+
`Packlist: ${truth?.npm_packlist_count ?? 'unknown'} files / ${truth?.npm_packlist_bytes ?? 'unknown'} bytes`,
|
|
15
|
+
'Release gates: passed',
|
|
16
|
+
'',
|
|
17
|
+
latest.body.trim()
|
|
18
|
+
].join('\n');
|
|
19
|
+
if (args.includes('--check')) {
|
|
20
|
+
assertGate(Boolean(truth && truth.schema === 'sks.release-proof-truth.v1'), 'release proof truth missing; run npm run release:proof-truth first');
|
|
21
|
+
assertGate(latest.version === pkg.version, 'latest changelog section must match package version', { latest: latest.version, package: pkg.version });
|
|
22
|
+
assertGate(body.includes(`Version: ${pkg.version}`) && body.includes('Commit:') && body.includes('Packlist:'), 'github release body helper missing source truth fields', { body });
|
|
23
|
+
}
|
|
24
|
+
console.log(body);
|
|
25
|
+
function readJson(rel) {
|
|
26
|
+
try {
|
|
27
|
+
return JSON.parse(fs.readFileSync(path.join(root, rel), 'utf8'));
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function latestChangelogSection(text) {
|
|
34
|
+
const matches = [...text.matchAll(/^## \[([^\]]+)\][^\n]*\n/gm)];
|
|
35
|
+
const first = matches.find((match) => /^[0-9]+\.[0-9]+\.[0-9]+$/.test(match[1]));
|
|
36
|
+
if (!first)
|
|
37
|
+
return { version: null, body: '' };
|
|
38
|
+
const second = matches.find((match) => (match.index || 0) > (first.index || 0));
|
|
39
|
+
const start = (first.index || 0) + first[0].length;
|
|
40
|
+
const end = second?.index || text.length;
|
|
41
|
+
return { version: first[1], body: text.slice(start, end) };
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=github-release-body-helper.js.map
|
|
@@ -25,6 +25,17 @@ console.log(JSON.stringify({
|
|
|
25
25
|
skipped_gate_ids: summary?.skipped_by_affected || [],
|
|
26
26
|
cached: summary?.cached || 0,
|
|
27
27
|
cached_gates: summary?.cached_gates || [],
|
|
28
|
+
version_neutralized_inputs: [
|
|
29
|
+
'package.json:version',
|
|
30
|
+
'package-lock.json:root.version',
|
|
31
|
+
'src/core/version.ts:PACKAGE_VERSION',
|
|
32
|
+
'src/core/fsx.ts:PACKAGE_VERSION',
|
|
33
|
+
'src/bin/sks.ts:FAST_PACKAGE_VERSION',
|
|
34
|
+
'dist/build-manifest.json:version'
|
|
35
|
+
],
|
|
36
|
+
behavior_affecting_inputs: [],
|
|
37
|
+
cache_key_policy: 'version-neutral-safe-v1',
|
|
38
|
+
cache_message: 'Release cache: version-only changes neutralized for behavior gates. Version correctness gates still ran uncached.',
|
|
28
39
|
executed: summary?.executed_gates?.length || 0,
|
|
29
40
|
executed_gates: summary?.executed_gates || [],
|
|
30
41
|
wall_ms: summary?.wall_ms || 0,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sneakoscope",
|
|
3
3
|
"displayName": "ㅅㅋㅅ",
|
|
4
|
-
"version": "3.0.
|
|
4
|
+
"version": "3.0.2",
|
|
5
5
|
"description": "Sneakoscope Codex: fast proof-first Codex trust layer with image-based Voxel TriWiki.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"homepage": "https://github.com/mandarange/Sneakoscope-Codex#readme",
|
|
@@ -562,6 +562,7 @@
|
|
|
562
562
|
"release:check:dag:no-cache": "node ./dist/scripts/release-gate-dag-runner.js --preset release --no-cache",
|
|
563
563
|
"release:check:dag:fail-fast": "node ./dist/scripts/release-gate-dag-runner.js --preset release --fail-fast",
|
|
564
564
|
"codex:0138-capability": "node ./dist/scripts/codex-0138-capability-check.js",
|
|
565
|
+
"codex:0139-capability": "node ./dist/scripts/codex-0139-capability-check.js",
|
|
565
566
|
"codex:0138-capability-artifact": "node ./dist/scripts/codex-0138-capability-artifact-check.js",
|
|
566
567
|
"codex-sdk:version-compat": "node ./dist/scripts/codex-sdk-version-compat-check.js",
|
|
567
568
|
"codex-app:handoff": "node ./dist/scripts/codex-app-handoff-check.js",
|
|
@@ -700,7 +701,24 @@
|
|
|
700
701
|
"codex:model-metadata": "node ./dist/scripts/codex-model-metadata-check.js",
|
|
701
702
|
"codex:effort-auto-discovery": "node ./dist/scripts/codex-effort-auto-discovery-check.js",
|
|
702
703
|
"codex:account-usage-autodiscovery": "node ./dist/scripts/codex-account-usage-autodiscovery-check.js",
|
|
703
|
-
"codex:0138-feature-probes": "node ./dist/scripts/codex-0138-feature-probes-check.js"
|
|
704
|
+
"codex:0138-feature-probes": "node ./dist/scripts/codex-0138-feature-probes-check.js",
|
|
705
|
+
"zellij:stacked-version-parser": "node ./dist/scripts/zellij-stacked-version-parser-check.js",
|
|
706
|
+
"zellij:stacked-version-matrix": "node ./dist/scripts/zellij-stacked-version-matrix-check.js",
|
|
707
|
+
"zellij:stacked-capability-routing": "node ./dist/scripts/zellij-stacked-capability-routing-check.js",
|
|
708
|
+
"zellij:pane-creation-lock-metrics": "node ./dist/scripts/zellij-pane-creation-lock-metrics-check.js",
|
|
709
|
+
"zellij:pane-lock-does-not-block-worker": "node ./dist/scripts/zellij-pane-lock-does-not-block-worker-check.js",
|
|
710
|
+
"zellij:pane-lock-concurrency-blackbox": "node ./dist/scripts/zellij-pane-lock-concurrency-blackbox.js",
|
|
711
|
+
"release:cache-input-classifier": "node ./dist/scripts/release-cache-input-classifier-check.js",
|
|
712
|
+
"release:cache-version-neutral-fixtures": "node ./dist/scripts/release-cache-version-neutral-fixture-check.js",
|
|
713
|
+
"release:cache-neutralization-report": "node ./dist/scripts/release-cache-neutralization-report-check.js",
|
|
714
|
+
"agent:message-bus-reader": "node ./dist/scripts/agent-message-bus-reader-check.js",
|
|
715
|
+
"runtime:proof-summary-messages": "node ./dist/scripts/runtime-proof-summary-messages-check.js",
|
|
716
|
+
"naruto:proof-message-summary": "node ./dist/scripts/naruto-proof-message-summary-check.js",
|
|
717
|
+
"zellij:update-prompt-mode": "node ./dist/scripts/zellij-update-prompt-mode-check.js",
|
|
718
|
+
"zellij:update-prompt-safety": "node ./dist/scripts/zellij-update-prompt-safety-check.js",
|
|
719
|
+
"zellij:update-prompt-matrix": "node ./dist/scripts/zellij-update-prompt-matrix-check.js",
|
|
720
|
+
"release:proof-truth": "node ./dist/scripts/release-proof-truth-check.js",
|
|
721
|
+
"release:github-body-helper": "node ./dist/scripts/github-release-body-helper.js --check"
|
|
704
722
|
},
|
|
705
723
|
"keywords": [
|
|
706
724
|
"sneakoscope",
|