sneakoscope 2.0.7 → 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 +44 -8
- package/dist/commands/zellij.js +144 -1
- package/dist/core/agents/agent-command-surface.js +4 -2
- package/dist/core/agents/agent-orchestrator.js +5 -2
- package/dist/core/agents/agent-patch-schema.js +4 -2
- package/dist/core/agents/native-cli-session-swarm.js +81 -9
- package/dist/core/commands/mad-sks-command.js +17 -1
- package/dist/core/commands/naruto-command.js +99 -7
- package/dist/core/fsx.js +1 -1
- package/dist/core/git/git-repo-detection.js +7 -0
- package/dist/core/git/git-worktree-cleanup.js +14 -3
- package/dist/core/git/git-worktree-diff.js +7 -2
- package/dist/core/git/git-worktree-manager.js +9 -2
- package/dist/core/git/git-worktree-patch-envelope.js +5 -5
- 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 +117 -0
- package/dist/core/release/release-gate-dag.js +190 -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 +58 -0
- 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 +180 -15
- 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/git-worktree-diff-envelope-check.js +17 -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-manifest-append-check.js +18 -0
- package/dist/scripts/git-worktree-untracked-diff-check.js +18 -0
- 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-worktree-coding-blackbox.js +29 -0
- 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-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-speed-budget-check.js +79 -0
- package/dist/scripts/release-readiness-report.js +1 -1
- package/dist/scripts/release-stability-report-check.js +99 -0
- package/dist/scripts/zellij-dashboard-pane-check.js +70 -0
- package/dist/scripts/zellij-dashboard-watch.js +41 -0
- 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/dist/scripts/zellij-worker-pane-real-ui-blackbox.js +185 -0
- package/package.json +32 -5
- package/schemas/release/release-gate-node.schema.json +52 -0
- package/schemas/zellij/zellij-right-column-state.schema.json +41 -0
|
@@ -5,6 +5,7 @@ import { appendJsonl, ensureDir, exists, nowIso, packageRoot, readJson, writeJso
|
|
|
5
5
|
import { fastModeEnv } from './fast-mode-policy.js';
|
|
6
6
|
import { validateAgentWorkerResult } from './agent-worker-pipeline.js';
|
|
7
7
|
import { closeWorkerPane, openWorkerPane } from '../zellij/zellij-worker-pane-manager.js';
|
|
8
|
+
import { recordHeadlessWorkerInRightColumn } from '../zellij/zellij-right-column-manager.js';
|
|
8
9
|
import { resolveProviderContext } from '../provider/provider-context.js';
|
|
9
10
|
export const NATIVE_CLI_SESSION_SWARM_SCHEMA = 'sks.agent-native-cli-session-swarm.v1';
|
|
10
11
|
export function createNativeCliSessionSwarmRecorder(root, input) {
|
|
@@ -101,7 +102,12 @@ class NativeCliSessionSwarmRecorder {
|
|
|
101
102
|
};
|
|
102
103
|
const stdout = fs.createWriteStream(path.join(this.root, stdoutRel), { flags: 'a' });
|
|
103
104
|
const stderr = fs.createWriteStream(path.join(this.root, stderrRel), { flags: 'a' });
|
|
104
|
-
|
|
105
|
+
const placement = String(ctx.opts.workerPlacement || this.input.workerPlacement || (this.input.backend === 'zellij' ? 'zellij-pane' : 'process'));
|
|
106
|
+
const useZellijPane = placement === 'zellij-pane'
|
|
107
|
+
&& ctx.opts.zellijPaneWorker !== false
|
|
108
|
+
&& (ctx.opts.zellijSessionName || this.input.missionId)
|
|
109
|
+
&& this.visibleZellijPaneCount() < this.zellijVisiblePaneCap(ctx.opts);
|
|
110
|
+
if (useZellijPane) {
|
|
105
111
|
stdout.end();
|
|
106
112
|
stderr.end();
|
|
107
113
|
return this.launchWorkerInZellijPane({
|
|
@@ -115,6 +121,22 @@ class NativeCliSessionSwarmRecorder {
|
|
|
115
121
|
workerDirRel
|
|
116
122
|
});
|
|
117
123
|
}
|
|
124
|
+
if (placement === 'zellij-pane' && ctx.opts.zellijPaneWorker !== false && !useZellijPane) {
|
|
125
|
+
record.worker_placement = 'headless';
|
|
126
|
+
record.headless_reason = `visible_pane_cap:${this.zellijVisiblePaneCap(ctx.opts)}`;
|
|
127
|
+
await recordHeadlessWorkerInRightColumn({
|
|
128
|
+
root: this.root,
|
|
129
|
+
projectRoot: ctx.opts.projectRoot || this.input.projectRoot || ctx.opts.cwd,
|
|
130
|
+
missionId: this.input.missionId,
|
|
131
|
+
sessionName: String(ctx.opts.zellijSessionName || (this.input.missionId ? `sks-${this.input.missionId}` : 'sks-agent-runtime')),
|
|
132
|
+
slotId: String(ctx.agent.slot_id || ctx.agent.id || 'slot-001'),
|
|
133
|
+
generationIndex: Number(ctx.agent.generation_index || 1),
|
|
134
|
+
reason: record.headless_reason
|
|
135
|
+
}).catch(() => null);
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
record.worker_placement = placement === 'zellij-pane' ? 'zellij-pane' : 'process';
|
|
139
|
+
}
|
|
118
140
|
const child = spawn(process.execPath, args, {
|
|
119
141
|
cwd: workerCwd,
|
|
120
142
|
env: {
|
|
@@ -190,11 +212,26 @@ class NativeCliSessionSwarmRecorder {
|
|
|
190
212
|
const activeToken = this.nextPaneToken--;
|
|
191
213
|
this.active.add(activeToken);
|
|
192
214
|
this.maxObserved = Math.max(this.maxObserved, this.active.size);
|
|
215
|
+
const providerContext = await resolveProviderContext({
|
|
216
|
+
root: this.root,
|
|
217
|
+
route: this.input.route,
|
|
218
|
+
serviceTier: this.input.fastModePolicy.service_tier
|
|
219
|
+
});
|
|
193
220
|
const workerCommand = buildPaneWorkerCommand({
|
|
194
221
|
args: input.args,
|
|
195
222
|
stdoutPath: path.join(this.root, input.stdoutRel),
|
|
196
223
|
stderrPath: path.join(this.root, input.stderrRel),
|
|
197
224
|
heartbeatPath: path.join(this.root, input.heartbeatRel),
|
|
225
|
+
header: buildPaneWorkerHeader({
|
|
226
|
+
slotId,
|
|
227
|
+
generationIndex: Number(input.ctx.agent.generation_index || 1),
|
|
228
|
+
role: input.ctx.agent.naruto_role || input.ctx.agent.role || input.ctx.agent.persona_id || 'worker',
|
|
229
|
+
backend: this.input.backend,
|
|
230
|
+
provider: providerContext.provider,
|
|
231
|
+
serviceTier: this.input.fastModePolicy.service_tier,
|
|
232
|
+
worktree,
|
|
233
|
+
task: input.ctx.slice?.description || input.ctx.slice?.title || input.ctx.slice?.id || ''
|
|
234
|
+
}),
|
|
198
235
|
env: {
|
|
199
236
|
...(input.ctx.opts.env || {}),
|
|
200
237
|
...fastModeEnv(this.input.fastModePolicy),
|
|
@@ -211,11 +248,6 @@ class NativeCliSessionSwarmRecorder {
|
|
|
211
248
|
SKS_ZELLIJ_SESSION_NAME: sessionName
|
|
212
249
|
}
|
|
213
250
|
});
|
|
214
|
-
const providerContext = await resolveProviderContext({
|
|
215
|
-
root: this.root,
|
|
216
|
-
route: this.input.route,
|
|
217
|
-
serviceTier: this.input.fastModePolicy.service_tier
|
|
218
|
-
});
|
|
219
251
|
let paneRecord = await openWorkerPane({
|
|
220
252
|
root: this.root,
|
|
221
253
|
missionId: this.input.missionId,
|
|
@@ -233,7 +265,26 @@ class NativeCliSessionSwarmRecorder {
|
|
|
233
265
|
cwd: workerCwd,
|
|
234
266
|
providerContext,
|
|
235
267
|
serviceTier: this.input.fastModePolicy.service_tier,
|
|
236
|
-
worktree: worktree ? { id: worktree.id, path: worktree.path, branch: worktree.branch } : null
|
|
268
|
+
worktree: worktree ? { id: worktree.id, path: worktree.path, branch: worktree.branch } : null,
|
|
269
|
+
backend: this.input.backend,
|
|
270
|
+
projectRoot: input.ctx.opts.projectRoot || this.input.projectRoot || input.ctx.opts.cwd,
|
|
271
|
+
rightColumnMode: 'spawn-on-first-worker',
|
|
272
|
+
visiblePaneCap: this.zellijVisiblePaneCap(input.ctx.opts),
|
|
273
|
+
dashboardSnapshot: {
|
|
274
|
+
mode: this.input.route || '$Agent',
|
|
275
|
+
backend_counts: { [this.input.backend]: this.input.targetActiveSlots },
|
|
276
|
+
placement_counts: {
|
|
277
|
+
'zellij-pane': this.zellijVisiblePaneCap(input.ctx.opts),
|
|
278
|
+
headless: Math.max(0, this.input.targetActiveSlots - this.zellijVisiblePaneCap(input.ctx.opts))
|
|
279
|
+
},
|
|
280
|
+
active_workers: this.input.targetActiveSlots,
|
|
281
|
+
visible_panes: this.zellijVisiblePaneCap(input.ctx.opts),
|
|
282
|
+
headless_workers: Math.max(0, this.input.targetActiveSlots - this.zellijVisiblePaneCap(input.ctx.opts)),
|
|
283
|
+
queue_depth: Math.max(0, this.input.requestedAgents - this.input.targetActiveSlots),
|
|
284
|
+
local_llm: { tps: 0, queue: 0 },
|
|
285
|
+
gpt_final_status: 'pending',
|
|
286
|
+
gate_progress: 'worker-spawn'
|
|
287
|
+
}
|
|
237
288
|
});
|
|
238
289
|
const launchBlockers = paneRecord.blockers || [];
|
|
239
290
|
input.record.command_line = ['zellij', '--session', sessionName, 'action', 'new-pane', '--direction', paneRecord.direction_applied, '--name', paneRecord.pane_name, '--', 'sh', '-lc', '<native-cli-worker-command>'];
|
|
@@ -334,6 +385,7 @@ class NativeCliSessionSwarmRecorder {
|
|
|
334
385
|
root: this.root,
|
|
335
386
|
paneRecord,
|
|
336
387
|
cwd: workerCwd,
|
|
388
|
+
projectRoot: input.ctx.opts.projectRoot || this.input.projectRoot || input.ctx.opts.cwd,
|
|
337
389
|
status: input.record.status === 'closed' ? 'closed' : 'failed',
|
|
338
390
|
blockers: input.record.blockers,
|
|
339
391
|
sdkThreadId,
|
|
@@ -404,6 +456,8 @@ class NativeCliSessionSwarmRecorder {
|
|
|
404
456
|
closed_worker_process_count: closed.length,
|
|
405
457
|
max_observed_worker_process_count: this.maxObserved,
|
|
406
458
|
active_worker_process_count: this.active.size,
|
|
459
|
+
visible_zellij_pane_cap: this.input.zellijVisiblePaneCap || null,
|
|
460
|
+
headless_overflow_worker_count: this.records.filter((row) => row.worker_placement === 'headless').length,
|
|
407
461
|
unique_worker_session_count: new Set(this.records.map((row) => row.session_id).filter(Boolean)).size,
|
|
408
462
|
unique_slot_count: new Set(this.records.map((row) => row.slot_id).filter(Boolean)).size,
|
|
409
463
|
unique_generation_count: new Set(this.records.map((row) => `${row.slot_id}:${row.generation_index}`).filter(Boolean)).size,
|
|
@@ -418,17 +472,35 @@ class NativeCliSessionSwarmRecorder {
|
|
|
418
472
|
blockers: this.records.flatMap((row) => row.blockers || [])
|
|
419
473
|
};
|
|
420
474
|
}
|
|
475
|
+
zellijVisiblePaneCap(opts = {}) {
|
|
476
|
+
const raw = Number(opts.zellijVisiblePaneCap || this.input.zellijVisiblePaneCap || this.input.targetActiveSlots || 1);
|
|
477
|
+
return Math.max(1, Math.floor(Number.isFinite(raw) ? raw : 1));
|
|
478
|
+
}
|
|
479
|
+
visibleZellijPaneCount() {
|
|
480
|
+
return this.records.filter((row) => row.scaling_primitive === 'native_cli_process_in_zellij_worker_pane' && (row.status === 'launching' || row.status === 'running')).length;
|
|
481
|
+
}
|
|
421
482
|
}
|
|
422
483
|
export function buildPaneWorkerCommand(input) {
|
|
423
484
|
const envPrefix = Object.entries(input.env)
|
|
424
485
|
.filter(([key, value]) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(key) && value != null)
|
|
425
|
-
.map(([key, value]) =>
|
|
486
|
+
.map(([key, value]) => `export ${key}=${shellQuote(String(value))};`)
|
|
426
487
|
.sort();
|
|
427
488
|
const command = [shellQuote(process.execPath), ...input.args.map(shellQuote)].join(' ');
|
|
428
489
|
const heartbeat = `printf '%s\\n' ${shellQuote(JSON.stringify({ schema: 'sks.zellij-worker-pane-event.v1', event: 'worker_command_exited' }))} >> ${shellQuote(input.heartbeatPath)}`;
|
|
429
490
|
const holdMs = Math.max(0, Number(process.env.SKS_ZELLIJ_WORKER_PANE_HOLD_MS || 1500));
|
|
430
491
|
const hold = holdMs > 0 ? `sleep ${shellQuote(String(Math.min(30, holdMs / 1000)))}` : ':';
|
|
431
|
-
|
|
492
|
+
const header = input.header ? `printf '%s\\n' ${shellQuote(input.header)} | tee -a ${shellQuote(input.stdoutPath)};` : '';
|
|
493
|
+
return `${envPrefix.join(' ')} ${header} ${command} >> ${shellQuote(input.stdoutPath)} 2>> ${shellQuote(input.stderrPath)}; code=$?; ${heartbeat}; ${hold}; exit $code`.trim();
|
|
494
|
+
}
|
|
495
|
+
function buildPaneWorkerHeader(input) {
|
|
496
|
+
return [
|
|
497
|
+
'SKS Worker',
|
|
498
|
+
`slot: ${input.slotId} gen: ${input.generationIndex} role: ${input.role}`,
|
|
499
|
+
`backend: ${input.backend} provider: ${input.provider} service: ${input.serviceTier}`,
|
|
500
|
+
`worktree: ${input.worktree ? `${input.worktree.id} branch: ${input.worktree.branch}` : '-'}`,
|
|
501
|
+
`task: ${String(input.task || '').slice(0, 160)}`,
|
|
502
|
+
'status: running'
|
|
503
|
+
].join('\n');
|
|
432
504
|
}
|
|
433
505
|
async function waitForWorkerResult(file, timeoutMs) {
|
|
434
506
|
const deadline = Date.now() + Math.max(1000, timeoutMs);
|
|
@@ -113,12 +113,24 @@ export async function madHighCommand(args = [], deps = {}) {
|
|
|
113
113
|
console.log(`MAD Zellij action: ${formatMadZellijAction(launch)}`);
|
|
114
114
|
return launch;
|
|
115
115
|
}
|
|
116
|
+
await writeJsonAtomic(path.join(madLaunch.dir, 'zellij-initial-ui.json'), {
|
|
117
|
+
schema: 'sks.zellij-initial-ui.v1',
|
|
118
|
+
ok: true,
|
|
119
|
+
mission_id: madLaunch.mission_id,
|
|
120
|
+
session_name: launch.session_name,
|
|
121
|
+
initial_panes: 'main-only',
|
|
122
|
+
dashboard_created: false,
|
|
123
|
+
worker_panes_created: 0,
|
|
124
|
+
right_column_mode: 'spawn-on-first-worker'
|
|
125
|
+
});
|
|
116
126
|
const madNativeSwarm = await startMadNativeSwarm(madLaunch.root, madLaunch, args, profile, {
|
|
117
127
|
env: {
|
|
118
128
|
...madSksEnv,
|
|
119
129
|
SKS_ZELLIJ_SESSION_NAME: launch.session_name
|
|
120
130
|
},
|
|
121
|
-
zellijSessionName: launch.session_name
|
|
131
|
+
zellijSessionName: launch.session_name,
|
|
132
|
+
workerPlacement: shouldAutoAttachZellij(args) ? 'zellij-pane' : 'process',
|
|
133
|
+
zellijVisiblePaneCap: Number(process.env.SKS_ZELLIJ_VISIBLE_PANE_CAP || 8)
|
|
122
134
|
});
|
|
123
135
|
// The launcher only creates a detached background session. In an interactive
|
|
124
136
|
// terminal, immediately attach so the session actually opens for the user
|
|
@@ -193,6 +205,8 @@ export async function startMadNativeSwarm(root, madLaunch, args = [], profile =
|
|
|
193
205
|
command.push('--real');
|
|
194
206
|
command.push('--zellij-session-name', opts.zellijSessionName || `sks-${madLaunch.mission_id}`);
|
|
195
207
|
command.push('--zellij-pane-worker');
|
|
208
|
+
command.push('--worker-placement', opts.workerPlacement || 'zellij-pane');
|
|
209
|
+
command.push('--zellij-visible-pane-cap', String(opts.zellijVisiblePaneCap || swarm.agents));
|
|
196
210
|
}
|
|
197
211
|
const baseReport = {
|
|
198
212
|
schema: 'sks.mad-sks-native-swarm.v1',
|
|
@@ -209,6 +223,8 @@ export async function startMadNativeSwarm(root, madLaunch, args = [], profile =
|
|
|
209
223
|
work_items: swarm.workItems,
|
|
210
224
|
backend: swarm.backend,
|
|
211
225
|
zellij_session_name: opts.zellijSessionName || null,
|
|
226
|
+
worker_placement: opts.workerPlacement || (swarm.backend === 'zellij' ? 'zellij-pane' : 'process'),
|
|
227
|
+
zellij_visible_pane_cap: opts.zellijVisiblePaneCap || null,
|
|
212
228
|
readonly: true,
|
|
213
229
|
command,
|
|
214
230
|
stdout_log: path.relative(root, stdoutLog),
|
|
@@ -10,7 +10,7 @@ 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';
|
|
@@ -30,6 +30,10 @@ export async function narutoCommand(commandOrArgs = 'naruto', maybeArgs = []) {
|
|
|
30
30
|
return narutoHelp(parsed);
|
|
31
31
|
if (parsed.action === 'status')
|
|
32
32
|
return narutoStatus(parsed);
|
|
33
|
+
if (parsed.action === 'dashboard')
|
|
34
|
+
return narutoDashboard(parsed);
|
|
35
|
+
if (parsed.action === 'workers')
|
|
36
|
+
return narutoWorkers(parsed);
|
|
33
37
|
return narutoRun(parsed);
|
|
34
38
|
}
|
|
35
39
|
async function narutoRun(parsed) {
|
|
@@ -108,6 +112,14 @@ async function narutoRun(parsed) {
|
|
|
108
112
|
const activeSlots = Math.max(1, Math.min(roster.agent_count, parsed.concurrency || Math.max(governor.safe_active_workers, backendMinimum), safe.cap));
|
|
109
113
|
const zellijVisiblePanes = Math.max(1, Math.min(activeSlots, governor.safe_zellij_visible_panes));
|
|
110
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
|
+
});
|
|
111
123
|
const verificationDag = buildNarutoVerificationDag(workGraph, { cwd: root });
|
|
112
124
|
const gptFinalPack = buildNarutoGptFinalPack({
|
|
113
125
|
missionId: mission.id,
|
|
@@ -131,6 +143,7 @@ async function narutoRun(parsed) {
|
|
|
131
143
|
roleDistribution,
|
|
132
144
|
governor,
|
|
133
145
|
activePool,
|
|
146
|
+
realActivePool,
|
|
134
147
|
verificationDag,
|
|
135
148
|
gptFinalPack,
|
|
136
149
|
zellijDashboard,
|
|
@@ -144,12 +157,25 @@ async function narutoRun(parsed) {
|
|
|
144
157
|
missionId: mission.id,
|
|
145
158
|
ledgerRoot,
|
|
146
159
|
kind: 'naruto',
|
|
147
|
-
slotCount:
|
|
160
|
+
slotCount: 0,
|
|
148
161
|
dryRun: false,
|
|
149
162
|
attach: false
|
|
150
163
|
});
|
|
151
164
|
if (liveZellij?.ok && liveZellij.capability?.status === 'ok') {
|
|
152
|
-
|
|
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));
|
|
153
179
|
if (parsed.attach)
|
|
154
180
|
attachZellijSessionInteractive(liveZellij.session_name, { cwd: process.cwd(), configPath: liveZellij.clipboard_config_path });
|
|
155
181
|
}
|
|
@@ -184,6 +210,10 @@ async function narutoRun(parsed) {
|
|
|
184
210
|
mock: parsed.mock,
|
|
185
211
|
real: parsed.real,
|
|
186
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,
|
|
187
217
|
// Shadow clones ALWAYS run in fast service tier — never honor --no-fast/standard.
|
|
188
218
|
fastMode: true,
|
|
189
219
|
serviceTier: 'fast',
|
|
@@ -223,7 +253,13 @@ async function narutoRun(parsed) {
|
|
|
223
253
|
ok: activePool.ok,
|
|
224
254
|
max_observed_active_workers: activePool.max_observed_active_workers,
|
|
225
255
|
refill_events: activePool.refill_events,
|
|
226
|
-
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
|
+
}
|
|
227
263
|
},
|
|
228
264
|
local_worker: localWorkerSummary,
|
|
229
265
|
proof: result.proof?.status || 'missing',
|
|
@@ -296,6 +332,57 @@ async function narutoStatus(parsed) {
|
|
|
296
332
|
console.log('Roles: ' + roleDistribution.entries.map((entry) => `${entry.role}:${entry.count}`).join(', '));
|
|
297
333
|
});
|
|
298
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
|
+
}
|
|
299
386
|
async function narutoHelp(parsed) {
|
|
300
387
|
const help = {
|
|
301
388
|
schema: NARUTO_RESULT_SCHEMA,
|
|
@@ -320,13 +407,13 @@ function parseNarutoArgs(args = []) {
|
|
|
320
407
|
if (hasFlag(args, '--help') || hasFlag(args, '-h'))
|
|
321
408
|
args = ['help', ...args.filter((arg) => arg !== '--help' && arg !== '-h')];
|
|
322
409
|
const first = args[0] && !String(args[0]).startsWith('--') ? String(args[0]) : '';
|
|
323
|
-
const actions = new Set(['run', 'status', 'help']);
|
|
410
|
+
const actions = new Set(['run', 'status', 'help', 'dashboard', 'workers']);
|
|
324
411
|
const action = (actions.has(first) ? first : 'run');
|
|
325
412
|
const rest = action === first ? args.slice(1) : args;
|
|
326
413
|
const json = hasFlag(args, '--json');
|
|
327
414
|
const requestedClones = Number(readOption(args, '--clones', readOption(args, '--agents', DEFAULT_NARUTO_CLONES)));
|
|
328
415
|
const clones = clampClones(requestedClones);
|
|
329
|
-
const workItems = clampWorkItems(Number(readOption(args, '--work-items', clones)), clones);
|
|
416
|
+
const workItems = clampWorkItems(Number(readOption(args, '--work-items', clones * 2)), clones);
|
|
330
417
|
const concurrency = normalizeConcurrency(readOption(args, '--concurrency', readOption(args, '--target-active-slots', null)), clones);
|
|
331
418
|
const useOllama = hasFlag(args, '--ollama') || hasFlag(args, '--local-model');
|
|
332
419
|
const noOllama = hasFlag(args, '--no-ollama') || hasFlag(args, '--no-local-model');
|
|
@@ -337,7 +424,10 @@ function parseNarutoArgs(args = []) {
|
|
|
337
424
|
const readonly = hasFlag(args, '--readonly') || hasFlag(args, '--read-only');
|
|
338
425
|
const writeModeRaw = String(readOption(args, '--write-mode', hasFlag(args, '--parallel-write') ? 'parallel' : '') || '');
|
|
339
426
|
const writeMode = (['proof-safe', 'parallel', 'serial', 'off'].includes(writeModeRaw) ? writeModeRaw : null);
|
|
340
|
-
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')));
|
|
341
431
|
const ollamaModel = String(readOption(args, '--ollama-model', readOption(args, '--local-model-model', '')) || '') || null;
|
|
342
432
|
const ollamaBaseUrl = String(readOption(args, '--ollama-base-url', readOption(args, '--local-model-base-url', '')) || '') || null;
|
|
343
433
|
const noOpenZellij = hasFlag(args, '--no-open-zellij') || hasFlag(args, '--no-zellij');
|
|
@@ -351,6 +441,8 @@ async function writeNarutoArtifacts(ledgerRoot, artifacts) {
|
|
|
351
441
|
await writeJsonAtomic(path.join(ledgerRoot, 'naruto-role-distribution.json'), artifacts.roleDistribution);
|
|
352
442
|
await writeJsonAtomic(path.join(ledgerRoot, 'naruto-concurrency-governor.json'), artifacts.governor);
|
|
353
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);
|
|
354
446
|
await writeJsonAtomic(path.join(ledgerRoot, 'naruto-verification-dag.json'), artifacts.verificationDag);
|
|
355
447
|
await writeJsonAtomic(path.join(ledgerRoot, 'naruto-gpt-final-pack.json'), artifacts.gptFinalPack);
|
|
356
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() {
|
|
@@ -18,6 +18,7 @@ export async function detectGitRepo(root = process.cwd()) {
|
|
|
18
18
|
const bare = await runGitCommand(cwd, ['rev-parse', '--is-bare-repository']);
|
|
19
19
|
const branch = await runGitCommand(cwd, ['branch', '--show-current']);
|
|
20
20
|
const head = await runGitCommand(cwd, ['rev-parse', 'HEAD']);
|
|
21
|
+
const status = await runGitCommand(cwd, ['status', '--porcelain=v1', '--untracked-files=all']);
|
|
21
22
|
if (!top.ok)
|
|
22
23
|
blockers.push(gitBlocker('git_root_unresolved', top));
|
|
23
24
|
if (!gitDir.ok)
|
|
@@ -26,6 +27,8 @@ export async function detectGitRepo(root = process.cwd()) {
|
|
|
26
27
|
blockers.push(gitBlocker('git_common_dir_unresolved', commonDir));
|
|
27
28
|
if (!head.ok)
|
|
28
29
|
blockers.push(gitBlocker('git_head_unresolved', head));
|
|
30
|
+
if (!status.ok)
|
|
31
|
+
blockers.push(gitBlocker('git_status_unresolved', status));
|
|
29
32
|
const repoRoot = top.ok ? path.resolve(gitOutputLine(top)) : null;
|
|
30
33
|
const resolvedGitDir = gitDir.ok ? absolutizeGitPath(cwd, gitOutputLine(gitDir)) : null;
|
|
31
34
|
const resolvedCommonDir = commonDir.ok ? absolutizeGitPath(cwd, gitOutputLine(commonDir)) : null;
|
|
@@ -43,6 +46,8 @@ export async function detectGitRepo(root = process.cwd()) {
|
|
|
43
46
|
worktree_git_dir: resolvedGitDir && resolvedCommonDir && resolvedGitDir !== resolvedCommonDir ? resolvedGitDir : null,
|
|
44
47
|
branch: gitOutputLine(branch) || null,
|
|
45
48
|
head: gitOutputLine(head) || null,
|
|
49
|
+
main_worktree_dirty: status.ok && status.stdout.trim().length > 0,
|
|
50
|
+
status_porcelain: status.stdout || '',
|
|
46
51
|
blockers
|
|
47
52
|
};
|
|
48
53
|
}
|
|
@@ -61,6 +66,8 @@ function baseDetection(cwd, gitBinary, gitAvailable, blockers) {
|
|
|
61
66
|
worktree_git_dir: null,
|
|
62
67
|
branch: null,
|
|
63
68
|
head: null,
|
|
69
|
+
main_worktree_dirty: false,
|
|
70
|
+
status_porcelain: '',
|
|
64
71
|
blockers
|
|
65
72
|
};
|
|
66
73
|
}
|
|
@@ -7,6 +7,8 @@ export async function cleanupGitWorktree(input) {
|
|
|
7
7
|
const status = await runGitCommand(worktreePath, ['status', '--porcelain=v1', '--untracked-files=all']);
|
|
8
8
|
const clean = status.ok && status.stdout.trim().length === 0;
|
|
9
9
|
if (!clean) {
|
|
10
|
+
const reason = `SKS retained dirty failed worker ${path.basename(worktreePath)}`;
|
|
11
|
+
const lock = await runGitCommand(repoRoot, ['worktree', 'lock', '--reason', reason, worktreePath]);
|
|
10
12
|
const lockPath = `${worktreePath}.retained.json`;
|
|
11
13
|
await writeJsonAtomic(lockPath, {
|
|
12
14
|
schema: 'sks.git-worktree-retention-lock.v1',
|
|
@@ -15,7 +17,10 @@ export async function cleanupGitWorktree(input) {
|
|
|
15
17
|
worktree_path: worktreePath,
|
|
16
18
|
branch: input.branch || null,
|
|
17
19
|
reason: status.ok ? 'dirty_worktree_retained' : 'status_failed_retained',
|
|
18
|
-
status_porcelain: status.stdout || null
|
|
20
|
+
status_porcelain: status.stdout || null,
|
|
21
|
+
git_locked: lock.ok,
|
|
22
|
+
unlock_command: `git worktree unlock ${JSON.stringify(worktreePath)}`,
|
|
23
|
+
cleanup_command: 'sks worktree cleanup --mission <id>'
|
|
19
24
|
});
|
|
20
25
|
return {
|
|
21
26
|
schema: 'sks.git-worktree-cleanup.v1',
|
|
@@ -27,7 +32,10 @@ export async function cleanupGitWorktree(input) {
|
|
|
27
32
|
clean: false,
|
|
28
33
|
action: 'retained_dirty',
|
|
29
34
|
retention_lock_path: lockPath,
|
|
30
|
-
blockers: []
|
|
35
|
+
blockers: lock.ok ? [] : ['git_worktree_lock_failed'],
|
|
36
|
+
git_locked: lock.ok,
|
|
37
|
+
unlock_command: `git worktree unlock ${JSON.stringify(worktreePath)}`,
|
|
38
|
+
cleanup_command: 'sks worktree cleanup --mission <id>'
|
|
31
39
|
};
|
|
32
40
|
}
|
|
33
41
|
const remove = await runGitCommand(repoRoot, ['worktree', 'remove', worktreePath]);
|
|
@@ -45,7 +53,10 @@ export async function cleanupGitWorktree(input) {
|
|
|
45
53
|
clean: true,
|
|
46
54
|
action: remove.ok ? 'removed' : 'remove_failed',
|
|
47
55
|
retention_lock_path: null,
|
|
48
|
-
blockers
|
|
56
|
+
blockers,
|
|
57
|
+
git_locked: false,
|
|
58
|
+
unlock_command: null,
|
|
59
|
+
cleanup_command: null
|
|
49
60
|
};
|
|
50
61
|
}
|
|
51
62
|
//# sourceMappingURL=git-worktree-cleanup.js.map
|
|
@@ -7,14 +7,19 @@ export async function exportGitWorktreeDiff(input) {
|
|
|
7
7
|
const branch = await runGitCommand(worktreePath, ['branch', '--show-current']);
|
|
8
8
|
const head = await runGitCommand(worktreePath, ['rev-parse', 'HEAD']);
|
|
9
9
|
const status = await runGitCommand(worktreePath, ['status', '--porcelain=v1', '--untracked-files=all']);
|
|
10
|
+
const untracked = await runGitCommand(worktreePath, ['ls-files', '--others', '--exclude-standard']);
|
|
11
|
+
const untrackedFiles = lines(untracked.stdout);
|
|
12
|
+
if (untrackedFiles.length) {
|
|
13
|
+
const addIntent = await runGitCommand(worktreePath, ['add', '-N', '--', ...untrackedFiles]);
|
|
14
|
+
if (!addIntent.ok)
|
|
15
|
+
blockers.push('git_worktree_untracked_intent_to_add_failed');
|
|
16
|
+
}
|
|
10
17
|
const diff = await runGitCommand(worktreePath, ['diff', '--binary', '--full-index', 'HEAD']);
|
|
11
18
|
const names = await runGitCommand(worktreePath, ['diff', '--name-only', 'HEAD']);
|
|
12
|
-
const untracked = await runGitCommand(worktreePath, ['ls-files', '--others', '--exclude-standard']);
|
|
13
19
|
if (!status.ok)
|
|
14
20
|
blockers.push('git_worktree_status_failed');
|
|
15
21
|
if (!diff.ok)
|
|
16
22
|
blockers.push('git_worktree_diff_failed');
|
|
17
|
-
const untrackedFiles = lines(untracked.stdout);
|
|
18
23
|
const trackedChanged = lines(names.stdout);
|
|
19
24
|
const changedFiles = [...new Set([...trackedChanged, ...untrackedFiles, ...statusFiles(status.stdout)])];
|
|
20
25
|
return {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import fsp from 'node:fs/promises';
|
|
3
|
-
import { ensureDir, nowIso, writeJsonAtomic } from '../fsx.js';
|
|
3
|
+
import { ensureDir, nowIso, readJson, writeJsonAtomic } from '../fsx.js';
|
|
4
4
|
import { evaluateGitWorktreeCapability } from './git-worktree-capability.js';
|
|
5
5
|
import { gitBlocker, gitOutputLine, runGitCommand } from './git-worktree-runner.js';
|
|
6
6
|
import { sanitizePathPart } from './git-worktree-root.js';
|
|
@@ -67,13 +67,20 @@ export async function allocateWorkerWorktree(input) {
|
|
|
67
67
|
return allocation;
|
|
68
68
|
}
|
|
69
69
|
async function appendWorktreeManifest(allocation) {
|
|
70
|
+
const current = await readJson(allocation.manifest_path, null).catch(() => null);
|
|
71
|
+
const allocations = Array.isArray(current?.allocations) ? current.allocations : [];
|
|
72
|
+
const key = (row) => `${row.mission_id}:${row.slot_id}:${row.generation_index}:${row.worker_id}`;
|
|
73
|
+
const nextAllocations = [
|
|
74
|
+
...allocations.filter((row) => key(row) !== key(allocation)),
|
|
75
|
+
allocation
|
|
76
|
+
];
|
|
70
77
|
const manifest = {
|
|
71
78
|
schema: 'sks.git-worktree-manifest.v1',
|
|
72
79
|
updated_at: nowIso(),
|
|
73
80
|
mission_id: allocation.mission_id,
|
|
74
81
|
repo_root: allocation.repo_root,
|
|
75
82
|
root: path.dirname(allocation.worktree_path),
|
|
76
|
-
allocations:
|
|
83
|
+
allocations: nextAllocations
|
|
77
84
|
};
|
|
78
85
|
await writeJsonAtomic(allocation.manifest_path, manifest);
|
|
79
86
|
}
|
|
@@ -20,11 +20,11 @@ export function buildGitWorktreePatchEnvelope(input) {
|
|
|
20
20
|
changed_files: changedFiles,
|
|
21
21
|
diff_bytes: input.diff.diff_bytes
|
|
22
22
|
},
|
|
23
|
-
operations:
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
operations: [{
|
|
24
|
+
op: 'git_apply_patch',
|
|
25
|
+
path: '.',
|
|
26
|
+
diff: input.diff.diff
|
|
27
|
+
}],
|
|
28
28
|
rationale: 'Process-generated patch envelope exported from an isolated Git worktree diff.',
|
|
29
29
|
verification_hint: {
|
|
30
30
|
command: 'git apply --3way --check <diff>',
|