sneakoscope 2.0.18 → 3.0.1
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 +127 -71
- 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/commands/mad-sks.js +2 -0
- package/dist/commands/zellij.js +58 -1
- package/dist/core/agents/agent-scheduler.js +32 -24
- package/dist/core/agents/native-cli-session-swarm.js +22 -2
- package/dist/core/codex-app/codex-app-handoff.js +30 -9
- package/dist/core/codex-app/codex-app-launcher.js +103 -0
- package/dist/core/codex-control/codex-0138-capability.js +42 -4
- package/dist/core/codex-control/codex-0139-capability.js +102 -0
- package/dist/core/codex-control/codex-model-capabilities.js +25 -4
- package/dist/core/codex-control/codex-model-metadata.js +91 -0
- package/dist/core/codex-plugins/codex-plugin-cache.js +38 -0
- package/dist/core/codex-plugins/codex-plugin-diff.js +73 -0
- package/dist/core/codex-plugins/codex-plugin-json.js +35 -11
- package/dist/core/commands/mad-sks-command.js +8 -0
- package/dist/core/commands/naruto-command.js +29 -0
- package/dist/core/commands/qa-loop-command.js +41 -6
- package/dist/core/fsx.js +1 -1
- package/dist/core/image/image-artifact-path-contract.js +2 -0
- package/dist/core/image/image-artifact-registry.js +33 -0
- package/dist/core/image-ux-review/imagegen-adapter.js +27 -16
- package/dist/core/pipeline-internals/runtime-core.js +4 -2
- package/dist/core/qa-loop/qa-loop-app-handoff-confirmation.js +51 -0
- package/dist/core/qa-loop/qa-loop-budget-policy.js +1 -1
- package/dist/core/qa-loop.js +44 -3
- package/dist/core/release/release-gate-cache-v2.js +47 -5
- package/dist/core/usage/codex-account-usage.js +77 -16
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/zellij-slot-pane-renderer.js +5 -2
- package/dist/core/zellij/zellij-slot-telemetry.js +65 -12
- package/dist/core/zellij/zellij-ui-mode.js +8 -1
- package/dist/core/zellij/zellij-update.js +307 -0
- package/dist/core/zellij/zellij-worker-pane-manager.js +211 -145
- package/package.json +23 -2
- package/dist/core/naruto/naruto-work-stealing.js +0 -11
- package/dist/core/zellij/zellij-right-column-layout-proof.js +0 -42
|
@@ -5,19 +5,44 @@ export const RELEASE_GATE_CACHE_V2_SCHEMA = 'sks.release-gate-cache.v2';
|
|
|
5
5
|
export function releaseGateCacheFile(root) {
|
|
6
6
|
return path.join(root, '.sneakoscope', 'reports', 'release-gates', 'cache-v2.json');
|
|
7
7
|
}
|
|
8
|
+
// Files whose only release-to-release difference is the version literal.
|
|
9
|
+
// Hashing them version-neutrally keeps a pure `sks versioning bump` from
|
|
10
|
+
// invalidating every behavior gate: bumping the version rewrites
|
|
11
|
+
// package.json, package-lock.json, and the three PACKAGE_VERSION constant
|
|
12
|
+
// sources, which are inputs of ~280 gates (via `package.json` and `src/**`).
|
|
13
|
+
// Before this normalization every publish re-ran the entire DAG from zero
|
|
14
|
+
// (test:blackbox alone is ~11 minutes) even when no behavior changed.
|
|
15
|
+
// Version-CORRECTNESS gates (release:version-truth, release:metadata, ...)
|
|
16
|
+
// are declared with `cache.enabled: false`, so they always re-run and still
|
|
17
|
+
// catch version drift. Set SKS_RELEASE_CACHE_VERSION_SENSITIVE=1 to restore
|
|
18
|
+
// the old fully version-sensitive hashing.
|
|
19
|
+
const VERSION_NEUTRAL_CACHE_FILES = new Set([
|
|
20
|
+
'package.json',
|
|
21
|
+
'package-lock.json',
|
|
22
|
+
'src/core/version.ts',
|
|
23
|
+
'src/core/fsx.ts',
|
|
24
|
+
'src/bin/sks.ts'
|
|
25
|
+
]);
|
|
8
26
|
export function releaseGateCacheKey(root, gate) {
|
|
9
27
|
const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
|
|
28
|
+
const releaseVersion = String(pkg.version || '');
|
|
29
|
+
const versionSensitive = process.env.SKS_RELEASE_CACHE_VERSION_SENSITIVE === '1';
|
|
10
30
|
const hash = crypto.createHash('sha256');
|
|
11
31
|
hash.update(gate.id);
|
|
12
32
|
hash.update(gate.command);
|
|
13
|
-
|
|
33
|
+
if (versionSensitive)
|
|
34
|
+
hash.update(releaseVersion);
|
|
14
35
|
hash.update(process.version);
|
|
15
36
|
hash.update(String(process.env.npm_config_user_agent || ''));
|
|
16
37
|
hash.update(JSON.stringify(gate.resource || []));
|
|
17
38
|
hash.update(JSON.stringify(gate.preset || []));
|
|
18
39
|
hashFileIfPresent(hash, path.join(root, 'release-gates.v2.json'));
|
|
19
|
-
|
|
20
|
-
|
|
40
|
+
if (versionSensitive || !gate.cache.inputs.length) {
|
|
41
|
+
// No declared inputs (or explicitly version-sensitive mode): fall back to
|
|
42
|
+
// the conservative global digests so such a gate cannot cache-hit forever.
|
|
43
|
+
hashFileIfPresent(hash, path.join(root, 'package.json'));
|
|
44
|
+
hashFileIfPresent(hash, path.join(root, 'dist', 'build-manifest.json'));
|
|
45
|
+
}
|
|
21
46
|
for (const input of gate.cache.inputs) {
|
|
22
47
|
const expanded = expandGlob(root, input);
|
|
23
48
|
hash.update(`input:${input}`);
|
|
@@ -26,12 +51,29 @@ export function releaseGateCacheKey(root, gate) {
|
|
|
26
51
|
continue;
|
|
27
52
|
}
|
|
28
53
|
for (const file of expanded) {
|
|
29
|
-
|
|
30
|
-
|
|
54
|
+
const rel = path.relative(root, file);
|
|
55
|
+
hash.update(rel);
|
|
56
|
+
if (!versionSensitive && VERSION_NEUTRAL_CACHE_FILES.has(rel))
|
|
57
|
+
hashVersionNeutralFile(hash, file, releaseVersion);
|
|
58
|
+
else
|
|
59
|
+
hashFileIfPresent(hash, file);
|
|
31
60
|
}
|
|
32
61
|
}
|
|
33
62
|
return hash.digest('hex');
|
|
34
63
|
}
|
|
64
|
+
function hashVersionNeutralFile(hash, file, releaseVersion) {
|
|
65
|
+
if (!fs.existsSync(file) || !fs.statSync(file).isFile())
|
|
66
|
+
return;
|
|
67
|
+
const text = fs.readFileSync(file, 'utf8');
|
|
68
|
+
if (!releaseVersion) {
|
|
69
|
+
hash.update(text);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
// Replace exact occurrences of the current release version literal so a
|
|
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__'));
|
|
76
|
+
}
|
|
35
77
|
export function expandGlob(root, input) {
|
|
36
78
|
const absolute = path.join(root, input);
|
|
37
79
|
if (!/[*!?[\]{}]/.test(input)) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import {
|
|
2
|
+
import { findCodexBinary } from '../codex-adapter.js';
|
|
3
|
+
import { nowIso, runProcess, writeJsonAtomic } from '../fsx.js';
|
|
3
4
|
export async function collectCodexAccountUsage() {
|
|
4
5
|
if (process.env.SKS_CODEX_ACCOUNT_USAGE_FAKE === '1') {
|
|
5
6
|
return {
|
|
@@ -15,22 +16,42 @@ export async function collectCodexAccountUsage() {
|
|
|
15
16
|
reset_at: null
|
|
16
17
|
},
|
|
17
18
|
usage_limit_tokens: 100000,
|
|
19
|
+
attempted_sources: ['fake'],
|
|
18
20
|
blockers: []
|
|
19
21
|
};
|
|
20
22
|
}
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
23
|
+
const attemptedSources = [];
|
|
24
|
+
const urls = [
|
|
25
|
+
['CODEX_APP_SERVER_USAGE_URL', process.env.CODEX_APP_SERVER_USAGE_URL],
|
|
26
|
+
['SKS_CODEX_APP_SERVER_USAGE_URL', process.env.SKS_CODEX_APP_SERVER_USAGE_URL],
|
|
27
|
+
...localWellKnownUsageUrls().map((url) => [`local:${url}`, url])
|
|
28
|
+
];
|
|
29
|
+
const blockers = [];
|
|
30
|
+
for (const [label, rawUrl] of urls) {
|
|
31
|
+
const url = String(rawUrl || '').trim();
|
|
32
|
+
if (!url)
|
|
33
|
+
continue;
|
|
34
|
+
attemptedSources.push(label);
|
|
35
|
+
try {
|
|
36
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(label.startsWith('local:') ? 800 : 5000) });
|
|
37
|
+
if (!response.ok) {
|
|
38
|
+
blockers.push(`codex_app_server_usage_http_${response.status}:${label}`);
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const payload = await response.json();
|
|
42
|
+
return normalizeUsagePayload(payload, 'app-server', attemptedSources);
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
blockers.push(`codex_app_server_usage_fetch_failed:${label}:${err?.message || String(err)}`);
|
|
46
|
+
}
|
|
33
47
|
}
|
|
48
|
+
const cli = await collectUsageFromCodexCli(attemptedSources).catch((err) => {
|
|
49
|
+
blockers.push(`codex_cli_usage_probe_failed:${err?.message || String(err)}`);
|
|
50
|
+
return null;
|
|
51
|
+
});
|
|
52
|
+
if (cli)
|
|
53
|
+
return cli;
|
|
54
|
+
return unavailable(attemptedSources.length ? blockers : ['codex_app_server_usage_endpoint_unavailable'], attemptedSources);
|
|
34
55
|
}
|
|
35
56
|
export async function writeCodexAccountUsageArtifacts(root, input = {}) {
|
|
36
57
|
const snapshot = await collectCodexAccountUsage();
|
|
@@ -43,7 +64,7 @@ export async function writeCodexAccountUsageArtifacts(root, input = {}) {
|
|
|
43
64
|
}
|
|
44
65
|
return { snapshot, root_artifact: rootArtifact, mission_artifact: missionArtifact };
|
|
45
66
|
}
|
|
46
|
-
function normalizeUsagePayload(payload, source) {
|
|
67
|
+
function normalizeUsagePayload(payload, source, attemptedSources) {
|
|
47
68
|
const usage = payload?.token_usage || payload?.usage || payload;
|
|
48
69
|
const input = Number(usage?.input_tokens || usage?.inputTokens || 0);
|
|
49
70
|
const output = Number(usage?.output_tokens || usage?.outputTokens || 0);
|
|
@@ -61,18 +82,58 @@ function normalizeUsagePayload(payload, source) {
|
|
|
61
82
|
reset_at: usage?.reset_at || usage?.resetAt || null
|
|
62
83
|
},
|
|
63
84
|
usage_limit_tokens: Number.isFinite(Number(payload?.usage_limit_tokens || payload?.usageLimitTokens)) ? Number(payload?.usage_limit_tokens || payload?.usageLimitTokens) : null,
|
|
85
|
+
attempted_sources: attemptedSources,
|
|
64
86
|
blockers: []
|
|
65
87
|
};
|
|
66
88
|
}
|
|
67
|
-
function unavailable(blockers) {
|
|
89
|
+
function unavailable(blockers, attemptedSources = []) {
|
|
68
90
|
return {
|
|
69
91
|
schema: 'sks.codex-account-usage.v1',
|
|
70
92
|
generated_at: nowIso(),
|
|
71
|
-
ok:
|
|
93
|
+
ok: false,
|
|
72
94
|
source: 'unavailable',
|
|
73
95
|
token_usage: null,
|
|
74
96
|
usage_limit_tokens: null,
|
|
97
|
+
attempted_sources: attemptedSources,
|
|
75
98
|
blockers
|
|
76
99
|
};
|
|
77
100
|
}
|
|
101
|
+
function localWellKnownUsageUrls() {
|
|
102
|
+
const ports = [
|
|
103
|
+
process.env.CODEX_APP_SERVER_PORT,
|
|
104
|
+
process.env.SKS_CODEX_APP_SERVER_PORT,
|
|
105
|
+
1455,
|
|
106
|
+
1456,
|
|
107
|
+
3000
|
|
108
|
+
].map((value) => Number(value)).filter((value, index, rows) => Number.isFinite(value) && value > 0 && rows.indexOf(value) === index);
|
|
109
|
+
return ports.flatMap((port) => [
|
|
110
|
+
`http://127.0.0.1:${port}/usage`,
|
|
111
|
+
`http://127.0.0.1:${port}/api/usage`,
|
|
112
|
+
`http://127.0.0.1:${port}/.well-known/codex/usage`
|
|
113
|
+
]);
|
|
114
|
+
}
|
|
115
|
+
async function collectUsageFromCodexCli(attemptedSources) {
|
|
116
|
+
const bin = await findCodexBinary();
|
|
117
|
+
if (!bin)
|
|
118
|
+
return null;
|
|
119
|
+
const commands = [
|
|
120
|
+
['account', 'usage', '--json'],
|
|
121
|
+
['usage', '--json'],
|
|
122
|
+
['app-server', 'status', '--json']
|
|
123
|
+
];
|
|
124
|
+
for (const args of commands) {
|
|
125
|
+
const label = `codex-cli:${args.join(' ')}`;
|
|
126
|
+
attemptedSources.push(label);
|
|
127
|
+
const result = await runProcess(bin, args, { timeoutMs: 3000, maxOutputBytes: 64 * 1024 }).catch(() => null);
|
|
128
|
+
if (!result || result.code !== 0)
|
|
129
|
+
continue;
|
|
130
|
+
try {
|
|
131
|
+
const payload = JSON.parse(`${result.stdout || ''}${result.stderr || ''}`.trim() || '{}');
|
|
132
|
+
const normalized = normalizeUsagePayload(payload, 'app-server', attemptedSources);
|
|
133
|
+
return { ...normalized, source: 'app-server' };
|
|
134
|
+
}
|
|
135
|
+
catch { }
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
78
139
|
//# sourceMappingURL=codex-account-usage.js.map
|
package/dist/core/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const PACKAGE_VERSION = '
|
|
1
|
+
export const PACKAGE_VERSION = '3.0.1';
|
|
2
2
|
//# sourceMappingURL=version.js.map
|
|
@@ -215,7 +215,10 @@ async function tryRenderTelemetrySlotPane(input) {
|
|
|
215
215
|
return null;
|
|
216
216
|
const staleRows = staleTelemetryRows(telemetryStatus(snapshot).telemetry_age_ms);
|
|
217
217
|
const fallbackRows = artifactFallbackRows(input.artifactRender);
|
|
218
|
-
|
|
218
|
+
// Always surface the live artifact rows (current file, tool events, stdout
|
|
219
|
+
// tail). Telemetry freshness only tells us the worker is alive — the user
|
|
220
|
+
// still needs to see WHAT the worker is doing right now.
|
|
221
|
+
const liveRows = fallbackRows;
|
|
219
222
|
if (slot.status === 'failed') {
|
|
220
223
|
return [
|
|
221
224
|
`${slot.slot_id} gen-${slot.generation_index} · FAILED`,
|
|
@@ -254,7 +257,7 @@ function artifactFallbackRows(text) {
|
|
|
254
257
|
.map((line) => line.replace(/^\|\s?/, '').replace(/\s?\|$/, '').trim())
|
|
255
258
|
.filter((line) => /^(heartbeat|doing|files|event|out|err):\s+/i.test(line))
|
|
256
259
|
.filter((line) => !/unknown|waiting for worker intake|no changed file yet/i.test(line))
|
|
257
|
-
.slice(-
|
|
260
|
+
.slice(-7)
|
|
258
261
|
.map((line) => `live: ${trimInline(line, 72)}`);
|
|
259
262
|
}
|
|
260
263
|
function findTelemetrySlot(snapshot, slotId, generationIndex) {
|
|
@@ -7,6 +7,7 @@ const telemetrySnapshotCache = new Map();
|
|
|
7
7
|
const telemetrySnapshotWriteCounts = new Map();
|
|
8
8
|
const telemetrySnapshotFlushCounts = new Map();
|
|
9
9
|
const telemetrySnapshotLastFlushMs = new Map();
|
|
10
|
+
const telemetrySnapshotDiskStat = new Map();
|
|
10
11
|
export function slotTelemetryEventPath(root, missionId) {
|
|
11
12
|
return path.join(inferMissionDir(root, missionId), 'zellij', 'slot-telemetry.events.jsonl');
|
|
12
13
|
}
|
|
@@ -33,24 +34,68 @@ export async function appendZellijSlotTelemetry(root, event) {
|
|
|
33
34
|
await rebuildZellijSlotTelemetrySnapshot(root, missionId);
|
|
34
35
|
}
|
|
35
36
|
export async function readZellijSlotTelemetrySnapshot(root, missionId) {
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return cached;
|
|
40
|
-
const existing = await readJson(snapshotPath, null);
|
|
41
|
-
if (existing?.schema === ZELLIJ_SLOT_TELEMETRY_SNAPSHOT_SCHEMA)
|
|
42
|
-
return existing;
|
|
37
|
+
const fresh = await readZellijSlotTelemetrySnapshotNoRebuild(root, missionId);
|
|
38
|
+
if (fresh)
|
|
39
|
+
return fresh;
|
|
43
40
|
return rebuildZellijSlotTelemetrySnapshot(root, missionId);
|
|
44
41
|
}
|
|
45
42
|
export async function readZellijSlotTelemetrySnapshotNoRebuild(root, missionId) {
|
|
46
43
|
const snapshotPath = slotTelemetrySnapshotPath(root, missionId);
|
|
47
44
|
const cached = telemetrySnapshotCache.get(snapshotPath);
|
|
48
|
-
|
|
45
|
+
const stat = await statTelemetryFile(snapshotPath);
|
|
46
|
+
const recorded = telemetrySnapshotDiskStat.get(snapshotPath);
|
|
47
|
+
const diskChanged = Boolean(stat) && (!recorded || recorded.mtimeMs !== stat.mtimeMs || recorded.size !== stat.size);
|
|
48
|
+
// CRITICAL: never serve a process-local cache forever. Long-lived reader
|
|
49
|
+
// processes (zellij slot pane renderers in --watch mode) must observe
|
|
50
|
+
// snapshot flushes performed by the orchestrator and worker processes,
|
|
51
|
+
// otherwise the pane renders the same frame for the entire mission.
|
|
52
|
+
if (!diskChanged && cached?.schema === ZELLIJ_SLOT_TELEMETRY_SNAPSHOT_SCHEMA)
|
|
49
53
|
return cached;
|
|
50
54
|
const existing = await readJson(snapshotPath, null);
|
|
51
|
-
if (
|
|
52
|
-
|
|
53
|
-
|
|
55
|
+
if (stat)
|
|
56
|
+
telemetrySnapshotDiskStat.set(snapshotPath, stat);
|
|
57
|
+
if (existing?.schema !== ZELLIJ_SLOT_TELEMETRY_SNAPSHOT_SCHEMA) {
|
|
58
|
+
return cached?.schema === ZELLIJ_SLOT_TELEMETRY_SNAPSHOT_SCHEMA ? cached : null;
|
|
59
|
+
}
|
|
60
|
+
const disk = existing;
|
|
61
|
+
// Merge with any locally cached (possibly not-yet-flushed) slot state so a
|
|
62
|
+
// writer process does not lose its pending events when another process
|
|
63
|
+
// flushed the snapshot file in the meantime. The DISK updated_at stays
|
|
64
|
+
// authoritative on the read path: it reflects the last real flush, which is
|
|
65
|
+
// what stale-telemetry detection must measure.
|
|
66
|
+
const merged = cached?.schema === ZELLIJ_SLOT_TELEMETRY_SNAPSHOT_SCHEMA ? mergeTelemetrySnapshots(disk, cached, { updatedAt: 'base' }) : disk;
|
|
67
|
+
telemetrySnapshotCache.set(snapshotPath, merged);
|
|
68
|
+
return merged;
|
|
69
|
+
}
|
|
70
|
+
export function mergeTelemetrySnapshots(base, overlay, opts = {}) {
|
|
71
|
+
const slots = { ...(base.slots || {}) };
|
|
72
|
+
for (const [key, row] of Object.entries(overlay.slots || {})) {
|
|
73
|
+
const existing = slots[key];
|
|
74
|
+
slots[key] = !existing || telemetryTsMs(row.latest_ts) >= telemetryTsMs(existing.latest_ts) ? row : existing;
|
|
75
|
+
}
|
|
76
|
+
const baseTs = Date.parse(String(base.updated_at || '')) || 0;
|
|
77
|
+
const overlayTs = Date.parse(String(overlay.updated_at || '')) || 0;
|
|
78
|
+
return {
|
|
79
|
+
schema: ZELLIJ_SLOT_TELEMETRY_SNAPSHOT_SCHEMA,
|
|
80
|
+
mission_id: base.mission_id || overlay.mission_id,
|
|
81
|
+
updated_at: opts.updatedAt === 'base' ? base.updated_at : overlayTs > baseTs ? overlay.updated_at : base.updated_at,
|
|
82
|
+
flush_count: Math.max(Number(base.flush_count || 0), Number(overlay.flush_count || 0)),
|
|
83
|
+
slots,
|
|
84
|
+
counts: countSlotTelemetry(slots)
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function telemetryTsMs(value) {
|
|
88
|
+
const parsed = Date.parse(String(value || ''));
|
|
89
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
90
|
+
}
|
|
91
|
+
async function statTelemetryFile(file) {
|
|
92
|
+
try {
|
|
93
|
+
const st = await fsp.stat(file);
|
|
94
|
+
return { mtimeMs: st.mtimeMs, size: st.size };
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
54
99
|
}
|
|
55
100
|
export function applyTelemetryEventToSnapshot(snapshot, event) {
|
|
56
101
|
const key = slotTelemetryKey(event.slot_id || event.worker_id, event.generation_index);
|
|
@@ -216,9 +261,17 @@ async function writeTelemetrySnapshotFast(file, snapshot) {
|
|
|
216
261
|
const flushCount = Number(telemetrySnapshotFlushCounts.get(file) || 0) + 1;
|
|
217
262
|
telemetrySnapshotFlushCounts.set(file, flushCount);
|
|
218
263
|
telemetrySnapshotLastFlushMs.set(file, Date.now());
|
|
219
|
-
|
|
264
|
+
// Merge with the on-disk snapshot before overwriting: multiple processes
|
|
265
|
+
// (orchestrator + worker children) flush this file concurrently and a plain
|
|
266
|
+
// overwrite would drop slots that only the other process has observed.
|
|
267
|
+
const disk = await readJson(file, null);
|
|
268
|
+
const merged = disk?.schema === ZELLIJ_SLOT_TELEMETRY_SNAPSHOT_SCHEMA ? mergeTelemetrySnapshots(disk, snapshot) : snapshot;
|
|
269
|
+
const next = { ...merged, flush_count: Math.max(flushCount, Number(merged.flush_count || 0)) };
|
|
220
270
|
telemetrySnapshotCache.set(file, next);
|
|
221
271
|
await fsp.writeFile(file, `${JSON.stringify(next)}\n`, 'utf8');
|
|
272
|
+
const stat = await statTelemetryFile(file);
|
|
273
|
+
if (stat)
|
|
274
|
+
telemetrySnapshotDiskStat.set(file, stat);
|
|
222
275
|
}
|
|
223
276
|
function shouldFlushTelemetrySnapshot(file, event) {
|
|
224
277
|
const next = (telemetrySnapshotWriteCounts.get(file) || 0) + 1;
|
|
@@ -2,7 +2,14 @@ export function resolveZellijUiMode(args = [], env = process.env) {
|
|
|
2
2
|
return resolveExplicitZellijUiMode(args, env) || 'compact-slots';
|
|
3
3
|
}
|
|
4
4
|
export function resolveZellijWorkerPaneUiMode(args = [], env = process.env) {
|
|
5
|
-
|
|
5
|
+
// Default worker panes to the live slot renderer (compact-slots). In
|
|
6
|
+
// 'full-debug' the pane runs the worker process itself, but the worker is
|
|
7
|
+
// invoked with --json and the codex SDK streams events to JSONL files — so
|
|
8
|
+
// the pane stays blank until the worker exits. The slot renderer re-reads
|
|
9
|
+
// heartbeat/event/stdout artifacts every second and actually shows what each
|
|
10
|
+
// parallel worker is doing in real time. 'full-debug' remains available via
|
|
11
|
+
// --zellij-full-debug or SKS_ZELLIJ_UI_MODE=full-debug.
|
|
12
|
+
return resolveExplicitZellijUiMode(args, env) || 'compact-slots';
|
|
6
13
|
}
|
|
7
14
|
function resolveExplicitZellijUiMode(args = [], env = process.env) {
|
|
8
15
|
const fromEnv = String(env.SKS_ZELLIJ_UI_MODE || '').trim();
|