sneakoscope 2.0.4 → 2.0.6

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 (120) hide show
  1. package/README.md +18 -11
  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 +78 -8
  8. package/dist/cli/install-helpers.js +23 -0
  9. package/dist/commands/codex-app.js +25 -3
  10. package/dist/commands/doctor.js +33 -4
  11. package/dist/commands/mad-sks.js +2 -2
  12. package/dist/core/agents/agent-orchestrator.js +22 -3
  13. package/dist/core/agents/agent-proof-evidence.js +59 -2
  14. package/dist/core/agents/agent-roster.js +35 -6
  15. package/dist/core/agents/agent-schema.js +1 -1
  16. package/dist/core/agents/agent-worker-pipeline.js +9 -1
  17. package/dist/core/agents/native-worker-backend-router.js +50 -10
  18. package/dist/core/agents/ollama-worker-config.js +164 -15
  19. package/dist/core/codex/codex-0-137-compat.js +119 -0
  20. package/dist/core/codex-app.js +124 -2
  21. package/dist/core/codex-control/codex-control-proof.js +4 -1
  22. package/dist/core/codex-control/codex-sdk-capability.js +1 -1
  23. package/dist/core/codex-control/codex-task-runner.js +329 -5
  24. package/dist/core/codex-control/python-codex-sdk-adapter.js +197 -0
  25. package/dist/core/codex-control/python-codex-sdk-event-translator.js +14 -0
  26. package/dist/core/commands/local-model-command.js +65 -19
  27. package/dist/core/commands/naruto-command.js +124 -8
  28. package/dist/core/commands/run-command.js +1 -1
  29. package/dist/core/doctor/doctor-readiness-matrix.js +21 -2
  30. package/dist/core/fsx.js +1 -1
  31. package/dist/core/hooks-runtime.js +2 -233
  32. package/dist/core/init.js +8 -8
  33. package/dist/core/local-llm/local-llm-backpressure.js +20 -0
  34. package/dist/core/local-llm/local-llm-capability.js +29 -0
  35. package/dist/core/local-llm/local-llm-client.js +100 -0
  36. package/dist/core/local-llm/local-llm-config.js +6 -1
  37. package/dist/core/local-llm/local-llm-context-cache.js +21 -0
  38. package/dist/core/local-llm/local-llm-control-adapter.js +101 -0
  39. package/dist/core/local-llm/local-llm-json-repair.js +52 -0
  40. package/dist/core/local-llm/local-llm-metrics.js +42 -0
  41. package/dist/core/local-llm/local-llm-ollama-client.js +67 -0
  42. package/dist/core/local-llm/local-llm-openai-compatible-client.js +30 -0
  43. package/dist/core/local-llm/local-llm-prompt-cache.js +12 -0
  44. package/dist/core/local-llm/local-llm-scheduler.js +29 -0
  45. package/dist/core/local-llm/local-llm-schema-enforcer.js +15 -0
  46. package/dist/core/local-llm/local-llm-smoke.js +83 -0
  47. package/dist/core/local-llm/local-llm-warmup.js +20 -0
  48. package/dist/core/local-llm/local-worker-eligibility.js +27 -0
  49. package/dist/core/naruto/hardware-capacity-probe.js +36 -0
  50. package/dist/core/naruto/naruto-active-pool.js +134 -0
  51. package/dist/core/naruto/naruto-backpressure.js +13 -0
  52. package/dist/core/naruto/naruto-concurrency-governor.js +65 -0
  53. package/dist/core/naruto/naruto-finalizer.js +18 -0
  54. package/dist/core/naruto/naruto-generation-scheduler.js +18 -0
  55. package/dist/core/naruto/naruto-gpt-final-pack.js +49 -0
  56. package/dist/core/naruto/naruto-parallel-patch-apply.js +95 -0
  57. package/dist/core/naruto/naruto-patch-transaction-batch.js +42 -0
  58. package/dist/core/naruto/naruto-role-policy.js +107 -0
  59. package/dist/core/naruto/naruto-verification-dag.js +42 -0
  60. package/dist/core/naruto/naruto-verification-pool.js +18 -0
  61. package/dist/core/naruto/naruto-work-graph.js +198 -0
  62. package/dist/core/naruto/naruto-work-item.js +40 -0
  63. package/dist/core/naruto/naruto-work-stealing.js +11 -0
  64. package/dist/core/naruto/resource-pressure-monitor.js +32 -0
  65. package/dist/core/pipeline/finalize-pipeline-result.js +58 -0
  66. package/dist/core/pipeline/gpt-final-required.js +12 -0
  67. package/dist/core/pipeline-internals/runtime-core.js +1 -1
  68. package/dist/core/ppt.js +31 -8
  69. package/dist/core/product-design-app-server.js +410 -0
  70. package/dist/core/product-design-plugin.js +139 -0
  71. package/dist/core/prompt/prompt-placeholder-guard.js +30 -0
  72. package/dist/core/router/capability-card.js +13 -0
  73. package/dist/core/router/route-cache.js +3 -0
  74. package/dist/core/router/ultra-router.js +2 -1
  75. package/dist/core/routes.js +12 -12
  76. package/dist/core/version.js +1 -1
  77. package/dist/core/zellij/zellij-lane-runtime.js +2 -2
  78. package/dist/core/zellij/zellij-naruto-dashboard.js +36 -0
  79. package/dist/core/zellij/zellij-worker-pane-manager.js +4 -4
  80. package/dist/scripts/blackbox-command-import-smoke.js +10 -1
  81. package/dist/scripts/check-package-boundary.js +12 -3
  82. package/dist/scripts/codex-0-137-compat-check.js +27 -0
  83. package/dist/scripts/codex-environment-scoped-approvals-check.js +10 -0
  84. package/dist/scripts/codex-plugin-list-json-check.js +8 -0
  85. package/dist/scripts/codex-thread-runtime-choice-check.js +10 -0
  86. package/dist/scripts/local-collab-all-pipelines-final-gpt-check.js +21 -0
  87. package/dist/scripts/local-llm-all-pipelines-check.js +11 -0
  88. package/dist/scripts/local-llm-cache-performance-check.js +10 -0
  89. package/dist/scripts/local-llm-capability-check.js +14 -0
  90. package/dist/scripts/local-llm-smoke-check.js +23 -0
  91. package/dist/scripts/local-llm-structured-output-check.js +11 -0
  92. package/dist/scripts/local-llm-throughput-check.js +10 -0
  93. package/dist/scripts/local-llm-tool-call-repair-check.js +10 -0
  94. package/dist/scripts/local-llm-warmup-check.js +11 -0
  95. package/dist/scripts/naruto-active-pool-check.js +39 -0
  96. package/dist/scripts/naruto-concurrency-governor-check.js +52 -0
  97. package/dist/scripts/naruto-gpt-final-pack-check.js +34 -0
  98. package/dist/scripts/naruto-parallel-patch-apply-check.js +41 -0
  99. package/dist/scripts/naruto-readonly-routing-check.js +116 -0
  100. package/dist/scripts/naruto-real-local-gpt-final-smoke.js +16 -0
  101. package/dist/scripts/naruto-role-distribution-check.js +23 -0
  102. package/dist/scripts/naruto-shadow-clone-swarm-check.js +13 -0
  103. package/dist/scripts/naruto-verification-pool-check.js +36 -0
  104. package/dist/scripts/naruto-work-graph-check.js +24 -0
  105. package/dist/scripts/naruto-zellij-massive-ui-check.js +23 -0
  106. package/dist/scripts/product-design-auto-install-check.js +119 -0
  107. package/dist/scripts/product-design-plugin-routing-check.js +101 -0
  108. package/dist/scripts/prompt-placeholder-guard-check.js +33 -0
  109. package/dist/scripts/python-codex-sdk-all-pipelines-check.js +47 -0
  110. package/dist/scripts/python-codex-sdk-capability-check.js +75 -0
  111. package/dist/scripts/python-codex-sdk-sandbox-policy-check.js +10 -0
  112. package/dist/scripts/python-codex-sdk-stream-bridge-check.js +12 -0
  113. package/dist/scripts/release-parallel-check.js +16 -2
  114. package/dist/scripts/release-provenance-check.js +21 -0
  115. package/dist/scripts/release-real-check.js +5 -0
  116. package/dist/scripts/zellij-worker-pane-manager-check.js +1 -1
  117. package/package.json +36 -4
  118. package/schemas/local-llm/local-model-config.schema.json +74 -0
  119. package/schemas/naruto/naruto-concurrency-governor.schema.json +21 -0
  120. package/schemas/naruto/naruto-work-graph.schema.json +22 -0
@@ -1,4 +1,6 @@
1
- import { resolveOllamaWorkerConfig, writeLocalModelConfig, readLocalModelConfig } from '../agents/ollama-worker-config.js';
1
+ import { applyLocalLlmSmokeResult, normalizeProvider, resolveOllamaWorkerConfig, writeLocalModelConfig, readLocalModelConfig } from '../agents/ollama-worker-config.js';
2
+ import { detectInstalledLocalModelCandidate, probeLocalLlmEndpoint } from '../local-llm/local-llm-client.js';
3
+ import { runLocalLlmGenerationSmoke, localLlmSmokeSchema } from '../local-llm/local-llm-smoke.js';
2
4
  export async function localModelCommand(args = []) {
3
5
  const action = normalizeLocalModelAction(args[0]);
4
6
  if (action === 'enable')
@@ -28,16 +30,62 @@ function normalizeLocalModelAction(value) {
28
30
  async function enable(args) {
29
31
  const model = readOption(args, '--model', firstPositional(args) || '');
30
32
  const baseUrl = readOption(args, '--base-url', '');
33
+ const provider = readOption(args, '--provider', '');
31
34
  const think = readBoolFlag(args, '--think', '--no-think');
32
- const patch = { enabled: true, provider: 'ollama' };
35
+ const skipSmoke = args.includes('--skip-smoke') || process.env.SKS_LOCAL_LLM_TOGGLE_ONLY === '1';
36
+ const patch = { enabled: true, status: 'enabled_unverified' };
37
+ const explicitConfig = Boolean(model || baseUrl || provider);
38
+ let detection = null;
39
+ if (!explicitConfig) {
40
+ detection = await detectInstalledLocalModelCandidate();
41
+ if (!detection) {
42
+ const config = await writeLocalModelConfig({ enabled: false, blockers: ['local_model_not_found'] });
43
+ process.exitCode = 1;
44
+ return {
45
+ schema: 'sks.local-model-command.v1',
46
+ ok: false,
47
+ action: 'enable',
48
+ message: '확인해보니 로컬 모델이 존재하지 않아 실행할 수 없습니다.',
49
+ config,
50
+ detection: null,
51
+ blockers: ['local_model_not_found']
52
+ };
53
+ }
54
+ patch.provider = detection.provider;
55
+ patch.model = detection.model;
56
+ patch.base_url = detection.base_url;
57
+ patch.endpoint = detection.endpoint;
58
+ }
59
+ if (provider)
60
+ patch.provider = normalizeProvider(provider);
33
61
  if (model)
34
62
  patch.model = model;
35
- if (baseUrl)
63
+ if (baseUrl) {
36
64
  patch.base_url = baseUrl;
65
+ patch.endpoint = baseUrl;
66
+ }
37
67
  if (think !== null)
38
68
  patch.think = think;
39
69
  const config = await writeLocalModelConfig(patch);
40
- return { schema: 'sks.local-model-command.v1', ok: true, action: 'enable', config };
70
+ const smoke = skipSmoke
71
+ ? { ok: false, skipped: true, status: 'enabled_unverified', reason: 'operator_skip_smoke', schema_valid: false, blockers: ['operator_skip_smoke'] }
72
+ : await runLocalLlmGenerationSmoke(config, {
73
+ prompt: 'Return strict JSON: {"status":"ok","summary":"local smoke passed"}',
74
+ schema: localLlmSmokeSchema,
75
+ timeoutMs: 20_000
76
+ });
77
+ const next = await writeLocalModelConfig(applyLocalLlmSmokeResult(config, smoke));
78
+ if (!skipSmoke && smoke.ok !== true)
79
+ process.exitCode = 1;
80
+ return {
81
+ schema: 'sks.local-model-command.v1',
82
+ ok: skipSmoke ? true : smoke.ok === true,
83
+ action: 'enable',
84
+ config: next,
85
+ detection,
86
+ smoke,
87
+ blockers: next.blockers
88
+ };
41
89
  }
42
90
  async function disable() {
43
91
  const config = await writeLocalModelConfig({ enabled: false });
@@ -55,37 +103,35 @@ async function setModel(args) {
55
103
  async function status() {
56
104
  const config = await readLocalModelConfig();
57
105
  const resolved = await resolveOllamaWorkerConfig();
58
- const api = await probeOllama(resolved.base_url);
106
+ const api = await probeLocalLlmEndpoint(resolved);
59
107
  return { schema: 'sks.local-model-command.v1', ok: true, action: 'status', config, resolved, api };
60
108
  }
61
- async function probeOllama(baseUrl) {
62
- try {
63
- const response = await fetch(`${baseUrl}/api/version`, { signal: AbortSignal.timeout(3000) });
64
- const text = await response.text();
65
- return { ok: response.ok, status: response.status, data: response.ok ? JSON.parse(text) : null };
66
- }
67
- catch (error) {
68
- return { ok: false, error: error instanceof Error ? error.message : String(error) };
69
- }
70
- }
71
109
  function emit(result, args) {
72
110
  if (args.includes('--json')) {
73
111
  console.log(JSON.stringify(result, null, 2));
74
112
  return result;
75
113
  }
76
114
  if (result.ok !== true) {
115
+ if (result.message)
116
+ console.log(result.message);
77
117
  console.log(`Local model: blocked (${(result.blockers || []).join(', ') || 'unknown'})`);
78
118
  return result;
79
119
  }
80
120
  const config = result.config || result.resolved || {};
81
121
  console.log(`Local model: ${config.enabled ? 'enabled' : 'disabled'}`);
82
- console.log(`Provider: ollama`);
122
+ console.log(`Provider: ${config.provider || 'unknown'}`);
83
123
  console.log(`Model: ${config.model || 'unknown'}`);
84
124
  console.log(`Base URL: ${config.base_url || config.baseUrl || 'unknown'}`);
125
+ if (config.status)
126
+ console.log(`Status: ${config.status}`);
127
+ if (result.detection)
128
+ console.log(`Detected: ${result.detection.source}`);
129
+ if (config.last_smoke?.result_path)
130
+ console.log(`Smoke: ${config.last_smoke.ok ? 'ok' : 'failed'} ${config.last_smoke.result_path}`);
85
131
  if (typeof config.think === 'boolean')
86
132
  console.log(`Think: ${config.think ? 'enabled' : 'disabled'}`);
87
133
  if (result.api)
88
- console.log(`Ollama API: ${result.api.ok ? 'ok' : 'not reachable'}`);
134
+ console.log(`Local model API: ${result.api.ok ? 'ok' : 'not reachable'}`);
89
135
  return result;
90
136
  }
91
137
  function readOption(args, name, fallback) {
@@ -105,12 +151,12 @@ function readBoolFlag(args, trueName, falseName) {
105
151
  function firstPositional(args = []) {
106
152
  for (let i = 0; i < args.length; i += 1) {
107
153
  const arg = String(args[i] || '');
108
- if (arg === '--model' || arg === '--base-url') {
154
+ if (arg === '--model' || arg === '--base-url' || arg === '--provider') {
109
155
  if (args[i + 1] && !String(args[i + 1]).startsWith('--'))
110
156
  i += 1;
111
157
  continue;
112
158
  }
113
- if (arg.startsWith('--model=') || arg.startsWith('--base-url='))
159
+ if (arg.startsWith('--model=') || arg.startsWith('--base-url=') || arg.startsWith('--provider='))
114
160
  continue;
115
161
  if (!arg.startsWith('--'))
116
162
  return arg;
@@ -1,12 +1,20 @@
1
1
  import path from 'node:path';
2
2
  import { createMission, findLatestMission, loadMission } from '../mission.js';
3
- import { readJson, sksRoot } from '../fsx.js';
3
+ import { 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';
7
7
  import { DEFAULT_NARUTO_CLONES, MAX_NARUTO_AGENT_COUNT } from '../agents/agent-schema.js';
8
8
  import { resolveOllamaWorkerConfig } from '../agents/ollama-worker-config.js';
9
9
  import { attachZellijSessionInteractive, launchZellijLayout } from '../zellij/zellij-launcher.js';
10
+ import { buildNarutoWorkGraph } from '../naruto/naruto-work-graph.js';
11
+ import { buildNarutoRoleDistribution } from '../naruto/naruto-role-policy.js';
12
+ import { decideNarutoConcurrency } from '../naruto/naruto-concurrency-governor.js';
13
+ import { simulateNarutoActivePool } from '../naruto/naruto-active-pool.js';
14
+ import { buildNarutoVerificationDag } from '../naruto/naruto-verification-dag.js';
15
+ import { buildNarutoGptFinalPack } from '../naruto/naruto-gpt-final-pack.js';
16
+ import { planNarutoZellijDashboard } from '../zellij/zellij-naruto-dashboard.js';
17
+ import { checkPromptPlaceholders } from '../prompt/prompt-placeholder-guard.js';
10
18
  const NARUTO_RESULT_SCHEMA = 'sks.naruto-command-result.v1';
11
19
  const NARUTO_ROUTE = '$Naruto';
12
20
  // $Naruto — Shadow Clone Swarm (影分身 / Kage Bunshin no Jutsu).
@@ -25,6 +33,28 @@ export async function narutoCommand(commandOrArgs = 'naruto', maybeArgs = []) {
25
33
  }
26
34
  async function narutoRun(parsed) {
27
35
  const root = await sksRoot();
36
+ const writeCapable = parsed.readonly !== true && parsed.writeMode !== 'off';
37
+ const patchEnvelopeBasePath = '.sneakoscope/naruto/patch-envelopes';
38
+ const placeholderGuard = checkPromptPlaceholders({
39
+ prompt: parsed.prompt,
40
+ writeCapable,
41
+ targetPaths: writeCapable ? [patchEnvelopeBasePath] : []
42
+ });
43
+ if (!placeholderGuard.ok) {
44
+ return emit(parsed, {
45
+ schema: NARUTO_RESULT_SCHEMA,
46
+ ok: false,
47
+ mode: 'NARUTO',
48
+ action: 'run',
49
+ status: 'blocked',
50
+ prompt_placeholder_guard: placeholderGuard,
51
+ blockers: placeholderGuard.blockers
52
+ }, () => {
53
+ console.log('$Naruto blocked before work graph creation: unresolved prompt placeholder or empty write target path.');
54
+ for (const blocker of placeholderGuard.blockers)
55
+ console.log('- ' + blocker);
56
+ });
57
+ }
28
58
  const roster = buildNarutoCloneRoster({
29
59
  clones: parsed.clones,
30
60
  prompt: parsed.prompt,
@@ -37,9 +67,52 @@ async function narutoRun(parsed) {
37
67
  const localWorker = await resolveNarutoLocalWorkerMode(parsed);
38
68
  const schedulerBackend = localWorker.auto_select_eligible ? 'ollama' : parsed.backend;
39
69
  const safe = systemSafeNarutoConcurrency({ backend: schedulerBackend });
40
- const activeSlots = Math.max(1, Math.min(roster.agent_count, parsed.concurrency || safe.cap));
70
+ const workGraph = buildNarutoWorkGraph({
71
+ prompt: parsed.prompt,
72
+ requestedClones: roster.agent_count,
73
+ totalWorkItems: parsed.workItems,
74
+ readonly: parsed.readonly,
75
+ writeCapable,
76
+ leaseBasePath: patchEnvelopeBasePath,
77
+ maxActiveWorkers: parsed.concurrency || safe.cap
78
+ });
79
+ const roleDistribution = buildNarutoRoleDistribution(workGraph.work_items, { readonly: parsed.readonly });
80
+ const governor = decideNarutoConcurrency({
81
+ requestedClones: roster.agent_count,
82
+ totalWorkItems: workGraph.total_work_items,
83
+ pendingWorkQueueSize: workGraph.total_work_items,
84
+ backend: schedulerBackend
85
+ });
86
+ const backendMinimum = schedulerBackend === 'fake' ? roster.agent_count : Math.min(roster.agent_count, 2);
87
+ const activeSlots = Math.max(1, Math.min(roster.agent_count, parsed.concurrency || Math.max(governor.safe_active_workers, backendMinimum), safe.cap));
88
+ const zellijVisiblePanes = Math.max(1, Math.min(activeSlots, governor.safe_zellij_visible_panes));
89
+ const activePool = simulateNarutoActivePool({ graph: workGraph, governor: { ...governor, safe_active_workers: activeSlots } });
90
+ const verificationDag = buildNarutoVerificationDag(workGraph, { cwd: root });
91
+ const gptFinalPack = buildNarutoGptFinalPack({
92
+ missionId: 'pending',
93
+ graph: workGraph,
94
+ roleDistribution,
95
+ localLlmMetrics: localWorker
96
+ });
97
+ const zellijDashboard = planNarutoZellijDashboard({
98
+ targetActiveWorkers: activeSlots,
99
+ visiblePaneCap: governor.safe_zellij_visible_panes,
100
+ backpressure: governor.backpressure,
101
+ roles: roleDistribution.work_item_roles.map((row) => row.role),
102
+ backend: schedulerBackend
103
+ });
41
104
  const mission = await createMission(root, { mode: 'naruto', prompt: parsed.prompt });
42
105
  const ledgerRoot = path.join(mission.dir, 'agents');
106
+ await writeNarutoArtifacts(ledgerRoot, {
107
+ workGraph,
108
+ roleDistribution,
109
+ governor,
110
+ activePool,
111
+ verificationDag,
112
+ gptFinalPack: { ...gptFinalPack, mission_id: mission.id },
113
+ zellijDashboard,
114
+ placeholderGuard
115
+ });
43
116
  let liveZellij = null;
44
117
  if (!parsed.json && !parsed.mock && !parsed.noOpenZellij) {
45
118
  liveZellij = await launchZellijLayout({
@@ -47,12 +120,12 @@ async function narutoRun(parsed) {
47
120
  missionId: mission.id,
48
121
  ledgerRoot,
49
122
  kind: 'naruto',
50
- slotCount: roster.agent_count,
123
+ slotCount: zellijVisiblePanes,
51
124
  dryRun: false,
52
125
  attach: false
53
126
  });
54
127
  if (liveZellij?.ok && liveZellij.capability?.status === 'ok') {
55
- console.log('Zellij: prepared ' + roster.agent_count + ' live clone lane(s) in ' + liveZellij.session_name + '. Attach with: ' + (liveZellij.attach_command_with_env || liveZellij.attach_command));
128
+ 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));
56
129
  if (parsed.attach)
57
130
  attachZellijSessionInteractive(liveZellij.session_name, { cwd: process.cwd(), configPath: liveZellij.clipboard_config_path });
58
131
  }
@@ -73,7 +146,7 @@ async function narutoRun(parsed) {
73
146
  agents: roster.agent_count,
74
147
  concurrency: activeSlots,
75
148
  targetActiveSlots: activeSlots,
76
- visualLaneCount: roster.agent_count,
149
+ visualLaneCount: zellijVisiblePanes,
77
150
  desiredWorkItemCount: parsed.workItems,
78
151
  maxAgentCount: MAX_NARUTO_AGENT_COUNT,
79
152
  narutoMode: true,
@@ -91,7 +164,7 @@ async function narutoRun(parsed) {
91
164
  fastMode: true,
92
165
  serviceTier: 'fast',
93
166
  noFast: false,
94
- ...(parsed.writeMode ? { writeMode: parsed.writeMode } : {}),
167
+ writeMode: writeCapable ? parsed.writeMode || 'parallel' : 'off',
95
168
  json: parsed.json
96
169
  });
97
170
  const clones = result.roster?.agent_count ?? roster.agent_count;
@@ -109,6 +182,22 @@ async function narutoRun(parsed) {
109
182
  target_active_slots: result.target_active_slots ?? activeSlots,
110
183
  concurrency_capped: clones > (result.target_active_slots ?? activeSlots),
111
184
  system: { cores: safe.cores, free_gb: safe.free_gb, safe_concurrency: safe.cap, heavy_backend: safe.heavy },
185
+ work_graph: {
186
+ total_work_items: workGraph.total_work_items,
187
+ mixed_work_kinds: workGraph.mixed_work_kinds,
188
+ write_allowed_count: workGraph.write_allowed_count,
189
+ active_wave_count: workGraph.active_waves.length,
190
+ parallel_write_wave_count: workGraph.active_waves.filter((wave) => wave.write_paths.length > 1).length,
191
+ ok: workGraph.ok
192
+ },
193
+ role_distribution: roleDistribution,
194
+ concurrency_governor: governor,
195
+ active_pool: {
196
+ ok: activePool.ok,
197
+ max_observed_active_workers: activePool.max_observed_active_workers,
198
+ refill_events: activePool.refill_events,
199
+ completed_count: activePool.completed_count
200
+ },
112
201
  local_worker: localWorkerSummary,
113
202
  proof: result.proof?.status || 'missing',
114
203
  run: result,
@@ -120,9 +209,10 @@ async function narutoRun(parsed) {
120
209
  console.log('Mission: ' + result.mission_id);
121
210
  console.log('Clones: ' + summary.clones + ' / max ' + MAX_NARUTO_AGENT_COUNT + ', running ' + summary.target_active_slots + ' at a time' + (summary.concurrency_capped ? ` (throttled to host capacity: ${safe.cores} cores, ${safe.free_gb} GB free)` : ''));
122
211
  console.log('Backend: ' + result.backend);
212
+ console.log('Roles: ' + roleDistribution.entries.map((entry) => `${entry.role}:${entry.count}`).join(', '));
123
213
  console.log('Proof: ' + summary.proof);
124
214
  if (summary.zellij?.ok && summary.zellij.capability?.status === 'ok')
125
- console.log('Zellij: prepared ' + summary.clones + ' native clone lane(s) in ' + summary.zellij.session_name);
215
+ console.log('Zellij: prepared ' + zellijVisiblePanes + ' visible active clone lane(s) in ' + summary.zellij.session_name + '; dashboard tracks ' + Math.max(0, activeSlots - zellijVisiblePanes) + ' headless active worker(s)');
126
216
  else if (summary.zellij?.ok)
127
217
  console.log('Zellij: optional live panes unavailable (' + ((summary.zellij.warnings || []).join('; ') || summary.zellij.capability?.status || 'unknown') + ')');
128
218
  });
@@ -148,6 +238,9 @@ async function narutoStatus(parsed) {
148
238
  const { dir } = await loadMission(root, id);
149
239
  const proof = await readJson(path.join(dir, 'agents', 'agent-proof-evidence.json'), null);
150
240
  const scheduler = await readJson(path.join(dir, 'agents', 'agent-scheduler-state.json'), null);
241
+ const roleDistribution = await readJson(path.join(dir, 'agents', 'naruto-role-distribution.json'), null);
242
+ const workGraph = await readJson(path.join(dir, 'agents', 'naruto-work-graph.json'), null);
243
+ const governor = await readJson(path.join(dir, 'agents', 'naruto-concurrency-governor.json'), null);
151
244
  const summary = {
152
245
  schema: NARUTO_RESULT_SCHEMA,
153
246
  ok: proof !== null,
@@ -156,13 +249,24 @@ async function narutoStatus(parsed) {
156
249
  proof: proof?.status || 'missing',
157
250
  target_active_slots: scheduler?.target_active_slots ?? null,
158
251
  max_active_slots: scheduler?.max_active_slots ?? null,
159
- completed: scheduler?.completed_count ?? null
252
+ completed: scheduler?.completed_count ?? null,
253
+ role_distribution: roleDistribution,
254
+ work_graph: workGraph ? {
255
+ total_work_items: workGraph.total_work_items,
256
+ mixed_work_kinds: workGraph.mixed_work_kinds,
257
+ write_allowed_count: workGraph.write_allowed_count,
258
+ active_wave_count: Array.isArray(workGraph.active_waves) ? workGraph.active_waves.length : null,
259
+ parallel_write_wave_count: Array.isArray(workGraph.active_waves) ? workGraph.active_waves.filter((wave) => Array.isArray(wave.write_paths) && wave.write_paths.length > 1).length : null
260
+ } : null,
261
+ concurrency_governor: governor
160
262
  };
161
263
  return emit(parsed, summary, () => {
162
264
  console.log('🍥 Naruto mission: ' + id);
163
265
  console.log('Proof: ' + summary.proof);
164
266
  if (summary.target_active_slots !== null)
165
267
  console.log('Active clones: ' + summary.target_active_slots + ' / max ' + summary.max_active_slots);
268
+ if (roleDistribution?.entries)
269
+ console.log('Roles: ' + roleDistribution.entries.map((entry) => `${entry.role}:${entry.count}`).join(', '));
166
270
  });
167
271
  }
168
272
  async function narutoHelp(parsed) {
@@ -186,6 +290,8 @@ async function narutoHelp(parsed) {
186
290
  });
187
291
  }
188
292
  function parseNarutoArgs(args = []) {
293
+ if (hasFlag(args, '--help') || hasFlag(args, '-h'))
294
+ args = ['help', ...args.filter((arg) => arg !== '--help' && arg !== '-h')];
189
295
  const first = args[0] && !String(args[0]).startsWith('--') ? String(args[0]) : '';
190
296
  const actions = new Set(['run', 'status', 'help']);
191
297
  const action = (actions.has(first) ? first : 'run');
@@ -213,6 +319,16 @@ function parseNarutoArgs(args = []) {
213
319
  const prompt = positionalArgs(rest, valueFlags).join(' ').trim() || 'Naruto shadow clone swarm run';
214
320
  return { action, prompt, clones, workItems, concurrency, backend, backendExplicit, mock, real, readonly, ollamaEnabled: useOllama && !noOllama, noOllama, ollamaModel, ollamaBaseUrl, writeMode, json, missionId, noOpenZellij, attach };
215
321
  }
322
+ async function writeNarutoArtifacts(ledgerRoot, artifacts) {
323
+ await writeJsonAtomic(path.join(ledgerRoot, 'naruto-work-graph.json'), artifacts.workGraph);
324
+ await writeJsonAtomic(path.join(ledgerRoot, 'naruto-role-distribution.json'), artifacts.roleDistribution);
325
+ await writeJsonAtomic(path.join(ledgerRoot, 'naruto-concurrency-governor.json'), artifacts.governor);
326
+ await writeJsonAtomic(path.join(ledgerRoot, 'naruto-active-pool.json'), artifacts.activePool);
327
+ await writeJsonAtomic(path.join(ledgerRoot, 'naruto-verification-dag.json'), artifacts.verificationDag);
328
+ await writeJsonAtomic(path.join(ledgerRoot, 'naruto-gpt-final-pack.json'), artifacts.gptFinalPack);
329
+ await writeJsonAtomic(path.join(ledgerRoot, 'naruto-zellij-dashboard.json'), artifacts.zellijDashboard);
330
+ await writeJsonAtomic(path.join(ledgerRoot, 'prompt-placeholder-guard.json'), artifacts.placeholderGuard);
331
+ }
216
332
  function clampClones(value) {
217
333
  if (!Number.isFinite(value) || value < 1)
218
334
  return DEFAULT_NARUTO_CLONES;
@@ -290,7 +290,7 @@ async function runSks(root, commandArgs) {
290
290
  cwd: root,
291
291
  timeoutMs: 180_000,
292
292
  maxOutputBytes: 512 * 1024,
293
- env: { SKS_SKIP_NPM_FRESHNESS_CHECK: '1', CI: 'true' },
293
+ env: { SKS_SKIP_NPM_FRESHNESS_CHECK: '1', SKS_LOCAL_LLM_TOGGLE_ONLY: '1', CI: 'true' },
294
294
  });
295
295
  }
296
296
  function routeExecutionResult(route, command, result, options = {}) {
@@ -54,6 +54,14 @@ export function buildDoctorReadinessMatrix(input = {}) {
54
54
  warnings.add('codex_app_fast_selector_repaired_restart_app_if_needed');
55
55
  if (input.codex_lb?.ok === false)
56
56
  warnings.add(`codex_lb_${input.codex_lb?.circuit?.state || 'blocked'}`);
57
+ const localModel = input.local_model || {};
58
+ const localStatus = String(localModel.status || (localModel.enabled ? 'enabled_unverified' : 'disabled'));
59
+ if (localModel.enabled === true && localStatus === 'enabled_unverified')
60
+ warnings.add('local_llm_enabled_unverified');
61
+ if (localModel.enabled === true && localStatus === 'degraded')
62
+ warnings.add('local_llm_degraded');
63
+ if (localModel.enabled === true && localStatus === 'blocked')
64
+ warnings.add('local_llm_blocked_worker_tier_disabled');
57
65
  const localCollaborationPolicy = resolveLocalCollaborationPolicy({ mode: input.local_collaboration?.mode || null });
58
66
  const gptFinalAvailable = input.local_collaboration?.gpt_final_arbiter_available === undefined
59
67
  ? codexBinOk
@@ -101,12 +109,23 @@ export function buildDoctorReadinessMatrix(input = {}) {
101
109
  codex_app_required_for_cli: false,
102
110
  local_collaboration: {
103
111
  mode: localCollaborationPolicy.mode,
104
- local_backend: input.local_collaboration?.local_backend || input.local_model?.provider || 'ollama',
105
- local_model: input.local_collaboration?.local_model || input.local_model?.model || null,
112
+ local_backend: input.local_collaboration?.local_backend || localModel.provider || 'ollama',
113
+ local_model: input.local_collaboration?.local_model || localModel.model || null,
106
114
  final_arbiter: gptFinalAvailable ? 'GPT available' : 'missing',
107
115
  final_apply_allowed: localCollaborationPolicy.gpt_final_required ? gptFinalAvailable : localCollaborationPolicy.mode === 'disabled',
108
116
  blockers: localCollaborationPolicy.gpt_final_required && !gptFinalAvailable ? ['gpt_final_arbiter_unavailable'] : localCollaborationPolicy.blockers
109
117
  },
118
+ local_llm: {
119
+ enabled: localModel.enabled === true,
120
+ status: localStatus,
121
+ provider: localModel.provider || 'ollama',
122
+ model: localModel.model || null,
123
+ endpoint: localModel.endpoint || localModel.base_url || null,
124
+ last_smoke: localModel.last_smoke || null,
125
+ final_arbiter: 'GPT required',
126
+ worker_tier_enabled: localModel.enabled === true && localStatus === 'verified',
127
+ blockers: normalizeList(localModel.blockers)
128
+ },
110
129
  ready: blockers.size === 0 && cliReady,
111
130
  primary_blocker: [...blockers][0] || null,
112
131
  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.4';
8
+ export const PACKAGE_VERSION = '2.0.6';
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() {