@askexenow/exe-os 0.9.10 → 0.9.12
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/cli.js +150 -8
- package/dist/bin/exe-boot.js +150 -8
- package/dist/bin/exe-dispatch.js +155 -8
- package/dist/bin/exe-gateway.js +531 -166
- package/dist/bin/exe-link.js +6 -0
- package/dist/bin/exe-session-cleanup.js +28 -8
- package/dist/bin/git-sweep.js +157 -10
- package/dist/bin/scan-tasks.js +155 -8
- package/dist/bin/setup.js +6 -0
- package/dist/gateway/index.js +144 -8
- package/dist/hooks/bug-report-worker.js +394 -258
- package/dist/hooks/codex-stop-task-finalizer.js +10 -8
- package/dist/hooks/commit-complete.js +155 -8
- package/dist/hooks/ingest-worker.js +155 -8
- package/dist/hooks/pre-compact.js +155 -8
- package/dist/hooks/prompt-submit.js +28 -8
- package/dist/hooks/session-end.js +157 -10
- package/dist/hooks/summary-worker.js +6 -0
- package/dist/index.js +144 -8
- package/dist/lib/cloud-sync.js +6 -0
- package/dist/lib/exe-daemon.js +45 -8
- package/dist/lib/messaging.js +10 -8
- package/dist/lib/tasks.js +460 -313
- package/dist/lib/tmux-routing.js +155 -8
- package/dist/mcp/server.js +396 -254
- package/dist/mcp/tools/create-task.js +449 -313
- package/dist/mcp/tools/send-message.js +10 -8
- package/dist/mcp/tools/update-task.js +460 -313
- package/dist/runtime/index.js +155 -8
- package/dist/tui/App.js +144 -8
- package/package.json +1 -1
package/dist/bin/exe-gateway.js
CHANGED
|
@@ -448,13 +448,13 @@ __export(config_exports, {
|
|
|
448
448
|
});
|
|
449
449
|
import { readFile, writeFile } from "fs/promises";
|
|
450
450
|
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
451
|
-
import
|
|
452
|
-
import
|
|
451
|
+
import path2 from "path";
|
|
452
|
+
import os2 from "os";
|
|
453
453
|
function resolveDataDir() {
|
|
454
454
|
if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
|
|
455
455
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
456
|
-
const newDir =
|
|
457
|
-
const legacyDir =
|
|
456
|
+
const newDir = path2.join(os2.homedir(), ".exe-os");
|
|
457
|
+
const legacyDir = path2.join(os2.homedir(), ".exe-mem");
|
|
458
458
|
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
459
459
|
try {
|
|
460
460
|
renameSync(legacyDir, newDir);
|
|
@@ -519,9 +519,9 @@ function normalizeAutoUpdate(raw) {
|
|
|
519
519
|
async function loadConfig() {
|
|
520
520
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
521
521
|
await ensurePrivateDir(dir);
|
|
522
|
-
const configPath =
|
|
522
|
+
const configPath = path2.join(dir, "config.json");
|
|
523
523
|
if (!existsSync2(configPath)) {
|
|
524
|
-
return { ...DEFAULT_CONFIG, dbPath:
|
|
524
|
+
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
525
525
|
}
|
|
526
526
|
const raw = await readFile(configPath, "utf-8");
|
|
527
527
|
try {
|
|
@@ -540,20 +540,20 @@ async function loadConfig() {
|
|
|
540
540
|
normalizeScalingRoadmap(migratedCfg);
|
|
541
541
|
normalizeSessionLifecycle(migratedCfg);
|
|
542
542
|
normalizeAutoUpdate(migratedCfg);
|
|
543
|
-
const config2 = { ...DEFAULT_CONFIG, dbPath:
|
|
543
|
+
const config2 = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
|
|
544
544
|
if (config2.dbPath.startsWith("~")) {
|
|
545
|
-
config2.dbPath = config2.dbPath.replace(/^~/,
|
|
545
|
+
config2.dbPath = config2.dbPath.replace(/^~/, os2.homedir());
|
|
546
546
|
}
|
|
547
547
|
return config2;
|
|
548
548
|
} catch {
|
|
549
|
-
return { ...DEFAULT_CONFIG, dbPath:
|
|
549
|
+
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
550
550
|
}
|
|
551
551
|
}
|
|
552
552
|
function loadConfigSync() {
|
|
553
553
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
554
|
-
const configPath =
|
|
554
|
+
const configPath = path2.join(dir, "config.json");
|
|
555
555
|
if (!existsSync2(configPath)) {
|
|
556
|
-
return { ...DEFAULT_CONFIG, dbPath:
|
|
556
|
+
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
557
557
|
}
|
|
558
558
|
try {
|
|
559
559
|
const raw = readFileSync(configPath, "utf-8");
|
|
@@ -563,15 +563,15 @@ function loadConfigSync() {
|
|
|
563
563
|
normalizeScalingRoadmap(migratedCfg);
|
|
564
564
|
normalizeSessionLifecycle(migratedCfg);
|
|
565
565
|
normalizeAutoUpdate(migratedCfg);
|
|
566
|
-
return { ...DEFAULT_CONFIG, dbPath:
|
|
566
|
+
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
|
|
567
567
|
} catch {
|
|
568
|
-
return { ...DEFAULT_CONFIG, dbPath:
|
|
568
|
+
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
569
569
|
}
|
|
570
570
|
}
|
|
571
571
|
async function saveConfig(config2) {
|
|
572
572
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
573
573
|
await ensurePrivateDir(dir);
|
|
574
|
-
const configPath =
|
|
574
|
+
const configPath = path2.join(dir, "config.json");
|
|
575
575
|
await writeFile(configPath, JSON.stringify(config2, null, 2) + "\n");
|
|
576
576
|
await enforcePrivateFile(configPath);
|
|
577
577
|
}
|
|
@@ -595,10 +595,10 @@ var init_config = __esm({
|
|
|
595
595
|
"use strict";
|
|
596
596
|
init_secure_files();
|
|
597
597
|
EXE_AI_DIR = resolveDataDir();
|
|
598
|
-
DB_PATH =
|
|
599
|
-
MODELS_DIR =
|
|
600
|
-
CONFIG_PATH =
|
|
601
|
-
LEGACY_LANCE_PATH =
|
|
598
|
+
DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
|
|
599
|
+
MODELS_DIR = path2.join(EXE_AI_DIR, "models");
|
|
600
|
+
CONFIG_PATH = path2.join(EXE_AI_DIR, "config.json");
|
|
601
|
+
LEGACY_LANCE_PATH = path2.join(EXE_AI_DIR, "local.lance");
|
|
602
602
|
CURRENT_CONFIG_VERSION = 1;
|
|
603
603
|
DEFAULT_CONFIG = {
|
|
604
604
|
config_version: CURRENT_CONFIG_VERSION,
|
|
@@ -711,7 +711,7 @@ __export(agent_config_exports, {
|
|
|
711
711
|
setAgentRuntime: () => setAgentRuntime
|
|
712
712
|
});
|
|
713
713
|
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
|
|
714
|
-
import
|
|
714
|
+
import path3 from "path";
|
|
715
715
|
function loadAgentConfig() {
|
|
716
716
|
if (!existsSync3(AGENT_CONFIG_PATH)) return {};
|
|
717
717
|
try {
|
|
@@ -721,7 +721,7 @@ function loadAgentConfig() {
|
|
|
721
721
|
}
|
|
722
722
|
}
|
|
723
723
|
function saveAgentConfig(config2) {
|
|
724
|
-
const dir =
|
|
724
|
+
const dir = path3.dirname(AGENT_CONFIG_PATH);
|
|
725
725
|
ensurePrivateDirSync(dir);
|
|
726
726
|
writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config2, null, 2) + "\n", "utf-8");
|
|
727
727
|
enforcePrivateFileSync(AGENT_CONFIG_PATH);
|
|
@@ -765,7 +765,7 @@ var init_agent_config = __esm({
|
|
|
765
765
|
init_config();
|
|
766
766
|
init_runtime_table();
|
|
767
767
|
init_secure_files();
|
|
768
|
-
AGENT_CONFIG_PATH =
|
|
768
|
+
AGENT_CONFIG_PATH = path3.join(EXE_AI_DIR, "agent-config.json");
|
|
769
769
|
KNOWN_RUNTIMES = {
|
|
770
770
|
claude: ["claude-opus-4", "claude-sonnet-4", "claude-haiku-4.5"],
|
|
771
771
|
codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
|
|
@@ -814,8 +814,8 @@ __export(employees_exports, {
|
|
|
814
814
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
815
815
|
import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
816
816
|
import { execSync } from "child_process";
|
|
817
|
-
import
|
|
818
|
-
import
|
|
817
|
+
import path4 from "path";
|
|
818
|
+
import os3 from "os";
|
|
819
819
|
function normalizeRole(role) {
|
|
820
820
|
return (role ?? "").trim().toLowerCase();
|
|
821
821
|
}
|
|
@@ -862,7 +862,7 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
|
862
862
|
}
|
|
863
863
|
}
|
|
864
864
|
async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
865
|
-
await mkdir2(
|
|
865
|
+
await mkdir2(path4.dirname(employeesPath), { recursive: true });
|
|
866
866
|
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
867
867
|
}
|
|
868
868
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
@@ -913,7 +913,7 @@ function addEmployee(employees, employee) {
|
|
|
913
913
|
function appendToCoordinatorTeam(employee) {
|
|
914
914
|
const coordinator = getCoordinatorEmployee(loadEmployeesSync());
|
|
915
915
|
if (!coordinator) return;
|
|
916
|
-
const idPath =
|
|
916
|
+
const idPath = path4.join(IDENTITY_DIR, `${coordinator.name}.md`);
|
|
917
917
|
if (!existsSync4(idPath)) return;
|
|
918
918
|
const content = readFileSync3(idPath, "utf-8");
|
|
919
919
|
if (content.includes(`**${capitalize(employee.name)}`)) return;
|
|
@@ -965,9 +965,9 @@ async function normalizeRosterCase(rosterPath) {
|
|
|
965
965
|
emp.name = emp.name.toLowerCase();
|
|
966
966
|
changed = true;
|
|
967
967
|
try {
|
|
968
|
-
const identityDir =
|
|
969
|
-
const oldPath =
|
|
970
|
-
const newPath =
|
|
968
|
+
const identityDir = path4.join(os3.homedir(), ".exe-os", "identity");
|
|
969
|
+
const oldPath = path4.join(identityDir, `${oldName}.md`);
|
|
970
|
+
const newPath = path4.join(identityDir, `${emp.name}.md`);
|
|
971
971
|
if (existsSync4(oldPath) && !existsSync4(newPath)) {
|
|
972
972
|
renameSync2(oldPath, newPath);
|
|
973
973
|
} else if (existsSync4(oldPath) && oldPath !== newPath) {
|
|
@@ -1002,7 +1002,7 @@ function registerBinSymlinks(name) {
|
|
|
1002
1002
|
errors.push("Could not find 'exe-os' in PATH");
|
|
1003
1003
|
return { created, skipped, errors };
|
|
1004
1004
|
}
|
|
1005
|
-
const binDir =
|
|
1005
|
+
const binDir = path4.dirname(exeBinPath);
|
|
1006
1006
|
let target;
|
|
1007
1007
|
try {
|
|
1008
1008
|
target = readlinkSync(exeBinPath);
|
|
@@ -1012,7 +1012,7 @@ function registerBinSymlinks(name) {
|
|
|
1012
1012
|
}
|
|
1013
1013
|
for (const suffix of ["", "-opencode"]) {
|
|
1014
1014
|
const linkName = `${name}${suffix}`;
|
|
1015
|
-
const linkPath =
|
|
1015
|
+
const linkPath = path4.join(binDir, linkName);
|
|
1016
1016
|
if (existsSync4(linkPath)) {
|
|
1017
1017
|
skipped.push(linkName);
|
|
1018
1018
|
continue;
|
|
@@ -1031,20 +1031,20 @@ var init_employees = __esm({
|
|
|
1031
1031
|
"src/lib/employees.ts"() {
|
|
1032
1032
|
"use strict";
|
|
1033
1033
|
init_config();
|
|
1034
|
-
EMPLOYEES_PATH =
|
|
1034
|
+
EMPLOYEES_PATH = path4.join(EXE_AI_DIR, "exe-employees.json");
|
|
1035
1035
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
1036
1036
|
COORDINATOR_ROLE = "COO";
|
|
1037
1037
|
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
1038
|
-
IDENTITY_DIR =
|
|
1038
|
+
IDENTITY_DIR = path4.join(EXE_AI_DIR, "identity");
|
|
1039
1039
|
TEAM_SECTION_RE = /^## Team\b.*$/m;
|
|
1040
1040
|
}
|
|
1041
1041
|
});
|
|
1042
1042
|
|
|
1043
1043
|
// src/lib/database-adapter.ts
|
|
1044
|
-
import
|
|
1045
|
-
import
|
|
1046
|
-
import { createRequire } from "module";
|
|
1047
|
-
import { pathToFileURL } from "url";
|
|
1044
|
+
import os4 from "os";
|
|
1045
|
+
import path5 from "path";
|
|
1046
|
+
import { createRequire as createRequire2 } from "module";
|
|
1047
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
1048
1048
|
function quotedIdentifier(identifier) {
|
|
1049
1049
|
return `"${identifier.replace(/"/g, '""')}"`;
|
|
1050
1050
|
}
|
|
@@ -1346,17 +1346,17 @@ async function loadPrismaClient() {
|
|
|
1346
1346
|
prismaClientPromise = (async () => {
|
|
1347
1347
|
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
1348
1348
|
if (explicitPath) {
|
|
1349
|
-
const module2 = await import(
|
|
1349
|
+
const module2 = await import(pathToFileURL2(explicitPath).href);
|
|
1350
1350
|
const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
|
|
1351
1351
|
if (!PrismaClient2) {
|
|
1352
1352
|
throw new Error(`No PrismaClient export found at ${explicitPath}`);
|
|
1353
1353
|
}
|
|
1354
1354
|
return new PrismaClient2();
|
|
1355
1355
|
}
|
|
1356
|
-
const exeDbRoot = process.env.EXE_DB_ROOT ??
|
|
1357
|
-
const requireFromExeDb =
|
|
1356
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path5.join(os4.homedir(), "exe-db");
|
|
1357
|
+
const requireFromExeDb = createRequire2(path5.join(exeDbRoot, "package.json"));
|
|
1358
1358
|
const prismaEntry = requireFromExeDb.resolve("@prisma/client");
|
|
1359
|
-
const module = await import(
|
|
1359
|
+
const module = await import(pathToFileURL2(prismaEntry).href);
|
|
1360
1360
|
const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
|
|
1361
1361
|
if (!PrismaClient) {
|
|
1362
1362
|
throw new Error(`No PrismaClient export found in ${prismaEntry}`);
|
|
@@ -2690,7 +2690,7 @@ var init_memory = __esm({
|
|
|
2690
2690
|
|
|
2691
2691
|
// src/lib/daemon-auth.ts
|
|
2692
2692
|
import crypto from "crypto";
|
|
2693
|
-
import
|
|
2693
|
+
import path6 from "path";
|
|
2694
2694
|
import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
2695
2695
|
function normalizeToken(token) {
|
|
2696
2696
|
if (!token) return null;
|
|
@@ -2721,17 +2721,17 @@ var init_daemon_auth = __esm({
|
|
|
2721
2721
|
"use strict";
|
|
2722
2722
|
init_config();
|
|
2723
2723
|
init_secure_files();
|
|
2724
|
-
DAEMON_TOKEN_PATH =
|
|
2724
|
+
DAEMON_TOKEN_PATH = path6.join(EXE_AI_DIR, "exed.token");
|
|
2725
2725
|
}
|
|
2726
2726
|
});
|
|
2727
2727
|
|
|
2728
2728
|
// src/lib/exe-daemon-client.ts
|
|
2729
2729
|
import net from "net";
|
|
2730
|
-
import
|
|
2730
|
+
import os5 from "os";
|
|
2731
2731
|
import { spawn } from "child_process";
|
|
2732
2732
|
import { randomUUID } from "crypto";
|
|
2733
2733
|
import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync5, openSync, closeSync, statSync } from "fs";
|
|
2734
|
-
import
|
|
2734
|
+
import path7 from "path";
|
|
2735
2735
|
import { fileURLToPath } from "url";
|
|
2736
2736
|
function handleData(chunk) {
|
|
2737
2737
|
_buffer += chunk.toString();
|
|
@@ -2782,17 +2782,17 @@ function cleanupStaleFiles() {
|
|
|
2782
2782
|
}
|
|
2783
2783
|
}
|
|
2784
2784
|
function findPackageRoot() {
|
|
2785
|
-
let dir =
|
|
2786
|
-
const { root } =
|
|
2785
|
+
let dir = path7.dirname(fileURLToPath(import.meta.url));
|
|
2786
|
+
const { root } = path7.parse(dir);
|
|
2787
2787
|
while (dir !== root) {
|
|
2788
|
-
if (existsSync6(
|
|
2789
|
-
dir =
|
|
2788
|
+
if (existsSync6(path7.join(dir, "package.json"))) return dir;
|
|
2789
|
+
dir = path7.dirname(dir);
|
|
2790
2790
|
}
|
|
2791
2791
|
return null;
|
|
2792
2792
|
}
|
|
2793
2793
|
function spawnDaemon() {
|
|
2794
|
-
const freeGB =
|
|
2795
|
-
const totalGB =
|
|
2794
|
+
const freeGB = os5.freemem() / (1024 * 1024 * 1024);
|
|
2795
|
+
const totalGB = os5.totalmem() / (1024 * 1024 * 1024);
|
|
2796
2796
|
if (totalGB <= 8) {
|
|
2797
2797
|
process.stderr.write(
|
|
2798
2798
|
`[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
|
|
@@ -2812,7 +2812,7 @@ function spawnDaemon() {
|
|
|
2812
2812
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
2813
2813
|
return;
|
|
2814
2814
|
}
|
|
2815
|
-
const daemonPath =
|
|
2815
|
+
const daemonPath = path7.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
2816
2816
|
if (!existsSync6(daemonPath)) {
|
|
2817
2817
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
2818
2818
|
`);
|
|
@@ -2822,7 +2822,7 @@ function spawnDaemon() {
|
|
|
2822
2822
|
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
2823
2823
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
2824
2824
|
`);
|
|
2825
|
-
const logPath =
|
|
2825
|
+
const logPath = path7.join(path7.dirname(SOCKET_PATH), "exed.log");
|
|
2826
2826
|
let stderrFd = "ignore";
|
|
2827
2827
|
try {
|
|
2828
2828
|
stderrFd = openSync(logPath, "a");
|
|
@@ -3112,9 +3112,9 @@ var init_exe_daemon_client = __esm({
|
|
|
3112
3112
|
"use strict";
|
|
3113
3113
|
init_config();
|
|
3114
3114
|
init_daemon_auth();
|
|
3115
|
-
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ??
|
|
3116
|
-
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ??
|
|
3117
|
-
SPAWN_LOCK_PATH =
|
|
3115
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path7.join(EXE_AI_DIR, "exed.sock");
|
|
3116
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path7.join(EXE_AI_DIR, "exed.pid");
|
|
3117
|
+
SPAWN_LOCK_PATH = path7.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
3118
3118
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
3119
3119
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
3120
3120
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
@@ -3171,8 +3171,8 @@ async function embedDirect(text) {
|
|
|
3171
3171
|
const llamaCpp = await import("node-llama-cpp");
|
|
3172
3172
|
const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
3173
3173
|
const { existsSync: existsSync19 } = await import("fs");
|
|
3174
|
-
const
|
|
3175
|
-
const modelPath =
|
|
3174
|
+
const path24 = await import("path");
|
|
3175
|
+
const modelPath = path24.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
|
|
3176
3176
|
if (!existsSync19(modelPath)) {
|
|
3177
3177
|
throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
|
|
3178
3178
|
}
|
|
@@ -3204,13 +3204,13 @@ var init_embedder = __esm({
|
|
|
3204
3204
|
// src/lib/keychain.ts
|
|
3205
3205
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
3206
3206
|
import { existsSync as existsSync7 } from "fs";
|
|
3207
|
-
import
|
|
3208
|
-
import
|
|
3207
|
+
import path8 from "path";
|
|
3208
|
+
import os6 from "os";
|
|
3209
3209
|
function getKeyDir() {
|
|
3210
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
3210
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path8.join(os6.homedir(), ".exe-os");
|
|
3211
3211
|
}
|
|
3212
3212
|
function getKeyPath() {
|
|
3213
|
-
return
|
|
3213
|
+
return path8.join(getKeyDir(), "master.key");
|
|
3214
3214
|
}
|
|
3215
3215
|
async function tryKeytar() {
|
|
3216
3216
|
try {
|
|
@@ -3233,7 +3233,7 @@ async function getMasterKey() {
|
|
|
3233
3233
|
const keyPath = getKeyPath();
|
|
3234
3234
|
if (!existsSync7(keyPath)) {
|
|
3235
3235
|
process.stderr.write(
|
|
3236
|
-
`[keychain] Key not found at ${keyPath} (HOME=${
|
|
3236
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os6.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
3237
3237
|
`
|
|
3238
3238
|
);
|
|
3239
3239
|
return null;
|
|
@@ -3272,7 +3272,7 @@ __export(shard_manager_exports, {
|
|
|
3272
3272
|
listShards: () => listShards,
|
|
3273
3273
|
shardExists: () => shardExists
|
|
3274
3274
|
});
|
|
3275
|
-
import
|
|
3275
|
+
import path9 from "path";
|
|
3276
3276
|
import { existsSync as existsSync8, mkdirSync as mkdirSync2, readdirSync } from "fs";
|
|
3277
3277
|
import { createClient as createClient2 } from "@libsql/client";
|
|
3278
3278
|
function initShardManager(encryptionKey) {
|
|
@@ -3307,7 +3307,7 @@ function getShardClient(projectName) {
|
|
|
3307
3307
|
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
3308
3308
|
evictLRU();
|
|
3309
3309
|
}
|
|
3310
|
-
const dbPath =
|
|
3310
|
+
const dbPath = path9.join(SHARDS_DIR, `${safeName}.db`);
|
|
3311
3311
|
const client = createClient2({
|
|
3312
3312
|
url: `file:${dbPath}`,
|
|
3313
3313
|
encryptionKey: _encryptionKey
|
|
@@ -3318,7 +3318,7 @@ function getShardClient(projectName) {
|
|
|
3318
3318
|
}
|
|
3319
3319
|
function shardExists(projectName) {
|
|
3320
3320
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
3321
|
-
return existsSync8(
|
|
3321
|
+
return existsSync8(path9.join(SHARDS_DIR, `${safeName}.db`));
|
|
3322
3322
|
}
|
|
3323
3323
|
function listShards() {
|
|
3324
3324
|
if (!existsSync8(SHARDS_DIR)) return [];
|
|
@@ -3568,7 +3568,7 @@ var init_shard_manager = __esm({
|
|
|
3568
3568
|
"src/lib/shard-manager.ts"() {
|
|
3569
3569
|
"use strict";
|
|
3570
3570
|
init_config();
|
|
3571
|
-
SHARDS_DIR =
|
|
3571
|
+
SHARDS_DIR = path9.join(EXE_AI_DIR, "shards");
|
|
3572
3572
|
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
3573
3573
|
MAX_OPEN_SHARDS = 10;
|
|
3574
3574
|
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
@@ -4346,8 +4346,8 @@ __export(wiki_client_exports, {
|
|
|
4346
4346
|
listDocuments: () => listDocuments,
|
|
4347
4347
|
listWorkspaces: () => listWorkspaces
|
|
4348
4348
|
});
|
|
4349
|
-
async function wikiFetch(config2,
|
|
4350
|
-
const url = `${config2.baseUrl}/api/v1${
|
|
4349
|
+
async function wikiFetch(config2, path24, method = "GET", body) {
|
|
4350
|
+
const url = `${config2.baseUrl}/api/v1${path24}`;
|
|
4351
4351
|
const headers = {
|
|
4352
4352
|
Authorization: `Bearer ${config2.apiKey}`,
|
|
4353
4353
|
"Content-Type": "application/json"
|
|
@@ -4380,7 +4380,7 @@ async function wikiFetch(config2, path23, method = "GET", body) {
|
|
|
4380
4380
|
}
|
|
4381
4381
|
}
|
|
4382
4382
|
if (!response.ok) {
|
|
4383
|
-
throw new Error(`Wiki API ${method} ${
|
|
4383
|
+
throw new Error(`Wiki API ${method} ${path24}: ${response.status} ${response.statusText}`);
|
|
4384
4384
|
}
|
|
4385
4385
|
return response.json();
|
|
4386
4386
|
} finally {
|
|
@@ -5411,10 +5411,10 @@ __export(license_exports, {
|
|
|
5411
5411
|
});
|
|
5412
5412
|
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync9, mkdirSync as mkdirSync3 } from "fs";
|
|
5413
5413
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
5414
|
-
import { createRequire as
|
|
5415
|
-
import { pathToFileURL as
|
|
5416
|
-
import
|
|
5417
|
-
import
|
|
5414
|
+
import { createRequire as createRequire3 } from "module";
|
|
5415
|
+
import { pathToFileURL as pathToFileURL3 } from "url";
|
|
5416
|
+
import os7 from "os";
|
|
5417
|
+
import path10 from "path";
|
|
5418
5418
|
import { jwtVerify, importSPKI } from "jose";
|
|
5419
5419
|
async function fetchRetry(url, init) {
|
|
5420
5420
|
try {
|
|
@@ -5425,7 +5425,7 @@ async function fetchRetry(url, init) {
|
|
|
5425
5425
|
}
|
|
5426
5426
|
}
|
|
5427
5427
|
function loadDeviceId() {
|
|
5428
|
-
const deviceJsonPath =
|
|
5428
|
+
const deviceJsonPath = path10.join(EXE_AI_DIR, "device.json");
|
|
5429
5429
|
try {
|
|
5430
5430
|
if (existsSync9(deviceJsonPath)) {
|
|
5431
5431
|
const data = JSON.parse(readFileSync6(deviceJsonPath, "utf8"));
|
|
@@ -5534,8 +5534,8 @@ function loadPrismaForLicense() {
|
|
|
5534
5534
|
if (_prismaFailed) return null;
|
|
5535
5535
|
const dbUrl = process.env.DATABASE_URL;
|
|
5536
5536
|
if (!dbUrl) {
|
|
5537
|
-
const exeDbRoot = process.env.EXE_DB_ROOT ??
|
|
5538
|
-
if (!existsSync9(
|
|
5537
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path10.join(os7.homedir(), "exe-db");
|
|
5538
|
+
if (!existsSync9(path10.join(exeDbRoot, "package.json"))) {
|
|
5539
5539
|
_prismaFailed = true;
|
|
5540
5540
|
return null;
|
|
5541
5541
|
}
|
|
@@ -5544,15 +5544,15 @@ function loadPrismaForLicense() {
|
|
|
5544
5544
|
_prismaPromise = (async () => {
|
|
5545
5545
|
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
5546
5546
|
if (explicitPath) {
|
|
5547
|
-
const mod2 = await import(
|
|
5547
|
+
const mod2 = await import(pathToFileURL3(explicitPath).href);
|
|
5548
5548
|
const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
|
|
5549
5549
|
if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
|
|
5550
5550
|
return new Ctor2();
|
|
5551
5551
|
}
|
|
5552
|
-
const exeDbRoot = process.env.EXE_DB_ROOT ??
|
|
5553
|
-
const req =
|
|
5552
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path10.join(os7.homedir(), "exe-db");
|
|
5553
|
+
const req = createRequire3(path10.join(exeDbRoot, "package.json"));
|
|
5554
5554
|
const entry = req.resolve("@prisma/client");
|
|
5555
|
-
const mod = await import(
|
|
5555
|
+
const mod = await import(pathToFileURL3(entry).href);
|
|
5556
5556
|
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
5557
5557
|
if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
|
|
5558
5558
|
return new Ctor();
|
|
@@ -5664,7 +5664,7 @@ async function checkLicense() {
|
|
|
5664
5664
|
let key = loadLicense();
|
|
5665
5665
|
if (!key) {
|
|
5666
5666
|
try {
|
|
5667
|
-
const configPath =
|
|
5667
|
+
const configPath = path10.join(EXE_AI_DIR, "config.json");
|
|
5668
5668
|
if (existsSync9(configPath)) {
|
|
5669
5669
|
const raw = JSON.parse(readFileSync6(configPath, "utf8"));
|
|
5670
5670
|
const cloud = raw.cloud;
|
|
@@ -5825,9 +5825,9 @@ var init_license = __esm({
|
|
|
5825
5825
|
"src/lib/license.ts"() {
|
|
5826
5826
|
"use strict";
|
|
5827
5827
|
init_config();
|
|
5828
|
-
LICENSE_PATH =
|
|
5829
|
-
CACHE_PATH =
|
|
5830
|
-
DEVICE_ID_PATH =
|
|
5828
|
+
LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
|
|
5829
|
+
CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
|
|
5830
|
+
DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
|
|
5831
5831
|
API_BASE = "https://askexe.com/cloud";
|
|
5832
5832
|
RETRY_DELAY_MS = 500;
|
|
5833
5833
|
LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
@@ -6727,16 +6727,16 @@ __export(imessage_exports, {
|
|
|
6727
6727
|
});
|
|
6728
6728
|
import { execFile } from "child_process";
|
|
6729
6729
|
import { promisify } from "util";
|
|
6730
|
-
import
|
|
6731
|
-
import
|
|
6730
|
+
import os8 from "os";
|
|
6731
|
+
import path11 from "path";
|
|
6732
6732
|
var execFileAsync, POLL_INTERVAL_MS, MESSAGES_DB_PATH, IMessageAdapter;
|
|
6733
6733
|
var init_imessage = __esm({
|
|
6734
6734
|
"src/gateway/adapters/imessage.ts"() {
|
|
6735
6735
|
"use strict";
|
|
6736
6736
|
execFileAsync = promisify(execFile);
|
|
6737
6737
|
POLL_INTERVAL_MS = 5e3;
|
|
6738
|
-
MESSAGES_DB_PATH =
|
|
6739
|
-
process.env.HOME ??
|
|
6738
|
+
MESSAGES_DB_PATH = path11.join(
|
|
6739
|
+
process.env.HOME ?? os8.homedir(),
|
|
6740
6740
|
"Library/Messages/chat.db"
|
|
6741
6741
|
);
|
|
6742
6742
|
IMessageAdapter = class {
|
|
@@ -7049,9 +7049,9 @@ __export(webhook_exports, {
|
|
|
7049
7049
|
WebhookAdapter: () => WebhookAdapter
|
|
7050
7050
|
});
|
|
7051
7051
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
7052
|
-
function resolvePath(obj,
|
|
7052
|
+
function resolvePath(obj, path24) {
|
|
7053
7053
|
let current = obj;
|
|
7054
|
-
for (const segment of
|
|
7054
|
+
for (const segment of path24.split(".")) {
|
|
7055
7055
|
if (current == null || typeof current !== "object") return void 0;
|
|
7056
7056
|
current = current[segment];
|
|
7057
7057
|
}
|
|
@@ -7201,10 +7201,10 @@ var init_whatsapp_accounts = __esm({
|
|
|
7201
7201
|
|
|
7202
7202
|
// src/lib/session-registry.ts
|
|
7203
7203
|
import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, existsSync as existsSync10 } from "fs";
|
|
7204
|
-
import
|
|
7205
|
-
import
|
|
7204
|
+
import path12 from "path";
|
|
7205
|
+
import os9 from "os";
|
|
7206
7206
|
function registerSession(entry) {
|
|
7207
|
-
const dir =
|
|
7207
|
+
const dir = path12.dirname(REGISTRY_PATH);
|
|
7208
7208
|
if (!existsSync10(dir)) {
|
|
7209
7209
|
mkdirSync5(dir, { recursive: true });
|
|
7210
7210
|
}
|
|
@@ -7229,7 +7229,7 @@ var REGISTRY_PATH;
|
|
|
7229
7229
|
var init_session_registry = __esm({
|
|
7230
7230
|
"src/lib/session-registry.ts"() {
|
|
7231
7231
|
"use strict";
|
|
7232
|
-
REGISTRY_PATH =
|
|
7232
|
+
REGISTRY_PATH = path12.join(os9.homedir(), ".exe-os", "session-registry.json");
|
|
7233
7233
|
}
|
|
7234
7234
|
});
|
|
7235
7235
|
|
|
@@ -7491,10 +7491,10 @@ __export(intercom_queue_exports, {
|
|
|
7491
7491
|
readQueue: () => readQueue
|
|
7492
7492
|
});
|
|
7493
7493
|
import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, renameSync as renameSync3, existsSync as existsSync11, mkdirSync as mkdirSync6 } from "fs";
|
|
7494
|
-
import
|
|
7495
|
-
import
|
|
7494
|
+
import path13 from "path";
|
|
7495
|
+
import os10 from "os";
|
|
7496
7496
|
function ensureDir() {
|
|
7497
|
-
const dir =
|
|
7497
|
+
const dir = path13.dirname(QUEUE_PATH);
|
|
7498
7498
|
if (!existsSync11(dir)) mkdirSync6(dir, { recursive: true });
|
|
7499
7499
|
}
|
|
7500
7500
|
function readQueue() {
|
|
@@ -7600,16 +7600,16 @@ var QUEUE_PATH, MAX_RETRIES2, TTL_MS, INTERCOM_LOG;
|
|
|
7600
7600
|
var init_intercom_queue = __esm({
|
|
7601
7601
|
"src/lib/intercom-queue.ts"() {
|
|
7602
7602
|
"use strict";
|
|
7603
|
-
QUEUE_PATH =
|
|
7603
|
+
QUEUE_PATH = path13.join(os10.homedir(), ".exe-os", "intercom-queue.json");
|
|
7604
7604
|
MAX_RETRIES2 = 5;
|
|
7605
7605
|
TTL_MS = 60 * 60 * 1e3;
|
|
7606
|
-
INTERCOM_LOG =
|
|
7606
|
+
INTERCOM_LOG = path13.join(os10.homedir(), ".exe-os", "intercom.log");
|
|
7607
7607
|
}
|
|
7608
7608
|
});
|
|
7609
7609
|
|
|
7610
7610
|
// src/lib/plan-limits.ts
|
|
7611
7611
|
import { readFileSync as readFileSync10, existsSync as existsSync12 } from "fs";
|
|
7612
|
-
import
|
|
7612
|
+
import path14 from "path";
|
|
7613
7613
|
function getLicenseSync() {
|
|
7614
7614
|
try {
|
|
7615
7615
|
if (!existsSync12(CACHE_PATH2)) return freeLicense();
|
|
@@ -7681,7 +7681,7 @@ var init_plan_limits = __esm({
|
|
|
7681
7681
|
this.name = "PlanLimitError";
|
|
7682
7682
|
}
|
|
7683
7683
|
};
|
|
7684
|
-
CACHE_PATH2 =
|
|
7684
|
+
CACHE_PATH2 = path14.join(EXE_AI_DIR, "license-cache.json");
|
|
7685
7685
|
}
|
|
7686
7686
|
});
|
|
7687
7687
|
|
|
@@ -7720,8 +7720,8 @@ var init_task_scope = __esm({
|
|
|
7720
7720
|
|
|
7721
7721
|
// src/lib/notifications.ts
|
|
7722
7722
|
import crypto4 from "crypto";
|
|
7723
|
-
import
|
|
7724
|
-
import
|
|
7723
|
+
import path15 from "path";
|
|
7724
|
+
import os11 from "os";
|
|
7725
7725
|
import {
|
|
7726
7726
|
readFileSync as readFileSync11,
|
|
7727
7727
|
readdirSync as readdirSync2,
|
|
@@ -7811,7 +7811,7 @@ var init_session_kill_telemetry = __esm({
|
|
|
7811
7811
|
|
|
7812
7812
|
// src/lib/project-name.ts
|
|
7813
7813
|
import { execSync as execSync4 } from "child_process";
|
|
7814
|
-
import
|
|
7814
|
+
import path16 from "path";
|
|
7815
7815
|
function getProjectName(cwd) {
|
|
7816
7816
|
const dir = cwd ?? process.cwd();
|
|
7817
7817
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
@@ -7824,7 +7824,7 @@ function getProjectName(cwd) {
|
|
|
7824
7824
|
timeout: 2e3,
|
|
7825
7825
|
stdio: ["pipe", "pipe", "pipe"]
|
|
7826
7826
|
}).trim();
|
|
7827
|
-
repoRoot =
|
|
7827
|
+
repoRoot = path16.dirname(gitCommonDir);
|
|
7828
7828
|
} catch {
|
|
7829
7829
|
repoRoot = execSync4("git rev-parse --show-toplevel", {
|
|
7830
7830
|
cwd: dir,
|
|
@@ -7833,11 +7833,11 @@ function getProjectName(cwd) {
|
|
|
7833
7833
|
stdio: ["pipe", "pipe", "pipe"]
|
|
7834
7834
|
}).trim();
|
|
7835
7835
|
}
|
|
7836
|
-
_cached2 =
|
|
7836
|
+
_cached2 = path16.basename(repoRoot);
|
|
7837
7837
|
_cachedCwd = dir;
|
|
7838
7838
|
return _cached2;
|
|
7839
7839
|
} catch {
|
|
7840
|
-
_cached2 =
|
|
7840
|
+
_cached2 = path16.basename(dir);
|
|
7841
7841
|
_cachedCwd = dir;
|
|
7842
7842
|
return _cached2;
|
|
7843
7843
|
}
|
|
@@ -7915,8 +7915,8 @@ var init_session_scope = __esm({
|
|
|
7915
7915
|
|
|
7916
7916
|
// src/lib/tasks-crud.ts
|
|
7917
7917
|
import crypto6 from "crypto";
|
|
7918
|
-
import
|
|
7919
|
-
import
|
|
7918
|
+
import path17 from "path";
|
|
7919
|
+
import os12 from "os";
|
|
7920
7920
|
import { execSync as execSync5 } from "child_process";
|
|
7921
7921
|
import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
|
|
7922
7922
|
import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
|
|
@@ -8113,8 +8113,8 @@ ${scopeMismatchWarning}` : scopeMismatchWarning;
|
|
|
8113
8113
|
}
|
|
8114
8114
|
if (input.baseDir) {
|
|
8115
8115
|
try {
|
|
8116
|
-
await mkdir4(
|
|
8117
|
-
await mkdir4(
|
|
8116
|
+
await mkdir4(path17.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
8117
|
+
await mkdir4(path17.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
8118
8118
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
8119
8119
|
await ensureGitignoreExe(input.baseDir);
|
|
8120
8120
|
} catch {
|
|
@@ -8150,9 +8150,9 @@ ${scopeMismatchWarning}` : scopeMismatchWarning;
|
|
|
8150
8150
|
});
|
|
8151
8151
|
if (input.baseDir) {
|
|
8152
8152
|
try {
|
|
8153
|
-
const EXE_OS_DIR =
|
|
8154
|
-
const mdPath =
|
|
8155
|
-
const mdDir =
|
|
8153
|
+
const EXE_OS_DIR = path17.join(os12.homedir(), ".exe-os");
|
|
8154
|
+
const mdPath = path17.join(EXE_OS_DIR, taskFile);
|
|
8155
|
+
const mdDir = path17.dirname(mdPath);
|
|
8156
8156
|
if (!existsSync14(mdDir)) await mkdir4(mdDir, { recursive: true });
|
|
8157
8157
|
const reviewer = input.reviewer ?? input.assignedBy;
|
|
8158
8158
|
const mdContent = `# ${input.title}
|
|
@@ -8453,7 +8453,7 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
8453
8453
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
8454
8454
|
}
|
|
8455
8455
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
8456
|
-
const archPath =
|
|
8456
|
+
const archPath = path17.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
8457
8457
|
try {
|
|
8458
8458
|
if (existsSync14(archPath)) return;
|
|
8459
8459
|
const template = [
|
|
@@ -8488,7 +8488,7 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
8488
8488
|
}
|
|
8489
8489
|
}
|
|
8490
8490
|
async function ensureGitignoreExe(baseDir) {
|
|
8491
|
-
const gitignorePath =
|
|
8491
|
+
const gitignorePath = path17.join(baseDir, ".gitignore");
|
|
8492
8492
|
try {
|
|
8493
8493
|
if (existsSync14(gitignorePath)) {
|
|
8494
8494
|
const content = readFileSync12(gitignorePath, "utf-8");
|
|
@@ -8522,8 +8522,35 @@ var init_tasks_crud = __esm({
|
|
|
8522
8522
|
});
|
|
8523
8523
|
|
|
8524
8524
|
// src/lib/tasks-review.ts
|
|
8525
|
-
|
|
8525
|
+
var tasks_review_exports = {};
|
|
8526
|
+
__export(tasks_review_exports, {
|
|
8527
|
+
cleanupOrphanedReviews: () => cleanupOrphanedReviews,
|
|
8528
|
+
cleanupReviewFile: () => cleanupReviewFile,
|
|
8529
|
+
countNewPendingReviewsSince: () => countNewPendingReviewsSince,
|
|
8530
|
+
countPendingReviews: () => countPendingReviews,
|
|
8531
|
+
createReviewForCompletedTask: () => createReviewForCompletedTask,
|
|
8532
|
+
formatAge: () => formatAge,
|
|
8533
|
+
getReviewChecklist: () => getReviewChecklist,
|
|
8534
|
+
isStale: () => isStale,
|
|
8535
|
+
listPendingReviews: () => listPendingReviews
|
|
8536
|
+
});
|
|
8537
|
+
import path18 from "path";
|
|
8526
8538
|
import { existsSync as existsSync15, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
|
|
8539
|
+
function formatAge(isoTimestamp) {
|
|
8540
|
+
if (!isoTimestamp) return "";
|
|
8541
|
+
const ms = Date.now() - new Date(isoTimestamp).getTime();
|
|
8542
|
+
if (ms < 0) return "just now";
|
|
8543
|
+
const minutes = Math.floor(ms / 6e4);
|
|
8544
|
+
if (minutes < 60) return `${minutes}m ago`;
|
|
8545
|
+
const hours = Math.floor(minutes / 60);
|
|
8546
|
+
if (hours < 24) return `${hours}h ago`;
|
|
8547
|
+
const days = Math.floor(hours / 24);
|
|
8548
|
+
return `${days}d ago`;
|
|
8549
|
+
}
|
|
8550
|
+
function isStale(isoTimestamp) {
|
|
8551
|
+
if (!isoTimestamp) return false;
|
|
8552
|
+
return Date.now() - new Date(isoTimestamp).getTime() > 24 * 60 * 60 * 1e3;
|
|
8553
|
+
}
|
|
8527
8554
|
async function countPendingReviews(sessionScope) {
|
|
8528
8555
|
const client = getClient();
|
|
8529
8556
|
const scope = strictSessionScopeFilter(
|
|
@@ -8644,6 +8671,95 @@ function getReviewChecklist(role, agent, taskSlug) {
|
|
|
8644
8671
|
]
|
|
8645
8672
|
};
|
|
8646
8673
|
}
|
|
8674
|
+
async function createReviewForCompletedTask(row, result, _baseDir, now) {
|
|
8675
|
+
const taskFile = String(row.task_file);
|
|
8676
|
+
const employees = await loadEmployees();
|
|
8677
|
+
const coordinatorName = getCoordinatorName(employees);
|
|
8678
|
+
if (isCoordinatorName(String(row.assigned_to), employees)) return;
|
|
8679
|
+
if (String(row.title).startsWith("Review:")) return;
|
|
8680
|
+
const fileName = taskFile.split("/").pop() ?? "";
|
|
8681
|
+
if (fileName.startsWith("review-") && String(row.assigned_by) === "system") return;
|
|
8682
|
+
if (fileName.startsWith("review-") && String(row.assigned_to) === "system") return;
|
|
8683
|
+
const client = getClient();
|
|
8684
|
+
const agent = String(row.assigned_to);
|
|
8685
|
+
const rawReviewer = row.reviewer || row.assigned_by;
|
|
8686
|
+
const reviewer = rawReviewer ? String(rawReviewer) : coordinatorName;
|
|
8687
|
+
const currentStatus = String(row.status ?? "");
|
|
8688
|
+
if (currentStatus === "done") return;
|
|
8689
|
+
const existingResult = String(row.result ?? "");
|
|
8690
|
+
if (existingResult.includes("## Review notes")) return;
|
|
8691
|
+
let reviewerRole = "unknown";
|
|
8692
|
+
try {
|
|
8693
|
+
const emp = getEmployee(employees, reviewer);
|
|
8694
|
+
if (emp) reviewerRole = emp.role;
|
|
8695
|
+
} catch {
|
|
8696
|
+
}
|
|
8697
|
+
const taskTitle = String(row.title);
|
|
8698
|
+
const taskSlug = taskFile.split("/").pop()?.replace(".md", "") ?? "unknown";
|
|
8699
|
+
const { lens, checklist } = getReviewChecklist(reviewerRole, agent, taskSlug);
|
|
8700
|
+
process.stderr.write(
|
|
8701
|
+
`[review] Annotating "${taskTitle}" for review by ${reviewer} (${reviewerRole})
|
|
8702
|
+
`
|
|
8703
|
+
);
|
|
8704
|
+
const reviewNotes = [
|
|
8705
|
+
`
|
|
8706
|
+
---
|
|
8707
|
+
## Review notes`,
|
|
8708
|
+
`Review lens: ${lens}`,
|
|
8709
|
+
`Reviewer: **${reviewer}** (${reviewerRole})`,
|
|
8710
|
+
"",
|
|
8711
|
+
"### Checklist",
|
|
8712
|
+
...checklist,
|
|
8713
|
+
"",
|
|
8714
|
+
"### Verdict",
|
|
8715
|
+
"- **Approved:** mark this task as done",
|
|
8716
|
+
"- **Needs work:** re-open with notes"
|
|
8717
|
+
].join("\n");
|
|
8718
|
+
const originalTaskId = String(row.id);
|
|
8719
|
+
const updatedResult = (result ?? "No result summary provided") + reviewNotes;
|
|
8720
|
+
await client.execute({
|
|
8721
|
+
sql: `UPDATE tasks SET result = ?, status = 'needs_review', updated_at = ?
|
|
8722
|
+
WHERE id = ?`,
|
|
8723
|
+
args: [updatedResult, now, originalTaskId]
|
|
8724
|
+
});
|
|
8725
|
+
orgBus.emit({
|
|
8726
|
+
type: "review_created",
|
|
8727
|
+
reviewId: originalTaskId,
|
|
8728
|
+
employee: agent,
|
|
8729
|
+
reviewer,
|
|
8730
|
+
timestamp: now
|
|
8731
|
+
});
|
|
8732
|
+
await writeNotification({
|
|
8733
|
+
agentId: agent,
|
|
8734
|
+
agentRole: String(row.assigned_to),
|
|
8735
|
+
event: "task_complete",
|
|
8736
|
+
project: String(row.project_name),
|
|
8737
|
+
summary: `completed "${taskTitle}" \u2014 ready for review`,
|
|
8738
|
+
taskFile
|
|
8739
|
+
});
|
|
8740
|
+
const originalPriority = String(row.priority).toLowerCase();
|
|
8741
|
+
const autoApprove = originalPriority === "p2" && result?.toLowerCase().includes("tests pass");
|
|
8742
|
+
if (!autoApprove) {
|
|
8743
|
+
try {
|
|
8744
|
+
const key = getSessionKey();
|
|
8745
|
+
const exeSession = getParentExe(key);
|
|
8746
|
+
if (exeSession) {
|
|
8747
|
+
sendIntercom(exeSession);
|
|
8748
|
+
}
|
|
8749
|
+
} catch {
|
|
8750
|
+
}
|
|
8751
|
+
}
|
|
8752
|
+
if (autoApprove) {
|
|
8753
|
+
process.stderr.write(
|
|
8754
|
+
`[review] Auto-approving "${taskTitle}" (P2 + tests pass)
|
|
8755
|
+
`
|
|
8756
|
+
);
|
|
8757
|
+
await client.execute({
|
|
8758
|
+
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE id = ?",
|
|
8759
|
+
args: [now, originalTaskId]
|
|
8760
|
+
});
|
|
8761
|
+
}
|
|
8762
|
+
}
|
|
8647
8763
|
async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
8648
8764
|
if (String(row.assigned_by) !== "system" || !taskFile.includes("review-")) return;
|
|
8649
8765
|
try {
|
|
@@ -8688,11 +8804,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
8688
8804
|
);
|
|
8689
8805
|
}
|
|
8690
8806
|
try {
|
|
8691
|
-
const cacheDir =
|
|
8807
|
+
const cacheDir = path18.join(EXE_AI_DIR, "session-cache");
|
|
8692
8808
|
if (existsSync15(cacheDir)) {
|
|
8693
8809
|
for (const f of readdirSync3(cacheDir)) {
|
|
8694
8810
|
if (f.startsWith("review-notified-")) {
|
|
8695
|
-
unlinkSync4(
|
|
8811
|
+
unlinkSync4(path18.join(cacheDir, f));
|
|
8696
8812
|
}
|
|
8697
8813
|
}
|
|
8698
8814
|
}
|
|
@@ -8714,7 +8830,7 @@ var init_tasks_review = __esm({
|
|
|
8714
8830
|
});
|
|
8715
8831
|
|
|
8716
8832
|
// src/lib/tasks-chain.ts
|
|
8717
|
-
import
|
|
8833
|
+
import path19 from "path";
|
|
8718
8834
|
import { readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
|
|
8719
8835
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
8720
8836
|
const client = getClient();
|
|
@@ -8731,7 +8847,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
8731
8847
|
});
|
|
8732
8848
|
for (const ur of unblockedRows.rows) {
|
|
8733
8849
|
try {
|
|
8734
|
-
const ubFile =
|
|
8850
|
+
const ubFile = path19.join(baseDir, String(ur.task_file));
|
|
8735
8851
|
let ubContent = await readFile4(ubFile, "utf-8");
|
|
8736
8852
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
8737
8853
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -9195,7 +9311,7 @@ __export(tasks_exports, {
|
|
|
9195
9311
|
updateTaskStatus: () => updateTaskStatus,
|
|
9196
9312
|
writeCheckpoint: () => writeCheckpoint
|
|
9197
9313
|
});
|
|
9198
|
-
import
|
|
9314
|
+
import path20 from "path";
|
|
9199
9315
|
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync7, unlinkSync as unlinkSync5 } from "fs";
|
|
9200
9316
|
async function createTask(input) {
|
|
9201
9317
|
const result = await createTaskCore(input);
|
|
@@ -9215,8 +9331,8 @@ async function updateTask(input) {
|
|
|
9215
9331
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
9216
9332
|
try {
|
|
9217
9333
|
const agent = String(row.assigned_to);
|
|
9218
|
-
const cacheDir =
|
|
9219
|
-
const cachePath =
|
|
9334
|
+
const cacheDir = path20.join(EXE_AI_DIR, "session-cache");
|
|
9335
|
+
const cachePath = path20.join(cacheDir, `current-task-${agent}.json`);
|
|
9220
9336
|
if (input.status === "in_progress") {
|
|
9221
9337
|
mkdirSync7(cacheDir, { recursive: true });
|
|
9222
9338
|
writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
@@ -9688,12 +9804,12 @@ __export(tmux_routing_exports, {
|
|
|
9688
9804
|
});
|
|
9689
9805
|
import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
|
|
9690
9806
|
import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, mkdirSync as mkdirSync8, existsSync as existsSync16, appendFileSync, readdirSync as readdirSync4 } from "fs";
|
|
9691
|
-
import
|
|
9692
|
-
import
|
|
9807
|
+
import path21 from "path";
|
|
9808
|
+
import os13 from "os";
|
|
9693
9809
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
9694
9810
|
import { unlinkSync as unlinkSync6 } from "fs";
|
|
9695
9811
|
function spawnLockPath(sessionName) {
|
|
9696
|
-
return
|
|
9812
|
+
return path21.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
9697
9813
|
}
|
|
9698
9814
|
function isProcessAlive(pid) {
|
|
9699
9815
|
try {
|
|
@@ -9730,8 +9846,8 @@ function releaseSpawnLock2(sessionName) {
|
|
|
9730
9846
|
function resolveBehaviorsExporterScript() {
|
|
9731
9847
|
try {
|
|
9732
9848
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
9733
|
-
const scriptPath =
|
|
9734
|
-
|
|
9849
|
+
const scriptPath = path21.join(
|
|
9850
|
+
path21.dirname(thisFile),
|
|
9735
9851
|
"..",
|
|
9736
9852
|
"bin",
|
|
9737
9853
|
"exe-export-behaviors.js"
|
|
@@ -9806,7 +9922,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
9806
9922
|
mkdirSync8(SESSION_CACHE, { recursive: true });
|
|
9807
9923
|
}
|
|
9808
9924
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
9809
|
-
const filePath =
|
|
9925
|
+
const filePath = path21.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
9810
9926
|
writeFileSync8(filePath, JSON.stringify({
|
|
9811
9927
|
parentExe: rootExe,
|
|
9812
9928
|
dispatchedBy: dispatchedBy || rootExe,
|
|
@@ -9815,7 +9931,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
9815
9931
|
}
|
|
9816
9932
|
function getParentExe(sessionKey) {
|
|
9817
9933
|
try {
|
|
9818
|
-
const data = JSON.parse(readFileSync13(
|
|
9934
|
+
const data = JSON.parse(readFileSync13(path21.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
9819
9935
|
return data.parentExe || null;
|
|
9820
9936
|
} catch {
|
|
9821
9937
|
return null;
|
|
@@ -9824,7 +9940,7 @@ function getParentExe(sessionKey) {
|
|
|
9824
9940
|
function getDispatchedBy(sessionKey) {
|
|
9825
9941
|
try {
|
|
9826
9942
|
const data = JSON.parse(readFileSync13(
|
|
9827
|
-
|
|
9943
|
+
path21.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
9828
9944
|
"utf8"
|
|
9829
9945
|
));
|
|
9830
9946
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -10007,20 +10123,22 @@ function sendIntercom(targetSession) {
|
|
|
10007
10123
|
logIntercom(`QUEUED \u2192 ${targetSession} (session busy)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
|
|
10008
10124
|
return "queued";
|
|
10009
10125
|
}
|
|
10010
|
-
|
|
10011
|
-
|
|
10012
|
-
|
|
10013
|
-
|
|
10014
|
-
|
|
10015
|
-
|
|
10016
|
-
|
|
10126
|
+
if (sessionState !== "idle") {
|
|
10127
|
+
try {
|
|
10128
|
+
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
10129
|
+
const agent = baseAgentName(rawAgent);
|
|
10130
|
+
const markerPath = path21.join(SESSION_CACHE, `current-task-${agent}.json`);
|
|
10131
|
+
if (existsSync16(markerPath)) {
|
|
10132
|
+
logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker + not idle \u2014 will auto-chain)`);
|
|
10133
|
+
return "debounced";
|
|
10134
|
+
}
|
|
10135
|
+
} catch {
|
|
10017
10136
|
}
|
|
10018
|
-
} catch {
|
|
10019
10137
|
}
|
|
10020
10138
|
try {
|
|
10021
10139
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
10022
10140
|
const agent = baseAgentName(rawAgent);
|
|
10023
|
-
const taskDir =
|
|
10141
|
+
const taskDir = path21.join(process.cwd(), "exe", agent);
|
|
10024
10142
|
if (existsSync16(taskDir)) {
|
|
10025
10143
|
const files = readdirSync4(taskDir).filter(
|
|
10026
10144
|
(f) => f.endsWith(".md") && f !== "DONE.txt"
|
|
@@ -10086,6 +10204,24 @@ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitl
|
|
|
10086
10204
|
try {
|
|
10087
10205
|
const sessions = transport.listSessions();
|
|
10088
10206
|
if (!sessions.includes(coordinatorSession)) return false;
|
|
10207
|
+
try {
|
|
10208
|
+
const { countPendingReviews: countPendingReviews2 } = (init_tasks_review(), __toCommonJS(tasks_review_exports));
|
|
10209
|
+
const pending = countPendingReviews2(coordinatorSession);
|
|
10210
|
+
if (pending instanceof Promise) {
|
|
10211
|
+
pending.then((count) => {
|
|
10212
|
+
if (count > 0) {
|
|
10213
|
+
execSync6(
|
|
10214
|
+
`tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
|
|
10215
|
+
{ timeout: 3e3 }
|
|
10216
|
+
);
|
|
10217
|
+
logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending)`);
|
|
10218
|
+
}
|
|
10219
|
+
}).catch(() => {
|
|
10220
|
+
});
|
|
10221
|
+
return true;
|
|
10222
|
+
}
|
|
10223
|
+
} catch {
|
|
10224
|
+
}
|
|
10089
10225
|
execSync6(
|
|
10090
10226
|
`tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
|
|
10091
10227
|
{ timeout: 3e3 }
|
|
@@ -10169,8 +10305,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
10169
10305
|
const transport = getTransport();
|
|
10170
10306
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
10171
10307
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
10172
|
-
const logDir =
|
|
10173
|
-
const logFile =
|
|
10308
|
+
const logDir = path21.join(os13.homedir(), ".exe-os", "session-logs");
|
|
10309
|
+
const logFile = path21.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
10174
10310
|
if (!existsSync16(logDir)) {
|
|
10175
10311
|
mkdirSync8(logDir, { recursive: true });
|
|
10176
10312
|
}
|
|
@@ -10178,14 +10314,14 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
10178
10314
|
let cleanupSuffix = "";
|
|
10179
10315
|
try {
|
|
10180
10316
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
10181
|
-
const cleanupScript =
|
|
10317
|
+
const cleanupScript = path21.join(path21.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
10182
10318
|
if (existsSync16(cleanupScript)) {
|
|
10183
10319
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
10184
10320
|
}
|
|
10185
10321
|
} catch {
|
|
10186
10322
|
}
|
|
10187
10323
|
try {
|
|
10188
|
-
const claudeJsonPath =
|
|
10324
|
+
const claudeJsonPath = path21.join(os13.homedir(), ".claude.json");
|
|
10189
10325
|
let claudeJson = {};
|
|
10190
10326
|
try {
|
|
10191
10327
|
claudeJson = JSON.parse(readFileSync13(claudeJsonPath, "utf8"));
|
|
@@ -10200,10 +10336,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
10200
10336
|
} catch {
|
|
10201
10337
|
}
|
|
10202
10338
|
try {
|
|
10203
|
-
const settingsDir =
|
|
10339
|
+
const settingsDir = path21.join(os13.homedir(), ".claude", "projects");
|
|
10204
10340
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
10205
|
-
const projSettingsDir =
|
|
10206
|
-
const settingsPath =
|
|
10341
|
+
const projSettingsDir = path21.join(settingsDir, normalizedKey);
|
|
10342
|
+
const settingsPath = path21.join(projSettingsDir, "settings.json");
|
|
10207
10343
|
let settings = {};
|
|
10208
10344
|
try {
|
|
10209
10345
|
settings = JSON.parse(readFileSync13(settingsPath, "utf8"));
|
|
@@ -10250,8 +10386,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
10250
10386
|
let behaviorsFlag = "";
|
|
10251
10387
|
let legacyFallbackWarned = false;
|
|
10252
10388
|
if (!useExeAgent && !useBinSymlink) {
|
|
10253
|
-
const identityPath =
|
|
10254
|
-
|
|
10389
|
+
const identityPath = path21.join(
|
|
10390
|
+
os13.homedir(),
|
|
10255
10391
|
".exe-os",
|
|
10256
10392
|
"identity",
|
|
10257
10393
|
`${employeeName}.md`
|
|
@@ -10266,7 +10402,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
10266
10402
|
}
|
|
10267
10403
|
const behaviorsFile = exportBehaviorsSync(
|
|
10268
10404
|
employeeName,
|
|
10269
|
-
|
|
10405
|
+
path21.basename(spawnCwd),
|
|
10270
10406
|
sessionName
|
|
10271
10407
|
);
|
|
10272
10408
|
if (behaviorsFile) {
|
|
@@ -10281,9 +10417,9 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
10281
10417
|
}
|
|
10282
10418
|
let sessionContextFlag = "";
|
|
10283
10419
|
try {
|
|
10284
|
-
const ctxDir =
|
|
10420
|
+
const ctxDir = path21.join(os13.homedir(), ".exe-os", "session-cache");
|
|
10285
10421
|
mkdirSync8(ctxDir, { recursive: true });
|
|
10286
|
-
const ctxFile =
|
|
10422
|
+
const ctxFile = path21.join(ctxDir, `session-context-${sessionName}.md`);
|
|
10287
10423
|
const ctxContent = [
|
|
10288
10424
|
`## Session Context`,
|
|
10289
10425
|
`You are running in tmux session: ${sessionName}.`,
|
|
@@ -10367,7 +10503,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
10367
10503
|
transport.pipeLog(sessionName, logFile);
|
|
10368
10504
|
try {
|
|
10369
10505
|
const mySession = getMySession();
|
|
10370
|
-
const dispatchInfo =
|
|
10506
|
+
const dispatchInfo = path21.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
10371
10507
|
writeFileSync8(dispatchInfo, JSON.stringify({
|
|
10372
10508
|
dispatchedBy: mySession,
|
|
10373
10509
|
rootExe: exeSession,
|
|
@@ -10442,15 +10578,15 @@ var init_tmux_routing = __esm({
|
|
|
10442
10578
|
init_intercom_queue();
|
|
10443
10579
|
init_plan_limits();
|
|
10444
10580
|
init_employees();
|
|
10445
|
-
SPAWN_LOCK_DIR =
|
|
10446
|
-
SESSION_CACHE =
|
|
10581
|
+
SPAWN_LOCK_DIR = path21.join(os13.homedir(), ".exe-os", "spawn-locks");
|
|
10582
|
+
SESSION_CACHE = path21.join(os13.homedir(), ".exe-os", "session-cache");
|
|
10447
10583
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
10448
10584
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
10449
10585
|
VERIFY_PANE_LINES = 200;
|
|
10450
10586
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
10451
10587
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
10452
|
-
INTERCOM_LOG2 =
|
|
10453
|
-
DEBOUNCE_FILE =
|
|
10588
|
+
INTERCOM_LOG2 = path21.join(os13.homedir(), ".exe-os", "intercom.log");
|
|
10589
|
+
DEBOUNCE_FILE = path21.join(SESSION_CACHE, "intercom-debounce.json");
|
|
10454
10590
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
10455
10591
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
|
|
10456
10592
|
}
|
|
@@ -10732,8 +10868,8 @@ var init_messaging = __esm({
|
|
|
10732
10868
|
// src/automation/trigger-engine.ts
|
|
10733
10869
|
import { readFileSync as readFileSync14, writeFileSync as writeFileSync9, existsSync as existsSync17, mkdirSync as mkdirSync9 } from "fs";
|
|
10734
10870
|
import { randomUUID as randomUUID8 } from "crypto";
|
|
10735
|
-
import
|
|
10736
|
-
import
|
|
10871
|
+
import path22 from "path";
|
|
10872
|
+
import os14 from "os";
|
|
10737
10873
|
function substituteTemplate(template, record) {
|
|
10738
10874
|
return template.replace(
|
|
10739
10875
|
/\{\{(\w+(?:\.\w+)*)\}\}/g,
|
|
@@ -11028,7 +11164,7 @@ var TRIGGERS_PATH, GRAPH_API_VERSION;
|
|
|
11028
11164
|
var init_trigger_engine = __esm({
|
|
11029
11165
|
"src/automation/trigger-engine.ts"() {
|
|
11030
11166
|
"use strict";
|
|
11031
|
-
TRIGGERS_PATH =
|
|
11167
|
+
TRIGGERS_PATH = path22.join(os14.homedir(), ".exe-os", "triggers.json");
|
|
11032
11168
|
GRAPH_API_VERSION = "v21.0";
|
|
11033
11169
|
}
|
|
11034
11170
|
});
|
|
@@ -11092,18 +11228,215 @@ var init_crm_webhook = __esm({
|
|
|
11092
11228
|
|
|
11093
11229
|
// src/bin/exe-gateway.ts
|
|
11094
11230
|
import { existsSync as existsSync18, readFileSync as readFileSync15 } from "fs";
|
|
11095
|
-
import
|
|
11096
|
-
import
|
|
11231
|
+
import path23 from "path";
|
|
11232
|
+
import os15 from "os";
|
|
11097
11233
|
|
|
11098
11234
|
// src/gateway/webhook-server.ts
|
|
11099
11235
|
import {
|
|
11100
11236
|
createServer
|
|
11101
11237
|
} from "http";
|
|
11238
|
+
|
|
11239
|
+
// src/gateway/query-handler.ts
|
|
11240
|
+
import os from "os";
|
|
11241
|
+
import path from "path";
|
|
11242
|
+
import { createRequire } from "module";
|
|
11243
|
+
import { pathToFileURL } from "url";
|
|
11244
|
+
var DEFAULT_LIMIT = 20;
|
|
11245
|
+
var MAX_LIMIT = 100;
|
|
11246
|
+
var DEFAULT_SCOPE = "all";
|
|
11247
|
+
var ALL_SCOPES = ["raw", "wiki", "memory", "gateway", "all"];
|
|
11248
|
+
var SCOPE_ORDER = ["raw", "memory", "wiki", "gateway"];
|
|
11249
|
+
var UNKNOWN_TIMESTAMP = (/* @__PURE__ */ new Date(0)).toISOString();
|
|
11250
|
+
var prismaPromise = null;
|
|
11251
|
+
function loadPrisma() {
|
|
11252
|
+
if (!prismaPromise) {
|
|
11253
|
+
prismaPromise = (async () => {
|
|
11254
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
11255
|
+
if (explicitPath) {
|
|
11256
|
+
const mod2 = await import(pathToFileURL(explicitPath).href);
|
|
11257
|
+
const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
|
|
11258
|
+
if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
|
|
11259
|
+
return new Ctor2();
|
|
11260
|
+
}
|
|
11261
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path.join(os.homedir(), "exe-db");
|
|
11262
|
+
const req = createRequire(path.join(exeDbRoot, "package.json"));
|
|
11263
|
+
const entry = req.resolve("@prisma/client");
|
|
11264
|
+
const mod = await import(pathToFileURL(entry).href);
|
|
11265
|
+
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
11266
|
+
if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
|
|
11267
|
+
return new Ctor();
|
|
11268
|
+
})();
|
|
11269
|
+
}
|
|
11270
|
+
return prismaPromise;
|
|
11271
|
+
}
|
|
11272
|
+
var QueryValidationError = class extends Error {
|
|
11273
|
+
constructor(message) {
|
|
11274
|
+
super(message);
|
|
11275
|
+
this.name = "QueryValidationError";
|
|
11276
|
+
}
|
|
11277
|
+
};
|
|
11278
|
+
var QueryHandler = class {
|
|
11279
|
+
constructor(getPrisma = loadPrisma) {
|
|
11280
|
+
this.getPrisma = getPrisma;
|
|
11281
|
+
}
|
|
11282
|
+
parseParams(input) {
|
|
11283
|
+
const url = typeof input === "string" ? new URL(input, "http://localhost") : input;
|
|
11284
|
+
const q = url.searchParams.get("q")?.trim() ?? "";
|
|
11285
|
+
if (!q) {
|
|
11286
|
+
throw new QueryValidationError("Query parameter 'q' is required");
|
|
11287
|
+
}
|
|
11288
|
+
const scope = url.searchParams.get("scope")?.trim().toLowerCase() ?? DEFAULT_SCOPE;
|
|
11289
|
+
if (!ALL_SCOPES.includes(scope)) {
|
|
11290
|
+
throw new QueryValidationError("Invalid scope");
|
|
11291
|
+
}
|
|
11292
|
+
const rawLimit = url.searchParams.get("limit");
|
|
11293
|
+
const parsedLimit = rawLimit ? Number.parseInt(rawLimit, 10) : DEFAULT_LIMIT;
|
|
11294
|
+
const limit = Number.isFinite(parsedLimit) && parsedLimit > 0 ? Math.min(parsedLimit, MAX_LIMIT) : DEFAULT_LIMIT;
|
|
11295
|
+
const source = url.searchParams.get("source")?.trim() || void 0;
|
|
11296
|
+
return { q, scope, source, limit };
|
|
11297
|
+
}
|
|
11298
|
+
async query(params) {
|
|
11299
|
+
const prisma = await this.getPrisma();
|
|
11300
|
+
const scopes = params.scope === "all" ? SCOPE_ORDER : [params.scope];
|
|
11301
|
+
const searchTerm = `%${params.q}%`;
|
|
11302
|
+
const results = (await Promise.all(
|
|
11303
|
+
scopes.map((scope) => this.queryScope(prisma, scope, searchTerm, params))
|
|
11304
|
+
)).flat();
|
|
11305
|
+
return {
|
|
11306
|
+
results,
|
|
11307
|
+
count: results.length,
|
|
11308
|
+
scopes_searched: scopes
|
|
11309
|
+
};
|
|
11310
|
+
}
|
|
11311
|
+
async queryScope(prisma, scope, searchTerm, params) {
|
|
11312
|
+
switch (scope) {
|
|
11313
|
+
case "raw":
|
|
11314
|
+
return this.queryRaw(prisma, searchTerm, params.source, params.limit);
|
|
11315
|
+
case "memory":
|
|
11316
|
+
return this.queryMemory(prisma, searchTerm, params.limit);
|
|
11317
|
+
case "wiki":
|
|
11318
|
+
return this.queryWiki(prisma, searchTerm, params.limit);
|
|
11319
|
+
case "gateway":
|
|
11320
|
+
return this.queryGateway(prisma, searchTerm, params.limit);
|
|
11321
|
+
}
|
|
11322
|
+
}
|
|
11323
|
+
async queryRaw(prisma, searchTerm, source, limit) {
|
|
11324
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
11325
|
+
`SELECT "id", "source", "event_type", substring("payload"::text, 1, 200) as snippet, "timestamp"
|
|
11326
|
+
FROM "raw"."raw_events"
|
|
11327
|
+
WHERE ("payload"::text ILIKE $1 OR "metadata"::text ILIKE $1)
|
|
11328
|
+
AND ($2::text IS NULL OR "source" = $2)
|
|
11329
|
+
ORDER BY "timestamp" DESC
|
|
11330
|
+
LIMIT $3`,
|
|
11331
|
+
searchTerm,
|
|
11332
|
+
source ?? null,
|
|
11333
|
+
limit
|
|
11334
|
+
);
|
|
11335
|
+
return rows.map((row) => ({
|
|
11336
|
+
scope: "raw",
|
|
11337
|
+
type: "event",
|
|
11338
|
+
snippet: sanitizeSnippet(row.snippet),
|
|
11339
|
+
timestamp: toIsoTimestamp(row.timestamp),
|
|
11340
|
+
metadata: {
|
|
11341
|
+
id: row.id,
|
|
11342
|
+
source: row.source,
|
|
11343
|
+
event_type: row.event_type
|
|
11344
|
+
}
|
|
11345
|
+
}));
|
|
11346
|
+
}
|
|
11347
|
+
async queryMemory(prisma, searchTerm, limit) {
|
|
11348
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
11349
|
+
`SELECT "id", "agent_id", "tool_name", substring("raw_text", 1, 200) as snippet, "timestamp", "project_name"
|
|
11350
|
+
FROM "memory"."memory_records"
|
|
11351
|
+
WHERE "raw_text" ILIKE $1 AND "status" = 'active'
|
|
11352
|
+
ORDER BY "timestamp" DESC
|
|
11353
|
+
LIMIT $2`,
|
|
11354
|
+
searchTerm,
|
|
11355
|
+
limit
|
|
11356
|
+
);
|
|
11357
|
+
return rows.map((row) => ({
|
|
11358
|
+
scope: "memory",
|
|
11359
|
+
type: "memory",
|
|
11360
|
+
snippet: sanitizeSnippet(row.snippet),
|
|
11361
|
+
timestamp: toIsoTimestamp(row.timestamp),
|
|
11362
|
+
metadata: {
|
|
11363
|
+
id: row.id,
|
|
11364
|
+
agent_id: row.agent_id,
|
|
11365
|
+
tool_name: row.tool_name,
|
|
11366
|
+
project_name: row.project_name
|
|
11367
|
+
}
|
|
11368
|
+
}));
|
|
11369
|
+
}
|
|
11370
|
+
async queryWiki(prisma, searchTerm, limit) {
|
|
11371
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
11372
|
+
`SELECT d."doc_id", d."filename", d."docpath", w."name" as workspace_name
|
|
11373
|
+
FROM "wiki"."workspace_documents" d
|
|
11374
|
+
JOIN "wiki"."workspaces" w ON w."id" = d."workspace_id"
|
|
11375
|
+
WHERE d."filename" ILIKE $1 OR d."docpath" ILIKE $1
|
|
11376
|
+
LIMIT $2`,
|
|
11377
|
+
searchTerm,
|
|
11378
|
+
limit
|
|
11379
|
+
);
|
|
11380
|
+
return rows.map((row) => ({
|
|
11381
|
+
scope: "wiki",
|
|
11382
|
+
type: "document",
|
|
11383
|
+
snippet: sanitizeSnippet([row.filename, row.docpath].filter(Boolean).join(" \u2014 ")),
|
|
11384
|
+
timestamp: UNKNOWN_TIMESTAMP,
|
|
11385
|
+
metadata: {
|
|
11386
|
+
doc_id: row.doc_id,
|
|
11387
|
+
filename: row.filename,
|
|
11388
|
+
docpath: row.docpath,
|
|
11389
|
+
workspace_name: row.workspace_name
|
|
11390
|
+
}
|
|
11391
|
+
}));
|
|
11392
|
+
}
|
|
11393
|
+
async queryGateway(prisma, searchTerm, limit) {
|
|
11394
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
11395
|
+
`SELECT m."id", m."text", m."from_jid", m."timestamp", c."display_name", c."platform"
|
|
11396
|
+
FROM "gateway"."messages" m
|
|
11397
|
+
JOIN "gateway"."threads" t ON t."id" = m."thread_id"
|
|
11398
|
+
JOIN "gateway"."contacts" c ON c."id" = t."contact_id"
|
|
11399
|
+
WHERE m."text" ILIKE $1
|
|
11400
|
+
ORDER BY m."timestamp" DESC
|
|
11401
|
+
LIMIT $2`,
|
|
11402
|
+
searchTerm,
|
|
11403
|
+
limit
|
|
11404
|
+
);
|
|
11405
|
+
return rows.map((row) => ({
|
|
11406
|
+
scope: "gateway",
|
|
11407
|
+
type: "message",
|
|
11408
|
+
snippet: sanitizeSnippet(row.text),
|
|
11409
|
+
timestamp: toIsoTimestamp(row.timestamp),
|
|
11410
|
+
metadata: {
|
|
11411
|
+
id: row.id,
|
|
11412
|
+
from_jid: row.from_jid,
|
|
11413
|
+
display_name: row.display_name,
|
|
11414
|
+
platform: row.platform
|
|
11415
|
+
}
|
|
11416
|
+
}));
|
|
11417
|
+
}
|
|
11418
|
+
};
|
|
11419
|
+
function sanitizeSnippet(value) {
|
|
11420
|
+
return String(value ?? "").slice(0, 200);
|
|
11421
|
+
}
|
|
11422
|
+
function toIsoTimestamp(value) {
|
|
11423
|
+
if (value instanceof Date) return value.toISOString();
|
|
11424
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
11425
|
+
const parsed = new Date(value);
|
|
11426
|
+
if (!Number.isNaN(parsed.getTime())) {
|
|
11427
|
+
return parsed.toISOString();
|
|
11428
|
+
}
|
|
11429
|
+
}
|
|
11430
|
+
return UNKNOWN_TIMESTAMP;
|
|
11431
|
+
}
|
|
11432
|
+
|
|
11433
|
+
// src/gateway/webhook-server.ts
|
|
11102
11434
|
var DEFAULT_HOST = process.env.EXE_WEBHOOK_HOST || "127.0.0.1";
|
|
11103
11435
|
var BODY_SIZE_LIMIT = 1048576;
|
|
11104
11436
|
var WebhookServer = class {
|
|
11105
|
-
constructor(config2) {
|
|
11437
|
+
constructor(config2, queryHandler = new QueryHandler()) {
|
|
11106
11438
|
this.config = config2;
|
|
11439
|
+
this.queryHandler = queryHandler;
|
|
11107
11440
|
if (process.env.NODE_ENV === "production" && !config2.authToken) {
|
|
11108
11441
|
throw new Error(
|
|
11109
11442
|
"[webhook-server] authToken is required in production. Set it in ~/.exe-os/gateway.json or pass it in WebhookServerConfig."
|
|
@@ -11155,6 +11488,10 @@ var WebhookServer = class {
|
|
|
11155
11488
|
this.handleHealth(res);
|
|
11156
11489
|
return;
|
|
11157
11490
|
}
|
|
11491
|
+
if (method === "GET" && isRoute(url, "/query")) {
|
|
11492
|
+
await this.handleQuery(req, res);
|
|
11493
|
+
return;
|
|
11494
|
+
}
|
|
11158
11495
|
if (method === "GET" && url.startsWith("/webhook/whatsapp")) {
|
|
11159
11496
|
this.handleWhatsAppVerification(req, res);
|
|
11160
11497
|
return;
|
|
@@ -11173,6 +11510,31 @@ var WebhookServer = class {
|
|
|
11173
11510
|
handlers: [...this.handlers.keys()]
|
|
11174
11511
|
});
|
|
11175
11512
|
}
|
|
11513
|
+
async handleQuery(req, res) {
|
|
11514
|
+
if (this.config.authToken && !this.verifyAuth(req)) {
|
|
11515
|
+
sendJson(res, 401, { error: "Unauthorized" });
|
|
11516
|
+
return;
|
|
11517
|
+
}
|
|
11518
|
+
try {
|
|
11519
|
+
const url = new URL(
|
|
11520
|
+
req.url ?? "/query",
|
|
11521
|
+
`http://${req.headers.host ?? "localhost"}`
|
|
11522
|
+
);
|
|
11523
|
+
const params = this.queryHandler.parseParams(url);
|
|
11524
|
+
const result = await this.queryHandler.query(params);
|
|
11525
|
+
sendJson(res, 200, result);
|
|
11526
|
+
} catch (err) {
|
|
11527
|
+
if (err instanceof QueryValidationError) {
|
|
11528
|
+
sendJson(res, 400, { error: err.message });
|
|
11529
|
+
return;
|
|
11530
|
+
}
|
|
11531
|
+
console.error(
|
|
11532
|
+
"[webhook-server] Query error:",
|
|
11533
|
+
err instanceof Error ? err.message : err
|
|
11534
|
+
);
|
|
11535
|
+
sendJson(res, 503, { error: "Database unavailable" });
|
|
11536
|
+
}
|
|
11537
|
+
}
|
|
11176
11538
|
handleWhatsAppVerification(req, res) {
|
|
11177
11539
|
const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
11178
11540
|
const mode = url.searchParams.get("hub.mode");
|
|
@@ -11234,6 +11596,9 @@ function extractPlatform(url) {
|
|
|
11234
11596
|
const match = url.match(/^\/webhook\/([a-zA-Z0-9_-]+)/);
|
|
11235
11597
|
return match?.[1] ?? null;
|
|
11236
11598
|
}
|
|
11599
|
+
function isRoute(url, path24) {
|
|
11600
|
+
return url === path24 || url.startsWith(`${path24}?`);
|
|
11601
|
+
}
|
|
11237
11602
|
function readBody(req) {
|
|
11238
11603
|
return new Promise((resolve, reject) => {
|
|
11239
11604
|
const chunks = [];
|
|
@@ -12059,8 +12424,8 @@ var BotRegistry = class {
|
|
|
12059
12424
|
|
|
12060
12425
|
// src/bin/exe-gateway.ts
|
|
12061
12426
|
init_employees();
|
|
12062
|
-
var CONFIG_DIR = process.env.EXE_GATEWAY_HOME ||
|
|
12063
|
-
var CONFIG_PATH3 = process.env.EXE_GATEWAY_CONFIG ||
|
|
12427
|
+
var CONFIG_DIR = process.env.EXE_GATEWAY_HOME || path23.join(os15.homedir(), ".exe-os");
|
|
12428
|
+
var CONFIG_PATH3 = process.env.EXE_GATEWAY_CONFIG || path23.join(CONFIG_DIR, "gateway.json");
|
|
12064
12429
|
var DEFAULT_PORT = 3100;
|
|
12065
12430
|
function loadConfig2() {
|
|
12066
12431
|
if (!existsSync18(CONFIG_PATH3)) {
|