sneakoscope 2.0.1 → 2.0.4

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 +26 -5
  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 +28 -8
  8. package/dist/cli/command-registry.js +2 -0
  9. package/dist/commands/doctor.js +29 -3
  10. package/dist/core/agents/agent-command-surface.js +13 -3
  11. package/dist/core/agents/agent-orchestrator.js +92 -4
  12. package/dist/core/agents/agent-output-validator.js +2 -1
  13. package/dist/core/agents/agent-patch-proof.js +5 -0
  14. package/dist/core/agents/agent-patch-schema.js +2 -1
  15. package/dist/core/agents/agent-proof-evidence.js +26 -0
  16. package/dist/core/agents/agent-roster.js +1 -1
  17. package/dist/core/agents/agent-runner-ollama.js +411 -0
  18. package/dist/core/agents/agent-schema.js +1 -1
  19. package/dist/core/agents/intelligent-work-graph.js +45 -3
  20. package/dist/core/agents/native-cli-session-swarm.js +8 -1
  21. package/dist/core/agents/native-cli-worker.js +1 -1
  22. package/dist/core/agents/native-worker-backend-router.js +44 -2
  23. package/dist/core/agents/ollama-worker-config.js +118 -0
  24. package/dist/core/auto-review.js +39 -6
  25. package/dist/core/codex-app/codex-app-fast-ui-repair.js +42 -3
  26. package/dist/core/codex-control/codex-fake-sdk-adapter.js +20 -0
  27. package/dist/core/codex-control/codex-output-schemas.js +5 -1
  28. package/dist/core/codex-control/gpt-final-arbiter.js +160 -0
  29. package/dist/core/codex-control/gpt-final-context-compressor.js +17 -0
  30. package/dist/core/codex-control/gpt-final-proof-pack.js +120 -0
  31. package/dist/core/codex-control/gpt-final-review-schema.js +71 -0
  32. package/dist/core/commands/basic-cli.js +36 -1
  33. package/dist/core/commands/local-model-command.js +120 -0
  34. package/dist/core/commands/mad-sks-command.js +58 -9
  35. package/dist/core/commands/naruto-command.js +77 -5
  36. package/dist/core/commands/run-command.js +33 -1
  37. package/dist/core/commands/team-command.js +31 -2
  38. package/dist/core/doctor/doctor-readiness-matrix.js +19 -0
  39. package/dist/core/feature-fixtures.js +5 -0
  40. package/dist/core/fsx.js +1 -1
  41. package/dist/core/git-simple.js +143 -4
  42. package/dist/core/hooks-runtime.js +1 -1
  43. package/dist/core/init.js +2 -0
  44. package/dist/core/local-llm/local-collaboration-policy.js +93 -0
  45. package/dist/core/local-llm/local-llm-config.js +15 -0
  46. package/dist/core/pipeline/final-gpt-patch-stage.js +31 -0
  47. package/dist/core/pipeline/final-gpt-review-stage.js +5 -0
  48. package/dist/core/provider/provider-context.js +72 -9
  49. package/dist/core/retention.js +11 -0
  50. package/dist/core/routes.js +21 -1
  51. package/dist/core/safety/mutation-guard.js +2 -0
  52. package/dist/core/team-live.js +7 -1
  53. package/dist/core/update-check.js +215 -25
  54. package/dist/core/verification/verification-worker-pool.js +12 -0
  55. package/dist/core/version.js +1 -1
  56. package/dist/core/zellij/zellij-worker-pane-manager.js +19 -2
  57. package/dist/scripts/agent-ast-aware-work-graph-check.js +1 -1
  58. package/dist/scripts/codex-sdk-team-naruto-agent-pipeline-check.js +2 -1
  59. package/dist/scripts/doctor-fixes-codex-app-fast-ui-check.js +12 -2
  60. package/dist/scripts/gpt-final-arbiter-check.js +63 -0
  61. package/dist/scripts/gpt-final-arbiter-performance-check.js +36 -0
  62. package/dist/scripts/local-collab-gpt-final-availability-check.js +58 -0
  63. package/dist/scripts/local-collab-no-local-only-final-check.js +27 -0
  64. package/dist/scripts/local-collab-policy-check.js +17 -0
  65. package/dist/scripts/mad-sks-app-ui-no-mutation-check.js +92 -0
  66. package/dist/scripts/mad-sks-zellij-default-pane-worker-check.js +37 -0
  67. package/dist/scripts/mad-sks-zellij-launch-check.js +2 -1
  68. package/dist/scripts/provider-context-config-toml-check.js +63 -0
  69. package/dist/scripts/release-gate-existence-audit.js +4 -0
  70. package/dist/scripts/runtime-no-mjs-scripts-check.js +3 -2
  71. package/dist/scripts/zellij-worker-pane-manager-check.js +3 -0
  72. package/dist/scripts/zellij-worker-pane-manager-single-owner-check.js +39 -0
  73. package/package.json +13 -4
  74. package/schemas/local-llm/local-collaboration-policy.schema.json +57 -0
@@ -8,6 +8,7 @@ import { assertAgentSessionGenerationsClosed } from './agent-session-generation.
8
8
  import { readZellijLaneSupervisor } from './zellij-lane-supervisor.js';
9
9
  import { writeFakeRealProofPolicyReport } from '../proof/fake-real-proof-policy.js';
10
10
  import { buildRuntimeTruthMatrix, writeRuntimeTruthMatrix } from '../proof/runtime-truth-matrix.js';
11
+ import { evaluateLocalCollaborationFinalGate, localCollaborationParticipated, resolveLocalCollaborationPolicy } from '../local-llm/local-collaboration-policy.js';
11
12
  export async function writeAgentProofEvidence(root, input) {
12
13
  const lifecycle = await assertAllAgentSessionsClosed(root);
13
14
  const terminal = await assertAgentTerminalSessionsClosed(root);
@@ -28,6 +29,18 @@ export async function writeAgentProofEvidence(root, input) {
28
29
  const patchRollbackProof = await readJson(path.join(root, 'agent-patch-rollback-proof.json'), null);
29
30
  const patchProof = await readJson(path.join(root, 'agent-patch-proof.json'), null);
30
31
  const patchSwarm = input.patchSwarm || await readJson(path.join(root, 'agent-patch-swarm-runtime.json'), null);
32
+ const localCollaborationPolicy = input.localCollaborationPolicy || await readJson(path.join(root, 'local-collaboration-policy.json'), null) || resolveLocalCollaborationPolicy();
33
+ const gptFinalArbiter = input.gptFinalArbiter || await readJson(path.join(root, 'gpt-final-arbiter', 'gpt-final-arbiter.json'), null);
34
+ const localParticipated = localCollaborationParticipated(input.results || []) || Number(gptFinalArbiter?.local_outputs_count || 0) > 0;
35
+ const finalGptPatchStage = input.finalGptPatchStage || null;
36
+ const localFinalGate = gptFinalArbiter?.final_gate || evaluateLocalCollaborationFinalGate({
37
+ policy: localCollaborationPolicy,
38
+ localParticipated,
39
+ gptFinalStatus: gptFinalArbiter?.result?.status || null,
40
+ gptFinalAvailable: Boolean(gptFinalArbiter),
41
+ gptFinalBackend: gptFinalArbiter?.backend || null,
42
+ applyPatches: parallelWritePolicy?.apply_patches === true
43
+ });
31
44
  const nativeCliSessionProof = input.nativeCliSessionProof || await readJson(path.join(root, 'native-cli-session-proof.json'), null);
32
45
  const noSubagentScalingPolicy = input.noSubagentScalingPolicy || await readJson(path.join(root, 'no-subagent-scaling-policy.json'), null);
33
46
  const fastModePropagation = input.fastModePropagation || await readJson(path.join(root, 'fast-mode-propagation-proof.json'), null);
@@ -161,6 +174,9 @@ export async function writeAgentProofEvidence(root, input) {
161
174
  ...(patchSwarm && !patchApplyOk ? ['patch_apply_not_ok'] : []),
162
175
  ...(patchSwarm && !patchVerificationOk ? ['patch_verification_not_ok'] : []),
163
176
  ...(patchSwarm && !patchRollbackOk ? ['patch_rollback_not_ok'] : []),
177
+ ...(localParticipated && localFinalGate.ok !== true ? localFinalGate.blockers || ['gpt_final_arbiter_gate_not_ok'] : []),
178
+ ...(localParticipated && gptFinalArbiter?.ok !== true ? gptFinalArbiter?.blockers || ['gpt_final_arbiter_not_ok'] : []),
179
+ ...(localParticipated && finalGptPatchStage?.ok === false ? finalGptPatchStage.blockers || ['final_gpt_patch_stage_not_ok'] : []),
164
180
  ...agentChangedFileLeaseViolations(input.results || [], input.partition?.leases || [])
165
181
  ];
166
182
  const evidence = {
@@ -197,6 +213,15 @@ export async function writeAgentProofEvidence(root, input) {
197
213
  parallel_write_apply_patches: parallelWritePolicy?.apply_patches === true,
198
214
  parallel_write_dry_run_patches: parallelWritePolicy?.dry_run_patches === true,
199
215
  parallel_write_max_write_agents: Number(parallelWritePolicy?.max_write_agents || 0),
216
+ local_collaboration_policy: 'local-collaboration-policy.json',
217
+ local_collaboration_mode: localCollaborationPolicy.mode || null,
218
+ local_collaboration_participated: localParticipated,
219
+ gpt_final_arbiter: gptFinalArbiter ? 'gpt-final-arbiter/gpt-final-arbiter.json' : null,
220
+ gpt_final_status: gptFinalArbiter?.result?.status || (localParticipated ? 'missing' : 'not_required_no_local_outputs'),
221
+ gpt_final_backend: gptFinalArbiter?.backend || null,
222
+ gpt_final_patch_source: finalGptPatchStage?.final_patch_source || (localParticipated ? 'blocked' : 'not_applicable'),
223
+ gpt_final_gate_ok: localFinalGate.ok === true,
224
+ gpt_final_gate: localFinalGate,
200
225
  patch_swarm_runtime: patchSwarm ? 'agent-patch-swarm-runtime.json' : null,
201
226
  patch_queue: patchSwarm ? 'agent-patch-queue.json' : null,
202
227
  patch_queue_events: patchSwarm ? 'agent-patch-queue-events.jsonl' : null,
@@ -329,6 +354,7 @@ export async function writeAgentProofEvidence(root, input) {
329
354
  'strategy-gate.json': strategyGate,
330
355
  'agent-patch-proof.json': patchProof,
331
356
  'agent-patch-swarm-runtime.json': patchSwarm,
357
+ 'gpt-final-arbiter/gpt-final-arbiter.json': gptFinalArbiter,
332
358
  'native-cli-session-proof.json': nativeCliSessionProof,
333
359
  'no-subagent-scaling-policy.json': noSubagentScalingPolicy,
334
360
  'fast-mode-propagation-proof.json': fastModePropagation
@@ -29,7 +29,7 @@ export function systemSafeNarutoConcurrency(opts = {}) {
29
29
  const freeGb = freeBytes / (1024 * 1024 * 1024);
30
30
  const totalGb = totalBytes / (1024 * 1024 * 1024);
31
31
  const backend = String(opts.backend || 'codex-sdk');
32
- const heavy = backend === 'codex-sdk' || backend === 'zellij' || backend === 'process';
32
+ const heavy = backend === 'codex-sdk' || backend === 'zellij' || backend === 'process' || backend === 'ollama';
33
33
  const ceiling = MAX_NARUTO_AGENT_COUNT;
34
34
  // macOS reports very low freemem while reclaimable memory is still available, so
35
35
  // budget against a total-memory-derived floor rather than the instantaneous free.
@@ -0,0 +1,411 @@
1
+ import path from 'node:path';
2
+ import { nowIso, sha256, writeJsonAtomic } from '../fsx.js';
3
+ import { loadTriWikiRuntimeContext, triWikiContextBlock, triWikiProofRecord } from '../triwiki-runtime.js';
4
+ import { validateAgentWorkerResult } from './agent-worker-pipeline.js';
5
+ import { normalizeAgentPatchEnvelope } from './agent-patch-schema.js';
6
+ import { resolveOllamaWorkerConfig } from './ollama-worker-config.js';
7
+ export const OLLAMA_WORKER_POLICY_SCHEMA = 'sks.ollama-worker-policy.v1';
8
+ export const OLLAMA_WORKER_REQUEST_SCHEMA = 'sks.ollama-worker-request.v1';
9
+ export const OLLAMA_WORKER_RESPONSE_SCHEMA = 'sks.ollama-worker-response.v1';
10
+ export async function runOllamaAgent(agent, slice, opts = {}) {
11
+ const root = path.resolve(opts.agentRoot || opts.cwd || process.cwd());
12
+ const workerDirRel = String(opts.workerDirRel || agent.session_artifact_dir || path.join('sessions', String(agent.id || 'ollama-worker'), 'worker'));
13
+ const workerDir = path.join(root, workerDirRel);
14
+ const triwikiContext = await loadTriWikiRuntimeContext(root);
15
+ const config = await resolveOllamaWorkerConfig({
16
+ backend: 'ollama',
17
+ ollamaEnabled: opts.ollamaEnabled === true || opts.ollama_enabled === true,
18
+ model: opts.ollamaModel || opts.ollama_model || null,
19
+ baseUrl: opts.ollamaBaseUrl || opts.ollama_base_url || null,
20
+ keepAlive: opts.ollamaKeepAlive || opts.ollama_keep_alive || null,
21
+ timeoutMs: Number(opts.ollamaTimeoutMs || opts.ollama_timeout_ms || 0) || null,
22
+ temperature: Number(opts.ollamaTemperature || opts.ollama_temperature || 0),
23
+ think: typeof opts.ollamaThink === 'boolean' ? opts.ollamaThink
24
+ : typeof opts.ollama_think === 'boolean' ? opts.ollama_think
25
+ : null
26
+ });
27
+ const policy = classifyOllamaWorkerSlice(slice, { route: opts.route, agent });
28
+ await writeJsonAtomic(path.join(workerDir, 'ollama-worker-config.json'), config);
29
+ await writeJsonAtomic(path.join(workerDir, 'ollama-worker-policy.json'), policy);
30
+ await writeJsonAtomic(path.join(workerDir, 'ollama-triwiki-context.json'), buildOllamaTriWikiArtifact(triwikiContext));
31
+ if (!config.ok || !policy.ok) {
32
+ return validateAgentWorkerResult(blockedResult(agent, slice, opts, [...config.blockers, ...policy.blockers], [
33
+ path.join(workerDirRel, 'ollama-worker-config.json'),
34
+ path.join(workerDirRel, 'ollama-worker-policy.json'),
35
+ path.join(workerDirRel, 'ollama-triwiki-context.json')
36
+ ]));
37
+ }
38
+ const requestId = `ollama:${sha256(`${nowIso()}:${agent.session_id || agent.id}:${slice?.id || ''}`).slice(0, 16)}`;
39
+ const request = buildOllamaGenerateRequest(agent, slice, opts, config, requestId, triwikiContext);
40
+ await writeJsonAtomic(path.join(workerDir, 'ollama-request.json'), {
41
+ schema: OLLAMA_WORKER_REQUEST_SCHEMA,
42
+ generated_at: nowIso(),
43
+ request_id: requestId,
44
+ endpoint: `${config.base_url}/api/generate`,
45
+ model: config.model,
46
+ keep_alive: config.keep_alive,
47
+ stream: false,
48
+ think: config.think,
49
+ policy: 'worker_only_no_strategy_planning_design',
50
+ triwiki_context: triWikiProofRecord(triwikiContext),
51
+ stack_current_docs_required: true,
52
+ prompt_sha256: sha256(request.prompt)
53
+ });
54
+ const response = await callOllamaGenerate(config, request)
55
+ .catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error) }));
56
+ const workerText = response.ok === true ? extractOllamaWorkerText(response.data) : { text: '', source: 'empty' };
57
+ await writeJsonAtomic(path.join(workerDir, 'ollama-response.json'), {
58
+ schema: OLLAMA_WORKER_RESPONSE_SCHEMA,
59
+ generated_at: nowIso(),
60
+ request_id: requestId,
61
+ ok: response.ok === true,
62
+ model: config.model,
63
+ response_sha256: response.ok === true ? sha256(workerText.text) : null,
64
+ response_source: response.ok === true ? workerText.source : null,
65
+ data: response.ok === true ? safeResponseData(response.data) : null,
66
+ error: response.ok === true ? null : response.error
67
+ });
68
+ if (response.ok !== true) {
69
+ return validateAgentWorkerResult(blockedResult(agent, slice, opts, ['ollama_generate_failed', String(response.error || 'unknown_error')], [
70
+ path.join(workerDirRel, 'ollama-triwiki-context.json'),
71
+ path.join(workerDirRel, 'ollama-request.json'),
72
+ path.join(workerDirRel, 'ollama-response.json')
73
+ ]));
74
+ }
75
+ const parsed = parseWorkerJson(workerText.text);
76
+ if (!parsed.ok) {
77
+ return validateAgentWorkerResult(blockedResult(agent, slice, opts, ['ollama_worker_json_parse_failed'], [
78
+ path.join(workerDirRel, 'ollama-triwiki-context.json'),
79
+ path.join(workerDirRel, 'ollama-request.json'),
80
+ path.join(workerDirRel, 'ollama-response.json')
81
+ ]));
82
+ }
83
+ const patchEnvelopes = normalizeOllamaPatchEnvelopes(parsed.value, agent, slice, opts, requestId);
84
+ const changedFiles = [...new Set(patchEnvelopes.flatMap((envelope) => envelope.operations.map((operation) => operation.path)))];
85
+ const writePaths = collectWritePaths(slice, opts);
86
+ return validateAgentWorkerResult({
87
+ mission_id: String(opts.missionId || opts.mission_id || ''),
88
+ agent_id: String(agent.id || 'ollama-worker'),
89
+ session_id: String(agent.session_id || ''),
90
+ persona_id: String(agent.persona_id || agent.id || 'ollama-worker'),
91
+ task_slice_id: String(slice?.id || ''),
92
+ status: writePaths.length > 0 && patchEnvelopes.length === 0 ? 'blocked' : 'done',
93
+ backend: 'ollama',
94
+ summary: String(parsed.value.summary || parsed.value.result || 'Ollama local worker completed.'),
95
+ findings: [
96
+ 'ollama local worker executed through /api/generate',
97
+ 'triwiki context consulted before local worker prompt',
98
+ ...stringArray(parsed.value.findings)
99
+ ],
100
+ proposed_changes: stringArray(parsed.value.proposed_changes || parsed.value.proposedChanges),
101
+ changed_files: changedFiles,
102
+ lease_compliance: { ok: true, violations: [] },
103
+ artifacts: [
104
+ path.join(workerDirRel, 'ollama-worker-config.json'),
105
+ path.join(workerDirRel, 'ollama-worker-policy.json'),
106
+ path.join(workerDirRel, 'ollama-triwiki-context.json'),
107
+ path.join(workerDirRel, 'ollama-request.json'),
108
+ path.join(workerDirRel, 'ollama-response.json')
109
+ ],
110
+ blockers: writePaths.length > 0 && patchEnvelopes.length === 0 ? ['ollama_no_patch_envelopes_for_write_task'] : [],
111
+ confidence: 'model_authored_local',
112
+ handoff_notes: 'Local Ollama worker produced worker JSON; parent SKS remains responsible for merge, apply, verification, and rollback.',
113
+ unverified: [
114
+ 'local_model_output_not_strategy_or_verification_authority',
115
+ ...(triwikiContext.present ? [] : ['triwiki_context_missing_parent_should_refresh_with_context7_or_official_docs_before_relying_on_local_worker'])
116
+ ],
117
+ writes: changedFiles,
118
+ ...(patchEnvelopes.length ? { patch_envelopes: patchEnvelopes } : {}),
119
+ model_authored_patch_envelopes: patchEnvelopes.length > 0,
120
+ fixture_patch_envelopes: false,
121
+ source_intelligence_refs: agent.source_intelligence_refs || opts.source_intelligence_refs || null,
122
+ goal_mode_ref: agent.goal_mode_ref || opts.goal_mode_ref || null,
123
+ verification: { status: 'passed', checks: ['ollama-api-generate', 'ollama-worker-policy', 'triwiki-runtime-context', 'agent-patch-envelope-schema'] },
124
+ recursion_guard: { ok: true, violations: [] }
125
+ });
126
+ }
127
+ export function classifyOllamaWorkerSlice(slice, input = {}) {
128
+ const writePaths = collectWritePaths(slice, {});
129
+ const text = [
130
+ input.route,
131
+ input.agent?.role,
132
+ input.agent?.persona_id,
133
+ slice?.role,
134
+ slice?.domain,
135
+ slice?.title,
136
+ slice?.description,
137
+ ...(Array.isArray(slice?.target_paths) ? slice.target_paths : [])
138
+ ].map((value) => String(value || '')).join('\n');
139
+ const bannedRole = /(?:^|\b)(architect|verifier|safety|integrator|schema|release|ux|db)(?:\b|$)/i.test(String(input.agent?.role || slice?.role || ''));
140
+ const collection = /\b(collect|gather|extract|inventory|list|scan|grep|tail|summarize|catalog)\b|수집|추출|목록|스캔|인벤토리/i.test(text);
141
+ const coding = /\b(code|implement|patch|write|edit|fix|mechanical|simple)\b|코드|작성|수정|구현|패치|단순/i.test(text);
142
+ const banned = /\b(strategy|strategize|planning|plan|architecture|architect|design|review|verify|verification|safety|risk|consensus|debate|orchestrate|policy|decide|decision|migration|database|schema)\b|전략|기획|설계|디자인|검증|리뷰|안전|위험|합의|토론|결정|마이그레이션|데이터베이스/i.test(text);
143
+ const allowed = !bannedRole && !banned && (writePaths.length > 0 || collection || coding);
144
+ const blockers = [
145
+ ...(allowed ? [] : ['ollama_worker_task_not_simple_code_or_collection']),
146
+ ...(bannedRole ? ['ollama_worker_role_blocked'] : []),
147
+ ...(banned ? ['ollama_worker_strategy_planning_design_blocked'] : [])
148
+ ];
149
+ return {
150
+ schema: OLLAMA_WORKER_POLICY_SCHEMA,
151
+ generated_at: nowIso(),
152
+ ok: blockers.length === 0,
153
+ worker_only: true,
154
+ no_strategy_planning_design: true,
155
+ allowed_work: ['simple_code_patch_envelopes', 'read_only_collection'],
156
+ write_path_count: writePaths.length,
157
+ collection_detected: collection,
158
+ coding_detected: coding,
159
+ blockers
160
+ };
161
+ }
162
+ function buildOllamaTriWikiArtifact(triwikiContext) {
163
+ return {
164
+ ...triwikiContext,
165
+ proof: triWikiProofRecord(triwikiContext),
166
+ stack_current_docs_policy: {
167
+ required: true,
168
+ memory_path: '.sneakoscope/memory/q2_facts/stack-current-docs.md',
169
+ refresh_command: 'sks wiki refresh',
170
+ validate_command: 'sks wiki validate .sneakoscope/wiki/context-pack.json',
171
+ current_docs_source: 'Context7 or official vendor docs',
172
+ parent_action_when_stale_or_missing: 'Refresh stack-current-docs evidence with Context7 or official docs, then refresh/validate TriWiki before retrying the local worker.'
173
+ }
174
+ };
175
+ }
176
+ function buildOllamaGenerateRequest(agent, slice, opts, config, requestId, triwikiContext) {
177
+ const writePaths = collectWritePaths(slice, opts);
178
+ const prompt = [
179
+ 'You are an SKS local Ollama worker. You are not an architect, planner, reviewer, verifier, safety judge, or strategist.',
180
+ 'Only perform the narrow worker task below. If the task asks for strategy, planning, design, review, verification, risk judgment, or orchestration, return JSON with status "blocked" and blockers.',
181
+ 'Before writing or collecting, consult the TriWiki context below first. Treat use_first as high-trust project memory and hydrate_first as source/evidence that the parent must verify before risky or user-visible work.',
182
+ 'If TriWiki is missing, stale, or lacks current stack syntax/version guidance, do not invent from model memory. Return blocked and tell the parent SKS route to update .sneakoscope/memory/q2_facts/stack-current-docs.md with Context7 or official vendor docs, then run `sks wiki refresh` and `sks wiki validate .sneakoscope/wiki/context-pack.json` before retrying.',
183
+ 'Return JSON only. Do not wrap it in markdown.',
184
+ 'Required shape: {"summary": string, "findings": string[], "proposed_changes": string[], "patch_envelopes": [patchEnvelope]}.',
185
+ 'Each patchEnvelope must contain operations: [{"op":"write","path":"relative/path","content":"text"}] or replace/unified_diff operations.',
186
+ 'Patch envelope operations may be write, replace, or unified_diff. Use only allowed write paths.',
187
+ '',
188
+ triWikiContextBlock(triwikiContext),
189
+ '',
190
+ JSON.stringify({
191
+ request_id: requestId,
192
+ mission_id: opts.missionId || opts.mission_id || '',
193
+ route: opts.route || '$Agent',
194
+ agent: {
195
+ id: agent.id || '',
196
+ session_id: agent.session_id || '',
197
+ slot_id: agent.slot_id || agent.id || '',
198
+ generation_index: Number(agent.generation_index || 1),
199
+ role: agent.role || agent.persona_id || ''
200
+ },
201
+ task_slice: slice || {},
202
+ allowed_write_paths: writePaths,
203
+ triwiki_context: triWikiProofRecord(triwikiContext),
204
+ stack_current_docs_policy: {
205
+ required: true,
206
+ memory_path: '.sneakoscope/memory/q2_facts/stack-current-docs.md',
207
+ refresh: 'sks wiki refresh',
208
+ validate: 'sks wiki validate .sneakoscope/wiki/context-pack.json',
209
+ current_docs_source: 'Context7 or official vendor docs'
210
+ }
211
+ }, null, 2)
212
+ ].join('\n');
213
+ return {
214
+ model: config.model,
215
+ prompt,
216
+ stream: false,
217
+ format: 'json',
218
+ think: config.think,
219
+ keep_alive: config.keep_alive,
220
+ options: {
221
+ temperature: config.temperature
222
+ }
223
+ };
224
+ }
225
+ async function callOllamaGenerate(config, request) {
226
+ const controller = new AbortController();
227
+ const timer = setTimeout(() => controller.abort(), config.timeout_ms);
228
+ try {
229
+ const response = await fetch(`${config.base_url}/api/generate`, {
230
+ method: 'POST',
231
+ headers: { 'Content-Type': 'application/json' },
232
+ body: JSON.stringify(request),
233
+ signal: controller.signal
234
+ });
235
+ const text = await response.text();
236
+ if (!response.ok)
237
+ return { ok: false, error: `http_${response.status}:${text.slice(0, 500)}` };
238
+ return { ok: true, data: JSON.parse(text) };
239
+ }
240
+ finally {
241
+ clearTimeout(timer);
242
+ }
243
+ }
244
+ function parseWorkerJson(text) {
245
+ const trimmed = text.trim().replace(/^```(?:json)?\s*/i, '').replace(/\s*```$/, '');
246
+ try {
247
+ return { ok: true, value: JSON.parse(trimmed) };
248
+ }
249
+ catch {
250
+ const match = trimmed.match(/\{[\s\S]*\}/);
251
+ if (!match)
252
+ return { ok: false };
253
+ try {
254
+ return { ok: true, value: JSON.parse(match[0] || '{}') };
255
+ }
256
+ catch {
257
+ return { ok: false };
258
+ }
259
+ }
260
+ }
261
+ function normalizeOllamaPatchEnvelopes(value, agent, slice, opts, requestId) {
262
+ const rawEnvelopes = Array.isArray(value?.patch_envelopes) ? value.patch_envelopes
263
+ : Array.isArray(value?.patchEnvelopes) ? value.patchEnvelopes
264
+ : value?.patch_envelope ? [value.patch_envelope]
265
+ : value?.patchEnvelope ? [value.patchEnvelope]
266
+ : Array.isArray(value?.operations) ? [{ operations: value.operations }]
267
+ : value?.operation ? [{ operation: value.operation }]
268
+ : looksLikePatchOperation(value) ? [value]
269
+ : [];
270
+ return rawEnvelopes.map((raw, index) => {
271
+ const operations = normalizeOllamaOperations(raw);
272
+ const allowedPaths = collectWritePaths(slice, opts);
273
+ const firstPath = String(operations[0]?.path || allowedPaths[index] || slice?.id || `ollama-${index + 1}`);
274
+ const leaseId = String(raw?.lease_id || raw?.lease_proof?.lease_id || `write:${String(agent.id || 'ollama')}:${firstPath}`);
275
+ const nodeId = String(slice?.micro_win_id || slice?.id || `ollama-patch-${index + 1}`);
276
+ const verificationNodeId = String(slice?.verification_node_id || `verify:${nodeId}`);
277
+ const rollbackNodeId = String(slice?.rollback_node_id || `rollback:${nodeId}`);
278
+ return normalizeAgentPatchEnvelope({
279
+ ...raw,
280
+ source: 'model_authored',
281
+ mission_id: String(opts.missionId || opts.mission_id || raw?.mission_id || ''),
282
+ route: String(opts.route || raw?.route || '$Agent'),
283
+ agent_id: String(agent.id || raw?.agent_id || 'ollama-worker'),
284
+ session_id: String(agent.session_id || raw?.session_id || ''),
285
+ slot_id: String(agent.slot_id || raw?.slot_id || agent.id || 'ollama-worker'),
286
+ generation_index: Number(agent.generation_index || raw?.generation_index || 1),
287
+ task_slice_id: String(slice?.id || raw?.task_slice_id || ''),
288
+ native_cli_worker_session_id: String(agent.session_id || raw?.native_cli_worker_session_id || ''),
289
+ native_cli_process_id: process.pid,
290
+ worker_process_id: process.pid,
291
+ backend_ollama_request_id: requestId,
292
+ fast_mode: opts.fastMode !== false,
293
+ service_tier: opts.serviceTier === 'standard' ? 'standard' : 'fast',
294
+ lease_id: leaseId,
295
+ allowed_paths: allowedPaths.length ? allowedPaths : raw?.allowed_paths,
296
+ strategy_task_id: nodeId,
297
+ verification_node_id: verificationNodeId,
298
+ rollback_node_id: rollbackNodeId,
299
+ lease_proof: {
300
+ lease_id: leaseId,
301
+ owner_agent: String(agent.id || 'ollama-worker'),
302
+ owner_persona: String(agent.persona_id || agent.role || 'ollama-worker'),
303
+ allowed_paths: allowedPaths.length ? allowedPaths : raw?.allowed_paths,
304
+ strategy_task_id: nodeId,
305
+ micro_win_id: slice?.micro_win_id ? String(slice.micro_win_id) : undefined,
306
+ protected_path_check: 'passed',
307
+ conflict_prediction_id: `conflict:${nodeId}`,
308
+ verification_node_id: verificationNodeId,
309
+ rollback_node_id: rollbackNodeId
310
+ },
311
+ operations
312
+ });
313
+ });
314
+ }
315
+ function normalizeOllamaOperations(raw) {
316
+ if (Array.isArray(raw?.operations))
317
+ return raw.operations;
318
+ if (raw?.operation)
319
+ return normalizeOllamaOperations(raw.operation);
320
+ if (Array.isArray(raw?.changes))
321
+ return raw.changes.flatMap((change) => normalizeOllamaOperations(change));
322
+ if (Array.isArray(raw?.edits))
323
+ return raw.edits.flatMap((edit) => normalizeOllamaOperations(edit));
324
+ const pathValue = raw?.path || raw?.file || raw?.filepath || raw?.file_path || raw?.target_path || raw?.targetPath;
325
+ if (pathValue && (raw?.content !== undefined || raw?.text !== undefined)) {
326
+ return [{ op: 'write', path: String(pathValue), content: String(raw.content ?? raw.text ?? '') }];
327
+ }
328
+ if (pathValue && (raw?.diff !== undefined || raw?.unified_diff !== undefined || raw?.unifiedDiff !== undefined)) {
329
+ return [{ op: 'unified_diff', path: String(pathValue), diff: String(raw.diff ?? raw.unified_diff ?? raw.unifiedDiff ?? '') }];
330
+ }
331
+ if (pathValue && (raw?.search !== undefined || raw?.find !== undefined || raw?.replace !== undefined)) {
332
+ return [{
333
+ op: 'replace',
334
+ path: String(pathValue),
335
+ search: String(raw.search ?? raw.find ?? ''),
336
+ replace: String(raw.replace || '')
337
+ }];
338
+ }
339
+ return [];
340
+ }
341
+ function looksLikePatchOperation(value) {
342
+ return Boolean(value && typeof value === 'object' && (value.path !== undefined ||
343
+ value.file !== undefined ||
344
+ value.filepath !== undefined ||
345
+ value.file_path !== undefined ||
346
+ value.target_path !== undefined ||
347
+ value.targetPath !== undefined ||
348
+ value.content !== undefined ||
349
+ value.text !== undefined ||
350
+ value.diff !== undefined ||
351
+ value.unified_diff !== undefined ||
352
+ value.unifiedDiff !== undefined ||
353
+ value.search !== undefined ||
354
+ value.find !== undefined));
355
+ }
356
+ function blockedResult(agent, slice, opts, blockers, artifacts) {
357
+ return {
358
+ mission_id: String(opts.missionId || opts.mission_id || ''),
359
+ agent_id: String(agent.id || 'ollama-worker'),
360
+ session_id: String(agent.session_id || ''),
361
+ persona_id: String(agent.persona_id || agent.id || 'ollama-worker'),
362
+ task_slice_id: String(slice?.id || ''),
363
+ status: 'blocked',
364
+ backend: 'ollama',
365
+ summary: 'Ollama local worker blocked by configuration or worker-only policy.',
366
+ findings: [],
367
+ proposed_changes: [],
368
+ changed_files: [],
369
+ lease_compliance: { ok: true, violations: [] },
370
+ artifacts,
371
+ blockers,
372
+ confidence: 'blocked',
373
+ handoff_notes: 'Local model did not run.',
374
+ unverified: [],
375
+ writes: [],
376
+ verification: { status: 'failed', checks: ['ollama-worker-policy'] },
377
+ recursion_guard: { ok: true, violations: [] }
378
+ };
379
+ }
380
+ function collectWritePaths(slice, opts) {
381
+ return [
382
+ ...(Array.isArray(slice?.write_paths) ? slice.write_paths : []),
383
+ ...(Array.isArray(opts?.write_paths) ? opts.write_paths : [])
384
+ ].map(String).filter(Boolean);
385
+ }
386
+ function stringArray(value) {
387
+ return Array.isArray(value) ? value.map(String) : [];
388
+ }
389
+ function safeResponseData(data) {
390
+ return {
391
+ model: data?.model || null,
392
+ created_at: data?.created_at || null,
393
+ done: data?.done === true,
394
+ response_present: typeof data?.response === 'string' && data.response.length > 0,
395
+ thinking_present: typeof data?.thinking === 'string' && data.thinking.length > 0,
396
+ total_duration: data?.total_duration || null,
397
+ load_duration: data?.load_duration || null,
398
+ prompt_eval_count: data?.prompt_eval_count || null,
399
+ eval_count: data?.eval_count || null
400
+ };
401
+ }
402
+ function extractOllamaWorkerText(data) {
403
+ const response = String(data?.response || '').trim();
404
+ if (response)
405
+ return { text: response, source: 'response' };
406
+ const thinking = String(data?.thinking || '').trim();
407
+ if (thinking)
408
+ return { text: thinking, source: 'thinking' };
409
+ return { text: '', source: 'empty' };
410
+ }
411
+ //# sourceMappingURL=agent-runner-ollama.js.map
@@ -14,7 +14,7 @@ export const DEFAULT_AGENT_CONCURRENCY = 5;
14
14
  // cap; every other roster/scheduler caller keeps MAX_AGENT_COUNT as the default.
15
15
  export const MAX_NARUTO_AGENT_COUNT = 100;
16
16
  export const DEFAULT_NARUTO_CLONES = 12;
17
- export const AGENT_BACKENDS = ['fake', 'process', 'codex-sdk', 'zellij'];
17
+ export const AGENT_BACKENDS = ['fake', 'process', 'codex-sdk', 'zellij', 'ollama'];
18
18
  export function normalizeAgentBackend(input) {
19
19
  const value = String(input || 'codex-sdk');
20
20
  return AGENT_BACKENDS.includes(value) ? value : 'codex-sdk';
@@ -77,6 +77,8 @@ export function enhanceTaskGraphWithIntelligence(taskGraph, graph) {
77
77
  return taskGraph;
78
78
  const bottleneckTargets = new Set((graph.integration_bottlenecks?.bottlenecks || []).map((row) => String(row.file)));
79
79
  const criticalTargets = new Set(graph.critical_path?.path || []);
80
+ const targetActiveSlots = Math.max(1, Math.floor(Number(taskGraph.target_active_slots || 1)));
81
+ const canSerializeCriticalPath = taskGraph.work_items.length > targetActiveSlots;
80
82
  const workItems = taskGraph.work_items.map((item, index) => {
81
83
  const targets = Array.isArray(item.target_paths) ? item.target_paths.map(String) : [];
82
84
  const critical = targets.some((file) => criticalTargets.has(file));
@@ -90,7 +92,7 @@ export function enhanceTaskGraphWithIntelligence(taskGraph, graph) {
90
92
  test_ownership_ref: 'agent-test-ownership-map.json',
91
93
  critical_path_ref: 'agent-critical-path.json',
92
94
  integration_bottleneck_ref: 'agent-integration-bottlenecks.json',
93
- dependencies: mergeDependencies(item.dependencies, critical && index > 0 ? [taskGraph.work_items[index - 1]?.work_item_id].filter(Boolean) : []),
95
+ dependencies: mergeDependencies(item.dependencies, canSerializeCriticalPath && critical && index > 0 ? [taskGraph.work_items[index - 1]?.work_item_id].filter(Boolean) : []),
94
96
  lease_requirements: [
95
97
  ...(Array.isArray(item.lease_requirements) ? item.lease_requirements : []),
96
98
  ...(bottleneck ? targets.map((file) => ({ kind: 'integration-bottleneck-read', path: file })) : [])
@@ -109,8 +111,9 @@ export function enhanceTaskGraphWithIntelligence(taskGraph, graph) {
109
111
  };
110
112
  }
111
113
  export async function writeIntelligentWorkGraphArtifacts(root, graph) {
112
- await writeJsonAtomic(path.join(root, 'agent-intelligent-work-graph.json'), graph);
113
- await writeJsonAtomic(path.join(root, 'agent-intelligent-work-graph-v2.json'), graph);
114
+ const compact = compactIntelligentWorkGraph(graph);
115
+ await writeJsonAtomic(path.join(root, 'agent-intelligent-work-graph.json'), compact);
116
+ await writeJsonAtomic(path.join(root, 'agent-intelligent-work-graph-v2.json'), compact);
114
117
  await writeJsonAtomic(path.join(root, 'agent-symbol-ownership-map.json'), {
115
118
  schema: 'sks.agent-symbol-ownership-map.v1',
116
119
  generated_at: graph.generated_at,
@@ -180,6 +183,45 @@ export async function writeIntelligentWorkGraphArtifacts(root, graph) {
180
183
  ...graph.integration_bottlenecks
181
184
  });
182
185
  }
186
+ export function compactIntelligentWorkGraph(graph) {
187
+ return {
188
+ schema: graph.schema,
189
+ generated_at: graph.generated_at,
190
+ ok: graph.ok,
191
+ mode: graph.mode,
192
+ compact: true,
193
+ source_inventory_count: graph.source_inventory_count,
194
+ test_inventory_count: graph.test_inventory_count,
195
+ docs_inventory_count: graph.docs_inventory_count,
196
+ script_schema_inventory_count: graph.script_schema_inventory_count,
197
+ dependency_edge_count: graph.dependency_edge_count,
198
+ ast_coverage: graph.ast_coverage,
199
+ test_ownership_confidence: graph.test_ownership_confidence,
200
+ changed_file_candidates: limitArray(graph.changed_file_candidates, 200),
201
+ route_domain_priority: limitArray(graph.route_domain_priority, 100),
202
+ critical_path: graph.critical_path,
203
+ integration_bottlenecks: graph.integration_bottlenecks,
204
+ parallelizable_groups: limitArray(graph.parallelizable_groups, 80),
205
+ serial_dependency_groups: limitArray(graph.serial_dependency_groups, 40),
206
+ work_graph_quality_score: graph.work_graph_quality_score,
207
+ proof_level: graph.proof_level,
208
+ ast_parser_limitations: limitArray(graph.ast_parser_limitations, 100),
209
+ warnings: limitArray(graph.warnings, 200),
210
+ blockers: graph.blockers,
211
+ expanded_artifacts: {
212
+ symbol_ownership: 'agent-symbol-ownership-map.json',
213
+ route_ownership: 'agent-route-ownership-map.json',
214
+ command_ownership: 'agent-command-ownership-map.json',
215
+ test_ownership: 'agent-test-ownership-map.json',
216
+ source_test_ownership: 'agent-source-test-ownership-v2.json',
217
+ critical_path: 'agent-critical-path-v2.json',
218
+ integration_bottlenecks: 'agent-integration-bottlenecks-v2.json'
219
+ }
220
+ };
221
+ }
222
+ function limitArray(items, limit) {
223
+ return Array.isArray(items) ? items.slice(0, limit) : [];
224
+ }
183
225
  function buildTestOwnershipMap(sourceFiles, testFiles, ast, dependencyGraph = {}) {
184
226
  const ownerBySource = {};
185
227
  const relations = [];
@@ -42,6 +42,8 @@ class NativeCliSessionSwarmRecorder {
42
42
  parent_mission_id: this.input.missionId,
43
43
  route: this.input.route,
44
44
  backend: this.input.backend,
45
+ backend_explicit: this.input.backendExplicit === true,
46
+ no_ollama: this.input.noOllama === true || ctx.opts.noOllama === true,
45
47
  agent_root: this.root,
46
48
  agent: ctx.agent,
47
49
  slice: ctx.slice,
@@ -51,6 +53,9 @@ class NativeCliSessionSwarmRecorder {
51
53
  patch_envelope_path: patchRel,
52
54
  service_tier: this.input.fastModePolicy.service_tier,
53
55
  fast_mode: this.input.fastModePolicy.fast_mode,
56
+ ollama_enabled: ctx.opts.ollamaEnabled === true || this.input.backend === 'ollama',
57
+ ollama_model: ctx.opts.ollamaModel || null,
58
+ ollama_base_url: ctx.opts.ollamaBaseUrl || null,
54
59
  source_intelligence_refs: ctx.agent.source_intelligence_refs || null,
55
60
  goal_mode_ref: ctx.agent.goal_mode_ref || null,
56
61
  strategy_refs: ctx.slice?.strategy_refs || null,
@@ -192,6 +197,8 @@ class NativeCliSessionSwarmRecorder {
192
197
  SKS_AGENT_SESSION_ID: String(input.ctx.agent.session_id || ''),
193
198
  SKS_AGENT_SLOT_ID: slotId,
194
199
  SKS_AGENT_GENERATION_INDEX: String(input.ctx.agent.generation_index || 1),
200
+ ...(input.ctx.opts.ollamaModel ? { SKS_OLLAMA_MODEL: String(input.ctx.opts.ollamaModel) } : {}),
201
+ ...(input.ctx.opts.ollamaBaseUrl ? { SKS_OLLAMA_BASE_URL: String(input.ctx.opts.ollamaBaseUrl) } : {}),
195
202
  SKS_ZELLIJ_WORKER_PANE: '1',
196
203
  SKS_ZELLIJ_SESSION_NAME: sessionName
197
204
  }
@@ -220,7 +227,7 @@ class NativeCliSessionSwarmRecorder {
220
227
  serviceTier: this.input.fastModePolicy.service_tier
221
228
  });
222
229
  const launchBlockers = paneRecord.blockers || [];
223
- input.record.command_line = ['zellij', '--session', sessionName, 'action', 'new-pane', '--name', paneRecord.pane_name, '--', 'sh', '-lc', '<native-cli-worker-command>'];
230
+ input.record.command_line = ['zellij', '--session', sessionName, 'action', 'new-pane', '--direction', paneRecord.direction_applied, '--name', paneRecord.pane_name, '--', 'sh', '-lc', '<native-cli-worker-command>'];
224
231
  input.record.zellij_session_name = sessionName;
225
232
  input.record.zellij_pane_id = paneRecord.pane_id || null;
226
233
  input.record.zellij_pane_id_source = paneRecord.pane_id_source;
@@ -159,7 +159,7 @@ export async function runNativeCliWorker(input = {}) {
159
159
  structured_output_valid: routed.report?.structured_output_valid === true,
160
160
  backend_router_report: routed.report,
161
161
  backend_child_process_ids: routed.report.child_process_ids,
162
- backend_child_execution: routed.report.child_process_ids.length > 0 || Boolean(routed.report?.sdk_thread_id) || backend === 'fake',
162
+ backend_child_execution: routed.report.child_process_ids.length > 0 || Boolean(routed.report?.sdk_thread_id) || backend === 'fake' || backend === 'ollama',
163
163
  recursion_guard_env: process.env.SKS_DISABLE_ROUTE_RECURSION === '1',
164
164
  worker_env: process.env.SKS_AGENT_WORKER === '1',
165
165
  exit_code: guard.ok ? 0 : 1