@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
|
@@ -78,6 +78,44 @@ var init_db_retry = __esm({
|
|
|
78
78
|
}
|
|
79
79
|
});
|
|
80
80
|
|
|
81
|
+
// src/lib/secure-files.ts
|
|
82
|
+
import { chmodSync, existsSync, mkdirSync } from "fs";
|
|
83
|
+
import { chmod, mkdir } from "fs/promises";
|
|
84
|
+
async function ensurePrivateDir(dirPath) {
|
|
85
|
+
await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
86
|
+
try {
|
|
87
|
+
await chmod(dirPath, PRIVATE_DIR_MODE);
|
|
88
|
+
} catch {
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function ensurePrivateDirSync(dirPath) {
|
|
92
|
+
mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
93
|
+
try {
|
|
94
|
+
chmodSync(dirPath, PRIVATE_DIR_MODE);
|
|
95
|
+
} catch {
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async function enforcePrivateFile(filePath) {
|
|
99
|
+
try {
|
|
100
|
+
await chmod(filePath, PRIVATE_FILE_MODE);
|
|
101
|
+
} catch {
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function enforcePrivateFileSync(filePath) {
|
|
105
|
+
try {
|
|
106
|
+
if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
|
|
107
|
+
} catch {
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
|
|
111
|
+
var init_secure_files = __esm({
|
|
112
|
+
"src/lib/secure-files.ts"() {
|
|
113
|
+
"use strict";
|
|
114
|
+
PRIVATE_DIR_MODE = 448;
|
|
115
|
+
PRIVATE_FILE_MODE = 384;
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
81
119
|
// src/lib/config.ts
|
|
82
120
|
var config_exports = {};
|
|
83
121
|
__export(config_exports, {
|
|
@@ -94,8 +132,8 @@ __export(config_exports, {
|
|
|
94
132
|
migrateConfig: () => migrateConfig,
|
|
95
133
|
saveConfig: () => saveConfig
|
|
96
134
|
});
|
|
97
|
-
import { readFile, writeFile
|
|
98
|
-
import { readFileSync, existsSync, renameSync } from "fs";
|
|
135
|
+
import { readFile, writeFile } from "fs/promises";
|
|
136
|
+
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
99
137
|
import path2 from "path";
|
|
100
138
|
import os from "os";
|
|
101
139
|
function resolveDataDir() {
|
|
@@ -103,7 +141,7 @@ function resolveDataDir() {
|
|
|
103
141
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
104
142
|
const newDir = path2.join(os.homedir(), ".exe-os");
|
|
105
143
|
const legacyDir = path2.join(os.homedir(), ".exe-mem");
|
|
106
|
-
if (!
|
|
144
|
+
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
107
145
|
try {
|
|
108
146
|
renameSync(legacyDir, newDir);
|
|
109
147
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -166,9 +204,9 @@ function normalizeAutoUpdate(raw) {
|
|
|
166
204
|
}
|
|
167
205
|
async function loadConfig() {
|
|
168
206
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
169
|
-
await
|
|
207
|
+
await ensurePrivateDir(dir);
|
|
170
208
|
const configPath = path2.join(dir, "config.json");
|
|
171
|
-
if (!
|
|
209
|
+
if (!existsSync2(configPath)) {
|
|
172
210
|
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
173
211
|
}
|
|
174
212
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -181,6 +219,7 @@ async function loadConfig() {
|
|
|
181
219
|
`);
|
|
182
220
|
try {
|
|
183
221
|
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
222
|
+
await enforcePrivateFile(configPath);
|
|
184
223
|
} catch {
|
|
185
224
|
}
|
|
186
225
|
}
|
|
@@ -199,7 +238,7 @@ async function loadConfig() {
|
|
|
199
238
|
function loadConfigSync() {
|
|
200
239
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
201
240
|
const configPath = path2.join(dir, "config.json");
|
|
202
|
-
if (!
|
|
241
|
+
if (!existsSync2(configPath)) {
|
|
203
242
|
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
204
243
|
}
|
|
205
244
|
try {
|
|
@@ -217,12 +256,10 @@ function loadConfigSync() {
|
|
|
217
256
|
}
|
|
218
257
|
async function saveConfig(config) {
|
|
219
258
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
220
|
-
await
|
|
259
|
+
await ensurePrivateDir(dir);
|
|
221
260
|
const configPath = path2.join(dir, "config.json");
|
|
222
261
|
await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
223
|
-
|
|
224
|
-
await chmod(configPath, 384);
|
|
225
|
-
}
|
|
262
|
+
await enforcePrivateFile(configPath);
|
|
226
263
|
}
|
|
227
264
|
async function loadConfigFrom(configPath) {
|
|
228
265
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -242,6 +279,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
|
|
|
242
279
|
var init_config = __esm({
|
|
243
280
|
"src/lib/config.ts"() {
|
|
244
281
|
"use strict";
|
|
282
|
+
init_secure_files();
|
|
245
283
|
EXE_AI_DIR = resolveDataDir();
|
|
246
284
|
DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
|
|
247
285
|
MODELS_DIR = path2.join(EXE_AI_DIR, "models");
|
|
@@ -320,7 +358,7 @@ var init_config = __esm({
|
|
|
320
358
|
|
|
321
359
|
// src/lib/employees.ts
|
|
322
360
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
323
|
-
import { existsSync as
|
|
361
|
+
import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
|
|
324
362
|
import { execSync as execSync2 } from "child_process";
|
|
325
363
|
import path3 from "path";
|
|
326
364
|
import os2 from "os";
|
|
@@ -337,14 +375,14 @@ function getCoordinatorName(employees = loadEmployeesSync()) {
|
|
|
337
375
|
return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
|
|
338
376
|
}
|
|
339
377
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
340
|
-
if (!
|
|
378
|
+
if (!existsSync3(employeesPath)) return [];
|
|
341
379
|
try {
|
|
342
380
|
return JSON.parse(readFileSync2(employeesPath, "utf-8"));
|
|
343
381
|
} catch {
|
|
344
382
|
return [];
|
|
345
383
|
}
|
|
346
384
|
}
|
|
347
|
-
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE;
|
|
385
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, IDENTITY_DIR;
|
|
348
386
|
var init_employees = __esm({
|
|
349
387
|
"src/lib/employees.ts"() {
|
|
350
388
|
"use strict";
|
|
@@ -352,12 +390,609 @@ var init_employees = __esm({
|
|
|
352
390
|
EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
|
|
353
391
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
354
392
|
COORDINATOR_ROLE = "COO";
|
|
393
|
+
IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
// src/lib/database-adapter.ts
|
|
398
|
+
import os3 from "os";
|
|
399
|
+
import path4 from "path";
|
|
400
|
+
import { createRequire } from "module";
|
|
401
|
+
import { pathToFileURL } from "url";
|
|
402
|
+
function quotedIdentifier(identifier) {
|
|
403
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
404
|
+
}
|
|
405
|
+
function unqualifiedTableName(name) {
|
|
406
|
+
const raw = name.trim().replace(/^"|"$/g, "");
|
|
407
|
+
const parts = raw.split(".");
|
|
408
|
+
return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
|
|
409
|
+
}
|
|
410
|
+
function stripTrailingSemicolon(sql) {
|
|
411
|
+
return sql.trim().replace(/;+\s*$/u, "");
|
|
412
|
+
}
|
|
413
|
+
function appendClause(sql, clause) {
|
|
414
|
+
const trimmed = stripTrailingSemicolon(sql);
|
|
415
|
+
const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
|
|
416
|
+
if (!returningMatch) {
|
|
417
|
+
return `${trimmed}${clause}`;
|
|
418
|
+
}
|
|
419
|
+
const idx = returningMatch.index;
|
|
420
|
+
return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
|
|
421
|
+
}
|
|
422
|
+
function normalizeStatement(stmt) {
|
|
423
|
+
if (typeof stmt === "string") {
|
|
424
|
+
return { kind: "positional", sql: stmt, args: [] };
|
|
425
|
+
}
|
|
426
|
+
const sql = stmt.sql;
|
|
427
|
+
if (Array.isArray(stmt.args) || stmt.args === void 0) {
|
|
428
|
+
return { kind: "positional", sql, args: stmt.args ?? [] };
|
|
429
|
+
}
|
|
430
|
+
return { kind: "named", sql, args: stmt.args };
|
|
431
|
+
}
|
|
432
|
+
function rewriteBooleanLiterals(sql) {
|
|
433
|
+
let out = sql;
|
|
434
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
435
|
+
const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
|
|
436
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
|
|
437
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
|
|
438
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
|
|
439
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
|
|
440
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
|
|
441
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
|
|
442
|
+
}
|
|
443
|
+
return out;
|
|
444
|
+
}
|
|
445
|
+
function rewriteInsertOrIgnore(sql) {
|
|
446
|
+
if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
|
|
447
|
+
return sql;
|
|
448
|
+
}
|
|
449
|
+
const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
|
|
450
|
+
return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
|
|
451
|
+
}
|
|
452
|
+
function rewriteInsertOrReplace(sql) {
|
|
453
|
+
const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
|
|
454
|
+
if (!match) {
|
|
455
|
+
return sql;
|
|
456
|
+
}
|
|
457
|
+
const rawTable = match[1];
|
|
458
|
+
const rawColumns = match[2];
|
|
459
|
+
const remainder = match[3];
|
|
460
|
+
const tableName = unqualifiedTableName(rawTable);
|
|
461
|
+
const conflictKeys = UPSERT_KEYS[tableName];
|
|
462
|
+
if (!conflictKeys?.length) {
|
|
463
|
+
return sql;
|
|
464
|
+
}
|
|
465
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
466
|
+
const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
|
|
467
|
+
const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
|
|
468
|
+
const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
|
|
469
|
+
return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
|
|
470
|
+
}
|
|
471
|
+
function rewriteSql(sql) {
|
|
472
|
+
let out = sql;
|
|
473
|
+
out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
|
|
474
|
+
out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
|
|
475
|
+
out = rewriteBooleanLiterals(out);
|
|
476
|
+
out = rewriteInsertOrReplace(out);
|
|
477
|
+
out = rewriteInsertOrIgnore(out);
|
|
478
|
+
return stripTrailingSemicolon(out);
|
|
479
|
+
}
|
|
480
|
+
function toBoolean(value) {
|
|
481
|
+
if (value === null || value === void 0) return value;
|
|
482
|
+
if (typeof value === "boolean") return value;
|
|
483
|
+
if (typeof value === "number") return value !== 0;
|
|
484
|
+
if (typeof value === "bigint") return value !== 0n;
|
|
485
|
+
if (typeof value === "string") {
|
|
486
|
+
const normalized = value.trim().toLowerCase();
|
|
487
|
+
if (normalized === "0" || normalized === "false") return false;
|
|
488
|
+
if (normalized === "1" || normalized === "true") return true;
|
|
489
|
+
}
|
|
490
|
+
return Boolean(value);
|
|
491
|
+
}
|
|
492
|
+
function countQuestionMarks(sql, end) {
|
|
493
|
+
let count = 0;
|
|
494
|
+
let inSingle = false;
|
|
495
|
+
let inDouble = false;
|
|
496
|
+
let inLineComment = false;
|
|
497
|
+
let inBlockComment = false;
|
|
498
|
+
for (let i = 0; i < end; i++) {
|
|
499
|
+
const ch = sql[i];
|
|
500
|
+
const next = sql[i + 1];
|
|
501
|
+
if (inLineComment) {
|
|
502
|
+
if (ch === "\n") inLineComment = false;
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
if (inBlockComment) {
|
|
506
|
+
if (ch === "*" && next === "/") {
|
|
507
|
+
inBlockComment = false;
|
|
508
|
+
i += 1;
|
|
509
|
+
}
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
513
|
+
inLineComment = true;
|
|
514
|
+
i += 1;
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
518
|
+
inBlockComment = true;
|
|
519
|
+
i += 1;
|
|
520
|
+
continue;
|
|
521
|
+
}
|
|
522
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
523
|
+
inSingle = !inSingle;
|
|
524
|
+
continue;
|
|
525
|
+
}
|
|
526
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
527
|
+
inDouble = !inDouble;
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
531
|
+
count += 1;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
return count;
|
|
535
|
+
}
|
|
536
|
+
function findBooleanPlaceholderIndexes(sql) {
|
|
537
|
+
const indexes = /* @__PURE__ */ new Set();
|
|
538
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
539
|
+
const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
|
|
540
|
+
for (const match of sql.matchAll(pattern)) {
|
|
541
|
+
const matchText = match[0];
|
|
542
|
+
const qIndex = match.index + matchText.lastIndexOf("?");
|
|
543
|
+
indexes.add(countQuestionMarks(sql, qIndex + 1));
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
return indexes;
|
|
547
|
+
}
|
|
548
|
+
function coerceInsertBooleanArgs(sql, args) {
|
|
549
|
+
const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
|
|
550
|
+
if (!match) return;
|
|
551
|
+
const rawTable = match[1];
|
|
552
|
+
const rawColumns = match[2];
|
|
553
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
554
|
+
if (!boolColumns?.size) return;
|
|
555
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
556
|
+
for (const [index, column] of columns.entries()) {
|
|
557
|
+
if (boolColumns.has(column) && index < args.length) {
|
|
558
|
+
args[index] = toBoolean(args[index]);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
function coerceUpdateBooleanArgs(sql, args) {
|
|
563
|
+
const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
|
|
564
|
+
if (!match) return;
|
|
565
|
+
const rawTable = match[1];
|
|
566
|
+
const setClause = match[2];
|
|
567
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
568
|
+
if (!boolColumns?.size) return;
|
|
569
|
+
const assignments = setClause.split(",");
|
|
570
|
+
let placeholderIndex = 0;
|
|
571
|
+
for (const assignment of assignments) {
|
|
572
|
+
if (!assignment.includes("?")) continue;
|
|
573
|
+
placeholderIndex += 1;
|
|
574
|
+
const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
|
|
575
|
+
if (colMatch && boolColumns.has(colMatch[1])) {
|
|
576
|
+
args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
function coerceBooleanArgs(sql, args) {
|
|
581
|
+
const nextArgs = [...args];
|
|
582
|
+
coerceInsertBooleanArgs(sql, nextArgs);
|
|
583
|
+
coerceUpdateBooleanArgs(sql, nextArgs);
|
|
584
|
+
const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
|
|
585
|
+
for (const index of placeholderIndexes) {
|
|
586
|
+
if (index > 0 && index <= nextArgs.length) {
|
|
587
|
+
nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
return nextArgs;
|
|
591
|
+
}
|
|
592
|
+
function convertQuestionMarksToDollarParams(sql) {
|
|
593
|
+
let out = "";
|
|
594
|
+
let placeholder = 0;
|
|
595
|
+
let inSingle = false;
|
|
596
|
+
let inDouble = false;
|
|
597
|
+
let inLineComment = false;
|
|
598
|
+
let inBlockComment = false;
|
|
599
|
+
for (let i = 0; i < sql.length; i++) {
|
|
600
|
+
const ch = sql[i];
|
|
601
|
+
const next = sql[i + 1];
|
|
602
|
+
if (inLineComment) {
|
|
603
|
+
out += ch;
|
|
604
|
+
if (ch === "\n") inLineComment = false;
|
|
605
|
+
continue;
|
|
606
|
+
}
|
|
607
|
+
if (inBlockComment) {
|
|
608
|
+
out += ch;
|
|
609
|
+
if (ch === "*" && next === "/") {
|
|
610
|
+
out += next;
|
|
611
|
+
inBlockComment = false;
|
|
612
|
+
i += 1;
|
|
613
|
+
}
|
|
614
|
+
continue;
|
|
615
|
+
}
|
|
616
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
617
|
+
out += ch + next;
|
|
618
|
+
inLineComment = true;
|
|
619
|
+
i += 1;
|
|
620
|
+
continue;
|
|
621
|
+
}
|
|
622
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
623
|
+
out += ch + next;
|
|
624
|
+
inBlockComment = true;
|
|
625
|
+
i += 1;
|
|
626
|
+
continue;
|
|
627
|
+
}
|
|
628
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
629
|
+
inSingle = !inSingle;
|
|
630
|
+
out += ch;
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
634
|
+
inDouble = !inDouble;
|
|
635
|
+
out += ch;
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
638
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
639
|
+
placeholder += 1;
|
|
640
|
+
out += `$${placeholder}`;
|
|
641
|
+
continue;
|
|
642
|
+
}
|
|
643
|
+
out += ch;
|
|
644
|
+
}
|
|
645
|
+
return out;
|
|
646
|
+
}
|
|
647
|
+
function translateStatementForPostgres(stmt) {
|
|
648
|
+
const normalized = normalizeStatement(stmt);
|
|
649
|
+
if (normalized.kind === "named") {
|
|
650
|
+
throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
|
|
651
|
+
}
|
|
652
|
+
const rewrittenSql = rewriteSql(normalized.sql);
|
|
653
|
+
const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
|
|
654
|
+
return {
|
|
655
|
+
sql: convertQuestionMarksToDollarParams(rewrittenSql),
|
|
656
|
+
args: coercedArgs
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
function shouldBypassPostgres(stmt) {
|
|
660
|
+
const normalized = normalizeStatement(stmt);
|
|
661
|
+
if (normalized.kind === "named") {
|
|
662
|
+
return true;
|
|
663
|
+
}
|
|
664
|
+
return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
|
|
665
|
+
}
|
|
666
|
+
function shouldFallbackOnError(error) {
|
|
667
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
668
|
+
return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
|
|
669
|
+
}
|
|
670
|
+
function isReadQuery(sql) {
|
|
671
|
+
const trimmed = sql.trimStart();
|
|
672
|
+
return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
|
|
673
|
+
}
|
|
674
|
+
function buildRow(row, columns) {
|
|
675
|
+
const values = columns.map((column) => row[column]);
|
|
676
|
+
return Object.assign(values, row);
|
|
677
|
+
}
|
|
678
|
+
function buildResultSet(rows, rowsAffected = 0) {
|
|
679
|
+
const columns = rows[0] ? Object.keys(rows[0]) : [];
|
|
680
|
+
const resultRows = rows.map((row) => buildRow(row, columns));
|
|
681
|
+
return {
|
|
682
|
+
columns,
|
|
683
|
+
columnTypes: columns.map(() => ""),
|
|
684
|
+
rows: resultRows,
|
|
685
|
+
rowsAffected,
|
|
686
|
+
lastInsertRowid: void 0,
|
|
687
|
+
toJSON() {
|
|
688
|
+
return {
|
|
689
|
+
columns,
|
|
690
|
+
columnTypes: columns.map(() => ""),
|
|
691
|
+
rows,
|
|
692
|
+
rowsAffected,
|
|
693
|
+
lastInsertRowid: void 0
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
async function loadPrismaClient() {
|
|
699
|
+
if (!prismaClientPromise) {
|
|
700
|
+
prismaClientPromise = (async () => {
|
|
701
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
702
|
+
if (explicitPath) {
|
|
703
|
+
const module2 = await import(pathToFileURL(explicitPath).href);
|
|
704
|
+
const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
|
|
705
|
+
if (!PrismaClient2) {
|
|
706
|
+
throw new Error(`No PrismaClient export found at ${explicitPath}`);
|
|
707
|
+
}
|
|
708
|
+
return new PrismaClient2();
|
|
709
|
+
}
|
|
710
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path4.join(os3.homedir(), "exe-db");
|
|
711
|
+
const requireFromExeDb = createRequire(path4.join(exeDbRoot, "package.json"));
|
|
712
|
+
const prismaEntry = requireFromExeDb.resolve("@prisma/client");
|
|
713
|
+
const module = await import(pathToFileURL(prismaEntry).href);
|
|
714
|
+
const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
|
|
715
|
+
if (!PrismaClient) {
|
|
716
|
+
throw new Error(`No PrismaClient export found in ${prismaEntry}`);
|
|
717
|
+
}
|
|
718
|
+
return new PrismaClient();
|
|
719
|
+
})();
|
|
720
|
+
}
|
|
721
|
+
return prismaClientPromise;
|
|
722
|
+
}
|
|
723
|
+
async function ensureCompatibilityViews(prisma) {
|
|
724
|
+
if (!compatibilityBootstrapPromise) {
|
|
725
|
+
compatibilityBootstrapPromise = (async () => {
|
|
726
|
+
for (const mapping of VIEW_MAPPINGS) {
|
|
727
|
+
const relation = mapping.source.replace(/"/g, "");
|
|
728
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
729
|
+
"SELECT to_regclass($1) AS regclass",
|
|
730
|
+
relation
|
|
731
|
+
);
|
|
732
|
+
if (!rows[0]?.regclass) {
|
|
733
|
+
continue;
|
|
734
|
+
}
|
|
735
|
+
await prisma.$executeRawUnsafe(
|
|
736
|
+
`CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
})();
|
|
740
|
+
}
|
|
741
|
+
return compatibilityBootstrapPromise;
|
|
742
|
+
}
|
|
743
|
+
async function executeOnPrisma(executor, stmt) {
|
|
744
|
+
const translated = translateStatementForPostgres(stmt);
|
|
745
|
+
if (isReadQuery(translated.sql)) {
|
|
746
|
+
const rows = await executor.$queryRawUnsafe(
|
|
747
|
+
translated.sql,
|
|
748
|
+
...translated.args
|
|
749
|
+
);
|
|
750
|
+
return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
|
|
751
|
+
}
|
|
752
|
+
const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
|
|
753
|
+
return buildResultSet([], rowsAffected);
|
|
754
|
+
}
|
|
755
|
+
function splitSqlStatements(sql) {
|
|
756
|
+
const parts = [];
|
|
757
|
+
let current = "";
|
|
758
|
+
let inSingle = false;
|
|
759
|
+
let inDouble = false;
|
|
760
|
+
let inLineComment = false;
|
|
761
|
+
let inBlockComment = false;
|
|
762
|
+
for (let i = 0; i < sql.length; i++) {
|
|
763
|
+
const ch = sql[i];
|
|
764
|
+
const next = sql[i + 1];
|
|
765
|
+
if (inLineComment) {
|
|
766
|
+
current += ch;
|
|
767
|
+
if (ch === "\n") inLineComment = false;
|
|
768
|
+
continue;
|
|
769
|
+
}
|
|
770
|
+
if (inBlockComment) {
|
|
771
|
+
current += ch;
|
|
772
|
+
if (ch === "*" && next === "/") {
|
|
773
|
+
current += next;
|
|
774
|
+
inBlockComment = false;
|
|
775
|
+
i += 1;
|
|
776
|
+
}
|
|
777
|
+
continue;
|
|
778
|
+
}
|
|
779
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
780
|
+
current += ch + next;
|
|
781
|
+
inLineComment = true;
|
|
782
|
+
i += 1;
|
|
783
|
+
continue;
|
|
784
|
+
}
|
|
785
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
786
|
+
current += ch + next;
|
|
787
|
+
inBlockComment = true;
|
|
788
|
+
i += 1;
|
|
789
|
+
continue;
|
|
790
|
+
}
|
|
791
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
792
|
+
inSingle = !inSingle;
|
|
793
|
+
current += ch;
|
|
794
|
+
continue;
|
|
795
|
+
}
|
|
796
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
797
|
+
inDouble = !inDouble;
|
|
798
|
+
current += ch;
|
|
799
|
+
continue;
|
|
800
|
+
}
|
|
801
|
+
if (!inSingle && !inDouble && ch === ";") {
|
|
802
|
+
if (current.trim()) {
|
|
803
|
+
parts.push(current.trim());
|
|
804
|
+
}
|
|
805
|
+
current = "";
|
|
806
|
+
continue;
|
|
807
|
+
}
|
|
808
|
+
current += ch;
|
|
809
|
+
}
|
|
810
|
+
if (current.trim()) {
|
|
811
|
+
parts.push(current.trim());
|
|
812
|
+
}
|
|
813
|
+
return parts;
|
|
814
|
+
}
|
|
815
|
+
async function createPrismaDbAdapter(fallbackClient) {
|
|
816
|
+
const prisma = await loadPrismaClient();
|
|
817
|
+
await ensureCompatibilityViews(prisma);
|
|
818
|
+
let closed = false;
|
|
819
|
+
let adapter;
|
|
820
|
+
const fallbackExecute = async (stmt, error) => {
|
|
821
|
+
if (!fallbackClient) {
|
|
822
|
+
if (error) throw error;
|
|
823
|
+
throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
|
|
824
|
+
}
|
|
825
|
+
if (error) {
|
|
826
|
+
process.stderr.write(
|
|
827
|
+
`[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
828
|
+
`
|
|
829
|
+
);
|
|
830
|
+
}
|
|
831
|
+
return fallbackClient.execute(stmt);
|
|
832
|
+
};
|
|
833
|
+
adapter = {
|
|
834
|
+
async execute(stmt) {
|
|
835
|
+
if (shouldBypassPostgres(stmt)) {
|
|
836
|
+
return fallbackExecute(stmt);
|
|
837
|
+
}
|
|
838
|
+
try {
|
|
839
|
+
return await executeOnPrisma(prisma, stmt);
|
|
840
|
+
} catch (error) {
|
|
841
|
+
if (shouldFallbackOnError(error)) {
|
|
842
|
+
return fallbackExecute(stmt, error);
|
|
843
|
+
}
|
|
844
|
+
throw error;
|
|
845
|
+
}
|
|
846
|
+
},
|
|
847
|
+
async batch(stmts, mode) {
|
|
848
|
+
if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
|
|
849
|
+
if (!fallbackClient) {
|
|
850
|
+
throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
|
|
851
|
+
}
|
|
852
|
+
return fallbackClient.batch(stmts, mode);
|
|
853
|
+
}
|
|
854
|
+
try {
|
|
855
|
+
if (prisma.$transaction) {
|
|
856
|
+
return await prisma.$transaction(async (tx) => {
|
|
857
|
+
const results2 = [];
|
|
858
|
+
for (const stmt of stmts) {
|
|
859
|
+
results2.push(await executeOnPrisma(tx, stmt));
|
|
860
|
+
}
|
|
861
|
+
return results2;
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
const results = [];
|
|
865
|
+
for (const stmt of stmts) {
|
|
866
|
+
results.push(await executeOnPrisma(prisma, stmt));
|
|
867
|
+
}
|
|
868
|
+
return results;
|
|
869
|
+
} catch (error) {
|
|
870
|
+
if (fallbackClient && shouldFallbackOnError(error)) {
|
|
871
|
+
process.stderr.write(
|
|
872
|
+
`[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
873
|
+
`
|
|
874
|
+
);
|
|
875
|
+
return fallbackClient.batch(stmts, mode);
|
|
876
|
+
}
|
|
877
|
+
throw error;
|
|
878
|
+
}
|
|
879
|
+
},
|
|
880
|
+
async migrate(stmts) {
|
|
881
|
+
if (fallbackClient) {
|
|
882
|
+
return fallbackClient.migrate(stmts);
|
|
883
|
+
}
|
|
884
|
+
return adapter.batch(stmts, "deferred");
|
|
885
|
+
},
|
|
886
|
+
async transaction(mode) {
|
|
887
|
+
if (!fallbackClient) {
|
|
888
|
+
throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
|
|
889
|
+
}
|
|
890
|
+
return fallbackClient.transaction(mode);
|
|
891
|
+
},
|
|
892
|
+
async executeMultiple(sql) {
|
|
893
|
+
if (fallbackClient && shouldBypassPostgres(sql)) {
|
|
894
|
+
return fallbackClient.executeMultiple(sql);
|
|
895
|
+
}
|
|
896
|
+
for (const statement of splitSqlStatements(sql)) {
|
|
897
|
+
await adapter.execute(statement);
|
|
898
|
+
}
|
|
899
|
+
},
|
|
900
|
+
async sync() {
|
|
901
|
+
if (fallbackClient) {
|
|
902
|
+
return fallbackClient.sync();
|
|
903
|
+
}
|
|
904
|
+
return { frame_no: 0, frames_synced: 0 };
|
|
905
|
+
},
|
|
906
|
+
close() {
|
|
907
|
+
closed = true;
|
|
908
|
+
prismaClientPromise = null;
|
|
909
|
+
compatibilityBootstrapPromise = null;
|
|
910
|
+
void prisma.$disconnect?.();
|
|
911
|
+
},
|
|
912
|
+
get closed() {
|
|
913
|
+
return closed;
|
|
914
|
+
},
|
|
915
|
+
get protocol() {
|
|
916
|
+
return "prisma-postgres";
|
|
917
|
+
}
|
|
918
|
+
};
|
|
919
|
+
return adapter;
|
|
920
|
+
}
|
|
921
|
+
var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
|
|
922
|
+
var init_database_adapter = __esm({
|
|
923
|
+
"src/lib/database-adapter.ts"() {
|
|
924
|
+
"use strict";
|
|
925
|
+
VIEW_MAPPINGS = [
|
|
926
|
+
{ view: "memories", source: "memory.memory_records" },
|
|
927
|
+
{ view: "tasks", source: "memory.tasks" },
|
|
928
|
+
{ view: "behaviors", source: "memory.behaviors" },
|
|
929
|
+
{ view: "entities", source: "memory.entities" },
|
|
930
|
+
{ view: "relationships", source: "memory.relationships" },
|
|
931
|
+
{ view: "entity_memories", source: "memory.entity_memories" },
|
|
932
|
+
{ view: "entity_aliases", source: "memory.entity_aliases" },
|
|
933
|
+
{ view: "notifications", source: "memory.notifications" },
|
|
934
|
+
{ view: "messages", source: "memory.messages" },
|
|
935
|
+
{ view: "users", source: "wiki.users" },
|
|
936
|
+
{ view: "workspaces", source: "wiki.workspaces" },
|
|
937
|
+
{ view: "workspace_users", source: "wiki.workspace_users" },
|
|
938
|
+
{ view: "documents", source: "wiki.workspace_documents" },
|
|
939
|
+
{ view: "chats", source: "wiki.workspace_chats" }
|
|
940
|
+
];
|
|
941
|
+
UPSERT_KEYS = {
|
|
942
|
+
memories: ["id"],
|
|
943
|
+
tasks: ["id"],
|
|
944
|
+
behaviors: ["id"],
|
|
945
|
+
entities: ["id"],
|
|
946
|
+
relationships: ["id"],
|
|
947
|
+
entity_aliases: ["alias"],
|
|
948
|
+
notifications: ["id"],
|
|
949
|
+
messages: ["id"],
|
|
950
|
+
users: ["id"],
|
|
951
|
+
workspaces: ["id"],
|
|
952
|
+
workspace_users: ["id"],
|
|
953
|
+
documents: ["id"],
|
|
954
|
+
chats: ["id"]
|
|
955
|
+
};
|
|
956
|
+
BOOLEAN_COLUMNS_BY_TABLE = {
|
|
957
|
+
memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
|
|
958
|
+
behaviors: /* @__PURE__ */ new Set(["active"]),
|
|
959
|
+
notifications: /* @__PURE__ */ new Set(["read"]),
|
|
960
|
+
users: /* @__PURE__ */ new Set(["has_personal_memory"])
|
|
961
|
+
};
|
|
962
|
+
BOOLEAN_COLUMN_NAMES = new Set(
|
|
963
|
+
Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
|
|
964
|
+
);
|
|
965
|
+
IMMEDIATE_FALLBACK_PATTERNS = [
|
|
966
|
+
/\bPRAGMA\b/i,
|
|
967
|
+
/\bsqlite_master\b/i,
|
|
968
|
+
/(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
|
|
969
|
+
/\bMATCH\b/i,
|
|
970
|
+
/\bvector_distance_cos\s*\(/i,
|
|
971
|
+
/\bjson_extract\s*\(/i,
|
|
972
|
+
/\bjulianday\s*\(/i,
|
|
973
|
+
/\bstrftime\s*\(/i,
|
|
974
|
+
/\blast_insert_rowid\s*\(/i
|
|
975
|
+
];
|
|
976
|
+
prismaClientPromise = null;
|
|
977
|
+
compatibilityBootstrapPromise = null;
|
|
355
978
|
}
|
|
356
979
|
});
|
|
357
980
|
|
|
358
981
|
// src/lib/database.ts
|
|
359
982
|
import { createClient } from "@libsql/client";
|
|
360
983
|
async function initDatabase(config) {
|
|
984
|
+
if (_walCheckpointTimer) {
|
|
985
|
+
clearInterval(_walCheckpointTimer);
|
|
986
|
+
_walCheckpointTimer = null;
|
|
987
|
+
}
|
|
988
|
+
if (_daemonClient) {
|
|
989
|
+
_daemonClient.close();
|
|
990
|
+
_daemonClient = null;
|
|
991
|
+
}
|
|
992
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
993
|
+
_adapterClient.close();
|
|
994
|
+
}
|
|
995
|
+
_adapterClient = null;
|
|
361
996
|
if (_client) {
|
|
362
997
|
_client.close();
|
|
363
998
|
_client = null;
|
|
@@ -371,6 +1006,7 @@ async function initDatabase(config) {
|
|
|
371
1006
|
}
|
|
372
1007
|
_client = createClient(opts);
|
|
373
1008
|
_resilientClient = wrapWithRetry(_client);
|
|
1009
|
+
_adapterClient = _resilientClient;
|
|
374
1010
|
_client.execute("PRAGMA busy_timeout = 30000").catch(() => {
|
|
375
1011
|
});
|
|
376
1012
|
_client.execute("PRAGMA journal_mode = WAL").catch(() => {
|
|
@@ -381,14 +1017,20 @@ async function initDatabase(config) {
|
|
|
381
1017
|
});
|
|
382
1018
|
}, 3e4);
|
|
383
1019
|
_walCheckpointTimer.unref();
|
|
1020
|
+
if (process.env.DATABASE_URL) {
|
|
1021
|
+
_adapterClient = await createPrismaDbAdapter(_resilientClient);
|
|
1022
|
+
}
|
|
384
1023
|
}
|
|
385
1024
|
function isInitialized() {
|
|
386
|
-
return _client !== null;
|
|
1025
|
+
return _adapterClient !== null || _client !== null;
|
|
387
1026
|
}
|
|
388
1027
|
function getClient() {
|
|
389
|
-
if (!
|
|
1028
|
+
if (!_adapterClient) {
|
|
390
1029
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
391
1030
|
}
|
|
1031
|
+
if (process.env.DATABASE_URL) {
|
|
1032
|
+
return _adapterClient;
|
|
1033
|
+
}
|
|
392
1034
|
if (process.env.EXE_IS_DAEMON === "1") {
|
|
393
1035
|
return _resilientClient;
|
|
394
1036
|
}
|
|
@@ -681,6 +1323,7 @@ async function ensureSchema() {
|
|
|
681
1323
|
project TEXT NOT NULL,
|
|
682
1324
|
summary TEXT NOT NULL,
|
|
683
1325
|
task_file TEXT,
|
|
1326
|
+
session_scope TEXT,
|
|
684
1327
|
read INTEGER NOT NULL DEFAULT 0,
|
|
685
1328
|
created_at TEXT NOT NULL
|
|
686
1329
|
);
|
|
@@ -689,7 +1332,7 @@ async function ensureSchema() {
|
|
|
689
1332
|
ON notifications(read);
|
|
690
1333
|
|
|
691
1334
|
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
692
|
-
ON notifications(agent_id);
|
|
1335
|
+
ON notifications(agent_id, session_scope);
|
|
693
1336
|
|
|
694
1337
|
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
695
1338
|
ON notifications(task_file);
|
|
@@ -727,6 +1370,7 @@ async function ensureSchema() {
|
|
|
727
1370
|
target_agent TEXT NOT NULL,
|
|
728
1371
|
target_project TEXT,
|
|
729
1372
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
1373
|
+
session_scope TEXT,
|
|
730
1374
|
content TEXT NOT NULL,
|
|
731
1375
|
priority TEXT DEFAULT 'normal',
|
|
732
1376
|
status TEXT DEFAULT 'pending',
|
|
@@ -740,10 +1384,31 @@ async function ensureSchema() {
|
|
|
740
1384
|
);
|
|
741
1385
|
|
|
742
1386
|
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
743
|
-
ON messages(target_agent, status);
|
|
1387
|
+
ON messages(target_agent, session_scope, status);
|
|
744
1388
|
|
|
745
1389
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
746
|
-
ON messages(target_agent, from_agent, server_seq);
|
|
1390
|
+
ON messages(target_agent, session_scope, from_agent, server_seq);
|
|
1391
|
+
`);
|
|
1392
|
+
try {
|
|
1393
|
+
await client.execute({
|
|
1394
|
+
sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
|
|
1395
|
+
args: []
|
|
1396
|
+
});
|
|
1397
|
+
} catch {
|
|
1398
|
+
}
|
|
1399
|
+
try {
|
|
1400
|
+
await client.execute({
|
|
1401
|
+
sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
|
|
1402
|
+
args: []
|
|
1403
|
+
});
|
|
1404
|
+
} catch {
|
|
1405
|
+
}
|
|
1406
|
+
await client.executeMultiple(`
|
|
1407
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
|
|
1408
|
+
ON notifications(agent_id, session_scope, read, created_at);
|
|
1409
|
+
|
|
1410
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
|
|
1411
|
+
ON messages(target_agent, session_scope, status, created_at);
|
|
747
1412
|
`);
|
|
748
1413
|
try {
|
|
749
1414
|
await client.execute({
|
|
@@ -1327,17 +1992,26 @@ async function ensureSchema() {
|
|
|
1327
1992
|
} catch {
|
|
1328
1993
|
}
|
|
1329
1994
|
}
|
|
1995
|
+
try {
|
|
1996
|
+
await client.execute({
|
|
1997
|
+
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
1998
|
+
args: []
|
|
1999
|
+
});
|
|
2000
|
+
} catch {
|
|
2001
|
+
}
|
|
1330
2002
|
}
|
|
1331
|
-
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso;
|
|
2003
|
+
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso;
|
|
1332
2004
|
var init_database = __esm({
|
|
1333
2005
|
"src/lib/database.ts"() {
|
|
1334
2006
|
"use strict";
|
|
1335
2007
|
init_db_retry();
|
|
1336
2008
|
init_employees();
|
|
2009
|
+
init_database_adapter();
|
|
1337
2010
|
_client = null;
|
|
1338
2011
|
_resilientClient = null;
|
|
1339
2012
|
_walCheckpointTimer = null;
|
|
1340
2013
|
_daemonClient = null;
|
|
2014
|
+
_adapterClient = null;
|
|
1341
2015
|
initTurso = initDatabase;
|
|
1342
2016
|
}
|
|
1343
2017
|
});
|
|
@@ -1347,6 +2021,7 @@ var shard_manager_exports = {};
|
|
|
1347
2021
|
__export(shard_manager_exports, {
|
|
1348
2022
|
disposeShards: () => disposeShards,
|
|
1349
2023
|
ensureShardSchema: () => ensureShardSchema,
|
|
2024
|
+
getOpenShardCount: () => getOpenShardCount,
|
|
1350
2025
|
getReadyShardClient: () => getReadyShardClient,
|
|
1351
2026
|
getShardClient: () => getShardClient,
|
|
1352
2027
|
getShardsDir: () => getShardsDir,
|
|
@@ -1355,15 +2030,18 @@ __export(shard_manager_exports, {
|
|
|
1355
2030
|
listShards: () => listShards,
|
|
1356
2031
|
shardExists: () => shardExists
|
|
1357
2032
|
});
|
|
1358
|
-
import
|
|
1359
|
-
import { existsSync as
|
|
2033
|
+
import path6 from "path";
|
|
2034
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync } from "fs";
|
|
1360
2035
|
import { createClient as createClient2 } from "@libsql/client";
|
|
1361
2036
|
function initShardManager(encryptionKey) {
|
|
1362
2037
|
_encryptionKey = encryptionKey;
|
|
1363
|
-
if (!
|
|
1364
|
-
|
|
2038
|
+
if (!existsSync5(SHARDS_DIR)) {
|
|
2039
|
+
mkdirSync2(SHARDS_DIR, { recursive: true });
|
|
1365
2040
|
}
|
|
1366
2041
|
_shardingEnabled = true;
|
|
2042
|
+
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
2043
|
+
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
2044
|
+
_evictionTimer.unref();
|
|
1367
2045
|
}
|
|
1368
2046
|
function isShardingEnabled() {
|
|
1369
2047
|
return _shardingEnabled;
|
|
@@ -1380,21 +2058,28 @@ function getShardClient(projectName) {
|
|
|
1380
2058
|
throw new Error(`Invalid project name for shard: "${projectName}"`);
|
|
1381
2059
|
}
|
|
1382
2060
|
const cached = _shards.get(safeName);
|
|
1383
|
-
if (cached)
|
|
1384
|
-
|
|
2061
|
+
if (cached) {
|
|
2062
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
2063
|
+
return cached;
|
|
2064
|
+
}
|
|
2065
|
+
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
2066
|
+
evictLRU();
|
|
2067
|
+
}
|
|
2068
|
+
const dbPath = path6.join(SHARDS_DIR, `${safeName}.db`);
|
|
1385
2069
|
const client = createClient2({
|
|
1386
2070
|
url: `file:${dbPath}`,
|
|
1387
2071
|
encryptionKey: _encryptionKey
|
|
1388
2072
|
});
|
|
1389
2073
|
_shards.set(safeName, client);
|
|
2074
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
1390
2075
|
return client;
|
|
1391
2076
|
}
|
|
1392
2077
|
function shardExists(projectName) {
|
|
1393
2078
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
1394
|
-
return
|
|
2079
|
+
return existsSync5(path6.join(SHARDS_DIR, `${safeName}.db`));
|
|
1395
2080
|
}
|
|
1396
2081
|
function listShards() {
|
|
1397
|
-
if (!
|
|
2082
|
+
if (!existsSync5(SHARDS_DIR)) return [];
|
|
1398
2083
|
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
1399
2084
|
}
|
|
1400
2085
|
async function ensureShardSchema(client) {
|
|
@@ -1446,6 +2131,8 @@ async function ensureShardSchema(client) {
|
|
|
1446
2131
|
for (const col of [
|
|
1447
2132
|
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
1448
2133
|
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
2134
|
+
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
2135
|
+
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
1449
2136
|
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
1450
2137
|
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
1451
2138
|
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
@@ -1468,7 +2155,23 @@ async function ensureShardSchema(client) {
|
|
|
1468
2155
|
// MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
|
|
1469
2156
|
"ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
|
|
1470
2157
|
"ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
|
|
1471
|
-
"ALTER TABLE memories ADD COLUMN trajectory TEXT"
|
|
2158
|
+
"ALTER TABLE memories ADD COLUMN trajectory TEXT",
|
|
2159
|
+
// Metadata enrichment columns (must match database.ts)
|
|
2160
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
2161
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
2162
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
2163
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
2164
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
2165
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
2166
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
2167
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
2168
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
2169
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
2170
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
2171
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
2172
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
2173
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
2174
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
1472
2175
|
]) {
|
|
1473
2176
|
try {
|
|
1474
2177
|
await client.execute(col);
|
|
@@ -1567,21 +2270,69 @@ async function getReadyShardClient(projectName) {
|
|
|
1567
2270
|
await ensureShardSchema(client);
|
|
1568
2271
|
return client;
|
|
1569
2272
|
}
|
|
2273
|
+
function evictLRU() {
|
|
2274
|
+
let oldest = null;
|
|
2275
|
+
let oldestTime = Infinity;
|
|
2276
|
+
for (const [name, time] of _shardLastAccess) {
|
|
2277
|
+
if (time < oldestTime) {
|
|
2278
|
+
oldestTime = time;
|
|
2279
|
+
oldest = name;
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
if (oldest) {
|
|
2283
|
+
const client = _shards.get(oldest);
|
|
2284
|
+
if (client) {
|
|
2285
|
+
client.close();
|
|
2286
|
+
}
|
|
2287
|
+
_shards.delete(oldest);
|
|
2288
|
+
_shardLastAccess.delete(oldest);
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
function evictIdleShards() {
|
|
2292
|
+
const now = Date.now();
|
|
2293
|
+
const toEvict = [];
|
|
2294
|
+
for (const [name, lastAccess] of _shardLastAccess) {
|
|
2295
|
+
if (now - lastAccess > SHARD_IDLE_MS) {
|
|
2296
|
+
toEvict.push(name);
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
for (const name of toEvict) {
|
|
2300
|
+
const client = _shards.get(name);
|
|
2301
|
+
if (client) {
|
|
2302
|
+
client.close();
|
|
2303
|
+
}
|
|
2304
|
+
_shards.delete(name);
|
|
2305
|
+
_shardLastAccess.delete(name);
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
function getOpenShardCount() {
|
|
2309
|
+
return _shards.size;
|
|
2310
|
+
}
|
|
1570
2311
|
function disposeShards() {
|
|
2312
|
+
if (_evictionTimer) {
|
|
2313
|
+
clearInterval(_evictionTimer);
|
|
2314
|
+
_evictionTimer = null;
|
|
2315
|
+
}
|
|
1571
2316
|
for (const [, client] of _shards) {
|
|
1572
2317
|
client.close();
|
|
1573
2318
|
}
|
|
1574
2319
|
_shards.clear();
|
|
2320
|
+
_shardLastAccess.clear();
|
|
1575
2321
|
_shardingEnabled = false;
|
|
1576
2322
|
_encryptionKey = null;
|
|
1577
2323
|
}
|
|
1578
|
-
var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
|
|
2324
|
+
var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
|
|
1579
2325
|
var init_shard_manager = __esm({
|
|
1580
2326
|
"src/lib/shard-manager.ts"() {
|
|
1581
2327
|
"use strict";
|
|
1582
2328
|
init_config();
|
|
1583
|
-
SHARDS_DIR =
|
|
2329
|
+
SHARDS_DIR = path6.join(EXE_AI_DIR, "shards");
|
|
2330
|
+
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
2331
|
+
MAX_OPEN_SHARDS = 10;
|
|
2332
|
+
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
1584
2333
|
_shards = /* @__PURE__ */ new Map();
|
|
2334
|
+
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
2335
|
+
_evictionTimer = null;
|
|
1585
2336
|
_encryptionKey = null;
|
|
1586
2337
|
_shardingEnabled = false;
|
|
1587
2338
|
}
|
|
@@ -1774,13 +2525,50 @@ ${p.content}`).join("\n\n");
|
|
|
1774
2525
|
}
|
|
1775
2526
|
});
|
|
1776
2527
|
|
|
2528
|
+
// src/lib/daemon-auth.ts
|
|
2529
|
+
import crypto from "crypto";
|
|
2530
|
+
import path9 from "path";
|
|
2531
|
+
import { existsSync as existsSync8, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
2532
|
+
function normalizeToken(token) {
|
|
2533
|
+
if (!token) return null;
|
|
2534
|
+
const trimmed = token.trim();
|
|
2535
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
2536
|
+
}
|
|
2537
|
+
function readDaemonToken() {
|
|
2538
|
+
try {
|
|
2539
|
+
if (!existsSync8(DAEMON_TOKEN_PATH)) return null;
|
|
2540
|
+
return normalizeToken(readFileSync5(DAEMON_TOKEN_PATH, "utf8"));
|
|
2541
|
+
} catch {
|
|
2542
|
+
return null;
|
|
2543
|
+
}
|
|
2544
|
+
}
|
|
2545
|
+
function ensureDaemonToken(seed) {
|
|
2546
|
+
const existing = readDaemonToken();
|
|
2547
|
+
if (existing) return existing;
|
|
2548
|
+
const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
|
|
2549
|
+
ensurePrivateDirSync(EXE_AI_DIR);
|
|
2550
|
+
writeFileSync3(DAEMON_TOKEN_PATH, `${token}
|
|
2551
|
+
`, "utf8");
|
|
2552
|
+
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
2553
|
+
return token;
|
|
2554
|
+
}
|
|
2555
|
+
var DAEMON_TOKEN_PATH;
|
|
2556
|
+
var init_daemon_auth = __esm({
|
|
2557
|
+
"src/lib/daemon-auth.ts"() {
|
|
2558
|
+
"use strict";
|
|
2559
|
+
init_config();
|
|
2560
|
+
init_secure_files();
|
|
2561
|
+
DAEMON_TOKEN_PATH = path9.join(EXE_AI_DIR, "exed.token");
|
|
2562
|
+
}
|
|
2563
|
+
});
|
|
2564
|
+
|
|
1777
2565
|
// src/lib/exe-daemon-client.ts
|
|
1778
2566
|
import net from "net";
|
|
1779
|
-
import
|
|
2567
|
+
import os6 from "os";
|
|
1780
2568
|
import { spawn } from "child_process";
|
|
1781
2569
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
1782
|
-
import { existsSync as
|
|
1783
|
-
import
|
|
2570
|
+
import { existsSync as existsSync9, unlinkSync as unlinkSync2, readFileSync as readFileSync6, openSync, closeSync, statSync } from "fs";
|
|
2571
|
+
import path10 from "path";
|
|
1784
2572
|
import { fileURLToPath } from "url";
|
|
1785
2573
|
function handleData(chunk) {
|
|
1786
2574
|
_buffer += chunk.toString();
|
|
@@ -1808,9 +2596,9 @@ function handleData(chunk) {
|
|
|
1808
2596
|
}
|
|
1809
2597
|
}
|
|
1810
2598
|
function cleanupStaleFiles() {
|
|
1811
|
-
if (
|
|
2599
|
+
if (existsSync9(PID_PATH)) {
|
|
1812
2600
|
try {
|
|
1813
|
-
const pid = parseInt(
|
|
2601
|
+
const pid = parseInt(readFileSync6(PID_PATH, "utf8").trim(), 10);
|
|
1814
2602
|
if (pid > 0) {
|
|
1815
2603
|
try {
|
|
1816
2604
|
process.kill(pid, 0);
|
|
@@ -1831,17 +2619,17 @@ function cleanupStaleFiles() {
|
|
|
1831
2619
|
}
|
|
1832
2620
|
}
|
|
1833
2621
|
function findPackageRoot() {
|
|
1834
|
-
let dir =
|
|
1835
|
-
const { root } =
|
|
2622
|
+
let dir = path10.dirname(fileURLToPath(import.meta.url));
|
|
2623
|
+
const { root } = path10.parse(dir);
|
|
1836
2624
|
while (dir !== root) {
|
|
1837
|
-
if (
|
|
1838
|
-
dir =
|
|
2625
|
+
if (existsSync9(path10.join(dir, "package.json"))) return dir;
|
|
2626
|
+
dir = path10.dirname(dir);
|
|
1839
2627
|
}
|
|
1840
2628
|
return null;
|
|
1841
2629
|
}
|
|
1842
2630
|
function spawnDaemon() {
|
|
1843
|
-
const freeGB =
|
|
1844
|
-
const totalGB =
|
|
2631
|
+
const freeGB = os6.freemem() / (1024 * 1024 * 1024);
|
|
2632
|
+
const totalGB = os6.totalmem() / (1024 * 1024 * 1024);
|
|
1845
2633
|
if (totalGB <= 8) {
|
|
1846
2634
|
process.stderr.write(
|
|
1847
2635
|
`[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
|
|
@@ -1861,16 +2649,17 @@ function spawnDaemon() {
|
|
|
1861
2649
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
1862
2650
|
return;
|
|
1863
2651
|
}
|
|
1864
|
-
const daemonPath =
|
|
1865
|
-
if (!
|
|
2652
|
+
const daemonPath = path10.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
2653
|
+
if (!existsSync9(daemonPath)) {
|
|
1866
2654
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
1867
2655
|
`);
|
|
1868
2656
|
return;
|
|
1869
2657
|
}
|
|
1870
2658
|
const resolvedPath = daemonPath;
|
|
2659
|
+
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
1871
2660
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
1872
2661
|
`);
|
|
1873
|
-
const logPath =
|
|
2662
|
+
const logPath = path10.join(path10.dirname(SOCKET_PATH), "exed.log");
|
|
1874
2663
|
let stderrFd = "ignore";
|
|
1875
2664
|
try {
|
|
1876
2665
|
stderrFd = openSync(logPath, "a");
|
|
@@ -1888,7 +2677,8 @@ function spawnDaemon() {
|
|
|
1888
2677
|
TMUX_PANE: void 0,
|
|
1889
2678
|
// Prevents resolveExeSession() from scoping to one session
|
|
1890
2679
|
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
1891
|
-
EXE_DAEMON_PID: PID_PATH
|
|
2680
|
+
EXE_DAEMON_PID: PID_PATH,
|
|
2681
|
+
[DAEMON_TOKEN_ENV]: daemonToken
|
|
1892
2682
|
}
|
|
1893
2683
|
});
|
|
1894
2684
|
child.unref();
|
|
@@ -1998,13 +2788,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
1998
2788
|
return;
|
|
1999
2789
|
}
|
|
2000
2790
|
const id = randomUUID3();
|
|
2791
|
+
const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
|
|
2001
2792
|
const timer = setTimeout(() => {
|
|
2002
2793
|
_pending.delete(id);
|
|
2003
2794
|
resolve({ error: "Request timeout" });
|
|
2004
2795
|
}, timeoutMs);
|
|
2005
2796
|
_pending.set(id, { resolve, timer });
|
|
2006
2797
|
try {
|
|
2007
|
-
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
2798
|
+
_socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
|
|
2008
2799
|
} catch {
|
|
2009
2800
|
clearTimeout(timer);
|
|
2010
2801
|
_pending.delete(id);
|
|
@@ -2021,74 +2812,123 @@ async function pingDaemon() {
|
|
|
2021
2812
|
return null;
|
|
2022
2813
|
}
|
|
2023
2814
|
function killAndRespawnDaemon() {
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2815
|
+
if (!acquireSpawnLock()) {
|
|
2816
|
+
process.stderr.write("[exed-client] Another process is already restarting daemon \u2014 skipping\n");
|
|
2817
|
+
if (_socket) {
|
|
2818
|
+
_socket.destroy();
|
|
2819
|
+
_socket = null;
|
|
2820
|
+
}
|
|
2821
|
+
_connected = false;
|
|
2822
|
+
_buffer = "";
|
|
2823
|
+
return;
|
|
2824
|
+
}
|
|
2825
|
+
try {
|
|
2826
|
+
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
2827
|
+
if (existsSync9(PID_PATH)) {
|
|
2828
|
+
try {
|
|
2829
|
+
const pid = parseInt(readFileSync6(PID_PATH, "utf8").trim(), 10);
|
|
2830
|
+
if (pid > 0) {
|
|
2831
|
+
try {
|
|
2832
|
+
process.kill(pid, "SIGKILL");
|
|
2833
|
+
} catch {
|
|
2834
|
+
}
|
|
2032
2835
|
}
|
|
2836
|
+
} catch {
|
|
2033
2837
|
}
|
|
2838
|
+
}
|
|
2839
|
+
if (_socket) {
|
|
2840
|
+
_socket.destroy();
|
|
2841
|
+
_socket = null;
|
|
2842
|
+
}
|
|
2843
|
+
_connected = false;
|
|
2844
|
+
_buffer = "";
|
|
2845
|
+
try {
|
|
2846
|
+
unlinkSync2(PID_PATH);
|
|
2034
2847
|
} catch {
|
|
2035
2848
|
}
|
|
2849
|
+
try {
|
|
2850
|
+
unlinkSync2(SOCKET_PATH);
|
|
2851
|
+
} catch {
|
|
2852
|
+
}
|
|
2853
|
+
spawnDaemon();
|
|
2854
|
+
} finally {
|
|
2855
|
+
releaseSpawnLock();
|
|
2036
2856
|
}
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
_socket = null;
|
|
2040
|
-
}
|
|
2041
|
-
_connected = false;
|
|
2042
|
-
_buffer = "";
|
|
2857
|
+
}
|
|
2858
|
+
function isDaemonTooYoung() {
|
|
2043
2859
|
try {
|
|
2044
|
-
|
|
2860
|
+
const stat = statSync(PID_PATH);
|
|
2861
|
+
return Date.now() - stat.mtimeMs < MIN_DAEMON_AGE_MS;
|
|
2045
2862
|
} catch {
|
|
2863
|
+
return false;
|
|
2046
2864
|
}
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2865
|
+
}
|
|
2866
|
+
async function retryThenRestart(doRequest, label) {
|
|
2867
|
+
const result = await doRequest();
|
|
2868
|
+
if (!result.error) {
|
|
2869
|
+
_consecutiveFailures = 0;
|
|
2870
|
+
return result;
|
|
2050
2871
|
}
|
|
2051
|
-
|
|
2872
|
+
_consecutiveFailures++;
|
|
2873
|
+
for (let i = 0; i < MAX_RETRIES_BEFORE_RESTART; i++) {
|
|
2874
|
+
const delayMs = RETRY_DELAYS_MS[i] ?? 5e3;
|
|
2875
|
+
process.stderr.write(`[exed-client] ${label} failed (${result.error}), retry ${i + 1}/${MAX_RETRIES_BEFORE_RESTART} in ${delayMs}ms
|
|
2876
|
+
`);
|
|
2877
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
2878
|
+
if (!_connected) {
|
|
2879
|
+
if (!await connectToSocket()) continue;
|
|
2880
|
+
}
|
|
2881
|
+
const retry = await doRequest();
|
|
2882
|
+
if (!retry.error) {
|
|
2883
|
+
_consecutiveFailures = 0;
|
|
2884
|
+
return retry;
|
|
2885
|
+
}
|
|
2886
|
+
_consecutiveFailures++;
|
|
2887
|
+
}
|
|
2888
|
+
if (isDaemonTooYoung()) {
|
|
2889
|
+
process.stderr.write(`[exed-client] ${label}: daemon too young (< ${MIN_DAEMON_AGE_MS / 1e3}s) \u2014 skipping restart
|
|
2890
|
+
`);
|
|
2891
|
+
return { error: result.error };
|
|
2892
|
+
}
|
|
2893
|
+
process.stderr.write(`[exed-client] ${label}: ${_consecutiveFailures} consecutive failures \u2014 restarting daemon
|
|
2894
|
+
`);
|
|
2895
|
+
killAndRespawnDaemon();
|
|
2896
|
+
const start = Date.now();
|
|
2897
|
+
let delay2 = 200;
|
|
2898
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
2899
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
2900
|
+
if (await connectToSocket()) break;
|
|
2901
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
2902
|
+
}
|
|
2903
|
+
if (!_connected) return { error: "Daemon restart failed" };
|
|
2904
|
+
const final = await doRequest();
|
|
2905
|
+
if (!final.error) _consecutiveFailures = 0;
|
|
2906
|
+
return final;
|
|
2052
2907
|
}
|
|
2053
2908
|
async function embedViaClient(text, priority = "high") {
|
|
2054
2909
|
if (!_connected && !await connectEmbedDaemon()) return null;
|
|
2055
2910
|
_requestCount++;
|
|
2056
2911
|
if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
|
|
2057
2912
|
const health = await pingDaemon();
|
|
2058
|
-
if (!health) {
|
|
2913
|
+
if (!health && !isDaemonTooYoung()) {
|
|
2059
2914
|
process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
|
|
2060
2915
|
`);
|
|
2061
2916
|
killAndRespawnDaemon();
|
|
2062
2917
|
const start = Date.now();
|
|
2063
|
-
let
|
|
2918
|
+
let d = 200;
|
|
2064
2919
|
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
2065
|
-
await new Promise((r) => setTimeout(r,
|
|
2920
|
+
await new Promise((r) => setTimeout(r, d));
|
|
2066
2921
|
if (await connectToSocket()) break;
|
|
2067
|
-
|
|
2922
|
+
d = Math.min(d * 2, 3e3);
|
|
2068
2923
|
}
|
|
2069
2924
|
if (!_connected) return null;
|
|
2070
2925
|
}
|
|
2071
2926
|
}
|
|
2072
|
-
const result = await
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
killAndRespawnDaemon();
|
|
2078
|
-
const start = Date.now();
|
|
2079
|
-
let delay2 = 200;
|
|
2080
|
-
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
2081
|
-
await new Promise((r) => setTimeout(r, delay2));
|
|
2082
|
-
if (await connectToSocket()) break;
|
|
2083
|
-
delay2 = Math.min(delay2 * 2, 3e3);
|
|
2084
|
-
}
|
|
2085
|
-
if (!_connected) return null;
|
|
2086
|
-
const retry = await sendRequest([text], priority);
|
|
2087
|
-
if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
|
|
2088
|
-
process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
|
|
2089
|
-
`);
|
|
2090
|
-
}
|
|
2091
|
-
return null;
|
|
2927
|
+
const result = await retryThenRestart(
|
|
2928
|
+
() => sendRequest([text], priority),
|
|
2929
|
+
"Embed"
|
|
2930
|
+
);
|
|
2931
|
+
return !result.error && result.vectors?.[0] ? result.vectors[0] : null;
|
|
2092
2932
|
}
|
|
2093
2933
|
function disconnectClient() {
|
|
2094
2934
|
if (_socket) {
|
|
@@ -2103,22 +2943,28 @@ function disconnectClient() {
|
|
|
2103
2943
|
entry.resolve({ error: "Client disconnected" });
|
|
2104
2944
|
}
|
|
2105
2945
|
}
|
|
2106
|
-
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, HEALTH_CHECK_INTERVAL, _pending, MAX_BUFFER;
|
|
2946
|
+
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, DAEMON_TOKEN_ENV, _socket, _connected, _buffer, _requestCount, _consecutiveFailures, HEALTH_CHECK_INTERVAL, MAX_RETRIES_BEFORE_RESTART, RETRY_DELAYS_MS, MIN_DAEMON_AGE_MS, _pending, MAX_BUFFER;
|
|
2107
2947
|
var init_exe_daemon_client = __esm({
|
|
2108
2948
|
"src/lib/exe-daemon-client.ts"() {
|
|
2109
2949
|
"use strict";
|
|
2110
2950
|
init_config();
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2951
|
+
init_daemon_auth();
|
|
2952
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path10.join(EXE_AI_DIR, "exed.sock");
|
|
2953
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path10.join(EXE_AI_DIR, "exed.pid");
|
|
2954
|
+
SPAWN_LOCK_PATH = path10.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
2114
2955
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
2115
2956
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
2116
2957
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
2958
|
+
DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
|
|
2117
2959
|
_socket = null;
|
|
2118
2960
|
_connected = false;
|
|
2119
2961
|
_buffer = "";
|
|
2120
2962
|
_requestCount = 0;
|
|
2963
|
+
_consecutiveFailures = 0;
|
|
2121
2964
|
HEALTH_CHECK_INTERVAL = 100;
|
|
2965
|
+
MAX_RETRIES_BEFORE_RESTART = 3;
|
|
2966
|
+
RETRY_DELAYS_MS = [1e3, 3e3, 5e3];
|
|
2967
|
+
MIN_DAEMON_AGE_MS = 3e4;
|
|
2122
2968
|
_pending = /* @__PURE__ */ new Map();
|
|
2123
2969
|
MAX_BUFFER = 1e7;
|
|
2124
2970
|
}
|
|
@@ -2161,10 +3007,10 @@ async function disposeEmbedder() {
|
|
|
2161
3007
|
async function embedDirect(text) {
|
|
2162
3008
|
const llamaCpp = await import("node-llama-cpp");
|
|
2163
3009
|
const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
2164
|
-
const { existsSync:
|
|
2165
|
-
const
|
|
2166
|
-
const modelPath =
|
|
2167
|
-
if (!
|
|
3010
|
+
const { existsSync: existsSync10 } = await import("fs");
|
|
3011
|
+
const path12 = await import("path");
|
|
3012
|
+
const modelPath = path12.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
|
|
3013
|
+
if (!existsSync10(modelPath)) {
|
|
2168
3014
|
throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
|
|
2169
3015
|
}
|
|
2170
3016
|
const llama = await llamaCpp.getLlama();
|
|
@@ -2193,9 +3039,9 @@ var init_embedder = __esm({
|
|
|
2193
3039
|
});
|
|
2194
3040
|
|
|
2195
3041
|
// src/adapters/claude/hooks/prompt-ingest-worker.ts
|
|
2196
|
-
import
|
|
2197
|
-
import { writeFileSync as
|
|
2198
|
-
import
|
|
3042
|
+
import crypto2 from "crypto";
|
|
3043
|
+
import { writeFileSync as writeFileSync4 } from "fs";
|
|
3044
|
+
import path11 from "path";
|
|
2199
3045
|
|
|
2200
3046
|
// src/lib/project-name.ts
|
|
2201
3047
|
import { execSync } from "child_process";
|
|
@@ -2240,16 +3086,16 @@ import { createHash } from "crypto";
|
|
|
2240
3086
|
|
|
2241
3087
|
// src/lib/keychain.ts
|
|
2242
3088
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
2243
|
-
import { existsSync as
|
|
2244
|
-
import
|
|
2245
|
-
import
|
|
3089
|
+
import { existsSync as existsSync4 } from "fs";
|
|
3090
|
+
import path5 from "path";
|
|
3091
|
+
import os4 from "os";
|
|
2246
3092
|
var SERVICE = "exe-mem";
|
|
2247
3093
|
var ACCOUNT = "master-key";
|
|
2248
3094
|
function getKeyDir() {
|
|
2249
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
3095
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path5.join(os4.homedir(), ".exe-os");
|
|
2250
3096
|
}
|
|
2251
3097
|
function getKeyPath() {
|
|
2252
|
-
return
|
|
3098
|
+
return path5.join(getKeyDir(), "master.key");
|
|
2253
3099
|
}
|
|
2254
3100
|
async function tryKeytar() {
|
|
2255
3101
|
try {
|
|
@@ -2270,9 +3116,9 @@ async function getMasterKey() {
|
|
|
2270
3116
|
}
|
|
2271
3117
|
}
|
|
2272
3118
|
const keyPath = getKeyPath();
|
|
2273
|
-
if (!
|
|
3119
|
+
if (!existsSync4(keyPath)) {
|
|
2274
3120
|
process.stderr.write(
|
|
2275
|
-
`[keychain] Key not found at ${keyPath} (HOME=${
|
|
3121
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os4.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
2276
3122
|
`
|
|
2277
3123
|
);
|
|
2278
3124
|
return null;
|
|
@@ -2696,18 +3542,21 @@ function vectorToBlob(vector) {
|
|
|
2696
3542
|
// src/lib/plan-limits.ts
|
|
2697
3543
|
init_database();
|
|
2698
3544
|
init_employees();
|
|
2699
|
-
import { readFileSync as readFileSync4, existsSync as
|
|
2700
|
-
import
|
|
3545
|
+
import { readFileSync as readFileSync4, existsSync as existsSync7 } from "fs";
|
|
3546
|
+
import path8 from "path";
|
|
2701
3547
|
|
|
2702
3548
|
// src/lib/license.ts
|
|
2703
3549
|
init_config();
|
|
2704
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as
|
|
3550
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
|
|
2705
3551
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
2706
|
-
import
|
|
3552
|
+
import { createRequire as createRequire2 } from "module";
|
|
3553
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
3554
|
+
import os5 from "os";
|
|
3555
|
+
import path7 from "path";
|
|
2707
3556
|
import { jwtVerify, importSPKI } from "jose";
|
|
2708
|
-
var LICENSE_PATH =
|
|
2709
|
-
var CACHE_PATH =
|
|
2710
|
-
var DEVICE_ID_PATH =
|
|
3557
|
+
var LICENSE_PATH = path7.join(EXE_AI_DIR, "license.key");
|
|
3558
|
+
var CACHE_PATH = path7.join(EXE_AI_DIR, "license-cache.json");
|
|
3559
|
+
var DEVICE_ID_PATH = path7.join(EXE_AI_DIR, "device-id");
|
|
2711
3560
|
var API_BASE = "https://askexe.com/cloud";
|
|
2712
3561
|
var RETRY_DELAY_MS = 500;
|
|
2713
3562
|
async function fetchRetry(url, init) {
|
|
@@ -2740,36 +3589,36 @@ var FREE_LICENSE = {
|
|
|
2740
3589
|
memoryLimit: 5e3
|
|
2741
3590
|
};
|
|
2742
3591
|
function loadDeviceId() {
|
|
2743
|
-
const deviceJsonPath =
|
|
3592
|
+
const deviceJsonPath = path7.join(EXE_AI_DIR, "device.json");
|
|
2744
3593
|
try {
|
|
2745
|
-
if (
|
|
3594
|
+
if (existsSync6(deviceJsonPath)) {
|
|
2746
3595
|
const data = JSON.parse(readFileSync3(deviceJsonPath, "utf8"));
|
|
2747
3596
|
if (data.deviceId) return data.deviceId;
|
|
2748
3597
|
}
|
|
2749
3598
|
} catch {
|
|
2750
3599
|
}
|
|
2751
3600
|
try {
|
|
2752
|
-
if (
|
|
3601
|
+
if (existsSync6(DEVICE_ID_PATH)) {
|
|
2753
3602
|
const id2 = readFileSync3(DEVICE_ID_PATH, "utf8").trim();
|
|
2754
3603
|
if (id2) return id2;
|
|
2755
3604
|
}
|
|
2756
3605
|
} catch {
|
|
2757
3606
|
}
|
|
2758
3607
|
const id = randomUUID2();
|
|
2759
|
-
|
|
3608
|
+
mkdirSync3(EXE_AI_DIR, { recursive: true });
|
|
2760
3609
|
writeFileSync2(DEVICE_ID_PATH, id, "utf8");
|
|
2761
3610
|
return id;
|
|
2762
3611
|
}
|
|
2763
3612
|
function loadLicense() {
|
|
2764
3613
|
try {
|
|
2765
|
-
if (!
|
|
3614
|
+
if (!existsSync6(LICENSE_PATH)) return null;
|
|
2766
3615
|
return readFileSync3(LICENSE_PATH, "utf8").trim();
|
|
2767
3616
|
} catch {
|
|
2768
3617
|
return null;
|
|
2769
3618
|
}
|
|
2770
3619
|
}
|
|
2771
3620
|
function saveLicense(apiKey) {
|
|
2772
|
-
|
|
3621
|
+
mkdirSync3(EXE_AI_DIR, { recursive: true });
|
|
2773
3622
|
writeFileSync2(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
|
|
2774
3623
|
}
|
|
2775
3624
|
async function verifyLicenseJwt(token) {
|
|
@@ -2796,7 +3645,7 @@ async function verifyLicenseJwt(token) {
|
|
|
2796
3645
|
}
|
|
2797
3646
|
async function getCachedLicense() {
|
|
2798
3647
|
try {
|
|
2799
|
-
if (!
|
|
3648
|
+
if (!existsSync6(CACHE_PATH)) return null;
|
|
2800
3649
|
const raw = JSON.parse(readFileSync3(CACHE_PATH, "utf8"));
|
|
2801
3650
|
if (!raw.token || typeof raw.token !== "string") return null;
|
|
2802
3651
|
return await verifyLicenseJwt(raw.token);
|
|
@@ -2806,7 +3655,7 @@ async function getCachedLicense() {
|
|
|
2806
3655
|
}
|
|
2807
3656
|
function readCachedToken() {
|
|
2808
3657
|
try {
|
|
2809
|
-
if (!
|
|
3658
|
+
if (!existsSync6(CACHE_PATH)) return null;
|
|
2810
3659
|
const raw = JSON.parse(readFileSync3(CACHE_PATH, "utf8"));
|
|
2811
3660
|
return typeof raw.token === "string" ? raw.token : null;
|
|
2812
3661
|
} catch {
|
|
@@ -2845,52 +3694,128 @@ function cacheResponse(token) {
|
|
|
2845
3694
|
} catch {
|
|
2846
3695
|
}
|
|
2847
3696
|
}
|
|
2848
|
-
|
|
2849
|
-
|
|
3697
|
+
var _prismaPromise = null;
|
|
3698
|
+
var _prismaFailed = false;
|
|
3699
|
+
function loadPrismaForLicense() {
|
|
3700
|
+
if (_prismaFailed) return null;
|
|
3701
|
+
const dbUrl = process.env.DATABASE_URL;
|
|
3702
|
+
if (!dbUrl) {
|
|
3703
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path7.join(os5.homedir(), "exe-db");
|
|
3704
|
+
if (!existsSync6(path7.join(exeDbRoot, "package.json"))) {
|
|
3705
|
+
_prismaFailed = true;
|
|
3706
|
+
return null;
|
|
3707
|
+
}
|
|
3708
|
+
}
|
|
3709
|
+
if (!_prismaPromise) {
|
|
3710
|
+
_prismaPromise = (async () => {
|
|
3711
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
3712
|
+
if (explicitPath) {
|
|
3713
|
+
const mod2 = await import(pathToFileURL2(explicitPath).href);
|
|
3714
|
+
const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
|
|
3715
|
+
if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
|
|
3716
|
+
return new Ctor2();
|
|
3717
|
+
}
|
|
3718
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path7.join(os5.homedir(), "exe-db");
|
|
3719
|
+
const req = createRequire2(path7.join(exeDbRoot, "package.json"));
|
|
3720
|
+
const entry = req.resolve("@prisma/client");
|
|
3721
|
+
const mod = await import(pathToFileURL2(entry).href);
|
|
3722
|
+
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
3723
|
+
if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
|
|
3724
|
+
return new Ctor();
|
|
3725
|
+
})().catch((err) => {
|
|
3726
|
+
_prismaFailed = true;
|
|
3727
|
+
_prismaPromise = null;
|
|
3728
|
+
throw err;
|
|
3729
|
+
});
|
|
3730
|
+
}
|
|
3731
|
+
return _prismaPromise;
|
|
3732
|
+
}
|
|
3733
|
+
async function validateViaPostgres(apiKey) {
|
|
3734
|
+
const loader = loadPrismaForLicense();
|
|
3735
|
+
if (!loader) return null;
|
|
3736
|
+
try {
|
|
3737
|
+
const prisma = await loader;
|
|
3738
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
3739
|
+
`SELECT plan, email, status, device_limit, employee_limit, memory_limit, expires_at
|
|
3740
|
+
FROM billing.licenses WHERE key = $1 LIMIT 1`,
|
|
3741
|
+
apiKey
|
|
3742
|
+
);
|
|
3743
|
+
if (!rows || rows.length === 0) return null;
|
|
3744
|
+
const row = rows[0];
|
|
3745
|
+
if (row.status !== "active") return null;
|
|
3746
|
+
if (row.expires_at && new Date(row.expires_at) < /* @__PURE__ */ new Date()) return null;
|
|
3747
|
+
const plan = row.plan;
|
|
3748
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
3749
|
+
return {
|
|
3750
|
+
valid: true,
|
|
3751
|
+
plan,
|
|
3752
|
+
email: row.email,
|
|
3753
|
+
expiresAt: row.expires_at ? new Date(row.expires_at).toISOString() : null,
|
|
3754
|
+
deviceLimit: row.device_limit ?? limits.devices,
|
|
3755
|
+
employeeLimit: row.employee_limit ?? limits.employees,
|
|
3756
|
+
memoryLimit: row.memory_limit ?? limits.memories
|
|
3757
|
+
};
|
|
3758
|
+
} catch {
|
|
3759
|
+
return null;
|
|
3760
|
+
}
|
|
3761
|
+
}
|
|
3762
|
+
async function validateViaCFWorker(apiKey, deviceId) {
|
|
2850
3763
|
try {
|
|
2851
3764
|
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
2852
3765
|
method: "POST",
|
|
2853
3766
|
headers: { "Content-Type": "application/json" },
|
|
2854
|
-
body: JSON.stringify({ apiKey, deviceId
|
|
3767
|
+
body: JSON.stringify({ apiKey, deviceId }),
|
|
2855
3768
|
signal: AbortSignal.timeout(1e4)
|
|
2856
3769
|
});
|
|
2857
|
-
if (res.ok)
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
3770
|
+
if (!res.ok) return null;
|
|
3771
|
+
const data = await res.json();
|
|
3772
|
+
if (data.error === "device_limit_exceeded") return null;
|
|
3773
|
+
if (!data.valid) return null;
|
|
3774
|
+
if (data.token) {
|
|
3775
|
+
cacheResponse(data.token);
|
|
3776
|
+
const verified = await verifyLicenseJwt(data.token);
|
|
3777
|
+
if (verified) return verified;
|
|
3778
|
+
}
|
|
3779
|
+
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
3780
|
+
return {
|
|
3781
|
+
valid: data.valid,
|
|
3782
|
+
plan: data.plan,
|
|
3783
|
+
email: data.email,
|
|
3784
|
+
expiresAt: data.expiresAt,
|
|
3785
|
+
deviceLimit: limits.devices,
|
|
3786
|
+
employeeLimit: limits.employees,
|
|
3787
|
+
memoryLimit: limits.memories
|
|
3788
|
+
};
|
|
3789
|
+
} catch {
|
|
3790
|
+
return null;
|
|
3791
|
+
}
|
|
3792
|
+
}
|
|
3793
|
+
async function validateLicense(apiKey, deviceId) {
|
|
3794
|
+
const did = deviceId ?? loadDeviceId();
|
|
3795
|
+
const pgResult = await validateViaPostgres(apiKey);
|
|
3796
|
+
if (pgResult) {
|
|
3797
|
+
try {
|
|
3798
|
+
writeFileSync2(CACHE_PATH, JSON.stringify({ pgLicense: pgResult, ts: Date.now() }), "utf8");
|
|
3799
|
+
} catch {
|
|
3800
|
+
}
|
|
3801
|
+
return pgResult;
|
|
3802
|
+
}
|
|
3803
|
+
const cfResult = await validateViaCFWorker(apiKey, did);
|
|
3804
|
+
if (cfResult) return cfResult;
|
|
3805
|
+
const cached = await getCachedLicense();
|
|
3806
|
+
if (cached) return cached;
|
|
3807
|
+
try {
|
|
3808
|
+
if (existsSync6(CACHE_PATH)) {
|
|
3809
|
+
const raw = JSON.parse(readFileSync3(CACHE_PATH, "utf8"));
|
|
3810
|
+
if (raw.pgLicense && raw.ts && Date.now() - raw.ts < 7 * 24 * 60 * 60 * 1e3) {
|
|
3811
|
+
return raw.pgLicense;
|
|
2870
3812
|
}
|
|
2871
|
-
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
2872
|
-
return {
|
|
2873
|
-
valid: data.valid,
|
|
2874
|
-
plan: data.plan,
|
|
2875
|
-
email: data.email,
|
|
2876
|
-
expiresAt: data.expiresAt,
|
|
2877
|
-
deviceLimit: limits.devices,
|
|
2878
|
-
employeeLimit: limits.employees,
|
|
2879
|
-
memoryLimit: limits.memories
|
|
2880
|
-
};
|
|
2881
3813
|
}
|
|
2882
|
-
const cached = await getCachedLicense();
|
|
2883
|
-
if (cached) return cached;
|
|
2884
|
-
const raw = getRawCachedPlan();
|
|
2885
|
-
if (raw) return raw;
|
|
2886
|
-
return { ...FREE_LICENSE, valid: false, plan: "free" };
|
|
2887
3814
|
} catch {
|
|
2888
|
-
const cached = await getCachedLicense();
|
|
2889
|
-
if (cached) return cached;
|
|
2890
|
-
const rawFallback = getRawCachedPlan();
|
|
2891
|
-
if (rawFallback) return rawFallback;
|
|
2892
|
-
return { ...FREE_LICENSE, valid: false, error: "offline" };
|
|
2893
3815
|
}
|
|
3816
|
+
const rawFallback = getRawCachedPlan();
|
|
3817
|
+
if (rawFallback) return rawFallback;
|
|
3818
|
+
return { ...FREE_LICENSE, valid: false };
|
|
2894
3819
|
}
|
|
2895
3820
|
var CACHE_MAX_AGE_MS = 36e5;
|
|
2896
3821
|
function getCacheAgeMs() {
|
|
@@ -2906,8 +3831,8 @@ async function checkLicense() {
|
|
|
2906
3831
|
let key = loadLicense();
|
|
2907
3832
|
if (!key) {
|
|
2908
3833
|
try {
|
|
2909
|
-
const configPath =
|
|
2910
|
-
if (
|
|
3834
|
+
const configPath = path7.join(EXE_AI_DIR, "config.json");
|
|
3835
|
+
if (existsSync6(configPath)) {
|
|
2911
3836
|
const raw = JSON.parse(readFileSync3(configPath, "utf8"));
|
|
2912
3837
|
const cloud = raw.cloud;
|
|
2913
3838
|
if (cloud?.apiKey) {
|
|
@@ -2933,7 +3858,7 @@ var PlanLimitError = class extends Error {
|
|
|
2933
3858
|
this.name = "PlanLimitError";
|
|
2934
3859
|
}
|
|
2935
3860
|
};
|
|
2936
|
-
var CACHE_PATH2 =
|
|
3861
|
+
var CACHE_PATH2 = path8.join(EXE_AI_DIR, "license-cache.json");
|
|
2937
3862
|
async function countActiveMemories() {
|
|
2938
3863
|
if (!isInitialized()) return 0;
|
|
2939
3864
|
const client = getClient();
|
|
@@ -2980,7 +3905,7 @@ async function main() {
|
|
|
2980
3905
|
await assertMemoryLimit();
|
|
2981
3906
|
const confidence = agentId === "default" ? 0.9 : 0.7;
|
|
2982
3907
|
await writeMemory({
|
|
2983
|
-
id:
|
|
3908
|
+
id: crypto2.randomUUID(),
|
|
2984
3909
|
agent_id: agentId,
|
|
2985
3910
|
agent_role: agentRole,
|
|
2986
3911
|
session_id: sessionId,
|
|
@@ -2996,8 +3921,8 @@ async function main() {
|
|
|
2996
3921
|
if (needsBackfill) {
|
|
2997
3922
|
try {
|
|
2998
3923
|
const { EXE_AI_DIR: exeDir } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
2999
|
-
const flagPath =
|
|
3000
|
-
|
|
3924
|
+
const flagPath = path11.join(exeDir, "session-cache", "needs-backfill");
|
|
3925
|
+
writeFileSync4(flagPath, "1");
|
|
3001
3926
|
} catch (err) {
|
|
3002
3927
|
process.stderr.write(`[prompt-ingest-worker] backfill flag write failed: ${err instanceof Error ? err.message : String(err)}
|
|
3003
3928
|
`);
|