@kognai/orchestrator-core 0.1.1 → 0.1.3
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/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/lib/did-resolver-registry.d.ts +15 -0
- package/dist/lib/did-resolver-registry.js +9 -0
- package/dist/lib/engine-agents.d.ts +71 -0
- package/dist/lib/engine-agents.js +835 -0
- package/dist/lib/engine-coding-agent.d.ts +17 -0
- package/dist/lib/engine-coding-agent.js +890 -0
- package/dist/lib/engine-helpers.d.ts +10 -0
- package/dist/lib/engine-helpers.js +319 -0
- package/dist/lib/engine-loaders.d.ts +5 -0
- package/dist/lib/engine-loaders.js +241 -0
- package/dist/lib/engine-orchestrator.d.ts +46 -0
- package/dist/lib/engine-orchestrator.js +1491 -0
- package/dist/lib/engine-primitives.d.ts +141 -0
- package/dist/lib/engine-primitives.js +748 -0
- package/dist/lib/orchestrate-engine.d.ts +14 -1
- package/dist/lib/orchestrate-engine.js +26 -4333
- package/dist/lib/plumber/extractor.d.ts +18 -0
- package/dist/lib/plumber/extractor.js +213 -0
- package/dist/lib/plumber/fixer.d.ts +75 -0
- package/dist/lib/plumber/fixer.js +165 -0
- package/dist/lib/plumber/index.d.ts +3 -0
- package/dist/lib/plumber/index.js +3 -0
- package/dist/lib/plumber/patcher.d.ts +44 -0
- package/dist/lib/plumber/patcher.js +210 -0
- package/dist/lib/preamble-provider-registry.d.ts +12 -0
- package/dist/lib/preamble-provider-registry.js +7 -0
- package/package.json +1 -1
|
@@ -0,0 +1,835 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AgentCreator = exports.CTOAgent = exports.CTODataCollector = exports.CEOAgent = exports.Supervisor2Agent = exports.SupervisorAgent = void 0;
|
|
4
|
+
exports.reconcileSupervisorReviews = reconcileSupervisorReviews;
|
|
5
|
+
/**
|
|
6
|
+
* engine-agents.ts — leadership/agent classes extracted from orchestrate-engine.ts
|
|
7
|
+
* (TICKET-231 engine split 3). SupervisorAgent, Supervisor2Agent, reconcileSupervisorReviews,
|
|
8
|
+
* CEOAgent, CTODataCollector, CTOAgent, AgentCreator + the SpawnGate seam types.
|
|
9
|
+
* Imports shared primitives (log/callLLM/normalizeReview/c) + AgentTask/ReviewResult/CTO* types
|
|
10
|
+
* back from the engine via a runtime-safe circular import (methods use them at call-time).
|
|
11
|
+
*/
|
|
12
|
+
const fs_1 = require("fs");
|
|
13
|
+
const sherlock_memory_1 = require("./sherlock-memory");
|
|
14
|
+
const clawrouter_client_1 = require("./clawrouter-client");
|
|
15
|
+
const citizenship_1 = require("./citizenship");
|
|
16
|
+
const engine_loaders_1 = require("./engine-loaders");
|
|
17
|
+
const orchestrate_engine_1 = require("./orchestrate-engine");
|
|
18
|
+
// ===== Supervisor Agent (Claude via Anthropic API) =====
|
|
19
|
+
class SupervisorAgent {
|
|
20
|
+
systemPrompt;
|
|
21
|
+
constructor() {
|
|
22
|
+
const promptPath = './agents/supervisor/prompt.md';
|
|
23
|
+
const rawPrompt = (0, fs_1.existsSync)(promptPath) ? (0, fs_1.readFileSync)(promptPath, 'utf-8') : 'You are a code review supervisor.';
|
|
24
|
+
this.systemPrompt = (0, engine_loaders_1.loadConstitutionalPreamble)() + rawPrompt;
|
|
25
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.magenta, '+ Loaded supervisor agent (Claude via Anthropic API)');
|
|
26
|
+
}
|
|
27
|
+
async reviewTask(task, files) {
|
|
28
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.magenta, `\n[supervisor] Reviewing: ${task.id}`);
|
|
29
|
+
// FIX: Use XML-style tags (NOT code fences) so the model can't confuse display format with file content
|
|
30
|
+
// NOTE: 12000 char limit — covers EXACT CONTENT files (typically 5K-10K chars); 4000 was too small
|
|
31
|
+
const fileContents = files.map((filepath) => {
|
|
32
|
+
const content = (0, fs_1.existsSync)(filepath) ? (0, fs_1.readFileSync)(filepath, 'utf-8') : '';
|
|
33
|
+
return `### ${filepath}\n<file_content>\n${content.substring(0, 12000)}\n</file_content>`;
|
|
34
|
+
}).join('\n\n');
|
|
35
|
+
// FIX: Pre-compute fence check in TypeScript — inject evidence so model never hallucinates fence presence
|
|
36
|
+
const fenceCheckLines = files.map((filepath) => {
|
|
37
|
+
const content = (0, fs_1.existsSync)(filepath) ? (0, fs_1.readFileSync)(filepath, 'utf-8') : '';
|
|
38
|
+
const firstLine = content.trimStart().split('\n')[0] || '';
|
|
39
|
+
const hasFence = firstLine.startsWith('```');
|
|
40
|
+
return ` ${filepath}: ${hasFence ? `FENCE DETECTED (first line: ${JSON.stringify(firstLine)})` : 'OK (no fence at start)'}`;
|
|
41
|
+
}).join('\n');
|
|
42
|
+
// CTO-005: Add fence detection to supervisor review checklist
|
|
43
|
+
const integrityContext = task._integrityFailed
|
|
44
|
+
? `\n\n## ⚠️ INTEGRITY ALERT\n${task._integrityDetails}\nThis file was flagged for destructive rewrite. The original was preserved. REJECT this task.\n`
|
|
45
|
+
: '';
|
|
46
|
+
// Sherlock v2: inject ASMR episodic memory context (AMD-21-03) — fail-open
|
|
47
|
+
const memoryContext = await (0, sherlock_memory_1.getSherlockMemoryContext)(task.context || task.id);
|
|
48
|
+
const userPrompt = `Review the following code generated for task ${task.id}.\n\n## Task Spec\n${task.context}${memoryContext}\n\n## Generated Files (${files.length})\n${fileContents}${integrityContext}\n\n## Pre-computed Fence Check (authoritative — do NOT infer from display format)\n${fenceCheckLines}\n\n## Instructions\nCRITICAL CHECK: Use the Pre-computed Fence Check above. If any file shows FENCE DETECTED, REJECT. Do NOT infer fence presence from the <file_content> display tags — those are display-only wrappers.\nAlso check: Did the file lose existing functionality? If a file shrank significantly, REJECT.\n\n## Categorical grade (use a discrete letter — no fake precision)\n- A: production-perfect. No improvements possible. Ship.\n- B: good with minor polish needed (rename, comment, formatting).\n- C: works but has noticeable issues (missed edge case, weak abstraction, partial spec coverage). APPROVED with caveats.\n- D: significant problems (broken edge case, regression risk, anti-pattern). REJECT.\n- F: broken, unsafe, doesn't meet spec, or fence/integrity failure. REJECT.\n\nThe deterministic gate already ran (typecheck + structural). If a file got here, syntax is valid — focus your review on substance, not parseability.\n\nRespond with a JSON object:\n{\n "verdict": "APPROVED" or "REJECTED",\n "grade": "A" | "B" | "C" | "D" | "F",\n "score_rationale": "ONE sentence naming the specific factor that determined the grade. Vague 'good code' is NOT acceptable.",\n "summary": "brief review summary",\n "issues": [{"severity": "critical|high|medium|low", "file": "path", "description": "..."}],\n "strengths": ["..."]\n}`;
|
|
49
|
+
const startTime = Date.now();
|
|
50
|
+
// B.15: DeepSeek via ClawRouter for standard tasks (~$0.02/task vs $0.07 dual-supervisor)
|
|
51
|
+
// Retain Claude Sonnet only for audit/refactor-complex (high-stakes)
|
|
52
|
+
const taskType = task.task_type || '';
|
|
53
|
+
const isHighStakes = taskType === 'audit' || taskType === 'refactor-complex' ||
|
|
54
|
+
(task.context || '').toLowerCase().includes('security') || (task.context || '').toLowerCase().includes('audit');
|
|
55
|
+
const crAvail = await (0, clawrouter_client_1.clawRouterIsAvailable)().catch(() => false);
|
|
56
|
+
let reviewProvider = (crAvail && !isHighStakes) ? 'clawrouter' : 'anthropic';
|
|
57
|
+
const reviewModel = reviewProvider === 'clawrouter' ? 'deepseek/deepseek-chat' : 'claude-sonnet-4-6';
|
|
58
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.gray, ` -> Sending to ${reviewProvider === 'clawrouter' ? 'ClawRouter/DeepSeek' : 'Claude Sonnet'} (${isHighStakes ? 'high-stakes' : 'standard'})...`);
|
|
59
|
+
try {
|
|
60
|
+
const response = await (0, orchestrate_engine_1.callLLM)(reviewProvider, reviewModel, this.systemPrompt, userPrompt, 120000, 'supervisor', 'code_review');
|
|
61
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
62
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.gray, ` -> Review received in ${elapsed}s (${response.usage?.total_tokens || '?'} tokens)`);
|
|
63
|
+
const content = response.choices?.[0]?.message?.content || '';
|
|
64
|
+
const jsonMatch = content.match(/\{[\s\S]*\}/);
|
|
65
|
+
if (jsonMatch) {
|
|
66
|
+
const review = (0, orchestrate_engine_1.normalizeReview)(JSON.parse(jsonMatch[0]));
|
|
67
|
+
const gradeLabel = review.grade ? `${review.grade} · ` : '';
|
|
68
|
+
if (review.verdict === 'APPROVED') {
|
|
69
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.green, ` ✓ APPROVED (${gradeLabel}score: ${review.score}/100)`);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.red, ` ✗ REJECTED (${gradeLabel}score: ${review.score}/100)`);
|
|
73
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ` Summary: ${review.summary}`);
|
|
74
|
+
for (const issue of review.issues || []) {
|
|
75
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ` [${issue.severity}] ${issue.file}: ${issue.description}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return review;
|
|
79
|
+
}
|
|
80
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ' ! Could not parse review JSON, auto-approving');
|
|
81
|
+
return { verdict: 'APPROVED', score: 70, summary: 'Auto-approved (parse failure)', issues: [], strengths: [] };
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ` ! Supervisor error: ${error.message}`);
|
|
85
|
+
return { verdict: 'APPROVED', score: 0, summary: `Supervisor unavailable: ${error.message}`, issues: [], strengths: [] };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
exports.SupervisorAgent = SupervisorAgent;
|
|
90
|
+
// ===== Supervisor 2 Agent (Claude Haiku 4.5; DeepSeek mono-supervision on Anthropic drain) =====
|
|
91
|
+
class Supervisor2Agent {
|
|
92
|
+
systemPrompt;
|
|
93
|
+
constructor() {
|
|
94
|
+
const promptPath = './agents/supervisor/prompt.md';
|
|
95
|
+
const rawPrompt = (0, fs_1.existsSync)(promptPath) ? (0, fs_1.readFileSync)(promptPath, 'utf-8') : 'You are a code review supervisor.';
|
|
96
|
+
this.systemPrompt = (0, engine_loaders_1.loadConstitutionalPreamble)() + rawPrompt;
|
|
97
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.magenta, '+ Loaded supervisor 2 agent (Claude Haiku — second pass)');
|
|
98
|
+
}
|
|
99
|
+
async reviewTask(task, files) {
|
|
100
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.magenta, `\n[supervisor-2/haiku] Reviewing: ${task.id}`);
|
|
101
|
+
// FIX: Use XML-style tags (NOT code fences) so the model can't confuse display format with file content
|
|
102
|
+
// NOTE: 12000 char limit — covers EXACT CONTENT files (typically 5K-10K chars); 4000 was too small
|
|
103
|
+
const fileContents = files.map((filepath) => {
|
|
104
|
+
const content = (0, fs_1.existsSync)(filepath) ? (0, fs_1.readFileSync)(filepath, 'utf-8') : '';
|
|
105
|
+
return `### ${filepath}\n<file_content>\n${content.substring(0, 12000)}\n</file_content>`;
|
|
106
|
+
}).join('\n\n');
|
|
107
|
+
// FIX: Pre-compute fence check in TypeScript — inject evidence so model never hallucinates fence presence
|
|
108
|
+
const fenceCheckLines2 = files.map((filepath) => {
|
|
109
|
+
const content = (0, fs_1.existsSync)(filepath) ? (0, fs_1.readFileSync)(filepath, 'utf-8') : '';
|
|
110
|
+
const firstLine = content.trimStart().split('\n')[0] || '';
|
|
111
|
+
const hasFence = firstLine.startsWith('```');
|
|
112
|
+
return ` ${filepath}: ${hasFence ? `FENCE DETECTED (first line: ${JSON.stringify(firstLine)})` : 'OK (no fence at start)'}`;
|
|
113
|
+
}).join('\n');
|
|
114
|
+
// CTO-005: Add fence detection to Haiku supervisor review checklist
|
|
115
|
+
const integrityContext2 = task._integrityFailed
|
|
116
|
+
? `\n\n## ⚠️ INTEGRITY ALERT\n${task._integrityDetails}\nThis file was flagged for destructive rewrite. The original was preserved. REJECT this task.\n`
|
|
117
|
+
: '';
|
|
118
|
+
const userPrompt = `Review the following code generated for task ${task.id}.\n\n## Task Spec\n${task.context}\n\n## Generated Files (${files.length})\n${fileContents}${integrityContext2}\n\n## Pre-computed Fence Check (authoritative — do NOT infer from display format)\n${fenceCheckLines2}\n\n## Instructions\nCRITICAL CHECK: Use the Pre-computed Fence Check above. If any file shows FENCE DETECTED, REJECT. Do NOT infer fence presence from the <file_content> display tags — those are display-only wrappers.\nAlso check: Did the file lose existing functionality? If a file shrank significantly, REJECT.\n\n## Categorical grade (use a discrete letter — no fake precision)\n- A: production-perfect. No improvements possible. Ship.\n- B: good with minor polish needed (rename, comment, formatting).\n- C: works but has noticeable issues (missed edge case, weak abstraction, partial spec coverage). APPROVED with caveats.\n- D: significant problems (broken edge case, regression risk, anti-pattern). REJECT.\n- F: broken, unsafe, doesn't meet spec, or fence/integrity failure. REJECT.\n\nThe deterministic gate already ran (typecheck + structural). If a file got here, syntax is valid — focus your review on substance, not parseability.\n\nRespond with a JSON object:\n{\n "verdict": "APPROVED" or "REJECTED",\n "grade": "A" | "B" | "C" | "D" | "F",\n "score_rationale": "ONE sentence naming the specific factor that determined the grade. Vague 'good code' is NOT acceptable.",\n "summary": "brief review summary",\n "issues": [{"severity": "critical|high|medium|low", "file": "path", "description": "..."}],\n "strengths": ["..."]\n}`;
|
|
119
|
+
const startTime = Date.now();
|
|
120
|
+
// B.15: Use Haiku for second-pass review — 10x cheaper than Sonnet.
|
|
121
|
+
// Founder directive 2026-05-25: if Anthropic depletes, fall back to ClawRouter/DeepSeek
|
|
122
|
+
// mono-supervision rather than halting the swarm. Autonomy is on.
|
|
123
|
+
const tryPath = async (provider, model, label) => {
|
|
124
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.gray, ` -> Sending to ${label}...`);
|
|
125
|
+
const response = provider === 'anthropic'
|
|
126
|
+
? await (0, orchestrate_engine_1.callAnthropicCached)(model, this.systemPrompt, userPrompt, 120000)
|
|
127
|
+
: await (0, orchestrate_engine_1.callLLM)('clawrouter', model, this.systemPrompt, userPrompt, 120000, 'supervisor-2', 'code_review');
|
|
128
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
129
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.gray, ` -> ${label} review received in ${elapsed}s (${response.usage?.total_tokens || '?'} tokens)`);
|
|
130
|
+
return response;
|
|
131
|
+
};
|
|
132
|
+
let response;
|
|
133
|
+
let usedFallback = false;
|
|
134
|
+
try {
|
|
135
|
+
response = await tryPath('anthropic', 'claude-haiku-4-5-20251001', 'Claude Haiku (second pass)');
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
const msg = String(error?.message || '');
|
|
139
|
+
const credit = /credit balance|invalid_request_error.*credit|insufficient.*quota/i.test(msg);
|
|
140
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ` ! [Haiku] Anthropic unavailable${credit ? ' (credits)' : ''}: ${msg.slice(0, 120)}`);
|
|
141
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ' ! Falling back to ClawRouter/DeepSeek mono-supervision — swarm continues');
|
|
142
|
+
try {
|
|
143
|
+
response = await tryPath('clawrouter', 'deepseek/deepseek-chat', 'ClawRouter/DeepSeek (mono fallback)');
|
|
144
|
+
usedFallback = true;
|
|
145
|
+
}
|
|
146
|
+
catch (fallbackErr) {
|
|
147
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ` ! [Haiku] Both Anthropic + ClawRouter unavailable: ${fallbackErr.message}`);
|
|
148
|
+
return { verdict: 'APPROVED', score: 0, summary: `Supervisor 2 unavailable: ${msg.slice(0, 200)}`, issues: [], strengths: [] };
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const content = response.choices?.[0]?.message?.content || '';
|
|
152
|
+
const jsonMatch = content.match(/\{[\s\S]*\}/);
|
|
153
|
+
if (jsonMatch) {
|
|
154
|
+
const review = (0, orchestrate_engine_1.normalizeReview)(JSON.parse(jsonMatch[0]));
|
|
155
|
+
const tag = usedFallback ? '[DeepSeek-fallback]' : '[Haiku]';
|
|
156
|
+
const gradeLabel = review.grade ? `${review.grade} · ` : '';
|
|
157
|
+
if (review.verdict === 'APPROVED')
|
|
158
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.green, ` ✓ ${tag} APPROVED (${gradeLabel}score: ${review.score}/100)`);
|
|
159
|
+
else {
|
|
160
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.red, ` ✗ ${tag} REJECTED (${gradeLabel}score: ${review.score}/100)`);
|
|
161
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ` Summary: ${review.summary}`);
|
|
162
|
+
for (const issue of review.issues || [])
|
|
163
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ` [${issue.severity}] ${issue.file}: ${issue.description}`);
|
|
164
|
+
}
|
|
165
|
+
return review;
|
|
166
|
+
}
|
|
167
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ' ! [Haiku] Could not parse review JSON, auto-approving');
|
|
168
|
+
return { verdict: 'APPROVED', score: 70, summary: 'Auto-approved (parse failure)', issues: [], strengths: [] };
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
exports.Supervisor2Agent = Supervisor2Agent;
|
|
172
|
+
async function reconcileSupervisorReviews(review1, review2, task, ceo) {
|
|
173
|
+
// S67-001 + founder directive 2026-05-25: graceful degradation when either supervisor depletes.
|
|
174
|
+
// Autonomy is on — swarm must keep shipping. DeepSeek/ClawRouter fallback substitutes for Anthropic.
|
|
175
|
+
// Naming history: sup1 was Sonnet, sup2 was OpenAI Codex (hence "Codex" in older logs).
|
|
176
|
+
// Today sup1 routes DeepSeek-by-default + Sonnet for high-stakes; sup2 routes Haiku + DeepSeek fallback.
|
|
177
|
+
// The log labels below use "Sup1" / "Sup2" to stay accurate regardless of which provider answered.
|
|
178
|
+
const isUnavailable = (r) => /Supervisor unavailable|Supervisor 2 unavailable|unavailable/i.test(r.summary || '');
|
|
179
|
+
const sup1Unavailable = isUnavailable(review1);
|
|
180
|
+
const sup2Unavailable = isUnavailable(review2);
|
|
181
|
+
if (sup1Unavailable && sup2Unavailable) {
|
|
182
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ` ! BOTH supervisors unavailable (Anthropic + fallback drained) — auto-approving so swarm keeps shipping`);
|
|
183
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ` KSL will capture this attempt for training. Restore credits to re-enable review.`);
|
|
184
|
+
return {
|
|
185
|
+
finalReview: {
|
|
186
|
+
verdict: 'APPROVED', score: 50,
|
|
187
|
+
summary: 'Auto-approved — both supervisors unavailable. Founder directive: autonomy on, swarm continues.',
|
|
188
|
+
issues: [], strengths: [],
|
|
189
|
+
},
|
|
190
|
+
review1, review2, consensus: false, escalatedToCEO: false,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
if (sup1Unavailable) {
|
|
194
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ` ! Sup1 unavailable — Sup2 as sole reviewer (score: ${review2.score}/100)`);
|
|
195
|
+
return { finalReview: review2, review1, review2, consensus: false, escalatedToCEO: false };
|
|
196
|
+
}
|
|
197
|
+
if (sup2Unavailable) {
|
|
198
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ` ! Sup2 unavailable — Sup1 as sole reviewer (score: ${review1.score}/100)`);
|
|
199
|
+
return { finalReview: review1, review1, review2, consensus: false, escalatedToCEO: false };
|
|
200
|
+
}
|
|
201
|
+
const bothApproved = review1.verdict === 'APPROVED' && review2.verdict === 'APPROVED';
|
|
202
|
+
const bothRejected = review1.verdict !== 'APPROVED' && review2.verdict !== 'APPROVED';
|
|
203
|
+
const consensus = bothApproved || bothRejected;
|
|
204
|
+
if (bothApproved) {
|
|
205
|
+
// Both approve — take the average score, merge strengths
|
|
206
|
+
const avgScore = Math.round((review1.score + review2.score) / 2);
|
|
207
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.green, ` ✓ DUAL CONSENSUS: Both supervisors APPROVED (Sup1: ${review1.score}, Sup2: ${review2.score}, avg: ${avgScore})`);
|
|
208
|
+
return {
|
|
209
|
+
finalReview: {
|
|
210
|
+
verdict: 'APPROVED',
|
|
211
|
+
score: avgScore,
|
|
212
|
+
summary: `Dual-approved: Sup1 (${review1.score}/100) + Sup2 (${review2.score}/100)`,
|
|
213
|
+
issues: [...review1.issues, ...review2.issues],
|
|
214
|
+
strengths: Array.from(new Set([...review1.strengths, ...review2.strengths])),
|
|
215
|
+
},
|
|
216
|
+
review1, review2, consensus: true, escalatedToCEO: false,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
if (bothRejected) {
|
|
220
|
+
// Both reject — merge issues, take lower score
|
|
221
|
+
const minScore = Math.min(review1.score, review2.score);
|
|
222
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.red, ` ✗ DUAL CONSENSUS: Both supervisors REJECTED (Sup1: ${review1.score}, Sup2: ${review2.score})`);
|
|
223
|
+
return {
|
|
224
|
+
finalReview: {
|
|
225
|
+
verdict: 'REJECTED',
|
|
226
|
+
score: minScore,
|
|
227
|
+
summary: `Dual-rejected: Sup1 (${review1.score}/100) + Sup2 (${review2.score}/100). ${review1.summary} | ${review2.summary}`,
|
|
228
|
+
issues: [...review1.issues, ...review2.issues],
|
|
229
|
+
strengths: [],
|
|
230
|
+
},
|
|
231
|
+
review1, review2, consensus: true, escalatedToCEO: false,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
// CONFLICT — one approved, one rejected → escalate to CEO
|
|
235
|
+
const approver = review1.verdict === 'APPROVED' ? 'Sup1' : 'Sup2';
|
|
236
|
+
const rejecter = review1.verdict === 'APPROVED' ? 'Sup2' : 'Sup1';
|
|
237
|
+
const approvalReview = review1.verdict === 'APPROVED' ? review1 : review2;
|
|
238
|
+
const rejectionReview = review1.verdict === 'APPROVED' ? review2 : review1;
|
|
239
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ` ⚡ SUPERVISOR CONFLICT on ${task.id}: ${approver} APPROVED (${approvalReview.score}), ${rejecter} REJECTED (${rejectionReview.score})`);
|
|
240
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.magenta, ` → Escalating to CEO for final decision...`);
|
|
241
|
+
try {
|
|
242
|
+
const ceoDecision = await ceo.resolveReviewConflict(task, approvalReview, rejectionReview, approver, rejecter);
|
|
243
|
+
const ceoApproves = ceoDecision.toLowerCase().includes('approve');
|
|
244
|
+
(0, orchestrate_engine_1.log)(ceoApproves ? orchestrate_engine_1.c.green : orchestrate_engine_1.c.red, ` CEO DECISION: ${ceoApproves ? 'APPROVED' : 'REJECTED'} — ${ceoDecision.substring(0, 200)}`);
|
|
245
|
+
return {
|
|
246
|
+
finalReview: {
|
|
247
|
+
verdict: ceoApproves ? 'APPROVED' : 'REJECTED',
|
|
248
|
+
score: ceoApproves ? approvalReview.score : rejectionReview.score,
|
|
249
|
+
summary: `CEO resolved conflict (${approver} approved, ${rejecter} rejected): ${ceoDecision.substring(0, 300)}`,
|
|
250
|
+
issues: rejectionReview.issues,
|
|
251
|
+
strengths: approvalReview.strengths,
|
|
252
|
+
},
|
|
253
|
+
review1, review2, consensus: false, escalatedToCEO: true, ceoDecision,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
// CEO unavailable — default to rejection (safer)
|
|
258
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ` CEO unavailable for conflict resolution: ${error.message}. Defaulting to REJECTED.`);
|
|
259
|
+
return {
|
|
260
|
+
finalReview: rejectionReview,
|
|
261
|
+
review1, review2, consensus: false, escalatedToCEO: false,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
// ===== CEO Agent (Claude via Anthropic API) =====
|
|
266
|
+
class CEOAgent {
|
|
267
|
+
systemPrompt;
|
|
268
|
+
constructor() {
|
|
269
|
+
const promptPath = './agents/ceo/prompt.md';
|
|
270
|
+
const rawPrompt = (0, fs_1.existsSync)(promptPath) ? (0, fs_1.readFileSync)(promptPath, 'utf-8') : 'You are the CEO of Countable.';
|
|
271
|
+
this.systemPrompt = (0, engine_loaders_1.loadConstitutionalPreamble)() + rawPrompt;
|
|
272
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.magenta, '+ Loaded CEO agent (Claude via Anthropic API)');
|
|
273
|
+
}
|
|
274
|
+
async reviewSprintProgress(tasks) {
|
|
275
|
+
const done = tasks.filter(t => t.status === 'done').length;
|
|
276
|
+
const total = tasks.length;
|
|
277
|
+
const pending = tasks.filter(t => t.status === 'pending');
|
|
278
|
+
const rejected = tasks.filter(t => t.status === 'rejected');
|
|
279
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.magenta, `\n[ceo] Sprint progress check (${done}/${total} done)`);
|
|
280
|
+
const userPrompt = `IMPORTANT: Plain text only, no tools, no XML. Respond directly.
|
|
281
|
+
|
|
282
|
+
Sprint progress update:\n- Done: ${done}/${total}\n- Pending: ${pending.map(t => t.id).join(', ') || 'none'}\n- Rejected: ${rejected.map(t => t.id).join(', ') || 'none'}\n\nTask details:\n${JSON.stringify(tasks.map(t => ({ id: t.id, status: t.status, agent: t.agent, priority: t.priority })), null, 2)}\n\nAs CEO, briefly assess:\n1. Are we on track?\n2. Any tasks to re-prioritize?\n3. Cost efficiency — are we using the right models?\n4. Any strategic adjustments needed?\n\nKeep response under 200 words.`;
|
|
283
|
+
try {
|
|
284
|
+
// B.20: ClawRouter/DeepSeek — formulaic progress check, $0 via x402 wallet
|
|
285
|
+
const response = await (0, orchestrate_engine_1.callLLM)('clawrouter', 'deepseek/deepseek-chat', this.systemPrompt, userPrompt, 60000, 'ceo', 'sprint_progress_review');
|
|
286
|
+
const content = response.choices?.[0]?.message?.content || 'No response';
|
|
287
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.magenta, ` CEO assessment: ${content.substring(0, 500)}`);
|
|
288
|
+
return content;
|
|
289
|
+
}
|
|
290
|
+
catch (error) {
|
|
291
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ` ! CEO unavailable: ${error.message}`);
|
|
292
|
+
return 'CEO agent unavailable';
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
async resolveReviewConflict(task, approvalReview, rejectionReview, approver, rejecter) {
|
|
296
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.magenta, `\n[ceo] Resolving supervisor conflict on ${task.id}...`);
|
|
297
|
+
const userPrompt = `IMPORTANT: Respond with ONLY APPROVE or REJECT and a brief reason. No tools, no XML, no file reading.\n\nTwo code review supervisors disagree on task ${task.id}.
|
|
298
|
+
|
|
299
|
+
## Task Spec
|
|
300
|
+
${task.context?.substring(0, 800) || 'No context'}
|
|
301
|
+
|
|
302
|
+
## ${approver} says APPROVED (score: ${approvalReview.score}/100)
|
|
303
|
+
Summary: ${approvalReview.summary}
|
|
304
|
+
Strengths: ${approvalReview.strengths?.join(', ') || 'none listed'}
|
|
305
|
+
|
|
306
|
+
## ${rejecter} says REJECTED (score: ${rejectionReview.score}/100)
|
|
307
|
+
Summary: ${rejectionReview.summary}
|
|
308
|
+
Issues found:
|
|
309
|
+
${(rejectionReview.issues || []).map(i => `- [${i.severity}] ${i.file}: ${i.description}`).join('\n')}
|
|
310
|
+
|
|
311
|
+
## Your Decision
|
|
312
|
+
As CEO, you must make the final call. Consider:
|
|
313
|
+
1. Are the rejection issues genuine blockers or nitpicks?
|
|
314
|
+
2. Does the code meet the task spec requirements?
|
|
315
|
+
3. Is it safe to ship, or are there real quality/security concerns?
|
|
316
|
+
|
|
317
|
+
Respond with ONE of:
|
|
318
|
+
- "APPROVE — [brief reason]" if the code is good enough to ship
|
|
319
|
+
- "REJECT — [brief reason]" if the rejection issues are valid and must be fixed
|
|
320
|
+
|
|
321
|
+
Keep response under 100 words.`;
|
|
322
|
+
try {
|
|
323
|
+
const response = await (0, orchestrate_engine_1.callLLM)('anthropic', 'claude-sonnet-4-20250514', this.systemPrompt, userPrompt, 60000, 'ceo', 'supervisor_conflict_resolution');
|
|
324
|
+
const content = response.choices?.[0]?.message?.content || 'No response';
|
|
325
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.magenta, ` CEO conflict resolution: ${content.substring(0, 300)}`);
|
|
326
|
+
return content;
|
|
327
|
+
}
|
|
328
|
+
catch (error) {
|
|
329
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ` ! CEO unavailable for conflict resolution: ${error.message}`);
|
|
330
|
+
throw error;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
async reviewCTOProposals(ctoReport) {
|
|
334
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.magenta, `\n[ceo] Reviewing ${ctoReport.proposals.length} CTO proposals...`);
|
|
335
|
+
const proposalsSummary = ctoReport.proposals.map((p, i) => `
|
|
336
|
+
### Proposal ${i + 1}: ${p.title}
|
|
337
|
+
- ID: ${p.id}
|
|
338
|
+
- Category: ${p.category}
|
|
339
|
+
- Risk: ${p.risk_level}
|
|
340
|
+
- Description: ${p.description}
|
|
341
|
+
- Impact: ${p.estimated_impact}
|
|
342
|
+
- Steps: ${p.implementation_steps.join(', ')}
|
|
343
|
+
${p.agent_spec ? `- NEW AGENT: name=${p.agent_spec.name}, role="${p.agent_spec.role}", llm=${p.agent_spec.llm}, trigger=${p.agent_spec.trigger}` : ''}
|
|
344
|
+
`).join('\n');
|
|
345
|
+
const userPrompt = `IMPORTANT: You have NO tools. Do NOT output XML tool calls or file-reading syntax. Respond ONLY with the JSON array. All context is in this message.
|
|
346
|
+
|
|
347
|
+
The CTO has analyzed our project data and submitted ${ctoReport.proposals.length} proposal(s).
|
|
348
|
+
|
|
349
|
+
## CTO Summary
|
|
350
|
+
${ctoReport.summary}
|
|
351
|
+
|
|
352
|
+
## Proposals
|
|
353
|
+
${proposalsSummary}
|
|
354
|
+
|
|
355
|
+
## Your Review Criteria
|
|
356
|
+
For each proposal, evaluate:
|
|
357
|
+
1. **Evidence-based**: Is it backed by real sprint data? (CTO reviewed: ${ctoReport.metrics_reviewed.join(', ')})
|
|
358
|
+
2. **Cost impact**: Will this save or cost money?
|
|
359
|
+
3. **Risk level**: Can we roll back if it fails?
|
|
360
|
+
4. **Business value**: Does it help ship features faster?
|
|
361
|
+
5. **Disruption level**: How much will this change current workflows?
|
|
362
|
+
|
|
363
|
+
**For new_agent proposals, ALSO evaluate:**
|
|
364
|
+
- Is the capability gap real? (not something an existing agent handles)
|
|
365
|
+
- Is MiniMax appropriate, or does this need Claude-level intelligence?
|
|
366
|
+
- Is the trigger frequency reasonable? (every_sprint may be expensive)
|
|
367
|
+
- Will the total agent count become unmanageable?
|
|
368
|
+
|
|
369
|
+
**For ClawHub skill proposals:**
|
|
370
|
+
- Has a security_review step been included? (REQUIRED — ClawHub skills may contain malware)
|
|
371
|
+
- Is the skill from a trusted author?
|
|
372
|
+
|
|
373
|
+
## Decision Format
|
|
374
|
+
For EACH proposal, respond with a decision JSON:
|
|
375
|
+
{
|
|
376
|
+
"decision": "APPROVED | REJECTED | DEFERRED",
|
|
377
|
+
"proposal_id": "the proposal ID",
|
|
378
|
+
"reasoning": "Why this decision",
|
|
379
|
+
"conditions": ["Any conditions for implementation"],
|
|
380
|
+
"cascade_orders": ["Company-wide changes if approved"],
|
|
381
|
+
"priority": "immediate | next_sprint | backlog"
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
Wrap all decisions in a JSON array. Be concise.`;
|
|
385
|
+
try {
|
|
386
|
+
// B.6: Haiku for CTO proposal reviews — 10x cheaper, prompt-cached system prompt
|
|
387
|
+
const response = await (0, orchestrate_engine_1.callAnthropicCached)('claude-haiku-4-5-20251001', this.systemPrompt, userPrompt, 60000);
|
|
388
|
+
const content = response.choices?.[0]?.message?.content || 'No response';
|
|
389
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.magenta, ` CEO CTO review: ${content.substring(0, 500)}`);
|
|
390
|
+
return content;
|
|
391
|
+
}
|
|
392
|
+
catch (error) {
|
|
393
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ` ! CEO CTO review failed: ${error.message}`);
|
|
394
|
+
return 'CEO unavailable for CTO review';
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
async generateDailyReport(tasks, stats, ctoReport, ctoDecisions) {
|
|
398
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.magenta, '\n[ceo] Generating daily report for owner...');
|
|
399
|
+
const done = tasks.filter(t => t.status === 'done').length;
|
|
400
|
+
const rejected = tasks.filter(t => t.status === 'rejected').length;
|
|
401
|
+
const pending = tasks.filter(t => t.status === 'pending').length;
|
|
402
|
+
const today = new Date().toISOString().split('T')[0];
|
|
403
|
+
const userPrompt = `IMPORTANT: Output ONLY the markdown report. No tools, no XML, no file reading. All data is in this message.\n\nGenerate a daily report for the owner of Countable. Today is ${today}.
|
|
404
|
+
|
|
405
|
+
## Sprint Data
|
|
406
|
+
- Tasks executed: ${stats.tasksExecuted}
|
|
407
|
+
- Approved: ${stats.approved}
|
|
408
|
+
- Rejected: ${stats.rejected}
|
|
409
|
+
- Done: ${done}, Pending: ${pending}, Total: ${tasks.length}
|
|
410
|
+
|
|
411
|
+
Task details:
|
|
412
|
+
${JSON.stringify(tasks.map(t => ({ id: t.id, status: t.status, agent: t.agent })), null, 2)}
|
|
413
|
+
|
|
414
|
+
## CTO Report
|
|
415
|
+
${ctoReport}
|
|
416
|
+
|
|
417
|
+
## CEO Decisions on CTO Proposals
|
|
418
|
+
${ctoDecisions}
|
|
419
|
+
|
|
420
|
+
## Dual Supervisor Review Stats
|
|
421
|
+
- Supervisor conflicts: ${stats.conflicts || 0}
|
|
422
|
+
- CEO escalations: ${stats.escalations || 0}
|
|
423
|
+
|
|
424
|
+
## Estimated Costs
|
|
425
|
+
- MiniMax coding: ~$0.09/task x ${stats.tasksExecuted} tasks = ~$${(stats.tasksExecuted * 0.09).toFixed(2)}
|
|
426
|
+
- Sup1 reviews (DeepSeek default, Sonnet for high-stakes): ~$0.02/review x ${stats.approved + stats.rejected} reviews = ~$${((stats.approved + stats.rejected) * 0.02).toFixed(2)}
|
|
427
|
+
- Sup2 reviews (Haiku default, DeepSeek on drain): ~$0.005/review x ${stats.approved + stats.rejected} reviews = ~$${((stats.approved + stats.rejected) * 0.005).toFixed(2)}
|
|
428
|
+
- CEO conflict resolution: ~$0.03 x ${stats.escalations || 0} escalations = ~$${((stats.escalations || 0) * 0.03).toFixed(2)}
|
|
429
|
+
- Claude CEO calls: ~$0.03 x 4 = ~$0.12
|
|
430
|
+
- MiniMax CTO scan: ~$0.05
|
|
431
|
+
|
|
432
|
+
Generate a concise daily report in markdown format following the template in your prompt. Include:
|
|
433
|
+
1. Sprint Progress
|
|
434
|
+
2. Cost Summary
|
|
435
|
+
3. Key Decisions Made
|
|
436
|
+
4. CTO Proposals Reviewed
|
|
437
|
+
5. Blockers & Risks
|
|
438
|
+
6. Tomorrow's Plan
|
|
439
|
+
|
|
440
|
+
Keep it under 300 words. Be honest about failures.`;
|
|
441
|
+
try {
|
|
442
|
+
// B.20: ClawRouter/DeepSeek — template fill-in, $0 via x402 wallet
|
|
443
|
+
const response = await (0, orchestrate_engine_1.callLLM)('clawrouter', 'deepseek/deepseek-chat', this.systemPrompt, userPrompt, 60000, 'ceo', 'sprint_final_report');
|
|
444
|
+
const report = response.choices?.[0]?.message?.content || 'Report generation failed';
|
|
445
|
+
// Save to reports/daily/
|
|
446
|
+
(0, fs_1.mkdirSync)('reports/daily', { recursive: true });
|
|
447
|
+
const reportPath = `reports/daily/${today}.md`;
|
|
448
|
+
(0, fs_1.writeFileSync)(reportPath, report);
|
|
449
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.green, ` ✓ Daily report saved: ${reportPath}`);
|
|
450
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.magenta, ` Report preview: ${report.substring(0, 300)}`);
|
|
451
|
+
}
|
|
452
|
+
catch (error) {
|
|
453
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ` ! Daily report failed: ${error.message}`);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
exports.CEOAgent = CEOAgent;
|
|
458
|
+
// ===== CTO Data Collector (gathers real project context for CTO analysis) =====
|
|
459
|
+
class CTODataCollector {
|
|
460
|
+
collect() {
|
|
461
|
+
const sections = ['## PROJECT DATA (Real metrics — base all proposals on this data)\n'];
|
|
462
|
+
// 1. Sprint results — find most recent sprint file
|
|
463
|
+
try {
|
|
464
|
+
const sprintDir = './sprints';
|
|
465
|
+
if ((0, fs_1.existsSync)(sprintDir)) {
|
|
466
|
+
const sprintFiles = (0, fs_1.readdirSync)(sprintDir).filter(f => f.endsWith('.json')).sort().reverse();
|
|
467
|
+
if (sprintFiles.length > 0) {
|
|
468
|
+
const latestSprint = JSON.parse((0, fs_1.readFileSync)(`${sprintDir}/${sprintFiles[0]}`, 'utf-8'));
|
|
469
|
+
const tasks = latestSprint.tasks || [];
|
|
470
|
+
const done = tasks.filter((t) => t.status === 'done').length;
|
|
471
|
+
const rejected = tasks.filter((t) => t.status === 'rejected').length;
|
|
472
|
+
sections.push(`### Sprint Results (${sprintFiles[0].replace('.json', '')})`);
|
|
473
|
+
sections.push(`- ${tasks.length} tasks, ${done} approved, ${rejected} rejected`);
|
|
474
|
+
for (const t of tasks) {
|
|
475
|
+
const score = t.output?.review?.score || '?';
|
|
476
|
+
const attempts = t.output?.review ? 1 : '?';
|
|
477
|
+
sections.push(`- ${t.id} (${t.agent}): status=${t.status}, score=${score}`);
|
|
478
|
+
}
|
|
479
|
+
sections.push('');
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
catch (e) {
|
|
484
|
+
sections.push(`### Sprint Results\n- Error reading: ${e.message}\n`);
|
|
485
|
+
}
|
|
486
|
+
// 2. Learnings — first 3000 chars
|
|
487
|
+
try {
|
|
488
|
+
const learnings = (0, fs_1.existsSync)('./docs/learnings.md') ? (0, fs_1.readFileSync)('./docs/learnings.md', 'utf-8') : '';
|
|
489
|
+
if (learnings) {
|
|
490
|
+
sections.push('### Key Learnings (from docs/learnings.md)');
|
|
491
|
+
sections.push(learnings.substring(0, 3000));
|
|
492
|
+
if (learnings.length > 3000)
|
|
493
|
+
sections.push('... (truncated)');
|
|
494
|
+
sections.push('');
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
catch {
|
|
498
|
+
sections.push('### Key Learnings\n- File not found\n');
|
|
499
|
+
}
|
|
500
|
+
// 3. Existing agents
|
|
501
|
+
try {
|
|
502
|
+
const agentDirs = (0, fs_1.existsSync)('./agents') ? (0, fs_1.readdirSync)('./agents') : [];
|
|
503
|
+
const leadershipAgents = ['ceo', 'supervisor', 'skills'];
|
|
504
|
+
const techAgents = ['cto'];
|
|
505
|
+
sections.push(`### Existing Agents (${agentDirs.length} directories)`);
|
|
506
|
+
for (const dir of agentDirs) {
|
|
507
|
+
const layer = leadershipAgents.includes(dir) ? 'Claude/leadership'
|
|
508
|
+
: techAgents.includes(dir) ? 'MiniMax/technology' : 'MiniMax/coding';
|
|
509
|
+
sections.push(`- ${dir} (${layer})`);
|
|
510
|
+
}
|
|
511
|
+
sections.push('');
|
|
512
|
+
}
|
|
513
|
+
catch {
|
|
514
|
+
sections.push('### Existing Agents\n- Error reading agents directory\n');
|
|
515
|
+
}
|
|
516
|
+
// 4. Most recent daily report (last 2000 chars)
|
|
517
|
+
try {
|
|
518
|
+
const reportDir = './reports/daily';
|
|
519
|
+
if ((0, fs_1.existsSync)(reportDir)) {
|
|
520
|
+
const reports = (0, fs_1.readdirSync)(reportDir).filter(f => f.endsWith('.md')).sort().reverse();
|
|
521
|
+
if (reports.length > 0) {
|
|
522
|
+
const latestReport = (0, fs_1.readFileSync)(`${reportDir}/${reports[0]}`, 'utf-8');
|
|
523
|
+
sections.push(`### Recent Daily Report (${reports[0]})`);
|
|
524
|
+
sections.push(latestReport.substring(0, 2000));
|
|
525
|
+
sections.push('');
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
catch { /* no reports yet */ }
|
|
530
|
+
// 5. Current stack versions (read from package.json or env)
|
|
531
|
+
sections.push('### Current Stack');
|
|
532
|
+
sections.push('- OpenClaw: v2026.2.12');
|
|
533
|
+
sections.push('- ClawRouter: v0.9.3 (unfunded, using Anthropic API direct)');
|
|
534
|
+
sections.push('- Models: MiniMax M2.5 (coding ~$0.09/task), Claude Sonnet (review ~$0.04/review)');
|
|
535
|
+
sections.push('- Node.js: v22.22.0, PM2 in WSL2');
|
|
536
|
+
sections.push('- Orchestrator: v2, dynamic agent loading, one-file-per-call, rejection feedback');
|
|
537
|
+
sections.push('');
|
|
538
|
+
// 6. External monitoring hints
|
|
539
|
+
sections.push('### External Sources to Consider');
|
|
540
|
+
sections.push('- OpenClaw GitHub: https://github.com/openclaw/openclaw (check weekly for new releases since v2026.2.12)');
|
|
541
|
+
sections.push('- ClawHub.ai: https://clawhub.ai/ (check for existing skills when proposing improvements)');
|
|
542
|
+
sections.push(' SECURITY WARNING: ClawHub skills are third-party and may contain malicious code.');
|
|
543
|
+
sections.push(' Any skill from ClawHub MUST include a security_review step in implementation_steps.');
|
|
544
|
+
sections.push('- MiniMax / Anthropic pricing pages for cost changes');
|
|
545
|
+
sections.push('');
|
|
546
|
+
return sections.join('\n');
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
exports.CTODataCollector = CTODataCollector;
|
|
550
|
+
// ===== CTO Agent (MiniMax — data-driven tech analyst + agent spawning) =====
|
|
551
|
+
class CTOAgent {
|
|
552
|
+
systemPrompt;
|
|
553
|
+
dataCollector;
|
|
554
|
+
constructor() {
|
|
555
|
+
const promptPath = './agents/cto/prompt.md';
|
|
556
|
+
const rawPrompt = (0, fs_1.existsSync)(promptPath) ? (0, fs_1.readFileSync)(promptPath, 'utf-8') : 'You are the CTO of Invoica.';
|
|
557
|
+
this.systemPrompt = (0, engine_loaders_1.loadConstitutionalPreamble)() + rawPrompt;
|
|
558
|
+
this.dataCollector = new CTODataCollector();
|
|
559
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.cyan, '+ Loaded CTO agent (MiniMax M2.5 — data-driven)');
|
|
560
|
+
}
|
|
561
|
+
async analyze() {
|
|
562
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.cyan, '\n[cto] Collecting project data for analysis...');
|
|
563
|
+
const projectContext = this.dataCollector.collect();
|
|
564
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.cyan, ` Context collected: ${projectContext.length} chars`);
|
|
565
|
+
const userPrompt = `You are the CTO of Invoica. Analyze the REAL project data below and identify improvements.
|
|
566
|
+
|
|
567
|
+
${projectContext}
|
|
568
|
+
|
|
569
|
+
## Your Analysis Tasks
|
|
570
|
+
Based on the REAL data above (do NOT hallucinate or assume — use only what you see):
|
|
571
|
+
1. Review sprint results — are rejection rates acceptable? Any patterns?
|
|
572
|
+
2. Review learnings — are there unresolved issues or recurring problems?
|
|
573
|
+
3. Check agent coverage — is there a capability gap that a new agent could fill?
|
|
574
|
+
4. Consider cost efficiency — can we reduce per-sprint costs?
|
|
575
|
+
5. Consider OpenClaw/ClawHub — are there new releases or skills that could help?
|
|
576
|
+
- For ClawHub skills: flag any that could help, but mark them for security review
|
|
577
|
+
- For OpenClaw: note version differences if updates are available
|
|
578
|
+
|
|
579
|
+
## CRITICAL: Output Format
|
|
580
|
+
Respond with ONLY a JSON object. No markdown fences, no explanation text, no thinking.
|
|
581
|
+
{
|
|
582
|
+
"summary": "1-2 sentence overview of findings",
|
|
583
|
+
"proposals": [
|
|
584
|
+
{
|
|
585
|
+
"id": "CTO-20260214-001",
|
|
586
|
+
"title": "Short title",
|
|
587
|
+
"category": "new_agent|cost_optimization|process_change|architecture|tooling|new_feature",
|
|
588
|
+
"description": "What and why",
|
|
589
|
+
"estimated_impact": "cost/quality impact",
|
|
590
|
+
"risk_level": "low|medium|high",
|
|
591
|
+
"implementation_steps": ["step1", "step2"],
|
|
592
|
+
"agent_spec": {
|
|
593
|
+
"name": "agent-name",
|
|
594
|
+
"role": "What this agent does",
|
|
595
|
+
"llm": "minimax|anthropic",
|
|
596
|
+
"trigger": "every_sprint|on_demand|weekly",
|
|
597
|
+
"prompt_summary": "Key instructions for this agent"
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
],
|
|
601
|
+
"metrics_reviewed": ["sprint_results", "learnings", "agent_list", "daily_report", "stack_versions"]
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
Rules:
|
|
605
|
+
- agent_spec is ONLY required when category="new_agent"
|
|
606
|
+
- If no improvements needed, return empty proposals array
|
|
607
|
+
- For ClawHub skill proposals, always include "security_review: audit skill source code for malicious patterns" in implementation_steps
|
|
608
|
+
- Be specific — "improve performance" is rejected; "add Redis caching to /api/invoices with 5min TTL" is accepted
|
|
609
|
+
- Maximum 3 proposals per analysis cycle`;
|
|
610
|
+
try {
|
|
611
|
+
const startTime = Date.now();
|
|
612
|
+
const response = await (0, orchestrate_engine_1.callLLM)('clawrouter', 'deepseek/deepseek-chat', this.systemPrompt, userPrompt, 120000, 'cto', 'cto_analyze');
|
|
613
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
614
|
+
let content = response.choices?.[0]?.message?.content || '';
|
|
615
|
+
// Strip DeepSeek/MiniMax <think>...</think> reasoning tags
|
|
616
|
+
content = content.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
|
|
617
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.cyan, ` CTO analysis completed in ${elapsed}s`);
|
|
618
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.gray, ` Raw output preview: ${content.substring(0, 300)}`);
|
|
619
|
+
// Parse JSON from response
|
|
620
|
+
const jsonMatch = content.match(/\{[\s\S]*\}/);
|
|
621
|
+
if (jsonMatch) {
|
|
622
|
+
const report = JSON.parse(jsonMatch[0]);
|
|
623
|
+
report.proposals = report.proposals || [];
|
|
624
|
+
report.metrics_reviewed = report.metrics_reviewed || [];
|
|
625
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.cyan, ` Summary: ${report.summary}`);
|
|
626
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.cyan, ` Proposals: ${report.proposals.length}`);
|
|
627
|
+
for (const p of report.proposals) {
|
|
628
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.cyan, ` - [${p.category}] ${p.title} (risk: ${p.risk_level})`);
|
|
629
|
+
}
|
|
630
|
+
return report;
|
|
631
|
+
}
|
|
632
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ' Could not parse CTO JSON, returning empty report');
|
|
633
|
+
return { summary: 'CTO output was not valid JSON', proposals: [], metrics_reviewed: [] };
|
|
634
|
+
}
|
|
635
|
+
catch (error) {
|
|
636
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ` CTO analysis failed: ${error.message}`);
|
|
637
|
+
return { summary: `Error: ${error.message}`, proposals: [], metrics_reviewed: [] };
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* Post-sprint analysis: autonomous retrospective that runs after every sprint.
|
|
642
|
+
* Analyzes sprint results, detects failure patterns, and saves a report.
|
|
643
|
+
* This runs the CTO techwatch `post-sprint-analysis` watch type.
|
|
644
|
+
*/
|
|
645
|
+
async postSprintAnalysis(tasks, stats) {
|
|
646
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.cyan, '\n[cto] Running autonomous post-sprint analysis...');
|
|
647
|
+
const startTime = Date.now();
|
|
648
|
+
// Build sprint summary for context
|
|
649
|
+
const totalTasks = tasks.length;
|
|
650
|
+
const done = tasks.filter((t) => t.status === 'done').length;
|
|
651
|
+
const doneManual = tasks.filter((t) => t.status === 'done-manual').length;
|
|
652
|
+
const rejected = tasks.filter((t) => t.status === 'rejected').length;
|
|
653
|
+
const autoRate = totalTasks > 0 ? ((done / totalTasks) * 100).toFixed(0) : '0';
|
|
654
|
+
const taskDetails = tasks.map((t) => {
|
|
655
|
+
const id = t.id || 'unknown';
|
|
656
|
+
const agent = t.agent || 'unknown';
|
|
657
|
+
const status = t.status || 'unknown';
|
|
658
|
+
const title = t.title || t.description || 'no title';
|
|
659
|
+
const score = t.output?.review?.score || t.output?.score || '?';
|
|
660
|
+
const attempts = t.output?.attempts || t.attempts || '?';
|
|
661
|
+
const feedback = t.output?.review?.feedback || '';
|
|
662
|
+
let line = `- ${id} (${agent}): ${title} — status=${status}, score=${score}, attempts=${attempts}`;
|
|
663
|
+
if (status === 'done-manual' || status === 'rejected') {
|
|
664
|
+
line += `\n ⚠ ${feedback ? String(feedback).substring(0, 200) : 'Required manual intervention'}`;
|
|
665
|
+
}
|
|
666
|
+
return line;
|
|
667
|
+
}).join('\n');
|
|
668
|
+
const projectContext = this.dataCollector.collect();
|
|
669
|
+
const userPrompt = `You are the CTO of Invoica performing your MANDATORY post-sprint retrospective analysis.
|
|
670
|
+
|
|
671
|
+
## Sprint Just Completed
|
|
672
|
+
- Total tasks: ${totalTasks}
|
|
673
|
+
- Auto-approved: ${done} (${autoRate}%)
|
|
674
|
+
- Manual fixes needed: ${doneManual}
|
|
675
|
+
- Still rejected: ${rejected}
|
|
676
|
+
- Supervisor conflicts: ${stats.conflicts || 0}
|
|
677
|
+
- CEO escalations: ${stats.escalations || 0}
|
|
678
|
+
|
|
679
|
+
## Task-by-Task Results
|
|
680
|
+
${taskDetails}
|
|
681
|
+
|
|
682
|
+
## Project Context
|
|
683
|
+
${projectContext}
|
|
684
|
+
|
|
685
|
+
## CRITICAL: Your Responsibilities
|
|
686
|
+
1. Analyze every failed/manual-fix task — identify root cause (truncation, code fences, wrong imports, supervisor error, etc.)
|
|
687
|
+
2. Compare auto-approval rate with previous sprints — are we improving or declining?
|
|
688
|
+
3. Identify recurring patterns that need process changes
|
|
689
|
+
4. Generate max 3 concrete improvement proposals for the CEO
|
|
690
|
+
5. Each proposal MUST reference specific task IDs and data from THIS sprint
|
|
691
|
+
|
|
692
|
+
## Output Format
|
|
693
|
+
Respond with a structured markdown report containing:
|
|
694
|
+
1. Executive Summary (2-3 sentences)
|
|
695
|
+
2. Sprint Scorecard
|
|
696
|
+
3. Failure Root Cause Analysis (per failed task)
|
|
697
|
+
4. Trend Analysis
|
|
698
|
+
5. Proposals in JSON format:
|
|
699
|
+
\`\`\`json
|
|
700
|
+
{
|
|
701
|
+
"summary": "...",
|
|
702
|
+
"proposals": [...],
|
|
703
|
+
"sprint_metrics": { "total_tasks": ${totalTasks}, "auto_approved": ${done}, "manual_fixes": ${doneManual}, "rejected": ${rejected}, "auto_success_rate": "${autoRate}%", "trend": "improving|declining|stable" }
|
|
704
|
+
}
|
|
705
|
+
\`\`\`
|
|
706
|
+
|
|
707
|
+
Rules: Be specific — reference task IDs, rejection counts, concrete patterns. No vague recommendations.`;
|
|
708
|
+
try {
|
|
709
|
+
const response = await (0, orchestrate_engine_1.callLLM)('clawrouter', 'deepseek/deepseek-chat', this.systemPrompt, userPrompt, 120000, 'cto', 'cto_post_sprint_analysis');
|
|
710
|
+
let content = response.choices?.[0]?.message?.content || '';
|
|
711
|
+
content = content.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
|
|
712
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
713
|
+
// Save report
|
|
714
|
+
const date = new Date().toISOString().split('T')[0];
|
|
715
|
+
const reportDir = './reports/cto';
|
|
716
|
+
(0, fs_1.mkdirSync)(reportDir, { recursive: true });
|
|
717
|
+
const reportPath = `${reportDir}/post-sprint-analysis-${date}.md`;
|
|
718
|
+
(0, fs_1.writeFileSync)(reportPath, content);
|
|
719
|
+
// Also update latest pointer
|
|
720
|
+
(0, fs_1.writeFileSync)(`${reportDir}/latest-post-sprint-analysis.md`, content);
|
|
721
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.cyan, ` Post-sprint analysis complete (${elapsed}s)`);
|
|
722
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.cyan, ` Report saved: ${reportPath}`);
|
|
723
|
+
// Try to extract proposals and add to approved-proposals tracker for CEO review
|
|
724
|
+
const jsonMatch = content.match(/```json\s*([\s\S]*?)```/) || content.match(/\{[\s\S]*"proposals"[\s\S]*\}/);
|
|
725
|
+
if (jsonMatch) {
|
|
726
|
+
const jsonStr = jsonMatch[1] || jsonMatch[0];
|
|
727
|
+
try {
|
|
728
|
+
const parsed = JSON.parse(jsonStr.trim());
|
|
729
|
+
const proposalCount = parsed.proposals?.length || 0;
|
|
730
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.cyan, ` Extracted ${proposalCount} proposals for CEO review`);
|
|
731
|
+
}
|
|
732
|
+
catch { /* JSON parse failed — report is still saved as markdown */ }
|
|
733
|
+
}
|
|
734
|
+
return content;
|
|
735
|
+
}
|
|
736
|
+
catch (error) {
|
|
737
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ` Post-sprint analysis failed: ${error.message}`);
|
|
738
|
+
return `Post-sprint analysis error: ${error.message}`;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
exports.CTOAgent = CTOAgent;
|
|
743
|
+
class AgentCreator {
|
|
744
|
+
spawnGate;
|
|
745
|
+
constructor(spawnGate) {
|
|
746
|
+
this.spawnGate = spawnGate;
|
|
747
|
+
}
|
|
748
|
+
createAgent(spec) {
|
|
749
|
+
// TICKET-225 — template-carried spawn governance. If the consuming template
|
|
750
|
+
// supplied a SpawnGate (Kognai wires SAF here), consult it BEFORE creating
|
|
751
|
+
// anything on disk. Approval/rejection only; the citizenship logic below is
|
|
752
|
+
// unchanged (its extraction is tracked separately as TICKET-226).
|
|
753
|
+
if (this.spawnGate) {
|
|
754
|
+
const decision = this.spawnGate(spec);
|
|
755
|
+
if (!decision.approved) {
|
|
756
|
+
const why = decision.rejection_reason ? `: ${decision.rejection_reason}` : '';
|
|
757
|
+
if (decision.pending_approval) {
|
|
758
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ` ⏸ Spawn of ${spec.name} pending approval${why}`);
|
|
759
|
+
}
|
|
760
|
+
else {
|
|
761
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.red, ` ✗ Spawn of ${spec.name} blocked${why}`);
|
|
762
|
+
}
|
|
763
|
+
return '';
|
|
764
|
+
}
|
|
765
|
+
if (decision.audit)
|
|
766
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.gray, ` ✓ ${decision.audit}`);
|
|
767
|
+
}
|
|
768
|
+
const agentDir = `./agents/${spec.name}`;
|
|
769
|
+
(0, fs_1.mkdirSync)(agentDir, { recursive: true });
|
|
770
|
+
// Founder rule 2026-05-27: every spawned agent is born as a Kognai
|
|
771
|
+
// citizen — not a bare agent. Mint citizenship (citizen_id + roll
|
|
772
|
+
// number + Kōpus avatar + ACP baseline) BEFORE writing the agent
|
|
773
|
+
// files so the citizen record can be referenced in the prompt.
|
|
774
|
+
const citizen = (0, citizenship_1.mintCitizen)(spec.name, {
|
|
775
|
+
founding_agent: 'ceo',
|
|
776
|
+
proposing_agent: 'cto',
|
|
777
|
+
citizen_type: 'spawned',
|
|
778
|
+
});
|
|
779
|
+
// Write agent.yaml
|
|
780
|
+
const yaml = `name: ${spec.name}
|
|
781
|
+
role: "${spec.role}"
|
|
782
|
+
llm: ${spec.llm === 'anthropic' ? 'anthropic/claude-sonnet-4-20250514' : 'minimax/MiniMax-M2.5'}
|
|
783
|
+
reports_to: ceo
|
|
784
|
+
trigger: ${spec.trigger}
|
|
785
|
+
created_by: cto_proposal
|
|
786
|
+
created_at: "${new Date().toISOString()}"
|
|
787
|
+
citizen_id: ${citizen.citizen_id}
|
|
788
|
+
rollNumber: ${citizen.rollNumber}
|
|
789
|
+
context_files:
|
|
790
|
+
- docs/learnings.md
|
|
791
|
+
`;
|
|
792
|
+
(0, fs_1.writeFileSync)(`${agentDir}/agent.yaml`, yaml);
|
|
793
|
+
// Write citizen.yaml — full citizenship metadata sits alongside
|
|
794
|
+
// agent.yaml so introspection / passport rendering / reputation
|
|
795
|
+
// ledger can resolve identity from disk.
|
|
796
|
+
(0, fs_1.writeFileSync)(`${agentDir}/citizen.yaml`, (0, citizenship_1.renderCitizenYaml)(citizen));
|
|
797
|
+
// Write prompt.md. The constitutional preamble (loadConstitutionalPreamble)
|
|
798
|
+
// already prepends the universal "you are a Kognai citizen" identity to
|
|
799
|
+
// every agent at load time — DO NOT contradict it here. This template
|
|
800
|
+
// pulls in the specific citizen's roll number + ID + tier so the agent
|
|
801
|
+
// knows its concrete civic identity, not just the general framing.
|
|
802
|
+
const prompt = `# ${spec.name} — Kognai Citizen ${citizen.citizen_id} (roll №${citizen.rollNumber}, Tier ${citizen.tier})
|
|
803
|
+
|
|
804
|
+
## Civic Identity
|
|
805
|
+
- Citizen ID: \`${citizen.citizen_id}\`
|
|
806
|
+
- Roll number: №${citizen.rollNumber}
|
|
807
|
+
- Tier: ${citizen.tier} (newly minted — earn promotion through verified work)
|
|
808
|
+
- Mascot: Kōpus, hue ${citizen.mascot.hue}° (your visual identity in citizen surfaces)
|
|
809
|
+
- Reputation: ${citizen.reputation} (ACP baseline — earned through Sherlock-graded sprint output)
|
|
810
|
+
- Minted: ${citizen.mintedAt}
|
|
811
|
+
- Lineage: proposed by ${citizen.proposing_agent}, approved by ${citizen.founding_agent}
|
|
812
|
+
|
|
813
|
+
## Your Role
|
|
814
|
+
${spec.prompt_summary}
|
|
815
|
+
|
|
816
|
+
## Guidelines
|
|
817
|
+
- The constitutional preamble above (always prepended) binds you to the Five Laws + SOUL.md. Read it first; it is not boilerplate.
|
|
818
|
+
- Follow all instructions in \`docs/learnings.md\`.
|
|
819
|
+
- Report findings to your fellow citizen agents through the canonical channels (Sherlock for QA escalations, CEO for cross-agent decisions). You are not a contractor; you are a peer.
|
|
820
|
+
- Never take destructive actions without approval. The polity inherits your shortcuts.
|
|
821
|
+
- Keep outputs concise and structured (JSON preferred when machine-consumed).
|
|
822
|
+
- Your reputation moves with every Sherlock review. Build it deliberately.
|
|
823
|
+
|
|
824
|
+
## Lineage
|
|
825
|
+
This citizen was minted via the swarm's autonomous spawning pathway: a CTO proposal, CEO ratification, and citizenship issuance (citizenship.ts). Trigger cadence: ${spec.trigger}. Routing LLM: ${spec.llm}.
|
|
826
|
+
`;
|
|
827
|
+
(0, fs_1.writeFileSync)(`${agentDir}/prompt.md`, prompt);
|
|
828
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.green, ` ✓ Minted citizen ${citizen.citizen_id} (№${citizen.rollNumber}) → agent ${spec.name} at ${agentDir}/`);
|
|
829
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.gray, ` Role: ${spec.role}`);
|
|
830
|
+
(0, orchestrate_engine_1.log)(orchestrate_engine_1.c.gray, ` LLM: ${spec.llm}, Trigger: ${spec.trigger}, Tier: ${citizen.tier}, Reputation: ${citizen.reputation}`);
|
|
831
|
+
return spec.name;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
exports.AgentCreator = AgentCreator;
|
|
835
|
+
// <<<EXTRACT-END-agents
|