@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
|
@@ -253,6 +253,118 @@ var init_config = __esm({
|
|
|
253
253
|
}
|
|
254
254
|
});
|
|
255
255
|
|
|
256
|
+
// src/lib/runtime-table.ts
|
|
257
|
+
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
258
|
+
var init_runtime_table = __esm({
|
|
259
|
+
"src/lib/runtime-table.ts"() {
|
|
260
|
+
"use strict";
|
|
261
|
+
RUNTIME_TABLE = {
|
|
262
|
+
codex: {
|
|
263
|
+
binary: "codex",
|
|
264
|
+
launchMode: "interactive",
|
|
265
|
+
autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
|
|
266
|
+
inlineFlag: "--no-alt-screen",
|
|
267
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
268
|
+
defaultModel: "gpt-5.4"
|
|
269
|
+
},
|
|
270
|
+
opencode: {
|
|
271
|
+
binary: "opencode",
|
|
272
|
+
launchMode: "exec",
|
|
273
|
+
autoApproveFlag: "--dangerously-skip-permissions",
|
|
274
|
+
inlineFlag: "",
|
|
275
|
+
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
276
|
+
defaultModel: "anthropic/claude-sonnet-4-6"
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
DEFAULT_RUNTIME = "claude";
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
// src/lib/agent-config.ts
|
|
284
|
+
var agent_config_exports = {};
|
|
285
|
+
__export(agent_config_exports, {
|
|
286
|
+
AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
|
|
287
|
+
DEFAULT_MODELS: () => DEFAULT_MODELS,
|
|
288
|
+
KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
|
|
289
|
+
RUNTIME_LABELS: () => RUNTIME_LABELS,
|
|
290
|
+
clearAgentRuntime: () => clearAgentRuntime,
|
|
291
|
+
getAgentRuntime: () => getAgentRuntime,
|
|
292
|
+
loadAgentConfig: () => loadAgentConfig,
|
|
293
|
+
saveAgentConfig: () => saveAgentConfig,
|
|
294
|
+
setAgentRuntime: () => setAgentRuntime
|
|
295
|
+
});
|
|
296
|
+
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync2, mkdirSync } from "fs";
|
|
297
|
+
import path2 from "path";
|
|
298
|
+
function loadAgentConfig() {
|
|
299
|
+
if (!existsSync2(AGENT_CONFIG_PATH)) return {};
|
|
300
|
+
try {
|
|
301
|
+
return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
|
|
302
|
+
} catch {
|
|
303
|
+
return {};
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
function saveAgentConfig(config) {
|
|
307
|
+
const dir = path2.dirname(AGENT_CONFIG_PATH);
|
|
308
|
+
if (!existsSync2(dir)) mkdirSync(dir, { recursive: true });
|
|
309
|
+
writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
310
|
+
}
|
|
311
|
+
function getAgentRuntime(agentId) {
|
|
312
|
+
const config = loadAgentConfig();
|
|
313
|
+
const entry = config[agentId];
|
|
314
|
+
if (entry) return entry;
|
|
315
|
+
const orgDefault = config["default"];
|
|
316
|
+
if (orgDefault) return orgDefault;
|
|
317
|
+
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
318
|
+
}
|
|
319
|
+
function setAgentRuntime(agentId, runtime, model) {
|
|
320
|
+
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
321
|
+
if (!knownModels) {
|
|
322
|
+
return {
|
|
323
|
+
ok: false,
|
|
324
|
+
error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
if (!knownModels.includes(model)) {
|
|
328
|
+
return {
|
|
329
|
+
ok: false,
|
|
330
|
+
error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
const config = loadAgentConfig();
|
|
334
|
+
config[agentId] = { runtime, model };
|
|
335
|
+
saveAgentConfig(config);
|
|
336
|
+
return { ok: true };
|
|
337
|
+
}
|
|
338
|
+
function clearAgentRuntime(agentId) {
|
|
339
|
+
const config = loadAgentConfig();
|
|
340
|
+
delete config[agentId];
|
|
341
|
+
saveAgentConfig(config);
|
|
342
|
+
}
|
|
343
|
+
var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
|
|
344
|
+
var init_agent_config = __esm({
|
|
345
|
+
"src/lib/agent-config.ts"() {
|
|
346
|
+
"use strict";
|
|
347
|
+
init_config();
|
|
348
|
+
init_runtime_table();
|
|
349
|
+
AGENT_CONFIG_PATH = path2.join(EXE_AI_DIR, "agent-config.json");
|
|
350
|
+
KNOWN_RUNTIMES = {
|
|
351
|
+
claude: ["claude-opus-4", "claude-sonnet-4", "claude-haiku-4.5"],
|
|
352
|
+
codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
|
|
353
|
+
opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
|
|
354
|
+
};
|
|
355
|
+
RUNTIME_LABELS = {
|
|
356
|
+
claude: "Claude Code (Anthropic)",
|
|
357
|
+
codex: "Codex (OpenAI)",
|
|
358
|
+
opencode: "OpenCode (open source)"
|
|
359
|
+
};
|
|
360
|
+
DEFAULT_MODELS = {
|
|
361
|
+
claude: "claude-opus-4",
|
|
362
|
+
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
363
|
+
opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
|
|
256
368
|
// src/lib/employees.ts
|
|
257
369
|
var employees_exports = {};
|
|
258
370
|
__export(employees_exports, {
|
|
@@ -268,6 +380,7 @@ __export(employees_exports, {
|
|
|
268
380
|
getEmployeeByRole: () => getEmployeeByRole,
|
|
269
381
|
getEmployeeNamesByRole: () => getEmployeeNamesByRole,
|
|
270
382
|
hasRole: () => hasRole,
|
|
383
|
+
hireEmployee: () => hireEmployee,
|
|
271
384
|
isCoordinatorName: () => isCoordinatorName,
|
|
272
385
|
isCoordinatorRole: () => isCoordinatorRole,
|
|
273
386
|
isMultiInstance: () => isMultiInstance,
|
|
@@ -280,9 +393,9 @@ __export(employees_exports, {
|
|
|
280
393
|
validateEmployeeName: () => validateEmployeeName
|
|
281
394
|
});
|
|
282
395
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
283
|
-
import { existsSync as
|
|
396
|
+
import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
284
397
|
import { execSync } from "child_process";
|
|
285
|
-
import
|
|
398
|
+
import path3 from "path";
|
|
286
399
|
import os2 from "os";
|
|
287
400
|
function normalizeRole(role) {
|
|
288
401
|
return (role ?? "").trim().toLowerCase();
|
|
@@ -319,7 +432,7 @@ function validateEmployeeName(name) {
|
|
|
319
432
|
return { valid: true };
|
|
320
433
|
}
|
|
321
434
|
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
322
|
-
if (!
|
|
435
|
+
if (!existsSync3(employeesPath)) {
|
|
323
436
|
return [];
|
|
324
437
|
}
|
|
325
438
|
const raw = await readFile2(employeesPath, "utf-8");
|
|
@@ -330,13 +443,13 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
|
330
443
|
}
|
|
331
444
|
}
|
|
332
445
|
async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
333
|
-
await mkdir2(
|
|
446
|
+
await mkdir2(path3.dirname(employeesPath), { recursive: true });
|
|
334
447
|
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
335
448
|
}
|
|
336
449
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
337
|
-
if (!
|
|
450
|
+
if (!existsSync3(employeesPath)) return [];
|
|
338
451
|
try {
|
|
339
|
-
return JSON.parse(
|
|
452
|
+
return JSON.parse(readFileSync3(employeesPath, "utf-8"));
|
|
340
453
|
} catch {
|
|
341
454
|
return [];
|
|
342
455
|
}
|
|
@@ -378,6 +491,52 @@ function addEmployee(employees, employee) {
|
|
|
378
491
|
}
|
|
379
492
|
return [...employees, normalized];
|
|
380
493
|
}
|
|
494
|
+
function appendToCoordinatorTeam(employee) {
|
|
495
|
+
const coordinator = getCoordinatorEmployee(loadEmployeesSync());
|
|
496
|
+
if (!coordinator) return;
|
|
497
|
+
const idPath = path3.join(IDENTITY_DIR, `${coordinator.name}.md`);
|
|
498
|
+
if (!existsSync3(idPath)) return;
|
|
499
|
+
const content = readFileSync3(idPath, "utf-8");
|
|
500
|
+
if (content.includes(`**${capitalize(employee.name)}`)) return;
|
|
501
|
+
const teamMatch = content.match(TEAM_SECTION_RE);
|
|
502
|
+
if (!teamMatch || teamMatch.index === void 0) return;
|
|
503
|
+
const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
|
|
504
|
+
const nextHeading = afterTeam.match(/\n## /);
|
|
505
|
+
const entry = `
|
|
506
|
+
**${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
|
|
507
|
+
`;
|
|
508
|
+
let updated;
|
|
509
|
+
if (nextHeading && nextHeading.index !== void 0) {
|
|
510
|
+
const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
|
|
511
|
+
updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
|
|
512
|
+
} else {
|
|
513
|
+
updated = content.trimEnd() + "\n" + entry;
|
|
514
|
+
}
|
|
515
|
+
writeFileSync2(idPath, updated, "utf-8");
|
|
516
|
+
}
|
|
517
|
+
function capitalize(s) {
|
|
518
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
519
|
+
}
|
|
520
|
+
async function hireEmployee(employee) {
|
|
521
|
+
const employees = await loadEmployees();
|
|
522
|
+
const updated = addEmployee(employees, employee);
|
|
523
|
+
await saveEmployees(updated);
|
|
524
|
+
try {
|
|
525
|
+
appendToCoordinatorTeam(employee);
|
|
526
|
+
} catch {
|
|
527
|
+
}
|
|
528
|
+
try {
|
|
529
|
+
const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
|
|
530
|
+
const config = loadAgentConfig2();
|
|
531
|
+
const name = employee.name.toLowerCase();
|
|
532
|
+
if (!config[name] && config["default"]) {
|
|
533
|
+
config[name] = { ...config["default"] };
|
|
534
|
+
saveAgentConfig2(config);
|
|
535
|
+
}
|
|
536
|
+
} catch {
|
|
537
|
+
}
|
|
538
|
+
return updated;
|
|
539
|
+
}
|
|
381
540
|
async function normalizeRosterCase(rosterPath) {
|
|
382
541
|
const employees = await loadEmployees(rosterPath);
|
|
383
542
|
let changed = false;
|
|
@@ -387,14 +546,14 @@ async function normalizeRosterCase(rosterPath) {
|
|
|
387
546
|
emp.name = emp.name.toLowerCase();
|
|
388
547
|
changed = true;
|
|
389
548
|
try {
|
|
390
|
-
const identityDir =
|
|
391
|
-
const oldPath =
|
|
392
|
-
const newPath =
|
|
393
|
-
if (
|
|
549
|
+
const identityDir = path3.join(os2.homedir(), ".exe-os", "identity");
|
|
550
|
+
const oldPath = path3.join(identityDir, `${oldName}.md`);
|
|
551
|
+
const newPath = path3.join(identityDir, `${emp.name}.md`);
|
|
552
|
+
if (existsSync3(oldPath) && !existsSync3(newPath)) {
|
|
394
553
|
renameSync2(oldPath, newPath);
|
|
395
|
-
} else if (
|
|
396
|
-
const content =
|
|
397
|
-
|
|
554
|
+
} else if (existsSync3(oldPath) && oldPath !== newPath) {
|
|
555
|
+
const content = readFileSync3(oldPath, "utf-8");
|
|
556
|
+
writeFileSync2(newPath, content, "utf-8");
|
|
398
557
|
if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
|
|
399
558
|
unlinkSync(oldPath);
|
|
400
559
|
}
|
|
@@ -424,7 +583,7 @@ function registerBinSymlinks(name) {
|
|
|
424
583
|
errors.push("Could not find 'exe-os' in PATH");
|
|
425
584
|
return { created, skipped, errors };
|
|
426
585
|
}
|
|
427
|
-
const binDir =
|
|
586
|
+
const binDir = path3.dirname(exeBinPath);
|
|
428
587
|
let target;
|
|
429
588
|
try {
|
|
430
589
|
target = readlinkSync(exeBinPath);
|
|
@@ -434,8 +593,8 @@ function registerBinSymlinks(name) {
|
|
|
434
593
|
}
|
|
435
594
|
for (const suffix of ["", "-opencode"]) {
|
|
436
595
|
const linkName = `${name}${suffix}`;
|
|
437
|
-
const linkPath =
|
|
438
|
-
if (
|
|
596
|
+
const linkPath = path3.join(binDir, linkName);
|
|
597
|
+
if (existsSync3(linkPath)) {
|
|
439
598
|
skipped.push(linkName);
|
|
440
599
|
continue;
|
|
441
600
|
}
|
|
@@ -448,21 +607,619 @@ function registerBinSymlinks(name) {
|
|
|
448
607
|
}
|
|
449
608
|
return { created, skipped, errors };
|
|
450
609
|
}
|
|
451
|
-
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
|
|
610
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
|
|
452
611
|
var init_employees = __esm({
|
|
453
612
|
"src/lib/employees.ts"() {
|
|
454
613
|
"use strict";
|
|
455
614
|
init_config();
|
|
456
|
-
EMPLOYEES_PATH =
|
|
615
|
+
EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
|
|
457
616
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
458
617
|
COORDINATOR_ROLE = "COO";
|
|
459
618
|
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
619
|
+
IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
|
|
620
|
+
TEAM_SECTION_RE = /^## Team\b.*$/m;
|
|
621
|
+
}
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
// src/lib/database-adapter.ts
|
|
625
|
+
import os3 from "os";
|
|
626
|
+
import path4 from "path";
|
|
627
|
+
import { createRequire } from "module";
|
|
628
|
+
import { pathToFileURL } from "url";
|
|
629
|
+
function quotedIdentifier(identifier) {
|
|
630
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
631
|
+
}
|
|
632
|
+
function unqualifiedTableName(name) {
|
|
633
|
+
const raw = name.trim().replace(/^"|"$/g, "");
|
|
634
|
+
const parts = raw.split(".");
|
|
635
|
+
return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
|
|
636
|
+
}
|
|
637
|
+
function stripTrailingSemicolon(sql) {
|
|
638
|
+
return sql.trim().replace(/;+\s*$/u, "");
|
|
639
|
+
}
|
|
640
|
+
function appendClause(sql, clause) {
|
|
641
|
+
const trimmed = stripTrailingSemicolon(sql);
|
|
642
|
+
const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
|
|
643
|
+
if (!returningMatch) {
|
|
644
|
+
return `${trimmed}${clause}`;
|
|
645
|
+
}
|
|
646
|
+
const idx = returningMatch.index;
|
|
647
|
+
return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
|
|
648
|
+
}
|
|
649
|
+
function normalizeStatement(stmt) {
|
|
650
|
+
if (typeof stmt === "string") {
|
|
651
|
+
return { kind: "positional", sql: stmt, args: [] };
|
|
652
|
+
}
|
|
653
|
+
const sql = stmt.sql;
|
|
654
|
+
if (Array.isArray(stmt.args) || stmt.args === void 0) {
|
|
655
|
+
return { kind: "positional", sql, args: stmt.args ?? [] };
|
|
656
|
+
}
|
|
657
|
+
return { kind: "named", sql, args: stmt.args };
|
|
658
|
+
}
|
|
659
|
+
function rewriteBooleanLiterals(sql) {
|
|
660
|
+
let out = sql;
|
|
661
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
662
|
+
const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
|
|
663
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
|
|
664
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
|
|
665
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
|
|
666
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
|
|
667
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
|
|
668
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
|
|
669
|
+
}
|
|
670
|
+
return out;
|
|
671
|
+
}
|
|
672
|
+
function rewriteInsertOrIgnore(sql) {
|
|
673
|
+
if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
|
|
674
|
+
return sql;
|
|
675
|
+
}
|
|
676
|
+
const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
|
|
677
|
+
return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
|
|
678
|
+
}
|
|
679
|
+
function rewriteInsertOrReplace(sql) {
|
|
680
|
+
const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
|
|
681
|
+
if (!match) {
|
|
682
|
+
return sql;
|
|
683
|
+
}
|
|
684
|
+
const rawTable = match[1];
|
|
685
|
+
const rawColumns = match[2];
|
|
686
|
+
const remainder = match[3];
|
|
687
|
+
const tableName = unqualifiedTableName(rawTable);
|
|
688
|
+
const conflictKeys = UPSERT_KEYS[tableName];
|
|
689
|
+
if (!conflictKeys?.length) {
|
|
690
|
+
return sql;
|
|
691
|
+
}
|
|
692
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
693
|
+
const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
|
|
694
|
+
const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
|
|
695
|
+
const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
|
|
696
|
+
return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
|
|
697
|
+
}
|
|
698
|
+
function rewriteSql(sql) {
|
|
699
|
+
let out = sql;
|
|
700
|
+
out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
|
|
701
|
+
out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
|
|
702
|
+
out = rewriteBooleanLiterals(out);
|
|
703
|
+
out = rewriteInsertOrReplace(out);
|
|
704
|
+
out = rewriteInsertOrIgnore(out);
|
|
705
|
+
return stripTrailingSemicolon(out);
|
|
706
|
+
}
|
|
707
|
+
function toBoolean(value) {
|
|
708
|
+
if (value === null || value === void 0) return value;
|
|
709
|
+
if (typeof value === "boolean") return value;
|
|
710
|
+
if (typeof value === "number") return value !== 0;
|
|
711
|
+
if (typeof value === "bigint") return value !== 0n;
|
|
712
|
+
if (typeof value === "string") {
|
|
713
|
+
const normalized = value.trim().toLowerCase();
|
|
714
|
+
if (normalized === "0" || normalized === "false") return false;
|
|
715
|
+
if (normalized === "1" || normalized === "true") return true;
|
|
716
|
+
}
|
|
717
|
+
return Boolean(value);
|
|
718
|
+
}
|
|
719
|
+
function countQuestionMarks(sql, end) {
|
|
720
|
+
let count = 0;
|
|
721
|
+
let inSingle = false;
|
|
722
|
+
let inDouble = false;
|
|
723
|
+
let inLineComment = false;
|
|
724
|
+
let inBlockComment = false;
|
|
725
|
+
for (let i = 0; i < end; i++) {
|
|
726
|
+
const ch = sql[i];
|
|
727
|
+
const next = sql[i + 1];
|
|
728
|
+
if (inLineComment) {
|
|
729
|
+
if (ch === "\n") inLineComment = false;
|
|
730
|
+
continue;
|
|
731
|
+
}
|
|
732
|
+
if (inBlockComment) {
|
|
733
|
+
if (ch === "*" && next === "/") {
|
|
734
|
+
inBlockComment = false;
|
|
735
|
+
i += 1;
|
|
736
|
+
}
|
|
737
|
+
continue;
|
|
738
|
+
}
|
|
739
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
740
|
+
inLineComment = true;
|
|
741
|
+
i += 1;
|
|
742
|
+
continue;
|
|
743
|
+
}
|
|
744
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
745
|
+
inBlockComment = true;
|
|
746
|
+
i += 1;
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
749
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
750
|
+
inSingle = !inSingle;
|
|
751
|
+
continue;
|
|
752
|
+
}
|
|
753
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
754
|
+
inDouble = !inDouble;
|
|
755
|
+
continue;
|
|
756
|
+
}
|
|
757
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
758
|
+
count += 1;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
return count;
|
|
762
|
+
}
|
|
763
|
+
function findBooleanPlaceholderIndexes(sql) {
|
|
764
|
+
const indexes = /* @__PURE__ */ new Set();
|
|
765
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
766
|
+
const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
|
|
767
|
+
for (const match of sql.matchAll(pattern)) {
|
|
768
|
+
const matchText = match[0];
|
|
769
|
+
const qIndex = match.index + matchText.lastIndexOf("?");
|
|
770
|
+
indexes.add(countQuestionMarks(sql, qIndex + 1));
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
return indexes;
|
|
774
|
+
}
|
|
775
|
+
function coerceInsertBooleanArgs(sql, args) {
|
|
776
|
+
const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
|
|
777
|
+
if (!match) return;
|
|
778
|
+
const rawTable = match[1];
|
|
779
|
+
const rawColumns = match[2];
|
|
780
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
781
|
+
if (!boolColumns?.size) return;
|
|
782
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
783
|
+
for (const [index, column] of columns.entries()) {
|
|
784
|
+
if (boolColumns.has(column) && index < args.length) {
|
|
785
|
+
args[index] = toBoolean(args[index]);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
function coerceUpdateBooleanArgs(sql, args) {
|
|
790
|
+
const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
|
|
791
|
+
if (!match) return;
|
|
792
|
+
const rawTable = match[1];
|
|
793
|
+
const setClause = match[2];
|
|
794
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
795
|
+
if (!boolColumns?.size) return;
|
|
796
|
+
const assignments = setClause.split(",");
|
|
797
|
+
let placeholderIndex = 0;
|
|
798
|
+
for (const assignment of assignments) {
|
|
799
|
+
if (!assignment.includes("?")) continue;
|
|
800
|
+
placeholderIndex += 1;
|
|
801
|
+
const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
|
|
802
|
+
if (colMatch && boolColumns.has(colMatch[1])) {
|
|
803
|
+
args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
function coerceBooleanArgs(sql, args) {
|
|
808
|
+
const nextArgs = [...args];
|
|
809
|
+
coerceInsertBooleanArgs(sql, nextArgs);
|
|
810
|
+
coerceUpdateBooleanArgs(sql, nextArgs);
|
|
811
|
+
const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
|
|
812
|
+
for (const index of placeholderIndexes) {
|
|
813
|
+
if (index > 0 && index <= nextArgs.length) {
|
|
814
|
+
nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
return nextArgs;
|
|
818
|
+
}
|
|
819
|
+
function convertQuestionMarksToDollarParams(sql) {
|
|
820
|
+
let out = "";
|
|
821
|
+
let placeholder = 0;
|
|
822
|
+
let inSingle = false;
|
|
823
|
+
let inDouble = false;
|
|
824
|
+
let inLineComment = false;
|
|
825
|
+
let inBlockComment = false;
|
|
826
|
+
for (let i = 0; i < sql.length; i++) {
|
|
827
|
+
const ch = sql[i];
|
|
828
|
+
const next = sql[i + 1];
|
|
829
|
+
if (inLineComment) {
|
|
830
|
+
out += ch;
|
|
831
|
+
if (ch === "\n") inLineComment = false;
|
|
832
|
+
continue;
|
|
833
|
+
}
|
|
834
|
+
if (inBlockComment) {
|
|
835
|
+
out += ch;
|
|
836
|
+
if (ch === "*" && next === "/") {
|
|
837
|
+
out += next;
|
|
838
|
+
inBlockComment = false;
|
|
839
|
+
i += 1;
|
|
840
|
+
}
|
|
841
|
+
continue;
|
|
842
|
+
}
|
|
843
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
844
|
+
out += ch + next;
|
|
845
|
+
inLineComment = true;
|
|
846
|
+
i += 1;
|
|
847
|
+
continue;
|
|
848
|
+
}
|
|
849
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
850
|
+
out += ch + next;
|
|
851
|
+
inBlockComment = true;
|
|
852
|
+
i += 1;
|
|
853
|
+
continue;
|
|
854
|
+
}
|
|
855
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
856
|
+
inSingle = !inSingle;
|
|
857
|
+
out += ch;
|
|
858
|
+
continue;
|
|
859
|
+
}
|
|
860
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
861
|
+
inDouble = !inDouble;
|
|
862
|
+
out += ch;
|
|
863
|
+
continue;
|
|
864
|
+
}
|
|
865
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
866
|
+
placeholder += 1;
|
|
867
|
+
out += `$${placeholder}`;
|
|
868
|
+
continue;
|
|
869
|
+
}
|
|
870
|
+
out += ch;
|
|
871
|
+
}
|
|
872
|
+
return out;
|
|
873
|
+
}
|
|
874
|
+
function translateStatementForPostgres(stmt) {
|
|
875
|
+
const normalized = normalizeStatement(stmt);
|
|
876
|
+
if (normalized.kind === "named") {
|
|
877
|
+
throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
|
|
878
|
+
}
|
|
879
|
+
const rewrittenSql = rewriteSql(normalized.sql);
|
|
880
|
+
const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
|
|
881
|
+
return {
|
|
882
|
+
sql: convertQuestionMarksToDollarParams(rewrittenSql),
|
|
883
|
+
args: coercedArgs
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
function shouldBypassPostgres(stmt) {
|
|
887
|
+
const normalized = normalizeStatement(stmt);
|
|
888
|
+
if (normalized.kind === "named") {
|
|
889
|
+
return true;
|
|
890
|
+
}
|
|
891
|
+
return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
|
|
892
|
+
}
|
|
893
|
+
function shouldFallbackOnError(error) {
|
|
894
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
895
|
+
return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
|
|
896
|
+
}
|
|
897
|
+
function isReadQuery(sql) {
|
|
898
|
+
const trimmed = sql.trimStart();
|
|
899
|
+
return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
|
|
900
|
+
}
|
|
901
|
+
function buildRow(row, columns) {
|
|
902
|
+
const values = columns.map((column) => row[column]);
|
|
903
|
+
return Object.assign(values, row);
|
|
904
|
+
}
|
|
905
|
+
function buildResultSet(rows, rowsAffected = 0) {
|
|
906
|
+
const columns = rows[0] ? Object.keys(rows[0]) : [];
|
|
907
|
+
const resultRows = rows.map((row) => buildRow(row, columns));
|
|
908
|
+
return {
|
|
909
|
+
columns,
|
|
910
|
+
columnTypes: columns.map(() => ""),
|
|
911
|
+
rows: resultRows,
|
|
912
|
+
rowsAffected,
|
|
913
|
+
lastInsertRowid: void 0,
|
|
914
|
+
toJSON() {
|
|
915
|
+
return {
|
|
916
|
+
columns,
|
|
917
|
+
columnTypes: columns.map(() => ""),
|
|
918
|
+
rows,
|
|
919
|
+
rowsAffected,
|
|
920
|
+
lastInsertRowid: void 0
|
|
921
|
+
};
|
|
922
|
+
}
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
async function loadPrismaClient() {
|
|
926
|
+
if (!prismaClientPromise) {
|
|
927
|
+
prismaClientPromise = (async () => {
|
|
928
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
929
|
+
if (explicitPath) {
|
|
930
|
+
const module2 = await import(pathToFileURL(explicitPath).href);
|
|
931
|
+
const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
|
|
932
|
+
if (!PrismaClient2) {
|
|
933
|
+
throw new Error(`No PrismaClient export found at ${explicitPath}`);
|
|
934
|
+
}
|
|
935
|
+
return new PrismaClient2();
|
|
936
|
+
}
|
|
937
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path4.join(os3.homedir(), "exe-db");
|
|
938
|
+
const requireFromExeDb = createRequire(path4.join(exeDbRoot, "package.json"));
|
|
939
|
+
const prismaEntry = requireFromExeDb.resolve("@prisma/client");
|
|
940
|
+
const module = await import(pathToFileURL(prismaEntry).href);
|
|
941
|
+
const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
|
|
942
|
+
if (!PrismaClient) {
|
|
943
|
+
throw new Error(`No PrismaClient export found in ${prismaEntry}`);
|
|
944
|
+
}
|
|
945
|
+
return new PrismaClient();
|
|
946
|
+
})();
|
|
947
|
+
}
|
|
948
|
+
return prismaClientPromise;
|
|
949
|
+
}
|
|
950
|
+
async function ensureCompatibilityViews(prisma) {
|
|
951
|
+
if (!compatibilityBootstrapPromise) {
|
|
952
|
+
compatibilityBootstrapPromise = (async () => {
|
|
953
|
+
for (const mapping of VIEW_MAPPINGS) {
|
|
954
|
+
const relation = mapping.source.replace(/"/g, "");
|
|
955
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
956
|
+
"SELECT to_regclass($1) AS regclass",
|
|
957
|
+
relation
|
|
958
|
+
);
|
|
959
|
+
if (!rows[0]?.regclass) {
|
|
960
|
+
continue;
|
|
961
|
+
}
|
|
962
|
+
await prisma.$executeRawUnsafe(
|
|
963
|
+
`CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
|
|
964
|
+
);
|
|
965
|
+
}
|
|
966
|
+
})();
|
|
967
|
+
}
|
|
968
|
+
return compatibilityBootstrapPromise;
|
|
969
|
+
}
|
|
970
|
+
async function executeOnPrisma(executor, stmt) {
|
|
971
|
+
const translated = translateStatementForPostgres(stmt);
|
|
972
|
+
if (isReadQuery(translated.sql)) {
|
|
973
|
+
const rows = await executor.$queryRawUnsafe(
|
|
974
|
+
translated.sql,
|
|
975
|
+
...translated.args
|
|
976
|
+
);
|
|
977
|
+
return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
|
|
978
|
+
}
|
|
979
|
+
const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
|
|
980
|
+
return buildResultSet([], rowsAffected);
|
|
981
|
+
}
|
|
982
|
+
function splitSqlStatements(sql) {
|
|
983
|
+
const parts = [];
|
|
984
|
+
let current = "";
|
|
985
|
+
let inSingle = false;
|
|
986
|
+
let inDouble = false;
|
|
987
|
+
let inLineComment = false;
|
|
988
|
+
let inBlockComment = false;
|
|
989
|
+
for (let i = 0; i < sql.length; i++) {
|
|
990
|
+
const ch = sql[i];
|
|
991
|
+
const next = sql[i + 1];
|
|
992
|
+
if (inLineComment) {
|
|
993
|
+
current += ch;
|
|
994
|
+
if (ch === "\n") inLineComment = false;
|
|
995
|
+
continue;
|
|
996
|
+
}
|
|
997
|
+
if (inBlockComment) {
|
|
998
|
+
current += ch;
|
|
999
|
+
if (ch === "*" && next === "/") {
|
|
1000
|
+
current += next;
|
|
1001
|
+
inBlockComment = false;
|
|
1002
|
+
i += 1;
|
|
1003
|
+
}
|
|
1004
|
+
continue;
|
|
1005
|
+
}
|
|
1006
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
1007
|
+
current += ch + next;
|
|
1008
|
+
inLineComment = true;
|
|
1009
|
+
i += 1;
|
|
1010
|
+
continue;
|
|
1011
|
+
}
|
|
1012
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
1013
|
+
current += ch + next;
|
|
1014
|
+
inBlockComment = true;
|
|
1015
|
+
i += 1;
|
|
1016
|
+
continue;
|
|
1017
|
+
}
|
|
1018
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
1019
|
+
inSingle = !inSingle;
|
|
1020
|
+
current += ch;
|
|
1021
|
+
continue;
|
|
1022
|
+
}
|
|
1023
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
1024
|
+
inDouble = !inDouble;
|
|
1025
|
+
current += ch;
|
|
1026
|
+
continue;
|
|
1027
|
+
}
|
|
1028
|
+
if (!inSingle && !inDouble && ch === ";") {
|
|
1029
|
+
if (current.trim()) {
|
|
1030
|
+
parts.push(current.trim());
|
|
1031
|
+
}
|
|
1032
|
+
current = "";
|
|
1033
|
+
continue;
|
|
1034
|
+
}
|
|
1035
|
+
current += ch;
|
|
1036
|
+
}
|
|
1037
|
+
if (current.trim()) {
|
|
1038
|
+
parts.push(current.trim());
|
|
1039
|
+
}
|
|
1040
|
+
return parts;
|
|
1041
|
+
}
|
|
1042
|
+
async function createPrismaDbAdapter(fallbackClient) {
|
|
1043
|
+
const prisma = await loadPrismaClient();
|
|
1044
|
+
await ensureCompatibilityViews(prisma);
|
|
1045
|
+
let closed = false;
|
|
1046
|
+
let adapter;
|
|
1047
|
+
const fallbackExecute = async (stmt, error) => {
|
|
1048
|
+
if (!fallbackClient) {
|
|
1049
|
+
if (error) throw error;
|
|
1050
|
+
throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
|
|
1051
|
+
}
|
|
1052
|
+
if (error) {
|
|
1053
|
+
process.stderr.write(
|
|
1054
|
+
`[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
1055
|
+
`
|
|
1056
|
+
);
|
|
1057
|
+
}
|
|
1058
|
+
return fallbackClient.execute(stmt);
|
|
1059
|
+
};
|
|
1060
|
+
adapter = {
|
|
1061
|
+
async execute(stmt) {
|
|
1062
|
+
if (shouldBypassPostgres(stmt)) {
|
|
1063
|
+
return fallbackExecute(stmt);
|
|
1064
|
+
}
|
|
1065
|
+
try {
|
|
1066
|
+
return await executeOnPrisma(prisma, stmt);
|
|
1067
|
+
} catch (error) {
|
|
1068
|
+
if (shouldFallbackOnError(error)) {
|
|
1069
|
+
return fallbackExecute(stmt, error);
|
|
1070
|
+
}
|
|
1071
|
+
throw error;
|
|
1072
|
+
}
|
|
1073
|
+
},
|
|
1074
|
+
async batch(stmts, mode) {
|
|
1075
|
+
if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
|
|
1076
|
+
if (!fallbackClient) {
|
|
1077
|
+
throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
|
|
1078
|
+
}
|
|
1079
|
+
return fallbackClient.batch(stmts, mode);
|
|
1080
|
+
}
|
|
1081
|
+
try {
|
|
1082
|
+
if (prisma.$transaction) {
|
|
1083
|
+
return await prisma.$transaction(async (tx) => {
|
|
1084
|
+
const results2 = [];
|
|
1085
|
+
for (const stmt of stmts) {
|
|
1086
|
+
results2.push(await executeOnPrisma(tx, stmt));
|
|
1087
|
+
}
|
|
1088
|
+
return results2;
|
|
1089
|
+
});
|
|
1090
|
+
}
|
|
1091
|
+
const results = [];
|
|
1092
|
+
for (const stmt of stmts) {
|
|
1093
|
+
results.push(await executeOnPrisma(prisma, stmt));
|
|
1094
|
+
}
|
|
1095
|
+
return results;
|
|
1096
|
+
} catch (error) {
|
|
1097
|
+
if (fallbackClient && shouldFallbackOnError(error)) {
|
|
1098
|
+
process.stderr.write(
|
|
1099
|
+
`[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
1100
|
+
`
|
|
1101
|
+
);
|
|
1102
|
+
return fallbackClient.batch(stmts, mode);
|
|
1103
|
+
}
|
|
1104
|
+
throw error;
|
|
1105
|
+
}
|
|
1106
|
+
},
|
|
1107
|
+
async migrate(stmts) {
|
|
1108
|
+
if (fallbackClient) {
|
|
1109
|
+
return fallbackClient.migrate(stmts);
|
|
1110
|
+
}
|
|
1111
|
+
return adapter.batch(stmts, "deferred");
|
|
1112
|
+
},
|
|
1113
|
+
async transaction(mode) {
|
|
1114
|
+
if (!fallbackClient) {
|
|
1115
|
+
throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
|
|
1116
|
+
}
|
|
1117
|
+
return fallbackClient.transaction(mode);
|
|
1118
|
+
},
|
|
1119
|
+
async executeMultiple(sql) {
|
|
1120
|
+
if (fallbackClient && shouldBypassPostgres(sql)) {
|
|
1121
|
+
return fallbackClient.executeMultiple(sql);
|
|
1122
|
+
}
|
|
1123
|
+
for (const statement of splitSqlStatements(sql)) {
|
|
1124
|
+
await adapter.execute(statement);
|
|
1125
|
+
}
|
|
1126
|
+
},
|
|
1127
|
+
async sync() {
|
|
1128
|
+
if (fallbackClient) {
|
|
1129
|
+
return fallbackClient.sync();
|
|
1130
|
+
}
|
|
1131
|
+
return { frame_no: 0, frames_synced: 0 };
|
|
1132
|
+
},
|
|
1133
|
+
close() {
|
|
1134
|
+
closed = true;
|
|
1135
|
+
prismaClientPromise = null;
|
|
1136
|
+
compatibilityBootstrapPromise = null;
|
|
1137
|
+
void prisma.$disconnect?.();
|
|
1138
|
+
},
|
|
1139
|
+
get closed() {
|
|
1140
|
+
return closed;
|
|
1141
|
+
},
|
|
1142
|
+
get protocol() {
|
|
1143
|
+
return "prisma-postgres";
|
|
1144
|
+
}
|
|
1145
|
+
};
|
|
1146
|
+
return adapter;
|
|
1147
|
+
}
|
|
1148
|
+
var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
|
|
1149
|
+
var init_database_adapter = __esm({
|
|
1150
|
+
"src/lib/database-adapter.ts"() {
|
|
1151
|
+
"use strict";
|
|
1152
|
+
VIEW_MAPPINGS = [
|
|
1153
|
+
{ view: "memories", source: "memory.memory_records" },
|
|
1154
|
+
{ view: "tasks", source: "memory.tasks" },
|
|
1155
|
+
{ view: "behaviors", source: "memory.behaviors" },
|
|
1156
|
+
{ view: "entities", source: "memory.entities" },
|
|
1157
|
+
{ view: "relationships", source: "memory.relationships" },
|
|
1158
|
+
{ view: "entity_memories", source: "memory.entity_memories" },
|
|
1159
|
+
{ view: "entity_aliases", source: "memory.entity_aliases" },
|
|
1160
|
+
{ view: "notifications", source: "memory.notifications" },
|
|
1161
|
+
{ view: "messages", source: "memory.messages" },
|
|
1162
|
+
{ view: "users", source: "wiki.users" },
|
|
1163
|
+
{ view: "workspaces", source: "wiki.workspaces" },
|
|
1164
|
+
{ view: "workspace_users", source: "wiki.workspace_users" },
|
|
1165
|
+
{ view: "documents", source: "wiki.workspace_documents" },
|
|
1166
|
+
{ view: "chats", source: "wiki.workspace_chats" }
|
|
1167
|
+
];
|
|
1168
|
+
UPSERT_KEYS = {
|
|
1169
|
+
memories: ["id"],
|
|
1170
|
+
tasks: ["id"],
|
|
1171
|
+
behaviors: ["id"],
|
|
1172
|
+
entities: ["id"],
|
|
1173
|
+
relationships: ["id"],
|
|
1174
|
+
entity_aliases: ["alias"],
|
|
1175
|
+
notifications: ["id"],
|
|
1176
|
+
messages: ["id"],
|
|
1177
|
+
users: ["id"],
|
|
1178
|
+
workspaces: ["id"],
|
|
1179
|
+
workspace_users: ["id"],
|
|
1180
|
+
documents: ["id"],
|
|
1181
|
+
chats: ["id"]
|
|
1182
|
+
};
|
|
1183
|
+
BOOLEAN_COLUMNS_BY_TABLE = {
|
|
1184
|
+
memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
|
|
1185
|
+
behaviors: /* @__PURE__ */ new Set(["active"]),
|
|
1186
|
+
notifications: /* @__PURE__ */ new Set(["read"]),
|
|
1187
|
+
users: /* @__PURE__ */ new Set(["has_personal_memory"])
|
|
1188
|
+
};
|
|
1189
|
+
BOOLEAN_COLUMN_NAMES = new Set(
|
|
1190
|
+
Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
|
|
1191
|
+
);
|
|
1192
|
+
IMMEDIATE_FALLBACK_PATTERNS = [
|
|
1193
|
+
/\bPRAGMA\b/i,
|
|
1194
|
+
/\bsqlite_master\b/i,
|
|
1195
|
+
/(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
|
|
1196
|
+
/\bMATCH\b/i,
|
|
1197
|
+
/\bvector_distance_cos\s*\(/i,
|
|
1198
|
+
/\bjson_extract\s*\(/i,
|
|
1199
|
+
/\bjulianday\s*\(/i,
|
|
1200
|
+
/\bstrftime\s*\(/i,
|
|
1201
|
+
/\blast_insert_rowid\s*\(/i
|
|
1202
|
+
];
|
|
1203
|
+
prismaClientPromise = null;
|
|
1204
|
+
compatibilityBootstrapPromise = null;
|
|
460
1205
|
}
|
|
461
1206
|
});
|
|
462
1207
|
|
|
463
1208
|
// src/lib/database.ts
|
|
464
1209
|
import { createClient } from "@libsql/client";
|
|
465
1210
|
async function initDatabase(config) {
|
|
1211
|
+
if (_walCheckpointTimer) {
|
|
1212
|
+
clearInterval(_walCheckpointTimer);
|
|
1213
|
+
_walCheckpointTimer = null;
|
|
1214
|
+
}
|
|
1215
|
+
if (_daemonClient) {
|
|
1216
|
+
_daemonClient.close();
|
|
1217
|
+
_daemonClient = null;
|
|
1218
|
+
}
|
|
1219
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
1220
|
+
_adapterClient.close();
|
|
1221
|
+
}
|
|
1222
|
+
_adapterClient = null;
|
|
466
1223
|
if (_client) {
|
|
467
1224
|
_client.close();
|
|
468
1225
|
_client = null;
|
|
@@ -476,6 +1233,7 @@ async function initDatabase(config) {
|
|
|
476
1233
|
}
|
|
477
1234
|
_client = createClient(opts);
|
|
478
1235
|
_resilientClient = wrapWithRetry(_client);
|
|
1236
|
+
_adapterClient = _resilientClient;
|
|
479
1237
|
_client.execute("PRAGMA busy_timeout = 30000").catch(() => {
|
|
480
1238
|
});
|
|
481
1239
|
_client.execute("PRAGMA journal_mode = WAL").catch(() => {
|
|
@@ -486,11 +1244,17 @@ async function initDatabase(config) {
|
|
|
486
1244
|
});
|
|
487
1245
|
}, 3e4);
|
|
488
1246
|
_walCheckpointTimer.unref();
|
|
1247
|
+
if (process.env.DATABASE_URL) {
|
|
1248
|
+
_adapterClient = await createPrismaDbAdapter(_resilientClient);
|
|
1249
|
+
}
|
|
489
1250
|
}
|
|
490
1251
|
function getClient() {
|
|
491
|
-
if (!
|
|
1252
|
+
if (!_adapterClient) {
|
|
492
1253
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
493
1254
|
}
|
|
1255
|
+
if (process.env.DATABASE_URL) {
|
|
1256
|
+
return _adapterClient;
|
|
1257
|
+
}
|
|
494
1258
|
if (process.env.EXE_IS_DAEMON === "1") {
|
|
495
1259
|
return _resilientClient;
|
|
496
1260
|
}
|
|
@@ -1431,26 +2195,36 @@ async function ensureSchema() {
|
|
|
1431
2195
|
}
|
|
1432
2196
|
}
|
|
1433
2197
|
async function disposeDatabase() {
|
|
2198
|
+
if (_walCheckpointTimer) {
|
|
2199
|
+
clearInterval(_walCheckpointTimer);
|
|
2200
|
+
_walCheckpointTimer = null;
|
|
2201
|
+
}
|
|
1434
2202
|
if (_daemonClient) {
|
|
1435
2203
|
_daemonClient.close();
|
|
1436
2204
|
_daemonClient = null;
|
|
1437
2205
|
}
|
|
2206
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
2207
|
+
_adapterClient.close();
|
|
2208
|
+
}
|
|
2209
|
+
_adapterClient = null;
|
|
1438
2210
|
if (_client) {
|
|
1439
2211
|
_client.close();
|
|
1440
2212
|
_client = null;
|
|
1441
2213
|
_resilientClient = null;
|
|
1442
2214
|
}
|
|
1443
2215
|
}
|
|
1444
|
-
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
|
|
2216
|
+
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
|
|
1445
2217
|
var init_database = __esm({
|
|
1446
2218
|
"src/lib/database.ts"() {
|
|
1447
2219
|
"use strict";
|
|
1448
2220
|
init_db_retry();
|
|
1449
2221
|
init_employees();
|
|
2222
|
+
init_database_adapter();
|
|
1450
2223
|
_client = null;
|
|
1451
2224
|
_resilientClient = null;
|
|
1452
2225
|
_walCheckpointTimer = null;
|
|
1453
2226
|
_daemonClient = null;
|
|
2227
|
+
_adapterClient = null;
|
|
1454
2228
|
initTurso = initDatabase;
|
|
1455
2229
|
disposeTurso = disposeDatabase;
|
|
1456
2230
|
}
|
|
@@ -1469,13 +2243,13 @@ __export(shard_manager_exports, {
|
|
|
1469
2243
|
listShards: () => listShards,
|
|
1470
2244
|
shardExists: () => shardExists
|
|
1471
2245
|
});
|
|
1472
|
-
import
|
|
1473
|
-
import { existsSync as
|
|
2246
|
+
import path6 from "path";
|
|
2247
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync } from "fs";
|
|
1474
2248
|
import { createClient as createClient2 } from "@libsql/client";
|
|
1475
2249
|
function initShardManager(encryptionKey) {
|
|
1476
2250
|
_encryptionKey = encryptionKey;
|
|
1477
|
-
if (!
|
|
1478
|
-
|
|
2251
|
+
if (!existsSync5(SHARDS_DIR)) {
|
|
2252
|
+
mkdirSync2(SHARDS_DIR, { recursive: true });
|
|
1479
2253
|
}
|
|
1480
2254
|
_shardingEnabled = true;
|
|
1481
2255
|
}
|
|
@@ -1495,7 +2269,7 @@ function getShardClient(projectName) {
|
|
|
1495
2269
|
}
|
|
1496
2270
|
const cached = _shards.get(safeName);
|
|
1497
2271
|
if (cached) return cached;
|
|
1498
|
-
const dbPath =
|
|
2272
|
+
const dbPath = path6.join(SHARDS_DIR, `${safeName}.db`);
|
|
1499
2273
|
const client = createClient2({
|
|
1500
2274
|
url: `file:${dbPath}`,
|
|
1501
2275
|
encryptionKey: _encryptionKey
|
|
@@ -1505,10 +2279,10 @@ function getShardClient(projectName) {
|
|
|
1505
2279
|
}
|
|
1506
2280
|
function shardExists(projectName) {
|
|
1507
2281
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
1508
|
-
return
|
|
2282
|
+
return existsSync5(path6.join(SHARDS_DIR, `${safeName}.db`));
|
|
1509
2283
|
}
|
|
1510
2284
|
function listShards() {
|
|
1511
|
-
if (!
|
|
2285
|
+
if (!existsSync5(SHARDS_DIR)) return [];
|
|
1512
2286
|
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
1513
2287
|
}
|
|
1514
2288
|
async function ensureShardSchema(client) {
|
|
@@ -1582,7 +2356,23 @@ async function ensureShardSchema(client) {
|
|
|
1582
2356
|
// MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
|
|
1583
2357
|
"ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
|
|
1584
2358
|
"ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
|
|
1585
|
-
"ALTER TABLE memories ADD COLUMN trajectory TEXT"
|
|
2359
|
+
"ALTER TABLE memories ADD COLUMN trajectory TEXT",
|
|
2360
|
+
// Metadata enrichment columns (must match database.ts)
|
|
2361
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
2362
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
2363
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
2364
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
2365
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
2366
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
2367
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
2368
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
2369
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
2370
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
2371
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
2372
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
2373
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
2374
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
2375
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
1586
2376
|
]) {
|
|
1587
2377
|
try {
|
|
1588
2378
|
await client.execute(col);
|
|
@@ -1694,7 +2484,7 @@ var init_shard_manager = __esm({
|
|
|
1694
2484
|
"src/lib/shard-manager.ts"() {
|
|
1695
2485
|
"use strict";
|
|
1696
2486
|
init_config();
|
|
1697
|
-
SHARDS_DIR =
|
|
2487
|
+
SHARDS_DIR = path6.join(EXE_AI_DIR, "shards");
|
|
1698
2488
|
_shards = /* @__PURE__ */ new Map();
|
|
1699
2489
|
_encryptionKey = null;
|
|
1700
2490
|
_shardingEnabled = false;
|
|
@@ -2583,9 +3373,9 @@ __export(active_agent_exports, {
|
|
|
2583
3373
|
resolveActiveAgentFromTmuxSession: () => resolveActiveAgentFromTmuxSession,
|
|
2584
3374
|
writeActiveAgent: () => writeActiveAgent
|
|
2585
3375
|
});
|
|
2586
|
-
import { readFileSync as
|
|
3376
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, unlinkSync as unlinkSync3, readdirSync as readdirSync3 } from "fs";
|
|
2587
3377
|
import { execSync as execSync4 } from "child_process";
|
|
2588
|
-
import
|
|
3378
|
+
import path8 from "path";
|
|
2589
3379
|
function isNameWithOptionalInstance(candidate, baseName) {
|
|
2590
3380
|
if (candidate === baseName) return true;
|
|
2591
3381
|
if (!candidate.startsWith(baseName)) return false;
|
|
@@ -2629,12 +3419,12 @@ function resolveActiveAgentFromTmuxSession(sessionName) {
|
|
|
2629
3419
|
return null;
|
|
2630
3420
|
}
|
|
2631
3421
|
function getMarkerPath() {
|
|
2632
|
-
return
|
|
3422
|
+
return path8.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
|
|
2633
3423
|
}
|
|
2634
3424
|
function writeActiveAgent(agentId, agentRole) {
|
|
2635
3425
|
try {
|
|
2636
|
-
|
|
2637
|
-
|
|
3426
|
+
mkdirSync4(CACHE_DIR, { recursive: true });
|
|
3427
|
+
writeFileSync4(
|
|
2638
3428
|
getMarkerPath(),
|
|
2639
3429
|
JSON.stringify({ agentId, agentRole, startedAt: (/* @__PURE__ */ new Date()).toISOString() })
|
|
2640
3430
|
);
|
|
@@ -2650,7 +3440,7 @@ function clearActiveAgent() {
|
|
|
2650
3440
|
function getActiveAgent() {
|
|
2651
3441
|
try {
|
|
2652
3442
|
const markerPath = getMarkerPath();
|
|
2653
|
-
const raw =
|
|
3443
|
+
const raw = readFileSync4(markerPath, "utf8");
|
|
2654
3444
|
const data = JSON.parse(raw);
|
|
2655
3445
|
if (data.agentId) {
|
|
2656
3446
|
if (data.startedAt) {
|
|
@@ -2698,14 +3488,14 @@ function getAllActiveAgents() {
|
|
|
2698
3488
|
const key = file.slice("active-agent-".length, -".json".length);
|
|
2699
3489
|
if (key === "undefined") continue;
|
|
2700
3490
|
try {
|
|
2701
|
-
const raw =
|
|
3491
|
+
const raw = readFileSync4(path8.join(CACHE_DIR, file), "utf8");
|
|
2702
3492
|
const data = JSON.parse(raw);
|
|
2703
3493
|
if (!data.agentId) continue;
|
|
2704
3494
|
if (data.startedAt) {
|
|
2705
3495
|
const age = Date.now() - new Date(data.startedAt).getTime();
|
|
2706
3496
|
if (age > STALE_MS) {
|
|
2707
3497
|
try {
|
|
2708
|
-
unlinkSync3(
|
|
3498
|
+
unlinkSync3(path8.join(CACHE_DIR, file));
|
|
2709
3499
|
} catch {
|
|
2710
3500
|
}
|
|
2711
3501
|
continue;
|
|
@@ -2728,11 +3518,11 @@ function getAllActiveAgents() {
|
|
|
2728
3518
|
function cleanupSessionMarkers() {
|
|
2729
3519
|
const key = getSessionKey();
|
|
2730
3520
|
try {
|
|
2731
|
-
unlinkSync3(
|
|
3521
|
+
unlinkSync3(path8.join(CACHE_DIR, `active-agent-${key}.json`));
|
|
2732
3522
|
} catch {
|
|
2733
3523
|
}
|
|
2734
3524
|
try {
|
|
2735
|
-
unlinkSync3(
|
|
3525
|
+
unlinkSync3(path8.join(CACHE_DIR, "active-agent-undefined.json"));
|
|
2736
3526
|
} catch {
|
|
2737
3527
|
}
|
|
2738
3528
|
}
|
|
@@ -2743,15 +3533,15 @@ var init_active_agent = __esm({
|
|
|
2743
3533
|
init_config();
|
|
2744
3534
|
init_session_key();
|
|
2745
3535
|
init_employees();
|
|
2746
|
-
CACHE_DIR =
|
|
3536
|
+
CACHE_DIR = path8.join(EXE_AI_DIR, "session-cache");
|
|
2747
3537
|
STALE_MS = 24 * 60 * 60 * 1e3;
|
|
2748
3538
|
}
|
|
2749
3539
|
});
|
|
2750
3540
|
|
|
2751
3541
|
// src/bin/exe-launch-agent.ts
|
|
2752
|
-
import
|
|
2753
|
-
import
|
|
2754
|
-
import { existsSync as
|
|
3542
|
+
import os6 from "os";
|
|
3543
|
+
import path9 from "path";
|
|
3544
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, readdirSync as readdirSync4 } from "fs";
|
|
2755
3545
|
import { spawnSync } from "child_process";
|
|
2756
3546
|
|
|
2757
3547
|
// src/lib/store.ts
|
|
@@ -2760,16 +3550,16 @@ init_database();
|
|
|
2760
3550
|
|
|
2761
3551
|
// src/lib/keychain.ts
|
|
2762
3552
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
2763
|
-
import { existsSync as
|
|
2764
|
-
import
|
|
2765
|
-
import
|
|
3553
|
+
import { existsSync as existsSync4 } from "fs";
|
|
3554
|
+
import path5 from "path";
|
|
3555
|
+
import os4 from "os";
|
|
2766
3556
|
var SERVICE = "exe-mem";
|
|
2767
3557
|
var ACCOUNT = "master-key";
|
|
2768
3558
|
function getKeyDir() {
|
|
2769
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
3559
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path5.join(os4.homedir(), ".exe-os");
|
|
2770
3560
|
}
|
|
2771
3561
|
function getKeyPath() {
|
|
2772
|
-
return
|
|
3562
|
+
return path5.join(getKeyDir(), "master.key");
|
|
2773
3563
|
}
|
|
2774
3564
|
async function tryKeytar() {
|
|
2775
3565
|
try {
|
|
@@ -2790,9 +3580,9 @@ async function getMasterKey() {
|
|
|
2790
3580
|
}
|
|
2791
3581
|
}
|
|
2792
3582
|
const keyPath = getKeyPath();
|
|
2793
|
-
if (!
|
|
3583
|
+
if (!existsSync4(keyPath)) {
|
|
2794
3584
|
process.stderr.write(
|
|
2795
|
-
`[keychain] Key not found at ${keyPath} (HOME=${
|
|
3585
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os4.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
2796
3586
|
`
|
|
2797
3587
|
);
|
|
2798
3588
|
return null;
|
|
@@ -3103,15 +3893,15 @@ function vectorToBlob(vector) {
|
|
|
3103
3893
|
}
|
|
3104
3894
|
|
|
3105
3895
|
// src/lib/behaviors-export.ts
|
|
3106
|
-
import
|
|
3107
|
-
import
|
|
3896
|
+
import os5 from "os";
|
|
3897
|
+
import path7 from "path";
|
|
3108
3898
|
import {
|
|
3109
|
-
existsSync as
|
|
3110
|
-
mkdirSync as
|
|
3899
|
+
existsSync as existsSync6,
|
|
3900
|
+
mkdirSync as mkdirSync3,
|
|
3111
3901
|
readdirSync as readdirSync2,
|
|
3112
3902
|
statSync,
|
|
3113
3903
|
unlinkSync as unlinkSync2,
|
|
3114
|
-
writeFileSync as
|
|
3904
|
+
writeFileSync as writeFileSync3
|
|
3115
3905
|
} from "fs";
|
|
3116
3906
|
|
|
3117
3907
|
// src/lib/behaviors.ts
|
|
@@ -3146,15 +3936,15 @@ async function listBehaviors(agentId, projectName, limit = 30) {
|
|
|
3146
3936
|
}
|
|
3147
3937
|
|
|
3148
3938
|
// src/lib/behaviors-export.ts
|
|
3149
|
-
var BEHAVIORS_EXPORT_DIR =
|
|
3150
|
-
|
|
3939
|
+
var BEHAVIORS_EXPORT_DIR = path7.join(
|
|
3940
|
+
os5.homedir(),
|
|
3151
3941
|
".exe-os",
|
|
3152
3942
|
"behaviors-export"
|
|
3153
3943
|
);
|
|
3154
3944
|
var STALE_EXPORT_AGE_MS = 60 * 60 * 1e3;
|
|
3155
3945
|
var EXPORT_BEHAVIOR_LIMIT = 30;
|
|
3156
3946
|
function sweepStaleBehaviorExports(now = Date.now()) {
|
|
3157
|
-
if (!
|
|
3947
|
+
if (!existsSync6(BEHAVIORS_EXPORT_DIR)) return;
|
|
3158
3948
|
let entries;
|
|
3159
3949
|
try {
|
|
3160
3950
|
entries = readdirSync2(BEHAVIORS_EXPORT_DIR);
|
|
@@ -3162,7 +3952,7 @@ function sweepStaleBehaviorExports(now = Date.now()) {
|
|
|
3162
3952
|
return;
|
|
3163
3953
|
}
|
|
3164
3954
|
for (const entry of entries) {
|
|
3165
|
-
const filePath =
|
|
3955
|
+
const filePath = path7.join(BEHAVIORS_EXPORT_DIR, entry);
|
|
3166
3956
|
try {
|
|
3167
3957
|
const stat = statSync(filePath);
|
|
3168
3958
|
if (now - stat.mtimeMs > STALE_EXPORT_AGE_MS) {
|
|
@@ -3195,22 +3985,22 @@ function renderBehaviorExport(behaviors) {
|
|
|
3195
3985
|
}
|
|
3196
3986
|
function exportFilePath(agentId, projectName, sessionKey) {
|
|
3197
3987
|
if (!sessionKey) {
|
|
3198
|
-
return
|
|
3988
|
+
return path7.join(BEHAVIORS_EXPORT_DIR, `${agentId}.md`);
|
|
3199
3989
|
}
|
|
3200
3990
|
const safeProject = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
3201
|
-
return
|
|
3991
|
+
return path7.join(
|
|
3202
3992
|
BEHAVIORS_EXPORT_DIR,
|
|
3203
3993
|
`${agentId}-${safeProject}-${sessionKey}.md`
|
|
3204
3994
|
);
|
|
3205
3995
|
}
|
|
3206
3996
|
async function exportBehaviorsForAgent(agentId, projectName, sessionKey) {
|
|
3207
|
-
|
|
3997
|
+
mkdirSync3(BEHAVIORS_EXPORT_DIR, { recursive: true });
|
|
3208
3998
|
sweepStaleBehaviorExports();
|
|
3209
3999
|
const behaviors = await listBehaviors(agentId, projectName, EXPORT_BEHAVIOR_LIMIT);
|
|
3210
4000
|
if (behaviors.length === 0) return null;
|
|
3211
4001
|
const body = renderBehaviorExport(behaviors);
|
|
3212
4002
|
const target = exportFilePath(agentId, projectName, sessionKey);
|
|
3213
|
-
|
|
4003
|
+
writeFileSync3(target, body, "utf-8");
|
|
3214
4004
|
return target;
|
|
3215
4005
|
}
|
|
3216
4006
|
|
|
@@ -3269,7 +4059,7 @@ function parseBasename(basename) {
|
|
|
3269
4059
|
return { agent, provider };
|
|
3270
4060
|
}
|
|
3271
4061
|
function resolveAgent(argv) {
|
|
3272
|
-
const invokedAs =
|
|
4062
|
+
const invokedAs = path9.basename(argv[1] ?? "");
|
|
3273
4063
|
if (invokedAs && invokedAs !== "exe-launch-agent" && !invokedAs.endsWith(".js")) {
|
|
3274
4064
|
const { agent: agent2, provider } = parseBasename(invokedAs.toLowerCase());
|
|
3275
4065
|
return { agent: agent2, provider, passthrough: argv.slice(2) };
|
|
@@ -3298,20 +4088,20 @@ async function isKnownAgent(agent) {
|
|
|
3298
4088
|
}
|
|
3299
4089
|
}
|
|
3300
4090
|
function identityPathFor(agent) {
|
|
3301
|
-
const dir =
|
|
3302
|
-
const exactPath =
|
|
3303
|
-
if (
|
|
4091
|
+
const dir = path9.join(os6.homedir(), ".exe-os", "identity");
|
|
4092
|
+
const exactPath = path9.join(dir, `${agent}.md`);
|
|
4093
|
+
if (existsSync7(exactPath)) return exactPath;
|
|
3304
4094
|
try {
|
|
3305
4095
|
const files = readdirSync4(dir);
|
|
3306
4096
|
const match = files.find((f) => f.toLowerCase() === `${agent.toLowerCase()}.md`);
|
|
3307
|
-
if (match) return
|
|
4097
|
+
if (match) return path9.join(dir, match);
|
|
3308
4098
|
} catch {
|
|
3309
4099
|
}
|
|
3310
4100
|
return exactPath;
|
|
3311
4101
|
}
|
|
3312
4102
|
function leanMcpConfigFor(agent) {
|
|
3313
|
-
const p =
|
|
3314
|
-
return
|
|
4103
|
+
const p = path9.join(os6.homedir(), ".exe-os", "mcp-configs", `${agent}-lean.json`);
|
|
4104
|
+
return existsSync7(p) ? p : null;
|
|
3315
4105
|
}
|
|
3316
4106
|
var _ccHelpOutput = null;
|
|
3317
4107
|
function getCcHelpOutput() {
|
|
@@ -3334,39 +4124,39 @@ function _resetCcHelpCache() {
|
|
|
3334
4124
|
function buildLaunchPlan(agent, behaviorsPath, passthrough, _hasAgentFlag, _provider) {
|
|
3335
4125
|
const args = ["--dangerously-skip-permissions"];
|
|
3336
4126
|
const idPath = identityPathFor(agent);
|
|
3337
|
-
const ccAgentPath =
|
|
4127
|
+
const ccAgentPath = path9.join(os6.homedir(), ".claude", "agents", `${agent}.md`);
|
|
3338
4128
|
let effectiveCcPath = null;
|
|
3339
|
-
if (
|
|
4129
|
+
if (existsSync7(ccAgentPath)) {
|
|
3340
4130
|
effectiveCcPath = ccAgentPath;
|
|
3341
4131
|
} else {
|
|
3342
4132
|
try {
|
|
3343
|
-
const ccAgentDir =
|
|
4133
|
+
const ccAgentDir = path9.join(os6.homedir(), ".claude", "agents");
|
|
3344
4134
|
const ccFiles = readdirSync4(ccAgentDir);
|
|
3345
4135
|
const ccMatch = ccFiles.find((f) => f.toLowerCase() === `${agent.toLowerCase()}.md`);
|
|
3346
|
-
if (ccMatch) effectiveCcPath =
|
|
4136
|
+
if (ccMatch) effectiveCcPath = path9.join(ccAgentDir, ccMatch);
|
|
3347
4137
|
} catch {
|
|
3348
4138
|
}
|
|
3349
4139
|
}
|
|
3350
|
-
const effectiveIdPath =
|
|
4140
|
+
const effectiveIdPath = existsSync7(idPath) ? idPath : effectiveCcPath;
|
|
3351
4141
|
let identityContent = null;
|
|
3352
|
-
if (effectiveIdPath &&
|
|
4142
|
+
if (effectiveIdPath && existsSync7(effectiveIdPath)) {
|
|
3353
4143
|
try {
|
|
3354
|
-
const content =
|
|
4144
|
+
const content = readFileSync5(effectiveIdPath, "utf-8");
|
|
3355
4145
|
if (content.trim().length > 0) identityContent = content;
|
|
3356
4146
|
} catch {
|
|
3357
4147
|
}
|
|
3358
4148
|
}
|
|
3359
4149
|
if (!identityContent) {
|
|
3360
4150
|
try {
|
|
3361
|
-
const rosterPath =
|
|
3362
|
-
if (
|
|
3363
|
-
const roster = JSON.parse(
|
|
4151
|
+
const rosterPath = path9.join(os6.homedir(), ".exe-os", "exe-employees.json");
|
|
4152
|
+
if (existsSync7(rosterPath)) {
|
|
4153
|
+
const roster = JSON.parse(readFileSync5(rosterPath, "utf8"));
|
|
3364
4154
|
const emp = roster.find((e) => e.name.toLowerCase() === agent.toLowerCase());
|
|
3365
4155
|
if (emp?.systemPrompt && emp.systemPrompt.trim().length > 20) {
|
|
3366
4156
|
identityContent = emp.systemPrompt;
|
|
3367
4157
|
try {
|
|
3368
|
-
const dir =
|
|
3369
|
-
if (!
|
|
4158
|
+
const dir = path9.dirname(idPath);
|
|
4159
|
+
if (!existsSync7(dir)) mkdirSync5(dir, { recursive: true });
|
|
3370
4160
|
const hasFrontmatter = identityContent.trimStart().startsWith("---");
|
|
3371
4161
|
const fileContent = hasFrontmatter ? identityContent : `---
|
|
3372
4162
|
role: ${(emp.role ?? "employee").toLowerCase()}
|
|
@@ -3378,7 +4168,7 @@ updated_at: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
|
3378
4168
|
---
|
|
3379
4169
|
|
|
3380
4170
|
${identityContent}`;
|
|
3381
|
-
|
|
4171
|
+
writeFileSync5(idPath, fileContent, "utf-8");
|
|
3382
4172
|
identityContent = fileContent;
|
|
3383
4173
|
process.stderr.write(`[exe-launch-agent] self-healed missing identity file: ${idPath}
|
|
3384
4174
|
`);
|
|
@@ -3406,15 +4196,15 @@ ${identityContent}`;
|
|
|
3406
4196
|
args.push("--system-prompt", getSessionPrompt(identityContent));
|
|
3407
4197
|
} else {
|
|
3408
4198
|
try {
|
|
3409
|
-
const tmpPath =
|
|
3410
|
-
|
|
3411
|
-
|
|
4199
|
+
const tmpPath = path9.join(os6.homedir(), ".exe-os", "session-cache", `${agent}-identity.md`);
|
|
4200
|
+
mkdirSync5(path9.dirname(tmpPath), { recursive: true });
|
|
4201
|
+
writeFileSync5(tmpPath, identityContent, "utf-8");
|
|
3412
4202
|
args.push("--append-system-prompt-file", tmpPath);
|
|
3413
4203
|
} catch {
|
|
3414
4204
|
}
|
|
3415
4205
|
}
|
|
3416
4206
|
}
|
|
3417
|
-
if (behaviorsPath &&
|
|
4207
|
+
if (behaviorsPath && existsSync7(behaviorsPath)) {
|
|
3418
4208
|
args.push("--append-system-prompt-file", behaviorsPath);
|
|
3419
4209
|
}
|
|
3420
4210
|
const leanMcp = leanMcpConfigFor(agent);
|
|
@@ -3535,28 +4325,28 @@ async function main() {
|
|
|
3535
4325
|
_resetCcAgentSupportCache();
|
|
3536
4326
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
3537
4327
|
if (hasAgentFlag) {
|
|
3538
|
-
const ccAgentDir =
|
|
3539
|
-
const ccAgentFile =
|
|
3540
|
-
if (!
|
|
4328
|
+
const ccAgentDir = path9.join(os6.homedir(), ".claude", "agents");
|
|
4329
|
+
const ccAgentFile = path9.join(ccAgentDir, `${agent}.md`);
|
|
4330
|
+
if (!existsSync7(ccAgentFile)) {
|
|
3541
4331
|
const exeIdentity = identityPathFor(agent);
|
|
3542
4332
|
let sourceFile = null;
|
|
3543
|
-
if (
|
|
4333
|
+
if (existsSync7(exeIdentity)) {
|
|
3544
4334
|
sourceFile = exeIdentity;
|
|
3545
4335
|
} else {
|
|
3546
4336
|
try {
|
|
3547
|
-
const identityDir =
|
|
4337
|
+
const identityDir = path9.dirname(exeIdentity);
|
|
3548
4338
|
const files = readdirSync4(identityDir);
|
|
3549
4339
|
const match = files.find((f) => f.toLowerCase() === `${agent.toLowerCase()}.md`);
|
|
3550
|
-
if (match) sourceFile =
|
|
4340
|
+
if (match) sourceFile = path9.join(identityDir, match);
|
|
3551
4341
|
} catch {
|
|
3552
4342
|
}
|
|
3553
4343
|
}
|
|
3554
4344
|
if (sourceFile) {
|
|
3555
4345
|
try {
|
|
3556
|
-
|
|
3557
|
-
let content =
|
|
4346
|
+
mkdirSync5(ccAgentDir, { recursive: true });
|
|
4347
|
+
let content = readFileSync5(sourceFile, "utf-8");
|
|
3558
4348
|
content = content.replace(/\$\{agent_id\}/g, baseAgentName(agent));
|
|
3559
|
-
|
|
4349
|
+
writeFileSync5(ccAgentFile, content, "utf-8");
|
|
3560
4350
|
process.stderr.write(
|
|
3561
4351
|
`[exe-launch-agent] auto-provisioned ${ccAgentFile} from ${sourceFile}
|
|
3562
4352
|
`
|
|
@@ -3576,7 +4366,7 @@ async function main() {
|
|
|
3576
4366
|
const empRole = (() => {
|
|
3577
4367
|
try {
|
|
3578
4368
|
const emps = __require("fs").readFileSync(
|
|
3579
|
-
|
|
4369
|
+
path9.join(os6.homedir(), ".exe-os", "exe-employees.json"),
|
|
3580
4370
|
"utf-8"
|
|
3581
4371
|
);
|
|
3582
4372
|
const found = JSON.parse(emps).find(
|