@askexenow/exe-os 0.8.83 → 0.8.85
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/backfill-conversations.js +746 -595
- package/dist/bin/backfill-responses.js +745 -594
- package/dist/bin/backfill-vectors.js +312 -226
- package/dist/bin/cleanup-stale-review-tasks.js +97 -2
- package/dist/bin/cli.js +14350 -12518
- package/dist/bin/exe-agent.js +97 -88
- package/dist/bin/exe-assign.js +1003 -854
- package/dist/bin/exe-boot.js +1257 -320
- package/dist/bin/exe-call.js +10 -0
- package/dist/bin/exe-cloud.js +29 -6
- package/dist/bin/exe-dispatch.js +210 -34
- package/dist/bin/exe-doctor.js +403 -6
- package/dist/bin/exe-export-behaviors.js +175 -72
- package/dist/bin/exe-forget.js +97 -2
- package/dist/bin/exe-gateway.js +550 -171
- package/dist/bin/exe-healthcheck.js +1 -0
- package/dist/bin/exe-heartbeat.js +100 -5
- package/dist/bin/exe-kill.js +175 -72
- package/dist/bin/exe-launch-agent.js +189 -76
- package/dist/bin/exe-link.js +902 -80
- package/dist/bin/exe-new-employee.js +38 -8
- package/dist/bin/exe-pending-messages.js +96 -2
- package/dist/bin/exe-pending-notifications.js +97 -2
- package/dist/bin/exe-pending-reviews.js +98 -3
- package/dist/bin/exe-rename.js +564 -23
- package/dist/bin/exe-review.js +231 -73
- package/dist/bin/exe-search.js +989 -226
- package/dist/bin/exe-session-cleanup.js +4806 -1665
- package/dist/bin/exe-settings.js +20 -5
- package/dist/bin/exe-status.js +97 -2
- package/dist/bin/exe-team.js +97 -2
- package/dist/bin/git-sweep.js +899 -207
- package/dist/bin/graph-backfill.js +175 -72
- package/dist/bin/graph-export.js +175 -72
- package/dist/bin/install.js +38 -7
- package/dist/bin/list-providers.js +1 -0
- package/dist/bin/scan-tasks.js +904 -211
- package/dist/bin/setup.js +867 -268
- package/dist/bin/shard-migrate.js +175 -72
- package/dist/bin/update.js +1 -0
- package/dist/bin/wiki-sync.js +175 -72
- package/dist/gateway/index.js +548 -166
- package/dist/hooks/bug-report-worker.js +208 -23
- package/dist/hooks/commit-complete.js +897 -205
- package/dist/hooks/error-recall.js +988 -226
- package/dist/hooks/ingest-worker.js +1638 -1194
- package/dist/hooks/ingest.js +3 -0
- package/dist/hooks/instructions-loaded.js +707 -97
- package/dist/hooks/notification.js +699 -89
- package/dist/hooks/post-compact.js +714 -104
- package/dist/hooks/pre-compact.js +897 -205
- package/dist/hooks/pre-tool-use.js +742 -123
- package/dist/hooks/prompt-ingest-worker.js +242 -101
- package/dist/hooks/prompt-submit.js +995 -233
- package/dist/hooks/response-ingest-worker.js +242 -101
- package/dist/hooks/session-end.js +3941 -400
- package/dist/hooks/session-start.js +1001 -226
- package/dist/hooks/stop.js +725 -115
- package/dist/hooks/subagent-stop.js +714 -104
- package/dist/hooks/summary-worker.js +1964 -1330
- package/dist/index.js +1651 -1053
- package/dist/lib/cloud-sync.js +907 -86
- package/dist/lib/consolidation.js +2 -1
- package/dist/lib/database.js +642 -87
- package/dist/lib/db-daemon-client.js +503 -0
- package/dist/lib/device-registry.js +547 -7
- package/dist/lib/embedder.js +14 -28
- package/dist/lib/employee-templates.js +84 -74
- package/dist/lib/employees.js +9 -0
- package/dist/lib/exe-daemon-client.js +16 -29
- package/dist/lib/exe-daemon.js +1955 -922
- package/dist/lib/hybrid-search.js +988 -226
- package/dist/lib/identity.js +87 -67
- package/dist/lib/keychain.js +9 -1
- package/dist/lib/messaging.js +8 -1
- package/dist/lib/reminders.js +91 -74
- package/dist/lib/schedules.js +96 -2
- package/dist/lib/skill-learning.js +103 -85
- package/dist/lib/store.js +234 -73
- package/dist/lib/tasks.js +111 -22
- package/dist/lib/tmux-routing.js +120 -31
- package/dist/lib/token-spend.js +273 -0
- package/dist/lib/ws-client.js +11 -0
- package/dist/mcp/server.js +5222 -475
- package/dist/mcp/tools/complete-reminder.js +94 -77
- package/dist/mcp/tools/create-reminder.js +94 -77
- package/dist/mcp/tools/create-task.js +120 -22
- package/dist/mcp/tools/deactivate-behavior.js +95 -77
- package/dist/mcp/tools/list-reminders.js +94 -77
- package/dist/mcp/tools/list-tasks.js +31 -1
- package/dist/mcp/tools/send-message.js +8 -1
- package/dist/mcp/tools/update-task.js +39 -10
- package/dist/runtime/index.js +911 -219
- package/dist/tui/App.js +997 -295
- package/package.json +6 -1
package/dist/bin/exe-assign.js
CHANGED
|
@@ -243,369 +243,693 @@ var init_employees = __esm({
|
|
|
243
243
|
}
|
|
244
244
|
});
|
|
245
245
|
|
|
246
|
-
// src/lib/
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
246
|
+
// src/lib/exe-daemon-client.ts
|
|
247
|
+
import net from "net";
|
|
248
|
+
import { spawn } from "child_process";
|
|
249
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
250
|
+
import { existsSync as existsSync3, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
|
|
251
|
+
import path3 from "path";
|
|
252
|
+
import { fileURLToPath } from "url";
|
|
253
|
+
function handleData(chunk) {
|
|
254
|
+
_buffer += chunk.toString();
|
|
255
|
+
if (_buffer.length > MAX_BUFFER) {
|
|
256
|
+
_buffer = "";
|
|
257
|
+
return;
|
|
251
258
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
async function retryOnBusy(fn, label) {
|
|
258
|
-
let lastError;
|
|
259
|
-
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
259
|
+
let newlineIdx;
|
|
260
|
+
while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
|
|
261
|
+
const line = _buffer.slice(0, newlineIdx).trim();
|
|
262
|
+
_buffer = _buffer.slice(newlineIdx + 1);
|
|
263
|
+
if (!line) continue;
|
|
260
264
|
try {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
265
|
+
const response = JSON.parse(line);
|
|
266
|
+
const id = response.id;
|
|
267
|
+
if (!id) continue;
|
|
268
|
+
const entry = _pending.get(id);
|
|
269
|
+
if (entry) {
|
|
270
|
+
clearTimeout(entry.timer);
|
|
271
|
+
_pending.delete(id);
|
|
272
|
+
entry.resolve(response);
|
|
266
273
|
}
|
|
267
|
-
|
|
268
|
-
const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
|
|
269
|
-
process.stderr.write(
|
|
270
|
-
`[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
|
|
271
|
-
`
|
|
272
|
-
);
|
|
273
|
-
await delay(backoff + jitter);
|
|
274
|
+
} catch {
|
|
274
275
|
}
|
|
275
276
|
}
|
|
276
|
-
throw lastError;
|
|
277
277
|
}
|
|
278
|
-
function
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
278
|
+
function cleanupStaleFiles() {
|
|
279
|
+
if (existsSync3(PID_PATH)) {
|
|
280
|
+
try {
|
|
281
|
+
const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
|
|
282
|
+
if (pid > 0) {
|
|
283
|
+
try {
|
|
284
|
+
process.kill(pid, 0);
|
|
285
|
+
return;
|
|
286
|
+
} catch {
|
|
287
|
+
}
|
|
286
288
|
}
|
|
287
|
-
|
|
289
|
+
} catch {
|
|
288
290
|
}
|
|
289
|
-
|
|
291
|
+
try {
|
|
292
|
+
unlinkSync2(PID_PATH);
|
|
293
|
+
} catch {
|
|
294
|
+
}
|
|
295
|
+
try {
|
|
296
|
+
unlinkSync2(SOCKET_PATH);
|
|
297
|
+
} catch {
|
|
298
|
+
}
|
|
299
|
+
}
|
|
290
300
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
MAX_JITTER_MS = 300;
|
|
301
|
+
function findPackageRoot() {
|
|
302
|
+
let dir = path3.dirname(fileURLToPath(import.meta.url));
|
|
303
|
+
const { root } = path3.parse(dir);
|
|
304
|
+
while (dir !== root) {
|
|
305
|
+
if (existsSync3(path3.join(dir, "package.json"))) return dir;
|
|
306
|
+
dir = path3.dirname(dir);
|
|
298
307
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
_client = null;
|
|
307
|
-
_resilientClient = null;
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
function spawnDaemon() {
|
|
311
|
+
const pkgRoot = findPackageRoot();
|
|
312
|
+
if (!pkgRoot) {
|
|
313
|
+
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
314
|
+
return;
|
|
308
315
|
}
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
316
|
+
const daemonPath = path3.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
317
|
+
if (!existsSync3(daemonPath)) {
|
|
318
|
+
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
319
|
+
`);
|
|
320
|
+
return;
|
|
314
321
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
322
|
+
const resolvedPath = daemonPath;
|
|
323
|
+
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
324
|
+
`);
|
|
325
|
+
const logPath = path3.join(path3.dirname(SOCKET_PATH), "exed.log");
|
|
326
|
+
let stderrFd = "ignore";
|
|
327
|
+
try {
|
|
328
|
+
stderrFd = openSync(logPath, "a");
|
|
329
|
+
} catch {
|
|
330
|
+
}
|
|
331
|
+
const child = spawn(process.execPath, [resolvedPath], {
|
|
332
|
+
detached: true,
|
|
333
|
+
stdio: ["ignore", "ignore", stderrFd],
|
|
334
|
+
env: {
|
|
335
|
+
...process.env,
|
|
336
|
+
TMUX: void 0,
|
|
337
|
+
// Daemon is global — must not inherit session scope
|
|
338
|
+
TMUX_PANE: void 0,
|
|
339
|
+
// Prevents resolveExeSession() from scoping to one session
|
|
340
|
+
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
341
|
+
EXE_DAEMON_PID: PID_PATH
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
child.unref();
|
|
345
|
+
if (typeof stderrFd === "number") {
|
|
346
|
+
try {
|
|
347
|
+
closeSync(stderrFd);
|
|
348
|
+
} catch {
|
|
349
|
+
}
|
|
321
350
|
}
|
|
322
|
-
return _resilientClient;
|
|
323
351
|
}
|
|
324
|
-
function
|
|
325
|
-
|
|
326
|
-
|
|
352
|
+
function acquireSpawnLock() {
|
|
353
|
+
try {
|
|
354
|
+
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
355
|
+
closeSync(fd);
|
|
356
|
+
return true;
|
|
357
|
+
} catch {
|
|
358
|
+
try {
|
|
359
|
+
const stat = statSync(SPAWN_LOCK_PATH);
|
|
360
|
+
if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
|
|
361
|
+
try {
|
|
362
|
+
unlinkSync2(SPAWN_LOCK_PATH);
|
|
363
|
+
} catch {
|
|
364
|
+
}
|
|
365
|
+
try {
|
|
366
|
+
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
367
|
+
closeSync(fd);
|
|
368
|
+
return true;
|
|
369
|
+
} catch {
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
} catch {
|
|
373
|
+
}
|
|
374
|
+
return false;
|
|
327
375
|
}
|
|
328
|
-
return _client;
|
|
329
376
|
}
|
|
330
|
-
|
|
331
|
-
const client = getRawClient();
|
|
332
|
-
await client.execute("PRAGMA journal_mode = WAL");
|
|
333
|
-
await client.execute("PRAGMA busy_timeout = 30000");
|
|
334
|
-
await client.execute("PRAGMA wal_autocheckpoint = 1000");
|
|
377
|
+
function releaseSpawnLock() {
|
|
335
378
|
try {
|
|
336
|
-
|
|
379
|
+
unlinkSync2(SPAWN_LOCK_PATH);
|
|
337
380
|
} catch {
|
|
338
381
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
timestamp TEXT NOT NULL,
|
|
346
|
-
tool_name TEXT NOT NULL,
|
|
347
|
-
project_name TEXT NOT NULL,
|
|
348
|
-
has_error INTEGER NOT NULL DEFAULT 0,
|
|
349
|
-
raw_text TEXT NOT NULL,
|
|
350
|
-
vector F32_BLOB(1024),
|
|
351
|
-
version INTEGER NOT NULL DEFAULT 0
|
|
352
|
-
);
|
|
353
|
-
|
|
354
|
-
CREATE INDEX IF NOT EXISTS idx_memories_agent
|
|
355
|
-
ON memories(agent_id);
|
|
356
|
-
|
|
357
|
-
CREATE INDEX IF NOT EXISTS idx_memories_timestamp
|
|
358
|
-
ON memories(timestamp);
|
|
359
|
-
|
|
360
|
-
CREATE INDEX IF NOT EXISTS idx_memories_session
|
|
361
|
-
ON memories(session_id);
|
|
362
|
-
|
|
363
|
-
CREATE INDEX IF NOT EXISTS idx_memories_project
|
|
364
|
-
ON memories(project_name);
|
|
365
|
-
|
|
366
|
-
CREATE INDEX IF NOT EXISTS idx_memories_tool
|
|
367
|
-
ON memories(tool_name);
|
|
368
|
-
|
|
369
|
-
CREATE INDEX IF NOT EXISTS idx_memories_version
|
|
370
|
-
ON memories(version);
|
|
371
|
-
|
|
372
|
-
CREATE INDEX IF NOT EXISTS idx_memories_agent_project
|
|
373
|
-
ON memories(agent_id, project_name);
|
|
374
|
-
`);
|
|
375
|
-
await client.executeMultiple(`
|
|
376
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
|
|
377
|
-
raw_text,
|
|
378
|
-
content='memories',
|
|
379
|
-
content_rowid='rowid'
|
|
380
|
-
);
|
|
381
|
-
|
|
382
|
-
CREATE TRIGGER IF NOT EXISTS memories_fts_ai AFTER INSERT ON memories BEGIN
|
|
383
|
-
INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
|
|
384
|
-
END;
|
|
385
|
-
|
|
386
|
-
CREATE TRIGGER IF NOT EXISTS memories_fts_ad AFTER DELETE ON memories BEGIN
|
|
387
|
-
INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
|
|
388
|
-
END;
|
|
389
|
-
|
|
390
|
-
CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories BEGIN
|
|
391
|
-
INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
|
|
392
|
-
INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
|
|
393
|
-
END;
|
|
394
|
-
`);
|
|
395
|
-
await client.executeMultiple(`
|
|
396
|
-
CREATE TABLE IF NOT EXISTS sync_meta (
|
|
397
|
-
key TEXT PRIMARY KEY,
|
|
398
|
-
value TEXT NOT NULL
|
|
399
|
-
);
|
|
400
|
-
`);
|
|
401
|
-
await client.executeMultiple(`
|
|
402
|
-
CREATE TABLE IF NOT EXISTS tasks (
|
|
403
|
-
id TEXT PRIMARY KEY,
|
|
404
|
-
title TEXT NOT NULL,
|
|
405
|
-
assigned_to TEXT NOT NULL,
|
|
406
|
-
assigned_by TEXT NOT NULL,
|
|
407
|
-
project_name TEXT NOT NULL,
|
|
408
|
-
priority TEXT NOT NULL DEFAULT 'p1',
|
|
409
|
-
status TEXT NOT NULL DEFAULT 'open',
|
|
410
|
-
task_file TEXT,
|
|
411
|
-
created_at TEXT NOT NULL,
|
|
412
|
-
updated_at TEXT NOT NULL
|
|
413
|
-
);
|
|
414
|
-
|
|
415
|
-
CREATE INDEX IF NOT EXISTS idx_tasks_assignee_status
|
|
416
|
-
ON tasks(assigned_to, status);
|
|
417
|
-
`);
|
|
418
|
-
await client.executeMultiple(`
|
|
419
|
-
CREATE TABLE IF NOT EXISTS behaviors (
|
|
420
|
-
id TEXT PRIMARY KEY,
|
|
421
|
-
agent_id TEXT NOT NULL,
|
|
422
|
-
project_name TEXT,
|
|
423
|
-
domain TEXT,
|
|
424
|
-
content TEXT NOT NULL,
|
|
425
|
-
active INTEGER NOT NULL DEFAULT 1,
|
|
426
|
-
created_at TEXT NOT NULL,
|
|
427
|
-
updated_at TEXT NOT NULL
|
|
428
|
-
);
|
|
429
|
-
|
|
430
|
-
CREATE INDEX IF NOT EXISTS idx_behaviors_agent
|
|
431
|
-
ON behaviors(agent_id, active);
|
|
432
|
-
`);
|
|
433
|
-
try {
|
|
434
|
-
const coordinatorName = getCoordinatorName();
|
|
435
|
-
const existing = await client.execute({
|
|
436
|
-
sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = ?",
|
|
437
|
-
args: [coordinatorName]
|
|
438
|
-
});
|
|
439
|
-
if (Number(existing.rows[0]?.cnt) === 0) {
|
|
440
|
-
const seededAt = "2026-03-25T00:00:00Z";
|
|
441
|
-
for (const [domain, content] of [
|
|
442
|
-
["workflow", `Don't ask "keep going?" \u2014 just keep executing phases/plans autonomously`],
|
|
443
|
-
["tool-use", "Always use create_task MCP tool, never write .md files directly for task creation"],
|
|
444
|
-
["workflow", "Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission"]
|
|
445
|
-
]) {
|
|
446
|
-
await client.execute({
|
|
447
|
-
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
|
|
448
|
-
VALUES (hex(randomblob(16)), ?, NULL, ?, ?, 1, ?, ?)`,
|
|
449
|
-
args: [coordinatorName, domain, content, seededAt, seededAt]
|
|
450
|
-
});
|
|
451
|
-
}
|
|
382
|
+
}
|
|
383
|
+
function connectToSocket() {
|
|
384
|
+
return new Promise((resolve) => {
|
|
385
|
+
if (_socket && _connected) {
|
|
386
|
+
resolve(true);
|
|
387
|
+
return;
|
|
452
388
|
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
389
|
+
const socket = net.createConnection({ path: SOCKET_PATH });
|
|
390
|
+
const connectTimeout = setTimeout(() => {
|
|
391
|
+
socket.destroy();
|
|
392
|
+
resolve(false);
|
|
393
|
+
}, 2e3);
|
|
394
|
+
socket.on("connect", () => {
|
|
395
|
+
clearTimeout(connectTimeout);
|
|
396
|
+
_socket = socket;
|
|
397
|
+
_connected = true;
|
|
398
|
+
_buffer = "";
|
|
399
|
+
socket.on("data", handleData);
|
|
400
|
+
socket.on("close", () => {
|
|
401
|
+
_connected = false;
|
|
402
|
+
_socket = null;
|
|
403
|
+
for (const [id, entry] of _pending) {
|
|
404
|
+
clearTimeout(entry.timer);
|
|
405
|
+
_pending.delete(id);
|
|
406
|
+
entry.resolve({ error: "Connection closed" });
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
socket.on("error", () => {
|
|
410
|
+
_connected = false;
|
|
411
|
+
_socket = null;
|
|
412
|
+
});
|
|
413
|
+
resolve(true);
|
|
459
414
|
});
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
await client.execute({
|
|
464
|
-
sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
|
|
465
|
-
args: []
|
|
415
|
+
socket.on("error", () => {
|
|
416
|
+
clearTimeout(connectTimeout);
|
|
417
|
+
resolve(false);
|
|
466
418
|
});
|
|
467
|
-
}
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
async function connectEmbedDaemon() {
|
|
422
|
+
if (_socket && _connected) return true;
|
|
423
|
+
if (await connectToSocket()) return true;
|
|
424
|
+
if (acquireSpawnLock()) {
|
|
425
|
+
try {
|
|
426
|
+
cleanupStaleFiles();
|
|
427
|
+
spawnDaemon();
|
|
428
|
+
} finally {
|
|
429
|
+
releaseSpawnLock();
|
|
430
|
+
}
|
|
468
431
|
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
432
|
+
const start = Date.now();
|
|
433
|
+
let delay2 = 100;
|
|
434
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
435
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
436
|
+
if (await connectToSocket()) return true;
|
|
437
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
475
438
|
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
439
|
+
return false;
|
|
440
|
+
}
|
|
441
|
+
function sendRequest(texts, priority) {
|
|
442
|
+
return sendDaemonRequest({ texts, priority });
|
|
443
|
+
}
|
|
444
|
+
function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
445
|
+
return new Promise((resolve) => {
|
|
446
|
+
if (!_socket || !_connected) {
|
|
447
|
+
resolve({ error: "Not connected" });
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
const id = randomUUID2();
|
|
451
|
+
const timer = setTimeout(() => {
|
|
452
|
+
_pending.delete(id);
|
|
453
|
+
resolve({ error: "Request timeout" });
|
|
454
|
+
}, timeoutMs);
|
|
455
|
+
_pending.set(id, { resolve, timer });
|
|
456
|
+
try {
|
|
457
|
+
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
458
|
+
} catch {
|
|
459
|
+
clearTimeout(timer);
|
|
460
|
+
_pending.delete(id);
|
|
461
|
+
resolve({ error: "Write failed" });
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
async function pingDaemon() {
|
|
466
|
+
if (!_socket || !_connected) return null;
|
|
467
|
+
const response = await sendDaemonRequest({ type: "health" }, 5e3);
|
|
468
|
+
if (response.health) {
|
|
469
|
+
return response.health;
|
|
484
470
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
471
|
+
return null;
|
|
472
|
+
}
|
|
473
|
+
function killAndRespawnDaemon() {
|
|
474
|
+
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
475
|
+
if (existsSync3(PID_PATH)) {
|
|
476
|
+
try {
|
|
477
|
+
const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
|
|
478
|
+
if (pid > 0) {
|
|
479
|
+
try {
|
|
480
|
+
process.kill(pid, "SIGKILL");
|
|
481
|
+
} catch {
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
} catch {
|
|
485
|
+
}
|
|
491
486
|
}
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
args: []
|
|
496
|
-
});
|
|
497
|
-
} catch {
|
|
487
|
+
if (_socket) {
|
|
488
|
+
_socket.destroy();
|
|
489
|
+
_socket = null;
|
|
498
490
|
}
|
|
491
|
+
_connected = false;
|
|
492
|
+
_buffer = "";
|
|
499
493
|
try {
|
|
500
|
-
|
|
501
|
-
sql: `ALTER TABLE tasks ADD COLUMN context TEXT`,
|
|
502
|
-
args: []
|
|
503
|
-
});
|
|
494
|
+
unlinkSync2(PID_PATH);
|
|
504
495
|
} catch {
|
|
505
496
|
}
|
|
506
497
|
try {
|
|
507
|
-
|
|
508
|
-
sql: `ALTER TABLE tasks ADD COLUMN result TEXT`,
|
|
509
|
-
args: []
|
|
510
|
-
});
|
|
498
|
+
unlinkSync2(SOCKET_PATH);
|
|
511
499
|
} catch {
|
|
512
500
|
}
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
501
|
+
spawnDaemon();
|
|
502
|
+
}
|
|
503
|
+
async function embedViaClient(text, priority = "high") {
|
|
504
|
+
if (!_connected && !await connectEmbedDaemon()) return null;
|
|
505
|
+
_requestCount++;
|
|
506
|
+
if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
|
|
507
|
+
const health = await pingDaemon();
|
|
508
|
+
if (!health) {
|
|
509
|
+
process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
|
|
510
|
+
`);
|
|
511
|
+
killAndRespawnDaemon();
|
|
512
|
+
const start = Date.now();
|
|
513
|
+
let delay2 = 200;
|
|
514
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
515
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
516
|
+
if (await connectToSocket()) break;
|
|
517
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
518
|
+
}
|
|
519
|
+
if (!_connected) return null;
|
|
520
|
+
}
|
|
519
521
|
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
522
|
+
const result = await sendRequest([text], priority);
|
|
523
|
+
if (!result.error && result.vectors?.[0]) return result.vectors[0];
|
|
524
|
+
if (result.error) {
|
|
525
|
+
process.stderr.write(`[exed-client] Embed failed (${result.error}) \u2014 attempting restart
|
|
526
|
+
`);
|
|
527
|
+
killAndRespawnDaemon();
|
|
528
|
+
const start = Date.now();
|
|
529
|
+
let delay2 = 200;
|
|
530
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
531
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
532
|
+
if (await connectToSocket()) break;
|
|
533
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
534
|
+
}
|
|
535
|
+
if (!_connected) return null;
|
|
536
|
+
const retry = await sendRequest([text], priority);
|
|
537
|
+
if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
|
|
538
|
+
process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
|
|
539
|
+
`);
|
|
526
540
|
}
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
541
|
+
return null;
|
|
542
|
+
}
|
|
543
|
+
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, HEALTH_CHECK_INTERVAL, _pending, MAX_BUFFER;
|
|
544
|
+
var init_exe_daemon_client = __esm({
|
|
545
|
+
"src/lib/exe-daemon-client.ts"() {
|
|
546
|
+
"use strict";
|
|
547
|
+
init_config();
|
|
548
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path3.join(EXE_AI_DIR, "exed.sock");
|
|
549
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path3.join(EXE_AI_DIR, "exed.pid");
|
|
550
|
+
SPAWN_LOCK_PATH = path3.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
551
|
+
SPAWN_LOCK_STALE_MS = 3e4;
|
|
552
|
+
CONNECT_TIMEOUT_MS = 15e3;
|
|
553
|
+
REQUEST_TIMEOUT_MS = 3e4;
|
|
554
|
+
_socket = null;
|
|
555
|
+
_connected = false;
|
|
556
|
+
_buffer = "";
|
|
557
|
+
_requestCount = 0;
|
|
558
|
+
HEALTH_CHECK_INTERVAL = 100;
|
|
559
|
+
_pending = /* @__PURE__ */ new Map();
|
|
560
|
+
MAX_BUFFER = 1e7;
|
|
533
561
|
}
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
// src/lib/db-retry.ts
|
|
565
|
+
function isBusyError(err) {
|
|
566
|
+
if (err instanceof Error) {
|
|
567
|
+
const msg = err.message.toLowerCase();
|
|
568
|
+
return msg.includes("sqlite_busy") || msg.includes("database is locked");
|
|
569
|
+
}
|
|
570
|
+
return false;
|
|
571
|
+
}
|
|
572
|
+
function delay(ms) {
|
|
573
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
574
|
+
}
|
|
575
|
+
async function retryOnBusy(fn, label) {
|
|
576
|
+
let lastError;
|
|
577
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
578
|
+
try {
|
|
579
|
+
return await fn();
|
|
580
|
+
} catch (err) {
|
|
581
|
+
lastError = err;
|
|
582
|
+
if (!isBusyError(err) || attempt === MAX_RETRIES) {
|
|
583
|
+
throw err;
|
|
584
|
+
}
|
|
585
|
+
const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
|
|
586
|
+
const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
|
|
587
|
+
process.stderr.write(
|
|
588
|
+
`[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
|
|
589
|
+
`
|
|
590
|
+
);
|
|
591
|
+
await delay(backoff + jitter);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
throw lastError;
|
|
595
|
+
}
|
|
596
|
+
function wrapWithRetry(client) {
|
|
597
|
+
return new Proxy(client, {
|
|
598
|
+
get(target, prop, receiver) {
|
|
599
|
+
if (prop === "execute") {
|
|
600
|
+
return (sql) => retryOnBusy(() => target.execute(sql), "execute");
|
|
601
|
+
}
|
|
602
|
+
if (prop === "batch") {
|
|
603
|
+
return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
|
|
604
|
+
}
|
|
605
|
+
return Reflect.get(target, prop, receiver);
|
|
606
|
+
}
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
|
|
610
|
+
var init_db_retry = __esm({
|
|
611
|
+
"src/lib/db-retry.ts"() {
|
|
612
|
+
"use strict";
|
|
613
|
+
MAX_RETRIES = 3;
|
|
614
|
+
BASE_DELAY_MS = 200;
|
|
615
|
+
MAX_JITTER_MS = 300;
|
|
616
|
+
}
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
// src/lib/database.ts
|
|
620
|
+
import { createClient } from "@libsql/client";
|
|
621
|
+
async function initDatabase(config) {
|
|
622
|
+
if (_client) {
|
|
623
|
+
_client.close();
|
|
624
|
+
_client = null;
|
|
625
|
+
_resilientClient = null;
|
|
626
|
+
}
|
|
627
|
+
const opts = {
|
|
628
|
+
url: `file:${config.dbPath}`
|
|
629
|
+
};
|
|
630
|
+
if (config.encryptionKey) {
|
|
631
|
+
opts.encryptionKey = config.encryptionKey;
|
|
632
|
+
}
|
|
633
|
+
_client = createClient(opts);
|
|
634
|
+
_resilientClient = wrapWithRetry(_client);
|
|
635
|
+
}
|
|
636
|
+
function getClient() {
|
|
637
|
+
if (!_resilientClient) {
|
|
638
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
639
|
+
}
|
|
640
|
+
if (process.env.EXE_IS_DAEMON === "1") {
|
|
641
|
+
return _resilientClient;
|
|
642
|
+
}
|
|
643
|
+
if (_daemonClient && _daemonClient._isDaemonActive()) {
|
|
644
|
+
return _daemonClient;
|
|
645
|
+
}
|
|
646
|
+
return _resilientClient;
|
|
647
|
+
}
|
|
648
|
+
function getRawClient() {
|
|
649
|
+
if (!_client) {
|
|
650
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
651
|
+
}
|
|
652
|
+
return _client;
|
|
653
|
+
}
|
|
654
|
+
async function ensureSchema() {
|
|
655
|
+
const client = getRawClient();
|
|
656
|
+
await client.execute("PRAGMA journal_mode = WAL");
|
|
657
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
658
|
+
await client.execute("PRAGMA wal_autocheckpoint = 1000");
|
|
659
|
+
try {
|
|
660
|
+
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
661
|
+
} catch {
|
|
662
|
+
}
|
|
663
|
+
await client.executeMultiple(`
|
|
664
|
+
CREATE TABLE IF NOT EXISTS memories (
|
|
665
|
+
id TEXT PRIMARY KEY,
|
|
666
|
+
agent_id TEXT NOT NULL,
|
|
667
|
+
agent_role TEXT NOT NULL,
|
|
668
|
+
session_id TEXT NOT NULL,
|
|
669
|
+
timestamp TEXT NOT NULL,
|
|
670
|
+
tool_name TEXT NOT NULL,
|
|
671
|
+
project_name TEXT NOT NULL,
|
|
672
|
+
has_error INTEGER NOT NULL DEFAULT 0,
|
|
673
|
+
raw_text TEXT NOT NULL,
|
|
674
|
+
vector F32_BLOB(1024),
|
|
675
|
+
version INTEGER NOT NULL DEFAULT 0
|
|
676
|
+
);
|
|
677
|
+
|
|
678
|
+
CREATE INDEX IF NOT EXISTS idx_memories_agent
|
|
679
|
+
ON memories(agent_id);
|
|
680
|
+
|
|
681
|
+
CREATE INDEX IF NOT EXISTS idx_memories_timestamp
|
|
682
|
+
ON memories(timestamp);
|
|
683
|
+
|
|
684
|
+
CREATE INDEX IF NOT EXISTS idx_memories_session
|
|
685
|
+
ON memories(session_id);
|
|
686
|
+
|
|
687
|
+
CREATE INDEX IF NOT EXISTS idx_memories_project
|
|
688
|
+
ON memories(project_name);
|
|
689
|
+
|
|
690
|
+
CREATE INDEX IF NOT EXISTS idx_memories_tool
|
|
691
|
+
ON memories(tool_name);
|
|
692
|
+
|
|
693
|
+
CREATE INDEX IF NOT EXISTS idx_memories_version
|
|
694
|
+
ON memories(version);
|
|
695
|
+
|
|
696
|
+
CREATE INDEX IF NOT EXISTS idx_memories_agent_project
|
|
697
|
+
ON memories(agent_id, project_name);
|
|
698
|
+
`);
|
|
699
|
+
await client.executeMultiple(`
|
|
700
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
|
|
701
|
+
raw_text,
|
|
702
|
+
content='memories',
|
|
703
|
+
content_rowid='rowid'
|
|
704
|
+
);
|
|
705
|
+
|
|
706
|
+
CREATE TRIGGER IF NOT EXISTS memories_fts_ai AFTER INSERT ON memories BEGIN
|
|
707
|
+
INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
|
|
708
|
+
END;
|
|
709
|
+
|
|
710
|
+
CREATE TRIGGER IF NOT EXISTS memories_fts_ad AFTER DELETE ON memories BEGIN
|
|
711
|
+
INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
|
|
712
|
+
END;
|
|
713
|
+
|
|
714
|
+
CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories BEGIN
|
|
715
|
+
INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
|
|
716
|
+
INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
|
|
717
|
+
END;
|
|
718
|
+
`);
|
|
719
|
+
await client.executeMultiple(`
|
|
720
|
+
CREATE TABLE IF NOT EXISTS sync_meta (
|
|
721
|
+
key TEXT PRIMARY KEY,
|
|
722
|
+
value TEXT NOT NULL
|
|
723
|
+
);
|
|
724
|
+
`);
|
|
725
|
+
await client.executeMultiple(`
|
|
726
|
+
CREATE TABLE IF NOT EXISTS tasks (
|
|
727
|
+
id TEXT PRIMARY KEY,
|
|
728
|
+
title TEXT NOT NULL,
|
|
729
|
+
assigned_to TEXT NOT NULL,
|
|
730
|
+
assigned_by TEXT NOT NULL,
|
|
731
|
+
project_name TEXT NOT NULL,
|
|
732
|
+
priority TEXT NOT NULL DEFAULT 'p1',
|
|
733
|
+
status TEXT NOT NULL DEFAULT 'open',
|
|
734
|
+
task_file TEXT,
|
|
735
|
+
created_at TEXT NOT NULL,
|
|
736
|
+
updated_at TEXT NOT NULL
|
|
737
|
+
);
|
|
738
|
+
|
|
739
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_assignee_status
|
|
740
|
+
ON tasks(assigned_to, status);
|
|
741
|
+
`);
|
|
742
|
+
await client.executeMultiple(`
|
|
743
|
+
CREATE TABLE IF NOT EXISTS behaviors (
|
|
744
|
+
id TEXT PRIMARY KEY,
|
|
745
|
+
agent_id TEXT NOT NULL,
|
|
746
|
+
project_name TEXT,
|
|
747
|
+
domain TEXT,
|
|
748
|
+
content TEXT NOT NULL,
|
|
749
|
+
active INTEGER NOT NULL DEFAULT 1,
|
|
750
|
+
created_at TEXT NOT NULL,
|
|
751
|
+
updated_at TEXT NOT NULL
|
|
752
|
+
);
|
|
753
|
+
|
|
754
|
+
CREATE INDEX IF NOT EXISTS idx_behaviors_agent
|
|
755
|
+
ON behaviors(agent_id, active);
|
|
756
|
+
`);
|
|
757
|
+
try {
|
|
758
|
+
const coordinatorName = getCoordinatorName();
|
|
759
|
+
const existing = await client.execute({
|
|
760
|
+
sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = ?",
|
|
761
|
+
args: [coordinatorName]
|
|
762
|
+
});
|
|
763
|
+
if (Number(existing.rows[0]?.cnt) === 0) {
|
|
764
|
+
const seededAt = "2026-03-25T00:00:00Z";
|
|
765
|
+
for (const [domain, content] of [
|
|
766
|
+
["workflow", `Don't ask "keep going?" \u2014 just keep executing phases/plans autonomously`],
|
|
767
|
+
["tool-use", "Always use create_task MCP tool, never write .md files directly for task creation"],
|
|
768
|
+
["workflow", "Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission"]
|
|
769
|
+
]) {
|
|
770
|
+
await client.execute({
|
|
771
|
+
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
|
|
772
|
+
VALUES (hex(randomblob(16)), ?, NULL, ?, ?, 1, ?, ?)`,
|
|
773
|
+
args: [coordinatorName, domain, content, seededAt, seededAt]
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
}
|
|
539
777
|
} catch {
|
|
540
778
|
}
|
|
541
779
|
try {
|
|
542
780
|
await client.execute({
|
|
543
|
-
sql: `ALTER TABLE
|
|
781
|
+
sql: `ALTER TABLE behaviors ADD COLUMN priority TEXT DEFAULT 'p1'`,
|
|
544
782
|
args: []
|
|
545
783
|
});
|
|
546
784
|
} catch {
|
|
547
785
|
}
|
|
548
786
|
try {
|
|
549
787
|
await client.execute({
|
|
550
|
-
sql: `ALTER TABLE
|
|
788
|
+
sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
|
|
551
789
|
args: []
|
|
552
790
|
});
|
|
553
791
|
} catch {
|
|
554
792
|
}
|
|
555
793
|
try {
|
|
556
794
|
await client.execute({
|
|
557
|
-
sql: `ALTER TABLE
|
|
795
|
+
sql: `ALTER TABLE tasks ADD COLUMN parent_task_id TEXT`,
|
|
558
796
|
args: []
|
|
559
797
|
});
|
|
560
798
|
} catch {
|
|
561
799
|
}
|
|
562
800
|
try {
|
|
563
801
|
await client.execute({
|
|
564
|
-
sql: `
|
|
802
|
+
sql: `CREATE INDEX IF NOT EXISTS idx_tasks_parent_task_id
|
|
803
|
+
ON tasks(parent_task_id)
|
|
804
|
+
WHERE parent_task_id IS NOT NULL`,
|
|
565
805
|
args: []
|
|
566
806
|
});
|
|
567
807
|
} catch {
|
|
568
808
|
}
|
|
569
809
|
try {
|
|
570
810
|
await client.execute({
|
|
571
|
-
sql: `
|
|
811
|
+
sql: `UPDATE tasks SET status = 'done' WHERE status = 'completed'`,
|
|
572
812
|
args: []
|
|
573
813
|
});
|
|
574
814
|
} catch {
|
|
575
815
|
}
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
816
|
+
try {
|
|
817
|
+
await client.execute({
|
|
818
|
+
sql: `ALTER TABLE tasks ADD COLUMN reviewer TEXT`,
|
|
819
|
+
args: []
|
|
820
|
+
});
|
|
821
|
+
} catch {
|
|
822
|
+
}
|
|
823
|
+
try {
|
|
824
|
+
await client.execute({
|
|
825
|
+
sql: `ALTER TABLE tasks ADD COLUMN context TEXT`,
|
|
826
|
+
args: []
|
|
827
|
+
});
|
|
828
|
+
} catch {
|
|
829
|
+
}
|
|
830
|
+
try {
|
|
831
|
+
await client.execute({
|
|
832
|
+
sql: `ALTER TABLE tasks ADD COLUMN result TEXT`,
|
|
833
|
+
args: []
|
|
834
|
+
});
|
|
835
|
+
} catch {
|
|
836
|
+
}
|
|
837
|
+
try {
|
|
838
|
+
await client.execute({
|
|
839
|
+
sql: `ALTER TABLE tasks ADD COLUMN assigned_tmux TEXT`,
|
|
840
|
+
args: []
|
|
841
|
+
});
|
|
842
|
+
} catch {
|
|
843
|
+
}
|
|
844
|
+
try {
|
|
845
|
+
await client.execute({
|
|
846
|
+
sql: `ALTER TABLE tasks ADD COLUMN checkpoint TEXT`,
|
|
847
|
+
args: []
|
|
848
|
+
});
|
|
849
|
+
} catch {
|
|
850
|
+
}
|
|
851
|
+
try {
|
|
852
|
+
await client.execute({
|
|
853
|
+
sql: `ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER NOT NULL DEFAULT 0`,
|
|
854
|
+
args: []
|
|
855
|
+
});
|
|
856
|
+
} catch {
|
|
857
|
+
}
|
|
858
|
+
try {
|
|
859
|
+
await client.execute({
|
|
860
|
+
sql: `ALTER TABLE tasks ADD COLUMN complexity TEXT NOT NULL DEFAULT 'standard'`,
|
|
861
|
+
args: []
|
|
862
|
+
});
|
|
863
|
+
} catch {
|
|
864
|
+
}
|
|
865
|
+
try {
|
|
866
|
+
await client.execute({
|
|
867
|
+
sql: `ALTER TABLE tasks ADD COLUMN session_scope TEXT`,
|
|
868
|
+
args: []
|
|
869
|
+
});
|
|
870
|
+
} catch {
|
|
871
|
+
}
|
|
872
|
+
try {
|
|
873
|
+
await client.execute({
|
|
874
|
+
sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
|
|
875
|
+
args: []
|
|
876
|
+
});
|
|
877
|
+
} catch {
|
|
878
|
+
}
|
|
879
|
+
try {
|
|
880
|
+
await client.execute({
|
|
881
|
+
sql: `ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0`,
|
|
882
|
+
args: []
|
|
883
|
+
});
|
|
884
|
+
} catch {
|
|
885
|
+
}
|
|
886
|
+
try {
|
|
887
|
+
await client.execute({
|
|
888
|
+
sql: `ALTER TABLE memories ADD COLUMN author_device_id TEXT`,
|
|
889
|
+
args: []
|
|
890
|
+
});
|
|
891
|
+
} catch {
|
|
892
|
+
}
|
|
893
|
+
try {
|
|
894
|
+
await client.execute({
|
|
895
|
+
sql: `ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'`,
|
|
896
|
+
args: []
|
|
897
|
+
});
|
|
898
|
+
} catch {
|
|
899
|
+
}
|
|
900
|
+
await client.executeMultiple(`
|
|
901
|
+
CREATE TABLE IF NOT EXISTS consolidations (
|
|
902
|
+
id TEXT PRIMARY KEY,
|
|
903
|
+
consolidated_memory_id TEXT NOT NULL,
|
|
904
|
+
source_memory_id TEXT NOT NULL,
|
|
905
|
+
created_at TEXT NOT NULL
|
|
906
|
+
);
|
|
907
|
+
|
|
908
|
+
CREATE INDEX IF NOT EXISTS idx_consolidations_source
|
|
909
|
+
ON consolidations(source_memory_id);
|
|
910
|
+
|
|
911
|
+
CREATE INDEX IF NOT EXISTS idx_consolidations_consolidated
|
|
912
|
+
ON consolidations(consolidated_memory_id);
|
|
913
|
+
`);
|
|
914
|
+
await client.executeMultiple(`
|
|
915
|
+
CREATE TABLE IF NOT EXISTS reminders (
|
|
916
|
+
id TEXT PRIMARY KEY,
|
|
917
|
+
text TEXT NOT NULL,
|
|
918
|
+
created_at TEXT NOT NULL,
|
|
919
|
+
due_date TEXT,
|
|
920
|
+
completed_at TEXT
|
|
921
|
+
);
|
|
922
|
+
`);
|
|
923
|
+
await client.executeMultiple(`
|
|
924
|
+
CREATE TABLE IF NOT EXISTS notifications (
|
|
925
|
+
id TEXT PRIMARY KEY,
|
|
926
|
+
agent_id TEXT NOT NULL,
|
|
927
|
+
agent_role TEXT NOT NULL,
|
|
928
|
+
event TEXT NOT NULL,
|
|
929
|
+
project TEXT NOT NULL,
|
|
930
|
+
summary TEXT NOT NULL,
|
|
931
|
+
task_file TEXT,
|
|
932
|
+
read INTEGER NOT NULL DEFAULT 0,
|
|
609
933
|
created_at TEXT NOT NULL
|
|
610
934
|
);
|
|
611
935
|
|
|
@@ -807,6 +1131,12 @@ async function ensureSchema() {
|
|
|
807
1131
|
} catch {
|
|
808
1132
|
}
|
|
809
1133
|
}
|
|
1134
|
+
try {
|
|
1135
|
+
await client.execute(
|
|
1136
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_content_hash ON memories(content_hash, agent_id)`
|
|
1137
|
+
);
|
|
1138
|
+
} catch {
|
|
1139
|
+
}
|
|
810
1140
|
await client.executeMultiple(`
|
|
811
1141
|
CREATE TABLE IF NOT EXISTS entities (
|
|
812
1142
|
id TEXT PRIMARY KEY,
|
|
@@ -859,7 +1189,30 @@ async function ensureSchema() {
|
|
|
859
1189
|
entity_id TEXT NOT NULL,
|
|
860
1190
|
PRIMARY KEY (hyperedge_id, entity_id)
|
|
861
1191
|
);
|
|
1192
|
+
|
|
1193
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
|
|
1194
|
+
name,
|
|
1195
|
+
content=entities,
|
|
1196
|
+
content_rowid=rowid
|
|
1197
|
+
);
|
|
1198
|
+
|
|
1199
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_ai AFTER INSERT ON entities BEGIN
|
|
1200
|
+
INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
|
|
1201
|
+
END;
|
|
1202
|
+
|
|
1203
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_ad AFTER DELETE ON entities BEGIN
|
|
1204
|
+
INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
|
|
1205
|
+
END;
|
|
1206
|
+
|
|
1207
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_au AFTER UPDATE ON entities BEGIN
|
|
1208
|
+
INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
|
|
1209
|
+
INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
|
|
1210
|
+
END;
|
|
862
1211
|
`);
|
|
1212
|
+
try {
|
|
1213
|
+
await client.execute("INSERT INTO entities_fts(entities_fts) VALUES('rebuild')");
|
|
1214
|
+
} catch {
|
|
1215
|
+
}
|
|
863
1216
|
await client.executeMultiple(`
|
|
864
1217
|
CREATE TABLE IF NOT EXISTS entity_aliases (
|
|
865
1218
|
alias TEXT NOT NULL PRIMARY KEY,
|
|
@@ -1040,6 +1393,33 @@ async function ensureSchema() {
|
|
|
1040
1393
|
CREATE INDEX IF NOT EXISTS idx_conversations_channel
|
|
1041
1394
|
ON conversations(channel_id);
|
|
1042
1395
|
`);
|
|
1396
|
+
await client.executeMultiple(`
|
|
1397
|
+
CREATE TABLE IF NOT EXISTS session_agent_map (
|
|
1398
|
+
session_uuid TEXT PRIMARY KEY,
|
|
1399
|
+
agent_id TEXT NOT NULL,
|
|
1400
|
+
session_name TEXT,
|
|
1401
|
+
task_id TEXT,
|
|
1402
|
+
project_name TEXT,
|
|
1403
|
+
started_at TEXT NOT NULL
|
|
1404
|
+
);
|
|
1405
|
+
|
|
1406
|
+
CREATE INDEX IF NOT EXISTS idx_session_agent_map_agent
|
|
1407
|
+
ON session_agent_map(agent_id);
|
|
1408
|
+
`);
|
|
1409
|
+
try {
|
|
1410
|
+
const mapCount = await client.execute({ sql: `SELECT COUNT(*) as cnt FROM session_agent_map`, args: [] });
|
|
1411
|
+
if (Number(mapCount.rows[0]?.cnt ?? 0) === 0) {
|
|
1412
|
+
await client.execute({
|
|
1413
|
+
sql: `INSERT OR IGNORE INTO session_agent_map (session_uuid, agent_id, session_name, started_at)
|
|
1414
|
+
SELECT session_id, agent_id, '', MIN(timestamp)
|
|
1415
|
+
FROM memories
|
|
1416
|
+
WHERE session_id IS NOT NULL AND session_id != '' AND agent_id IS NOT NULL AND agent_id != ''
|
|
1417
|
+
GROUP BY session_id, agent_id`,
|
|
1418
|
+
args: []
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
1421
|
+
} catch {
|
|
1422
|
+
}
|
|
1043
1423
|
try {
|
|
1044
1424
|
await client.execute({
|
|
1045
1425
|
sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
|
|
@@ -1173,8 +1553,30 @@ async function ensureSchema() {
|
|
|
1173
1553
|
});
|
|
1174
1554
|
} catch {
|
|
1175
1555
|
}
|
|
1556
|
+
for (const col of [
|
|
1557
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
1558
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
1559
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
1560
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
1561
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
1562
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
1563
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
1564
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
1565
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
1566
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
1567
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
1568
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
1569
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
1570
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
1571
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
1572
|
+
]) {
|
|
1573
|
+
try {
|
|
1574
|
+
await client.execute(col);
|
|
1575
|
+
} catch {
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1176
1578
|
}
|
|
1177
|
-
var _client, _resilientClient, initTurso;
|
|
1579
|
+
var _client, _resilientClient, _daemonClient, initTurso;
|
|
1178
1580
|
var init_database = __esm({
|
|
1179
1581
|
"src/lib/database.ts"() {
|
|
1180
1582
|
"use strict";
|
|
@@ -1182,6 +1584,7 @@ var init_database = __esm({
|
|
|
1182
1584
|
init_employees();
|
|
1183
1585
|
_client = null;
|
|
1184
1586
|
_resilientClient = null;
|
|
1587
|
+
_daemonClient = null;
|
|
1185
1588
|
initTurso = initDatabase;
|
|
1186
1589
|
}
|
|
1187
1590
|
});
|
|
@@ -1566,509 +1969,184 @@ ${p.content}`).join("\n\n");
|
|
|
1566
1969
|
}
|
|
1567
1970
|
_cacheLoaded = true;
|
|
1568
1971
|
return customerOnly;
|
|
1569
|
-
}
|
|
1570
|
-
function getGlobalProceduresBlock() {
|
|
1571
|
-
const sections = [];
|
|
1572
|
-
if (_platformCache) sections.push(_platformCache);
|
|
1573
|
-
if (_cacheLoaded && _customerCache) sections.push(_customerCache);
|
|
1574
|
-
if (sections.length === 0) return "";
|
|
1575
|
-
return `## Organization-Wide Procedures (MANDATORY \u2014 supersedes all other rules)
|
|
1576
|
-
|
|
1577
|
-
${sections.join("\n\n")}
|
|
1578
|
-
`;
|
|
1579
|
-
}
|
|
1580
|
-
async function storeGlobalProcedure(input) {
|
|
1581
|
-
const id = randomUUID3();
|
|
1582
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1583
|
-
const client = getClient();
|
|
1584
|
-
await client.execute({
|
|
1585
|
-
sql: `INSERT INTO global_procedures (id, title, content, priority, domain, active, created_at, updated_at)
|
|
1586
|
-
VALUES (?, ?, ?, ?, ?, 1, ?, ?)`,
|
|
1587
|
-
args: [id, input.title, input.content, input.priority ?? "p0", input.domain ?? null, now, now]
|
|
1588
|
-
});
|
|
1589
|
-
await loadGlobalProcedures();
|
|
1590
|
-
return id;
|
|
1591
|
-
}
|
|
1592
|
-
async function deactivateGlobalProcedure(id) {
|
|
1593
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1594
|
-
const client = getClient();
|
|
1595
|
-
const result = await client.execute({
|
|
1596
|
-
sql: "UPDATE global_procedures SET active = 0, updated_at = ? WHERE id = ?",
|
|
1597
|
-
args: [now, id]
|
|
1598
|
-
});
|
|
1599
|
-
await loadGlobalProcedures();
|
|
1600
|
-
return result.rowsAffected > 0;
|
|
1601
|
-
}
|
|
1602
|
-
var _customerCache, _cacheLoaded, _platformCache;
|
|
1603
|
-
var init_global_procedures = __esm({
|
|
1604
|
-
"src/lib/global-procedures.ts"() {
|
|
1605
|
-
"use strict";
|
|
1606
|
-
init_database();
|
|
1607
|
-
init_platform_procedures();
|
|
1608
|
-
_customerCache = "";
|
|
1609
|
-
_cacheLoaded = false;
|
|
1610
|
-
_platformCache = PLATFORM_PROCEDURES.map((p) => `### ${p.title}
|
|
1611
|
-
${p.content}`).join("\n\n");
|
|
1612
|
-
}
|
|
1613
|
-
});
|
|
1614
|
-
|
|
1615
|
-
// src/bin/exe-assign.ts
|
|
1616
|
-
init_employees();
|
|
1617
|
-
|
|
1618
|
-
// src/lib/task-router.ts
|
|
1619
|
-
init_employees();
|
|
1620
|
-
import { randomUUID } from "crypto";
|
|
1621
|
-
var DEFAULT_BLOOM_CONFIG = {
|
|
1622
|
-
complexityToTier: {
|
|
1623
|
-
routine: "junior",
|
|
1624
|
-
standard: "standard",
|
|
1625
|
-
complex: "senior",
|
|
1626
|
-
critical: "specialist"
|
|
1627
|
-
},
|
|
1628
|
-
tierRules: {
|
|
1629
|
-
junior: {
|
|
1630
|
-
eligible: [],
|
|
1631
|
-
// resolved dynamically from roster (Principal Engineer role)
|
|
1632
|
-
reviewRequired: false,
|
|
1633
|
-
manualOnly: false
|
|
1634
|
-
},
|
|
1635
|
-
standard: {
|
|
1636
|
-
eligible: [],
|
|
1637
|
-
// resolved dynamically from roster (Principal Engineer role)
|
|
1638
|
-
reviewRequired: false,
|
|
1639
|
-
manualOnly: false
|
|
1640
|
-
},
|
|
1641
|
-
senior: {
|
|
1642
|
-
eligible: [],
|
|
1643
|
-
// any specialist, but review required
|
|
1644
|
-
reviewRequired: true,
|
|
1645
|
-
manualOnly: false
|
|
1646
|
-
},
|
|
1647
|
-
specialist: {
|
|
1648
|
-
eligible: [],
|
|
1649
|
-
reviewRequired: true,
|
|
1650
|
-
manualOnly: true
|
|
1651
|
-
}
|
|
1652
|
-
}
|
|
1653
|
-
};
|
|
1654
|
-
function resolveBloomRouting(complexity, config = DEFAULT_BLOOM_CONFIG) {
|
|
1655
|
-
const tier = config.complexityToTier[complexity];
|
|
1656
|
-
const rule = config.tierRules[tier];
|
|
1657
|
-
return {
|
|
1658
|
-
tier,
|
|
1659
|
-
reviewRequired: rule.reviewRequired,
|
|
1660
|
-
manualOnly: rule.manualOnly,
|
|
1661
|
-
eligible: rule.eligible
|
|
1662
|
-
};
|
|
1663
|
-
}
|
|
1664
|
-
async function scoreEmployee(taskVector, agentId, searchFn) {
|
|
1665
|
-
const results = await searchFn(taskVector, agentId, { limit: 5 });
|
|
1666
|
-
if (results.length === 0) {
|
|
1667
|
-
return { agentId, score: 0 };
|
|
1668
|
-
}
|
|
1669
|
-
const distances = results.map((r) => r["_distance"]).filter((d) => typeof d === "number");
|
|
1670
|
-
if (distances.length > 0) {
|
|
1671
|
-
const avgDistance = distances.reduce((sum, d) => sum + d, 0) / distances.length;
|
|
1672
|
-
return { agentId, score: 1 / (1 + avgDistance) };
|
|
1673
|
-
}
|
|
1674
|
-
return { agentId, score: results.length / 5 };
|
|
1675
|
-
}
|
|
1676
|
-
async function routeTask(taskDescription, employees, embedFn, searchFn, options) {
|
|
1677
|
-
let specialists = employees.filter((e) => !isCoordinatorRole(e.role));
|
|
1678
|
-
if (specialists.length === 0) {
|
|
1679
|
-
throw new Error(
|
|
1680
|
-
"No specialist employees available. Create one with /exe-new-employee."
|
|
1681
|
-
);
|
|
1682
|
-
}
|
|
1683
|
-
let bloomRouting;
|
|
1684
|
-
if (options?.complexity) {
|
|
1685
|
-
bloomRouting = resolveBloomRouting(options.complexity, options.bloomConfig);
|
|
1686
|
-
if (bloomRouting.manualOnly) {
|
|
1687
|
-
throw new Error(
|
|
1688
|
-
`Task complexity "${options.complexity}" requires manual assignment (tier: ${bloomRouting.tier}).`
|
|
1689
|
-
);
|
|
1690
|
-
}
|
|
1691
|
-
if (bloomRouting.eligible.length > 0) {
|
|
1692
|
-
const eligible = new Set(bloomRouting.eligible);
|
|
1693
|
-
const filtered = specialists.filter((e) => eligible.has(e.name));
|
|
1694
|
-
if (filtered.length > 0) {
|
|
1695
|
-
specialists = filtered;
|
|
1696
|
-
}
|
|
1697
|
-
}
|
|
1698
|
-
}
|
|
1699
|
-
const taskVector = await embedFn(taskDescription);
|
|
1700
|
-
const scored = await Promise.all(
|
|
1701
|
-
specialists.map(async (emp) => {
|
|
1702
|
-
const { score } = await scoreEmployee(taskVector, emp.name, searchFn);
|
|
1703
|
-
return { employee: emp, score };
|
|
1704
|
-
})
|
|
1705
|
-
);
|
|
1706
|
-
scored.sort((a, b) => b.score - a.score);
|
|
1707
|
-
return { ...scored[0], bloomRouting };
|
|
1708
|
-
}
|
|
1709
|
-
function createAssignmentMemory(taskDescription, assignedTo, assignedBy) {
|
|
1710
|
-
return {
|
|
1711
|
-
id: randomUUID(),
|
|
1712
|
-
agent_id: assignedTo.name,
|
|
1713
|
-
agent_role: assignedTo.role,
|
|
1714
|
-
session_id: `assign-${Date.now()}`,
|
|
1715
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1716
|
-
tool_name: "assignment",
|
|
1717
|
-
project_name: "",
|
|
1718
|
-
has_error: false,
|
|
1719
|
-
raw_text: `Task assigned by ${assignedBy}: ${taskDescription}`,
|
|
1720
|
-
vector: null
|
|
1721
|
-
// Will be backfilled
|
|
1722
|
-
};
|
|
1723
|
-
}
|
|
1724
|
-
function formatAssignmentOutput(employee, task, score, mode, bloomRouting) {
|
|
1725
|
-
const modeLabel = mode === "auto" ? "auto-routed" : "direct";
|
|
1726
|
-
const percent = Math.round(score * 100);
|
|
1727
|
-
const lines = [
|
|
1728
|
-
`Assigned to ${employee.name} (${employee.role})`,
|
|
1729
|
-
`Mode: ${modeLabel}`,
|
|
1730
|
-
`Relevance: ${percent}%`,
|
|
1731
|
-
`Task: ${task}`
|
|
1732
|
-
];
|
|
1733
|
-
if (bloomRouting) {
|
|
1734
|
-
lines.push(`Complexity tier: ${bloomRouting.tier}`);
|
|
1735
|
-
if (bloomRouting.reviewRequired) {
|
|
1736
|
-
lines.push(`Review: required`);
|
|
1737
|
-
}
|
|
1738
|
-
}
|
|
1739
|
-
return lines.join("\n");
|
|
1740
|
-
}
|
|
1741
|
-
|
|
1742
|
-
// src/types/memory.ts
|
|
1743
|
-
var EMBEDDING_DIM = 1024;
|
|
1744
|
-
|
|
1745
|
-
// src/lib/exe-daemon-client.ts
|
|
1746
|
-
init_config();
|
|
1747
|
-
import net from "net";
|
|
1748
|
-
import { spawn } from "child_process";
|
|
1749
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
1750
|
-
import { existsSync as existsSync3, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
|
|
1751
|
-
import path3 from "path";
|
|
1752
|
-
import { fileURLToPath } from "url";
|
|
1753
|
-
var SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path3.join(EXE_AI_DIR, "exed.sock");
|
|
1754
|
-
var PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path3.join(EXE_AI_DIR, "exed.pid");
|
|
1755
|
-
var SPAWN_LOCK_PATH = path3.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
1756
|
-
var SPAWN_LOCK_STALE_MS = 3e4;
|
|
1757
|
-
var CONNECT_TIMEOUT_MS = 15e3;
|
|
1758
|
-
var REQUEST_TIMEOUT_MS = 3e4;
|
|
1759
|
-
var _socket = null;
|
|
1760
|
-
var _connected = false;
|
|
1761
|
-
var _buffer = "";
|
|
1762
|
-
var _requestCount = 0;
|
|
1763
|
-
var HEALTH_CHECK_INTERVAL = 100;
|
|
1764
|
-
var _pending = /* @__PURE__ */ new Map();
|
|
1765
|
-
var MAX_BUFFER = 1e7;
|
|
1766
|
-
function handleData(chunk) {
|
|
1767
|
-
_buffer += chunk.toString();
|
|
1768
|
-
if (_buffer.length > MAX_BUFFER) {
|
|
1769
|
-
_buffer = "";
|
|
1770
|
-
return;
|
|
1771
|
-
}
|
|
1772
|
-
let newlineIdx;
|
|
1773
|
-
while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
|
|
1774
|
-
const line = _buffer.slice(0, newlineIdx).trim();
|
|
1775
|
-
_buffer = _buffer.slice(newlineIdx + 1);
|
|
1776
|
-
if (!line) continue;
|
|
1777
|
-
try {
|
|
1778
|
-
const response = JSON.parse(line);
|
|
1779
|
-
const entry = _pending.get(response.id);
|
|
1780
|
-
if (entry) {
|
|
1781
|
-
clearTimeout(entry.timer);
|
|
1782
|
-
_pending.delete(response.id);
|
|
1783
|
-
entry.resolve(response);
|
|
1784
|
-
}
|
|
1785
|
-
} catch {
|
|
1786
|
-
}
|
|
1787
|
-
}
|
|
1788
|
-
}
|
|
1789
|
-
function cleanupStaleFiles() {
|
|
1790
|
-
if (existsSync3(PID_PATH)) {
|
|
1791
|
-
try {
|
|
1792
|
-
const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
|
|
1793
|
-
if (pid > 0) {
|
|
1794
|
-
try {
|
|
1795
|
-
process.kill(pid, 0);
|
|
1796
|
-
return;
|
|
1797
|
-
} catch {
|
|
1798
|
-
}
|
|
1799
|
-
}
|
|
1800
|
-
} catch {
|
|
1801
|
-
}
|
|
1802
|
-
try {
|
|
1803
|
-
unlinkSync2(PID_PATH);
|
|
1804
|
-
} catch {
|
|
1805
|
-
}
|
|
1806
|
-
try {
|
|
1807
|
-
unlinkSync2(SOCKET_PATH);
|
|
1808
|
-
} catch {
|
|
1809
|
-
}
|
|
1810
|
-
}
|
|
1811
|
-
}
|
|
1812
|
-
function findPackageRoot() {
|
|
1813
|
-
let dir = path3.dirname(fileURLToPath(import.meta.url));
|
|
1814
|
-
const { root } = path3.parse(dir);
|
|
1815
|
-
while (dir !== root) {
|
|
1816
|
-
if (existsSync3(path3.join(dir, "package.json"))) return dir;
|
|
1817
|
-
dir = path3.dirname(dir);
|
|
1818
|
-
}
|
|
1819
|
-
return null;
|
|
1820
|
-
}
|
|
1821
|
-
function spawnDaemon() {
|
|
1822
|
-
const pkgRoot = findPackageRoot();
|
|
1823
|
-
if (!pkgRoot) {
|
|
1824
|
-
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
1825
|
-
return;
|
|
1826
|
-
}
|
|
1827
|
-
const daemonPath = path3.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
1828
|
-
if (!existsSync3(daemonPath)) {
|
|
1829
|
-
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
1830
|
-
`);
|
|
1831
|
-
return;
|
|
1832
|
-
}
|
|
1833
|
-
const resolvedPath = daemonPath;
|
|
1834
|
-
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
1835
|
-
`);
|
|
1836
|
-
const logPath = path3.join(path3.dirname(SOCKET_PATH), "exed.log");
|
|
1837
|
-
let stderrFd = "ignore";
|
|
1838
|
-
try {
|
|
1839
|
-
stderrFd = openSync(logPath, "a");
|
|
1840
|
-
} catch {
|
|
1841
|
-
}
|
|
1842
|
-
const child = spawn(process.execPath, [resolvedPath], {
|
|
1843
|
-
detached: true,
|
|
1844
|
-
stdio: ["ignore", "ignore", stderrFd],
|
|
1845
|
-
env: {
|
|
1846
|
-
...process.env,
|
|
1847
|
-
TMUX: void 0,
|
|
1848
|
-
// Daemon is global — must not inherit session scope
|
|
1849
|
-
TMUX_PANE: void 0,
|
|
1850
|
-
// Prevents resolveExeSession() from scoping to one session
|
|
1851
|
-
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
1852
|
-
EXE_DAEMON_PID: PID_PATH
|
|
1853
|
-
}
|
|
1854
|
-
});
|
|
1855
|
-
child.unref();
|
|
1856
|
-
if (typeof stderrFd === "number") {
|
|
1857
|
-
try {
|
|
1858
|
-
closeSync(stderrFd);
|
|
1859
|
-
} catch {
|
|
1860
|
-
}
|
|
1861
|
-
}
|
|
1862
|
-
}
|
|
1863
|
-
function acquireSpawnLock() {
|
|
1864
|
-
try {
|
|
1865
|
-
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
1866
|
-
closeSync(fd);
|
|
1867
|
-
return true;
|
|
1868
|
-
} catch {
|
|
1869
|
-
try {
|
|
1870
|
-
const stat = statSync(SPAWN_LOCK_PATH);
|
|
1871
|
-
if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
|
|
1872
|
-
try {
|
|
1873
|
-
unlinkSync2(SPAWN_LOCK_PATH);
|
|
1874
|
-
} catch {
|
|
1875
|
-
}
|
|
1876
|
-
try {
|
|
1877
|
-
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
1878
|
-
closeSync(fd);
|
|
1879
|
-
return true;
|
|
1880
|
-
} catch {
|
|
1881
|
-
}
|
|
1882
|
-
}
|
|
1883
|
-
} catch {
|
|
1884
|
-
}
|
|
1885
|
-
return false;
|
|
1886
|
-
}
|
|
1887
|
-
}
|
|
1888
|
-
function releaseSpawnLock() {
|
|
1889
|
-
try {
|
|
1890
|
-
unlinkSync2(SPAWN_LOCK_PATH);
|
|
1891
|
-
} catch {
|
|
1892
|
-
}
|
|
1893
|
-
}
|
|
1894
|
-
function connectToSocket() {
|
|
1895
|
-
return new Promise((resolve) => {
|
|
1896
|
-
if (_socket && _connected) {
|
|
1897
|
-
resolve(true);
|
|
1898
|
-
return;
|
|
1899
|
-
}
|
|
1900
|
-
const socket = net.createConnection({ path: SOCKET_PATH });
|
|
1901
|
-
const connectTimeout = setTimeout(() => {
|
|
1902
|
-
socket.destroy();
|
|
1903
|
-
resolve(false);
|
|
1904
|
-
}, 2e3);
|
|
1905
|
-
socket.on("connect", () => {
|
|
1906
|
-
clearTimeout(connectTimeout);
|
|
1907
|
-
_socket = socket;
|
|
1908
|
-
_connected = true;
|
|
1909
|
-
_buffer = "";
|
|
1910
|
-
socket.on("data", handleData);
|
|
1911
|
-
socket.on("close", () => {
|
|
1912
|
-
_connected = false;
|
|
1913
|
-
_socket = null;
|
|
1914
|
-
for (const [id, entry] of _pending) {
|
|
1915
|
-
clearTimeout(entry.timer);
|
|
1916
|
-
_pending.delete(id);
|
|
1917
|
-
entry.resolve({ error: "Connection closed" });
|
|
1918
|
-
}
|
|
1919
|
-
});
|
|
1920
|
-
socket.on("error", () => {
|
|
1921
|
-
_connected = false;
|
|
1922
|
-
_socket = null;
|
|
1923
|
-
});
|
|
1924
|
-
resolve(true);
|
|
1925
|
-
});
|
|
1926
|
-
socket.on("error", () => {
|
|
1927
|
-
clearTimeout(connectTimeout);
|
|
1928
|
-
resolve(false);
|
|
1929
|
-
});
|
|
1930
|
-
});
|
|
1931
|
-
}
|
|
1932
|
-
async function connectEmbedDaemon() {
|
|
1933
|
-
if (_socket && _connected) return true;
|
|
1934
|
-
if (await connectToSocket()) return true;
|
|
1935
|
-
if (acquireSpawnLock()) {
|
|
1936
|
-
try {
|
|
1937
|
-
cleanupStaleFiles();
|
|
1938
|
-
spawnDaemon();
|
|
1939
|
-
} finally {
|
|
1940
|
-
releaseSpawnLock();
|
|
1941
|
-
}
|
|
1942
|
-
}
|
|
1943
|
-
const start = Date.now();
|
|
1944
|
-
let delay2 = 100;
|
|
1945
|
-
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
1946
|
-
await new Promise((r) => setTimeout(r, delay2));
|
|
1947
|
-
if (await connectToSocket()) return true;
|
|
1948
|
-
delay2 = Math.min(delay2 * 2, 3e3);
|
|
1949
|
-
}
|
|
1950
|
-
return false;
|
|
1951
|
-
}
|
|
1952
|
-
function sendRequest(texts, priority) {
|
|
1953
|
-
return new Promise((resolve) => {
|
|
1954
|
-
if (!_socket || !_connected) {
|
|
1955
|
-
resolve({ error: "Not connected" });
|
|
1956
|
-
return;
|
|
1957
|
-
}
|
|
1958
|
-
const id = randomUUID2();
|
|
1959
|
-
const timer = setTimeout(() => {
|
|
1960
|
-
_pending.delete(id);
|
|
1961
|
-
resolve({ error: "Request timeout" });
|
|
1962
|
-
}, REQUEST_TIMEOUT_MS);
|
|
1963
|
-
_pending.set(id, { resolve, timer });
|
|
1964
|
-
try {
|
|
1965
|
-
_socket.write(JSON.stringify({ id, texts, priority }) + "\n");
|
|
1966
|
-
} catch {
|
|
1967
|
-
clearTimeout(timer);
|
|
1968
|
-
_pending.delete(id);
|
|
1969
|
-
resolve({ error: "Write failed" });
|
|
1970
|
-
}
|
|
1972
|
+
}
|
|
1973
|
+
function getGlobalProceduresBlock() {
|
|
1974
|
+
const sections = [];
|
|
1975
|
+
if (_platformCache) sections.push(_platformCache);
|
|
1976
|
+
if (_cacheLoaded && _customerCache) sections.push(_customerCache);
|
|
1977
|
+
if (sections.length === 0) return "";
|
|
1978
|
+
return `## Organization-Wide Procedures (MANDATORY \u2014 supersedes all other rules)
|
|
1979
|
+
|
|
1980
|
+
${sections.join("\n\n")}
|
|
1981
|
+
`;
|
|
1982
|
+
}
|
|
1983
|
+
async function storeGlobalProcedure(input) {
|
|
1984
|
+
const id = randomUUID3();
|
|
1985
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1986
|
+
const client = getClient();
|
|
1987
|
+
await client.execute({
|
|
1988
|
+
sql: `INSERT INTO global_procedures (id, title, content, priority, domain, active, created_at, updated_at)
|
|
1989
|
+
VALUES (?, ?, ?, ?, ?, 1, ?, ?)`,
|
|
1990
|
+
args: [id, input.title, input.content, input.priority ?? "p0", input.domain ?? null, now, now]
|
|
1971
1991
|
});
|
|
1992
|
+
await loadGlobalProcedures();
|
|
1993
|
+
return id;
|
|
1972
1994
|
}
|
|
1973
|
-
async function
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
resolve(null);
|
|
1980
|
-
}, 5e3);
|
|
1981
|
-
_pending.set(id, {
|
|
1982
|
-
resolve: (data) => {
|
|
1983
|
-
if (data.health) {
|
|
1984
|
-
resolve(data.health);
|
|
1985
|
-
} else {
|
|
1986
|
-
resolve(null);
|
|
1987
|
-
}
|
|
1988
|
-
},
|
|
1989
|
-
timer
|
|
1990
|
-
});
|
|
1991
|
-
try {
|
|
1992
|
-
_socket.write(JSON.stringify({ id, type: "health" }) + "\n");
|
|
1993
|
-
} catch {
|
|
1994
|
-
clearTimeout(timer);
|
|
1995
|
-
_pending.delete(id);
|
|
1996
|
-
resolve(null);
|
|
1997
|
-
}
|
|
1995
|
+
async function deactivateGlobalProcedure(id) {
|
|
1996
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1997
|
+
const client = getClient();
|
|
1998
|
+
const result = await client.execute({
|
|
1999
|
+
sql: "UPDATE global_procedures SET active = 0, updated_at = ? WHERE id = ?",
|
|
2000
|
+
args: [now, id]
|
|
1998
2001
|
});
|
|
2002
|
+
await loadGlobalProcedures();
|
|
2003
|
+
return result.rowsAffected > 0;
|
|
1999
2004
|
}
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
}
|
|
2011
|
-
} catch {
|
|
2012
|
-
}
|
|
2005
|
+
var _customerCache, _cacheLoaded, _platformCache;
|
|
2006
|
+
var init_global_procedures = __esm({
|
|
2007
|
+
"src/lib/global-procedures.ts"() {
|
|
2008
|
+
"use strict";
|
|
2009
|
+
init_database();
|
|
2010
|
+
init_platform_procedures();
|
|
2011
|
+
_customerCache = "";
|
|
2012
|
+
_cacheLoaded = false;
|
|
2013
|
+
_platformCache = PLATFORM_PROCEDURES.map((p) => `### ${p.title}
|
|
2014
|
+
${p.content}`).join("\n\n");
|
|
2013
2015
|
}
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2016
|
+
});
|
|
2017
|
+
|
|
2018
|
+
// src/bin/exe-assign.ts
|
|
2019
|
+
init_employees();
|
|
2020
|
+
|
|
2021
|
+
// src/lib/task-router.ts
|
|
2022
|
+
init_employees();
|
|
2023
|
+
import { randomUUID } from "crypto";
|
|
2024
|
+
var DEFAULT_BLOOM_CONFIG = {
|
|
2025
|
+
complexityToTier: {
|
|
2026
|
+
routine: "junior",
|
|
2027
|
+
standard: "standard",
|
|
2028
|
+
complex: "senior",
|
|
2029
|
+
critical: "specialist"
|
|
2030
|
+
},
|
|
2031
|
+
tierRules: {
|
|
2032
|
+
junior: {
|
|
2033
|
+
eligible: [],
|
|
2034
|
+
// resolved dynamically from roster (Principal Engineer role)
|
|
2035
|
+
reviewRequired: false,
|
|
2036
|
+
manualOnly: false
|
|
2037
|
+
},
|
|
2038
|
+
standard: {
|
|
2039
|
+
eligible: [],
|
|
2040
|
+
// resolved dynamically from roster (Principal Engineer role)
|
|
2041
|
+
reviewRequired: false,
|
|
2042
|
+
manualOnly: false
|
|
2043
|
+
},
|
|
2044
|
+
senior: {
|
|
2045
|
+
eligible: [],
|
|
2046
|
+
// any specialist, but review required
|
|
2047
|
+
reviewRequired: true,
|
|
2048
|
+
manualOnly: false
|
|
2049
|
+
},
|
|
2050
|
+
specialist: {
|
|
2051
|
+
eligible: [],
|
|
2052
|
+
reviewRequired: true,
|
|
2053
|
+
manualOnly: true
|
|
2054
|
+
}
|
|
2017
2055
|
}
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2056
|
+
};
|
|
2057
|
+
function resolveBloomRouting(complexity, config = DEFAULT_BLOOM_CONFIG) {
|
|
2058
|
+
const tier = config.complexityToTier[complexity];
|
|
2059
|
+
const rule = config.tierRules[tier];
|
|
2060
|
+
return {
|
|
2061
|
+
tier,
|
|
2062
|
+
reviewRequired: rule.reviewRequired,
|
|
2063
|
+
manualOnly: rule.manualOnly,
|
|
2064
|
+
eligible: rule.eligible
|
|
2065
|
+
};
|
|
2066
|
+
}
|
|
2067
|
+
async function scoreEmployee(taskVector, agentId, searchFn) {
|
|
2068
|
+
const results = await searchFn(taskVector, agentId, { limit: 5 });
|
|
2069
|
+
if (results.length === 0) {
|
|
2070
|
+
return { agentId, score: 0 };
|
|
2023
2071
|
}
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2072
|
+
const distances = results.map((r) => r["_distance"]).filter((d) => typeof d === "number");
|
|
2073
|
+
if (distances.length > 0) {
|
|
2074
|
+
const avgDistance = distances.reduce((sum, d) => sum + d, 0) / distances.length;
|
|
2075
|
+
return { agentId, score: 1 / (1 + avgDistance) };
|
|
2027
2076
|
}
|
|
2028
|
-
|
|
2077
|
+
return { agentId, score: results.length / 5 };
|
|
2029
2078
|
}
|
|
2030
|
-
async function
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2079
|
+
async function routeTask(taskDescription, employees, embedFn, searchFn, options) {
|
|
2080
|
+
let specialists = employees.filter((e) => !isCoordinatorRole(e.role));
|
|
2081
|
+
if (specialists.length === 0) {
|
|
2082
|
+
throw new Error(
|
|
2083
|
+
"No specialist employees available. Create one with /exe-new-employee."
|
|
2084
|
+
);
|
|
2085
|
+
}
|
|
2086
|
+
let bloomRouting;
|
|
2087
|
+
if (options?.complexity) {
|
|
2088
|
+
bloomRouting = resolveBloomRouting(options.complexity, options.bloomConfig);
|
|
2089
|
+
if (bloomRouting.manualOnly) {
|
|
2090
|
+
throw new Error(
|
|
2091
|
+
`Task complexity "${options.complexity}" requires manual assignment (tier: ${bloomRouting.tier}).`
|
|
2092
|
+
);
|
|
2093
|
+
}
|
|
2094
|
+
if (bloomRouting.eligible.length > 0) {
|
|
2095
|
+
const eligible = new Set(bloomRouting.eligible);
|
|
2096
|
+
const filtered = specialists.filter((e) => eligible.has(e.name));
|
|
2097
|
+
if (filtered.length > 0) {
|
|
2098
|
+
specialists = filtered;
|
|
2045
2099
|
}
|
|
2046
|
-
if (!_connected) return null;
|
|
2047
2100
|
}
|
|
2048
2101
|
}
|
|
2049
|
-
const
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2102
|
+
const taskVector = await embedFn(taskDescription);
|
|
2103
|
+
const scored = await Promise.all(
|
|
2104
|
+
specialists.map(async (emp) => {
|
|
2105
|
+
const { score } = await scoreEmployee(taskVector, emp.name, searchFn);
|
|
2106
|
+
return { employee: emp, score };
|
|
2107
|
+
})
|
|
2108
|
+
);
|
|
2109
|
+
scored.sort((a, b) => b.score - a.score);
|
|
2110
|
+
return { ...scored[0], bloomRouting };
|
|
2111
|
+
}
|
|
2112
|
+
function createAssignmentMemory(taskDescription, assignedTo, assignedBy) {
|
|
2113
|
+
return {
|
|
2114
|
+
id: randomUUID(),
|
|
2115
|
+
agent_id: assignedTo.name,
|
|
2116
|
+
agent_role: assignedTo.role,
|
|
2117
|
+
session_id: `assign-${Date.now()}`,
|
|
2118
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2119
|
+
tool_name: "assignment",
|
|
2120
|
+
project_name: "",
|
|
2121
|
+
has_error: false,
|
|
2122
|
+
raw_text: `Task assigned by ${assignedBy}: ${taskDescription}`,
|
|
2123
|
+
vector: null
|
|
2124
|
+
// Will be backfilled
|
|
2125
|
+
};
|
|
2126
|
+
}
|
|
2127
|
+
function formatAssignmentOutput(employee, task, score, mode, bloomRouting) {
|
|
2128
|
+
const modeLabel = mode === "auto" ? "auto-routed" : "direct";
|
|
2129
|
+
const percent = Math.round(score * 100);
|
|
2130
|
+
const lines = [
|
|
2131
|
+
`Assigned to ${employee.name} (${employee.role})`,
|
|
2132
|
+
`Mode: ${modeLabel}`,
|
|
2133
|
+
`Relevance: ${percent}%`,
|
|
2134
|
+
`Task: ${task}`
|
|
2135
|
+
];
|
|
2136
|
+
if (bloomRouting) {
|
|
2137
|
+
lines.push(`Complexity tier: ${bloomRouting.tier}`);
|
|
2138
|
+
if (bloomRouting.reviewRequired) {
|
|
2139
|
+
lines.push(`Review: required`);
|
|
2061
2140
|
}
|
|
2062
|
-
if (!_connected) return null;
|
|
2063
|
-
const retry = await sendRequest([text], priority);
|
|
2064
|
-
if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
|
|
2065
|
-
process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
|
|
2066
|
-
`);
|
|
2067
2141
|
}
|
|
2068
|
-
return
|
|
2142
|
+
return lines.join("\n");
|
|
2069
2143
|
}
|
|
2070
2144
|
|
|
2145
|
+
// src/types/memory.ts
|
|
2146
|
+
var EMBEDDING_DIM = 1024;
|
|
2147
|
+
|
|
2071
2148
|
// src/lib/embedder.ts
|
|
2149
|
+
init_exe_daemon_client();
|
|
2072
2150
|
async function embed(text) {
|
|
2073
2151
|
const priority = process.env.EXE_EMBED_PRIORITY === "low" ? "low" : "high";
|
|
2074
2152
|
const vector = await embedViaClient(text, priority);
|
|
@@ -2086,6 +2164,7 @@ async function embed(text) {
|
|
|
2086
2164
|
}
|
|
2087
2165
|
|
|
2088
2166
|
// src/lib/store.ts
|
|
2167
|
+
import { createHash } from "crypto";
|
|
2089
2168
|
init_database();
|
|
2090
2169
|
|
|
2091
2170
|
// src/lib/keychain.ts
|
|
@@ -2121,12 +2200,20 @@ async function getMasterKey() {
|
|
|
2121
2200
|
}
|
|
2122
2201
|
const keyPath = getKeyPath();
|
|
2123
2202
|
if (!existsSync4(keyPath)) {
|
|
2203
|
+
process.stderr.write(
|
|
2204
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os3.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
2205
|
+
`
|
|
2206
|
+
);
|
|
2124
2207
|
return null;
|
|
2125
2208
|
}
|
|
2126
2209
|
try {
|
|
2127
2210
|
const content = await readFile3(keyPath, "utf-8");
|
|
2128
2211
|
return Buffer.from(content.trim(), "base64");
|
|
2129
|
-
} catch {
|
|
2212
|
+
} catch (err) {
|
|
2213
|
+
process.stderr.write(
|
|
2214
|
+
`[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
|
|
2215
|
+
`
|
|
2216
|
+
);
|
|
2130
2217
|
return null;
|
|
2131
2218
|
}
|
|
2132
2219
|
}
|
|
@@ -2265,12 +2352,52 @@ function classifyTier(record) {
|
|
|
2265
2352
|
if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
|
|
2266
2353
|
return 3;
|
|
2267
2354
|
}
|
|
2355
|
+
function inferFilePaths(record) {
|
|
2356
|
+
if (!["Read", "Write", "Edit"].includes(record.tool_name)) return null;
|
|
2357
|
+
const firstLine = record.raw_text.split("\n")[0] ?? "";
|
|
2358
|
+
const match = firstLine.match(/(\/[\w./-]+\.\w+)/);
|
|
2359
|
+
return match ? JSON.stringify([match[1]]) : null;
|
|
2360
|
+
}
|
|
2361
|
+
function inferCommitHash(record) {
|
|
2362
|
+
if (record.tool_name !== "Bash") return null;
|
|
2363
|
+
const match = record.raw_text.match(/\b([a-f0-9]{7,40})\b/);
|
|
2364
|
+
return match ? match[1] : null;
|
|
2365
|
+
}
|
|
2366
|
+
function inferLanguageType(record) {
|
|
2367
|
+
const text = record.raw_text;
|
|
2368
|
+
if (!text || text.length < 10) return null;
|
|
2369
|
+
const trimmed = text.trimStart();
|
|
2370
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) return "json";
|
|
2371
|
+
if (/\b(SELECT|INSERT|UPDATE|DELETE|CREATE TABLE|ALTER TABLE)\b/i.test(text)) return "sql";
|
|
2372
|
+
if (/\b(function |const |import |export |class |def |async |=>)\b/.test(text)) return "code";
|
|
2373
|
+
if (trimmed.startsWith("#") || trimmed.startsWith("*")) return "prose";
|
|
2374
|
+
return "mixed";
|
|
2375
|
+
}
|
|
2376
|
+
function inferDomain(record) {
|
|
2377
|
+
const proj = (record.project_name ?? "").toLowerCase();
|
|
2378
|
+
if (proj.includes("marketing") || proj.includes("content")) return "marketing";
|
|
2379
|
+
if (proj.includes("crm") || proj.includes("customer")) return "customer";
|
|
2380
|
+
return null;
|
|
2381
|
+
}
|
|
2268
2382
|
async function writeMemory(record) {
|
|
2269
2383
|
if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
|
|
2270
2384
|
throw new Error(
|
|
2271
2385
|
`Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
|
|
2272
2386
|
);
|
|
2273
2387
|
}
|
|
2388
|
+
const contentHash = createHash("md5").update(record.raw_text).digest("hex");
|
|
2389
|
+
if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
|
|
2390
|
+
return;
|
|
2391
|
+
}
|
|
2392
|
+
try {
|
|
2393
|
+
const client = getClient();
|
|
2394
|
+
const existing = await client.execute({
|
|
2395
|
+
sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
|
|
2396
|
+
args: [contentHash, record.agent_id]
|
|
2397
|
+
});
|
|
2398
|
+
if (existing.rows.length > 0) return;
|
|
2399
|
+
} catch {
|
|
2400
|
+
}
|
|
2274
2401
|
const dbRow = {
|
|
2275
2402
|
id: record.id,
|
|
2276
2403
|
agent_id: record.agent_id,
|
|
@@ -2300,7 +2427,23 @@ async function writeMemory(record) {
|
|
|
2300
2427
|
supersedes_id: record.supersedes_id ?? null,
|
|
2301
2428
|
draft: record.draft ? 1 : 0,
|
|
2302
2429
|
memory_type: record.memory_type ?? "raw",
|
|
2303
|
-
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
|
|
2430
|
+
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
|
|
2431
|
+
content_hash: contentHash,
|
|
2432
|
+
intent: record.intent ?? null,
|
|
2433
|
+
outcome: record.outcome ?? null,
|
|
2434
|
+
domain: record.domain ?? inferDomain(record),
|
|
2435
|
+
referenced_entities: record.referenced_entities ?? null,
|
|
2436
|
+
retrieval_count: record.retrieval_count ?? 0,
|
|
2437
|
+
chain_position: record.chain_position ?? null,
|
|
2438
|
+
review_status: record.review_status ?? null,
|
|
2439
|
+
context_window_pct: record.context_window_pct ?? null,
|
|
2440
|
+
file_paths: record.file_paths ?? inferFilePaths(record),
|
|
2441
|
+
commit_hash: record.commit_hash ?? inferCommitHash(record),
|
|
2442
|
+
duration_ms: record.duration_ms ?? null,
|
|
2443
|
+
token_cost: record.token_cost ?? null,
|
|
2444
|
+
audience: record.audience ?? null,
|
|
2445
|
+
language_type: record.language_type ?? inferLanguageType(record),
|
|
2446
|
+
parent_memory_id: record.parent_memory_id ?? null
|
|
2304
2447
|
};
|
|
2305
2448
|
_pendingRecords.push(dbRow);
|
|
2306
2449
|
orgBus.emit({
|
|
@@ -2358,80 +2501,85 @@ async function flushBatch() {
|
|
|
2358
2501
|
const draft = row.draft ? 1 : 0;
|
|
2359
2502
|
const memoryType = row.memory_type ?? "raw";
|
|
2360
2503
|
const trajectory = row.trajectory ?? null;
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2504
|
+
const contentHash = row.content_hash ?? null;
|
|
2505
|
+
const intent = row.intent ?? null;
|
|
2506
|
+
const outcome = row.outcome ?? null;
|
|
2507
|
+
const domain = row.domain ?? null;
|
|
2508
|
+
const referencedEntities = row.referenced_entities ?? null;
|
|
2509
|
+
const retrievalCount = row.retrieval_count ?? 0;
|
|
2510
|
+
const chainPosition = row.chain_position ?? null;
|
|
2511
|
+
const reviewStatus = row.review_status ?? null;
|
|
2512
|
+
const contextWindowPct = row.context_window_pct ?? null;
|
|
2513
|
+
const filePaths = row.file_paths ?? null;
|
|
2514
|
+
const commitHash = row.commit_hash ?? null;
|
|
2515
|
+
const durationMs = row.duration_ms ?? null;
|
|
2516
|
+
const tokenCost = row.token_cost ?? null;
|
|
2517
|
+
const audience = row.audience ?? null;
|
|
2518
|
+
const languageType = row.language_type ?? null;
|
|
2519
|
+
const parentMemoryId = row.parent_memory_id ?? null;
|
|
2520
|
+
const cols = `id, agent_id, agent_role, session_id, timestamp,
|
|
2371
2521
|
tool_name, project_name,
|
|
2372
2522
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
2373
2523
|
confidence, last_accessed,
|
|
2374
2524
|
workspace_id, document_id, user_id, char_offset, page_number,
|
|
2375
|
-
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
trajectory
|
|
2434
|
-
]
|
|
2525
|
+
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
|
|
2526
|
+
intent, outcome, domain, referenced_entities, retrieval_count,
|
|
2527
|
+
chain_position, review_status, context_window_pct, file_paths, commit_hash,
|
|
2528
|
+
duration_ms, token_cost, audience, language_type, parent_memory_id`;
|
|
2529
|
+
const metaArgs = [
|
|
2530
|
+
intent,
|
|
2531
|
+
outcome,
|
|
2532
|
+
domain,
|
|
2533
|
+
referencedEntities,
|
|
2534
|
+
retrievalCount,
|
|
2535
|
+
chainPosition,
|
|
2536
|
+
reviewStatus,
|
|
2537
|
+
contextWindowPct,
|
|
2538
|
+
filePaths,
|
|
2539
|
+
commitHash,
|
|
2540
|
+
durationMs,
|
|
2541
|
+
tokenCost,
|
|
2542
|
+
audience,
|
|
2543
|
+
languageType,
|
|
2544
|
+
parentMemoryId
|
|
2545
|
+
];
|
|
2546
|
+
const baseArgs = [
|
|
2547
|
+
row.id,
|
|
2548
|
+
row.agent_id,
|
|
2549
|
+
row.agent_role,
|
|
2550
|
+
row.session_id,
|
|
2551
|
+
row.timestamp,
|
|
2552
|
+
row.tool_name,
|
|
2553
|
+
row.project_name,
|
|
2554
|
+
row.has_error,
|
|
2555
|
+
row.raw_text
|
|
2556
|
+
];
|
|
2557
|
+
const sharedArgs = [
|
|
2558
|
+
row.version,
|
|
2559
|
+
taskId,
|
|
2560
|
+
importance,
|
|
2561
|
+
status,
|
|
2562
|
+
confidence,
|
|
2563
|
+
lastAccessed,
|
|
2564
|
+
workspaceId,
|
|
2565
|
+
documentId,
|
|
2566
|
+
userId,
|
|
2567
|
+
charOffset,
|
|
2568
|
+
pageNumber,
|
|
2569
|
+
sourcePath,
|
|
2570
|
+
sourceType,
|
|
2571
|
+
tier,
|
|
2572
|
+
supersedesId,
|
|
2573
|
+
draft,
|
|
2574
|
+
memoryType,
|
|
2575
|
+
trajectory,
|
|
2576
|
+
contentHash
|
|
2577
|
+
];
|
|
2578
|
+
return {
|
|
2579
|
+
sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
|
|
2580
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
|
|
2581
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
2582
|
+
args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
|
|
2435
2583
|
};
|
|
2436
2584
|
};
|
|
2437
2585
|
const globalClient = getClient();
|
|
@@ -2575,6 +2723,7 @@ import { realpathSync } from "fs";
|
|
|
2575
2723
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2576
2724
|
function isMainModule(importMetaUrl) {
|
|
2577
2725
|
if (process.argv[1] == null) return false;
|
|
2726
|
+
if (process.argv[1].includes("mcp/server")) return false;
|
|
2578
2727
|
try {
|
|
2579
2728
|
const scriptPath = realpathSync(process.argv[1]);
|
|
2580
2729
|
const modulePath = realpathSync(fileURLToPath2(importMetaUrl));
|