@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/lib/database.js
CHANGED
|
@@ -8,9 +8,34 @@ var __export = (target, all) => {
|
|
|
8
8
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
+
// src/lib/secure-files.ts
|
|
12
|
+
import { chmodSync, existsSync, mkdirSync } from "fs";
|
|
13
|
+
import { chmod, mkdir } from "fs/promises";
|
|
14
|
+
function ensurePrivateDirSync(dirPath) {
|
|
15
|
+
mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
16
|
+
try {
|
|
17
|
+
chmodSync(dirPath, PRIVATE_DIR_MODE);
|
|
18
|
+
} catch {
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function enforcePrivateFileSync(filePath) {
|
|
22
|
+
try {
|
|
23
|
+
if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
|
|
24
|
+
} catch {
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
|
|
28
|
+
var init_secure_files = __esm({
|
|
29
|
+
"src/lib/secure-files.ts"() {
|
|
30
|
+
"use strict";
|
|
31
|
+
PRIVATE_DIR_MODE = 448;
|
|
32
|
+
PRIVATE_FILE_MODE = 384;
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
11
36
|
// src/lib/config.ts
|
|
12
|
-
import { readFile, writeFile
|
|
13
|
-
import { readFileSync, existsSync, renameSync } from "fs";
|
|
37
|
+
import { readFile, writeFile } from "fs/promises";
|
|
38
|
+
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
14
39
|
import path from "path";
|
|
15
40
|
import os from "os";
|
|
16
41
|
function resolveDataDir() {
|
|
@@ -18,7 +43,7 @@ function resolveDataDir() {
|
|
|
18
43
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
19
44
|
const newDir = path.join(os.homedir(), ".exe-os");
|
|
20
45
|
const legacyDir = path.join(os.homedir(), ".exe-mem");
|
|
21
|
-
if (!
|
|
46
|
+
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
22
47
|
try {
|
|
23
48
|
renameSync(legacyDir, newDir);
|
|
24
49
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -33,6 +58,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
|
|
|
33
58
|
var init_config = __esm({
|
|
34
59
|
"src/lib/config.ts"() {
|
|
35
60
|
"use strict";
|
|
61
|
+
init_secure_files();
|
|
36
62
|
EXE_AI_DIR = resolveDataDir();
|
|
37
63
|
DB_PATH = path.join(EXE_AI_DIR, "memories.db");
|
|
38
64
|
MODELS_DIR = path.join(EXE_AI_DIR, "models");
|
|
@@ -99,13 +125,50 @@ var init_config = __esm({
|
|
|
99
125
|
}
|
|
100
126
|
});
|
|
101
127
|
|
|
128
|
+
// src/lib/daemon-auth.ts
|
|
129
|
+
import crypto from "crypto";
|
|
130
|
+
import path4 from "path";
|
|
131
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
132
|
+
function normalizeToken(token) {
|
|
133
|
+
if (!token) return null;
|
|
134
|
+
const trimmed = token.trim();
|
|
135
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
136
|
+
}
|
|
137
|
+
function readDaemonToken() {
|
|
138
|
+
try {
|
|
139
|
+
if (!existsSync4(DAEMON_TOKEN_PATH)) return null;
|
|
140
|
+
return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
|
|
141
|
+
} catch {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function ensureDaemonToken(seed) {
|
|
146
|
+
const existing = readDaemonToken();
|
|
147
|
+
if (existing) return existing;
|
|
148
|
+
const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
|
|
149
|
+
ensurePrivateDirSync(EXE_AI_DIR);
|
|
150
|
+
writeFileSync2(DAEMON_TOKEN_PATH, `${token}
|
|
151
|
+
`, "utf8");
|
|
152
|
+
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
153
|
+
return token;
|
|
154
|
+
}
|
|
155
|
+
var DAEMON_TOKEN_PATH;
|
|
156
|
+
var init_daemon_auth = __esm({
|
|
157
|
+
"src/lib/daemon-auth.ts"() {
|
|
158
|
+
"use strict";
|
|
159
|
+
init_config();
|
|
160
|
+
init_secure_files();
|
|
161
|
+
DAEMON_TOKEN_PATH = path4.join(EXE_AI_DIR, "exed.token");
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
102
165
|
// src/lib/exe-daemon-client.ts
|
|
103
166
|
import net from "net";
|
|
104
|
-
import
|
|
167
|
+
import os4 from "os";
|
|
105
168
|
import { spawn } from "child_process";
|
|
106
169
|
import { randomUUID } from "crypto";
|
|
107
|
-
import { existsSync as
|
|
108
|
-
import
|
|
170
|
+
import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
|
|
171
|
+
import path5 from "path";
|
|
109
172
|
import { fileURLToPath } from "url";
|
|
110
173
|
function handleData(chunk) {
|
|
111
174
|
_buffer += chunk.toString();
|
|
@@ -133,9 +196,9 @@ function handleData(chunk) {
|
|
|
133
196
|
}
|
|
134
197
|
}
|
|
135
198
|
function cleanupStaleFiles() {
|
|
136
|
-
if (
|
|
199
|
+
if (existsSync5(PID_PATH)) {
|
|
137
200
|
try {
|
|
138
|
-
const pid = parseInt(
|
|
201
|
+
const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
|
|
139
202
|
if (pid > 0) {
|
|
140
203
|
try {
|
|
141
204
|
process.kill(pid, 0);
|
|
@@ -156,17 +219,17 @@ function cleanupStaleFiles() {
|
|
|
156
219
|
}
|
|
157
220
|
}
|
|
158
221
|
function findPackageRoot() {
|
|
159
|
-
let dir =
|
|
160
|
-
const { root } =
|
|
222
|
+
let dir = path5.dirname(fileURLToPath(import.meta.url));
|
|
223
|
+
const { root } = path5.parse(dir);
|
|
161
224
|
while (dir !== root) {
|
|
162
|
-
if (
|
|
163
|
-
dir =
|
|
225
|
+
if (existsSync5(path5.join(dir, "package.json"))) return dir;
|
|
226
|
+
dir = path5.dirname(dir);
|
|
164
227
|
}
|
|
165
228
|
return null;
|
|
166
229
|
}
|
|
167
230
|
function spawnDaemon() {
|
|
168
|
-
const freeGB =
|
|
169
|
-
const totalGB =
|
|
231
|
+
const freeGB = os4.freemem() / (1024 * 1024 * 1024);
|
|
232
|
+
const totalGB = os4.totalmem() / (1024 * 1024 * 1024);
|
|
170
233
|
if (totalGB <= 8) {
|
|
171
234
|
process.stderr.write(
|
|
172
235
|
`[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
|
|
@@ -186,16 +249,17 @@ function spawnDaemon() {
|
|
|
186
249
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
187
250
|
return;
|
|
188
251
|
}
|
|
189
|
-
const daemonPath =
|
|
190
|
-
if (!
|
|
252
|
+
const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
253
|
+
if (!existsSync5(daemonPath)) {
|
|
191
254
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
192
255
|
`);
|
|
193
256
|
return;
|
|
194
257
|
}
|
|
195
258
|
const resolvedPath = daemonPath;
|
|
259
|
+
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
196
260
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
197
261
|
`);
|
|
198
|
-
const logPath =
|
|
262
|
+
const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
|
|
199
263
|
let stderrFd = "ignore";
|
|
200
264
|
try {
|
|
201
265
|
stderrFd = openSync(logPath, "a");
|
|
@@ -213,7 +277,8 @@ function spawnDaemon() {
|
|
|
213
277
|
TMUX_PANE: void 0,
|
|
214
278
|
// Prevents resolveExeSession() from scoping to one session
|
|
215
279
|
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
216
|
-
EXE_DAEMON_PID: PID_PATH
|
|
280
|
+
EXE_DAEMON_PID: PID_PATH,
|
|
281
|
+
[DAEMON_TOKEN_ENV]: daemonToken
|
|
217
282
|
}
|
|
218
283
|
});
|
|
219
284
|
child.unref();
|
|
@@ -320,13 +385,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
320
385
|
return;
|
|
321
386
|
}
|
|
322
387
|
const id = randomUUID();
|
|
388
|
+
const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
|
|
323
389
|
const timer = setTimeout(() => {
|
|
324
390
|
_pending.delete(id);
|
|
325
391
|
resolve({ error: "Request timeout" });
|
|
326
392
|
}, timeoutMs);
|
|
327
393
|
_pending.set(id, { resolve, timer });
|
|
328
394
|
try {
|
|
329
|
-
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
395
|
+
_socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
|
|
330
396
|
} catch {
|
|
331
397
|
clearTimeout(timer);
|
|
332
398
|
_pending.delete(id);
|
|
@@ -337,17 +403,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
337
403
|
function isClientConnected() {
|
|
338
404
|
return _connected;
|
|
339
405
|
}
|
|
340
|
-
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
|
|
406
|
+
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;
|
|
341
407
|
var init_exe_daemon_client = __esm({
|
|
342
408
|
"src/lib/exe-daemon-client.ts"() {
|
|
343
409
|
"use strict";
|
|
344
410
|
init_config();
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
411
|
+
init_daemon_auth();
|
|
412
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
|
|
413
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
|
|
414
|
+
SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
348
415
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
349
416
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
350
417
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
418
|
+
DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
|
|
351
419
|
_socket = null;
|
|
352
420
|
_connected = false;
|
|
353
421
|
_buffer = "";
|
|
@@ -426,7 +494,7 @@ __export(db_daemon_client_exports, {
|
|
|
426
494
|
createDaemonDbClient: () => createDaemonDbClient,
|
|
427
495
|
initDaemonDbClient: () => initDaemonDbClient
|
|
428
496
|
});
|
|
429
|
-
function
|
|
497
|
+
function normalizeStatement2(stmt) {
|
|
430
498
|
if (typeof stmt === "string") {
|
|
431
499
|
return { sql: stmt, args: [] };
|
|
432
500
|
}
|
|
@@ -450,7 +518,7 @@ function createDaemonDbClient(fallbackClient) {
|
|
|
450
518
|
if (!_useDaemon || !isClientConnected()) {
|
|
451
519
|
return fallbackClient.execute(stmt);
|
|
452
520
|
}
|
|
453
|
-
const { sql, args } =
|
|
521
|
+
const { sql, args } = normalizeStatement2(stmt);
|
|
454
522
|
const response = await sendDaemonRequest({
|
|
455
523
|
type: "db-execute",
|
|
456
524
|
sql,
|
|
@@ -475,7 +543,7 @@ function createDaemonDbClient(fallbackClient) {
|
|
|
475
543
|
if (!_useDaemon || !isClientConnected()) {
|
|
476
544
|
return fallbackClient.batch(stmts, mode);
|
|
477
545
|
}
|
|
478
|
-
const statements = stmts.map(
|
|
546
|
+
const statements = stmts.map(normalizeStatement2);
|
|
479
547
|
const response = await sendDaemonRequest({
|
|
480
548
|
type: "db-batch",
|
|
481
549
|
statements,
|
|
@@ -610,7 +678,7 @@ function wrapWithRetry(client) {
|
|
|
610
678
|
// src/lib/employees.ts
|
|
611
679
|
init_config();
|
|
612
680
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
613
|
-
import { existsSync as
|
|
681
|
+
import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
|
|
614
682
|
import { execSync } from "child_process";
|
|
615
683
|
import path2 from "path";
|
|
616
684
|
import os2 from "os";
|
|
@@ -630,21 +698,613 @@ function getCoordinatorName(employees = loadEmployeesSync()) {
|
|
|
630
698
|
return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
|
|
631
699
|
}
|
|
632
700
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
633
|
-
if (!
|
|
701
|
+
if (!existsSync3(employeesPath)) return [];
|
|
634
702
|
try {
|
|
635
703
|
return JSON.parse(readFileSync2(employeesPath, "utf-8"));
|
|
636
704
|
} catch {
|
|
637
705
|
return [];
|
|
638
706
|
}
|
|
639
707
|
}
|
|
708
|
+
var IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
|
|
709
|
+
|
|
710
|
+
// src/lib/database-adapter.ts
|
|
711
|
+
import os3 from "os";
|
|
712
|
+
import path3 from "path";
|
|
713
|
+
import { createRequire } from "module";
|
|
714
|
+
import { pathToFileURL } from "url";
|
|
715
|
+
var VIEW_MAPPINGS = [
|
|
716
|
+
{ view: "memories", source: "memory.memory_records" },
|
|
717
|
+
{ view: "tasks", source: "memory.tasks" },
|
|
718
|
+
{ view: "behaviors", source: "memory.behaviors" },
|
|
719
|
+
{ view: "entities", source: "memory.entities" },
|
|
720
|
+
{ view: "relationships", source: "memory.relationships" },
|
|
721
|
+
{ view: "entity_memories", source: "memory.entity_memories" },
|
|
722
|
+
{ view: "entity_aliases", source: "memory.entity_aliases" },
|
|
723
|
+
{ view: "notifications", source: "memory.notifications" },
|
|
724
|
+
{ view: "messages", source: "memory.messages" },
|
|
725
|
+
{ view: "users", source: "wiki.users" },
|
|
726
|
+
{ view: "workspaces", source: "wiki.workspaces" },
|
|
727
|
+
{ view: "workspace_users", source: "wiki.workspace_users" },
|
|
728
|
+
{ view: "documents", source: "wiki.workspace_documents" },
|
|
729
|
+
{ view: "chats", source: "wiki.workspace_chats" }
|
|
730
|
+
];
|
|
731
|
+
var UPSERT_KEYS = {
|
|
732
|
+
memories: ["id"],
|
|
733
|
+
tasks: ["id"],
|
|
734
|
+
behaviors: ["id"],
|
|
735
|
+
entities: ["id"],
|
|
736
|
+
relationships: ["id"],
|
|
737
|
+
entity_aliases: ["alias"],
|
|
738
|
+
notifications: ["id"],
|
|
739
|
+
messages: ["id"],
|
|
740
|
+
users: ["id"],
|
|
741
|
+
workspaces: ["id"],
|
|
742
|
+
workspace_users: ["id"],
|
|
743
|
+
documents: ["id"],
|
|
744
|
+
chats: ["id"]
|
|
745
|
+
};
|
|
746
|
+
var BOOLEAN_COLUMNS_BY_TABLE = {
|
|
747
|
+
memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
|
|
748
|
+
behaviors: /* @__PURE__ */ new Set(["active"]),
|
|
749
|
+
notifications: /* @__PURE__ */ new Set(["read"]),
|
|
750
|
+
users: /* @__PURE__ */ new Set(["has_personal_memory"])
|
|
751
|
+
};
|
|
752
|
+
var BOOLEAN_COLUMN_NAMES = new Set(
|
|
753
|
+
Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
|
|
754
|
+
);
|
|
755
|
+
var IMMEDIATE_FALLBACK_PATTERNS = [
|
|
756
|
+
/\bPRAGMA\b/i,
|
|
757
|
+
/\bsqlite_master\b/i,
|
|
758
|
+
/(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
|
|
759
|
+
/\bMATCH\b/i,
|
|
760
|
+
/\bvector_distance_cos\s*\(/i,
|
|
761
|
+
/\bjson_extract\s*\(/i,
|
|
762
|
+
/\bjulianday\s*\(/i,
|
|
763
|
+
/\bstrftime\s*\(/i,
|
|
764
|
+
/\blast_insert_rowid\s*\(/i
|
|
765
|
+
];
|
|
766
|
+
var prismaClientPromise = null;
|
|
767
|
+
var compatibilityBootstrapPromise = null;
|
|
768
|
+
function quotedIdentifier(identifier) {
|
|
769
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
770
|
+
}
|
|
771
|
+
function unqualifiedTableName(name) {
|
|
772
|
+
const raw = name.trim().replace(/^"|"$/g, "");
|
|
773
|
+
const parts = raw.split(".");
|
|
774
|
+
return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
|
|
775
|
+
}
|
|
776
|
+
function stripTrailingSemicolon(sql) {
|
|
777
|
+
return sql.trim().replace(/;+\s*$/u, "");
|
|
778
|
+
}
|
|
779
|
+
function appendClause(sql, clause) {
|
|
780
|
+
const trimmed = stripTrailingSemicolon(sql);
|
|
781
|
+
const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
|
|
782
|
+
if (!returningMatch) {
|
|
783
|
+
return `${trimmed}${clause}`;
|
|
784
|
+
}
|
|
785
|
+
const idx = returningMatch.index;
|
|
786
|
+
return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
|
|
787
|
+
}
|
|
788
|
+
function normalizeStatement(stmt) {
|
|
789
|
+
if (typeof stmt === "string") {
|
|
790
|
+
return { kind: "positional", sql: stmt, args: [] };
|
|
791
|
+
}
|
|
792
|
+
const sql = stmt.sql;
|
|
793
|
+
if (Array.isArray(stmt.args) || stmt.args === void 0) {
|
|
794
|
+
return { kind: "positional", sql, args: stmt.args ?? [] };
|
|
795
|
+
}
|
|
796
|
+
return { kind: "named", sql, args: stmt.args };
|
|
797
|
+
}
|
|
798
|
+
function rewriteBooleanLiterals(sql) {
|
|
799
|
+
let out = sql;
|
|
800
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
801
|
+
const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
|
|
802
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
|
|
803
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
|
|
804
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
|
|
805
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
|
|
806
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
|
|
807
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
|
|
808
|
+
}
|
|
809
|
+
return out;
|
|
810
|
+
}
|
|
811
|
+
function rewriteInsertOrIgnore(sql) {
|
|
812
|
+
if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
|
|
813
|
+
return sql;
|
|
814
|
+
}
|
|
815
|
+
const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
|
|
816
|
+
return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
|
|
817
|
+
}
|
|
818
|
+
function rewriteInsertOrReplace(sql) {
|
|
819
|
+
const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
|
|
820
|
+
if (!match) {
|
|
821
|
+
return sql;
|
|
822
|
+
}
|
|
823
|
+
const rawTable = match[1];
|
|
824
|
+
const rawColumns = match[2];
|
|
825
|
+
const remainder = match[3];
|
|
826
|
+
const tableName = unqualifiedTableName(rawTable);
|
|
827
|
+
const conflictKeys = UPSERT_KEYS[tableName];
|
|
828
|
+
if (!conflictKeys?.length) {
|
|
829
|
+
return sql;
|
|
830
|
+
}
|
|
831
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
832
|
+
const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
|
|
833
|
+
const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
|
|
834
|
+
const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
|
|
835
|
+
return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
|
|
836
|
+
}
|
|
837
|
+
function rewriteSql(sql) {
|
|
838
|
+
let out = sql;
|
|
839
|
+
out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
|
|
840
|
+
out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
|
|
841
|
+
out = rewriteBooleanLiterals(out);
|
|
842
|
+
out = rewriteInsertOrReplace(out);
|
|
843
|
+
out = rewriteInsertOrIgnore(out);
|
|
844
|
+
return stripTrailingSemicolon(out);
|
|
845
|
+
}
|
|
846
|
+
function toBoolean(value) {
|
|
847
|
+
if (value === null || value === void 0) return value;
|
|
848
|
+
if (typeof value === "boolean") return value;
|
|
849
|
+
if (typeof value === "number") return value !== 0;
|
|
850
|
+
if (typeof value === "bigint") return value !== 0n;
|
|
851
|
+
if (typeof value === "string") {
|
|
852
|
+
const normalized = value.trim().toLowerCase();
|
|
853
|
+
if (normalized === "0" || normalized === "false") return false;
|
|
854
|
+
if (normalized === "1" || normalized === "true") return true;
|
|
855
|
+
}
|
|
856
|
+
return Boolean(value);
|
|
857
|
+
}
|
|
858
|
+
function countQuestionMarks(sql, end) {
|
|
859
|
+
let count = 0;
|
|
860
|
+
let inSingle = false;
|
|
861
|
+
let inDouble = false;
|
|
862
|
+
let inLineComment = false;
|
|
863
|
+
let inBlockComment = false;
|
|
864
|
+
for (let i = 0; i < end; i++) {
|
|
865
|
+
const ch = sql[i];
|
|
866
|
+
const next = sql[i + 1];
|
|
867
|
+
if (inLineComment) {
|
|
868
|
+
if (ch === "\n") inLineComment = false;
|
|
869
|
+
continue;
|
|
870
|
+
}
|
|
871
|
+
if (inBlockComment) {
|
|
872
|
+
if (ch === "*" && next === "/") {
|
|
873
|
+
inBlockComment = false;
|
|
874
|
+
i += 1;
|
|
875
|
+
}
|
|
876
|
+
continue;
|
|
877
|
+
}
|
|
878
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
879
|
+
inLineComment = true;
|
|
880
|
+
i += 1;
|
|
881
|
+
continue;
|
|
882
|
+
}
|
|
883
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
884
|
+
inBlockComment = true;
|
|
885
|
+
i += 1;
|
|
886
|
+
continue;
|
|
887
|
+
}
|
|
888
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
889
|
+
inSingle = !inSingle;
|
|
890
|
+
continue;
|
|
891
|
+
}
|
|
892
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
893
|
+
inDouble = !inDouble;
|
|
894
|
+
continue;
|
|
895
|
+
}
|
|
896
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
897
|
+
count += 1;
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
return count;
|
|
901
|
+
}
|
|
902
|
+
function findBooleanPlaceholderIndexes(sql) {
|
|
903
|
+
const indexes = /* @__PURE__ */ new Set();
|
|
904
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
905
|
+
const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
|
|
906
|
+
for (const match of sql.matchAll(pattern)) {
|
|
907
|
+
const matchText = match[0];
|
|
908
|
+
const qIndex = match.index + matchText.lastIndexOf("?");
|
|
909
|
+
indexes.add(countQuestionMarks(sql, qIndex + 1));
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
return indexes;
|
|
913
|
+
}
|
|
914
|
+
function coerceInsertBooleanArgs(sql, args) {
|
|
915
|
+
const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
|
|
916
|
+
if (!match) return;
|
|
917
|
+
const rawTable = match[1];
|
|
918
|
+
const rawColumns = match[2];
|
|
919
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
920
|
+
if (!boolColumns?.size) return;
|
|
921
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
922
|
+
for (const [index, column] of columns.entries()) {
|
|
923
|
+
if (boolColumns.has(column) && index < args.length) {
|
|
924
|
+
args[index] = toBoolean(args[index]);
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
function coerceUpdateBooleanArgs(sql, args) {
|
|
929
|
+
const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
|
|
930
|
+
if (!match) return;
|
|
931
|
+
const rawTable = match[1];
|
|
932
|
+
const setClause = match[2];
|
|
933
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
934
|
+
if (!boolColumns?.size) return;
|
|
935
|
+
const assignments = setClause.split(",");
|
|
936
|
+
let placeholderIndex = 0;
|
|
937
|
+
for (const assignment of assignments) {
|
|
938
|
+
if (!assignment.includes("?")) continue;
|
|
939
|
+
placeholderIndex += 1;
|
|
940
|
+
const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
|
|
941
|
+
if (colMatch && boolColumns.has(colMatch[1])) {
|
|
942
|
+
args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
function coerceBooleanArgs(sql, args) {
|
|
947
|
+
const nextArgs = [...args];
|
|
948
|
+
coerceInsertBooleanArgs(sql, nextArgs);
|
|
949
|
+
coerceUpdateBooleanArgs(sql, nextArgs);
|
|
950
|
+
const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
|
|
951
|
+
for (const index of placeholderIndexes) {
|
|
952
|
+
if (index > 0 && index <= nextArgs.length) {
|
|
953
|
+
nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
return nextArgs;
|
|
957
|
+
}
|
|
958
|
+
function convertQuestionMarksToDollarParams(sql) {
|
|
959
|
+
let out = "";
|
|
960
|
+
let placeholder = 0;
|
|
961
|
+
let inSingle = false;
|
|
962
|
+
let inDouble = false;
|
|
963
|
+
let inLineComment = false;
|
|
964
|
+
let inBlockComment = false;
|
|
965
|
+
for (let i = 0; i < sql.length; i++) {
|
|
966
|
+
const ch = sql[i];
|
|
967
|
+
const next = sql[i + 1];
|
|
968
|
+
if (inLineComment) {
|
|
969
|
+
out += ch;
|
|
970
|
+
if (ch === "\n") inLineComment = false;
|
|
971
|
+
continue;
|
|
972
|
+
}
|
|
973
|
+
if (inBlockComment) {
|
|
974
|
+
out += ch;
|
|
975
|
+
if (ch === "*" && next === "/") {
|
|
976
|
+
out += next;
|
|
977
|
+
inBlockComment = false;
|
|
978
|
+
i += 1;
|
|
979
|
+
}
|
|
980
|
+
continue;
|
|
981
|
+
}
|
|
982
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
983
|
+
out += ch + next;
|
|
984
|
+
inLineComment = true;
|
|
985
|
+
i += 1;
|
|
986
|
+
continue;
|
|
987
|
+
}
|
|
988
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
989
|
+
out += ch + next;
|
|
990
|
+
inBlockComment = true;
|
|
991
|
+
i += 1;
|
|
992
|
+
continue;
|
|
993
|
+
}
|
|
994
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
995
|
+
inSingle = !inSingle;
|
|
996
|
+
out += ch;
|
|
997
|
+
continue;
|
|
998
|
+
}
|
|
999
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
1000
|
+
inDouble = !inDouble;
|
|
1001
|
+
out += ch;
|
|
1002
|
+
continue;
|
|
1003
|
+
}
|
|
1004
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
1005
|
+
placeholder += 1;
|
|
1006
|
+
out += `$${placeholder}`;
|
|
1007
|
+
continue;
|
|
1008
|
+
}
|
|
1009
|
+
out += ch;
|
|
1010
|
+
}
|
|
1011
|
+
return out;
|
|
1012
|
+
}
|
|
1013
|
+
function translateStatementForPostgres(stmt) {
|
|
1014
|
+
const normalized = normalizeStatement(stmt);
|
|
1015
|
+
if (normalized.kind === "named") {
|
|
1016
|
+
throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
|
|
1017
|
+
}
|
|
1018
|
+
const rewrittenSql = rewriteSql(normalized.sql);
|
|
1019
|
+
const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
|
|
1020
|
+
return {
|
|
1021
|
+
sql: convertQuestionMarksToDollarParams(rewrittenSql),
|
|
1022
|
+
args: coercedArgs
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
function shouldBypassPostgres(stmt) {
|
|
1026
|
+
const normalized = normalizeStatement(stmt);
|
|
1027
|
+
if (normalized.kind === "named") {
|
|
1028
|
+
return true;
|
|
1029
|
+
}
|
|
1030
|
+
return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
|
|
1031
|
+
}
|
|
1032
|
+
function shouldFallbackOnError(error) {
|
|
1033
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1034
|
+
return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
|
|
1035
|
+
}
|
|
1036
|
+
function isReadQuery(sql) {
|
|
1037
|
+
const trimmed = sql.trimStart();
|
|
1038
|
+
return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
|
|
1039
|
+
}
|
|
1040
|
+
function buildRow(row, columns) {
|
|
1041
|
+
const values = columns.map((column) => row[column]);
|
|
1042
|
+
return Object.assign(values, row);
|
|
1043
|
+
}
|
|
1044
|
+
function buildResultSet(rows, rowsAffected = 0) {
|
|
1045
|
+
const columns = rows[0] ? Object.keys(rows[0]) : [];
|
|
1046
|
+
const resultRows = rows.map((row) => buildRow(row, columns));
|
|
1047
|
+
return {
|
|
1048
|
+
columns,
|
|
1049
|
+
columnTypes: columns.map(() => ""),
|
|
1050
|
+
rows: resultRows,
|
|
1051
|
+
rowsAffected,
|
|
1052
|
+
lastInsertRowid: void 0,
|
|
1053
|
+
toJSON() {
|
|
1054
|
+
return {
|
|
1055
|
+
columns,
|
|
1056
|
+
columnTypes: columns.map(() => ""),
|
|
1057
|
+
rows,
|
|
1058
|
+
rowsAffected,
|
|
1059
|
+
lastInsertRowid: void 0
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
async function loadPrismaClient() {
|
|
1065
|
+
if (!prismaClientPromise) {
|
|
1066
|
+
prismaClientPromise = (async () => {
|
|
1067
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
1068
|
+
if (explicitPath) {
|
|
1069
|
+
const module2 = await import(pathToFileURL(explicitPath).href);
|
|
1070
|
+
const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
|
|
1071
|
+
if (!PrismaClient2) {
|
|
1072
|
+
throw new Error(`No PrismaClient export found at ${explicitPath}`);
|
|
1073
|
+
}
|
|
1074
|
+
return new PrismaClient2();
|
|
1075
|
+
}
|
|
1076
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path3.join(os3.homedir(), "exe-db");
|
|
1077
|
+
const requireFromExeDb = createRequire(path3.join(exeDbRoot, "package.json"));
|
|
1078
|
+
const prismaEntry = requireFromExeDb.resolve("@prisma/client");
|
|
1079
|
+
const module = await import(pathToFileURL(prismaEntry).href);
|
|
1080
|
+
const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
|
|
1081
|
+
if (!PrismaClient) {
|
|
1082
|
+
throw new Error(`No PrismaClient export found in ${prismaEntry}`);
|
|
1083
|
+
}
|
|
1084
|
+
return new PrismaClient();
|
|
1085
|
+
})();
|
|
1086
|
+
}
|
|
1087
|
+
return prismaClientPromise;
|
|
1088
|
+
}
|
|
1089
|
+
async function ensureCompatibilityViews(prisma) {
|
|
1090
|
+
if (!compatibilityBootstrapPromise) {
|
|
1091
|
+
compatibilityBootstrapPromise = (async () => {
|
|
1092
|
+
for (const mapping of VIEW_MAPPINGS) {
|
|
1093
|
+
const relation = mapping.source.replace(/"/g, "");
|
|
1094
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
1095
|
+
"SELECT to_regclass($1) AS regclass",
|
|
1096
|
+
relation
|
|
1097
|
+
);
|
|
1098
|
+
if (!rows[0]?.regclass) {
|
|
1099
|
+
continue;
|
|
1100
|
+
}
|
|
1101
|
+
await prisma.$executeRawUnsafe(
|
|
1102
|
+
`CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
|
|
1103
|
+
);
|
|
1104
|
+
}
|
|
1105
|
+
})();
|
|
1106
|
+
}
|
|
1107
|
+
return compatibilityBootstrapPromise;
|
|
1108
|
+
}
|
|
1109
|
+
async function executeOnPrisma(executor, stmt) {
|
|
1110
|
+
const translated = translateStatementForPostgres(stmt);
|
|
1111
|
+
if (isReadQuery(translated.sql)) {
|
|
1112
|
+
const rows = await executor.$queryRawUnsafe(
|
|
1113
|
+
translated.sql,
|
|
1114
|
+
...translated.args
|
|
1115
|
+
);
|
|
1116
|
+
return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
|
|
1117
|
+
}
|
|
1118
|
+
const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
|
|
1119
|
+
return buildResultSet([], rowsAffected);
|
|
1120
|
+
}
|
|
1121
|
+
function splitSqlStatements(sql) {
|
|
1122
|
+
const parts = [];
|
|
1123
|
+
let current = "";
|
|
1124
|
+
let inSingle = false;
|
|
1125
|
+
let inDouble = false;
|
|
1126
|
+
let inLineComment = false;
|
|
1127
|
+
let inBlockComment = false;
|
|
1128
|
+
for (let i = 0; i < sql.length; i++) {
|
|
1129
|
+
const ch = sql[i];
|
|
1130
|
+
const next = sql[i + 1];
|
|
1131
|
+
if (inLineComment) {
|
|
1132
|
+
current += ch;
|
|
1133
|
+
if (ch === "\n") inLineComment = false;
|
|
1134
|
+
continue;
|
|
1135
|
+
}
|
|
1136
|
+
if (inBlockComment) {
|
|
1137
|
+
current += ch;
|
|
1138
|
+
if (ch === "*" && next === "/") {
|
|
1139
|
+
current += next;
|
|
1140
|
+
inBlockComment = false;
|
|
1141
|
+
i += 1;
|
|
1142
|
+
}
|
|
1143
|
+
continue;
|
|
1144
|
+
}
|
|
1145
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
1146
|
+
current += ch + next;
|
|
1147
|
+
inLineComment = true;
|
|
1148
|
+
i += 1;
|
|
1149
|
+
continue;
|
|
1150
|
+
}
|
|
1151
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
1152
|
+
current += ch + next;
|
|
1153
|
+
inBlockComment = true;
|
|
1154
|
+
i += 1;
|
|
1155
|
+
continue;
|
|
1156
|
+
}
|
|
1157
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
1158
|
+
inSingle = !inSingle;
|
|
1159
|
+
current += ch;
|
|
1160
|
+
continue;
|
|
1161
|
+
}
|
|
1162
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
1163
|
+
inDouble = !inDouble;
|
|
1164
|
+
current += ch;
|
|
1165
|
+
continue;
|
|
1166
|
+
}
|
|
1167
|
+
if (!inSingle && !inDouble && ch === ";") {
|
|
1168
|
+
if (current.trim()) {
|
|
1169
|
+
parts.push(current.trim());
|
|
1170
|
+
}
|
|
1171
|
+
current = "";
|
|
1172
|
+
continue;
|
|
1173
|
+
}
|
|
1174
|
+
current += ch;
|
|
1175
|
+
}
|
|
1176
|
+
if (current.trim()) {
|
|
1177
|
+
parts.push(current.trim());
|
|
1178
|
+
}
|
|
1179
|
+
return parts;
|
|
1180
|
+
}
|
|
1181
|
+
async function createPrismaDbAdapter(fallbackClient) {
|
|
1182
|
+
const prisma = await loadPrismaClient();
|
|
1183
|
+
await ensureCompatibilityViews(prisma);
|
|
1184
|
+
let closed = false;
|
|
1185
|
+
let adapter;
|
|
1186
|
+
const fallbackExecute = async (stmt, error) => {
|
|
1187
|
+
if (!fallbackClient) {
|
|
1188
|
+
if (error) throw error;
|
|
1189
|
+
throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
|
|
1190
|
+
}
|
|
1191
|
+
if (error) {
|
|
1192
|
+
process.stderr.write(
|
|
1193
|
+
`[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
1194
|
+
`
|
|
1195
|
+
);
|
|
1196
|
+
}
|
|
1197
|
+
return fallbackClient.execute(stmt);
|
|
1198
|
+
};
|
|
1199
|
+
adapter = {
|
|
1200
|
+
async execute(stmt) {
|
|
1201
|
+
if (shouldBypassPostgres(stmt)) {
|
|
1202
|
+
return fallbackExecute(stmt);
|
|
1203
|
+
}
|
|
1204
|
+
try {
|
|
1205
|
+
return await executeOnPrisma(prisma, stmt);
|
|
1206
|
+
} catch (error) {
|
|
1207
|
+
if (shouldFallbackOnError(error)) {
|
|
1208
|
+
return fallbackExecute(stmt, error);
|
|
1209
|
+
}
|
|
1210
|
+
throw error;
|
|
1211
|
+
}
|
|
1212
|
+
},
|
|
1213
|
+
async batch(stmts, mode) {
|
|
1214
|
+
if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
|
|
1215
|
+
if (!fallbackClient) {
|
|
1216
|
+
throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
|
|
1217
|
+
}
|
|
1218
|
+
return fallbackClient.batch(stmts, mode);
|
|
1219
|
+
}
|
|
1220
|
+
try {
|
|
1221
|
+
if (prisma.$transaction) {
|
|
1222
|
+
return await prisma.$transaction(async (tx) => {
|
|
1223
|
+
const results2 = [];
|
|
1224
|
+
for (const stmt of stmts) {
|
|
1225
|
+
results2.push(await executeOnPrisma(tx, stmt));
|
|
1226
|
+
}
|
|
1227
|
+
return results2;
|
|
1228
|
+
});
|
|
1229
|
+
}
|
|
1230
|
+
const results = [];
|
|
1231
|
+
for (const stmt of stmts) {
|
|
1232
|
+
results.push(await executeOnPrisma(prisma, stmt));
|
|
1233
|
+
}
|
|
1234
|
+
return results;
|
|
1235
|
+
} catch (error) {
|
|
1236
|
+
if (fallbackClient && shouldFallbackOnError(error)) {
|
|
1237
|
+
process.stderr.write(
|
|
1238
|
+
`[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
1239
|
+
`
|
|
1240
|
+
);
|
|
1241
|
+
return fallbackClient.batch(stmts, mode);
|
|
1242
|
+
}
|
|
1243
|
+
throw error;
|
|
1244
|
+
}
|
|
1245
|
+
},
|
|
1246
|
+
async migrate(stmts) {
|
|
1247
|
+
if (fallbackClient) {
|
|
1248
|
+
return fallbackClient.migrate(stmts);
|
|
1249
|
+
}
|
|
1250
|
+
return adapter.batch(stmts, "deferred");
|
|
1251
|
+
},
|
|
1252
|
+
async transaction(mode) {
|
|
1253
|
+
if (!fallbackClient) {
|
|
1254
|
+
throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
|
|
1255
|
+
}
|
|
1256
|
+
return fallbackClient.transaction(mode);
|
|
1257
|
+
},
|
|
1258
|
+
async executeMultiple(sql) {
|
|
1259
|
+
if (fallbackClient && shouldBypassPostgres(sql)) {
|
|
1260
|
+
return fallbackClient.executeMultiple(sql);
|
|
1261
|
+
}
|
|
1262
|
+
for (const statement of splitSqlStatements(sql)) {
|
|
1263
|
+
await adapter.execute(statement);
|
|
1264
|
+
}
|
|
1265
|
+
},
|
|
1266
|
+
async sync() {
|
|
1267
|
+
if (fallbackClient) {
|
|
1268
|
+
return fallbackClient.sync();
|
|
1269
|
+
}
|
|
1270
|
+
return { frame_no: 0, frames_synced: 0 };
|
|
1271
|
+
},
|
|
1272
|
+
close() {
|
|
1273
|
+
closed = true;
|
|
1274
|
+
prismaClientPromise = null;
|
|
1275
|
+
compatibilityBootstrapPromise = null;
|
|
1276
|
+
void prisma.$disconnect?.();
|
|
1277
|
+
},
|
|
1278
|
+
get closed() {
|
|
1279
|
+
return closed;
|
|
1280
|
+
},
|
|
1281
|
+
get protocol() {
|
|
1282
|
+
return "prisma-postgres";
|
|
1283
|
+
}
|
|
1284
|
+
};
|
|
1285
|
+
return adapter;
|
|
1286
|
+
}
|
|
640
1287
|
|
|
641
1288
|
// src/lib/database.ts
|
|
642
1289
|
var _client = null;
|
|
643
1290
|
var _resilientClient = null;
|
|
644
1291
|
var _walCheckpointTimer = null;
|
|
645
1292
|
var _daemonClient = null;
|
|
1293
|
+
var _adapterClient = null;
|
|
646
1294
|
var initTurso = initDatabase;
|
|
647
1295
|
async function initDatabase(config) {
|
|
1296
|
+
if (_walCheckpointTimer) {
|
|
1297
|
+
clearInterval(_walCheckpointTimer);
|
|
1298
|
+
_walCheckpointTimer = null;
|
|
1299
|
+
}
|
|
1300
|
+
if (_daemonClient) {
|
|
1301
|
+
_daemonClient.close();
|
|
1302
|
+
_daemonClient = null;
|
|
1303
|
+
}
|
|
1304
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
1305
|
+
_adapterClient.close();
|
|
1306
|
+
}
|
|
1307
|
+
_adapterClient = null;
|
|
648
1308
|
if (_client) {
|
|
649
1309
|
_client.close();
|
|
650
1310
|
_client = null;
|
|
@@ -658,6 +1318,7 @@ async function initDatabase(config) {
|
|
|
658
1318
|
}
|
|
659
1319
|
_client = createClient(opts);
|
|
660
1320
|
_resilientClient = wrapWithRetry(_client);
|
|
1321
|
+
_adapterClient = _resilientClient;
|
|
661
1322
|
_client.execute("PRAGMA busy_timeout = 30000").catch(() => {
|
|
662
1323
|
});
|
|
663
1324
|
_client.execute("PRAGMA journal_mode = WAL").catch(() => {
|
|
@@ -668,14 +1329,20 @@ async function initDatabase(config) {
|
|
|
668
1329
|
});
|
|
669
1330
|
}, 3e4);
|
|
670
1331
|
_walCheckpointTimer.unref();
|
|
1332
|
+
if (process.env.DATABASE_URL) {
|
|
1333
|
+
_adapterClient = await createPrismaDbAdapter(_resilientClient);
|
|
1334
|
+
}
|
|
671
1335
|
}
|
|
672
1336
|
function isInitialized() {
|
|
673
|
-
return _client !== null;
|
|
1337
|
+
return _adapterClient !== null || _client !== null;
|
|
674
1338
|
}
|
|
675
1339
|
function getClient() {
|
|
676
|
-
if (!
|
|
1340
|
+
if (!_adapterClient) {
|
|
677
1341
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
678
1342
|
}
|
|
1343
|
+
if (process.env.DATABASE_URL) {
|
|
1344
|
+
return _adapterClient;
|
|
1345
|
+
}
|
|
679
1346
|
if (process.env.EXE_IS_DAEMON === "1") {
|
|
680
1347
|
return _resilientClient;
|
|
681
1348
|
}
|
|
@@ -685,6 +1352,7 @@ function getClient() {
|
|
|
685
1352
|
return _resilientClient;
|
|
686
1353
|
}
|
|
687
1354
|
async function initDaemonClient() {
|
|
1355
|
+
if (process.env.DATABASE_URL) return;
|
|
688
1356
|
if (process.env.EXE_IS_DAEMON === "1") return;
|
|
689
1357
|
if (!_resilientClient) return;
|
|
690
1358
|
try {
|
|
@@ -981,6 +1649,7 @@ async function ensureSchema() {
|
|
|
981
1649
|
project TEXT NOT NULL,
|
|
982
1650
|
summary TEXT NOT NULL,
|
|
983
1651
|
task_file TEXT,
|
|
1652
|
+
session_scope TEXT,
|
|
984
1653
|
read INTEGER NOT NULL DEFAULT 0,
|
|
985
1654
|
created_at TEXT NOT NULL
|
|
986
1655
|
);
|
|
@@ -989,7 +1658,7 @@ async function ensureSchema() {
|
|
|
989
1658
|
ON notifications(read);
|
|
990
1659
|
|
|
991
1660
|
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
992
|
-
ON notifications(agent_id);
|
|
1661
|
+
ON notifications(agent_id, session_scope);
|
|
993
1662
|
|
|
994
1663
|
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
995
1664
|
ON notifications(task_file);
|
|
@@ -1027,6 +1696,7 @@ async function ensureSchema() {
|
|
|
1027
1696
|
target_agent TEXT NOT NULL,
|
|
1028
1697
|
target_project TEXT,
|
|
1029
1698
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
1699
|
+
session_scope TEXT,
|
|
1030
1700
|
content TEXT NOT NULL,
|
|
1031
1701
|
priority TEXT DEFAULT 'normal',
|
|
1032
1702
|
status TEXT DEFAULT 'pending',
|
|
@@ -1040,10 +1710,31 @@ async function ensureSchema() {
|
|
|
1040
1710
|
);
|
|
1041
1711
|
|
|
1042
1712
|
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
1043
|
-
ON messages(target_agent, status);
|
|
1713
|
+
ON messages(target_agent, session_scope, status);
|
|
1044
1714
|
|
|
1045
1715
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
1046
|
-
ON messages(target_agent, from_agent, server_seq);
|
|
1716
|
+
ON messages(target_agent, session_scope, from_agent, server_seq);
|
|
1717
|
+
`);
|
|
1718
|
+
try {
|
|
1719
|
+
await client.execute({
|
|
1720
|
+
sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
|
|
1721
|
+
args: []
|
|
1722
|
+
});
|
|
1723
|
+
} catch {
|
|
1724
|
+
}
|
|
1725
|
+
try {
|
|
1726
|
+
await client.execute({
|
|
1727
|
+
sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
|
|
1728
|
+
args: []
|
|
1729
|
+
});
|
|
1730
|
+
} catch {
|
|
1731
|
+
}
|
|
1732
|
+
await client.executeMultiple(`
|
|
1733
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
|
|
1734
|
+
ON notifications(agent_id, session_scope, read, created_at);
|
|
1735
|
+
|
|
1736
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
|
|
1737
|
+
ON messages(target_agent, session_scope, status, created_at);
|
|
1047
1738
|
`);
|
|
1048
1739
|
try {
|
|
1049
1740
|
await client.execute({
|
|
@@ -1627,13 +2318,28 @@ async function ensureSchema() {
|
|
|
1627
2318
|
} catch {
|
|
1628
2319
|
}
|
|
1629
2320
|
}
|
|
2321
|
+
try {
|
|
2322
|
+
await client.execute({
|
|
2323
|
+
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
2324
|
+
args: []
|
|
2325
|
+
});
|
|
2326
|
+
} catch {
|
|
2327
|
+
}
|
|
1630
2328
|
}
|
|
1631
2329
|
var disposeTurso = disposeDatabase;
|
|
1632
2330
|
async function disposeDatabase() {
|
|
2331
|
+
if (_walCheckpointTimer) {
|
|
2332
|
+
clearInterval(_walCheckpointTimer);
|
|
2333
|
+
_walCheckpointTimer = null;
|
|
2334
|
+
}
|
|
1633
2335
|
if (_daemonClient) {
|
|
1634
2336
|
_daemonClient.close();
|
|
1635
2337
|
_daemonClient = null;
|
|
1636
2338
|
}
|
|
2339
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
2340
|
+
_adapterClient.close();
|
|
2341
|
+
}
|
|
2342
|
+
_adapterClient = null;
|
|
1637
2343
|
if (_client) {
|
|
1638
2344
|
_client.close();
|
|
1639
2345
|
_client = null;
|