@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
|
@@ -329,6 +329,118 @@ var init_db_retry = __esm({
|
|
|
329
329
|
}
|
|
330
330
|
});
|
|
331
331
|
|
|
332
|
+
// src/lib/runtime-table.ts
|
|
333
|
+
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
334
|
+
var init_runtime_table = __esm({
|
|
335
|
+
"src/lib/runtime-table.ts"() {
|
|
336
|
+
"use strict";
|
|
337
|
+
RUNTIME_TABLE = {
|
|
338
|
+
codex: {
|
|
339
|
+
binary: "codex",
|
|
340
|
+
launchMode: "interactive",
|
|
341
|
+
autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
|
|
342
|
+
inlineFlag: "--no-alt-screen",
|
|
343
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
344
|
+
defaultModel: "gpt-5.4"
|
|
345
|
+
},
|
|
346
|
+
opencode: {
|
|
347
|
+
binary: "opencode",
|
|
348
|
+
launchMode: "exec",
|
|
349
|
+
autoApproveFlag: "--dangerously-skip-permissions",
|
|
350
|
+
inlineFlag: "",
|
|
351
|
+
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
352
|
+
defaultModel: "anthropic/claude-sonnet-4-6"
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
DEFAULT_RUNTIME = "claude";
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
// src/lib/agent-config.ts
|
|
360
|
+
var agent_config_exports = {};
|
|
361
|
+
__export(agent_config_exports, {
|
|
362
|
+
AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
|
|
363
|
+
DEFAULT_MODELS: () => DEFAULT_MODELS,
|
|
364
|
+
KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
|
|
365
|
+
RUNTIME_LABELS: () => RUNTIME_LABELS,
|
|
366
|
+
clearAgentRuntime: () => clearAgentRuntime,
|
|
367
|
+
getAgentRuntime: () => getAgentRuntime,
|
|
368
|
+
loadAgentConfig: () => loadAgentConfig,
|
|
369
|
+
saveAgentConfig: () => saveAgentConfig,
|
|
370
|
+
setAgentRuntime: () => setAgentRuntime
|
|
371
|
+
});
|
|
372
|
+
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync2, mkdirSync } from "fs";
|
|
373
|
+
import path2 from "path";
|
|
374
|
+
function loadAgentConfig() {
|
|
375
|
+
if (!existsSync2(AGENT_CONFIG_PATH)) return {};
|
|
376
|
+
try {
|
|
377
|
+
return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
|
|
378
|
+
} catch {
|
|
379
|
+
return {};
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
function saveAgentConfig(config) {
|
|
383
|
+
const dir = path2.dirname(AGENT_CONFIG_PATH);
|
|
384
|
+
if (!existsSync2(dir)) mkdirSync(dir, { recursive: true });
|
|
385
|
+
writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
386
|
+
}
|
|
387
|
+
function getAgentRuntime(agentId) {
|
|
388
|
+
const config = loadAgentConfig();
|
|
389
|
+
const entry = config[agentId];
|
|
390
|
+
if (entry) return entry;
|
|
391
|
+
const orgDefault = config["default"];
|
|
392
|
+
if (orgDefault) return orgDefault;
|
|
393
|
+
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
394
|
+
}
|
|
395
|
+
function setAgentRuntime(agentId, runtime, model) {
|
|
396
|
+
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
397
|
+
if (!knownModels) {
|
|
398
|
+
return {
|
|
399
|
+
ok: false,
|
|
400
|
+
error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
if (!knownModels.includes(model)) {
|
|
404
|
+
return {
|
|
405
|
+
ok: false,
|
|
406
|
+
error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
const config = loadAgentConfig();
|
|
410
|
+
config[agentId] = { runtime, model };
|
|
411
|
+
saveAgentConfig(config);
|
|
412
|
+
return { ok: true };
|
|
413
|
+
}
|
|
414
|
+
function clearAgentRuntime(agentId) {
|
|
415
|
+
const config = loadAgentConfig();
|
|
416
|
+
delete config[agentId];
|
|
417
|
+
saveAgentConfig(config);
|
|
418
|
+
}
|
|
419
|
+
var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
|
|
420
|
+
var init_agent_config = __esm({
|
|
421
|
+
"src/lib/agent-config.ts"() {
|
|
422
|
+
"use strict";
|
|
423
|
+
init_config();
|
|
424
|
+
init_runtime_table();
|
|
425
|
+
AGENT_CONFIG_PATH = path2.join(EXE_AI_DIR, "agent-config.json");
|
|
426
|
+
KNOWN_RUNTIMES = {
|
|
427
|
+
claude: ["claude-opus-4", "claude-sonnet-4", "claude-haiku-4.5"],
|
|
428
|
+
codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
|
|
429
|
+
opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
|
|
430
|
+
};
|
|
431
|
+
RUNTIME_LABELS = {
|
|
432
|
+
claude: "Claude Code (Anthropic)",
|
|
433
|
+
codex: "Codex (OpenAI)",
|
|
434
|
+
opencode: "OpenCode (open source)"
|
|
435
|
+
};
|
|
436
|
+
DEFAULT_MODELS = {
|
|
437
|
+
claude: "claude-opus-4",
|
|
438
|
+
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
439
|
+
opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
|
|
332
444
|
// src/lib/employees.ts
|
|
333
445
|
var employees_exports = {};
|
|
334
446
|
__export(employees_exports, {
|
|
@@ -344,6 +456,7 @@ __export(employees_exports, {
|
|
|
344
456
|
getEmployeeByRole: () => getEmployeeByRole,
|
|
345
457
|
getEmployeeNamesByRole: () => getEmployeeNamesByRole,
|
|
346
458
|
hasRole: () => hasRole,
|
|
459
|
+
hireEmployee: () => hireEmployee,
|
|
347
460
|
isCoordinatorName: () => isCoordinatorName,
|
|
348
461
|
isCoordinatorRole: () => isCoordinatorRole,
|
|
349
462
|
isMultiInstance: () => isMultiInstance,
|
|
@@ -356,9 +469,9 @@ __export(employees_exports, {
|
|
|
356
469
|
validateEmployeeName: () => validateEmployeeName
|
|
357
470
|
});
|
|
358
471
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
359
|
-
import { existsSync as
|
|
472
|
+
import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
360
473
|
import { execSync } from "child_process";
|
|
361
|
-
import
|
|
474
|
+
import path3 from "path";
|
|
362
475
|
import os2 from "os";
|
|
363
476
|
function normalizeRole(role) {
|
|
364
477
|
return (role ?? "").trim().toLowerCase();
|
|
@@ -395,7 +508,7 @@ function validateEmployeeName(name) {
|
|
|
395
508
|
return { valid: true };
|
|
396
509
|
}
|
|
397
510
|
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
398
|
-
if (!
|
|
511
|
+
if (!existsSync3(employeesPath)) {
|
|
399
512
|
return [];
|
|
400
513
|
}
|
|
401
514
|
const raw = await readFile2(employeesPath, "utf-8");
|
|
@@ -406,13 +519,13 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
|
406
519
|
}
|
|
407
520
|
}
|
|
408
521
|
async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
409
|
-
await mkdir2(
|
|
522
|
+
await mkdir2(path3.dirname(employeesPath), { recursive: true });
|
|
410
523
|
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
411
524
|
}
|
|
412
525
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
413
|
-
if (!
|
|
526
|
+
if (!existsSync3(employeesPath)) return [];
|
|
414
527
|
try {
|
|
415
|
-
return JSON.parse(
|
|
528
|
+
return JSON.parse(readFileSync3(employeesPath, "utf-8"));
|
|
416
529
|
} catch {
|
|
417
530
|
return [];
|
|
418
531
|
}
|
|
@@ -454,6 +567,52 @@ function addEmployee(employees, employee) {
|
|
|
454
567
|
}
|
|
455
568
|
return [...employees, normalized];
|
|
456
569
|
}
|
|
570
|
+
function appendToCoordinatorTeam(employee) {
|
|
571
|
+
const coordinator = getCoordinatorEmployee(loadEmployeesSync());
|
|
572
|
+
if (!coordinator) return;
|
|
573
|
+
const idPath = path3.join(IDENTITY_DIR, `${coordinator.name}.md`);
|
|
574
|
+
if (!existsSync3(idPath)) return;
|
|
575
|
+
const content = readFileSync3(idPath, "utf-8");
|
|
576
|
+
if (content.includes(`**${capitalize(employee.name)}`)) return;
|
|
577
|
+
const teamMatch = content.match(TEAM_SECTION_RE);
|
|
578
|
+
if (!teamMatch || teamMatch.index === void 0) return;
|
|
579
|
+
const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
|
|
580
|
+
const nextHeading = afterTeam.match(/\n## /);
|
|
581
|
+
const entry = `
|
|
582
|
+
**${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
|
|
583
|
+
`;
|
|
584
|
+
let updated;
|
|
585
|
+
if (nextHeading && nextHeading.index !== void 0) {
|
|
586
|
+
const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
|
|
587
|
+
updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
|
|
588
|
+
} else {
|
|
589
|
+
updated = content.trimEnd() + "\n" + entry;
|
|
590
|
+
}
|
|
591
|
+
writeFileSync2(idPath, updated, "utf-8");
|
|
592
|
+
}
|
|
593
|
+
function capitalize(s) {
|
|
594
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
595
|
+
}
|
|
596
|
+
async function hireEmployee(employee) {
|
|
597
|
+
const employees = await loadEmployees();
|
|
598
|
+
const updated = addEmployee(employees, employee);
|
|
599
|
+
await saveEmployees(updated);
|
|
600
|
+
try {
|
|
601
|
+
appendToCoordinatorTeam(employee);
|
|
602
|
+
} catch {
|
|
603
|
+
}
|
|
604
|
+
try {
|
|
605
|
+
const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
|
|
606
|
+
const config = loadAgentConfig2();
|
|
607
|
+
const name = employee.name.toLowerCase();
|
|
608
|
+
if (!config[name] && config["default"]) {
|
|
609
|
+
config[name] = { ...config["default"] };
|
|
610
|
+
saveAgentConfig2(config);
|
|
611
|
+
}
|
|
612
|
+
} catch {
|
|
613
|
+
}
|
|
614
|
+
return updated;
|
|
615
|
+
}
|
|
457
616
|
async function normalizeRosterCase(rosterPath) {
|
|
458
617
|
const employees = await loadEmployees(rosterPath);
|
|
459
618
|
let changed = false;
|
|
@@ -463,14 +622,14 @@ async function normalizeRosterCase(rosterPath) {
|
|
|
463
622
|
emp.name = emp.name.toLowerCase();
|
|
464
623
|
changed = true;
|
|
465
624
|
try {
|
|
466
|
-
const identityDir =
|
|
467
|
-
const oldPath =
|
|
468
|
-
const newPath =
|
|
469
|
-
if (
|
|
625
|
+
const identityDir = path3.join(os2.homedir(), ".exe-os", "identity");
|
|
626
|
+
const oldPath = path3.join(identityDir, `${oldName}.md`);
|
|
627
|
+
const newPath = path3.join(identityDir, `${emp.name}.md`);
|
|
628
|
+
if (existsSync3(oldPath) && !existsSync3(newPath)) {
|
|
470
629
|
renameSync2(oldPath, newPath);
|
|
471
|
-
} else if (
|
|
472
|
-
const content =
|
|
473
|
-
|
|
630
|
+
} else if (existsSync3(oldPath) && oldPath !== newPath) {
|
|
631
|
+
const content = readFileSync3(oldPath, "utf-8");
|
|
632
|
+
writeFileSync2(newPath, content, "utf-8");
|
|
474
633
|
if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
|
|
475
634
|
unlinkSync(oldPath);
|
|
476
635
|
}
|
|
@@ -500,7 +659,7 @@ function registerBinSymlinks(name) {
|
|
|
500
659
|
errors.push("Could not find 'exe-os' in PATH");
|
|
501
660
|
return { created, skipped, errors };
|
|
502
661
|
}
|
|
503
|
-
const binDir =
|
|
662
|
+
const binDir = path3.dirname(exeBinPath);
|
|
504
663
|
let target;
|
|
505
664
|
try {
|
|
506
665
|
target = readlinkSync(exeBinPath);
|
|
@@ -510,8 +669,8 @@ function registerBinSymlinks(name) {
|
|
|
510
669
|
}
|
|
511
670
|
for (const suffix of ["", "-opencode"]) {
|
|
512
671
|
const linkName = `${name}${suffix}`;
|
|
513
|
-
const linkPath =
|
|
514
|
-
if (
|
|
672
|
+
const linkPath = path3.join(binDir, linkName);
|
|
673
|
+
if (existsSync3(linkPath)) {
|
|
515
674
|
skipped.push(linkName);
|
|
516
675
|
continue;
|
|
517
676
|
}
|
|
@@ -524,21 +683,619 @@ function registerBinSymlinks(name) {
|
|
|
524
683
|
}
|
|
525
684
|
return { created, skipped, errors };
|
|
526
685
|
}
|
|
527
|
-
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
|
|
686
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
|
|
528
687
|
var init_employees = __esm({
|
|
529
688
|
"src/lib/employees.ts"() {
|
|
530
689
|
"use strict";
|
|
531
690
|
init_config();
|
|
532
|
-
EMPLOYEES_PATH =
|
|
691
|
+
EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
|
|
533
692
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
534
693
|
COORDINATOR_ROLE = "COO";
|
|
535
694
|
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
695
|
+
IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
|
|
696
|
+
TEAM_SECTION_RE = /^## Team\b.*$/m;
|
|
697
|
+
}
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
// src/lib/database-adapter.ts
|
|
701
|
+
import os3 from "os";
|
|
702
|
+
import path4 from "path";
|
|
703
|
+
import { createRequire } from "module";
|
|
704
|
+
import { pathToFileURL } from "url";
|
|
705
|
+
function quotedIdentifier(identifier) {
|
|
706
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
707
|
+
}
|
|
708
|
+
function unqualifiedTableName(name) {
|
|
709
|
+
const raw = name.trim().replace(/^"|"$/g, "");
|
|
710
|
+
const parts = raw.split(".");
|
|
711
|
+
return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
|
|
712
|
+
}
|
|
713
|
+
function stripTrailingSemicolon(sql) {
|
|
714
|
+
return sql.trim().replace(/;+\s*$/u, "");
|
|
715
|
+
}
|
|
716
|
+
function appendClause(sql, clause) {
|
|
717
|
+
const trimmed = stripTrailingSemicolon(sql);
|
|
718
|
+
const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
|
|
719
|
+
if (!returningMatch) {
|
|
720
|
+
return `${trimmed}${clause}`;
|
|
721
|
+
}
|
|
722
|
+
const idx = returningMatch.index;
|
|
723
|
+
return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
|
|
724
|
+
}
|
|
725
|
+
function normalizeStatement(stmt) {
|
|
726
|
+
if (typeof stmt === "string") {
|
|
727
|
+
return { kind: "positional", sql: stmt, args: [] };
|
|
728
|
+
}
|
|
729
|
+
const sql = stmt.sql;
|
|
730
|
+
if (Array.isArray(stmt.args) || stmt.args === void 0) {
|
|
731
|
+
return { kind: "positional", sql, args: stmt.args ?? [] };
|
|
732
|
+
}
|
|
733
|
+
return { kind: "named", sql, args: stmt.args };
|
|
734
|
+
}
|
|
735
|
+
function rewriteBooleanLiterals(sql) {
|
|
736
|
+
let out = sql;
|
|
737
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
738
|
+
const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
|
|
739
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
|
|
740
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
|
|
741
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
|
|
742
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
|
|
743
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
|
|
744
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
|
|
745
|
+
}
|
|
746
|
+
return out;
|
|
747
|
+
}
|
|
748
|
+
function rewriteInsertOrIgnore(sql) {
|
|
749
|
+
if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
|
|
750
|
+
return sql;
|
|
751
|
+
}
|
|
752
|
+
const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
|
|
753
|
+
return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
|
|
754
|
+
}
|
|
755
|
+
function rewriteInsertOrReplace(sql) {
|
|
756
|
+
const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
|
|
757
|
+
if (!match) {
|
|
758
|
+
return sql;
|
|
759
|
+
}
|
|
760
|
+
const rawTable = match[1];
|
|
761
|
+
const rawColumns = match[2];
|
|
762
|
+
const remainder = match[3];
|
|
763
|
+
const tableName = unqualifiedTableName(rawTable);
|
|
764
|
+
const conflictKeys = UPSERT_KEYS[tableName];
|
|
765
|
+
if (!conflictKeys?.length) {
|
|
766
|
+
return sql;
|
|
767
|
+
}
|
|
768
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
769
|
+
const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
|
|
770
|
+
const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
|
|
771
|
+
const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
|
|
772
|
+
return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
|
|
773
|
+
}
|
|
774
|
+
function rewriteSql(sql) {
|
|
775
|
+
let out = sql;
|
|
776
|
+
out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
|
|
777
|
+
out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
|
|
778
|
+
out = rewriteBooleanLiterals(out);
|
|
779
|
+
out = rewriteInsertOrReplace(out);
|
|
780
|
+
out = rewriteInsertOrIgnore(out);
|
|
781
|
+
return stripTrailingSemicolon(out);
|
|
782
|
+
}
|
|
783
|
+
function toBoolean(value) {
|
|
784
|
+
if (value === null || value === void 0) return value;
|
|
785
|
+
if (typeof value === "boolean") return value;
|
|
786
|
+
if (typeof value === "number") return value !== 0;
|
|
787
|
+
if (typeof value === "bigint") return value !== 0n;
|
|
788
|
+
if (typeof value === "string") {
|
|
789
|
+
const normalized = value.trim().toLowerCase();
|
|
790
|
+
if (normalized === "0" || normalized === "false") return false;
|
|
791
|
+
if (normalized === "1" || normalized === "true") return true;
|
|
792
|
+
}
|
|
793
|
+
return Boolean(value);
|
|
794
|
+
}
|
|
795
|
+
function countQuestionMarks(sql, end) {
|
|
796
|
+
let count = 0;
|
|
797
|
+
let inSingle = false;
|
|
798
|
+
let inDouble = false;
|
|
799
|
+
let inLineComment = false;
|
|
800
|
+
let inBlockComment = false;
|
|
801
|
+
for (let i = 0; i < end; i++) {
|
|
802
|
+
const ch = sql[i];
|
|
803
|
+
const next = sql[i + 1];
|
|
804
|
+
if (inLineComment) {
|
|
805
|
+
if (ch === "\n") inLineComment = false;
|
|
806
|
+
continue;
|
|
807
|
+
}
|
|
808
|
+
if (inBlockComment) {
|
|
809
|
+
if (ch === "*" && next === "/") {
|
|
810
|
+
inBlockComment = false;
|
|
811
|
+
i += 1;
|
|
812
|
+
}
|
|
813
|
+
continue;
|
|
814
|
+
}
|
|
815
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
816
|
+
inLineComment = true;
|
|
817
|
+
i += 1;
|
|
818
|
+
continue;
|
|
819
|
+
}
|
|
820
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
821
|
+
inBlockComment = true;
|
|
822
|
+
i += 1;
|
|
823
|
+
continue;
|
|
824
|
+
}
|
|
825
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
826
|
+
inSingle = !inSingle;
|
|
827
|
+
continue;
|
|
828
|
+
}
|
|
829
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
830
|
+
inDouble = !inDouble;
|
|
831
|
+
continue;
|
|
832
|
+
}
|
|
833
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
834
|
+
count += 1;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
return count;
|
|
838
|
+
}
|
|
839
|
+
function findBooleanPlaceholderIndexes(sql) {
|
|
840
|
+
const indexes = /* @__PURE__ */ new Set();
|
|
841
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
842
|
+
const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
|
|
843
|
+
for (const match of sql.matchAll(pattern)) {
|
|
844
|
+
const matchText = match[0];
|
|
845
|
+
const qIndex = match.index + matchText.lastIndexOf("?");
|
|
846
|
+
indexes.add(countQuestionMarks(sql, qIndex + 1));
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
return indexes;
|
|
850
|
+
}
|
|
851
|
+
function coerceInsertBooleanArgs(sql, args) {
|
|
852
|
+
const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
|
|
853
|
+
if (!match) return;
|
|
854
|
+
const rawTable = match[1];
|
|
855
|
+
const rawColumns = match[2];
|
|
856
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
857
|
+
if (!boolColumns?.size) return;
|
|
858
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
859
|
+
for (const [index, column] of columns.entries()) {
|
|
860
|
+
if (boolColumns.has(column) && index < args.length) {
|
|
861
|
+
args[index] = toBoolean(args[index]);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
function coerceUpdateBooleanArgs(sql, args) {
|
|
866
|
+
const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
|
|
867
|
+
if (!match) return;
|
|
868
|
+
const rawTable = match[1];
|
|
869
|
+
const setClause = match[2];
|
|
870
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
871
|
+
if (!boolColumns?.size) return;
|
|
872
|
+
const assignments = setClause.split(",");
|
|
873
|
+
let placeholderIndex = 0;
|
|
874
|
+
for (const assignment of assignments) {
|
|
875
|
+
if (!assignment.includes("?")) continue;
|
|
876
|
+
placeholderIndex += 1;
|
|
877
|
+
const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
|
|
878
|
+
if (colMatch && boolColumns.has(colMatch[1])) {
|
|
879
|
+
args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
function coerceBooleanArgs(sql, args) {
|
|
884
|
+
const nextArgs = [...args];
|
|
885
|
+
coerceInsertBooleanArgs(sql, nextArgs);
|
|
886
|
+
coerceUpdateBooleanArgs(sql, nextArgs);
|
|
887
|
+
const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
|
|
888
|
+
for (const index of placeholderIndexes) {
|
|
889
|
+
if (index > 0 && index <= nextArgs.length) {
|
|
890
|
+
nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
return nextArgs;
|
|
894
|
+
}
|
|
895
|
+
function convertQuestionMarksToDollarParams(sql) {
|
|
896
|
+
let out = "";
|
|
897
|
+
let placeholder = 0;
|
|
898
|
+
let inSingle = false;
|
|
899
|
+
let inDouble = false;
|
|
900
|
+
let inLineComment = false;
|
|
901
|
+
let inBlockComment = false;
|
|
902
|
+
for (let i = 0; i < sql.length; i++) {
|
|
903
|
+
const ch = sql[i];
|
|
904
|
+
const next = sql[i + 1];
|
|
905
|
+
if (inLineComment) {
|
|
906
|
+
out += ch;
|
|
907
|
+
if (ch === "\n") inLineComment = false;
|
|
908
|
+
continue;
|
|
909
|
+
}
|
|
910
|
+
if (inBlockComment) {
|
|
911
|
+
out += ch;
|
|
912
|
+
if (ch === "*" && next === "/") {
|
|
913
|
+
out += next;
|
|
914
|
+
inBlockComment = false;
|
|
915
|
+
i += 1;
|
|
916
|
+
}
|
|
917
|
+
continue;
|
|
918
|
+
}
|
|
919
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
920
|
+
out += ch + next;
|
|
921
|
+
inLineComment = true;
|
|
922
|
+
i += 1;
|
|
923
|
+
continue;
|
|
924
|
+
}
|
|
925
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
926
|
+
out += ch + next;
|
|
927
|
+
inBlockComment = true;
|
|
928
|
+
i += 1;
|
|
929
|
+
continue;
|
|
930
|
+
}
|
|
931
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
932
|
+
inSingle = !inSingle;
|
|
933
|
+
out += ch;
|
|
934
|
+
continue;
|
|
935
|
+
}
|
|
936
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
937
|
+
inDouble = !inDouble;
|
|
938
|
+
out += ch;
|
|
939
|
+
continue;
|
|
940
|
+
}
|
|
941
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
942
|
+
placeholder += 1;
|
|
943
|
+
out += `$${placeholder}`;
|
|
944
|
+
continue;
|
|
945
|
+
}
|
|
946
|
+
out += ch;
|
|
947
|
+
}
|
|
948
|
+
return out;
|
|
949
|
+
}
|
|
950
|
+
function translateStatementForPostgres(stmt) {
|
|
951
|
+
const normalized = normalizeStatement(stmt);
|
|
952
|
+
if (normalized.kind === "named") {
|
|
953
|
+
throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
|
|
954
|
+
}
|
|
955
|
+
const rewrittenSql = rewriteSql(normalized.sql);
|
|
956
|
+
const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
|
|
957
|
+
return {
|
|
958
|
+
sql: convertQuestionMarksToDollarParams(rewrittenSql),
|
|
959
|
+
args: coercedArgs
|
|
960
|
+
};
|
|
961
|
+
}
|
|
962
|
+
function shouldBypassPostgres(stmt) {
|
|
963
|
+
const normalized = normalizeStatement(stmt);
|
|
964
|
+
if (normalized.kind === "named") {
|
|
965
|
+
return true;
|
|
966
|
+
}
|
|
967
|
+
return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
|
|
968
|
+
}
|
|
969
|
+
function shouldFallbackOnError(error) {
|
|
970
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
971
|
+
return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
|
|
972
|
+
}
|
|
973
|
+
function isReadQuery(sql) {
|
|
974
|
+
const trimmed = sql.trimStart();
|
|
975
|
+
return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
|
|
976
|
+
}
|
|
977
|
+
function buildRow(row, columns) {
|
|
978
|
+
const values = columns.map((column) => row[column]);
|
|
979
|
+
return Object.assign(values, row);
|
|
980
|
+
}
|
|
981
|
+
function buildResultSet(rows, rowsAffected = 0) {
|
|
982
|
+
const columns = rows[0] ? Object.keys(rows[0]) : [];
|
|
983
|
+
const resultRows = rows.map((row) => buildRow(row, columns));
|
|
984
|
+
return {
|
|
985
|
+
columns,
|
|
986
|
+
columnTypes: columns.map(() => ""),
|
|
987
|
+
rows: resultRows,
|
|
988
|
+
rowsAffected,
|
|
989
|
+
lastInsertRowid: void 0,
|
|
990
|
+
toJSON() {
|
|
991
|
+
return {
|
|
992
|
+
columns,
|
|
993
|
+
columnTypes: columns.map(() => ""),
|
|
994
|
+
rows,
|
|
995
|
+
rowsAffected,
|
|
996
|
+
lastInsertRowid: void 0
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
async function loadPrismaClient() {
|
|
1002
|
+
if (!prismaClientPromise) {
|
|
1003
|
+
prismaClientPromise = (async () => {
|
|
1004
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
1005
|
+
if (explicitPath) {
|
|
1006
|
+
const module2 = await import(pathToFileURL(explicitPath).href);
|
|
1007
|
+
const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
|
|
1008
|
+
if (!PrismaClient2) {
|
|
1009
|
+
throw new Error(`No PrismaClient export found at ${explicitPath}`);
|
|
1010
|
+
}
|
|
1011
|
+
return new PrismaClient2();
|
|
1012
|
+
}
|
|
1013
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path4.join(os3.homedir(), "exe-db");
|
|
1014
|
+
const requireFromExeDb = createRequire(path4.join(exeDbRoot, "package.json"));
|
|
1015
|
+
const prismaEntry = requireFromExeDb.resolve("@prisma/client");
|
|
1016
|
+
const module = await import(pathToFileURL(prismaEntry).href);
|
|
1017
|
+
const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
|
|
1018
|
+
if (!PrismaClient) {
|
|
1019
|
+
throw new Error(`No PrismaClient export found in ${prismaEntry}`);
|
|
1020
|
+
}
|
|
1021
|
+
return new PrismaClient();
|
|
1022
|
+
})();
|
|
1023
|
+
}
|
|
1024
|
+
return prismaClientPromise;
|
|
1025
|
+
}
|
|
1026
|
+
async function ensureCompatibilityViews(prisma) {
|
|
1027
|
+
if (!compatibilityBootstrapPromise) {
|
|
1028
|
+
compatibilityBootstrapPromise = (async () => {
|
|
1029
|
+
for (const mapping of VIEW_MAPPINGS) {
|
|
1030
|
+
const relation = mapping.source.replace(/"/g, "");
|
|
1031
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
1032
|
+
"SELECT to_regclass($1) AS regclass",
|
|
1033
|
+
relation
|
|
1034
|
+
);
|
|
1035
|
+
if (!rows[0]?.regclass) {
|
|
1036
|
+
continue;
|
|
1037
|
+
}
|
|
1038
|
+
await prisma.$executeRawUnsafe(
|
|
1039
|
+
`CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
|
|
1040
|
+
);
|
|
1041
|
+
}
|
|
1042
|
+
})();
|
|
1043
|
+
}
|
|
1044
|
+
return compatibilityBootstrapPromise;
|
|
1045
|
+
}
|
|
1046
|
+
async function executeOnPrisma(executor, stmt) {
|
|
1047
|
+
const translated = translateStatementForPostgres(stmt);
|
|
1048
|
+
if (isReadQuery(translated.sql)) {
|
|
1049
|
+
const rows = await executor.$queryRawUnsafe(
|
|
1050
|
+
translated.sql,
|
|
1051
|
+
...translated.args
|
|
1052
|
+
);
|
|
1053
|
+
return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
|
|
1054
|
+
}
|
|
1055
|
+
const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
|
|
1056
|
+
return buildResultSet([], rowsAffected);
|
|
1057
|
+
}
|
|
1058
|
+
function splitSqlStatements(sql) {
|
|
1059
|
+
const parts = [];
|
|
1060
|
+
let current = "";
|
|
1061
|
+
let inSingle = false;
|
|
1062
|
+
let inDouble = false;
|
|
1063
|
+
let inLineComment = false;
|
|
1064
|
+
let inBlockComment = false;
|
|
1065
|
+
for (let i = 0; i < sql.length; i++) {
|
|
1066
|
+
const ch = sql[i];
|
|
1067
|
+
const next = sql[i + 1];
|
|
1068
|
+
if (inLineComment) {
|
|
1069
|
+
current += ch;
|
|
1070
|
+
if (ch === "\n") inLineComment = false;
|
|
1071
|
+
continue;
|
|
1072
|
+
}
|
|
1073
|
+
if (inBlockComment) {
|
|
1074
|
+
current += ch;
|
|
1075
|
+
if (ch === "*" && next === "/") {
|
|
1076
|
+
current += next;
|
|
1077
|
+
inBlockComment = false;
|
|
1078
|
+
i += 1;
|
|
1079
|
+
}
|
|
1080
|
+
continue;
|
|
1081
|
+
}
|
|
1082
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
1083
|
+
current += ch + next;
|
|
1084
|
+
inLineComment = true;
|
|
1085
|
+
i += 1;
|
|
1086
|
+
continue;
|
|
1087
|
+
}
|
|
1088
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
1089
|
+
current += ch + next;
|
|
1090
|
+
inBlockComment = true;
|
|
1091
|
+
i += 1;
|
|
1092
|
+
continue;
|
|
1093
|
+
}
|
|
1094
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
1095
|
+
inSingle = !inSingle;
|
|
1096
|
+
current += ch;
|
|
1097
|
+
continue;
|
|
1098
|
+
}
|
|
1099
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
1100
|
+
inDouble = !inDouble;
|
|
1101
|
+
current += ch;
|
|
1102
|
+
continue;
|
|
1103
|
+
}
|
|
1104
|
+
if (!inSingle && !inDouble && ch === ";") {
|
|
1105
|
+
if (current.trim()) {
|
|
1106
|
+
parts.push(current.trim());
|
|
1107
|
+
}
|
|
1108
|
+
current = "";
|
|
1109
|
+
continue;
|
|
1110
|
+
}
|
|
1111
|
+
current += ch;
|
|
1112
|
+
}
|
|
1113
|
+
if (current.trim()) {
|
|
1114
|
+
parts.push(current.trim());
|
|
1115
|
+
}
|
|
1116
|
+
return parts;
|
|
1117
|
+
}
|
|
1118
|
+
async function createPrismaDbAdapter(fallbackClient) {
|
|
1119
|
+
const prisma = await loadPrismaClient();
|
|
1120
|
+
await ensureCompatibilityViews(prisma);
|
|
1121
|
+
let closed = false;
|
|
1122
|
+
let adapter;
|
|
1123
|
+
const fallbackExecute = async (stmt, error) => {
|
|
1124
|
+
if (!fallbackClient) {
|
|
1125
|
+
if (error) throw error;
|
|
1126
|
+
throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
|
|
1127
|
+
}
|
|
1128
|
+
if (error) {
|
|
1129
|
+
process.stderr.write(
|
|
1130
|
+
`[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
1131
|
+
`
|
|
1132
|
+
);
|
|
1133
|
+
}
|
|
1134
|
+
return fallbackClient.execute(stmt);
|
|
1135
|
+
};
|
|
1136
|
+
adapter = {
|
|
1137
|
+
async execute(stmt) {
|
|
1138
|
+
if (shouldBypassPostgres(stmt)) {
|
|
1139
|
+
return fallbackExecute(stmt);
|
|
1140
|
+
}
|
|
1141
|
+
try {
|
|
1142
|
+
return await executeOnPrisma(prisma, stmt);
|
|
1143
|
+
} catch (error) {
|
|
1144
|
+
if (shouldFallbackOnError(error)) {
|
|
1145
|
+
return fallbackExecute(stmt, error);
|
|
1146
|
+
}
|
|
1147
|
+
throw error;
|
|
1148
|
+
}
|
|
1149
|
+
},
|
|
1150
|
+
async batch(stmts, mode) {
|
|
1151
|
+
if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
|
|
1152
|
+
if (!fallbackClient) {
|
|
1153
|
+
throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
|
|
1154
|
+
}
|
|
1155
|
+
return fallbackClient.batch(stmts, mode);
|
|
1156
|
+
}
|
|
1157
|
+
try {
|
|
1158
|
+
if (prisma.$transaction) {
|
|
1159
|
+
return await prisma.$transaction(async (tx) => {
|
|
1160
|
+
const results2 = [];
|
|
1161
|
+
for (const stmt of stmts) {
|
|
1162
|
+
results2.push(await executeOnPrisma(tx, stmt));
|
|
1163
|
+
}
|
|
1164
|
+
return results2;
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
1167
|
+
const results = [];
|
|
1168
|
+
for (const stmt of stmts) {
|
|
1169
|
+
results.push(await executeOnPrisma(prisma, stmt));
|
|
1170
|
+
}
|
|
1171
|
+
return results;
|
|
1172
|
+
} catch (error) {
|
|
1173
|
+
if (fallbackClient && shouldFallbackOnError(error)) {
|
|
1174
|
+
process.stderr.write(
|
|
1175
|
+
`[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
1176
|
+
`
|
|
1177
|
+
);
|
|
1178
|
+
return fallbackClient.batch(stmts, mode);
|
|
1179
|
+
}
|
|
1180
|
+
throw error;
|
|
1181
|
+
}
|
|
1182
|
+
},
|
|
1183
|
+
async migrate(stmts) {
|
|
1184
|
+
if (fallbackClient) {
|
|
1185
|
+
return fallbackClient.migrate(stmts);
|
|
1186
|
+
}
|
|
1187
|
+
return adapter.batch(stmts, "deferred");
|
|
1188
|
+
},
|
|
1189
|
+
async transaction(mode) {
|
|
1190
|
+
if (!fallbackClient) {
|
|
1191
|
+
throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
|
|
1192
|
+
}
|
|
1193
|
+
return fallbackClient.transaction(mode);
|
|
1194
|
+
},
|
|
1195
|
+
async executeMultiple(sql) {
|
|
1196
|
+
if (fallbackClient && shouldBypassPostgres(sql)) {
|
|
1197
|
+
return fallbackClient.executeMultiple(sql);
|
|
1198
|
+
}
|
|
1199
|
+
for (const statement of splitSqlStatements(sql)) {
|
|
1200
|
+
await adapter.execute(statement);
|
|
1201
|
+
}
|
|
1202
|
+
},
|
|
1203
|
+
async sync() {
|
|
1204
|
+
if (fallbackClient) {
|
|
1205
|
+
return fallbackClient.sync();
|
|
1206
|
+
}
|
|
1207
|
+
return { frame_no: 0, frames_synced: 0 };
|
|
1208
|
+
},
|
|
1209
|
+
close() {
|
|
1210
|
+
closed = true;
|
|
1211
|
+
prismaClientPromise = null;
|
|
1212
|
+
compatibilityBootstrapPromise = null;
|
|
1213
|
+
void prisma.$disconnect?.();
|
|
1214
|
+
},
|
|
1215
|
+
get closed() {
|
|
1216
|
+
return closed;
|
|
1217
|
+
},
|
|
1218
|
+
get protocol() {
|
|
1219
|
+
return "prisma-postgres";
|
|
1220
|
+
}
|
|
1221
|
+
};
|
|
1222
|
+
return adapter;
|
|
1223
|
+
}
|
|
1224
|
+
var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
|
|
1225
|
+
var init_database_adapter = __esm({
|
|
1226
|
+
"src/lib/database-adapter.ts"() {
|
|
1227
|
+
"use strict";
|
|
1228
|
+
VIEW_MAPPINGS = [
|
|
1229
|
+
{ view: "memories", source: "memory.memory_records" },
|
|
1230
|
+
{ view: "tasks", source: "memory.tasks" },
|
|
1231
|
+
{ view: "behaviors", source: "memory.behaviors" },
|
|
1232
|
+
{ view: "entities", source: "memory.entities" },
|
|
1233
|
+
{ view: "relationships", source: "memory.relationships" },
|
|
1234
|
+
{ view: "entity_memories", source: "memory.entity_memories" },
|
|
1235
|
+
{ view: "entity_aliases", source: "memory.entity_aliases" },
|
|
1236
|
+
{ view: "notifications", source: "memory.notifications" },
|
|
1237
|
+
{ view: "messages", source: "memory.messages" },
|
|
1238
|
+
{ view: "users", source: "wiki.users" },
|
|
1239
|
+
{ view: "workspaces", source: "wiki.workspaces" },
|
|
1240
|
+
{ view: "workspace_users", source: "wiki.workspace_users" },
|
|
1241
|
+
{ view: "documents", source: "wiki.workspace_documents" },
|
|
1242
|
+
{ view: "chats", source: "wiki.workspace_chats" }
|
|
1243
|
+
];
|
|
1244
|
+
UPSERT_KEYS = {
|
|
1245
|
+
memories: ["id"],
|
|
1246
|
+
tasks: ["id"],
|
|
1247
|
+
behaviors: ["id"],
|
|
1248
|
+
entities: ["id"],
|
|
1249
|
+
relationships: ["id"],
|
|
1250
|
+
entity_aliases: ["alias"],
|
|
1251
|
+
notifications: ["id"],
|
|
1252
|
+
messages: ["id"],
|
|
1253
|
+
users: ["id"],
|
|
1254
|
+
workspaces: ["id"],
|
|
1255
|
+
workspace_users: ["id"],
|
|
1256
|
+
documents: ["id"],
|
|
1257
|
+
chats: ["id"]
|
|
1258
|
+
};
|
|
1259
|
+
BOOLEAN_COLUMNS_BY_TABLE = {
|
|
1260
|
+
memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
|
|
1261
|
+
behaviors: /* @__PURE__ */ new Set(["active"]),
|
|
1262
|
+
notifications: /* @__PURE__ */ new Set(["read"]),
|
|
1263
|
+
users: /* @__PURE__ */ new Set(["has_personal_memory"])
|
|
1264
|
+
};
|
|
1265
|
+
BOOLEAN_COLUMN_NAMES = new Set(
|
|
1266
|
+
Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
|
|
1267
|
+
);
|
|
1268
|
+
IMMEDIATE_FALLBACK_PATTERNS = [
|
|
1269
|
+
/\bPRAGMA\b/i,
|
|
1270
|
+
/\bsqlite_master\b/i,
|
|
1271
|
+
/(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
|
|
1272
|
+
/\bMATCH\b/i,
|
|
1273
|
+
/\bvector_distance_cos\s*\(/i,
|
|
1274
|
+
/\bjson_extract\s*\(/i,
|
|
1275
|
+
/\bjulianday\s*\(/i,
|
|
1276
|
+
/\bstrftime\s*\(/i,
|
|
1277
|
+
/\blast_insert_rowid\s*\(/i
|
|
1278
|
+
];
|
|
1279
|
+
prismaClientPromise = null;
|
|
1280
|
+
compatibilityBootstrapPromise = null;
|
|
536
1281
|
}
|
|
537
1282
|
});
|
|
538
1283
|
|
|
539
1284
|
// src/lib/database.ts
|
|
540
1285
|
import { createClient } from "@libsql/client";
|
|
541
1286
|
async function initDatabase(config) {
|
|
1287
|
+
if (_walCheckpointTimer) {
|
|
1288
|
+
clearInterval(_walCheckpointTimer);
|
|
1289
|
+
_walCheckpointTimer = null;
|
|
1290
|
+
}
|
|
1291
|
+
if (_daemonClient) {
|
|
1292
|
+
_daemonClient.close();
|
|
1293
|
+
_daemonClient = null;
|
|
1294
|
+
}
|
|
1295
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
1296
|
+
_adapterClient.close();
|
|
1297
|
+
}
|
|
1298
|
+
_adapterClient = null;
|
|
542
1299
|
if (_client) {
|
|
543
1300
|
_client.close();
|
|
544
1301
|
_client = null;
|
|
@@ -552,6 +1309,7 @@ async function initDatabase(config) {
|
|
|
552
1309
|
}
|
|
553
1310
|
_client = createClient(opts);
|
|
554
1311
|
_resilientClient = wrapWithRetry(_client);
|
|
1312
|
+
_adapterClient = _resilientClient;
|
|
555
1313
|
_client.execute("PRAGMA busy_timeout = 30000").catch(() => {
|
|
556
1314
|
});
|
|
557
1315
|
_client.execute("PRAGMA journal_mode = WAL").catch(() => {
|
|
@@ -562,11 +1320,17 @@ async function initDatabase(config) {
|
|
|
562
1320
|
});
|
|
563
1321
|
}, 3e4);
|
|
564
1322
|
_walCheckpointTimer.unref();
|
|
1323
|
+
if (process.env.DATABASE_URL) {
|
|
1324
|
+
_adapterClient = await createPrismaDbAdapter(_resilientClient);
|
|
1325
|
+
}
|
|
565
1326
|
}
|
|
566
1327
|
function getClient() {
|
|
567
|
-
if (!
|
|
1328
|
+
if (!_adapterClient) {
|
|
568
1329
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
569
1330
|
}
|
|
1331
|
+
if (process.env.DATABASE_URL) {
|
|
1332
|
+
return _adapterClient;
|
|
1333
|
+
}
|
|
570
1334
|
if (process.env.EXE_IS_DAEMON === "1") {
|
|
571
1335
|
return _resilientClient;
|
|
572
1336
|
}
|
|
@@ -1507,26 +2271,36 @@ async function ensureSchema() {
|
|
|
1507
2271
|
}
|
|
1508
2272
|
}
|
|
1509
2273
|
async function disposeDatabase() {
|
|
2274
|
+
if (_walCheckpointTimer) {
|
|
2275
|
+
clearInterval(_walCheckpointTimer);
|
|
2276
|
+
_walCheckpointTimer = null;
|
|
2277
|
+
}
|
|
1510
2278
|
if (_daemonClient) {
|
|
1511
2279
|
_daemonClient.close();
|
|
1512
2280
|
_daemonClient = null;
|
|
1513
2281
|
}
|
|
2282
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
2283
|
+
_adapterClient.close();
|
|
2284
|
+
}
|
|
2285
|
+
_adapterClient = null;
|
|
1514
2286
|
if (_client) {
|
|
1515
2287
|
_client.close();
|
|
1516
2288
|
_client = null;
|
|
1517
2289
|
_resilientClient = null;
|
|
1518
2290
|
}
|
|
1519
2291
|
}
|
|
1520
|
-
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
|
|
2292
|
+
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
|
|
1521
2293
|
var init_database = __esm({
|
|
1522
2294
|
"src/lib/database.ts"() {
|
|
1523
2295
|
"use strict";
|
|
1524
2296
|
init_db_retry();
|
|
1525
2297
|
init_employees();
|
|
2298
|
+
init_database_adapter();
|
|
1526
2299
|
_client = null;
|
|
1527
2300
|
_resilientClient = null;
|
|
1528
2301
|
_walCheckpointTimer = null;
|
|
1529
2302
|
_daemonClient = null;
|
|
2303
|
+
_adapterClient = null;
|
|
1530
2304
|
initTurso = initDatabase;
|
|
1531
2305
|
disposeTurso = disposeDatabase;
|
|
1532
2306
|
}
|
|
@@ -1534,14 +2308,14 @@ var init_database = __esm({
|
|
|
1534
2308
|
|
|
1535
2309
|
// src/lib/keychain.ts
|
|
1536
2310
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
1537
|
-
import { existsSync as
|
|
1538
|
-
import
|
|
1539
|
-
import
|
|
2311
|
+
import { existsSync as existsSync4 } from "fs";
|
|
2312
|
+
import path5 from "path";
|
|
2313
|
+
import os4 from "os";
|
|
1540
2314
|
function getKeyDir() {
|
|
1541
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
2315
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path5.join(os4.homedir(), ".exe-os");
|
|
1542
2316
|
}
|
|
1543
2317
|
function getKeyPath() {
|
|
1544
|
-
return
|
|
2318
|
+
return path5.join(getKeyDir(), "master.key");
|
|
1545
2319
|
}
|
|
1546
2320
|
async function tryKeytar() {
|
|
1547
2321
|
try {
|
|
@@ -1562,9 +2336,9 @@ async function getMasterKey() {
|
|
|
1562
2336
|
}
|
|
1563
2337
|
}
|
|
1564
2338
|
const keyPath = getKeyPath();
|
|
1565
|
-
if (!
|
|
2339
|
+
if (!existsSync4(keyPath)) {
|
|
1566
2340
|
process.stderr.write(
|
|
1567
|
-
`[keychain] Key not found at ${keyPath} (HOME=${
|
|
2341
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os4.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
1568
2342
|
`
|
|
1569
2343
|
);
|
|
1570
2344
|
return null;
|
|
@@ -1657,13 +2431,13 @@ __export(shard_manager_exports, {
|
|
|
1657
2431
|
listShards: () => listShards,
|
|
1658
2432
|
shardExists: () => shardExists
|
|
1659
2433
|
});
|
|
1660
|
-
import
|
|
1661
|
-
import { existsSync as
|
|
2434
|
+
import path6 from "path";
|
|
2435
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync } from "fs";
|
|
1662
2436
|
import { createClient as createClient2 } from "@libsql/client";
|
|
1663
2437
|
function initShardManager(encryptionKey) {
|
|
1664
2438
|
_encryptionKey = encryptionKey;
|
|
1665
|
-
if (!
|
|
1666
|
-
|
|
2439
|
+
if (!existsSync5(SHARDS_DIR)) {
|
|
2440
|
+
mkdirSync2(SHARDS_DIR, { recursive: true });
|
|
1667
2441
|
}
|
|
1668
2442
|
_shardingEnabled = true;
|
|
1669
2443
|
}
|
|
@@ -1683,7 +2457,7 @@ function getShardClient(projectName) {
|
|
|
1683
2457
|
}
|
|
1684
2458
|
const cached = _shards.get(safeName);
|
|
1685
2459
|
if (cached) return cached;
|
|
1686
|
-
const dbPath =
|
|
2460
|
+
const dbPath = path6.join(SHARDS_DIR, `${safeName}.db`);
|
|
1687
2461
|
const client = createClient2({
|
|
1688
2462
|
url: `file:${dbPath}`,
|
|
1689
2463
|
encryptionKey: _encryptionKey
|
|
@@ -1693,10 +2467,10 @@ function getShardClient(projectName) {
|
|
|
1693
2467
|
}
|
|
1694
2468
|
function shardExists(projectName) {
|
|
1695
2469
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
1696
|
-
return
|
|
2470
|
+
return existsSync5(path6.join(SHARDS_DIR, `${safeName}.db`));
|
|
1697
2471
|
}
|
|
1698
2472
|
function listShards() {
|
|
1699
|
-
if (!
|
|
2473
|
+
if (!existsSync5(SHARDS_DIR)) return [];
|
|
1700
2474
|
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
1701
2475
|
}
|
|
1702
2476
|
async function ensureShardSchema(client) {
|
|
@@ -1770,7 +2544,23 @@ async function ensureShardSchema(client) {
|
|
|
1770
2544
|
// MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
|
|
1771
2545
|
"ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
|
|
1772
2546
|
"ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
|
|
1773
|
-
"ALTER TABLE memories ADD COLUMN trajectory TEXT"
|
|
2547
|
+
"ALTER TABLE memories ADD COLUMN trajectory TEXT",
|
|
2548
|
+
// Metadata enrichment columns (must match database.ts)
|
|
2549
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
2550
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
2551
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
2552
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
2553
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
2554
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
2555
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
2556
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
2557
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
2558
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
2559
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
2560
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
2561
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
2562
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
2563
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
1774
2564
|
]) {
|
|
1775
2565
|
try {
|
|
1776
2566
|
await client.execute(col);
|
|
@@ -1882,7 +2672,7 @@ var init_shard_manager = __esm({
|
|
|
1882
2672
|
"src/lib/shard-manager.ts"() {
|
|
1883
2673
|
"use strict";
|
|
1884
2674
|
init_config();
|
|
1885
|
-
SHARDS_DIR =
|
|
2675
|
+
SHARDS_DIR = path6.join(EXE_AI_DIR, "shards");
|
|
1886
2676
|
_shards = /* @__PURE__ */ new Map();
|
|
1887
2677
|
_encryptionKey = null;
|
|
1888
2678
|
_shardingEnabled = false;
|
|
@@ -2745,8 +3535,8 @@ __export(reranker_exports, {
|
|
|
2745
3535
|
rerankWithContext: () => rerankWithContext,
|
|
2746
3536
|
rerankWithScores: () => rerankWithScores
|
|
2747
3537
|
});
|
|
2748
|
-
import
|
|
2749
|
-
import { existsSync as
|
|
3538
|
+
import path7 from "path";
|
|
3539
|
+
import { existsSync as existsSync6 } from "fs";
|
|
2750
3540
|
function resetIdleTimer() {
|
|
2751
3541
|
if (_idleTimer) clearTimeout(_idleTimer);
|
|
2752
3542
|
_idleTimer = setTimeout(() => {
|
|
@@ -2757,18 +3547,18 @@ function resetIdleTimer() {
|
|
|
2757
3547
|
}
|
|
2758
3548
|
}
|
|
2759
3549
|
function isRerankerAvailable() {
|
|
2760
|
-
return
|
|
3550
|
+
return existsSync6(path7.join(MODELS_DIR, RERANKER_MODEL_FILE));
|
|
2761
3551
|
}
|
|
2762
3552
|
function getRerankerModelPath() {
|
|
2763
|
-
return
|
|
3553
|
+
return path7.join(MODELS_DIR, RERANKER_MODEL_FILE);
|
|
2764
3554
|
}
|
|
2765
3555
|
async function ensureLoaded() {
|
|
2766
3556
|
if (_rerankerContext) {
|
|
2767
3557
|
resetIdleTimer();
|
|
2768
3558
|
return;
|
|
2769
3559
|
}
|
|
2770
|
-
const modelPath =
|
|
2771
|
-
if (!
|
|
3560
|
+
const modelPath = path7.join(MODELS_DIR, RERANKER_MODEL_FILE);
|
|
3561
|
+
if (!existsSync6(modelPath)) {
|
|
2772
3562
|
throw new Error(
|
|
2773
3563
|
`Reranker model not found at ${modelPath}. Run /exe-setup to download it.`
|
|
2774
3564
|
);
|
|
@@ -2866,11 +3656,11 @@ var init_reranker = __esm({
|
|
|
2866
3656
|
|
|
2867
3657
|
// src/lib/exe-daemon-client.ts
|
|
2868
3658
|
import net from "net";
|
|
2869
|
-
import
|
|
3659
|
+
import os5 from "os";
|
|
2870
3660
|
import { spawn } from "child_process";
|
|
2871
3661
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
2872
|
-
import { existsSync as
|
|
2873
|
-
import
|
|
3662
|
+
import { existsSync as existsSync7, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
|
|
3663
|
+
import path8 from "path";
|
|
2874
3664
|
import { fileURLToPath } from "url";
|
|
2875
3665
|
function handleData(chunk) {
|
|
2876
3666
|
_buffer += chunk.toString();
|
|
@@ -2898,9 +3688,9 @@ function handleData(chunk) {
|
|
|
2898
3688
|
}
|
|
2899
3689
|
}
|
|
2900
3690
|
function cleanupStaleFiles() {
|
|
2901
|
-
if (
|
|
3691
|
+
if (existsSync7(PID_PATH)) {
|
|
2902
3692
|
try {
|
|
2903
|
-
const pid = parseInt(
|
|
3693
|
+
const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
|
|
2904
3694
|
if (pid > 0) {
|
|
2905
3695
|
try {
|
|
2906
3696
|
process.kill(pid, 0);
|
|
@@ -2921,17 +3711,17 @@ function cleanupStaleFiles() {
|
|
|
2921
3711
|
}
|
|
2922
3712
|
}
|
|
2923
3713
|
function findPackageRoot() {
|
|
2924
|
-
let dir =
|
|
2925
|
-
const { root } =
|
|
3714
|
+
let dir = path8.dirname(fileURLToPath(import.meta.url));
|
|
3715
|
+
const { root } = path8.parse(dir);
|
|
2926
3716
|
while (dir !== root) {
|
|
2927
|
-
if (
|
|
2928
|
-
dir =
|
|
3717
|
+
if (existsSync7(path8.join(dir, "package.json"))) return dir;
|
|
3718
|
+
dir = path8.dirname(dir);
|
|
2929
3719
|
}
|
|
2930
3720
|
return null;
|
|
2931
3721
|
}
|
|
2932
3722
|
function spawnDaemon() {
|
|
2933
|
-
const freeGB =
|
|
2934
|
-
const totalGB =
|
|
3723
|
+
const freeGB = os5.freemem() / (1024 * 1024 * 1024);
|
|
3724
|
+
const totalGB = os5.totalmem() / (1024 * 1024 * 1024);
|
|
2935
3725
|
if (totalGB <= 8) {
|
|
2936
3726
|
process.stderr.write(
|
|
2937
3727
|
`[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
|
|
@@ -2951,8 +3741,8 @@ function spawnDaemon() {
|
|
|
2951
3741
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
2952
3742
|
return;
|
|
2953
3743
|
}
|
|
2954
|
-
const daemonPath =
|
|
2955
|
-
if (!
|
|
3744
|
+
const daemonPath = path8.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
3745
|
+
if (!existsSync7(daemonPath)) {
|
|
2956
3746
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
2957
3747
|
`);
|
|
2958
3748
|
return;
|
|
@@ -2960,7 +3750,7 @@ function spawnDaemon() {
|
|
|
2960
3750
|
const resolvedPath = daemonPath;
|
|
2961
3751
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
2962
3752
|
`);
|
|
2963
|
-
const logPath =
|
|
3753
|
+
const logPath = path8.join(path8.dirname(SOCKET_PATH), "exed.log");
|
|
2964
3754
|
let stderrFd = "ignore";
|
|
2965
3755
|
try {
|
|
2966
3756
|
stderrFd = openSync(logPath, "a");
|
|
@@ -3111,74 +3901,123 @@ async function pingDaemon() {
|
|
|
3111
3901
|
return null;
|
|
3112
3902
|
}
|
|
3113
3903
|
function killAndRespawnDaemon() {
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3904
|
+
if (!acquireSpawnLock()) {
|
|
3905
|
+
process.stderr.write("[exed-client] Another process is already restarting daemon \u2014 skipping\n");
|
|
3906
|
+
if (_socket) {
|
|
3907
|
+
_socket.destroy();
|
|
3908
|
+
_socket = null;
|
|
3909
|
+
}
|
|
3910
|
+
_connected = false;
|
|
3911
|
+
_buffer = "";
|
|
3912
|
+
return;
|
|
3913
|
+
}
|
|
3914
|
+
try {
|
|
3915
|
+
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
3916
|
+
if (existsSync7(PID_PATH)) {
|
|
3917
|
+
try {
|
|
3918
|
+
const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
|
|
3919
|
+
if (pid > 0) {
|
|
3920
|
+
try {
|
|
3921
|
+
process.kill(pid, "SIGKILL");
|
|
3922
|
+
} catch {
|
|
3923
|
+
}
|
|
3122
3924
|
}
|
|
3925
|
+
} catch {
|
|
3123
3926
|
}
|
|
3927
|
+
}
|
|
3928
|
+
if (_socket) {
|
|
3929
|
+
_socket.destroy();
|
|
3930
|
+
_socket = null;
|
|
3931
|
+
}
|
|
3932
|
+
_connected = false;
|
|
3933
|
+
_buffer = "";
|
|
3934
|
+
try {
|
|
3935
|
+
unlinkSync2(PID_PATH);
|
|
3124
3936
|
} catch {
|
|
3125
3937
|
}
|
|
3938
|
+
try {
|
|
3939
|
+
unlinkSync2(SOCKET_PATH);
|
|
3940
|
+
} catch {
|
|
3941
|
+
}
|
|
3942
|
+
spawnDaemon();
|
|
3943
|
+
} finally {
|
|
3944
|
+
releaseSpawnLock();
|
|
3126
3945
|
}
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
_socket = null;
|
|
3130
|
-
}
|
|
3131
|
-
_connected = false;
|
|
3132
|
-
_buffer = "";
|
|
3946
|
+
}
|
|
3947
|
+
function isDaemonTooYoung() {
|
|
3133
3948
|
try {
|
|
3134
|
-
|
|
3949
|
+
const stat = statSync(PID_PATH);
|
|
3950
|
+
return Date.now() - stat.mtimeMs < MIN_DAEMON_AGE_MS;
|
|
3135
3951
|
} catch {
|
|
3952
|
+
return false;
|
|
3136
3953
|
}
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3954
|
+
}
|
|
3955
|
+
async function retryThenRestart(doRequest, label) {
|
|
3956
|
+
const result = await doRequest();
|
|
3957
|
+
if (!result.error) {
|
|
3958
|
+
_consecutiveFailures = 0;
|
|
3959
|
+
return result;
|
|
3960
|
+
}
|
|
3961
|
+
_consecutiveFailures++;
|
|
3962
|
+
for (let i = 0; i < MAX_RETRIES_BEFORE_RESTART; i++) {
|
|
3963
|
+
const delayMs = RETRY_DELAYS_MS[i] ?? 5e3;
|
|
3964
|
+
process.stderr.write(`[exed-client] ${label} failed (${result.error}), retry ${i + 1}/${MAX_RETRIES_BEFORE_RESTART} in ${delayMs}ms
|
|
3965
|
+
`);
|
|
3966
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
3967
|
+
if (!_connected) {
|
|
3968
|
+
if (!await connectToSocket()) continue;
|
|
3969
|
+
}
|
|
3970
|
+
const retry = await doRequest();
|
|
3971
|
+
if (!retry.error) {
|
|
3972
|
+
_consecutiveFailures = 0;
|
|
3973
|
+
return retry;
|
|
3974
|
+
}
|
|
3975
|
+
_consecutiveFailures++;
|
|
3976
|
+
}
|
|
3977
|
+
if (isDaemonTooYoung()) {
|
|
3978
|
+
process.stderr.write(`[exed-client] ${label}: daemon too young (< ${MIN_DAEMON_AGE_MS / 1e3}s) \u2014 skipping restart
|
|
3979
|
+
`);
|
|
3980
|
+
return { error: result.error };
|
|
3981
|
+
}
|
|
3982
|
+
process.stderr.write(`[exed-client] ${label}: ${_consecutiveFailures} consecutive failures \u2014 restarting daemon
|
|
3983
|
+
`);
|
|
3984
|
+
killAndRespawnDaemon();
|
|
3985
|
+
const start = Date.now();
|
|
3986
|
+
let delay2 = 200;
|
|
3987
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
3988
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
3989
|
+
if (await connectToSocket()) break;
|
|
3990
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
3140
3991
|
}
|
|
3141
|
-
|
|
3992
|
+
if (!_connected) return { error: "Daemon restart failed" };
|
|
3993
|
+
const final = await doRequest();
|
|
3994
|
+
if (!final.error) _consecutiveFailures = 0;
|
|
3995
|
+
return final;
|
|
3142
3996
|
}
|
|
3143
3997
|
async function embedViaClient(text, priority = "high") {
|
|
3144
3998
|
if (!_connected && !await connectEmbedDaemon()) return null;
|
|
3145
3999
|
_requestCount++;
|
|
3146
4000
|
if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
|
|
3147
4001
|
const health = await pingDaemon();
|
|
3148
|
-
if (!health) {
|
|
4002
|
+
if (!health && !isDaemonTooYoung()) {
|
|
3149
4003
|
process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
|
|
3150
4004
|
`);
|
|
3151
4005
|
killAndRespawnDaemon();
|
|
3152
4006
|
const start = Date.now();
|
|
3153
|
-
let
|
|
4007
|
+
let d = 200;
|
|
3154
4008
|
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
3155
|
-
await new Promise((r) => setTimeout(r,
|
|
4009
|
+
await new Promise((r) => setTimeout(r, d));
|
|
3156
4010
|
if (await connectToSocket()) break;
|
|
3157
|
-
|
|
4011
|
+
d = Math.min(d * 2, 3e3);
|
|
3158
4012
|
}
|
|
3159
4013
|
if (!_connected) return null;
|
|
3160
4014
|
}
|
|
3161
4015
|
}
|
|
3162
|
-
const result = await
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
killAndRespawnDaemon();
|
|
3168
|
-
const start = Date.now();
|
|
3169
|
-
let delay2 = 200;
|
|
3170
|
-
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
3171
|
-
await new Promise((r) => setTimeout(r, delay2));
|
|
3172
|
-
if (await connectToSocket()) break;
|
|
3173
|
-
delay2 = Math.min(delay2 * 2, 3e3);
|
|
3174
|
-
}
|
|
3175
|
-
if (!_connected) return null;
|
|
3176
|
-
const retry = await sendRequest([text], priority);
|
|
3177
|
-
if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
|
|
3178
|
-
process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
|
|
3179
|
-
`);
|
|
3180
|
-
}
|
|
3181
|
-
return null;
|
|
4016
|
+
const result = await retryThenRestart(
|
|
4017
|
+
() => sendRequest([text], priority),
|
|
4018
|
+
"Embed"
|
|
4019
|
+
);
|
|
4020
|
+
return !result.error && result.vectors?.[0] ? result.vectors[0] : null;
|
|
3182
4021
|
}
|
|
3183
4022
|
function disconnectClient() {
|
|
3184
4023
|
if (_socket) {
|
|
@@ -3193,14 +4032,14 @@ function disconnectClient() {
|
|
|
3193
4032
|
entry.resolve({ error: "Client disconnected" });
|
|
3194
4033
|
}
|
|
3195
4034
|
}
|
|
3196
|
-
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;
|
|
4035
|
+
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;
|
|
3197
4036
|
var init_exe_daemon_client = __esm({
|
|
3198
4037
|
"src/lib/exe-daemon-client.ts"() {
|
|
3199
4038
|
"use strict";
|
|
3200
4039
|
init_config();
|
|
3201
|
-
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ??
|
|
3202
|
-
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ??
|
|
3203
|
-
SPAWN_LOCK_PATH =
|
|
4040
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path8.join(EXE_AI_DIR, "exed.sock");
|
|
4041
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path8.join(EXE_AI_DIR, "exed.pid");
|
|
4042
|
+
SPAWN_LOCK_PATH = path8.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
3204
4043
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
3205
4044
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
3206
4045
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
@@ -3208,7 +4047,11 @@ var init_exe_daemon_client = __esm({
|
|
|
3208
4047
|
_connected = false;
|
|
3209
4048
|
_buffer = "";
|
|
3210
4049
|
_requestCount = 0;
|
|
4050
|
+
_consecutiveFailures = 0;
|
|
3211
4051
|
HEALTH_CHECK_INTERVAL = 100;
|
|
4052
|
+
MAX_RETRIES_BEFORE_RESTART = 3;
|
|
4053
|
+
RETRY_DELAYS_MS = [1e3, 3e3, 5e3];
|
|
4054
|
+
MIN_DAEMON_AGE_MS = 3e4;
|
|
3212
4055
|
_pending = /* @__PURE__ */ new Map();
|
|
3213
4056
|
MAX_BUFFER = 1e7;
|
|
3214
4057
|
}
|
|
@@ -3252,8 +4095,8 @@ async function embedDirect(text) {
|
|
|
3252
4095
|
const llamaCpp = await import("node-llama-cpp");
|
|
3253
4096
|
const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
3254
4097
|
const { existsSync: existsSync18 } = await import("fs");
|
|
3255
|
-
const
|
|
3256
|
-
const modelPath =
|
|
4098
|
+
const path23 = await import("path");
|
|
4099
|
+
const modelPath = path23.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
|
|
3257
4100
|
if (!existsSync18(modelPath)) {
|
|
3258
4101
|
throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
|
|
3259
4102
|
}
|
|
@@ -3289,7 +4132,7 @@ __export(project_name_exports, {
|
|
|
3289
4132
|
getProjectName: () => getProjectName
|
|
3290
4133
|
});
|
|
3291
4134
|
import { execSync as execSync2 } from "child_process";
|
|
3292
|
-
import
|
|
4135
|
+
import path9 from "path";
|
|
3293
4136
|
function getProjectName(cwd) {
|
|
3294
4137
|
const dir = cwd ?? process.cwd();
|
|
3295
4138
|
if (_cached && _cachedCwd === dir) return _cached;
|
|
@@ -3302,7 +4145,7 @@ function getProjectName(cwd) {
|
|
|
3302
4145
|
timeout: 2e3,
|
|
3303
4146
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3304
4147
|
}).trim();
|
|
3305
|
-
repoRoot =
|
|
4148
|
+
repoRoot = path9.dirname(gitCommonDir);
|
|
3306
4149
|
} catch {
|
|
3307
4150
|
repoRoot = execSync2("git rev-parse --show-toplevel", {
|
|
3308
4151
|
cwd: dir,
|
|
@@ -3311,11 +4154,11 @@ function getProjectName(cwd) {
|
|
|
3311
4154
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3312
4155
|
}).trim();
|
|
3313
4156
|
}
|
|
3314
|
-
_cached =
|
|
4157
|
+
_cached = path9.basename(repoRoot);
|
|
3315
4158
|
_cachedCwd = dir;
|
|
3316
4159
|
return _cached;
|
|
3317
4160
|
} catch {
|
|
3318
|
-
_cached =
|
|
4161
|
+
_cached = path9.basename(dir);
|
|
3319
4162
|
_cachedCwd = dir;
|
|
3320
4163
|
return _cached;
|
|
3321
4164
|
}
|
|
@@ -3339,8 +4182,8 @@ __export(file_grep_exports, {
|
|
|
3339
4182
|
grepProjectFiles: () => grepProjectFiles
|
|
3340
4183
|
});
|
|
3341
4184
|
import { execSync as execSync3 } from "child_process";
|
|
3342
|
-
import { readFileSync as
|
|
3343
|
-
import
|
|
4185
|
+
import { readFileSync as readFileSync5, readdirSync as readdirSync2, statSync as statSync2, existsSync as existsSync8 } from "fs";
|
|
4186
|
+
import path10 from "path";
|
|
3344
4187
|
import crypto from "crypto";
|
|
3345
4188
|
function hasRipgrep() {
|
|
3346
4189
|
if (_hasRg === null) {
|
|
@@ -3380,7 +4223,7 @@ async function grepProjectFiles(query, projectRoot, options) {
|
|
|
3380
4223
|
session_id: "file-grep",
|
|
3381
4224
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3382
4225
|
tool_name: "file_grep",
|
|
3383
|
-
project_name:
|
|
4226
|
+
project_name: path10.basename(projectRoot),
|
|
3384
4227
|
has_error: false,
|
|
3385
4228
|
raw_text: `${prefix} ${buildSnippet(hit, projectRoot)}`,
|
|
3386
4229
|
vector: null,
|
|
@@ -3392,7 +4235,7 @@ function getChunkContext(filePath, lineNumber) {
|
|
|
3392
4235
|
try {
|
|
3393
4236
|
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
3394
4237
|
if (ext !== "ts" && ext !== "tsx" && ext !== "js" && ext !== "jsx") return "";
|
|
3395
|
-
const source =
|
|
4238
|
+
const source = readFileSync5(filePath, "utf8");
|
|
3396
4239
|
const lines = source.split("\n");
|
|
3397
4240
|
for (let i = Math.min(lineNumber - 1, lines.length - 1); i >= 0; i--) {
|
|
3398
4241
|
const line = lines[i];
|
|
@@ -3454,11 +4297,11 @@ function grepWithNodeFs(pattern, projectRoot, patterns) {
|
|
|
3454
4297
|
const files = collectFiles(projectRoot, patterns ?? DEFAULT_PATTERNS);
|
|
3455
4298
|
const hits = [];
|
|
3456
4299
|
for (const filePath of files.slice(0, MAX_FILES)) {
|
|
3457
|
-
const absPath =
|
|
4300
|
+
const absPath = path10.join(projectRoot, filePath);
|
|
3458
4301
|
try {
|
|
3459
4302
|
const stat = statSync2(absPath);
|
|
3460
4303
|
if (stat.size > MAX_FILE_SIZE) continue;
|
|
3461
|
-
const content =
|
|
4304
|
+
const content = readFileSync5(absPath, "utf8");
|
|
3462
4305
|
const lines = content.split("\n");
|
|
3463
4306
|
const matches = content.match(regex);
|
|
3464
4307
|
if (!matches || matches.length === 0) continue;
|
|
@@ -3481,15 +4324,15 @@ function collectFiles(root, patterns) {
|
|
|
3481
4324
|
const files = [];
|
|
3482
4325
|
function walk(dir, relative) {
|
|
3483
4326
|
if (files.length >= MAX_FILES) return;
|
|
3484
|
-
const basename =
|
|
4327
|
+
const basename = path10.basename(dir);
|
|
3485
4328
|
if (EXCLUDE_DIRS.includes(basename)) return;
|
|
3486
4329
|
try {
|
|
3487
4330
|
const entries = readdirSync2(dir, { withFileTypes: true });
|
|
3488
4331
|
for (const entry of entries) {
|
|
3489
4332
|
if (files.length >= MAX_FILES) return;
|
|
3490
|
-
const rel =
|
|
4333
|
+
const rel = path10.join(relative, entry.name);
|
|
3491
4334
|
if (entry.isDirectory()) {
|
|
3492
|
-
walk(
|
|
4335
|
+
walk(path10.join(dir, entry.name), rel);
|
|
3493
4336
|
} else if (entry.isFile()) {
|
|
3494
4337
|
for (const pat of patterns) {
|
|
3495
4338
|
if (matchGlob(rel, pat)) {
|
|
@@ -3521,7 +4364,7 @@ function matchGlob(filePath, pattern) {
|
|
|
3521
4364
|
if (slashIdx !== -1) {
|
|
3522
4365
|
const dir = pattern.slice(0, slashIdx);
|
|
3523
4366
|
const ext2 = pattern.slice(slashIdx + 1).replace("*", "");
|
|
3524
|
-
const fileDir =
|
|
4367
|
+
const fileDir = path10.dirname(filePath);
|
|
3525
4368
|
return fileDir === dir && filePath.endsWith(ext2);
|
|
3526
4369
|
}
|
|
3527
4370
|
const ext = pattern.replace("*", "");
|
|
@@ -3529,9 +4372,9 @@ function matchGlob(filePath, pattern) {
|
|
|
3529
4372
|
}
|
|
3530
4373
|
function buildSnippet(hit, projectRoot) {
|
|
3531
4374
|
try {
|
|
3532
|
-
const absPath =
|
|
3533
|
-
if (!
|
|
3534
|
-
const lines =
|
|
4375
|
+
const absPath = path10.join(projectRoot, hit.filePath);
|
|
4376
|
+
if (!existsSync8(absPath)) return hit.matchLine;
|
|
4377
|
+
const lines = readFileSync5(absPath, "utf8").split("\n");
|
|
3535
4378
|
const start = Math.max(0, hit.lineNumber - 3);
|
|
3536
4379
|
const end = Math.min(lines.length, hit.lineNumber + 2);
|
|
3537
4380
|
return lines.slice(start, end).join("\n").slice(0, 500);
|
|
@@ -4196,13 +5039,13 @@ var init_session_key = __esm({
|
|
|
4196
5039
|
});
|
|
4197
5040
|
|
|
4198
5041
|
// src/lib/session-registry.ts
|
|
4199
|
-
import { readFileSync as
|
|
4200
|
-
import
|
|
4201
|
-
import
|
|
5042
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync9 } from "fs";
|
|
5043
|
+
import path12 from "path";
|
|
5044
|
+
import os6 from "os";
|
|
4202
5045
|
function registerSession(entry) {
|
|
4203
|
-
const dir =
|
|
4204
|
-
if (!
|
|
4205
|
-
|
|
5046
|
+
const dir = path12.dirname(REGISTRY_PATH);
|
|
5047
|
+
if (!existsSync9(dir)) {
|
|
5048
|
+
mkdirSync4(dir, { recursive: true });
|
|
4206
5049
|
}
|
|
4207
5050
|
const sessions = listSessions();
|
|
4208
5051
|
const idx = sessions.findIndex((s) => s.windowName === entry.windowName);
|
|
@@ -4211,11 +5054,11 @@ function registerSession(entry) {
|
|
|
4211
5054
|
} else {
|
|
4212
5055
|
sessions.push(entry);
|
|
4213
5056
|
}
|
|
4214
|
-
|
|
5057
|
+
writeFileSync4(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
|
|
4215
5058
|
}
|
|
4216
5059
|
function listSessions() {
|
|
4217
5060
|
try {
|
|
4218
|
-
const raw =
|
|
5061
|
+
const raw = readFileSync7(REGISTRY_PATH, "utf8");
|
|
4219
5062
|
return JSON.parse(raw);
|
|
4220
5063
|
} catch {
|
|
4221
5064
|
return [];
|
|
@@ -4225,7 +5068,7 @@ var REGISTRY_PATH;
|
|
|
4225
5068
|
var init_session_registry = __esm({
|
|
4226
5069
|
"src/lib/session-registry.ts"() {
|
|
4227
5070
|
"use strict";
|
|
4228
|
-
REGISTRY_PATH =
|
|
5071
|
+
REGISTRY_PATH = path12.join(os6.homedir(), ".exe-os", "session-registry.json");
|
|
4229
5072
|
}
|
|
4230
5073
|
});
|
|
4231
5074
|
|
|
@@ -4417,118 +5260,6 @@ var init_provider_table = __esm({
|
|
|
4417
5260
|
}
|
|
4418
5261
|
});
|
|
4419
5262
|
|
|
4420
|
-
// src/lib/runtime-table.ts
|
|
4421
|
-
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
4422
|
-
var init_runtime_table = __esm({
|
|
4423
|
-
"src/lib/runtime-table.ts"() {
|
|
4424
|
-
"use strict";
|
|
4425
|
-
RUNTIME_TABLE = {
|
|
4426
|
-
codex: {
|
|
4427
|
-
binary: "codex",
|
|
4428
|
-
launchMode: "interactive",
|
|
4429
|
-
autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
|
|
4430
|
-
inlineFlag: "--no-alt-screen",
|
|
4431
|
-
apiKeyEnv: "OPENAI_API_KEY",
|
|
4432
|
-
defaultModel: "gpt-5.4"
|
|
4433
|
-
},
|
|
4434
|
-
opencode: {
|
|
4435
|
-
binary: "opencode",
|
|
4436
|
-
launchMode: "exec",
|
|
4437
|
-
autoApproveFlag: "--dangerously-skip-permissions",
|
|
4438
|
-
inlineFlag: "",
|
|
4439
|
-
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
4440
|
-
defaultModel: "anthropic/claude-sonnet-4-6"
|
|
4441
|
-
}
|
|
4442
|
-
};
|
|
4443
|
-
DEFAULT_RUNTIME = "claude";
|
|
4444
|
-
}
|
|
4445
|
-
});
|
|
4446
|
-
|
|
4447
|
-
// src/lib/agent-config.ts
|
|
4448
|
-
var agent_config_exports = {};
|
|
4449
|
-
__export(agent_config_exports, {
|
|
4450
|
-
AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
|
|
4451
|
-
DEFAULT_MODELS: () => DEFAULT_MODELS,
|
|
4452
|
-
KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
|
|
4453
|
-
RUNTIME_LABELS: () => RUNTIME_LABELS,
|
|
4454
|
-
clearAgentRuntime: () => clearAgentRuntime,
|
|
4455
|
-
getAgentRuntime: () => getAgentRuntime,
|
|
4456
|
-
loadAgentConfig: () => loadAgentConfig,
|
|
4457
|
-
saveAgentConfig: () => saveAgentConfig,
|
|
4458
|
-
setAgentRuntime: () => setAgentRuntime
|
|
4459
|
-
});
|
|
4460
|
-
import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, existsSync as existsSync9, mkdirSync as mkdirSync4 } from "fs";
|
|
4461
|
-
import path11 from "path";
|
|
4462
|
-
function loadAgentConfig() {
|
|
4463
|
-
if (!existsSync9(AGENT_CONFIG_PATH)) return {};
|
|
4464
|
-
try {
|
|
4465
|
-
return JSON.parse(readFileSync7(AGENT_CONFIG_PATH, "utf-8"));
|
|
4466
|
-
} catch {
|
|
4467
|
-
return {};
|
|
4468
|
-
}
|
|
4469
|
-
}
|
|
4470
|
-
function saveAgentConfig(config) {
|
|
4471
|
-
const dir = path11.dirname(AGENT_CONFIG_PATH);
|
|
4472
|
-
if (!existsSync9(dir)) mkdirSync4(dir, { recursive: true });
|
|
4473
|
-
writeFileSync4(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
4474
|
-
}
|
|
4475
|
-
function getAgentRuntime(agentId) {
|
|
4476
|
-
const config = loadAgentConfig();
|
|
4477
|
-
const entry = config[agentId];
|
|
4478
|
-
if (entry) return entry;
|
|
4479
|
-
const orgDefault = config["default"];
|
|
4480
|
-
if (orgDefault) return orgDefault;
|
|
4481
|
-
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
4482
|
-
}
|
|
4483
|
-
function setAgentRuntime(agentId, runtime, model) {
|
|
4484
|
-
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
4485
|
-
if (!knownModels) {
|
|
4486
|
-
return {
|
|
4487
|
-
ok: false,
|
|
4488
|
-
error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
|
|
4489
|
-
};
|
|
4490
|
-
}
|
|
4491
|
-
if (!knownModels.includes(model)) {
|
|
4492
|
-
return {
|
|
4493
|
-
ok: false,
|
|
4494
|
-
error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
|
|
4495
|
-
};
|
|
4496
|
-
}
|
|
4497
|
-
const config = loadAgentConfig();
|
|
4498
|
-
config[agentId] = { runtime, model };
|
|
4499
|
-
saveAgentConfig(config);
|
|
4500
|
-
return { ok: true };
|
|
4501
|
-
}
|
|
4502
|
-
function clearAgentRuntime(agentId) {
|
|
4503
|
-
const config = loadAgentConfig();
|
|
4504
|
-
delete config[agentId];
|
|
4505
|
-
saveAgentConfig(config);
|
|
4506
|
-
}
|
|
4507
|
-
var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
|
|
4508
|
-
var init_agent_config = __esm({
|
|
4509
|
-
"src/lib/agent-config.ts"() {
|
|
4510
|
-
"use strict";
|
|
4511
|
-
init_config();
|
|
4512
|
-
init_runtime_table();
|
|
4513
|
-
AGENT_CONFIG_PATH = path11.join(EXE_AI_DIR, "agent-config.json");
|
|
4514
|
-
KNOWN_RUNTIMES = {
|
|
4515
|
-
claude: ["claude-opus-4", "claude-sonnet-4", "claude-haiku-4.5"],
|
|
4516
|
-
codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
|
|
4517
|
-
opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
|
|
4518
|
-
};
|
|
4519
|
-
RUNTIME_LABELS = {
|
|
4520
|
-
claude: "Claude Code (Anthropic)",
|
|
4521
|
-
codex: "Codex (OpenAI)",
|
|
4522
|
-
opencode: "OpenCode (open source)"
|
|
4523
|
-
};
|
|
4524
|
-
DEFAULT_MODELS = {
|
|
4525
|
-
claude: "claude-opus-4",
|
|
4526
|
-
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
4527
|
-
opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
|
|
4528
|
-
};
|
|
4529
|
-
}
|
|
4530
|
-
});
|
|
4531
|
-
|
|
4532
5263
|
// src/lib/intercom-queue.ts
|
|
4533
5264
|
var intercom_queue_exports = {};
|
|
4534
5265
|
__export(intercom_queue_exports, {
|
|
@@ -4539,10 +5270,10 @@ __export(intercom_queue_exports, {
|
|
|
4539
5270
|
readQueue: () => readQueue
|
|
4540
5271
|
});
|
|
4541
5272
|
import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, renameSync as renameSync3, existsSync as existsSync10, mkdirSync as mkdirSync5 } from "fs";
|
|
4542
|
-
import
|
|
4543
|
-
import
|
|
5273
|
+
import path13 from "path";
|
|
5274
|
+
import os7 from "os";
|
|
4544
5275
|
function ensureDir() {
|
|
4545
|
-
const dir =
|
|
5276
|
+
const dir = path13.dirname(QUEUE_PATH);
|
|
4546
5277
|
if (!existsSync10(dir)) mkdirSync5(dir, { recursive: true });
|
|
4547
5278
|
}
|
|
4548
5279
|
function readQueue() {
|
|
@@ -4648,26 +5379,26 @@ var QUEUE_PATH, MAX_RETRIES2, TTL_MS, INTERCOM_LOG;
|
|
|
4648
5379
|
var init_intercom_queue = __esm({
|
|
4649
5380
|
"src/lib/intercom-queue.ts"() {
|
|
4650
5381
|
"use strict";
|
|
4651
|
-
QUEUE_PATH =
|
|
5382
|
+
QUEUE_PATH = path13.join(os7.homedir(), ".exe-os", "intercom-queue.json");
|
|
4652
5383
|
MAX_RETRIES2 = 5;
|
|
4653
5384
|
TTL_MS = 60 * 60 * 1e3;
|
|
4654
|
-
INTERCOM_LOG =
|
|
5385
|
+
INTERCOM_LOG = path13.join(os7.homedir(), ".exe-os", "intercom.log");
|
|
4655
5386
|
}
|
|
4656
5387
|
});
|
|
4657
5388
|
|
|
4658
5389
|
// src/lib/license.ts
|
|
4659
5390
|
import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, existsSync as existsSync11, mkdirSync as mkdirSync6 } from "fs";
|
|
4660
5391
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
4661
|
-
import
|
|
5392
|
+
import path14 from "path";
|
|
4662
5393
|
import { jwtVerify, importSPKI } from "jose";
|
|
4663
5394
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
|
|
4664
5395
|
var init_license = __esm({
|
|
4665
5396
|
"src/lib/license.ts"() {
|
|
4666
5397
|
"use strict";
|
|
4667
5398
|
init_config();
|
|
4668
|
-
LICENSE_PATH =
|
|
4669
|
-
CACHE_PATH =
|
|
4670
|
-
DEVICE_ID_PATH =
|
|
5399
|
+
LICENSE_PATH = path14.join(EXE_AI_DIR, "license.key");
|
|
5400
|
+
CACHE_PATH = path14.join(EXE_AI_DIR, "license-cache.json");
|
|
5401
|
+
DEVICE_ID_PATH = path14.join(EXE_AI_DIR, "device-id");
|
|
4671
5402
|
PLAN_LIMITS = {
|
|
4672
5403
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
4673
5404
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -4680,7 +5411,7 @@ var init_license = __esm({
|
|
|
4680
5411
|
|
|
4681
5412
|
// src/lib/plan-limits.ts
|
|
4682
5413
|
import { readFileSync as readFileSync10, existsSync as existsSync12 } from "fs";
|
|
4683
|
-
import
|
|
5414
|
+
import path15 from "path";
|
|
4684
5415
|
function getLicenseSync() {
|
|
4685
5416
|
try {
|
|
4686
5417
|
if (!existsSync12(CACHE_PATH2)) return freeLicense();
|
|
@@ -4752,14 +5483,14 @@ var init_plan_limits = __esm({
|
|
|
4752
5483
|
this.name = "PlanLimitError";
|
|
4753
5484
|
}
|
|
4754
5485
|
};
|
|
4755
|
-
CACHE_PATH2 =
|
|
5486
|
+
CACHE_PATH2 = path15.join(EXE_AI_DIR, "license-cache.json");
|
|
4756
5487
|
}
|
|
4757
5488
|
});
|
|
4758
5489
|
|
|
4759
5490
|
// src/lib/notifications.ts
|
|
4760
5491
|
import crypto2 from "crypto";
|
|
4761
|
-
import
|
|
4762
|
-
import
|
|
5492
|
+
import path16 from "path";
|
|
5493
|
+
import os8 from "os";
|
|
4763
5494
|
import {
|
|
4764
5495
|
readFileSync as readFileSync11,
|
|
4765
5496
|
readdirSync as readdirSync4,
|
|
@@ -4868,8 +5599,8 @@ var init_task_scope = __esm({
|
|
|
4868
5599
|
|
|
4869
5600
|
// src/lib/tasks-crud.ts
|
|
4870
5601
|
import crypto4 from "crypto";
|
|
4871
|
-
import
|
|
4872
|
-
import
|
|
5602
|
+
import path17 from "path";
|
|
5603
|
+
import os9 from "os";
|
|
4873
5604
|
import { execSync as execSync7 } from "child_process";
|
|
4874
5605
|
import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
|
|
4875
5606
|
import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
|
|
@@ -5047,8 +5778,8 @@ ${laneWarning}` : laneWarning;
|
|
|
5047
5778
|
}
|
|
5048
5779
|
if (input2.baseDir) {
|
|
5049
5780
|
try {
|
|
5050
|
-
await mkdir4(
|
|
5051
|
-
await mkdir4(
|
|
5781
|
+
await mkdir4(path17.join(input2.baseDir, "exe", "output"), { recursive: true });
|
|
5782
|
+
await mkdir4(path17.join(input2.baseDir, "exe", "research"), { recursive: true });
|
|
5052
5783
|
await ensureArchitectureDoc(input2.baseDir, input2.projectName);
|
|
5053
5784
|
await ensureGitignoreExe(input2.baseDir);
|
|
5054
5785
|
} catch {
|
|
@@ -5084,9 +5815,9 @@ ${laneWarning}` : laneWarning;
|
|
|
5084
5815
|
});
|
|
5085
5816
|
if (input2.baseDir) {
|
|
5086
5817
|
try {
|
|
5087
|
-
const EXE_OS_DIR =
|
|
5088
|
-
const mdPath =
|
|
5089
|
-
const mdDir =
|
|
5818
|
+
const EXE_OS_DIR = path17.join(os9.homedir(), ".exe-os");
|
|
5819
|
+
const mdPath = path17.join(EXE_OS_DIR, taskFile);
|
|
5820
|
+
const mdDir = path17.dirname(mdPath);
|
|
5090
5821
|
if (!existsSync14(mdDir)) await mkdir4(mdDir, { recursive: true });
|
|
5091
5822
|
const reviewer = input2.reviewer ?? input2.assignedBy;
|
|
5092
5823
|
const mdContent = `# ${input2.title}
|
|
@@ -5387,7 +6118,7 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
5387
6118
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
5388
6119
|
}
|
|
5389
6120
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
5390
|
-
const archPath =
|
|
6121
|
+
const archPath = path17.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
5391
6122
|
try {
|
|
5392
6123
|
if (existsSync14(archPath)) return;
|
|
5393
6124
|
const template = [
|
|
@@ -5422,7 +6153,7 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
5422
6153
|
}
|
|
5423
6154
|
}
|
|
5424
6155
|
async function ensureGitignoreExe(baseDir) {
|
|
5425
|
-
const gitignorePath =
|
|
6156
|
+
const gitignorePath = path17.join(baseDir, ".gitignore");
|
|
5426
6157
|
try {
|
|
5427
6158
|
if (existsSync14(gitignorePath)) {
|
|
5428
6159
|
const content = readFileSync12(gitignorePath, "utf-8");
|
|
@@ -5468,7 +6199,7 @@ __export(tasks_review_exports, {
|
|
|
5468
6199
|
isStale: () => isStale,
|
|
5469
6200
|
listPendingReviews: () => listPendingReviews
|
|
5470
6201
|
});
|
|
5471
|
-
import
|
|
6202
|
+
import path18 from "path";
|
|
5472
6203
|
import { existsSync as existsSync15, readdirSync as readdirSync5, unlinkSync as unlinkSync5 } from "fs";
|
|
5473
6204
|
function formatAge(isoTimestamp) {
|
|
5474
6205
|
if (!isoTimestamp) return "";
|
|
@@ -5489,7 +6220,7 @@ async function countPendingReviews(sessionScope) {
|
|
|
5489
6220
|
const client = getClient();
|
|
5490
6221
|
if (sessionScope) {
|
|
5491
6222
|
const result2 = await client.execute({
|
|
5492
|
-
sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review' AND
|
|
6223
|
+
sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review' AND session_scope = ?",
|
|
5493
6224
|
args: [sessionScope]
|
|
5494
6225
|
});
|
|
5495
6226
|
return Number(result2.rows[0]?.cnt) || 0;
|
|
@@ -5754,11 +6485,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
5754
6485
|
);
|
|
5755
6486
|
}
|
|
5756
6487
|
try {
|
|
5757
|
-
const cacheDir =
|
|
6488
|
+
const cacheDir = path18.join(EXE_AI_DIR, "session-cache");
|
|
5758
6489
|
if (existsSync15(cacheDir)) {
|
|
5759
6490
|
for (const f of readdirSync5(cacheDir)) {
|
|
5760
6491
|
if (f.startsWith("review-notified-")) {
|
|
5761
|
-
unlinkSync5(
|
|
6492
|
+
unlinkSync5(path18.join(cacheDir, f));
|
|
5762
6493
|
}
|
|
5763
6494
|
}
|
|
5764
6495
|
}
|
|
@@ -5779,7 +6510,7 @@ var init_tasks_review = __esm({
|
|
|
5779
6510
|
});
|
|
5780
6511
|
|
|
5781
6512
|
// src/lib/tasks-chain.ts
|
|
5782
|
-
import
|
|
6513
|
+
import path19 from "path";
|
|
5783
6514
|
import { readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
|
|
5784
6515
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
5785
6516
|
const client = getClient();
|
|
@@ -5796,7 +6527,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
5796
6527
|
});
|
|
5797
6528
|
for (const ur of unblockedRows.rows) {
|
|
5798
6529
|
try {
|
|
5799
|
-
const ubFile =
|
|
6530
|
+
const ubFile = path19.join(baseDir, String(ur.task_file));
|
|
5800
6531
|
let ubContent = await readFile4(ubFile, "utf-8");
|
|
5801
6532
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
5802
6533
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -6322,7 +7053,7 @@ __export(tasks_exports, {
|
|
|
6322
7053
|
updateTaskStatus: () => updateTaskStatus,
|
|
6323
7054
|
writeCheckpoint: () => writeCheckpoint
|
|
6324
7055
|
});
|
|
6325
|
-
import
|
|
7056
|
+
import path20 from "path";
|
|
6326
7057
|
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync7, unlinkSync as unlinkSync6 } from "fs";
|
|
6327
7058
|
async function createTask(input2) {
|
|
6328
7059
|
const result = await createTaskCore(input2);
|
|
@@ -6342,8 +7073,8 @@ async function updateTask(input2) {
|
|
|
6342
7073
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input2);
|
|
6343
7074
|
try {
|
|
6344
7075
|
const agent = String(row.assigned_to);
|
|
6345
|
-
const cacheDir =
|
|
6346
|
-
const cachePath =
|
|
7076
|
+
const cacheDir = path20.join(EXE_AI_DIR, "session-cache");
|
|
7077
|
+
const cachePath = path20.join(cacheDir, `current-task-${agent}.json`);
|
|
6347
7078
|
if (input2.status === "in_progress") {
|
|
6348
7079
|
mkdirSync7(cacheDir, { recursive: true });
|
|
6349
7080
|
writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
@@ -6814,12 +7545,12 @@ __export(tmux_routing_exports, {
|
|
|
6814
7545
|
});
|
|
6815
7546
|
import { execFileSync as execFileSync2, execSync as execSync8 } from "child_process";
|
|
6816
7547
|
import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, mkdirSync as mkdirSync8, existsSync as existsSync16, appendFileSync, readdirSync as readdirSync6 } from "fs";
|
|
6817
|
-
import
|
|
6818
|
-
import
|
|
7548
|
+
import path21 from "path";
|
|
7549
|
+
import os10 from "os";
|
|
6819
7550
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6820
7551
|
import { unlinkSync as unlinkSync7 } from "fs";
|
|
6821
7552
|
function spawnLockPath(sessionName) {
|
|
6822
|
-
return
|
|
7553
|
+
return path21.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
6823
7554
|
}
|
|
6824
7555
|
function isProcessAlive(pid) {
|
|
6825
7556
|
try {
|
|
@@ -6856,8 +7587,8 @@ function releaseSpawnLock2(sessionName) {
|
|
|
6856
7587
|
function resolveBehaviorsExporterScript() {
|
|
6857
7588
|
try {
|
|
6858
7589
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
6859
|
-
const scriptPath =
|
|
6860
|
-
|
|
7590
|
+
const scriptPath = path21.join(
|
|
7591
|
+
path21.dirname(thisFile),
|
|
6861
7592
|
"..",
|
|
6862
7593
|
"bin",
|
|
6863
7594
|
"exe-export-behaviors.js"
|
|
@@ -6932,7 +7663,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
6932
7663
|
mkdirSync8(SESSION_CACHE, { recursive: true });
|
|
6933
7664
|
}
|
|
6934
7665
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
6935
|
-
const filePath =
|
|
7666
|
+
const filePath = path21.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
6936
7667
|
writeFileSync8(filePath, JSON.stringify({
|
|
6937
7668
|
parentExe: rootExe,
|
|
6938
7669
|
dispatchedBy: dispatchedBy || rootExe,
|
|
@@ -6941,7 +7672,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
6941
7672
|
}
|
|
6942
7673
|
function getParentExe(sessionKey) {
|
|
6943
7674
|
try {
|
|
6944
|
-
const data = JSON.parse(readFileSync13(
|
|
7675
|
+
const data = JSON.parse(readFileSync13(path21.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
6945
7676
|
return data.parentExe || null;
|
|
6946
7677
|
} catch {
|
|
6947
7678
|
return null;
|
|
@@ -6950,7 +7681,7 @@ function getParentExe(sessionKey) {
|
|
|
6950
7681
|
function getDispatchedBy(sessionKey) {
|
|
6951
7682
|
try {
|
|
6952
7683
|
const data = JSON.parse(readFileSync13(
|
|
6953
|
-
|
|
7684
|
+
path21.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
6954
7685
|
"utf8"
|
|
6955
7686
|
));
|
|
6956
7687
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -7136,7 +7867,7 @@ function sendIntercom(targetSession) {
|
|
|
7136
7867
|
try {
|
|
7137
7868
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
7138
7869
|
const agent = baseAgentName(rawAgent);
|
|
7139
|
-
const markerPath =
|
|
7870
|
+
const markerPath = path21.join(SESSION_CACHE, `current-task-${agent}.json`);
|
|
7140
7871
|
if (existsSync16(markerPath)) {
|
|
7141
7872
|
logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
|
|
7142
7873
|
return "debounced";
|
|
@@ -7146,7 +7877,7 @@ function sendIntercom(targetSession) {
|
|
|
7146
7877
|
try {
|
|
7147
7878
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
7148
7879
|
const agent = baseAgentName(rawAgent);
|
|
7149
|
-
const taskDir =
|
|
7880
|
+
const taskDir = path21.join(process.cwd(), "exe", agent);
|
|
7150
7881
|
if (existsSync16(taskDir)) {
|
|
7151
7882
|
const files = readdirSync6(taskDir).filter(
|
|
7152
7883
|
(f) => f.endsWith(".md") && f !== "DONE.txt"
|
|
@@ -7280,8 +8011,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
7280
8011
|
const transport = getTransport();
|
|
7281
8012
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
7282
8013
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
7283
|
-
const logDir =
|
|
7284
|
-
const logFile =
|
|
8014
|
+
const logDir = path21.join(os10.homedir(), ".exe-os", "session-logs");
|
|
8015
|
+
const logFile = path21.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
7285
8016
|
if (!existsSync16(logDir)) {
|
|
7286
8017
|
mkdirSync8(logDir, { recursive: true });
|
|
7287
8018
|
}
|
|
@@ -7289,14 +8020,14 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
7289
8020
|
let cleanupSuffix = "";
|
|
7290
8021
|
try {
|
|
7291
8022
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
7292
|
-
const cleanupScript =
|
|
8023
|
+
const cleanupScript = path21.join(path21.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
7293
8024
|
if (existsSync16(cleanupScript)) {
|
|
7294
8025
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
7295
8026
|
}
|
|
7296
8027
|
} catch {
|
|
7297
8028
|
}
|
|
7298
8029
|
try {
|
|
7299
|
-
const claudeJsonPath =
|
|
8030
|
+
const claudeJsonPath = path21.join(os10.homedir(), ".claude.json");
|
|
7300
8031
|
let claudeJson = {};
|
|
7301
8032
|
try {
|
|
7302
8033
|
claudeJson = JSON.parse(readFileSync13(claudeJsonPath, "utf8"));
|
|
@@ -7311,10 +8042,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
7311
8042
|
} catch {
|
|
7312
8043
|
}
|
|
7313
8044
|
try {
|
|
7314
|
-
const settingsDir =
|
|
8045
|
+
const settingsDir = path21.join(os10.homedir(), ".claude", "projects");
|
|
7315
8046
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
7316
|
-
const projSettingsDir =
|
|
7317
|
-
const settingsPath =
|
|
8047
|
+
const projSettingsDir = path21.join(settingsDir, normalizedKey);
|
|
8048
|
+
const settingsPath = path21.join(projSettingsDir, "settings.json");
|
|
7318
8049
|
let settings = {};
|
|
7319
8050
|
try {
|
|
7320
8051
|
settings = JSON.parse(readFileSync13(settingsPath, "utf8"));
|
|
@@ -7361,8 +8092,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
7361
8092
|
let behaviorsFlag = "";
|
|
7362
8093
|
let legacyFallbackWarned = false;
|
|
7363
8094
|
if (!useExeAgent && !useBinSymlink) {
|
|
7364
|
-
const identityPath =
|
|
7365
|
-
|
|
8095
|
+
const identityPath = path21.join(
|
|
8096
|
+
os10.homedir(),
|
|
7366
8097
|
".exe-os",
|
|
7367
8098
|
"identity",
|
|
7368
8099
|
`${employeeName}.md`
|
|
@@ -7377,7 +8108,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
7377
8108
|
}
|
|
7378
8109
|
const behaviorsFile = exportBehaviorsSync(
|
|
7379
8110
|
employeeName,
|
|
7380
|
-
|
|
8111
|
+
path21.basename(spawnCwd),
|
|
7381
8112
|
sessionName
|
|
7382
8113
|
);
|
|
7383
8114
|
if (behaviorsFile) {
|
|
@@ -7392,9 +8123,9 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
7392
8123
|
}
|
|
7393
8124
|
let sessionContextFlag = "";
|
|
7394
8125
|
try {
|
|
7395
|
-
const ctxDir =
|
|
8126
|
+
const ctxDir = path21.join(os10.homedir(), ".exe-os", "session-cache");
|
|
7396
8127
|
mkdirSync8(ctxDir, { recursive: true });
|
|
7397
|
-
const ctxFile =
|
|
8128
|
+
const ctxFile = path21.join(ctxDir, `session-context-${sessionName}.md`);
|
|
7398
8129
|
const ctxContent = [
|
|
7399
8130
|
`## Session Context`,
|
|
7400
8131
|
`You are running in tmux session: ${sessionName}.`,
|
|
@@ -7478,7 +8209,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
7478
8209
|
transport.pipeLog(sessionName, logFile);
|
|
7479
8210
|
try {
|
|
7480
8211
|
const mySession = getMySession();
|
|
7481
|
-
const dispatchInfo =
|
|
8212
|
+
const dispatchInfo = path21.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
7482
8213
|
writeFileSync8(dispatchInfo, JSON.stringify({
|
|
7483
8214
|
dispatchedBy: mySession,
|
|
7484
8215
|
rootExe: exeSession,
|
|
@@ -7553,15 +8284,15 @@ var init_tmux_routing = __esm({
|
|
|
7553
8284
|
init_intercom_queue();
|
|
7554
8285
|
init_plan_limits();
|
|
7555
8286
|
init_employees();
|
|
7556
|
-
SPAWN_LOCK_DIR =
|
|
7557
|
-
SESSION_CACHE =
|
|
8287
|
+
SPAWN_LOCK_DIR = path21.join(os10.homedir(), ".exe-os", "spawn-locks");
|
|
8288
|
+
SESSION_CACHE = path21.join(os10.homedir(), ".exe-os", "session-cache");
|
|
7558
8289
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
7559
8290
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
7560
8291
|
VERIFY_PANE_LINES = 200;
|
|
7561
8292
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
7562
8293
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
7563
|
-
INTERCOM_LOG2 =
|
|
7564
|
-
DEBOUNCE_FILE =
|
|
8294
|
+
INTERCOM_LOG2 = path21.join(os10.homedir(), ".exe-os", "intercom.log");
|
|
8295
|
+
DEBOUNCE_FILE = path21.join(SESSION_CACHE, "intercom-debounce.json");
|
|
7565
8296
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
7566
8297
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
|
|
7567
8298
|
}
|
|
@@ -7824,7 +8555,7 @@ init_config();
|
|
|
7824
8555
|
init_store();
|
|
7825
8556
|
import { spawn as spawn2 } from "child_process";
|
|
7826
8557
|
import { readFileSync as readFileSync14, writeFileSync as writeFileSync9, mkdirSync as mkdirSync9, existsSync as existsSync17, openSync as openSync2, closeSync as closeSync2 } from "fs";
|
|
7827
|
-
import
|
|
8558
|
+
import path22 from "path";
|
|
7828
8559
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
7829
8560
|
|
|
7830
8561
|
// src/lib/hybrid-search.ts
|
|
@@ -8364,10 +9095,10 @@ async function trajectoryBypass(queryText, agentId, options, limit) {
|
|
|
8364
9095
|
init_config();
|
|
8365
9096
|
init_session_key();
|
|
8366
9097
|
init_employees();
|
|
8367
|
-
import { readFileSync as
|
|
9098
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, unlinkSync as unlinkSync3, readdirSync as readdirSync3 } from "fs";
|
|
8368
9099
|
import { execSync as execSync5 } from "child_process";
|
|
8369
|
-
import
|
|
8370
|
-
var CACHE_DIR =
|
|
9100
|
+
import path11 from "path";
|
|
9101
|
+
var CACHE_DIR = path11.join(EXE_AI_DIR, "session-cache");
|
|
8371
9102
|
var STALE_MS = 24 * 60 * 60 * 1e3;
|
|
8372
9103
|
function isNameWithOptionalInstance(candidate, baseName) {
|
|
8373
9104
|
if (candidate === baseName) return true;
|
|
@@ -8412,12 +9143,12 @@ function resolveActiveAgentFromTmuxSession(sessionName) {
|
|
|
8412
9143
|
return null;
|
|
8413
9144
|
}
|
|
8414
9145
|
function getMarkerPath() {
|
|
8415
|
-
return
|
|
9146
|
+
return path11.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
|
|
8416
9147
|
}
|
|
8417
9148
|
function getActiveAgent() {
|
|
8418
9149
|
try {
|
|
8419
9150
|
const markerPath = getMarkerPath();
|
|
8420
|
-
const raw =
|
|
9151
|
+
const raw = readFileSync6(markerPath, "utf8");
|
|
8421
9152
|
const data = JSON.parse(raw);
|
|
8422
9153
|
if (data.agentId) {
|
|
8423
9154
|
if (data.startedAt) {
|
|
@@ -8470,7 +9201,7 @@ if (!process.env.AGENT_ID) {
|
|
|
8470
9201
|
if (!loadConfigSync().autoRetrieval) {
|
|
8471
9202
|
process.exit(0);
|
|
8472
9203
|
}
|
|
8473
|
-
var WORKER_LOG_PATH =
|
|
9204
|
+
var WORKER_LOG_PATH = path22.join(EXE_AI_DIR, "workers.log");
|
|
8474
9205
|
function openWorkerLog() {
|
|
8475
9206
|
try {
|
|
8476
9207
|
return openSync2(WORKER_LOG_PATH, "a");
|
|
@@ -8478,10 +9209,10 @@ function openWorkerLog() {
|
|
|
8478
9209
|
return "ignore";
|
|
8479
9210
|
}
|
|
8480
9211
|
}
|
|
8481
|
-
var CACHE_DIR2 =
|
|
9212
|
+
var CACHE_DIR2 = path22.join(EXE_AI_DIR, "session-cache");
|
|
8482
9213
|
function loadInjectedIds(sessionId) {
|
|
8483
9214
|
try {
|
|
8484
|
-
const raw = readFileSync14(
|
|
9215
|
+
const raw = readFileSync14(path22.join(CACHE_DIR2, `${sessionId}.json`), "utf8");
|
|
8485
9216
|
return new Set(JSON.parse(raw));
|
|
8486
9217
|
} catch {
|
|
8487
9218
|
return /* @__PURE__ */ new Set();
|
|
@@ -8491,7 +9222,7 @@ function saveInjectedIds(sessionId, ids) {
|
|
|
8491
9222
|
try {
|
|
8492
9223
|
mkdirSync9(CACHE_DIR2, { recursive: true });
|
|
8493
9224
|
writeFileSync9(
|
|
8494
|
-
|
|
9225
|
+
path22.join(CACHE_DIR2, `${sessionId}.json`),
|
|
8495
9226
|
JSON.stringify([...ids])
|
|
8496
9227
|
);
|
|
8497
9228
|
} catch {
|
|
@@ -8541,13 +9272,23 @@ process.stdin.on("end", async () => {
|
|
|
8541
9272
|
const { getAgentRuntime: getAgentRuntime2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
|
|
8542
9273
|
const { baseAgentName: baseAgentName2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
8543
9274
|
const { isSessionBusy: isSessionBusy2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
9275
|
+
const MARKER_STALE_MS = 4 * 60 * 60 * 1e3;
|
|
8544
9276
|
const hasInProgressTask = (agentName) => {
|
|
8545
9277
|
try {
|
|
8546
|
-
const
|
|
8547
|
-
const
|
|
8548
|
-
const
|
|
8549
|
-
const markerPath =
|
|
8550
|
-
|
|
9278
|
+
const p = __require("path");
|
|
9279
|
+
const fs = __require("fs");
|
|
9280
|
+
const o = __require("os");
|
|
9281
|
+
const markerPath = p.join(o.homedir(), ".exe-os", "session-cache", `current-task-${agentName}.json`);
|
|
9282
|
+
if (!fs.existsSync(markerPath)) return false;
|
|
9283
|
+
const stat = fs.statSync(markerPath);
|
|
9284
|
+
if (Date.now() - stat.mtimeMs > MARKER_STALE_MS) {
|
|
9285
|
+
try {
|
|
9286
|
+
fs.unlinkSync(markerPath);
|
|
9287
|
+
} catch {
|
|
9288
|
+
}
|
|
9289
|
+
return false;
|
|
9290
|
+
}
|
|
9291
|
+
return true;
|
|
8551
9292
|
} catch {
|
|
8552
9293
|
return false;
|
|
8553
9294
|
}
|
|
@@ -8599,7 +9340,7 @@ ${fresh.map(
|
|
|
8599
9340
|
try {
|
|
8600
9341
|
const { countPendingReviews: countPendingReviews2, countNewPendingReviewsSince: countNewPendingReviewsSince2 } = await Promise.resolve().then(() => (init_tasks_review(), tasks_review_exports));
|
|
8601
9342
|
const sessionKey = getSessionKey();
|
|
8602
|
-
const lastCheckPath =
|
|
9343
|
+
const lastCheckPath = path22.join(CACHE_DIR2, `review-lastcheck-${sessionKey}.json`);
|
|
8603
9344
|
let sessionScope;
|
|
8604
9345
|
try {
|
|
8605
9346
|
const { execSync: execSync9 } = await import("child_process");
|
|
@@ -8667,8 +9408,8 @@ IMPORTANT: After completing your current task, you MUST address the pending revi
|
|
|
8667
9408
|
function spawnPromptWorker(prompt, sessionId, agent) {
|
|
8668
9409
|
if (!loadConfigSync().autoIngestion) return;
|
|
8669
9410
|
try {
|
|
8670
|
-
const workerPath =
|
|
8671
|
-
|
|
9411
|
+
const workerPath = path22.resolve(
|
|
9412
|
+
path22.dirname(fileURLToPath3(import.meta.url)),
|
|
8672
9413
|
"prompt-ingest-worker.js"
|
|
8673
9414
|
);
|
|
8674
9415
|
if (!existsSync17(workerPath)) {
|