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.
- package/README.md +18 -11
- 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 +78 -8
- package/dist/cli/install-helpers.js +23 -0
- package/dist/commands/codex-app.js +25 -3
- package/dist/commands/doctor.js +33 -4
- package/dist/commands/mad-sks.js +2 -2
- package/dist/core/agents/agent-orchestrator.js +22 -3
- package/dist/core/agents/agent-proof-evidence.js +59 -2
- package/dist/core/agents/agent-roster.js +35 -6
- package/dist/core/agents/agent-schema.js +1 -1
- package/dist/core/agents/agent-worker-pipeline.js +9 -1
- package/dist/core/agents/native-worker-backend-router.js +50 -10
- package/dist/core/agents/ollama-worker-config.js +164 -15
- package/dist/core/codex/codex-0-137-compat.js +119 -0
- package/dist/core/codex-app.js +124 -2
- package/dist/core/codex-control/codex-control-proof.js +4 -1
- package/dist/core/codex-control/codex-sdk-capability.js +1 -1
- package/dist/core/codex-control/codex-task-runner.js +329 -5
- package/dist/core/codex-control/python-codex-sdk-adapter.js +197 -0
- package/dist/core/codex-control/python-codex-sdk-event-translator.js +14 -0
- package/dist/core/commands/local-model-command.js +65 -19
- package/dist/core/commands/naruto-command.js +124 -8
- package/dist/core/commands/run-command.js +1 -1
- package/dist/core/doctor/doctor-readiness-matrix.js +21 -2
- package/dist/core/fsx.js +1 -1
- package/dist/core/hooks-runtime.js +2 -233
- package/dist/core/init.js +8 -8
- package/dist/core/local-llm/local-llm-backpressure.js +20 -0
- package/dist/core/local-llm/local-llm-capability.js +29 -0
- package/dist/core/local-llm/local-llm-client.js +100 -0
- package/dist/core/local-llm/local-llm-config.js +6 -1
- package/dist/core/local-llm/local-llm-context-cache.js +21 -0
- package/dist/core/local-llm/local-llm-control-adapter.js +101 -0
- package/dist/core/local-llm/local-llm-json-repair.js +52 -0
- package/dist/core/local-llm/local-llm-metrics.js +42 -0
- package/dist/core/local-llm/local-llm-ollama-client.js +67 -0
- package/dist/core/local-llm/local-llm-openai-compatible-client.js +30 -0
- package/dist/core/local-llm/local-llm-prompt-cache.js +12 -0
- package/dist/core/local-llm/local-llm-scheduler.js +29 -0
- package/dist/core/local-llm/local-llm-schema-enforcer.js +15 -0
- package/dist/core/local-llm/local-llm-smoke.js +83 -0
- package/dist/core/local-llm/local-llm-warmup.js +20 -0
- package/dist/core/local-llm/local-worker-eligibility.js +27 -0
- package/dist/core/naruto/hardware-capacity-probe.js +36 -0
- package/dist/core/naruto/naruto-active-pool.js +134 -0
- package/dist/core/naruto/naruto-backpressure.js +13 -0
- package/dist/core/naruto/naruto-concurrency-governor.js +65 -0
- package/dist/core/naruto/naruto-finalizer.js +18 -0
- package/dist/core/naruto/naruto-generation-scheduler.js +18 -0
- package/dist/core/naruto/naruto-gpt-final-pack.js +49 -0
- package/dist/core/naruto/naruto-parallel-patch-apply.js +95 -0
- package/dist/core/naruto/naruto-patch-transaction-batch.js +42 -0
- package/dist/core/naruto/naruto-role-policy.js +107 -0
- package/dist/core/naruto/naruto-verification-dag.js +42 -0
- package/dist/core/naruto/naruto-verification-pool.js +18 -0
- package/dist/core/naruto/naruto-work-graph.js +198 -0
- package/dist/core/naruto/naruto-work-item.js +40 -0
- package/dist/core/naruto/naruto-work-stealing.js +11 -0
- package/dist/core/naruto/resource-pressure-monitor.js +32 -0
- package/dist/core/pipeline/finalize-pipeline-result.js +58 -0
- package/dist/core/pipeline/gpt-final-required.js +12 -0
- package/dist/core/pipeline-internals/runtime-core.js +1 -1
- package/dist/core/ppt.js +31 -8
- package/dist/core/product-design-app-server.js +410 -0
- package/dist/core/product-design-plugin.js +139 -0
- package/dist/core/prompt/prompt-placeholder-guard.js +30 -0
- package/dist/core/router/capability-card.js +13 -0
- package/dist/core/router/route-cache.js +3 -0
- package/dist/core/router/ultra-router.js +2 -1
- package/dist/core/routes.js +12 -12
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/zellij-lane-runtime.js +2 -2
- package/dist/core/zellij/zellij-naruto-dashboard.js +36 -0
- package/dist/core/zellij/zellij-worker-pane-manager.js +4 -4
- package/dist/scripts/blackbox-command-import-smoke.js +10 -1
- package/dist/scripts/check-package-boundary.js +12 -3
- package/dist/scripts/codex-0-137-compat-check.js +27 -0
- package/dist/scripts/codex-environment-scoped-approvals-check.js +10 -0
- package/dist/scripts/codex-plugin-list-json-check.js +8 -0
- package/dist/scripts/codex-thread-runtime-choice-check.js +10 -0
- package/dist/scripts/local-collab-all-pipelines-final-gpt-check.js +21 -0
- package/dist/scripts/local-llm-all-pipelines-check.js +11 -0
- package/dist/scripts/local-llm-cache-performance-check.js +10 -0
- package/dist/scripts/local-llm-capability-check.js +14 -0
- package/dist/scripts/local-llm-smoke-check.js +23 -0
- package/dist/scripts/local-llm-structured-output-check.js +11 -0
- package/dist/scripts/local-llm-throughput-check.js +10 -0
- package/dist/scripts/local-llm-tool-call-repair-check.js +10 -0
- package/dist/scripts/local-llm-warmup-check.js +11 -0
- package/dist/scripts/naruto-active-pool-check.js +39 -0
- package/dist/scripts/naruto-concurrency-governor-check.js +52 -0
- package/dist/scripts/naruto-gpt-final-pack-check.js +34 -0
- package/dist/scripts/naruto-parallel-patch-apply-check.js +41 -0
- package/dist/scripts/naruto-readonly-routing-check.js +116 -0
- package/dist/scripts/naruto-real-local-gpt-final-smoke.js +16 -0
- package/dist/scripts/naruto-role-distribution-check.js +23 -0
- package/dist/scripts/naruto-shadow-clone-swarm-check.js +13 -0
- package/dist/scripts/naruto-verification-pool-check.js +36 -0
- package/dist/scripts/naruto-work-graph-check.js +24 -0
- package/dist/scripts/naruto-zellij-massive-ui-check.js +23 -0
- package/dist/scripts/product-design-auto-install-check.js +119 -0
- package/dist/scripts/product-design-plugin-routing-check.js +101 -0
- package/dist/scripts/prompt-placeholder-guard-check.js +33 -0
- package/dist/scripts/python-codex-sdk-all-pipelines-check.js +47 -0
- package/dist/scripts/python-codex-sdk-capability-check.js +75 -0
- package/dist/scripts/python-codex-sdk-sandbox-policy-check.js +10 -0
- package/dist/scripts/python-codex-sdk-stream-bridge-check.js +12 -0
- package/dist/scripts/release-parallel-check.js +16 -2
- package/dist/scripts/release-provenance-check.js +21 -0
- package/dist/scripts/release-real-check.js +5 -0
- package/dist/scripts/zellij-worker-pane-manager-check.js +1 -1
- package/package.json +36 -4
- package/schemas/local-llm/local-model-config.schema.json +74 -0
- package/schemas/naruto/naruto-concurrency-governor.schema.json +21 -0
- package/schemas/naruto/naruto-work-graph.schema.json +22 -0
|
@@ -31,6 +31,13 @@ export async function writeAgentProofEvidence(root, input) {
|
|
|
31
31
|
const patchSwarm = input.patchSwarm || await readJson(path.join(root, 'agent-patch-swarm-runtime.json'), null);
|
|
32
32
|
const localCollaborationPolicy = input.localCollaborationPolicy || await readJson(path.join(root, 'local-collaboration-policy.json'), null) || resolveLocalCollaborationPolicy();
|
|
33
33
|
const gptFinalArbiter = input.gptFinalArbiter || await readJson(path.join(root, 'gpt-final-arbiter', 'gpt-final-arbiter.json'), null);
|
|
34
|
+
const narutoWorkGraph = await readJson(path.join(root, 'naruto-work-graph.json'), null);
|
|
35
|
+
const narutoRoleDistribution = await readJson(path.join(root, 'naruto-role-distribution.json'), null);
|
|
36
|
+
const narutoConcurrencyGovernor = await readJson(path.join(root, 'naruto-concurrency-governor.json'), null);
|
|
37
|
+
const narutoActivePool = await readJson(path.join(root, 'naruto-active-pool.json'), null);
|
|
38
|
+
const narutoVerificationDag = await readJson(path.join(root, 'naruto-verification-dag.json'), null);
|
|
39
|
+
const narutoGptFinalPack = await readJson(path.join(root, 'naruto-gpt-final-pack.json'), null);
|
|
40
|
+
const narutoZellijDashboard = await readJson(path.join(root, 'naruto-zellij-dashboard.json'), null);
|
|
34
41
|
const localParticipated = localCollaborationParticipated(input.results || []) || Number(gptFinalArbiter?.local_outputs_count || 0) > 0;
|
|
35
42
|
const finalGptPatchStage = input.finalGptPatchStage || null;
|
|
36
43
|
const localFinalGate = gptFinalArbiter?.final_gate || evaluateLocalCollaborationFinalGate({
|
|
@@ -74,6 +81,7 @@ export async function writeAgentProofEvidence(root, input) {
|
|
|
74
81
|
const workQueueGoalRefsOk = Boolean(workQueue?.items?.length) && workQueue.items.every((item) => item.goal_mode_ref);
|
|
75
82
|
const workQueueStrategyRefsOk = Boolean(workQueue?.items?.length) && workQueue.items.every((item) => item.slice?.strategy_refs);
|
|
76
83
|
const route = String(input.route || taskGraph?.route_type || '$Agent');
|
|
84
|
+
const isNarutoRoute = route === '$Naruto';
|
|
77
85
|
const routeCommand = String(input.routeCommand || 'sks agent run');
|
|
78
86
|
const genericAgentRouteStandIn = !/\$?agent$/i.test(route) && /\bagent\s+run\b/i.test(routeCommand) && /--route/i.test(routeCommand);
|
|
79
87
|
const realRouteCommandUsed = !genericAgentRouteStandIn;
|
|
@@ -111,6 +119,14 @@ export async function writeAgentProofEvidence(root, input) {
|
|
|
111
119
|
return !changed || Boolean(row.rollback_digest);
|
|
112
120
|
}) : true;
|
|
113
121
|
const parallelPatchApplyVerified = patchSwarm ? Array.isArray(patchProof?.wall_clock_parallel_evidence) && patchProof.wall_clock_parallel_evidence.length > 0 || Number(patchSwarm?.parallel_apply_count || 0) > 1 : false;
|
|
122
|
+
const readOnlyNoWriteLeaseMode = isReadOnlyNoWriteLeaseMode({
|
|
123
|
+
results: input.results || [],
|
|
124
|
+
leases: input.partition?.leases || [],
|
|
125
|
+
parallelWritePolicy,
|
|
126
|
+
taskGraph,
|
|
127
|
+
narutoWorkGraph
|
|
128
|
+
});
|
|
129
|
+
const changedFileLeaseBlockers = readOnlyNoWriteLeaseMode ? [] : agentChangedFileLeaseViolations(input.results || [], input.partition?.leases || []);
|
|
114
130
|
const blockers = [
|
|
115
131
|
...(lifecycle.ok ? [] : ['agent_lifecycle_not_all_closed']),
|
|
116
132
|
...(lifecycle.ok ? [] : lifecycle.open_sessions.map((id) => 'session_open:' + id)),
|
|
@@ -177,7 +193,17 @@ export async function writeAgentProofEvidence(root, input) {
|
|
|
177
193
|
...(localParticipated && localFinalGate.ok !== true ? localFinalGate.blockers || ['gpt_final_arbiter_gate_not_ok'] : []),
|
|
178
194
|
...(localParticipated && gptFinalArbiter?.ok !== true ? gptFinalArbiter?.blockers || ['gpt_final_arbiter_not_ok'] : []),
|
|
179
195
|
...(localParticipated && finalGptPatchStage?.ok === false ? finalGptPatchStage.blockers || ['final_gpt_patch_stage_not_ok'] : []),
|
|
180
|
-
...
|
|
196
|
+
...(isNarutoRoute && !narutoWorkGraph ? ['naruto_work_graph_missing'] : []),
|
|
197
|
+
...(isNarutoRoute && narutoWorkGraph?.ok === false ? narutoWorkGraph.blockers || ['naruto_work_graph_not_ok'] : []),
|
|
198
|
+
...(isNarutoRoute && !narutoRoleDistribution ? ['naruto_role_distribution_missing'] : []),
|
|
199
|
+
...(isNarutoRoute && narutoRoleDistribution?.ok === false ? narutoRoleDistribution.blockers || ['naruto_role_distribution_not_ok'] : []),
|
|
200
|
+
...(isNarutoRoute && !narutoConcurrencyGovernor ? ['naruto_concurrency_governor_missing'] : []),
|
|
201
|
+
...(isNarutoRoute && !narutoActivePool ? ['naruto_active_pool_missing'] : []),
|
|
202
|
+
...(isNarutoRoute && narutoActivePool?.ok === false ? narutoActivePool.blockers || ['naruto_active_pool_not_ok'] : []),
|
|
203
|
+
...(isNarutoRoute && !narutoVerificationDag ? ['naruto_verification_dag_missing'] : []),
|
|
204
|
+
...(isNarutoRoute && !narutoGptFinalPack ? ['naruto_gpt_final_pack_missing'] : []),
|
|
205
|
+
...(isNarutoRoute && !narutoZellijDashboard ? ['naruto_zellij_dashboard_missing'] : []),
|
|
206
|
+
...changedFileLeaseBlockers
|
|
181
207
|
];
|
|
182
208
|
const evidence = {
|
|
183
209
|
schema: AGENT_PROOF_EVIDENCE_SCHEMA,
|
|
@@ -222,6 +248,23 @@ export async function writeAgentProofEvidence(root, input) {
|
|
|
222
248
|
gpt_final_patch_source: finalGptPatchStage?.final_patch_source || (localParticipated ? 'blocked' : 'not_applicable'),
|
|
223
249
|
gpt_final_gate_ok: localFinalGate.ok === true,
|
|
224
250
|
gpt_final_gate: localFinalGate,
|
|
251
|
+
naruto_work_graph: narutoWorkGraph ? 'naruto-work-graph.json' : null,
|
|
252
|
+
naruto_total_work_items: Number(narutoWorkGraph?.total_work_items || 0),
|
|
253
|
+
naruto_mixed_work_kinds: narutoWorkGraph?.mixed_work_kinds || [],
|
|
254
|
+
naruto_write_allowed_count: Number(narutoWorkGraph?.write_allowed_count || 0),
|
|
255
|
+
naruto_role_distribution: narutoRoleDistribution ? 'naruto-role-distribution.json' : null,
|
|
256
|
+
naruto_role_distribution_entries: narutoRoleDistribution?.entries || [],
|
|
257
|
+
naruto_verifier_only: narutoRoleDistribution?.verifier_only === true,
|
|
258
|
+
naruto_implementation_like_ratio: Number(narutoRoleDistribution?.implementation_like_ratio || 0),
|
|
259
|
+
naruto_concurrency_governor: narutoConcurrencyGovernor ? 'naruto-concurrency-governor.json' : null,
|
|
260
|
+
naruto_safe_active_workers: Number(narutoConcurrencyGovernor?.safe_active_workers || 0),
|
|
261
|
+
naruto_safe_zellij_visible_panes: Number(narutoConcurrencyGovernor?.safe_zellij_visible_panes || 0),
|
|
262
|
+
naruto_headless_workers: Number(narutoConcurrencyGovernor?.headless_workers || 0),
|
|
263
|
+
naruto_active_pool: narutoActivePool ? 'naruto-active-pool.json' : null,
|
|
264
|
+
naruto_active_pool_refill_events: Number(narutoActivePool?.refill_events || 0),
|
|
265
|
+
naruto_verification_dag: narutoVerificationDag ? 'naruto-verification-dag.json' : null,
|
|
266
|
+
naruto_gpt_final_pack: narutoGptFinalPack ? 'naruto-gpt-final-pack.json' : null,
|
|
267
|
+
naruto_zellij_dashboard: narutoZellijDashboard ? 'naruto-zellij-dashboard.json' : null,
|
|
225
268
|
patch_swarm_runtime: patchSwarm ? 'agent-patch-swarm-runtime.json' : null,
|
|
226
269
|
patch_queue: patchSwarm ? 'agent-patch-queue.json' : null,
|
|
227
270
|
patch_queue_events: patchSwarm ? 'agent-patch-queue-events.jsonl' : null,
|
|
@@ -334,7 +377,7 @@ export async function writeAgentProofEvidence(root, input) {
|
|
|
334
377
|
triwiki_use_first_count: Number(input.triwikiContext?.use_first?.length || 0),
|
|
335
378
|
triwiki_hydrate_first_count: Number(input.triwikiContext?.hydrate_first?.length || 0),
|
|
336
379
|
triwiki_claim_count: Number(input.triwikiContext?.claim_count || 0),
|
|
337
|
-
changed_files_lease_checked:
|
|
380
|
+
changed_files_lease_checked: !readOnlyNoWriteLeaseMode,
|
|
338
381
|
dependency_collision_risk: input.partition?.no_overlap_proof?.dependency_collision_risk || [],
|
|
339
382
|
blockers
|
|
340
383
|
};
|
|
@@ -413,6 +456,20 @@ function agentChangedFileLeaseViolations(results, leases) {
|
|
|
413
456
|
}
|
|
414
457
|
return violations;
|
|
415
458
|
}
|
|
459
|
+
function isReadOnlyNoWriteLeaseMode(input) {
|
|
460
|
+
const writeLeaseCount = input.leases.filter((lease) => lease.kind === 'write').length;
|
|
461
|
+
if (writeLeaseCount > 0)
|
|
462
|
+
return false;
|
|
463
|
+
const resultWriteSignals = input.results.some((result) => (Array.isArray(result?.writes) && result.writes.length > 0)
|
|
464
|
+
|| (Array.isArray(result?.patch_envelopes) && result.patch_envelopes.length > 0));
|
|
465
|
+
if (resultWriteSignals)
|
|
466
|
+
return false;
|
|
467
|
+
const policyReadonly = input.parallelWritePolicy?.readonly === true;
|
|
468
|
+
const policyWriteOff = String(input.parallelWritePolicy?.write_mode || 'off') === 'off';
|
|
469
|
+
const narutoReadOnly = input.narutoWorkGraph?.readonly === true || Number(input.narutoWorkGraph?.write_allowed_count || 0) === 0;
|
|
470
|
+
const taskGraphNoWrites = Number(input.taskGraph?.write_allowed_count || 0) === 0;
|
|
471
|
+
return policyReadonly || policyWriteOff || narutoReadOnly || taskGraphNoWrites;
|
|
472
|
+
}
|
|
416
473
|
function pathWithin(file, leasePath) {
|
|
417
474
|
const left = String(file || '').replace(/\\/g, '/').replace(/^\.\//, '');
|
|
418
475
|
const right = String(leasePath || '').replace(/\\/g, '/').replace(/^\.\//, '').replace(/\/+$/, '');
|
|
@@ -2,6 +2,7 @@ import os from 'node:os';
|
|
|
2
2
|
import { DEFAULT_AGENT_CONCURRENCY, DEFAULT_AGENT_COUNT, DEFAULT_NARUTO_CLONES, MAX_AGENT_COUNT, MAX_NARUTO_AGENT_COUNT, agentSessionId } from './agent-schema.js';
|
|
3
3
|
import { defaultAgentPersonas, validatePersonaUniqueness } from './agent-persona.js';
|
|
4
4
|
import { buildAgentEffortPolicy, decideAgentEffort, decideNarutoCloneEffort } from './agent-effort-policy.js';
|
|
5
|
+
import { mapNarutoRoleToAgentRole, narutoRoleAllowsWrite } from '../naruto/naruto-role-policy.js';
|
|
5
6
|
// $Naruto must never blindly spawn the full clone count at once, but the live
|
|
6
7
|
// CONCURRENCY ceiling is NOT a function of CPU cores. Each clone is a separate CLI
|
|
7
8
|
// worker process that spends ~all of its wall-clock awaiting the Codex API
|
|
@@ -156,28 +157,40 @@ export function buildNarutoCloneRoster(opts = {}) {
|
|
|
156
157
|
const basePool = pool.length ? pool : defaultAgentPersonas(DEFAULT_AGENT_COUNT);
|
|
157
158
|
const personas = [];
|
|
158
159
|
const roster = [];
|
|
160
|
+
const roleCycle = narutoRoleCycle(readonly);
|
|
159
161
|
for (let index = 0; index < cloneCount; index += 1) {
|
|
160
162
|
const base = basePool[index % basePool.length];
|
|
161
163
|
const cloneTag = 'clone-' + String(index + 1).padStart(3, '0');
|
|
162
164
|
const id = 'naruto_' + cloneTag.replace(/-/g, '_');
|
|
163
|
-
const
|
|
165
|
+
const narutoRole = roleCycle[index % roleCycle.length] || 'verifier';
|
|
166
|
+
const writeAllowed = !readonly && narutoRoleAllowsWrite(narutoRole);
|
|
167
|
+
const cloneReadonly = readonly || !writeAllowed;
|
|
168
|
+
const role = mapNarutoRoleToAgentRole(narutoRole);
|
|
169
|
+
const allowedTools = writeAllowed ? ['read', 'search', 'edit', 'test'] : narutoRole === 'verifier' ? ['read', 'search', 'test'] : ['read', 'search'];
|
|
164
170
|
// Dynamic per-clone effort like team mode, capped at low/medium and always fast.
|
|
165
|
-
const effort = decideNarutoCloneEffort({ persona: base, prompt: opts.prompt || '', agentId: id, readonly: cloneReadonly });
|
|
171
|
+
const effort = decideNarutoCloneEffort({ persona: { ...base, role, allowed_tools: allowedTools, read_only: cloneReadonly, write_policy: writeAllowed ? 'exclusive Naruto patch-envelope lease required' : 'read-only Naruto role' }, prompt: opts.prompt || '', agentId: id, readonly: cloneReadonly });
|
|
166
172
|
const persona = {
|
|
167
173
|
...base,
|
|
168
174
|
id,
|
|
169
175
|
stable_id: base.stable_id + '-' + cloneTag,
|
|
170
|
-
|
|
171
|
-
|
|
176
|
+
role,
|
|
177
|
+
naruto_role: narutoRole,
|
|
178
|
+
write_allowed: writeAllowed,
|
|
179
|
+
read_only: cloneReadonly,
|
|
180
|
+
allowed_tools: allowedTools,
|
|
181
|
+
write_policy: writeAllowed ? 'exclusive Naruto patch-envelope lease required' : 'read-only Naruto role',
|
|
182
|
+
prompt: 'SHADOW CLONE: ' + cloneTag + ' (Kage Bunshin of ' + base.stable_id + ')\nNARUTO ROLE: ' + narutoRole + '\n' + base.prompt
|
|
172
183
|
};
|
|
173
184
|
personas.push(persona);
|
|
174
185
|
roster.push({
|
|
175
186
|
id,
|
|
176
187
|
session_id: agentSessionId(id, index + 1),
|
|
177
188
|
persona_id: id,
|
|
178
|
-
role
|
|
189
|
+
role,
|
|
190
|
+
naruto_role: narutoRole,
|
|
191
|
+
write_allowed: writeAllowed,
|
|
179
192
|
index: index + 1,
|
|
180
|
-
write_policy: cloneReadonly ? 'read-only' :
|
|
193
|
+
write_policy: cloneReadonly ? 'read-only' : 'exclusive Naruto patch-envelope lease required',
|
|
181
194
|
status: 'pending',
|
|
182
195
|
reasoning_effort: effort.reasoning_effort,
|
|
183
196
|
model_reasoning_effort: effort.model_reasoning_effort,
|
|
@@ -208,4 +221,20 @@ export function buildNarutoCloneRoster(opts = {}) {
|
|
|
208
221
|
};
|
|
209
222
|
return { ...result, effort_policy: buildAgentEffortPolicy(result) };
|
|
210
223
|
}
|
|
224
|
+
function narutoRoleCycle(readonly) {
|
|
225
|
+
if (readonly)
|
|
226
|
+
return ['verifier', 'researcher', 'verifier', 'gpt_final_arbiter'];
|
|
227
|
+
return [
|
|
228
|
+
'implementer',
|
|
229
|
+
'modifier',
|
|
230
|
+
'test_writer',
|
|
231
|
+
'verifier',
|
|
232
|
+
'researcher',
|
|
233
|
+
'conflict_resolver',
|
|
234
|
+
'rollback_planner',
|
|
235
|
+
'integrator',
|
|
236
|
+
'modifier',
|
|
237
|
+
'test_writer'
|
|
238
|
+
];
|
|
239
|
+
}
|
|
211
240
|
//# sourceMappingURL=agent-roster.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', 'ollama'];
|
|
17
|
+
export const AGENT_BACKENDS = ['fake', 'process', 'codex-sdk', 'zellij', 'ollama', 'local-llm'];
|
|
18
18
|
export function normalizeAgentBackend(input) {
|
|
19
19
|
const value = String(input || 'codex-sdk');
|
|
20
20
|
return AGENT_BACKENDS.includes(value) ? value : 'codex-sdk';
|
|
@@ -67,7 +67,8 @@ export function validateAgentWorkerResult(result) {
|
|
|
67
67
|
normalized.blockers.push(...patchEnvelopeValidation.blockers.map((issue) => 'patch_envelope_invalid:' + issue));
|
|
68
68
|
normalized.verification = { status: 'failed', checks: [...normalized.verification.checks, 'agent-patch-envelope-schema'] };
|
|
69
69
|
}
|
|
70
|
-
|
|
70
|
+
const readOnlyOrNoopWithoutPatch = acceptsNoPatchReadOnlyOrNoop(result?.no_patch_reason);
|
|
71
|
+
if (patchEnvelopeValidation.envelopes.length === 0 && (normalized.writes.length > 0 || (!readOnlyOrNoopWithoutPatch && normalized.changed_files.length > 0))) {
|
|
71
72
|
normalized.status = 'blocked';
|
|
72
73
|
normalized.blockers.push('no_patch_generated');
|
|
73
74
|
normalized.verification = { status: 'failed', checks: [...normalized.verification.checks, 'agent-patch-envelope-required-for-write'] };
|
|
@@ -132,4 +133,11 @@ function normalizeVerification(value) {
|
|
|
132
133
|
checks: Array.isArray(value?.checks) ? value.checks : []
|
|
133
134
|
};
|
|
134
135
|
}
|
|
136
|
+
function acceptsNoPatchReadOnlyOrNoop(value) {
|
|
137
|
+
if (!value || typeof value !== 'object')
|
|
138
|
+
return false;
|
|
139
|
+
return value.ok === true
|
|
140
|
+
&& value.read_only_or_noop_evidence === true
|
|
141
|
+
&& String(value.reason || '') === 'read_only_or_no_write_paths';
|
|
142
|
+
}
|
|
135
143
|
//# sourceMappingURL=agent-worker-pipeline.js.map
|
|
@@ -57,6 +57,7 @@ export async function runNativeWorkerBackendRouter(input) {
|
|
|
57
57
|
result = validateAgentWorkerResult({
|
|
58
58
|
...processRun,
|
|
59
59
|
patch_envelopes: patchEnvelopes,
|
|
60
|
+
...(patchEnvelopes.length ? {} : { no_patch_reason: buildNoPatchReason(input, backend) }),
|
|
60
61
|
artifacts: [...new Set([...(processRun.artifacts || []), ...(patchEnvelopes.length ? [input.patchRel] : [])])],
|
|
61
62
|
process_child_report: processReport,
|
|
62
63
|
model_authored_patch_envelopes: false,
|
|
@@ -81,12 +82,14 @@ export async function runNativeWorkerBackendRouter(input) {
|
|
|
81
82
|
...ollamaRun,
|
|
82
83
|
backend: 'ollama',
|
|
83
84
|
patch_envelopes: patchEnvelopes,
|
|
85
|
+
...(patchEnvelopes.length ? {} : { no_patch_reason: buildNoPatchReason(input, backend) }),
|
|
84
86
|
model_authored_patch_envelopes: patchEnvelopes.length > 0,
|
|
85
87
|
fixture_patch_envelopes: false,
|
|
86
88
|
verification: { status: ollamaRun.status === 'done' ? 'passed' : 'failed', checks: [...(ollamaRun.verification?.checks || []), 'native-worker-backend-router', 'ollama-api-generate'] }
|
|
87
89
|
});
|
|
88
90
|
}
|
|
89
|
-
else if (backend === 'codex-sdk' || backend === 'zellij') {
|
|
91
|
+
else if (backend === 'codex-sdk' || backend === 'zellij' || backend === 'local-llm') {
|
|
92
|
+
const localPreferred = backend === 'local-llm';
|
|
90
93
|
const sdkTask = await runCodexTask({
|
|
91
94
|
route: String(input.intake.route || '$Agent'),
|
|
92
95
|
tier: 'worker',
|
|
@@ -111,6 +114,12 @@ export async function runNativeWorkerBackendRouter(input) {
|
|
|
111
114
|
user_confirmed_full_access: false,
|
|
112
115
|
mad_sks_authorized: input.intake.mad_sks_authorized === true || process.env.SKS_MAD_SKS_ACTIVE === '1'
|
|
113
116
|
},
|
|
117
|
+
backendPreference: localPreferred ? ['local-llm', 'codex-sdk'] : ['codex-sdk'],
|
|
118
|
+
allowLocalLlm: localPreferred,
|
|
119
|
+
...(localPreferred ? { localLlmPolicy: {
|
|
120
|
+
mode: 'local_preferred',
|
|
121
|
+
requiresGptFinal: true
|
|
122
|
+
} } : {}),
|
|
114
123
|
mutationLedgerRoot: path.join(root, input.workerDirRel),
|
|
115
124
|
zellijPaneId: await readZellijPaneId(root, input.workerDirRel),
|
|
116
125
|
reliabilityPolicy: {
|
|
@@ -121,12 +130,14 @@ export async function runNativeWorkerBackendRouter(input) {
|
|
|
121
130
|
outputLastMessagePath = sdkTask.workerResultPath;
|
|
122
131
|
const sdkWorkerResult = await readJson(sdkTask.workerResultPath, null);
|
|
123
132
|
patchEnvelopes = normalizeSdkPatchEnvelopes(sdkWorkerResult?.patch_envelopes || [], input, sdkTask.sdkThreadId);
|
|
124
|
-
proofLevel = sdkTask.ok ? (patchEnvelopes.length ? 'model_authored' : 'codex_sdk_thread_proven') : 'blocked';
|
|
133
|
+
proofLevel = sdkTask.ok ? (patchEnvelopes.length ? 'model_authored' : sdkTask.backend === 'local-llm' ? 'local_llm_worker_proven' : 'codex_sdk_thread_proven') : 'blocked';
|
|
125
134
|
const sdkReport = {
|
|
126
135
|
schema: 'sks.codex-sdk-worker-adapter.v1',
|
|
127
|
-
backend:
|
|
136
|
+
backend: sdkTask.backend,
|
|
137
|
+
backend_family: sdkTask.backend_family,
|
|
128
138
|
sdk_thread_id: sdkTask.sdkThreadId,
|
|
129
139
|
sdk_run_id: sdkTask.sdkRunId,
|
|
140
|
+
local_llm_proof_path: sdkTask.localLlmProofPath || null,
|
|
130
141
|
stream_event_count: sdkTask.streamEventCount,
|
|
131
142
|
structured_output_valid: sdkTask.structuredOutputValid,
|
|
132
143
|
worker_result_path: sdkTask.workerResultPath,
|
|
@@ -136,17 +147,31 @@ export async function runNativeWorkerBackendRouter(input) {
|
|
|
136
147
|
childReports = [sdkReport];
|
|
137
148
|
result = validateAgentWorkerResult({
|
|
138
149
|
...sdkWorkerResult,
|
|
139
|
-
backend: 'codex-sdk',
|
|
150
|
+
backend: sdkTask.backend === 'local-llm' ? 'local-llm' : 'codex-sdk',
|
|
140
151
|
patch_envelopes: patchEnvelopes,
|
|
152
|
+
...(patchEnvelopes.length ? {} : { no_patch_reason: buildNoPatchReason(input, sdkTask.backend || backend) }),
|
|
141
153
|
codex_child_report: sdkReport,
|
|
142
154
|
codex_sdk_thread: sdkReport,
|
|
143
155
|
model_authored_patch_envelopes: patchEnvelopes.length > 0,
|
|
144
156
|
fixture_patch_envelopes: false,
|
|
145
|
-
artifacts: [...new Set([
|
|
157
|
+
artifacts: [...new Set([
|
|
158
|
+
...(sdkWorkerResult?.artifacts || []),
|
|
159
|
+
path.relative(root, sdkTask.workerResultPath),
|
|
160
|
+
path.join(input.workerDirRel, 'codex-control-proof.json'),
|
|
161
|
+
path.join(input.workerDirRel, 'codex-thread-registry.json'),
|
|
162
|
+
sdkTask.backend === 'local-llm' ? path.join(input.workerDirRel, 'local-llm-events.jsonl') : path.join(input.workerDirRel, 'codex-sdk-events.jsonl'),
|
|
163
|
+
...(sdkTask.localLlmProofPath ? [path.relative(root, sdkTask.localLlmProofPath)] : [])
|
|
164
|
+
])],
|
|
146
165
|
blockers: [...(sdkWorkerResult?.blockers || []), ...sdkTask.blockers],
|
|
147
166
|
verification: {
|
|
148
167
|
status: sdkTask.ok ? 'passed' : 'failed',
|
|
149
|
-
checks: [
|
|
168
|
+
checks: [
|
|
169
|
+
...(sdkWorkerResult?.verification?.checks || []),
|
|
170
|
+
sdkTask.backend === 'local-llm' ? 'local-llm-control-plane' : 'codex-sdk-control-plane',
|
|
171
|
+
sdkTask.backend === 'local-llm' ? 'local-llm-event-stream' : 'codex-sdk-event-stream',
|
|
172
|
+
sdkTask.backend === 'local-llm' ? 'local-llm-structured-output' : 'codex-sdk-structured-output',
|
|
173
|
+
...(sdkTask.backend === 'local-llm' ? ['gpt-final-required-before-acceptance'] : [])
|
|
174
|
+
]
|
|
150
175
|
}
|
|
151
176
|
});
|
|
152
177
|
}
|
|
@@ -167,6 +192,7 @@ export async function runNativeWorkerBackendRouter(input) {
|
|
|
167
192
|
result = validateAgentWorkerResult({
|
|
168
193
|
...zellijRun,
|
|
169
194
|
patch_envelopes: patchEnvelopes,
|
|
195
|
+
...(patchEnvelopes.length ? {} : { no_patch_reason: buildNoPatchReason(input, backend) }),
|
|
170
196
|
zellij_child_report: zellijReport,
|
|
171
197
|
model_authored_patch_envelopes: false,
|
|
172
198
|
fixture_patch_envelopes: false,
|
|
@@ -221,13 +247,13 @@ async function maybeAutoSelectOllamaBackend(backend, input) {
|
|
|
221
247
|
model: input.intake?.ollama_model || null,
|
|
222
248
|
baseUrl: input.intake?.ollama_base_url || null
|
|
223
249
|
}).catch(() => null);
|
|
224
|
-
if (!config?.ok || config.enabled !== true)
|
|
250
|
+
if (!config?.ok || config.enabled !== true || config.status !== 'verified')
|
|
225
251
|
return backend;
|
|
226
252
|
const policy = classifyOllamaWorkerSlice(input.slice, { route: input.intake?.route, agent: input.agent });
|
|
227
|
-
return policy.ok ? '
|
|
253
|
+
return policy.ok ? 'local-llm' : backend;
|
|
228
254
|
}
|
|
229
255
|
function normalizeBackend(value) {
|
|
230
|
-
return value === 'fake' || value === 'process' || value === 'codex-sdk' || value === 'zellij' || value === 'ollama' ? value : null;
|
|
256
|
+
return value === 'fake' || value === 'process' || value === 'codex-sdk' || value === 'zellij' || value === 'ollama' || value === 'local-llm' ? value : null;
|
|
231
257
|
}
|
|
232
258
|
function envelopeOpts(input, source, childPid) {
|
|
233
259
|
return {
|
|
@@ -257,10 +283,24 @@ function buildWorkerPrompt(slice) {
|
|
|
257
283
|
'',
|
|
258
284
|
write.length
|
|
259
285
|
? `Write-capable slice. Return JSON matching ${CODEX_AGENT_WORKER_RESULT_SCHEMA_ID}; include patch_envelopes for write_paths=${JSON.stringify(write)}.`
|
|
260
|
-
: `Read-only slice. Return JSON matching ${CODEX_AGENT_WORKER_RESULT_SCHEMA_ID}.`,
|
|
286
|
+
: `Read-only slice. Return JSON matching ${CODEX_AGENT_WORKER_RESULT_SCHEMA_ID}; do not report pre-existing repository dirtiness as changed_files.`,
|
|
261
287
|
'Required JSON fields: status, summary, findings, changed_files, patch_envelopes, verification, rollback_notes, blockers.'
|
|
262
288
|
].join('\n');
|
|
263
289
|
}
|
|
290
|
+
function buildNoPatchReason(input, backend) {
|
|
291
|
+
const writePathCount = writePaths(input.slice, input.intake).length;
|
|
292
|
+
return {
|
|
293
|
+
schema: 'sks.native-cli-worker-no-patch-reason.v1',
|
|
294
|
+
generated_at: nowIso(),
|
|
295
|
+
ok: writePathCount === 0,
|
|
296
|
+
reason: writePathCount ? 'write_capable_task_without_backend_patch_envelope' : 'read_only_or_no_write_paths',
|
|
297
|
+
route_justification: writePathCount ? 'backend returned no patch envelopes for a write-capable task' : 'task has no write paths',
|
|
298
|
+
read_only_or_noop_evidence: writePathCount === 0,
|
|
299
|
+
task_slice_id: input.slice?.id || null,
|
|
300
|
+
backend,
|
|
301
|
+
blockers: writePathCount && backend !== 'fake' ? ['write_capable_no_patch_envelope'] : []
|
|
302
|
+
};
|
|
303
|
+
}
|
|
264
304
|
function hasWriteLease(slice, intake) {
|
|
265
305
|
return writePaths(slice, intake).length > 0;
|
|
266
306
|
}
|
|
@@ -2,12 +2,16 @@ import os from 'node:os';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { ensureDir, exists, nowIso, readJson, writeJsonAtomic } from '../fsx.js';
|
|
4
4
|
export const LOCAL_MODEL_CONFIG_SCHEMA = 'sks.local-model-config.v1';
|
|
5
|
+
export const LOCAL_MODEL_CONFIG_SCHEMA_V2 = 'sks.local-model-config.v2';
|
|
5
6
|
export const OLLAMA_WORKER_CONFIG_SCHEMA = 'sks.ollama-worker-config.v1';
|
|
6
7
|
export const DEFAULT_OLLAMA_CODER_MODEL = 'rafw007/qwen36-a3b-claude-coder:q4_K_M';
|
|
7
8
|
export const DEFAULT_OLLAMA_BASE_URL = 'http://127.0.0.1:11434';
|
|
9
|
+
export const DEFAULT_MLX_LM_MODEL = 'mlx-community/Qwen3.6-35B-A3B-4bit';
|
|
10
|
+
export const DEFAULT_MLX_LM_BASE_URL = 'http://127.0.0.1:8080';
|
|
8
11
|
export const DEFAULT_OLLAMA_KEEP_ALIVE = '30m';
|
|
9
12
|
export const DEFAULT_OLLAMA_TIMEOUT_MS = 120_000;
|
|
10
13
|
export const DEFAULT_OLLAMA_THINK = false;
|
|
14
|
+
export const LOCAL_LLM_SMOKE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
11
15
|
export function localModelConfigPath() {
|
|
12
16
|
return process.env.SKS_LOCAL_MODEL_CONFIG
|
|
13
17
|
? path.resolve(process.env.SKS_LOCAL_MODEL_CONFIG)
|
|
@@ -30,28 +34,38 @@ export async function resolveOllamaWorkerConfig(input = {}) {
|
|
|
30
34
|
const explicitDisable = boolEnv(process.env.SKS_OLLAMA_WORKERS) === false;
|
|
31
35
|
const explicitEnable = boolEnv(process.env.SKS_OLLAMA_WORKERS) === true || input.ollamaEnabled === true || input.backend === 'ollama';
|
|
32
36
|
const enabled = explicitDisable ? false : explicitEnable || stored.enabled === true;
|
|
33
|
-
const
|
|
34
|
-
const
|
|
37
|
+
const explicitProvider = firstText(process.env.SKS_LOCAL_LLM_PROVIDER, input.provider, input.backend === 'ollama' ? 'ollama' : '');
|
|
38
|
+
const provider = explicitProvider ? normalizeProvider(explicitProvider) : normalizeProvider(stored.provider);
|
|
39
|
+
const storedMatchesProvider = stored.provider === provider;
|
|
40
|
+
const model = firstText(process.env.SKS_LOCAL_LLM_MODEL, process.env.SKS_OLLAMA_MODEL, input.model, storedMatchesProvider ? stored.model : '', defaultModelForProvider(provider));
|
|
41
|
+
const baseUrl = trimTrailingSlash(firstText(process.env.SKS_LOCAL_LLM_BASE_URL, process.env.SKS_OLLAMA_BASE_URL, input.baseUrl, storedMatchesProvider ? stored.base_url : '', defaultBaseUrlForProvider(provider)));
|
|
35
42
|
const keepAlive = firstText(process.env.SKS_OLLAMA_KEEP_ALIVE, input.keepAlive, stored.keep_alive, DEFAULT_OLLAMA_KEEP_ALIVE);
|
|
36
43
|
const timeoutMs = positiveNumber(process.env.SKS_OLLAMA_TIMEOUT_MS, input.timeoutMs, stored.timeout_ms, DEFAULT_OLLAMA_TIMEOUT_MS);
|
|
37
44
|
const temperature = finiteNumber(process.env.SKS_OLLAMA_TEMPERATURE, input.temperature, stored.temperature, 0.1);
|
|
38
45
|
const think = boolEnv(process.env.SKS_OLLAMA_THINK) ?? input.think ?? stored.think ?? DEFAULT_OLLAMA_THINK;
|
|
46
|
+
const status = enabled ? resolveEnabledStatus(stored) : 'disabled';
|
|
39
47
|
const blockers = [
|
|
40
48
|
...(enabled ? [] : ['ollama_workers_disabled']),
|
|
41
|
-
...(
|
|
42
|
-
...(!
|
|
49
|
+
...(enabled && status !== 'verified' ? [`local_llm_${status}`] : []),
|
|
50
|
+
...(!model ? ['local_model_missing'] : []),
|
|
51
|
+
...(!baseUrl ? ['local_model_base_url_missing'] : [])
|
|
43
52
|
];
|
|
44
53
|
return {
|
|
45
54
|
schema: OLLAMA_WORKER_CONFIG_SCHEMA,
|
|
46
55
|
ok: blockers.length === 0,
|
|
47
56
|
enabled,
|
|
48
|
-
|
|
57
|
+
status,
|
|
58
|
+
provider,
|
|
49
59
|
model,
|
|
50
60
|
base_url: baseUrl,
|
|
61
|
+
endpoint: baseUrl,
|
|
51
62
|
keep_alive: keepAlive,
|
|
52
63
|
timeout_ms: timeoutMs,
|
|
53
64
|
temperature,
|
|
54
65
|
think,
|
|
66
|
+
policy: stored.policy,
|
|
67
|
+
capability: stored.capability,
|
|
68
|
+
last_smoke: stored.last_smoke,
|
|
55
69
|
config_path: localModelConfigPath(),
|
|
56
70
|
explicit_disable: explicitDisable,
|
|
57
71
|
explicit_enable: explicitEnable,
|
|
@@ -59,25 +73,62 @@ export async function resolveOllamaWorkerConfig(input = {}) {
|
|
|
59
73
|
};
|
|
60
74
|
}
|
|
61
75
|
export function normalizeLocalModelConfig(raw = {}) {
|
|
76
|
+
const enabled = raw.enabled === true;
|
|
77
|
+
const lastSmoke = normalizeSmoke(raw.last_smoke || raw.lastSmoke || null);
|
|
78
|
+
const status = enabled ? normalizeStatus(raw.status, lastSmoke) : 'disabled';
|
|
79
|
+
const provider = normalizeProvider(raw.provider);
|
|
80
|
+
const baseUrl = trimTrailingSlash(firstText(raw.base_url, raw.baseUrl, raw.endpoint, defaultBaseUrlForProvider(provider)));
|
|
62
81
|
return {
|
|
63
|
-
schema:
|
|
82
|
+
schema: LOCAL_MODEL_CONFIG_SCHEMA_V2,
|
|
64
83
|
...(raw.generated_at ? { generated_at: String(raw.generated_at) } : { generated_at: nowIso() }),
|
|
65
84
|
...(raw.updated_at ? { updated_at: String(raw.updated_at) } : {}),
|
|
66
|
-
enabled
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
85
|
+
enabled,
|
|
86
|
+
status,
|
|
87
|
+
provider,
|
|
88
|
+
model: firstText(raw.model, defaultModelForProvider(provider)),
|
|
89
|
+
endpoint: baseUrl,
|
|
90
|
+
base_url: baseUrl,
|
|
70
91
|
keep_alive: firstText(raw.keep_alive, raw.keepAlive, DEFAULT_OLLAMA_KEEP_ALIVE),
|
|
71
92
|
timeout_ms: positiveNumber(raw.timeout_ms, raw.timeoutMs, DEFAULT_OLLAMA_TIMEOUT_MS),
|
|
72
93
|
temperature: finiteNumber(raw.temperature, 0.1),
|
|
73
94
|
think: typeof raw.think === 'boolean' ? raw.think : DEFAULT_OLLAMA_THINK,
|
|
74
|
-
policy:
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
95
|
+
policy: normalizePolicy(raw.policy),
|
|
96
|
+
capability: normalizeCapability(raw.capability),
|
|
97
|
+
last_smoke: lastSmoke,
|
|
98
|
+
blockers: Array.isArray(raw.blockers) ? raw.blockers.map(String) : status === 'blocked' ? ['local_llm_smoke_failed'] : []
|
|
79
99
|
};
|
|
80
100
|
}
|
|
101
|
+
export function applyLocalLlmSmokeResult(config, smoke) {
|
|
102
|
+
const blockers = Array.isArray(smoke.blockers) ? smoke.blockers.map(String) : [];
|
|
103
|
+
const status = smoke.skipped
|
|
104
|
+
? 'enabled_unverified'
|
|
105
|
+
: smoke.ok && smoke.schema_valid !== false
|
|
106
|
+
? 'verified'
|
|
107
|
+
: smoke.status === 'degraded'
|
|
108
|
+
? 'degraded'
|
|
109
|
+
: 'blocked';
|
|
110
|
+
return normalizeLocalModelConfig({
|
|
111
|
+
...config,
|
|
112
|
+
enabled: true,
|
|
113
|
+
status,
|
|
114
|
+
capability: {
|
|
115
|
+
...config.capability,
|
|
116
|
+
api_reachable: smoke.ok !== false,
|
|
117
|
+
model_installed: smoke.ok !== false
|
|
118
|
+
},
|
|
119
|
+
last_smoke: {
|
|
120
|
+
...smoke,
|
|
121
|
+
status
|
|
122
|
+
},
|
|
123
|
+
blockers
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
export function localModelSmokeFresh(smoke, now = Date.now()) {
|
|
127
|
+
if (!smoke?.ok || smoke.schema_valid === false || !smoke.ran_at)
|
|
128
|
+
return false;
|
|
129
|
+
const ranAt = Date.parse(smoke.ran_at);
|
|
130
|
+
return Number.isFinite(ranAt) && now - ranAt <= LOCAL_LLM_SMOKE_TTL_MS;
|
|
131
|
+
}
|
|
81
132
|
export function boolEnv(value) {
|
|
82
133
|
const text = String(value ?? '').trim().toLowerCase();
|
|
83
134
|
if (!text)
|
|
@@ -99,6 +150,104 @@ function firstText(...values) {
|
|
|
99
150
|
function trimTrailingSlash(value) {
|
|
100
151
|
return value.replace(/\/+$/, '');
|
|
101
152
|
}
|
|
153
|
+
export function normalizeProvider(...values) {
|
|
154
|
+
for (const value of values) {
|
|
155
|
+
const text = String(value ?? '').trim().toLowerCase();
|
|
156
|
+
if (!text)
|
|
157
|
+
continue;
|
|
158
|
+
if (['ollama'].includes(text))
|
|
159
|
+
return 'ollama';
|
|
160
|
+
if (['mlx', 'mlx-lm', 'mlx_lm', 'mlxlm'].includes(text))
|
|
161
|
+
return 'mlx-lm';
|
|
162
|
+
if (['openai-compatible', 'openai_compatible', 'openai', 'openai-compatible-local'].includes(text))
|
|
163
|
+
return 'openai-compatible';
|
|
164
|
+
}
|
|
165
|
+
return 'ollama';
|
|
166
|
+
}
|
|
167
|
+
export function defaultModelForProvider(provider) {
|
|
168
|
+
if (provider === 'mlx-lm')
|
|
169
|
+
return DEFAULT_MLX_LM_MODEL;
|
|
170
|
+
if (provider === 'openai-compatible')
|
|
171
|
+
return '';
|
|
172
|
+
return DEFAULT_OLLAMA_CODER_MODEL;
|
|
173
|
+
}
|
|
174
|
+
export function defaultBaseUrlForProvider(provider) {
|
|
175
|
+
if (provider === 'mlx-lm')
|
|
176
|
+
return DEFAULT_MLX_LM_BASE_URL;
|
|
177
|
+
if (provider === 'openai-compatible')
|
|
178
|
+
return '';
|
|
179
|
+
return DEFAULT_OLLAMA_BASE_URL;
|
|
180
|
+
}
|
|
181
|
+
function resolveEnabledStatus(config) {
|
|
182
|
+
if (config.status === 'verified' && !localModelSmokeFresh(config.last_smoke))
|
|
183
|
+
return 'enabled_unverified';
|
|
184
|
+
return config.status === 'disabled' ? 'enabled_unverified' : config.status;
|
|
185
|
+
}
|
|
186
|
+
function normalizeStatus(value, smoke) {
|
|
187
|
+
const text = String(value ?? '').trim();
|
|
188
|
+
const known = ['disabled', 'enabled_unverified', 'verified', 'degraded', 'blocked'];
|
|
189
|
+
if (known.includes(text)) {
|
|
190
|
+
if (text === 'verified' && !localModelSmokeFresh(smoke))
|
|
191
|
+
return 'enabled_unverified';
|
|
192
|
+
return text;
|
|
193
|
+
}
|
|
194
|
+
return localModelSmokeFresh(smoke) ? 'verified' : 'enabled_unverified';
|
|
195
|
+
}
|
|
196
|
+
function normalizePolicy(value) {
|
|
197
|
+
const defaultAllowed = ['simple_patch_envelope', 'read_only_collection', 'grep_like_qa', 'test_generation_draft'];
|
|
198
|
+
const allowed = [
|
|
199
|
+
...defaultAllowed,
|
|
200
|
+
...(Array.isArray(value?.allowed_task_classes) ? value.allowed_task_classes : []),
|
|
201
|
+
...(Array.isArray(value?.allowed_work) ? value.allowed_work : [])
|
|
202
|
+
];
|
|
203
|
+
const forbidden = Array.isArray(value?.forbidden_task_classes) ? value.forbidden_task_classes : [
|
|
204
|
+
'planning',
|
|
205
|
+
'strategy',
|
|
206
|
+
'final_review',
|
|
207
|
+
'verification_authority',
|
|
208
|
+
'safety_authority',
|
|
209
|
+
'integration_authority'
|
|
210
|
+
];
|
|
211
|
+
return {
|
|
212
|
+
role: 'worker_only',
|
|
213
|
+
allowed_task_classes: [...new Set(allowed.map(String))],
|
|
214
|
+
forbidden_task_classes: forbidden.map(String),
|
|
215
|
+
requires_gpt_final: value?.requires_gpt_final !== false
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
function normalizeCapability(value) {
|
|
219
|
+
return {
|
|
220
|
+
api_reachable: value?.api_reachable === true,
|
|
221
|
+
model_installed: value?.model_installed === true,
|
|
222
|
+
supports_streaming: value?.supports_streaming !== false,
|
|
223
|
+
supports_json_schema: value?.supports_json_schema === true,
|
|
224
|
+
supports_tools: value?.supports_tools === true,
|
|
225
|
+
supports_images: value?.supports_images === true,
|
|
226
|
+
context_window: positiveNumber(value?.context_window, 32768),
|
|
227
|
+
max_parallel_requests: Math.max(1, Math.min(16, positiveNumber(value?.max_parallel_requests, 4)))
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
function normalizeSmoke(value) {
|
|
231
|
+
if (!value || typeof value !== 'object')
|
|
232
|
+
return null;
|
|
233
|
+
return {
|
|
234
|
+
ok: value.ok === true,
|
|
235
|
+
...(value.skipped === true ? { skipped: true } : {}),
|
|
236
|
+
...(value.ran_at ? { ran_at: String(value.ran_at) } : {}),
|
|
237
|
+
...(value.prompt_hash ? { prompt_hash: String(value.prompt_hash) } : {}),
|
|
238
|
+
...(Number.isFinite(Number(value.latency_ms)) ? { latency_ms: Number(value.latency_ms) } : {}),
|
|
239
|
+
...(Number.isFinite(Number(value.tokens_per_second)) ? { tokens_per_second: Number(value.tokens_per_second) } : {}),
|
|
240
|
+
...(typeof value.schema_valid === 'boolean' ? { schema_valid: value.schema_valid } : {}),
|
|
241
|
+
...(value.result_path ? { result_path: String(value.result_path) } : {}),
|
|
242
|
+
...(normalizeKnownStatus(value.status) ? { status: normalizeKnownStatus(value.status) } : {}),
|
|
243
|
+
...(value.reason ? { reason: String(value.reason) } : {}),
|
|
244
|
+
...(Array.isArray(value.blockers) ? { blockers: value.blockers.map(String) } : {})
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
function normalizeKnownStatus(value) {
|
|
248
|
+
const text = String(value ?? '').trim();
|
|
249
|
+
return ['disabled', 'enabled_unverified', 'verified', 'degraded', 'blocked'].includes(text) ? text : undefined;
|
|
250
|
+
}
|
|
102
251
|
function positiveNumber(...values) {
|
|
103
252
|
for (const value of values) {
|
|
104
253
|
const n = Number(value);
|