@kernel.chat/kbot 3.95.0 → 3.97.1
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 +3 -3
- package/dist/agent.js +30 -0
- package/dist/cli.js +1 -1
- package/dist/coordinator.d.ts +164 -0
- package/dist/coordinator.js +839 -0
- package/dist/doctor.js +5 -4
- package/dist/share.js +1 -1
- package/dist/streaming.js +1 -1
- package/dist/tools/audio-engine.d.ts +76 -0
- package/dist/tools/audio-engine.js +583 -24
- package/dist/tools/audit.js +2 -2
- package/dist/tools/containers.js +75 -14
- package/dist/tools/index.js +6 -0
- package/dist/tools/sprite-engine.d.ts +18 -0
- package/dist/tools/sprite-engine.js +435 -1
- package/dist/tools/stream-brain.js +1 -1
- package/dist/tools/stream-character.js +4 -4
- package/dist/tools/stream-chat-ai.d.ts +56 -0
- package/dist/tools/stream-chat-ai.js +625 -0
- package/dist/tools/stream-commands.d.ts +91 -0
- package/dist/tools/stream-commands.js +911 -0
- package/dist/tools/stream-intelligence.js +2 -2
- package/dist/tools/stream-overlay.d.ts +53 -0
- package/dist/tools/stream-overlay.js +494 -0
- package/dist/tools/stream-renderer.js +706 -107
- package/dist/tools/stream-vod.d.ts +60 -0
- package/dist/tools/stream-vod.js +449 -0
- package/dist/tools/stream-weather.d.ts +79 -0
- package/dist/tools/stream-weather.js +811 -0
- package/dist/tools/tile-world.d.ts +6 -0
- package/dist/tools/tile-world.js +3 -3
- package/dist/ui.js +23 -21
- package/dist/watcher.d.ts +16 -0
- package/dist/watcher.js +111 -0
- package/package.json +5 -4
|
@@ -0,0 +1,839 @@
|
|
|
1
|
+
// kbot Intelligence Coordinator — Unified Brain
|
|
2
|
+
//
|
|
3
|
+
// Orchestrates kbot's 14 intelligence systems into a coherent cognitive loop.
|
|
4
|
+
// Called before, during, and after each agent interaction.
|
|
5
|
+
//
|
|
6
|
+
// All module imports are LAZY (dynamic) to avoid circular dependency issues.
|
|
7
|
+
// State persists to ~/.kbot/coordinator-state.json.
|
|
8
|
+
import { homedir } from 'node:os';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
11
|
+
import chalk from 'chalk';
|
|
12
|
+
// ── Lazy module loaders (dynamic imports to break circular deps) ──
|
|
13
|
+
async function getLearning() { return import('./learning.js'); }
|
|
14
|
+
async function getConfidence() { return import('./confidence.js'); }
|
|
15
|
+
async function getReasoning() { return import('./reasoning.js'); }
|
|
16
|
+
async function getGraphMemory() { return import('./graph-memory.js'); }
|
|
17
|
+
async function getLearnedRouter() { return import('./learned-router.js'); }
|
|
18
|
+
async function getIntentionality() { return import('./intentionality.js'); }
|
|
19
|
+
async function getTemporal() { return import('./temporal.js'); }
|
|
20
|
+
async function getBehaviour() { return import('./behaviour.js'); }
|
|
21
|
+
async function getEmergent() {
|
|
22
|
+
try {
|
|
23
|
+
// emergent.ts doesn't exist yet — will be created when emergent module is built
|
|
24
|
+
// Using indirect import to prevent TypeScript from resolving at compile time
|
|
25
|
+
const path = './tools/emergent' + '.js';
|
|
26
|
+
return await import(/* @vite-ignore */ path);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// ── Defaults ──
|
|
33
|
+
const DEFAULT_CONFIDENCE_THRESHOLD = 0.4;
|
|
34
|
+
const CONSOLIDATION_INTERVAL = 10; // every N interactions
|
|
35
|
+
const MAX_EVAL_HISTORY = 200;
|
|
36
|
+
const MAX_INSIGHTS = 100;
|
|
37
|
+
const MAX_CONFLICTS = 50;
|
|
38
|
+
const MAX_GOALS = 20;
|
|
39
|
+
function defaultState() {
|
|
40
|
+
return {
|
|
41
|
+
lastPolicy: 'balanced',
|
|
42
|
+
confidenceThreshold: DEFAULT_CONFIDENCE_THRESHOLD,
|
|
43
|
+
totalInteractions: 0,
|
|
44
|
+
successRate: 0.5,
|
|
45
|
+
activeGoals: [],
|
|
46
|
+
recentInsights: [],
|
|
47
|
+
conflictLog: [],
|
|
48
|
+
evalHistory: [],
|
|
49
|
+
patternsLearnedToday: 0,
|
|
50
|
+
patternsLearnedDate: new Date().toISOString().slice(0, 10),
|
|
51
|
+
routingAccuracy: 0.5,
|
|
52
|
+
lastConsolidation: null,
|
|
53
|
+
startedAt: new Date().toISOString(),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
// ── Persistence helpers ──
|
|
57
|
+
const KBOT_DIR = join(homedir(), '.kbot');
|
|
58
|
+
const STATE_FILE = join(KBOT_DIR, 'coordinator-state.json');
|
|
59
|
+
function ensureDir() {
|
|
60
|
+
if (!existsSync(KBOT_DIR))
|
|
61
|
+
mkdirSync(KBOT_DIR, { recursive: true });
|
|
62
|
+
}
|
|
63
|
+
function loadState() {
|
|
64
|
+
ensureDir();
|
|
65
|
+
if (!existsSync(STATE_FILE))
|
|
66
|
+
return defaultState();
|
|
67
|
+
try {
|
|
68
|
+
return { ...defaultState(), ...JSON.parse(readFileSync(STATE_FILE, 'utf-8')) };
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return defaultState();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function saveState(state) {
|
|
75
|
+
ensureDir();
|
|
76
|
+
try {
|
|
77
|
+
writeFileSync(STATE_FILE, JSON.stringify(state, null, 2), 'utf-8');
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// best-effort — coordinator state can be regenerated
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// ── Utility ──
|
|
84
|
+
function shortHash(s) {
|
|
85
|
+
let hash = 0;
|
|
86
|
+
for (let i = 0; i < s.length; i++) {
|
|
87
|
+
hash = ((hash << 5) - hash + s.charCodeAt(i)) | 0;
|
|
88
|
+
}
|
|
89
|
+
return Math.abs(hash).toString(36).slice(0, 8);
|
|
90
|
+
}
|
|
91
|
+
function shortId() {
|
|
92
|
+
return Date.now().toString(36) + Math.random().toString(36).slice(2, 6);
|
|
93
|
+
}
|
|
94
|
+
function todayStr() {
|
|
95
|
+
return new Date().toISOString().slice(0, 10);
|
|
96
|
+
}
|
|
97
|
+
// ── IntelligenceCoordinator ──
|
|
98
|
+
export class IntelligenceCoordinator {
|
|
99
|
+
state;
|
|
100
|
+
anticipatedTools = [];
|
|
101
|
+
currentSessionId = null;
|
|
102
|
+
pendingRouteAgent = null;
|
|
103
|
+
initTime = Date.now();
|
|
104
|
+
constructor() {
|
|
105
|
+
this.state = loadState();
|
|
106
|
+
// Reset daily pattern counter if it's a new day
|
|
107
|
+
if (this.state.patternsLearnedDate !== todayStr()) {
|
|
108
|
+
this.state.patternsLearnedToday = 0;
|
|
109
|
+
this.state.patternsLearnedDate = todayStr();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// ── Phase 1: Pre-Execution (before LLM API call) ──
|
|
113
|
+
async preProcess(message, sessionId) {
|
|
114
|
+
this.currentSessionId = sessionId;
|
|
115
|
+
this.state.totalInteractions++;
|
|
116
|
+
const result = {
|
|
117
|
+
agent: null,
|
|
118
|
+
confidence: 0.5,
|
|
119
|
+
graphContext: '',
|
|
120
|
+
reasoning: '',
|
|
121
|
+
toolHints: [],
|
|
122
|
+
systemPromptAddition: '',
|
|
123
|
+
needsClarification: false,
|
|
124
|
+
drives: null,
|
|
125
|
+
anticipation: null,
|
|
126
|
+
};
|
|
127
|
+
// 1. Route message through learned-router
|
|
128
|
+
try {
|
|
129
|
+
const router = await getLearnedRouter();
|
|
130
|
+
const route = router.learnedRoute(message);
|
|
131
|
+
if (route) {
|
|
132
|
+
result.agent = route.agent;
|
|
133
|
+
result.confidence = route.confidence;
|
|
134
|
+
this.pendingRouteAgent = route.agent;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
catch { /* non-critical */ }
|
|
138
|
+
// 2. Check confidence — if below threshold, flag for clarification
|
|
139
|
+
try {
|
|
140
|
+
const conf = await getConfidence();
|
|
141
|
+
const score = conf.estimateConfidence(message, '');
|
|
142
|
+
result.confidence = Math.max(result.confidence, score.overall);
|
|
143
|
+
if (score.overall < this.state.confidenceThreshold) {
|
|
144
|
+
result.needsClarification = true;
|
|
145
|
+
result.clarificationReason = score.reasoning || 'Low confidence — consider asking for more details';
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
catch { /* non-critical */ }
|
|
149
|
+
// 3. Query graph-memory for relevant context
|
|
150
|
+
try {
|
|
151
|
+
const graph = await getGraphMemory();
|
|
152
|
+
const contextStr = graph.toContext(1500);
|
|
153
|
+
if (contextStr && contextStr.length > 10) {
|
|
154
|
+
result.graphContext = contextStr;
|
|
155
|
+
}
|
|
156
|
+
// Also find nodes related to message keywords
|
|
157
|
+
const found = graph.findNode(message.slice(0, 100));
|
|
158
|
+
if (found.length > 0) {
|
|
159
|
+
const entityNames = found.slice(0, 5).map(n => n.name).join(', ');
|
|
160
|
+
result.graphContext += `\nRelated entities: ${entityNames}`;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
catch { /* non-critical */ }
|
|
164
|
+
// 4. Run abductive reasoning to infer intent
|
|
165
|
+
try {
|
|
166
|
+
const reasoning = await getReasoning();
|
|
167
|
+
const strategy = reasoning.selectStrategy(message, '');
|
|
168
|
+
result.reasoning = strategy.reasoning || strategy.chosenStrategy;
|
|
169
|
+
}
|
|
170
|
+
catch { /* non-critical */ }
|
|
171
|
+
// 5. Check intentionality drives
|
|
172
|
+
try {
|
|
173
|
+
const intent = await getIntentionality();
|
|
174
|
+
const drives = intent.getDriveState();
|
|
175
|
+
if (drives && drives.drives && drives.drives.length > 0) {
|
|
176
|
+
const top = drives.drives.reduce((a, b) => (b.weight * (1 - b.currentSatisfaction)) > (a.weight * (1 - a.currentSatisfaction)) ? b : a);
|
|
177
|
+
result.drives = { dominant: top.name, level: top.weight };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
catch { /* non-critical */ }
|
|
181
|
+
// 6. Anticipate what tools will be needed (temporal)
|
|
182
|
+
try {
|
|
183
|
+
const temporal = await getTemporal();
|
|
184
|
+
const anticipated = temporal.anticipateNext([message], message);
|
|
185
|
+
if (anticipated && anticipated.length > 0 && anticipated[0].prediction) {
|
|
186
|
+
result.anticipation = anticipated[0].prediction;
|
|
187
|
+
result.toolHints = anticipated[0].preparation || [];
|
|
188
|
+
this.anticipatedTools = result.toolHints;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
catch { /* non-critical */ }
|
|
192
|
+
// 7. Load behaviour rules for system prompt
|
|
193
|
+
try {
|
|
194
|
+
const behaviour = await getBehaviour();
|
|
195
|
+
const prompt = behaviour.getBehaviourPrompt();
|
|
196
|
+
if (prompt && prompt.length > 5) {
|
|
197
|
+
result.systemPromptAddition = prompt;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
catch { /* non-critical */ }
|
|
201
|
+
// 8. Synthesize policy from signals
|
|
202
|
+
this.state.lastPolicy = this.synthesizePolicy(result.confidence);
|
|
203
|
+
// Add policy hint to system prompt
|
|
204
|
+
if (this.state.lastPolicy === 'explore') {
|
|
205
|
+
result.systemPromptAddition += '\n\nNote: Consider unconventional approaches. The user may benefit from a different perspective.';
|
|
206
|
+
}
|
|
207
|
+
else if (this.state.lastPolicy === 'exploit') {
|
|
208
|
+
result.systemPromptAddition += '\n\nNote: Use the most reliable, proven approach. The user needs a direct solution.';
|
|
209
|
+
}
|
|
210
|
+
// Trim empty lines from system prompt addition
|
|
211
|
+
result.systemPromptAddition = result.systemPromptAddition.trim();
|
|
212
|
+
this.save();
|
|
213
|
+
return result;
|
|
214
|
+
}
|
|
215
|
+
// ── Phase 2: Tool Oversight (before each tool execution) ──
|
|
216
|
+
evaluateToolCall(toolName, args, _context) {
|
|
217
|
+
const evaluation = {
|
|
218
|
+
allow: true,
|
|
219
|
+
anticipated: false,
|
|
220
|
+
};
|
|
221
|
+
// 1. Check if tool matches anticipated needs
|
|
222
|
+
if (this.anticipatedTools.length > 0) {
|
|
223
|
+
evaluation.anticipated = this.anticipatedTools.some(hint => toolName.includes(hint) || hint.includes(toolName));
|
|
224
|
+
}
|
|
225
|
+
// 2. Confidence-gate: warn if success rate is low for this tool pattern
|
|
226
|
+
// We check the eval history for similar tool usage patterns
|
|
227
|
+
const recentEvals = this.state.evalHistory.slice(-20);
|
|
228
|
+
if (recentEvals.length >= 5) {
|
|
229
|
+
const avgScore = recentEvals.reduce((s, e) => s + e.score, 0) / recentEvals.length;
|
|
230
|
+
if (avgScore < 0.3) {
|
|
231
|
+
evaluation.warn = `Recent interaction quality is low (${(avgScore * 100).toFixed(0)}%). Consider verifying approach.`;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
// 3. Check behaviour rules for restrictions (synchronous)
|
|
235
|
+
// We do a lightweight check against known restricted patterns
|
|
236
|
+
try {
|
|
237
|
+
// Destructive tool patterns that should trigger caution
|
|
238
|
+
const cautionPatterns = [
|
|
239
|
+
'delete', 'remove', 'drop', 'destroy', 'force', 'reset',
|
|
240
|
+
'truncate', 'wipe', 'purge', 'nuke',
|
|
241
|
+
];
|
|
242
|
+
const toolLower = toolName.toLowerCase();
|
|
243
|
+
const argsStr = JSON.stringify(args).toLowerCase();
|
|
244
|
+
const isDestructive = cautionPatterns.some(p => toolLower.includes(p) || argsStr.includes(p));
|
|
245
|
+
if (isDestructive && !evaluation.warn) {
|
|
246
|
+
evaluation.warn = `Tool "${toolName}" appears destructive. Verify intent.`;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
catch { /* non-critical */ }
|
|
250
|
+
// 4. Log tool usage to graph-memory (async, fire-and-forget)
|
|
251
|
+
this.logToolToGraph(toolName, args).catch(() => { });
|
|
252
|
+
return evaluation;
|
|
253
|
+
}
|
|
254
|
+
// ── Phase 3: Post-Response Self-Evaluation ──
|
|
255
|
+
async postProcess(message, response, toolsUsed, sessionId) {
|
|
256
|
+
const result = {
|
|
257
|
+
score: 0.5,
|
|
258
|
+
patternsExtracted: 0,
|
|
259
|
+
insightsGenerated: 0,
|
|
260
|
+
graphUpdates: 0,
|
|
261
|
+
consolidationTriggered: false,
|
|
262
|
+
};
|
|
263
|
+
// 1. Self-evaluate: was the response helpful? (heuristic, no LLM call)
|
|
264
|
+
result.score = this.selfEvaluate(message, response, toolsUsed);
|
|
265
|
+
// Record the evaluation
|
|
266
|
+
const evalEntry = {
|
|
267
|
+
sessionId,
|
|
268
|
+
messageHash: shortHash(message),
|
|
269
|
+
score: result.score,
|
|
270
|
+
toolSuccessRate: toolsUsed.length > 0 ? result.score : 1,
|
|
271
|
+
responseAppropriate: result.score >= 0.4,
|
|
272
|
+
patternsMatched: 0,
|
|
273
|
+
timestamp: new Date().toISOString(),
|
|
274
|
+
};
|
|
275
|
+
this.state.evalHistory.push(evalEntry);
|
|
276
|
+
if (this.state.evalHistory.length > MAX_EVAL_HISTORY) {
|
|
277
|
+
this.state.evalHistory = this.state.evalHistory.slice(-MAX_EVAL_HISTORY);
|
|
278
|
+
}
|
|
279
|
+
// Update rolling success rate (exponential moving average, alpha=0.1)
|
|
280
|
+
this.state.successRate = this.state.successRate * 0.9 + result.score * 0.1;
|
|
281
|
+
// 2. Extract patterns from successful interaction
|
|
282
|
+
if (result.score >= 0.5) {
|
|
283
|
+
try {
|
|
284
|
+
const learning = await getLearning();
|
|
285
|
+
learning.learnFromExchange(message, response, toolsUsed);
|
|
286
|
+
result.patternsExtracted++;
|
|
287
|
+
this.state.patternsLearnedToday++;
|
|
288
|
+
}
|
|
289
|
+
catch { /* non-critical */ }
|
|
290
|
+
}
|
|
291
|
+
// 3. Record routing outcome
|
|
292
|
+
if (this.pendingRouteAgent) {
|
|
293
|
+
try {
|
|
294
|
+
const router = await getLearnedRouter();
|
|
295
|
+
router.recordRoute(message, this.pendingRouteAgent, 'learned', result.score >= 0.5);
|
|
296
|
+
// Update routing accuracy
|
|
297
|
+
const routerStats = router.getRoutingStats();
|
|
298
|
+
if (routerStats.totalRoutes > 0) {
|
|
299
|
+
this.state.routingAccuracy = routerStats.learnedHits / routerStats.totalRoutes;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
catch { /* non-critical */ }
|
|
303
|
+
this.pendingRouteAgent = null;
|
|
304
|
+
}
|
|
305
|
+
// 4. Update graph-memory with entities from the exchange
|
|
306
|
+
try {
|
|
307
|
+
const graph = await getGraphMemory();
|
|
308
|
+
const entities = graph.extractEntities(message, response);
|
|
309
|
+
result.graphUpdates = entities.length;
|
|
310
|
+
}
|
|
311
|
+
catch { /* non-critical */ }
|
|
312
|
+
// 5. Update confidence calibration
|
|
313
|
+
try {
|
|
314
|
+
const conf = await getConfidence();
|
|
315
|
+
conf.recordCalibration(message, result.score, result.score >= 0.5 ? 1 : 0);
|
|
316
|
+
}
|
|
317
|
+
catch { /* non-critical */ }
|
|
318
|
+
// 6. Update intentionality drives
|
|
319
|
+
try {
|
|
320
|
+
const intent = await getIntentionality();
|
|
321
|
+
intent.updateMotivation({
|
|
322
|
+
type: result.score >= 0.6 ? 'task_success' : result.score >= 0.3 ? 'learned_something' : 'task_failure',
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
catch { /* non-critical */ }
|
|
326
|
+
// 7. Check if emergent insights arise
|
|
327
|
+
try {
|
|
328
|
+
const emergent = await getEmergent();
|
|
329
|
+
if (emergent && typeof emergent.synthesizeAcross === 'function') {
|
|
330
|
+
const insights = await emergent.synthesizeAcross(this.state.evalHistory.slice(-10).map(e => e.messageHash));
|
|
331
|
+
if (insights && Array.isArray(insights)) {
|
|
332
|
+
for (const insight of insights.slice(0, 3)) {
|
|
333
|
+
this.addInsight(typeof insight === 'string' ? insight : JSON.stringify(insight), 'emergent', 0.6);
|
|
334
|
+
result.insightsGenerated++;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
catch { /* emergent module may not exist yet */ }
|
|
340
|
+
// 8. Check if consolidation is needed
|
|
341
|
+
if (this.state.totalInteractions % CONSOLIDATION_INTERVAL === 0) {
|
|
342
|
+
result.consolidationTriggered = true;
|
|
343
|
+
// Fire-and-forget — don't block the response
|
|
344
|
+
this.consolidate().catch(() => { });
|
|
345
|
+
}
|
|
346
|
+
this.save();
|
|
347
|
+
return result;
|
|
348
|
+
}
|
|
349
|
+
// ── Phase 4: Cross-Session Learning ──
|
|
350
|
+
async consolidate() {
|
|
351
|
+
const result = {
|
|
352
|
+
patternsConsolidated: 0,
|
|
353
|
+
rulesAdded: 0,
|
|
354
|
+
insightsFound: 0,
|
|
355
|
+
graphPruned: { nodes: 0, edges: 0 },
|
|
356
|
+
routingAccuracy: this.state.routingAccuracy,
|
|
357
|
+
};
|
|
358
|
+
// 1. Run selfTrain() on accumulated patterns
|
|
359
|
+
try {
|
|
360
|
+
const learning = await getLearning();
|
|
361
|
+
const trained = learning.selfTrain();
|
|
362
|
+
result.patternsConsolidated = trained.optimized ?? 0;
|
|
363
|
+
}
|
|
364
|
+
catch { /* non-critical */ }
|
|
365
|
+
// 2. Prune weak graph edges
|
|
366
|
+
try {
|
|
367
|
+
const graph = await getGraphMemory();
|
|
368
|
+
// Decay nodes unused for 30 days
|
|
369
|
+
graph.decayUnused(30);
|
|
370
|
+
// Prune very weak connections
|
|
371
|
+
const pruned = graph.prune(0.1);
|
|
372
|
+
result.graphPruned = { nodes: pruned.removedNodes, edges: pruned.removedEdges };
|
|
373
|
+
graph.save();
|
|
374
|
+
}
|
|
375
|
+
catch { /* non-critical */ }
|
|
376
|
+
// 3. Derive behaviour rules from recurring patterns
|
|
377
|
+
try {
|
|
378
|
+
const learning = await getLearning();
|
|
379
|
+
const topPatterns = learning.getTopPatterns(5);
|
|
380
|
+
const behaviour = await getBehaviour();
|
|
381
|
+
for (const pattern of topPatterns) {
|
|
382
|
+
// If a pattern has been used many times, it might warrant a behaviour rule
|
|
383
|
+
if (pattern.hits >= 10 && pattern.successRate >= 0.8) {
|
|
384
|
+
const ruleText = `When asked about "${pattern.intent}", prefer tools: ${pattern.toolSequence.join(', ')}`;
|
|
385
|
+
const added = behaviour.learnGeneral(ruleText);
|
|
386
|
+
if (added)
|
|
387
|
+
result.rulesAdded++;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
catch { /* non-critical */ }
|
|
392
|
+
// 4. Run emergent synthesis
|
|
393
|
+
try {
|
|
394
|
+
const emergent = await getEmergent();
|
|
395
|
+
if (emergent && typeof emergent.consolidate === 'function') {
|
|
396
|
+
const consolidated = await emergent.consolidate();
|
|
397
|
+
if (consolidated && typeof consolidated.insights === 'number') {
|
|
398
|
+
result.insightsFound = consolidated.insights;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
catch { /* emergent module may not exist yet */ }
|
|
403
|
+
// 5. Update routing weights from outcome history
|
|
404
|
+
try {
|
|
405
|
+
const router = await getLearnedRouter();
|
|
406
|
+
const stats = router.getRoutingStats();
|
|
407
|
+
if (stats.totalRoutes > 0) {
|
|
408
|
+
result.routingAccuracy = stats.learnedHits / stats.totalRoutes;
|
|
409
|
+
this.state.routingAccuracy = result.routingAccuracy;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
catch { /* non-critical */ }
|
|
413
|
+
this.state.lastConsolidation = new Date().toISOString();
|
|
414
|
+
this.save();
|
|
415
|
+
return result;
|
|
416
|
+
}
|
|
417
|
+
// ── Self-Evaluation (heuristic, no LLM call) ──
|
|
418
|
+
selfEvaluate(message, response, toolsUsed) {
|
|
419
|
+
let score = 0.5; // neutral baseline
|
|
420
|
+
// Response length appropriateness
|
|
421
|
+
if (message.length > 50 && response.length < 20) {
|
|
422
|
+
score -= 0.15;
|
|
423
|
+
}
|
|
424
|
+
else if (response.length > 50) {
|
|
425
|
+
score += 0.1;
|
|
426
|
+
}
|
|
427
|
+
// Tool success signals
|
|
428
|
+
if (toolsUsed.length > 0) {
|
|
429
|
+
const errorPatterns = ['error', 'failed', 'could not', 'unable to', 'not found', 'permission denied'];
|
|
430
|
+
const responseLower = response.toLowerCase();
|
|
431
|
+
const errorCount = errorPatterns.filter(p => responseLower.includes(p)).length;
|
|
432
|
+
if (errorCount === 0)
|
|
433
|
+
score += 0.2;
|
|
434
|
+
else if (errorCount >= 3)
|
|
435
|
+
score -= 0.2;
|
|
436
|
+
}
|
|
437
|
+
else {
|
|
438
|
+
score += 0.05;
|
|
439
|
+
}
|
|
440
|
+
// Pattern match bonus
|
|
441
|
+
try {
|
|
442
|
+
if (this.pendingRouteAgent)
|
|
443
|
+
score += 0.1;
|
|
444
|
+
}
|
|
445
|
+
catch { /* non-critical */ }
|
|
446
|
+
// Actionable content bonus (code blocks, file paths)
|
|
447
|
+
if (response.includes('```') || response.match(/(?:\/[\w./-]+\.\w+)/)) {
|
|
448
|
+
score += 0.1;
|
|
449
|
+
}
|
|
450
|
+
// Repetition penalty
|
|
451
|
+
const recentHashes = this.state.evalHistory.slice(-3).map(e => e.messageHash);
|
|
452
|
+
if (recentHashes.includes(shortHash(response.slice(0, 200)))) {
|
|
453
|
+
score -= 0.1;
|
|
454
|
+
}
|
|
455
|
+
// Clamp to [0, 1]
|
|
456
|
+
return Math.max(0, Math.min(1, score));
|
|
457
|
+
}
|
|
458
|
+
// ── Policy Synthesis ──
|
|
459
|
+
synthesizePolicy(confidence) {
|
|
460
|
+
// High confidence + high success rate → exploit (use proven approaches)
|
|
461
|
+
if (confidence > 0.7 && this.state.successRate > 0.7)
|
|
462
|
+
return 'exploit';
|
|
463
|
+
// Low confidence or low success → explore (try new approaches)
|
|
464
|
+
if (confidence < 0.4 || this.state.successRate < 0.3)
|
|
465
|
+
return 'explore';
|
|
466
|
+
return 'balanced';
|
|
467
|
+
}
|
|
468
|
+
// ── Graph Memory Logging ──
|
|
469
|
+
async logToolToGraph(toolName, args) {
|
|
470
|
+
try {
|
|
471
|
+
const graph = await getGraphMemory();
|
|
472
|
+
// Add the tool as an entity
|
|
473
|
+
const toolNode = graph.findNode(toolName);
|
|
474
|
+
let toolNodeId;
|
|
475
|
+
if (toolNode.length > 0) {
|
|
476
|
+
toolNodeId = toolNode[0].id;
|
|
477
|
+
}
|
|
478
|
+
else {
|
|
479
|
+
const added = graph.addNode('entity', toolName, { kind: 'tool', lastUsed: new Date().toISOString() });
|
|
480
|
+
toolNodeId = added?.id || '';
|
|
481
|
+
}
|
|
482
|
+
// If we have a session goal, connect tool to goal
|
|
483
|
+
if (toolNodeId && this.state.activeGoals.length > 0) {
|
|
484
|
+
const currentGoal = this.state.activeGoals[this.state.activeGoals.length - 1];
|
|
485
|
+
const goalNodes = graph.findNode(currentGoal.description.slice(0, 50));
|
|
486
|
+
if (goalNodes.length > 0) {
|
|
487
|
+
graph.addEdge(toolNodeId, goalNodes[0].id, 'used_for', 0.5);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
graph.save();
|
|
491
|
+
}
|
|
492
|
+
catch { /* graph logging is non-critical */ }
|
|
493
|
+
}
|
|
494
|
+
// ── Insight Management ──
|
|
495
|
+
addInsight(content, source, confidence) {
|
|
496
|
+
this.state.recentInsights.push({
|
|
497
|
+
id: shortId(),
|
|
498
|
+
content,
|
|
499
|
+
source,
|
|
500
|
+
confidence,
|
|
501
|
+
timestamp: new Date().toISOString(),
|
|
502
|
+
});
|
|
503
|
+
// Trim to max
|
|
504
|
+
if (this.state.recentInsights.length > MAX_INSIGHTS) {
|
|
505
|
+
this.state.recentInsights = this.state.recentInsights.slice(-MAX_INSIGHTS);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
// ── Goal Management ──
|
|
509
|
+
addGoal(description, priority = 0.5) {
|
|
510
|
+
const goal = {
|
|
511
|
+
id: shortId(),
|
|
512
|
+
description,
|
|
513
|
+
priority: Math.max(0, Math.min(1, priority)),
|
|
514
|
+
status: 'active',
|
|
515
|
+
created: new Date().toISOString(),
|
|
516
|
+
toolsUsed: [],
|
|
517
|
+
};
|
|
518
|
+
this.state.activeGoals.push(goal);
|
|
519
|
+
// Trim old completed/abandoned goals
|
|
520
|
+
if (this.state.activeGoals.length > MAX_GOALS) {
|
|
521
|
+
this.state.activeGoals = this.state.activeGoals
|
|
522
|
+
.filter(g => g.status === 'active')
|
|
523
|
+
.slice(-MAX_GOALS);
|
|
524
|
+
}
|
|
525
|
+
this.save();
|
|
526
|
+
return goal;
|
|
527
|
+
}
|
|
528
|
+
completeGoal(goalId) {
|
|
529
|
+
const goal = this.state.activeGoals.find(g => g.id === goalId);
|
|
530
|
+
if (goal) {
|
|
531
|
+
goal.status = 'completed';
|
|
532
|
+
this.save();
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
// ── Conflict Detection ──
|
|
536
|
+
recordConflict(modules, description, resolution) {
|
|
537
|
+
this.state.conflictLog.push({
|
|
538
|
+
modules,
|
|
539
|
+
description,
|
|
540
|
+
resolution: resolution ?? null,
|
|
541
|
+
timestamp: new Date().toISOString(),
|
|
542
|
+
});
|
|
543
|
+
if (this.state.conflictLog.length > MAX_CONFLICTS) {
|
|
544
|
+
this.state.conflictLog = this.state.conflictLog.slice(-MAX_CONFLICTS);
|
|
545
|
+
}
|
|
546
|
+
this.save();
|
|
547
|
+
}
|
|
548
|
+
// ── Persistence ──
|
|
549
|
+
load() {
|
|
550
|
+
this.state = loadState();
|
|
551
|
+
if (this.state.patternsLearnedDate !== todayStr()) {
|
|
552
|
+
this.state.patternsLearnedToday = 0;
|
|
553
|
+
this.state.patternsLearnedDate = todayStr();
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
save() {
|
|
557
|
+
saveState(this.state);
|
|
558
|
+
}
|
|
559
|
+
// ── Diagnostics ──
|
|
560
|
+
getStats() {
|
|
561
|
+
return {
|
|
562
|
+
totalInteractions: this.state.totalInteractions,
|
|
563
|
+
successRate: Math.round(this.state.successRate * 1000) / 1000,
|
|
564
|
+
patternsLearnedToday: this.state.patternsLearnedToday,
|
|
565
|
+
routingAccuracy: Math.round(this.state.routingAccuracy * 1000) / 1000,
|
|
566
|
+
activeGoals: this.state.activeGoals.filter(g => g.status === 'active').length,
|
|
567
|
+
recentInsights: this.state.recentInsights.length,
|
|
568
|
+
conflicts: this.state.conflictLog.length,
|
|
569
|
+
lastConsolidation: this.state.lastConsolidation,
|
|
570
|
+
policy: this.state.lastPolicy,
|
|
571
|
+
confidenceThreshold: this.state.confidenceThreshold,
|
|
572
|
+
uptimeMs: Date.now() - this.initTime,
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
getHealthReport() {
|
|
576
|
+
const stats = this.getStats();
|
|
577
|
+
const lines = [
|
|
578
|
+
'=== Intelligence Coordinator Health Report ===',
|
|
579
|
+
'',
|
|
580
|
+
`Total interactions: ${stats.totalInteractions}`,
|
|
581
|
+
`Success rate: ${(stats.successRate * 100).toFixed(1)}%`,
|
|
582
|
+
`Routing accuracy: ${(stats.routingAccuracy * 100).toFixed(1)}%`,
|
|
583
|
+
`Policy: ${stats.policy}`,
|
|
584
|
+
`Confidence threshold: ${stats.confidenceThreshold}`,
|
|
585
|
+
'',
|
|
586
|
+
`Active goals: ${stats.activeGoals}`,
|
|
587
|
+
`Recent insights: ${stats.recentInsights}`,
|
|
588
|
+
`Conflicts: ${stats.conflicts}`,
|
|
589
|
+
`Patterns learned today: ${stats.patternsLearnedToday}`,
|
|
590
|
+
'',
|
|
591
|
+
`Last consolidation: ${stats.lastConsolidation ?? 'never'}`,
|
|
592
|
+
`Uptime: ${Math.round(stats.uptimeMs / 1000)}s`,
|
|
593
|
+
];
|
|
594
|
+
// Health checks
|
|
595
|
+
const issues = [];
|
|
596
|
+
if (stats.successRate < 0.3)
|
|
597
|
+
issues.push('LOW: Success rate below 30%');
|
|
598
|
+
if (stats.routingAccuracy < 0.3)
|
|
599
|
+
issues.push('LOW: Routing accuracy below 30%');
|
|
600
|
+
if (stats.totalInteractions > 50 && stats.patternsLearnedToday === 0) {
|
|
601
|
+
issues.push('STALE: No patterns learned today despite activity');
|
|
602
|
+
}
|
|
603
|
+
if (!stats.lastConsolidation) {
|
|
604
|
+
issues.push('PENDING: No consolidation has ever run');
|
|
605
|
+
}
|
|
606
|
+
if (issues.length > 0) {
|
|
607
|
+
lines.push('', '--- Issues ---');
|
|
608
|
+
for (const issue of issues)
|
|
609
|
+
lines.push(` ! ${issue}`);
|
|
610
|
+
}
|
|
611
|
+
else {
|
|
612
|
+
lines.push('', 'All systems nominal.');
|
|
613
|
+
}
|
|
614
|
+
return lines.join('\n');
|
|
615
|
+
}
|
|
616
|
+
getState() {
|
|
617
|
+
return this.state;
|
|
618
|
+
}
|
|
619
|
+
/** Adjust the confidence threshold (e.g., user prefers fewer clarification requests) */
|
|
620
|
+
setConfidenceThreshold(threshold) {
|
|
621
|
+
this.state.confidenceThreshold = Math.max(0, Math.min(1, threshold));
|
|
622
|
+
this.save();
|
|
623
|
+
}
|
|
624
|
+
/** Reset all state (for testing or fresh start) */
|
|
625
|
+
reset() {
|
|
626
|
+
this.state = defaultState();
|
|
627
|
+
this.anticipatedTools = [];
|
|
628
|
+
this.pendingRouteAgent = null;
|
|
629
|
+
this.save();
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
// ── Singleton ──
|
|
633
|
+
let singleton = null;
|
|
634
|
+
export function getCoordinator() {
|
|
635
|
+
if (!singleton) {
|
|
636
|
+
singleton = new IntelligenceCoordinator();
|
|
637
|
+
}
|
|
638
|
+
return singleton;
|
|
639
|
+
}
|
|
640
|
+
// ── Tool Registration ──
|
|
641
|
+
/** Register coordinator tools with the kbot tool registry */
|
|
642
|
+
export function registerCoordinatorTools() {
|
|
643
|
+
// Lazy import to avoid circular deps at module load time
|
|
644
|
+
import('./tools/index.js').then(({ registerTool }) => {
|
|
645
|
+
registerTool({
|
|
646
|
+
name: 'coordinator_status',
|
|
647
|
+
description: 'Show intelligence coordinator stats: success rate, routing accuracy, policy, interactions, goals, insights',
|
|
648
|
+
parameters: {},
|
|
649
|
+
tier: 'free',
|
|
650
|
+
execute: async () => {
|
|
651
|
+
const c = getCoordinator();
|
|
652
|
+
const stats = c.getStats();
|
|
653
|
+
return JSON.stringify(stats, null, 2);
|
|
654
|
+
},
|
|
655
|
+
});
|
|
656
|
+
registerTool({
|
|
657
|
+
name: 'coordinator_health',
|
|
658
|
+
description: 'Run a health check on all intelligence subsystems and report issues',
|
|
659
|
+
parameters: {},
|
|
660
|
+
tier: 'free',
|
|
661
|
+
execute: async () => {
|
|
662
|
+
const c = getCoordinator();
|
|
663
|
+
return c.getHealthReport();
|
|
664
|
+
},
|
|
665
|
+
});
|
|
666
|
+
registerTool({
|
|
667
|
+
name: 'coordinator_consolidate',
|
|
668
|
+
description: 'Force a cross-session learning consolidation: self-train patterns, prune graph, derive behaviour rules, synthesize insights',
|
|
669
|
+
parameters: {},
|
|
670
|
+
tier: 'free',
|
|
671
|
+
execute: async () => {
|
|
672
|
+
const c = getCoordinator();
|
|
673
|
+
const result = await c.consolidate();
|
|
674
|
+
return JSON.stringify(result, null, 2);
|
|
675
|
+
},
|
|
676
|
+
});
|
|
677
|
+
registerTool({
|
|
678
|
+
name: 'coordinator_orchestrate',
|
|
679
|
+
description: 'Decompose a goal into sub-tasks, assign specialist agents, execute in dependency order, and synthesize results',
|
|
680
|
+
parameters: { goal: { type: 'string', description: 'The high-level goal to orchestrate', required: true } },
|
|
681
|
+
tier: 'free',
|
|
682
|
+
execute: async (args) => coordinate(args.goal),
|
|
683
|
+
});
|
|
684
|
+
}).catch(() => {
|
|
685
|
+
// tools/index.js not available — skip registration
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
// ── Goal Decomposition & Multi-Agent Orchestration ──
|
|
689
|
+
//
|
|
690
|
+
// Takes a high-level goal and:
|
|
691
|
+
// 1. Decomposes it into ordered sub-tasks via LLM
|
|
692
|
+
// 2. Assigns each sub-task to the best specialist agent
|
|
693
|
+
// 3. Manages dependencies between sub-tasks
|
|
694
|
+
// 4. Synthesizes results into a final output
|
|
695
|
+
// 5. Learns from the execution for next time
|
|
696
|
+
const COORD_AMETHYST = typeof chalk.hex === 'function' ? chalk.hex('#6B5B95') : ((s) => s);
|
|
697
|
+
const DECOMPOSE_PROMPT = `You are a task decomposition engine. Break the goal into 2-6 concrete sub-tasks.
|
|
698
|
+
Output ONLY valid JSON: {"tasks":[{"id":"t1","goal":"...","agent":"agent_id","dependencies":[]}]}
|
|
699
|
+
Agents: kernel (general), coder (code/debug), researcher (research), writer (docs), analyst (strategy), guardian (security), infrastructure (devops).
|
|
700
|
+
Rules: each task independently verifiable, use dependencies for ordering, assign best agent, one objective per task.`;
|
|
701
|
+
/**
|
|
702
|
+
* Decompose a high-level goal into ordered sub-tasks with dependencies.
|
|
703
|
+
* Uses the LLM (via runAgent) to break the goal into 2-6 concrete tasks.
|
|
704
|
+
*/
|
|
705
|
+
export async function decompose(goal) {
|
|
706
|
+
const planId = shortId();
|
|
707
|
+
const plan = {
|
|
708
|
+
id: planId,
|
|
709
|
+
goal,
|
|
710
|
+
tasks: [],
|
|
711
|
+
createdAt: new Date().toISOString(),
|
|
712
|
+
status: 'planning',
|
|
713
|
+
};
|
|
714
|
+
process.stderr.write(` ${COORD_AMETHYST('◆ decompose')} ${chalk.dim(goal.slice(0, 80))}${goal.length > 80 ? '...' : ''}\n`);
|
|
715
|
+
try {
|
|
716
|
+
// Lazy import to avoid circular dependency
|
|
717
|
+
const { runAgent } = await import('./agent.js');
|
|
718
|
+
const response = await runAgent(`${DECOMPOSE_PROMPT}\n\nGoal: ${goal}\n\nOutput JSON:`, { agent: 'kernel', skipPlanner: true, sessionId: `coord-${planId}` });
|
|
719
|
+
// Parse JSON from response
|
|
720
|
+
const jsonMatch = response.content.match(/\{[\s\S]*\}/);
|
|
721
|
+
if (jsonMatch) {
|
|
722
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
723
|
+
plan.tasks = (parsed.tasks || []).slice(0, 6).map(t => ({
|
|
724
|
+
id: t.id || shortId(),
|
|
725
|
+
goal: t.goal,
|
|
726
|
+
status: 'pending',
|
|
727
|
+
agent: t.agent || 'kernel',
|
|
728
|
+
dependencies: t.dependencies || [],
|
|
729
|
+
}));
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
catch (err) {
|
|
733
|
+
process.stderr.write(` ${chalk.red('✗')} decomposition failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
734
|
+
}
|
|
735
|
+
// Fallback: if decomposition produced nothing, create a single task
|
|
736
|
+
if (plan.tasks.length === 0) {
|
|
737
|
+
plan.tasks = [{
|
|
738
|
+
id: 't1',
|
|
739
|
+
goal,
|
|
740
|
+
status: 'pending',
|
|
741
|
+
agent: 'kernel',
|
|
742
|
+
dependencies: [],
|
|
743
|
+
}];
|
|
744
|
+
}
|
|
745
|
+
plan.status = 'executing';
|
|
746
|
+
for (const task of plan.tasks) {
|
|
747
|
+
const deps = task.dependencies.length > 0 ? chalk.dim(` -> ${task.dependencies.join(',')}`) : '';
|
|
748
|
+
process.stderr.write(` ${chalk.dim('│')} ${chalk.cyan(task.id)} ${task.goal} ${chalk.magenta(`@${task.agent}`)}${deps}\n`);
|
|
749
|
+
}
|
|
750
|
+
return plan;
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* Execute a plan's tasks in dependency order (sequential for now).
|
|
754
|
+
* Tasks whose dependencies are all 'done' are eligible to run.
|
|
755
|
+
*/
|
|
756
|
+
export async function execute(plan) {
|
|
757
|
+
const { runAgent } = await import('./agent.js');
|
|
758
|
+
const completed = new Set();
|
|
759
|
+
process.stderr.write(`\n ${COORD_AMETHYST('◆ execute')} ${plan.tasks.length} tasks\n`);
|
|
760
|
+
while (completed.size < plan.tasks.length) {
|
|
761
|
+
// Find tasks whose dependencies are all satisfied
|
|
762
|
+
const ready = plan.tasks.filter(t => t.status === 'pending' &&
|
|
763
|
+
t.dependencies.every(dep => completed.has(dep)));
|
|
764
|
+
if (ready.length === 0) {
|
|
765
|
+
// Remaining tasks have unmet deps — mark them failed
|
|
766
|
+
for (const t of plan.tasks) {
|
|
767
|
+
if (t.status === 'pending') {
|
|
768
|
+
t.status = 'failed';
|
|
769
|
+
t.error = 'Unmet dependencies (earlier tasks failed)';
|
|
770
|
+
completed.add(t.id);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
break;
|
|
774
|
+
}
|
|
775
|
+
// Execute ready tasks sequentially (parallel execution can come later)
|
|
776
|
+
for (const task of ready) {
|
|
777
|
+
task.status = 'running';
|
|
778
|
+
process.stderr.write(` ${chalk.dim('├')} ${chalk.yellow('●')} ${task.id}: ${task.goal}\n`);
|
|
779
|
+
// Gather dependency results as context
|
|
780
|
+
const depCtx = task.dependencies
|
|
781
|
+
.map(id => { const d = plan.tasks.find(t => t.id === id); return d?.result ? `[${id}]: ${d.result.slice(0, 500)}` : ''; })
|
|
782
|
+
.filter(Boolean).join('\n');
|
|
783
|
+
const prompt = `You are executing a sub-task of a larger plan.${depCtx ? `\n\nPrevious results:\n${depCtx}` : ''}\n\nYour task: ${task.goal}\n\nExecute this now.`;
|
|
784
|
+
try {
|
|
785
|
+
const response = await runAgent(prompt, {
|
|
786
|
+
agent: task.agent,
|
|
787
|
+
skipPlanner: true,
|
|
788
|
+
sessionId: `coord-${plan.id}-${task.id}`,
|
|
789
|
+
});
|
|
790
|
+
task.result = response.content;
|
|
791
|
+
task.status = 'done';
|
|
792
|
+
process.stderr.write(` ${chalk.dim('│')} ${chalk.green('✓')} ${task.id} done\n`);
|
|
793
|
+
}
|
|
794
|
+
catch (err) {
|
|
795
|
+
task.status = 'failed';
|
|
796
|
+
task.error = err instanceof Error ? err.message : String(err);
|
|
797
|
+
process.stderr.write(` ${chalk.dim('│')} ${chalk.red('✗')} ${task.id} failed: ${task.error}\n`);
|
|
798
|
+
}
|
|
799
|
+
completed.add(task.id);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
// Determine final plan status
|
|
803
|
+
const failed = plan.tasks.filter(t => t.status === 'failed');
|
|
804
|
+
plan.status = failed.length === 0 ? 'done' : 'failed';
|
|
805
|
+
plan.completedAt = new Date().toISOString();
|
|
806
|
+
const done = plan.tasks.filter(t => t.status === 'done').length;
|
|
807
|
+
process.stderr.write(` ${chalk.dim('└')} ${done}/${plan.tasks.length} tasks succeeded\n`);
|
|
808
|
+
// Record outcome for learning
|
|
809
|
+
try {
|
|
810
|
+
const coord = getCoordinator();
|
|
811
|
+
if (plan.status === 'done')
|
|
812
|
+
coord.addGoal(plan.goal, 0.7).status = 'completed';
|
|
813
|
+
const learning = await getLearning();
|
|
814
|
+
learning.learnFromExchange(`[coordinator] ${plan.goal}`, `${done}/${plan.tasks.length} done`, plan.tasks.map(t => t.agent));
|
|
815
|
+
}
|
|
816
|
+
catch { /* non-critical */ }
|
|
817
|
+
return plan;
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* Convenience: decompose a goal, execute the plan, and synthesize results.
|
|
821
|
+
* Returns a human-readable summary of what was accomplished.
|
|
822
|
+
*/
|
|
823
|
+
export async function coordinate(goal) {
|
|
824
|
+
const plan = await decompose(goal);
|
|
825
|
+
const executed = await execute(plan);
|
|
826
|
+
// Synthesize results
|
|
827
|
+
const results = executed.tasks
|
|
828
|
+
.filter(t => t.status === 'done' && t.result)
|
|
829
|
+
.map(t => `## ${t.goal}\n${t.result}`)
|
|
830
|
+
.join('\n\n');
|
|
831
|
+
const failed = executed.tasks.filter(t => t.status === 'failed');
|
|
832
|
+
const failSummary = failed.length > 0
|
|
833
|
+
? `\n\n---\n${failed.length} task(s) failed:\n${failed.map(t => `- ${t.goal}: ${t.error}`).join('\n')}`
|
|
834
|
+
: '';
|
|
835
|
+
return results + failSummary;
|
|
836
|
+
}
|
|
837
|
+
// Auto-register tools when this module is imported
|
|
838
|
+
registerCoordinatorTools();
|
|
839
|
+
//# sourceMappingURL=coordinator.js.map
|