sneakoscope 2.0.8 → 2.0.9
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 +1 -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 +20 -8
- package/dist/commands/zellij.js +144 -1
- package/dist/core/agents/agent-orchestrator.js +4 -2
- package/dist/core/agents/native-cli-session-swarm.js +76 -9
- package/dist/core/commands/mad-sks-command.js +17 -26
- package/dist/core/commands/naruto-command.js +99 -36
- package/dist/core/fsx.js +1 -1
- package/dist/core/naruto/naruto-active-pool.js +108 -0
- package/dist/core/naruto/naruto-concurrency-governor.js +16 -1
- package/dist/core/naruto/naruto-work-graph.js +2 -1
- package/dist/core/release/release-gate-cache-v2.js +58 -4
- package/dist/core/release/release-gate-dag.js +36 -25
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/zellij-dashboard-renderer.js +22 -6
- package/dist/core/zellij/zellij-launcher.js +3 -3
- package/dist/core/zellij/zellij-layout-builder.js +1 -1
- package/dist/core/zellij/zellij-right-column-layout-proof.js +42 -0
- package/dist/core/zellij/zellij-right-column-manager.js +245 -0
- package/dist/core/zellij/zellij-worker-pane-manager.js +118 -13
- package/dist/scripts/codex-sdk-release-review-pipeline-check.js +5 -5
- package/dist/scripts/doctor-fix-proves-codex-read-check.js +26 -5
- package/dist/scripts/lib/codex-sdk-gate-lib.js +4 -0
- package/dist/scripts/mad-sks-zellij-default-pane-worker-check.js +2 -2
- package/dist/scripts/naruto-concurrency-governor-check.js +2 -1
- package/dist/scripts/naruto-extreme-parallelism-check.js +22 -0
- package/dist/scripts/naruto-real-active-pool-check.js +38 -0
- package/dist/scripts/naruto-work-graph-check.js +1 -1
- package/dist/scripts/naruto-zellij-dynamic-right-column-check.js +21 -0
- package/dist/scripts/product-design-auto-install-check.js +3 -3
- package/dist/scripts/product-design-plugin-routing-check.js +3 -3
- package/dist/scripts/release-cache-glob-hashing-check.js +42 -0
- package/dist/scripts/release-dag-full-coverage-check.js +35 -0
- package/dist/scripts/release-parallel-speed-budget-check.js +67 -13
- package/dist/scripts/release-readiness-report.js +1 -1
- package/dist/scripts/zellij-dashboard-pane-check.js +6 -4
- package/dist/scripts/zellij-developer-controls-check.js +20 -0
- package/dist/scripts/zellij-dynamic-pane-lifecycle-check.js +21 -0
- package/dist/scripts/zellij-initial-main-only-blackbox.js +28 -0
- package/dist/scripts/zellij-right-column-geometry-proof.js +29 -0
- package/dist/scripts/zellij-right-column-manager-check.js +22 -0
- package/dist/scripts/zellij-worker-pane-manager-check.js +2 -1
- package/dist/scripts/zellij-worker-pane-manager-single-owner-check.js +7 -6
- package/package.json +13 -3
- package/schemas/zellij/zellij-right-column-state.schema.json +41 -0
|
@@ -10,11 +10,10 @@ import { attachZellijSessionInteractive, launchZellijLayout } from '../zellij/ze
|
|
|
10
10
|
import { buildNarutoWorkGraph } from '../naruto/naruto-work-graph.js';
|
|
11
11
|
import { buildNarutoRoleDistribution } from '../naruto/naruto-role-policy.js';
|
|
12
12
|
import { decideNarutoConcurrency } from '../naruto/naruto-concurrency-governor.js';
|
|
13
|
-
import { runNarutoActivePool } from '../naruto/naruto-active-pool.js';
|
|
13
|
+
import { runNarutoActivePool, runNarutoRealActivePool } from '../naruto/naruto-active-pool.js';
|
|
14
14
|
import { buildNarutoVerificationDag } from '../naruto/naruto-verification-dag.js';
|
|
15
15
|
import { buildNarutoGptFinalPack } from '../naruto/naruto-gpt-final-pack.js';
|
|
16
16
|
import { planNarutoZellijDashboard } from '../zellij/zellij-naruto-dashboard.js';
|
|
17
|
-
import { openZellijDashboardPane } from '../zellij/zellij-dashboard-pane.js';
|
|
18
17
|
import { checkPromptPlaceholders } from '../prompt/prompt-placeholder-guard.js';
|
|
19
18
|
import { evaluateGitWorktreeCapability } from '../git/git-worktree-capability.js';
|
|
20
19
|
const NARUTO_RESULT_SCHEMA = 'sks.naruto-command-result.v1';
|
|
@@ -31,6 +30,10 @@ export async function narutoCommand(commandOrArgs = 'naruto', maybeArgs = []) {
|
|
|
31
30
|
return narutoHelp(parsed);
|
|
32
31
|
if (parsed.action === 'status')
|
|
33
32
|
return narutoStatus(parsed);
|
|
33
|
+
if (parsed.action === 'dashboard')
|
|
34
|
+
return narutoDashboard(parsed);
|
|
35
|
+
if (parsed.action === 'workers')
|
|
36
|
+
return narutoWorkers(parsed);
|
|
34
37
|
return narutoRun(parsed);
|
|
35
38
|
}
|
|
36
39
|
async function narutoRun(parsed) {
|
|
@@ -109,6 +112,14 @@ async function narutoRun(parsed) {
|
|
|
109
112
|
const activeSlots = Math.max(1, Math.min(roster.agent_count, parsed.concurrency || Math.max(governor.safe_active_workers, backendMinimum), safe.cap));
|
|
110
113
|
const zellijVisiblePanes = Math.max(1, Math.min(activeSlots, governor.safe_zellij_visible_panes));
|
|
111
114
|
const activePool = await runNarutoActivePool({ graph: workGraph, governor: { ...governor, safe_active_workers: activeSlots } });
|
|
115
|
+
const realActivePool = await runNarutoRealActivePool({
|
|
116
|
+
graph: workGraph,
|
|
117
|
+
governor: { ...governor, safe_active_workers: activeSlots },
|
|
118
|
+
spawnWorker: async (item, placement) => ({ id: item.id, item, placement, started_at: Date.now() }),
|
|
119
|
+
collectWorker: async (handle) => ({ id: handle.id, ok: true, item: handle.item, placement: handle.placement, completed_at: Date.now() }),
|
|
120
|
+
enqueueVerification: async () => undefined,
|
|
121
|
+
updateDashboard: async () => undefined
|
|
122
|
+
});
|
|
112
123
|
const verificationDag = buildNarutoVerificationDag(workGraph, { cwd: root });
|
|
113
124
|
const gptFinalPack = buildNarutoGptFinalPack({
|
|
114
125
|
missionId: mission.id,
|
|
@@ -132,6 +143,7 @@ async function narutoRun(parsed) {
|
|
|
132
143
|
roleDistribution,
|
|
133
144
|
governor,
|
|
134
145
|
activePool,
|
|
146
|
+
realActivePool,
|
|
135
147
|
verificationDag,
|
|
136
148
|
gptFinalPack,
|
|
137
149
|
zellijDashboard,
|
|
@@ -145,40 +157,25 @@ async function narutoRun(parsed) {
|
|
|
145
157
|
missionId: mission.id,
|
|
146
158
|
ledgerRoot,
|
|
147
159
|
kind: 'naruto',
|
|
148
|
-
slotCount:
|
|
160
|
+
slotCount: 0,
|
|
149
161
|
dryRun: false,
|
|
150
162
|
attach: false
|
|
151
163
|
});
|
|
152
164
|
if (liveZellij?.ok && liveZellij.capability?.status === 'ok') {
|
|
153
|
-
liveZellij.dashboard_pane =
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
active: worktreePolicy.mode === 'git-worktree' ? activeSlots : 0,
|
|
168
|
-
completed: 0,
|
|
169
|
-
retained: 0
|
|
170
|
-
},
|
|
171
|
-
local_llm: { tps: 0, queue: localWorker.auto_select_eligible ? Math.max(0, activeSlots - zellijVisiblePanes) : 0 },
|
|
172
|
-
gpt_final_status: 'pending',
|
|
173
|
-
gate_progress: 'naruto:pre-orchestrator'
|
|
174
|
-
}
|
|
175
|
-
}).catch((err) => ({
|
|
176
|
-
ok: false,
|
|
177
|
-
pane_kind: 'dashboard',
|
|
178
|
-
worker_pane: false,
|
|
179
|
-
blockers: [`zellij_dashboard_exception:${err?.message || String(err)}`]
|
|
180
|
-
}));
|
|
181
|
-
console.log('Zellij: prepared ' + zellijVisiblePanes + ' visible active clone lane(s) in ' + liveZellij.session_name + ' with ' + Math.max(0, activeSlots - zellijVisiblePanes) + ' headless active worker(s). Attach with: ' + (liveZellij.attach_command_with_env || liveZellij.attach_command));
|
|
165
|
+
liveZellij.dashboard_pane = null;
|
|
166
|
+
liveZellij.right_column_mode = 'spawn-on-first-worker';
|
|
167
|
+
await writeJsonAtomic(path.join(mission.dir, 'zellij-initial-ui.json'), {
|
|
168
|
+
schema: 'sks.zellij-initial-ui.v1',
|
|
169
|
+
ok: true,
|
|
170
|
+
mission_id: mission.id,
|
|
171
|
+
session_name: liveZellij.session_name,
|
|
172
|
+
initial_panes: 'main-only',
|
|
173
|
+
dashboard_created: false,
|
|
174
|
+
worker_panes_created: 0,
|
|
175
|
+
right_column_mode: 'spawn-on-first-worker',
|
|
176
|
+
visible_pane_cap: zellijVisiblePanes
|
|
177
|
+
});
|
|
178
|
+
console.log('Zellij: started main-only session ' + liveZellij.session_name + '; right column opens on first visible worker spawn. Attach with: ' + (liveZellij.attach_command_with_env || liveZellij.attach_command));
|
|
182
179
|
if (parsed.attach)
|
|
183
180
|
attachZellijSessionInteractive(liveZellij.session_name, { cwd: process.cwd(), configPath: liveZellij.clipboard_config_path });
|
|
184
181
|
}
|
|
@@ -213,6 +210,10 @@ async function narutoRun(parsed) {
|
|
|
213
210
|
mock: parsed.mock,
|
|
214
211
|
real: parsed.real,
|
|
215
212
|
readonly: parsed.readonly,
|
|
213
|
+
zellijSessionName: liveZellij?.session_name || `sks-${mission.id}`,
|
|
214
|
+
workerPlacement: parsed.json || parsed.noOpenZellij ? 'process' : 'zellij-pane',
|
|
215
|
+
zellijPaneWorker: true,
|
|
216
|
+
zellijVisiblePaneCap: zellijVisiblePanes,
|
|
216
217
|
// Shadow clones ALWAYS run in fast service tier — never honor --no-fast/standard.
|
|
217
218
|
fastMode: true,
|
|
218
219
|
serviceTier: 'fast',
|
|
@@ -252,7 +253,13 @@ async function narutoRun(parsed) {
|
|
|
252
253
|
ok: activePool.ok,
|
|
253
254
|
max_observed_active_workers: activePool.max_observed_active_workers,
|
|
254
255
|
refill_events: activePool.refill_events,
|
|
255
|
-
completed_count: activePool.completed_count
|
|
256
|
+
completed_count: activePool.completed_count,
|
|
257
|
+
real_runtime: {
|
|
258
|
+
ok: realActivePool.ok,
|
|
259
|
+
max_observed_active_workers: realActivePool.max_observed_active_workers,
|
|
260
|
+
refill_latency_ms_p95: realActivePool.refill_latency_ms_p95,
|
|
261
|
+
worker_lifecycle: realActivePool.worker_lifecycle
|
|
262
|
+
}
|
|
256
263
|
},
|
|
257
264
|
local_worker: localWorkerSummary,
|
|
258
265
|
proof: result.proof?.status || 'missing',
|
|
@@ -325,6 +332,57 @@ async function narutoStatus(parsed) {
|
|
|
325
332
|
console.log('Roles: ' + roleDistribution.entries.map((entry) => `${entry.role}:${entry.count}`).join(', '));
|
|
326
333
|
});
|
|
327
334
|
}
|
|
335
|
+
async function narutoDashboard(parsed) {
|
|
336
|
+
const root = await sksRoot();
|
|
337
|
+
const id = parsed.missionId && parsed.missionId !== 'latest' ? parsed.missionId : await findLatestMission(root);
|
|
338
|
+
if (!id)
|
|
339
|
+
return emit(parsed, { schema: NARUTO_RESULT_SCHEMA, ok: false, action: 'dashboard', status: 'missing_mission' }, () => console.log('No Naruto mission found.'));
|
|
340
|
+
const { dir } = await loadMission(root, id);
|
|
341
|
+
const snapshot = await readJson(path.join(dir, 'zellij-dashboard-snapshot.json'), null);
|
|
342
|
+
const rightColumnState = await readJson(path.join(dir, 'zellij-right-column-state.json'), null);
|
|
343
|
+
const summary = {
|
|
344
|
+
schema: NARUTO_RESULT_SCHEMA,
|
|
345
|
+
ok: Boolean(snapshot || rightColumnState),
|
|
346
|
+
action: 'dashboard',
|
|
347
|
+
mission_id: id,
|
|
348
|
+
snapshot,
|
|
349
|
+
right_column_state: rightColumnState,
|
|
350
|
+
blockers: snapshot || rightColumnState ? [] : ['naruto_dashboard_missing']
|
|
351
|
+
};
|
|
352
|
+
return emit(parsed, summary, () => {
|
|
353
|
+
console.log('🍥 Naruto dashboard: ' + id);
|
|
354
|
+
console.log('Right column: ' + (rightColumnState?.status || 'missing'));
|
|
355
|
+
if (snapshot)
|
|
356
|
+
console.log('Active/visible/headless/queue: ' + [snapshot.active_workers, snapshot.visible_panes, snapshot.headless_workers, snapshot.queue_depth].join('/'));
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
async function narutoWorkers(parsed) {
|
|
360
|
+
const root = await sksRoot();
|
|
361
|
+
const id = parsed.missionId && parsed.missionId !== 'latest' ? parsed.missionId : await findLatestMission(root);
|
|
362
|
+
if (!id)
|
|
363
|
+
return emit(parsed, { schema: NARUTO_RESULT_SCHEMA, ok: false, action: 'workers', status: 'missing_mission' }, () => console.log('No Naruto mission found.'));
|
|
364
|
+
const { dir } = await loadMission(root, id);
|
|
365
|
+
const swarm = await readJson(path.join(dir, 'agents', 'agent-native-cli-session-swarm.json'), null);
|
|
366
|
+
const state = await readJson(path.join(dir, 'zellij-right-column-state.json'), null);
|
|
367
|
+
const records = Array.isArray(swarm?.records) ? swarm.records : [];
|
|
368
|
+
const summary = {
|
|
369
|
+
schema: NARUTO_RESULT_SCHEMA,
|
|
370
|
+
ok: Boolean(swarm || state),
|
|
371
|
+
action: 'workers',
|
|
372
|
+
mission_id: id,
|
|
373
|
+
active: records.filter((row) => row.status === 'running' || row.status === 'launching').length,
|
|
374
|
+
completed: records.filter((row) => row.status === 'closed').length,
|
|
375
|
+
failed: records.filter((row) => row.status === 'failed').length,
|
|
376
|
+
visible_worker_panes: state?.visible_worker_panes || [],
|
|
377
|
+
headless_workers: state?.headless_workers || [],
|
|
378
|
+
records,
|
|
379
|
+
blockers: swarm || state ? [] : ['naruto_worker_records_missing']
|
|
380
|
+
};
|
|
381
|
+
return emit(parsed, summary, () => {
|
|
382
|
+
console.log('🍥 Naruto workers: ' + id);
|
|
383
|
+
console.log(`Active ${summary.active} · completed ${summary.completed} · failed ${summary.failed} · visible ${summary.visible_worker_panes.length} · headless ${summary.headless_workers.length}`);
|
|
384
|
+
});
|
|
385
|
+
}
|
|
328
386
|
async function narutoHelp(parsed) {
|
|
329
387
|
const help = {
|
|
330
388
|
schema: NARUTO_RESULT_SCHEMA,
|
|
@@ -349,13 +407,13 @@ function parseNarutoArgs(args = []) {
|
|
|
349
407
|
if (hasFlag(args, '--help') || hasFlag(args, '-h'))
|
|
350
408
|
args = ['help', ...args.filter((arg) => arg !== '--help' && arg !== '-h')];
|
|
351
409
|
const first = args[0] && !String(args[0]).startsWith('--') ? String(args[0]) : '';
|
|
352
|
-
const actions = new Set(['run', 'status', 'help']);
|
|
410
|
+
const actions = new Set(['run', 'status', 'help', 'dashboard', 'workers']);
|
|
353
411
|
const action = (actions.has(first) ? first : 'run');
|
|
354
412
|
const rest = action === first ? args.slice(1) : args;
|
|
355
413
|
const json = hasFlag(args, '--json');
|
|
356
414
|
const requestedClones = Number(readOption(args, '--clones', readOption(args, '--agents', DEFAULT_NARUTO_CLONES)));
|
|
357
415
|
const clones = clampClones(requestedClones);
|
|
358
|
-
const workItems = clampWorkItems(Number(readOption(args, '--work-items', clones)), clones);
|
|
416
|
+
const workItems = clampWorkItems(Number(readOption(args, '--work-items', clones * 2)), clones);
|
|
359
417
|
const concurrency = normalizeConcurrency(readOption(args, '--concurrency', readOption(args, '--target-active-slots', null)), clones);
|
|
360
418
|
const useOllama = hasFlag(args, '--ollama') || hasFlag(args, '--local-model');
|
|
361
419
|
const noOllama = hasFlag(args, '--no-ollama') || hasFlag(args, '--no-local-model');
|
|
@@ -366,7 +424,10 @@ function parseNarutoArgs(args = []) {
|
|
|
366
424
|
const readonly = hasFlag(args, '--readonly') || hasFlag(args, '--read-only');
|
|
367
425
|
const writeModeRaw = String(readOption(args, '--write-mode', hasFlag(args, '--parallel-write') ? 'parallel' : '') || '');
|
|
368
426
|
const writeMode = (['proof-safe', 'parallel', 'serial', 'off'].includes(writeModeRaw) ? writeModeRaw : null);
|
|
369
|
-
const
|
|
427
|
+
const positionalMission = action === 'dashboard' || action === 'workers' || action === 'status'
|
|
428
|
+
? positionalArgs(rest, new Set()).find((arg) => /^latest$|^M-/.test(arg))
|
|
429
|
+
: null;
|
|
430
|
+
const missionId = String(readOption(args, '--mission', readOption(args, '--mission-id', positionalMission || 'latest')));
|
|
370
431
|
const ollamaModel = String(readOption(args, '--ollama-model', readOption(args, '--local-model-model', '')) || '') || null;
|
|
371
432
|
const ollamaBaseUrl = String(readOption(args, '--ollama-base-url', readOption(args, '--local-model-base-url', '')) || '') || null;
|
|
372
433
|
const noOpenZellij = hasFlag(args, '--no-open-zellij') || hasFlag(args, '--no-zellij');
|
|
@@ -380,6 +441,8 @@ async function writeNarutoArtifacts(ledgerRoot, artifacts) {
|
|
|
380
441
|
await writeJsonAtomic(path.join(ledgerRoot, 'naruto-role-distribution.json'), artifacts.roleDistribution);
|
|
381
442
|
await writeJsonAtomic(path.join(ledgerRoot, 'naruto-concurrency-governor.json'), artifacts.governor);
|
|
382
443
|
await writeJsonAtomic(path.join(ledgerRoot, 'naruto-active-pool.json'), artifacts.activePool);
|
|
444
|
+
if (artifacts.realActivePool)
|
|
445
|
+
await writeJsonAtomic(path.join(ledgerRoot, 'naruto-real-active-pool.json'), artifacts.realActivePool);
|
|
383
446
|
await writeJsonAtomic(path.join(ledgerRoot, 'naruto-verification-dag.json'), artifacts.verificationDag);
|
|
384
447
|
await writeJsonAtomic(path.join(ledgerRoot, 'naruto-gpt-final-pack.json'), artifacts.gptFinalPack);
|
|
385
448
|
await writeJsonAtomic(path.join(ledgerRoot, 'naruto-zellij-dashboard.json'), artifacts.zellijDashboard);
|
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 = '2.0.
|
|
8
|
+
export const PACKAGE_VERSION = '2.0.9';
|
|
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() {
|
|
@@ -1,4 +1,96 @@
|
|
|
1
1
|
import { createNarutoGeneration, completeNarutoGeneration } from './naruto-generation-scheduler.js';
|
|
2
|
+
export async function runNarutoRealActivePool(input) {
|
|
3
|
+
const safeActiveWorkers = Math.max(1, input.governor.safe_active_workers);
|
|
4
|
+
const visibleCap = Math.max(0, input.governor.safe_zellij_visible_panes);
|
|
5
|
+
const pending = [...input.graph.work_items];
|
|
6
|
+
const active = new Map();
|
|
7
|
+
const completed = new Map();
|
|
8
|
+
const byId = new Map(input.graph.work_items.map((item) => [item.id, item]));
|
|
9
|
+
const timeline = [];
|
|
10
|
+
const lifecycle = [];
|
|
11
|
+
const refillLatencies = [];
|
|
12
|
+
let tick = 0;
|
|
13
|
+
let refillEvents = 0;
|
|
14
|
+
let maxObserved = 0;
|
|
15
|
+
let visibleRunning = 0;
|
|
16
|
+
while (pending.length || active.size) {
|
|
17
|
+
const beforeLaunch = Date.now();
|
|
18
|
+
let launched = 0;
|
|
19
|
+
for (;;) {
|
|
20
|
+
if (active.size >= safeActiveWorkers)
|
|
21
|
+
break;
|
|
22
|
+
const item = popRunnable(pending, new Set(completed.keys()), activeToGenerationMap(active), byId);
|
|
23
|
+
if (!item)
|
|
24
|
+
break;
|
|
25
|
+
const placement = visibleRunning < visibleCap
|
|
26
|
+
? { placement: 'zellij-pane', visible_index: visibleRunning + 1, reason: 'within_visible_cap' }
|
|
27
|
+
: { placement: 'headless', visible_index: null, reason: `visible_pane_cap:${visibleCap}` };
|
|
28
|
+
if (placement.placement === 'zellij-pane')
|
|
29
|
+
visibleRunning += 1;
|
|
30
|
+
const handle = await input.spawnWorker(item, placement);
|
|
31
|
+
active.set(handle.id, handle);
|
|
32
|
+
lifecycle.push({ work_item_id: item.id, placement: placement.placement, started_at: handle.started_at, completed_at: null, ok: null });
|
|
33
|
+
launched += 1;
|
|
34
|
+
await input.updateDashboard({ event_type: 'worker_spawned', work_item_id: item.id, active_workers: active.size, pending_workers: pending.length, completed_workers: completed.size, placement });
|
|
35
|
+
}
|
|
36
|
+
if (launched) {
|
|
37
|
+
refillEvents += launched;
|
|
38
|
+
refillLatencies.push(Date.now() - beforeLaunch);
|
|
39
|
+
await input.updateDashboard({ event_type: 'refill', active_workers: active.size, pending_workers: pending.length, completed_workers: completed.size });
|
|
40
|
+
}
|
|
41
|
+
maxObserved = Math.max(maxObserved, active.size);
|
|
42
|
+
timeline.push({ tick, active: active.size, pending: pending.length, completed: completed.size, event: launched ? 'refill' : 'wait' });
|
|
43
|
+
const done = [...active.values()].slice(0, Math.max(1, Math.ceil(active.size / 2)));
|
|
44
|
+
if (!done.length)
|
|
45
|
+
break;
|
|
46
|
+
for (const handle of done) {
|
|
47
|
+
active.delete(handle.id);
|
|
48
|
+
if (handle.placement.placement === 'zellij-pane')
|
|
49
|
+
visibleRunning = Math.max(0, visibleRunning - 1);
|
|
50
|
+
const result = await input.collectWorker(handle);
|
|
51
|
+
completed.set(result.item.id, result);
|
|
52
|
+
const row = lifecycle.find((entry) => entry.work_item_id === result.item.id && entry.completed_at == null);
|
|
53
|
+
if (row) {
|
|
54
|
+
row.completed_at = result.completed_at;
|
|
55
|
+
row.ok = result.ok;
|
|
56
|
+
}
|
|
57
|
+
await input.updateDashboard({ event_type: 'worker_completed', work_item_id: result.item.id, active_workers: active.size, pending_workers: pending.length, completed_workers: completed.size, placement: result.placement });
|
|
58
|
+
if (result.item.verification_required) {
|
|
59
|
+
await input.enqueueVerification(result);
|
|
60
|
+
await input.updateDashboard({ event_type: 'verification_enqueued', work_item_id: result.item.id, active_workers: active.size, pending_workers: pending.length, completed_workers: completed.size, placement: result.placement });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
tick += 1;
|
|
64
|
+
if (tick > input.graph.work_items.length * 4 + 20)
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
await input.updateDashboard({ event_type: 'pool_drained', active_workers: active.size, pending_workers: pending.length, completed_workers: completed.size });
|
|
68
|
+
const failedCount = [...completed.values()].filter((result) => !result.ok).length;
|
|
69
|
+
const blockers = [
|
|
70
|
+
...(pending.length ? ['naruto_real_active_pool_pending_not_drained'] : []),
|
|
71
|
+
...(active.size ? ['naruto_real_active_pool_active_not_drained'] : []),
|
|
72
|
+
...(maxObserved > safeActiveWorkers ? ['naruto_real_active_pool_exceeded_safe_workers'] : []),
|
|
73
|
+
...[...completed.values()].flatMap((result) => result.blockers || [])
|
|
74
|
+
];
|
|
75
|
+
return {
|
|
76
|
+
schema: 'sks.naruto-active-pool.v1',
|
|
77
|
+
ok: blockers.length === 0,
|
|
78
|
+
runtime_mode: 'real-worker-lifecycle',
|
|
79
|
+
safe_active_workers: safeActiveWorkers,
|
|
80
|
+
total_work_items: input.graph.total_work_items,
|
|
81
|
+
completed_count: completed.size,
|
|
82
|
+
failed_count: failedCount,
|
|
83
|
+
refill_events: refillEvents,
|
|
84
|
+
max_observed_active_workers: maxObserved,
|
|
85
|
+
duplicate_execution_count: 0,
|
|
86
|
+
conflict_items_enqueued: 0,
|
|
87
|
+
max_observed_write_lease_conflicts: 0,
|
|
88
|
+
refill_latency_ms_p95: percentile(refillLatencies, 0.95),
|
|
89
|
+
worker_lifecycle: lifecycle,
|
|
90
|
+
timeline,
|
|
91
|
+
blockers
|
|
92
|
+
};
|
|
93
|
+
}
|
|
2
94
|
export async function runNarutoActivePool(input) {
|
|
3
95
|
const base = simulateNarutoActivePool(input);
|
|
4
96
|
const allocations = [];
|
|
@@ -34,6 +126,22 @@ export async function runNarutoActivePool(input) {
|
|
|
34
126
|
blockers: [...base.blockers, ...allocationBlockers]
|
|
35
127
|
};
|
|
36
128
|
}
|
|
129
|
+
function activeToGenerationMap(active) {
|
|
130
|
+
const out = new Map();
|
|
131
|
+
let index = 1;
|
|
132
|
+
for (const handle of active.values()) {
|
|
133
|
+
out.set(handle.id, createNarutoGeneration(handle.item, index, 0));
|
|
134
|
+
index += 1;
|
|
135
|
+
}
|
|
136
|
+
return out;
|
|
137
|
+
}
|
|
138
|
+
function percentile(values, quantile) {
|
|
139
|
+
if (!values.length)
|
|
140
|
+
return 0;
|
|
141
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
142
|
+
const index = Math.min(sorted.length - 1, Math.max(0, Math.ceil(sorted.length * quantile) - 1));
|
|
143
|
+
return sorted[index] || 0;
|
|
144
|
+
}
|
|
37
145
|
export function simulateNarutoActivePool(input) {
|
|
38
146
|
const safeActiveWorkers = Math.max(1, input.governor.safe_active_workers);
|
|
39
147
|
const retryLimit = Math.max(0, Math.floor(Number(input.retryLimit ?? 1)));
|
|
@@ -17,11 +17,20 @@ export function decideNarutoConcurrency(input = {}) {
|
|
|
17
17
|
const gbPerWorker = heavy ? Number(process.env.SKS_NARUTO_GB_PER_WORKER || 0.25) : Number(process.env.SKS_NARUTO_LIGHT_GB_PER_WORKER || 0.1);
|
|
18
18
|
const memoryCap = Math.max(1, Math.floor(memoryBudgetGb / Math.max(0.05, gbPerWorker)));
|
|
19
19
|
const fdCap = Math.max(1, Math.floor((hardware.file_descriptor_limit - hardware.process_count) / 6));
|
|
20
|
+
const cpuCap = Math.max(1, hardware.cpu_core_count * (heavy ? 2 : 4));
|
|
21
|
+
const ioCap = Math.max(2, Math.floor(hardware.cpu_core_count / 2));
|
|
22
|
+
const processCap = Math.max(1, Number(process.env.SKS_NARUTO_HEADLESS_PROCESS_CAP || cpuCap + ioCap));
|
|
23
|
+
const gitWorktreeCap = Math.max(1, Number(process.env.SKS_NARUTO_GIT_WORKTREE_CAP || Math.min(requestedClones, processCap)));
|
|
20
24
|
const localLlmParallel = Math.max(1, Math.min(4, hardware.local_llm_max_parallel_requests));
|
|
21
25
|
const remoteCodexParallel = Math.max(1, Math.min(hardware.remote_api_rate_limit_budget, requestedClones));
|
|
26
|
+
const backendBudget = backend === 'ollama' || backend === 'local-llm'
|
|
27
|
+
? localLlmParallel
|
|
28
|
+
: backend === 'codex-sdk' || backend === 'zellij'
|
|
29
|
+
? Math.max(remoteCodexParallel, Math.min(requestedClones, processCap))
|
|
30
|
+
: processCap;
|
|
22
31
|
const queueCap = Math.max(1, Math.min(requestedClones, pending || totalWorkItems));
|
|
23
32
|
const leaseCap = Math.max(1, requestedClones - leaseConflicts);
|
|
24
|
-
const rawSafe = Math.max(1, Math.min(requestedClones, totalWorkItems, memoryCap, fdCap,
|
|
33
|
+
const rawSafe = Math.max(1, Math.min(requestedClones, totalWorkItems, memoryCap, fdCap, cpuCap + ioCap, gitWorktreeCap + processCap, backendBudget, queueCap, leaseCap, 100));
|
|
25
34
|
const pressure = monitorNarutoResourcePressure(hardware, { activeWorkers: rawSafe, zellijVisiblePaneCap });
|
|
26
35
|
const backpressure = applyNarutoBackpressure(rawSafe, pressure);
|
|
27
36
|
const safeActiveWorkers = Math.max(1, Math.min(rawSafe, backpressure.adjusted_active_workers));
|
|
@@ -29,6 +38,9 @@ export function decideNarutoConcurrency(input = {}) {
|
|
|
29
38
|
const reasons = [
|
|
30
39
|
...(memoryCap < requestedClones ? ['memory_cap'] : []),
|
|
31
40
|
...(fdCap < requestedClones ? ['file_descriptor_budget'] : []),
|
|
41
|
+
...(cpuCap + ioCap < requestedClones ? ['cpu_io_budget'] : []),
|
|
42
|
+
...(gitWorktreeCap + processCap < requestedClones ? ['git_worktree_process_budget'] : []),
|
|
43
|
+
...(backendBudget < requestedClones ? ['backend_parallel_budget'] : []),
|
|
32
44
|
...(remoteCodexParallel < requestedClones ? ['remote_api_rate_limit_budget'] : []),
|
|
33
45
|
...(localLlmParallel <= 4 ? ['local_llm_max_parallel_requests'] : []),
|
|
34
46
|
...(safeVisible < safeActiveWorkers ? ['zellij_ui_pane_budget'] : []),
|
|
@@ -44,6 +56,9 @@ export function decideNarutoConcurrency(input = {}) {
|
|
|
44
56
|
headless_workers: Math.max(0, safeActiveWorkers - safeVisible),
|
|
45
57
|
local_llm_parallel: localLlmParallel,
|
|
46
58
|
remote_codex_parallel: remoteCodexParallel,
|
|
59
|
+
process_parallel: processCap,
|
|
60
|
+
git_worktree_parallel: gitWorktreeCap,
|
|
61
|
+
cpu_io_parallel: cpuCap + ioCap,
|
|
47
62
|
verification_parallel: Math.max(1, Math.min(hardware.cpu_core_count * 2, safeActiveWorkers, 16)),
|
|
48
63
|
reasons: [...new Set(reasons)],
|
|
49
64
|
backpressure: backpressure.backpressure,
|
|
@@ -28,7 +28,8 @@ export function buildNarutoWorkGraph(input = {}) {
|
|
|
28
28
|
const requestedClones = normalizePositiveInt(input.requestedClones, 12);
|
|
29
29
|
const readonly = input.readonly === true;
|
|
30
30
|
const writeCapable = input.writeCapable !== false && !readonly;
|
|
31
|
-
const
|
|
31
|
+
const minimumFanout = writeCapable ? requestedClones * 2 : requestedClones;
|
|
32
|
+
const totalWorkItems = Math.max(minimumFanout, normalizePositiveInt(input.totalWorkItems, minimumFanout));
|
|
32
33
|
const kindCycle = writeCapable ? WRITE_CAPABLE_KIND_CYCLE : READONLY_KIND_CYCLE;
|
|
33
34
|
const basePath = normalizeNarutoPath(input.leaseBasePath || '.sneakoscope/naruto/patch-envelopes');
|
|
34
35
|
const targetPaths = normalizePaths(input.targetPaths || []);
|
|
@@ -19,14 +19,59 @@ export function releaseGateCacheKey(root, gate) {
|
|
|
19
19
|
hashFileIfPresent(hash, path.join(root, 'package.json'));
|
|
20
20
|
hashFileIfPresent(hash, path.join(root, 'dist', 'build-manifest.json'));
|
|
21
21
|
for (const input of gate.cache.inputs) {
|
|
22
|
-
const
|
|
23
|
-
|
|
22
|
+
const expanded = expandGlob(root, input);
|
|
23
|
+
hash.update(`input:${input}`);
|
|
24
|
+
if (!expanded.length) {
|
|
25
|
+
hash.update(`missing_or_empty:${input}`);
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
for (const file of expanded) {
|
|
29
|
+
hash.update(path.relative(root, file));
|
|
24
30
|
hashFileIfPresent(hash, file);
|
|
25
|
-
|
|
26
|
-
hash.update(input);
|
|
31
|
+
}
|
|
27
32
|
}
|
|
28
33
|
return hash.digest('hex');
|
|
29
34
|
}
|
|
35
|
+
export function expandGlob(root, input) {
|
|
36
|
+
const absolute = path.join(root, input);
|
|
37
|
+
if (!/[*!?[\]{}]/.test(input)) {
|
|
38
|
+
if (!fs.existsSync(absolute))
|
|
39
|
+
return [];
|
|
40
|
+
const stat = fs.statSync(absolute);
|
|
41
|
+
if (stat.isDirectory())
|
|
42
|
+
return hashDirectoryRecursive(absolute);
|
|
43
|
+
return stat.isFile() ? [absolute] : [];
|
|
44
|
+
}
|
|
45
|
+
if (input.endsWith('/**')) {
|
|
46
|
+
const dir = path.join(root, input.slice(0, -3));
|
|
47
|
+
return fs.existsSync(dir) && fs.statSync(dir).isDirectory() ? hashDirectoryRecursive(dir) : [];
|
|
48
|
+
}
|
|
49
|
+
const firstWildcard = input.search(/[*!?[\]{}]/);
|
|
50
|
+
const prefix = input.slice(0, firstWildcard);
|
|
51
|
+
const base = path.join(root, prefix.includes('/') ? prefix.slice(0, prefix.lastIndexOf('/')) : '');
|
|
52
|
+
if (!fs.existsSync(base))
|
|
53
|
+
return [];
|
|
54
|
+
const re = globToRegExp(input);
|
|
55
|
+
return hashDirectoryRecursive(base).filter((file) => re.test(path.relative(root, file)));
|
|
56
|
+
}
|
|
57
|
+
export function hashDirectoryRecursive(dir) {
|
|
58
|
+
if (!fs.existsSync(dir))
|
|
59
|
+
return [];
|
|
60
|
+
const out = [];
|
|
61
|
+
const stack = [dir];
|
|
62
|
+
while (stack.length) {
|
|
63
|
+
const current = stack.pop();
|
|
64
|
+
for (const name of fs.readdirSync(current).sort()) {
|
|
65
|
+
const file = path.join(current, name);
|
|
66
|
+
const stat = fs.statSync(file);
|
|
67
|
+
if (stat.isDirectory())
|
|
68
|
+
stack.push(file);
|
|
69
|
+
else if (stat.isFile())
|
|
70
|
+
out.push(file);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return out.sort();
|
|
74
|
+
}
|
|
30
75
|
export function readReleaseGateCacheHit(root, gate) {
|
|
31
76
|
try {
|
|
32
77
|
const parsed = JSON.parse(fs.readFileSync(releaseGateCacheFile(root), 'utf8'));
|
|
@@ -60,4 +105,13 @@ function hashFileIfPresent(hash, file) {
|
|
|
60
105
|
if (fs.existsSync(file) && fs.statSync(file).isFile())
|
|
61
106
|
hash.update(fs.readFileSync(file));
|
|
62
107
|
}
|
|
108
|
+
function globToRegExp(input) {
|
|
109
|
+
const escaped = input
|
|
110
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
|
111
|
+
.replace(/\*\*/g, '\u0000')
|
|
112
|
+
.replace(/\*/g, '[^/]*')
|
|
113
|
+
.replace(/\?/g, '[^/]')
|
|
114
|
+
.replace(/\u0000/g, '.*');
|
|
115
|
+
return new RegExp(`^${escaped}$`);
|
|
116
|
+
}
|
|
63
117
|
//# sourceMappingURL=release-gate-cache-v2.js.map
|
|
@@ -35,6 +35,39 @@ export async function runReleaseGateDag(input) {
|
|
|
35
35
|
let cached = 0;
|
|
36
36
|
let sumGateMs = 0;
|
|
37
37
|
let peakRunning = 0;
|
|
38
|
+
const writeSummarySnapshot = (finished = false) => {
|
|
39
|
+
const wallMs = Date.now() - started;
|
|
40
|
+
const failures = [...failed.values()].map((row) => ({ id: row.id, exit_code: row.exit_code, stderr_tail: row.stderr_tail }));
|
|
41
|
+
const snapshot = {
|
|
42
|
+
schema: 'sks.release-gate-dag-run.v1',
|
|
43
|
+
ok: failures.length === 0,
|
|
44
|
+
run_id: runId,
|
|
45
|
+
selected_preset: preset,
|
|
46
|
+
total_gates: manifest.gates.length,
|
|
47
|
+
selected_gates: selected.length,
|
|
48
|
+
completed: completed.size,
|
|
49
|
+
failed: failed.size,
|
|
50
|
+
cached,
|
|
51
|
+
wall_ms: wallMs,
|
|
52
|
+
sum_gate_ms: sumGateMs,
|
|
53
|
+
cpu_time_saved_ms: Math.max(0, sumGateMs - wallMs),
|
|
54
|
+
parallelism_gain: wallMs > 0 ? Number((sumGateMs / wallMs).toFixed(2)) : 1,
|
|
55
|
+
critical_path_ms: estimateCriticalPath(selected, completed),
|
|
56
|
+
peak_running: peakRunning,
|
|
57
|
+
peak_resources: peakResources,
|
|
58
|
+
budget_snapshot: budget,
|
|
59
|
+
budget_summary: summarizeReleaseGateBudget(budget),
|
|
60
|
+
report_dir: reportDir,
|
|
61
|
+
failures
|
|
62
|
+
};
|
|
63
|
+
if (!finished) {
|
|
64
|
+
snapshot.in_progress = true;
|
|
65
|
+
snapshot.pending = pending.size;
|
|
66
|
+
snapshot.running = running.size;
|
|
67
|
+
}
|
|
68
|
+
writeReleaseGateJson(path.join(reportDir, 'summary.json'), snapshot);
|
|
69
|
+
return snapshot;
|
|
70
|
+
};
|
|
38
71
|
if (input.explain) {
|
|
39
72
|
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
73
|
}
|
|
@@ -51,6 +84,7 @@ export async function runReleaseGateDag(input) {
|
|
|
51
84
|
cached += 1;
|
|
52
85
|
progressed = true;
|
|
53
86
|
appendReleaseGateJsonl(timeline, { event: 'cache_hit', gate_id: gate.id, at: new Date().toISOString() });
|
|
87
|
+
writeSummarySnapshot(false);
|
|
54
88
|
continue;
|
|
55
89
|
}
|
|
56
90
|
appendReleaseGateJsonl(timeline, { event: 'start', gate_id: gate.id, resource: gate.resource, at: new Date().toISOString() });
|
|
@@ -102,32 +136,9 @@ export async function runReleaseGateDag(input) {
|
|
|
102
136
|
}
|
|
103
137
|
}
|
|
104
138
|
appendReleaseGateJsonl(timeline, { event: result.ok ? 'pass' : 'fail', gate_id: result.id, duration_ms: result.duration_ms, at: new Date().toISOString() });
|
|
139
|
+
writeSummarySnapshot(false);
|
|
105
140
|
}
|
|
106
|
-
const
|
|
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);
|
|
141
|
+
const result = writeSummarySnapshot(true);
|
|
131
142
|
return result;
|
|
132
143
|
}
|
|
133
144
|
function selectPreset(manifest, preset) {
|
package/dist/core/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const PACKAGE_VERSION = '2.0.
|
|
1
|
+
export const PACKAGE_VERSION = '2.0.9';
|
|
2
2
|
//# sourceMappingURL=version.js.map
|
|
@@ -6,6 +6,10 @@ export function buildZellijDashboardSnapshot(input) {
|
|
|
6
6
|
generated_at: nowIso(),
|
|
7
7
|
mission_id: input.mission_id,
|
|
8
8
|
mode: input.mode || 'naruto',
|
|
9
|
+
route: input.route || input.mode || '$Naruto',
|
|
10
|
+
provider: input.provider || 'unknown',
|
|
11
|
+
service_tier: input.service_tier || 'fast',
|
|
12
|
+
backpressure: input.backpressure || 'normal',
|
|
9
13
|
backend_counts: input.backend_counts || { 'codex-sdk': 1 },
|
|
10
14
|
placement_counts: input.placement_counts || { 'zellij-pane': 1 },
|
|
11
15
|
active_workers: Number(input.active_workers || 0),
|
|
@@ -16,6 +20,15 @@ export function buildZellijDashboardSnapshot(input) {
|
|
|
16
20
|
local_llm: input.local_llm || { tps: 0, queue: 0 },
|
|
17
21
|
gpt_final_status: input.gpt_final_status || 'not_started',
|
|
18
22
|
gate_progress: input.gate_progress || 'not_release',
|
|
23
|
+
patch_verify: input.patch_verify || {
|
|
24
|
+
patches: 0,
|
|
25
|
+
gpt_approved: 0,
|
|
26
|
+
conflicts: 0,
|
|
27
|
+
verification_running: 0,
|
|
28
|
+
verification_passed: 0,
|
|
29
|
+
verification_failed: 0
|
|
30
|
+
},
|
|
31
|
+
workers: input.workers || [],
|
|
19
32
|
latest_blockers: input.latest_blockers || []
|
|
20
33
|
};
|
|
21
34
|
}
|
|
@@ -25,18 +38,21 @@ export function renderZellijDashboardText(snapshot) {
|
|
|
25
38
|
return [
|
|
26
39
|
'SKS Dashboard',
|
|
27
40
|
`Mission: ${snapshot.mission_id}`,
|
|
28
|
-
`
|
|
41
|
+
`Route / mode: ${snapshot.route} / ${snapshot.mode} / ${snapshot.service_tier}`,
|
|
42
|
+
`Provider: ${snapshot.provider}`,
|
|
29
43
|
`Backend counts: ${backendCounts || 'none'}`,
|
|
30
44
|
`Placement counts: ${placementCounts || 'none'}`,
|
|
31
|
-
`Active
|
|
32
|
-
`
|
|
33
|
-
`Headless workers: ${snapshot.headless_workers}`,
|
|
34
|
-
`Queue depth: ${snapshot.queue_depth}`,
|
|
45
|
+
`Active / visible / headless / queued: ${snapshot.active_workers}/${snapshot.visible_panes}/${snapshot.headless_workers}/${snapshot.queue_depth}`,
|
|
46
|
+
`Backpressure: ${snapshot.backpressure}`,
|
|
35
47
|
`Worktrees active/completed/retained: ${snapshot.worktrees.active}/${snapshot.worktrees.completed}/${snapshot.worktrees.retained}`,
|
|
36
48
|
`Local LLM TPS / queue: ${snapshot.local_llm.tps}/${snapshot.local_llm.queue}`,
|
|
37
49
|
`GPT final status: ${snapshot.gpt_final_status}`,
|
|
38
50
|
`Gate progress: ${snapshot.gate_progress}`,
|
|
39
|
-
`
|
|
51
|
+
`Patch / verify: patches ${snapshot.patch_verify.patches} · approved ${snapshot.patch_verify.gpt_approved} · conflicts ${snapshot.patch_verify.conflicts} · verify ${snapshot.patch_verify.verification_running}/${snapshot.patch_verify.verification_passed}/${snapshot.patch_verify.verification_failed}`,
|
|
52
|
+
'Workers:',
|
|
53
|
+
...(snapshot.workers.length ? snapshot.workers.slice(0, 12).map((worker) => `${worker.slot_id} gen-${worker.generation_index} ${worker.role} ${worker.backend}/${worker.provider}/${worker.service_tier} WT:${worker.worktree_id || '-'} ${worker.status} ${worker.current_file || ''} ${worker.latest_heartbeat || ''}`) : ['none']),
|
|
54
|
+
`Latest blockers: ${snapshot.latest_blockers.length ? snapshot.latest_blockers.join(', ') : 'none'}`,
|
|
55
|
+
'Controls: q detach | /stop selected | /focus slot | /logs'
|
|
40
56
|
].join('\n');
|
|
41
57
|
}
|
|
42
58
|
//# sourceMappingURL=zellij-dashboard-renderer.js.map
|
|
@@ -19,7 +19,7 @@ export async function launchZellijLayout(opts = {}) {
|
|
|
19
19
|
ledgerRoot,
|
|
20
20
|
cwd: opts.cwd || root,
|
|
21
21
|
kind: opts.kind || 'agent',
|
|
22
|
-
slotCount: opts.slotCount
|
|
22
|
+
slotCount: opts.slotCount ?? 1,
|
|
23
23
|
title: `SKS ${opts.kind || 'agent'} ${missionId}`,
|
|
24
24
|
codexArgs: opts.codexArgs || [],
|
|
25
25
|
launchEnv: opts.launchEnv || {}
|
|
@@ -124,7 +124,7 @@ export async function launchMadZellijUi(args = [], opts = {}) {
|
|
|
124
124
|
const launchOpts = {
|
|
125
125
|
...opts,
|
|
126
126
|
kind: 'mad',
|
|
127
|
-
slotCount: opts.slotCount
|
|
127
|
+
slotCount: opts.slotCount ?? 1
|
|
128
128
|
};
|
|
129
129
|
const resolvedSession = session || opts.session;
|
|
130
130
|
if (resolvedSession)
|
|
@@ -135,7 +135,7 @@ export async function launchTeamZellijView(opts = {}) {
|
|
|
135
135
|
return launchZellijLayout({
|
|
136
136
|
...opts,
|
|
137
137
|
kind: 'team',
|
|
138
|
-
slotCount: opts.slotCount
|
|
138
|
+
slotCount: opts.slotCount ?? 5
|
|
139
139
|
});
|
|
140
140
|
}
|
|
141
141
|
/**
|