@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/tui/App.js
CHANGED
|
@@ -355,6 +355,44 @@ var init_provider_table = __esm({
|
|
|
355
355
|
}
|
|
356
356
|
});
|
|
357
357
|
|
|
358
|
+
// src/lib/secure-files.ts
|
|
359
|
+
import { chmodSync, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
|
|
360
|
+
import { chmod, mkdir } from "fs/promises";
|
|
361
|
+
async function ensurePrivateDir(dirPath) {
|
|
362
|
+
await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
363
|
+
try {
|
|
364
|
+
await chmod(dirPath, PRIVATE_DIR_MODE);
|
|
365
|
+
} catch {
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
function ensurePrivateDirSync(dirPath) {
|
|
369
|
+
mkdirSync2(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
370
|
+
try {
|
|
371
|
+
chmodSync(dirPath, PRIVATE_DIR_MODE);
|
|
372
|
+
} catch {
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
async function enforcePrivateFile(filePath) {
|
|
376
|
+
try {
|
|
377
|
+
await chmod(filePath, PRIVATE_FILE_MODE);
|
|
378
|
+
} catch {
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
function enforcePrivateFileSync(filePath) {
|
|
382
|
+
try {
|
|
383
|
+
if (existsSync3(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
|
|
384
|
+
} catch {
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
|
|
388
|
+
var init_secure_files = __esm({
|
|
389
|
+
"src/lib/secure-files.ts"() {
|
|
390
|
+
"use strict";
|
|
391
|
+
PRIVATE_DIR_MODE = 448;
|
|
392
|
+
PRIVATE_FILE_MODE = 384;
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
|
|
358
396
|
// src/lib/config.ts
|
|
359
397
|
var config_exports = {};
|
|
360
398
|
__export(config_exports, {
|
|
@@ -371,8 +409,8 @@ __export(config_exports, {
|
|
|
371
409
|
migrateConfig: () => migrateConfig,
|
|
372
410
|
saveConfig: () => saveConfig
|
|
373
411
|
});
|
|
374
|
-
import { readFile, writeFile
|
|
375
|
-
import { readFileSync as readFileSync3, existsSync as
|
|
412
|
+
import { readFile, writeFile } from "fs/promises";
|
|
413
|
+
import { readFileSync as readFileSync3, existsSync as existsSync4, renameSync } from "fs";
|
|
376
414
|
import path2 from "path";
|
|
377
415
|
import os2 from "os";
|
|
378
416
|
function resolveDataDir() {
|
|
@@ -380,7 +418,7 @@ function resolveDataDir() {
|
|
|
380
418
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
381
419
|
const newDir = path2.join(os2.homedir(), ".exe-os");
|
|
382
420
|
const legacyDir = path2.join(os2.homedir(), ".exe-mem");
|
|
383
|
-
if (!
|
|
421
|
+
if (!existsSync4(newDir) && existsSync4(legacyDir)) {
|
|
384
422
|
try {
|
|
385
423
|
renameSync(legacyDir, newDir);
|
|
386
424
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -443,9 +481,9 @@ function normalizeAutoUpdate(raw) {
|
|
|
443
481
|
}
|
|
444
482
|
async function loadConfig() {
|
|
445
483
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
446
|
-
await
|
|
484
|
+
await ensurePrivateDir(dir);
|
|
447
485
|
const configPath = path2.join(dir, "config.json");
|
|
448
|
-
if (!
|
|
486
|
+
if (!existsSync4(configPath)) {
|
|
449
487
|
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
450
488
|
}
|
|
451
489
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -458,6 +496,7 @@ async function loadConfig() {
|
|
|
458
496
|
`);
|
|
459
497
|
try {
|
|
460
498
|
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
499
|
+
await enforcePrivateFile(configPath);
|
|
461
500
|
} catch {
|
|
462
501
|
}
|
|
463
502
|
}
|
|
@@ -476,7 +515,7 @@ async function loadConfig() {
|
|
|
476
515
|
function loadConfigSync() {
|
|
477
516
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
478
517
|
const configPath = path2.join(dir, "config.json");
|
|
479
|
-
if (!
|
|
518
|
+
if (!existsSync4(configPath)) {
|
|
480
519
|
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
481
520
|
}
|
|
482
521
|
try {
|
|
@@ -494,12 +533,10 @@ function loadConfigSync() {
|
|
|
494
533
|
}
|
|
495
534
|
async function saveConfig(config) {
|
|
496
535
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
497
|
-
await
|
|
536
|
+
await ensurePrivateDir(dir);
|
|
498
537
|
const configPath = path2.join(dir, "config.json");
|
|
499
538
|
await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
500
|
-
|
|
501
|
-
await chmod(configPath, 384);
|
|
502
|
-
}
|
|
539
|
+
await enforcePrivateFile(configPath);
|
|
503
540
|
}
|
|
504
541
|
async function loadConfigFrom(configPath) {
|
|
505
542
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -519,6 +556,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
|
|
|
519
556
|
var init_config = __esm({
|
|
520
557
|
"src/lib/config.ts"() {
|
|
521
558
|
"use strict";
|
|
559
|
+
init_secure_files();
|
|
522
560
|
EXE_AI_DIR = resolveDataDir();
|
|
523
561
|
DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
|
|
524
562
|
MODELS_DIR = path2.join(EXE_AI_DIR, "models");
|
|
@@ -623,16 +661,34 @@ var init_runtime_table = __esm({
|
|
|
623
661
|
});
|
|
624
662
|
|
|
625
663
|
// src/lib/agent-config.ts
|
|
626
|
-
|
|
664
|
+
var agent_config_exports = {};
|
|
665
|
+
__export(agent_config_exports, {
|
|
666
|
+
AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
|
|
667
|
+
DEFAULT_MODELS: () => DEFAULT_MODELS,
|
|
668
|
+
KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
|
|
669
|
+
RUNTIME_LABELS: () => RUNTIME_LABELS,
|
|
670
|
+
clearAgentRuntime: () => clearAgentRuntime,
|
|
671
|
+
getAgentRuntime: () => getAgentRuntime,
|
|
672
|
+
loadAgentConfig: () => loadAgentConfig,
|
|
673
|
+
saveAgentConfig: () => saveAgentConfig,
|
|
674
|
+
setAgentRuntime: () => setAgentRuntime
|
|
675
|
+
});
|
|
676
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync5 } from "fs";
|
|
627
677
|
import path3 from "path";
|
|
628
678
|
function loadAgentConfig() {
|
|
629
|
-
if (!
|
|
679
|
+
if (!existsSync5(AGENT_CONFIG_PATH)) return {};
|
|
630
680
|
try {
|
|
631
681
|
return JSON.parse(readFileSync4(AGENT_CONFIG_PATH, "utf-8"));
|
|
632
682
|
} catch {
|
|
633
683
|
return {};
|
|
634
684
|
}
|
|
635
685
|
}
|
|
686
|
+
function saveAgentConfig(config) {
|
|
687
|
+
const dir = path3.dirname(AGENT_CONFIG_PATH);
|
|
688
|
+
ensurePrivateDirSync(dir);
|
|
689
|
+
writeFileSync2(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
690
|
+
enforcePrivateFileSync(AGENT_CONFIG_PATH);
|
|
691
|
+
}
|
|
636
692
|
function getAgentRuntime(agentId) {
|
|
637
693
|
const config = loadAgentConfig();
|
|
638
694
|
const entry = config[agentId];
|
|
@@ -641,13 +697,48 @@ function getAgentRuntime(agentId) {
|
|
|
641
697
|
if (orgDefault) return orgDefault;
|
|
642
698
|
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
643
699
|
}
|
|
644
|
-
|
|
700
|
+
function setAgentRuntime(agentId, runtime, model) {
|
|
701
|
+
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
702
|
+
if (!knownModels) {
|
|
703
|
+
return {
|
|
704
|
+
ok: false,
|
|
705
|
+
error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
if (!knownModels.includes(model)) {
|
|
709
|
+
return {
|
|
710
|
+
ok: false,
|
|
711
|
+
error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
const config = loadAgentConfig();
|
|
715
|
+
config[agentId] = { runtime, model };
|
|
716
|
+
saveAgentConfig(config);
|
|
717
|
+
return { ok: true };
|
|
718
|
+
}
|
|
719
|
+
function clearAgentRuntime(agentId) {
|
|
720
|
+
const config = loadAgentConfig();
|
|
721
|
+
delete config[agentId];
|
|
722
|
+
saveAgentConfig(config);
|
|
723
|
+
}
|
|
724
|
+
var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
|
|
645
725
|
var init_agent_config = __esm({
|
|
646
726
|
"src/lib/agent-config.ts"() {
|
|
647
727
|
"use strict";
|
|
648
728
|
init_config();
|
|
649
729
|
init_runtime_table();
|
|
730
|
+
init_secure_files();
|
|
650
731
|
AGENT_CONFIG_PATH = path3.join(EXE_AI_DIR, "agent-config.json");
|
|
732
|
+
KNOWN_RUNTIMES = {
|
|
733
|
+
claude: ["claude-opus-4", "claude-sonnet-4", "claude-haiku-4.5"],
|
|
734
|
+
codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
|
|
735
|
+
opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
|
|
736
|
+
};
|
|
737
|
+
RUNTIME_LABELS = {
|
|
738
|
+
claude: "Claude Code (Anthropic)",
|
|
739
|
+
codex: "Codex (OpenAI)",
|
|
740
|
+
opencode: "OpenCode (open source)"
|
|
741
|
+
};
|
|
651
742
|
DEFAULT_MODELS = {
|
|
652
743
|
claude: "claude-opus-4",
|
|
653
744
|
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
@@ -665,16 +756,16 @@ __export(intercom_queue_exports, {
|
|
|
665
756
|
queueIntercom: () => queueIntercom,
|
|
666
757
|
readQueue: () => readQueue
|
|
667
758
|
});
|
|
668
|
-
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as
|
|
759
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
|
|
669
760
|
import path4 from "path";
|
|
670
761
|
import os3 from "os";
|
|
671
762
|
function ensureDir() {
|
|
672
763
|
const dir = path4.dirname(QUEUE_PATH);
|
|
673
|
-
if (!
|
|
764
|
+
if (!existsSync6(dir)) mkdirSync3(dir, { recursive: true });
|
|
674
765
|
}
|
|
675
766
|
function readQueue() {
|
|
676
767
|
try {
|
|
677
|
-
if (!
|
|
768
|
+
if (!existsSync6(QUEUE_PATH)) return [];
|
|
678
769
|
return JSON.parse(readFileSync5(QUEUE_PATH, "utf8"));
|
|
679
770
|
} catch {
|
|
680
771
|
return [];
|
|
@@ -852,6 +943,7 @@ __export(employees_exports, {
|
|
|
852
943
|
getEmployeeByRole: () => getEmployeeByRole,
|
|
853
944
|
getEmployeeNamesByRole: () => getEmployeeNamesByRole,
|
|
854
945
|
hasRole: () => hasRole,
|
|
946
|
+
hireEmployee: () => hireEmployee,
|
|
855
947
|
isCoordinatorName: () => isCoordinatorName,
|
|
856
948
|
isCoordinatorRole: () => isCoordinatorRole,
|
|
857
949
|
isMultiInstance: () => isMultiInstance,
|
|
@@ -864,7 +956,7 @@ __export(employees_exports, {
|
|
|
864
956
|
validateEmployeeName: () => validateEmployeeName
|
|
865
957
|
});
|
|
866
958
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
867
|
-
import { existsSync as
|
|
959
|
+
import { existsSync as existsSync7, symlinkSync, readlinkSync, readFileSync as readFileSync6, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
|
|
868
960
|
import { execSync as execSync4 } from "child_process";
|
|
869
961
|
import path5 from "path";
|
|
870
962
|
import os4 from "os";
|
|
@@ -903,7 +995,7 @@ function validateEmployeeName(name) {
|
|
|
903
995
|
return { valid: true };
|
|
904
996
|
}
|
|
905
997
|
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
906
|
-
if (!
|
|
998
|
+
if (!existsSync7(employeesPath)) {
|
|
907
999
|
return [];
|
|
908
1000
|
}
|
|
909
1001
|
const raw = await readFile2(employeesPath, "utf-8");
|
|
@@ -918,7 +1010,7 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
|
918
1010
|
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
919
1011
|
}
|
|
920
1012
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
921
|
-
if (!
|
|
1013
|
+
if (!existsSync7(employeesPath)) return [];
|
|
922
1014
|
try {
|
|
923
1015
|
return JSON.parse(readFileSync6(employeesPath, "utf-8"));
|
|
924
1016
|
} catch {
|
|
@@ -962,6 +1054,52 @@ function addEmployee(employees, employee) {
|
|
|
962
1054
|
}
|
|
963
1055
|
return [...employees, normalized];
|
|
964
1056
|
}
|
|
1057
|
+
function appendToCoordinatorTeam(employee) {
|
|
1058
|
+
const coordinator = getCoordinatorEmployee(loadEmployeesSync());
|
|
1059
|
+
if (!coordinator) return;
|
|
1060
|
+
const idPath = path5.join(IDENTITY_DIR, `${coordinator.name}.md`);
|
|
1061
|
+
if (!existsSync7(idPath)) return;
|
|
1062
|
+
const content = readFileSync6(idPath, "utf-8");
|
|
1063
|
+
if (content.includes(`**${capitalize(employee.name)}`)) return;
|
|
1064
|
+
const teamMatch = content.match(TEAM_SECTION_RE);
|
|
1065
|
+
if (!teamMatch || teamMatch.index === void 0) return;
|
|
1066
|
+
const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
|
|
1067
|
+
const nextHeading = afterTeam.match(/\n## /);
|
|
1068
|
+
const entry = `
|
|
1069
|
+
**${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
|
|
1070
|
+
`;
|
|
1071
|
+
let updated;
|
|
1072
|
+
if (nextHeading && nextHeading.index !== void 0) {
|
|
1073
|
+
const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
|
|
1074
|
+
updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
|
|
1075
|
+
} else {
|
|
1076
|
+
updated = content.trimEnd() + "\n" + entry;
|
|
1077
|
+
}
|
|
1078
|
+
writeFileSync4(idPath, updated, "utf-8");
|
|
1079
|
+
}
|
|
1080
|
+
function capitalize(s) {
|
|
1081
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
1082
|
+
}
|
|
1083
|
+
async function hireEmployee(employee) {
|
|
1084
|
+
const employees = await loadEmployees();
|
|
1085
|
+
const updated = addEmployee(employees, employee);
|
|
1086
|
+
await saveEmployees(updated);
|
|
1087
|
+
try {
|
|
1088
|
+
appendToCoordinatorTeam(employee);
|
|
1089
|
+
} catch {
|
|
1090
|
+
}
|
|
1091
|
+
try {
|
|
1092
|
+
const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
|
|
1093
|
+
const config = loadAgentConfig2();
|
|
1094
|
+
const name = employee.name.toLowerCase();
|
|
1095
|
+
if (!config[name] && config["default"]) {
|
|
1096
|
+
config[name] = { ...config["default"] };
|
|
1097
|
+
saveAgentConfig2(config);
|
|
1098
|
+
}
|
|
1099
|
+
} catch {
|
|
1100
|
+
}
|
|
1101
|
+
return updated;
|
|
1102
|
+
}
|
|
965
1103
|
async function normalizeRosterCase(rosterPath) {
|
|
966
1104
|
const employees = await loadEmployees(rosterPath);
|
|
967
1105
|
let changed = false;
|
|
@@ -974,9 +1112,9 @@ async function normalizeRosterCase(rosterPath) {
|
|
|
974
1112
|
const identityDir = path5.join(os4.homedir(), ".exe-os", "identity");
|
|
975
1113
|
const oldPath = path5.join(identityDir, `${oldName}.md`);
|
|
976
1114
|
const newPath = path5.join(identityDir, `${emp.name}.md`);
|
|
977
|
-
if (
|
|
1115
|
+
if (existsSync7(oldPath) && !existsSync7(newPath)) {
|
|
978
1116
|
renameSync3(oldPath, newPath);
|
|
979
|
-
} else if (
|
|
1117
|
+
} else if (existsSync7(oldPath) && oldPath !== newPath) {
|
|
980
1118
|
const content = readFileSync6(oldPath, "utf-8");
|
|
981
1119
|
writeFileSync4(newPath, content, "utf-8");
|
|
982
1120
|
if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
|
|
@@ -1019,7 +1157,7 @@ function registerBinSymlinks(name) {
|
|
|
1019
1157
|
for (const suffix of ["", "-opencode"]) {
|
|
1020
1158
|
const linkName = `${name}${suffix}`;
|
|
1021
1159
|
const linkPath = path5.join(binDir, linkName);
|
|
1022
|
-
if (
|
|
1160
|
+
if (existsSync7(linkPath)) {
|
|
1023
1161
|
skipped.push(linkName);
|
|
1024
1162
|
continue;
|
|
1025
1163
|
}
|
|
@@ -1032,7 +1170,7 @@ function registerBinSymlinks(name) {
|
|
|
1032
1170
|
}
|
|
1033
1171
|
return { created, skipped, errors };
|
|
1034
1172
|
}
|
|
1035
|
-
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
|
|
1173
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
|
|
1036
1174
|
var init_employees = __esm({
|
|
1037
1175
|
"src/lib/employees.ts"() {
|
|
1038
1176
|
"use strict";
|
|
@@ -1041,16 +1179,639 @@ var init_employees = __esm({
|
|
|
1041
1179
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
1042
1180
|
COORDINATOR_ROLE = "COO";
|
|
1043
1181
|
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
1182
|
+
IDENTITY_DIR = path5.join(EXE_AI_DIR, "identity");
|
|
1183
|
+
TEAM_SECTION_RE = /^## Team\b.*$/m;
|
|
1184
|
+
}
|
|
1185
|
+
});
|
|
1186
|
+
|
|
1187
|
+
// src/lib/database-adapter.ts
|
|
1188
|
+
import os5 from "os";
|
|
1189
|
+
import path6 from "path";
|
|
1190
|
+
import { createRequire } from "module";
|
|
1191
|
+
import { pathToFileURL } from "url";
|
|
1192
|
+
function quotedIdentifier(identifier) {
|
|
1193
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
1194
|
+
}
|
|
1195
|
+
function unqualifiedTableName(name) {
|
|
1196
|
+
const raw = name.trim().replace(/^"|"$/g, "");
|
|
1197
|
+
const parts = raw.split(".");
|
|
1198
|
+
return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
|
|
1199
|
+
}
|
|
1200
|
+
function stripTrailingSemicolon(sql) {
|
|
1201
|
+
return sql.trim().replace(/;+\s*$/u, "");
|
|
1202
|
+
}
|
|
1203
|
+
function appendClause(sql, clause) {
|
|
1204
|
+
const trimmed = stripTrailingSemicolon(sql);
|
|
1205
|
+
const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
|
|
1206
|
+
if (!returningMatch) {
|
|
1207
|
+
return `${trimmed}${clause}`;
|
|
1208
|
+
}
|
|
1209
|
+
const idx = returningMatch.index;
|
|
1210
|
+
return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
|
|
1211
|
+
}
|
|
1212
|
+
function normalizeStatement(stmt) {
|
|
1213
|
+
if (typeof stmt === "string") {
|
|
1214
|
+
return { kind: "positional", sql: stmt, args: [] };
|
|
1215
|
+
}
|
|
1216
|
+
const sql = stmt.sql;
|
|
1217
|
+
if (Array.isArray(stmt.args) || stmt.args === void 0) {
|
|
1218
|
+
return { kind: "positional", sql, args: stmt.args ?? [] };
|
|
1219
|
+
}
|
|
1220
|
+
return { kind: "named", sql, args: stmt.args };
|
|
1221
|
+
}
|
|
1222
|
+
function rewriteBooleanLiterals(sql) {
|
|
1223
|
+
let out = sql;
|
|
1224
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
1225
|
+
const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
|
|
1226
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
|
|
1227
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
|
|
1228
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
|
|
1229
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
|
|
1230
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
|
|
1231
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
|
|
1232
|
+
}
|
|
1233
|
+
return out;
|
|
1234
|
+
}
|
|
1235
|
+
function rewriteInsertOrIgnore(sql) {
|
|
1236
|
+
if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
|
|
1237
|
+
return sql;
|
|
1238
|
+
}
|
|
1239
|
+
const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
|
|
1240
|
+
return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
|
|
1241
|
+
}
|
|
1242
|
+
function rewriteInsertOrReplace(sql) {
|
|
1243
|
+
const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
|
|
1244
|
+
if (!match) {
|
|
1245
|
+
return sql;
|
|
1246
|
+
}
|
|
1247
|
+
const rawTable = match[1];
|
|
1248
|
+
const rawColumns = match[2];
|
|
1249
|
+
const remainder = match[3];
|
|
1250
|
+
const tableName = unqualifiedTableName(rawTable);
|
|
1251
|
+
const conflictKeys = UPSERT_KEYS[tableName];
|
|
1252
|
+
if (!conflictKeys?.length) {
|
|
1253
|
+
return sql;
|
|
1254
|
+
}
|
|
1255
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
1256
|
+
const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
|
|
1257
|
+
const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
|
|
1258
|
+
const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
|
|
1259
|
+
return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
|
|
1260
|
+
}
|
|
1261
|
+
function rewriteSql(sql) {
|
|
1262
|
+
let out = sql;
|
|
1263
|
+
out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
|
|
1264
|
+
out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
|
|
1265
|
+
out = rewriteBooleanLiterals(out);
|
|
1266
|
+
out = rewriteInsertOrReplace(out);
|
|
1267
|
+
out = rewriteInsertOrIgnore(out);
|
|
1268
|
+
return stripTrailingSemicolon(out);
|
|
1269
|
+
}
|
|
1270
|
+
function toBoolean(value) {
|
|
1271
|
+
if (value === null || value === void 0) return value;
|
|
1272
|
+
if (typeof value === "boolean") return value;
|
|
1273
|
+
if (typeof value === "number") return value !== 0;
|
|
1274
|
+
if (typeof value === "bigint") return value !== 0n;
|
|
1275
|
+
if (typeof value === "string") {
|
|
1276
|
+
const normalized = value.trim().toLowerCase();
|
|
1277
|
+
if (normalized === "0" || normalized === "false") return false;
|
|
1278
|
+
if (normalized === "1" || normalized === "true") return true;
|
|
1279
|
+
}
|
|
1280
|
+
return Boolean(value);
|
|
1281
|
+
}
|
|
1282
|
+
function countQuestionMarks(sql, end) {
|
|
1283
|
+
let count = 0;
|
|
1284
|
+
let inSingle = false;
|
|
1285
|
+
let inDouble = false;
|
|
1286
|
+
let inLineComment = false;
|
|
1287
|
+
let inBlockComment = false;
|
|
1288
|
+
for (let i = 0; i < end; i++) {
|
|
1289
|
+
const ch = sql[i];
|
|
1290
|
+
const next = sql[i + 1];
|
|
1291
|
+
if (inLineComment) {
|
|
1292
|
+
if (ch === "\n") inLineComment = false;
|
|
1293
|
+
continue;
|
|
1294
|
+
}
|
|
1295
|
+
if (inBlockComment) {
|
|
1296
|
+
if (ch === "*" && next === "/") {
|
|
1297
|
+
inBlockComment = false;
|
|
1298
|
+
i += 1;
|
|
1299
|
+
}
|
|
1300
|
+
continue;
|
|
1301
|
+
}
|
|
1302
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
1303
|
+
inLineComment = true;
|
|
1304
|
+
i += 1;
|
|
1305
|
+
continue;
|
|
1306
|
+
}
|
|
1307
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
1308
|
+
inBlockComment = true;
|
|
1309
|
+
i += 1;
|
|
1310
|
+
continue;
|
|
1311
|
+
}
|
|
1312
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
1313
|
+
inSingle = !inSingle;
|
|
1314
|
+
continue;
|
|
1315
|
+
}
|
|
1316
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
1317
|
+
inDouble = !inDouble;
|
|
1318
|
+
continue;
|
|
1319
|
+
}
|
|
1320
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
1321
|
+
count += 1;
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
return count;
|
|
1325
|
+
}
|
|
1326
|
+
function findBooleanPlaceholderIndexes(sql) {
|
|
1327
|
+
const indexes = /* @__PURE__ */ new Set();
|
|
1328
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
1329
|
+
const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
|
|
1330
|
+
for (const match of sql.matchAll(pattern)) {
|
|
1331
|
+
const matchText = match[0];
|
|
1332
|
+
const qIndex = match.index + matchText.lastIndexOf("?");
|
|
1333
|
+
indexes.add(countQuestionMarks(sql, qIndex + 1));
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
return indexes;
|
|
1337
|
+
}
|
|
1338
|
+
function coerceInsertBooleanArgs(sql, args) {
|
|
1339
|
+
const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
|
|
1340
|
+
if (!match) return;
|
|
1341
|
+
const rawTable = match[1];
|
|
1342
|
+
const rawColumns = match[2];
|
|
1343
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
1344
|
+
if (!boolColumns?.size) return;
|
|
1345
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
1346
|
+
for (const [index, column] of columns.entries()) {
|
|
1347
|
+
if (boolColumns.has(column) && index < args.length) {
|
|
1348
|
+
args[index] = toBoolean(args[index]);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
function coerceUpdateBooleanArgs(sql, args) {
|
|
1353
|
+
const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
|
|
1354
|
+
if (!match) return;
|
|
1355
|
+
const rawTable = match[1];
|
|
1356
|
+
const setClause = match[2];
|
|
1357
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
1358
|
+
if (!boolColumns?.size) return;
|
|
1359
|
+
const assignments = setClause.split(",");
|
|
1360
|
+
let placeholderIndex = 0;
|
|
1361
|
+
for (const assignment of assignments) {
|
|
1362
|
+
if (!assignment.includes("?")) continue;
|
|
1363
|
+
placeholderIndex += 1;
|
|
1364
|
+
const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
|
|
1365
|
+
if (colMatch && boolColumns.has(colMatch[1])) {
|
|
1366
|
+
args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
function coerceBooleanArgs(sql, args) {
|
|
1371
|
+
const nextArgs = [...args];
|
|
1372
|
+
coerceInsertBooleanArgs(sql, nextArgs);
|
|
1373
|
+
coerceUpdateBooleanArgs(sql, nextArgs);
|
|
1374
|
+
const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
|
|
1375
|
+
for (const index of placeholderIndexes) {
|
|
1376
|
+
if (index > 0 && index <= nextArgs.length) {
|
|
1377
|
+
nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
return nextArgs;
|
|
1381
|
+
}
|
|
1382
|
+
function convertQuestionMarksToDollarParams(sql) {
|
|
1383
|
+
let out = "";
|
|
1384
|
+
let placeholder = 0;
|
|
1385
|
+
let inSingle = false;
|
|
1386
|
+
let inDouble = false;
|
|
1387
|
+
let inLineComment = false;
|
|
1388
|
+
let inBlockComment = false;
|
|
1389
|
+
for (let i = 0; i < sql.length; i++) {
|
|
1390
|
+
const ch = sql[i];
|
|
1391
|
+
const next = sql[i + 1];
|
|
1392
|
+
if (inLineComment) {
|
|
1393
|
+
out += ch;
|
|
1394
|
+
if (ch === "\n") inLineComment = false;
|
|
1395
|
+
continue;
|
|
1396
|
+
}
|
|
1397
|
+
if (inBlockComment) {
|
|
1398
|
+
out += ch;
|
|
1399
|
+
if (ch === "*" && next === "/") {
|
|
1400
|
+
out += next;
|
|
1401
|
+
inBlockComment = false;
|
|
1402
|
+
i += 1;
|
|
1403
|
+
}
|
|
1404
|
+
continue;
|
|
1405
|
+
}
|
|
1406
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
1407
|
+
out += ch + next;
|
|
1408
|
+
inLineComment = true;
|
|
1409
|
+
i += 1;
|
|
1410
|
+
continue;
|
|
1411
|
+
}
|
|
1412
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
1413
|
+
out += ch + next;
|
|
1414
|
+
inBlockComment = true;
|
|
1415
|
+
i += 1;
|
|
1416
|
+
continue;
|
|
1417
|
+
}
|
|
1418
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
1419
|
+
inSingle = !inSingle;
|
|
1420
|
+
out += ch;
|
|
1421
|
+
continue;
|
|
1422
|
+
}
|
|
1423
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
1424
|
+
inDouble = !inDouble;
|
|
1425
|
+
out += ch;
|
|
1426
|
+
continue;
|
|
1427
|
+
}
|
|
1428
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
1429
|
+
placeholder += 1;
|
|
1430
|
+
out += `$${placeholder}`;
|
|
1431
|
+
continue;
|
|
1432
|
+
}
|
|
1433
|
+
out += ch;
|
|
1434
|
+
}
|
|
1435
|
+
return out;
|
|
1436
|
+
}
|
|
1437
|
+
function translateStatementForPostgres(stmt) {
|
|
1438
|
+
const normalized = normalizeStatement(stmt);
|
|
1439
|
+
if (normalized.kind === "named") {
|
|
1440
|
+
throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
|
|
1441
|
+
}
|
|
1442
|
+
const rewrittenSql = rewriteSql(normalized.sql);
|
|
1443
|
+
const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
|
|
1444
|
+
return {
|
|
1445
|
+
sql: convertQuestionMarksToDollarParams(rewrittenSql),
|
|
1446
|
+
args: coercedArgs
|
|
1447
|
+
};
|
|
1448
|
+
}
|
|
1449
|
+
function shouldBypassPostgres(stmt) {
|
|
1450
|
+
const normalized = normalizeStatement(stmt);
|
|
1451
|
+
if (normalized.kind === "named") {
|
|
1452
|
+
return true;
|
|
1453
|
+
}
|
|
1454
|
+
return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
|
|
1455
|
+
}
|
|
1456
|
+
function shouldFallbackOnError(error) {
|
|
1457
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1458
|
+
return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
|
|
1459
|
+
}
|
|
1460
|
+
function isReadQuery(sql) {
|
|
1461
|
+
const trimmed = sql.trimStart();
|
|
1462
|
+
return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
|
|
1463
|
+
}
|
|
1464
|
+
function buildRow(row, columns) {
|
|
1465
|
+
const values = columns.map((column) => row[column]);
|
|
1466
|
+
return Object.assign(values, row);
|
|
1467
|
+
}
|
|
1468
|
+
function buildResultSet(rows, rowsAffected = 0) {
|
|
1469
|
+
const columns = rows[0] ? Object.keys(rows[0]) : [];
|
|
1470
|
+
const resultRows = rows.map((row) => buildRow(row, columns));
|
|
1471
|
+
return {
|
|
1472
|
+
columns,
|
|
1473
|
+
columnTypes: columns.map(() => ""),
|
|
1474
|
+
rows: resultRows,
|
|
1475
|
+
rowsAffected,
|
|
1476
|
+
lastInsertRowid: void 0,
|
|
1477
|
+
toJSON() {
|
|
1478
|
+
return {
|
|
1479
|
+
columns,
|
|
1480
|
+
columnTypes: columns.map(() => ""),
|
|
1481
|
+
rows,
|
|
1482
|
+
rowsAffected,
|
|
1483
|
+
lastInsertRowid: void 0
|
|
1484
|
+
};
|
|
1485
|
+
}
|
|
1486
|
+
};
|
|
1487
|
+
}
|
|
1488
|
+
async function loadPrismaClient() {
|
|
1489
|
+
if (!prismaClientPromise) {
|
|
1490
|
+
prismaClientPromise = (async () => {
|
|
1491
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
1492
|
+
if (explicitPath) {
|
|
1493
|
+
const module2 = await import(pathToFileURL(explicitPath).href);
|
|
1494
|
+
const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
|
|
1495
|
+
if (!PrismaClient2) {
|
|
1496
|
+
throw new Error(`No PrismaClient export found at ${explicitPath}`);
|
|
1497
|
+
}
|
|
1498
|
+
return new PrismaClient2();
|
|
1499
|
+
}
|
|
1500
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path6.join(os5.homedir(), "exe-db");
|
|
1501
|
+
const requireFromExeDb = createRequire(path6.join(exeDbRoot, "package.json"));
|
|
1502
|
+
const prismaEntry = requireFromExeDb.resolve("@prisma/client");
|
|
1503
|
+
const module = await import(pathToFileURL(prismaEntry).href);
|
|
1504
|
+
const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
|
|
1505
|
+
if (!PrismaClient) {
|
|
1506
|
+
throw new Error(`No PrismaClient export found in ${prismaEntry}`);
|
|
1507
|
+
}
|
|
1508
|
+
return new PrismaClient();
|
|
1509
|
+
})();
|
|
1510
|
+
}
|
|
1511
|
+
return prismaClientPromise;
|
|
1512
|
+
}
|
|
1513
|
+
async function ensureCompatibilityViews(prisma) {
|
|
1514
|
+
if (!compatibilityBootstrapPromise) {
|
|
1515
|
+
compatibilityBootstrapPromise = (async () => {
|
|
1516
|
+
for (const mapping of VIEW_MAPPINGS) {
|
|
1517
|
+
const relation = mapping.source.replace(/"/g, "");
|
|
1518
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
1519
|
+
"SELECT to_regclass($1) AS regclass",
|
|
1520
|
+
relation
|
|
1521
|
+
);
|
|
1522
|
+
if (!rows[0]?.regclass) {
|
|
1523
|
+
continue;
|
|
1524
|
+
}
|
|
1525
|
+
await prisma.$executeRawUnsafe(
|
|
1526
|
+
`CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
|
|
1527
|
+
);
|
|
1528
|
+
}
|
|
1529
|
+
})();
|
|
1530
|
+
}
|
|
1531
|
+
return compatibilityBootstrapPromise;
|
|
1532
|
+
}
|
|
1533
|
+
async function executeOnPrisma(executor, stmt) {
|
|
1534
|
+
const translated = translateStatementForPostgres(stmt);
|
|
1535
|
+
if (isReadQuery(translated.sql)) {
|
|
1536
|
+
const rows = await executor.$queryRawUnsafe(
|
|
1537
|
+
translated.sql,
|
|
1538
|
+
...translated.args
|
|
1539
|
+
);
|
|
1540
|
+
return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
|
|
1541
|
+
}
|
|
1542
|
+
const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
|
|
1543
|
+
return buildResultSet([], rowsAffected);
|
|
1544
|
+
}
|
|
1545
|
+
function splitSqlStatements(sql) {
|
|
1546
|
+
const parts = [];
|
|
1547
|
+
let current = "";
|
|
1548
|
+
let inSingle = false;
|
|
1549
|
+
let inDouble = false;
|
|
1550
|
+
let inLineComment = false;
|
|
1551
|
+
let inBlockComment = false;
|
|
1552
|
+
for (let i = 0; i < sql.length; i++) {
|
|
1553
|
+
const ch = sql[i];
|
|
1554
|
+
const next = sql[i + 1];
|
|
1555
|
+
if (inLineComment) {
|
|
1556
|
+
current += ch;
|
|
1557
|
+
if (ch === "\n") inLineComment = false;
|
|
1558
|
+
continue;
|
|
1559
|
+
}
|
|
1560
|
+
if (inBlockComment) {
|
|
1561
|
+
current += ch;
|
|
1562
|
+
if (ch === "*" && next === "/") {
|
|
1563
|
+
current += next;
|
|
1564
|
+
inBlockComment = false;
|
|
1565
|
+
i += 1;
|
|
1566
|
+
}
|
|
1567
|
+
continue;
|
|
1568
|
+
}
|
|
1569
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
1570
|
+
current += ch + next;
|
|
1571
|
+
inLineComment = true;
|
|
1572
|
+
i += 1;
|
|
1573
|
+
continue;
|
|
1574
|
+
}
|
|
1575
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
1576
|
+
current += ch + next;
|
|
1577
|
+
inBlockComment = true;
|
|
1578
|
+
i += 1;
|
|
1579
|
+
continue;
|
|
1580
|
+
}
|
|
1581
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
1582
|
+
inSingle = !inSingle;
|
|
1583
|
+
current += ch;
|
|
1584
|
+
continue;
|
|
1585
|
+
}
|
|
1586
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
1587
|
+
inDouble = !inDouble;
|
|
1588
|
+
current += ch;
|
|
1589
|
+
continue;
|
|
1590
|
+
}
|
|
1591
|
+
if (!inSingle && !inDouble && ch === ";") {
|
|
1592
|
+
if (current.trim()) {
|
|
1593
|
+
parts.push(current.trim());
|
|
1594
|
+
}
|
|
1595
|
+
current = "";
|
|
1596
|
+
continue;
|
|
1597
|
+
}
|
|
1598
|
+
current += ch;
|
|
1599
|
+
}
|
|
1600
|
+
if (current.trim()) {
|
|
1601
|
+
parts.push(current.trim());
|
|
1602
|
+
}
|
|
1603
|
+
return parts;
|
|
1604
|
+
}
|
|
1605
|
+
async function createPrismaDbAdapter(fallbackClient) {
|
|
1606
|
+
const prisma = await loadPrismaClient();
|
|
1607
|
+
await ensureCompatibilityViews(prisma);
|
|
1608
|
+
let closed = false;
|
|
1609
|
+
let adapter;
|
|
1610
|
+
const fallbackExecute = async (stmt, error) => {
|
|
1611
|
+
if (!fallbackClient) {
|
|
1612
|
+
if (error) throw error;
|
|
1613
|
+
throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
|
|
1614
|
+
}
|
|
1615
|
+
if (error) {
|
|
1616
|
+
process.stderr.write(
|
|
1617
|
+
`[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
1618
|
+
`
|
|
1619
|
+
);
|
|
1620
|
+
}
|
|
1621
|
+
return fallbackClient.execute(stmt);
|
|
1622
|
+
};
|
|
1623
|
+
adapter = {
|
|
1624
|
+
async execute(stmt) {
|
|
1625
|
+
if (shouldBypassPostgres(stmt)) {
|
|
1626
|
+
return fallbackExecute(stmt);
|
|
1627
|
+
}
|
|
1628
|
+
try {
|
|
1629
|
+
return await executeOnPrisma(prisma, stmt);
|
|
1630
|
+
} catch (error) {
|
|
1631
|
+
if (shouldFallbackOnError(error)) {
|
|
1632
|
+
return fallbackExecute(stmt, error);
|
|
1633
|
+
}
|
|
1634
|
+
throw error;
|
|
1635
|
+
}
|
|
1636
|
+
},
|
|
1637
|
+
async batch(stmts, mode) {
|
|
1638
|
+
if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
|
|
1639
|
+
if (!fallbackClient) {
|
|
1640
|
+
throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
|
|
1641
|
+
}
|
|
1642
|
+
return fallbackClient.batch(stmts, mode);
|
|
1643
|
+
}
|
|
1644
|
+
try {
|
|
1645
|
+
if (prisma.$transaction) {
|
|
1646
|
+
return await prisma.$transaction(async (tx) => {
|
|
1647
|
+
const results2 = [];
|
|
1648
|
+
for (const stmt of stmts) {
|
|
1649
|
+
results2.push(await executeOnPrisma(tx, stmt));
|
|
1650
|
+
}
|
|
1651
|
+
return results2;
|
|
1652
|
+
});
|
|
1653
|
+
}
|
|
1654
|
+
const results = [];
|
|
1655
|
+
for (const stmt of stmts) {
|
|
1656
|
+
results.push(await executeOnPrisma(prisma, stmt));
|
|
1657
|
+
}
|
|
1658
|
+
return results;
|
|
1659
|
+
} catch (error) {
|
|
1660
|
+
if (fallbackClient && shouldFallbackOnError(error)) {
|
|
1661
|
+
process.stderr.write(
|
|
1662
|
+
`[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
1663
|
+
`
|
|
1664
|
+
);
|
|
1665
|
+
return fallbackClient.batch(stmts, mode);
|
|
1666
|
+
}
|
|
1667
|
+
throw error;
|
|
1668
|
+
}
|
|
1669
|
+
},
|
|
1670
|
+
async migrate(stmts) {
|
|
1671
|
+
if (fallbackClient) {
|
|
1672
|
+
return fallbackClient.migrate(stmts);
|
|
1673
|
+
}
|
|
1674
|
+
return adapter.batch(stmts, "deferred");
|
|
1675
|
+
},
|
|
1676
|
+
async transaction(mode) {
|
|
1677
|
+
if (!fallbackClient) {
|
|
1678
|
+
throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
|
|
1679
|
+
}
|
|
1680
|
+
return fallbackClient.transaction(mode);
|
|
1681
|
+
},
|
|
1682
|
+
async executeMultiple(sql) {
|
|
1683
|
+
if (fallbackClient && shouldBypassPostgres(sql)) {
|
|
1684
|
+
return fallbackClient.executeMultiple(sql);
|
|
1685
|
+
}
|
|
1686
|
+
for (const statement of splitSqlStatements(sql)) {
|
|
1687
|
+
await adapter.execute(statement);
|
|
1688
|
+
}
|
|
1689
|
+
},
|
|
1690
|
+
async sync() {
|
|
1691
|
+
if (fallbackClient) {
|
|
1692
|
+
return fallbackClient.sync();
|
|
1693
|
+
}
|
|
1694
|
+
return { frame_no: 0, frames_synced: 0 };
|
|
1695
|
+
},
|
|
1696
|
+
close() {
|
|
1697
|
+
closed = true;
|
|
1698
|
+
prismaClientPromise = null;
|
|
1699
|
+
compatibilityBootstrapPromise = null;
|
|
1700
|
+
void prisma.$disconnect?.();
|
|
1701
|
+
},
|
|
1702
|
+
get closed() {
|
|
1703
|
+
return closed;
|
|
1704
|
+
},
|
|
1705
|
+
get protocol() {
|
|
1706
|
+
return "prisma-postgres";
|
|
1707
|
+
}
|
|
1708
|
+
};
|
|
1709
|
+
return adapter;
|
|
1710
|
+
}
|
|
1711
|
+
var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
|
|
1712
|
+
var init_database_adapter = __esm({
|
|
1713
|
+
"src/lib/database-adapter.ts"() {
|
|
1714
|
+
"use strict";
|
|
1715
|
+
VIEW_MAPPINGS = [
|
|
1716
|
+
{ view: "memories", source: "memory.memory_records" },
|
|
1717
|
+
{ view: "tasks", source: "memory.tasks" },
|
|
1718
|
+
{ view: "behaviors", source: "memory.behaviors" },
|
|
1719
|
+
{ view: "entities", source: "memory.entities" },
|
|
1720
|
+
{ view: "relationships", source: "memory.relationships" },
|
|
1721
|
+
{ view: "entity_memories", source: "memory.entity_memories" },
|
|
1722
|
+
{ view: "entity_aliases", source: "memory.entity_aliases" },
|
|
1723
|
+
{ view: "notifications", source: "memory.notifications" },
|
|
1724
|
+
{ view: "messages", source: "memory.messages" },
|
|
1725
|
+
{ view: "users", source: "wiki.users" },
|
|
1726
|
+
{ view: "workspaces", source: "wiki.workspaces" },
|
|
1727
|
+
{ view: "workspace_users", source: "wiki.workspace_users" },
|
|
1728
|
+
{ view: "documents", source: "wiki.workspace_documents" },
|
|
1729
|
+
{ view: "chats", source: "wiki.workspace_chats" }
|
|
1730
|
+
];
|
|
1731
|
+
UPSERT_KEYS = {
|
|
1732
|
+
memories: ["id"],
|
|
1733
|
+
tasks: ["id"],
|
|
1734
|
+
behaviors: ["id"],
|
|
1735
|
+
entities: ["id"],
|
|
1736
|
+
relationships: ["id"],
|
|
1737
|
+
entity_aliases: ["alias"],
|
|
1738
|
+
notifications: ["id"],
|
|
1739
|
+
messages: ["id"],
|
|
1740
|
+
users: ["id"],
|
|
1741
|
+
workspaces: ["id"],
|
|
1742
|
+
workspace_users: ["id"],
|
|
1743
|
+
documents: ["id"],
|
|
1744
|
+
chats: ["id"]
|
|
1745
|
+
};
|
|
1746
|
+
BOOLEAN_COLUMNS_BY_TABLE = {
|
|
1747
|
+
memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
|
|
1748
|
+
behaviors: /* @__PURE__ */ new Set(["active"]),
|
|
1749
|
+
notifications: /* @__PURE__ */ new Set(["read"]),
|
|
1750
|
+
users: /* @__PURE__ */ new Set(["has_personal_memory"])
|
|
1751
|
+
};
|
|
1752
|
+
BOOLEAN_COLUMN_NAMES = new Set(
|
|
1753
|
+
Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
|
|
1754
|
+
);
|
|
1755
|
+
IMMEDIATE_FALLBACK_PATTERNS = [
|
|
1756
|
+
/\bPRAGMA\b/i,
|
|
1757
|
+
/\bsqlite_master\b/i,
|
|
1758
|
+
/(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
|
|
1759
|
+
/\bMATCH\b/i,
|
|
1760
|
+
/\bvector_distance_cos\s*\(/i,
|
|
1761
|
+
/\bjson_extract\s*\(/i,
|
|
1762
|
+
/\bjulianday\s*\(/i,
|
|
1763
|
+
/\bstrftime\s*\(/i,
|
|
1764
|
+
/\blast_insert_rowid\s*\(/i
|
|
1765
|
+
];
|
|
1766
|
+
prismaClientPromise = null;
|
|
1767
|
+
compatibilityBootstrapPromise = null;
|
|
1768
|
+
}
|
|
1769
|
+
});
|
|
1770
|
+
|
|
1771
|
+
// src/lib/daemon-auth.ts
|
|
1772
|
+
import crypto from "crypto";
|
|
1773
|
+
import path7 from "path";
|
|
1774
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
1775
|
+
function normalizeToken(token) {
|
|
1776
|
+
if (!token) return null;
|
|
1777
|
+
const trimmed = token.trim();
|
|
1778
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
1779
|
+
}
|
|
1780
|
+
function readDaemonToken() {
|
|
1781
|
+
try {
|
|
1782
|
+
if (!existsSync8(DAEMON_TOKEN_PATH)) return null;
|
|
1783
|
+
return normalizeToken(readFileSync7(DAEMON_TOKEN_PATH, "utf8"));
|
|
1784
|
+
} catch {
|
|
1785
|
+
return null;
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
function ensureDaemonToken(seed) {
|
|
1789
|
+
const existing = readDaemonToken();
|
|
1790
|
+
if (existing) return existing;
|
|
1791
|
+
const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
|
|
1792
|
+
ensurePrivateDirSync(EXE_AI_DIR);
|
|
1793
|
+
writeFileSync5(DAEMON_TOKEN_PATH, `${token}
|
|
1794
|
+
`, "utf8");
|
|
1795
|
+
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
1796
|
+
return token;
|
|
1797
|
+
}
|
|
1798
|
+
var DAEMON_TOKEN_PATH;
|
|
1799
|
+
var init_daemon_auth = __esm({
|
|
1800
|
+
"src/lib/daemon-auth.ts"() {
|
|
1801
|
+
"use strict";
|
|
1802
|
+
init_config();
|
|
1803
|
+
init_secure_files();
|
|
1804
|
+
DAEMON_TOKEN_PATH = path7.join(EXE_AI_DIR, "exed.token");
|
|
1044
1805
|
}
|
|
1045
1806
|
});
|
|
1046
1807
|
|
|
1047
1808
|
// src/lib/exe-daemon-client.ts
|
|
1048
1809
|
import net from "net";
|
|
1049
|
-
import
|
|
1810
|
+
import os6 from "os";
|
|
1050
1811
|
import { spawn } from "child_process";
|
|
1051
1812
|
import { randomUUID } from "crypto";
|
|
1052
|
-
import { existsSync as
|
|
1053
|
-
import
|
|
1813
|
+
import { existsSync as existsSync9, unlinkSync as unlinkSync2, readFileSync as readFileSync8, openSync, closeSync, statSync } from "fs";
|
|
1814
|
+
import path8 from "path";
|
|
1054
1815
|
import { fileURLToPath } from "url";
|
|
1055
1816
|
function handleData(chunk) {
|
|
1056
1817
|
_buffer += chunk.toString();
|
|
@@ -1078,9 +1839,9 @@ function handleData(chunk) {
|
|
|
1078
1839
|
}
|
|
1079
1840
|
}
|
|
1080
1841
|
function cleanupStaleFiles() {
|
|
1081
|
-
if (
|
|
1842
|
+
if (existsSync9(PID_PATH)) {
|
|
1082
1843
|
try {
|
|
1083
|
-
const pid = parseInt(
|
|
1844
|
+
const pid = parseInt(readFileSync8(PID_PATH, "utf8").trim(), 10);
|
|
1084
1845
|
if (pid > 0) {
|
|
1085
1846
|
try {
|
|
1086
1847
|
process.kill(pid, 0);
|
|
@@ -1101,17 +1862,17 @@ function cleanupStaleFiles() {
|
|
|
1101
1862
|
}
|
|
1102
1863
|
}
|
|
1103
1864
|
function findPackageRoot() {
|
|
1104
|
-
let dir =
|
|
1105
|
-
const { root } =
|
|
1865
|
+
let dir = path8.dirname(fileURLToPath(import.meta.url));
|
|
1866
|
+
const { root } = path8.parse(dir);
|
|
1106
1867
|
while (dir !== root) {
|
|
1107
|
-
if (
|
|
1108
|
-
dir =
|
|
1868
|
+
if (existsSync9(path8.join(dir, "package.json"))) return dir;
|
|
1869
|
+
dir = path8.dirname(dir);
|
|
1109
1870
|
}
|
|
1110
1871
|
return null;
|
|
1111
1872
|
}
|
|
1112
1873
|
function spawnDaemon() {
|
|
1113
|
-
const freeGB =
|
|
1114
|
-
const totalGB =
|
|
1874
|
+
const freeGB = os6.freemem() / (1024 * 1024 * 1024);
|
|
1875
|
+
const totalGB = os6.totalmem() / (1024 * 1024 * 1024);
|
|
1115
1876
|
if (totalGB <= 8) {
|
|
1116
1877
|
process.stderr.write(
|
|
1117
1878
|
`[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
|
|
@@ -1131,16 +1892,17 @@ function spawnDaemon() {
|
|
|
1131
1892
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
1132
1893
|
return;
|
|
1133
1894
|
}
|
|
1134
|
-
const daemonPath =
|
|
1135
|
-
if (!
|
|
1895
|
+
const daemonPath = path8.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
1896
|
+
if (!existsSync9(daemonPath)) {
|
|
1136
1897
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
1137
1898
|
`);
|
|
1138
1899
|
return;
|
|
1139
1900
|
}
|
|
1140
1901
|
const resolvedPath = daemonPath;
|
|
1902
|
+
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
1141
1903
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
1142
1904
|
`);
|
|
1143
|
-
const logPath =
|
|
1905
|
+
const logPath = path8.join(path8.dirname(SOCKET_PATH), "exed.log");
|
|
1144
1906
|
let stderrFd = "ignore";
|
|
1145
1907
|
try {
|
|
1146
1908
|
stderrFd = openSync(logPath, "a");
|
|
@@ -1158,7 +1920,8 @@ function spawnDaemon() {
|
|
|
1158
1920
|
TMUX_PANE: void 0,
|
|
1159
1921
|
// Prevents resolveExeSession() from scoping to one session
|
|
1160
1922
|
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
1161
|
-
EXE_DAEMON_PID: PID_PATH
|
|
1923
|
+
EXE_DAEMON_PID: PID_PATH,
|
|
1924
|
+
[DAEMON_TOKEN_ENV]: daemonToken
|
|
1162
1925
|
}
|
|
1163
1926
|
});
|
|
1164
1927
|
child.unref();
|
|
@@ -1265,13 +2028,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
1265
2028
|
return;
|
|
1266
2029
|
}
|
|
1267
2030
|
const id = randomUUID();
|
|
2031
|
+
const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
|
|
1268
2032
|
const timer = setTimeout(() => {
|
|
1269
2033
|
_pending.delete(id);
|
|
1270
2034
|
resolve({ error: "Request timeout" });
|
|
1271
2035
|
}, timeoutMs);
|
|
1272
2036
|
_pending.set(id, { resolve, timer });
|
|
1273
2037
|
try {
|
|
1274
|
-
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
2038
|
+
_socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
|
|
1275
2039
|
} catch {
|
|
1276
2040
|
clearTimeout(timer);
|
|
1277
2041
|
_pending.delete(id);
|
|
@@ -1282,17 +2046,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
1282
2046
|
function isClientConnected() {
|
|
1283
2047
|
return _connected;
|
|
1284
2048
|
}
|
|
1285
|
-
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
|
|
2049
|
+
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, DAEMON_TOKEN_ENV, _socket, _connected, _buffer, _pending, MAX_BUFFER;
|
|
1286
2050
|
var init_exe_daemon_client = __esm({
|
|
1287
2051
|
"src/lib/exe-daemon-client.ts"() {
|
|
1288
2052
|
"use strict";
|
|
1289
2053
|
init_config();
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
2054
|
+
init_daemon_auth();
|
|
2055
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path8.join(EXE_AI_DIR, "exed.sock");
|
|
2056
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path8.join(EXE_AI_DIR, "exed.pid");
|
|
2057
|
+
SPAWN_LOCK_PATH = path8.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
1293
2058
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
1294
2059
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
1295
2060
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
2061
|
+
DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
|
|
1296
2062
|
_socket = null;
|
|
1297
2063
|
_connected = false;
|
|
1298
2064
|
_buffer = "";
|
|
@@ -1371,7 +2137,7 @@ __export(db_daemon_client_exports, {
|
|
|
1371
2137
|
createDaemonDbClient: () => createDaemonDbClient,
|
|
1372
2138
|
initDaemonDbClient: () => initDaemonDbClient
|
|
1373
2139
|
});
|
|
1374
|
-
function
|
|
2140
|
+
function normalizeStatement2(stmt) {
|
|
1375
2141
|
if (typeof stmt === "string") {
|
|
1376
2142
|
return { sql: stmt, args: [] };
|
|
1377
2143
|
}
|
|
@@ -1395,7 +2161,7 @@ function createDaemonDbClient(fallbackClient) {
|
|
|
1395
2161
|
if (!_useDaemon || !isClientConnected()) {
|
|
1396
2162
|
return fallbackClient.execute(stmt);
|
|
1397
2163
|
}
|
|
1398
|
-
const { sql, args } =
|
|
2164
|
+
const { sql, args } = normalizeStatement2(stmt);
|
|
1399
2165
|
const response = await sendDaemonRequest({
|
|
1400
2166
|
type: "db-execute",
|
|
1401
2167
|
sql,
|
|
@@ -1420,7 +2186,7 @@ function createDaemonDbClient(fallbackClient) {
|
|
|
1420
2186
|
if (!_useDaemon || !isClientConnected()) {
|
|
1421
2187
|
return fallbackClient.batch(stmts, mode);
|
|
1422
2188
|
}
|
|
1423
|
-
const statements = stmts.map(
|
|
2189
|
+
const statements = stmts.map(normalizeStatement2);
|
|
1424
2190
|
const response = await sendDaemonRequest({
|
|
1425
2191
|
type: "db-batch",
|
|
1426
2192
|
statements,
|
|
@@ -1515,6 +2281,18 @@ __export(database_exports, {
|
|
|
1515
2281
|
});
|
|
1516
2282
|
import { createClient } from "@libsql/client";
|
|
1517
2283
|
async function initDatabase(config) {
|
|
2284
|
+
if (_walCheckpointTimer) {
|
|
2285
|
+
clearInterval(_walCheckpointTimer);
|
|
2286
|
+
_walCheckpointTimer = null;
|
|
2287
|
+
}
|
|
2288
|
+
if (_daemonClient) {
|
|
2289
|
+
_daemonClient.close();
|
|
2290
|
+
_daemonClient = null;
|
|
2291
|
+
}
|
|
2292
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
2293
|
+
_adapterClient.close();
|
|
2294
|
+
}
|
|
2295
|
+
_adapterClient = null;
|
|
1518
2296
|
if (_client) {
|
|
1519
2297
|
_client.close();
|
|
1520
2298
|
_client = null;
|
|
@@ -1528,6 +2306,7 @@ async function initDatabase(config) {
|
|
|
1528
2306
|
}
|
|
1529
2307
|
_client = createClient(opts);
|
|
1530
2308
|
_resilientClient = wrapWithRetry(_client);
|
|
2309
|
+
_adapterClient = _resilientClient;
|
|
1531
2310
|
_client.execute("PRAGMA busy_timeout = 30000").catch(() => {
|
|
1532
2311
|
});
|
|
1533
2312
|
_client.execute("PRAGMA journal_mode = WAL").catch(() => {
|
|
@@ -1538,14 +2317,20 @@ async function initDatabase(config) {
|
|
|
1538
2317
|
});
|
|
1539
2318
|
}, 3e4);
|
|
1540
2319
|
_walCheckpointTimer.unref();
|
|
2320
|
+
if (process.env.DATABASE_URL) {
|
|
2321
|
+
_adapterClient = await createPrismaDbAdapter(_resilientClient);
|
|
2322
|
+
}
|
|
1541
2323
|
}
|
|
1542
2324
|
function isInitialized() {
|
|
1543
|
-
return _client !== null;
|
|
2325
|
+
return _adapterClient !== null || _client !== null;
|
|
1544
2326
|
}
|
|
1545
2327
|
function getClient() {
|
|
1546
|
-
if (!
|
|
2328
|
+
if (!_adapterClient) {
|
|
1547
2329
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
1548
2330
|
}
|
|
2331
|
+
if (process.env.DATABASE_URL) {
|
|
2332
|
+
return _adapterClient;
|
|
2333
|
+
}
|
|
1549
2334
|
if (process.env.EXE_IS_DAEMON === "1") {
|
|
1550
2335
|
return _resilientClient;
|
|
1551
2336
|
}
|
|
@@ -1555,6 +2340,7 @@ function getClient() {
|
|
|
1555
2340
|
return _resilientClient;
|
|
1556
2341
|
}
|
|
1557
2342
|
async function initDaemonClient() {
|
|
2343
|
+
if (process.env.DATABASE_URL) return;
|
|
1558
2344
|
if (process.env.EXE_IS_DAEMON === "1") return;
|
|
1559
2345
|
if (!_resilientClient) return;
|
|
1560
2346
|
try {
|
|
@@ -1851,6 +2637,7 @@ async function ensureSchema() {
|
|
|
1851
2637
|
project TEXT NOT NULL,
|
|
1852
2638
|
summary TEXT NOT NULL,
|
|
1853
2639
|
task_file TEXT,
|
|
2640
|
+
session_scope TEXT,
|
|
1854
2641
|
read INTEGER NOT NULL DEFAULT 0,
|
|
1855
2642
|
created_at TEXT NOT NULL
|
|
1856
2643
|
);
|
|
@@ -1859,7 +2646,7 @@ async function ensureSchema() {
|
|
|
1859
2646
|
ON notifications(read);
|
|
1860
2647
|
|
|
1861
2648
|
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
1862
|
-
ON notifications(agent_id);
|
|
2649
|
+
ON notifications(agent_id, session_scope);
|
|
1863
2650
|
|
|
1864
2651
|
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
1865
2652
|
ON notifications(task_file);
|
|
@@ -1897,6 +2684,7 @@ async function ensureSchema() {
|
|
|
1897
2684
|
target_agent TEXT NOT NULL,
|
|
1898
2685
|
target_project TEXT,
|
|
1899
2686
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
2687
|
+
session_scope TEXT,
|
|
1900
2688
|
content TEXT NOT NULL,
|
|
1901
2689
|
priority TEXT DEFAULT 'normal',
|
|
1902
2690
|
status TEXT DEFAULT 'pending',
|
|
@@ -1910,10 +2698,31 @@ async function ensureSchema() {
|
|
|
1910
2698
|
);
|
|
1911
2699
|
|
|
1912
2700
|
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
1913
|
-
ON messages(target_agent, status);
|
|
2701
|
+
ON messages(target_agent, session_scope, status);
|
|
1914
2702
|
|
|
1915
2703
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
1916
|
-
ON messages(target_agent, from_agent, server_seq);
|
|
2704
|
+
ON messages(target_agent, session_scope, from_agent, server_seq);
|
|
2705
|
+
`);
|
|
2706
|
+
try {
|
|
2707
|
+
await client.execute({
|
|
2708
|
+
sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
|
|
2709
|
+
args: []
|
|
2710
|
+
});
|
|
2711
|
+
} catch {
|
|
2712
|
+
}
|
|
2713
|
+
try {
|
|
2714
|
+
await client.execute({
|
|
2715
|
+
sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
|
|
2716
|
+
args: []
|
|
2717
|
+
});
|
|
2718
|
+
} catch {
|
|
2719
|
+
}
|
|
2720
|
+
await client.executeMultiple(`
|
|
2721
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
|
|
2722
|
+
ON notifications(agent_id, session_scope, read, created_at);
|
|
2723
|
+
|
|
2724
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
|
|
2725
|
+
ON messages(target_agent, session_scope, status, created_at);
|
|
1917
2726
|
`);
|
|
1918
2727
|
try {
|
|
1919
2728
|
await client.execute({
|
|
@@ -2497,28 +3306,45 @@ async function ensureSchema() {
|
|
|
2497
3306
|
} catch {
|
|
2498
3307
|
}
|
|
2499
3308
|
}
|
|
3309
|
+
try {
|
|
3310
|
+
await client.execute({
|
|
3311
|
+
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
3312
|
+
args: []
|
|
3313
|
+
});
|
|
3314
|
+
} catch {
|
|
3315
|
+
}
|
|
2500
3316
|
}
|
|
2501
3317
|
async function disposeDatabase() {
|
|
3318
|
+
if (_walCheckpointTimer) {
|
|
3319
|
+
clearInterval(_walCheckpointTimer);
|
|
3320
|
+
_walCheckpointTimer = null;
|
|
3321
|
+
}
|
|
2502
3322
|
if (_daemonClient) {
|
|
2503
3323
|
_daemonClient.close();
|
|
2504
3324
|
_daemonClient = null;
|
|
2505
3325
|
}
|
|
3326
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
3327
|
+
_adapterClient.close();
|
|
3328
|
+
}
|
|
3329
|
+
_adapterClient = null;
|
|
2506
3330
|
if (_client) {
|
|
2507
3331
|
_client.close();
|
|
2508
3332
|
_client = null;
|
|
2509
3333
|
_resilientClient = null;
|
|
2510
3334
|
}
|
|
2511
3335
|
}
|
|
2512
|
-
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
|
|
3336
|
+
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
|
|
2513
3337
|
var init_database = __esm({
|
|
2514
3338
|
"src/lib/database.ts"() {
|
|
2515
3339
|
"use strict";
|
|
2516
3340
|
init_db_retry();
|
|
2517
3341
|
init_employees();
|
|
3342
|
+
init_database_adapter();
|
|
2518
3343
|
_client = null;
|
|
2519
3344
|
_resilientClient = null;
|
|
2520
3345
|
_walCheckpointTimer = null;
|
|
2521
3346
|
_daemonClient = null;
|
|
3347
|
+
_adapterClient = null;
|
|
2522
3348
|
initTurso = initDatabase;
|
|
2523
3349
|
disposeTurso = disposeDatabase;
|
|
2524
3350
|
}
|
|
@@ -2541,9 +3367,12 @@ __export(license_exports, {
|
|
|
2541
3367
|
stopLicenseRevalidation: () => stopLicenseRevalidation,
|
|
2542
3368
|
validateLicense: () => validateLicense
|
|
2543
3369
|
});
|
|
2544
|
-
import { readFileSync as
|
|
3370
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, existsSync as existsSync10, mkdirSync as mkdirSync4 } from "fs";
|
|
2545
3371
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
2546
|
-
import
|
|
3372
|
+
import { createRequire as createRequire2 } from "module";
|
|
3373
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
3374
|
+
import os7 from "os";
|
|
3375
|
+
import path9 from "path";
|
|
2547
3376
|
import { jwtVerify, importSPKI } from "jose";
|
|
2548
3377
|
async function fetchRetry(url, init) {
|
|
2549
3378
|
try {
|
|
@@ -2554,37 +3383,37 @@ async function fetchRetry(url, init) {
|
|
|
2554
3383
|
}
|
|
2555
3384
|
}
|
|
2556
3385
|
function loadDeviceId() {
|
|
2557
|
-
const deviceJsonPath =
|
|
3386
|
+
const deviceJsonPath = path9.join(EXE_AI_DIR, "device.json");
|
|
2558
3387
|
try {
|
|
2559
|
-
if (
|
|
2560
|
-
const data = JSON.parse(
|
|
3388
|
+
if (existsSync10(deviceJsonPath)) {
|
|
3389
|
+
const data = JSON.parse(readFileSync9(deviceJsonPath, "utf8"));
|
|
2561
3390
|
if (data.deviceId) return data.deviceId;
|
|
2562
3391
|
}
|
|
2563
3392
|
} catch {
|
|
2564
3393
|
}
|
|
2565
3394
|
try {
|
|
2566
|
-
if (
|
|
2567
|
-
const id2 =
|
|
3395
|
+
if (existsSync10(DEVICE_ID_PATH)) {
|
|
3396
|
+
const id2 = readFileSync9(DEVICE_ID_PATH, "utf8").trim();
|
|
2568
3397
|
if (id2) return id2;
|
|
2569
3398
|
}
|
|
2570
3399
|
} catch {
|
|
2571
3400
|
}
|
|
2572
3401
|
const id = randomUUID2();
|
|
2573
3402
|
mkdirSync4(EXE_AI_DIR, { recursive: true });
|
|
2574
|
-
|
|
3403
|
+
writeFileSync6(DEVICE_ID_PATH, id, "utf8");
|
|
2575
3404
|
return id;
|
|
2576
3405
|
}
|
|
2577
3406
|
function loadLicense() {
|
|
2578
3407
|
try {
|
|
2579
|
-
if (!
|
|
2580
|
-
return
|
|
3408
|
+
if (!existsSync10(LICENSE_PATH)) return null;
|
|
3409
|
+
return readFileSync9(LICENSE_PATH, "utf8").trim();
|
|
2581
3410
|
} catch {
|
|
2582
3411
|
return null;
|
|
2583
3412
|
}
|
|
2584
3413
|
}
|
|
2585
3414
|
function saveLicense(apiKey) {
|
|
2586
3415
|
mkdirSync4(EXE_AI_DIR, { recursive: true });
|
|
2587
|
-
|
|
3416
|
+
writeFileSync6(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
|
|
2588
3417
|
}
|
|
2589
3418
|
async function verifyLicenseJwt(token) {
|
|
2590
3419
|
try {
|
|
@@ -2610,8 +3439,8 @@ async function verifyLicenseJwt(token) {
|
|
|
2610
3439
|
}
|
|
2611
3440
|
async function getCachedLicense() {
|
|
2612
3441
|
try {
|
|
2613
|
-
if (!
|
|
2614
|
-
const raw = JSON.parse(
|
|
3442
|
+
if (!existsSync10(CACHE_PATH)) return null;
|
|
3443
|
+
const raw = JSON.parse(readFileSync9(CACHE_PATH, "utf8"));
|
|
2615
3444
|
if (!raw.token || typeof raw.token !== "string") return null;
|
|
2616
3445
|
return await verifyLicenseJwt(raw.token);
|
|
2617
3446
|
} catch {
|
|
@@ -2620,8 +3449,8 @@ async function getCachedLicense() {
|
|
|
2620
3449
|
}
|
|
2621
3450
|
function readCachedToken() {
|
|
2622
3451
|
try {
|
|
2623
|
-
if (!
|
|
2624
|
-
const raw = JSON.parse(
|
|
3452
|
+
if (!existsSync10(CACHE_PATH)) return null;
|
|
3453
|
+
const raw = JSON.parse(readFileSync9(CACHE_PATH, "utf8"));
|
|
2625
3454
|
return typeof raw.token === "string" ? raw.token : null;
|
|
2626
3455
|
} catch {
|
|
2627
3456
|
return null;
|
|
@@ -2655,56 +3484,130 @@ function getRawCachedPlan() {
|
|
|
2655
3484
|
}
|
|
2656
3485
|
function cacheResponse(token) {
|
|
2657
3486
|
try {
|
|
2658
|
-
|
|
3487
|
+
writeFileSync6(CACHE_PATH, JSON.stringify({ token }), "utf8");
|
|
2659
3488
|
} catch {
|
|
2660
3489
|
}
|
|
2661
3490
|
}
|
|
2662
|
-
|
|
2663
|
-
|
|
3491
|
+
function loadPrismaForLicense() {
|
|
3492
|
+
if (_prismaFailed) return null;
|
|
3493
|
+
const dbUrl = process.env.DATABASE_URL;
|
|
3494
|
+
if (!dbUrl) {
|
|
3495
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path9.join(os7.homedir(), "exe-db");
|
|
3496
|
+
if (!existsSync10(path9.join(exeDbRoot, "package.json"))) {
|
|
3497
|
+
_prismaFailed = true;
|
|
3498
|
+
return null;
|
|
3499
|
+
}
|
|
3500
|
+
}
|
|
3501
|
+
if (!_prismaPromise) {
|
|
3502
|
+
_prismaPromise = (async () => {
|
|
3503
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
3504
|
+
if (explicitPath) {
|
|
3505
|
+
const mod2 = await import(pathToFileURL2(explicitPath).href);
|
|
3506
|
+
const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
|
|
3507
|
+
if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
|
|
3508
|
+
return new Ctor2();
|
|
3509
|
+
}
|
|
3510
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path9.join(os7.homedir(), "exe-db");
|
|
3511
|
+
const req = createRequire2(path9.join(exeDbRoot, "package.json"));
|
|
3512
|
+
const entry = req.resolve("@prisma/client");
|
|
3513
|
+
const mod = await import(pathToFileURL2(entry).href);
|
|
3514
|
+
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
3515
|
+
if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
|
|
3516
|
+
return new Ctor();
|
|
3517
|
+
})().catch((err) => {
|
|
3518
|
+
_prismaFailed = true;
|
|
3519
|
+
_prismaPromise = null;
|
|
3520
|
+
throw err;
|
|
3521
|
+
});
|
|
3522
|
+
}
|
|
3523
|
+
return _prismaPromise;
|
|
3524
|
+
}
|
|
3525
|
+
async function validateViaPostgres(apiKey) {
|
|
3526
|
+
const loader = loadPrismaForLicense();
|
|
3527
|
+
if (!loader) return null;
|
|
3528
|
+
try {
|
|
3529
|
+
const prisma = await loader;
|
|
3530
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
3531
|
+
`SELECT plan, email, status, device_limit, employee_limit, memory_limit, expires_at
|
|
3532
|
+
FROM billing.licenses WHERE key = $1 LIMIT 1`,
|
|
3533
|
+
apiKey
|
|
3534
|
+
);
|
|
3535
|
+
if (!rows || rows.length === 0) return null;
|
|
3536
|
+
const row = rows[0];
|
|
3537
|
+
if (row.status !== "active") return null;
|
|
3538
|
+
if (row.expires_at && new Date(row.expires_at) < /* @__PURE__ */ new Date()) return null;
|
|
3539
|
+
const plan = row.plan;
|
|
3540
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
3541
|
+
return {
|
|
3542
|
+
valid: true,
|
|
3543
|
+
plan,
|
|
3544
|
+
email: row.email,
|
|
3545
|
+
expiresAt: row.expires_at ? new Date(row.expires_at).toISOString() : null,
|
|
3546
|
+
deviceLimit: row.device_limit ?? limits.devices,
|
|
3547
|
+
employeeLimit: row.employee_limit ?? limits.employees,
|
|
3548
|
+
memoryLimit: row.memory_limit ?? limits.memories
|
|
3549
|
+
};
|
|
3550
|
+
} catch {
|
|
3551
|
+
return null;
|
|
3552
|
+
}
|
|
3553
|
+
}
|
|
3554
|
+
async function validateViaCFWorker(apiKey, deviceId) {
|
|
2664
3555
|
try {
|
|
2665
3556
|
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
2666
3557
|
method: "POST",
|
|
2667
3558
|
headers: { "Content-Type": "application/json" },
|
|
2668
|
-
body: JSON.stringify({ apiKey, deviceId
|
|
3559
|
+
body: JSON.stringify({ apiKey, deviceId }),
|
|
2669
3560
|
signal: AbortSignal.timeout(1e4)
|
|
2670
3561
|
});
|
|
2671
|
-
if (res.ok)
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
3562
|
+
if (!res.ok) return null;
|
|
3563
|
+
const data = await res.json();
|
|
3564
|
+
if (data.error === "device_limit_exceeded") return null;
|
|
3565
|
+
if (!data.valid) return null;
|
|
3566
|
+
if (data.token) {
|
|
3567
|
+
cacheResponse(data.token);
|
|
3568
|
+
const verified = await verifyLicenseJwt(data.token);
|
|
3569
|
+
if (verified) return verified;
|
|
3570
|
+
}
|
|
3571
|
+
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
3572
|
+
return {
|
|
3573
|
+
valid: data.valid,
|
|
3574
|
+
plan: data.plan,
|
|
3575
|
+
email: data.email,
|
|
3576
|
+
expiresAt: data.expiresAt,
|
|
3577
|
+
deviceLimit: limits.devices,
|
|
3578
|
+
employeeLimit: limits.employees,
|
|
3579
|
+
memoryLimit: limits.memories
|
|
3580
|
+
};
|
|
3581
|
+
} catch {
|
|
3582
|
+
return null;
|
|
3583
|
+
}
|
|
3584
|
+
}
|
|
3585
|
+
async function validateLicense(apiKey, deviceId) {
|
|
3586
|
+
const did = deviceId ?? loadDeviceId();
|
|
3587
|
+
const pgResult = await validateViaPostgres(apiKey);
|
|
3588
|
+
if (pgResult) {
|
|
3589
|
+
try {
|
|
3590
|
+
writeFileSync6(CACHE_PATH, JSON.stringify({ pgLicense: pgResult, ts: Date.now() }), "utf8");
|
|
3591
|
+
} catch {
|
|
3592
|
+
}
|
|
3593
|
+
return pgResult;
|
|
3594
|
+
}
|
|
3595
|
+
const cfResult = await validateViaCFWorker(apiKey, did);
|
|
3596
|
+
if (cfResult) return cfResult;
|
|
3597
|
+
const cached = await getCachedLicense();
|
|
3598
|
+
if (cached) return cached;
|
|
3599
|
+
try {
|
|
3600
|
+
if (existsSync10(CACHE_PATH)) {
|
|
3601
|
+
const raw = JSON.parse(readFileSync9(CACHE_PATH, "utf8"));
|
|
3602
|
+
if (raw.pgLicense && raw.ts && Date.now() - raw.ts < 7 * 24 * 60 * 60 * 1e3) {
|
|
3603
|
+
return raw.pgLicense;
|
|
3604
|
+
}
|
|
2695
3605
|
}
|
|
2696
|
-
const cached = await getCachedLicense();
|
|
2697
|
-
if (cached) return cached;
|
|
2698
|
-
const raw = getRawCachedPlan();
|
|
2699
|
-
if (raw) return raw;
|
|
2700
|
-
return { ...FREE_LICENSE, valid: false, plan: "free" };
|
|
2701
3606
|
} catch {
|
|
2702
|
-
const cached = await getCachedLicense();
|
|
2703
|
-
if (cached) return cached;
|
|
2704
|
-
const rawFallback = getRawCachedPlan();
|
|
2705
|
-
if (rawFallback) return rawFallback;
|
|
2706
|
-
return { ...FREE_LICENSE, valid: false, error: "offline" };
|
|
2707
3607
|
}
|
|
3608
|
+
const rawFallback = getRawCachedPlan();
|
|
3609
|
+
if (rawFallback) return rawFallback;
|
|
3610
|
+
return { ...FREE_LICENSE, valid: false };
|
|
2708
3611
|
}
|
|
2709
3612
|
function getCacheAgeMs() {
|
|
2710
3613
|
try {
|
|
@@ -2719,9 +3622,9 @@ async function checkLicense() {
|
|
|
2719
3622
|
let key = loadLicense();
|
|
2720
3623
|
if (!key) {
|
|
2721
3624
|
try {
|
|
2722
|
-
const configPath =
|
|
2723
|
-
if (
|
|
2724
|
-
const raw = JSON.parse(
|
|
3625
|
+
const configPath = path9.join(EXE_AI_DIR, "config.json");
|
|
3626
|
+
if (existsSync10(configPath)) {
|
|
3627
|
+
const raw = JSON.parse(readFileSync9(configPath, "utf8"));
|
|
2725
3628
|
const cloud = raw.cloud;
|
|
2726
3629
|
if (cloud?.apiKey) {
|
|
2727
3630
|
key = cloud.apiKey;
|
|
@@ -2875,14 +3778,14 @@ function stopLicenseRevalidation() {
|
|
|
2875
3778
|
_revalTimer = null;
|
|
2876
3779
|
}
|
|
2877
3780
|
}
|
|
2878
|
-
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;
|
|
3781
|
+
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;
|
|
2879
3782
|
var init_license = __esm({
|
|
2880
3783
|
"src/lib/license.ts"() {
|
|
2881
3784
|
"use strict";
|
|
2882
3785
|
init_config();
|
|
2883
|
-
LICENSE_PATH =
|
|
2884
|
-
CACHE_PATH =
|
|
2885
|
-
DEVICE_ID_PATH =
|
|
3786
|
+
LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
|
|
3787
|
+
CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
|
|
3788
|
+
DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
|
|
2886
3789
|
API_BASE = "https://askexe.com/cloud";
|
|
2887
3790
|
RETRY_DELAY_MS = 500;
|
|
2888
3791
|
LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
@@ -2906,18 +3809,20 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
|
2906
3809
|
employeeLimit: 1,
|
|
2907
3810
|
memoryLimit: 5e3
|
|
2908
3811
|
};
|
|
3812
|
+
_prismaPromise = null;
|
|
3813
|
+
_prismaFailed = false;
|
|
2909
3814
|
CACHE_MAX_AGE_MS = 36e5;
|
|
2910
3815
|
_revalTimer = null;
|
|
2911
3816
|
}
|
|
2912
3817
|
});
|
|
2913
3818
|
|
|
2914
3819
|
// src/lib/plan-limits.ts
|
|
2915
|
-
import { readFileSync as
|
|
2916
|
-
import
|
|
3820
|
+
import { readFileSync as readFileSync10, existsSync as existsSync11 } from "fs";
|
|
3821
|
+
import path10 from "path";
|
|
2917
3822
|
function getLicenseSync() {
|
|
2918
3823
|
try {
|
|
2919
|
-
if (!
|
|
2920
|
-
const raw = JSON.parse(
|
|
3824
|
+
if (!existsSync11(CACHE_PATH2)) return freeLicense();
|
|
3825
|
+
const raw = JSON.parse(readFileSync10(CACHE_PATH2, "utf8"));
|
|
2921
3826
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
2922
3827
|
const parts = raw.token.split(".");
|
|
2923
3828
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -2955,8 +3860,8 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
2955
3860
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
2956
3861
|
let count = 0;
|
|
2957
3862
|
try {
|
|
2958
|
-
if (
|
|
2959
|
-
const raw =
|
|
3863
|
+
if (existsSync11(filePath)) {
|
|
3864
|
+
const raw = readFileSync10(filePath, "utf8");
|
|
2960
3865
|
const employees = JSON.parse(raw);
|
|
2961
3866
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
2962
3867
|
}
|
|
@@ -2985,29 +3890,30 @@ var init_plan_limits = __esm({
|
|
|
2985
3890
|
this.name = "PlanLimitError";
|
|
2986
3891
|
}
|
|
2987
3892
|
};
|
|
2988
|
-
CACHE_PATH2 =
|
|
3893
|
+
CACHE_PATH2 = path10.join(EXE_AI_DIR, "license-cache.json");
|
|
2989
3894
|
}
|
|
2990
3895
|
});
|
|
2991
3896
|
|
|
2992
3897
|
// src/lib/notifications.ts
|
|
2993
|
-
import
|
|
2994
|
-
import
|
|
2995
|
-
import
|
|
3898
|
+
import crypto2 from "crypto";
|
|
3899
|
+
import path11 from "path";
|
|
3900
|
+
import os8 from "os";
|
|
2996
3901
|
import {
|
|
2997
|
-
readFileSync as
|
|
3902
|
+
readFileSync as readFileSync11,
|
|
2998
3903
|
readdirSync,
|
|
2999
3904
|
unlinkSync as unlinkSync3,
|
|
3000
|
-
existsSync as
|
|
3905
|
+
existsSync as existsSync12,
|
|
3001
3906
|
rmdirSync
|
|
3002
3907
|
} from "fs";
|
|
3003
3908
|
async function writeNotification(notification) {
|
|
3004
3909
|
try {
|
|
3005
3910
|
const client = getClient();
|
|
3006
|
-
const id =
|
|
3911
|
+
const id = crypto2.randomUUID();
|
|
3007
3912
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3913
|
+
const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
|
|
3008
3914
|
await client.execute({
|
|
3009
|
-
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
|
|
3010
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
3915
|
+
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
3916
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
3011
3917
|
args: [
|
|
3012
3918
|
id,
|
|
3013
3919
|
notification.agentId,
|
|
@@ -3016,6 +3922,7 @@ async function writeNotification(notification) {
|
|
|
3016
3922
|
notification.project,
|
|
3017
3923
|
notification.summary,
|
|
3018
3924
|
notification.taskFile ?? null,
|
|
3925
|
+
sessionScope,
|
|
3019
3926
|
now
|
|
3020
3927
|
]
|
|
3021
3928
|
});
|
|
@@ -3024,12 +3931,14 @@ async function writeNotification(notification) {
|
|
|
3024
3931
|
`);
|
|
3025
3932
|
}
|
|
3026
3933
|
}
|
|
3027
|
-
async function markAsReadByTaskFile(taskFile) {
|
|
3934
|
+
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
3028
3935
|
try {
|
|
3029
3936
|
const client = getClient();
|
|
3937
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
3030
3938
|
await client.execute({
|
|
3031
|
-
sql:
|
|
3032
|
-
|
|
3939
|
+
sql: `UPDATE notifications SET read = 1
|
|
3940
|
+
WHERE task_file = ? AND read = 0${scope.sql}`,
|
|
3941
|
+
args: [taskFile, ...scope.args]
|
|
3033
3942
|
});
|
|
3034
3943
|
} catch {
|
|
3035
3944
|
}
|
|
@@ -3038,11 +3947,12 @@ var init_notifications = __esm({
|
|
|
3038
3947
|
"src/lib/notifications.ts"() {
|
|
3039
3948
|
"use strict";
|
|
3040
3949
|
init_database();
|
|
3950
|
+
init_task_scope();
|
|
3041
3951
|
}
|
|
3042
3952
|
});
|
|
3043
3953
|
|
|
3044
3954
|
// src/lib/session-kill-telemetry.ts
|
|
3045
|
-
import
|
|
3955
|
+
import crypto3 from "crypto";
|
|
3046
3956
|
async function recordSessionKill(input) {
|
|
3047
3957
|
try {
|
|
3048
3958
|
const client = getClient();
|
|
@@ -3052,7 +3962,7 @@ async function recordSessionKill(input) {
|
|
|
3052
3962
|
ticks_idle, estimated_tokens_saved)
|
|
3053
3963
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
3054
3964
|
args: [
|
|
3055
|
-
|
|
3965
|
+
crypto3.randomUUID(),
|
|
3056
3966
|
input.sessionName,
|
|
3057
3967
|
input.agentId,
|
|
3058
3968
|
(/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -3147,12 +4057,12 @@ __export(tasks_crud_exports, {
|
|
|
3147
4057
|
updateTaskStatus: () => updateTaskStatus,
|
|
3148
4058
|
writeCheckpoint: () => writeCheckpoint
|
|
3149
4059
|
});
|
|
3150
|
-
import
|
|
3151
|
-
import
|
|
3152
|
-
import
|
|
4060
|
+
import crypto4 from "crypto";
|
|
4061
|
+
import path12 from "path";
|
|
4062
|
+
import os9 from "os";
|
|
3153
4063
|
import { execSync as execSync5 } from "child_process";
|
|
3154
4064
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
3155
|
-
import { existsSync as
|
|
4065
|
+
import { existsSync as existsSync13, readFileSync as readFileSync12 } from "fs";
|
|
3156
4066
|
async function writeCheckpoint(input) {
|
|
3157
4067
|
const client = getClient();
|
|
3158
4068
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -3268,7 +4178,7 @@ async function resolveTask(client, identifier, scopeSession) {
|
|
|
3268
4178
|
}
|
|
3269
4179
|
async function createTaskCore(input) {
|
|
3270
4180
|
const client = getClient();
|
|
3271
|
-
const id =
|
|
4181
|
+
const id = crypto4.randomUUID();
|
|
3272
4182
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3273
4183
|
const slug = slugify(input.title);
|
|
3274
4184
|
let earlySessionScope = null;
|
|
@@ -3327,8 +4237,8 @@ ${laneWarning}` : laneWarning;
|
|
|
3327
4237
|
}
|
|
3328
4238
|
if (input.baseDir) {
|
|
3329
4239
|
try {
|
|
3330
|
-
await mkdir3(
|
|
3331
|
-
await mkdir3(
|
|
4240
|
+
await mkdir3(path12.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
4241
|
+
await mkdir3(path12.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
3332
4242
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
3333
4243
|
await ensureGitignoreExe(input.baseDir);
|
|
3334
4244
|
} catch {
|
|
@@ -3364,13 +4274,19 @@ ${laneWarning}` : laneWarning;
|
|
|
3364
4274
|
});
|
|
3365
4275
|
if (input.baseDir) {
|
|
3366
4276
|
try {
|
|
3367
|
-
const EXE_OS_DIR =
|
|
3368
|
-
const mdPath =
|
|
3369
|
-
const mdDir =
|
|
3370
|
-
if (!
|
|
4277
|
+
const EXE_OS_DIR = path12.join(os9.homedir(), ".exe-os");
|
|
4278
|
+
const mdPath = path12.join(EXE_OS_DIR, taskFile);
|
|
4279
|
+
const mdDir = path12.dirname(mdPath);
|
|
4280
|
+
if (!existsSync13(mdDir)) await mkdir3(mdDir, { recursive: true });
|
|
3371
4281
|
const reviewer = input.reviewer ?? input.assignedBy;
|
|
3372
4282
|
const mdContent = `# ${input.title}
|
|
3373
4283
|
|
|
4284
|
+
## MANDATORY: When done
|
|
4285
|
+
|
|
4286
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
4287
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
4288
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
4289
|
+
|
|
3374
4290
|
**ID:** ${id}
|
|
3375
4291
|
**Status:** ${initialStatus}
|
|
3376
4292
|
**Priority:** ${input.priority}
|
|
@@ -3384,12 +4300,6 @@ ${laneWarning}` : laneWarning;
|
|
|
3384
4300
|
## Context
|
|
3385
4301
|
|
|
3386
4302
|
${input.context}
|
|
3387
|
-
|
|
3388
|
-
## MANDATORY: When done
|
|
3389
|
-
|
|
3390
|
-
You MUST call update_task with status "done" and a result summary when finished.
|
|
3391
|
-
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
3392
|
-
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
3393
4303
|
`;
|
|
3394
4304
|
await writeFile3(mdPath, mdContent, "utf-8");
|
|
3395
4305
|
} catch (err) {
|
|
@@ -3638,7 +4548,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
3638
4548
|
await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
|
|
3639
4549
|
} catch {
|
|
3640
4550
|
}
|
|
3641
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
4551
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
3642
4552
|
try {
|
|
3643
4553
|
const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
3644
4554
|
clearQueueForAgent2(String(row.assigned_to));
|
|
@@ -3667,9 +4577,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
3667
4577
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
3668
4578
|
}
|
|
3669
4579
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
3670
|
-
const archPath =
|
|
4580
|
+
const archPath = path12.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
3671
4581
|
try {
|
|
3672
|
-
if (
|
|
4582
|
+
if (existsSync13(archPath)) return;
|
|
3673
4583
|
const template = [
|
|
3674
4584
|
`# ${projectName} \u2014 System Architecture`,
|
|
3675
4585
|
"",
|
|
@@ -3702,10 +4612,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
3702
4612
|
}
|
|
3703
4613
|
}
|
|
3704
4614
|
async function ensureGitignoreExe(baseDir) {
|
|
3705
|
-
const gitignorePath =
|
|
4615
|
+
const gitignorePath = path12.join(baseDir, ".gitignore");
|
|
3706
4616
|
try {
|
|
3707
|
-
if (
|
|
3708
|
-
const content =
|
|
4617
|
+
if (existsSync13(gitignorePath)) {
|
|
4618
|
+
const content = readFileSync12(gitignorePath, "utf-8");
|
|
3709
4619
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
3710
4620
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
3711
4621
|
} else {
|
|
@@ -3736,58 +4646,42 @@ var init_tasks_crud = __esm({
|
|
|
3736
4646
|
});
|
|
3737
4647
|
|
|
3738
4648
|
// src/lib/tasks-review.ts
|
|
3739
|
-
import
|
|
3740
|
-
import { existsSync as
|
|
4649
|
+
import path13 from "path";
|
|
4650
|
+
import { existsSync as existsSync14, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
|
|
3741
4651
|
async function countPendingReviews(sessionScope) {
|
|
3742
4652
|
const client = getClient();
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
args: [sessionScope]
|
|
3747
|
-
});
|
|
3748
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
3749
|
-
}
|
|
4653
|
+
const scope = strictSessionScopeFilter(
|
|
4654
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
4655
|
+
);
|
|
3750
4656
|
const result = await client.execute({
|
|
3751
|
-
sql:
|
|
3752
|
-
|
|
4657
|
+
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
4658
|
+
WHERE status = 'needs_review'${scope.sql}`,
|
|
4659
|
+
args: [...scope.args]
|
|
3753
4660
|
});
|
|
3754
4661
|
return Number(result.rows[0]?.cnt) || 0;
|
|
3755
4662
|
}
|
|
3756
4663
|
async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
3757
4664
|
const client = getClient();
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
WHERE status = 'needs_review' AND updated_at > ?
|
|
3762
|
-
AND session_scope = ?`,
|
|
3763
|
-
args: [sinceIso, sessionScope]
|
|
3764
|
-
});
|
|
3765
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
3766
|
-
}
|
|
4665
|
+
const scope = strictSessionScopeFilter(
|
|
4666
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
4667
|
+
);
|
|
3767
4668
|
const result = await client.execute({
|
|
3768
4669
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3769
|
-
WHERE status = 'needs_review' AND updated_at >
|
|
3770
|
-
args: [sinceIso]
|
|
4670
|
+
WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
|
|
4671
|
+
args: [sinceIso, ...scope.args]
|
|
3771
4672
|
});
|
|
3772
4673
|
return Number(result.rows[0]?.cnt) || 0;
|
|
3773
4674
|
}
|
|
3774
4675
|
async function listPendingReviews(limit, sessionScope) {
|
|
3775
4676
|
const client = getClient();
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
WHERE status = 'needs_review'
|
|
3780
|
-
AND session_scope = ?
|
|
3781
|
-
ORDER BY updated_at ASC LIMIT ?`,
|
|
3782
|
-
args: [sessionScope, limit]
|
|
3783
|
-
});
|
|
3784
|
-
return result2.rows;
|
|
3785
|
-
}
|
|
4677
|
+
const scope = strictSessionScopeFilter(
|
|
4678
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
4679
|
+
);
|
|
3786
4680
|
const result = await client.execute({
|
|
3787
4681
|
sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
|
|
3788
|
-
WHERE status = 'needs_review'
|
|
4682
|
+
WHERE status = 'needs_review'${scope.sql}
|
|
3789
4683
|
ORDER BY updated_at ASC LIMIT ?`,
|
|
3790
|
-
args: [limit]
|
|
4684
|
+
args: [...scope.args, limit]
|
|
3791
4685
|
});
|
|
3792
4686
|
return result.rows;
|
|
3793
4687
|
}
|
|
@@ -3799,7 +4693,7 @@ async function cleanupOrphanedReviews() {
|
|
|
3799
4693
|
WHERE status IN ('open', 'needs_review', 'in_progress')
|
|
3800
4694
|
AND assigned_by = 'system'
|
|
3801
4695
|
AND title LIKE 'Review:%'
|
|
3802
|
-
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
|
|
4696
|
+
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
|
|
3803
4697
|
args: [now]
|
|
3804
4698
|
});
|
|
3805
4699
|
const r1b = await client.execute({
|
|
@@ -3918,11 +4812,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
3918
4812
|
);
|
|
3919
4813
|
}
|
|
3920
4814
|
try {
|
|
3921
|
-
const cacheDir =
|
|
3922
|
-
if (
|
|
4815
|
+
const cacheDir = path13.join(EXE_AI_DIR, "session-cache");
|
|
4816
|
+
if (existsSync14(cacheDir)) {
|
|
3923
4817
|
for (const f of readdirSync2(cacheDir)) {
|
|
3924
4818
|
if (f.startsWith("review-notified-")) {
|
|
3925
|
-
unlinkSync4(
|
|
4819
|
+
unlinkSync4(path13.join(cacheDir, f));
|
|
3926
4820
|
}
|
|
3927
4821
|
}
|
|
3928
4822
|
}
|
|
@@ -3939,11 +4833,12 @@ var init_tasks_review = __esm({
|
|
|
3939
4833
|
init_tmux_routing();
|
|
3940
4834
|
init_session_key();
|
|
3941
4835
|
init_state_bus();
|
|
4836
|
+
init_task_scope();
|
|
3942
4837
|
}
|
|
3943
4838
|
});
|
|
3944
4839
|
|
|
3945
4840
|
// src/lib/tasks-chain.ts
|
|
3946
|
-
import
|
|
4841
|
+
import path14 from "path";
|
|
3947
4842
|
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
3948
4843
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
3949
4844
|
const client = getClient();
|
|
@@ -3960,7 +4855,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
3960
4855
|
});
|
|
3961
4856
|
for (const ur of unblockedRows.rows) {
|
|
3962
4857
|
try {
|
|
3963
|
-
const ubFile =
|
|
4858
|
+
const ubFile = path14.join(baseDir, String(ur.task_file));
|
|
3964
4859
|
let ubContent = await readFile3(ubFile, "utf-8");
|
|
3965
4860
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
3966
4861
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -3995,7 +4890,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
|
|
|
3995
4890
|
const scScope = sessionScopeFilter();
|
|
3996
4891
|
const remaining = await client.execute({
|
|
3997
4892
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3998
|
-
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
|
|
4893
|
+
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
|
|
3999
4894
|
args: [parentTaskId, ...scScope.args]
|
|
4000
4895
|
});
|
|
4001
4896
|
const cnt = Number(remaining.rows[0]?.cnt ?? 1);
|
|
@@ -4029,7 +4924,7 @@ var init_tasks_chain = __esm({
|
|
|
4029
4924
|
|
|
4030
4925
|
// src/lib/project-name.ts
|
|
4031
4926
|
import { execSync as execSync6 } from "child_process";
|
|
4032
|
-
import
|
|
4927
|
+
import path15 from "path";
|
|
4033
4928
|
function getProjectName(cwd2) {
|
|
4034
4929
|
const dir = cwd2 ?? process.cwd();
|
|
4035
4930
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
@@ -4042,7 +4937,7 @@ function getProjectName(cwd2) {
|
|
|
4042
4937
|
timeout: 2e3,
|
|
4043
4938
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4044
4939
|
}).trim();
|
|
4045
|
-
repoRoot =
|
|
4940
|
+
repoRoot = path15.dirname(gitCommonDir);
|
|
4046
4941
|
} catch {
|
|
4047
4942
|
repoRoot = execSync6("git rev-parse --show-toplevel", {
|
|
4048
4943
|
cwd: dir,
|
|
@@ -4051,11 +4946,11 @@ function getProjectName(cwd2) {
|
|
|
4051
4946
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4052
4947
|
}).trim();
|
|
4053
4948
|
}
|
|
4054
|
-
_cached2 =
|
|
4949
|
+
_cached2 = path15.basename(repoRoot);
|
|
4055
4950
|
_cachedCwd = dir;
|
|
4056
4951
|
return _cached2;
|
|
4057
4952
|
} catch {
|
|
4058
|
-
_cached2 =
|
|
4953
|
+
_cached2 = path15.basename(dir);
|
|
4059
4954
|
_cachedCwd = dir;
|
|
4060
4955
|
return _cached2;
|
|
4061
4956
|
}
|
|
@@ -4198,10 +5093,10 @@ var init_tasks_notify = __esm({
|
|
|
4198
5093
|
});
|
|
4199
5094
|
|
|
4200
5095
|
// src/lib/behaviors.ts
|
|
4201
|
-
import
|
|
5096
|
+
import crypto5 from "crypto";
|
|
4202
5097
|
async function storeBehavior(opts) {
|
|
4203
5098
|
const client = getClient();
|
|
4204
|
-
const id =
|
|
5099
|
+
const id = crypto5.randomUUID();
|
|
4205
5100
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4206
5101
|
await client.execute({
|
|
4207
5102
|
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
|
|
@@ -4230,7 +5125,7 @@ __export(skill_learning_exports, {
|
|
|
4230
5125
|
storeTrajectory: () => storeTrajectory,
|
|
4231
5126
|
sweepTrajectories: () => sweepTrajectories
|
|
4232
5127
|
});
|
|
4233
|
-
import
|
|
5128
|
+
import crypto6 from "crypto";
|
|
4234
5129
|
async function extractTrajectory(taskId, agentId) {
|
|
4235
5130
|
const client = getClient();
|
|
4236
5131
|
const result = await client.execute({
|
|
@@ -4259,11 +5154,11 @@ async function extractTrajectory(taskId, agentId) {
|
|
|
4259
5154
|
return signature;
|
|
4260
5155
|
}
|
|
4261
5156
|
function hashSignature(signature) {
|
|
4262
|
-
return
|
|
5157
|
+
return crypto6.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
|
|
4263
5158
|
}
|
|
4264
5159
|
async function storeTrajectory(opts) {
|
|
4265
5160
|
const client = getClient();
|
|
4266
|
-
const id =
|
|
5161
|
+
const id = crypto6.randomUUID();
|
|
4267
5162
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4268
5163
|
const signatureHash = hashSignature(opts.signature);
|
|
4269
5164
|
await client.execute({
|
|
@@ -4528,8 +5423,8 @@ __export(tasks_exports, {
|
|
|
4528
5423
|
updateTaskStatus: () => updateTaskStatus,
|
|
4529
5424
|
writeCheckpoint: () => writeCheckpoint
|
|
4530
5425
|
});
|
|
4531
|
-
import
|
|
4532
|
-
import { writeFileSync as
|
|
5426
|
+
import path16 from "path";
|
|
5427
|
+
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
|
|
4533
5428
|
async function createTask(input) {
|
|
4534
5429
|
const result = await createTaskCore(input);
|
|
4535
5430
|
if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -4548,12 +5443,12 @@ async function updateTask(input) {
|
|
|
4548
5443
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
4549
5444
|
try {
|
|
4550
5445
|
const agent = String(row.assigned_to);
|
|
4551
|
-
const cacheDir =
|
|
4552
|
-
const cachePath =
|
|
5446
|
+
const cacheDir = path16.join(EXE_AI_DIR, "session-cache");
|
|
5447
|
+
const cachePath = path16.join(cacheDir, `current-task-${agent}.json`);
|
|
4553
5448
|
if (input.status === "in_progress") {
|
|
4554
5449
|
mkdirSync5(cacheDir, { recursive: true });
|
|
4555
|
-
|
|
4556
|
-
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
5450
|
+
writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
5451
|
+
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
|
|
4557
5452
|
try {
|
|
4558
5453
|
unlinkSync5(cachePath);
|
|
4559
5454
|
} catch {
|
|
@@ -4561,10 +5456,10 @@ async function updateTask(input) {
|
|
|
4561
5456
|
}
|
|
4562
5457
|
} catch {
|
|
4563
5458
|
}
|
|
4564
|
-
if (input.status === "done") {
|
|
5459
|
+
if (input.status === "done" || input.status === "closed") {
|
|
4565
5460
|
await cleanupReviewFile(row, taskFile, input.baseDir);
|
|
4566
5461
|
}
|
|
4567
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
5462
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
4568
5463
|
try {
|
|
4569
5464
|
const client = getClient();
|
|
4570
5465
|
const taskTitle = String(row.title);
|
|
@@ -4580,7 +5475,7 @@ async function updateTask(input) {
|
|
|
4580
5475
|
if (!isCoordinatorName(assignedAgent)) {
|
|
4581
5476
|
try {
|
|
4582
5477
|
const draftClient = getClient();
|
|
4583
|
-
if (input.status === "done") {
|
|
5478
|
+
if (input.status === "done" || input.status === "closed") {
|
|
4584
5479
|
await draftClient.execute({
|
|
4585
5480
|
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
4586
5481
|
args: [assignedAgent]
|
|
@@ -4597,7 +5492,7 @@ async function updateTask(input) {
|
|
|
4597
5492
|
try {
|
|
4598
5493
|
const client = getClient();
|
|
4599
5494
|
const cascaded = await client.execute({
|
|
4600
|
-
sql: `UPDATE tasks SET status = '
|
|
5495
|
+
sql: `UPDATE tasks SET status = 'closed', updated_at = ?
|
|
4601
5496
|
WHERE parent_task_id = ? AND status = 'needs_review'`,
|
|
4602
5497
|
args: [now, taskId]
|
|
4603
5498
|
});
|
|
@@ -4610,14 +5505,14 @@ async function updateTask(input) {
|
|
|
4610
5505
|
} catch {
|
|
4611
5506
|
}
|
|
4612
5507
|
}
|
|
4613
|
-
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
5508
|
+
const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
|
|
4614
5509
|
if (isTerminal) {
|
|
4615
5510
|
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
4616
5511
|
if (!isCoordinator) {
|
|
4617
5512
|
notifyTaskDone();
|
|
4618
5513
|
}
|
|
4619
5514
|
await markTaskNotificationsRead(taskFile);
|
|
4620
|
-
if (input.status === "done") {
|
|
5515
|
+
if (input.status === "done" || input.status === "closed") {
|
|
4621
5516
|
try {
|
|
4622
5517
|
await cascadeUnblock(taskId, input.baseDir, now);
|
|
4623
5518
|
} catch {
|
|
@@ -4637,7 +5532,7 @@ async function updateTask(input) {
|
|
|
4637
5532
|
}
|
|
4638
5533
|
}
|
|
4639
5534
|
}
|
|
4640
|
-
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
5535
|
+
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
4641
5536
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
4642
5537
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
4643
5538
|
taskId,
|
|
@@ -5009,6 +5904,7 @@ __export(tmux_routing_exports, {
|
|
|
5009
5904
|
isEmployeeAlive: () => isEmployeeAlive,
|
|
5010
5905
|
isExeSession: () => isExeSession,
|
|
5011
5906
|
isSessionBusy: () => isSessionBusy,
|
|
5907
|
+
notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
|
|
5012
5908
|
notifyParentExe: () => notifyParentExe,
|
|
5013
5909
|
parseParentExe: () => parseParentExe,
|
|
5014
5910
|
registerParentExe: () => registerParentExe,
|
|
@@ -5019,13 +5915,13 @@ __export(tmux_routing_exports, {
|
|
|
5019
5915
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
5020
5916
|
});
|
|
5021
5917
|
import { execFileSync as execFileSync3, execSync as execSync7 } from "child_process";
|
|
5022
|
-
import { readFileSync as
|
|
5023
|
-
import
|
|
5024
|
-
import
|
|
5918
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, mkdirSync as mkdirSync6, existsSync as existsSync15, appendFileSync, readdirSync as readdirSync3 } from "fs";
|
|
5919
|
+
import path17 from "path";
|
|
5920
|
+
import os10 from "os";
|
|
5025
5921
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5026
5922
|
import { unlinkSync as unlinkSync6 } from "fs";
|
|
5027
5923
|
function spawnLockPath(sessionName) {
|
|
5028
|
-
return
|
|
5924
|
+
return path17.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
5029
5925
|
}
|
|
5030
5926
|
function isProcessAlive(pid) {
|
|
5031
5927
|
try {
|
|
@@ -5036,13 +5932,13 @@ function isProcessAlive(pid) {
|
|
|
5036
5932
|
}
|
|
5037
5933
|
}
|
|
5038
5934
|
function acquireSpawnLock2(sessionName) {
|
|
5039
|
-
if (!
|
|
5935
|
+
if (!existsSync15(SPAWN_LOCK_DIR)) {
|
|
5040
5936
|
mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
|
|
5041
5937
|
}
|
|
5042
5938
|
const lockFile = spawnLockPath(sessionName);
|
|
5043
|
-
if (
|
|
5939
|
+
if (existsSync15(lockFile)) {
|
|
5044
5940
|
try {
|
|
5045
|
-
const lock = JSON.parse(
|
|
5941
|
+
const lock = JSON.parse(readFileSync13(lockFile, "utf8"));
|
|
5046
5942
|
const age = Date.now() - lock.timestamp;
|
|
5047
5943
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
5048
5944
|
return false;
|
|
@@ -5050,7 +5946,7 @@ function acquireSpawnLock2(sessionName) {
|
|
|
5050
5946
|
} catch {
|
|
5051
5947
|
}
|
|
5052
5948
|
}
|
|
5053
|
-
|
|
5949
|
+
writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
5054
5950
|
return true;
|
|
5055
5951
|
}
|
|
5056
5952
|
function releaseSpawnLock2(sessionName) {
|
|
@@ -5062,13 +5958,13 @@ function releaseSpawnLock2(sessionName) {
|
|
|
5062
5958
|
function resolveBehaviorsExporterScript() {
|
|
5063
5959
|
try {
|
|
5064
5960
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
5065
|
-
const scriptPath =
|
|
5066
|
-
|
|
5961
|
+
const scriptPath = path17.join(
|
|
5962
|
+
path17.dirname(thisFile),
|
|
5067
5963
|
"..",
|
|
5068
5964
|
"bin",
|
|
5069
5965
|
"exe-export-behaviors.js"
|
|
5070
5966
|
);
|
|
5071
|
-
return
|
|
5967
|
+
return existsSync15(scriptPath) ? scriptPath : null;
|
|
5072
5968
|
} catch {
|
|
5073
5969
|
return null;
|
|
5074
5970
|
}
|
|
@@ -5134,12 +6030,12 @@ function extractRootExe(name) {
|
|
|
5134
6030
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
5135
6031
|
}
|
|
5136
6032
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
5137
|
-
if (!
|
|
6033
|
+
if (!existsSync15(SESSION_CACHE)) {
|
|
5138
6034
|
mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
5139
6035
|
}
|
|
5140
6036
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
5141
|
-
const filePath =
|
|
5142
|
-
|
|
6037
|
+
const filePath = path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
6038
|
+
writeFileSync8(filePath, JSON.stringify({
|
|
5143
6039
|
parentExe: rootExe,
|
|
5144
6040
|
dispatchedBy: dispatchedBy || rootExe,
|
|
5145
6041
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -5147,7 +6043,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
5147
6043
|
}
|
|
5148
6044
|
function getParentExe(sessionKey) {
|
|
5149
6045
|
try {
|
|
5150
|
-
const data = JSON.parse(
|
|
6046
|
+
const data = JSON.parse(readFileSync13(path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
5151
6047
|
return data.parentExe || null;
|
|
5152
6048
|
} catch {
|
|
5153
6049
|
return null;
|
|
@@ -5155,8 +6051,8 @@ function getParentExe(sessionKey) {
|
|
|
5155
6051
|
}
|
|
5156
6052
|
function getDispatchedBy(sessionKey) {
|
|
5157
6053
|
try {
|
|
5158
|
-
const data = JSON.parse(
|
|
5159
|
-
|
|
6054
|
+
const data = JSON.parse(readFileSync13(
|
|
6055
|
+
path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
5160
6056
|
"utf8"
|
|
5161
6057
|
));
|
|
5162
6058
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -5226,8 +6122,8 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
5226
6122
|
}
|
|
5227
6123
|
function readDebounceState() {
|
|
5228
6124
|
try {
|
|
5229
|
-
if (!
|
|
5230
|
-
const raw = JSON.parse(
|
|
6125
|
+
if (!existsSync15(DEBOUNCE_FILE)) return {};
|
|
6126
|
+
const raw = JSON.parse(readFileSync13(DEBOUNCE_FILE, "utf8"));
|
|
5231
6127
|
const state = {};
|
|
5232
6128
|
for (const [key, val] of Object.entries(raw)) {
|
|
5233
6129
|
if (typeof val === "number") {
|
|
@@ -5243,8 +6139,8 @@ function readDebounceState() {
|
|
|
5243
6139
|
}
|
|
5244
6140
|
function writeDebounceState(state) {
|
|
5245
6141
|
try {
|
|
5246
|
-
if (!
|
|
5247
|
-
|
|
6142
|
+
if (!existsSync15(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
6143
|
+
writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
|
|
5248
6144
|
} catch {
|
|
5249
6145
|
}
|
|
5250
6146
|
}
|
|
@@ -5342,8 +6238,8 @@ function sendIntercom(targetSession) {
|
|
|
5342
6238
|
try {
|
|
5343
6239
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
5344
6240
|
const agent = baseAgentName(rawAgent);
|
|
5345
|
-
const markerPath =
|
|
5346
|
-
if (
|
|
6241
|
+
const markerPath = path17.join(SESSION_CACHE, `current-task-${agent}.json`);
|
|
6242
|
+
if (existsSync15(markerPath)) {
|
|
5347
6243
|
logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
|
|
5348
6244
|
return "debounced";
|
|
5349
6245
|
}
|
|
@@ -5352,8 +6248,8 @@ function sendIntercom(targetSession) {
|
|
|
5352
6248
|
try {
|
|
5353
6249
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
5354
6250
|
const agent = baseAgentName(rawAgent);
|
|
5355
|
-
const taskDir =
|
|
5356
|
-
if (
|
|
6251
|
+
const taskDir = path17.join(process.cwd(), "exe", agent);
|
|
6252
|
+
if (existsSync15(taskDir)) {
|
|
5357
6253
|
const files = readdirSync3(taskDir).filter(
|
|
5358
6254
|
(f) => f.endsWith(".md") && f !== "DONE.txt"
|
|
5359
6255
|
);
|
|
@@ -5413,6 +6309,21 @@ function notifyParentExe(sessionKey) {
|
|
|
5413
6309
|
}
|
|
5414
6310
|
return true;
|
|
5415
6311
|
}
|
|
6312
|
+
function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
|
|
6313
|
+
const transport = getTransport();
|
|
6314
|
+
try {
|
|
6315
|
+
const sessions = transport.listSessions();
|
|
6316
|
+
if (!sessions.includes(coordinatorSession)) return false;
|
|
6317
|
+
execSync7(
|
|
6318
|
+
`tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
|
|
6319
|
+
{ timeout: 3e3 }
|
|
6320
|
+
);
|
|
6321
|
+
logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
|
|
6322
|
+
return true;
|
|
6323
|
+
} catch {
|
|
6324
|
+
return false;
|
|
6325
|
+
}
|
|
6326
|
+
}
|
|
5416
6327
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
5417
6328
|
if (isCoordinatorName(employeeName)) {
|
|
5418
6329
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
@@ -5486,26 +6397,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5486
6397
|
const transport = getTransport();
|
|
5487
6398
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
5488
6399
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
5489
|
-
const logDir =
|
|
5490
|
-
const logFile =
|
|
5491
|
-
if (!
|
|
6400
|
+
const logDir = path17.join(os10.homedir(), ".exe-os", "session-logs");
|
|
6401
|
+
const logFile = path17.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
6402
|
+
if (!existsSync15(logDir)) {
|
|
5492
6403
|
mkdirSync6(logDir, { recursive: true });
|
|
5493
6404
|
}
|
|
5494
6405
|
transport.kill(sessionName);
|
|
5495
6406
|
let cleanupSuffix = "";
|
|
5496
6407
|
try {
|
|
5497
6408
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
5498
|
-
const cleanupScript =
|
|
5499
|
-
if (
|
|
6409
|
+
const cleanupScript = path17.join(path17.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
6410
|
+
if (existsSync15(cleanupScript)) {
|
|
5500
6411
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
5501
6412
|
}
|
|
5502
6413
|
} catch {
|
|
5503
6414
|
}
|
|
5504
6415
|
try {
|
|
5505
|
-
const claudeJsonPath =
|
|
6416
|
+
const claudeJsonPath = path17.join(os10.homedir(), ".claude.json");
|
|
5506
6417
|
let claudeJson = {};
|
|
5507
6418
|
try {
|
|
5508
|
-
claudeJson = JSON.parse(
|
|
6419
|
+
claudeJson = JSON.parse(readFileSync13(claudeJsonPath, "utf8"));
|
|
5509
6420
|
} catch {
|
|
5510
6421
|
}
|
|
5511
6422
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -5513,17 +6424,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5513
6424
|
const trustDir = opts?.cwd ?? projectDir;
|
|
5514
6425
|
if (!projects[trustDir]) projects[trustDir] = {};
|
|
5515
6426
|
projects[trustDir].hasTrustDialogAccepted = true;
|
|
5516
|
-
|
|
6427
|
+
writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
5517
6428
|
} catch {
|
|
5518
6429
|
}
|
|
5519
6430
|
try {
|
|
5520
|
-
const settingsDir =
|
|
6431
|
+
const settingsDir = path17.join(os10.homedir(), ".claude", "projects");
|
|
5521
6432
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
5522
|
-
const projSettingsDir =
|
|
5523
|
-
const settingsPath =
|
|
6433
|
+
const projSettingsDir = path17.join(settingsDir, normalizedKey);
|
|
6434
|
+
const settingsPath = path17.join(projSettingsDir, "settings.json");
|
|
5524
6435
|
let settings = {};
|
|
5525
6436
|
try {
|
|
5526
|
-
settings = JSON.parse(
|
|
6437
|
+
settings = JSON.parse(readFileSync13(settingsPath, "utf8"));
|
|
5527
6438
|
} catch {
|
|
5528
6439
|
}
|
|
5529
6440
|
const perms = settings.permissions ?? {};
|
|
@@ -5552,7 +6463,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5552
6463
|
perms.allow = allow;
|
|
5553
6464
|
settings.permissions = perms;
|
|
5554
6465
|
mkdirSync6(projSettingsDir, { recursive: true });
|
|
5555
|
-
|
|
6466
|
+
writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
5556
6467
|
}
|
|
5557
6468
|
} catch {
|
|
5558
6469
|
}
|
|
@@ -5567,8 +6478,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5567
6478
|
let behaviorsFlag = "";
|
|
5568
6479
|
let legacyFallbackWarned = false;
|
|
5569
6480
|
if (!useExeAgent && !useBinSymlink) {
|
|
5570
|
-
const identityPath =
|
|
5571
|
-
|
|
6481
|
+
const identityPath = path17.join(
|
|
6482
|
+
os10.homedir(),
|
|
5572
6483
|
".exe-os",
|
|
5573
6484
|
"identity",
|
|
5574
6485
|
`${employeeName}.md`
|
|
@@ -5577,13 +6488,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5577
6488
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
5578
6489
|
if (hasAgentFlag) {
|
|
5579
6490
|
identityFlag = ` --agent ${employeeName}`;
|
|
5580
|
-
} else if (
|
|
6491
|
+
} else if (existsSync15(identityPath)) {
|
|
5581
6492
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
5582
6493
|
legacyFallbackWarned = true;
|
|
5583
6494
|
}
|
|
5584
6495
|
const behaviorsFile = exportBehaviorsSync(
|
|
5585
6496
|
employeeName,
|
|
5586
|
-
|
|
6497
|
+
path17.basename(spawnCwd),
|
|
5587
6498
|
sessionName
|
|
5588
6499
|
);
|
|
5589
6500
|
if (behaviorsFile) {
|
|
@@ -5598,16 +6509,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5598
6509
|
}
|
|
5599
6510
|
let sessionContextFlag = "";
|
|
5600
6511
|
try {
|
|
5601
|
-
const ctxDir =
|
|
6512
|
+
const ctxDir = path17.join(os10.homedir(), ".exe-os", "session-cache");
|
|
5602
6513
|
mkdirSync6(ctxDir, { recursive: true });
|
|
5603
|
-
const ctxFile =
|
|
6514
|
+
const ctxFile = path17.join(ctxDir, `session-context-${sessionName}.md`);
|
|
5604
6515
|
const ctxContent = [
|
|
5605
6516
|
`## Session Context`,
|
|
5606
6517
|
`You are running in tmux session: ${sessionName}.`,
|
|
5607
6518
|
`Your parent coordinator session is ${exeSession}.`,
|
|
5608
6519
|
`Your employees (if any) use the -${exeSession} suffix.`
|
|
5609
6520
|
].join("\n");
|
|
5610
|
-
|
|
6521
|
+
writeFileSync8(ctxFile, ctxContent);
|
|
5611
6522
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
5612
6523
|
} catch {
|
|
5613
6524
|
}
|
|
@@ -5684,8 +6595,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5684
6595
|
transport.pipeLog(sessionName, logFile);
|
|
5685
6596
|
try {
|
|
5686
6597
|
const mySession = getMySession();
|
|
5687
|
-
const dispatchInfo =
|
|
5688
|
-
|
|
6598
|
+
const dispatchInfo = path17.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
6599
|
+
writeFileSync8(dispatchInfo, JSON.stringify({
|
|
5689
6600
|
dispatchedBy: mySession,
|
|
5690
6601
|
rootExe: exeSession,
|
|
5691
6602
|
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
|
|
@@ -5759,15 +6670,15 @@ var init_tmux_routing = __esm({
|
|
|
5759
6670
|
init_intercom_queue();
|
|
5760
6671
|
init_plan_limits();
|
|
5761
6672
|
init_employees();
|
|
5762
|
-
SPAWN_LOCK_DIR =
|
|
5763
|
-
SESSION_CACHE =
|
|
6673
|
+
SPAWN_LOCK_DIR = path17.join(os10.homedir(), ".exe-os", "spawn-locks");
|
|
6674
|
+
SESSION_CACHE = path17.join(os10.homedir(), ".exe-os", "session-cache");
|
|
5764
6675
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
5765
6676
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
5766
6677
|
VERIFY_PANE_LINES = 200;
|
|
5767
6678
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
5768
6679
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
5769
|
-
INTERCOM_LOG2 =
|
|
5770
|
-
DEBOUNCE_FILE =
|
|
6680
|
+
INTERCOM_LOG2 = path17.join(os10.homedir(), ".exe-os", "intercom.log");
|
|
6681
|
+
DEBOUNCE_FILE = path17.join(SESSION_CACHE, "intercom-debounce.json");
|
|
5771
6682
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
5772
6683
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
|
|
5773
6684
|
}
|
|
@@ -5777,7 +6688,8 @@ var init_tmux_routing = __esm({
|
|
|
5777
6688
|
var task_scope_exports = {};
|
|
5778
6689
|
__export(task_scope_exports, {
|
|
5779
6690
|
getCurrentSessionScope: () => getCurrentSessionScope,
|
|
5780
|
-
sessionScopeFilter: () => sessionScopeFilter
|
|
6691
|
+
sessionScopeFilter: () => sessionScopeFilter,
|
|
6692
|
+
strictSessionScopeFilter: () => strictSessionScopeFilter
|
|
5781
6693
|
});
|
|
5782
6694
|
function getCurrentSessionScope() {
|
|
5783
6695
|
try {
|
|
@@ -5795,6 +6707,15 @@ function sessionScopeFilter(sessionScope, tableAlias) {
|
|
|
5795
6707
|
args: [scope]
|
|
5796
6708
|
};
|
|
5797
6709
|
}
|
|
6710
|
+
function strictSessionScopeFilter(sessionScope, tableAlias) {
|
|
6711
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
6712
|
+
if (!scope) return { sql: "", args: [] };
|
|
6713
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
6714
|
+
return {
|
|
6715
|
+
sql: ` AND ${col} = ?`,
|
|
6716
|
+
args: [scope]
|
|
6717
|
+
};
|
|
6718
|
+
}
|
|
5798
6719
|
var init_task_scope = __esm({
|
|
5799
6720
|
"src/lib/task-scope.ts"() {
|
|
5800
6721
|
"use strict";
|
|
@@ -7653,10 +8574,10 @@ var init_hooks = __esm({
|
|
|
7653
8574
|
});
|
|
7654
8575
|
|
|
7655
8576
|
// src/runtime/safety-checks.ts
|
|
7656
|
-
import
|
|
7657
|
-
import
|
|
8577
|
+
import path18 from "path";
|
|
8578
|
+
import os11 from "os";
|
|
7658
8579
|
function checkPathSafety(filePath) {
|
|
7659
|
-
const resolved =
|
|
8580
|
+
const resolved = path18.resolve(filePath);
|
|
7660
8581
|
for (const { pattern, reason } of BYPASS_IMMUNE_PATTERNS) {
|
|
7661
8582
|
const matches = typeof pattern === "function" ? pattern(resolved) : pattern.test(resolved);
|
|
7662
8583
|
if (matches) {
|
|
@@ -7666,7 +8587,7 @@ function checkPathSafety(filePath) {
|
|
|
7666
8587
|
return { safe: true, bypassImmune: true };
|
|
7667
8588
|
}
|
|
7668
8589
|
function checkReadPathSafety(filePath) {
|
|
7669
|
-
const resolved =
|
|
8590
|
+
const resolved = path18.resolve(filePath);
|
|
7670
8591
|
const credPatterns = BYPASS_IMMUNE_PATTERNS.filter(
|
|
7671
8592
|
(p) => typeof p.pattern !== "function" && (p.reason.includes("secrets") || p.reason.includes("Private key") || p.reason.includes("Credential"))
|
|
7672
8593
|
);
|
|
@@ -7681,7 +8602,7 @@ var HOME, BYPASS_IMMUNE_PATTERNS;
|
|
|
7681
8602
|
var init_safety_checks = __esm({
|
|
7682
8603
|
"src/runtime/safety-checks.ts"() {
|
|
7683
8604
|
"use strict";
|
|
7684
|
-
HOME =
|
|
8605
|
+
HOME = os11.homedir();
|
|
7685
8606
|
BYPASS_IMMUNE_PATTERNS = [
|
|
7686
8607
|
{
|
|
7687
8608
|
pattern: /\/\.git\/hooks\//,
|
|
@@ -7692,11 +8613,11 @@ var init_safety_checks = __esm({
|
|
|
7692
8613
|
reason: "Git config can set hooks and command execution"
|
|
7693
8614
|
},
|
|
7694
8615
|
{
|
|
7695
|
-
pattern: (p) => p.startsWith(
|
|
8616
|
+
pattern: (p) => p.startsWith(path18.join(HOME, ".claude")),
|
|
7696
8617
|
reason: "Claude configuration files are protected"
|
|
7697
8618
|
},
|
|
7698
8619
|
{
|
|
7699
|
-
pattern: (p) => p.startsWith(
|
|
8620
|
+
pattern: (p) => p.startsWith(path18.join(HOME, ".exe-os")),
|
|
7700
8621
|
reason: "exe-os configuration files are protected"
|
|
7701
8622
|
},
|
|
7702
8623
|
{
|
|
@@ -7713,7 +8634,7 @@ var init_safety_checks = __esm({
|
|
|
7713
8634
|
},
|
|
7714
8635
|
{
|
|
7715
8636
|
pattern: (p) => {
|
|
7716
|
-
const name =
|
|
8637
|
+
const name = path18.basename(p);
|
|
7717
8638
|
return [".bashrc", ".zshrc", ".profile", ".bash_profile", ".zprofile", ".zshenv"].includes(name);
|
|
7718
8639
|
},
|
|
7719
8640
|
reason: "Shell configuration files can execute arbitrary code on login"
|
|
@@ -7740,7 +8661,7 @@ __export(file_read_exports, {
|
|
|
7740
8661
|
FileReadTool: () => FileReadTool
|
|
7741
8662
|
});
|
|
7742
8663
|
import fs3 from "fs/promises";
|
|
7743
|
-
import
|
|
8664
|
+
import path19 from "path";
|
|
7744
8665
|
import { z } from "zod";
|
|
7745
8666
|
function isBinary(buf) {
|
|
7746
8667
|
for (let i = 0; i < buf.length; i++) {
|
|
@@ -7776,7 +8697,7 @@ var init_file_read = __esm({
|
|
|
7776
8697
|
return { behavior: "allow" };
|
|
7777
8698
|
},
|
|
7778
8699
|
async call(input, context) {
|
|
7779
|
-
const filePath =
|
|
8700
|
+
const filePath = path19.isAbsolute(input.file_path) ? input.file_path : path19.resolve(context.cwd, input.file_path);
|
|
7780
8701
|
let stat;
|
|
7781
8702
|
try {
|
|
7782
8703
|
stat = await fs3.stat(filePath);
|
|
@@ -7816,7 +8737,7 @@ __export(glob_exports, {
|
|
|
7816
8737
|
GlobTool: () => GlobTool
|
|
7817
8738
|
});
|
|
7818
8739
|
import fs4 from "fs/promises";
|
|
7819
|
-
import
|
|
8740
|
+
import path20 from "path";
|
|
7820
8741
|
import { z as z2 } from "zod";
|
|
7821
8742
|
async function walkDir(dir, maxDepth = 10) {
|
|
7822
8743
|
const results = [];
|
|
@@ -7832,7 +8753,7 @@ async function walkDir(dir, maxDepth = 10) {
|
|
|
7832
8753
|
if (entry.isDirectory() && (entry.name === "node_modules" || entry.name === ".git")) {
|
|
7833
8754
|
continue;
|
|
7834
8755
|
}
|
|
7835
|
-
const fullPath =
|
|
8756
|
+
const fullPath = path20.join(current, entry.name);
|
|
7836
8757
|
if (entry.isDirectory()) {
|
|
7837
8758
|
await walk(fullPath, depth + 1);
|
|
7838
8759
|
} else {
|
|
@@ -7866,11 +8787,11 @@ var init_glob = __esm({
|
|
|
7866
8787
|
inputSchema: inputSchema2,
|
|
7867
8788
|
isReadOnly: true,
|
|
7868
8789
|
async call(input, context) {
|
|
7869
|
-
const baseDir = input.path ?
|
|
8790
|
+
const baseDir = input.path ? path20.isAbsolute(input.path) ? input.path : path20.resolve(context.cwd, input.path) : context.cwd;
|
|
7870
8791
|
try {
|
|
7871
8792
|
const entries = await walkDir(baseDir);
|
|
7872
8793
|
const matched = entries.filter(
|
|
7873
|
-
(e) => simpleGlobMatch(
|
|
8794
|
+
(e) => simpleGlobMatch(path20.relative(baseDir, e.path), input.pattern)
|
|
7874
8795
|
);
|
|
7875
8796
|
matched.sort((a, b) => b.mtime - a.mtime);
|
|
7876
8797
|
if (matched.length === 0) {
|
|
@@ -7896,7 +8817,7 @@ __export(grep_exports, {
|
|
|
7896
8817
|
});
|
|
7897
8818
|
import { spawn as spawn2 } from "child_process";
|
|
7898
8819
|
import fs5 from "fs/promises";
|
|
7899
|
-
import
|
|
8820
|
+
import path21 from "path";
|
|
7900
8821
|
import { z as z3 } from "zod";
|
|
7901
8822
|
function runRipgrep(input, searchPath, context) {
|
|
7902
8823
|
return new Promise((resolve, reject) => {
|
|
@@ -7950,7 +8871,7 @@ async function nodeGrep(input, searchPath) {
|
|
|
7950
8871
|
}
|
|
7951
8872
|
for (const entry of entries) {
|
|
7952
8873
|
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
7953
|
-
const fullPath =
|
|
8874
|
+
const fullPath = path21.join(dir, entry.name);
|
|
7954
8875
|
if (entry.isDirectory()) {
|
|
7955
8876
|
await walk(fullPath);
|
|
7956
8877
|
} else {
|
|
@@ -7996,7 +8917,7 @@ var init_grep = __esm({
|
|
|
7996
8917
|
inputSchema: inputSchema3,
|
|
7997
8918
|
isReadOnly: true,
|
|
7998
8919
|
async call(input, context) {
|
|
7999
|
-
const searchPath = input.path ?
|
|
8920
|
+
const searchPath = input.path ? path21.isAbsolute(input.path) ? input.path : path21.resolve(context.cwd, input.path) : context.cwd;
|
|
8000
8921
|
try {
|
|
8001
8922
|
const result = await runRipgrep(input, searchPath, context);
|
|
8002
8923
|
return result;
|
|
@@ -8021,7 +8942,7 @@ __export(file_write_exports, {
|
|
|
8021
8942
|
FileWriteTool: () => FileWriteTool
|
|
8022
8943
|
});
|
|
8023
8944
|
import fs6 from "fs/promises";
|
|
8024
|
-
import
|
|
8945
|
+
import path22 from "path";
|
|
8025
8946
|
import { z as z4 } from "zod";
|
|
8026
8947
|
var inputSchema4, FileWriteTool;
|
|
8027
8948
|
var init_file_write = __esm({
|
|
@@ -8049,8 +8970,8 @@ var init_file_write = __esm({
|
|
|
8049
8970
|
return { behavior: "allow" };
|
|
8050
8971
|
},
|
|
8051
8972
|
async call(input, context) {
|
|
8052
|
-
const filePath =
|
|
8053
|
-
const dir =
|
|
8973
|
+
const filePath = path22.isAbsolute(input.file_path) ? input.file_path : path22.resolve(context.cwd, input.file_path);
|
|
8974
|
+
const dir = path22.dirname(filePath);
|
|
8054
8975
|
await fs6.mkdir(dir, { recursive: true });
|
|
8055
8976
|
await fs6.writeFile(filePath, input.content, "utf-8");
|
|
8056
8977
|
return {
|
|
@@ -8068,7 +8989,7 @@ __export(file_edit_exports, {
|
|
|
8068
8989
|
FileEditTool: () => FileEditTool
|
|
8069
8990
|
});
|
|
8070
8991
|
import fs7 from "fs/promises";
|
|
8071
|
-
import
|
|
8992
|
+
import path23 from "path";
|
|
8072
8993
|
import { z as z5 } from "zod";
|
|
8073
8994
|
function countOccurrences(haystack, needle) {
|
|
8074
8995
|
let count = 0;
|
|
@@ -8109,7 +9030,7 @@ var init_file_edit = __esm({
|
|
|
8109
9030
|
return { behavior: "allow" };
|
|
8110
9031
|
},
|
|
8111
9032
|
async call(input, context) {
|
|
8112
|
-
const filePath =
|
|
9033
|
+
const filePath = path23.isAbsolute(input.file_path) ? input.file_path : path23.resolve(context.cwd, input.file_path);
|
|
8113
9034
|
let content;
|
|
8114
9035
|
try {
|
|
8115
9036
|
content = await fs7.readFile(filePath, "utf-8");
|
|
@@ -8716,6 +9637,133 @@ ${task.context}`,
|
|
|
8716
9637
|
}
|
|
8717
9638
|
});
|
|
8718
9639
|
|
|
9640
|
+
// src/lib/tui-data.ts
|
|
9641
|
+
var tui_data_exports = {};
|
|
9642
|
+
__export(tui_data_exports, {
|
|
9643
|
+
loadMemoryDashboard: () => loadMemoryDashboard,
|
|
9644
|
+
loadTaskList: () => loadTaskList,
|
|
9645
|
+
loadTeamMetrics: () => loadTeamMetrics,
|
|
9646
|
+
searchWikiMemoryRows: () => searchWikiMemoryRows
|
|
9647
|
+
});
|
|
9648
|
+
async function loadMemoryDashboard(limit) {
|
|
9649
|
+
const client = getClient();
|
|
9650
|
+
const [countResult, recentResult, agentResult] = await Promise.all([
|
|
9651
|
+
client.execute("SELECT COUNT(*) as cnt FROM memories"),
|
|
9652
|
+
client.execute({
|
|
9653
|
+
sql: "SELECT agent_id, tool_name, project_name, raw_text, timestamp FROM memories ORDER BY timestamp DESC LIMIT ?",
|
|
9654
|
+
args: [limit]
|
|
9655
|
+
}),
|
|
9656
|
+
client.execute("SELECT agent_id, COUNT(*) as cnt FROM memories GROUP BY agent_id ORDER BY cnt DESC")
|
|
9657
|
+
]);
|
|
9658
|
+
return {
|
|
9659
|
+
total: Number(countResult.rows[0]?.cnt ?? 0),
|
|
9660
|
+
recent: recentResult.rows.map((row) => ({
|
|
9661
|
+
agentId: String(row.agent_id ?? "unknown"),
|
|
9662
|
+
toolName: String(row.tool_name ?? ""),
|
|
9663
|
+
projectName: String(row.project_name ?? ""),
|
|
9664
|
+
rawText: String(row.raw_text ?? ""),
|
|
9665
|
+
timestamp: String(row.timestamp ?? "")
|
|
9666
|
+
})),
|
|
9667
|
+
byAgent: agentResult.rows.map((row) => ({
|
|
9668
|
+
agentId: String(row.agent_id ?? "unknown"),
|
|
9669
|
+
count: Number(row.cnt ?? 0)
|
|
9670
|
+
}))
|
|
9671
|
+
};
|
|
9672
|
+
}
|
|
9673
|
+
async function loadTeamMetrics(employeeNames) {
|
|
9674
|
+
const client = getClient();
|
|
9675
|
+
const memoryCounts = /* @__PURE__ */ new Map();
|
|
9676
|
+
const projectsByEmployee = /* @__PURE__ */ new Map();
|
|
9677
|
+
const currentTaskByEmployee = /* @__PURE__ */ new Map();
|
|
9678
|
+
const scope = sessionScopeFilter();
|
|
9679
|
+
const memResult = await client.execute("SELECT agent_id, COUNT(*) as cnt FROM memories GROUP BY agent_id");
|
|
9680
|
+
for (const row of memResult.rows) {
|
|
9681
|
+
memoryCounts.set(String(row.agent_id), Number(row.cnt));
|
|
9682
|
+
}
|
|
9683
|
+
for (const employeeName of employeeNames) {
|
|
9684
|
+
const [projectResult, taskResult] = await Promise.all([
|
|
9685
|
+
client.execute({
|
|
9686
|
+
sql: `SELECT DISTINCT project_name,
|
|
9687
|
+
MAX(CASE WHEN status = 'in_progress' THEN 1 WHEN status = 'open' THEN 2 ELSE 3 END) as urgency
|
|
9688
|
+
FROM tasks
|
|
9689
|
+
WHERE assigned_to = ? AND status IN ('open','in_progress','done')${scope.sql}
|
|
9690
|
+
GROUP BY project_name
|
|
9691
|
+
ORDER BY urgency ASC
|
|
9692
|
+
LIMIT 5`,
|
|
9693
|
+
args: [employeeName, ...scope.args]
|
|
9694
|
+
}),
|
|
9695
|
+
client.execute({
|
|
9696
|
+
sql: `SELECT title FROM tasks
|
|
9697
|
+
WHERE assigned_to = ? AND status = 'in_progress'${scope.sql}
|
|
9698
|
+
ORDER BY updated_at DESC
|
|
9699
|
+
LIMIT 1`,
|
|
9700
|
+
args: [employeeName, ...scope.args]
|
|
9701
|
+
})
|
|
9702
|
+
]);
|
|
9703
|
+
const projects = projectResult.rows.map((row) => {
|
|
9704
|
+
const urgency = Number(row.urgency);
|
|
9705
|
+
return {
|
|
9706
|
+
name: String(row.project_name),
|
|
9707
|
+
status: urgency === 1 ? "active" : urgency === 2 ? "has_tasks" : "idle"
|
|
9708
|
+
};
|
|
9709
|
+
});
|
|
9710
|
+
if (projects.length > 0) projectsByEmployee.set(employeeName, projects);
|
|
9711
|
+
if (taskResult.rows.length > 0) {
|
|
9712
|
+
currentTaskByEmployee.set(employeeName, String(taskResult.rows[0].title));
|
|
9713
|
+
}
|
|
9714
|
+
}
|
|
9715
|
+
return { memoryCounts, projectsByEmployee, currentTaskByEmployee };
|
|
9716
|
+
}
|
|
9717
|
+
async function loadTaskList() {
|
|
9718
|
+
const client = getClient();
|
|
9719
|
+
const scope = sessionScopeFilter();
|
|
9720
|
+
const result = await client.execute({
|
|
9721
|
+
sql: `SELECT id, title, priority, assigned_to, assigned_by, status, project_name, created_at, result
|
|
9722
|
+
FROM tasks
|
|
9723
|
+
WHERE 1=1${scope.sql}
|
|
9724
|
+
ORDER BY
|
|
9725
|
+
CASE status WHEN 'in_progress' THEN 0 WHEN 'open' THEN 1 WHEN 'blocked' THEN 2 WHEN 'needs_review' THEN 3 WHEN 'done' THEN 4 ELSE 5 END,
|
|
9726
|
+
CASE priority WHEN 'p0' THEN 0 WHEN 'p1' THEN 1 WHEN 'p2' THEN 2 ELSE 3 END,
|
|
9727
|
+
created_at DESC`,
|
|
9728
|
+
args: [...scope.args]
|
|
9729
|
+
});
|
|
9730
|
+
return result.rows.map((row) => ({
|
|
9731
|
+
id: String(row.id ?? ""),
|
|
9732
|
+
title: String(row.title ?? ""),
|
|
9733
|
+
priority: String(row.priority ?? "p2").toUpperCase(),
|
|
9734
|
+
assignedTo: String(row.assigned_to ?? ""),
|
|
9735
|
+
assignedBy: String(row.assigned_by ?? ""),
|
|
9736
|
+
status: String(row.status ?? "open"),
|
|
9737
|
+
projectName: String(row.project_name ?? ""),
|
|
9738
|
+
createdAt: String(row.created_at ?? ""),
|
|
9739
|
+
result: String(row.result ?? "")
|
|
9740
|
+
}));
|
|
9741
|
+
}
|
|
9742
|
+
async function searchWikiMemoryRows(query) {
|
|
9743
|
+
const client = getClient();
|
|
9744
|
+
const result = await client.execute({
|
|
9745
|
+
sql: `SELECT id, agent_id, raw_text, timestamp, project_name
|
|
9746
|
+
FROM memories
|
|
9747
|
+
WHERE raw_text LIKE ? AND COALESCE(status, 'active') = 'active'
|
|
9748
|
+
ORDER BY timestamp DESC LIMIT 20`,
|
|
9749
|
+
args: [`%${query}%`]
|
|
9750
|
+
});
|
|
9751
|
+
return result.rows.map((row) => ({
|
|
9752
|
+
id: String(row.id),
|
|
9753
|
+
agentId: String(row.agent_id),
|
|
9754
|
+
rawText: String(row.raw_text),
|
|
9755
|
+
timestamp: String(row.timestamp),
|
|
9756
|
+
projectName: String(row.project_name ?? "")
|
|
9757
|
+
}));
|
|
9758
|
+
}
|
|
9759
|
+
var init_tui_data = __esm({
|
|
9760
|
+
"src/lib/tui-data.ts"() {
|
|
9761
|
+
"use strict";
|
|
9762
|
+
init_database();
|
|
9763
|
+
init_task_scope();
|
|
9764
|
+
}
|
|
9765
|
+
});
|
|
9766
|
+
|
|
8719
9767
|
// src/lib/message-queue.ts
|
|
8720
9768
|
var DEFAULT_MAX_SIZE, DEFAULT_TTL_MS, MessageQueue;
|
|
8721
9769
|
var init_message_queue = __esm({
|
|
@@ -8774,14 +9822,14 @@ __export(keychain_exports, {
|
|
|
8774
9822
|
setMasterKey: () => setMasterKey
|
|
8775
9823
|
});
|
|
8776
9824
|
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
8777
|
-
import { existsSync as
|
|
8778
|
-
import
|
|
8779
|
-
import
|
|
9825
|
+
import { existsSync as existsSync16 } from "fs";
|
|
9826
|
+
import path26 from "path";
|
|
9827
|
+
import os12 from "os";
|
|
8780
9828
|
function getKeyDir() {
|
|
8781
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
9829
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path26.join(os12.homedir(), ".exe-os");
|
|
8782
9830
|
}
|
|
8783
9831
|
function getKeyPath() {
|
|
8784
|
-
return
|
|
9832
|
+
return path26.join(getKeyDir(), "master.key");
|
|
8785
9833
|
}
|
|
8786
9834
|
async function tryKeytar() {
|
|
8787
9835
|
try {
|
|
@@ -8802,9 +9850,9 @@ async function getMasterKey() {
|
|
|
8802
9850
|
}
|
|
8803
9851
|
}
|
|
8804
9852
|
const keyPath = getKeyPath();
|
|
8805
|
-
if (!
|
|
9853
|
+
if (!existsSync16(keyPath)) {
|
|
8806
9854
|
process.stderr.write(
|
|
8807
|
-
`[keychain] Key not found at ${keyPath} (HOME=${
|
|
9855
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os12.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
8808
9856
|
`
|
|
8809
9857
|
);
|
|
8810
9858
|
return null;
|
|
@@ -8845,7 +9893,7 @@ async function deleteMasterKey() {
|
|
|
8845
9893
|
}
|
|
8846
9894
|
}
|
|
8847
9895
|
const keyPath = getKeyPath();
|
|
8848
|
-
if (
|
|
9896
|
+
if (existsSync16(keyPath)) {
|
|
8849
9897
|
await unlink(keyPath);
|
|
8850
9898
|
}
|
|
8851
9899
|
}
|
|
@@ -8894,16 +9942,16 @@ __export(ws_auth_exports, {
|
|
|
8894
9942
|
deriveWsAuthToken: () => deriveWsAuthToken,
|
|
8895
9943
|
hashAuthToken: () => hashAuthToken
|
|
8896
9944
|
});
|
|
8897
|
-
import
|
|
9945
|
+
import crypto7 from "crypto";
|
|
8898
9946
|
function deriveWsAuthToken(masterKey) {
|
|
8899
|
-
return Buffer.from(
|
|
9947
|
+
return Buffer.from(crypto7.hkdfSync("sha256", masterKey, "", WS_AUTH_HKDF_INFO, 32));
|
|
8900
9948
|
}
|
|
8901
9949
|
function deriveOrgId(masterKey) {
|
|
8902
|
-
const raw = Buffer.from(
|
|
8903
|
-
return
|
|
9950
|
+
const raw = Buffer.from(crypto7.hkdfSync("sha256", masterKey, "", ORG_ID_HKDF_INFO, 32));
|
|
9951
|
+
return crypto7.createHash("sha256").update(raw).digest("hex").slice(0, 32);
|
|
8904
9952
|
}
|
|
8905
9953
|
function hashAuthToken(token) {
|
|
8906
|
-
return
|
|
9954
|
+
return crypto7.createHash("sha256").update(token).digest("hex");
|
|
8907
9955
|
}
|
|
8908
9956
|
var WS_AUTH_HKDF_INFO, ORG_ID_HKDF_INFO;
|
|
8909
9957
|
var init_ws_auth = __esm({
|
|
@@ -9145,8 +10193,8 @@ __export(wiki_client_exports, {
|
|
|
9145
10193
|
listDocuments: () => listDocuments,
|
|
9146
10194
|
listWorkspaces: () => listWorkspaces
|
|
9147
10195
|
});
|
|
9148
|
-
async function wikiFetch(config,
|
|
9149
|
-
const url = `${config.baseUrl}/api/v1${
|
|
10196
|
+
async function wikiFetch(config, path28, method = "GET", body) {
|
|
10197
|
+
const url = `${config.baseUrl}/api/v1${path28}`;
|
|
9150
10198
|
const headers = {
|
|
9151
10199
|
Authorization: `Bearer ${config.apiKey}`,
|
|
9152
10200
|
"Content-Type": "application/json"
|
|
@@ -9179,7 +10227,7 @@ async function wikiFetch(config, path26, method = "GET", body) {
|
|
|
9179
10227
|
}
|
|
9180
10228
|
}
|
|
9181
10229
|
if (!response.ok) {
|
|
9182
|
-
throw new Error(`Wiki API ${method} ${
|
|
10230
|
+
throw new Error(`Wiki API ${method} ${path28}: ${response.status} ${response.statusText}`);
|
|
9183
10231
|
}
|
|
9184
10232
|
return response.json();
|
|
9185
10233
|
} finally {
|
|
@@ -9292,6 +10340,7 @@ var shard_manager_exports = {};
|
|
|
9292
10340
|
__export(shard_manager_exports, {
|
|
9293
10341
|
disposeShards: () => disposeShards,
|
|
9294
10342
|
ensureShardSchema: () => ensureShardSchema,
|
|
10343
|
+
getOpenShardCount: () => getOpenShardCount,
|
|
9295
10344
|
getReadyShardClient: () => getReadyShardClient,
|
|
9296
10345
|
getShardClient: () => getShardClient,
|
|
9297
10346
|
getShardsDir: () => getShardsDir,
|
|
@@ -9300,15 +10349,18 @@ __export(shard_manager_exports, {
|
|
|
9300
10349
|
listShards: () => listShards,
|
|
9301
10350
|
shardExists: () => shardExists
|
|
9302
10351
|
});
|
|
9303
|
-
import
|
|
9304
|
-
import { existsSync as
|
|
10352
|
+
import path27 from "path";
|
|
10353
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
|
|
9305
10354
|
import { createClient as createClient2 } from "@libsql/client";
|
|
9306
10355
|
function initShardManager(encryptionKey) {
|
|
9307
10356
|
_encryptionKey = encryptionKey;
|
|
9308
|
-
if (!
|
|
10357
|
+
if (!existsSync17(SHARDS_DIR)) {
|
|
9309
10358
|
mkdirSync7(SHARDS_DIR, { recursive: true });
|
|
9310
10359
|
}
|
|
9311
10360
|
_shardingEnabled = true;
|
|
10361
|
+
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
10362
|
+
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
10363
|
+
_evictionTimer.unref();
|
|
9312
10364
|
}
|
|
9313
10365
|
function isShardingEnabled() {
|
|
9314
10366
|
return _shardingEnabled;
|
|
@@ -9325,21 +10377,28 @@ function getShardClient(projectName) {
|
|
|
9325
10377
|
throw new Error(`Invalid project name for shard: "${projectName}"`);
|
|
9326
10378
|
}
|
|
9327
10379
|
const cached = _shards.get(safeName);
|
|
9328
|
-
if (cached)
|
|
9329
|
-
|
|
10380
|
+
if (cached) {
|
|
10381
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
10382
|
+
return cached;
|
|
10383
|
+
}
|
|
10384
|
+
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
10385
|
+
evictLRU();
|
|
10386
|
+
}
|
|
10387
|
+
const dbPath = path27.join(SHARDS_DIR, `${safeName}.db`);
|
|
9330
10388
|
const client = createClient2({
|
|
9331
10389
|
url: `file:${dbPath}`,
|
|
9332
10390
|
encryptionKey: _encryptionKey
|
|
9333
10391
|
});
|
|
9334
10392
|
_shards.set(safeName, client);
|
|
10393
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
9335
10394
|
return client;
|
|
9336
10395
|
}
|
|
9337
10396
|
function shardExists(projectName) {
|
|
9338
10397
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
9339
|
-
return
|
|
10398
|
+
return existsSync17(path27.join(SHARDS_DIR, `${safeName}.db`));
|
|
9340
10399
|
}
|
|
9341
10400
|
function listShards() {
|
|
9342
|
-
if (!
|
|
10401
|
+
if (!existsSync17(SHARDS_DIR)) return [];
|
|
9343
10402
|
return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
9344
10403
|
}
|
|
9345
10404
|
async function ensureShardSchema(client) {
|
|
@@ -9391,6 +10450,8 @@ async function ensureShardSchema(client) {
|
|
|
9391
10450
|
for (const col of [
|
|
9392
10451
|
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
9393
10452
|
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
10453
|
+
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
10454
|
+
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
9394
10455
|
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
9395
10456
|
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
9396
10457
|
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
@@ -9413,7 +10474,23 @@ async function ensureShardSchema(client) {
|
|
|
9413
10474
|
// MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
|
|
9414
10475
|
"ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
|
|
9415
10476
|
"ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
|
|
9416
|
-
"ALTER TABLE memories ADD COLUMN trajectory TEXT"
|
|
10477
|
+
"ALTER TABLE memories ADD COLUMN trajectory TEXT",
|
|
10478
|
+
// Metadata enrichment columns (must match database.ts)
|
|
10479
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
10480
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
10481
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
10482
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
10483
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
10484
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
10485
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
10486
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
10487
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
10488
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
10489
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
10490
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
10491
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
10492
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
10493
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
9417
10494
|
]) {
|
|
9418
10495
|
try {
|
|
9419
10496
|
await client.execute(col);
|
|
@@ -9512,21 +10589,69 @@ async function getReadyShardClient(projectName) {
|
|
|
9512
10589
|
await ensureShardSchema(client);
|
|
9513
10590
|
return client;
|
|
9514
10591
|
}
|
|
10592
|
+
function evictLRU() {
|
|
10593
|
+
let oldest = null;
|
|
10594
|
+
let oldestTime = Infinity;
|
|
10595
|
+
for (const [name, time] of _shardLastAccess) {
|
|
10596
|
+
if (time < oldestTime) {
|
|
10597
|
+
oldestTime = time;
|
|
10598
|
+
oldest = name;
|
|
10599
|
+
}
|
|
10600
|
+
}
|
|
10601
|
+
if (oldest) {
|
|
10602
|
+
const client = _shards.get(oldest);
|
|
10603
|
+
if (client) {
|
|
10604
|
+
client.close();
|
|
10605
|
+
}
|
|
10606
|
+
_shards.delete(oldest);
|
|
10607
|
+
_shardLastAccess.delete(oldest);
|
|
10608
|
+
}
|
|
10609
|
+
}
|
|
10610
|
+
function evictIdleShards() {
|
|
10611
|
+
const now = Date.now();
|
|
10612
|
+
const toEvict = [];
|
|
10613
|
+
for (const [name, lastAccess] of _shardLastAccess) {
|
|
10614
|
+
if (now - lastAccess > SHARD_IDLE_MS) {
|
|
10615
|
+
toEvict.push(name);
|
|
10616
|
+
}
|
|
10617
|
+
}
|
|
10618
|
+
for (const name of toEvict) {
|
|
10619
|
+
const client = _shards.get(name);
|
|
10620
|
+
if (client) {
|
|
10621
|
+
client.close();
|
|
10622
|
+
}
|
|
10623
|
+
_shards.delete(name);
|
|
10624
|
+
_shardLastAccess.delete(name);
|
|
10625
|
+
}
|
|
10626
|
+
}
|
|
10627
|
+
function getOpenShardCount() {
|
|
10628
|
+
return _shards.size;
|
|
10629
|
+
}
|
|
9515
10630
|
function disposeShards() {
|
|
10631
|
+
if (_evictionTimer) {
|
|
10632
|
+
clearInterval(_evictionTimer);
|
|
10633
|
+
_evictionTimer = null;
|
|
10634
|
+
}
|
|
9516
10635
|
for (const [, client] of _shards) {
|
|
9517
10636
|
client.close();
|
|
9518
10637
|
}
|
|
9519
10638
|
_shards.clear();
|
|
10639
|
+
_shardLastAccess.clear();
|
|
9520
10640
|
_shardingEnabled = false;
|
|
9521
10641
|
_encryptionKey = null;
|
|
9522
10642
|
}
|
|
9523
|
-
var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
|
|
10643
|
+
var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
|
|
9524
10644
|
var init_shard_manager = __esm({
|
|
9525
10645
|
"src/lib/shard-manager.ts"() {
|
|
9526
10646
|
"use strict";
|
|
9527
10647
|
init_config();
|
|
9528
|
-
SHARDS_DIR =
|
|
10648
|
+
SHARDS_DIR = path27.join(EXE_AI_DIR, "shards");
|
|
10649
|
+
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
10650
|
+
MAX_OPEN_SHARDS = 10;
|
|
10651
|
+
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
9529
10652
|
_shards = /* @__PURE__ */ new Map();
|
|
10653
|
+
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
10654
|
+
_evictionTimer = null;
|
|
9530
10655
|
_encryptionKey = null;
|
|
9531
10656
|
_shardingEnabled = false;
|
|
9532
10657
|
}
|
|
@@ -14097,8 +15222,8 @@ function Text({ color, backgroundColor, dimColor = false, bold = false, italic =
|
|
|
14097
15222
|
}
|
|
14098
15223
|
|
|
14099
15224
|
// src/tui/ink/components/ErrorOverview.js
|
|
14100
|
-
var cleanupPath = (
|
|
14101
|
-
return
|
|
15225
|
+
var cleanupPath = (path28) => {
|
|
15226
|
+
return path28?.replace(`file://${cwd()}/`, "");
|
|
14102
15227
|
};
|
|
14103
15228
|
var stackUtils = new StackUtils({
|
|
14104
15229
|
cwd: cwd(),
|
|
@@ -16030,11 +17155,11 @@ function Footer() {
|
|
|
16030
17155
|
} catch {
|
|
16031
17156
|
}
|
|
16032
17157
|
try {
|
|
16033
|
-
const { existsSync:
|
|
17158
|
+
const { existsSync: existsSync18 } = await import("fs");
|
|
16034
17159
|
const { join } = await import("path");
|
|
16035
17160
|
const home = process.env.HOME ?? "";
|
|
16036
17161
|
const pidPath = join(home, ".exe-os", "exed.pid");
|
|
16037
|
-
setDaemon(
|
|
17162
|
+
setDaemon(existsSync18(pidPath) ? "running" : "stopped");
|
|
16038
17163
|
} catch {
|
|
16039
17164
|
setDaemon("unknown");
|
|
16040
17165
|
}
|
|
@@ -16116,7 +17241,7 @@ function Footer() {
|
|
|
16116
17241
|
// src/tui/views/CommandCenter.tsx
|
|
16117
17242
|
import { useState as useState6, useEffect as useEffect8, useMemo as useMemo4, useCallback as useCallback4, useRef as useRef4 } from "react";
|
|
16118
17243
|
import TextInput from "ink-text-input";
|
|
16119
|
-
import
|
|
17244
|
+
import path24 from "path";
|
|
16120
17245
|
import { homedir } from "os";
|
|
16121
17246
|
|
|
16122
17247
|
// src/tui/components/StatusDot.tsx
|
|
@@ -16903,15 +18028,15 @@ function CommandCenterView({
|
|
|
16903
18028
|
const { createPermissionsFromPreset: createPermissionsFromPreset2, EMPLOYEE_PERMISSIONS: EMPLOYEE_PERMISSIONS2 } = await Promise.resolve().then(() => (init_permissions(), permissions_exports));
|
|
16904
18029
|
const { getPresetByRole: getPresetByRole2 } = await Promise.resolve().then(() => (init_permission_presets(), permission_presets_exports));
|
|
16905
18030
|
const { createDefaultHooks: createDefaultHooks2 } = await Promise.resolve().then(() => (init_hooks(), hooks_exports));
|
|
16906
|
-
const { readFileSync:
|
|
18031
|
+
const { readFileSync: readFileSync14, existsSync: existsSync18 } = await import("fs");
|
|
16907
18032
|
const { join } = await import("path");
|
|
16908
18033
|
const { homedir: homedir3 } = await import("os");
|
|
16909
18034
|
const configPath = join(homedir3(), ".exe-os", "config.json");
|
|
16910
18035
|
let failoverChain = ["anthropic", "opencode", "gemini", "openai"];
|
|
16911
18036
|
let providerConfigs = {};
|
|
16912
|
-
if (
|
|
18037
|
+
if (existsSync18(configPath)) {
|
|
16913
18038
|
try {
|
|
16914
|
-
const raw = JSON.parse(
|
|
18039
|
+
const raw = JSON.parse(readFileSync14(configPath, "utf8"));
|
|
16915
18040
|
if (Array.isArray(raw.failoverChain)) failoverChain = raw.failoverChain;
|
|
16916
18041
|
if (raw.providers && typeof raw.providers === "object") {
|
|
16917
18042
|
providerConfigs = raw.providers;
|
|
@@ -16972,7 +18097,7 @@ function CommandCenterView({
|
|
|
16972
18097
|
const markerDir = join(homedir3(), ".exe-os", "session-cache");
|
|
16973
18098
|
const agentFiles = (await import("fs")).readdirSync(markerDir).filter((f) => f.startsWith("active-agent-"));
|
|
16974
18099
|
for (const f of agentFiles) {
|
|
16975
|
-
const data = JSON.parse(
|
|
18100
|
+
const data = JSON.parse(readFileSync14(join(markerDir, f), "utf8"));
|
|
16976
18101
|
if (data.agentRole) {
|
|
16977
18102
|
agentRole = data.agentRole;
|
|
16978
18103
|
break;
|
|
@@ -17117,7 +18242,7 @@ function CommandCenterView({
|
|
|
17117
18242
|
const demoEntries = DEMO_PROJECTS.map((p) => ({
|
|
17118
18243
|
projectName: p.projectName,
|
|
17119
18244
|
exeSession: p.exeSession,
|
|
17120
|
-
projectDir:
|
|
18245
|
+
projectDir: path24.join(homedir(), p.projectName),
|
|
17121
18246
|
employeeCount: p.employees.length,
|
|
17122
18247
|
activeCount: p.employees.filter((e) => e.status === "active").length,
|
|
17123
18248
|
memoryCount: p.employees.length * 4e3,
|
|
@@ -17155,7 +18280,7 @@ function CommandCenterView({
|
|
|
17155
18280
|
const { listSessions: listSessions2 } = await Promise.resolve().then(() => (init_session_registry(), session_registry_exports));
|
|
17156
18281
|
const { listTmuxSessions: listTmuxSessions2, inTmux: inTmux2 } = await Promise.resolve().then(() => (init_tmux_status(), tmux_status_exports));
|
|
17157
18282
|
const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
17158
|
-
const { existsSync:
|
|
18283
|
+
const { existsSync: existsSync18 } = await import("fs");
|
|
17159
18284
|
const { join } = await import("path");
|
|
17160
18285
|
const client = getClient2();
|
|
17161
18286
|
if (!client) {
|
|
@@ -17226,7 +18351,7 @@ function CommandCenterView({
|
|
|
17226
18351
|
}
|
|
17227
18352
|
const memoryCount = memoryCounts.get(name) ?? 0;
|
|
17228
18353
|
const openTaskCount = openTaskCounts.get(name) ?? 0;
|
|
17229
|
-
const hasGit = projectDir ?
|
|
18354
|
+
const hasGit = projectDir ? existsSync18(join(projectDir, ".git")) : false;
|
|
17230
18355
|
const type = hasGit ? "code" : memoryCount > 0 ? "code" : "automation";
|
|
17231
18356
|
projectList.push({
|
|
17232
18357
|
projectName: name,
|
|
@@ -17251,7 +18376,7 @@ function CommandCenterView({
|
|
|
17251
18376
|
setHealth((h) => ({ ...h, memories: Number(totalResult.rows[0]?.cnt ?? 0) }));
|
|
17252
18377
|
try {
|
|
17253
18378
|
const pidPath = join(process.env.HOME ?? "", ".exe-os", "exed.pid");
|
|
17254
|
-
setHealth((h) => ({ ...h, daemon:
|
|
18379
|
+
setHealth((h) => ({ ...h, daemon: existsSync18(pidPath) ? "running" : "stopped" }));
|
|
17255
18380
|
} catch {
|
|
17256
18381
|
}
|
|
17257
18382
|
const activityResult = await client.execute(
|
|
@@ -17466,7 +18591,7 @@ function ChatMessageRow({ msg }) {
|
|
|
17466
18591
|
|
|
17467
18592
|
// src/tui/views/Sessions.tsx
|
|
17468
18593
|
import React19, { useState as useState9, useEffect as useEffect11, useCallback as useCallback6 } from "react";
|
|
17469
|
-
import
|
|
18594
|
+
import path25 from "path";
|
|
17470
18595
|
import { homedir as homedir2 } from "os";
|
|
17471
18596
|
|
|
17472
18597
|
// src/tui/components/TmuxPane.tsx
|
|
@@ -17770,7 +18895,7 @@ function SessionsView({
|
|
|
17770
18895
|
if (demo) {
|
|
17771
18896
|
setProjects(DEMO_PROJECTS.map((p) => ({
|
|
17772
18897
|
...p,
|
|
17773
|
-
projectDir:
|
|
18898
|
+
projectDir: path25.join(homedir2(), p.projectName),
|
|
17774
18899
|
employees: p.employees.map((e) => ({ ...e, attached: e.status === "active" }))
|
|
17775
18900
|
})));
|
|
17776
18901
|
return;
|
|
@@ -18166,7 +19291,6 @@ function SessionsView({
|
|
|
18166
19291
|
|
|
18167
19292
|
// src/tui/views/Tasks.tsx
|
|
18168
19293
|
import React20, { useState as useState10, useEffect as useEffect12, useMemo as useMemo5 } from "react";
|
|
18169
|
-
init_task_scope();
|
|
18170
19294
|
import { Fragment as Fragment3, jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
18171
19295
|
var STATUS_FILTERS = ["all", "open", "in_progress", "done", "blocked", "needs_review"];
|
|
18172
19296
|
var PRIORITY_COLORS = {
|
|
@@ -18299,37 +19423,22 @@ function TasksView({ onBack }) {
|
|
|
18299
19423
|
const { withTrace: withTrace2 } = await Promise.resolve().then(() => (init_telemetry(), telemetry_exports));
|
|
18300
19424
|
return withTrace2("tui.tasks.loadTasks", async () => {
|
|
18301
19425
|
try {
|
|
18302
|
-
const {
|
|
18303
|
-
const
|
|
18304
|
-
|
|
18305
|
-
|
|
18306
|
-
|
|
18307
|
-
|
|
18308
|
-
|
|
18309
|
-
|
|
18310
|
-
|
|
18311
|
-
|
|
18312
|
-
|
|
18313
|
-
|
|
18314
|
-
|
|
18315
|
-
})
|
|
18316
|
-
|
|
18317
|
-
|
|
18318
|
-
id: String(r.id ?? ""),
|
|
18319
|
-
priority: String(r.priority ?? "p2").toUpperCase(),
|
|
18320
|
-
title: String(r.title ?? ""),
|
|
18321
|
-
assignee: String(r.assigned_to ?? ""),
|
|
18322
|
-
assignedBy: String(r.assigned_by ?? ""),
|
|
18323
|
-
status: String(r.status ?? "open"),
|
|
18324
|
-
project: String(r.project_name ?? ""),
|
|
18325
|
-
createdAt: String(r.created_at ?? ""),
|
|
18326
|
-
result: String(r.result ?? "")
|
|
18327
|
-
}))
|
|
18328
|
-
);
|
|
18329
|
-
setDbError(null);
|
|
18330
|
-
} else {
|
|
18331
|
-
setDbError("Database client not initialized. Run exe-os setup.");
|
|
18332
|
-
}
|
|
19426
|
+
const { loadTaskList: loadTaskList2 } = await Promise.resolve().then(() => (init_tui_data(), tui_data_exports));
|
|
19427
|
+
const tasks = await loadTaskList2();
|
|
19428
|
+
setAllTasks(
|
|
19429
|
+
tasks.map((task) => ({
|
|
19430
|
+
id: task.id,
|
|
19431
|
+
priority: task.priority,
|
|
19432
|
+
title: task.title,
|
|
19433
|
+
assignee: task.assignedTo,
|
|
19434
|
+
assignedBy: task.assignedBy,
|
|
19435
|
+
status: task.status,
|
|
19436
|
+
project: task.projectName,
|
|
19437
|
+
createdAt: task.createdAt,
|
|
19438
|
+
result: task.result
|
|
19439
|
+
}))
|
|
19440
|
+
);
|
|
19441
|
+
setDbError(null);
|
|
18333
19442
|
} catch (err) {
|
|
18334
19443
|
setDbError(err instanceof Error ? err.message : "Unknown error");
|
|
18335
19444
|
} finally {
|
|
@@ -18667,12 +19776,12 @@ async function loadGatewayConfig() {
|
|
|
18667
19776
|
state.running = false;
|
|
18668
19777
|
}
|
|
18669
19778
|
try {
|
|
18670
|
-
const { existsSync:
|
|
19779
|
+
const { existsSync: existsSync18, readFileSync: readFileSync14 } = await import("fs");
|
|
18671
19780
|
const { join } = await import("path");
|
|
18672
19781
|
const home = process.env.HOME ?? "";
|
|
18673
19782
|
const configPath = join(home, ".exe-os", "gateway.json");
|
|
18674
|
-
if (
|
|
18675
|
-
const raw = JSON.parse(
|
|
19783
|
+
if (existsSync18(configPath)) {
|
|
19784
|
+
const raw = JSON.parse(readFileSync14(configPath, "utf8"));
|
|
18676
19785
|
state.port = raw.port ?? 3100;
|
|
18677
19786
|
state.gatewayUrl = raw.gatewayUrl ?? "";
|
|
18678
19787
|
if (raw.adapters) {
|
|
@@ -19072,7 +20181,6 @@ function getAgentStatus(agentId) {
|
|
|
19072
20181
|
}
|
|
19073
20182
|
|
|
19074
20183
|
// src/tui/views/Team.tsx
|
|
19075
|
-
init_task_scope();
|
|
19076
20184
|
import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
19077
20185
|
var DEPRECATED_PROJECTS = /* @__PURE__ */ new Set(["exe-ai-employees"]);
|
|
19078
20186
|
function roleBadgeColor(role) {
|
|
@@ -19145,41 +20253,16 @@ function TeamView({ onBack, onViewSessions }) {
|
|
|
19145
20253
|
let projectsByEmployee = /* @__PURE__ */ new Map();
|
|
19146
20254
|
let currentTaskByEmployee = /* @__PURE__ */ new Map();
|
|
19147
20255
|
try {
|
|
19148
|
-
const {
|
|
19149
|
-
const
|
|
19150
|
-
|
|
19151
|
-
|
|
19152
|
-
|
|
19153
|
-
|
|
19154
|
-
|
|
19155
|
-
|
|
19156
|
-
|
|
19157
|
-
|
|
19158
|
-
sql: `SELECT DISTINCT project_name,
|
|
19159
|
-
MAX(CASE WHEN status = 'in_progress' THEN 1 WHEN status = 'open' THEN 2 ELSE 3 END) as urgency
|
|
19160
|
-
FROM tasks WHERE assigned_to = ? AND status IN ('open','in_progress','done')${tmScope.sql}
|
|
19161
|
-
GROUP BY project_name ORDER BY urgency ASC LIMIT 5`,
|
|
19162
|
-
args: [emp.name, ...tmScope.args]
|
|
19163
|
-
});
|
|
19164
|
-
const projects = projResult.rows.filter((r) => !DEPRECATED_PROJECTS.has(String(r.project_name))).map((r) => {
|
|
19165
|
-
const urgency = Number(r.urgency);
|
|
19166
|
-
let pStatus = "idle";
|
|
19167
|
-
if (urgency === 1) pStatus = "active";
|
|
19168
|
-
else if (urgency === 2) pStatus = "has_tasks";
|
|
19169
|
-
return { name: String(r.project_name), status: pStatus };
|
|
19170
|
-
});
|
|
19171
|
-
if (projects.length > 0) projectsByEmployee.set(emp.name, projects);
|
|
19172
|
-
}
|
|
19173
|
-
for (const emp of roster) {
|
|
19174
|
-
const taskResult = await client.execute({
|
|
19175
|
-
sql: `SELECT title FROM tasks WHERE assigned_to = ? AND status = 'in_progress'${tmScope.sql} ORDER BY updated_at DESC LIMIT 1`,
|
|
19176
|
-
args: [emp.name, ...tmScope.args]
|
|
19177
|
-
});
|
|
19178
|
-
if (taskResult.rows.length > 0) {
|
|
19179
|
-
currentTaskByEmployee.set(emp.name, String(taskResult.rows[0].title));
|
|
19180
|
-
}
|
|
19181
|
-
}
|
|
19182
|
-
}
|
|
20256
|
+
const { loadTeamMetrics: loadTeamMetrics2 } = await Promise.resolve().then(() => (init_tui_data(), tui_data_exports));
|
|
20257
|
+
const teamMetrics = await loadTeamMetrics2(roster.map((emp) => emp.name));
|
|
20258
|
+
memoryCounts = teamMetrics.memoryCounts;
|
|
20259
|
+
projectsByEmployee = new Map(
|
|
20260
|
+
Array.from(teamMetrics.projectsByEmployee.entries()).map(([name, projects]) => [
|
|
20261
|
+
name,
|
|
20262
|
+
projects.filter((project) => !DEPRECATED_PROJECTS.has(project.name))
|
|
20263
|
+
])
|
|
20264
|
+
);
|
|
20265
|
+
currentTaskByEmployee = teamMetrics.currentTaskByEmployee;
|
|
19183
20266
|
} catch {
|
|
19184
20267
|
}
|
|
19185
20268
|
const teamData = roster.map((emp) => {
|
|
@@ -19198,12 +20281,12 @@ function TeamView({ onBack, onViewSessions }) {
|
|
|
19198
20281
|
setMembers(teamData);
|
|
19199
20282
|
setDbError(null);
|
|
19200
20283
|
try {
|
|
19201
|
-
const { existsSync:
|
|
20284
|
+
const { existsSync: existsSync18, readFileSync: readFileSync14 } = await import("fs");
|
|
19202
20285
|
const { join } = await import("path");
|
|
19203
20286
|
const home = process.env.HOME ?? "";
|
|
19204
20287
|
const gatewayConfig = join(home, ".exe-os", "gateway.json");
|
|
19205
|
-
if (
|
|
19206
|
-
const raw = JSON.parse(
|
|
20288
|
+
if (existsSync18(gatewayConfig)) {
|
|
20289
|
+
const raw = JSON.parse(readFileSync14(gatewayConfig, "utf8"));
|
|
19207
20290
|
if (raw.agents && raw.agents.length > 0) {
|
|
19208
20291
|
setExternals(raw.agents.map((a) => ({
|
|
19209
20292
|
name: a.name,
|
|
@@ -19517,24 +20600,8 @@ function WikiView({ onBack }) {
|
|
|
19517
20600
|
}
|
|
19518
20601
|
setSearchLoading(true);
|
|
19519
20602
|
try {
|
|
19520
|
-
const {
|
|
19521
|
-
|
|
19522
|
-
const result = await client.execute({
|
|
19523
|
-
sql: `SELECT id, agent_id, raw_text, timestamp, project_name
|
|
19524
|
-
FROM memories
|
|
19525
|
-
WHERE raw_text LIKE ? AND COALESCE(status, 'active') = 'active'
|
|
19526
|
-
ORDER BY timestamp DESC LIMIT 20`,
|
|
19527
|
-
args: [`%${query}%`]
|
|
19528
|
-
});
|
|
19529
|
-
setSearchResults(
|
|
19530
|
-
result.rows.map((r) => ({
|
|
19531
|
-
id: String(r.id),
|
|
19532
|
-
agentId: String(r.agent_id),
|
|
19533
|
-
rawText: String(r.raw_text),
|
|
19534
|
-
timestamp: String(r.timestamp),
|
|
19535
|
-
projectName: String(r.project_name ?? "")
|
|
19536
|
-
}))
|
|
19537
|
-
);
|
|
20603
|
+
const { searchWikiMemoryRows: searchWikiMemoryRows2 } = await Promise.resolve().then(() => (init_tui_data(), tui_data_exports));
|
|
20604
|
+
setSearchResults(await searchWikiMemoryRows2(query));
|
|
19538
20605
|
} catch {
|
|
19539
20606
|
setSearchResults([]);
|
|
19540
20607
|
} finally {
|
|
@@ -19867,12 +20934,12 @@ function SettingsView({ onBack }) {
|
|
|
19867
20934
|
}
|
|
19868
20935
|
setProviders(providerList);
|
|
19869
20936
|
try {
|
|
19870
|
-
const { existsSync:
|
|
20937
|
+
const { existsSync: existsSync18 } = await import("fs");
|
|
19871
20938
|
const { join } = await import("path");
|
|
19872
20939
|
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
19873
20940
|
const cfg = await loadConfig2();
|
|
19874
20941
|
const home = process.env.HOME ?? "";
|
|
19875
|
-
const hasKey =
|
|
20942
|
+
const hasKey = existsSync18(join(home, ".exe-os", "master.key"));
|
|
19876
20943
|
if (cfg.cloud) {
|
|
19877
20944
|
setCloud({
|
|
19878
20945
|
configured: true,
|
|
@@ -19885,22 +20952,22 @@ function SettingsView({ onBack }) {
|
|
|
19885
20952
|
const pidPath = join(home, ".exe-os", "exed.pid");
|
|
19886
20953
|
let daemon = "unknown";
|
|
19887
20954
|
try {
|
|
19888
|
-
daemon =
|
|
20955
|
+
daemon = existsSync18(pidPath) ? "running" : "stopped";
|
|
19889
20956
|
} catch {
|
|
19890
20957
|
}
|
|
19891
20958
|
let version = "unknown";
|
|
19892
20959
|
try {
|
|
19893
|
-
const { readFileSync:
|
|
19894
|
-
const { createRequire } = await import("module");
|
|
19895
|
-
const require2 =
|
|
20960
|
+
const { readFileSync: readFileSync14 } = await import("fs");
|
|
20961
|
+
const { createRequire: createRequire3 } = await import("module");
|
|
20962
|
+
const require2 = createRequire3(import.meta.url);
|
|
19896
20963
|
const pkgPath = require2.resolve("@askexenow/exe-os/package.json");
|
|
19897
|
-
const pkg = JSON.parse(
|
|
20964
|
+
const pkg = JSON.parse(readFileSync14(pkgPath, "utf8"));
|
|
19898
20965
|
version = pkg.version;
|
|
19899
20966
|
} catch {
|
|
19900
20967
|
try {
|
|
19901
|
-
const { readFileSync:
|
|
20968
|
+
const { readFileSync: readFileSync14 } = await import("fs");
|
|
19902
20969
|
const { join: joinPath } = await import("path");
|
|
19903
|
-
const pkg = JSON.parse(
|
|
20970
|
+
const pkg = JSON.parse(readFileSync14(joinPath(process.cwd(), "package.json"), "utf8"));
|
|
19904
20971
|
version = pkg.version;
|
|
19905
20972
|
} catch {
|
|
19906
20973
|
}
|