@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
|
@@ -25,9 +25,47 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
25
25
|
};
|
|
26
26
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
27
27
|
|
|
28
|
+
// src/lib/secure-files.ts
|
|
29
|
+
import { chmodSync, existsSync, mkdirSync } from "fs";
|
|
30
|
+
import { chmod, mkdir } from "fs/promises";
|
|
31
|
+
async function ensurePrivateDir(dirPath) {
|
|
32
|
+
await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
33
|
+
try {
|
|
34
|
+
await chmod(dirPath, PRIVATE_DIR_MODE);
|
|
35
|
+
} catch {
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function ensurePrivateDirSync(dirPath) {
|
|
39
|
+
mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
40
|
+
try {
|
|
41
|
+
chmodSync(dirPath, PRIVATE_DIR_MODE);
|
|
42
|
+
} catch {
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async function enforcePrivateFile(filePath) {
|
|
46
|
+
try {
|
|
47
|
+
await chmod(filePath, PRIVATE_FILE_MODE);
|
|
48
|
+
} catch {
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function enforcePrivateFileSync(filePath) {
|
|
52
|
+
try {
|
|
53
|
+
if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
|
|
54
|
+
} catch {
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
|
|
58
|
+
var init_secure_files = __esm({
|
|
59
|
+
"src/lib/secure-files.ts"() {
|
|
60
|
+
"use strict";
|
|
61
|
+
PRIVATE_DIR_MODE = 448;
|
|
62
|
+
PRIVATE_FILE_MODE = 384;
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
28
66
|
// src/lib/config.ts
|
|
29
|
-
import { readFile, writeFile
|
|
30
|
-
import { readFileSync, existsSync, renameSync } from "fs";
|
|
67
|
+
import { readFile, writeFile } from "fs/promises";
|
|
68
|
+
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
31
69
|
import path from "path";
|
|
32
70
|
import os from "os";
|
|
33
71
|
function resolveDataDir() {
|
|
@@ -35,7 +73,7 @@ function resolveDataDir() {
|
|
|
35
73
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
36
74
|
const newDir = path.join(os.homedir(), ".exe-os");
|
|
37
75
|
const legacyDir = path.join(os.homedir(), ".exe-mem");
|
|
38
|
-
if (!
|
|
76
|
+
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
39
77
|
try {
|
|
40
78
|
renameSync(legacyDir, newDir);
|
|
41
79
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -98,9 +136,9 @@ function normalizeAutoUpdate(raw) {
|
|
|
98
136
|
}
|
|
99
137
|
async function loadConfig() {
|
|
100
138
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
101
|
-
await
|
|
139
|
+
await ensurePrivateDir(dir);
|
|
102
140
|
const configPath = path.join(dir, "config.json");
|
|
103
|
-
if (!
|
|
141
|
+
if (!existsSync2(configPath)) {
|
|
104
142
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
105
143
|
}
|
|
106
144
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -113,6 +151,7 @@ async function loadConfig() {
|
|
|
113
151
|
`);
|
|
114
152
|
try {
|
|
115
153
|
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
154
|
+
await enforcePrivateFile(configPath);
|
|
116
155
|
} catch {
|
|
117
156
|
}
|
|
118
157
|
}
|
|
@@ -132,6 +171,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
|
|
|
132
171
|
var init_config = __esm({
|
|
133
172
|
"src/lib/config.ts"() {
|
|
134
173
|
"use strict";
|
|
174
|
+
init_secure_files();
|
|
135
175
|
EXE_AI_DIR = resolveDataDir();
|
|
136
176
|
DB_PATH = path.join(EXE_AI_DIR, "memories.db");
|
|
137
177
|
MODELS_DIR = path.join(EXE_AI_DIR, "models");
|
|
@@ -278,7 +318,7 @@ var init_session_key = __esm({
|
|
|
278
318
|
|
|
279
319
|
// src/lib/employees.ts
|
|
280
320
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
281
|
-
import { existsSync as
|
|
321
|
+
import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
|
|
282
322
|
import { execSync as execSync2 } from "child_process";
|
|
283
323
|
import path2 from "path";
|
|
284
324
|
import os2 from "os";
|
|
@@ -299,7 +339,7 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
|
299
339
|
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
300
340
|
}
|
|
301
341
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
302
|
-
if (!
|
|
342
|
+
if (!existsSync3(employeesPath)) return [];
|
|
303
343
|
try {
|
|
304
344
|
return JSON.parse(readFileSync2(employeesPath, "utf-8"));
|
|
305
345
|
} catch {
|
|
@@ -337,13 +377,13 @@ var init_employees = __esm({
|
|
|
337
377
|
});
|
|
338
378
|
|
|
339
379
|
// src/lib/session-registry.ts
|
|
340
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as
|
|
380
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync4 } from "fs";
|
|
341
381
|
import path4 from "path";
|
|
342
382
|
import os3 from "os";
|
|
343
383
|
function registerSession(entry) {
|
|
344
384
|
const dir = path4.dirname(REGISTRY_PATH);
|
|
345
|
-
if (!
|
|
346
|
-
|
|
385
|
+
if (!existsSync4(dir)) {
|
|
386
|
+
mkdirSync3(dir, { recursive: true });
|
|
347
387
|
}
|
|
348
388
|
const sessions = listSessions();
|
|
349
389
|
const idx = sessions.findIndex((s) => s.windowName === entry.windowName);
|
|
@@ -578,10 +618,10 @@ var init_runtime_table = __esm({
|
|
|
578
618
|
});
|
|
579
619
|
|
|
580
620
|
// src/lib/agent-config.ts
|
|
581
|
-
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as
|
|
621
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync5 } from "fs";
|
|
582
622
|
import path5 from "path";
|
|
583
623
|
function loadAgentConfig() {
|
|
584
|
-
if (!
|
|
624
|
+
if (!existsSync5(AGENT_CONFIG_PATH)) return {};
|
|
585
625
|
try {
|
|
586
626
|
return JSON.parse(readFileSync5(AGENT_CONFIG_PATH, "utf-8"));
|
|
587
627
|
} catch {
|
|
@@ -602,6 +642,7 @@ var init_agent_config = __esm({
|
|
|
602
642
|
"use strict";
|
|
603
643
|
init_config();
|
|
604
644
|
init_runtime_table();
|
|
645
|
+
init_secure_files();
|
|
605
646
|
AGENT_CONFIG_PATH = path5.join(EXE_AI_DIR, "agent-config.json");
|
|
606
647
|
DEFAULT_MODELS = {
|
|
607
648
|
claude: "claude-opus-4",
|
|
@@ -620,16 +661,16 @@ __export(intercom_queue_exports, {
|
|
|
620
661
|
queueIntercom: () => queueIntercom,
|
|
621
662
|
readQueue: () => readQueue
|
|
622
663
|
});
|
|
623
|
-
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, renameSync as renameSync3, existsSync as
|
|
664
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, renameSync as renameSync3, existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
|
|
624
665
|
import path6 from "path";
|
|
625
666
|
import os4 from "os";
|
|
626
667
|
function ensureDir() {
|
|
627
668
|
const dir = path6.dirname(QUEUE_PATH);
|
|
628
|
-
if (!
|
|
669
|
+
if (!existsSync6(dir)) mkdirSync4(dir, { recursive: true });
|
|
629
670
|
}
|
|
630
671
|
function readQueue() {
|
|
631
672
|
try {
|
|
632
|
-
if (!
|
|
673
|
+
if (!existsSync6(QUEUE_PATH)) return [];
|
|
633
674
|
return JSON.parse(readFileSync6(QUEUE_PATH, "utf8"));
|
|
634
675
|
} catch {
|
|
635
676
|
return [];
|
|
@@ -1376,13 +1417,50 @@ var init_database_adapter = __esm({
|
|
|
1376
1417
|
}
|
|
1377
1418
|
});
|
|
1378
1419
|
|
|
1420
|
+
// src/lib/daemon-auth.ts
|
|
1421
|
+
import crypto from "crypto";
|
|
1422
|
+
import path8 from "path";
|
|
1423
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
|
|
1424
|
+
function normalizeToken(token) {
|
|
1425
|
+
if (!token) return null;
|
|
1426
|
+
const trimmed = token.trim();
|
|
1427
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
1428
|
+
}
|
|
1429
|
+
function readDaemonToken() {
|
|
1430
|
+
try {
|
|
1431
|
+
if (!existsSync7(DAEMON_TOKEN_PATH)) return null;
|
|
1432
|
+
return normalizeToken(readFileSync7(DAEMON_TOKEN_PATH, "utf8"));
|
|
1433
|
+
} catch {
|
|
1434
|
+
return null;
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
function ensureDaemonToken(seed) {
|
|
1438
|
+
const existing = readDaemonToken();
|
|
1439
|
+
if (existing) return existing;
|
|
1440
|
+
const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
|
|
1441
|
+
ensurePrivateDirSync(EXE_AI_DIR);
|
|
1442
|
+
writeFileSync6(DAEMON_TOKEN_PATH, `${token}
|
|
1443
|
+
`, "utf8");
|
|
1444
|
+
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
1445
|
+
return token;
|
|
1446
|
+
}
|
|
1447
|
+
var DAEMON_TOKEN_PATH;
|
|
1448
|
+
var init_daemon_auth = __esm({
|
|
1449
|
+
"src/lib/daemon-auth.ts"() {
|
|
1450
|
+
"use strict";
|
|
1451
|
+
init_config();
|
|
1452
|
+
init_secure_files();
|
|
1453
|
+
DAEMON_TOKEN_PATH = path8.join(EXE_AI_DIR, "exed.token");
|
|
1454
|
+
}
|
|
1455
|
+
});
|
|
1456
|
+
|
|
1379
1457
|
// src/lib/exe-daemon-client.ts
|
|
1380
1458
|
import net from "net";
|
|
1381
1459
|
import os6 from "os";
|
|
1382
1460
|
import { spawn } from "child_process";
|
|
1383
1461
|
import { randomUUID } from "crypto";
|
|
1384
|
-
import { existsSync as
|
|
1385
|
-
import
|
|
1462
|
+
import { existsSync as existsSync8, unlinkSync as unlinkSync3, readFileSync as readFileSync8, openSync, closeSync, statSync } from "fs";
|
|
1463
|
+
import path9 from "path";
|
|
1386
1464
|
import { fileURLToPath } from "url";
|
|
1387
1465
|
function handleData(chunk) {
|
|
1388
1466
|
_buffer += chunk.toString();
|
|
@@ -1410,9 +1488,9 @@ function handleData(chunk) {
|
|
|
1410
1488
|
}
|
|
1411
1489
|
}
|
|
1412
1490
|
function cleanupStaleFiles() {
|
|
1413
|
-
if (
|
|
1491
|
+
if (existsSync8(PID_PATH)) {
|
|
1414
1492
|
try {
|
|
1415
|
-
const pid = parseInt(
|
|
1493
|
+
const pid = parseInt(readFileSync8(PID_PATH, "utf8").trim(), 10);
|
|
1416
1494
|
if (pid > 0) {
|
|
1417
1495
|
try {
|
|
1418
1496
|
process.kill(pid, 0);
|
|
@@ -1433,11 +1511,11 @@ function cleanupStaleFiles() {
|
|
|
1433
1511
|
}
|
|
1434
1512
|
}
|
|
1435
1513
|
function findPackageRoot() {
|
|
1436
|
-
let dir =
|
|
1437
|
-
const { root } =
|
|
1514
|
+
let dir = path9.dirname(fileURLToPath(import.meta.url));
|
|
1515
|
+
const { root } = path9.parse(dir);
|
|
1438
1516
|
while (dir !== root) {
|
|
1439
|
-
if (
|
|
1440
|
-
dir =
|
|
1517
|
+
if (existsSync8(path9.join(dir, "package.json"))) return dir;
|
|
1518
|
+
dir = path9.dirname(dir);
|
|
1441
1519
|
}
|
|
1442
1520
|
return null;
|
|
1443
1521
|
}
|
|
@@ -1463,16 +1541,17 @@ function spawnDaemon() {
|
|
|
1463
1541
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
1464
1542
|
return;
|
|
1465
1543
|
}
|
|
1466
|
-
const daemonPath =
|
|
1467
|
-
if (!
|
|
1544
|
+
const daemonPath = path9.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
1545
|
+
if (!existsSync8(daemonPath)) {
|
|
1468
1546
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
1469
1547
|
`);
|
|
1470
1548
|
return;
|
|
1471
1549
|
}
|
|
1472
1550
|
const resolvedPath = daemonPath;
|
|
1551
|
+
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
1473
1552
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
1474
1553
|
`);
|
|
1475
|
-
const logPath =
|
|
1554
|
+
const logPath = path9.join(path9.dirname(SOCKET_PATH), "exed.log");
|
|
1476
1555
|
let stderrFd = "ignore";
|
|
1477
1556
|
try {
|
|
1478
1557
|
stderrFd = openSync(logPath, "a");
|
|
@@ -1490,7 +1569,8 @@ function spawnDaemon() {
|
|
|
1490
1569
|
TMUX_PANE: void 0,
|
|
1491
1570
|
// Prevents resolveExeSession() from scoping to one session
|
|
1492
1571
|
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
1493
|
-
EXE_DAEMON_PID: PID_PATH
|
|
1572
|
+
EXE_DAEMON_PID: PID_PATH,
|
|
1573
|
+
[DAEMON_TOKEN_ENV]: daemonToken
|
|
1494
1574
|
}
|
|
1495
1575
|
});
|
|
1496
1576
|
child.unref();
|
|
@@ -1597,13 +1677,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
1597
1677
|
return;
|
|
1598
1678
|
}
|
|
1599
1679
|
const id = randomUUID();
|
|
1680
|
+
const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
|
|
1600
1681
|
const timer = setTimeout(() => {
|
|
1601
1682
|
_pending.delete(id);
|
|
1602
1683
|
resolve({ error: "Request timeout" });
|
|
1603
1684
|
}, timeoutMs);
|
|
1604
1685
|
_pending.set(id, { resolve, timer });
|
|
1605
1686
|
try {
|
|
1606
|
-
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
1687
|
+
_socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
|
|
1607
1688
|
} catch {
|
|
1608
1689
|
clearTimeout(timer);
|
|
1609
1690
|
_pending.delete(id);
|
|
@@ -1614,17 +1695,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
1614
1695
|
function isClientConnected() {
|
|
1615
1696
|
return _connected;
|
|
1616
1697
|
}
|
|
1617
|
-
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
|
|
1698
|
+
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, DAEMON_TOKEN_ENV, _socket, _connected, _buffer, _pending, MAX_BUFFER;
|
|
1618
1699
|
var init_exe_daemon_client = __esm({
|
|
1619
1700
|
"src/lib/exe-daemon-client.ts"() {
|
|
1620
1701
|
"use strict";
|
|
1621
1702
|
init_config();
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1703
|
+
init_daemon_auth();
|
|
1704
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path9.join(EXE_AI_DIR, "exed.sock");
|
|
1705
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path9.join(EXE_AI_DIR, "exed.pid");
|
|
1706
|
+
SPAWN_LOCK_PATH = path9.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
1625
1707
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
1626
1708
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
1627
1709
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
1710
|
+
DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
|
|
1628
1711
|
_socket = null;
|
|
1629
1712
|
_connected = false;
|
|
1630
1713
|
_buffer = "";
|
|
@@ -2203,6 +2286,7 @@ async function ensureSchema() {
|
|
|
2203
2286
|
project TEXT NOT NULL,
|
|
2204
2287
|
summary TEXT NOT NULL,
|
|
2205
2288
|
task_file TEXT,
|
|
2289
|
+
session_scope TEXT,
|
|
2206
2290
|
read INTEGER NOT NULL DEFAULT 0,
|
|
2207
2291
|
created_at TEXT NOT NULL
|
|
2208
2292
|
);
|
|
@@ -2211,7 +2295,7 @@ async function ensureSchema() {
|
|
|
2211
2295
|
ON notifications(read);
|
|
2212
2296
|
|
|
2213
2297
|
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
2214
|
-
ON notifications(agent_id);
|
|
2298
|
+
ON notifications(agent_id, session_scope);
|
|
2215
2299
|
|
|
2216
2300
|
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
2217
2301
|
ON notifications(task_file);
|
|
@@ -2249,6 +2333,7 @@ async function ensureSchema() {
|
|
|
2249
2333
|
target_agent TEXT NOT NULL,
|
|
2250
2334
|
target_project TEXT,
|
|
2251
2335
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
2336
|
+
session_scope TEXT,
|
|
2252
2337
|
content TEXT NOT NULL,
|
|
2253
2338
|
priority TEXT DEFAULT 'normal',
|
|
2254
2339
|
status TEXT DEFAULT 'pending',
|
|
@@ -2262,10 +2347,31 @@ async function ensureSchema() {
|
|
|
2262
2347
|
);
|
|
2263
2348
|
|
|
2264
2349
|
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
2265
|
-
ON messages(target_agent, status);
|
|
2350
|
+
ON messages(target_agent, session_scope, status);
|
|
2266
2351
|
|
|
2267
2352
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
2268
|
-
ON messages(target_agent, from_agent, server_seq);
|
|
2353
|
+
ON messages(target_agent, session_scope, from_agent, server_seq);
|
|
2354
|
+
`);
|
|
2355
|
+
try {
|
|
2356
|
+
await client.execute({
|
|
2357
|
+
sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
|
|
2358
|
+
args: []
|
|
2359
|
+
});
|
|
2360
|
+
} catch {
|
|
2361
|
+
}
|
|
2362
|
+
try {
|
|
2363
|
+
await client.execute({
|
|
2364
|
+
sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
|
|
2365
|
+
args: []
|
|
2366
|
+
});
|
|
2367
|
+
} catch {
|
|
2368
|
+
}
|
|
2369
|
+
await client.executeMultiple(`
|
|
2370
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
|
|
2371
|
+
ON notifications(agent_id, session_scope, read, created_at);
|
|
2372
|
+
|
|
2373
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
|
|
2374
|
+
ON messages(target_agent, session_scope, status, created_at);
|
|
2269
2375
|
`);
|
|
2270
2376
|
try {
|
|
2271
2377
|
await client.execute({
|
|
@@ -2849,6 +2955,13 @@ async function ensureSchema() {
|
|
|
2849
2955
|
} catch {
|
|
2850
2956
|
}
|
|
2851
2957
|
}
|
|
2958
|
+
try {
|
|
2959
|
+
await client.execute({
|
|
2960
|
+
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
2961
|
+
args: []
|
|
2962
|
+
});
|
|
2963
|
+
} catch {
|
|
2964
|
+
}
|
|
2852
2965
|
}
|
|
2853
2966
|
async function disposeDatabase() {
|
|
2854
2967
|
if (_walCheckpointTimer) {
|
|
@@ -2887,18 +3000,21 @@ var init_database = __esm({
|
|
|
2887
3000
|
});
|
|
2888
3001
|
|
|
2889
3002
|
// src/lib/license.ts
|
|
2890
|
-
import { readFileSync as
|
|
3003
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync7, existsSync as existsSync9, mkdirSync as mkdirSync5 } from "fs";
|
|
2891
3004
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
2892
|
-
import
|
|
3005
|
+
import { createRequire as createRequire2 } from "module";
|
|
3006
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
3007
|
+
import os7 from "os";
|
|
3008
|
+
import path10 from "path";
|
|
2893
3009
|
import { jwtVerify, importSPKI } from "jose";
|
|
2894
3010
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
|
|
2895
3011
|
var init_license = __esm({
|
|
2896
3012
|
"src/lib/license.ts"() {
|
|
2897
3013
|
"use strict";
|
|
2898
3014
|
init_config();
|
|
2899
|
-
LICENSE_PATH =
|
|
2900
|
-
CACHE_PATH =
|
|
2901
|
-
DEVICE_ID_PATH =
|
|
3015
|
+
LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
|
|
3016
|
+
CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
|
|
3017
|
+
DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
|
|
2902
3018
|
PLAN_LIMITS = {
|
|
2903
3019
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
2904
3020
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -2910,12 +3026,12 @@ var init_license = __esm({
|
|
|
2910
3026
|
});
|
|
2911
3027
|
|
|
2912
3028
|
// src/lib/plan-limits.ts
|
|
2913
|
-
import { readFileSync as
|
|
2914
|
-
import
|
|
3029
|
+
import { readFileSync as readFileSync10, existsSync as existsSync10 } from "fs";
|
|
3030
|
+
import path11 from "path";
|
|
2915
3031
|
function getLicenseSync() {
|
|
2916
3032
|
try {
|
|
2917
|
-
if (!
|
|
2918
|
-
const raw = JSON.parse(
|
|
3033
|
+
if (!existsSync10(CACHE_PATH2)) return freeLicense();
|
|
3034
|
+
const raw = JSON.parse(readFileSync10(CACHE_PATH2, "utf8"));
|
|
2919
3035
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
2920
3036
|
const parts = raw.token.split(".");
|
|
2921
3037
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -2953,8 +3069,8 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
2953
3069
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
2954
3070
|
let count = 0;
|
|
2955
3071
|
try {
|
|
2956
|
-
if (
|
|
2957
|
-
const raw =
|
|
3072
|
+
if (existsSync10(filePath)) {
|
|
3073
|
+
const raw = readFileSync10(filePath, "utf8");
|
|
2958
3074
|
const employees = JSON.parse(raw);
|
|
2959
3075
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
2960
3076
|
}
|
|
@@ -2983,29 +3099,30 @@ var init_plan_limits = __esm({
|
|
|
2983
3099
|
this.name = "PlanLimitError";
|
|
2984
3100
|
}
|
|
2985
3101
|
};
|
|
2986
|
-
CACHE_PATH2 =
|
|
3102
|
+
CACHE_PATH2 = path11.join(EXE_AI_DIR, "license-cache.json");
|
|
2987
3103
|
}
|
|
2988
3104
|
});
|
|
2989
3105
|
|
|
2990
3106
|
// src/lib/notifications.ts
|
|
2991
|
-
import
|
|
2992
|
-
import
|
|
2993
|
-
import
|
|
3107
|
+
import crypto2 from "crypto";
|
|
3108
|
+
import path12 from "path";
|
|
3109
|
+
import os8 from "os";
|
|
2994
3110
|
import {
|
|
2995
|
-
readFileSync as
|
|
3111
|
+
readFileSync as readFileSync11,
|
|
2996
3112
|
readdirSync as readdirSync2,
|
|
2997
3113
|
unlinkSync as unlinkSync4,
|
|
2998
|
-
existsSync as
|
|
3114
|
+
existsSync as existsSync11,
|
|
2999
3115
|
rmdirSync
|
|
3000
3116
|
} from "fs";
|
|
3001
3117
|
async function writeNotification(notification) {
|
|
3002
3118
|
try {
|
|
3003
3119
|
const client = getClient();
|
|
3004
|
-
const id =
|
|
3120
|
+
const id = crypto2.randomUUID();
|
|
3005
3121
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3122
|
+
const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
|
|
3006
3123
|
await client.execute({
|
|
3007
|
-
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
|
|
3008
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
3124
|
+
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
3125
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
3009
3126
|
args: [
|
|
3010
3127
|
id,
|
|
3011
3128
|
notification.agentId,
|
|
@@ -3014,6 +3131,7 @@ async function writeNotification(notification) {
|
|
|
3014
3131
|
notification.project,
|
|
3015
3132
|
notification.summary,
|
|
3016
3133
|
notification.taskFile ?? null,
|
|
3134
|
+
sessionScope,
|
|
3017
3135
|
now
|
|
3018
3136
|
]
|
|
3019
3137
|
});
|
|
@@ -3022,12 +3140,14 @@ async function writeNotification(notification) {
|
|
|
3022
3140
|
`);
|
|
3023
3141
|
}
|
|
3024
3142
|
}
|
|
3025
|
-
async function markAsReadByTaskFile(taskFile) {
|
|
3143
|
+
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
3026
3144
|
try {
|
|
3027
3145
|
const client = getClient();
|
|
3146
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
3028
3147
|
await client.execute({
|
|
3029
|
-
sql:
|
|
3030
|
-
|
|
3148
|
+
sql: `UPDATE notifications SET read = 1
|
|
3149
|
+
WHERE task_file = ? AND read = 0${scope.sql}`,
|
|
3150
|
+
args: [taskFile, ...scope.args]
|
|
3031
3151
|
});
|
|
3032
3152
|
} catch {
|
|
3033
3153
|
}
|
|
@@ -3036,11 +3156,12 @@ var init_notifications = __esm({
|
|
|
3036
3156
|
"src/lib/notifications.ts"() {
|
|
3037
3157
|
"use strict";
|
|
3038
3158
|
init_database();
|
|
3159
|
+
init_task_scope();
|
|
3039
3160
|
}
|
|
3040
3161
|
});
|
|
3041
3162
|
|
|
3042
3163
|
// src/lib/session-kill-telemetry.ts
|
|
3043
|
-
import
|
|
3164
|
+
import crypto3 from "crypto";
|
|
3044
3165
|
async function recordSessionKill(input2) {
|
|
3045
3166
|
try {
|
|
3046
3167
|
const client = getClient();
|
|
@@ -3050,7 +3171,7 @@ async function recordSessionKill(input2) {
|
|
|
3050
3171
|
ticks_idle, estimated_tokens_saved)
|
|
3051
3172
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
3052
3173
|
args: [
|
|
3053
|
-
|
|
3174
|
+
crypto3.randomUUID(),
|
|
3054
3175
|
input2.sessionName,
|
|
3055
3176
|
input2.agentId,
|
|
3056
3177
|
(/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -3128,13 +3249,117 @@ var init_state_bus = __esm({
|
|
|
3128
3249
|
}
|
|
3129
3250
|
});
|
|
3130
3251
|
|
|
3131
|
-
// src/lib/
|
|
3132
|
-
import crypto3 from "crypto";
|
|
3133
|
-
import path12 from "path";
|
|
3134
|
-
import os8 from "os";
|
|
3252
|
+
// src/lib/project-name.ts
|
|
3135
3253
|
import { execSync as execSync5 } from "child_process";
|
|
3254
|
+
import path13 from "path";
|
|
3255
|
+
function getProjectName(cwd) {
|
|
3256
|
+
const dir = cwd ?? process.cwd();
|
|
3257
|
+
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
3258
|
+
try {
|
|
3259
|
+
let repoRoot;
|
|
3260
|
+
try {
|
|
3261
|
+
const gitCommonDir = execSync5("git rev-parse --path-format=absolute --git-common-dir", {
|
|
3262
|
+
cwd: dir,
|
|
3263
|
+
encoding: "utf8",
|
|
3264
|
+
timeout: 2e3,
|
|
3265
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3266
|
+
}).trim();
|
|
3267
|
+
repoRoot = path13.dirname(gitCommonDir);
|
|
3268
|
+
} catch {
|
|
3269
|
+
repoRoot = execSync5("git rev-parse --show-toplevel", {
|
|
3270
|
+
cwd: dir,
|
|
3271
|
+
encoding: "utf8",
|
|
3272
|
+
timeout: 2e3,
|
|
3273
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3274
|
+
}).trim();
|
|
3275
|
+
}
|
|
3276
|
+
_cached2 = path13.basename(repoRoot);
|
|
3277
|
+
_cachedCwd = dir;
|
|
3278
|
+
return _cached2;
|
|
3279
|
+
} catch {
|
|
3280
|
+
_cached2 = path13.basename(dir);
|
|
3281
|
+
_cachedCwd = dir;
|
|
3282
|
+
return _cached2;
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
3285
|
+
var _cached2, _cachedCwd;
|
|
3286
|
+
var init_project_name = __esm({
|
|
3287
|
+
"src/lib/project-name.ts"() {
|
|
3288
|
+
"use strict";
|
|
3289
|
+
_cached2 = null;
|
|
3290
|
+
_cachedCwd = null;
|
|
3291
|
+
}
|
|
3292
|
+
});
|
|
3293
|
+
|
|
3294
|
+
// src/lib/session-scope.ts
|
|
3295
|
+
var session_scope_exports = {};
|
|
3296
|
+
__export(session_scope_exports, {
|
|
3297
|
+
assertSessionScope: () => assertSessionScope,
|
|
3298
|
+
findSessionForProject: () => findSessionForProject,
|
|
3299
|
+
getSessionProject: () => getSessionProject
|
|
3300
|
+
});
|
|
3301
|
+
function getSessionProject(sessionName) {
|
|
3302
|
+
const sessions = listSessions();
|
|
3303
|
+
const entry = sessions.find((s) => s.windowName === sessionName);
|
|
3304
|
+
if (!entry) return null;
|
|
3305
|
+
const parts = entry.projectDir.split("/").filter(Boolean);
|
|
3306
|
+
return parts[parts.length - 1] ?? null;
|
|
3307
|
+
}
|
|
3308
|
+
function findSessionForProject(projectName) {
|
|
3309
|
+
const sessions = listSessions();
|
|
3310
|
+
for (const s of sessions) {
|
|
3311
|
+
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
3312
|
+
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
3313
|
+
}
|
|
3314
|
+
return null;
|
|
3315
|
+
}
|
|
3316
|
+
function assertSessionScope(actionType, targetProject) {
|
|
3317
|
+
try {
|
|
3318
|
+
const currentProject = getProjectName();
|
|
3319
|
+
const exeSession = resolveExeSession();
|
|
3320
|
+
if (!exeSession) {
|
|
3321
|
+
return { allowed: true, reason: "no_session" };
|
|
3322
|
+
}
|
|
3323
|
+
if (currentProject === targetProject) {
|
|
3324
|
+
return {
|
|
3325
|
+
allowed: true,
|
|
3326
|
+
reason: "same_session",
|
|
3327
|
+
currentProject,
|
|
3328
|
+
targetProject
|
|
3329
|
+
};
|
|
3330
|
+
}
|
|
3331
|
+
process.stderr.write(
|
|
3332
|
+
`[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
|
|
3333
|
+
`
|
|
3334
|
+
);
|
|
3335
|
+
return {
|
|
3336
|
+
allowed: false,
|
|
3337
|
+
reason: "cross_session_denied",
|
|
3338
|
+
currentProject,
|
|
3339
|
+
targetProject,
|
|
3340
|
+
targetSession: findSessionForProject(targetProject)?.windowName
|
|
3341
|
+
};
|
|
3342
|
+
} catch {
|
|
3343
|
+
return { allowed: true, reason: "no_session" };
|
|
3344
|
+
}
|
|
3345
|
+
}
|
|
3346
|
+
var init_session_scope = __esm({
|
|
3347
|
+
"src/lib/session-scope.ts"() {
|
|
3348
|
+
"use strict";
|
|
3349
|
+
init_session_registry();
|
|
3350
|
+
init_project_name();
|
|
3351
|
+
init_tmux_routing();
|
|
3352
|
+
init_employees();
|
|
3353
|
+
}
|
|
3354
|
+
});
|
|
3355
|
+
|
|
3356
|
+
// src/lib/tasks-crud.ts
|
|
3357
|
+
import crypto4 from "crypto";
|
|
3358
|
+
import path14 from "path";
|
|
3359
|
+
import os9 from "os";
|
|
3360
|
+
import { execSync as execSync6 } from "child_process";
|
|
3136
3361
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
3137
|
-
import { existsSync as
|
|
3362
|
+
import { existsSync as existsSync12, readFileSync as readFileSync12 } from "fs";
|
|
3138
3363
|
async function writeCheckpoint(input2) {
|
|
3139
3364
|
const client = getClient();
|
|
3140
3365
|
const row = await resolveTask(client, input2.taskId);
|
|
@@ -3250,13 +3475,28 @@ async function resolveTask(client, identifier, scopeSession) {
|
|
|
3250
3475
|
}
|
|
3251
3476
|
async function createTaskCore(input2) {
|
|
3252
3477
|
const client = getClient();
|
|
3253
|
-
const id =
|
|
3478
|
+
const id = crypto4.randomUUID();
|
|
3254
3479
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3255
3480
|
const slug = slugify(input2.title);
|
|
3256
3481
|
let earlySessionScope = null;
|
|
3482
|
+
let scopeMismatchWarning;
|
|
3257
3483
|
try {
|
|
3258
3484
|
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
3259
|
-
|
|
3485
|
+
const resolved = resolveExeSession2();
|
|
3486
|
+
if (resolved && input2.projectName) {
|
|
3487
|
+
const { getSessionProject: getSessionProject2 } = await Promise.resolve().then(() => (init_session_scope(), session_scope_exports));
|
|
3488
|
+
const sessionProject = getSessionProject2(resolved);
|
|
3489
|
+
if (sessionProject && sessionProject !== input2.projectName) {
|
|
3490
|
+
scopeMismatchWarning = `session/project mismatch: session "${resolved}" owns "${sessionProject}" but task targets "${input2.projectName}". Routed to default scope.`;
|
|
3491
|
+
process.stderr.write(`[create_task] ${scopeMismatchWarning}
|
|
3492
|
+
`);
|
|
3493
|
+
earlySessionScope = null;
|
|
3494
|
+
} else {
|
|
3495
|
+
earlySessionScope = resolved;
|
|
3496
|
+
}
|
|
3497
|
+
} else {
|
|
3498
|
+
earlySessionScope = resolved;
|
|
3499
|
+
}
|
|
3260
3500
|
} catch {
|
|
3261
3501
|
}
|
|
3262
3502
|
const scope = earlySessionScope ?? "default";
|
|
@@ -3307,10 +3547,14 @@ async function createTaskCore(input2) {
|
|
|
3307
3547
|
${laneWarning}` : laneWarning;
|
|
3308
3548
|
}
|
|
3309
3549
|
}
|
|
3550
|
+
if (scopeMismatchWarning) {
|
|
3551
|
+
warning = warning ? `${warning}
|
|
3552
|
+
${scopeMismatchWarning}` : scopeMismatchWarning;
|
|
3553
|
+
}
|
|
3310
3554
|
if (input2.baseDir) {
|
|
3311
3555
|
try {
|
|
3312
|
-
await mkdir3(
|
|
3313
|
-
await mkdir3(
|
|
3556
|
+
await mkdir3(path14.join(input2.baseDir, "exe", "output"), { recursive: true });
|
|
3557
|
+
await mkdir3(path14.join(input2.baseDir, "exe", "research"), { recursive: true });
|
|
3314
3558
|
await ensureArchitectureDoc(input2.baseDir, input2.projectName);
|
|
3315
3559
|
await ensureGitignoreExe(input2.baseDir);
|
|
3316
3560
|
} catch {
|
|
@@ -3346,13 +3590,19 @@ ${laneWarning}` : laneWarning;
|
|
|
3346
3590
|
});
|
|
3347
3591
|
if (input2.baseDir) {
|
|
3348
3592
|
try {
|
|
3349
|
-
const EXE_OS_DIR =
|
|
3350
|
-
const mdPath =
|
|
3351
|
-
const mdDir =
|
|
3352
|
-
if (!
|
|
3593
|
+
const EXE_OS_DIR = path14.join(os9.homedir(), ".exe-os");
|
|
3594
|
+
const mdPath = path14.join(EXE_OS_DIR, taskFile);
|
|
3595
|
+
const mdDir = path14.dirname(mdPath);
|
|
3596
|
+
if (!existsSync12(mdDir)) await mkdir3(mdDir, { recursive: true });
|
|
3353
3597
|
const reviewer = input2.reviewer ?? input2.assignedBy;
|
|
3354
3598
|
const mdContent = `# ${input2.title}
|
|
3355
3599
|
|
|
3600
|
+
## MANDATORY: When done
|
|
3601
|
+
|
|
3602
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
3603
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
3604
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
3605
|
+
|
|
3356
3606
|
**ID:** ${id}
|
|
3357
3607
|
**Status:** ${initialStatus}
|
|
3358
3608
|
**Priority:** ${input2.priority}
|
|
@@ -3366,12 +3616,6 @@ ${laneWarning}` : laneWarning;
|
|
|
3366
3616
|
## Context
|
|
3367
3617
|
|
|
3368
3618
|
${input2.context}
|
|
3369
|
-
|
|
3370
|
-
## MANDATORY: When done
|
|
3371
|
-
|
|
3372
|
-
You MUST call update_task with status "done" and a result summary when finished.
|
|
3373
|
-
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
3374
|
-
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
3375
3619
|
`;
|
|
3376
3620
|
await writeFile3(mdPath, mdContent, "utf-8");
|
|
3377
3621
|
} catch (err) {
|
|
@@ -3453,14 +3697,14 @@ function isTmuxSessionAlive(identifier) {
|
|
|
3453
3697
|
if (!identifier || identifier === "unknown") return true;
|
|
3454
3698
|
try {
|
|
3455
3699
|
if (identifier.startsWith("%")) {
|
|
3456
|
-
const output =
|
|
3700
|
+
const output = execSync6("tmux list-panes -a -F '#{pane_id}'", {
|
|
3457
3701
|
timeout: 2e3,
|
|
3458
3702
|
encoding: "utf8",
|
|
3459
3703
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3460
3704
|
});
|
|
3461
3705
|
return output.split("\n").some((l) => l.trim() === identifier);
|
|
3462
3706
|
} else {
|
|
3463
|
-
|
|
3707
|
+
execSync6(`tmux has-session -t ${JSON.stringify(identifier)}`, {
|
|
3464
3708
|
timeout: 2e3,
|
|
3465
3709
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3466
3710
|
});
|
|
@@ -3469,7 +3713,7 @@ function isTmuxSessionAlive(identifier) {
|
|
|
3469
3713
|
} catch {
|
|
3470
3714
|
if (identifier.startsWith("%")) return true;
|
|
3471
3715
|
try {
|
|
3472
|
-
|
|
3716
|
+
execSync6("tmux list-sessions", {
|
|
3473
3717
|
timeout: 2e3,
|
|
3474
3718
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3475
3719
|
});
|
|
@@ -3484,12 +3728,12 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
|
|
|
3484
3728
|
if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
|
|
3485
3729
|
try {
|
|
3486
3730
|
const since = new Date(taskCreatedAt).toISOString();
|
|
3487
|
-
const branch =
|
|
3731
|
+
const branch = execSync6(
|
|
3488
3732
|
"git rev-parse --abbrev-ref HEAD 2>/dev/null",
|
|
3489
3733
|
{ encoding: "utf8", timeout: 3e3 }
|
|
3490
3734
|
).trim();
|
|
3491
3735
|
const branchArg = branch && branch !== "HEAD" ? branch : "";
|
|
3492
|
-
const commitCount =
|
|
3736
|
+
const commitCount = execSync6(
|
|
3493
3737
|
`git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
|
|
3494
3738
|
{ encoding: "utf8", timeout: 5e3 }
|
|
3495
3739
|
).trim();
|
|
@@ -3620,7 +3864,7 @@ ${input2.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
3620
3864
|
await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
|
|
3621
3865
|
} catch {
|
|
3622
3866
|
}
|
|
3623
|
-
if (input2.status === "done" || input2.status === "cancelled") {
|
|
3867
|
+
if (input2.status === "done" || input2.status === "cancelled" || input2.status === "closed") {
|
|
3624
3868
|
try {
|
|
3625
3869
|
const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
3626
3870
|
clearQueueForAgent2(String(row.assigned_to));
|
|
@@ -3649,9 +3893,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
3649
3893
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
3650
3894
|
}
|
|
3651
3895
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
3652
|
-
const archPath =
|
|
3896
|
+
const archPath = path14.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
3653
3897
|
try {
|
|
3654
|
-
if (
|
|
3898
|
+
if (existsSync12(archPath)) return;
|
|
3655
3899
|
const template = [
|
|
3656
3900
|
`# ${projectName} \u2014 System Architecture`,
|
|
3657
3901
|
"",
|
|
@@ -3684,10 +3928,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
3684
3928
|
}
|
|
3685
3929
|
}
|
|
3686
3930
|
async function ensureGitignoreExe(baseDir) {
|
|
3687
|
-
const gitignorePath =
|
|
3931
|
+
const gitignorePath = path14.join(baseDir, ".gitignore");
|
|
3688
3932
|
try {
|
|
3689
|
-
if (
|
|
3690
|
-
const content =
|
|
3933
|
+
if (existsSync12(gitignorePath)) {
|
|
3934
|
+
const content = readFileSync12(gitignorePath, "utf-8");
|
|
3691
3935
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
3692
3936
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
3693
3937
|
} else {
|
|
@@ -3718,58 +3962,42 @@ var init_tasks_crud = __esm({
|
|
|
3718
3962
|
});
|
|
3719
3963
|
|
|
3720
3964
|
// src/lib/tasks-review.ts
|
|
3721
|
-
import
|
|
3722
|
-
import { existsSync as
|
|
3965
|
+
import path15 from "path";
|
|
3966
|
+
import { existsSync as existsSync13, readdirSync as readdirSync3, unlinkSync as unlinkSync5 } from "fs";
|
|
3723
3967
|
async function countPendingReviews(sessionScope) {
|
|
3724
3968
|
const client = getClient();
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
args: [sessionScope]
|
|
3729
|
-
});
|
|
3730
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
3731
|
-
}
|
|
3969
|
+
const scope = strictSessionScopeFilter(
|
|
3970
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
3971
|
+
);
|
|
3732
3972
|
const result = await client.execute({
|
|
3733
|
-
sql:
|
|
3734
|
-
|
|
3973
|
+
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3974
|
+
WHERE status = 'needs_review'${scope.sql}`,
|
|
3975
|
+
args: [...scope.args]
|
|
3735
3976
|
});
|
|
3736
3977
|
return Number(result.rows[0]?.cnt) || 0;
|
|
3737
3978
|
}
|
|
3738
3979
|
async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
3739
3980
|
const client = getClient();
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
WHERE status = 'needs_review' AND updated_at > ?
|
|
3744
|
-
AND session_scope = ?`,
|
|
3745
|
-
args: [sinceIso, sessionScope]
|
|
3746
|
-
});
|
|
3747
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
3748
|
-
}
|
|
3981
|
+
const scope = strictSessionScopeFilter(
|
|
3982
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
3983
|
+
);
|
|
3749
3984
|
const result = await client.execute({
|
|
3750
3985
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3751
|
-
WHERE status = 'needs_review' AND updated_at >
|
|
3752
|
-
args: [sinceIso]
|
|
3986
|
+
WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
|
|
3987
|
+
args: [sinceIso, ...scope.args]
|
|
3753
3988
|
});
|
|
3754
3989
|
return Number(result.rows[0]?.cnt) || 0;
|
|
3755
3990
|
}
|
|
3756
3991
|
async function listPendingReviews(limit, sessionScope) {
|
|
3757
3992
|
const client = getClient();
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
WHERE status = 'needs_review'
|
|
3762
|
-
AND session_scope = ?
|
|
3763
|
-
ORDER BY updated_at ASC LIMIT ?`,
|
|
3764
|
-
args: [sessionScope, limit]
|
|
3765
|
-
});
|
|
3766
|
-
return result2.rows;
|
|
3767
|
-
}
|
|
3993
|
+
const scope = strictSessionScopeFilter(
|
|
3994
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
3995
|
+
);
|
|
3768
3996
|
const result = await client.execute({
|
|
3769
3997
|
sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
|
|
3770
|
-
WHERE status = 'needs_review'
|
|
3998
|
+
WHERE status = 'needs_review'${scope.sql}
|
|
3771
3999
|
ORDER BY updated_at ASC LIMIT ?`,
|
|
3772
|
-
args: [limit]
|
|
4000
|
+
args: [...scope.args, limit]
|
|
3773
4001
|
});
|
|
3774
4002
|
return result.rows;
|
|
3775
4003
|
}
|
|
@@ -3781,7 +4009,7 @@ async function cleanupOrphanedReviews() {
|
|
|
3781
4009
|
WHERE status IN ('open', 'needs_review', 'in_progress')
|
|
3782
4010
|
AND assigned_by = 'system'
|
|
3783
4011
|
AND title LIKE 'Review:%'
|
|
3784
|
-
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
|
|
4012
|
+
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
|
|
3785
4013
|
args: [now]
|
|
3786
4014
|
});
|
|
3787
4015
|
const r1b = await client.execute({
|
|
@@ -3900,11 +4128,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
3900
4128
|
);
|
|
3901
4129
|
}
|
|
3902
4130
|
try {
|
|
3903
|
-
const cacheDir =
|
|
3904
|
-
if (
|
|
4131
|
+
const cacheDir = path15.join(EXE_AI_DIR, "session-cache");
|
|
4132
|
+
if (existsSync13(cacheDir)) {
|
|
3905
4133
|
for (const f of readdirSync3(cacheDir)) {
|
|
3906
4134
|
if (f.startsWith("review-notified-")) {
|
|
3907
|
-
unlinkSync5(
|
|
4135
|
+
unlinkSync5(path15.join(cacheDir, f));
|
|
3908
4136
|
}
|
|
3909
4137
|
}
|
|
3910
4138
|
}
|
|
@@ -3921,11 +4149,12 @@ var init_tasks_review = __esm({
|
|
|
3921
4149
|
init_tmux_routing();
|
|
3922
4150
|
init_session_key();
|
|
3923
4151
|
init_state_bus();
|
|
4152
|
+
init_task_scope();
|
|
3924
4153
|
}
|
|
3925
4154
|
});
|
|
3926
4155
|
|
|
3927
4156
|
// src/lib/tasks-chain.ts
|
|
3928
|
-
import
|
|
4157
|
+
import path16 from "path";
|
|
3929
4158
|
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
3930
4159
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
3931
4160
|
const client = getClient();
|
|
@@ -3942,7 +4171,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
3942
4171
|
});
|
|
3943
4172
|
for (const ur of unblockedRows.rows) {
|
|
3944
4173
|
try {
|
|
3945
|
-
const ubFile =
|
|
4174
|
+
const ubFile = path16.join(baseDir, String(ur.task_file));
|
|
3946
4175
|
let ubContent = await readFile3(ubFile, "utf-8");
|
|
3947
4176
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
3948
4177
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -3977,7 +4206,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
|
|
|
3977
4206
|
const scScope = sessionScopeFilter();
|
|
3978
4207
|
const remaining = await client.execute({
|
|
3979
4208
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3980
|
-
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
|
|
4209
|
+
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
|
|
3981
4210
|
args: [parentTaskId, ...scScope.args]
|
|
3982
4211
|
});
|
|
3983
4212
|
const cnt = Number(remaining.rows[0]?.cnt ?? 1);
|
|
@@ -4009,110 +4238,6 @@ var init_tasks_chain = __esm({
|
|
|
4009
4238
|
}
|
|
4010
4239
|
});
|
|
4011
4240
|
|
|
4012
|
-
// src/lib/project-name.ts
|
|
4013
|
-
import { execSync as execSync6 } from "child_process";
|
|
4014
|
-
import path15 from "path";
|
|
4015
|
-
function getProjectName(cwd) {
|
|
4016
|
-
const dir = cwd ?? process.cwd();
|
|
4017
|
-
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
4018
|
-
try {
|
|
4019
|
-
let repoRoot;
|
|
4020
|
-
try {
|
|
4021
|
-
const gitCommonDir = execSync6("git rev-parse --path-format=absolute --git-common-dir", {
|
|
4022
|
-
cwd: dir,
|
|
4023
|
-
encoding: "utf8",
|
|
4024
|
-
timeout: 2e3,
|
|
4025
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
4026
|
-
}).trim();
|
|
4027
|
-
repoRoot = path15.dirname(gitCommonDir);
|
|
4028
|
-
} catch {
|
|
4029
|
-
repoRoot = execSync6("git rev-parse --show-toplevel", {
|
|
4030
|
-
cwd: dir,
|
|
4031
|
-
encoding: "utf8",
|
|
4032
|
-
timeout: 2e3,
|
|
4033
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
4034
|
-
}).trim();
|
|
4035
|
-
}
|
|
4036
|
-
_cached2 = path15.basename(repoRoot);
|
|
4037
|
-
_cachedCwd = dir;
|
|
4038
|
-
return _cached2;
|
|
4039
|
-
} catch {
|
|
4040
|
-
_cached2 = path15.basename(dir);
|
|
4041
|
-
_cachedCwd = dir;
|
|
4042
|
-
return _cached2;
|
|
4043
|
-
}
|
|
4044
|
-
}
|
|
4045
|
-
var _cached2, _cachedCwd;
|
|
4046
|
-
var init_project_name = __esm({
|
|
4047
|
-
"src/lib/project-name.ts"() {
|
|
4048
|
-
"use strict";
|
|
4049
|
-
_cached2 = null;
|
|
4050
|
-
_cachedCwd = null;
|
|
4051
|
-
}
|
|
4052
|
-
});
|
|
4053
|
-
|
|
4054
|
-
// src/lib/session-scope.ts
|
|
4055
|
-
var session_scope_exports = {};
|
|
4056
|
-
__export(session_scope_exports, {
|
|
4057
|
-
assertSessionScope: () => assertSessionScope,
|
|
4058
|
-
findSessionForProject: () => findSessionForProject,
|
|
4059
|
-
getSessionProject: () => getSessionProject
|
|
4060
|
-
});
|
|
4061
|
-
function getSessionProject(sessionName) {
|
|
4062
|
-
const sessions = listSessions();
|
|
4063
|
-
const entry = sessions.find((s) => s.windowName === sessionName);
|
|
4064
|
-
if (!entry) return null;
|
|
4065
|
-
const parts = entry.projectDir.split("/").filter(Boolean);
|
|
4066
|
-
return parts[parts.length - 1] ?? null;
|
|
4067
|
-
}
|
|
4068
|
-
function findSessionForProject(projectName) {
|
|
4069
|
-
const sessions = listSessions();
|
|
4070
|
-
for (const s of sessions) {
|
|
4071
|
-
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
4072
|
-
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
4073
|
-
}
|
|
4074
|
-
return null;
|
|
4075
|
-
}
|
|
4076
|
-
function assertSessionScope(actionType, targetProject) {
|
|
4077
|
-
try {
|
|
4078
|
-
const currentProject = getProjectName();
|
|
4079
|
-
const exeSession = resolveExeSession();
|
|
4080
|
-
if (!exeSession) {
|
|
4081
|
-
return { allowed: true, reason: "no_session" };
|
|
4082
|
-
}
|
|
4083
|
-
if (currentProject === targetProject) {
|
|
4084
|
-
return {
|
|
4085
|
-
allowed: true,
|
|
4086
|
-
reason: "same_session",
|
|
4087
|
-
currentProject,
|
|
4088
|
-
targetProject
|
|
4089
|
-
};
|
|
4090
|
-
}
|
|
4091
|
-
process.stderr.write(
|
|
4092
|
-
`[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
|
|
4093
|
-
`
|
|
4094
|
-
);
|
|
4095
|
-
return {
|
|
4096
|
-
allowed: false,
|
|
4097
|
-
reason: "cross_session_denied",
|
|
4098
|
-
currentProject,
|
|
4099
|
-
targetProject,
|
|
4100
|
-
targetSession: findSessionForProject(targetProject)?.windowName
|
|
4101
|
-
};
|
|
4102
|
-
} catch {
|
|
4103
|
-
return { allowed: true, reason: "no_session" };
|
|
4104
|
-
}
|
|
4105
|
-
}
|
|
4106
|
-
var init_session_scope = __esm({
|
|
4107
|
-
"src/lib/session-scope.ts"() {
|
|
4108
|
-
"use strict";
|
|
4109
|
-
init_session_registry();
|
|
4110
|
-
init_project_name();
|
|
4111
|
-
init_tmux_routing();
|
|
4112
|
-
init_employees();
|
|
4113
|
-
}
|
|
4114
|
-
});
|
|
4115
|
-
|
|
4116
4241
|
// src/lib/tasks-notify.ts
|
|
4117
4242
|
async function dispatchTaskToEmployee(input2) {
|
|
4118
4243
|
if (isCoordinatorName(input2.assignedTo)) return { dispatched: "skipped" };
|
|
@@ -4180,10 +4305,10 @@ var init_tasks_notify = __esm({
|
|
|
4180
4305
|
});
|
|
4181
4306
|
|
|
4182
4307
|
// src/lib/behaviors.ts
|
|
4183
|
-
import
|
|
4308
|
+
import crypto5 from "crypto";
|
|
4184
4309
|
async function storeBehavior(opts) {
|
|
4185
4310
|
const client = getClient();
|
|
4186
|
-
const id =
|
|
4311
|
+
const id = crypto5.randomUUID();
|
|
4187
4312
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4188
4313
|
await client.execute({
|
|
4189
4314
|
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
|
|
@@ -4212,7 +4337,7 @@ __export(skill_learning_exports, {
|
|
|
4212
4337
|
storeTrajectory: () => storeTrajectory,
|
|
4213
4338
|
sweepTrajectories: () => sweepTrajectories
|
|
4214
4339
|
});
|
|
4215
|
-
import
|
|
4340
|
+
import crypto6 from "crypto";
|
|
4216
4341
|
async function extractTrajectory(taskId, agentId) {
|
|
4217
4342
|
const client = getClient();
|
|
4218
4343
|
const result = await client.execute({
|
|
@@ -4241,11 +4366,11 @@ async function extractTrajectory(taskId, agentId) {
|
|
|
4241
4366
|
return signature;
|
|
4242
4367
|
}
|
|
4243
4368
|
function hashSignature(signature) {
|
|
4244
|
-
return
|
|
4369
|
+
return crypto6.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
|
|
4245
4370
|
}
|
|
4246
4371
|
async function storeTrajectory(opts) {
|
|
4247
4372
|
const client = getClient();
|
|
4248
|
-
const id =
|
|
4373
|
+
const id = crypto6.randomUUID();
|
|
4249
4374
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4250
4375
|
const signatureHash = hashSignature(opts.signature);
|
|
4251
4376
|
await client.execute({
|
|
@@ -4510,8 +4635,8 @@ __export(tasks_exports, {
|
|
|
4510
4635
|
updateTaskStatus: () => updateTaskStatus,
|
|
4511
4636
|
writeCheckpoint: () => writeCheckpoint
|
|
4512
4637
|
});
|
|
4513
|
-
import
|
|
4514
|
-
import { writeFileSync as
|
|
4638
|
+
import path17 from "path";
|
|
4639
|
+
import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync6, unlinkSync as unlinkSync6 } from "fs";
|
|
4515
4640
|
async function createTask(input2) {
|
|
4516
4641
|
const result = await createTaskCore(input2);
|
|
4517
4642
|
if (!input2.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -4530,12 +4655,12 @@ async function updateTask(input2) {
|
|
|
4530
4655
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input2);
|
|
4531
4656
|
try {
|
|
4532
4657
|
const agent = String(row.assigned_to);
|
|
4533
|
-
const cacheDir =
|
|
4534
|
-
const cachePath =
|
|
4658
|
+
const cacheDir = path17.join(EXE_AI_DIR, "session-cache");
|
|
4659
|
+
const cachePath = path17.join(cacheDir, `current-task-${agent}.json`);
|
|
4535
4660
|
if (input2.status === "in_progress") {
|
|
4536
4661
|
mkdirSync6(cacheDir, { recursive: true });
|
|
4537
|
-
|
|
4538
|
-
} else if (input2.status === "done" || input2.status === "blocked" || input2.status === "cancelled") {
|
|
4662
|
+
writeFileSync8(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
4663
|
+
} else if (input2.status === "done" || input2.status === "blocked" || input2.status === "cancelled" || input2.status === "closed") {
|
|
4539
4664
|
try {
|
|
4540
4665
|
unlinkSync6(cachePath);
|
|
4541
4666
|
} catch {
|
|
@@ -4543,10 +4668,10 @@ async function updateTask(input2) {
|
|
|
4543
4668
|
}
|
|
4544
4669
|
} catch {
|
|
4545
4670
|
}
|
|
4546
|
-
if (input2.status === "done") {
|
|
4671
|
+
if (input2.status === "done" || input2.status === "closed") {
|
|
4547
4672
|
await cleanupReviewFile(row, taskFile, input2.baseDir);
|
|
4548
4673
|
}
|
|
4549
|
-
if (input2.status === "done" || input2.status === "cancelled") {
|
|
4674
|
+
if (input2.status === "done" || input2.status === "cancelled" || input2.status === "closed") {
|
|
4550
4675
|
try {
|
|
4551
4676
|
const client = getClient();
|
|
4552
4677
|
const taskTitle = String(row.title);
|
|
@@ -4562,7 +4687,7 @@ async function updateTask(input2) {
|
|
|
4562
4687
|
if (!isCoordinatorName(assignedAgent)) {
|
|
4563
4688
|
try {
|
|
4564
4689
|
const draftClient = getClient();
|
|
4565
|
-
if (input2.status === "done") {
|
|
4690
|
+
if (input2.status === "done" || input2.status === "closed") {
|
|
4566
4691
|
await draftClient.execute({
|
|
4567
4692
|
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
4568
4693
|
args: [assignedAgent]
|
|
@@ -4579,7 +4704,7 @@ async function updateTask(input2) {
|
|
|
4579
4704
|
try {
|
|
4580
4705
|
const client = getClient();
|
|
4581
4706
|
const cascaded = await client.execute({
|
|
4582
|
-
sql: `UPDATE tasks SET status = '
|
|
4707
|
+
sql: `UPDATE tasks SET status = 'closed', updated_at = ?
|
|
4583
4708
|
WHERE parent_task_id = ? AND status = 'needs_review'`,
|
|
4584
4709
|
args: [now, taskId]
|
|
4585
4710
|
});
|
|
@@ -4592,14 +4717,14 @@ async function updateTask(input2) {
|
|
|
4592
4717
|
} catch {
|
|
4593
4718
|
}
|
|
4594
4719
|
}
|
|
4595
|
-
const isTerminal = input2.status === "done" || input2.status === "needs_review";
|
|
4720
|
+
const isTerminal = input2.status === "done" || input2.status === "needs_review" || input2.status === "closed";
|
|
4596
4721
|
if (isTerminal) {
|
|
4597
4722
|
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
4598
4723
|
if (!isCoordinator) {
|
|
4599
4724
|
notifyTaskDone();
|
|
4600
4725
|
}
|
|
4601
4726
|
await markTaskNotificationsRead(taskFile);
|
|
4602
|
-
if (input2.status === "done") {
|
|
4727
|
+
if (input2.status === "done" || input2.status === "closed") {
|
|
4603
4728
|
try {
|
|
4604
4729
|
await cascadeUnblock(taskId, input2.baseDir, now);
|
|
4605
4730
|
} catch {
|
|
@@ -4619,7 +4744,7 @@ async function updateTask(input2) {
|
|
|
4619
4744
|
}
|
|
4620
4745
|
}
|
|
4621
4746
|
}
|
|
4622
|
-
if (input2.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
4747
|
+
if ((input2.status === "done" || input2.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
4623
4748
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
4624
4749
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
4625
4750
|
taskId,
|
|
@@ -4991,6 +5116,7 @@ __export(tmux_routing_exports, {
|
|
|
4991
5116
|
isEmployeeAlive: () => isEmployeeAlive,
|
|
4992
5117
|
isExeSession: () => isExeSession,
|
|
4993
5118
|
isSessionBusy: () => isSessionBusy,
|
|
5119
|
+
notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
|
|
4994
5120
|
notifyParentExe: () => notifyParentExe,
|
|
4995
5121
|
parseParentExe: () => parseParentExe,
|
|
4996
5122
|
registerParentExe: () => registerParentExe,
|
|
@@ -5001,13 +5127,13 @@ __export(tmux_routing_exports, {
|
|
|
5001
5127
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
5002
5128
|
});
|
|
5003
5129
|
import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
|
|
5004
|
-
import { readFileSync as
|
|
5005
|
-
import
|
|
5006
|
-
import
|
|
5130
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync9, mkdirSync as mkdirSync7, existsSync as existsSync14, appendFileSync, readdirSync as readdirSync4 } from "fs";
|
|
5131
|
+
import path18 from "path";
|
|
5132
|
+
import os10 from "os";
|
|
5007
5133
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5008
5134
|
import { unlinkSync as unlinkSync7 } from "fs";
|
|
5009
5135
|
function spawnLockPath(sessionName) {
|
|
5010
|
-
return
|
|
5136
|
+
return path18.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
5011
5137
|
}
|
|
5012
5138
|
function isProcessAlive(pid) {
|
|
5013
5139
|
try {
|
|
@@ -5018,13 +5144,13 @@ function isProcessAlive(pid) {
|
|
|
5018
5144
|
}
|
|
5019
5145
|
}
|
|
5020
5146
|
function acquireSpawnLock2(sessionName) {
|
|
5021
|
-
if (!
|
|
5147
|
+
if (!existsSync14(SPAWN_LOCK_DIR)) {
|
|
5022
5148
|
mkdirSync7(SPAWN_LOCK_DIR, { recursive: true });
|
|
5023
5149
|
}
|
|
5024
5150
|
const lockFile = spawnLockPath(sessionName);
|
|
5025
|
-
if (
|
|
5151
|
+
if (existsSync14(lockFile)) {
|
|
5026
5152
|
try {
|
|
5027
|
-
const lock = JSON.parse(
|
|
5153
|
+
const lock = JSON.parse(readFileSync13(lockFile, "utf8"));
|
|
5028
5154
|
const age = Date.now() - lock.timestamp;
|
|
5029
5155
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
5030
5156
|
return false;
|
|
@@ -5032,7 +5158,7 @@ function acquireSpawnLock2(sessionName) {
|
|
|
5032
5158
|
} catch {
|
|
5033
5159
|
}
|
|
5034
5160
|
}
|
|
5035
|
-
|
|
5161
|
+
writeFileSync9(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
5036
5162
|
return true;
|
|
5037
5163
|
}
|
|
5038
5164
|
function releaseSpawnLock2(sessionName) {
|
|
@@ -5044,13 +5170,13 @@ function releaseSpawnLock2(sessionName) {
|
|
|
5044
5170
|
function resolveBehaviorsExporterScript() {
|
|
5045
5171
|
try {
|
|
5046
5172
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
5047
|
-
const scriptPath =
|
|
5048
|
-
|
|
5173
|
+
const scriptPath = path18.join(
|
|
5174
|
+
path18.dirname(thisFile),
|
|
5049
5175
|
"..",
|
|
5050
5176
|
"bin",
|
|
5051
5177
|
"exe-export-behaviors.js"
|
|
5052
5178
|
);
|
|
5053
|
-
return
|
|
5179
|
+
return existsSync14(scriptPath) ? scriptPath : null;
|
|
5054
5180
|
} catch {
|
|
5055
5181
|
return null;
|
|
5056
5182
|
}
|
|
@@ -5116,12 +5242,12 @@ function extractRootExe(name) {
|
|
|
5116
5242
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
5117
5243
|
}
|
|
5118
5244
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
5119
|
-
if (!
|
|
5245
|
+
if (!existsSync14(SESSION_CACHE)) {
|
|
5120
5246
|
mkdirSync7(SESSION_CACHE, { recursive: true });
|
|
5121
5247
|
}
|
|
5122
5248
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
5123
|
-
const filePath =
|
|
5124
|
-
|
|
5249
|
+
const filePath = path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
5250
|
+
writeFileSync9(filePath, JSON.stringify({
|
|
5125
5251
|
parentExe: rootExe,
|
|
5126
5252
|
dispatchedBy: dispatchedBy || rootExe,
|
|
5127
5253
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -5129,7 +5255,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
5129
5255
|
}
|
|
5130
5256
|
function getParentExe(sessionKey) {
|
|
5131
5257
|
try {
|
|
5132
|
-
const data = JSON.parse(
|
|
5258
|
+
const data = JSON.parse(readFileSync13(path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
5133
5259
|
return data.parentExe || null;
|
|
5134
5260
|
} catch {
|
|
5135
5261
|
return null;
|
|
@@ -5137,8 +5263,8 @@ function getParentExe(sessionKey) {
|
|
|
5137
5263
|
}
|
|
5138
5264
|
function getDispatchedBy(sessionKey) {
|
|
5139
5265
|
try {
|
|
5140
|
-
const data = JSON.parse(
|
|
5141
|
-
|
|
5266
|
+
const data = JSON.parse(readFileSync13(
|
|
5267
|
+
path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
5142
5268
|
"utf8"
|
|
5143
5269
|
));
|
|
5144
5270
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -5208,8 +5334,8 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
5208
5334
|
}
|
|
5209
5335
|
function readDebounceState() {
|
|
5210
5336
|
try {
|
|
5211
|
-
if (!
|
|
5212
|
-
const raw = JSON.parse(
|
|
5337
|
+
if (!existsSync14(DEBOUNCE_FILE)) return {};
|
|
5338
|
+
const raw = JSON.parse(readFileSync13(DEBOUNCE_FILE, "utf8"));
|
|
5213
5339
|
const state = {};
|
|
5214
5340
|
for (const [key, val] of Object.entries(raw)) {
|
|
5215
5341
|
if (typeof val === "number") {
|
|
@@ -5225,8 +5351,8 @@ function readDebounceState() {
|
|
|
5225
5351
|
}
|
|
5226
5352
|
function writeDebounceState(state) {
|
|
5227
5353
|
try {
|
|
5228
|
-
if (!
|
|
5229
|
-
|
|
5354
|
+
if (!existsSync14(SESSION_CACHE)) mkdirSync7(SESSION_CACHE, { recursive: true });
|
|
5355
|
+
writeFileSync9(DEBOUNCE_FILE, JSON.stringify(state));
|
|
5230
5356
|
} catch {
|
|
5231
5357
|
}
|
|
5232
5358
|
}
|
|
@@ -5324,8 +5450,8 @@ function sendIntercom(targetSession) {
|
|
|
5324
5450
|
try {
|
|
5325
5451
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
5326
5452
|
const agent = baseAgentName(rawAgent);
|
|
5327
|
-
const markerPath =
|
|
5328
|
-
if (
|
|
5453
|
+
const markerPath = path18.join(SESSION_CACHE, `current-task-${agent}.json`);
|
|
5454
|
+
if (existsSync14(markerPath)) {
|
|
5329
5455
|
logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
|
|
5330
5456
|
return "debounced";
|
|
5331
5457
|
}
|
|
@@ -5334,8 +5460,8 @@ function sendIntercom(targetSession) {
|
|
|
5334
5460
|
try {
|
|
5335
5461
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
5336
5462
|
const agent = baseAgentName(rawAgent);
|
|
5337
|
-
const taskDir =
|
|
5338
|
-
if (
|
|
5463
|
+
const taskDir = path18.join(process.cwd(), "exe", agent);
|
|
5464
|
+
if (existsSync14(taskDir)) {
|
|
5339
5465
|
const files = readdirSync4(taskDir).filter(
|
|
5340
5466
|
(f) => f.endsWith(".md") && f !== "DONE.txt"
|
|
5341
5467
|
);
|
|
@@ -5395,6 +5521,21 @@ function notifyParentExe(sessionKey) {
|
|
|
5395
5521
|
}
|
|
5396
5522
|
return true;
|
|
5397
5523
|
}
|
|
5524
|
+
function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
|
|
5525
|
+
const transport = getTransport();
|
|
5526
|
+
try {
|
|
5527
|
+
const sessions = transport.listSessions();
|
|
5528
|
+
if (!sessions.includes(coordinatorSession)) return false;
|
|
5529
|
+
execSync7(
|
|
5530
|
+
`tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
|
|
5531
|
+
{ timeout: 3e3 }
|
|
5532
|
+
);
|
|
5533
|
+
logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
|
|
5534
|
+
return true;
|
|
5535
|
+
} catch {
|
|
5536
|
+
return false;
|
|
5537
|
+
}
|
|
5538
|
+
}
|
|
5398
5539
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
5399
5540
|
if (isCoordinatorName(employeeName)) {
|
|
5400
5541
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
@@ -5468,26 +5609,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5468
5609
|
const transport = getTransport();
|
|
5469
5610
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
5470
5611
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
5471
|
-
const logDir =
|
|
5472
|
-
const logFile =
|
|
5473
|
-
if (!
|
|
5612
|
+
const logDir = path18.join(os10.homedir(), ".exe-os", "session-logs");
|
|
5613
|
+
const logFile = path18.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
5614
|
+
if (!existsSync14(logDir)) {
|
|
5474
5615
|
mkdirSync7(logDir, { recursive: true });
|
|
5475
5616
|
}
|
|
5476
5617
|
transport.kill(sessionName);
|
|
5477
5618
|
let cleanupSuffix = "";
|
|
5478
5619
|
try {
|
|
5479
5620
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
5480
|
-
const cleanupScript =
|
|
5481
|
-
if (
|
|
5621
|
+
const cleanupScript = path18.join(path18.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
5622
|
+
if (existsSync14(cleanupScript)) {
|
|
5482
5623
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
5483
5624
|
}
|
|
5484
5625
|
} catch {
|
|
5485
5626
|
}
|
|
5486
5627
|
try {
|
|
5487
|
-
const claudeJsonPath =
|
|
5628
|
+
const claudeJsonPath = path18.join(os10.homedir(), ".claude.json");
|
|
5488
5629
|
let claudeJson = {};
|
|
5489
5630
|
try {
|
|
5490
|
-
claudeJson = JSON.parse(
|
|
5631
|
+
claudeJson = JSON.parse(readFileSync13(claudeJsonPath, "utf8"));
|
|
5491
5632
|
} catch {
|
|
5492
5633
|
}
|
|
5493
5634
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -5495,17 +5636,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5495
5636
|
const trustDir = opts?.cwd ?? projectDir;
|
|
5496
5637
|
if (!projects[trustDir]) projects[trustDir] = {};
|
|
5497
5638
|
projects[trustDir].hasTrustDialogAccepted = true;
|
|
5498
|
-
|
|
5639
|
+
writeFileSync9(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
5499
5640
|
} catch {
|
|
5500
5641
|
}
|
|
5501
5642
|
try {
|
|
5502
|
-
const settingsDir =
|
|
5643
|
+
const settingsDir = path18.join(os10.homedir(), ".claude", "projects");
|
|
5503
5644
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
5504
|
-
const projSettingsDir =
|
|
5505
|
-
const settingsPath =
|
|
5645
|
+
const projSettingsDir = path18.join(settingsDir, normalizedKey);
|
|
5646
|
+
const settingsPath = path18.join(projSettingsDir, "settings.json");
|
|
5506
5647
|
let settings = {};
|
|
5507
5648
|
try {
|
|
5508
|
-
settings = JSON.parse(
|
|
5649
|
+
settings = JSON.parse(readFileSync13(settingsPath, "utf8"));
|
|
5509
5650
|
} catch {
|
|
5510
5651
|
}
|
|
5511
5652
|
const perms = settings.permissions ?? {};
|
|
@@ -5534,7 +5675,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5534
5675
|
perms.allow = allow;
|
|
5535
5676
|
settings.permissions = perms;
|
|
5536
5677
|
mkdirSync7(projSettingsDir, { recursive: true });
|
|
5537
|
-
|
|
5678
|
+
writeFileSync9(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
5538
5679
|
}
|
|
5539
5680
|
} catch {
|
|
5540
5681
|
}
|
|
@@ -5549,8 +5690,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5549
5690
|
let behaviorsFlag = "";
|
|
5550
5691
|
let legacyFallbackWarned = false;
|
|
5551
5692
|
if (!useExeAgent && !useBinSymlink) {
|
|
5552
|
-
const identityPath =
|
|
5553
|
-
|
|
5693
|
+
const identityPath = path18.join(
|
|
5694
|
+
os10.homedir(),
|
|
5554
5695
|
".exe-os",
|
|
5555
5696
|
"identity",
|
|
5556
5697
|
`${employeeName}.md`
|
|
@@ -5559,13 +5700,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5559
5700
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
5560
5701
|
if (hasAgentFlag) {
|
|
5561
5702
|
identityFlag = ` --agent ${employeeName}`;
|
|
5562
|
-
} else if (
|
|
5703
|
+
} else if (existsSync14(identityPath)) {
|
|
5563
5704
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
5564
5705
|
legacyFallbackWarned = true;
|
|
5565
5706
|
}
|
|
5566
5707
|
const behaviorsFile = exportBehaviorsSync(
|
|
5567
5708
|
employeeName,
|
|
5568
|
-
|
|
5709
|
+
path18.basename(spawnCwd),
|
|
5569
5710
|
sessionName
|
|
5570
5711
|
);
|
|
5571
5712
|
if (behaviorsFile) {
|
|
@@ -5580,16 +5721,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5580
5721
|
}
|
|
5581
5722
|
let sessionContextFlag = "";
|
|
5582
5723
|
try {
|
|
5583
|
-
const ctxDir =
|
|
5724
|
+
const ctxDir = path18.join(os10.homedir(), ".exe-os", "session-cache");
|
|
5584
5725
|
mkdirSync7(ctxDir, { recursive: true });
|
|
5585
|
-
const ctxFile =
|
|
5726
|
+
const ctxFile = path18.join(ctxDir, `session-context-${sessionName}.md`);
|
|
5586
5727
|
const ctxContent = [
|
|
5587
5728
|
`## Session Context`,
|
|
5588
5729
|
`You are running in tmux session: ${sessionName}.`,
|
|
5589
5730
|
`Your parent coordinator session is ${exeSession}.`,
|
|
5590
5731
|
`Your employees (if any) use the -${exeSession} suffix.`
|
|
5591
5732
|
].join("\n");
|
|
5592
|
-
|
|
5733
|
+
writeFileSync9(ctxFile, ctxContent);
|
|
5593
5734
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
5594
5735
|
} catch {
|
|
5595
5736
|
}
|
|
@@ -5666,8 +5807,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5666
5807
|
transport.pipeLog(sessionName, logFile);
|
|
5667
5808
|
try {
|
|
5668
5809
|
const mySession = getMySession();
|
|
5669
|
-
const dispatchInfo =
|
|
5670
|
-
|
|
5810
|
+
const dispatchInfo = path18.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
5811
|
+
writeFileSync9(dispatchInfo, JSON.stringify({
|
|
5671
5812
|
dispatchedBy: mySession,
|
|
5672
5813
|
rootExe: exeSession,
|
|
5673
5814
|
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
|
|
@@ -5741,15 +5882,15 @@ var init_tmux_routing = __esm({
|
|
|
5741
5882
|
init_intercom_queue();
|
|
5742
5883
|
init_plan_limits();
|
|
5743
5884
|
init_employees();
|
|
5744
|
-
SPAWN_LOCK_DIR =
|
|
5745
|
-
SESSION_CACHE =
|
|
5885
|
+
SPAWN_LOCK_DIR = path18.join(os10.homedir(), ".exe-os", "spawn-locks");
|
|
5886
|
+
SESSION_CACHE = path18.join(os10.homedir(), ".exe-os", "session-cache");
|
|
5746
5887
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
5747
5888
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
5748
5889
|
VERIFY_PANE_LINES = 200;
|
|
5749
5890
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
5750
5891
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
5751
|
-
INTERCOM_LOG2 =
|
|
5752
|
-
DEBOUNCE_FILE =
|
|
5892
|
+
INTERCOM_LOG2 = path18.join(os10.homedir(), ".exe-os", "intercom.log");
|
|
5893
|
+
DEBOUNCE_FILE = path18.join(SESSION_CACHE, "intercom-debounce.json");
|
|
5753
5894
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
5754
5895
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
|
|
5755
5896
|
}
|
|
@@ -5772,6 +5913,15 @@ function sessionScopeFilter(sessionScope, tableAlias) {
|
|
|
5772
5913
|
args: [scope]
|
|
5773
5914
|
};
|
|
5774
5915
|
}
|
|
5916
|
+
function strictSessionScopeFilter(sessionScope, tableAlias) {
|
|
5917
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
5918
|
+
if (!scope) return { sql: "", args: [] };
|
|
5919
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
5920
|
+
return {
|
|
5921
|
+
sql: ` AND ${col} = ?`,
|
|
5922
|
+
args: [scope]
|
|
5923
|
+
};
|
|
5924
|
+
}
|
|
5775
5925
|
var init_task_scope = __esm({
|
|
5776
5926
|
"src/lib/task-scope.ts"() {
|
|
5777
5927
|
"use strict";
|
|
@@ -5790,14 +5940,14 @@ var init_memory = __esm({
|
|
|
5790
5940
|
|
|
5791
5941
|
// src/lib/keychain.ts
|
|
5792
5942
|
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
5793
|
-
import { existsSync as
|
|
5794
|
-
import
|
|
5795
|
-
import
|
|
5943
|
+
import { existsSync as existsSync15 } from "fs";
|
|
5944
|
+
import path19 from "path";
|
|
5945
|
+
import os11 from "os";
|
|
5796
5946
|
function getKeyDir() {
|
|
5797
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
5947
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path19.join(os11.homedir(), ".exe-os");
|
|
5798
5948
|
}
|
|
5799
5949
|
function getKeyPath() {
|
|
5800
|
-
return
|
|
5950
|
+
return path19.join(getKeyDir(), "master.key");
|
|
5801
5951
|
}
|
|
5802
5952
|
async function tryKeytar() {
|
|
5803
5953
|
try {
|
|
@@ -5818,9 +5968,9 @@ async function getMasterKey() {
|
|
|
5818
5968
|
}
|
|
5819
5969
|
}
|
|
5820
5970
|
const keyPath = getKeyPath();
|
|
5821
|
-
if (!
|
|
5971
|
+
if (!existsSync15(keyPath)) {
|
|
5822
5972
|
process.stderr.write(
|
|
5823
|
-
`[keychain] Key not found at ${keyPath} (HOME=${
|
|
5973
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os11.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
5824
5974
|
`
|
|
5825
5975
|
);
|
|
5826
5976
|
return null;
|
|
@@ -5850,6 +6000,7 @@ var shard_manager_exports = {};
|
|
|
5850
6000
|
__export(shard_manager_exports, {
|
|
5851
6001
|
disposeShards: () => disposeShards,
|
|
5852
6002
|
ensureShardSchema: () => ensureShardSchema,
|
|
6003
|
+
getOpenShardCount: () => getOpenShardCount,
|
|
5853
6004
|
getReadyShardClient: () => getReadyShardClient,
|
|
5854
6005
|
getShardClient: () => getShardClient,
|
|
5855
6006
|
getShardsDir: () => getShardsDir,
|
|
@@ -5858,15 +6009,18 @@ __export(shard_manager_exports, {
|
|
|
5858
6009
|
listShards: () => listShards,
|
|
5859
6010
|
shardExists: () => shardExists
|
|
5860
6011
|
});
|
|
5861
|
-
import
|
|
5862
|
-
import { existsSync as
|
|
6012
|
+
import path20 from "path";
|
|
6013
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync8, readdirSync as readdirSync5 } from "fs";
|
|
5863
6014
|
import { createClient as createClient2 } from "@libsql/client";
|
|
5864
6015
|
function initShardManager(encryptionKey) {
|
|
5865
6016
|
_encryptionKey = encryptionKey;
|
|
5866
|
-
if (!
|
|
6017
|
+
if (!existsSync16(SHARDS_DIR)) {
|
|
5867
6018
|
mkdirSync8(SHARDS_DIR, { recursive: true });
|
|
5868
6019
|
}
|
|
5869
6020
|
_shardingEnabled = true;
|
|
6021
|
+
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
6022
|
+
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
6023
|
+
_evictionTimer.unref();
|
|
5870
6024
|
}
|
|
5871
6025
|
function isShardingEnabled() {
|
|
5872
6026
|
return _shardingEnabled;
|
|
@@ -5883,21 +6037,28 @@ function getShardClient(projectName) {
|
|
|
5883
6037
|
throw new Error(`Invalid project name for shard: "${projectName}"`);
|
|
5884
6038
|
}
|
|
5885
6039
|
const cached = _shards.get(safeName);
|
|
5886
|
-
if (cached)
|
|
5887
|
-
|
|
6040
|
+
if (cached) {
|
|
6041
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
6042
|
+
return cached;
|
|
6043
|
+
}
|
|
6044
|
+
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
6045
|
+
evictLRU();
|
|
6046
|
+
}
|
|
6047
|
+
const dbPath = path20.join(SHARDS_DIR, `${safeName}.db`);
|
|
5888
6048
|
const client = createClient2({
|
|
5889
6049
|
url: `file:${dbPath}`,
|
|
5890
6050
|
encryptionKey: _encryptionKey
|
|
5891
6051
|
});
|
|
5892
6052
|
_shards.set(safeName, client);
|
|
6053
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
5893
6054
|
return client;
|
|
5894
6055
|
}
|
|
5895
6056
|
function shardExists(projectName) {
|
|
5896
6057
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
5897
|
-
return
|
|
6058
|
+
return existsSync16(path20.join(SHARDS_DIR, `${safeName}.db`));
|
|
5898
6059
|
}
|
|
5899
6060
|
function listShards() {
|
|
5900
|
-
if (!
|
|
6061
|
+
if (!existsSync16(SHARDS_DIR)) return [];
|
|
5901
6062
|
return readdirSync5(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
5902
6063
|
}
|
|
5903
6064
|
async function ensureShardSchema(client) {
|
|
@@ -5949,6 +6110,8 @@ async function ensureShardSchema(client) {
|
|
|
5949
6110
|
for (const col of [
|
|
5950
6111
|
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
5951
6112
|
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
6113
|
+
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
6114
|
+
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
5952
6115
|
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
5953
6116
|
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
5954
6117
|
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
@@ -6086,21 +6249,69 @@ async function getReadyShardClient(projectName) {
|
|
|
6086
6249
|
await ensureShardSchema(client);
|
|
6087
6250
|
return client;
|
|
6088
6251
|
}
|
|
6252
|
+
function evictLRU() {
|
|
6253
|
+
let oldest = null;
|
|
6254
|
+
let oldestTime = Infinity;
|
|
6255
|
+
for (const [name, time] of _shardLastAccess) {
|
|
6256
|
+
if (time < oldestTime) {
|
|
6257
|
+
oldestTime = time;
|
|
6258
|
+
oldest = name;
|
|
6259
|
+
}
|
|
6260
|
+
}
|
|
6261
|
+
if (oldest) {
|
|
6262
|
+
const client = _shards.get(oldest);
|
|
6263
|
+
if (client) {
|
|
6264
|
+
client.close();
|
|
6265
|
+
}
|
|
6266
|
+
_shards.delete(oldest);
|
|
6267
|
+
_shardLastAccess.delete(oldest);
|
|
6268
|
+
}
|
|
6269
|
+
}
|
|
6270
|
+
function evictIdleShards() {
|
|
6271
|
+
const now = Date.now();
|
|
6272
|
+
const toEvict = [];
|
|
6273
|
+
for (const [name, lastAccess] of _shardLastAccess) {
|
|
6274
|
+
if (now - lastAccess > SHARD_IDLE_MS) {
|
|
6275
|
+
toEvict.push(name);
|
|
6276
|
+
}
|
|
6277
|
+
}
|
|
6278
|
+
for (const name of toEvict) {
|
|
6279
|
+
const client = _shards.get(name);
|
|
6280
|
+
if (client) {
|
|
6281
|
+
client.close();
|
|
6282
|
+
}
|
|
6283
|
+
_shards.delete(name);
|
|
6284
|
+
_shardLastAccess.delete(name);
|
|
6285
|
+
}
|
|
6286
|
+
}
|
|
6287
|
+
function getOpenShardCount() {
|
|
6288
|
+
return _shards.size;
|
|
6289
|
+
}
|
|
6089
6290
|
function disposeShards() {
|
|
6291
|
+
if (_evictionTimer) {
|
|
6292
|
+
clearInterval(_evictionTimer);
|
|
6293
|
+
_evictionTimer = null;
|
|
6294
|
+
}
|
|
6090
6295
|
for (const [, client] of _shards) {
|
|
6091
6296
|
client.close();
|
|
6092
6297
|
}
|
|
6093
6298
|
_shards.clear();
|
|
6299
|
+
_shardLastAccess.clear();
|
|
6094
6300
|
_shardingEnabled = false;
|
|
6095
6301
|
_encryptionKey = null;
|
|
6096
6302
|
}
|
|
6097
|
-
var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
|
|
6303
|
+
var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
|
|
6098
6304
|
var init_shard_manager = __esm({
|
|
6099
6305
|
"src/lib/shard-manager.ts"() {
|
|
6100
6306
|
"use strict";
|
|
6101
6307
|
init_config();
|
|
6102
|
-
SHARDS_DIR =
|
|
6308
|
+
SHARDS_DIR = path20.join(EXE_AI_DIR, "shards");
|
|
6309
|
+
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
6310
|
+
MAX_OPEN_SHARDS = 10;
|
|
6311
|
+
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
6103
6312
|
_shards = /* @__PURE__ */ new Map();
|
|
6313
|
+
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
6314
|
+
_evictionTimer = null;
|
|
6104
6315
|
_encryptionKey = null;
|
|
6105
6316
|
_shardingEnabled = false;
|
|
6106
6317
|
}
|
|
@@ -6864,13 +7075,13 @@ var init_store = __esm({
|
|
|
6864
7075
|
});
|
|
6865
7076
|
|
|
6866
7077
|
// src/adapters/claude/hooks/pre-compact.ts
|
|
6867
|
-
import
|
|
7078
|
+
import crypto7 from "crypto";
|
|
6868
7079
|
|
|
6869
7080
|
// src/lib/active-agent.ts
|
|
6870
7081
|
init_config();
|
|
6871
7082
|
init_session_key();
|
|
6872
7083
|
init_employees();
|
|
6873
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync, unlinkSync as unlinkSync2, readdirSync } from "fs";
|
|
7084
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2, readdirSync } from "fs";
|
|
6874
7085
|
import { execSync as execSync3 } from "child_process";
|
|
6875
7086
|
import path3 from "path";
|
|
6876
7087
|
var CACHE_DIR = path3.join(EXE_AI_DIR, "session-cache");
|
|
@@ -7037,7 +7248,7 @@ ${taskLines}`);
|
|
|
7037
7248
|
recoveryLines.push(`Files: ${lastCheckpoint.files_touched.join(", ")}`);
|
|
7038
7249
|
}
|
|
7039
7250
|
await writeMemory2({
|
|
7040
|
-
id:
|
|
7251
|
+
id: crypto7.randomUUID(),
|
|
7041
7252
|
agent_id: agent.agentId,
|
|
7042
7253
|
agent_role: agent.agentRole,
|
|
7043
7254
|
session_id: payload.session_id,
|