@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
|
@@ -8,9 +8,47 @@ var __export = (target, all) => {
|
|
|
8
8
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
+
// src/lib/secure-files.ts
|
|
12
|
+
import { chmodSync, existsSync, mkdirSync } from "fs";
|
|
13
|
+
import { chmod, mkdir } from "fs/promises";
|
|
14
|
+
async function ensurePrivateDir(dirPath) {
|
|
15
|
+
await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
16
|
+
try {
|
|
17
|
+
await chmod(dirPath, PRIVATE_DIR_MODE);
|
|
18
|
+
} catch {
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function ensurePrivateDirSync(dirPath) {
|
|
22
|
+
mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
23
|
+
try {
|
|
24
|
+
chmodSync(dirPath, PRIVATE_DIR_MODE);
|
|
25
|
+
} catch {
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async function enforcePrivateFile(filePath) {
|
|
29
|
+
try {
|
|
30
|
+
await chmod(filePath, PRIVATE_FILE_MODE);
|
|
31
|
+
} catch {
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function enforcePrivateFileSync(filePath) {
|
|
35
|
+
try {
|
|
36
|
+
if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
|
|
37
|
+
} catch {
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
|
|
41
|
+
var init_secure_files = __esm({
|
|
42
|
+
"src/lib/secure-files.ts"() {
|
|
43
|
+
"use strict";
|
|
44
|
+
PRIVATE_DIR_MODE = 448;
|
|
45
|
+
PRIVATE_FILE_MODE = 384;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
11
49
|
// src/lib/config.ts
|
|
12
|
-
import { readFile, writeFile
|
|
13
|
-
import { readFileSync, existsSync, renameSync } from "fs";
|
|
50
|
+
import { readFile, writeFile } from "fs/promises";
|
|
51
|
+
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
14
52
|
import path from "path";
|
|
15
53
|
import os from "os";
|
|
16
54
|
function resolveDataDir() {
|
|
@@ -18,7 +56,7 @@ function resolveDataDir() {
|
|
|
18
56
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
19
57
|
const newDir = path.join(os.homedir(), ".exe-os");
|
|
20
58
|
const legacyDir = path.join(os.homedir(), ".exe-mem");
|
|
21
|
-
if (!
|
|
59
|
+
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
22
60
|
try {
|
|
23
61
|
renameSync(legacyDir, newDir);
|
|
24
62
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -81,9 +119,9 @@ function normalizeAutoUpdate(raw) {
|
|
|
81
119
|
}
|
|
82
120
|
async function loadConfig() {
|
|
83
121
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
84
|
-
await
|
|
122
|
+
await ensurePrivateDir(dir);
|
|
85
123
|
const configPath = path.join(dir, "config.json");
|
|
86
|
-
if (!
|
|
124
|
+
if (!existsSync2(configPath)) {
|
|
87
125
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
88
126
|
}
|
|
89
127
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -96,6 +134,7 @@ async function loadConfig() {
|
|
|
96
134
|
`);
|
|
97
135
|
try {
|
|
98
136
|
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
137
|
+
await enforcePrivateFile(configPath);
|
|
99
138
|
} catch {
|
|
100
139
|
}
|
|
101
140
|
}
|
|
@@ -115,6 +154,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
|
|
|
115
154
|
var init_config = __esm({
|
|
116
155
|
"src/lib/config.ts"() {
|
|
117
156
|
"use strict";
|
|
157
|
+
init_secure_files();
|
|
118
158
|
EXE_AI_DIR = resolveDataDir();
|
|
119
159
|
DB_PATH = path.join(EXE_AI_DIR, "memories.db");
|
|
120
160
|
MODELS_DIR = path.join(EXE_AI_DIR, "models");
|
|
@@ -193,7 +233,7 @@ var init_config = __esm({
|
|
|
193
233
|
|
|
194
234
|
// src/lib/employees.ts
|
|
195
235
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
196
|
-
import { existsSync as
|
|
236
|
+
import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
|
|
197
237
|
import { execSync as execSync2 } from "child_process";
|
|
198
238
|
import path2 from "path";
|
|
199
239
|
import os2 from "os";
|
|
@@ -210,7 +250,7 @@ function getCoordinatorName(employees = loadEmployeesSync()) {
|
|
|
210
250
|
return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
|
|
211
251
|
}
|
|
212
252
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
213
|
-
if (!
|
|
253
|
+
if (!existsSync3(employeesPath)) return [];
|
|
214
254
|
try {
|
|
215
255
|
return JSON.parse(readFileSync2(employeesPath, "utf-8"));
|
|
216
256
|
} catch {
|
|
@@ -220,7 +260,7 @@ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
|
220
260
|
function getEmployee(employees, name) {
|
|
221
261
|
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
222
262
|
}
|
|
223
|
-
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE;
|
|
263
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, IDENTITY_DIR;
|
|
224
264
|
var init_employees = __esm({
|
|
225
265
|
"src/lib/employees.ts"() {
|
|
226
266
|
"use strict";
|
|
@@ -228,6 +268,7 @@ var init_employees = __esm({
|
|
|
228
268
|
EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
|
|
229
269
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
230
270
|
COORDINATOR_ROLE = "COO";
|
|
271
|
+
IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
|
|
231
272
|
}
|
|
232
273
|
});
|
|
233
274
|
|
|
@@ -286,13 +327,634 @@ var init_db_retry = __esm({
|
|
|
286
327
|
}
|
|
287
328
|
});
|
|
288
329
|
|
|
330
|
+
// src/lib/database-adapter.ts
|
|
331
|
+
import os3 from "os";
|
|
332
|
+
import path4 from "path";
|
|
333
|
+
import { createRequire } from "module";
|
|
334
|
+
import { pathToFileURL } from "url";
|
|
335
|
+
function quotedIdentifier(identifier) {
|
|
336
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
337
|
+
}
|
|
338
|
+
function unqualifiedTableName(name) {
|
|
339
|
+
const raw = name.trim().replace(/^"|"$/g, "");
|
|
340
|
+
const parts = raw.split(".");
|
|
341
|
+
return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
|
|
342
|
+
}
|
|
343
|
+
function stripTrailingSemicolon(sql) {
|
|
344
|
+
return sql.trim().replace(/;+\s*$/u, "");
|
|
345
|
+
}
|
|
346
|
+
function appendClause(sql, clause) {
|
|
347
|
+
const trimmed = stripTrailingSemicolon(sql);
|
|
348
|
+
const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
|
|
349
|
+
if (!returningMatch) {
|
|
350
|
+
return `${trimmed}${clause}`;
|
|
351
|
+
}
|
|
352
|
+
const idx = returningMatch.index;
|
|
353
|
+
return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
|
|
354
|
+
}
|
|
355
|
+
function normalizeStatement(stmt) {
|
|
356
|
+
if (typeof stmt === "string") {
|
|
357
|
+
return { kind: "positional", sql: stmt, args: [] };
|
|
358
|
+
}
|
|
359
|
+
const sql = stmt.sql;
|
|
360
|
+
if (Array.isArray(stmt.args) || stmt.args === void 0) {
|
|
361
|
+
return { kind: "positional", sql, args: stmt.args ?? [] };
|
|
362
|
+
}
|
|
363
|
+
return { kind: "named", sql, args: stmt.args };
|
|
364
|
+
}
|
|
365
|
+
function rewriteBooleanLiterals(sql) {
|
|
366
|
+
let out = sql;
|
|
367
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
368
|
+
const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
|
|
369
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
|
|
370
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
|
|
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
|
+
}
|
|
376
|
+
return out;
|
|
377
|
+
}
|
|
378
|
+
function rewriteInsertOrIgnore(sql) {
|
|
379
|
+
if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
|
|
380
|
+
return sql;
|
|
381
|
+
}
|
|
382
|
+
const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
|
|
383
|
+
return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
|
|
384
|
+
}
|
|
385
|
+
function rewriteInsertOrReplace(sql) {
|
|
386
|
+
const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
|
|
387
|
+
if (!match) {
|
|
388
|
+
return sql;
|
|
389
|
+
}
|
|
390
|
+
const rawTable = match[1];
|
|
391
|
+
const rawColumns = match[2];
|
|
392
|
+
const remainder = match[3];
|
|
393
|
+
const tableName = unqualifiedTableName(rawTable);
|
|
394
|
+
const conflictKeys = UPSERT_KEYS[tableName];
|
|
395
|
+
if (!conflictKeys?.length) {
|
|
396
|
+
return sql;
|
|
397
|
+
}
|
|
398
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
399
|
+
const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
|
|
400
|
+
const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
|
|
401
|
+
const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
|
|
402
|
+
return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
|
|
403
|
+
}
|
|
404
|
+
function rewriteSql(sql) {
|
|
405
|
+
let out = sql;
|
|
406
|
+
out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
|
|
407
|
+
out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
|
|
408
|
+
out = rewriteBooleanLiterals(out);
|
|
409
|
+
out = rewriteInsertOrReplace(out);
|
|
410
|
+
out = rewriteInsertOrIgnore(out);
|
|
411
|
+
return stripTrailingSemicolon(out);
|
|
412
|
+
}
|
|
413
|
+
function toBoolean(value) {
|
|
414
|
+
if (value === null || value === void 0) return value;
|
|
415
|
+
if (typeof value === "boolean") return value;
|
|
416
|
+
if (typeof value === "number") return value !== 0;
|
|
417
|
+
if (typeof value === "bigint") return value !== 0n;
|
|
418
|
+
if (typeof value === "string") {
|
|
419
|
+
const normalized = value.trim().toLowerCase();
|
|
420
|
+
if (normalized === "0" || normalized === "false") return false;
|
|
421
|
+
if (normalized === "1" || normalized === "true") return true;
|
|
422
|
+
}
|
|
423
|
+
return Boolean(value);
|
|
424
|
+
}
|
|
425
|
+
function countQuestionMarks(sql, end) {
|
|
426
|
+
let count = 0;
|
|
427
|
+
let inSingle = false;
|
|
428
|
+
let inDouble = false;
|
|
429
|
+
let inLineComment = false;
|
|
430
|
+
let inBlockComment = false;
|
|
431
|
+
for (let i = 0; i < end; i++) {
|
|
432
|
+
const ch = sql[i];
|
|
433
|
+
const next = sql[i + 1];
|
|
434
|
+
if (inLineComment) {
|
|
435
|
+
if (ch === "\n") inLineComment = false;
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
if (inBlockComment) {
|
|
439
|
+
if (ch === "*" && next === "/") {
|
|
440
|
+
inBlockComment = false;
|
|
441
|
+
i += 1;
|
|
442
|
+
}
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
446
|
+
inLineComment = true;
|
|
447
|
+
i += 1;
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
451
|
+
inBlockComment = true;
|
|
452
|
+
i += 1;
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
456
|
+
inSingle = !inSingle;
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
460
|
+
inDouble = !inDouble;
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
463
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
464
|
+
count += 1;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
return count;
|
|
468
|
+
}
|
|
469
|
+
function findBooleanPlaceholderIndexes(sql) {
|
|
470
|
+
const indexes = /* @__PURE__ */ new Set();
|
|
471
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
472
|
+
const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
|
|
473
|
+
for (const match of sql.matchAll(pattern)) {
|
|
474
|
+
const matchText = match[0];
|
|
475
|
+
const qIndex = match.index + matchText.lastIndexOf("?");
|
|
476
|
+
indexes.add(countQuestionMarks(sql, qIndex + 1));
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
return indexes;
|
|
480
|
+
}
|
|
481
|
+
function coerceInsertBooleanArgs(sql, args) {
|
|
482
|
+
const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
|
|
483
|
+
if (!match) return;
|
|
484
|
+
const rawTable = match[1];
|
|
485
|
+
const rawColumns = match[2];
|
|
486
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
487
|
+
if (!boolColumns?.size) return;
|
|
488
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
489
|
+
for (const [index, column] of columns.entries()) {
|
|
490
|
+
if (boolColumns.has(column) && index < args.length) {
|
|
491
|
+
args[index] = toBoolean(args[index]);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
function coerceUpdateBooleanArgs(sql, args) {
|
|
496
|
+
const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
|
|
497
|
+
if (!match) return;
|
|
498
|
+
const rawTable = match[1];
|
|
499
|
+
const setClause = match[2];
|
|
500
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
501
|
+
if (!boolColumns?.size) return;
|
|
502
|
+
const assignments = setClause.split(",");
|
|
503
|
+
let placeholderIndex = 0;
|
|
504
|
+
for (const assignment of assignments) {
|
|
505
|
+
if (!assignment.includes("?")) continue;
|
|
506
|
+
placeholderIndex += 1;
|
|
507
|
+
const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
|
|
508
|
+
if (colMatch && boolColumns.has(colMatch[1])) {
|
|
509
|
+
args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
function coerceBooleanArgs(sql, args) {
|
|
514
|
+
const nextArgs = [...args];
|
|
515
|
+
coerceInsertBooleanArgs(sql, nextArgs);
|
|
516
|
+
coerceUpdateBooleanArgs(sql, nextArgs);
|
|
517
|
+
const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
|
|
518
|
+
for (const index of placeholderIndexes) {
|
|
519
|
+
if (index > 0 && index <= nextArgs.length) {
|
|
520
|
+
nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
return nextArgs;
|
|
524
|
+
}
|
|
525
|
+
function convertQuestionMarksToDollarParams(sql) {
|
|
526
|
+
let out = "";
|
|
527
|
+
let placeholder = 0;
|
|
528
|
+
let inSingle = false;
|
|
529
|
+
let inDouble = false;
|
|
530
|
+
let inLineComment = false;
|
|
531
|
+
let inBlockComment = false;
|
|
532
|
+
for (let i = 0; i < sql.length; i++) {
|
|
533
|
+
const ch = sql[i];
|
|
534
|
+
const next = sql[i + 1];
|
|
535
|
+
if (inLineComment) {
|
|
536
|
+
out += ch;
|
|
537
|
+
if (ch === "\n") inLineComment = false;
|
|
538
|
+
continue;
|
|
539
|
+
}
|
|
540
|
+
if (inBlockComment) {
|
|
541
|
+
out += ch;
|
|
542
|
+
if (ch === "*" && next === "/") {
|
|
543
|
+
out += next;
|
|
544
|
+
inBlockComment = false;
|
|
545
|
+
i += 1;
|
|
546
|
+
}
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
550
|
+
out += ch + next;
|
|
551
|
+
inLineComment = true;
|
|
552
|
+
i += 1;
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
555
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
556
|
+
out += ch + next;
|
|
557
|
+
inBlockComment = true;
|
|
558
|
+
i += 1;
|
|
559
|
+
continue;
|
|
560
|
+
}
|
|
561
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
562
|
+
inSingle = !inSingle;
|
|
563
|
+
out += ch;
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
567
|
+
inDouble = !inDouble;
|
|
568
|
+
out += ch;
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
572
|
+
placeholder += 1;
|
|
573
|
+
out += `$${placeholder}`;
|
|
574
|
+
continue;
|
|
575
|
+
}
|
|
576
|
+
out += ch;
|
|
577
|
+
}
|
|
578
|
+
return out;
|
|
579
|
+
}
|
|
580
|
+
function translateStatementForPostgres(stmt) {
|
|
581
|
+
const normalized = normalizeStatement(stmt);
|
|
582
|
+
if (normalized.kind === "named") {
|
|
583
|
+
throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
|
|
584
|
+
}
|
|
585
|
+
const rewrittenSql = rewriteSql(normalized.sql);
|
|
586
|
+
const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
|
|
587
|
+
return {
|
|
588
|
+
sql: convertQuestionMarksToDollarParams(rewrittenSql),
|
|
589
|
+
args: coercedArgs
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
function shouldBypassPostgres(stmt) {
|
|
593
|
+
const normalized = normalizeStatement(stmt);
|
|
594
|
+
if (normalized.kind === "named") {
|
|
595
|
+
return true;
|
|
596
|
+
}
|
|
597
|
+
return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
|
|
598
|
+
}
|
|
599
|
+
function shouldFallbackOnError(error) {
|
|
600
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
601
|
+
return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
|
|
602
|
+
}
|
|
603
|
+
function isReadQuery(sql) {
|
|
604
|
+
const trimmed = sql.trimStart();
|
|
605
|
+
return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
|
|
606
|
+
}
|
|
607
|
+
function buildRow(row, columns) {
|
|
608
|
+
const values = columns.map((column) => row[column]);
|
|
609
|
+
return Object.assign(values, row);
|
|
610
|
+
}
|
|
611
|
+
function buildResultSet(rows, rowsAffected = 0) {
|
|
612
|
+
const columns = rows[0] ? Object.keys(rows[0]) : [];
|
|
613
|
+
const resultRows = rows.map((row) => buildRow(row, columns));
|
|
614
|
+
return {
|
|
615
|
+
columns,
|
|
616
|
+
columnTypes: columns.map(() => ""),
|
|
617
|
+
rows: resultRows,
|
|
618
|
+
rowsAffected,
|
|
619
|
+
lastInsertRowid: void 0,
|
|
620
|
+
toJSON() {
|
|
621
|
+
return {
|
|
622
|
+
columns,
|
|
623
|
+
columnTypes: columns.map(() => ""),
|
|
624
|
+
rows,
|
|
625
|
+
rowsAffected,
|
|
626
|
+
lastInsertRowid: void 0
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
async function loadPrismaClient() {
|
|
632
|
+
if (!prismaClientPromise) {
|
|
633
|
+
prismaClientPromise = (async () => {
|
|
634
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
635
|
+
if (explicitPath) {
|
|
636
|
+
const module2 = await import(pathToFileURL(explicitPath).href);
|
|
637
|
+
const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
|
|
638
|
+
if (!PrismaClient2) {
|
|
639
|
+
throw new Error(`No PrismaClient export found at ${explicitPath}`);
|
|
640
|
+
}
|
|
641
|
+
return new PrismaClient2();
|
|
642
|
+
}
|
|
643
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path4.join(os3.homedir(), "exe-db");
|
|
644
|
+
const requireFromExeDb = createRequire(path4.join(exeDbRoot, "package.json"));
|
|
645
|
+
const prismaEntry = requireFromExeDb.resolve("@prisma/client");
|
|
646
|
+
const module = await import(pathToFileURL(prismaEntry).href);
|
|
647
|
+
const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
|
|
648
|
+
if (!PrismaClient) {
|
|
649
|
+
throw new Error(`No PrismaClient export found in ${prismaEntry}`);
|
|
650
|
+
}
|
|
651
|
+
return new PrismaClient();
|
|
652
|
+
})();
|
|
653
|
+
}
|
|
654
|
+
return prismaClientPromise;
|
|
655
|
+
}
|
|
656
|
+
async function ensureCompatibilityViews(prisma) {
|
|
657
|
+
if (!compatibilityBootstrapPromise) {
|
|
658
|
+
compatibilityBootstrapPromise = (async () => {
|
|
659
|
+
for (const mapping of VIEW_MAPPINGS) {
|
|
660
|
+
const relation = mapping.source.replace(/"/g, "");
|
|
661
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
662
|
+
"SELECT to_regclass($1) AS regclass",
|
|
663
|
+
relation
|
|
664
|
+
);
|
|
665
|
+
if (!rows[0]?.regclass) {
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
668
|
+
await prisma.$executeRawUnsafe(
|
|
669
|
+
`CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
})();
|
|
673
|
+
}
|
|
674
|
+
return compatibilityBootstrapPromise;
|
|
675
|
+
}
|
|
676
|
+
async function executeOnPrisma(executor, stmt) {
|
|
677
|
+
const translated = translateStatementForPostgres(stmt);
|
|
678
|
+
if (isReadQuery(translated.sql)) {
|
|
679
|
+
const rows = await executor.$queryRawUnsafe(
|
|
680
|
+
translated.sql,
|
|
681
|
+
...translated.args
|
|
682
|
+
);
|
|
683
|
+
return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
|
|
684
|
+
}
|
|
685
|
+
const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
|
|
686
|
+
return buildResultSet([], rowsAffected);
|
|
687
|
+
}
|
|
688
|
+
function splitSqlStatements(sql) {
|
|
689
|
+
const parts = [];
|
|
690
|
+
let current = "";
|
|
691
|
+
let inSingle = false;
|
|
692
|
+
let inDouble = false;
|
|
693
|
+
let inLineComment = false;
|
|
694
|
+
let inBlockComment = false;
|
|
695
|
+
for (let i = 0; i < sql.length; i++) {
|
|
696
|
+
const ch = sql[i];
|
|
697
|
+
const next = sql[i + 1];
|
|
698
|
+
if (inLineComment) {
|
|
699
|
+
current += ch;
|
|
700
|
+
if (ch === "\n") inLineComment = false;
|
|
701
|
+
continue;
|
|
702
|
+
}
|
|
703
|
+
if (inBlockComment) {
|
|
704
|
+
current += ch;
|
|
705
|
+
if (ch === "*" && next === "/") {
|
|
706
|
+
current += next;
|
|
707
|
+
inBlockComment = false;
|
|
708
|
+
i += 1;
|
|
709
|
+
}
|
|
710
|
+
continue;
|
|
711
|
+
}
|
|
712
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
713
|
+
current += ch + next;
|
|
714
|
+
inLineComment = true;
|
|
715
|
+
i += 1;
|
|
716
|
+
continue;
|
|
717
|
+
}
|
|
718
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
719
|
+
current += ch + next;
|
|
720
|
+
inBlockComment = true;
|
|
721
|
+
i += 1;
|
|
722
|
+
continue;
|
|
723
|
+
}
|
|
724
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
725
|
+
inSingle = !inSingle;
|
|
726
|
+
current += ch;
|
|
727
|
+
continue;
|
|
728
|
+
}
|
|
729
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
730
|
+
inDouble = !inDouble;
|
|
731
|
+
current += ch;
|
|
732
|
+
continue;
|
|
733
|
+
}
|
|
734
|
+
if (!inSingle && !inDouble && ch === ";") {
|
|
735
|
+
if (current.trim()) {
|
|
736
|
+
parts.push(current.trim());
|
|
737
|
+
}
|
|
738
|
+
current = "";
|
|
739
|
+
continue;
|
|
740
|
+
}
|
|
741
|
+
current += ch;
|
|
742
|
+
}
|
|
743
|
+
if (current.trim()) {
|
|
744
|
+
parts.push(current.trim());
|
|
745
|
+
}
|
|
746
|
+
return parts;
|
|
747
|
+
}
|
|
748
|
+
async function createPrismaDbAdapter(fallbackClient) {
|
|
749
|
+
const prisma = await loadPrismaClient();
|
|
750
|
+
await ensureCompatibilityViews(prisma);
|
|
751
|
+
let closed = false;
|
|
752
|
+
let adapter;
|
|
753
|
+
const fallbackExecute = async (stmt, error) => {
|
|
754
|
+
if (!fallbackClient) {
|
|
755
|
+
if (error) throw error;
|
|
756
|
+
throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
|
|
757
|
+
}
|
|
758
|
+
if (error) {
|
|
759
|
+
process.stderr.write(
|
|
760
|
+
`[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
761
|
+
`
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
return fallbackClient.execute(stmt);
|
|
765
|
+
};
|
|
766
|
+
adapter = {
|
|
767
|
+
async execute(stmt) {
|
|
768
|
+
if (shouldBypassPostgres(stmt)) {
|
|
769
|
+
return fallbackExecute(stmt);
|
|
770
|
+
}
|
|
771
|
+
try {
|
|
772
|
+
return await executeOnPrisma(prisma, stmt);
|
|
773
|
+
} catch (error) {
|
|
774
|
+
if (shouldFallbackOnError(error)) {
|
|
775
|
+
return fallbackExecute(stmt, error);
|
|
776
|
+
}
|
|
777
|
+
throw error;
|
|
778
|
+
}
|
|
779
|
+
},
|
|
780
|
+
async batch(stmts, mode) {
|
|
781
|
+
if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
|
|
782
|
+
if (!fallbackClient) {
|
|
783
|
+
throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
|
|
784
|
+
}
|
|
785
|
+
return fallbackClient.batch(stmts, mode);
|
|
786
|
+
}
|
|
787
|
+
try {
|
|
788
|
+
if (prisma.$transaction) {
|
|
789
|
+
return await prisma.$transaction(async (tx) => {
|
|
790
|
+
const results2 = [];
|
|
791
|
+
for (const stmt of stmts) {
|
|
792
|
+
results2.push(await executeOnPrisma(tx, stmt));
|
|
793
|
+
}
|
|
794
|
+
return results2;
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
const results = [];
|
|
798
|
+
for (const stmt of stmts) {
|
|
799
|
+
results.push(await executeOnPrisma(prisma, stmt));
|
|
800
|
+
}
|
|
801
|
+
return results;
|
|
802
|
+
} catch (error) {
|
|
803
|
+
if (fallbackClient && shouldFallbackOnError(error)) {
|
|
804
|
+
process.stderr.write(
|
|
805
|
+
`[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
806
|
+
`
|
|
807
|
+
);
|
|
808
|
+
return fallbackClient.batch(stmts, mode);
|
|
809
|
+
}
|
|
810
|
+
throw error;
|
|
811
|
+
}
|
|
812
|
+
},
|
|
813
|
+
async migrate(stmts) {
|
|
814
|
+
if (fallbackClient) {
|
|
815
|
+
return fallbackClient.migrate(stmts);
|
|
816
|
+
}
|
|
817
|
+
return adapter.batch(stmts, "deferred");
|
|
818
|
+
},
|
|
819
|
+
async transaction(mode) {
|
|
820
|
+
if (!fallbackClient) {
|
|
821
|
+
throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
|
|
822
|
+
}
|
|
823
|
+
return fallbackClient.transaction(mode);
|
|
824
|
+
},
|
|
825
|
+
async executeMultiple(sql) {
|
|
826
|
+
if (fallbackClient && shouldBypassPostgres(sql)) {
|
|
827
|
+
return fallbackClient.executeMultiple(sql);
|
|
828
|
+
}
|
|
829
|
+
for (const statement of splitSqlStatements(sql)) {
|
|
830
|
+
await adapter.execute(statement);
|
|
831
|
+
}
|
|
832
|
+
},
|
|
833
|
+
async sync() {
|
|
834
|
+
if (fallbackClient) {
|
|
835
|
+
return fallbackClient.sync();
|
|
836
|
+
}
|
|
837
|
+
return { frame_no: 0, frames_synced: 0 };
|
|
838
|
+
},
|
|
839
|
+
close() {
|
|
840
|
+
closed = true;
|
|
841
|
+
prismaClientPromise = null;
|
|
842
|
+
compatibilityBootstrapPromise = null;
|
|
843
|
+
void prisma.$disconnect?.();
|
|
844
|
+
},
|
|
845
|
+
get closed() {
|
|
846
|
+
return closed;
|
|
847
|
+
},
|
|
848
|
+
get protocol() {
|
|
849
|
+
return "prisma-postgres";
|
|
850
|
+
}
|
|
851
|
+
};
|
|
852
|
+
return adapter;
|
|
853
|
+
}
|
|
854
|
+
var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
|
|
855
|
+
var init_database_adapter = __esm({
|
|
856
|
+
"src/lib/database-adapter.ts"() {
|
|
857
|
+
"use strict";
|
|
858
|
+
VIEW_MAPPINGS = [
|
|
859
|
+
{ view: "memories", source: "memory.memory_records" },
|
|
860
|
+
{ view: "tasks", source: "memory.tasks" },
|
|
861
|
+
{ view: "behaviors", source: "memory.behaviors" },
|
|
862
|
+
{ view: "entities", source: "memory.entities" },
|
|
863
|
+
{ view: "relationships", source: "memory.relationships" },
|
|
864
|
+
{ view: "entity_memories", source: "memory.entity_memories" },
|
|
865
|
+
{ view: "entity_aliases", source: "memory.entity_aliases" },
|
|
866
|
+
{ view: "notifications", source: "memory.notifications" },
|
|
867
|
+
{ view: "messages", source: "memory.messages" },
|
|
868
|
+
{ view: "users", source: "wiki.users" },
|
|
869
|
+
{ view: "workspaces", source: "wiki.workspaces" },
|
|
870
|
+
{ view: "workspace_users", source: "wiki.workspace_users" },
|
|
871
|
+
{ view: "documents", source: "wiki.workspace_documents" },
|
|
872
|
+
{ view: "chats", source: "wiki.workspace_chats" }
|
|
873
|
+
];
|
|
874
|
+
UPSERT_KEYS = {
|
|
875
|
+
memories: ["id"],
|
|
876
|
+
tasks: ["id"],
|
|
877
|
+
behaviors: ["id"],
|
|
878
|
+
entities: ["id"],
|
|
879
|
+
relationships: ["id"],
|
|
880
|
+
entity_aliases: ["alias"],
|
|
881
|
+
notifications: ["id"],
|
|
882
|
+
messages: ["id"],
|
|
883
|
+
users: ["id"],
|
|
884
|
+
workspaces: ["id"],
|
|
885
|
+
workspace_users: ["id"],
|
|
886
|
+
documents: ["id"],
|
|
887
|
+
chats: ["id"]
|
|
888
|
+
};
|
|
889
|
+
BOOLEAN_COLUMNS_BY_TABLE = {
|
|
890
|
+
memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
|
|
891
|
+
behaviors: /* @__PURE__ */ new Set(["active"]),
|
|
892
|
+
notifications: /* @__PURE__ */ new Set(["read"]),
|
|
893
|
+
users: /* @__PURE__ */ new Set(["has_personal_memory"])
|
|
894
|
+
};
|
|
895
|
+
BOOLEAN_COLUMN_NAMES = new Set(
|
|
896
|
+
Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
|
|
897
|
+
);
|
|
898
|
+
IMMEDIATE_FALLBACK_PATTERNS = [
|
|
899
|
+
/\bPRAGMA\b/i,
|
|
900
|
+
/\bsqlite_master\b/i,
|
|
901
|
+
/(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
|
|
902
|
+
/\bMATCH\b/i,
|
|
903
|
+
/\bvector_distance_cos\s*\(/i,
|
|
904
|
+
/\bjson_extract\s*\(/i,
|
|
905
|
+
/\bjulianday\s*\(/i,
|
|
906
|
+
/\bstrftime\s*\(/i,
|
|
907
|
+
/\blast_insert_rowid\s*\(/i
|
|
908
|
+
];
|
|
909
|
+
prismaClientPromise = null;
|
|
910
|
+
compatibilityBootstrapPromise = null;
|
|
911
|
+
}
|
|
912
|
+
});
|
|
913
|
+
|
|
914
|
+
// src/lib/daemon-auth.ts
|
|
915
|
+
import crypto from "crypto";
|
|
916
|
+
import path5 from "path";
|
|
917
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
918
|
+
function normalizeToken(token) {
|
|
919
|
+
if (!token) return null;
|
|
920
|
+
const trimmed = token.trim();
|
|
921
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
922
|
+
}
|
|
923
|
+
function readDaemonToken() {
|
|
924
|
+
try {
|
|
925
|
+
if (!existsSync4(DAEMON_TOKEN_PATH)) return null;
|
|
926
|
+
return normalizeToken(readFileSync4(DAEMON_TOKEN_PATH, "utf8"));
|
|
927
|
+
} catch {
|
|
928
|
+
return null;
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
function ensureDaemonToken(seed) {
|
|
932
|
+
const existing = readDaemonToken();
|
|
933
|
+
if (existing) return existing;
|
|
934
|
+
const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
|
|
935
|
+
ensurePrivateDirSync(EXE_AI_DIR);
|
|
936
|
+
writeFileSync3(DAEMON_TOKEN_PATH, `${token}
|
|
937
|
+
`, "utf8");
|
|
938
|
+
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
939
|
+
return token;
|
|
940
|
+
}
|
|
941
|
+
var DAEMON_TOKEN_PATH;
|
|
942
|
+
var init_daemon_auth = __esm({
|
|
943
|
+
"src/lib/daemon-auth.ts"() {
|
|
944
|
+
"use strict";
|
|
945
|
+
init_config();
|
|
946
|
+
init_secure_files();
|
|
947
|
+
DAEMON_TOKEN_PATH = path5.join(EXE_AI_DIR, "exed.token");
|
|
948
|
+
}
|
|
949
|
+
});
|
|
950
|
+
|
|
289
951
|
// src/lib/exe-daemon-client.ts
|
|
290
952
|
import net from "net";
|
|
291
|
-
import
|
|
953
|
+
import os4 from "os";
|
|
292
954
|
import { spawn } from "child_process";
|
|
293
955
|
import { randomUUID } from "crypto";
|
|
294
|
-
import { existsSync as
|
|
295
|
-
import
|
|
956
|
+
import { existsSync as existsSync5, unlinkSync as unlinkSync3, readFileSync as readFileSync5, openSync, closeSync, statSync } from "fs";
|
|
957
|
+
import path6 from "path";
|
|
296
958
|
import { fileURLToPath } from "url";
|
|
297
959
|
function handleData(chunk) {
|
|
298
960
|
_buffer += chunk.toString();
|
|
@@ -320,9 +982,9 @@ function handleData(chunk) {
|
|
|
320
982
|
}
|
|
321
983
|
}
|
|
322
984
|
function cleanupStaleFiles() {
|
|
323
|
-
if (
|
|
985
|
+
if (existsSync5(PID_PATH)) {
|
|
324
986
|
try {
|
|
325
|
-
const pid = parseInt(
|
|
987
|
+
const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
|
|
326
988
|
if (pid > 0) {
|
|
327
989
|
try {
|
|
328
990
|
process.kill(pid, 0);
|
|
@@ -343,17 +1005,17 @@ function cleanupStaleFiles() {
|
|
|
343
1005
|
}
|
|
344
1006
|
}
|
|
345
1007
|
function findPackageRoot() {
|
|
346
|
-
let dir =
|
|
347
|
-
const { root } =
|
|
1008
|
+
let dir = path6.dirname(fileURLToPath(import.meta.url));
|
|
1009
|
+
const { root } = path6.parse(dir);
|
|
348
1010
|
while (dir !== root) {
|
|
349
|
-
if (
|
|
350
|
-
dir =
|
|
1011
|
+
if (existsSync5(path6.join(dir, "package.json"))) return dir;
|
|
1012
|
+
dir = path6.dirname(dir);
|
|
351
1013
|
}
|
|
352
1014
|
return null;
|
|
353
1015
|
}
|
|
354
1016
|
function spawnDaemon() {
|
|
355
|
-
const freeGB =
|
|
356
|
-
const totalGB =
|
|
1017
|
+
const freeGB = os4.freemem() / (1024 * 1024 * 1024);
|
|
1018
|
+
const totalGB = os4.totalmem() / (1024 * 1024 * 1024);
|
|
357
1019
|
if (totalGB <= 8) {
|
|
358
1020
|
process.stderr.write(
|
|
359
1021
|
`[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
|
|
@@ -373,16 +1035,17 @@ function spawnDaemon() {
|
|
|
373
1035
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
374
1036
|
return;
|
|
375
1037
|
}
|
|
376
|
-
const daemonPath =
|
|
377
|
-
if (!
|
|
1038
|
+
const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
1039
|
+
if (!existsSync5(daemonPath)) {
|
|
378
1040
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
379
1041
|
`);
|
|
380
1042
|
return;
|
|
381
1043
|
}
|
|
382
1044
|
const resolvedPath = daemonPath;
|
|
1045
|
+
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
383
1046
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
384
1047
|
`);
|
|
385
|
-
const logPath =
|
|
1048
|
+
const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
|
|
386
1049
|
let stderrFd = "ignore";
|
|
387
1050
|
try {
|
|
388
1051
|
stderrFd = openSync(logPath, "a");
|
|
@@ -400,7 +1063,8 @@ function spawnDaemon() {
|
|
|
400
1063
|
TMUX_PANE: void 0,
|
|
401
1064
|
// Prevents resolveExeSession() from scoping to one session
|
|
402
1065
|
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
403
|
-
EXE_DAEMON_PID: PID_PATH
|
|
1066
|
+
EXE_DAEMON_PID: PID_PATH,
|
|
1067
|
+
[DAEMON_TOKEN_ENV]: daemonToken
|
|
404
1068
|
}
|
|
405
1069
|
});
|
|
406
1070
|
child.unref();
|
|
@@ -507,13 +1171,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
507
1171
|
return;
|
|
508
1172
|
}
|
|
509
1173
|
const id = randomUUID();
|
|
1174
|
+
const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
|
|
510
1175
|
const timer = setTimeout(() => {
|
|
511
1176
|
_pending.delete(id);
|
|
512
1177
|
resolve({ error: "Request timeout" });
|
|
513
1178
|
}, timeoutMs);
|
|
514
1179
|
_pending.set(id, { resolve, timer });
|
|
515
1180
|
try {
|
|
516
|
-
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
1181
|
+
_socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
|
|
517
1182
|
} catch {
|
|
518
1183
|
clearTimeout(timer);
|
|
519
1184
|
_pending.delete(id);
|
|
@@ -524,17 +1189,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
524
1189
|
function isClientConnected() {
|
|
525
1190
|
return _connected;
|
|
526
1191
|
}
|
|
527
|
-
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
|
|
1192
|
+
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, DAEMON_TOKEN_ENV, _socket, _connected, _buffer, _pending, MAX_BUFFER;
|
|
528
1193
|
var init_exe_daemon_client = __esm({
|
|
529
1194
|
"src/lib/exe-daemon-client.ts"() {
|
|
530
1195
|
"use strict";
|
|
531
1196
|
init_config();
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
1197
|
+
init_daemon_auth();
|
|
1198
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
|
|
1199
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
|
|
1200
|
+
SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
535
1201
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
536
1202
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
537
1203
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
1204
|
+
DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
|
|
538
1205
|
_socket = null;
|
|
539
1206
|
_connected = false;
|
|
540
1207
|
_buffer = "";
|
|
@@ -613,7 +1280,7 @@ __export(db_daemon_client_exports, {
|
|
|
613
1280
|
createDaemonDbClient: () => createDaemonDbClient,
|
|
614
1281
|
initDaemonDbClient: () => initDaemonDbClient
|
|
615
1282
|
});
|
|
616
|
-
function
|
|
1283
|
+
function normalizeStatement2(stmt) {
|
|
617
1284
|
if (typeof stmt === "string") {
|
|
618
1285
|
return { sql: stmt, args: [] };
|
|
619
1286
|
}
|
|
@@ -637,7 +1304,7 @@ function createDaemonDbClient(fallbackClient) {
|
|
|
637
1304
|
if (!_useDaemon || !isClientConnected()) {
|
|
638
1305
|
return fallbackClient.execute(stmt);
|
|
639
1306
|
}
|
|
640
|
-
const { sql, args } =
|
|
1307
|
+
const { sql, args } = normalizeStatement2(stmt);
|
|
641
1308
|
const response = await sendDaemonRequest({
|
|
642
1309
|
type: "db-execute",
|
|
643
1310
|
sql,
|
|
@@ -662,7 +1329,7 @@ function createDaemonDbClient(fallbackClient) {
|
|
|
662
1329
|
if (!_useDaemon || !isClientConnected()) {
|
|
663
1330
|
return fallbackClient.batch(stmts, mode);
|
|
664
1331
|
}
|
|
665
|
-
const statements = stmts.map(
|
|
1332
|
+
const statements = stmts.map(normalizeStatement2);
|
|
666
1333
|
const response = await sendDaemonRequest({
|
|
667
1334
|
type: "db-batch",
|
|
668
1335
|
statements,
|
|
@@ -757,6 +1424,18 @@ __export(database_exports, {
|
|
|
757
1424
|
});
|
|
758
1425
|
import { createClient } from "@libsql/client";
|
|
759
1426
|
async function initDatabase(config) {
|
|
1427
|
+
if (_walCheckpointTimer) {
|
|
1428
|
+
clearInterval(_walCheckpointTimer);
|
|
1429
|
+
_walCheckpointTimer = null;
|
|
1430
|
+
}
|
|
1431
|
+
if (_daemonClient) {
|
|
1432
|
+
_daemonClient.close();
|
|
1433
|
+
_daemonClient = null;
|
|
1434
|
+
}
|
|
1435
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
1436
|
+
_adapterClient.close();
|
|
1437
|
+
}
|
|
1438
|
+
_adapterClient = null;
|
|
760
1439
|
if (_client) {
|
|
761
1440
|
_client.close();
|
|
762
1441
|
_client = null;
|
|
@@ -770,6 +1449,7 @@ async function initDatabase(config) {
|
|
|
770
1449
|
}
|
|
771
1450
|
_client = createClient(opts);
|
|
772
1451
|
_resilientClient = wrapWithRetry(_client);
|
|
1452
|
+
_adapterClient = _resilientClient;
|
|
773
1453
|
_client.execute("PRAGMA busy_timeout = 30000").catch(() => {
|
|
774
1454
|
});
|
|
775
1455
|
_client.execute("PRAGMA journal_mode = WAL").catch(() => {
|
|
@@ -780,14 +1460,20 @@ async function initDatabase(config) {
|
|
|
780
1460
|
});
|
|
781
1461
|
}, 3e4);
|
|
782
1462
|
_walCheckpointTimer.unref();
|
|
1463
|
+
if (process.env.DATABASE_URL) {
|
|
1464
|
+
_adapterClient = await createPrismaDbAdapter(_resilientClient);
|
|
1465
|
+
}
|
|
783
1466
|
}
|
|
784
1467
|
function isInitialized() {
|
|
785
|
-
return _client !== null;
|
|
1468
|
+
return _adapterClient !== null || _client !== null;
|
|
786
1469
|
}
|
|
787
1470
|
function getClient() {
|
|
788
|
-
if (!
|
|
1471
|
+
if (!_adapterClient) {
|
|
789
1472
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
790
1473
|
}
|
|
1474
|
+
if (process.env.DATABASE_URL) {
|
|
1475
|
+
return _adapterClient;
|
|
1476
|
+
}
|
|
791
1477
|
if (process.env.EXE_IS_DAEMON === "1") {
|
|
792
1478
|
return _resilientClient;
|
|
793
1479
|
}
|
|
@@ -797,6 +1483,7 @@ function getClient() {
|
|
|
797
1483
|
return _resilientClient;
|
|
798
1484
|
}
|
|
799
1485
|
async function initDaemonClient() {
|
|
1486
|
+
if (process.env.DATABASE_URL) return;
|
|
800
1487
|
if (process.env.EXE_IS_DAEMON === "1") return;
|
|
801
1488
|
if (!_resilientClient) return;
|
|
802
1489
|
try {
|
|
@@ -1093,6 +1780,7 @@ async function ensureSchema() {
|
|
|
1093
1780
|
project TEXT NOT NULL,
|
|
1094
1781
|
summary TEXT NOT NULL,
|
|
1095
1782
|
task_file TEXT,
|
|
1783
|
+
session_scope TEXT,
|
|
1096
1784
|
read INTEGER NOT NULL DEFAULT 0,
|
|
1097
1785
|
created_at TEXT NOT NULL
|
|
1098
1786
|
);
|
|
@@ -1101,7 +1789,7 @@ async function ensureSchema() {
|
|
|
1101
1789
|
ON notifications(read);
|
|
1102
1790
|
|
|
1103
1791
|
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
1104
|
-
ON notifications(agent_id);
|
|
1792
|
+
ON notifications(agent_id, session_scope);
|
|
1105
1793
|
|
|
1106
1794
|
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
1107
1795
|
ON notifications(task_file);
|
|
@@ -1139,6 +1827,7 @@ async function ensureSchema() {
|
|
|
1139
1827
|
target_agent TEXT NOT NULL,
|
|
1140
1828
|
target_project TEXT,
|
|
1141
1829
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
1830
|
+
session_scope TEXT,
|
|
1142
1831
|
content TEXT NOT NULL,
|
|
1143
1832
|
priority TEXT DEFAULT 'normal',
|
|
1144
1833
|
status TEXT DEFAULT 'pending',
|
|
@@ -1152,10 +1841,31 @@ async function ensureSchema() {
|
|
|
1152
1841
|
);
|
|
1153
1842
|
|
|
1154
1843
|
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
1155
|
-
ON messages(target_agent, status);
|
|
1844
|
+
ON messages(target_agent, session_scope, status);
|
|
1156
1845
|
|
|
1157
1846
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
1158
|
-
ON messages(target_agent, from_agent, server_seq);
|
|
1847
|
+
ON messages(target_agent, session_scope, from_agent, server_seq);
|
|
1848
|
+
`);
|
|
1849
|
+
try {
|
|
1850
|
+
await client.execute({
|
|
1851
|
+
sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
|
|
1852
|
+
args: []
|
|
1853
|
+
});
|
|
1854
|
+
} catch {
|
|
1855
|
+
}
|
|
1856
|
+
try {
|
|
1857
|
+
await client.execute({
|
|
1858
|
+
sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
|
|
1859
|
+
args: []
|
|
1860
|
+
});
|
|
1861
|
+
} catch {
|
|
1862
|
+
}
|
|
1863
|
+
await client.executeMultiple(`
|
|
1864
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
|
|
1865
|
+
ON notifications(agent_id, session_scope, read, created_at);
|
|
1866
|
+
|
|
1867
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
|
|
1868
|
+
ON messages(target_agent, session_scope, status, created_at);
|
|
1159
1869
|
`);
|
|
1160
1870
|
try {
|
|
1161
1871
|
await client.execute({
|
|
@@ -1739,28 +2449,45 @@ async function ensureSchema() {
|
|
|
1739
2449
|
} catch {
|
|
1740
2450
|
}
|
|
1741
2451
|
}
|
|
2452
|
+
try {
|
|
2453
|
+
await client.execute({
|
|
2454
|
+
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
2455
|
+
args: []
|
|
2456
|
+
});
|
|
2457
|
+
} catch {
|
|
2458
|
+
}
|
|
1742
2459
|
}
|
|
1743
2460
|
async function disposeDatabase() {
|
|
2461
|
+
if (_walCheckpointTimer) {
|
|
2462
|
+
clearInterval(_walCheckpointTimer);
|
|
2463
|
+
_walCheckpointTimer = null;
|
|
2464
|
+
}
|
|
1744
2465
|
if (_daemonClient) {
|
|
1745
2466
|
_daemonClient.close();
|
|
1746
2467
|
_daemonClient = null;
|
|
1747
2468
|
}
|
|
2469
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
2470
|
+
_adapterClient.close();
|
|
2471
|
+
}
|
|
2472
|
+
_adapterClient = null;
|
|
1748
2473
|
if (_client) {
|
|
1749
2474
|
_client.close();
|
|
1750
2475
|
_client = null;
|
|
1751
2476
|
_resilientClient = null;
|
|
1752
2477
|
}
|
|
1753
2478
|
}
|
|
1754
|
-
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
|
|
2479
|
+
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
|
|
1755
2480
|
var init_database = __esm({
|
|
1756
2481
|
"src/lib/database.ts"() {
|
|
1757
2482
|
"use strict";
|
|
1758
2483
|
init_db_retry();
|
|
1759
2484
|
init_employees();
|
|
2485
|
+
init_database_adapter();
|
|
1760
2486
|
_client = null;
|
|
1761
2487
|
_resilientClient = null;
|
|
1762
2488
|
_walCheckpointTimer = null;
|
|
1763
2489
|
_daemonClient = null;
|
|
2490
|
+
_adapterClient = null;
|
|
1764
2491
|
initTurso = initDatabase;
|
|
1765
2492
|
disposeTurso = disposeDatabase;
|
|
1766
2493
|
}
|
|
@@ -1777,14 +2504,14 @@ var init_memory = __esm({
|
|
|
1777
2504
|
|
|
1778
2505
|
// src/lib/keychain.ts
|
|
1779
2506
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
1780
|
-
import { existsSync as
|
|
1781
|
-
import
|
|
1782
|
-
import
|
|
2507
|
+
import { existsSync as existsSync7 } from "fs";
|
|
2508
|
+
import path8 from "path";
|
|
2509
|
+
import os5 from "os";
|
|
1783
2510
|
function getKeyDir() {
|
|
1784
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
2511
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path8.join(os5.homedir(), ".exe-os");
|
|
1785
2512
|
}
|
|
1786
2513
|
function getKeyPath() {
|
|
1787
|
-
return
|
|
2514
|
+
return path8.join(getKeyDir(), "master.key");
|
|
1788
2515
|
}
|
|
1789
2516
|
async function tryKeytar() {
|
|
1790
2517
|
try {
|
|
@@ -1805,9 +2532,9 @@ async function getMasterKey() {
|
|
|
1805
2532
|
}
|
|
1806
2533
|
}
|
|
1807
2534
|
const keyPath = getKeyPath();
|
|
1808
|
-
if (!
|
|
2535
|
+
if (!existsSync7(keyPath)) {
|
|
1809
2536
|
process.stderr.write(
|
|
1810
|
-
`[keychain] Key not found at ${keyPath} (HOME=${
|
|
2537
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os5.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
1811
2538
|
`
|
|
1812
2539
|
);
|
|
1813
2540
|
return null;
|
|
@@ -1892,6 +2619,7 @@ var shard_manager_exports = {};
|
|
|
1892
2619
|
__export(shard_manager_exports, {
|
|
1893
2620
|
disposeShards: () => disposeShards,
|
|
1894
2621
|
ensureShardSchema: () => ensureShardSchema,
|
|
2622
|
+
getOpenShardCount: () => getOpenShardCount,
|
|
1895
2623
|
getReadyShardClient: () => getReadyShardClient,
|
|
1896
2624
|
getShardClient: () => getShardClient,
|
|
1897
2625
|
getShardsDir: () => getShardsDir,
|
|
@@ -1900,15 +2628,18 @@ __export(shard_manager_exports, {
|
|
|
1900
2628
|
listShards: () => listShards,
|
|
1901
2629
|
shardExists: () => shardExists
|
|
1902
2630
|
});
|
|
1903
|
-
import
|
|
1904
|
-
import { existsSync as
|
|
2631
|
+
import path9 from "path";
|
|
2632
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync4, readdirSync as readdirSync3 } from "fs";
|
|
1905
2633
|
import { createClient as createClient2 } from "@libsql/client";
|
|
1906
2634
|
function initShardManager(encryptionKey) {
|
|
1907
2635
|
_encryptionKey = encryptionKey;
|
|
1908
|
-
if (!
|
|
1909
|
-
|
|
2636
|
+
if (!existsSync8(SHARDS_DIR)) {
|
|
2637
|
+
mkdirSync4(SHARDS_DIR, { recursive: true });
|
|
1910
2638
|
}
|
|
1911
2639
|
_shardingEnabled = true;
|
|
2640
|
+
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
2641
|
+
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
2642
|
+
_evictionTimer.unref();
|
|
1912
2643
|
}
|
|
1913
2644
|
function isShardingEnabled() {
|
|
1914
2645
|
return _shardingEnabled;
|
|
@@ -1925,21 +2656,28 @@ function getShardClient(projectName) {
|
|
|
1925
2656
|
throw new Error(`Invalid project name for shard: "${projectName}"`);
|
|
1926
2657
|
}
|
|
1927
2658
|
const cached = _shards.get(safeName);
|
|
1928
|
-
if (cached)
|
|
1929
|
-
|
|
2659
|
+
if (cached) {
|
|
2660
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
2661
|
+
return cached;
|
|
2662
|
+
}
|
|
2663
|
+
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
2664
|
+
evictLRU();
|
|
2665
|
+
}
|
|
2666
|
+
const dbPath = path9.join(SHARDS_DIR, `${safeName}.db`);
|
|
1930
2667
|
const client = createClient2({
|
|
1931
2668
|
url: `file:${dbPath}`,
|
|
1932
2669
|
encryptionKey: _encryptionKey
|
|
1933
2670
|
});
|
|
1934
2671
|
_shards.set(safeName, client);
|
|
2672
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
1935
2673
|
return client;
|
|
1936
2674
|
}
|
|
1937
2675
|
function shardExists(projectName) {
|
|
1938
2676
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
1939
|
-
return
|
|
2677
|
+
return existsSync8(path9.join(SHARDS_DIR, `${safeName}.db`));
|
|
1940
2678
|
}
|
|
1941
2679
|
function listShards() {
|
|
1942
|
-
if (!
|
|
2680
|
+
if (!existsSync8(SHARDS_DIR)) return [];
|
|
1943
2681
|
return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
1944
2682
|
}
|
|
1945
2683
|
async function ensureShardSchema(client) {
|
|
@@ -1991,6 +2729,8 @@ async function ensureShardSchema(client) {
|
|
|
1991
2729
|
for (const col of [
|
|
1992
2730
|
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
1993
2731
|
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
2732
|
+
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
2733
|
+
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
1994
2734
|
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
1995
2735
|
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
1996
2736
|
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
@@ -2013,7 +2753,23 @@ async function ensureShardSchema(client) {
|
|
|
2013
2753
|
// MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
|
|
2014
2754
|
"ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
|
|
2015
2755
|
"ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
|
|
2016
|
-
"ALTER TABLE memories ADD COLUMN trajectory TEXT"
|
|
2756
|
+
"ALTER TABLE memories ADD COLUMN trajectory TEXT",
|
|
2757
|
+
// Metadata enrichment columns (must match database.ts)
|
|
2758
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
2759
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
2760
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
2761
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
2762
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
2763
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
2764
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
2765
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
2766
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
2767
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
2768
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
2769
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
2770
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
2771
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
2772
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
2017
2773
|
]) {
|
|
2018
2774
|
try {
|
|
2019
2775
|
await client.execute(col);
|
|
@@ -2112,21 +2868,69 @@ async function getReadyShardClient(projectName) {
|
|
|
2112
2868
|
await ensureShardSchema(client);
|
|
2113
2869
|
return client;
|
|
2114
2870
|
}
|
|
2871
|
+
function evictLRU() {
|
|
2872
|
+
let oldest = null;
|
|
2873
|
+
let oldestTime = Infinity;
|
|
2874
|
+
for (const [name, time] of _shardLastAccess) {
|
|
2875
|
+
if (time < oldestTime) {
|
|
2876
|
+
oldestTime = time;
|
|
2877
|
+
oldest = name;
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2880
|
+
if (oldest) {
|
|
2881
|
+
const client = _shards.get(oldest);
|
|
2882
|
+
if (client) {
|
|
2883
|
+
client.close();
|
|
2884
|
+
}
|
|
2885
|
+
_shards.delete(oldest);
|
|
2886
|
+
_shardLastAccess.delete(oldest);
|
|
2887
|
+
}
|
|
2888
|
+
}
|
|
2889
|
+
function evictIdleShards() {
|
|
2890
|
+
const now = Date.now();
|
|
2891
|
+
const toEvict = [];
|
|
2892
|
+
for (const [name, lastAccess] of _shardLastAccess) {
|
|
2893
|
+
if (now - lastAccess > SHARD_IDLE_MS) {
|
|
2894
|
+
toEvict.push(name);
|
|
2895
|
+
}
|
|
2896
|
+
}
|
|
2897
|
+
for (const name of toEvict) {
|
|
2898
|
+
const client = _shards.get(name);
|
|
2899
|
+
if (client) {
|
|
2900
|
+
client.close();
|
|
2901
|
+
}
|
|
2902
|
+
_shards.delete(name);
|
|
2903
|
+
_shardLastAccess.delete(name);
|
|
2904
|
+
}
|
|
2905
|
+
}
|
|
2906
|
+
function getOpenShardCount() {
|
|
2907
|
+
return _shards.size;
|
|
2908
|
+
}
|
|
2115
2909
|
function disposeShards() {
|
|
2910
|
+
if (_evictionTimer) {
|
|
2911
|
+
clearInterval(_evictionTimer);
|
|
2912
|
+
_evictionTimer = null;
|
|
2913
|
+
}
|
|
2116
2914
|
for (const [, client] of _shards) {
|
|
2117
2915
|
client.close();
|
|
2118
2916
|
}
|
|
2119
2917
|
_shards.clear();
|
|
2918
|
+
_shardLastAccess.clear();
|
|
2120
2919
|
_shardingEnabled = false;
|
|
2121
2920
|
_encryptionKey = null;
|
|
2122
2921
|
}
|
|
2123
|
-
var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
|
|
2922
|
+
var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
|
|
2124
2923
|
var init_shard_manager = __esm({
|
|
2125
2924
|
"src/lib/shard-manager.ts"() {
|
|
2126
2925
|
"use strict";
|
|
2127
2926
|
init_config();
|
|
2128
|
-
SHARDS_DIR =
|
|
2927
|
+
SHARDS_DIR = path9.join(EXE_AI_DIR, "shards");
|
|
2928
|
+
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
2929
|
+
MAX_OPEN_SHARDS = 10;
|
|
2930
|
+
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
2129
2931
|
_shards = /* @__PURE__ */ new Map();
|
|
2932
|
+
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
2933
|
+
_evictionTimer = null;
|
|
2130
2934
|
_encryptionKey = null;
|
|
2131
2935
|
_shardingEnabled = false;
|
|
2132
2936
|
}
|
|
@@ -2891,7 +3695,7 @@ var init_store = __esm({
|
|
|
2891
3695
|
|
|
2892
3696
|
// src/lib/active-agent.ts
|
|
2893
3697
|
init_config();
|
|
2894
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync, unlinkSync as unlinkSync2, readdirSync } from "fs";
|
|
3698
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2, readdirSync } from "fs";
|
|
2895
3699
|
import { execSync as execSync3 } from "child_process";
|
|
2896
3700
|
import path3 from "path";
|
|
2897
3701
|
|
|
@@ -3052,18 +3856,18 @@ function getActiveAgent() {
|
|
|
3052
3856
|
// src/lib/identity.ts
|
|
3053
3857
|
init_config();
|
|
3054
3858
|
init_database();
|
|
3055
|
-
import { existsSync as
|
|
3859
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
3056
3860
|
import { readdirSync as readdirSync2 } from "fs";
|
|
3057
|
-
import
|
|
3861
|
+
import path7 from "path";
|
|
3058
3862
|
import { createHash } from "crypto";
|
|
3059
|
-
var
|
|
3863
|
+
var IDENTITY_DIR2 = path7.join(EXE_AI_DIR, "identity");
|
|
3060
3864
|
function ensureDir() {
|
|
3061
|
-
if (!
|
|
3062
|
-
|
|
3865
|
+
if (!existsSync6(IDENTITY_DIR2)) {
|
|
3866
|
+
mkdirSync3(IDENTITY_DIR2, { recursive: true });
|
|
3063
3867
|
}
|
|
3064
3868
|
}
|
|
3065
3869
|
function identityPath(agentId) {
|
|
3066
|
-
return
|
|
3870
|
+
return path7.join(IDENTITY_DIR2, `${agentId}.md`);
|
|
3067
3871
|
}
|
|
3068
3872
|
function parseFrontmatter(raw) {
|
|
3069
3873
|
const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
@@ -3104,8 +3908,8 @@ function contentHash(content) {
|
|
|
3104
3908
|
}
|
|
3105
3909
|
function getIdentity(agentId) {
|
|
3106
3910
|
const filePath = identityPath(agentId);
|
|
3107
|
-
if (!
|
|
3108
|
-
const raw =
|
|
3911
|
+
if (!existsSync6(filePath)) return null;
|
|
3912
|
+
const raw = readFileSync6(filePath, "utf-8");
|
|
3109
3913
|
const { frontmatter, body } = parseFrontmatter(raw);
|
|
3110
3914
|
return {
|
|
3111
3915
|
agentId,
|
|
@@ -3117,7 +3921,7 @@ function getIdentity(agentId) {
|
|
|
3117
3921
|
}
|
|
3118
3922
|
function listIdentities() {
|
|
3119
3923
|
ensureDir();
|
|
3120
|
-
const files = readdirSync2(
|
|
3924
|
+
const files = readdirSync2(IDENTITY_DIR2).filter((f) => f.endsWith(".md"));
|
|
3121
3925
|
const results = [];
|
|
3122
3926
|
for (const file of files) {
|
|
3123
3927
|
const agentId = file.replace(".md", "");
|
|
@@ -3205,12 +4009,12 @@ process.stdin.on("end", async () => {
|
|
|
3205
4009
|
} catch {
|
|
3206
4010
|
}
|
|
3207
4011
|
try {
|
|
3208
|
-
const { readFileSync:
|
|
4012
|
+
const { readFileSync: readFileSync7, existsSync: existsSync9 } = await import("fs");
|
|
3209
4013
|
const { join } = await import("path");
|
|
3210
|
-
const
|
|
3211
|
-
const claudeMd = join(
|
|
3212
|
-
if (
|
|
3213
|
-
const content =
|
|
4014
|
+
const os6 = await import("os");
|
|
4015
|
+
const claudeMd = join(os6.homedir(), ".claude", "CLAUDE.md");
|
|
4016
|
+
if (existsSync9(claudeMd)) {
|
|
4017
|
+
const content = readFileSync7(claudeMd, "utf8");
|
|
3214
4018
|
const hasOrchRules = content.includes("exe-os:orchestration-start");
|
|
3215
4019
|
if (hasOrchRules) {
|
|
3216
4020
|
checks.push("PASS: orchestration rules intact in CLAUDE.md");
|