sneakoscope 2.0.9 → 2.0.11

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 (74) hide show
  1. package/README.md +8 -4
  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 +37 -8
  8. package/dist/cli/command-registry.js +2 -0
  9. package/dist/cli/install-helpers.js +8 -20
  10. package/dist/commands/doctor.js +23 -10
  11. package/dist/commands/zellij-slot-column-anchor.js +23 -0
  12. package/dist/commands/zellij-slot-pane.js +26 -0
  13. package/dist/core/agents/agent-orchestrator.js +255 -16
  14. package/dist/core/agents/agent-patch-schema.js +8 -1
  15. package/dist/core/agents/agent-role-config.js +92 -0
  16. package/dist/core/agents/native-cli-session-swarm.js +186 -71
  17. package/dist/core/commands/naruto-command.js +165 -11
  18. package/dist/core/doctor/doctor-readiness-matrix.js +6 -0
  19. package/dist/core/fsx.js +1 -1
  20. package/dist/core/git/git-worktree-checkpoint.js +52 -0
  21. package/dist/core/git/git-worktree-cross-rebase.js +54 -0
  22. package/dist/core/git/git-worktree-merge-queue.js +69 -0
  23. package/dist/core/git/git-worktree-patch-envelope.js +8 -1
  24. package/dist/core/hooks-runtime.js +4 -0
  25. package/dist/core/init.js +3 -2
  26. package/dist/core/naruto/naruto-active-pool.js +35 -2
  27. package/dist/core/naruto/naruto-allocation-policy.js +99 -0
  28. package/dist/core/naruto/naruto-concurrency-governor.js +1 -1
  29. package/dist/core/naruto/naruto-real-worker-child.js +134 -0
  30. package/dist/core/naruto/naruto-real-worker-runtime.js +121 -0
  31. package/dist/core/naruto/naruto-rebalance-policy.js +36 -0
  32. package/dist/core/naruto/naruto-task-hints.js +71 -0
  33. package/dist/core/pipeline/finalize-pipeline-result.js +3 -1
  34. package/dist/core/pipeline/gpt-final-required.js +22 -2
  35. package/dist/core/version.js +1 -1
  36. package/dist/core/zellij/zellij-right-column-manager.js +111 -9
  37. package/dist/core/zellij/zellij-slot-column-anchor.js +59 -0
  38. package/dist/core/zellij/zellij-slot-pane-renderer.js +82 -0
  39. package/dist/core/zellij/zellij-ui-mode.js +16 -0
  40. package/dist/core/zellij/zellij-worker-pane-manager.js +104 -13
  41. package/dist/scripts/agent-role-config-repair-check.js +33 -0
  42. package/dist/scripts/git-worktree-checkpoint-check.js +20 -0
  43. package/dist/scripts/git-worktree-cross-rebase-check.js +27 -0
  44. package/dist/scripts/git-worktree-integration-primary-check.js +4 -2
  45. package/dist/scripts/git-worktree-integration-primary-runtime-check.js +20 -0
  46. package/dist/scripts/mutation-callsite-coverage-check.js +2 -1
  47. package/dist/scripts/naruto-actual-worker-control-plane-check.js +29 -0
  48. package/dist/scripts/naruto-allocation-policy-check.js +33 -0
  49. package/dist/scripts/naruto-extreme-parallelism-check.js +1 -1
  50. package/dist/scripts/naruto-extreme-parallelism-real-check.js +43 -0
  51. package/dist/scripts/naruto-orchestrator-runtime-source-check.js +11 -0
  52. package/dist/scripts/naruto-real-active-pool-check.js +3 -2
  53. package/dist/scripts/naruto-real-active-pool-runtime-check.js +55 -0
  54. package/dist/scripts/naruto-rebalance-policy-check.js +28 -0
  55. package/dist/scripts/naruto-shadow-clone-swarm-check.js +7 -3
  56. package/dist/scripts/naruto-zellij-dynamic-right-column-check.js +29 -2
  57. package/dist/scripts/readme-architecture-imagegen-official-check.js +4 -3
  58. package/dist/scripts/release-check-dynamic-execute.js +27 -1
  59. package/dist/scripts/release-check-dynamic.js +38 -11
  60. package/dist/scripts/release-check-stamp.js +7 -2
  61. package/dist/scripts/release-dag-full-coverage-check.js +15 -1
  62. package/dist/scripts/release-dynamic-performance-check.js +31 -1
  63. package/dist/scripts/release-gate-existence-audit.js +29 -33
  64. package/dist/scripts/release-readiness-report.js +14 -3
  65. package/dist/scripts/zellij-first-slot-down-stack-check.js +20 -0
  66. package/dist/scripts/zellij-first-slot-down-stack-real-check.js +16 -0
  67. package/dist/scripts/zellij-right-column-geometry-proof.js +155 -22
  68. package/dist/scripts/zellij-right-column-headless-overflow-check.js +22 -0
  69. package/dist/scripts/zellij-right-column-manager-check.js +9 -4
  70. package/dist/scripts/zellij-slot-column-anchor-check.js +24 -0
  71. package/dist/scripts/zellij-slot-only-ui-check.js +24 -0
  72. package/dist/scripts/zellij-slot-pane-renderer-check.js +38 -0
  73. package/dist/scripts/zellij-worker-pane-manager-single-owner-check.js +11 -4
  74. package/package.json +22 -5
@@ -5,8 +5,10 @@ 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
+ import { closeWorkerInRightColumn, recordHeadlessWorkerInRightColumn } from '../zellij/zellij-right-column-manager.js';
9
9
  import { resolveProviderContext } from '../provider/provider-context.js';
10
+ import { buildZellijSlotPaneCommand } from '../zellij/zellij-slot-pane-renderer.js';
11
+ import { resolveZellijUiMode } from '../zellij/zellij-ui-mode.js';
10
12
  export const NATIVE_CLI_SESSION_SWARM_SCHEMA = 'sks.agent-native-cli-session-swarm.v1';
11
13
  export function createNativeCliSessionSwarmRecorder(root, input) {
12
14
  return new NativeCliSessionSwarmRecorder(root, input);
@@ -19,6 +21,7 @@ class NativeCliSessionSwarmRecorder {
19
21
  maxObserved = 0;
20
22
  writeLock = Promise.resolve();
21
23
  nextPaneToken = -1;
24
+ visibleZellijReservations = new Set();
22
25
  constructor(root, input) {
23
26
  this.root = root;
24
27
  this.input = input;
@@ -103,10 +106,12 @@ class NativeCliSessionSwarmRecorder {
103
106
  const stdout = fs.createWriteStream(path.join(this.root, stdoutRel), { flags: 'a' });
104
107
  const stderr = fs.createWriteStream(path.join(this.root, stderrRel), { flags: 'a' });
105
108
  const placement = String(ctx.opts.workerPlacement || this.input.workerPlacement || (this.input.backend === 'zellij' ? 'zellij-pane' : 'process'));
106
- const useZellijPane = placement === 'zellij-pane'
109
+ const zellijReservation = placement === 'zellij-pane'
107
110
  && ctx.opts.zellijPaneWorker !== false
108
111
  && (ctx.opts.zellijSessionName || this.input.missionId)
109
- && this.visibleZellijPaneCount() < this.zellijVisiblePaneCap(ctx.opts);
112
+ ? this.reserveVisibleZellijPane(ctx.opts, String(ctx.agent.session_id || ctx.agent.id || `${Date.now()}:${Math.random()}`))
113
+ : null;
114
+ const useZellijPane = Boolean(zellijReservation);
110
115
  if (useZellijPane) {
111
116
  stdout.end();
112
117
  stderr.end();
@@ -118,7 +123,8 @@ class NativeCliSessionSwarmRecorder {
118
123
  stdoutRel,
119
124
  stderrRel,
120
125
  heartbeatRel,
121
- workerDirRel
126
+ workerDirRel,
127
+ zellijReservation
122
128
  });
123
129
  }
124
130
  if (placement === 'zellij-pane' && ctx.opts.zellijPaneWorker !== false && !useZellijPane) {
@@ -174,6 +180,17 @@ class NativeCliSessionSwarmRecorder {
174
180
  record.exit_code = exit.code;
175
181
  record.signal = exit.signal;
176
182
  record.status = exit.code === 0 ? 'closed' : 'failed';
183
+ if (record.worker_placement === 'headless') {
184
+ await closeWorkerInRightColumn({
185
+ root: this.root,
186
+ projectRoot: ctx.opts.projectRoot || this.input.projectRoot || ctx.opts.cwd,
187
+ missionId: this.input.missionId,
188
+ slotId: String(ctx.agent.slot_id || ctx.agent.id || 'slot-001'),
189
+ generationIndex: Number(ctx.agent.generation_index || 1),
190
+ paneId: null,
191
+ status: record.status === 'closed' ? 'closed' : 'failed'
192
+ }).catch(() => null);
193
+ }
177
194
  const parsed = await readJson(path.join(this.root, resultRel), null).catch(() => null);
178
195
  if (!parsed) {
179
196
  record.blockers = ['native_cli_worker_result_missing'];
@@ -217,77 +234,102 @@ class NativeCliSessionSwarmRecorder {
217
234
  route: this.input.route,
218
235
  serviceTier: this.input.fastModePolicy.service_tier
219
236
  });
220
- const workerCommand = buildPaneWorkerCommand({
221
- args: input.args,
222
- stdoutPath: path.join(this.root, input.stdoutRel),
223
- stderrPath: path.join(this.root, input.stderrRel),
224
- heartbeatPath: path.join(this.root, input.heartbeatRel),
225
- header: buildPaneWorkerHeader({
237
+ const uiMode = resolveZellijUiMode(Array.isArray(input.ctx.opts.args) ? input.ctx.opts.args : [], process.env);
238
+ const workerEnv = {
239
+ ...(input.ctx.opts.env || {}),
240
+ ...fastModeEnv(this.input.fastModePolicy),
241
+ SKS_AGENT_WORKER: '1',
242
+ SKS_PIPELINE_MODE: 'agent-worker',
243
+ SKS_DISABLE_ROUTE_RECURSION: '1',
244
+ SKS_PARENT_MISSION_ID: this.input.missionId,
245
+ SKS_AGENT_SESSION_ID: String(input.ctx.agent.session_id || ''),
246
+ SKS_AGENT_SLOT_ID: slotId,
247
+ SKS_AGENT_GENERATION_INDEX: String(input.ctx.agent.generation_index || 1),
248
+ ...(input.ctx.opts.ollamaModel ? { SKS_OLLAMA_MODEL: String(input.ctx.opts.ollamaModel) } : {}),
249
+ ...(input.ctx.opts.ollamaBaseUrl ? { SKS_OLLAMA_BASE_URL: String(input.ctx.opts.ollamaBaseUrl) } : {}),
250
+ SKS_ZELLIJ_SESSION_NAME: sessionName
251
+ };
252
+ const role = String(input.ctx.agent.naruto_role || input.ctx.agent.role || input.ctx.agent.persona_id || 'worker');
253
+ const workerCommand = uiMode === 'full-debug'
254
+ ? buildPaneWorkerCommand({
255
+ args: input.args,
256
+ stdoutPath: path.join(this.root, input.stdoutRel),
257
+ stderrPath: path.join(this.root, input.stderrRel),
258
+ heartbeatPath: path.join(this.root, input.heartbeatRel),
259
+ header: buildPaneWorkerHeader({
260
+ slotId,
261
+ generationIndex: Number(input.ctx.agent.generation_index || 1),
262
+ role,
263
+ backend: this.input.backend,
264
+ provider: providerContext.provider,
265
+ serviceTier: this.input.fastModePolicy.service_tier,
266
+ worktree,
267
+ task: input.ctx.slice?.description || input.ctx.slice?.title || input.ctx.slice?.id || ''
268
+ }),
269
+ env: {
270
+ ...workerEnv,
271
+ SKS_ZELLIJ_WORKER_PANE: '1'
272
+ }
273
+ })
274
+ : buildZellijSlotPaneCommand({
275
+ cliPath: String(input.args[0] || ''),
276
+ missionId: this.input.missionId,
226
277
  slotId,
227
278
  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',
279
+ artifactDir: path.join(this.root, input.workerDirRel),
229
280
  backend: this.input.backend,
230
- provider: providerContext.provider,
281
+ role,
282
+ mode: uiMode,
283
+ watch: true
284
+ });
285
+ let paneRecord;
286
+ try {
287
+ paneRecord = await openWorkerPane({
288
+ root: this.root,
289
+ missionId: this.input.missionId,
290
+ sessionName,
291
+ slotId,
292
+ generationIndex: Number(input.ctx.agent.generation_index || 1),
293
+ sessionId: String(input.ctx.agent.session_id || ''),
294
+ workerArtifactDir: input.workerDirRel,
295
+ workerCommand,
296
+ resultPath: input.resultRel,
297
+ heartbeatPath: input.heartbeatRel,
298
+ patchEnvelopePath: input.record.patch_envelope_path,
299
+ stdoutLog: input.stdoutRel,
300
+ stderrLog: input.stderrRel,
301
+ cwd: workerCwd,
302
+ providerContext,
231
303
  serviceTier: this.input.fastModePolicy.service_tier,
232
- worktree,
233
- task: input.ctx.slice?.description || input.ctx.slice?.title || input.ctx.slice?.id || ''
234
- }),
235
- env: {
236
- ...(input.ctx.opts.env || {}),
237
- ...fastModeEnv(this.input.fastModePolicy),
238
- SKS_AGENT_WORKER: '1',
239
- SKS_PIPELINE_MODE: 'agent-worker',
240
- SKS_DISABLE_ROUTE_RECURSION: '1',
241
- SKS_PARENT_MISSION_ID: this.input.missionId,
242
- SKS_AGENT_SESSION_ID: String(input.ctx.agent.session_id || ''),
243
- SKS_AGENT_SLOT_ID: slotId,
244
- SKS_AGENT_GENERATION_INDEX: String(input.ctx.agent.generation_index || 1),
245
- ...(input.ctx.opts.ollamaModel ? { SKS_OLLAMA_MODEL: String(input.ctx.opts.ollamaModel) } : {}),
246
- ...(input.ctx.opts.ollamaBaseUrl ? { SKS_OLLAMA_BASE_URL: String(input.ctx.opts.ollamaBaseUrl) } : {}),
247
- SKS_ZELLIJ_WORKER_PANE: '1',
248
- SKS_ZELLIJ_SESSION_NAME: sessionName
249
- }
250
- });
251
- let paneRecord = await openWorkerPane({
252
- root: this.root,
253
- missionId: this.input.missionId,
254
- sessionName,
255
- slotId,
256
- generationIndex: Number(input.ctx.agent.generation_index || 1),
257
- sessionId: String(input.ctx.agent.session_id || ''),
258
- workerArtifactDir: input.workerDirRel,
259
- workerCommand,
260
- resultPath: input.resultRel,
261
- heartbeatPath: input.heartbeatRel,
262
- patchEnvelopePath: input.record.patch_envelope_path,
263
- stdoutLog: input.stdoutRel,
264
- stderrLog: input.stderrRel,
265
- cwd: workerCwd,
266
- providerContext,
267
- serviceTier: this.input.fastModePolicy.service_tier,
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
- }
288
- });
304
+ worktree: worktree ? { id: worktree.id, path: worktree.path, branch: worktree.branch } : null,
305
+ backend: this.input.backend,
306
+ uiMode,
307
+ projectRoot: input.ctx.opts.projectRoot || this.input.projectRoot || input.ctx.opts.cwd,
308
+ rightColumnMode: 'spawn-on-first-worker',
309
+ visiblePaneCap: this.zellijVisiblePaneCap(input.ctx.opts),
310
+ dashboardSnapshot: {
311
+ mode: this.input.route || '$Agent',
312
+ backend_counts: { [this.input.backend]: this.input.targetActiveSlots },
313
+ placement_counts: {
314
+ 'zellij-pane': this.zellijVisiblePaneCap(input.ctx.opts),
315
+ headless: Math.max(0, this.input.targetActiveSlots - this.zellijVisiblePaneCap(input.ctx.opts))
316
+ },
317
+ active_workers: this.input.targetActiveSlots,
318
+ visible_panes: this.zellijVisiblePaneCap(input.ctx.opts),
319
+ headless_workers: Math.max(0, this.input.targetActiveSlots - this.zellijVisiblePaneCap(input.ctx.opts)),
320
+ queue_depth: Math.max(0, this.input.requestedAgents - this.input.targetActiveSlots),
321
+ local_llm: { tps: 0, queue: 0 },
322
+ gpt_final_status: 'pending',
323
+ gate_progress: 'worker-spawn'
324
+ }
325
+ });
326
+ }
327
+ finally {
328
+ if (input.zellijReservation)
329
+ this.releaseVisibleZellijReservation(input.zellijReservation);
330
+ }
289
331
  const launchBlockers = paneRecord.blockers || [];
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>'];
332
+ input.record.command_line = ['zellij', '--session', sessionName, 'action', 'new-pane', '--direction', paneRecord.direction_applied, '--name', paneRecord.pane_name, '--', 'sh', '-lc', uiMode === 'full-debug' ? '<native-cli-worker-command>' : '<zellij-slot-pane-renderer-command>'];
291
333
  input.record.zellij_session_name = sessionName;
292
334
  input.record.zellij_pane_id = paneRecord.pane_id || null;
293
335
  input.record.zellij_pane_id_source = paneRecord.pane_id_source;
@@ -300,6 +342,8 @@ class NativeCliSessionSwarmRecorder {
300
342
  input.record.service_tier = paneRecord.service_tier;
301
343
  input.record.provider_context = paneRecord.provider_context;
302
344
  input.record.worktree = worktree;
345
+ input.record.zellij_ui_mode = uiMode;
346
+ input.record.slot_visualization = uiMode === 'full-debug' ? 'worker-command-pane' : 'zellij-slot-pane-renderer';
303
347
  input.record.status = launchBlockers.length ? 'failed' : 'running';
304
348
  input.record.blockers = launchBlockers;
305
349
  await this.record(input.record);
@@ -325,6 +369,20 @@ class NativeCliSessionSwarmRecorder {
325
369
  goal_mode_ref: input.ctx.agent.goal_mode_ref || null
326
370
  });
327
371
  }
372
+ const processRun = uiMode === 'full-debug'
373
+ ? null
374
+ : await this.spawnCompactSlotWorkerProcess({
375
+ args: input.args,
376
+ cwd: workerCwd,
377
+ env: workerEnv,
378
+ stdoutRel: input.stdoutRel,
379
+ stderrRel: input.stderrRel
380
+ });
381
+ if (processRun?.pid) {
382
+ input.record.pid = processRun.pid;
383
+ input.record.process_id = processRun.pid;
384
+ await this.record(input.record);
385
+ }
328
386
  await waitForWorkerHeartbeat(path.join(this.root, input.heartbeatRel), Number(process.env.SKS_ZELLIJ_WORKER_HEARTBEAT_TIMEOUT_MS || 30000));
329
387
  await appendJsonl(path.join(this.root, input.workerDirRel, 'zellij-worker-pane-events.jsonl'), {
330
388
  schema: 'sks.zellij-worker-pane-event.v1',
@@ -337,6 +395,7 @@ class NativeCliSessionSwarmRecorder {
337
395
  worker_artifact_dir: input.workerDirRel
338
396
  });
339
397
  const parsed = await waitForWorkerResult(path.join(this.root, input.resultRel), Number(process.env.SKS_ZELLIJ_WORKER_RESULT_TIMEOUT_MS || 120000));
398
+ const compactExit = processRun ? await processRun.wait(parsed ? 10000 : 1000) : null;
340
399
  this.active.delete(activeToken);
341
400
  input.record.closed_at = nowIso();
342
401
  const workerProcessReport = await readJson(path.join(this.root, input.workerDirRel, 'worker-process-report.json'), null).catch(() => null);
@@ -368,8 +427,10 @@ class NativeCliSessionSwarmRecorder {
368
427
  result_path: input.resultRel
369
428
  });
370
429
  }
371
- input.record.pid = Number(workerProcessReport?.pid) || null;
430
+ input.record.pid = Number(workerProcessReport?.pid || processRun?.pid) || null;
372
431
  input.record.process_id = input.record.pid;
432
+ input.record.compact_worker_exit_code = compactExit?.code ?? null;
433
+ input.record.compact_worker_signal = compactExit?.signal ?? null;
373
434
  input.record.sdk_thread_id = sdkThreadId;
374
435
  input.record.sdk_run_id = sdkRunId;
375
436
  input.record.stream_event_count = Number(workerProcessReport?.stream_event_count || workerProcessReport?.backend_router_report?.stream_event_count || 0);
@@ -479,6 +540,46 @@ class NativeCliSessionSwarmRecorder {
479
540
  visibleZellijPaneCount() {
480
541
  return this.records.filter((row) => row.scaling_primitive === 'native_cli_process_in_zellij_worker_pane' && (row.status === 'launching' || row.status === 'running')).length;
481
542
  }
543
+ reserveVisibleZellijPane(opts = {}, token) {
544
+ const cap = this.zellijVisiblePaneCap(opts);
545
+ if (this.visibleZellijPaneCount() + this.visibleZellijReservations.size >= cap)
546
+ return null;
547
+ this.visibleZellijReservations.add(token);
548
+ return token;
549
+ }
550
+ releaseVisibleZellijReservation(token) {
551
+ this.visibleZellijReservations.delete(token);
552
+ }
553
+ async spawnCompactSlotWorkerProcess(input) {
554
+ const stdout = fs.createWriteStream(path.join(this.root, input.stdoutRel), { flags: 'a' });
555
+ const stderr = fs.createWriteStream(path.join(this.root, input.stderrRel), { flags: 'a' });
556
+ const child = spawn(process.execPath, input.args, {
557
+ cwd: input.cwd,
558
+ env: {
559
+ ...process.env,
560
+ ...Object.fromEntries(Object.entries(input.env).filter(([, value]) => value != null).map(([key, value]) => [key, String(value)]))
561
+ },
562
+ stdio: ['ignore', 'pipe', 'pipe']
563
+ });
564
+ child.stdout?.pipe(stdout);
565
+ child.stderr?.pipe(stderr);
566
+ const exitPromise = new Promise((resolve) => {
567
+ child.on('close', (code, signal) => {
568
+ stdout.end();
569
+ stderr.end();
570
+ resolve({ code, signal });
571
+ });
572
+ child.on('error', () => {
573
+ stdout.end();
574
+ stderr.end();
575
+ resolve({ code: 1, signal: null });
576
+ });
577
+ });
578
+ return {
579
+ pid: child.pid || null,
580
+ wait: async (timeoutMs) => waitForChildExit(child, exitPromise, timeoutMs)
581
+ };
582
+ }
482
583
  }
483
584
  export function buildPaneWorkerCommand(input) {
484
585
  const envPrefix = Object.entries(input.env)
@@ -555,4 +656,18 @@ function normalizeWorkerWorktree(value) {
555
656
  main_repo_root: value?.main_repo_root == null ? null : String(value.main_repo_root)
556
657
  };
557
658
  }
659
+ async function waitForChildExit(child, exitPromise, timeoutMs) {
660
+ let timer = null;
661
+ const timeout = new Promise((resolve) => {
662
+ timer = setTimeout(() => {
663
+ if (!child.killed)
664
+ child.kill();
665
+ resolve({ code: null, signal: 'SIGTERM' });
666
+ }, Math.max(1000, timeoutMs));
667
+ });
668
+ const result = await Promise.race([exitPromise, timeout]);
669
+ if (timer)
670
+ clearTimeout(timer);
671
+ return result;
672
+ }
558
673
  //# sourceMappingURL=native-cli-session-swarm.js.map
@@ -1,6 +1,6 @@
1
1
  import path from 'node:path';
2
2
  import { createMission, findLatestMission, loadMission } from '../mission.js';
3
- import { readJson, sksRoot, writeJsonAtomic } from '../fsx.js';
3
+ import { nowIso, readJson, sksRoot, writeJsonAtomic } from '../fsx.js';
4
4
  import { runNativeAgentOrchestrator } from '../agents/agent-orchestrator.js';
5
5
  import { classifyOllamaWorkerSlice } from '../agents/agent-runner-ollama.js';
6
6
  import { buildNarutoCloneRoster, systemSafeNarutoConcurrency } from '../agents/agent-roster.js';
@@ -11,6 +11,9 @@ 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
13
  import { runNarutoActivePool, runNarutoRealActivePool } from '../naruto/naruto-active-pool.js';
14
+ import { collectActualNarutoWorker, spawnActualNarutoWorker } from '../naruto/naruto-real-worker-runtime.js';
15
+ import { allocateNarutoTasksToWorkers } from '../naruto/naruto-allocation-policy.js';
16
+ import { rebalanceNarutoReadyWork } from '../naruto/naruto-rebalance-policy.js';
14
17
  import { buildNarutoVerificationDag } from '../naruto/naruto-verification-dag.js';
15
18
  import { buildNarutoGptFinalPack } from '../naruto/naruto-gpt-final-pack.js';
16
19
  import { planNarutoZellijDashboard } from '../zellij/zellij-naruto-dashboard.js';
@@ -102,6 +105,50 @@ async function narutoRun(parsed) {
102
105
  worktreePolicy
103
106
  });
104
107
  const roleDistribution = buildNarutoRoleDistribution(workGraph.work_items, { readonly: parsed.readonly });
108
+ const allocationWorkers = buildNarutoAllocationWorkers(workGraph, roleDistribution, roster);
109
+ const allocationAssignments = allocateNarutoTasksToWorkers(workGraph.work_items, allocationWorkers);
110
+ const allocationPolicy = {
111
+ schema: 'sks.naruto-allocation-policy.v1',
112
+ generated_at: nowIso(),
113
+ ok: allocationWorkers.length > 0 && allocationAssignments.length === workGraph.work_items.length,
114
+ scoring_model: {
115
+ same_primary_role: 18,
116
+ declared_role: 12,
117
+ same_path_lane: 12,
118
+ overlap_each: 4,
119
+ assigned_task_penalty_each: -4,
120
+ write_conflict_penalty: -20,
121
+ dependency_incomplete: '-Infinity'
122
+ },
123
+ workers: allocationWorkers,
124
+ assignments: allocationAssignments.map((row) => ({
125
+ task_id: row.id,
126
+ owner: row.owner,
127
+ score: Number.isFinite(row.allocation_score) ? row.allocation_score : '-Infinity',
128
+ reason: row.allocation_reason,
129
+ role: row.required_role,
130
+ kind: row.kind,
131
+ paths: row.hints.paths,
132
+ domains: row.hints.domains,
133
+ write_paths: row.hints.writePaths
134
+ })),
135
+ blockers: allocationWorkers.length ? [] : ['naruto_allocation_workers_missing']
136
+ };
137
+ const rebalanceDecisions = rebalanceNarutoReadyWork({
138
+ tasks: workGraph.work_items.map((item) => ({ ...item, owner: null, status: 'pending' })),
139
+ workers: allocationWorkers.map((worker) => ({ ...worker, alive: true, state: 'idle' })),
140
+ completedTaskIds: [],
141
+ reclaimedTaskIds: []
142
+ });
143
+ const rebalancePolicy = {
144
+ schema: 'sks.naruto-rebalance-policy.v1',
145
+ generated_at: nowIso(),
146
+ ok: true,
147
+ trigger: 'idle_worker_ready_queue',
148
+ decisions: rebalanceDecisions,
149
+ blocked_by_dependency_count: workGraph.work_items.filter((item) => item.dependencies.length > 0).length,
150
+ blockers: []
151
+ };
105
152
  const governor = decideNarutoConcurrency({
106
153
  requestedClones: roster.agent_count,
107
154
  totalWorkItems: workGraph.total_work_items,
@@ -112,14 +159,52 @@ async function narutoRun(parsed) {
112
159
  const activeSlots = Math.max(1, Math.min(roster.agent_count, parsed.concurrency || Math.max(governor.safe_active_workers, backendMinimum), safe.cap));
113
160
  const zellijVisiblePanes = Math.max(1, Math.min(activeSlots, governor.safe_zellij_visible_panes));
114
161
  const activePool = await runNarutoActivePool({ graph: workGraph, governor: { ...governor, safe_active_workers: activeSlots } });
162
+ const realRuntimeSmokeGraph = buildNarutoWorkGraph({
163
+ prompt: parsed.prompt,
164
+ requestedClones: Math.min(2, roster.agent_count),
165
+ totalWorkItems: Math.min(2, workGraph.total_work_items),
166
+ readonly: true,
167
+ writeCapable: false,
168
+ leaseBasePath: patchEnvelopeBasePath,
169
+ maxActiveWorkers: Math.min(2, activeSlots),
170
+ worktreePolicy: {
171
+ mode: 'patch-envelope-only',
172
+ required: false,
173
+ main_repo_root: worktreePolicy.main_repo_root,
174
+ worktree_root: null,
175
+ fallback_reason: 'pre_run_smoke_never_owns_production_runtime'
176
+ }
177
+ });
178
+ const realRuntimeWorktreePolicy = {
179
+ mode: 'patch-envelope-only',
180
+ required: false,
181
+ main_repo_root: worktreePolicy.main_repo_root,
182
+ worktree_root: null,
183
+ fallback_reason: 'pre_run_smoke_never_owns_production_runtime'
184
+ };
115
185
  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() }),
186
+ graph: realRuntimeSmokeGraph,
187
+ governor: { ...governor, safe_active_workers: Math.min(2, activeSlots), safe_zellij_visible_panes: Math.min(1, zellijVisiblePanes) },
188
+ spawnWorker: async (item, placement) => spawnActualNarutoWorker({
189
+ root,
190
+ missionId: mission.id,
191
+ item,
192
+ placement,
193
+ backend: 'fake',
194
+ worktreePolicy: realRuntimeWorktreePolicy,
195
+ zellijSessionName: `sks-${mission.id}`,
196
+ visiblePaneCap: zellijVisiblePanes
197
+ }),
198
+ collectWorker: async (handle) => collectActualNarutoWorker(handle),
120
199
  enqueueVerification: async () => undefined,
121
200
  updateDashboard: async () => undefined
122
201
  });
202
+ const realActivePoolSmoke = {
203
+ ...realActivePool,
204
+ runtime_source_of_truth: 'pre_run_smoke_only',
205
+ production_runtime_source_of_truth: 'agent-orchestrator-scheduler',
206
+ smoke_graph_total_work_items: realRuntimeSmokeGraph.total_work_items
207
+ };
123
208
  const verificationDag = buildNarutoVerificationDag(workGraph, { cwd: root });
124
209
  const gptFinalPack = buildNarutoGptFinalPack({
125
210
  missionId: mission.id,
@@ -143,7 +228,9 @@ async function narutoRun(parsed) {
143
228
  roleDistribution,
144
229
  governor,
145
230
  activePool,
146
- realActivePool,
231
+ realActivePool: realActivePoolSmoke,
232
+ allocationPolicy,
233
+ rebalancePolicy,
147
234
  verificationDag,
148
235
  gptFinalPack,
149
236
  zellijDashboard,
@@ -235,6 +322,8 @@ async function narutoRun(parsed) {
235
322
  max_clones: MAX_NARUTO_AGENT_COUNT,
236
323
  concurrency: result.target_active_slots ?? activeSlots,
237
324
  target_active_slots: result.target_active_slots ?? activeSlots,
325
+ runtime_source_of_truth: 'agent-orchestrator-scheduler',
326
+ pre_run_real_active_pool_source: 'smoke_only',
238
327
  concurrency_capped: clones > (result.target_active_slots ?? activeSlots),
239
328
  system: { cores: safe.cores, free_gb: safe.free_gb, safe_concurrency: safe.cap, heavy_backend: safe.heavy },
240
329
  work_graph: {
@@ -248,6 +337,8 @@ async function narutoRun(parsed) {
248
337
  },
249
338
  git_worktree: gitWorktreeCapability,
250
339
  role_distribution: roleDistribution,
340
+ allocation_policy: allocationPolicy,
341
+ rebalance_policy: rebalancePolicy,
251
342
  concurrency_governor: governor,
252
343
  active_pool: {
253
344
  ok: activePool.ok,
@@ -255,15 +346,23 @@ async function narutoRun(parsed) {
255
346
  refill_events: activePool.refill_events,
256
347
  completed_count: activePool.completed_count,
257
348
  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
349
+ ok: realActivePoolSmoke.ok,
350
+ runtime_source_of_truth: realActivePoolSmoke.runtime_source_of_truth,
351
+ production_runtime_source_of_truth: realActivePoolSmoke.production_runtime_source_of_truth,
352
+ active_cap: realActivePoolSmoke.active_cap,
353
+ max_observed_active_workers: realActivePoolSmoke.max_observed_active_workers,
354
+ average_active_workers: realActivePoolSmoke.average_active_workers,
355
+ active_pool_utilization: realActivePoolSmoke.active_pool_utilization,
356
+ refill_latency_ms_p95: realActivePoolSmoke.refill_latency_ms_p95,
357
+ visible_workers: realActivePoolSmoke.visible_workers,
358
+ headless_workers: realActivePoolSmoke.headless_workers,
359
+ worker_lifecycle_count: realActivePoolSmoke.worker_lifecycle.length,
360
+ worker_lifecycle_sample: realActivePoolSmoke.worker_lifecycle.slice(0, 5)
262
361
  }
263
362
  },
264
363
  local_worker: localWorkerSummary,
265
364
  proof: result.proof?.status || 'missing',
266
- run: result,
365
+ run: compactNarutoRunResult(result),
267
366
  zellij: null
268
367
  };
269
368
  summary.zellij = liveZellij;
@@ -280,6 +379,57 @@ async function narutoRun(parsed) {
280
379
  console.log('Zellij: optional live panes unavailable (' + ((summary.zellij.warnings || []).join('; ') || summary.zellij.capability?.status || 'unknown') + ')');
281
380
  });
282
381
  }
382
+ function compactNarutoRunResult(result) {
383
+ return {
384
+ schema: result?.schema || 'sks.agent-run.v1',
385
+ ok: result?.ok === true,
386
+ mission_id: result?.mission_id || null,
387
+ route: result?.route || NARUTO_ROUTE,
388
+ backend: result?.backend || null,
389
+ target_active_slots: result?.target_active_slots ?? null,
390
+ proof: result?.proof ? {
391
+ ok: result.proof.ok === true,
392
+ status: result.proof.status || null,
393
+ blockers: result.proof.blockers || []
394
+ } : null,
395
+ scheduler: result?.scheduler ? {
396
+ state: {
397
+ completed_count: result.scheduler.state?.completed_count ?? result.scheduler.completed_count ?? null,
398
+ failed_count: result.scheduler.state?.failed_count ?? result.scheduler.failed_count ?? null,
399
+ blocked_count: result.scheduler.state?.blocked_count ?? result.scheduler.blocked_count ?? null,
400
+ max_observed_active_slots: result.scheduler.state?.max_observed_active_slots ?? result.scheduler.max_observed_active_slots ?? null
401
+ }
402
+ } : null,
403
+ artifacts: {
404
+ ledger_root: result?.ledger_root || null,
405
+ proof: 'agent-proof-evidence.json',
406
+ scheduler: 'agent-scheduler-state.json',
407
+ native_cli_session_swarm: 'agent-native-cli-session-swarm.json',
408
+ naruto_real_active_pool: 'naruto-real-active-pool.json'
409
+ }
410
+ };
411
+ }
412
+ function buildNarutoAllocationWorkers(workGraph, roleDistribution, roster) {
413
+ const workItems = Array.isArray(workGraph?.work_items) ? workGraph.work_items : [];
414
+ const roleByWorkItem = new Map((roleDistribution?.work_item_roles || []).map((row) => [String(row.work_item_id), String(row.role || '')]));
415
+ const rosterRows = Array.isArray(roster?.roster) ? roster.roster : [];
416
+ const count = Math.max(1, Math.min(Number(roster?.agent_count || rosterRows.length || workItems.length || 1), Math.max(1, workItems.length || 1)));
417
+ return Array.from({ length: count }, (_unused, index) => {
418
+ const agent = rosterRows[index] || {};
419
+ const item = workItems[index % Math.max(1, workItems.length)] || {};
420
+ const role = String(agent.naruto_role || agent.role || roleByWorkItem.get(String(item.id || '')) || item.required_role || 'worker');
421
+ return {
422
+ id: String(agent.id || `clone-${String(index + 1).padStart(3, '0')}`),
423
+ role,
424
+ lane: narutoAllocationLane(item)
425
+ };
426
+ });
427
+ }
428
+ function narutoAllocationLane(item) {
429
+ const firstPath = String((item?.write_paths || item?.target_paths || item?.readonly_paths || [])[0] || '');
430
+ const parts = firstPath.replace(/\\/g, '/').split('/').filter(Boolean);
431
+ return parts.slice(0, Math.min(2, parts.length)).join('/') || null;
432
+ }
283
433
  function summarizeNarutoLocalWorkerResult(localWorker, result) {
284
434
  const backendCounts = {};
285
435
  const rows = Array.isArray(result?.results) ? result.results : [];
@@ -443,6 +593,10 @@ async function writeNarutoArtifacts(ledgerRoot, artifacts) {
443
593
  await writeJsonAtomic(path.join(ledgerRoot, 'naruto-active-pool.json'), artifacts.activePool);
444
594
  if (artifacts.realActivePool)
445
595
  await writeJsonAtomic(path.join(ledgerRoot, 'naruto-real-active-pool.json'), artifacts.realActivePool);
596
+ if (artifacts.allocationPolicy)
597
+ await writeJsonAtomic(path.join(ledgerRoot, 'naruto-allocation-policy.json'), artifacts.allocationPolicy);
598
+ if (artifacts.rebalancePolicy)
599
+ await writeJsonAtomic(path.join(ledgerRoot, 'naruto-rebalance-policy.json'), artifacts.rebalancePolicy);
446
600
  await writeJsonAtomic(path.join(ledgerRoot, 'naruto-verification-dag.json'), artifacts.verificationDag);
447
601
  await writeJsonAtomic(path.join(ledgerRoot, 'naruto-gpt-final-pack.json'), artifacts.gptFinalPack);
448
602
  await writeJsonAtomic(path.join(ledgerRoot, 'naruto-zellij-dashboard.json'), artifacts.zellijDashboard);
@@ -62,6 +62,11 @@ export function buildDoctorReadinessMatrix(input = {}) {
62
62
  warnings.add('local_llm_degraded');
63
63
  if (localModel.enabled === true && localStatus === 'blocked')
64
64
  warnings.add('local_llm_blocked_worker_tier_disabled');
65
+ const agentRoleConfig = input.agent_role_config || {};
66
+ if (agentRoleConfig.ok === false)
67
+ blockers.add('agent_role_config_repair_failed');
68
+ if (Array.isArray(agentRoleConfig.missing) && agentRoleConfig.missing.length && agentRoleConfig.apply !== true)
69
+ warnings.add('agent_role_config_missing_repair_available');
65
70
  const localCollaborationPolicy = resolveLocalCollaborationPolicy({ mode: input.local_collaboration?.mode || null });
66
71
  const gptFinalAvailable = input.local_collaboration?.gpt_final_arbiter_available === undefined
67
72
  ? codexBinOk
@@ -126,6 +131,7 @@ export function buildDoctorReadinessMatrix(input = {}) {
126
131
  worker_tier_enabled: localModel.enabled === true && localStatus === 'verified',
127
132
  blockers: normalizeList(localModel.blockers)
128
133
  },
134
+ agent_role_config: agentRoleConfig,
129
135
  ready: blockers.size === 0 && cliReady,
130
136
  primary_blocker: [...blockers][0] || null,
131
137
  blockers: [...blockers],
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.9';
8
+ export const PACKAGE_VERSION = '2.0.11';
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() {