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
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { nowIso, sha256 } from '../fsx.js';
|
|
2
|
+
import { evaluateLocalWorkerEligibility } from './local-worker-eligibility.js';
|
|
3
|
+
import { callLocalLlmGenerate, localLlmTokensPerSecond } from './local-llm-client.js';
|
|
4
|
+
import { enforceLocalLlmJsonSchema } from './local-llm-schema-enforcer.js';
|
|
5
|
+
export async function runLocalLlmTask(input, opts) {
|
|
6
|
+
const started = Date.now();
|
|
7
|
+
const requestId = `local-llm:${sha256(`${nowIso()}:${input.missionId}:${input.workItemId || ''}:${input.slotId || ''}`).slice(0, 16)}`;
|
|
8
|
+
const eligibility = evaluateLocalWorkerEligibility(input, opts.config);
|
|
9
|
+
if (!eligibility.ok) {
|
|
10
|
+
return {
|
|
11
|
+
ok: false,
|
|
12
|
+
backend: 'local-llm',
|
|
13
|
+
backendFamily: 'local-llm',
|
|
14
|
+
requestId,
|
|
15
|
+
events: [],
|
|
16
|
+
finalResponse: '',
|
|
17
|
+
structuredOutput: null,
|
|
18
|
+
structuredOutputValid: false,
|
|
19
|
+
proof: buildProof(input, opts.config, requestId, started, eligibility, null, ['local_llm_eligibility_blocked', ...eligibility.blockers]),
|
|
20
|
+
blockers: ['local_llm_eligibility_blocked', ...eligibility.blockers]
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
const prompt = buildLocalLlmTaskPrompt(input, opts.outputSchema);
|
|
24
|
+
const response = await callLocalLlmGenerate(opts.config, {
|
|
25
|
+
model: opts.config.model,
|
|
26
|
+
prompt,
|
|
27
|
+
stream: false,
|
|
28
|
+
format: 'json',
|
|
29
|
+
think: opts.config.think,
|
|
30
|
+
keep_alive: opts.config.keep_alive,
|
|
31
|
+
options: { temperature: opts.config.temperature }
|
|
32
|
+
});
|
|
33
|
+
const latencyMs = Math.max(0, Date.now() - started);
|
|
34
|
+
const enforced = response.ok ? enforceLocalLlmJsonSchema(response.text, opts.outputSchema) : { ok: false, value: null, schema_valid: false, issues: [response.error] };
|
|
35
|
+
const blockers = [
|
|
36
|
+
...(response.ok ? [] : ['local_llm_generate_failed', response.error]),
|
|
37
|
+
...(enforced.ok ? [] : ['local_llm_structured_output_invalid', ...(enforced.issues || []).map((issue) => `schema:${String(issue)}`)])
|
|
38
|
+
];
|
|
39
|
+
const event = {
|
|
40
|
+
schema: 'sks.local-llm-event.v1',
|
|
41
|
+
generated_at: nowIso(),
|
|
42
|
+
type: response.ok ? 'local_llm_generate_completed' : 'local_llm_generate_failed',
|
|
43
|
+
request_id: requestId,
|
|
44
|
+
latency_ms: latencyMs,
|
|
45
|
+
schema_valid: enforced.schema_valid === true
|
|
46
|
+
};
|
|
47
|
+
return {
|
|
48
|
+
ok: blockers.length === 0,
|
|
49
|
+
backend: 'local-llm',
|
|
50
|
+
backendFamily: 'local-llm',
|
|
51
|
+
requestId,
|
|
52
|
+
events: [event],
|
|
53
|
+
finalResponse: JSON.stringify(enforced.value || {}),
|
|
54
|
+
structuredOutput: enforced.value,
|
|
55
|
+
structuredOutputValid: enforced.schema_valid === true,
|
|
56
|
+
proof: buildProof(input, opts.config, requestId, started, eligibility, response.ok ? response.data : null, blockers, latencyMs, response.ok ? localLlmTokensPerSecond(response.data, response.text, latencyMs) : 0),
|
|
57
|
+
blockers
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function buildLocalLlmTaskPrompt(input, outputSchema) {
|
|
61
|
+
return [
|
|
62
|
+
'You are an SKS local LLM worker backend. You are worker-only.',
|
|
63
|
+
'You must not make strategy, planning, safety, verification, integration, or final acceptance decisions.',
|
|
64
|
+
'Return JSON only. Natural language outside JSON is invalid.',
|
|
65
|
+
'Your output is a draft and requires GPT Final Arbiter before final acceptance or patch application.',
|
|
66
|
+
JSON.stringify({
|
|
67
|
+
route: input.route,
|
|
68
|
+
mission_id: input.missionId,
|
|
69
|
+
work_item_id: input.workItemId || null,
|
|
70
|
+
slot_id: input.slotId || null,
|
|
71
|
+
sandbox_policy: input.sandboxPolicy,
|
|
72
|
+
prompt: input.prompt,
|
|
73
|
+
output_schema: outputSchema
|
|
74
|
+
}, null, 2)
|
|
75
|
+
].join('\n');
|
|
76
|
+
}
|
|
77
|
+
function buildProof(input, config, requestId, started, eligibility, rawResponse, blockers, latencyMs = Math.max(0, Date.now() - started), tokensPerSecond = 0) {
|
|
78
|
+
return {
|
|
79
|
+
schema: 'sks.local-llm-proof.v1',
|
|
80
|
+
generated_at: nowIso(),
|
|
81
|
+
ok: blockers.length === 0,
|
|
82
|
+
request_id: requestId,
|
|
83
|
+
provider: config.provider,
|
|
84
|
+
model: config.model,
|
|
85
|
+
endpoint: config.base_url,
|
|
86
|
+
backend: 'local-llm',
|
|
87
|
+
backend_family: 'local-llm',
|
|
88
|
+
route: input.route,
|
|
89
|
+
mission_id: input.missionId,
|
|
90
|
+
work_item_id: input.workItemId || null,
|
|
91
|
+
slot_id: input.slotId || null,
|
|
92
|
+
safety_scope: 'worker_only_requires_gpt_final',
|
|
93
|
+
latency_ms: latencyMs,
|
|
94
|
+
tokens_per_second: tokensPerSecond,
|
|
95
|
+
schema_valid: blockers.length === 0,
|
|
96
|
+
eligibility,
|
|
97
|
+
raw_response: rawResponse,
|
|
98
|
+
blockers
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=local-llm-control-adapter.js.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export function parseOrRepairLocalLlmJson(text) {
|
|
2
|
+
const raw = String(text || '').trim().replace(/^```(?:json)?\s*/i, '').replace(/\s*```$/, '');
|
|
3
|
+
const direct = tryParse(raw);
|
|
4
|
+
if (direct.ok)
|
|
5
|
+
return { ok: true, value: direct.value, repaired: false, attempts: 0 };
|
|
6
|
+
const extracted = extractFirstJsonObject(raw);
|
|
7
|
+
if (extracted) {
|
|
8
|
+
const parsed = tryParse(extracted);
|
|
9
|
+
if (parsed.ok)
|
|
10
|
+
return { ok: true, value: parsed.value, repaired: true, attempts: 1 };
|
|
11
|
+
}
|
|
12
|
+
return { ok: false, value: null, repaired: false, attempts: 1, error: direct.error || 'json_parse_failed' };
|
|
13
|
+
}
|
|
14
|
+
function tryParse(text) {
|
|
15
|
+
try {
|
|
16
|
+
return { ok: true, value: JSON.parse(text) };
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function extractFirstJsonObject(text) {
|
|
23
|
+
const start = text.indexOf('{');
|
|
24
|
+
if (start < 0)
|
|
25
|
+
return '';
|
|
26
|
+
let depth = 0;
|
|
27
|
+
let inString = false;
|
|
28
|
+
let escaped = false;
|
|
29
|
+
for (let i = start; i < text.length; i += 1) {
|
|
30
|
+
const ch = text[i];
|
|
31
|
+
if (inString) {
|
|
32
|
+
if (escaped)
|
|
33
|
+
escaped = false;
|
|
34
|
+
else if (ch === '\\')
|
|
35
|
+
escaped = true;
|
|
36
|
+
else if (ch === '"')
|
|
37
|
+
inString = false;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (ch === '"')
|
|
41
|
+
inString = true;
|
|
42
|
+
else if (ch === '{')
|
|
43
|
+
depth += 1;
|
|
44
|
+
else if (ch === '}') {
|
|
45
|
+
depth -= 1;
|
|
46
|
+
if (depth === 0)
|
|
47
|
+
return text.slice(start, i + 1);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return '';
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=local-llm-json-repair.js.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { classifyLocalLlmBackpressure } from './local-llm-backpressure.js';
|
|
2
|
+
export function buildLocalLlmMetrics(input) {
|
|
3
|
+
const latencies = numeric(input.latencyMsSamples);
|
|
4
|
+
const metrics = {
|
|
5
|
+
schema: 'sks.local-llm-metrics.v1',
|
|
6
|
+
provider: input.provider || 'ollama',
|
|
7
|
+
model: input.model || 'unknown',
|
|
8
|
+
active_requests: Math.max(0, Math.floor(Number(input.activeRequests || 0))),
|
|
9
|
+
max_parallel_requests: Math.max(1, Math.floor(Number(input.maxParallelRequests || 1))),
|
|
10
|
+
queue_depth: Math.max(0, Math.floor(Number(input.queueDepth || 0))),
|
|
11
|
+
avg_first_token_ms: average(numeric(input.firstTokenMsSamples)),
|
|
12
|
+
avg_tokens_per_second: average(numeric(input.tokenPerSecondSamples)),
|
|
13
|
+
p95_latency_ms: percentile(latencies, 0.95)
|
|
14
|
+
};
|
|
15
|
+
const backpressureInput = {
|
|
16
|
+
activeRequests: metrics.active_requests,
|
|
17
|
+
maxParallelRequests: metrics.max_parallel_requests,
|
|
18
|
+
queueDepth: metrics.queue_depth
|
|
19
|
+
};
|
|
20
|
+
if (metrics.p95_latency_ms !== undefined)
|
|
21
|
+
backpressureInput.p95LatencyMs = metrics.p95_latency_ms;
|
|
22
|
+
return {
|
|
23
|
+
...metrics,
|
|
24
|
+
backpressure: classifyLocalLlmBackpressure(backpressureInput).state
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function numeric(values = []) {
|
|
28
|
+
return values.map(Number).filter((value) => Number.isFinite(value) && value >= 0);
|
|
29
|
+
}
|
|
30
|
+
function average(values) {
|
|
31
|
+
if (!values.length)
|
|
32
|
+
return 0;
|
|
33
|
+
return Number((values.reduce((sum, value) => sum + value, 0) / values.length).toFixed(2));
|
|
34
|
+
}
|
|
35
|
+
function percentile(values, p) {
|
|
36
|
+
if (!values.length)
|
|
37
|
+
return 0;
|
|
38
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
39
|
+
const index = Math.min(sorted.length - 1, Math.ceil(sorted.length * p) - 1);
|
|
40
|
+
return sorted[index];
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=local-llm-metrics.js.map
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export async function probeOllamaVersion(baseUrl, timeoutMs = 3000) {
|
|
2
|
+
try {
|
|
3
|
+
const response = await fetch(`${trimTrailingSlash(baseUrl)}/api/version`, { signal: AbortSignal.timeout(timeoutMs) });
|
|
4
|
+
const text = await response.text();
|
|
5
|
+
return { ok: response.ok, status: response.status, data: response.ok ? JSON.parse(text) : null, error: response.ok ? null : text.slice(0, 500) };
|
|
6
|
+
}
|
|
7
|
+
catch (error) {
|
|
8
|
+
return { ok: false, status: 0, data: null, error: error instanceof Error ? error.message : String(error) };
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export async function listOllamaModels(baseUrl, timeoutMs = 5000) {
|
|
12
|
+
try {
|
|
13
|
+
const response = await fetch(`${trimTrailingSlash(baseUrl)}/api/tags`, { signal: AbortSignal.timeout(timeoutMs) });
|
|
14
|
+
const text = await response.text();
|
|
15
|
+
const data = response.ok ? JSON.parse(text) : null;
|
|
16
|
+
const models = Array.isArray(data?.models) ? data.models.map((model) => String(model?.name || '')).filter(Boolean) : [];
|
|
17
|
+
return { ok: response.ok, status: response.status, models, data, error: response.ok ? null : text.slice(0, 500) };
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
return { ok: false, status: 0, models: [], data: null, error: error instanceof Error ? error.message : String(error) };
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export async function callOllamaGenerate(config, request) {
|
|
24
|
+
const controller = new AbortController();
|
|
25
|
+
const timer = setTimeout(() => controller.abort(), Math.max(1, Number(config.timeout_ms || 20_000)));
|
|
26
|
+
try {
|
|
27
|
+
const response = await fetch(`${trimTrailingSlash(config.base_url)}/api/generate`, {
|
|
28
|
+
method: 'POST',
|
|
29
|
+
headers: { 'Content-Type': 'application/json' },
|
|
30
|
+
body: JSON.stringify(request),
|
|
31
|
+
signal: controller.signal
|
|
32
|
+
});
|
|
33
|
+
const text = await response.text();
|
|
34
|
+
if (!response.ok)
|
|
35
|
+
return { ok: false, status: response.status, error: `http_${response.status}:${text.slice(0, 500)}` };
|
|
36
|
+
const data = JSON.parse(text);
|
|
37
|
+
return { ok: true, data, text: extractOllamaText(data) };
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
clearTimeout(timer);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export function extractOllamaText(data) {
|
|
47
|
+
if (typeof data.response === 'string')
|
|
48
|
+
return data.response;
|
|
49
|
+
if (typeof data.message?.content === 'string')
|
|
50
|
+
return data.message.content;
|
|
51
|
+
if (typeof data.content === 'string')
|
|
52
|
+
return data.content;
|
|
53
|
+
return '';
|
|
54
|
+
}
|
|
55
|
+
export function ollamaTokensPerSecond(data, fallbackText = '', latencyMs = 0) {
|
|
56
|
+
const evalCount = Number(data.eval_count || 0);
|
|
57
|
+
const evalDurationNs = Number(data.eval_duration || 0);
|
|
58
|
+
if (evalCount > 0 && evalDurationNs > 0)
|
|
59
|
+
return Number((evalCount / (evalDurationNs / 1_000_000_000)).toFixed(2));
|
|
60
|
+
const approximateTokens = Math.max(1, Math.ceil(fallbackText.length / 4));
|
|
61
|
+
const seconds = Math.max(0.001, latencyMs / 1000);
|
|
62
|
+
return Number((approximateTokens / seconds).toFixed(2));
|
|
63
|
+
}
|
|
64
|
+
function trimTrailingSlash(value) {
|
|
65
|
+
return String(value || '').replace(/\/+$/, '');
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=local-llm-ollama-client.js.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export async function callOpenAiCompatibleLocalChat(input, timeoutMs = 20_000) {
|
|
2
|
+
try {
|
|
3
|
+
const response = await fetch(`${input.endpoint.replace(/\/+$/, '')}/v1/chat/completions`, {
|
|
4
|
+
method: 'POST',
|
|
5
|
+
headers: { 'Content-Type': 'application/json' },
|
|
6
|
+
body: JSON.stringify({
|
|
7
|
+
model: input.model,
|
|
8
|
+
messages: input.messages,
|
|
9
|
+
temperature: input.temperature ?? 0
|
|
10
|
+
}),
|
|
11
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
12
|
+
});
|
|
13
|
+
const text = await response.text();
|
|
14
|
+
return {
|
|
15
|
+
ok: response.ok,
|
|
16
|
+
status: response.status,
|
|
17
|
+
data: response.ok ? JSON.parse(text) : null,
|
|
18
|
+
error: response.ok ? null : text.slice(0, 500)
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
return {
|
|
23
|
+
ok: false,
|
|
24
|
+
status: 0,
|
|
25
|
+
data: null,
|
|
26
|
+
error: error instanceof Error ? error.message : String(error)
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=local-llm-openai-compatible-client.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { buildLocalLlmContextCacheKey } from './local-llm-context-cache.js';
|
|
2
|
+
export function buildLocalLlmPromptCacheRecord(input) {
|
|
3
|
+
const key = buildLocalLlmContextCacheKey(input);
|
|
4
|
+
return {
|
|
5
|
+
schema: 'sks.local-llm-prompt-cache.v1',
|
|
6
|
+
cache_key: key.key,
|
|
7
|
+
source_hashes: input,
|
|
8
|
+
cacheable: Object.values(input).every(Boolean),
|
|
9
|
+
forbidden_material: ['secrets', 'auth tokens', 'raw private config']
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=local-llm-prompt-cache.js.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { buildLocalLlmMetrics } from './local-llm-metrics.js';
|
|
2
|
+
export function planLocalLlmSchedule(input) {
|
|
3
|
+
const max = Math.max(1, Math.floor(Number(input.maxParallelRequests || 1)));
|
|
4
|
+
const active = Math.max(0, Math.floor(Number(input.activeRequests || 0)));
|
|
5
|
+
const available = Math.max(0, max - active);
|
|
6
|
+
const queueDepth = Math.max(0, input.workItems.length - available);
|
|
7
|
+
const dispatch = input.workItems.slice(0, available);
|
|
8
|
+
const queued = input.workItems.slice(available);
|
|
9
|
+
const metrics = buildLocalLlmMetrics({
|
|
10
|
+
activeRequests: active + dispatch.length,
|
|
11
|
+
maxParallelRequests: max,
|
|
12
|
+
queueDepth,
|
|
13
|
+
latencyMsSamples: input.latencyMsSamples || []
|
|
14
|
+
});
|
|
15
|
+
return {
|
|
16
|
+
schema: 'sks.local-llm-scheduler-plan.v1',
|
|
17
|
+
ok: metrics.active_requests <= max,
|
|
18
|
+
max_parallel_requests: max,
|
|
19
|
+
active_requests: metrics.active_requests,
|
|
20
|
+
dispatch_count: dispatch.length,
|
|
21
|
+
queued_count: queued.length,
|
|
22
|
+
backpressure: metrics.backpressure,
|
|
23
|
+
dispatch,
|
|
24
|
+
queued,
|
|
25
|
+
metrics,
|
|
26
|
+
blockers: metrics.active_requests <= max ? [] : ['local_llm_active_requests_exceeded']
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=local-llm-scheduler.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { validateJsonSchemaRecursive } from '../json-schema-validator.js';
|
|
2
|
+
import { parseOrRepairLocalLlmJson } from './local-llm-json-repair.js';
|
|
3
|
+
export function enforceLocalLlmJsonSchema(text, schema) {
|
|
4
|
+
const parsed = parseOrRepairLocalLlmJson(text);
|
|
5
|
+
const validation = parsed.ok ? validateJsonSchemaRecursive(parsed.value, schema) : { ok: false, issues: [parsed.error || 'json_parse_failed'] };
|
|
6
|
+
return {
|
|
7
|
+
ok: parsed.ok && validation.ok,
|
|
8
|
+
value: parsed.ok ? parsed.value : null,
|
|
9
|
+
repaired: parsed.repaired,
|
|
10
|
+
repair_attempts: parsed.attempts,
|
|
11
|
+
schema_valid: validation.ok,
|
|
12
|
+
issues: validation.issues
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=local-llm-schema-enforcer.js.map
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { ensureDir, nowIso, sha256, writeJsonAtomic } from '../fsx.js';
|
|
4
|
+
import { detectLocalLlmCapability } from './local-llm-capability.js';
|
|
5
|
+
import { callLocalLlmGenerate, localLlmTokensPerSecond } from './local-llm-client.js';
|
|
6
|
+
import { enforceLocalLlmJsonSchema } from './local-llm-schema-enforcer.js';
|
|
7
|
+
export const LOCAL_LLM_SMOKE_SCHEMA = 'sks.local-llm-smoke.v1';
|
|
8
|
+
export const localLlmSmokeSchema = {
|
|
9
|
+
type: 'object',
|
|
10
|
+
required: ['status', 'summary'],
|
|
11
|
+
properties: {
|
|
12
|
+
status: { type: 'string' },
|
|
13
|
+
summary: { type: 'string' }
|
|
14
|
+
},
|
|
15
|
+
additionalProperties: false
|
|
16
|
+
};
|
|
17
|
+
export async function runLocalLlmGenerationSmoke(config, opts = {}) {
|
|
18
|
+
const started = Date.now();
|
|
19
|
+
const prompt = opts.prompt || 'Return strict JSON: {"status":"ok","summary":"local smoke passed"}';
|
|
20
|
+
const schema = opts.schema || localLlmSmokeSchema;
|
|
21
|
+
const capability = await detectLocalLlmCapability(config);
|
|
22
|
+
const reportPath = expandHome(opts.reportPath || path.join(os.homedir(), '.sneakoscope', 'reports', 'local-llm-smoke.json'));
|
|
23
|
+
if (!capability.ok) {
|
|
24
|
+
const smoke = {
|
|
25
|
+
ok: false,
|
|
26
|
+
ran_at: nowIso(),
|
|
27
|
+
prompt_hash: sha256(prompt),
|
|
28
|
+
latency_ms: Math.max(0, Date.now() - started),
|
|
29
|
+
tokens_per_second: 0,
|
|
30
|
+
schema_valid: false,
|
|
31
|
+
result_path: reportPath,
|
|
32
|
+
status: 'blocked',
|
|
33
|
+
blockers: capability.blockers
|
|
34
|
+
};
|
|
35
|
+
await writeSmokeReport(reportPath, config, smoke, capability);
|
|
36
|
+
return smoke;
|
|
37
|
+
}
|
|
38
|
+
const request = {
|
|
39
|
+
model: config.model,
|
|
40
|
+
prompt,
|
|
41
|
+
stream: false,
|
|
42
|
+
format: 'json',
|
|
43
|
+
think: config.think,
|
|
44
|
+
keep_alive: config.keep_alive,
|
|
45
|
+
options: { temperature: 0 }
|
|
46
|
+
};
|
|
47
|
+
const response = await callLocalLlmGenerate({ ...config, timeout_ms: opts.timeoutMs || 20_000 }, request);
|
|
48
|
+
const latencyMs = Math.max(0, Date.now() - started);
|
|
49
|
+
const enforced = response.ok ? enforceLocalLlmJsonSchema(response.text, schema) : { ok: false, schema_valid: false, issues: [response.error] };
|
|
50
|
+
const smoke = {
|
|
51
|
+
ok: response.ok && enforced.ok,
|
|
52
|
+
ran_at: nowIso(),
|
|
53
|
+
prompt_hash: sha256(prompt),
|
|
54
|
+
latency_ms: latencyMs,
|
|
55
|
+
tokens_per_second: response.ok ? localLlmTokensPerSecond(response.data, response.text, latencyMs) : 0,
|
|
56
|
+
schema_valid: enforced.schema_valid === true,
|
|
57
|
+
result_path: reportPath,
|
|
58
|
+
status: response.ok && enforced.ok ? 'verified' : 'blocked',
|
|
59
|
+
blockers: [
|
|
60
|
+
...(response.ok ? [] : ['local_llm_generate_failed', response.error]),
|
|
61
|
+
...(enforced.ok ? [] : ['local_llm_smoke_schema_invalid', ...(enforced.issues || []).map(String)])
|
|
62
|
+
]
|
|
63
|
+
};
|
|
64
|
+
await writeSmokeReport(reportPath, config, smoke, capability, response.ok ? response.data : null);
|
|
65
|
+
return smoke;
|
|
66
|
+
}
|
|
67
|
+
async function writeSmokeReport(reportPath, config, smoke, capability, rawResponse = null) {
|
|
68
|
+
await ensureDir(path.dirname(reportPath));
|
|
69
|
+
await writeJsonAtomic(reportPath, {
|
|
70
|
+
schema: LOCAL_LLM_SMOKE_SCHEMA,
|
|
71
|
+
generated_at: nowIso(),
|
|
72
|
+
provider: config.provider,
|
|
73
|
+
model: config.model,
|
|
74
|
+
endpoint: config.base_url,
|
|
75
|
+
smoke,
|
|
76
|
+
capability,
|
|
77
|
+
raw_response: rawResponse
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
function expandHome(value) {
|
|
81
|
+
return value.startsWith('~/') ? path.join(os.homedir(), value.slice(2)) : value;
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=local-llm-smoke.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { nowIso } from '../fsx.js';
|
|
2
|
+
export function buildLocalLlmWarmupState(config, input) {
|
|
3
|
+
const ttlMs = Math.max(1, Number(input.ttlMs || 10 * 60 * 1000));
|
|
4
|
+
const warmedAt = nowIso();
|
|
5
|
+
return {
|
|
6
|
+
schema: 'sks.local-llm-warmup-state.v1',
|
|
7
|
+
ok: input.ok,
|
|
8
|
+
provider: config.provider,
|
|
9
|
+
model: config.model,
|
|
10
|
+
endpoint: config.base_url,
|
|
11
|
+
warmed_at: warmedAt,
|
|
12
|
+
expires_at: new Date(Date.parse(warmedAt) + ttlMs).toISOString(),
|
|
13
|
+
explicit_only: true,
|
|
14
|
+
postinstall_allowed: false,
|
|
15
|
+
release_check_real_warmup_allowed: false,
|
|
16
|
+
release_real_check_requires_env: 'SKS_REQUIRE_LOCAL_LLM=1',
|
|
17
|
+
reason: input.reason || null
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=local-llm-warmup.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export function evaluateLocalWorkerEligibility(input, config) {
|
|
2
|
+
const prompt = String(input.prompt || '');
|
|
3
|
+
const writePaths = Array.isArray(input.requestedScopeContract?.write_paths) ? input.requestedScopeContract.write_paths.map(String) : [];
|
|
4
|
+
const forbidden = /\b(strategy|planning|architecture|design|final review|verification authority|safety authority|integration|database|migration|permission approval)\b/i.test(prompt);
|
|
5
|
+
const allowed = /\b(worker|patch|read-only|grep|qa|test|docstring|summary|refactor)\b/i.test(`${input.route} ${prompt}`);
|
|
6
|
+
const blockers = [
|
|
7
|
+
...(config.enabled ? [] : ['local_llm_disabled']),
|
|
8
|
+
...(config.status === 'verified' ? [] : [`local_llm_${config.status}`]),
|
|
9
|
+
...(input.tier === 'worker' ? [] : ['local_llm_worker_tier_only']),
|
|
10
|
+
...(input.sandboxPolicy === 'full-access' ? ['local_llm_full_access_blocked'] : []),
|
|
11
|
+
...(input.localLlmPolicy?.requiresGptFinal === false ? ['local_llm_requires_gpt_final_policy_missing'] : []),
|
|
12
|
+
...(forbidden ? ['local_llm_forbidden_task_class'] : []),
|
|
13
|
+
...(!allowed && !writePaths.length ? ['local_llm_task_not_eligible'] : [])
|
|
14
|
+
];
|
|
15
|
+
return {
|
|
16
|
+
schema: 'sks.local-worker-eligibility.v1',
|
|
17
|
+
ok: blockers.length === 0,
|
|
18
|
+
task_classes: {
|
|
19
|
+
allowed_detected: allowed,
|
|
20
|
+
forbidden_detected: forbidden,
|
|
21
|
+
write_path_count: writePaths.length
|
|
22
|
+
},
|
|
23
|
+
requires_gpt_final: true,
|
|
24
|
+
blockers
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=local-worker-eligibility.js.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
export function probeHardwareCapacity(input = {}) {
|
|
3
|
+
const memory = process.memoryUsage();
|
|
4
|
+
return {
|
|
5
|
+
schema: 'sks.naruto-hardware-capacity-probe.v1',
|
|
6
|
+
cpu_core_count: normalizePositiveInt(input.cores, os.cpus()?.length || 1),
|
|
7
|
+
current_load_average: input.loadAverage || os.loadavg(),
|
|
8
|
+
free_memory_bytes: normalizePositiveInt(input.freeMemoryBytes, os.freemem()),
|
|
9
|
+
total_memory_bytes: normalizePositiveInt(input.totalMemoryBytes, os.totalmem()),
|
|
10
|
+
node_heap_used_bytes: normalizePositiveInt(input.nodeHeapUsedBytes, memory.heapUsed),
|
|
11
|
+
node_heap_total_bytes: normalizePositiveInt(input.nodeHeapTotalBytes, memory.heapTotal),
|
|
12
|
+
process_count: normalizePositiveInt(input.processCount, 1),
|
|
13
|
+
file_descriptor_limit: normalizePositiveInt(input.fileDescriptorLimit, Number(process.env.SKS_NARUTO_FD_LIMIT) || 256),
|
|
14
|
+
zellij_pane_count: normalizeNonNegativeInt(input.zellijPaneCount, 0),
|
|
15
|
+
terminal_columns: normalizePositiveInt(input.terminalColumns, process.stdout.columns || 120),
|
|
16
|
+
terminal_rows: normalizePositiveInt(input.terminalRows, process.stdout.rows || 40),
|
|
17
|
+
local_llm_max_parallel_requests: normalizePositiveInt(input.localLlmMaxParallelRequests, Number(process.env.SKS_LOCAL_LLM_MAX_PARALLEL_REQUESTS) || 4),
|
|
18
|
+
remote_api_rate_limit_budget: normalizePositiveInt(input.remoteApiRateLimitBudget, Number(process.env.SKS_REMOTE_API_PARALLEL_BUDGET) || 12),
|
|
19
|
+
gpu_available: input.gpuAvailable === true,
|
|
20
|
+
gpu_vram_mb: normalizeNonNegativeInt(input.gpuVramMb, 0),
|
|
21
|
+
disk_io_pressure: Math.max(0, Math.min(1, Number(input.diskIoPressure ?? 0)))
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function normalizePositiveInt(value, fallback) {
|
|
25
|
+
const parsed = Number(value);
|
|
26
|
+
if (!Number.isFinite(parsed) || parsed < 1)
|
|
27
|
+
return Math.max(1, Math.floor(fallback));
|
|
28
|
+
return Math.floor(parsed);
|
|
29
|
+
}
|
|
30
|
+
function normalizeNonNegativeInt(value, fallback) {
|
|
31
|
+
const parsed = Number(value);
|
|
32
|
+
if (!Number.isFinite(parsed) || parsed < 0)
|
|
33
|
+
return Math.max(0, Math.floor(fallback));
|
|
34
|
+
return Math.floor(parsed);
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=hardware-capacity-probe.js.map
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { createNarutoGeneration, completeNarutoGeneration } from './naruto-generation-scheduler.js';
|
|
2
|
+
export function simulateNarutoActivePool(input) {
|
|
3
|
+
const safeActiveWorkers = Math.max(1, input.governor.safe_active_workers);
|
|
4
|
+
const retryLimit = Math.max(0, Math.floor(Number(input.retryLimit ?? 1)));
|
|
5
|
+
const failIds = new Set((input.failWorkItemIds || []).map(String));
|
|
6
|
+
const pending = [...input.graph.work_items];
|
|
7
|
+
const active = new Map();
|
|
8
|
+
const completed = new Set();
|
|
9
|
+
const failed = new Set();
|
|
10
|
+
const executed = new Map();
|
|
11
|
+
const byId = new Map(input.graph.work_items.map((item) => [item.id, item]));
|
|
12
|
+
const timeline = [];
|
|
13
|
+
let generationIndex = 1;
|
|
14
|
+
let tick = 0;
|
|
15
|
+
let refillEvents = 0;
|
|
16
|
+
let maxObserved = 0;
|
|
17
|
+
let maxObservedWriteLeaseConflicts = 0;
|
|
18
|
+
let conflictItemsEnqueued = 0;
|
|
19
|
+
while (pending.length || active.size) {
|
|
20
|
+
let launched = 0;
|
|
21
|
+
for (;;) {
|
|
22
|
+
if (active.size >= safeActiveWorkers)
|
|
23
|
+
break;
|
|
24
|
+
const next = popRunnable(pending, completed, active, byId);
|
|
25
|
+
if (!next)
|
|
26
|
+
break;
|
|
27
|
+
const generation = createNarutoGeneration(next, generationIndex, tick);
|
|
28
|
+
generationIndex += 1;
|
|
29
|
+
active.set(generation.generation_id, generation);
|
|
30
|
+
executed.set(next.id, (executed.get(next.id) || 0) + 1);
|
|
31
|
+
launched += 1;
|
|
32
|
+
}
|
|
33
|
+
if (launched)
|
|
34
|
+
refillEvents += launched;
|
|
35
|
+
maxObserved = Math.max(maxObserved, active.size);
|
|
36
|
+
maxObservedWriteLeaseConflicts = Math.max(maxObservedWriteLeaseConflicts, countActiveWriteLeaseConflicts(active, byId));
|
|
37
|
+
timeline.push({ tick, active: active.size, pending: pending.length, completed: completed.size, event: launched ? 'refill' : 'wait' });
|
|
38
|
+
const done = [...active.values()].slice(0, Math.max(1, Math.ceil(active.size / 2)));
|
|
39
|
+
if (!done.length && pending.length)
|
|
40
|
+
break;
|
|
41
|
+
for (const generation of done) {
|
|
42
|
+
active.delete(generation.generation_id);
|
|
43
|
+
const shouldFail = failIds.has(generation.work_item_id) && (executed.get(generation.work_item_id) || 0) <= retryLimit;
|
|
44
|
+
completeNarutoGeneration(generation, tick + 1, shouldFail);
|
|
45
|
+
if (shouldFail) {
|
|
46
|
+
failed.add(generation.work_item_id);
|
|
47
|
+
conflictItemsEnqueued += 1;
|
|
48
|
+
const followup = conflictResolutionFollowup(generation.work_item_id, input.graph.work_items.length + conflictItemsEnqueued);
|
|
49
|
+
pending.push(followup);
|
|
50
|
+
byId.set(followup.id, followup);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
completed.add(generation.work_item_id);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
tick += 1;
|
|
57
|
+
if (tick > input.graph.work_items.length * 4 + 20)
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
const duplicateExecutionCount = [...executed.values()].filter((count) => count > 1).length;
|
|
61
|
+
const blockers = [
|
|
62
|
+
...(pending.length ? ['naruto_active_pool_pending_not_drained'] : []),
|
|
63
|
+
...(active.size ? ['naruto_active_pool_active_not_drained'] : []),
|
|
64
|
+
...(maxObserved > safeActiveWorkers ? ['naruto_active_pool_exceeded_safe_workers'] : []),
|
|
65
|
+
...(maxObservedWriteLeaseConflicts > 0 ? ['naruto_active_pool_overlapping_write_leases'] : []),
|
|
66
|
+
...(duplicateExecutionCount > conflictItemsEnqueued ? ['naruto_active_pool_duplicate_execution_without_retry'] : [])
|
|
67
|
+
];
|
|
68
|
+
return {
|
|
69
|
+
schema: 'sks.naruto-active-pool.v1',
|
|
70
|
+
ok: blockers.length === 0,
|
|
71
|
+
safe_active_workers: safeActiveWorkers,
|
|
72
|
+
total_work_items: input.graph.total_work_items,
|
|
73
|
+
completed_count: completed.size,
|
|
74
|
+
failed_count: failed.size,
|
|
75
|
+
refill_events: refillEvents,
|
|
76
|
+
max_observed_active_workers: maxObserved,
|
|
77
|
+
duplicate_execution_count: duplicateExecutionCount,
|
|
78
|
+
conflict_items_enqueued: conflictItemsEnqueued,
|
|
79
|
+
max_observed_write_lease_conflicts: maxObservedWriteLeaseConflicts,
|
|
80
|
+
timeline,
|
|
81
|
+
blockers
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function popRunnable(pending, completed, active, byId) {
|
|
85
|
+
const activeWorkIds = new Set([...active.values()].map((item) => item.work_item_id));
|
|
86
|
+
for (let index = 0; index < pending.length; index += 1) {
|
|
87
|
+
const item = pending[index];
|
|
88
|
+
if (!item)
|
|
89
|
+
continue;
|
|
90
|
+
if (activeWorkIds.has(item.id))
|
|
91
|
+
continue;
|
|
92
|
+
if (!item.dependencies.every((dep) => completed.has(dep)))
|
|
93
|
+
continue;
|
|
94
|
+
const writeConflict = [...active.values()].some((generation) => {
|
|
95
|
+
const activeItem = byId.get(generation.work_item_id);
|
|
96
|
+
return activeItem?.write_paths.some((file) => item.write_paths.includes(file));
|
|
97
|
+
});
|
|
98
|
+
if (writeConflict)
|
|
99
|
+
continue;
|
|
100
|
+
pending.splice(index, 1);
|
|
101
|
+
return item;
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
function countActiveWriteLeaseConflicts(active, byId) {
|
|
106
|
+
const counts = new Map();
|
|
107
|
+
for (const generation of active.values()) {
|
|
108
|
+
const item = byId.get(generation.work_item_id);
|
|
109
|
+
for (const file of item?.write_paths || [])
|
|
110
|
+
counts.set(file, (counts.get(file) || 0) + 1);
|
|
111
|
+
}
|
|
112
|
+
return [...counts.values()].filter((count) => count > 1).reduce((sum, count) => sum + count - 1, 0);
|
|
113
|
+
}
|
|
114
|
+
function conflictResolutionFollowup(failedId, index) {
|
|
115
|
+
const id = `NW-CONFLICT-${String(index).padStart(4, '0')}`;
|
|
116
|
+
return {
|
|
117
|
+
id,
|
|
118
|
+
kind: 'conflict_resolution',
|
|
119
|
+
title: `Resolve failed work item ${failedId}`,
|
|
120
|
+
target_paths: [],
|
|
121
|
+
readonly_paths: [],
|
|
122
|
+
write_paths: [`.sneakoscope/naruto/conflicts/${id}.json`],
|
|
123
|
+
required_role: 'conflict_resolver',
|
|
124
|
+
write_allowed: true,
|
|
125
|
+
verification_required: true,
|
|
126
|
+
dependencies: [],
|
|
127
|
+
can_run_in_parallel_with: [],
|
|
128
|
+
conflicts_with: [],
|
|
129
|
+
estimated_cost: { tokens: 4000, latency_ms: 45000, cpu_weight: 1, memory_mb: 256, gpu_weight: 0 },
|
|
130
|
+
lease_requirements: [{ path: `.sneakoscope/naruto/conflicts/${id}.json`, kind: 'write' }],
|
|
131
|
+
acceptance: { requires_patch_envelope: true, requires_verification: true, requires_gpt_final: true }
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=naruto-active-pool.js.map
|