@askexenow/exe-os 0.9.7 → 0.9.9
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 +953 -105
- package/dist/bin/backfill-responses.js +952 -104
- package/dist/bin/backfill-vectors.js +956 -108
- package/dist/bin/cleanup-stale-review-tasks.js +802 -58
- package/dist/bin/cli.js +2292 -1070
- package/dist/bin/exe-agent-config.js +157 -101
- package/dist/bin/exe-agent.js +55 -29
- package/dist/bin/exe-assign.js +940 -92
- package/dist/bin/exe-boot.js +1424 -442
- package/dist/bin/exe-call.js +240 -141
- package/dist/bin/exe-cloud.js +198 -70
- package/dist/bin/exe-dispatch.js +951 -192
- package/dist/bin/exe-doctor.js +791 -51
- package/dist/bin/exe-export-behaviors.js +790 -42
- package/dist/bin/exe-forget.js +771 -31
- package/dist/bin/exe-gateway.js +1592 -521
- package/dist/bin/exe-heartbeat.js +850 -109
- package/dist/bin/exe-kill.js +783 -35
- package/dist/bin/exe-launch-agent.js +1030 -107
- package/dist/bin/exe-link.js +916 -110
- package/dist/bin/exe-new-employee.js +526 -217
- package/dist/bin/exe-pending-messages.js +1046 -62
- package/dist/bin/exe-pending-notifications.js +1318 -111
- package/dist/bin/exe-pending-reviews.js +1040 -72
- package/dist/bin/exe-rename.js +772 -59
- package/dist/bin/exe-review.js +772 -32
- package/dist/bin/exe-search.js +982 -128
- package/dist/bin/exe-session-cleanup.js +1180 -306
- package/dist/bin/exe-settings.js +185 -105
- package/dist/bin/exe-start-codex.js +886 -132
- package/dist/bin/exe-start-opencode.js +873 -119
- package/dist/bin/exe-status.js +803 -59
- package/dist/bin/exe-team.js +772 -32
- package/dist/bin/git-sweep.js +1046 -223
- package/dist/bin/graph-backfill.js +779 -31
- package/dist/bin/graph-export.js +785 -37
- package/dist/bin/install.js +632 -200
- package/dist/bin/scan-tasks.js +1055 -232
- package/dist/bin/setup.js +1419 -320
- package/dist/bin/shard-migrate.js +783 -35
- package/dist/bin/update.js +138 -49
- package/dist/bin/wiki-sync.js +782 -34
- package/dist/gateway/index.js +1444 -449
- package/dist/hooks/bug-report-worker.js +1141 -269
- package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
- package/dist/hooks/commit-complete.js +1044 -221
- package/dist/hooks/error-recall.js +989 -135
- package/dist/hooks/exe-heartbeat-hook.js +99 -75
- package/dist/hooks/ingest-worker.js +4176 -3226
- package/dist/hooks/ingest.js +920 -168
- package/dist/hooks/instructions-loaded.js +874 -70
- package/dist/hooks/notification.js +860 -56
- package/dist/hooks/post-compact.js +881 -73
- package/dist/hooks/pre-compact.js +1050 -227
- package/dist/hooks/pre-tool-use.js +1084 -159
- package/dist/hooks/prompt-ingest-worker.js +1089 -164
- package/dist/hooks/prompt-submit.js +1469 -515
- package/dist/hooks/response-ingest-worker.js +1104 -179
- package/dist/hooks/session-end.js +1085 -251
- package/dist/hooks/session-start.js +1241 -231
- package/dist/hooks/stop.js +935 -109
- package/dist/hooks/subagent-stop.js +881 -73
- package/dist/hooks/summary-worker.js +1323 -307
- package/dist/index.js +1449 -452
- package/dist/lib/agent-config.js +28 -6
- package/dist/lib/cloud-sync.js +909 -115
- package/dist/lib/config.js +30 -10
- package/dist/lib/consolidation.js +42 -9
- package/dist/lib/database.js +739 -33
- package/dist/lib/db-daemon-client.js +73 -19
- package/dist/lib/db.js +2359 -0
- package/dist/lib/device-registry.js +760 -47
- package/dist/lib/embedder.js +201 -73
- package/dist/lib/employee-templates.js +30 -4
- package/dist/lib/employees.js +290 -86
- package/dist/lib/exe-daemon-client.js +187 -83
- package/dist/lib/exe-daemon.js +1696 -616
- package/dist/lib/hybrid-search.js +982 -128
- package/dist/lib/identity.js +43 -13
- package/dist/lib/license.js +133 -48
- package/dist/lib/messaging.js +167 -80
- package/dist/lib/reminders.js +35 -5
- package/dist/lib/schedules.js +772 -32
- package/dist/lib/skill-learning.js +54 -7
- package/dist/lib/store.js +779 -31
- package/dist/lib/task-router.js +94 -73
- package/dist/lib/tasks.js +298 -225
- package/dist/lib/tmux-routing.js +246 -172
- package/dist/lib/token-spend.js +52 -14
- package/dist/mcp/server.js +2893 -850
- package/dist/mcp/tools/complete-reminder.js +35 -5
- package/dist/mcp/tools/create-reminder.js +35 -5
- package/dist/mcp/tools/create-task.js +507 -323
- package/dist/mcp/tools/deactivate-behavior.js +40 -10
- package/dist/mcp/tools/list-reminders.js +35 -5
- package/dist/mcp/tools/list-tasks.js +277 -104
- package/dist/mcp/tools/send-message.js +129 -56
- package/dist/mcp/tools/update-task.js +1864 -188
- package/dist/runtime/index.js +1083 -259
- package/dist/tui/App.js +1501 -434
- package/package.json +3 -2
|
@@ -64,9 +64,47 @@ var init_db_retry = __esm({
|
|
|
64
64
|
}
|
|
65
65
|
});
|
|
66
66
|
|
|
67
|
+
// src/lib/secure-files.ts
|
|
68
|
+
import { chmodSync, existsSync, mkdirSync } from "fs";
|
|
69
|
+
import { chmod, mkdir } from "fs/promises";
|
|
70
|
+
async function ensurePrivateDir(dirPath) {
|
|
71
|
+
await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
72
|
+
try {
|
|
73
|
+
await chmod(dirPath, PRIVATE_DIR_MODE);
|
|
74
|
+
} catch {
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function ensurePrivateDirSync(dirPath) {
|
|
78
|
+
mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
79
|
+
try {
|
|
80
|
+
chmodSync(dirPath, PRIVATE_DIR_MODE);
|
|
81
|
+
} catch {
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async function enforcePrivateFile(filePath) {
|
|
85
|
+
try {
|
|
86
|
+
await chmod(filePath, PRIVATE_FILE_MODE);
|
|
87
|
+
} catch {
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function enforcePrivateFileSync(filePath) {
|
|
91
|
+
try {
|
|
92
|
+
if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
|
|
93
|
+
} catch {
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
|
|
97
|
+
var init_secure_files = __esm({
|
|
98
|
+
"src/lib/secure-files.ts"() {
|
|
99
|
+
"use strict";
|
|
100
|
+
PRIVATE_DIR_MODE = 448;
|
|
101
|
+
PRIVATE_FILE_MODE = 384;
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
67
105
|
// src/lib/config.ts
|
|
68
|
-
import { readFile, writeFile
|
|
69
|
-
import { readFileSync, existsSync, renameSync } from "fs";
|
|
106
|
+
import { readFile, writeFile } from "fs/promises";
|
|
107
|
+
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
70
108
|
import path from "path";
|
|
71
109
|
import os from "os";
|
|
72
110
|
function resolveDataDir() {
|
|
@@ -74,7 +112,7 @@ function resolveDataDir() {
|
|
|
74
112
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
75
113
|
const newDir = path.join(os.homedir(), ".exe-os");
|
|
76
114
|
const legacyDir = path.join(os.homedir(), ".exe-mem");
|
|
77
|
-
if (!
|
|
115
|
+
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
78
116
|
try {
|
|
79
117
|
renameSync(legacyDir, newDir);
|
|
80
118
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -137,9 +175,9 @@ function normalizeAutoUpdate(raw) {
|
|
|
137
175
|
}
|
|
138
176
|
async function loadConfig() {
|
|
139
177
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
140
|
-
await
|
|
178
|
+
await ensurePrivateDir(dir);
|
|
141
179
|
const configPath = path.join(dir, "config.json");
|
|
142
|
-
if (!
|
|
180
|
+
if (!existsSync2(configPath)) {
|
|
143
181
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
144
182
|
}
|
|
145
183
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -152,6 +190,7 @@ async function loadConfig() {
|
|
|
152
190
|
`);
|
|
153
191
|
try {
|
|
154
192
|
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
193
|
+
await enforcePrivateFile(configPath);
|
|
155
194
|
} catch {
|
|
156
195
|
}
|
|
157
196
|
}
|
|
@@ -171,6 +210,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
|
|
|
171
210
|
var init_config = __esm({
|
|
172
211
|
"src/lib/config.ts"() {
|
|
173
212
|
"use strict";
|
|
213
|
+
init_secure_files();
|
|
174
214
|
EXE_AI_DIR = resolveDataDir();
|
|
175
215
|
DB_PATH = path.join(EXE_AI_DIR, "memories.db");
|
|
176
216
|
MODELS_DIR = path.join(EXE_AI_DIR, "models");
|
|
@@ -249,7 +289,7 @@ var init_config = __esm({
|
|
|
249
289
|
|
|
250
290
|
// src/lib/employees.ts
|
|
251
291
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
252
|
-
import { existsSync as
|
|
292
|
+
import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
|
|
253
293
|
import { execSync } from "child_process";
|
|
254
294
|
import path2 from "path";
|
|
255
295
|
import os2 from "os";
|
|
@@ -270,14 +310,14 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
|
270
310
|
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
271
311
|
}
|
|
272
312
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
273
|
-
if (!
|
|
313
|
+
if (!existsSync3(employeesPath)) return [];
|
|
274
314
|
try {
|
|
275
315
|
return JSON.parse(readFileSync2(employeesPath, "utf-8"));
|
|
276
316
|
} catch {
|
|
277
317
|
return [];
|
|
278
318
|
}
|
|
279
319
|
}
|
|
280
|
-
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE;
|
|
320
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, IDENTITY_DIR;
|
|
281
321
|
var init_employees = __esm({
|
|
282
322
|
"src/lib/employees.ts"() {
|
|
283
323
|
"use strict";
|
|
@@ -285,12 +325,609 @@ var init_employees = __esm({
|
|
|
285
325
|
EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
|
|
286
326
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
287
327
|
COORDINATOR_ROLE = "COO";
|
|
328
|
+
IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// src/lib/database-adapter.ts
|
|
333
|
+
import os3 from "os";
|
|
334
|
+
import path3 from "path";
|
|
335
|
+
import { createRequire } from "module";
|
|
336
|
+
import { pathToFileURL } from "url";
|
|
337
|
+
function quotedIdentifier(identifier) {
|
|
338
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
339
|
+
}
|
|
340
|
+
function unqualifiedTableName(name) {
|
|
341
|
+
const raw = name.trim().replace(/^"|"$/g, "");
|
|
342
|
+
const parts = raw.split(".");
|
|
343
|
+
return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
|
|
344
|
+
}
|
|
345
|
+
function stripTrailingSemicolon(sql) {
|
|
346
|
+
return sql.trim().replace(/;+\s*$/u, "");
|
|
347
|
+
}
|
|
348
|
+
function appendClause(sql, clause) {
|
|
349
|
+
const trimmed = stripTrailingSemicolon(sql);
|
|
350
|
+
const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
|
|
351
|
+
if (!returningMatch) {
|
|
352
|
+
return `${trimmed}${clause}`;
|
|
353
|
+
}
|
|
354
|
+
const idx = returningMatch.index;
|
|
355
|
+
return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
|
|
356
|
+
}
|
|
357
|
+
function normalizeStatement(stmt) {
|
|
358
|
+
if (typeof stmt === "string") {
|
|
359
|
+
return { kind: "positional", sql: stmt, args: [] };
|
|
360
|
+
}
|
|
361
|
+
const sql = stmt.sql;
|
|
362
|
+
if (Array.isArray(stmt.args) || stmt.args === void 0) {
|
|
363
|
+
return { kind: "positional", sql, args: stmt.args ?? [] };
|
|
364
|
+
}
|
|
365
|
+
return { kind: "named", sql, args: stmt.args };
|
|
366
|
+
}
|
|
367
|
+
function rewriteBooleanLiterals(sql) {
|
|
368
|
+
let out = sql;
|
|
369
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
370
|
+
const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
|
|
371
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
|
|
372
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
|
|
373
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
|
|
374
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
|
|
375
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
|
|
376
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
|
|
377
|
+
}
|
|
378
|
+
return out;
|
|
379
|
+
}
|
|
380
|
+
function rewriteInsertOrIgnore(sql) {
|
|
381
|
+
if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
|
|
382
|
+
return sql;
|
|
383
|
+
}
|
|
384
|
+
const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
|
|
385
|
+
return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
|
|
386
|
+
}
|
|
387
|
+
function rewriteInsertOrReplace(sql) {
|
|
388
|
+
const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
|
|
389
|
+
if (!match) {
|
|
390
|
+
return sql;
|
|
391
|
+
}
|
|
392
|
+
const rawTable = match[1];
|
|
393
|
+
const rawColumns = match[2];
|
|
394
|
+
const remainder = match[3];
|
|
395
|
+
const tableName = unqualifiedTableName(rawTable);
|
|
396
|
+
const conflictKeys = UPSERT_KEYS[tableName];
|
|
397
|
+
if (!conflictKeys?.length) {
|
|
398
|
+
return sql;
|
|
399
|
+
}
|
|
400
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
401
|
+
const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
|
|
402
|
+
const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
|
|
403
|
+
const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
|
|
404
|
+
return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
|
|
405
|
+
}
|
|
406
|
+
function rewriteSql(sql) {
|
|
407
|
+
let out = sql;
|
|
408
|
+
out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
|
|
409
|
+
out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
|
|
410
|
+
out = rewriteBooleanLiterals(out);
|
|
411
|
+
out = rewriteInsertOrReplace(out);
|
|
412
|
+
out = rewriteInsertOrIgnore(out);
|
|
413
|
+
return stripTrailingSemicolon(out);
|
|
414
|
+
}
|
|
415
|
+
function toBoolean(value) {
|
|
416
|
+
if (value === null || value === void 0) return value;
|
|
417
|
+
if (typeof value === "boolean") return value;
|
|
418
|
+
if (typeof value === "number") return value !== 0;
|
|
419
|
+
if (typeof value === "bigint") return value !== 0n;
|
|
420
|
+
if (typeof value === "string") {
|
|
421
|
+
const normalized = value.trim().toLowerCase();
|
|
422
|
+
if (normalized === "0" || normalized === "false") return false;
|
|
423
|
+
if (normalized === "1" || normalized === "true") return true;
|
|
424
|
+
}
|
|
425
|
+
return Boolean(value);
|
|
426
|
+
}
|
|
427
|
+
function countQuestionMarks(sql, end) {
|
|
428
|
+
let count = 0;
|
|
429
|
+
let inSingle = false;
|
|
430
|
+
let inDouble = false;
|
|
431
|
+
let inLineComment = false;
|
|
432
|
+
let inBlockComment = false;
|
|
433
|
+
for (let i = 0; i < end; i++) {
|
|
434
|
+
const ch = sql[i];
|
|
435
|
+
const next = sql[i + 1];
|
|
436
|
+
if (inLineComment) {
|
|
437
|
+
if (ch === "\n") inLineComment = false;
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
if (inBlockComment) {
|
|
441
|
+
if (ch === "*" && next === "/") {
|
|
442
|
+
inBlockComment = false;
|
|
443
|
+
i += 1;
|
|
444
|
+
}
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
448
|
+
inLineComment = true;
|
|
449
|
+
i += 1;
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
453
|
+
inBlockComment = true;
|
|
454
|
+
i += 1;
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
458
|
+
inSingle = !inSingle;
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
462
|
+
inDouble = !inDouble;
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
466
|
+
count += 1;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return count;
|
|
470
|
+
}
|
|
471
|
+
function findBooleanPlaceholderIndexes(sql) {
|
|
472
|
+
const indexes = /* @__PURE__ */ new Set();
|
|
473
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
474
|
+
const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
|
|
475
|
+
for (const match of sql.matchAll(pattern)) {
|
|
476
|
+
const matchText = match[0];
|
|
477
|
+
const qIndex = match.index + matchText.lastIndexOf("?");
|
|
478
|
+
indexes.add(countQuestionMarks(sql, qIndex + 1));
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
return indexes;
|
|
482
|
+
}
|
|
483
|
+
function coerceInsertBooleanArgs(sql, args) {
|
|
484
|
+
const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
|
|
485
|
+
if (!match) return;
|
|
486
|
+
const rawTable = match[1];
|
|
487
|
+
const rawColumns = match[2];
|
|
488
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
489
|
+
if (!boolColumns?.size) return;
|
|
490
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
491
|
+
for (const [index, column] of columns.entries()) {
|
|
492
|
+
if (boolColumns.has(column) && index < args.length) {
|
|
493
|
+
args[index] = toBoolean(args[index]);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
function coerceUpdateBooleanArgs(sql, args) {
|
|
498
|
+
const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
|
|
499
|
+
if (!match) return;
|
|
500
|
+
const rawTable = match[1];
|
|
501
|
+
const setClause = match[2];
|
|
502
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
503
|
+
if (!boolColumns?.size) return;
|
|
504
|
+
const assignments = setClause.split(",");
|
|
505
|
+
let placeholderIndex = 0;
|
|
506
|
+
for (const assignment of assignments) {
|
|
507
|
+
if (!assignment.includes("?")) continue;
|
|
508
|
+
placeholderIndex += 1;
|
|
509
|
+
const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
|
|
510
|
+
if (colMatch && boolColumns.has(colMatch[1])) {
|
|
511
|
+
args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
function coerceBooleanArgs(sql, args) {
|
|
516
|
+
const nextArgs = [...args];
|
|
517
|
+
coerceInsertBooleanArgs(sql, nextArgs);
|
|
518
|
+
coerceUpdateBooleanArgs(sql, nextArgs);
|
|
519
|
+
const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
|
|
520
|
+
for (const index of placeholderIndexes) {
|
|
521
|
+
if (index > 0 && index <= nextArgs.length) {
|
|
522
|
+
nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return nextArgs;
|
|
526
|
+
}
|
|
527
|
+
function convertQuestionMarksToDollarParams(sql) {
|
|
528
|
+
let out = "";
|
|
529
|
+
let placeholder = 0;
|
|
530
|
+
let inSingle = false;
|
|
531
|
+
let inDouble = false;
|
|
532
|
+
let inLineComment = false;
|
|
533
|
+
let inBlockComment = false;
|
|
534
|
+
for (let i = 0; i < sql.length; i++) {
|
|
535
|
+
const ch = sql[i];
|
|
536
|
+
const next = sql[i + 1];
|
|
537
|
+
if (inLineComment) {
|
|
538
|
+
out += ch;
|
|
539
|
+
if (ch === "\n") inLineComment = false;
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
if (inBlockComment) {
|
|
543
|
+
out += ch;
|
|
544
|
+
if (ch === "*" && next === "/") {
|
|
545
|
+
out += next;
|
|
546
|
+
inBlockComment = false;
|
|
547
|
+
i += 1;
|
|
548
|
+
}
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
552
|
+
out += ch + next;
|
|
553
|
+
inLineComment = true;
|
|
554
|
+
i += 1;
|
|
555
|
+
continue;
|
|
556
|
+
}
|
|
557
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
558
|
+
out += ch + next;
|
|
559
|
+
inBlockComment = true;
|
|
560
|
+
i += 1;
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
563
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
564
|
+
inSingle = !inSingle;
|
|
565
|
+
out += ch;
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
569
|
+
inDouble = !inDouble;
|
|
570
|
+
out += ch;
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
573
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
574
|
+
placeholder += 1;
|
|
575
|
+
out += `$${placeholder}`;
|
|
576
|
+
continue;
|
|
577
|
+
}
|
|
578
|
+
out += ch;
|
|
579
|
+
}
|
|
580
|
+
return out;
|
|
581
|
+
}
|
|
582
|
+
function translateStatementForPostgres(stmt) {
|
|
583
|
+
const normalized = normalizeStatement(stmt);
|
|
584
|
+
if (normalized.kind === "named") {
|
|
585
|
+
throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
|
|
586
|
+
}
|
|
587
|
+
const rewrittenSql = rewriteSql(normalized.sql);
|
|
588
|
+
const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
|
|
589
|
+
return {
|
|
590
|
+
sql: convertQuestionMarksToDollarParams(rewrittenSql),
|
|
591
|
+
args: coercedArgs
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
function shouldBypassPostgres(stmt) {
|
|
595
|
+
const normalized = normalizeStatement(stmt);
|
|
596
|
+
if (normalized.kind === "named") {
|
|
597
|
+
return true;
|
|
598
|
+
}
|
|
599
|
+
return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
|
|
600
|
+
}
|
|
601
|
+
function shouldFallbackOnError(error) {
|
|
602
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
603
|
+
return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
|
|
604
|
+
}
|
|
605
|
+
function isReadQuery(sql) {
|
|
606
|
+
const trimmed = sql.trimStart();
|
|
607
|
+
return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
|
|
608
|
+
}
|
|
609
|
+
function buildRow(row, columns) {
|
|
610
|
+
const values = columns.map((column) => row[column]);
|
|
611
|
+
return Object.assign(values, row);
|
|
612
|
+
}
|
|
613
|
+
function buildResultSet(rows, rowsAffected = 0) {
|
|
614
|
+
const columns = rows[0] ? Object.keys(rows[0]) : [];
|
|
615
|
+
const resultRows = rows.map((row) => buildRow(row, columns));
|
|
616
|
+
return {
|
|
617
|
+
columns,
|
|
618
|
+
columnTypes: columns.map(() => ""),
|
|
619
|
+
rows: resultRows,
|
|
620
|
+
rowsAffected,
|
|
621
|
+
lastInsertRowid: void 0,
|
|
622
|
+
toJSON() {
|
|
623
|
+
return {
|
|
624
|
+
columns,
|
|
625
|
+
columnTypes: columns.map(() => ""),
|
|
626
|
+
rows,
|
|
627
|
+
rowsAffected,
|
|
628
|
+
lastInsertRowid: void 0
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
async function loadPrismaClient() {
|
|
634
|
+
if (!prismaClientPromise) {
|
|
635
|
+
prismaClientPromise = (async () => {
|
|
636
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
637
|
+
if (explicitPath) {
|
|
638
|
+
const module2 = await import(pathToFileURL(explicitPath).href);
|
|
639
|
+
const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
|
|
640
|
+
if (!PrismaClient2) {
|
|
641
|
+
throw new Error(`No PrismaClient export found at ${explicitPath}`);
|
|
642
|
+
}
|
|
643
|
+
return new PrismaClient2();
|
|
644
|
+
}
|
|
645
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path3.join(os3.homedir(), "exe-db");
|
|
646
|
+
const requireFromExeDb = createRequire(path3.join(exeDbRoot, "package.json"));
|
|
647
|
+
const prismaEntry = requireFromExeDb.resolve("@prisma/client");
|
|
648
|
+
const module = await import(pathToFileURL(prismaEntry).href);
|
|
649
|
+
const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
|
|
650
|
+
if (!PrismaClient) {
|
|
651
|
+
throw new Error(`No PrismaClient export found in ${prismaEntry}`);
|
|
652
|
+
}
|
|
653
|
+
return new PrismaClient();
|
|
654
|
+
})();
|
|
655
|
+
}
|
|
656
|
+
return prismaClientPromise;
|
|
657
|
+
}
|
|
658
|
+
async function ensureCompatibilityViews(prisma) {
|
|
659
|
+
if (!compatibilityBootstrapPromise) {
|
|
660
|
+
compatibilityBootstrapPromise = (async () => {
|
|
661
|
+
for (const mapping of VIEW_MAPPINGS) {
|
|
662
|
+
const relation = mapping.source.replace(/"/g, "");
|
|
663
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
664
|
+
"SELECT to_regclass($1) AS regclass",
|
|
665
|
+
relation
|
|
666
|
+
);
|
|
667
|
+
if (!rows[0]?.regclass) {
|
|
668
|
+
continue;
|
|
669
|
+
}
|
|
670
|
+
await prisma.$executeRawUnsafe(
|
|
671
|
+
`CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
})();
|
|
675
|
+
}
|
|
676
|
+
return compatibilityBootstrapPromise;
|
|
677
|
+
}
|
|
678
|
+
async function executeOnPrisma(executor, stmt) {
|
|
679
|
+
const translated = translateStatementForPostgres(stmt);
|
|
680
|
+
if (isReadQuery(translated.sql)) {
|
|
681
|
+
const rows = await executor.$queryRawUnsafe(
|
|
682
|
+
translated.sql,
|
|
683
|
+
...translated.args
|
|
684
|
+
);
|
|
685
|
+
return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
|
|
686
|
+
}
|
|
687
|
+
const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
|
|
688
|
+
return buildResultSet([], rowsAffected);
|
|
689
|
+
}
|
|
690
|
+
function splitSqlStatements(sql) {
|
|
691
|
+
const parts = [];
|
|
692
|
+
let current = "";
|
|
693
|
+
let inSingle = false;
|
|
694
|
+
let inDouble = false;
|
|
695
|
+
let inLineComment = false;
|
|
696
|
+
let inBlockComment = false;
|
|
697
|
+
for (let i = 0; i < sql.length; i++) {
|
|
698
|
+
const ch = sql[i];
|
|
699
|
+
const next = sql[i + 1];
|
|
700
|
+
if (inLineComment) {
|
|
701
|
+
current += ch;
|
|
702
|
+
if (ch === "\n") inLineComment = false;
|
|
703
|
+
continue;
|
|
704
|
+
}
|
|
705
|
+
if (inBlockComment) {
|
|
706
|
+
current += ch;
|
|
707
|
+
if (ch === "*" && next === "/") {
|
|
708
|
+
current += next;
|
|
709
|
+
inBlockComment = false;
|
|
710
|
+
i += 1;
|
|
711
|
+
}
|
|
712
|
+
continue;
|
|
713
|
+
}
|
|
714
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
715
|
+
current += ch + next;
|
|
716
|
+
inLineComment = true;
|
|
717
|
+
i += 1;
|
|
718
|
+
continue;
|
|
719
|
+
}
|
|
720
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
721
|
+
current += ch + next;
|
|
722
|
+
inBlockComment = true;
|
|
723
|
+
i += 1;
|
|
724
|
+
continue;
|
|
725
|
+
}
|
|
726
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
727
|
+
inSingle = !inSingle;
|
|
728
|
+
current += ch;
|
|
729
|
+
continue;
|
|
730
|
+
}
|
|
731
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
732
|
+
inDouble = !inDouble;
|
|
733
|
+
current += ch;
|
|
734
|
+
continue;
|
|
735
|
+
}
|
|
736
|
+
if (!inSingle && !inDouble && ch === ";") {
|
|
737
|
+
if (current.trim()) {
|
|
738
|
+
parts.push(current.trim());
|
|
739
|
+
}
|
|
740
|
+
current = "";
|
|
741
|
+
continue;
|
|
742
|
+
}
|
|
743
|
+
current += ch;
|
|
744
|
+
}
|
|
745
|
+
if (current.trim()) {
|
|
746
|
+
parts.push(current.trim());
|
|
747
|
+
}
|
|
748
|
+
return parts;
|
|
749
|
+
}
|
|
750
|
+
async function createPrismaDbAdapter(fallbackClient) {
|
|
751
|
+
const prisma = await loadPrismaClient();
|
|
752
|
+
await ensureCompatibilityViews(prisma);
|
|
753
|
+
let closed = false;
|
|
754
|
+
let adapter;
|
|
755
|
+
const fallbackExecute = async (stmt, error) => {
|
|
756
|
+
if (!fallbackClient) {
|
|
757
|
+
if (error) throw error;
|
|
758
|
+
throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
|
|
759
|
+
}
|
|
760
|
+
if (error) {
|
|
761
|
+
process.stderr.write(
|
|
762
|
+
`[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
763
|
+
`
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
return fallbackClient.execute(stmt);
|
|
767
|
+
};
|
|
768
|
+
adapter = {
|
|
769
|
+
async execute(stmt) {
|
|
770
|
+
if (shouldBypassPostgres(stmt)) {
|
|
771
|
+
return fallbackExecute(stmt);
|
|
772
|
+
}
|
|
773
|
+
try {
|
|
774
|
+
return await executeOnPrisma(prisma, stmt);
|
|
775
|
+
} catch (error) {
|
|
776
|
+
if (shouldFallbackOnError(error)) {
|
|
777
|
+
return fallbackExecute(stmt, error);
|
|
778
|
+
}
|
|
779
|
+
throw error;
|
|
780
|
+
}
|
|
781
|
+
},
|
|
782
|
+
async batch(stmts, mode) {
|
|
783
|
+
if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
|
|
784
|
+
if (!fallbackClient) {
|
|
785
|
+
throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
|
|
786
|
+
}
|
|
787
|
+
return fallbackClient.batch(stmts, mode);
|
|
788
|
+
}
|
|
789
|
+
try {
|
|
790
|
+
if (prisma.$transaction) {
|
|
791
|
+
return await prisma.$transaction(async (tx) => {
|
|
792
|
+
const results2 = [];
|
|
793
|
+
for (const stmt of stmts) {
|
|
794
|
+
results2.push(await executeOnPrisma(tx, stmt));
|
|
795
|
+
}
|
|
796
|
+
return results2;
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
const results = [];
|
|
800
|
+
for (const stmt of stmts) {
|
|
801
|
+
results.push(await executeOnPrisma(prisma, stmt));
|
|
802
|
+
}
|
|
803
|
+
return results;
|
|
804
|
+
} catch (error) {
|
|
805
|
+
if (fallbackClient && shouldFallbackOnError(error)) {
|
|
806
|
+
process.stderr.write(
|
|
807
|
+
`[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
808
|
+
`
|
|
809
|
+
);
|
|
810
|
+
return fallbackClient.batch(stmts, mode);
|
|
811
|
+
}
|
|
812
|
+
throw error;
|
|
813
|
+
}
|
|
814
|
+
},
|
|
815
|
+
async migrate(stmts) {
|
|
816
|
+
if (fallbackClient) {
|
|
817
|
+
return fallbackClient.migrate(stmts);
|
|
818
|
+
}
|
|
819
|
+
return adapter.batch(stmts, "deferred");
|
|
820
|
+
},
|
|
821
|
+
async transaction(mode) {
|
|
822
|
+
if (!fallbackClient) {
|
|
823
|
+
throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
|
|
824
|
+
}
|
|
825
|
+
return fallbackClient.transaction(mode);
|
|
826
|
+
},
|
|
827
|
+
async executeMultiple(sql) {
|
|
828
|
+
if (fallbackClient && shouldBypassPostgres(sql)) {
|
|
829
|
+
return fallbackClient.executeMultiple(sql);
|
|
830
|
+
}
|
|
831
|
+
for (const statement of splitSqlStatements(sql)) {
|
|
832
|
+
await adapter.execute(statement);
|
|
833
|
+
}
|
|
834
|
+
},
|
|
835
|
+
async sync() {
|
|
836
|
+
if (fallbackClient) {
|
|
837
|
+
return fallbackClient.sync();
|
|
838
|
+
}
|
|
839
|
+
return { frame_no: 0, frames_synced: 0 };
|
|
840
|
+
},
|
|
841
|
+
close() {
|
|
842
|
+
closed = true;
|
|
843
|
+
prismaClientPromise = null;
|
|
844
|
+
compatibilityBootstrapPromise = null;
|
|
845
|
+
void prisma.$disconnect?.();
|
|
846
|
+
},
|
|
847
|
+
get closed() {
|
|
848
|
+
return closed;
|
|
849
|
+
},
|
|
850
|
+
get protocol() {
|
|
851
|
+
return "prisma-postgres";
|
|
852
|
+
}
|
|
853
|
+
};
|
|
854
|
+
return adapter;
|
|
855
|
+
}
|
|
856
|
+
var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
|
|
857
|
+
var init_database_adapter = __esm({
|
|
858
|
+
"src/lib/database-adapter.ts"() {
|
|
859
|
+
"use strict";
|
|
860
|
+
VIEW_MAPPINGS = [
|
|
861
|
+
{ view: "memories", source: "memory.memory_records" },
|
|
862
|
+
{ view: "tasks", source: "memory.tasks" },
|
|
863
|
+
{ view: "behaviors", source: "memory.behaviors" },
|
|
864
|
+
{ view: "entities", source: "memory.entities" },
|
|
865
|
+
{ view: "relationships", source: "memory.relationships" },
|
|
866
|
+
{ view: "entity_memories", source: "memory.entity_memories" },
|
|
867
|
+
{ view: "entity_aliases", source: "memory.entity_aliases" },
|
|
868
|
+
{ view: "notifications", source: "memory.notifications" },
|
|
869
|
+
{ view: "messages", source: "memory.messages" },
|
|
870
|
+
{ view: "users", source: "wiki.users" },
|
|
871
|
+
{ view: "workspaces", source: "wiki.workspaces" },
|
|
872
|
+
{ view: "workspace_users", source: "wiki.workspace_users" },
|
|
873
|
+
{ view: "documents", source: "wiki.workspace_documents" },
|
|
874
|
+
{ view: "chats", source: "wiki.workspace_chats" }
|
|
875
|
+
];
|
|
876
|
+
UPSERT_KEYS = {
|
|
877
|
+
memories: ["id"],
|
|
878
|
+
tasks: ["id"],
|
|
879
|
+
behaviors: ["id"],
|
|
880
|
+
entities: ["id"],
|
|
881
|
+
relationships: ["id"],
|
|
882
|
+
entity_aliases: ["alias"],
|
|
883
|
+
notifications: ["id"],
|
|
884
|
+
messages: ["id"],
|
|
885
|
+
users: ["id"],
|
|
886
|
+
workspaces: ["id"],
|
|
887
|
+
workspace_users: ["id"],
|
|
888
|
+
documents: ["id"],
|
|
889
|
+
chats: ["id"]
|
|
890
|
+
};
|
|
891
|
+
BOOLEAN_COLUMNS_BY_TABLE = {
|
|
892
|
+
memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
|
|
893
|
+
behaviors: /* @__PURE__ */ new Set(["active"]),
|
|
894
|
+
notifications: /* @__PURE__ */ new Set(["read"]),
|
|
895
|
+
users: /* @__PURE__ */ new Set(["has_personal_memory"])
|
|
896
|
+
};
|
|
897
|
+
BOOLEAN_COLUMN_NAMES = new Set(
|
|
898
|
+
Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
|
|
899
|
+
);
|
|
900
|
+
IMMEDIATE_FALLBACK_PATTERNS = [
|
|
901
|
+
/\bPRAGMA\b/i,
|
|
902
|
+
/\bsqlite_master\b/i,
|
|
903
|
+
/(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
|
|
904
|
+
/\bMATCH\b/i,
|
|
905
|
+
/\bvector_distance_cos\s*\(/i,
|
|
906
|
+
/\bjson_extract\s*\(/i,
|
|
907
|
+
/\bjulianday\s*\(/i,
|
|
908
|
+
/\bstrftime\s*\(/i,
|
|
909
|
+
/\blast_insert_rowid\s*\(/i
|
|
910
|
+
];
|
|
911
|
+
prismaClientPromise = null;
|
|
912
|
+
compatibilityBootstrapPromise = null;
|
|
288
913
|
}
|
|
289
914
|
});
|
|
290
915
|
|
|
291
916
|
// src/lib/database.ts
|
|
292
917
|
import { createClient } from "@libsql/client";
|
|
293
918
|
async function initDatabase(config) {
|
|
919
|
+
if (_walCheckpointTimer) {
|
|
920
|
+
clearInterval(_walCheckpointTimer);
|
|
921
|
+
_walCheckpointTimer = null;
|
|
922
|
+
}
|
|
923
|
+
if (_daemonClient) {
|
|
924
|
+
_daemonClient.close();
|
|
925
|
+
_daemonClient = null;
|
|
926
|
+
}
|
|
927
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
928
|
+
_adapterClient.close();
|
|
929
|
+
}
|
|
930
|
+
_adapterClient = null;
|
|
294
931
|
if (_client) {
|
|
295
932
|
_client.close();
|
|
296
933
|
_client = null;
|
|
@@ -304,6 +941,7 @@ async function initDatabase(config) {
|
|
|
304
941
|
}
|
|
305
942
|
_client = createClient(opts);
|
|
306
943
|
_resilientClient = wrapWithRetry(_client);
|
|
944
|
+
_adapterClient = _resilientClient;
|
|
307
945
|
_client.execute("PRAGMA busy_timeout = 30000").catch(() => {
|
|
308
946
|
});
|
|
309
947
|
_client.execute("PRAGMA journal_mode = WAL").catch(() => {
|
|
@@ -314,11 +952,17 @@ async function initDatabase(config) {
|
|
|
314
952
|
});
|
|
315
953
|
}, 3e4);
|
|
316
954
|
_walCheckpointTimer.unref();
|
|
955
|
+
if (process.env.DATABASE_URL) {
|
|
956
|
+
_adapterClient = await createPrismaDbAdapter(_resilientClient);
|
|
957
|
+
}
|
|
317
958
|
}
|
|
318
959
|
function getClient() {
|
|
319
|
-
if (!
|
|
960
|
+
if (!_adapterClient) {
|
|
320
961
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
321
962
|
}
|
|
963
|
+
if (process.env.DATABASE_URL) {
|
|
964
|
+
return _adapterClient;
|
|
965
|
+
}
|
|
322
966
|
if (process.env.EXE_IS_DAEMON === "1") {
|
|
323
967
|
return _resilientClient;
|
|
324
968
|
}
|
|
@@ -611,6 +1255,7 @@ async function ensureSchema() {
|
|
|
611
1255
|
project TEXT NOT NULL,
|
|
612
1256
|
summary TEXT NOT NULL,
|
|
613
1257
|
task_file TEXT,
|
|
1258
|
+
session_scope TEXT,
|
|
614
1259
|
read INTEGER NOT NULL DEFAULT 0,
|
|
615
1260
|
created_at TEXT NOT NULL
|
|
616
1261
|
);
|
|
@@ -619,7 +1264,7 @@ async function ensureSchema() {
|
|
|
619
1264
|
ON notifications(read);
|
|
620
1265
|
|
|
621
1266
|
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
622
|
-
ON notifications(agent_id);
|
|
1267
|
+
ON notifications(agent_id, session_scope);
|
|
623
1268
|
|
|
624
1269
|
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
625
1270
|
ON notifications(task_file);
|
|
@@ -657,6 +1302,7 @@ async function ensureSchema() {
|
|
|
657
1302
|
target_agent TEXT NOT NULL,
|
|
658
1303
|
target_project TEXT,
|
|
659
1304
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
1305
|
+
session_scope TEXT,
|
|
660
1306
|
content TEXT NOT NULL,
|
|
661
1307
|
priority TEXT DEFAULT 'normal',
|
|
662
1308
|
status TEXT DEFAULT 'pending',
|
|
@@ -670,10 +1316,31 @@ async function ensureSchema() {
|
|
|
670
1316
|
);
|
|
671
1317
|
|
|
672
1318
|
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
673
|
-
ON messages(target_agent, status);
|
|
1319
|
+
ON messages(target_agent, session_scope, status);
|
|
674
1320
|
|
|
675
1321
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
676
|
-
ON messages(target_agent, from_agent, server_seq);
|
|
1322
|
+
ON messages(target_agent, session_scope, from_agent, server_seq);
|
|
1323
|
+
`);
|
|
1324
|
+
try {
|
|
1325
|
+
await client.execute({
|
|
1326
|
+
sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
|
|
1327
|
+
args: []
|
|
1328
|
+
});
|
|
1329
|
+
} catch {
|
|
1330
|
+
}
|
|
1331
|
+
try {
|
|
1332
|
+
await client.execute({
|
|
1333
|
+
sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
|
|
1334
|
+
args: []
|
|
1335
|
+
});
|
|
1336
|
+
} catch {
|
|
1337
|
+
}
|
|
1338
|
+
await client.executeMultiple(`
|
|
1339
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
|
|
1340
|
+
ON notifications(agent_id, session_scope, read, created_at);
|
|
1341
|
+
|
|
1342
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
|
|
1343
|
+
ON messages(target_agent, session_scope, status, created_at);
|
|
677
1344
|
`);
|
|
678
1345
|
try {
|
|
679
1346
|
await client.execute({
|
|
@@ -1257,17 +1924,26 @@ async function ensureSchema() {
|
|
|
1257
1924
|
} catch {
|
|
1258
1925
|
}
|
|
1259
1926
|
}
|
|
1927
|
+
try {
|
|
1928
|
+
await client.execute({
|
|
1929
|
+
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
1930
|
+
args: []
|
|
1931
|
+
});
|
|
1932
|
+
} catch {
|
|
1933
|
+
}
|
|
1260
1934
|
}
|
|
1261
|
-
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso;
|
|
1935
|
+
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso;
|
|
1262
1936
|
var init_database = __esm({
|
|
1263
1937
|
"src/lib/database.ts"() {
|
|
1264
1938
|
"use strict";
|
|
1265
1939
|
init_db_retry();
|
|
1266
1940
|
init_employees();
|
|
1941
|
+
init_database_adapter();
|
|
1267
1942
|
_client = null;
|
|
1268
1943
|
_resilientClient = null;
|
|
1269
1944
|
_walCheckpointTimer = null;
|
|
1270
1945
|
_daemonClient = null;
|
|
1946
|
+
_adapterClient = null;
|
|
1271
1947
|
initTurso = initDatabase;
|
|
1272
1948
|
}
|
|
1273
1949
|
});
|
|
@@ -1277,6 +1953,7 @@ var shard_manager_exports = {};
|
|
|
1277
1953
|
__export(shard_manager_exports, {
|
|
1278
1954
|
disposeShards: () => disposeShards,
|
|
1279
1955
|
ensureShardSchema: () => ensureShardSchema,
|
|
1956
|
+
getOpenShardCount: () => getOpenShardCount,
|
|
1280
1957
|
getReadyShardClient: () => getReadyShardClient,
|
|
1281
1958
|
getShardClient: () => getShardClient,
|
|
1282
1959
|
getShardsDir: () => getShardsDir,
|
|
@@ -1285,15 +1962,18 @@ __export(shard_manager_exports, {
|
|
|
1285
1962
|
listShards: () => listShards,
|
|
1286
1963
|
shardExists: () => shardExists
|
|
1287
1964
|
});
|
|
1288
|
-
import
|
|
1289
|
-
import { existsSync as
|
|
1965
|
+
import path5 from "path";
|
|
1966
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync } from "fs";
|
|
1290
1967
|
import { createClient as createClient2 } from "@libsql/client";
|
|
1291
1968
|
function initShardManager(encryptionKey) {
|
|
1292
1969
|
_encryptionKey = encryptionKey;
|
|
1293
|
-
if (!
|
|
1294
|
-
|
|
1970
|
+
if (!existsSync5(SHARDS_DIR)) {
|
|
1971
|
+
mkdirSync2(SHARDS_DIR, { recursive: true });
|
|
1295
1972
|
}
|
|
1296
1973
|
_shardingEnabled = true;
|
|
1974
|
+
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
1975
|
+
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
1976
|
+
_evictionTimer.unref();
|
|
1297
1977
|
}
|
|
1298
1978
|
function isShardingEnabled() {
|
|
1299
1979
|
return _shardingEnabled;
|
|
@@ -1310,21 +1990,28 @@ function getShardClient(projectName) {
|
|
|
1310
1990
|
throw new Error(`Invalid project name for shard: "${projectName}"`);
|
|
1311
1991
|
}
|
|
1312
1992
|
const cached = _shards.get(safeName);
|
|
1313
|
-
if (cached)
|
|
1314
|
-
|
|
1993
|
+
if (cached) {
|
|
1994
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
1995
|
+
return cached;
|
|
1996
|
+
}
|
|
1997
|
+
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
1998
|
+
evictLRU();
|
|
1999
|
+
}
|
|
2000
|
+
const dbPath = path5.join(SHARDS_DIR, `${safeName}.db`);
|
|
1315
2001
|
const client = createClient2({
|
|
1316
2002
|
url: `file:${dbPath}`,
|
|
1317
2003
|
encryptionKey: _encryptionKey
|
|
1318
2004
|
});
|
|
1319
2005
|
_shards.set(safeName, client);
|
|
2006
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
1320
2007
|
return client;
|
|
1321
2008
|
}
|
|
1322
2009
|
function shardExists(projectName) {
|
|
1323
2010
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
1324
|
-
return
|
|
2011
|
+
return existsSync5(path5.join(SHARDS_DIR, `${safeName}.db`));
|
|
1325
2012
|
}
|
|
1326
2013
|
function listShards() {
|
|
1327
|
-
if (!
|
|
2014
|
+
if (!existsSync5(SHARDS_DIR)) return [];
|
|
1328
2015
|
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
1329
2016
|
}
|
|
1330
2017
|
async function ensureShardSchema(client) {
|
|
@@ -1376,6 +2063,8 @@ async function ensureShardSchema(client) {
|
|
|
1376
2063
|
for (const col of [
|
|
1377
2064
|
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
1378
2065
|
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
2066
|
+
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
2067
|
+
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
1379
2068
|
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
1380
2069
|
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
1381
2070
|
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
@@ -1398,7 +2087,23 @@ async function ensureShardSchema(client) {
|
|
|
1398
2087
|
// MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
|
|
1399
2088
|
"ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
|
|
1400
2089
|
"ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
|
|
1401
|
-
"ALTER TABLE memories ADD COLUMN trajectory TEXT"
|
|
2090
|
+
"ALTER TABLE memories ADD COLUMN trajectory TEXT",
|
|
2091
|
+
// Metadata enrichment columns (must match database.ts)
|
|
2092
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
2093
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
2094
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
2095
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
2096
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
2097
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
2098
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
2099
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
2100
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
2101
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
2102
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
2103
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
2104
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
2105
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
2106
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
1402
2107
|
]) {
|
|
1403
2108
|
try {
|
|
1404
2109
|
await client.execute(col);
|
|
@@ -1497,21 +2202,69 @@ async function getReadyShardClient(projectName) {
|
|
|
1497
2202
|
await ensureShardSchema(client);
|
|
1498
2203
|
return client;
|
|
1499
2204
|
}
|
|
2205
|
+
function evictLRU() {
|
|
2206
|
+
let oldest = null;
|
|
2207
|
+
let oldestTime = Infinity;
|
|
2208
|
+
for (const [name, time] of _shardLastAccess) {
|
|
2209
|
+
if (time < oldestTime) {
|
|
2210
|
+
oldestTime = time;
|
|
2211
|
+
oldest = name;
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
if (oldest) {
|
|
2215
|
+
const client = _shards.get(oldest);
|
|
2216
|
+
if (client) {
|
|
2217
|
+
client.close();
|
|
2218
|
+
}
|
|
2219
|
+
_shards.delete(oldest);
|
|
2220
|
+
_shardLastAccess.delete(oldest);
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
|
+
function evictIdleShards() {
|
|
2224
|
+
const now = Date.now();
|
|
2225
|
+
const toEvict = [];
|
|
2226
|
+
for (const [name, lastAccess] of _shardLastAccess) {
|
|
2227
|
+
if (now - lastAccess > SHARD_IDLE_MS) {
|
|
2228
|
+
toEvict.push(name);
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
for (const name of toEvict) {
|
|
2232
|
+
const client = _shards.get(name);
|
|
2233
|
+
if (client) {
|
|
2234
|
+
client.close();
|
|
2235
|
+
}
|
|
2236
|
+
_shards.delete(name);
|
|
2237
|
+
_shardLastAccess.delete(name);
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
function getOpenShardCount() {
|
|
2241
|
+
return _shards.size;
|
|
2242
|
+
}
|
|
1500
2243
|
function disposeShards() {
|
|
2244
|
+
if (_evictionTimer) {
|
|
2245
|
+
clearInterval(_evictionTimer);
|
|
2246
|
+
_evictionTimer = null;
|
|
2247
|
+
}
|
|
1501
2248
|
for (const [, client] of _shards) {
|
|
1502
2249
|
client.close();
|
|
1503
2250
|
}
|
|
1504
2251
|
_shards.clear();
|
|
2252
|
+
_shardLastAccess.clear();
|
|
1505
2253
|
_shardingEnabled = false;
|
|
1506
2254
|
_encryptionKey = null;
|
|
1507
2255
|
}
|
|
1508
|
-
var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
|
|
2256
|
+
var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
|
|
1509
2257
|
var init_shard_manager = __esm({
|
|
1510
2258
|
"src/lib/shard-manager.ts"() {
|
|
1511
2259
|
"use strict";
|
|
1512
2260
|
init_config();
|
|
1513
|
-
SHARDS_DIR =
|
|
2261
|
+
SHARDS_DIR = path5.join(EXE_AI_DIR, "shards");
|
|
2262
|
+
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
2263
|
+
MAX_OPEN_SHARDS = 10;
|
|
2264
|
+
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
1514
2265
|
_shards = /* @__PURE__ */ new Map();
|
|
2266
|
+
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
2267
|
+
_evictionTimer = null;
|
|
1515
2268
|
_encryptionKey = null;
|
|
1516
2269
|
_shardingEnabled = false;
|
|
1517
2270
|
}
|
|
@@ -1704,13 +2457,50 @@ ${p.content}`).join("\n\n");
|
|
|
1704
2457
|
}
|
|
1705
2458
|
});
|
|
1706
2459
|
|
|
2460
|
+
// src/lib/daemon-auth.ts
|
|
2461
|
+
import crypto from "crypto";
|
|
2462
|
+
import path6 from "path";
|
|
2463
|
+
import { existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
2464
|
+
function normalizeToken(token) {
|
|
2465
|
+
if (!token) return null;
|
|
2466
|
+
const trimmed = token.trim();
|
|
2467
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
2468
|
+
}
|
|
2469
|
+
function readDaemonToken() {
|
|
2470
|
+
try {
|
|
2471
|
+
if (!existsSync6(DAEMON_TOKEN_PATH)) return null;
|
|
2472
|
+
return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
|
|
2473
|
+
} catch {
|
|
2474
|
+
return null;
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2477
|
+
function ensureDaemonToken(seed) {
|
|
2478
|
+
const existing = readDaemonToken();
|
|
2479
|
+
if (existing) return existing;
|
|
2480
|
+
const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
|
|
2481
|
+
ensurePrivateDirSync(EXE_AI_DIR);
|
|
2482
|
+
writeFileSync2(DAEMON_TOKEN_PATH, `${token}
|
|
2483
|
+
`, "utf8");
|
|
2484
|
+
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
2485
|
+
return token;
|
|
2486
|
+
}
|
|
2487
|
+
var DAEMON_TOKEN_PATH;
|
|
2488
|
+
var init_daemon_auth = __esm({
|
|
2489
|
+
"src/lib/daemon-auth.ts"() {
|
|
2490
|
+
"use strict";
|
|
2491
|
+
init_config();
|
|
2492
|
+
init_secure_files();
|
|
2493
|
+
DAEMON_TOKEN_PATH = path6.join(EXE_AI_DIR, "exed.token");
|
|
2494
|
+
}
|
|
2495
|
+
});
|
|
2496
|
+
|
|
1707
2497
|
// src/lib/exe-daemon-client.ts
|
|
1708
2498
|
import net from "net";
|
|
1709
|
-
import
|
|
2499
|
+
import os5 from "os";
|
|
1710
2500
|
import { spawn } from "child_process";
|
|
1711
2501
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
1712
|
-
import { existsSync as
|
|
1713
|
-
import
|
|
2502
|
+
import { existsSync as existsSync7, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
|
|
2503
|
+
import path7 from "path";
|
|
1714
2504
|
import { fileURLToPath } from "url";
|
|
1715
2505
|
function handleData(chunk) {
|
|
1716
2506
|
_buffer += chunk.toString();
|
|
@@ -1738,9 +2528,9 @@ function handleData(chunk) {
|
|
|
1738
2528
|
}
|
|
1739
2529
|
}
|
|
1740
2530
|
function cleanupStaleFiles() {
|
|
1741
|
-
if (
|
|
2531
|
+
if (existsSync7(PID_PATH)) {
|
|
1742
2532
|
try {
|
|
1743
|
-
const pid = parseInt(
|
|
2533
|
+
const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
|
|
1744
2534
|
if (pid > 0) {
|
|
1745
2535
|
try {
|
|
1746
2536
|
process.kill(pid, 0);
|
|
@@ -1761,17 +2551,17 @@ function cleanupStaleFiles() {
|
|
|
1761
2551
|
}
|
|
1762
2552
|
}
|
|
1763
2553
|
function findPackageRoot() {
|
|
1764
|
-
let dir =
|
|
1765
|
-
const { root } =
|
|
2554
|
+
let dir = path7.dirname(fileURLToPath(import.meta.url));
|
|
2555
|
+
const { root } = path7.parse(dir);
|
|
1766
2556
|
while (dir !== root) {
|
|
1767
|
-
if (
|
|
1768
|
-
dir =
|
|
2557
|
+
if (existsSync7(path7.join(dir, "package.json"))) return dir;
|
|
2558
|
+
dir = path7.dirname(dir);
|
|
1769
2559
|
}
|
|
1770
2560
|
return null;
|
|
1771
2561
|
}
|
|
1772
2562
|
function spawnDaemon() {
|
|
1773
|
-
const freeGB =
|
|
1774
|
-
const totalGB =
|
|
2563
|
+
const freeGB = os5.freemem() / (1024 * 1024 * 1024);
|
|
2564
|
+
const totalGB = os5.totalmem() / (1024 * 1024 * 1024);
|
|
1775
2565
|
if (totalGB <= 8) {
|
|
1776
2566
|
process.stderr.write(
|
|
1777
2567
|
`[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
|
|
@@ -1791,16 +2581,17 @@ function spawnDaemon() {
|
|
|
1791
2581
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
1792
2582
|
return;
|
|
1793
2583
|
}
|
|
1794
|
-
const daemonPath =
|
|
1795
|
-
if (!
|
|
2584
|
+
const daemonPath = path7.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
2585
|
+
if (!existsSync7(daemonPath)) {
|
|
1796
2586
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
1797
2587
|
`);
|
|
1798
2588
|
return;
|
|
1799
2589
|
}
|
|
1800
2590
|
const resolvedPath = daemonPath;
|
|
2591
|
+
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
1801
2592
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
1802
2593
|
`);
|
|
1803
|
-
const logPath =
|
|
2594
|
+
const logPath = path7.join(path7.dirname(SOCKET_PATH), "exed.log");
|
|
1804
2595
|
let stderrFd = "ignore";
|
|
1805
2596
|
try {
|
|
1806
2597
|
stderrFd = openSync(logPath, "a");
|
|
@@ -1818,7 +2609,8 @@ function spawnDaemon() {
|
|
|
1818
2609
|
TMUX_PANE: void 0,
|
|
1819
2610
|
// Prevents resolveExeSession() from scoping to one session
|
|
1820
2611
|
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
1821
|
-
EXE_DAEMON_PID: PID_PATH
|
|
2612
|
+
EXE_DAEMON_PID: PID_PATH,
|
|
2613
|
+
[DAEMON_TOKEN_ENV]: daemonToken
|
|
1822
2614
|
}
|
|
1823
2615
|
});
|
|
1824
2616
|
child.unref();
|
|
@@ -1928,13 +2720,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
1928
2720
|
return;
|
|
1929
2721
|
}
|
|
1930
2722
|
const id = randomUUID2();
|
|
2723
|
+
const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
|
|
1931
2724
|
const timer = setTimeout(() => {
|
|
1932
2725
|
_pending.delete(id);
|
|
1933
2726
|
resolve({ error: "Request timeout" });
|
|
1934
2727
|
}, timeoutMs);
|
|
1935
2728
|
_pending.set(id, { resolve, timer });
|
|
1936
2729
|
try {
|
|
1937
|
-
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
2730
|
+
_socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
|
|
1938
2731
|
} catch {
|
|
1939
2732
|
clearTimeout(timer);
|
|
1940
2733
|
_pending.delete(id);
|
|
@@ -1951,101 +2744,156 @@ async function pingDaemon() {
|
|
|
1951
2744
|
return null;
|
|
1952
2745
|
}
|
|
1953
2746
|
function killAndRespawnDaemon() {
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
2747
|
+
if (!acquireSpawnLock()) {
|
|
2748
|
+
process.stderr.write("[exed-client] Another process is already restarting daemon \u2014 skipping\n");
|
|
2749
|
+
if (_socket) {
|
|
2750
|
+
_socket.destroy();
|
|
2751
|
+
_socket = null;
|
|
2752
|
+
}
|
|
2753
|
+
_connected = false;
|
|
2754
|
+
_buffer = "";
|
|
2755
|
+
return;
|
|
2756
|
+
}
|
|
2757
|
+
try {
|
|
2758
|
+
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
2759
|
+
if (existsSync7(PID_PATH)) {
|
|
2760
|
+
try {
|
|
2761
|
+
const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
|
|
2762
|
+
if (pid > 0) {
|
|
2763
|
+
try {
|
|
2764
|
+
process.kill(pid, "SIGKILL");
|
|
2765
|
+
} catch {
|
|
2766
|
+
}
|
|
1962
2767
|
}
|
|
2768
|
+
} catch {
|
|
1963
2769
|
}
|
|
2770
|
+
}
|
|
2771
|
+
if (_socket) {
|
|
2772
|
+
_socket.destroy();
|
|
2773
|
+
_socket = null;
|
|
2774
|
+
}
|
|
2775
|
+
_connected = false;
|
|
2776
|
+
_buffer = "";
|
|
2777
|
+
try {
|
|
2778
|
+
unlinkSync2(PID_PATH);
|
|
1964
2779
|
} catch {
|
|
1965
2780
|
}
|
|
2781
|
+
try {
|
|
2782
|
+
unlinkSync2(SOCKET_PATH);
|
|
2783
|
+
} catch {
|
|
2784
|
+
}
|
|
2785
|
+
spawnDaemon();
|
|
2786
|
+
} finally {
|
|
2787
|
+
releaseSpawnLock();
|
|
1966
2788
|
}
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
_socket = null;
|
|
1970
|
-
}
|
|
1971
|
-
_connected = false;
|
|
1972
|
-
_buffer = "";
|
|
2789
|
+
}
|
|
2790
|
+
function isDaemonTooYoung() {
|
|
1973
2791
|
try {
|
|
1974
|
-
|
|
2792
|
+
const stat2 = statSync(PID_PATH);
|
|
2793
|
+
return Date.now() - stat2.mtimeMs < MIN_DAEMON_AGE_MS;
|
|
1975
2794
|
} catch {
|
|
2795
|
+
return false;
|
|
1976
2796
|
}
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
2797
|
+
}
|
|
2798
|
+
async function retryThenRestart(doRequest, label) {
|
|
2799
|
+
const result = await doRequest();
|
|
2800
|
+
if (!result.error) {
|
|
2801
|
+
_consecutiveFailures = 0;
|
|
2802
|
+
return result;
|
|
2803
|
+
}
|
|
2804
|
+
_consecutiveFailures++;
|
|
2805
|
+
for (let i = 0; i < MAX_RETRIES_BEFORE_RESTART; i++) {
|
|
2806
|
+
const delayMs = RETRY_DELAYS_MS[i] ?? 5e3;
|
|
2807
|
+
process.stderr.write(`[exed-client] ${label} failed (${result.error}), retry ${i + 1}/${MAX_RETRIES_BEFORE_RESTART} in ${delayMs}ms
|
|
2808
|
+
`);
|
|
2809
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
2810
|
+
if (!_connected) {
|
|
2811
|
+
if (!await connectToSocket()) continue;
|
|
2812
|
+
}
|
|
2813
|
+
const retry = await doRequest();
|
|
2814
|
+
if (!retry.error) {
|
|
2815
|
+
_consecutiveFailures = 0;
|
|
2816
|
+
return retry;
|
|
2817
|
+
}
|
|
2818
|
+
_consecutiveFailures++;
|
|
2819
|
+
}
|
|
2820
|
+
if (isDaemonTooYoung()) {
|
|
2821
|
+
process.stderr.write(`[exed-client] ${label}: daemon too young (< ${MIN_DAEMON_AGE_MS / 1e3}s) \u2014 skipping restart
|
|
2822
|
+
`);
|
|
2823
|
+
return { error: result.error };
|
|
1980
2824
|
}
|
|
1981
|
-
|
|
2825
|
+
process.stderr.write(`[exed-client] ${label}: ${_consecutiveFailures} consecutive failures \u2014 restarting daemon
|
|
2826
|
+
`);
|
|
2827
|
+
killAndRespawnDaemon();
|
|
2828
|
+
const start = Date.now();
|
|
2829
|
+
let delay2 = 200;
|
|
2830
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
2831
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
2832
|
+
if (await connectToSocket()) break;
|
|
2833
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
2834
|
+
}
|
|
2835
|
+
if (!_connected) return { error: "Daemon restart failed" };
|
|
2836
|
+
const final = await doRequest();
|
|
2837
|
+
if (!final.error) _consecutiveFailures = 0;
|
|
2838
|
+
return final;
|
|
1982
2839
|
}
|
|
1983
2840
|
async function embedViaClient(text, priority = "high") {
|
|
1984
2841
|
if (!_connected && !await connectEmbedDaemon()) return null;
|
|
1985
2842
|
_requestCount++;
|
|
1986
2843
|
if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
|
|
1987
2844
|
const health = await pingDaemon();
|
|
1988
|
-
if (!health) {
|
|
2845
|
+
if (!health && !isDaemonTooYoung()) {
|
|
1989
2846
|
process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
|
|
1990
2847
|
`);
|
|
1991
2848
|
killAndRespawnDaemon();
|
|
1992
2849
|
const start = Date.now();
|
|
1993
|
-
let
|
|
2850
|
+
let d = 200;
|
|
1994
2851
|
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
1995
|
-
await new Promise((r) => setTimeout(r,
|
|
2852
|
+
await new Promise((r) => setTimeout(r, d));
|
|
1996
2853
|
if (await connectToSocket()) break;
|
|
1997
|
-
|
|
2854
|
+
d = Math.min(d * 2, 3e3);
|
|
1998
2855
|
}
|
|
1999
2856
|
if (!_connected) return null;
|
|
2000
2857
|
}
|
|
2001
2858
|
}
|
|
2002
|
-
const result = await
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
killAndRespawnDaemon();
|
|
2008
|
-
const start = Date.now();
|
|
2009
|
-
let delay2 = 200;
|
|
2010
|
-
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
2011
|
-
await new Promise((r) => setTimeout(r, delay2));
|
|
2012
|
-
if (await connectToSocket()) break;
|
|
2013
|
-
delay2 = Math.min(delay2 * 2, 3e3);
|
|
2014
|
-
}
|
|
2015
|
-
if (!_connected) return null;
|
|
2016
|
-
const retry = await sendRequest([text], priority);
|
|
2017
|
-
if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
|
|
2018
|
-
process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
|
|
2019
|
-
`);
|
|
2020
|
-
}
|
|
2021
|
-
return null;
|
|
2859
|
+
const result = await retryThenRestart(
|
|
2860
|
+
() => sendRequest([text], priority),
|
|
2861
|
+
"Embed"
|
|
2862
|
+
);
|
|
2863
|
+
return !result.error && result.vectors?.[0] ? result.vectors[0] : null;
|
|
2022
2864
|
}
|
|
2023
|
-
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;
|
|
2865
|
+
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, DAEMON_TOKEN_ENV, _socket, _connected, _buffer, _requestCount, _consecutiveFailures, HEALTH_CHECK_INTERVAL, MAX_RETRIES_BEFORE_RESTART, RETRY_DELAYS_MS, MIN_DAEMON_AGE_MS, _pending, MAX_BUFFER;
|
|
2024
2866
|
var init_exe_daemon_client = __esm({
|
|
2025
2867
|
"src/lib/exe-daemon-client.ts"() {
|
|
2026
2868
|
"use strict";
|
|
2027
2869
|
init_config();
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2870
|
+
init_daemon_auth();
|
|
2871
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path7.join(EXE_AI_DIR, "exed.sock");
|
|
2872
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path7.join(EXE_AI_DIR, "exed.pid");
|
|
2873
|
+
SPAWN_LOCK_PATH = path7.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
2031
2874
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
2032
2875
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
2033
2876
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
2877
|
+
DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
|
|
2034
2878
|
_socket = null;
|
|
2035
2879
|
_connected = false;
|
|
2036
2880
|
_buffer = "";
|
|
2037
2881
|
_requestCount = 0;
|
|
2882
|
+
_consecutiveFailures = 0;
|
|
2038
2883
|
HEALTH_CHECK_INTERVAL = 100;
|
|
2884
|
+
MAX_RETRIES_BEFORE_RESTART = 3;
|
|
2885
|
+
RETRY_DELAYS_MS = [1e3, 3e3, 5e3];
|
|
2886
|
+
MIN_DAEMON_AGE_MS = 3e4;
|
|
2039
2887
|
_pending = /* @__PURE__ */ new Map();
|
|
2040
2888
|
MAX_BUFFER = 1e7;
|
|
2041
2889
|
}
|
|
2042
2890
|
});
|
|
2043
2891
|
|
|
2044
2892
|
// src/bin/backfill-conversations.ts
|
|
2045
|
-
import
|
|
2893
|
+
import crypto2 from "crypto";
|
|
2046
2894
|
import { createReadStream } from "fs";
|
|
2047
2895
|
import { readdir, stat } from "fs/promises";
|
|
2048
|
-
import
|
|
2896
|
+
import path8 from "path";
|
|
2049
2897
|
import { createInterface } from "readline";
|
|
2050
2898
|
import { homedir } from "os";
|
|
2051
2899
|
import { parseArgs } from "util";
|
|
@@ -2061,16 +2909,16 @@ init_database();
|
|
|
2061
2909
|
|
|
2062
2910
|
// src/lib/keychain.ts
|
|
2063
2911
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
2064
|
-
import { existsSync as
|
|
2065
|
-
import
|
|
2066
|
-
import
|
|
2912
|
+
import { existsSync as existsSync4 } from "fs";
|
|
2913
|
+
import path4 from "path";
|
|
2914
|
+
import os4 from "os";
|
|
2067
2915
|
var SERVICE = "exe-mem";
|
|
2068
2916
|
var ACCOUNT = "master-key";
|
|
2069
2917
|
function getKeyDir() {
|
|
2070
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
2918
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path4.join(os4.homedir(), ".exe-os");
|
|
2071
2919
|
}
|
|
2072
2920
|
function getKeyPath() {
|
|
2073
|
-
return
|
|
2921
|
+
return path4.join(getKeyDir(), "master.key");
|
|
2074
2922
|
}
|
|
2075
2923
|
async function tryKeytar() {
|
|
2076
2924
|
try {
|
|
@@ -2091,9 +2939,9 @@ async function getMasterKey() {
|
|
|
2091
2939
|
}
|
|
2092
2940
|
}
|
|
2093
2941
|
const keyPath = getKeyPath();
|
|
2094
|
-
if (!
|
|
2942
|
+
if (!existsSync4(keyPath)) {
|
|
2095
2943
|
process.stderr.write(
|
|
2096
|
-
`[keychain] Key not found at ${keyPath} (HOME=${
|
|
2944
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os4.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
2097
2945
|
`
|
|
2098
2946
|
);
|
|
2099
2947
|
return null;
|
|
@@ -2540,7 +3388,7 @@ var MIN_MESSAGES = 3;
|
|
|
2540
3388
|
var MAX_SUMMARY_LENGTH = 4e3;
|
|
2541
3389
|
var MAX_WALK_DEPTH = 10;
|
|
2542
3390
|
async function findJsonlFiles(sinceDate, projectFilter) {
|
|
2543
|
-
const projectsDir =
|
|
3391
|
+
const projectsDir = path8.join(homedir(), ".claude", "projects");
|
|
2544
3392
|
const files = [];
|
|
2545
3393
|
async function walk(dir, depth = 0) {
|
|
2546
3394
|
if (depth > MAX_WALK_DEPTH) return;
|
|
@@ -2551,7 +3399,7 @@ async function findJsonlFiles(sinceDate, projectFilter) {
|
|
|
2551
3399
|
return;
|
|
2552
3400
|
}
|
|
2553
3401
|
for (const entry of entries) {
|
|
2554
|
-
const full =
|
|
3402
|
+
const full = path8.join(dir, entry.name);
|
|
2555
3403
|
if (entry.isDirectory()) {
|
|
2556
3404
|
if (entry.name === "subagents" || entry.name === "tool-results") continue;
|
|
2557
3405
|
await walk(full, depth + 1);
|
|
@@ -2576,7 +3424,7 @@ async function findJsonlFiles(sinceDate, projectFilter) {
|
|
|
2576
3424
|
if (!entry.isDirectory()) continue;
|
|
2577
3425
|
const decoded = decodeProjectDir(entry.name);
|
|
2578
3426
|
if (decoded.toLowerCase().includes(projectFilter.toLowerCase())) {
|
|
2579
|
-
await walk(
|
|
3427
|
+
await walk(path8.join(projectsDir, entry.name));
|
|
2580
3428
|
}
|
|
2581
3429
|
}
|
|
2582
3430
|
} else {
|
|
@@ -2593,14 +3441,14 @@ function decodeProjectDir(dirName) {
|
|
|
2593
3441
|
return dirName;
|
|
2594
3442
|
}
|
|
2595
3443
|
function projectNameFromPath(filePath) {
|
|
2596
|
-
const projectsDir =
|
|
2597
|
-
const relative =
|
|
2598
|
-
const projectDir = relative.split(
|
|
3444
|
+
const projectsDir = path8.join(homedir(), ".claude", "projects");
|
|
3445
|
+
const relative = path8.relative(projectsDir, filePath);
|
|
3446
|
+
const projectDir = relative.split(path8.sep)[0] ?? "unknown";
|
|
2599
3447
|
return decodeProjectDir(projectDir);
|
|
2600
3448
|
}
|
|
2601
3449
|
async function parseConversation(filePath) {
|
|
2602
3450
|
const conv = {
|
|
2603
|
-
sessionId:
|
|
3451
|
+
sessionId: path8.basename(filePath, ".jsonl"),
|
|
2604
3452
|
projectName: projectNameFromPath(filePath),
|
|
2605
3453
|
cwd: void 0,
|
|
2606
3454
|
startTime: void 0,
|
|
@@ -2664,7 +3512,7 @@ async function parseConversation(filePath) {
|
|
|
2664
3512
|
}
|
|
2665
3513
|
}
|
|
2666
3514
|
if (conv.cwd) {
|
|
2667
|
-
conv.projectName =
|
|
3515
|
+
conv.projectName = path8.basename(conv.cwd);
|
|
2668
3516
|
const worktreeMatch = conv.cwd.match(/\.worktrees\/([^/]+)/);
|
|
2669
3517
|
if (worktreeMatch?.[1]) {
|
|
2670
3518
|
conv.agentId = worktreeMatch[1];
|
|
@@ -2837,7 +3685,7 @@ async function backfillConversations(options) {
|
|
|
2837
3685
|
}
|
|
2838
3686
|
}
|
|
2839
3687
|
await writeMemory({
|
|
2840
|
-
id:
|
|
3688
|
+
id: crypto2.randomUUID(),
|
|
2841
3689
|
agent_id: conv.agentId,
|
|
2842
3690
|
agent_role: isCoordinatorName(conv.agentId) ? "COO" : "specialist",
|
|
2843
3691
|
session_id: conv.sessionId,
|