@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
|
@@ -89,6 +89,44 @@ var init_db_retry = __esm({
|
|
|
89
89
|
}
|
|
90
90
|
});
|
|
91
91
|
|
|
92
|
+
// src/lib/secure-files.ts
|
|
93
|
+
import { chmodSync, existsSync, mkdirSync } from "fs";
|
|
94
|
+
import { chmod, mkdir } from "fs/promises";
|
|
95
|
+
async function ensurePrivateDir(dirPath) {
|
|
96
|
+
await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
97
|
+
try {
|
|
98
|
+
await chmod(dirPath, PRIVATE_DIR_MODE);
|
|
99
|
+
} catch {
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function ensurePrivateDirSync(dirPath) {
|
|
103
|
+
mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
104
|
+
try {
|
|
105
|
+
chmodSync(dirPath, PRIVATE_DIR_MODE);
|
|
106
|
+
} catch {
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async function enforcePrivateFile(filePath) {
|
|
110
|
+
try {
|
|
111
|
+
await chmod(filePath, PRIVATE_FILE_MODE);
|
|
112
|
+
} catch {
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function enforcePrivateFileSync(filePath) {
|
|
116
|
+
try {
|
|
117
|
+
if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
|
|
118
|
+
} catch {
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
|
|
122
|
+
var init_secure_files = __esm({
|
|
123
|
+
"src/lib/secure-files.ts"() {
|
|
124
|
+
"use strict";
|
|
125
|
+
PRIVATE_DIR_MODE = 448;
|
|
126
|
+
PRIVATE_FILE_MODE = 384;
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
92
130
|
// src/lib/config.ts
|
|
93
131
|
var config_exports = {};
|
|
94
132
|
__export(config_exports, {
|
|
@@ -105,8 +143,8 @@ __export(config_exports, {
|
|
|
105
143
|
migrateConfig: () => migrateConfig,
|
|
106
144
|
saveConfig: () => saveConfig
|
|
107
145
|
});
|
|
108
|
-
import { readFile, writeFile
|
|
109
|
-
import { readFileSync, existsSync, renameSync } from "fs";
|
|
146
|
+
import { readFile, writeFile } from "fs/promises";
|
|
147
|
+
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
110
148
|
import path from "path";
|
|
111
149
|
import os from "os";
|
|
112
150
|
function resolveDataDir() {
|
|
@@ -114,7 +152,7 @@ function resolveDataDir() {
|
|
|
114
152
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
115
153
|
const newDir = path.join(os.homedir(), ".exe-os");
|
|
116
154
|
const legacyDir = path.join(os.homedir(), ".exe-mem");
|
|
117
|
-
if (!
|
|
155
|
+
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
118
156
|
try {
|
|
119
157
|
renameSync(legacyDir, newDir);
|
|
120
158
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -177,9 +215,9 @@ function normalizeAutoUpdate(raw) {
|
|
|
177
215
|
}
|
|
178
216
|
async function loadConfig() {
|
|
179
217
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
180
|
-
await
|
|
218
|
+
await ensurePrivateDir(dir);
|
|
181
219
|
const configPath = path.join(dir, "config.json");
|
|
182
|
-
if (!
|
|
220
|
+
if (!existsSync2(configPath)) {
|
|
183
221
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
184
222
|
}
|
|
185
223
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -192,6 +230,7 @@ async function loadConfig() {
|
|
|
192
230
|
`);
|
|
193
231
|
try {
|
|
194
232
|
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
233
|
+
await enforcePrivateFile(configPath);
|
|
195
234
|
} catch {
|
|
196
235
|
}
|
|
197
236
|
}
|
|
@@ -210,7 +249,7 @@ async function loadConfig() {
|
|
|
210
249
|
function loadConfigSync() {
|
|
211
250
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
212
251
|
const configPath = path.join(dir, "config.json");
|
|
213
|
-
if (!
|
|
252
|
+
if (!existsSync2(configPath)) {
|
|
214
253
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
215
254
|
}
|
|
216
255
|
try {
|
|
@@ -228,12 +267,10 @@ function loadConfigSync() {
|
|
|
228
267
|
}
|
|
229
268
|
async function saveConfig(config) {
|
|
230
269
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
231
|
-
await
|
|
270
|
+
await ensurePrivateDir(dir);
|
|
232
271
|
const configPath = path.join(dir, "config.json");
|
|
233
272
|
await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
234
|
-
|
|
235
|
-
await chmod(configPath, 384);
|
|
236
|
-
}
|
|
273
|
+
await enforcePrivateFile(configPath);
|
|
237
274
|
}
|
|
238
275
|
async function loadConfigFrom(configPath) {
|
|
239
276
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -253,6 +290,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
|
|
|
253
290
|
var init_config = __esm({
|
|
254
291
|
"src/lib/config.ts"() {
|
|
255
292
|
"use strict";
|
|
293
|
+
init_secure_files();
|
|
256
294
|
EXE_AI_DIR = resolveDataDir();
|
|
257
295
|
DB_PATH = path.join(EXE_AI_DIR, "memories.db");
|
|
258
296
|
MODELS_DIR = path.join(EXE_AI_DIR, "models");
|
|
@@ -331,7 +369,7 @@ var init_config = __esm({
|
|
|
331
369
|
|
|
332
370
|
// src/lib/employees.ts
|
|
333
371
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
334
|
-
import { existsSync as
|
|
372
|
+
import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
|
|
335
373
|
import { execSync } from "child_process";
|
|
336
374
|
import path2 from "path";
|
|
337
375
|
import os2 from "os";
|
|
@@ -355,7 +393,7 @@ function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
|
|
|
355
393
|
return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
|
|
356
394
|
}
|
|
357
395
|
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
358
|
-
if (!
|
|
396
|
+
if (!existsSync3(employeesPath)) {
|
|
359
397
|
return [];
|
|
360
398
|
}
|
|
361
399
|
const raw = await readFile2(employeesPath, "utf-8");
|
|
@@ -370,7 +408,7 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
|
370
408
|
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
371
409
|
}
|
|
372
410
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
373
|
-
if (!
|
|
411
|
+
if (!existsSync3(employeesPath)) return [];
|
|
374
412
|
try {
|
|
375
413
|
return JSON.parse(readFileSync2(employeesPath, "utf-8"));
|
|
376
414
|
} catch {
|
|
@@ -404,7 +442,7 @@ function registerBinSymlinks(name) {
|
|
|
404
442
|
for (const suffix of ["", "-opencode"]) {
|
|
405
443
|
const linkName = `${name}${suffix}`;
|
|
406
444
|
const linkPath = path2.join(binDir, linkName);
|
|
407
|
-
if (
|
|
445
|
+
if (existsSync3(linkPath)) {
|
|
408
446
|
skipped.push(linkName);
|
|
409
447
|
continue;
|
|
410
448
|
}
|
|
@@ -417,7 +455,7 @@ function registerBinSymlinks(name) {
|
|
|
417
455
|
}
|
|
418
456
|
return { created, skipped, errors };
|
|
419
457
|
}
|
|
420
|
-
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE;
|
|
458
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, IDENTITY_DIR;
|
|
421
459
|
var init_employees = __esm({
|
|
422
460
|
"src/lib/employees.ts"() {
|
|
423
461
|
"use strict";
|
|
@@ -425,16 +463,638 @@ var init_employees = __esm({
|
|
|
425
463
|
EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
|
|
426
464
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
427
465
|
COORDINATOR_ROLE = "COO";
|
|
466
|
+
IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
// src/lib/database-adapter.ts
|
|
471
|
+
import os3 from "os";
|
|
472
|
+
import path3 from "path";
|
|
473
|
+
import { createRequire } from "module";
|
|
474
|
+
import { pathToFileURL } from "url";
|
|
475
|
+
function quotedIdentifier(identifier) {
|
|
476
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
477
|
+
}
|
|
478
|
+
function unqualifiedTableName(name) {
|
|
479
|
+
const raw = name.trim().replace(/^"|"$/g, "");
|
|
480
|
+
const parts = raw.split(".");
|
|
481
|
+
return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
|
|
482
|
+
}
|
|
483
|
+
function stripTrailingSemicolon(sql) {
|
|
484
|
+
return sql.trim().replace(/;+\s*$/u, "");
|
|
485
|
+
}
|
|
486
|
+
function appendClause(sql, clause) {
|
|
487
|
+
const trimmed = stripTrailingSemicolon(sql);
|
|
488
|
+
const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
|
|
489
|
+
if (!returningMatch) {
|
|
490
|
+
return `${trimmed}${clause}`;
|
|
491
|
+
}
|
|
492
|
+
const idx = returningMatch.index;
|
|
493
|
+
return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
|
|
494
|
+
}
|
|
495
|
+
function normalizeStatement(stmt) {
|
|
496
|
+
if (typeof stmt === "string") {
|
|
497
|
+
return { kind: "positional", sql: stmt, args: [] };
|
|
498
|
+
}
|
|
499
|
+
const sql = stmt.sql;
|
|
500
|
+
if (Array.isArray(stmt.args) || stmt.args === void 0) {
|
|
501
|
+
return { kind: "positional", sql, args: stmt.args ?? [] };
|
|
502
|
+
}
|
|
503
|
+
return { kind: "named", sql, args: stmt.args };
|
|
504
|
+
}
|
|
505
|
+
function rewriteBooleanLiterals(sql) {
|
|
506
|
+
let out = sql;
|
|
507
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
508
|
+
const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
|
|
509
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
|
|
510
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
|
|
511
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
|
|
512
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
|
|
513
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
|
|
514
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
|
|
515
|
+
}
|
|
516
|
+
return out;
|
|
517
|
+
}
|
|
518
|
+
function rewriteInsertOrIgnore(sql) {
|
|
519
|
+
if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
|
|
520
|
+
return sql;
|
|
521
|
+
}
|
|
522
|
+
const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
|
|
523
|
+
return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
|
|
524
|
+
}
|
|
525
|
+
function rewriteInsertOrReplace(sql) {
|
|
526
|
+
const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
|
|
527
|
+
if (!match) {
|
|
528
|
+
return sql;
|
|
529
|
+
}
|
|
530
|
+
const rawTable = match[1];
|
|
531
|
+
const rawColumns = match[2];
|
|
532
|
+
const remainder = match[3];
|
|
533
|
+
const tableName = unqualifiedTableName(rawTable);
|
|
534
|
+
const conflictKeys = UPSERT_KEYS[tableName];
|
|
535
|
+
if (!conflictKeys?.length) {
|
|
536
|
+
return sql;
|
|
537
|
+
}
|
|
538
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
539
|
+
const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
|
|
540
|
+
const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
|
|
541
|
+
const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
|
|
542
|
+
return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
|
|
543
|
+
}
|
|
544
|
+
function rewriteSql(sql) {
|
|
545
|
+
let out = sql;
|
|
546
|
+
out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
|
|
547
|
+
out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
|
|
548
|
+
out = rewriteBooleanLiterals(out);
|
|
549
|
+
out = rewriteInsertOrReplace(out);
|
|
550
|
+
out = rewriteInsertOrIgnore(out);
|
|
551
|
+
return stripTrailingSemicolon(out);
|
|
552
|
+
}
|
|
553
|
+
function toBoolean(value) {
|
|
554
|
+
if (value === null || value === void 0) return value;
|
|
555
|
+
if (typeof value === "boolean") return value;
|
|
556
|
+
if (typeof value === "number") return value !== 0;
|
|
557
|
+
if (typeof value === "bigint") return value !== 0n;
|
|
558
|
+
if (typeof value === "string") {
|
|
559
|
+
const normalized = value.trim().toLowerCase();
|
|
560
|
+
if (normalized === "0" || normalized === "false") return false;
|
|
561
|
+
if (normalized === "1" || normalized === "true") return true;
|
|
562
|
+
}
|
|
563
|
+
return Boolean(value);
|
|
564
|
+
}
|
|
565
|
+
function countQuestionMarks(sql, end) {
|
|
566
|
+
let count = 0;
|
|
567
|
+
let inSingle = false;
|
|
568
|
+
let inDouble = false;
|
|
569
|
+
let inLineComment = false;
|
|
570
|
+
let inBlockComment = false;
|
|
571
|
+
for (let i = 0; i < end; i++) {
|
|
572
|
+
const ch = sql[i];
|
|
573
|
+
const next = sql[i + 1];
|
|
574
|
+
if (inLineComment) {
|
|
575
|
+
if (ch === "\n") inLineComment = false;
|
|
576
|
+
continue;
|
|
577
|
+
}
|
|
578
|
+
if (inBlockComment) {
|
|
579
|
+
if (ch === "*" && next === "/") {
|
|
580
|
+
inBlockComment = false;
|
|
581
|
+
i += 1;
|
|
582
|
+
}
|
|
583
|
+
continue;
|
|
584
|
+
}
|
|
585
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
586
|
+
inLineComment = true;
|
|
587
|
+
i += 1;
|
|
588
|
+
continue;
|
|
589
|
+
}
|
|
590
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
591
|
+
inBlockComment = true;
|
|
592
|
+
i += 1;
|
|
593
|
+
continue;
|
|
594
|
+
}
|
|
595
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
596
|
+
inSingle = !inSingle;
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
600
|
+
inDouble = !inDouble;
|
|
601
|
+
continue;
|
|
602
|
+
}
|
|
603
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
604
|
+
count += 1;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
return count;
|
|
608
|
+
}
|
|
609
|
+
function findBooleanPlaceholderIndexes(sql) {
|
|
610
|
+
const indexes = /* @__PURE__ */ new Set();
|
|
611
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
612
|
+
const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
|
|
613
|
+
for (const match of sql.matchAll(pattern)) {
|
|
614
|
+
const matchText = match[0];
|
|
615
|
+
const qIndex = match.index + matchText.lastIndexOf("?");
|
|
616
|
+
indexes.add(countQuestionMarks(sql, qIndex + 1));
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
return indexes;
|
|
620
|
+
}
|
|
621
|
+
function coerceInsertBooleanArgs(sql, args) {
|
|
622
|
+
const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
|
|
623
|
+
if (!match) return;
|
|
624
|
+
const rawTable = match[1];
|
|
625
|
+
const rawColumns = match[2];
|
|
626
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
627
|
+
if (!boolColumns?.size) return;
|
|
628
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
629
|
+
for (const [index, column] of columns.entries()) {
|
|
630
|
+
if (boolColumns.has(column) && index < args.length) {
|
|
631
|
+
args[index] = toBoolean(args[index]);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
function coerceUpdateBooleanArgs(sql, args) {
|
|
636
|
+
const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
|
|
637
|
+
if (!match) return;
|
|
638
|
+
const rawTable = match[1];
|
|
639
|
+
const setClause = match[2];
|
|
640
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
641
|
+
if (!boolColumns?.size) return;
|
|
642
|
+
const assignments = setClause.split(",");
|
|
643
|
+
let placeholderIndex = 0;
|
|
644
|
+
for (const assignment of assignments) {
|
|
645
|
+
if (!assignment.includes("?")) continue;
|
|
646
|
+
placeholderIndex += 1;
|
|
647
|
+
const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
|
|
648
|
+
if (colMatch && boolColumns.has(colMatch[1])) {
|
|
649
|
+
args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
function coerceBooleanArgs(sql, args) {
|
|
654
|
+
const nextArgs = [...args];
|
|
655
|
+
coerceInsertBooleanArgs(sql, nextArgs);
|
|
656
|
+
coerceUpdateBooleanArgs(sql, nextArgs);
|
|
657
|
+
const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
|
|
658
|
+
for (const index of placeholderIndexes) {
|
|
659
|
+
if (index > 0 && index <= nextArgs.length) {
|
|
660
|
+
nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
return nextArgs;
|
|
664
|
+
}
|
|
665
|
+
function convertQuestionMarksToDollarParams(sql) {
|
|
666
|
+
let out = "";
|
|
667
|
+
let placeholder = 0;
|
|
668
|
+
let inSingle = false;
|
|
669
|
+
let inDouble = false;
|
|
670
|
+
let inLineComment = false;
|
|
671
|
+
let inBlockComment = false;
|
|
672
|
+
for (let i = 0; i < sql.length; i++) {
|
|
673
|
+
const ch = sql[i];
|
|
674
|
+
const next = sql[i + 1];
|
|
675
|
+
if (inLineComment) {
|
|
676
|
+
out += ch;
|
|
677
|
+
if (ch === "\n") inLineComment = false;
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
if (inBlockComment) {
|
|
681
|
+
out += ch;
|
|
682
|
+
if (ch === "*" && next === "/") {
|
|
683
|
+
out += next;
|
|
684
|
+
inBlockComment = false;
|
|
685
|
+
i += 1;
|
|
686
|
+
}
|
|
687
|
+
continue;
|
|
688
|
+
}
|
|
689
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
690
|
+
out += ch + next;
|
|
691
|
+
inLineComment = true;
|
|
692
|
+
i += 1;
|
|
693
|
+
continue;
|
|
694
|
+
}
|
|
695
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
696
|
+
out += ch + next;
|
|
697
|
+
inBlockComment = true;
|
|
698
|
+
i += 1;
|
|
699
|
+
continue;
|
|
700
|
+
}
|
|
701
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
702
|
+
inSingle = !inSingle;
|
|
703
|
+
out += ch;
|
|
704
|
+
continue;
|
|
705
|
+
}
|
|
706
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
707
|
+
inDouble = !inDouble;
|
|
708
|
+
out += ch;
|
|
709
|
+
continue;
|
|
710
|
+
}
|
|
711
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
712
|
+
placeholder += 1;
|
|
713
|
+
out += `$${placeholder}`;
|
|
714
|
+
continue;
|
|
715
|
+
}
|
|
716
|
+
out += ch;
|
|
717
|
+
}
|
|
718
|
+
return out;
|
|
719
|
+
}
|
|
720
|
+
function translateStatementForPostgres(stmt) {
|
|
721
|
+
const normalized = normalizeStatement(stmt);
|
|
722
|
+
if (normalized.kind === "named") {
|
|
723
|
+
throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
|
|
724
|
+
}
|
|
725
|
+
const rewrittenSql = rewriteSql(normalized.sql);
|
|
726
|
+
const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
|
|
727
|
+
return {
|
|
728
|
+
sql: convertQuestionMarksToDollarParams(rewrittenSql),
|
|
729
|
+
args: coercedArgs
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
function shouldBypassPostgres(stmt) {
|
|
733
|
+
const normalized = normalizeStatement(stmt);
|
|
734
|
+
if (normalized.kind === "named") {
|
|
735
|
+
return true;
|
|
736
|
+
}
|
|
737
|
+
return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
|
|
738
|
+
}
|
|
739
|
+
function shouldFallbackOnError(error) {
|
|
740
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
741
|
+
return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
|
|
742
|
+
}
|
|
743
|
+
function isReadQuery(sql) {
|
|
744
|
+
const trimmed = sql.trimStart();
|
|
745
|
+
return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
|
|
746
|
+
}
|
|
747
|
+
function buildRow(row, columns) {
|
|
748
|
+
const values = columns.map((column) => row[column]);
|
|
749
|
+
return Object.assign(values, row);
|
|
750
|
+
}
|
|
751
|
+
function buildResultSet(rows, rowsAffected = 0) {
|
|
752
|
+
const columns = rows[0] ? Object.keys(rows[0]) : [];
|
|
753
|
+
const resultRows = rows.map((row) => buildRow(row, columns));
|
|
754
|
+
return {
|
|
755
|
+
columns,
|
|
756
|
+
columnTypes: columns.map(() => ""),
|
|
757
|
+
rows: resultRows,
|
|
758
|
+
rowsAffected,
|
|
759
|
+
lastInsertRowid: void 0,
|
|
760
|
+
toJSON() {
|
|
761
|
+
return {
|
|
762
|
+
columns,
|
|
763
|
+
columnTypes: columns.map(() => ""),
|
|
764
|
+
rows,
|
|
765
|
+
rowsAffected,
|
|
766
|
+
lastInsertRowid: void 0
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
async function loadPrismaClient() {
|
|
772
|
+
if (!prismaClientPromise) {
|
|
773
|
+
prismaClientPromise = (async () => {
|
|
774
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
775
|
+
if (explicitPath) {
|
|
776
|
+
const module2 = await import(pathToFileURL(explicitPath).href);
|
|
777
|
+
const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
|
|
778
|
+
if (!PrismaClient2) {
|
|
779
|
+
throw new Error(`No PrismaClient export found at ${explicitPath}`);
|
|
780
|
+
}
|
|
781
|
+
return new PrismaClient2();
|
|
782
|
+
}
|
|
783
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path3.join(os3.homedir(), "exe-db");
|
|
784
|
+
const requireFromExeDb = createRequire(path3.join(exeDbRoot, "package.json"));
|
|
785
|
+
const prismaEntry = requireFromExeDb.resolve("@prisma/client");
|
|
786
|
+
const module = await import(pathToFileURL(prismaEntry).href);
|
|
787
|
+
const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
|
|
788
|
+
if (!PrismaClient) {
|
|
789
|
+
throw new Error(`No PrismaClient export found in ${prismaEntry}`);
|
|
790
|
+
}
|
|
791
|
+
return new PrismaClient();
|
|
792
|
+
})();
|
|
793
|
+
}
|
|
794
|
+
return prismaClientPromise;
|
|
795
|
+
}
|
|
796
|
+
async function ensureCompatibilityViews(prisma) {
|
|
797
|
+
if (!compatibilityBootstrapPromise) {
|
|
798
|
+
compatibilityBootstrapPromise = (async () => {
|
|
799
|
+
for (const mapping of VIEW_MAPPINGS) {
|
|
800
|
+
const relation = mapping.source.replace(/"/g, "");
|
|
801
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
802
|
+
"SELECT to_regclass($1) AS regclass",
|
|
803
|
+
relation
|
|
804
|
+
);
|
|
805
|
+
if (!rows[0]?.regclass) {
|
|
806
|
+
continue;
|
|
807
|
+
}
|
|
808
|
+
await prisma.$executeRawUnsafe(
|
|
809
|
+
`CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
|
|
810
|
+
);
|
|
811
|
+
}
|
|
812
|
+
})();
|
|
813
|
+
}
|
|
814
|
+
return compatibilityBootstrapPromise;
|
|
815
|
+
}
|
|
816
|
+
async function executeOnPrisma(executor, stmt) {
|
|
817
|
+
const translated = translateStatementForPostgres(stmt);
|
|
818
|
+
if (isReadQuery(translated.sql)) {
|
|
819
|
+
const rows = await executor.$queryRawUnsafe(
|
|
820
|
+
translated.sql,
|
|
821
|
+
...translated.args
|
|
822
|
+
);
|
|
823
|
+
return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
|
|
824
|
+
}
|
|
825
|
+
const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
|
|
826
|
+
return buildResultSet([], rowsAffected);
|
|
827
|
+
}
|
|
828
|
+
function splitSqlStatements(sql) {
|
|
829
|
+
const parts = [];
|
|
830
|
+
let current = "";
|
|
831
|
+
let inSingle = false;
|
|
832
|
+
let inDouble = false;
|
|
833
|
+
let inLineComment = false;
|
|
834
|
+
let inBlockComment = false;
|
|
835
|
+
for (let i = 0; i < sql.length; i++) {
|
|
836
|
+
const ch = sql[i];
|
|
837
|
+
const next = sql[i + 1];
|
|
838
|
+
if (inLineComment) {
|
|
839
|
+
current += ch;
|
|
840
|
+
if (ch === "\n") inLineComment = false;
|
|
841
|
+
continue;
|
|
842
|
+
}
|
|
843
|
+
if (inBlockComment) {
|
|
844
|
+
current += ch;
|
|
845
|
+
if (ch === "*" && next === "/") {
|
|
846
|
+
current += next;
|
|
847
|
+
inBlockComment = false;
|
|
848
|
+
i += 1;
|
|
849
|
+
}
|
|
850
|
+
continue;
|
|
851
|
+
}
|
|
852
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
853
|
+
current += ch + next;
|
|
854
|
+
inLineComment = true;
|
|
855
|
+
i += 1;
|
|
856
|
+
continue;
|
|
857
|
+
}
|
|
858
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
859
|
+
current += ch + next;
|
|
860
|
+
inBlockComment = true;
|
|
861
|
+
i += 1;
|
|
862
|
+
continue;
|
|
863
|
+
}
|
|
864
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
865
|
+
inSingle = !inSingle;
|
|
866
|
+
current += ch;
|
|
867
|
+
continue;
|
|
868
|
+
}
|
|
869
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
870
|
+
inDouble = !inDouble;
|
|
871
|
+
current += ch;
|
|
872
|
+
continue;
|
|
873
|
+
}
|
|
874
|
+
if (!inSingle && !inDouble && ch === ";") {
|
|
875
|
+
if (current.trim()) {
|
|
876
|
+
parts.push(current.trim());
|
|
877
|
+
}
|
|
878
|
+
current = "";
|
|
879
|
+
continue;
|
|
880
|
+
}
|
|
881
|
+
current += ch;
|
|
882
|
+
}
|
|
883
|
+
if (current.trim()) {
|
|
884
|
+
parts.push(current.trim());
|
|
885
|
+
}
|
|
886
|
+
return parts;
|
|
887
|
+
}
|
|
888
|
+
async function createPrismaDbAdapter(fallbackClient) {
|
|
889
|
+
const prisma = await loadPrismaClient();
|
|
890
|
+
await ensureCompatibilityViews(prisma);
|
|
891
|
+
let closed = false;
|
|
892
|
+
let adapter;
|
|
893
|
+
const fallbackExecute = async (stmt, error) => {
|
|
894
|
+
if (!fallbackClient) {
|
|
895
|
+
if (error) throw error;
|
|
896
|
+
throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
|
|
897
|
+
}
|
|
898
|
+
if (error) {
|
|
899
|
+
process.stderr.write(
|
|
900
|
+
`[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
901
|
+
`
|
|
902
|
+
);
|
|
903
|
+
}
|
|
904
|
+
return fallbackClient.execute(stmt);
|
|
905
|
+
};
|
|
906
|
+
adapter = {
|
|
907
|
+
async execute(stmt) {
|
|
908
|
+
if (shouldBypassPostgres(stmt)) {
|
|
909
|
+
return fallbackExecute(stmt);
|
|
910
|
+
}
|
|
911
|
+
try {
|
|
912
|
+
return await executeOnPrisma(prisma, stmt);
|
|
913
|
+
} catch (error) {
|
|
914
|
+
if (shouldFallbackOnError(error)) {
|
|
915
|
+
return fallbackExecute(stmt, error);
|
|
916
|
+
}
|
|
917
|
+
throw error;
|
|
918
|
+
}
|
|
919
|
+
},
|
|
920
|
+
async batch(stmts, mode) {
|
|
921
|
+
if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
|
|
922
|
+
if (!fallbackClient) {
|
|
923
|
+
throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
|
|
924
|
+
}
|
|
925
|
+
return fallbackClient.batch(stmts, mode);
|
|
926
|
+
}
|
|
927
|
+
try {
|
|
928
|
+
if (prisma.$transaction) {
|
|
929
|
+
return await prisma.$transaction(async (tx) => {
|
|
930
|
+
const results2 = [];
|
|
931
|
+
for (const stmt of stmts) {
|
|
932
|
+
results2.push(await executeOnPrisma(tx, stmt));
|
|
933
|
+
}
|
|
934
|
+
return results2;
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
const results = [];
|
|
938
|
+
for (const stmt of stmts) {
|
|
939
|
+
results.push(await executeOnPrisma(prisma, stmt));
|
|
940
|
+
}
|
|
941
|
+
return results;
|
|
942
|
+
} catch (error) {
|
|
943
|
+
if (fallbackClient && shouldFallbackOnError(error)) {
|
|
944
|
+
process.stderr.write(
|
|
945
|
+
`[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
946
|
+
`
|
|
947
|
+
);
|
|
948
|
+
return fallbackClient.batch(stmts, mode);
|
|
949
|
+
}
|
|
950
|
+
throw error;
|
|
951
|
+
}
|
|
952
|
+
},
|
|
953
|
+
async migrate(stmts) {
|
|
954
|
+
if (fallbackClient) {
|
|
955
|
+
return fallbackClient.migrate(stmts);
|
|
956
|
+
}
|
|
957
|
+
return adapter.batch(stmts, "deferred");
|
|
958
|
+
},
|
|
959
|
+
async transaction(mode) {
|
|
960
|
+
if (!fallbackClient) {
|
|
961
|
+
throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
|
|
962
|
+
}
|
|
963
|
+
return fallbackClient.transaction(mode);
|
|
964
|
+
},
|
|
965
|
+
async executeMultiple(sql) {
|
|
966
|
+
if (fallbackClient && shouldBypassPostgres(sql)) {
|
|
967
|
+
return fallbackClient.executeMultiple(sql);
|
|
968
|
+
}
|
|
969
|
+
for (const statement of splitSqlStatements(sql)) {
|
|
970
|
+
await adapter.execute(statement);
|
|
971
|
+
}
|
|
972
|
+
},
|
|
973
|
+
async sync() {
|
|
974
|
+
if (fallbackClient) {
|
|
975
|
+
return fallbackClient.sync();
|
|
976
|
+
}
|
|
977
|
+
return { frame_no: 0, frames_synced: 0 };
|
|
978
|
+
},
|
|
979
|
+
close() {
|
|
980
|
+
closed = true;
|
|
981
|
+
prismaClientPromise = null;
|
|
982
|
+
compatibilityBootstrapPromise = null;
|
|
983
|
+
void prisma.$disconnect?.();
|
|
984
|
+
},
|
|
985
|
+
get closed() {
|
|
986
|
+
return closed;
|
|
987
|
+
},
|
|
988
|
+
get protocol() {
|
|
989
|
+
return "prisma-postgres";
|
|
990
|
+
}
|
|
991
|
+
};
|
|
992
|
+
return adapter;
|
|
993
|
+
}
|
|
994
|
+
var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
|
|
995
|
+
var init_database_adapter = __esm({
|
|
996
|
+
"src/lib/database-adapter.ts"() {
|
|
997
|
+
"use strict";
|
|
998
|
+
VIEW_MAPPINGS = [
|
|
999
|
+
{ view: "memories", source: "memory.memory_records" },
|
|
1000
|
+
{ view: "tasks", source: "memory.tasks" },
|
|
1001
|
+
{ view: "behaviors", source: "memory.behaviors" },
|
|
1002
|
+
{ view: "entities", source: "memory.entities" },
|
|
1003
|
+
{ view: "relationships", source: "memory.relationships" },
|
|
1004
|
+
{ view: "entity_memories", source: "memory.entity_memories" },
|
|
1005
|
+
{ view: "entity_aliases", source: "memory.entity_aliases" },
|
|
1006
|
+
{ view: "notifications", source: "memory.notifications" },
|
|
1007
|
+
{ view: "messages", source: "memory.messages" },
|
|
1008
|
+
{ view: "users", source: "wiki.users" },
|
|
1009
|
+
{ view: "workspaces", source: "wiki.workspaces" },
|
|
1010
|
+
{ view: "workspace_users", source: "wiki.workspace_users" },
|
|
1011
|
+
{ view: "documents", source: "wiki.workspace_documents" },
|
|
1012
|
+
{ view: "chats", source: "wiki.workspace_chats" }
|
|
1013
|
+
];
|
|
1014
|
+
UPSERT_KEYS = {
|
|
1015
|
+
memories: ["id"],
|
|
1016
|
+
tasks: ["id"],
|
|
1017
|
+
behaviors: ["id"],
|
|
1018
|
+
entities: ["id"],
|
|
1019
|
+
relationships: ["id"],
|
|
1020
|
+
entity_aliases: ["alias"],
|
|
1021
|
+
notifications: ["id"],
|
|
1022
|
+
messages: ["id"],
|
|
1023
|
+
users: ["id"],
|
|
1024
|
+
workspaces: ["id"],
|
|
1025
|
+
workspace_users: ["id"],
|
|
1026
|
+
documents: ["id"],
|
|
1027
|
+
chats: ["id"]
|
|
1028
|
+
};
|
|
1029
|
+
BOOLEAN_COLUMNS_BY_TABLE = {
|
|
1030
|
+
memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
|
|
1031
|
+
behaviors: /* @__PURE__ */ new Set(["active"]),
|
|
1032
|
+
notifications: /* @__PURE__ */ new Set(["read"]),
|
|
1033
|
+
users: /* @__PURE__ */ new Set(["has_personal_memory"])
|
|
1034
|
+
};
|
|
1035
|
+
BOOLEAN_COLUMN_NAMES = new Set(
|
|
1036
|
+
Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
|
|
1037
|
+
);
|
|
1038
|
+
IMMEDIATE_FALLBACK_PATTERNS = [
|
|
1039
|
+
/\bPRAGMA\b/i,
|
|
1040
|
+
/\bsqlite_master\b/i,
|
|
1041
|
+
/(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
|
|
1042
|
+
/\bMATCH\b/i,
|
|
1043
|
+
/\bvector_distance_cos\s*\(/i,
|
|
1044
|
+
/\bjson_extract\s*\(/i,
|
|
1045
|
+
/\bjulianday\s*\(/i,
|
|
1046
|
+
/\bstrftime\s*\(/i,
|
|
1047
|
+
/\blast_insert_rowid\s*\(/i
|
|
1048
|
+
];
|
|
1049
|
+
prismaClientPromise = null;
|
|
1050
|
+
compatibilityBootstrapPromise = null;
|
|
1051
|
+
}
|
|
1052
|
+
});
|
|
1053
|
+
|
|
1054
|
+
// src/lib/daemon-auth.ts
|
|
1055
|
+
import crypto from "crypto";
|
|
1056
|
+
import path4 from "path";
|
|
1057
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
1058
|
+
function normalizeToken(token) {
|
|
1059
|
+
if (!token) return null;
|
|
1060
|
+
const trimmed = token.trim();
|
|
1061
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
1062
|
+
}
|
|
1063
|
+
function readDaemonToken() {
|
|
1064
|
+
try {
|
|
1065
|
+
if (!existsSync4(DAEMON_TOKEN_PATH)) return null;
|
|
1066
|
+
return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
|
|
1067
|
+
} catch {
|
|
1068
|
+
return null;
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
function ensureDaemonToken(seed) {
|
|
1072
|
+
const existing = readDaemonToken();
|
|
1073
|
+
if (existing) return existing;
|
|
1074
|
+
const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
|
|
1075
|
+
ensurePrivateDirSync(EXE_AI_DIR);
|
|
1076
|
+
writeFileSync2(DAEMON_TOKEN_PATH, `${token}
|
|
1077
|
+
`, "utf8");
|
|
1078
|
+
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
1079
|
+
return token;
|
|
1080
|
+
}
|
|
1081
|
+
var DAEMON_TOKEN_PATH;
|
|
1082
|
+
var init_daemon_auth = __esm({
|
|
1083
|
+
"src/lib/daemon-auth.ts"() {
|
|
1084
|
+
"use strict";
|
|
1085
|
+
init_config();
|
|
1086
|
+
init_secure_files();
|
|
1087
|
+
DAEMON_TOKEN_PATH = path4.join(EXE_AI_DIR, "exed.token");
|
|
428
1088
|
}
|
|
429
1089
|
});
|
|
430
1090
|
|
|
431
1091
|
// src/lib/exe-daemon-client.ts
|
|
432
1092
|
import net from "net";
|
|
433
|
-
import
|
|
1093
|
+
import os4 from "os";
|
|
434
1094
|
import { spawn } from "child_process";
|
|
435
1095
|
import { randomUUID } from "crypto";
|
|
436
|
-
import { existsSync as
|
|
437
|
-
import
|
|
1096
|
+
import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
|
|
1097
|
+
import path5 from "path";
|
|
438
1098
|
import { fileURLToPath } from "url";
|
|
439
1099
|
function handleData(chunk) {
|
|
440
1100
|
_buffer += chunk.toString();
|
|
@@ -462,9 +1122,9 @@ function handleData(chunk) {
|
|
|
462
1122
|
}
|
|
463
1123
|
}
|
|
464
1124
|
function cleanupStaleFiles() {
|
|
465
|
-
if (
|
|
1125
|
+
if (existsSync5(PID_PATH)) {
|
|
466
1126
|
try {
|
|
467
|
-
const pid = parseInt(
|
|
1127
|
+
const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
|
|
468
1128
|
if (pid > 0) {
|
|
469
1129
|
try {
|
|
470
1130
|
process.kill(pid, 0);
|
|
@@ -485,17 +1145,17 @@ function cleanupStaleFiles() {
|
|
|
485
1145
|
}
|
|
486
1146
|
}
|
|
487
1147
|
function findPackageRoot() {
|
|
488
|
-
let dir =
|
|
489
|
-
const { root } =
|
|
1148
|
+
let dir = path5.dirname(fileURLToPath(import.meta.url));
|
|
1149
|
+
const { root } = path5.parse(dir);
|
|
490
1150
|
while (dir !== root) {
|
|
491
|
-
if (
|
|
492
|
-
dir =
|
|
1151
|
+
if (existsSync5(path5.join(dir, "package.json"))) return dir;
|
|
1152
|
+
dir = path5.dirname(dir);
|
|
493
1153
|
}
|
|
494
1154
|
return null;
|
|
495
1155
|
}
|
|
496
1156
|
function spawnDaemon() {
|
|
497
|
-
const freeGB =
|
|
498
|
-
const totalGB =
|
|
1157
|
+
const freeGB = os4.freemem() / (1024 * 1024 * 1024);
|
|
1158
|
+
const totalGB = os4.totalmem() / (1024 * 1024 * 1024);
|
|
499
1159
|
if (totalGB <= 8) {
|
|
500
1160
|
process.stderr.write(
|
|
501
1161
|
`[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
|
|
@@ -515,16 +1175,17 @@ function spawnDaemon() {
|
|
|
515
1175
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
516
1176
|
return;
|
|
517
1177
|
}
|
|
518
|
-
const daemonPath =
|
|
519
|
-
if (!
|
|
1178
|
+
const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
1179
|
+
if (!existsSync5(daemonPath)) {
|
|
520
1180
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
521
1181
|
`);
|
|
522
1182
|
return;
|
|
523
1183
|
}
|
|
524
1184
|
const resolvedPath = daemonPath;
|
|
1185
|
+
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
525
1186
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
526
1187
|
`);
|
|
527
|
-
const logPath =
|
|
1188
|
+
const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
|
|
528
1189
|
let stderrFd = "ignore";
|
|
529
1190
|
try {
|
|
530
1191
|
stderrFd = openSync(logPath, "a");
|
|
@@ -542,7 +1203,8 @@ function spawnDaemon() {
|
|
|
542
1203
|
TMUX_PANE: void 0,
|
|
543
1204
|
// Prevents resolveExeSession() from scoping to one session
|
|
544
1205
|
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
545
|
-
EXE_DAEMON_PID: PID_PATH
|
|
1206
|
+
EXE_DAEMON_PID: PID_PATH,
|
|
1207
|
+
[DAEMON_TOKEN_ENV]: daemonToken
|
|
546
1208
|
}
|
|
547
1209
|
});
|
|
548
1210
|
child.unref();
|
|
@@ -652,13 +1314,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
652
1314
|
return;
|
|
653
1315
|
}
|
|
654
1316
|
const id = randomUUID();
|
|
1317
|
+
const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
|
|
655
1318
|
const timer = setTimeout(() => {
|
|
656
1319
|
_pending.delete(id);
|
|
657
1320
|
resolve({ error: "Request timeout" });
|
|
658
1321
|
}, timeoutMs);
|
|
659
1322
|
_pending.set(id, { resolve, timer });
|
|
660
1323
|
try {
|
|
661
|
-
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
1324
|
+
_socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
|
|
662
1325
|
} catch {
|
|
663
1326
|
clearTimeout(timer);
|
|
664
1327
|
_pending.delete(id);
|
|
@@ -675,74 +1338,123 @@ async function pingDaemon() {
|
|
|
675
1338
|
return null;
|
|
676
1339
|
}
|
|
677
1340
|
function killAndRespawnDaemon() {
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
1341
|
+
if (!acquireSpawnLock()) {
|
|
1342
|
+
process.stderr.write("[exed-client] Another process is already restarting daemon \u2014 skipping\n");
|
|
1343
|
+
if (_socket) {
|
|
1344
|
+
_socket.destroy();
|
|
1345
|
+
_socket = null;
|
|
1346
|
+
}
|
|
1347
|
+
_connected = false;
|
|
1348
|
+
_buffer = "";
|
|
1349
|
+
return;
|
|
1350
|
+
}
|
|
1351
|
+
try {
|
|
1352
|
+
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
1353
|
+
if (existsSync5(PID_PATH)) {
|
|
1354
|
+
try {
|
|
1355
|
+
const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
|
|
1356
|
+
if (pid > 0) {
|
|
1357
|
+
try {
|
|
1358
|
+
process.kill(pid, "SIGKILL");
|
|
1359
|
+
} catch {
|
|
1360
|
+
}
|
|
686
1361
|
}
|
|
1362
|
+
} catch {
|
|
687
1363
|
}
|
|
1364
|
+
}
|
|
1365
|
+
if (_socket) {
|
|
1366
|
+
_socket.destroy();
|
|
1367
|
+
_socket = null;
|
|
1368
|
+
}
|
|
1369
|
+
_connected = false;
|
|
1370
|
+
_buffer = "";
|
|
1371
|
+
try {
|
|
1372
|
+
unlinkSync2(PID_PATH);
|
|
688
1373
|
} catch {
|
|
689
1374
|
}
|
|
1375
|
+
try {
|
|
1376
|
+
unlinkSync2(SOCKET_PATH);
|
|
1377
|
+
} catch {
|
|
1378
|
+
}
|
|
1379
|
+
spawnDaemon();
|
|
1380
|
+
} finally {
|
|
1381
|
+
releaseSpawnLock();
|
|
690
1382
|
}
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
_socket = null;
|
|
694
|
-
}
|
|
695
|
-
_connected = false;
|
|
696
|
-
_buffer = "";
|
|
1383
|
+
}
|
|
1384
|
+
function isDaemonTooYoung() {
|
|
697
1385
|
try {
|
|
698
|
-
|
|
1386
|
+
const stat = statSync(PID_PATH);
|
|
1387
|
+
return Date.now() - stat.mtimeMs < MIN_DAEMON_AGE_MS;
|
|
699
1388
|
} catch {
|
|
1389
|
+
return false;
|
|
700
1390
|
}
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
1391
|
+
}
|
|
1392
|
+
async function retryThenRestart(doRequest, label) {
|
|
1393
|
+
const result = await doRequest();
|
|
1394
|
+
if (!result.error) {
|
|
1395
|
+
_consecutiveFailures = 0;
|
|
1396
|
+
return result;
|
|
704
1397
|
}
|
|
705
|
-
|
|
1398
|
+
_consecutiveFailures++;
|
|
1399
|
+
for (let i = 0; i < MAX_RETRIES_BEFORE_RESTART; i++) {
|
|
1400
|
+
const delayMs = RETRY_DELAYS_MS[i] ?? 5e3;
|
|
1401
|
+
process.stderr.write(`[exed-client] ${label} failed (${result.error}), retry ${i + 1}/${MAX_RETRIES_BEFORE_RESTART} in ${delayMs}ms
|
|
1402
|
+
`);
|
|
1403
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
1404
|
+
if (!_connected) {
|
|
1405
|
+
if (!await connectToSocket()) continue;
|
|
1406
|
+
}
|
|
1407
|
+
const retry = await doRequest();
|
|
1408
|
+
if (!retry.error) {
|
|
1409
|
+
_consecutiveFailures = 0;
|
|
1410
|
+
return retry;
|
|
1411
|
+
}
|
|
1412
|
+
_consecutiveFailures++;
|
|
1413
|
+
}
|
|
1414
|
+
if (isDaemonTooYoung()) {
|
|
1415
|
+
process.stderr.write(`[exed-client] ${label}: daemon too young (< ${MIN_DAEMON_AGE_MS / 1e3}s) \u2014 skipping restart
|
|
1416
|
+
`);
|
|
1417
|
+
return { error: result.error };
|
|
1418
|
+
}
|
|
1419
|
+
process.stderr.write(`[exed-client] ${label}: ${_consecutiveFailures} consecutive failures \u2014 restarting daemon
|
|
1420
|
+
`);
|
|
1421
|
+
killAndRespawnDaemon();
|
|
1422
|
+
const start = Date.now();
|
|
1423
|
+
let delay2 = 200;
|
|
1424
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
1425
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
1426
|
+
if (await connectToSocket()) break;
|
|
1427
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
1428
|
+
}
|
|
1429
|
+
if (!_connected) return { error: "Daemon restart failed" };
|
|
1430
|
+
const final = await doRequest();
|
|
1431
|
+
if (!final.error) _consecutiveFailures = 0;
|
|
1432
|
+
return final;
|
|
706
1433
|
}
|
|
707
1434
|
async function embedViaClient(text, priority = "high") {
|
|
708
1435
|
if (!_connected && !await connectEmbedDaemon()) return null;
|
|
709
1436
|
_requestCount++;
|
|
710
1437
|
if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
|
|
711
1438
|
const health = await pingDaemon();
|
|
712
|
-
if (!health) {
|
|
1439
|
+
if (!health && !isDaemonTooYoung()) {
|
|
713
1440
|
process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
|
|
714
1441
|
`);
|
|
715
1442
|
killAndRespawnDaemon();
|
|
716
1443
|
const start = Date.now();
|
|
717
|
-
let
|
|
1444
|
+
let d = 200;
|
|
718
1445
|
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
719
|
-
await new Promise((r) => setTimeout(r,
|
|
1446
|
+
await new Promise((r) => setTimeout(r, d));
|
|
720
1447
|
if (await connectToSocket()) break;
|
|
721
|
-
|
|
1448
|
+
d = Math.min(d * 2, 3e3);
|
|
722
1449
|
}
|
|
723
1450
|
if (!_connected) return null;
|
|
724
1451
|
}
|
|
725
1452
|
}
|
|
726
|
-
const result = await
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
killAndRespawnDaemon();
|
|
732
|
-
const start = Date.now();
|
|
733
|
-
let delay2 = 200;
|
|
734
|
-
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
735
|
-
await new Promise((r) => setTimeout(r, delay2));
|
|
736
|
-
if (await connectToSocket()) break;
|
|
737
|
-
delay2 = Math.min(delay2 * 2, 3e3);
|
|
738
|
-
}
|
|
739
|
-
if (!_connected) return null;
|
|
740
|
-
const retry = await sendRequest([text], priority);
|
|
741
|
-
if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
|
|
742
|
-
process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
|
|
743
|
-
`);
|
|
744
|
-
}
|
|
745
|
-
return null;
|
|
1453
|
+
const result = await retryThenRestart(
|
|
1454
|
+
() => sendRequest([text], priority),
|
|
1455
|
+
"Embed"
|
|
1456
|
+
);
|
|
1457
|
+
return !result.error && result.vectors?.[0] ? result.vectors[0] : null;
|
|
746
1458
|
}
|
|
747
1459
|
function disconnectClient() {
|
|
748
1460
|
if (_socket) {
|
|
@@ -760,22 +1472,28 @@ function disconnectClient() {
|
|
|
760
1472
|
function isClientConnected() {
|
|
761
1473
|
return _connected;
|
|
762
1474
|
}
|
|
763
|
-
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;
|
|
1475
|
+
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;
|
|
764
1476
|
var init_exe_daemon_client = __esm({
|
|
765
1477
|
"src/lib/exe-daemon-client.ts"() {
|
|
766
1478
|
"use strict";
|
|
767
1479
|
init_config();
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
1480
|
+
init_daemon_auth();
|
|
1481
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
|
|
1482
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
|
|
1483
|
+
SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
771
1484
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
772
1485
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
773
1486
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
1487
|
+
DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
|
|
774
1488
|
_socket = null;
|
|
775
1489
|
_connected = false;
|
|
776
1490
|
_buffer = "";
|
|
777
1491
|
_requestCount = 0;
|
|
1492
|
+
_consecutiveFailures = 0;
|
|
778
1493
|
HEALTH_CHECK_INTERVAL = 100;
|
|
1494
|
+
MAX_RETRIES_BEFORE_RESTART = 3;
|
|
1495
|
+
RETRY_DELAYS_MS = [1e3, 3e3, 5e3];
|
|
1496
|
+
MIN_DAEMON_AGE_MS = 3e4;
|
|
779
1497
|
_pending = /* @__PURE__ */ new Map();
|
|
780
1498
|
MAX_BUFFER = 1e7;
|
|
781
1499
|
}
|
|
@@ -851,7 +1569,7 @@ __export(db_daemon_client_exports, {
|
|
|
851
1569
|
createDaemonDbClient: () => createDaemonDbClient,
|
|
852
1570
|
initDaemonDbClient: () => initDaemonDbClient
|
|
853
1571
|
});
|
|
854
|
-
function
|
|
1572
|
+
function normalizeStatement2(stmt) {
|
|
855
1573
|
if (typeof stmt === "string") {
|
|
856
1574
|
return { sql: stmt, args: [] };
|
|
857
1575
|
}
|
|
@@ -875,7 +1593,7 @@ function createDaemonDbClient(fallbackClient) {
|
|
|
875
1593
|
if (!_useDaemon || !isClientConnected()) {
|
|
876
1594
|
return fallbackClient.execute(stmt);
|
|
877
1595
|
}
|
|
878
|
-
const { sql, args } =
|
|
1596
|
+
const { sql, args } = normalizeStatement2(stmt);
|
|
879
1597
|
const response = await sendDaemonRequest({
|
|
880
1598
|
type: "db-execute",
|
|
881
1599
|
sql,
|
|
@@ -900,7 +1618,7 @@ function createDaemonDbClient(fallbackClient) {
|
|
|
900
1618
|
if (!_useDaemon || !isClientConnected()) {
|
|
901
1619
|
return fallbackClient.batch(stmts, mode);
|
|
902
1620
|
}
|
|
903
|
-
const statements = stmts.map(
|
|
1621
|
+
const statements = stmts.map(normalizeStatement2);
|
|
904
1622
|
const response = await sendDaemonRequest({
|
|
905
1623
|
type: "db-batch",
|
|
906
1624
|
statements,
|
|
@@ -995,6 +1713,18 @@ __export(database_exports, {
|
|
|
995
1713
|
});
|
|
996
1714
|
import { createClient } from "@libsql/client";
|
|
997
1715
|
async function initDatabase(config) {
|
|
1716
|
+
if (_walCheckpointTimer) {
|
|
1717
|
+
clearInterval(_walCheckpointTimer);
|
|
1718
|
+
_walCheckpointTimer = null;
|
|
1719
|
+
}
|
|
1720
|
+
if (_daemonClient) {
|
|
1721
|
+
_daemonClient.close();
|
|
1722
|
+
_daemonClient = null;
|
|
1723
|
+
}
|
|
1724
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
1725
|
+
_adapterClient.close();
|
|
1726
|
+
}
|
|
1727
|
+
_adapterClient = null;
|
|
998
1728
|
if (_client) {
|
|
999
1729
|
_client.close();
|
|
1000
1730
|
_client = null;
|
|
@@ -1008,6 +1738,7 @@ async function initDatabase(config) {
|
|
|
1008
1738
|
}
|
|
1009
1739
|
_client = createClient(opts);
|
|
1010
1740
|
_resilientClient = wrapWithRetry(_client);
|
|
1741
|
+
_adapterClient = _resilientClient;
|
|
1011
1742
|
_client.execute("PRAGMA busy_timeout = 30000").catch(() => {
|
|
1012
1743
|
});
|
|
1013
1744
|
_client.execute("PRAGMA journal_mode = WAL").catch(() => {
|
|
@@ -1018,14 +1749,20 @@ async function initDatabase(config) {
|
|
|
1018
1749
|
});
|
|
1019
1750
|
}, 3e4);
|
|
1020
1751
|
_walCheckpointTimer.unref();
|
|
1752
|
+
if (process.env.DATABASE_URL) {
|
|
1753
|
+
_adapterClient = await createPrismaDbAdapter(_resilientClient);
|
|
1754
|
+
}
|
|
1021
1755
|
}
|
|
1022
1756
|
function isInitialized() {
|
|
1023
|
-
return _client !== null;
|
|
1757
|
+
return _adapterClient !== null || _client !== null;
|
|
1024
1758
|
}
|
|
1025
1759
|
function getClient() {
|
|
1026
|
-
if (!
|
|
1760
|
+
if (!_adapterClient) {
|
|
1027
1761
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
1028
1762
|
}
|
|
1763
|
+
if (process.env.DATABASE_URL) {
|
|
1764
|
+
return _adapterClient;
|
|
1765
|
+
}
|
|
1029
1766
|
if (process.env.EXE_IS_DAEMON === "1") {
|
|
1030
1767
|
return _resilientClient;
|
|
1031
1768
|
}
|
|
@@ -1035,6 +1772,7 @@ function getClient() {
|
|
|
1035
1772
|
return _resilientClient;
|
|
1036
1773
|
}
|
|
1037
1774
|
async function initDaemonClient() {
|
|
1775
|
+
if (process.env.DATABASE_URL) return;
|
|
1038
1776
|
if (process.env.EXE_IS_DAEMON === "1") return;
|
|
1039
1777
|
if (!_resilientClient) return;
|
|
1040
1778
|
try {
|
|
@@ -1331,6 +2069,7 @@ async function ensureSchema() {
|
|
|
1331
2069
|
project TEXT NOT NULL,
|
|
1332
2070
|
summary TEXT NOT NULL,
|
|
1333
2071
|
task_file TEXT,
|
|
2072
|
+
session_scope TEXT,
|
|
1334
2073
|
read INTEGER NOT NULL DEFAULT 0,
|
|
1335
2074
|
created_at TEXT NOT NULL
|
|
1336
2075
|
);
|
|
@@ -1339,7 +2078,7 @@ async function ensureSchema() {
|
|
|
1339
2078
|
ON notifications(read);
|
|
1340
2079
|
|
|
1341
2080
|
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
1342
|
-
ON notifications(agent_id);
|
|
2081
|
+
ON notifications(agent_id, session_scope);
|
|
1343
2082
|
|
|
1344
2083
|
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
1345
2084
|
ON notifications(task_file);
|
|
@@ -1377,6 +2116,7 @@ async function ensureSchema() {
|
|
|
1377
2116
|
target_agent TEXT NOT NULL,
|
|
1378
2117
|
target_project TEXT,
|
|
1379
2118
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
2119
|
+
session_scope TEXT,
|
|
1380
2120
|
content TEXT NOT NULL,
|
|
1381
2121
|
priority TEXT DEFAULT 'normal',
|
|
1382
2122
|
status TEXT DEFAULT 'pending',
|
|
@@ -1390,10 +2130,31 @@ async function ensureSchema() {
|
|
|
1390
2130
|
);
|
|
1391
2131
|
|
|
1392
2132
|
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
1393
|
-
ON messages(target_agent, status);
|
|
2133
|
+
ON messages(target_agent, session_scope, status);
|
|
1394
2134
|
|
|
1395
2135
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
1396
|
-
ON messages(target_agent, from_agent, server_seq);
|
|
2136
|
+
ON messages(target_agent, session_scope, from_agent, server_seq);
|
|
2137
|
+
`);
|
|
2138
|
+
try {
|
|
2139
|
+
await client.execute({
|
|
2140
|
+
sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
|
|
2141
|
+
args: []
|
|
2142
|
+
});
|
|
2143
|
+
} catch {
|
|
2144
|
+
}
|
|
2145
|
+
try {
|
|
2146
|
+
await client.execute({
|
|
2147
|
+
sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
|
|
2148
|
+
args: []
|
|
2149
|
+
});
|
|
2150
|
+
} catch {
|
|
2151
|
+
}
|
|
2152
|
+
await client.executeMultiple(`
|
|
2153
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
|
|
2154
|
+
ON notifications(agent_id, session_scope, read, created_at);
|
|
2155
|
+
|
|
2156
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
|
|
2157
|
+
ON messages(target_agent, session_scope, status, created_at);
|
|
1397
2158
|
`);
|
|
1398
2159
|
try {
|
|
1399
2160
|
await client.execute({
|
|
@@ -1977,28 +2738,45 @@ async function ensureSchema() {
|
|
|
1977
2738
|
} catch {
|
|
1978
2739
|
}
|
|
1979
2740
|
}
|
|
2741
|
+
try {
|
|
2742
|
+
await client.execute({
|
|
2743
|
+
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
2744
|
+
args: []
|
|
2745
|
+
});
|
|
2746
|
+
} catch {
|
|
2747
|
+
}
|
|
1980
2748
|
}
|
|
1981
2749
|
async function disposeDatabase() {
|
|
2750
|
+
if (_walCheckpointTimer) {
|
|
2751
|
+
clearInterval(_walCheckpointTimer);
|
|
2752
|
+
_walCheckpointTimer = null;
|
|
2753
|
+
}
|
|
1982
2754
|
if (_daemonClient) {
|
|
1983
2755
|
_daemonClient.close();
|
|
1984
2756
|
_daemonClient = null;
|
|
1985
2757
|
}
|
|
2758
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
2759
|
+
_adapterClient.close();
|
|
2760
|
+
}
|
|
2761
|
+
_adapterClient = null;
|
|
1986
2762
|
if (_client) {
|
|
1987
2763
|
_client.close();
|
|
1988
2764
|
_client = null;
|
|
1989
2765
|
_resilientClient = null;
|
|
1990
2766
|
}
|
|
1991
2767
|
}
|
|
1992
|
-
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
|
|
2768
|
+
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
|
|
1993
2769
|
var init_database = __esm({
|
|
1994
2770
|
"src/lib/database.ts"() {
|
|
1995
2771
|
"use strict";
|
|
1996
2772
|
init_db_retry();
|
|
1997
2773
|
init_employees();
|
|
2774
|
+
init_database_adapter();
|
|
1998
2775
|
_client = null;
|
|
1999
2776
|
_resilientClient = null;
|
|
2000
2777
|
_walCheckpointTimer = null;
|
|
2001
2778
|
_daemonClient = null;
|
|
2779
|
+
_adapterClient = null;
|
|
2002
2780
|
initTurso = initDatabase;
|
|
2003
2781
|
disposeTurso = disposeDatabase;
|
|
2004
2782
|
}
|
|
@@ -2014,14 +2792,14 @@ __export(keychain_exports, {
|
|
|
2014
2792
|
setMasterKey: () => setMasterKey
|
|
2015
2793
|
});
|
|
2016
2794
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
2017
|
-
import { existsSync as
|
|
2018
|
-
import
|
|
2019
|
-
import
|
|
2795
|
+
import { existsSync as existsSync6 } from "fs";
|
|
2796
|
+
import path6 from "path";
|
|
2797
|
+
import os5 from "os";
|
|
2020
2798
|
function getKeyDir() {
|
|
2021
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
2799
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path6.join(os5.homedir(), ".exe-os");
|
|
2022
2800
|
}
|
|
2023
2801
|
function getKeyPath() {
|
|
2024
|
-
return
|
|
2802
|
+
return path6.join(getKeyDir(), "master.key");
|
|
2025
2803
|
}
|
|
2026
2804
|
async function tryKeytar() {
|
|
2027
2805
|
try {
|
|
@@ -2042,9 +2820,9 @@ async function getMasterKey() {
|
|
|
2042
2820
|
}
|
|
2043
2821
|
}
|
|
2044
2822
|
const keyPath = getKeyPath();
|
|
2045
|
-
if (!
|
|
2823
|
+
if (!existsSync6(keyPath)) {
|
|
2046
2824
|
process.stderr.write(
|
|
2047
|
-
`[keychain] Key not found at ${keyPath} (HOME=${
|
|
2825
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os5.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
2048
2826
|
`
|
|
2049
2827
|
);
|
|
2050
2828
|
return null;
|
|
@@ -2085,7 +2863,7 @@ async function deleteMasterKey() {
|
|
|
2085
2863
|
}
|
|
2086
2864
|
}
|
|
2087
2865
|
const keyPath = getKeyPath();
|
|
2088
|
-
if (
|
|
2866
|
+
if (existsSync6(keyPath)) {
|
|
2089
2867
|
await unlink(keyPath);
|
|
2090
2868
|
}
|
|
2091
2869
|
}
|
|
@@ -2187,6 +2965,7 @@ var shard_manager_exports = {};
|
|
|
2187
2965
|
__export(shard_manager_exports, {
|
|
2188
2966
|
disposeShards: () => disposeShards,
|
|
2189
2967
|
ensureShardSchema: () => ensureShardSchema,
|
|
2968
|
+
getOpenShardCount: () => getOpenShardCount,
|
|
2190
2969
|
getReadyShardClient: () => getReadyShardClient,
|
|
2191
2970
|
getShardClient: () => getShardClient,
|
|
2192
2971
|
getShardsDir: () => getShardsDir,
|
|
@@ -2195,15 +2974,18 @@ __export(shard_manager_exports, {
|
|
|
2195
2974
|
listShards: () => listShards,
|
|
2196
2975
|
shardExists: () => shardExists
|
|
2197
2976
|
});
|
|
2198
|
-
import
|
|
2199
|
-
import { existsSync as
|
|
2977
|
+
import path7 from "path";
|
|
2978
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync2, readdirSync } from "fs";
|
|
2200
2979
|
import { createClient as createClient2 } from "@libsql/client";
|
|
2201
2980
|
function initShardManager(encryptionKey) {
|
|
2202
2981
|
_encryptionKey = encryptionKey;
|
|
2203
|
-
if (!
|
|
2204
|
-
|
|
2982
|
+
if (!existsSync7(SHARDS_DIR)) {
|
|
2983
|
+
mkdirSync2(SHARDS_DIR, { recursive: true });
|
|
2205
2984
|
}
|
|
2206
2985
|
_shardingEnabled = true;
|
|
2986
|
+
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
2987
|
+
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
2988
|
+
_evictionTimer.unref();
|
|
2207
2989
|
}
|
|
2208
2990
|
function isShardingEnabled() {
|
|
2209
2991
|
return _shardingEnabled;
|
|
@@ -2220,21 +3002,28 @@ function getShardClient(projectName) {
|
|
|
2220
3002
|
throw new Error(`Invalid project name for shard: "${projectName}"`);
|
|
2221
3003
|
}
|
|
2222
3004
|
const cached = _shards.get(safeName);
|
|
2223
|
-
if (cached)
|
|
2224
|
-
|
|
3005
|
+
if (cached) {
|
|
3006
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
3007
|
+
return cached;
|
|
3008
|
+
}
|
|
3009
|
+
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
3010
|
+
evictLRU();
|
|
3011
|
+
}
|
|
3012
|
+
const dbPath = path7.join(SHARDS_DIR, `${safeName}.db`);
|
|
2225
3013
|
const client = createClient2({
|
|
2226
3014
|
url: `file:${dbPath}`,
|
|
2227
3015
|
encryptionKey: _encryptionKey
|
|
2228
3016
|
});
|
|
2229
3017
|
_shards.set(safeName, client);
|
|
3018
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
2230
3019
|
return client;
|
|
2231
3020
|
}
|
|
2232
3021
|
function shardExists(projectName) {
|
|
2233
3022
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
2234
|
-
return
|
|
3023
|
+
return existsSync7(path7.join(SHARDS_DIR, `${safeName}.db`));
|
|
2235
3024
|
}
|
|
2236
3025
|
function listShards() {
|
|
2237
|
-
if (!
|
|
3026
|
+
if (!existsSync7(SHARDS_DIR)) return [];
|
|
2238
3027
|
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
2239
3028
|
}
|
|
2240
3029
|
async function ensureShardSchema(client) {
|
|
@@ -2286,6 +3075,8 @@ async function ensureShardSchema(client) {
|
|
|
2286
3075
|
for (const col of [
|
|
2287
3076
|
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
2288
3077
|
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
3078
|
+
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
3079
|
+
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
2289
3080
|
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
2290
3081
|
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
2291
3082
|
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
@@ -2308,7 +3099,23 @@ async function ensureShardSchema(client) {
|
|
|
2308
3099
|
// MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
|
|
2309
3100
|
"ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
|
|
2310
3101
|
"ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
|
|
2311
|
-
"ALTER TABLE memories ADD COLUMN trajectory TEXT"
|
|
3102
|
+
"ALTER TABLE memories ADD COLUMN trajectory TEXT",
|
|
3103
|
+
// Metadata enrichment columns (must match database.ts)
|
|
3104
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
3105
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
3106
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
3107
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
3108
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
3109
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
3110
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
3111
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
3112
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
3113
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
3114
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
3115
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
3116
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
3117
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
3118
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
2312
3119
|
]) {
|
|
2313
3120
|
try {
|
|
2314
3121
|
await client.execute(col);
|
|
@@ -2407,21 +3214,69 @@ async function getReadyShardClient(projectName) {
|
|
|
2407
3214
|
await ensureShardSchema(client);
|
|
2408
3215
|
return client;
|
|
2409
3216
|
}
|
|
3217
|
+
function evictLRU() {
|
|
3218
|
+
let oldest = null;
|
|
3219
|
+
let oldestTime = Infinity;
|
|
3220
|
+
for (const [name, time] of _shardLastAccess) {
|
|
3221
|
+
if (time < oldestTime) {
|
|
3222
|
+
oldestTime = time;
|
|
3223
|
+
oldest = name;
|
|
3224
|
+
}
|
|
3225
|
+
}
|
|
3226
|
+
if (oldest) {
|
|
3227
|
+
const client = _shards.get(oldest);
|
|
3228
|
+
if (client) {
|
|
3229
|
+
client.close();
|
|
3230
|
+
}
|
|
3231
|
+
_shards.delete(oldest);
|
|
3232
|
+
_shardLastAccess.delete(oldest);
|
|
3233
|
+
}
|
|
3234
|
+
}
|
|
3235
|
+
function evictIdleShards() {
|
|
3236
|
+
const now = Date.now();
|
|
3237
|
+
const toEvict = [];
|
|
3238
|
+
for (const [name, lastAccess] of _shardLastAccess) {
|
|
3239
|
+
if (now - lastAccess > SHARD_IDLE_MS) {
|
|
3240
|
+
toEvict.push(name);
|
|
3241
|
+
}
|
|
3242
|
+
}
|
|
3243
|
+
for (const name of toEvict) {
|
|
3244
|
+
const client = _shards.get(name);
|
|
3245
|
+
if (client) {
|
|
3246
|
+
client.close();
|
|
3247
|
+
}
|
|
3248
|
+
_shards.delete(name);
|
|
3249
|
+
_shardLastAccess.delete(name);
|
|
3250
|
+
}
|
|
3251
|
+
}
|
|
3252
|
+
function getOpenShardCount() {
|
|
3253
|
+
return _shards.size;
|
|
3254
|
+
}
|
|
2410
3255
|
function disposeShards() {
|
|
3256
|
+
if (_evictionTimer) {
|
|
3257
|
+
clearInterval(_evictionTimer);
|
|
3258
|
+
_evictionTimer = null;
|
|
3259
|
+
}
|
|
2411
3260
|
for (const [, client] of _shards) {
|
|
2412
3261
|
client.close();
|
|
2413
3262
|
}
|
|
2414
3263
|
_shards.clear();
|
|
3264
|
+
_shardLastAccess.clear();
|
|
2415
3265
|
_shardingEnabled = false;
|
|
2416
3266
|
_encryptionKey = null;
|
|
2417
3267
|
}
|
|
2418
|
-
var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
|
|
3268
|
+
var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
|
|
2419
3269
|
var init_shard_manager = __esm({
|
|
2420
3270
|
"src/lib/shard-manager.ts"() {
|
|
2421
3271
|
"use strict";
|
|
2422
3272
|
init_config();
|
|
2423
|
-
SHARDS_DIR =
|
|
3273
|
+
SHARDS_DIR = path7.join(EXE_AI_DIR, "shards");
|
|
3274
|
+
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
3275
|
+
MAX_OPEN_SHARDS = 10;
|
|
3276
|
+
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
2424
3277
|
_shards = /* @__PURE__ */ new Map();
|
|
3278
|
+
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
3279
|
+
_evictionTimer = null;
|
|
2425
3280
|
_encryptionKey = null;
|
|
2426
3281
|
_shardingEnabled = false;
|
|
2427
3282
|
}
|
|
@@ -2614,56 +3469,14 @@ ${p.content}`).join("\n\n");
|
|
|
2614
3469
|
}
|
|
2615
3470
|
});
|
|
2616
3471
|
|
|
2617
|
-
// src/lib/notifications.ts
|
|
2618
|
-
import crypto from "crypto";
|
|
2619
|
-
import path6 from "path";
|
|
2620
|
-
import os5 from "os";
|
|
2621
|
-
import {
|
|
2622
|
-
readFileSync as readFileSync4,
|
|
2623
|
-
readdirSync as readdirSync2,
|
|
2624
|
-
unlinkSync as unlinkSync3,
|
|
2625
|
-
existsSync as existsSync6,
|
|
2626
|
-
rmdirSync
|
|
2627
|
-
} from "fs";
|
|
2628
|
-
async function writeNotification(notification) {
|
|
2629
|
-
try {
|
|
2630
|
-
const client = getClient();
|
|
2631
|
-
const id = crypto.randomUUID();
|
|
2632
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2633
|
-
await client.execute({
|
|
2634
|
-
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
|
|
2635
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
2636
|
-
args: [
|
|
2637
|
-
id,
|
|
2638
|
-
notification.agentId,
|
|
2639
|
-
notification.agentRole,
|
|
2640
|
-
notification.event,
|
|
2641
|
-
notification.project,
|
|
2642
|
-
notification.summary,
|
|
2643
|
-
notification.taskFile ?? null,
|
|
2644
|
-
now
|
|
2645
|
-
]
|
|
2646
|
-
});
|
|
2647
|
-
} catch (err) {
|
|
2648
|
-
process.stderr.write(`[notifications] WRITE FAILED: ${err instanceof Error ? err.message : String(err)}
|
|
2649
|
-
`);
|
|
2650
|
-
}
|
|
2651
|
-
}
|
|
2652
|
-
var init_notifications = __esm({
|
|
2653
|
-
"src/lib/notifications.ts"() {
|
|
2654
|
-
"use strict";
|
|
2655
|
-
init_database();
|
|
2656
|
-
}
|
|
2657
|
-
});
|
|
2658
|
-
|
|
2659
3472
|
// src/lib/session-registry.ts
|
|
2660
|
-
import
|
|
3473
|
+
import path8 from "path";
|
|
2661
3474
|
import os6 from "os";
|
|
2662
3475
|
var REGISTRY_PATH;
|
|
2663
3476
|
var init_session_registry = __esm({
|
|
2664
3477
|
"src/lib/session-registry.ts"() {
|
|
2665
3478
|
"use strict";
|
|
2666
|
-
REGISTRY_PATH =
|
|
3479
|
+
REGISTRY_PATH = path8.join(os6.homedir(), ".exe-os", "session-registry.json");
|
|
2667
3480
|
}
|
|
2668
3481
|
});
|
|
2669
3482
|
|
|
@@ -2898,15 +3711,16 @@ var init_runtime_table = __esm({
|
|
|
2898
3711
|
});
|
|
2899
3712
|
|
|
2900
3713
|
// src/lib/agent-config.ts
|
|
2901
|
-
import { readFileSync as readFileSync5, writeFileSync as
|
|
2902
|
-
import
|
|
3714
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync8 } from "fs";
|
|
3715
|
+
import path9 from "path";
|
|
2903
3716
|
var AGENT_CONFIG_PATH, DEFAULT_MODELS;
|
|
2904
3717
|
var init_agent_config = __esm({
|
|
2905
3718
|
"src/lib/agent-config.ts"() {
|
|
2906
3719
|
"use strict";
|
|
2907
3720
|
init_config();
|
|
2908
3721
|
init_runtime_table();
|
|
2909
|
-
|
|
3722
|
+
init_secure_files();
|
|
3723
|
+
AGENT_CONFIG_PATH = path9.join(EXE_AI_DIR, "agent-config.json");
|
|
2910
3724
|
DEFAULT_MODELS = {
|
|
2911
3725
|
claude: "claude-opus-4",
|
|
2912
3726
|
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
@@ -2916,16 +3730,16 @@ var init_agent_config = __esm({
|
|
|
2916
3730
|
});
|
|
2917
3731
|
|
|
2918
3732
|
// src/lib/intercom-queue.ts
|
|
2919
|
-
import { readFileSync as readFileSync6, writeFileSync as
|
|
2920
|
-
import
|
|
3733
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync9, mkdirSync as mkdirSync3 } from "fs";
|
|
3734
|
+
import path10 from "path";
|
|
2921
3735
|
import os7 from "os";
|
|
2922
3736
|
var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
|
|
2923
3737
|
var init_intercom_queue = __esm({
|
|
2924
3738
|
"src/lib/intercom-queue.ts"() {
|
|
2925
3739
|
"use strict";
|
|
2926
|
-
QUEUE_PATH =
|
|
3740
|
+
QUEUE_PATH = path10.join(os7.homedir(), ".exe-os", "intercom-queue.json");
|
|
2927
3741
|
TTL_MS = 60 * 60 * 1e3;
|
|
2928
|
-
INTERCOM_LOG =
|
|
3742
|
+
INTERCOM_LOG = path10.join(os7.homedir(), ".exe-os", "intercom.log");
|
|
2929
3743
|
}
|
|
2930
3744
|
});
|
|
2931
3745
|
|
|
@@ -2946,9 +3760,12 @@ __export(license_exports, {
|
|
|
2946
3760
|
stopLicenseRevalidation: () => stopLicenseRevalidation,
|
|
2947
3761
|
validateLicense: () => validateLicense
|
|
2948
3762
|
});
|
|
2949
|
-
import { readFileSync as readFileSync7, writeFileSync as
|
|
3763
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync10, mkdirSync as mkdirSync4 } from "fs";
|
|
2950
3764
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
2951
|
-
import
|
|
3765
|
+
import { createRequire as createRequire2 } from "module";
|
|
3766
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
3767
|
+
import os8 from "os";
|
|
3768
|
+
import path11 from "path";
|
|
2952
3769
|
import { jwtVerify, importSPKI } from "jose";
|
|
2953
3770
|
async function fetchRetry(url, init) {
|
|
2954
3771
|
try {
|
|
@@ -2959,16 +3776,16 @@ async function fetchRetry(url, init) {
|
|
|
2959
3776
|
}
|
|
2960
3777
|
}
|
|
2961
3778
|
function loadDeviceId() {
|
|
2962
|
-
const deviceJsonPath =
|
|
3779
|
+
const deviceJsonPath = path11.join(EXE_AI_DIR, "device.json");
|
|
2963
3780
|
try {
|
|
2964
|
-
if (
|
|
3781
|
+
if (existsSync10(deviceJsonPath)) {
|
|
2965
3782
|
const data = JSON.parse(readFileSync7(deviceJsonPath, "utf8"));
|
|
2966
3783
|
if (data.deviceId) return data.deviceId;
|
|
2967
3784
|
}
|
|
2968
3785
|
} catch {
|
|
2969
3786
|
}
|
|
2970
3787
|
try {
|
|
2971
|
-
if (
|
|
3788
|
+
if (existsSync10(DEVICE_ID_PATH)) {
|
|
2972
3789
|
const id2 = readFileSync7(DEVICE_ID_PATH, "utf8").trim();
|
|
2973
3790
|
if (id2) return id2;
|
|
2974
3791
|
}
|
|
@@ -2976,12 +3793,12 @@ function loadDeviceId() {
|
|
|
2976
3793
|
}
|
|
2977
3794
|
const id = randomUUID3();
|
|
2978
3795
|
mkdirSync4(EXE_AI_DIR, { recursive: true });
|
|
2979
|
-
|
|
3796
|
+
writeFileSync5(DEVICE_ID_PATH, id, "utf8");
|
|
2980
3797
|
return id;
|
|
2981
3798
|
}
|
|
2982
3799
|
function loadLicense() {
|
|
2983
3800
|
try {
|
|
2984
|
-
if (!
|
|
3801
|
+
if (!existsSync10(LICENSE_PATH)) return null;
|
|
2985
3802
|
return readFileSync7(LICENSE_PATH, "utf8").trim();
|
|
2986
3803
|
} catch {
|
|
2987
3804
|
return null;
|
|
@@ -2989,7 +3806,7 @@ function loadLicense() {
|
|
|
2989
3806
|
}
|
|
2990
3807
|
function saveLicense(apiKey) {
|
|
2991
3808
|
mkdirSync4(EXE_AI_DIR, { recursive: true });
|
|
2992
|
-
|
|
3809
|
+
writeFileSync5(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
|
|
2993
3810
|
}
|
|
2994
3811
|
async function verifyLicenseJwt(token) {
|
|
2995
3812
|
try {
|
|
@@ -3015,7 +3832,7 @@ async function verifyLicenseJwt(token) {
|
|
|
3015
3832
|
}
|
|
3016
3833
|
async function getCachedLicense() {
|
|
3017
3834
|
try {
|
|
3018
|
-
if (!
|
|
3835
|
+
if (!existsSync10(CACHE_PATH)) return null;
|
|
3019
3836
|
const raw = JSON.parse(readFileSync7(CACHE_PATH, "utf8"));
|
|
3020
3837
|
if (!raw.token || typeof raw.token !== "string") return null;
|
|
3021
3838
|
return await verifyLicenseJwt(raw.token);
|
|
@@ -3025,7 +3842,7 @@ async function getCachedLicense() {
|
|
|
3025
3842
|
}
|
|
3026
3843
|
function readCachedToken() {
|
|
3027
3844
|
try {
|
|
3028
|
-
if (!
|
|
3845
|
+
if (!existsSync10(CACHE_PATH)) return null;
|
|
3029
3846
|
const raw = JSON.parse(readFileSync7(CACHE_PATH, "utf8"));
|
|
3030
3847
|
return typeof raw.token === "string" ? raw.token : null;
|
|
3031
3848
|
} catch {
|
|
@@ -3060,56 +3877,130 @@ function getRawCachedPlan() {
|
|
|
3060
3877
|
}
|
|
3061
3878
|
function cacheResponse(token) {
|
|
3062
3879
|
try {
|
|
3063
|
-
|
|
3880
|
+
writeFileSync5(CACHE_PATH, JSON.stringify({ token }), "utf8");
|
|
3064
3881
|
} catch {
|
|
3065
3882
|
}
|
|
3066
3883
|
}
|
|
3067
|
-
|
|
3068
|
-
|
|
3884
|
+
function loadPrismaForLicense() {
|
|
3885
|
+
if (_prismaFailed) return null;
|
|
3886
|
+
const dbUrl = process.env.DATABASE_URL;
|
|
3887
|
+
if (!dbUrl) {
|
|
3888
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path11.join(os8.homedir(), "exe-db");
|
|
3889
|
+
if (!existsSync10(path11.join(exeDbRoot, "package.json"))) {
|
|
3890
|
+
_prismaFailed = true;
|
|
3891
|
+
return null;
|
|
3892
|
+
}
|
|
3893
|
+
}
|
|
3894
|
+
if (!_prismaPromise) {
|
|
3895
|
+
_prismaPromise = (async () => {
|
|
3896
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
3897
|
+
if (explicitPath) {
|
|
3898
|
+
const mod2 = await import(pathToFileURL2(explicitPath).href);
|
|
3899
|
+
const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
|
|
3900
|
+
if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
|
|
3901
|
+
return new Ctor2();
|
|
3902
|
+
}
|
|
3903
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path11.join(os8.homedir(), "exe-db");
|
|
3904
|
+
const req = createRequire2(path11.join(exeDbRoot, "package.json"));
|
|
3905
|
+
const entry = req.resolve("@prisma/client");
|
|
3906
|
+
const mod = await import(pathToFileURL2(entry).href);
|
|
3907
|
+
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
3908
|
+
if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
|
|
3909
|
+
return new Ctor();
|
|
3910
|
+
})().catch((err) => {
|
|
3911
|
+
_prismaFailed = true;
|
|
3912
|
+
_prismaPromise = null;
|
|
3913
|
+
throw err;
|
|
3914
|
+
});
|
|
3915
|
+
}
|
|
3916
|
+
return _prismaPromise;
|
|
3917
|
+
}
|
|
3918
|
+
async function validateViaPostgres(apiKey) {
|
|
3919
|
+
const loader = loadPrismaForLicense();
|
|
3920
|
+
if (!loader) return null;
|
|
3921
|
+
try {
|
|
3922
|
+
const prisma = await loader;
|
|
3923
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
3924
|
+
`SELECT plan, email, status, device_limit, employee_limit, memory_limit, expires_at
|
|
3925
|
+
FROM billing.licenses WHERE key = $1 LIMIT 1`,
|
|
3926
|
+
apiKey
|
|
3927
|
+
);
|
|
3928
|
+
if (!rows || rows.length === 0) return null;
|
|
3929
|
+
const row = rows[0];
|
|
3930
|
+
if (row.status !== "active") return null;
|
|
3931
|
+
if (row.expires_at && new Date(row.expires_at) < /* @__PURE__ */ new Date()) return null;
|
|
3932
|
+
const plan = row.plan;
|
|
3933
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
3934
|
+
return {
|
|
3935
|
+
valid: true,
|
|
3936
|
+
plan,
|
|
3937
|
+
email: row.email,
|
|
3938
|
+
expiresAt: row.expires_at ? new Date(row.expires_at).toISOString() : null,
|
|
3939
|
+
deviceLimit: row.device_limit ?? limits.devices,
|
|
3940
|
+
employeeLimit: row.employee_limit ?? limits.employees,
|
|
3941
|
+
memoryLimit: row.memory_limit ?? limits.memories
|
|
3942
|
+
};
|
|
3943
|
+
} catch {
|
|
3944
|
+
return null;
|
|
3945
|
+
}
|
|
3946
|
+
}
|
|
3947
|
+
async function validateViaCFWorker(apiKey, deviceId) {
|
|
3069
3948
|
try {
|
|
3070
3949
|
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
3071
3950
|
method: "POST",
|
|
3072
3951
|
headers: { "Content-Type": "application/json" },
|
|
3073
|
-
body: JSON.stringify({ apiKey, deviceId
|
|
3952
|
+
body: JSON.stringify({ apiKey, deviceId }),
|
|
3074
3953
|
signal: AbortSignal.timeout(1e4)
|
|
3075
3954
|
});
|
|
3076
|
-
if (res.ok)
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3955
|
+
if (!res.ok) return null;
|
|
3956
|
+
const data = await res.json();
|
|
3957
|
+
if (data.error === "device_limit_exceeded") return null;
|
|
3958
|
+
if (!data.valid) return null;
|
|
3959
|
+
if (data.token) {
|
|
3960
|
+
cacheResponse(data.token);
|
|
3961
|
+
const verified = await verifyLicenseJwt(data.token);
|
|
3962
|
+
if (verified) return verified;
|
|
3963
|
+
}
|
|
3964
|
+
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
3965
|
+
return {
|
|
3966
|
+
valid: data.valid,
|
|
3967
|
+
plan: data.plan,
|
|
3968
|
+
email: data.email,
|
|
3969
|
+
expiresAt: data.expiresAt,
|
|
3970
|
+
deviceLimit: limits.devices,
|
|
3971
|
+
employeeLimit: limits.employees,
|
|
3972
|
+
memoryLimit: limits.memories
|
|
3973
|
+
};
|
|
3974
|
+
} catch {
|
|
3975
|
+
return null;
|
|
3976
|
+
}
|
|
3977
|
+
}
|
|
3978
|
+
async function validateLicense(apiKey, deviceId) {
|
|
3979
|
+
const did = deviceId ?? loadDeviceId();
|
|
3980
|
+
const pgResult = await validateViaPostgres(apiKey);
|
|
3981
|
+
if (pgResult) {
|
|
3982
|
+
try {
|
|
3983
|
+
writeFileSync5(CACHE_PATH, JSON.stringify({ pgLicense: pgResult, ts: Date.now() }), "utf8");
|
|
3984
|
+
} catch {
|
|
3985
|
+
}
|
|
3986
|
+
return pgResult;
|
|
3987
|
+
}
|
|
3988
|
+
const cfResult = await validateViaCFWorker(apiKey, did);
|
|
3989
|
+
if (cfResult) return cfResult;
|
|
3990
|
+
const cached = await getCachedLicense();
|
|
3991
|
+
if (cached) return cached;
|
|
3992
|
+
try {
|
|
3993
|
+
if (existsSync10(CACHE_PATH)) {
|
|
3994
|
+
const raw = JSON.parse(readFileSync7(CACHE_PATH, "utf8"));
|
|
3995
|
+
if (raw.pgLicense && raw.ts && Date.now() - raw.ts < 7 * 24 * 60 * 60 * 1e3) {
|
|
3996
|
+
return raw.pgLicense;
|
|
3089
3997
|
}
|
|
3090
|
-
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
3091
|
-
return {
|
|
3092
|
-
valid: data.valid,
|
|
3093
|
-
plan: data.plan,
|
|
3094
|
-
email: data.email,
|
|
3095
|
-
expiresAt: data.expiresAt,
|
|
3096
|
-
deviceLimit: limits.devices,
|
|
3097
|
-
employeeLimit: limits.employees,
|
|
3098
|
-
memoryLimit: limits.memories
|
|
3099
|
-
};
|
|
3100
3998
|
}
|
|
3101
|
-
const cached = await getCachedLicense();
|
|
3102
|
-
if (cached) return cached;
|
|
3103
|
-
const raw = getRawCachedPlan();
|
|
3104
|
-
if (raw) return raw;
|
|
3105
|
-
return { ...FREE_LICENSE, valid: false, plan: "free" };
|
|
3106
3999
|
} catch {
|
|
3107
|
-
const cached = await getCachedLicense();
|
|
3108
|
-
if (cached) return cached;
|
|
3109
|
-
const rawFallback = getRawCachedPlan();
|
|
3110
|
-
if (rawFallback) return rawFallback;
|
|
3111
|
-
return { ...FREE_LICENSE, valid: false, error: "offline" };
|
|
3112
4000
|
}
|
|
4001
|
+
const rawFallback = getRawCachedPlan();
|
|
4002
|
+
if (rawFallback) return rawFallback;
|
|
4003
|
+
return { ...FREE_LICENSE, valid: false };
|
|
3113
4004
|
}
|
|
3114
4005
|
function getCacheAgeMs() {
|
|
3115
4006
|
try {
|
|
@@ -3124,8 +4015,8 @@ async function checkLicense() {
|
|
|
3124
4015
|
let key = loadLicense();
|
|
3125
4016
|
if (!key) {
|
|
3126
4017
|
try {
|
|
3127
|
-
const configPath =
|
|
3128
|
-
if (
|
|
4018
|
+
const configPath = path11.join(EXE_AI_DIR, "config.json");
|
|
4019
|
+
if (existsSync10(configPath)) {
|
|
3129
4020
|
const raw = JSON.parse(readFileSync7(configPath, "utf8"));
|
|
3130
4021
|
const cloud = raw.cloud;
|
|
3131
4022
|
if (cloud?.apiKey) {
|
|
@@ -3280,14 +4171,14 @@ function stopLicenseRevalidation() {
|
|
|
3280
4171
|
_revalTimer = null;
|
|
3281
4172
|
}
|
|
3282
4173
|
}
|
|
3283
|
-
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, _revalTimer;
|
|
4174
|
+
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, _revalTimer;
|
|
3284
4175
|
var init_license = __esm({
|
|
3285
4176
|
"src/lib/license.ts"() {
|
|
3286
4177
|
"use strict";
|
|
3287
4178
|
init_config();
|
|
3288
|
-
LICENSE_PATH =
|
|
3289
|
-
CACHE_PATH =
|
|
3290
|
-
DEVICE_ID_PATH =
|
|
4179
|
+
LICENSE_PATH = path11.join(EXE_AI_DIR, "license.key");
|
|
4180
|
+
CACHE_PATH = path11.join(EXE_AI_DIR, "license-cache.json");
|
|
4181
|
+
DEVICE_ID_PATH = path11.join(EXE_AI_DIR, "device-id");
|
|
3291
4182
|
API_BASE = "https://askexe.com/cloud";
|
|
3292
4183
|
RETRY_DELAY_MS = 500;
|
|
3293
4184
|
LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
@@ -3311,6 +4202,8 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
|
3311
4202
|
employeeLimit: 1,
|
|
3312
4203
|
memoryLimit: 5e3
|
|
3313
4204
|
};
|
|
4205
|
+
_prismaPromise = null;
|
|
4206
|
+
_prismaFailed = false;
|
|
3314
4207
|
CACHE_MAX_AGE_MS = 36e5;
|
|
3315
4208
|
_revalTimer = null;
|
|
3316
4209
|
}
|
|
@@ -3327,11 +4220,11 @@ __export(plan_limits_exports, {
|
|
|
3327
4220
|
countActiveMemories: () => countActiveMemories,
|
|
3328
4221
|
getLicenseSync: () => getLicenseSync
|
|
3329
4222
|
});
|
|
3330
|
-
import { readFileSync as readFileSync8, existsSync as
|
|
3331
|
-
import
|
|
4223
|
+
import { readFileSync as readFileSync8, existsSync as existsSync11 } from "fs";
|
|
4224
|
+
import path12 from "path";
|
|
3332
4225
|
function getLicenseSync() {
|
|
3333
4226
|
try {
|
|
3334
|
-
if (!
|
|
4227
|
+
if (!existsSync11(CACHE_PATH2)) return freeLicense();
|
|
3335
4228
|
const raw = JSON.parse(readFileSync8(CACHE_PATH2, "utf8"));
|
|
3336
4229
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
3337
4230
|
const parts = raw.token.split(".");
|
|
@@ -3399,7 +4292,7 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
3399
4292
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
3400
4293
|
let count = 0;
|
|
3401
4294
|
try {
|
|
3402
|
-
if (
|
|
4295
|
+
if (existsSync11(filePath)) {
|
|
3403
4296
|
const raw = readFileSync8(filePath, "utf8");
|
|
3404
4297
|
const employees = JSON.parse(raw);
|
|
3405
4298
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
@@ -3437,14 +4330,14 @@ var init_plan_limits = __esm({
|
|
|
3437
4330
|
this.name = "PlanLimitError";
|
|
3438
4331
|
}
|
|
3439
4332
|
};
|
|
3440
|
-
CACHE_PATH2 =
|
|
4333
|
+
CACHE_PATH2 = path12.join(EXE_AI_DIR, "license-cache.json");
|
|
3441
4334
|
}
|
|
3442
4335
|
});
|
|
3443
4336
|
|
|
3444
4337
|
// src/lib/tmux-routing.ts
|
|
3445
|
-
import { readFileSync as readFileSync9, writeFileSync as
|
|
3446
|
-
import
|
|
3447
|
-
import
|
|
4338
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, existsSync as existsSync12, appendFileSync, readdirSync as readdirSync2 } from "fs";
|
|
4339
|
+
import path13 from "path";
|
|
4340
|
+
import os9 from "os";
|
|
3448
4341
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3449
4342
|
function getMySession() {
|
|
3450
4343
|
return getTransport().getMySession();
|
|
@@ -3457,7 +4350,7 @@ function extractRootExe(name) {
|
|
|
3457
4350
|
}
|
|
3458
4351
|
function getParentExe(sessionKey) {
|
|
3459
4352
|
try {
|
|
3460
|
-
const data = JSON.parse(readFileSync9(
|
|
4353
|
+
const data = JSON.parse(readFileSync9(path13.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
3461
4354
|
return data.parentExe || null;
|
|
3462
4355
|
} catch {
|
|
3463
4356
|
return null;
|
|
@@ -3500,10 +4393,10 @@ var init_tmux_routing = __esm({
|
|
|
3500
4393
|
init_intercom_queue();
|
|
3501
4394
|
init_plan_limits();
|
|
3502
4395
|
init_employees();
|
|
3503
|
-
SPAWN_LOCK_DIR =
|
|
3504
|
-
SESSION_CACHE =
|
|
3505
|
-
INTERCOM_LOG2 =
|
|
3506
|
-
DEBOUNCE_FILE =
|
|
4396
|
+
SPAWN_LOCK_DIR = path13.join(os9.homedir(), ".exe-os", "spawn-locks");
|
|
4397
|
+
SESSION_CACHE = path13.join(os9.homedir(), ".exe-os", "session-cache");
|
|
4398
|
+
INTERCOM_LOG2 = path13.join(os9.homedir(), ".exe-os", "intercom.log");
|
|
4399
|
+
DEBOUNCE_FILE = path13.join(SESSION_CACHE, "intercom-debounce.json");
|
|
3507
4400
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
3508
4401
|
}
|
|
3509
4402
|
});
|
|
@@ -3532,6 +4425,51 @@ var init_task_scope = __esm({
|
|
|
3532
4425
|
}
|
|
3533
4426
|
});
|
|
3534
4427
|
|
|
4428
|
+
// src/lib/notifications.ts
|
|
4429
|
+
import crypto2 from "crypto";
|
|
4430
|
+
import path14 from "path";
|
|
4431
|
+
import os10 from "os";
|
|
4432
|
+
import {
|
|
4433
|
+
readFileSync as readFileSync10,
|
|
4434
|
+
readdirSync as readdirSync3,
|
|
4435
|
+
unlinkSync as unlinkSync3,
|
|
4436
|
+
existsSync as existsSync13,
|
|
4437
|
+
rmdirSync
|
|
4438
|
+
} from "fs";
|
|
4439
|
+
async function writeNotification(notification) {
|
|
4440
|
+
try {
|
|
4441
|
+
const client = getClient();
|
|
4442
|
+
const id = crypto2.randomUUID();
|
|
4443
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4444
|
+
const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
|
|
4445
|
+
await client.execute({
|
|
4446
|
+
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
4447
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
4448
|
+
args: [
|
|
4449
|
+
id,
|
|
4450
|
+
notification.agentId,
|
|
4451
|
+
notification.agentRole,
|
|
4452
|
+
notification.event,
|
|
4453
|
+
notification.project,
|
|
4454
|
+
notification.summary,
|
|
4455
|
+
notification.taskFile ?? null,
|
|
4456
|
+
sessionScope,
|
|
4457
|
+
now
|
|
4458
|
+
]
|
|
4459
|
+
});
|
|
4460
|
+
} catch (err) {
|
|
4461
|
+
process.stderr.write(`[notifications] WRITE FAILED: ${err instanceof Error ? err.message : String(err)}
|
|
4462
|
+
`);
|
|
4463
|
+
}
|
|
4464
|
+
}
|
|
4465
|
+
var init_notifications = __esm({
|
|
4466
|
+
"src/lib/notifications.ts"() {
|
|
4467
|
+
"use strict";
|
|
4468
|
+
init_database();
|
|
4469
|
+
init_task_scope();
|
|
4470
|
+
}
|
|
4471
|
+
});
|
|
4472
|
+
|
|
3535
4473
|
// src/lib/embedder.ts
|
|
3536
4474
|
var embedder_exports = {};
|
|
3537
4475
|
__export(embedder_exports, {
|
|
@@ -3569,10 +4507,10 @@ async function disposeEmbedder() {
|
|
|
3569
4507
|
async function embedDirect(text) {
|
|
3570
4508
|
const llamaCpp = await import("node-llama-cpp");
|
|
3571
4509
|
const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
3572
|
-
const { existsSync:
|
|
3573
|
-
const
|
|
3574
|
-
const modelPath =
|
|
3575
|
-
if (!
|
|
4510
|
+
const { existsSync: existsSync18 } = await import("fs");
|
|
4511
|
+
const path19 = await import("path");
|
|
4512
|
+
const modelPath = path19.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
|
|
4513
|
+
if (!existsSync18(modelPath)) {
|
|
3576
4514
|
throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
|
|
3577
4515
|
}
|
|
3578
4516
|
const llama = await llamaCpp.getLlama();
|
|
@@ -3610,14 +4548,14 @@ __export(worker_gate_exports, {
|
|
|
3610
4548
|
tryAcquireBackfillLock: () => tryAcquireBackfillLock,
|
|
3611
4549
|
tryAcquireWorkerSlot: () => tryAcquireWorkerSlot
|
|
3612
4550
|
});
|
|
3613
|
-
import { readdirSync as readdirSync4, writeFileSync as
|
|
3614
|
-
import
|
|
4551
|
+
import { readdirSync as readdirSync4, writeFileSync as writeFileSync7, unlinkSync as unlinkSync4, mkdirSync as mkdirSync6, existsSync as existsSync14 } from "fs";
|
|
4552
|
+
import path15 from "path";
|
|
3615
4553
|
function tryAcquireWorkerSlot() {
|
|
3616
4554
|
try {
|
|
3617
4555
|
mkdirSync6(WORKER_PID_DIR, { recursive: true });
|
|
3618
4556
|
const reservationId = `res-${process.pid}-${Date.now()}`;
|
|
3619
|
-
const reservationPath =
|
|
3620
|
-
|
|
4557
|
+
const reservationPath = path15.join(WORKER_PID_DIR, `${reservationId}.pid`);
|
|
4558
|
+
writeFileSync7(reservationPath, String(process.pid));
|
|
3621
4559
|
const files = readdirSync4(WORKER_PID_DIR);
|
|
3622
4560
|
let alive = 0;
|
|
3623
4561
|
for (const f of files) {
|
|
@@ -3634,7 +4572,7 @@ function tryAcquireWorkerSlot() {
|
|
|
3634
4572
|
alive++;
|
|
3635
4573
|
} catch {
|
|
3636
4574
|
try {
|
|
3637
|
-
unlinkSync4(
|
|
4575
|
+
unlinkSync4(path15.join(WORKER_PID_DIR, f));
|
|
3638
4576
|
} catch {
|
|
3639
4577
|
}
|
|
3640
4578
|
}
|
|
@@ -3658,20 +4596,20 @@ function tryAcquireWorkerSlot() {
|
|
|
3658
4596
|
function registerWorkerPid(pid) {
|
|
3659
4597
|
try {
|
|
3660
4598
|
mkdirSync6(WORKER_PID_DIR, { recursive: true });
|
|
3661
|
-
|
|
4599
|
+
writeFileSync7(path15.join(WORKER_PID_DIR, `worker-${pid}.pid`), String(pid));
|
|
3662
4600
|
} catch {
|
|
3663
4601
|
}
|
|
3664
4602
|
}
|
|
3665
4603
|
function cleanupWorkerPid() {
|
|
3666
4604
|
try {
|
|
3667
|
-
unlinkSync4(
|
|
4605
|
+
unlinkSync4(path15.join(WORKER_PID_DIR, `worker-${process.pid}.pid`));
|
|
3668
4606
|
} catch {
|
|
3669
4607
|
}
|
|
3670
4608
|
}
|
|
3671
4609
|
function tryAcquireBackfillLock() {
|
|
3672
4610
|
try {
|
|
3673
4611
|
mkdirSync6(WORKER_PID_DIR, { recursive: true });
|
|
3674
|
-
if (
|
|
4612
|
+
if (existsSync14(BACKFILL_LOCK)) {
|
|
3675
4613
|
try {
|
|
3676
4614
|
const pid = parseInt(
|
|
3677
4615
|
__require("fs").readFileSync(BACKFILL_LOCK, "utf8").trim(),
|
|
@@ -3687,7 +4625,7 @@ function tryAcquireBackfillLock() {
|
|
|
3687
4625
|
} catch {
|
|
3688
4626
|
}
|
|
3689
4627
|
}
|
|
3690
|
-
|
|
4628
|
+
writeFileSync7(BACKFILL_LOCK, String(process.pid));
|
|
3691
4629
|
return true;
|
|
3692
4630
|
} catch {
|
|
3693
4631
|
return true;
|
|
@@ -3704,9 +4642,9 @@ var init_worker_gate = __esm({
|
|
|
3704
4642
|
"src/lib/worker-gate.ts"() {
|
|
3705
4643
|
"use strict";
|
|
3706
4644
|
init_config();
|
|
3707
|
-
WORKER_PID_DIR =
|
|
4645
|
+
WORKER_PID_DIR = path15.join(EXE_AI_DIR, "worker-pids");
|
|
3708
4646
|
MAX_CONCURRENT_WORKERS = 3;
|
|
3709
|
-
BACKFILL_LOCK =
|
|
4647
|
+
BACKFILL_LOCK = path15.join(WORKER_PID_DIR, "backfill.lock");
|
|
3710
4648
|
}
|
|
3711
4649
|
});
|
|
3712
4650
|
|
|
@@ -3718,13 +4656,13 @@ __export(crypto_exports, {
|
|
|
3718
4656
|
initSyncCrypto: () => initSyncCrypto,
|
|
3719
4657
|
isSyncCryptoInitialized: () => isSyncCryptoInitialized
|
|
3720
4658
|
});
|
|
3721
|
-
import
|
|
4659
|
+
import crypto3 from "crypto";
|
|
3722
4660
|
function initSyncCrypto(masterKey) {
|
|
3723
4661
|
if (masterKey.length !== 32) {
|
|
3724
4662
|
throw new Error(`Master key must be 32 bytes, got ${masterKey.length}`);
|
|
3725
4663
|
}
|
|
3726
4664
|
_syncKey = Buffer.from(
|
|
3727
|
-
|
|
4665
|
+
crypto3.hkdfSync("sha256", masterKey, "", SYNC_HKDF_INFO, 32)
|
|
3728
4666
|
);
|
|
3729
4667
|
}
|
|
3730
4668
|
function isSyncCryptoInitialized() {
|
|
@@ -3738,8 +4676,8 @@ function requireSyncKey() {
|
|
|
3738
4676
|
}
|
|
3739
4677
|
function encryptSyncBlob(data) {
|
|
3740
4678
|
const key = requireSyncKey();
|
|
3741
|
-
const iv =
|
|
3742
|
-
const cipher =
|
|
4679
|
+
const iv = crypto3.randomBytes(IV_LENGTH);
|
|
4680
|
+
const cipher = crypto3.createCipheriv(ALGORITHM, key, iv);
|
|
3743
4681
|
const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
|
|
3744
4682
|
const tag = cipher.getAuthTag();
|
|
3745
4683
|
return Buffer.concat([iv, encrypted, tag]).toString("base64");
|
|
@@ -3753,7 +4691,7 @@ function decryptSyncBlob(ciphertext) {
|
|
|
3753
4691
|
const iv = combined.subarray(0, IV_LENGTH);
|
|
3754
4692
|
const tag = combined.subarray(combined.length - TAG_LENGTH);
|
|
3755
4693
|
const encrypted = combined.subarray(IV_LENGTH, combined.length - TAG_LENGTH);
|
|
3756
|
-
const decipher =
|
|
4694
|
+
const decipher = crypto3.createDecipheriv(ALGORITHM, key, iv);
|
|
3757
4695
|
decipher.setAuthTag(tag);
|
|
3758
4696
|
return Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
|
3759
4697
|
}
|
|
@@ -3808,8 +4746,8 @@ __export(crdt_sync_exports, {
|
|
|
3808
4746
|
rebuildFromDb: () => rebuildFromDb
|
|
3809
4747
|
});
|
|
3810
4748
|
import * as Y from "yjs";
|
|
3811
|
-
import { readFileSync as
|
|
3812
|
-
import
|
|
4749
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync8, existsSync as existsSync15, mkdirSync as mkdirSync7, unlinkSync as unlinkSync5 } from "fs";
|
|
4750
|
+
import path16 from "path";
|
|
3813
4751
|
import { homedir } from "os";
|
|
3814
4752
|
function getStatePath() {
|
|
3815
4753
|
return _statePathOverride ?? DEFAULT_STATE_PATH;
|
|
@@ -3821,9 +4759,9 @@ function initCrdtDoc() {
|
|
|
3821
4759
|
if (doc) return doc;
|
|
3822
4760
|
doc = new Y.Doc();
|
|
3823
4761
|
const sp = getStatePath();
|
|
3824
|
-
if (
|
|
4762
|
+
if (existsSync15(sp)) {
|
|
3825
4763
|
try {
|
|
3826
|
-
const state =
|
|
4764
|
+
const state = readFileSync11(sp);
|
|
3827
4765
|
Y.applyUpdate(doc, new Uint8Array(state));
|
|
3828
4766
|
} catch {
|
|
3829
4767
|
console.warn("[crdt-sync] WARN: corrupted state file, rebuilding from DB");
|
|
@@ -3965,10 +4903,10 @@ function persistState() {
|
|
|
3965
4903
|
if (!doc) return;
|
|
3966
4904
|
try {
|
|
3967
4905
|
const sp = getStatePath();
|
|
3968
|
-
const dir =
|
|
3969
|
-
if (!
|
|
4906
|
+
const dir = path16.dirname(sp);
|
|
4907
|
+
if (!existsSync15(dir)) mkdirSync7(dir, { recursive: true });
|
|
3970
4908
|
const state = Y.encodeStateAsUpdate(doc);
|
|
3971
|
-
|
|
4909
|
+
writeFileSync8(sp, Buffer.from(state));
|
|
3972
4910
|
} catch {
|
|
3973
4911
|
}
|
|
3974
4912
|
}
|
|
@@ -4009,7 +4947,7 @@ var DEFAULT_STATE_PATH, _statePathOverride, doc;
|
|
|
4009
4947
|
var init_crdt_sync = __esm({
|
|
4010
4948
|
"src/lib/crdt-sync.ts"() {
|
|
4011
4949
|
"use strict";
|
|
4012
|
-
DEFAULT_STATE_PATH =
|
|
4950
|
+
DEFAULT_STATE_PATH = path16.join(homedir(), ".exe-os", "crdt-state.bin");
|
|
4013
4951
|
_statePathOverride = null;
|
|
4014
4952
|
doc = null;
|
|
4015
4953
|
}
|
|
@@ -4041,39 +4979,107 @@ __export(cloud_sync_exports, {
|
|
|
4041
4979
|
cloudSync: () => cloudSync,
|
|
4042
4980
|
mergeConfig: () => mergeConfig,
|
|
4043
4981
|
mergeRosterFromRemote: () => mergeRosterFromRemote,
|
|
4982
|
+
pushToPostgres: () => pushToPostgres,
|
|
4044
4983
|
recordRosterDeletion: () => recordRosterDeletion
|
|
4045
4984
|
});
|
|
4046
|
-
import { readFileSync as
|
|
4047
|
-
import
|
|
4048
|
-
import
|
|
4985
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync9, existsSync as existsSync16, readdirSync as readdirSync5, mkdirSync as mkdirSync8, appendFileSync as appendFileSync2, unlinkSync as unlinkSync6, openSync as openSync2, closeSync as closeSync2 } from "fs";
|
|
4986
|
+
import crypto4 from "crypto";
|
|
4987
|
+
import path17 from "path";
|
|
4049
4988
|
import { homedir as homedir2 } from "os";
|
|
4050
4989
|
function sqlSafe(v) {
|
|
4051
4990
|
return v === void 0 ? null : v;
|
|
4052
4991
|
}
|
|
4053
4992
|
function logError(msg) {
|
|
4054
4993
|
try {
|
|
4055
|
-
const logPath =
|
|
4994
|
+
const logPath = path17.join(homedir2(), ".exe-os", "workers.log");
|
|
4056
4995
|
appendFileSync2(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
|
|
4057
4996
|
`);
|
|
4058
4997
|
} catch {
|
|
4059
4998
|
}
|
|
4060
4999
|
}
|
|
5000
|
+
function loadPgClient() {
|
|
5001
|
+
if (_pgFailed) return null;
|
|
5002
|
+
const postgresUrl = process.env.DATABASE_URL;
|
|
5003
|
+
const configPath = path17.join(EXE_AI_DIR, "config.json");
|
|
5004
|
+
let cloudPostgresUrl;
|
|
5005
|
+
try {
|
|
5006
|
+
if (existsSync16(configPath)) {
|
|
5007
|
+
const cfg = JSON.parse(readFileSync12(configPath, "utf8"));
|
|
5008
|
+
cloudPostgresUrl = cfg.cloud?.postgresUrl;
|
|
5009
|
+
if (cfg.cloud?.syncToPostgres === false) {
|
|
5010
|
+
_pgFailed = true;
|
|
5011
|
+
return null;
|
|
5012
|
+
}
|
|
5013
|
+
}
|
|
5014
|
+
} catch {
|
|
5015
|
+
}
|
|
5016
|
+
const url = postgresUrl || cloudPostgresUrl;
|
|
5017
|
+
if (!url) {
|
|
5018
|
+
_pgFailed = true;
|
|
5019
|
+
return null;
|
|
5020
|
+
}
|
|
5021
|
+
if (!_pgPromise) {
|
|
5022
|
+
_pgPromise = (async () => {
|
|
5023
|
+
const { createRequire: createRequire3 } = await import("module");
|
|
5024
|
+
const { pathToFileURL: pathToFileURL3 } = await import("url");
|
|
5025
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path17.join(homedir2(), "exe-db");
|
|
5026
|
+
const req = createRequire3(path17.join(exeDbRoot, "package.json"));
|
|
5027
|
+
const entry = req.resolve("@prisma/client");
|
|
5028
|
+
const mod = await import(pathToFileURL3(entry).href);
|
|
5029
|
+
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
5030
|
+
if (!Ctor) throw new Error("No PrismaClient");
|
|
5031
|
+
return new Ctor();
|
|
5032
|
+
})().catch(() => {
|
|
5033
|
+
_pgFailed = true;
|
|
5034
|
+
_pgPromise = null;
|
|
5035
|
+
throw new Error("pg_unavailable");
|
|
5036
|
+
});
|
|
5037
|
+
}
|
|
5038
|
+
return _pgPromise;
|
|
5039
|
+
}
|
|
5040
|
+
async function pushToPostgres(records) {
|
|
5041
|
+
const loader = loadPgClient();
|
|
5042
|
+
if (!loader) return 0;
|
|
5043
|
+
let prisma;
|
|
5044
|
+
try {
|
|
5045
|
+
prisma = await loader;
|
|
5046
|
+
} catch {
|
|
5047
|
+
return 0;
|
|
5048
|
+
}
|
|
5049
|
+
let inserted = 0;
|
|
5050
|
+
for (const rec of records) {
|
|
5051
|
+
try {
|
|
5052
|
+
await prisma.$executeRawUnsafe(
|
|
5053
|
+
`INSERT INTO raw.raw_events (id, source, source_id, event_type, payload, metadata, timestamp)
|
|
5054
|
+
VALUES (gen_random_uuid(), 'cloud_sync', $1, 'memory', $2::jsonb, $3::jsonb, $4)
|
|
5055
|
+
ON CONFLICT (source, source_id, event_type) DO NOTHING`,
|
|
5056
|
+
String(rec.id ?? ""),
|
|
5057
|
+
JSON.stringify(rec),
|
|
5058
|
+
JSON.stringify({ agent_id: rec.agent_id, project_name: rec.project_name, tool_name: rec.tool_name }),
|
|
5059
|
+
rec.timestamp ? new Date(String(rec.timestamp)) : /* @__PURE__ */ new Date()
|
|
5060
|
+
);
|
|
5061
|
+
inserted++;
|
|
5062
|
+
} catch {
|
|
5063
|
+
}
|
|
5064
|
+
}
|
|
5065
|
+
return inserted;
|
|
5066
|
+
}
|
|
4061
5067
|
async function withRosterLock(fn) {
|
|
4062
5068
|
try {
|
|
4063
5069
|
const fd = openSync2(ROSTER_LOCK_PATH, "wx");
|
|
4064
5070
|
closeSync2(fd);
|
|
4065
|
-
|
|
5071
|
+
writeFileSync9(ROSTER_LOCK_PATH, String(Date.now()));
|
|
4066
5072
|
} catch (err) {
|
|
4067
5073
|
if (err.code === "EEXIST") {
|
|
4068
5074
|
try {
|
|
4069
|
-
const ts = parseInt(
|
|
5075
|
+
const ts = parseInt(readFileSync12(ROSTER_LOCK_PATH, "utf-8"), 10);
|
|
4070
5076
|
if (Date.now() - ts < LOCK_STALE_MS) {
|
|
4071
5077
|
throw new Error("Roster merge already in progress \u2014 another sync is running");
|
|
4072
5078
|
}
|
|
4073
5079
|
unlinkSync6(ROSTER_LOCK_PATH);
|
|
4074
5080
|
const fd = openSync2(ROSTER_LOCK_PATH, "wx");
|
|
4075
5081
|
closeSync2(fd);
|
|
4076
|
-
|
|
5082
|
+
writeFileSync9(ROSTER_LOCK_PATH, String(Date.now()));
|
|
4077
5083
|
} catch (retryErr) {
|
|
4078
5084
|
if (retryErr instanceof Error && retryErr.message.includes("already in progress")) throw retryErr;
|
|
4079
5085
|
throw new Error("Roster merge already in progress \u2014 another sync is running");
|
|
@@ -4343,6 +5349,10 @@ async function cloudSync(config) {
|
|
|
4343
5349
|
const maxVersion = Number(records[records.length - 1].version);
|
|
4344
5350
|
const pushOk = await cloudPush(records, maxVersion, config);
|
|
4345
5351
|
if (!pushOk) break;
|
|
5352
|
+
try {
|
|
5353
|
+
await pushToPostgres(records);
|
|
5354
|
+
} catch {
|
|
5355
|
+
}
|
|
4346
5356
|
await client.execute({
|
|
4347
5357
|
sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_cloud_push_version', ?)",
|
|
4348
5358
|
args: [String(maxVersion)]
|
|
@@ -4447,8 +5457,8 @@ async function cloudSync(config) {
|
|
|
4447
5457
|
try {
|
|
4448
5458
|
const employees = await loadEmployees();
|
|
4449
5459
|
rosterResult.employees = employees.length;
|
|
4450
|
-
const idDir =
|
|
4451
|
-
if (
|
|
5460
|
+
const idDir = path17.join(EXE_AI_DIR, "identity");
|
|
5461
|
+
if (existsSync16(idDir)) {
|
|
4452
5462
|
rosterResult.identities = readdirSync5(idDir).filter((f) => f.endsWith(".md")).length;
|
|
4453
5463
|
}
|
|
4454
5464
|
} catch {
|
|
@@ -4469,62 +5479,62 @@ async function cloudSync(config) {
|
|
|
4469
5479
|
function recordRosterDeletion(name) {
|
|
4470
5480
|
let deletions = [];
|
|
4471
5481
|
try {
|
|
4472
|
-
if (
|
|
4473
|
-
deletions = JSON.parse(
|
|
5482
|
+
if (existsSync16(ROSTER_DELETIONS_PATH)) {
|
|
5483
|
+
deletions = JSON.parse(readFileSync12(ROSTER_DELETIONS_PATH, "utf-8"));
|
|
4474
5484
|
}
|
|
4475
5485
|
} catch {
|
|
4476
5486
|
}
|
|
4477
5487
|
if (!deletions.includes(name)) deletions.push(name);
|
|
4478
|
-
|
|
5488
|
+
writeFileSync9(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
|
|
4479
5489
|
}
|
|
4480
5490
|
function consumeRosterDeletions() {
|
|
4481
5491
|
try {
|
|
4482
|
-
if (!
|
|
4483
|
-
const deletions = JSON.parse(
|
|
4484
|
-
|
|
5492
|
+
if (!existsSync16(ROSTER_DELETIONS_PATH)) return [];
|
|
5493
|
+
const deletions = JSON.parse(readFileSync12(ROSTER_DELETIONS_PATH, "utf-8"));
|
|
5494
|
+
writeFileSync9(ROSTER_DELETIONS_PATH, "[]");
|
|
4485
5495
|
return deletions;
|
|
4486
5496
|
} catch {
|
|
4487
5497
|
return [];
|
|
4488
5498
|
}
|
|
4489
5499
|
}
|
|
4490
5500
|
function buildRosterBlob(paths) {
|
|
4491
|
-
const rosterPath = paths?.rosterPath ??
|
|
4492
|
-
const identityDir = paths?.identityDir ??
|
|
4493
|
-
const configPath = paths?.configPath ??
|
|
5501
|
+
const rosterPath = paths?.rosterPath ?? path17.join(EXE_AI_DIR, "exe-employees.json");
|
|
5502
|
+
const identityDir = paths?.identityDir ?? path17.join(EXE_AI_DIR, "identity");
|
|
5503
|
+
const configPath = paths?.configPath ?? path17.join(EXE_AI_DIR, "config.json");
|
|
4494
5504
|
let roster = [];
|
|
4495
|
-
if (
|
|
5505
|
+
if (existsSync16(rosterPath)) {
|
|
4496
5506
|
try {
|
|
4497
|
-
roster = JSON.parse(
|
|
5507
|
+
roster = JSON.parse(readFileSync12(rosterPath, "utf-8"));
|
|
4498
5508
|
} catch {
|
|
4499
5509
|
}
|
|
4500
5510
|
}
|
|
4501
5511
|
const identities = {};
|
|
4502
|
-
if (
|
|
5512
|
+
if (existsSync16(identityDir)) {
|
|
4503
5513
|
for (const file of readdirSync5(identityDir).filter((f) => f.endsWith(".md"))) {
|
|
4504
5514
|
try {
|
|
4505
|
-
identities[file] =
|
|
5515
|
+
identities[file] = readFileSync12(path17.join(identityDir, file), "utf-8");
|
|
4506
5516
|
} catch {
|
|
4507
5517
|
}
|
|
4508
5518
|
}
|
|
4509
5519
|
}
|
|
4510
5520
|
let config;
|
|
4511
|
-
if (
|
|
5521
|
+
if (existsSync16(configPath)) {
|
|
4512
5522
|
try {
|
|
4513
|
-
config = JSON.parse(
|
|
5523
|
+
config = JSON.parse(readFileSync12(configPath, "utf-8"));
|
|
4514
5524
|
} catch {
|
|
4515
5525
|
}
|
|
4516
5526
|
}
|
|
4517
5527
|
let agentConfig;
|
|
4518
|
-
const agentConfigPath =
|
|
4519
|
-
if (
|
|
5528
|
+
const agentConfigPath = path17.join(EXE_AI_DIR, "agent-config.json");
|
|
5529
|
+
if (existsSync16(agentConfigPath)) {
|
|
4520
5530
|
try {
|
|
4521
|
-
agentConfig = JSON.parse(
|
|
5531
|
+
agentConfig = JSON.parse(readFileSync12(agentConfigPath, "utf-8"));
|
|
4522
5532
|
} catch {
|
|
4523
5533
|
}
|
|
4524
5534
|
}
|
|
4525
5535
|
const deletedNames = consumeRosterDeletions();
|
|
4526
5536
|
const content = JSON.stringify({ roster, identities, config, agentConfig, deletedNames });
|
|
4527
|
-
const hash =
|
|
5537
|
+
const hash = crypto4.createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
4528
5538
|
return { roster, identities, config, agentConfig, deletedNames, version: hash };
|
|
4529
5539
|
}
|
|
4530
5540
|
async function cloudPushRoster(config) {
|
|
@@ -4594,23 +5604,24 @@ async function cloudPullRoster(config) {
|
|
|
4594
5604
|
}
|
|
4595
5605
|
}
|
|
4596
5606
|
function mergeConfig(remoteConfig, configPath) {
|
|
4597
|
-
const cfgPath = configPath ??
|
|
5607
|
+
const cfgPath = configPath ?? path17.join(EXE_AI_DIR, "config.json");
|
|
4598
5608
|
let local = {};
|
|
4599
|
-
if (
|
|
5609
|
+
if (existsSync16(cfgPath)) {
|
|
4600
5610
|
try {
|
|
4601
|
-
local = JSON.parse(
|
|
5611
|
+
local = JSON.parse(readFileSync12(cfgPath, "utf-8"));
|
|
4602
5612
|
} catch {
|
|
4603
5613
|
}
|
|
4604
5614
|
}
|
|
4605
5615
|
const merged = { ...remoteConfig, ...local };
|
|
4606
|
-
const dir =
|
|
4607
|
-
|
|
4608
|
-
|
|
5616
|
+
const dir = path17.dirname(cfgPath);
|
|
5617
|
+
ensurePrivateDirSync(dir);
|
|
5618
|
+
writeFileSync9(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
|
|
5619
|
+
enforcePrivateFileSync(cfgPath);
|
|
4609
5620
|
}
|
|
4610
5621
|
async function mergeRosterFromRemote(remote, paths) {
|
|
4611
5622
|
return withRosterLock(async () => {
|
|
4612
5623
|
const rosterPath = paths?.rosterPath ?? void 0;
|
|
4613
|
-
const identityDir = paths?.identityDir ??
|
|
5624
|
+
const identityDir = paths?.identityDir ?? path17.join(EXE_AI_DIR, "identity");
|
|
4614
5625
|
const localEmployees = await loadEmployees(rosterPath);
|
|
4615
5626
|
const localNames = new Set(localEmployees.map((e) => e.name));
|
|
4616
5627
|
let added = 0;
|
|
@@ -4631,15 +5642,15 @@ async function mergeRosterFromRemote(remote, paths) {
|
|
|
4631
5642
|
) ?? lookupKey;
|
|
4632
5643
|
const remoteIdentity = remote.identities[matchedKey];
|
|
4633
5644
|
if (remoteIdentity) {
|
|
4634
|
-
if (!
|
|
4635
|
-
const idPath =
|
|
5645
|
+
if (!existsSync16(identityDir)) mkdirSync8(identityDir, { recursive: true });
|
|
5646
|
+
const idPath = path17.join(identityDir, `${remoteEmp.name}.md`);
|
|
4636
5647
|
let localIdentity = null;
|
|
4637
5648
|
try {
|
|
4638
|
-
localIdentity =
|
|
5649
|
+
localIdentity = existsSync16(idPath) ? readFileSync12(idPath, "utf-8") : null;
|
|
4639
5650
|
} catch {
|
|
4640
5651
|
}
|
|
4641
5652
|
if (localIdentity !== remoteIdentity) {
|
|
4642
|
-
|
|
5653
|
+
writeFileSync9(idPath, remoteIdentity, "utf-8");
|
|
4643
5654
|
identitiesUpdated++;
|
|
4644
5655
|
}
|
|
4645
5656
|
}
|
|
@@ -4665,16 +5676,18 @@ async function mergeRosterFromRemote(remote, paths) {
|
|
|
4665
5676
|
}
|
|
4666
5677
|
if (remote.agentConfig && Object.keys(remote.agentConfig).length > 0) {
|
|
4667
5678
|
try {
|
|
4668
|
-
const agentConfigPath =
|
|
5679
|
+
const agentConfigPath = path17.join(EXE_AI_DIR, "agent-config.json");
|
|
4669
5680
|
let local = {};
|
|
4670
|
-
if (
|
|
5681
|
+
if (existsSync16(agentConfigPath)) {
|
|
4671
5682
|
try {
|
|
4672
|
-
local = JSON.parse(
|
|
5683
|
+
local = JSON.parse(readFileSync12(agentConfigPath, "utf-8"));
|
|
4673
5684
|
} catch {
|
|
4674
5685
|
}
|
|
4675
5686
|
}
|
|
4676
5687
|
const merged = { ...remote.agentConfig, ...local };
|
|
4677
|
-
|
|
5688
|
+
ensurePrivateDirSync(path17.dirname(agentConfigPath));
|
|
5689
|
+
writeFileSync9(agentConfigPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
5690
|
+
enforcePrivateFileSync(agentConfigPath);
|
|
4678
5691
|
} catch {
|
|
4679
5692
|
}
|
|
4680
5693
|
}
|
|
@@ -5098,7 +6111,7 @@ async function cloudPullDocuments(config) {
|
|
|
5098
6111
|
}
|
|
5099
6112
|
return { pulled };
|
|
5100
6113
|
}
|
|
5101
|
-
var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, ROSTER_DELETIONS_PATH;
|
|
6114
|
+
var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, _pgPromise, _pgFailed, ROSTER_DELETIONS_PATH;
|
|
5102
6115
|
var init_cloud_sync = __esm({
|
|
5103
6116
|
"src/lib/cloud-sync.ts"() {
|
|
5104
6117
|
"use strict";
|
|
@@ -5109,12 +6122,15 @@ var init_cloud_sync = __esm({
|
|
|
5109
6122
|
init_config();
|
|
5110
6123
|
init_crdt_sync();
|
|
5111
6124
|
init_employees();
|
|
6125
|
+
init_secure_files();
|
|
5112
6126
|
LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
|
|
5113
6127
|
FETCH_TIMEOUT_MS = 3e4;
|
|
5114
6128
|
PUSH_BATCH_SIZE = 5e3;
|
|
5115
|
-
ROSTER_LOCK_PATH =
|
|
6129
|
+
ROSTER_LOCK_PATH = path17.join(EXE_AI_DIR, "roster-merge.lock");
|
|
5116
6130
|
LOCK_STALE_MS = 3e4;
|
|
5117
|
-
|
|
6131
|
+
_pgPromise = null;
|
|
6132
|
+
_pgFailed = false;
|
|
6133
|
+
ROSTER_DELETIONS_PATH = path17.join(EXE_AI_DIR, "roster-deletions.json");
|
|
5118
6134
|
}
|
|
5119
6135
|
});
|
|
5120
6136
|
|
|
@@ -5481,10 +6497,10 @@ init_database();
|
|
|
5481
6497
|
init_notifications();
|
|
5482
6498
|
init_task_scope();
|
|
5483
6499
|
init_employees();
|
|
5484
|
-
import
|
|
6500
|
+
import crypto5 from "crypto";
|
|
5485
6501
|
import { execSync as execSync4 } from "child_process";
|
|
5486
|
-
import { existsSync as
|
|
5487
|
-
import
|
|
6502
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync9, openSync as openSync3, closeSync as closeSync3 } from "fs";
|
|
6503
|
+
import path18 from "path";
|
|
5488
6504
|
async function main() {
|
|
5489
6505
|
const agentId = process.env.AGENT_ID ?? "default";
|
|
5490
6506
|
const agentRole = process.env.AGENT_ROLE ?? "employee";
|
|
@@ -5564,7 +6580,7 @@ async function main() {
|
|
|
5564
6580
|
if (limitReached) {
|
|
5565
6581
|
} else {
|
|
5566
6582
|
await writeMemory({
|
|
5567
|
-
id:
|
|
6583
|
+
id: crypto5.randomUUID(),
|
|
5568
6584
|
agent_id: agentId,
|
|
5569
6585
|
agent_role: agentRole,
|
|
5570
6586
|
session_id: `auto-summary-${Date.now()}`,
|
|
@@ -5619,8 +6635,8 @@ async function main() {
|
|
|
5619
6635
|
}
|
|
5620
6636
|
try {
|
|
5621
6637
|
const { EXE_AI_DIR: EXE_AI_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
5622
|
-
const flagPath =
|
|
5623
|
-
if (
|
|
6638
|
+
const flagPath = path18.join(EXE_AI_DIR2, "session-cache", "needs-backfill");
|
|
6639
|
+
if (existsSync17(flagPath)) {
|
|
5624
6640
|
const { tryAcquireWorkerSlot: tryAcquireWorkerSlot2, registerWorkerPid: registerWorkerPid2 } = await Promise.resolve().then(() => (init_worker_gate(), worker_gate_exports));
|
|
5625
6641
|
if (!tryAcquireWorkerSlot2()) {
|
|
5626
6642
|
process.stderr.write("[summary-worker] Backfill needed but worker gate full \u2014 skipping\n");
|
|
@@ -5628,11 +6644,11 @@ async function main() {
|
|
|
5628
6644
|
const { spawn: spawn2 } = await import("child_process");
|
|
5629
6645
|
const { fileURLToPath: fileURLToPath3 } = await import("url");
|
|
5630
6646
|
const thisFile = fileURLToPath3(import.meta.url);
|
|
5631
|
-
const backfillPath =
|
|
5632
|
-
if (
|
|
6647
|
+
const backfillPath = path18.resolve(path18.dirname(thisFile), "backfill-vectors.js");
|
|
6648
|
+
if (existsSync17(backfillPath)) {
|
|
5633
6649
|
const { EXE_AI_DIR: exeDir2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
5634
|
-
const bLogPath =
|
|
5635
|
-
mkdirSync9(
|
|
6650
|
+
const bLogPath = path18.join(exeDir2, "workers.log");
|
|
6651
|
+
mkdirSync9(path18.dirname(bLogPath), { recursive: true });
|
|
5636
6652
|
const bLogFd = openSync3(bLogPath, "a");
|
|
5637
6653
|
const child = spawn2(process.execPath, [backfillPath], {
|
|
5638
6654
|
detached: true,
|