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.
Files changed (78) hide show
  1. package/README.md +1 -1
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/crates/sks-core/src/main.rs +1 -1
  5. package/dist/.sks-build-stamp.json +4 -4
  6. package/dist/bin/sks.js +1 -1
  7. package/dist/build-manifest.json +44 -8
  8. package/dist/commands/zellij.js +144 -1
  9. package/dist/core/agents/agent-command-surface.js +4 -2
  10. package/dist/core/agents/agent-orchestrator.js +5 -2
  11. package/dist/core/agents/agent-patch-schema.js +4 -2
  12. package/dist/core/agents/native-cli-session-swarm.js +81 -9
  13. package/dist/core/commands/mad-sks-command.js +17 -1
  14. package/dist/core/commands/naruto-command.js +99 -7
  15. package/dist/core/fsx.js +1 -1
  16. package/dist/core/git/git-repo-detection.js +7 -0
  17. package/dist/core/git/git-worktree-cleanup.js +14 -3
  18. package/dist/core/git/git-worktree-diff.js +7 -2
  19. package/dist/core/git/git-worktree-manager.js +9 -2
  20. package/dist/core/git/git-worktree-patch-envelope.js +5 -5
  21. package/dist/core/naruto/naruto-active-pool.js +108 -0
  22. package/dist/core/naruto/naruto-concurrency-governor.js +16 -1
  23. package/dist/core/naruto/naruto-work-graph.js +2 -1
  24. package/dist/core/release/release-gate-cache-v2.js +117 -0
  25. package/dist/core/release/release-gate-dag.js +190 -0
  26. package/dist/core/release/release-gate-hermetic-env.js +32 -0
  27. package/dist/core/release/release-gate-node.js +62 -0
  28. package/dist/core/release/release-gate-report.js +11 -0
  29. package/dist/core/release/release-gate-resource-governor.js +54 -0
  30. package/dist/core/release/release-gate-scheduler.js +15 -0
  31. package/dist/core/version.js +1 -1
  32. package/dist/core/zellij/zellij-dashboard-pane.js +71 -0
  33. package/dist/core/zellij/zellij-dashboard-renderer.js +58 -0
  34. package/dist/core/zellij/zellij-launcher.js +3 -3
  35. package/dist/core/zellij/zellij-layout-builder.js +1 -1
  36. package/dist/core/zellij/zellij-right-column-layout-proof.js +42 -0
  37. package/dist/core/zellij/zellij-right-column-manager.js +245 -0
  38. package/dist/core/zellij/zellij-worker-pane-manager.js +180 -15
  39. package/dist/scripts/codex-sdk-release-review-pipeline-check.js +5 -5
  40. package/dist/scripts/doctor-fix-proves-codex-read-check.js +26 -5
  41. package/dist/scripts/git-worktree-diff-envelope-check.js +17 -0
  42. package/dist/scripts/git-worktree-dirty-lock-check.js +17 -0
  43. package/dist/scripts/git-worktree-dirty-main-detection-check.js +14 -0
  44. package/dist/scripts/git-worktree-integration-primary-check.js +22 -0
  45. package/dist/scripts/git-worktree-manifest-append-check.js +18 -0
  46. package/dist/scripts/git-worktree-untracked-diff-check.js +18 -0
  47. package/dist/scripts/lib/codex-sdk-gate-lib.js +4 -0
  48. package/dist/scripts/mad-sks-zellij-default-pane-worker-check.js +2 -2
  49. package/dist/scripts/naruto-concurrency-governor-check.js +2 -1
  50. package/dist/scripts/naruto-extreme-parallelism-check.js +22 -0
  51. package/dist/scripts/naruto-real-active-pool-check.js +38 -0
  52. package/dist/scripts/naruto-work-graph-check.js +1 -1
  53. package/dist/scripts/naruto-worktree-coding-blackbox.js +29 -0
  54. package/dist/scripts/naruto-zellij-dynamic-right-column-check.js +21 -0
  55. package/dist/scripts/product-design-auto-install-check.js +3 -3
  56. package/dist/scripts/product-design-plugin-routing-check.js +3 -3
  57. package/dist/scripts/release-cache-glob-hashing-check.js +42 -0
  58. package/dist/scripts/release-dag-full-coverage-check.js +35 -0
  59. package/dist/scripts/release-gate-dag-runner-check.js +17 -0
  60. package/dist/scripts/release-gate-dag-runner.js +32 -0
  61. package/dist/scripts/release-gate-worker.js +10 -0
  62. package/dist/scripts/release-metadata-1-19-check.js +8 -2
  63. package/dist/scripts/release-parallel-speed-budget-check.js +79 -0
  64. package/dist/scripts/release-readiness-report.js +1 -1
  65. package/dist/scripts/release-stability-report-check.js +99 -0
  66. package/dist/scripts/zellij-dashboard-pane-check.js +70 -0
  67. package/dist/scripts/zellij-dashboard-watch.js +41 -0
  68. package/dist/scripts/zellij-developer-controls-check.js +20 -0
  69. package/dist/scripts/zellij-dynamic-pane-lifecycle-check.js +21 -0
  70. package/dist/scripts/zellij-initial-main-only-blackbox.js +28 -0
  71. package/dist/scripts/zellij-right-column-geometry-proof.js +29 -0
  72. package/dist/scripts/zellij-right-column-manager-check.js +22 -0
  73. package/dist/scripts/zellij-worker-pane-manager-check.js +2 -1
  74. package/dist/scripts/zellij-worker-pane-manager-single-owner-check.js +7 -6
  75. package/dist/scripts/zellij-worker-pane-real-ui-blackbox.js +185 -0
  76. package/package.json +32 -5
  77. package/schemas/release/release-gate-node.schema.json +52 -0
  78. 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
- if (this.input.backend === 'zellij' && ctx.opts.real === true && ctx.opts.zellijPaneWorker !== false) {
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]) => `${key}=${shellQuote(String(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
- return `${envPrefix.join(' ')} ${command} > ${shellQuote(input.stdoutPath)} 2> ${shellQuote(input.stderrPath)}; code=$?; ${heartbeat}; ${hold}; exit $code`.trim();
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: zellijVisiblePanes,
160
+ slotCount: 0,
148
161
  dryRun: false,
149
162
  attach: false
150
163
  });
151
164
  if (liveZellij?.ok && liveZellij.capability?.status === 'ok') {
152
- 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));
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 missionId = String(readOption(args, '--mission', readOption(args, '--mission-id', 'latest')));
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.7';
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: [allocation]
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: changedFiles.map((file) => ({
24
- op: 'unified_diff',
25
- path: file,
26
- diff: input.diff.diff
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>',