@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,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");
|
|
@@ -156,7 +182,7 @@ var init_db_retry = __esm({
|
|
|
156
182
|
|
|
157
183
|
// src/lib/employees.ts
|
|
158
184
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
159
|
-
import { existsSync as
|
|
185
|
+
import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
|
|
160
186
|
import { execSync } from "child_process";
|
|
161
187
|
import path2 from "path";
|
|
162
188
|
import os2 from "os";
|
|
@@ -173,14 +199,14 @@ function getCoordinatorName(employees = loadEmployeesSync()) {
|
|
|
173
199
|
return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
|
|
174
200
|
}
|
|
175
201
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
176
|
-
if (!
|
|
202
|
+
if (!existsSync3(employeesPath)) return [];
|
|
177
203
|
try {
|
|
178
204
|
return JSON.parse(readFileSync2(employeesPath, "utf-8"));
|
|
179
205
|
} catch {
|
|
180
206
|
return [];
|
|
181
207
|
}
|
|
182
208
|
}
|
|
183
|
-
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE;
|
|
209
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, IDENTITY_DIR;
|
|
184
210
|
var init_employees = __esm({
|
|
185
211
|
"src/lib/employees.ts"() {
|
|
186
212
|
"use strict";
|
|
@@ -188,16 +214,638 @@ var init_employees = __esm({
|
|
|
188
214
|
EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
|
|
189
215
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
190
216
|
COORDINATOR_ROLE = "COO";
|
|
217
|
+
IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// src/lib/database-adapter.ts
|
|
222
|
+
import os3 from "os";
|
|
223
|
+
import path3 from "path";
|
|
224
|
+
import { createRequire } from "module";
|
|
225
|
+
import { pathToFileURL } from "url";
|
|
226
|
+
function quotedIdentifier(identifier) {
|
|
227
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
228
|
+
}
|
|
229
|
+
function unqualifiedTableName(name) {
|
|
230
|
+
const raw = name.trim().replace(/^"|"$/g, "");
|
|
231
|
+
const parts = raw.split(".");
|
|
232
|
+
return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
|
|
233
|
+
}
|
|
234
|
+
function stripTrailingSemicolon(sql) {
|
|
235
|
+
return sql.trim().replace(/;+\s*$/u, "");
|
|
236
|
+
}
|
|
237
|
+
function appendClause(sql, clause) {
|
|
238
|
+
const trimmed = stripTrailingSemicolon(sql);
|
|
239
|
+
const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
|
|
240
|
+
if (!returningMatch) {
|
|
241
|
+
return `${trimmed}${clause}`;
|
|
242
|
+
}
|
|
243
|
+
const idx = returningMatch.index;
|
|
244
|
+
return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
|
|
245
|
+
}
|
|
246
|
+
function normalizeStatement(stmt) {
|
|
247
|
+
if (typeof stmt === "string") {
|
|
248
|
+
return { kind: "positional", sql: stmt, args: [] };
|
|
249
|
+
}
|
|
250
|
+
const sql = stmt.sql;
|
|
251
|
+
if (Array.isArray(stmt.args) || stmt.args === void 0) {
|
|
252
|
+
return { kind: "positional", sql, args: stmt.args ?? [] };
|
|
253
|
+
}
|
|
254
|
+
return { kind: "named", sql, args: stmt.args };
|
|
255
|
+
}
|
|
256
|
+
function rewriteBooleanLiterals(sql) {
|
|
257
|
+
let out = sql;
|
|
258
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
259
|
+
const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
|
|
260
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
|
|
261
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
|
|
262
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
|
|
263
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
|
|
264
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
|
|
265
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
|
|
266
|
+
}
|
|
267
|
+
return out;
|
|
268
|
+
}
|
|
269
|
+
function rewriteInsertOrIgnore(sql) {
|
|
270
|
+
if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
|
|
271
|
+
return sql;
|
|
272
|
+
}
|
|
273
|
+
const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
|
|
274
|
+
return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
|
|
275
|
+
}
|
|
276
|
+
function rewriteInsertOrReplace(sql) {
|
|
277
|
+
const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
|
|
278
|
+
if (!match) {
|
|
279
|
+
return sql;
|
|
280
|
+
}
|
|
281
|
+
const rawTable = match[1];
|
|
282
|
+
const rawColumns = match[2];
|
|
283
|
+
const remainder = match[3];
|
|
284
|
+
const tableName = unqualifiedTableName(rawTable);
|
|
285
|
+
const conflictKeys = UPSERT_KEYS[tableName];
|
|
286
|
+
if (!conflictKeys?.length) {
|
|
287
|
+
return sql;
|
|
288
|
+
}
|
|
289
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
290
|
+
const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
|
|
291
|
+
const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
|
|
292
|
+
const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
|
|
293
|
+
return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
|
|
294
|
+
}
|
|
295
|
+
function rewriteSql(sql) {
|
|
296
|
+
let out = sql;
|
|
297
|
+
out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
|
|
298
|
+
out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
|
|
299
|
+
out = rewriteBooleanLiterals(out);
|
|
300
|
+
out = rewriteInsertOrReplace(out);
|
|
301
|
+
out = rewriteInsertOrIgnore(out);
|
|
302
|
+
return stripTrailingSemicolon(out);
|
|
303
|
+
}
|
|
304
|
+
function toBoolean(value) {
|
|
305
|
+
if (value === null || value === void 0) return value;
|
|
306
|
+
if (typeof value === "boolean") return value;
|
|
307
|
+
if (typeof value === "number") return value !== 0;
|
|
308
|
+
if (typeof value === "bigint") return value !== 0n;
|
|
309
|
+
if (typeof value === "string") {
|
|
310
|
+
const normalized = value.trim().toLowerCase();
|
|
311
|
+
if (normalized === "0" || normalized === "false") return false;
|
|
312
|
+
if (normalized === "1" || normalized === "true") return true;
|
|
313
|
+
}
|
|
314
|
+
return Boolean(value);
|
|
315
|
+
}
|
|
316
|
+
function countQuestionMarks(sql, end) {
|
|
317
|
+
let count = 0;
|
|
318
|
+
let inSingle = false;
|
|
319
|
+
let inDouble = false;
|
|
320
|
+
let inLineComment = false;
|
|
321
|
+
let inBlockComment = false;
|
|
322
|
+
for (let i = 0; i < end; i++) {
|
|
323
|
+
const ch = sql[i];
|
|
324
|
+
const next = sql[i + 1];
|
|
325
|
+
if (inLineComment) {
|
|
326
|
+
if (ch === "\n") inLineComment = false;
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
if (inBlockComment) {
|
|
330
|
+
if (ch === "*" && next === "/") {
|
|
331
|
+
inBlockComment = false;
|
|
332
|
+
i += 1;
|
|
333
|
+
}
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
337
|
+
inLineComment = true;
|
|
338
|
+
i += 1;
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
342
|
+
inBlockComment = true;
|
|
343
|
+
i += 1;
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
347
|
+
inSingle = !inSingle;
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
351
|
+
inDouble = !inDouble;
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
355
|
+
count += 1;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return count;
|
|
359
|
+
}
|
|
360
|
+
function findBooleanPlaceholderIndexes(sql) {
|
|
361
|
+
const indexes = /* @__PURE__ */ new Set();
|
|
362
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
363
|
+
const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
|
|
364
|
+
for (const match of sql.matchAll(pattern)) {
|
|
365
|
+
const matchText = match[0];
|
|
366
|
+
const qIndex = match.index + matchText.lastIndexOf("?");
|
|
367
|
+
indexes.add(countQuestionMarks(sql, qIndex + 1));
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return indexes;
|
|
371
|
+
}
|
|
372
|
+
function coerceInsertBooleanArgs(sql, args) {
|
|
373
|
+
const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
|
|
374
|
+
if (!match) return;
|
|
375
|
+
const rawTable = match[1];
|
|
376
|
+
const rawColumns = match[2];
|
|
377
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
378
|
+
if (!boolColumns?.size) return;
|
|
379
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
380
|
+
for (const [index, column] of columns.entries()) {
|
|
381
|
+
if (boolColumns.has(column) && index < args.length) {
|
|
382
|
+
args[index] = toBoolean(args[index]);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
function coerceUpdateBooleanArgs(sql, args) {
|
|
387
|
+
const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
|
|
388
|
+
if (!match) return;
|
|
389
|
+
const rawTable = match[1];
|
|
390
|
+
const setClause = match[2];
|
|
391
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
392
|
+
if (!boolColumns?.size) return;
|
|
393
|
+
const assignments = setClause.split(",");
|
|
394
|
+
let placeholderIndex = 0;
|
|
395
|
+
for (const assignment of assignments) {
|
|
396
|
+
if (!assignment.includes("?")) continue;
|
|
397
|
+
placeholderIndex += 1;
|
|
398
|
+
const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
|
|
399
|
+
if (colMatch && boolColumns.has(colMatch[1])) {
|
|
400
|
+
args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
function coerceBooleanArgs(sql, args) {
|
|
405
|
+
const nextArgs = [...args];
|
|
406
|
+
coerceInsertBooleanArgs(sql, nextArgs);
|
|
407
|
+
coerceUpdateBooleanArgs(sql, nextArgs);
|
|
408
|
+
const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
|
|
409
|
+
for (const index of placeholderIndexes) {
|
|
410
|
+
if (index > 0 && index <= nextArgs.length) {
|
|
411
|
+
nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return nextArgs;
|
|
415
|
+
}
|
|
416
|
+
function convertQuestionMarksToDollarParams(sql) {
|
|
417
|
+
let out = "";
|
|
418
|
+
let placeholder = 0;
|
|
419
|
+
let inSingle = false;
|
|
420
|
+
let inDouble = false;
|
|
421
|
+
let inLineComment = false;
|
|
422
|
+
let inBlockComment = false;
|
|
423
|
+
for (let i = 0; i < sql.length; i++) {
|
|
424
|
+
const ch = sql[i];
|
|
425
|
+
const next = sql[i + 1];
|
|
426
|
+
if (inLineComment) {
|
|
427
|
+
out += ch;
|
|
428
|
+
if (ch === "\n") inLineComment = false;
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
if (inBlockComment) {
|
|
432
|
+
out += ch;
|
|
433
|
+
if (ch === "*" && next === "/") {
|
|
434
|
+
out += next;
|
|
435
|
+
inBlockComment = false;
|
|
436
|
+
i += 1;
|
|
437
|
+
}
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
441
|
+
out += ch + next;
|
|
442
|
+
inLineComment = true;
|
|
443
|
+
i += 1;
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
447
|
+
out += ch + next;
|
|
448
|
+
inBlockComment = true;
|
|
449
|
+
i += 1;
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
453
|
+
inSingle = !inSingle;
|
|
454
|
+
out += ch;
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
458
|
+
inDouble = !inDouble;
|
|
459
|
+
out += ch;
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
463
|
+
placeholder += 1;
|
|
464
|
+
out += `$${placeholder}`;
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
out += ch;
|
|
468
|
+
}
|
|
469
|
+
return out;
|
|
470
|
+
}
|
|
471
|
+
function translateStatementForPostgres(stmt) {
|
|
472
|
+
const normalized = normalizeStatement(stmt);
|
|
473
|
+
if (normalized.kind === "named") {
|
|
474
|
+
throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
|
|
475
|
+
}
|
|
476
|
+
const rewrittenSql = rewriteSql(normalized.sql);
|
|
477
|
+
const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
|
|
478
|
+
return {
|
|
479
|
+
sql: convertQuestionMarksToDollarParams(rewrittenSql),
|
|
480
|
+
args: coercedArgs
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
function shouldBypassPostgres(stmt) {
|
|
484
|
+
const normalized = normalizeStatement(stmt);
|
|
485
|
+
if (normalized.kind === "named") {
|
|
486
|
+
return true;
|
|
487
|
+
}
|
|
488
|
+
return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
|
|
489
|
+
}
|
|
490
|
+
function shouldFallbackOnError(error) {
|
|
491
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
492
|
+
return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
|
|
493
|
+
}
|
|
494
|
+
function isReadQuery(sql) {
|
|
495
|
+
const trimmed = sql.trimStart();
|
|
496
|
+
return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
|
|
497
|
+
}
|
|
498
|
+
function buildRow(row, columns) {
|
|
499
|
+
const values = columns.map((column) => row[column]);
|
|
500
|
+
return Object.assign(values, row);
|
|
501
|
+
}
|
|
502
|
+
function buildResultSet(rows, rowsAffected = 0) {
|
|
503
|
+
const columns = rows[0] ? Object.keys(rows[0]) : [];
|
|
504
|
+
const resultRows = rows.map((row) => buildRow(row, columns));
|
|
505
|
+
return {
|
|
506
|
+
columns,
|
|
507
|
+
columnTypes: columns.map(() => ""),
|
|
508
|
+
rows: resultRows,
|
|
509
|
+
rowsAffected,
|
|
510
|
+
lastInsertRowid: void 0,
|
|
511
|
+
toJSON() {
|
|
512
|
+
return {
|
|
513
|
+
columns,
|
|
514
|
+
columnTypes: columns.map(() => ""),
|
|
515
|
+
rows,
|
|
516
|
+
rowsAffected,
|
|
517
|
+
lastInsertRowid: void 0
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
async function loadPrismaClient() {
|
|
523
|
+
if (!prismaClientPromise) {
|
|
524
|
+
prismaClientPromise = (async () => {
|
|
525
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
526
|
+
if (explicitPath) {
|
|
527
|
+
const module2 = await import(pathToFileURL(explicitPath).href);
|
|
528
|
+
const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
|
|
529
|
+
if (!PrismaClient2) {
|
|
530
|
+
throw new Error(`No PrismaClient export found at ${explicitPath}`);
|
|
531
|
+
}
|
|
532
|
+
return new PrismaClient2();
|
|
533
|
+
}
|
|
534
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path3.join(os3.homedir(), "exe-db");
|
|
535
|
+
const requireFromExeDb = createRequire(path3.join(exeDbRoot, "package.json"));
|
|
536
|
+
const prismaEntry = requireFromExeDb.resolve("@prisma/client");
|
|
537
|
+
const module = await import(pathToFileURL(prismaEntry).href);
|
|
538
|
+
const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
|
|
539
|
+
if (!PrismaClient) {
|
|
540
|
+
throw new Error(`No PrismaClient export found in ${prismaEntry}`);
|
|
541
|
+
}
|
|
542
|
+
return new PrismaClient();
|
|
543
|
+
})();
|
|
544
|
+
}
|
|
545
|
+
return prismaClientPromise;
|
|
546
|
+
}
|
|
547
|
+
async function ensureCompatibilityViews(prisma) {
|
|
548
|
+
if (!compatibilityBootstrapPromise) {
|
|
549
|
+
compatibilityBootstrapPromise = (async () => {
|
|
550
|
+
for (const mapping of VIEW_MAPPINGS) {
|
|
551
|
+
const relation = mapping.source.replace(/"/g, "");
|
|
552
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
553
|
+
"SELECT to_regclass($1) AS regclass",
|
|
554
|
+
relation
|
|
555
|
+
);
|
|
556
|
+
if (!rows[0]?.regclass) {
|
|
557
|
+
continue;
|
|
558
|
+
}
|
|
559
|
+
await prisma.$executeRawUnsafe(
|
|
560
|
+
`CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
})();
|
|
564
|
+
}
|
|
565
|
+
return compatibilityBootstrapPromise;
|
|
566
|
+
}
|
|
567
|
+
async function executeOnPrisma(executor, stmt) {
|
|
568
|
+
const translated = translateStatementForPostgres(stmt);
|
|
569
|
+
if (isReadQuery(translated.sql)) {
|
|
570
|
+
const rows = await executor.$queryRawUnsafe(
|
|
571
|
+
translated.sql,
|
|
572
|
+
...translated.args
|
|
573
|
+
);
|
|
574
|
+
return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
|
|
575
|
+
}
|
|
576
|
+
const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
|
|
577
|
+
return buildResultSet([], rowsAffected);
|
|
578
|
+
}
|
|
579
|
+
function splitSqlStatements(sql) {
|
|
580
|
+
const parts = [];
|
|
581
|
+
let current = "";
|
|
582
|
+
let inSingle = false;
|
|
583
|
+
let inDouble = false;
|
|
584
|
+
let inLineComment = false;
|
|
585
|
+
let inBlockComment = false;
|
|
586
|
+
for (let i = 0; i < sql.length; i++) {
|
|
587
|
+
const ch = sql[i];
|
|
588
|
+
const next = sql[i + 1];
|
|
589
|
+
if (inLineComment) {
|
|
590
|
+
current += ch;
|
|
591
|
+
if (ch === "\n") inLineComment = false;
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
if (inBlockComment) {
|
|
595
|
+
current += ch;
|
|
596
|
+
if (ch === "*" && next === "/") {
|
|
597
|
+
current += next;
|
|
598
|
+
inBlockComment = false;
|
|
599
|
+
i += 1;
|
|
600
|
+
}
|
|
601
|
+
continue;
|
|
602
|
+
}
|
|
603
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
604
|
+
current += ch + next;
|
|
605
|
+
inLineComment = true;
|
|
606
|
+
i += 1;
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
610
|
+
current += ch + next;
|
|
611
|
+
inBlockComment = true;
|
|
612
|
+
i += 1;
|
|
613
|
+
continue;
|
|
614
|
+
}
|
|
615
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
616
|
+
inSingle = !inSingle;
|
|
617
|
+
current += ch;
|
|
618
|
+
continue;
|
|
619
|
+
}
|
|
620
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
621
|
+
inDouble = !inDouble;
|
|
622
|
+
current += ch;
|
|
623
|
+
continue;
|
|
624
|
+
}
|
|
625
|
+
if (!inSingle && !inDouble && ch === ";") {
|
|
626
|
+
if (current.trim()) {
|
|
627
|
+
parts.push(current.trim());
|
|
628
|
+
}
|
|
629
|
+
current = "";
|
|
630
|
+
continue;
|
|
631
|
+
}
|
|
632
|
+
current += ch;
|
|
633
|
+
}
|
|
634
|
+
if (current.trim()) {
|
|
635
|
+
parts.push(current.trim());
|
|
636
|
+
}
|
|
637
|
+
return parts;
|
|
638
|
+
}
|
|
639
|
+
async function createPrismaDbAdapter(fallbackClient) {
|
|
640
|
+
const prisma = await loadPrismaClient();
|
|
641
|
+
await ensureCompatibilityViews(prisma);
|
|
642
|
+
let closed = false;
|
|
643
|
+
let adapter;
|
|
644
|
+
const fallbackExecute = async (stmt, error) => {
|
|
645
|
+
if (!fallbackClient) {
|
|
646
|
+
if (error) throw error;
|
|
647
|
+
throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
|
|
648
|
+
}
|
|
649
|
+
if (error) {
|
|
650
|
+
process.stderr.write(
|
|
651
|
+
`[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
652
|
+
`
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
return fallbackClient.execute(stmt);
|
|
656
|
+
};
|
|
657
|
+
adapter = {
|
|
658
|
+
async execute(stmt) {
|
|
659
|
+
if (shouldBypassPostgres(stmt)) {
|
|
660
|
+
return fallbackExecute(stmt);
|
|
661
|
+
}
|
|
662
|
+
try {
|
|
663
|
+
return await executeOnPrisma(prisma, stmt);
|
|
664
|
+
} catch (error) {
|
|
665
|
+
if (shouldFallbackOnError(error)) {
|
|
666
|
+
return fallbackExecute(stmt, error);
|
|
667
|
+
}
|
|
668
|
+
throw error;
|
|
669
|
+
}
|
|
670
|
+
},
|
|
671
|
+
async batch(stmts, mode) {
|
|
672
|
+
if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
|
|
673
|
+
if (!fallbackClient) {
|
|
674
|
+
throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
|
|
675
|
+
}
|
|
676
|
+
return fallbackClient.batch(stmts, mode);
|
|
677
|
+
}
|
|
678
|
+
try {
|
|
679
|
+
if (prisma.$transaction) {
|
|
680
|
+
return await prisma.$transaction(async (tx) => {
|
|
681
|
+
const results2 = [];
|
|
682
|
+
for (const stmt of stmts) {
|
|
683
|
+
results2.push(await executeOnPrisma(tx, stmt));
|
|
684
|
+
}
|
|
685
|
+
return results2;
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
const results = [];
|
|
689
|
+
for (const stmt of stmts) {
|
|
690
|
+
results.push(await executeOnPrisma(prisma, stmt));
|
|
691
|
+
}
|
|
692
|
+
return results;
|
|
693
|
+
} catch (error) {
|
|
694
|
+
if (fallbackClient && shouldFallbackOnError(error)) {
|
|
695
|
+
process.stderr.write(
|
|
696
|
+
`[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
697
|
+
`
|
|
698
|
+
);
|
|
699
|
+
return fallbackClient.batch(stmts, mode);
|
|
700
|
+
}
|
|
701
|
+
throw error;
|
|
702
|
+
}
|
|
703
|
+
},
|
|
704
|
+
async migrate(stmts) {
|
|
705
|
+
if (fallbackClient) {
|
|
706
|
+
return fallbackClient.migrate(stmts);
|
|
707
|
+
}
|
|
708
|
+
return adapter.batch(stmts, "deferred");
|
|
709
|
+
},
|
|
710
|
+
async transaction(mode) {
|
|
711
|
+
if (!fallbackClient) {
|
|
712
|
+
throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
|
|
713
|
+
}
|
|
714
|
+
return fallbackClient.transaction(mode);
|
|
715
|
+
},
|
|
716
|
+
async executeMultiple(sql) {
|
|
717
|
+
if (fallbackClient && shouldBypassPostgres(sql)) {
|
|
718
|
+
return fallbackClient.executeMultiple(sql);
|
|
719
|
+
}
|
|
720
|
+
for (const statement of splitSqlStatements(sql)) {
|
|
721
|
+
await adapter.execute(statement);
|
|
722
|
+
}
|
|
723
|
+
},
|
|
724
|
+
async sync() {
|
|
725
|
+
if (fallbackClient) {
|
|
726
|
+
return fallbackClient.sync();
|
|
727
|
+
}
|
|
728
|
+
return { frame_no: 0, frames_synced: 0 };
|
|
729
|
+
},
|
|
730
|
+
close() {
|
|
731
|
+
closed = true;
|
|
732
|
+
prismaClientPromise = null;
|
|
733
|
+
compatibilityBootstrapPromise = null;
|
|
734
|
+
void prisma.$disconnect?.();
|
|
735
|
+
},
|
|
736
|
+
get closed() {
|
|
737
|
+
return closed;
|
|
738
|
+
},
|
|
739
|
+
get protocol() {
|
|
740
|
+
return "prisma-postgres";
|
|
741
|
+
}
|
|
742
|
+
};
|
|
743
|
+
return adapter;
|
|
744
|
+
}
|
|
745
|
+
var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
|
|
746
|
+
var init_database_adapter = __esm({
|
|
747
|
+
"src/lib/database-adapter.ts"() {
|
|
748
|
+
"use strict";
|
|
749
|
+
VIEW_MAPPINGS = [
|
|
750
|
+
{ view: "memories", source: "memory.memory_records" },
|
|
751
|
+
{ view: "tasks", source: "memory.tasks" },
|
|
752
|
+
{ view: "behaviors", source: "memory.behaviors" },
|
|
753
|
+
{ view: "entities", source: "memory.entities" },
|
|
754
|
+
{ view: "relationships", source: "memory.relationships" },
|
|
755
|
+
{ view: "entity_memories", source: "memory.entity_memories" },
|
|
756
|
+
{ view: "entity_aliases", source: "memory.entity_aliases" },
|
|
757
|
+
{ view: "notifications", source: "memory.notifications" },
|
|
758
|
+
{ view: "messages", source: "memory.messages" },
|
|
759
|
+
{ view: "users", source: "wiki.users" },
|
|
760
|
+
{ view: "workspaces", source: "wiki.workspaces" },
|
|
761
|
+
{ view: "workspace_users", source: "wiki.workspace_users" },
|
|
762
|
+
{ view: "documents", source: "wiki.workspace_documents" },
|
|
763
|
+
{ view: "chats", source: "wiki.workspace_chats" }
|
|
764
|
+
];
|
|
765
|
+
UPSERT_KEYS = {
|
|
766
|
+
memories: ["id"],
|
|
767
|
+
tasks: ["id"],
|
|
768
|
+
behaviors: ["id"],
|
|
769
|
+
entities: ["id"],
|
|
770
|
+
relationships: ["id"],
|
|
771
|
+
entity_aliases: ["alias"],
|
|
772
|
+
notifications: ["id"],
|
|
773
|
+
messages: ["id"],
|
|
774
|
+
users: ["id"],
|
|
775
|
+
workspaces: ["id"],
|
|
776
|
+
workspace_users: ["id"],
|
|
777
|
+
documents: ["id"],
|
|
778
|
+
chats: ["id"]
|
|
779
|
+
};
|
|
780
|
+
BOOLEAN_COLUMNS_BY_TABLE = {
|
|
781
|
+
memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
|
|
782
|
+
behaviors: /* @__PURE__ */ new Set(["active"]),
|
|
783
|
+
notifications: /* @__PURE__ */ new Set(["read"]),
|
|
784
|
+
users: /* @__PURE__ */ new Set(["has_personal_memory"])
|
|
785
|
+
};
|
|
786
|
+
BOOLEAN_COLUMN_NAMES = new Set(
|
|
787
|
+
Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
|
|
788
|
+
);
|
|
789
|
+
IMMEDIATE_FALLBACK_PATTERNS = [
|
|
790
|
+
/\bPRAGMA\b/i,
|
|
791
|
+
/\bsqlite_master\b/i,
|
|
792
|
+
/(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
|
|
793
|
+
/\bMATCH\b/i,
|
|
794
|
+
/\bvector_distance_cos\s*\(/i,
|
|
795
|
+
/\bjson_extract\s*\(/i,
|
|
796
|
+
/\bjulianday\s*\(/i,
|
|
797
|
+
/\bstrftime\s*\(/i,
|
|
798
|
+
/\blast_insert_rowid\s*\(/i
|
|
799
|
+
];
|
|
800
|
+
prismaClientPromise = null;
|
|
801
|
+
compatibilityBootstrapPromise = null;
|
|
802
|
+
}
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
// src/lib/daemon-auth.ts
|
|
806
|
+
import crypto from "crypto";
|
|
807
|
+
import path4 from "path";
|
|
808
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
809
|
+
function normalizeToken(token) {
|
|
810
|
+
if (!token) return null;
|
|
811
|
+
const trimmed = token.trim();
|
|
812
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
813
|
+
}
|
|
814
|
+
function readDaemonToken() {
|
|
815
|
+
try {
|
|
816
|
+
if (!existsSync4(DAEMON_TOKEN_PATH)) return null;
|
|
817
|
+
return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
|
|
818
|
+
} catch {
|
|
819
|
+
return null;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
function ensureDaemonToken(seed) {
|
|
823
|
+
const existing = readDaemonToken();
|
|
824
|
+
if (existing) return existing;
|
|
825
|
+
const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
|
|
826
|
+
ensurePrivateDirSync(EXE_AI_DIR);
|
|
827
|
+
writeFileSync2(DAEMON_TOKEN_PATH, `${token}
|
|
828
|
+
`, "utf8");
|
|
829
|
+
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
830
|
+
return token;
|
|
831
|
+
}
|
|
832
|
+
var DAEMON_TOKEN_PATH;
|
|
833
|
+
var init_daemon_auth = __esm({
|
|
834
|
+
"src/lib/daemon-auth.ts"() {
|
|
835
|
+
"use strict";
|
|
836
|
+
init_config();
|
|
837
|
+
init_secure_files();
|
|
838
|
+
DAEMON_TOKEN_PATH = path4.join(EXE_AI_DIR, "exed.token");
|
|
191
839
|
}
|
|
192
840
|
});
|
|
193
841
|
|
|
194
842
|
// src/lib/exe-daemon-client.ts
|
|
195
843
|
import net from "net";
|
|
196
|
-
import
|
|
844
|
+
import os4 from "os";
|
|
197
845
|
import { spawn } from "child_process";
|
|
198
846
|
import { randomUUID } from "crypto";
|
|
199
|
-
import { existsSync as
|
|
200
|
-
import
|
|
847
|
+
import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
|
|
848
|
+
import path5 from "path";
|
|
201
849
|
import { fileURLToPath } from "url";
|
|
202
850
|
function handleData(chunk) {
|
|
203
851
|
_buffer += chunk.toString();
|
|
@@ -225,9 +873,9 @@ function handleData(chunk) {
|
|
|
225
873
|
}
|
|
226
874
|
}
|
|
227
875
|
function cleanupStaleFiles() {
|
|
228
|
-
if (
|
|
876
|
+
if (existsSync5(PID_PATH)) {
|
|
229
877
|
try {
|
|
230
|
-
const pid = parseInt(
|
|
878
|
+
const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
|
|
231
879
|
if (pid > 0) {
|
|
232
880
|
try {
|
|
233
881
|
process.kill(pid, 0);
|
|
@@ -248,17 +896,17 @@ function cleanupStaleFiles() {
|
|
|
248
896
|
}
|
|
249
897
|
}
|
|
250
898
|
function findPackageRoot() {
|
|
251
|
-
let dir =
|
|
252
|
-
const { root } =
|
|
899
|
+
let dir = path5.dirname(fileURLToPath(import.meta.url));
|
|
900
|
+
const { root } = path5.parse(dir);
|
|
253
901
|
while (dir !== root) {
|
|
254
|
-
if (
|
|
255
|
-
dir =
|
|
902
|
+
if (existsSync5(path5.join(dir, "package.json"))) return dir;
|
|
903
|
+
dir = path5.dirname(dir);
|
|
256
904
|
}
|
|
257
905
|
return null;
|
|
258
906
|
}
|
|
259
907
|
function spawnDaemon() {
|
|
260
|
-
const freeGB =
|
|
261
|
-
const totalGB =
|
|
908
|
+
const freeGB = os4.freemem() / (1024 * 1024 * 1024);
|
|
909
|
+
const totalGB = os4.totalmem() / (1024 * 1024 * 1024);
|
|
262
910
|
if (totalGB <= 8) {
|
|
263
911
|
process.stderr.write(
|
|
264
912
|
`[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
|
|
@@ -278,16 +926,17 @@ function spawnDaemon() {
|
|
|
278
926
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
279
927
|
return;
|
|
280
928
|
}
|
|
281
|
-
const daemonPath =
|
|
282
|
-
if (!
|
|
929
|
+
const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
930
|
+
if (!existsSync5(daemonPath)) {
|
|
283
931
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
284
932
|
`);
|
|
285
933
|
return;
|
|
286
934
|
}
|
|
287
935
|
const resolvedPath = daemonPath;
|
|
936
|
+
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
288
937
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
289
938
|
`);
|
|
290
|
-
const logPath =
|
|
939
|
+
const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
|
|
291
940
|
let stderrFd = "ignore";
|
|
292
941
|
try {
|
|
293
942
|
stderrFd = openSync(logPath, "a");
|
|
@@ -305,7 +954,8 @@ function spawnDaemon() {
|
|
|
305
954
|
TMUX_PANE: void 0,
|
|
306
955
|
// Prevents resolveExeSession() from scoping to one session
|
|
307
956
|
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
308
|
-
EXE_DAEMON_PID: PID_PATH
|
|
957
|
+
EXE_DAEMON_PID: PID_PATH,
|
|
958
|
+
[DAEMON_TOKEN_ENV]: daemonToken
|
|
309
959
|
}
|
|
310
960
|
});
|
|
311
961
|
child.unref();
|
|
@@ -412,13 +1062,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
412
1062
|
return;
|
|
413
1063
|
}
|
|
414
1064
|
const id = randomUUID();
|
|
1065
|
+
const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
|
|
415
1066
|
const timer = setTimeout(() => {
|
|
416
1067
|
_pending.delete(id);
|
|
417
1068
|
resolve({ error: "Request timeout" });
|
|
418
1069
|
}, timeoutMs);
|
|
419
1070
|
_pending.set(id, { resolve, timer });
|
|
420
1071
|
try {
|
|
421
|
-
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
1072
|
+
_socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
|
|
422
1073
|
} catch {
|
|
423
1074
|
clearTimeout(timer);
|
|
424
1075
|
_pending.delete(id);
|
|
@@ -429,17 +1080,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
429
1080
|
function isClientConnected() {
|
|
430
1081
|
return _connected;
|
|
431
1082
|
}
|
|
432
|
-
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
|
|
1083
|
+
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;
|
|
433
1084
|
var init_exe_daemon_client = __esm({
|
|
434
1085
|
"src/lib/exe-daemon-client.ts"() {
|
|
435
1086
|
"use strict";
|
|
436
1087
|
init_config();
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
1088
|
+
init_daemon_auth();
|
|
1089
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
|
|
1090
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
|
|
1091
|
+
SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
440
1092
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
441
1093
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
442
1094
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
1095
|
+
DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
|
|
443
1096
|
_socket = null;
|
|
444
1097
|
_connected = false;
|
|
445
1098
|
_buffer = "";
|
|
@@ -518,7 +1171,7 @@ __export(db_daemon_client_exports, {
|
|
|
518
1171
|
createDaemonDbClient: () => createDaemonDbClient,
|
|
519
1172
|
initDaemonDbClient: () => initDaemonDbClient
|
|
520
1173
|
});
|
|
521
|
-
function
|
|
1174
|
+
function normalizeStatement2(stmt) {
|
|
522
1175
|
if (typeof stmt === "string") {
|
|
523
1176
|
return { sql: stmt, args: [] };
|
|
524
1177
|
}
|
|
@@ -542,7 +1195,7 @@ function createDaemonDbClient(fallbackClient) {
|
|
|
542
1195
|
if (!_useDaemon || !isClientConnected()) {
|
|
543
1196
|
return fallbackClient.execute(stmt);
|
|
544
1197
|
}
|
|
545
|
-
const { sql, args } =
|
|
1198
|
+
const { sql, args } = normalizeStatement2(stmt);
|
|
546
1199
|
const response = await sendDaemonRequest({
|
|
547
1200
|
type: "db-execute",
|
|
548
1201
|
sql,
|
|
@@ -567,7 +1220,7 @@ function createDaemonDbClient(fallbackClient) {
|
|
|
567
1220
|
if (!_useDaemon || !isClientConnected()) {
|
|
568
1221
|
return fallbackClient.batch(stmts, mode);
|
|
569
1222
|
}
|
|
570
|
-
const statements = stmts.map(
|
|
1223
|
+
const statements = stmts.map(normalizeStatement2);
|
|
571
1224
|
const response = await sendDaemonRequest({
|
|
572
1225
|
type: "db-batch",
|
|
573
1226
|
statements,
|
|
@@ -662,6 +1315,18 @@ __export(database_exports, {
|
|
|
662
1315
|
});
|
|
663
1316
|
import { createClient } from "@libsql/client";
|
|
664
1317
|
async function initDatabase(config) {
|
|
1318
|
+
if (_walCheckpointTimer) {
|
|
1319
|
+
clearInterval(_walCheckpointTimer);
|
|
1320
|
+
_walCheckpointTimer = null;
|
|
1321
|
+
}
|
|
1322
|
+
if (_daemonClient) {
|
|
1323
|
+
_daemonClient.close();
|
|
1324
|
+
_daemonClient = null;
|
|
1325
|
+
}
|
|
1326
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
1327
|
+
_adapterClient.close();
|
|
1328
|
+
}
|
|
1329
|
+
_adapterClient = null;
|
|
665
1330
|
if (_client) {
|
|
666
1331
|
_client.close();
|
|
667
1332
|
_client = null;
|
|
@@ -675,6 +1340,7 @@ async function initDatabase(config) {
|
|
|
675
1340
|
}
|
|
676
1341
|
_client = createClient(opts);
|
|
677
1342
|
_resilientClient = wrapWithRetry(_client);
|
|
1343
|
+
_adapterClient = _resilientClient;
|
|
678
1344
|
_client.execute("PRAGMA busy_timeout = 30000").catch(() => {
|
|
679
1345
|
});
|
|
680
1346
|
_client.execute("PRAGMA journal_mode = WAL").catch(() => {
|
|
@@ -685,14 +1351,20 @@ async function initDatabase(config) {
|
|
|
685
1351
|
});
|
|
686
1352
|
}, 3e4);
|
|
687
1353
|
_walCheckpointTimer.unref();
|
|
1354
|
+
if (process.env.DATABASE_URL) {
|
|
1355
|
+
_adapterClient = await createPrismaDbAdapter(_resilientClient);
|
|
1356
|
+
}
|
|
688
1357
|
}
|
|
689
1358
|
function isInitialized() {
|
|
690
|
-
return _client !== null;
|
|
1359
|
+
return _adapterClient !== null || _client !== null;
|
|
691
1360
|
}
|
|
692
1361
|
function getClient() {
|
|
693
|
-
if (!
|
|
1362
|
+
if (!_adapterClient) {
|
|
694
1363
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
695
1364
|
}
|
|
1365
|
+
if (process.env.DATABASE_URL) {
|
|
1366
|
+
return _adapterClient;
|
|
1367
|
+
}
|
|
696
1368
|
if (process.env.EXE_IS_DAEMON === "1") {
|
|
697
1369
|
return _resilientClient;
|
|
698
1370
|
}
|
|
@@ -702,6 +1374,7 @@ function getClient() {
|
|
|
702
1374
|
return _resilientClient;
|
|
703
1375
|
}
|
|
704
1376
|
async function initDaemonClient() {
|
|
1377
|
+
if (process.env.DATABASE_URL) return;
|
|
705
1378
|
if (process.env.EXE_IS_DAEMON === "1") return;
|
|
706
1379
|
if (!_resilientClient) return;
|
|
707
1380
|
try {
|
|
@@ -998,6 +1671,7 @@ async function ensureSchema() {
|
|
|
998
1671
|
project TEXT NOT NULL,
|
|
999
1672
|
summary TEXT NOT NULL,
|
|
1000
1673
|
task_file TEXT,
|
|
1674
|
+
session_scope TEXT,
|
|
1001
1675
|
read INTEGER NOT NULL DEFAULT 0,
|
|
1002
1676
|
created_at TEXT NOT NULL
|
|
1003
1677
|
);
|
|
@@ -1006,7 +1680,7 @@ async function ensureSchema() {
|
|
|
1006
1680
|
ON notifications(read);
|
|
1007
1681
|
|
|
1008
1682
|
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
1009
|
-
ON notifications(agent_id);
|
|
1683
|
+
ON notifications(agent_id, session_scope);
|
|
1010
1684
|
|
|
1011
1685
|
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
1012
1686
|
ON notifications(task_file);
|
|
@@ -1044,6 +1718,7 @@ async function ensureSchema() {
|
|
|
1044
1718
|
target_agent TEXT NOT NULL,
|
|
1045
1719
|
target_project TEXT,
|
|
1046
1720
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
1721
|
+
session_scope TEXT,
|
|
1047
1722
|
content TEXT NOT NULL,
|
|
1048
1723
|
priority TEXT DEFAULT 'normal',
|
|
1049
1724
|
status TEXT DEFAULT 'pending',
|
|
@@ -1057,10 +1732,31 @@ async function ensureSchema() {
|
|
|
1057
1732
|
);
|
|
1058
1733
|
|
|
1059
1734
|
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
1060
|
-
ON messages(target_agent, status);
|
|
1735
|
+
ON messages(target_agent, session_scope, status);
|
|
1061
1736
|
|
|
1062
1737
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
1063
|
-
ON messages(target_agent, from_agent, server_seq);
|
|
1738
|
+
ON messages(target_agent, session_scope, from_agent, server_seq);
|
|
1739
|
+
`);
|
|
1740
|
+
try {
|
|
1741
|
+
await client.execute({
|
|
1742
|
+
sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
|
|
1743
|
+
args: []
|
|
1744
|
+
});
|
|
1745
|
+
} catch {
|
|
1746
|
+
}
|
|
1747
|
+
try {
|
|
1748
|
+
await client.execute({
|
|
1749
|
+
sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
|
|
1750
|
+
args: []
|
|
1751
|
+
});
|
|
1752
|
+
} catch {
|
|
1753
|
+
}
|
|
1754
|
+
await client.executeMultiple(`
|
|
1755
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
|
|
1756
|
+
ON notifications(agent_id, session_scope, read, created_at);
|
|
1757
|
+
|
|
1758
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
|
|
1759
|
+
ON messages(target_agent, session_scope, status, created_at);
|
|
1064
1760
|
`);
|
|
1065
1761
|
try {
|
|
1066
1762
|
await client.execute({
|
|
@@ -1644,28 +2340,45 @@ async function ensureSchema() {
|
|
|
1644
2340
|
} catch {
|
|
1645
2341
|
}
|
|
1646
2342
|
}
|
|
2343
|
+
try {
|
|
2344
|
+
await client.execute({
|
|
2345
|
+
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
2346
|
+
args: []
|
|
2347
|
+
});
|
|
2348
|
+
} catch {
|
|
2349
|
+
}
|
|
1647
2350
|
}
|
|
1648
2351
|
async function disposeDatabase() {
|
|
2352
|
+
if (_walCheckpointTimer) {
|
|
2353
|
+
clearInterval(_walCheckpointTimer);
|
|
2354
|
+
_walCheckpointTimer = null;
|
|
2355
|
+
}
|
|
1649
2356
|
if (_daemonClient) {
|
|
1650
2357
|
_daemonClient.close();
|
|
1651
2358
|
_daemonClient = null;
|
|
1652
2359
|
}
|
|
2360
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
2361
|
+
_adapterClient.close();
|
|
2362
|
+
}
|
|
2363
|
+
_adapterClient = null;
|
|
1653
2364
|
if (_client) {
|
|
1654
2365
|
_client.close();
|
|
1655
2366
|
_client = null;
|
|
1656
2367
|
_resilientClient = null;
|
|
1657
2368
|
}
|
|
1658
2369
|
}
|
|
1659
|
-
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
|
|
2370
|
+
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
|
|
1660
2371
|
var init_database = __esm({
|
|
1661
2372
|
"src/lib/database.ts"() {
|
|
1662
2373
|
"use strict";
|
|
1663
2374
|
init_db_retry();
|
|
1664
2375
|
init_employees();
|
|
2376
|
+
init_database_adapter();
|
|
1665
2377
|
_client = null;
|
|
1666
2378
|
_resilientClient = null;
|
|
1667
2379
|
_walCheckpointTimer = null;
|
|
1668
2380
|
_daemonClient = null;
|
|
2381
|
+
_adapterClient = null;
|
|
1669
2382
|
initTurso = initDatabase;
|
|
1670
2383
|
disposeTurso = disposeDatabase;
|
|
1671
2384
|
}
|
|
@@ -1673,15 +2386,15 @@ var init_database = __esm({
|
|
|
1673
2386
|
|
|
1674
2387
|
// src/lib/device-registry.ts
|
|
1675
2388
|
init_config();
|
|
1676
|
-
import
|
|
1677
|
-
import
|
|
1678
|
-
import { readFileSync as
|
|
1679
|
-
import
|
|
1680
|
-
var DEVICE_JSON_PATH =
|
|
2389
|
+
import crypto2 from "crypto";
|
|
2390
|
+
import os5 from "os";
|
|
2391
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync6 } from "fs";
|
|
2392
|
+
import path6 from "path";
|
|
2393
|
+
var DEVICE_JSON_PATH = path6.join(EXE_AI_DIR, "device.json");
|
|
1681
2394
|
function getDeviceInfo() {
|
|
1682
|
-
if (
|
|
2395
|
+
if (existsSync6(DEVICE_JSON_PATH)) {
|
|
1683
2396
|
try {
|
|
1684
|
-
const raw =
|
|
2397
|
+
const raw = readFileSync5(DEVICE_JSON_PATH, "utf8");
|
|
1685
2398
|
const data = JSON.parse(raw);
|
|
1686
2399
|
if (data.deviceId && data.friendlyName && data.hostname) {
|
|
1687
2400
|
return data;
|
|
@@ -1689,20 +2402,20 @@ function getDeviceInfo() {
|
|
|
1689
2402
|
} catch {
|
|
1690
2403
|
}
|
|
1691
2404
|
}
|
|
1692
|
-
const hostname =
|
|
2405
|
+
const hostname = os5.hostname();
|
|
1693
2406
|
const info = {
|
|
1694
|
-
deviceId:
|
|
2407
|
+
deviceId: crypto2.randomUUID(),
|
|
1695
2408
|
friendlyName: hostname.replace(/\./g, "-").toLowerCase(),
|
|
1696
2409
|
hostname
|
|
1697
2410
|
};
|
|
1698
|
-
|
|
1699
|
-
|
|
2411
|
+
mkdirSync2(path6.dirname(DEVICE_JSON_PATH), { recursive: true });
|
|
2412
|
+
writeFileSync3(DEVICE_JSON_PATH, JSON.stringify(info, null, 2));
|
|
1700
2413
|
return info;
|
|
1701
2414
|
}
|
|
1702
2415
|
function setFriendlyName(name) {
|
|
1703
2416
|
const info = getDeviceInfo();
|
|
1704
2417
|
info.friendlyName = name;
|
|
1705
|
-
|
|
2418
|
+
writeFileSync3(DEVICE_JSON_PATH, JSON.stringify(info, null, 2));
|
|
1706
2419
|
}
|
|
1707
2420
|
async function resolveTargetDevice(targetAgent, targetProject) {
|
|
1708
2421
|
const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|