@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
|
@@ -338,7 +338,7 @@ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
|
338
338
|
return [];
|
|
339
339
|
}
|
|
340
340
|
}
|
|
341
|
-
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE;
|
|
341
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, IDENTITY_DIR;
|
|
342
342
|
var init_employees = __esm({
|
|
343
343
|
"src/lib/employees.ts"() {
|
|
344
344
|
"use strict";
|
|
@@ -346,12 +346,609 @@ var init_employees = __esm({
|
|
|
346
346
|
EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
|
|
347
347
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
348
348
|
COORDINATOR_ROLE = "COO";
|
|
349
|
+
IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
// src/lib/database-adapter.ts
|
|
354
|
+
import os3 from "os";
|
|
355
|
+
import path3 from "path";
|
|
356
|
+
import { createRequire } from "module";
|
|
357
|
+
import { pathToFileURL } from "url";
|
|
358
|
+
function quotedIdentifier(identifier) {
|
|
359
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
360
|
+
}
|
|
361
|
+
function unqualifiedTableName(name) {
|
|
362
|
+
const raw = name.trim().replace(/^"|"$/g, "");
|
|
363
|
+
const parts = raw.split(".");
|
|
364
|
+
return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
|
|
365
|
+
}
|
|
366
|
+
function stripTrailingSemicolon(sql) {
|
|
367
|
+
return sql.trim().replace(/;+\s*$/u, "");
|
|
368
|
+
}
|
|
369
|
+
function appendClause(sql, clause) {
|
|
370
|
+
const trimmed = stripTrailingSemicolon(sql);
|
|
371
|
+
const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
|
|
372
|
+
if (!returningMatch) {
|
|
373
|
+
return `${trimmed}${clause}`;
|
|
374
|
+
}
|
|
375
|
+
const idx = returningMatch.index;
|
|
376
|
+
return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
|
|
377
|
+
}
|
|
378
|
+
function normalizeStatement(stmt) {
|
|
379
|
+
if (typeof stmt === "string") {
|
|
380
|
+
return { kind: "positional", sql: stmt, args: [] };
|
|
381
|
+
}
|
|
382
|
+
const sql = stmt.sql;
|
|
383
|
+
if (Array.isArray(stmt.args) || stmt.args === void 0) {
|
|
384
|
+
return { kind: "positional", sql, args: stmt.args ?? [] };
|
|
385
|
+
}
|
|
386
|
+
return { kind: "named", sql, args: stmt.args };
|
|
387
|
+
}
|
|
388
|
+
function rewriteBooleanLiterals(sql) {
|
|
389
|
+
let out = sql;
|
|
390
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
391
|
+
const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
|
|
392
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
|
|
393
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
|
|
394
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
|
|
395
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
|
|
396
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
|
|
397
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
|
|
398
|
+
}
|
|
399
|
+
return out;
|
|
400
|
+
}
|
|
401
|
+
function rewriteInsertOrIgnore(sql) {
|
|
402
|
+
if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
|
|
403
|
+
return sql;
|
|
404
|
+
}
|
|
405
|
+
const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
|
|
406
|
+
return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
|
|
407
|
+
}
|
|
408
|
+
function rewriteInsertOrReplace(sql) {
|
|
409
|
+
const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
|
|
410
|
+
if (!match) {
|
|
411
|
+
return sql;
|
|
412
|
+
}
|
|
413
|
+
const rawTable = match[1];
|
|
414
|
+
const rawColumns = match[2];
|
|
415
|
+
const remainder = match[3];
|
|
416
|
+
const tableName = unqualifiedTableName(rawTable);
|
|
417
|
+
const conflictKeys = UPSERT_KEYS[tableName];
|
|
418
|
+
if (!conflictKeys?.length) {
|
|
419
|
+
return sql;
|
|
420
|
+
}
|
|
421
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
422
|
+
const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
|
|
423
|
+
const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
|
|
424
|
+
const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
|
|
425
|
+
return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
|
|
426
|
+
}
|
|
427
|
+
function rewriteSql(sql) {
|
|
428
|
+
let out = sql;
|
|
429
|
+
out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
|
|
430
|
+
out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
|
|
431
|
+
out = rewriteBooleanLiterals(out);
|
|
432
|
+
out = rewriteInsertOrReplace(out);
|
|
433
|
+
out = rewriteInsertOrIgnore(out);
|
|
434
|
+
return stripTrailingSemicolon(out);
|
|
435
|
+
}
|
|
436
|
+
function toBoolean(value) {
|
|
437
|
+
if (value === null || value === void 0) return value;
|
|
438
|
+
if (typeof value === "boolean") return value;
|
|
439
|
+
if (typeof value === "number") return value !== 0;
|
|
440
|
+
if (typeof value === "bigint") return value !== 0n;
|
|
441
|
+
if (typeof value === "string") {
|
|
442
|
+
const normalized = value.trim().toLowerCase();
|
|
443
|
+
if (normalized === "0" || normalized === "false") return false;
|
|
444
|
+
if (normalized === "1" || normalized === "true") return true;
|
|
445
|
+
}
|
|
446
|
+
return Boolean(value);
|
|
447
|
+
}
|
|
448
|
+
function countQuestionMarks(sql, end) {
|
|
449
|
+
let count = 0;
|
|
450
|
+
let inSingle = false;
|
|
451
|
+
let inDouble = false;
|
|
452
|
+
let inLineComment = false;
|
|
453
|
+
let inBlockComment = false;
|
|
454
|
+
for (let i = 0; i < end; i++) {
|
|
455
|
+
const ch = sql[i];
|
|
456
|
+
const next = sql[i + 1];
|
|
457
|
+
if (inLineComment) {
|
|
458
|
+
if (ch === "\n") inLineComment = false;
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
if (inBlockComment) {
|
|
462
|
+
if (ch === "*" && next === "/") {
|
|
463
|
+
inBlockComment = false;
|
|
464
|
+
i += 1;
|
|
465
|
+
}
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
469
|
+
inLineComment = true;
|
|
470
|
+
i += 1;
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
474
|
+
inBlockComment = true;
|
|
475
|
+
i += 1;
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
479
|
+
inSingle = !inSingle;
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
483
|
+
inDouble = !inDouble;
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
487
|
+
count += 1;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return count;
|
|
491
|
+
}
|
|
492
|
+
function findBooleanPlaceholderIndexes(sql) {
|
|
493
|
+
const indexes = /* @__PURE__ */ new Set();
|
|
494
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
495
|
+
const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
|
|
496
|
+
for (const match of sql.matchAll(pattern)) {
|
|
497
|
+
const matchText = match[0];
|
|
498
|
+
const qIndex = match.index + matchText.lastIndexOf("?");
|
|
499
|
+
indexes.add(countQuestionMarks(sql, qIndex + 1));
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
return indexes;
|
|
503
|
+
}
|
|
504
|
+
function coerceInsertBooleanArgs(sql, args) {
|
|
505
|
+
const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
|
|
506
|
+
if (!match) return;
|
|
507
|
+
const rawTable = match[1];
|
|
508
|
+
const rawColumns = match[2];
|
|
509
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
510
|
+
if (!boolColumns?.size) return;
|
|
511
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
512
|
+
for (const [index, column] of columns.entries()) {
|
|
513
|
+
if (boolColumns.has(column) && index < args.length) {
|
|
514
|
+
args[index] = toBoolean(args[index]);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
function coerceUpdateBooleanArgs(sql, args) {
|
|
519
|
+
const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
|
|
520
|
+
if (!match) return;
|
|
521
|
+
const rawTable = match[1];
|
|
522
|
+
const setClause = match[2];
|
|
523
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
524
|
+
if (!boolColumns?.size) return;
|
|
525
|
+
const assignments = setClause.split(",");
|
|
526
|
+
let placeholderIndex = 0;
|
|
527
|
+
for (const assignment of assignments) {
|
|
528
|
+
if (!assignment.includes("?")) continue;
|
|
529
|
+
placeholderIndex += 1;
|
|
530
|
+
const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
|
|
531
|
+
if (colMatch && boolColumns.has(colMatch[1])) {
|
|
532
|
+
args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
function coerceBooleanArgs(sql, args) {
|
|
537
|
+
const nextArgs = [...args];
|
|
538
|
+
coerceInsertBooleanArgs(sql, nextArgs);
|
|
539
|
+
coerceUpdateBooleanArgs(sql, nextArgs);
|
|
540
|
+
const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
|
|
541
|
+
for (const index of placeholderIndexes) {
|
|
542
|
+
if (index > 0 && index <= nextArgs.length) {
|
|
543
|
+
nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
return nextArgs;
|
|
547
|
+
}
|
|
548
|
+
function convertQuestionMarksToDollarParams(sql) {
|
|
549
|
+
let out = "";
|
|
550
|
+
let placeholder = 0;
|
|
551
|
+
let inSingle = false;
|
|
552
|
+
let inDouble = false;
|
|
553
|
+
let inLineComment = false;
|
|
554
|
+
let inBlockComment = false;
|
|
555
|
+
for (let i = 0; i < sql.length; i++) {
|
|
556
|
+
const ch = sql[i];
|
|
557
|
+
const next = sql[i + 1];
|
|
558
|
+
if (inLineComment) {
|
|
559
|
+
out += ch;
|
|
560
|
+
if (ch === "\n") inLineComment = false;
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
563
|
+
if (inBlockComment) {
|
|
564
|
+
out += ch;
|
|
565
|
+
if (ch === "*" && next === "/") {
|
|
566
|
+
out += next;
|
|
567
|
+
inBlockComment = false;
|
|
568
|
+
i += 1;
|
|
569
|
+
}
|
|
570
|
+
continue;
|
|
571
|
+
}
|
|
572
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
573
|
+
out += ch + next;
|
|
574
|
+
inLineComment = true;
|
|
575
|
+
i += 1;
|
|
576
|
+
continue;
|
|
577
|
+
}
|
|
578
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
579
|
+
out += ch + next;
|
|
580
|
+
inBlockComment = true;
|
|
581
|
+
i += 1;
|
|
582
|
+
continue;
|
|
583
|
+
}
|
|
584
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
585
|
+
inSingle = !inSingle;
|
|
586
|
+
out += ch;
|
|
587
|
+
continue;
|
|
588
|
+
}
|
|
589
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
590
|
+
inDouble = !inDouble;
|
|
591
|
+
out += ch;
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
595
|
+
placeholder += 1;
|
|
596
|
+
out += `$${placeholder}`;
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
out += ch;
|
|
600
|
+
}
|
|
601
|
+
return out;
|
|
602
|
+
}
|
|
603
|
+
function translateStatementForPostgres(stmt) {
|
|
604
|
+
const normalized = normalizeStatement(stmt);
|
|
605
|
+
if (normalized.kind === "named") {
|
|
606
|
+
throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
|
|
607
|
+
}
|
|
608
|
+
const rewrittenSql = rewriteSql(normalized.sql);
|
|
609
|
+
const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
|
|
610
|
+
return {
|
|
611
|
+
sql: convertQuestionMarksToDollarParams(rewrittenSql),
|
|
612
|
+
args: coercedArgs
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
function shouldBypassPostgres(stmt) {
|
|
616
|
+
const normalized = normalizeStatement(stmt);
|
|
617
|
+
if (normalized.kind === "named") {
|
|
618
|
+
return true;
|
|
619
|
+
}
|
|
620
|
+
return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
|
|
621
|
+
}
|
|
622
|
+
function shouldFallbackOnError(error) {
|
|
623
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
624
|
+
return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
|
|
625
|
+
}
|
|
626
|
+
function isReadQuery(sql) {
|
|
627
|
+
const trimmed = sql.trimStart();
|
|
628
|
+
return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
|
|
629
|
+
}
|
|
630
|
+
function buildRow(row, columns) {
|
|
631
|
+
const values = columns.map((column) => row[column]);
|
|
632
|
+
return Object.assign(values, row);
|
|
633
|
+
}
|
|
634
|
+
function buildResultSet(rows, rowsAffected = 0) {
|
|
635
|
+
const columns = rows[0] ? Object.keys(rows[0]) : [];
|
|
636
|
+
const resultRows = rows.map((row) => buildRow(row, columns));
|
|
637
|
+
return {
|
|
638
|
+
columns,
|
|
639
|
+
columnTypes: columns.map(() => ""),
|
|
640
|
+
rows: resultRows,
|
|
641
|
+
rowsAffected,
|
|
642
|
+
lastInsertRowid: void 0,
|
|
643
|
+
toJSON() {
|
|
644
|
+
return {
|
|
645
|
+
columns,
|
|
646
|
+
columnTypes: columns.map(() => ""),
|
|
647
|
+
rows,
|
|
648
|
+
rowsAffected,
|
|
649
|
+
lastInsertRowid: void 0
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
async function loadPrismaClient() {
|
|
655
|
+
if (!prismaClientPromise) {
|
|
656
|
+
prismaClientPromise = (async () => {
|
|
657
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
658
|
+
if (explicitPath) {
|
|
659
|
+
const module2 = await import(pathToFileURL(explicitPath).href);
|
|
660
|
+
const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
|
|
661
|
+
if (!PrismaClient2) {
|
|
662
|
+
throw new Error(`No PrismaClient export found at ${explicitPath}`);
|
|
663
|
+
}
|
|
664
|
+
return new PrismaClient2();
|
|
665
|
+
}
|
|
666
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path3.join(os3.homedir(), "exe-db");
|
|
667
|
+
const requireFromExeDb = createRequire(path3.join(exeDbRoot, "package.json"));
|
|
668
|
+
const prismaEntry = requireFromExeDb.resolve("@prisma/client");
|
|
669
|
+
const module = await import(pathToFileURL(prismaEntry).href);
|
|
670
|
+
const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
|
|
671
|
+
if (!PrismaClient) {
|
|
672
|
+
throw new Error(`No PrismaClient export found in ${prismaEntry}`);
|
|
673
|
+
}
|
|
674
|
+
return new PrismaClient();
|
|
675
|
+
})();
|
|
676
|
+
}
|
|
677
|
+
return prismaClientPromise;
|
|
678
|
+
}
|
|
679
|
+
async function ensureCompatibilityViews(prisma) {
|
|
680
|
+
if (!compatibilityBootstrapPromise) {
|
|
681
|
+
compatibilityBootstrapPromise = (async () => {
|
|
682
|
+
for (const mapping of VIEW_MAPPINGS) {
|
|
683
|
+
const relation = mapping.source.replace(/"/g, "");
|
|
684
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
685
|
+
"SELECT to_regclass($1) AS regclass",
|
|
686
|
+
relation
|
|
687
|
+
);
|
|
688
|
+
if (!rows[0]?.regclass) {
|
|
689
|
+
continue;
|
|
690
|
+
}
|
|
691
|
+
await prisma.$executeRawUnsafe(
|
|
692
|
+
`CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
|
|
693
|
+
);
|
|
694
|
+
}
|
|
695
|
+
})();
|
|
696
|
+
}
|
|
697
|
+
return compatibilityBootstrapPromise;
|
|
698
|
+
}
|
|
699
|
+
async function executeOnPrisma(executor, stmt) {
|
|
700
|
+
const translated = translateStatementForPostgres(stmt);
|
|
701
|
+
if (isReadQuery(translated.sql)) {
|
|
702
|
+
const rows = await executor.$queryRawUnsafe(
|
|
703
|
+
translated.sql,
|
|
704
|
+
...translated.args
|
|
705
|
+
);
|
|
706
|
+
return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
|
|
707
|
+
}
|
|
708
|
+
const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
|
|
709
|
+
return buildResultSet([], rowsAffected);
|
|
710
|
+
}
|
|
711
|
+
function splitSqlStatements(sql) {
|
|
712
|
+
const parts = [];
|
|
713
|
+
let current = "";
|
|
714
|
+
let inSingle = false;
|
|
715
|
+
let inDouble = false;
|
|
716
|
+
let inLineComment = false;
|
|
717
|
+
let inBlockComment = false;
|
|
718
|
+
for (let i = 0; i < sql.length; i++) {
|
|
719
|
+
const ch = sql[i];
|
|
720
|
+
const next = sql[i + 1];
|
|
721
|
+
if (inLineComment) {
|
|
722
|
+
current += ch;
|
|
723
|
+
if (ch === "\n") inLineComment = false;
|
|
724
|
+
continue;
|
|
725
|
+
}
|
|
726
|
+
if (inBlockComment) {
|
|
727
|
+
current += ch;
|
|
728
|
+
if (ch === "*" && next === "/") {
|
|
729
|
+
current += next;
|
|
730
|
+
inBlockComment = false;
|
|
731
|
+
i += 1;
|
|
732
|
+
}
|
|
733
|
+
continue;
|
|
734
|
+
}
|
|
735
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
736
|
+
current += ch + next;
|
|
737
|
+
inLineComment = true;
|
|
738
|
+
i += 1;
|
|
739
|
+
continue;
|
|
740
|
+
}
|
|
741
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
742
|
+
current += ch + next;
|
|
743
|
+
inBlockComment = true;
|
|
744
|
+
i += 1;
|
|
745
|
+
continue;
|
|
746
|
+
}
|
|
747
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
748
|
+
inSingle = !inSingle;
|
|
749
|
+
current += ch;
|
|
750
|
+
continue;
|
|
751
|
+
}
|
|
752
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
753
|
+
inDouble = !inDouble;
|
|
754
|
+
current += ch;
|
|
755
|
+
continue;
|
|
756
|
+
}
|
|
757
|
+
if (!inSingle && !inDouble && ch === ";") {
|
|
758
|
+
if (current.trim()) {
|
|
759
|
+
parts.push(current.trim());
|
|
760
|
+
}
|
|
761
|
+
current = "";
|
|
762
|
+
continue;
|
|
763
|
+
}
|
|
764
|
+
current += ch;
|
|
765
|
+
}
|
|
766
|
+
if (current.trim()) {
|
|
767
|
+
parts.push(current.trim());
|
|
768
|
+
}
|
|
769
|
+
return parts;
|
|
770
|
+
}
|
|
771
|
+
async function createPrismaDbAdapter(fallbackClient) {
|
|
772
|
+
const prisma = await loadPrismaClient();
|
|
773
|
+
await ensureCompatibilityViews(prisma);
|
|
774
|
+
let closed = false;
|
|
775
|
+
let adapter;
|
|
776
|
+
const fallbackExecute = async (stmt, error) => {
|
|
777
|
+
if (!fallbackClient) {
|
|
778
|
+
if (error) throw error;
|
|
779
|
+
throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
|
|
780
|
+
}
|
|
781
|
+
if (error) {
|
|
782
|
+
process.stderr.write(
|
|
783
|
+
`[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
784
|
+
`
|
|
785
|
+
);
|
|
786
|
+
}
|
|
787
|
+
return fallbackClient.execute(stmt);
|
|
788
|
+
};
|
|
789
|
+
adapter = {
|
|
790
|
+
async execute(stmt) {
|
|
791
|
+
if (shouldBypassPostgres(stmt)) {
|
|
792
|
+
return fallbackExecute(stmt);
|
|
793
|
+
}
|
|
794
|
+
try {
|
|
795
|
+
return await executeOnPrisma(prisma, stmt);
|
|
796
|
+
} catch (error) {
|
|
797
|
+
if (shouldFallbackOnError(error)) {
|
|
798
|
+
return fallbackExecute(stmt, error);
|
|
799
|
+
}
|
|
800
|
+
throw error;
|
|
801
|
+
}
|
|
802
|
+
},
|
|
803
|
+
async batch(stmts, mode) {
|
|
804
|
+
if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
|
|
805
|
+
if (!fallbackClient) {
|
|
806
|
+
throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
|
|
807
|
+
}
|
|
808
|
+
return fallbackClient.batch(stmts, mode);
|
|
809
|
+
}
|
|
810
|
+
try {
|
|
811
|
+
if (prisma.$transaction) {
|
|
812
|
+
return await prisma.$transaction(async (tx) => {
|
|
813
|
+
const results2 = [];
|
|
814
|
+
for (const stmt of stmts) {
|
|
815
|
+
results2.push(await executeOnPrisma(tx, stmt));
|
|
816
|
+
}
|
|
817
|
+
return results2;
|
|
818
|
+
});
|
|
819
|
+
}
|
|
820
|
+
const results = [];
|
|
821
|
+
for (const stmt of stmts) {
|
|
822
|
+
results.push(await executeOnPrisma(prisma, stmt));
|
|
823
|
+
}
|
|
824
|
+
return results;
|
|
825
|
+
} catch (error) {
|
|
826
|
+
if (fallbackClient && shouldFallbackOnError(error)) {
|
|
827
|
+
process.stderr.write(
|
|
828
|
+
`[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
829
|
+
`
|
|
830
|
+
);
|
|
831
|
+
return fallbackClient.batch(stmts, mode);
|
|
832
|
+
}
|
|
833
|
+
throw error;
|
|
834
|
+
}
|
|
835
|
+
},
|
|
836
|
+
async migrate(stmts) {
|
|
837
|
+
if (fallbackClient) {
|
|
838
|
+
return fallbackClient.migrate(stmts);
|
|
839
|
+
}
|
|
840
|
+
return adapter.batch(stmts, "deferred");
|
|
841
|
+
},
|
|
842
|
+
async transaction(mode) {
|
|
843
|
+
if (!fallbackClient) {
|
|
844
|
+
throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
|
|
845
|
+
}
|
|
846
|
+
return fallbackClient.transaction(mode);
|
|
847
|
+
},
|
|
848
|
+
async executeMultiple(sql) {
|
|
849
|
+
if (fallbackClient && shouldBypassPostgres(sql)) {
|
|
850
|
+
return fallbackClient.executeMultiple(sql);
|
|
851
|
+
}
|
|
852
|
+
for (const statement of splitSqlStatements(sql)) {
|
|
853
|
+
await adapter.execute(statement);
|
|
854
|
+
}
|
|
855
|
+
},
|
|
856
|
+
async sync() {
|
|
857
|
+
if (fallbackClient) {
|
|
858
|
+
return fallbackClient.sync();
|
|
859
|
+
}
|
|
860
|
+
return { frame_no: 0, frames_synced: 0 };
|
|
861
|
+
},
|
|
862
|
+
close() {
|
|
863
|
+
closed = true;
|
|
864
|
+
prismaClientPromise = null;
|
|
865
|
+
compatibilityBootstrapPromise = null;
|
|
866
|
+
void prisma.$disconnect?.();
|
|
867
|
+
},
|
|
868
|
+
get closed() {
|
|
869
|
+
return closed;
|
|
870
|
+
},
|
|
871
|
+
get protocol() {
|
|
872
|
+
return "prisma-postgres";
|
|
873
|
+
}
|
|
874
|
+
};
|
|
875
|
+
return adapter;
|
|
876
|
+
}
|
|
877
|
+
var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
|
|
878
|
+
var init_database_adapter = __esm({
|
|
879
|
+
"src/lib/database-adapter.ts"() {
|
|
880
|
+
"use strict";
|
|
881
|
+
VIEW_MAPPINGS = [
|
|
882
|
+
{ view: "memories", source: "memory.memory_records" },
|
|
883
|
+
{ view: "tasks", source: "memory.tasks" },
|
|
884
|
+
{ view: "behaviors", source: "memory.behaviors" },
|
|
885
|
+
{ view: "entities", source: "memory.entities" },
|
|
886
|
+
{ view: "relationships", source: "memory.relationships" },
|
|
887
|
+
{ view: "entity_memories", source: "memory.entity_memories" },
|
|
888
|
+
{ view: "entity_aliases", source: "memory.entity_aliases" },
|
|
889
|
+
{ view: "notifications", source: "memory.notifications" },
|
|
890
|
+
{ view: "messages", source: "memory.messages" },
|
|
891
|
+
{ view: "users", source: "wiki.users" },
|
|
892
|
+
{ view: "workspaces", source: "wiki.workspaces" },
|
|
893
|
+
{ view: "workspace_users", source: "wiki.workspace_users" },
|
|
894
|
+
{ view: "documents", source: "wiki.workspace_documents" },
|
|
895
|
+
{ view: "chats", source: "wiki.workspace_chats" }
|
|
896
|
+
];
|
|
897
|
+
UPSERT_KEYS = {
|
|
898
|
+
memories: ["id"],
|
|
899
|
+
tasks: ["id"],
|
|
900
|
+
behaviors: ["id"],
|
|
901
|
+
entities: ["id"],
|
|
902
|
+
relationships: ["id"],
|
|
903
|
+
entity_aliases: ["alias"],
|
|
904
|
+
notifications: ["id"],
|
|
905
|
+
messages: ["id"],
|
|
906
|
+
users: ["id"],
|
|
907
|
+
workspaces: ["id"],
|
|
908
|
+
workspace_users: ["id"],
|
|
909
|
+
documents: ["id"],
|
|
910
|
+
chats: ["id"]
|
|
911
|
+
};
|
|
912
|
+
BOOLEAN_COLUMNS_BY_TABLE = {
|
|
913
|
+
memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
|
|
914
|
+
behaviors: /* @__PURE__ */ new Set(["active"]),
|
|
915
|
+
notifications: /* @__PURE__ */ new Set(["read"]),
|
|
916
|
+
users: /* @__PURE__ */ new Set(["has_personal_memory"])
|
|
917
|
+
};
|
|
918
|
+
BOOLEAN_COLUMN_NAMES = new Set(
|
|
919
|
+
Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
|
|
920
|
+
);
|
|
921
|
+
IMMEDIATE_FALLBACK_PATTERNS = [
|
|
922
|
+
/\bPRAGMA\b/i,
|
|
923
|
+
/\bsqlite_master\b/i,
|
|
924
|
+
/(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
|
|
925
|
+
/\bMATCH\b/i,
|
|
926
|
+
/\bvector_distance_cos\s*\(/i,
|
|
927
|
+
/\bjson_extract\s*\(/i,
|
|
928
|
+
/\bjulianday\s*\(/i,
|
|
929
|
+
/\bstrftime\s*\(/i,
|
|
930
|
+
/\blast_insert_rowid\s*\(/i
|
|
931
|
+
];
|
|
932
|
+
prismaClientPromise = null;
|
|
933
|
+
compatibilityBootstrapPromise = null;
|
|
349
934
|
}
|
|
350
935
|
});
|
|
351
936
|
|
|
352
937
|
// src/lib/database.ts
|
|
353
938
|
import { createClient } from "@libsql/client";
|
|
354
939
|
async function initDatabase(config) {
|
|
940
|
+
if (_walCheckpointTimer) {
|
|
941
|
+
clearInterval(_walCheckpointTimer);
|
|
942
|
+
_walCheckpointTimer = null;
|
|
943
|
+
}
|
|
944
|
+
if (_daemonClient) {
|
|
945
|
+
_daemonClient.close();
|
|
946
|
+
_daemonClient = null;
|
|
947
|
+
}
|
|
948
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
949
|
+
_adapterClient.close();
|
|
950
|
+
}
|
|
951
|
+
_adapterClient = null;
|
|
355
952
|
if (_client) {
|
|
356
953
|
_client.close();
|
|
357
954
|
_client = null;
|
|
@@ -365,6 +962,7 @@ async function initDatabase(config) {
|
|
|
365
962
|
}
|
|
366
963
|
_client = createClient(opts);
|
|
367
964
|
_resilientClient = wrapWithRetry(_client);
|
|
965
|
+
_adapterClient = _resilientClient;
|
|
368
966
|
_client.execute("PRAGMA busy_timeout = 30000").catch(() => {
|
|
369
967
|
});
|
|
370
968
|
_client.execute("PRAGMA journal_mode = WAL").catch(() => {
|
|
@@ -375,11 +973,17 @@ async function initDatabase(config) {
|
|
|
375
973
|
});
|
|
376
974
|
}, 3e4);
|
|
377
975
|
_walCheckpointTimer.unref();
|
|
976
|
+
if (process.env.DATABASE_URL) {
|
|
977
|
+
_adapterClient = await createPrismaDbAdapter(_resilientClient);
|
|
978
|
+
}
|
|
378
979
|
}
|
|
379
980
|
function getClient() {
|
|
380
|
-
if (!
|
|
981
|
+
if (!_adapterClient) {
|
|
381
982
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
382
983
|
}
|
|
984
|
+
if (process.env.DATABASE_URL) {
|
|
985
|
+
return _adapterClient;
|
|
986
|
+
}
|
|
383
987
|
if (process.env.EXE_IS_DAEMON === "1") {
|
|
384
988
|
return _resilientClient;
|
|
385
989
|
}
|
|
@@ -1320,26 +1924,36 @@ async function ensureSchema() {
|
|
|
1320
1924
|
}
|
|
1321
1925
|
}
|
|
1322
1926
|
async function disposeDatabase() {
|
|
1927
|
+
if (_walCheckpointTimer) {
|
|
1928
|
+
clearInterval(_walCheckpointTimer);
|
|
1929
|
+
_walCheckpointTimer = null;
|
|
1930
|
+
}
|
|
1323
1931
|
if (_daemonClient) {
|
|
1324
1932
|
_daemonClient.close();
|
|
1325
1933
|
_daemonClient = null;
|
|
1326
1934
|
}
|
|
1935
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
1936
|
+
_adapterClient.close();
|
|
1937
|
+
}
|
|
1938
|
+
_adapterClient = null;
|
|
1327
1939
|
if (_client) {
|
|
1328
1940
|
_client.close();
|
|
1329
1941
|
_client = null;
|
|
1330
1942
|
_resilientClient = null;
|
|
1331
1943
|
}
|
|
1332
1944
|
}
|
|
1333
|
-
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
|
|
1945
|
+
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
|
|
1334
1946
|
var init_database = __esm({
|
|
1335
1947
|
"src/lib/database.ts"() {
|
|
1336
1948
|
"use strict";
|
|
1337
1949
|
init_db_retry();
|
|
1338
1950
|
init_employees();
|
|
1951
|
+
init_database_adapter();
|
|
1339
1952
|
_client = null;
|
|
1340
1953
|
_resilientClient = null;
|
|
1341
1954
|
_walCheckpointTimer = null;
|
|
1342
1955
|
_daemonClient = null;
|
|
1956
|
+
_adapterClient = null;
|
|
1343
1957
|
initTurso = initDatabase;
|
|
1344
1958
|
disposeTurso = disposeDatabase;
|
|
1345
1959
|
}
|
|
@@ -1348,13 +1962,13 @@ var init_database = __esm({
|
|
|
1348
1962
|
// src/lib/keychain.ts
|
|
1349
1963
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
1350
1964
|
import { existsSync as existsSync3 } from "fs";
|
|
1351
|
-
import
|
|
1352
|
-
import
|
|
1965
|
+
import path4 from "path";
|
|
1966
|
+
import os4 from "os";
|
|
1353
1967
|
function getKeyDir() {
|
|
1354
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
1968
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path4.join(os4.homedir(), ".exe-os");
|
|
1355
1969
|
}
|
|
1356
1970
|
function getKeyPath() {
|
|
1357
|
-
return
|
|
1971
|
+
return path4.join(getKeyDir(), "master.key");
|
|
1358
1972
|
}
|
|
1359
1973
|
async function tryKeytar() {
|
|
1360
1974
|
try {
|
|
@@ -1377,7 +1991,7 @@ async function getMasterKey() {
|
|
|
1377
1991
|
const keyPath = getKeyPath();
|
|
1378
1992
|
if (!existsSync3(keyPath)) {
|
|
1379
1993
|
process.stderr.write(
|
|
1380
|
-
`[keychain] Key not found at ${keyPath} (HOME=${
|
|
1994
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os4.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
1381
1995
|
`
|
|
1382
1996
|
);
|
|
1383
1997
|
return null;
|
|
@@ -1470,7 +2084,7 @@ __export(shard_manager_exports, {
|
|
|
1470
2084
|
listShards: () => listShards,
|
|
1471
2085
|
shardExists: () => shardExists
|
|
1472
2086
|
});
|
|
1473
|
-
import
|
|
2087
|
+
import path5 from "path";
|
|
1474
2088
|
import { existsSync as existsSync4, mkdirSync, readdirSync } from "fs";
|
|
1475
2089
|
import { createClient as createClient2 } from "@libsql/client";
|
|
1476
2090
|
function initShardManager(encryptionKey) {
|
|
@@ -1496,7 +2110,7 @@ function getShardClient(projectName) {
|
|
|
1496
2110
|
}
|
|
1497
2111
|
const cached = _shards.get(safeName);
|
|
1498
2112
|
if (cached) return cached;
|
|
1499
|
-
const dbPath =
|
|
2113
|
+
const dbPath = path5.join(SHARDS_DIR, `${safeName}.db`);
|
|
1500
2114
|
const client = createClient2({
|
|
1501
2115
|
url: `file:${dbPath}`,
|
|
1502
2116
|
encryptionKey: _encryptionKey
|
|
@@ -1506,7 +2120,7 @@ function getShardClient(projectName) {
|
|
|
1506
2120
|
}
|
|
1507
2121
|
function shardExists(projectName) {
|
|
1508
2122
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
1509
|
-
return existsSync4(
|
|
2123
|
+
return existsSync4(path5.join(SHARDS_DIR, `${safeName}.db`));
|
|
1510
2124
|
}
|
|
1511
2125
|
function listShards() {
|
|
1512
2126
|
if (!existsSync4(SHARDS_DIR)) return [];
|
|
@@ -1583,7 +2197,23 @@ async function ensureShardSchema(client) {
|
|
|
1583
2197
|
// MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
|
|
1584
2198
|
"ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
|
|
1585
2199
|
"ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
|
|
1586
|
-
"ALTER TABLE memories ADD COLUMN trajectory TEXT"
|
|
2200
|
+
"ALTER TABLE memories ADD COLUMN trajectory TEXT",
|
|
2201
|
+
// Metadata enrichment columns (must match database.ts)
|
|
2202
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
2203
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
2204
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
2205
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
2206
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
2207
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
2208
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
2209
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
2210
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
2211
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
2212
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
2213
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
2214
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
2215
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
2216
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
1587
2217
|
]) {
|
|
1588
2218
|
try {
|
|
1589
2219
|
await client.execute(col);
|
|
@@ -1695,7 +2325,7 @@ var init_shard_manager = __esm({
|
|
|
1695
2325
|
"src/lib/shard-manager.ts"() {
|
|
1696
2326
|
"use strict";
|
|
1697
2327
|
init_config();
|
|
1698
|
-
SHARDS_DIR =
|
|
2328
|
+
SHARDS_DIR = path5.join(EXE_AI_DIR, "shards");
|
|
1699
2329
|
_shards = /* @__PURE__ */ new Map();
|
|
1700
2330
|
_encryptionKey = null;
|
|
1701
2331
|
_shardingEnabled = false;
|
|
@@ -2558,7 +3188,7 @@ __export(reranker_exports, {
|
|
|
2558
3188
|
rerankWithContext: () => rerankWithContext,
|
|
2559
3189
|
rerankWithScores: () => rerankWithScores
|
|
2560
3190
|
});
|
|
2561
|
-
import
|
|
3191
|
+
import path6 from "path";
|
|
2562
3192
|
import { existsSync as existsSync5 } from "fs";
|
|
2563
3193
|
function resetIdleTimer() {
|
|
2564
3194
|
if (_idleTimer) clearTimeout(_idleTimer);
|
|
@@ -2570,17 +3200,17 @@ function resetIdleTimer() {
|
|
|
2570
3200
|
}
|
|
2571
3201
|
}
|
|
2572
3202
|
function isRerankerAvailable() {
|
|
2573
|
-
return existsSync5(
|
|
3203
|
+
return existsSync5(path6.join(MODELS_DIR, RERANKER_MODEL_FILE));
|
|
2574
3204
|
}
|
|
2575
3205
|
function getRerankerModelPath() {
|
|
2576
|
-
return
|
|
3206
|
+
return path6.join(MODELS_DIR, RERANKER_MODEL_FILE);
|
|
2577
3207
|
}
|
|
2578
3208
|
async function ensureLoaded() {
|
|
2579
3209
|
if (_rerankerContext) {
|
|
2580
3210
|
resetIdleTimer();
|
|
2581
3211
|
return;
|
|
2582
3212
|
}
|
|
2583
|
-
const modelPath =
|
|
3213
|
+
const modelPath = path6.join(MODELS_DIR, RERANKER_MODEL_FILE);
|
|
2584
3214
|
if (!existsSync5(modelPath)) {
|
|
2585
3215
|
throw new Error(
|
|
2586
3216
|
`Reranker model not found at ${modelPath}. Run /exe-setup to download it.`
|
|
@@ -2679,11 +3309,11 @@ var init_reranker = __esm({
|
|
|
2679
3309
|
|
|
2680
3310
|
// src/lib/exe-daemon-client.ts
|
|
2681
3311
|
import net from "net";
|
|
2682
|
-
import
|
|
3312
|
+
import os5 from "os";
|
|
2683
3313
|
import { spawn } from "child_process";
|
|
2684
3314
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
2685
3315
|
import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
|
|
2686
|
-
import
|
|
3316
|
+
import path7 from "path";
|
|
2687
3317
|
import { fileURLToPath } from "url";
|
|
2688
3318
|
function handleData(chunk) {
|
|
2689
3319
|
_buffer += chunk.toString();
|
|
@@ -2734,17 +3364,17 @@ function cleanupStaleFiles() {
|
|
|
2734
3364
|
}
|
|
2735
3365
|
}
|
|
2736
3366
|
function findPackageRoot() {
|
|
2737
|
-
let dir =
|
|
2738
|
-
const { root } =
|
|
3367
|
+
let dir = path7.dirname(fileURLToPath(import.meta.url));
|
|
3368
|
+
const { root } = path7.parse(dir);
|
|
2739
3369
|
while (dir !== root) {
|
|
2740
|
-
if (existsSync6(
|
|
2741
|
-
dir =
|
|
3370
|
+
if (existsSync6(path7.join(dir, "package.json"))) return dir;
|
|
3371
|
+
dir = path7.dirname(dir);
|
|
2742
3372
|
}
|
|
2743
3373
|
return null;
|
|
2744
3374
|
}
|
|
2745
3375
|
function spawnDaemon() {
|
|
2746
|
-
const freeGB =
|
|
2747
|
-
const totalGB =
|
|
3376
|
+
const freeGB = os5.freemem() / (1024 * 1024 * 1024);
|
|
3377
|
+
const totalGB = os5.totalmem() / (1024 * 1024 * 1024);
|
|
2748
3378
|
if (totalGB <= 8) {
|
|
2749
3379
|
process.stderr.write(
|
|
2750
3380
|
`[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
|
|
@@ -2764,7 +3394,7 @@ function spawnDaemon() {
|
|
|
2764
3394
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
2765
3395
|
return;
|
|
2766
3396
|
}
|
|
2767
|
-
const daemonPath =
|
|
3397
|
+
const daemonPath = path7.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
2768
3398
|
if (!existsSync6(daemonPath)) {
|
|
2769
3399
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
2770
3400
|
`);
|
|
@@ -2773,7 +3403,7 @@ function spawnDaemon() {
|
|
|
2773
3403
|
const resolvedPath = daemonPath;
|
|
2774
3404
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
2775
3405
|
`);
|
|
2776
|
-
const logPath =
|
|
3406
|
+
const logPath = path7.join(path7.dirname(SOCKET_PATH), "exed.log");
|
|
2777
3407
|
let stderrFd = "ignore";
|
|
2778
3408
|
try {
|
|
2779
3409
|
stderrFd = openSync(logPath, "a");
|
|
@@ -2924,74 +3554,123 @@ async function pingDaemon() {
|
|
|
2924
3554
|
return null;
|
|
2925
3555
|
}
|
|
2926
3556
|
function killAndRespawnDaemon() {
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
3557
|
+
if (!acquireSpawnLock()) {
|
|
3558
|
+
process.stderr.write("[exed-client] Another process is already restarting daemon \u2014 skipping\n");
|
|
3559
|
+
if (_socket) {
|
|
3560
|
+
_socket.destroy();
|
|
3561
|
+
_socket = null;
|
|
3562
|
+
}
|
|
3563
|
+
_connected = false;
|
|
3564
|
+
_buffer = "";
|
|
3565
|
+
return;
|
|
3566
|
+
}
|
|
3567
|
+
try {
|
|
3568
|
+
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
3569
|
+
if (existsSync6(PID_PATH)) {
|
|
3570
|
+
try {
|
|
3571
|
+
const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
|
|
3572
|
+
if (pid > 0) {
|
|
3573
|
+
try {
|
|
3574
|
+
process.kill(pid, "SIGKILL");
|
|
3575
|
+
} catch {
|
|
3576
|
+
}
|
|
2935
3577
|
}
|
|
3578
|
+
} catch {
|
|
2936
3579
|
}
|
|
3580
|
+
}
|
|
3581
|
+
if (_socket) {
|
|
3582
|
+
_socket.destroy();
|
|
3583
|
+
_socket = null;
|
|
3584
|
+
}
|
|
3585
|
+
_connected = false;
|
|
3586
|
+
_buffer = "";
|
|
3587
|
+
try {
|
|
3588
|
+
unlinkSync2(PID_PATH);
|
|
2937
3589
|
} catch {
|
|
2938
3590
|
}
|
|
3591
|
+
try {
|
|
3592
|
+
unlinkSync2(SOCKET_PATH);
|
|
3593
|
+
} catch {
|
|
3594
|
+
}
|
|
3595
|
+
spawnDaemon();
|
|
3596
|
+
} finally {
|
|
3597
|
+
releaseSpawnLock();
|
|
2939
3598
|
}
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
_socket = null;
|
|
2943
|
-
}
|
|
2944
|
-
_connected = false;
|
|
2945
|
-
_buffer = "";
|
|
3599
|
+
}
|
|
3600
|
+
function isDaemonTooYoung() {
|
|
2946
3601
|
try {
|
|
2947
|
-
|
|
3602
|
+
const stat = statSync(PID_PATH);
|
|
3603
|
+
return Date.now() - stat.mtimeMs < MIN_DAEMON_AGE_MS;
|
|
2948
3604
|
} catch {
|
|
3605
|
+
return false;
|
|
2949
3606
|
}
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
3607
|
+
}
|
|
3608
|
+
async function retryThenRestart(doRequest, label) {
|
|
3609
|
+
const result = await doRequest();
|
|
3610
|
+
if (!result.error) {
|
|
3611
|
+
_consecutiveFailures = 0;
|
|
3612
|
+
return result;
|
|
3613
|
+
}
|
|
3614
|
+
_consecutiveFailures++;
|
|
3615
|
+
for (let i = 0; i < MAX_RETRIES_BEFORE_RESTART; i++) {
|
|
3616
|
+
const delayMs = RETRY_DELAYS_MS[i] ?? 5e3;
|
|
3617
|
+
process.stderr.write(`[exed-client] ${label} failed (${result.error}), retry ${i + 1}/${MAX_RETRIES_BEFORE_RESTART} in ${delayMs}ms
|
|
3618
|
+
`);
|
|
3619
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
3620
|
+
if (!_connected) {
|
|
3621
|
+
if (!await connectToSocket()) continue;
|
|
3622
|
+
}
|
|
3623
|
+
const retry = await doRequest();
|
|
3624
|
+
if (!retry.error) {
|
|
3625
|
+
_consecutiveFailures = 0;
|
|
3626
|
+
return retry;
|
|
3627
|
+
}
|
|
3628
|
+
_consecutiveFailures++;
|
|
3629
|
+
}
|
|
3630
|
+
if (isDaemonTooYoung()) {
|
|
3631
|
+
process.stderr.write(`[exed-client] ${label}: daemon too young (< ${MIN_DAEMON_AGE_MS / 1e3}s) \u2014 skipping restart
|
|
3632
|
+
`);
|
|
3633
|
+
return { error: result.error };
|
|
3634
|
+
}
|
|
3635
|
+
process.stderr.write(`[exed-client] ${label}: ${_consecutiveFailures} consecutive failures \u2014 restarting daemon
|
|
3636
|
+
`);
|
|
3637
|
+
killAndRespawnDaemon();
|
|
3638
|
+
const start = Date.now();
|
|
3639
|
+
let delay2 = 200;
|
|
3640
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
3641
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
3642
|
+
if (await connectToSocket()) break;
|
|
3643
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
2953
3644
|
}
|
|
2954
|
-
|
|
3645
|
+
if (!_connected) return { error: "Daemon restart failed" };
|
|
3646
|
+
const final = await doRequest();
|
|
3647
|
+
if (!final.error) _consecutiveFailures = 0;
|
|
3648
|
+
return final;
|
|
2955
3649
|
}
|
|
2956
3650
|
async function embedViaClient(text, priority = "high") {
|
|
2957
3651
|
if (!_connected && !await connectEmbedDaemon()) return null;
|
|
2958
3652
|
_requestCount++;
|
|
2959
3653
|
if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
|
|
2960
3654
|
const health = await pingDaemon();
|
|
2961
|
-
if (!health) {
|
|
3655
|
+
if (!health && !isDaemonTooYoung()) {
|
|
2962
3656
|
process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
|
|
2963
3657
|
`);
|
|
2964
3658
|
killAndRespawnDaemon();
|
|
2965
3659
|
const start = Date.now();
|
|
2966
|
-
let
|
|
3660
|
+
let d = 200;
|
|
2967
3661
|
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
2968
|
-
await new Promise((r) => setTimeout(r,
|
|
3662
|
+
await new Promise((r) => setTimeout(r, d));
|
|
2969
3663
|
if (await connectToSocket()) break;
|
|
2970
|
-
|
|
3664
|
+
d = Math.min(d * 2, 3e3);
|
|
2971
3665
|
}
|
|
2972
3666
|
if (!_connected) return null;
|
|
2973
3667
|
}
|
|
2974
3668
|
}
|
|
2975
|
-
const result = await
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
killAndRespawnDaemon();
|
|
2981
|
-
const start = Date.now();
|
|
2982
|
-
let delay2 = 200;
|
|
2983
|
-
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
2984
|
-
await new Promise((r) => setTimeout(r, delay2));
|
|
2985
|
-
if (await connectToSocket()) break;
|
|
2986
|
-
delay2 = Math.min(delay2 * 2, 3e3);
|
|
2987
|
-
}
|
|
2988
|
-
if (!_connected) return null;
|
|
2989
|
-
const retry = await sendRequest([text], priority);
|
|
2990
|
-
if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
|
|
2991
|
-
process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
|
|
2992
|
-
`);
|
|
2993
|
-
}
|
|
2994
|
-
return null;
|
|
3669
|
+
const result = await retryThenRestart(
|
|
3670
|
+
() => sendRequest([text], priority),
|
|
3671
|
+
"Embed"
|
|
3672
|
+
);
|
|
3673
|
+
return !result.error && result.vectors?.[0] ? result.vectors[0] : null;
|
|
2995
3674
|
}
|
|
2996
3675
|
function disconnectClient() {
|
|
2997
3676
|
if (_socket) {
|
|
@@ -3006,14 +3685,14 @@ function disconnectClient() {
|
|
|
3006
3685
|
entry.resolve({ error: "Client disconnected" });
|
|
3007
3686
|
}
|
|
3008
3687
|
}
|
|
3009
|
-
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;
|
|
3688
|
+
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;
|
|
3010
3689
|
var init_exe_daemon_client = __esm({
|
|
3011
3690
|
"src/lib/exe-daemon-client.ts"() {
|
|
3012
3691
|
"use strict";
|
|
3013
3692
|
init_config();
|
|
3014
|
-
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ??
|
|
3015
|
-
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ??
|
|
3016
|
-
SPAWN_LOCK_PATH =
|
|
3693
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path7.join(EXE_AI_DIR, "exed.sock");
|
|
3694
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path7.join(EXE_AI_DIR, "exed.pid");
|
|
3695
|
+
SPAWN_LOCK_PATH = path7.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
3017
3696
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
3018
3697
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
3019
3698
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
@@ -3021,7 +3700,11 @@ var init_exe_daemon_client = __esm({
|
|
|
3021
3700
|
_connected = false;
|
|
3022
3701
|
_buffer = "";
|
|
3023
3702
|
_requestCount = 0;
|
|
3703
|
+
_consecutiveFailures = 0;
|
|
3024
3704
|
HEALTH_CHECK_INTERVAL = 100;
|
|
3705
|
+
MAX_RETRIES_BEFORE_RESTART = 3;
|
|
3706
|
+
RETRY_DELAYS_MS = [1e3, 3e3, 5e3];
|
|
3707
|
+
MIN_DAEMON_AGE_MS = 3e4;
|
|
3025
3708
|
_pending = /* @__PURE__ */ new Map();
|
|
3026
3709
|
MAX_BUFFER = 1e7;
|
|
3027
3710
|
}
|
|
@@ -3065,8 +3748,8 @@ async function embedDirect(text) {
|
|
|
3065
3748
|
const llamaCpp = await import("node-llama-cpp");
|
|
3066
3749
|
const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
3067
3750
|
const { existsSync: existsSync8 } = await import("fs");
|
|
3068
|
-
const
|
|
3069
|
-
const modelPath =
|
|
3751
|
+
const path10 = await import("path");
|
|
3752
|
+
const modelPath = path10.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
|
|
3070
3753
|
if (!existsSync8(modelPath)) {
|
|
3071
3754
|
throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
|
|
3072
3755
|
}
|
|
@@ -3102,7 +3785,7 @@ __export(project_name_exports, {
|
|
|
3102
3785
|
getProjectName: () => getProjectName
|
|
3103
3786
|
});
|
|
3104
3787
|
import { execSync as execSync2 } from "child_process";
|
|
3105
|
-
import
|
|
3788
|
+
import path8 from "path";
|
|
3106
3789
|
function getProjectName(cwd) {
|
|
3107
3790
|
const dir = cwd ?? process.cwd();
|
|
3108
3791
|
if (_cached && _cachedCwd === dir) return _cached;
|
|
@@ -3115,7 +3798,7 @@ function getProjectName(cwd) {
|
|
|
3115
3798
|
timeout: 2e3,
|
|
3116
3799
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3117
3800
|
}).trim();
|
|
3118
|
-
repoRoot =
|
|
3801
|
+
repoRoot = path8.dirname(gitCommonDir);
|
|
3119
3802
|
} catch {
|
|
3120
3803
|
repoRoot = execSync2("git rev-parse --show-toplevel", {
|
|
3121
3804
|
cwd: dir,
|
|
@@ -3124,11 +3807,11 @@ function getProjectName(cwd) {
|
|
|
3124
3807
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3125
3808
|
}).trim();
|
|
3126
3809
|
}
|
|
3127
|
-
_cached =
|
|
3810
|
+
_cached = path8.basename(repoRoot);
|
|
3128
3811
|
_cachedCwd = dir;
|
|
3129
3812
|
return _cached;
|
|
3130
3813
|
} catch {
|
|
3131
|
-
_cached =
|
|
3814
|
+
_cached = path8.basename(dir);
|
|
3132
3815
|
_cachedCwd = dir;
|
|
3133
3816
|
return _cached;
|
|
3134
3817
|
}
|
|
@@ -3153,7 +3836,7 @@ __export(file_grep_exports, {
|
|
|
3153
3836
|
});
|
|
3154
3837
|
import { execSync as execSync3 } from "child_process";
|
|
3155
3838
|
import { readFileSync as readFileSync4, readdirSync as readdirSync2, statSync as statSync2, existsSync as existsSync7 } from "fs";
|
|
3156
|
-
import
|
|
3839
|
+
import path9 from "path";
|
|
3157
3840
|
import crypto from "crypto";
|
|
3158
3841
|
function hasRipgrep() {
|
|
3159
3842
|
if (_hasRg === null) {
|
|
@@ -3193,7 +3876,7 @@ async function grepProjectFiles(query, projectRoot, options) {
|
|
|
3193
3876
|
session_id: "file-grep",
|
|
3194
3877
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3195
3878
|
tool_name: "file_grep",
|
|
3196
|
-
project_name:
|
|
3879
|
+
project_name: path9.basename(projectRoot),
|
|
3197
3880
|
has_error: false,
|
|
3198
3881
|
raw_text: `${prefix} ${buildSnippet(hit, projectRoot)}`,
|
|
3199
3882
|
vector: null,
|
|
@@ -3267,7 +3950,7 @@ function grepWithNodeFs(pattern, projectRoot, patterns) {
|
|
|
3267
3950
|
const files = collectFiles(projectRoot, patterns ?? DEFAULT_PATTERNS);
|
|
3268
3951
|
const hits = [];
|
|
3269
3952
|
for (const filePath of files.slice(0, MAX_FILES)) {
|
|
3270
|
-
const absPath =
|
|
3953
|
+
const absPath = path9.join(projectRoot, filePath);
|
|
3271
3954
|
try {
|
|
3272
3955
|
const stat = statSync2(absPath);
|
|
3273
3956
|
if (stat.size > MAX_FILE_SIZE) continue;
|
|
@@ -3294,15 +3977,15 @@ function collectFiles(root, patterns) {
|
|
|
3294
3977
|
const files = [];
|
|
3295
3978
|
function walk(dir, relative) {
|
|
3296
3979
|
if (files.length >= MAX_FILES) return;
|
|
3297
|
-
const basename =
|
|
3980
|
+
const basename = path9.basename(dir);
|
|
3298
3981
|
if (EXCLUDE_DIRS.includes(basename)) return;
|
|
3299
3982
|
try {
|
|
3300
3983
|
const entries = readdirSync2(dir, { withFileTypes: true });
|
|
3301
3984
|
for (const entry of entries) {
|
|
3302
3985
|
if (files.length >= MAX_FILES) return;
|
|
3303
|
-
const rel =
|
|
3986
|
+
const rel = path9.join(relative, entry.name);
|
|
3304
3987
|
if (entry.isDirectory()) {
|
|
3305
|
-
walk(
|
|
3988
|
+
walk(path9.join(dir, entry.name), rel);
|
|
3306
3989
|
} else if (entry.isFile()) {
|
|
3307
3990
|
for (const pat of patterns) {
|
|
3308
3991
|
if (matchGlob(rel, pat)) {
|
|
@@ -3334,7 +4017,7 @@ function matchGlob(filePath, pattern) {
|
|
|
3334
4017
|
if (slashIdx !== -1) {
|
|
3335
4018
|
const dir = pattern.slice(0, slashIdx);
|
|
3336
4019
|
const ext2 = pattern.slice(slashIdx + 1).replace("*", "");
|
|
3337
|
-
const fileDir =
|
|
4020
|
+
const fileDir = path9.dirname(filePath);
|
|
3338
4021
|
return fileDir === dir && filePath.endsWith(ext2);
|
|
3339
4022
|
}
|
|
3340
4023
|
const ext = pattern.replace("*", "");
|
|
@@ -3342,7 +4025,7 @@ function matchGlob(filePath, pattern) {
|
|
|
3342
4025
|
}
|
|
3343
4026
|
function buildSnippet(hit, projectRoot) {
|
|
3344
4027
|
try {
|
|
3345
|
-
const absPath =
|
|
4028
|
+
const absPath = path9.join(projectRoot, hit.filePath);
|
|
3346
4029
|
if (!existsSync7(absPath)) return hit.matchLine;
|
|
3347
4030
|
const lines = readFileSync4(absPath, "utf8").split("\n");
|
|
3348
4031
|
const start = Math.max(0, hit.lineNumber - 3);
|