@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
|
@@ -355,7 +355,7 @@ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
|
355
355
|
return [];
|
|
356
356
|
}
|
|
357
357
|
}
|
|
358
|
-
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE;
|
|
358
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, IDENTITY_DIR;
|
|
359
359
|
var init_employees = __esm({
|
|
360
360
|
"src/lib/employees.ts"() {
|
|
361
361
|
"use strict";
|
|
@@ -363,12 +363,609 @@ var init_employees = __esm({
|
|
|
363
363
|
EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
|
|
364
364
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
365
365
|
COORDINATOR_ROLE = "COO";
|
|
366
|
+
IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
// src/lib/database-adapter.ts
|
|
371
|
+
import os3 from "os";
|
|
372
|
+
import path4 from "path";
|
|
373
|
+
import { createRequire } from "module";
|
|
374
|
+
import { pathToFileURL } from "url";
|
|
375
|
+
function quotedIdentifier(identifier) {
|
|
376
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
377
|
+
}
|
|
378
|
+
function unqualifiedTableName(name) {
|
|
379
|
+
const raw = name.trim().replace(/^"|"$/g, "");
|
|
380
|
+
const parts = raw.split(".");
|
|
381
|
+
return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
|
|
382
|
+
}
|
|
383
|
+
function stripTrailingSemicolon(sql) {
|
|
384
|
+
return sql.trim().replace(/;+\s*$/u, "");
|
|
385
|
+
}
|
|
386
|
+
function appendClause(sql, clause) {
|
|
387
|
+
const trimmed = stripTrailingSemicolon(sql);
|
|
388
|
+
const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
|
|
389
|
+
if (!returningMatch) {
|
|
390
|
+
return `${trimmed}${clause}`;
|
|
391
|
+
}
|
|
392
|
+
const idx = returningMatch.index;
|
|
393
|
+
return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
|
|
394
|
+
}
|
|
395
|
+
function normalizeStatement(stmt) {
|
|
396
|
+
if (typeof stmt === "string") {
|
|
397
|
+
return { kind: "positional", sql: stmt, args: [] };
|
|
398
|
+
}
|
|
399
|
+
const sql = stmt.sql;
|
|
400
|
+
if (Array.isArray(stmt.args) || stmt.args === void 0) {
|
|
401
|
+
return { kind: "positional", sql, args: stmt.args ?? [] };
|
|
402
|
+
}
|
|
403
|
+
return { kind: "named", sql, args: stmt.args };
|
|
404
|
+
}
|
|
405
|
+
function rewriteBooleanLiterals(sql) {
|
|
406
|
+
let out = sql;
|
|
407
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
408
|
+
const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
|
|
409
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
|
|
410
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
|
|
411
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
|
|
412
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
|
|
413
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
|
|
414
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
|
|
415
|
+
}
|
|
416
|
+
return out;
|
|
417
|
+
}
|
|
418
|
+
function rewriteInsertOrIgnore(sql) {
|
|
419
|
+
if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
|
|
420
|
+
return sql;
|
|
421
|
+
}
|
|
422
|
+
const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
|
|
423
|
+
return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
|
|
424
|
+
}
|
|
425
|
+
function rewriteInsertOrReplace(sql) {
|
|
426
|
+
const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
|
|
427
|
+
if (!match) {
|
|
428
|
+
return sql;
|
|
429
|
+
}
|
|
430
|
+
const rawTable = match[1];
|
|
431
|
+
const rawColumns = match[2];
|
|
432
|
+
const remainder = match[3];
|
|
433
|
+
const tableName = unqualifiedTableName(rawTable);
|
|
434
|
+
const conflictKeys = UPSERT_KEYS[tableName];
|
|
435
|
+
if (!conflictKeys?.length) {
|
|
436
|
+
return sql;
|
|
437
|
+
}
|
|
438
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
439
|
+
const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
|
|
440
|
+
const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
|
|
441
|
+
const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
|
|
442
|
+
return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
|
|
443
|
+
}
|
|
444
|
+
function rewriteSql(sql) {
|
|
445
|
+
let out = sql;
|
|
446
|
+
out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
|
|
447
|
+
out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
|
|
448
|
+
out = rewriteBooleanLiterals(out);
|
|
449
|
+
out = rewriteInsertOrReplace(out);
|
|
450
|
+
out = rewriteInsertOrIgnore(out);
|
|
451
|
+
return stripTrailingSemicolon(out);
|
|
452
|
+
}
|
|
453
|
+
function toBoolean(value) {
|
|
454
|
+
if (value === null || value === void 0) return value;
|
|
455
|
+
if (typeof value === "boolean") return value;
|
|
456
|
+
if (typeof value === "number") return value !== 0;
|
|
457
|
+
if (typeof value === "bigint") return value !== 0n;
|
|
458
|
+
if (typeof value === "string") {
|
|
459
|
+
const normalized = value.trim().toLowerCase();
|
|
460
|
+
if (normalized === "0" || normalized === "false") return false;
|
|
461
|
+
if (normalized === "1" || normalized === "true") return true;
|
|
462
|
+
}
|
|
463
|
+
return Boolean(value);
|
|
464
|
+
}
|
|
465
|
+
function countQuestionMarks(sql, end) {
|
|
466
|
+
let count = 0;
|
|
467
|
+
let inSingle = false;
|
|
468
|
+
let inDouble = false;
|
|
469
|
+
let inLineComment = false;
|
|
470
|
+
let inBlockComment = false;
|
|
471
|
+
for (let i = 0; i < end; i++) {
|
|
472
|
+
const ch = sql[i];
|
|
473
|
+
const next = sql[i + 1];
|
|
474
|
+
if (inLineComment) {
|
|
475
|
+
if (ch === "\n") inLineComment = false;
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
if (inBlockComment) {
|
|
479
|
+
if (ch === "*" && next === "/") {
|
|
480
|
+
inBlockComment = false;
|
|
481
|
+
i += 1;
|
|
482
|
+
}
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
485
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
486
|
+
inLineComment = true;
|
|
487
|
+
i += 1;
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
491
|
+
inBlockComment = true;
|
|
492
|
+
i += 1;
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
496
|
+
inSingle = !inSingle;
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
500
|
+
inDouble = !inDouble;
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
504
|
+
count += 1;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return count;
|
|
508
|
+
}
|
|
509
|
+
function findBooleanPlaceholderIndexes(sql) {
|
|
510
|
+
const indexes = /* @__PURE__ */ new Set();
|
|
511
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
512
|
+
const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
|
|
513
|
+
for (const match of sql.matchAll(pattern)) {
|
|
514
|
+
const matchText = match[0];
|
|
515
|
+
const qIndex = match.index + matchText.lastIndexOf("?");
|
|
516
|
+
indexes.add(countQuestionMarks(sql, qIndex + 1));
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
return indexes;
|
|
520
|
+
}
|
|
521
|
+
function coerceInsertBooleanArgs(sql, args) {
|
|
522
|
+
const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
|
|
523
|
+
if (!match) return;
|
|
524
|
+
const rawTable = match[1];
|
|
525
|
+
const rawColumns = match[2];
|
|
526
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
527
|
+
if (!boolColumns?.size) return;
|
|
528
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
529
|
+
for (const [index, column] of columns.entries()) {
|
|
530
|
+
if (boolColumns.has(column) && index < args.length) {
|
|
531
|
+
args[index] = toBoolean(args[index]);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
function coerceUpdateBooleanArgs(sql, args) {
|
|
536
|
+
const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
|
|
537
|
+
if (!match) return;
|
|
538
|
+
const rawTable = match[1];
|
|
539
|
+
const setClause = match[2];
|
|
540
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
541
|
+
if (!boolColumns?.size) return;
|
|
542
|
+
const assignments = setClause.split(",");
|
|
543
|
+
let placeholderIndex = 0;
|
|
544
|
+
for (const assignment of assignments) {
|
|
545
|
+
if (!assignment.includes("?")) continue;
|
|
546
|
+
placeholderIndex += 1;
|
|
547
|
+
const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
|
|
548
|
+
if (colMatch && boolColumns.has(colMatch[1])) {
|
|
549
|
+
args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
function coerceBooleanArgs(sql, args) {
|
|
554
|
+
const nextArgs = [...args];
|
|
555
|
+
coerceInsertBooleanArgs(sql, nextArgs);
|
|
556
|
+
coerceUpdateBooleanArgs(sql, nextArgs);
|
|
557
|
+
const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
|
|
558
|
+
for (const index of placeholderIndexes) {
|
|
559
|
+
if (index > 0 && index <= nextArgs.length) {
|
|
560
|
+
nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
return nextArgs;
|
|
564
|
+
}
|
|
565
|
+
function convertQuestionMarksToDollarParams(sql) {
|
|
566
|
+
let out = "";
|
|
567
|
+
let placeholder = 0;
|
|
568
|
+
let inSingle = false;
|
|
569
|
+
let inDouble = false;
|
|
570
|
+
let inLineComment = false;
|
|
571
|
+
let inBlockComment = false;
|
|
572
|
+
for (let i = 0; i < sql.length; i++) {
|
|
573
|
+
const ch = sql[i];
|
|
574
|
+
const next = sql[i + 1];
|
|
575
|
+
if (inLineComment) {
|
|
576
|
+
out += ch;
|
|
577
|
+
if (ch === "\n") inLineComment = false;
|
|
578
|
+
continue;
|
|
579
|
+
}
|
|
580
|
+
if (inBlockComment) {
|
|
581
|
+
out += ch;
|
|
582
|
+
if (ch === "*" && next === "/") {
|
|
583
|
+
out += next;
|
|
584
|
+
inBlockComment = false;
|
|
585
|
+
i += 1;
|
|
586
|
+
}
|
|
587
|
+
continue;
|
|
588
|
+
}
|
|
589
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
590
|
+
out += ch + next;
|
|
591
|
+
inLineComment = true;
|
|
592
|
+
i += 1;
|
|
593
|
+
continue;
|
|
594
|
+
}
|
|
595
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
596
|
+
out += ch + next;
|
|
597
|
+
inBlockComment = true;
|
|
598
|
+
i += 1;
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
601
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
602
|
+
inSingle = !inSingle;
|
|
603
|
+
out += ch;
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
606
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
607
|
+
inDouble = !inDouble;
|
|
608
|
+
out += ch;
|
|
609
|
+
continue;
|
|
610
|
+
}
|
|
611
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
612
|
+
placeholder += 1;
|
|
613
|
+
out += `$${placeholder}`;
|
|
614
|
+
continue;
|
|
615
|
+
}
|
|
616
|
+
out += ch;
|
|
617
|
+
}
|
|
618
|
+
return out;
|
|
619
|
+
}
|
|
620
|
+
function translateStatementForPostgres(stmt) {
|
|
621
|
+
const normalized = normalizeStatement(stmt);
|
|
622
|
+
if (normalized.kind === "named") {
|
|
623
|
+
throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
|
|
624
|
+
}
|
|
625
|
+
const rewrittenSql = rewriteSql(normalized.sql);
|
|
626
|
+
const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
|
|
627
|
+
return {
|
|
628
|
+
sql: convertQuestionMarksToDollarParams(rewrittenSql),
|
|
629
|
+
args: coercedArgs
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
function shouldBypassPostgres(stmt) {
|
|
633
|
+
const normalized = normalizeStatement(stmt);
|
|
634
|
+
if (normalized.kind === "named") {
|
|
635
|
+
return true;
|
|
636
|
+
}
|
|
637
|
+
return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
|
|
638
|
+
}
|
|
639
|
+
function shouldFallbackOnError(error) {
|
|
640
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
641
|
+
return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
|
|
642
|
+
}
|
|
643
|
+
function isReadQuery(sql) {
|
|
644
|
+
const trimmed = sql.trimStart();
|
|
645
|
+
return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
|
|
646
|
+
}
|
|
647
|
+
function buildRow(row, columns) {
|
|
648
|
+
const values = columns.map((column) => row[column]);
|
|
649
|
+
return Object.assign(values, row);
|
|
650
|
+
}
|
|
651
|
+
function buildResultSet(rows, rowsAffected = 0) {
|
|
652
|
+
const columns = rows[0] ? Object.keys(rows[0]) : [];
|
|
653
|
+
const resultRows = rows.map((row) => buildRow(row, columns));
|
|
654
|
+
return {
|
|
655
|
+
columns,
|
|
656
|
+
columnTypes: columns.map(() => ""),
|
|
657
|
+
rows: resultRows,
|
|
658
|
+
rowsAffected,
|
|
659
|
+
lastInsertRowid: void 0,
|
|
660
|
+
toJSON() {
|
|
661
|
+
return {
|
|
662
|
+
columns,
|
|
663
|
+
columnTypes: columns.map(() => ""),
|
|
664
|
+
rows,
|
|
665
|
+
rowsAffected,
|
|
666
|
+
lastInsertRowid: void 0
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
async function loadPrismaClient() {
|
|
672
|
+
if (!prismaClientPromise) {
|
|
673
|
+
prismaClientPromise = (async () => {
|
|
674
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
675
|
+
if (explicitPath) {
|
|
676
|
+
const module2 = await import(pathToFileURL(explicitPath).href);
|
|
677
|
+
const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
|
|
678
|
+
if (!PrismaClient2) {
|
|
679
|
+
throw new Error(`No PrismaClient export found at ${explicitPath}`);
|
|
680
|
+
}
|
|
681
|
+
return new PrismaClient2();
|
|
682
|
+
}
|
|
683
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path4.join(os3.homedir(), "exe-db");
|
|
684
|
+
const requireFromExeDb = createRequire(path4.join(exeDbRoot, "package.json"));
|
|
685
|
+
const prismaEntry = requireFromExeDb.resolve("@prisma/client");
|
|
686
|
+
const module = await import(pathToFileURL(prismaEntry).href);
|
|
687
|
+
const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
|
|
688
|
+
if (!PrismaClient) {
|
|
689
|
+
throw new Error(`No PrismaClient export found in ${prismaEntry}`);
|
|
690
|
+
}
|
|
691
|
+
return new PrismaClient();
|
|
692
|
+
})();
|
|
693
|
+
}
|
|
694
|
+
return prismaClientPromise;
|
|
695
|
+
}
|
|
696
|
+
async function ensureCompatibilityViews(prisma) {
|
|
697
|
+
if (!compatibilityBootstrapPromise) {
|
|
698
|
+
compatibilityBootstrapPromise = (async () => {
|
|
699
|
+
for (const mapping of VIEW_MAPPINGS) {
|
|
700
|
+
const relation = mapping.source.replace(/"/g, "");
|
|
701
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
702
|
+
"SELECT to_regclass($1) AS regclass",
|
|
703
|
+
relation
|
|
704
|
+
);
|
|
705
|
+
if (!rows[0]?.regclass) {
|
|
706
|
+
continue;
|
|
707
|
+
}
|
|
708
|
+
await prisma.$executeRawUnsafe(
|
|
709
|
+
`CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
})();
|
|
713
|
+
}
|
|
714
|
+
return compatibilityBootstrapPromise;
|
|
715
|
+
}
|
|
716
|
+
async function executeOnPrisma(executor, stmt) {
|
|
717
|
+
const translated = translateStatementForPostgres(stmt);
|
|
718
|
+
if (isReadQuery(translated.sql)) {
|
|
719
|
+
const rows = await executor.$queryRawUnsafe(
|
|
720
|
+
translated.sql,
|
|
721
|
+
...translated.args
|
|
722
|
+
);
|
|
723
|
+
return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
|
|
724
|
+
}
|
|
725
|
+
const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
|
|
726
|
+
return buildResultSet([], rowsAffected);
|
|
727
|
+
}
|
|
728
|
+
function splitSqlStatements(sql) {
|
|
729
|
+
const parts = [];
|
|
730
|
+
let current = "";
|
|
731
|
+
let inSingle = false;
|
|
732
|
+
let inDouble = false;
|
|
733
|
+
let inLineComment = false;
|
|
734
|
+
let inBlockComment = false;
|
|
735
|
+
for (let i = 0; i < sql.length; i++) {
|
|
736
|
+
const ch = sql[i];
|
|
737
|
+
const next = sql[i + 1];
|
|
738
|
+
if (inLineComment) {
|
|
739
|
+
current += ch;
|
|
740
|
+
if (ch === "\n") inLineComment = false;
|
|
741
|
+
continue;
|
|
742
|
+
}
|
|
743
|
+
if (inBlockComment) {
|
|
744
|
+
current += ch;
|
|
745
|
+
if (ch === "*" && next === "/") {
|
|
746
|
+
current += next;
|
|
747
|
+
inBlockComment = false;
|
|
748
|
+
i += 1;
|
|
749
|
+
}
|
|
750
|
+
continue;
|
|
751
|
+
}
|
|
752
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
753
|
+
current += ch + next;
|
|
754
|
+
inLineComment = true;
|
|
755
|
+
i += 1;
|
|
756
|
+
continue;
|
|
757
|
+
}
|
|
758
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
759
|
+
current += ch + next;
|
|
760
|
+
inBlockComment = true;
|
|
761
|
+
i += 1;
|
|
762
|
+
continue;
|
|
763
|
+
}
|
|
764
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
765
|
+
inSingle = !inSingle;
|
|
766
|
+
current += ch;
|
|
767
|
+
continue;
|
|
768
|
+
}
|
|
769
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
770
|
+
inDouble = !inDouble;
|
|
771
|
+
current += ch;
|
|
772
|
+
continue;
|
|
773
|
+
}
|
|
774
|
+
if (!inSingle && !inDouble && ch === ";") {
|
|
775
|
+
if (current.trim()) {
|
|
776
|
+
parts.push(current.trim());
|
|
777
|
+
}
|
|
778
|
+
current = "";
|
|
779
|
+
continue;
|
|
780
|
+
}
|
|
781
|
+
current += ch;
|
|
782
|
+
}
|
|
783
|
+
if (current.trim()) {
|
|
784
|
+
parts.push(current.trim());
|
|
785
|
+
}
|
|
786
|
+
return parts;
|
|
787
|
+
}
|
|
788
|
+
async function createPrismaDbAdapter(fallbackClient) {
|
|
789
|
+
const prisma = await loadPrismaClient();
|
|
790
|
+
await ensureCompatibilityViews(prisma);
|
|
791
|
+
let closed = false;
|
|
792
|
+
let adapter;
|
|
793
|
+
const fallbackExecute = async (stmt, error) => {
|
|
794
|
+
if (!fallbackClient) {
|
|
795
|
+
if (error) throw error;
|
|
796
|
+
throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
|
|
797
|
+
}
|
|
798
|
+
if (error) {
|
|
799
|
+
process.stderr.write(
|
|
800
|
+
`[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
801
|
+
`
|
|
802
|
+
);
|
|
803
|
+
}
|
|
804
|
+
return fallbackClient.execute(stmt);
|
|
805
|
+
};
|
|
806
|
+
adapter = {
|
|
807
|
+
async execute(stmt) {
|
|
808
|
+
if (shouldBypassPostgres(stmt)) {
|
|
809
|
+
return fallbackExecute(stmt);
|
|
810
|
+
}
|
|
811
|
+
try {
|
|
812
|
+
return await executeOnPrisma(prisma, stmt);
|
|
813
|
+
} catch (error) {
|
|
814
|
+
if (shouldFallbackOnError(error)) {
|
|
815
|
+
return fallbackExecute(stmt, error);
|
|
816
|
+
}
|
|
817
|
+
throw error;
|
|
818
|
+
}
|
|
819
|
+
},
|
|
820
|
+
async batch(stmts, mode) {
|
|
821
|
+
if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
|
|
822
|
+
if (!fallbackClient) {
|
|
823
|
+
throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
|
|
824
|
+
}
|
|
825
|
+
return fallbackClient.batch(stmts, mode);
|
|
826
|
+
}
|
|
827
|
+
try {
|
|
828
|
+
if (prisma.$transaction) {
|
|
829
|
+
return await prisma.$transaction(async (tx) => {
|
|
830
|
+
const results2 = [];
|
|
831
|
+
for (const stmt of stmts) {
|
|
832
|
+
results2.push(await executeOnPrisma(tx, stmt));
|
|
833
|
+
}
|
|
834
|
+
return results2;
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
const results = [];
|
|
838
|
+
for (const stmt of stmts) {
|
|
839
|
+
results.push(await executeOnPrisma(prisma, stmt));
|
|
840
|
+
}
|
|
841
|
+
return results;
|
|
842
|
+
} catch (error) {
|
|
843
|
+
if (fallbackClient && shouldFallbackOnError(error)) {
|
|
844
|
+
process.stderr.write(
|
|
845
|
+
`[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
846
|
+
`
|
|
847
|
+
);
|
|
848
|
+
return fallbackClient.batch(stmts, mode);
|
|
849
|
+
}
|
|
850
|
+
throw error;
|
|
851
|
+
}
|
|
852
|
+
},
|
|
853
|
+
async migrate(stmts) {
|
|
854
|
+
if (fallbackClient) {
|
|
855
|
+
return fallbackClient.migrate(stmts);
|
|
856
|
+
}
|
|
857
|
+
return adapter.batch(stmts, "deferred");
|
|
858
|
+
},
|
|
859
|
+
async transaction(mode) {
|
|
860
|
+
if (!fallbackClient) {
|
|
861
|
+
throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
|
|
862
|
+
}
|
|
863
|
+
return fallbackClient.transaction(mode);
|
|
864
|
+
},
|
|
865
|
+
async executeMultiple(sql) {
|
|
866
|
+
if (fallbackClient && shouldBypassPostgres(sql)) {
|
|
867
|
+
return fallbackClient.executeMultiple(sql);
|
|
868
|
+
}
|
|
869
|
+
for (const statement of splitSqlStatements(sql)) {
|
|
870
|
+
await adapter.execute(statement);
|
|
871
|
+
}
|
|
872
|
+
},
|
|
873
|
+
async sync() {
|
|
874
|
+
if (fallbackClient) {
|
|
875
|
+
return fallbackClient.sync();
|
|
876
|
+
}
|
|
877
|
+
return { frame_no: 0, frames_synced: 0 };
|
|
878
|
+
},
|
|
879
|
+
close() {
|
|
880
|
+
closed = true;
|
|
881
|
+
prismaClientPromise = null;
|
|
882
|
+
compatibilityBootstrapPromise = null;
|
|
883
|
+
void prisma.$disconnect?.();
|
|
884
|
+
},
|
|
885
|
+
get closed() {
|
|
886
|
+
return closed;
|
|
887
|
+
},
|
|
888
|
+
get protocol() {
|
|
889
|
+
return "prisma-postgres";
|
|
890
|
+
}
|
|
891
|
+
};
|
|
892
|
+
return adapter;
|
|
893
|
+
}
|
|
894
|
+
var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
|
|
895
|
+
var init_database_adapter = __esm({
|
|
896
|
+
"src/lib/database-adapter.ts"() {
|
|
897
|
+
"use strict";
|
|
898
|
+
VIEW_MAPPINGS = [
|
|
899
|
+
{ view: "memories", source: "memory.memory_records" },
|
|
900
|
+
{ view: "tasks", source: "memory.tasks" },
|
|
901
|
+
{ view: "behaviors", source: "memory.behaviors" },
|
|
902
|
+
{ view: "entities", source: "memory.entities" },
|
|
903
|
+
{ view: "relationships", source: "memory.relationships" },
|
|
904
|
+
{ view: "entity_memories", source: "memory.entity_memories" },
|
|
905
|
+
{ view: "entity_aliases", source: "memory.entity_aliases" },
|
|
906
|
+
{ view: "notifications", source: "memory.notifications" },
|
|
907
|
+
{ view: "messages", source: "memory.messages" },
|
|
908
|
+
{ view: "users", source: "wiki.users" },
|
|
909
|
+
{ view: "workspaces", source: "wiki.workspaces" },
|
|
910
|
+
{ view: "workspace_users", source: "wiki.workspace_users" },
|
|
911
|
+
{ view: "documents", source: "wiki.workspace_documents" },
|
|
912
|
+
{ view: "chats", source: "wiki.workspace_chats" }
|
|
913
|
+
];
|
|
914
|
+
UPSERT_KEYS = {
|
|
915
|
+
memories: ["id"],
|
|
916
|
+
tasks: ["id"],
|
|
917
|
+
behaviors: ["id"],
|
|
918
|
+
entities: ["id"],
|
|
919
|
+
relationships: ["id"],
|
|
920
|
+
entity_aliases: ["alias"],
|
|
921
|
+
notifications: ["id"],
|
|
922
|
+
messages: ["id"],
|
|
923
|
+
users: ["id"],
|
|
924
|
+
workspaces: ["id"],
|
|
925
|
+
workspace_users: ["id"],
|
|
926
|
+
documents: ["id"],
|
|
927
|
+
chats: ["id"]
|
|
928
|
+
};
|
|
929
|
+
BOOLEAN_COLUMNS_BY_TABLE = {
|
|
930
|
+
memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
|
|
931
|
+
behaviors: /* @__PURE__ */ new Set(["active"]),
|
|
932
|
+
notifications: /* @__PURE__ */ new Set(["read"]),
|
|
933
|
+
users: /* @__PURE__ */ new Set(["has_personal_memory"])
|
|
934
|
+
};
|
|
935
|
+
BOOLEAN_COLUMN_NAMES = new Set(
|
|
936
|
+
Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
|
|
937
|
+
);
|
|
938
|
+
IMMEDIATE_FALLBACK_PATTERNS = [
|
|
939
|
+
/\bPRAGMA\b/i,
|
|
940
|
+
/\bsqlite_master\b/i,
|
|
941
|
+
/(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
|
|
942
|
+
/\bMATCH\b/i,
|
|
943
|
+
/\bvector_distance_cos\s*\(/i,
|
|
944
|
+
/\bjson_extract\s*\(/i,
|
|
945
|
+
/\bjulianday\s*\(/i,
|
|
946
|
+
/\bstrftime\s*\(/i,
|
|
947
|
+
/\blast_insert_rowid\s*\(/i
|
|
948
|
+
];
|
|
949
|
+
prismaClientPromise = null;
|
|
950
|
+
compatibilityBootstrapPromise = null;
|
|
366
951
|
}
|
|
367
952
|
});
|
|
368
953
|
|
|
369
954
|
// src/lib/database.ts
|
|
370
955
|
import { createClient } from "@libsql/client";
|
|
371
956
|
async function initDatabase(config) {
|
|
957
|
+
if (_walCheckpointTimer) {
|
|
958
|
+
clearInterval(_walCheckpointTimer);
|
|
959
|
+
_walCheckpointTimer = null;
|
|
960
|
+
}
|
|
961
|
+
if (_daemonClient) {
|
|
962
|
+
_daemonClient.close();
|
|
963
|
+
_daemonClient = null;
|
|
964
|
+
}
|
|
965
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
966
|
+
_adapterClient.close();
|
|
967
|
+
}
|
|
968
|
+
_adapterClient = null;
|
|
372
969
|
if (_client) {
|
|
373
970
|
_client.close();
|
|
374
971
|
_client = null;
|
|
@@ -382,6 +979,7 @@ async function initDatabase(config) {
|
|
|
382
979
|
}
|
|
383
980
|
_client = createClient(opts);
|
|
384
981
|
_resilientClient = wrapWithRetry(_client);
|
|
982
|
+
_adapterClient = _resilientClient;
|
|
385
983
|
_client.execute("PRAGMA busy_timeout = 30000").catch(() => {
|
|
386
984
|
});
|
|
387
985
|
_client.execute("PRAGMA journal_mode = WAL").catch(() => {
|
|
@@ -392,14 +990,20 @@ async function initDatabase(config) {
|
|
|
392
990
|
});
|
|
393
991
|
}, 3e4);
|
|
394
992
|
_walCheckpointTimer.unref();
|
|
993
|
+
if (process.env.DATABASE_URL) {
|
|
994
|
+
_adapterClient = await createPrismaDbAdapter(_resilientClient);
|
|
995
|
+
}
|
|
395
996
|
}
|
|
396
997
|
function isInitialized() {
|
|
397
|
-
return _client !== null;
|
|
998
|
+
return _adapterClient !== null || _client !== null;
|
|
398
999
|
}
|
|
399
1000
|
function getClient() {
|
|
400
|
-
if (!
|
|
1001
|
+
if (!_adapterClient) {
|
|
401
1002
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
402
1003
|
}
|
|
1004
|
+
if (process.env.DATABASE_URL) {
|
|
1005
|
+
return _adapterClient;
|
|
1006
|
+
}
|
|
403
1007
|
if (process.env.EXE_IS_DAEMON === "1") {
|
|
404
1008
|
return _resilientClient;
|
|
405
1009
|
}
|
|
@@ -1339,16 +1943,18 @@ async function ensureSchema() {
|
|
|
1339
1943
|
}
|
|
1340
1944
|
}
|
|
1341
1945
|
}
|
|
1342
|
-
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso;
|
|
1946
|
+
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso;
|
|
1343
1947
|
var init_database = __esm({
|
|
1344
1948
|
"src/lib/database.ts"() {
|
|
1345
1949
|
"use strict";
|
|
1346
1950
|
init_db_retry();
|
|
1347
1951
|
init_employees();
|
|
1952
|
+
init_database_adapter();
|
|
1348
1953
|
_client = null;
|
|
1349
1954
|
_resilientClient = null;
|
|
1350
1955
|
_walCheckpointTimer = null;
|
|
1351
1956
|
_daemonClient = null;
|
|
1957
|
+
_adapterClient = null;
|
|
1352
1958
|
initTurso = initDatabase;
|
|
1353
1959
|
}
|
|
1354
1960
|
});
|
|
@@ -1366,7 +1972,7 @@ __export(shard_manager_exports, {
|
|
|
1366
1972
|
listShards: () => listShards,
|
|
1367
1973
|
shardExists: () => shardExists
|
|
1368
1974
|
});
|
|
1369
|
-
import
|
|
1975
|
+
import path6 from "path";
|
|
1370
1976
|
import { existsSync as existsSync4, mkdirSync, readdirSync } from "fs";
|
|
1371
1977
|
import { createClient as createClient2 } from "@libsql/client";
|
|
1372
1978
|
function initShardManager(encryptionKey) {
|
|
@@ -1392,7 +1998,7 @@ function getShardClient(projectName) {
|
|
|
1392
1998
|
}
|
|
1393
1999
|
const cached = _shards.get(safeName);
|
|
1394
2000
|
if (cached) return cached;
|
|
1395
|
-
const dbPath =
|
|
2001
|
+
const dbPath = path6.join(SHARDS_DIR, `${safeName}.db`);
|
|
1396
2002
|
const client = createClient2({
|
|
1397
2003
|
url: `file:${dbPath}`,
|
|
1398
2004
|
encryptionKey: _encryptionKey
|
|
@@ -1402,7 +2008,7 @@ function getShardClient(projectName) {
|
|
|
1402
2008
|
}
|
|
1403
2009
|
function shardExists(projectName) {
|
|
1404
2010
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
1405
|
-
return existsSync4(
|
|
2011
|
+
return existsSync4(path6.join(SHARDS_DIR, `${safeName}.db`));
|
|
1406
2012
|
}
|
|
1407
2013
|
function listShards() {
|
|
1408
2014
|
if (!existsSync4(SHARDS_DIR)) return [];
|
|
@@ -1479,7 +2085,23 @@ async function ensureShardSchema(client) {
|
|
|
1479
2085
|
// MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
|
|
1480
2086
|
"ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
|
|
1481
2087
|
"ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
|
|
1482
|
-
"ALTER TABLE memories ADD COLUMN trajectory TEXT"
|
|
2088
|
+
"ALTER TABLE memories ADD COLUMN trajectory TEXT",
|
|
2089
|
+
// Metadata enrichment columns (must match database.ts)
|
|
2090
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
2091
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
2092
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
2093
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
2094
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
2095
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
2096
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
2097
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
2098
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
2099
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
2100
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
2101
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
2102
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
2103
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
2104
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
1483
2105
|
]) {
|
|
1484
2106
|
try {
|
|
1485
2107
|
await client.execute(col);
|
|
@@ -1591,7 +2213,7 @@ var init_shard_manager = __esm({
|
|
|
1591
2213
|
"src/lib/shard-manager.ts"() {
|
|
1592
2214
|
"use strict";
|
|
1593
2215
|
init_config();
|
|
1594
|
-
SHARDS_DIR =
|
|
2216
|
+
SHARDS_DIR = path6.join(EXE_AI_DIR, "shards");
|
|
1595
2217
|
_shards = /* @__PURE__ */ new Map();
|
|
1596
2218
|
_encryptionKey = null;
|
|
1597
2219
|
_shardingEnabled = false;
|
|
@@ -1787,11 +2409,11 @@ ${p.content}`).join("\n\n");
|
|
|
1787
2409
|
|
|
1788
2410
|
// src/lib/exe-daemon-client.ts
|
|
1789
2411
|
import net from "net";
|
|
1790
|
-
import
|
|
2412
|
+
import os5 from "os";
|
|
1791
2413
|
import { spawn } from "child_process";
|
|
1792
2414
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
1793
2415
|
import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
|
|
1794
|
-
import
|
|
2416
|
+
import path7 from "path";
|
|
1795
2417
|
import { fileURLToPath } from "url";
|
|
1796
2418
|
function handleData(chunk) {
|
|
1797
2419
|
_buffer += chunk.toString();
|
|
@@ -1842,17 +2464,17 @@ function cleanupStaleFiles() {
|
|
|
1842
2464
|
}
|
|
1843
2465
|
}
|
|
1844
2466
|
function findPackageRoot() {
|
|
1845
|
-
let dir =
|
|
1846
|
-
const { root } =
|
|
2467
|
+
let dir = path7.dirname(fileURLToPath(import.meta.url));
|
|
2468
|
+
const { root } = path7.parse(dir);
|
|
1847
2469
|
while (dir !== root) {
|
|
1848
|
-
if (existsSync5(
|
|
1849
|
-
dir =
|
|
2470
|
+
if (existsSync5(path7.join(dir, "package.json"))) return dir;
|
|
2471
|
+
dir = path7.dirname(dir);
|
|
1850
2472
|
}
|
|
1851
2473
|
return null;
|
|
1852
2474
|
}
|
|
1853
2475
|
function spawnDaemon() {
|
|
1854
|
-
const freeGB =
|
|
1855
|
-
const totalGB =
|
|
2476
|
+
const freeGB = os5.freemem() / (1024 * 1024 * 1024);
|
|
2477
|
+
const totalGB = os5.totalmem() / (1024 * 1024 * 1024);
|
|
1856
2478
|
if (totalGB <= 8) {
|
|
1857
2479
|
process.stderr.write(
|
|
1858
2480
|
`[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
|
|
@@ -1872,7 +2494,7 @@ function spawnDaemon() {
|
|
|
1872
2494
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
1873
2495
|
return;
|
|
1874
2496
|
}
|
|
1875
|
-
const daemonPath =
|
|
2497
|
+
const daemonPath = path7.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
1876
2498
|
if (!existsSync5(daemonPath)) {
|
|
1877
2499
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
1878
2500
|
`);
|
|
@@ -1881,7 +2503,7 @@ function spawnDaemon() {
|
|
|
1881
2503
|
const resolvedPath = daemonPath;
|
|
1882
2504
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
1883
2505
|
`);
|
|
1884
|
-
const logPath =
|
|
2506
|
+
const logPath = path7.join(path7.dirname(SOCKET_PATH), "exed.log");
|
|
1885
2507
|
let stderrFd = "ignore";
|
|
1886
2508
|
try {
|
|
1887
2509
|
stderrFd = openSync(logPath, "a");
|
|
@@ -2032,74 +2654,123 @@ async function pingDaemon() {
|
|
|
2032
2654
|
return null;
|
|
2033
2655
|
}
|
|
2034
2656
|
function killAndRespawnDaemon() {
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2657
|
+
if (!acquireSpawnLock()) {
|
|
2658
|
+
process.stderr.write("[exed-client] Another process is already restarting daemon \u2014 skipping\n");
|
|
2659
|
+
if (_socket) {
|
|
2660
|
+
_socket.destroy();
|
|
2661
|
+
_socket = null;
|
|
2662
|
+
}
|
|
2663
|
+
_connected = false;
|
|
2664
|
+
_buffer = "";
|
|
2665
|
+
return;
|
|
2666
|
+
}
|
|
2667
|
+
try {
|
|
2668
|
+
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
2669
|
+
if (existsSync5(PID_PATH)) {
|
|
2670
|
+
try {
|
|
2671
|
+
const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
|
|
2672
|
+
if (pid > 0) {
|
|
2673
|
+
try {
|
|
2674
|
+
process.kill(pid, "SIGKILL");
|
|
2675
|
+
} catch {
|
|
2676
|
+
}
|
|
2043
2677
|
}
|
|
2678
|
+
} catch {
|
|
2044
2679
|
}
|
|
2680
|
+
}
|
|
2681
|
+
if (_socket) {
|
|
2682
|
+
_socket.destroy();
|
|
2683
|
+
_socket = null;
|
|
2684
|
+
}
|
|
2685
|
+
_connected = false;
|
|
2686
|
+
_buffer = "";
|
|
2687
|
+
try {
|
|
2688
|
+
unlinkSync2(PID_PATH);
|
|
2045
2689
|
} catch {
|
|
2046
2690
|
}
|
|
2691
|
+
try {
|
|
2692
|
+
unlinkSync2(SOCKET_PATH);
|
|
2693
|
+
} catch {
|
|
2694
|
+
}
|
|
2695
|
+
spawnDaemon();
|
|
2696
|
+
} finally {
|
|
2697
|
+
releaseSpawnLock();
|
|
2047
2698
|
}
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
_socket = null;
|
|
2051
|
-
}
|
|
2052
|
-
_connected = false;
|
|
2053
|
-
_buffer = "";
|
|
2699
|
+
}
|
|
2700
|
+
function isDaemonTooYoung() {
|
|
2054
2701
|
try {
|
|
2055
|
-
|
|
2702
|
+
const stat = statSync(PID_PATH);
|
|
2703
|
+
return Date.now() - stat.mtimeMs < MIN_DAEMON_AGE_MS;
|
|
2056
2704
|
} catch {
|
|
2705
|
+
return false;
|
|
2057
2706
|
}
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2707
|
+
}
|
|
2708
|
+
async function retryThenRestart(doRequest, label) {
|
|
2709
|
+
const result = await doRequest();
|
|
2710
|
+
if (!result.error) {
|
|
2711
|
+
_consecutiveFailures = 0;
|
|
2712
|
+
return result;
|
|
2713
|
+
}
|
|
2714
|
+
_consecutiveFailures++;
|
|
2715
|
+
for (let i = 0; i < MAX_RETRIES_BEFORE_RESTART; i++) {
|
|
2716
|
+
const delayMs = RETRY_DELAYS_MS[i] ?? 5e3;
|
|
2717
|
+
process.stderr.write(`[exed-client] ${label} failed (${result.error}), retry ${i + 1}/${MAX_RETRIES_BEFORE_RESTART} in ${delayMs}ms
|
|
2718
|
+
`);
|
|
2719
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
2720
|
+
if (!_connected) {
|
|
2721
|
+
if (!await connectToSocket()) continue;
|
|
2722
|
+
}
|
|
2723
|
+
const retry = await doRequest();
|
|
2724
|
+
if (!retry.error) {
|
|
2725
|
+
_consecutiveFailures = 0;
|
|
2726
|
+
return retry;
|
|
2727
|
+
}
|
|
2728
|
+
_consecutiveFailures++;
|
|
2729
|
+
}
|
|
2730
|
+
if (isDaemonTooYoung()) {
|
|
2731
|
+
process.stderr.write(`[exed-client] ${label}: daemon too young (< ${MIN_DAEMON_AGE_MS / 1e3}s) \u2014 skipping restart
|
|
2732
|
+
`);
|
|
2733
|
+
return { error: result.error };
|
|
2734
|
+
}
|
|
2735
|
+
process.stderr.write(`[exed-client] ${label}: ${_consecutiveFailures} consecutive failures \u2014 restarting daemon
|
|
2736
|
+
`);
|
|
2737
|
+
killAndRespawnDaemon();
|
|
2738
|
+
const start = Date.now();
|
|
2739
|
+
let delay2 = 200;
|
|
2740
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
2741
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
2742
|
+
if (await connectToSocket()) break;
|
|
2743
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
2061
2744
|
}
|
|
2062
|
-
|
|
2745
|
+
if (!_connected) return { error: "Daemon restart failed" };
|
|
2746
|
+
const final = await doRequest();
|
|
2747
|
+
if (!final.error) _consecutiveFailures = 0;
|
|
2748
|
+
return final;
|
|
2063
2749
|
}
|
|
2064
2750
|
async function embedViaClient(text, priority = "high") {
|
|
2065
2751
|
if (!_connected && !await connectEmbedDaemon()) return null;
|
|
2066
2752
|
_requestCount++;
|
|
2067
2753
|
if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
|
|
2068
2754
|
const health = await pingDaemon();
|
|
2069
|
-
if (!health) {
|
|
2755
|
+
if (!health && !isDaemonTooYoung()) {
|
|
2070
2756
|
process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
|
|
2071
2757
|
`);
|
|
2072
2758
|
killAndRespawnDaemon();
|
|
2073
2759
|
const start = Date.now();
|
|
2074
|
-
let
|
|
2760
|
+
let d = 200;
|
|
2075
2761
|
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
2076
|
-
await new Promise((r) => setTimeout(r,
|
|
2762
|
+
await new Promise((r) => setTimeout(r, d));
|
|
2077
2763
|
if (await connectToSocket()) break;
|
|
2078
|
-
|
|
2764
|
+
d = Math.min(d * 2, 3e3);
|
|
2079
2765
|
}
|
|
2080
2766
|
if (!_connected) return null;
|
|
2081
2767
|
}
|
|
2082
2768
|
}
|
|
2083
|
-
const result = await
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
killAndRespawnDaemon();
|
|
2089
|
-
const start = Date.now();
|
|
2090
|
-
let delay2 = 200;
|
|
2091
|
-
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
2092
|
-
await new Promise((r) => setTimeout(r, delay2));
|
|
2093
|
-
if (await connectToSocket()) break;
|
|
2094
|
-
delay2 = Math.min(delay2 * 2, 3e3);
|
|
2095
|
-
}
|
|
2096
|
-
if (!_connected) return null;
|
|
2097
|
-
const retry = await sendRequest([text], priority);
|
|
2098
|
-
if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
|
|
2099
|
-
process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
|
|
2100
|
-
`);
|
|
2101
|
-
}
|
|
2102
|
-
return null;
|
|
2769
|
+
const result = await retryThenRestart(
|
|
2770
|
+
() => sendRequest([text], priority),
|
|
2771
|
+
"Embed"
|
|
2772
|
+
);
|
|
2773
|
+
return !result.error && result.vectors?.[0] ? result.vectors[0] : null;
|
|
2103
2774
|
}
|
|
2104
2775
|
function disconnectClient() {
|
|
2105
2776
|
if (_socket) {
|
|
@@ -2114,14 +2785,14 @@ function disconnectClient() {
|
|
|
2114
2785
|
entry.resolve({ error: "Client disconnected" });
|
|
2115
2786
|
}
|
|
2116
2787
|
}
|
|
2117
|
-
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;
|
|
2788
|
+
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;
|
|
2118
2789
|
var init_exe_daemon_client = __esm({
|
|
2119
2790
|
"src/lib/exe-daemon-client.ts"() {
|
|
2120
2791
|
"use strict";
|
|
2121
2792
|
init_config();
|
|
2122
|
-
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ??
|
|
2123
|
-
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ??
|
|
2124
|
-
SPAWN_LOCK_PATH =
|
|
2793
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path7.join(EXE_AI_DIR, "exed.sock");
|
|
2794
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path7.join(EXE_AI_DIR, "exed.pid");
|
|
2795
|
+
SPAWN_LOCK_PATH = path7.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
2125
2796
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
2126
2797
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
2127
2798
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
@@ -2129,7 +2800,11 @@ var init_exe_daemon_client = __esm({
|
|
|
2129
2800
|
_connected = false;
|
|
2130
2801
|
_buffer = "";
|
|
2131
2802
|
_requestCount = 0;
|
|
2803
|
+
_consecutiveFailures = 0;
|
|
2132
2804
|
HEALTH_CHECK_INTERVAL = 100;
|
|
2805
|
+
MAX_RETRIES_BEFORE_RESTART = 3;
|
|
2806
|
+
RETRY_DELAYS_MS = [1e3, 3e3, 5e3];
|
|
2807
|
+
MIN_DAEMON_AGE_MS = 3e4;
|
|
2133
2808
|
_pending = /* @__PURE__ */ new Map();
|
|
2134
2809
|
MAX_BUFFER = 1e7;
|
|
2135
2810
|
}
|
|
@@ -2173,8 +2848,8 @@ async function embedDirect(text) {
|
|
|
2173
2848
|
const llamaCpp = await import("node-llama-cpp");
|
|
2174
2849
|
const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
2175
2850
|
const { existsSync: existsSync8 } = await import("fs");
|
|
2176
|
-
const
|
|
2177
|
-
const modelPath =
|
|
2851
|
+
const path11 = await import("path");
|
|
2852
|
+
const modelPath = path11.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
|
|
2178
2853
|
if (!existsSync8(modelPath)) {
|
|
2179
2854
|
throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
|
|
2180
2855
|
}
|
|
@@ -2206,7 +2881,7 @@ var init_embedder = __esm({
|
|
|
2206
2881
|
// src/lib/license.ts
|
|
2207
2882
|
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync6, mkdirSync as mkdirSync2 } from "fs";
|
|
2208
2883
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
2209
|
-
import
|
|
2884
|
+
import path8 from "path";
|
|
2210
2885
|
import { jwtVerify, importSPKI } from "jose";
|
|
2211
2886
|
async function fetchRetry(url, init) {
|
|
2212
2887
|
try {
|
|
@@ -2217,7 +2892,7 @@ async function fetchRetry(url, init) {
|
|
|
2217
2892
|
}
|
|
2218
2893
|
}
|
|
2219
2894
|
function loadDeviceId() {
|
|
2220
|
-
const deviceJsonPath =
|
|
2895
|
+
const deviceJsonPath = path8.join(EXE_AI_DIR, "device.json");
|
|
2221
2896
|
try {
|
|
2222
2897
|
if (existsSync6(deviceJsonPath)) {
|
|
2223
2898
|
const data = JSON.parse(readFileSync4(deviceJsonPath, "utf8"));
|
|
@@ -2382,7 +3057,7 @@ async function checkLicense() {
|
|
|
2382
3057
|
let key = loadLicense();
|
|
2383
3058
|
if (!key) {
|
|
2384
3059
|
try {
|
|
2385
|
-
const configPath =
|
|
3060
|
+
const configPath = path8.join(EXE_AI_DIR, "config.json");
|
|
2386
3061
|
if (existsSync6(configPath)) {
|
|
2387
3062
|
const raw = JSON.parse(readFileSync4(configPath, "utf8"));
|
|
2388
3063
|
const cloud = raw.cloud;
|
|
@@ -2415,9 +3090,9 @@ var init_license = __esm({
|
|
|
2415
3090
|
"src/lib/license.ts"() {
|
|
2416
3091
|
"use strict";
|
|
2417
3092
|
init_config();
|
|
2418
|
-
LICENSE_PATH =
|
|
2419
|
-
CACHE_PATH =
|
|
2420
|
-
DEVICE_ID_PATH =
|
|
3093
|
+
LICENSE_PATH = path8.join(EXE_AI_DIR, "license.key");
|
|
3094
|
+
CACHE_PATH = path8.join(EXE_AI_DIR, "license-cache.json");
|
|
3095
|
+
DEVICE_ID_PATH = path8.join(EXE_AI_DIR, "device-id");
|
|
2421
3096
|
API_BASE = "https://askexe.com/cloud";
|
|
2422
3097
|
RETRY_DELAY_MS = 500;
|
|
2423
3098
|
LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
@@ -2457,7 +3132,7 @@ __export(plan_limits_exports, {
|
|
|
2457
3132
|
getLicenseSync: () => getLicenseSync
|
|
2458
3133
|
});
|
|
2459
3134
|
import { readFileSync as readFileSync5, existsSync as existsSync7 } from "fs";
|
|
2460
|
-
import
|
|
3135
|
+
import path9 from "path";
|
|
2461
3136
|
function getLicenseSync() {
|
|
2462
3137
|
try {
|
|
2463
3138
|
if (!existsSync7(CACHE_PATH2)) return freeLicense();
|
|
@@ -2566,14 +3241,14 @@ var init_plan_limits = __esm({
|
|
|
2566
3241
|
this.name = "PlanLimitError";
|
|
2567
3242
|
}
|
|
2568
3243
|
};
|
|
2569
|
-
CACHE_PATH2 =
|
|
3244
|
+
CACHE_PATH2 = path9.join(EXE_AI_DIR, "license-cache.json");
|
|
2570
3245
|
}
|
|
2571
3246
|
});
|
|
2572
3247
|
|
|
2573
3248
|
// src/adapters/claude/hooks/response-ingest-worker.ts
|
|
2574
3249
|
import crypto from "crypto";
|
|
2575
3250
|
import { writeFileSync as writeFileSync3 } from "fs";
|
|
2576
|
-
import
|
|
3251
|
+
import path10 from "path";
|
|
2577
3252
|
|
|
2578
3253
|
// src/lib/project-name.ts
|
|
2579
3254
|
import { execSync } from "child_process";
|
|
@@ -2619,15 +3294,15 @@ import { createHash } from "crypto";
|
|
|
2619
3294
|
// src/lib/keychain.ts
|
|
2620
3295
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
2621
3296
|
import { existsSync as existsSync3 } from "fs";
|
|
2622
|
-
import
|
|
2623
|
-
import
|
|
3297
|
+
import path5 from "path";
|
|
3298
|
+
import os4 from "os";
|
|
2624
3299
|
var SERVICE = "exe-mem";
|
|
2625
3300
|
var ACCOUNT = "master-key";
|
|
2626
3301
|
function getKeyDir() {
|
|
2627
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
3302
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path5.join(os4.homedir(), ".exe-os");
|
|
2628
3303
|
}
|
|
2629
3304
|
function getKeyPath() {
|
|
2630
|
-
return
|
|
3305
|
+
return path5.join(getKeyDir(), "master.key");
|
|
2631
3306
|
}
|
|
2632
3307
|
async function tryKeytar() {
|
|
2633
3308
|
try {
|
|
@@ -2650,7 +3325,7 @@ async function getMasterKey() {
|
|
|
2650
3325
|
const keyPath = getKeyPath();
|
|
2651
3326
|
if (!existsSync3(keyPath)) {
|
|
2652
3327
|
process.stderr.write(
|
|
2653
|
-
`[keychain] Key not found at ${keyPath} (HOME=${
|
|
3328
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os4.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
2654
3329
|
`
|
|
2655
3330
|
);
|
|
2656
3331
|
return null;
|
|
@@ -3123,7 +3798,7 @@ async function main() {
|
|
|
3123
3798
|
if (needsBackfill) {
|
|
3124
3799
|
try {
|
|
3125
3800
|
const { EXE_AI_DIR: exeDir } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
3126
|
-
const flagPath =
|
|
3801
|
+
const flagPath = path10.join(exeDir, "session-cache", "needs-backfill");
|
|
3127
3802
|
writeFileSync3(flagPath, "1");
|
|
3128
3803
|
} catch (err) {
|
|
3129
3804
|
process.stderr.write(`[response-ingest-worker] backfill flag write failed: ${err instanceof Error ? err.message : String(err)}
|