@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/gateway/index.js
CHANGED
|
@@ -391,6 +391,44 @@ var init_db_retry = __esm({
|
|
|
391
391
|
}
|
|
392
392
|
});
|
|
393
393
|
|
|
394
|
+
// src/lib/secure-files.ts
|
|
395
|
+
import { chmodSync, existsSync, mkdirSync } from "fs";
|
|
396
|
+
import { chmod, mkdir } from "fs/promises";
|
|
397
|
+
async function ensurePrivateDir(dirPath) {
|
|
398
|
+
await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
399
|
+
try {
|
|
400
|
+
await chmod(dirPath, PRIVATE_DIR_MODE);
|
|
401
|
+
} catch {
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
function ensurePrivateDirSync(dirPath) {
|
|
405
|
+
mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
406
|
+
try {
|
|
407
|
+
chmodSync(dirPath, PRIVATE_DIR_MODE);
|
|
408
|
+
} catch {
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
async function enforcePrivateFile(filePath) {
|
|
412
|
+
try {
|
|
413
|
+
await chmod(filePath, PRIVATE_FILE_MODE);
|
|
414
|
+
} catch {
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
function enforcePrivateFileSync(filePath) {
|
|
418
|
+
try {
|
|
419
|
+
if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
|
|
420
|
+
} catch {
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
|
|
424
|
+
var init_secure_files = __esm({
|
|
425
|
+
"src/lib/secure-files.ts"() {
|
|
426
|
+
"use strict";
|
|
427
|
+
PRIVATE_DIR_MODE = 448;
|
|
428
|
+
PRIVATE_FILE_MODE = 384;
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
|
|
394
432
|
// src/lib/config.ts
|
|
395
433
|
var config_exports = {};
|
|
396
434
|
__export(config_exports, {
|
|
@@ -407,8 +445,8 @@ __export(config_exports, {
|
|
|
407
445
|
migrateConfig: () => migrateConfig,
|
|
408
446
|
saveConfig: () => saveConfig
|
|
409
447
|
});
|
|
410
|
-
import { readFile, writeFile
|
|
411
|
-
import { readFileSync, existsSync, renameSync } from "fs";
|
|
448
|
+
import { readFile, writeFile } from "fs/promises";
|
|
449
|
+
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
412
450
|
import path from "path";
|
|
413
451
|
import os from "os";
|
|
414
452
|
function resolveDataDir() {
|
|
@@ -416,7 +454,7 @@ function resolveDataDir() {
|
|
|
416
454
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
417
455
|
const newDir = path.join(os.homedir(), ".exe-os");
|
|
418
456
|
const legacyDir = path.join(os.homedir(), ".exe-mem");
|
|
419
|
-
if (!
|
|
457
|
+
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
420
458
|
try {
|
|
421
459
|
renameSync(legacyDir, newDir);
|
|
422
460
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -479,9 +517,9 @@ function normalizeAutoUpdate(raw) {
|
|
|
479
517
|
}
|
|
480
518
|
async function loadConfig() {
|
|
481
519
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
482
|
-
await
|
|
520
|
+
await ensurePrivateDir(dir);
|
|
483
521
|
const configPath = path.join(dir, "config.json");
|
|
484
|
-
if (!
|
|
522
|
+
if (!existsSync2(configPath)) {
|
|
485
523
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
486
524
|
}
|
|
487
525
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -494,6 +532,7 @@ async function loadConfig() {
|
|
|
494
532
|
`);
|
|
495
533
|
try {
|
|
496
534
|
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
535
|
+
await enforcePrivateFile(configPath);
|
|
497
536
|
} catch {
|
|
498
537
|
}
|
|
499
538
|
}
|
|
@@ -512,7 +551,7 @@ async function loadConfig() {
|
|
|
512
551
|
function loadConfigSync() {
|
|
513
552
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
514
553
|
const configPath = path.join(dir, "config.json");
|
|
515
|
-
if (!
|
|
554
|
+
if (!existsSync2(configPath)) {
|
|
516
555
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
517
556
|
}
|
|
518
557
|
try {
|
|
@@ -530,12 +569,10 @@ function loadConfigSync() {
|
|
|
530
569
|
}
|
|
531
570
|
async function saveConfig(config2) {
|
|
532
571
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
533
|
-
await
|
|
572
|
+
await ensurePrivateDir(dir);
|
|
534
573
|
const configPath = path.join(dir, "config.json");
|
|
535
574
|
await writeFile(configPath, JSON.stringify(config2, null, 2) + "\n");
|
|
536
|
-
|
|
537
|
-
await chmod(configPath, 384);
|
|
538
|
-
}
|
|
575
|
+
await enforcePrivateFile(configPath);
|
|
539
576
|
}
|
|
540
577
|
async function loadConfigFrom(configPath) {
|
|
541
578
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -555,6 +592,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
|
|
|
555
592
|
var init_config = __esm({
|
|
556
593
|
"src/lib/config.ts"() {
|
|
557
594
|
"use strict";
|
|
595
|
+
init_secure_files();
|
|
558
596
|
EXE_AI_DIR = resolveDataDir();
|
|
559
597
|
DB_PATH = path.join(EXE_AI_DIR, "memories.db");
|
|
560
598
|
MODELS_DIR = path.join(EXE_AI_DIR, "models");
|
|
@@ -671,10 +709,10 @@ __export(agent_config_exports, {
|
|
|
671
709
|
saveAgentConfig: () => saveAgentConfig,
|
|
672
710
|
setAgentRuntime: () => setAgentRuntime
|
|
673
711
|
});
|
|
674
|
-
import { readFileSync as readFileSync2, writeFileSync, existsSync as
|
|
712
|
+
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
|
|
675
713
|
import path2 from "path";
|
|
676
714
|
function loadAgentConfig() {
|
|
677
|
-
if (!
|
|
715
|
+
if (!existsSync3(AGENT_CONFIG_PATH)) return {};
|
|
678
716
|
try {
|
|
679
717
|
return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
|
|
680
718
|
} catch {
|
|
@@ -683,8 +721,9 @@ function loadAgentConfig() {
|
|
|
683
721
|
}
|
|
684
722
|
function saveAgentConfig(config2) {
|
|
685
723
|
const dir = path2.dirname(AGENT_CONFIG_PATH);
|
|
686
|
-
|
|
724
|
+
ensurePrivateDirSync(dir);
|
|
687
725
|
writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config2, null, 2) + "\n", "utf-8");
|
|
726
|
+
enforcePrivateFileSync(AGENT_CONFIG_PATH);
|
|
688
727
|
}
|
|
689
728
|
function getAgentRuntime(agentId) {
|
|
690
729
|
const config2 = loadAgentConfig();
|
|
@@ -724,6 +763,7 @@ var init_agent_config = __esm({
|
|
|
724
763
|
"use strict";
|
|
725
764
|
init_config();
|
|
726
765
|
init_runtime_table();
|
|
766
|
+
init_secure_files();
|
|
727
767
|
AGENT_CONFIG_PATH = path2.join(EXE_AI_DIR, "agent-config.json");
|
|
728
768
|
KNOWN_RUNTIMES = {
|
|
729
769
|
claude: ["claude-opus-4", "claude-sonnet-4", "claude-haiku-4.5"],
|
|
@@ -771,7 +811,7 @@ __export(employees_exports, {
|
|
|
771
811
|
validateEmployeeName: () => validateEmployeeName
|
|
772
812
|
});
|
|
773
813
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
774
|
-
import { existsSync as
|
|
814
|
+
import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
775
815
|
import { execSync } from "child_process";
|
|
776
816
|
import path3 from "path";
|
|
777
817
|
import os2 from "os";
|
|
@@ -810,7 +850,7 @@ function validateEmployeeName(name) {
|
|
|
810
850
|
return { valid: true };
|
|
811
851
|
}
|
|
812
852
|
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
813
|
-
if (!
|
|
853
|
+
if (!existsSync4(employeesPath)) {
|
|
814
854
|
return [];
|
|
815
855
|
}
|
|
816
856
|
const raw = await readFile2(employeesPath, "utf-8");
|
|
@@ -825,7 +865,7 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
|
825
865
|
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
826
866
|
}
|
|
827
867
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
828
|
-
if (!
|
|
868
|
+
if (!existsSync4(employeesPath)) return [];
|
|
829
869
|
try {
|
|
830
870
|
return JSON.parse(readFileSync3(employeesPath, "utf-8"));
|
|
831
871
|
} catch {
|
|
@@ -873,7 +913,7 @@ function appendToCoordinatorTeam(employee) {
|
|
|
873
913
|
const coordinator = getCoordinatorEmployee(loadEmployeesSync());
|
|
874
914
|
if (!coordinator) return;
|
|
875
915
|
const idPath = path3.join(IDENTITY_DIR, `${coordinator.name}.md`);
|
|
876
|
-
if (!
|
|
916
|
+
if (!existsSync4(idPath)) return;
|
|
877
917
|
const content = readFileSync3(idPath, "utf-8");
|
|
878
918
|
if (content.includes(`**${capitalize(employee.name)}`)) return;
|
|
879
919
|
const teamMatch = content.match(TEAM_SECTION_RE);
|
|
@@ -927,9 +967,9 @@ async function normalizeRosterCase(rosterPath) {
|
|
|
927
967
|
const identityDir = path3.join(os2.homedir(), ".exe-os", "identity");
|
|
928
968
|
const oldPath = path3.join(identityDir, `${oldName}.md`);
|
|
929
969
|
const newPath = path3.join(identityDir, `${emp.name}.md`);
|
|
930
|
-
if (
|
|
970
|
+
if (existsSync4(oldPath) && !existsSync4(newPath)) {
|
|
931
971
|
renameSync2(oldPath, newPath);
|
|
932
|
-
} else if (
|
|
972
|
+
} else if (existsSync4(oldPath) && oldPath !== newPath) {
|
|
933
973
|
const content = readFileSync3(oldPath, "utf-8");
|
|
934
974
|
writeFileSync2(newPath, content, "utf-8");
|
|
935
975
|
if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
|
|
@@ -972,7 +1012,7 @@ function registerBinSymlinks(name) {
|
|
|
972
1012
|
for (const suffix of ["", "-opencode"]) {
|
|
973
1013
|
const linkName = `${name}${suffix}`;
|
|
974
1014
|
const linkPath = path3.join(binDir, linkName);
|
|
975
|
-
if (
|
|
1015
|
+
if (existsSync4(linkPath)) {
|
|
976
1016
|
skipped.push(linkName);
|
|
977
1017
|
continue;
|
|
978
1018
|
}
|
|
@@ -1925,6 +1965,7 @@ async function ensureSchema() {
|
|
|
1925
1965
|
project TEXT NOT NULL,
|
|
1926
1966
|
summary TEXT NOT NULL,
|
|
1927
1967
|
task_file TEXT,
|
|
1968
|
+
session_scope TEXT,
|
|
1928
1969
|
read INTEGER NOT NULL DEFAULT 0,
|
|
1929
1970
|
created_at TEXT NOT NULL
|
|
1930
1971
|
);
|
|
@@ -1933,7 +1974,7 @@ async function ensureSchema() {
|
|
|
1933
1974
|
ON notifications(read);
|
|
1934
1975
|
|
|
1935
1976
|
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
1936
|
-
ON notifications(agent_id);
|
|
1977
|
+
ON notifications(agent_id, session_scope);
|
|
1937
1978
|
|
|
1938
1979
|
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
1939
1980
|
ON notifications(task_file);
|
|
@@ -1971,6 +2012,7 @@ async function ensureSchema() {
|
|
|
1971
2012
|
target_agent TEXT NOT NULL,
|
|
1972
2013
|
target_project TEXT,
|
|
1973
2014
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
2015
|
+
session_scope TEXT,
|
|
1974
2016
|
content TEXT NOT NULL,
|
|
1975
2017
|
priority TEXT DEFAULT 'normal',
|
|
1976
2018
|
status TEXT DEFAULT 'pending',
|
|
@@ -1984,10 +2026,31 @@ async function ensureSchema() {
|
|
|
1984
2026
|
);
|
|
1985
2027
|
|
|
1986
2028
|
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
1987
|
-
ON messages(target_agent, status);
|
|
2029
|
+
ON messages(target_agent, session_scope, status);
|
|
1988
2030
|
|
|
1989
2031
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
1990
|
-
ON messages(target_agent, from_agent, server_seq);
|
|
2032
|
+
ON messages(target_agent, session_scope, from_agent, server_seq);
|
|
2033
|
+
`);
|
|
2034
|
+
try {
|
|
2035
|
+
await client.execute({
|
|
2036
|
+
sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
|
|
2037
|
+
args: []
|
|
2038
|
+
});
|
|
2039
|
+
} catch {
|
|
2040
|
+
}
|
|
2041
|
+
try {
|
|
2042
|
+
await client.execute({
|
|
2043
|
+
sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
|
|
2044
|
+
args: []
|
|
2045
|
+
});
|
|
2046
|
+
} catch {
|
|
2047
|
+
}
|
|
2048
|
+
await client.executeMultiple(`
|
|
2049
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
|
|
2050
|
+
ON notifications(agent_id, session_scope, read, created_at);
|
|
2051
|
+
|
|
2052
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
|
|
2053
|
+
ON messages(target_agent, session_scope, status, created_at);
|
|
1991
2054
|
`);
|
|
1992
2055
|
try {
|
|
1993
2056
|
await client.execute({
|
|
@@ -2571,6 +2634,13 @@ async function ensureSchema() {
|
|
|
2571
2634
|
} catch {
|
|
2572
2635
|
}
|
|
2573
2636
|
}
|
|
2637
|
+
try {
|
|
2638
|
+
await client.execute({
|
|
2639
|
+
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
2640
|
+
args: []
|
|
2641
|
+
});
|
|
2642
|
+
} catch {
|
|
2643
|
+
}
|
|
2574
2644
|
}
|
|
2575
2645
|
async function disposeDatabase() {
|
|
2576
2646
|
if (_walCheckpointTimer) {
|
|
@@ -2617,13 +2687,50 @@ var init_memory = __esm({
|
|
|
2617
2687
|
}
|
|
2618
2688
|
});
|
|
2619
2689
|
|
|
2690
|
+
// src/lib/daemon-auth.ts
|
|
2691
|
+
import crypto from "crypto";
|
|
2692
|
+
import path5 from "path";
|
|
2693
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
2694
|
+
function normalizeToken(token) {
|
|
2695
|
+
if (!token) return null;
|
|
2696
|
+
const trimmed = token.trim();
|
|
2697
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
2698
|
+
}
|
|
2699
|
+
function readDaemonToken() {
|
|
2700
|
+
try {
|
|
2701
|
+
if (!existsSync5(DAEMON_TOKEN_PATH)) return null;
|
|
2702
|
+
return normalizeToken(readFileSync4(DAEMON_TOKEN_PATH, "utf8"));
|
|
2703
|
+
} catch {
|
|
2704
|
+
return null;
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
function ensureDaemonToken(seed) {
|
|
2708
|
+
const existing = readDaemonToken();
|
|
2709
|
+
if (existing) return existing;
|
|
2710
|
+
const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
|
|
2711
|
+
ensurePrivateDirSync(EXE_AI_DIR);
|
|
2712
|
+
writeFileSync3(DAEMON_TOKEN_PATH, `${token}
|
|
2713
|
+
`, "utf8");
|
|
2714
|
+
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
2715
|
+
return token;
|
|
2716
|
+
}
|
|
2717
|
+
var DAEMON_TOKEN_PATH;
|
|
2718
|
+
var init_daemon_auth = __esm({
|
|
2719
|
+
"src/lib/daemon-auth.ts"() {
|
|
2720
|
+
"use strict";
|
|
2721
|
+
init_config();
|
|
2722
|
+
init_secure_files();
|
|
2723
|
+
DAEMON_TOKEN_PATH = path5.join(EXE_AI_DIR, "exed.token");
|
|
2724
|
+
}
|
|
2725
|
+
});
|
|
2726
|
+
|
|
2620
2727
|
// src/lib/exe-daemon-client.ts
|
|
2621
2728
|
import net from "net";
|
|
2622
2729
|
import os4 from "os";
|
|
2623
2730
|
import { spawn } from "child_process";
|
|
2624
2731
|
import { randomUUID } from "crypto";
|
|
2625
|
-
import { existsSync as
|
|
2626
|
-
import
|
|
2732
|
+
import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync5, openSync, closeSync, statSync } from "fs";
|
|
2733
|
+
import path6 from "path";
|
|
2627
2734
|
import { fileURLToPath } from "url";
|
|
2628
2735
|
function handleData(chunk) {
|
|
2629
2736
|
_buffer += chunk.toString();
|
|
@@ -2651,9 +2758,9 @@ function handleData(chunk) {
|
|
|
2651
2758
|
}
|
|
2652
2759
|
}
|
|
2653
2760
|
function cleanupStaleFiles() {
|
|
2654
|
-
if (
|
|
2761
|
+
if (existsSync6(PID_PATH)) {
|
|
2655
2762
|
try {
|
|
2656
|
-
const pid = parseInt(
|
|
2763
|
+
const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
|
|
2657
2764
|
if (pid > 0) {
|
|
2658
2765
|
try {
|
|
2659
2766
|
process.kill(pid, 0);
|
|
@@ -2674,11 +2781,11 @@ function cleanupStaleFiles() {
|
|
|
2674
2781
|
}
|
|
2675
2782
|
}
|
|
2676
2783
|
function findPackageRoot() {
|
|
2677
|
-
let dir =
|
|
2678
|
-
const { root } =
|
|
2784
|
+
let dir = path6.dirname(fileURLToPath(import.meta.url));
|
|
2785
|
+
const { root } = path6.parse(dir);
|
|
2679
2786
|
while (dir !== root) {
|
|
2680
|
-
if (
|
|
2681
|
-
dir =
|
|
2787
|
+
if (existsSync6(path6.join(dir, "package.json"))) return dir;
|
|
2788
|
+
dir = path6.dirname(dir);
|
|
2682
2789
|
}
|
|
2683
2790
|
return null;
|
|
2684
2791
|
}
|
|
@@ -2704,16 +2811,17 @@ function spawnDaemon() {
|
|
|
2704
2811
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
2705
2812
|
return;
|
|
2706
2813
|
}
|
|
2707
|
-
const daemonPath =
|
|
2708
|
-
if (!
|
|
2814
|
+
const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
2815
|
+
if (!existsSync6(daemonPath)) {
|
|
2709
2816
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
2710
2817
|
`);
|
|
2711
2818
|
return;
|
|
2712
2819
|
}
|
|
2713
2820
|
const resolvedPath = daemonPath;
|
|
2821
|
+
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
2714
2822
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
2715
2823
|
`);
|
|
2716
|
-
const logPath =
|
|
2824
|
+
const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
|
|
2717
2825
|
let stderrFd = "ignore";
|
|
2718
2826
|
try {
|
|
2719
2827
|
stderrFd = openSync(logPath, "a");
|
|
@@ -2731,7 +2839,8 @@ function spawnDaemon() {
|
|
|
2731
2839
|
TMUX_PANE: void 0,
|
|
2732
2840
|
// Prevents resolveExeSession() from scoping to one session
|
|
2733
2841
|
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
2734
|
-
EXE_DAEMON_PID: PID_PATH
|
|
2842
|
+
EXE_DAEMON_PID: PID_PATH,
|
|
2843
|
+
[DAEMON_TOKEN_ENV]: daemonToken
|
|
2735
2844
|
}
|
|
2736
2845
|
});
|
|
2737
2846
|
child.unref();
|
|
@@ -2841,13 +2950,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
2841
2950
|
return;
|
|
2842
2951
|
}
|
|
2843
2952
|
const id = randomUUID();
|
|
2953
|
+
const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
|
|
2844
2954
|
const timer = setTimeout(() => {
|
|
2845
2955
|
_pending.delete(id);
|
|
2846
2956
|
resolve({ error: "Request timeout" });
|
|
2847
2957
|
}, timeoutMs);
|
|
2848
2958
|
_pending.set(id, { resolve, timer });
|
|
2849
2959
|
try {
|
|
2850
|
-
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
2960
|
+
_socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
|
|
2851
2961
|
} catch {
|
|
2852
2962
|
clearTimeout(timer);
|
|
2853
2963
|
_pending.delete(id);
|
|
@@ -2876,9 +2986,9 @@ function killAndRespawnDaemon() {
|
|
|
2876
2986
|
}
|
|
2877
2987
|
try {
|
|
2878
2988
|
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
2879
|
-
if (
|
|
2989
|
+
if (existsSync6(PID_PATH)) {
|
|
2880
2990
|
try {
|
|
2881
|
-
const pid = parseInt(
|
|
2991
|
+
const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
|
|
2882
2992
|
if (pid > 0) {
|
|
2883
2993
|
try {
|
|
2884
2994
|
process.kill(pid, "SIGKILL");
|
|
@@ -2995,17 +3105,19 @@ function disconnectClient() {
|
|
|
2995
3105
|
entry.resolve({ error: "Client disconnected" });
|
|
2996
3106
|
}
|
|
2997
3107
|
}
|
|
2998
|
-
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;
|
|
3108
|
+
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;
|
|
2999
3109
|
var init_exe_daemon_client = __esm({
|
|
3000
3110
|
"src/lib/exe-daemon-client.ts"() {
|
|
3001
3111
|
"use strict";
|
|
3002
3112
|
init_config();
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3113
|
+
init_daemon_auth();
|
|
3114
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
|
|
3115
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
|
|
3116
|
+
SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
3006
3117
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
3007
3118
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
3008
3119
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
3120
|
+
DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
|
|
3009
3121
|
_socket = null;
|
|
3010
3122
|
_connected = false;
|
|
3011
3123
|
_buffer = "";
|
|
@@ -3057,10 +3169,10 @@ async function disposeEmbedder() {
|
|
|
3057
3169
|
async function embedDirect(text) {
|
|
3058
3170
|
const llamaCpp = await import("node-llama-cpp");
|
|
3059
3171
|
const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
3060
|
-
const { existsSync:
|
|
3061
|
-
const
|
|
3062
|
-
const modelPath =
|
|
3063
|
-
if (!
|
|
3172
|
+
const { existsSync: existsSync18 } = await import("fs");
|
|
3173
|
+
const path22 = await import("path");
|
|
3174
|
+
const modelPath = path22.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
|
|
3175
|
+
if (!existsSync18(modelPath)) {
|
|
3064
3176
|
throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
|
|
3065
3177
|
}
|
|
3066
3178
|
const llama = await llamaCpp.getLlama();
|
|
@@ -3090,14 +3202,14 @@ var init_embedder = __esm({
|
|
|
3090
3202
|
|
|
3091
3203
|
// src/lib/keychain.ts
|
|
3092
3204
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
3093
|
-
import { existsSync as
|
|
3094
|
-
import
|
|
3205
|
+
import { existsSync as existsSync7 } from "fs";
|
|
3206
|
+
import path7 from "path";
|
|
3095
3207
|
import os5 from "os";
|
|
3096
3208
|
function getKeyDir() {
|
|
3097
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
3209
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path7.join(os5.homedir(), ".exe-os");
|
|
3098
3210
|
}
|
|
3099
3211
|
function getKeyPath() {
|
|
3100
|
-
return
|
|
3212
|
+
return path7.join(getKeyDir(), "master.key");
|
|
3101
3213
|
}
|
|
3102
3214
|
async function tryKeytar() {
|
|
3103
3215
|
try {
|
|
@@ -3118,7 +3230,7 @@ async function getMasterKey() {
|
|
|
3118
3230
|
}
|
|
3119
3231
|
}
|
|
3120
3232
|
const keyPath = getKeyPath();
|
|
3121
|
-
if (!
|
|
3233
|
+
if (!existsSync7(keyPath)) {
|
|
3122
3234
|
process.stderr.write(
|
|
3123
3235
|
`[keychain] Key not found at ${keyPath} (HOME=${os5.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
3124
3236
|
`
|
|
@@ -3150,6 +3262,7 @@ var shard_manager_exports = {};
|
|
|
3150
3262
|
__export(shard_manager_exports, {
|
|
3151
3263
|
disposeShards: () => disposeShards,
|
|
3152
3264
|
ensureShardSchema: () => ensureShardSchema,
|
|
3265
|
+
getOpenShardCount: () => getOpenShardCount,
|
|
3153
3266
|
getReadyShardClient: () => getReadyShardClient,
|
|
3154
3267
|
getShardClient: () => getShardClient,
|
|
3155
3268
|
getShardsDir: () => getShardsDir,
|
|
@@ -3158,15 +3271,18 @@ __export(shard_manager_exports, {
|
|
|
3158
3271
|
listShards: () => listShards,
|
|
3159
3272
|
shardExists: () => shardExists
|
|
3160
3273
|
});
|
|
3161
|
-
import
|
|
3162
|
-
import { existsSync as
|
|
3274
|
+
import path8 from "path";
|
|
3275
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync2, readdirSync } from "fs";
|
|
3163
3276
|
import { createClient as createClient2 } from "@libsql/client";
|
|
3164
3277
|
function initShardManager(encryptionKey) {
|
|
3165
3278
|
_encryptionKey = encryptionKey;
|
|
3166
|
-
if (!
|
|
3279
|
+
if (!existsSync8(SHARDS_DIR)) {
|
|
3167
3280
|
mkdirSync2(SHARDS_DIR, { recursive: true });
|
|
3168
3281
|
}
|
|
3169
3282
|
_shardingEnabled = true;
|
|
3283
|
+
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
3284
|
+
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
3285
|
+
_evictionTimer.unref();
|
|
3170
3286
|
}
|
|
3171
3287
|
function isShardingEnabled() {
|
|
3172
3288
|
return _shardingEnabled;
|
|
@@ -3183,21 +3299,28 @@ function getShardClient(projectName) {
|
|
|
3183
3299
|
throw new Error(`Invalid project name for shard: "${projectName}"`);
|
|
3184
3300
|
}
|
|
3185
3301
|
const cached = _shards.get(safeName);
|
|
3186
|
-
if (cached)
|
|
3187
|
-
|
|
3302
|
+
if (cached) {
|
|
3303
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
3304
|
+
return cached;
|
|
3305
|
+
}
|
|
3306
|
+
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
3307
|
+
evictLRU();
|
|
3308
|
+
}
|
|
3309
|
+
const dbPath = path8.join(SHARDS_DIR, `${safeName}.db`);
|
|
3188
3310
|
const client = createClient2({
|
|
3189
3311
|
url: `file:${dbPath}`,
|
|
3190
3312
|
encryptionKey: _encryptionKey
|
|
3191
3313
|
});
|
|
3192
3314
|
_shards.set(safeName, client);
|
|
3315
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
3193
3316
|
return client;
|
|
3194
3317
|
}
|
|
3195
3318
|
function shardExists(projectName) {
|
|
3196
3319
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
3197
|
-
return
|
|
3320
|
+
return existsSync8(path8.join(SHARDS_DIR, `${safeName}.db`));
|
|
3198
3321
|
}
|
|
3199
3322
|
function listShards() {
|
|
3200
|
-
if (!
|
|
3323
|
+
if (!existsSync8(SHARDS_DIR)) return [];
|
|
3201
3324
|
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
3202
3325
|
}
|
|
3203
3326
|
async function ensureShardSchema(client) {
|
|
@@ -3249,6 +3372,8 @@ async function ensureShardSchema(client) {
|
|
|
3249
3372
|
for (const col of [
|
|
3250
3373
|
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
3251
3374
|
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
3375
|
+
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
3376
|
+
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
3252
3377
|
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
3253
3378
|
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
3254
3379
|
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
@@ -3386,21 +3511,69 @@ async function getReadyShardClient(projectName) {
|
|
|
3386
3511
|
await ensureShardSchema(client);
|
|
3387
3512
|
return client;
|
|
3388
3513
|
}
|
|
3514
|
+
function evictLRU() {
|
|
3515
|
+
let oldest = null;
|
|
3516
|
+
let oldestTime = Infinity;
|
|
3517
|
+
for (const [name, time] of _shardLastAccess) {
|
|
3518
|
+
if (time < oldestTime) {
|
|
3519
|
+
oldestTime = time;
|
|
3520
|
+
oldest = name;
|
|
3521
|
+
}
|
|
3522
|
+
}
|
|
3523
|
+
if (oldest) {
|
|
3524
|
+
const client = _shards.get(oldest);
|
|
3525
|
+
if (client) {
|
|
3526
|
+
client.close();
|
|
3527
|
+
}
|
|
3528
|
+
_shards.delete(oldest);
|
|
3529
|
+
_shardLastAccess.delete(oldest);
|
|
3530
|
+
}
|
|
3531
|
+
}
|
|
3532
|
+
function evictIdleShards() {
|
|
3533
|
+
const now = Date.now();
|
|
3534
|
+
const toEvict = [];
|
|
3535
|
+
for (const [name, lastAccess] of _shardLastAccess) {
|
|
3536
|
+
if (now - lastAccess > SHARD_IDLE_MS) {
|
|
3537
|
+
toEvict.push(name);
|
|
3538
|
+
}
|
|
3539
|
+
}
|
|
3540
|
+
for (const name of toEvict) {
|
|
3541
|
+
const client = _shards.get(name);
|
|
3542
|
+
if (client) {
|
|
3543
|
+
client.close();
|
|
3544
|
+
}
|
|
3545
|
+
_shards.delete(name);
|
|
3546
|
+
_shardLastAccess.delete(name);
|
|
3547
|
+
}
|
|
3548
|
+
}
|
|
3549
|
+
function getOpenShardCount() {
|
|
3550
|
+
return _shards.size;
|
|
3551
|
+
}
|
|
3389
3552
|
function disposeShards() {
|
|
3553
|
+
if (_evictionTimer) {
|
|
3554
|
+
clearInterval(_evictionTimer);
|
|
3555
|
+
_evictionTimer = null;
|
|
3556
|
+
}
|
|
3390
3557
|
for (const [, client] of _shards) {
|
|
3391
3558
|
client.close();
|
|
3392
3559
|
}
|
|
3393
3560
|
_shards.clear();
|
|
3561
|
+
_shardLastAccess.clear();
|
|
3394
3562
|
_shardingEnabled = false;
|
|
3395
3563
|
_encryptionKey = null;
|
|
3396
3564
|
}
|
|
3397
|
-
var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
|
|
3565
|
+
var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
|
|
3398
3566
|
var init_shard_manager = __esm({
|
|
3399
3567
|
"src/lib/shard-manager.ts"() {
|
|
3400
3568
|
"use strict";
|
|
3401
3569
|
init_config();
|
|
3402
|
-
SHARDS_DIR =
|
|
3570
|
+
SHARDS_DIR = path8.join(EXE_AI_DIR, "shards");
|
|
3571
|
+
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
3572
|
+
MAX_OPEN_SHARDS = 10;
|
|
3573
|
+
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
3403
3574
|
_shards = /* @__PURE__ */ new Map();
|
|
3575
|
+
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
3576
|
+
_evictionTimer = null;
|
|
3404
3577
|
_encryptionKey = null;
|
|
3405
3578
|
_shardingEnabled = false;
|
|
3406
3579
|
}
|
|
@@ -4172,8 +4345,8 @@ __export(wiki_client_exports, {
|
|
|
4172
4345
|
listDocuments: () => listDocuments,
|
|
4173
4346
|
listWorkspaces: () => listWorkspaces
|
|
4174
4347
|
});
|
|
4175
|
-
async function wikiFetch(config2,
|
|
4176
|
-
const url = `${config2.baseUrl}/api/v1${
|
|
4348
|
+
async function wikiFetch(config2, path22, method = "GET", body) {
|
|
4349
|
+
const url = `${config2.baseUrl}/api/v1${path22}`;
|
|
4177
4350
|
const headers = {
|
|
4178
4351
|
Authorization: `Bearer ${config2.apiKey}`,
|
|
4179
4352
|
"Content-Type": "application/json"
|
|
@@ -4206,7 +4379,7 @@ async function wikiFetch(config2, path21, method = "GET", body) {
|
|
|
4206
4379
|
}
|
|
4207
4380
|
}
|
|
4208
4381
|
if (!response.ok) {
|
|
4209
|
-
throw new Error(`Wiki API ${method} ${
|
|
4382
|
+
throw new Error(`Wiki API ${method} ${path22}: ${response.status} ${response.statusText}`);
|
|
4210
4383
|
}
|
|
4211
4384
|
return response.json();
|
|
4212
4385
|
} finally {
|
|
@@ -4499,13 +4672,13 @@ __export(graph_rag_exports, {
|
|
|
4499
4672
|
resolveAlias: () => resolveAlias,
|
|
4500
4673
|
storeExtraction: () => storeExtraction
|
|
4501
4674
|
});
|
|
4502
|
-
import
|
|
4675
|
+
import crypto2 from "crypto";
|
|
4503
4676
|
function normalizeEntityName(name) {
|
|
4504
4677
|
return name.replace(/\s*\([^)]*\)\s*/g, "").trim().toLowerCase();
|
|
4505
4678
|
}
|
|
4506
4679
|
function entityId(name, type) {
|
|
4507
4680
|
const normalized = normalizeEntityName(name);
|
|
4508
|
-
return
|
|
4681
|
+
return crypto2.createHash("sha256").update(`${normalized}::${type.toLowerCase()}`).digest("hex").slice(0, 16);
|
|
4509
4682
|
}
|
|
4510
4683
|
async function resolveAlias(client, name) {
|
|
4511
4684
|
const normalized = normalizeEntityName(name);
|
|
@@ -4755,7 +4928,7 @@ async function storeExtraction(client, extraction, memoryId, timestamp) {
|
|
|
4755
4928
|
const targetAlias = await resolveAlias(client, r.target);
|
|
4756
4929
|
const sourceId = sourceAlias ?? entityId(r.source, r.sourceType);
|
|
4757
4930
|
const targetId = targetAlias ?? entityId(r.target, r.targetType);
|
|
4758
|
-
const relId =
|
|
4931
|
+
const relId = crypto2.randomUUID().slice(0, 16);
|
|
4759
4932
|
try {
|
|
4760
4933
|
await client.execute({
|
|
4761
4934
|
sql: `INSERT OR IGNORE INTO entities (id, name, type, first_seen, last_seen)
|
|
@@ -4818,7 +4991,7 @@ async function storeExtraction(client, extraction, memoryId, timestamp) {
|
|
|
4818
4991
|
}
|
|
4819
4992
|
}
|
|
4820
4993
|
for (const h of extraction.hyperedges) {
|
|
4821
|
-
const hId =
|
|
4994
|
+
const hId = crypto2.randomUUID().slice(0, 16);
|
|
4822
4995
|
try {
|
|
4823
4996
|
await client.execute({
|
|
4824
4997
|
sql: `INSERT OR IGNORE INTO hyperedges (id, label, relation, confidence, timestamp)
|
|
@@ -4882,7 +5055,7 @@ async function extractBatch(client, batchSize = 50, model = "claude-haiku-4-5-20
|
|
|
4882
5055
|
totalEntities += stored.entitiesStored;
|
|
4883
5056
|
totalRelationships += stored.relationshipsStored;
|
|
4884
5057
|
}
|
|
4885
|
-
const contentHash =
|
|
5058
|
+
const contentHash = crypto2.createHash("sha256").update(rawContent).digest("hex").slice(0, 32);
|
|
4886
5059
|
await client.execute({
|
|
4887
5060
|
sql: "UPDATE memories SET graph_extracted = 1, content_hash = ?, graph_extracted_hash = ? WHERE id = ?",
|
|
4888
5061
|
args: [contentHash, contentHash, memoryId]
|
|
@@ -5227,13 +5400,13 @@ __export(whatsapp_accounts_exports, {
|
|
|
5227
5400
|
getDefaultAccount: () => getDefaultAccount,
|
|
5228
5401
|
loadAccounts: () => loadAccounts
|
|
5229
5402
|
});
|
|
5230
|
-
import { readFileSync as
|
|
5403
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
5231
5404
|
import { join as join2 } from "path";
|
|
5232
5405
|
import { homedir as homedir2 } from "os";
|
|
5233
5406
|
function loadAccounts() {
|
|
5234
5407
|
if (cachedAccounts !== null) return cachedAccounts;
|
|
5235
5408
|
try {
|
|
5236
|
-
const raw =
|
|
5409
|
+
const raw = readFileSync6(CONFIG_PATH2, "utf8");
|
|
5237
5410
|
const parsed = JSON.parse(raw);
|
|
5238
5411
|
if (!Array.isArray(parsed)) {
|
|
5239
5412
|
console.warn("[whatsapp] Config is not an array, ignoring");
|
|
@@ -5273,12 +5446,12 @@ var init_whatsapp_accounts = __esm({
|
|
|
5273
5446
|
});
|
|
5274
5447
|
|
|
5275
5448
|
// src/lib/session-registry.ts
|
|
5276
|
-
import { readFileSync as
|
|
5277
|
-
import
|
|
5449
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync9 } from "fs";
|
|
5450
|
+
import path10 from "path";
|
|
5278
5451
|
import os7 from "os";
|
|
5279
5452
|
function registerSession(entry) {
|
|
5280
|
-
const dir =
|
|
5281
|
-
if (!
|
|
5453
|
+
const dir = path10.dirname(REGISTRY_PATH);
|
|
5454
|
+
if (!existsSync9(dir)) {
|
|
5282
5455
|
mkdirSync4(dir, { recursive: true });
|
|
5283
5456
|
}
|
|
5284
5457
|
const sessions = listSessions();
|
|
@@ -5288,11 +5461,11 @@ function registerSession(entry) {
|
|
|
5288
5461
|
} else {
|
|
5289
5462
|
sessions.push(entry);
|
|
5290
5463
|
}
|
|
5291
|
-
|
|
5464
|
+
writeFileSync4(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
|
|
5292
5465
|
}
|
|
5293
5466
|
function listSessions() {
|
|
5294
5467
|
try {
|
|
5295
|
-
const raw =
|
|
5468
|
+
const raw = readFileSync7(REGISTRY_PATH, "utf8");
|
|
5296
5469
|
return JSON.parse(raw);
|
|
5297
5470
|
} catch {
|
|
5298
5471
|
return [];
|
|
@@ -5302,7 +5475,7 @@ var REGISTRY_PATH;
|
|
|
5302
5475
|
var init_session_registry = __esm({
|
|
5303
5476
|
"src/lib/session-registry.ts"() {
|
|
5304
5477
|
"use strict";
|
|
5305
|
-
REGISTRY_PATH =
|
|
5478
|
+
REGISTRY_PATH = path10.join(os7.homedir(), ".exe-os", "session-registry.json");
|
|
5306
5479
|
}
|
|
5307
5480
|
});
|
|
5308
5481
|
|
|
@@ -5563,17 +5736,17 @@ __export(intercom_queue_exports, {
|
|
|
5563
5736
|
queueIntercom: () => queueIntercom,
|
|
5564
5737
|
readQueue: () => readQueue
|
|
5565
5738
|
});
|
|
5566
|
-
import { readFileSync as
|
|
5567
|
-
import
|
|
5739
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, renameSync as renameSync3, existsSync as existsSync10, mkdirSync as mkdirSync5 } from "fs";
|
|
5740
|
+
import path11 from "path";
|
|
5568
5741
|
import os8 from "os";
|
|
5569
5742
|
function ensureDir() {
|
|
5570
|
-
const dir =
|
|
5571
|
-
if (!
|
|
5743
|
+
const dir = path11.dirname(QUEUE_PATH);
|
|
5744
|
+
if (!existsSync10(dir)) mkdirSync5(dir, { recursive: true });
|
|
5572
5745
|
}
|
|
5573
5746
|
function readQueue() {
|
|
5574
5747
|
try {
|
|
5575
|
-
if (!
|
|
5576
|
-
return JSON.parse(
|
|
5748
|
+
if (!existsSync10(QUEUE_PATH)) return [];
|
|
5749
|
+
return JSON.parse(readFileSync8(QUEUE_PATH, "utf8"));
|
|
5577
5750
|
} catch {
|
|
5578
5751
|
return [];
|
|
5579
5752
|
}
|
|
@@ -5581,7 +5754,7 @@ function readQueue() {
|
|
|
5581
5754
|
function writeQueue(queue) {
|
|
5582
5755
|
ensureDir();
|
|
5583
5756
|
const tmp = `${QUEUE_PATH}.tmp`;
|
|
5584
|
-
|
|
5757
|
+
writeFileSync5(tmp, JSON.stringify(queue, null, 2));
|
|
5585
5758
|
renameSync3(tmp, QUEUE_PATH);
|
|
5586
5759
|
}
|
|
5587
5760
|
function queueIntercom(targetSession, reason) {
|
|
@@ -5673,26 +5846,29 @@ var QUEUE_PATH, MAX_RETRIES2, TTL_MS, INTERCOM_LOG;
|
|
|
5673
5846
|
var init_intercom_queue = __esm({
|
|
5674
5847
|
"src/lib/intercom-queue.ts"() {
|
|
5675
5848
|
"use strict";
|
|
5676
|
-
QUEUE_PATH =
|
|
5849
|
+
QUEUE_PATH = path11.join(os8.homedir(), ".exe-os", "intercom-queue.json");
|
|
5677
5850
|
MAX_RETRIES2 = 5;
|
|
5678
5851
|
TTL_MS = 60 * 60 * 1e3;
|
|
5679
|
-
INTERCOM_LOG =
|
|
5852
|
+
INTERCOM_LOG = path11.join(os8.homedir(), ".exe-os", "intercom.log");
|
|
5680
5853
|
}
|
|
5681
5854
|
});
|
|
5682
5855
|
|
|
5683
5856
|
// src/lib/license.ts
|
|
5684
|
-
import { readFileSync as
|
|
5857
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, existsSync as existsSync11, mkdirSync as mkdirSync6 } from "fs";
|
|
5685
5858
|
import { randomUUID as randomUUID11 } from "crypto";
|
|
5686
|
-
import
|
|
5859
|
+
import { createRequire as createRequire2 } from "module";
|
|
5860
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
5861
|
+
import os9 from "os";
|
|
5862
|
+
import path12 from "path";
|
|
5687
5863
|
import { jwtVerify, importSPKI } from "jose";
|
|
5688
5864
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
|
|
5689
5865
|
var init_license = __esm({
|
|
5690
5866
|
"src/lib/license.ts"() {
|
|
5691
5867
|
"use strict";
|
|
5692
5868
|
init_config();
|
|
5693
|
-
LICENSE_PATH =
|
|
5694
|
-
CACHE_PATH =
|
|
5695
|
-
DEVICE_ID_PATH =
|
|
5869
|
+
LICENSE_PATH = path12.join(EXE_AI_DIR, "license.key");
|
|
5870
|
+
CACHE_PATH = path12.join(EXE_AI_DIR, "license-cache.json");
|
|
5871
|
+
DEVICE_ID_PATH = path12.join(EXE_AI_DIR, "device-id");
|
|
5696
5872
|
PLAN_LIMITS = {
|
|
5697
5873
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
5698
5874
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -5704,12 +5880,12 @@ var init_license = __esm({
|
|
|
5704
5880
|
});
|
|
5705
5881
|
|
|
5706
5882
|
// src/lib/plan-limits.ts
|
|
5707
|
-
import { readFileSync as
|
|
5708
|
-
import
|
|
5883
|
+
import { readFileSync as readFileSync10, existsSync as existsSync12 } from "fs";
|
|
5884
|
+
import path13 from "path";
|
|
5709
5885
|
function getLicenseSync() {
|
|
5710
5886
|
try {
|
|
5711
|
-
if (!
|
|
5712
|
-
const raw = JSON.parse(
|
|
5887
|
+
if (!existsSync12(CACHE_PATH2)) return freeLicense();
|
|
5888
|
+
const raw = JSON.parse(readFileSync10(CACHE_PATH2, "utf8"));
|
|
5713
5889
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
5714
5890
|
const parts = raw.token.split(".");
|
|
5715
5891
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -5747,8 +5923,8 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
5747
5923
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
5748
5924
|
let count = 0;
|
|
5749
5925
|
try {
|
|
5750
|
-
if (
|
|
5751
|
-
const raw =
|
|
5926
|
+
if (existsSync12(filePath)) {
|
|
5927
|
+
const raw = readFileSync10(filePath, "utf8");
|
|
5752
5928
|
const employees = JSON.parse(raw);
|
|
5753
5929
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
5754
5930
|
}
|
|
@@ -5777,29 +5953,63 @@ var init_plan_limits = __esm({
|
|
|
5777
5953
|
this.name = "PlanLimitError";
|
|
5778
5954
|
}
|
|
5779
5955
|
};
|
|
5780
|
-
CACHE_PATH2 =
|
|
5956
|
+
CACHE_PATH2 = path13.join(EXE_AI_DIR, "license-cache.json");
|
|
5957
|
+
}
|
|
5958
|
+
});
|
|
5959
|
+
|
|
5960
|
+
// src/lib/task-scope.ts
|
|
5961
|
+
function getCurrentSessionScope() {
|
|
5962
|
+
try {
|
|
5963
|
+
return resolveExeSession();
|
|
5964
|
+
} catch {
|
|
5965
|
+
return null;
|
|
5966
|
+
}
|
|
5967
|
+
}
|
|
5968
|
+
function sessionScopeFilter(sessionScope, tableAlias) {
|
|
5969
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
5970
|
+
if (!scope) return { sql: "", args: [] };
|
|
5971
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
5972
|
+
return {
|
|
5973
|
+
sql: ` AND (${col} IS NULL OR ${col} = ?)`,
|
|
5974
|
+
args: [scope]
|
|
5975
|
+
};
|
|
5976
|
+
}
|
|
5977
|
+
function strictSessionScopeFilter(sessionScope, tableAlias) {
|
|
5978
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
5979
|
+
if (!scope) return { sql: "", args: [] };
|
|
5980
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
5981
|
+
return {
|
|
5982
|
+
sql: ` AND ${col} = ?`,
|
|
5983
|
+
args: [scope]
|
|
5984
|
+
};
|
|
5985
|
+
}
|
|
5986
|
+
var init_task_scope = __esm({
|
|
5987
|
+
"src/lib/task-scope.ts"() {
|
|
5988
|
+
"use strict";
|
|
5989
|
+
init_tmux_routing();
|
|
5781
5990
|
}
|
|
5782
5991
|
});
|
|
5783
5992
|
|
|
5784
5993
|
// src/lib/notifications.ts
|
|
5785
|
-
import
|
|
5786
|
-
import
|
|
5787
|
-
import
|
|
5994
|
+
import crypto4 from "crypto";
|
|
5995
|
+
import path14 from "path";
|
|
5996
|
+
import os10 from "os";
|
|
5788
5997
|
import {
|
|
5789
|
-
readFileSync as
|
|
5998
|
+
readFileSync as readFileSync11,
|
|
5790
5999
|
readdirSync as readdirSync2,
|
|
5791
6000
|
unlinkSync as unlinkSync3,
|
|
5792
|
-
existsSync as
|
|
6001
|
+
existsSync as existsSync13,
|
|
5793
6002
|
rmdirSync
|
|
5794
6003
|
} from "fs";
|
|
5795
6004
|
async function writeNotification(notification) {
|
|
5796
6005
|
try {
|
|
5797
6006
|
const client = getClient();
|
|
5798
|
-
const id =
|
|
6007
|
+
const id = crypto4.randomUUID();
|
|
5799
6008
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6009
|
+
const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
|
|
5800
6010
|
await client.execute({
|
|
5801
|
-
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
|
|
5802
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
6011
|
+
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
6012
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
5803
6013
|
args: [
|
|
5804
6014
|
id,
|
|
5805
6015
|
notification.agentId,
|
|
@@ -5808,6 +6018,7 @@ async function writeNotification(notification) {
|
|
|
5808
6018
|
notification.project,
|
|
5809
6019
|
notification.summary,
|
|
5810
6020
|
notification.taskFile ?? null,
|
|
6021
|
+
sessionScope,
|
|
5811
6022
|
now
|
|
5812
6023
|
]
|
|
5813
6024
|
});
|
|
@@ -5816,12 +6027,14 @@ async function writeNotification(notification) {
|
|
|
5816
6027
|
`);
|
|
5817
6028
|
}
|
|
5818
6029
|
}
|
|
5819
|
-
async function markAsReadByTaskFile(taskFile) {
|
|
6030
|
+
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
5820
6031
|
try {
|
|
5821
6032
|
const client = getClient();
|
|
6033
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
5822
6034
|
await client.execute({
|
|
5823
|
-
sql:
|
|
5824
|
-
|
|
6035
|
+
sql: `UPDATE notifications SET read = 1
|
|
6036
|
+
WHERE task_file = ? AND read = 0${scope.sql}`,
|
|
6037
|
+
args: [taskFile, ...scope.args]
|
|
5825
6038
|
});
|
|
5826
6039
|
} catch {
|
|
5827
6040
|
}
|
|
@@ -5830,11 +6043,12 @@ var init_notifications = __esm({
|
|
|
5830
6043
|
"src/lib/notifications.ts"() {
|
|
5831
6044
|
"use strict";
|
|
5832
6045
|
init_database();
|
|
6046
|
+
init_task_scope();
|
|
5833
6047
|
}
|
|
5834
6048
|
});
|
|
5835
6049
|
|
|
5836
6050
|
// src/lib/session-kill-telemetry.ts
|
|
5837
|
-
import
|
|
6051
|
+
import crypto5 from "crypto";
|
|
5838
6052
|
async function recordSessionKill(input) {
|
|
5839
6053
|
try {
|
|
5840
6054
|
const client = getClient();
|
|
@@ -5844,7 +6058,7 @@ async function recordSessionKill(input) {
|
|
|
5844
6058
|
ticks_idle, estimated_tokens_saved)
|
|
5845
6059
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
5846
6060
|
args: [
|
|
5847
|
-
|
|
6061
|
+
crypto5.randomUUID(),
|
|
5848
6062
|
input.sessionName,
|
|
5849
6063
|
input.agentId,
|
|
5850
6064
|
(/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -5867,37 +6081,117 @@ var init_session_kill_telemetry = __esm({
|
|
|
5867
6081
|
}
|
|
5868
6082
|
});
|
|
5869
6083
|
|
|
5870
|
-
// src/lib/
|
|
5871
|
-
|
|
6084
|
+
// src/lib/project-name.ts
|
|
6085
|
+
import { execSync as execSync4 } from "child_process";
|
|
6086
|
+
import path15 from "path";
|
|
6087
|
+
function getProjectName(cwd) {
|
|
6088
|
+
const dir = cwd ?? process.cwd();
|
|
6089
|
+
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
5872
6090
|
try {
|
|
5873
|
-
|
|
6091
|
+
let repoRoot;
|
|
6092
|
+
try {
|
|
6093
|
+
const gitCommonDir = execSync4("git rev-parse --path-format=absolute --git-common-dir", {
|
|
6094
|
+
cwd: dir,
|
|
6095
|
+
encoding: "utf8",
|
|
6096
|
+
timeout: 2e3,
|
|
6097
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
6098
|
+
}).trim();
|
|
6099
|
+
repoRoot = path15.dirname(gitCommonDir);
|
|
6100
|
+
} catch {
|
|
6101
|
+
repoRoot = execSync4("git rev-parse --show-toplevel", {
|
|
6102
|
+
cwd: dir,
|
|
6103
|
+
encoding: "utf8",
|
|
6104
|
+
timeout: 2e3,
|
|
6105
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
6106
|
+
}).trim();
|
|
6107
|
+
}
|
|
6108
|
+
_cached2 = path15.basename(repoRoot);
|
|
6109
|
+
_cachedCwd = dir;
|
|
6110
|
+
return _cached2;
|
|
5874
6111
|
} catch {
|
|
5875
|
-
|
|
6112
|
+
_cached2 = path15.basename(dir);
|
|
6113
|
+
_cachedCwd = dir;
|
|
6114
|
+
return _cached2;
|
|
5876
6115
|
}
|
|
5877
6116
|
}
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
|
|
6117
|
+
var _cached2, _cachedCwd;
|
|
6118
|
+
var init_project_name = __esm({
|
|
6119
|
+
"src/lib/project-name.ts"() {
|
|
6120
|
+
"use strict";
|
|
6121
|
+
_cached2 = null;
|
|
6122
|
+
_cachedCwd = null;
|
|
6123
|
+
}
|
|
6124
|
+
});
|
|
6125
|
+
|
|
6126
|
+
// src/lib/session-scope.ts
|
|
6127
|
+
var session_scope_exports = {};
|
|
6128
|
+
__export(session_scope_exports, {
|
|
6129
|
+
assertSessionScope: () => assertSessionScope,
|
|
6130
|
+
findSessionForProject: () => findSessionForProject,
|
|
6131
|
+
getSessionProject: () => getSessionProject
|
|
6132
|
+
});
|
|
6133
|
+
function getSessionProject(sessionName) {
|
|
6134
|
+
const sessions = listSessions();
|
|
6135
|
+
const entry = sessions.find((s) => s.windowName === sessionName);
|
|
6136
|
+
if (!entry) return null;
|
|
6137
|
+
const parts = entry.projectDir.split("/").filter(Boolean);
|
|
6138
|
+
return parts[parts.length - 1] ?? null;
|
|
5886
6139
|
}
|
|
5887
|
-
|
|
5888
|
-
|
|
6140
|
+
function findSessionForProject(projectName) {
|
|
6141
|
+
const sessions = listSessions();
|
|
6142
|
+
for (const s of sessions) {
|
|
6143
|
+
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
6144
|
+
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
6145
|
+
}
|
|
6146
|
+
return null;
|
|
6147
|
+
}
|
|
6148
|
+
function assertSessionScope(actionType, targetProject) {
|
|
6149
|
+
try {
|
|
6150
|
+
const currentProject = getProjectName();
|
|
6151
|
+
const exeSession = resolveExeSession();
|
|
6152
|
+
if (!exeSession) {
|
|
6153
|
+
return { allowed: true, reason: "no_session" };
|
|
6154
|
+
}
|
|
6155
|
+
if (currentProject === targetProject) {
|
|
6156
|
+
return {
|
|
6157
|
+
allowed: true,
|
|
6158
|
+
reason: "same_session",
|
|
6159
|
+
currentProject,
|
|
6160
|
+
targetProject
|
|
6161
|
+
};
|
|
6162
|
+
}
|
|
6163
|
+
process.stderr.write(
|
|
6164
|
+
`[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
|
|
6165
|
+
`
|
|
6166
|
+
);
|
|
6167
|
+
return {
|
|
6168
|
+
allowed: false,
|
|
6169
|
+
reason: "cross_session_denied",
|
|
6170
|
+
currentProject,
|
|
6171
|
+
targetProject,
|
|
6172
|
+
targetSession: findSessionForProject(targetProject)?.windowName
|
|
6173
|
+
};
|
|
6174
|
+
} catch {
|
|
6175
|
+
return { allowed: true, reason: "no_session" };
|
|
6176
|
+
}
|
|
6177
|
+
}
|
|
6178
|
+
var init_session_scope = __esm({
|
|
6179
|
+
"src/lib/session-scope.ts"() {
|
|
5889
6180
|
"use strict";
|
|
6181
|
+
init_session_registry();
|
|
6182
|
+
init_project_name();
|
|
5890
6183
|
init_tmux_routing();
|
|
6184
|
+
init_employees();
|
|
5891
6185
|
}
|
|
5892
6186
|
});
|
|
5893
6187
|
|
|
5894
6188
|
// src/lib/tasks-crud.ts
|
|
5895
|
-
import
|
|
5896
|
-
import
|
|
5897
|
-
import
|
|
5898
|
-
import { execSync as
|
|
6189
|
+
import crypto6 from "crypto";
|
|
6190
|
+
import path16 from "path";
|
|
6191
|
+
import os11 from "os";
|
|
6192
|
+
import { execSync as execSync5 } from "child_process";
|
|
5899
6193
|
import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
|
|
5900
|
-
import { existsSync as
|
|
6194
|
+
import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
|
|
5901
6195
|
async function writeCheckpoint(input) {
|
|
5902
6196
|
const client = getClient();
|
|
5903
6197
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -6013,13 +6307,28 @@ async function resolveTask(client, identifier, scopeSession) {
|
|
|
6013
6307
|
}
|
|
6014
6308
|
async function createTaskCore(input) {
|
|
6015
6309
|
const client = getClient();
|
|
6016
|
-
const id =
|
|
6310
|
+
const id = crypto6.randomUUID();
|
|
6017
6311
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6018
6312
|
const slug = slugify(input.title);
|
|
6019
6313
|
let earlySessionScope = null;
|
|
6314
|
+
let scopeMismatchWarning;
|
|
6020
6315
|
try {
|
|
6021
6316
|
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
6022
|
-
|
|
6317
|
+
const resolved = resolveExeSession2();
|
|
6318
|
+
if (resolved && input.projectName) {
|
|
6319
|
+
const { getSessionProject: getSessionProject2 } = await Promise.resolve().then(() => (init_session_scope(), session_scope_exports));
|
|
6320
|
+
const sessionProject = getSessionProject2(resolved);
|
|
6321
|
+
if (sessionProject && sessionProject !== input.projectName) {
|
|
6322
|
+
scopeMismatchWarning = `session/project mismatch: session "${resolved}" owns "${sessionProject}" but task targets "${input.projectName}". Routed to default scope.`;
|
|
6323
|
+
process.stderr.write(`[create_task] ${scopeMismatchWarning}
|
|
6324
|
+
`);
|
|
6325
|
+
earlySessionScope = null;
|
|
6326
|
+
} else {
|
|
6327
|
+
earlySessionScope = resolved;
|
|
6328
|
+
}
|
|
6329
|
+
} else {
|
|
6330
|
+
earlySessionScope = resolved;
|
|
6331
|
+
}
|
|
6023
6332
|
} catch {
|
|
6024
6333
|
}
|
|
6025
6334
|
const scope = earlySessionScope ?? "default";
|
|
@@ -6070,10 +6379,14 @@ async function createTaskCore(input) {
|
|
|
6070
6379
|
${laneWarning}` : laneWarning;
|
|
6071
6380
|
}
|
|
6072
6381
|
}
|
|
6382
|
+
if (scopeMismatchWarning) {
|
|
6383
|
+
warning = warning ? `${warning}
|
|
6384
|
+
${scopeMismatchWarning}` : scopeMismatchWarning;
|
|
6385
|
+
}
|
|
6073
6386
|
if (input.baseDir) {
|
|
6074
6387
|
try {
|
|
6075
|
-
await mkdir4(
|
|
6076
|
-
await mkdir4(
|
|
6388
|
+
await mkdir4(path16.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
6389
|
+
await mkdir4(path16.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
6077
6390
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
6078
6391
|
await ensureGitignoreExe(input.baseDir);
|
|
6079
6392
|
} catch {
|
|
@@ -6109,13 +6422,19 @@ ${laneWarning}` : laneWarning;
|
|
|
6109
6422
|
});
|
|
6110
6423
|
if (input.baseDir) {
|
|
6111
6424
|
try {
|
|
6112
|
-
const EXE_OS_DIR =
|
|
6113
|
-
const mdPath =
|
|
6114
|
-
const mdDir =
|
|
6115
|
-
if (!
|
|
6425
|
+
const EXE_OS_DIR = path16.join(os11.homedir(), ".exe-os");
|
|
6426
|
+
const mdPath = path16.join(EXE_OS_DIR, taskFile);
|
|
6427
|
+
const mdDir = path16.dirname(mdPath);
|
|
6428
|
+
if (!existsSync14(mdDir)) await mkdir4(mdDir, { recursive: true });
|
|
6116
6429
|
const reviewer = input.reviewer ?? input.assignedBy;
|
|
6117
6430
|
const mdContent = `# ${input.title}
|
|
6118
6431
|
|
|
6432
|
+
## MANDATORY: When done
|
|
6433
|
+
|
|
6434
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
6435
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
6436
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
6437
|
+
|
|
6119
6438
|
**ID:** ${id}
|
|
6120
6439
|
**Status:** ${initialStatus}
|
|
6121
6440
|
**Priority:** ${input.priority}
|
|
@@ -6129,12 +6448,6 @@ ${laneWarning}` : laneWarning;
|
|
|
6129
6448
|
## Context
|
|
6130
6449
|
|
|
6131
6450
|
${input.context}
|
|
6132
|
-
|
|
6133
|
-
## MANDATORY: When done
|
|
6134
|
-
|
|
6135
|
-
You MUST call update_task with status "done" and a result summary when finished.
|
|
6136
|
-
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
6137
|
-
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
6138
6451
|
`;
|
|
6139
6452
|
await writeFile4(mdPath, mdContent, "utf-8");
|
|
6140
6453
|
} catch (err) {
|
|
@@ -6216,14 +6529,14 @@ function isTmuxSessionAlive(identifier) {
|
|
|
6216
6529
|
if (!identifier || identifier === "unknown") return true;
|
|
6217
6530
|
try {
|
|
6218
6531
|
if (identifier.startsWith("%")) {
|
|
6219
|
-
const output =
|
|
6532
|
+
const output = execSync5("tmux list-panes -a -F '#{pane_id}'", {
|
|
6220
6533
|
timeout: 2e3,
|
|
6221
6534
|
encoding: "utf8",
|
|
6222
6535
|
stdio: ["pipe", "pipe", "pipe"]
|
|
6223
6536
|
});
|
|
6224
6537
|
return output.split("\n").some((l) => l.trim() === identifier);
|
|
6225
6538
|
} else {
|
|
6226
|
-
|
|
6539
|
+
execSync5(`tmux has-session -t ${JSON.stringify(identifier)}`, {
|
|
6227
6540
|
timeout: 2e3,
|
|
6228
6541
|
stdio: ["pipe", "pipe", "pipe"]
|
|
6229
6542
|
});
|
|
@@ -6232,7 +6545,7 @@ function isTmuxSessionAlive(identifier) {
|
|
|
6232
6545
|
} catch {
|
|
6233
6546
|
if (identifier.startsWith("%")) return true;
|
|
6234
6547
|
try {
|
|
6235
|
-
|
|
6548
|
+
execSync5("tmux list-sessions", {
|
|
6236
6549
|
timeout: 2e3,
|
|
6237
6550
|
stdio: ["pipe", "pipe", "pipe"]
|
|
6238
6551
|
});
|
|
@@ -6247,12 +6560,12 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
|
|
|
6247
6560
|
if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
|
|
6248
6561
|
try {
|
|
6249
6562
|
const since = new Date(taskCreatedAt).toISOString();
|
|
6250
|
-
const branch =
|
|
6563
|
+
const branch = execSync5(
|
|
6251
6564
|
"git rev-parse --abbrev-ref HEAD 2>/dev/null",
|
|
6252
6565
|
{ encoding: "utf8", timeout: 3e3 }
|
|
6253
6566
|
).trim();
|
|
6254
6567
|
const branchArg = branch && branch !== "HEAD" ? branch : "";
|
|
6255
|
-
const commitCount =
|
|
6568
|
+
const commitCount = execSync5(
|
|
6256
6569
|
`git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
|
|
6257
6570
|
{ encoding: "utf8", timeout: 5e3 }
|
|
6258
6571
|
).trim();
|
|
@@ -6383,7 +6696,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
6383
6696
|
await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
|
|
6384
6697
|
} catch {
|
|
6385
6698
|
}
|
|
6386
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
6699
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
6387
6700
|
try {
|
|
6388
6701
|
const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
6389
6702
|
clearQueueForAgent2(String(row.assigned_to));
|
|
@@ -6412,9 +6725,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
6412
6725
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
6413
6726
|
}
|
|
6414
6727
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
6415
|
-
const archPath =
|
|
6728
|
+
const archPath = path16.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
6416
6729
|
try {
|
|
6417
|
-
if (
|
|
6730
|
+
if (existsSync14(archPath)) return;
|
|
6418
6731
|
const template = [
|
|
6419
6732
|
`# ${projectName} \u2014 System Architecture`,
|
|
6420
6733
|
"",
|
|
@@ -6447,10 +6760,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
6447
6760
|
}
|
|
6448
6761
|
}
|
|
6449
6762
|
async function ensureGitignoreExe(baseDir) {
|
|
6450
|
-
const gitignorePath =
|
|
6763
|
+
const gitignorePath = path16.join(baseDir, ".gitignore");
|
|
6451
6764
|
try {
|
|
6452
|
-
if (
|
|
6453
|
-
const content =
|
|
6765
|
+
if (existsSync14(gitignorePath)) {
|
|
6766
|
+
const content = readFileSync12(gitignorePath, "utf-8");
|
|
6454
6767
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
6455
6768
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
6456
6769
|
} else {
|
|
@@ -6481,58 +6794,42 @@ var init_tasks_crud = __esm({
|
|
|
6481
6794
|
});
|
|
6482
6795
|
|
|
6483
6796
|
// src/lib/tasks-review.ts
|
|
6484
|
-
import
|
|
6485
|
-
import { existsSync as
|
|
6797
|
+
import path17 from "path";
|
|
6798
|
+
import { existsSync as existsSync15, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
|
|
6486
6799
|
async function countPendingReviews(sessionScope) {
|
|
6487
6800
|
const client = getClient();
|
|
6488
|
-
|
|
6489
|
-
|
|
6490
|
-
|
|
6491
|
-
args: [sessionScope]
|
|
6492
|
-
});
|
|
6493
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
6494
|
-
}
|
|
6801
|
+
const scope = strictSessionScopeFilter(
|
|
6802
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
6803
|
+
);
|
|
6495
6804
|
const result = await client.execute({
|
|
6496
|
-
sql:
|
|
6497
|
-
|
|
6805
|
+
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
6806
|
+
WHERE status = 'needs_review'${scope.sql}`,
|
|
6807
|
+
args: [...scope.args]
|
|
6498
6808
|
});
|
|
6499
6809
|
return Number(result.rows[0]?.cnt) || 0;
|
|
6500
6810
|
}
|
|
6501
6811
|
async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
6502
6812
|
const client = getClient();
|
|
6503
|
-
|
|
6504
|
-
|
|
6505
|
-
|
|
6506
|
-
WHERE status = 'needs_review' AND updated_at > ?
|
|
6507
|
-
AND session_scope = ?`,
|
|
6508
|
-
args: [sinceIso, sessionScope]
|
|
6509
|
-
});
|
|
6510
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
6511
|
-
}
|
|
6813
|
+
const scope = strictSessionScopeFilter(
|
|
6814
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
6815
|
+
);
|
|
6512
6816
|
const result = await client.execute({
|
|
6513
6817
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
6514
|
-
WHERE status = 'needs_review' AND updated_at >
|
|
6515
|
-
args: [sinceIso]
|
|
6818
|
+
WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
|
|
6819
|
+
args: [sinceIso, ...scope.args]
|
|
6516
6820
|
});
|
|
6517
6821
|
return Number(result.rows[0]?.cnt) || 0;
|
|
6518
6822
|
}
|
|
6519
6823
|
async function listPendingReviews(limit, sessionScope) {
|
|
6520
6824
|
const client = getClient();
|
|
6521
|
-
|
|
6522
|
-
|
|
6523
|
-
|
|
6524
|
-
WHERE status = 'needs_review'
|
|
6525
|
-
AND session_scope = ?
|
|
6526
|
-
ORDER BY updated_at ASC LIMIT ?`,
|
|
6527
|
-
args: [sessionScope, limit]
|
|
6528
|
-
});
|
|
6529
|
-
return result2.rows;
|
|
6530
|
-
}
|
|
6825
|
+
const scope = strictSessionScopeFilter(
|
|
6826
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
6827
|
+
);
|
|
6531
6828
|
const result = await client.execute({
|
|
6532
6829
|
sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
|
|
6533
|
-
WHERE status = 'needs_review'
|
|
6830
|
+
WHERE status = 'needs_review'${scope.sql}
|
|
6534
6831
|
ORDER BY updated_at ASC LIMIT ?`,
|
|
6535
|
-
args: [limit]
|
|
6832
|
+
args: [...scope.args, limit]
|
|
6536
6833
|
});
|
|
6537
6834
|
return result.rows;
|
|
6538
6835
|
}
|
|
@@ -6544,7 +6841,7 @@ async function cleanupOrphanedReviews() {
|
|
|
6544
6841
|
WHERE status IN ('open', 'needs_review', 'in_progress')
|
|
6545
6842
|
AND assigned_by = 'system'
|
|
6546
6843
|
AND title LIKE 'Review:%'
|
|
6547
|
-
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
|
|
6844
|
+
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
|
|
6548
6845
|
args: [now]
|
|
6549
6846
|
});
|
|
6550
6847
|
const r1b = await client.execute({
|
|
@@ -6663,11 +6960,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
6663
6960
|
);
|
|
6664
6961
|
}
|
|
6665
6962
|
try {
|
|
6666
|
-
const cacheDir =
|
|
6667
|
-
if (
|
|
6963
|
+
const cacheDir = path17.join(EXE_AI_DIR, "session-cache");
|
|
6964
|
+
if (existsSync15(cacheDir)) {
|
|
6668
6965
|
for (const f of readdirSync3(cacheDir)) {
|
|
6669
6966
|
if (f.startsWith("review-notified-")) {
|
|
6670
|
-
unlinkSync4(
|
|
6967
|
+
unlinkSync4(path17.join(cacheDir, f));
|
|
6671
6968
|
}
|
|
6672
6969
|
}
|
|
6673
6970
|
}
|
|
@@ -6684,11 +6981,12 @@ var init_tasks_review = __esm({
|
|
|
6684
6981
|
init_tmux_routing();
|
|
6685
6982
|
init_session_key();
|
|
6686
6983
|
init_state_bus();
|
|
6984
|
+
init_task_scope();
|
|
6687
6985
|
}
|
|
6688
6986
|
});
|
|
6689
6987
|
|
|
6690
6988
|
// src/lib/tasks-chain.ts
|
|
6691
|
-
import
|
|
6989
|
+
import path18 from "path";
|
|
6692
6990
|
import { readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
|
|
6693
6991
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
6694
6992
|
const client = getClient();
|
|
@@ -6705,7 +7003,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
6705
7003
|
});
|
|
6706
7004
|
for (const ur of unblockedRows.rows) {
|
|
6707
7005
|
try {
|
|
6708
|
-
const ubFile =
|
|
7006
|
+
const ubFile = path18.join(baseDir, String(ur.task_file));
|
|
6709
7007
|
let ubContent = await readFile4(ubFile, "utf-8");
|
|
6710
7008
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
6711
7009
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -6740,7 +7038,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
|
|
|
6740
7038
|
const scScope = sessionScopeFilter();
|
|
6741
7039
|
const remaining = await client.execute({
|
|
6742
7040
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
6743
|
-
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
|
|
7041
|
+
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
|
|
6744
7042
|
args: [parentTaskId, ...scScope.args]
|
|
6745
7043
|
});
|
|
6746
7044
|
const cnt = Number(remaining.rows[0]?.cnt ?? 1);
|
|
@@ -6772,110 +7070,6 @@ var init_tasks_chain = __esm({
|
|
|
6772
7070
|
}
|
|
6773
7071
|
});
|
|
6774
7072
|
|
|
6775
|
-
// src/lib/project-name.ts
|
|
6776
|
-
import { execSync as execSync5 } from "child_process";
|
|
6777
|
-
import path17 from "path";
|
|
6778
|
-
function getProjectName(cwd) {
|
|
6779
|
-
const dir = cwd ?? process.cwd();
|
|
6780
|
-
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
6781
|
-
try {
|
|
6782
|
-
let repoRoot;
|
|
6783
|
-
try {
|
|
6784
|
-
const gitCommonDir = execSync5("git rev-parse --path-format=absolute --git-common-dir", {
|
|
6785
|
-
cwd: dir,
|
|
6786
|
-
encoding: "utf8",
|
|
6787
|
-
timeout: 2e3,
|
|
6788
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
6789
|
-
}).trim();
|
|
6790
|
-
repoRoot = path17.dirname(gitCommonDir);
|
|
6791
|
-
} catch {
|
|
6792
|
-
repoRoot = execSync5("git rev-parse --show-toplevel", {
|
|
6793
|
-
cwd: dir,
|
|
6794
|
-
encoding: "utf8",
|
|
6795
|
-
timeout: 2e3,
|
|
6796
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
6797
|
-
}).trim();
|
|
6798
|
-
}
|
|
6799
|
-
_cached2 = path17.basename(repoRoot);
|
|
6800
|
-
_cachedCwd = dir;
|
|
6801
|
-
return _cached2;
|
|
6802
|
-
} catch {
|
|
6803
|
-
_cached2 = path17.basename(dir);
|
|
6804
|
-
_cachedCwd = dir;
|
|
6805
|
-
return _cached2;
|
|
6806
|
-
}
|
|
6807
|
-
}
|
|
6808
|
-
var _cached2, _cachedCwd;
|
|
6809
|
-
var init_project_name = __esm({
|
|
6810
|
-
"src/lib/project-name.ts"() {
|
|
6811
|
-
"use strict";
|
|
6812
|
-
_cached2 = null;
|
|
6813
|
-
_cachedCwd = null;
|
|
6814
|
-
}
|
|
6815
|
-
});
|
|
6816
|
-
|
|
6817
|
-
// src/lib/session-scope.ts
|
|
6818
|
-
var session_scope_exports = {};
|
|
6819
|
-
__export(session_scope_exports, {
|
|
6820
|
-
assertSessionScope: () => assertSessionScope,
|
|
6821
|
-
findSessionForProject: () => findSessionForProject,
|
|
6822
|
-
getSessionProject: () => getSessionProject
|
|
6823
|
-
});
|
|
6824
|
-
function getSessionProject(sessionName) {
|
|
6825
|
-
const sessions = listSessions();
|
|
6826
|
-
const entry = sessions.find((s) => s.windowName === sessionName);
|
|
6827
|
-
if (!entry) return null;
|
|
6828
|
-
const parts = entry.projectDir.split("/").filter(Boolean);
|
|
6829
|
-
return parts[parts.length - 1] ?? null;
|
|
6830
|
-
}
|
|
6831
|
-
function findSessionForProject(projectName) {
|
|
6832
|
-
const sessions = listSessions();
|
|
6833
|
-
for (const s of sessions) {
|
|
6834
|
-
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
6835
|
-
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
6836
|
-
}
|
|
6837
|
-
return null;
|
|
6838
|
-
}
|
|
6839
|
-
function assertSessionScope(actionType, targetProject) {
|
|
6840
|
-
try {
|
|
6841
|
-
const currentProject = getProjectName();
|
|
6842
|
-
const exeSession = resolveExeSession();
|
|
6843
|
-
if (!exeSession) {
|
|
6844
|
-
return { allowed: true, reason: "no_session" };
|
|
6845
|
-
}
|
|
6846
|
-
if (currentProject === targetProject) {
|
|
6847
|
-
return {
|
|
6848
|
-
allowed: true,
|
|
6849
|
-
reason: "same_session",
|
|
6850
|
-
currentProject,
|
|
6851
|
-
targetProject
|
|
6852
|
-
};
|
|
6853
|
-
}
|
|
6854
|
-
process.stderr.write(
|
|
6855
|
-
`[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
|
|
6856
|
-
`
|
|
6857
|
-
);
|
|
6858
|
-
return {
|
|
6859
|
-
allowed: false,
|
|
6860
|
-
reason: "cross_session_denied",
|
|
6861
|
-
currentProject,
|
|
6862
|
-
targetProject,
|
|
6863
|
-
targetSession: findSessionForProject(targetProject)?.windowName
|
|
6864
|
-
};
|
|
6865
|
-
} catch {
|
|
6866
|
-
return { allowed: true, reason: "no_session" };
|
|
6867
|
-
}
|
|
6868
|
-
}
|
|
6869
|
-
var init_session_scope = __esm({
|
|
6870
|
-
"src/lib/session-scope.ts"() {
|
|
6871
|
-
"use strict";
|
|
6872
|
-
init_session_registry();
|
|
6873
|
-
init_project_name();
|
|
6874
|
-
init_tmux_routing();
|
|
6875
|
-
init_employees();
|
|
6876
|
-
}
|
|
6877
|
-
});
|
|
6878
|
-
|
|
6879
7073
|
// src/lib/tasks-notify.ts
|
|
6880
7074
|
async function dispatchTaskToEmployee(input) {
|
|
6881
7075
|
if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
|
|
@@ -6943,10 +7137,10 @@ var init_tasks_notify = __esm({
|
|
|
6943
7137
|
});
|
|
6944
7138
|
|
|
6945
7139
|
// src/lib/behaviors.ts
|
|
6946
|
-
import
|
|
7140
|
+
import crypto7 from "crypto";
|
|
6947
7141
|
async function storeBehavior(opts) {
|
|
6948
7142
|
const client = getClient();
|
|
6949
|
-
const id =
|
|
7143
|
+
const id = crypto7.randomUUID();
|
|
6950
7144
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6951
7145
|
await client.execute({
|
|
6952
7146
|
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
|
|
@@ -6975,7 +7169,7 @@ __export(skill_learning_exports, {
|
|
|
6975
7169
|
storeTrajectory: () => storeTrajectory,
|
|
6976
7170
|
sweepTrajectories: () => sweepTrajectories
|
|
6977
7171
|
});
|
|
6978
|
-
import
|
|
7172
|
+
import crypto8 from "crypto";
|
|
6979
7173
|
async function extractTrajectory(taskId, agentId) {
|
|
6980
7174
|
const client = getClient();
|
|
6981
7175
|
const result = await client.execute({
|
|
@@ -7004,11 +7198,11 @@ async function extractTrajectory(taskId, agentId) {
|
|
|
7004
7198
|
return signature;
|
|
7005
7199
|
}
|
|
7006
7200
|
function hashSignature(signature) {
|
|
7007
|
-
return
|
|
7201
|
+
return crypto8.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
|
|
7008
7202
|
}
|
|
7009
7203
|
async function storeTrajectory(opts) {
|
|
7010
7204
|
const client = getClient();
|
|
7011
|
-
const id =
|
|
7205
|
+
const id = crypto8.randomUUID();
|
|
7012
7206
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7013
7207
|
const signatureHash = hashSignature(opts.signature);
|
|
7014
7208
|
await client.execute({
|
|
@@ -7273,8 +7467,8 @@ __export(tasks_exports, {
|
|
|
7273
7467
|
updateTaskStatus: () => updateTaskStatus,
|
|
7274
7468
|
writeCheckpoint: () => writeCheckpoint
|
|
7275
7469
|
});
|
|
7276
|
-
import
|
|
7277
|
-
import { writeFileSync as
|
|
7470
|
+
import path19 from "path";
|
|
7471
|
+
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync7, unlinkSync as unlinkSync5 } from "fs";
|
|
7278
7472
|
async function createTask(input) {
|
|
7279
7473
|
const result = await createTaskCore(input);
|
|
7280
7474
|
if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -7293,12 +7487,12 @@ async function updateTask(input) {
|
|
|
7293
7487
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
7294
7488
|
try {
|
|
7295
7489
|
const agent = String(row.assigned_to);
|
|
7296
|
-
const cacheDir =
|
|
7297
|
-
const cachePath =
|
|
7490
|
+
const cacheDir = path19.join(EXE_AI_DIR, "session-cache");
|
|
7491
|
+
const cachePath = path19.join(cacheDir, `current-task-${agent}.json`);
|
|
7298
7492
|
if (input.status === "in_progress") {
|
|
7299
7493
|
mkdirSync7(cacheDir, { recursive: true });
|
|
7300
|
-
|
|
7301
|
-
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
7494
|
+
writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
7495
|
+
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
|
|
7302
7496
|
try {
|
|
7303
7497
|
unlinkSync5(cachePath);
|
|
7304
7498
|
} catch {
|
|
@@ -7306,10 +7500,10 @@ async function updateTask(input) {
|
|
|
7306
7500
|
}
|
|
7307
7501
|
} catch {
|
|
7308
7502
|
}
|
|
7309
|
-
if (input.status === "done") {
|
|
7503
|
+
if (input.status === "done" || input.status === "closed") {
|
|
7310
7504
|
await cleanupReviewFile(row, taskFile, input.baseDir);
|
|
7311
7505
|
}
|
|
7312
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
7506
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
7313
7507
|
try {
|
|
7314
7508
|
const client = getClient();
|
|
7315
7509
|
const taskTitle = String(row.title);
|
|
@@ -7325,7 +7519,7 @@ async function updateTask(input) {
|
|
|
7325
7519
|
if (!isCoordinatorName(assignedAgent)) {
|
|
7326
7520
|
try {
|
|
7327
7521
|
const draftClient = getClient();
|
|
7328
|
-
if (input.status === "done") {
|
|
7522
|
+
if (input.status === "done" || input.status === "closed") {
|
|
7329
7523
|
await draftClient.execute({
|
|
7330
7524
|
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
7331
7525
|
args: [assignedAgent]
|
|
@@ -7342,7 +7536,7 @@ async function updateTask(input) {
|
|
|
7342
7536
|
try {
|
|
7343
7537
|
const client = getClient();
|
|
7344
7538
|
const cascaded = await client.execute({
|
|
7345
|
-
sql: `UPDATE tasks SET status = '
|
|
7539
|
+
sql: `UPDATE tasks SET status = 'closed', updated_at = ?
|
|
7346
7540
|
WHERE parent_task_id = ? AND status = 'needs_review'`,
|
|
7347
7541
|
args: [now, taskId]
|
|
7348
7542
|
});
|
|
@@ -7355,14 +7549,14 @@ async function updateTask(input) {
|
|
|
7355
7549
|
} catch {
|
|
7356
7550
|
}
|
|
7357
7551
|
}
|
|
7358
|
-
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
7552
|
+
const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
|
|
7359
7553
|
if (isTerminal) {
|
|
7360
7554
|
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
7361
7555
|
if (!isCoordinator) {
|
|
7362
7556
|
notifyTaskDone();
|
|
7363
7557
|
}
|
|
7364
7558
|
await markTaskNotificationsRead(taskFile);
|
|
7365
|
-
if (input.status === "done") {
|
|
7559
|
+
if (input.status === "done" || input.status === "closed") {
|
|
7366
7560
|
try {
|
|
7367
7561
|
await cascadeUnblock(taskId, input.baseDir, now);
|
|
7368
7562
|
} catch {
|
|
@@ -7382,7 +7576,7 @@ async function updateTask(input) {
|
|
|
7382
7576
|
}
|
|
7383
7577
|
}
|
|
7384
7578
|
}
|
|
7385
|
-
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
7579
|
+
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
7386
7580
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
7387
7581
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
7388
7582
|
taskId,
|
|
@@ -7754,6 +7948,7 @@ __export(tmux_routing_exports, {
|
|
|
7754
7948
|
isEmployeeAlive: () => isEmployeeAlive,
|
|
7755
7949
|
isExeSession: () => isExeSession,
|
|
7756
7950
|
isSessionBusy: () => isSessionBusy,
|
|
7951
|
+
notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
|
|
7757
7952
|
notifyParentExe: () => notifyParentExe,
|
|
7758
7953
|
parseParentExe: () => parseParentExe,
|
|
7759
7954
|
registerParentExe: () => registerParentExe,
|
|
@@ -7764,13 +7959,13 @@ __export(tmux_routing_exports, {
|
|
|
7764
7959
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
7765
7960
|
});
|
|
7766
7961
|
import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
|
|
7767
|
-
import { readFileSync as
|
|
7768
|
-
import
|
|
7769
|
-
import
|
|
7962
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, mkdirSync as mkdirSync8, existsSync as existsSync16, appendFileSync, readdirSync as readdirSync4 } from "fs";
|
|
7963
|
+
import path20 from "path";
|
|
7964
|
+
import os12 from "os";
|
|
7770
7965
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7771
7966
|
import { unlinkSync as unlinkSync6 } from "fs";
|
|
7772
7967
|
function spawnLockPath(sessionName) {
|
|
7773
|
-
return
|
|
7968
|
+
return path20.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
7774
7969
|
}
|
|
7775
7970
|
function isProcessAlive(pid) {
|
|
7776
7971
|
try {
|
|
@@ -7781,13 +7976,13 @@ function isProcessAlive(pid) {
|
|
|
7781
7976
|
}
|
|
7782
7977
|
}
|
|
7783
7978
|
function acquireSpawnLock2(sessionName) {
|
|
7784
|
-
if (!
|
|
7979
|
+
if (!existsSync16(SPAWN_LOCK_DIR)) {
|
|
7785
7980
|
mkdirSync8(SPAWN_LOCK_DIR, { recursive: true });
|
|
7786
7981
|
}
|
|
7787
7982
|
const lockFile = spawnLockPath(sessionName);
|
|
7788
|
-
if (
|
|
7983
|
+
if (existsSync16(lockFile)) {
|
|
7789
7984
|
try {
|
|
7790
|
-
const lock = JSON.parse(
|
|
7985
|
+
const lock = JSON.parse(readFileSync13(lockFile, "utf8"));
|
|
7791
7986
|
const age = Date.now() - lock.timestamp;
|
|
7792
7987
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
7793
7988
|
return false;
|
|
@@ -7795,7 +7990,7 @@ function acquireSpawnLock2(sessionName) {
|
|
|
7795
7990
|
} catch {
|
|
7796
7991
|
}
|
|
7797
7992
|
}
|
|
7798
|
-
|
|
7993
|
+
writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
7799
7994
|
return true;
|
|
7800
7995
|
}
|
|
7801
7996
|
function releaseSpawnLock2(sessionName) {
|
|
@@ -7807,13 +8002,13 @@ function releaseSpawnLock2(sessionName) {
|
|
|
7807
8002
|
function resolveBehaviorsExporterScript() {
|
|
7808
8003
|
try {
|
|
7809
8004
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
7810
|
-
const scriptPath =
|
|
7811
|
-
|
|
8005
|
+
const scriptPath = path20.join(
|
|
8006
|
+
path20.dirname(thisFile),
|
|
7812
8007
|
"..",
|
|
7813
8008
|
"bin",
|
|
7814
8009
|
"exe-export-behaviors.js"
|
|
7815
8010
|
);
|
|
7816
|
-
return
|
|
8011
|
+
return existsSync16(scriptPath) ? scriptPath : null;
|
|
7817
8012
|
} catch {
|
|
7818
8013
|
return null;
|
|
7819
8014
|
}
|
|
@@ -7879,12 +8074,12 @@ function extractRootExe(name) {
|
|
|
7879
8074
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
7880
8075
|
}
|
|
7881
8076
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
7882
|
-
if (!
|
|
8077
|
+
if (!existsSync16(SESSION_CACHE)) {
|
|
7883
8078
|
mkdirSync8(SESSION_CACHE, { recursive: true });
|
|
7884
8079
|
}
|
|
7885
8080
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
7886
|
-
const filePath =
|
|
7887
|
-
|
|
8081
|
+
const filePath = path20.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
8082
|
+
writeFileSync8(filePath, JSON.stringify({
|
|
7888
8083
|
parentExe: rootExe,
|
|
7889
8084
|
dispatchedBy: dispatchedBy || rootExe,
|
|
7890
8085
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -7892,7 +8087,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
7892
8087
|
}
|
|
7893
8088
|
function getParentExe(sessionKey) {
|
|
7894
8089
|
try {
|
|
7895
|
-
const data = JSON.parse(
|
|
8090
|
+
const data = JSON.parse(readFileSync13(path20.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
7896
8091
|
return data.parentExe || null;
|
|
7897
8092
|
} catch {
|
|
7898
8093
|
return null;
|
|
@@ -7900,8 +8095,8 @@ function getParentExe(sessionKey) {
|
|
|
7900
8095
|
}
|
|
7901
8096
|
function getDispatchedBy(sessionKey) {
|
|
7902
8097
|
try {
|
|
7903
|
-
const data = JSON.parse(
|
|
7904
|
-
|
|
8098
|
+
const data = JSON.parse(readFileSync13(
|
|
8099
|
+
path20.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
7905
8100
|
"utf8"
|
|
7906
8101
|
));
|
|
7907
8102
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -7971,8 +8166,8 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
7971
8166
|
}
|
|
7972
8167
|
function readDebounceState() {
|
|
7973
8168
|
try {
|
|
7974
|
-
if (!
|
|
7975
|
-
const raw = JSON.parse(
|
|
8169
|
+
if (!existsSync16(DEBOUNCE_FILE)) return {};
|
|
8170
|
+
const raw = JSON.parse(readFileSync13(DEBOUNCE_FILE, "utf8"));
|
|
7976
8171
|
const state = {};
|
|
7977
8172
|
for (const [key, val] of Object.entries(raw)) {
|
|
7978
8173
|
if (typeof val === "number") {
|
|
@@ -7988,8 +8183,8 @@ function readDebounceState() {
|
|
|
7988
8183
|
}
|
|
7989
8184
|
function writeDebounceState(state) {
|
|
7990
8185
|
try {
|
|
7991
|
-
if (!
|
|
7992
|
-
|
|
8186
|
+
if (!existsSync16(SESSION_CACHE)) mkdirSync8(SESSION_CACHE, { recursive: true });
|
|
8187
|
+
writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
|
|
7993
8188
|
} catch {
|
|
7994
8189
|
}
|
|
7995
8190
|
}
|
|
@@ -8087,8 +8282,8 @@ function sendIntercom(targetSession) {
|
|
|
8087
8282
|
try {
|
|
8088
8283
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
8089
8284
|
const agent = baseAgentName(rawAgent);
|
|
8090
|
-
const markerPath =
|
|
8091
|
-
if (
|
|
8285
|
+
const markerPath = path20.join(SESSION_CACHE, `current-task-${agent}.json`);
|
|
8286
|
+
if (existsSync16(markerPath)) {
|
|
8092
8287
|
logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
|
|
8093
8288
|
return "debounced";
|
|
8094
8289
|
}
|
|
@@ -8097,8 +8292,8 @@ function sendIntercom(targetSession) {
|
|
|
8097
8292
|
try {
|
|
8098
8293
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
8099
8294
|
const agent = baseAgentName(rawAgent);
|
|
8100
|
-
const taskDir =
|
|
8101
|
-
if (
|
|
8295
|
+
const taskDir = path20.join(process.cwd(), "exe", agent);
|
|
8296
|
+
if (existsSync16(taskDir)) {
|
|
8102
8297
|
const files = readdirSync4(taskDir).filter(
|
|
8103
8298
|
(f) => f.endsWith(".md") && f !== "DONE.txt"
|
|
8104
8299
|
);
|
|
@@ -8158,6 +8353,21 @@ function notifyParentExe(sessionKey) {
|
|
|
8158
8353
|
}
|
|
8159
8354
|
return true;
|
|
8160
8355
|
}
|
|
8356
|
+
function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
|
|
8357
|
+
const transport = getTransport();
|
|
8358
|
+
try {
|
|
8359
|
+
const sessions = transport.listSessions();
|
|
8360
|
+
if (!sessions.includes(coordinatorSession)) return false;
|
|
8361
|
+
execSync6(
|
|
8362
|
+
`tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
|
|
8363
|
+
{ timeout: 3e3 }
|
|
8364
|
+
);
|
|
8365
|
+
logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
|
|
8366
|
+
return true;
|
|
8367
|
+
} catch {
|
|
8368
|
+
return false;
|
|
8369
|
+
}
|
|
8370
|
+
}
|
|
8161
8371
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
8162
8372
|
if (isCoordinatorName(employeeName)) {
|
|
8163
8373
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
@@ -8231,26 +8441,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
8231
8441
|
const transport = getTransport();
|
|
8232
8442
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
8233
8443
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
8234
|
-
const logDir =
|
|
8235
|
-
const logFile =
|
|
8236
|
-
if (!
|
|
8444
|
+
const logDir = path20.join(os12.homedir(), ".exe-os", "session-logs");
|
|
8445
|
+
const logFile = path20.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
8446
|
+
if (!existsSync16(logDir)) {
|
|
8237
8447
|
mkdirSync8(logDir, { recursive: true });
|
|
8238
8448
|
}
|
|
8239
8449
|
transport.kill(sessionName);
|
|
8240
8450
|
let cleanupSuffix = "";
|
|
8241
8451
|
try {
|
|
8242
8452
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
8243
|
-
const cleanupScript =
|
|
8244
|
-
if (
|
|
8453
|
+
const cleanupScript = path20.join(path20.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
8454
|
+
if (existsSync16(cleanupScript)) {
|
|
8245
8455
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
8246
8456
|
}
|
|
8247
8457
|
} catch {
|
|
8248
8458
|
}
|
|
8249
8459
|
try {
|
|
8250
|
-
const claudeJsonPath =
|
|
8460
|
+
const claudeJsonPath = path20.join(os12.homedir(), ".claude.json");
|
|
8251
8461
|
let claudeJson = {};
|
|
8252
8462
|
try {
|
|
8253
|
-
claudeJson = JSON.parse(
|
|
8463
|
+
claudeJson = JSON.parse(readFileSync13(claudeJsonPath, "utf8"));
|
|
8254
8464
|
} catch {
|
|
8255
8465
|
}
|
|
8256
8466
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -8258,17 +8468,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
8258
8468
|
const trustDir = opts?.cwd ?? projectDir;
|
|
8259
8469
|
if (!projects[trustDir]) projects[trustDir] = {};
|
|
8260
8470
|
projects[trustDir].hasTrustDialogAccepted = true;
|
|
8261
|
-
|
|
8471
|
+
writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
8262
8472
|
} catch {
|
|
8263
8473
|
}
|
|
8264
8474
|
try {
|
|
8265
|
-
const settingsDir =
|
|
8475
|
+
const settingsDir = path20.join(os12.homedir(), ".claude", "projects");
|
|
8266
8476
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
8267
|
-
const projSettingsDir =
|
|
8268
|
-
const settingsPath =
|
|
8477
|
+
const projSettingsDir = path20.join(settingsDir, normalizedKey);
|
|
8478
|
+
const settingsPath = path20.join(projSettingsDir, "settings.json");
|
|
8269
8479
|
let settings = {};
|
|
8270
8480
|
try {
|
|
8271
|
-
settings = JSON.parse(
|
|
8481
|
+
settings = JSON.parse(readFileSync13(settingsPath, "utf8"));
|
|
8272
8482
|
} catch {
|
|
8273
8483
|
}
|
|
8274
8484
|
const perms = settings.permissions ?? {};
|
|
@@ -8297,7 +8507,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
8297
8507
|
perms.allow = allow;
|
|
8298
8508
|
settings.permissions = perms;
|
|
8299
8509
|
mkdirSync8(projSettingsDir, { recursive: true });
|
|
8300
|
-
|
|
8510
|
+
writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
8301
8511
|
}
|
|
8302
8512
|
} catch {
|
|
8303
8513
|
}
|
|
@@ -8312,8 +8522,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
8312
8522
|
let behaviorsFlag = "";
|
|
8313
8523
|
let legacyFallbackWarned = false;
|
|
8314
8524
|
if (!useExeAgent && !useBinSymlink) {
|
|
8315
|
-
const identityPath =
|
|
8316
|
-
|
|
8525
|
+
const identityPath = path20.join(
|
|
8526
|
+
os12.homedir(),
|
|
8317
8527
|
".exe-os",
|
|
8318
8528
|
"identity",
|
|
8319
8529
|
`${employeeName}.md`
|
|
@@ -8322,13 +8532,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
8322
8532
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
8323
8533
|
if (hasAgentFlag) {
|
|
8324
8534
|
identityFlag = ` --agent ${employeeName}`;
|
|
8325
|
-
} else if (
|
|
8535
|
+
} else if (existsSync16(identityPath)) {
|
|
8326
8536
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
8327
8537
|
legacyFallbackWarned = true;
|
|
8328
8538
|
}
|
|
8329
8539
|
const behaviorsFile = exportBehaviorsSync(
|
|
8330
8540
|
employeeName,
|
|
8331
|
-
|
|
8541
|
+
path20.basename(spawnCwd),
|
|
8332
8542
|
sessionName
|
|
8333
8543
|
);
|
|
8334
8544
|
if (behaviorsFile) {
|
|
@@ -8343,16 +8553,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
8343
8553
|
}
|
|
8344
8554
|
let sessionContextFlag = "";
|
|
8345
8555
|
try {
|
|
8346
|
-
const ctxDir =
|
|
8556
|
+
const ctxDir = path20.join(os12.homedir(), ".exe-os", "session-cache");
|
|
8347
8557
|
mkdirSync8(ctxDir, { recursive: true });
|
|
8348
|
-
const ctxFile =
|
|
8558
|
+
const ctxFile = path20.join(ctxDir, `session-context-${sessionName}.md`);
|
|
8349
8559
|
const ctxContent = [
|
|
8350
8560
|
`## Session Context`,
|
|
8351
8561
|
`You are running in tmux session: ${sessionName}.`,
|
|
8352
8562
|
`Your parent coordinator session is ${exeSession}.`,
|
|
8353
8563
|
`Your employees (if any) use the -${exeSession} suffix.`
|
|
8354
8564
|
].join("\n");
|
|
8355
|
-
|
|
8565
|
+
writeFileSync8(ctxFile, ctxContent);
|
|
8356
8566
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
8357
8567
|
} catch {
|
|
8358
8568
|
}
|
|
@@ -8429,8 +8639,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
8429
8639
|
transport.pipeLog(sessionName, logFile);
|
|
8430
8640
|
try {
|
|
8431
8641
|
const mySession = getMySession();
|
|
8432
|
-
const dispatchInfo =
|
|
8433
|
-
|
|
8642
|
+
const dispatchInfo = path20.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
8643
|
+
writeFileSync8(dispatchInfo, JSON.stringify({
|
|
8434
8644
|
dispatchedBy: mySession,
|
|
8435
8645
|
rootExe: exeSession,
|
|
8436
8646
|
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
|
|
@@ -8504,15 +8714,15 @@ var init_tmux_routing = __esm({
|
|
|
8504
8714
|
init_intercom_queue();
|
|
8505
8715
|
init_plan_limits();
|
|
8506
8716
|
init_employees();
|
|
8507
|
-
SPAWN_LOCK_DIR =
|
|
8508
|
-
SESSION_CACHE =
|
|
8717
|
+
SPAWN_LOCK_DIR = path20.join(os12.homedir(), ".exe-os", "spawn-locks");
|
|
8718
|
+
SESSION_CACHE = path20.join(os12.homedir(), ".exe-os", "session-cache");
|
|
8509
8719
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
8510
8720
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
8511
8721
|
VERIFY_PANE_LINES = 200;
|
|
8512
8722
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
8513
8723
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
8514
|
-
INTERCOM_LOG2 =
|
|
8515
|
-
DEBOUNCE_FILE =
|
|
8724
|
+
INTERCOM_LOG2 = path20.join(os12.homedir(), ".exe-os", "intercom.log");
|
|
8725
|
+
DEBOUNCE_FILE = path20.join(SESSION_CACHE, "intercom-debounce.json");
|
|
8516
8726
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
8517
8727
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
|
|
8518
8728
|
}
|
|
@@ -8535,10 +8745,10 @@ __export(messaging_exports, {
|
|
|
8535
8745
|
sendMessage: () => sendMessage,
|
|
8536
8746
|
setWsClientSend: () => setWsClientSend
|
|
8537
8747
|
});
|
|
8538
|
-
import
|
|
8748
|
+
import crypto9 from "crypto";
|
|
8539
8749
|
function generateUlid() {
|
|
8540
8750
|
const timestamp = Date.now().toString(36).padStart(10, "0");
|
|
8541
|
-
const random =
|
|
8751
|
+
const random = crypto9.randomBytes(10).toString("hex").slice(0, 16);
|
|
8542
8752
|
return (timestamp + random).toUpperCase();
|
|
8543
8753
|
}
|
|
8544
8754
|
function rowToMessage(row) {
|
|
@@ -8549,6 +8759,7 @@ function rowToMessage(row) {
|
|
|
8549
8759
|
targetAgent: row.target_agent,
|
|
8550
8760
|
targetProject: row.target_project ?? null,
|
|
8551
8761
|
targetDevice: row.target_device,
|
|
8762
|
+
sessionScope: row.session_scope ?? null,
|
|
8552
8763
|
content: row.content,
|
|
8553
8764
|
priority: row.priority ?? "normal",
|
|
8554
8765
|
status: row.status ?? "pending",
|
|
@@ -8566,15 +8777,17 @@ async function sendMessage(input) {
|
|
|
8566
8777
|
const id = generateUlid();
|
|
8567
8778
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8568
8779
|
const targetDevice = input.targetDevice ?? "local";
|
|
8780
|
+
const sessionScope = input.sessionScope === void 0 ? resolveExeSession() : input.sessionScope;
|
|
8569
8781
|
await client.execute({
|
|
8570
|
-
sql: `INSERT INTO messages (id, from_agent, from_device, target_agent, target_project, target_device, content, priority, status, created_at)
|
|
8571
|
-
VALUES (?, ?, 'local', ?, ?, ?, ?, ?, 'pending', ?)`,
|
|
8782
|
+
sql: `INSERT INTO messages (id, from_agent, from_device, target_agent, target_project, target_device, session_scope, content, priority, status, created_at)
|
|
8783
|
+
VALUES (?, ?, 'local', ?, ?, ?, ?, ?, ?, 'pending', ?)`,
|
|
8572
8784
|
args: [
|
|
8573
8785
|
id,
|
|
8574
8786
|
input.fromAgent,
|
|
8575
8787
|
input.targetAgent,
|
|
8576
8788
|
input.targetProject ?? null,
|
|
8577
8789
|
targetDevice,
|
|
8790
|
+
sessionScope,
|
|
8578
8791
|
input.content,
|
|
8579
8792
|
input.priority ?? "normal",
|
|
8580
8793
|
now
|
|
@@ -8588,9 +8801,10 @@ async function sendMessage(input) {
|
|
|
8588
8801
|
}
|
|
8589
8802
|
} catch {
|
|
8590
8803
|
}
|
|
8804
|
+
const sentScope = strictSessionScopeFilter(sessionScope);
|
|
8591
8805
|
const result = await client.execute({
|
|
8592
|
-
sql:
|
|
8593
|
-
args: [id]
|
|
8806
|
+
sql: `SELECT * FROM messages WHERE id = ?${sentScope.sql}`,
|
|
8807
|
+
args: [id, ...sentScope.args]
|
|
8594
8808
|
});
|
|
8595
8809
|
return rowToMessage(result.rows[0]);
|
|
8596
8810
|
}
|
|
@@ -8614,6 +8828,7 @@ async function deliverCrossMachineMessage(messageId, targetDevice) {
|
|
|
8614
8828
|
fromAgent: msg.fromAgent,
|
|
8615
8829
|
targetAgent: msg.targetAgent,
|
|
8616
8830
|
targetProject: msg.targetProject,
|
|
8831
|
+
sessionScope: msg.sessionScope,
|
|
8617
8832
|
content: msg.content,
|
|
8618
8833
|
priority: msg.priority,
|
|
8619
8834
|
createdAt: msg.createdAt
|
|
@@ -8657,7 +8872,7 @@ async function deliverLocalMessage(messageId) {
|
|
|
8657
8872
|
} catch {
|
|
8658
8873
|
const newRetryCount = msg.retryCount + 1;
|
|
8659
8874
|
if (newRetryCount >= MAX_RETRIES3) {
|
|
8660
|
-
await markFailed(messageId, "session unavailable after 10 retries");
|
|
8875
|
+
await markFailed(messageId, "session unavailable after 10 retries", msg.sessionScope);
|
|
8661
8876
|
} else {
|
|
8662
8877
|
await client.execute({
|
|
8663
8878
|
sql: "UPDATE messages SET retry_count = ? WHERE id = ?",
|
|
@@ -8667,85 +8882,101 @@ async function deliverLocalMessage(messageId) {
|
|
|
8667
8882
|
return false;
|
|
8668
8883
|
}
|
|
8669
8884
|
}
|
|
8670
|
-
async function getPendingMessages(targetAgent) {
|
|
8885
|
+
async function getPendingMessages(targetAgent, sessionScope) {
|
|
8671
8886
|
const client = getClient();
|
|
8887
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
8672
8888
|
const result = await client.execute({
|
|
8673
8889
|
sql: `SELECT * FROM messages
|
|
8674
|
-
WHERE target_agent = ? AND status IN ('pending', 'delivered')
|
|
8890
|
+
WHERE target_agent = ? AND status IN ('pending', 'delivered')${scope.sql}
|
|
8675
8891
|
ORDER BY id`,
|
|
8676
|
-
args: [targetAgent]
|
|
8892
|
+
args: [targetAgent, ...scope.args]
|
|
8677
8893
|
});
|
|
8678
8894
|
return result.rows.map((row) => rowToMessage(row));
|
|
8679
8895
|
}
|
|
8680
|
-
async function markRead(messageId) {
|
|
8896
|
+
async function markRead(messageId, sessionScope) {
|
|
8681
8897
|
const client = getClient();
|
|
8898
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
8682
8899
|
await client.execute({
|
|
8683
|
-
sql:
|
|
8684
|
-
|
|
8900
|
+
sql: `UPDATE messages SET status = 'read'
|
|
8901
|
+
WHERE id = ? AND status IN ('pending', 'delivered')${scope.sql}`,
|
|
8902
|
+
args: [messageId, ...scope.args]
|
|
8685
8903
|
});
|
|
8686
8904
|
}
|
|
8687
|
-
async function markAcknowledged(messageId) {
|
|
8905
|
+
async function markAcknowledged(messageId, sessionScope) {
|
|
8688
8906
|
const client = getClient();
|
|
8907
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
8689
8908
|
await client.execute({
|
|
8690
|
-
sql:
|
|
8691
|
-
|
|
8909
|
+
sql: `UPDATE messages SET status = 'acknowledged', processed_at = ?
|
|
8910
|
+
WHERE id = ? AND status = 'read'${scope.sql}`,
|
|
8911
|
+
args: [(/* @__PURE__ */ new Date()).toISOString(), messageId, ...scope.args]
|
|
8692
8912
|
});
|
|
8693
8913
|
}
|
|
8694
|
-
async function markProcessed(messageId) {
|
|
8914
|
+
async function markProcessed(messageId, sessionScope) {
|
|
8695
8915
|
const client = getClient();
|
|
8916
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
8696
8917
|
await client.execute({
|
|
8697
|
-
sql:
|
|
8698
|
-
|
|
8918
|
+
sql: `UPDATE messages SET status = 'processed', processed_at = ?
|
|
8919
|
+
WHERE id = ?${scope.sql}`,
|
|
8920
|
+
args: [(/* @__PURE__ */ new Date()).toISOString(), messageId, ...scope.args]
|
|
8699
8921
|
});
|
|
8700
8922
|
}
|
|
8701
|
-
async function getMessageStatus(messageId) {
|
|
8923
|
+
async function getMessageStatus(messageId, sessionScope) {
|
|
8702
8924
|
const client = getClient();
|
|
8925
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
8703
8926
|
const result = await client.execute({
|
|
8704
|
-
sql:
|
|
8705
|
-
args: [messageId]
|
|
8927
|
+
sql: `SELECT status FROM messages WHERE id = ?${scope.sql}`,
|
|
8928
|
+
args: [messageId, ...scope.args]
|
|
8706
8929
|
});
|
|
8707
8930
|
return result.rows[0]?.status ?? null;
|
|
8708
8931
|
}
|
|
8709
|
-
async function getUnacknowledgedMessages(targetAgent) {
|
|
8932
|
+
async function getUnacknowledgedMessages(targetAgent, sessionScope) {
|
|
8710
8933
|
const client = getClient();
|
|
8934
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
8711
8935
|
const result = await client.execute({
|
|
8712
8936
|
sql: `SELECT * FROM messages
|
|
8713
|
-
WHERE target_agent = ? AND status IN ('pending', 'delivered', 'read')
|
|
8937
|
+
WHERE target_agent = ? AND status IN ('pending', 'delivered', 'read')${scope.sql}
|
|
8714
8938
|
ORDER BY id`,
|
|
8715
|
-
args: [targetAgent]
|
|
8939
|
+
args: [targetAgent, ...scope.args]
|
|
8716
8940
|
});
|
|
8717
8941
|
return result.rows.map((row) => rowToMessage(row));
|
|
8718
8942
|
}
|
|
8719
|
-
async function getReadMessages(targetAgent) {
|
|
8943
|
+
async function getReadMessages(targetAgent, sessionScope) {
|
|
8720
8944
|
const client = getClient();
|
|
8945
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
8721
8946
|
const result = await client.execute({
|
|
8722
|
-
sql:
|
|
8723
|
-
|
|
8947
|
+
sql: `SELECT * FROM messages
|
|
8948
|
+
WHERE target_agent = ? AND status = 'read'${scope.sql}
|
|
8949
|
+
ORDER BY id`,
|
|
8950
|
+
args: [targetAgent, ...scope.args]
|
|
8724
8951
|
});
|
|
8725
8952
|
return result.rows.map((row) => rowToMessage(row));
|
|
8726
8953
|
}
|
|
8727
|
-
async function markFailed(messageId, reason) {
|
|
8954
|
+
async function markFailed(messageId, reason, sessionScope) {
|
|
8728
8955
|
const client = getClient();
|
|
8956
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
8729
8957
|
await client.execute({
|
|
8730
|
-
sql:
|
|
8731
|
-
|
|
8958
|
+
sql: `UPDATE messages SET status = 'failed', failed_at = ?, failure_reason = ?
|
|
8959
|
+
WHERE id = ?${scope.sql}`,
|
|
8960
|
+
args: [(/* @__PURE__ */ new Date()).toISOString(), reason, messageId, ...scope.args]
|
|
8732
8961
|
});
|
|
8733
8962
|
}
|
|
8734
|
-
async function getFailedMessages() {
|
|
8963
|
+
async function getFailedMessages(sessionScope) {
|
|
8735
8964
|
const client = getClient();
|
|
8965
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
8736
8966
|
const result = await client.execute({
|
|
8737
|
-
sql:
|
|
8738
|
-
args: []
|
|
8967
|
+
sql: `SELECT * FROM messages WHERE status = 'failed'${scope.sql} ORDER BY created_at DESC`,
|
|
8968
|
+
args: [...scope.args]
|
|
8739
8969
|
});
|
|
8740
8970
|
return result.rows.map((row) => rowToMessage(row));
|
|
8741
8971
|
}
|
|
8742
|
-
async function retryPendingMessages() {
|
|
8972
|
+
async function retryPendingMessages(sessionScope) {
|
|
8743
8973
|
const client = getClient();
|
|
8974
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
8744
8975
|
const result = await client.execute({
|
|
8745
8976
|
sql: `SELECT * FROM messages
|
|
8746
|
-
WHERE status = 'pending' AND retry_count <
|
|
8977
|
+
WHERE status = 'pending' AND retry_count < ?${scope.sql}
|
|
8747
8978
|
ORDER BY id`,
|
|
8748
|
-
args: [MAX_RETRIES3]
|
|
8979
|
+
args: [MAX_RETRIES3, ...scope.args]
|
|
8749
8980
|
});
|
|
8750
8981
|
let delivered = 0;
|
|
8751
8982
|
for (const row of result.rows) {
|
|
@@ -8764,6 +8995,7 @@ var init_messaging = __esm({
|
|
|
8764
8995
|
"use strict";
|
|
8765
8996
|
init_database();
|
|
8766
8997
|
init_tmux_routing();
|
|
8998
|
+
init_task_scope();
|
|
8767
8999
|
MAX_RETRIES3 = 10;
|
|
8768
9000
|
_wsClientSend = null;
|
|
8769
9001
|
}
|
|
@@ -8961,11 +9193,11 @@ init_crm_bridge();
|
|
|
8961
9193
|
|
|
8962
9194
|
// src/lib/pipeline-router.ts
|
|
8963
9195
|
init_database();
|
|
8964
|
-
import
|
|
9196
|
+
import crypto3 from "crypto";
|
|
8965
9197
|
async function sinkConversationStore(msg, agentResponse, agentName) {
|
|
8966
9198
|
try {
|
|
8967
9199
|
const client = getClient();
|
|
8968
|
-
const id =
|
|
9200
|
+
const id = crypto3.randomUUID();
|
|
8969
9201
|
const mediaJson = msg.media ? JSON.stringify(msg.media) : null;
|
|
8970
9202
|
await client.execute({
|
|
8971
9203
|
sql: `INSERT INTO conversations
|
|
@@ -9015,7 +9247,7 @@ async function sinkMemory(msg, agentResponse, agentName) {
|
|
|
9015
9247
|
].filter(Boolean).join("\n");
|
|
9016
9248
|
const vector = await embed2(rawText);
|
|
9017
9249
|
await writeMemory2({
|
|
9018
|
-
id:
|
|
9250
|
+
id: crypto3.randomUUID(),
|
|
9019
9251
|
agent_id: agentName ?? "gateway",
|
|
9020
9252
|
agent_role: "gateway",
|
|
9021
9253
|
session_id: `gateway-${msg.platform}`,
|
|
@@ -11697,10 +11929,10 @@ var SlackAdapter = class {
|
|
|
11697
11929
|
import { execFile } from "child_process";
|
|
11698
11930
|
import { promisify } from "util";
|
|
11699
11931
|
import os6 from "os";
|
|
11700
|
-
import
|
|
11932
|
+
import path9 from "path";
|
|
11701
11933
|
var execFileAsync = promisify(execFile);
|
|
11702
11934
|
var POLL_INTERVAL_MS = 5e3;
|
|
11703
|
-
var MESSAGES_DB_PATH =
|
|
11935
|
+
var MESSAGES_DB_PATH = path9.join(
|
|
11704
11936
|
process.env.HOME ?? os6.homedir(),
|
|
11705
11937
|
"Library/Messages/chat.db"
|
|
11706
11938
|
);
|
|
@@ -12544,11 +12776,11 @@ async function ensureCRMContact(info) {
|
|
|
12544
12776
|
}
|
|
12545
12777
|
|
|
12546
12778
|
// src/automation/trigger-engine.ts
|
|
12547
|
-
import { readFileSync as
|
|
12779
|
+
import { readFileSync as readFileSync14, writeFileSync as writeFileSync9, existsSync as existsSync17, mkdirSync as mkdirSync9 } from "fs";
|
|
12548
12780
|
import { randomUUID as randomUUID12 } from "crypto";
|
|
12549
|
-
import
|
|
12550
|
-
import
|
|
12551
|
-
var TRIGGERS_PATH =
|
|
12781
|
+
import path21 from "path";
|
|
12782
|
+
import os13 from "os";
|
|
12783
|
+
var TRIGGERS_PATH = path21.join(os13.homedir(), ".exe-os", "triggers.json");
|
|
12552
12784
|
var GRAPH_API_VERSION = "v21.0";
|
|
12553
12785
|
function substituteTemplate(template, record) {
|
|
12554
12786
|
return template.replace(
|
|
@@ -12602,9 +12834,9 @@ function evaluateConditions(conditions, record) {
|
|
|
12602
12834
|
return conditions.every((c) => evaluateCondition(c, record));
|
|
12603
12835
|
}
|
|
12604
12836
|
function loadTriggers(project) {
|
|
12605
|
-
if (!
|
|
12837
|
+
if (!existsSync17(TRIGGERS_PATH)) return [];
|
|
12606
12838
|
try {
|
|
12607
|
-
const raw =
|
|
12839
|
+
const raw = readFileSync14(TRIGGERS_PATH, "utf-8");
|
|
12608
12840
|
const all = JSON.parse(raw);
|
|
12609
12841
|
if (!Array.isArray(all)) return [];
|
|
12610
12842
|
if (project) {
|