@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
|
@@ -19,9 +19,47 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
19
19
|
};
|
|
20
20
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
21
21
|
|
|
22
|
+
// src/lib/secure-files.ts
|
|
23
|
+
import { chmodSync, existsSync, mkdirSync } from "fs";
|
|
24
|
+
import { chmod, mkdir } from "fs/promises";
|
|
25
|
+
async function ensurePrivateDir(dirPath) {
|
|
26
|
+
await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
27
|
+
try {
|
|
28
|
+
await chmod(dirPath, PRIVATE_DIR_MODE);
|
|
29
|
+
} catch {
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function ensurePrivateDirSync(dirPath) {
|
|
33
|
+
mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
34
|
+
try {
|
|
35
|
+
chmodSync(dirPath, PRIVATE_DIR_MODE);
|
|
36
|
+
} catch {
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async function enforcePrivateFile(filePath) {
|
|
40
|
+
try {
|
|
41
|
+
await chmod(filePath, PRIVATE_FILE_MODE);
|
|
42
|
+
} catch {
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function enforcePrivateFileSync(filePath) {
|
|
46
|
+
try {
|
|
47
|
+
if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
|
|
48
|
+
} catch {
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
|
|
52
|
+
var init_secure_files = __esm({
|
|
53
|
+
"src/lib/secure-files.ts"() {
|
|
54
|
+
"use strict";
|
|
55
|
+
PRIVATE_DIR_MODE = 448;
|
|
56
|
+
PRIVATE_FILE_MODE = 384;
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
22
60
|
// src/lib/config.ts
|
|
23
|
-
import { readFile, writeFile
|
|
24
|
-
import { readFileSync, existsSync, renameSync } from "fs";
|
|
61
|
+
import { readFile, writeFile } from "fs/promises";
|
|
62
|
+
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
25
63
|
import path from "path";
|
|
26
64
|
import os from "os";
|
|
27
65
|
function resolveDataDir() {
|
|
@@ -29,7 +67,7 @@ function resolveDataDir() {
|
|
|
29
67
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
30
68
|
const newDir = path.join(os.homedir(), ".exe-os");
|
|
31
69
|
const legacyDir = path.join(os.homedir(), ".exe-mem");
|
|
32
|
-
if (!
|
|
70
|
+
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
33
71
|
try {
|
|
34
72
|
renameSync(legacyDir, newDir);
|
|
35
73
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -92,9 +130,9 @@ function normalizeAutoUpdate(raw) {
|
|
|
92
130
|
}
|
|
93
131
|
async function loadConfig() {
|
|
94
132
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
95
|
-
await
|
|
133
|
+
await ensurePrivateDir(dir);
|
|
96
134
|
const configPath = path.join(dir, "config.json");
|
|
97
|
-
if (!
|
|
135
|
+
if (!existsSync2(configPath)) {
|
|
98
136
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
99
137
|
}
|
|
100
138
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -107,6 +145,7 @@ async function loadConfig() {
|
|
|
107
145
|
`);
|
|
108
146
|
try {
|
|
109
147
|
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
148
|
+
await enforcePrivateFile(configPath);
|
|
110
149
|
} catch {
|
|
111
150
|
}
|
|
112
151
|
}
|
|
@@ -126,6 +165,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
|
|
|
126
165
|
var init_config = __esm({
|
|
127
166
|
"src/lib/config.ts"() {
|
|
128
167
|
"use strict";
|
|
168
|
+
init_secure_files();
|
|
129
169
|
EXE_AI_DIR = resolveDataDir();
|
|
130
170
|
DB_PATH = path.join(EXE_AI_DIR, "memories.db");
|
|
131
171
|
MODELS_DIR = path.join(EXE_AI_DIR, "models");
|
|
@@ -272,7 +312,7 @@ var init_session_key = __esm({
|
|
|
272
312
|
|
|
273
313
|
// src/lib/employees.ts
|
|
274
314
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
275
|
-
import { existsSync as
|
|
315
|
+
import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
|
|
276
316
|
import { execSync as execSync2 } from "child_process";
|
|
277
317
|
import path2 from "path";
|
|
278
318
|
import os2 from "os";
|
|
@@ -296,7 +336,7 @@ function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
|
|
|
296
336
|
return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
|
|
297
337
|
}
|
|
298
338
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
299
|
-
if (!
|
|
339
|
+
if (!existsSync3(employeesPath)) return [];
|
|
300
340
|
try {
|
|
301
341
|
return JSON.parse(readFileSync2(employeesPath, "utf-8"));
|
|
302
342
|
} catch {
|
|
@@ -306,7 +346,7 @@ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
|
306
346
|
function getEmployee(employees, name) {
|
|
307
347
|
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
308
348
|
}
|
|
309
|
-
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE;
|
|
349
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, IDENTITY_DIR;
|
|
310
350
|
var init_employees = __esm({
|
|
311
351
|
"src/lib/employees.ts"() {
|
|
312
352
|
"use strict";
|
|
@@ -314,6 +354,7 @@ var init_employees = __esm({
|
|
|
314
354
|
EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
|
|
315
355
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
316
356
|
COORDINATOR_ROLE = "COO";
|
|
357
|
+
IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
|
|
317
358
|
}
|
|
318
359
|
});
|
|
319
360
|
|
|
@@ -491,7 +532,7 @@ var init_runtime_table = __esm({
|
|
|
491
532
|
});
|
|
492
533
|
|
|
493
534
|
// src/lib/agent-config.ts
|
|
494
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as
|
|
535
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync4 } from "fs";
|
|
495
536
|
import path5 from "path";
|
|
496
537
|
var AGENT_CONFIG_PATH, DEFAULT_MODELS;
|
|
497
538
|
var init_agent_config = __esm({
|
|
@@ -499,6 +540,7 @@ var init_agent_config = __esm({
|
|
|
499
540
|
"use strict";
|
|
500
541
|
init_config();
|
|
501
542
|
init_runtime_table();
|
|
543
|
+
init_secure_files();
|
|
502
544
|
AGENT_CONFIG_PATH = path5.join(EXE_AI_DIR, "agent-config.json");
|
|
503
545
|
DEFAULT_MODELS = {
|
|
504
546
|
claude: "claude-opus-4",
|
|
@@ -509,7 +551,7 @@ var init_agent_config = __esm({
|
|
|
509
551
|
});
|
|
510
552
|
|
|
511
553
|
// src/lib/intercom-queue.ts
|
|
512
|
-
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as
|
|
554
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
|
|
513
555
|
import path6 from "path";
|
|
514
556
|
import os4 from "os";
|
|
515
557
|
var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
|
|
@@ -577,13 +619,634 @@ var init_db_retry = __esm({
|
|
|
577
619
|
}
|
|
578
620
|
});
|
|
579
621
|
|
|
622
|
+
// src/lib/database-adapter.ts
|
|
623
|
+
import os5 from "os";
|
|
624
|
+
import path7 from "path";
|
|
625
|
+
import { createRequire } from "module";
|
|
626
|
+
import { pathToFileURL } from "url";
|
|
627
|
+
function quotedIdentifier(identifier) {
|
|
628
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
629
|
+
}
|
|
630
|
+
function unqualifiedTableName(name) {
|
|
631
|
+
const raw = name.trim().replace(/^"|"$/g, "");
|
|
632
|
+
const parts = raw.split(".");
|
|
633
|
+
return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
|
|
634
|
+
}
|
|
635
|
+
function stripTrailingSemicolon(sql) {
|
|
636
|
+
return sql.trim().replace(/;+\s*$/u, "");
|
|
637
|
+
}
|
|
638
|
+
function appendClause(sql, clause) {
|
|
639
|
+
const trimmed = stripTrailingSemicolon(sql);
|
|
640
|
+
const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
|
|
641
|
+
if (!returningMatch) {
|
|
642
|
+
return `${trimmed}${clause}`;
|
|
643
|
+
}
|
|
644
|
+
const idx = returningMatch.index;
|
|
645
|
+
return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
|
|
646
|
+
}
|
|
647
|
+
function normalizeStatement(stmt) {
|
|
648
|
+
if (typeof stmt === "string") {
|
|
649
|
+
return { kind: "positional", sql: stmt, args: [] };
|
|
650
|
+
}
|
|
651
|
+
const sql = stmt.sql;
|
|
652
|
+
if (Array.isArray(stmt.args) || stmt.args === void 0) {
|
|
653
|
+
return { kind: "positional", sql, args: stmt.args ?? [] };
|
|
654
|
+
}
|
|
655
|
+
return { kind: "named", sql, args: stmt.args };
|
|
656
|
+
}
|
|
657
|
+
function rewriteBooleanLiterals(sql) {
|
|
658
|
+
let out = sql;
|
|
659
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
660
|
+
const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
|
|
661
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
|
|
662
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
|
|
663
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
|
|
664
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
|
|
665
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
|
|
666
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
|
|
667
|
+
}
|
|
668
|
+
return out;
|
|
669
|
+
}
|
|
670
|
+
function rewriteInsertOrIgnore(sql) {
|
|
671
|
+
if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
|
|
672
|
+
return sql;
|
|
673
|
+
}
|
|
674
|
+
const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
|
|
675
|
+
return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
|
|
676
|
+
}
|
|
677
|
+
function rewriteInsertOrReplace(sql) {
|
|
678
|
+
const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
|
|
679
|
+
if (!match) {
|
|
680
|
+
return sql;
|
|
681
|
+
}
|
|
682
|
+
const rawTable = match[1];
|
|
683
|
+
const rawColumns = match[2];
|
|
684
|
+
const remainder = match[3];
|
|
685
|
+
const tableName = unqualifiedTableName(rawTable);
|
|
686
|
+
const conflictKeys = UPSERT_KEYS[tableName];
|
|
687
|
+
if (!conflictKeys?.length) {
|
|
688
|
+
return sql;
|
|
689
|
+
}
|
|
690
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
691
|
+
const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
|
|
692
|
+
const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
|
|
693
|
+
const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
|
|
694
|
+
return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
|
|
695
|
+
}
|
|
696
|
+
function rewriteSql(sql) {
|
|
697
|
+
let out = sql;
|
|
698
|
+
out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
|
|
699
|
+
out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
|
|
700
|
+
out = rewriteBooleanLiterals(out);
|
|
701
|
+
out = rewriteInsertOrReplace(out);
|
|
702
|
+
out = rewriteInsertOrIgnore(out);
|
|
703
|
+
return stripTrailingSemicolon(out);
|
|
704
|
+
}
|
|
705
|
+
function toBoolean(value) {
|
|
706
|
+
if (value === null || value === void 0) return value;
|
|
707
|
+
if (typeof value === "boolean") return value;
|
|
708
|
+
if (typeof value === "number") return value !== 0;
|
|
709
|
+
if (typeof value === "bigint") return value !== 0n;
|
|
710
|
+
if (typeof value === "string") {
|
|
711
|
+
const normalized = value.trim().toLowerCase();
|
|
712
|
+
if (normalized === "0" || normalized === "false") return false;
|
|
713
|
+
if (normalized === "1" || normalized === "true") return true;
|
|
714
|
+
}
|
|
715
|
+
return Boolean(value);
|
|
716
|
+
}
|
|
717
|
+
function countQuestionMarks(sql, end) {
|
|
718
|
+
let count = 0;
|
|
719
|
+
let inSingle = false;
|
|
720
|
+
let inDouble = false;
|
|
721
|
+
let inLineComment = false;
|
|
722
|
+
let inBlockComment = false;
|
|
723
|
+
for (let i = 0; i < end; i++) {
|
|
724
|
+
const ch = sql[i];
|
|
725
|
+
const next = sql[i + 1];
|
|
726
|
+
if (inLineComment) {
|
|
727
|
+
if (ch === "\n") inLineComment = false;
|
|
728
|
+
continue;
|
|
729
|
+
}
|
|
730
|
+
if (inBlockComment) {
|
|
731
|
+
if (ch === "*" && next === "/") {
|
|
732
|
+
inBlockComment = false;
|
|
733
|
+
i += 1;
|
|
734
|
+
}
|
|
735
|
+
continue;
|
|
736
|
+
}
|
|
737
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
738
|
+
inLineComment = true;
|
|
739
|
+
i += 1;
|
|
740
|
+
continue;
|
|
741
|
+
}
|
|
742
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
743
|
+
inBlockComment = true;
|
|
744
|
+
i += 1;
|
|
745
|
+
continue;
|
|
746
|
+
}
|
|
747
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
748
|
+
inSingle = !inSingle;
|
|
749
|
+
continue;
|
|
750
|
+
}
|
|
751
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
752
|
+
inDouble = !inDouble;
|
|
753
|
+
continue;
|
|
754
|
+
}
|
|
755
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
756
|
+
count += 1;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
return count;
|
|
760
|
+
}
|
|
761
|
+
function findBooleanPlaceholderIndexes(sql) {
|
|
762
|
+
const indexes = /* @__PURE__ */ new Set();
|
|
763
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
764
|
+
const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
|
|
765
|
+
for (const match of sql.matchAll(pattern)) {
|
|
766
|
+
const matchText = match[0];
|
|
767
|
+
const qIndex = match.index + matchText.lastIndexOf("?");
|
|
768
|
+
indexes.add(countQuestionMarks(sql, qIndex + 1));
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
return indexes;
|
|
772
|
+
}
|
|
773
|
+
function coerceInsertBooleanArgs(sql, args) {
|
|
774
|
+
const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
|
|
775
|
+
if (!match) return;
|
|
776
|
+
const rawTable = match[1];
|
|
777
|
+
const rawColumns = match[2];
|
|
778
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
779
|
+
if (!boolColumns?.size) return;
|
|
780
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
781
|
+
for (const [index, column] of columns.entries()) {
|
|
782
|
+
if (boolColumns.has(column) && index < args.length) {
|
|
783
|
+
args[index] = toBoolean(args[index]);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
function coerceUpdateBooleanArgs(sql, args) {
|
|
788
|
+
const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
|
|
789
|
+
if (!match) return;
|
|
790
|
+
const rawTable = match[1];
|
|
791
|
+
const setClause = match[2];
|
|
792
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
793
|
+
if (!boolColumns?.size) return;
|
|
794
|
+
const assignments = setClause.split(",");
|
|
795
|
+
let placeholderIndex = 0;
|
|
796
|
+
for (const assignment of assignments) {
|
|
797
|
+
if (!assignment.includes("?")) continue;
|
|
798
|
+
placeholderIndex += 1;
|
|
799
|
+
const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
|
|
800
|
+
if (colMatch && boolColumns.has(colMatch[1])) {
|
|
801
|
+
args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
function coerceBooleanArgs(sql, args) {
|
|
806
|
+
const nextArgs = [...args];
|
|
807
|
+
coerceInsertBooleanArgs(sql, nextArgs);
|
|
808
|
+
coerceUpdateBooleanArgs(sql, nextArgs);
|
|
809
|
+
const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
|
|
810
|
+
for (const index of placeholderIndexes) {
|
|
811
|
+
if (index > 0 && index <= nextArgs.length) {
|
|
812
|
+
nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
return nextArgs;
|
|
816
|
+
}
|
|
817
|
+
function convertQuestionMarksToDollarParams(sql) {
|
|
818
|
+
let out = "";
|
|
819
|
+
let placeholder = 0;
|
|
820
|
+
let inSingle = false;
|
|
821
|
+
let inDouble = false;
|
|
822
|
+
let inLineComment = false;
|
|
823
|
+
let inBlockComment = false;
|
|
824
|
+
for (let i = 0; i < sql.length; i++) {
|
|
825
|
+
const ch = sql[i];
|
|
826
|
+
const next = sql[i + 1];
|
|
827
|
+
if (inLineComment) {
|
|
828
|
+
out += ch;
|
|
829
|
+
if (ch === "\n") inLineComment = false;
|
|
830
|
+
continue;
|
|
831
|
+
}
|
|
832
|
+
if (inBlockComment) {
|
|
833
|
+
out += ch;
|
|
834
|
+
if (ch === "*" && next === "/") {
|
|
835
|
+
out += next;
|
|
836
|
+
inBlockComment = false;
|
|
837
|
+
i += 1;
|
|
838
|
+
}
|
|
839
|
+
continue;
|
|
840
|
+
}
|
|
841
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
842
|
+
out += ch + next;
|
|
843
|
+
inLineComment = true;
|
|
844
|
+
i += 1;
|
|
845
|
+
continue;
|
|
846
|
+
}
|
|
847
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
848
|
+
out += ch + next;
|
|
849
|
+
inBlockComment = true;
|
|
850
|
+
i += 1;
|
|
851
|
+
continue;
|
|
852
|
+
}
|
|
853
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
854
|
+
inSingle = !inSingle;
|
|
855
|
+
out += ch;
|
|
856
|
+
continue;
|
|
857
|
+
}
|
|
858
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
859
|
+
inDouble = !inDouble;
|
|
860
|
+
out += ch;
|
|
861
|
+
continue;
|
|
862
|
+
}
|
|
863
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
864
|
+
placeholder += 1;
|
|
865
|
+
out += `$${placeholder}`;
|
|
866
|
+
continue;
|
|
867
|
+
}
|
|
868
|
+
out += ch;
|
|
869
|
+
}
|
|
870
|
+
return out;
|
|
871
|
+
}
|
|
872
|
+
function translateStatementForPostgres(stmt) {
|
|
873
|
+
const normalized = normalizeStatement(stmt);
|
|
874
|
+
if (normalized.kind === "named") {
|
|
875
|
+
throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
|
|
876
|
+
}
|
|
877
|
+
const rewrittenSql = rewriteSql(normalized.sql);
|
|
878
|
+
const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
|
|
879
|
+
return {
|
|
880
|
+
sql: convertQuestionMarksToDollarParams(rewrittenSql),
|
|
881
|
+
args: coercedArgs
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
function shouldBypassPostgres(stmt) {
|
|
885
|
+
const normalized = normalizeStatement(stmt);
|
|
886
|
+
if (normalized.kind === "named") {
|
|
887
|
+
return true;
|
|
888
|
+
}
|
|
889
|
+
return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
|
|
890
|
+
}
|
|
891
|
+
function shouldFallbackOnError(error) {
|
|
892
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
893
|
+
return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
|
|
894
|
+
}
|
|
895
|
+
function isReadQuery(sql) {
|
|
896
|
+
const trimmed = sql.trimStart();
|
|
897
|
+
return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
|
|
898
|
+
}
|
|
899
|
+
function buildRow(row, columns) {
|
|
900
|
+
const values = columns.map((column) => row[column]);
|
|
901
|
+
return Object.assign(values, row);
|
|
902
|
+
}
|
|
903
|
+
function buildResultSet(rows, rowsAffected = 0) {
|
|
904
|
+
const columns = rows[0] ? Object.keys(rows[0]) : [];
|
|
905
|
+
const resultRows = rows.map((row) => buildRow(row, columns));
|
|
906
|
+
return {
|
|
907
|
+
columns,
|
|
908
|
+
columnTypes: columns.map(() => ""),
|
|
909
|
+
rows: resultRows,
|
|
910
|
+
rowsAffected,
|
|
911
|
+
lastInsertRowid: void 0,
|
|
912
|
+
toJSON() {
|
|
913
|
+
return {
|
|
914
|
+
columns,
|
|
915
|
+
columnTypes: columns.map(() => ""),
|
|
916
|
+
rows,
|
|
917
|
+
rowsAffected,
|
|
918
|
+
lastInsertRowid: void 0
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
};
|
|
922
|
+
}
|
|
923
|
+
async function loadPrismaClient() {
|
|
924
|
+
if (!prismaClientPromise) {
|
|
925
|
+
prismaClientPromise = (async () => {
|
|
926
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
927
|
+
if (explicitPath) {
|
|
928
|
+
const module2 = await import(pathToFileURL(explicitPath).href);
|
|
929
|
+
const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
|
|
930
|
+
if (!PrismaClient2) {
|
|
931
|
+
throw new Error(`No PrismaClient export found at ${explicitPath}`);
|
|
932
|
+
}
|
|
933
|
+
return new PrismaClient2();
|
|
934
|
+
}
|
|
935
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path7.join(os5.homedir(), "exe-db");
|
|
936
|
+
const requireFromExeDb = createRequire(path7.join(exeDbRoot, "package.json"));
|
|
937
|
+
const prismaEntry = requireFromExeDb.resolve("@prisma/client");
|
|
938
|
+
const module = await import(pathToFileURL(prismaEntry).href);
|
|
939
|
+
const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
|
|
940
|
+
if (!PrismaClient) {
|
|
941
|
+
throw new Error(`No PrismaClient export found in ${prismaEntry}`);
|
|
942
|
+
}
|
|
943
|
+
return new PrismaClient();
|
|
944
|
+
})();
|
|
945
|
+
}
|
|
946
|
+
return prismaClientPromise;
|
|
947
|
+
}
|
|
948
|
+
async function ensureCompatibilityViews(prisma) {
|
|
949
|
+
if (!compatibilityBootstrapPromise) {
|
|
950
|
+
compatibilityBootstrapPromise = (async () => {
|
|
951
|
+
for (const mapping of VIEW_MAPPINGS) {
|
|
952
|
+
const relation = mapping.source.replace(/"/g, "");
|
|
953
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
954
|
+
"SELECT to_regclass($1) AS regclass",
|
|
955
|
+
relation
|
|
956
|
+
);
|
|
957
|
+
if (!rows[0]?.regclass) {
|
|
958
|
+
continue;
|
|
959
|
+
}
|
|
960
|
+
await prisma.$executeRawUnsafe(
|
|
961
|
+
`CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
|
|
962
|
+
);
|
|
963
|
+
}
|
|
964
|
+
})();
|
|
965
|
+
}
|
|
966
|
+
return compatibilityBootstrapPromise;
|
|
967
|
+
}
|
|
968
|
+
async function executeOnPrisma(executor, stmt) {
|
|
969
|
+
const translated = translateStatementForPostgres(stmt);
|
|
970
|
+
if (isReadQuery(translated.sql)) {
|
|
971
|
+
const rows = await executor.$queryRawUnsafe(
|
|
972
|
+
translated.sql,
|
|
973
|
+
...translated.args
|
|
974
|
+
);
|
|
975
|
+
return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
|
|
976
|
+
}
|
|
977
|
+
const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
|
|
978
|
+
return buildResultSet([], rowsAffected);
|
|
979
|
+
}
|
|
980
|
+
function splitSqlStatements(sql) {
|
|
981
|
+
const parts = [];
|
|
982
|
+
let current = "";
|
|
983
|
+
let inSingle = false;
|
|
984
|
+
let inDouble = false;
|
|
985
|
+
let inLineComment = false;
|
|
986
|
+
let inBlockComment = false;
|
|
987
|
+
for (let i = 0; i < sql.length; i++) {
|
|
988
|
+
const ch = sql[i];
|
|
989
|
+
const next = sql[i + 1];
|
|
990
|
+
if (inLineComment) {
|
|
991
|
+
current += ch;
|
|
992
|
+
if (ch === "\n") inLineComment = false;
|
|
993
|
+
continue;
|
|
994
|
+
}
|
|
995
|
+
if (inBlockComment) {
|
|
996
|
+
current += ch;
|
|
997
|
+
if (ch === "*" && next === "/") {
|
|
998
|
+
current += next;
|
|
999
|
+
inBlockComment = false;
|
|
1000
|
+
i += 1;
|
|
1001
|
+
}
|
|
1002
|
+
continue;
|
|
1003
|
+
}
|
|
1004
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
1005
|
+
current += ch + next;
|
|
1006
|
+
inLineComment = true;
|
|
1007
|
+
i += 1;
|
|
1008
|
+
continue;
|
|
1009
|
+
}
|
|
1010
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
1011
|
+
current += ch + next;
|
|
1012
|
+
inBlockComment = true;
|
|
1013
|
+
i += 1;
|
|
1014
|
+
continue;
|
|
1015
|
+
}
|
|
1016
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
1017
|
+
inSingle = !inSingle;
|
|
1018
|
+
current += ch;
|
|
1019
|
+
continue;
|
|
1020
|
+
}
|
|
1021
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
1022
|
+
inDouble = !inDouble;
|
|
1023
|
+
current += ch;
|
|
1024
|
+
continue;
|
|
1025
|
+
}
|
|
1026
|
+
if (!inSingle && !inDouble && ch === ";") {
|
|
1027
|
+
if (current.trim()) {
|
|
1028
|
+
parts.push(current.trim());
|
|
1029
|
+
}
|
|
1030
|
+
current = "";
|
|
1031
|
+
continue;
|
|
1032
|
+
}
|
|
1033
|
+
current += ch;
|
|
1034
|
+
}
|
|
1035
|
+
if (current.trim()) {
|
|
1036
|
+
parts.push(current.trim());
|
|
1037
|
+
}
|
|
1038
|
+
return parts;
|
|
1039
|
+
}
|
|
1040
|
+
async function createPrismaDbAdapter(fallbackClient) {
|
|
1041
|
+
const prisma = await loadPrismaClient();
|
|
1042
|
+
await ensureCompatibilityViews(prisma);
|
|
1043
|
+
let closed = false;
|
|
1044
|
+
let adapter;
|
|
1045
|
+
const fallbackExecute = async (stmt, error) => {
|
|
1046
|
+
if (!fallbackClient) {
|
|
1047
|
+
if (error) throw error;
|
|
1048
|
+
throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
|
|
1049
|
+
}
|
|
1050
|
+
if (error) {
|
|
1051
|
+
process.stderr.write(
|
|
1052
|
+
`[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
1053
|
+
`
|
|
1054
|
+
);
|
|
1055
|
+
}
|
|
1056
|
+
return fallbackClient.execute(stmt);
|
|
1057
|
+
};
|
|
1058
|
+
adapter = {
|
|
1059
|
+
async execute(stmt) {
|
|
1060
|
+
if (shouldBypassPostgres(stmt)) {
|
|
1061
|
+
return fallbackExecute(stmt);
|
|
1062
|
+
}
|
|
1063
|
+
try {
|
|
1064
|
+
return await executeOnPrisma(prisma, stmt);
|
|
1065
|
+
} catch (error) {
|
|
1066
|
+
if (shouldFallbackOnError(error)) {
|
|
1067
|
+
return fallbackExecute(stmt, error);
|
|
1068
|
+
}
|
|
1069
|
+
throw error;
|
|
1070
|
+
}
|
|
1071
|
+
},
|
|
1072
|
+
async batch(stmts, mode) {
|
|
1073
|
+
if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
|
|
1074
|
+
if (!fallbackClient) {
|
|
1075
|
+
throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
|
|
1076
|
+
}
|
|
1077
|
+
return fallbackClient.batch(stmts, mode);
|
|
1078
|
+
}
|
|
1079
|
+
try {
|
|
1080
|
+
if (prisma.$transaction) {
|
|
1081
|
+
return await prisma.$transaction(async (tx) => {
|
|
1082
|
+
const results2 = [];
|
|
1083
|
+
for (const stmt of stmts) {
|
|
1084
|
+
results2.push(await executeOnPrisma(tx, stmt));
|
|
1085
|
+
}
|
|
1086
|
+
return results2;
|
|
1087
|
+
});
|
|
1088
|
+
}
|
|
1089
|
+
const results = [];
|
|
1090
|
+
for (const stmt of stmts) {
|
|
1091
|
+
results.push(await executeOnPrisma(prisma, stmt));
|
|
1092
|
+
}
|
|
1093
|
+
return results;
|
|
1094
|
+
} catch (error) {
|
|
1095
|
+
if (fallbackClient && shouldFallbackOnError(error)) {
|
|
1096
|
+
process.stderr.write(
|
|
1097
|
+
`[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
1098
|
+
`
|
|
1099
|
+
);
|
|
1100
|
+
return fallbackClient.batch(stmts, mode);
|
|
1101
|
+
}
|
|
1102
|
+
throw error;
|
|
1103
|
+
}
|
|
1104
|
+
},
|
|
1105
|
+
async migrate(stmts) {
|
|
1106
|
+
if (fallbackClient) {
|
|
1107
|
+
return fallbackClient.migrate(stmts);
|
|
1108
|
+
}
|
|
1109
|
+
return adapter.batch(stmts, "deferred");
|
|
1110
|
+
},
|
|
1111
|
+
async transaction(mode) {
|
|
1112
|
+
if (!fallbackClient) {
|
|
1113
|
+
throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
|
|
1114
|
+
}
|
|
1115
|
+
return fallbackClient.transaction(mode);
|
|
1116
|
+
},
|
|
1117
|
+
async executeMultiple(sql) {
|
|
1118
|
+
if (fallbackClient && shouldBypassPostgres(sql)) {
|
|
1119
|
+
return fallbackClient.executeMultiple(sql);
|
|
1120
|
+
}
|
|
1121
|
+
for (const statement of splitSqlStatements(sql)) {
|
|
1122
|
+
await adapter.execute(statement);
|
|
1123
|
+
}
|
|
1124
|
+
},
|
|
1125
|
+
async sync() {
|
|
1126
|
+
if (fallbackClient) {
|
|
1127
|
+
return fallbackClient.sync();
|
|
1128
|
+
}
|
|
1129
|
+
return { frame_no: 0, frames_synced: 0 };
|
|
1130
|
+
},
|
|
1131
|
+
close() {
|
|
1132
|
+
closed = true;
|
|
1133
|
+
prismaClientPromise = null;
|
|
1134
|
+
compatibilityBootstrapPromise = null;
|
|
1135
|
+
void prisma.$disconnect?.();
|
|
1136
|
+
},
|
|
1137
|
+
get closed() {
|
|
1138
|
+
return closed;
|
|
1139
|
+
},
|
|
1140
|
+
get protocol() {
|
|
1141
|
+
return "prisma-postgres";
|
|
1142
|
+
}
|
|
1143
|
+
};
|
|
1144
|
+
return adapter;
|
|
1145
|
+
}
|
|
1146
|
+
var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
|
|
1147
|
+
var init_database_adapter = __esm({
|
|
1148
|
+
"src/lib/database-adapter.ts"() {
|
|
1149
|
+
"use strict";
|
|
1150
|
+
VIEW_MAPPINGS = [
|
|
1151
|
+
{ view: "memories", source: "memory.memory_records" },
|
|
1152
|
+
{ view: "tasks", source: "memory.tasks" },
|
|
1153
|
+
{ view: "behaviors", source: "memory.behaviors" },
|
|
1154
|
+
{ view: "entities", source: "memory.entities" },
|
|
1155
|
+
{ view: "relationships", source: "memory.relationships" },
|
|
1156
|
+
{ view: "entity_memories", source: "memory.entity_memories" },
|
|
1157
|
+
{ view: "entity_aliases", source: "memory.entity_aliases" },
|
|
1158
|
+
{ view: "notifications", source: "memory.notifications" },
|
|
1159
|
+
{ view: "messages", source: "memory.messages" },
|
|
1160
|
+
{ view: "users", source: "wiki.users" },
|
|
1161
|
+
{ view: "workspaces", source: "wiki.workspaces" },
|
|
1162
|
+
{ view: "workspace_users", source: "wiki.workspace_users" },
|
|
1163
|
+
{ view: "documents", source: "wiki.workspace_documents" },
|
|
1164
|
+
{ view: "chats", source: "wiki.workspace_chats" }
|
|
1165
|
+
];
|
|
1166
|
+
UPSERT_KEYS = {
|
|
1167
|
+
memories: ["id"],
|
|
1168
|
+
tasks: ["id"],
|
|
1169
|
+
behaviors: ["id"],
|
|
1170
|
+
entities: ["id"],
|
|
1171
|
+
relationships: ["id"],
|
|
1172
|
+
entity_aliases: ["alias"],
|
|
1173
|
+
notifications: ["id"],
|
|
1174
|
+
messages: ["id"],
|
|
1175
|
+
users: ["id"],
|
|
1176
|
+
workspaces: ["id"],
|
|
1177
|
+
workspace_users: ["id"],
|
|
1178
|
+
documents: ["id"],
|
|
1179
|
+
chats: ["id"]
|
|
1180
|
+
};
|
|
1181
|
+
BOOLEAN_COLUMNS_BY_TABLE = {
|
|
1182
|
+
memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
|
|
1183
|
+
behaviors: /* @__PURE__ */ new Set(["active"]),
|
|
1184
|
+
notifications: /* @__PURE__ */ new Set(["read"]),
|
|
1185
|
+
users: /* @__PURE__ */ new Set(["has_personal_memory"])
|
|
1186
|
+
};
|
|
1187
|
+
BOOLEAN_COLUMN_NAMES = new Set(
|
|
1188
|
+
Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
|
|
1189
|
+
);
|
|
1190
|
+
IMMEDIATE_FALLBACK_PATTERNS = [
|
|
1191
|
+
/\bPRAGMA\b/i,
|
|
1192
|
+
/\bsqlite_master\b/i,
|
|
1193
|
+
/(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
|
|
1194
|
+
/\bMATCH\b/i,
|
|
1195
|
+
/\bvector_distance_cos\s*\(/i,
|
|
1196
|
+
/\bjson_extract\s*\(/i,
|
|
1197
|
+
/\bjulianday\s*\(/i,
|
|
1198
|
+
/\bstrftime\s*\(/i,
|
|
1199
|
+
/\blast_insert_rowid\s*\(/i
|
|
1200
|
+
];
|
|
1201
|
+
prismaClientPromise = null;
|
|
1202
|
+
compatibilityBootstrapPromise = null;
|
|
1203
|
+
}
|
|
1204
|
+
});
|
|
1205
|
+
|
|
1206
|
+
// src/lib/daemon-auth.ts
|
|
1207
|
+
import crypto from "crypto";
|
|
1208
|
+
import path8 from "path";
|
|
1209
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
1210
|
+
function normalizeToken(token) {
|
|
1211
|
+
if (!token) return null;
|
|
1212
|
+
const trimmed = token.trim();
|
|
1213
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
1214
|
+
}
|
|
1215
|
+
function readDaemonToken() {
|
|
1216
|
+
try {
|
|
1217
|
+
if (!existsSync6(DAEMON_TOKEN_PATH)) return null;
|
|
1218
|
+
return normalizeToken(readFileSync6(DAEMON_TOKEN_PATH, "utf8"));
|
|
1219
|
+
} catch {
|
|
1220
|
+
return null;
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
function ensureDaemonToken(seed) {
|
|
1224
|
+
const existing = readDaemonToken();
|
|
1225
|
+
if (existing) return existing;
|
|
1226
|
+
const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
|
|
1227
|
+
ensurePrivateDirSync(EXE_AI_DIR);
|
|
1228
|
+
writeFileSync5(DAEMON_TOKEN_PATH, `${token}
|
|
1229
|
+
`, "utf8");
|
|
1230
|
+
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
1231
|
+
return token;
|
|
1232
|
+
}
|
|
1233
|
+
var DAEMON_TOKEN_PATH;
|
|
1234
|
+
var init_daemon_auth = __esm({
|
|
1235
|
+
"src/lib/daemon-auth.ts"() {
|
|
1236
|
+
"use strict";
|
|
1237
|
+
init_config();
|
|
1238
|
+
init_secure_files();
|
|
1239
|
+
DAEMON_TOKEN_PATH = path8.join(EXE_AI_DIR, "exed.token");
|
|
1240
|
+
}
|
|
1241
|
+
});
|
|
1242
|
+
|
|
580
1243
|
// src/lib/exe-daemon-client.ts
|
|
581
1244
|
import net from "net";
|
|
582
|
-
import
|
|
1245
|
+
import os6 from "os";
|
|
583
1246
|
import { spawn } from "child_process";
|
|
584
1247
|
import { randomUUID } from "crypto";
|
|
585
|
-
import { existsSync as
|
|
586
|
-
import
|
|
1248
|
+
import { existsSync as existsSync7, unlinkSync as unlinkSync3, readFileSync as readFileSync7, openSync, closeSync, statSync } from "fs";
|
|
1249
|
+
import path9 from "path";
|
|
587
1250
|
import { fileURLToPath } from "url";
|
|
588
1251
|
function handleData(chunk) {
|
|
589
1252
|
_buffer += chunk.toString();
|
|
@@ -611,9 +1274,9 @@ function handleData(chunk) {
|
|
|
611
1274
|
}
|
|
612
1275
|
}
|
|
613
1276
|
function cleanupStaleFiles() {
|
|
614
|
-
if (
|
|
1277
|
+
if (existsSync7(PID_PATH)) {
|
|
615
1278
|
try {
|
|
616
|
-
const pid = parseInt(
|
|
1279
|
+
const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
|
|
617
1280
|
if (pid > 0) {
|
|
618
1281
|
try {
|
|
619
1282
|
process.kill(pid, 0);
|
|
@@ -634,17 +1297,17 @@ function cleanupStaleFiles() {
|
|
|
634
1297
|
}
|
|
635
1298
|
}
|
|
636
1299
|
function findPackageRoot() {
|
|
637
|
-
let dir =
|
|
638
|
-
const { root } =
|
|
1300
|
+
let dir = path9.dirname(fileURLToPath(import.meta.url));
|
|
1301
|
+
const { root } = path9.parse(dir);
|
|
639
1302
|
while (dir !== root) {
|
|
640
|
-
if (
|
|
641
|
-
dir =
|
|
1303
|
+
if (existsSync7(path9.join(dir, "package.json"))) return dir;
|
|
1304
|
+
dir = path9.dirname(dir);
|
|
642
1305
|
}
|
|
643
1306
|
return null;
|
|
644
1307
|
}
|
|
645
1308
|
function spawnDaemon() {
|
|
646
|
-
const freeGB =
|
|
647
|
-
const totalGB =
|
|
1309
|
+
const freeGB = os6.freemem() / (1024 * 1024 * 1024);
|
|
1310
|
+
const totalGB = os6.totalmem() / (1024 * 1024 * 1024);
|
|
648
1311
|
if (totalGB <= 8) {
|
|
649
1312
|
process.stderr.write(
|
|
650
1313
|
`[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
|
|
@@ -664,16 +1327,17 @@ function spawnDaemon() {
|
|
|
664
1327
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
665
1328
|
return;
|
|
666
1329
|
}
|
|
667
|
-
const daemonPath =
|
|
668
|
-
if (!
|
|
1330
|
+
const daemonPath = path9.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
1331
|
+
if (!existsSync7(daemonPath)) {
|
|
669
1332
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
670
1333
|
`);
|
|
671
1334
|
return;
|
|
672
1335
|
}
|
|
673
1336
|
const resolvedPath = daemonPath;
|
|
1337
|
+
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
674
1338
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
675
1339
|
`);
|
|
676
|
-
const logPath =
|
|
1340
|
+
const logPath = path9.join(path9.dirname(SOCKET_PATH), "exed.log");
|
|
677
1341
|
let stderrFd = "ignore";
|
|
678
1342
|
try {
|
|
679
1343
|
stderrFd = openSync(logPath, "a");
|
|
@@ -691,7 +1355,8 @@ function spawnDaemon() {
|
|
|
691
1355
|
TMUX_PANE: void 0,
|
|
692
1356
|
// Prevents resolveExeSession() from scoping to one session
|
|
693
1357
|
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
694
|
-
EXE_DAEMON_PID: PID_PATH
|
|
1358
|
+
EXE_DAEMON_PID: PID_PATH,
|
|
1359
|
+
[DAEMON_TOKEN_ENV]: daemonToken
|
|
695
1360
|
}
|
|
696
1361
|
});
|
|
697
1362
|
child.unref();
|
|
@@ -798,13 +1463,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
798
1463
|
return;
|
|
799
1464
|
}
|
|
800
1465
|
const id = randomUUID();
|
|
1466
|
+
const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
|
|
801
1467
|
const timer = setTimeout(() => {
|
|
802
1468
|
_pending.delete(id);
|
|
803
1469
|
resolve({ error: "Request timeout" });
|
|
804
1470
|
}, timeoutMs);
|
|
805
1471
|
_pending.set(id, { resolve, timer });
|
|
806
1472
|
try {
|
|
807
|
-
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
1473
|
+
_socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
|
|
808
1474
|
} catch {
|
|
809
1475
|
clearTimeout(timer);
|
|
810
1476
|
_pending.delete(id);
|
|
@@ -815,17 +1481,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
815
1481
|
function isClientConnected() {
|
|
816
1482
|
return _connected;
|
|
817
1483
|
}
|
|
818
|
-
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
|
|
1484
|
+
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;
|
|
819
1485
|
var init_exe_daemon_client = __esm({
|
|
820
1486
|
"src/lib/exe-daemon-client.ts"() {
|
|
821
1487
|
"use strict";
|
|
822
1488
|
init_config();
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
1489
|
+
init_daemon_auth();
|
|
1490
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path9.join(EXE_AI_DIR, "exed.sock");
|
|
1491
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path9.join(EXE_AI_DIR, "exed.pid");
|
|
1492
|
+
SPAWN_LOCK_PATH = path9.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
826
1493
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
827
1494
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
828
1495
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
1496
|
+
DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
|
|
829
1497
|
_socket = null;
|
|
830
1498
|
_connected = false;
|
|
831
1499
|
_buffer = "";
|
|
@@ -904,7 +1572,7 @@ __export(db_daemon_client_exports, {
|
|
|
904
1572
|
createDaemonDbClient: () => createDaemonDbClient,
|
|
905
1573
|
initDaemonDbClient: () => initDaemonDbClient
|
|
906
1574
|
});
|
|
907
|
-
function
|
|
1575
|
+
function normalizeStatement2(stmt) {
|
|
908
1576
|
if (typeof stmt === "string") {
|
|
909
1577
|
return { sql: stmt, args: [] };
|
|
910
1578
|
}
|
|
@@ -928,7 +1596,7 @@ function createDaemonDbClient(fallbackClient) {
|
|
|
928
1596
|
if (!_useDaemon || !isClientConnected()) {
|
|
929
1597
|
return fallbackClient.execute(stmt);
|
|
930
1598
|
}
|
|
931
|
-
const { sql, args } =
|
|
1599
|
+
const { sql, args } = normalizeStatement2(stmt);
|
|
932
1600
|
const response = await sendDaemonRequest({
|
|
933
1601
|
type: "db-execute",
|
|
934
1602
|
sql,
|
|
@@ -953,7 +1621,7 @@ function createDaemonDbClient(fallbackClient) {
|
|
|
953
1621
|
if (!_useDaemon || !isClientConnected()) {
|
|
954
1622
|
return fallbackClient.batch(stmts, mode);
|
|
955
1623
|
}
|
|
956
|
-
const statements = stmts.map(
|
|
1624
|
+
const statements = stmts.map(normalizeStatement2);
|
|
957
1625
|
const response = await sendDaemonRequest({
|
|
958
1626
|
type: "db-batch",
|
|
959
1627
|
statements,
|
|
@@ -1048,6 +1716,18 @@ __export(database_exports, {
|
|
|
1048
1716
|
});
|
|
1049
1717
|
import { createClient } from "@libsql/client";
|
|
1050
1718
|
async function initDatabase(config) {
|
|
1719
|
+
if (_walCheckpointTimer) {
|
|
1720
|
+
clearInterval(_walCheckpointTimer);
|
|
1721
|
+
_walCheckpointTimer = null;
|
|
1722
|
+
}
|
|
1723
|
+
if (_daemonClient) {
|
|
1724
|
+
_daemonClient.close();
|
|
1725
|
+
_daemonClient = null;
|
|
1726
|
+
}
|
|
1727
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
1728
|
+
_adapterClient.close();
|
|
1729
|
+
}
|
|
1730
|
+
_adapterClient = null;
|
|
1051
1731
|
if (_client) {
|
|
1052
1732
|
_client.close();
|
|
1053
1733
|
_client = null;
|
|
@@ -1061,6 +1741,7 @@ async function initDatabase(config) {
|
|
|
1061
1741
|
}
|
|
1062
1742
|
_client = createClient(opts);
|
|
1063
1743
|
_resilientClient = wrapWithRetry(_client);
|
|
1744
|
+
_adapterClient = _resilientClient;
|
|
1064
1745
|
_client.execute("PRAGMA busy_timeout = 30000").catch(() => {
|
|
1065
1746
|
});
|
|
1066
1747
|
_client.execute("PRAGMA journal_mode = WAL").catch(() => {
|
|
@@ -1071,14 +1752,20 @@ async function initDatabase(config) {
|
|
|
1071
1752
|
});
|
|
1072
1753
|
}, 3e4);
|
|
1073
1754
|
_walCheckpointTimer.unref();
|
|
1755
|
+
if (process.env.DATABASE_URL) {
|
|
1756
|
+
_adapterClient = await createPrismaDbAdapter(_resilientClient);
|
|
1757
|
+
}
|
|
1074
1758
|
}
|
|
1075
1759
|
function isInitialized() {
|
|
1076
|
-
return _client !== null;
|
|
1760
|
+
return _adapterClient !== null || _client !== null;
|
|
1077
1761
|
}
|
|
1078
1762
|
function getClient() {
|
|
1079
|
-
if (!
|
|
1763
|
+
if (!_adapterClient) {
|
|
1080
1764
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
1081
1765
|
}
|
|
1766
|
+
if (process.env.DATABASE_URL) {
|
|
1767
|
+
return _adapterClient;
|
|
1768
|
+
}
|
|
1082
1769
|
if (process.env.EXE_IS_DAEMON === "1") {
|
|
1083
1770
|
return _resilientClient;
|
|
1084
1771
|
}
|
|
@@ -1088,6 +1775,7 @@ function getClient() {
|
|
|
1088
1775
|
return _resilientClient;
|
|
1089
1776
|
}
|
|
1090
1777
|
async function initDaemonClient() {
|
|
1778
|
+
if (process.env.DATABASE_URL) return;
|
|
1091
1779
|
if (process.env.EXE_IS_DAEMON === "1") return;
|
|
1092
1780
|
if (!_resilientClient) return;
|
|
1093
1781
|
try {
|
|
@@ -1384,6 +2072,7 @@ async function ensureSchema() {
|
|
|
1384
2072
|
project TEXT NOT NULL,
|
|
1385
2073
|
summary TEXT NOT NULL,
|
|
1386
2074
|
task_file TEXT,
|
|
2075
|
+
session_scope TEXT,
|
|
1387
2076
|
read INTEGER NOT NULL DEFAULT 0,
|
|
1388
2077
|
created_at TEXT NOT NULL
|
|
1389
2078
|
);
|
|
@@ -1392,7 +2081,7 @@ async function ensureSchema() {
|
|
|
1392
2081
|
ON notifications(read);
|
|
1393
2082
|
|
|
1394
2083
|
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
1395
|
-
ON notifications(agent_id);
|
|
2084
|
+
ON notifications(agent_id, session_scope);
|
|
1396
2085
|
|
|
1397
2086
|
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
1398
2087
|
ON notifications(task_file);
|
|
@@ -1430,6 +2119,7 @@ async function ensureSchema() {
|
|
|
1430
2119
|
target_agent TEXT NOT NULL,
|
|
1431
2120
|
target_project TEXT,
|
|
1432
2121
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
2122
|
+
session_scope TEXT,
|
|
1433
2123
|
content TEXT NOT NULL,
|
|
1434
2124
|
priority TEXT DEFAULT 'normal',
|
|
1435
2125
|
status TEXT DEFAULT 'pending',
|
|
@@ -1443,10 +2133,31 @@ async function ensureSchema() {
|
|
|
1443
2133
|
);
|
|
1444
2134
|
|
|
1445
2135
|
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
1446
|
-
ON messages(target_agent, status);
|
|
2136
|
+
ON messages(target_agent, session_scope, status);
|
|
1447
2137
|
|
|
1448
2138
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
1449
|
-
ON messages(target_agent, from_agent, server_seq);
|
|
2139
|
+
ON messages(target_agent, session_scope, from_agent, server_seq);
|
|
2140
|
+
`);
|
|
2141
|
+
try {
|
|
2142
|
+
await client.execute({
|
|
2143
|
+
sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
|
|
2144
|
+
args: []
|
|
2145
|
+
});
|
|
2146
|
+
} catch {
|
|
2147
|
+
}
|
|
2148
|
+
try {
|
|
2149
|
+
await client.execute({
|
|
2150
|
+
sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
|
|
2151
|
+
args: []
|
|
2152
|
+
});
|
|
2153
|
+
} catch {
|
|
2154
|
+
}
|
|
2155
|
+
await client.executeMultiple(`
|
|
2156
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
|
|
2157
|
+
ON notifications(agent_id, session_scope, read, created_at);
|
|
2158
|
+
|
|
2159
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
|
|
2160
|
+
ON messages(target_agent, session_scope, status, created_at);
|
|
1450
2161
|
`);
|
|
1451
2162
|
try {
|
|
1452
2163
|
await client.execute({
|
|
@@ -2030,52 +2741,72 @@ async function ensureSchema() {
|
|
|
2030
2741
|
} catch {
|
|
2031
2742
|
}
|
|
2032
2743
|
}
|
|
2744
|
+
try {
|
|
2745
|
+
await client.execute({
|
|
2746
|
+
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
2747
|
+
args: []
|
|
2748
|
+
});
|
|
2749
|
+
} catch {
|
|
2750
|
+
}
|
|
2033
2751
|
}
|
|
2034
2752
|
async function disposeDatabase() {
|
|
2753
|
+
if (_walCheckpointTimer) {
|
|
2754
|
+
clearInterval(_walCheckpointTimer);
|
|
2755
|
+
_walCheckpointTimer = null;
|
|
2756
|
+
}
|
|
2035
2757
|
if (_daemonClient) {
|
|
2036
2758
|
_daemonClient.close();
|
|
2037
2759
|
_daemonClient = null;
|
|
2038
2760
|
}
|
|
2761
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
2762
|
+
_adapterClient.close();
|
|
2763
|
+
}
|
|
2764
|
+
_adapterClient = null;
|
|
2039
2765
|
if (_client) {
|
|
2040
2766
|
_client.close();
|
|
2041
2767
|
_client = null;
|
|
2042
2768
|
_resilientClient = null;
|
|
2043
2769
|
}
|
|
2044
2770
|
}
|
|
2045
|
-
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
|
|
2771
|
+
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
|
|
2046
2772
|
var init_database = __esm({
|
|
2047
2773
|
"src/lib/database.ts"() {
|
|
2048
2774
|
"use strict";
|
|
2049
2775
|
init_db_retry();
|
|
2050
2776
|
init_employees();
|
|
2777
|
+
init_database_adapter();
|
|
2051
2778
|
_client = null;
|
|
2052
2779
|
_resilientClient = null;
|
|
2053
2780
|
_walCheckpointTimer = null;
|
|
2054
2781
|
_daemonClient = null;
|
|
2782
|
+
_adapterClient = null;
|
|
2055
2783
|
initTurso = initDatabase;
|
|
2056
2784
|
disposeTurso = disposeDatabase;
|
|
2057
2785
|
}
|
|
2058
2786
|
});
|
|
2059
2787
|
|
|
2060
2788
|
// src/lib/license.ts
|
|
2061
|
-
import { readFileSync as
|
|
2789
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync8, mkdirSync as mkdirSync4 } from "fs";
|
|
2062
2790
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
2063
|
-
import
|
|
2791
|
+
import { createRequire as createRequire2 } from "module";
|
|
2792
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
2793
|
+
import os7 from "os";
|
|
2794
|
+
import path10 from "path";
|
|
2064
2795
|
import { jwtVerify, importSPKI } from "jose";
|
|
2065
2796
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH;
|
|
2066
2797
|
var init_license = __esm({
|
|
2067
2798
|
"src/lib/license.ts"() {
|
|
2068
2799
|
"use strict";
|
|
2069
2800
|
init_config();
|
|
2070
|
-
LICENSE_PATH =
|
|
2071
|
-
CACHE_PATH =
|
|
2072
|
-
DEVICE_ID_PATH =
|
|
2801
|
+
LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
|
|
2802
|
+
CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
|
|
2803
|
+
DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
|
|
2073
2804
|
}
|
|
2074
2805
|
});
|
|
2075
2806
|
|
|
2076
2807
|
// src/lib/plan-limits.ts
|
|
2077
|
-
import { readFileSync as
|
|
2078
|
-
import
|
|
2808
|
+
import { readFileSync as readFileSync9, existsSync as existsSync9 } from "fs";
|
|
2809
|
+
import path11 from "path";
|
|
2079
2810
|
var CACHE_PATH2;
|
|
2080
2811
|
var init_plan_limits = __esm({
|
|
2081
2812
|
"src/lib/plan-limits.ts"() {
|
|
@@ -2084,14 +2815,14 @@ var init_plan_limits = __esm({
|
|
|
2084
2815
|
init_employees();
|
|
2085
2816
|
init_license();
|
|
2086
2817
|
init_config();
|
|
2087
|
-
CACHE_PATH2 =
|
|
2818
|
+
CACHE_PATH2 = path11.join(EXE_AI_DIR, "license-cache.json");
|
|
2088
2819
|
}
|
|
2089
2820
|
});
|
|
2090
2821
|
|
|
2091
2822
|
// src/lib/tmux-routing.ts
|
|
2092
|
-
import { readFileSync as
|
|
2093
|
-
import
|
|
2094
|
-
import
|
|
2823
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, mkdirSync as mkdirSync5, existsSync as existsSync10, appendFileSync, readdirSync as readdirSync2 } from "fs";
|
|
2824
|
+
import path12 from "path";
|
|
2825
|
+
import os8 from "os";
|
|
2095
2826
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2096
2827
|
function getMySession() {
|
|
2097
2828
|
return getTransport().getMySession();
|
|
@@ -2104,7 +2835,7 @@ function extractRootExe(name) {
|
|
|
2104
2835
|
}
|
|
2105
2836
|
function getParentExe(sessionKey) {
|
|
2106
2837
|
try {
|
|
2107
|
-
const data = JSON.parse(
|
|
2838
|
+
const data = JSON.parse(readFileSync10(path12.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
2108
2839
|
return data.parentExe || null;
|
|
2109
2840
|
} catch {
|
|
2110
2841
|
return null;
|
|
@@ -2147,10 +2878,10 @@ var init_tmux_routing = __esm({
|
|
|
2147
2878
|
init_intercom_queue();
|
|
2148
2879
|
init_plan_limits();
|
|
2149
2880
|
init_employees();
|
|
2150
|
-
SPAWN_LOCK_DIR =
|
|
2151
|
-
SESSION_CACHE =
|
|
2152
|
-
INTERCOM_LOG2 =
|
|
2153
|
-
DEBOUNCE_FILE =
|
|
2881
|
+
SPAWN_LOCK_DIR = path12.join(os8.homedir(), ".exe-os", "spawn-locks");
|
|
2882
|
+
SESSION_CACHE = path12.join(os8.homedir(), ".exe-os", "session-cache");
|
|
2883
|
+
INTERCOM_LOG2 = path12.join(os8.homedir(), ".exe-os", "intercom.log");
|
|
2884
|
+
DEBOUNCE_FILE = path12.join(SESSION_CACHE, "intercom-debounce.json");
|
|
2154
2885
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
2155
2886
|
}
|
|
2156
2887
|
});
|
|
@@ -2190,14 +2921,14 @@ var init_memory = __esm({
|
|
|
2190
2921
|
|
|
2191
2922
|
// src/lib/keychain.ts
|
|
2192
2923
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
2193
|
-
import { existsSync as
|
|
2194
|
-
import
|
|
2195
|
-
import
|
|
2924
|
+
import { existsSync as existsSync11 } from "fs";
|
|
2925
|
+
import path13 from "path";
|
|
2926
|
+
import os9 from "os";
|
|
2196
2927
|
function getKeyDir() {
|
|
2197
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
2928
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path13.join(os9.homedir(), ".exe-os");
|
|
2198
2929
|
}
|
|
2199
2930
|
function getKeyPath() {
|
|
2200
|
-
return
|
|
2931
|
+
return path13.join(getKeyDir(), "master.key");
|
|
2201
2932
|
}
|
|
2202
2933
|
async function tryKeytar() {
|
|
2203
2934
|
try {
|
|
@@ -2218,9 +2949,9 @@ async function getMasterKey() {
|
|
|
2218
2949
|
}
|
|
2219
2950
|
}
|
|
2220
2951
|
const keyPath = getKeyPath();
|
|
2221
|
-
if (!
|
|
2952
|
+
if (!existsSync11(keyPath)) {
|
|
2222
2953
|
process.stderr.write(
|
|
2223
|
-
`[keychain] Key not found at ${keyPath} (HOME=${
|
|
2954
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os9.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
2224
2955
|
`
|
|
2225
2956
|
);
|
|
2226
2957
|
return null;
|
|
@@ -2305,6 +3036,7 @@ var shard_manager_exports = {};
|
|
|
2305
3036
|
__export(shard_manager_exports, {
|
|
2306
3037
|
disposeShards: () => disposeShards,
|
|
2307
3038
|
ensureShardSchema: () => ensureShardSchema,
|
|
3039
|
+
getOpenShardCount: () => getOpenShardCount,
|
|
2308
3040
|
getReadyShardClient: () => getReadyShardClient,
|
|
2309
3041
|
getShardClient: () => getShardClient,
|
|
2310
3042
|
getShardsDir: () => getShardsDir,
|
|
@@ -2313,15 +3045,18 @@ __export(shard_manager_exports, {
|
|
|
2313
3045
|
listShards: () => listShards,
|
|
2314
3046
|
shardExists: () => shardExists
|
|
2315
3047
|
});
|
|
2316
|
-
import
|
|
2317
|
-
import { existsSync as
|
|
3048
|
+
import path14 from "path";
|
|
3049
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync6, readdirSync as readdirSync3 } from "fs";
|
|
2318
3050
|
import { createClient as createClient2 } from "@libsql/client";
|
|
2319
3051
|
function initShardManager(encryptionKey) {
|
|
2320
3052
|
_encryptionKey = encryptionKey;
|
|
2321
|
-
if (!
|
|
3053
|
+
if (!existsSync12(SHARDS_DIR)) {
|
|
2322
3054
|
mkdirSync6(SHARDS_DIR, { recursive: true });
|
|
2323
3055
|
}
|
|
2324
3056
|
_shardingEnabled = true;
|
|
3057
|
+
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
3058
|
+
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
3059
|
+
_evictionTimer.unref();
|
|
2325
3060
|
}
|
|
2326
3061
|
function isShardingEnabled() {
|
|
2327
3062
|
return _shardingEnabled;
|
|
@@ -2338,21 +3073,28 @@ function getShardClient(projectName) {
|
|
|
2338
3073
|
throw new Error(`Invalid project name for shard: "${projectName}"`);
|
|
2339
3074
|
}
|
|
2340
3075
|
const cached = _shards.get(safeName);
|
|
2341
|
-
if (cached)
|
|
2342
|
-
|
|
3076
|
+
if (cached) {
|
|
3077
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
3078
|
+
return cached;
|
|
3079
|
+
}
|
|
3080
|
+
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
3081
|
+
evictLRU();
|
|
3082
|
+
}
|
|
3083
|
+
const dbPath = path14.join(SHARDS_DIR, `${safeName}.db`);
|
|
2343
3084
|
const client = createClient2({
|
|
2344
3085
|
url: `file:${dbPath}`,
|
|
2345
3086
|
encryptionKey: _encryptionKey
|
|
2346
3087
|
});
|
|
2347
3088
|
_shards.set(safeName, client);
|
|
3089
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
2348
3090
|
return client;
|
|
2349
3091
|
}
|
|
2350
3092
|
function shardExists(projectName) {
|
|
2351
3093
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
2352
|
-
return
|
|
3094
|
+
return existsSync12(path14.join(SHARDS_DIR, `${safeName}.db`));
|
|
2353
3095
|
}
|
|
2354
3096
|
function listShards() {
|
|
2355
|
-
if (!
|
|
3097
|
+
if (!existsSync12(SHARDS_DIR)) return [];
|
|
2356
3098
|
return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
2357
3099
|
}
|
|
2358
3100
|
async function ensureShardSchema(client) {
|
|
@@ -2404,6 +3146,8 @@ async function ensureShardSchema(client) {
|
|
|
2404
3146
|
for (const col of [
|
|
2405
3147
|
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
2406
3148
|
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
3149
|
+
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
3150
|
+
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
2407
3151
|
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
2408
3152
|
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
2409
3153
|
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
@@ -2426,7 +3170,23 @@ async function ensureShardSchema(client) {
|
|
|
2426
3170
|
// MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
|
|
2427
3171
|
"ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
|
|
2428
3172
|
"ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
|
|
2429
|
-
"ALTER TABLE memories ADD COLUMN trajectory TEXT"
|
|
3173
|
+
"ALTER TABLE memories ADD COLUMN trajectory TEXT",
|
|
3174
|
+
// Metadata enrichment columns (must match database.ts)
|
|
3175
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
3176
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
3177
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
3178
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
3179
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
3180
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
3181
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
3182
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
3183
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
3184
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
3185
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
3186
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
3187
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
3188
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
3189
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
2430
3190
|
]) {
|
|
2431
3191
|
try {
|
|
2432
3192
|
await client.execute(col);
|
|
@@ -2525,21 +3285,69 @@ async function getReadyShardClient(projectName) {
|
|
|
2525
3285
|
await ensureShardSchema(client);
|
|
2526
3286
|
return client;
|
|
2527
3287
|
}
|
|
3288
|
+
function evictLRU() {
|
|
3289
|
+
let oldest = null;
|
|
3290
|
+
let oldestTime = Infinity;
|
|
3291
|
+
for (const [name, time] of _shardLastAccess) {
|
|
3292
|
+
if (time < oldestTime) {
|
|
3293
|
+
oldestTime = time;
|
|
3294
|
+
oldest = name;
|
|
3295
|
+
}
|
|
3296
|
+
}
|
|
3297
|
+
if (oldest) {
|
|
3298
|
+
const client = _shards.get(oldest);
|
|
3299
|
+
if (client) {
|
|
3300
|
+
client.close();
|
|
3301
|
+
}
|
|
3302
|
+
_shards.delete(oldest);
|
|
3303
|
+
_shardLastAccess.delete(oldest);
|
|
3304
|
+
}
|
|
3305
|
+
}
|
|
3306
|
+
function evictIdleShards() {
|
|
3307
|
+
const now = Date.now();
|
|
3308
|
+
const toEvict = [];
|
|
3309
|
+
for (const [name, lastAccess] of _shardLastAccess) {
|
|
3310
|
+
if (now - lastAccess > SHARD_IDLE_MS) {
|
|
3311
|
+
toEvict.push(name);
|
|
3312
|
+
}
|
|
3313
|
+
}
|
|
3314
|
+
for (const name of toEvict) {
|
|
3315
|
+
const client = _shards.get(name);
|
|
3316
|
+
if (client) {
|
|
3317
|
+
client.close();
|
|
3318
|
+
}
|
|
3319
|
+
_shards.delete(name);
|
|
3320
|
+
_shardLastAccess.delete(name);
|
|
3321
|
+
}
|
|
3322
|
+
}
|
|
3323
|
+
function getOpenShardCount() {
|
|
3324
|
+
return _shards.size;
|
|
3325
|
+
}
|
|
2528
3326
|
function disposeShards() {
|
|
3327
|
+
if (_evictionTimer) {
|
|
3328
|
+
clearInterval(_evictionTimer);
|
|
3329
|
+
_evictionTimer = null;
|
|
3330
|
+
}
|
|
2529
3331
|
for (const [, client] of _shards) {
|
|
2530
3332
|
client.close();
|
|
2531
3333
|
}
|
|
2532
3334
|
_shards.clear();
|
|
3335
|
+
_shardLastAccess.clear();
|
|
2533
3336
|
_shardingEnabled = false;
|
|
2534
3337
|
_encryptionKey = null;
|
|
2535
3338
|
}
|
|
2536
|
-
var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
|
|
3339
|
+
var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
|
|
2537
3340
|
var init_shard_manager = __esm({
|
|
2538
3341
|
"src/lib/shard-manager.ts"() {
|
|
2539
3342
|
"use strict";
|
|
2540
3343
|
init_config();
|
|
2541
|
-
SHARDS_DIR =
|
|
3344
|
+
SHARDS_DIR = path14.join(EXE_AI_DIR, "shards");
|
|
3345
|
+
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
3346
|
+
MAX_OPEN_SHARDS = 10;
|
|
3347
|
+
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
2542
3348
|
_shards = /* @__PURE__ */ new Map();
|
|
3349
|
+
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
3350
|
+
_evictionTimer = null;
|
|
2543
3351
|
_encryptionKey = null;
|
|
2544
3352
|
_shardingEnabled = false;
|
|
2545
3353
|
}
|
|
@@ -3306,7 +4114,7 @@ var init_store = __esm({
|
|
|
3306
4114
|
init_config();
|
|
3307
4115
|
init_session_key();
|
|
3308
4116
|
init_employees();
|
|
3309
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync, unlinkSync as unlinkSync2, readdirSync } from "fs";
|
|
4117
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2, readdirSync } from "fs";
|
|
3310
4118
|
import { execSync as execSync3 } from "child_process";
|
|
3311
4119
|
import path3 from "path";
|
|
3312
4120
|
var CACHE_DIR = path3.join(EXE_AI_DIR, "session-cache");
|