sneakoscope 4.0.7 → 4.0.9
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 +2 -2
- 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/bin/sks.js +1 -1
- package/dist/cli/command-registry.js +1 -0
- package/dist/core/commands/glm-command.js +8 -1
- package/dist/core/commands/naruto-command.js +25 -0
- package/dist/core/commands/stop-gate-command.js +63 -0
- package/dist/core/fsx.js +1 -1
- package/dist/core/pipeline-internals/runtime-gates.js +28 -4
- package/dist/core/providers/glm/glm-bench.js +4 -4
- package/dist/core/providers/glm/glm-direct-run.js +1 -1
- package/dist/core/providers/glm/glm-latency-trace.js +1 -1
- package/dist/core/providers/glm/glm-request-cache.js +10 -2
- package/dist/core/providers/glm/naruto/glm-naruto-artifacts.js +2 -0
- package/dist/core/providers/glm/naruto/glm-naruto-bench.js +68 -0
- package/dist/core/providers/glm/naruto/glm-naruto-budget.js +45 -0
- package/dist/core/providers/glm/naruto/glm-naruto-command.js +97 -0
- package/dist/core/providers/glm/naruto/glm-naruto-concurrency-governor.js +37 -0
- package/dist/core/providers/glm/naruto/glm-naruto-conflict-graph.js +74 -0
- package/dist/core/providers/glm/naruto/glm-naruto-decomposer.js +99 -0
- package/dist/core/providers/glm/naruto/glm-naruto-file-lease.js +23 -0
- package/dist/core/providers/glm/naruto/glm-naruto-finalizer.js +22 -0
- package/dist/core/providers/glm/naruto/glm-naruto-judge.js +84 -0
- package/dist/core/providers/glm/naruto/glm-naruto-merge-planner.js +57 -0
- package/dist/core/providers/glm/naruto/glm-naruto-orchestrator.js +277 -0
- package/dist/core/providers/glm/naruto/glm-naruto-patch-envelope.js +55 -0
- package/dist/core/providers/glm/naruto/glm-naruto-quorum.js +37 -0
- package/dist/core/providers/glm/naruto/glm-naruto-rate-limiter.js +18 -0
- package/dist/core/providers/glm/naruto/glm-naruto-repair-wave.js +21 -0
- package/dist/core/providers/glm/naruto/glm-naruto-shard-planner.js +32 -0
- package/dist/core/providers/glm/naruto/glm-naruto-trace.js +91 -0
- package/dist/core/providers/glm/naruto/glm-naruto-types.js +37 -0
- package/dist/core/providers/glm/naruto/glm-naruto-work-graph.js +2 -0
- package/dist/core/providers/glm/naruto/glm-naruto-worker-pool.js +79 -0
- package/dist/core/providers/glm/naruto/glm-naruto-worker-runtime.js +198 -0
- package/dist/core/providers/glm/naruto/glm-naruto-worker.js +2 -0
- package/dist/core/providers/glm/naruto/glm-naruto-worktree.js +48 -0
- package/dist/core/providers/openrouter/openrouter-provider-health.js +46 -0
- package/dist/core/providers/openrouter/openrouter-secret-store.js +33 -0
- package/dist/core/providers/openrouter/openrouter-stream.js +101 -8
- package/dist/core/stop-gate/stop-gate-check.js +208 -0
- package/dist/core/stop-gate/stop-gate-diagnostics.js +4 -0
- package/dist/core/stop-gate/stop-gate-resolver.js +122 -0
- package/dist/core/stop-gate/stop-gate-types.js +2 -0
- package/dist/core/stop-gate/stop-gate-writer.js +76 -0
- package/dist/core/version.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import { nowIso } from '../../../fsx.js';
|
|
3
|
+
import { GLM_52_OPENROUTER_MODEL } from '../glm-52-settings.js';
|
|
4
|
+
import { parseUnifiedDiffPatch } from '../glm-patch-parser.js';
|
|
5
|
+
export function createPatchEnvelope(input) {
|
|
6
|
+
const parsed = parseUnifiedDiffPatch(input.patch);
|
|
7
|
+
return {
|
|
8
|
+
schema: 'sks.glm-naruto-patch-envelope.v1',
|
|
9
|
+
mission_id: input.missionId,
|
|
10
|
+
worker_id: input.workerId,
|
|
11
|
+
shard_id: input.shardId,
|
|
12
|
+
base_digest: input.baseDigest,
|
|
13
|
+
target_paths: parsed.touchedPaths,
|
|
14
|
+
patch: input.patch,
|
|
15
|
+
patch_sha256: crypto.createHash('sha256').update(input.patch).digest('hex'),
|
|
16
|
+
model: GLM_52_OPENROUTER_MODEL,
|
|
17
|
+
provider: 'openrouter',
|
|
18
|
+
reasoning_effort: input.reasoningEffort,
|
|
19
|
+
gpt_fallback_allowed: false,
|
|
20
|
+
generated_at: nowIso(),
|
|
21
|
+
status: input.status || 'candidate',
|
|
22
|
+
blockers: input.blockers || [],
|
|
23
|
+
warnings: input.warnings || [],
|
|
24
|
+
strategy: input.strategy
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export function normalizePatchForDigest(patch) {
|
|
28
|
+
return patch.replace(/\s+/g, ' ').trim();
|
|
29
|
+
}
|
|
30
|
+
export function digestPatch(patch) {
|
|
31
|
+
return crypto.createHash('sha256').update(normalizePatchForDigest(patch)).digest('hex');
|
|
32
|
+
}
|
|
33
|
+
export function parsePatchCandidateOutput(text) {
|
|
34
|
+
const patchStart = text.indexOf('<sks_patch_candidate>');
|
|
35
|
+
const patchEnd = text.indexOf('</sks_patch_candidate>');
|
|
36
|
+
if (patchStart >= 0 && patchEnd > patchStart) {
|
|
37
|
+
return { kind: 'patch', content: text.slice(patchStart + '<sks_patch_candidate>'.length, patchEnd).trim() };
|
|
38
|
+
}
|
|
39
|
+
const needStart = text.indexOf('<sks_need_context>');
|
|
40
|
+
const needEnd = text.indexOf('</sks_need_context>');
|
|
41
|
+
if (needStart >= 0 && needEnd > needStart) {
|
|
42
|
+
const body = text.slice(needStart + '<sks_need_context>'.length, needEnd).trim();
|
|
43
|
+
const paths = body.split(/\r?\n/).map((line) => line.match(/^\s*-\s*(.+?)\s*$/)?.[1]).filter((v) => Boolean(v));
|
|
44
|
+
return { kind: 'need_context', content: body, paths };
|
|
45
|
+
}
|
|
46
|
+
const blockedStart = text.indexOf('<sks_blocked>');
|
|
47
|
+
const blockedEnd = text.indexOf('</sks_blocked>');
|
|
48
|
+
if (blockedStart >= 0 && blockedEnd > blockedStart) {
|
|
49
|
+
const body = text.slice(blockedStart + '<sks_blocked>'.length, blockedEnd).trim();
|
|
50
|
+
const reason = body.match(/reason:\s*(.+)/i)?.[1]?.trim() || body;
|
|
51
|
+
return { kind: 'blocked', content: body, reason };
|
|
52
|
+
}
|
|
53
|
+
return { kind: 'malformed', content: text.trim(), reason: 'missing_glm_naruto_output_envelope' };
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=glm-naruto-patch-envelope.js.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { digestPatch } from './glm-naruto-patch-envelope.js';
|
|
2
|
+
export function evaluateQuorum(envelopes) {
|
|
3
|
+
const byShard = new Map();
|
|
4
|
+
for (const env of envelopes) {
|
|
5
|
+
if (env.status !== 'gate_passed' && env.status !== 'candidate')
|
|
6
|
+
continue;
|
|
7
|
+
const list = byShard.get(env.shard_id) || [];
|
|
8
|
+
list.push(env);
|
|
9
|
+
byShard.set(env.shard_id, list);
|
|
10
|
+
}
|
|
11
|
+
const results = [];
|
|
12
|
+
for (const [shardId, envs] of byShard) {
|
|
13
|
+
const digestCounts = new Map();
|
|
14
|
+
for (const env of envs) {
|
|
15
|
+
const digest = digestPatch(env.patch);
|
|
16
|
+
const existing = digestCounts.get(digest) || { count: 0, patches: [] };
|
|
17
|
+
existing.count++;
|
|
18
|
+
existing.patches.push(env.patch);
|
|
19
|
+
digestCounts.set(digest, existing);
|
|
20
|
+
}
|
|
21
|
+
let best = null;
|
|
22
|
+
for (const [digest, info] of digestCounts) {
|
|
23
|
+
if (!best || info.count > best.count) {
|
|
24
|
+
best = { count: info.count, patches: info.patches, digest };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
results.push({
|
|
28
|
+
shardId,
|
|
29
|
+
consensusDigest: best ? best.digest : null,
|
|
30
|
+
voteCount: best ? best.count : 0,
|
|
31
|
+
totalCandidates: envs.length,
|
|
32
|
+
consensusPatches: best ? best.patches : []
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return results;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=glm-naruto-quorum.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function createRateLimitState() {
|
|
2
|
+
return { rateLimited: false, retryAfterMs: 0, consecutive429: 0, last429At: null };
|
|
3
|
+
}
|
|
4
|
+
export function handleRateLimit(state, retryAfterMs) {
|
|
5
|
+
return {
|
|
6
|
+
rateLimited: true,
|
|
7
|
+
retryAfterMs,
|
|
8
|
+
consecutive429: state.consecutive429 + 1,
|
|
9
|
+
last429At: Date.now()
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export function clearRateLimit(state) {
|
|
13
|
+
return { rateLimited: false, retryAfterMs: 0, consecutive429: 0, last429At: state.last429At };
|
|
14
|
+
}
|
|
15
|
+
export function shouldBackoff(state) {
|
|
16
|
+
return state.consecutive429 > 3 || (state.last429At !== null && Date.now() - state.last429At < state.retryAfterMs);
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=glm-naruto-rate-limiter.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { GLM_NARUTO_LIMITS } from './glm-naruto-types.js';
|
|
2
|
+
export function planRepairWave(input) {
|
|
3
|
+
if (input.repairWaveCount >= GLM_NARUTO_LIMITS.max_repair_waves) {
|
|
4
|
+
return { shardsToRepair: [], canRepair: false, reason: 'max_repair_waves_reached' };
|
|
5
|
+
}
|
|
6
|
+
const failedShardIds = new Set(input.failedEnvelopes.map((e) => e.shard_id));
|
|
7
|
+
const shardsToRepair = input.shards.filter((s) => failedShardIds.has(s.id));
|
|
8
|
+
if (shardsToRepair.length === 0) {
|
|
9
|
+
return { shardsToRepair: [], canRepair: false, reason: 'no_failed_shards_to_repair' };
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
shardsToRepair: shardsToRepair.map((s) => ({
|
|
13
|
+
...s,
|
|
14
|
+
strategy: 'defensive_fix',
|
|
15
|
+
patches_per_shard: 1
|
|
16
|
+
})),
|
|
17
|
+
canRepair: true,
|
|
18
|
+
reason: 'repair_wave_planned'
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=glm-naruto-repair-wave.js.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { NARUTO_PATCH_STRATEGIES, GLM_NARUTO_DEFAULTS } from './glm-naruto-types.js';
|
|
2
|
+
export function planShardCandidates(graph) {
|
|
3
|
+
return graph.shards
|
|
4
|
+
.filter((shard) => shard.mutable)
|
|
5
|
+
.map((shard) => {
|
|
6
|
+
const strategies = assignStrategies(shard);
|
|
7
|
+
return {
|
|
8
|
+
shard,
|
|
9
|
+
strategies,
|
|
10
|
+
candidate_count: shard.patches_per_shard
|
|
11
|
+
};
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
function assignStrategies(shard) {
|
|
15
|
+
const base = Math.max(0, NARUTO_PATCH_STRATEGIES.indexOf(shard.strategy));
|
|
16
|
+
const result = [shard.strategy];
|
|
17
|
+
for (let i = 1; i < shard.patches_per_shard && i < NARUTO_PATCH_STRATEGIES.length; i++) {
|
|
18
|
+
const next = NARUTO_PATCH_STRATEGIES[(base + i) % NARUTO_PATCH_STRATEGIES.length] || 'minimal_patch';
|
|
19
|
+
if (!result.includes(next))
|
|
20
|
+
result.push(next);
|
|
21
|
+
}
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
export function computeInitialLaneMix(graph) {
|
|
25
|
+
const mutable = graph.mutable_shards.length;
|
|
26
|
+
const total = Math.max(mutable, GLM_NARUTO_DEFAULTS.safe_active_start);
|
|
27
|
+
const patchWorkers = Math.ceil(total * GLM_NARUTO_DEFAULTS.patch_worker_ratio);
|
|
28
|
+
const scouts = Math.max(0, Math.floor(total * GLM_NARUTO_DEFAULTS.scout_ratio));
|
|
29
|
+
const verifiers = Math.max(1, total - patchWorkers - scouts);
|
|
30
|
+
return { patch_workers: patchWorkers, scouts, verifiers };
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=glm-naruto-shard-planner.js.map
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { ensureDir, nowIso, writeJsonAtomic } from '../../../fsx.js';
|
|
3
|
+
export function createMissionTrace(missionId) {
|
|
4
|
+
return {
|
|
5
|
+
missionId,
|
|
6
|
+
startedMs: Date.now(),
|
|
7
|
+
workerTraces: []
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export function recordWorkerTrace(state, trace) {
|
|
11
|
+
return { ...state, workerTraces: [...state.workerTraces, trace] };
|
|
12
|
+
}
|
|
13
|
+
export async function writeMissionArtifacts(input) {
|
|
14
|
+
const dir = path.join(input.root, '.sneakoscope', 'glm-naruto', input.missionId);
|
|
15
|
+
await ensureDir(dir);
|
|
16
|
+
if (input.workGraph)
|
|
17
|
+
await writeJsonAtomic(path.join(dir, 'work-graph.json'), input.workGraph);
|
|
18
|
+
if (input.conflictGraph)
|
|
19
|
+
await writeJsonAtomic(path.join(dir, 'conflict-graph.json'), input.conflictGraph);
|
|
20
|
+
if (input.mergePlan)
|
|
21
|
+
await writeJsonAtomic(path.join(dir, 'final-merge-plan.json'), input.mergePlan);
|
|
22
|
+
if (input.judgeResult)
|
|
23
|
+
await writeJsonAtomic(path.join(dir, 'judge-result.json'), input.judgeResult);
|
|
24
|
+
if (input.workerTraces.length > 0)
|
|
25
|
+
await writeJsonAtomic(path.join(dir, 'worker-traces.json'), input.workerTraces);
|
|
26
|
+
if (input.providerHealth)
|
|
27
|
+
await writeJsonAtomic(path.join(dir, 'provider-health.json'), input.providerHealth);
|
|
28
|
+
if (input.termination)
|
|
29
|
+
await writeJsonAtomic(path.join(dir, 'termination.json'), input.termination);
|
|
30
|
+
if (input.applyResult)
|
|
31
|
+
await writeJsonAtomic(path.join(dir, 'apply-result.json'), input.applyResult);
|
|
32
|
+
if (input.verificationSummary)
|
|
33
|
+
await writeJsonAtomic(path.join(dir, 'verification-summary.json'), input.verificationSummary);
|
|
34
|
+
if (input.missionResult)
|
|
35
|
+
await writeJsonAtomic(path.join(dir, 'mission-result.json'), input.missionResult);
|
|
36
|
+
// 4.0.9: Write per-worker patch envelope / request-summary / stream-trace / gate-result artifacts.
|
|
37
|
+
if (input.envelopes && input.envelopes.length > 0) {
|
|
38
|
+
const workersDir = path.join(dir, 'workers');
|
|
39
|
+
await ensureDir(workersDir);
|
|
40
|
+
for (const env of input.envelopes) {
|
|
41
|
+
const workerId = String(env.worker_id || env.shard_id || 'unknown');
|
|
42
|
+
const workerDir = path.join(workersDir, workerId);
|
|
43
|
+
await ensureDir(workerDir);
|
|
44
|
+
await writeJsonAtomic(path.join(workerDir, 'patch-envelope.json'), env);
|
|
45
|
+
await writeJsonAtomic(path.join(workerDir, 'request-summary.json'), {
|
|
46
|
+
schema: 'sks.glm-naruto-worker-request-summary.v1',
|
|
47
|
+
worker_id: workerId,
|
|
48
|
+
shard_id: env.shard_id,
|
|
49
|
+
model: env.model || null,
|
|
50
|
+
provider: 'openrouter',
|
|
51
|
+
request_body_size: env.request_body_size ?? null,
|
|
52
|
+
cached: env.cached ?? false,
|
|
53
|
+
created_at: nowIso(),
|
|
54
|
+
});
|
|
55
|
+
await writeJsonAtomic(path.join(workerDir, 'stream-trace.json'), {
|
|
56
|
+
schema: 'sks.glm-naruto-worker-stream-trace.v1',
|
|
57
|
+
worker_id: workerId,
|
|
58
|
+
ttft_ms: env.ttft_ms ?? null,
|
|
59
|
+
chunk_count: env.chunk_count ?? null,
|
|
60
|
+
real_stream: env.real_stream ?? true,
|
|
61
|
+
idle_timeout_ms: env.idle_timeout_ms ?? null,
|
|
62
|
+
created_at: nowIso(),
|
|
63
|
+
});
|
|
64
|
+
await writeJsonAtomic(path.join(workerDir, 'gate-result.json'), {
|
|
65
|
+
schema: 'sks.glm-naruto-worker-gate-result.v1',
|
|
66
|
+
worker_id: workerId,
|
|
67
|
+
shard_id: env.shard_id,
|
|
68
|
+
status: env.status,
|
|
69
|
+
gate_passed: env.status === 'gate_passed',
|
|
70
|
+
verification_passed: env.verification_passed ?? (env.status === 'gate_passed'),
|
|
71
|
+
created_at: nowIso(),
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return dir;
|
|
76
|
+
}
|
|
77
|
+
export function buildMissionSummary(input) {
|
|
78
|
+
return {
|
|
79
|
+
wall_clock_ms: Date.now() - input.startedMs,
|
|
80
|
+
workers_started: input.workerTraces.length,
|
|
81
|
+
workers_completed: input.workerTraces.filter((t) => t.status === 'completed').length,
|
|
82
|
+
patch_candidates: input.patchCandidates,
|
|
83
|
+
gate_passed_candidates: input.gatePassed,
|
|
84
|
+
mergeable_candidates: input.mergeable,
|
|
85
|
+
applied_patches: input.appliedPatches,
|
|
86
|
+
failed_shards: input.failedShards,
|
|
87
|
+
repair_waves: input.repairWaves,
|
|
88
|
+
budget_used_ms: Date.now() - input.startedMs
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=glm-naruto-trace.js.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { GLM_52_OPENROUTER_MODEL } from '../glm-52-settings.js';
|
|
2
|
+
export const GLM_NARUTO_LIMITS = {
|
|
3
|
+
max_waves_speed: 3,
|
|
4
|
+
max_waves_deep: 5,
|
|
5
|
+
max_wall_clock_ms: 300_000,
|
|
6
|
+
max_worker_runtime_ms: 90_000,
|
|
7
|
+
max_total_requests: 128,
|
|
8
|
+
max_requests_per_shard: 4,
|
|
9
|
+
max_no_progress_waves: 1,
|
|
10
|
+
max_repeated_patch_digest: 1,
|
|
11
|
+
max_repair_waves: 1,
|
|
12
|
+
max_merge_attempts: 2
|
|
13
|
+
};
|
|
14
|
+
export const GLM_NARUTO_DEFAULTS = {
|
|
15
|
+
default_clones: 12,
|
|
16
|
+
safe_active_start: 6,
|
|
17
|
+
max_clones: 64,
|
|
18
|
+
patch_worker_ratio: 0.70,
|
|
19
|
+
scout_ratio: 0.10,
|
|
20
|
+
verifier_ratio: 0.20,
|
|
21
|
+
default_patches_per_shard: 2,
|
|
22
|
+
critical_patches_per_shard: 3,
|
|
23
|
+
default_max_tokens: 4096,
|
|
24
|
+
judge_max_tokens: 8192,
|
|
25
|
+
patch_temperature: 0.25,
|
|
26
|
+
patch_top_p: 0.85,
|
|
27
|
+
judge_temperature: 0.1,
|
|
28
|
+
judge_top_p: 0.8
|
|
29
|
+
};
|
|
30
|
+
export const NARUTO_PATCH_STRATEGIES = [
|
|
31
|
+
'minimal_patch',
|
|
32
|
+
'test_first_fix',
|
|
33
|
+
'type_safe_fix',
|
|
34
|
+
'refactor_local',
|
|
35
|
+
'defensive_fix'
|
|
36
|
+
];
|
|
37
|
+
//# sourceMappingURL=glm-naruto-types.js.map
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { runPatchWorker } from './glm-naruto-worker-runtime.js';
|
|
2
|
+
import { checkAndApplyGlmPatch } from '../glm-patch-apply.js';
|
|
3
|
+
import { evaluateGlmSpeedGate } from '../glm-speed-gate.js';
|
|
4
|
+
import { decideConcurrency } from './glm-naruto-concurrency-governor.js';
|
|
5
|
+
import { planFileLeases } from './glm-naruto-file-lease.js';
|
|
6
|
+
export async function runPatchWorkerPool(input) {
|
|
7
|
+
const envelopes = [];
|
|
8
|
+
const traces = [];
|
|
9
|
+
const failedShardIds = [];
|
|
10
|
+
const concurrencyDecisions = [];
|
|
11
|
+
const shardPathMap = new Map();
|
|
12
|
+
for (const shard of input.shards) {
|
|
13
|
+
shardPathMap.set(shard.id, shard.target_paths);
|
|
14
|
+
}
|
|
15
|
+
const leases = planFileLeases(shardPathMap);
|
|
16
|
+
const mutableShards = input.shards.filter((s) => s.mutable);
|
|
17
|
+
const decision = decideConcurrency({
|
|
18
|
+
requestedClones: input.maxWorkers,
|
|
19
|
+
activeWorkers: Math.min(input.maxWorkers, mutableShards.length),
|
|
20
|
+
rateLimited429: 0,
|
|
21
|
+
ttftP90Ms: 0,
|
|
22
|
+
failureRate: 0,
|
|
23
|
+
operatorMax: input.maxWorkers
|
|
24
|
+
});
|
|
25
|
+
concurrencyDecisions.push(decision);
|
|
26
|
+
const workerTasks = [];
|
|
27
|
+
let workerIdx = 0;
|
|
28
|
+
for (const shard of mutableShards) {
|
|
29
|
+
const strategies = input.strategies.get(shard.id) || [shard.strategy];
|
|
30
|
+
for (const strategy of strategies) {
|
|
31
|
+
const workerId = `worker-${shard.id}-${strategy}-${workerIdx++}`;
|
|
32
|
+
const shardWithStrategy = { ...shard, strategy };
|
|
33
|
+
workerTasks.push(runPatchWorker({
|
|
34
|
+
apiKey: input.apiKey,
|
|
35
|
+
missionId: input.missionId,
|
|
36
|
+
workerId,
|
|
37
|
+
shard: shardWithStrategy,
|
|
38
|
+
contextSummary: input.contextSummary,
|
|
39
|
+
timeoutMs: input.workerTimeoutMs
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const results = await Promise.allSettled(workerTasks);
|
|
44
|
+
for (const result of results) {
|
|
45
|
+
if (result.status === 'fulfilled' && result.value.ok && result.value.envelope) {
|
|
46
|
+
const gate = evaluateGlmSpeedGate(result.value.envelope.patch);
|
|
47
|
+
let envelope = result.value.envelope;
|
|
48
|
+
if (gate.ok) {
|
|
49
|
+
const applyCheck = await checkAndApplyGlmPatch({
|
|
50
|
+
cwd: input.cwd,
|
|
51
|
+
patch: envelope.patch,
|
|
52
|
+
apply: false
|
|
53
|
+
});
|
|
54
|
+
envelope = applyCheck.ok
|
|
55
|
+
? { ...envelope, status: 'gate_passed' }
|
|
56
|
+
: { ...envelope, status: 'gate_failed', blockers: [applyCheck.error.code] };
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
envelope = {
|
|
60
|
+
...envelope,
|
|
61
|
+
status: 'gate_failed',
|
|
62
|
+
blockers: gate.checks.filter((c) => !c.ok).map((c) => c.reason || c.id)
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
envelopes.push(envelope);
|
|
66
|
+
traces.push(result.value.trace);
|
|
67
|
+
}
|
|
68
|
+
else if (result.status === 'fulfilled') {
|
|
69
|
+
traces.push(result.value.trace);
|
|
70
|
+
failedShardIds.push(result.value.trace.shard_id);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
// rejected promise
|
|
74
|
+
failedShardIds.push('unknown');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return { envelopes, traces, failedShardIds, concurrencyDecisions };
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=glm-naruto-worker-pool.js.map
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import { nowIso } from '../../../fsx.js';
|
|
3
|
+
import { GLM_52_OPENROUTER_MODEL } from '../glm-52-settings.js';
|
|
4
|
+
import { buildGlm52Request } from '../glm-52-request.js';
|
|
5
|
+
import { sendOpenRouterChatCompletionStream } from '../../openrouter/openrouter-stream.js';
|
|
6
|
+
import { assertGlm52ActualModel } from '../glm-52-response-guard.js';
|
|
7
|
+
import { encodeGlmRequestWithCache } from '../glm-request-cache.js';
|
|
8
|
+
import { parsePatchCandidateOutput, createPatchEnvelope, digestPatch } from './glm-naruto-patch-envelope.js';
|
|
9
|
+
const STABLE_SYSTEM_PREFIX = `You are a SKS GLM Naruto patch worker. Model lock: ${GLM_52_OPENROUTER_MODEL}. No GPT/OpenAI fallback allowed. Output only <sks_patch_candidate>, <sks_need_context>, or <sks_blocked> envelopes. Use unified diff format for patches. Never write to main workspace directly. Follow proof-first mutation rules.`;
|
|
10
|
+
export async function runPatchWorker(input) {
|
|
11
|
+
const started = Date.now();
|
|
12
|
+
const sessionId = `sks-glm-naruto-${input.missionId}-${input.workerId}`;
|
|
13
|
+
const reasoningEffort = input.shard.reasoning;
|
|
14
|
+
const shardSuffix = JSON.stringify({
|
|
15
|
+
shard_id: input.shard.id,
|
|
16
|
+
task: input.shard.task,
|
|
17
|
+
target_paths: input.shard.target_paths,
|
|
18
|
+
forbidden_paths: input.shard.forbidden_paths,
|
|
19
|
+
base_digest: input.shard.base_digest,
|
|
20
|
+
strategy: input.shard.strategy,
|
|
21
|
+
context: input.contextSummary,
|
|
22
|
+
output_requirement: 'Produce a unified diff patch inside <sks_patch_candidate> tags with summary, target_paths, base_digest, strategy, and patch fields.'
|
|
23
|
+
});
|
|
24
|
+
const messages = [
|
|
25
|
+
{ role: 'system', content: STABLE_SYSTEM_PREFIX },
|
|
26
|
+
{ role: 'user', content: shardSuffix }
|
|
27
|
+
];
|
|
28
|
+
const request = buildGlm52Request({
|
|
29
|
+
profile: 'speed',
|
|
30
|
+
messages,
|
|
31
|
+
maxTokens: input.shard.max_tokens,
|
|
32
|
+
toolChoice: 'none',
|
|
33
|
+
parallelToolCalls: false,
|
|
34
|
+
providerSort: 'throughput'
|
|
35
|
+
});
|
|
36
|
+
const requestWithSession = { ...request, session_id: sessionId };
|
|
37
|
+
const encoded = encodeGlmRequestWithCache(requestWithSession);
|
|
38
|
+
const traceBase = {
|
|
39
|
+
worker_id: input.workerId,
|
|
40
|
+
shard_id: input.shard.id,
|
|
41
|
+
strategy: input.shard.strategy,
|
|
42
|
+
model: GLM_52_OPENROUTER_MODEL,
|
|
43
|
+
provider: 'openrouter',
|
|
44
|
+
session_id: sessionId,
|
|
45
|
+
ttft_ms: null,
|
|
46
|
+
total_ms: 0,
|
|
47
|
+
request_cache_hit: encoded.cacheHit,
|
|
48
|
+
output_digest: crypto.createHash('sha256').update(shardSuffix).digest('hex'),
|
|
49
|
+
patch_digest: null,
|
|
50
|
+
status: 'running'
|
|
51
|
+
};
|
|
52
|
+
try {
|
|
53
|
+
const response = await sendOpenRouterChatCompletionStream({
|
|
54
|
+
apiKey: input.apiKey,
|
|
55
|
+
request: requestWithSession,
|
|
56
|
+
timeoutMs: input.timeoutMs,
|
|
57
|
+
idleTimeoutMs: 60_000
|
|
58
|
+
});
|
|
59
|
+
if (!response.ok) {
|
|
60
|
+
return {
|
|
61
|
+
envelope: null,
|
|
62
|
+
trace: { ...traceBase, total_ms: Date.now() - started, status: 'failed' },
|
|
63
|
+
ok: false,
|
|
64
|
+
error: response.error.code
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const modelGuard = assertGlm52ActualModel(response.value.model);
|
|
68
|
+
if (!modelGuard.ok) {
|
|
69
|
+
return {
|
|
70
|
+
envelope: null,
|
|
71
|
+
trace: { ...traceBase, total_ms: Date.now() - started, status: 'blocked' },
|
|
72
|
+
ok: false,
|
|
73
|
+
error: `model_guard:${modelGuard.code}`
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const parsed = parsePatchCandidateOutput(response.value.content);
|
|
77
|
+
if (parsed.kind !== 'patch') {
|
|
78
|
+
return {
|
|
79
|
+
envelope: null,
|
|
80
|
+
trace: {
|
|
81
|
+
...traceBase,
|
|
82
|
+
ttft_ms: response.value.ttft_ms,
|
|
83
|
+
total_ms: Date.now() - started,
|
|
84
|
+
status: parsed.kind === 'blocked' ? 'blocked' : 'no_patch'
|
|
85
|
+
},
|
|
86
|
+
ok: false,
|
|
87
|
+
error: parsed.kind
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const envelope = createPatchEnvelope({
|
|
91
|
+
missionId: input.missionId,
|
|
92
|
+
workerId: input.workerId,
|
|
93
|
+
shardId: input.shard.id,
|
|
94
|
+
baseDigest: input.shard.base_digest,
|
|
95
|
+
patch: parsed.content,
|
|
96
|
+
strategy: input.shard.strategy,
|
|
97
|
+
reasoningEffort
|
|
98
|
+
});
|
|
99
|
+
const trace = {
|
|
100
|
+
...traceBase,
|
|
101
|
+
ttft_ms: response.value.ttft_ms,
|
|
102
|
+
total_ms: Date.now() - started,
|
|
103
|
+
patch_digest: digestPatch(envelope.patch),
|
|
104
|
+
status: 'completed'
|
|
105
|
+
};
|
|
106
|
+
return { envelope, trace, ok: true };
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
return {
|
|
110
|
+
envelope: null,
|
|
111
|
+
trace: { ...traceBase, total_ms: Date.now() - started, status: 'failed' },
|
|
112
|
+
ok: false,
|
|
113
|
+
error: err instanceof Error ? err.message : String(err)
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
export async function runVerifierWorker(input) {
|
|
118
|
+
const started = Date.now();
|
|
119
|
+
const sessionId = `sks-glm-naruto-verify-${input.missionId}-${input.workerId}`;
|
|
120
|
+
const messages = [
|
|
121
|
+
{ role: 'system', content: `You are a SKS GLM Naruto verifier. Model: ${GLM_52_OPENROUTER_MODEL}. No GPT fallback. Check if the patch is correct and safe. Output JSON: {"ok":true/false,"issues":["..."]}` },
|
|
122
|
+
{ role: 'user', content: JSON.stringify({ patch_sha256: input.envelope.patch_sha256, target_paths: input.envelope.target_paths, patch: input.envelope.patch.slice(0, 4000) }) }
|
|
123
|
+
];
|
|
124
|
+
const request = buildGlm52Request({
|
|
125
|
+
profile: 'speed',
|
|
126
|
+
messages,
|
|
127
|
+
maxTokens: 2048,
|
|
128
|
+
toolChoice: 'none',
|
|
129
|
+
parallelToolCalls: false
|
|
130
|
+
});
|
|
131
|
+
try {
|
|
132
|
+
const response = await sendOpenRouterChatCompletionStream({
|
|
133
|
+
apiKey: input.apiKey,
|
|
134
|
+
request: { ...request, session_id: sessionId },
|
|
135
|
+
timeoutMs: input.timeoutMs,
|
|
136
|
+
idleTimeoutMs: 60_000
|
|
137
|
+
});
|
|
138
|
+
if (!response.ok) {
|
|
139
|
+
return {
|
|
140
|
+
ok: false,
|
|
141
|
+
trace: {
|
|
142
|
+
worker_id: input.workerId,
|
|
143
|
+
shard_id: input.envelope.shard_id,
|
|
144
|
+
strategy: 'minimal_patch',
|
|
145
|
+
model: GLM_52_OPENROUTER_MODEL,
|
|
146
|
+
provider: 'openrouter',
|
|
147
|
+
session_id: sessionId,
|
|
148
|
+
ttft_ms: null,
|
|
149
|
+
total_ms: Date.now() - started,
|
|
150
|
+
request_cache_hit: false,
|
|
151
|
+
output_digest: '',
|
|
152
|
+
patch_digest: input.envelope.patch_sha256,
|
|
153
|
+
status: 'failed'
|
|
154
|
+
},
|
|
155
|
+
issues: [response.error.code]
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
ok: true,
|
|
160
|
+
trace: {
|
|
161
|
+
worker_id: input.workerId,
|
|
162
|
+
shard_id: input.envelope.shard_id,
|
|
163
|
+
strategy: 'minimal_patch',
|
|
164
|
+
model: GLM_52_OPENROUTER_MODEL,
|
|
165
|
+
provider: 'openrouter',
|
|
166
|
+
session_id: sessionId,
|
|
167
|
+
ttft_ms: response.value.ttft_ms,
|
|
168
|
+
total_ms: Date.now() - started,
|
|
169
|
+
request_cache_hit: false,
|
|
170
|
+
output_digest: crypto.createHash('sha256').update(response.value.content).digest('hex'),
|
|
171
|
+
patch_digest: input.envelope.patch_sha256,
|
|
172
|
+
status: 'completed'
|
|
173
|
+
},
|
|
174
|
+
issues: []
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
return {
|
|
179
|
+
ok: false,
|
|
180
|
+
trace: {
|
|
181
|
+
worker_id: input.workerId,
|
|
182
|
+
shard_id: input.envelope.shard_id,
|
|
183
|
+
strategy: 'minimal_patch',
|
|
184
|
+
model: GLM_52_OPENROUTER_MODEL,
|
|
185
|
+
provider: 'openrouter',
|
|
186
|
+
session_id: sessionId,
|
|
187
|
+
ttft_ms: null,
|
|
188
|
+
total_ms: Date.now() - started,
|
|
189
|
+
request_cache_hit: false,
|
|
190
|
+
output_digest: '',
|
|
191
|
+
patch_digest: input.envelope.patch_sha256,
|
|
192
|
+
status: 'failed'
|
|
193
|
+
},
|
|
194
|
+
issues: [err instanceof Error ? err.message : String(err)]
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
//# sourceMappingURL=glm-naruto-worker-runtime.js.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
export async function createWorktree(repoRoot, missionId, workerId, baseCommit) {
|
|
4
|
+
const branch = `sks-glm-naruto/${missionId}/${workerId}`;
|
|
5
|
+
const worktreePath = path.join(repoRoot, '.sneakoscope', 'glm-naruto', 'worktrees', missionId, workerId);
|
|
6
|
+
await runGit(repoRoot, ['worktree', 'add', '-b', branch, worktreePath, baseCommit || 'HEAD']);
|
|
7
|
+
return { path: worktreePath, workerId, branch };
|
|
8
|
+
}
|
|
9
|
+
export async function removeWorktree(repoRoot, worktree) {
|
|
10
|
+
try {
|
|
11
|
+
await runGit(repoRoot, ['worktree', 'remove', '--force', worktree.path]);
|
|
12
|
+
await runGit(repoRoot, ['branch', '-D', worktree.branch]);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
// best-effort cleanup
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export async function applyPatchToWorktree(worktreePath, patch) {
|
|
19
|
+
try {
|
|
20
|
+
const result = await runGit(worktreePath, ['apply', '--whitespace=nowarn', '-'], patch);
|
|
21
|
+
return { ok: result.code === 0, stderr: result.stderr };
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
return { ok: false, stderr: String(err) };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export async function getWorktreeDiff(worktreePath) {
|
|
28
|
+
const result = await runGit(worktreePath, ['diff', 'HEAD']);
|
|
29
|
+
return result.stdout;
|
|
30
|
+
}
|
|
31
|
+
export async function resetWorktree(worktreePath) {
|
|
32
|
+
await runGit(worktreePath, ['checkout', '--', '.']);
|
|
33
|
+
}
|
|
34
|
+
function runGit(cwd, args, stdin) {
|
|
35
|
+
return new Promise((resolve) => {
|
|
36
|
+
const child = spawn('git', [...args], { cwd, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
37
|
+
let stdout = '';
|
|
38
|
+
let stderr = '';
|
|
39
|
+
child.stdout.on('data', (chunk) => { stdout += String(chunk); });
|
|
40
|
+
child.stderr.on('data', (chunk) => { stderr += String(chunk); });
|
|
41
|
+
child.on('close', (code) => resolve({ code, stdout, stderr }));
|
|
42
|
+
if (stdin !== undefined)
|
|
43
|
+
child.stdin.end(stdin);
|
|
44
|
+
else
|
|
45
|
+
child.stdin.end();
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=glm-naruto-worktree.js.map
|