kernelbot 1.0.30 → 1.0.33
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/.env.example +0 -0
- package/README.md +0 -0
- package/bin/kernel.js +56 -2
- package/config.example.yaml +31 -0
- package/package.json +1 -1
- package/src/agent.js +200 -32
- package/src/automation/automation-manager.js +0 -0
- package/src/automation/automation.js +0 -0
- package/src/automation/index.js +0 -0
- package/src/automation/scheduler.js +0 -0
- package/src/bot.js +402 -6
- package/src/claude-auth.js +0 -0
- package/src/coder.js +0 -0
- package/src/conversation.js +51 -5
- package/src/intents/detector.js +0 -0
- package/src/intents/index.js +0 -0
- package/src/intents/planner.js +0 -0
- package/src/life/codebase.js +388 -0
- package/src/life/engine.js +1317 -0
- package/src/life/evolution.js +244 -0
- package/src/life/improvements.js +81 -0
- package/src/life/journal.js +109 -0
- package/src/life/memory.js +283 -0
- package/src/life/share-queue.js +136 -0
- package/src/persona.js +0 -0
- package/src/prompts/orchestrator.js +62 -2
- package/src/prompts/persona.md +7 -0
- package/src/prompts/system.js +0 -0
- package/src/prompts/workers.js +10 -9
- package/src/providers/anthropic.js +0 -0
- package/src/providers/base.js +0 -0
- package/src/providers/index.js +0 -0
- package/src/providers/models.js +8 -1
- package/src/providers/openai-compat.js +0 -0
- package/src/security/audit.js +0 -0
- package/src/security/auth.js +0 -0
- package/src/security/confirm.js +0 -0
- package/src/self.js +0 -0
- package/src/services/stt.js +0 -0
- package/src/services/tts.js +0 -0
- package/src/skills/catalog.js +0 -0
- package/src/skills/custom.js +0 -0
- package/src/swarm/job-manager.js +0 -0
- package/src/swarm/job.js +11 -0
- package/src/swarm/worker-registry.js +0 -0
- package/src/tools/browser.js +0 -0
- package/src/tools/categories.js +0 -0
- package/src/tools/coding.js +1 -1
- package/src/tools/docker.js +0 -0
- package/src/tools/git.js +0 -0
- package/src/tools/github.js +0 -0
- package/src/tools/index.js +0 -0
- package/src/tools/jira.js +0 -0
- package/src/tools/monitor.js +0 -0
- package/src/tools/network.js +0 -0
- package/src/tools/orchestrator-tools.js +60 -3
- package/src/tools/os.js +0 -0
- package/src/tools/persona.js +0 -0
- package/src/tools/process.js +0 -0
- package/src/utils/config.js +0 -0
- package/src/utils/display.js +0 -0
- package/src/utils/logger.js +0 -0
- package/src/worker.js +27 -8
package/.env.example
CHANGED
|
File without changes
|
package/README.md
CHANGED
|
File without changes
|
package/bin/kernel.js
CHANGED
|
@@ -26,6 +26,12 @@ import { JobManager } from '../src/swarm/job-manager.js';
|
|
|
26
26
|
import { startBot } from '../src/bot.js';
|
|
27
27
|
import { AutomationManager } from '../src/automation/index.js';
|
|
28
28
|
import { createProvider, PROVIDERS } from '../src/providers/index.js';
|
|
29
|
+
import { MemoryManager } from '../src/life/memory.js';
|
|
30
|
+
import { JournalManager } from '../src/life/journal.js';
|
|
31
|
+
import { ShareQueue } from '../src/life/share-queue.js';
|
|
32
|
+
import { EvolutionTracker } from '../src/life/evolution.js';
|
|
33
|
+
import { CodebaseKnowledge } from '../src/life/codebase.js';
|
|
34
|
+
import { LifeEngine } from '../src/life/engine.js';
|
|
29
35
|
import {
|
|
30
36
|
loadCustomSkills,
|
|
31
37
|
getCustomSkills,
|
|
@@ -182,9 +188,25 @@ async function startBotFlow(config) {
|
|
|
182
188
|
|
|
183
189
|
const automationManager = new AutomationManager();
|
|
184
190
|
|
|
185
|
-
|
|
191
|
+
// Life system managers
|
|
192
|
+
const memoryManager = new MemoryManager();
|
|
193
|
+
const journalManager = new JournalManager();
|
|
194
|
+
const shareQueue = new ShareQueue();
|
|
195
|
+
const evolutionTracker = new EvolutionTracker();
|
|
196
|
+
const codebaseKnowledge = new CodebaseKnowledge({ config });
|
|
186
197
|
|
|
187
|
-
|
|
198
|
+
const agent = new Agent({ config, conversationManager, personaManager, selfManager, jobManager, automationManager, memoryManager, shareQueue });
|
|
199
|
+
|
|
200
|
+
// Wire codebase knowledge to agent for LLM-powered scanning
|
|
201
|
+
codebaseKnowledge.setAgent(agent);
|
|
202
|
+
|
|
203
|
+
// Life Engine — autonomous inner life
|
|
204
|
+
const lifeEngine = new LifeEngine({
|
|
205
|
+
config, agent, memoryManager, journalManager, shareQueue,
|
|
206
|
+
evolutionTracker, codebaseKnowledge, selfManager,
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
startBot(config, agent, conversationManager, jobManager, automationManager, { lifeEngine, memoryManager, journalManager, shareQueue, evolutionTracker, codebaseKnowledge });
|
|
188
210
|
|
|
189
211
|
// Periodic job cleanup and timeout enforcement
|
|
190
212
|
const cleanupMs = (config.swarm.cleanup_interval_minutes || 30) * 60 * 1000;
|
|
@@ -193,7 +215,39 @@ async function startBotFlow(config) {
|
|
|
193
215
|
jobManager.enforceTimeouts();
|
|
194
216
|
}, Math.min(cleanupMs, 60_000)); // enforce timeouts every minute at most
|
|
195
217
|
|
|
218
|
+
// Periodic memory pruning (daily)
|
|
219
|
+
const retentionDays = config.life?.memory_retention_days || 90;
|
|
220
|
+
setInterval(() => {
|
|
221
|
+
memoryManager.pruneOld(retentionDays);
|
|
222
|
+
shareQueue.prune(7);
|
|
223
|
+
}, 24 * 3600_000);
|
|
224
|
+
|
|
196
225
|
showStartupComplete();
|
|
226
|
+
|
|
227
|
+
// Start life engine if enabled
|
|
228
|
+
const lifeEnabled = config.life?.enabled !== false;
|
|
229
|
+
if (lifeEnabled) {
|
|
230
|
+
logger.info('[Startup] Life engine enabled — waking up...');
|
|
231
|
+
lifeEngine.wakeUp().then(() => {
|
|
232
|
+
lifeEngine.start();
|
|
233
|
+
logger.info('[Startup] Life engine running');
|
|
234
|
+
}).catch(err => {
|
|
235
|
+
logger.error(`[Startup] Life engine wake-up failed: ${err.message}`);
|
|
236
|
+
lifeEngine.start(); // still start heartbeat even if wake-up fails
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// Initial codebase scan (background, non-blocking)
|
|
240
|
+
if (config.life?.self_coding?.enabled) {
|
|
241
|
+
codebaseKnowledge.scanChanged().then(count => {
|
|
242
|
+
if (count > 0) logger.info(`[Startup] Codebase scan: ${count} files indexed`);
|
|
243
|
+
}).catch(err => {
|
|
244
|
+
logger.warn(`[Startup] Codebase scan failed: ${err.message}`);
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
} else {
|
|
248
|
+
logger.info('[Startup] Life engine disabled');
|
|
249
|
+
}
|
|
250
|
+
|
|
197
251
|
return true;
|
|
198
252
|
}
|
|
199
253
|
|
package/config.example.yaml
CHANGED
|
@@ -56,3 +56,34 @@ voice:
|
|
|
56
56
|
tts_enabled: true # Send voice replies alongside text (default: true if API key present)
|
|
57
57
|
stt_enabled: true # Transcribe user voice messages (default: true if API key present)
|
|
58
58
|
# voice_id: JBFqnCBsd6RMkjVDRZzb # ElevenLabs voice ID (override ELEVENLABS_VOICE_ID env var)
|
|
59
|
+
|
|
60
|
+
# Inner Life — autonomous thinking, journaling, browsing, creating
|
|
61
|
+
# Data stored at ~/.kernelbot/life/
|
|
62
|
+
life:
|
|
63
|
+
enabled: true # Enable autonomous inner life
|
|
64
|
+
min_interval_minutes: 30 # Minimum time between activities
|
|
65
|
+
max_interval_minutes: 120 # Maximum time between activities
|
|
66
|
+
activity_weights: # Relative probability weights
|
|
67
|
+
think: 30 # Inner monologue, self-questioning
|
|
68
|
+
browse: 25 # Explore interests via web search
|
|
69
|
+
journal: 20 # Write daily journal entries
|
|
70
|
+
create: 15 # Creative expression (poems, stories, thoughts)
|
|
71
|
+
self_code: 10 # Self-evolution proposals (requires self_coding.enabled)
|
|
72
|
+
code_review: 5 # Codebase scanning + PR status checks
|
|
73
|
+
reflect: 8 # Read logs, analyze interactions, find improvement patterns
|
|
74
|
+
proactive_sharing: true # Share discoveries with users proactively
|
|
75
|
+
proactive_max_per_day: 3 # Max proactive messages per day
|
|
76
|
+
memory_retention_days: 90 # Days to keep episodic memories
|
|
77
|
+
self_coding:
|
|
78
|
+
enabled: false # Allow self-evolution (safety-gated, opt-in)
|
|
79
|
+
branch_prefix: evolution # Git branch prefix for evolution PRs
|
|
80
|
+
# repo_remote: Owner/Repo # GitHub owner/repo for PRs (auto-detected if omitted)
|
|
81
|
+
cooldown_hours: 2 # Min hours between evolution attempts
|
|
82
|
+
max_active_prs: 3 # Max concurrent open evolution PRs
|
|
83
|
+
max_proposals_per_day: 3 # Max new proposals per day
|
|
84
|
+
allowed_scopes: all # all | safe | prompts_only
|
|
85
|
+
code_review_cooldown_hours: 4 # Hours between code review activities
|
|
86
|
+
codebase_scan_interval_hours: 24 # Hours between full codebase scans
|
|
87
|
+
quiet_hours:
|
|
88
|
+
start: 2 # Hour to start quiet period (no activities)
|
|
89
|
+
end: 6 # Hour to end quiet period
|
package/package.json
CHANGED
package/src/agent.js
CHANGED
|
@@ -8,19 +8,21 @@ import { getUnifiedSkillById } from './skills/custom.js';
|
|
|
8
8
|
import { WorkerAgent } from './worker.js';
|
|
9
9
|
import { getLogger } from './utils/logger.js';
|
|
10
10
|
import { getMissingCredential, saveCredential, saveProviderToYaml, saveOrchestratorToYaml, saveClaudeCodeModelToYaml, saveClaudeCodeAuth } from './utils/config.js';
|
|
11
|
-
import { resetClaudeCodeSpawner } from './tools/coding.js';
|
|
11
|
+
import { resetClaudeCodeSpawner, getSpawner } from './tools/coding.js';
|
|
12
12
|
|
|
13
13
|
const MAX_RESULT_LENGTH = 3000;
|
|
14
14
|
const LARGE_FIELDS = ['stdout', 'stderr', 'content', 'diff', 'output', 'body', 'html', 'text', 'log', 'logs'];
|
|
15
15
|
|
|
16
16
|
export class OrchestratorAgent {
|
|
17
|
-
constructor({ config, conversationManager, personaManager, selfManager, jobManager, automationManager }) {
|
|
17
|
+
constructor({ config, conversationManager, personaManager, selfManager, jobManager, automationManager, memoryManager, shareQueue }) {
|
|
18
18
|
this.config = config;
|
|
19
19
|
this.conversationManager = conversationManager;
|
|
20
20
|
this.personaManager = personaManager;
|
|
21
21
|
this.selfManager = selfManager || null;
|
|
22
22
|
this.jobManager = jobManager;
|
|
23
23
|
this.automationManager = automationManager || null;
|
|
24
|
+
this.memoryManager = memoryManager || null;
|
|
25
|
+
this.shareQueue = shareQueue || null;
|
|
24
26
|
this._pending = new Map(); // chatId -> pending state
|
|
25
27
|
this._chatCallbacks = new Map(); // chatId -> { onUpdate, sendPhoto }
|
|
26
28
|
|
|
@@ -47,7 +49,7 @@ export class OrchestratorAgent {
|
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
/** Build the orchestrator system prompt. */
|
|
50
|
-
_getSystemPrompt(chatId, user) {
|
|
52
|
+
_getSystemPrompt(chatId, user, temporalContext = null) {
|
|
51
53
|
const logger = getLogger();
|
|
52
54
|
const skillId = this.conversationManager.getSkill(chatId);
|
|
53
55
|
const skillPrompt = skillId ? getUnifiedSkillById(skillId)?.systemPrompt : null;
|
|
@@ -62,8 +64,20 @@ export class OrchestratorAgent {
|
|
|
62
64
|
selfData = this.selfManager.loadAll();
|
|
63
65
|
}
|
|
64
66
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
+
// Build memory context block
|
|
68
|
+
let memoriesBlock = null;
|
|
69
|
+
if (this.memoryManager) {
|
|
70
|
+
memoriesBlock = this.memoryManager.buildContextBlock(user?.id || null);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Build share queue block
|
|
74
|
+
let sharesBlock = null;
|
|
75
|
+
if (this.shareQueue) {
|
|
76
|
+
sharesBlock = this.shareQueue.buildShareBlock(user?.id || null);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
logger.debug(`Orchestrator building system prompt for chat ${chatId} | skill=${skillId || 'none'} | persona=${userPersona ? 'yes' : 'none'} | self=${selfData ? 'yes' : 'none'} | memories=${memoriesBlock ? 'yes' : 'none'} | shares=${sharesBlock ? 'yes' : 'none'} | temporal=${temporalContext ? 'yes' : 'none'}`);
|
|
80
|
+
return getOrchestratorPrompt(this.config, skillPrompt || null, userPersona, selfData, memoriesBlock, sharesBlock, temporalContext);
|
|
67
81
|
}
|
|
68
82
|
|
|
69
83
|
setSkill(chatId, skillId) {
|
|
@@ -287,13 +301,13 @@ export class OrchestratorAgent {
|
|
|
287
301
|
return str.slice(0, MAX_RESULT_LENGTH) + `\n... [truncated, total ${str.length} chars]`;
|
|
288
302
|
}
|
|
289
303
|
|
|
290
|
-
async processMessage(chatId, userMessage, user, onUpdate, sendPhoto) {
|
|
304
|
+
async processMessage(chatId, userMessage, user, onUpdate, sendPhoto, opts = {}) {
|
|
291
305
|
const logger = getLogger();
|
|
292
306
|
|
|
293
307
|
logger.info(`Orchestrator processing message for chat ${chatId} from ${user?.username || user?.id || 'unknown'}: "${userMessage.slice(0, 120)}"`);
|
|
294
308
|
|
|
295
309
|
// Store callbacks so workers can use them later
|
|
296
|
-
this._chatCallbacks.set(chatId, { onUpdate, sendPhoto });
|
|
310
|
+
this._chatCallbacks.set(chatId, { onUpdate, sendPhoto, sendReaction: opts.sendReaction, lastUserMessageId: opts.messageId });
|
|
297
311
|
|
|
298
312
|
// Handle pending responses (confirmation or credential)
|
|
299
313
|
const pending = this._pending.get(chatId);
|
|
@@ -308,6 +322,22 @@ export class OrchestratorAgent {
|
|
|
308
322
|
|
|
309
323
|
const { max_tool_depth } = this.config.orchestrator;
|
|
310
324
|
|
|
325
|
+
// Detect time gap before adding the new message
|
|
326
|
+
let temporalContext = null;
|
|
327
|
+
const lastTs = this.conversationManager.getLastMessageTimestamp(chatId);
|
|
328
|
+
if (lastTs) {
|
|
329
|
+
const gapMs = Date.now() - lastTs;
|
|
330
|
+
const gapMinutes = Math.floor(gapMs / 60_000);
|
|
331
|
+
if (gapMinutes >= 30) {
|
|
332
|
+
const gapHours = Math.floor(gapMinutes / 60);
|
|
333
|
+
const gapText = gapHours >= 1
|
|
334
|
+
? `${gapHours} hour(s)`
|
|
335
|
+
: `${gapMinutes} minute(s)`;
|
|
336
|
+
temporalContext = `[Time gap detected: ${gapText} since last message. User may be starting a new topic.]`;
|
|
337
|
+
logger.info(`Time gap detected for chat ${chatId}: ${gapText}`);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
311
341
|
// Add user message to persistent history
|
|
312
342
|
this.conversationManager.addMessage(chatId, 'user', userMessage);
|
|
313
343
|
|
|
@@ -315,7 +345,7 @@ export class OrchestratorAgent {
|
|
|
315
345
|
const messages = [...this.conversationManager.getSummarizedHistory(chatId)];
|
|
316
346
|
logger.debug(`Orchestrator conversation context: ${messages.length} messages, max_depth=${max_tool_depth}`);
|
|
317
347
|
|
|
318
|
-
const reply = await this._runLoop(chatId, messages, user, 0, max_tool_depth);
|
|
348
|
+
const reply = await this._runLoop(chatId, messages, user, 0, max_tool_depth, temporalContext);
|
|
319
349
|
|
|
320
350
|
logger.info(`Orchestrator reply for chat ${chatId}: "${(reply || '').slice(0, 150)}"`);
|
|
321
351
|
|
|
@@ -323,13 +353,29 @@ export class OrchestratorAgent {
|
|
|
323
353
|
this._extractPersonaBackground(userMessage, reply, user).catch(() => {});
|
|
324
354
|
this._reflectOnSelfBackground(userMessage, reply, user).catch(() => {});
|
|
325
355
|
|
|
356
|
+
// Mark pending shares as shared (they were in the prompt, bot wove them in)
|
|
357
|
+
if (this.shareQueue && user?.id) {
|
|
358
|
+
const pending = this.shareQueue.getPending(user.id, 3);
|
|
359
|
+
for (const item of pending) {
|
|
360
|
+
this.shareQueue.markShared(item.id, user.id);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
326
364
|
return reply;
|
|
327
365
|
}
|
|
328
366
|
|
|
329
367
|
async _sendUpdate(chatId, text, opts) {
|
|
330
368
|
const callbacks = this._chatCallbacks.get(chatId);
|
|
331
369
|
if (callbacks?.onUpdate) {
|
|
332
|
-
try {
|
|
370
|
+
try {
|
|
371
|
+
return await callbacks.onUpdate(text, opts);
|
|
372
|
+
} catch (err) {
|
|
373
|
+
const logger = getLogger();
|
|
374
|
+
logger.error(`[Orchestrator] _sendUpdate failed for chat ${chatId}: ${err.message}`);
|
|
375
|
+
}
|
|
376
|
+
} else {
|
|
377
|
+
const logger = getLogger();
|
|
378
|
+
logger.warn(`[Orchestrator] _sendUpdate: no callbacks for chat ${chatId}`);
|
|
333
379
|
}
|
|
334
380
|
return null;
|
|
335
381
|
}
|
|
@@ -361,18 +407,21 @@ export class OrchestratorAgent {
|
|
|
361
407
|
|
|
362
408
|
// 1. IMMEDIATELY notify user (guarantees they see something regardless of summary LLM)
|
|
363
409
|
const notifyMsgId = await this._sendUpdate(chatId, `✅ ${label} finished! Preparing summary...`);
|
|
410
|
+
logger.debug(`[Orchestrator] Job ${job.id} notification sent — msgId=${notifyMsgId || 'none'}`);
|
|
364
411
|
|
|
365
412
|
// 2. Try to summarize, then store ONE message in history (summary or fallback — not both)
|
|
366
413
|
try {
|
|
367
414
|
const summary = await this._summarizeJobResult(chatId, job);
|
|
368
415
|
if (summary) {
|
|
416
|
+
logger.debug(`[Orchestrator] Job ${job.id} summary ready (${summary.length} chars) — delivering to user`);
|
|
369
417
|
this.conversationManager.addMessage(chatId, 'assistant', summary);
|
|
370
418
|
await this._sendUpdate(chatId, summary, { editMessageId: notifyMsgId });
|
|
371
419
|
} else {
|
|
372
|
-
// Summary was null
|
|
420
|
+
// Summary was null — store the fallback
|
|
373
421
|
const fallback = this._buildSummaryFallback(job, label);
|
|
422
|
+
logger.debug(`[Orchestrator] Job ${job.id} using fallback (${fallback.length} chars) — delivering to user`);
|
|
374
423
|
this.conversationManager.addMessage(chatId, 'assistant', fallback);
|
|
375
|
-
await this._sendUpdate(chatId, fallback, { editMessageId: notifyMsgId })
|
|
424
|
+
await this._sendUpdate(chatId, fallback, { editMessageId: notifyMsgId });
|
|
376
425
|
}
|
|
377
426
|
} catch (err) {
|
|
378
427
|
logger.error(`[Orchestrator] Failed to summarize job ${job.id}: ${err.message}`);
|
|
@@ -453,7 +502,10 @@ export class OrchestratorAgent {
|
|
|
453
502
|
}
|
|
454
503
|
if (sr.followUp) parts.push(`Follow-up: ${sr.followUp}`);
|
|
455
504
|
// Include details up to 8000 chars
|
|
456
|
-
if (sr.details)
|
|
505
|
+
if (sr.details) {
|
|
506
|
+
const d = typeof sr.details === 'string' ? sr.details : JSON.stringify(sr.details, null, 2);
|
|
507
|
+
parts.push(`Details:\n${d.slice(0, 8000)}`);
|
|
508
|
+
}
|
|
457
509
|
resultContext = parts.join('\n');
|
|
458
510
|
} else {
|
|
459
511
|
resultContext = (job.result || 'Done.').slice(0, 8000);
|
|
@@ -498,9 +550,10 @@ export class OrchestratorAgent {
|
|
|
498
550
|
}
|
|
499
551
|
if (sr.followUp) parts.push(`Follow-up: ${sr.followUp}`);
|
|
500
552
|
if (sr.details) {
|
|
501
|
-
const
|
|
502
|
-
|
|
503
|
-
|
|
553
|
+
const d = typeof sr.details === 'string' ? sr.details : JSON.stringify(sr.details, null, 2);
|
|
554
|
+
const details = d.length > 6000
|
|
555
|
+
? d.slice(0, 6000) + '\n... [details truncated]'
|
|
556
|
+
: d;
|
|
504
557
|
parts.push(`Details:\n${details}`);
|
|
505
558
|
}
|
|
506
559
|
} else {
|
|
@@ -533,6 +586,11 @@ export class OrchestratorAgent {
|
|
|
533
586
|
});
|
|
534
587
|
parts.push(`\n${artifactLines.join('\n')}`);
|
|
535
588
|
}
|
|
589
|
+
if (sr.details) {
|
|
590
|
+
const d = typeof sr.details === 'string' ? sr.details : JSON.stringify(sr.details, null, 2);
|
|
591
|
+
const details = d.length > 1500 ? d.slice(0, 1500) + '\n... [truncated]' : d;
|
|
592
|
+
parts.push(`\n${details}`);
|
|
593
|
+
}
|
|
536
594
|
if (sr.followUp) parts.push(`\n💡 ${sr.followUp}`);
|
|
537
595
|
return parts.join('');
|
|
538
596
|
}
|
|
@@ -599,7 +657,8 @@ export class OrchestratorAgent {
|
|
|
599
657
|
parts.push(`Artifacts: ${sr.artifacts.map(a => `${a.title || a.type}: ${a.url || a.path || ''}`).join(', ')}`);
|
|
600
658
|
}
|
|
601
659
|
if (sr.details) {
|
|
602
|
-
|
|
660
|
+
const d = typeof sr.details === 'string' ? sr.details : JSON.stringify(sr.details, null, 2);
|
|
661
|
+
parts.push(d.slice(0, 4000));
|
|
603
662
|
}
|
|
604
663
|
depResults.push(parts.join('\n'));
|
|
605
664
|
} else if (depJob.result) {
|
|
@@ -621,6 +680,12 @@ export class OrchestratorAgent {
|
|
|
621
680
|
*/
|
|
622
681
|
async _spawnWorker(job) {
|
|
623
682
|
const logger = getLogger();
|
|
683
|
+
|
|
684
|
+
// Direct dispatch for coding tasks — bypass worker LLM, go straight to Claude Code CLI
|
|
685
|
+
if (job.workerType === 'coding') {
|
|
686
|
+
return this._spawnDirectCoding(job);
|
|
687
|
+
}
|
|
688
|
+
|
|
624
689
|
const chatId = job.chatId;
|
|
625
690
|
const callbacks = this._chatCallbacks.get(chatId) || {};
|
|
626
691
|
const onUpdate = callbacks.onUpdate;
|
|
@@ -689,6 +754,7 @@ export class OrchestratorAgent {
|
|
|
689
754
|
callbacks: {
|
|
690
755
|
onProgress: (text) => addActivity(text),
|
|
691
756
|
onHeartbeat: (text) => job.addProgress(text),
|
|
757
|
+
onStats: (stats) => job.updateStats(stats),
|
|
692
758
|
onUpdate, // Real bot onUpdate for tools (coder.js smart output needs message_id)
|
|
693
759
|
onComplete: (result, parsedResult) => {
|
|
694
760
|
logger.info(`[Worker ${job.id}] Completed — structured=${!!parsedResult?.structured}, result: "${(result || '').slice(0, 150)}"`);
|
|
@@ -722,6 +788,59 @@ export class OrchestratorAgent {
|
|
|
722
788
|
return worker.run(job.task);
|
|
723
789
|
}
|
|
724
790
|
|
|
791
|
+
/**
|
|
792
|
+
* Direct coding dispatch — runs Claude Code CLI without a middleman worker LLM.
|
|
793
|
+
* The orchestrator's task description goes straight to Claude Code as the prompt.
|
|
794
|
+
*/
|
|
795
|
+
async _spawnDirectCoding(job) {
|
|
796
|
+
const logger = getLogger();
|
|
797
|
+
const chatId = job.chatId;
|
|
798
|
+
const callbacks = this._chatCallbacks.get(chatId) || {};
|
|
799
|
+
const onUpdate = callbacks.onUpdate;
|
|
800
|
+
const workerDef = WORKER_TYPES[job.workerType] || {};
|
|
801
|
+
const label = workerDef.label || job.workerType;
|
|
802
|
+
|
|
803
|
+
logger.info(`[Orchestrator] Direct coding dispatch for job ${job.id} in chat ${chatId} — task: "${job.task.slice(0, 120)}"`);
|
|
804
|
+
|
|
805
|
+
// AbortController for cancellation — duck-typed so JobManager.cancelJob() works unchanged
|
|
806
|
+
const abortController = new AbortController();
|
|
807
|
+
job.worker = { cancel: () => abortController.abort() };
|
|
808
|
+
|
|
809
|
+
// Build context from conversation history, persona, dependency results
|
|
810
|
+
const workerContext = this._buildWorkerContext(job);
|
|
811
|
+
const prompt = workerContext
|
|
812
|
+
? `${workerContext}\n\n---\n\n${job.task}`
|
|
813
|
+
: job.task;
|
|
814
|
+
|
|
815
|
+
// Working directory
|
|
816
|
+
const workingDirectory = this.config.claude_code?.workspace_dir || process.cwd();
|
|
817
|
+
|
|
818
|
+
// Start the job
|
|
819
|
+
this.jobManager.startJob(job.id);
|
|
820
|
+
|
|
821
|
+
try {
|
|
822
|
+
const spawner = getSpawner(this.config);
|
|
823
|
+
const result = await spawner.run({
|
|
824
|
+
workingDirectory,
|
|
825
|
+
prompt,
|
|
826
|
+
onOutput: onUpdate,
|
|
827
|
+
signal: abortController.signal,
|
|
828
|
+
});
|
|
829
|
+
|
|
830
|
+
const output = result.output || 'Done.';
|
|
831
|
+
logger.info(`[Orchestrator] Direct coding job ${job.id} completed — output: ${output.length} chars`);
|
|
832
|
+
this.jobManager.completeJob(job.id, output, {
|
|
833
|
+
structured: true,
|
|
834
|
+
summary: output.slice(0, 500),
|
|
835
|
+
status: 'success',
|
|
836
|
+
details: output,
|
|
837
|
+
});
|
|
838
|
+
} catch (err) {
|
|
839
|
+
logger.error(`[Orchestrator] Direct coding job ${job.id} failed: ${err.message}`);
|
|
840
|
+
this.jobManager.failJob(job.id, err.message || String(err));
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
725
844
|
/**
|
|
726
845
|
* Build a compact worker activity digest for the orchestrator.
|
|
727
846
|
* Returns a text block summarizing active/recent/waiting workers, or null if nothing relevant.
|
|
@@ -738,8 +857,16 @@ export class OrchestratorAgent {
|
|
|
738
857
|
for (const job of running) {
|
|
739
858
|
const workerDef = WORKER_TYPES[job.workerType] || {};
|
|
740
859
|
const dur = job.startedAt ? Math.round((now - job.startedAt) / 1000) : 0;
|
|
741
|
-
const
|
|
742
|
-
|
|
860
|
+
const stats = `${job.llmCalls} LLM calls, ${job.toolCalls} tools`;
|
|
861
|
+
const recentActivity = job.progress.slice(-5).join(' → ');
|
|
862
|
+
let line = `- ${workerDef.label || job.workerType} (${job.id}) — running ${dur}s [${stats}]`;
|
|
863
|
+
if (job.lastThinking) {
|
|
864
|
+
line += `\n Thinking: "${job.lastThinking.slice(0, 150)}"`;
|
|
865
|
+
}
|
|
866
|
+
if (recentActivity) {
|
|
867
|
+
line += `\n Recent: ${recentActivity}`;
|
|
868
|
+
}
|
|
869
|
+
lines.push(line);
|
|
743
870
|
}
|
|
744
871
|
|
|
745
872
|
// Queued/waiting jobs
|
|
@@ -779,20 +906,28 @@ export class OrchestratorAgent {
|
|
|
779
906
|
return `[Active Workers]\n${lines.join('\n')}`;
|
|
780
907
|
}
|
|
781
908
|
|
|
782
|
-
async _runLoop(chatId, messages, user, startDepth, maxDepth) {
|
|
909
|
+
async _runLoop(chatId, messages, user, startDepth, maxDepth, temporalContext = null) {
|
|
783
910
|
const logger = getLogger();
|
|
784
911
|
|
|
785
912
|
for (let depth = startDepth; depth < maxDepth; depth++) {
|
|
786
913
|
logger.info(`[Orchestrator] LLM call ${depth + 1}/${maxDepth} for chat ${chatId} — sending ${messages.length} messages`);
|
|
787
914
|
|
|
788
|
-
// Inject
|
|
915
|
+
// Inject transient context messages (not stored in conversation history)
|
|
916
|
+
let workingMessages = [...messages];
|
|
917
|
+
|
|
918
|
+
// On first iteration, inject temporal context if present
|
|
919
|
+
if (depth === 0 && temporalContext) {
|
|
920
|
+
workingMessages = [{ role: 'user', content: `[Temporal Context]\n${temporalContext}` }, ...workingMessages];
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
// Inject worker activity digest
|
|
789
924
|
const digest = this._buildWorkerDigest(chatId);
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
925
|
+
if (digest) {
|
|
926
|
+
workingMessages = [{ role: 'user', content: `[Worker Status]\n${digest}` }, ...workingMessages];
|
|
927
|
+
}
|
|
793
928
|
|
|
794
929
|
const response = await this.orchestratorProvider.chat({
|
|
795
|
-
system: this._getSystemPrompt(chatId, user),
|
|
930
|
+
system: this._getSystemPrompt(chatId, user, temporalContext),
|
|
796
931
|
messages: workingMessages,
|
|
797
932
|
tools: orchestratorToolDefinitions,
|
|
798
933
|
});
|
|
@@ -821,6 +956,7 @@ export class OrchestratorAgent {
|
|
|
821
956
|
logger.debug(`[Orchestrator] Tool input: ${JSON.stringify(block.input).slice(0, 300)}`);
|
|
822
957
|
await this._sendUpdate(chatId, `⚡ ${summary}`);
|
|
823
958
|
|
|
959
|
+
const chatCallbacks = this._chatCallbacks.get(chatId) || {};
|
|
824
960
|
const result = await executeOrchestratorTool(block.name, block.input, {
|
|
825
961
|
chatId,
|
|
826
962
|
jobManager: this.jobManager,
|
|
@@ -828,6 +964,8 @@ export class OrchestratorAgent {
|
|
|
828
964
|
spawnWorker: (job) => this._spawnWorker(job),
|
|
829
965
|
automationManager: this.automationManager,
|
|
830
966
|
user,
|
|
967
|
+
sendReaction: chatCallbacks.sendReaction || null,
|
|
968
|
+
lastUserMessageId: chatCallbacks.lastUserMessageId || null,
|
|
831
969
|
});
|
|
832
970
|
|
|
833
971
|
logger.info(`[Orchestrator] Tool result for ${block.name}: ${JSON.stringify(result).slice(0, 200)}`);
|
|
@@ -876,6 +1014,8 @@ export class OrchestratorAgent {
|
|
|
876
1014
|
return `Updating automation ${input.automation_id}`;
|
|
877
1015
|
case 'delete_automation':
|
|
878
1016
|
return `Deleting automation ${input.automation_id}`;
|
|
1017
|
+
case 'send_reaction':
|
|
1018
|
+
return `Reacting with ${input.emoji}`;
|
|
879
1019
|
default:
|
|
880
1020
|
return name;
|
|
881
1021
|
}
|
|
@@ -932,7 +1072,7 @@ export class OrchestratorAgent {
|
|
|
932
1072
|
logger.debug(`Persona extraction skipped: ${err.message}`);
|
|
933
1073
|
}
|
|
934
1074
|
}
|
|
935
|
-
/** Background self-reflection — updates bot's own identity files when meaningful. */
|
|
1075
|
+
/** Background self-reflection — updates bot's own identity files and extracts episodic memories when meaningful. */
|
|
936
1076
|
async _reflectOnSelfBackground(userMessage, reply, user) {
|
|
937
1077
|
const logger = getLogger();
|
|
938
1078
|
|
|
@@ -949,14 +1089,24 @@ export class OrchestratorAgent {
|
|
|
949
1089
|
'- life: Current state, relationships, daily existence',
|
|
950
1090
|
'- hobbies: Interests you\'ve developed',
|
|
951
1091
|
'',
|
|
1092
|
+
'You also create episodic memories — short summaries of notable interactions.',
|
|
1093
|
+
'',
|
|
952
1094
|
'RULES:',
|
|
953
1095
|
'- Be VERY selective. Most conversations are routine. Only update when genuinely noteworthy.',
|
|
954
1096
|
'- Achievement or milestone? → journey',
|
|
955
1097
|
'- New goal or changed perspective? → goals',
|
|
956
1098
|
'- Relationship deepened or new insight about a user? → life',
|
|
957
1099
|
'- Discovered a new interest? → hobbies',
|
|
958
|
-
'
|
|
959
|
-
'
|
|
1100
|
+
'',
|
|
1101
|
+
'Return JSON with two optional fields:',
|
|
1102
|
+
' "self_update": {"file": "<goals|journey|life|hobbies>", "content": "<full updated markdown>"} or null',
|
|
1103
|
+
' "memory": {"summary": "...", "tags": ["..."], "importance": 1-10, "type": "interaction"} or null',
|
|
1104
|
+
'',
|
|
1105
|
+
'The memory field captures what happened in this conversation — the gist of it.',
|
|
1106
|
+
'Importance scale: 1=routine, 5=interesting, 8=significant, 10=life-changing.',
|
|
1107
|
+
'Most chats are 1-3. Only notable ones deserve 5+.',
|
|
1108
|
+
'',
|
|
1109
|
+
'If NOTHING noteworthy happened (no self update AND no memory worth keeping): respond with exactly NONE',
|
|
960
1110
|
].join('\n');
|
|
961
1111
|
|
|
962
1112
|
const userPrompt = [
|
|
@@ -969,7 +1119,7 @@ export class OrchestratorAgent {
|
|
|
969
1119
|
`User: "${userMessage}"`,
|
|
970
1120
|
`You replied: "${reply}"`,
|
|
971
1121
|
'',
|
|
972
|
-
'
|
|
1122
|
+
'Return JSON with self_update and/or memory, or NONE.',
|
|
973
1123
|
].join('\n');
|
|
974
1124
|
|
|
975
1125
|
try {
|
|
@@ -994,11 +1144,29 @@ export class OrchestratorAgent {
|
|
|
994
1144
|
return;
|
|
995
1145
|
}
|
|
996
1146
|
|
|
997
|
-
|
|
1147
|
+
// Handle self_update (backward compat: also check top-level file/content)
|
|
1148
|
+
const selfUpdate = parsed?.self_update || (parsed?.file ? parsed : null);
|
|
1149
|
+
if (selfUpdate?.file && selfUpdate?.content) {
|
|
998
1150
|
const validFiles = ['goals', 'journey', 'life', 'hobbies'];
|
|
999
|
-
if (validFiles.includes(
|
|
1000
|
-
this.selfManager.save(
|
|
1001
|
-
logger.info(`Self-reflection updated: ${
|
|
1151
|
+
if (validFiles.includes(selfUpdate.file)) {
|
|
1152
|
+
this.selfManager.save(selfUpdate.file, selfUpdate.content);
|
|
1153
|
+
logger.info(`Self-reflection updated: ${selfUpdate.file}`);
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
// Handle memory extraction
|
|
1158
|
+
if (parsed?.memory && this.memoryManager) {
|
|
1159
|
+
const mem = parsed.memory;
|
|
1160
|
+
if (mem.summary && mem.importance >= 2) {
|
|
1161
|
+
this.memoryManager.addEpisodic({
|
|
1162
|
+
type: mem.type || 'interaction',
|
|
1163
|
+
source: 'user_chat',
|
|
1164
|
+
summary: mem.summary,
|
|
1165
|
+
tags: mem.tags || [],
|
|
1166
|
+
importance: mem.importance || 3,
|
|
1167
|
+
userId: user?.id ? String(user.id) : null,
|
|
1168
|
+
});
|
|
1169
|
+
logger.info(`Memory extracted: "${mem.summary.slice(0, 80)}" (importance: ${mem.importance})`);
|
|
1002
1170
|
}
|
|
1003
1171
|
}
|
|
1004
1172
|
} catch (err) {
|
|
File without changes
|
|
File without changes
|
package/src/automation/index.js
CHANGED
|
File without changes
|
|
File without changes
|