sneakoscope 2.0.15 → 2.0.16
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 +1 -1
- 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/cli/command-registry.js +1 -1
- package/dist/core/agents/agent-orchestrator.js +66 -3
- package/dist/core/agents/agent-scheduler.js +204 -86
- package/dist/core/agents/agent-schema.js +1 -1
- package/dist/core/agents/native-cli-session-swarm.js +87 -21
- package/dist/core/agents/parallel-runtime-proof.js +217 -0
- package/dist/core/codex-control/codex-task-runner.js +32 -4
- package/dist/core/codex-control/model-call-concurrency.js +106 -0
- package/dist/core/commands/naruto-command.js +48 -5
- package/dist/core/commands/team-command.js +0 -176
- package/dist/core/db-safety.js +34 -6
- package/dist/core/fsx.js +1 -1
- package/dist/core/git/git-worktree-capability.js +18 -0
- package/dist/core/git/git-worktree-manager.js +80 -0
- package/dist/core/git/git-worktree-pool.js +4 -0
- package/dist/core/mad-db/mad-db-capability.js +33 -1
- package/dist/core/mad-db/mad-db-ledger.js +14 -0
- package/dist/core/mad-db/mad-db-policy-resolver.js +2 -0
- package/dist/core/naruto/naruto-concurrency-governor.js +14 -1
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/zellij-slot-telemetry.js +56 -1
- package/dist/scripts/release-speed-summary.js +2 -0
- package/package.json +25 -1
- package/schemas/agents/parallel-runtime-proof.schema.json +48 -0
|
@@ -165,10 +165,12 @@ async function narutoRun(parsed) {
|
|
|
165
165
|
requestedClones: roster.agent_count,
|
|
166
166
|
totalWorkItems: workGraph.total_work_items,
|
|
167
167
|
pendingWorkQueueSize: workGraph.total_work_items,
|
|
168
|
-
backend: schedulerBackend
|
|
168
|
+
backend: schedulerBackend,
|
|
169
|
+
parallelismMode: parsed.parallelism
|
|
169
170
|
});
|
|
170
171
|
const backendMinimum = schedulerBackend === 'fake' ? roster.agent_count : Math.min(roster.agent_count, 2);
|
|
171
|
-
const
|
|
172
|
+
const activeCap = parsed.parallelism === 'safe' ? safe.cap : MAX_NARUTO_AGENT_COUNT;
|
|
173
|
+
const activeSlots = Math.max(1, Math.min(roster.agent_count, parsed.concurrency || Math.max(governor.safe_active_workers, backendMinimum), activeCap));
|
|
172
174
|
const zellijVisiblePanes = Math.max(1, Math.min(activeSlots, governor.safe_zellij_visible_panes));
|
|
173
175
|
const activePool = await runNarutoActivePool({ graph: workGraph, governor: { ...governor, safe_active_workers: activeSlots } });
|
|
174
176
|
const runPreRunSmoke = parsed.smoke === true || process.env.SKS_NARUTO_PRE_RUN_SMOKE === '1';
|
|
@@ -281,6 +283,20 @@ async function narutoRun(parsed) {
|
|
|
281
283
|
prompt: parsed.prompt
|
|
282
284
|
});
|
|
283
285
|
let liveZellij = null;
|
|
286
|
+
if (!parsed.json) {
|
|
287
|
+
console.log('$Naruto starting:');
|
|
288
|
+
console.log(' clones requested: ' + roster.agent_count);
|
|
289
|
+
console.log(' work items: ' + workGraph.total_work_items);
|
|
290
|
+
console.log(' target active workers: ' + activeSlots);
|
|
291
|
+
console.log(' visible panes: ' + zellijVisiblePanes);
|
|
292
|
+
console.log(' headless workers: ' + Math.max(0, activeSlots - zellijVisiblePanes));
|
|
293
|
+
console.log(' backend: ' + schedulerBackend);
|
|
294
|
+
console.log(' parallelism mode: ' + parsed.parallelism);
|
|
295
|
+
if (activeSlots < roster.agent_count)
|
|
296
|
+
console.log(' cap reasons: ' + (governor.reasons.join(', ') || 'host safety cap'));
|
|
297
|
+
if (parsed.parallelism !== 'safe' && activeSlots < 10)
|
|
298
|
+
console.log(' warning: active workers below 10 in non-safe mode');
|
|
299
|
+
}
|
|
284
300
|
if (!parsed.json && !parsed.mock && !parsed.noOpenZellij) {
|
|
285
301
|
liveZellij = await launchZellijLayout({
|
|
286
302
|
root,
|
|
@@ -356,8 +372,11 @@ async function narutoRun(parsed) {
|
|
|
356
372
|
narutoRebalancePolicy: rebalancePolicy,
|
|
357
373
|
json: parsed.json
|
|
358
374
|
});
|
|
375
|
+
const parallelRuntime = result.parallel_runtime_proof || null;
|
|
359
376
|
const nativeProofOk = result.proof?.ok === true || result.proof?.status === 'passed';
|
|
360
377
|
const finalAccepted = result.proof?.status === 'passed' || result.proof?.gpt_final_status === 'approved';
|
|
378
|
+
const parallelRuntimeOk = !parsed.mock || roster.agent_count < 16 || (parallelRuntime?.passed === true
|
|
379
|
+
&& Number(parallelRuntime.max_observed_active_workers || 0) >= Math.min(16, activeSlots));
|
|
361
380
|
await writeJsonAtomic(path.join(mission.dir, 'naruto-gate.json'), {
|
|
362
381
|
schema: 'sks.naruto-gate.v1',
|
|
363
382
|
passed: result.ok === true && nativeProofOk && finalAccepted,
|
|
@@ -374,9 +393,10 @@ async function narutoRun(parsed) {
|
|
|
374
393
|
gpt_final_pack_ready: true,
|
|
375
394
|
zellij_dashboard_ready: zellijDashboard.ok === true,
|
|
376
395
|
native_agent_proof: nativeProofOk,
|
|
396
|
+
parallel_runtime_proof: parallelRuntimeOk,
|
|
377
397
|
final_arbiter_accepted: finalAccepted,
|
|
378
398
|
session_cleanup: result.proof?.all_sessions_closed === true || nativeProofOk,
|
|
379
|
-
blockers: result.proof?.blockers || [],
|
|
399
|
+
blockers: [...(result.proof?.blockers || []), ...(parallelRuntimeOk ? [] : ['naruto_parallel_runtime_proof_below_gate'])],
|
|
380
400
|
updated_at: nowIso()
|
|
381
401
|
});
|
|
382
402
|
await setCurrent(root, {
|
|
@@ -442,6 +462,15 @@ async function narutoRun(parsed) {
|
|
|
442
462
|
worker_lifecycle_sample: realActivePoolSmoke.worker_lifecycle.slice(0, 5)
|
|
443
463
|
}
|
|
444
464
|
},
|
|
465
|
+
parallel_runtime: parallelRuntime ? {
|
|
466
|
+
proof_path: path.join(result.ledger_root || '', 'parallel-runtime-proof.json'),
|
|
467
|
+
max_observed_active_workers: parallelRuntime.max_observed_active_workers,
|
|
468
|
+
unique_worker_pids: parallelRuntime.unique_worker_pids,
|
|
469
|
+
speedup_ratio: parallelRuntime.speedup_ratio,
|
|
470
|
+
visible_panes: parallelRuntime.visible_panes,
|
|
471
|
+
headless_workers: parallelRuntime.headless_workers,
|
|
472
|
+
passed: parallelRuntime.passed
|
|
473
|
+
} : null,
|
|
445
474
|
local_worker: localWorkerSummary,
|
|
446
475
|
proof: result.proof?.status || 'missing',
|
|
447
476
|
run: compactNarutoRunResult(result),
|
|
@@ -455,6 +484,13 @@ async function narutoRun(parsed) {
|
|
|
455
484
|
console.log('Backend: ' + result.backend);
|
|
456
485
|
console.log('Roles: ' + roleDistribution.entries.map((entry) => `${entry.role}:${entry.count}`).join(', '));
|
|
457
486
|
console.log('Proof: ' + summary.proof);
|
|
487
|
+
if (summary.parallel_runtime) {
|
|
488
|
+
console.log('$Naruto parallel proof:');
|
|
489
|
+
console.log(' max active workers: ' + summary.parallel_runtime.max_observed_active_workers);
|
|
490
|
+
console.log(' unique PIDs: ' + summary.parallel_runtime.unique_worker_pids);
|
|
491
|
+
console.log(' speedup: ' + summary.parallel_runtime.speedup_ratio + 'x');
|
|
492
|
+
console.log(' result: ' + (summary.parallel_runtime.passed ? 'passed' : 'blocked'));
|
|
493
|
+
}
|
|
458
494
|
if (summary.zellij?.ok && summary.zellij.capability?.status === 'ok')
|
|
459
495
|
console.log('Zellij: prepared ' + zellijVisiblePanes + ' visible active clone lane(s) in ' + summary.zellij.session_name + '; dashboard tracks ' + Math.max(0, activeSlots - zellijVisiblePanes) + ' headless active worker(s)');
|
|
460
496
|
else if (summary.zellij?.ok)
|
|
@@ -716,9 +752,16 @@ function parseNarutoArgs(args = []) {
|
|
|
716
752
|
const noOpenZellij = hasFlag(args, '--no-open-zellij') || hasFlag(args, '--no-zellij');
|
|
717
753
|
const attach = hasFlag(args, '--attach');
|
|
718
754
|
const smoke = hasFlag(args, '--smoke');
|
|
719
|
-
const
|
|
755
|
+
const parallelism = normalizeParallelism(readOption(args, '--parallelism', 'extreme'));
|
|
756
|
+
const valueFlags = new Set(['--clones', '--agents', '--work-items', '--concurrency', '--target-active-slots', '--backend', '--write-mode', '--mission', '--mission-id', '--ollama-model', '--local-model-model', '--ollama-base-url', '--local-model-base-url', '--parallelism']);
|
|
720
757
|
const prompt = positionalArgs(rest, valueFlags).join(' ').trim() || 'Naruto shadow clone swarm run';
|
|
721
|
-
return { action, prompt, clones, workItems, concurrency, backend, backendExplicit, mock, real, readonly, ollamaEnabled: useOllama && !noOllama, noOllama, ollamaModel, ollamaBaseUrl, writeMode, json, missionId, noOpenZellij, attach, smoke };
|
|
758
|
+
return { action, prompt, clones, workItems, concurrency, backend, backendExplicit, mock, real, readonly, ollamaEnabled: useOllama && !noOllama, noOllama, ollamaModel, ollamaBaseUrl, writeMode, json, missionId, noOpenZellij, attach, smoke, parallelism };
|
|
759
|
+
}
|
|
760
|
+
function normalizeParallelism(value) {
|
|
761
|
+
const text = String(value || 'extreme').toLowerCase();
|
|
762
|
+
if (text === 'safe' || text === 'balanced' || text === 'extreme')
|
|
763
|
+
return text;
|
|
764
|
+
return 'extreme';
|
|
722
765
|
}
|
|
723
766
|
async function writeNarutoArtifacts(ledgerRoot, artifacts) {
|
|
724
767
|
await writeJsonAtomic(path.join(ledgerRoot, 'naruto-work-graph.json'), artifacts.workGraph);
|
|
@@ -25,182 +25,6 @@ export async function team(args = []) {
|
|
|
25
25
|
if (teamSubcommands.has(args[0]))
|
|
26
26
|
return teamCommand(args[0], args.slice(1));
|
|
27
27
|
return redirectTeamCreateToNaruto(args);
|
|
28
|
-
const jsonOutput = flag(args, '--json');
|
|
29
|
-
const mock = flag(args, '--mock');
|
|
30
|
-
const openZellij = !mock && !jsonOutput && !flag(args, '--no-open-zellij') && !flag(args, '--no-zellij');
|
|
31
|
-
const useOllama = flag(args, '--ollama') || flag(args, '--local-model');
|
|
32
|
-
const noOllama = flag(args, '--no-ollama') || flag(args, '--no-local-model');
|
|
33
|
-
const ollamaModel = readFlagValue(args, '--ollama-model', readFlagValue(args, '--local-model-model', '')) || null;
|
|
34
|
-
const ollamaBaseUrl = readFlagValue(args, '--ollama-base-url', readFlagValue(args, '--local-model-base-url', '')) || null;
|
|
35
|
-
const cleanCreateArgs = stripTeamCreateControlArgs(args);
|
|
36
|
-
const opts = parseTeamCreateArgs(cleanCreateArgs);
|
|
37
|
-
const { prompt, agentSessions, roleCounts, roster } = opts;
|
|
38
|
-
const targetActiveSlots = readBoundedIntegerFlag(args, '--target-active-slots', roster.bundle_size, 1, 20);
|
|
39
|
-
const visualLaneCount = roster.bundle_size;
|
|
40
|
-
const desiredWorkItemCount = readBoundedIntegerFlag(args, '--work-items', targetActiveSlots, 1, 200);
|
|
41
|
-
const minimumWorkItems = readBoundedIntegerFlag(args, '--minimum-work-items', targetActiveSlots, 1, 200);
|
|
42
|
-
const maxQueueExpansion = readBoundedIntegerFlag(args, '--max-queue-expansion', 10, 0, 200);
|
|
43
|
-
const profile = readFlagValue(args, '--profile', '') || null;
|
|
44
|
-
const writeMode = readFlagValue(args, '--write-mode', flag(args, '--parallel-write') ? 'parallel' : 'off');
|
|
45
|
-
const applyPatches = flag(args, '--apply-patches');
|
|
46
|
-
const dryRunPatches = flag(args, '--dry-run-patches') || flag(args, '--dryrun-patches');
|
|
47
|
-
const maxWriteAgents = readBoundedIntegerFlag(args, '--max-write-agents', Math.min(roster.bundle_size, 5), 1, 20);
|
|
48
|
-
if (!prompt) {
|
|
49
|
-
console.error('Usage: sks team "task" [20:agents] [executor:5 reviewer:6 user:1] [--agents N] [--work-items N] [--target-active-slots N] [--profile NAME] [--write-mode off|proof-safe|parallel|serial] [--apply-patches] [--ollama|--no-ollama] [--no-open-zellij] [--json] [--mock]');
|
|
50
|
-
process.exitCode = 1;
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
const root = await sksRoot();
|
|
54
|
-
if (!(await exists(path.join(root, '.sneakoscope'))))
|
|
55
|
-
await initProject(root, {});
|
|
56
|
-
const { id, dir } = await createMission(root, { mode: 'team', prompt });
|
|
57
|
-
const schema = buildQuestionSchema(prompt);
|
|
58
|
-
await writeQuestions(dir, schema);
|
|
59
|
-
const plan = buildTeamPlan(id, prompt, { agentSessions, roleCounts, roster, targetActiveSlots });
|
|
60
|
-
await writeJsonAtomic(path.join(dir, 'team-plan.json'), plan);
|
|
61
|
-
await writeJsonAtomic(path.join(dir, SSOT_GUARD_ARTIFACT), plan.ssot_guard);
|
|
62
|
-
await writeTextAtomic(path.join(dir, 'team-workflow.md'), teamWorkflowMarkdown(plan));
|
|
63
|
-
const liveFiles = await initTeamLive(id, dir, prompt, { agentSessions, roleCounts, roster });
|
|
64
|
-
await writeJsonAtomic(path.join(dir, 'team-roster.json'), { schema_version: 1, mission_id: id, role_counts: roleCounts, agent_sessions: agentSessions, bundle_size: roster.bundle_size, roster, confirmed: true, source: 'default_or_prompt_team_spec' });
|
|
65
|
-
const fromChatImgRequired = hasFromChatImgSignal(prompt);
|
|
66
|
-
const teamReasoning = teamReasoningPolicy(prompt, roster);
|
|
67
|
-
const promptEffort = teamReasoning.prompt_policy?.effort || 'medium';
|
|
68
|
-
const runtime = await writeTeamRuntimeArtifacts(dir, plan, {});
|
|
69
|
-
const effortDecision = await writeEffortDecision(dir, {
|
|
70
|
-
mission_id: id,
|
|
71
|
-
task_id: 'TEAM-INTAKE',
|
|
72
|
-
route: fromChatImgRequired ? 'from-chat-img' : 'team',
|
|
73
|
-
prompt,
|
|
74
|
-
tool_use: promptEffort === 'medium',
|
|
75
|
-
multi_step_decision: promptEffort !== 'low',
|
|
76
|
-
spans_many_files: promptEffort === 'high' || promptEffort === 'xhigh',
|
|
77
|
-
is_deterministic: promptEffort === 'low',
|
|
78
|
-
has_verified_skill: true,
|
|
79
|
-
high_risk: promptEffort === 'high' || promptEffort === 'xhigh',
|
|
80
|
-
risk_scores: {
|
|
81
|
-
security: /security|auth|permission|database|supabase|sql|보안|권한|데이터베이스/i.test(prompt) ? 0.8 : 0.1,
|
|
82
|
-
destructive_action: /delete|drop|reset|remove|삭제|초기화/i.test(prompt) ? 0.8 : 0.1,
|
|
83
|
-
user_impact: /release|publish|deploy|commit|push|production|배포|커밋|푸쉬|운영/i.test(prompt) ? 0.8 : 0.3
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
const workOrder = createWorkOrderLedger({ missionId: id, route: fromChatImgRequired ? 'from-chat-img' : 'team', sourcesComplete: !fromChatImgRequired, requests: [{ verbatim: prompt, normalized_requirement: prompt, implementation_tasks: ['TASK-001'], status: 'pending' }] });
|
|
87
|
-
await writeWorkOrderLedger(dir, workOrder);
|
|
88
|
-
if (fromChatImgRequired)
|
|
89
|
-
await writeFromChatImgArtifacts(dir, { missionId: id, requests: [{ verbatim: prompt }], ambiguities: ['image source inventory must be completed before implementation'] });
|
|
90
|
-
let liveZellij = null;
|
|
91
|
-
if (!mock && openZellij) {
|
|
92
|
-
liveZellij = await launchTeamZellijView({ root, missionId: id, ledgerRoot: path.join(dir, 'agents'), slotCount: visualLaneCount, dryRun: false, attach: false });
|
|
93
|
-
if (liveZellij?.ok && liveZellij.capability?.status === 'ok')
|
|
94
|
-
console.log(`Zellij: prepared ${visualLaneCount} native agent lane(s) in ${liveZellij.session_name}. Attach with: ${liveZellij.attach_command_with_env || liveZellij.attach_command}`);
|
|
95
|
-
else if (liveZellij?.ok)
|
|
96
|
-
console.log(`Zellij: optional live panes unavailable (${(liveZellij.warnings || []).join('; ') || liveZellij.capability?.status || 'unknown'}).`);
|
|
97
|
-
else
|
|
98
|
-
console.log(`Zellij: blocked (${Array.from(new Set(liveZellij?.blockers || [])).join('; ')})`);
|
|
99
|
-
}
|
|
100
|
-
const nativeAgentRun = await runNativeAgentOrchestrator({
|
|
101
|
-
root,
|
|
102
|
-
missionId: id,
|
|
103
|
-
route: '$Team',
|
|
104
|
-
prompt,
|
|
105
|
-
backend: mock ? 'fake' : 'codex-sdk',
|
|
106
|
-
mock,
|
|
107
|
-
agents: roster.bundle_size,
|
|
108
|
-
targetActiveSlots,
|
|
109
|
-
visualLaneCount,
|
|
110
|
-
desiredWorkItemCount,
|
|
111
|
-
minimumWorkItems,
|
|
112
|
-
maxQueueExpansion,
|
|
113
|
-
concurrency: Math.min(agentSessions, roster.bundle_size),
|
|
114
|
-
readonly: !applyPatches && writeMode === 'off',
|
|
115
|
-
profile,
|
|
116
|
-
writeMode: writeMode,
|
|
117
|
-
applyPatches,
|
|
118
|
-
dryRunPatches,
|
|
119
|
-
maxWriteAgents,
|
|
120
|
-
ollamaEnabled: useOllama && !noOllama,
|
|
121
|
-
noOllama,
|
|
122
|
-
ollamaModel,
|
|
123
|
-
ollamaBaseUrl,
|
|
124
|
-
routeCommand: 'sks team',
|
|
125
|
-
routeBlackboxKind: 'actual_team_command'
|
|
126
|
-
});
|
|
127
|
-
await appendTeamEvent(dir, {
|
|
128
|
-
agent: 'native_agent_orchestrator',
|
|
129
|
-
phase: 'native_agent_intake',
|
|
130
|
-
type: nativeAgentRun.ok ? 'complete' : 'blocked',
|
|
131
|
-
artifact: 'agents/agent-proof-evidence.json',
|
|
132
|
-
message: 'Native agent orchestrator completed with ' + nativeAgentRun.backend + ' backend; proof ' + (nativeAgentRun.proof?.status || 'unknown') + '.'
|
|
133
|
-
});
|
|
134
|
-
let dashboardState = await writeTeamDashboardState(dir, { missionId: id, mission: { id, mode: 'team' }, effort: effortDecision.selected_effort, phase: 'intake', next_action: fromChatImgRequired ? 'complete visual source inventory and work-order mapping' : 'run Team native agent intake agents' });
|
|
135
|
-
await writeJsonAtomic(path.join(dir, 'team-gate.json'), { passed: false, team_roster_confirmed: true, native_agent_proof: nativeAgentRun.proof?.ok === true, agent_central_ledger: true, analysis_artifact: false, triwiki_refreshed: false, triwiki_validated: false, ssot_guard: false, consensus_artifact: false, ...runtime.gate_fields, implementation_team_fresh: false, review_artifact: false, integration_evidence: false, session_cleanup: false, context7_evidence: false, ...(fromChatImgRequired ? { from_chat_img_required: true, from_chat_img_request_coverage: false } : {}) });
|
|
136
|
-
dashboardState = await writeTeamDashboardState(dir, { missionId: id, mission: { id, mode: 'team' }, effort: effortDecision.selected_effort, phase: 'intake', next_action: fromChatImgRequired ? 'complete visual source inventory and work-order mapping' : 'run Team native agent intake agents' });
|
|
137
|
-
const route = routePrompt(`$Team ${prompt}`) || ROUTES.find((candidate) => candidate.id === 'Team');
|
|
138
|
-
const routeReason = routeReasoning(route, prompt);
|
|
139
|
-
const pipelinePlan = await writePipelinePlan(dir, { missionId: id, route, task: prompt, required: false, ambiguity: { required: false, status: 'team_cli_direct' } });
|
|
140
|
-
await setCurrent(root, { mission_id: id, route: 'Team', route_command: '$Team', mode: 'TEAM', phase: mock ? 'TEAM_FIXTURE_DONE' : 'TEAM_NATIVE_AGENT_INTAKE', questions_allowed: false, implementation_allowed: true, context7_required: false, context7_verified: mock, subagents_required: false, subagents_verified: true, native_sessions_required: true, native_sessions_verified: nativeAgentRun.proof?.ok === true, reflection_required: true, visible_progress_required: true, context_tracking: 'triwiki', required_skills: route?.requiredSkills || ['team'], stop_gate: 'team-gate.json', reasoning_effort: routeReason.effort, reasoning_profile: routeReason.profile, reasoning_temporary: true, team_agent_reasoning_policy: teamReasoning, goal_continuation: pipelinePlan.goal_continuation, agent_sessions: agentSessions, target_active_slots: targetActiveSlots, role_counts: roleCounts, team_roster_confirmed: true, team_graph_ready: runtime.ok, team_live_ready: true, from_chat_img_required: fromChatImgRequired, pipeline_plan_ready: validatePipelinePlan(pipelinePlan).ok, pipeline_plan_path: PIPELINE_PLAN_ARTIFACT, native_agent_backend: nativeAgentRun.backend, native_agent_proof: 'agents/agent-proof-evidence.json', prompt });
|
|
141
|
-
const result = {
|
|
142
|
-
mission_id: id,
|
|
143
|
-
mission_dir: dir,
|
|
144
|
-
plan: path.join(dir, 'team-plan.json'),
|
|
145
|
-
workflow: path.join(dir, 'team-workflow.md'),
|
|
146
|
-
team_graph: path.join(dir, TEAM_GRAPH_ARTIFACT),
|
|
147
|
-
runtime_tasks: path.join(dir, TEAM_RUNTIME_TASKS_ARTIFACT),
|
|
148
|
-
decomposition_report: path.join(dir, TEAM_DECOMPOSITION_ARTIFACT),
|
|
149
|
-
worker_inbox_dir: path.join(dir, TEAM_INBOX_DIR),
|
|
150
|
-
live: liveFiles.live,
|
|
151
|
-
transcript: liveFiles.transcript,
|
|
152
|
-
dashboard: liveFiles.dashboard,
|
|
153
|
-
dashboard_state: path.join(dir, ARTIFACT_FILES.team_dashboard_state),
|
|
154
|
-
effort_decision: path.join(dir, ARTIFACT_FILES.effort_decision),
|
|
155
|
-
work_order_ledger: path.join(dir, ARTIFACT_FILES.work_order_ledger),
|
|
156
|
-
pipeline_plan: path.join(dir, PIPELINE_PLAN_ARTIFACT),
|
|
157
|
-
dashboard_state_valid: dashboardState.ok,
|
|
158
|
-
context_pack: path.join(root, '.sneakoscope', 'wiki', 'context-pack.json'),
|
|
159
|
-
agent_sessions: agentSessions,
|
|
160
|
-
bundle_size: roster.bundle_size,
|
|
161
|
-
target_active_slots: targetActiveSlots,
|
|
162
|
-
visual_lane_count: visualLaneCount,
|
|
163
|
-
desired_work_items: desiredWorkItemCount,
|
|
164
|
-
minimum_work_items: minimumWorkItems,
|
|
165
|
-
max_queue_expansion: maxQueueExpansion,
|
|
166
|
-
role_counts: roleCounts,
|
|
167
|
-
questions: path.join(dir, 'questions.md'),
|
|
168
|
-
native_agent_run: nativeAgentRun,
|
|
169
|
-
codex_agents: ['native_agent_orchestrator', 'agent_central_ledger', 'agent_proof_evidence', 'agent_review_lane', 'agent_integration_lane']
|
|
170
|
-
};
|
|
171
|
-
if (mock) {
|
|
172
|
-
await writeTextAtomic(path.join(dir, 'team-analysis.md'), `# Team Native Agent Analysis\n\nMock Team fixture completed native agent intake for ${id}.\n`);
|
|
173
|
-
await writeTextAtomic(path.join(dir, 'team-consensus.md'), `# Team Consensus\n\nMock Team fixture consensus reached for ${id}.\n`);
|
|
174
|
-
await writeTextAtomic(path.join(dir, 'team-review.md'), `# Team Review\n\nMock Team fixture review completed with ${MIN_TEAM_REVIEWER_LANES} validation lanes for ${id}.\n`);
|
|
175
|
-
await writeTextAtomic(path.join(dir, 'context7-evidence.jsonl'), `${JSON.stringify({ schema: 'sks.context7-evidence.v1', mission_id: id, route: '$Team', status: 'mock_not_required', generated_at: nowIso() })}\n`);
|
|
176
|
-
const cleanup = { schema_version: 1, mission_id: id, status: 'clean', passed: true, all_sessions_closed: true, outstanding_sessions: 0, live_transcript_finalized: true, mock: true, generated_at: nowIso() };
|
|
177
|
-
await writeJsonAtomic(path.join(dir, TEAM_SESSION_CLEANUP_ARTIFACT), cleanup);
|
|
178
|
-
const gate = { passed: true, team_roster_confirmed: true, native_agent_proof: nativeAgentRun.proof?.ok === true, agent_central_ledger: true, analysis_artifact: true, triwiki_refreshed: true, triwiki_validated: true, ssot_guard: true, consensus_artifact: true, ...runtime.gate_fields, implementation_team_fresh: true, review_artifact: true, review_lanes: MIN_TEAM_REVIEWER_LANES, integration_evidence: true, session_cleanup: true, context7_evidence: true, mock: true };
|
|
179
|
-
await writeJsonAtomic(path.join(dir, 'team-gate.json'), gate);
|
|
180
|
-
const proof = await maybeFinalizeRoute(root, { missionId: id, route: '$Team', gateFile: 'team-gate.json', gate, mock: true, statusHint: 'verified_partial', artifacts: ['agents/agent-proof-evidence.json', SSOT_GUARD_ARTIFACT, 'team-gate.json', TEAM_SESSION_CLEANUP_ARTIFACT, 'team-plan.json', 'team-runtime-tasks.json', 'completion-proof.json'], claims: [{ id: 'team-fixture-complete', status: 'verified_partial' }], command: { cmd: `sks team "${prompt}" --mock`, status: 0 } });
|
|
181
|
-
result.mock = true;
|
|
182
|
-
result.proof = proof.validation;
|
|
183
|
-
}
|
|
184
|
-
else {
|
|
185
|
-
result.zellij = liveZellij || await launchTeamZellijView({ root, missionId: id, ledgerRoot: path.join(dir, 'agents'), slotCount: visualLaneCount, dryRun: jsonOutput || !openZellij, attach: false });
|
|
186
|
-
if (openZellij && result.zellij?.ok && result.zellij.capability?.status === 'ok' && shouldAutoAttachTeamZellij(args)) {
|
|
187
|
-
attachZellijSessionInteractive(result.zellij.session_name, { cwd: root, configPath: result.zellij.clipboard_config_path });
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
if (jsonOutput)
|
|
191
|
-
return console.log(JSON.stringify(result, null, 2));
|
|
192
|
-
console.log(`Team mission created: ${id}`);
|
|
193
|
-
console.log(`Agent sessions: ${agentSessions}`);
|
|
194
|
-
console.log(`Role counts: ${formatRoleCounts(roleCounts)}`);
|
|
195
|
-
console.log(`Review policy: minimum ${MIN_TEAM_REVIEWER_LANES} reviewer/QA validation lanes`);
|
|
196
|
-
if (result.zellij?.ok && result.zellij.capability?.status === 'ok')
|
|
197
|
-
console.log(`Zellij: prepared ${visualLaneCount} native agent lane(s) in ${result.zellij.session_name}`);
|
|
198
|
-
else if (result.zellij?.ok)
|
|
199
|
-
console.log(`Zellij: optional live panes unavailable (${(result.zellij.warnings || []).join('; ') || result.zellij.capability?.status || 'unknown'})`);
|
|
200
|
-
else if (!mock)
|
|
201
|
-
console.log(`Zellij: blocked (${Array.from(new Set(result.zellij?.blockers || [])).join('; ')})`);
|
|
202
|
-
console.log(`Watch: sks team watch ${id}`);
|
|
203
|
-
console.log(`Artifacts: .sneakoscope/missions/${id}`);
|
|
204
28
|
}
|
|
205
29
|
async function redirectTeamCreateToNaruto(args = []) {
|
|
206
30
|
const root = await sksRoot();
|
package/dist/core/db-safety.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import { exists, readJson, writeJsonAtomic, readText, nowIso, appendJsonlBounded } from './fsx.js';
|
|
2
|
+
import { exists, readJson, writeJsonAtomic, readText, nowIso, appendJsonlBounded, sha256 } from './fsx.js';
|
|
3
3
|
import { missionDir, setCurrent } from './mission.js';
|
|
4
4
|
import { evaluateMadSksPermissionGate, isMadSksRouteState } from './permission-gates.js';
|
|
5
5
|
import { resolveMadDbMutationPolicy } from './mad-db/mad-db-policy-resolver.js';
|
|
6
|
-
import {
|
|
7
|
-
import { appendMadDbLedgerEvent } from './mad-db/mad-db-ledger.js';
|
|
6
|
+
import { recordMadDbOperation } from './mad-db/mad-db-capability.js';
|
|
7
|
+
import { appendMadDbLedgerEvent, appendMadDbOperationLifecycle } from './mad-db/mad-db-ledger.js';
|
|
8
8
|
export const DEFAULT_DB_SAFETY_POLICY = Object.freeze({
|
|
9
9
|
schema_version: 1,
|
|
10
10
|
mode: 'read_only_default',
|
|
@@ -510,6 +510,16 @@ export async function checkDbOperation(root, state, payload, { duringNoQuestion
|
|
|
510
510
|
const madDb = await resolveMadDbMutationPolicy(root, state, classification);
|
|
511
511
|
if (madDb.allowed === true && state?.mission_id) {
|
|
512
512
|
const madDbDecision = madDb;
|
|
513
|
+
const operationId = `mad-db-op-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
514
|
+
const sqlHash = classification.sql?.statements?.length ? sha256(String(classification.sql.statements.join('\n'))) : null;
|
|
515
|
+
await appendMadDbOperationLifecycle(root, state.mission_id, {
|
|
516
|
+
type: 'db_operation.started',
|
|
517
|
+
operationId,
|
|
518
|
+
cycleId: madDbDecision.cycle_id,
|
|
519
|
+
toolName: classification.toolName || null,
|
|
520
|
+
sqlHash,
|
|
521
|
+
destructive: classification.level === 'destructive'
|
|
522
|
+
});
|
|
513
523
|
const decision = {
|
|
514
524
|
allowed: true,
|
|
515
525
|
action: 'allow',
|
|
@@ -522,11 +532,29 @@ export async function checkDbOperation(root, state, payload, { duringNoQuestion
|
|
|
522
532
|
one_cycle_only: true,
|
|
523
533
|
cycle_id: madDbDecision.cycle_id,
|
|
524
534
|
capability_file: 'mad-db-capability.json',
|
|
525
|
-
consumed:
|
|
535
|
+
consumed: false,
|
|
536
|
+
operation_id: operationId,
|
|
537
|
+
operation_count: Number(madDbDecision.operation_count || 0) + 1,
|
|
538
|
+
max_operations: madDbDecision.max_operations || 20
|
|
526
539
|
}
|
|
527
540
|
};
|
|
528
|
-
await appendMadDbLedgerEvent(root, state.mission_id, { type: 'db_mutation.allowed', cycle_id: madDbDecision.cycle_id, mode: madDbDecision.mode, classification });
|
|
529
|
-
await
|
|
541
|
+
await appendMadDbLedgerEvent(root, state.mission_id, { type: 'db_mutation.allowed', cycle_id: madDbDecision.cycle_id, mode: madDbDecision.mode, classification, operation_id: operationId });
|
|
542
|
+
await appendMadDbOperationLifecycle(root, state.mission_id, {
|
|
543
|
+
type: 'db_operation.allowed',
|
|
544
|
+
operationId,
|
|
545
|
+
cycleId: madDbDecision.cycle_id,
|
|
546
|
+
toolName: classification.toolName || null,
|
|
547
|
+
sqlHash,
|
|
548
|
+
destructive: classification.level === 'destructive',
|
|
549
|
+
resultStatus: 'unknown_pending_tool_result'
|
|
550
|
+
});
|
|
551
|
+
const updatedCapability = await recordMadDbOperation(root, state.mission_id, {
|
|
552
|
+
operationId,
|
|
553
|
+
...(classification.toolName ? { toolName: classification.toolName } : {}),
|
|
554
|
+
...(sqlHash ? { sqlHash } : {})
|
|
555
|
+
});
|
|
556
|
+
decision.mad_db.consumed = updatedCapability?.consumed === true;
|
|
557
|
+
decision.mad_db.operation_count = updatedCapability?.operation_count ?? decision.mad_db.operation_count;
|
|
530
558
|
await appendJsonlBounded(path.join(missionDir(root, state.mission_id), 'db-safety.jsonl'), { ts: nowIso(), decision });
|
|
531
559
|
return decision;
|
|
532
560
|
}
|
package/dist/core/fsx.js
CHANGED
|
@@ -5,7 +5,7 @@ import os from 'node:os';
|
|
|
5
5
|
import crypto from 'node:crypto';
|
|
6
6
|
import { spawn } from 'node:child_process';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
|
-
export const PACKAGE_VERSION = '2.0.
|
|
8
|
+
export const PACKAGE_VERSION = '2.0.16';
|
|
9
9
|
export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
|
|
10
10
|
export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
|
|
11
11
|
export function nowIso() {
|
|
@@ -4,6 +4,7 @@ import { gitBlocker, runGitCommand } from './git-worktree-runner.js';
|
|
|
4
4
|
import { resolveGitWorktreeRoot } from './git-worktree-root.js';
|
|
5
5
|
export async function evaluateGitWorktreeCapability(input = {}) {
|
|
6
6
|
const requireGitWorktree = input.requireGitWorktree === true || process.env.SKS_REQUIRE_GIT_WORKTREE === '1';
|
|
7
|
+
const disabledByEnv = process.env.SKS_DISABLE_GIT_WORKTREE === '1';
|
|
7
8
|
const detection = await detectGitRepo(input.root || process.cwd());
|
|
8
9
|
const blockers = [...detection.blockers];
|
|
9
10
|
const gitAvailable = Boolean(detection.git_binary);
|
|
@@ -24,6 +25,23 @@ export async function evaluateGitWorktreeCapability(input = {}) {
|
|
|
24
25
|
blockers
|
|
25
26
|
};
|
|
26
27
|
}
|
|
28
|
+
if (disabledByEnv) {
|
|
29
|
+
if (requireGitWorktree)
|
|
30
|
+
blockers.push('git_worktree_disabled_by_env');
|
|
31
|
+
return {
|
|
32
|
+
schema: 'sks.git-worktree-capability.v1',
|
|
33
|
+
ok: blockers.length === 0,
|
|
34
|
+
mode: 'patch-envelope-only',
|
|
35
|
+
require_git_worktree: requireGitWorktree,
|
|
36
|
+
git_available: gitAvailable,
|
|
37
|
+
is_git_repo: true,
|
|
38
|
+
worktree_supported: false,
|
|
39
|
+
worktree_probe_attempted: false,
|
|
40
|
+
detection,
|
|
41
|
+
root_resolution: null,
|
|
42
|
+
blockers: [...new Set(blockers)]
|
|
43
|
+
};
|
|
44
|
+
}
|
|
27
45
|
const rootResolution = resolveGitWorktreeRoot({
|
|
28
46
|
repoRoot: detection.root,
|
|
29
47
|
missionId: input.missionId || 'capability'
|
|
@@ -4,7 +4,21 @@ import { ensureDir, nowIso, readJson, writeJsonAtomic } from '../fsx.js';
|
|
|
4
4
|
import { evaluateGitWorktreeCapability } from './git-worktree-capability.js';
|
|
5
5
|
import { gitBlocker, gitOutputLine, runGitCommand } from './git-worktree-runner.js';
|
|
6
6
|
import { sanitizePathPart } from './git-worktree-root.js';
|
|
7
|
+
import { appendParallelRuntimeEvent } from '../agents/parallel-runtime-proof.js';
|
|
7
8
|
export async function allocateWorkerWorktree(input) {
|
|
9
|
+
const eventRoot = input.repoRoot || process.cwd();
|
|
10
|
+
const writeProofEvents = await shouldWriteWorktreeAllocationProof(eventRoot, input.missionId);
|
|
11
|
+
if (writeProofEvents)
|
|
12
|
+
await appendParallelRuntimeEvent(eventRoot, input.missionId, {
|
|
13
|
+
event_type: 'worktree_allocation_started',
|
|
14
|
+
slot_id: input.slotId || input.workerId || null,
|
|
15
|
+
generation_index: input.generationIndex || 1,
|
|
16
|
+
session_id: null,
|
|
17
|
+
pid: null,
|
|
18
|
+
backend: 'git-worktree',
|
|
19
|
+
placement: 'unknown',
|
|
20
|
+
worktree_id: input.workerId || null
|
|
21
|
+
}).catch(() => undefined);
|
|
8
22
|
const capability = await evaluateGitWorktreeCapability({
|
|
9
23
|
root: input.repoRoot || process.cwd(),
|
|
10
24
|
missionId: input.missionId,
|
|
@@ -64,8 +78,63 @@ export async function allocateWorkerWorktree(input) {
|
|
|
64
78
|
blockers: [...new Set(blockers)]
|
|
65
79
|
};
|
|
66
80
|
await appendWorktreeManifest(allocation);
|
|
81
|
+
if (writeProofEvents)
|
|
82
|
+
await appendParallelRuntimeEvent(eventRoot, input.missionId, {
|
|
83
|
+
event_type: 'worktree_allocation_completed',
|
|
84
|
+
slot_id: allocation.slot_id,
|
|
85
|
+
generation_index: allocation.generation_index,
|
|
86
|
+
session_id: null,
|
|
87
|
+
pid: null,
|
|
88
|
+
backend: 'git-worktree',
|
|
89
|
+
placement: 'unknown',
|
|
90
|
+
worktree_id: `${allocation.slot_id}-gen-${allocation.generation_index}`,
|
|
91
|
+
meta: {
|
|
92
|
+
ok: allocation.ok,
|
|
93
|
+
worktree_path: allocation.worktree_path,
|
|
94
|
+
branch: allocation.branch,
|
|
95
|
+
blockers: allocation.blockers
|
|
96
|
+
}
|
|
97
|
+
}).catch(() => undefined);
|
|
67
98
|
return allocation;
|
|
68
99
|
}
|
|
100
|
+
export async function allocateWorkerWorktreesBatch(input) {
|
|
101
|
+
const maxParallel = Math.max(1, Math.floor(Number(input.maxParallel || 1)));
|
|
102
|
+
const queue = uniqueWorktreeAllocationInputs(input.workers);
|
|
103
|
+
const allocations = [];
|
|
104
|
+
const workers = Array.from({ length: Math.min(maxParallel, queue.length) }, async () => {
|
|
105
|
+
while (queue.length) {
|
|
106
|
+
const next = queue.shift();
|
|
107
|
+
if (!next)
|
|
108
|
+
continue;
|
|
109
|
+
allocations.push(await allocateWorkerWorktree({
|
|
110
|
+
repoRoot: input.root,
|
|
111
|
+
missionId: input.missionId,
|
|
112
|
+
workerId: next.workerId,
|
|
113
|
+
...(next.slotId === undefined ? {} : { slotId: next.slotId }),
|
|
114
|
+
...(next.generationIndex === undefined ? {} : { generationIndex: next.generationIndex }),
|
|
115
|
+
...(next.baseRef === undefined ? {} : { baseRef: next.baseRef }),
|
|
116
|
+
...(next.branchPrefix === undefined ? {} : { branchPrefix: next.branchPrefix })
|
|
117
|
+
}));
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
await Promise.all(workers);
|
|
121
|
+
return allocations;
|
|
122
|
+
}
|
|
123
|
+
function uniqueWorktreeAllocationInputs(workers = []) {
|
|
124
|
+
const seen = new Set();
|
|
125
|
+
const unique = [];
|
|
126
|
+
for (const worker of workers) {
|
|
127
|
+
const workerId = sanitizePathPart(worker.workerId || 'worker');
|
|
128
|
+
const slotId = sanitizePathPart(worker.slotId || workerId);
|
|
129
|
+
const generationIndex = Math.max(1, Math.floor(Number(worker.generationIndex || 1)));
|
|
130
|
+
const key = `${workerId}:${slotId}:${generationIndex}`;
|
|
131
|
+
if (seen.has(key))
|
|
132
|
+
continue;
|
|
133
|
+
seen.add(key);
|
|
134
|
+
unique.push({ ...worker, workerId, slotId, generationIndex });
|
|
135
|
+
}
|
|
136
|
+
return unique;
|
|
137
|
+
}
|
|
69
138
|
async function appendWorktreeManifest(allocation) {
|
|
70
139
|
const current = await readJson(allocation.manifest_path, null).catch(() => null);
|
|
71
140
|
const allocations = Array.isArray(current?.allocations) ? current.allocations : [];
|
|
@@ -84,6 +153,17 @@ async function appendWorktreeManifest(allocation) {
|
|
|
84
153
|
};
|
|
85
154
|
await writeJsonAtomic(allocation.manifest_path, manifest);
|
|
86
155
|
}
|
|
156
|
+
async function shouldWriteWorktreeAllocationProof(root, missionId) {
|
|
157
|
+
if (process.env.SKS_GIT_WORKTREE_ALLOCATION_PROOF === '0')
|
|
158
|
+
return false;
|
|
159
|
+
try {
|
|
160
|
+
const stat = await fsp.stat(path.join(root, '.sneakoscope', 'missions', missionId, 'agents'));
|
|
161
|
+
return stat.isDirectory();
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
87
167
|
function sanitizeBranchPart(value) {
|
|
88
168
|
return sanitizePathPart(value).replace(/\./g, '-').slice(0, 48) || 'item';
|
|
89
169
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { nowIso } from '../fsx.js';
|
|
2
|
+
import { allocateWorkerWorktreesBatch as allocateBatch } from './git-worktree-manager.js';
|
|
2
3
|
export function planGitWorktreePool(input) {
|
|
3
4
|
const reusable = [...(input.reusableWorktrees || [])];
|
|
4
5
|
const assignments = input.workerIds.map((workerId) => {
|
|
@@ -20,4 +21,7 @@ export function planGitWorktreePool(input) {
|
|
|
20
21
|
blockers: []
|
|
21
22
|
};
|
|
22
23
|
}
|
|
24
|
+
export async function allocateWorkerWorktreesBatch(input) {
|
|
25
|
+
return allocateBatch(input);
|
|
26
|
+
}
|
|
23
27
|
//# sourceMappingURL=git-worktree-pool.js.map
|
|
@@ -27,7 +27,9 @@ export async function createMadDbCapability(root, input) {
|
|
|
27
27
|
},
|
|
28
28
|
consumed: false,
|
|
29
29
|
consumed_at: null,
|
|
30
|
-
consumed_by: null
|
|
30
|
+
consumed_by: null,
|
|
31
|
+
max_operations: Math.max(1, Math.floor(Number(process.env.SKS_MAD_DB_MAX_OPERATIONS || 20))),
|
|
32
|
+
operation_count: 0
|
|
31
33
|
};
|
|
32
34
|
const dir = missionDir(root, input.missionId);
|
|
33
35
|
await writeJsonAtomic(path.join(dir, MAD_DB_CAPABILITY_FILE), capability);
|
|
@@ -52,9 +54,39 @@ export function isMadDbCapabilityActive(capability, nowMs = Date.now()) {
|
|
|
52
54
|
return capability.enabled === true
|
|
53
55
|
&& capability.consumed !== true
|
|
54
56
|
&& capability.one_cycle_only === true
|
|
57
|
+
&& Number(capability.operation_count || 0) < Number(capability.max_operations || 20)
|
|
55
58
|
&& Number.isFinite(expires)
|
|
56
59
|
&& expires > nowMs;
|
|
57
60
|
}
|
|
61
|
+
export async function recordMadDbOperation(root, missionId, input = {}) {
|
|
62
|
+
const capability = await readMadDbCapability(root, missionId);
|
|
63
|
+
if (!isMadDbCapabilityActive(capability))
|
|
64
|
+
return capability;
|
|
65
|
+
const operationCount = Number(capability.operation_count || 0) + 1;
|
|
66
|
+
const maxOperations = Math.max(1, Number(capability.max_operations || 20));
|
|
67
|
+
const updated = {
|
|
68
|
+
...capability,
|
|
69
|
+
operation_count: operationCount,
|
|
70
|
+
max_operations: maxOperations
|
|
71
|
+
};
|
|
72
|
+
const dir = missionDir(root, missionId);
|
|
73
|
+
await writeJsonAtomic(path.join(dir, MAD_DB_CAPABILITY_FILE), updated);
|
|
74
|
+
await appendJsonlBounded(path.join(dir, 'mad-db-ledger.jsonl'), {
|
|
75
|
+
ts: nowIso(),
|
|
76
|
+
type: 'db_operation.counted',
|
|
77
|
+
mission_id: missionId,
|
|
78
|
+
cycle_id: updated.cycle_id,
|
|
79
|
+
operation_id: input.operationId || null,
|
|
80
|
+
tool_name: input.toolName || null,
|
|
81
|
+
sql_hash: input.sqlHash || null,
|
|
82
|
+
operation_count: operationCount,
|
|
83
|
+
max_operations: maxOperations
|
|
84
|
+
});
|
|
85
|
+
if (operationCount >= maxOperations) {
|
|
86
|
+
return consumeMadDbCapability(root, missionId, { consumedBy: 'db-safety-checkDbOperation', reason: 'mad_db_max_operations_reached' });
|
|
87
|
+
}
|
|
88
|
+
return updated;
|
|
89
|
+
}
|
|
58
90
|
export async function consumeMadDbCapability(root, missionId, input = {}) {
|
|
59
91
|
const capability = await readMadDbCapability(root, missionId);
|
|
60
92
|
if (!isMadDbCapabilityActive(capability))
|
|
@@ -14,4 +14,18 @@ export async function appendMadDbLedgerEvent(root, missionId, event) {
|
|
|
14
14
|
await writeJsonAtomic(path.join(dir, 'mad-db-ledger.latest.json'), row).catch(() => undefined);
|
|
15
15
|
return row;
|
|
16
16
|
}
|
|
17
|
+
export async function appendMadDbOperationLifecycle(root, missionId, input) {
|
|
18
|
+
return appendMadDbLedgerEvent(root, missionId, {
|
|
19
|
+
type: input.type,
|
|
20
|
+
operation_id: input.operationId,
|
|
21
|
+
cycle_id: input.cycleId || null,
|
|
22
|
+
mcp_server: input.mcpServer || null,
|
|
23
|
+
tool_name: input.toolName || null,
|
|
24
|
+
sql_hash: input.sqlHash || null,
|
|
25
|
+
destructive: input.destructive === true,
|
|
26
|
+
result_status: input.resultStatus || 'unknown_pending_tool_result',
|
|
27
|
+
row_count: input.rowCount ?? null,
|
|
28
|
+
error: input.error || null
|
|
29
|
+
});
|
|
30
|
+
}
|
|
17
31
|
//# sourceMappingURL=mad-db-ledger.js.map
|
|
@@ -20,6 +20,8 @@ export async function resolveMadDbMutationPolicy(root, state = {}, classificatio
|
|
|
20
20
|
audit_required: true,
|
|
21
21
|
mission_id: missionId,
|
|
22
22
|
cycle_id: capability.cycle_id,
|
|
23
|
+
operation_count: capability.operation_count || 0,
|
|
24
|
+
max_operations: capability.max_operations || 20,
|
|
23
25
|
capability
|
|
24
26
|
};
|
|
25
27
|
}
|
|
@@ -9,6 +9,7 @@ export function decideNarutoConcurrency(input = {}) {
|
|
|
9
9
|
const hardware = probeHardwareCapacity(input.hardware || {});
|
|
10
10
|
const zellijVisiblePaneCap = normalizePositiveInt(input.zellijVisiblePaneCap, Math.min(8, Math.max(4, Math.floor(hardware.terminal_rows / 5))));
|
|
11
11
|
const backend = String(input.backend || 'codex-sdk');
|
|
12
|
+
const parallelismMode = normalizeParallelismMode(input.parallelismMode);
|
|
12
13
|
const freeGb = hardware.free_memory_bytes / (1024 * 1024 * 1024);
|
|
13
14
|
const totalGb = hardware.total_memory_bytes / (1024 * 1024 * 1024);
|
|
14
15
|
const reclaimableFloorGb = totalGb >= 32 ? 16 : totalGb >= 16 ? 8 : totalGb >= 8 ? 4 : Math.max(1, freeGb);
|
|
@@ -33,7 +34,12 @@ export function decideNarutoConcurrency(input = {}) {
|
|
|
33
34
|
const rawSafe = Math.max(1, Math.min(requestedClones, totalWorkItems, memoryCap, fdCap, cpuCap + ioCap, gitWorktreeCap + processCap, backendBudget, queueCap, leaseCap, 100));
|
|
34
35
|
const pressure = monitorNarutoResourcePressure(hardware, { activeWorkers: rawSafe, zellijVisiblePaneCap });
|
|
35
36
|
const backpressure = applyNarutoBackpressure(rawSafe, pressure);
|
|
36
|
-
const
|
|
37
|
+
const currentSafeActiveWorkers = Math.max(1, Math.min(rawSafe, backpressure.adjusted_active_workers));
|
|
38
|
+
const safeActiveWorkers = parallelismMode === 'extreme'
|
|
39
|
+
? rawSafe
|
|
40
|
+
: parallelismMode === 'balanced'
|
|
41
|
+
? Math.max(1, Math.min(rawSafe, Math.max(16, Math.floor(rawSafe * 0.75))))
|
|
42
|
+
: currentSafeActiveWorkers;
|
|
37
43
|
const safeVisible = Math.min(safeActiveWorkers, zellijVisiblePaneCap);
|
|
38
44
|
const reasons = [
|
|
39
45
|
...(memoryCap < requestedClones ? ['memory_cap'] : []),
|
|
@@ -60,11 +66,18 @@ export function decideNarutoConcurrency(input = {}) {
|
|
|
60
66
|
git_worktree_parallel: gitWorktreeCap,
|
|
61
67
|
cpu_io_parallel: cpuCap + ioCap,
|
|
62
68
|
verification_parallel: Math.max(1, Math.min(hardware.cpu_core_count * 2, safeActiveWorkers, 16)),
|
|
69
|
+
parallelism_mode: parallelismMode,
|
|
63
70
|
reasons: [...new Set(reasons)],
|
|
64
71
|
backpressure: backpressure.backpressure,
|
|
65
72
|
hardware
|
|
66
73
|
};
|
|
67
74
|
}
|
|
75
|
+
function normalizeParallelismMode(value) {
|
|
76
|
+
const text = String(value || process.env.SKS_NARUTO_PARALLELISM || 'extreme').toLowerCase();
|
|
77
|
+
if (text === 'safe' || text === 'balanced' || text === 'extreme')
|
|
78
|
+
return text;
|
|
79
|
+
return 'extreme';
|
|
80
|
+
}
|
|
68
81
|
function normalizePositiveInt(value, fallback) {
|
|
69
82
|
const parsed = Number(value);
|
|
70
83
|
if (!Number.isFinite(parsed) || parsed < 1)
|
package/dist/core/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const PACKAGE_VERSION = '2.0.
|
|
1
|
+
export const PACKAGE_VERSION = '2.0.16';
|
|
2
2
|
//# sourceMappingURL=version.js.map
|