@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
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
// kbot User Behavior — Passive Desktop Observation
|
|
2
|
+
//
|
|
3
|
+
// Captures what the user is doing on their machine: which apps are open,
|
|
4
|
+
// active window, time of day, screen count, whether Ollama is running.
|
|
5
|
+
// Snapshots are stored locally at ~/.kbot/memory/behavior/ as timestamped JSON.
|
|
6
|
+
//
|
|
7
|
+
// Privacy-conscious: captures app names and window titles only.
|
|
8
|
+
// No window contents, no screenshots, no keylogging. macOS only (osascript).
|
|
9
|
+
//
|
|
10
|
+
// Used by the dream engine (Tier 7) to consolidate workflow patterns:
|
|
11
|
+
// - Most-used apps, active hours, app switching habits
|
|
12
|
+
// - App combinations (e.g., "Ableton + Chrome often open together")
|
|
13
|
+
// - Productivity rhythm, context-switching tendencies
|
|
14
|
+
import { execSync } from 'node:child_process';
|
|
15
|
+
import { homedir, platform } from 'node:os';
|
|
16
|
+
import { join } from 'node:path';
|
|
17
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, unlinkSync, } from 'node:fs';
|
|
18
|
+
// ── Constants ──
|
|
19
|
+
const BEHAVIOR_DIR = join(homedir(), '.kbot', 'memory', 'behavior');
|
|
20
|
+
const MAX_SNAPSHOTS = 100;
|
|
21
|
+
// ── Helpers ──
|
|
22
|
+
function ensureDir() {
|
|
23
|
+
if (!existsSync(BEHAVIOR_DIR))
|
|
24
|
+
mkdirSync(BEHAVIOR_DIR, { recursive: true });
|
|
25
|
+
}
|
|
26
|
+
function exec(cmd, timeoutMs = 3000) {
|
|
27
|
+
try {
|
|
28
|
+
return execSync(cmd, {
|
|
29
|
+
encoding: 'utf-8',
|
|
30
|
+
timeout: timeoutMs,
|
|
31
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
32
|
+
}).trim();
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return '';
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** Get list of visible app names via AppleScript (macOS only) */
|
|
39
|
+
function getVisibleApps() {
|
|
40
|
+
if (platform() !== 'darwin')
|
|
41
|
+
return [];
|
|
42
|
+
const raw = exec('osascript -e \'tell application "System Events" to get name of every process whose visible is true\'', 5000);
|
|
43
|
+
if (!raw)
|
|
44
|
+
return [];
|
|
45
|
+
// AppleScript returns comma-separated list
|
|
46
|
+
return raw.split(',').map(s => s.trim()).filter(Boolean);
|
|
47
|
+
}
|
|
48
|
+
/** Get the frontmost app name */
|
|
49
|
+
function getActiveApp() {
|
|
50
|
+
if (platform() !== 'darwin')
|
|
51
|
+
return null;
|
|
52
|
+
const raw = exec('osascript -e \'tell application "System Events" to get name of first process whose frontmost is true\'');
|
|
53
|
+
return raw || null;
|
|
54
|
+
}
|
|
55
|
+
/** Get the active window title */
|
|
56
|
+
function getActiveWindowTitle() {
|
|
57
|
+
if (platform() !== 'darwin')
|
|
58
|
+
return null;
|
|
59
|
+
const raw = exec('osascript -e \'tell application "System Events" to get title of front window of (first process whose frontmost is true)\'');
|
|
60
|
+
// osascript returns empty or error if no window
|
|
61
|
+
if (!raw || raw.includes('error') || raw.includes('missing value'))
|
|
62
|
+
return null;
|
|
63
|
+
return raw;
|
|
64
|
+
}
|
|
65
|
+
/** Get the number of connected screens */
|
|
66
|
+
function getScreenCount() {
|
|
67
|
+
if (platform() !== 'darwin')
|
|
68
|
+
return 1;
|
|
69
|
+
const raw = exec('system_profiler SPDisplaysDataType 2>/dev/null', 5000);
|
|
70
|
+
if (!raw)
|
|
71
|
+
return 1;
|
|
72
|
+
const matches = raw.match(/Resolution:/g);
|
|
73
|
+
return matches ? matches.length : 1;
|
|
74
|
+
}
|
|
75
|
+
/** Check if Ollama process is running */
|
|
76
|
+
function isOllamaRunning() {
|
|
77
|
+
const raw = exec('pgrep -x ollama 2>/dev/null || pgrep -f "ollama serve" 2>/dev/null');
|
|
78
|
+
return raw.length > 0;
|
|
79
|
+
}
|
|
80
|
+
// ── Snapshot Management ──
|
|
81
|
+
function getSnapshotFiles() {
|
|
82
|
+
ensureDir();
|
|
83
|
+
return readdirSync(BEHAVIOR_DIR)
|
|
84
|
+
.filter(f => f.endsWith('.json') && f.startsWith('snap_'))
|
|
85
|
+
.sort();
|
|
86
|
+
}
|
|
87
|
+
/** Prune old snapshots beyond MAX_SNAPSHOTS (delete oldest) */
|
|
88
|
+
function pruneSnapshots() {
|
|
89
|
+
const files = getSnapshotFiles();
|
|
90
|
+
if (files.length <= MAX_SNAPSHOTS)
|
|
91
|
+
return;
|
|
92
|
+
const toDelete = files.slice(0, files.length - MAX_SNAPSHOTS);
|
|
93
|
+
for (const f of toDelete) {
|
|
94
|
+
try {
|
|
95
|
+
unlinkSync(join(BEHAVIOR_DIR, f));
|
|
96
|
+
}
|
|
97
|
+
catch { /* ignore */ }
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function loadSnapshot(filename) {
|
|
101
|
+
try {
|
|
102
|
+
const raw = readFileSync(join(BEHAVIOR_DIR, filename), 'utf-8');
|
|
103
|
+
return JSON.parse(raw);
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// ── Public API ──
|
|
110
|
+
/**
|
|
111
|
+
* Capture a behavior snapshot right now.
|
|
112
|
+
* Runs osascript to detect visible apps, active window, screen count, etc.
|
|
113
|
+
* Stores the snapshot to ~/.kbot/memory/behavior/ as a timestamped JSON file.
|
|
114
|
+
*
|
|
115
|
+
* Non-blocking-safe: catches all errors so it never crashes the caller.
|
|
116
|
+
* macOS only — returns null on other platforms.
|
|
117
|
+
*/
|
|
118
|
+
export function captureUserBehavior() {
|
|
119
|
+
if (platform() !== 'darwin')
|
|
120
|
+
return null;
|
|
121
|
+
try {
|
|
122
|
+
const now = new Date();
|
|
123
|
+
const snapshot = {
|
|
124
|
+
timestamp: now.toISOString(),
|
|
125
|
+
hour: now.getHours(),
|
|
126
|
+
dayOfWeek: now.getDay(),
|
|
127
|
+
visibleApps: getVisibleApps(),
|
|
128
|
+
activeApp: getActiveApp(),
|
|
129
|
+
activeWindowTitle: getActiveWindowTitle(),
|
|
130
|
+
screenCount: getScreenCount(),
|
|
131
|
+
ollamaRunning: isOllamaRunning(),
|
|
132
|
+
};
|
|
133
|
+
ensureDir();
|
|
134
|
+
const filename = `snap_${now.toISOString().replace(/[:.]/g, '-')}.json`;
|
|
135
|
+
writeFileSync(join(BEHAVIOR_DIR, filename), JSON.stringify(snapshot, null, 2));
|
|
136
|
+
// Prune old snapshots
|
|
137
|
+
pruneSnapshots();
|
|
138
|
+
return snapshot;
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
// Non-critical — never crash the caller
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Read recent snapshots and produce a behavior summary.
|
|
147
|
+
* @param hours How many hours of history to analyze (default: 24)
|
|
148
|
+
*/
|
|
149
|
+
export function getBehaviorSummary(hours = 24) {
|
|
150
|
+
try {
|
|
151
|
+
const files = getSnapshotFiles();
|
|
152
|
+
if (files.length === 0)
|
|
153
|
+
return null;
|
|
154
|
+
const cutoff = Date.now() - hours * 60 * 60 * 1000;
|
|
155
|
+
const snapshots = [];
|
|
156
|
+
// Read from newest to oldest, stop when out of range
|
|
157
|
+
for (let i = files.length - 1; i >= 0; i--) {
|
|
158
|
+
const snap = loadSnapshot(files[i]);
|
|
159
|
+
if (!snap)
|
|
160
|
+
continue;
|
|
161
|
+
if (new Date(snap.timestamp).getTime() < cutoff)
|
|
162
|
+
break;
|
|
163
|
+
snapshots.push(snap);
|
|
164
|
+
}
|
|
165
|
+
if (snapshots.length === 0)
|
|
166
|
+
return null;
|
|
167
|
+
// ── App frequency ──
|
|
168
|
+
const appCounts = new Map();
|
|
169
|
+
for (const snap of snapshots) {
|
|
170
|
+
for (const app of snap.visibleApps) {
|
|
171
|
+
appCounts.set(app, (appCounts.get(app) || 0) + 1);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const topApps = Array.from(appCounts.entries())
|
|
175
|
+
.sort((a, b) => b[1] - a[1])
|
|
176
|
+
.slice(0, 15)
|
|
177
|
+
.map(([app, count]) => ({
|
|
178
|
+
app,
|
|
179
|
+
count,
|
|
180
|
+
percent: Math.round((count / snapshots.length) * 100),
|
|
181
|
+
}));
|
|
182
|
+
// ── Active hours ──
|
|
183
|
+
const activeHours = {};
|
|
184
|
+
for (const snap of snapshots) {
|
|
185
|
+
activeHours[snap.hour] = (activeHours[snap.hour] || 0) + 1;
|
|
186
|
+
}
|
|
187
|
+
// ── App combinations (pairs seen together) ──
|
|
188
|
+
const pairCounts = new Map();
|
|
189
|
+
for (const snap of snapshots) {
|
|
190
|
+
const apps = snap.visibleApps.slice().sort();
|
|
191
|
+
for (let i = 0; i < apps.length; i++) {
|
|
192
|
+
for (let j = i + 1; j < apps.length; j++) {
|
|
193
|
+
const key = `${apps[i]}|${apps[j]}`;
|
|
194
|
+
pairCounts.set(key, (pairCounts.get(key) || 0) + 1);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const appCombinations = Array.from(pairCounts.entries())
|
|
199
|
+
.filter(([, count]) => count >= 2) // Only show pairs seen 2+ times
|
|
200
|
+
.sort((a, b) => b[1] - a[1])
|
|
201
|
+
.slice(0, 10)
|
|
202
|
+
.map(([key, count]) => ({
|
|
203
|
+
apps: key.split('|'),
|
|
204
|
+
count,
|
|
205
|
+
}));
|
|
206
|
+
// ── Average visible apps ──
|
|
207
|
+
const totalVisible = snapshots.reduce((sum, s) => sum + s.visibleApps.length, 0);
|
|
208
|
+
const avgVisibleApps = Math.round((totalVisible / snapshots.length) * 10) / 10;
|
|
209
|
+
// ── Most common active app ──
|
|
210
|
+
const activeCounts = new Map();
|
|
211
|
+
for (const snap of snapshots) {
|
|
212
|
+
if (snap.activeApp) {
|
|
213
|
+
activeCounts.set(snap.activeApp, (activeCounts.get(snap.activeApp) || 0) + 1);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
const mostActiveApp = activeCounts.size > 0
|
|
217
|
+
? Array.from(activeCounts.entries()).sort((a, b) => b[1] - a[1])[0][0]
|
|
218
|
+
: null;
|
|
219
|
+
// ── Ollama usage ──
|
|
220
|
+
const ollamaCount = snapshots.filter(s => s.ollamaRunning).length;
|
|
221
|
+
const ollamaUsageRate = Math.round((ollamaCount / snapshots.length) * 100) / 100;
|
|
222
|
+
// ── Human-readable text ──
|
|
223
|
+
const textLines = [];
|
|
224
|
+
textLines.push(`Behavior summary (${snapshots.length} snapshots over ${hours}h):`);
|
|
225
|
+
if (topApps.length > 0) {
|
|
226
|
+
textLines.push(`\nTop apps: ${topApps.slice(0, 8).map(a => `${a.app} (${a.percent}%)`).join(', ')}`);
|
|
227
|
+
}
|
|
228
|
+
if (mostActiveApp) {
|
|
229
|
+
textLines.push(`Most focused app: ${mostActiveApp}`);
|
|
230
|
+
}
|
|
231
|
+
// Active hours summary
|
|
232
|
+
const hourEntries = Object.entries(activeHours)
|
|
233
|
+
.map(([h, c]) => ({ hour: parseInt(h), count: c }))
|
|
234
|
+
.sort((a, b) => b.count - a.count);
|
|
235
|
+
if (hourEntries.length > 0) {
|
|
236
|
+
const peakHours = hourEntries.slice(0, 3).map(e => `${e.hour}:00`).join(', ');
|
|
237
|
+
textLines.push(`Peak activity hours: ${peakHours}`);
|
|
238
|
+
}
|
|
239
|
+
if (appCombinations.length > 0) {
|
|
240
|
+
textLines.push(`\nCommon app pairings:`);
|
|
241
|
+
for (const combo of appCombinations.slice(0, 5)) {
|
|
242
|
+
textLines.push(` ${combo.apps.join(' + ')} (${combo.count}x)`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
textLines.push(`\nAvg visible apps: ${avgVisibleApps}`);
|
|
246
|
+
textLines.push(`Ollama running: ${Math.round(ollamaUsageRate * 100)}% of the time`);
|
|
247
|
+
return {
|
|
248
|
+
snapshotCount: snapshots.length,
|
|
249
|
+
hoursCovered: hours,
|
|
250
|
+
topApps,
|
|
251
|
+
activeHours,
|
|
252
|
+
appCombinations,
|
|
253
|
+
avgVisibleApps,
|
|
254
|
+
mostActiveApp,
|
|
255
|
+
ollamaUsageRate,
|
|
256
|
+
text: textLines.join('\n'),
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Get a compact text summary suitable for dream engine injection.
|
|
265
|
+
* Returns null if no data available.
|
|
266
|
+
*/
|
|
267
|
+
export function getBehaviorForDream(hours = 48) {
|
|
268
|
+
const summary = getBehaviorSummary(hours);
|
|
269
|
+
if (!summary || summary.snapshotCount < 2)
|
|
270
|
+
return null;
|
|
271
|
+
const lines = [];
|
|
272
|
+
// Top apps
|
|
273
|
+
if (summary.topApps.length > 0) {
|
|
274
|
+
lines.push(`Top apps by frequency: ${summary.topApps.slice(0, 10).map(a => `${a.app} (${a.percent}%)`).join(', ')}`);
|
|
275
|
+
}
|
|
276
|
+
// Most focused
|
|
277
|
+
if (summary.mostActiveApp) {
|
|
278
|
+
lines.push(`Most focused (frontmost) app: ${summary.mostActiveApp}`);
|
|
279
|
+
}
|
|
280
|
+
// Active hours
|
|
281
|
+
const hourEntries = Object.entries(summary.activeHours)
|
|
282
|
+
.map(([h, c]) => ({ hour: parseInt(h), count: c }))
|
|
283
|
+
.sort((a, b) => b.count - a.count);
|
|
284
|
+
if (hourEntries.length > 0) {
|
|
285
|
+
const peakHours = hourEntries.slice(0, 4).map(e => `${e.hour}:00`).join(', ');
|
|
286
|
+
lines.push(`Peak activity hours: ${peakHours}`);
|
|
287
|
+
}
|
|
288
|
+
// App combinations
|
|
289
|
+
if (summary.appCombinations.length > 0) {
|
|
290
|
+
const combos = summary.appCombinations.slice(0, 5)
|
|
291
|
+
.map(c => `${c.apps.join(' + ')} (${c.count}x)`)
|
|
292
|
+
.join('; ');
|
|
293
|
+
lines.push(`App combinations: ${combos}`);
|
|
294
|
+
}
|
|
295
|
+
// Context
|
|
296
|
+
lines.push(`Avg visible apps: ${summary.avgVisibleApps}`);
|
|
297
|
+
lines.push(`Ollama running: ${Math.round(summary.ollamaUsageRate * 100)}% of snapshots`);
|
|
298
|
+
lines.push(`Based on ${summary.snapshotCount} snapshots over ${summary.hoursCovered}h`);
|
|
299
|
+
return lines.join('\n');
|
|
300
|
+
}
|
|
301
|
+
//# sourceMappingURL=user-behavior.js.map
|
package/package.json
CHANGED