@askexenow/exe-os 0.9.8 → 0.9.10
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 +222 -49
- package/dist/bin/backfill-responses.js +221 -48
- package/dist/bin/backfill-vectors.js +225 -52
- package/dist/bin/cleanup-stale-review-tasks.js +150 -28
- package/dist/bin/cli.js +1411 -953
- package/dist/bin/exe-agent-config.js +36 -8
- package/dist/bin/exe-agent.js +14 -4
- package/dist/bin/exe-assign.js +221 -48
- package/dist/bin/exe-boot.js +913 -543
- package/dist/bin/exe-call.js +41 -13
- package/dist/bin/exe-cloud.js +163 -58
- package/dist/bin/exe-dispatch.js +418 -262
- package/dist/bin/exe-doctor.js +145 -27
- package/dist/bin/exe-export-behaviors.js +141 -23
- package/dist/bin/exe-forget.js +137 -19
- package/dist/bin/exe-gateway.js +793 -485
- package/dist/bin/exe-heartbeat.js +227 -108
- package/dist/bin/exe-kill.js +138 -20
- package/dist/bin/exe-launch-agent.js +172 -39
- package/dist/bin/exe-link.js +291 -100
- package/dist/bin/exe-new-employee.js +214 -106
- package/dist/bin/exe-pending-messages.js +395 -33
- package/dist/bin/exe-pending-notifications.js +684 -99
- package/dist/bin/exe-pending-reviews.js +420 -74
- package/dist/bin/exe-rename.js +147 -49
- package/dist/bin/exe-review.js +138 -20
- package/dist/bin/exe-search.js +240 -69
- package/dist/bin/exe-session-cleanup.js +566 -357
- package/dist/bin/exe-settings.js +61 -17
- package/dist/bin/exe-start-codex.js +158 -39
- package/dist/bin/exe-start-opencode.js +157 -38
- package/dist/bin/exe-status.js +151 -29
- package/dist/bin/exe-team.js +138 -20
- package/dist/bin/git-sweep.js +530 -319
- package/dist/bin/graph-backfill.js +137 -19
- package/dist/bin/graph-export.js +140 -22
- package/dist/bin/install.js +90 -61
- package/dist/bin/scan-tasks.js +547 -336
- package/dist/bin/setup.js +564 -293
- package/dist/bin/shard-migrate.js +139 -21
- package/dist/bin/update.js +138 -49
- package/dist/bin/wiki-sync.js +137 -19
- package/dist/gateway/index.js +649 -417
- package/dist/hooks/bug-report-worker.js +486 -316
- package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
- package/dist/hooks/commit-complete.js +528 -317
- package/dist/hooks/error-recall.js +245 -74
- package/dist/hooks/exe-heartbeat-hook.js +16 -6
- package/dist/hooks/ingest-worker.js +3442 -3157
- package/dist/hooks/ingest.js +832 -97
- package/dist/hooks/instructions-loaded.js +227 -54
- package/dist/hooks/notification.js +216 -43
- package/dist/hooks/post-compact.js +239 -62
- package/dist/hooks/pre-compact.js +534 -323
- package/dist/hooks/pre-tool-use.js +268 -90
- package/dist/hooks/prompt-ingest-worker.js +352 -102
- package/dist/hooks/prompt-submit.js +614 -382
- package/dist/hooks/response-ingest-worker.js +372 -122
- package/dist/hooks/session-end.js +569 -347
- package/dist/hooks/session-start.js +313 -127
- package/dist/hooks/stop.js +293 -98
- package/dist/hooks/subagent-stop.js +239 -62
- package/dist/hooks/summary-worker.js +568 -236
- package/dist/index.js +664 -431
- package/dist/lib/agent-config.js +28 -6
- package/dist/lib/cloud-sync.js +284 -105
- package/dist/lib/config.js +30 -10
- package/dist/lib/consolidation.js +16 -6
- package/dist/lib/database.js +123 -25
- package/dist/lib/db-daemon-client.js +73 -19
- package/dist/lib/db.js +123 -25
- package/dist/lib/device-registry.js +133 -35
- package/dist/lib/embedder.js +107 -32
- package/dist/lib/employee-templates.js +14 -4
- package/dist/lib/employees.js +41 -13
- package/dist/lib/exe-daemon-client.js +88 -22
- package/dist/lib/exe-daemon.js +1049 -680
- package/dist/lib/hybrid-search.js +240 -69
- package/dist/lib/identity.js +18 -8
- package/dist/lib/license.js +133 -48
- package/dist/lib/messaging.js +116 -56
- package/dist/lib/reminders.js +14 -4
- package/dist/lib/schedules.js +137 -19
- package/dist/lib/skill-learning.js +33 -6
- package/dist/lib/store.js +137 -19
- package/dist/lib/task-router.js +14 -4
- package/dist/lib/tasks.js +422 -357
- package/dist/lib/tmux-routing.js +314 -248
- package/dist/lib/token-spend.js +26 -8
- package/dist/mcp/server.js +1408 -672
- package/dist/mcp/tools/complete-reminder.js +14 -4
- package/dist/mcp/tools/create-reminder.js +14 -4
- package/dist/mcp/tools/create-task.js +448 -371
- package/dist/mcp/tools/deactivate-behavior.js +16 -6
- package/dist/mcp/tools/list-reminders.js +14 -4
- package/dist/mcp/tools/list-tasks.js +123 -107
- package/dist/mcp/tools/send-message.js +75 -29
- package/dist/mcp/tools/update-task.js +1983 -315
- package/dist/runtime/index.js +567 -355
- package/dist/tui/App.js +887 -531
- package/package.json +4 -4
package/dist/bin/exe-gateway.js
CHANGED
|
@@ -392,6 +392,44 @@ var init_db_retry = __esm({
|
|
|
392
392
|
}
|
|
393
393
|
});
|
|
394
394
|
|
|
395
|
+
// src/lib/secure-files.ts
|
|
396
|
+
import { chmodSync, existsSync, mkdirSync } from "fs";
|
|
397
|
+
import { chmod, mkdir } from "fs/promises";
|
|
398
|
+
async function ensurePrivateDir(dirPath) {
|
|
399
|
+
await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
400
|
+
try {
|
|
401
|
+
await chmod(dirPath, PRIVATE_DIR_MODE);
|
|
402
|
+
} catch {
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
function ensurePrivateDirSync(dirPath) {
|
|
406
|
+
mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
407
|
+
try {
|
|
408
|
+
chmodSync(dirPath, PRIVATE_DIR_MODE);
|
|
409
|
+
} catch {
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
async function enforcePrivateFile(filePath) {
|
|
413
|
+
try {
|
|
414
|
+
await chmod(filePath, PRIVATE_FILE_MODE);
|
|
415
|
+
} catch {
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
function enforcePrivateFileSync(filePath) {
|
|
419
|
+
try {
|
|
420
|
+
if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
|
|
421
|
+
} catch {
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
|
|
425
|
+
var init_secure_files = __esm({
|
|
426
|
+
"src/lib/secure-files.ts"() {
|
|
427
|
+
"use strict";
|
|
428
|
+
PRIVATE_DIR_MODE = 448;
|
|
429
|
+
PRIVATE_FILE_MODE = 384;
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
|
|
395
433
|
// src/lib/config.ts
|
|
396
434
|
var config_exports = {};
|
|
397
435
|
__export(config_exports, {
|
|
@@ -408,8 +446,8 @@ __export(config_exports, {
|
|
|
408
446
|
migrateConfig: () => migrateConfig,
|
|
409
447
|
saveConfig: () => saveConfig
|
|
410
448
|
});
|
|
411
|
-
import { readFile, writeFile
|
|
412
|
-
import { readFileSync, existsSync, renameSync } from "fs";
|
|
449
|
+
import { readFile, writeFile } from "fs/promises";
|
|
450
|
+
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
413
451
|
import path from "path";
|
|
414
452
|
import os from "os";
|
|
415
453
|
function resolveDataDir() {
|
|
@@ -417,7 +455,7 @@ function resolveDataDir() {
|
|
|
417
455
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
418
456
|
const newDir = path.join(os.homedir(), ".exe-os");
|
|
419
457
|
const legacyDir = path.join(os.homedir(), ".exe-mem");
|
|
420
|
-
if (!
|
|
458
|
+
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
421
459
|
try {
|
|
422
460
|
renameSync(legacyDir, newDir);
|
|
423
461
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -480,9 +518,9 @@ function normalizeAutoUpdate(raw) {
|
|
|
480
518
|
}
|
|
481
519
|
async function loadConfig() {
|
|
482
520
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
483
|
-
await
|
|
521
|
+
await ensurePrivateDir(dir);
|
|
484
522
|
const configPath = path.join(dir, "config.json");
|
|
485
|
-
if (!
|
|
523
|
+
if (!existsSync2(configPath)) {
|
|
486
524
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
487
525
|
}
|
|
488
526
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -495,6 +533,7 @@ async function loadConfig() {
|
|
|
495
533
|
`);
|
|
496
534
|
try {
|
|
497
535
|
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
536
|
+
await enforcePrivateFile(configPath);
|
|
498
537
|
} catch {
|
|
499
538
|
}
|
|
500
539
|
}
|
|
@@ -513,7 +552,7 @@ async function loadConfig() {
|
|
|
513
552
|
function loadConfigSync() {
|
|
514
553
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
515
554
|
const configPath = path.join(dir, "config.json");
|
|
516
|
-
if (!
|
|
555
|
+
if (!existsSync2(configPath)) {
|
|
517
556
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
518
557
|
}
|
|
519
558
|
try {
|
|
@@ -531,12 +570,10 @@ function loadConfigSync() {
|
|
|
531
570
|
}
|
|
532
571
|
async function saveConfig(config2) {
|
|
533
572
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
534
|
-
await
|
|
573
|
+
await ensurePrivateDir(dir);
|
|
535
574
|
const configPath = path.join(dir, "config.json");
|
|
536
575
|
await writeFile(configPath, JSON.stringify(config2, null, 2) + "\n");
|
|
537
|
-
|
|
538
|
-
await chmod(configPath, 384);
|
|
539
|
-
}
|
|
576
|
+
await enforcePrivateFile(configPath);
|
|
540
577
|
}
|
|
541
578
|
async function loadConfigFrom(configPath) {
|
|
542
579
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -556,6 +593,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
|
|
|
556
593
|
var init_config = __esm({
|
|
557
594
|
"src/lib/config.ts"() {
|
|
558
595
|
"use strict";
|
|
596
|
+
init_secure_files();
|
|
559
597
|
EXE_AI_DIR = resolveDataDir();
|
|
560
598
|
DB_PATH = path.join(EXE_AI_DIR, "memories.db");
|
|
561
599
|
MODELS_DIR = path.join(EXE_AI_DIR, "models");
|
|
@@ -672,10 +710,10 @@ __export(agent_config_exports, {
|
|
|
672
710
|
saveAgentConfig: () => saveAgentConfig,
|
|
673
711
|
setAgentRuntime: () => setAgentRuntime
|
|
674
712
|
});
|
|
675
|
-
import { readFileSync as readFileSync2, writeFileSync, existsSync as
|
|
713
|
+
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
|
|
676
714
|
import path2 from "path";
|
|
677
715
|
function loadAgentConfig() {
|
|
678
|
-
if (!
|
|
716
|
+
if (!existsSync3(AGENT_CONFIG_PATH)) return {};
|
|
679
717
|
try {
|
|
680
718
|
return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
|
|
681
719
|
} catch {
|
|
@@ -684,8 +722,9 @@ function loadAgentConfig() {
|
|
|
684
722
|
}
|
|
685
723
|
function saveAgentConfig(config2) {
|
|
686
724
|
const dir = path2.dirname(AGENT_CONFIG_PATH);
|
|
687
|
-
|
|
725
|
+
ensurePrivateDirSync(dir);
|
|
688
726
|
writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config2, null, 2) + "\n", "utf-8");
|
|
727
|
+
enforcePrivateFileSync(AGENT_CONFIG_PATH);
|
|
689
728
|
}
|
|
690
729
|
function getAgentRuntime(agentId) {
|
|
691
730
|
const config2 = loadAgentConfig();
|
|
@@ -725,6 +764,7 @@ var init_agent_config = __esm({
|
|
|
725
764
|
"use strict";
|
|
726
765
|
init_config();
|
|
727
766
|
init_runtime_table();
|
|
767
|
+
init_secure_files();
|
|
728
768
|
AGENT_CONFIG_PATH = path2.join(EXE_AI_DIR, "agent-config.json");
|
|
729
769
|
KNOWN_RUNTIMES = {
|
|
730
770
|
claude: ["claude-opus-4", "claude-sonnet-4", "claude-haiku-4.5"],
|
|
@@ -772,7 +812,7 @@ __export(employees_exports, {
|
|
|
772
812
|
validateEmployeeName: () => validateEmployeeName
|
|
773
813
|
});
|
|
774
814
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
775
|
-
import { existsSync as
|
|
815
|
+
import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
776
816
|
import { execSync } from "child_process";
|
|
777
817
|
import path3 from "path";
|
|
778
818
|
import os2 from "os";
|
|
@@ -811,7 +851,7 @@ function validateEmployeeName(name) {
|
|
|
811
851
|
return { valid: true };
|
|
812
852
|
}
|
|
813
853
|
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
814
|
-
if (!
|
|
854
|
+
if (!existsSync4(employeesPath)) {
|
|
815
855
|
return [];
|
|
816
856
|
}
|
|
817
857
|
const raw = await readFile2(employeesPath, "utf-8");
|
|
@@ -826,7 +866,7 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
|
826
866
|
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
827
867
|
}
|
|
828
868
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
829
|
-
if (!
|
|
869
|
+
if (!existsSync4(employeesPath)) return [];
|
|
830
870
|
try {
|
|
831
871
|
return JSON.parse(readFileSync3(employeesPath, "utf-8"));
|
|
832
872
|
} catch {
|
|
@@ -874,7 +914,7 @@ function appendToCoordinatorTeam(employee) {
|
|
|
874
914
|
const coordinator = getCoordinatorEmployee(loadEmployeesSync());
|
|
875
915
|
if (!coordinator) return;
|
|
876
916
|
const idPath = path3.join(IDENTITY_DIR, `${coordinator.name}.md`);
|
|
877
|
-
if (!
|
|
917
|
+
if (!existsSync4(idPath)) return;
|
|
878
918
|
const content = readFileSync3(idPath, "utf-8");
|
|
879
919
|
if (content.includes(`**${capitalize(employee.name)}`)) return;
|
|
880
920
|
const teamMatch = content.match(TEAM_SECTION_RE);
|
|
@@ -928,9 +968,9 @@ async function normalizeRosterCase(rosterPath) {
|
|
|
928
968
|
const identityDir = path3.join(os2.homedir(), ".exe-os", "identity");
|
|
929
969
|
const oldPath = path3.join(identityDir, `${oldName}.md`);
|
|
930
970
|
const newPath = path3.join(identityDir, `${emp.name}.md`);
|
|
931
|
-
if (
|
|
971
|
+
if (existsSync4(oldPath) && !existsSync4(newPath)) {
|
|
932
972
|
renameSync2(oldPath, newPath);
|
|
933
|
-
} else if (
|
|
973
|
+
} else if (existsSync4(oldPath) && oldPath !== newPath) {
|
|
934
974
|
const content = readFileSync3(oldPath, "utf-8");
|
|
935
975
|
writeFileSync2(newPath, content, "utf-8");
|
|
936
976
|
if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
|
|
@@ -973,7 +1013,7 @@ function registerBinSymlinks(name) {
|
|
|
973
1013
|
for (const suffix of ["", "-opencode"]) {
|
|
974
1014
|
const linkName = `${name}${suffix}`;
|
|
975
1015
|
const linkPath = path3.join(binDir, linkName);
|
|
976
|
-
if (
|
|
1016
|
+
if (existsSync4(linkPath)) {
|
|
977
1017
|
skipped.push(linkName);
|
|
978
1018
|
continue;
|
|
979
1019
|
}
|
|
@@ -1926,6 +1966,7 @@ async function ensureSchema() {
|
|
|
1926
1966
|
project TEXT NOT NULL,
|
|
1927
1967
|
summary TEXT NOT NULL,
|
|
1928
1968
|
task_file TEXT,
|
|
1969
|
+
session_scope TEXT,
|
|
1929
1970
|
read INTEGER NOT NULL DEFAULT 0,
|
|
1930
1971
|
created_at TEXT NOT NULL
|
|
1931
1972
|
);
|
|
@@ -1934,7 +1975,7 @@ async function ensureSchema() {
|
|
|
1934
1975
|
ON notifications(read);
|
|
1935
1976
|
|
|
1936
1977
|
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
1937
|
-
ON notifications(agent_id);
|
|
1978
|
+
ON notifications(agent_id, session_scope);
|
|
1938
1979
|
|
|
1939
1980
|
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
1940
1981
|
ON notifications(task_file);
|
|
@@ -1972,6 +2013,7 @@ async function ensureSchema() {
|
|
|
1972
2013
|
target_agent TEXT NOT NULL,
|
|
1973
2014
|
target_project TEXT,
|
|
1974
2015
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
2016
|
+
session_scope TEXT,
|
|
1975
2017
|
content TEXT NOT NULL,
|
|
1976
2018
|
priority TEXT DEFAULT 'normal',
|
|
1977
2019
|
status TEXT DEFAULT 'pending',
|
|
@@ -1985,10 +2027,31 @@ async function ensureSchema() {
|
|
|
1985
2027
|
);
|
|
1986
2028
|
|
|
1987
2029
|
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
1988
|
-
ON messages(target_agent, status);
|
|
2030
|
+
ON messages(target_agent, session_scope, status);
|
|
1989
2031
|
|
|
1990
2032
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
1991
|
-
ON messages(target_agent, from_agent, server_seq);
|
|
2033
|
+
ON messages(target_agent, session_scope, from_agent, server_seq);
|
|
2034
|
+
`);
|
|
2035
|
+
try {
|
|
2036
|
+
await client.execute({
|
|
2037
|
+
sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
|
|
2038
|
+
args: []
|
|
2039
|
+
});
|
|
2040
|
+
} catch {
|
|
2041
|
+
}
|
|
2042
|
+
try {
|
|
2043
|
+
await client.execute({
|
|
2044
|
+
sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
|
|
2045
|
+
args: []
|
|
2046
|
+
});
|
|
2047
|
+
} catch {
|
|
2048
|
+
}
|
|
2049
|
+
await client.executeMultiple(`
|
|
2050
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
|
|
2051
|
+
ON notifications(agent_id, session_scope, read, created_at);
|
|
2052
|
+
|
|
2053
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
|
|
2054
|
+
ON messages(target_agent, session_scope, status, created_at);
|
|
1992
2055
|
`);
|
|
1993
2056
|
try {
|
|
1994
2057
|
await client.execute({
|
|
@@ -2572,6 +2635,13 @@ async function ensureSchema() {
|
|
|
2572
2635
|
} catch {
|
|
2573
2636
|
}
|
|
2574
2637
|
}
|
|
2638
|
+
try {
|
|
2639
|
+
await client.execute({
|
|
2640
|
+
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
2641
|
+
args: []
|
|
2642
|
+
});
|
|
2643
|
+
} catch {
|
|
2644
|
+
}
|
|
2575
2645
|
}
|
|
2576
2646
|
async function disposeDatabase() {
|
|
2577
2647
|
if (_walCheckpointTimer) {
|
|
@@ -2618,13 +2688,50 @@ var init_memory = __esm({
|
|
|
2618
2688
|
}
|
|
2619
2689
|
});
|
|
2620
2690
|
|
|
2691
|
+
// src/lib/daemon-auth.ts
|
|
2692
|
+
import crypto from "crypto";
|
|
2693
|
+
import path5 from "path";
|
|
2694
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
2695
|
+
function normalizeToken(token) {
|
|
2696
|
+
if (!token) return null;
|
|
2697
|
+
const trimmed = token.trim();
|
|
2698
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
2699
|
+
}
|
|
2700
|
+
function readDaemonToken() {
|
|
2701
|
+
try {
|
|
2702
|
+
if (!existsSync5(DAEMON_TOKEN_PATH)) return null;
|
|
2703
|
+
return normalizeToken(readFileSync4(DAEMON_TOKEN_PATH, "utf8"));
|
|
2704
|
+
} catch {
|
|
2705
|
+
return null;
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
function ensureDaemonToken(seed) {
|
|
2709
|
+
const existing = readDaemonToken();
|
|
2710
|
+
if (existing) return existing;
|
|
2711
|
+
const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
|
|
2712
|
+
ensurePrivateDirSync(EXE_AI_DIR);
|
|
2713
|
+
writeFileSync3(DAEMON_TOKEN_PATH, `${token}
|
|
2714
|
+
`, "utf8");
|
|
2715
|
+
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
2716
|
+
return token;
|
|
2717
|
+
}
|
|
2718
|
+
var DAEMON_TOKEN_PATH;
|
|
2719
|
+
var init_daemon_auth = __esm({
|
|
2720
|
+
"src/lib/daemon-auth.ts"() {
|
|
2721
|
+
"use strict";
|
|
2722
|
+
init_config();
|
|
2723
|
+
init_secure_files();
|
|
2724
|
+
DAEMON_TOKEN_PATH = path5.join(EXE_AI_DIR, "exed.token");
|
|
2725
|
+
}
|
|
2726
|
+
});
|
|
2727
|
+
|
|
2621
2728
|
// src/lib/exe-daemon-client.ts
|
|
2622
2729
|
import net from "net";
|
|
2623
2730
|
import os4 from "os";
|
|
2624
2731
|
import { spawn } from "child_process";
|
|
2625
2732
|
import { randomUUID } from "crypto";
|
|
2626
|
-
import { existsSync as
|
|
2627
|
-
import
|
|
2733
|
+
import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync5, openSync, closeSync, statSync } from "fs";
|
|
2734
|
+
import path6 from "path";
|
|
2628
2735
|
import { fileURLToPath } from "url";
|
|
2629
2736
|
function handleData(chunk) {
|
|
2630
2737
|
_buffer += chunk.toString();
|
|
@@ -2652,9 +2759,9 @@ function handleData(chunk) {
|
|
|
2652
2759
|
}
|
|
2653
2760
|
}
|
|
2654
2761
|
function cleanupStaleFiles() {
|
|
2655
|
-
if (
|
|
2762
|
+
if (existsSync6(PID_PATH)) {
|
|
2656
2763
|
try {
|
|
2657
|
-
const pid = parseInt(
|
|
2764
|
+
const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
|
|
2658
2765
|
if (pid > 0) {
|
|
2659
2766
|
try {
|
|
2660
2767
|
process.kill(pid, 0);
|
|
@@ -2675,11 +2782,11 @@ function cleanupStaleFiles() {
|
|
|
2675
2782
|
}
|
|
2676
2783
|
}
|
|
2677
2784
|
function findPackageRoot() {
|
|
2678
|
-
let dir =
|
|
2679
|
-
const { root } =
|
|
2785
|
+
let dir = path6.dirname(fileURLToPath(import.meta.url));
|
|
2786
|
+
const { root } = path6.parse(dir);
|
|
2680
2787
|
while (dir !== root) {
|
|
2681
|
-
if (
|
|
2682
|
-
dir =
|
|
2788
|
+
if (existsSync6(path6.join(dir, "package.json"))) return dir;
|
|
2789
|
+
dir = path6.dirname(dir);
|
|
2683
2790
|
}
|
|
2684
2791
|
return null;
|
|
2685
2792
|
}
|
|
@@ -2705,16 +2812,17 @@ function spawnDaemon() {
|
|
|
2705
2812
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
2706
2813
|
return;
|
|
2707
2814
|
}
|
|
2708
|
-
const daemonPath =
|
|
2709
|
-
if (!
|
|
2815
|
+
const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
2816
|
+
if (!existsSync6(daemonPath)) {
|
|
2710
2817
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
2711
2818
|
`);
|
|
2712
2819
|
return;
|
|
2713
2820
|
}
|
|
2714
2821
|
const resolvedPath = daemonPath;
|
|
2822
|
+
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
2715
2823
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
2716
2824
|
`);
|
|
2717
|
-
const logPath =
|
|
2825
|
+
const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
|
|
2718
2826
|
let stderrFd = "ignore";
|
|
2719
2827
|
try {
|
|
2720
2828
|
stderrFd = openSync(logPath, "a");
|
|
@@ -2732,7 +2840,8 @@ function spawnDaemon() {
|
|
|
2732
2840
|
TMUX_PANE: void 0,
|
|
2733
2841
|
// Prevents resolveExeSession() from scoping to one session
|
|
2734
2842
|
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
2735
|
-
EXE_DAEMON_PID: PID_PATH
|
|
2843
|
+
EXE_DAEMON_PID: PID_PATH,
|
|
2844
|
+
[DAEMON_TOKEN_ENV]: daemonToken
|
|
2736
2845
|
}
|
|
2737
2846
|
});
|
|
2738
2847
|
child.unref();
|
|
@@ -2842,13 +2951,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
2842
2951
|
return;
|
|
2843
2952
|
}
|
|
2844
2953
|
const id = randomUUID();
|
|
2954
|
+
const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
|
|
2845
2955
|
const timer = setTimeout(() => {
|
|
2846
2956
|
_pending.delete(id);
|
|
2847
2957
|
resolve({ error: "Request timeout" });
|
|
2848
2958
|
}, timeoutMs);
|
|
2849
2959
|
_pending.set(id, { resolve, timer });
|
|
2850
2960
|
try {
|
|
2851
|
-
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
2961
|
+
_socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
|
|
2852
2962
|
} catch {
|
|
2853
2963
|
clearTimeout(timer);
|
|
2854
2964
|
_pending.delete(id);
|
|
@@ -2877,9 +2987,9 @@ function killAndRespawnDaemon() {
|
|
|
2877
2987
|
}
|
|
2878
2988
|
try {
|
|
2879
2989
|
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
2880
|
-
if (
|
|
2990
|
+
if (existsSync6(PID_PATH)) {
|
|
2881
2991
|
try {
|
|
2882
|
-
const pid = parseInt(
|
|
2992
|
+
const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
|
|
2883
2993
|
if (pid > 0) {
|
|
2884
2994
|
try {
|
|
2885
2995
|
process.kill(pid, "SIGKILL");
|
|
@@ -2996,17 +3106,19 @@ function disconnectClient() {
|
|
|
2996
3106
|
entry.resolve({ error: "Client disconnected" });
|
|
2997
3107
|
}
|
|
2998
3108
|
}
|
|
2999
|
-
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, _consecutiveFailures, HEALTH_CHECK_INTERVAL, MAX_RETRIES_BEFORE_RESTART, RETRY_DELAYS_MS, MIN_DAEMON_AGE_MS, _pending, MAX_BUFFER;
|
|
3109
|
+
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, DAEMON_TOKEN_ENV, _socket, _connected, _buffer, _requestCount, _consecutiveFailures, HEALTH_CHECK_INTERVAL, MAX_RETRIES_BEFORE_RESTART, RETRY_DELAYS_MS, MIN_DAEMON_AGE_MS, _pending, MAX_BUFFER;
|
|
3000
3110
|
var init_exe_daemon_client = __esm({
|
|
3001
3111
|
"src/lib/exe-daemon-client.ts"() {
|
|
3002
3112
|
"use strict";
|
|
3003
3113
|
init_config();
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3114
|
+
init_daemon_auth();
|
|
3115
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
|
|
3116
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
|
|
3117
|
+
SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
3007
3118
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
3008
3119
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
3009
3120
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
3121
|
+
DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
|
|
3010
3122
|
_socket = null;
|
|
3011
3123
|
_connected = false;
|
|
3012
3124
|
_buffer = "";
|
|
@@ -3058,10 +3170,10 @@ async function disposeEmbedder() {
|
|
|
3058
3170
|
async function embedDirect(text) {
|
|
3059
3171
|
const llamaCpp = await import("node-llama-cpp");
|
|
3060
3172
|
const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
3061
|
-
const { existsSync:
|
|
3062
|
-
const
|
|
3063
|
-
const modelPath =
|
|
3064
|
-
if (!
|
|
3173
|
+
const { existsSync: existsSync19 } = await import("fs");
|
|
3174
|
+
const path23 = await import("path");
|
|
3175
|
+
const modelPath = path23.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
|
|
3176
|
+
if (!existsSync19(modelPath)) {
|
|
3065
3177
|
throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
|
|
3066
3178
|
}
|
|
3067
3179
|
const llama = await llamaCpp.getLlama();
|
|
@@ -3091,14 +3203,14 @@ var init_embedder = __esm({
|
|
|
3091
3203
|
|
|
3092
3204
|
// src/lib/keychain.ts
|
|
3093
3205
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
3094
|
-
import { existsSync as
|
|
3095
|
-
import
|
|
3206
|
+
import { existsSync as existsSync7 } from "fs";
|
|
3207
|
+
import path7 from "path";
|
|
3096
3208
|
import os5 from "os";
|
|
3097
3209
|
function getKeyDir() {
|
|
3098
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
3210
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path7.join(os5.homedir(), ".exe-os");
|
|
3099
3211
|
}
|
|
3100
3212
|
function getKeyPath() {
|
|
3101
|
-
return
|
|
3213
|
+
return path7.join(getKeyDir(), "master.key");
|
|
3102
3214
|
}
|
|
3103
3215
|
async function tryKeytar() {
|
|
3104
3216
|
try {
|
|
@@ -3119,7 +3231,7 @@ async function getMasterKey() {
|
|
|
3119
3231
|
}
|
|
3120
3232
|
}
|
|
3121
3233
|
const keyPath = getKeyPath();
|
|
3122
|
-
if (!
|
|
3234
|
+
if (!existsSync7(keyPath)) {
|
|
3123
3235
|
process.stderr.write(
|
|
3124
3236
|
`[keychain] Key not found at ${keyPath} (HOME=${os5.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
3125
3237
|
`
|
|
@@ -3151,6 +3263,7 @@ var shard_manager_exports = {};
|
|
|
3151
3263
|
__export(shard_manager_exports, {
|
|
3152
3264
|
disposeShards: () => disposeShards,
|
|
3153
3265
|
ensureShardSchema: () => ensureShardSchema,
|
|
3266
|
+
getOpenShardCount: () => getOpenShardCount,
|
|
3154
3267
|
getReadyShardClient: () => getReadyShardClient,
|
|
3155
3268
|
getShardClient: () => getShardClient,
|
|
3156
3269
|
getShardsDir: () => getShardsDir,
|
|
@@ -3159,15 +3272,18 @@ __export(shard_manager_exports, {
|
|
|
3159
3272
|
listShards: () => listShards,
|
|
3160
3273
|
shardExists: () => shardExists
|
|
3161
3274
|
});
|
|
3162
|
-
import
|
|
3163
|
-
import { existsSync as
|
|
3275
|
+
import path8 from "path";
|
|
3276
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync2, readdirSync } from "fs";
|
|
3164
3277
|
import { createClient as createClient2 } from "@libsql/client";
|
|
3165
3278
|
function initShardManager(encryptionKey) {
|
|
3166
3279
|
_encryptionKey = encryptionKey;
|
|
3167
|
-
if (!
|
|
3280
|
+
if (!existsSync8(SHARDS_DIR)) {
|
|
3168
3281
|
mkdirSync2(SHARDS_DIR, { recursive: true });
|
|
3169
3282
|
}
|
|
3170
3283
|
_shardingEnabled = true;
|
|
3284
|
+
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
3285
|
+
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
3286
|
+
_evictionTimer.unref();
|
|
3171
3287
|
}
|
|
3172
3288
|
function isShardingEnabled() {
|
|
3173
3289
|
return _shardingEnabled;
|
|
@@ -3184,21 +3300,28 @@ function getShardClient(projectName) {
|
|
|
3184
3300
|
throw new Error(`Invalid project name for shard: "${projectName}"`);
|
|
3185
3301
|
}
|
|
3186
3302
|
const cached = _shards.get(safeName);
|
|
3187
|
-
if (cached)
|
|
3188
|
-
|
|
3303
|
+
if (cached) {
|
|
3304
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
3305
|
+
return cached;
|
|
3306
|
+
}
|
|
3307
|
+
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
3308
|
+
evictLRU();
|
|
3309
|
+
}
|
|
3310
|
+
const dbPath = path8.join(SHARDS_DIR, `${safeName}.db`);
|
|
3189
3311
|
const client = createClient2({
|
|
3190
3312
|
url: `file:${dbPath}`,
|
|
3191
3313
|
encryptionKey: _encryptionKey
|
|
3192
3314
|
});
|
|
3193
3315
|
_shards.set(safeName, client);
|
|
3316
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
3194
3317
|
return client;
|
|
3195
3318
|
}
|
|
3196
3319
|
function shardExists(projectName) {
|
|
3197
3320
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
3198
|
-
return
|
|
3321
|
+
return existsSync8(path8.join(SHARDS_DIR, `${safeName}.db`));
|
|
3199
3322
|
}
|
|
3200
3323
|
function listShards() {
|
|
3201
|
-
if (!
|
|
3324
|
+
if (!existsSync8(SHARDS_DIR)) return [];
|
|
3202
3325
|
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
3203
3326
|
}
|
|
3204
3327
|
async function ensureShardSchema(client) {
|
|
@@ -3250,6 +3373,8 @@ async function ensureShardSchema(client) {
|
|
|
3250
3373
|
for (const col of [
|
|
3251
3374
|
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
3252
3375
|
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
3376
|
+
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
3377
|
+
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
3253
3378
|
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
3254
3379
|
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
3255
3380
|
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
@@ -3387,21 +3512,69 @@ async function getReadyShardClient(projectName) {
|
|
|
3387
3512
|
await ensureShardSchema(client);
|
|
3388
3513
|
return client;
|
|
3389
3514
|
}
|
|
3515
|
+
function evictLRU() {
|
|
3516
|
+
let oldest = null;
|
|
3517
|
+
let oldestTime = Infinity;
|
|
3518
|
+
for (const [name, time] of _shardLastAccess) {
|
|
3519
|
+
if (time < oldestTime) {
|
|
3520
|
+
oldestTime = time;
|
|
3521
|
+
oldest = name;
|
|
3522
|
+
}
|
|
3523
|
+
}
|
|
3524
|
+
if (oldest) {
|
|
3525
|
+
const client = _shards.get(oldest);
|
|
3526
|
+
if (client) {
|
|
3527
|
+
client.close();
|
|
3528
|
+
}
|
|
3529
|
+
_shards.delete(oldest);
|
|
3530
|
+
_shardLastAccess.delete(oldest);
|
|
3531
|
+
}
|
|
3532
|
+
}
|
|
3533
|
+
function evictIdleShards() {
|
|
3534
|
+
const now = Date.now();
|
|
3535
|
+
const toEvict = [];
|
|
3536
|
+
for (const [name, lastAccess] of _shardLastAccess) {
|
|
3537
|
+
if (now - lastAccess > SHARD_IDLE_MS) {
|
|
3538
|
+
toEvict.push(name);
|
|
3539
|
+
}
|
|
3540
|
+
}
|
|
3541
|
+
for (const name of toEvict) {
|
|
3542
|
+
const client = _shards.get(name);
|
|
3543
|
+
if (client) {
|
|
3544
|
+
client.close();
|
|
3545
|
+
}
|
|
3546
|
+
_shards.delete(name);
|
|
3547
|
+
_shardLastAccess.delete(name);
|
|
3548
|
+
}
|
|
3549
|
+
}
|
|
3550
|
+
function getOpenShardCount() {
|
|
3551
|
+
return _shards.size;
|
|
3552
|
+
}
|
|
3390
3553
|
function disposeShards() {
|
|
3554
|
+
if (_evictionTimer) {
|
|
3555
|
+
clearInterval(_evictionTimer);
|
|
3556
|
+
_evictionTimer = null;
|
|
3557
|
+
}
|
|
3391
3558
|
for (const [, client] of _shards) {
|
|
3392
3559
|
client.close();
|
|
3393
3560
|
}
|
|
3394
3561
|
_shards.clear();
|
|
3562
|
+
_shardLastAccess.clear();
|
|
3395
3563
|
_shardingEnabled = false;
|
|
3396
3564
|
_encryptionKey = null;
|
|
3397
3565
|
}
|
|
3398
|
-
var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
|
|
3566
|
+
var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
|
|
3399
3567
|
var init_shard_manager = __esm({
|
|
3400
3568
|
"src/lib/shard-manager.ts"() {
|
|
3401
3569
|
"use strict";
|
|
3402
3570
|
init_config();
|
|
3403
|
-
SHARDS_DIR =
|
|
3571
|
+
SHARDS_DIR = path8.join(EXE_AI_DIR, "shards");
|
|
3572
|
+
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
3573
|
+
MAX_OPEN_SHARDS = 10;
|
|
3574
|
+
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
3404
3575
|
_shards = /* @__PURE__ */ new Map();
|
|
3576
|
+
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
3577
|
+
_evictionTimer = null;
|
|
3405
3578
|
_encryptionKey = null;
|
|
3406
3579
|
_shardingEnabled = false;
|
|
3407
3580
|
}
|
|
@@ -4173,8 +4346,8 @@ __export(wiki_client_exports, {
|
|
|
4173
4346
|
listDocuments: () => listDocuments,
|
|
4174
4347
|
listWorkspaces: () => listWorkspaces
|
|
4175
4348
|
});
|
|
4176
|
-
async function wikiFetch(config2,
|
|
4177
|
-
const url = `${config2.baseUrl}/api/v1${
|
|
4349
|
+
async function wikiFetch(config2, path23, method = "GET", body) {
|
|
4350
|
+
const url = `${config2.baseUrl}/api/v1${path23}`;
|
|
4178
4351
|
const headers = {
|
|
4179
4352
|
Authorization: `Bearer ${config2.apiKey}`,
|
|
4180
4353
|
"Content-Type": "application/json"
|
|
@@ -4207,7 +4380,7 @@ async function wikiFetch(config2, path22, method = "GET", body) {
|
|
|
4207
4380
|
}
|
|
4208
4381
|
}
|
|
4209
4382
|
if (!response.ok) {
|
|
4210
|
-
throw new Error(`Wiki API ${method} ${
|
|
4383
|
+
throw new Error(`Wiki API ${method} ${path23}: ${response.status} ${response.statusText}`);
|
|
4211
4384
|
}
|
|
4212
4385
|
return response.json();
|
|
4213
4386
|
} finally {
|
|
@@ -4500,13 +4673,13 @@ __export(graph_rag_exports, {
|
|
|
4500
4673
|
resolveAlias: () => resolveAlias,
|
|
4501
4674
|
storeExtraction: () => storeExtraction
|
|
4502
4675
|
});
|
|
4503
|
-
import
|
|
4676
|
+
import crypto2 from "crypto";
|
|
4504
4677
|
function normalizeEntityName(name) {
|
|
4505
4678
|
return name.replace(/\s*\([^)]*\)\s*/g, "").trim().toLowerCase();
|
|
4506
4679
|
}
|
|
4507
4680
|
function entityId(name, type) {
|
|
4508
4681
|
const normalized = normalizeEntityName(name);
|
|
4509
|
-
return
|
|
4682
|
+
return crypto2.createHash("sha256").update(`${normalized}::${type.toLowerCase()}`).digest("hex").slice(0, 16);
|
|
4510
4683
|
}
|
|
4511
4684
|
async function resolveAlias(client, name) {
|
|
4512
4685
|
const normalized = normalizeEntityName(name);
|
|
@@ -4756,7 +4929,7 @@ async function storeExtraction(client, extraction, memoryId, timestamp) {
|
|
|
4756
4929
|
const targetAlias = await resolveAlias(client, r.target);
|
|
4757
4930
|
const sourceId = sourceAlias ?? entityId(r.source, r.sourceType);
|
|
4758
4931
|
const targetId = targetAlias ?? entityId(r.target, r.targetType);
|
|
4759
|
-
const relId =
|
|
4932
|
+
const relId = crypto2.randomUUID().slice(0, 16);
|
|
4760
4933
|
try {
|
|
4761
4934
|
await client.execute({
|
|
4762
4935
|
sql: `INSERT OR IGNORE INTO entities (id, name, type, first_seen, last_seen)
|
|
@@ -4819,7 +4992,7 @@ async function storeExtraction(client, extraction, memoryId, timestamp) {
|
|
|
4819
4992
|
}
|
|
4820
4993
|
}
|
|
4821
4994
|
for (const h of extraction.hyperedges) {
|
|
4822
|
-
const hId =
|
|
4995
|
+
const hId = crypto2.randomUUID().slice(0, 16);
|
|
4823
4996
|
try {
|
|
4824
4997
|
await client.execute({
|
|
4825
4998
|
sql: `INSERT OR IGNORE INTO hyperedges (id, label, relation, confidence, timestamp)
|
|
@@ -4883,7 +5056,7 @@ async function extractBatch(client, batchSize = 50, model = "claude-haiku-4-5-20
|
|
|
4883
5056
|
totalEntities += stored.entitiesStored;
|
|
4884
5057
|
totalRelationships += stored.relationshipsStored;
|
|
4885
5058
|
}
|
|
4886
|
-
const contentHash =
|
|
5059
|
+
const contentHash = crypto2.createHash("sha256").update(rawContent).digest("hex").slice(0, 32);
|
|
4887
5060
|
await client.execute({
|
|
4888
5061
|
sql: "UPDATE memories SET graph_extracted = 1, content_hash = ?, graph_extracted_hash = ? WHERE id = ?",
|
|
4889
5062
|
args: [contentHash, contentHash, memoryId]
|
|
@@ -5236,9 +5409,12 @@ __export(license_exports, {
|
|
|
5236
5409
|
stopLicenseRevalidation: () => stopLicenseRevalidation,
|
|
5237
5410
|
validateLicense: () => validateLicense
|
|
5238
5411
|
});
|
|
5239
|
-
import { readFileSync as
|
|
5412
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync9, mkdirSync as mkdirSync3 } from "fs";
|
|
5240
5413
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
5241
|
-
import
|
|
5414
|
+
import { createRequire as createRequire2 } from "module";
|
|
5415
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
5416
|
+
import os6 from "os";
|
|
5417
|
+
import path9 from "path";
|
|
5242
5418
|
import { jwtVerify, importSPKI } from "jose";
|
|
5243
5419
|
async function fetchRetry(url, init) {
|
|
5244
5420
|
try {
|
|
@@ -5249,37 +5425,37 @@ async function fetchRetry(url, init) {
|
|
|
5249
5425
|
}
|
|
5250
5426
|
}
|
|
5251
5427
|
function loadDeviceId() {
|
|
5252
|
-
const deviceJsonPath =
|
|
5428
|
+
const deviceJsonPath = path9.join(EXE_AI_DIR, "device.json");
|
|
5253
5429
|
try {
|
|
5254
|
-
if (
|
|
5255
|
-
const data = JSON.parse(
|
|
5430
|
+
if (existsSync9(deviceJsonPath)) {
|
|
5431
|
+
const data = JSON.parse(readFileSync6(deviceJsonPath, "utf8"));
|
|
5256
5432
|
if (data.deviceId) return data.deviceId;
|
|
5257
5433
|
}
|
|
5258
5434
|
} catch {
|
|
5259
5435
|
}
|
|
5260
5436
|
try {
|
|
5261
|
-
if (
|
|
5262
|
-
const id2 =
|
|
5437
|
+
if (existsSync9(DEVICE_ID_PATH)) {
|
|
5438
|
+
const id2 = readFileSync6(DEVICE_ID_PATH, "utf8").trim();
|
|
5263
5439
|
if (id2) return id2;
|
|
5264
5440
|
}
|
|
5265
5441
|
} catch {
|
|
5266
5442
|
}
|
|
5267
5443
|
const id = randomUUID3();
|
|
5268
5444
|
mkdirSync3(EXE_AI_DIR, { recursive: true });
|
|
5269
|
-
|
|
5445
|
+
writeFileSync4(DEVICE_ID_PATH, id, "utf8");
|
|
5270
5446
|
return id;
|
|
5271
5447
|
}
|
|
5272
5448
|
function loadLicense() {
|
|
5273
5449
|
try {
|
|
5274
|
-
if (!
|
|
5275
|
-
return
|
|
5450
|
+
if (!existsSync9(LICENSE_PATH)) return null;
|
|
5451
|
+
return readFileSync6(LICENSE_PATH, "utf8").trim();
|
|
5276
5452
|
} catch {
|
|
5277
5453
|
return null;
|
|
5278
5454
|
}
|
|
5279
5455
|
}
|
|
5280
5456
|
function saveLicense(apiKey) {
|
|
5281
5457
|
mkdirSync3(EXE_AI_DIR, { recursive: true });
|
|
5282
|
-
|
|
5458
|
+
writeFileSync4(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
|
|
5283
5459
|
}
|
|
5284
5460
|
async function verifyLicenseJwt(token) {
|
|
5285
5461
|
try {
|
|
@@ -5305,8 +5481,8 @@ async function verifyLicenseJwt(token) {
|
|
|
5305
5481
|
}
|
|
5306
5482
|
async function getCachedLicense() {
|
|
5307
5483
|
try {
|
|
5308
|
-
if (!
|
|
5309
|
-
const raw = JSON.parse(
|
|
5484
|
+
if (!existsSync9(CACHE_PATH)) return null;
|
|
5485
|
+
const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
|
|
5310
5486
|
if (!raw.token || typeof raw.token !== "string") return null;
|
|
5311
5487
|
return await verifyLicenseJwt(raw.token);
|
|
5312
5488
|
} catch {
|
|
@@ -5315,8 +5491,8 @@ async function getCachedLicense() {
|
|
|
5315
5491
|
}
|
|
5316
5492
|
function readCachedToken() {
|
|
5317
5493
|
try {
|
|
5318
|
-
if (!
|
|
5319
|
-
const raw = JSON.parse(
|
|
5494
|
+
if (!existsSync9(CACHE_PATH)) return null;
|
|
5495
|
+
const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
|
|
5320
5496
|
return typeof raw.token === "string" ? raw.token : null;
|
|
5321
5497
|
} catch {
|
|
5322
5498
|
return null;
|
|
@@ -5350,57 +5526,131 @@ function getRawCachedPlan() {
|
|
|
5350
5526
|
}
|
|
5351
5527
|
function cacheResponse(token) {
|
|
5352
5528
|
try {
|
|
5353
|
-
|
|
5529
|
+
writeFileSync4(CACHE_PATH, JSON.stringify({ token }), "utf8");
|
|
5354
5530
|
} catch {
|
|
5355
5531
|
}
|
|
5356
5532
|
}
|
|
5357
|
-
|
|
5358
|
-
|
|
5533
|
+
function loadPrismaForLicense() {
|
|
5534
|
+
if (_prismaFailed) return null;
|
|
5535
|
+
const dbUrl = process.env.DATABASE_URL;
|
|
5536
|
+
if (!dbUrl) {
|
|
5537
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path9.join(os6.homedir(), "exe-db");
|
|
5538
|
+
if (!existsSync9(path9.join(exeDbRoot, "package.json"))) {
|
|
5539
|
+
_prismaFailed = true;
|
|
5540
|
+
return null;
|
|
5541
|
+
}
|
|
5542
|
+
}
|
|
5543
|
+
if (!_prismaPromise) {
|
|
5544
|
+
_prismaPromise = (async () => {
|
|
5545
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
5546
|
+
if (explicitPath) {
|
|
5547
|
+
const mod2 = await import(pathToFileURL2(explicitPath).href);
|
|
5548
|
+
const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
|
|
5549
|
+
if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
|
|
5550
|
+
return new Ctor2();
|
|
5551
|
+
}
|
|
5552
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path9.join(os6.homedir(), "exe-db");
|
|
5553
|
+
const req = createRequire2(path9.join(exeDbRoot, "package.json"));
|
|
5554
|
+
const entry = req.resolve("@prisma/client");
|
|
5555
|
+
const mod = await import(pathToFileURL2(entry).href);
|
|
5556
|
+
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
5557
|
+
if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
|
|
5558
|
+
return new Ctor();
|
|
5559
|
+
})().catch((err) => {
|
|
5560
|
+
_prismaFailed = true;
|
|
5561
|
+
_prismaPromise = null;
|
|
5562
|
+
throw err;
|
|
5563
|
+
});
|
|
5564
|
+
}
|
|
5565
|
+
return _prismaPromise;
|
|
5566
|
+
}
|
|
5567
|
+
async function validateViaPostgres(apiKey) {
|
|
5568
|
+
const loader = loadPrismaForLicense();
|
|
5569
|
+
if (!loader) return null;
|
|
5570
|
+
try {
|
|
5571
|
+
const prisma = await loader;
|
|
5572
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
5573
|
+
`SELECT plan, email, status, device_limit, employee_limit, memory_limit, expires_at
|
|
5574
|
+
FROM billing.licenses WHERE key = $1 LIMIT 1`,
|
|
5575
|
+
apiKey
|
|
5576
|
+
);
|
|
5577
|
+
if (!rows || rows.length === 0) return null;
|
|
5578
|
+
const row = rows[0];
|
|
5579
|
+
if (row.status !== "active") return null;
|
|
5580
|
+
if (row.expires_at && new Date(row.expires_at) < /* @__PURE__ */ new Date()) return null;
|
|
5581
|
+
const plan = row.plan;
|
|
5582
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
5583
|
+
return {
|
|
5584
|
+
valid: true,
|
|
5585
|
+
plan,
|
|
5586
|
+
email: row.email,
|
|
5587
|
+
expiresAt: row.expires_at ? new Date(row.expires_at).toISOString() : null,
|
|
5588
|
+
deviceLimit: row.device_limit ?? limits.devices,
|
|
5589
|
+
employeeLimit: row.employee_limit ?? limits.employees,
|
|
5590
|
+
memoryLimit: row.memory_limit ?? limits.memories
|
|
5591
|
+
};
|
|
5592
|
+
} catch {
|
|
5593
|
+
return null;
|
|
5594
|
+
}
|
|
5595
|
+
}
|
|
5596
|
+
async function validateViaCFWorker(apiKey, deviceId) {
|
|
5359
5597
|
try {
|
|
5360
5598
|
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
5361
5599
|
method: "POST",
|
|
5362
5600
|
headers: { "Content-Type": "application/json" },
|
|
5363
|
-
body: JSON.stringify({ apiKey, deviceId
|
|
5601
|
+
body: JSON.stringify({ apiKey, deviceId }),
|
|
5364
5602
|
signal: AbortSignal.timeout(1e4)
|
|
5365
5603
|
});
|
|
5366
|
-
if (res.ok)
|
|
5367
|
-
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
|
|
5371
|
-
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
}
|
|
5375
|
-
if (data.token) {
|
|
5376
|
-
cacheResponse(data.token);
|
|
5377
|
-
const verified = await verifyLicenseJwt(data.token);
|
|
5378
|
-
if (verified) return verified;
|
|
5379
|
-
}
|
|
5380
|
-
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
5381
|
-
return {
|
|
5382
|
-
valid: data.valid,
|
|
5383
|
-
plan: data.plan,
|
|
5384
|
-
email: data.email,
|
|
5385
|
-
expiresAt: data.expiresAt,
|
|
5386
|
-
deviceLimit: limits.devices,
|
|
5387
|
-
employeeLimit: limits.employees,
|
|
5388
|
-
memoryLimit: limits.memories
|
|
5389
|
-
};
|
|
5604
|
+
if (!res.ok) return null;
|
|
5605
|
+
const data = await res.json();
|
|
5606
|
+
if (data.error === "device_limit_exceeded") return null;
|
|
5607
|
+
if (!data.valid) return null;
|
|
5608
|
+
if (data.token) {
|
|
5609
|
+
cacheResponse(data.token);
|
|
5610
|
+
const verified = await verifyLicenseJwt(data.token);
|
|
5611
|
+
if (verified) return verified;
|
|
5390
5612
|
}
|
|
5391
|
-
const
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5613
|
+
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
5614
|
+
return {
|
|
5615
|
+
valid: data.valid,
|
|
5616
|
+
plan: data.plan,
|
|
5617
|
+
email: data.email,
|
|
5618
|
+
expiresAt: data.expiresAt,
|
|
5619
|
+
deviceLimit: limits.devices,
|
|
5620
|
+
employeeLimit: limits.employees,
|
|
5621
|
+
memoryLimit: limits.memories
|
|
5622
|
+
};
|
|
5396
5623
|
} catch {
|
|
5397
|
-
|
|
5398
|
-
if (cached) return cached;
|
|
5399
|
-
const rawFallback = getRawCachedPlan();
|
|
5400
|
-
if (rawFallback) return rawFallback;
|
|
5401
|
-
return { ...FREE_LICENSE, valid: false, error: "offline" };
|
|
5624
|
+
return null;
|
|
5402
5625
|
}
|
|
5403
5626
|
}
|
|
5627
|
+
async function validateLicense(apiKey, deviceId) {
|
|
5628
|
+
const did = deviceId ?? loadDeviceId();
|
|
5629
|
+
const pgResult = await validateViaPostgres(apiKey);
|
|
5630
|
+
if (pgResult) {
|
|
5631
|
+
try {
|
|
5632
|
+
writeFileSync4(CACHE_PATH, JSON.stringify({ pgLicense: pgResult, ts: Date.now() }), "utf8");
|
|
5633
|
+
} catch {
|
|
5634
|
+
}
|
|
5635
|
+
return pgResult;
|
|
5636
|
+
}
|
|
5637
|
+
const cfResult = await validateViaCFWorker(apiKey, did);
|
|
5638
|
+
if (cfResult) return cfResult;
|
|
5639
|
+
const cached = await getCachedLicense();
|
|
5640
|
+
if (cached) return cached;
|
|
5641
|
+
try {
|
|
5642
|
+
if (existsSync9(CACHE_PATH)) {
|
|
5643
|
+
const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
|
|
5644
|
+
if (raw.pgLicense && raw.ts && Date.now() - raw.ts < 7 * 24 * 60 * 60 * 1e3) {
|
|
5645
|
+
return raw.pgLicense;
|
|
5646
|
+
}
|
|
5647
|
+
}
|
|
5648
|
+
} catch {
|
|
5649
|
+
}
|
|
5650
|
+
const rawFallback = getRawCachedPlan();
|
|
5651
|
+
if (rawFallback) return rawFallback;
|
|
5652
|
+
return { ...FREE_LICENSE, valid: false };
|
|
5653
|
+
}
|
|
5404
5654
|
function getCacheAgeMs() {
|
|
5405
5655
|
try {
|
|
5406
5656
|
const { statSync: statSync2 } = __require("fs");
|
|
@@ -5414,9 +5664,9 @@ async function checkLicense() {
|
|
|
5414
5664
|
let key = loadLicense();
|
|
5415
5665
|
if (!key) {
|
|
5416
5666
|
try {
|
|
5417
|
-
const configPath =
|
|
5418
|
-
if (
|
|
5419
|
-
const raw = JSON.parse(
|
|
5667
|
+
const configPath = path9.join(EXE_AI_DIR, "config.json");
|
|
5668
|
+
if (existsSync9(configPath)) {
|
|
5669
|
+
const raw = JSON.parse(readFileSync6(configPath, "utf8"));
|
|
5420
5670
|
const cloud = raw.cloud;
|
|
5421
5671
|
if (cloud?.apiKey) {
|
|
5422
5672
|
key = cloud.apiKey;
|
|
@@ -5570,14 +5820,14 @@ function stopLicenseRevalidation() {
|
|
|
5570
5820
|
_revalTimer = null;
|
|
5571
5821
|
}
|
|
5572
5822
|
}
|
|
5573
|
-
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, RETRY_DELAY_MS, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, CACHE_MAX_AGE_MS, _revalTimer;
|
|
5823
|
+
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, RETRY_DELAY_MS, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, _prismaPromise, _prismaFailed, CACHE_MAX_AGE_MS, _revalTimer;
|
|
5574
5824
|
var init_license = __esm({
|
|
5575
5825
|
"src/lib/license.ts"() {
|
|
5576
5826
|
"use strict";
|
|
5577
5827
|
init_config();
|
|
5578
|
-
LICENSE_PATH =
|
|
5579
|
-
CACHE_PATH =
|
|
5580
|
-
DEVICE_ID_PATH =
|
|
5828
|
+
LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
|
|
5829
|
+
CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
|
|
5830
|
+
DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
|
|
5581
5831
|
API_BASE = "https://askexe.com/cloud";
|
|
5582
5832
|
RETRY_DELAY_MS = 500;
|
|
5583
5833
|
LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
@@ -5601,6 +5851,8 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
|
5601
5851
|
employeeLimit: 1,
|
|
5602
5852
|
memoryLimit: 5e3
|
|
5603
5853
|
};
|
|
5854
|
+
_prismaPromise = null;
|
|
5855
|
+
_prismaFailed = false;
|
|
5604
5856
|
CACHE_MAX_AGE_MS = 36e5;
|
|
5605
5857
|
_revalTimer = null;
|
|
5606
5858
|
}
|
|
@@ -6475,16 +6727,16 @@ __export(imessage_exports, {
|
|
|
6475
6727
|
});
|
|
6476
6728
|
import { execFile } from "child_process";
|
|
6477
6729
|
import { promisify } from "util";
|
|
6478
|
-
import
|
|
6479
|
-
import
|
|
6730
|
+
import os7 from "os";
|
|
6731
|
+
import path10 from "path";
|
|
6480
6732
|
var execFileAsync, POLL_INTERVAL_MS, MESSAGES_DB_PATH, IMessageAdapter;
|
|
6481
6733
|
var init_imessage = __esm({
|
|
6482
6734
|
"src/gateway/adapters/imessage.ts"() {
|
|
6483
6735
|
"use strict";
|
|
6484
6736
|
execFileAsync = promisify(execFile);
|
|
6485
6737
|
POLL_INTERVAL_MS = 5e3;
|
|
6486
|
-
MESSAGES_DB_PATH =
|
|
6487
|
-
process.env.HOME ??
|
|
6738
|
+
MESSAGES_DB_PATH = path10.join(
|
|
6739
|
+
process.env.HOME ?? os7.homedir(),
|
|
6488
6740
|
"Library/Messages/chat.db"
|
|
6489
6741
|
);
|
|
6490
6742
|
IMessageAdapter = class {
|
|
@@ -6797,9 +7049,9 @@ __export(webhook_exports, {
|
|
|
6797
7049
|
WebhookAdapter: () => WebhookAdapter
|
|
6798
7050
|
});
|
|
6799
7051
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
6800
|
-
function resolvePath(obj,
|
|
7052
|
+
function resolvePath(obj, path23) {
|
|
6801
7053
|
let current = obj;
|
|
6802
|
-
for (const segment of
|
|
7054
|
+
for (const segment of path23.split(".")) {
|
|
6803
7055
|
if (current == null || typeof current !== "object") return void 0;
|
|
6804
7056
|
current = current[segment];
|
|
6805
7057
|
}
|
|
@@ -6902,13 +7154,13 @@ __export(whatsapp_accounts_exports, {
|
|
|
6902
7154
|
getDefaultAccount: () => getDefaultAccount,
|
|
6903
7155
|
loadAccounts: () => loadAccounts
|
|
6904
7156
|
});
|
|
6905
|
-
import { readFileSync as
|
|
7157
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
6906
7158
|
import { join as join2 } from "path";
|
|
6907
7159
|
import { homedir as homedir2 } from "os";
|
|
6908
7160
|
function loadAccounts() {
|
|
6909
7161
|
if (cachedAccounts !== null) return cachedAccounts;
|
|
6910
7162
|
try {
|
|
6911
|
-
const raw =
|
|
7163
|
+
const raw = readFileSync7(CONFIG_PATH2, "utf8");
|
|
6912
7164
|
const parsed = JSON.parse(raw);
|
|
6913
7165
|
if (!Array.isArray(parsed)) {
|
|
6914
7166
|
console.warn("[whatsapp] Config is not an array, ignoring");
|
|
@@ -6948,12 +7200,12 @@ var init_whatsapp_accounts = __esm({
|
|
|
6948
7200
|
});
|
|
6949
7201
|
|
|
6950
7202
|
// src/lib/session-registry.ts
|
|
6951
|
-
import { readFileSync as
|
|
6952
|
-
import
|
|
6953
|
-
import
|
|
7203
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, existsSync as existsSync10 } from "fs";
|
|
7204
|
+
import path11 from "path";
|
|
7205
|
+
import os8 from "os";
|
|
6954
7206
|
function registerSession(entry) {
|
|
6955
|
-
const dir =
|
|
6956
|
-
if (!
|
|
7207
|
+
const dir = path11.dirname(REGISTRY_PATH);
|
|
7208
|
+
if (!existsSync10(dir)) {
|
|
6957
7209
|
mkdirSync5(dir, { recursive: true });
|
|
6958
7210
|
}
|
|
6959
7211
|
const sessions = listSessions();
|
|
@@ -6963,11 +7215,11 @@ function registerSession(entry) {
|
|
|
6963
7215
|
} else {
|
|
6964
7216
|
sessions.push(entry);
|
|
6965
7217
|
}
|
|
6966
|
-
|
|
7218
|
+
writeFileSync5(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
|
|
6967
7219
|
}
|
|
6968
7220
|
function listSessions() {
|
|
6969
7221
|
try {
|
|
6970
|
-
const raw =
|
|
7222
|
+
const raw = readFileSync8(REGISTRY_PATH, "utf8");
|
|
6971
7223
|
return JSON.parse(raw);
|
|
6972
7224
|
} catch {
|
|
6973
7225
|
return [];
|
|
@@ -6977,7 +7229,7 @@ var REGISTRY_PATH;
|
|
|
6977
7229
|
var init_session_registry = __esm({
|
|
6978
7230
|
"src/lib/session-registry.ts"() {
|
|
6979
7231
|
"use strict";
|
|
6980
|
-
REGISTRY_PATH =
|
|
7232
|
+
REGISTRY_PATH = path11.join(os8.homedir(), ".exe-os", "session-registry.json");
|
|
6981
7233
|
}
|
|
6982
7234
|
});
|
|
6983
7235
|
|
|
@@ -7238,17 +7490,17 @@ __export(intercom_queue_exports, {
|
|
|
7238
7490
|
queueIntercom: () => queueIntercom,
|
|
7239
7491
|
readQueue: () => readQueue
|
|
7240
7492
|
});
|
|
7241
|
-
import { readFileSync as
|
|
7242
|
-
import
|
|
7243
|
-
import
|
|
7493
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, renameSync as renameSync3, existsSync as existsSync11, mkdirSync as mkdirSync6 } from "fs";
|
|
7494
|
+
import path12 from "path";
|
|
7495
|
+
import os9 from "os";
|
|
7244
7496
|
function ensureDir() {
|
|
7245
|
-
const dir =
|
|
7246
|
-
if (!
|
|
7497
|
+
const dir = path12.dirname(QUEUE_PATH);
|
|
7498
|
+
if (!existsSync11(dir)) mkdirSync6(dir, { recursive: true });
|
|
7247
7499
|
}
|
|
7248
7500
|
function readQueue() {
|
|
7249
7501
|
try {
|
|
7250
|
-
if (!
|
|
7251
|
-
return JSON.parse(
|
|
7502
|
+
if (!existsSync11(QUEUE_PATH)) return [];
|
|
7503
|
+
return JSON.parse(readFileSync9(QUEUE_PATH, "utf8"));
|
|
7252
7504
|
} catch {
|
|
7253
7505
|
return [];
|
|
7254
7506
|
}
|
|
@@ -7256,7 +7508,7 @@ function readQueue() {
|
|
|
7256
7508
|
function writeQueue(queue) {
|
|
7257
7509
|
ensureDir();
|
|
7258
7510
|
const tmp = `${QUEUE_PATH}.tmp`;
|
|
7259
|
-
|
|
7511
|
+
writeFileSync6(tmp, JSON.stringify(queue, null, 2));
|
|
7260
7512
|
renameSync3(tmp, QUEUE_PATH);
|
|
7261
7513
|
}
|
|
7262
7514
|
function queueIntercom(targetSession, reason) {
|
|
@@ -7348,20 +7600,20 @@ var QUEUE_PATH, MAX_RETRIES2, TTL_MS, INTERCOM_LOG;
|
|
|
7348
7600
|
var init_intercom_queue = __esm({
|
|
7349
7601
|
"src/lib/intercom-queue.ts"() {
|
|
7350
7602
|
"use strict";
|
|
7351
|
-
QUEUE_PATH =
|
|
7603
|
+
QUEUE_PATH = path12.join(os9.homedir(), ".exe-os", "intercom-queue.json");
|
|
7352
7604
|
MAX_RETRIES2 = 5;
|
|
7353
7605
|
TTL_MS = 60 * 60 * 1e3;
|
|
7354
|
-
INTERCOM_LOG =
|
|
7606
|
+
INTERCOM_LOG = path12.join(os9.homedir(), ".exe-os", "intercom.log");
|
|
7355
7607
|
}
|
|
7356
7608
|
});
|
|
7357
7609
|
|
|
7358
7610
|
// src/lib/plan-limits.ts
|
|
7359
|
-
import { readFileSync as
|
|
7360
|
-
import
|
|
7611
|
+
import { readFileSync as readFileSync10, existsSync as existsSync12 } from "fs";
|
|
7612
|
+
import path13 from "path";
|
|
7361
7613
|
function getLicenseSync() {
|
|
7362
7614
|
try {
|
|
7363
|
-
if (!
|
|
7364
|
-
const raw = JSON.parse(
|
|
7615
|
+
if (!existsSync12(CACHE_PATH2)) return freeLicense();
|
|
7616
|
+
const raw = JSON.parse(readFileSync10(CACHE_PATH2, "utf8"));
|
|
7365
7617
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
7366
7618
|
const parts = raw.token.split(".");
|
|
7367
7619
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -7399,8 +7651,8 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
7399
7651
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
7400
7652
|
let count = 0;
|
|
7401
7653
|
try {
|
|
7402
|
-
if (
|
|
7403
|
-
const raw =
|
|
7654
|
+
if (existsSync12(filePath)) {
|
|
7655
|
+
const raw = readFileSync10(filePath, "utf8");
|
|
7404
7656
|
const employees = JSON.parse(raw);
|
|
7405
7657
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
7406
7658
|
}
|
|
@@ -7429,29 +7681,63 @@ var init_plan_limits = __esm({
|
|
|
7429
7681
|
this.name = "PlanLimitError";
|
|
7430
7682
|
}
|
|
7431
7683
|
};
|
|
7432
|
-
CACHE_PATH2 =
|
|
7684
|
+
CACHE_PATH2 = path13.join(EXE_AI_DIR, "license-cache.json");
|
|
7685
|
+
}
|
|
7686
|
+
});
|
|
7687
|
+
|
|
7688
|
+
// src/lib/task-scope.ts
|
|
7689
|
+
function getCurrentSessionScope() {
|
|
7690
|
+
try {
|
|
7691
|
+
return resolveExeSession();
|
|
7692
|
+
} catch {
|
|
7693
|
+
return null;
|
|
7694
|
+
}
|
|
7695
|
+
}
|
|
7696
|
+
function sessionScopeFilter(sessionScope, tableAlias) {
|
|
7697
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
7698
|
+
if (!scope) return { sql: "", args: [] };
|
|
7699
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
7700
|
+
return {
|
|
7701
|
+
sql: ` AND (${col} IS NULL OR ${col} = ?)`,
|
|
7702
|
+
args: [scope]
|
|
7703
|
+
};
|
|
7704
|
+
}
|
|
7705
|
+
function strictSessionScopeFilter(sessionScope, tableAlias) {
|
|
7706
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
7707
|
+
if (!scope) return { sql: "", args: [] };
|
|
7708
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
7709
|
+
return {
|
|
7710
|
+
sql: ` AND ${col} = ?`,
|
|
7711
|
+
args: [scope]
|
|
7712
|
+
};
|
|
7713
|
+
}
|
|
7714
|
+
var init_task_scope = __esm({
|
|
7715
|
+
"src/lib/task-scope.ts"() {
|
|
7716
|
+
"use strict";
|
|
7717
|
+
init_tmux_routing();
|
|
7433
7718
|
}
|
|
7434
7719
|
});
|
|
7435
7720
|
|
|
7436
7721
|
// src/lib/notifications.ts
|
|
7437
|
-
import
|
|
7438
|
-
import
|
|
7439
|
-
import
|
|
7722
|
+
import crypto4 from "crypto";
|
|
7723
|
+
import path14 from "path";
|
|
7724
|
+
import os10 from "os";
|
|
7440
7725
|
import {
|
|
7441
|
-
readFileSync as
|
|
7726
|
+
readFileSync as readFileSync11,
|
|
7442
7727
|
readdirSync as readdirSync2,
|
|
7443
7728
|
unlinkSync as unlinkSync3,
|
|
7444
|
-
existsSync as
|
|
7729
|
+
existsSync as existsSync13,
|
|
7445
7730
|
rmdirSync
|
|
7446
7731
|
} from "fs";
|
|
7447
7732
|
async function writeNotification(notification) {
|
|
7448
7733
|
try {
|
|
7449
7734
|
const client = getClient();
|
|
7450
|
-
const id =
|
|
7735
|
+
const id = crypto4.randomUUID();
|
|
7451
7736
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7737
|
+
const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
|
|
7452
7738
|
await client.execute({
|
|
7453
|
-
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
|
|
7454
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
7739
|
+
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
7740
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
7455
7741
|
args: [
|
|
7456
7742
|
id,
|
|
7457
7743
|
notification.agentId,
|
|
@@ -7460,6 +7746,7 @@ async function writeNotification(notification) {
|
|
|
7460
7746
|
notification.project,
|
|
7461
7747
|
notification.summary,
|
|
7462
7748
|
notification.taskFile ?? null,
|
|
7749
|
+
sessionScope,
|
|
7463
7750
|
now
|
|
7464
7751
|
]
|
|
7465
7752
|
});
|
|
@@ -7468,12 +7755,14 @@ async function writeNotification(notification) {
|
|
|
7468
7755
|
`);
|
|
7469
7756
|
}
|
|
7470
7757
|
}
|
|
7471
|
-
async function markAsReadByTaskFile(taskFile) {
|
|
7758
|
+
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
7472
7759
|
try {
|
|
7473
7760
|
const client = getClient();
|
|
7761
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
7474
7762
|
await client.execute({
|
|
7475
|
-
sql:
|
|
7476
|
-
|
|
7763
|
+
sql: `UPDATE notifications SET read = 1
|
|
7764
|
+
WHERE task_file = ? AND read = 0${scope.sql}`,
|
|
7765
|
+
args: [taskFile, ...scope.args]
|
|
7477
7766
|
});
|
|
7478
7767
|
} catch {
|
|
7479
7768
|
}
|
|
@@ -7482,11 +7771,12 @@ var init_notifications = __esm({
|
|
|
7482
7771
|
"src/lib/notifications.ts"() {
|
|
7483
7772
|
"use strict";
|
|
7484
7773
|
init_database();
|
|
7774
|
+
init_task_scope();
|
|
7485
7775
|
}
|
|
7486
7776
|
});
|
|
7487
7777
|
|
|
7488
7778
|
// src/lib/session-kill-telemetry.ts
|
|
7489
|
-
import
|
|
7779
|
+
import crypto5 from "crypto";
|
|
7490
7780
|
async function recordSessionKill(input) {
|
|
7491
7781
|
try {
|
|
7492
7782
|
const client = getClient();
|
|
@@ -7496,7 +7786,7 @@ async function recordSessionKill(input) {
|
|
|
7496
7786
|
ticks_idle, estimated_tokens_saved)
|
|
7497
7787
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
7498
7788
|
args: [
|
|
7499
|
-
|
|
7789
|
+
crypto5.randomUUID(),
|
|
7500
7790
|
input.sessionName,
|
|
7501
7791
|
input.agentId,
|
|
7502
7792
|
(/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -7519,37 +7809,117 @@ var init_session_kill_telemetry = __esm({
|
|
|
7519
7809
|
}
|
|
7520
7810
|
});
|
|
7521
7811
|
|
|
7522
|
-
// src/lib/
|
|
7523
|
-
|
|
7812
|
+
// src/lib/project-name.ts
|
|
7813
|
+
import { execSync as execSync4 } from "child_process";
|
|
7814
|
+
import path15 from "path";
|
|
7815
|
+
function getProjectName(cwd) {
|
|
7816
|
+
const dir = cwd ?? process.cwd();
|
|
7817
|
+
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
7524
7818
|
try {
|
|
7525
|
-
|
|
7819
|
+
let repoRoot;
|
|
7820
|
+
try {
|
|
7821
|
+
const gitCommonDir = execSync4("git rev-parse --path-format=absolute --git-common-dir", {
|
|
7822
|
+
cwd: dir,
|
|
7823
|
+
encoding: "utf8",
|
|
7824
|
+
timeout: 2e3,
|
|
7825
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
7826
|
+
}).trim();
|
|
7827
|
+
repoRoot = path15.dirname(gitCommonDir);
|
|
7828
|
+
} catch {
|
|
7829
|
+
repoRoot = execSync4("git rev-parse --show-toplevel", {
|
|
7830
|
+
cwd: dir,
|
|
7831
|
+
encoding: "utf8",
|
|
7832
|
+
timeout: 2e3,
|
|
7833
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
7834
|
+
}).trim();
|
|
7835
|
+
}
|
|
7836
|
+
_cached2 = path15.basename(repoRoot);
|
|
7837
|
+
_cachedCwd = dir;
|
|
7838
|
+
return _cached2;
|
|
7526
7839
|
} catch {
|
|
7527
|
-
|
|
7840
|
+
_cached2 = path15.basename(dir);
|
|
7841
|
+
_cachedCwd = dir;
|
|
7842
|
+
return _cached2;
|
|
7528
7843
|
}
|
|
7529
7844
|
}
|
|
7530
|
-
|
|
7531
|
-
|
|
7532
|
-
|
|
7533
|
-
|
|
7534
|
-
|
|
7535
|
-
|
|
7536
|
-
|
|
7537
|
-
|
|
7845
|
+
var _cached2, _cachedCwd;
|
|
7846
|
+
var init_project_name = __esm({
|
|
7847
|
+
"src/lib/project-name.ts"() {
|
|
7848
|
+
"use strict";
|
|
7849
|
+
_cached2 = null;
|
|
7850
|
+
_cachedCwd = null;
|
|
7851
|
+
}
|
|
7852
|
+
});
|
|
7853
|
+
|
|
7854
|
+
// src/lib/session-scope.ts
|
|
7855
|
+
var session_scope_exports = {};
|
|
7856
|
+
__export(session_scope_exports, {
|
|
7857
|
+
assertSessionScope: () => assertSessionScope,
|
|
7858
|
+
findSessionForProject: () => findSessionForProject,
|
|
7859
|
+
getSessionProject: () => getSessionProject
|
|
7860
|
+
});
|
|
7861
|
+
function getSessionProject(sessionName) {
|
|
7862
|
+
const sessions = listSessions();
|
|
7863
|
+
const entry = sessions.find((s) => s.windowName === sessionName);
|
|
7864
|
+
if (!entry) return null;
|
|
7865
|
+
const parts = entry.projectDir.split("/").filter(Boolean);
|
|
7866
|
+
return parts[parts.length - 1] ?? null;
|
|
7538
7867
|
}
|
|
7539
|
-
|
|
7540
|
-
|
|
7868
|
+
function findSessionForProject(projectName) {
|
|
7869
|
+
const sessions = listSessions();
|
|
7870
|
+
for (const s of sessions) {
|
|
7871
|
+
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
7872
|
+
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
7873
|
+
}
|
|
7874
|
+
return null;
|
|
7875
|
+
}
|
|
7876
|
+
function assertSessionScope(actionType, targetProject) {
|
|
7877
|
+
try {
|
|
7878
|
+
const currentProject = getProjectName();
|
|
7879
|
+
const exeSession = resolveExeSession();
|
|
7880
|
+
if (!exeSession) {
|
|
7881
|
+
return { allowed: true, reason: "no_session" };
|
|
7882
|
+
}
|
|
7883
|
+
if (currentProject === targetProject) {
|
|
7884
|
+
return {
|
|
7885
|
+
allowed: true,
|
|
7886
|
+
reason: "same_session",
|
|
7887
|
+
currentProject,
|
|
7888
|
+
targetProject
|
|
7889
|
+
};
|
|
7890
|
+
}
|
|
7891
|
+
process.stderr.write(
|
|
7892
|
+
`[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
|
|
7893
|
+
`
|
|
7894
|
+
);
|
|
7895
|
+
return {
|
|
7896
|
+
allowed: false,
|
|
7897
|
+
reason: "cross_session_denied",
|
|
7898
|
+
currentProject,
|
|
7899
|
+
targetProject,
|
|
7900
|
+
targetSession: findSessionForProject(targetProject)?.windowName
|
|
7901
|
+
};
|
|
7902
|
+
} catch {
|
|
7903
|
+
return { allowed: true, reason: "no_session" };
|
|
7904
|
+
}
|
|
7905
|
+
}
|
|
7906
|
+
var init_session_scope = __esm({
|
|
7907
|
+
"src/lib/session-scope.ts"() {
|
|
7541
7908
|
"use strict";
|
|
7909
|
+
init_session_registry();
|
|
7910
|
+
init_project_name();
|
|
7542
7911
|
init_tmux_routing();
|
|
7912
|
+
init_employees();
|
|
7543
7913
|
}
|
|
7544
7914
|
});
|
|
7545
7915
|
|
|
7546
7916
|
// src/lib/tasks-crud.ts
|
|
7547
|
-
import
|
|
7548
|
-
import
|
|
7549
|
-
import
|
|
7550
|
-
import { execSync as
|
|
7917
|
+
import crypto6 from "crypto";
|
|
7918
|
+
import path16 from "path";
|
|
7919
|
+
import os11 from "os";
|
|
7920
|
+
import { execSync as execSync5 } from "child_process";
|
|
7551
7921
|
import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
|
|
7552
|
-
import { existsSync as
|
|
7922
|
+
import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
|
|
7553
7923
|
async function writeCheckpoint(input) {
|
|
7554
7924
|
const client = getClient();
|
|
7555
7925
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -7665,13 +8035,28 @@ async function resolveTask(client, identifier, scopeSession) {
|
|
|
7665
8035
|
}
|
|
7666
8036
|
async function createTaskCore(input) {
|
|
7667
8037
|
const client = getClient();
|
|
7668
|
-
const id =
|
|
8038
|
+
const id = crypto6.randomUUID();
|
|
7669
8039
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7670
8040
|
const slug = slugify(input.title);
|
|
7671
8041
|
let earlySessionScope = null;
|
|
8042
|
+
let scopeMismatchWarning;
|
|
7672
8043
|
try {
|
|
7673
8044
|
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
7674
|
-
|
|
8045
|
+
const resolved = resolveExeSession2();
|
|
8046
|
+
if (resolved && input.projectName) {
|
|
8047
|
+
const { getSessionProject: getSessionProject2 } = await Promise.resolve().then(() => (init_session_scope(), session_scope_exports));
|
|
8048
|
+
const sessionProject = getSessionProject2(resolved);
|
|
8049
|
+
if (sessionProject && sessionProject !== input.projectName) {
|
|
8050
|
+
scopeMismatchWarning = `session/project mismatch: session "${resolved}" owns "${sessionProject}" but task targets "${input.projectName}". Routed to default scope.`;
|
|
8051
|
+
process.stderr.write(`[create_task] ${scopeMismatchWarning}
|
|
8052
|
+
`);
|
|
8053
|
+
earlySessionScope = null;
|
|
8054
|
+
} else {
|
|
8055
|
+
earlySessionScope = resolved;
|
|
8056
|
+
}
|
|
8057
|
+
} else {
|
|
8058
|
+
earlySessionScope = resolved;
|
|
8059
|
+
}
|
|
7675
8060
|
} catch {
|
|
7676
8061
|
}
|
|
7677
8062
|
const scope = earlySessionScope ?? "default";
|
|
@@ -7722,10 +8107,14 @@ async function createTaskCore(input) {
|
|
|
7722
8107
|
${laneWarning}` : laneWarning;
|
|
7723
8108
|
}
|
|
7724
8109
|
}
|
|
8110
|
+
if (scopeMismatchWarning) {
|
|
8111
|
+
warning = warning ? `${warning}
|
|
8112
|
+
${scopeMismatchWarning}` : scopeMismatchWarning;
|
|
8113
|
+
}
|
|
7725
8114
|
if (input.baseDir) {
|
|
7726
8115
|
try {
|
|
7727
|
-
await mkdir4(
|
|
7728
|
-
await mkdir4(
|
|
8116
|
+
await mkdir4(path16.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
8117
|
+
await mkdir4(path16.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
7729
8118
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
7730
8119
|
await ensureGitignoreExe(input.baseDir);
|
|
7731
8120
|
} catch {
|
|
@@ -7761,13 +8150,19 @@ ${laneWarning}` : laneWarning;
|
|
|
7761
8150
|
});
|
|
7762
8151
|
if (input.baseDir) {
|
|
7763
8152
|
try {
|
|
7764
|
-
const EXE_OS_DIR =
|
|
7765
|
-
const mdPath =
|
|
7766
|
-
const mdDir =
|
|
7767
|
-
if (!
|
|
8153
|
+
const EXE_OS_DIR = path16.join(os11.homedir(), ".exe-os");
|
|
8154
|
+
const mdPath = path16.join(EXE_OS_DIR, taskFile);
|
|
8155
|
+
const mdDir = path16.dirname(mdPath);
|
|
8156
|
+
if (!existsSync14(mdDir)) await mkdir4(mdDir, { recursive: true });
|
|
7768
8157
|
const reviewer = input.reviewer ?? input.assignedBy;
|
|
7769
8158
|
const mdContent = `# ${input.title}
|
|
7770
8159
|
|
|
8160
|
+
## MANDATORY: When done
|
|
8161
|
+
|
|
8162
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
8163
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
8164
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
8165
|
+
|
|
7771
8166
|
**ID:** ${id}
|
|
7772
8167
|
**Status:** ${initialStatus}
|
|
7773
8168
|
**Priority:** ${input.priority}
|
|
@@ -7781,12 +8176,6 @@ ${laneWarning}` : laneWarning;
|
|
|
7781
8176
|
## Context
|
|
7782
8177
|
|
|
7783
8178
|
${input.context}
|
|
7784
|
-
|
|
7785
|
-
## MANDATORY: When done
|
|
7786
|
-
|
|
7787
|
-
You MUST call update_task with status "done" and a result summary when finished.
|
|
7788
|
-
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
7789
|
-
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
7790
8179
|
`;
|
|
7791
8180
|
await writeFile4(mdPath, mdContent, "utf-8");
|
|
7792
8181
|
} catch (err) {
|
|
@@ -7868,14 +8257,14 @@ function isTmuxSessionAlive(identifier) {
|
|
|
7868
8257
|
if (!identifier || identifier === "unknown") return true;
|
|
7869
8258
|
try {
|
|
7870
8259
|
if (identifier.startsWith("%")) {
|
|
7871
|
-
const output =
|
|
8260
|
+
const output = execSync5("tmux list-panes -a -F '#{pane_id}'", {
|
|
7872
8261
|
timeout: 2e3,
|
|
7873
8262
|
encoding: "utf8",
|
|
7874
8263
|
stdio: ["pipe", "pipe", "pipe"]
|
|
7875
8264
|
});
|
|
7876
8265
|
return output.split("\n").some((l) => l.trim() === identifier);
|
|
7877
8266
|
} else {
|
|
7878
|
-
|
|
8267
|
+
execSync5(`tmux has-session -t ${JSON.stringify(identifier)}`, {
|
|
7879
8268
|
timeout: 2e3,
|
|
7880
8269
|
stdio: ["pipe", "pipe", "pipe"]
|
|
7881
8270
|
});
|
|
@@ -7884,7 +8273,7 @@ function isTmuxSessionAlive(identifier) {
|
|
|
7884
8273
|
} catch {
|
|
7885
8274
|
if (identifier.startsWith("%")) return true;
|
|
7886
8275
|
try {
|
|
7887
|
-
|
|
8276
|
+
execSync5("tmux list-sessions", {
|
|
7888
8277
|
timeout: 2e3,
|
|
7889
8278
|
stdio: ["pipe", "pipe", "pipe"]
|
|
7890
8279
|
});
|
|
@@ -7899,12 +8288,12 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
|
|
|
7899
8288
|
if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
|
|
7900
8289
|
try {
|
|
7901
8290
|
const since = new Date(taskCreatedAt).toISOString();
|
|
7902
|
-
const branch =
|
|
8291
|
+
const branch = execSync5(
|
|
7903
8292
|
"git rev-parse --abbrev-ref HEAD 2>/dev/null",
|
|
7904
8293
|
{ encoding: "utf8", timeout: 3e3 }
|
|
7905
8294
|
).trim();
|
|
7906
8295
|
const branchArg = branch && branch !== "HEAD" ? branch : "";
|
|
7907
|
-
const commitCount =
|
|
8296
|
+
const commitCount = execSync5(
|
|
7908
8297
|
`git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
|
|
7909
8298
|
{ encoding: "utf8", timeout: 5e3 }
|
|
7910
8299
|
).trim();
|
|
@@ -8035,7 +8424,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
8035
8424
|
await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
|
|
8036
8425
|
} catch {
|
|
8037
8426
|
}
|
|
8038
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
8427
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
8039
8428
|
try {
|
|
8040
8429
|
const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
8041
8430
|
clearQueueForAgent2(String(row.assigned_to));
|
|
@@ -8064,9 +8453,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
8064
8453
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
8065
8454
|
}
|
|
8066
8455
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
8067
|
-
const archPath =
|
|
8456
|
+
const archPath = path16.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
8068
8457
|
try {
|
|
8069
|
-
if (
|
|
8458
|
+
if (existsSync14(archPath)) return;
|
|
8070
8459
|
const template = [
|
|
8071
8460
|
`# ${projectName} \u2014 System Architecture`,
|
|
8072
8461
|
"",
|
|
@@ -8099,10 +8488,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
8099
8488
|
}
|
|
8100
8489
|
}
|
|
8101
8490
|
async function ensureGitignoreExe(baseDir) {
|
|
8102
|
-
const gitignorePath =
|
|
8491
|
+
const gitignorePath = path16.join(baseDir, ".gitignore");
|
|
8103
8492
|
try {
|
|
8104
|
-
if (
|
|
8105
|
-
const content =
|
|
8493
|
+
if (existsSync14(gitignorePath)) {
|
|
8494
|
+
const content = readFileSync12(gitignorePath, "utf-8");
|
|
8106
8495
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
8107
8496
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
8108
8497
|
} else {
|
|
@@ -8133,58 +8522,42 @@ var init_tasks_crud = __esm({
|
|
|
8133
8522
|
});
|
|
8134
8523
|
|
|
8135
8524
|
// src/lib/tasks-review.ts
|
|
8136
|
-
import
|
|
8137
|
-
import { existsSync as
|
|
8525
|
+
import path17 from "path";
|
|
8526
|
+
import { existsSync as existsSync15, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
|
|
8138
8527
|
async function countPendingReviews(sessionScope) {
|
|
8139
8528
|
const client = getClient();
|
|
8140
|
-
|
|
8141
|
-
|
|
8142
|
-
|
|
8143
|
-
args: [sessionScope]
|
|
8144
|
-
});
|
|
8145
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
8146
|
-
}
|
|
8529
|
+
const scope = strictSessionScopeFilter(
|
|
8530
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
8531
|
+
);
|
|
8147
8532
|
const result = await client.execute({
|
|
8148
|
-
sql:
|
|
8149
|
-
|
|
8533
|
+
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
8534
|
+
WHERE status = 'needs_review'${scope.sql}`,
|
|
8535
|
+
args: [...scope.args]
|
|
8150
8536
|
});
|
|
8151
8537
|
return Number(result.rows[0]?.cnt) || 0;
|
|
8152
8538
|
}
|
|
8153
8539
|
async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
8154
8540
|
const client = getClient();
|
|
8155
|
-
|
|
8156
|
-
|
|
8157
|
-
|
|
8158
|
-
WHERE status = 'needs_review' AND updated_at > ?
|
|
8159
|
-
AND session_scope = ?`,
|
|
8160
|
-
args: [sinceIso, sessionScope]
|
|
8161
|
-
});
|
|
8162
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
8163
|
-
}
|
|
8541
|
+
const scope = strictSessionScopeFilter(
|
|
8542
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
8543
|
+
);
|
|
8164
8544
|
const result = await client.execute({
|
|
8165
8545
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
8166
|
-
WHERE status = 'needs_review' AND updated_at >
|
|
8167
|
-
args: [sinceIso]
|
|
8546
|
+
WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
|
|
8547
|
+
args: [sinceIso, ...scope.args]
|
|
8168
8548
|
});
|
|
8169
8549
|
return Number(result.rows[0]?.cnt) || 0;
|
|
8170
8550
|
}
|
|
8171
8551
|
async function listPendingReviews(limit, sessionScope) {
|
|
8172
8552
|
const client = getClient();
|
|
8173
|
-
|
|
8174
|
-
|
|
8175
|
-
|
|
8176
|
-
WHERE status = 'needs_review'
|
|
8177
|
-
AND session_scope = ?
|
|
8178
|
-
ORDER BY updated_at ASC LIMIT ?`,
|
|
8179
|
-
args: [sessionScope, limit]
|
|
8180
|
-
});
|
|
8181
|
-
return result2.rows;
|
|
8182
|
-
}
|
|
8553
|
+
const scope = strictSessionScopeFilter(
|
|
8554
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
8555
|
+
);
|
|
8183
8556
|
const result = await client.execute({
|
|
8184
8557
|
sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
|
|
8185
|
-
WHERE status = 'needs_review'
|
|
8558
|
+
WHERE status = 'needs_review'${scope.sql}
|
|
8186
8559
|
ORDER BY updated_at ASC LIMIT ?`,
|
|
8187
|
-
args: [limit]
|
|
8560
|
+
args: [...scope.args, limit]
|
|
8188
8561
|
});
|
|
8189
8562
|
return result.rows;
|
|
8190
8563
|
}
|
|
@@ -8196,7 +8569,7 @@ async function cleanupOrphanedReviews() {
|
|
|
8196
8569
|
WHERE status IN ('open', 'needs_review', 'in_progress')
|
|
8197
8570
|
AND assigned_by = 'system'
|
|
8198
8571
|
AND title LIKE 'Review:%'
|
|
8199
|
-
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
|
|
8572
|
+
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
|
|
8200
8573
|
args: [now]
|
|
8201
8574
|
});
|
|
8202
8575
|
const r1b = await client.execute({
|
|
@@ -8315,11 +8688,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
8315
8688
|
);
|
|
8316
8689
|
}
|
|
8317
8690
|
try {
|
|
8318
|
-
const cacheDir =
|
|
8319
|
-
if (
|
|
8691
|
+
const cacheDir = path17.join(EXE_AI_DIR, "session-cache");
|
|
8692
|
+
if (existsSync15(cacheDir)) {
|
|
8320
8693
|
for (const f of readdirSync3(cacheDir)) {
|
|
8321
8694
|
if (f.startsWith("review-notified-")) {
|
|
8322
|
-
unlinkSync4(
|
|
8695
|
+
unlinkSync4(path17.join(cacheDir, f));
|
|
8323
8696
|
}
|
|
8324
8697
|
}
|
|
8325
8698
|
}
|
|
@@ -8336,11 +8709,12 @@ var init_tasks_review = __esm({
|
|
|
8336
8709
|
init_tmux_routing();
|
|
8337
8710
|
init_session_key();
|
|
8338
8711
|
init_state_bus();
|
|
8712
|
+
init_task_scope();
|
|
8339
8713
|
}
|
|
8340
8714
|
});
|
|
8341
8715
|
|
|
8342
8716
|
// src/lib/tasks-chain.ts
|
|
8343
|
-
import
|
|
8717
|
+
import path18 from "path";
|
|
8344
8718
|
import { readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
|
|
8345
8719
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
8346
8720
|
const client = getClient();
|
|
@@ -8357,7 +8731,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
8357
8731
|
});
|
|
8358
8732
|
for (const ur of unblockedRows.rows) {
|
|
8359
8733
|
try {
|
|
8360
|
-
const ubFile =
|
|
8734
|
+
const ubFile = path18.join(baseDir, String(ur.task_file));
|
|
8361
8735
|
let ubContent = await readFile4(ubFile, "utf-8");
|
|
8362
8736
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
8363
8737
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -8392,7 +8766,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
|
|
|
8392
8766
|
const scScope = sessionScopeFilter();
|
|
8393
8767
|
const remaining = await client.execute({
|
|
8394
8768
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
8395
|
-
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
|
|
8769
|
+
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
|
|
8396
8770
|
args: [parentTaskId, ...scScope.args]
|
|
8397
8771
|
});
|
|
8398
8772
|
const cnt = Number(remaining.rows[0]?.cnt ?? 1);
|
|
@@ -8424,110 +8798,6 @@ var init_tasks_chain = __esm({
|
|
|
8424
8798
|
}
|
|
8425
8799
|
});
|
|
8426
8800
|
|
|
8427
|
-
// src/lib/project-name.ts
|
|
8428
|
-
import { execSync as execSync5 } from "child_process";
|
|
8429
|
-
import path17 from "path";
|
|
8430
|
-
function getProjectName(cwd) {
|
|
8431
|
-
const dir = cwd ?? process.cwd();
|
|
8432
|
-
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
8433
|
-
try {
|
|
8434
|
-
let repoRoot;
|
|
8435
|
-
try {
|
|
8436
|
-
const gitCommonDir = execSync5("git rev-parse --path-format=absolute --git-common-dir", {
|
|
8437
|
-
cwd: dir,
|
|
8438
|
-
encoding: "utf8",
|
|
8439
|
-
timeout: 2e3,
|
|
8440
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
8441
|
-
}).trim();
|
|
8442
|
-
repoRoot = path17.dirname(gitCommonDir);
|
|
8443
|
-
} catch {
|
|
8444
|
-
repoRoot = execSync5("git rev-parse --show-toplevel", {
|
|
8445
|
-
cwd: dir,
|
|
8446
|
-
encoding: "utf8",
|
|
8447
|
-
timeout: 2e3,
|
|
8448
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
8449
|
-
}).trim();
|
|
8450
|
-
}
|
|
8451
|
-
_cached2 = path17.basename(repoRoot);
|
|
8452
|
-
_cachedCwd = dir;
|
|
8453
|
-
return _cached2;
|
|
8454
|
-
} catch {
|
|
8455
|
-
_cached2 = path17.basename(dir);
|
|
8456
|
-
_cachedCwd = dir;
|
|
8457
|
-
return _cached2;
|
|
8458
|
-
}
|
|
8459
|
-
}
|
|
8460
|
-
var _cached2, _cachedCwd;
|
|
8461
|
-
var init_project_name = __esm({
|
|
8462
|
-
"src/lib/project-name.ts"() {
|
|
8463
|
-
"use strict";
|
|
8464
|
-
_cached2 = null;
|
|
8465
|
-
_cachedCwd = null;
|
|
8466
|
-
}
|
|
8467
|
-
});
|
|
8468
|
-
|
|
8469
|
-
// src/lib/session-scope.ts
|
|
8470
|
-
var session_scope_exports = {};
|
|
8471
|
-
__export(session_scope_exports, {
|
|
8472
|
-
assertSessionScope: () => assertSessionScope,
|
|
8473
|
-
findSessionForProject: () => findSessionForProject,
|
|
8474
|
-
getSessionProject: () => getSessionProject
|
|
8475
|
-
});
|
|
8476
|
-
function getSessionProject(sessionName) {
|
|
8477
|
-
const sessions = listSessions();
|
|
8478
|
-
const entry = sessions.find((s) => s.windowName === sessionName);
|
|
8479
|
-
if (!entry) return null;
|
|
8480
|
-
const parts = entry.projectDir.split("/").filter(Boolean);
|
|
8481
|
-
return parts[parts.length - 1] ?? null;
|
|
8482
|
-
}
|
|
8483
|
-
function findSessionForProject(projectName) {
|
|
8484
|
-
const sessions = listSessions();
|
|
8485
|
-
for (const s of sessions) {
|
|
8486
|
-
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
8487
|
-
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
8488
|
-
}
|
|
8489
|
-
return null;
|
|
8490
|
-
}
|
|
8491
|
-
function assertSessionScope(actionType, targetProject) {
|
|
8492
|
-
try {
|
|
8493
|
-
const currentProject = getProjectName();
|
|
8494
|
-
const exeSession = resolveExeSession();
|
|
8495
|
-
if (!exeSession) {
|
|
8496
|
-
return { allowed: true, reason: "no_session" };
|
|
8497
|
-
}
|
|
8498
|
-
if (currentProject === targetProject) {
|
|
8499
|
-
return {
|
|
8500
|
-
allowed: true,
|
|
8501
|
-
reason: "same_session",
|
|
8502
|
-
currentProject,
|
|
8503
|
-
targetProject
|
|
8504
|
-
};
|
|
8505
|
-
}
|
|
8506
|
-
process.stderr.write(
|
|
8507
|
-
`[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
|
|
8508
|
-
`
|
|
8509
|
-
);
|
|
8510
|
-
return {
|
|
8511
|
-
allowed: false,
|
|
8512
|
-
reason: "cross_session_denied",
|
|
8513
|
-
currentProject,
|
|
8514
|
-
targetProject,
|
|
8515
|
-
targetSession: findSessionForProject(targetProject)?.windowName
|
|
8516
|
-
};
|
|
8517
|
-
} catch {
|
|
8518
|
-
return { allowed: true, reason: "no_session" };
|
|
8519
|
-
}
|
|
8520
|
-
}
|
|
8521
|
-
var init_session_scope = __esm({
|
|
8522
|
-
"src/lib/session-scope.ts"() {
|
|
8523
|
-
"use strict";
|
|
8524
|
-
init_session_registry();
|
|
8525
|
-
init_project_name();
|
|
8526
|
-
init_tmux_routing();
|
|
8527
|
-
init_employees();
|
|
8528
|
-
}
|
|
8529
|
-
});
|
|
8530
|
-
|
|
8531
8801
|
// src/lib/tasks-notify.ts
|
|
8532
8802
|
async function dispatchTaskToEmployee(input) {
|
|
8533
8803
|
if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
|
|
@@ -8595,10 +8865,10 @@ var init_tasks_notify = __esm({
|
|
|
8595
8865
|
});
|
|
8596
8866
|
|
|
8597
8867
|
// src/lib/behaviors.ts
|
|
8598
|
-
import
|
|
8868
|
+
import crypto7 from "crypto";
|
|
8599
8869
|
async function storeBehavior(opts) {
|
|
8600
8870
|
const client = getClient();
|
|
8601
|
-
const id =
|
|
8871
|
+
const id = crypto7.randomUUID();
|
|
8602
8872
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8603
8873
|
await client.execute({
|
|
8604
8874
|
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
|
|
@@ -8627,7 +8897,7 @@ __export(skill_learning_exports, {
|
|
|
8627
8897
|
storeTrajectory: () => storeTrajectory,
|
|
8628
8898
|
sweepTrajectories: () => sweepTrajectories
|
|
8629
8899
|
});
|
|
8630
|
-
import
|
|
8900
|
+
import crypto8 from "crypto";
|
|
8631
8901
|
async function extractTrajectory(taskId, agentId) {
|
|
8632
8902
|
const client = getClient();
|
|
8633
8903
|
const result = await client.execute({
|
|
@@ -8656,11 +8926,11 @@ async function extractTrajectory(taskId, agentId) {
|
|
|
8656
8926
|
return signature;
|
|
8657
8927
|
}
|
|
8658
8928
|
function hashSignature(signature) {
|
|
8659
|
-
return
|
|
8929
|
+
return crypto8.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
|
|
8660
8930
|
}
|
|
8661
8931
|
async function storeTrajectory(opts) {
|
|
8662
8932
|
const client = getClient();
|
|
8663
|
-
const id =
|
|
8933
|
+
const id = crypto8.randomUUID();
|
|
8664
8934
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8665
8935
|
const signatureHash = hashSignature(opts.signature);
|
|
8666
8936
|
await client.execute({
|
|
@@ -8925,8 +9195,8 @@ __export(tasks_exports, {
|
|
|
8925
9195
|
updateTaskStatus: () => updateTaskStatus,
|
|
8926
9196
|
writeCheckpoint: () => writeCheckpoint
|
|
8927
9197
|
});
|
|
8928
|
-
import
|
|
8929
|
-
import { writeFileSync as
|
|
9198
|
+
import path19 from "path";
|
|
9199
|
+
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync7, unlinkSync as unlinkSync5 } from "fs";
|
|
8930
9200
|
async function createTask(input) {
|
|
8931
9201
|
const result = await createTaskCore(input);
|
|
8932
9202
|
if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -8945,12 +9215,12 @@ async function updateTask(input) {
|
|
|
8945
9215
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
8946
9216
|
try {
|
|
8947
9217
|
const agent = String(row.assigned_to);
|
|
8948
|
-
const cacheDir =
|
|
8949
|
-
const cachePath =
|
|
9218
|
+
const cacheDir = path19.join(EXE_AI_DIR, "session-cache");
|
|
9219
|
+
const cachePath = path19.join(cacheDir, `current-task-${agent}.json`);
|
|
8950
9220
|
if (input.status === "in_progress") {
|
|
8951
9221
|
mkdirSync7(cacheDir, { recursive: true });
|
|
8952
|
-
|
|
8953
|
-
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
9222
|
+
writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
9223
|
+
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
|
|
8954
9224
|
try {
|
|
8955
9225
|
unlinkSync5(cachePath);
|
|
8956
9226
|
} catch {
|
|
@@ -8958,10 +9228,10 @@ async function updateTask(input) {
|
|
|
8958
9228
|
}
|
|
8959
9229
|
} catch {
|
|
8960
9230
|
}
|
|
8961
|
-
if (input.status === "done") {
|
|
9231
|
+
if (input.status === "done" || input.status === "closed") {
|
|
8962
9232
|
await cleanupReviewFile(row, taskFile, input.baseDir);
|
|
8963
9233
|
}
|
|
8964
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
9234
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
8965
9235
|
try {
|
|
8966
9236
|
const client = getClient();
|
|
8967
9237
|
const taskTitle = String(row.title);
|
|
@@ -8977,7 +9247,7 @@ async function updateTask(input) {
|
|
|
8977
9247
|
if (!isCoordinatorName(assignedAgent)) {
|
|
8978
9248
|
try {
|
|
8979
9249
|
const draftClient = getClient();
|
|
8980
|
-
if (input.status === "done") {
|
|
9250
|
+
if (input.status === "done" || input.status === "closed") {
|
|
8981
9251
|
await draftClient.execute({
|
|
8982
9252
|
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
8983
9253
|
args: [assignedAgent]
|
|
@@ -8994,7 +9264,7 @@ async function updateTask(input) {
|
|
|
8994
9264
|
try {
|
|
8995
9265
|
const client = getClient();
|
|
8996
9266
|
const cascaded = await client.execute({
|
|
8997
|
-
sql: `UPDATE tasks SET status = '
|
|
9267
|
+
sql: `UPDATE tasks SET status = 'closed', updated_at = ?
|
|
8998
9268
|
WHERE parent_task_id = ? AND status = 'needs_review'`,
|
|
8999
9269
|
args: [now, taskId]
|
|
9000
9270
|
});
|
|
@@ -9007,14 +9277,14 @@ async function updateTask(input) {
|
|
|
9007
9277
|
} catch {
|
|
9008
9278
|
}
|
|
9009
9279
|
}
|
|
9010
|
-
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
9280
|
+
const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
|
|
9011
9281
|
if (isTerminal) {
|
|
9012
9282
|
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
9013
9283
|
if (!isCoordinator) {
|
|
9014
9284
|
notifyTaskDone();
|
|
9015
9285
|
}
|
|
9016
9286
|
await markTaskNotificationsRead(taskFile);
|
|
9017
|
-
if (input.status === "done") {
|
|
9287
|
+
if (input.status === "done" || input.status === "closed") {
|
|
9018
9288
|
try {
|
|
9019
9289
|
await cascadeUnblock(taskId, input.baseDir, now);
|
|
9020
9290
|
} catch {
|
|
@@ -9034,7 +9304,7 @@ async function updateTask(input) {
|
|
|
9034
9304
|
}
|
|
9035
9305
|
}
|
|
9036
9306
|
}
|
|
9037
|
-
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
9307
|
+
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
9038
9308
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
9039
9309
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
9040
9310
|
taskId,
|
|
@@ -9406,6 +9676,7 @@ __export(tmux_routing_exports, {
|
|
|
9406
9676
|
isEmployeeAlive: () => isEmployeeAlive,
|
|
9407
9677
|
isExeSession: () => isExeSession,
|
|
9408
9678
|
isSessionBusy: () => isSessionBusy,
|
|
9679
|
+
notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
|
|
9409
9680
|
notifyParentExe: () => notifyParentExe,
|
|
9410
9681
|
parseParentExe: () => parseParentExe,
|
|
9411
9682
|
registerParentExe: () => registerParentExe,
|
|
@@ -9416,13 +9687,13 @@ __export(tmux_routing_exports, {
|
|
|
9416
9687
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
9417
9688
|
});
|
|
9418
9689
|
import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
|
|
9419
|
-
import { readFileSync as
|
|
9420
|
-
import
|
|
9421
|
-
import
|
|
9690
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, mkdirSync as mkdirSync8, existsSync as existsSync16, appendFileSync, readdirSync as readdirSync4 } from "fs";
|
|
9691
|
+
import path20 from "path";
|
|
9692
|
+
import os12 from "os";
|
|
9422
9693
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
9423
9694
|
import { unlinkSync as unlinkSync6 } from "fs";
|
|
9424
9695
|
function spawnLockPath(sessionName) {
|
|
9425
|
-
return
|
|
9696
|
+
return path20.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
9426
9697
|
}
|
|
9427
9698
|
function isProcessAlive(pid) {
|
|
9428
9699
|
try {
|
|
@@ -9433,13 +9704,13 @@ function isProcessAlive(pid) {
|
|
|
9433
9704
|
}
|
|
9434
9705
|
}
|
|
9435
9706
|
function acquireSpawnLock2(sessionName) {
|
|
9436
|
-
if (!
|
|
9707
|
+
if (!existsSync16(SPAWN_LOCK_DIR)) {
|
|
9437
9708
|
mkdirSync8(SPAWN_LOCK_DIR, { recursive: true });
|
|
9438
9709
|
}
|
|
9439
9710
|
const lockFile = spawnLockPath(sessionName);
|
|
9440
|
-
if (
|
|
9711
|
+
if (existsSync16(lockFile)) {
|
|
9441
9712
|
try {
|
|
9442
|
-
const lock = JSON.parse(
|
|
9713
|
+
const lock = JSON.parse(readFileSync13(lockFile, "utf8"));
|
|
9443
9714
|
const age = Date.now() - lock.timestamp;
|
|
9444
9715
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
9445
9716
|
return false;
|
|
@@ -9447,7 +9718,7 @@ function acquireSpawnLock2(sessionName) {
|
|
|
9447
9718
|
} catch {
|
|
9448
9719
|
}
|
|
9449
9720
|
}
|
|
9450
|
-
|
|
9721
|
+
writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
9451
9722
|
return true;
|
|
9452
9723
|
}
|
|
9453
9724
|
function releaseSpawnLock2(sessionName) {
|
|
@@ -9459,13 +9730,13 @@ function releaseSpawnLock2(sessionName) {
|
|
|
9459
9730
|
function resolveBehaviorsExporterScript() {
|
|
9460
9731
|
try {
|
|
9461
9732
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
9462
|
-
const scriptPath =
|
|
9463
|
-
|
|
9733
|
+
const scriptPath = path20.join(
|
|
9734
|
+
path20.dirname(thisFile),
|
|
9464
9735
|
"..",
|
|
9465
9736
|
"bin",
|
|
9466
9737
|
"exe-export-behaviors.js"
|
|
9467
9738
|
);
|
|
9468
|
-
return
|
|
9739
|
+
return existsSync16(scriptPath) ? scriptPath : null;
|
|
9469
9740
|
} catch {
|
|
9470
9741
|
return null;
|
|
9471
9742
|
}
|
|
@@ -9531,12 +9802,12 @@ function extractRootExe(name) {
|
|
|
9531
9802
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
9532
9803
|
}
|
|
9533
9804
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
9534
|
-
if (!
|
|
9805
|
+
if (!existsSync16(SESSION_CACHE)) {
|
|
9535
9806
|
mkdirSync8(SESSION_CACHE, { recursive: true });
|
|
9536
9807
|
}
|
|
9537
9808
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
9538
|
-
const filePath =
|
|
9539
|
-
|
|
9809
|
+
const filePath = path20.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
9810
|
+
writeFileSync8(filePath, JSON.stringify({
|
|
9540
9811
|
parentExe: rootExe,
|
|
9541
9812
|
dispatchedBy: dispatchedBy || rootExe,
|
|
9542
9813
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -9544,7 +9815,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
9544
9815
|
}
|
|
9545
9816
|
function getParentExe(sessionKey) {
|
|
9546
9817
|
try {
|
|
9547
|
-
const data = JSON.parse(
|
|
9818
|
+
const data = JSON.parse(readFileSync13(path20.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
9548
9819
|
return data.parentExe || null;
|
|
9549
9820
|
} catch {
|
|
9550
9821
|
return null;
|
|
@@ -9552,8 +9823,8 @@ function getParentExe(sessionKey) {
|
|
|
9552
9823
|
}
|
|
9553
9824
|
function getDispatchedBy(sessionKey) {
|
|
9554
9825
|
try {
|
|
9555
|
-
const data = JSON.parse(
|
|
9556
|
-
|
|
9826
|
+
const data = JSON.parse(readFileSync13(
|
|
9827
|
+
path20.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
9557
9828
|
"utf8"
|
|
9558
9829
|
));
|
|
9559
9830
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -9623,8 +9894,8 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
9623
9894
|
}
|
|
9624
9895
|
function readDebounceState() {
|
|
9625
9896
|
try {
|
|
9626
|
-
if (!
|
|
9627
|
-
const raw = JSON.parse(
|
|
9897
|
+
if (!existsSync16(DEBOUNCE_FILE)) return {};
|
|
9898
|
+
const raw = JSON.parse(readFileSync13(DEBOUNCE_FILE, "utf8"));
|
|
9628
9899
|
const state = {};
|
|
9629
9900
|
for (const [key, val] of Object.entries(raw)) {
|
|
9630
9901
|
if (typeof val === "number") {
|
|
@@ -9640,8 +9911,8 @@ function readDebounceState() {
|
|
|
9640
9911
|
}
|
|
9641
9912
|
function writeDebounceState(state) {
|
|
9642
9913
|
try {
|
|
9643
|
-
if (!
|
|
9644
|
-
|
|
9914
|
+
if (!existsSync16(SESSION_CACHE)) mkdirSync8(SESSION_CACHE, { recursive: true });
|
|
9915
|
+
writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
|
|
9645
9916
|
} catch {
|
|
9646
9917
|
}
|
|
9647
9918
|
}
|
|
@@ -9739,8 +10010,8 @@ function sendIntercom(targetSession) {
|
|
|
9739
10010
|
try {
|
|
9740
10011
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
9741
10012
|
const agent = baseAgentName(rawAgent);
|
|
9742
|
-
const markerPath =
|
|
9743
|
-
if (
|
|
10013
|
+
const markerPath = path20.join(SESSION_CACHE, `current-task-${agent}.json`);
|
|
10014
|
+
if (existsSync16(markerPath)) {
|
|
9744
10015
|
logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
|
|
9745
10016
|
return "debounced";
|
|
9746
10017
|
}
|
|
@@ -9749,8 +10020,8 @@ function sendIntercom(targetSession) {
|
|
|
9749
10020
|
try {
|
|
9750
10021
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
9751
10022
|
const agent = baseAgentName(rawAgent);
|
|
9752
|
-
const taskDir =
|
|
9753
|
-
if (
|
|
10023
|
+
const taskDir = path20.join(process.cwd(), "exe", agent);
|
|
10024
|
+
if (existsSync16(taskDir)) {
|
|
9754
10025
|
const files = readdirSync4(taskDir).filter(
|
|
9755
10026
|
(f) => f.endsWith(".md") && f !== "DONE.txt"
|
|
9756
10027
|
);
|
|
@@ -9810,6 +10081,21 @@ function notifyParentExe(sessionKey) {
|
|
|
9810
10081
|
}
|
|
9811
10082
|
return true;
|
|
9812
10083
|
}
|
|
10084
|
+
function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
|
|
10085
|
+
const transport = getTransport();
|
|
10086
|
+
try {
|
|
10087
|
+
const sessions = transport.listSessions();
|
|
10088
|
+
if (!sessions.includes(coordinatorSession)) return false;
|
|
10089
|
+
execSync6(
|
|
10090
|
+
`tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
|
|
10091
|
+
{ timeout: 3e3 }
|
|
10092
|
+
);
|
|
10093
|
+
logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
|
|
10094
|
+
return true;
|
|
10095
|
+
} catch {
|
|
10096
|
+
return false;
|
|
10097
|
+
}
|
|
10098
|
+
}
|
|
9813
10099
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
9814
10100
|
if (isCoordinatorName(employeeName)) {
|
|
9815
10101
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
@@ -9883,26 +10169,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
9883
10169
|
const transport = getTransport();
|
|
9884
10170
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
9885
10171
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
9886
|
-
const logDir =
|
|
9887
|
-
const logFile =
|
|
9888
|
-
if (!
|
|
10172
|
+
const logDir = path20.join(os12.homedir(), ".exe-os", "session-logs");
|
|
10173
|
+
const logFile = path20.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
10174
|
+
if (!existsSync16(logDir)) {
|
|
9889
10175
|
mkdirSync8(logDir, { recursive: true });
|
|
9890
10176
|
}
|
|
9891
10177
|
transport.kill(sessionName);
|
|
9892
10178
|
let cleanupSuffix = "";
|
|
9893
10179
|
try {
|
|
9894
10180
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
9895
|
-
const cleanupScript =
|
|
9896
|
-
if (
|
|
10181
|
+
const cleanupScript = path20.join(path20.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
10182
|
+
if (existsSync16(cleanupScript)) {
|
|
9897
10183
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
9898
10184
|
}
|
|
9899
10185
|
} catch {
|
|
9900
10186
|
}
|
|
9901
10187
|
try {
|
|
9902
|
-
const claudeJsonPath =
|
|
10188
|
+
const claudeJsonPath = path20.join(os12.homedir(), ".claude.json");
|
|
9903
10189
|
let claudeJson = {};
|
|
9904
10190
|
try {
|
|
9905
|
-
claudeJson = JSON.parse(
|
|
10191
|
+
claudeJson = JSON.parse(readFileSync13(claudeJsonPath, "utf8"));
|
|
9906
10192
|
} catch {
|
|
9907
10193
|
}
|
|
9908
10194
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -9910,17 +10196,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
9910
10196
|
const trustDir = opts?.cwd ?? projectDir;
|
|
9911
10197
|
if (!projects[trustDir]) projects[trustDir] = {};
|
|
9912
10198
|
projects[trustDir].hasTrustDialogAccepted = true;
|
|
9913
|
-
|
|
10199
|
+
writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
9914
10200
|
} catch {
|
|
9915
10201
|
}
|
|
9916
10202
|
try {
|
|
9917
|
-
const settingsDir =
|
|
10203
|
+
const settingsDir = path20.join(os12.homedir(), ".claude", "projects");
|
|
9918
10204
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
9919
|
-
const projSettingsDir =
|
|
9920
|
-
const settingsPath =
|
|
10205
|
+
const projSettingsDir = path20.join(settingsDir, normalizedKey);
|
|
10206
|
+
const settingsPath = path20.join(projSettingsDir, "settings.json");
|
|
9921
10207
|
let settings = {};
|
|
9922
10208
|
try {
|
|
9923
|
-
settings = JSON.parse(
|
|
10209
|
+
settings = JSON.parse(readFileSync13(settingsPath, "utf8"));
|
|
9924
10210
|
} catch {
|
|
9925
10211
|
}
|
|
9926
10212
|
const perms = settings.permissions ?? {};
|
|
@@ -9949,7 +10235,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
9949
10235
|
perms.allow = allow;
|
|
9950
10236
|
settings.permissions = perms;
|
|
9951
10237
|
mkdirSync8(projSettingsDir, { recursive: true });
|
|
9952
|
-
|
|
10238
|
+
writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
9953
10239
|
}
|
|
9954
10240
|
} catch {
|
|
9955
10241
|
}
|
|
@@ -9964,8 +10250,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
9964
10250
|
let behaviorsFlag = "";
|
|
9965
10251
|
let legacyFallbackWarned = false;
|
|
9966
10252
|
if (!useExeAgent && !useBinSymlink) {
|
|
9967
|
-
const identityPath =
|
|
9968
|
-
|
|
10253
|
+
const identityPath = path20.join(
|
|
10254
|
+
os12.homedir(),
|
|
9969
10255
|
".exe-os",
|
|
9970
10256
|
"identity",
|
|
9971
10257
|
`${employeeName}.md`
|
|
@@ -9974,13 +10260,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
9974
10260
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
9975
10261
|
if (hasAgentFlag) {
|
|
9976
10262
|
identityFlag = ` --agent ${employeeName}`;
|
|
9977
|
-
} else if (
|
|
10263
|
+
} else if (existsSync16(identityPath)) {
|
|
9978
10264
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
9979
10265
|
legacyFallbackWarned = true;
|
|
9980
10266
|
}
|
|
9981
10267
|
const behaviorsFile = exportBehaviorsSync(
|
|
9982
10268
|
employeeName,
|
|
9983
|
-
|
|
10269
|
+
path20.basename(spawnCwd),
|
|
9984
10270
|
sessionName
|
|
9985
10271
|
);
|
|
9986
10272
|
if (behaviorsFile) {
|
|
@@ -9995,16 +10281,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
9995
10281
|
}
|
|
9996
10282
|
let sessionContextFlag = "";
|
|
9997
10283
|
try {
|
|
9998
|
-
const ctxDir =
|
|
10284
|
+
const ctxDir = path20.join(os12.homedir(), ".exe-os", "session-cache");
|
|
9999
10285
|
mkdirSync8(ctxDir, { recursive: true });
|
|
10000
|
-
const ctxFile =
|
|
10286
|
+
const ctxFile = path20.join(ctxDir, `session-context-${sessionName}.md`);
|
|
10001
10287
|
const ctxContent = [
|
|
10002
10288
|
`## Session Context`,
|
|
10003
10289
|
`You are running in tmux session: ${sessionName}.`,
|
|
10004
10290
|
`Your parent coordinator session is ${exeSession}.`,
|
|
10005
10291
|
`Your employees (if any) use the -${exeSession} suffix.`
|
|
10006
10292
|
].join("\n");
|
|
10007
|
-
|
|
10293
|
+
writeFileSync8(ctxFile, ctxContent);
|
|
10008
10294
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
10009
10295
|
} catch {
|
|
10010
10296
|
}
|
|
@@ -10081,8 +10367,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
10081
10367
|
transport.pipeLog(sessionName, logFile);
|
|
10082
10368
|
try {
|
|
10083
10369
|
const mySession = getMySession();
|
|
10084
|
-
const dispatchInfo =
|
|
10085
|
-
|
|
10370
|
+
const dispatchInfo = path20.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
10371
|
+
writeFileSync8(dispatchInfo, JSON.stringify({
|
|
10086
10372
|
dispatchedBy: mySession,
|
|
10087
10373
|
rootExe: exeSession,
|
|
10088
10374
|
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
|
|
@@ -10156,15 +10442,15 @@ var init_tmux_routing = __esm({
|
|
|
10156
10442
|
init_intercom_queue();
|
|
10157
10443
|
init_plan_limits();
|
|
10158
10444
|
init_employees();
|
|
10159
|
-
SPAWN_LOCK_DIR =
|
|
10160
|
-
SESSION_CACHE =
|
|
10445
|
+
SPAWN_LOCK_DIR = path20.join(os12.homedir(), ".exe-os", "spawn-locks");
|
|
10446
|
+
SESSION_CACHE = path20.join(os12.homedir(), ".exe-os", "session-cache");
|
|
10161
10447
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
10162
10448
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
10163
10449
|
VERIFY_PANE_LINES = 200;
|
|
10164
10450
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
10165
10451
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
10166
|
-
INTERCOM_LOG2 =
|
|
10167
|
-
DEBOUNCE_FILE =
|
|
10452
|
+
INTERCOM_LOG2 = path20.join(os12.homedir(), ".exe-os", "intercom.log");
|
|
10453
|
+
DEBOUNCE_FILE = path20.join(SESSION_CACHE, "intercom-debounce.json");
|
|
10168
10454
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
10169
10455
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
|
|
10170
10456
|
}
|
|
@@ -10187,10 +10473,10 @@ __export(messaging_exports, {
|
|
|
10187
10473
|
sendMessage: () => sendMessage,
|
|
10188
10474
|
setWsClientSend: () => setWsClientSend
|
|
10189
10475
|
});
|
|
10190
|
-
import
|
|
10476
|
+
import crypto9 from "crypto";
|
|
10191
10477
|
function generateUlid() {
|
|
10192
10478
|
const timestamp = Date.now().toString(36).padStart(10, "0");
|
|
10193
|
-
const random =
|
|
10479
|
+
const random = crypto9.randomBytes(10).toString("hex").slice(0, 16);
|
|
10194
10480
|
return (timestamp + random).toUpperCase();
|
|
10195
10481
|
}
|
|
10196
10482
|
function rowToMessage(row) {
|
|
@@ -10201,6 +10487,7 @@ function rowToMessage(row) {
|
|
|
10201
10487
|
targetAgent: row.target_agent,
|
|
10202
10488
|
targetProject: row.target_project ?? null,
|
|
10203
10489
|
targetDevice: row.target_device,
|
|
10490
|
+
sessionScope: row.session_scope ?? null,
|
|
10204
10491
|
content: row.content,
|
|
10205
10492
|
priority: row.priority ?? "normal",
|
|
10206
10493
|
status: row.status ?? "pending",
|
|
@@ -10218,15 +10505,17 @@ async function sendMessage(input) {
|
|
|
10218
10505
|
const id = generateUlid();
|
|
10219
10506
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
10220
10507
|
const targetDevice = input.targetDevice ?? "local";
|
|
10508
|
+
const sessionScope = input.sessionScope === void 0 ? resolveExeSession() : input.sessionScope;
|
|
10221
10509
|
await client.execute({
|
|
10222
|
-
sql: `INSERT INTO messages (id, from_agent, from_device, target_agent, target_project, target_device, content, priority, status, created_at)
|
|
10223
|
-
VALUES (?, ?, 'local', ?, ?, ?, ?, ?, 'pending', ?)`,
|
|
10510
|
+
sql: `INSERT INTO messages (id, from_agent, from_device, target_agent, target_project, target_device, session_scope, content, priority, status, created_at)
|
|
10511
|
+
VALUES (?, ?, 'local', ?, ?, ?, ?, ?, ?, 'pending', ?)`,
|
|
10224
10512
|
args: [
|
|
10225
10513
|
id,
|
|
10226
10514
|
input.fromAgent,
|
|
10227
10515
|
input.targetAgent,
|
|
10228
10516
|
input.targetProject ?? null,
|
|
10229
10517
|
targetDevice,
|
|
10518
|
+
sessionScope,
|
|
10230
10519
|
input.content,
|
|
10231
10520
|
input.priority ?? "normal",
|
|
10232
10521
|
now
|
|
@@ -10240,9 +10529,10 @@ async function sendMessage(input) {
|
|
|
10240
10529
|
}
|
|
10241
10530
|
} catch {
|
|
10242
10531
|
}
|
|
10532
|
+
const sentScope = strictSessionScopeFilter(sessionScope);
|
|
10243
10533
|
const result = await client.execute({
|
|
10244
|
-
sql:
|
|
10245
|
-
args: [id]
|
|
10534
|
+
sql: `SELECT * FROM messages WHERE id = ?${sentScope.sql}`,
|
|
10535
|
+
args: [id, ...sentScope.args]
|
|
10246
10536
|
});
|
|
10247
10537
|
return rowToMessage(result.rows[0]);
|
|
10248
10538
|
}
|
|
@@ -10266,6 +10556,7 @@ async function deliverCrossMachineMessage(messageId, targetDevice) {
|
|
|
10266
10556
|
fromAgent: msg.fromAgent,
|
|
10267
10557
|
targetAgent: msg.targetAgent,
|
|
10268
10558
|
targetProject: msg.targetProject,
|
|
10559
|
+
sessionScope: msg.sessionScope,
|
|
10269
10560
|
content: msg.content,
|
|
10270
10561
|
priority: msg.priority,
|
|
10271
10562
|
createdAt: msg.createdAt
|
|
@@ -10309,7 +10600,7 @@ async function deliverLocalMessage(messageId) {
|
|
|
10309
10600
|
} catch {
|
|
10310
10601
|
const newRetryCount = msg.retryCount + 1;
|
|
10311
10602
|
if (newRetryCount >= MAX_RETRIES3) {
|
|
10312
|
-
await markFailed(messageId, "session unavailable after 10 retries");
|
|
10603
|
+
await markFailed(messageId, "session unavailable after 10 retries", msg.sessionScope);
|
|
10313
10604
|
} else {
|
|
10314
10605
|
await client.execute({
|
|
10315
10606
|
sql: "UPDATE messages SET retry_count = ? WHERE id = ?",
|
|
@@ -10319,85 +10610,101 @@ async function deliverLocalMessage(messageId) {
|
|
|
10319
10610
|
return false;
|
|
10320
10611
|
}
|
|
10321
10612
|
}
|
|
10322
|
-
async function getPendingMessages(targetAgent) {
|
|
10613
|
+
async function getPendingMessages(targetAgent, sessionScope) {
|
|
10323
10614
|
const client = getClient();
|
|
10615
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
10324
10616
|
const result = await client.execute({
|
|
10325
10617
|
sql: `SELECT * FROM messages
|
|
10326
|
-
WHERE target_agent = ? AND status IN ('pending', 'delivered')
|
|
10618
|
+
WHERE target_agent = ? AND status IN ('pending', 'delivered')${scope.sql}
|
|
10327
10619
|
ORDER BY id`,
|
|
10328
|
-
args: [targetAgent]
|
|
10620
|
+
args: [targetAgent, ...scope.args]
|
|
10329
10621
|
});
|
|
10330
10622
|
return result.rows.map((row) => rowToMessage(row));
|
|
10331
10623
|
}
|
|
10332
|
-
async function markRead(messageId) {
|
|
10624
|
+
async function markRead(messageId, sessionScope) {
|
|
10333
10625
|
const client = getClient();
|
|
10626
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
10334
10627
|
await client.execute({
|
|
10335
|
-
sql:
|
|
10336
|
-
|
|
10628
|
+
sql: `UPDATE messages SET status = 'read'
|
|
10629
|
+
WHERE id = ? AND status IN ('pending', 'delivered')${scope.sql}`,
|
|
10630
|
+
args: [messageId, ...scope.args]
|
|
10337
10631
|
});
|
|
10338
10632
|
}
|
|
10339
|
-
async function markAcknowledged(messageId) {
|
|
10633
|
+
async function markAcknowledged(messageId, sessionScope) {
|
|
10340
10634
|
const client = getClient();
|
|
10635
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
10341
10636
|
await client.execute({
|
|
10342
|
-
sql:
|
|
10343
|
-
|
|
10637
|
+
sql: `UPDATE messages SET status = 'acknowledged', processed_at = ?
|
|
10638
|
+
WHERE id = ? AND status = 'read'${scope.sql}`,
|
|
10639
|
+
args: [(/* @__PURE__ */ new Date()).toISOString(), messageId, ...scope.args]
|
|
10344
10640
|
});
|
|
10345
10641
|
}
|
|
10346
|
-
async function markProcessed(messageId) {
|
|
10642
|
+
async function markProcessed(messageId, sessionScope) {
|
|
10347
10643
|
const client = getClient();
|
|
10644
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
10348
10645
|
await client.execute({
|
|
10349
|
-
sql:
|
|
10350
|
-
|
|
10646
|
+
sql: `UPDATE messages SET status = 'processed', processed_at = ?
|
|
10647
|
+
WHERE id = ?${scope.sql}`,
|
|
10648
|
+
args: [(/* @__PURE__ */ new Date()).toISOString(), messageId, ...scope.args]
|
|
10351
10649
|
});
|
|
10352
10650
|
}
|
|
10353
|
-
async function getMessageStatus(messageId) {
|
|
10651
|
+
async function getMessageStatus(messageId, sessionScope) {
|
|
10354
10652
|
const client = getClient();
|
|
10653
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
10355
10654
|
const result = await client.execute({
|
|
10356
|
-
sql:
|
|
10357
|
-
args: [messageId]
|
|
10655
|
+
sql: `SELECT status FROM messages WHERE id = ?${scope.sql}`,
|
|
10656
|
+
args: [messageId, ...scope.args]
|
|
10358
10657
|
});
|
|
10359
10658
|
return result.rows[0]?.status ?? null;
|
|
10360
10659
|
}
|
|
10361
|
-
async function getUnacknowledgedMessages(targetAgent) {
|
|
10660
|
+
async function getUnacknowledgedMessages(targetAgent, sessionScope) {
|
|
10362
10661
|
const client = getClient();
|
|
10662
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
10363
10663
|
const result = await client.execute({
|
|
10364
10664
|
sql: `SELECT * FROM messages
|
|
10365
|
-
WHERE target_agent = ? AND status IN ('pending', 'delivered', 'read')
|
|
10665
|
+
WHERE target_agent = ? AND status IN ('pending', 'delivered', 'read')${scope.sql}
|
|
10366
10666
|
ORDER BY id`,
|
|
10367
|
-
args: [targetAgent]
|
|
10667
|
+
args: [targetAgent, ...scope.args]
|
|
10368
10668
|
});
|
|
10369
10669
|
return result.rows.map((row) => rowToMessage(row));
|
|
10370
10670
|
}
|
|
10371
|
-
async function getReadMessages(targetAgent) {
|
|
10671
|
+
async function getReadMessages(targetAgent, sessionScope) {
|
|
10372
10672
|
const client = getClient();
|
|
10673
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
10373
10674
|
const result = await client.execute({
|
|
10374
|
-
sql:
|
|
10375
|
-
|
|
10675
|
+
sql: `SELECT * FROM messages
|
|
10676
|
+
WHERE target_agent = ? AND status = 'read'${scope.sql}
|
|
10677
|
+
ORDER BY id`,
|
|
10678
|
+
args: [targetAgent, ...scope.args]
|
|
10376
10679
|
});
|
|
10377
10680
|
return result.rows.map((row) => rowToMessage(row));
|
|
10378
10681
|
}
|
|
10379
|
-
async function markFailed(messageId, reason) {
|
|
10682
|
+
async function markFailed(messageId, reason, sessionScope) {
|
|
10380
10683
|
const client = getClient();
|
|
10684
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
10381
10685
|
await client.execute({
|
|
10382
|
-
sql:
|
|
10383
|
-
|
|
10686
|
+
sql: `UPDATE messages SET status = 'failed', failed_at = ?, failure_reason = ?
|
|
10687
|
+
WHERE id = ?${scope.sql}`,
|
|
10688
|
+
args: [(/* @__PURE__ */ new Date()).toISOString(), reason, messageId, ...scope.args]
|
|
10384
10689
|
});
|
|
10385
10690
|
}
|
|
10386
|
-
async function getFailedMessages() {
|
|
10691
|
+
async function getFailedMessages(sessionScope) {
|
|
10387
10692
|
const client = getClient();
|
|
10693
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
10388
10694
|
const result = await client.execute({
|
|
10389
|
-
sql:
|
|
10390
|
-
args: []
|
|
10695
|
+
sql: `SELECT * FROM messages WHERE status = 'failed'${scope.sql} ORDER BY created_at DESC`,
|
|
10696
|
+
args: [...scope.args]
|
|
10391
10697
|
});
|
|
10392
10698
|
return result.rows.map((row) => rowToMessage(row));
|
|
10393
10699
|
}
|
|
10394
|
-
async function retryPendingMessages() {
|
|
10700
|
+
async function retryPendingMessages(sessionScope) {
|
|
10395
10701
|
const client = getClient();
|
|
10702
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
10396
10703
|
const result = await client.execute({
|
|
10397
10704
|
sql: `SELECT * FROM messages
|
|
10398
|
-
WHERE status = 'pending' AND retry_count <
|
|
10705
|
+
WHERE status = 'pending' AND retry_count < ?${scope.sql}
|
|
10399
10706
|
ORDER BY id`,
|
|
10400
|
-
args: [MAX_RETRIES3]
|
|
10707
|
+
args: [MAX_RETRIES3, ...scope.args]
|
|
10401
10708
|
});
|
|
10402
10709
|
let delivered = 0;
|
|
10403
10710
|
for (const row of result.rows) {
|
|
@@ -10416,16 +10723,17 @@ var init_messaging = __esm({
|
|
|
10416
10723
|
"use strict";
|
|
10417
10724
|
init_database();
|
|
10418
10725
|
init_tmux_routing();
|
|
10726
|
+
init_task_scope();
|
|
10419
10727
|
MAX_RETRIES3 = 10;
|
|
10420
10728
|
_wsClientSend = null;
|
|
10421
10729
|
}
|
|
10422
10730
|
});
|
|
10423
10731
|
|
|
10424
10732
|
// src/automation/trigger-engine.ts
|
|
10425
|
-
import { readFileSync as
|
|
10733
|
+
import { readFileSync as readFileSync14, writeFileSync as writeFileSync9, existsSync as existsSync17, mkdirSync as mkdirSync9 } from "fs";
|
|
10426
10734
|
import { randomUUID as randomUUID8 } from "crypto";
|
|
10427
|
-
import
|
|
10428
|
-
import
|
|
10735
|
+
import path21 from "path";
|
|
10736
|
+
import os13 from "os";
|
|
10429
10737
|
function substituteTemplate(template, record) {
|
|
10430
10738
|
return template.replace(
|
|
10431
10739
|
/\{\{(\w+(?:\.\w+)*)\}\}/g,
|
|
@@ -10478,9 +10786,9 @@ function evaluateConditions(conditions, record) {
|
|
|
10478
10786
|
return conditions.every((c) => evaluateCondition(c, record));
|
|
10479
10787
|
}
|
|
10480
10788
|
function loadTriggers(project) {
|
|
10481
|
-
if (!
|
|
10789
|
+
if (!existsSync17(TRIGGERS_PATH)) return [];
|
|
10482
10790
|
try {
|
|
10483
|
-
const raw =
|
|
10791
|
+
const raw = readFileSync14(TRIGGERS_PATH, "utf-8");
|
|
10484
10792
|
const all = JSON.parse(raw);
|
|
10485
10793
|
if (!Array.isArray(all)) return [];
|
|
10486
10794
|
if (project) {
|
|
@@ -10720,7 +11028,7 @@ var TRIGGERS_PATH, GRAPH_API_VERSION;
|
|
|
10720
11028
|
var init_trigger_engine = __esm({
|
|
10721
11029
|
"src/automation/trigger-engine.ts"() {
|
|
10722
11030
|
"use strict";
|
|
10723
|
-
TRIGGERS_PATH =
|
|
11031
|
+
TRIGGERS_PATH = path21.join(os13.homedir(), ".exe-os", "triggers.json");
|
|
10724
11032
|
GRAPH_API_VERSION = "v21.0";
|
|
10725
11033
|
}
|
|
10726
11034
|
});
|
|
@@ -10783,9 +11091,9 @@ var init_crm_webhook = __esm({
|
|
|
10783
11091
|
});
|
|
10784
11092
|
|
|
10785
11093
|
// src/bin/exe-gateway.ts
|
|
10786
|
-
import { existsSync as
|
|
10787
|
-
import
|
|
10788
|
-
import
|
|
11094
|
+
import { existsSync as existsSync18, readFileSync as readFileSync15 } from "fs";
|
|
11095
|
+
import path22 from "path";
|
|
11096
|
+
import os14 from "os";
|
|
10789
11097
|
|
|
10790
11098
|
// src/gateway/webhook-server.ts
|
|
10791
11099
|
import {
|
|
@@ -11027,11 +11335,11 @@ init_crm_bridge();
|
|
|
11027
11335
|
|
|
11028
11336
|
// src/lib/pipeline-router.ts
|
|
11029
11337
|
init_database();
|
|
11030
|
-
import
|
|
11338
|
+
import crypto3 from "crypto";
|
|
11031
11339
|
async function sinkConversationStore(msg, agentResponse, agentName) {
|
|
11032
11340
|
try {
|
|
11033
11341
|
const client = getClient();
|
|
11034
|
-
const id =
|
|
11342
|
+
const id = crypto3.randomUUID();
|
|
11035
11343
|
const mediaJson = msg.media ? JSON.stringify(msg.media) : null;
|
|
11036
11344
|
await client.execute({
|
|
11037
11345
|
sql: `INSERT INTO conversations
|
|
@@ -11081,7 +11389,7 @@ async function sinkMemory(msg, agentResponse, agentName) {
|
|
|
11081
11389
|
].filter(Boolean).join("\n");
|
|
11082
11390
|
const vector = await embed2(rawText);
|
|
11083
11391
|
await writeMemory2({
|
|
11084
|
-
id:
|
|
11392
|
+
id: crypto3.randomUUID(),
|
|
11085
11393
|
agent_id: agentName ?? "gateway",
|
|
11086
11394
|
agent_role: "gateway",
|
|
11087
11395
|
session_id: `gateway-${msg.platform}`,
|
|
@@ -11751,18 +12059,18 @@ var BotRegistry = class {
|
|
|
11751
12059
|
|
|
11752
12060
|
// src/bin/exe-gateway.ts
|
|
11753
12061
|
init_employees();
|
|
11754
|
-
var CONFIG_DIR =
|
|
11755
|
-
var CONFIG_PATH3 =
|
|
12062
|
+
var CONFIG_DIR = process.env.EXE_GATEWAY_HOME || path22.join(os14.homedir(), ".exe-os");
|
|
12063
|
+
var CONFIG_PATH3 = process.env.EXE_GATEWAY_CONFIG || path22.join(CONFIG_DIR, "gateway.json");
|
|
11756
12064
|
var DEFAULT_PORT = 3100;
|
|
11757
12065
|
function loadConfig2() {
|
|
11758
|
-
if (!
|
|
12066
|
+
if (!existsSync18(CONFIG_PATH3)) {
|
|
11759
12067
|
console.log(
|
|
11760
12068
|
`[exe-gateway] No config at ${CONFIG_PATH3} \u2014 using defaults (port ${DEFAULT_PORT})`
|
|
11761
12069
|
);
|
|
11762
12070
|
return {};
|
|
11763
12071
|
}
|
|
11764
12072
|
try {
|
|
11765
|
-
const raw =
|
|
12073
|
+
const raw = readFileSync15(CONFIG_PATH3, "utf-8");
|
|
11766
12074
|
return JSON.parse(raw);
|
|
11767
12075
|
} catch (err) {
|
|
11768
12076
|
console.error(
|