@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
package/dist/bin/exe-link.js
CHANGED
|
@@ -132,6 +132,44 @@ var init_keychain = __esm({
|
|
|
132
132
|
}
|
|
133
133
|
});
|
|
134
134
|
|
|
135
|
+
// src/lib/secure-files.ts
|
|
136
|
+
import { chmodSync, existsSync as existsSync2, mkdirSync } from "fs";
|
|
137
|
+
import { chmod as chmod2, mkdir as mkdir2 } from "fs/promises";
|
|
138
|
+
async function ensurePrivateDir(dirPath) {
|
|
139
|
+
await mkdir2(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
140
|
+
try {
|
|
141
|
+
await chmod2(dirPath, PRIVATE_DIR_MODE);
|
|
142
|
+
} catch {
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function ensurePrivateDirSync(dirPath) {
|
|
146
|
+
mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
147
|
+
try {
|
|
148
|
+
chmodSync(dirPath, PRIVATE_DIR_MODE);
|
|
149
|
+
} catch {
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
async function enforcePrivateFile(filePath) {
|
|
153
|
+
try {
|
|
154
|
+
await chmod2(filePath, PRIVATE_FILE_MODE);
|
|
155
|
+
} catch {
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function enforcePrivateFileSync(filePath) {
|
|
159
|
+
try {
|
|
160
|
+
if (existsSync2(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
|
|
161
|
+
} catch {
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
|
|
165
|
+
var init_secure_files = __esm({
|
|
166
|
+
"src/lib/secure-files.ts"() {
|
|
167
|
+
"use strict";
|
|
168
|
+
PRIVATE_DIR_MODE = 448;
|
|
169
|
+
PRIVATE_FILE_MODE = 384;
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
135
173
|
// src/lib/config.ts
|
|
136
174
|
var config_exports = {};
|
|
137
175
|
__export(config_exports, {
|
|
@@ -148,8 +186,8 @@ __export(config_exports, {
|
|
|
148
186
|
migrateConfig: () => migrateConfig,
|
|
149
187
|
saveConfig: () => saveConfig
|
|
150
188
|
});
|
|
151
|
-
import { readFile as readFile2, writeFile as writeFile2
|
|
152
|
-
import { readFileSync, existsSync as
|
|
189
|
+
import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
190
|
+
import { readFileSync, existsSync as existsSync3, renameSync } from "fs";
|
|
153
191
|
import path2 from "path";
|
|
154
192
|
import os2 from "os";
|
|
155
193
|
function resolveDataDir() {
|
|
@@ -157,7 +195,7 @@ function resolveDataDir() {
|
|
|
157
195
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
158
196
|
const newDir = path2.join(os2.homedir(), ".exe-os");
|
|
159
197
|
const legacyDir = path2.join(os2.homedir(), ".exe-mem");
|
|
160
|
-
if (!
|
|
198
|
+
if (!existsSync3(newDir) && existsSync3(legacyDir)) {
|
|
161
199
|
try {
|
|
162
200
|
renameSync(legacyDir, newDir);
|
|
163
201
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -220,9 +258,9 @@ function normalizeAutoUpdate(raw) {
|
|
|
220
258
|
}
|
|
221
259
|
async function loadConfig() {
|
|
222
260
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
223
|
-
await
|
|
261
|
+
await ensurePrivateDir(dir);
|
|
224
262
|
const configPath = path2.join(dir, "config.json");
|
|
225
|
-
if (!
|
|
263
|
+
if (!existsSync3(configPath)) {
|
|
226
264
|
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
227
265
|
}
|
|
228
266
|
const raw = await readFile2(configPath, "utf-8");
|
|
@@ -235,6 +273,7 @@ async function loadConfig() {
|
|
|
235
273
|
`);
|
|
236
274
|
try {
|
|
237
275
|
await writeFile2(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
276
|
+
await enforcePrivateFile(configPath);
|
|
238
277
|
} catch {
|
|
239
278
|
}
|
|
240
279
|
}
|
|
@@ -253,7 +292,7 @@ async function loadConfig() {
|
|
|
253
292
|
function loadConfigSync() {
|
|
254
293
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
255
294
|
const configPath = path2.join(dir, "config.json");
|
|
256
|
-
if (!
|
|
295
|
+
if (!existsSync3(configPath)) {
|
|
257
296
|
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
258
297
|
}
|
|
259
298
|
try {
|
|
@@ -271,12 +310,10 @@ function loadConfigSync() {
|
|
|
271
310
|
}
|
|
272
311
|
async function saveConfig(config) {
|
|
273
312
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
274
|
-
await
|
|
313
|
+
await ensurePrivateDir(dir);
|
|
275
314
|
const configPath = path2.join(dir, "config.json");
|
|
276
315
|
await writeFile2(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
277
|
-
|
|
278
|
-
await chmod2(configPath, 384);
|
|
279
|
-
}
|
|
316
|
+
await enforcePrivateFile(configPath);
|
|
280
317
|
}
|
|
281
318
|
async function loadConfigFrom(configPath) {
|
|
282
319
|
const raw = await readFile2(configPath, "utf-8");
|
|
@@ -296,6 +333,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
|
|
|
296
333
|
var init_config = __esm({
|
|
297
334
|
"src/lib/config.ts"() {
|
|
298
335
|
"use strict";
|
|
336
|
+
init_secure_files();
|
|
299
337
|
EXE_AI_DIR = resolveDataDir();
|
|
300
338
|
DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
|
|
301
339
|
MODELS_DIR = path2.join(EXE_AI_DIR, "models");
|
|
@@ -488,7 +526,7 @@ var init_db_retry = __esm({
|
|
|
488
526
|
|
|
489
527
|
// src/lib/employees.ts
|
|
490
528
|
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
491
|
-
import { existsSync as
|
|
529
|
+
import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
|
|
492
530
|
import { execSync } from "child_process";
|
|
493
531
|
import path3 from "path";
|
|
494
532
|
import os3 from "os";
|
|
@@ -505,7 +543,7 @@ function getCoordinatorName(employees = loadEmployeesSync()) {
|
|
|
505
543
|
return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
|
|
506
544
|
}
|
|
507
545
|
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
508
|
-
if (!
|
|
546
|
+
if (!existsSync4(employeesPath)) {
|
|
509
547
|
return [];
|
|
510
548
|
}
|
|
511
549
|
const raw = await readFile3(employeesPath, "utf-8");
|
|
@@ -520,7 +558,7 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
|
520
558
|
await writeFile3(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
521
559
|
}
|
|
522
560
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
523
|
-
if (!
|
|
561
|
+
if (!existsSync4(employeesPath)) return [];
|
|
524
562
|
try {
|
|
525
563
|
return JSON.parse(readFileSync2(employeesPath, "utf-8"));
|
|
526
564
|
} catch {
|
|
@@ -554,7 +592,7 @@ function registerBinSymlinks(name) {
|
|
|
554
592
|
for (const suffix of ["", "-opencode"]) {
|
|
555
593
|
const linkName = `${name}${suffix}`;
|
|
556
594
|
const linkPath = path3.join(binDir, linkName);
|
|
557
|
-
if (
|
|
595
|
+
if (existsSync4(linkPath)) {
|
|
558
596
|
skipped.push(linkName);
|
|
559
597
|
continue;
|
|
560
598
|
}
|
|
@@ -567,7 +605,7 @@ function registerBinSymlinks(name) {
|
|
|
567
605
|
}
|
|
568
606
|
return { created, skipped, errors };
|
|
569
607
|
}
|
|
570
|
-
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE;
|
|
608
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, IDENTITY_DIR;
|
|
571
609
|
var init_employees = __esm({
|
|
572
610
|
"src/lib/employees.ts"() {
|
|
573
611
|
"use strict";
|
|
@@ -575,16 +613,638 @@ var init_employees = __esm({
|
|
|
575
613
|
EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
|
|
576
614
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
577
615
|
COORDINATOR_ROLE = "COO";
|
|
616
|
+
IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
|
|
617
|
+
}
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
// src/lib/database-adapter.ts
|
|
621
|
+
import os4 from "os";
|
|
622
|
+
import path4 from "path";
|
|
623
|
+
import { createRequire } from "module";
|
|
624
|
+
import { pathToFileURL } from "url";
|
|
625
|
+
function quotedIdentifier(identifier) {
|
|
626
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
627
|
+
}
|
|
628
|
+
function unqualifiedTableName(name) {
|
|
629
|
+
const raw = name.trim().replace(/^"|"$/g, "");
|
|
630
|
+
const parts = raw.split(".");
|
|
631
|
+
return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
|
|
632
|
+
}
|
|
633
|
+
function stripTrailingSemicolon(sql) {
|
|
634
|
+
return sql.trim().replace(/;+\s*$/u, "");
|
|
635
|
+
}
|
|
636
|
+
function appendClause(sql, clause) {
|
|
637
|
+
const trimmed = stripTrailingSemicolon(sql);
|
|
638
|
+
const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
|
|
639
|
+
if (!returningMatch) {
|
|
640
|
+
return `${trimmed}${clause}`;
|
|
641
|
+
}
|
|
642
|
+
const idx = returningMatch.index;
|
|
643
|
+
return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
|
|
644
|
+
}
|
|
645
|
+
function normalizeStatement(stmt) {
|
|
646
|
+
if (typeof stmt === "string") {
|
|
647
|
+
return { kind: "positional", sql: stmt, args: [] };
|
|
648
|
+
}
|
|
649
|
+
const sql = stmt.sql;
|
|
650
|
+
if (Array.isArray(stmt.args) || stmt.args === void 0) {
|
|
651
|
+
return { kind: "positional", sql, args: stmt.args ?? [] };
|
|
652
|
+
}
|
|
653
|
+
return { kind: "named", sql, args: stmt.args };
|
|
654
|
+
}
|
|
655
|
+
function rewriteBooleanLiterals(sql) {
|
|
656
|
+
let out = sql;
|
|
657
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
658
|
+
const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
|
|
659
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
|
|
660
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
|
|
661
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
|
|
662
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
|
|
663
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
|
|
664
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
|
|
665
|
+
}
|
|
666
|
+
return out;
|
|
667
|
+
}
|
|
668
|
+
function rewriteInsertOrIgnore(sql) {
|
|
669
|
+
if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
|
|
670
|
+
return sql;
|
|
671
|
+
}
|
|
672
|
+
const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
|
|
673
|
+
return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
|
|
674
|
+
}
|
|
675
|
+
function rewriteInsertOrReplace(sql) {
|
|
676
|
+
const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
|
|
677
|
+
if (!match) {
|
|
678
|
+
return sql;
|
|
679
|
+
}
|
|
680
|
+
const rawTable = match[1];
|
|
681
|
+
const rawColumns = match[2];
|
|
682
|
+
const remainder = match[3];
|
|
683
|
+
const tableName = unqualifiedTableName(rawTable);
|
|
684
|
+
const conflictKeys = UPSERT_KEYS[tableName];
|
|
685
|
+
if (!conflictKeys?.length) {
|
|
686
|
+
return sql;
|
|
687
|
+
}
|
|
688
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
689
|
+
const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
|
|
690
|
+
const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
|
|
691
|
+
const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
|
|
692
|
+
return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
|
|
693
|
+
}
|
|
694
|
+
function rewriteSql(sql) {
|
|
695
|
+
let out = sql;
|
|
696
|
+
out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
|
|
697
|
+
out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
|
|
698
|
+
out = rewriteBooleanLiterals(out);
|
|
699
|
+
out = rewriteInsertOrReplace(out);
|
|
700
|
+
out = rewriteInsertOrIgnore(out);
|
|
701
|
+
return stripTrailingSemicolon(out);
|
|
702
|
+
}
|
|
703
|
+
function toBoolean(value) {
|
|
704
|
+
if (value === null || value === void 0) return value;
|
|
705
|
+
if (typeof value === "boolean") return value;
|
|
706
|
+
if (typeof value === "number") return value !== 0;
|
|
707
|
+
if (typeof value === "bigint") return value !== 0n;
|
|
708
|
+
if (typeof value === "string") {
|
|
709
|
+
const normalized = value.trim().toLowerCase();
|
|
710
|
+
if (normalized === "0" || normalized === "false") return false;
|
|
711
|
+
if (normalized === "1" || normalized === "true") return true;
|
|
712
|
+
}
|
|
713
|
+
return Boolean(value);
|
|
714
|
+
}
|
|
715
|
+
function countQuestionMarks(sql, end) {
|
|
716
|
+
let count = 0;
|
|
717
|
+
let inSingle = false;
|
|
718
|
+
let inDouble = false;
|
|
719
|
+
let inLineComment = false;
|
|
720
|
+
let inBlockComment = false;
|
|
721
|
+
for (let i = 0; i < end; i++) {
|
|
722
|
+
const ch = sql[i];
|
|
723
|
+
const next = sql[i + 1];
|
|
724
|
+
if (inLineComment) {
|
|
725
|
+
if (ch === "\n") inLineComment = false;
|
|
726
|
+
continue;
|
|
727
|
+
}
|
|
728
|
+
if (inBlockComment) {
|
|
729
|
+
if (ch === "*" && next === "/") {
|
|
730
|
+
inBlockComment = false;
|
|
731
|
+
i += 1;
|
|
732
|
+
}
|
|
733
|
+
continue;
|
|
734
|
+
}
|
|
735
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
736
|
+
inLineComment = true;
|
|
737
|
+
i += 1;
|
|
738
|
+
continue;
|
|
739
|
+
}
|
|
740
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
741
|
+
inBlockComment = true;
|
|
742
|
+
i += 1;
|
|
743
|
+
continue;
|
|
744
|
+
}
|
|
745
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
746
|
+
inSingle = !inSingle;
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
749
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
750
|
+
inDouble = !inDouble;
|
|
751
|
+
continue;
|
|
752
|
+
}
|
|
753
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
754
|
+
count += 1;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
return count;
|
|
758
|
+
}
|
|
759
|
+
function findBooleanPlaceholderIndexes(sql) {
|
|
760
|
+
const indexes = /* @__PURE__ */ new Set();
|
|
761
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
762
|
+
const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
|
|
763
|
+
for (const match of sql.matchAll(pattern)) {
|
|
764
|
+
const matchText = match[0];
|
|
765
|
+
const qIndex = match.index + matchText.lastIndexOf("?");
|
|
766
|
+
indexes.add(countQuestionMarks(sql, qIndex + 1));
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
return indexes;
|
|
770
|
+
}
|
|
771
|
+
function coerceInsertBooleanArgs(sql, args) {
|
|
772
|
+
const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
|
|
773
|
+
if (!match) return;
|
|
774
|
+
const rawTable = match[1];
|
|
775
|
+
const rawColumns = match[2];
|
|
776
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
777
|
+
if (!boolColumns?.size) return;
|
|
778
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
779
|
+
for (const [index, column] of columns.entries()) {
|
|
780
|
+
if (boolColumns.has(column) && index < args.length) {
|
|
781
|
+
args[index] = toBoolean(args[index]);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
function coerceUpdateBooleanArgs(sql, args) {
|
|
786
|
+
const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
|
|
787
|
+
if (!match) return;
|
|
788
|
+
const rawTable = match[1];
|
|
789
|
+
const setClause = match[2];
|
|
790
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
791
|
+
if (!boolColumns?.size) return;
|
|
792
|
+
const assignments = setClause.split(",");
|
|
793
|
+
let placeholderIndex = 0;
|
|
794
|
+
for (const assignment of assignments) {
|
|
795
|
+
if (!assignment.includes("?")) continue;
|
|
796
|
+
placeholderIndex += 1;
|
|
797
|
+
const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
|
|
798
|
+
if (colMatch && boolColumns.has(colMatch[1])) {
|
|
799
|
+
args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
function coerceBooleanArgs(sql, args) {
|
|
804
|
+
const nextArgs = [...args];
|
|
805
|
+
coerceInsertBooleanArgs(sql, nextArgs);
|
|
806
|
+
coerceUpdateBooleanArgs(sql, nextArgs);
|
|
807
|
+
const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
|
|
808
|
+
for (const index of placeholderIndexes) {
|
|
809
|
+
if (index > 0 && index <= nextArgs.length) {
|
|
810
|
+
nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
return nextArgs;
|
|
814
|
+
}
|
|
815
|
+
function convertQuestionMarksToDollarParams(sql) {
|
|
816
|
+
let out = "";
|
|
817
|
+
let placeholder = 0;
|
|
818
|
+
let inSingle = false;
|
|
819
|
+
let inDouble = false;
|
|
820
|
+
let inLineComment = false;
|
|
821
|
+
let inBlockComment = false;
|
|
822
|
+
for (let i = 0; i < sql.length; i++) {
|
|
823
|
+
const ch = sql[i];
|
|
824
|
+
const next = sql[i + 1];
|
|
825
|
+
if (inLineComment) {
|
|
826
|
+
out += ch;
|
|
827
|
+
if (ch === "\n") inLineComment = false;
|
|
828
|
+
continue;
|
|
829
|
+
}
|
|
830
|
+
if (inBlockComment) {
|
|
831
|
+
out += ch;
|
|
832
|
+
if (ch === "*" && next === "/") {
|
|
833
|
+
out += next;
|
|
834
|
+
inBlockComment = false;
|
|
835
|
+
i += 1;
|
|
836
|
+
}
|
|
837
|
+
continue;
|
|
838
|
+
}
|
|
839
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
840
|
+
out += ch + next;
|
|
841
|
+
inLineComment = true;
|
|
842
|
+
i += 1;
|
|
843
|
+
continue;
|
|
844
|
+
}
|
|
845
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
846
|
+
out += ch + next;
|
|
847
|
+
inBlockComment = true;
|
|
848
|
+
i += 1;
|
|
849
|
+
continue;
|
|
850
|
+
}
|
|
851
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
852
|
+
inSingle = !inSingle;
|
|
853
|
+
out += ch;
|
|
854
|
+
continue;
|
|
855
|
+
}
|
|
856
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
857
|
+
inDouble = !inDouble;
|
|
858
|
+
out += ch;
|
|
859
|
+
continue;
|
|
860
|
+
}
|
|
861
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
862
|
+
placeholder += 1;
|
|
863
|
+
out += `$${placeholder}`;
|
|
864
|
+
continue;
|
|
865
|
+
}
|
|
866
|
+
out += ch;
|
|
867
|
+
}
|
|
868
|
+
return out;
|
|
869
|
+
}
|
|
870
|
+
function translateStatementForPostgres(stmt) {
|
|
871
|
+
const normalized = normalizeStatement(stmt);
|
|
872
|
+
if (normalized.kind === "named") {
|
|
873
|
+
throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
|
|
874
|
+
}
|
|
875
|
+
const rewrittenSql = rewriteSql(normalized.sql);
|
|
876
|
+
const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
|
|
877
|
+
return {
|
|
878
|
+
sql: convertQuestionMarksToDollarParams(rewrittenSql),
|
|
879
|
+
args: coercedArgs
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
function shouldBypassPostgres(stmt) {
|
|
883
|
+
const normalized = normalizeStatement(stmt);
|
|
884
|
+
if (normalized.kind === "named") {
|
|
885
|
+
return true;
|
|
886
|
+
}
|
|
887
|
+
return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
|
|
888
|
+
}
|
|
889
|
+
function shouldFallbackOnError(error) {
|
|
890
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
891
|
+
return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
|
|
892
|
+
}
|
|
893
|
+
function isReadQuery(sql) {
|
|
894
|
+
const trimmed = sql.trimStart();
|
|
895
|
+
return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
|
|
896
|
+
}
|
|
897
|
+
function buildRow(row, columns) {
|
|
898
|
+
const values = columns.map((column) => row[column]);
|
|
899
|
+
return Object.assign(values, row);
|
|
900
|
+
}
|
|
901
|
+
function buildResultSet(rows, rowsAffected = 0) {
|
|
902
|
+
const columns = rows[0] ? Object.keys(rows[0]) : [];
|
|
903
|
+
const resultRows = rows.map((row) => buildRow(row, columns));
|
|
904
|
+
return {
|
|
905
|
+
columns,
|
|
906
|
+
columnTypes: columns.map(() => ""),
|
|
907
|
+
rows: resultRows,
|
|
908
|
+
rowsAffected,
|
|
909
|
+
lastInsertRowid: void 0,
|
|
910
|
+
toJSON() {
|
|
911
|
+
return {
|
|
912
|
+
columns,
|
|
913
|
+
columnTypes: columns.map(() => ""),
|
|
914
|
+
rows,
|
|
915
|
+
rowsAffected,
|
|
916
|
+
lastInsertRowid: void 0
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
async function loadPrismaClient() {
|
|
922
|
+
if (!prismaClientPromise) {
|
|
923
|
+
prismaClientPromise = (async () => {
|
|
924
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
925
|
+
if (explicitPath) {
|
|
926
|
+
const module2 = await import(pathToFileURL(explicitPath).href);
|
|
927
|
+
const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
|
|
928
|
+
if (!PrismaClient2) {
|
|
929
|
+
throw new Error(`No PrismaClient export found at ${explicitPath}`);
|
|
930
|
+
}
|
|
931
|
+
return new PrismaClient2();
|
|
932
|
+
}
|
|
933
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path4.join(os4.homedir(), "exe-db");
|
|
934
|
+
const requireFromExeDb = createRequire(path4.join(exeDbRoot, "package.json"));
|
|
935
|
+
const prismaEntry = requireFromExeDb.resolve("@prisma/client");
|
|
936
|
+
const module = await import(pathToFileURL(prismaEntry).href);
|
|
937
|
+
const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
|
|
938
|
+
if (!PrismaClient) {
|
|
939
|
+
throw new Error(`No PrismaClient export found in ${prismaEntry}`);
|
|
940
|
+
}
|
|
941
|
+
return new PrismaClient();
|
|
942
|
+
})();
|
|
943
|
+
}
|
|
944
|
+
return prismaClientPromise;
|
|
945
|
+
}
|
|
946
|
+
async function ensureCompatibilityViews(prisma) {
|
|
947
|
+
if (!compatibilityBootstrapPromise) {
|
|
948
|
+
compatibilityBootstrapPromise = (async () => {
|
|
949
|
+
for (const mapping of VIEW_MAPPINGS) {
|
|
950
|
+
const relation = mapping.source.replace(/"/g, "");
|
|
951
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
952
|
+
"SELECT to_regclass($1) AS regclass",
|
|
953
|
+
relation
|
|
954
|
+
);
|
|
955
|
+
if (!rows[0]?.regclass) {
|
|
956
|
+
continue;
|
|
957
|
+
}
|
|
958
|
+
await prisma.$executeRawUnsafe(
|
|
959
|
+
`CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
|
|
960
|
+
);
|
|
961
|
+
}
|
|
962
|
+
})();
|
|
963
|
+
}
|
|
964
|
+
return compatibilityBootstrapPromise;
|
|
965
|
+
}
|
|
966
|
+
async function executeOnPrisma(executor, stmt) {
|
|
967
|
+
const translated = translateStatementForPostgres(stmt);
|
|
968
|
+
if (isReadQuery(translated.sql)) {
|
|
969
|
+
const rows = await executor.$queryRawUnsafe(
|
|
970
|
+
translated.sql,
|
|
971
|
+
...translated.args
|
|
972
|
+
);
|
|
973
|
+
return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
|
|
974
|
+
}
|
|
975
|
+
const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
|
|
976
|
+
return buildResultSet([], rowsAffected);
|
|
977
|
+
}
|
|
978
|
+
function splitSqlStatements(sql) {
|
|
979
|
+
const parts = [];
|
|
980
|
+
let current = "";
|
|
981
|
+
let inSingle = false;
|
|
982
|
+
let inDouble = false;
|
|
983
|
+
let inLineComment = false;
|
|
984
|
+
let inBlockComment = false;
|
|
985
|
+
for (let i = 0; i < sql.length; i++) {
|
|
986
|
+
const ch = sql[i];
|
|
987
|
+
const next = sql[i + 1];
|
|
988
|
+
if (inLineComment) {
|
|
989
|
+
current += ch;
|
|
990
|
+
if (ch === "\n") inLineComment = false;
|
|
991
|
+
continue;
|
|
992
|
+
}
|
|
993
|
+
if (inBlockComment) {
|
|
994
|
+
current += ch;
|
|
995
|
+
if (ch === "*" && next === "/") {
|
|
996
|
+
current += next;
|
|
997
|
+
inBlockComment = false;
|
|
998
|
+
i += 1;
|
|
999
|
+
}
|
|
1000
|
+
continue;
|
|
1001
|
+
}
|
|
1002
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
1003
|
+
current += ch + next;
|
|
1004
|
+
inLineComment = true;
|
|
1005
|
+
i += 1;
|
|
1006
|
+
continue;
|
|
1007
|
+
}
|
|
1008
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
1009
|
+
current += ch + next;
|
|
1010
|
+
inBlockComment = true;
|
|
1011
|
+
i += 1;
|
|
1012
|
+
continue;
|
|
1013
|
+
}
|
|
1014
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
1015
|
+
inSingle = !inSingle;
|
|
1016
|
+
current += ch;
|
|
1017
|
+
continue;
|
|
1018
|
+
}
|
|
1019
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
1020
|
+
inDouble = !inDouble;
|
|
1021
|
+
current += ch;
|
|
1022
|
+
continue;
|
|
1023
|
+
}
|
|
1024
|
+
if (!inSingle && !inDouble && ch === ";") {
|
|
1025
|
+
if (current.trim()) {
|
|
1026
|
+
parts.push(current.trim());
|
|
1027
|
+
}
|
|
1028
|
+
current = "";
|
|
1029
|
+
continue;
|
|
1030
|
+
}
|
|
1031
|
+
current += ch;
|
|
1032
|
+
}
|
|
1033
|
+
if (current.trim()) {
|
|
1034
|
+
parts.push(current.trim());
|
|
1035
|
+
}
|
|
1036
|
+
return parts;
|
|
1037
|
+
}
|
|
1038
|
+
async function createPrismaDbAdapter(fallbackClient) {
|
|
1039
|
+
const prisma = await loadPrismaClient();
|
|
1040
|
+
await ensureCompatibilityViews(prisma);
|
|
1041
|
+
let closed = false;
|
|
1042
|
+
let adapter;
|
|
1043
|
+
const fallbackExecute = async (stmt, error) => {
|
|
1044
|
+
if (!fallbackClient) {
|
|
1045
|
+
if (error) throw error;
|
|
1046
|
+
throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
|
|
1047
|
+
}
|
|
1048
|
+
if (error) {
|
|
1049
|
+
process.stderr.write(
|
|
1050
|
+
`[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
1051
|
+
`
|
|
1052
|
+
);
|
|
1053
|
+
}
|
|
1054
|
+
return fallbackClient.execute(stmt);
|
|
1055
|
+
};
|
|
1056
|
+
adapter = {
|
|
1057
|
+
async execute(stmt) {
|
|
1058
|
+
if (shouldBypassPostgres(stmt)) {
|
|
1059
|
+
return fallbackExecute(stmt);
|
|
1060
|
+
}
|
|
1061
|
+
try {
|
|
1062
|
+
return await executeOnPrisma(prisma, stmt);
|
|
1063
|
+
} catch (error) {
|
|
1064
|
+
if (shouldFallbackOnError(error)) {
|
|
1065
|
+
return fallbackExecute(stmt, error);
|
|
1066
|
+
}
|
|
1067
|
+
throw error;
|
|
1068
|
+
}
|
|
1069
|
+
},
|
|
1070
|
+
async batch(stmts, mode) {
|
|
1071
|
+
if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
|
|
1072
|
+
if (!fallbackClient) {
|
|
1073
|
+
throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
|
|
1074
|
+
}
|
|
1075
|
+
return fallbackClient.batch(stmts, mode);
|
|
1076
|
+
}
|
|
1077
|
+
try {
|
|
1078
|
+
if (prisma.$transaction) {
|
|
1079
|
+
return await prisma.$transaction(async (tx) => {
|
|
1080
|
+
const results2 = [];
|
|
1081
|
+
for (const stmt of stmts) {
|
|
1082
|
+
results2.push(await executeOnPrisma(tx, stmt));
|
|
1083
|
+
}
|
|
1084
|
+
return results2;
|
|
1085
|
+
});
|
|
1086
|
+
}
|
|
1087
|
+
const results = [];
|
|
1088
|
+
for (const stmt of stmts) {
|
|
1089
|
+
results.push(await executeOnPrisma(prisma, stmt));
|
|
1090
|
+
}
|
|
1091
|
+
return results;
|
|
1092
|
+
} catch (error) {
|
|
1093
|
+
if (fallbackClient && shouldFallbackOnError(error)) {
|
|
1094
|
+
process.stderr.write(
|
|
1095
|
+
`[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
1096
|
+
`
|
|
1097
|
+
);
|
|
1098
|
+
return fallbackClient.batch(stmts, mode);
|
|
1099
|
+
}
|
|
1100
|
+
throw error;
|
|
1101
|
+
}
|
|
1102
|
+
},
|
|
1103
|
+
async migrate(stmts) {
|
|
1104
|
+
if (fallbackClient) {
|
|
1105
|
+
return fallbackClient.migrate(stmts);
|
|
1106
|
+
}
|
|
1107
|
+
return adapter.batch(stmts, "deferred");
|
|
1108
|
+
},
|
|
1109
|
+
async transaction(mode) {
|
|
1110
|
+
if (!fallbackClient) {
|
|
1111
|
+
throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
|
|
1112
|
+
}
|
|
1113
|
+
return fallbackClient.transaction(mode);
|
|
1114
|
+
},
|
|
1115
|
+
async executeMultiple(sql) {
|
|
1116
|
+
if (fallbackClient && shouldBypassPostgres(sql)) {
|
|
1117
|
+
return fallbackClient.executeMultiple(sql);
|
|
1118
|
+
}
|
|
1119
|
+
for (const statement of splitSqlStatements(sql)) {
|
|
1120
|
+
await adapter.execute(statement);
|
|
1121
|
+
}
|
|
1122
|
+
},
|
|
1123
|
+
async sync() {
|
|
1124
|
+
if (fallbackClient) {
|
|
1125
|
+
return fallbackClient.sync();
|
|
1126
|
+
}
|
|
1127
|
+
return { frame_no: 0, frames_synced: 0 };
|
|
1128
|
+
},
|
|
1129
|
+
close() {
|
|
1130
|
+
closed = true;
|
|
1131
|
+
prismaClientPromise = null;
|
|
1132
|
+
compatibilityBootstrapPromise = null;
|
|
1133
|
+
void prisma.$disconnect?.();
|
|
1134
|
+
},
|
|
1135
|
+
get closed() {
|
|
1136
|
+
return closed;
|
|
1137
|
+
},
|
|
1138
|
+
get protocol() {
|
|
1139
|
+
return "prisma-postgres";
|
|
1140
|
+
}
|
|
1141
|
+
};
|
|
1142
|
+
return adapter;
|
|
1143
|
+
}
|
|
1144
|
+
var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
|
|
1145
|
+
var init_database_adapter = __esm({
|
|
1146
|
+
"src/lib/database-adapter.ts"() {
|
|
1147
|
+
"use strict";
|
|
1148
|
+
VIEW_MAPPINGS = [
|
|
1149
|
+
{ view: "memories", source: "memory.memory_records" },
|
|
1150
|
+
{ view: "tasks", source: "memory.tasks" },
|
|
1151
|
+
{ view: "behaviors", source: "memory.behaviors" },
|
|
1152
|
+
{ view: "entities", source: "memory.entities" },
|
|
1153
|
+
{ view: "relationships", source: "memory.relationships" },
|
|
1154
|
+
{ view: "entity_memories", source: "memory.entity_memories" },
|
|
1155
|
+
{ view: "entity_aliases", source: "memory.entity_aliases" },
|
|
1156
|
+
{ view: "notifications", source: "memory.notifications" },
|
|
1157
|
+
{ view: "messages", source: "memory.messages" },
|
|
1158
|
+
{ view: "users", source: "wiki.users" },
|
|
1159
|
+
{ view: "workspaces", source: "wiki.workspaces" },
|
|
1160
|
+
{ view: "workspace_users", source: "wiki.workspace_users" },
|
|
1161
|
+
{ view: "documents", source: "wiki.workspace_documents" },
|
|
1162
|
+
{ view: "chats", source: "wiki.workspace_chats" }
|
|
1163
|
+
];
|
|
1164
|
+
UPSERT_KEYS = {
|
|
1165
|
+
memories: ["id"],
|
|
1166
|
+
tasks: ["id"],
|
|
1167
|
+
behaviors: ["id"],
|
|
1168
|
+
entities: ["id"],
|
|
1169
|
+
relationships: ["id"],
|
|
1170
|
+
entity_aliases: ["alias"],
|
|
1171
|
+
notifications: ["id"],
|
|
1172
|
+
messages: ["id"],
|
|
1173
|
+
users: ["id"],
|
|
1174
|
+
workspaces: ["id"],
|
|
1175
|
+
workspace_users: ["id"],
|
|
1176
|
+
documents: ["id"],
|
|
1177
|
+
chats: ["id"]
|
|
1178
|
+
};
|
|
1179
|
+
BOOLEAN_COLUMNS_BY_TABLE = {
|
|
1180
|
+
memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
|
|
1181
|
+
behaviors: /* @__PURE__ */ new Set(["active"]),
|
|
1182
|
+
notifications: /* @__PURE__ */ new Set(["read"]),
|
|
1183
|
+
users: /* @__PURE__ */ new Set(["has_personal_memory"])
|
|
1184
|
+
};
|
|
1185
|
+
BOOLEAN_COLUMN_NAMES = new Set(
|
|
1186
|
+
Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
|
|
1187
|
+
);
|
|
1188
|
+
IMMEDIATE_FALLBACK_PATTERNS = [
|
|
1189
|
+
/\bPRAGMA\b/i,
|
|
1190
|
+
/\bsqlite_master\b/i,
|
|
1191
|
+
/(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
|
|
1192
|
+
/\bMATCH\b/i,
|
|
1193
|
+
/\bvector_distance_cos\s*\(/i,
|
|
1194
|
+
/\bjson_extract\s*\(/i,
|
|
1195
|
+
/\bjulianday\s*\(/i,
|
|
1196
|
+
/\bstrftime\s*\(/i,
|
|
1197
|
+
/\blast_insert_rowid\s*\(/i
|
|
1198
|
+
];
|
|
1199
|
+
prismaClientPromise = null;
|
|
1200
|
+
compatibilityBootstrapPromise = null;
|
|
1201
|
+
}
|
|
1202
|
+
});
|
|
1203
|
+
|
|
1204
|
+
// src/lib/daemon-auth.ts
|
|
1205
|
+
import crypto2 from "crypto";
|
|
1206
|
+
import path5 from "path";
|
|
1207
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
1208
|
+
function normalizeToken(token) {
|
|
1209
|
+
if (!token) return null;
|
|
1210
|
+
const trimmed = token.trim();
|
|
1211
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
1212
|
+
}
|
|
1213
|
+
function readDaemonToken() {
|
|
1214
|
+
try {
|
|
1215
|
+
if (!existsSync5(DAEMON_TOKEN_PATH)) return null;
|
|
1216
|
+
return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
|
|
1217
|
+
} catch {
|
|
1218
|
+
return null;
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
function ensureDaemonToken(seed) {
|
|
1222
|
+
const existing = readDaemonToken();
|
|
1223
|
+
if (existing) return existing;
|
|
1224
|
+
const token = normalizeToken(seed) ?? crypto2.randomBytes(32).toString("hex");
|
|
1225
|
+
ensurePrivateDirSync(EXE_AI_DIR);
|
|
1226
|
+
writeFileSync2(DAEMON_TOKEN_PATH, `${token}
|
|
1227
|
+
`, "utf8");
|
|
1228
|
+
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
1229
|
+
return token;
|
|
1230
|
+
}
|
|
1231
|
+
var DAEMON_TOKEN_PATH;
|
|
1232
|
+
var init_daemon_auth = __esm({
|
|
1233
|
+
"src/lib/daemon-auth.ts"() {
|
|
1234
|
+
"use strict";
|
|
1235
|
+
init_config();
|
|
1236
|
+
init_secure_files();
|
|
1237
|
+
DAEMON_TOKEN_PATH = path5.join(EXE_AI_DIR, "exed.token");
|
|
578
1238
|
}
|
|
579
1239
|
});
|
|
580
1240
|
|
|
581
1241
|
// src/lib/exe-daemon-client.ts
|
|
582
1242
|
import net from "net";
|
|
583
|
-
import
|
|
1243
|
+
import os5 from "os";
|
|
584
1244
|
import { spawn } from "child_process";
|
|
585
1245
|
import { randomUUID } from "crypto";
|
|
586
|
-
import { existsSync as
|
|
587
|
-
import
|
|
1246
|
+
import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
|
|
1247
|
+
import path6 from "path";
|
|
588
1248
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
589
1249
|
function handleData(chunk) {
|
|
590
1250
|
_buffer += chunk.toString();
|
|
@@ -612,9 +1272,9 @@ function handleData(chunk) {
|
|
|
612
1272
|
}
|
|
613
1273
|
}
|
|
614
1274
|
function cleanupStaleFiles() {
|
|
615
|
-
if (
|
|
1275
|
+
if (existsSync6(PID_PATH)) {
|
|
616
1276
|
try {
|
|
617
|
-
const pid = parseInt(
|
|
1277
|
+
const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
|
|
618
1278
|
if (pid > 0) {
|
|
619
1279
|
try {
|
|
620
1280
|
process.kill(pid, 0);
|
|
@@ -635,17 +1295,17 @@ function cleanupStaleFiles() {
|
|
|
635
1295
|
}
|
|
636
1296
|
}
|
|
637
1297
|
function findPackageRoot() {
|
|
638
|
-
let dir =
|
|
639
|
-
const { root } =
|
|
1298
|
+
let dir = path6.dirname(fileURLToPath2(import.meta.url));
|
|
1299
|
+
const { root } = path6.parse(dir);
|
|
640
1300
|
while (dir !== root) {
|
|
641
|
-
if (
|
|
642
|
-
dir =
|
|
1301
|
+
if (existsSync6(path6.join(dir, "package.json"))) return dir;
|
|
1302
|
+
dir = path6.dirname(dir);
|
|
643
1303
|
}
|
|
644
1304
|
return null;
|
|
645
1305
|
}
|
|
646
1306
|
function spawnDaemon() {
|
|
647
|
-
const freeGB =
|
|
648
|
-
const totalGB =
|
|
1307
|
+
const freeGB = os5.freemem() / (1024 * 1024 * 1024);
|
|
1308
|
+
const totalGB = os5.totalmem() / (1024 * 1024 * 1024);
|
|
649
1309
|
if (totalGB <= 8) {
|
|
650
1310
|
process.stderr.write(
|
|
651
1311
|
`[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
|
|
@@ -665,16 +1325,17 @@ function spawnDaemon() {
|
|
|
665
1325
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
666
1326
|
return;
|
|
667
1327
|
}
|
|
668
|
-
const daemonPath =
|
|
669
|
-
if (!
|
|
1328
|
+
const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
1329
|
+
if (!existsSync6(daemonPath)) {
|
|
670
1330
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
671
1331
|
`);
|
|
672
1332
|
return;
|
|
673
1333
|
}
|
|
674
1334
|
const resolvedPath = daemonPath;
|
|
1335
|
+
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
675
1336
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
676
1337
|
`);
|
|
677
|
-
const logPath =
|
|
1338
|
+
const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
|
|
678
1339
|
let stderrFd = "ignore";
|
|
679
1340
|
try {
|
|
680
1341
|
stderrFd = openSync(logPath, "a");
|
|
@@ -692,7 +1353,8 @@ function spawnDaemon() {
|
|
|
692
1353
|
TMUX_PANE: void 0,
|
|
693
1354
|
// Prevents resolveExeSession() from scoping to one session
|
|
694
1355
|
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
695
|
-
EXE_DAEMON_PID: PID_PATH
|
|
1356
|
+
EXE_DAEMON_PID: PID_PATH,
|
|
1357
|
+
[DAEMON_TOKEN_ENV]: daemonToken
|
|
696
1358
|
}
|
|
697
1359
|
});
|
|
698
1360
|
child.unref();
|
|
@@ -799,13 +1461,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
799
1461
|
return;
|
|
800
1462
|
}
|
|
801
1463
|
const id = randomUUID();
|
|
1464
|
+
const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
|
|
802
1465
|
const timer = setTimeout(() => {
|
|
803
1466
|
_pending.delete(id);
|
|
804
1467
|
resolve({ error: "Request timeout" });
|
|
805
1468
|
}, timeoutMs);
|
|
806
1469
|
_pending.set(id, { resolve, timer });
|
|
807
1470
|
try {
|
|
808
|
-
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
1471
|
+
_socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
|
|
809
1472
|
} catch {
|
|
810
1473
|
clearTimeout(timer);
|
|
811
1474
|
_pending.delete(id);
|
|
@@ -816,17 +1479,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
816
1479
|
function isClientConnected() {
|
|
817
1480
|
return _connected;
|
|
818
1481
|
}
|
|
819
|
-
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
|
|
1482
|
+
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, DAEMON_TOKEN_ENV, _socket, _connected, _buffer, _pending, MAX_BUFFER;
|
|
820
1483
|
var init_exe_daemon_client = __esm({
|
|
821
1484
|
"src/lib/exe-daemon-client.ts"() {
|
|
822
1485
|
"use strict";
|
|
823
1486
|
init_config();
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
1487
|
+
init_daemon_auth();
|
|
1488
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
|
|
1489
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
|
|
1490
|
+
SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
827
1491
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
828
1492
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
829
1493
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
1494
|
+
DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
|
|
830
1495
|
_socket = null;
|
|
831
1496
|
_connected = false;
|
|
832
1497
|
_buffer = "";
|
|
@@ -905,7 +1570,7 @@ __export(db_daemon_client_exports, {
|
|
|
905
1570
|
createDaemonDbClient: () => createDaemonDbClient,
|
|
906
1571
|
initDaemonDbClient: () => initDaemonDbClient
|
|
907
1572
|
});
|
|
908
|
-
function
|
|
1573
|
+
function normalizeStatement2(stmt) {
|
|
909
1574
|
if (typeof stmt === "string") {
|
|
910
1575
|
return { sql: stmt, args: [] };
|
|
911
1576
|
}
|
|
@@ -929,7 +1594,7 @@ function createDaemonDbClient(fallbackClient) {
|
|
|
929
1594
|
if (!_useDaemon || !isClientConnected()) {
|
|
930
1595
|
return fallbackClient.execute(stmt);
|
|
931
1596
|
}
|
|
932
|
-
const { sql, args } =
|
|
1597
|
+
const { sql, args } = normalizeStatement2(stmt);
|
|
933
1598
|
const response = await sendDaemonRequest({
|
|
934
1599
|
type: "db-execute",
|
|
935
1600
|
sql,
|
|
@@ -954,7 +1619,7 @@ function createDaemonDbClient(fallbackClient) {
|
|
|
954
1619
|
if (!_useDaemon || !isClientConnected()) {
|
|
955
1620
|
return fallbackClient.batch(stmts, mode);
|
|
956
1621
|
}
|
|
957
|
-
const statements = stmts.map(
|
|
1622
|
+
const statements = stmts.map(normalizeStatement2);
|
|
958
1623
|
const response = await sendDaemonRequest({
|
|
959
1624
|
type: "db-batch",
|
|
960
1625
|
statements,
|
|
@@ -1049,6 +1714,18 @@ __export(database_exports, {
|
|
|
1049
1714
|
});
|
|
1050
1715
|
import { createClient } from "@libsql/client";
|
|
1051
1716
|
async function initDatabase(config) {
|
|
1717
|
+
if (_walCheckpointTimer) {
|
|
1718
|
+
clearInterval(_walCheckpointTimer);
|
|
1719
|
+
_walCheckpointTimer = null;
|
|
1720
|
+
}
|
|
1721
|
+
if (_daemonClient) {
|
|
1722
|
+
_daemonClient.close();
|
|
1723
|
+
_daemonClient = null;
|
|
1724
|
+
}
|
|
1725
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
1726
|
+
_adapterClient.close();
|
|
1727
|
+
}
|
|
1728
|
+
_adapterClient = null;
|
|
1052
1729
|
if (_client) {
|
|
1053
1730
|
_client.close();
|
|
1054
1731
|
_client = null;
|
|
@@ -1062,6 +1739,7 @@ async function initDatabase(config) {
|
|
|
1062
1739
|
}
|
|
1063
1740
|
_client = createClient(opts);
|
|
1064
1741
|
_resilientClient = wrapWithRetry(_client);
|
|
1742
|
+
_adapterClient = _resilientClient;
|
|
1065
1743
|
_client.execute("PRAGMA busy_timeout = 30000").catch(() => {
|
|
1066
1744
|
});
|
|
1067
1745
|
_client.execute("PRAGMA journal_mode = WAL").catch(() => {
|
|
@@ -1072,14 +1750,20 @@ async function initDatabase(config) {
|
|
|
1072
1750
|
});
|
|
1073
1751
|
}, 3e4);
|
|
1074
1752
|
_walCheckpointTimer.unref();
|
|
1753
|
+
if (process.env.DATABASE_URL) {
|
|
1754
|
+
_adapterClient = await createPrismaDbAdapter(_resilientClient);
|
|
1755
|
+
}
|
|
1075
1756
|
}
|
|
1076
1757
|
function isInitialized() {
|
|
1077
|
-
return _client !== null;
|
|
1758
|
+
return _adapterClient !== null || _client !== null;
|
|
1078
1759
|
}
|
|
1079
1760
|
function getClient() {
|
|
1080
|
-
if (!
|
|
1761
|
+
if (!_adapterClient) {
|
|
1081
1762
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
1082
1763
|
}
|
|
1764
|
+
if (process.env.DATABASE_URL) {
|
|
1765
|
+
return _adapterClient;
|
|
1766
|
+
}
|
|
1083
1767
|
if (process.env.EXE_IS_DAEMON === "1") {
|
|
1084
1768
|
return _resilientClient;
|
|
1085
1769
|
}
|
|
@@ -1089,6 +1773,7 @@ function getClient() {
|
|
|
1089
1773
|
return _resilientClient;
|
|
1090
1774
|
}
|
|
1091
1775
|
async function initDaemonClient() {
|
|
1776
|
+
if (process.env.DATABASE_URL) return;
|
|
1092
1777
|
if (process.env.EXE_IS_DAEMON === "1") return;
|
|
1093
1778
|
if (!_resilientClient) return;
|
|
1094
1779
|
try {
|
|
@@ -1385,6 +2070,7 @@ async function ensureSchema() {
|
|
|
1385
2070
|
project TEXT NOT NULL,
|
|
1386
2071
|
summary TEXT NOT NULL,
|
|
1387
2072
|
task_file TEXT,
|
|
2073
|
+
session_scope TEXT,
|
|
1388
2074
|
read INTEGER NOT NULL DEFAULT 0,
|
|
1389
2075
|
created_at TEXT NOT NULL
|
|
1390
2076
|
);
|
|
@@ -1393,7 +2079,7 @@ async function ensureSchema() {
|
|
|
1393
2079
|
ON notifications(read);
|
|
1394
2080
|
|
|
1395
2081
|
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
1396
|
-
ON notifications(agent_id);
|
|
2082
|
+
ON notifications(agent_id, session_scope);
|
|
1397
2083
|
|
|
1398
2084
|
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
1399
2085
|
ON notifications(task_file);
|
|
@@ -1431,6 +2117,7 @@ async function ensureSchema() {
|
|
|
1431
2117
|
target_agent TEXT NOT NULL,
|
|
1432
2118
|
target_project TEXT,
|
|
1433
2119
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
2120
|
+
session_scope TEXT,
|
|
1434
2121
|
content TEXT NOT NULL,
|
|
1435
2122
|
priority TEXT DEFAULT 'normal',
|
|
1436
2123
|
status TEXT DEFAULT 'pending',
|
|
@@ -1444,10 +2131,31 @@ async function ensureSchema() {
|
|
|
1444
2131
|
);
|
|
1445
2132
|
|
|
1446
2133
|
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
1447
|
-
ON messages(target_agent, status);
|
|
2134
|
+
ON messages(target_agent, session_scope, status);
|
|
1448
2135
|
|
|
1449
2136
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
1450
|
-
ON messages(target_agent, from_agent, server_seq);
|
|
2137
|
+
ON messages(target_agent, session_scope, from_agent, server_seq);
|
|
2138
|
+
`);
|
|
2139
|
+
try {
|
|
2140
|
+
await client.execute({
|
|
2141
|
+
sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
|
|
2142
|
+
args: []
|
|
2143
|
+
});
|
|
2144
|
+
} catch {
|
|
2145
|
+
}
|
|
2146
|
+
try {
|
|
2147
|
+
await client.execute({
|
|
2148
|
+
sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
|
|
2149
|
+
args: []
|
|
2150
|
+
});
|
|
2151
|
+
} catch {
|
|
2152
|
+
}
|
|
2153
|
+
await client.executeMultiple(`
|
|
2154
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
|
|
2155
|
+
ON notifications(agent_id, session_scope, read, created_at);
|
|
2156
|
+
|
|
2157
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
|
|
2158
|
+
ON messages(target_agent, session_scope, status, created_at);
|
|
1451
2159
|
`);
|
|
1452
2160
|
try {
|
|
1453
2161
|
await client.execute({
|
|
@@ -2031,28 +2739,45 @@ async function ensureSchema() {
|
|
|
2031
2739
|
} catch {
|
|
2032
2740
|
}
|
|
2033
2741
|
}
|
|
2742
|
+
try {
|
|
2743
|
+
await client.execute({
|
|
2744
|
+
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
2745
|
+
args: []
|
|
2746
|
+
});
|
|
2747
|
+
} catch {
|
|
2748
|
+
}
|
|
2034
2749
|
}
|
|
2035
2750
|
async function disposeDatabase() {
|
|
2751
|
+
if (_walCheckpointTimer) {
|
|
2752
|
+
clearInterval(_walCheckpointTimer);
|
|
2753
|
+
_walCheckpointTimer = null;
|
|
2754
|
+
}
|
|
2036
2755
|
if (_daemonClient) {
|
|
2037
2756
|
_daemonClient.close();
|
|
2038
2757
|
_daemonClient = null;
|
|
2039
2758
|
}
|
|
2759
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
2760
|
+
_adapterClient.close();
|
|
2761
|
+
}
|
|
2762
|
+
_adapterClient = null;
|
|
2040
2763
|
if (_client) {
|
|
2041
2764
|
_client.close();
|
|
2042
2765
|
_client = null;
|
|
2043
2766
|
_resilientClient = null;
|
|
2044
2767
|
}
|
|
2045
2768
|
}
|
|
2046
|
-
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
|
|
2769
|
+
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
|
|
2047
2770
|
var init_database = __esm({
|
|
2048
2771
|
"src/lib/database.ts"() {
|
|
2049
2772
|
"use strict";
|
|
2050
2773
|
init_db_retry();
|
|
2051
2774
|
init_employees();
|
|
2775
|
+
init_database_adapter();
|
|
2052
2776
|
_client = null;
|
|
2053
2777
|
_resilientClient = null;
|
|
2054
2778
|
_walCheckpointTimer = null;
|
|
2055
2779
|
_daemonClient = null;
|
|
2780
|
+
_adapterClient = null;
|
|
2056
2781
|
initTurso = initDatabase;
|
|
2057
2782
|
disposeTurso = disposeDatabase;
|
|
2058
2783
|
}
|
|
@@ -2079,29 +2804,32 @@ var init_compress = __esm({
|
|
|
2079
2804
|
});
|
|
2080
2805
|
|
|
2081
2806
|
// src/lib/license.ts
|
|
2082
|
-
import { readFileSync as
|
|
2807
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync7, mkdirSync as mkdirSync2 } from "fs";
|
|
2083
2808
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
2084
|
-
import
|
|
2809
|
+
import { createRequire as createRequire2 } from "module";
|
|
2810
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
2811
|
+
import os6 from "os";
|
|
2812
|
+
import path7 from "path";
|
|
2085
2813
|
import { jwtVerify, importSPKI } from "jose";
|
|
2086
2814
|
function loadDeviceId() {
|
|
2087
|
-
const deviceJsonPath =
|
|
2815
|
+
const deviceJsonPath = path7.join(EXE_AI_DIR, "device.json");
|
|
2088
2816
|
try {
|
|
2089
|
-
if (
|
|
2090
|
-
const data = JSON.parse(
|
|
2817
|
+
if (existsSync7(deviceJsonPath)) {
|
|
2818
|
+
const data = JSON.parse(readFileSync5(deviceJsonPath, "utf8"));
|
|
2091
2819
|
if (data.deviceId) return data.deviceId;
|
|
2092
2820
|
}
|
|
2093
2821
|
} catch {
|
|
2094
2822
|
}
|
|
2095
2823
|
try {
|
|
2096
|
-
if (
|
|
2097
|
-
const id2 =
|
|
2824
|
+
if (existsSync7(DEVICE_ID_PATH)) {
|
|
2825
|
+
const id2 = readFileSync5(DEVICE_ID_PATH, "utf8").trim();
|
|
2098
2826
|
if (id2) return id2;
|
|
2099
2827
|
}
|
|
2100
2828
|
} catch {
|
|
2101
2829
|
}
|
|
2102
2830
|
const id = randomUUID2();
|
|
2103
|
-
|
|
2104
|
-
|
|
2831
|
+
mkdirSync2(EXE_AI_DIR, { recursive: true });
|
|
2832
|
+
writeFileSync3(DEVICE_ID_PATH, id, "utf8");
|
|
2105
2833
|
return id;
|
|
2106
2834
|
}
|
|
2107
2835
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH;
|
|
@@ -2109,9 +2837,9 @@ var init_license = __esm({
|
|
|
2109
2837
|
"src/lib/license.ts"() {
|
|
2110
2838
|
"use strict";
|
|
2111
2839
|
init_config();
|
|
2112
|
-
LICENSE_PATH =
|
|
2113
|
-
CACHE_PATH =
|
|
2114
|
-
DEVICE_ID_PATH =
|
|
2840
|
+
LICENSE_PATH = path7.join(EXE_AI_DIR, "license.key");
|
|
2841
|
+
CACHE_PATH = path7.join(EXE_AI_DIR, "license-cache.json");
|
|
2842
|
+
DEVICE_ID_PATH = path7.join(EXE_AI_DIR, "device-id");
|
|
2115
2843
|
}
|
|
2116
2844
|
});
|
|
2117
2845
|
|
|
@@ -2134,8 +2862,8 @@ __export(crdt_sync_exports, {
|
|
|
2134
2862
|
rebuildFromDb: () => rebuildFromDb
|
|
2135
2863
|
});
|
|
2136
2864
|
import * as Y from "yjs";
|
|
2137
|
-
import { readFileSync as
|
|
2138
|
-
import
|
|
2865
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync8, mkdirSync as mkdirSync3, unlinkSync as unlinkSync3 } from "fs";
|
|
2866
|
+
import path8 from "path";
|
|
2139
2867
|
import { homedir } from "os";
|
|
2140
2868
|
function getStatePath() {
|
|
2141
2869
|
return _statePathOverride ?? DEFAULT_STATE_PATH;
|
|
@@ -2147,9 +2875,9 @@ function initCrdtDoc() {
|
|
|
2147
2875
|
if (doc) return doc;
|
|
2148
2876
|
doc = new Y.Doc();
|
|
2149
2877
|
const sp = getStatePath();
|
|
2150
|
-
if (
|
|
2878
|
+
if (existsSync8(sp)) {
|
|
2151
2879
|
try {
|
|
2152
|
-
const state =
|
|
2880
|
+
const state = readFileSync6(sp);
|
|
2153
2881
|
Y.applyUpdate(doc, new Uint8Array(state));
|
|
2154
2882
|
} catch {
|
|
2155
2883
|
console.warn("[crdt-sync] WARN: corrupted state file, rebuilding from DB");
|
|
@@ -2291,10 +3019,10 @@ function persistState() {
|
|
|
2291
3019
|
if (!doc) return;
|
|
2292
3020
|
try {
|
|
2293
3021
|
const sp = getStatePath();
|
|
2294
|
-
const dir =
|
|
2295
|
-
if (!
|
|
3022
|
+
const dir = path8.dirname(sp);
|
|
3023
|
+
if (!existsSync8(dir)) mkdirSync3(dir, { recursive: true });
|
|
2296
3024
|
const state = Y.encodeStateAsUpdate(doc);
|
|
2297
|
-
|
|
3025
|
+
writeFileSync4(sp, Buffer.from(state));
|
|
2298
3026
|
} catch {
|
|
2299
3027
|
}
|
|
2300
3028
|
}
|
|
@@ -2335,7 +3063,7 @@ var DEFAULT_STATE_PATH, _statePathOverride, doc;
|
|
|
2335
3063
|
var init_crdt_sync = __esm({
|
|
2336
3064
|
"src/lib/crdt-sync.ts"() {
|
|
2337
3065
|
"use strict";
|
|
2338
|
-
DEFAULT_STATE_PATH =
|
|
3066
|
+
DEFAULT_STATE_PATH = path8.join(homedir(), ".exe-os", "crdt-state.bin");
|
|
2339
3067
|
_statePathOverride = null;
|
|
2340
3068
|
doc = null;
|
|
2341
3069
|
}
|
|
@@ -2367,39 +3095,107 @@ __export(cloud_sync_exports, {
|
|
|
2367
3095
|
cloudSync: () => cloudSync,
|
|
2368
3096
|
mergeConfig: () => mergeConfig,
|
|
2369
3097
|
mergeRosterFromRemote: () => mergeRosterFromRemote,
|
|
3098
|
+
pushToPostgres: () => pushToPostgres,
|
|
2370
3099
|
recordRosterDeletion: () => recordRosterDeletion
|
|
2371
3100
|
});
|
|
2372
|
-
import { readFileSync as
|
|
2373
|
-
import
|
|
2374
|
-
import
|
|
3101
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync9, readdirSync, mkdirSync as mkdirSync4, appendFileSync, unlinkSync as unlinkSync4, openSync as openSync2, closeSync as closeSync2 } from "fs";
|
|
3102
|
+
import crypto3 from "crypto";
|
|
3103
|
+
import path9 from "path";
|
|
2375
3104
|
import { homedir as homedir2 } from "os";
|
|
2376
3105
|
function sqlSafe(v) {
|
|
2377
3106
|
return v === void 0 ? null : v;
|
|
2378
3107
|
}
|
|
2379
3108
|
function logError(msg) {
|
|
2380
3109
|
try {
|
|
2381
|
-
const logPath =
|
|
3110
|
+
const logPath = path9.join(homedir2(), ".exe-os", "workers.log");
|
|
2382
3111
|
appendFileSync(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
|
|
2383
3112
|
`);
|
|
2384
3113
|
} catch {
|
|
2385
3114
|
}
|
|
2386
3115
|
}
|
|
3116
|
+
function loadPgClient() {
|
|
3117
|
+
if (_pgFailed) return null;
|
|
3118
|
+
const postgresUrl = process.env.DATABASE_URL;
|
|
3119
|
+
const configPath = path9.join(EXE_AI_DIR, "config.json");
|
|
3120
|
+
let cloudPostgresUrl;
|
|
3121
|
+
try {
|
|
3122
|
+
if (existsSync9(configPath)) {
|
|
3123
|
+
const cfg = JSON.parse(readFileSync7(configPath, "utf8"));
|
|
3124
|
+
cloudPostgresUrl = cfg.cloud?.postgresUrl;
|
|
3125
|
+
if (cfg.cloud?.syncToPostgres === false) {
|
|
3126
|
+
_pgFailed = true;
|
|
3127
|
+
return null;
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3130
|
+
} catch {
|
|
3131
|
+
}
|
|
3132
|
+
const url = postgresUrl || cloudPostgresUrl;
|
|
3133
|
+
if (!url) {
|
|
3134
|
+
_pgFailed = true;
|
|
3135
|
+
return null;
|
|
3136
|
+
}
|
|
3137
|
+
if (!_pgPromise) {
|
|
3138
|
+
_pgPromise = (async () => {
|
|
3139
|
+
const { createRequire: createRequire3 } = await import("module");
|
|
3140
|
+
const { pathToFileURL: pathToFileURL3 } = await import("url");
|
|
3141
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path9.join(homedir2(), "exe-db");
|
|
3142
|
+
const req = createRequire3(path9.join(exeDbRoot, "package.json"));
|
|
3143
|
+
const entry = req.resolve("@prisma/client");
|
|
3144
|
+
const mod = await import(pathToFileURL3(entry).href);
|
|
3145
|
+
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
3146
|
+
if (!Ctor) throw new Error("No PrismaClient");
|
|
3147
|
+
return new Ctor();
|
|
3148
|
+
})().catch(() => {
|
|
3149
|
+
_pgFailed = true;
|
|
3150
|
+
_pgPromise = null;
|
|
3151
|
+
throw new Error("pg_unavailable");
|
|
3152
|
+
});
|
|
3153
|
+
}
|
|
3154
|
+
return _pgPromise;
|
|
3155
|
+
}
|
|
3156
|
+
async function pushToPostgres(records) {
|
|
3157
|
+
const loader = loadPgClient();
|
|
3158
|
+
if (!loader) return 0;
|
|
3159
|
+
let prisma;
|
|
3160
|
+
try {
|
|
3161
|
+
prisma = await loader;
|
|
3162
|
+
} catch {
|
|
3163
|
+
return 0;
|
|
3164
|
+
}
|
|
3165
|
+
let inserted = 0;
|
|
3166
|
+
for (const rec of records) {
|
|
3167
|
+
try {
|
|
3168
|
+
await prisma.$executeRawUnsafe(
|
|
3169
|
+
`INSERT INTO raw.raw_events (id, source, source_id, event_type, payload, metadata, timestamp)
|
|
3170
|
+
VALUES (gen_random_uuid(), 'cloud_sync', $1, 'memory', $2::jsonb, $3::jsonb, $4)
|
|
3171
|
+
ON CONFLICT (source, source_id, event_type) DO NOTHING`,
|
|
3172
|
+
String(rec.id ?? ""),
|
|
3173
|
+
JSON.stringify(rec),
|
|
3174
|
+
JSON.stringify({ agent_id: rec.agent_id, project_name: rec.project_name, tool_name: rec.tool_name }),
|
|
3175
|
+
rec.timestamp ? new Date(String(rec.timestamp)) : /* @__PURE__ */ new Date()
|
|
3176
|
+
);
|
|
3177
|
+
inserted++;
|
|
3178
|
+
} catch {
|
|
3179
|
+
}
|
|
3180
|
+
}
|
|
3181
|
+
return inserted;
|
|
3182
|
+
}
|
|
2387
3183
|
async function withRosterLock(fn) {
|
|
2388
3184
|
try {
|
|
2389
3185
|
const fd = openSync2(ROSTER_LOCK_PATH, "wx");
|
|
2390
3186
|
closeSync2(fd);
|
|
2391
|
-
|
|
3187
|
+
writeFileSync5(ROSTER_LOCK_PATH, String(Date.now()));
|
|
2392
3188
|
} catch (err) {
|
|
2393
3189
|
if (err.code === "EEXIST") {
|
|
2394
3190
|
try {
|
|
2395
|
-
const ts = parseInt(
|
|
3191
|
+
const ts = parseInt(readFileSync7(ROSTER_LOCK_PATH, "utf-8"), 10);
|
|
2396
3192
|
if (Date.now() - ts < LOCK_STALE_MS) {
|
|
2397
3193
|
throw new Error("Roster merge already in progress \u2014 another sync is running");
|
|
2398
3194
|
}
|
|
2399
3195
|
unlinkSync4(ROSTER_LOCK_PATH);
|
|
2400
3196
|
const fd = openSync2(ROSTER_LOCK_PATH, "wx");
|
|
2401
3197
|
closeSync2(fd);
|
|
2402
|
-
|
|
3198
|
+
writeFileSync5(ROSTER_LOCK_PATH, String(Date.now()));
|
|
2403
3199
|
} catch (retryErr) {
|
|
2404
3200
|
if (retryErr instanceof Error && retryErr.message.includes("already in progress")) throw retryErr;
|
|
2405
3201
|
throw new Error("Roster merge already in progress \u2014 another sync is running");
|
|
@@ -2669,6 +3465,10 @@ async function cloudSync(config) {
|
|
|
2669
3465
|
const maxVersion = Number(records[records.length - 1].version);
|
|
2670
3466
|
const pushOk = await cloudPush(records, maxVersion, config);
|
|
2671
3467
|
if (!pushOk) break;
|
|
3468
|
+
try {
|
|
3469
|
+
await pushToPostgres(records);
|
|
3470
|
+
} catch {
|
|
3471
|
+
}
|
|
2672
3472
|
await client.execute({
|
|
2673
3473
|
sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_cloud_push_version', ?)",
|
|
2674
3474
|
args: [String(maxVersion)]
|
|
@@ -2773,8 +3573,8 @@ async function cloudSync(config) {
|
|
|
2773
3573
|
try {
|
|
2774
3574
|
const employees = await loadEmployees();
|
|
2775
3575
|
rosterResult.employees = employees.length;
|
|
2776
|
-
const idDir =
|
|
2777
|
-
if (
|
|
3576
|
+
const idDir = path9.join(EXE_AI_DIR, "identity");
|
|
3577
|
+
if (existsSync9(idDir)) {
|
|
2778
3578
|
rosterResult.identities = readdirSync(idDir).filter((f) => f.endsWith(".md")).length;
|
|
2779
3579
|
}
|
|
2780
3580
|
} catch {
|
|
@@ -2795,62 +3595,62 @@ async function cloudSync(config) {
|
|
|
2795
3595
|
function recordRosterDeletion(name) {
|
|
2796
3596
|
let deletions = [];
|
|
2797
3597
|
try {
|
|
2798
|
-
if (
|
|
2799
|
-
deletions = JSON.parse(
|
|
3598
|
+
if (existsSync9(ROSTER_DELETIONS_PATH)) {
|
|
3599
|
+
deletions = JSON.parse(readFileSync7(ROSTER_DELETIONS_PATH, "utf-8"));
|
|
2800
3600
|
}
|
|
2801
3601
|
} catch {
|
|
2802
3602
|
}
|
|
2803
3603
|
if (!deletions.includes(name)) deletions.push(name);
|
|
2804
|
-
|
|
3604
|
+
writeFileSync5(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
|
|
2805
3605
|
}
|
|
2806
3606
|
function consumeRosterDeletions() {
|
|
2807
3607
|
try {
|
|
2808
|
-
if (!
|
|
2809
|
-
const deletions = JSON.parse(
|
|
2810
|
-
|
|
3608
|
+
if (!existsSync9(ROSTER_DELETIONS_PATH)) return [];
|
|
3609
|
+
const deletions = JSON.parse(readFileSync7(ROSTER_DELETIONS_PATH, "utf-8"));
|
|
3610
|
+
writeFileSync5(ROSTER_DELETIONS_PATH, "[]");
|
|
2811
3611
|
return deletions;
|
|
2812
3612
|
} catch {
|
|
2813
3613
|
return [];
|
|
2814
3614
|
}
|
|
2815
3615
|
}
|
|
2816
3616
|
function buildRosterBlob(paths) {
|
|
2817
|
-
const rosterPath = paths?.rosterPath ??
|
|
2818
|
-
const identityDir = paths?.identityDir ??
|
|
2819
|
-
const configPath = paths?.configPath ??
|
|
3617
|
+
const rosterPath = paths?.rosterPath ?? path9.join(EXE_AI_DIR, "exe-employees.json");
|
|
3618
|
+
const identityDir = paths?.identityDir ?? path9.join(EXE_AI_DIR, "identity");
|
|
3619
|
+
const configPath = paths?.configPath ?? path9.join(EXE_AI_DIR, "config.json");
|
|
2820
3620
|
let roster = [];
|
|
2821
|
-
if (
|
|
3621
|
+
if (existsSync9(rosterPath)) {
|
|
2822
3622
|
try {
|
|
2823
|
-
roster = JSON.parse(
|
|
3623
|
+
roster = JSON.parse(readFileSync7(rosterPath, "utf-8"));
|
|
2824
3624
|
} catch {
|
|
2825
3625
|
}
|
|
2826
3626
|
}
|
|
2827
3627
|
const identities = {};
|
|
2828
|
-
if (
|
|
3628
|
+
if (existsSync9(identityDir)) {
|
|
2829
3629
|
for (const file of readdirSync(identityDir).filter((f) => f.endsWith(".md"))) {
|
|
2830
3630
|
try {
|
|
2831
|
-
identities[file] =
|
|
3631
|
+
identities[file] = readFileSync7(path9.join(identityDir, file), "utf-8");
|
|
2832
3632
|
} catch {
|
|
2833
3633
|
}
|
|
2834
3634
|
}
|
|
2835
3635
|
}
|
|
2836
3636
|
let config;
|
|
2837
|
-
if (
|
|
3637
|
+
if (existsSync9(configPath)) {
|
|
2838
3638
|
try {
|
|
2839
|
-
config = JSON.parse(
|
|
3639
|
+
config = JSON.parse(readFileSync7(configPath, "utf-8"));
|
|
2840
3640
|
} catch {
|
|
2841
3641
|
}
|
|
2842
3642
|
}
|
|
2843
3643
|
let agentConfig;
|
|
2844
|
-
const agentConfigPath =
|
|
2845
|
-
if (
|
|
3644
|
+
const agentConfigPath = path9.join(EXE_AI_DIR, "agent-config.json");
|
|
3645
|
+
if (existsSync9(agentConfigPath)) {
|
|
2846
3646
|
try {
|
|
2847
|
-
agentConfig = JSON.parse(
|
|
3647
|
+
agentConfig = JSON.parse(readFileSync7(agentConfigPath, "utf-8"));
|
|
2848
3648
|
} catch {
|
|
2849
3649
|
}
|
|
2850
3650
|
}
|
|
2851
3651
|
const deletedNames = consumeRosterDeletions();
|
|
2852
3652
|
const content = JSON.stringify({ roster, identities, config, agentConfig, deletedNames });
|
|
2853
|
-
const hash =
|
|
3653
|
+
const hash = crypto3.createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
2854
3654
|
return { roster, identities, config, agentConfig, deletedNames, version: hash };
|
|
2855
3655
|
}
|
|
2856
3656
|
async function cloudPushRoster(config) {
|
|
@@ -2920,23 +3720,24 @@ async function cloudPullRoster(config) {
|
|
|
2920
3720
|
}
|
|
2921
3721
|
}
|
|
2922
3722
|
function mergeConfig(remoteConfig, configPath) {
|
|
2923
|
-
const cfgPath = configPath ??
|
|
3723
|
+
const cfgPath = configPath ?? path9.join(EXE_AI_DIR, "config.json");
|
|
2924
3724
|
let local = {};
|
|
2925
|
-
if (
|
|
3725
|
+
if (existsSync9(cfgPath)) {
|
|
2926
3726
|
try {
|
|
2927
|
-
local = JSON.parse(
|
|
3727
|
+
local = JSON.parse(readFileSync7(cfgPath, "utf-8"));
|
|
2928
3728
|
} catch {
|
|
2929
3729
|
}
|
|
2930
3730
|
}
|
|
2931
3731
|
const merged = { ...remoteConfig, ...local };
|
|
2932
|
-
const dir =
|
|
2933
|
-
|
|
2934
|
-
|
|
3732
|
+
const dir = path9.dirname(cfgPath);
|
|
3733
|
+
ensurePrivateDirSync(dir);
|
|
3734
|
+
writeFileSync5(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
|
|
3735
|
+
enforcePrivateFileSync(cfgPath);
|
|
2935
3736
|
}
|
|
2936
3737
|
async function mergeRosterFromRemote(remote, paths) {
|
|
2937
3738
|
return withRosterLock(async () => {
|
|
2938
3739
|
const rosterPath = paths?.rosterPath ?? void 0;
|
|
2939
|
-
const identityDir = paths?.identityDir ??
|
|
3740
|
+
const identityDir = paths?.identityDir ?? path9.join(EXE_AI_DIR, "identity");
|
|
2940
3741
|
const localEmployees = await loadEmployees(rosterPath);
|
|
2941
3742
|
const localNames = new Set(localEmployees.map((e) => e.name));
|
|
2942
3743
|
let added = 0;
|
|
@@ -2957,15 +3758,15 @@ async function mergeRosterFromRemote(remote, paths) {
|
|
|
2957
3758
|
) ?? lookupKey;
|
|
2958
3759
|
const remoteIdentity = remote.identities[matchedKey];
|
|
2959
3760
|
if (remoteIdentity) {
|
|
2960
|
-
if (!
|
|
2961
|
-
const idPath =
|
|
3761
|
+
if (!existsSync9(identityDir)) mkdirSync4(identityDir, { recursive: true });
|
|
3762
|
+
const idPath = path9.join(identityDir, `${remoteEmp.name}.md`);
|
|
2962
3763
|
let localIdentity = null;
|
|
2963
3764
|
try {
|
|
2964
|
-
localIdentity =
|
|
3765
|
+
localIdentity = existsSync9(idPath) ? readFileSync7(idPath, "utf-8") : null;
|
|
2965
3766
|
} catch {
|
|
2966
3767
|
}
|
|
2967
3768
|
if (localIdentity !== remoteIdentity) {
|
|
2968
|
-
|
|
3769
|
+
writeFileSync5(idPath, remoteIdentity, "utf-8");
|
|
2969
3770
|
identitiesUpdated++;
|
|
2970
3771
|
}
|
|
2971
3772
|
}
|
|
@@ -2991,16 +3792,18 @@ async function mergeRosterFromRemote(remote, paths) {
|
|
|
2991
3792
|
}
|
|
2992
3793
|
if (remote.agentConfig && Object.keys(remote.agentConfig).length > 0) {
|
|
2993
3794
|
try {
|
|
2994
|
-
const agentConfigPath =
|
|
3795
|
+
const agentConfigPath = path9.join(EXE_AI_DIR, "agent-config.json");
|
|
2995
3796
|
let local = {};
|
|
2996
|
-
if (
|
|
3797
|
+
if (existsSync9(agentConfigPath)) {
|
|
2997
3798
|
try {
|
|
2998
|
-
local = JSON.parse(
|
|
3799
|
+
local = JSON.parse(readFileSync7(agentConfigPath, "utf-8"));
|
|
2999
3800
|
} catch {
|
|
3000
3801
|
}
|
|
3001
3802
|
}
|
|
3002
3803
|
const merged = { ...remote.agentConfig, ...local };
|
|
3003
|
-
|
|
3804
|
+
ensurePrivateDirSync(path9.dirname(agentConfigPath));
|
|
3805
|
+
writeFileSync5(agentConfigPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
3806
|
+
enforcePrivateFileSync(agentConfigPath);
|
|
3004
3807
|
} catch {
|
|
3005
3808
|
}
|
|
3006
3809
|
}
|
|
@@ -3424,7 +4227,7 @@ async function cloudPullDocuments(config) {
|
|
|
3424
4227
|
}
|
|
3425
4228
|
return { pulled };
|
|
3426
4229
|
}
|
|
3427
|
-
var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, ROSTER_DELETIONS_PATH;
|
|
4230
|
+
var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, _pgPromise, _pgFailed, ROSTER_DELETIONS_PATH;
|
|
3428
4231
|
var init_cloud_sync = __esm({
|
|
3429
4232
|
"src/lib/cloud-sync.ts"() {
|
|
3430
4233
|
"use strict";
|
|
@@ -3435,12 +4238,15 @@ var init_cloud_sync = __esm({
|
|
|
3435
4238
|
init_config();
|
|
3436
4239
|
init_crdt_sync();
|
|
3437
4240
|
init_employees();
|
|
4241
|
+
init_secure_files();
|
|
3438
4242
|
LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
|
|
3439
4243
|
FETCH_TIMEOUT_MS = 3e4;
|
|
3440
4244
|
PUSH_BATCH_SIZE = 5e3;
|
|
3441
|
-
ROSTER_LOCK_PATH =
|
|
4245
|
+
ROSTER_LOCK_PATH = path9.join(EXE_AI_DIR, "roster-merge.lock");
|
|
3442
4246
|
LOCK_STALE_MS = 3e4;
|
|
3443
|
-
|
|
4247
|
+
_pgPromise = null;
|
|
4248
|
+
_pgFailed = false;
|
|
4249
|
+
ROSTER_DELETIONS_PATH = path9.join(EXE_AI_DIR, "roster-deletions.json");
|
|
3444
4250
|
}
|
|
3445
4251
|
});
|
|
3446
4252
|
|