@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
|
@@ -279,7 +279,7 @@ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
|
279
279
|
return [];
|
|
280
280
|
}
|
|
281
281
|
}
|
|
282
|
-
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE;
|
|
282
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, IDENTITY_DIR;
|
|
283
283
|
var init_employees = __esm({
|
|
284
284
|
"src/lib/employees.ts"() {
|
|
285
285
|
"use strict";
|
|
@@ -287,12 +287,609 @@ var init_employees = __esm({
|
|
|
287
287
|
EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
|
|
288
288
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
289
289
|
COORDINATOR_ROLE = "COO";
|
|
290
|
+
IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// src/lib/database-adapter.ts
|
|
295
|
+
import os3 from "os";
|
|
296
|
+
import path3 from "path";
|
|
297
|
+
import { createRequire } from "module";
|
|
298
|
+
import { pathToFileURL } from "url";
|
|
299
|
+
function quotedIdentifier(identifier) {
|
|
300
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
301
|
+
}
|
|
302
|
+
function unqualifiedTableName(name) {
|
|
303
|
+
const raw = name.trim().replace(/^"|"$/g, "");
|
|
304
|
+
const parts = raw.split(".");
|
|
305
|
+
return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
|
|
306
|
+
}
|
|
307
|
+
function stripTrailingSemicolon(sql) {
|
|
308
|
+
return sql.trim().replace(/;+\s*$/u, "");
|
|
309
|
+
}
|
|
310
|
+
function appendClause(sql, clause) {
|
|
311
|
+
const trimmed = stripTrailingSemicolon(sql);
|
|
312
|
+
const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
|
|
313
|
+
if (!returningMatch) {
|
|
314
|
+
return `${trimmed}${clause}`;
|
|
315
|
+
}
|
|
316
|
+
const idx = returningMatch.index;
|
|
317
|
+
return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
|
|
318
|
+
}
|
|
319
|
+
function normalizeStatement(stmt) {
|
|
320
|
+
if (typeof stmt === "string") {
|
|
321
|
+
return { kind: "positional", sql: stmt, args: [] };
|
|
322
|
+
}
|
|
323
|
+
const sql = stmt.sql;
|
|
324
|
+
if (Array.isArray(stmt.args) || stmt.args === void 0) {
|
|
325
|
+
return { kind: "positional", sql, args: stmt.args ?? [] };
|
|
326
|
+
}
|
|
327
|
+
return { kind: "named", sql, args: stmt.args };
|
|
328
|
+
}
|
|
329
|
+
function rewriteBooleanLiterals(sql) {
|
|
330
|
+
let out = sql;
|
|
331
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
332
|
+
const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
|
|
333
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
|
|
334
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
|
|
335
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
|
|
336
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
|
|
337
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
|
|
338
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
|
|
339
|
+
}
|
|
340
|
+
return out;
|
|
341
|
+
}
|
|
342
|
+
function rewriteInsertOrIgnore(sql) {
|
|
343
|
+
if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
|
|
344
|
+
return sql;
|
|
345
|
+
}
|
|
346
|
+
const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
|
|
347
|
+
return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
|
|
348
|
+
}
|
|
349
|
+
function rewriteInsertOrReplace(sql) {
|
|
350
|
+
const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
|
|
351
|
+
if (!match) {
|
|
352
|
+
return sql;
|
|
353
|
+
}
|
|
354
|
+
const rawTable = match[1];
|
|
355
|
+
const rawColumns = match[2];
|
|
356
|
+
const remainder = match[3];
|
|
357
|
+
const tableName = unqualifiedTableName(rawTable);
|
|
358
|
+
const conflictKeys = UPSERT_KEYS[tableName];
|
|
359
|
+
if (!conflictKeys?.length) {
|
|
360
|
+
return sql;
|
|
361
|
+
}
|
|
362
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
363
|
+
const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
|
|
364
|
+
const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
|
|
365
|
+
const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
|
|
366
|
+
return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
|
|
367
|
+
}
|
|
368
|
+
function rewriteSql(sql) {
|
|
369
|
+
let out = sql;
|
|
370
|
+
out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
|
|
371
|
+
out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
|
|
372
|
+
out = rewriteBooleanLiterals(out);
|
|
373
|
+
out = rewriteInsertOrReplace(out);
|
|
374
|
+
out = rewriteInsertOrIgnore(out);
|
|
375
|
+
return stripTrailingSemicolon(out);
|
|
376
|
+
}
|
|
377
|
+
function toBoolean(value) {
|
|
378
|
+
if (value === null || value === void 0) return value;
|
|
379
|
+
if (typeof value === "boolean") return value;
|
|
380
|
+
if (typeof value === "number") return value !== 0;
|
|
381
|
+
if (typeof value === "bigint") return value !== 0n;
|
|
382
|
+
if (typeof value === "string") {
|
|
383
|
+
const normalized = value.trim().toLowerCase();
|
|
384
|
+
if (normalized === "0" || normalized === "false") return false;
|
|
385
|
+
if (normalized === "1" || normalized === "true") return true;
|
|
386
|
+
}
|
|
387
|
+
return Boolean(value);
|
|
388
|
+
}
|
|
389
|
+
function countQuestionMarks(sql, end) {
|
|
390
|
+
let count = 0;
|
|
391
|
+
let inSingle = false;
|
|
392
|
+
let inDouble = false;
|
|
393
|
+
let inLineComment = false;
|
|
394
|
+
let inBlockComment = false;
|
|
395
|
+
for (let i = 0; i < end; i++) {
|
|
396
|
+
const ch = sql[i];
|
|
397
|
+
const next = sql[i + 1];
|
|
398
|
+
if (inLineComment) {
|
|
399
|
+
if (ch === "\n") inLineComment = false;
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
if (inBlockComment) {
|
|
403
|
+
if (ch === "*" && next === "/") {
|
|
404
|
+
inBlockComment = false;
|
|
405
|
+
i += 1;
|
|
406
|
+
}
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
410
|
+
inLineComment = true;
|
|
411
|
+
i += 1;
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
415
|
+
inBlockComment = true;
|
|
416
|
+
i += 1;
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
420
|
+
inSingle = !inSingle;
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
424
|
+
inDouble = !inDouble;
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
428
|
+
count += 1;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
return count;
|
|
432
|
+
}
|
|
433
|
+
function findBooleanPlaceholderIndexes(sql) {
|
|
434
|
+
const indexes = /* @__PURE__ */ new Set();
|
|
435
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
436
|
+
const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
|
|
437
|
+
for (const match of sql.matchAll(pattern)) {
|
|
438
|
+
const matchText = match[0];
|
|
439
|
+
const qIndex = match.index + matchText.lastIndexOf("?");
|
|
440
|
+
indexes.add(countQuestionMarks(sql, qIndex + 1));
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
return indexes;
|
|
444
|
+
}
|
|
445
|
+
function coerceInsertBooleanArgs(sql, args) {
|
|
446
|
+
const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
|
|
447
|
+
if (!match) return;
|
|
448
|
+
const rawTable = match[1];
|
|
449
|
+
const rawColumns = match[2];
|
|
450
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
451
|
+
if (!boolColumns?.size) return;
|
|
452
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
453
|
+
for (const [index, column] of columns.entries()) {
|
|
454
|
+
if (boolColumns.has(column) && index < args.length) {
|
|
455
|
+
args[index] = toBoolean(args[index]);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
function coerceUpdateBooleanArgs(sql, args) {
|
|
460
|
+
const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
|
|
461
|
+
if (!match) return;
|
|
462
|
+
const rawTable = match[1];
|
|
463
|
+
const setClause = match[2];
|
|
464
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
465
|
+
if (!boolColumns?.size) return;
|
|
466
|
+
const assignments = setClause.split(",");
|
|
467
|
+
let placeholderIndex = 0;
|
|
468
|
+
for (const assignment of assignments) {
|
|
469
|
+
if (!assignment.includes("?")) continue;
|
|
470
|
+
placeholderIndex += 1;
|
|
471
|
+
const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
|
|
472
|
+
if (colMatch && boolColumns.has(colMatch[1])) {
|
|
473
|
+
args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
function coerceBooleanArgs(sql, args) {
|
|
478
|
+
const nextArgs = [...args];
|
|
479
|
+
coerceInsertBooleanArgs(sql, nextArgs);
|
|
480
|
+
coerceUpdateBooleanArgs(sql, nextArgs);
|
|
481
|
+
const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
|
|
482
|
+
for (const index of placeholderIndexes) {
|
|
483
|
+
if (index > 0 && index <= nextArgs.length) {
|
|
484
|
+
nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
return nextArgs;
|
|
488
|
+
}
|
|
489
|
+
function convertQuestionMarksToDollarParams(sql) {
|
|
490
|
+
let out = "";
|
|
491
|
+
let placeholder = 0;
|
|
492
|
+
let inSingle = false;
|
|
493
|
+
let inDouble = false;
|
|
494
|
+
let inLineComment = false;
|
|
495
|
+
let inBlockComment = false;
|
|
496
|
+
for (let i = 0; i < sql.length; i++) {
|
|
497
|
+
const ch = sql[i];
|
|
498
|
+
const next = sql[i + 1];
|
|
499
|
+
if (inLineComment) {
|
|
500
|
+
out += ch;
|
|
501
|
+
if (ch === "\n") inLineComment = false;
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
if (inBlockComment) {
|
|
505
|
+
out += ch;
|
|
506
|
+
if (ch === "*" && next === "/") {
|
|
507
|
+
out += next;
|
|
508
|
+
inBlockComment = false;
|
|
509
|
+
i += 1;
|
|
510
|
+
}
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
514
|
+
out += ch + next;
|
|
515
|
+
inLineComment = true;
|
|
516
|
+
i += 1;
|
|
517
|
+
continue;
|
|
518
|
+
}
|
|
519
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
520
|
+
out += ch + next;
|
|
521
|
+
inBlockComment = true;
|
|
522
|
+
i += 1;
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
526
|
+
inSingle = !inSingle;
|
|
527
|
+
out += ch;
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
531
|
+
inDouble = !inDouble;
|
|
532
|
+
out += ch;
|
|
533
|
+
continue;
|
|
534
|
+
}
|
|
535
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
536
|
+
placeholder += 1;
|
|
537
|
+
out += `$${placeholder}`;
|
|
538
|
+
continue;
|
|
539
|
+
}
|
|
540
|
+
out += ch;
|
|
541
|
+
}
|
|
542
|
+
return out;
|
|
543
|
+
}
|
|
544
|
+
function translateStatementForPostgres(stmt) {
|
|
545
|
+
const normalized = normalizeStatement(stmt);
|
|
546
|
+
if (normalized.kind === "named") {
|
|
547
|
+
throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
|
|
548
|
+
}
|
|
549
|
+
const rewrittenSql = rewriteSql(normalized.sql);
|
|
550
|
+
const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
|
|
551
|
+
return {
|
|
552
|
+
sql: convertQuestionMarksToDollarParams(rewrittenSql),
|
|
553
|
+
args: coercedArgs
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
function shouldBypassPostgres(stmt) {
|
|
557
|
+
const normalized = normalizeStatement(stmt);
|
|
558
|
+
if (normalized.kind === "named") {
|
|
559
|
+
return true;
|
|
560
|
+
}
|
|
561
|
+
return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
|
|
562
|
+
}
|
|
563
|
+
function shouldFallbackOnError(error) {
|
|
564
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
565
|
+
return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
|
|
566
|
+
}
|
|
567
|
+
function isReadQuery(sql) {
|
|
568
|
+
const trimmed = sql.trimStart();
|
|
569
|
+
return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
|
|
570
|
+
}
|
|
571
|
+
function buildRow(row, columns) {
|
|
572
|
+
const values = columns.map((column) => row[column]);
|
|
573
|
+
return Object.assign(values, row);
|
|
574
|
+
}
|
|
575
|
+
function buildResultSet(rows, rowsAffected = 0) {
|
|
576
|
+
const columns = rows[0] ? Object.keys(rows[0]) : [];
|
|
577
|
+
const resultRows = rows.map((row) => buildRow(row, columns));
|
|
578
|
+
return {
|
|
579
|
+
columns,
|
|
580
|
+
columnTypes: columns.map(() => ""),
|
|
581
|
+
rows: resultRows,
|
|
582
|
+
rowsAffected,
|
|
583
|
+
lastInsertRowid: void 0,
|
|
584
|
+
toJSON() {
|
|
585
|
+
return {
|
|
586
|
+
columns,
|
|
587
|
+
columnTypes: columns.map(() => ""),
|
|
588
|
+
rows,
|
|
589
|
+
rowsAffected,
|
|
590
|
+
lastInsertRowid: void 0
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
async function loadPrismaClient() {
|
|
596
|
+
if (!prismaClientPromise) {
|
|
597
|
+
prismaClientPromise = (async () => {
|
|
598
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
599
|
+
if (explicitPath) {
|
|
600
|
+
const module2 = await import(pathToFileURL(explicitPath).href);
|
|
601
|
+
const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
|
|
602
|
+
if (!PrismaClient2) {
|
|
603
|
+
throw new Error(`No PrismaClient export found at ${explicitPath}`);
|
|
604
|
+
}
|
|
605
|
+
return new PrismaClient2();
|
|
606
|
+
}
|
|
607
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path3.join(os3.homedir(), "exe-db");
|
|
608
|
+
const requireFromExeDb = createRequire(path3.join(exeDbRoot, "package.json"));
|
|
609
|
+
const prismaEntry = requireFromExeDb.resolve("@prisma/client");
|
|
610
|
+
const module = await import(pathToFileURL(prismaEntry).href);
|
|
611
|
+
const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
|
|
612
|
+
if (!PrismaClient) {
|
|
613
|
+
throw new Error(`No PrismaClient export found in ${prismaEntry}`);
|
|
614
|
+
}
|
|
615
|
+
return new PrismaClient();
|
|
616
|
+
})();
|
|
617
|
+
}
|
|
618
|
+
return prismaClientPromise;
|
|
619
|
+
}
|
|
620
|
+
async function ensureCompatibilityViews(prisma) {
|
|
621
|
+
if (!compatibilityBootstrapPromise) {
|
|
622
|
+
compatibilityBootstrapPromise = (async () => {
|
|
623
|
+
for (const mapping of VIEW_MAPPINGS) {
|
|
624
|
+
const relation = mapping.source.replace(/"/g, "");
|
|
625
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
626
|
+
"SELECT to_regclass($1) AS regclass",
|
|
627
|
+
relation
|
|
628
|
+
);
|
|
629
|
+
if (!rows[0]?.regclass) {
|
|
630
|
+
continue;
|
|
631
|
+
}
|
|
632
|
+
await prisma.$executeRawUnsafe(
|
|
633
|
+
`CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
})();
|
|
637
|
+
}
|
|
638
|
+
return compatibilityBootstrapPromise;
|
|
639
|
+
}
|
|
640
|
+
async function executeOnPrisma(executor, stmt) {
|
|
641
|
+
const translated = translateStatementForPostgres(stmt);
|
|
642
|
+
if (isReadQuery(translated.sql)) {
|
|
643
|
+
const rows = await executor.$queryRawUnsafe(
|
|
644
|
+
translated.sql,
|
|
645
|
+
...translated.args
|
|
646
|
+
);
|
|
647
|
+
return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
|
|
648
|
+
}
|
|
649
|
+
const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
|
|
650
|
+
return buildResultSet([], rowsAffected);
|
|
651
|
+
}
|
|
652
|
+
function splitSqlStatements(sql) {
|
|
653
|
+
const parts = [];
|
|
654
|
+
let current = "";
|
|
655
|
+
let inSingle = false;
|
|
656
|
+
let inDouble = false;
|
|
657
|
+
let inLineComment = false;
|
|
658
|
+
let inBlockComment = false;
|
|
659
|
+
for (let i = 0; i < sql.length; i++) {
|
|
660
|
+
const ch = sql[i];
|
|
661
|
+
const next = sql[i + 1];
|
|
662
|
+
if (inLineComment) {
|
|
663
|
+
current += ch;
|
|
664
|
+
if (ch === "\n") inLineComment = false;
|
|
665
|
+
continue;
|
|
666
|
+
}
|
|
667
|
+
if (inBlockComment) {
|
|
668
|
+
current += ch;
|
|
669
|
+
if (ch === "*" && next === "/") {
|
|
670
|
+
current += next;
|
|
671
|
+
inBlockComment = false;
|
|
672
|
+
i += 1;
|
|
673
|
+
}
|
|
674
|
+
continue;
|
|
675
|
+
}
|
|
676
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
677
|
+
current += ch + next;
|
|
678
|
+
inLineComment = true;
|
|
679
|
+
i += 1;
|
|
680
|
+
continue;
|
|
681
|
+
}
|
|
682
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
683
|
+
current += ch + next;
|
|
684
|
+
inBlockComment = true;
|
|
685
|
+
i += 1;
|
|
686
|
+
continue;
|
|
687
|
+
}
|
|
688
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
689
|
+
inSingle = !inSingle;
|
|
690
|
+
current += ch;
|
|
691
|
+
continue;
|
|
692
|
+
}
|
|
693
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
694
|
+
inDouble = !inDouble;
|
|
695
|
+
current += ch;
|
|
696
|
+
continue;
|
|
697
|
+
}
|
|
698
|
+
if (!inSingle && !inDouble && ch === ";") {
|
|
699
|
+
if (current.trim()) {
|
|
700
|
+
parts.push(current.trim());
|
|
701
|
+
}
|
|
702
|
+
current = "";
|
|
703
|
+
continue;
|
|
704
|
+
}
|
|
705
|
+
current += ch;
|
|
706
|
+
}
|
|
707
|
+
if (current.trim()) {
|
|
708
|
+
parts.push(current.trim());
|
|
709
|
+
}
|
|
710
|
+
return parts;
|
|
711
|
+
}
|
|
712
|
+
async function createPrismaDbAdapter(fallbackClient) {
|
|
713
|
+
const prisma = await loadPrismaClient();
|
|
714
|
+
await ensureCompatibilityViews(prisma);
|
|
715
|
+
let closed = false;
|
|
716
|
+
let adapter;
|
|
717
|
+
const fallbackExecute = async (stmt, error) => {
|
|
718
|
+
if (!fallbackClient) {
|
|
719
|
+
if (error) throw error;
|
|
720
|
+
throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
|
|
721
|
+
}
|
|
722
|
+
if (error) {
|
|
723
|
+
process.stderr.write(
|
|
724
|
+
`[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
725
|
+
`
|
|
726
|
+
);
|
|
727
|
+
}
|
|
728
|
+
return fallbackClient.execute(stmt);
|
|
729
|
+
};
|
|
730
|
+
adapter = {
|
|
731
|
+
async execute(stmt) {
|
|
732
|
+
if (shouldBypassPostgres(stmt)) {
|
|
733
|
+
return fallbackExecute(stmt);
|
|
734
|
+
}
|
|
735
|
+
try {
|
|
736
|
+
return await executeOnPrisma(prisma, stmt);
|
|
737
|
+
} catch (error) {
|
|
738
|
+
if (shouldFallbackOnError(error)) {
|
|
739
|
+
return fallbackExecute(stmt, error);
|
|
740
|
+
}
|
|
741
|
+
throw error;
|
|
742
|
+
}
|
|
743
|
+
},
|
|
744
|
+
async batch(stmts, mode) {
|
|
745
|
+
if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
|
|
746
|
+
if (!fallbackClient) {
|
|
747
|
+
throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
|
|
748
|
+
}
|
|
749
|
+
return fallbackClient.batch(stmts, mode);
|
|
750
|
+
}
|
|
751
|
+
try {
|
|
752
|
+
if (prisma.$transaction) {
|
|
753
|
+
return await prisma.$transaction(async (tx) => {
|
|
754
|
+
const results2 = [];
|
|
755
|
+
for (const stmt of stmts) {
|
|
756
|
+
results2.push(await executeOnPrisma(tx, stmt));
|
|
757
|
+
}
|
|
758
|
+
return results2;
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
const results = [];
|
|
762
|
+
for (const stmt of stmts) {
|
|
763
|
+
results.push(await executeOnPrisma(prisma, stmt));
|
|
764
|
+
}
|
|
765
|
+
return results;
|
|
766
|
+
} catch (error) {
|
|
767
|
+
if (fallbackClient && shouldFallbackOnError(error)) {
|
|
768
|
+
process.stderr.write(
|
|
769
|
+
`[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
770
|
+
`
|
|
771
|
+
);
|
|
772
|
+
return fallbackClient.batch(stmts, mode);
|
|
773
|
+
}
|
|
774
|
+
throw error;
|
|
775
|
+
}
|
|
776
|
+
},
|
|
777
|
+
async migrate(stmts) {
|
|
778
|
+
if (fallbackClient) {
|
|
779
|
+
return fallbackClient.migrate(stmts);
|
|
780
|
+
}
|
|
781
|
+
return adapter.batch(stmts, "deferred");
|
|
782
|
+
},
|
|
783
|
+
async transaction(mode) {
|
|
784
|
+
if (!fallbackClient) {
|
|
785
|
+
throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
|
|
786
|
+
}
|
|
787
|
+
return fallbackClient.transaction(mode);
|
|
788
|
+
},
|
|
789
|
+
async executeMultiple(sql) {
|
|
790
|
+
if (fallbackClient && shouldBypassPostgres(sql)) {
|
|
791
|
+
return fallbackClient.executeMultiple(sql);
|
|
792
|
+
}
|
|
793
|
+
for (const statement of splitSqlStatements(sql)) {
|
|
794
|
+
await adapter.execute(statement);
|
|
795
|
+
}
|
|
796
|
+
},
|
|
797
|
+
async sync() {
|
|
798
|
+
if (fallbackClient) {
|
|
799
|
+
return fallbackClient.sync();
|
|
800
|
+
}
|
|
801
|
+
return { frame_no: 0, frames_synced: 0 };
|
|
802
|
+
},
|
|
803
|
+
close() {
|
|
804
|
+
closed = true;
|
|
805
|
+
prismaClientPromise = null;
|
|
806
|
+
compatibilityBootstrapPromise = null;
|
|
807
|
+
void prisma.$disconnect?.();
|
|
808
|
+
},
|
|
809
|
+
get closed() {
|
|
810
|
+
return closed;
|
|
811
|
+
},
|
|
812
|
+
get protocol() {
|
|
813
|
+
return "prisma-postgres";
|
|
814
|
+
}
|
|
815
|
+
};
|
|
816
|
+
return adapter;
|
|
817
|
+
}
|
|
818
|
+
var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
|
|
819
|
+
var init_database_adapter = __esm({
|
|
820
|
+
"src/lib/database-adapter.ts"() {
|
|
821
|
+
"use strict";
|
|
822
|
+
VIEW_MAPPINGS = [
|
|
823
|
+
{ view: "memories", source: "memory.memory_records" },
|
|
824
|
+
{ view: "tasks", source: "memory.tasks" },
|
|
825
|
+
{ view: "behaviors", source: "memory.behaviors" },
|
|
826
|
+
{ view: "entities", source: "memory.entities" },
|
|
827
|
+
{ view: "relationships", source: "memory.relationships" },
|
|
828
|
+
{ view: "entity_memories", source: "memory.entity_memories" },
|
|
829
|
+
{ view: "entity_aliases", source: "memory.entity_aliases" },
|
|
830
|
+
{ view: "notifications", source: "memory.notifications" },
|
|
831
|
+
{ view: "messages", source: "memory.messages" },
|
|
832
|
+
{ view: "users", source: "wiki.users" },
|
|
833
|
+
{ view: "workspaces", source: "wiki.workspaces" },
|
|
834
|
+
{ view: "workspace_users", source: "wiki.workspace_users" },
|
|
835
|
+
{ view: "documents", source: "wiki.workspace_documents" },
|
|
836
|
+
{ view: "chats", source: "wiki.workspace_chats" }
|
|
837
|
+
];
|
|
838
|
+
UPSERT_KEYS = {
|
|
839
|
+
memories: ["id"],
|
|
840
|
+
tasks: ["id"],
|
|
841
|
+
behaviors: ["id"],
|
|
842
|
+
entities: ["id"],
|
|
843
|
+
relationships: ["id"],
|
|
844
|
+
entity_aliases: ["alias"],
|
|
845
|
+
notifications: ["id"],
|
|
846
|
+
messages: ["id"],
|
|
847
|
+
users: ["id"],
|
|
848
|
+
workspaces: ["id"],
|
|
849
|
+
workspace_users: ["id"],
|
|
850
|
+
documents: ["id"],
|
|
851
|
+
chats: ["id"]
|
|
852
|
+
};
|
|
853
|
+
BOOLEAN_COLUMNS_BY_TABLE = {
|
|
854
|
+
memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
|
|
855
|
+
behaviors: /* @__PURE__ */ new Set(["active"]),
|
|
856
|
+
notifications: /* @__PURE__ */ new Set(["read"]),
|
|
857
|
+
users: /* @__PURE__ */ new Set(["has_personal_memory"])
|
|
858
|
+
};
|
|
859
|
+
BOOLEAN_COLUMN_NAMES = new Set(
|
|
860
|
+
Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
|
|
861
|
+
);
|
|
862
|
+
IMMEDIATE_FALLBACK_PATTERNS = [
|
|
863
|
+
/\bPRAGMA\b/i,
|
|
864
|
+
/\bsqlite_master\b/i,
|
|
865
|
+
/(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
|
|
866
|
+
/\bMATCH\b/i,
|
|
867
|
+
/\bvector_distance_cos\s*\(/i,
|
|
868
|
+
/\bjson_extract\s*\(/i,
|
|
869
|
+
/\bjulianday\s*\(/i,
|
|
870
|
+
/\bstrftime\s*\(/i,
|
|
871
|
+
/\blast_insert_rowid\s*\(/i
|
|
872
|
+
];
|
|
873
|
+
prismaClientPromise = null;
|
|
874
|
+
compatibilityBootstrapPromise = null;
|
|
290
875
|
}
|
|
291
876
|
});
|
|
292
877
|
|
|
293
878
|
// src/lib/database.ts
|
|
294
879
|
import { createClient } from "@libsql/client";
|
|
295
880
|
async function initDatabase(config) {
|
|
881
|
+
if (_walCheckpointTimer) {
|
|
882
|
+
clearInterval(_walCheckpointTimer);
|
|
883
|
+
_walCheckpointTimer = null;
|
|
884
|
+
}
|
|
885
|
+
if (_daemonClient) {
|
|
886
|
+
_daemonClient.close();
|
|
887
|
+
_daemonClient = null;
|
|
888
|
+
}
|
|
889
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
890
|
+
_adapterClient.close();
|
|
891
|
+
}
|
|
892
|
+
_adapterClient = null;
|
|
296
893
|
if (_client) {
|
|
297
894
|
_client.close();
|
|
298
895
|
_client = null;
|
|
@@ -306,6 +903,7 @@ async function initDatabase(config) {
|
|
|
306
903
|
}
|
|
307
904
|
_client = createClient(opts);
|
|
308
905
|
_resilientClient = wrapWithRetry(_client);
|
|
906
|
+
_adapterClient = _resilientClient;
|
|
309
907
|
_client.execute("PRAGMA busy_timeout = 30000").catch(() => {
|
|
310
908
|
});
|
|
311
909
|
_client.execute("PRAGMA journal_mode = WAL").catch(() => {
|
|
@@ -316,11 +914,17 @@ async function initDatabase(config) {
|
|
|
316
914
|
});
|
|
317
915
|
}, 3e4);
|
|
318
916
|
_walCheckpointTimer.unref();
|
|
917
|
+
if (process.env.DATABASE_URL) {
|
|
918
|
+
_adapterClient = await createPrismaDbAdapter(_resilientClient);
|
|
919
|
+
}
|
|
319
920
|
}
|
|
320
921
|
function getClient() {
|
|
321
|
-
if (!
|
|
922
|
+
if (!_adapterClient) {
|
|
322
923
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
323
924
|
}
|
|
925
|
+
if (process.env.DATABASE_URL) {
|
|
926
|
+
return _adapterClient;
|
|
927
|
+
}
|
|
324
928
|
if (process.env.EXE_IS_DAEMON === "1") {
|
|
325
929
|
return _resilientClient;
|
|
326
930
|
}
|
|
@@ -1260,16 +1864,18 @@ async function ensureSchema() {
|
|
|
1260
1864
|
}
|
|
1261
1865
|
}
|
|
1262
1866
|
}
|
|
1263
|
-
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso;
|
|
1867
|
+
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso;
|
|
1264
1868
|
var init_database = __esm({
|
|
1265
1869
|
"src/lib/database.ts"() {
|
|
1266
1870
|
"use strict";
|
|
1267
1871
|
init_db_retry();
|
|
1268
1872
|
init_employees();
|
|
1873
|
+
init_database_adapter();
|
|
1269
1874
|
_client = null;
|
|
1270
1875
|
_resilientClient = null;
|
|
1271
1876
|
_walCheckpointTimer = null;
|
|
1272
1877
|
_daemonClient = null;
|
|
1878
|
+
_adapterClient = null;
|
|
1273
1879
|
initTurso = initDatabase;
|
|
1274
1880
|
}
|
|
1275
1881
|
});
|
|
@@ -1287,7 +1893,7 @@ __export(shard_manager_exports, {
|
|
|
1287
1893
|
listShards: () => listShards,
|
|
1288
1894
|
shardExists: () => shardExists
|
|
1289
1895
|
});
|
|
1290
|
-
import
|
|
1896
|
+
import path5 from "path";
|
|
1291
1897
|
import { existsSync as existsSync4, mkdirSync, readdirSync } from "fs";
|
|
1292
1898
|
import { createClient as createClient2 } from "@libsql/client";
|
|
1293
1899
|
function initShardManager(encryptionKey) {
|
|
@@ -1313,7 +1919,7 @@ function getShardClient(projectName) {
|
|
|
1313
1919
|
}
|
|
1314
1920
|
const cached = _shards.get(safeName);
|
|
1315
1921
|
if (cached) return cached;
|
|
1316
|
-
const dbPath =
|
|
1922
|
+
const dbPath = path5.join(SHARDS_DIR, `${safeName}.db`);
|
|
1317
1923
|
const client = createClient2({
|
|
1318
1924
|
url: `file:${dbPath}`,
|
|
1319
1925
|
encryptionKey: _encryptionKey
|
|
@@ -1323,7 +1929,7 @@ function getShardClient(projectName) {
|
|
|
1323
1929
|
}
|
|
1324
1930
|
function shardExists(projectName) {
|
|
1325
1931
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
1326
|
-
return existsSync4(
|
|
1932
|
+
return existsSync4(path5.join(SHARDS_DIR, `${safeName}.db`));
|
|
1327
1933
|
}
|
|
1328
1934
|
function listShards() {
|
|
1329
1935
|
if (!existsSync4(SHARDS_DIR)) return [];
|
|
@@ -1400,7 +2006,23 @@ async function ensureShardSchema(client) {
|
|
|
1400
2006
|
// MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
|
|
1401
2007
|
"ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
|
|
1402
2008
|
"ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
|
|
1403
|
-
"ALTER TABLE memories ADD COLUMN trajectory TEXT"
|
|
2009
|
+
"ALTER TABLE memories ADD COLUMN trajectory TEXT",
|
|
2010
|
+
// Metadata enrichment columns (must match database.ts)
|
|
2011
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
2012
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
2013
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
2014
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
2015
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
2016
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
2017
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
2018
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
2019
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
2020
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
2021
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
2022
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
2023
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
2024
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
2025
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
1404
2026
|
]) {
|
|
1405
2027
|
try {
|
|
1406
2028
|
await client.execute(col);
|
|
@@ -1512,7 +2134,7 @@ var init_shard_manager = __esm({
|
|
|
1512
2134
|
"src/lib/shard-manager.ts"() {
|
|
1513
2135
|
"use strict";
|
|
1514
2136
|
init_config();
|
|
1515
|
-
SHARDS_DIR =
|
|
2137
|
+
SHARDS_DIR = path5.join(EXE_AI_DIR, "shards");
|
|
1516
2138
|
_shards = /* @__PURE__ */ new Map();
|
|
1517
2139
|
_encryptionKey = null;
|
|
1518
2140
|
_shardingEnabled = false;
|
|
@@ -1708,11 +2330,11 @@ ${p.content}`).join("\n\n");
|
|
|
1708
2330
|
|
|
1709
2331
|
// src/lib/exe-daemon-client.ts
|
|
1710
2332
|
import net from "net";
|
|
1711
|
-
import
|
|
2333
|
+
import os5 from "os";
|
|
1712
2334
|
import { spawn } from "child_process";
|
|
1713
2335
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
1714
2336
|
import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
|
|
1715
|
-
import
|
|
2337
|
+
import path6 from "path";
|
|
1716
2338
|
import { fileURLToPath } from "url";
|
|
1717
2339
|
function handleData(chunk) {
|
|
1718
2340
|
_buffer += chunk.toString();
|
|
@@ -1763,17 +2385,17 @@ function cleanupStaleFiles() {
|
|
|
1763
2385
|
}
|
|
1764
2386
|
}
|
|
1765
2387
|
function findPackageRoot() {
|
|
1766
|
-
let dir =
|
|
1767
|
-
const { root } =
|
|
2388
|
+
let dir = path6.dirname(fileURLToPath(import.meta.url));
|
|
2389
|
+
const { root } = path6.parse(dir);
|
|
1768
2390
|
while (dir !== root) {
|
|
1769
|
-
if (existsSync5(
|
|
1770
|
-
dir =
|
|
2391
|
+
if (existsSync5(path6.join(dir, "package.json"))) return dir;
|
|
2392
|
+
dir = path6.dirname(dir);
|
|
1771
2393
|
}
|
|
1772
2394
|
return null;
|
|
1773
2395
|
}
|
|
1774
2396
|
function spawnDaemon() {
|
|
1775
|
-
const freeGB =
|
|
1776
|
-
const totalGB =
|
|
2397
|
+
const freeGB = os5.freemem() / (1024 * 1024 * 1024);
|
|
2398
|
+
const totalGB = os5.totalmem() / (1024 * 1024 * 1024);
|
|
1777
2399
|
if (totalGB <= 8) {
|
|
1778
2400
|
process.stderr.write(
|
|
1779
2401
|
`[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
|
|
@@ -1793,7 +2415,7 @@ function spawnDaemon() {
|
|
|
1793
2415
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
1794
2416
|
return;
|
|
1795
2417
|
}
|
|
1796
|
-
const daemonPath =
|
|
2418
|
+
const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
1797
2419
|
if (!existsSync5(daemonPath)) {
|
|
1798
2420
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
1799
2421
|
`);
|
|
@@ -1802,7 +2424,7 @@ function spawnDaemon() {
|
|
|
1802
2424
|
const resolvedPath = daemonPath;
|
|
1803
2425
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
1804
2426
|
`);
|
|
1805
|
-
const logPath =
|
|
2427
|
+
const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
|
|
1806
2428
|
let stderrFd = "ignore";
|
|
1807
2429
|
try {
|
|
1808
2430
|
stderrFd = openSync(logPath, "a");
|
|
@@ -1953,83 +2575,132 @@ async function pingDaemon() {
|
|
|
1953
2575
|
return null;
|
|
1954
2576
|
}
|
|
1955
2577
|
function killAndRespawnDaemon() {
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
2578
|
+
if (!acquireSpawnLock()) {
|
|
2579
|
+
process.stderr.write("[exed-client] Another process is already restarting daemon \u2014 skipping\n");
|
|
2580
|
+
if (_socket) {
|
|
2581
|
+
_socket.destroy();
|
|
2582
|
+
_socket = null;
|
|
2583
|
+
}
|
|
2584
|
+
_connected = false;
|
|
2585
|
+
_buffer = "";
|
|
2586
|
+
return;
|
|
2587
|
+
}
|
|
2588
|
+
try {
|
|
2589
|
+
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
2590
|
+
if (existsSync5(PID_PATH)) {
|
|
2591
|
+
try {
|
|
2592
|
+
const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
|
|
2593
|
+
if (pid > 0) {
|
|
2594
|
+
try {
|
|
2595
|
+
process.kill(pid, "SIGKILL");
|
|
2596
|
+
} catch {
|
|
2597
|
+
}
|
|
1964
2598
|
}
|
|
2599
|
+
} catch {
|
|
1965
2600
|
}
|
|
2601
|
+
}
|
|
2602
|
+
if (_socket) {
|
|
2603
|
+
_socket.destroy();
|
|
2604
|
+
_socket = null;
|
|
2605
|
+
}
|
|
2606
|
+
_connected = false;
|
|
2607
|
+
_buffer = "";
|
|
2608
|
+
try {
|
|
2609
|
+
unlinkSync2(PID_PATH);
|
|
1966
2610
|
} catch {
|
|
1967
2611
|
}
|
|
2612
|
+
try {
|
|
2613
|
+
unlinkSync2(SOCKET_PATH);
|
|
2614
|
+
} catch {
|
|
2615
|
+
}
|
|
2616
|
+
spawnDaemon();
|
|
2617
|
+
} finally {
|
|
2618
|
+
releaseSpawnLock();
|
|
1968
2619
|
}
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
_socket = null;
|
|
1972
|
-
}
|
|
1973
|
-
_connected = false;
|
|
1974
|
-
_buffer = "";
|
|
2620
|
+
}
|
|
2621
|
+
function isDaemonTooYoung() {
|
|
1975
2622
|
try {
|
|
1976
|
-
|
|
2623
|
+
const stat = statSync(PID_PATH);
|
|
2624
|
+
return Date.now() - stat.mtimeMs < MIN_DAEMON_AGE_MS;
|
|
1977
2625
|
} catch {
|
|
2626
|
+
return false;
|
|
1978
2627
|
}
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
2628
|
+
}
|
|
2629
|
+
async function retryThenRestart(doRequest, label) {
|
|
2630
|
+
const result = await doRequest();
|
|
2631
|
+
if (!result.error) {
|
|
2632
|
+
_consecutiveFailures = 0;
|
|
2633
|
+
return result;
|
|
2634
|
+
}
|
|
2635
|
+
_consecutiveFailures++;
|
|
2636
|
+
for (let i = 0; i < MAX_RETRIES_BEFORE_RESTART; i++) {
|
|
2637
|
+
const delayMs = RETRY_DELAYS_MS[i] ?? 5e3;
|
|
2638
|
+
process.stderr.write(`[exed-client] ${label} failed (${result.error}), retry ${i + 1}/${MAX_RETRIES_BEFORE_RESTART} in ${delayMs}ms
|
|
2639
|
+
`);
|
|
2640
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
2641
|
+
if (!_connected) {
|
|
2642
|
+
if (!await connectToSocket()) continue;
|
|
2643
|
+
}
|
|
2644
|
+
const retry = await doRequest();
|
|
2645
|
+
if (!retry.error) {
|
|
2646
|
+
_consecutiveFailures = 0;
|
|
2647
|
+
return retry;
|
|
2648
|
+
}
|
|
2649
|
+
_consecutiveFailures++;
|
|
1982
2650
|
}
|
|
1983
|
-
|
|
2651
|
+
if (isDaemonTooYoung()) {
|
|
2652
|
+
process.stderr.write(`[exed-client] ${label}: daemon too young (< ${MIN_DAEMON_AGE_MS / 1e3}s) \u2014 skipping restart
|
|
2653
|
+
`);
|
|
2654
|
+
return { error: result.error };
|
|
2655
|
+
}
|
|
2656
|
+
process.stderr.write(`[exed-client] ${label}: ${_consecutiveFailures} consecutive failures \u2014 restarting daemon
|
|
2657
|
+
`);
|
|
2658
|
+
killAndRespawnDaemon();
|
|
2659
|
+
const start = Date.now();
|
|
2660
|
+
let delay2 = 200;
|
|
2661
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
2662
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
2663
|
+
if (await connectToSocket()) break;
|
|
2664
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
2665
|
+
}
|
|
2666
|
+
if (!_connected) return { error: "Daemon restart failed" };
|
|
2667
|
+
const final = await doRequest();
|
|
2668
|
+
if (!final.error) _consecutiveFailures = 0;
|
|
2669
|
+
return final;
|
|
1984
2670
|
}
|
|
1985
2671
|
async function embedViaClient(text, priority = "high") {
|
|
1986
2672
|
if (!_connected && !await connectEmbedDaemon()) return null;
|
|
1987
2673
|
_requestCount++;
|
|
1988
2674
|
if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
|
|
1989
2675
|
const health = await pingDaemon();
|
|
1990
|
-
if (!health) {
|
|
2676
|
+
if (!health && !isDaemonTooYoung()) {
|
|
1991
2677
|
process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
|
|
1992
2678
|
`);
|
|
1993
2679
|
killAndRespawnDaemon();
|
|
1994
2680
|
const start = Date.now();
|
|
1995
|
-
let
|
|
2681
|
+
let d = 200;
|
|
1996
2682
|
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
1997
|
-
await new Promise((r) => setTimeout(r,
|
|
2683
|
+
await new Promise((r) => setTimeout(r, d));
|
|
1998
2684
|
if (await connectToSocket()) break;
|
|
1999
|
-
|
|
2685
|
+
d = Math.min(d * 2, 3e3);
|
|
2000
2686
|
}
|
|
2001
2687
|
if (!_connected) return null;
|
|
2002
2688
|
}
|
|
2003
2689
|
}
|
|
2004
|
-
const result = await
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
killAndRespawnDaemon();
|
|
2010
|
-
const start = Date.now();
|
|
2011
|
-
let delay2 = 200;
|
|
2012
|
-
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
2013
|
-
await new Promise((r) => setTimeout(r, delay2));
|
|
2014
|
-
if (await connectToSocket()) break;
|
|
2015
|
-
delay2 = Math.min(delay2 * 2, 3e3);
|
|
2016
|
-
}
|
|
2017
|
-
if (!_connected) return null;
|
|
2018
|
-
const retry = await sendRequest([text], priority);
|
|
2019
|
-
if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
|
|
2020
|
-
process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
|
|
2021
|
-
`);
|
|
2022
|
-
}
|
|
2023
|
-
return null;
|
|
2690
|
+
const result = await retryThenRestart(
|
|
2691
|
+
() => sendRequest([text], priority),
|
|
2692
|
+
"Embed"
|
|
2693
|
+
);
|
|
2694
|
+
return !result.error && result.vectors?.[0] ? result.vectors[0] : null;
|
|
2024
2695
|
}
|
|
2025
|
-
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, HEALTH_CHECK_INTERVAL, _pending, MAX_BUFFER;
|
|
2696
|
+
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, _consecutiveFailures, HEALTH_CHECK_INTERVAL, MAX_RETRIES_BEFORE_RESTART, RETRY_DELAYS_MS, MIN_DAEMON_AGE_MS, _pending, MAX_BUFFER;
|
|
2026
2697
|
var init_exe_daemon_client = __esm({
|
|
2027
2698
|
"src/lib/exe-daemon-client.ts"() {
|
|
2028
2699
|
"use strict";
|
|
2029
2700
|
init_config();
|
|
2030
|
-
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ??
|
|
2031
|
-
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ??
|
|
2032
|
-
SPAWN_LOCK_PATH =
|
|
2701
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
|
|
2702
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
|
|
2703
|
+
SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
2033
2704
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
2034
2705
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
2035
2706
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
@@ -2037,7 +2708,11 @@ var init_exe_daemon_client = __esm({
|
|
|
2037
2708
|
_connected = false;
|
|
2038
2709
|
_buffer = "";
|
|
2039
2710
|
_requestCount = 0;
|
|
2711
|
+
_consecutiveFailures = 0;
|
|
2040
2712
|
HEALTH_CHECK_INTERVAL = 100;
|
|
2713
|
+
MAX_RETRIES_BEFORE_RESTART = 3;
|
|
2714
|
+
RETRY_DELAYS_MS = [1e3, 3e3, 5e3];
|
|
2715
|
+
MIN_DAEMON_AGE_MS = 3e4;
|
|
2041
2716
|
_pending = /* @__PURE__ */ new Map();
|
|
2042
2717
|
MAX_BUFFER = 1e7;
|
|
2043
2718
|
}
|
|
@@ -2050,15 +2725,15 @@ init_database();
|
|
|
2050
2725
|
// src/lib/keychain.ts
|
|
2051
2726
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
2052
2727
|
import { existsSync as existsSync3 } from "fs";
|
|
2053
|
-
import
|
|
2054
|
-
import
|
|
2728
|
+
import path4 from "path";
|
|
2729
|
+
import os4 from "os";
|
|
2055
2730
|
var SERVICE = "exe-mem";
|
|
2056
2731
|
var ACCOUNT = "master-key";
|
|
2057
2732
|
function getKeyDir() {
|
|
2058
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
2733
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path4.join(os4.homedir(), ".exe-os");
|
|
2059
2734
|
}
|
|
2060
2735
|
function getKeyPath() {
|
|
2061
|
-
return
|
|
2736
|
+
return path4.join(getKeyDir(), "master.key");
|
|
2062
2737
|
}
|
|
2063
2738
|
async function tryKeytar() {
|
|
2064
2739
|
try {
|
|
@@ -2081,7 +2756,7 @@ async function getMasterKey() {
|
|
|
2081
2756
|
const keyPath = getKeyPath();
|
|
2082
2757
|
if (!existsSync3(keyPath)) {
|
|
2083
2758
|
process.stderr.write(
|
|
2084
|
-
`[keychain] Key not found at ${keyPath} (HOME=${
|
|
2759
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os4.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
2085
2760
|
`
|
|
2086
2761
|
);
|
|
2087
2762
|
return null;
|
|
@@ -2256,27 +2931,27 @@ function isMainModule(importMetaUrl) {
|
|
|
2256
2931
|
|
|
2257
2932
|
// src/bin/backfill-vectors.ts
|
|
2258
2933
|
import { existsSync as existsSync7, unlinkSync as unlinkSync4 } from "fs";
|
|
2259
|
-
import
|
|
2934
|
+
import path8 from "path";
|
|
2260
2935
|
|
|
2261
2936
|
// src/lib/worker-gate.ts
|
|
2262
2937
|
init_config();
|
|
2263
2938
|
import { readdirSync as readdirSync2, writeFileSync as writeFileSync2, unlinkSync as unlinkSync3, mkdirSync as mkdirSync2, existsSync as existsSync6 } from "fs";
|
|
2264
|
-
import
|
|
2265
|
-
var WORKER_PID_DIR =
|
|
2939
|
+
import path7 from "path";
|
|
2940
|
+
var WORKER_PID_DIR = path7.join(EXE_AI_DIR, "worker-pids");
|
|
2266
2941
|
function registerWorkerPid(pid) {
|
|
2267
2942
|
try {
|
|
2268
2943
|
mkdirSync2(WORKER_PID_DIR, { recursive: true });
|
|
2269
|
-
writeFileSync2(
|
|
2944
|
+
writeFileSync2(path7.join(WORKER_PID_DIR, `worker-${pid}.pid`), String(pid));
|
|
2270
2945
|
} catch {
|
|
2271
2946
|
}
|
|
2272
2947
|
}
|
|
2273
2948
|
function cleanupWorkerPid() {
|
|
2274
2949
|
try {
|
|
2275
|
-
unlinkSync3(
|
|
2950
|
+
unlinkSync3(path7.join(WORKER_PID_DIR, `worker-${process.pid}.pid`));
|
|
2276
2951
|
} catch {
|
|
2277
2952
|
}
|
|
2278
2953
|
}
|
|
2279
|
-
var BACKFILL_LOCK =
|
|
2954
|
+
var BACKFILL_LOCK = path7.join(WORKER_PID_DIR, "backfill.lock");
|
|
2280
2955
|
function tryAcquireBackfillLock() {
|
|
2281
2956
|
try {
|
|
2282
2957
|
mkdirSync2(WORKER_PID_DIR, { recursive: true });
|
|
@@ -2311,7 +2986,7 @@ function releaseBackfillLock() {
|
|
|
2311
2986
|
|
|
2312
2987
|
// src/bin/backfill-vectors.ts
|
|
2313
2988
|
var BATCH_SIZE = 100;
|
|
2314
|
-
var BACKFILL_FLAG =
|
|
2989
|
+
var BACKFILL_FLAG = path8.join(EXE_AI_DIR, "session-cache", "needs-backfill");
|
|
2315
2990
|
async function backfillVectors() {
|
|
2316
2991
|
if (!tryAcquireBackfillLock()) {
|
|
2317
2992
|
process.stderr.write("[backfill] Another backfill is already running \u2014 exiting\n");
|