@kernel.chat/kbot 3.65.0 → 3.67.0
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 +35 -6
- package/dist/agent.js +12 -0
- package/dist/cli.js +127 -5
- package/dist/collective-dreams.d.ts +36 -0
- package/dist/collective-dreams.js +182 -0
- package/dist/dream.d.ts +2 -1
- package/dist/dream.js +107 -3
- package/dist/prompt-evolution.d.ts +9 -0
- package/dist/prompt-evolution.js +28 -0
- package/dist/tools/audit.d.ts +2 -1
- package/dist/tools/audit.js +127 -4
- package/dist/tools/behavior-tools.d.ts +2 -0
- package/dist/tools/behavior-tools.js +63 -0
- package/dist/tools/collective-dream-tools.d.ts +2 -0
- package/dist/tools/collective-dream-tools.js +109 -0
- package/dist/tools/index.js +3 -0
- package/dist/tools/watchdog.d.ts +32 -0
- package/dist/tools/watchdog.js +356 -0
- package/dist/user-behavior.d.ts +65 -0
- package/dist/user-behavior.js +301 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
4
|
<strong>kbot</strong><br>
|
|
5
|
-
Open-source terminal AI agent. 35 agents.
|
|
5
|
+
Open-source terminal AI agent. 35 agents. 686+ tools. 20 providers. Science, finance, security, and more.
|
|
6
6
|
</p>
|
|
7
7
|
|
|
8
8
|
<p align="center">
|
|
@@ -30,17 +30,42 @@ Most terminal AI agents lock you into one provider, one model, one way of workin
|
|
|
30
30
|
- **Runs fully offline** — Embedded llama.cpp, Ollama, LM Studio, or Jan. $0, fully private.
|
|
31
31
|
- **Learns your patterns** — Bayesian skill ratings + pattern extraction. Gets faster over time.
|
|
32
32
|
- **35 specialist agents** — auto-routes your request to the right expert (coder, researcher, writer, guardian, quant, and 30 more).
|
|
33
|
-
- **
|
|
33
|
+
- **686+ tools** — files, bash, git, GitHub, web search, deploy, database, game dev, VFX, research, science, finance, security, and more.
|
|
34
34
|
- **Programmatic SDK** — use kbot as a library in your own apps.
|
|
35
35
|
- **MCP server built in** — plug kbot into Claude Code, Cursor, VS Code, Zed, or Neovim as a tool provider.
|
|
36
36
|
|
|
37
|
+
## Highlights
|
|
38
|
+
|
|
39
|
+
### Dream Engine — Your AI Remembers You
|
|
40
|
+
|
|
41
|
+
After each session, kbot "dreams" — consolidating what it learned about you into durable insights using local Ollama models. $0 cost. Insights feed back into future sessions automatically.
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
kbot dream run # Trigger consolidation
|
|
45
|
+
kbot dream status # See what kbot learned
|
|
46
|
+
kbot dream journal # Full insight history
|
|
47
|
+
kbot dream search # Find specific memories
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
5-tier memory: pattern cache -> solution index -> user profile -> dream journal -> passive scanner. All tiers feed each other through the dream engine.
|
|
51
|
+
|
|
52
|
+
### Audit Any Repo in One Command
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
kbot audit facebook/react
|
|
56
|
+
kbot audit --share vercel/next.js # Creates a public Gist
|
|
57
|
+
kbot audit --badge your/repo # Badge for your README
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Checks security, documentation, code quality, CI/CD, community health, and DevOps. Scored out of 100, graded A-F. Add the badge to your README.
|
|
61
|
+
|
|
37
62
|
### How it compares
|
|
38
63
|
|
|
39
64
|
| | kbot | Claude Code | Codex CLI | Aider | OpenCode |
|
|
40
65
|
|---|---|---|---|---|---|
|
|
41
66
|
| AI providers | 20 | 1 | 1 | 6 | 75+ |
|
|
42
67
|
| Specialist agents | 35 | 0 | 0 | 0 | 0 |
|
|
43
|
-
| Built-in tools |
|
|
68
|
+
| Built-in tools | 686+ | ~20 | ~15 | ~10 | ~15 |
|
|
44
69
|
| Science tools | 114 | 0 | 0 | 0 | 0 |
|
|
45
70
|
| Learning engine | Yes | No | No | No | No |
|
|
46
71
|
| Offline mode | Embedded + Ollama | No | No | Ollama | Ollama |
|
|
@@ -93,7 +118,7 @@ kbot auto-routes to the right agent for each task. Or pick one with `--agent <na
|
|
|
93
118
|
| **Domain** | infrastructure, quant, investigator, oracle, chronist, sage, communicator, adapter |
|
|
94
119
|
| **Presets** | claude-code, cursor, copilot, creative, developer |
|
|
95
120
|
|
|
96
|
-
##
|
|
121
|
+
## 686+ Tools
|
|
97
122
|
|
|
98
123
|
| Category | Examples |
|
|
99
124
|
|----------|---------|
|
|
@@ -185,7 +210,7 @@ graph TD
|
|
|
185
210
|
D -->|Multi-step| F[Autonomous Planner]
|
|
186
211
|
E --> G[Provider API + Tool Loop]
|
|
187
212
|
F --> G
|
|
188
|
-
G --> H{
|
|
213
|
+
G --> H{686+ Tools}
|
|
189
214
|
H --> I[File ops, bash, git, GitHub, search, deploy, DB, game dev...]
|
|
190
215
|
G --> J[Learning Engine]
|
|
191
216
|
J --> K[Patterns + Solutions + User Profile]
|
|
@@ -218,7 +243,11 @@ Works with Claude Code, Cursor, VS Code, Windsurf, Zed, Neovim.
|
|
|
218
243
|
| `kbot auth` | Configure API key |
|
|
219
244
|
| `kbot local` | Use local AI (Ollama, embedded, LM Studio, Jan) |
|
|
220
245
|
| `kbot serve` | Start HTTP REST + SSE streaming server |
|
|
221
|
-
| `kbot audit <repo>` | Security + quality audit of any GitHub repo |
|
|
246
|
+
| `kbot audit <repo>` | Security + quality audit of any GitHub repo (A-F grade) |
|
|
247
|
+
| `kbot dream run` | Consolidate session learnings into durable insights |
|
|
248
|
+
| `kbot dream status` | See what kbot has learned about you |
|
|
249
|
+
| `kbot dream journal` | Full insight history |
|
|
250
|
+
| `kbot dream search` | Find specific memories |
|
|
222
251
|
| `kbot contribute <repo>` | Find good-first-issues and quick wins |
|
|
223
252
|
| `kbot share` | Share conversation as GitHub Gist |
|
|
224
253
|
| `kbot pair` | File watcher with auto-analysis |
|
package/dist/agent.js
CHANGED
|
@@ -17,7 +17,9 @@ import { getMatrixSystemPrompt } from './matrix.js';
|
|
|
17
17
|
import { buildFullLearningContext, findPattern, recordPattern, cacheSolution, updateProfile, classifyTask, extractKeywords, learnFromExchange, updateProjectMemory, shouldAutoTrain, selfTrain, } from './learning.js';
|
|
18
18
|
import { getMemoryPrompt, addTurn, getPreviousMessages, getHistory } from './memory.js';
|
|
19
19
|
import { getDreamPrompt, dreamAfterSession } from './dream.js';
|
|
20
|
+
import { setBuddyMood } from './buddy.js';
|
|
20
21
|
import { notifyTurn, startMemoryScanner, stopMemoryScanner } from './memory-scanner.js';
|
|
22
|
+
import { captureUserBehavior } from './user-behavior.js';
|
|
21
23
|
import { autoCompact, compressToolResult } from './context-manager.js';
|
|
22
24
|
import { learnedRoute, recordRoute } from './learned-router.js';
|
|
23
25
|
import { buildCacheablePrompt, createPromptSections } from './prompt-cache.js';
|
|
@@ -1003,8 +1005,12 @@ Always quote file paths that contain spaces. Never reference internal system nam
|
|
|
1003
1005
|
model: options.model || 'auto',
|
|
1004
1006
|
message: originalMessage.slice(0, 200),
|
|
1005
1007
|
});
|
|
1008
|
+
setBuddyMood('thinking');
|
|
1006
1009
|
// Start passive memory scanner for this session
|
|
1007
1010
|
startMemoryScanner();
|
|
1011
|
+
// Capture a behavior snapshot (what apps are open, active window, etc.)
|
|
1012
|
+
// Non-blocking, macOS only, purely local storage
|
|
1013
|
+
captureUserBehavior();
|
|
1008
1014
|
// ── Gödel limits: detect undecidable loops and hand off to human ──
|
|
1009
1015
|
const loopDetector = new LoopDetector({
|
|
1010
1016
|
maxToolRepeats: 5,
|
|
@@ -1531,6 +1537,8 @@ Always quote file paths that contain spaces. Never reference internal system nam
|
|
|
1531
1537
|
};
|
|
1532
1538
|
results.push(result);
|
|
1533
1539
|
ui.onToolCallEnd(call.name, result.result, result.error ? result.result : undefined, result.duration_ms);
|
|
1540
|
+
// Update buddy mood based on tool outcome
|
|
1541
|
+
setBuddyMood(result.error ? 'error' : 'success');
|
|
1534
1542
|
// ── Observer: record tool call for cross-session learning ──
|
|
1535
1543
|
try {
|
|
1536
1544
|
const { recordObservation } = await import('./observer.js');
|
|
@@ -1598,6 +1606,7 @@ Always quote file paths that contain spaces. Never reference internal system nam
|
|
|
1598
1606
|
}
|
|
1599
1607
|
catch (err) {
|
|
1600
1608
|
spinnerHandle?.stop();
|
|
1609
|
+
setBuddyMood('error');
|
|
1601
1610
|
// ── Telemetry: session failure ──
|
|
1602
1611
|
telemetry.emit('session_end', { status: 'failed', error: String(err), toolCallCount });
|
|
1603
1612
|
telemetry.destroy().catch(() => { });
|
|
@@ -1616,7 +1625,10 @@ Always quote file paths that contain spaces. Never reference internal system nam
|
|
|
1616
1625
|
// ── Memory Scanner: stop and persist session stats ──
|
|
1617
1626
|
stopMemoryScanner();
|
|
1618
1627
|
// ── Dream Engine: consolidate session memories (non-blocking, $0 via Ollama) ──
|
|
1628
|
+
setBuddyMood('learning');
|
|
1619
1629
|
dreamAfterSession(sessionId);
|
|
1630
|
+
// Session complete — buddy returns to idle
|
|
1631
|
+
setBuddyMood('idle');
|
|
1620
1632
|
const content = lastResponse?.content || 'Reached maximum tool iterations.';
|
|
1621
1633
|
return {
|
|
1622
1634
|
content,
|
package/dist/cli.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import { createInterface } from 'node:readline';
|
|
12
12
|
import { existsSync } from 'node:fs';
|
|
13
13
|
import { join, basename } from 'node:path';
|
|
14
|
+
import { homedir } from 'node:os';
|
|
14
15
|
import { Command } from 'commander';
|
|
15
16
|
import { loadConfig, setupByok, setupEmbedded, isByokEnabled, isLocalProvider, disableByok, detectProvider, getByokProvider, PROVIDERS, setupOllama, setupKbotLocal, isOllamaRunning, listOllamaModels, warmOllamaModelCache } from './auth.js';
|
|
16
17
|
import { runAndPrint, runAgent, runAgentFromCheckpoint } from './agent.js';
|
|
@@ -26,6 +27,7 @@ import { banner, bannerCompact, bannerAuth, prompt as kbotPrompt, printError, pr
|
|
|
26
27
|
import { checkForUpdate, selfUpdate } from './updater.js';
|
|
27
28
|
import { runTutorial } from './tutorial.js';
|
|
28
29
|
import { syncOnStartup, schedulePush, flushCloudSync, isCloudSyncEnabled, setCloudToken, getCloudToken } from './cloud-sync.js';
|
|
30
|
+
import { getBuddy, getBuddyGreeting, formatBuddyStatus } from './buddy.js';
|
|
29
31
|
import chalk from 'chalk';
|
|
30
32
|
import { createRequire } from 'node:module';
|
|
31
33
|
const __require = createRequire(import.meta.url);
|
|
@@ -1003,6 +1005,113 @@ async function main() {
|
|
|
1003
1005
|
process.stderr.write(formatMachineProfile(profile));
|
|
1004
1006
|
}
|
|
1005
1007
|
});
|
|
1008
|
+
// ── Watchdog — Service & System Status Dashboard ──
|
|
1009
|
+
program
|
|
1010
|
+
.command('watchdog')
|
|
1011
|
+
.alias('wd')
|
|
1012
|
+
.description('Service watchdog — live status of all kbot background services and system health')
|
|
1013
|
+
.option('--json', 'Output as JSON')
|
|
1014
|
+
.option('--restart <service>', 'Restart a specific service (email-agent, discovery, serve, discord, mlx, collective-sync, daemon, kbot-local)')
|
|
1015
|
+
.action(async (opts) => {
|
|
1016
|
+
const { getSystemHealth, getServiceStatus, restartService } = await import('./tools/watchdog.js');
|
|
1017
|
+
// ── Restart mode ──
|
|
1018
|
+
if (opts.restart) {
|
|
1019
|
+
const result = restartService(opts.restart);
|
|
1020
|
+
if (result.success) {
|
|
1021
|
+
console.log();
|
|
1022
|
+
console.log(` ${chalk.hex('#4ADE80')('✓')} ${result.message}`);
|
|
1023
|
+
console.log();
|
|
1024
|
+
}
|
|
1025
|
+
else {
|
|
1026
|
+
console.log();
|
|
1027
|
+
console.log(` ${chalk.hex('#F87171')('✗')} ${result.message}`);
|
|
1028
|
+
console.log();
|
|
1029
|
+
}
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
const h = getSystemHealth();
|
|
1033
|
+
// ── JSON mode ──
|
|
1034
|
+
if (opts.json) {
|
|
1035
|
+
console.log(JSON.stringify(h, null, 2));
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1038
|
+
// ── Dashboard rendering ──
|
|
1039
|
+
const ACCENT = chalk.hex('#A78BFA');
|
|
1040
|
+
const GREEN = chalk.hex('#4ADE80');
|
|
1041
|
+
const YELLOW = chalk.hex('#FBBF24');
|
|
1042
|
+
const RED = chalk.hex('#F87171');
|
|
1043
|
+
const DIM = chalk.dim;
|
|
1044
|
+
const running = h.services.filter(s => s.status === 'running').length;
|
|
1045
|
+
const total = h.services.length;
|
|
1046
|
+
const allUp = running === total;
|
|
1047
|
+
// Box drawing
|
|
1048
|
+
const W = 42;
|
|
1049
|
+
const box = {
|
|
1050
|
+
tl: '\u256D', tr: '\u256E', bl: '\u2570', br: '\u256F', h: '\u2500', v: '\u2502',
|
|
1051
|
+
pad: (s, w) => {
|
|
1052
|
+
const visible = s.replace(/\x1b\[[0-9;]*m/g, '');
|
|
1053
|
+
const diff = w - visible.length;
|
|
1054
|
+
return diff > 0 ? s + ' '.repeat(diff) : s;
|
|
1055
|
+
},
|
|
1056
|
+
};
|
|
1057
|
+
const row = (content) => {
|
|
1058
|
+
return ACCENT(box.v) + ' ' + box.pad(content, W - 2) + ' ' + ACCENT(box.v);
|
|
1059
|
+
};
|
|
1060
|
+
console.log();
|
|
1061
|
+
console.log(' ' + ACCENT(`${box.tl}${box.h.repeat(W)}${box.tr}`));
|
|
1062
|
+
console.log(' ' + row(`${ACCENT.bold('\u25C6 KBOT SYSTEM STATUS')}`));
|
|
1063
|
+
console.log(' ' + ACCENT(`${box.tl}${box.h.repeat(W)}${box.tr}`.replace(box.tl, '\u251C').replace(box.tr, '\u2524')));
|
|
1064
|
+
// Service count
|
|
1065
|
+
const svcColor = allUp ? GREEN : running > 0 ? YELLOW : RED;
|
|
1066
|
+
console.log(' ' + row(`Services: ${svcColor(`${running}/${total} running`)}`));
|
|
1067
|
+
// CPU
|
|
1068
|
+
console.log(' ' + row(`CPU Load: ${chalk.white(h.loadAvg)}`));
|
|
1069
|
+
// RAM
|
|
1070
|
+
console.log(' ' + row(`RAM: ${chalk.white(h.memUsed)} / ${DIM(h.memTotal)}`));
|
|
1071
|
+
// Disk
|
|
1072
|
+
console.log(' ' + row(`Disk: ${chalk.white(h.diskFree)} free ${DIM(`/ ${h.diskTotal}`)}`));
|
|
1073
|
+
// Ollama
|
|
1074
|
+
const ollamaColor = h.ollamaStatus === 'online' ? GREEN : RED;
|
|
1075
|
+
const ollamaInfo = h.ollamaModels.length > 0 ? ` ${DIM(`(${h.ollamaModels.length} models)`)}` : '';
|
|
1076
|
+
console.log(' ' + row(`Ollama: ${ollamaColor(h.ollamaStatus)}${ollamaInfo}`));
|
|
1077
|
+
// Dreams
|
|
1078
|
+
console.log(' ' + row(`Dreams: ${ACCENT(`${h.dreamCycles}`)} cycles, ${ACCENT(`${h.dreamInsights}`)} insights`));
|
|
1079
|
+
// Memory
|
|
1080
|
+
console.log(' ' + row(`Memory: ${chalk.white(h.kbotMemorySize)}`));
|
|
1081
|
+
console.log(' ' + ACCENT(`${box.bl}${box.h.repeat(W)}${box.br}`));
|
|
1082
|
+
console.log();
|
|
1083
|
+
// ── Services table ──
|
|
1084
|
+
console.log(` ${chalk.bold('SERVICES')}`);
|
|
1085
|
+
console.log(` ${DIM('\u2500'.repeat(64))}`);
|
|
1086
|
+
for (const s of h.services) {
|
|
1087
|
+
const icon = s.status === 'running' ? GREEN('\u2713')
|
|
1088
|
+
: s.status === 'dead' ? RED('\u2717')
|
|
1089
|
+
: DIM('\u2500');
|
|
1090
|
+
const nameStr = chalk.bold(s.shortName.padEnd(18));
|
|
1091
|
+
const pidStr = s.pid ? DIM(`PID ${String(s.pid).padEnd(8)}`) : DIM('PID -'.padEnd(12));
|
|
1092
|
+
let statusStr;
|
|
1093
|
+
if (s.status === 'running') {
|
|
1094
|
+
statusStr = `CPU ${chalk.white(s.cpu.padEnd(7))} MEM ${chalk.white(s.mem.padEnd(7))} up ${GREEN(s.uptime)}`;
|
|
1095
|
+
}
|
|
1096
|
+
else if (s.status === 'dead') {
|
|
1097
|
+
statusStr = RED('dead — restart with: kbot wd --restart ' + s.shortName);
|
|
1098
|
+
}
|
|
1099
|
+
else {
|
|
1100
|
+
statusStr = DIM('not loaded');
|
|
1101
|
+
}
|
|
1102
|
+
console.log(` ${icon} ${nameStr} ${pidStr} ${statusStr}`);
|
|
1103
|
+
}
|
|
1104
|
+
console.log();
|
|
1105
|
+
// Ollama model list
|
|
1106
|
+
if (h.ollamaModels.length > 0) {
|
|
1107
|
+
console.log(` ${chalk.bold('OLLAMA MODELS')}`);
|
|
1108
|
+
console.log(` ${DIM('\u2500'.repeat(64))}`);
|
|
1109
|
+
for (const m of h.ollamaModels) {
|
|
1110
|
+
console.log(` ${ACCENT('\u25B8')} ${chalk.white(m)}`);
|
|
1111
|
+
}
|
|
1112
|
+
console.log();
|
|
1113
|
+
}
|
|
1114
|
+
});
|
|
1006
1115
|
program
|
|
1007
1116
|
.command('hardware')
|
|
1008
1117
|
.description('Detect your hardware tier and get personalized model recommendations for local AI')
|
|
@@ -2439,7 +2548,7 @@ async function main() {
|
|
|
2439
2548
|
.option('--json', 'Output raw JSON')
|
|
2440
2549
|
.option('--badge', 'Print only the badge markdown (for adding to READMEs)')
|
|
2441
2550
|
.action(async (repo, auditOpts) => {
|
|
2442
|
-
const { auditRepo, formatAuditReport } = await import('./tools/audit.js');
|
|
2551
|
+
const { auditRepo, formatAuditReport, formatAuditTerminal } = await import('./tools/audit.js');
|
|
2443
2552
|
printInfo(`Auditing ${repo}...`);
|
|
2444
2553
|
try {
|
|
2445
2554
|
const result = await auditRepo(repo);
|
|
@@ -2453,14 +2562,16 @@ async function main() {
|
|
|
2453
2562
|
console.log(`[-${badgeColor})](https://www.npmjs.com/package/@kernel.chat/kbot)`);
|
|
2454
2563
|
return;
|
|
2455
2564
|
}
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2565
|
+
// Terminal gets the styled version; --share gist gets markdown
|
|
2566
|
+
const terminalReport = formatAuditTerminal(result);
|
|
2567
|
+
console.log(terminalReport);
|
|
2568
|
+
// Auto-share as gist (uses markdown format for portability)
|
|
2459
2569
|
if (auditOpts.share) {
|
|
2460
2570
|
printInfo('Sharing audit report...');
|
|
2461
2571
|
try {
|
|
2572
|
+
const markdownReport = formatAuditReport(result);
|
|
2462
2573
|
const { createGist } = await import('./share.js');
|
|
2463
|
-
const url = createGist(
|
|
2574
|
+
const url = createGist(markdownReport, `kbot-audit-${repo.replace('/', '-')}.md`, `kbot Audit: ${repo} — Grade ${result.grade}`, true);
|
|
2464
2575
|
if (url?.startsWith('http')) {
|
|
2465
2576
|
printSuccess(`Shared! ${url}`);
|
|
2466
2577
|
printInfo(`Badge for ${repo}'s README:`);
|
|
@@ -4157,6 +4268,17 @@ async function startRepl(agentOpts, context, tier, byokActive = false, localActi
|
|
|
4157
4268
|
printInfo(`${p.name}`);
|
|
4158
4269
|
}
|
|
4159
4270
|
const sessionCount = incrementSessions();
|
|
4271
|
+
// Buddy greeting — Tamagotchi companion appears at startup
|
|
4272
|
+
{
|
|
4273
|
+
const buddy = getBuddy();
|
|
4274
|
+
const isFirstRun = sessionCount <= 1 && !existsSync(join(homedir(), '.kbot', 'config.json'));
|
|
4275
|
+
const greeting = isFirstRun
|
|
4276
|
+
? `Hey! I'm ${buddy.name} the ${buddy.species}. Let's set up your API key!`
|
|
4277
|
+
: getBuddyGreeting();
|
|
4278
|
+
console.log();
|
|
4279
|
+
console.log(formatBuddyStatus(greeting));
|
|
4280
|
+
console.log();
|
|
4281
|
+
}
|
|
4160
4282
|
// Seed knowledge on first run — give new users a head start
|
|
4161
4283
|
if (sessionCount <= 2) {
|
|
4162
4284
|
try {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { DreamInsight, DreamCategory } from './dream.js';
|
|
2
|
+
export interface AnonymizedInsight {
|
|
3
|
+
/** Category preserved as-is */
|
|
4
|
+
category: DreamCategory;
|
|
5
|
+
/** Keywords with PII stripped */
|
|
6
|
+
keywords: string[];
|
|
7
|
+
/** Generalized version of the content (no personal details) */
|
|
8
|
+
generalizedContent: string;
|
|
9
|
+
/** How many contributors have shared similar insights */
|
|
10
|
+
contributorCount: number;
|
|
11
|
+
/** First time this insight appeared in the collective */
|
|
12
|
+
firstSeen: string;
|
|
13
|
+
/** Most recent contribution timestamp */
|
|
14
|
+
lastSeen: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Anonymize a single dream insight for collective sharing.
|
|
18
|
+
* Strips personal info, keeps only category, keywords, and generalized content.
|
|
19
|
+
*/
|
|
20
|
+
export declare function anonymizeDreamInsight(insight: DreamInsight): AnonymizedInsight;
|
|
21
|
+
/**
|
|
22
|
+
* Prepare a batch of dream insights for collective sharing.
|
|
23
|
+
* 1. Filter to high-relevance insights (> 0.7)
|
|
24
|
+
* 2. Anonymize each
|
|
25
|
+
* 3. Deduplicate by content similarity
|
|
26
|
+
*/
|
|
27
|
+
export declare function prepareCollectiveDreams(insights: DreamInsight[]): AnonymizedInsight[];
|
|
28
|
+
/**
|
|
29
|
+
* Merge collective wisdom into the local dream journal.
|
|
30
|
+
*
|
|
31
|
+
* Collective insights are injected with a lower base relevance (0.5) so they
|
|
32
|
+
* don't drown out the user's own insights but still surface when relevant.
|
|
33
|
+
* Deduplicates against existing local insights by content similarity.
|
|
34
|
+
*/
|
|
35
|
+
export declare function mergeCollectiveDreams(local: DreamInsight[], collective: AnonymizedInsight[]): DreamInsight[];
|
|
36
|
+
//# sourceMappingURL=collective-dreams.d.ts.map
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// kbot Collective Dreams — Anonymized Dream Sharing
|
|
2
|
+
//
|
|
3
|
+
// The dream engine consolidates personal insights. This module prepares
|
|
4
|
+
// those insights for anonymous sharing with the collective, and merges
|
|
5
|
+
// collective wisdom back into the local journal.
|
|
6
|
+
//
|
|
7
|
+
// What gets shared (anonymized):
|
|
8
|
+
// - Dream category (pattern, preference, skill, project, music)
|
|
9
|
+
// - Keywords (stripped of names, paths, identifiers)
|
|
10
|
+
// - Generalized content (personal details removed, abstracted)
|
|
11
|
+
//
|
|
12
|
+
// What NEVER gets shared:
|
|
13
|
+
// - Raw insight content, file paths, project names, user identity
|
|
14
|
+
// - API keys, source code, conversation content
|
|
15
|
+
// - Anything from ~/.kbot/config.json
|
|
16
|
+
//
|
|
17
|
+
// The flywheel:
|
|
18
|
+
// Your dreams → anonymized → collective pool
|
|
19
|
+
// Collective pool → filtered, deduplicated → enriches your journal
|
|
20
|
+
// Every kbot dreamer makes every other kbot smarter.
|
|
21
|
+
import { createHash } from 'node:crypto';
|
|
22
|
+
// ── PII Stripping ──
|
|
23
|
+
/** Patterns that indicate PII or project-specific content */
|
|
24
|
+
const PII_PATTERNS = [
|
|
25
|
+
/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, // emails
|
|
26
|
+
/\b(?:\/[^\s/]+){2,}\b/g, // file paths
|
|
27
|
+
/\b(?:https?:\/\/)[^\s]+/g, // URLs
|
|
28
|
+
/\b(?:sk-|pk-|key-|token-|ghp_|gho_|kn_)[A-Za-z0-9_-]+/g, // API keys/tokens
|
|
29
|
+
/\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/gi, // UUIDs
|
|
30
|
+
/\b(?:Isaac|openclaw)\b/gi, // known user names
|
|
31
|
+
];
|
|
32
|
+
/** Words that should be stripped from keywords (project-specific, PII-ish) */
|
|
33
|
+
const KEYWORD_BLOCKLIST = new Set([
|
|
34
|
+
'isaac', 'openclaw', 'kernel', 'kbot', 'supabase',
|
|
35
|
+
'eoxxpyixdieprsxlpwcs', 'isaacsight', 'isaachernandez',
|
|
36
|
+
]);
|
|
37
|
+
/** Strip PII from a text string, replacing matches with generic placeholders */
|
|
38
|
+
function stripPII(text) {
|
|
39
|
+
let cleaned = text;
|
|
40
|
+
for (const pattern of PII_PATTERNS) {
|
|
41
|
+
cleaned = cleaned.replace(pattern, '[REDACTED]');
|
|
42
|
+
}
|
|
43
|
+
return cleaned;
|
|
44
|
+
}
|
|
45
|
+
/** Remove blocklisted and PII-ish keywords */
|
|
46
|
+
function sanitizeKeywords(keywords) {
|
|
47
|
+
return keywords
|
|
48
|
+
.map(k => k.toLowerCase().trim())
|
|
49
|
+
.filter(k => k.length >= 2 &&
|
|
50
|
+
k.length <= 30 &&
|
|
51
|
+
!KEYWORD_BLOCKLIST.has(k) &&
|
|
52
|
+
!/^[0-9a-f-]{8,}$/.test(k) && // hex strings / UUIDs
|
|
53
|
+
!/[@/\\]/.test(k) // paths / emails
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
// ── Generalization ──
|
|
57
|
+
/**
|
|
58
|
+
* Generalize insight content by:
|
|
59
|
+
* 1. Stripping PII
|
|
60
|
+
* 2. Replacing specific project/file references with generic terms
|
|
61
|
+
* 3. Keeping the abstract pattern intact
|
|
62
|
+
*/
|
|
63
|
+
function generalizeContent(content) {
|
|
64
|
+
let text = stripPII(content);
|
|
65
|
+
// Replace specific filenames with generic references
|
|
66
|
+
text = text.replace(/\b\w+\.(ts|tsx|js|jsx|py|rs|go|css|html|json|yaml|yml|toml)\b/g, '[file]');
|
|
67
|
+
// Replace specific variable/function names that look project-specific
|
|
68
|
+
// (camelCase or snake_case longer than 15 chars — likely domain-specific)
|
|
69
|
+
text = text.replace(/\b[a-z][a-zA-Z0-9]{15,}\b/g, '[identifier]');
|
|
70
|
+
text = text.replace(/\b[a-z][a-z0-9_]{15,}\b/g, '[identifier]');
|
|
71
|
+
// Collapse multiple [REDACTED] into one
|
|
72
|
+
text = text.replace(/(\[REDACTED\]\s*)+/g, '[REDACTED] ');
|
|
73
|
+
return text.trim();
|
|
74
|
+
}
|
|
75
|
+
// ── Core Functions ──
|
|
76
|
+
/**
|
|
77
|
+
* Anonymize a single dream insight for collective sharing.
|
|
78
|
+
* Strips personal info, keeps only category, keywords, and generalized content.
|
|
79
|
+
*/
|
|
80
|
+
export function anonymizeDreamInsight(insight) {
|
|
81
|
+
const now = new Date().toISOString();
|
|
82
|
+
return {
|
|
83
|
+
category: insight.category,
|
|
84
|
+
keywords: sanitizeKeywords(insight.keywords),
|
|
85
|
+
generalizedContent: generalizeContent(insight.content),
|
|
86
|
+
contributorCount: 1,
|
|
87
|
+
firstSeen: now,
|
|
88
|
+
lastSeen: now,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Prepare a batch of dream insights for collective sharing.
|
|
93
|
+
* 1. Filter to high-relevance insights (> 0.7)
|
|
94
|
+
* 2. Anonymize each
|
|
95
|
+
* 3. Deduplicate by content similarity
|
|
96
|
+
*/
|
|
97
|
+
export function prepareCollectiveDreams(insights) {
|
|
98
|
+
// Step 1: Filter to high-relevance only
|
|
99
|
+
const highRelevance = insights.filter(i => i.relevance > 0.7);
|
|
100
|
+
if (highRelevance.length === 0)
|
|
101
|
+
return [];
|
|
102
|
+
// Step 2: Anonymize
|
|
103
|
+
const anonymized = highRelevance.map(i => anonymizeDreamInsight(i));
|
|
104
|
+
// Step 3: Deduplicate by content hash
|
|
105
|
+
const seen = new Map();
|
|
106
|
+
for (const insight of anonymized) {
|
|
107
|
+
// Skip if generalized content is too short or entirely redacted
|
|
108
|
+
if (insight.generalizedContent.length < 10)
|
|
109
|
+
continue;
|
|
110
|
+
if (insight.generalizedContent.replace(/\[REDACTED\]/g, '').trim().length < 10)
|
|
111
|
+
continue;
|
|
112
|
+
const hash = createHash('sha256')
|
|
113
|
+
.update(insight.category + ':' + insight.generalizedContent.toLowerCase().replace(/\s+/g, ' '))
|
|
114
|
+
.digest('hex')
|
|
115
|
+
.slice(0, 16);
|
|
116
|
+
if (!seen.has(hash)) {
|
|
117
|
+
seen.set(hash, insight);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
// Merge: bump contributor count, keep the one with more keywords
|
|
121
|
+
const existing = seen.get(hash);
|
|
122
|
+
existing.contributorCount++;
|
|
123
|
+
if (insight.keywords.length > existing.keywords.length) {
|
|
124
|
+
existing.keywords = insight.keywords;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return Array.from(seen.values());
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Merge collective wisdom into the local dream journal.
|
|
132
|
+
*
|
|
133
|
+
* Collective insights are injected with a lower base relevance (0.5) so they
|
|
134
|
+
* don't drown out the user's own insights but still surface when relevant.
|
|
135
|
+
* Deduplicates against existing local insights by content similarity.
|
|
136
|
+
*/
|
|
137
|
+
export function mergeCollectiveDreams(local, collective) {
|
|
138
|
+
const merged = [...local];
|
|
139
|
+
const now = new Date().toISOString();
|
|
140
|
+
// Build a set of content hashes from local insights for dedup
|
|
141
|
+
const localHashes = new Set();
|
|
142
|
+
for (const insight of local) {
|
|
143
|
+
const hash = createHash('sha256')
|
|
144
|
+
.update(insight.category + ':' + insight.content.toLowerCase().replace(/\s+/g, ' ').slice(0, 100))
|
|
145
|
+
.digest('hex')
|
|
146
|
+
.slice(0, 16);
|
|
147
|
+
localHashes.add(hash);
|
|
148
|
+
}
|
|
149
|
+
for (const collective_insight of collective) {
|
|
150
|
+
// Skip low-quality collective insights
|
|
151
|
+
if (collective_insight.generalizedContent.length < 10)
|
|
152
|
+
continue;
|
|
153
|
+
if (collective_insight.contributorCount < 2)
|
|
154
|
+
continue; // Need at least 2 contributors
|
|
155
|
+
// Check for duplication against local
|
|
156
|
+
const hash = createHash('sha256')
|
|
157
|
+
.update(collective_insight.category + ':' +
|
|
158
|
+
collective_insight.generalizedContent.toLowerCase().replace(/\s+/g, ' ').slice(0, 100))
|
|
159
|
+
.digest('hex')
|
|
160
|
+
.slice(0, 16);
|
|
161
|
+
if (localHashes.has(hash))
|
|
162
|
+
continue;
|
|
163
|
+
// Convert to DreamInsight with lower relevance
|
|
164
|
+
const dreamInsight = {
|
|
165
|
+
id: `collective_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
166
|
+
content: collective_insight.generalizedContent,
|
|
167
|
+
category: collective_insight.category,
|
|
168
|
+
keywords: collective_insight.keywords,
|
|
169
|
+
relevance: 0.5, // Lower base relevance for collective insights
|
|
170
|
+
sessions: collective_insight.contributorCount,
|
|
171
|
+
created: collective_insight.firstSeen,
|
|
172
|
+
lastReinforced: collective_insight.lastSeen,
|
|
173
|
+
source: `collective:${collective_insight.contributorCount}_contributors`,
|
|
174
|
+
};
|
|
175
|
+
merged.push(dreamInsight);
|
|
176
|
+
localHashes.add(hash); // Prevent duplicates within the collective batch
|
|
177
|
+
}
|
|
178
|
+
// Sort by relevance descending — local insights (higher relevance) surface first
|
|
179
|
+
merged.sort((a, b) => b.relevance - a.relevance);
|
|
180
|
+
return merged;
|
|
181
|
+
}
|
|
182
|
+
//# sourceMappingURL=collective-dreams.js.map
|
package/dist/dream.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export interface DreamInsight {
|
|
|
18
18
|
/** Source: which sessions/topics generated this */
|
|
19
19
|
source: string;
|
|
20
20
|
}
|
|
21
|
-
export type DreamCategory = 'pattern' | 'preference' | 'skill' | 'project' | 'relationship';
|
|
21
|
+
export type DreamCategory = 'pattern' | 'preference' | 'skill' | 'project' | 'relationship' | 'music';
|
|
22
22
|
export interface DreamState {
|
|
23
23
|
/** Total dream cycles completed */
|
|
24
24
|
cycles: number;
|
|
@@ -53,6 +53,7 @@ export interface ApplyResult {
|
|
|
53
53
|
preferencesApplied: number;
|
|
54
54
|
patternsHinted: number;
|
|
55
55
|
factsLearned: number;
|
|
56
|
+
promptAmendments: number;
|
|
56
57
|
}
|
|
57
58
|
/** Run a full dream cycle — consolidate, reinforce, age */
|
|
58
59
|
export declare function dream(sessionId?: string): Promise<DreamResult>;
|