@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,7 +375,7 @@ function getCoordinatorName(employees = loadEmployeesSync()) {
|
|
|
337
375
|
return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
|
|
338
376
|
}
|
|
339
377
|
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
340
|
-
if (!
|
|
378
|
+
if (!existsSync3(employeesPath)) {
|
|
341
379
|
return [];
|
|
342
380
|
}
|
|
343
381
|
const raw = await readFile2(employeesPath, "utf-8");
|
|
@@ -348,14 +386,14 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
|
348
386
|
}
|
|
349
387
|
}
|
|
350
388
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
351
|
-
if (!
|
|
389
|
+
if (!existsSync3(employeesPath)) return [];
|
|
352
390
|
try {
|
|
353
391
|
return JSON.parse(readFileSync2(employeesPath, "utf-8"));
|
|
354
392
|
} catch {
|
|
355
393
|
return [];
|
|
356
394
|
}
|
|
357
395
|
}
|
|
358
|
-
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE;
|
|
396
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, IDENTITY_DIR;
|
|
359
397
|
var init_employees = __esm({
|
|
360
398
|
"src/lib/employees.ts"() {
|
|
361
399
|
"use strict";
|
|
@@ -363,12 +401,609 @@ var init_employees = __esm({
|
|
|
363
401
|
EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
|
|
364
402
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
365
403
|
COORDINATOR_ROLE = "COO";
|
|
404
|
+
IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
// src/lib/database-adapter.ts
|
|
409
|
+
import os3 from "os";
|
|
410
|
+
import path4 from "path";
|
|
411
|
+
import { createRequire } from "module";
|
|
412
|
+
import { pathToFileURL } from "url";
|
|
413
|
+
function quotedIdentifier(identifier) {
|
|
414
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
415
|
+
}
|
|
416
|
+
function unqualifiedTableName(name) {
|
|
417
|
+
const raw = name.trim().replace(/^"|"$/g, "");
|
|
418
|
+
const parts = raw.split(".");
|
|
419
|
+
return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
|
|
420
|
+
}
|
|
421
|
+
function stripTrailingSemicolon(sql) {
|
|
422
|
+
return sql.trim().replace(/;+\s*$/u, "");
|
|
423
|
+
}
|
|
424
|
+
function appendClause(sql, clause) {
|
|
425
|
+
const trimmed = stripTrailingSemicolon(sql);
|
|
426
|
+
const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
|
|
427
|
+
if (!returningMatch) {
|
|
428
|
+
return `${trimmed}${clause}`;
|
|
429
|
+
}
|
|
430
|
+
const idx = returningMatch.index;
|
|
431
|
+
return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
|
|
432
|
+
}
|
|
433
|
+
function normalizeStatement(stmt) {
|
|
434
|
+
if (typeof stmt === "string") {
|
|
435
|
+
return { kind: "positional", sql: stmt, args: [] };
|
|
436
|
+
}
|
|
437
|
+
const sql = stmt.sql;
|
|
438
|
+
if (Array.isArray(stmt.args) || stmt.args === void 0) {
|
|
439
|
+
return { kind: "positional", sql, args: stmt.args ?? [] };
|
|
440
|
+
}
|
|
441
|
+
return { kind: "named", sql, args: stmt.args };
|
|
442
|
+
}
|
|
443
|
+
function rewriteBooleanLiterals(sql) {
|
|
444
|
+
let out = sql;
|
|
445
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
446
|
+
const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
|
|
447
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
|
|
448
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
|
|
449
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
|
|
450
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
|
|
451
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
|
|
452
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
|
|
453
|
+
}
|
|
454
|
+
return out;
|
|
455
|
+
}
|
|
456
|
+
function rewriteInsertOrIgnore(sql) {
|
|
457
|
+
if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
|
|
458
|
+
return sql;
|
|
459
|
+
}
|
|
460
|
+
const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
|
|
461
|
+
return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
|
|
462
|
+
}
|
|
463
|
+
function rewriteInsertOrReplace(sql) {
|
|
464
|
+
const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
|
|
465
|
+
if (!match) {
|
|
466
|
+
return sql;
|
|
467
|
+
}
|
|
468
|
+
const rawTable = match[1];
|
|
469
|
+
const rawColumns = match[2];
|
|
470
|
+
const remainder = match[3];
|
|
471
|
+
const tableName = unqualifiedTableName(rawTable);
|
|
472
|
+
const conflictKeys = UPSERT_KEYS[tableName];
|
|
473
|
+
if (!conflictKeys?.length) {
|
|
474
|
+
return sql;
|
|
475
|
+
}
|
|
476
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
477
|
+
const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
|
|
478
|
+
const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
|
|
479
|
+
const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
|
|
480
|
+
return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
|
|
481
|
+
}
|
|
482
|
+
function rewriteSql(sql) {
|
|
483
|
+
let out = sql;
|
|
484
|
+
out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
|
|
485
|
+
out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
|
|
486
|
+
out = rewriteBooleanLiterals(out);
|
|
487
|
+
out = rewriteInsertOrReplace(out);
|
|
488
|
+
out = rewriteInsertOrIgnore(out);
|
|
489
|
+
return stripTrailingSemicolon(out);
|
|
490
|
+
}
|
|
491
|
+
function toBoolean(value) {
|
|
492
|
+
if (value === null || value === void 0) return value;
|
|
493
|
+
if (typeof value === "boolean") return value;
|
|
494
|
+
if (typeof value === "number") return value !== 0;
|
|
495
|
+
if (typeof value === "bigint") return value !== 0n;
|
|
496
|
+
if (typeof value === "string") {
|
|
497
|
+
const normalized = value.trim().toLowerCase();
|
|
498
|
+
if (normalized === "0" || normalized === "false") return false;
|
|
499
|
+
if (normalized === "1" || normalized === "true") return true;
|
|
500
|
+
}
|
|
501
|
+
return Boolean(value);
|
|
502
|
+
}
|
|
503
|
+
function countQuestionMarks(sql, end) {
|
|
504
|
+
let count = 0;
|
|
505
|
+
let inSingle = false;
|
|
506
|
+
let inDouble = false;
|
|
507
|
+
let inLineComment = false;
|
|
508
|
+
let inBlockComment = false;
|
|
509
|
+
for (let i = 0; i < end; i++) {
|
|
510
|
+
const ch = sql[i];
|
|
511
|
+
const next = sql[i + 1];
|
|
512
|
+
if (inLineComment) {
|
|
513
|
+
if (ch === "\n") inLineComment = false;
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
516
|
+
if (inBlockComment) {
|
|
517
|
+
if (ch === "*" && next === "/") {
|
|
518
|
+
inBlockComment = false;
|
|
519
|
+
i += 1;
|
|
520
|
+
}
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
524
|
+
inLineComment = true;
|
|
525
|
+
i += 1;
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
528
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
529
|
+
inBlockComment = true;
|
|
530
|
+
i += 1;
|
|
531
|
+
continue;
|
|
532
|
+
}
|
|
533
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
534
|
+
inSingle = !inSingle;
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
537
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
538
|
+
inDouble = !inDouble;
|
|
539
|
+
continue;
|
|
540
|
+
}
|
|
541
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
542
|
+
count += 1;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
return count;
|
|
546
|
+
}
|
|
547
|
+
function findBooleanPlaceholderIndexes(sql) {
|
|
548
|
+
const indexes = /* @__PURE__ */ new Set();
|
|
549
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
550
|
+
const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
|
|
551
|
+
for (const match of sql.matchAll(pattern)) {
|
|
552
|
+
const matchText = match[0];
|
|
553
|
+
const qIndex = match.index + matchText.lastIndexOf("?");
|
|
554
|
+
indexes.add(countQuestionMarks(sql, qIndex + 1));
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return indexes;
|
|
558
|
+
}
|
|
559
|
+
function coerceInsertBooleanArgs(sql, args) {
|
|
560
|
+
const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
|
|
561
|
+
if (!match) return;
|
|
562
|
+
const rawTable = match[1];
|
|
563
|
+
const rawColumns = match[2];
|
|
564
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
565
|
+
if (!boolColumns?.size) return;
|
|
566
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
567
|
+
for (const [index, column] of columns.entries()) {
|
|
568
|
+
if (boolColumns.has(column) && index < args.length) {
|
|
569
|
+
args[index] = toBoolean(args[index]);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
function coerceUpdateBooleanArgs(sql, args) {
|
|
574
|
+
const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
|
|
575
|
+
if (!match) return;
|
|
576
|
+
const rawTable = match[1];
|
|
577
|
+
const setClause = match[2];
|
|
578
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
579
|
+
if (!boolColumns?.size) return;
|
|
580
|
+
const assignments = setClause.split(",");
|
|
581
|
+
let placeholderIndex = 0;
|
|
582
|
+
for (const assignment of assignments) {
|
|
583
|
+
if (!assignment.includes("?")) continue;
|
|
584
|
+
placeholderIndex += 1;
|
|
585
|
+
const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
|
|
586
|
+
if (colMatch && boolColumns.has(colMatch[1])) {
|
|
587
|
+
args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
function coerceBooleanArgs(sql, args) {
|
|
592
|
+
const nextArgs = [...args];
|
|
593
|
+
coerceInsertBooleanArgs(sql, nextArgs);
|
|
594
|
+
coerceUpdateBooleanArgs(sql, nextArgs);
|
|
595
|
+
const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
|
|
596
|
+
for (const index of placeholderIndexes) {
|
|
597
|
+
if (index > 0 && index <= nextArgs.length) {
|
|
598
|
+
nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
return nextArgs;
|
|
602
|
+
}
|
|
603
|
+
function convertQuestionMarksToDollarParams(sql) {
|
|
604
|
+
let out = "";
|
|
605
|
+
let placeholder = 0;
|
|
606
|
+
let inSingle = false;
|
|
607
|
+
let inDouble = false;
|
|
608
|
+
let inLineComment = false;
|
|
609
|
+
let inBlockComment = false;
|
|
610
|
+
for (let i = 0; i < sql.length; i++) {
|
|
611
|
+
const ch = sql[i];
|
|
612
|
+
const next = sql[i + 1];
|
|
613
|
+
if (inLineComment) {
|
|
614
|
+
out += ch;
|
|
615
|
+
if (ch === "\n") inLineComment = false;
|
|
616
|
+
continue;
|
|
617
|
+
}
|
|
618
|
+
if (inBlockComment) {
|
|
619
|
+
out += ch;
|
|
620
|
+
if (ch === "*" && next === "/") {
|
|
621
|
+
out += next;
|
|
622
|
+
inBlockComment = false;
|
|
623
|
+
i += 1;
|
|
624
|
+
}
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
628
|
+
out += ch + next;
|
|
629
|
+
inLineComment = true;
|
|
630
|
+
i += 1;
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
634
|
+
out += ch + next;
|
|
635
|
+
inBlockComment = true;
|
|
636
|
+
i += 1;
|
|
637
|
+
continue;
|
|
638
|
+
}
|
|
639
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
640
|
+
inSingle = !inSingle;
|
|
641
|
+
out += ch;
|
|
642
|
+
continue;
|
|
643
|
+
}
|
|
644
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
645
|
+
inDouble = !inDouble;
|
|
646
|
+
out += ch;
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
649
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
650
|
+
placeholder += 1;
|
|
651
|
+
out += `$${placeholder}`;
|
|
652
|
+
continue;
|
|
653
|
+
}
|
|
654
|
+
out += ch;
|
|
655
|
+
}
|
|
656
|
+
return out;
|
|
657
|
+
}
|
|
658
|
+
function translateStatementForPostgres(stmt) {
|
|
659
|
+
const normalized = normalizeStatement(stmt);
|
|
660
|
+
if (normalized.kind === "named") {
|
|
661
|
+
throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
|
|
662
|
+
}
|
|
663
|
+
const rewrittenSql = rewriteSql(normalized.sql);
|
|
664
|
+
const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
|
|
665
|
+
return {
|
|
666
|
+
sql: convertQuestionMarksToDollarParams(rewrittenSql),
|
|
667
|
+
args: coercedArgs
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
function shouldBypassPostgres(stmt) {
|
|
671
|
+
const normalized = normalizeStatement(stmt);
|
|
672
|
+
if (normalized.kind === "named") {
|
|
673
|
+
return true;
|
|
674
|
+
}
|
|
675
|
+
return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
|
|
676
|
+
}
|
|
677
|
+
function shouldFallbackOnError(error) {
|
|
678
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
679
|
+
return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
|
|
680
|
+
}
|
|
681
|
+
function isReadQuery(sql) {
|
|
682
|
+
const trimmed = sql.trimStart();
|
|
683
|
+
return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
|
|
684
|
+
}
|
|
685
|
+
function buildRow(row, columns) {
|
|
686
|
+
const values = columns.map((column) => row[column]);
|
|
687
|
+
return Object.assign(values, row);
|
|
688
|
+
}
|
|
689
|
+
function buildResultSet(rows, rowsAffected = 0) {
|
|
690
|
+
const columns = rows[0] ? Object.keys(rows[0]) : [];
|
|
691
|
+
const resultRows = rows.map((row) => buildRow(row, columns));
|
|
692
|
+
return {
|
|
693
|
+
columns,
|
|
694
|
+
columnTypes: columns.map(() => ""),
|
|
695
|
+
rows: resultRows,
|
|
696
|
+
rowsAffected,
|
|
697
|
+
lastInsertRowid: void 0,
|
|
698
|
+
toJSON() {
|
|
699
|
+
return {
|
|
700
|
+
columns,
|
|
701
|
+
columnTypes: columns.map(() => ""),
|
|
702
|
+
rows,
|
|
703
|
+
rowsAffected,
|
|
704
|
+
lastInsertRowid: void 0
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
async function loadPrismaClient() {
|
|
710
|
+
if (!prismaClientPromise) {
|
|
711
|
+
prismaClientPromise = (async () => {
|
|
712
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
713
|
+
if (explicitPath) {
|
|
714
|
+
const module2 = await import(pathToFileURL(explicitPath).href);
|
|
715
|
+
const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
|
|
716
|
+
if (!PrismaClient2) {
|
|
717
|
+
throw new Error(`No PrismaClient export found at ${explicitPath}`);
|
|
718
|
+
}
|
|
719
|
+
return new PrismaClient2();
|
|
720
|
+
}
|
|
721
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path4.join(os3.homedir(), "exe-db");
|
|
722
|
+
const requireFromExeDb = createRequire(path4.join(exeDbRoot, "package.json"));
|
|
723
|
+
const prismaEntry = requireFromExeDb.resolve("@prisma/client");
|
|
724
|
+
const module = await import(pathToFileURL(prismaEntry).href);
|
|
725
|
+
const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
|
|
726
|
+
if (!PrismaClient) {
|
|
727
|
+
throw new Error(`No PrismaClient export found in ${prismaEntry}`);
|
|
728
|
+
}
|
|
729
|
+
return new PrismaClient();
|
|
730
|
+
})();
|
|
731
|
+
}
|
|
732
|
+
return prismaClientPromise;
|
|
733
|
+
}
|
|
734
|
+
async function ensureCompatibilityViews(prisma) {
|
|
735
|
+
if (!compatibilityBootstrapPromise) {
|
|
736
|
+
compatibilityBootstrapPromise = (async () => {
|
|
737
|
+
for (const mapping of VIEW_MAPPINGS) {
|
|
738
|
+
const relation = mapping.source.replace(/"/g, "");
|
|
739
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
740
|
+
"SELECT to_regclass($1) AS regclass",
|
|
741
|
+
relation
|
|
742
|
+
);
|
|
743
|
+
if (!rows[0]?.regclass) {
|
|
744
|
+
continue;
|
|
745
|
+
}
|
|
746
|
+
await prisma.$executeRawUnsafe(
|
|
747
|
+
`CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
|
|
748
|
+
);
|
|
749
|
+
}
|
|
750
|
+
})();
|
|
751
|
+
}
|
|
752
|
+
return compatibilityBootstrapPromise;
|
|
753
|
+
}
|
|
754
|
+
async function executeOnPrisma(executor, stmt) {
|
|
755
|
+
const translated = translateStatementForPostgres(stmt);
|
|
756
|
+
if (isReadQuery(translated.sql)) {
|
|
757
|
+
const rows = await executor.$queryRawUnsafe(
|
|
758
|
+
translated.sql,
|
|
759
|
+
...translated.args
|
|
760
|
+
);
|
|
761
|
+
return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
|
|
762
|
+
}
|
|
763
|
+
const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
|
|
764
|
+
return buildResultSet([], rowsAffected);
|
|
765
|
+
}
|
|
766
|
+
function splitSqlStatements(sql) {
|
|
767
|
+
const parts = [];
|
|
768
|
+
let current = "";
|
|
769
|
+
let inSingle = false;
|
|
770
|
+
let inDouble = false;
|
|
771
|
+
let inLineComment = false;
|
|
772
|
+
let inBlockComment = false;
|
|
773
|
+
for (let i = 0; i < sql.length; i++) {
|
|
774
|
+
const ch = sql[i];
|
|
775
|
+
const next = sql[i + 1];
|
|
776
|
+
if (inLineComment) {
|
|
777
|
+
current += ch;
|
|
778
|
+
if (ch === "\n") inLineComment = false;
|
|
779
|
+
continue;
|
|
780
|
+
}
|
|
781
|
+
if (inBlockComment) {
|
|
782
|
+
current += ch;
|
|
783
|
+
if (ch === "*" && next === "/") {
|
|
784
|
+
current += next;
|
|
785
|
+
inBlockComment = false;
|
|
786
|
+
i += 1;
|
|
787
|
+
}
|
|
788
|
+
continue;
|
|
789
|
+
}
|
|
790
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
791
|
+
current += ch + next;
|
|
792
|
+
inLineComment = true;
|
|
793
|
+
i += 1;
|
|
794
|
+
continue;
|
|
795
|
+
}
|
|
796
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
797
|
+
current += ch + next;
|
|
798
|
+
inBlockComment = true;
|
|
799
|
+
i += 1;
|
|
800
|
+
continue;
|
|
801
|
+
}
|
|
802
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
803
|
+
inSingle = !inSingle;
|
|
804
|
+
current += ch;
|
|
805
|
+
continue;
|
|
806
|
+
}
|
|
807
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
808
|
+
inDouble = !inDouble;
|
|
809
|
+
current += ch;
|
|
810
|
+
continue;
|
|
811
|
+
}
|
|
812
|
+
if (!inSingle && !inDouble && ch === ";") {
|
|
813
|
+
if (current.trim()) {
|
|
814
|
+
parts.push(current.trim());
|
|
815
|
+
}
|
|
816
|
+
current = "";
|
|
817
|
+
continue;
|
|
818
|
+
}
|
|
819
|
+
current += ch;
|
|
820
|
+
}
|
|
821
|
+
if (current.trim()) {
|
|
822
|
+
parts.push(current.trim());
|
|
823
|
+
}
|
|
824
|
+
return parts;
|
|
825
|
+
}
|
|
826
|
+
async function createPrismaDbAdapter(fallbackClient) {
|
|
827
|
+
const prisma = await loadPrismaClient();
|
|
828
|
+
await ensureCompatibilityViews(prisma);
|
|
829
|
+
let closed = false;
|
|
830
|
+
let adapter;
|
|
831
|
+
const fallbackExecute = async (stmt, error) => {
|
|
832
|
+
if (!fallbackClient) {
|
|
833
|
+
if (error) throw error;
|
|
834
|
+
throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
|
|
835
|
+
}
|
|
836
|
+
if (error) {
|
|
837
|
+
process.stderr.write(
|
|
838
|
+
`[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
839
|
+
`
|
|
840
|
+
);
|
|
841
|
+
}
|
|
842
|
+
return fallbackClient.execute(stmt);
|
|
843
|
+
};
|
|
844
|
+
adapter = {
|
|
845
|
+
async execute(stmt) {
|
|
846
|
+
if (shouldBypassPostgres(stmt)) {
|
|
847
|
+
return fallbackExecute(stmt);
|
|
848
|
+
}
|
|
849
|
+
try {
|
|
850
|
+
return await executeOnPrisma(prisma, stmt);
|
|
851
|
+
} catch (error) {
|
|
852
|
+
if (shouldFallbackOnError(error)) {
|
|
853
|
+
return fallbackExecute(stmt, error);
|
|
854
|
+
}
|
|
855
|
+
throw error;
|
|
856
|
+
}
|
|
857
|
+
},
|
|
858
|
+
async batch(stmts, mode) {
|
|
859
|
+
if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
|
|
860
|
+
if (!fallbackClient) {
|
|
861
|
+
throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
|
|
862
|
+
}
|
|
863
|
+
return fallbackClient.batch(stmts, mode);
|
|
864
|
+
}
|
|
865
|
+
try {
|
|
866
|
+
if (prisma.$transaction) {
|
|
867
|
+
return await prisma.$transaction(async (tx) => {
|
|
868
|
+
const results2 = [];
|
|
869
|
+
for (const stmt of stmts) {
|
|
870
|
+
results2.push(await executeOnPrisma(tx, stmt));
|
|
871
|
+
}
|
|
872
|
+
return results2;
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
const results = [];
|
|
876
|
+
for (const stmt of stmts) {
|
|
877
|
+
results.push(await executeOnPrisma(prisma, stmt));
|
|
878
|
+
}
|
|
879
|
+
return results;
|
|
880
|
+
} catch (error) {
|
|
881
|
+
if (fallbackClient && shouldFallbackOnError(error)) {
|
|
882
|
+
process.stderr.write(
|
|
883
|
+
`[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
884
|
+
`
|
|
885
|
+
);
|
|
886
|
+
return fallbackClient.batch(stmts, mode);
|
|
887
|
+
}
|
|
888
|
+
throw error;
|
|
889
|
+
}
|
|
890
|
+
},
|
|
891
|
+
async migrate(stmts) {
|
|
892
|
+
if (fallbackClient) {
|
|
893
|
+
return fallbackClient.migrate(stmts);
|
|
894
|
+
}
|
|
895
|
+
return adapter.batch(stmts, "deferred");
|
|
896
|
+
},
|
|
897
|
+
async transaction(mode) {
|
|
898
|
+
if (!fallbackClient) {
|
|
899
|
+
throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
|
|
900
|
+
}
|
|
901
|
+
return fallbackClient.transaction(mode);
|
|
902
|
+
},
|
|
903
|
+
async executeMultiple(sql) {
|
|
904
|
+
if (fallbackClient && shouldBypassPostgres(sql)) {
|
|
905
|
+
return fallbackClient.executeMultiple(sql);
|
|
906
|
+
}
|
|
907
|
+
for (const statement of splitSqlStatements(sql)) {
|
|
908
|
+
await adapter.execute(statement);
|
|
909
|
+
}
|
|
910
|
+
},
|
|
911
|
+
async sync() {
|
|
912
|
+
if (fallbackClient) {
|
|
913
|
+
return fallbackClient.sync();
|
|
914
|
+
}
|
|
915
|
+
return { frame_no: 0, frames_synced: 0 };
|
|
916
|
+
},
|
|
917
|
+
close() {
|
|
918
|
+
closed = true;
|
|
919
|
+
prismaClientPromise = null;
|
|
920
|
+
compatibilityBootstrapPromise = null;
|
|
921
|
+
void prisma.$disconnect?.();
|
|
922
|
+
},
|
|
923
|
+
get closed() {
|
|
924
|
+
return closed;
|
|
925
|
+
},
|
|
926
|
+
get protocol() {
|
|
927
|
+
return "prisma-postgres";
|
|
928
|
+
}
|
|
929
|
+
};
|
|
930
|
+
return adapter;
|
|
931
|
+
}
|
|
932
|
+
var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
|
|
933
|
+
var init_database_adapter = __esm({
|
|
934
|
+
"src/lib/database-adapter.ts"() {
|
|
935
|
+
"use strict";
|
|
936
|
+
VIEW_MAPPINGS = [
|
|
937
|
+
{ view: "memories", source: "memory.memory_records" },
|
|
938
|
+
{ view: "tasks", source: "memory.tasks" },
|
|
939
|
+
{ view: "behaviors", source: "memory.behaviors" },
|
|
940
|
+
{ view: "entities", source: "memory.entities" },
|
|
941
|
+
{ view: "relationships", source: "memory.relationships" },
|
|
942
|
+
{ view: "entity_memories", source: "memory.entity_memories" },
|
|
943
|
+
{ view: "entity_aliases", source: "memory.entity_aliases" },
|
|
944
|
+
{ view: "notifications", source: "memory.notifications" },
|
|
945
|
+
{ view: "messages", source: "memory.messages" },
|
|
946
|
+
{ view: "users", source: "wiki.users" },
|
|
947
|
+
{ view: "workspaces", source: "wiki.workspaces" },
|
|
948
|
+
{ view: "workspace_users", source: "wiki.workspace_users" },
|
|
949
|
+
{ view: "documents", source: "wiki.workspace_documents" },
|
|
950
|
+
{ view: "chats", source: "wiki.workspace_chats" }
|
|
951
|
+
];
|
|
952
|
+
UPSERT_KEYS = {
|
|
953
|
+
memories: ["id"],
|
|
954
|
+
tasks: ["id"],
|
|
955
|
+
behaviors: ["id"],
|
|
956
|
+
entities: ["id"],
|
|
957
|
+
relationships: ["id"],
|
|
958
|
+
entity_aliases: ["alias"],
|
|
959
|
+
notifications: ["id"],
|
|
960
|
+
messages: ["id"],
|
|
961
|
+
users: ["id"],
|
|
962
|
+
workspaces: ["id"],
|
|
963
|
+
workspace_users: ["id"],
|
|
964
|
+
documents: ["id"],
|
|
965
|
+
chats: ["id"]
|
|
966
|
+
};
|
|
967
|
+
BOOLEAN_COLUMNS_BY_TABLE = {
|
|
968
|
+
memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
|
|
969
|
+
behaviors: /* @__PURE__ */ new Set(["active"]),
|
|
970
|
+
notifications: /* @__PURE__ */ new Set(["read"]),
|
|
971
|
+
users: /* @__PURE__ */ new Set(["has_personal_memory"])
|
|
972
|
+
};
|
|
973
|
+
BOOLEAN_COLUMN_NAMES = new Set(
|
|
974
|
+
Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
|
|
975
|
+
);
|
|
976
|
+
IMMEDIATE_FALLBACK_PATTERNS = [
|
|
977
|
+
/\bPRAGMA\b/i,
|
|
978
|
+
/\bsqlite_master\b/i,
|
|
979
|
+
/(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
|
|
980
|
+
/\bMATCH\b/i,
|
|
981
|
+
/\bvector_distance_cos\s*\(/i,
|
|
982
|
+
/\bjson_extract\s*\(/i,
|
|
983
|
+
/\bjulianday\s*\(/i,
|
|
984
|
+
/\bstrftime\s*\(/i,
|
|
985
|
+
/\blast_insert_rowid\s*\(/i
|
|
986
|
+
];
|
|
987
|
+
prismaClientPromise = null;
|
|
988
|
+
compatibilityBootstrapPromise = null;
|
|
366
989
|
}
|
|
367
990
|
});
|
|
368
991
|
|
|
369
992
|
// src/lib/database.ts
|
|
370
993
|
import { createClient } from "@libsql/client";
|
|
371
994
|
async function initDatabase(config) {
|
|
995
|
+
if (_walCheckpointTimer) {
|
|
996
|
+
clearInterval(_walCheckpointTimer);
|
|
997
|
+
_walCheckpointTimer = null;
|
|
998
|
+
}
|
|
999
|
+
if (_daemonClient) {
|
|
1000
|
+
_daemonClient.close();
|
|
1001
|
+
_daemonClient = null;
|
|
1002
|
+
}
|
|
1003
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
1004
|
+
_adapterClient.close();
|
|
1005
|
+
}
|
|
1006
|
+
_adapterClient = null;
|
|
372
1007
|
if (_client) {
|
|
373
1008
|
_client.close();
|
|
374
1009
|
_client = null;
|
|
@@ -382,6 +1017,7 @@ async function initDatabase(config) {
|
|
|
382
1017
|
}
|
|
383
1018
|
_client = createClient(opts);
|
|
384
1019
|
_resilientClient = wrapWithRetry(_client);
|
|
1020
|
+
_adapterClient = _resilientClient;
|
|
385
1021
|
_client.execute("PRAGMA busy_timeout = 30000").catch(() => {
|
|
386
1022
|
});
|
|
387
1023
|
_client.execute("PRAGMA journal_mode = WAL").catch(() => {
|
|
@@ -392,14 +1028,20 @@ async function initDatabase(config) {
|
|
|
392
1028
|
});
|
|
393
1029
|
}, 3e4);
|
|
394
1030
|
_walCheckpointTimer.unref();
|
|
1031
|
+
if (process.env.DATABASE_URL) {
|
|
1032
|
+
_adapterClient = await createPrismaDbAdapter(_resilientClient);
|
|
1033
|
+
}
|
|
395
1034
|
}
|
|
396
1035
|
function isInitialized() {
|
|
397
|
-
return _client !== null;
|
|
1036
|
+
return _adapterClient !== null || _client !== null;
|
|
398
1037
|
}
|
|
399
1038
|
function getClient() {
|
|
400
|
-
if (!
|
|
1039
|
+
if (!_adapterClient) {
|
|
401
1040
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
402
1041
|
}
|
|
1042
|
+
if (process.env.DATABASE_URL) {
|
|
1043
|
+
return _adapterClient;
|
|
1044
|
+
}
|
|
403
1045
|
if (process.env.EXE_IS_DAEMON === "1") {
|
|
404
1046
|
return _resilientClient;
|
|
405
1047
|
}
|
|
@@ -692,6 +1334,7 @@ async function ensureSchema() {
|
|
|
692
1334
|
project TEXT NOT NULL,
|
|
693
1335
|
summary TEXT NOT NULL,
|
|
694
1336
|
task_file TEXT,
|
|
1337
|
+
session_scope TEXT,
|
|
695
1338
|
read INTEGER NOT NULL DEFAULT 0,
|
|
696
1339
|
created_at TEXT NOT NULL
|
|
697
1340
|
);
|
|
@@ -700,7 +1343,7 @@ async function ensureSchema() {
|
|
|
700
1343
|
ON notifications(read);
|
|
701
1344
|
|
|
702
1345
|
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
703
|
-
ON notifications(agent_id);
|
|
1346
|
+
ON notifications(agent_id, session_scope);
|
|
704
1347
|
|
|
705
1348
|
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
706
1349
|
ON notifications(task_file);
|
|
@@ -738,6 +1381,7 @@ async function ensureSchema() {
|
|
|
738
1381
|
target_agent TEXT NOT NULL,
|
|
739
1382
|
target_project TEXT,
|
|
740
1383
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
1384
|
+
session_scope TEXT,
|
|
741
1385
|
content TEXT NOT NULL,
|
|
742
1386
|
priority TEXT DEFAULT 'normal',
|
|
743
1387
|
status TEXT DEFAULT 'pending',
|
|
@@ -751,10 +1395,31 @@ async function ensureSchema() {
|
|
|
751
1395
|
);
|
|
752
1396
|
|
|
753
1397
|
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
754
|
-
ON messages(target_agent, status);
|
|
1398
|
+
ON messages(target_agent, session_scope, status);
|
|
755
1399
|
|
|
756
1400
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
757
|
-
ON messages(target_agent, from_agent, server_seq);
|
|
1401
|
+
ON messages(target_agent, session_scope, from_agent, server_seq);
|
|
1402
|
+
`);
|
|
1403
|
+
try {
|
|
1404
|
+
await client.execute({
|
|
1405
|
+
sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
|
|
1406
|
+
args: []
|
|
1407
|
+
});
|
|
1408
|
+
} catch {
|
|
1409
|
+
}
|
|
1410
|
+
try {
|
|
1411
|
+
await client.execute({
|
|
1412
|
+
sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
|
|
1413
|
+
args: []
|
|
1414
|
+
});
|
|
1415
|
+
} catch {
|
|
1416
|
+
}
|
|
1417
|
+
await client.executeMultiple(`
|
|
1418
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
|
|
1419
|
+
ON notifications(agent_id, session_scope, read, created_at);
|
|
1420
|
+
|
|
1421
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
|
|
1422
|
+
ON messages(target_agent, session_scope, status, created_at);
|
|
758
1423
|
`);
|
|
759
1424
|
try {
|
|
760
1425
|
await client.execute({
|
|
@@ -1338,17 +2003,26 @@ async function ensureSchema() {
|
|
|
1338
2003
|
} catch {
|
|
1339
2004
|
}
|
|
1340
2005
|
}
|
|
2006
|
+
try {
|
|
2007
|
+
await client.execute({
|
|
2008
|
+
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
2009
|
+
args: []
|
|
2010
|
+
});
|
|
2011
|
+
} catch {
|
|
2012
|
+
}
|
|
1341
2013
|
}
|
|
1342
|
-
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso;
|
|
2014
|
+
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso;
|
|
1343
2015
|
var init_database = __esm({
|
|
1344
2016
|
"src/lib/database.ts"() {
|
|
1345
2017
|
"use strict";
|
|
1346
2018
|
init_db_retry();
|
|
1347
2019
|
init_employees();
|
|
2020
|
+
init_database_adapter();
|
|
1348
2021
|
_client = null;
|
|
1349
2022
|
_resilientClient = null;
|
|
1350
2023
|
_walCheckpointTimer = null;
|
|
1351
2024
|
_daemonClient = null;
|
|
2025
|
+
_adapterClient = null;
|
|
1352
2026
|
initTurso = initDatabase;
|
|
1353
2027
|
}
|
|
1354
2028
|
});
|
|
@@ -1358,6 +2032,7 @@ var shard_manager_exports = {};
|
|
|
1358
2032
|
__export(shard_manager_exports, {
|
|
1359
2033
|
disposeShards: () => disposeShards,
|
|
1360
2034
|
ensureShardSchema: () => ensureShardSchema,
|
|
2035
|
+
getOpenShardCount: () => getOpenShardCount,
|
|
1361
2036
|
getReadyShardClient: () => getReadyShardClient,
|
|
1362
2037
|
getShardClient: () => getShardClient,
|
|
1363
2038
|
getShardsDir: () => getShardsDir,
|
|
@@ -1366,15 +2041,18 @@ __export(shard_manager_exports, {
|
|
|
1366
2041
|
listShards: () => listShards,
|
|
1367
2042
|
shardExists: () => shardExists
|
|
1368
2043
|
});
|
|
1369
|
-
import
|
|
1370
|
-
import { existsSync as
|
|
2044
|
+
import path6 from "path";
|
|
2045
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync } from "fs";
|
|
1371
2046
|
import { createClient as createClient2 } from "@libsql/client";
|
|
1372
2047
|
function initShardManager(encryptionKey) {
|
|
1373
2048
|
_encryptionKey = encryptionKey;
|
|
1374
|
-
if (!
|
|
1375
|
-
|
|
2049
|
+
if (!existsSync5(SHARDS_DIR)) {
|
|
2050
|
+
mkdirSync2(SHARDS_DIR, { recursive: true });
|
|
1376
2051
|
}
|
|
1377
2052
|
_shardingEnabled = true;
|
|
2053
|
+
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
2054
|
+
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
2055
|
+
_evictionTimer.unref();
|
|
1378
2056
|
}
|
|
1379
2057
|
function isShardingEnabled() {
|
|
1380
2058
|
return _shardingEnabled;
|
|
@@ -1391,21 +2069,28 @@ function getShardClient(projectName) {
|
|
|
1391
2069
|
throw new Error(`Invalid project name for shard: "${projectName}"`);
|
|
1392
2070
|
}
|
|
1393
2071
|
const cached = _shards.get(safeName);
|
|
1394
|
-
if (cached)
|
|
1395
|
-
|
|
2072
|
+
if (cached) {
|
|
2073
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
2074
|
+
return cached;
|
|
2075
|
+
}
|
|
2076
|
+
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
2077
|
+
evictLRU();
|
|
2078
|
+
}
|
|
2079
|
+
const dbPath = path6.join(SHARDS_DIR, `${safeName}.db`);
|
|
1396
2080
|
const client = createClient2({
|
|
1397
2081
|
url: `file:${dbPath}`,
|
|
1398
2082
|
encryptionKey: _encryptionKey
|
|
1399
2083
|
});
|
|
1400
2084
|
_shards.set(safeName, client);
|
|
2085
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
1401
2086
|
return client;
|
|
1402
2087
|
}
|
|
1403
2088
|
function shardExists(projectName) {
|
|
1404
2089
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
1405
|
-
return
|
|
2090
|
+
return existsSync5(path6.join(SHARDS_DIR, `${safeName}.db`));
|
|
1406
2091
|
}
|
|
1407
2092
|
function listShards() {
|
|
1408
|
-
if (!
|
|
2093
|
+
if (!existsSync5(SHARDS_DIR)) return [];
|
|
1409
2094
|
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
1410
2095
|
}
|
|
1411
2096
|
async function ensureShardSchema(client) {
|
|
@@ -1457,6 +2142,8 @@ async function ensureShardSchema(client) {
|
|
|
1457
2142
|
for (const col of [
|
|
1458
2143
|
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
1459
2144
|
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
2145
|
+
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
2146
|
+
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
1460
2147
|
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
1461
2148
|
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
1462
2149
|
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
@@ -1479,7 +2166,23 @@ async function ensureShardSchema(client) {
|
|
|
1479
2166
|
// MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
|
|
1480
2167
|
"ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
|
|
1481
2168
|
"ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
|
|
1482
|
-
"ALTER TABLE memories ADD COLUMN trajectory TEXT"
|
|
2169
|
+
"ALTER TABLE memories ADD COLUMN trajectory TEXT",
|
|
2170
|
+
// Metadata enrichment columns (must match database.ts)
|
|
2171
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
2172
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
2173
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
2174
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
2175
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
2176
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
2177
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
2178
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
2179
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
2180
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
2181
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
2182
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
2183
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
2184
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
2185
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
1483
2186
|
]) {
|
|
1484
2187
|
try {
|
|
1485
2188
|
await client.execute(col);
|
|
@@ -1578,21 +2281,69 @@ async function getReadyShardClient(projectName) {
|
|
|
1578
2281
|
await ensureShardSchema(client);
|
|
1579
2282
|
return client;
|
|
1580
2283
|
}
|
|
2284
|
+
function evictLRU() {
|
|
2285
|
+
let oldest = null;
|
|
2286
|
+
let oldestTime = Infinity;
|
|
2287
|
+
for (const [name, time] of _shardLastAccess) {
|
|
2288
|
+
if (time < oldestTime) {
|
|
2289
|
+
oldestTime = time;
|
|
2290
|
+
oldest = name;
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
if (oldest) {
|
|
2294
|
+
const client = _shards.get(oldest);
|
|
2295
|
+
if (client) {
|
|
2296
|
+
client.close();
|
|
2297
|
+
}
|
|
2298
|
+
_shards.delete(oldest);
|
|
2299
|
+
_shardLastAccess.delete(oldest);
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
function evictIdleShards() {
|
|
2303
|
+
const now = Date.now();
|
|
2304
|
+
const toEvict = [];
|
|
2305
|
+
for (const [name, lastAccess] of _shardLastAccess) {
|
|
2306
|
+
if (now - lastAccess > SHARD_IDLE_MS) {
|
|
2307
|
+
toEvict.push(name);
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
for (const name of toEvict) {
|
|
2311
|
+
const client = _shards.get(name);
|
|
2312
|
+
if (client) {
|
|
2313
|
+
client.close();
|
|
2314
|
+
}
|
|
2315
|
+
_shards.delete(name);
|
|
2316
|
+
_shardLastAccess.delete(name);
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
function getOpenShardCount() {
|
|
2320
|
+
return _shards.size;
|
|
2321
|
+
}
|
|
1581
2322
|
function disposeShards() {
|
|
2323
|
+
if (_evictionTimer) {
|
|
2324
|
+
clearInterval(_evictionTimer);
|
|
2325
|
+
_evictionTimer = null;
|
|
2326
|
+
}
|
|
1582
2327
|
for (const [, client] of _shards) {
|
|
1583
2328
|
client.close();
|
|
1584
2329
|
}
|
|
1585
2330
|
_shards.clear();
|
|
2331
|
+
_shardLastAccess.clear();
|
|
1586
2332
|
_shardingEnabled = false;
|
|
1587
2333
|
_encryptionKey = null;
|
|
1588
2334
|
}
|
|
1589
|
-
var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
|
|
2335
|
+
var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
|
|
1590
2336
|
var init_shard_manager = __esm({
|
|
1591
2337
|
"src/lib/shard-manager.ts"() {
|
|
1592
2338
|
"use strict";
|
|
1593
2339
|
init_config();
|
|
1594
|
-
SHARDS_DIR =
|
|
2340
|
+
SHARDS_DIR = path6.join(EXE_AI_DIR, "shards");
|
|
2341
|
+
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
2342
|
+
MAX_OPEN_SHARDS = 10;
|
|
2343
|
+
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
1595
2344
|
_shards = /* @__PURE__ */ new Map();
|
|
2345
|
+
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
2346
|
+
_evictionTimer = null;
|
|
1596
2347
|
_encryptionKey = null;
|
|
1597
2348
|
_shardingEnabled = false;
|
|
1598
2349
|
}
|
|
@@ -1785,13 +2536,50 @@ ${p.content}`).join("\n\n");
|
|
|
1785
2536
|
}
|
|
1786
2537
|
});
|
|
1787
2538
|
|
|
2539
|
+
// src/lib/daemon-auth.ts
|
|
2540
|
+
import crypto from "crypto";
|
|
2541
|
+
import path7 from "path";
|
|
2542
|
+
import { existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
2543
|
+
function normalizeToken(token) {
|
|
2544
|
+
if (!token) return null;
|
|
2545
|
+
const trimmed = token.trim();
|
|
2546
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
2547
|
+
}
|
|
2548
|
+
function readDaemonToken() {
|
|
2549
|
+
try {
|
|
2550
|
+
if (!existsSync6(DAEMON_TOKEN_PATH)) return null;
|
|
2551
|
+
return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
|
|
2552
|
+
} catch {
|
|
2553
|
+
return null;
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
function ensureDaemonToken(seed) {
|
|
2557
|
+
const existing = readDaemonToken();
|
|
2558
|
+
if (existing) return existing;
|
|
2559
|
+
const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
|
|
2560
|
+
ensurePrivateDirSync(EXE_AI_DIR);
|
|
2561
|
+
writeFileSync2(DAEMON_TOKEN_PATH, `${token}
|
|
2562
|
+
`, "utf8");
|
|
2563
|
+
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
2564
|
+
return token;
|
|
2565
|
+
}
|
|
2566
|
+
var DAEMON_TOKEN_PATH;
|
|
2567
|
+
var init_daemon_auth = __esm({
|
|
2568
|
+
"src/lib/daemon-auth.ts"() {
|
|
2569
|
+
"use strict";
|
|
2570
|
+
init_config();
|
|
2571
|
+
init_secure_files();
|
|
2572
|
+
DAEMON_TOKEN_PATH = path7.join(EXE_AI_DIR, "exed.token");
|
|
2573
|
+
}
|
|
2574
|
+
});
|
|
2575
|
+
|
|
1788
2576
|
// src/lib/exe-daemon-client.ts
|
|
1789
2577
|
import net from "net";
|
|
1790
|
-
import
|
|
2578
|
+
import os5 from "os";
|
|
1791
2579
|
import { spawn } from "child_process";
|
|
1792
2580
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
1793
|
-
import { existsSync as
|
|
1794
|
-
import
|
|
2581
|
+
import { existsSync as existsSync7, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
|
|
2582
|
+
import path8 from "path";
|
|
1795
2583
|
import { fileURLToPath } from "url";
|
|
1796
2584
|
function handleData(chunk) {
|
|
1797
2585
|
_buffer += chunk.toString();
|
|
@@ -1819,9 +2607,9 @@ function handleData(chunk) {
|
|
|
1819
2607
|
}
|
|
1820
2608
|
}
|
|
1821
2609
|
function cleanupStaleFiles() {
|
|
1822
|
-
if (
|
|
2610
|
+
if (existsSync7(PID_PATH)) {
|
|
1823
2611
|
try {
|
|
1824
|
-
const pid = parseInt(
|
|
2612
|
+
const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
|
|
1825
2613
|
if (pid > 0) {
|
|
1826
2614
|
try {
|
|
1827
2615
|
process.kill(pid, 0);
|
|
@@ -1842,17 +2630,17 @@ function cleanupStaleFiles() {
|
|
|
1842
2630
|
}
|
|
1843
2631
|
}
|
|
1844
2632
|
function findPackageRoot() {
|
|
1845
|
-
let dir =
|
|
1846
|
-
const { root } =
|
|
2633
|
+
let dir = path8.dirname(fileURLToPath(import.meta.url));
|
|
2634
|
+
const { root } = path8.parse(dir);
|
|
1847
2635
|
while (dir !== root) {
|
|
1848
|
-
if (
|
|
1849
|
-
dir =
|
|
2636
|
+
if (existsSync7(path8.join(dir, "package.json"))) return dir;
|
|
2637
|
+
dir = path8.dirname(dir);
|
|
1850
2638
|
}
|
|
1851
2639
|
return null;
|
|
1852
2640
|
}
|
|
1853
2641
|
function spawnDaemon() {
|
|
1854
|
-
const freeGB =
|
|
1855
|
-
const totalGB =
|
|
2642
|
+
const freeGB = os5.freemem() / (1024 * 1024 * 1024);
|
|
2643
|
+
const totalGB = os5.totalmem() / (1024 * 1024 * 1024);
|
|
1856
2644
|
if (totalGB <= 8) {
|
|
1857
2645
|
process.stderr.write(
|
|
1858
2646
|
`[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
|
|
@@ -1872,16 +2660,17 @@ function spawnDaemon() {
|
|
|
1872
2660
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
1873
2661
|
return;
|
|
1874
2662
|
}
|
|
1875
|
-
const daemonPath =
|
|
1876
|
-
if (!
|
|
2663
|
+
const daemonPath = path8.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
2664
|
+
if (!existsSync7(daemonPath)) {
|
|
1877
2665
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
1878
2666
|
`);
|
|
1879
2667
|
return;
|
|
1880
2668
|
}
|
|
1881
2669
|
const resolvedPath = daemonPath;
|
|
2670
|
+
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
1882
2671
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
1883
2672
|
`);
|
|
1884
|
-
const logPath =
|
|
2673
|
+
const logPath = path8.join(path8.dirname(SOCKET_PATH), "exed.log");
|
|
1885
2674
|
let stderrFd = "ignore";
|
|
1886
2675
|
try {
|
|
1887
2676
|
stderrFd = openSync(logPath, "a");
|
|
@@ -1899,7 +2688,8 @@ function spawnDaemon() {
|
|
|
1899
2688
|
TMUX_PANE: void 0,
|
|
1900
2689
|
// Prevents resolveExeSession() from scoping to one session
|
|
1901
2690
|
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
1902
|
-
EXE_DAEMON_PID: PID_PATH
|
|
2691
|
+
EXE_DAEMON_PID: PID_PATH,
|
|
2692
|
+
[DAEMON_TOKEN_ENV]: daemonToken
|
|
1903
2693
|
}
|
|
1904
2694
|
});
|
|
1905
2695
|
child.unref();
|
|
@@ -2009,13 +2799,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
2009
2799
|
return;
|
|
2010
2800
|
}
|
|
2011
2801
|
const id = randomUUID2();
|
|
2802
|
+
const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
|
|
2012
2803
|
const timer = setTimeout(() => {
|
|
2013
2804
|
_pending.delete(id);
|
|
2014
2805
|
resolve({ error: "Request timeout" });
|
|
2015
2806
|
}, timeoutMs);
|
|
2016
2807
|
_pending.set(id, { resolve, timer });
|
|
2017
2808
|
try {
|
|
2018
|
-
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
2809
|
+
_socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
|
|
2019
2810
|
} catch {
|
|
2020
2811
|
clearTimeout(timer);
|
|
2021
2812
|
_pending.delete(id);
|
|
@@ -2032,74 +2823,123 @@ async function pingDaemon() {
|
|
|
2032
2823
|
return null;
|
|
2033
2824
|
}
|
|
2034
2825
|
function killAndRespawnDaemon() {
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2826
|
+
if (!acquireSpawnLock()) {
|
|
2827
|
+
process.stderr.write("[exed-client] Another process is already restarting daemon \u2014 skipping\n");
|
|
2828
|
+
if (_socket) {
|
|
2829
|
+
_socket.destroy();
|
|
2830
|
+
_socket = null;
|
|
2831
|
+
}
|
|
2832
|
+
_connected = false;
|
|
2833
|
+
_buffer = "";
|
|
2834
|
+
return;
|
|
2835
|
+
}
|
|
2836
|
+
try {
|
|
2837
|
+
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
2838
|
+
if (existsSync7(PID_PATH)) {
|
|
2839
|
+
try {
|
|
2840
|
+
const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
|
|
2841
|
+
if (pid > 0) {
|
|
2842
|
+
try {
|
|
2843
|
+
process.kill(pid, "SIGKILL");
|
|
2844
|
+
} catch {
|
|
2845
|
+
}
|
|
2043
2846
|
}
|
|
2847
|
+
} catch {
|
|
2044
2848
|
}
|
|
2849
|
+
}
|
|
2850
|
+
if (_socket) {
|
|
2851
|
+
_socket.destroy();
|
|
2852
|
+
_socket = null;
|
|
2853
|
+
}
|
|
2854
|
+
_connected = false;
|
|
2855
|
+
_buffer = "";
|
|
2856
|
+
try {
|
|
2857
|
+
unlinkSync2(PID_PATH);
|
|
2045
2858
|
} catch {
|
|
2046
2859
|
}
|
|
2860
|
+
try {
|
|
2861
|
+
unlinkSync2(SOCKET_PATH);
|
|
2862
|
+
} catch {
|
|
2863
|
+
}
|
|
2864
|
+
spawnDaemon();
|
|
2865
|
+
} finally {
|
|
2866
|
+
releaseSpawnLock();
|
|
2047
2867
|
}
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
_socket = null;
|
|
2051
|
-
}
|
|
2052
|
-
_connected = false;
|
|
2053
|
-
_buffer = "";
|
|
2868
|
+
}
|
|
2869
|
+
function isDaemonTooYoung() {
|
|
2054
2870
|
try {
|
|
2055
|
-
|
|
2871
|
+
const stat = statSync(PID_PATH);
|
|
2872
|
+
return Date.now() - stat.mtimeMs < MIN_DAEMON_AGE_MS;
|
|
2056
2873
|
} catch {
|
|
2874
|
+
return false;
|
|
2057
2875
|
}
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2876
|
+
}
|
|
2877
|
+
async function retryThenRestart(doRequest, label) {
|
|
2878
|
+
const result = await doRequest();
|
|
2879
|
+
if (!result.error) {
|
|
2880
|
+
_consecutiveFailures = 0;
|
|
2881
|
+
return result;
|
|
2882
|
+
}
|
|
2883
|
+
_consecutiveFailures++;
|
|
2884
|
+
for (let i = 0; i < MAX_RETRIES_BEFORE_RESTART; i++) {
|
|
2885
|
+
const delayMs = RETRY_DELAYS_MS[i] ?? 5e3;
|
|
2886
|
+
process.stderr.write(`[exed-client] ${label} failed (${result.error}), retry ${i + 1}/${MAX_RETRIES_BEFORE_RESTART} in ${delayMs}ms
|
|
2887
|
+
`);
|
|
2888
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
2889
|
+
if (!_connected) {
|
|
2890
|
+
if (!await connectToSocket()) continue;
|
|
2891
|
+
}
|
|
2892
|
+
const retry = await doRequest();
|
|
2893
|
+
if (!retry.error) {
|
|
2894
|
+
_consecutiveFailures = 0;
|
|
2895
|
+
return retry;
|
|
2896
|
+
}
|
|
2897
|
+
_consecutiveFailures++;
|
|
2898
|
+
}
|
|
2899
|
+
if (isDaemonTooYoung()) {
|
|
2900
|
+
process.stderr.write(`[exed-client] ${label}: daemon too young (< ${MIN_DAEMON_AGE_MS / 1e3}s) \u2014 skipping restart
|
|
2901
|
+
`);
|
|
2902
|
+
return { error: result.error };
|
|
2903
|
+
}
|
|
2904
|
+
process.stderr.write(`[exed-client] ${label}: ${_consecutiveFailures} consecutive failures \u2014 restarting daemon
|
|
2905
|
+
`);
|
|
2906
|
+
killAndRespawnDaemon();
|
|
2907
|
+
const start = Date.now();
|
|
2908
|
+
let delay2 = 200;
|
|
2909
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
2910
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
2911
|
+
if (await connectToSocket()) break;
|
|
2912
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
2061
2913
|
}
|
|
2062
|
-
|
|
2914
|
+
if (!_connected) return { error: "Daemon restart failed" };
|
|
2915
|
+
const final = await doRequest();
|
|
2916
|
+
if (!final.error) _consecutiveFailures = 0;
|
|
2917
|
+
return final;
|
|
2063
2918
|
}
|
|
2064
2919
|
async function embedViaClient(text, priority = "high") {
|
|
2065
2920
|
if (!_connected && !await connectEmbedDaemon()) return null;
|
|
2066
2921
|
_requestCount++;
|
|
2067
2922
|
if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
|
|
2068
2923
|
const health = await pingDaemon();
|
|
2069
|
-
if (!health) {
|
|
2924
|
+
if (!health && !isDaemonTooYoung()) {
|
|
2070
2925
|
process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
|
|
2071
2926
|
`);
|
|
2072
2927
|
killAndRespawnDaemon();
|
|
2073
2928
|
const start = Date.now();
|
|
2074
|
-
let
|
|
2929
|
+
let d = 200;
|
|
2075
2930
|
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
2076
|
-
await new Promise((r) => setTimeout(r,
|
|
2931
|
+
await new Promise((r) => setTimeout(r, d));
|
|
2077
2932
|
if (await connectToSocket()) break;
|
|
2078
|
-
|
|
2933
|
+
d = Math.min(d * 2, 3e3);
|
|
2079
2934
|
}
|
|
2080
2935
|
if (!_connected) return null;
|
|
2081
2936
|
}
|
|
2082
2937
|
}
|
|
2083
|
-
const result = await
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
killAndRespawnDaemon();
|
|
2089
|
-
const start = Date.now();
|
|
2090
|
-
let delay2 = 200;
|
|
2091
|
-
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
2092
|
-
await new Promise((r) => setTimeout(r, delay2));
|
|
2093
|
-
if (await connectToSocket()) break;
|
|
2094
|
-
delay2 = Math.min(delay2 * 2, 3e3);
|
|
2095
|
-
}
|
|
2096
|
-
if (!_connected) return null;
|
|
2097
|
-
const retry = await sendRequest([text], priority);
|
|
2098
|
-
if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
|
|
2099
|
-
process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
|
|
2100
|
-
`);
|
|
2101
|
-
}
|
|
2102
|
-
return null;
|
|
2938
|
+
const result = await retryThenRestart(
|
|
2939
|
+
() => sendRequest([text], priority),
|
|
2940
|
+
"Embed"
|
|
2941
|
+
);
|
|
2942
|
+
return !result.error && result.vectors?.[0] ? result.vectors[0] : null;
|
|
2103
2943
|
}
|
|
2104
2944
|
function disconnectClient() {
|
|
2105
2945
|
if (_socket) {
|
|
@@ -2114,22 +2954,28 @@ function disconnectClient() {
|
|
|
2114
2954
|
entry.resolve({ error: "Client disconnected" });
|
|
2115
2955
|
}
|
|
2116
2956
|
}
|
|
2117
|
-
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;
|
|
2957
|
+
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;
|
|
2118
2958
|
var init_exe_daemon_client = __esm({
|
|
2119
2959
|
"src/lib/exe-daemon-client.ts"() {
|
|
2120
2960
|
"use strict";
|
|
2121
2961
|
init_config();
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2962
|
+
init_daemon_auth();
|
|
2963
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path8.join(EXE_AI_DIR, "exed.sock");
|
|
2964
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path8.join(EXE_AI_DIR, "exed.pid");
|
|
2965
|
+
SPAWN_LOCK_PATH = path8.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
2125
2966
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
2126
2967
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
2127
2968
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
2969
|
+
DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
|
|
2128
2970
|
_socket = null;
|
|
2129
2971
|
_connected = false;
|
|
2130
2972
|
_buffer = "";
|
|
2131
2973
|
_requestCount = 0;
|
|
2974
|
+
_consecutiveFailures = 0;
|
|
2132
2975
|
HEALTH_CHECK_INTERVAL = 100;
|
|
2976
|
+
MAX_RETRIES_BEFORE_RESTART = 3;
|
|
2977
|
+
RETRY_DELAYS_MS = [1e3, 3e3, 5e3];
|
|
2978
|
+
MIN_DAEMON_AGE_MS = 3e4;
|
|
2133
2979
|
_pending = /* @__PURE__ */ new Map();
|
|
2134
2980
|
MAX_BUFFER = 1e7;
|
|
2135
2981
|
}
|
|
@@ -2172,10 +3018,10 @@ async function disposeEmbedder() {
|
|
|
2172
3018
|
async function embedDirect(text) {
|
|
2173
3019
|
const llamaCpp = await import("node-llama-cpp");
|
|
2174
3020
|
const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
2175
|
-
const { existsSync:
|
|
2176
|
-
const
|
|
2177
|
-
const modelPath =
|
|
2178
|
-
if (!
|
|
3021
|
+
const { existsSync: existsSync10 } = await import("fs");
|
|
3022
|
+
const path12 = await import("path");
|
|
3023
|
+
const modelPath = path12.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
|
|
3024
|
+
if (!existsSync10(modelPath)) {
|
|
2179
3025
|
throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
|
|
2180
3026
|
}
|
|
2181
3027
|
const llama = await llamaCpp.getLlama();
|
|
@@ -2204,9 +3050,12 @@ var init_embedder = __esm({
|
|
|
2204
3050
|
});
|
|
2205
3051
|
|
|
2206
3052
|
// src/lib/license.ts
|
|
2207
|
-
import { readFileSync as
|
|
3053
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync8, mkdirSync as mkdirSync3 } from "fs";
|
|
2208
3054
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
2209
|
-
import
|
|
3055
|
+
import { createRequire as createRequire2 } from "module";
|
|
3056
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
3057
|
+
import os6 from "os";
|
|
3058
|
+
import path9 from "path";
|
|
2210
3059
|
import { jwtVerify, importSPKI } from "jose";
|
|
2211
3060
|
async function fetchRetry(url, init) {
|
|
2212
3061
|
try {
|
|
@@ -2217,37 +3066,37 @@ async function fetchRetry(url, init) {
|
|
|
2217
3066
|
}
|
|
2218
3067
|
}
|
|
2219
3068
|
function loadDeviceId() {
|
|
2220
|
-
const deviceJsonPath =
|
|
3069
|
+
const deviceJsonPath = path9.join(EXE_AI_DIR, "device.json");
|
|
2221
3070
|
try {
|
|
2222
|
-
if (
|
|
2223
|
-
const data = JSON.parse(
|
|
3071
|
+
if (existsSync8(deviceJsonPath)) {
|
|
3072
|
+
const data = JSON.parse(readFileSync5(deviceJsonPath, "utf8"));
|
|
2224
3073
|
if (data.deviceId) return data.deviceId;
|
|
2225
3074
|
}
|
|
2226
3075
|
} catch {
|
|
2227
3076
|
}
|
|
2228
3077
|
try {
|
|
2229
|
-
if (
|
|
2230
|
-
const id2 =
|
|
3078
|
+
if (existsSync8(DEVICE_ID_PATH)) {
|
|
3079
|
+
const id2 = readFileSync5(DEVICE_ID_PATH, "utf8").trim();
|
|
2231
3080
|
if (id2) return id2;
|
|
2232
3081
|
}
|
|
2233
3082
|
} catch {
|
|
2234
3083
|
}
|
|
2235
3084
|
const id = randomUUID3();
|
|
2236
|
-
|
|
2237
|
-
|
|
3085
|
+
mkdirSync3(EXE_AI_DIR, { recursive: true });
|
|
3086
|
+
writeFileSync3(DEVICE_ID_PATH, id, "utf8");
|
|
2238
3087
|
return id;
|
|
2239
3088
|
}
|
|
2240
3089
|
function loadLicense() {
|
|
2241
3090
|
try {
|
|
2242
|
-
if (!
|
|
2243
|
-
return
|
|
3091
|
+
if (!existsSync8(LICENSE_PATH)) return null;
|
|
3092
|
+
return readFileSync5(LICENSE_PATH, "utf8").trim();
|
|
2244
3093
|
} catch {
|
|
2245
3094
|
return null;
|
|
2246
3095
|
}
|
|
2247
3096
|
}
|
|
2248
3097
|
function saveLicense(apiKey) {
|
|
2249
|
-
|
|
2250
|
-
|
|
3098
|
+
mkdirSync3(EXE_AI_DIR, { recursive: true });
|
|
3099
|
+
writeFileSync3(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
|
|
2251
3100
|
}
|
|
2252
3101
|
async function verifyLicenseJwt(token) {
|
|
2253
3102
|
try {
|
|
@@ -2273,8 +3122,8 @@ async function verifyLicenseJwt(token) {
|
|
|
2273
3122
|
}
|
|
2274
3123
|
async function getCachedLicense() {
|
|
2275
3124
|
try {
|
|
2276
|
-
if (!
|
|
2277
|
-
const raw = JSON.parse(
|
|
3125
|
+
if (!existsSync8(CACHE_PATH)) return null;
|
|
3126
|
+
const raw = JSON.parse(readFileSync5(CACHE_PATH, "utf8"));
|
|
2278
3127
|
if (!raw.token || typeof raw.token !== "string") return null;
|
|
2279
3128
|
return await verifyLicenseJwt(raw.token);
|
|
2280
3129
|
} catch {
|
|
@@ -2283,8 +3132,8 @@ async function getCachedLicense() {
|
|
|
2283
3132
|
}
|
|
2284
3133
|
function readCachedToken() {
|
|
2285
3134
|
try {
|
|
2286
|
-
if (!
|
|
2287
|
-
const raw = JSON.parse(
|
|
3135
|
+
if (!existsSync8(CACHE_PATH)) return null;
|
|
3136
|
+
const raw = JSON.parse(readFileSync5(CACHE_PATH, "utf8"));
|
|
2288
3137
|
return typeof raw.token === "string" ? raw.token : null;
|
|
2289
3138
|
} catch {
|
|
2290
3139
|
return null;
|
|
@@ -2318,56 +3167,130 @@ function getRawCachedPlan() {
|
|
|
2318
3167
|
}
|
|
2319
3168
|
function cacheResponse(token) {
|
|
2320
3169
|
try {
|
|
2321
|
-
|
|
3170
|
+
writeFileSync3(CACHE_PATH, JSON.stringify({ token }), "utf8");
|
|
2322
3171
|
} catch {
|
|
2323
3172
|
}
|
|
2324
3173
|
}
|
|
2325
|
-
|
|
2326
|
-
|
|
3174
|
+
function loadPrismaForLicense() {
|
|
3175
|
+
if (_prismaFailed) return null;
|
|
3176
|
+
const dbUrl = process.env.DATABASE_URL;
|
|
3177
|
+
if (!dbUrl) {
|
|
3178
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path9.join(os6.homedir(), "exe-db");
|
|
3179
|
+
if (!existsSync8(path9.join(exeDbRoot, "package.json"))) {
|
|
3180
|
+
_prismaFailed = true;
|
|
3181
|
+
return null;
|
|
3182
|
+
}
|
|
3183
|
+
}
|
|
3184
|
+
if (!_prismaPromise) {
|
|
3185
|
+
_prismaPromise = (async () => {
|
|
3186
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
3187
|
+
if (explicitPath) {
|
|
3188
|
+
const mod2 = await import(pathToFileURL2(explicitPath).href);
|
|
3189
|
+
const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
|
|
3190
|
+
if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
|
|
3191
|
+
return new Ctor2();
|
|
3192
|
+
}
|
|
3193
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path9.join(os6.homedir(), "exe-db");
|
|
3194
|
+
const req = createRequire2(path9.join(exeDbRoot, "package.json"));
|
|
3195
|
+
const entry = req.resolve("@prisma/client");
|
|
3196
|
+
const mod = await import(pathToFileURL2(entry).href);
|
|
3197
|
+
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
3198
|
+
if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
|
|
3199
|
+
return new Ctor();
|
|
3200
|
+
})().catch((err) => {
|
|
3201
|
+
_prismaFailed = true;
|
|
3202
|
+
_prismaPromise = null;
|
|
3203
|
+
throw err;
|
|
3204
|
+
});
|
|
3205
|
+
}
|
|
3206
|
+
return _prismaPromise;
|
|
3207
|
+
}
|
|
3208
|
+
async function validateViaPostgres(apiKey) {
|
|
3209
|
+
const loader = loadPrismaForLicense();
|
|
3210
|
+
if (!loader) return null;
|
|
3211
|
+
try {
|
|
3212
|
+
const prisma = await loader;
|
|
3213
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
3214
|
+
`SELECT plan, email, status, device_limit, employee_limit, memory_limit, expires_at
|
|
3215
|
+
FROM billing.licenses WHERE key = $1 LIMIT 1`,
|
|
3216
|
+
apiKey
|
|
3217
|
+
);
|
|
3218
|
+
if (!rows || rows.length === 0) return null;
|
|
3219
|
+
const row = rows[0];
|
|
3220
|
+
if (row.status !== "active") return null;
|
|
3221
|
+
if (row.expires_at && new Date(row.expires_at) < /* @__PURE__ */ new Date()) return null;
|
|
3222
|
+
const plan = row.plan;
|
|
3223
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
3224
|
+
return {
|
|
3225
|
+
valid: true,
|
|
3226
|
+
plan,
|
|
3227
|
+
email: row.email,
|
|
3228
|
+
expiresAt: row.expires_at ? new Date(row.expires_at).toISOString() : null,
|
|
3229
|
+
deviceLimit: row.device_limit ?? limits.devices,
|
|
3230
|
+
employeeLimit: row.employee_limit ?? limits.employees,
|
|
3231
|
+
memoryLimit: row.memory_limit ?? limits.memories
|
|
3232
|
+
};
|
|
3233
|
+
} catch {
|
|
3234
|
+
return null;
|
|
3235
|
+
}
|
|
3236
|
+
}
|
|
3237
|
+
async function validateViaCFWorker(apiKey, deviceId) {
|
|
2327
3238
|
try {
|
|
2328
3239
|
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
2329
3240
|
method: "POST",
|
|
2330
3241
|
headers: { "Content-Type": "application/json" },
|
|
2331
|
-
body: JSON.stringify({ apiKey, deviceId
|
|
3242
|
+
body: JSON.stringify({ apiKey, deviceId }),
|
|
2332
3243
|
signal: AbortSignal.timeout(1e4)
|
|
2333
3244
|
});
|
|
2334
|
-
if (res.ok)
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
3245
|
+
if (!res.ok) return null;
|
|
3246
|
+
const data = await res.json();
|
|
3247
|
+
if (data.error === "device_limit_exceeded") return null;
|
|
3248
|
+
if (!data.valid) return null;
|
|
3249
|
+
if (data.token) {
|
|
3250
|
+
cacheResponse(data.token);
|
|
3251
|
+
const verified = await verifyLicenseJwt(data.token);
|
|
3252
|
+
if (verified) return verified;
|
|
3253
|
+
}
|
|
3254
|
+
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
3255
|
+
return {
|
|
3256
|
+
valid: data.valid,
|
|
3257
|
+
plan: data.plan,
|
|
3258
|
+
email: data.email,
|
|
3259
|
+
expiresAt: data.expiresAt,
|
|
3260
|
+
deviceLimit: limits.devices,
|
|
3261
|
+
employeeLimit: limits.employees,
|
|
3262
|
+
memoryLimit: limits.memories
|
|
3263
|
+
};
|
|
3264
|
+
} catch {
|
|
3265
|
+
return null;
|
|
3266
|
+
}
|
|
3267
|
+
}
|
|
3268
|
+
async function validateLicense(apiKey, deviceId) {
|
|
3269
|
+
const did = deviceId ?? loadDeviceId();
|
|
3270
|
+
const pgResult = await validateViaPostgres(apiKey);
|
|
3271
|
+
if (pgResult) {
|
|
3272
|
+
try {
|
|
3273
|
+
writeFileSync3(CACHE_PATH, JSON.stringify({ pgLicense: pgResult, ts: Date.now() }), "utf8");
|
|
3274
|
+
} catch {
|
|
3275
|
+
}
|
|
3276
|
+
return pgResult;
|
|
3277
|
+
}
|
|
3278
|
+
const cfResult = await validateViaCFWorker(apiKey, did);
|
|
3279
|
+
if (cfResult) return cfResult;
|
|
3280
|
+
const cached = await getCachedLicense();
|
|
3281
|
+
if (cached) return cached;
|
|
3282
|
+
try {
|
|
3283
|
+
if (existsSync8(CACHE_PATH)) {
|
|
3284
|
+
const raw = JSON.parse(readFileSync5(CACHE_PATH, "utf8"));
|
|
3285
|
+
if (raw.pgLicense && raw.ts && Date.now() - raw.ts < 7 * 24 * 60 * 60 * 1e3) {
|
|
3286
|
+
return raw.pgLicense;
|
|
2347
3287
|
}
|
|
2348
|
-
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
2349
|
-
return {
|
|
2350
|
-
valid: data.valid,
|
|
2351
|
-
plan: data.plan,
|
|
2352
|
-
email: data.email,
|
|
2353
|
-
expiresAt: data.expiresAt,
|
|
2354
|
-
deviceLimit: limits.devices,
|
|
2355
|
-
employeeLimit: limits.employees,
|
|
2356
|
-
memoryLimit: limits.memories
|
|
2357
|
-
};
|
|
2358
3288
|
}
|
|
2359
|
-
const cached = await getCachedLicense();
|
|
2360
|
-
if (cached) return cached;
|
|
2361
|
-
const raw = getRawCachedPlan();
|
|
2362
|
-
if (raw) return raw;
|
|
2363
|
-
return { ...FREE_LICENSE, valid: false, plan: "free" };
|
|
2364
3289
|
} catch {
|
|
2365
|
-
const cached = await getCachedLicense();
|
|
2366
|
-
if (cached) return cached;
|
|
2367
|
-
const rawFallback = getRawCachedPlan();
|
|
2368
|
-
if (rawFallback) return rawFallback;
|
|
2369
|
-
return { ...FREE_LICENSE, valid: false, error: "offline" };
|
|
2370
3290
|
}
|
|
3291
|
+
const rawFallback = getRawCachedPlan();
|
|
3292
|
+
if (rawFallback) return rawFallback;
|
|
3293
|
+
return { ...FREE_LICENSE, valid: false };
|
|
2371
3294
|
}
|
|
2372
3295
|
function getCacheAgeMs() {
|
|
2373
3296
|
try {
|
|
@@ -2382,9 +3305,9 @@ async function checkLicense() {
|
|
|
2382
3305
|
let key = loadLicense();
|
|
2383
3306
|
if (!key) {
|
|
2384
3307
|
try {
|
|
2385
|
-
const configPath =
|
|
2386
|
-
if (
|
|
2387
|
-
const raw = JSON.parse(
|
|
3308
|
+
const configPath = path9.join(EXE_AI_DIR, "config.json");
|
|
3309
|
+
if (existsSync8(configPath)) {
|
|
3310
|
+
const raw = JSON.parse(readFileSync5(configPath, "utf8"));
|
|
2388
3311
|
const cloud = raw.cloud;
|
|
2389
3312
|
if (cloud?.apiKey) {
|
|
2390
3313
|
key = cloud.apiKey;
|
|
@@ -2410,14 +3333,14 @@ function isFeatureAllowed(license, feature) {
|
|
|
2410
3333
|
return license.plan === "team" || license.plan === "agency" || license.plan === "enterprise";
|
|
2411
3334
|
}
|
|
2412
3335
|
}
|
|
2413
|
-
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, RETRY_DELAY_MS, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, CACHE_MAX_AGE_MS;
|
|
3336
|
+
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, RETRY_DELAY_MS, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, _prismaPromise, _prismaFailed, CACHE_MAX_AGE_MS;
|
|
2414
3337
|
var init_license = __esm({
|
|
2415
3338
|
"src/lib/license.ts"() {
|
|
2416
3339
|
"use strict";
|
|
2417
3340
|
init_config();
|
|
2418
|
-
LICENSE_PATH =
|
|
2419
|
-
CACHE_PATH =
|
|
2420
|
-
DEVICE_ID_PATH =
|
|
3341
|
+
LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
|
|
3342
|
+
CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
|
|
3343
|
+
DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
|
|
2421
3344
|
API_BASE = "https://askexe.com/cloud";
|
|
2422
3345
|
RETRY_DELAY_MS = 500;
|
|
2423
3346
|
LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
@@ -2441,6 +3364,8 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
|
2441
3364
|
employeeLimit: 1,
|
|
2442
3365
|
memoryLimit: 5e3
|
|
2443
3366
|
};
|
|
3367
|
+
_prismaPromise = null;
|
|
3368
|
+
_prismaFailed = false;
|
|
2444
3369
|
CACHE_MAX_AGE_MS = 36e5;
|
|
2445
3370
|
}
|
|
2446
3371
|
});
|
|
@@ -2456,12 +3381,12 @@ __export(plan_limits_exports, {
|
|
|
2456
3381
|
countActiveMemories: () => countActiveMemories,
|
|
2457
3382
|
getLicenseSync: () => getLicenseSync
|
|
2458
3383
|
});
|
|
2459
|
-
import { readFileSync as
|
|
2460
|
-
import
|
|
3384
|
+
import { readFileSync as readFileSync6, existsSync as existsSync9 } from "fs";
|
|
3385
|
+
import path10 from "path";
|
|
2461
3386
|
function getLicenseSync() {
|
|
2462
3387
|
try {
|
|
2463
|
-
if (!
|
|
2464
|
-
const raw = JSON.parse(
|
|
3388
|
+
if (!existsSync9(CACHE_PATH2)) return freeLicense();
|
|
3389
|
+
const raw = JSON.parse(readFileSync6(CACHE_PATH2, "utf8"));
|
|
2465
3390
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
2466
3391
|
const parts = raw.token.split(".");
|
|
2467
3392
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -2528,8 +3453,8 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
2528
3453
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
2529
3454
|
let count = 0;
|
|
2530
3455
|
try {
|
|
2531
|
-
if (
|
|
2532
|
-
const raw =
|
|
3456
|
+
if (existsSync9(filePath)) {
|
|
3457
|
+
const raw = readFileSync6(filePath, "utf8");
|
|
2533
3458
|
const employees = JSON.parse(raw);
|
|
2534
3459
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
2535
3460
|
}
|
|
@@ -2566,14 +3491,14 @@ var init_plan_limits = __esm({
|
|
|
2566
3491
|
this.name = "PlanLimitError";
|
|
2567
3492
|
}
|
|
2568
3493
|
};
|
|
2569
|
-
CACHE_PATH2 =
|
|
3494
|
+
CACHE_PATH2 = path10.join(EXE_AI_DIR, "license-cache.json");
|
|
2570
3495
|
}
|
|
2571
3496
|
});
|
|
2572
3497
|
|
|
2573
3498
|
// src/adapters/claude/hooks/response-ingest-worker.ts
|
|
2574
|
-
import
|
|
2575
|
-
import { writeFileSync as
|
|
2576
|
-
import
|
|
3499
|
+
import crypto2 from "crypto";
|
|
3500
|
+
import { writeFileSync as writeFileSync4 } from "fs";
|
|
3501
|
+
import path11 from "path";
|
|
2577
3502
|
|
|
2578
3503
|
// src/lib/project-name.ts
|
|
2579
3504
|
import { execSync } from "child_process";
|
|
@@ -2618,16 +3543,16 @@ import { createHash } from "crypto";
|
|
|
2618
3543
|
|
|
2619
3544
|
// src/lib/keychain.ts
|
|
2620
3545
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
2621
|
-
import { existsSync as
|
|
2622
|
-
import
|
|
2623
|
-
import
|
|
3546
|
+
import { existsSync as existsSync4 } from "fs";
|
|
3547
|
+
import path5 from "path";
|
|
3548
|
+
import os4 from "os";
|
|
2624
3549
|
var SERVICE = "exe-mem";
|
|
2625
3550
|
var ACCOUNT = "master-key";
|
|
2626
3551
|
function getKeyDir() {
|
|
2627
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
3552
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path5.join(os4.homedir(), ".exe-os");
|
|
2628
3553
|
}
|
|
2629
3554
|
function getKeyPath() {
|
|
2630
|
-
return
|
|
3555
|
+
return path5.join(getKeyDir(), "master.key");
|
|
2631
3556
|
}
|
|
2632
3557
|
async function tryKeytar() {
|
|
2633
3558
|
try {
|
|
@@ -2648,9 +3573,9 @@ async function getMasterKey() {
|
|
|
2648
3573
|
}
|
|
2649
3574
|
}
|
|
2650
3575
|
const keyPath = getKeyPath();
|
|
2651
|
-
if (!
|
|
3576
|
+
if (!existsSync4(keyPath)) {
|
|
2652
3577
|
process.stderr.write(
|
|
2653
|
-
`[keychain] Key not found at ${keyPath} (HOME=${
|
|
3578
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os4.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
2654
3579
|
`
|
|
2655
3580
|
);
|
|
2656
3581
|
return null;
|
|
@@ -3107,7 +4032,7 @@ async function main() {
|
|
|
3107
4032
|
process.exit(0);
|
|
3108
4033
|
}
|
|
3109
4034
|
await writeMemory({
|
|
3110
|
-
id:
|
|
4035
|
+
id: crypto2.randomUUID(),
|
|
3111
4036
|
agent_id: agentId,
|
|
3112
4037
|
agent_role: agentRole,
|
|
3113
4038
|
session_id: sessionId,
|
|
@@ -3123,8 +4048,8 @@ async function main() {
|
|
|
3123
4048
|
if (needsBackfill) {
|
|
3124
4049
|
try {
|
|
3125
4050
|
const { EXE_AI_DIR: exeDir } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
3126
|
-
const flagPath =
|
|
3127
|
-
|
|
4051
|
+
const flagPath = path11.join(exeDir, "session-cache", "needs-backfill");
|
|
4052
|
+
writeFileSync4(flagPath, "1");
|
|
3128
4053
|
} catch (err) {
|
|
3129
4054
|
process.stderr.write(`[response-ingest-worker] backfill flag write failed: ${err instanceof Error ? err.message : String(err)}
|
|
3130
4055
|
`);
|