@askexenow/exe-os 0.9.7 → 0.9.9
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 +953 -105
- package/dist/bin/backfill-responses.js +952 -104
- package/dist/bin/backfill-vectors.js +956 -108
- package/dist/bin/cleanup-stale-review-tasks.js +802 -58
- package/dist/bin/cli.js +2292 -1070
- package/dist/bin/exe-agent-config.js +157 -101
- package/dist/bin/exe-agent.js +55 -29
- package/dist/bin/exe-assign.js +940 -92
- package/dist/bin/exe-boot.js +1424 -442
- package/dist/bin/exe-call.js +240 -141
- package/dist/bin/exe-cloud.js +198 -70
- package/dist/bin/exe-dispatch.js +951 -192
- package/dist/bin/exe-doctor.js +791 -51
- package/dist/bin/exe-export-behaviors.js +790 -42
- package/dist/bin/exe-forget.js +771 -31
- package/dist/bin/exe-gateway.js +1592 -521
- package/dist/bin/exe-heartbeat.js +850 -109
- package/dist/bin/exe-kill.js +783 -35
- package/dist/bin/exe-launch-agent.js +1030 -107
- package/dist/bin/exe-link.js +916 -110
- package/dist/bin/exe-new-employee.js +526 -217
- package/dist/bin/exe-pending-messages.js +1046 -62
- package/dist/bin/exe-pending-notifications.js +1318 -111
- package/dist/bin/exe-pending-reviews.js +1040 -72
- package/dist/bin/exe-rename.js +772 -59
- package/dist/bin/exe-review.js +772 -32
- package/dist/bin/exe-search.js +982 -128
- package/dist/bin/exe-session-cleanup.js +1180 -306
- package/dist/bin/exe-settings.js +185 -105
- package/dist/bin/exe-start-codex.js +886 -132
- package/dist/bin/exe-start-opencode.js +873 -119
- package/dist/bin/exe-status.js +803 -59
- package/dist/bin/exe-team.js +772 -32
- package/dist/bin/git-sweep.js +1046 -223
- package/dist/bin/graph-backfill.js +779 -31
- package/dist/bin/graph-export.js +785 -37
- package/dist/bin/install.js +632 -200
- package/dist/bin/scan-tasks.js +1055 -232
- package/dist/bin/setup.js +1419 -320
- package/dist/bin/shard-migrate.js +783 -35
- package/dist/bin/update.js +138 -49
- package/dist/bin/wiki-sync.js +782 -34
- package/dist/gateway/index.js +1444 -449
- package/dist/hooks/bug-report-worker.js +1141 -269
- package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
- package/dist/hooks/commit-complete.js +1044 -221
- package/dist/hooks/error-recall.js +989 -135
- package/dist/hooks/exe-heartbeat-hook.js +99 -75
- package/dist/hooks/ingest-worker.js +4176 -3226
- package/dist/hooks/ingest.js +920 -168
- package/dist/hooks/instructions-loaded.js +874 -70
- package/dist/hooks/notification.js +860 -56
- package/dist/hooks/post-compact.js +881 -73
- package/dist/hooks/pre-compact.js +1050 -227
- package/dist/hooks/pre-tool-use.js +1084 -159
- package/dist/hooks/prompt-ingest-worker.js +1089 -164
- package/dist/hooks/prompt-submit.js +1469 -515
- package/dist/hooks/response-ingest-worker.js +1104 -179
- package/dist/hooks/session-end.js +1085 -251
- package/dist/hooks/session-start.js +1241 -231
- package/dist/hooks/stop.js +935 -109
- package/dist/hooks/subagent-stop.js +881 -73
- package/dist/hooks/summary-worker.js +1323 -307
- package/dist/index.js +1449 -452
- package/dist/lib/agent-config.js +28 -6
- package/dist/lib/cloud-sync.js +909 -115
- package/dist/lib/config.js +30 -10
- package/dist/lib/consolidation.js +42 -9
- package/dist/lib/database.js +739 -33
- package/dist/lib/db-daemon-client.js +73 -19
- package/dist/lib/db.js +2359 -0
- package/dist/lib/device-registry.js +760 -47
- package/dist/lib/embedder.js +201 -73
- package/dist/lib/employee-templates.js +30 -4
- package/dist/lib/employees.js +290 -86
- package/dist/lib/exe-daemon-client.js +187 -83
- package/dist/lib/exe-daemon.js +1696 -616
- package/dist/lib/hybrid-search.js +982 -128
- package/dist/lib/identity.js +43 -13
- package/dist/lib/license.js +133 -48
- package/dist/lib/messaging.js +167 -80
- package/dist/lib/reminders.js +35 -5
- package/dist/lib/schedules.js +772 -32
- package/dist/lib/skill-learning.js +54 -7
- package/dist/lib/store.js +779 -31
- package/dist/lib/task-router.js +94 -73
- package/dist/lib/tasks.js +298 -225
- package/dist/lib/tmux-routing.js +246 -172
- package/dist/lib/token-spend.js +52 -14
- package/dist/mcp/server.js +2893 -850
- package/dist/mcp/tools/complete-reminder.js +35 -5
- package/dist/mcp/tools/create-reminder.js +35 -5
- package/dist/mcp/tools/create-task.js +507 -323
- package/dist/mcp/tools/deactivate-behavior.js +40 -10
- package/dist/mcp/tools/list-reminders.js +35 -5
- package/dist/mcp/tools/list-tasks.js +277 -104
- package/dist/mcp/tools/send-message.js +129 -56
- package/dist/mcp/tools/update-task.js +1864 -188
- package/dist/runtime/index.js +1083 -259
- package/dist/tui/App.js +1501 -434
- package/package.json +3 -2
package/dist/bin/exe-gateway.js
CHANGED
|
@@ -392,6 +392,44 @@ var init_db_retry = __esm({
|
|
|
392
392
|
}
|
|
393
393
|
});
|
|
394
394
|
|
|
395
|
+
// src/lib/secure-files.ts
|
|
396
|
+
import { chmodSync, existsSync, mkdirSync } from "fs";
|
|
397
|
+
import { chmod, mkdir } from "fs/promises";
|
|
398
|
+
async function ensurePrivateDir(dirPath) {
|
|
399
|
+
await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
400
|
+
try {
|
|
401
|
+
await chmod(dirPath, PRIVATE_DIR_MODE);
|
|
402
|
+
} catch {
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
function ensurePrivateDirSync(dirPath) {
|
|
406
|
+
mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
407
|
+
try {
|
|
408
|
+
chmodSync(dirPath, PRIVATE_DIR_MODE);
|
|
409
|
+
} catch {
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
async function enforcePrivateFile(filePath) {
|
|
413
|
+
try {
|
|
414
|
+
await chmod(filePath, PRIVATE_FILE_MODE);
|
|
415
|
+
} catch {
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
function enforcePrivateFileSync(filePath) {
|
|
419
|
+
try {
|
|
420
|
+
if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
|
|
421
|
+
} catch {
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
|
|
425
|
+
var init_secure_files = __esm({
|
|
426
|
+
"src/lib/secure-files.ts"() {
|
|
427
|
+
"use strict";
|
|
428
|
+
PRIVATE_DIR_MODE = 448;
|
|
429
|
+
PRIVATE_FILE_MODE = 384;
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
|
|
395
433
|
// src/lib/config.ts
|
|
396
434
|
var config_exports = {};
|
|
397
435
|
__export(config_exports, {
|
|
@@ -408,8 +446,8 @@ __export(config_exports, {
|
|
|
408
446
|
migrateConfig: () => migrateConfig,
|
|
409
447
|
saveConfig: () => saveConfig
|
|
410
448
|
});
|
|
411
|
-
import { readFile, writeFile
|
|
412
|
-
import { readFileSync, existsSync, renameSync } from "fs";
|
|
449
|
+
import { readFile, writeFile } from "fs/promises";
|
|
450
|
+
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
413
451
|
import path from "path";
|
|
414
452
|
import os from "os";
|
|
415
453
|
function resolveDataDir() {
|
|
@@ -417,7 +455,7 @@ function resolveDataDir() {
|
|
|
417
455
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
418
456
|
const newDir = path.join(os.homedir(), ".exe-os");
|
|
419
457
|
const legacyDir = path.join(os.homedir(), ".exe-mem");
|
|
420
|
-
if (!
|
|
458
|
+
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
421
459
|
try {
|
|
422
460
|
renameSync(legacyDir, newDir);
|
|
423
461
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -480,9 +518,9 @@ function normalizeAutoUpdate(raw) {
|
|
|
480
518
|
}
|
|
481
519
|
async function loadConfig() {
|
|
482
520
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
483
|
-
await
|
|
521
|
+
await ensurePrivateDir(dir);
|
|
484
522
|
const configPath = path.join(dir, "config.json");
|
|
485
|
-
if (!
|
|
523
|
+
if (!existsSync2(configPath)) {
|
|
486
524
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
487
525
|
}
|
|
488
526
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -495,6 +533,7 @@ async function loadConfig() {
|
|
|
495
533
|
`);
|
|
496
534
|
try {
|
|
497
535
|
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
536
|
+
await enforcePrivateFile(configPath);
|
|
498
537
|
} catch {
|
|
499
538
|
}
|
|
500
539
|
}
|
|
@@ -513,7 +552,7 @@ async function loadConfig() {
|
|
|
513
552
|
function loadConfigSync() {
|
|
514
553
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
515
554
|
const configPath = path.join(dir, "config.json");
|
|
516
|
-
if (!
|
|
555
|
+
if (!existsSync2(configPath)) {
|
|
517
556
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
518
557
|
}
|
|
519
558
|
try {
|
|
@@ -531,12 +570,10 @@ function loadConfigSync() {
|
|
|
531
570
|
}
|
|
532
571
|
async function saveConfig(config2) {
|
|
533
572
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
534
|
-
await
|
|
573
|
+
await ensurePrivateDir(dir);
|
|
535
574
|
const configPath = path.join(dir, "config.json");
|
|
536
575
|
await writeFile(configPath, JSON.stringify(config2, null, 2) + "\n");
|
|
537
|
-
|
|
538
|
-
await chmod(configPath, 384);
|
|
539
|
-
}
|
|
576
|
+
await enforcePrivateFile(configPath);
|
|
540
577
|
}
|
|
541
578
|
async function loadConfigFrom(configPath) {
|
|
542
579
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -556,6 +593,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
|
|
|
556
593
|
var init_config = __esm({
|
|
557
594
|
"src/lib/config.ts"() {
|
|
558
595
|
"use strict";
|
|
596
|
+
init_secure_files();
|
|
559
597
|
EXE_AI_DIR = resolveDataDir();
|
|
560
598
|
DB_PATH = path.join(EXE_AI_DIR, "memories.db");
|
|
561
599
|
MODELS_DIR = path.join(EXE_AI_DIR, "models");
|
|
@@ -632,6 +670,120 @@ var init_config = __esm({
|
|
|
632
670
|
}
|
|
633
671
|
});
|
|
634
672
|
|
|
673
|
+
// src/lib/runtime-table.ts
|
|
674
|
+
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
675
|
+
var init_runtime_table = __esm({
|
|
676
|
+
"src/lib/runtime-table.ts"() {
|
|
677
|
+
"use strict";
|
|
678
|
+
RUNTIME_TABLE = {
|
|
679
|
+
codex: {
|
|
680
|
+
binary: "codex",
|
|
681
|
+
launchMode: "interactive",
|
|
682
|
+
autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
|
|
683
|
+
inlineFlag: "--no-alt-screen",
|
|
684
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
685
|
+
defaultModel: "gpt-5.4"
|
|
686
|
+
},
|
|
687
|
+
opencode: {
|
|
688
|
+
binary: "opencode",
|
|
689
|
+
launchMode: "exec",
|
|
690
|
+
autoApproveFlag: "--dangerously-skip-permissions",
|
|
691
|
+
inlineFlag: "",
|
|
692
|
+
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
693
|
+
defaultModel: "anthropic/claude-sonnet-4-6"
|
|
694
|
+
}
|
|
695
|
+
};
|
|
696
|
+
DEFAULT_RUNTIME = "claude";
|
|
697
|
+
}
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
// src/lib/agent-config.ts
|
|
701
|
+
var agent_config_exports = {};
|
|
702
|
+
__export(agent_config_exports, {
|
|
703
|
+
AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
|
|
704
|
+
DEFAULT_MODELS: () => DEFAULT_MODELS,
|
|
705
|
+
KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
|
|
706
|
+
RUNTIME_LABELS: () => RUNTIME_LABELS,
|
|
707
|
+
clearAgentRuntime: () => clearAgentRuntime,
|
|
708
|
+
getAgentRuntime: () => getAgentRuntime,
|
|
709
|
+
loadAgentConfig: () => loadAgentConfig,
|
|
710
|
+
saveAgentConfig: () => saveAgentConfig,
|
|
711
|
+
setAgentRuntime: () => setAgentRuntime
|
|
712
|
+
});
|
|
713
|
+
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
|
|
714
|
+
import path2 from "path";
|
|
715
|
+
function loadAgentConfig() {
|
|
716
|
+
if (!existsSync3(AGENT_CONFIG_PATH)) return {};
|
|
717
|
+
try {
|
|
718
|
+
return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
|
|
719
|
+
} catch {
|
|
720
|
+
return {};
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
function saveAgentConfig(config2) {
|
|
724
|
+
const dir = path2.dirname(AGENT_CONFIG_PATH);
|
|
725
|
+
ensurePrivateDirSync(dir);
|
|
726
|
+
writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config2, null, 2) + "\n", "utf-8");
|
|
727
|
+
enforcePrivateFileSync(AGENT_CONFIG_PATH);
|
|
728
|
+
}
|
|
729
|
+
function getAgentRuntime(agentId) {
|
|
730
|
+
const config2 = loadAgentConfig();
|
|
731
|
+
const entry = config2[agentId];
|
|
732
|
+
if (entry) return entry;
|
|
733
|
+
const orgDefault = config2["default"];
|
|
734
|
+
if (orgDefault) return orgDefault;
|
|
735
|
+
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
736
|
+
}
|
|
737
|
+
function setAgentRuntime(agentId, runtime, model) {
|
|
738
|
+
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
739
|
+
if (!knownModels) {
|
|
740
|
+
return {
|
|
741
|
+
ok: false,
|
|
742
|
+
error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
if (!knownModels.includes(model)) {
|
|
746
|
+
return {
|
|
747
|
+
ok: false,
|
|
748
|
+
error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
const config2 = loadAgentConfig();
|
|
752
|
+
config2[agentId] = { runtime, model };
|
|
753
|
+
saveAgentConfig(config2);
|
|
754
|
+
return { ok: true };
|
|
755
|
+
}
|
|
756
|
+
function clearAgentRuntime(agentId) {
|
|
757
|
+
const config2 = loadAgentConfig();
|
|
758
|
+
delete config2[agentId];
|
|
759
|
+
saveAgentConfig(config2);
|
|
760
|
+
}
|
|
761
|
+
var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
|
|
762
|
+
var init_agent_config = __esm({
|
|
763
|
+
"src/lib/agent-config.ts"() {
|
|
764
|
+
"use strict";
|
|
765
|
+
init_config();
|
|
766
|
+
init_runtime_table();
|
|
767
|
+
init_secure_files();
|
|
768
|
+
AGENT_CONFIG_PATH = path2.join(EXE_AI_DIR, "agent-config.json");
|
|
769
|
+
KNOWN_RUNTIMES = {
|
|
770
|
+
claude: ["claude-opus-4", "claude-sonnet-4", "claude-haiku-4.5"],
|
|
771
|
+
codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
|
|
772
|
+
opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
|
|
773
|
+
};
|
|
774
|
+
RUNTIME_LABELS = {
|
|
775
|
+
claude: "Claude Code (Anthropic)",
|
|
776
|
+
codex: "Codex (OpenAI)",
|
|
777
|
+
opencode: "OpenCode (open source)"
|
|
778
|
+
};
|
|
779
|
+
DEFAULT_MODELS = {
|
|
780
|
+
claude: "claude-opus-4",
|
|
781
|
+
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
782
|
+
opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
});
|
|
786
|
+
|
|
635
787
|
// src/lib/employees.ts
|
|
636
788
|
var employees_exports = {};
|
|
637
789
|
__export(employees_exports, {
|
|
@@ -647,6 +799,7 @@ __export(employees_exports, {
|
|
|
647
799
|
getEmployeeByRole: () => getEmployeeByRole,
|
|
648
800
|
getEmployeeNamesByRole: () => getEmployeeNamesByRole,
|
|
649
801
|
hasRole: () => hasRole,
|
|
802
|
+
hireEmployee: () => hireEmployee,
|
|
650
803
|
isCoordinatorName: () => isCoordinatorName,
|
|
651
804
|
isCoordinatorRole: () => isCoordinatorRole,
|
|
652
805
|
isMultiInstance: () => isMultiInstance,
|
|
@@ -659,9 +812,9 @@ __export(employees_exports, {
|
|
|
659
812
|
validateEmployeeName: () => validateEmployeeName
|
|
660
813
|
});
|
|
661
814
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
662
|
-
import { existsSync as
|
|
815
|
+
import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
663
816
|
import { execSync } from "child_process";
|
|
664
|
-
import
|
|
817
|
+
import path3 from "path";
|
|
665
818
|
import os2 from "os";
|
|
666
819
|
function normalizeRole(role) {
|
|
667
820
|
return (role ?? "").trim().toLowerCase();
|
|
@@ -698,7 +851,7 @@ function validateEmployeeName(name) {
|
|
|
698
851
|
return { valid: true };
|
|
699
852
|
}
|
|
700
853
|
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
701
|
-
if (!
|
|
854
|
+
if (!existsSync4(employeesPath)) {
|
|
702
855
|
return [];
|
|
703
856
|
}
|
|
704
857
|
const raw = await readFile2(employeesPath, "utf-8");
|
|
@@ -709,13 +862,13 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
|
709
862
|
}
|
|
710
863
|
}
|
|
711
864
|
async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
712
|
-
await mkdir2(
|
|
865
|
+
await mkdir2(path3.dirname(employeesPath), { recursive: true });
|
|
713
866
|
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
714
867
|
}
|
|
715
868
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
716
|
-
if (!
|
|
869
|
+
if (!existsSync4(employeesPath)) return [];
|
|
717
870
|
try {
|
|
718
|
-
return JSON.parse(
|
|
871
|
+
return JSON.parse(readFileSync3(employeesPath, "utf-8"));
|
|
719
872
|
} catch {
|
|
720
873
|
return [];
|
|
721
874
|
}
|
|
@@ -757,6 +910,52 @@ function addEmployee(employees, employee) {
|
|
|
757
910
|
}
|
|
758
911
|
return [...employees, normalized];
|
|
759
912
|
}
|
|
913
|
+
function appendToCoordinatorTeam(employee) {
|
|
914
|
+
const coordinator = getCoordinatorEmployee(loadEmployeesSync());
|
|
915
|
+
if (!coordinator) return;
|
|
916
|
+
const idPath = path3.join(IDENTITY_DIR, `${coordinator.name}.md`);
|
|
917
|
+
if (!existsSync4(idPath)) return;
|
|
918
|
+
const content = readFileSync3(idPath, "utf-8");
|
|
919
|
+
if (content.includes(`**${capitalize(employee.name)}`)) return;
|
|
920
|
+
const teamMatch = content.match(TEAM_SECTION_RE);
|
|
921
|
+
if (!teamMatch || teamMatch.index === void 0) return;
|
|
922
|
+
const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
|
|
923
|
+
const nextHeading = afterTeam.match(/\n## /);
|
|
924
|
+
const entry = `
|
|
925
|
+
**${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
|
|
926
|
+
`;
|
|
927
|
+
let updated;
|
|
928
|
+
if (nextHeading && nextHeading.index !== void 0) {
|
|
929
|
+
const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
|
|
930
|
+
updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
|
|
931
|
+
} else {
|
|
932
|
+
updated = content.trimEnd() + "\n" + entry;
|
|
933
|
+
}
|
|
934
|
+
writeFileSync2(idPath, updated, "utf-8");
|
|
935
|
+
}
|
|
936
|
+
function capitalize(s) {
|
|
937
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
938
|
+
}
|
|
939
|
+
async function hireEmployee(employee) {
|
|
940
|
+
const employees = await loadEmployees();
|
|
941
|
+
const updated = addEmployee(employees, employee);
|
|
942
|
+
await saveEmployees(updated);
|
|
943
|
+
try {
|
|
944
|
+
appendToCoordinatorTeam(employee);
|
|
945
|
+
} catch {
|
|
946
|
+
}
|
|
947
|
+
try {
|
|
948
|
+
const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
|
|
949
|
+
const config2 = loadAgentConfig2();
|
|
950
|
+
const name = employee.name.toLowerCase();
|
|
951
|
+
if (!config2[name] && config2["default"]) {
|
|
952
|
+
config2[name] = { ...config2["default"] };
|
|
953
|
+
saveAgentConfig2(config2);
|
|
954
|
+
}
|
|
955
|
+
} catch {
|
|
956
|
+
}
|
|
957
|
+
return updated;
|
|
958
|
+
}
|
|
760
959
|
async function normalizeRosterCase(rosterPath) {
|
|
761
960
|
const employees = await loadEmployees(rosterPath);
|
|
762
961
|
let changed = false;
|
|
@@ -766,14 +965,14 @@ async function normalizeRosterCase(rosterPath) {
|
|
|
766
965
|
emp.name = emp.name.toLowerCase();
|
|
767
966
|
changed = true;
|
|
768
967
|
try {
|
|
769
|
-
const identityDir =
|
|
770
|
-
const oldPath =
|
|
771
|
-
const newPath =
|
|
772
|
-
if (
|
|
968
|
+
const identityDir = path3.join(os2.homedir(), ".exe-os", "identity");
|
|
969
|
+
const oldPath = path3.join(identityDir, `${oldName}.md`);
|
|
970
|
+
const newPath = path3.join(identityDir, `${emp.name}.md`);
|
|
971
|
+
if (existsSync4(oldPath) && !existsSync4(newPath)) {
|
|
773
972
|
renameSync2(oldPath, newPath);
|
|
774
|
-
} else if (
|
|
775
|
-
const content =
|
|
776
|
-
|
|
973
|
+
} else if (existsSync4(oldPath) && oldPath !== newPath) {
|
|
974
|
+
const content = readFileSync3(oldPath, "utf-8");
|
|
975
|
+
writeFileSync2(newPath, content, "utf-8");
|
|
777
976
|
if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
|
|
778
977
|
unlinkSync(oldPath);
|
|
779
978
|
}
|
|
@@ -803,7 +1002,7 @@ function registerBinSymlinks(name) {
|
|
|
803
1002
|
errors.push("Could not find 'exe-os' in PATH");
|
|
804
1003
|
return { created, skipped, errors };
|
|
805
1004
|
}
|
|
806
|
-
const binDir =
|
|
1005
|
+
const binDir = path3.dirname(exeBinPath);
|
|
807
1006
|
let target;
|
|
808
1007
|
try {
|
|
809
1008
|
target = readlinkSync(exeBinPath);
|
|
@@ -813,8 +1012,8 @@ function registerBinSymlinks(name) {
|
|
|
813
1012
|
}
|
|
814
1013
|
for (const suffix of ["", "-opencode"]) {
|
|
815
1014
|
const linkName = `${name}${suffix}`;
|
|
816
|
-
const linkPath =
|
|
817
|
-
if (
|
|
1015
|
+
const linkPath = path3.join(binDir, linkName);
|
|
1016
|
+
if (existsSync4(linkPath)) {
|
|
818
1017
|
skipped.push(linkName);
|
|
819
1018
|
continue;
|
|
820
1019
|
}
|
|
@@ -827,21 +1026,619 @@ function registerBinSymlinks(name) {
|
|
|
827
1026
|
}
|
|
828
1027
|
return { created, skipped, errors };
|
|
829
1028
|
}
|
|
830
|
-
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
|
|
1029
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
|
|
831
1030
|
var init_employees = __esm({
|
|
832
1031
|
"src/lib/employees.ts"() {
|
|
833
1032
|
"use strict";
|
|
834
1033
|
init_config();
|
|
835
|
-
EMPLOYEES_PATH =
|
|
1034
|
+
EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
|
|
836
1035
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
837
1036
|
COORDINATOR_ROLE = "COO";
|
|
838
1037
|
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
1038
|
+
IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
|
|
1039
|
+
TEAM_SECTION_RE = /^## Team\b.*$/m;
|
|
1040
|
+
}
|
|
1041
|
+
});
|
|
1042
|
+
|
|
1043
|
+
// src/lib/database-adapter.ts
|
|
1044
|
+
import os3 from "os";
|
|
1045
|
+
import path4 from "path";
|
|
1046
|
+
import { createRequire } from "module";
|
|
1047
|
+
import { pathToFileURL } from "url";
|
|
1048
|
+
function quotedIdentifier(identifier) {
|
|
1049
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
1050
|
+
}
|
|
1051
|
+
function unqualifiedTableName(name) {
|
|
1052
|
+
const raw = name.trim().replace(/^"|"$/g, "");
|
|
1053
|
+
const parts = raw.split(".");
|
|
1054
|
+
return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
|
|
1055
|
+
}
|
|
1056
|
+
function stripTrailingSemicolon(sql) {
|
|
1057
|
+
return sql.trim().replace(/;+\s*$/u, "");
|
|
1058
|
+
}
|
|
1059
|
+
function appendClause(sql, clause) {
|
|
1060
|
+
const trimmed = stripTrailingSemicolon(sql);
|
|
1061
|
+
const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
|
|
1062
|
+
if (!returningMatch) {
|
|
1063
|
+
return `${trimmed}${clause}`;
|
|
1064
|
+
}
|
|
1065
|
+
const idx = returningMatch.index;
|
|
1066
|
+
return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
|
|
1067
|
+
}
|
|
1068
|
+
function normalizeStatement(stmt) {
|
|
1069
|
+
if (typeof stmt === "string") {
|
|
1070
|
+
return { kind: "positional", sql: stmt, args: [] };
|
|
1071
|
+
}
|
|
1072
|
+
const sql = stmt.sql;
|
|
1073
|
+
if (Array.isArray(stmt.args) || stmt.args === void 0) {
|
|
1074
|
+
return { kind: "positional", sql, args: stmt.args ?? [] };
|
|
1075
|
+
}
|
|
1076
|
+
return { kind: "named", sql, args: stmt.args };
|
|
1077
|
+
}
|
|
1078
|
+
function rewriteBooleanLiterals(sql) {
|
|
1079
|
+
let out = sql;
|
|
1080
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
1081
|
+
const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
|
|
1082
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
|
|
1083
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
|
|
1084
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
|
|
1085
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
|
|
1086
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
|
|
1087
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
|
|
1088
|
+
}
|
|
1089
|
+
return out;
|
|
1090
|
+
}
|
|
1091
|
+
function rewriteInsertOrIgnore(sql) {
|
|
1092
|
+
if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
|
|
1093
|
+
return sql;
|
|
1094
|
+
}
|
|
1095
|
+
const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
|
|
1096
|
+
return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
|
|
1097
|
+
}
|
|
1098
|
+
function rewriteInsertOrReplace(sql) {
|
|
1099
|
+
const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
|
|
1100
|
+
if (!match) {
|
|
1101
|
+
return sql;
|
|
1102
|
+
}
|
|
1103
|
+
const rawTable = match[1];
|
|
1104
|
+
const rawColumns = match[2];
|
|
1105
|
+
const remainder = match[3];
|
|
1106
|
+
const tableName = unqualifiedTableName(rawTable);
|
|
1107
|
+
const conflictKeys = UPSERT_KEYS[tableName];
|
|
1108
|
+
if (!conflictKeys?.length) {
|
|
1109
|
+
return sql;
|
|
1110
|
+
}
|
|
1111
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
1112
|
+
const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
|
|
1113
|
+
const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
|
|
1114
|
+
const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
|
|
1115
|
+
return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
|
|
1116
|
+
}
|
|
1117
|
+
function rewriteSql(sql) {
|
|
1118
|
+
let out = sql;
|
|
1119
|
+
out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
|
|
1120
|
+
out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
|
|
1121
|
+
out = rewriteBooleanLiterals(out);
|
|
1122
|
+
out = rewriteInsertOrReplace(out);
|
|
1123
|
+
out = rewriteInsertOrIgnore(out);
|
|
1124
|
+
return stripTrailingSemicolon(out);
|
|
1125
|
+
}
|
|
1126
|
+
function toBoolean(value) {
|
|
1127
|
+
if (value === null || value === void 0) return value;
|
|
1128
|
+
if (typeof value === "boolean") return value;
|
|
1129
|
+
if (typeof value === "number") return value !== 0;
|
|
1130
|
+
if (typeof value === "bigint") return value !== 0n;
|
|
1131
|
+
if (typeof value === "string") {
|
|
1132
|
+
const normalized = value.trim().toLowerCase();
|
|
1133
|
+
if (normalized === "0" || normalized === "false") return false;
|
|
1134
|
+
if (normalized === "1" || normalized === "true") return true;
|
|
1135
|
+
}
|
|
1136
|
+
return Boolean(value);
|
|
1137
|
+
}
|
|
1138
|
+
function countQuestionMarks(sql, end) {
|
|
1139
|
+
let count = 0;
|
|
1140
|
+
let inSingle = false;
|
|
1141
|
+
let inDouble = false;
|
|
1142
|
+
let inLineComment = false;
|
|
1143
|
+
let inBlockComment = false;
|
|
1144
|
+
for (let i = 0; i < end; i++) {
|
|
1145
|
+
const ch = sql[i];
|
|
1146
|
+
const next = sql[i + 1];
|
|
1147
|
+
if (inLineComment) {
|
|
1148
|
+
if (ch === "\n") inLineComment = false;
|
|
1149
|
+
continue;
|
|
1150
|
+
}
|
|
1151
|
+
if (inBlockComment) {
|
|
1152
|
+
if (ch === "*" && next === "/") {
|
|
1153
|
+
inBlockComment = false;
|
|
1154
|
+
i += 1;
|
|
1155
|
+
}
|
|
1156
|
+
continue;
|
|
1157
|
+
}
|
|
1158
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
1159
|
+
inLineComment = true;
|
|
1160
|
+
i += 1;
|
|
1161
|
+
continue;
|
|
1162
|
+
}
|
|
1163
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
1164
|
+
inBlockComment = true;
|
|
1165
|
+
i += 1;
|
|
1166
|
+
continue;
|
|
1167
|
+
}
|
|
1168
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
1169
|
+
inSingle = !inSingle;
|
|
1170
|
+
continue;
|
|
1171
|
+
}
|
|
1172
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
1173
|
+
inDouble = !inDouble;
|
|
1174
|
+
continue;
|
|
1175
|
+
}
|
|
1176
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
1177
|
+
count += 1;
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
return count;
|
|
1181
|
+
}
|
|
1182
|
+
function findBooleanPlaceholderIndexes(sql) {
|
|
1183
|
+
const indexes = /* @__PURE__ */ new Set();
|
|
1184
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
1185
|
+
const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
|
|
1186
|
+
for (const match of sql.matchAll(pattern)) {
|
|
1187
|
+
const matchText = match[0];
|
|
1188
|
+
const qIndex = match.index + matchText.lastIndexOf("?");
|
|
1189
|
+
indexes.add(countQuestionMarks(sql, qIndex + 1));
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
return indexes;
|
|
1193
|
+
}
|
|
1194
|
+
function coerceInsertBooleanArgs(sql, args) {
|
|
1195
|
+
const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
|
|
1196
|
+
if (!match) return;
|
|
1197
|
+
const rawTable = match[1];
|
|
1198
|
+
const rawColumns = match[2];
|
|
1199
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
1200
|
+
if (!boolColumns?.size) return;
|
|
1201
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
1202
|
+
for (const [index, column] of columns.entries()) {
|
|
1203
|
+
if (boolColumns.has(column) && index < args.length) {
|
|
1204
|
+
args[index] = toBoolean(args[index]);
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
function coerceUpdateBooleanArgs(sql, args) {
|
|
1209
|
+
const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
|
|
1210
|
+
if (!match) return;
|
|
1211
|
+
const rawTable = match[1];
|
|
1212
|
+
const setClause = match[2];
|
|
1213
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
1214
|
+
if (!boolColumns?.size) return;
|
|
1215
|
+
const assignments = setClause.split(",");
|
|
1216
|
+
let placeholderIndex = 0;
|
|
1217
|
+
for (const assignment of assignments) {
|
|
1218
|
+
if (!assignment.includes("?")) continue;
|
|
1219
|
+
placeholderIndex += 1;
|
|
1220
|
+
const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
|
|
1221
|
+
if (colMatch && boolColumns.has(colMatch[1])) {
|
|
1222
|
+
args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
function coerceBooleanArgs(sql, args) {
|
|
1227
|
+
const nextArgs = [...args];
|
|
1228
|
+
coerceInsertBooleanArgs(sql, nextArgs);
|
|
1229
|
+
coerceUpdateBooleanArgs(sql, nextArgs);
|
|
1230
|
+
const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
|
|
1231
|
+
for (const index of placeholderIndexes) {
|
|
1232
|
+
if (index > 0 && index <= nextArgs.length) {
|
|
1233
|
+
nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
return nextArgs;
|
|
1237
|
+
}
|
|
1238
|
+
function convertQuestionMarksToDollarParams(sql) {
|
|
1239
|
+
let out = "";
|
|
1240
|
+
let placeholder = 0;
|
|
1241
|
+
let inSingle = false;
|
|
1242
|
+
let inDouble = false;
|
|
1243
|
+
let inLineComment = false;
|
|
1244
|
+
let inBlockComment = false;
|
|
1245
|
+
for (let i = 0; i < sql.length; i++) {
|
|
1246
|
+
const ch = sql[i];
|
|
1247
|
+
const next = sql[i + 1];
|
|
1248
|
+
if (inLineComment) {
|
|
1249
|
+
out += ch;
|
|
1250
|
+
if (ch === "\n") inLineComment = false;
|
|
1251
|
+
continue;
|
|
1252
|
+
}
|
|
1253
|
+
if (inBlockComment) {
|
|
1254
|
+
out += ch;
|
|
1255
|
+
if (ch === "*" && next === "/") {
|
|
1256
|
+
out += next;
|
|
1257
|
+
inBlockComment = false;
|
|
1258
|
+
i += 1;
|
|
1259
|
+
}
|
|
1260
|
+
continue;
|
|
1261
|
+
}
|
|
1262
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
1263
|
+
out += ch + next;
|
|
1264
|
+
inLineComment = true;
|
|
1265
|
+
i += 1;
|
|
1266
|
+
continue;
|
|
1267
|
+
}
|
|
1268
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
1269
|
+
out += ch + next;
|
|
1270
|
+
inBlockComment = true;
|
|
1271
|
+
i += 1;
|
|
1272
|
+
continue;
|
|
1273
|
+
}
|
|
1274
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
1275
|
+
inSingle = !inSingle;
|
|
1276
|
+
out += ch;
|
|
1277
|
+
continue;
|
|
1278
|
+
}
|
|
1279
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
1280
|
+
inDouble = !inDouble;
|
|
1281
|
+
out += ch;
|
|
1282
|
+
continue;
|
|
1283
|
+
}
|
|
1284
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
1285
|
+
placeholder += 1;
|
|
1286
|
+
out += `$${placeholder}`;
|
|
1287
|
+
continue;
|
|
1288
|
+
}
|
|
1289
|
+
out += ch;
|
|
1290
|
+
}
|
|
1291
|
+
return out;
|
|
1292
|
+
}
|
|
1293
|
+
function translateStatementForPostgres(stmt) {
|
|
1294
|
+
const normalized = normalizeStatement(stmt);
|
|
1295
|
+
if (normalized.kind === "named") {
|
|
1296
|
+
throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
|
|
1297
|
+
}
|
|
1298
|
+
const rewrittenSql = rewriteSql(normalized.sql);
|
|
1299
|
+
const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
|
|
1300
|
+
return {
|
|
1301
|
+
sql: convertQuestionMarksToDollarParams(rewrittenSql),
|
|
1302
|
+
args: coercedArgs
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
function shouldBypassPostgres(stmt) {
|
|
1306
|
+
const normalized = normalizeStatement(stmt);
|
|
1307
|
+
if (normalized.kind === "named") {
|
|
1308
|
+
return true;
|
|
1309
|
+
}
|
|
1310
|
+
return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
|
|
1311
|
+
}
|
|
1312
|
+
function shouldFallbackOnError(error) {
|
|
1313
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1314
|
+
return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
|
|
1315
|
+
}
|
|
1316
|
+
function isReadQuery(sql) {
|
|
1317
|
+
const trimmed = sql.trimStart();
|
|
1318
|
+
return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
|
|
1319
|
+
}
|
|
1320
|
+
function buildRow(row, columns) {
|
|
1321
|
+
const values = columns.map((column) => row[column]);
|
|
1322
|
+
return Object.assign(values, row);
|
|
1323
|
+
}
|
|
1324
|
+
function buildResultSet(rows, rowsAffected = 0) {
|
|
1325
|
+
const columns = rows[0] ? Object.keys(rows[0]) : [];
|
|
1326
|
+
const resultRows = rows.map((row) => buildRow(row, columns));
|
|
1327
|
+
return {
|
|
1328
|
+
columns,
|
|
1329
|
+
columnTypes: columns.map(() => ""),
|
|
1330
|
+
rows: resultRows,
|
|
1331
|
+
rowsAffected,
|
|
1332
|
+
lastInsertRowid: void 0,
|
|
1333
|
+
toJSON() {
|
|
1334
|
+
return {
|
|
1335
|
+
columns,
|
|
1336
|
+
columnTypes: columns.map(() => ""),
|
|
1337
|
+
rows,
|
|
1338
|
+
rowsAffected,
|
|
1339
|
+
lastInsertRowid: void 0
|
|
1340
|
+
};
|
|
1341
|
+
}
|
|
1342
|
+
};
|
|
1343
|
+
}
|
|
1344
|
+
async function loadPrismaClient() {
|
|
1345
|
+
if (!prismaClientPromise) {
|
|
1346
|
+
prismaClientPromise = (async () => {
|
|
1347
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
1348
|
+
if (explicitPath) {
|
|
1349
|
+
const module2 = await import(pathToFileURL(explicitPath).href);
|
|
1350
|
+
const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
|
|
1351
|
+
if (!PrismaClient2) {
|
|
1352
|
+
throw new Error(`No PrismaClient export found at ${explicitPath}`);
|
|
1353
|
+
}
|
|
1354
|
+
return new PrismaClient2();
|
|
1355
|
+
}
|
|
1356
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path4.join(os3.homedir(), "exe-db");
|
|
1357
|
+
const requireFromExeDb = createRequire(path4.join(exeDbRoot, "package.json"));
|
|
1358
|
+
const prismaEntry = requireFromExeDb.resolve("@prisma/client");
|
|
1359
|
+
const module = await import(pathToFileURL(prismaEntry).href);
|
|
1360
|
+
const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
|
|
1361
|
+
if (!PrismaClient) {
|
|
1362
|
+
throw new Error(`No PrismaClient export found in ${prismaEntry}`);
|
|
1363
|
+
}
|
|
1364
|
+
return new PrismaClient();
|
|
1365
|
+
})();
|
|
1366
|
+
}
|
|
1367
|
+
return prismaClientPromise;
|
|
1368
|
+
}
|
|
1369
|
+
async function ensureCompatibilityViews(prisma) {
|
|
1370
|
+
if (!compatibilityBootstrapPromise) {
|
|
1371
|
+
compatibilityBootstrapPromise = (async () => {
|
|
1372
|
+
for (const mapping of VIEW_MAPPINGS) {
|
|
1373
|
+
const relation = mapping.source.replace(/"/g, "");
|
|
1374
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
1375
|
+
"SELECT to_regclass($1) AS regclass",
|
|
1376
|
+
relation
|
|
1377
|
+
);
|
|
1378
|
+
if (!rows[0]?.regclass) {
|
|
1379
|
+
continue;
|
|
1380
|
+
}
|
|
1381
|
+
await prisma.$executeRawUnsafe(
|
|
1382
|
+
`CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
|
|
1383
|
+
);
|
|
1384
|
+
}
|
|
1385
|
+
})();
|
|
1386
|
+
}
|
|
1387
|
+
return compatibilityBootstrapPromise;
|
|
1388
|
+
}
|
|
1389
|
+
async function executeOnPrisma(executor, stmt) {
|
|
1390
|
+
const translated = translateStatementForPostgres(stmt);
|
|
1391
|
+
if (isReadQuery(translated.sql)) {
|
|
1392
|
+
const rows = await executor.$queryRawUnsafe(
|
|
1393
|
+
translated.sql,
|
|
1394
|
+
...translated.args
|
|
1395
|
+
);
|
|
1396
|
+
return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
|
|
1397
|
+
}
|
|
1398
|
+
const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
|
|
1399
|
+
return buildResultSet([], rowsAffected);
|
|
1400
|
+
}
|
|
1401
|
+
function splitSqlStatements(sql) {
|
|
1402
|
+
const parts = [];
|
|
1403
|
+
let current = "";
|
|
1404
|
+
let inSingle = false;
|
|
1405
|
+
let inDouble = false;
|
|
1406
|
+
let inLineComment = false;
|
|
1407
|
+
let inBlockComment = false;
|
|
1408
|
+
for (let i = 0; i < sql.length; i++) {
|
|
1409
|
+
const ch = sql[i];
|
|
1410
|
+
const next = sql[i + 1];
|
|
1411
|
+
if (inLineComment) {
|
|
1412
|
+
current += ch;
|
|
1413
|
+
if (ch === "\n") inLineComment = false;
|
|
1414
|
+
continue;
|
|
1415
|
+
}
|
|
1416
|
+
if (inBlockComment) {
|
|
1417
|
+
current += ch;
|
|
1418
|
+
if (ch === "*" && next === "/") {
|
|
1419
|
+
current += next;
|
|
1420
|
+
inBlockComment = false;
|
|
1421
|
+
i += 1;
|
|
1422
|
+
}
|
|
1423
|
+
continue;
|
|
1424
|
+
}
|
|
1425
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
1426
|
+
current += ch + next;
|
|
1427
|
+
inLineComment = true;
|
|
1428
|
+
i += 1;
|
|
1429
|
+
continue;
|
|
1430
|
+
}
|
|
1431
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
1432
|
+
current += ch + next;
|
|
1433
|
+
inBlockComment = true;
|
|
1434
|
+
i += 1;
|
|
1435
|
+
continue;
|
|
1436
|
+
}
|
|
1437
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
1438
|
+
inSingle = !inSingle;
|
|
1439
|
+
current += ch;
|
|
1440
|
+
continue;
|
|
1441
|
+
}
|
|
1442
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
1443
|
+
inDouble = !inDouble;
|
|
1444
|
+
current += ch;
|
|
1445
|
+
continue;
|
|
1446
|
+
}
|
|
1447
|
+
if (!inSingle && !inDouble && ch === ";") {
|
|
1448
|
+
if (current.trim()) {
|
|
1449
|
+
parts.push(current.trim());
|
|
1450
|
+
}
|
|
1451
|
+
current = "";
|
|
1452
|
+
continue;
|
|
1453
|
+
}
|
|
1454
|
+
current += ch;
|
|
1455
|
+
}
|
|
1456
|
+
if (current.trim()) {
|
|
1457
|
+
parts.push(current.trim());
|
|
1458
|
+
}
|
|
1459
|
+
return parts;
|
|
1460
|
+
}
|
|
1461
|
+
async function createPrismaDbAdapter(fallbackClient) {
|
|
1462
|
+
const prisma = await loadPrismaClient();
|
|
1463
|
+
await ensureCompatibilityViews(prisma);
|
|
1464
|
+
let closed = false;
|
|
1465
|
+
let adapter;
|
|
1466
|
+
const fallbackExecute = async (stmt, error) => {
|
|
1467
|
+
if (!fallbackClient) {
|
|
1468
|
+
if (error) throw error;
|
|
1469
|
+
throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
|
|
1470
|
+
}
|
|
1471
|
+
if (error) {
|
|
1472
|
+
process.stderr.write(
|
|
1473
|
+
`[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
1474
|
+
`
|
|
1475
|
+
);
|
|
1476
|
+
}
|
|
1477
|
+
return fallbackClient.execute(stmt);
|
|
1478
|
+
};
|
|
1479
|
+
adapter = {
|
|
1480
|
+
async execute(stmt) {
|
|
1481
|
+
if (shouldBypassPostgres(stmt)) {
|
|
1482
|
+
return fallbackExecute(stmt);
|
|
1483
|
+
}
|
|
1484
|
+
try {
|
|
1485
|
+
return await executeOnPrisma(prisma, stmt);
|
|
1486
|
+
} catch (error) {
|
|
1487
|
+
if (shouldFallbackOnError(error)) {
|
|
1488
|
+
return fallbackExecute(stmt, error);
|
|
1489
|
+
}
|
|
1490
|
+
throw error;
|
|
1491
|
+
}
|
|
1492
|
+
},
|
|
1493
|
+
async batch(stmts, mode) {
|
|
1494
|
+
if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
|
|
1495
|
+
if (!fallbackClient) {
|
|
1496
|
+
throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
|
|
1497
|
+
}
|
|
1498
|
+
return fallbackClient.batch(stmts, mode);
|
|
1499
|
+
}
|
|
1500
|
+
try {
|
|
1501
|
+
if (prisma.$transaction) {
|
|
1502
|
+
return await prisma.$transaction(async (tx) => {
|
|
1503
|
+
const results2 = [];
|
|
1504
|
+
for (const stmt of stmts) {
|
|
1505
|
+
results2.push(await executeOnPrisma(tx, stmt));
|
|
1506
|
+
}
|
|
1507
|
+
return results2;
|
|
1508
|
+
});
|
|
1509
|
+
}
|
|
1510
|
+
const results = [];
|
|
1511
|
+
for (const stmt of stmts) {
|
|
1512
|
+
results.push(await executeOnPrisma(prisma, stmt));
|
|
1513
|
+
}
|
|
1514
|
+
return results;
|
|
1515
|
+
} catch (error) {
|
|
1516
|
+
if (fallbackClient && shouldFallbackOnError(error)) {
|
|
1517
|
+
process.stderr.write(
|
|
1518
|
+
`[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
1519
|
+
`
|
|
1520
|
+
);
|
|
1521
|
+
return fallbackClient.batch(stmts, mode);
|
|
1522
|
+
}
|
|
1523
|
+
throw error;
|
|
1524
|
+
}
|
|
1525
|
+
},
|
|
1526
|
+
async migrate(stmts) {
|
|
1527
|
+
if (fallbackClient) {
|
|
1528
|
+
return fallbackClient.migrate(stmts);
|
|
1529
|
+
}
|
|
1530
|
+
return adapter.batch(stmts, "deferred");
|
|
1531
|
+
},
|
|
1532
|
+
async transaction(mode) {
|
|
1533
|
+
if (!fallbackClient) {
|
|
1534
|
+
throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
|
|
1535
|
+
}
|
|
1536
|
+
return fallbackClient.transaction(mode);
|
|
1537
|
+
},
|
|
1538
|
+
async executeMultiple(sql) {
|
|
1539
|
+
if (fallbackClient && shouldBypassPostgres(sql)) {
|
|
1540
|
+
return fallbackClient.executeMultiple(sql);
|
|
1541
|
+
}
|
|
1542
|
+
for (const statement of splitSqlStatements(sql)) {
|
|
1543
|
+
await adapter.execute(statement);
|
|
1544
|
+
}
|
|
1545
|
+
},
|
|
1546
|
+
async sync() {
|
|
1547
|
+
if (fallbackClient) {
|
|
1548
|
+
return fallbackClient.sync();
|
|
1549
|
+
}
|
|
1550
|
+
return { frame_no: 0, frames_synced: 0 };
|
|
1551
|
+
},
|
|
1552
|
+
close() {
|
|
1553
|
+
closed = true;
|
|
1554
|
+
prismaClientPromise = null;
|
|
1555
|
+
compatibilityBootstrapPromise = null;
|
|
1556
|
+
void prisma.$disconnect?.();
|
|
1557
|
+
},
|
|
1558
|
+
get closed() {
|
|
1559
|
+
return closed;
|
|
1560
|
+
},
|
|
1561
|
+
get protocol() {
|
|
1562
|
+
return "prisma-postgres";
|
|
1563
|
+
}
|
|
1564
|
+
};
|
|
1565
|
+
return adapter;
|
|
1566
|
+
}
|
|
1567
|
+
var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
|
|
1568
|
+
var init_database_adapter = __esm({
|
|
1569
|
+
"src/lib/database-adapter.ts"() {
|
|
1570
|
+
"use strict";
|
|
1571
|
+
VIEW_MAPPINGS = [
|
|
1572
|
+
{ view: "memories", source: "memory.memory_records" },
|
|
1573
|
+
{ view: "tasks", source: "memory.tasks" },
|
|
1574
|
+
{ view: "behaviors", source: "memory.behaviors" },
|
|
1575
|
+
{ view: "entities", source: "memory.entities" },
|
|
1576
|
+
{ view: "relationships", source: "memory.relationships" },
|
|
1577
|
+
{ view: "entity_memories", source: "memory.entity_memories" },
|
|
1578
|
+
{ view: "entity_aliases", source: "memory.entity_aliases" },
|
|
1579
|
+
{ view: "notifications", source: "memory.notifications" },
|
|
1580
|
+
{ view: "messages", source: "memory.messages" },
|
|
1581
|
+
{ view: "users", source: "wiki.users" },
|
|
1582
|
+
{ view: "workspaces", source: "wiki.workspaces" },
|
|
1583
|
+
{ view: "workspace_users", source: "wiki.workspace_users" },
|
|
1584
|
+
{ view: "documents", source: "wiki.workspace_documents" },
|
|
1585
|
+
{ view: "chats", source: "wiki.workspace_chats" }
|
|
1586
|
+
];
|
|
1587
|
+
UPSERT_KEYS = {
|
|
1588
|
+
memories: ["id"],
|
|
1589
|
+
tasks: ["id"],
|
|
1590
|
+
behaviors: ["id"],
|
|
1591
|
+
entities: ["id"],
|
|
1592
|
+
relationships: ["id"],
|
|
1593
|
+
entity_aliases: ["alias"],
|
|
1594
|
+
notifications: ["id"],
|
|
1595
|
+
messages: ["id"],
|
|
1596
|
+
users: ["id"],
|
|
1597
|
+
workspaces: ["id"],
|
|
1598
|
+
workspace_users: ["id"],
|
|
1599
|
+
documents: ["id"],
|
|
1600
|
+
chats: ["id"]
|
|
1601
|
+
};
|
|
1602
|
+
BOOLEAN_COLUMNS_BY_TABLE = {
|
|
1603
|
+
memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
|
|
1604
|
+
behaviors: /* @__PURE__ */ new Set(["active"]),
|
|
1605
|
+
notifications: /* @__PURE__ */ new Set(["read"]),
|
|
1606
|
+
users: /* @__PURE__ */ new Set(["has_personal_memory"])
|
|
1607
|
+
};
|
|
1608
|
+
BOOLEAN_COLUMN_NAMES = new Set(
|
|
1609
|
+
Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
|
|
1610
|
+
);
|
|
1611
|
+
IMMEDIATE_FALLBACK_PATTERNS = [
|
|
1612
|
+
/\bPRAGMA\b/i,
|
|
1613
|
+
/\bsqlite_master\b/i,
|
|
1614
|
+
/(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
|
|
1615
|
+
/\bMATCH\b/i,
|
|
1616
|
+
/\bvector_distance_cos\s*\(/i,
|
|
1617
|
+
/\bjson_extract\s*\(/i,
|
|
1618
|
+
/\bjulianday\s*\(/i,
|
|
1619
|
+
/\bstrftime\s*\(/i,
|
|
1620
|
+
/\blast_insert_rowid\s*\(/i
|
|
1621
|
+
];
|
|
1622
|
+
prismaClientPromise = null;
|
|
1623
|
+
compatibilityBootstrapPromise = null;
|
|
839
1624
|
}
|
|
840
1625
|
});
|
|
841
1626
|
|
|
842
1627
|
// src/lib/database.ts
|
|
843
1628
|
import { createClient } from "@libsql/client";
|
|
844
1629
|
async function initDatabase(config2) {
|
|
1630
|
+
if (_walCheckpointTimer) {
|
|
1631
|
+
clearInterval(_walCheckpointTimer);
|
|
1632
|
+
_walCheckpointTimer = null;
|
|
1633
|
+
}
|
|
1634
|
+
if (_daemonClient) {
|
|
1635
|
+
_daemonClient.close();
|
|
1636
|
+
_daemonClient = null;
|
|
1637
|
+
}
|
|
1638
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
1639
|
+
_adapterClient.close();
|
|
1640
|
+
}
|
|
1641
|
+
_adapterClient = null;
|
|
845
1642
|
if (_client) {
|
|
846
1643
|
_client.close();
|
|
847
1644
|
_client = null;
|
|
@@ -855,6 +1652,7 @@ async function initDatabase(config2) {
|
|
|
855
1652
|
}
|
|
856
1653
|
_client = createClient(opts);
|
|
857
1654
|
_resilientClient = wrapWithRetry(_client);
|
|
1655
|
+
_adapterClient = _resilientClient;
|
|
858
1656
|
_client.execute("PRAGMA busy_timeout = 30000").catch(() => {
|
|
859
1657
|
});
|
|
860
1658
|
_client.execute("PRAGMA journal_mode = WAL").catch(() => {
|
|
@@ -865,11 +1663,17 @@ async function initDatabase(config2) {
|
|
|
865
1663
|
});
|
|
866
1664
|
}, 3e4);
|
|
867
1665
|
_walCheckpointTimer.unref();
|
|
1666
|
+
if (process.env.DATABASE_URL) {
|
|
1667
|
+
_adapterClient = await createPrismaDbAdapter(_resilientClient);
|
|
1668
|
+
}
|
|
868
1669
|
}
|
|
869
1670
|
function getClient() {
|
|
870
|
-
if (!
|
|
1671
|
+
if (!_adapterClient) {
|
|
871
1672
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
872
1673
|
}
|
|
1674
|
+
if (process.env.DATABASE_URL) {
|
|
1675
|
+
return _adapterClient;
|
|
1676
|
+
}
|
|
873
1677
|
if (process.env.EXE_IS_DAEMON === "1") {
|
|
874
1678
|
return _resilientClient;
|
|
875
1679
|
}
|
|
@@ -1162,6 +1966,7 @@ async function ensureSchema() {
|
|
|
1162
1966
|
project TEXT NOT NULL,
|
|
1163
1967
|
summary TEXT NOT NULL,
|
|
1164
1968
|
task_file TEXT,
|
|
1969
|
+
session_scope TEXT,
|
|
1165
1970
|
read INTEGER NOT NULL DEFAULT 0,
|
|
1166
1971
|
created_at TEXT NOT NULL
|
|
1167
1972
|
);
|
|
@@ -1170,7 +1975,7 @@ async function ensureSchema() {
|
|
|
1170
1975
|
ON notifications(read);
|
|
1171
1976
|
|
|
1172
1977
|
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
1173
|
-
ON notifications(agent_id);
|
|
1978
|
+
ON notifications(agent_id, session_scope);
|
|
1174
1979
|
|
|
1175
1980
|
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
1176
1981
|
ON notifications(task_file);
|
|
@@ -1208,6 +2013,7 @@ async function ensureSchema() {
|
|
|
1208
2013
|
target_agent TEXT NOT NULL,
|
|
1209
2014
|
target_project TEXT,
|
|
1210
2015
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
2016
|
+
session_scope TEXT,
|
|
1211
2017
|
content TEXT NOT NULL,
|
|
1212
2018
|
priority TEXT DEFAULT 'normal',
|
|
1213
2019
|
status TEXT DEFAULT 'pending',
|
|
@@ -1221,10 +2027,31 @@ async function ensureSchema() {
|
|
|
1221
2027
|
);
|
|
1222
2028
|
|
|
1223
2029
|
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
1224
|
-
ON messages(target_agent, status);
|
|
2030
|
+
ON messages(target_agent, session_scope, status);
|
|
1225
2031
|
|
|
1226
2032
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
1227
|
-
ON messages(target_agent, from_agent, server_seq);
|
|
2033
|
+
ON messages(target_agent, session_scope, from_agent, server_seq);
|
|
2034
|
+
`);
|
|
2035
|
+
try {
|
|
2036
|
+
await client.execute({
|
|
2037
|
+
sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
|
|
2038
|
+
args: []
|
|
2039
|
+
});
|
|
2040
|
+
} catch {
|
|
2041
|
+
}
|
|
2042
|
+
try {
|
|
2043
|
+
await client.execute({
|
|
2044
|
+
sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
|
|
2045
|
+
args: []
|
|
2046
|
+
});
|
|
2047
|
+
} catch {
|
|
2048
|
+
}
|
|
2049
|
+
await client.executeMultiple(`
|
|
2050
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
|
|
2051
|
+
ON notifications(agent_id, session_scope, read, created_at);
|
|
2052
|
+
|
|
2053
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
|
|
2054
|
+
ON messages(target_agent, session_scope, status, created_at);
|
|
1228
2055
|
`);
|
|
1229
2056
|
try {
|
|
1230
2057
|
await client.execute({
|
|
@@ -1808,28 +2635,45 @@ async function ensureSchema() {
|
|
|
1808
2635
|
} catch {
|
|
1809
2636
|
}
|
|
1810
2637
|
}
|
|
2638
|
+
try {
|
|
2639
|
+
await client.execute({
|
|
2640
|
+
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
2641
|
+
args: []
|
|
2642
|
+
});
|
|
2643
|
+
} catch {
|
|
2644
|
+
}
|
|
1811
2645
|
}
|
|
1812
2646
|
async function disposeDatabase() {
|
|
2647
|
+
if (_walCheckpointTimer) {
|
|
2648
|
+
clearInterval(_walCheckpointTimer);
|
|
2649
|
+
_walCheckpointTimer = null;
|
|
2650
|
+
}
|
|
1813
2651
|
if (_daemonClient) {
|
|
1814
2652
|
_daemonClient.close();
|
|
1815
2653
|
_daemonClient = null;
|
|
1816
2654
|
}
|
|
2655
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
2656
|
+
_adapterClient.close();
|
|
2657
|
+
}
|
|
2658
|
+
_adapterClient = null;
|
|
1817
2659
|
if (_client) {
|
|
1818
2660
|
_client.close();
|
|
1819
2661
|
_client = null;
|
|
1820
2662
|
_resilientClient = null;
|
|
1821
2663
|
}
|
|
1822
2664
|
}
|
|
1823
|
-
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
|
|
2665
|
+
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
|
|
1824
2666
|
var init_database = __esm({
|
|
1825
2667
|
"src/lib/database.ts"() {
|
|
1826
2668
|
"use strict";
|
|
1827
2669
|
init_db_retry();
|
|
1828
2670
|
init_employees();
|
|
2671
|
+
init_database_adapter();
|
|
1829
2672
|
_client = null;
|
|
1830
2673
|
_resilientClient = null;
|
|
1831
2674
|
_walCheckpointTimer = null;
|
|
1832
2675
|
_daemonClient = null;
|
|
2676
|
+
_adapterClient = null;
|
|
1833
2677
|
initTurso = initDatabase;
|
|
1834
2678
|
disposeTurso = disposeDatabase;
|
|
1835
2679
|
}
|
|
@@ -1844,13 +2688,50 @@ var init_memory = __esm({
|
|
|
1844
2688
|
}
|
|
1845
2689
|
});
|
|
1846
2690
|
|
|
2691
|
+
// src/lib/daemon-auth.ts
|
|
2692
|
+
import crypto from "crypto";
|
|
2693
|
+
import path5 from "path";
|
|
2694
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
2695
|
+
function normalizeToken(token) {
|
|
2696
|
+
if (!token) return null;
|
|
2697
|
+
const trimmed = token.trim();
|
|
2698
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
2699
|
+
}
|
|
2700
|
+
function readDaemonToken() {
|
|
2701
|
+
try {
|
|
2702
|
+
if (!existsSync5(DAEMON_TOKEN_PATH)) return null;
|
|
2703
|
+
return normalizeToken(readFileSync4(DAEMON_TOKEN_PATH, "utf8"));
|
|
2704
|
+
} catch {
|
|
2705
|
+
return null;
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
function ensureDaemonToken(seed) {
|
|
2709
|
+
const existing = readDaemonToken();
|
|
2710
|
+
if (existing) return existing;
|
|
2711
|
+
const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
|
|
2712
|
+
ensurePrivateDirSync(EXE_AI_DIR);
|
|
2713
|
+
writeFileSync3(DAEMON_TOKEN_PATH, `${token}
|
|
2714
|
+
`, "utf8");
|
|
2715
|
+
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
2716
|
+
return token;
|
|
2717
|
+
}
|
|
2718
|
+
var DAEMON_TOKEN_PATH;
|
|
2719
|
+
var init_daemon_auth = __esm({
|
|
2720
|
+
"src/lib/daemon-auth.ts"() {
|
|
2721
|
+
"use strict";
|
|
2722
|
+
init_config();
|
|
2723
|
+
init_secure_files();
|
|
2724
|
+
DAEMON_TOKEN_PATH = path5.join(EXE_AI_DIR, "exed.token");
|
|
2725
|
+
}
|
|
2726
|
+
});
|
|
2727
|
+
|
|
1847
2728
|
// src/lib/exe-daemon-client.ts
|
|
1848
2729
|
import net from "net";
|
|
1849
|
-
import
|
|
2730
|
+
import os4 from "os";
|
|
1850
2731
|
import { spawn } from "child_process";
|
|
1851
2732
|
import { randomUUID } from "crypto";
|
|
1852
|
-
import { existsSync as
|
|
1853
|
-
import
|
|
2733
|
+
import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync5, openSync, closeSync, statSync } from "fs";
|
|
2734
|
+
import path6 from "path";
|
|
1854
2735
|
import { fileURLToPath } from "url";
|
|
1855
2736
|
function handleData(chunk) {
|
|
1856
2737
|
_buffer += chunk.toString();
|
|
@@ -1878,9 +2759,9 @@ function handleData(chunk) {
|
|
|
1878
2759
|
}
|
|
1879
2760
|
}
|
|
1880
2761
|
function cleanupStaleFiles() {
|
|
1881
|
-
if (
|
|
2762
|
+
if (existsSync6(PID_PATH)) {
|
|
1882
2763
|
try {
|
|
1883
|
-
const pid = parseInt(
|
|
2764
|
+
const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
|
|
1884
2765
|
if (pid > 0) {
|
|
1885
2766
|
try {
|
|
1886
2767
|
process.kill(pid, 0);
|
|
@@ -1901,17 +2782,17 @@ function cleanupStaleFiles() {
|
|
|
1901
2782
|
}
|
|
1902
2783
|
}
|
|
1903
2784
|
function findPackageRoot() {
|
|
1904
|
-
let dir =
|
|
1905
|
-
const { root } =
|
|
2785
|
+
let dir = path6.dirname(fileURLToPath(import.meta.url));
|
|
2786
|
+
const { root } = path6.parse(dir);
|
|
1906
2787
|
while (dir !== root) {
|
|
1907
|
-
if (
|
|
1908
|
-
dir =
|
|
2788
|
+
if (existsSync6(path6.join(dir, "package.json"))) return dir;
|
|
2789
|
+
dir = path6.dirname(dir);
|
|
1909
2790
|
}
|
|
1910
2791
|
return null;
|
|
1911
2792
|
}
|
|
1912
2793
|
function spawnDaemon() {
|
|
1913
|
-
const freeGB =
|
|
1914
|
-
const totalGB =
|
|
2794
|
+
const freeGB = os4.freemem() / (1024 * 1024 * 1024);
|
|
2795
|
+
const totalGB = os4.totalmem() / (1024 * 1024 * 1024);
|
|
1915
2796
|
if (totalGB <= 8) {
|
|
1916
2797
|
process.stderr.write(
|
|
1917
2798
|
`[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
|
|
@@ -1931,16 +2812,17 @@ function spawnDaemon() {
|
|
|
1931
2812
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
1932
2813
|
return;
|
|
1933
2814
|
}
|
|
1934
|
-
const daemonPath =
|
|
1935
|
-
if (!
|
|
2815
|
+
const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
2816
|
+
if (!existsSync6(daemonPath)) {
|
|
1936
2817
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
1937
2818
|
`);
|
|
1938
2819
|
return;
|
|
1939
2820
|
}
|
|
1940
2821
|
const resolvedPath = daemonPath;
|
|
2822
|
+
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
1941
2823
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
1942
2824
|
`);
|
|
1943
|
-
const logPath =
|
|
2825
|
+
const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
|
|
1944
2826
|
let stderrFd = "ignore";
|
|
1945
2827
|
try {
|
|
1946
2828
|
stderrFd = openSync(logPath, "a");
|
|
@@ -1958,7 +2840,8 @@ function spawnDaemon() {
|
|
|
1958
2840
|
TMUX_PANE: void 0,
|
|
1959
2841
|
// Prevents resolveExeSession() from scoping to one session
|
|
1960
2842
|
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
1961
|
-
EXE_DAEMON_PID: PID_PATH
|
|
2843
|
+
EXE_DAEMON_PID: PID_PATH,
|
|
2844
|
+
[DAEMON_TOKEN_ENV]: daemonToken
|
|
1962
2845
|
}
|
|
1963
2846
|
});
|
|
1964
2847
|
child.unref();
|
|
@@ -2068,13 +2951,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
2068
2951
|
return;
|
|
2069
2952
|
}
|
|
2070
2953
|
const id = randomUUID();
|
|
2954
|
+
const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
|
|
2071
2955
|
const timer = setTimeout(() => {
|
|
2072
2956
|
_pending.delete(id);
|
|
2073
2957
|
resolve({ error: "Request timeout" });
|
|
2074
2958
|
}, timeoutMs);
|
|
2075
2959
|
_pending.set(id, { resolve, timer });
|
|
2076
2960
|
try {
|
|
2077
|
-
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
2961
|
+
_socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
|
|
2078
2962
|
} catch {
|
|
2079
2963
|
clearTimeout(timer);
|
|
2080
2964
|
_pending.delete(id);
|
|
@@ -2091,74 +2975,123 @@ async function pingDaemon() {
|
|
|
2091
2975
|
return null;
|
|
2092
2976
|
}
|
|
2093
2977
|
function killAndRespawnDaemon() {
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2978
|
+
if (!acquireSpawnLock()) {
|
|
2979
|
+
process.stderr.write("[exed-client] Another process is already restarting daemon \u2014 skipping\n");
|
|
2980
|
+
if (_socket) {
|
|
2981
|
+
_socket.destroy();
|
|
2982
|
+
_socket = null;
|
|
2983
|
+
}
|
|
2984
|
+
_connected = false;
|
|
2985
|
+
_buffer = "";
|
|
2986
|
+
return;
|
|
2987
|
+
}
|
|
2988
|
+
try {
|
|
2989
|
+
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
2990
|
+
if (existsSync6(PID_PATH)) {
|
|
2991
|
+
try {
|
|
2992
|
+
const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
|
|
2993
|
+
if (pid > 0) {
|
|
2994
|
+
try {
|
|
2995
|
+
process.kill(pid, "SIGKILL");
|
|
2996
|
+
} catch {
|
|
2997
|
+
}
|
|
2102
2998
|
}
|
|
2999
|
+
} catch {
|
|
2103
3000
|
}
|
|
3001
|
+
}
|
|
3002
|
+
if (_socket) {
|
|
3003
|
+
_socket.destroy();
|
|
3004
|
+
_socket = null;
|
|
3005
|
+
}
|
|
3006
|
+
_connected = false;
|
|
3007
|
+
_buffer = "";
|
|
3008
|
+
try {
|
|
3009
|
+
unlinkSync2(PID_PATH);
|
|
2104
3010
|
} catch {
|
|
2105
3011
|
}
|
|
3012
|
+
try {
|
|
3013
|
+
unlinkSync2(SOCKET_PATH);
|
|
3014
|
+
} catch {
|
|
3015
|
+
}
|
|
3016
|
+
spawnDaemon();
|
|
3017
|
+
} finally {
|
|
3018
|
+
releaseSpawnLock();
|
|
2106
3019
|
}
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
_socket = null;
|
|
2110
|
-
}
|
|
2111
|
-
_connected = false;
|
|
2112
|
-
_buffer = "";
|
|
3020
|
+
}
|
|
3021
|
+
function isDaemonTooYoung() {
|
|
2113
3022
|
try {
|
|
2114
|
-
|
|
3023
|
+
const stat = statSync(PID_PATH);
|
|
3024
|
+
return Date.now() - stat.mtimeMs < MIN_DAEMON_AGE_MS;
|
|
2115
3025
|
} catch {
|
|
3026
|
+
return false;
|
|
2116
3027
|
}
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
3028
|
+
}
|
|
3029
|
+
async function retryThenRestart(doRequest, label) {
|
|
3030
|
+
const result = await doRequest();
|
|
3031
|
+
if (!result.error) {
|
|
3032
|
+
_consecutiveFailures = 0;
|
|
3033
|
+
return result;
|
|
3034
|
+
}
|
|
3035
|
+
_consecutiveFailures++;
|
|
3036
|
+
for (let i = 0; i < MAX_RETRIES_BEFORE_RESTART; i++) {
|
|
3037
|
+
const delayMs = RETRY_DELAYS_MS[i] ?? 5e3;
|
|
3038
|
+
process.stderr.write(`[exed-client] ${label} failed (${result.error}), retry ${i + 1}/${MAX_RETRIES_BEFORE_RESTART} in ${delayMs}ms
|
|
3039
|
+
`);
|
|
3040
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
3041
|
+
if (!_connected) {
|
|
3042
|
+
if (!await connectToSocket()) continue;
|
|
3043
|
+
}
|
|
3044
|
+
const retry = await doRequest();
|
|
3045
|
+
if (!retry.error) {
|
|
3046
|
+
_consecutiveFailures = 0;
|
|
3047
|
+
return retry;
|
|
3048
|
+
}
|
|
3049
|
+
_consecutiveFailures++;
|
|
2120
3050
|
}
|
|
2121
|
-
|
|
3051
|
+
if (isDaemonTooYoung()) {
|
|
3052
|
+
process.stderr.write(`[exed-client] ${label}: daemon too young (< ${MIN_DAEMON_AGE_MS / 1e3}s) \u2014 skipping restart
|
|
3053
|
+
`);
|
|
3054
|
+
return { error: result.error };
|
|
3055
|
+
}
|
|
3056
|
+
process.stderr.write(`[exed-client] ${label}: ${_consecutiveFailures} consecutive failures \u2014 restarting daemon
|
|
3057
|
+
`);
|
|
3058
|
+
killAndRespawnDaemon();
|
|
3059
|
+
const start = Date.now();
|
|
3060
|
+
let delay2 = 200;
|
|
3061
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
3062
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
3063
|
+
if (await connectToSocket()) break;
|
|
3064
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
3065
|
+
}
|
|
3066
|
+
if (!_connected) return { error: "Daemon restart failed" };
|
|
3067
|
+
const final = await doRequest();
|
|
3068
|
+
if (!final.error) _consecutiveFailures = 0;
|
|
3069
|
+
return final;
|
|
2122
3070
|
}
|
|
2123
3071
|
async function embedViaClient(text, priority = "high") {
|
|
2124
3072
|
if (!_connected && !await connectEmbedDaemon()) return null;
|
|
2125
3073
|
_requestCount++;
|
|
2126
3074
|
if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
|
|
2127
3075
|
const health = await pingDaemon();
|
|
2128
|
-
if (!health) {
|
|
3076
|
+
if (!health && !isDaemonTooYoung()) {
|
|
2129
3077
|
process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
|
|
2130
3078
|
`);
|
|
2131
3079
|
killAndRespawnDaemon();
|
|
2132
3080
|
const start = Date.now();
|
|
2133
|
-
let
|
|
3081
|
+
let d = 200;
|
|
2134
3082
|
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
2135
|
-
await new Promise((r) => setTimeout(r,
|
|
3083
|
+
await new Promise((r) => setTimeout(r, d));
|
|
2136
3084
|
if (await connectToSocket()) break;
|
|
2137
|
-
|
|
3085
|
+
d = Math.min(d * 2, 3e3);
|
|
2138
3086
|
}
|
|
2139
3087
|
if (!_connected) return null;
|
|
2140
3088
|
}
|
|
2141
3089
|
}
|
|
2142
|
-
const result = await
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
killAndRespawnDaemon();
|
|
2148
|
-
const start = Date.now();
|
|
2149
|
-
let delay2 = 200;
|
|
2150
|
-
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
2151
|
-
await new Promise((r) => setTimeout(r, delay2));
|
|
2152
|
-
if (await connectToSocket()) break;
|
|
2153
|
-
delay2 = Math.min(delay2 * 2, 3e3);
|
|
2154
|
-
}
|
|
2155
|
-
if (!_connected) return null;
|
|
2156
|
-
const retry = await sendRequest([text], priority);
|
|
2157
|
-
if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
|
|
2158
|
-
process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
|
|
2159
|
-
`);
|
|
2160
|
-
}
|
|
2161
|
-
return null;
|
|
3090
|
+
const result = await retryThenRestart(
|
|
3091
|
+
() => sendRequest([text], priority),
|
|
3092
|
+
"Embed"
|
|
3093
|
+
);
|
|
3094
|
+
return !result.error && result.vectors?.[0] ? result.vectors[0] : null;
|
|
2162
3095
|
}
|
|
2163
3096
|
function disconnectClient() {
|
|
2164
3097
|
if (_socket) {
|
|
@@ -2173,22 +3106,28 @@ function disconnectClient() {
|
|
|
2173
3106
|
entry.resolve({ error: "Client disconnected" });
|
|
2174
3107
|
}
|
|
2175
3108
|
}
|
|
2176
|
-
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;
|
|
3109
|
+
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, DAEMON_TOKEN_ENV, _socket, _connected, _buffer, _requestCount, _consecutiveFailures, HEALTH_CHECK_INTERVAL, MAX_RETRIES_BEFORE_RESTART, RETRY_DELAYS_MS, MIN_DAEMON_AGE_MS, _pending, MAX_BUFFER;
|
|
2177
3110
|
var init_exe_daemon_client = __esm({
|
|
2178
3111
|
"src/lib/exe-daemon-client.ts"() {
|
|
2179
3112
|
"use strict";
|
|
2180
3113
|
init_config();
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
3114
|
+
init_daemon_auth();
|
|
3115
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
|
|
3116
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
|
|
3117
|
+
SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
2184
3118
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
2185
3119
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
2186
3120
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
3121
|
+
DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
|
|
2187
3122
|
_socket = null;
|
|
2188
3123
|
_connected = false;
|
|
2189
3124
|
_buffer = "";
|
|
2190
3125
|
_requestCount = 0;
|
|
3126
|
+
_consecutiveFailures = 0;
|
|
2191
3127
|
HEALTH_CHECK_INTERVAL = 100;
|
|
3128
|
+
MAX_RETRIES_BEFORE_RESTART = 3;
|
|
3129
|
+
RETRY_DELAYS_MS = [1e3, 3e3, 5e3];
|
|
3130
|
+
MIN_DAEMON_AGE_MS = 3e4;
|
|
2192
3131
|
_pending = /* @__PURE__ */ new Map();
|
|
2193
3132
|
MAX_BUFFER = 1e7;
|
|
2194
3133
|
}
|
|
@@ -2231,10 +3170,10 @@ async function disposeEmbedder() {
|
|
|
2231
3170
|
async function embedDirect(text) {
|
|
2232
3171
|
const llamaCpp = await import("node-llama-cpp");
|
|
2233
3172
|
const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
2234
|
-
const { existsSync:
|
|
2235
|
-
const
|
|
2236
|
-
const modelPath =
|
|
2237
|
-
if (!
|
|
3173
|
+
const { existsSync: existsSync19 } = await import("fs");
|
|
3174
|
+
const path23 = await import("path");
|
|
3175
|
+
const modelPath = path23.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
|
|
3176
|
+
if (!existsSync19(modelPath)) {
|
|
2238
3177
|
throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
|
|
2239
3178
|
}
|
|
2240
3179
|
const llama = await llamaCpp.getLlama();
|
|
@@ -2264,14 +3203,14 @@ var init_embedder = __esm({
|
|
|
2264
3203
|
|
|
2265
3204
|
// src/lib/keychain.ts
|
|
2266
3205
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
2267
|
-
import { existsSync as
|
|
2268
|
-
import
|
|
2269
|
-
import
|
|
3206
|
+
import { existsSync as existsSync7 } from "fs";
|
|
3207
|
+
import path7 from "path";
|
|
3208
|
+
import os5 from "os";
|
|
2270
3209
|
function getKeyDir() {
|
|
2271
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
3210
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path7.join(os5.homedir(), ".exe-os");
|
|
2272
3211
|
}
|
|
2273
3212
|
function getKeyPath() {
|
|
2274
|
-
return
|
|
3213
|
+
return path7.join(getKeyDir(), "master.key");
|
|
2275
3214
|
}
|
|
2276
3215
|
async function tryKeytar() {
|
|
2277
3216
|
try {
|
|
@@ -2292,9 +3231,9 @@ async function getMasterKey() {
|
|
|
2292
3231
|
}
|
|
2293
3232
|
}
|
|
2294
3233
|
const keyPath = getKeyPath();
|
|
2295
|
-
if (!
|
|
3234
|
+
if (!existsSync7(keyPath)) {
|
|
2296
3235
|
process.stderr.write(
|
|
2297
|
-
`[keychain] Key not found at ${keyPath} (HOME=${
|
|
3236
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os5.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
2298
3237
|
`
|
|
2299
3238
|
);
|
|
2300
3239
|
return null;
|
|
@@ -2324,6 +3263,7 @@ var shard_manager_exports = {};
|
|
|
2324
3263
|
__export(shard_manager_exports, {
|
|
2325
3264
|
disposeShards: () => disposeShards,
|
|
2326
3265
|
ensureShardSchema: () => ensureShardSchema,
|
|
3266
|
+
getOpenShardCount: () => getOpenShardCount,
|
|
2327
3267
|
getReadyShardClient: () => getReadyShardClient,
|
|
2328
3268
|
getShardClient: () => getShardClient,
|
|
2329
3269
|
getShardsDir: () => getShardsDir,
|
|
@@ -2332,15 +3272,18 @@ __export(shard_manager_exports, {
|
|
|
2332
3272
|
listShards: () => listShards,
|
|
2333
3273
|
shardExists: () => shardExists
|
|
2334
3274
|
});
|
|
2335
|
-
import
|
|
2336
|
-
import { existsSync as
|
|
3275
|
+
import path8 from "path";
|
|
3276
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync2, readdirSync } from "fs";
|
|
2337
3277
|
import { createClient as createClient2 } from "@libsql/client";
|
|
2338
3278
|
function initShardManager(encryptionKey) {
|
|
2339
3279
|
_encryptionKey = encryptionKey;
|
|
2340
|
-
if (!
|
|
2341
|
-
|
|
3280
|
+
if (!existsSync8(SHARDS_DIR)) {
|
|
3281
|
+
mkdirSync2(SHARDS_DIR, { recursive: true });
|
|
2342
3282
|
}
|
|
2343
3283
|
_shardingEnabled = true;
|
|
3284
|
+
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
3285
|
+
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
3286
|
+
_evictionTimer.unref();
|
|
2344
3287
|
}
|
|
2345
3288
|
function isShardingEnabled() {
|
|
2346
3289
|
return _shardingEnabled;
|
|
@@ -2357,21 +3300,28 @@ function getShardClient(projectName) {
|
|
|
2357
3300
|
throw new Error(`Invalid project name for shard: "${projectName}"`);
|
|
2358
3301
|
}
|
|
2359
3302
|
const cached = _shards.get(safeName);
|
|
2360
|
-
if (cached)
|
|
2361
|
-
|
|
3303
|
+
if (cached) {
|
|
3304
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
3305
|
+
return cached;
|
|
3306
|
+
}
|
|
3307
|
+
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
3308
|
+
evictLRU();
|
|
3309
|
+
}
|
|
3310
|
+
const dbPath = path8.join(SHARDS_DIR, `${safeName}.db`);
|
|
2362
3311
|
const client = createClient2({
|
|
2363
3312
|
url: `file:${dbPath}`,
|
|
2364
3313
|
encryptionKey: _encryptionKey
|
|
2365
3314
|
});
|
|
2366
3315
|
_shards.set(safeName, client);
|
|
3316
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
2367
3317
|
return client;
|
|
2368
3318
|
}
|
|
2369
3319
|
function shardExists(projectName) {
|
|
2370
3320
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
2371
|
-
return
|
|
3321
|
+
return existsSync8(path8.join(SHARDS_DIR, `${safeName}.db`));
|
|
2372
3322
|
}
|
|
2373
3323
|
function listShards() {
|
|
2374
|
-
if (!
|
|
3324
|
+
if (!existsSync8(SHARDS_DIR)) return [];
|
|
2375
3325
|
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
2376
3326
|
}
|
|
2377
3327
|
async function ensureShardSchema(client) {
|
|
@@ -2423,6 +3373,8 @@ async function ensureShardSchema(client) {
|
|
|
2423
3373
|
for (const col of [
|
|
2424
3374
|
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
2425
3375
|
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
3376
|
+
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
3377
|
+
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
2426
3378
|
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
2427
3379
|
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
2428
3380
|
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
@@ -2445,7 +3397,23 @@ async function ensureShardSchema(client) {
|
|
|
2445
3397
|
// MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
|
|
2446
3398
|
"ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
|
|
2447
3399
|
"ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
|
|
2448
|
-
"ALTER TABLE memories ADD COLUMN trajectory TEXT"
|
|
3400
|
+
"ALTER TABLE memories ADD COLUMN trajectory TEXT",
|
|
3401
|
+
// Metadata enrichment columns (must match database.ts)
|
|
3402
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
3403
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
3404
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
3405
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
3406
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
3407
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
3408
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
3409
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
3410
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
3411
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
3412
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
3413
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
3414
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
3415
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
3416
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
2449
3417
|
]) {
|
|
2450
3418
|
try {
|
|
2451
3419
|
await client.execute(col);
|
|
@@ -2544,21 +3512,69 @@ async function getReadyShardClient(projectName) {
|
|
|
2544
3512
|
await ensureShardSchema(client);
|
|
2545
3513
|
return client;
|
|
2546
3514
|
}
|
|
3515
|
+
function evictLRU() {
|
|
3516
|
+
let oldest = null;
|
|
3517
|
+
let oldestTime = Infinity;
|
|
3518
|
+
for (const [name, time] of _shardLastAccess) {
|
|
3519
|
+
if (time < oldestTime) {
|
|
3520
|
+
oldestTime = time;
|
|
3521
|
+
oldest = name;
|
|
3522
|
+
}
|
|
3523
|
+
}
|
|
3524
|
+
if (oldest) {
|
|
3525
|
+
const client = _shards.get(oldest);
|
|
3526
|
+
if (client) {
|
|
3527
|
+
client.close();
|
|
3528
|
+
}
|
|
3529
|
+
_shards.delete(oldest);
|
|
3530
|
+
_shardLastAccess.delete(oldest);
|
|
3531
|
+
}
|
|
3532
|
+
}
|
|
3533
|
+
function evictIdleShards() {
|
|
3534
|
+
const now = Date.now();
|
|
3535
|
+
const toEvict = [];
|
|
3536
|
+
for (const [name, lastAccess] of _shardLastAccess) {
|
|
3537
|
+
if (now - lastAccess > SHARD_IDLE_MS) {
|
|
3538
|
+
toEvict.push(name);
|
|
3539
|
+
}
|
|
3540
|
+
}
|
|
3541
|
+
for (const name of toEvict) {
|
|
3542
|
+
const client = _shards.get(name);
|
|
3543
|
+
if (client) {
|
|
3544
|
+
client.close();
|
|
3545
|
+
}
|
|
3546
|
+
_shards.delete(name);
|
|
3547
|
+
_shardLastAccess.delete(name);
|
|
3548
|
+
}
|
|
3549
|
+
}
|
|
3550
|
+
function getOpenShardCount() {
|
|
3551
|
+
return _shards.size;
|
|
3552
|
+
}
|
|
2547
3553
|
function disposeShards() {
|
|
3554
|
+
if (_evictionTimer) {
|
|
3555
|
+
clearInterval(_evictionTimer);
|
|
3556
|
+
_evictionTimer = null;
|
|
3557
|
+
}
|
|
2548
3558
|
for (const [, client] of _shards) {
|
|
2549
3559
|
client.close();
|
|
2550
3560
|
}
|
|
2551
3561
|
_shards.clear();
|
|
3562
|
+
_shardLastAccess.clear();
|
|
2552
3563
|
_shardingEnabled = false;
|
|
2553
3564
|
_encryptionKey = null;
|
|
2554
3565
|
}
|
|
2555
|
-
var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
|
|
3566
|
+
var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
|
|
2556
3567
|
var init_shard_manager = __esm({
|
|
2557
3568
|
"src/lib/shard-manager.ts"() {
|
|
2558
3569
|
"use strict";
|
|
2559
3570
|
init_config();
|
|
2560
|
-
SHARDS_DIR =
|
|
3571
|
+
SHARDS_DIR = path8.join(EXE_AI_DIR, "shards");
|
|
3572
|
+
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
3573
|
+
MAX_OPEN_SHARDS = 10;
|
|
3574
|
+
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
2561
3575
|
_shards = /* @__PURE__ */ new Map();
|
|
3576
|
+
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
3577
|
+
_evictionTimer = null;
|
|
2562
3578
|
_encryptionKey = null;
|
|
2563
3579
|
_shardingEnabled = false;
|
|
2564
3580
|
}
|
|
@@ -3330,8 +4346,8 @@ __export(wiki_client_exports, {
|
|
|
3330
4346
|
listDocuments: () => listDocuments,
|
|
3331
4347
|
listWorkspaces: () => listWorkspaces
|
|
3332
4348
|
});
|
|
3333
|
-
async function wikiFetch(config2,
|
|
3334
|
-
const url = `${config2.baseUrl}/api/v1${
|
|
4349
|
+
async function wikiFetch(config2, path23, method = "GET", body) {
|
|
4350
|
+
const url = `${config2.baseUrl}/api/v1${path23}`;
|
|
3335
4351
|
const headers = {
|
|
3336
4352
|
Authorization: `Bearer ${config2.apiKey}`,
|
|
3337
4353
|
"Content-Type": "application/json"
|
|
@@ -3364,7 +4380,7 @@ async function wikiFetch(config2, path21, method = "GET", body) {
|
|
|
3364
4380
|
}
|
|
3365
4381
|
}
|
|
3366
4382
|
if (!response.ok) {
|
|
3367
|
-
throw new Error(`Wiki API ${method} ${
|
|
4383
|
+
throw new Error(`Wiki API ${method} ${path23}: ${response.status} ${response.statusText}`);
|
|
3368
4384
|
}
|
|
3369
4385
|
return response.json();
|
|
3370
4386
|
} finally {
|
|
@@ -3657,13 +4673,13 @@ __export(graph_rag_exports, {
|
|
|
3657
4673
|
resolveAlias: () => resolveAlias,
|
|
3658
4674
|
storeExtraction: () => storeExtraction
|
|
3659
4675
|
});
|
|
3660
|
-
import
|
|
4676
|
+
import crypto2 from "crypto";
|
|
3661
4677
|
function normalizeEntityName(name) {
|
|
3662
4678
|
return name.replace(/\s*\([^)]*\)\s*/g, "").trim().toLowerCase();
|
|
3663
4679
|
}
|
|
3664
4680
|
function entityId(name, type) {
|
|
3665
4681
|
const normalized = normalizeEntityName(name);
|
|
3666
|
-
return
|
|
4682
|
+
return crypto2.createHash("sha256").update(`${normalized}::${type.toLowerCase()}`).digest("hex").slice(0, 16);
|
|
3667
4683
|
}
|
|
3668
4684
|
async function resolveAlias(client, name) {
|
|
3669
4685
|
const normalized = normalizeEntityName(name);
|
|
@@ -3913,7 +4929,7 @@ async function storeExtraction(client, extraction, memoryId, timestamp) {
|
|
|
3913
4929
|
const targetAlias = await resolveAlias(client, r.target);
|
|
3914
4930
|
const sourceId = sourceAlias ?? entityId(r.source, r.sourceType);
|
|
3915
4931
|
const targetId = targetAlias ?? entityId(r.target, r.targetType);
|
|
3916
|
-
const relId =
|
|
4932
|
+
const relId = crypto2.randomUUID().slice(0, 16);
|
|
3917
4933
|
try {
|
|
3918
4934
|
await client.execute({
|
|
3919
4935
|
sql: `INSERT OR IGNORE INTO entities (id, name, type, first_seen, last_seen)
|
|
@@ -3976,7 +4992,7 @@ async function storeExtraction(client, extraction, memoryId, timestamp) {
|
|
|
3976
4992
|
}
|
|
3977
4993
|
}
|
|
3978
4994
|
for (const h of extraction.hyperedges) {
|
|
3979
|
-
const hId =
|
|
4995
|
+
const hId = crypto2.randomUUID().slice(0, 16);
|
|
3980
4996
|
try {
|
|
3981
4997
|
await client.execute({
|
|
3982
4998
|
sql: `INSERT OR IGNORE INTO hyperedges (id, label, relation, confidence, timestamp)
|
|
@@ -4040,7 +5056,7 @@ async function extractBatch(client, batchSize = 50, model = "claude-haiku-4-5-20
|
|
|
4040
5056
|
totalEntities += stored.entitiesStored;
|
|
4041
5057
|
totalRelationships += stored.relationshipsStored;
|
|
4042
5058
|
}
|
|
4043
|
-
const contentHash =
|
|
5059
|
+
const contentHash = crypto2.createHash("sha256").update(rawContent).digest("hex").slice(0, 32);
|
|
4044
5060
|
await client.execute({
|
|
4045
5061
|
sql: "UPDATE memories SET graph_extracted = 1, content_hash = ?, graph_extracted_hash = ? WHERE id = ?",
|
|
4046
5062
|
args: [contentHash, contentHash, memoryId]
|
|
@@ -4393,9 +5409,12 @@ __export(license_exports, {
|
|
|
4393
5409
|
stopLicenseRevalidation: () => stopLicenseRevalidation,
|
|
4394
5410
|
validateLicense: () => validateLicense
|
|
4395
5411
|
});
|
|
4396
|
-
import { readFileSync as
|
|
5412
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync9, mkdirSync as mkdirSync3 } from "fs";
|
|
4397
5413
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
4398
|
-
import
|
|
5414
|
+
import { createRequire as createRequire2 } from "module";
|
|
5415
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
5416
|
+
import os6 from "os";
|
|
5417
|
+
import path9 from "path";
|
|
4399
5418
|
import { jwtVerify, importSPKI } from "jose";
|
|
4400
5419
|
async function fetchRetry(url, init) {
|
|
4401
5420
|
try {
|
|
@@ -4406,37 +5425,37 @@ async function fetchRetry(url, init) {
|
|
|
4406
5425
|
}
|
|
4407
5426
|
}
|
|
4408
5427
|
function loadDeviceId() {
|
|
4409
|
-
const deviceJsonPath =
|
|
5428
|
+
const deviceJsonPath = path9.join(EXE_AI_DIR, "device.json");
|
|
4410
5429
|
try {
|
|
4411
|
-
if (
|
|
4412
|
-
const data = JSON.parse(
|
|
5430
|
+
if (existsSync9(deviceJsonPath)) {
|
|
5431
|
+
const data = JSON.parse(readFileSync6(deviceJsonPath, "utf8"));
|
|
4413
5432
|
if (data.deviceId) return data.deviceId;
|
|
4414
5433
|
}
|
|
4415
5434
|
} catch {
|
|
4416
5435
|
}
|
|
4417
5436
|
try {
|
|
4418
|
-
if (
|
|
4419
|
-
const id2 =
|
|
5437
|
+
if (existsSync9(DEVICE_ID_PATH)) {
|
|
5438
|
+
const id2 = readFileSync6(DEVICE_ID_PATH, "utf8").trim();
|
|
4420
5439
|
if (id2) return id2;
|
|
4421
5440
|
}
|
|
4422
5441
|
} catch {
|
|
4423
5442
|
}
|
|
4424
5443
|
const id = randomUUID3();
|
|
4425
|
-
|
|
4426
|
-
|
|
5444
|
+
mkdirSync3(EXE_AI_DIR, { recursive: true });
|
|
5445
|
+
writeFileSync4(DEVICE_ID_PATH, id, "utf8");
|
|
4427
5446
|
return id;
|
|
4428
5447
|
}
|
|
4429
5448
|
function loadLicense() {
|
|
4430
5449
|
try {
|
|
4431
|
-
if (!
|
|
4432
|
-
return
|
|
5450
|
+
if (!existsSync9(LICENSE_PATH)) return null;
|
|
5451
|
+
return readFileSync6(LICENSE_PATH, "utf8").trim();
|
|
4433
5452
|
} catch {
|
|
4434
5453
|
return null;
|
|
4435
5454
|
}
|
|
4436
5455
|
}
|
|
4437
5456
|
function saveLicense(apiKey) {
|
|
4438
|
-
|
|
4439
|
-
|
|
5457
|
+
mkdirSync3(EXE_AI_DIR, { recursive: true });
|
|
5458
|
+
writeFileSync4(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
|
|
4440
5459
|
}
|
|
4441
5460
|
async function verifyLicenseJwt(token) {
|
|
4442
5461
|
try {
|
|
@@ -4462,8 +5481,8 @@ async function verifyLicenseJwt(token) {
|
|
|
4462
5481
|
}
|
|
4463
5482
|
async function getCachedLicense() {
|
|
4464
5483
|
try {
|
|
4465
|
-
if (!
|
|
4466
|
-
const raw = JSON.parse(
|
|
5484
|
+
if (!existsSync9(CACHE_PATH)) return null;
|
|
5485
|
+
const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
|
|
4467
5486
|
if (!raw.token || typeof raw.token !== "string") return null;
|
|
4468
5487
|
return await verifyLicenseJwt(raw.token);
|
|
4469
5488
|
} catch {
|
|
@@ -4472,8 +5491,8 @@ async function getCachedLicense() {
|
|
|
4472
5491
|
}
|
|
4473
5492
|
function readCachedToken() {
|
|
4474
5493
|
try {
|
|
4475
|
-
if (!
|
|
4476
|
-
const raw = JSON.parse(
|
|
5494
|
+
if (!existsSync9(CACHE_PATH)) return null;
|
|
5495
|
+
const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
|
|
4477
5496
|
return typeof raw.token === "string" ? raw.token : null;
|
|
4478
5497
|
} catch {
|
|
4479
5498
|
return null;
|
|
@@ -4495,68 +5514,142 @@ function getRawCachedPlan() {
|
|
|
4495
5514
|
return {
|
|
4496
5515
|
valid: true,
|
|
4497
5516
|
plan,
|
|
4498
|
-
email: payload.sub ?? "",
|
|
4499
|
-
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
4500
|
-
deviceLimit: limits.devices,
|
|
4501
|
-
employeeLimit: limits.employees,
|
|
4502
|
-
memoryLimit: limits.memories
|
|
5517
|
+
email: payload.sub ?? "",
|
|
5518
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
5519
|
+
deviceLimit: limits.devices,
|
|
5520
|
+
employeeLimit: limits.employees,
|
|
5521
|
+
memoryLimit: limits.memories
|
|
5522
|
+
};
|
|
5523
|
+
} catch {
|
|
5524
|
+
return null;
|
|
5525
|
+
}
|
|
5526
|
+
}
|
|
5527
|
+
function cacheResponse(token) {
|
|
5528
|
+
try {
|
|
5529
|
+
writeFileSync4(CACHE_PATH, JSON.stringify({ token }), "utf8");
|
|
5530
|
+
} catch {
|
|
5531
|
+
}
|
|
5532
|
+
}
|
|
5533
|
+
function loadPrismaForLicense() {
|
|
5534
|
+
if (_prismaFailed) return null;
|
|
5535
|
+
const dbUrl = process.env.DATABASE_URL;
|
|
5536
|
+
if (!dbUrl) {
|
|
5537
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path9.join(os6.homedir(), "exe-db");
|
|
5538
|
+
if (!existsSync9(path9.join(exeDbRoot, "package.json"))) {
|
|
5539
|
+
_prismaFailed = true;
|
|
5540
|
+
return null;
|
|
5541
|
+
}
|
|
5542
|
+
}
|
|
5543
|
+
if (!_prismaPromise) {
|
|
5544
|
+
_prismaPromise = (async () => {
|
|
5545
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
5546
|
+
if (explicitPath) {
|
|
5547
|
+
const mod2 = await import(pathToFileURL2(explicitPath).href);
|
|
5548
|
+
const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
|
|
5549
|
+
if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
|
|
5550
|
+
return new Ctor2();
|
|
5551
|
+
}
|
|
5552
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path9.join(os6.homedir(), "exe-db");
|
|
5553
|
+
const req = createRequire2(path9.join(exeDbRoot, "package.json"));
|
|
5554
|
+
const entry = req.resolve("@prisma/client");
|
|
5555
|
+
const mod = await import(pathToFileURL2(entry).href);
|
|
5556
|
+
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
5557
|
+
if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
|
|
5558
|
+
return new Ctor();
|
|
5559
|
+
})().catch((err) => {
|
|
5560
|
+
_prismaFailed = true;
|
|
5561
|
+
_prismaPromise = null;
|
|
5562
|
+
throw err;
|
|
5563
|
+
});
|
|
5564
|
+
}
|
|
5565
|
+
return _prismaPromise;
|
|
5566
|
+
}
|
|
5567
|
+
async function validateViaPostgres(apiKey) {
|
|
5568
|
+
const loader = loadPrismaForLicense();
|
|
5569
|
+
if (!loader) return null;
|
|
5570
|
+
try {
|
|
5571
|
+
const prisma = await loader;
|
|
5572
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
5573
|
+
`SELECT plan, email, status, device_limit, employee_limit, memory_limit, expires_at
|
|
5574
|
+
FROM billing.licenses WHERE key = $1 LIMIT 1`,
|
|
5575
|
+
apiKey
|
|
5576
|
+
);
|
|
5577
|
+
if (!rows || rows.length === 0) return null;
|
|
5578
|
+
const row = rows[0];
|
|
5579
|
+
if (row.status !== "active") return null;
|
|
5580
|
+
if (row.expires_at && new Date(row.expires_at) < /* @__PURE__ */ new Date()) return null;
|
|
5581
|
+
const plan = row.plan;
|
|
5582
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
5583
|
+
return {
|
|
5584
|
+
valid: true,
|
|
5585
|
+
plan,
|
|
5586
|
+
email: row.email,
|
|
5587
|
+
expiresAt: row.expires_at ? new Date(row.expires_at).toISOString() : null,
|
|
5588
|
+
deviceLimit: row.device_limit ?? limits.devices,
|
|
5589
|
+
employeeLimit: row.employee_limit ?? limits.employees,
|
|
5590
|
+
memoryLimit: row.memory_limit ?? limits.memories
|
|
4503
5591
|
};
|
|
4504
5592
|
} catch {
|
|
4505
5593
|
return null;
|
|
4506
5594
|
}
|
|
4507
5595
|
}
|
|
4508
|
-
function
|
|
5596
|
+
async function validateViaCFWorker(apiKey, deviceId) {
|
|
4509
5597
|
try {
|
|
4510
|
-
|
|
5598
|
+
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
5599
|
+
method: "POST",
|
|
5600
|
+
headers: { "Content-Type": "application/json" },
|
|
5601
|
+
body: JSON.stringify({ apiKey, deviceId }),
|
|
5602
|
+
signal: AbortSignal.timeout(1e4)
|
|
5603
|
+
});
|
|
5604
|
+
if (!res.ok) return null;
|
|
5605
|
+
const data = await res.json();
|
|
5606
|
+
if (data.error === "device_limit_exceeded") return null;
|
|
5607
|
+
if (!data.valid) return null;
|
|
5608
|
+
if (data.token) {
|
|
5609
|
+
cacheResponse(data.token);
|
|
5610
|
+
const verified = await verifyLicenseJwt(data.token);
|
|
5611
|
+
if (verified) return verified;
|
|
5612
|
+
}
|
|
5613
|
+
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
5614
|
+
return {
|
|
5615
|
+
valid: data.valid,
|
|
5616
|
+
plan: data.plan,
|
|
5617
|
+
email: data.email,
|
|
5618
|
+
expiresAt: data.expiresAt,
|
|
5619
|
+
deviceLimit: limits.devices,
|
|
5620
|
+
employeeLimit: limits.employees,
|
|
5621
|
+
memoryLimit: limits.memories
|
|
5622
|
+
};
|
|
4511
5623
|
} catch {
|
|
5624
|
+
return null;
|
|
4512
5625
|
}
|
|
4513
5626
|
}
|
|
4514
5627
|
async function validateLicense(apiKey, deviceId) {
|
|
4515
5628
|
const did = deviceId ?? loadDeviceId();
|
|
5629
|
+
const pgResult = await validateViaPostgres(apiKey);
|
|
5630
|
+
if (pgResult) {
|
|
5631
|
+
try {
|
|
5632
|
+
writeFileSync4(CACHE_PATH, JSON.stringify({ pgLicense: pgResult, ts: Date.now() }), "utf8");
|
|
5633
|
+
} catch {
|
|
5634
|
+
}
|
|
5635
|
+
return pgResult;
|
|
5636
|
+
}
|
|
5637
|
+
const cfResult = await validateViaCFWorker(apiKey, did);
|
|
5638
|
+
if (cfResult) return cfResult;
|
|
5639
|
+
const cached = await getCachedLicense();
|
|
5640
|
+
if (cached) return cached;
|
|
4516
5641
|
try {
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
});
|
|
4523
|
-
if (res.ok) {
|
|
4524
|
-
const data = await res.json();
|
|
4525
|
-
if (data.error === "device_limit_exceeded") {
|
|
4526
|
-
const cached2 = await getCachedLicense();
|
|
4527
|
-
if (cached2) return cached2;
|
|
4528
|
-
const raw2 = getRawCachedPlan();
|
|
4529
|
-
if (raw2) return { ...raw2, valid: false };
|
|
4530
|
-
return { ...FREE_LICENSE, valid: false, plan: "free" };
|
|
4531
|
-
}
|
|
4532
|
-
if (data.token) {
|
|
4533
|
-
cacheResponse(data.token);
|
|
4534
|
-
const verified = await verifyLicenseJwt(data.token);
|
|
4535
|
-
if (verified) return verified;
|
|
4536
|
-
}
|
|
4537
|
-
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
4538
|
-
return {
|
|
4539
|
-
valid: data.valid,
|
|
4540
|
-
plan: data.plan,
|
|
4541
|
-
email: data.email,
|
|
4542
|
-
expiresAt: data.expiresAt,
|
|
4543
|
-
deviceLimit: limits.devices,
|
|
4544
|
-
employeeLimit: limits.employees,
|
|
4545
|
-
memoryLimit: limits.memories
|
|
4546
|
-
};
|
|
5642
|
+
if (existsSync9(CACHE_PATH)) {
|
|
5643
|
+
const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
|
|
5644
|
+
if (raw.pgLicense && raw.ts && Date.now() - raw.ts < 7 * 24 * 60 * 60 * 1e3) {
|
|
5645
|
+
return raw.pgLicense;
|
|
5646
|
+
}
|
|
4547
5647
|
}
|
|
4548
|
-
const cached = await getCachedLicense();
|
|
4549
|
-
if (cached) return cached;
|
|
4550
|
-
const raw = getRawCachedPlan();
|
|
4551
|
-
if (raw) return raw;
|
|
4552
|
-
return { ...FREE_LICENSE, valid: false, plan: "free" };
|
|
4553
5648
|
} catch {
|
|
4554
|
-
const cached = await getCachedLicense();
|
|
4555
|
-
if (cached) return cached;
|
|
4556
|
-
const rawFallback = getRawCachedPlan();
|
|
4557
|
-
if (rawFallback) return rawFallback;
|
|
4558
|
-
return { ...FREE_LICENSE, valid: false, error: "offline" };
|
|
4559
5649
|
}
|
|
5650
|
+
const rawFallback = getRawCachedPlan();
|
|
5651
|
+
if (rawFallback) return rawFallback;
|
|
5652
|
+
return { ...FREE_LICENSE, valid: false };
|
|
4560
5653
|
}
|
|
4561
5654
|
function getCacheAgeMs() {
|
|
4562
5655
|
try {
|
|
@@ -4571,9 +5664,9 @@ async function checkLicense() {
|
|
|
4571
5664
|
let key = loadLicense();
|
|
4572
5665
|
if (!key) {
|
|
4573
5666
|
try {
|
|
4574
|
-
const configPath =
|
|
4575
|
-
if (
|
|
4576
|
-
const raw = JSON.parse(
|
|
5667
|
+
const configPath = path9.join(EXE_AI_DIR, "config.json");
|
|
5668
|
+
if (existsSync9(configPath)) {
|
|
5669
|
+
const raw = JSON.parse(readFileSync6(configPath, "utf8"));
|
|
4577
5670
|
const cloud = raw.cloud;
|
|
4578
5671
|
if (cloud?.apiKey) {
|
|
4579
5672
|
key = cloud.apiKey;
|
|
@@ -4727,14 +5820,14 @@ function stopLicenseRevalidation() {
|
|
|
4727
5820
|
_revalTimer = null;
|
|
4728
5821
|
}
|
|
4729
5822
|
}
|
|
4730
|
-
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, RETRY_DELAY_MS, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, CACHE_MAX_AGE_MS, _revalTimer;
|
|
5823
|
+
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, RETRY_DELAY_MS, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, _prismaPromise, _prismaFailed, CACHE_MAX_AGE_MS, _revalTimer;
|
|
4731
5824
|
var init_license = __esm({
|
|
4732
5825
|
"src/lib/license.ts"() {
|
|
4733
5826
|
"use strict";
|
|
4734
5827
|
init_config();
|
|
4735
|
-
LICENSE_PATH =
|
|
4736
|
-
CACHE_PATH =
|
|
4737
|
-
DEVICE_ID_PATH =
|
|
5828
|
+
LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
|
|
5829
|
+
CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
|
|
5830
|
+
DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
|
|
4738
5831
|
API_BASE = "https://askexe.com/cloud";
|
|
4739
5832
|
RETRY_DELAY_MS = 500;
|
|
4740
5833
|
LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
@@ -4758,6 +5851,8 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
|
4758
5851
|
employeeLimit: 1,
|
|
4759
5852
|
memoryLimit: 5e3
|
|
4760
5853
|
};
|
|
5854
|
+
_prismaPromise = null;
|
|
5855
|
+
_prismaFailed = false;
|
|
4761
5856
|
CACHE_MAX_AGE_MS = 36e5;
|
|
4762
5857
|
_revalTimer = null;
|
|
4763
5858
|
}
|
|
@@ -4772,7 +5867,7 @@ __export(whatsapp_exports, {
|
|
|
4772
5867
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
4773
5868
|
import { homedir } from "os";
|
|
4774
5869
|
import { join } from "path";
|
|
4775
|
-
import { mkdirSync as
|
|
5870
|
+
import { mkdirSync as mkdirSync4 } from "fs";
|
|
4776
5871
|
function calculateBackoff(retryCount) {
|
|
4777
5872
|
const base = Math.min(
|
|
4778
5873
|
INITIAL_BACKOFF_MS * BACKOFF_MULTIPLIER ** retryCount,
|
|
@@ -4802,7 +5897,7 @@ var init_whatsapp = __esm({
|
|
|
4802
5897
|
disconnectedAt = 0;
|
|
4803
5898
|
async connect(config2) {
|
|
4804
5899
|
this.authDir = config2.credentials.authDir ?? AUTH_DIR;
|
|
4805
|
-
|
|
5900
|
+
mkdirSync4(this.authDir, { recursive: true });
|
|
4806
5901
|
const baileys = await import("@whiskeysockets/baileys");
|
|
4807
5902
|
const { makeWASocket, useMultiFileAuthState, fetchLatestBaileysVersion, DisconnectReason, makeCacheableSignalKeyStore } = baileys;
|
|
4808
5903
|
const { state, saveCreds } = await useMultiFileAuthState(this.authDir);
|
|
@@ -5632,16 +6727,16 @@ __export(imessage_exports, {
|
|
|
5632
6727
|
});
|
|
5633
6728
|
import { execFile } from "child_process";
|
|
5634
6729
|
import { promisify } from "util";
|
|
5635
|
-
import
|
|
5636
|
-
import
|
|
6730
|
+
import os7 from "os";
|
|
6731
|
+
import path10 from "path";
|
|
5637
6732
|
var execFileAsync, POLL_INTERVAL_MS, MESSAGES_DB_PATH, IMessageAdapter;
|
|
5638
6733
|
var init_imessage = __esm({
|
|
5639
6734
|
"src/gateway/adapters/imessage.ts"() {
|
|
5640
6735
|
"use strict";
|
|
5641
6736
|
execFileAsync = promisify(execFile);
|
|
5642
6737
|
POLL_INTERVAL_MS = 5e3;
|
|
5643
|
-
MESSAGES_DB_PATH =
|
|
5644
|
-
process.env.HOME ??
|
|
6738
|
+
MESSAGES_DB_PATH = path10.join(
|
|
6739
|
+
process.env.HOME ?? os7.homedir(),
|
|
5645
6740
|
"Library/Messages/chat.db"
|
|
5646
6741
|
);
|
|
5647
6742
|
IMessageAdapter = class {
|
|
@@ -5954,9 +7049,9 @@ __export(webhook_exports, {
|
|
|
5954
7049
|
WebhookAdapter: () => WebhookAdapter
|
|
5955
7050
|
});
|
|
5956
7051
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
5957
|
-
function resolvePath(obj,
|
|
7052
|
+
function resolvePath(obj, path23) {
|
|
5958
7053
|
let current = obj;
|
|
5959
|
-
for (const segment of
|
|
7054
|
+
for (const segment of path23.split(".")) {
|
|
5960
7055
|
if (current == null || typeof current !== "object") return void 0;
|
|
5961
7056
|
current = current[segment];
|
|
5962
7057
|
}
|
|
@@ -6059,13 +7154,13 @@ __export(whatsapp_accounts_exports, {
|
|
|
6059
7154
|
getDefaultAccount: () => getDefaultAccount,
|
|
6060
7155
|
loadAccounts: () => loadAccounts
|
|
6061
7156
|
});
|
|
6062
|
-
import { readFileSync as
|
|
7157
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
6063
7158
|
import { join as join2 } from "path";
|
|
6064
7159
|
import { homedir as homedir2 } from "os";
|
|
6065
7160
|
function loadAccounts() {
|
|
6066
7161
|
if (cachedAccounts !== null) return cachedAccounts;
|
|
6067
7162
|
try {
|
|
6068
|
-
const raw =
|
|
7163
|
+
const raw = readFileSync7(CONFIG_PATH2, "utf8");
|
|
6069
7164
|
const parsed = JSON.parse(raw);
|
|
6070
7165
|
if (!Array.isArray(parsed)) {
|
|
6071
7166
|
console.warn("[whatsapp] Config is not an array, ignoring");
|
|
@@ -6105,13 +7200,13 @@ var init_whatsapp_accounts = __esm({
|
|
|
6105
7200
|
});
|
|
6106
7201
|
|
|
6107
7202
|
// src/lib/session-registry.ts
|
|
6108
|
-
import { readFileSync as
|
|
6109
|
-
import
|
|
6110
|
-
import
|
|
7203
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, existsSync as existsSync10 } from "fs";
|
|
7204
|
+
import path11 from "path";
|
|
7205
|
+
import os8 from "os";
|
|
6111
7206
|
function registerSession(entry) {
|
|
6112
|
-
const dir =
|
|
6113
|
-
if (!
|
|
6114
|
-
|
|
7207
|
+
const dir = path11.dirname(REGISTRY_PATH);
|
|
7208
|
+
if (!existsSync10(dir)) {
|
|
7209
|
+
mkdirSync5(dir, { recursive: true });
|
|
6115
7210
|
}
|
|
6116
7211
|
const sessions = listSessions();
|
|
6117
7212
|
const idx = sessions.findIndex((s) => s.windowName === entry.windowName);
|
|
@@ -6120,11 +7215,11 @@ function registerSession(entry) {
|
|
|
6120
7215
|
} else {
|
|
6121
7216
|
sessions.push(entry);
|
|
6122
7217
|
}
|
|
6123
|
-
|
|
7218
|
+
writeFileSync5(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
|
|
6124
7219
|
}
|
|
6125
7220
|
function listSessions() {
|
|
6126
7221
|
try {
|
|
6127
|
-
const raw =
|
|
7222
|
+
const raw = readFileSync8(REGISTRY_PATH, "utf8");
|
|
6128
7223
|
return JSON.parse(raw);
|
|
6129
7224
|
} catch {
|
|
6130
7225
|
return [];
|
|
@@ -6134,7 +7229,7 @@ var REGISTRY_PATH;
|
|
|
6134
7229
|
var init_session_registry = __esm({
|
|
6135
7230
|
"src/lib/session-registry.ts"() {
|
|
6136
7231
|
"use strict";
|
|
6137
|
-
REGISTRY_PATH =
|
|
7232
|
+
REGISTRY_PATH = path11.join(os8.homedir(), ".exe-os", "session-registry.json");
|
|
6138
7233
|
}
|
|
6139
7234
|
});
|
|
6140
7235
|
|
|
@@ -6386,67 +7481,6 @@ var init_provider_table = __esm({
|
|
|
6386
7481
|
}
|
|
6387
7482
|
});
|
|
6388
7483
|
|
|
6389
|
-
// src/lib/runtime-table.ts
|
|
6390
|
-
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
6391
|
-
var init_runtime_table = __esm({
|
|
6392
|
-
"src/lib/runtime-table.ts"() {
|
|
6393
|
-
"use strict";
|
|
6394
|
-
RUNTIME_TABLE = {
|
|
6395
|
-
codex: {
|
|
6396
|
-
binary: "codex",
|
|
6397
|
-
launchMode: "interactive",
|
|
6398
|
-
autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
|
|
6399
|
-
inlineFlag: "--no-alt-screen",
|
|
6400
|
-
apiKeyEnv: "OPENAI_API_KEY",
|
|
6401
|
-
defaultModel: "gpt-5.4"
|
|
6402
|
-
},
|
|
6403
|
-
opencode: {
|
|
6404
|
-
binary: "opencode",
|
|
6405
|
-
launchMode: "exec",
|
|
6406
|
-
autoApproveFlag: "--dangerously-skip-permissions",
|
|
6407
|
-
inlineFlag: "",
|
|
6408
|
-
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
6409
|
-
defaultModel: "anthropic/claude-sonnet-4-6"
|
|
6410
|
-
}
|
|
6411
|
-
};
|
|
6412
|
-
DEFAULT_RUNTIME = "claude";
|
|
6413
|
-
}
|
|
6414
|
-
});
|
|
6415
|
-
|
|
6416
|
-
// src/lib/agent-config.ts
|
|
6417
|
-
import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, existsSync as existsSync8, mkdirSync as mkdirSync5 } from "fs";
|
|
6418
|
-
import path9 from "path";
|
|
6419
|
-
function loadAgentConfig() {
|
|
6420
|
-
if (!existsSync8(AGENT_CONFIG_PATH)) return {};
|
|
6421
|
-
try {
|
|
6422
|
-
return JSON.parse(readFileSync7(AGENT_CONFIG_PATH, "utf-8"));
|
|
6423
|
-
} catch {
|
|
6424
|
-
return {};
|
|
6425
|
-
}
|
|
6426
|
-
}
|
|
6427
|
-
function getAgentRuntime(agentId) {
|
|
6428
|
-
const config2 = loadAgentConfig();
|
|
6429
|
-
const entry = config2[agentId];
|
|
6430
|
-
if (entry) return entry;
|
|
6431
|
-
const orgDefault = config2["default"];
|
|
6432
|
-
if (orgDefault) return orgDefault;
|
|
6433
|
-
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
6434
|
-
}
|
|
6435
|
-
var AGENT_CONFIG_PATH, DEFAULT_MODELS;
|
|
6436
|
-
var init_agent_config = __esm({
|
|
6437
|
-
"src/lib/agent-config.ts"() {
|
|
6438
|
-
"use strict";
|
|
6439
|
-
init_config();
|
|
6440
|
-
init_runtime_table();
|
|
6441
|
-
AGENT_CONFIG_PATH = path9.join(EXE_AI_DIR, "agent-config.json");
|
|
6442
|
-
DEFAULT_MODELS = {
|
|
6443
|
-
claude: "claude-opus-4",
|
|
6444
|
-
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
6445
|
-
opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
|
|
6446
|
-
};
|
|
6447
|
-
}
|
|
6448
|
-
});
|
|
6449
|
-
|
|
6450
7484
|
// src/lib/intercom-queue.ts
|
|
6451
7485
|
var intercom_queue_exports = {};
|
|
6452
7486
|
__export(intercom_queue_exports, {
|
|
@@ -6456,17 +7490,17 @@ __export(intercom_queue_exports, {
|
|
|
6456
7490
|
queueIntercom: () => queueIntercom,
|
|
6457
7491
|
readQueue: () => readQueue
|
|
6458
7492
|
});
|
|
6459
|
-
import { readFileSync as
|
|
6460
|
-
import
|
|
6461
|
-
import
|
|
7493
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, renameSync as renameSync3, existsSync as existsSync11, mkdirSync as mkdirSync6 } from "fs";
|
|
7494
|
+
import path12 from "path";
|
|
7495
|
+
import os9 from "os";
|
|
6462
7496
|
function ensureDir() {
|
|
6463
|
-
const dir =
|
|
6464
|
-
if (!
|
|
7497
|
+
const dir = path12.dirname(QUEUE_PATH);
|
|
7498
|
+
if (!existsSync11(dir)) mkdirSync6(dir, { recursive: true });
|
|
6465
7499
|
}
|
|
6466
7500
|
function readQueue() {
|
|
6467
7501
|
try {
|
|
6468
|
-
if (!
|
|
6469
|
-
return JSON.parse(
|
|
7502
|
+
if (!existsSync11(QUEUE_PATH)) return [];
|
|
7503
|
+
return JSON.parse(readFileSync9(QUEUE_PATH, "utf8"));
|
|
6470
7504
|
} catch {
|
|
6471
7505
|
return [];
|
|
6472
7506
|
}
|
|
@@ -6474,7 +7508,7 @@ function readQueue() {
|
|
|
6474
7508
|
function writeQueue(queue) {
|
|
6475
7509
|
ensureDir();
|
|
6476
7510
|
const tmp = `${QUEUE_PATH}.tmp`;
|
|
6477
|
-
|
|
7511
|
+
writeFileSync6(tmp, JSON.stringify(queue, null, 2));
|
|
6478
7512
|
renameSync3(tmp, QUEUE_PATH);
|
|
6479
7513
|
}
|
|
6480
7514
|
function queueIntercom(targetSession, reason) {
|
|
@@ -6566,20 +7600,20 @@ var QUEUE_PATH, MAX_RETRIES2, TTL_MS, INTERCOM_LOG;
|
|
|
6566
7600
|
var init_intercom_queue = __esm({
|
|
6567
7601
|
"src/lib/intercom-queue.ts"() {
|
|
6568
7602
|
"use strict";
|
|
6569
|
-
QUEUE_PATH =
|
|
7603
|
+
QUEUE_PATH = path12.join(os9.homedir(), ".exe-os", "intercom-queue.json");
|
|
6570
7604
|
MAX_RETRIES2 = 5;
|
|
6571
7605
|
TTL_MS = 60 * 60 * 1e3;
|
|
6572
|
-
INTERCOM_LOG =
|
|
7606
|
+
INTERCOM_LOG = path12.join(os9.homedir(), ".exe-os", "intercom.log");
|
|
6573
7607
|
}
|
|
6574
7608
|
});
|
|
6575
7609
|
|
|
6576
7610
|
// src/lib/plan-limits.ts
|
|
6577
|
-
import { readFileSync as
|
|
6578
|
-
import
|
|
7611
|
+
import { readFileSync as readFileSync10, existsSync as existsSync12 } from "fs";
|
|
7612
|
+
import path13 from "path";
|
|
6579
7613
|
function getLicenseSync() {
|
|
6580
7614
|
try {
|
|
6581
|
-
if (!
|
|
6582
|
-
const raw = JSON.parse(
|
|
7615
|
+
if (!existsSync12(CACHE_PATH2)) return freeLicense();
|
|
7616
|
+
const raw = JSON.parse(readFileSync10(CACHE_PATH2, "utf8"));
|
|
6583
7617
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
6584
7618
|
const parts = raw.token.split(".");
|
|
6585
7619
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -6617,8 +7651,8 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
6617
7651
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
6618
7652
|
let count = 0;
|
|
6619
7653
|
try {
|
|
6620
|
-
if (
|
|
6621
|
-
const raw =
|
|
7654
|
+
if (existsSync12(filePath)) {
|
|
7655
|
+
const raw = readFileSync10(filePath, "utf8");
|
|
6622
7656
|
const employees = JSON.parse(raw);
|
|
6623
7657
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
6624
7658
|
}
|
|
@@ -6647,29 +7681,63 @@ var init_plan_limits = __esm({
|
|
|
6647
7681
|
this.name = "PlanLimitError";
|
|
6648
7682
|
}
|
|
6649
7683
|
};
|
|
6650
|
-
CACHE_PATH2 =
|
|
7684
|
+
CACHE_PATH2 = path13.join(EXE_AI_DIR, "license-cache.json");
|
|
7685
|
+
}
|
|
7686
|
+
});
|
|
7687
|
+
|
|
7688
|
+
// src/lib/task-scope.ts
|
|
7689
|
+
function getCurrentSessionScope() {
|
|
7690
|
+
try {
|
|
7691
|
+
return resolveExeSession();
|
|
7692
|
+
} catch {
|
|
7693
|
+
return null;
|
|
7694
|
+
}
|
|
7695
|
+
}
|
|
7696
|
+
function sessionScopeFilter(sessionScope, tableAlias) {
|
|
7697
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
7698
|
+
if (!scope) return { sql: "", args: [] };
|
|
7699
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
7700
|
+
return {
|
|
7701
|
+
sql: ` AND (${col} IS NULL OR ${col} = ?)`,
|
|
7702
|
+
args: [scope]
|
|
7703
|
+
};
|
|
7704
|
+
}
|
|
7705
|
+
function strictSessionScopeFilter(sessionScope, tableAlias) {
|
|
7706
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
7707
|
+
if (!scope) return { sql: "", args: [] };
|
|
7708
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
7709
|
+
return {
|
|
7710
|
+
sql: ` AND ${col} = ?`,
|
|
7711
|
+
args: [scope]
|
|
7712
|
+
};
|
|
7713
|
+
}
|
|
7714
|
+
var init_task_scope = __esm({
|
|
7715
|
+
"src/lib/task-scope.ts"() {
|
|
7716
|
+
"use strict";
|
|
7717
|
+
init_tmux_routing();
|
|
6651
7718
|
}
|
|
6652
7719
|
});
|
|
6653
7720
|
|
|
6654
7721
|
// src/lib/notifications.ts
|
|
6655
|
-
import
|
|
6656
|
-
import
|
|
6657
|
-
import
|
|
7722
|
+
import crypto4 from "crypto";
|
|
7723
|
+
import path14 from "path";
|
|
7724
|
+
import os10 from "os";
|
|
6658
7725
|
import {
|
|
6659
|
-
readFileSync as
|
|
7726
|
+
readFileSync as readFileSync11,
|
|
6660
7727
|
readdirSync as readdirSync2,
|
|
6661
7728
|
unlinkSync as unlinkSync3,
|
|
6662
|
-
existsSync as
|
|
7729
|
+
existsSync as existsSync13,
|
|
6663
7730
|
rmdirSync
|
|
6664
7731
|
} from "fs";
|
|
6665
7732
|
async function writeNotification(notification) {
|
|
6666
7733
|
try {
|
|
6667
7734
|
const client = getClient();
|
|
6668
|
-
const id =
|
|
7735
|
+
const id = crypto4.randomUUID();
|
|
6669
7736
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7737
|
+
const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
|
|
6670
7738
|
await client.execute({
|
|
6671
|
-
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
|
|
6672
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
7739
|
+
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
7740
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
6673
7741
|
args: [
|
|
6674
7742
|
id,
|
|
6675
7743
|
notification.agentId,
|
|
@@ -6678,6 +7746,7 @@ async function writeNotification(notification) {
|
|
|
6678
7746
|
notification.project,
|
|
6679
7747
|
notification.summary,
|
|
6680
7748
|
notification.taskFile ?? null,
|
|
7749
|
+
sessionScope,
|
|
6681
7750
|
now
|
|
6682
7751
|
]
|
|
6683
7752
|
});
|
|
@@ -6686,12 +7755,14 @@ async function writeNotification(notification) {
|
|
|
6686
7755
|
`);
|
|
6687
7756
|
}
|
|
6688
7757
|
}
|
|
6689
|
-
async function markAsReadByTaskFile(taskFile) {
|
|
7758
|
+
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
6690
7759
|
try {
|
|
6691
7760
|
const client = getClient();
|
|
7761
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
6692
7762
|
await client.execute({
|
|
6693
|
-
sql:
|
|
6694
|
-
|
|
7763
|
+
sql: `UPDATE notifications SET read = 1
|
|
7764
|
+
WHERE task_file = ? AND read = 0${scope.sql}`,
|
|
7765
|
+
args: [taskFile, ...scope.args]
|
|
6695
7766
|
});
|
|
6696
7767
|
} catch {
|
|
6697
7768
|
}
|
|
@@ -6700,11 +7771,12 @@ var init_notifications = __esm({
|
|
|
6700
7771
|
"src/lib/notifications.ts"() {
|
|
6701
7772
|
"use strict";
|
|
6702
7773
|
init_database();
|
|
7774
|
+
init_task_scope();
|
|
6703
7775
|
}
|
|
6704
7776
|
});
|
|
6705
7777
|
|
|
6706
7778
|
// src/lib/session-kill-telemetry.ts
|
|
6707
|
-
import
|
|
7779
|
+
import crypto5 from "crypto";
|
|
6708
7780
|
async function recordSessionKill(input) {
|
|
6709
7781
|
try {
|
|
6710
7782
|
const client = getClient();
|
|
@@ -6714,7 +7786,7 @@ async function recordSessionKill(input) {
|
|
|
6714
7786
|
ticks_idle, estimated_tokens_saved)
|
|
6715
7787
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
6716
7788
|
args: [
|
|
6717
|
-
|
|
7789
|
+
crypto5.randomUUID(),
|
|
6718
7790
|
input.sessionName,
|
|
6719
7791
|
input.agentId,
|
|
6720
7792
|
(/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -6737,37 +7809,13 @@ var init_session_kill_telemetry = __esm({
|
|
|
6737
7809
|
}
|
|
6738
7810
|
});
|
|
6739
7811
|
|
|
6740
|
-
// src/lib/task-scope.ts
|
|
6741
|
-
function getCurrentSessionScope() {
|
|
6742
|
-
try {
|
|
6743
|
-
return resolveExeSession();
|
|
6744
|
-
} catch {
|
|
6745
|
-
return null;
|
|
6746
|
-
}
|
|
6747
|
-
}
|
|
6748
|
-
function sessionScopeFilter(sessionScope, tableAlias) {
|
|
6749
|
-
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
6750
|
-
if (!scope) return { sql: "", args: [] };
|
|
6751
|
-
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
6752
|
-
return {
|
|
6753
|
-
sql: ` AND (${col} IS NULL OR ${col} = ?)`,
|
|
6754
|
-
args: [scope]
|
|
6755
|
-
};
|
|
6756
|
-
}
|
|
6757
|
-
var init_task_scope = __esm({
|
|
6758
|
-
"src/lib/task-scope.ts"() {
|
|
6759
|
-
"use strict";
|
|
6760
|
-
init_tmux_routing();
|
|
6761
|
-
}
|
|
6762
|
-
});
|
|
6763
|
-
|
|
6764
7812
|
// src/lib/tasks-crud.ts
|
|
6765
|
-
import
|
|
6766
|
-
import
|
|
6767
|
-
import
|
|
7813
|
+
import crypto6 from "crypto";
|
|
7814
|
+
import path15 from "path";
|
|
7815
|
+
import os11 from "os";
|
|
6768
7816
|
import { execSync as execSync4 } from "child_process";
|
|
6769
7817
|
import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
|
|
6770
|
-
import { existsSync as
|
|
7818
|
+
import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
|
|
6771
7819
|
async function writeCheckpoint(input) {
|
|
6772
7820
|
const client = getClient();
|
|
6773
7821
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -6883,7 +7931,7 @@ async function resolveTask(client, identifier, scopeSession) {
|
|
|
6883
7931
|
}
|
|
6884
7932
|
async function createTaskCore(input) {
|
|
6885
7933
|
const client = getClient();
|
|
6886
|
-
const id =
|
|
7934
|
+
const id = crypto6.randomUUID();
|
|
6887
7935
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6888
7936
|
const slug = slugify(input.title);
|
|
6889
7937
|
let earlySessionScope = null;
|
|
@@ -6942,8 +7990,8 @@ ${laneWarning}` : laneWarning;
|
|
|
6942
7990
|
}
|
|
6943
7991
|
if (input.baseDir) {
|
|
6944
7992
|
try {
|
|
6945
|
-
await mkdir4(
|
|
6946
|
-
await mkdir4(
|
|
7993
|
+
await mkdir4(path15.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
7994
|
+
await mkdir4(path15.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
6947
7995
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
6948
7996
|
await ensureGitignoreExe(input.baseDir);
|
|
6949
7997
|
} catch {
|
|
@@ -6979,13 +8027,19 @@ ${laneWarning}` : laneWarning;
|
|
|
6979
8027
|
});
|
|
6980
8028
|
if (input.baseDir) {
|
|
6981
8029
|
try {
|
|
6982
|
-
const EXE_OS_DIR =
|
|
6983
|
-
const mdPath =
|
|
6984
|
-
const mdDir =
|
|
6985
|
-
if (!
|
|
8030
|
+
const EXE_OS_DIR = path15.join(os11.homedir(), ".exe-os");
|
|
8031
|
+
const mdPath = path15.join(EXE_OS_DIR, taskFile);
|
|
8032
|
+
const mdDir = path15.dirname(mdPath);
|
|
8033
|
+
if (!existsSync14(mdDir)) await mkdir4(mdDir, { recursive: true });
|
|
6986
8034
|
const reviewer = input.reviewer ?? input.assignedBy;
|
|
6987
8035
|
const mdContent = `# ${input.title}
|
|
6988
8036
|
|
|
8037
|
+
## MANDATORY: When done
|
|
8038
|
+
|
|
8039
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
8040
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
8041
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
8042
|
+
|
|
6989
8043
|
**ID:** ${id}
|
|
6990
8044
|
**Status:** ${initialStatus}
|
|
6991
8045
|
**Priority:** ${input.priority}
|
|
@@ -6999,12 +8053,6 @@ ${laneWarning}` : laneWarning;
|
|
|
6999
8053
|
## Context
|
|
7000
8054
|
|
|
7001
8055
|
${input.context}
|
|
7002
|
-
|
|
7003
|
-
## MANDATORY: When done
|
|
7004
|
-
|
|
7005
|
-
You MUST call update_task with status "done" and a result summary when finished.
|
|
7006
|
-
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
7007
|
-
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
7008
8056
|
`;
|
|
7009
8057
|
await writeFile4(mdPath, mdContent, "utf-8");
|
|
7010
8058
|
} catch (err) {
|
|
@@ -7253,7 +8301,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
7253
8301
|
await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
|
|
7254
8302
|
} catch {
|
|
7255
8303
|
}
|
|
7256
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
8304
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
7257
8305
|
try {
|
|
7258
8306
|
const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
7259
8307
|
clearQueueForAgent2(String(row.assigned_to));
|
|
@@ -7282,9 +8330,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
7282
8330
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
7283
8331
|
}
|
|
7284
8332
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
7285
|
-
const archPath =
|
|
8333
|
+
const archPath = path15.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
7286
8334
|
try {
|
|
7287
|
-
if (
|
|
8335
|
+
if (existsSync14(archPath)) return;
|
|
7288
8336
|
const template = [
|
|
7289
8337
|
`# ${projectName} \u2014 System Architecture`,
|
|
7290
8338
|
"",
|
|
@@ -7317,10 +8365,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
7317
8365
|
}
|
|
7318
8366
|
}
|
|
7319
8367
|
async function ensureGitignoreExe(baseDir) {
|
|
7320
|
-
const gitignorePath =
|
|
8368
|
+
const gitignorePath = path15.join(baseDir, ".gitignore");
|
|
7321
8369
|
try {
|
|
7322
|
-
if (
|
|
7323
|
-
const content =
|
|
8370
|
+
if (existsSync14(gitignorePath)) {
|
|
8371
|
+
const content = readFileSync12(gitignorePath, "utf-8");
|
|
7324
8372
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
7325
8373
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
7326
8374
|
} else {
|
|
@@ -7351,58 +8399,42 @@ var init_tasks_crud = __esm({
|
|
|
7351
8399
|
});
|
|
7352
8400
|
|
|
7353
8401
|
// src/lib/tasks-review.ts
|
|
7354
|
-
import
|
|
7355
|
-
import { existsSync as
|
|
8402
|
+
import path16 from "path";
|
|
8403
|
+
import { existsSync as existsSync15, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
|
|
7356
8404
|
async function countPendingReviews(sessionScope) {
|
|
7357
8405
|
const client = getClient();
|
|
7358
|
-
|
|
7359
|
-
|
|
7360
|
-
|
|
7361
|
-
args: [sessionScope]
|
|
7362
|
-
});
|
|
7363
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
7364
|
-
}
|
|
8406
|
+
const scope = strictSessionScopeFilter(
|
|
8407
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
8408
|
+
);
|
|
7365
8409
|
const result = await client.execute({
|
|
7366
|
-
sql:
|
|
7367
|
-
|
|
8410
|
+
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
8411
|
+
WHERE status = 'needs_review'${scope.sql}`,
|
|
8412
|
+
args: [...scope.args]
|
|
7368
8413
|
});
|
|
7369
8414
|
return Number(result.rows[0]?.cnt) || 0;
|
|
7370
8415
|
}
|
|
7371
8416
|
async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
7372
8417
|
const client = getClient();
|
|
7373
|
-
|
|
7374
|
-
|
|
7375
|
-
|
|
7376
|
-
WHERE status = 'needs_review' AND updated_at > ?
|
|
7377
|
-
AND session_scope = ?`,
|
|
7378
|
-
args: [sinceIso, sessionScope]
|
|
7379
|
-
});
|
|
7380
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
7381
|
-
}
|
|
8418
|
+
const scope = strictSessionScopeFilter(
|
|
8419
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
8420
|
+
);
|
|
7382
8421
|
const result = await client.execute({
|
|
7383
8422
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
7384
|
-
WHERE status = 'needs_review' AND updated_at >
|
|
7385
|
-
args: [sinceIso]
|
|
8423
|
+
WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
|
|
8424
|
+
args: [sinceIso, ...scope.args]
|
|
7386
8425
|
});
|
|
7387
8426
|
return Number(result.rows[0]?.cnt) || 0;
|
|
7388
8427
|
}
|
|
7389
8428
|
async function listPendingReviews(limit, sessionScope) {
|
|
7390
8429
|
const client = getClient();
|
|
7391
|
-
|
|
7392
|
-
|
|
7393
|
-
|
|
7394
|
-
WHERE status = 'needs_review'
|
|
7395
|
-
AND session_scope = ?
|
|
7396
|
-
ORDER BY updated_at ASC LIMIT ?`,
|
|
7397
|
-
args: [sessionScope, limit]
|
|
7398
|
-
});
|
|
7399
|
-
return result2.rows;
|
|
7400
|
-
}
|
|
8430
|
+
const scope = strictSessionScopeFilter(
|
|
8431
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
8432
|
+
);
|
|
7401
8433
|
const result = await client.execute({
|
|
7402
8434
|
sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
|
|
7403
|
-
WHERE status = 'needs_review'
|
|
8435
|
+
WHERE status = 'needs_review'${scope.sql}
|
|
7404
8436
|
ORDER BY updated_at ASC LIMIT ?`,
|
|
7405
|
-
args: [limit]
|
|
8437
|
+
args: [...scope.args, limit]
|
|
7406
8438
|
});
|
|
7407
8439
|
return result.rows;
|
|
7408
8440
|
}
|
|
@@ -7414,7 +8446,7 @@ async function cleanupOrphanedReviews() {
|
|
|
7414
8446
|
WHERE status IN ('open', 'needs_review', 'in_progress')
|
|
7415
8447
|
AND assigned_by = 'system'
|
|
7416
8448
|
AND title LIKE 'Review:%'
|
|
7417
|
-
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
|
|
8449
|
+
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
|
|
7418
8450
|
args: [now]
|
|
7419
8451
|
});
|
|
7420
8452
|
const r1b = await client.execute({
|
|
@@ -7533,11 +8565,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
7533
8565
|
);
|
|
7534
8566
|
}
|
|
7535
8567
|
try {
|
|
7536
|
-
const cacheDir =
|
|
7537
|
-
if (
|
|
8568
|
+
const cacheDir = path16.join(EXE_AI_DIR, "session-cache");
|
|
8569
|
+
if (existsSync15(cacheDir)) {
|
|
7538
8570
|
for (const f of readdirSync3(cacheDir)) {
|
|
7539
8571
|
if (f.startsWith("review-notified-")) {
|
|
7540
|
-
unlinkSync4(
|
|
8572
|
+
unlinkSync4(path16.join(cacheDir, f));
|
|
7541
8573
|
}
|
|
7542
8574
|
}
|
|
7543
8575
|
}
|
|
@@ -7554,11 +8586,12 @@ var init_tasks_review = __esm({
|
|
|
7554
8586
|
init_tmux_routing();
|
|
7555
8587
|
init_session_key();
|
|
7556
8588
|
init_state_bus();
|
|
8589
|
+
init_task_scope();
|
|
7557
8590
|
}
|
|
7558
8591
|
});
|
|
7559
8592
|
|
|
7560
8593
|
// src/lib/tasks-chain.ts
|
|
7561
|
-
import
|
|
8594
|
+
import path17 from "path";
|
|
7562
8595
|
import { readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
|
|
7563
8596
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
7564
8597
|
const client = getClient();
|
|
@@ -7575,7 +8608,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
7575
8608
|
});
|
|
7576
8609
|
for (const ur of unblockedRows.rows) {
|
|
7577
8610
|
try {
|
|
7578
|
-
const ubFile =
|
|
8611
|
+
const ubFile = path17.join(baseDir, String(ur.task_file));
|
|
7579
8612
|
let ubContent = await readFile4(ubFile, "utf-8");
|
|
7580
8613
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
7581
8614
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -7610,7 +8643,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
|
|
|
7610
8643
|
const scScope = sessionScopeFilter();
|
|
7611
8644
|
const remaining = await client.execute({
|
|
7612
8645
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
7613
|
-
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
|
|
8646
|
+
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
|
|
7614
8647
|
args: [parentTaskId, ...scScope.args]
|
|
7615
8648
|
});
|
|
7616
8649
|
const cnt = Number(remaining.rows[0]?.cnt ?? 1);
|
|
@@ -7644,7 +8677,7 @@ var init_tasks_chain = __esm({
|
|
|
7644
8677
|
|
|
7645
8678
|
// src/lib/project-name.ts
|
|
7646
8679
|
import { execSync as execSync5 } from "child_process";
|
|
7647
|
-
import
|
|
8680
|
+
import path18 from "path";
|
|
7648
8681
|
function getProjectName(cwd) {
|
|
7649
8682
|
const dir = cwd ?? process.cwd();
|
|
7650
8683
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
@@ -7657,7 +8690,7 @@ function getProjectName(cwd) {
|
|
|
7657
8690
|
timeout: 2e3,
|
|
7658
8691
|
stdio: ["pipe", "pipe", "pipe"]
|
|
7659
8692
|
}).trim();
|
|
7660
|
-
repoRoot =
|
|
8693
|
+
repoRoot = path18.dirname(gitCommonDir);
|
|
7661
8694
|
} catch {
|
|
7662
8695
|
repoRoot = execSync5("git rev-parse --show-toplevel", {
|
|
7663
8696
|
cwd: dir,
|
|
@@ -7666,11 +8699,11 @@ function getProjectName(cwd) {
|
|
|
7666
8699
|
stdio: ["pipe", "pipe", "pipe"]
|
|
7667
8700
|
}).trim();
|
|
7668
8701
|
}
|
|
7669
|
-
_cached2 =
|
|
8702
|
+
_cached2 = path18.basename(repoRoot);
|
|
7670
8703
|
_cachedCwd = dir;
|
|
7671
8704
|
return _cached2;
|
|
7672
8705
|
} catch {
|
|
7673
|
-
_cached2 =
|
|
8706
|
+
_cached2 = path18.basename(dir);
|
|
7674
8707
|
_cachedCwd = dir;
|
|
7675
8708
|
return _cached2;
|
|
7676
8709
|
}
|
|
@@ -7813,10 +8846,10 @@ var init_tasks_notify = __esm({
|
|
|
7813
8846
|
});
|
|
7814
8847
|
|
|
7815
8848
|
// src/lib/behaviors.ts
|
|
7816
|
-
import
|
|
8849
|
+
import crypto7 from "crypto";
|
|
7817
8850
|
async function storeBehavior(opts) {
|
|
7818
8851
|
const client = getClient();
|
|
7819
|
-
const id =
|
|
8852
|
+
const id = crypto7.randomUUID();
|
|
7820
8853
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7821
8854
|
await client.execute({
|
|
7822
8855
|
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
|
|
@@ -7845,7 +8878,7 @@ __export(skill_learning_exports, {
|
|
|
7845
8878
|
storeTrajectory: () => storeTrajectory,
|
|
7846
8879
|
sweepTrajectories: () => sweepTrajectories
|
|
7847
8880
|
});
|
|
7848
|
-
import
|
|
8881
|
+
import crypto8 from "crypto";
|
|
7849
8882
|
async function extractTrajectory(taskId, agentId) {
|
|
7850
8883
|
const client = getClient();
|
|
7851
8884
|
const result = await client.execute({
|
|
@@ -7874,11 +8907,11 @@ async function extractTrajectory(taskId, agentId) {
|
|
|
7874
8907
|
return signature;
|
|
7875
8908
|
}
|
|
7876
8909
|
function hashSignature(signature) {
|
|
7877
|
-
return
|
|
8910
|
+
return crypto8.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
|
|
7878
8911
|
}
|
|
7879
8912
|
async function storeTrajectory(opts) {
|
|
7880
8913
|
const client = getClient();
|
|
7881
|
-
const id =
|
|
8914
|
+
const id = crypto8.randomUUID();
|
|
7882
8915
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7883
8916
|
const signatureHash = hashSignature(opts.signature);
|
|
7884
8917
|
await client.execute({
|
|
@@ -8143,8 +9176,8 @@ __export(tasks_exports, {
|
|
|
8143
9176
|
updateTaskStatus: () => updateTaskStatus,
|
|
8144
9177
|
writeCheckpoint: () => writeCheckpoint
|
|
8145
9178
|
});
|
|
8146
|
-
import
|
|
8147
|
-
import { writeFileSync as
|
|
9179
|
+
import path19 from "path";
|
|
9180
|
+
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync7, unlinkSync as unlinkSync5 } from "fs";
|
|
8148
9181
|
async function createTask(input) {
|
|
8149
9182
|
const result = await createTaskCore(input);
|
|
8150
9183
|
if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -8163,12 +9196,12 @@ async function updateTask(input) {
|
|
|
8163
9196
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
8164
9197
|
try {
|
|
8165
9198
|
const agent = String(row.assigned_to);
|
|
8166
|
-
const cacheDir =
|
|
8167
|
-
const cachePath =
|
|
9199
|
+
const cacheDir = path19.join(EXE_AI_DIR, "session-cache");
|
|
9200
|
+
const cachePath = path19.join(cacheDir, `current-task-${agent}.json`);
|
|
8168
9201
|
if (input.status === "in_progress") {
|
|
8169
9202
|
mkdirSync7(cacheDir, { recursive: true });
|
|
8170
|
-
|
|
8171
|
-
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
9203
|
+
writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
9204
|
+
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
|
|
8172
9205
|
try {
|
|
8173
9206
|
unlinkSync5(cachePath);
|
|
8174
9207
|
} catch {
|
|
@@ -8176,10 +9209,10 @@ async function updateTask(input) {
|
|
|
8176
9209
|
}
|
|
8177
9210
|
} catch {
|
|
8178
9211
|
}
|
|
8179
|
-
if (input.status === "done") {
|
|
9212
|
+
if (input.status === "done" || input.status === "closed") {
|
|
8180
9213
|
await cleanupReviewFile(row, taskFile, input.baseDir);
|
|
8181
9214
|
}
|
|
8182
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
9215
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
8183
9216
|
try {
|
|
8184
9217
|
const client = getClient();
|
|
8185
9218
|
const taskTitle = String(row.title);
|
|
@@ -8195,7 +9228,7 @@ async function updateTask(input) {
|
|
|
8195
9228
|
if (!isCoordinatorName(assignedAgent)) {
|
|
8196
9229
|
try {
|
|
8197
9230
|
const draftClient = getClient();
|
|
8198
|
-
if (input.status === "done") {
|
|
9231
|
+
if (input.status === "done" || input.status === "closed") {
|
|
8199
9232
|
await draftClient.execute({
|
|
8200
9233
|
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
8201
9234
|
args: [assignedAgent]
|
|
@@ -8212,7 +9245,7 @@ async function updateTask(input) {
|
|
|
8212
9245
|
try {
|
|
8213
9246
|
const client = getClient();
|
|
8214
9247
|
const cascaded = await client.execute({
|
|
8215
|
-
sql: `UPDATE tasks SET status = '
|
|
9248
|
+
sql: `UPDATE tasks SET status = 'closed', updated_at = ?
|
|
8216
9249
|
WHERE parent_task_id = ? AND status = 'needs_review'`,
|
|
8217
9250
|
args: [now, taskId]
|
|
8218
9251
|
});
|
|
@@ -8225,14 +9258,14 @@ async function updateTask(input) {
|
|
|
8225
9258
|
} catch {
|
|
8226
9259
|
}
|
|
8227
9260
|
}
|
|
8228
|
-
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
9261
|
+
const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
|
|
8229
9262
|
if (isTerminal) {
|
|
8230
9263
|
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
8231
9264
|
if (!isCoordinator) {
|
|
8232
9265
|
notifyTaskDone();
|
|
8233
9266
|
}
|
|
8234
9267
|
await markTaskNotificationsRead(taskFile);
|
|
8235
|
-
if (input.status === "done") {
|
|
9268
|
+
if (input.status === "done" || input.status === "closed") {
|
|
8236
9269
|
try {
|
|
8237
9270
|
await cascadeUnblock(taskId, input.baseDir, now);
|
|
8238
9271
|
} catch {
|
|
@@ -8252,7 +9285,7 @@ async function updateTask(input) {
|
|
|
8252
9285
|
}
|
|
8253
9286
|
}
|
|
8254
9287
|
}
|
|
8255
|
-
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
9288
|
+
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
8256
9289
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
8257
9290
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
8258
9291
|
taskId,
|
|
@@ -8624,6 +9657,7 @@ __export(tmux_routing_exports, {
|
|
|
8624
9657
|
isEmployeeAlive: () => isEmployeeAlive,
|
|
8625
9658
|
isExeSession: () => isExeSession,
|
|
8626
9659
|
isSessionBusy: () => isSessionBusy,
|
|
9660
|
+
notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
|
|
8627
9661
|
notifyParentExe: () => notifyParentExe,
|
|
8628
9662
|
parseParentExe: () => parseParentExe,
|
|
8629
9663
|
registerParentExe: () => registerParentExe,
|
|
@@ -8634,13 +9668,13 @@ __export(tmux_routing_exports, {
|
|
|
8634
9668
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
8635
9669
|
});
|
|
8636
9670
|
import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
|
|
8637
|
-
import { readFileSync as
|
|
8638
|
-
import
|
|
8639
|
-
import
|
|
9671
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, mkdirSync as mkdirSync8, existsSync as existsSync16, appendFileSync, readdirSync as readdirSync4 } from "fs";
|
|
9672
|
+
import path20 from "path";
|
|
9673
|
+
import os12 from "os";
|
|
8640
9674
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
8641
9675
|
import { unlinkSync as unlinkSync6 } from "fs";
|
|
8642
9676
|
function spawnLockPath(sessionName) {
|
|
8643
|
-
return
|
|
9677
|
+
return path20.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
8644
9678
|
}
|
|
8645
9679
|
function isProcessAlive(pid) {
|
|
8646
9680
|
try {
|
|
@@ -8651,13 +9685,13 @@ function isProcessAlive(pid) {
|
|
|
8651
9685
|
}
|
|
8652
9686
|
}
|
|
8653
9687
|
function acquireSpawnLock2(sessionName) {
|
|
8654
|
-
if (!
|
|
9688
|
+
if (!existsSync16(SPAWN_LOCK_DIR)) {
|
|
8655
9689
|
mkdirSync8(SPAWN_LOCK_DIR, { recursive: true });
|
|
8656
9690
|
}
|
|
8657
9691
|
const lockFile = spawnLockPath(sessionName);
|
|
8658
|
-
if (
|
|
9692
|
+
if (existsSync16(lockFile)) {
|
|
8659
9693
|
try {
|
|
8660
|
-
const lock = JSON.parse(
|
|
9694
|
+
const lock = JSON.parse(readFileSync13(lockFile, "utf8"));
|
|
8661
9695
|
const age = Date.now() - lock.timestamp;
|
|
8662
9696
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
8663
9697
|
return false;
|
|
@@ -8665,7 +9699,7 @@ function acquireSpawnLock2(sessionName) {
|
|
|
8665
9699
|
} catch {
|
|
8666
9700
|
}
|
|
8667
9701
|
}
|
|
8668
|
-
|
|
9702
|
+
writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
8669
9703
|
return true;
|
|
8670
9704
|
}
|
|
8671
9705
|
function releaseSpawnLock2(sessionName) {
|
|
@@ -8677,13 +9711,13 @@ function releaseSpawnLock2(sessionName) {
|
|
|
8677
9711
|
function resolveBehaviorsExporterScript() {
|
|
8678
9712
|
try {
|
|
8679
9713
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
8680
|
-
const scriptPath =
|
|
8681
|
-
|
|
9714
|
+
const scriptPath = path20.join(
|
|
9715
|
+
path20.dirname(thisFile),
|
|
8682
9716
|
"..",
|
|
8683
9717
|
"bin",
|
|
8684
9718
|
"exe-export-behaviors.js"
|
|
8685
9719
|
);
|
|
8686
|
-
return
|
|
9720
|
+
return existsSync16(scriptPath) ? scriptPath : null;
|
|
8687
9721
|
} catch {
|
|
8688
9722
|
return null;
|
|
8689
9723
|
}
|
|
@@ -8749,12 +9783,12 @@ function extractRootExe(name) {
|
|
|
8749
9783
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
8750
9784
|
}
|
|
8751
9785
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
8752
|
-
if (!
|
|
9786
|
+
if (!existsSync16(SESSION_CACHE)) {
|
|
8753
9787
|
mkdirSync8(SESSION_CACHE, { recursive: true });
|
|
8754
9788
|
}
|
|
8755
9789
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
8756
|
-
const filePath =
|
|
8757
|
-
|
|
9790
|
+
const filePath = path20.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
9791
|
+
writeFileSync8(filePath, JSON.stringify({
|
|
8758
9792
|
parentExe: rootExe,
|
|
8759
9793
|
dispatchedBy: dispatchedBy || rootExe,
|
|
8760
9794
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -8762,7 +9796,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
8762
9796
|
}
|
|
8763
9797
|
function getParentExe(sessionKey) {
|
|
8764
9798
|
try {
|
|
8765
|
-
const data = JSON.parse(
|
|
9799
|
+
const data = JSON.parse(readFileSync13(path20.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
8766
9800
|
return data.parentExe || null;
|
|
8767
9801
|
} catch {
|
|
8768
9802
|
return null;
|
|
@@ -8770,8 +9804,8 @@ function getParentExe(sessionKey) {
|
|
|
8770
9804
|
}
|
|
8771
9805
|
function getDispatchedBy(sessionKey) {
|
|
8772
9806
|
try {
|
|
8773
|
-
const data = JSON.parse(
|
|
8774
|
-
|
|
9807
|
+
const data = JSON.parse(readFileSync13(
|
|
9808
|
+
path20.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
8775
9809
|
"utf8"
|
|
8776
9810
|
));
|
|
8777
9811
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -8841,8 +9875,8 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
8841
9875
|
}
|
|
8842
9876
|
function readDebounceState() {
|
|
8843
9877
|
try {
|
|
8844
|
-
if (!
|
|
8845
|
-
const raw = JSON.parse(
|
|
9878
|
+
if (!existsSync16(DEBOUNCE_FILE)) return {};
|
|
9879
|
+
const raw = JSON.parse(readFileSync13(DEBOUNCE_FILE, "utf8"));
|
|
8846
9880
|
const state = {};
|
|
8847
9881
|
for (const [key, val] of Object.entries(raw)) {
|
|
8848
9882
|
if (typeof val === "number") {
|
|
@@ -8858,8 +9892,8 @@ function readDebounceState() {
|
|
|
8858
9892
|
}
|
|
8859
9893
|
function writeDebounceState(state) {
|
|
8860
9894
|
try {
|
|
8861
|
-
if (!
|
|
8862
|
-
|
|
9895
|
+
if (!existsSync16(SESSION_CACHE)) mkdirSync8(SESSION_CACHE, { recursive: true });
|
|
9896
|
+
writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
|
|
8863
9897
|
} catch {
|
|
8864
9898
|
}
|
|
8865
9899
|
}
|
|
@@ -8957,8 +9991,8 @@ function sendIntercom(targetSession) {
|
|
|
8957
9991
|
try {
|
|
8958
9992
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
8959
9993
|
const agent = baseAgentName(rawAgent);
|
|
8960
|
-
const markerPath =
|
|
8961
|
-
if (
|
|
9994
|
+
const markerPath = path20.join(SESSION_CACHE, `current-task-${agent}.json`);
|
|
9995
|
+
if (existsSync16(markerPath)) {
|
|
8962
9996
|
logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
|
|
8963
9997
|
return "debounced";
|
|
8964
9998
|
}
|
|
@@ -8967,8 +10001,8 @@ function sendIntercom(targetSession) {
|
|
|
8967
10001
|
try {
|
|
8968
10002
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
8969
10003
|
const agent = baseAgentName(rawAgent);
|
|
8970
|
-
const taskDir =
|
|
8971
|
-
if (
|
|
10004
|
+
const taskDir = path20.join(process.cwd(), "exe", agent);
|
|
10005
|
+
if (existsSync16(taskDir)) {
|
|
8972
10006
|
const files = readdirSync4(taskDir).filter(
|
|
8973
10007
|
(f) => f.endsWith(".md") && f !== "DONE.txt"
|
|
8974
10008
|
);
|
|
@@ -9028,6 +10062,21 @@ function notifyParentExe(sessionKey) {
|
|
|
9028
10062
|
}
|
|
9029
10063
|
return true;
|
|
9030
10064
|
}
|
|
10065
|
+
function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
|
|
10066
|
+
const transport = getTransport();
|
|
10067
|
+
try {
|
|
10068
|
+
const sessions = transport.listSessions();
|
|
10069
|
+
if (!sessions.includes(coordinatorSession)) return false;
|
|
10070
|
+
execSync6(
|
|
10071
|
+
`tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
|
|
10072
|
+
{ timeout: 3e3 }
|
|
10073
|
+
);
|
|
10074
|
+
logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
|
|
10075
|
+
return true;
|
|
10076
|
+
} catch {
|
|
10077
|
+
return false;
|
|
10078
|
+
}
|
|
10079
|
+
}
|
|
9031
10080
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
9032
10081
|
if (isCoordinatorName(employeeName)) {
|
|
9033
10082
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
@@ -9101,26 +10150,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
9101
10150
|
const transport = getTransport();
|
|
9102
10151
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
9103
10152
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
9104
|
-
const logDir =
|
|
9105
|
-
const logFile =
|
|
9106
|
-
if (!
|
|
10153
|
+
const logDir = path20.join(os12.homedir(), ".exe-os", "session-logs");
|
|
10154
|
+
const logFile = path20.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
10155
|
+
if (!existsSync16(logDir)) {
|
|
9107
10156
|
mkdirSync8(logDir, { recursive: true });
|
|
9108
10157
|
}
|
|
9109
10158
|
transport.kill(sessionName);
|
|
9110
10159
|
let cleanupSuffix = "";
|
|
9111
10160
|
try {
|
|
9112
10161
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
9113
|
-
const cleanupScript =
|
|
9114
|
-
if (
|
|
10162
|
+
const cleanupScript = path20.join(path20.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
10163
|
+
if (existsSync16(cleanupScript)) {
|
|
9115
10164
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
9116
10165
|
}
|
|
9117
10166
|
} catch {
|
|
9118
10167
|
}
|
|
9119
10168
|
try {
|
|
9120
|
-
const claudeJsonPath =
|
|
10169
|
+
const claudeJsonPath = path20.join(os12.homedir(), ".claude.json");
|
|
9121
10170
|
let claudeJson = {};
|
|
9122
10171
|
try {
|
|
9123
|
-
claudeJson = JSON.parse(
|
|
10172
|
+
claudeJson = JSON.parse(readFileSync13(claudeJsonPath, "utf8"));
|
|
9124
10173
|
} catch {
|
|
9125
10174
|
}
|
|
9126
10175
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -9128,17 +10177,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
9128
10177
|
const trustDir = opts?.cwd ?? projectDir;
|
|
9129
10178
|
if (!projects[trustDir]) projects[trustDir] = {};
|
|
9130
10179
|
projects[trustDir].hasTrustDialogAccepted = true;
|
|
9131
|
-
|
|
10180
|
+
writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
9132
10181
|
} catch {
|
|
9133
10182
|
}
|
|
9134
10183
|
try {
|
|
9135
|
-
const settingsDir =
|
|
10184
|
+
const settingsDir = path20.join(os12.homedir(), ".claude", "projects");
|
|
9136
10185
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
9137
|
-
const projSettingsDir =
|
|
9138
|
-
const settingsPath =
|
|
10186
|
+
const projSettingsDir = path20.join(settingsDir, normalizedKey);
|
|
10187
|
+
const settingsPath = path20.join(projSettingsDir, "settings.json");
|
|
9139
10188
|
let settings = {};
|
|
9140
10189
|
try {
|
|
9141
|
-
settings = JSON.parse(
|
|
10190
|
+
settings = JSON.parse(readFileSync13(settingsPath, "utf8"));
|
|
9142
10191
|
} catch {
|
|
9143
10192
|
}
|
|
9144
10193
|
const perms = settings.permissions ?? {};
|
|
@@ -9167,7 +10216,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
9167
10216
|
perms.allow = allow;
|
|
9168
10217
|
settings.permissions = perms;
|
|
9169
10218
|
mkdirSync8(projSettingsDir, { recursive: true });
|
|
9170
|
-
|
|
10219
|
+
writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
9171
10220
|
}
|
|
9172
10221
|
} catch {
|
|
9173
10222
|
}
|
|
@@ -9182,8 +10231,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
9182
10231
|
let behaviorsFlag = "";
|
|
9183
10232
|
let legacyFallbackWarned = false;
|
|
9184
10233
|
if (!useExeAgent && !useBinSymlink) {
|
|
9185
|
-
const identityPath =
|
|
9186
|
-
|
|
10234
|
+
const identityPath = path20.join(
|
|
10235
|
+
os12.homedir(),
|
|
9187
10236
|
".exe-os",
|
|
9188
10237
|
"identity",
|
|
9189
10238
|
`${employeeName}.md`
|
|
@@ -9192,13 +10241,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
9192
10241
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
9193
10242
|
if (hasAgentFlag) {
|
|
9194
10243
|
identityFlag = ` --agent ${employeeName}`;
|
|
9195
|
-
} else if (
|
|
10244
|
+
} else if (existsSync16(identityPath)) {
|
|
9196
10245
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
9197
10246
|
legacyFallbackWarned = true;
|
|
9198
10247
|
}
|
|
9199
10248
|
const behaviorsFile = exportBehaviorsSync(
|
|
9200
10249
|
employeeName,
|
|
9201
|
-
|
|
10250
|
+
path20.basename(spawnCwd),
|
|
9202
10251
|
sessionName
|
|
9203
10252
|
);
|
|
9204
10253
|
if (behaviorsFile) {
|
|
@@ -9213,16 +10262,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
9213
10262
|
}
|
|
9214
10263
|
let sessionContextFlag = "";
|
|
9215
10264
|
try {
|
|
9216
|
-
const ctxDir =
|
|
10265
|
+
const ctxDir = path20.join(os12.homedir(), ".exe-os", "session-cache");
|
|
9217
10266
|
mkdirSync8(ctxDir, { recursive: true });
|
|
9218
|
-
const ctxFile =
|
|
10267
|
+
const ctxFile = path20.join(ctxDir, `session-context-${sessionName}.md`);
|
|
9219
10268
|
const ctxContent = [
|
|
9220
10269
|
`## Session Context`,
|
|
9221
10270
|
`You are running in tmux session: ${sessionName}.`,
|
|
9222
10271
|
`Your parent coordinator session is ${exeSession}.`,
|
|
9223
10272
|
`Your employees (if any) use the -${exeSession} suffix.`
|
|
9224
10273
|
].join("\n");
|
|
9225
|
-
|
|
10274
|
+
writeFileSync8(ctxFile, ctxContent);
|
|
9226
10275
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
9227
10276
|
} catch {
|
|
9228
10277
|
}
|
|
@@ -9299,8 +10348,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
9299
10348
|
transport.pipeLog(sessionName, logFile);
|
|
9300
10349
|
try {
|
|
9301
10350
|
const mySession = getMySession();
|
|
9302
|
-
const dispatchInfo =
|
|
9303
|
-
|
|
10351
|
+
const dispatchInfo = path20.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
10352
|
+
writeFileSync8(dispatchInfo, JSON.stringify({
|
|
9304
10353
|
dispatchedBy: mySession,
|
|
9305
10354
|
rootExe: exeSession,
|
|
9306
10355
|
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
|
|
@@ -9374,15 +10423,15 @@ var init_tmux_routing = __esm({
|
|
|
9374
10423
|
init_intercom_queue();
|
|
9375
10424
|
init_plan_limits();
|
|
9376
10425
|
init_employees();
|
|
9377
|
-
SPAWN_LOCK_DIR =
|
|
9378
|
-
SESSION_CACHE =
|
|
10426
|
+
SPAWN_LOCK_DIR = path20.join(os12.homedir(), ".exe-os", "spawn-locks");
|
|
10427
|
+
SESSION_CACHE = path20.join(os12.homedir(), ".exe-os", "session-cache");
|
|
9379
10428
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
9380
10429
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
9381
10430
|
VERIFY_PANE_LINES = 200;
|
|
9382
10431
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
9383
10432
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
9384
|
-
INTERCOM_LOG2 =
|
|
9385
|
-
DEBOUNCE_FILE =
|
|
10433
|
+
INTERCOM_LOG2 = path20.join(os12.homedir(), ".exe-os", "intercom.log");
|
|
10434
|
+
DEBOUNCE_FILE = path20.join(SESSION_CACHE, "intercom-debounce.json");
|
|
9386
10435
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
9387
10436
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
|
|
9388
10437
|
}
|
|
@@ -9405,10 +10454,10 @@ __export(messaging_exports, {
|
|
|
9405
10454
|
sendMessage: () => sendMessage,
|
|
9406
10455
|
setWsClientSend: () => setWsClientSend
|
|
9407
10456
|
});
|
|
9408
|
-
import
|
|
10457
|
+
import crypto9 from "crypto";
|
|
9409
10458
|
function generateUlid() {
|
|
9410
10459
|
const timestamp = Date.now().toString(36).padStart(10, "0");
|
|
9411
|
-
const random =
|
|
10460
|
+
const random = crypto9.randomBytes(10).toString("hex").slice(0, 16);
|
|
9412
10461
|
return (timestamp + random).toUpperCase();
|
|
9413
10462
|
}
|
|
9414
10463
|
function rowToMessage(row) {
|
|
@@ -9419,6 +10468,7 @@ function rowToMessage(row) {
|
|
|
9419
10468
|
targetAgent: row.target_agent,
|
|
9420
10469
|
targetProject: row.target_project ?? null,
|
|
9421
10470
|
targetDevice: row.target_device,
|
|
10471
|
+
sessionScope: row.session_scope ?? null,
|
|
9422
10472
|
content: row.content,
|
|
9423
10473
|
priority: row.priority ?? "normal",
|
|
9424
10474
|
status: row.status ?? "pending",
|
|
@@ -9436,15 +10486,17 @@ async function sendMessage(input) {
|
|
|
9436
10486
|
const id = generateUlid();
|
|
9437
10487
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9438
10488
|
const targetDevice = input.targetDevice ?? "local";
|
|
10489
|
+
const sessionScope = input.sessionScope === void 0 ? resolveExeSession() : input.sessionScope;
|
|
9439
10490
|
await client.execute({
|
|
9440
|
-
sql: `INSERT INTO messages (id, from_agent, from_device, target_agent, target_project, target_device, content, priority, status, created_at)
|
|
9441
|
-
VALUES (?, ?, 'local', ?, ?, ?, ?, ?, 'pending', ?)`,
|
|
10491
|
+
sql: `INSERT INTO messages (id, from_agent, from_device, target_agent, target_project, target_device, session_scope, content, priority, status, created_at)
|
|
10492
|
+
VALUES (?, ?, 'local', ?, ?, ?, ?, ?, ?, 'pending', ?)`,
|
|
9442
10493
|
args: [
|
|
9443
10494
|
id,
|
|
9444
10495
|
input.fromAgent,
|
|
9445
10496
|
input.targetAgent,
|
|
9446
10497
|
input.targetProject ?? null,
|
|
9447
10498
|
targetDevice,
|
|
10499
|
+
sessionScope,
|
|
9448
10500
|
input.content,
|
|
9449
10501
|
input.priority ?? "normal",
|
|
9450
10502
|
now
|
|
@@ -9458,9 +10510,10 @@ async function sendMessage(input) {
|
|
|
9458
10510
|
}
|
|
9459
10511
|
} catch {
|
|
9460
10512
|
}
|
|
10513
|
+
const sentScope = strictSessionScopeFilter(sessionScope);
|
|
9461
10514
|
const result = await client.execute({
|
|
9462
|
-
sql:
|
|
9463
|
-
args: [id]
|
|
10515
|
+
sql: `SELECT * FROM messages WHERE id = ?${sentScope.sql}`,
|
|
10516
|
+
args: [id, ...sentScope.args]
|
|
9464
10517
|
});
|
|
9465
10518
|
return rowToMessage(result.rows[0]);
|
|
9466
10519
|
}
|
|
@@ -9484,6 +10537,7 @@ async function deliverCrossMachineMessage(messageId, targetDevice) {
|
|
|
9484
10537
|
fromAgent: msg.fromAgent,
|
|
9485
10538
|
targetAgent: msg.targetAgent,
|
|
9486
10539
|
targetProject: msg.targetProject,
|
|
10540
|
+
sessionScope: msg.sessionScope,
|
|
9487
10541
|
content: msg.content,
|
|
9488
10542
|
priority: msg.priority,
|
|
9489
10543
|
createdAt: msg.createdAt
|
|
@@ -9527,7 +10581,7 @@ async function deliverLocalMessage(messageId) {
|
|
|
9527
10581
|
} catch {
|
|
9528
10582
|
const newRetryCount = msg.retryCount + 1;
|
|
9529
10583
|
if (newRetryCount >= MAX_RETRIES3) {
|
|
9530
|
-
await markFailed(messageId, "session unavailable after 10 retries");
|
|
10584
|
+
await markFailed(messageId, "session unavailable after 10 retries", msg.sessionScope);
|
|
9531
10585
|
} else {
|
|
9532
10586
|
await client.execute({
|
|
9533
10587
|
sql: "UPDATE messages SET retry_count = ? WHERE id = ?",
|
|
@@ -9537,85 +10591,101 @@ async function deliverLocalMessage(messageId) {
|
|
|
9537
10591
|
return false;
|
|
9538
10592
|
}
|
|
9539
10593
|
}
|
|
9540
|
-
async function getPendingMessages(targetAgent) {
|
|
10594
|
+
async function getPendingMessages(targetAgent, sessionScope) {
|
|
9541
10595
|
const client = getClient();
|
|
10596
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
9542
10597
|
const result = await client.execute({
|
|
9543
10598
|
sql: `SELECT * FROM messages
|
|
9544
|
-
WHERE target_agent = ? AND status IN ('pending', 'delivered')
|
|
10599
|
+
WHERE target_agent = ? AND status IN ('pending', 'delivered')${scope.sql}
|
|
9545
10600
|
ORDER BY id`,
|
|
9546
|
-
args: [targetAgent]
|
|
10601
|
+
args: [targetAgent, ...scope.args]
|
|
9547
10602
|
});
|
|
9548
10603
|
return result.rows.map((row) => rowToMessage(row));
|
|
9549
10604
|
}
|
|
9550
|
-
async function markRead(messageId) {
|
|
10605
|
+
async function markRead(messageId, sessionScope) {
|
|
9551
10606
|
const client = getClient();
|
|
10607
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
9552
10608
|
await client.execute({
|
|
9553
|
-
sql:
|
|
9554
|
-
|
|
10609
|
+
sql: `UPDATE messages SET status = 'read'
|
|
10610
|
+
WHERE id = ? AND status IN ('pending', 'delivered')${scope.sql}`,
|
|
10611
|
+
args: [messageId, ...scope.args]
|
|
9555
10612
|
});
|
|
9556
10613
|
}
|
|
9557
|
-
async function markAcknowledged(messageId) {
|
|
10614
|
+
async function markAcknowledged(messageId, sessionScope) {
|
|
9558
10615
|
const client = getClient();
|
|
10616
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
9559
10617
|
await client.execute({
|
|
9560
|
-
sql:
|
|
9561
|
-
|
|
10618
|
+
sql: `UPDATE messages SET status = 'acknowledged', processed_at = ?
|
|
10619
|
+
WHERE id = ? AND status = 'read'${scope.sql}`,
|
|
10620
|
+
args: [(/* @__PURE__ */ new Date()).toISOString(), messageId, ...scope.args]
|
|
9562
10621
|
});
|
|
9563
10622
|
}
|
|
9564
|
-
async function markProcessed(messageId) {
|
|
10623
|
+
async function markProcessed(messageId, sessionScope) {
|
|
9565
10624
|
const client = getClient();
|
|
10625
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
9566
10626
|
await client.execute({
|
|
9567
|
-
sql:
|
|
9568
|
-
|
|
10627
|
+
sql: `UPDATE messages SET status = 'processed', processed_at = ?
|
|
10628
|
+
WHERE id = ?${scope.sql}`,
|
|
10629
|
+
args: [(/* @__PURE__ */ new Date()).toISOString(), messageId, ...scope.args]
|
|
9569
10630
|
});
|
|
9570
10631
|
}
|
|
9571
|
-
async function getMessageStatus(messageId) {
|
|
10632
|
+
async function getMessageStatus(messageId, sessionScope) {
|
|
9572
10633
|
const client = getClient();
|
|
10634
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
9573
10635
|
const result = await client.execute({
|
|
9574
|
-
sql:
|
|
9575
|
-
args: [messageId]
|
|
10636
|
+
sql: `SELECT status FROM messages WHERE id = ?${scope.sql}`,
|
|
10637
|
+
args: [messageId, ...scope.args]
|
|
9576
10638
|
});
|
|
9577
10639
|
return result.rows[0]?.status ?? null;
|
|
9578
10640
|
}
|
|
9579
|
-
async function getUnacknowledgedMessages(targetAgent) {
|
|
10641
|
+
async function getUnacknowledgedMessages(targetAgent, sessionScope) {
|
|
9580
10642
|
const client = getClient();
|
|
10643
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
9581
10644
|
const result = await client.execute({
|
|
9582
10645
|
sql: `SELECT * FROM messages
|
|
9583
|
-
WHERE target_agent = ? AND status IN ('pending', 'delivered', 'read')
|
|
10646
|
+
WHERE target_agent = ? AND status IN ('pending', 'delivered', 'read')${scope.sql}
|
|
9584
10647
|
ORDER BY id`,
|
|
9585
|
-
args: [targetAgent]
|
|
10648
|
+
args: [targetAgent, ...scope.args]
|
|
9586
10649
|
});
|
|
9587
10650
|
return result.rows.map((row) => rowToMessage(row));
|
|
9588
10651
|
}
|
|
9589
|
-
async function getReadMessages(targetAgent) {
|
|
10652
|
+
async function getReadMessages(targetAgent, sessionScope) {
|
|
9590
10653
|
const client = getClient();
|
|
10654
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
9591
10655
|
const result = await client.execute({
|
|
9592
|
-
sql:
|
|
9593
|
-
|
|
10656
|
+
sql: `SELECT * FROM messages
|
|
10657
|
+
WHERE target_agent = ? AND status = 'read'${scope.sql}
|
|
10658
|
+
ORDER BY id`,
|
|
10659
|
+
args: [targetAgent, ...scope.args]
|
|
9594
10660
|
});
|
|
9595
10661
|
return result.rows.map((row) => rowToMessage(row));
|
|
9596
10662
|
}
|
|
9597
|
-
async function markFailed(messageId, reason) {
|
|
10663
|
+
async function markFailed(messageId, reason, sessionScope) {
|
|
9598
10664
|
const client = getClient();
|
|
10665
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
9599
10666
|
await client.execute({
|
|
9600
|
-
sql:
|
|
9601
|
-
|
|
10667
|
+
sql: `UPDATE messages SET status = 'failed', failed_at = ?, failure_reason = ?
|
|
10668
|
+
WHERE id = ?${scope.sql}`,
|
|
10669
|
+
args: [(/* @__PURE__ */ new Date()).toISOString(), reason, messageId, ...scope.args]
|
|
9602
10670
|
});
|
|
9603
10671
|
}
|
|
9604
|
-
async function getFailedMessages() {
|
|
10672
|
+
async function getFailedMessages(sessionScope) {
|
|
9605
10673
|
const client = getClient();
|
|
10674
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
9606
10675
|
const result = await client.execute({
|
|
9607
|
-
sql:
|
|
9608
|
-
args: []
|
|
10676
|
+
sql: `SELECT * FROM messages WHERE status = 'failed'${scope.sql} ORDER BY created_at DESC`,
|
|
10677
|
+
args: [...scope.args]
|
|
9609
10678
|
});
|
|
9610
10679
|
return result.rows.map((row) => rowToMessage(row));
|
|
9611
10680
|
}
|
|
9612
|
-
async function retryPendingMessages() {
|
|
10681
|
+
async function retryPendingMessages(sessionScope) {
|
|
9613
10682
|
const client = getClient();
|
|
10683
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
9614
10684
|
const result = await client.execute({
|
|
9615
10685
|
sql: `SELECT * FROM messages
|
|
9616
|
-
WHERE status = 'pending' AND retry_count <
|
|
10686
|
+
WHERE status = 'pending' AND retry_count < ?${scope.sql}
|
|
9617
10687
|
ORDER BY id`,
|
|
9618
|
-
args: [MAX_RETRIES3]
|
|
10688
|
+
args: [MAX_RETRIES3, ...scope.args]
|
|
9619
10689
|
});
|
|
9620
10690
|
let delivered = 0;
|
|
9621
10691
|
for (const row of result.rows) {
|
|
@@ -9634,16 +10704,17 @@ var init_messaging = __esm({
|
|
|
9634
10704
|
"use strict";
|
|
9635
10705
|
init_database();
|
|
9636
10706
|
init_tmux_routing();
|
|
10707
|
+
init_task_scope();
|
|
9637
10708
|
MAX_RETRIES3 = 10;
|
|
9638
10709
|
_wsClientSend = null;
|
|
9639
10710
|
}
|
|
9640
10711
|
});
|
|
9641
10712
|
|
|
9642
10713
|
// src/automation/trigger-engine.ts
|
|
9643
|
-
import { readFileSync as
|
|
10714
|
+
import { readFileSync as readFileSync14, writeFileSync as writeFileSync9, existsSync as existsSync17, mkdirSync as mkdirSync9 } from "fs";
|
|
9644
10715
|
import { randomUUID as randomUUID8 } from "crypto";
|
|
9645
|
-
import
|
|
9646
|
-
import
|
|
10716
|
+
import path21 from "path";
|
|
10717
|
+
import os13 from "os";
|
|
9647
10718
|
function substituteTemplate(template, record) {
|
|
9648
10719
|
return template.replace(
|
|
9649
10720
|
/\{\{(\w+(?:\.\w+)*)\}\}/g,
|
|
@@ -9696,9 +10767,9 @@ function evaluateConditions(conditions, record) {
|
|
|
9696
10767
|
return conditions.every((c) => evaluateCondition(c, record));
|
|
9697
10768
|
}
|
|
9698
10769
|
function loadTriggers(project) {
|
|
9699
|
-
if (!
|
|
10770
|
+
if (!existsSync17(TRIGGERS_PATH)) return [];
|
|
9700
10771
|
try {
|
|
9701
|
-
const raw =
|
|
10772
|
+
const raw = readFileSync14(TRIGGERS_PATH, "utf-8");
|
|
9702
10773
|
const all = JSON.parse(raw);
|
|
9703
10774
|
if (!Array.isArray(all)) return [];
|
|
9704
10775
|
if (project) {
|
|
@@ -9938,7 +11009,7 @@ var TRIGGERS_PATH, GRAPH_API_VERSION;
|
|
|
9938
11009
|
var init_trigger_engine = __esm({
|
|
9939
11010
|
"src/automation/trigger-engine.ts"() {
|
|
9940
11011
|
"use strict";
|
|
9941
|
-
TRIGGERS_PATH =
|
|
11012
|
+
TRIGGERS_PATH = path21.join(os13.homedir(), ".exe-os", "triggers.json");
|
|
9942
11013
|
GRAPH_API_VERSION = "v21.0";
|
|
9943
11014
|
}
|
|
9944
11015
|
});
|
|
@@ -10001,9 +11072,9 @@ var init_crm_webhook = __esm({
|
|
|
10001
11072
|
});
|
|
10002
11073
|
|
|
10003
11074
|
// src/bin/exe-gateway.ts
|
|
10004
|
-
import { existsSync as
|
|
10005
|
-
import
|
|
10006
|
-
import
|
|
11075
|
+
import { existsSync as existsSync18, readFileSync as readFileSync15 } from "fs";
|
|
11076
|
+
import path22 from "path";
|
|
11077
|
+
import os14 from "os";
|
|
10007
11078
|
|
|
10008
11079
|
// src/gateway/webhook-server.ts
|
|
10009
11080
|
import {
|
|
@@ -10245,11 +11316,11 @@ init_crm_bridge();
|
|
|
10245
11316
|
|
|
10246
11317
|
// src/lib/pipeline-router.ts
|
|
10247
11318
|
init_database();
|
|
10248
|
-
import
|
|
11319
|
+
import crypto3 from "crypto";
|
|
10249
11320
|
async function sinkConversationStore(msg, agentResponse, agentName) {
|
|
10250
11321
|
try {
|
|
10251
11322
|
const client = getClient();
|
|
10252
|
-
const id =
|
|
11323
|
+
const id = crypto3.randomUUID();
|
|
10253
11324
|
const mediaJson = msg.media ? JSON.stringify(msg.media) : null;
|
|
10254
11325
|
await client.execute({
|
|
10255
11326
|
sql: `INSERT INTO conversations
|
|
@@ -10299,7 +11370,7 @@ async function sinkMemory(msg, agentResponse, agentName) {
|
|
|
10299
11370
|
].filter(Boolean).join("\n");
|
|
10300
11371
|
const vector = await embed2(rawText);
|
|
10301
11372
|
await writeMemory2({
|
|
10302
|
-
id:
|
|
11373
|
+
id: crypto3.randomUUID(),
|
|
10303
11374
|
agent_id: agentName ?? "gateway",
|
|
10304
11375
|
agent_role: "gateway",
|
|
10305
11376
|
session_id: `gateway-${msg.platform}`,
|
|
@@ -10969,18 +12040,18 @@ var BotRegistry = class {
|
|
|
10969
12040
|
|
|
10970
12041
|
// src/bin/exe-gateway.ts
|
|
10971
12042
|
init_employees();
|
|
10972
|
-
var CONFIG_DIR =
|
|
10973
|
-
var CONFIG_PATH3 =
|
|
12043
|
+
var CONFIG_DIR = process.env.EXE_GATEWAY_HOME || path22.join(os14.homedir(), ".exe-os");
|
|
12044
|
+
var CONFIG_PATH3 = process.env.EXE_GATEWAY_CONFIG || path22.join(CONFIG_DIR, "gateway.json");
|
|
10974
12045
|
var DEFAULT_PORT = 3100;
|
|
10975
12046
|
function loadConfig2() {
|
|
10976
|
-
if (!
|
|
12047
|
+
if (!existsSync18(CONFIG_PATH3)) {
|
|
10977
12048
|
console.log(
|
|
10978
12049
|
`[exe-gateway] No config at ${CONFIG_PATH3} \u2014 using defaults (port ${DEFAULT_PORT})`
|
|
10979
12050
|
);
|
|
10980
12051
|
return {};
|
|
10981
12052
|
}
|
|
10982
12053
|
try {
|
|
10983
|
-
const raw =
|
|
12054
|
+
const raw = readFileSync15(CONFIG_PATH3, "utf-8");
|
|
10984
12055
|
return JSON.parse(raw);
|
|
10985
12056
|
} catch (err) {
|
|
10986
12057
|
console.error(
|