@askexenow/exe-os 0.9.7 → 0.9.8
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 +754 -79
- package/dist/bin/backfill-responses.js +752 -77
- package/dist/bin/backfill-vectors.js +752 -77
- package/dist/bin/cleanup-stale-review-tasks.js +657 -35
- package/dist/bin/cli.js +1388 -605
- package/dist/bin/exe-agent-config.js +123 -95
- package/dist/bin/exe-agent.js +41 -25
- package/dist/bin/exe-assign.js +732 -57
- package/dist/bin/exe-boot.js +784 -153
- package/dist/bin/exe-call.js +209 -138
- package/dist/bin/exe-cloud.js +35 -12
- package/dist/bin/exe-dispatch.js +692 -70
- package/dist/bin/exe-doctor.js +648 -26
- package/dist/bin/exe-export-behaviors.js +650 -20
- package/dist/bin/exe-forget.js +635 -13
- package/dist/bin/exe-gateway.js +1053 -271
- package/dist/bin/exe-heartbeat.js +665 -43
- package/dist/bin/exe-kill.js +646 -16
- package/dist/bin/exe-launch-agent.js +887 -97
- package/dist/bin/exe-link.js +658 -43
- package/dist/bin/exe-new-employee.js +378 -177
- package/dist/bin/exe-pending-messages.js +656 -34
- package/dist/bin/exe-pending-notifications.js +635 -13
- package/dist/bin/exe-pending-reviews.js +659 -37
- package/dist/bin/exe-rename.js +645 -30
- package/dist/bin/exe-review.js +635 -13
- package/dist/bin/exe-search.js +771 -88
- package/dist/bin/exe-session-cleanup.js +834 -150
- package/dist/bin/exe-settings.js +127 -91
- package/dist/bin/exe-start-codex.js +729 -94
- package/dist/bin/exe-start-opencode.js +717 -82
- package/dist/bin/exe-status.js +657 -35
- package/dist/bin/exe-team.js +635 -13
- package/dist/bin/git-sweep.js +720 -89
- package/dist/bin/graph-backfill.js +643 -13
- package/dist/bin/graph-export.js +646 -16
- package/dist/bin/install.js +596 -193
- package/dist/bin/scan-tasks.js +724 -93
- package/dist/bin/setup.js +1038 -210
- package/dist/bin/shard-migrate.js +645 -15
- package/dist/bin/wiki-sync.js +646 -16
- package/dist/gateway/index.js +1027 -245
- package/dist/hooks/bug-report-worker.js +891 -170
- package/dist/hooks/commit-complete.js +718 -87
- package/dist/hooks/error-recall.js +776 -93
- package/dist/hooks/exe-heartbeat-hook.js +85 -71
- package/dist/hooks/ingest-worker.js +840 -156
- package/dist/hooks/ingest.js +90 -73
- package/dist/hooks/instructions-loaded.js +669 -38
- package/dist/hooks/notification.js +661 -30
- package/dist/hooks/post-compact.js +674 -43
- package/dist/hooks/pre-compact.js +718 -87
- package/dist/hooks/pre-tool-use.js +872 -125
- package/dist/hooks/prompt-ingest-worker.js +758 -83
- package/dist/hooks/prompt-submit.js +1060 -319
- package/dist/hooks/response-ingest-worker.js +758 -83
- package/dist/hooks/session-end.js +721 -90
- package/dist/hooks/session-start.js +1031 -207
- package/dist/hooks/stop.js +680 -49
- package/dist/hooks/subagent-stop.js +674 -43
- package/dist/hooks/summary-worker.js +816 -132
- package/dist/index.js +1015 -232
- package/dist/lib/cloud-sync.js +663 -48
- package/dist/lib/consolidation.js +26 -3
- package/dist/lib/database.js +626 -18
- package/dist/lib/db.js +2261 -0
- package/dist/lib/device-registry.js +640 -25
- package/dist/lib/embedder.js +96 -43
- package/dist/lib/employee-templates.js +16 -0
- package/dist/lib/employees.js +259 -83
- package/dist/lib/exe-daemon-client.js +101 -63
- package/dist/lib/exe-daemon.js +894 -162
- package/dist/lib/hybrid-search.js +771 -88
- package/dist/lib/identity.js +27 -7
- package/dist/lib/messaging.js +55 -28
- package/dist/lib/reminders.js +21 -1
- package/dist/lib/schedules.js +636 -14
- package/dist/lib/skill-learning.js +21 -1
- package/dist/lib/store.js +643 -13
- package/dist/lib/task-router.js +82 -71
- package/dist/lib/tasks.js +98 -71
- package/dist/lib/tmux-routing.js +87 -60
- package/dist/lib/token-spend.js +26 -6
- package/dist/mcp/server.js +1784 -458
- package/dist/mcp/tools/complete-reminder.js +21 -1
- package/dist/mcp/tools/create-reminder.js +21 -1
- package/dist/mcp/tools/create-task.js +290 -164
- package/dist/mcp/tools/deactivate-behavior.js +24 -4
- package/dist/mcp/tools/list-reminders.js +21 -1
- package/dist/mcp/tools/list-tasks.js +195 -38
- package/dist/mcp/tools/send-message.js +58 -31
- package/dist/mcp/tools/update-task.js +75 -48
- package/dist/runtime/index.js +720 -89
- package/dist/tui/App.js +853 -123
- package/package.json +3 -2
package/dist/lib/cloud-sync.js
CHANGED
|
@@ -235,7 +235,7 @@ function registerBinSymlinks(name) {
|
|
|
235
235
|
}
|
|
236
236
|
return { created, skipped, errors };
|
|
237
237
|
}
|
|
238
|
-
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE;
|
|
238
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, IDENTITY_DIR;
|
|
239
239
|
var init_employees = __esm({
|
|
240
240
|
"src/lib/employees.ts"() {
|
|
241
241
|
"use strict";
|
|
@@ -243,16 +243,601 @@ var init_employees = __esm({
|
|
|
243
243
|
EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
|
|
244
244
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
245
245
|
COORDINATOR_ROLE = "COO";
|
|
246
|
+
IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// src/lib/database-adapter.ts
|
|
251
|
+
import os3 from "os";
|
|
252
|
+
import path3 from "path";
|
|
253
|
+
import { createRequire } from "module";
|
|
254
|
+
import { pathToFileURL } from "url";
|
|
255
|
+
function quotedIdentifier(identifier) {
|
|
256
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
257
|
+
}
|
|
258
|
+
function unqualifiedTableName(name) {
|
|
259
|
+
const raw = name.trim().replace(/^"|"$/g, "");
|
|
260
|
+
const parts = raw.split(".");
|
|
261
|
+
return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
|
|
262
|
+
}
|
|
263
|
+
function stripTrailingSemicolon(sql) {
|
|
264
|
+
return sql.trim().replace(/;+\s*$/u, "");
|
|
265
|
+
}
|
|
266
|
+
function appendClause(sql, clause) {
|
|
267
|
+
const trimmed = stripTrailingSemicolon(sql);
|
|
268
|
+
const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
|
|
269
|
+
if (!returningMatch) {
|
|
270
|
+
return `${trimmed}${clause}`;
|
|
271
|
+
}
|
|
272
|
+
const idx = returningMatch.index;
|
|
273
|
+
return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
|
|
274
|
+
}
|
|
275
|
+
function normalizeStatement(stmt) {
|
|
276
|
+
if (typeof stmt === "string") {
|
|
277
|
+
return { kind: "positional", sql: stmt, args: [] };
|
|
278
|
+
}
|
|
279
|
+
const sql = stmt.sql;
|
|
280
|
+
if (Array.isArray(stmt.args) || stmt.args === void 0) {
|
|
281
|
+
return { kind: "positional", sql, args: stmt.args ?? [] };
|
|
282
|
+
}
|
|
283
|
+
return { kind: "named", sql, args: stmt.args };
|
|
284
|
+
}
|
|
285
|
+
function rewriteBooleanLiterals(sql) {
|
|
286
|
+
let out = sql;
|
|
287
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
288
|
+
const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
|
|
289
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
|
|
290
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
|
|
291
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
|
|
292
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
|
|
293
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
|
|
294
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
|
|
295
|
+
}
|
|
296
|
+
return out;
|
|
297
|
+
}
|
|
298
|
+
function rewriteInsertOrIgnore(sql) {
|
|
299
|
+
if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
|
|
300
|
+
return sql;
|
|
301
|
+
}
|
|
302
|
+
const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
|
|
303
|
+
return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
|
|
304
|
+
}
|
|
305
|
+
function rewriteInsertOrReplace(sql) {
|
|
306
|
+
const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
|
|
307
|
+
if (!match) {
|
|
308
|
+
return sql;
|
|
309
|
+
}
|
|
310
|
+
const rawTable = match[1];
|
|
311
|
+
const rawColumns = match[2];
|
|
312
|
+
const remainder = match[3];
|
|
313
|
+
const tableName = unqualifiedTableName(rawTable);
|
|
314
|
+
const conflictKeys = UPSERT_KEYS[tableName];
|
|
315
|
+
if (!conflictKeys?.length) {
|
|
316
|
+
return sql;
|
|
317
|
+
}
|
|
318
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
319
|
+
const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
|
|
320
|
+
const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
|
|
321
|
+
const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
|
|
322
|
+
return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
|
|
323
|
+
}
|
|
324
|
+
function rewriteSql(sql) {
|
|
325
|
+
let out = sql;
|
|
326
|
+
out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
|
|
327
|
+
out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
|
|
328
|
+
out = rewriteBooleanLiterals(out);
|
|
329
|
+
out = rewriteInsertOrReplace(out);
|
|
330
|
+
out = rewriteInsertOrIgnore(out);
|
|
331
|
+
return stripTrailingSemicolon(out);
|
|
332
|
+
}
|
|
333
|
+
function toBoolean(value) {
|
|
334
|
+
if (value === null || value === void 0) return value;
|
|
335
|
+
if (typeof value === "boolean") return value;
|
|
336
|
+
if (typeof value === "number") return value !== 0;
|
|
337
|
+
if (typeof value === "bigint") return value !== 0n;
|
|
338
|
+
if (typeof value === "string") {
|
|
339
|
+
const normalized = value.trim().toLowerCase();
|
|
340
|
+
if (normalized === "0" || normalized === "false") return false;
|
|
341
|
+
if (normalized === "1" || normalized === "true") return true;
|
|
342
|
+
}
|
|
343
|
+
return Boolean(value);
|
|
344
|
+
}
|
|
345
|
+
function countQuestionMarks(sql, end) {
|
|
346
|
+
let count = 0;
|
|
347
|
+
let inSingle = false;
|
|
348
|
+
let inDouble = false;
|
|
349
|
+
let inLineComment = false;
|
|
350
|
+
let inBlockComment = false;
|
|
351
|
+
for (let i = 0; i < end; i++) {
|
|
352
|
+
const ch = sql[i];
|
|
353
|
+
const next = sql[i + 1];
|
|
354
|
+
if (inLineComment) {
|
|
355
|
+
if (ch === "\n") inLineComment = false;
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
if (inBlockComment) {
|
|
359
|
+
if (ch === "*" && next === "/") {
|
|
360
|
+
inBlockComment = false;
|
|
361
|
+
i += 1;
|
|
362
|
+
}
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
366
|
+
inLineComment = true;
|
|
367
|
+
i += 1;
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
371
|
+
inBlockComment = true;
|
|
372
|
+
i += 1;
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
376
|
+
inSingle = !inSingle;
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
380
|
+
inDouble = !inDouble;
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
384
|
+
count += 1;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return count;
|
|
388
|
+
}
|
|
389
|
+
function findBooleanPlaceholderIndexes(sql) {
|
|
390
|
+
const indexes = /* @__PURE__ */ new Set();
|
|
391
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
392
|
+
const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
|
|
393
|
+
for (const match of sql.matchAll(pattern)) {
|
|
394
|
+
const matchText = match[0];
|
|
395
|
+
const qIndex = match.index + matchText.lastIndexOf("?");
|
|
396
|
+
indexes.add(countQuestionMarks(sql, qIndex + 1));
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return indexes;
|
|
400
|
+
}
|
|
401
|
+
function coerceInsertBooleanArgs(sql, args) {
|
|
402
|
+
const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
|
|
403
|
+
if (!match) return;
|
|
404
|
+
const rawTable = match[1];
|
|
405
|
+
const rawColumns = match[2];
|
|
406
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
407
|
+
if (!boolColumns?.size) return;
|
|
408
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
409
|
+
for (const [index, column] of columns.entries()) {
|
|
410
|
+
if (boolColumns.has(column) && index < args.length) {
|
|
411
|
+
args[index] = toBoolean(args[index]);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
function coerceUpdateBooleanArgs(sql, args) {
|
|
416
|
+
const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
|
|
417
|
+
if (!match) return;
|
|
418
|
+
const rawTable = match[1];
|
|
419
|
+
const setClause = match[2];
|
|
420
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
421
|
+
if (!boolColumns?.size) return;
|
|
422
|
+
const assignments = setClause.split(",");
|
|
423
|
+
let placeholderIndex = 0;
|
|
424
|
+
for (const assignment of assignments) {
|
|
425
|
+
if (!assignment.includes("?")) continue;
|
|
426
|
+
placeholderIndex += 1;
|
|
427
|
+
const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
|
|
428
|
+
if (colMatch && boolColumns.has(colMatch[1])) {
|
|
429
|
+
args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
function coerceBooleanArgs(sql, args) {
|
|
434
|
+
const nextArgs = [...args];
|
|
435
|
+
coerceInsertBooleanArgs(sql, nextArgs);
|
|
436
|
+
coerceUpdateBooleanArgs(sql, nextArgs);
|
|
437
|
+
const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
|
|
438
|
+
for (const index of placeholderIndexes) {
|
|
439
|
+
if (index > 0 && index <= nextArgs.length) {
|
|
440
|
+
nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
return nextArgs;
|
|
444
|
+
}
|
|
445
|
+
function convertQuestionMarksToDollarParams(sql) {
|
|
446
|
+
let out = "";
|
|
447
|
+
let placeholder = 0;
|
|
448
|
+
let inSingle = false;
|
|
449
|
+
let inDouble = false;
|
|
450
|
+
let inLineComment = false;
|
|
451
|
+
let inBlockComment = false;
|
|
452
|
+
for (let i = 0; i < sql.length; i++) {
|
|
453
|
+
const ch = sql[i];
|
|
454
|
+
const next = sql[i + 1];
|
|
455
|
+
if (inLineComment) {
|
|
456
|
+
out += ch;
|
|
457
|
+
if (ch === "\n") inLineComment = false;
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
if (inBlockComment) {
|
|
461
|
+
out += ch;
|
|
462
|
+
if (ch === "*" && next === "/") {
|
|
463
|
+
out += next;
|
|
464
|
+
inBlockComment = false;
|
|
465
|
+
i += 1;
|
|
466
|
+
}
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
470
|
+
out += ch + next;
|
|
471
|
+
inLineComment = true;
|
|
472
|
+
i += 1;
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
476
|
+
out += ch + next;
|
|
477
|
+
inBlockComment = true;
|
|
478
|
+
i += 1;
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
482
|
+
inSingle = !inSingle;
|
|
483
|
+
out += ch;
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
487
|
+
inDouble = !inDouble;
|
|
488
|
+
out += ch;
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
492
|
+
placeholder += 1;
|
|
493
|
+
out += `$${placeholder}`;
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
out += ch;
|
|
497
|
+
}
|
|
498
|
+
return out;
|
|
499
|
+
}
|
|
500
|
+
function translateStatementForPostgres(stmt) {
|
|
501
|
+
const normalized = normalizeStatement(stmt);
|
|
502
|
+
if (normalized.kind === "named") {
|
|
503
|
+
throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
|
|
504
|
+
}
|
|
505
|
+
const rewrittenSql = rewriteSql(normalized.sql);
|
|
506
|
+
const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
|
|
507
|
+
return {
|
|
508
|
+
sql: convertQuestionMarksToDollarParams(rewrittenSql),
|
|
509
|
+
args: coercedArgs
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
function shouldBypassPostgres(stmt) {
|
|
513
|
+
const normalized = normalizeStatement(stmt);
|
|
514
|
+
if (normalized.kind === "named") {
|
|
515
|
+
return true;
|
|
516
|
+
}
|
|
517
|
+
return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
|
|
518
|
+
}
|
|
519
|
+
function shouldFallbackOnError(error) {
|
|
520
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
521
|
+
return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
|
|
522
|
+
}
|
|
523
|
+
function isReadQuery(sql) {
|
|
524
|
+
const trimmed = sql.trimStart();
|
|
525
|
+
return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
|
|
526
|
+
}
|
|
527
|
+
function buildRow(row, columns) {
|
|
528
|
+
const values = columns.map((column) => row[column]);
|
|
529
|
+
return Object.assign(values, row);
|
|
530
|
+
}
|
|
531
|
+
function buildResultSet(rows, rowsAffected = 0) {
|
|
532
|
+
const columns = rows[0] ? Object.keys(rows[0]) : [];
|
|
533
|
+
const resultRows = rows.map((row) => buildRow(row, columns));
|
|
534
|
+
return {
|
|
535
|
+
columns,
|
|
536
|
+
columnTypes: columns.map(() => ""),
|
|
537
|
+
rows: resultRows,
|
|
538
|
+
rowsAffected,
|
|
539
|
+
lastInsertRowid: void 0,
|
|
540
|
+
toJSON() {
|
|
541
|
+
return {
|
|
542
|
+
columns,
|
|
543
|
+
columnTypes: columns.map(() => ""),
|
|
544
|
+
rows,
|
|
545
|
+
rowsAffected,
|
|
546
|
+
lastInsertRowid: void 0
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
async function loadPrismaClient() {
|
|
552
|
+
if (!prismaClientPromise) {
|
|
553
|
+
prismaClientPromise = (async () => {
|
|
554
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
555
|
+
if (explicitPath) {
|
|
556
|
+
const module2 = await import(pathToFileURL(explicitPath).href);
|
|
557
|
+
const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
|
|
558
|
+
if (!PrismaClient2) {
|
|
559
|
+
throw new Error(`No PrismaClient export found at ${explicitPath}`);
|
|
560
|
+
}
|
|
561
|
+
return new PrismaClient2();
|
|
562
|
+
}
|
|
563
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path3.join(os3.homedir(), "exe-db");
|
|
564
|
+
const requireFromExeDb = createRequire(path3.join(exeDbRoot, "package.json"));
|
|
565
|
+
const prismaEntry = requireFromExeDb.resolve("@prisma/client");
|
|
566
|
+
const module = await import(pathToFileURL(prismaEntry).href);
|
|
567
|
+
const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
|
|
568
|
+
if (!PrismaClient) {
|
|
569
|
+
throw new Error(`No PrismaClient export found in ${prismaEntry}`);
|
|
570
|
+
}
|
|
571
|
+
return new PrismaClient();
|
|
572
|
+
})();
|
|
573
|
+
}
|
|
574
|
+
return prismaClientPromise;
|
|
575
|
+
}
|
|
576
|
+
async function ensureCompatibilityViews(prisma) {
|
|
577
|
+
if (!compatibilityBootstrapPromise) {
|
|
578
|
+
compatibilityBootstrapPromise = (async () => {
|
|
579
|
+
for (const mapping of VIEW_MAPPINGS) {
|
|
580
|
+
const relation = mapping.source.replace(/"/g, "");
|
|
581
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
582
|
+
"SELECT to_regclass($1) AS regclass",
|
|
583
|
+
relation
|
|
584
|
+
);
|
|
585
|
+
if (!rows[0]?.regclass) {
|
|
586
|
+
continue;
|
|
587
|
+
}
|
|
588
|
+
await prisma.$executeRawUnsafe(
|
|
589
|
+
`CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
})();
|
|
593
|
+
}
|
|
594
|
+
return compatibilityBootstrapPromise;
|
|
595
|
+
}
|
|
596
|
+
async function executeOnPrisma(executor, stmt) {
|
|
597
|
+
const translated = translateStatementForPostgres(stmt);
|
|
598
|
+
if (isReadQuery(translated.sql)) {
|
|
599
|
+
const rows = await executor.$queryRawUnsafe(
|
|
600
|
+
translated.sql,
|
|
601
|
+
...translated.args
|
|
602
|
+
);
|
|
603
|
+
return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
|
|
604
|
+
}
|
|
605
|
+
const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
|
|
606
|
+
return buildResultSet([], rowsAffected);
|
|
607
|
+
}
|
|
608
|
+
function splitSqlStatements(sql) {
|
|
609
|
+
const parts = [];
|
|
610
|
+
let current = "";
|
|
611
|
+
let inSingle = false;
|
|
612
|
+
let inDouble = false;
|
|
613
|
+
let inLineComment = false;
|
|
614
|
+
let inBlockComment = false;
|
|
615
|
+
for (let i = 0; i < sql.length; i++) {
|
|
616
|
+
const ch = sql[i];
|
|
617
|
+
const next = sql[i + 1];
|
|
618
|
+
if (inLineComment) {
|
|
619
|
+
current += ch;
|
|
620
|
+
if (ch === "\n") inLineComment = false;
|
|
621
|
+
continue;
|
|
622
|
+
}
|
|
623
|
+
if (inBlockComment) {
|
|
624
|
+
current += ch;
|
|
625
|
+
if (ch === "*" && next === "/") {
|
|
626
|
+
current += next;
|
|
627
|
+
inBlockComment = false;
|
|
628
|
+
i += 1;
|
|
629
|
+
}
|
|
630
|
+
continue;
|
|
631
|
+
}
|
|
632
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
633
|
+
current += ch + next;
|
|
634
|
+
inLineComment = true;
|
|
635
|
+
i += 1;
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
638
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
639
|
+
current += ch + next;
|
|
640
|
+
inBlockComment = true;
|
|
641
|
+
i += 1;
|
|
642
|
+
continue;
|
|
643
|
+
}
|
|
644
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
645
|
+
inSingle = !inSingle;
|
|
646
|
+
current += ch;
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
649
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
650
|
+
inDouble = !inDouble;
|
|
651
|
+
current += ch;
|
|
652
|
+
continue;
|
|
653
|
+
}
|
|
654
|
+
if (!inSingle && !inDouble && ch === ";") {
|
|
655
|
+
if (current.trim()) {
|
|
656
|
+
parts.push(current.trim());
|
|
657
|
+
}
|
|
658
|
+
current = "";
|
|
659
|
+
continue;
|
|
660
|
+
}
|
|
661
|
+
current += ch;
|
|
662
|
+
}
|
|
663
|
+
if (current.trim()) {
|
|
664
|
+
parts.push(current.trim());
|
|
665
|
+
}
|
|
666
|
+
return parts;
|
|
667
|
+
}
|
|
668
|
+
async function createPrismaDbAdapter(fallbackClient) {
|
|
669
|
+
const prisma = await loadPrismaClient();
|
|
670
|
+
await ensureCompatibilityViews(prisma);
|
|
671
|
+
let closed = false;
|
|
672
|
+
let adapter;
|
|
673
|
+
const fallbackExecute = async (stmt, error) => {
|
|
674
|
+
if (!fallbackClient) {
|
|
675
|
+
if (error) throw error;
|
|
676
|
+
throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
|
|
677
|
+
}
|
|
678
|
+
if (error) {
|
|
679
|
+
process.stderr.write(
|
|
680
|
+
`[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
681
|
+
`
|
|
682
|
+
);
|
|
683
|
+
}
|
|
684
|
+
return fallbackClient.execute(stmt);
|
|
685
|
+
};
|
|
686
|
+
adapter = {
|
|
687
|
+
async execute(stmt) {
|
|
688
|
+
if (shouldBypassPostgres(stmt)) {
|
|
689
|
+
return fallbackExecute(stmt);
|
|
690
|
+
}
|
|
691
|
+
try {
|
|
692
|
+
return await executeOnPrisma(prisma, stmt);
|
|
693
|
+
} catch (error) {
|
|
694
|
+
if (shouldFallbackOnError(error)) {
|
|
695
|
+
return fallbackExecute(stmt, error);
|
|
696
|
+
}
|
|
697
|
+
throw error;
|
|
698
|
+
}
|
|
699
|
+
},
|
|
700
|
+
async batch(stmts, mode) {
|
|
701
|
+
if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
|
|
702
|
+
if (!fallbackClient) {
|
|
703
|
+
throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
|
|
704
|
+
}
|
|
705
|
+
return fallbackClient.batch(stmts, mode);
|
|
706
|
+
}
|
|
707
|
+
try {
|
|
708
|
+
if (prisma.$transaction) {
|
|
709
|
+
return await prisma.$transaction(async (tx) => {
|
|
710
|
+
const results2 = [];
|
|
711
|
+
for (const stmt of stmts) {
|
|
712
|
+
results2.push(await executeOnPrisma(tx, stmt));
|
|
713
|
+
}
|
|
714
|
+
return results2;
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
const results = [];
|
|
718
|
+
for (const stmt of stmts) {
|
|
719
|
+
results.push(await executeOnPrisma(prisma, stmt));
|
|
720
|
+
}
|
|
721
|
+
return results;
|
|
722
|
+
} catch (error) {
|
|
723
|
+
if (fallbackClient && shouldFallbackOnError(error)) {
|
|
724
|
+
process.stderr.write(
|
|
725
|
+
`[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
726
|
+
`
|
|
727
|
+
);
|
|
728
|
+
return fallbackClient.batch(stmts, mode);
|
|
729
|
+
}
|
|
730
|
+
throw error;
|
|
731
|
+
}
|
|
732
|
+
},
|
|
733
|
+
async migrate(stmts) {
|
|
734
|
+
if (fallbackClient) {
|
|
735
|
+
return fallbackClient.migrate(stmts);
|
|
736
|
+
}
|
|
737
|
+
return adapter.batch(stmts, "deferred");
|
|
738
|
+
},
|
|
739
|
+
async transaction(mode) {
|
|
740
|
+
if (!fallbackClient) {
|
|
741
|
+
throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
|
|
742
|
+
}
|
|
743
|
+
return fallbackClient.transaction(mode);
|
|
744
|
+
},
|
|
745
|
+
async executeMultiple(sql) {
|
|
746
|
+
if (fallbackClient && shouldBypassPostgres(sql)) {
|
|
747
|
+
return fallbackClient.executeMultiple(sql);
|
|
748
|
+
}
|
|
749
|
+
for (const statement of splitSqlStatements(sql)) {
|
|
750
|
+
await adapter.execute(statement);
|
|
751
|
+
}
|
|
752
|
+
},
|
|
753
|
+
async sync() {
|
|
754
|
+
if (fallbackClient) {
|
|
755
|
+
return fallbackClient.sync();
|
|
756
|
+
}
|
|
757
|
+
return { frame_no: 0, frames_synced: 0 };
|
|
758
|
+
},
|
|
759
|
+
close() {
|
|
760
|
+
closed = true;
|
|
761
|
+
prismaClientPromise = null;
|
|
762
|
+
compatibilityBootstrapPromise = null;
|
|
763
|
+
void prisma.$disconnect?.();
|
|
764
|
+
},
|
|
765
|
+
get closed() {
|
|
766
|
+
return closed;
|
|
767
|
+
},
|
|
768
|
+
get protocol() {
|
|
769
|
+
return "prisma-postgres";
|
|
770
|
+
}
|
|
771
|
+
};
|
|
772
|
+
return adapter;
|
|
773
|
+
}
|
|
774
|
+
var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
|
|
775
|
+
var init_database_adapter = __esm({
|
|
776
|
+
"src/lib/database-adapter.ts"() {
|
|
777
|
+
"use strict";
|
|
778
|
+
VIEW_MAPPINGS = [
|
|
779
|
+
{ view: "memories", source: "memory.memory_records" },
|
|
780
|
+
{ view: "tasks", source: "memory.tasks" },
|
|
781
|
+
{ view: "behaviors", source: "memory.behaviors" },
|
|
782
|
+
{ view: "entities", source: "memory.entities" },
|
|
783
|
+
{ view: "relationships", source: "memory.relationships" },
|
|
784
|
+
{ view: "entity_memories", source: "memory.entity_memories" },
|
|
785
|
+
{ view: "entity_aliases", source: "memory.entity_aliases" },
|
|
786
|
+
{ view: "notifications", source: "memory.notifications" },
|
|
787
|
+
{ view: "messages", source: "memory.messages" },
|
|
788
|
+
{ view: "users", source: "wiki.users" },
|
|
789
|
+
{ view: "workspaces", source: "wiki.workspaces" },
|
|
790
|
+
{ view: "workspace_users", source: "wiki.workspace_users" },
|
|
791
|
+
{ view: "documents", source: "wiki.workspace_documents" },
|
|
792
|
+
{ view: "chats", source: "wiki.workspace_chats" }
|
|
793
|
+
];
|
|
794
|
+
UPSERT_KEYS = {
|
|
795
|
+
memories: ["id"],
|
|
796
|
+
tasks: ["id"],
|
|
797
|
+
behaviors: ["id"],
|
|
798
|
+
entities: ["id"],
|
|
799
|
+
relationships: ["id"],
|
|
800
|
+
entity_aliases: ["alias"],
|
|
801
|
+
notifications: ["id"],
|
|
802
|
+
messages: ["id"],
|
|
803
|
+
users: ["id"],
|
|
804
|
+
workspaces: ["id"],
|
|
805
|
+
workspace_users: ["id"],
|
|
806
|
+
documents: ["id"],
|
|
807
|
+
chats: ["id"]
|
|
808
|
+
};
|
|
809
|
+
BOOLEAN_COLUMNS_BY_TABLE = {
|
|
810
|
+
memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
|
|
811
|
+
behaviors: /* @__PURE__ */ new Set(["active"]),
|
|
812
|
+
notifications: /* @__PURE__ */ new Set(["read"]),
|
|
813
|
+
users: /* @__PURE__ */ new Set(["has_personal_memory"])
|
|
814
|
+
};
|
|
815
|
+
BOOLEAN_COLUMN_NAMES = new Set(
|
|
816
|
+
Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
|
|
817
|
+
);
|
|
818
|
+
IMMEDIATE_FALLBACK_PATTERNS = [
|
|
819
|
+
/\bPRAGMA\b/i,
|
|
820
|
+
/\bsqlite_master\b/i,
|
|
821
|
+
/(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
|
|
822
|
+
/\bMATCH\b/i,
|
|
823
|
+
/\bvector_distance_cos\s*\(/i,
|
|
824
|
+
/\bjson_extract\s*\(/i,
|
|
825
|
+
/\bjulianday\s*\(/i,
|
|
826
|
+
/\bstrftime\s*\(/i,
|
|
827
|
+
/\blast_insert_rowid\s*\(/i
|
|
828
|
+
];
|
|
829
|
+
prismaClientPromise = null;
|
|
830
|
+
compatibilityBootstrapPromise = null;
|
|
246
831
|
}
|
|
247
832
|
});
|
|
248
833
|
|
|
249
834
|
// src/lib/exe-daemon-client.ts
|
|
250
835
|
import net from "net";
|
|
251
|
-
import
|
|
836
|
+
import os4 from "os";
|
|
252
837
|
import { spawn } from "child_process";
|
|
253
838
|
import { randomUUID } from "crypto";
|
|
254
839
|
import { existsSync as existsSync3, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
|
|
255
|
-
import
|
|
840
|
+
import path4 from "path";
|
|
256
841
|
import { fileURLToPath } from "url";
|
|
257
842
|
function handleData(chunk) {
|
|
258
843
|
_buffer += chunk.toString();
|
|
@@ -303,17 +888,17 @@ function cleanupStaleFiles() {
|
|
|
303
888
|
}
|
|
304
889
|
}
|
|
305
890
|
function findPackageRoot() {
|
|
306
|
-
let dir =
|
|
307
|
-
const { root } =
|
|
891
|
+
let dir = path4.dirname(fileURLToPath(import.meta.url));
|
|
892
|
+
const { root } = path4.parse(dir);
|
|
308
893
|
while (dir !== root) {
|
|
309
|
-
if (existsSync3(
|
|
310
|
-
dir =
|
|
894
|
+
if (existsSync3(path4.join(dir, "package.json"))) return dir;
|
|
895
|
+
dir = path4.dirname(dir);
|
|
311
896
|
}
|
|
312
897
|
return null;
|
|
313
898
|
}
|
|
314
899
|
function spawnDaemon() {
|
|
315
|
-
const freeGB =
|
|
316
|
-
const totalGB =
|
|
900
|
+
const freeGB = os4.freemem() / (1024 * 1024 * 1024);
|
|
901
|
+
const totalGB = os4.totalmem() / (1024 * 1024 * 1024);
|
|
317
902
|
if (totalGB <= 8) {
|
|
318
903
|
process.stderr.write(
|
|
319
904
|
`[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
|
|
@@ -333,7 +918,7 @@ function spawnDaemon() {
|
|
|
333
918
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
334
919
|
return;
|
|
335
920
|
}
|
|
336
|
-
const daemonPath =
|
|
921
|
+
const daemonPath = path4.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
337
922
|
if (!existsSync3(daemonPath)) {
|
|
338
923
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
339
924
|
`);
|
|
@@ -342,7 +927,7 @@ function spawnDaemon() {
|
|
|
342
927
|
const resolvedPath = daemonPath;
|
|
343
928
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
344
929
|
`);
|
|
345
|
-
const logPath =
|
|
930
|
+
const logPath = path4.join(path4.dirname(SOCKET_PATH), "exed.log");
|
|
346
931
|
let stderrFd = "ignore";
|
|
347
932
|
try {
|
|
348
933
|
stderrFd = openSync(logPath, "a");
|
|
@@ -489,9 +1074,9 @@ var init_exe_daemon_client = __esm({
|
|
|
489
1074
|
"src/lib/exe-daemon-client.ts"() {
|
|
490
1075
|
"use strict";
|
|
491
1076
|
init_config();
|
|
492
|
-
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ??
|
|
493
|
-
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ??
|
|
494
|
-
SPAWN_LOCK_PATH =
|
|
1077
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path4.join(EXE_AI_DIR, "exed.sock");
|
|
1078
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path4.join(EXE_AI_DIR, "exed.pid");
|
|
1079
|
+
SPAWN_LOCK_PATH = path4.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
495
1080
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
496
1081
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
497
1082
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
@@ -573,7 +1158,7 @@ __export(db_daemon_client_exports, {
|
|
|
573
1158
|
createDaemonDbClient: () => createDaemonDbClient,
|
|
574
1159
|
initDaemonDbClient: () => initDaemonDbClient
|
|
575
1160
|
});
|
|
576
|
-
function
|
|
1161
|
+
function normalizeStatement2(stmt) {
|
|
577
1162
|
if (typeof stmt === "string") {
|
|
578
1163
|
return { sql: stmt, args: [] };
|
|
579
1164
|
}
|
|
@@ -597,7 +1182,7 @@ function createDaemonDbClient(fallbackClient) {
|
|
|
597
1182
|
if (!_useDaemon || !isClientConnected()) {
|
|
598
1183
|
return fallbackClient.execute(stmt);
|
|
599
1184
|
}
|
|
600
|
-
const { sql, args } =
|
|
1185
|
+
const { sql, args } = normalizeStatement2(stmt);
|
|
601
1186
|
const response = await sendDaemonRequest({
|
|
602
1187
|
type: "db-execute",
|
|
603
1188
|
sql,
|
|
@@ -622,7 +1207,7 @@ function createDaemonDbClient(fallbackClient) {
|
|
|
622
1207
|
if (!_useDaemon || !isClientConnected()) {
|
|
623
1208
|
return fallbackClient.batch(stmts, mode);
|
|
624
1209
|
}
|
|
625
|
-
const statements = stmts.map(
|
|
1210
|
+
const statements = stmts.map(normalizeStatement2);
|
|
626
1211
|
const response = await sendDaemonRequest({
|
|
627
1212
|
type: "db-batch",
|
|
628
1213
|
statements,
|
|
@@ -717,6 +1302,18 @@ __export(database_exports, {
|
|
|
717
1302
|
});
|
|
718
1303
|
import { createClient } from "@libsql/client";
|
|
719
1304
|
async function initDatabase(config) {
|
|
1305
|
+
if (_walCheckpointTimer) {
|
|
1306
|
+
clearInterval(_walCheckpointTimer);
|
|
1307
|
+
_walCheckpointTimer = null;
|
|
1308
|
+
}
|
|
1309
|
+
if (_daemonClient) {
|
|
1310
|
+
_daemonClient.close();
|
|
1311
|
+
_daemonClient = null;
|
|
1312
|
+
}
|
|
1313
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
1314
|
+
_adapterClient.close();
|
|
1315
|
+
}
|
|
1316
|
+
_adapterClient = null;
|
|
720
1317
|
if (_client) {
|
|
721
1318
|
_client.close();
|
|
722
1319
|
_client = null;
|
|
@@ -730,6 +1327,7 @@ async function initDatabase(config) {
|
|
|
730
1327
|
}
|
|
731
1328
|
_client = createClient(opts);
|
|
732
1329
|
_resilientClient = wrapWithRetry(_client);
|
|
1330
|
+
_adapterClient = _resilientClient;
|
|
733
1331
|
_client.execute("PRAGMA busy_timeout = 30000").catch(() => {
|
|
734
1332
|
});
|
|
735
1333
|
_client.execute("PRAGMA journal_mode = WAL").catch(() => {
|
|
@@ -740,14 +1338,20 @@ async function initDatabase(config) {
|
|
|
740
1338
|
});
|
|
741
1339
|
}, 3e4);
|
|
742
1340
|
_walCheckpointTimer.unref();
|
|
1341
|
+
if (process.env.DATABASE_URL) {
|
|
1342
|
+
_adapterClient = await createPrismaDbAdapter(_resilientClient);
|
|
1343
|
+
}
|
|
743
1344
|
}
|
|
744
1345
|
function isInitialized() {
|
|
745
|
-
return _client !== null;
|
|
1346
|
+
return _adapterClient !== null || _client !== null;
|
|
746
1347
|
}
|
|
747
1348
|
function getClient() {
|
|
748
|
-
if (!
|
|
1349
|
+
if (!_adapterClient) {
|
|
749
1350
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
750
1351
|
}
|
|
1352
|
+
if (process.env.DATABASE_URL) {
|
|
1353
|
+
return _adapterClient;
|
|
1354
|
+
}
|
|
751
1355
|
if (process.env.EXE_IS_DAEMON === "1") {
|
|
752
1356
|
return _resilientClient;
|
|
753
1357
|
}
|
|
@@ -757,6 +1361,7 @@ function getClient() {
|
|
|
757
1361
|
return _resilientClient;
|
|
758
1362
|
}
|
|
759
1363
|
async function initDaemonClient() {
|
|
1364
|
+
if (process.env.DATABASE_URL) return;
|
|
760
1365
|
if (process.env.EXE_IS_DAEMON === "1") return;
|
|
761
1366
|
if (!_resilientClient) return;
|
|
762
1367
|
try {
|
|
@@ -1701,26 +2306,36 @@ async function ensureSchema() {
|
|
|
1701
2306
|
}
|
|
1702
2307
|
}
|
|
1703
2308
|
async function disposeDatabase() {
|
|
2309
|
+
if (_walCheckpointTimer) {
|
|
2310
|
+
clearInterval(_walCheckpointTimer);
|
|
2311
|
+
_walCheckpointTimer = null;
|
|
2312
|
+
}
|
|
1704
2313
|
if (_daemonClient) {
|
|
1705
2314
|
_daemonClient.close();
|
|
1706
2315
|
_daemonClient = null;
|
|
1707
2316
|
}
|
|
2317
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
2318
|
+
_adapterClient.close();
|
|
2319
|
+
}
|
|
2320
|
+
_adapterClient = null;
|
|
1708
2321
|
if (_client) {
|
|
1709
2322
|
_client.close();
|
|
1710
2323
|
_client = null;
|
|
1711
2324
|
_resilientClient = null;
|
|
1712
2325
|
}
|
|
1713
2326
|
}
|
|
1714
|
-
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
|
|
2327
|
+
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
|
|
1715
2328
|
var init_database = __esm({
|
|
1716
2329
|
"src/lib/database.ts"() {
|
|
1717
2330
|
"use strict";
|
|
1718
2331
|
init_db_retry();
|
|
1719
2332
|
init_employees();
|
|
2333
|
+
init_database_adapter();
|
|
1720
2334
|
_client = null;
|
|
1721
2335
|
_resilientClient = null;
|
|
1722
2336
|
_walCheckpointTimer = null;
|
|
1723
2337
|
_daemonClient = null;
|
|
2338
|
+
_adapterClient = null;
|
|
1724
2339
|
initTurso = initDatabase;
|
|
1725
2340
|
disposeTurso = disposeDatabase;
|
|
1726
2341
|
}
|
|
@@ -1746,7 +2361,7 @@ __export(crdt_sync_exports, {
|
|
|
1746
2361
|
});
|
|
1747
2362
|
import * as Y from "yjs";
|
|
1748
2363
|
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync2, unlinkSync as unlinkSync3 } from "fs";
|
|
1749
|
-
import
|
|
2364
|
+
import path6 from "path";
|
|
1750
2365
|
import { homedir } from "os";
|
|
1751
2366
|
function getStatePath() {
|
|
1752
2367
|
return _statePathOverride ?? DEFAULT_STATE_PATH;
|
|
@@ -1902,7 +2517,7 @@ function persistState() {
|
|
|
1902
2517
|
if (!doc) return;
|
|
1903
2518
|
try {
|
|
1904
2519
|
const sp = getStatePath();
|
|
1905
|
-
const dir =
|
|
2520
|
+
const dir = path6.dirname(sp);
|
|
1906
2521
|
if (!existsSync5(dir)) mkdirSync2(dir, { recursive: true });
|
|
1907
2522
|
const state = Y.encodeStateAsUpdate(doc);
|
|
1908
2523
|
writeFileSync3(sp, Buffer.from(state));
|
|
@@ -1946,7 +2561,7 @@ var DEFAULT_STATE_PATH, _statePathOverride, doc;
|
|
|
1946
2561
|
var init_crdt_sync = __esm({
|
|
1947
2562
|
"src/lib/crdt-sync.ts"() {
|
|
1948
2563
|
"use strict";
|
|
1949
|
-
DEFAULT_STATE_PATH =
|
|
2564
|
+
DEFAULT_STATE_PATH = path6.join(homedir(), ".exe-os", "crdt-state.bin");
|
|
1950
2565
|
_statePathOverride = null;
|
|
1951
2566
|
doc = null;
|
|
1952
2567
|
}
|
|
@@ -1963,13 +2578,13 @@ __export(keychain_exports, {
|
|
|
1963
2578
|
});
|
|
1964
2579
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
1965
2580
|
import { existsSync as existsSync6 } from "fs";
|
|
1966
|
-
import
|
|
1967
|
-
import
|
|
2581
|
+
import path7 from "path";
|
|
2582
|
+
import os5 from "os";
|
|
1968
2583
|
function getKeyDir() {
|
|
1969
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
2584
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path7.join(os5.homedir(), ".exe-os");
|
|
1970
2585
|
}
|
|
1971
2586
|
function getKeyPath() {
|
|
1972
|
-
return
|
|
2587
|
+
return path7.join(getKeyDir(), "master.key");
|
|
1973
2588
|
}
|
|
1974
2589
|
async function tryKeytar() {
|
|
1975
2590
|
try {
|
|
@@ -1992,7 +2607,7 @@ async function getMasterKey() {
|
|
|
1992
2607
|
const keyPath = getKeyPath();
|
|
1993
2608
|
if (!existsSync6(keyPath)) {
|
|
1994
2609
|
process.stderr.write(
|
|
1995
|
-
`[keychain] Key not found at ${keyPath} (HOME=${
|
|
2610
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os5.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
1996
2611
|
`
|
|
1997
2612
|
);
|
|
1998
2613
|
return null;
|
|
@@ -2079,7 +2694,7 @@ var init_keychain = __esm({
|
|
|
2079
2694
|
init_database();
|
|
2080
2695
|
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync7, readdirSync, mkdirSync as mkdirSync3, appendFileSync, unlinkSync as unlinkSync4, openSync as openSync2, closeSync as closeSync2 } from "fs";
|
|
2081
2696
|
import crypto2 from "crypto";
|
|
2082
|
-
import
|
|
2697
|
+
import path8 from "path";
|
|
2083
2698
|
import { homedir as homedir2 } from "os";
|
|
2084
2699
|
|
|
2085
2700
|
// src/lib/crypto.ts
|
|
@@ -2147,13 +2762,13 @@ function decompress(input) {
|
|
|
2147
2762
|
init_config();
|
|
2148
2763
|
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync4, mkdirSync } from "fs";
|
|
2149
2764
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
2150
|
-
import
|
|
2765
|
+
import path5 from "path";
|
|
2151
2766
|
import { jwtVerify, importSPKI } from "jose";
|
|
2152
|
-
var LICENSE_PATH =
|
|
2153
|
-
var CACHE_PATH =
|
|
2154
|
-
var DEVICE_ID_PATH =
|
|
2767
|
+
var LICENSE_PATH = path5.join(EXE_AI_DIR, "license.key");
|
|
2768
|
+
var CACHE_PATH = path5.join(EXE_AI_DIR, "license-cache.json");
|
|
2769
|
+
var DEVICE_ID_PATH = path5.join(EXE_AI_DIR, "device-id");
|
|
2155
2770
|
function loadDeviceId() {
|
|
2156
|
-
const deviceJsonPath =
|
|
2771
|
+
const deviceJsonPath = path5.join(EXE_AI_DIR, "device.json");
|
|
2157
2772
|
try {
|
|
2158
2773
|
if (existsSync4(deviceJsonPath)) {
|
|
2159
2774
|
const data = JSON.parse(readFileSync4(deviceJsonPath, "utf8"));
|
|
@@ -2183,7 +2798,7 @@ function sqlSafe(v) {
|
|
|
2183
2798
|
}
|
|
2184
2799
|
function logError(msg) {
|
|
2185
2800
|
try {
|
|
2186
|
-
const logPath =
|
|
2801
|
+
const logPath = path8.join(homedir2(), ".exe-os", "workers.log");
|
|
2187
2802
|
appendFileSync(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
|
|
2188
2803
|
`);
|
|
2189
2804
|
} catch {
|
|
@@ -2192,7 +2807,7 @@ function logError(msg) {
|
|
|
2192
2807
|
var LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
|
|
2193
2808
|
var FETCH_TIMEOUT_MS = 3e4;
|
|
2194
2809
|
var PUSH_BATCH_SIZE = 5e3;
|
|
2195
|
-
var ROSTER_LOCK_PATH =
|
|
2810
|
+
var ROSTER_LOCK_PATH = path8.join(EXE_AI_DIR, "roster-merge.lock");
|
|
2196
2811
|
var LOCK_STALE_MS = 3e4;
|
|
2197
2812
|
async function withRosterLock(fn) {
|
|
2198
2813
|
try {
|
|
@@ -2583,7 +3198,7 @@ async function cloudSync(config) {
|
|
|
2583
3198
|
try {
|
|
2584
3199
|
const employees = await loadEmployees();
|
|
2585
3200
|
rosterResult.employees = employees.length;
|
|
2586
|
-
const idDir =
|
|
3201
|
+
const idDir = path8.join(EXE_AI_DIR, "identity");
|
|
2587
3202
|
if (existsSync7(idDir)) {
|
|
2588
3203
|
rosterResult.identities = readdirSync(idDir).filter((f) => f.endsWith(".md")).length;
|
|
2589
3204
|
}
|
|
@@ -2602,7 +3217,7 @@ async function cloudSync(config) {
|
|
|
2602
3217
|
roster: rosterResult
|
|
2603
3218
|
};
|
|
2604
3219
|
}
|
|
2605
|
-
var ROSTER_DELETIONS_PATH =
|
|
3220
|
+
var ROSTER_DELETIONS_PATH = path8.join(EXE_AI_DIR, "roster-deletions.json");
|
|
2606
3221
|
function recordRosterDeletion(name) {
|
|
2607
3222
|
let deletions = [];
|
|
2608
3223
|
try {
|
|
@@ -2625,9 +3240,9 @@ function consumeRosterDeletions() {
|
|
|
2625
3240
|
}
|
|
2626
3241
|
}
|
|
2627
3242
|
function buildRosterBlob(paths) {
|
|
2628
|
-
const rosterPath = paths?.rosterPath ??
|
|
2629
|
-
const identityDir = paths?.identityDir ??
|
|
2630
|
-
const configPath = paths?.configPath ??
|
|
3243
|
+
const rosterPath = paths?.rosterPath ?? path8.join(EXE_AI_DIR, "exe-employees.json");
|
|
3244
|
+
const identityDir = paths?.identityDir ?? path8.join(EXE_AI_DIR, "identity");
|
|
3245
|
+
const configPath = paths?.configPath ?? path8.join(EXE_AI_DIR, "config.json");
|
|
2631
3246
|
let roster = [];
|
|
2632
3247
|
if (existsSync7(rosterPath)) {
|
|
2633
3248
|
try {
|
|
@@ -2639,7 +3254,7 @@ function buildRosterBlob(paths) {
|
|
|
2639
3254
|
if (existsSync7(identityDir)) {
|
|
2640
3255
|
for (const file of readdirSync(identityDir).filter((f) => f.endsWith(".md"))) {
|
|
2641
3256
|
try {
|
|
2642
|
-
identities[file] = readFileSync6(
|
|
3257
|
+
identities[file] = readFileSync6(path8.join(identityDir, file), "utf-8");
|
|
2643
3258
|
} catch {
|
|
2644
3259
|
}
|
|
2645
3260
|
}
|
|
@@ -2652,7 +3267,7 @@ function buildRosterBlob(paths) {
|
|
|
2652
3267
|
}
|
|
2653
3268
|
}
|
|
2654
3269
|
let agentConfig;
|
|
2655
|
-
const agentConfigPath =
|
|
3270
|
+
const agentConfigPath = path8.join(EXE_AI_DIR, "agent-config.json");
|
|
2656
3271
|
if (existsSync7(agentConfigPath)) {
|
|
2657
3272
|
try {
|
|
2658
3273
|
agentConfig = JSON.parse(readFileSync6(agentConfigPath, "utf-8"));
|
|
@@ -2731,7 +3346,7 @@ async function cloudPullRoster(config) {
|
|
|
2731
3346
|
}
|
|
2732
3347
|
}
|
|
2733
3348
|
function mergeConfig(remoteConfig, configPath) {
|
|
2734
|
-
const cfgPath = configPath ??
|
|
3349
|
+
const cfgPath = configPath ?? path8.join(EXE_AI_DIR, "config.json");
|
|
2735
3350
|
let local = {};
|
|
2736
3351
|
if (existsSync7(cfgPath)) {
|
|
2737
3352
|
try {
|
|
@@ -2740,14 +3355,14 @@ function mergeConfig(remoteConfig, configPath) {
|
|
|
2740
3355
|
}
|
|
2741
3356
|
}
|
|
2742
3357
|
const merged = { ...remoteConfig, ...local };
|
|
2743
|
-
const dir =
|
|
3358
|
+
const dir = path8.dirname(cfgPath);
|
|
2744
3359
|
if (!existsSync7(dir)) mkdirSync3(dir, { recursive: true });
|
|
2745
3360
|
writeFileSync4(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
|
|
2746
3361
|
}
|
|
2747
3362
|
async function mergeRosterFromRemote(remote, paths) {
|
|
2748
3363
|
return withRosterLock(async () => {
|
|
2749
3364
|
const rosterPath = paths?.rosterPath ?? void 0;
|
|
2750
|
-
const identityDir = paths?.identityDir ??
|
|
3365
|
+
const identityDir = paths?.identityDir ?? path8.join(EXE_AI_DIR, "identity");
|
|
2751
3366
|
const localEmployees = await loadEmployees(rosterPath);
|
|
2752
3367
|
const localNames = new Set(localEmployees.map((e) => e.name));
|
|
2753
3368
|
let added = 0;
|
|
@@ -2769,7 +3384,7 @@ async function mergeRosterFromRemote(remote, paths) {
|
|
|
2769
3384
|
const remoteIdentity = remote.identities[matchedKey];
|
|
2770
3385
|
if (remoteIdentity) {
|
|
2771
3386
|
if (!existsSync7(identityDir)) mkdirSync3(identityDir, { recursive: true });
|
|
2772
|
-
const idPath =
|
|
3387
|
+
const idPath = path8.join(identityDir, `${remoteEmp.name}.md`);
|
|
2773
3388
|
let localIdentity = null;
|
|
2774
3389
|
try {
|
|
2775
3390
|
localIdentity = existsSync7(idPath) ? readFileSync6(idPath, "utf-8") : null;
|
|
@@ -2802,7 +3417,7 @@ async function mergeRosterFromRemote(remote, paths) {
|
|
|
2802
3417
|
}
|
|
2803
3418
|
if (remote.agentConfig && Object.keys(remote.agentConfig).length > 0) {
|
|
2804
3419
|
try {
|
|
2805
|
-
const agentConfigPath =
|
|
3420
|
+
const agentConfigPath = path8.join(EXE_AI_DIR, "agent-config.json");
|
|
2806
3421
|
let local = {};
|
|
2807
3422
|
if (existsSync7(agentConfigPath)) {
|
|
2808
3423
|
try {
|