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