@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
|
@@ -8,6 +8,44 @@ 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
|
+
async function ensurePrivateDir(dirPath) {
|
|
15
|
+
await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
16
|
+
try {
|
|
17
|
+
await chmod(dirPath, PRIVATE_DIR_MODE);
|
|
18
|
+
} catch {
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function ensurePrivateDirSync(dirPath) {
|
|
22
|
+
mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
23
|
+
try {
|
|
24
|
+
chmodSync(dirPath, PRIVATE_DIR_MODE);
|
|
25
|
+
} catch {
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async function enforcePrivateFile(filePath) {
|
|
29
|
+
try {
|
|
30
|
+
await chmod(filePath, PRIVATE_FILE_MODE);
|
|
31
|
+
} catch {
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function enforcePrivateFileSync(filePath) {
|
|
35
|
+
try {
|
|
36
|
+
if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
|
|
37
|
+
} catch {
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
|
|
41
|
+
var init_secure_files = __esm({
|
|
42
|
+
"src/lib/secure-files.ts"() {
|
|
43
|
+
"use strict";
|
|
44
|
+
PRIVATE_DIR_MODE = 448;
|
|
45
|
+
PRIVATE_FILE_MODE = 384;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
11
49
|
// src/lib/config.ts
|
|
12
50
|
var config_exports = {};
|
|
13
51
|
__export(config_exports, {
|
|
@@ -24,8 +62,8 @@ __export(config_exports, {
|
|
|
24
62
|
migrateConfig: () => migrateConfig,
|
|
25
63
|
saveConfig: () => saveConfig
|
|
26
64
|
});
|
|
27
|
-
import { readFile, writeFile
|
|
28
|
-
import { readFileSync, existsSync, renameSync } from "fs";
|
|
65
|
+
import { readFile, writeFile } from "fs/promises";
|
|
66
|
+
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
29
67
|
import path from "path";
|
|
30
68
|
import os from "os";
|
|
31
69
|
function resolveDataDir() {
|
|
@@ -33,7 +71,7 @@ function resolveDataDir() {
|
|
|
33
71
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
34
72
|
const newDir = path.join(os.homedir(), ".exe-os");
|
|
35
73
|
const legacyDir = path.join(os.homedir(), ".exe-mem");
|
|
36
|
-
if (!
|
|
74
|
+
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
37
75
|
try {
|
|
38
76
|
renameSync(legacyDir, newDir);
|
|
39
77
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -96,9 +134,9 @@ function normalizeAutoUpdate(raw) {
|
|
|
96
134
|
}
|
|
97
135
|
async function loadConfig() {
|
|
98
136
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
99
|
-
await
|
|
137
|
+
await ensurePrivateDir(dir);
|
|
100
138
|
const configPath = path.join(dir, "config.json");
|
|
101
|
-
if (!
|
|
139
|
+
if (!existsSync2(configPath)) {
|
|
102
140
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
103
141
|
}
|
|
104
142
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -111,6 +149,7 @@ async function loadConfig() {
|
|
|
111
149
|
`);
|
|
112
150
|
try {
|
|
113
151
|
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
152
|
+
await enforcePrivateFile(configPath);
|
|
114
153
|
} catch {
|
|
115
154
|
}
|
|
116
155
|
}
|
|
@@ -129,7 +168,7 @@ async function loadConfig() {
|
|
|
129
168
|
function loadConfigSync() {
|
|
130
169
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
131
170
|
const configPath = path.join(dir, "config.json");
|
|
132
|
-
if (!
|
|
171
|
+
if (!existsSync2(configPath)) {
|
|
133
172
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
134
173
|
}
|
|
135
174
|
try {
|
|
@@ -147,12 +186,10 @@ function loadConfigSync() {
|
|
|
147
186
|
}
|
|
148
187
|
async function saveConfig(config) {
|
|
149
188
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
150
|
-
await
|
|
189
|
+
await ensurePrivateDir(dir);
|
|
151
190
|
const configPath = path.join(dir, "config.json");
|
|
152
191
|
await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
153
|
-
|
|
154
|
-
await chmod(configPath, 384);
|
|
155
|
-
}
|
|
192
|
+
await enforcePrivateFile(configPath);
|
|
156
193
|
}
|
|
157
194
|
async function loadConfigFrom(configPath) {
|
|
158
195
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -172,6 +209,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
|
|
|
172
209
|
var init_config = __esm({
|
|
173
210
|
"src/lib/config.ts"() {
|
|
174
211
|
"use strict";
|
|
212
|
+
init_secure_files();
|
|
175
213
|
EXE_AI_DIR = resolveDataDir();
|
|
176
214
|
DB_PATH = path.join(EXE_AI_DIR, "memories.db");
|
|
177
215
|
MODELS_DIR = path.join(EXE_AI_DIR, "models");
|
|
@@ -314,7 +352,7 @@ var init_db_retry = __esm({
|
|
|
314
352
|
|
|
315
353
|
// src/lib/employees.ts
|
|
316
354
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
317
|
-
import { existsSync as
|
|
355
|
+
import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
|
|
318
356
|
import { execSync } from "child_process";
|
|
319
357
|
import path2 from "path";
|
|
320
358
|
import os2 from "os";
|
|
@@ -331,7 +369,7 @@ function getCoordinatorName(employees = loadEmployeesSync()) {
|
|
|
331
369
|
return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
|
|
332
370
|
}
|
|
333
371
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
334
|
-
if (!
|
|
372
|
+
if (!existsSync3(employeesPath)) return [];
|
|
335
373
|
try {
|
|
336
374
|
return JSON.parse(readFileSync2(employeesPath, "utf-8"));
|
|
337
375
|
} catch {
|
|
@@ -341,7 +379,7 @@ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
|
341
379
|
function getEmployee(employees, name) {
|
|
342
380
|
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
343
381
|
}
|
|
344
|
-
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE;
|
|
382
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, IDENTITY_DIR;
|
|
345
383
|
var init_employees = __esm({
|
|
346
384
|
"src/lib/employees.ts"() {
|
|
347
385
|
"use strict";
|
|
@@ -349,12 +387,609 @@ var init_employees = __esm({
|
|
|
349
387
|
EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
|
|
350
388
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
351
389
|
COORDINATOR_ROLE = "COO";
|
|
390
|
+
IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
// src/lib/database-adapter.ts
|
|
395
|
+
import os3 from "os";
|
|
396
|
+
import path3 from "path";
|
|
397
|
+
import { createRequire } from "module";
|
|
398
|
+
import { pathToFileURL } from "url";
|
|
399
|
+
function quotedIdentifier(identifier) {
|
|
400
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
401
|
+
}
|
|
402
|
+
function unqualifiedTableName(name) {
|
|
403
|
+
const raw = name.trim().replace(/^"|"$/g, "");
|
|
404
|
+
const parts = raw.split(".");
|
|
405
|
+
return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
|
|
406
|
+
}
|
|
407
|
+
function stripTrailingSemicolon(sql) {
|
|
408
|
+
return sql.trim().replace(/;+\s*$/u, "");
|
|
409
|
+
}
|
|
410
|
+
function appendClause(sql, clause) {
|
|
411
|
+
const trimmed = stripTrailingSemicolon(sql);
|
|
412
|
+
const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
|
|
413
|
+
if (!returningMatch) {
|
|
414
|
+
return `${trimmed}${clause}`;
|
|
415
|
+
}
|
|
416
|
+
const idx = returningMatch.index;
|
|
417
|
+
return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
|
|
418
|
+
}
|
|
419
|
+
function normalizeStatement(stmt) {
|
|
420
|
+
if (typeof stmt === "string") {
|
|
421
|
+
return { kind: "positional", sql: stmt, args: [] };
|
|
422
|
+
}
|
|
423
|
+
const sql = stmt.sql;
|
|
424
|
+
if (Array.isArray(stmt.args) || stmt.args === void 0) {
|
|
425
|
+
return { kind: "positional", sql, args: stmt.args ?? [] };
|
|
426
|
+
}
|
|
427
|
+
return { kind: "named", sql, args: stmt.args };
|
|
428
|
+
}
|
|
429
|
+
function rewriteBooleanLiterals(sql) {
|
|
430
|
+
let out = sql;
|
|
431
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
432
|
+
const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
|
|
433
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
|
|
434
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
|
|
435
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
|
|
436
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
|
|
437
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
|
|
438
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
|
|
439
|
+
}
|
|
440
|
+
return out;
|
|
441
|
+
}
|
|
442
|
+
function rewriteInsertOrIgnore(sql) {
|
|
443
|
+
if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
|
|
444
|
+
return sql;
|
|
445
|
+
}
|
|
446
|
+
const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
|
|
447
|
+
return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
|
|
448
|
+
}
|
|
449
|
+
function rewriteInsertOrReplace(sql) {
|
|
450
|
+
const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
|
|
451
|
+
if (!match) {
|
|
452
|
+
return sql;
|
|
453
|
+
}
|
|
454
|
+
const rawTable = match[1];
|
|
455
|
+
const rawColumns = match[2];
|
|
456
|
+
const remainder = match[3];
|
|
457
|
+
const tableName = unqualifiedTableName(rawTable);
|
|
458
|
+
const conflictKeys = UPSERT_KEYS[tableName];
|
|
459
|
+
if (!conflictKeys?.length) {
|
|
460
|
+
return sql;
|
|
461
|
+
}
|
|
462
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
463
|
+
const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
|
|
464
|
+
const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
|
|
465
|
+
const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
|
|
466
|
+
return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
|
|
467
|
+
}
|
|
468
|
+
function rewriteSql(sql) {
|
|
469
|
+
let out = sql;
|
|
470
|
+
out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
|
|
471
|
+
out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
|
|
472
|
+
out = rewriteBooleanLiterals(out);
|
|
473
|
+
out = rewriteInsertOrReplace(out);
|
|
474
|
+
out = rewriteInsertOrIgnore(out);
|
|
475
|
+
return stripTrailingSemicolon(out);
|
|
476
|
+
}
|
|
477
|
+
function toBoolean(value) {
|
|
478
|
+
if (value === null || value === void 0) return value;
|
|
479
|
+
if (typeof value === "boolean") return value;
|
|
480
|
+
if (typeof value === "number") return value !== 0;
|
|
481
|
+
if (typeof value === "bigint") return value !== 0n;
|
|
482
|
+
if (typeof value === "string") {
|
|
483
|
+
const normalized = value.trim().toLowerCase();
|
|
484
|
+
if (normalized === "0" || normalized === "false") return false;
|
|
485
|
+
if (normalized === "1" || normalized === "true") return true;
|
|
486
|
+
}
|
|
487
|
+
return Boolean(value);
|
|
488
|
+
}
|
|
489
|
+
function countQuestionMarks(sql, end) {
|
|
490
|
+
let count = 0;
|
|
491
|
+
let inSingle = false;
|
|
492
|
+
let inDouble = false;
|
|
493
|
+
let inLineComment = false;
|
|
494
|
+
let inBlockComment = false;
|
|
495
|
+
for (let i = 0; i < end; i++) {
|
|
496
|
+
const ch = sql[i];
|
|
497
|
+
const next = sql[i + 1];
|
|
498
|
+
if (inLineComment) {
|
|
499
|
+
if (ch === "\n") inLineComment = false;
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
if (inBlockComment) {
|
|
503
|
+
if (ch === "*" && next === "/") {
|
|
504
|
+
inBlockComment = false;
|
|
505
|
+
i += 1;
|
|
506
|
+
}
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
510
|
+
inLineComment = true;
|
|
511
|
+
i += 1;
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
515
|
+
inBlockComment = true;
|
|
516
|
+
i += 1;
|
|
517
|
+
continue;
|
|
518
|
+
}
|
|
519
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
520
|
+
inSingle = !inSingle;
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
524
|
+
inDouble = !inDouble;
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
528
|
+
count += 1;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
return count;
|
|
532
|
+
}
|
|
533
|
+
function findBooleanPlaceholderIndexes(sql) {
|
|
534
|
+
const indexes = /* @__PURE__ */ new Set();
|
|
535
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
536
|
+
const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
|
|
537
|
+
for (const match of sql.matchAll(pattern)) {
|
|
538
|
+
const matchText = match[0];
|
|
539
|
+
const qIndex = match.index + matchText.lastIndexOf("?");
|
|
540
|
+
indexes.add(countQuestionMarks(sql, qIndex + 1));
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
return indexes;
|
|
544
|
+
}
|
|
545
|
+
function coerceInsertBooleanArgs(sql, args) {
|
|
546
|
+
const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
|
|
547
|
+
if (!match) return;
|
|
548
|
+
const rawTable = match[1];
|
|
549
|
+
const rawColumns = match[2];
|
|
550
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
551
|
+
if (!boolColumns?.size) return;
|
|
552
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
553
|
+
for (const [index, column] of columns.entries()) {
|
|
554
|
+
if (boolColumns.has(column) && index < args.length) {
|
|
555
|
+
args[index] = toBoolean(args[index]);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
function coerceUpdateBooleanArgs(sql, args) {
|
|
560
|
+
const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
|
|
561
|
+
if (!match) return;
|
|
562
|
+
const rawTable = match[1];
|
|
563
|
+
const setClause = match[2];
|
|
564
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
565
|
+
if (!boolColumns?.size) return;
|
|
566
|
+
const assignments = setClause.split(",");
|
|
567
|
+
let placeholderIndex = 0;
|
|
568
|
+
for (const assignment of assignments) {
|
|
569
|
+
if (!assignment.includes("?")) continue;
|
|
570
|
+
placeholderIndex += 1;
|
|
571
|
+
const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
|
|
572
|
+
if (colMatch && boolColumns.has(colMatch[1])) {
|
|
573
|
+
args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
function coerceBooleanArgs(sql, args) {
|
|
578
|
+
const nextArgs = [...args];
|
|
579
|
+
coerceInsertBooleanArgs(sql, nextArgs);
|
|
580
|
+
coerceUpdateBooleanArgs(sql, nextArgs);
|
|
581
|
+
const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
|
|
582
|
+
for (const index of placeholderIndexes) {
|
|
583
|
+
if (index > 0 && index <= nextArgs.length) {
|
|
584
|
+
nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
return nextArgs;
|
|
588
|
+
}
|
|
589
|
+
function convertQuestionMarksToDollarParams(sql) {
|
|
590
|
+
let out = "";
|
|
591
|
+
let placeholder = 0;
|
|
592
|
+
let inSingle = false;
|
|
593
|
+
let inDouble = false;
|
|
594
|
+
let inLineComment = false;
|
|
595
|
+
let inBlockComment = false;
|
|
596
|
+
for (let i = 0; i < sql.length; i++) {
|
|
597
|
+
const ch = sql[i];
|
|
598
|
+
const next = sql[i + 1];
|
|
599
|
+
if (inLineComment) {
|
|
600
|
+
out += ch;
|
|
601
|
+
if (ch === "\n") inLineComment = false;
|
|
602
|
+
continue;
|
|
603
|
+
}
|
|
604
|
+
if (inBlockComment) {
|
|
605
|
+
out += ch;
|
|
606
|
+
if (ch === "*" && next === "/") {
|
|
607
|
+
out += next;
|
|
608
|
+
inBlockComment = false;
|
|
609
|
+
i += 1;
|
|
610
|
+
}
|
|
611
|
+
continue;
|
|
612
|
+
}
|
|
613
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
614
|
+
out += ch + next;
|
|
615
|
+
inLineComment = true;
|
|
616
|
+
i += 1;
|
|
617
|
+
continue;
|
|
618
|
+
}
|
|
619
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
620
|
+
out += ch + next;
|
|
621
|
+
inBlockComment = true;
|
|
622
|
+
i += 1;
|
|
623
|
+
continue;
|
|
624
|
+
}
|
|
625
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
626
|
+
inSingle = !inSingle;
|
|
627
|
+
out += ch;
|
|
628
|
+
continue;
|
|
629
|
+
}
|
|
630
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
631
|
+
inDouble = !inDouble;
|
|
632
|
+
out += ch;
|
|
633
|
+
continue;
|
|
634
|
+
}
|
|
635
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
636
|
+
placeholder += 1;
|
|
637
|
+
out += `$${placeholder}`;
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
640
|
+
out += ch;
|
|
641
|
+
}
|
|
642
|
+
return out;
|
|
643
|
+
}
|
|
644
|
+
function translateStatementForPostgres(stmt) {
|
|
645
|
+
const normalized = normalizeStatement(stmt);
|
|
646
|
+
if (normalized.kind === "named") {
|
|
647
|
+
throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
|
|
648
|
+
}
|
|
649
|
+
const rewrittenSql = rewriteSql(normalized.sql);
|
|
650
|
+
const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
|
|
651
|
+
return {
|
|
652
|
+
sql: convertQuestionMarksToDollarParams(rewrittenSql),
|
|
653
|
+
args: coercedArgs
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
function shouldBypassPostgres(stmt) {
|
|
657
|
+
const normalized = normalizeStatement(stmt);
|
|
658
|
+
if (normalized.kind === "named") {
|
|
659
|
+
return true;
|
|
660
|
+
}
|
|
661
|
+
return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
|
|
662
|
+
}
|
|
663
|
+
function shouldFallbackOnError(error) {
|
|
664
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
665
|
+
return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
|
|
666
|
+
}
|
|
667
|
+
function isReadQuery(sql) {
|
|
668
|
+
const trimmed = sql.trimStart();
|
|
669
|
+
return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
|
|
670
|
+
}
|
|
671
|
+
function buildRow(row, columns) {
|
|
672
|
+
const values = columns.map((column) => row[column]);
|
|
673
|
+
return Object.assign(values, row);
|
|
674
|
+
}
|
|
675
|
+
function buildResultSet(rows, rowsAffected = 0) {
|
|
676
|
+
const columns = rows[0] ? Object.keys(rows[0]) : [];
|
|
677
|
+
const resultRows = rows.map((row) => buildRow(row, columns));
|
|
678
|
+
return {
|
|
679
|
+
columns,
|
|
680
|
+
columnTypes: columns.map(() => ""),
|
|
681
|
+
rows: resultRows,
|
|
682
|
+
rowsAffected,
|
|
683
|
+
lastInsertRowid: void 0,
|
|
684
|
+
toJSON() {
|
|
685
|
+
return {
|
|
686
|
+
columns,
|
|
687
|
+
columnTypes: columns.map(() => ""),
|
|
688
|
+
rows,
|
|
689
|
+
rowsAffected,
|
|
690
|
+
lastInsertRowid: void 0
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
async function loadPrismaClient() {
|
|
696
|
+
if (!prismaClientPromise) {
|
|
697
|
+
prismaClientPromise = (async () => {
|
|
698
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
699
|
+
if (explicitPath) {
|
|
700
|
+
const module2 = await import(pathToFileURL(explicitPath).href);
|
|
701
|
+
const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
|
|
702
|
+
if (!PrismaClient2) {
|
|
703
|
+
throw new Error(`No PrismaClient export found at ${explicitPath}`);
|
|
704
|
+
}
|
|
705
|
+
return new PrismaClient2();
|
|
706
|
+
}
|
|
707
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path3.join(os3.homedir(), "exe-db");
|
|
708
|
+
const requireFromExeDb = createRequire(path3.join(exeDbRoot, "package.json"));
|
|
709
|
+
const prismaEntry = requireFromExeDb.resolve("@prisma/client");
|
|
710
|
+
const module = await import(pathToFileURL(prismaEntry).href);
|
|
711
|
+
const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
|
|
712
|
+
if (!PrismaClient) {
|
|
713
|
+
throw new Error(`No PrismaClient export found in ${prismaEntry}`);
|
|
714
|
+
}
|
|
715
|
+
return new PrismaClient();
|
|
716
|
+
})();
|
|
717
|
+
}
|
|
718
|
+
return prismaClientPromise;
|
|
719
|
+
}
|
|
720
|
+
async function ensureCompatibilityViews(prisma) {
|
|
721
|
+
if (!compatibilityBootstrapPromise) {
|
|
722
|
+
compatibilityBootstrapPromise = (async () => {
|
|
723
|
+
for (const mapping of VIEW_MAPPINGS) {
|
|
724
|
+
const relation = mapping.source.replace(/"/g, "");
|
|
725
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
726
|
+
"SELECT to_regclass($1) AS regclass",
|
|
727
|
+
relation
|
|
728
|
+
);
|
|
729
|
+
if (!rows[0]?.regclass) {
|
|
730
|
+
continue;
|
|
731
|
+
}
|
|
732
|
+
await prisma.$executeRawUnsafe(
|
|
733
|
+
`CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
|
|
734
|
+
);
|
|
735
|
+
}
|
|
736
|
+
})();
|
|
737
|
+
}
|
|
738
|
+
return compatibilityBootstrapPromise;
|
|
739
|
+
}
|
|
740
|
+
async function executeOnPrisma(executor, stmt) {
|
|
741
|
+
const translated = translateStatementForPostgres(stmt);
|
|
742
|
+
if (isReadQuery(translated.sql)) {
|
|
743
|
+
const rows = await executor.$queryRawUnsafe(
|
|
744
|
+
translated.sql,
|
|
745
|
+
...translated.args
|
|
746
|
+
);
|
|
747
|
+
return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
|
|
748
|
+
}
|
|
749
|
+
const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
|
|
750
|
+
return buildResultSet([], rowsAffected);
|
|
751
|
+
}
|
|
752
|
+
function splitSqlStatements(sql) {
|
|
753
|
+
const parts = [];
|
|
754
|
+
let current = "";
|
|
755
|
+
let inSingle = false;
|
|
756
|
+
let inDouble = false;
|
|
757
|
+
let inLineComment = false;
|
|
758
|
+
let inBlockComment = false;
|
|
759
|
+
for (let i = 0; i < sql.length; i++) {
|
|
760
|
+
const ch = sql[i];
|
|
761
|
+
const next = sql[i + 1];
|
|
762
|
+
if (inLineComment) {
|
|
763
|
+
current += ch;
|
|
764
|
+
if (ch === "\n") inLineComment = false;
|
|
765
|
+
continue;
|
|
766
|
+
}
|
|
767
|
+
if (inBlockComment) {
|
|
768
|
+
current += ch;
|
|
769
|
+
if (ch === "*" && next === "/") {
|
|
770
|
+
current += next;
|
|
771
|
+
inBlockComment = false;
|
|
772
|
+
i += 1;
|
|
773
|
+
}
|
|
774
|
+
continue;
|
|
775
|
+
}
|
|
776
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
777
|
+
current += ch + next;
|
|
778
|
+
inLineComment = true;
|
|
779
|
+
i += 1;
|
|
780
|
+
continue;
|
|
781
|
+
}
|
|
782
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
783
|
+
current += ch + next;
|
|
784
|
+
inBlockComment = true;
|
|
785
|
+
i += 1;
|
|
786
|
+
continue;
|
|
787
|
+
}
|
|
788
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
789
|
+
inSingle = !inSingle;
|
|
790
|
+
current += ch;
|
|
791
|
+
continue;
|
|
792
|
+
}
|
|
793
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
794
|
+
inDouble = !inDouble;
|
|
795
|
+
current += ch;
|
|
796
|
+
continue;
|
|
797
|
+
}
|
|
798
|
+
if (!inSingle && !inDouble && ch === ";") {
|
|
799
|
+
if (current.trim()) {
|
|
800
|
+
parts.push(current.trim());
|
|
801
|
+
}
|
|
802
|
+
current = "";
|
|
803
|
+
continue;
|
|
804
|
+
}
|
|
805
|
+
current += ch;
|
|
806
|
+
}
|
|
807
|
+
if (current.trim()) {
|
|
808
|
+
parts.push(current.trim());
|
|
809
|
+
}
|
|
810
|
+
return parts;
|
|
811
|
+
}
|
|
812
|
+
async function createPrismaDbAdapter(fallbackClient) {
|
|
813
|
+
const prisma = await loadPrismaClient();
|
|
814
|
+
await ensureCompatibilityViews(prisma);
|
|
815
|
+
let closed = false;
|
|
816
|
+
let adapter;
|
|
817
|
+
const fallbackExecute = async (stmt, error) => {
|
|
818
|
+
if (!fallbackClient) {
|
|
819
|
+
if (error) throw error;
|
|
820
|
+
throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
|
|
821
|
+
}
|
|
822
|
+
if (error) {
|
|
823
|
+
process.stderr.write(
|
|
824
|
+
`[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
825
|
+
`
|
|
826
|
+
);
|
|
827
|
+
}
|
|
828
|
+
return fallbackClient.execute(stmt);
|
|
829
|
+
};
|
|
830
|
+
adapter = {
|
|
831
|
+
async execute(stmt) {
|
|
832
|
+
if (shouldBypassPostgres(stmt)) {
|
|
833
|
+
return fallbackExecute(stmt);
|
|
834
|
+
}
|
|
835
|
+
try {
|
|
836
|
+
return await executeOnPrisma(prisma, stmt);
|
|
837
|
+
} catch (error) {
|
|
838
|
+
if (shouldFallbackOnError(error)) {
|
|
839
|
+
return fallbackExecute(stmt, error);
|
|
840
|
+
}
|
|
841
|
+
throw error;
|
|
842
|
+
}
|
|
843
|
+
},
|
|
844
|
+
async batch(stmts, mode) {
|
|
845
|
+
if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
|
|
846
|
+
if (!fallbackClient) {
|
|
847
|
+
throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
|
|
848
|
+
}
|
|
849
|
+
return fallbackClient.batch(stmts, mode);
|
|
850
|
+
}
|
|
851
|
+
try {
|
|
852
|
+
if (prisma.$transaction) {
|
|
853
|
+
return await prisma.$transaction(async (tx) => {
|
|
854
|
+
const results2 = [];
|
|
855
|
+
for (const stmt of stmts) {
|
|
856
|
+
results2.push(await executeOnPrisma(tx, stmt));
|
|
857
|
+
}
|
|
858
|
+
return results2;
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
const results = [];
|
|
862
|
+
for (const stmt of stmts) {
|
|
863
|
+
results.push(await executeOnPrisma(prisma, stmt));
|
|
864
|
+
}
|
|
865
|
+
return results;
|
|
866
|
+
} catch (error) {
|
|
867
|
+
if (fallbackClient && shouldFallbackOnError(error)) {
|
|
868
|
+
process.stderr.write(
|
|
869
|
+
`[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
870
|
+
`
|
|
871
|
+
);
|
|
872
|
+
return fallbackClient.batch(stmts, mode);
|
|
873
|
+
}
|
|
874
|
+
throw error;
|
|
875
|
+
}
|
|
876
|
+
},
|
|
877
|
+
async migrate(stmts) {
|
|
878
|
+
if (fallbackClient) {
|
|
879
|
+
return fallbackClient.migrate(stmts);
|
|
880
|
+
}
|
|
881
|
+
return adapter.batch(stmts, "deferred");
|
|
882
|
+
},
|
|
883
|
+
async transaction(mode) {
|
|
884
|
+
if (!fallbackClient) {
|
|
885
|
+
throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
|
|
886
|
+
}
|
|
887
|
+
return fallbackClient.transaction(mode);
|
|
888
|
+
},
|
|
889
|
+
async executeMultiple(sql) {
|
|
890
|
+
if (fallbackClient && shouldBypassPostgres(sql)) {
|
|
891
|
+
return fallbackClient.executeMultiple(sql);
|
|
892
|
+
}
|
|
893
|
+
for (const statement of splitSqlStatements(sql)) {
|
|
894
|
+
await adapter.execute(statement);
|
|
895
|
+
}
|
|
896
|
+
},
|
|
897
|
+
async sync() {
|
|
898
|
+
if (fallbackClient) {
|
|
899
|
+
return fallbackClient.sync();
|
|
900
|
+
}
|
|
901
|
+
return { frame_no: 0, frames_synced: 0 };
|
|
902
|
+
},
|
|
903
|
+
close() {
|
|
904
|
+
closed = true;
|
|
905
|
+
prismaClientPromise = null;
|
|
906
|
+
compatibilityBootstrapPromise = null;
|
|
907
|
+
void prisma.$disconnect?.();
|
|
908
|
+
},
|
|
909
|
+
get closed() {
|
|
910
|
+
return closed;
|
|
911
|
+
},
|
|
912
|
+
get protocol() {
|
|
913
|
+
return "prisma-postgres";
|
|
914
|
+
}
|
|
915
|
+
};
|
|
916
|
+
return adapter;
|
|
917
|
+
}
|
|
918
|
+
var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
|
|
919
|
+
var init_database_adapter = __esm({
|
|
920
|
+
"src/lib/database-adapter.ts"() {
|
|
921
|
+
"use strict";
|
|
922
|
+
VIEW_MAPPINGS = [
|
|
923
|
+
{ view: "memories", source: "memory.memory_records" },
|
|
924
|
+
{ view: "tasks", source: "memory.tasks" },
|
|
925
|
+
{ view: "behaviors", source: "memory.behaviors" },
|
|
926
|
+
{ view: "entities", source: "memory.entities" },
|
|
927
|
+
{ view: "relationships", source: "memory.relationships" },
|
|
928
|
+
{ view: "entity_memories", source: "memory.entity_memories" },
|
|
929
|
+
{ view: "entity_aliases", source: "memory.entity_aliases" },
|
|
930
|
+
{ view: "notifications", source: "memory.notifications" },
|
|
931
|
+
{ view: "messages", source: "memory.messages" },
|
|
932
|
+
{ view: "users", source: "wiki.users" },
|
|
933
|
+
{ view: "workspaces", source: "wiki.workspaces" },
|
|
934
|
+
{ view: "workspace_users", source: "wiki.workspace_users" },
|
|
935
|
+
{ view: "documents", source: "wiki.workspace_documents" },
|
|
936
|
+
{ view: "chats", source: "wiki.workspace_chats" }
|
|
937
|
+
];
|
|
938
|
+
UPSERT_KEYS = {
|
|
939
|
+
memories: ["id"],
|
|
940
|
+
tasks: ["id"],
|
|
941
|
+
behaviors: ["id"],
|
|
942
|
+
entities: ["id"],
|
|
943
|
+
relationships: ["id"],
|
|
944
|
+
entity_aliases: ["alias"],
|
|
945
|
+
notifications: ["id"],
|
|
946
|
+
messages: ["id"],
|
|
947
|
+
users: ["id"],
|
|
948
|
+
workspaces: ["id"],
|
|
949
|
+
workspace_users: ["id"],
|
|
950
|
+
documents: ["id"],
|
|
951
|
+
chats: ["id"]
|
|
952
|
+
};
|
|
953
|
+
BOOLEAN_COLUMNS_BY_TABLE = {
|
|
954
|
+
memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
|
|
955
|
+
behaviors: /* @__PURE__ */ new Set(["active"]),
|
|
956
|
+
notifications: /* @__PURE__ */ new Set(["read"]),
|
|
957
|
+
users: /* @__PURE__ */ new Set(["has_personal_memory"])
|
|
958
|
+
};
|
|
959
|
+
BOOLEAN_COLUMN_NAMES = new Set(
|
|
960
|
+
Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
|
|
961
|
+
);
|
|
962
|
+
IMMEDIATE_FALLBACK_PATTERNS = [
|
|
963
|
+
/\bPRAGMA\b/i,
|
|
964
|
+
/\bsqlite_master\b/i,
|
|
965
|
+
/(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
|
|
966
|
+
/\bMATCH\b/i,
|
|
967
|
+
/\bvector_distance_cos\s*\(/i,
|
|
968
|
+
/\bjson_extract\s*\(/i,
|
|
969
|
+
/\bjulianday\s*\(/i,
|
|
970
|
+
/\bstrftime\s*\(/i,
|
|
971
|
+
/\blast_insert_rowid\s*\(/i
|
|
972
|
+
];
|
|
973
|
+
prismaClientPromise = null;
|
|
974
|
+
compatibilityBootstrapPromise = null;
|
|
352
975
|
}
|
|
353
976
|
});
|
|
354
977
|
|
|
355
978
|
// src/lib/database.ts
|
|
356
979
|
import { createClient } from "@libsql/client";
|
|
357
980
|
async function initDatabase(config) {
|
|
981
|
+
if (_walCheckpointTimer) {
|
|
982
|
+
clearInterval(_walCheckpointTimer);
|
|
983
|
+
_walCheckpointTimer = null;
|
|
984
|
+
}
|
|
985
|
+
if (_daemonClient) {
|
|
986
|
+
_daemonClient.close();
|
|
987
|
+
_daemonClient = null;
|
|
988
|
+
}
|
|
989
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
990
|
+
_adapterClient.close();
|
|
991
|
+
}
|
|
992
|
+
_adapterClient = null;
|
|
358
993
|
if (_client) {
|
|
359
994
|
_client.close();
|
|
360
995
|
_client = null;
|
|
@@ -368,6 +1003,7 @@ async function initDatabase(config) {
|
|
|
368
1003
|
}
|
|
369
1004
|
_client = createClient(opts);
|
|
370
1005
|
_resilientClient = wrapWithRetry(_client);
|
|
1006
|
+
_adapterClient = _resilientClient;
|
|
371
1007
|
_client.execute("PRAGMA busy_timeout = 30000").catch(() => {
|
|
372
1008
|
});
|
|
373
1009
|
_client.execute("PRAGMA journal_mode = WAL").catch(() => {
|
|
@@ -378,11 +1014,17 @@ async function initDatabase(config) {
|
|
|
378
1014
|
});
|
|
379
1015
|
}, 3e4);
|
|
380
1016
|
_walCheckpointTimer.unref();
|
|
1017
|
+
if (process.env.DATABASE_URL) {
|
|
1018
|
+
_adapterClient = await createPrismaDbAdapter(_resilientClient);
|
|
1019
|
+
}
|
|
381
1020
|
}
|
|
382
1021
|
function getClient() {
|
|
383
|
-
if (!
|
|
1022
|
+
if (!_adapterClient) {
|
|
384
1023
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
385
1024
|
}
|
|
1025
|
+
if (process.env.DATABASE_URL) {
|
|
1026
|
+
return _adapterClient;
|
|
1027
|
+
}
|
|
386
1028
|
if (process.env.EXE_IS_DAEMON === "1") {
|
|
387
1029
|
return _resilientClient;
|
|
388
1030
|
}
|
|
@@ -675,6 +1317,7 @@ async function ensureSchema() {
|
|
|
675
1317
|
project TEXT NOT NULL,
|
|
676
1318
|
summary TEXT NOT NULL,
|
|
677
1319
|
task_file TEXT,
|
|
1320
|
+
session_scope TEXT,
|
|
678
1321
|
read INTEGER NOT NULL DEFAULT 0,
|
|
679
1322
|
created_at TEXT NOT NULL
|
|
680
1323
|
);
|
|
@@ -683,7 +1326,7 @@ async function ensureSchema() {
|
|
|
683
1326
|
ON notifications(read);
|
|
684
1327
|
|
|
685
1328
|
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
686
|
-
ON notifications(agent_id);
|
|
1329
|
+
ON notifications(agent_id, session_scope);
|
|
687
1330
|
|
|
688
1331
|
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
689
1332
|
ON notifications(task_file);
|
|
@@ -721,6 +1364,7 @@ async function ensureSchema() {
|
|
|
721
1364
|
target_agent TEXT NOT NULL,
|
|
722
1365
|
target_project TEXT,
|
|
723
1366
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
1367
|
+
session_scope TEXT,
|
|
724
1368
|
content TEXT NOT NULL,
|
|
725
1369
|
priority TEXT DEFAULT 'normal',
|
|
726
1370
|
status TEXT DEFAULT 'pending',
|
|
@@ -734,10 +1378,31 @@ async function ensureSchema() {
|
|
|
734
1378
|
);
|
|
735
1379
|
|
|
736
1380
|
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
737
|
-
ON messages(target_agent, status);
|
|
1381
|
+
ON messages(target_agent, session_scope, status);
|
|
738
1382
|
|
|
739
1383
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
740
|
-
ON messages(target_agent, from_agent, server_seq);
|
|
1384
|
+
ON messages(target_agent, session_scope, from_agent, server_seq);
|
|
1385
|
+
`);
|
|
1386
|
+
try {
|
|
1387
|
+
await client.execute({
|
|
1388
|
+
sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
|
|
1389
|
+
args: []
|
|
1390
|
+
});
|
|
1391
|
+
} catch {
|
|
1392
|
+
}
|
|
1393
|
+
try {
|
|
1394
|
+
await client.execute({
|
|
1395
|
+
sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
|
|
1396
|
+
args: []
|
|
1397
|
+
});
|
|
1398
|
+
} catch {
|
|
1399
|
+
}
|
|
1400
|
+
await client.executeMultiple(`
|
|
1401
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
|
|
1402
|
+
ON notifications(agent_id, session_scope, read, created_at);
|
|
1403
|
+
|
|
1404
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
|
|
1405
|
+
ON messages(target_agent, session_scope, status, created_at);
|
|
741
1406
|
`);
|
|
742
1407
|
try {
|
|
743
1408
|
await client.execute({
|
|
@@ -1321,28 +1986,45 @@ async function ensureSchema() {
|
|
|
1321
1986
|
} catch {
|
|
1322
1987
|
}
|
|
1323
1988
|
}
|
|
1989
|
+
try {
|
|
1990
|
+
await client.execute({
|
|
1991
|
+
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
1992
|
+
args: []
|
|
1993
|
+
});
|
|
1994
|
+
} catch {
|
|
1995
|
+
}
|
|
1324
1996
|
}
|
|
1325
1997
|
async function disposeDatabase() {
|
|
1998
|
+
if (_walCheckpointTimer) {
|
|
1999
|
+
clearInterval(_walCheckpointTimer);
|
|
2000
|
+
_walCheckpointTimer = null;
|
|
2001
|
+
}
|
|
1326
2002
|
if (_daemonClient) {
|
|
1327
2003
|
_daemonClient.close();
|
|
1328
2004
|
_daemonClient = null;
|
|
1329
2005
|
}
|
|
2006
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
2007
|
+
_adapterClient.close();
|
|
2008
|
+
}
|
|
2009
|
+
_adapterClient = null;
|
|
1330
2010
|
if (_client) {
|
|
1331
2011
|
_client.close();
|
|
1332
2012
|
_client = null;
|
|
1333
2013
|
_resilientClient = null;
|
|
1334
2014
|
}
|
|
1335
2015
|
}
|
|
1336
|
-
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
|
|
2016
|
+
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
|
|
1337
2017
|
var init_database = __esm({
|
|
1338
2018
|
"src/lib/database.ts"() {
|
|
1339
2019
|
"use strict";
|
|
1340
2020
|
init_db_retry();
|
|
1341
2021
|
init_employees();
|
|
2022
|
+
init_database_adapter();
|
|
1342
2023
|
_client = null;
|
|
1343
2024
|
_resilientClient = null;
|
|
1344
2025
|
_walCheckpointTimer = null;
|
|
1345
2026
|
_daemonClient = null;
|
|
2027
|
+
_adapterClient = null;
|
|
1346
2028
|
initTurso = initDatabase;
|
|
1347
2029
|
disposeTurso = disposeDatabase;
|
|
1348
2030
|
}
|
|
@@ -1350,14 +2032,14 @@ var init_database = __esm({
|
|
|
1350
2032
|
|
|
1351
2033
|
// src/lib/keychain.ts
|
|
1352
2034
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
1353
|
-
import { existsSync as
|
|
1354
|
-
import
|
|
1355
|
-
import
|
|
2035
|
+
import { existsSync as existsSync4 } from "fs";
|
|
2036
|
+
import path4 from "path";
|
|
2037
|
+
import os4 from "os";
|
|
1356
2038
|
function getKeyDir() {
|
|
1357
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
2039
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path4.join(os4.homedir(), ".exe-os");
|
|
1358
2040
|
}
|
|
1359
2041
|
function getKeyPath() {
|
|
1360
|
-
return
|
|
2042
|
+
return path4.join(getKeyDir(), "master.key");
|
|
1361
2043
|
}
|
|
1362
2044
|
async function tryKeytar() {
|
|
1363
2045
|
try {
|
|
@@ -1378,9 +2060,9 @@ async function getMasterKey() {
|
|
|
1378
2060
|
}
|
|
1379
2061
|
}
|
|
1380
2062
|
const keyPath = getKeyPath();
|
|
1381
|
-
if (!
|
|
2063
|
+
if (!existsSync4(keyPath)) {
|
|
1382
2064
|
process.stderr.write(
|
|
1383
|
-
`[keychain] Key not found at ${keyPath} (HOME=${
|
|
2065
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os4.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
1384
2066
|
`
|
|
1385
2067
|
);
|
|
1386
2068
|
return null;
|
|
@@ -1465,6 +2147,7 @@ var shard_manager_exports = {};
|
|
|
1465
2147
|
__export(shard_manager_exports, {
|
|
1466
2148
|
disposeShards: () => disposeShards,
|
|
1467
2149
|
ensureShardSchema: () => ensureShardSchema,
|
|
2150
|
+
getOpenShardCount: () => getOpenShardCount,
|
|
1468
2151
|
getReadyShardClient: () => getReadyShardClient,
|
|
1469
2152
|
getShardClient: () => getShardClient,
|
|
1470
2153
|
getShardsDir: () => getShardsDir,
|
|
@@ -1473,15 +2156,18 @@ __export(shard_manager_exports, {
|
|
|
1473
2156
|
listShards: () => listShards,
|
|
1474
2157
|
shardExists: () => shardExists
|
|
1475
2158
|
});
|
|
1476
|
-
import
|
|
1477
|
-
import { existsSync as
|
|
2159
|
+
import path5 from "path";
|
|
2160
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync } from "fs";
|
|
1478
2161
|
import { createClient as createClient2 } from "@libsql/client";
|
|
1479
2162
|
function initShardManager(encryptionKey) {
|
|
1480
2163
|
_encryptionKey = encryptionKey;
|
|
1481
|
-
if (!
|
|
1482
|
-
|
|
2164
|
+
if (!existsSync5(SHARDS_DIR)) {
|
|
2165
|
+
mkdirSync2(SHARDS_DIR, { recursive: true });
|
|
1483
2166
|
}
|
|
1484
2167
|
_shardingEnabled = true;
|
|
2168
|
+
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
2169
|
+
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
2170
|
+
_evictionTimer.unref();
|
|
1485
2171
|
}
|
|
1486
2172
|
function isShardingEnabled() {
|
|
1487
2173
|
return _shardingEnabled;
|
|
@@ -1498,21 +2184,28 @@ function getShardClient(projectName) {
|
|
|
1498
2184
|
throw new Error(`Invalid project name for shard: "${projectName}"`);
|
|
1499
2185
|
}
|
|
1500
2186
|
const cached = _shards.get(safeName);
|
|
1501
|
-
if (cached)
|
|
1502
|
-
|
|
2187
|
+
if (cached) {
|
|
2188
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
2189
|
+
return cached;
|
|
2190
|
+
}
|
|
2191
|
+
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
2192
|
+
evictLRU();
|
|
2193
|
+
}
|
|
2194
|
+
const dbPath = path5.join(SHARDS_DIR, `${safeName}.db`);
|
|
1503
2195
|
const client = createClient2({
|
|
1504
2196
|
url: `file:${dbPath}`,
|
|
1505
2197
|
encryptionKey: _encryptionKey
|
|
1506
2198
|
});
|
|
1507
2199
|
_shards.set(safeName, client);
|
|
2200
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
1508
2201
|
return client;
|
|
1509
2202
|
}
|
|
1510
2203
|
function shardExists(projectName) {
|
|
1511
2204
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
1512
|
-
return
|
|
2205
|
+
return existsSync5(path5.join(SHARDS_DIR, `${safeName}.db`));
|
|
1513
2206
|
}
|
|
1514
2207
|
function listShards() {
|
|
1515
|
-
if (!
|
|
2208
|
+
if (!existsSync5(SHARDS_DIR)) return [];
|
|
1516
2209
|
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
1517
2210
|
}
|
|
1518
2211
|
async function ensureShardSchema(client) {
|
|
@@ -1564,6 +2257,8 @@ async function ensureShardSchema(client) {
|
|
|
1564
2257
|
for (const col of [
|
|
1565
2258
|
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
1566
2259
|
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
2260
|
+
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
2261
|
+
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
1567
2262
|
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
1568
2263
|
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
1569
2264
|
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
@@ -1586,7 +2281,23 @@ async function ensureShardSchema(client) {
|
|
|
1586
2281
|
// MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
|
|
1587
2282
|
"ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
|
|
1588
2283
|
"ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
|
|
1589
|
-
"ALTER TABLE memories ADD COLUMN trajectory TEXT"
|
|
2284
|
+
"ALTER TABLE memories ADD COLUMN trajectory TEXT",
|
|
2285
|
+
// Metadata enrichment columns (must match database.ts)
|
|
2286
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
2287
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
2288
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
2289
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
2290
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
2291
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
2292
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
2293
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
2294
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
2295
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
2296
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
2297
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
2298
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
2299
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
2300
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
1590
2301
|
]) {
|
|
1591
2302
|
try {
|
|
1592
2303
|
await client.execute(col);
|
|
@@ -1685,21 +2396,69 @@ async function getReadyShardClient(projectName) {
|
|
|
1685
2396
|
await ensureShardSchema(client);
|
|
1686
2397
|
return client;
|
|
1687
2398
|
}
|
|
2399
|
+
function evictLRU() {
|
|
2400
|
+
let oldest = null;
|
|
2401
|
+
let oldestTime = Infinity;
|
|
2402
|
+
for (const [name, time] of _shardLastAccess) {
|
|
2403
|
+
if (time < oldestTime) {
|
|
2404
|
+
oldestTime = time;
|
|
2405
|
+
oldest = name;
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
if (oldest) {
|
|
2409
|
+
const client = _shards.get(oldest);
|
|
2410
|
+
if (client) {
|
|
2411
|
+
client.close();
|
|
2412
|
+
}
|
|
2413
|
+
_shards.delete(oldest);
|
|
2414
|
+
_shardLastAccess.delete(oldest);
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
function evictIdleShards() {
|
|
2418
|
+
const now = Date.now();
|
|
2419
|
+
const toEvict = [];
|
|
2420
|
+
for (const [name, lastAccess] of _shardLastAccess) {
|
|
2421
|
+
if (now - lastAccess > SHARD_IDLE_MS) {
|
|
2422
|
+
toEvict.push(name);
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
for (const name of toEvict) {
|
|
2426
|
+
const client = _shards.get(name);
|
|
2427
|
+
if (client) {
|
|
2428
|
+
client.close();
|
|
2429
|
+
}
|
|
2430
|
+
_shards.delete(name);
|
|
2431
|
+
_shardLastAccess.delete(name);
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
function getOpenShardCount() {
|
|
2435
|
+
return _shards.size;
|
|
2436
|
+
}
|
|
1688
2437
|
function disposeShards() {
|
|
2438
|
+
if (_evictionTimer) {
|
|
2439
|
+
clearInterval(_evictionTimer);
|
|
2440
|
+
_evictionTimer = null;
|
|
2441
|
+
}
|
|
1689
2442
|
for (const [, client] of _shards) {
|
|
1690
2443
|
client.close();
|
|
1691
2444
|
}
|
|
1692
2445
|
_shards.clear();
|
|
2446
|
+
_shardLastAccess.clear();
|
|
1693
2447
|
_shardingEnabled = false;
|
|
1694
2448
|
_encryptionKey = null;
|
|
1695
2449
|
}
|
|
1696
|
-
var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
|
|
2450
|
+
var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
|
|
1697
2451
|
var init_shard_manager = __esm({
|
|
1698
2452
|
"src/lib/shard-manager.ts"() {
|
|
1699
2453
|
"use strict";
|
|
1700
2454
|
init_config();
|
|
1701
|
-
SHARDS_DIR =
|
|
2455
|
+
SHARDS_DIR = path5.join(EXE_AI_DIR, "shards");
|
|
2456
|
+
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
2457
|
+
MAX_OPEN_SHARDS = 10;
|
|
2458
|
+
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
1702
2459
|
_shards = /* @__PURE__ */ new Map();
|
|
2460
|
+
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
2461
|
+
_evictionTimer = null;
|
|
1703
2462
|
_encryptionKey = null;
|
|
1704
2463
|
_shardingEnabled = false;
|
|
1705
2464
|
}
|
|
@@ -2561,8 +3320,8 @@ __export(reranker_exports, {
|
|
|
2561
3320
|
rerankWithContext: () => rerankWithContext,
|
|
2562
3321
|
rerankWithScores: () => rerankWithScores
|
|
2563
3322
|
});
|
|
2564
|
-
import
|
|
2565
|
-
import { existsSync as
|
|
3323
|
+
import path6 from "path";
|
|
3324
|
+
import { existsSync as existsSync6 } from "fs";
|
|
2566
3325
|
function resetIdleTimer() {
|
|
2567
3326
|
if (_idleTimer) clearTimeout(_idleTimer);
|
|
2568
3327
|
_idleTimer = setTimeout(() => {
|
|
@@ -2573,18 +3332,18 @@ function resetIdleTimer() {
|
|
|
2573
3332
|
}
|
|
2574
3333
|
}
|
|
2575
3334
|
function isRerankerAvailable() {
|
|
2576
|
-
return
|
|
3335
|
+
return existsSync6(path6.join(MODELS_DIR, RERANKER_MODEL_FILE));
|
|
2577
3336
|
}
|
|
2578
3337
|
function getRerankerModelPath() {
|
|
2579
|
-
return
|
|
3338
|
+
return path6.join(MODELS_DIR, RERANKER_MODEL_FILE);
|
|
2580
3339
|
}
|
|
2581
3340
|
async function ensureLoaded() {
|
|
2582
3341
|
if (_rerankerContext) {
|
|
2583
3342
|
resetIdleTimer();
|
|
2584
3343
|
return;
|
|
2585
3344
|
}
|
|
2586
|
-
const modelPath =
|
|
2587
|
-
if (!
|
|
3345
|
+
const modelPath = path6.join(MODELS_DIR, RERANKER_MODEL_FILE);
|
|
3346
|
+
if (!existsSync6(modelPath)) {
|
|
2588
3347
|
throw new Error(
|
|
2589
3348
|
`Reranker model not found at ${modelPath}. Run /exe-setup to download it.`
|
|
2590
3349
|
);
|
|
@@ -2680,13 +3439,50 @@ var init_reranker = __esm({
|
|
|
2680
3439
|
}
|
|
2681
3440
|
});
|
|
2682
3441
|
|
|
3442
|
+
// src/lib/daemon-auth.ts
|
|
3443
|
+
import crypto2 from "crypto";
|
|
3444
|
+
import path7 from "path";
|
|
3445
|
+
import { existsSync as existsSync7, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
3446
|
+
function normalizeToken(token) {
|
|
3447
|
+
if (!token) return null;
|
|
3448
|
+
const trimmed = token.trim();
|
|
3449
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
3450
|
+
}
|
|
3451
|
+
function readDaemonToken() {
|
|
3452
|
+
try {
|
|
3453
|
+
if (!existsSync7(DAEMON_TOKEN_PATH)) return null;
|
|
3454
|
+
return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
|
|
3455
|
+
} catch {
|
|
3456
|
+
return null;
|
|
3457
|
+
}
|
|
3458
|
+
}
|
|
3459
|
+
function ensureDaemonToken(seed) {
|
|
3460
|
+
const existing = readDaemonToken();
|
|
3461
|
+
if (existing) return existing;
|
|
3462
|
+
const token = normalizeToken(seed) ?? crypto2.randomBytes(32).toString("hex");
|
|
3463
|
+
ensurePrivateDirSync(EXE_AI_DIR);
|
|
3464
|
+
writeFileSync2(DAEMON_TOKEN_PATH, `${token}
|
|
3465
|
+
`, "utf8");
|
|
3466
|
+
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
3467
|
+
return token;
|
|
3468
|
+
}
|
|
3469
|
+
var DAEMON_TOKEN_PATH;
|
|
3470
|
+
var init_daemon_auth = __esm({
|
|
3471
|
+
"src/lib/daemon-auth.ts"() {
|
|
3472
|
+
"use strict";
|
|
3473
|
+
init_config();
|
|
3474
|
+
init_secure_files();
|
|
3475
|
+
DAEMON_TOKEN_PATH = path7.join(EXE_AI_DIR, "exed.token");
|
|
3476
|
+
}
|
|
3477
|
+
});
|
|
3478
|
+
|
|
2683
3479
|
// src/lib/exe-daemon-client.ts
|
|
2684
3480
|
import net from "net";
|
|
2685
|
-
import
|
|
3481
|
+
import os5 from "os";
|
|
2686
3482
|
import { spawn } from "child_process";
|
|
2687
3483
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
2688
|
-
import { existsSync as
|
|
2689
|
-
import
|
|
3484
|
+
import { existsSync as existsSync8, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
|
|
3485
|
+
import path8 from "path";
|
|
2690
3486
|
import { fileURLToPath } from "url";
|
|
2691
3487
|
function handleData(chunk) {
|
|
2692
3488
|
_buffer += chunk.toString();
|
|
@@ -2714,9 +3510,9 @@ function handleData(chunk) {
|
|
|
2714
3510
|
}
|
|
2715
3511
|
}
|
|
2716
3512
|
function cleanupStaleFiles() {
|
|
2717
|
-
if (
|
|
3513
|
+
if (existsSync8(PID_PATH)) {
|
|
2718
3514
|
try {
|
|
2719
|
-
const pid = parseInt(
|
|
3515
|
+
const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
|
|
2720
3516
|
if (pid > 0) {
|
|
2721
3517
|
try {
|
|
2722
3518
|
process.kill(pid, 0);
|
|
@@ -2737,17 +3533,17 @@ function cleanupStaleFiles() {
|
|
|
2737
3533
|
}
|
|
2738
3534
|
}
|
|
2739
3535
|
function findPackageRoot() {
|
|
2740
|
-
let dir =
|
|
2741
|
-
const { root } =
|
|
3536
|
+
let dir = path8.dirname(fileURLToPath(import.meta.url));
|
|
3537
|
+
const { root } = path8.parse(dir);
|
|
2742
3538
|
while (dir !== root) {
|
|
2743
|
-
if (
|
|
2744
|
-
dir =
|
|
3539
|
+
if (existsSync8(path8.join(dir, "package.json"))) return dir;
|
|
3540
|
+
dir = path8.dirname(dir);
|
|
2745
3541
|
}
|
|
2746
3542
|
return null;
|
|
2747
3543
|
}
|
|
2748
3544
|
function spawnDaemon() {
|
|
2749
|
-
const freeGB =
|
|
2750
|
-
const totalGB =
|
|
3545
|
+
const freeGB = os5.freemem() / (1024 * 1024 * 1024);
|
|
3546
|
+
const totalGB = os5.totalmem() / (1024 * 1024 * 1024);
|
|
2751
3547
|
if (totalGB <= 8) {
|
|
2752
3548
|
process.stderr.write(
|
|
2753
3549
|
`[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
|
|
@@ -2767,16 +3563,17 @@ function spawnDaemon() {
|
|
|
2767
3563
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
2768
3564
|
return;
|
|
2769
3565
|
}
|
|
2770
|
-
const daemonPath =
|
|
2771
|
-
if (!
|
|
3566
|
+
const daemonPath = path8.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
3567
|
+
if (!existsSync8(daemonPath)) {
|
|
2772
3568
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
2773
3569
|
`);
|
|
2774
3570
|
return;
|
|
2775
3571
|
}
|
|
2776
3572
|
const resolvedPath = daemonPath;
|
|
3573
|
+
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
2777
3574
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
2778
3575
|
`);
|
|
2779
|
-
const logPath =
|
|
3576
|
+
const logPath = path8.join(path8.dirname(SOCKET_PATH), "exed.log");
|
|
2780
3577
|
let stderrFd = "ignore";
|
|
2781
3578
|
try {
|
|
2782
3579
|
stderrFd = openSync(logPath, "a");
|
|
@@ -2794,7 +3591,8 @@ function spawnDaemon() {
|
|
|
2794
3591
|
TMUX_PANE: void 0,
|
|
2795
3592
|
// Prevents resolveExeSession() from scoping to one session
|
|
2796
3593
|
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
2797
|
-
EXE_DAEMON_PID: PID_PATH
|
|
3594
|
+
EXE_DAEMON_PID: PID_PATH,
|
|
3595
|
+
[DAEMON_TOKEN_ENV]: daemonToken
|
|
2798
3596
|
}
|
|
2799
3597
|
});
|
|
2800
3598
|
child.unref();
|
|
@@ -2904,13 +3702,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
2904
3702
|
return;
|
|
2905
3703
|
}
|
|
2906
3704
|
const id = randomUUID2();
|
|
3705
|
+
const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
|
|
2907
3706
|
const timer = setTimeout(() => {
|
|
2908
3707
|
_pending.delete(id);
|
|
2909
3708
|
resolve({ error: "Request timeout" });
|
|
2910
3709
|
}, timeoutMs);
|
|
2911
3710
|
_pending.set(id, { resolve, timer });
|
|
2912
3711
|
try {
|
|
2913
|
-
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
3712
|
+
_socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
|
|
2914
3713
|
} catch {
|
|
2915
3714
|
clearTimeout(timer);
|
|
2916
3715
|
_pending.delete(id);
|
|
@@ -2927,74 +3726,123 @@ async function pingDaemon() {
|
|
|
2927
3726
|
return null;
|
|
2928
3727
|
}
|
|
2929
3728
|
function killAndRespawnDaemon() {
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
3729
|
+
if (!acquireSpawnLock()) {
|
|
3730
|
+
process.stderr.write("[exed-client] Another process is already restarting daemon \u2014 skipping\n");
|
|
3731
|
+
if (_socket) {
|
|
3732
|
+
_socket.destroy();
|
|
3733
|
+
_socket = null;
|
|
3734
|
+
}
|
|
3735
|
+
_connected = false;
|
|
3736
|
+
_buffer = "";
|
|
3737
|
+
return;
|
|
3738
|
+
}
|
|
3739
|
+
try {
|
|
3740
|
+
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
3741
|
+
if (existsSync8(PID_PATH)) {
|
|
3742
|
+
try {
|
|
3743
|
+
const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
|
|
3744
|
+
if (pid > 0) {
|
|
3745
|
+
try {
|
|
3746
|
+
process.kill(pid, "SIGKILL");
|
|
3747
|
+
} catch {
|
|
3748
|
+
}
|
|
2938
3749
|
}
|
|
3750
|
+
} catch {
|
|
2939
3751
|
}
|
|
3752
|
+
}
|
|
3753
|
+
if (_socket) {
|
|
3754
|
+
_socket.destroy();
|
|
3755
|
+
_socket = null;
|
|
3756
|
+
}
|
|
3757
|
+
_connected = false;
|
|
3758
|
+
_buffer = "";
|
|
3759
|
+
try {
|
|
3760
|
+
unlinkSync2(PID_PATH);
|
|
2940
3761
|
} catch {
|
|
2941
3762
|
}
|
|
3763
|
+
try {
|
|
3764
|
+
unlinkSync2(SOCKET_PATH);
|
|
3765
|
+
} catch {
|
|
3766
|
+
}
|
|
3767
|
+
spawnDaemon();
|
|
3768
|
+
} finally {
|
|
3769
|
+
releaseSpawnLock();
|
|
2942
3770
|
}
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
_socket = null;
|
|
2946
|
-
}
|
|
2947
|
-
_connected = false;
|
|
2948
|
-
_buffer = "";
|
|
3771
|
+
}
|
|
3772
|
+
function isDaemonTooYoung() {
|
|
2949
3773
|
try {
|
|
2950
|
-
|
|
3774
|
+
const stat = statSync(PID_PATH);
|
|
3775
|
+
return Date.now() - stat.mtimeMs < MIN_DAEMON_AGE_MS;
|
|
2951
3776
|
} catch {
|
|
3777
|
+
return false;
|
|
2952
3778
|
}
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
3779
|
+
}
|
|
3780
|
+
async function retryThenRestart(doRequest, label) {
|
|
3781
|
+
const result = await doRequest();
|
|
3782
|
+
if (!result.error) {
|
|
3783
|
+
_consecutiveFailures = 0;
|
|
3784
|
+
return result;
|
|
3785
|
+
}
|
|
3786
|
+
_consecutiveFailures++;
|
|
3787
|
+
for (let i = 0; i < MAX_RETRIES_BEFORE_RESTART; i++) {
|
|
3788
|
+
const delayMs = RETRY_DELAYS_MS[i] ?? 5e3;
|
|
3789
|
+
process.stderr.write(`[exed-client] ${label} failed (${result.error}), retry ${i + 1}/${MAX_RETRIES_BEFORE_RESTART} in ${delayMs}ms
|
|
3790
|
+
`);
|
|
3791
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
3792
|
+
if (!_connected) {
|
|
3793
|
+
if (!await connectToSocket()) continue;
|
|
3794
|
+
}
|
|
3795
|
+
const retry = await doRequest();
|
|
3796
|
+
if (!retry.error) {
|
|
3797
|
+
_consecutiveFailures = 0;
|
|
3798
|
+
return retry;
|
|
3799
|
+
}
|
|
3800
|
+
_consecutiveFailures++;
|
|
3801
|
+
}
|
|
3802
|
+
if (isDaemonTooYoung()) {
|
|
3803
|
+
process.stderr.write(`[exed-client] ${label}: daemon too young (< ${MIN_DAEMON_AGE_MS / 1e3}s) \u2014 skipping restart
|
|
3804
|
+
`);
|
|
3805
|
+
return { error: result.error };
|
|
2956
3806
|
}
|
|
2957
|
-
|
|
3807
|
+
process.stderr.write(`[exed-client] ${label}: ${_consecutiveFailures} consecutive failures \u2014 restarting daemon
|
|
3808
|
+
`);
|
|
3809
|
+
killAndRespawnDaemon();
|
|
3810
|
+
const start = Date.now();
|
|
3811
|
+
let delay2 = 200;
|
|
3812
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
3813
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
3814
|
+
if (await connectToSocket()) break;
|
|
3815
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
3816
|
+
}
|
|
3817
|
+
if (!_connected) return { error: "Daemon restart failed" };
|
|
3818
|
+
const final = await doRequest();
|
|
3819
|
+
if (!final.error) _consecutiveFailures = 0;
|
|
3820
|
+
return final;
|
|
2958
3821
|
}
|
|
2959
3822
|
async function embedViaClient(text, priority = "high") {
|
|
2960
3823
|
if (!_connected && !await connectEmbedDaemon()) return null;
|
|
2961
3824
|
_requestCount++;
|
|
2962
3825
|
if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
|
|
2963
3826
|
const health = await pingDaemon();
|
|
2964
|
-
if (!health) {
|
|
3827
|
+
if (!health && !isDaemonTooYoung()) {
|
|
2965
3828
|
process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
|
|
2966
3829
|
`);
|
|
2967
3830
|
killAndRespawnDaemon();
|
|
2968
3831
|
const start = Date.now();
|
|
2969
|
-
let
|
|
3832
|
+
let d = 200;
|
|
2970
3833
|
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
2971
|
-
await new Promise((r) => setTimeout(r,
|
|
3834
|
+
await new Promise((r) => setTimeout(r, d));
|
|
2972
3835
|
if (await connectToSocket()) break;
|
|
2973
|
-
|
|
3836
|
+
d = Math.min(d * 2, 3e3);
|
|
2974
3837
|
}
|
|
2975
3838
|
if (!_connected) return null;
|
|
2976
3839
|
}
|
|
2977
3840
|
}
|
|
2978
|
-
const result = await
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
killAndRespawnDaemon();
|
|
2984
|
-
const start = Date.now();
|
|
2985
|
-
let delay2 = 200;
|
|
2986
|
-
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
2987
|
-
await new Promise((r) => setTimeout(r, delay2));
|
|
2988
|
-
if (await connectToSocket()) break;
|
|
2989
|
-
delay2 = Math.min(delay2 * 2, 3e3);
|
|
2990
|
-
}
|
|
2991
|
-
if (!_connected) return null;
|
|
2992
|
-
const retry = await sendRequest([text], priority);
|
|
2993
|
-
if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
|
|
2994
|
-
process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
|
|
2995
|
-
`);
|
|
2996
|
-
}
|
|
2997
|
-
return null;
|
|
3841
|
+
const result = await retryThenRestart(
|
|
3842
|
+
() => sendRequest([text], priority),
|
|
3843
|
+
"Embed"
|
|
3844
|
+
);
|
|
3845
|
+
return !result.error && result.vectors?.[0] ? result.vectors[0] : null;
|
|
2998
3846
|
}
|
|
2999
3847
|
function disconnectClient() {
|
|
3000
3848
|
if (_socket) {
|
|
@@ -3009,22 +3857,28 @@ function disconnectClient() {
|
|
|
3009
3857
|
entry.resolve({ error: "Client disconnected" });
|
|
3010
3858
|
}
|
|
3011
3859
|
}
|
|
3012
|
-
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, HEALTH_CHECK_INTERVAL, _pending, MAX_BUFFER;
|
|
3860
|
+
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, DAEMON_TOKEN_ENV, _socket, _connected, _buffer, _requestCount, _consecutiveFailures, HEALTH_CHECK_INTERVAL, MAX_RETRIES_BEFORE_RESTART, RETRY_DELAYS_MS, MIN_DAEMON_AGE_MS, _pending, MAX_BUFFER;
|
|
3013
3861
|
var init_exe_daemon_client = __esm({
|
|
3014
3862
|
"src/lib/exe-daemon-client.ts"() {
|
|
3015
3863
|
"use strict";
|
|
3016
3864
|
init_config();
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3865
|
+
init_daemon_auth();
|
|
3866
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path8.join(EXE_AI_DIR, "exed.sock");
|
|
3867
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path8.join(EXE_AI_DIR, "exed.pid");
|
|
3868
|
+
SPAWN_LOCK_PATH = path8.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
3020
3869
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
3021
3870
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
3022
3871
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
3872
|
+
DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
|
|
3023
3873
|
_socket = null;
|
|
3024
3874
|
_connected = false;
|
|
3025
3875
|
_buffer = "";
|
|
3026
3876
|
_requestCount = 0;
|
|
3877
|
+
_consecutiveFailures = 0;
|
|
3027
3878
|
HEALTH_CHECK_INTERVAL = 100;
|
|
3879
|
+
MAX_RETRIES_BEFORE_RESTART = 3;
|
|
3880
|
+
RETRY_DELAYS_MS = [1e3, 3e3, 5e3];
|
|
3881
|
+
MIN_DAEMON_AGE_MS = 3e4;
|
|
3028
3882
|
_pending = /* @__PURE__ */ new Map();
|
|
3029
3883
|
MAX_BUFFER = 1e7;
|
|
3030
3884
|
}
|
|
@@ -3067,10 +3921,10 @@ async function disposeEmbedder() {
|
|
|
3067
3921
|
async function embedDirect(text) {
|
|
3068
3922
|
const llamaCpp = await import("node-llama-cpp");
|
|
3069
3923
|
const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
3070
|
-
const { existsSync:
|
|
3071
|
-
const
|
|
3072
|
-
const modelPath =
|
|
3073
|
-
if (!
|
|
3924
|
+
const { existsSync: existsSync10 } = await import("fs");
|
|
3925
|
+
const path12 = await import("path");
|
|
3926
|
+
const modelPath = path12.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
|
|
3927
|
+
if (!existsSync10(modelPath)) {
|
|
3074
3928
|
throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
|
|
3075
3929
|
}
|
|
3076
3930
|
const llama = await llamaCpp.getLlama();
|
|
@@ -3105,7 +3959,7 @@ __export(project_name_exports, {
|
|
|
3105
3959
|
getProjectName: () => getProjectName
|
|
3106
3960
|
});
|
|
3107
3961
|
import { execSync as execSync2 } from "child_process";
|
|
3108
|
-
import
|
|
3962
|
+
import path9 from "path";
|
|
3109
3963
|
function getProjectName(cwd) {
|
|
3110
3964
|
const dir = cwd ?? process.cwd();
|
|
3111
3965
|
if (_cached && _cachedCwd === dir) return _cached;
|
|
@@ -3118,7 +3972,7 @@ function getProjectName(cwd) {
|
|
|
3118
3972
|
timeout: 2e3,
|
|
3119
3973
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3120
3974
|
}).trim();
|
|
3121
|
-
repoRoot =
|
|
3975
|
+
repoRoot = path9.dirname(gitCommonDir);
|
|
3122
3976
|
} catch {
|
|
3123
3977
|
repoRoot = execSync2("git rev-parse --show-toplevel", {
|
|
3124
3978
|
cwd: dir,
|
|
@@ -3127,11 +3981,11 @@ function getProjectName(cwd) {
|
|
|
3127
3981
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3128
3982
|
}).trim();
|
|
3129
3983
|
}
|
|
3130
|
-
_cached =
|
|
3984
|
+
_cached = path9.basename(repoRoot);
|
|
3131
3985
|
_cachedCwd = dir;
|
|
3132
3986
|
return _cached;
|
|
3133
3987
|
} catch {
|
|
3134
|
-
_cached =
|
|
3988
|
+
_cached = path9.basename(dir);
|
|
3135
3989
|
_cachedCwd = dir;
|
|
3136
3990
|
return _cached;
|
|
3137
3991
|
}
|
|
@@ -3155,9 +4009,9 @@ __export(file_grep_exports, {
|
|
|
3155
4009
|
grepProjectFiles: () => grepProjectFiles
|
|
3156
4010
|
});
|
|
3157
4011
|
import { execSync as execSync3 } from "child_process";
|
|
3158
|
-
import { readFileSync as
|
|
3159
|
-
import
|
|
3160
|
-
import
|
|
4012
|
+
import { readFileSync as readFileSync5, readdirSync as readdirSync2, statSync as statSync2, existsSync as existsSync9 } from "fs";
|
|
4013
|
+
import path10 from "path";
|
|
4014
|
+
import crypto3 from "crypto";
|
|
3161
4015
|
function hasRipgrep() {
|
|
3162
4016
|
if (_hasRg === null) {
|
|
3163
4017
|
try {
|
|
@@ -3190,13 +4044,13 @@ async function grepProjectFiles(query, projectRoot, options) {
|
|
|
3190
4044
|
const chunkCtx = getChunkContext(hit.filePath, hit.lineNumber);
|
|
3191
4045
|
const prefix = chunkCtx ? `[file: ${hit.filePath}:${hit.lineNumber} in ${chunkCtx}]` : `[file: ${hit.filePath}:${hit.lineNumber}]`;
|
|
3192
4046
|
return {
|
|
3193
|
-
id:
|
|
4047
|
+
id: crypto3.createHash("sha256").update(`${hit.filePath}:${hit.lineNumber}`).digest("hex").slice(0, 36),
|
|
3194
4048
|
agent_id: "project",
|
|
3195
4049
|
agent_role: "file",
|
|
3196
4050
|
session_id: "file-grep",
|
|
3197
4051
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3198
4052
|
tool_name: "file_grep",
|
|
3199
|
-
project_name:
|
|
4053
|
+
project_name: path10.basename(projectRoot),
|
|
3200
4054
|
has_error: false,
|
|
3201
4055
|
raw_text: `${prefix} ${buildSnippet(hit, projectRoot)}`,
|
|
3202
4056
|
vector: null,
|
|
@@ -3208,7 +4062,7 @@ function getChunkContext(filePath, lineNumber) {
|
|
|
3208
4062
|
try {
|
|
3209
4063
|
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
3210
4064
|
if (ext !== "ts" && ext !== "tsx" && ext !== "js" && ext !== "jsx") return "";
|
|
3211
|
-
const source =
|
|
4065
|
+
const source = readFileSync5(filePath, "utf8");
|
|
3212
4066
|
const lines = source.split("\n");
|
|
3213
4067
|
for (let i = Math.min(lineNumber - 1, lines.length - 1); i >= 0; i--) {
|
|
3214
4068
|
const line = lines[i];
|
|
@@ -3270,11 +4124,11 @@ function grepWithNodeFs(pattern, projectRoot, patterns) {
|
|
|
3270
4124
|
const files = collectFiles(projectRoot, patterns ?? DEFAULT_PATTERNS);
|
|
3271
4125
|
const hits = [];
|
|
3272
4126
|
for (const filePath of files.slice(0, MAX_FILES)) {
|
|
3273
|
-
const absPath =
|
|
4127
|
+
const absPath = path10.join(projectRoot, filePath);
|
|
3274
4128
|
try {
|
|
3275
4129
|
const stat = statSync2(absPath);
|
|
3276
4130
|
if (stat.size > MAX_FILE_SIZE) continue;
|
|
3277
|
-
const content =
|
|
4131
|
+
const content = readFileSync5(absPath, "utf8");
|
|
3278
4132
|
const lines = content.split("\n");
|
|
3279
4133
|
const matches = content.match(regex);
|
|
3280
4134
|
if (!matches || matches.length === 0) continue;
|
|
@@ -3297,15 +4151,15 @@ function collectFiles(root, patterns) {
|
|
|
3297
4151
|
const files = [];
|
|
3298
4152
|
function walk(dir, relative) {
|
|
3299
4153
|
if (files.length >= MAX_FILES) return;
|
|
3300
|
-
const basename =
|
|
4154
|
+
const basename = path10.basename(dir);
|
|
3301
4155
|
if (EXCLUDE_DIRS.includes(basename)) return;
|
|
3302
4156
|
try {
|
|
3303
4157
|
const entries = readdirSync2(dir, { withFileTypes: true });
|
|
3304
4158
|
for (const entry of entries) {
|
|
3305
4159
|
if (files.length >= MAX_FILES) return;
|
|
3306
|
-
const rel =
|
|
4160
|
+
const rel = path10.join(relative, entry.name);
|
|
3307
4161
|
if (entry.isDirectory()) {
|
|
3308
|
-
walk(
|
|
4162
|
+
walk(path10.join(dir, entry.name), rel);
|
|
3309
4163
|
} else if (entry.isFile()) {
|
|
3310
4164
|
for (const pat of patterns) {
|
|
3311
4165
|
if (matchGlob(rel, pat)) {
|
|
@@ -3337,7 +4191,7 @@ function matchGlob(filePath, pattern) {
|
|
|
3337
4191
|
if (slashIdx !== -1) {
|
|
3338
4192
|
const dir = pattern.slice(0, slashIdx);
|
|
3339
4193
|
const ext2 = pattern.slice(slashIdx + 1).replace("*", "");
|
|
3340
|
-
const fileDir =
|
|
4194
|
+
const fileDir = path10.dirname(filePath);
|
|
3341
4195
|
return fileDir === dir && filePath.endsWith(ext2);
|
|
3342
4196
|
}
|
|
3343
4197
|
const ext = pattern.replace("*", "");
|
|
@@ -3345,9 +4199,9 @@ function matchGlob(filePath, pattern) {
|
|
|
3345
4199
|
}
|
|
3346
4200
|
function buildSnippet(hit, projectRoot) {
|
|
3347
4201
|
try {
|
|
3348
|
-
const absPath =
|
|
3349
|
-
if (!
|
|
3350
|
-
const lines =
|
|
4202
|
+
const absPath = path10.join(projectRoot, hit.filePath);
|
|
4203
|
+
if (!existsSync9(absPath)) return hit.matchLine;
|
|
4204
|
+
const lines = readFileSync5(absPath, "utf8").split("\n");
|
|
3351
4205
|
const start = Math.max(0, hit.lineNumber - 3);
|
|
3352
4206
|
const end = Math.min(lines.length, hit.lineNumber + 2);
|
|
3353
4207
|
return lines.slice(start, end).join("\n").slice(0, 500);
|
|
@@ -4591,9 +5445,9 @@ init_database();
|
|
|
4591
5445
|
|
|
4592
5446
|
// src/lib/active-agent.ts
|
|
4593
5447
|
init_config();
|
|
4594
|
-
import { readFileSync as
|
|
5448
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, unlinkSync as unlinkSync3, readdirSync as readdirSync3 } from "fs";
|
|
4595
5449
|
import { execSync as execSync5 } from "child_process";
|
|
4596
|
-
import
|
|
5450
|
+
import path11 from "path";
|
|
4597
5451
|
|
|
4598
5452
|
// src/lib/session-key.ts
|
|
4599
5453
|
import { execSync as execSync4 } from "child_process";
|
|
@@ -4659,7 +5513,7 @@ function getSessionKey() {
|
|
|
4659
5513
|
|
|
4660
5514
|
// src/lib/active-agent.ts
|
|
4661
5515
|
init_employees();
|
|
4662
|
-
var CACHE_DIR =
|
|
5516
|
+
var CACHE_DIR = path11.join(EXE_AI_DIR, "session-cache");
|
|
4663
5517
|
var STALE_MS = 24 * 60 * 60 * 1e3;
|
|
4664
5518
|
function isNameWithOptionalInstance(candidate, baseName) {
|
|
4665
5519
|
if (candidate === baseName) return true;
|
|
@@ -4704,12 +5558,12 @@ function resolveActiveAgentFromTmuxSession(sessionName) {
|
|
|
4704
5558
|
return null;
|
|
4705
5559
|
}
|
|
4706
5560
|
function getMarkerPath() {
|
|
4707
|
-
return
|
|
5561
|
+
return path11.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
|
|
4708
5562
|
}
|
|
4709
5563
|
function getActiveAgent() {
|
|
4710
5564
|
try {
|
|
4711
5565
|
const markerPath = getMarkerPath();
|
|
4712
|
-
const raw =
|
|
5566
|
+
const raw = readFileSync6(markerPath, "utf8");
|
|
4713
5567
|
const data = JSON.parse(raw);
|
|
4714
5568
|
if (data.agentId) {
|
|
4715
5569
|
if (data.startedAt) {
|
|
@@ -4774,10 +5628,10 @@ process.stdin.on("end", async () => {
|
|
|
4774
5628
|
}
|
|
4775
5629
|
if (process.env.EXE_DEBUG_HOOKS) {
|
|
4776
5630
|
try {
|
|
4777
|
-
const
|
|
5631
|
+
const os6 = await import("os");
|
|
4778
5632
|
const fs = await import("fs");
|
|
4779
5633
|
const nodePath = await import("path");
|
|
4780
|
-
const debugPath = nodePath.default.join(
|
|
5634
|
+
const debugPath = nodePath.default.join(os6.default.homedir(), ".exe-os", "logs", "hook-stdin-error-recall.log");
|
|
4781
5635
|
fs.mkdirSync(nodePath.default.dirname(debugPath), { recursive: true });
|
|
4782
5636
|
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
4783
5637
|
const snippet = input.length > 2e3 ? input.slice(0, 2e3) + "...[truncated]" : input;
|