sneakoscope 2.0.6 → 2.0.8
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 +6 -1
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/crates/sks-core/src/main.rs +1 -1
- package/dist/.sks-build-stamp.json +4 -4
- package/dist/bin/sks.js +1 -1
- package/dist/build-manifest.json +56 -8
- package/dist/core/agents/agent-command-surface.js +4 -2
- package/dist/core/agents/agent-orchestrator.js +140 -4
- package/dist/core/agents/agent-patch-schema.js +20 -4
- package/dist/core/agents/agent-proof-evidence.js +3 -0
- package/dist/core/agents/native-cli-session-swarm.js +31 -5
- package/dist/core/agents/native-cli-worker.js +28 -1
- package/dist/core/codex-control/python-codex-sdk-adapter.js +28 -4
- package/dist/core/commands/mad-sks-command.js +25 -0
- package/dist/core/commands/naruto-command.js +68 -10
- package/dist/core/feature-registry.js +2 -0
- package/dist/core/fsx.js +1 -1
- package/dist/core/git/git-integration-worktree.js +15 -0
- package/dist/core/git/git-repo-detection.js +79 -0
- package/dist/core/git/git-worktree-cache-policy.js +36 -0
- package/dist/core/git/git-worktree-capability.js +54 -0
- package/dist/core/git/git-worktree-cleanup.js +62 -0
- package/dist/core/git/git-worktree-conflict-resolver.js +13 -0
- package/dist/core/git/git-worktree-diff.js +55 -0
- package/dist/core/git/git-worktree-manager.js +93 -0
- package/dist/core/git/git-worktree-merge-queue.js +55 -0
- package/dist/core/git/git-worktree-patch-envelope.js +35 -0
- package/dist/core/git/git-worktree-pool.js +23 -0
- package/dist/core/git/git-worktree-root.js +52 -0
- package/dist/core/git/git-worktree-runner.js +40 -0
- package/dist/core/naruto/naruto-active-pool.js +35 -0
- package/dist/core/naruto/naruto-gpt-final-pack.js +2 -0
- package/dist/core/naruto/naruto-work-graph.js +16 -1
- package/dist/core/release/release-gate-cache-v2.js +63 -0
- package/dist/core/release/release-gate-dag.js +179 -0
- package/dist/core/release/release-gate-hermetic-env.js +32 -0
- package/dist/core/release/release-gate-node.js +62 -0
- package/dist/core/release/release-gate-report.js +11 -0
- package/dist/core/release/release-gate-resource-governor.js +54 -0
- package/dist/core/release/release-gate-scheduler.js +15 -0
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/zellij-dashboard-pane.js +71 -0
- package/dist/core/zellij/zellij-dashboard-renderer.js +42 -0
- package/dist/core/zellij/zellij-naruto-dashboard.js +10 -1
- package/dist/core/zellij/zellij-worker-pane-manager.js +68 -6
- package/dist/scripts/git-worktree-cache-performance-check.js +25 -0
- package/dist/scripts/git-worktree-capability-check.js +27 -0
- package/dist/scripts/git-worktree-cleanup-check.js +27 -0
- package/dist/scripts/git-worktree-diff-envelope-check.js +17 -0
- package/dist/scripts/git-worktree-diff-export-check.js +43 -0
- package/dist/scripts/git-worktree-dirty-lock-check.js +17 -0
- package/dist/scripts/git-worktree-dirty-main-detection-check.js +14 -0
- package/dist/scripts/git-worktree-integration-primary-check.js +22 -0
- package/dist/scripts/git-worktree-manager-check.js +37 -0
- package/dist/scripts/git-worktree-manifest-append-check.js +18 -0
- package/dist/scripts/git-worktree-merge-queue-check.js +30 -0
- package/dist/scripts/git-worktree-pool-performance-check.js +20 -0
- package/dist/scripts/git-worktree-untracked-diff-check.js +18 -0
- package/dist/scripts/lib/git-worktree-fixture.js +33 -0
- package/dist/scripts/naruto-shadow-clone-swarm-check.js +9 -5
- package/dist/scripts/naruto-worktree-coding-blackbox.js +29 -0
- package/dist/scripts/naruto-worktree-coding-check.js +44 -0
- package/dist/scripts/naruto-worktree-gpt-final-check.js +45 -0
- package/dist/scripts/naruto-worktree-zellij-ui-check.js +28 -0
- package/dist/scripts/release-gate-dag-runner-check.js +17 -0
- package/dist/scripts/release-gate-dag-runner.js +32 -0
- package/dist/scripts/release-gate-worker.js +10 -0
- package/dist/scripts/release-metadata-1-19-check.js +8 -2
- package/dist/scripts/release-parallel-check.js +1 -1
- package/dist/scripts/release-parallel-speed-budget-check.js +25 -0
- package/dist/scripts/release-stability-report-check.js +99 -0
- package/dist/scripts/zellij-dashboard-pane-check.js +68 -0
- package/dist/scripts/zellij-dashboard-watch.js +41 -0
- package/dist/scripts/zellij-worker-pane-real-ui-blackbox.js +185 -0
- package/package.json +33 -5
- package/schemas/git/git-worktree-capability.schema.json +19 -0
- package/schemas/git/git-worktree-manifest.schema.json +36 -0
- package/schemas/release/release-gate-node.schema.json +52 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import { createReleaseGateHermeticEnv } from './release-gate-hermetic-env.js';
|
|
5
|
+
import { appendReleaseGateJsonl, writeReleaseGateJson } from './release-gate-report.js';
|
|
6
|
+
import { findReadyReleaseGateNodes, findReleaseGatesBlockedByFailedDeps, pickReadyLaunchableReleaseGates } from './release-gate-scheduler.js';
|
|
7
|
+
import { readReleaseGateCacheHit, writeReleaseGateCacheHit } from './release-gate-cache-v2.js';
|
|
8
|
+
import { RELEASE_GATE_NODE_SCHEMA, validateReleaseGateManifest } from './release-gate-node.js';
|
|
9
|
+
import { countReleaseGateResources, defaultReleaseGateBudget, summarizeReleaseGateBudget } from './release-gate-resource-governor.js';
|
|
10
|
+
export function loadReleaseGateManifest(root, file = 'release-gates.v2.json') {
|
|
11
|
+
const manifestPath = path.join(root, file);
|
|
12
|
+
const parsed = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
13
|
+
const validation = validateReleaseGateManifest(parsed);
|
|
14
|
+
if (!validation.ok || !validation.manifest) {
|
|
15
|
+
throw new Error(`invalid ${file}: ${validation.errors.join(', ')}`);
|
|
16
|
+
}
|
|
17
|
+
return validation.manifest;
|
|
18
|
+
}
|
|
19
|
+
export async function runReleaseGateDag(input) {
|
|
20
|
+
const root = path.resolve(input.root);
|
|
21
|
+
const preset = input.preset || 'release';
|
|
22
|
+
const manifest = loadReleaseGateManifest(root);
|
|
23
|
+
const selected = selectPreset(manifest, preset);
|
|
24
|
+
const runId = `rg-${new Date().toISOString().replace(/[:.]/g, '-')}-${process.pid}`;
|
|
25
|
+
const reportDir = path.join(root, '.sneakoscope', 'reports', 'release-gates', runId);
|
|
26
|
+
fs.mkdirSync(reportDir, { recursive: true });
|
|
27
|
+
const timeline = path.join(reportDir, 'timeline.jsonl');
|
|
28
|
+
const started = Date.now();
|
|
29
|
+
const pending = new Map(selected.map((gate) => [gate.id, gate]));
|
|
30
|
+
const running = new Map();
|
|
31
|
+
const completed = new Map();
|
|
32
|
+
const failed = new Map();
|
|
33
|
+
const budget = defaultReleaseGateBudget();
|
|
34
|
+
const peakResources = {};
|
|
35
|
+
let cached = 0;
|
|
36
|
+
let sumGateMs = 0;
|
|
37
|
+
let peakRunning = 0;
|
|
38
|
+
if (input.explain) {
|
|
39
|
+
writeReleaseGateJson(path.join(reportDir, 'explain.json'), { schema: RELEASE_GATE_NODE_SCHEMA, preset, budget, gates: selected.map((gate) => ({ id: gate.id, deps: gate.deps, resource: gate.resource, command: gate.command })) });
|
|
40
|
+
}
|
|
41
|
+
while (pending.size || running.size) {
|
|
42
|
+
const ready = findReadyReleaseGateNodes({ pending, completed, failed });
|
|
43
|
+
const launchable = pickReadyLaunchableReleaseGates({ ready, running: [...running.values()].map((row) => row.gate) });
|
|
44
|
+
let progressed = false;
|
|
45
|
+
for (const gate of launchable) {
|
|
46
|
+
pending.delete(gate.id);
|
|
47
|
+
const cacheHit = !input.noCache && gate.cache.enabled && readReleaseGateCacheHit(root, gate);
|
|
48
|
+
if (cacheHit) {
|
|
49
|
+
const result = { id: gate.id, ok: true, exit_code: 0, duration_ms: 0, cached: true, stderr_tail: '' };
|
|
50
|
+
completed.set(gate.id, result);
|
|
51
|
+
cached += 1;
|
|
52
|
+
progressed = true;
|
|
53
|
+
appendReleaseGateJsonl(timeline, { event: 'cache_hit', gate_id: gate.id, at: new Date().toISOString() });
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
appendReleaseGateJsonl(timeline, { event: 'start', gate_id: gate.id, resource: gate.resource, at: new Date().toISOString() });
|
|
57
|
+
running.set(gate.id, { gate, promise: runGate(root, runId, reportDir, gate) });
|
|
58
|
+
peakRunning = Math.max(peakRunning, running.size);
|
|
59
|
+
const used = countReleaseGateResources([...running.values()].map((row) => row.gate));
|
|
60
|
+
for (const [resource, count] of Object.entries(used)) {
|
|
61
|
+
peakResources[resource] = Math.max(peakResources[resource] || 0, Number(count) || 0);
|
|
62
|
+
}
|
|
63
|
+
progressed = true;
|
|
64
|
+
}
|
|
65
|
+
if (!running.size) {
|
|
66
|
+
const blockedByFailedDeps = findReleaseGatesBlockedByFailedDeps({ pending, failed });
|
|
67
|
+
if (blockedByFailedDeps.length) {
|
|
68
|
+
for (const gate of blockedByFailedDeps) {
|
|
69
|
+
pending.delete(gate.id);
|
|
70
|
+
const result = {
|
|
71
|
+
id: gate.id,
|
|
72
|
+
ok: false,
|
|
73
|
+
exit_code: null,
|
|
74
|
+
duration_ms: 0,
|
|
75
|
+
cached: false,
|
|
76
|
+
stderr_tail: `blocked by failed dependency: ${gate.deps.filter((dep) => failed.has(dep)).join(', ')}`
|
|
77
|
+
};
|
|
78
|
+
failed.set(gate.id, result);
|
|
79
|
+
appendReleaseGateJsonl(timeline, { event: 'blocked_by_failed_dependency', gate_id: gate.id, deps: gate.deps.filter((dep) => failed.has(dep)), at: new Date().toISOString() });
|
|
80
|
+
}
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (progressed)
|
|
84
|
+
continue;
|
|
85
|
+
const blocked = [...pending.keys()];
|
|
86
|
+
throw new Error(`release gate DAG stalled: ${blocked.join(', ')}`);
|
|
87
|
+
}
|
|
88
|
+
const result = await Promise.race([...running.values()].map((row) => row.promise));
|
|
89
|
+
running.delete(result.id);
|
|
90
|
+
sumGateMs += result.duration_ms;
|
|
91
|
+
if (result.ok) {
|
|
92
|
+
completed.set(result.id, result);
|
|
93
|
+
const gate = selected.find((row) => row.id === result.id);
|
|
94
|
+
if (gate?.cache.enabled && !input.noCache)
|
|
95
|
+
writeReleaseGateCacheHit(root, gate);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
failed.set(result.id, result);
|
|
99
|
+
if (input.failFast) {
|
|
100
|
+
for (const id of [...pending.keys()])
|
|
101
|
+
pending.delete(id);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
appendReleaseGateJsonl(timeline, { event: result.ok ? 'pass' : 'fail', gate_id: result.id, duration_ms: result.duration_ms, at: new Date().toISOString() });
|
|
105
|
+
}
|
|
106
|
+
const wallMs = Date.now() - started;
|
|
107
|
+
const failures = [...failed.values()].map((row) => ({ id: row.id, exit_code: row.exit_code, stderr_tail: row.stderr_tail }));
|
|
108
|
+
const result = {
|
|
109
|
+
schema: 'sks.release-gate-dag-run.v1',
|
|
110
|
+
ok: failures.length === 0,
|
|
111
|
+
run_id: runId,
|
|
112
|
+
selected_preset: preset,
|
|
113
|
+
total_gates: manifest.gates.length,
|
|
114
|
+
selected_gates: selected.length,
|
|
115
|
+
completed: completed.size,
|
|
116
|
+
failed: failed.size,
|
|
117
|
+
cached,
|
|
118
|
+
wall_ms: wallMs,
|
|
119
|
+
sum_gate_ms: sumGateMs,
|
|
120
|
+
cpu_time_saved_ms: Math.max(0, sumGateMs - wallMs),
|
|
121
|
+
parallelism_gain: wallMs > 0 ? Number((sumGateMs / wallMs).toFixed(2)) : 1,
|
|
122
|
+
critical_path_ms: estimateCriticalPath(selected, completed),
|
|
123
|
+
peak_running: peakRunning,
|
|
124
|
+
peak_resources: peakResources,
|
|
125
|
+
budget_snapshot: budget,
|
|
126
|
+
budget_summary: summarizeReleaseGateBudget(budget),
|
|
127
|
+
report_dir: reportDir,
|
|
128
|
+
failures
|
|
129
|
+
};
|
|
130
|
+
writeReleaseGateJson(path.join(reportDir, 'summary.json'), result);
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
133
|
+
function selectPreset(manifest, preset) {
|
|
134
|
+
return manifest.gates.filter((gate) => gate.preset.includes(preset));
|
|
135
|
+
}
|
|
136
|
+
function runGate(root, runId, reportRoot, gate) {
|
|
137
|
+
const started = Date.now();
|
|
138
|
+
const hermetic = createReleaseGateHermeticEnv({ root, runId, gate, reportRoot });
|
|
139
|
+
const stdoutFile = path.join(hermetic.report_dir, 'stdout.log');
|
|
140
|
+
const stderrFile = path.join(hermetic.report_dir, 'stderr.log');
|
|
141
|
+
const out = fs.createWriteStream(stdoutFile);
|
|
142
|
+
const err = fs.createWriteStream(stderrFile);
|
|
143
|
+
return new Promise((resolve) => {
|
|
144
|
+
const child = spawn(gate.command, { cwd: root, shell: true, env: hermetic.env, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
145
|
+
const timer = setTimeout(() => child.kill('SIGTERM'), gate.timeout_ms);
|
|
146
|
+
child.stdout.pipe(out);
|
|
147
|
+
child.stderr.pipe(err);
|
|
148
|
+
child.on('close', (code) => {
|
|
149
|
+
clearTimeout(timer);
|
|
150
|
+
out.end();
|
|
151
|
+
err.end();
|
|
152
|
+
const durationMs = Date.now() - started;
|
|
153
|
+
const stderrTail = tail(fs.existsSync(stderrFile) ? fs.readFileSync(stderrFile, 'utf8') : '');
|
|
154
|
+
const result = { id: gate.id, ok: code === 0, exit_code: code, duration_ms: durationMs, cached: false, stderr_tail: stderrTail };
|
|
155
|
+
writeReleaseGateJson(path.join(hermetic.report_dir, 'result.json'), { schema: 'sks.release-gate-result.v1', ...result, stdout_log: stdoutFile, stderr_log: stderrFile });
|
|
156
|
+
resolve(result);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
function estimateCriticalPath(gates, completed) {
|
|
161
|
+
const byId = new Map(gates.map((gate) => [gate.id, gate]));
|
|
162
|
+
const memo = new Map();
|
|
163
|
+
const visit = (id) => {
|
|
164
|
+
if (memo.has(id))
|
|
165
|
+
return memo.get(id);
|
|
166
|
+
const gate = byId.get(id);
|
|
167
|
+
if (!gate)
|
|
168
|
+
return 0;
|
|
169
|
+
const own = completed.get(id)?.duration_ms || 0;
|
|
170
|
+
const dep = Math.max(0, ...gate.deps.map(visit));
|
|
171
|
+
memo.set(id, own + dep);
|
|
172
|
+
return own + dep;
|
|
173
|
+
};
|
|
174
|
+
return Math.max(0, ...gates.map((gate) => visit(gate.id)));
|
|
175
|
+
}
|
|
176
|
+
function tail(value, limit = 1200) {
|
|
177
|
+
return value.length > limit ? value.slice(-limit) : value;
|
|
178
|
+
}
|
|
179
|
+
//# sourceMappingURL=release-gate-dag.js.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
export function createReleaseGateHermeticEnv(input) {
|
|
5
|
+
const safeId = input.gate.id.replace(/[^a-zA-Z0-9._-]+/g, '-');
|
|
6
|
+
const tmpRoot = path.join(os.tmpdir(), 'sks-gate', input.runId, safeId);
|
|
7
|
+
const home = path.join(tmpRoot, 'home');
|
|
8
|
+
const codexHome = path.join(tmpRoot, 'codex-home');
|
|
9
|
+
const cacheHome = path.join(tmpRoot, 'xdg-cache');
|
|
10
|
+
const reportDir = path.join(input.reportRoot, safeId);
|
|
11
|
+
fs.mkdirSync(home, { recursive: true });
|
|
12
|
+
fs.mkdirSync(codexHome, { recursive: true });
|
|
13
|
+
fs.mkdirSync(cacheHome, { recursive: true });
|
|
14
|
+
fs.mkdirSync(reportDir, { recursive: true });
|
|
15
|
+
return {
|
|
16
|
+
tmp_dir: tmpRoot,
|
|
17
|
+
report_dir: reportDir,
|
|
18
|
+
env: {
|
|
19
|
+
...process.env,
|
|
20
|
+
SKS_GATE_ID: input.gate.id,
|
|
21
|
+
SKS_GATE_RUN_ID: input.runId,
|
|
22
|
+
SKS_REPORT_DIR: reportDir,
|
|
23
|
+
SKS_TMP_DIR: tmpRoot,
|
|
24
|
+
HOME: input.gate.isolation.home === 'temp' ? home : process.env.HOME,
|
|
25
|
+
CODEX_HOME: input.gate.isolation.codex_home === 'temp' ? codexHome : process.env.CODEX_HOME,
|
|
26
|
+
XDG_CACHE_HOME: cacheHome,
|
|
27
|
+
SKS_DISABLE_REAL_MODEL_CALLS: input.gate.preset.includes('real-check') ? process.env.SKS_DISABLE_REAL_MODEL_CALLS || '0' : '1',
|
|
28
|
+
SKS_DISABLE_GLOBAL_CONFIG_MUTATION: '1'
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=release-gate-hermetic-env.js.map
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export const RELEASE_GATE_NODE_SCHEMA = 'sks.release-gates.v2';
|
|
2
|
+
export const RELEASE_GATE_RESOURCE_CLASSES = [
|
|
3
|
+
'cpu-light',
|
|
4
|
+
'cpu-heavy',
|
|
5
|
+
'io-light',
|
|
6
|
+
'io-heavy',
|
|
7
|
+
'git',
|
|
8
|
+
'git-worktree',
|
|
9
|
+
'zellij-real',
|
|
10
|
+
'local-llm-real',
|
|
11
|
+
'remote-model-real',
|
|
12
|
+
'python',
|
|
13
|
+
'network',
|
|
14
|
+
'global-config',
|
|
15
|
+
'publish',
|
|
16
|
+
'fs-read'
|
|
17
|
+
];
|
|
18
|
+
export function validateReleaseGateManifest(input) {
|
|
19
|
+
const errors = [];
|
|
20
|
+
if (input?.schema !== RELEASE_GATE_NODE_SCHEMA)
|
|
21
|
+
errors.push('schema_mismatch');
|
|
22
|
+
if (!Array.isArray(input?.gates))
|
|
23
|
+
errors.push('gates_missing');
|
|
24
|
+
const ids = new Set();
|
|
25
|
+
const resources = new Set(RELEASE_GATE_RESOURCE_CLASSES);
|
|
26
|
+
for (const gate of Array.isArray(input?.gates) ? input.gates : []) {
|
|
27
|
+
if (!gate?.id)
|
|
28
|
+
errors.push('gate_id_missing');
|
|
29
|
+
if (gate?.id && ids.has(gate.id))
|
|
30
|
+
errors.push(`gate_duplicate:${gate.id}`);
|
|
31
|
+
if (gate?.id)
|
|
32
|
+
ids.add(gate.id);
|
|
33
|
+
if (!gate?.command)
|
|
34
|
+
errors.push(`gate_command_missing:${gate?.id || 'unknown'}`);
|
|
35
|
+
if (!Array.isArray(gate?.deps))
|
|
36
|
+
errors.push(`gate_deps_missing:${gate?.id || 'unknown'}`);
|
|
37
|
+
if (!Array.isArray(gate?.resource) || !gate.resource.length)
|
|
38
|
+
errors.push(`gate_resource_missing:${gate?.id || 'unknown'}`);
|
|
39
|
+
for (const resource of Array.isArray(gate?.resource) ? gate.resource : []) {
|
|
40
|
+
if (!resources.has(resource))
|
|
41
|
+
errors.push(`gate_unknown_resource:${gate?.id || 'unknown'}:${resource}`);
|
|
42
|
+
}
|
|
43
|
+
if (gate?.side_effect !== 'hermetic' && gate?.side_effect !== 'real-env')
|
|
44
|
+
errors.push(`gate_side_effect_invalid:${gate?.id || 'unknown'}`);
|
|
45
|
+
if (!Number.isFinite(Number(gate?.timeout_ms)) || Number(gate.timeout_ms) <= 0)
|
|
46
|
+
errors.push(`gate_timeout_missing:${gate?.id || 'unknown'}`);
|
|
47
|
+
if (!gate?.cache || typeof gate.cache.enabled !== 'boolean' || !Array.isArray(gate.cache.inputs))
|
|
48
|
+
errors.push(`gate_cache_missing:${gate?.id || 'unknown'}`);
|
|
49
|
+
if (!gate?.isolation || gate.isolation.report_dir !== 'per-gate')
|
|
50
|
+
errors.push(`gate_isolation_missing:${gate?.id || 'unknown'}`);
|
|
51
|
+
if (!Array.isArray(gate?.preset))
|
|
52
|
+
errors.push(`gate_preset_missing:${gate?.id || 'unknown'}`);
|
|
53
|
+
}
|
|
54
|
+
for (const gate of Array.isArray(input?.gates) ? input.gates : []) {
|
|
55
|
+
for (const dep of Array.isArray(gate?.deps) ? gate.deps : []) {
|
|
56
|
+
if (!ids.has(dep))
|
|
57
|
+
errors.push(`gate_unknown_dep:${gate.id}:${dep}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return errors.length ? { ok: false, errors } : { ok: true, manifest: input, errors };
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=release-gate-node.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
export function writeReleaseGateJson(file, value) {
|
|
4
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
5
|
+
fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`);
|
|
6
|
+
}
|
|
7
|
+
export function appendReleaseGateJsonl(file, value) {
|
|
8
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
9
|
+
fs.appendFileSync(file, `${JSON.stringify(value)}\n`);
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=release-gate-report.js.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
export function defaultReleaseGateBudget() {
|
|
3
|
+
const cores = Math.max(1, os.cpus().length || 1);
|
|
4
|
+
return {
|
|
5
|
+
'cpu-light': Math.min(32, cores * 4),
|
|
6
|
+
'cpu-heavy': Math.max(1, cores - 1),
|
|
7
|
+
'io-light': Math.min(64, cores * 8),
|
|
8
|
+
'io-heavy': Math.min(8, cores),
|
|
9
|
+
git: Math.min(8, cores),
|
|
10
|
+
'git-worktree': Math.min(6, cores),
|
|
11
|
+
python: Math.min(8, cores),
|
|
12
|
+
network: 8,
|
|
13
|
+
'zellij-real': 1,
|
|
14
|
+
'local-llm-real': Math.max(1, Number(process.env.SKS_LOCAL_LLM_MAX_PARALLEL || 1)),
|
|
15
|
+
'remote-model-real': 4,
|
|
16
|
+
'global-config': 1,
|
|
17
|
+
publish: 1,
|
|
18
|
+
'fs-read': Math.min(64, cores * 8)
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export function summarizeReleaseGateBudget(budget = defaultReleaseGateBudget()) {
|
|
22
|
+
return Object.entries(budget)
|
|
23
|
+
.filter(([, value]) => Number(value) > 0)
|
|
24
|
+
.map(([resource, value]) => `${resource}=${value}`)
|
|
25
|
+
.join(' ');
|
|
26
|
+
}
|
|
27
|
+
export function countReleaseGateResources(running) {
|
|
28
|
+
return usedResources(running);
|
|
29
|
+
}
|
|
30
|
+
export function pickLaunchableReleaseGates(input) {
|
|
31
|
+
const budget = input.budget || defaultReleaseGateBudget();
|
|
32
|
+
const used = usedResources(input.running);
|
|
33
|
+
const launchable = [];
|
|
34
|
+
for (const gate of input.ready) {
|
|
35
|
+
if (fits(gate, used, budget)) {
|
|
36
|
+
launchable.push(gate);
|
|
37
|
+
for (const resource of gate.resource)
|
|
38
|
+
used[resource] = (used[resource] || 0) + 1;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return launchable;
|
|
42
|
+
}
|
|
43
|
+
function usedResources(running) {
|
|
44
|
+
const used = {};
|
|
45
|
+
for (const gate of running) {
|
|
46
|
+
for (const resource of gate.resource)
|
|
47
|
+
used[resource] = (used[resource] || 0) + 1;
|
|
48
|
+
}
|
|
49
|
+
return used;
|
|
50
|
+
}
|
|
51
|
+
function fits(gate, used, budget) {
|
|
52
|
+
return gate.resource.every((resource) => (used[resource] || 0) < budget[resource]);
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=release-gate-resource-governor.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { defaultReleaseGateBudget, pickLaunchableReleaseGates } from './release-gate-resource-governor.js';
|
|
2
|
+
export function findReadyReleaseGateNodes(input) {
|
|
3
|
+
return [...input.pending.values()].filter((gate) => gate.deps.every((dep) => input.completed.has(dep)) && !gate.deps.some((dep) => input.failed.has(dep)));
|
|
4
|
+
}
|
|
5
|
+
export function findReleaseGatesBlockedByFailedDeps(input) {
|
|
6
|
+
return [...input.pending.values()].filter((gate) => gate.deps.some((dep) => input.failed.has(dep)));
|
|
7
|
+
}
|
|
8
|
+
export function pickReadyLaunchableReleaseGates(input) {
|
|
9
|
+
return pickLaunchableReleaseGates({
|
|
10
|
+
ready: input.ready,
|
|
11
|
+
running: input.running,
|
|
12
|
+
budget: defaultReleaseGateBudget()
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=release-gate-scheduler.js.map
|
package/dist/core/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const PACKAGE_VERSION = '2.0.
|
|
1
|
+
export const PACKAGE_VERSION = '2.0.8';
|
|
2
2
|
//# sourceMappingURL=version.js.map
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { ensureDir, nowIso, writeJsonAtomic } from '../fsx.js';
|
|
3
|
+
import { runZellij } from './zellij-command.js';
|
|
4
|
+
import { extractZellijPaneIdFromOutput } from './zellij-lane-runtime.js';
|
|
5
|
+
import { buildZellijDashboardSnapshot } from './zellij-dashboard-renderer.js';
|
|
6
|
+
export const ZELLIJ_DASHBOARD_PANE_SCHEMA = 'sks.zellij-dashboard-pane.v1';
|
|
7
|
+
export async function openZellijDashboardPane(input) {
|
|
8
|
+
const root = path.resolve(input.root);
|
|
9
|
+
const cwd = input.cwd || root;
|
|
10
|
+
const paneTitle = `dashboard · ${input.missionId}`;
|
|
11
|
+
const dashboardDir = path.join(root, '.sneakoscope', 'missions', input.missionId);
|
|
12
|
+
await ensureDir(dashboardDir);
|
|
13
|
+
const snapshot = buildZellijDashboardSnapshot({ ...(input.snapshot || {}), mission_id: input.missionId });
|
|
14
|
+
const snapshotPath = path.join(dashboardDir, 'zellij-dashboard-snapshot.json');
|
|
15
|
+
await writeJsonAtomic(snapshotPath, snapshot);
|
|
16
|
+
const watchScript = path.join(root, 'dist', 'scripts', 'zellij-dashboard-watch.js');
|
|
17
|
+
const command = `${shellQuote(process.execPath)} ${shellQuote(watchScript)} --snapshot ${shellQuote(snapshotPath)} --interval-ms 1000`;
|
|
18
|
+
const createSession = await runZellij(['attach', '--create-background', input.sessionName], { cwd, timeoutMs: 5000, optional: true });
|
|
19
|
+
const launch = await runZellij(['--session', input.sessionName, 'action', 'new-pane', '--direction', 'right', '--name', paneTitle, '--', 'sh', '-lc', command], {
|
|
20
|
+
cwd,
|
|
21
|
+
timeoutMs: 5000,
|
|
22
|
+
optional: false
|
|
23
|
+
});
|
|
24
|
+
const stdoutPaneId = launch.ok ? extractZellijPaneIdFromOutput(launch.stdout_tail) : null;
|
|
25
|
+
const listed = await runZellij(['--session', input.sessionName, 'action', 'list-panes', '--json', '--all'], { cwd, timeoutMs: 5000, optional: true });
|
|
26
|
+
const rows = parseRows(listed.stdout_tail);
|
|
27
|
+
const pane = stdoutPaneId ? null : rows.find((row) => String(row.title || row.name || '').includes(paneTitle));
|
|
28
|
+
const paneId = stdoutPaneId || pane?.pane_id || pane?.paneId || pane?.id || null;
|
|
29
|
+
const dump = await runZellij(['--session', input.sessionName, 'action', 'dump-screen'], { cwd, timeoutMs: 5000, optional: true });
|
|
30
|
+
const source = stdoutPaneId ? 'zellij_dashboard_new_pane_stdout' : paneId ? 'zellij_dashboard_list_panes' : 'zellij_dashboard_missing';
|
|
31
|
+
const blockers = [
|
|
32
|
+
...(createSession.ok || /Session already exists/i.test(createSession.stderr_tail || '') ? [] : createSession.blockers.map((blocker) => `zellij_dashboard_session_${blocker}`)),
|
|
33
|
+
...(launch.ok ? [] : launch.blockers.map((blocker) => `zellij_dashboard_pane_${blocker}`)),
|
|
34
|
+
...(paneId ? [] : ['zellij_dashboard_pane_id_missing'])
|
|
35
|
+
];
|
|
36
|
+
const record = {
|
|
37
|
+
schema: ZELLIJ_DASHBOARD_PANE_SCHEMA,
|
|
38
|
+
generated_at: nowIso(),
|
|
39
|
+
ok: blockers.length === 0,
|
|
40
|
+
mission_id: input.missionId,
|
|
41
|
+
session_name: input.sessionName,
|
|
42
|
+
pane_title: paneTitle,
|
|
43
|
+
pane_id: paneId == null ? null : String(paneId),
|
|
44
|
+
pane_id_source: source,
|
|
45
|
+
pane_kind: 'dashboard',
|
|
46
|
+
worker_pane: false,
|
|
47
|
+
command,
|
|
48
|
+
launch,
|
|
49
|
+
list_panes: listed,
|
|
50
|
+
dump_screen: dump,
|
|
51
|
+
snapshot,
|
|
52
|
+
blockers
|
|
53
|
+
};
|
|
54
|
+
await writeJsonAtomic(path.join(dashboardDir, 'zellij-dashboard-pane.json'), record);
|
|
55
|
+
return record;
|
|
56
|
+
}
|
|
57
|
+
function parseRows(text) {
|
|
58
|
+
if (!String(text || '').trim())
|
|
59
|
+
return [];
|
|
60
|
+
try {
|
|
61
|
+
const parsed = JSON.parse(String(text));
|
|
62
|
+
return Array.isArray(parsed) ? parsed : Array.isArray(parsed?.panes) ? parsed.panes : [];
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function shellQuote(value) {
|
|
69
|
+
return `'${String(value).replace(/'/g, `'\\''`)}'`;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=zellij-dashboard-pane.js.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { nowIso } from '../fsx.js';
|
|
2
|
+
export const ZELLIJ_DASHBOARD_RENDER_SCHEMA = 'sks.zellij-dashboard-render.v1';
|
|
3
|
+
export function buildZellijDashboardSnapshot(input) {
|
|
4
|
+
return {
|
|
5
|
+
schema: ZELLIJ_DASHBOARD_RENDER_SCHEMA,
|
|
6
|
+
generated_at: nowIso(),
|
|
7
|
+
mission_id: input.mission_id,
|
|
8
|
+
mode: input.mode || 'naruto',
|
|
9
|
+
backend_counts: input.backend_counts || { 'codex-sdk': 1 },
|
|
10
|
+
placement_counts: input.placement_counts || { 'zellij-pane': 1 },
|
|
11
|
+
active_workers: Number(input.active_workers || 0),
|
|
12
|
+
visible_panes: Number(input.visible_panes || 0),
|
|
13
|
+
headless_workers: Number(input.headless_workers || 0),
|
|
14
|
+
queue_depth: Number(input.queue_depth || 0),
|
|
15
|
+
worktrees: input.worktrees || { active: 0, completed: 0, retained: 0 },
|
|
16
|
+
local_llm: input.local_llm || { tps: 0, queue: 0 },
|
|
17
|
+
gpt_final_status: input.gpt_final_status || 'not_started',
|
|
18
|
+
gate_progress: input.gate_progress || 'not_release',
|
|
19
|
+
latest_blockers: input.latest_blockers || []
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export function renderZellijDashboardText(snapshot) {
|
|
23
|
+
const backendCounts = Object.entries(snapshot.backend_counts).map(([key, value]) => `${key}=${value}`).join(' ');
|
|
24
|
+
const placementCounts = Object.entries(snapshot.placement_counts).map(([key, value]) => `${key}=${value}`).join(' ');
|
|
25
|
+
return [
|
|
26
|
+
'SKS Dashboard',
|
|
27
|
+
`Mission: ${snapshot.mission_id}`,
|
|
28
|
+
`Mode: ${snapshot.mode}`,
|
|
29
|
+
`Backend counts: ${backendCounts || 'none'}`,
|
|
30
|
+
`Placement counts: ${placementCounts || 'none'}`,
|
|
31
|
+
`Active workers: ${snapshot.active_workers}`,
|
|
32
|
+
`Visible panes: ${snapshot.visible_panes}`,
|
|
33
|
+
`Headless workers: ${snapshot.headless_workers}`,
|
|
34
|
+
`Queue depth: ${snapshot.queue_depth}`,
|
|
35
|
+
`Worktrees active/completed/retained: ${snapshot.worktrees.active}/${snapshot.worktrees.completed}/${snapshot.worktrees.retained}`,
|
|
36
|
+
`Local LLM TPS / queue: ${snapshot.local_llm.tps}/${snapshot.local_llm.queue}`,
|
|
37
|
+
`GPT final status: ${snapshot.gpt_final_status}`,
|
|
38
|
+
`Gate progress: ${snapshot.gate_progress}`,
|
|
39
|
+
`Latest blockers: ${snapshot.latest_blockers.length ? snapshot.latest_blockers.join(', ') : 'none'}`
|
|
40
|
+
].join('\n');
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=zellij-dashboard-renderer.js.map
|
|
@@ -4,11 +4,18 @@ export function planNarutoZellijDashboard(input) {
|
|
|
4
4
|
const visibleWorkerPanes = Math.min(targetActiveWorkers, visiblePaneCap);
|
|
5
5
|
const headlessWorkers = Math.max(0, targetActiveWorkers - visibleWorkerPanes);
|
|
6
6
|
const backend = input.backend || 'codex-sdk';
|
|
7
|
+
const worktreeMode = input.worktreePolicy?.mode || 'patch-envelope-only';
|
|
7
8
|
const roles = input.roles && input.roles.length ? input.roles : ['implementer', 'modifier', 'verifier', 'test_writer'];
|
|
9
|
+
const worktreeLabels = [];
|
|
8
10
|
const paneTitles = Array.from({ length: visibleWorkerPanes }, (_, index) => {
|
|
9
11
|
const slot = `slot-${String(index + 1).padStart(3, '0')}`;
|
|
10
12
|
const role = roles[index % roles.length] || 'worker';
|
|
11
|
-
|
|
13
|
+
if (worktreeMode !== 'git-worktree')
|
|
14
|
+
return `${slot}/gen-1 · ${role} · ${backend} · active`;
|
|
15
|
+
const wt = `WT:${slot}-gen-1`;
|
|
16
|
+
const branch = `branch:${slot}/gen-1`;
|
|
17
|
+
worktreeLabels.push(`${wt} ${branch}`);
|
|
18
|
+
return `${slot}/gen-1 · ${wt} · ${branch} · ${role} · ${backend} · active`;
|
|
12
19
|
});
|
|
13
20
|
const blockers = [
|
|
14
21
|
...(visibleWorkerPanes > visiblePaneCap ? ['naruto_zellij_visible_panes_exceed_cap'] : []),
|
|
@@ -29,6 +36,8 @@ export function planNarutoZellijDashboard(input) {
|
|
|
29
36
|
backpressure: input.backpressure || 'normal'
|
|
30
37
|
},
|
|
31
38
|
pane_titles: paneTitles,
|
|
39
|
+
worktree_mode: worktreeMode,
|
|
40
|
+
worktree_labels: worktreeLabels,
|
|
32
41
|
ok: blockers.length === 0,
|
|
33
42
|
blockers
|
|
34
43
|
};
|
|
@@ -9,10 +9,11 @@ export const ZELLIJ_WORKER_PANE_EVENT_SCHEMA = 'sks.zellij-worker-pane-event.v1'
|
|
|
9
9
|
export function buildWorkerPaneName(slotId, generationIndex) {
|
|
10
10
|
return `${slotId}/gen-${Math.max(1, Math.floor(Number(generationIndex) || 1))}`;
|
|
11
11
|
}
|
|
12
|
-
export function buildWorkerPaneTitle(slotId, generationIndex, context, serviceTier, backend, status) {
|
|
12
|
+
export function buildWorkerPaneTitle(slotId, generationIndex, context, serviceTier, backend, status, worktree) {
|
|
13
13
|
const base = buildWorkerPaneName(slotId, generationIndex);
|
|
14
14
|
const normalized = normalizePaneProviderContext(context, serviceTier);
|
|
15
|
-
|
|
15
|
+
const wt = worktree ? ` · WT:${worktree.id} · branch:${worktree.branch}` : '';
|
|
16
|
+
return `${base}${wt} · ${backend || 'codex-sdk'} · ${providerPaneLabel(normalized)} · ${status || 'launching'}`;
|
|
16
17
|
}
|
|
17
18
|
export function isRealZellijWorkerPaneIdSource(value) {
|
|
18
19
|
return value === 'zellij_worker_new_pane_stdout' || value === 'zellij_worker_list_panes';
|
|
@@ -22,7 +23,7 @@ export function buildWorkerPaneArtifact(input) {
|
|
|
22
23
|
const paneIdSource = input.paneIdSource || 'zellij_worker_pane_launch_failed';
|
|
23
24
|
const blockers = input.blockers || [];
|
|
24
25
|
const providerContext = normalizePaneProviderContext(input.providerContext, input.serviceTier);
|
|
25
|
-
const paneTitle = buildWorkerPaneTitle(input.slotId, input.generationIndex, providerContext, input.serviceTier, input.backend, input.
|
|
26
|
+
const paneTitle = buildWorkerPaneTitle(input.slotId, input.generationIndex, providerContext, input.serviceTier, input.backend, input.statusLabel || input.status, input.worktree || null);
|
|
26
27
|
return {
|
|
27
28
|
schema: ZELLIJ_WORKER_PANE_SCHEMA,
|
|
28
29
|
generated_at: now,
|
|
@@ -42,6 +43,7 @@ export function buildWorkerPaneArtifact(input) {
|
|
|
42
43
|
provider: providerContext.provider,
|
|
43
44
|
service_tier: providerContext.service_tier,
|
|
44
45
|
provider_context: providerContext,
|
|
46
|
+
worktree: input.worktree || null,
|
|
45
47
|
worker_artifact_dir: input.workerArtifactDir,
|
|
46
48
|
worker_result_path: input.resultPath,
|
|
47
49
|
heartbeat_path: input.heartbeatPath,
|
|
@@ -77,12 +79,13 @@ export async function openWorkerPane(input) {
|
|
|
77
79
|
const workerDir = path.join(root, input.workerArtifactDir);
|
|
78
80
|
await ensureDir(workerDir);
|
|
79
81
|
await appendWorkerPaneEvent(root, 'session_launch_started', input, {});
|
|
80
|
-
const
|
|
82
|
+
const createSessionRaw = await runZellij(['attach', '--create-background', input.sessionName], {
|
|
81
83
|
cwd,
|
|
82
84
|
timeoutMs: 5000,
|
|
83
85
|
optional: false
|
|
84
86
|
});
|
|
85
|
-
const
|
|
87
|
+
const createSession = normalizeExistingZellijSession(input.sessionName, createSessionRaw);
|
|
88
|
+
const paneName = buildWorkerPaneTitle(input.slotId, input.generationIndex, providerContext, input.serviceTier, input.backend, input.statusLabel || 'running', input.worktree || null);
|
|
86
89
|
let launch = createSession.ok
|
|
87
90
|
? await runZellij(['--session', input.sessionName, 'action', 'new-pane', '--direction', 'right', '--name', paneName, '--', 'sh', '-lc', input.workerCommand], {
|
|
88
91
|
cwd,
|
|
@@ -105,6 +108,7 @@ export async function openWorkerPane(input) {
|
|
|
105
108
|
const stdoutPaneId = launch?.ok ? extractZellijPaneIdFromOutput(launch.stdout_tail) : null;
|
|
106
109
|
const reconciledPane = stdoutPaneId ? null : launch?.ok ? await reconcileZellijWorkerPaneId(input.sessionName, paneName, path.join(root, input.resultPath), cwd) : null;
|
|
107
110
|
const paneId = stdoutPaneId || reconciledPane?.pane_id || null;
|
|
111
|
+
const renamePane = paneId ? await renameZellijPaneById(input.sessionName, paneId, paneName, cwd) : null;
|
|
108
112
|
const paneIdSource = stdoutPaneId
|
|
109
113
|
? 'zellij_worker_new_pane_stdout'
|
|
110
114
|
: reconciledPane?.pane_id
|
|
@@ -123,7 +127,7 @@ export async function openWorkerPane(input) {
|
|
|
123
127
|
paneIdSource,
|
|
124
128
|
createSession,
|
|
125
129
|
launch,
|
|
126
|
-
paneReconciliation: reconciledPane,
|
|
130
|
+
paneReconciliation: { ...(reconciledPane || {}), rename_pane: renamePane },
|
|
127
131
|
directionApplied,
|
|
128
132
|
status: blockers.length ? 'failed' : 'running',
|
|
129
133
|
providerContext,
|
|
@@ -260,6 +264,11 @@ async function reconcileZellijWorkerPaneId(sessionName, paneName, resultPath, cw
|
|
|
260
264
|
timeoutMs: 5000,
|
|
261
265
|
optional: true
|
|
262
266
|
});
|
|
267
|
+
const screen = await runZellij(['--session', sessionName, 'action', 'dump-screen'], {
|
|
268
|
+
cwd,
|
|
269
|
+
timeoutMs: 5000,
|
|
270
|
+
optional: true
|
|
271
|
+
});
|
|
263
272
|
const rows = parsePaneRows(listed.stdout_tail);
|
|
264
273
|
const pane = rows.find((row) => {
|
|
265
274
|
const title = String(row.title || row.name || row.pane_name || '');
|
|
@@ -278,6 +287,7 @@ async function reconcileZellijWorkerPaneId(sessionName, paneName, resultPath, cw
|
|
|
278
287
|
pane_id: paneId == null ? null : String(paneId),
|
|
279
288
|
listed_count: rows.length,
|
|
280
289
|
command: listed,
|
|
290
|
+
dump_screen: screen,
|
|
281
291
|
blockers: paneId == null ? ['zellij_worker_pane_id_not_reconciled'] : []
|
|
282
292
|
};
|
|
283
293
|
}
|
|
@@ -328,4 +338,56 @@ function parsePaneRows(text) {
|
|
|
328
338
|
return [];
|
|
329
339
|
}
|
|
330
340
|
}
|
|
341
|
+
async function renameZellijPaneById(sessionName, paneId, paneName, cwd) {
|
|
342
|
+
const numeric = Number.parseInt(paneId.replace(/^terminal_/, ''), 10);
|
|
343
|
+
const numericCandidates = Number.isFinite(numeric)
|
|
344
|
+
? [numeric, numeric + 1, numeric - 1].filter((value) => value >= 0).map(String)
|
|
345
|
+
: [];
|
|
346
|
+
const candidates = [...new Set([paneId, paneId.replace(/^terminal_/, ''), ...numericCandidates].filter(Boolean))];
|
|
347
|
+
let last = null;
|
|
348
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
349
|
+
if (attempt > 0)
|
|
350
|
+
await sleep(200 * attempt);
|
|
351
|
+
for (const candidate of candidates) {
|
|
352
|
+
last = await runZellij(['--session', sessionName, 'action', 'rename-pane', '--pane-id', candidate, paneName], {
|
|
353
|
+
cwd,
|
|
354
|
+
timeoutMs: 5000,
|
|
355
|
+
optional: true
|
|
356
|
+
});
|
|
357
|
+
if (last.ok)
|
|
358
|
+
return last;
|
|
359
|
+
const focus = await runZellij(['--session', sessionName, 'action', 'focus-pane-id', candidate], {
|
|
360
|
+
cwd,
|
|
361
|
+
timeoutMs: 5000,
|
|
362
|
+
optional: true
|
|
363
|
+
});
|
|
364
|
+
if (focus.ok) {
|
|
365
|
+
last = await runZellij(['--session', sessionName, 'action', 'rename-pane', paneName], {
|
|
366
|
+
cwd,
|
|
367
|
+
timeoutMs: 5000,
|
|
368
|
+
optional: true
|
|
369
|
+
});
|
|
370
|
+
if (last.ok)
|
|
371
|
+
return last;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return last;
|
|
376
|
+
}
|
|
377
|
+
function sleep(ms) {
|
|
378
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
379
|
+
}
|
|
380
|
+
function normalizeExistingZellijSession(sessionName, result) {
|
|
381
|
+
if (result.ok)
|
|
382
|
+
return result;
|
|
383
|
+
if (/Session already exists/i.test(result.stderr_tail || '')) {
|
|
384
|
+
return {
|
|
385
|
+
...result,
|
|
386
|
+
ok: true,
|
|
387
|
+
blockers: [],
|
|
388
|
+
warnings: [...result.warnings, `zellij_session_already_exists:${sessionName}`]
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
return result;
|
|
392
|
+
}
|
|
331
393
|
//# sourceMappingURL=zellij-worker-pane-manager.js.map
|