sneakoscope 2.0.1 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -5
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/crates/sks-core/src/main.rs +1 -1
- package/dist/.sks-build-stamp.json +4 -4
- package/dist/bin/sks.js +1 -1
- package/dist/build-manifest.json +15 -8
- package/dist/cli/command-registry.js +2 -0
- package/dist/commands/doctor.js +29 -3
- package/dist/core/agents/agent-command-surface.js +13 -3
- package/dist/core/agents/agent-orchestrator.js +22 -0
- package/dist/core/agents/agent-output-validator.js +2 -1
- package/dist/core/agents/agent-patch-schema.js +2 -1
- package/dist/core/agents/agent-roster.js +1 -1
- package/dist/core/agents/agent-runner-ollama.js +411 -0
- package/dist/core/agents/agent-schema.js +1 -1
- package/dist/core/agents/intelligent-work-graph.js +45 -3
- package/dist/core/agents/native-cli-session-swarm.js +8 -1
- package/dist/core/agents/native-cli-worker.js +1 -1
- package/dist/core/agents/native-worker-backend-router.js +44 -2
- package/dist/core/agents/ollama-worker-config.js +118 -0
- package/dist/core/auto-review.js +39 -6
- package/dist/core/codex-app/codex-app-fast-ui-repair.js +42 -3
- package/dist/core/commands/basic-cli.js +36 -1
- package/dist/core/commands/local-model-command.js +105 -0
- package/dist/core/commands/mad-sks-command.js +58 -9
- package/dist/core/commands/run-command.js +29 -1
- package/dist/core/commands/team-command.js +31 -2
- package/dist/core/doctor/doctor-readiness-matrix.js +4 -0
- package/dist/core/feature-fixtures.js +1 -0
- package/dist/core/fsx.js +1 -1
- package/dist/core/hooks-runtime.js +1 -1
- package/dist/core/init.js +2 -0
- package/dist/core/provider/provider-context.js +72 -9
- package/dist/core/retention.js +11 -0
- package/dist/core/routes.js +21 -1
- package/dist/core/team-live.js +7 -1
- package/dist/core/update-check.js +156 -1
- package/dist/core/verification/verification-worker-pool.js +12 -0
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/zellij-worker-pane-manager.js +19 -2
- package/dist/scripts/agent-ast-aware-work-graph-check.js +1 -1
- package/dist/scripts/doctor-fixes-codex-app-fast-ui-check.js +12 -2
- package/dist/scripts/mad-sks-app-ui-no-mutation-check.js +92 -0
- package/dist/scripts/mad-sks-zellij-default-pane-worker-check.js +37 -0
- package/dist/scripts/mad-sks-zellij-launch-check.js +2 -1
- package/dist/scripts/provider-context-config-toml-check.js +63 -0
- package/dist/scripts/release-gate-existence-audit.js +4 -0
- package/dist/scripts/runtime-no-mjs-scripts-check.js +3 -2
- package/dist/scripts/zellij-worker-pane-manager-check.js +3 -0
- package/dist/scripts/zellij-worker-pane-manager-single-owner-check.js +39 -0
- package/package.json +7 -3
|
@@ -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
|
-
|
|
113
|
-
await writeJsonAtomic(path.join(root, 'agent-intelligent-work-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
|
|
@@ -2,6 +2,8 @@ import path from 'node:path';
|
|
|
2
2
|
import { nowIso, readJson, writeJsonAtomic } from '../fsx.js';
|
|
3
3
|
import { buildFixturePatchEnvelopes } from './agent-runner-fake.js';
|
|
4
4
|
import { runProcessAgent } from './agent-runner-process.js';
|
|
5
|
+
import { classifyOllamaWorkerSlice, runOllamaAgent } from './agent-runner-ollama.js';
|
|
6
|
+
import { resolveOllamaWorkerConfig } from './ollama-worker-config.js';
|
|
5
7
|
import { runZellijAgent } from './agent-runner-zellij.js';
|
|
6
8
|
import { validateAgentWorkerResult } from './agent-worker-pipeline.js';
|
|
7
9
|
import { normalizeAgentPatchEnvelope } from './agent-patch-schema.js';
|
|
@@ -11,7 +13,8 @@ export const NATIVE_WORKER_BACKEND_ROUTER_SCHEMA = 'sks.native-worker-backend-ro
|
|
|
11
13
|
export async function runNativeWorkerBackendRouter(input) {
|
|
12
14
|
const root = path.resolve(input.agentRoot);
|
|
13
15
|
const requestedBackend = String(input.backend || '');
|
|
14
|
-
|
|
16
|
+
let backend = normalizeBackend(requestedBackend);
|
|
17
|
+
backend = await maybeAutoSelectOllamaBackend(backend, input);
|
|
15
18
|
const reportRel = path.join(input.workerDirRel, 'worker-backend-router-report.json');
|
|
16
19
|
const startedAt = nowIso();
|
|
17
20
|
let result;
|
|
@@ -61,6 +64,28 @@ export async function runNativeWorkerBackendRouter(input) {
|
|
|
61
64
|
verification: { status: processRun.status === 'done' ? 'passed' : 'failed', checks: [...(processRun.verification?.checks || []), 'native-worker-backend-router', 'process-child-execution'] }
|
|
62
65
|
});
|
|
63
66
|
}
|
|
67
|
+
else if (backend === 'ollama') {
|
|
68
|
+
const ollamaRun = await runOllamaAgent(input.agent, input.slice, {
|
|
69
|
+
...input.intake,
|
|
70
|
+
missionId: input.intake.mission_id || input.intake.parent_mission_id || '',
|
|
71
|
+
agentRoot: root,
|
|
72
|
+
workerDirRel: input.workerDirRel,
|
|
73
|
+
cwd: input.intake.cwd || root,
|
|
74
|
+
route: input.intake.route || '$Agent',
|
|
75
|
+
fastMode: input.fastModePolicy.fast_mode,
|
|
76
|
+
serviceTier: input.fastModePolicy.service_tier
|
|
77
|
+
});
|
|
78
|
+
patchEnvelopes = Array.isArray(ollamaRun.patch_envelopes) ? ollamaRun.patch_envelopes : [];
|
|
79
|
+
proofLevel = ollamaRun.status === 'done' ? (patchEnvelopes.length ? 'model_authored' : 'ollama_worker_proven') : 'blocked';
|
|
80
|
+
result = validateAgentWorkerResult({
|
|
81
|
+
...ollamaRun,
|
|
82
|
+
backend: 'ollama',
|
|
83
|
+
patch_envelopes: patchEnvelopes,
|
|
84
|
+
model_authored_patch_envelopes: patchEnvelopes.length > 0,
|
|
85
|
+
fixture_patch_envelopes: false,
|
|
86
|
+
verification: { status: ollamaRun.status === 'done' ? 'passed' : 'failed', checks: [...(ollamaRun.verification?.checks || []), 'native-worker-backend-router', 'ollama-api-generate'] }
|
|
87
|
+
});
|
|
88
|
+
}
|
|
64
89
|
else if (backend === 'codex-sdk' || backend === 'zellij') {
|
|
65
90
|
const sdkTask = await runCodexTask({
|
|
66
91
|
route: String(input.intake.route || '$Agent'),
|
|
@@ -155,6 +180,7 @@ export async function runNativeWorkerBackendRouter(input) {
|
|
|
155
180
|
finished_at: nowIso(),
|
|
156
181
|
ok: result.status === 'done',
|
|
157
182
|
selected_backend: backend || input.backend,
|
|
183
|
+
requested_backend: requestedBackend,
|
|
158
184
|
agent_id: input.agent.id,
|
|
159
185
|
session_id: input.agent.session_id,
|
|
160
186
|
worker_process_id: process.pid,
|
|
@@ -170,6 +196,7 @@ export async function runNativeWorkerBackendRouter(input) {
|
|
|
170
196
|
sdk_run_id: childReports.find((report) => report?.sdk_run_id)?.sdk_run_id || null,
|
|
171
197
|
stream_event_count: Number(childReports.find((report) => report?.stream_event_count)?.stream_event_count || 0),
|
|
172
198
|
structured_output_valid: childReports.some((report) => report?.structured_output_valid === true),
|
|
199
|
+
ollama_request_ids: patchEnvelopes.map((envelope) => envelope.backend_ollama_request_id).filter(Boolean),
|
|
173
200
|
blockers: result.blockers || []
|
|
174
201
|
};
|
|
175
202
|
await writeJsonAtomic(path.join(root, reportRel), report);
|
|
@@ -184,8 +211,23 @@ export async function runNativeWorkerBackendRouter(input) {
|
|
|
184
211
|
patchEnvelopes
|
|
185
212
|
};
|
|
186
213
|
}
|
|
214
|
+
async function maybeAutoSelectOllamaBackend(backend, input) {
|
|
215
|
+
if (backend !== 'codex-sdk')
|
|
216
|
+
return backend;
|
|
217
|
+
if (input.intake?.backend_explicit === true || input.intake?.no_ollama === true)
|
|
218
|
+
return backend;
|
|
219
|
+
const config = await resolveOllamaWorkerConfig({
|
|
220
|
+
ollamaEnabled: input.intake?.ollama_enabled === true,
|
|
221
|
+
model: input.intake?.ollama_model || null,
|
|
222
|
+
baseUrl: input.intake?.ollama_base_url || null
|
|
223
|
+
}).catch(() => null);
|
|
224
|
+
if (!config?.ok || config.enabled !== true)
|
|
225
|
+
return backend;
|
|
226
|
+
const policy = classifyOllamaWorkerSlice(input.slice, { route: input.intake?.route, agent: input.agent });
|
|
227
|
+
return policy.ok ? 'ollama' : backend;
|
|
228
|
+
}
|
|
187
229
|
function normalizeBackend(value) {
|
|
188
|
-
return value === 'fake' || value === 'process' || value === 'codex-sdk' || value === 'zellij' ? value : null;
|
|
230
|
+
return value === 'fake' || value === 'process' || value === 'codex-sdk' || value === 'zellij' || value === 'ollama' ? value : null;
|
|
189
231
|
}
|
|
190
232
|
function envelopeOpts(input, source, childPid) {
|
|
191
233
|
return {
|