@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/runtime/index.js
CHANGED
|
@@ -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 path2 from "path";
|
|
32
70
|
import os2 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 = path2.join(os2.homedir(), ".exe-os");
|
|
37
75
|
const legacyDir = path2.join(os2.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 = path2.join(dir, "config.json");
|
|
103
|
-
if (!
|
|
141
|
+
if (!existsSync2(configPath)) {
|
|
104
142
|
return { ...DEFAULT_CONFIG, dbPath: path2.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 = path2.join(EXE_AI_DIR, "memories.db");
|
|
137
177
|
MODELS_DIR = path2.join(EXE_AI_DIR, "models");
|
|
@@ -210,7 +250,7 @@ var init_config = __esm({
|
|
|
210
250
|
|
|
211
251
|
// src/lib/employees.ts
|
|
212
252
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
213
|
-
import { existsSync as
|
|
253
|
+
import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
|
|
214
254
|
import { execSync } from "child_process";
|
|
215
255
|
import path3 from "path";
|
|
216
256
|
import os3 from "os";
|
|
@@ -231,7 +271,7 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
|
231
271
|
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
232
272
|
}
|
|
233
273
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
234
|
-
if (!
|
|
274
|
+
if (!existsSync3(employeesPath)) return [];
|
|
235
275
|
try {
|
|
236
276
|
return JSON.parse(readFileSync2(employeesPath, "utf-8"));
|
|
237
277
|
} catch {
|
|
@@ -275,14 +315,14 @@ __export(session_registry_exports, {
|
|
|
275
315
|
pruneStaleSessions: () => pruneStaleSessions,
|
|
276
316
|
registerSession: () => registerSession
|
|
277
317
|
});
|
|
278
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync, existsSync as
|
|
318
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync4 } from "fs";
|
|
279
319
|
import { execSync as execSync2 } from "child_process";
|
|
280
320
|
import path4 from "path";
|
|
281
321
|
import os4 from "os";
|
|
282
322
|
function registerSession(entry) {
|
|
283
323
|
const dir = path4.dirname(REGISTRY_PATH);
|
|
284
|
-
if (!
|
|
285
|
-
|
|
324
|
+
if (!existsSync4(dir)) {
|
|
325
|
+
mkdirSync2(dir, { recursive: true });
|
|
286
326
|
}
|
|
287
327
|
const sessions = listSessions();
|
|
288
328
|
const idx = sessions.findIndex((s) => s.windowName === entry.windowName);
|
|
@@ -614,10 +654,10 @@ var init_runtime_table = __esm({
|
|
|
614
654
|
});
|
|
615
655
|
|
|
616
656
|
// src/lib/agent-config.ts
|
|
617
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as
|
|
657
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5 } from "fs";
|
|
618
658
|
import path5 from "path";
|
|
619
659
|
function loadAgentConfig() {
|
|
620
|
-
if (!
|
|
660
|
+
if (!existsSync5(AGENT_CONFIG_PATH)) return {};
|
|
621
661
|
try {
|
|
622
662
|
return JSON.parse(readFileSync4(AGENT_CONFIG_PATH, "utf-8"));
|
|
623
663
|
} catch {
|
|
@@ -638,6 +678,7 @@ var init_agent_config = __esm({
|
|
|
638
678
|
"use strict";
|
|
639
679
|
init_config();
|
|
640
680
|
init_runtime_table();
|
|
681
|
+
init_secure_files();
|
|
641
682
|
AGENT_CONFIG_PATH = path5.join(EXE_AI_DIR, "agent-config.json");
|
|
642
683
|
DEFAULT_MODELS = {
|
|
643
684
|
claude: "claude-opus-4",
|
|
@@ -656,16 +697,16 @@ __export(intercom_queue_exports, {
|
|
|
656
697
|
queueIntercom: () => queueIntercom,
|
|
657
698
|
readQueue: () => readQueue
|
|
658
699
|
});
|
|
659
|
-
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as
|
|
700
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
|
|
660
701
|
import path6 from "path";
|
|
661
702
|
import os5 from "os";
|
|
662
703
|
function ensureDir() {
|
|
663
704
|
const dir = path6.dirname(QUEUE_PATH);
|
|
664
|
-
if (!
|
|
705
|
+
if (!existsSync6(dir)) mkdirSync3(dir, { recursive: true });
|
|
665
706
|
}
|
|
666
707
|
function readQueue() {
|
|
667
708
|
try {
|
|
668
|
-
if (!
|
|
709
|
+
if (!existsSync6(QUEUE_PATH)) return [];
|
|
669
710
|
return JSON.parse(readFileSync5(QUEUE_PATH, "utf8"));
|
|
670
711
|
} catch {
|
|
671
712
|
return [];
|
|
@@ -1412,13 +1453,50 @@ var init_database_adapter = __esm({
|
|
|
1412
1453
|
}
|
|
1413
1454
|
});
|
|
1414
1455
|
|
|
1456
|
+
// src/lib/daemon-auth.ts
|
|
1457
|
+
import crypto from "crypto";
|
|
1458
|
+
import path8 from "path";
|
|
1459
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
1460
|
+
function normalizeToken(token) {
|
|
1461
|
+
if (!token) return null;
|
|
1462
|
+
const trimmed = token.trim();
|
|
1463
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
1464
|
+
}
|
|
1465
|
+
function readDaemonToken() {
|
|
1466
|
+
try {
|
|
1467
|
+
if (!existsSync7(DAEMON_TOKEN_PATH)) return null;
|
|
1468
|
+
return normalizeToken(readFileSync6(DAEMON_TOKEN_PATH, "utf8"));
|
|
1469
|
+
} catch {
|
|
1470
|
+
return null;
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
function ensureDaemonToken(seed) {
|
|
1474
|
+
const existing = readDaemonToken();
|
|
1475
|
+
if (existing) return existing;
|
|
1476
|
+
const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
|
|
1477
|
+
ensurePrivateDirSync(EXE_AI_DIR);
|
|
1478
|
+
writeFileSync5(DAEMON_TOKEN_PATH, `${token}
|
|
1479
|
+
`, "utf8");
|
|
1480
|
+
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
1481
|
+
return token;
|
|
1482
|
+
}
|
|
1483
|
+
var DAEMON_TOKEN_PATH;
|
|
1484
|
+
var init_daemon_auth = __esm({
|
|
1485
|
+
"src/lib/daemon-auth.ts"() {
|
|
1486
|
+
"use strict";
|
|
1487
|
+
init_config();
|
|
1488
|
+
init_secure_files();
|
|
1489
|
+
DAEMON_TOKEN_PATH = path8.join(EXE_AI_DIR, "exed.token");
|
|
1490
|
+
}
|
|
1491
|
+
});
|
|
1492
|
+
|
|
1415
1493
|
// src/lib/exe-daemon-client.ts
|
|
1416
1494
|
import net from "net";
|
|
1417
1495
|
import os7 from "os";
|
|
1418
1496
|
import { spawn } from "child_process";
|
|
1419
1497
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
1420
|
-
import { existsSync as
|
|
1421
|
-
import
|
|
1498
|
+
import { existsSync as existsSync8, unlinkSync as unlinkSync2, readFileSync as readFileSync7, openSync, closeSync, statSync } from "fs";
|
|
1499
|
+
import path9 from "path";
|
|
1422
1500
|
import { fileURLToPath } from "url";
|
|
1423
1501
|
function handleData(chunk) {
|
|
1424
1502
|
_buffer += chunk.toString();
|
|
@@ -1446,9 +1524,9 @@ function handleData(chunk) {
|
|
|
1446
1524
|
}
|
|
1447
1525
|
}
|
|
1448
1526
|
function cleanupStaleFiles() {
|
|
1449
|
-
if (
|
|
1527
|
+
if (existsSync8(PID_PATH)) {
|
|
1450
1528
|
try {
|
|
1451
|
-
const pid = parseInt(
|
|
1529
|
+
const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
|
|
1452
1530
|
if (pid > 0) {
|
|
1453
1531
|
try {
|
|
1454
1532
|
process.kill(pid, 0);
|
|
@@ -1469,11 +1547,11 @@ function cleanupStaleFiles() {
|
|
|
1469
1547
|
}
|
|
1470
1548
|
}
|
|
1471
1549
|
function findPackageRoot() {
|
|
1472
|
-
let dir =
|
|
1473
|
-
const { root } =
|
|
1550
|
+
let dir = path9.dirname(fileURLToPath(import.meta.url));
|
|
1551
|
+
const { root } = path9.parse(dir);
|
|
1474
1552
|
while (dir !== root) {
|
|
1475
|
-
if (
|
|
1476
|
-
dir =
|
|
1553
|
+
if (existsSync8(path9.join(dir, "package.json"))) return dir;
|
|
1554
|
+
dir = path9.dirname(dir);
|
|
1477
1555
|
}
|
|
1478
1556
|
return null;
|
|
1479
1557
|
}
|
|
@@ -1499,16 +1577,17 @@ function spawnDaemon() {
|
|
|
1499
1577
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
1500
1578
|
return;
|
|
1501
1579
|
}
|
|
1502
|
-
const daemonPath =
|
|
1503
|
-
if (!
|
|
1580
|
+
const daemonPath = path9.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
1581
|
+
if (!existsSync8(daemonPath)) {
|
|
1504
1582
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
1505
1583
|
`);
|
|
1506
1584
|
return;
|
|
1507
1585
|
}
|
|
1508
1586
|
const resolvedPath = daemonPath;
|
|
1587
|
+
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
1509
1588
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
1510
1589
|
`);
|
|
1511
|
-
const logPath =
|
|
1590
|
+
const logPath = path9.join(path9.dirname(SOCKET_PATH), "exed.log");
|
|
1512
1591
|
let stderrFd = "ignore";
|
|
1513
1592
|
try {
|
|
1514
1593
|
stderrFd = openSync(logPath, "a");
|
|
@@ -1526,7 +1605,8 @@ function spawnDaemon() {
|
|
|
1526
1605
|
TMUX_PANE: void 0,
|
|
1527
1606
|
// Prevents resolveExeSession() from scoping to one session
|
|
1528
1607
|
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
1529
|
-
EXE_DAEMON_PID: PID_PATH
|
|
1608
|
+
EXE_DAEMON_PID: PID_PATH,
|
|
1609
|
+
[DAEMON_TOKEN_ENV]: daemonToken
|
|
1530
1610
|
}
|
|
1531
1611
|
});
|
|
1532
1612
|
child.unref();
|
|
@@ -1633,13 +1713,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
1633
1713
|
return;
|
|
1634
1714
|
}
|
|
1635
1715
|
const id = randomUUID2();
|
|
1716
|
+
const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
|
|
1636
1717
|
const timer = setTimeout(() => {
|
|
1637
1718
|
_pending.delete(id);
|
|
1638
1719
|
resolve({ error: "Request timeout" });
|
|
1639
1720
|
}, timeoutMs);
|
|
1640
1721
|
_pending.set(id, { resolve, timer });
|
|
1641
1722
|
try {
|
|
1642
|
-
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
1723
|
+
_socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
|
|
1643
1724
|
} catch {
|
|
1644
1725
|
clearTimeout(timer);
|
|
1645
1726
|
_pending.delete(id);
|
|
@@ -1650,17 +1731,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
1650
1731
|
function isClientConnected() {
|
|
1651
1732
|
return _connected;
|
|
1652
1733
|
}
|
|
1653
|
-
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
|
|
1734
|
+
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;
|
|
1654
1735
|
var init_exe_daemon_client = __esm({
|
|
1655
1736
|
"src/lib/exe-daemon-client.ts"() {
|
|
1656
1737
|
"use strict";
|
|
1657
1738
|
init_config();
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1739
|
+
init_daemon_auth();
|
|
1740
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path9.join(EXE_AI_DIR, "exed.sock");
|
|
1741
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path9.join(EXE_AI_DIR, "exed.pid");
|
|
1742
|
+
SPAWN_LOCK_PATH = path9.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
1661
1743
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
1662
1744
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
1663
1745
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
1746
|
+
DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
|
|
1664
1747
|
_socket = null;
|
|
1665
1748
|
_connected = false;
|
|
1666
1749
|
_buffer = "";
|
|
@@ -2239,6 +2322,7 @@ async function ensureSchema() {
|
|
|
2239
2322
|
project TEXT NOT NULL,
|
|
2240
2323
|
summary TEXT NOT NULL,
|
|
2241
2324
|
task_file TEXT,
|
|
2325
|
+
session_scope TEXT,
|
|
2242
2326
|
read INTEGER NOT NULL DEFAULT 0,
|
|
2243
2327
|
created_at TEXT NOT NULL
|
|
2244
2328
|
);
|
|
@@ -2247,7 +2331,7 @@ async function ensureSchema() {
|
|
|
2247
2331
|
ON notifications(read);
|
|
2248
2332
|
|
|
2249
2333
|
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
2250
|
-
ON notifications(agent_id);
|
|
2334
|
+
ON notifications(agent_id, session_scope);
|
|
2251
2335
|
|
|
2252
2336
|
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
2253
2337
|
ON notifications(task_file);
|
|
@@ -2285,6 +2369,7 @@ async function ensureSchema() {
|
|
|
2285
2369
|
target_agent TEXT NOT NULL,
|
|
2286
2370
|
target_project TEXT,
|
|
2287
2371
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
2372
|
+
session_scope TEXT,
|
|
2288
2373
|
content TEXT NOT NULL,
|
|
2289
2374
|
priority TEXT DEFAULT 'normal',
|
|
2290
2375
|
status TEXT DEFAULT 'pending',
|
|
@@ -2298,10 +2383,31 @@ async function ensureSchema() {
|
|
|
2298
2383
|
);
|
|
2299
2384
|
|
|
2300
2385
|
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
2301
|
-
ON messages(target_agent, status);
|
|
2386
|
+
ON messages(target_agent, session_scope, status);
|
|
2302
2387
|
|
|
2303
2388
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
2304
|
-
ON messages(target_agent, from_agent, server_seq);
|
|
2389
|
+
ON messages(target_agent, session_scope, from_agent, server_seq);
|
|
2390
|
+
`);
|
|
2391
|
+
try {
|
|
2392
|
+
await client.execute({
|
|
2393
|
+
sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
|
|
2394
|
+
args: []
|
|
2395
|
+
});
|
|
2396
|
+
} catch {
|
|
2397
|
+
}
|
|
2398
|
+
try {
|
|
2399
|
+
await client.execute({
|
|
2400
|
+
sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
|
|
2401
|
+
args: []
|
|
2402
|
+
});
|
|
2403
|
+
} catch {
|
|
2404
|
+
}
|
|
2405
|
+
await client.executeMultiple(`
|
|
2406
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
|
|
2407
|
+
ON notifications(agent_id, session_scope, read, created_at);
|
|
2408
|
+
|
|
2409
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
|
|
2410
|
+
ON messages(target_agent, session_scope, status, created_at);
|
|
2305
2411
|
`);
|
|
2306
2412
|
try {
|
|
2307
2413
|
await client.execute({
|
|
@@ -2885,6 +2991,13 @@ async function ensureSchema() {
|
|
|
2885
2991
|
} catch {
|
|
2886
2992
|
}
|
|
2887
2993
|
}
|
|
2994
|
+
try {
|
|
2995
|
+
await client.execute({
|
|
2996
|
+
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
2997
|
+
args: []
|
|
2998
|
+
});
|
|
2999
|
+
} catch {
|
|
3000
|
+
}
|
|
2888
3001
|
}
|
|
2889
3002
|
async function disposeDatabase() {
|
|
2890
3003
|
if (_walCheckpointTimer) {
|
|
@@ -2923,18 +3036,21 @@ var init_database = __esm({
|
|
|
2923
3036
|
});
|
|
2924
3037
|
|
|
2925
3038
|
// src/lib/license.ts
|
|
2926
|
-
import { readFileSync as
|
|
3039
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync9, mkdirSync as mkdirSync4 } from "fs";
|
|
2927
3040
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
2928
|
-
import
|
|
3041
|
+
import { createRequire as createRequire2 } from "module";
|
|
3042
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
3043
|
+
import os8 from "os";
|
|
3044
|
+
import path10 from "path";
|
|
2929
3045
|
import { jwtVerify, importSPKI } from "jose";
|
|
2930
3046
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
|
|
2931
3047
|
var init_license = __esm({
|
|
2932
3048
|
"src/lib/license.ts"() {
|
|
2933
3049
|
"use strict";
|
|
2934
3050
|
init_config();
|
|
2935
|
-
LICENSE_PATH =
|
|
2936
|
-
CACHE_PATH =
|
|
2937
|
-
DEVICE_ID_PATH =
|
|
3051
|
+
LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
|
|
3052
|
+
CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
|
|
3053
|
+
DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
|
|
2938
3054
|
PLAN_LIMITS = {
|
|
2939
3055
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
2940
3056
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -2946,12 +3062,12 @@ var init_license = __esm({
|
|
|
2946
3062
|
});
|
|
2947
3063
|
|
|
2948
3064
|
// src/lib/plan-limits.ts
|
|
2949
|
-
import { readFileSync as
|
|
2950
|
-
import
|
|
3065
|
+
import { readFileSync as readFileSync9, existsSync as existsSync10 } from "fs";
|
|
3066
|
+
import path11 from "path";
|
|
2951
3067
|
function getLicenseSync() {
|
|
2952
3068
|
try {
|
|
2953
|
-
if (!
|
|
2954
|
-
const raw = JSON.parse(
|
|
3069
|
+
if (!existsSync10(CACHE_PATH2)) return freeLicense();
|
|
3070
|
+
const raw = JSON.parse(readFileSync9(CACHE_PATH2, "utf8"));
|
|
2955
3071
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
2956
3072
|
const parts = raw.token.split(".");
|
|
2957
3073
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -2989,8 +3105,8 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
2989
3105
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
2990
3106
|
let count = 0;
|
|
2991
3107
|
try {
|
|
2992
|
-
if (
|
|
2993
|
-
const raw =
|
|
3108
|
+
if (existsSync10(filePath)) {
|
|
3109
|
+
const raw = readFileSync9(filePath, "utf8");
|
|
2994
3110
|
const employees = JSON.parse(raw);
|
|
2995
3111
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
2996
3112
|
}
|
|
@@ -3019,29 +3135,69 @@ var init_plan_limits = __esm({
|
|
|
3019
3135
|
this.name = "PlanLimitError";
|
|
3020
3136
|
}
|
|
3021
3137
|
};
|
|
3022
|
-
CACHE_PATH2 =
|
|
3138
|
+
CACHE_PATH2 = path11.join(EXE_AI_DIR, "license-cache.json");
|
|
3139
|
+
}
|
|
3140
|
+
});
|
|
3141
|
+
|
|
3142
|
+
// src/lib/task-scope.ts
|
|
3143
|
+
var task_scope_exports = {};
|
|
3144
|
+
__export(task_scope_exports, {
|
|
3145
|
+
getCurrentSessionScope: () => getCurrentSessionScope,
|
|
3146
|
+
sessionScopeFilter: () => sessionScopeFilter,
|
|
3147
|
+
strictSessionScopeFilter: () => strictSessionScopeFilter
|
|
3148
|
+
});
|
|
3149
|
+
function getCurrentSessionScope() {
|
|
3150
|
+
try {
|
|
3151
|
+
return resolveExeSession();
|
|
3152
|
+
} catch {
|
|
3153
|
+
return null;
|
|
3154
|
+
}
|
|
3155
|
+
}
|
|
3156
|
+
function sessionScopeFilter(sessionScope, tableAlias) {
|
|
3157
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
3158
|
+
if (!scope) return { sql: "", args: [] };
|
|
3159
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
3160
|
+
return {
|
|
3161
|
+
sql: ` AND (${col} IS NULL OR ${col} = ?)`,
|
|
3162
|
+
args: [scope]
|
|
3163
|
+
};
|
|
3164
|
+
}
|
|
3165
|
+
function strictSessionScopeFilter(sessionScope, tableAlias) {
|
|
3166
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
3167
|
+
if (!scope) return { sql: "", args: [] };
|
|
3168
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
3169
|
+
return {
|
|
3170
|
+
sql: ` AND ${col} = ?`,
|
|
3171
|
+
args: [scope]
|
|
3172
|
+
};
|
|
3173
|
+
}
|
|
3174
|
+
var init_task_scope = __esm({
|
|
3175
|
+
"src/lib/task-scope.ts"() {
|
|
3176
|
+
"use strict";
|
|
3177
|
+
init_tmux_routing();
|
|
3023
3178
|
}
|
|
3024
3179
|
});
|
|
3025
3180
|
|
|
3026
3181
|
// src/lib/notifications.ts
|
|
3027
|
-
import
|
|
3028
|
-
import
|
|
3029
|
-
import
|
|
3182
|
+
import crypto2 from "crypto";
|
|
3183
|
+
import path12 from "path";
|
|
3184
|
+
import os9 from "os";
|
|
3030
3185
|
import {
|
|
3031
|
-
readFileSync as
|
|
3186
|
+
readFileSync as readFileSync10,
|
|
3032
3187
|
readdirSync,
|
|
3033
3188
|
unlinkSync as unlinkSync3,
|
|
3034
|
-
existsSync as
|
|
3189
|
+
existsSync as existsSync11,
|
|
3035
3190
|
rmdirSync
|
|
3036
3191
|
} from "fs";
|
|
3037
3192
|
async function writeNotification(notification) {
|
|
3038
3193
|
try {
|
|
3039
3194
|
const client = getClient();
|
|
3040
|
-
const id =
|
|
3195
|
+
const id = crypto2.randomUUID();
|
|
3041
3196
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3197
|
+
const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
|
|
3042
3198
|
await client.execute({
|
|
3043
|
-
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
|
|
3044
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
3199
|
+
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
3200
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
3045
3201
|
args: [
|
|
3046
3202
|
id,
|
|
3047
3203
|
notification.agentId,
|
|
@@ -3050,6 +3206,7 @@ async function writeNotification(notification) {
|
|
|
3050
3206
|
notification.project,
|
|
3051
3207
|
notification.summary,
|
|
3052
3208
|
notification.taskFile ?? null,
|
|
3209
|
+
sessionScope,
|
|
3053
3210
|
now
|
|
3054
3211
|
]
|
|
3055
3212
|
});
|
|
@@ -3058,12 +3215,14 @@ async function writeNotification(notification) {
|
|
|
3058
3215
|
`);
|
|
3059
3216
|
}
|
|
3060
3217
|
}
|
|
3061
|
-
async function markAsReadByTaskFile(taskFile) {
|
|
3218
|
+
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
3062
3219
|
try {
|
|
3063
3220
|
const client = getClient();
|
|
3221
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
3064
3222
|
await client.execute({
|
|
3065
|
-
sql:
|
|
3066
|
-
|
|
3223
|
+
sql: `UPDATE notifications SET read = 1
|
|
3224
|
+
WHERE task_file = ? AND read = 0${scope.sql}`,
|
|
3225
|
+
args: [taskFile, ...scope.args]
|
|
3067
3226
|
});
|
|
3068
3227
|
} catch {
|
|
3069
3228
|
}
|
|
@@ -3072,11 +3231,12 @@ var init_notifications = __esm({
|
|
|
3072
3231
|
"src/lib/notifications.ts"() {
|
|
3073
3232
|
"use strict";
|
|
3074
3233
|
init_database();
|
|
3234
|
+
init_task_scope();
|
|
3075
3235
|
}
|
|
3076
3236
|
});
|
|
3077
3237
|
|
|
3078
3238
|
// src/lib/session-kill-telemetry.ts
|
|
3079
|
-
import
|
|
3239
|
+
import crypto3 from "crypto";
|
|
3080
3240
|
async function recordSessionKill(input) {
|
|
3081
3241
|
try {
|
|
3082
3242
|
const client = getClient();
|
|
@@ -3086,7 +3246,7 @@ async function recordSessionKill(input) {
|
|
|
3086
3246
|
ticks_idle, estimated_tokens_saved)
|
|
3087
3247
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
3088
3248
|
args: [
|
|
3089
|
-
|
|
3249
|
+
crypto3.randomUUID(),
|
|
3090
3250
|
input.sessionName,
|
|
3091
3251
|
input.agentId,
|
|
3092
3252
|
(/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -3109,35 +3269,6 @@ var init_session_kill_telemetry = __esm({
|
|
|
3109
3269
|
}
|
|
3110
3270
|
});
|
|
3111
3271
|
|
|
3112
|
-
// src/lib/task-scope.ts
|
|
3113
|
-
var task_scope_exports = {};
|
|
3114
|
-
__export(task_scope_exports, {
|
|
3115
|
-
getCurrentSessionScope: () => getCurrentSessionScope,
|
|
3116
|
-
sessionScopeFilter: () => sessionScopeFilter
|
|
3117
|
-
});
|
|
3118
|
-
function getCurrentSessionScope() {
|
|
3119
|
-
try {
|
|
3120
|
-
return resolveExeSession();
|
|
3121
|
-
} catch {
|
|
3122
|
-
return null;
|
|
3123
|
-
}
|
|
3124
|
-
}
|
|
3125
|
-
function sessionScopeFilter(sessionScope, tableAlias) {
|
|
3126
|
-
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
3127
|
-
if (!scope) return { sql: "", args: [] };
|
|
3128
|
-
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
3129
|
-
return {
|
|
3130
|
-
sql: ` AND (${col} IS NULL OR ${col} = ?)`,
|
|
3131
|
-
args: [scope]
|
|
3132
|
-
};
|
|
3133
|
-
}
|
|
3134
|
-
var init_task_scope = __esm({
|
|
3135
|
-
"src/lib/task-scope.ts"() {
|
|
3136
|
-
"use strict";
|
|
3137
|
-
init_tmux_routing();
|
|
3138
|
-
}
|
|
3139
|
-
});
|
|
3140
|
-
|
|
3141
3272
|
// src/lib/state-bus.ts
|
|
3142
3273
|
var StateBus, orgBus;
|
|
3143
3274
|
var init_state_bus = __esm({
|
|
@@ -3193,13 +3324,117 @@ var init_state_bus = __esm({
|
|
|
3193
3324
|
}
|
|
3194
3325
|
});
|
|
3195
3326
|
|
|
3196
|
-
// src/lib/
|
|
3197
|
-
import crypto3 from "crypto";
|
|
3198
|
-
import path12 from "path";
|
|
3199
|
-
import os9 from "os";
|
|
3327
|
+
// src/lib/project-name.ts
|
|
3200
3328
|
import { execSync as execSync5 } from "child_process";
|
|
3329
|
+
import path13 from "path";
|
|
3330
|
+
function getProjectName(cwd) {
|
|
3331
|
+
const dir = cwd ?? process.cwd();
|
|
3332
|
+
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
3333
|
+
try {
|
|
3334
|
+
let repoRoot;
|
|
3335
|
+
try {
|
|
3336
|
+
const gitCommonDir = execSync5("git rev-parse --path-format=absolute --git-common-dir", {
|
|
3337
|
+
cwd: dir,
|
|
3338
|
+
encoding: "utf8",
|
|
3339
|
+
timeout: 2e3,
|
|
3340
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3341
|
+
}).trim();
|
|
3342
|
+
repoRoot = path13.dirname(gitCommonDir);
|
|
3343
|
+
} catch {
|
|
3344
|
+
repoRoot = execSync5("git rev-parse --show-toplevel", {
|
|
3345
|
+
cwd: dir,
|
|
3346
|
+
encoding: "utf8",
|
|
3347
|
+
timeout: 2e3,
|
|
3348
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3349
|
+
}).trim();
|
|
3350
|
+
}
|
|
3351
|
+
_cached2 = path13.basename(repoRoot);
|
|
3352
|
+
_cachedCwd = dir;
|
|
3353
|
+
return _cached2;
|
|
3354
|
+
} catch {
|
|
3355
|
+
_cached2 = path13.basename(dir);
|
|
3356
|
+
_cachedCwd = dir;
|
|
3357
|
+
return _cached2;
|
|
3358
|
+
}
|
|
3359
|
+
}
|
|
3360
|
+
var _cached2, _cachedCwd;
|
|
3361
|
+
var init_project_name = __esm({
|
|
3362
|
+
"src/lib/project-name.ts"() {
|
|
3363
|
+
"use strict";
|
|
3364
|
+
_cached2 = null;
|
|
3365
|
+
_cachedCwd = null;
|
|
3366
|
+
}
|
|
3367
|
+
});
|
|
3368
|
+
|
|
3369
|
+
// src/lib/session-scope.ts
|
|
3370
|
+
var session_scope_exports = {};
|
|
3371
|
+
__export(session_scope_exports, {
|
|
3372
|
+
assertSessionScope: () => assertSessionScope,
|
|
3373
|
+
findSessionForProject: () => findSessionForProject,
|
|
3374
|
+
getSessionProject: () => getSessionProject
|
|
3375
|
+
});
|
|
3376
|
+
function getSessionProject(sessionName) {
|
|
3377
|
+
const sessions = listSessions();
|
|
3378
|
+
const entry = sessions.find((s) => s.windowName === sessionName);
|
|
3379
|
+
if (!entry) return null;
|
|
3380
|
+
const parts = entry.projectDir.split("/").filter(Boolean);
|
|
3381
|
+
return parts[parts.length - 1] ?? null;
|
|
3382
|
+
}
|
|
3383
|
+
function findSessionForProject(projectName) {
|
|
3384
|
+
const sessions = listSessions();
|
|
3385
|
+
for (const s of sessions) {
|
|
3386
|
+
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
3387
|
+
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
3388
|
+
}
|
|
3389
|
+
return null;
|
|
3390
|
+
}
|
|
3391
|
+
function assertSessionScope(actionType, targetProject) {
|
|
3392
|
+
try {
|
|
3393
|
+
const currentProject = getProjectName();
|
|
3394
|
+
const exeSession = resolveExeSession();
|
|
3395
|
+
if (!exeSession) {
|
|
3396
|
+
return { allowed: true, reason: "no_session" };
|
|
3397
|
+
}
|
|
3398
|
+
if (currentProject === targetProject) {
|
|
3399
|
+
return {
|
|
3400
|
+
allowed: true,
|
|
3401
|
+
reason: "same_session",
|
|
3402
|
+
currentProject,
|
|
3403
|
+
targetProject
|
|
3404
|
+
};
|
|
3405
|
+
}
|
|
3406
|
+
process.stderr.write(
|
|
3407
|
+
`[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
|
|
3408
|
+
`
|
|
3409
|
+
);
|
|
3410
|
+
return {
|
|
3411
|
+
allowed: false,
|
|
3412
|
+
reason: "cross_session_denied",
|
|
3413
|
+
currentProject,
|
|
3414
|
+
targetProject,
|
|
3415
|
+
targetSession: findSessionForProject(targetProject)?.windowName
|
|
3416
|
+
};
|
|
3417
|
+
} catch {
|
|
3418
|
+
return { allowed: true, reason: "no_session" };
|
|
3419
|
+
}
|
|
3420
|
+
}
|
|
3421
|
+
var init_session_scope = __esm({
|
|
3422
|
+
"src/lib/session-scope.ts"() {
|
|
3423
|
+
"use strict";
|
|
3424
|
+
init_session_registry();
|
|
3425
|
+
init_project_name();
|
|
3426
|
+
init_tmux_routing();
|
|
3427
|
+
init_employees();
|
|
3428
|
+
}
|
|
3429
|
+
});
|
|
3430
|
+
|
|
3431
|
+
// src/lib/tasks-crud.ts
|
|
3432
|
+
import crypto4 from "crypto";
|
|
3433
|
+
import path14 from "path";
|
|
3434
|
+
import os10 from "os";
|
|
3435
|
+
import { execSync as execSync6 } from "child_process";
|
|
3201
3436
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
3202
|
-
import { existsSync as
|
|
3437
|
+
import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
|
|
3203
3438
|
async function writeCheckpoint(input) {
|
|
3204
3439
|
const client = getClient();
|
|
3205
3440
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -3315,13 +3550,28 @@ async function resolveTask(client, identifier, scopeSession) {
|
|
|
3315
3550
|
}
|
|
3316
3551
|
async function createTaskCore(input) {
|
|
3317
3552
|
const client = getClient();
|
|
3318
|
-
const id =
|
|
3553
|
+
const id = crypto4.randomUUID();
|
|
3319
3554
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3320
3555
|
const slug = slugify(input.title);
|
|
3321
3556
|
let earlySessionScope = null;
|
|
3557
|
+
let scopeMismatchWarning;
|
|
3322
3558
|
try {
|
|
3323
3559
|
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
3324
|
-
|
|
3560
|
+
const resolved = resolveExeSession2();
|
|
3561
|
+
if (resolved && input.projectName) {
|
|
3562
|
+
const { getSessionProject: getSessionProject2 } = await Promise.resolve().then(() => (init_session_scope(), session_scope_exports));
|
|
3563
|
+
const sessionProject = getSessionProject2(resolved);
|
|
3564
|
+
if (sessionProject && sessionProject !== input.projectName) {
|
|
3565
|
+
scopeMismatchWarning = `session/project mismatch: session "${resolved}" owns "${sessionProject}" but task targets "${input.projectName}". Routed to default scope.`;
|
|
3566
|
+
process.stderr.write(`[create_task] ${scopeMismatchWarning}
|
|
3567
|
+
`);
|
|
3568
|
+
earlySessionScope = null;
|
|
3569
|
+
} else {
|
|
3570
|
+
earlySessionScope = resolved;
|
|
3571
|
+
}
|
|
3572
|
+
} else {
|
|
3573
|
+
earlySessionScope = resolved;
|
|
3574
|
+
}
|
|
3325
3575
|
} catch {
|
|
3326
3576
|
}
|
|
3327
3577
|
const scope = earlySessionScope ?? "default";
|
|
@@ -3372,10 +3622,14 @@ async function createTaskCore(input) {
|
|
|
3372
3622
|
${laneWarning}` : laneWarning;
|
|
3373
3623
|
}
|
|
3374
3624
|
}
|
|
3625
|
+
if (scopeMismatchWarning) {
|
|
3626
|
+
warning = warning ? `${warning}
|
|
3627
|
+
${scopeMismatchWarning}` : scopeMismatchWarning;
|
|
3628
|
+
}
|
|
3375
3629
|
if (input.baseDir) {
|
|
3376
3630
|
try {
|
|
3377
|
-
await mkdir3(
|
|
3378
|
-
await mkdir3(
|
|
3631
|
+
await mkdir3(path14.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
3632
|
+
await mkdir3(path14.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
3379
3633
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
3380
3634
|
await ensureGitignoreExe(input.baseDir);
|
|
3381
3635
|
} catch {
|
|
@@ -3411,13 +3665,19 @@ ${laneWarning}` : laneWarning;
|
|
|
3411
3665
|
});
|
|
3412
3666
|
if (input.baseDir) {
|
|
3413
3667
|
try {
|
|
3414
|
-
const EXE_OS_DIR =
|
|
3415
|
-
const mdPath =
|
|
3416
|
-
const mdDir =
|
|
3417
|
-
if (!
|
|
3668
|
+
const EXE_OS_DIR = path14.join(os10.homedir(), ".exe-os");
|
|
3669
|
+
const mdPath = path14.join(EXE_OS_DIR, taskFile);
|
|
3670
|
+
const mdDir = path14.dirname(mdPath);
|
|
3671
|
+
if (!existsSync12(mdDir)) await mkdir3(mdDir, { recursive: true });
|
|
3418
3672
|
const reviewer = input.reviewer ?? input.assignedBy;
|
|
3419
3673
|
const mdContent = `# ${input.title}
|
|
3420
3674
|
|
|
3675
|
+
## MANDATORY: When done
|
|
3676
|
+
|
|
3677
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
3678
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
3679
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
3680
|
+
|
|
3421
3681
|
**ID:** ${id}
|
|
3422
3682
|
**Status:** ${initialStatus}
|
|
3423
3683
|
**Priority:** ${input.priority}
|
|
@@ -3431,12 +3691,6 @@ ${laneWarning}` : laneWarning;
|
|
|
3431
3691
|
## Context
|
|
3432
3692
|
|
|
3433
3693
|
${input.context}
|
|
3434
|
-
|
|
3435
|
-
## MANDATORY: When done
|
|
3436
|
-
|
|
3437
|
-
You MUST call update_task with status "done" and a result summary when finished.
|
|
3438
|
-
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
3439
|
-
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
3440
3694
|
`;
|
|
3441
3695
|
await writeFile3(mdPath, mdContent, "utf-8");
|
|
3442
3696
|
} catch (err) {
|
|
@@ -3518,14 +3772,14 @@ function isTmuxSessionAlive(identifier) {
|
|
|
3518
3772
|
if (!identifier || identifier === "unknown") return true;
|
|
3519
3773
|
try {
|
|
3520
3774
|
if (identifier.startsWith("%")) {
|
|
3521
|
-
const output =
|
|
3775
|
+
const output = execSync6("tmux list-panes -a -F '#{pane_id}'", {
|
|
3522
3776
|
timeout: 2e3,
|
|
3523
3777
|
encoding: "utf8",
|
|
3524
3778
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3525
3779
|
});
|
|
3526
3780
|
return output.split("\n").some((l) => l.trim() === identifier);
|
|
3527
3781
|
} else {
|
|
3528
|
-
|
|
3782
|
+
execSync6(`tmux has-session -t ${JSON.stringify(identifier)}`, {
|
|
3529
3783
|
timeout: 2e3,
|
|
3530
3784
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3531
3785
|
});
|
|
@@ -3534,7 +3788,7 @@ function isTmuxSessionAlive(identifier) {
|
|
|
3534
3788
|
} catch {
|
|
3535
3789
|
if (identifier.startsWith("%")) return true;
|
|
3536
3790
|
try {
|
|
3537
|
-
|
|
3791
|
+
execSync6("tmux list-sessions", {
|
|
3538
3792
|
timeout: 2e3,
|
|
3539
3793
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3540
3794
|
});
|
|
@@ -3549,12 +3803,12 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
|
|
|
3549
3803
|
if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
|
|
3550
3804
|
try {
|
|
3551
3805
|
const since = new Date(taskCreatedAt).toISOString();
|
|
3552
|
-
const branch =
|
|
3806
|
+
const branch = execSync6(
|
|
3553
3807
|
"git rev-parse --abbrev-ref HEAD 2>/dev/null",
|
|
3554
3808
|
{ encoding: "utf8", timeout: 3e3 }
|
|
3555
3809
|
).trim();
|
|
3556
3810
|
const branchArg = branch && branch !== "HEAD" ? branch : "";
|
|
3557
|
-
const commitCount =
|
|
3811
|
+
const commitCount = execSync6(
|
|
3558
3812
|
`git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
|
|
3559
3813
|
{ encoding: "utf8", timeout: 5e3 }
|
|
3560
3814
|
).trim();
|
|
@@ -3685,7 +3939,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
3685
3939
|
await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
|
|
3686
3940
|
} catch {
|
|
3687
3941
|
}
|
|
3688
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
3942
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
3689
3943
|
try {
|
|
3690
3944
|
const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
3691
3945
|
clearQueueForAgent2(String(row.assigned_to));
|
|
@@ -3714,9 +3968,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
3714
3968
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
3715
3969
|
}
|
|
3716
3970
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
3717
|
-
const archPath =
|
|
3971
|
+
const archPath = path14.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
3718
3972
|
try {
|
|
3719
|
-
if (
|
|
3973
|
+
if (existsSync12(archPath)) return;
|
|
3720
3974
|
const template = [
|
|
3721
3975
|
`# ${projectName} \u2014 System Architecture`,
|
|
3722
3976
|
"",
|
|
@@ -3749,10 +4003,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
3749
4003
|
}
|
|
3750
4004
|
}
|
|
3751
4005
|
async function ensureGitignoreExe(baseDir) {
|
|
3752
|
-
const gitignorePath =
|
|
4006
|
+
const gitignorePath = path14.join(baseDir, ".gitignore");
|
|
3753
4007
|
try {
|
|
3754
|
-
if (
|
|
3755
|
-
const content =
|
|
4008
|
+
if (existsSync12(gitignorePath)) {
|
|
4009
|
+
const content = readFileSync11(gitignorePath, "utf-8");
|
|
3756
4010
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
3757
4011
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
3758
4012
|
} else {
|
|
@@ -3783,58 +4037,42 @@ var init_tasks_crud = __esm({
|
|
|
3783
4037
|
});
|
|
3784
4038
|
|
|
3785
4039
|
// src/lib/tasks-review.ts
|
|
3786
|
-
import
|
|
3787
|
-
import { existsSync as
|
|
4040
|
+
import path15 from "path";
|
|
4041
|
+
import { existsSync as existsSync13, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
|
|
3788
4042
|
async function countPendingReviews(sessionScope) {
|
|
3789
4043
|
const client = getClient();
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
args: [sessionScope]
|
|
3794
|
-
});
|
|
3795
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
3796
|
-
}
|
|
4044
|
+
const scope = strictSessionScopeFilter(
|
|
4045
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
4046
|
+
);
|
|
3797
4047
|
const result = await client.execute({
|
|
3798
|
-
sql:
|
|
3799
|
-
|
|
4048
|
+
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
4049
|
+
WHERE status = 'needs_review'${scope.sql}`,
|
|
4050
|
+
args: [...scope.args]
|
|
3800
4051
|
});
|
|
3801
4052
|
return Number(result.rows[0]?.cnt) || 0;
|
|
3802
4053
|
}
|
|
3803
4054
|
async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
3804
4055
|
const client = getClient();
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
WHERE status = 'needs_review' AND updated_at > ?
|
|
3809
|
-
AND session_scope = ?`,
|
|
3810
|
-
args: [sinceIso, sessionScope]
|
|
3811
|
-
});
|
|
3812
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
3813
|
-
}
|
|
4056
|
+
const scope = strictSessionScopeFilter(
|
|
4057
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
4058
|
+
);
|
|
3814
4059
|
const result = await client.execute({
|
|
3815
4060
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3816
|
-
WHERE status = 'needs_review' AND updated_at >
|
|
3817
|
-
args: [sinceIso]
|
|
4061
|
+
WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
|
|
4062
|
+
args: [sinceIso, ...scope.args]
|
|
3818
4063
|
});
|
|
3819
4064
|
return Number(result.rows[0]?.cnt) || 0;
|
|
3820
4065
|
}
|
|
3821
4066
|
async function listPendingReviews(limit, sessionScope) {
|
|
3822
4067
|
const client = getClient();
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
WHERE status = 'needs_review'
|
|
3827
|
-
AND session_scope = ?
|
|
3828
|
-
ORDER BY updated_at ASC LIMIT ?`,
|
|
3829
|
-
args: [sessionScope, limit]
|
|
3830
|
-
});
|
|
3831
|
-
return result2.rows;
|
|
3832
|
-
}
|
|
4068
|
+
const scope = strictSessionScopeFilter(
|
|
4069
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
4070
|
+
);
|
|
3833
4071
|
const result = await client.execute({
|
|
3834
4072
|
sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
|
|
3835
|
-
WHERE status = 'needs_review'
|
|
4073
|
+
WHERE status = 'needs_review'${scope.sql}
|
|
3836
4074
|
ORDER BY updated_at ASC LIMIT ?`,
|
|
3837
|
-
args: [limit]
|
|
4075
|
+
args: [...scope.args, limit]
|
|
3838
4076
|
});
|
|
3839
4077
|
return result.rows;
|
|
3840
4078
|
}
|
|
@@ -3846,7 +4084,7 @@ async function cleanupOrphanedReviews() {
|
|
|
3846
4084
|
WHERE status IN ('open', 'needs_review', 'in_progress')
|
|
3847
4085
|
AND assigned_by = 'system'
|
|
3848
4086
|
AND title LIKE 'Review:%'
|
|
3849
|
-
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
|
|
4087
|
+
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
|
|
3850
4088
|
args: [now]
|
|
3851
4089
|
});
|
|
3852
4090
|
const r1b = await client.execute({
|
|
@@ -3965,11 +4203,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
3965
4203
|
);
|
|
3966
4204
|
}
|
|
3967
4205
|
try {
|
|
3968
|
-
const cacheDir =
|
|
3969
|
-
if (
|
|
4206
|
+
const cacheDir = path15.join(EXE_AI_DIR, "session-cache");
|
|
4207
|
+
if (existsSync13(cacheDir)) {
|
|
3970
4208
|
for (const f of readdirSync2(cacheDir)) {
|
|
3971
4209
|
if (f.startsWith("review-notified-")) {
|
|
3972
|
-
unlinkSync4(
|
|
4210
|
+
unlinkSync4(path15.join(cacheDir, f));
|
|
3973
4211
|
}
|
|
3974
4212
|
}
|
|
3975
4213
|
}
|
|
@@ -3986,11 +4224,12 @@ var init_tasks_review = __esm({
|
|
|
3986
4224
|
init_tmux_routing();
|
|
3987
4225
|
init_session_key();
|
|
3988
4226
|
init_state_bus();
|
|
4227
|
+
init_task_scope();
|
|
3989
4228
|
}
|
|
3990
4229
|
});
|
|
3991
4230
|
|
|
3992
4231
|
// src/lib/tasks-chain.ts
|
|
3993
|
-
import
|
|
4232
|
+
import path16 from "path";
|
|
3994
4233
|
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
3995
4234
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
3996
4235
|
const client = getClient();
|
|
@@ -4007,7 +4246,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
4007
4246
|
});
|
|
4008
4247
|
for (const ur of unblockedRows.rows) {
|
|
4009
4248
|
try {
|
|
4010
|
-
const ubFile =
|
|
4249
|
+
const ubFile = path16.join(baseDir, String(ur.task_file));
|
|
4011
4250
|
let ubContent = await readFile3(ubFile, "utf-8");
|
|
4012
4251
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
4013
4252
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -4042,7 +4281,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
|
|
|
4042
4281
|
const scScope = sessionScopeFilter();
|
|
4043
4282
|
const remaining = await client.execute({
|
|
4044
4283
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
4045
|
-
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
|
|
4284
|
+
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
|
|
4046
4285
|
args: [parentTaskId, ...scScope.args]
|
|
4047
4286
|
});
|
|
4048
4287
|
const cnt = Number(remaining.rows[0]?.cnt ?? 1);
|
|
@@ -4074,110 +4313,6 @@ var init_tasks_chain = __esm({
|
|
|
4074
4313
|
}
|
|
4075
4314
|
});
|
|
4076
4315
|
|
|
4077
|
-
// src/lib/project-name.ts
|
|
4078
|
-
import { execSync as execSync6 } from "child_process";
|
|
4079
|
-
import path15 from "path";
|
|
4080
|
-
function getProjectName(cwd) {
|
|
4081
|
-
const dir = cwd ?? process.cwd();
|
|
4082
|
-
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
4083
|
-
try {
|
|
4084
|
-
let repoRoot;
|
|
4085
|
-
try {
|
|
4086
|
-
const gitCommonDir = execSync6("git rev-parse --path-format=absolute --git-common-dir", {
|
|
4087
|
-
cwd: dir,
|
|
4088
|
-
encoding: "utf8",
|
|
4089
|
-
timeout: 2e3,
|
|
4090
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
4091
|
-
}).trim();
|
|
4092
|
-
repoRoot = path15.dirname(gitCommonDir);
|
|
4093
|
-
} catch {
|
|
4094
|
-
repoRoot = execSync6("git rev-parse --show-toplevel", {
|
|
4095
|
-
cwd: dir,
|
|
4096
|
-
encoding: "utf8",
|
|
4097
|
-
timeout: 2e3,
|
|
4098
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
4099
|
-
}).trim();
|
|
4100
|
-
}
|
|
4101
|
-
_cached2 = path15.basename(repoRoot);
|
|
4102
|
-
_cachedCwd = dir;
|
|
4103
|
-
return _cached2;
|
|
4104
|
-
} catch {
|
|
4105
|
-
_cached2 = path15.basename(dir);
|
|
4106
|
-
_cachedCwd = dir;
|
|
4107
|
-
return _cached2;
|
|
4108
|
-
}
|
|
4109
|
-
}
|
|
4110
|
-
var _cached2, _cachedCwd;
|
|
4111
|
-
var init_project_name = __esm({
|
|
4112
|
-
"src/lib/project-name.ts"() {
|
|
4113
|
-
"use strict";
|
|
4114
|
-
_cached2 = null;
|
|
4115
|
-
_cachedCwd = null;
|
|
4116
|
-
}
|
|
4117
|
-
});
|
|
4118
|
-
|
|
4119
|
-
// src/lib/session-scope.ts
|
|
4120
|
-
var session_scope_exports = {};
|
|
4121
|
-
__export(session_scope_exports, {
|
|
4122
|
-
assertSessionScope: () => assertSessionScope,
|
|
4123
|
-
findSessionForProject: () => findSessionForProject,
|
|
4124
|
-
getSessionProject: () => getSessionProject
|
|
4125
|
-
});
|
|
4126
|
-
function getSessionProject(sessionName) {
|
|
4127
|
-
const sessions = listSessions();
|
|
4128
|
-
const entry = sessions.find((s) => s.windowName === sessionName);
|
|
4129
|
-
if (!entry) return null;
|
|
4130
|
-
const parts = entry.projectDir.split("/").filter(Boolean);
|
|
4131
|
-
return parts[parts.length - 1] ?? null;
|
|
4132
|
-
}
|
|
4133
|
-
function findSessionForProject(projectName) {
|
|
4134
|
-
const sessions = listSessions();
|
|
4135
|
-
for (const s of sessions) {
|
|
4136
|
-
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
4137
|
-
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
4138
|
-
}
|
|
4139
|
-
return null;
|
|
4140
|
-
}
|
|
4141
|
-
function assertSessionScope(actionType, targetProject) {
|
|
4142
|
-
try {
|
|
4143
|
-
const currentProject = getProjectName();
|
|
4144
|
-
const exeSession = resolveExeSession();
|
|
4145
|
-
if (!exeSession) {
|
|
4146
|
-
return { allowed: true, reason: "no_session" };
|
|
4147
|
-
}
|
|
4148
|
-
if (currentProject === targetProject) {
|
|
4149
|
-
return {
|
|
4150
|
-
allowed: true,
|
|
4151
|
-
reason: "same_session",
|
|
4152
|
-
currentProject,
|
|
4153
|
-
targetProject
|
|
4154
|
-
};
|
|
4155
|
-
}
|
|
4156
|
-
process.stderr.write(
|
|
4157
|
-
`[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
|
|
4158
|
-
`
|
|
4159
|
-
);
|
|
4160
|
-
return {
|
|
4161
|
-
allowed: false,
|
|
4162
|
-
reason: "cross_session_denied",
|
|
4163
|
-
currentProject,
|
|
4164
|
-
targetProject,
|
|
4165
|
-
targetSession: findSessionForProject(targetProject)?.windowName
|
|
4166
|
-
};
|
|
4167
|
-
} catch {
|
|
4168
|
-
return { allowed: true, reason: "no_session" };
|
|
4169
|
-
}
|
|
4170
|
-
}
|
|
4171
|
-
var init_session_scope = __esm({
|
|
4172
|
-
"src/lib/session-scope.ts"() {
|
|
4173
|
-
"use strict";
|
|
4174
|
-
init_session_registry();
|
|
4175
|
-
init_project_name();
|
|
4176
|
-
init_tmux_routing();
|
|
4177
|
-
init_employees();
|
|
4178
|
-
}
|
|
4179
|
-
});
|
|
4180
|
-
|
|
4181
4316
|
// src/lib/tasks-notify.ts
|
|
4182
4317
|
async function dispatchTaskToEmployee(input) {
|
|
4183
4318
|
if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
|
|
@@ -4252,10 +4387,10 @@ __export(behaviors_exports, {
|
|
|
4252
4387
|
listBehaviorsByDomain: () => listBehaviorsByDomain,
|
|
4253
4388
|
storeBehavior: () => storeBehavior
|
|
4254
4389
|
});
|
|
4255
|
-
import
|
|
4390
|
+
import crypto5 from "crypto";
|
|
4256
4391
|
async function storeBehavior(opts) {
|
|
4257
4392
|
const client = getClient();
|
|
4258
|
-
const id =
|
|
4393
|
+
const id = crypto5.randomUUID();
|
|
4259
4394
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4260
4395
|
await client.execute({
|
|
4261
4396
|
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
|
|
@@ -4339,7 +4474,7 @@ __export(skill_learning_exports, {
|
|
|
4339
4474
|
storeTrajectory: () => storeTrajectory,
|
|
4340
4475
|
sweepTrajectories: () => sweepTrajectories
|
|
4341
4476
|
});
|
|
4342
|
-
import
|
|
4477
|
+
import crypto6 from "crypto";
|
|
4343
4478
|
async function extractTrajectory(taskId, agentId) {
|
|
4344
4479
|
const client = getClient();
|
|
4345
4480
|
const result = await client.execute({
|
|
@@ -4368,11 +4503,11 @@ async function extractTrajectory(taskId, agentId) {
|
|
|
4368
4503
|
return signature;
|
|
4369
4504
|
}
|
|
4370
4505
|
function hashSignature(signature) {
|
|
4371
|
-
return
|
|
4506
|
+
return crypto6.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
|
|
4372
4507
|
}
|
|
4373
4508
|
async function storeTrajectory(opts) {
|
|
4374
4509
|
const client = getClient();
|
|
4375
|
-
const id =
|
|
4510
|
+
const id = crypto6.randomUUID();
|
|
4376
4511
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4377
4512
|
const signatureHash = hashSignature(opts.signature);
|
|
4378
4513
|
await client.execute({
|
|
@@ -4637,8 +4772,8 @@ __export(tasks_exports, {
|
|
|
4637
4772
|
updateTaskStatus: () => updateTaskStatus,
|
|
4638
4773
|
writeCheckpoint: () => writeCheckpoint
|
|
4639
4774
|
});
|
|
4640
|
-
import
|
|
4641
|
-
import { writeFileSync as
|
|
4775
|
+
import path17 from "path";
|
|
4776
|
+
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
|
|
4642
4777
|
async function createTask(input) {
|
|
4643
4778
|
const result = await createTaskCore(input);
|
|
4644
4779
|
if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -4657,12 +4792,12 @@ async function updateTask(input) {
|
|
|
4657
4792
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
4658
4793
|
try {
|
|
4659
4794
|
const agent = String(row.assigned_to);
|
|
4660
|
-
const cacheDir =
|
|
4661
|
-
const cachePath =
|
|
4795
|
+
const cacheDir = path17.join(EXE_AI_DIR, "session-cache");
|
|
4796
|
+
const cachePath = path17.join(cacheDir, `current-task-${agent}.json`);
|
|
4662
4797
|
if (input.status === "in_progress") {
|
|
4663
4798
|
mkdirSync5(cacheDir, { recursive: true });
|
|
4664
|
-
|
|
4665
|
-
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
4799
|
+
writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
4800
|
+
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
|
|
4666
4801
|
try {
|
|
4667
4802
|
unlinkSync5(cachePath);
|
|
4668
4803
|
} catch {
|
|
@@ -4670,10 +4805,10 @@ async function updateTask(input) {
|
|
|
4670
4805
|
}
|
|
4671
4806
|
} catch {
|
|
4672
4807
|
}
|
|
4673
|
-
if (input.status === "done") {
|
|
4808
|
+
if (input.status === "done" || input.status === "closed") {
|
|
4674
4809
|
await cleanupReviewFile(row, taskFile, input.baseDir);
|
|
4675
4810
|
}
|
|
4676
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
4811
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
4677
4812
|
try {
|
|
4678
4813
|
const client = getClient();
|
|
4679
4814
|
const taskTitle = String(row.title);
|
|
@@ -4689,7 +4824,7 @@ async function updateTask(input) {
|
|
|
4689
4824
|
if (!isCoordinatorName(assignedAgent)) {
|
|
4690
4825
|
try {
|
|
4691
4826
|
const draftClient = getClient();
|
|
4692
|
-
if (input.status === "done") {
|
|
4827
|
+
if (input.status === "done" || input.status === "closed") {
|
|
4693
4828
|
await draftClient.execute({
|
|
4694
4829
|
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
4695
4830
|
args: [assignedAgent]
|
|
@@ -4706,7 +4841,7 @@ async function updateTask(input) {
|
|
|
4706
4841
|
try {
|
|
4707
4842
|
const client = getClient();
|
|
4708
4843
|
const cascaded = await client.execute({
|
|
4709
|
-
sql: `UPDATE tasks SET status = '
|
|
4844
|
+
sql: `UPDATE tasks SET status = 'closed', updated_at = ?
|
|
4710
4845
|
WHERE parent_task_id = ? AND status = 'needs_review'`,
|
|
4711
4846
|
args: [now, taskId]
|
|
4712
4847
|
});
|
|
@@ -4719,14 +4854,14 @@ async function updateTask(input) {
|
|
|
4719
4854
|
} catch {
|
|
4720
4855
|
}
|
|
4721
4856
|
}
|
|
4722
|
-
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
4857
|
+
const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
|
|
4723
4858
|
if (isTerminal) {
|
|
4724
4859
|
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
4725
4860
|
if (!isCoordinator) {
|
|
4726
4861
|
notifyTaskDone();
|
|
4727
4862
|
}
|
|
4728
4863
|
await markTaskNotificationsRead(taskFile);
|
|
4729
|
-
if (input.status === "done") {
|
|
4864
|
+
if (input.status === "done" || input.status === "closed") {
|
|
4730
4865
|
try {
|
|
4731
4866
|
await cascadeUnblock(taskId, input.baseDir, now);
|
|
4732
4867
|
} catch {
|
|
@@ -4746,7 +4881,7 @@ async function updateTask(input) {
|
|
|
4746
4881
|
}
|
|
4747
4882
|
}
|
|
4748
4883
|
}
|
|
4749
|
-
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
4884
|
+
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
4750
4885
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
4751
4886
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
4752
4887
|
taskId,
|
|
@@ -5118,6 +5253,7 @@ __export(tmux_routing_exports, {
|
|
|
5118
5253
|
isEmployeeAlive: () => isEmployeeAlive,
|
|
5119
5254
|
isExeSession: () => isExeSession,
|
|
5120
5255
|
isSessionBusy: () => isSessionBusy,
|
|
5256
|
+
notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
|
|
5121
5257
|
notifyParentExe: () => notifyParentExe,
|
|
5122
5258
|
parseParentExe: () => parseParentExe,
|
|
5123
5259
|
registerParentExe: () => registerParentExe,
|
|
@@ -5128,13 +5264,13 @@ __export(tmux_routing_exports, {
|
|
|
5128
5264
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
5129
5265
|
});
|
|
5130
5266
|
import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
|
|
5131
|
-
import { readFileSync as
|
|
5132
|
-
import
|
|
5133
|
-
import
|
|
5267
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync6, existsSync as existsSync14, appendFileSync, readdirSync as readdirSync3 } from "fs";
|
|
5268
|
+
import path18 from "path";
|
|
5269
|
+
import os11 from "os";
|
|
5134
5270
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5135
5271
|
import { unlinkSync as unlinkSync6 } from "fs";
|
|
5136
5272
|
function spawnLockPath(sessionName) {
|
|
5137
|
-
return
|
|
5273
|
+
return path18.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
5138
5274
|
}
|
|
5139
5275
|
function isProcessAlive(pid) {
|
|
5140
5276
|
try {
|
|
@@ -5145,13 +5281,13 @@ function isProcessAlive(pid) {
|
|
|
5145
5281
|
}
|
|
5146
5282
|
}
|
|
5147
5283
|
function acquireSpawnLock2(sessionName) {
|
|
5148
|
-
if (!
|
|
5284
|
+
if (!existsSync14(SPAWN_LOCK_DIR)) {
|
|
5149
5285
|
mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
|
|
5150
5286
|
}
|
|
5151
5287
|
const lockFile = spawnLockPath(sessionName);
|
|
5152
|
-
if (
|
|
5288
|
+
if (existsSync14(lockFile)) {
|
|
5153
5289
|
try {
|
|
5154
|
-
const lock = JSON.parse(
|
|
5290
|
+
const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
|
|
5155
5291
|
const age = Date.now() - lock.timestamp;
|
|
5156
5292
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
5157
5293
|
return false;
|
|
@@ -5159,7 +5295,7 @@ function acquireSpawnLock2(sessionName) {
|
|
|
5159
5295
|
} catch {
|
|
5160
5296
|
}
|
|
5161
5297
|
}
|
|
5162
|
-
|
|
5298
|
+
writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
5163
5299
|
return true;
|
|
5164
5300
|
}
|
|
5165
5301
|
function releaseSpawnLock2(sessionName) {
|
|
@@ -5171,13 +5307,13 @@ function releaseSpawnLock2(sessionName) {
|
|
|
5171
5307
|
function resolveBehaviorsExporterScript() {
|
|
5172
5308
|
try {
|
|
5173
5309
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
5174
|
-
const scriptPath =
|
|
5175
|
-
|
|
5310
|
+
const scriptPath = path18.join(
|
|
5311
|
+
path18.dirname(thisFile),
|
|
5176
5312
|
"..",
|
|
5177
5313
|
"bin",
|
|
5178
5314
|
"exe-export-behaviors.js"
|
|
5179
5315
|
);
|
|
5180
|
-
return
|
|
5316
|
+
return existsSync14(scriptPath) ? scriptPath : null;
|
|
5181
5317
|
} catch {
|
|
5182
5318
|
return null;
|
|
5183
5319
|
}
|
|
@@ -5243,12 +5379,12 @@ function extractRootExe(name) {
|
|
|
5243
5379
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
5244
5380
|
}
|
|
5245
5381
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
5246
|
-
if (!
|
|
5382
|
+
if (!existsSync14(SESSION_CACHE)) {
|
|
5247
5383
|
mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
5248
5384
|
}
|
|
5249
5385
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
5250
|
-
const filePath =
|
|
5251
|
-
|
|
5386
|
+
const filePath = path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
5387
|
+
writeFileSync8(filePath, JSON.stringify({
|
|
5252
5388
|
parentExe: rootExe,
|
|
5253
5389
|
dispatchedBy: dispatchedBy || rootExe,
|
|
5254
5390
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -5256,7 +5392,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
5256
5392
|
}
|
|
5257
5393
|
function getParentExe(sessionKey) {
|
|
5258
5394
|
try {
|
|
5259
|
-
const data = JSON.parse(
|
|
5395
|
+
const data = JSON.parse(readFileSync12(path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
5260
5396
|
return data.parentExe || null;
|
|
5261
5397
|
} catch {
|
|
5262
5398
|
return null;
|
|
@@ -5264,8 +5400,8 @@ function getParentExe(sessionKey) {
|
|
|
5264
5400
|
}
|
|
5265
5401
|
function getDispatchedBy(sessionKey) {
|
|
5266
5402
|
try {
|
|
5267
|
-
const data = JSON.parse(
|
|
5268
|
-
|
|
5403
|
+
const data = JSON.parse(readFileSync12(
|
|
5404
|
+
path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
5269
5405
|
"utf8"
|
|
5270
5406
|
));
|
|
5271
5407
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -5335,8 +5471,8 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
5335
5471
|
}
|
|
5336
5472
|
function readDebounceState() {
|
|
5337
5473
|
try {
|
|
5338
|
-
if (!
|
|
5339
|
-
const raw = JSON.parse(
|
|
5474
|
+
if (!existsSync14(DEBOUNCE_FILE)) return {};
|
|
5475
|
+
const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
|
|
5340
5476
|
const state = {};
|
|
5341
5477
|
for (const [key, val] of Object.entries(raw)) {
|
|
5342
5478
|
if (typeof val === "number") {
|
|
@@ -5352,8 +5488,8 @@ function readDebounceState() {
|
|
|
5352
5488
|
}
|
|
5353
5489
|
function writeDebounceState(state) {
|
|
5354
5490
|
try {
|
|
5355
|
-
if (!
|
|
5356
|
-
|
|
5491
|
+
if (!existsSync14(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
5492
|
+
writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
|
|
5357
5493
|
} catch {
|
|
5358
5494
|
}
|
|
5359
5495
|
}
|
|
@@ -5451,8 +5587,8 @@ function sendIntercom(targetSession) {
|
|
|
5451
5587
|
try {
|
|
5452
5588
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
5453
5589
|
const agent = baseAgentName(rawAgent);
|
|
5454
|
-
const markerPath =
|
|
5455
|
-
if (
|
|
5590
|
+
const markerPath = path18.join(SESSION_CACHE, `current-task-${agent}.json`);
|
|
5591
|
+
if (existsSync14(markerPath)) {
|
|
5456
5592
|
logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
|
|
5457
5593
|
return "debounced";
|
|
5458
5594
|
}
|
|
@@ -5461,8 +5597,8 @@ function sendIntercom(targetSession) {
|
|
|
5461
5597
|
try {
|
|
5462
5598
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
5463
5599
|
const agent = baseAgentName(rawAgent);
|
|
5464
|
-
const taskDir =
|
|
5465
|
-
if (
|
|
5600
|
+
const taskDir = path18.join(process.cwd(), "exe", agent);
|
|
5601
|
+
if (existsSync14(taskDir)) {
|
|
5466
5602
|
const files = readdirSync3(taskDir).filter(
|
|
5467
5603
|
(f) => f.endsWith(".md") && f !== "DONE.txt"
|
|
5468
5604
|
);
|
|
@@ -5522,6 +5658,21 @@ function notifyParentExe(sessionKey) {
|
|
|
5522
5658
|
}
|
|
5523
5659
|
return true;
|
|
5524
5660
|
}
|
|
5661
|
+
function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
|
|
5662
|
+
const transport = getTransport();
|
|
5663
|
+
try {
|
|
5664
|
+
const sessions = transport.listSessions();
|
|
5665
|
+
if (!sessions.includes(coordinatorSession)) return false;
|
|
5666
|
+
execSync7(
|
|
5667
|
+
`tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
|
|
5668
|
+
{ timeout: 3e3 }
|
|
5669
|
+
);
|
|
5670
|
+
logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
|
|
5671
|
+
return true;
|
|
5672
|
+
} catch {
|
|
5673
|
+
return false;
|
|
5674
|
+
}
|
|
5675
|
+
}
|
|
5525
5676
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
5526
5677
|
if (isCoordinatorName(employeeName)) {
|
|
5527
5678
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
@@ -5595,26 +5746,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5595
5746
|
const transport = getTransport();
|
|
5596
5747
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
5597
5748
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
5598
|
-
const logDir =
|
|
5599
|
-
const logFile =
|
|
5600
|
-
if (!
|
|
5749
|
+
const logDir = path18.join(os11.homedir(), ".exe-os", "session-logs");
|
|
5750
|
+
const logFile = path18.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
5751
|
+
if (!existsSync14(logDir)) {
|
|
5601
5752
|
mkdirSync6(logDir, { recursive: true });
|
|
5602
5753
|
}
|
|
5603
5754
|
transport.kill(sessionName);
|
|
5604
5755
|
let cleanupSuffix = "";
|
|
5605
5756
|
try {
|
|
5606
5757
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
5607
|
-
const cleanupScript =
|
|
5608
|
-
if (
|
|
5758
|
+
const cleanupScript = path18.join(path18.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
5759
|
+
if (existsSync14(cleanupScript)) {
|
|
5609
5760
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
5610
5761
|
}
|
|
5611
5762
|
} catch {
|
|
5612
5763
|
}
|
|
5613
5764
|
try {
|
|
5614
|
-
const claudeJsonPath =
|
|
5765
|
+
const claudeJsonPath = path18.join(os11.homedir(), ".claude.json");
|
|
5615
5766
|
let claudeJson = {};
|
|
5616
5767
|
try {
|
|
5617
|
-
claudeJson = JSON.parse(
|
|
5768
|
+
claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
|
|
5618
5769
|
} catch {
|
|
5619
5770
|
}
|
|
5620
5771
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -5622,17 +5773,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5622
5773
|
const trustDir = opts?.cwd ?? projectDir;
|
|
5623
5774
|
if (!projects[trustDir]) projects[trustDir] = {};
|
|
5624
5775
|
projects[trustDir].hasTrustDialogAccepted = true;
|
|
5625
|
-
|
|
5776
|
+
writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
5626
5777
|
} catch {
|
|
5627
5778
|
}
|
|
5628
5779
|
try {
|
|
5629
|
-
const settingsDir =
|
|
5780
|
+
const settingsDir = path18.join(os11.homedir(), ".claude", "projects");
|
|
5630
5781
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
5631
|
-
const projSettingsDir =
|
|
5632
|
-
const settingsPath =
|
|
5782
|
+
const projSettingsDir = path18.join(settingsDir, normalizedKey);
|
|
5783
|
+
const settingsPath = path18.join(projSettingsDir, "settings.json");
|
|
5633
5784
|
let settings = {};
|
|
5634
5785
|
try {
|
|
5635
|
-
settings = JSON.parse(
|
|
5786
|
+
settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
|
|
5636
5787
|
} catch {
|
|
5637
5788
|
}
|
|
5638
5789
|
const perms = settings.permissions ?? {};
|
|
@@ -5661,7 +5812,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5661
5812
|
perms.allow = allow;
|
|
5662
5813
|
settings.permissions = perms;
|
|
5663
5814
|
mkdirSync6(projSettingsDir, { recursive: true });
|
|
5664
|
-
|
|
5815
|
+
writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
5665
5816
|
}
|
|
5666
5817
|
} catch {
|
|
5667
5818
|
}
|
|
@@ -5676,8 +5827,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5676
5827
|
let behaviorsFlag = "";
|
|
5677
5828
|
let legacyFallbackWarned = false;
|
|
5678
5829
|
if (!useExeAgent && !useBinSymlink) {
|
|
5679
|
-
const identityPath =
|
|
5680
|
-
|
|
5830
|
+
const identityPath = path18.join(
|
|
5831
|
+
os11.homedir(),
|
|
5681
5832
|
".exe-os",
|
|
5682
5833
|
"identity",
|
|
5683
5834
|
`${employeeName}.md`
|
|
@@ -5686,13 +5837,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5686
5837
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
5687
5838
|
if (hasAgentFlag) {
|
|
5688
5839
|
identityFlag = ` --agent ${employeeName}`;
|
|
5689
|
-
} else if (
|
|
5840
|
+
} else if (existsSync14(identityPath)) {
|
|
5690
5841
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
5691
5842
|
legacyFallbackWarned = true;
|
|
5692
5843
|
}
|
|
5693
5844
|
const behaviorsFile = exportBehaviorsSync(
|
|
5694
5845
|
employeeName,
|
|
5695
|
-
|
|
5846
|
+
path18.basename(spawnCwd),
|
|
5696
5847
|
sessionName
|
|
5697
5848
|
);
|
|
5698
5849
|
if (behaviorsFile) {
|
|
@@ -5707,16 +5858,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5707
5858
|
}
|
|
5708
5859
|
let sessionContextFlag = "";
|
|
5709
5860
|
try {
|
|
5710
|
-
const ctxDir =
|
|
5861
|
+
const ctxDir = path18.join(os11.homedir(), ".exe-os", "session-cache");
|
|
5711
5862
|
mkdirSync6(ctxDir, { recursive: true });
|
|
5712
|
-
const ctxFile =
|
|
5863
|
+
const ctxFile = path18.join(ctxDir, `session-context-${sessionName}.md`);
|
|
5713
5864
|
const ctxContent = [
|
|
5714
5865
|
`## Session Context`,
|
|
5715
5866
|
`You are running in tmux session: ${sessionName}.`,
|
|
5716
5867
|
`Your parent coordinator session is ${exeSession}.`,
|
|
5717
5868
|
`Your employees (if any) use the -${exeSession} suffix.`
|
|
5718
5869
|
].join("\n");
|
|
5719
|
-
|
|
5870
|
+
writeFileSync8(ctxFile, ctxContent);
|
|
5720
5871
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
5721
5872
|
} catch {
|
|
5722
5873
|
}
|
|
@@ -5793,8 +5944,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5793
5944
|
transport.pipeLog(sessionName, logFile);
|
|
5794
5945
|
try {
|
|
5795
5946
|
const mySession = getMySession();
|
|
5796
|
-
const dispatchInfo =
|
|
5797
|
-
|
|
5947
|
+
const dispatchInfo = path18.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
5948
|
+
writeFileSync8(dispatchInfo, JSON.stringify({
|
|
5798
5949
|
dispatchedBy: mySession,
|
|
5799
5950
|
rootExe: exeSession,
|
|
5800
5951
|
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
|
|
@@ -5868,15 +6019,15 @@ var init_tmux_routing = __esm({
|
|
|
5868
6019
|
init_intercom_queue();
|
|
5869
6020
|
init_plan_limits();
|
|
5870
6021
|
init_employees();
|
|
5871
|
-
SPAWN_LOCK_DIR =
|
|
5872
|
-
SESSION_CACHE =
|
|
6022
|
+
SPAWN_LOCK_DIR = path18.join(os11.homedir(), ".exe-os", "spawn-locks");
|
|
6023
|
+
SESSION_CACHE = path18.join(os11.homedir(), ".exe-os", "session-cache");
|
|
5873
6024
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
5874
6025
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
5875
6026
|
VERIFY_PANE_LINES = 200;
|
|
5876
6027
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
5877
6028
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
5878
|
-
INTERCOM_LOG2 =
|
|
5879
|
-
DEBOUNCE_FILE =
|
|
6029
|
+
INTERCOM_LOG2 = path18.join(os11.homedir(), ".exe-os", "intercom.log");
|
|
6030
|
+
DEBOUNCE_FILE = path18.join(SESSION_CACHE, "intercom-debounce.json");
|
|
5880
6031
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
5881
6032
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
|
|
5882
6033
|
}
|
|
@@ -5893,14 +6044,14 @@ var init_memory = __esm({
|
|
|
5893
6044
|
|
|
5894
6045
|
// src/lib/keychain.ts
|
|
5895
6046
|
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
5896
|
-
import { existsSync as
|
|
5897
|
-
import
|
|
5898
|
-
import
|
|
6047
|
+
import { existsSync as existsSync15 } from "fs";
|
|
6048
|
+
import path19 from "path";
|
|
6049
|
+
import os12 from "os";
|
|
5899
6050
|
function getKeyDir() {
|
|
5900
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
6051
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path19.join(os12.homedir(), ".exe-os");
|
|
5901
6052
|
}
|
|
5902
6053
|
function getKeyPath() {
|
|
5903
|
-
return
|
|
6054
|
+
return path19.join(getKeyDir(), "master.key");
|
|
5904
6055
|
}
|
|
5905
6056
|
async function tryKeytar() {
|
|
5906
6057
|
try {
|
|
@@ -5921,9 +6072,9 @@ async function getMasterKey() {
|
|
|
5921
6072
|
}
|
|
5922
6073
|
}
|
|
5923
6074
|
const keyPath = getKeyPath();
|
|
5924
|
-
if (!
|
|
6075
|
+
if (!existsSync15(keyPath)) {
|
|
5925
6076
|
process.stderr.write(
|
|
5926
|
-
`[keychain] Key not found at ${keyPath} (HOME=${
|
|
6077
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os12.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
5927
6078
|
`
|
|
5928
6079
|
);
|
|
5929
6080
|
return null;
|
|
@@ -5953,6 +6104,7 @@ var shard_manager_exports = {};
|
|
|
5953
6104
|
__export(shard_manager_exports, {
|
|
5954
6105
|
disposeShards: () => disposeShards,
|
|
5955
6106
|
ensureShardSchema: () => ensureShardSchema,
|
|
6107
|
+
getOpenShardCount: () => getOpenShardCount,
|
|
5956
6108
|
getReadyShardClient: () => getReadyShardClient,
|
|
5957
6109
|
getShardClient: () => getShardClient,
|
|
5958
6110
|
getShardsDir: () => getShardsDir,
|
|
@@ -5961,15 +6113,18 @@ __export(shard_manager_exports, {
|
|
|
5961
6113
|
listShards: () => listShards,
|
|
5962
6114
|
shardExists: () => shardExists
|
|
5963
6115
|
});
|
|
5964
|
-
import
|
|
5965
|
-
import { existsSync as
|
|
6116
|
+
import path20 from "path";
|
|
6117
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
|
|
5966
6118
|
import { createClient as createClient2 } from "@libsql/client";
|
|
5967
6119
|
function initShardManager(encryptionKey) {
|
|
5968
6120
|
_encryptionKey = encryptionKey;
|
|
5969
|
-
if (!
|
|
6121
|
+
if (!existsSync16(SHARDS_DIR)) {
|
|
5970
6122
|
mkdirSync7(SHARDS_DIR, { recursive: true });
|
|
5971
6123
|
}
|
|
5972
6124
|
_shardingEnabled = true;
|
|
6125
|
+
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
6126
|
+
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
6127
|
+
_evictionTimer.unref();
|
|
5973
6128
|
}
|
|
5974
6129
|
function isShardingEnabled() {
|
|
5975
6130
|
return _shardingEnabled;
|
|
@@ -5986,21 +6141,28 @@ function getShardClient(projectName) {
|
|
|
5986
6141
|
throw new Error(`Invalid project name for shard: "${projectName}"`);
|
|
5987
6142
|
}
|
|
5988
6143
|
const cached = _shards.get(safeName);
|
|
5989
|
-
if (cached)
|
|
5990
|
-
|
|
6144
|
+
if (cached) {
|
|
6145
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
6146
|
+
return cached;
|
|
6147
|
+
}
|
|
6148
|
+
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
6149
|
+
evictLRU();
|
|
6150
|
+
}
|
|
6151
|
+
const dbPath = path20.join(SHARDS_DIR, `${safeName}.db`);
|
|
5991
6152
|
const client = createClient2({
|
|
5992
6153
|
url: `file:${dbPath}`,
|
|
5993
6154
|
encryptionKey: _encryptionKey
|
|
5994
6155
|
});
|
|
5995
6156
|
_shards.set(safeName, client);
|
|
6157
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
5996
6158
|
return client;
|
|
5997
6159
|
}
|
|
5998
6160
|
function shardExists(projectName) {
|
|
5999
6161
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
6000
|
-
return
|
|
6162
|
+
return existsSync16(path20.join(SHARDS_DIR, `${safeName}.db`));
|
|
6001
6163
|
}
|
|
6002
6164
|
function listShards() {
|
|
6003
|
-
if (!
|
|
6165
|
+
if (!existsSync16(SHARDS_DIR)) return [];
|
|
6004
6166
|
return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
6005
6167
|
}
|
|
6006
6168
|
async function ensureShardSchema(client) {
|
|
@@ -6052,6 +6214,8 @@ async function ensureShardSchema(client) {
|
|
|
6052
6214
|
for (const col of [
|
|
6053
6215
|
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
6054
6216
|
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
6217
|
+
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
6218
|
+
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
6055
6219
|
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
6056
6220
|
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
6057
6221
|
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
@@ -6189,21 +6353,69 @@ async function getReadyShardClient(projectName) {
|
|
|
6189
6353
|
await ensureShardSchema(client);
|
|
6190
6354
|
return client;
|
|
6191
6355
|
}
|
|
6356
|
+
function evictLRU() {
|
|
6357
|
+
let oldest = null;
|
|
6358
|
+
let oldestTime = Infinity;
|
|
6359
|
+
for (const [name, time] of _shardLastAccess) {
|
|
6360
|
+
if (time < oldestTime) {
|
|
6361
|
+
oldestTime = time;
|
|
6362
|
+
oldest = name;
|
|
6363
|
+
}
|
|
6364
|
+
}
|
|
6365
|
+
if (oldest) {
|
|
6366
|
+
const client = _shards.get(oldest);
|
|
6367
|
+
if (client) {
|
|
6368
|
+
client.close();
|
|
6369
|
+
}
|
|
6370
|
+
_shards.delete(oldest);
|
|
6371
|
+
_shardLastAccess.delete(oldest);
|
|
6372
|
+
}
|
|
6373
|
+
}
|
|
6374
|
+
function evictIdleShards() {
|
|
6375
|
+
const now = Date.now();
|
|
6376
|
+
const toEvict = [];
|
|
6377
|
+
for (const [name, lastAccess] of _shardLastAccess) {
|
|
6378
|
+
if (now - lastAccess > SHARD_IDLE_MS) {
|
|
6379
|
+
toEvict.push(name);
|
|
6380
|
+
}
|
|
6381
|
+
}
|
|
6382
|
+
for (const name of toEvict) {
|
|
6383
|
+
const client = _shards.get(name);
|
|
6384
|
+
if (client) {
|
|
6385
|
+
client.close();
|
|
6386
|
+
}
|
|
6387
|
+
_shards.delete(name);
|
|
6388
|
+
_shardLastAccess.delete(name);
|
|
6389
|
+
}
|
|
6390
|
+
}
|
|
6391
|
+
function getOpenShardCount() {
|
|
6392
|
+
return _shards.size;
|
|
6393
|
+
}
|
|
6192
6394
|
function disposeShards() {
|
|
6395
|
+
if (_evictionTimer) {
|
|
6396
|
+
clearInterval(_evictionTimer);
|
|
6397
|
+
_evictionTimer = null;
|
|
6398
|
+
}
|
|
6193
6399
|
for (const [, client] of _shards) {
|
|
6194
6400
|
client.close();
|
|
6195
6401
|
}
|
|
6196
6402
|
_shards.clear();
|
|
6403
|
+
_shardLastAccess.clear();
|
|
6197
6404
|
_shardingEnabled = false;
|
|
6198
6405
|
_encryptionKey = null;
|
|
6199
6406
|
}
|
|
6200
|
-
var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
|
|
6407
|
+
var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
|
|
6201
6408
|
var init_shard_manager = __esm({
|
|
6202
6409
|
"src/lib/shard-manager.ts"() {
|
|
6203
6410
|
"use strict";
|
|
6204
6411
|
init_config();
|
|
6205
|
-
SHARDS_DIR =
|
|
6412
|
+
SHARDS_DIR = path20.join(EXE_AI_DIR, "shards");
|
|
6413
|
+
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
6414
|
+
MAX_OPEN_SHARDS = 10;
|
|
6415
|
+
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
6206
6416
|
_shards = /* @__PURE__ */ new Map();
|
|
6417
|
+
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
6418
|
+
_evictionTimer = null;
|
|
6207
6419
|
_encryptionKey = null;
|
|
6208
6420
|
_shardingEnabled = false;
|
|
6209
6421
|
}
|
|
@@ -6999,8 +7211,8 @@ function findContainingChunk(filePath, snippet) {
|
|
|
6999
7211
|
try {
|
|
7000
7212
|
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
7001
7213
|
if (ext !== "ts" && ext !== "tsx" && ext !== "js" && ext !== "jsx") return "";
|
|
7002
|
-
const { readFileSync:
|
|
7003
|
-
const source =
|
|
7214
|
+
const { readFileSync: readFileSync13 } = __require("fs");
|
|
7215
|
+
const source = readFileSync13(filePath, "utf8");
|
|
7004
7216
|
const lines = source.split("\n");
|
|
7005
7217
|
const lowerSnippet = snippet.toLowerCase().slice(0, 80);
|
|
7006
7218
|
let matchLine = -1;
|
|
@@ -7066,9 +7278,9 @@ function extractBash(input, response) {
|
|
|
7066
7278
|
}
|
|
7067
7279
|
function extractGrep(input, response) {
|
|
7068
7280
|
const pattern = String(input.pattern ?? "");
|
|
7069
|
-
const
|
|
7281
|
+
const path21 = input.path ? String(input.path) : "";
|
|
7070
7282
|
const output = String(response.text ?? response.content ?? JSON.stringify(response).slice(0, MAX_OUTPUT));
|
|
7071
|
-
return `Searched for "${pattern}"${
|
|
7283
|
+
return `Searched for "${pattern}"${path21 ? ` in ${path21}` : ""}
|
|
7072
7284
|
${output.slice(0, MAX_OUTPUT)}`;
|
|
7073
7285
|
}
|
|
7074
7286
|
function extractGlob(input, response) {
|
|
@@ -7171,7 +7383,7 @@ __export(error_detector_exports, {
|
|
|
7171
7383
|
errorFingerprint: () => errorFingerprint,
|
|
7172
7384
|
isExeOsError: () => isExeOsError
|
|
7173
7385
|
});
|
|
7174
|
-
import
|
|
7386
|
+
import crypto7 from "crypto";
|
|
7175
7387
|
function isRealStderr(stderr) {
|
|
7176
7388
|
const lines = stderr.trim().split("\n");
|
|
7177
7389
|
const meaningful = lines.filter(
|
|
@@ -7242,7 +7454,7 @@ function classifyError(errorText) {
|
|
|
7242
7454
|
}
|
|
7243
7455
|
function errorFingerprint(toolName, errorText) {
|
|
7244
7456
|
const normalized = errorText.replace(/\d{4}-\d{2}-\d{2}T[\d:.]+Z/g, "TIMESTAMP").replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, "UUID").replace(/\/Users\/[^\s]+/g, "PATH").replace(/:\d+:\d+/g, ":LINE:COL").slice(0, 200);
|
|
7245
|
-
return
|
|
7457
|
+
return crypto7.createHash("sha256").update(`${toolName}:${normalized}`).digest("hex").slice(0, 16);
|
|
7246
7458
|
}
|
|
7247
7459
|
var ERROR_PATTERNS, FILE_CONTENT_TOOLS, STDERR_IGNORE_PATTERNS, USER_ERROR_PATTERNS, SYSTEM_BUG_PATTERNS;
|
|
7248
7460
|
var init_error_detector = __esm({
|