@askexenow/exe-os 0.9.8 → 0.9.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/backfill-conversations.js +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 +1295 -856
- 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 +778 -427
- package/dist/bin/exe-call.js +41 -13
- package/dist/bin/exe-cloud.js +163 -58
- package/dist/bin/exe-dispatch.js +276 -139
- 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 +677 -388
- 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 +440 -250
- 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 +404 -212
- 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 +412 -220
- 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 +533 -320
- package/dist/hooks/bug-report-worker.js +344 -193
- package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
- package/dist/hooks/commit-complete.js +402 -210
- package/dist/hooks/error-recall.js +245 -74
- package/dist/hooks/exe-heartbeat-hook.js +16 -6
- package/dist/hooks/ingest-worker.js +3423 -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 +408 -216
- package/dist/hooks/pre-tool-use.js +268 -90
- package/dist/hooks/prompt-ingest-worker.js +352 -102
- package/dist/hooks/prompt-submit.js +541 -328
- package/dist/hooks/response-ingest-worker.js +372 -122
- package/dist/hooks/session-end.js +443 -240
- 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 +538 -324
- 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 +935 -587
- 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 +280 -234
- package/dist/lib/tmux-routing.js +172 -125
- package/dist/lib/token-spend.js +26 -8
- package/dist/mcp/server.js +1326 -609
- 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 +306 -248
- 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 +1848 -199
- package/dist/runtime/index.js +441 -248
- package/dist/tui/App.js +761 -424
- package/package.json +1 -1
package/dist/bin/scan-tasks.js
CHANGED
|
@@ -320,9 +320,47 @@ var init_provider_table = __esm({
|
|
|
320
320
|
}
|
|
321
321
|
});
|
|
322
322
|
|
|
323
|
+
// src/lib/secure-files.ts
|
|
324
|
+
import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
325
|
+
import { chmod, mkdir } from "fs/promises";
|
|
326
|
+
async function ensurePrivateDir(dirPath) {
|
|
327
|
+
await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
328
|
+
try {
|
|
329
|
+
await chmod(dirPath, PRIVATE_DIR_MODE);
|
|
330
|
+
} catch {
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
function ensurePrivateDirSync(dirPath) {
|
|
334
|
+
mkdirSync2(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
335
|
+
try {
|
|
336
|
+
chmodSync(dirPath, PRIVATE_DIR_MODE);
|
|
337
|
+
} catch {
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
async function enforcePrivateFile(filePath) {
|
|
341
|
+
try {
|
|
342
|
+
await chmod(filePath, PRIVATE_FILE_MODE);
|
|
343
|
+
} catch {
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
function enforcePrivateFileSync(filePath) {
|
|
347
|
+
try {
|
|
348
|
+
if (existsSync2(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
|
|
349
|
+
} catch {
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
|
|
353
|
+
var init_secure_files = __esm({
|
|
354
|
+
"src/lib/secure-files.ts"() {
|
|
355
|
+
"use strict";
|
|
356
|
+
PRIVATE_DIR_MODE = 448;
|
|
357
|
+
PRIVATE_FILE_MODE = 384;
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
|
|
323
361
|
// src/lib/config.ts
|
|
324
|
-
import { readFile, writeFile
|
|
325
|
-
import { readFileSync as readFileSync2, existsSync as
|
|
362
|
+
import { readFile, writeFile } from "fs/promises";
|
|
363
|
+
import { readFileSync as readFileSync2, existsSync as existsSync3, renameSync } from "fs";
|
|
326
364
|
import path2 from "path";
|
|
327
365
|
import os2 from "os";
|
|
328
366
|
function resolveDataDir() {
|
|
@@ -330,7 +368,7 @@ function resolveDataDir() {
|
|
|
330
368
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
331
369
|
const newDir = path2.join(os2.homedir(), ".exe-os");
|
|
332
370
|
const legacyDir = path2.join(os2.homedir(), ".exe-mem");
|
|
333
|
-
if (!
|
|
371
|
+
if (!existsSync3(newDir) && existsSync3(legacyDir)) {
|
|
334
372
|
try {
|
|
335
373
|
renameSync(legacyDir, newDir);
|
|
336
374
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -393,9 +431,9 @@ function normalizeAutoUpdate(raw) {
|
|
|
393
431
|
}
|
|
394
432
|
async function loadConfig() {
|
|
395
433
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
396
|
-
await
|
|
434
|
+
await ensurePrivateDir(dir);
|
|
397
435
|
const configPath = path2.join(dir, "config.json");
|
|
398
|
-
if (!
|
|
436
|
+
if (!existsSync3(configPath)) {
|
|
399
437
|
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
400
438
|
}
|
|
401
439
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -408,6 +446,7 @@ async function loadConfig() {
|
|
|
408
446
|
`);
|
|
409
447
|
try {
|
|
410
448
|
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
449
|
+
await enforcePrivateFile(configPath);
|
|
411
450
|
} catch {
|
|
412
451
|
}
|
|
413
452
|
}
|
|
@@ -427,6 +466,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
|
|
|
427
466
|
var init_config = __esm({
|
|
428
467
|
"src/lib/config.ts"() {
|
|
429
468
|
"use strict";
|
|
469
|
+
init_secure_files();
|
|
430
470
|
EXE_AI_DIR = resolveDataDir();
|
|
431
471
|
DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
|
|
432
472
|
MODELS_DIR = path2.join(EXE_AI_DIR, "models");
|
|
@@ -531,10 +571,10 @@ var init_runtime_table = __esm({
|
|
|
531
571
|
});
|
|
532
572
|
|
|
533
573
|
// src/lib/agent-config.ts
|
|
534
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as
|
|
574
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync4 } from "fs";
|
|
535
575
|
import path3 from "path";
|
|
536
576
|
function loadAgentConfig() {
|
|
537
|
-
if (!
|
|
577
|
+
if (!existsSync4(AGENT_CONFIG_PATH)) return {};
|
|
538
578
|
try {
|
|
539
579
|
return JSON.parse(readFileSync3(AGENT_CONFIG_PATH, "utf-8"));
|
|
540
580
|
} catch {
|
|
@@ -555,6 +595,7 @@ var init_agent_config = __esm({
|
|
|
555
595
|
"use strict";
|
|
556
596
|
init_config();
|
|
557
597
|
init_runtime_table();
|
|
598
|
+
init_secure_files();
|
|
558
599
|
AGENT_CONFIG_PATH = path3.join(EXE_AI_DIR, "agent-config.json");
|
|
559
600
|
DEFAULT_MODELS = {
|
|
560
601
|
claude: "claude-opus-4",
|
|
@@ -573,16 +614,16 @@ __export(intercom_queue_exports, {
|
|
|
573
614
|
queueIntercom: () => queueIntercom,
|
|
574
615
|
readQueue: () => readQueue
|
|
575
616
|
});
|
|
576
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as
|
|
617
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
|
|
577
618
|
import path4 from "path";
|
|
578
619
|
import os3 from "os";
|
|
579
620
|
function ensureDir() {
|
|
580
621
|
const dir = path4.dirname(QUEUE_PATH);
|
|
581
|
-
if (!
|
|
622
|
+
if (!existsSync5(dir)) mkdirSync3(dir, { recursive: true });
|
|
582
623
|
}
|
|
583
624
|
function readQueue() {
|
|
584
625
|
try {
|
|
585
|
-
if (!
|
|
626
|
+
if (!existsSync5(QUEUE_PATH)) return [];
|
|
586
627
|
return JSON.parse(readFileSync4(QUEUE_PATH, "utf8"));
|
|
587
628
|
} catch {
|
|
588
629
|
return [];
|
|
@@ -747,7 +788,7 @@ var init_db_retry = __esm({
|
|
|
747
788
|
|
|
748
789
|
// src/lib/employees.ts
|
|
749
790
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
750
|
-
import { existsSync as
|
|
791
|
+
import { existsSync as existsSync6, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
|
|
751
792
|
import { execSync as execSync3 } from "child_process";
|
|
752
793
|
import path5 from "path";
|
|
753
794
|
import os4 from "os";
|
|
@@ -768,7 +809,7 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
|
768
809
|
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
769
810
|
}
|
|
770
811
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
771
|
-
if (!
|
|
812
|
+
if (!existsSync6(employeesPath)) return [];
|
|
772
813
|
try {
|
|
773
814
|
return JSON.parse(readFileSync5(employeesPath, "utf-8"));
|
|
774
815
|
} catch {
|
|
@@ -1389,13 +1430,50 @@ var init_database_adapter = __esm({
|
|
|
1389
1430
|
}
|
|
1390
1431
|
});
|
|
1391
1432
|
|
|
1433
|
+
// src/lib/daemon-auth.ts
|
|
1434
|
+
import crypto from "crypto";
|
|
1435
|
+
import path7 from "path";
|
|
1436
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
1437
|
+
function normalizeToken(token) {
|
|
1438
|
+
if (!token) return null;
|
|
1439
|
+
const trimmed = token.trim();
|
|
1440
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
1441
|
+
}
|
|
1442
|
+
function readDaemonToken() {
|
|
1443
|
+
try {
|
|
1444
|
+
if (!existsSync7(DAEMON_TOKEN_PATH)) return null;
|
|
1445
|
+
return normalizeToken(readFileSync6(DAEMON_TOKEN_PATH, "utf8"));
|
|
1446
|
+
} catch {
|
|
1447
|
+
return null;
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
function ensureDaemonToken(seed) {
|
|
1451
|
+
const existing = readDaemonToken();
|
|
1452
|
+
if (existing) return existing;
|
|
1453
|
+
const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
|
|
1454
|
+
ensurePrivateDirSync(EXE_AI_DIR);
|
|
1455
|
+
writeFileSync5(DAEMON_TOKEN_PATH, `${token}
|
|
1456
|
+
`, "utf8");
|
|
1457
|
+
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
1458
|
+
return token;
|
|
1459
|
+
}
|
|
1460
|
+
var DAEMON_TOKEN_PATH;
|
|
1461
|
+
var init_daemon_auth = __esm({
|
|
1462
|
+
"src/lib/daemon-auth.ts"() {
|
|
1463
|
+
"use strict";
|
|
1464
|
+
init_config();
|
|
1465
|
+
init_secure_files();
|
|
1466
|
+
DAEMON_TOKEN_PATH = path7.join(EXE_AI_DIR, "exed.token");
|
|
1467
|
+
}
|
|
1468
|
+
});
|
|
1469
|
+
|
|
1392
1470
|
// src/lib/exe-daemon-client.ts
|
|
1393
1471
|
import net from "net";
|
|
1394
1472
|
import os6 from "os";
|
|
1395
1473
|
import { spawn } from "child_process";
|
|
1396
1474
|
import { randomUUID } from "crypto";
|
|
1397
|
-
import { existsSync as
|
|
1398
|
-
import
|
|
1475
|
+
import { existsSync as existsSync8, unlinkSync as unlinkSync2, readFileSync as readFileSync7, openSync, closeSync, statSync } from "fs";
|
|
1476
|
+
import path8 from "path";
|
|
1399
1477
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1400
1478
|
function handleData(chunk) {
|
|
1401
1479
|
_buffer += chunk.toString();
|
|
@@ -1423,9 +1501,9 @@ function handleData(chunk) {
|
|
|
1423
1501
|
}
|
|
1424
1502
|
}
|
|
1425
1503
|
function cleanupStaleFiles() {
|
|
1426
|
-
if (
|
|
1504
|
+
if (existsSync8(PID_PATH)) {
|
|
1427
1505
|
try {
|
|
1428
|
-
const pid = parseInt(
|
|
1506
|
+
const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
|
|
1429
1507
|
if (pid > 0) {
|
|
1430
1508
|
try {
|
|
1431
1509
|
process.kill(pid, 0);
|
|
@@ -1446,11 +1524,11 @@ function cleanupStaleFiles() {
|
|
|
1446
1524
|
}
|
|
1447
1525
|
}
|
|
1448
1526
|
function findPackageRoot() {
|
|
1449
|
-
let dir =
|
|
1450
|
-
const { root } =
|
|
1527
|
+
let dir = path8.dirname(fileURLToPath2(import.meta.url));
|
|
1528
|
+
const { root } = path8.parse(dir);
|
|
1451
1529
|
while (dir !== root) {
|
|
1452
|
-
if (
|
|
1453
|
-
dir =
|
|
1530
|
+
if (existsSync8(path8.join(dir, "package.json"))) return dir;
|
|
1531
|
+
dir = path8.dirname(dir);
|
|
1454
1532
|
}
|
|
1455
1533
|
return null;
|
|
1456
1534
|
}
|
|
@@ -1476,16 +1554,17 @@ function spawnDaemon() {
|
|
|
1476
1554
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
1477
1555
|
return;
|
|
1478
1556
|
}
|
|
1479
|
-
const daemonPath =
|
|
1480
|
-
if (!
|
|
1557
|
+
const daemonPath = path8.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
1558
|
+
if (!existsSync8(daemonPath)) {
|
|
1481
1559
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
1482
1560
|
`);
|
|
1483
1561
|
return;
|
|
1484
1562
|
}
|
|
1485
1563
|
const resolvedPath = daemonPath;
|
|
1564
|
+
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
1486
1565
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
1487
1566
|
`);
|
|
1488
|
-
const logPath =
|
|
1567
|
+
const logPath = path8.join(path8.dirname(SOCKET_PATH), "exed.log");
|
|
1489
1568
|
let stderrFd = "ignore";
|
|
1490
1569
|
try {
|
|
1491
1570
|
stderrFd = openSync(logPath, "a");
|
|
@@ -1503,7 +1582,8 @@ function spawnDaemon() {
|
|
|
1503
1582
|
TMUX_PANE: void 0,
|
|
1504
1583
|
// Prevents resolveExeSession() from scoping to one session
|
|
1505
1584
|
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
1506
|
-
EXE_DAEMON_PID: PID_PATH
|
|
1585
|
+
EXE_DAEMON_PID: PID_PATH,
|
|
1586
|
+
[DAEMON_TOKEN_ENV]: daemonToken
|
|
1507
1587
|
}
|
|
1508
1588
|
});
|
|
1509
1589
|
child.unref();
|
|
@@ -1610,13 +1690,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
1610
1690
|
return;
|
|
1611
1691
|
}
|
|
1612
1692
|
const id = randomUUID();
|
|
1693
|
+
const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
|
|
1613
1694
|
const timer = setTimeout(() => {
|
|
1614
1695
|
_pending.delete(id);
|
|
1615
1696
|
resolve({ error: "Request timeout" });
|
|
1616
1697
|
}, timeoutMs);
|
|
1617
1698
|
_pending.set(id, { resolve, timer });
|
|
1618
1699
|
try {
|
|
1619
|
-
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
1700
|
+
_socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
|
|
1620
1701
|
} catch {
|
|
1621
1702
|
clearTimeout(timer);
|
|
1622
1703
|
_pending.delete(id);
|
|
@@ -1627,17 +1708,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
1627
1708
|
function isClientConnected() {
|
|
1628
1709
|
return _connected;
|
|
1629
1710
|
}
|
|
1630
|
-
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
|
|
1711
|
+
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;
|
|
1631
1712
|
var init_exe_daemon_client = __esm({
|
|
1632
1713
|
"src/lib/exe-daemon-client.ts"() {
|
|
1633
1714
|
"use strict";
|
|
1634
1715
|
init_config();
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1716
|
+
init_daemon_auth();
|
|
1717
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path8.join(EXE_AI_DIR, "exed.sock");
|
|
1718
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path8.join(EXE_AI_DIR, "exed.pid");
|
|
1719
|
+
SPAWN_LOCK_PATH = path8.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
1638
1720
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
1639
1721
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
1640
1722
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
1723
|
+
DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
|
|
1641
1724
|
_socket = null;
|
|
1642
1725
|
_connected = false;
|
|
1643
1726
|
_buffer = "";
|
|
@@ -2216,6 +2299,7 @@ async function ensureSchema() {
|
|
|
2216
2299
|
project TEXT NOT NULL,
|
|
2217
2300
|
summary TEXT NOT NULL,
|
|
2218
2301
|
task_file TEXT,
|
|
2302
|
+
session_scope TEXT,
|
|
2219
2303
|
read INTEGER NOT NULL DEFAULT 0,
|
|
2220
2304
|
created_at TEXT NOT NULL
|
|
2221
2305
|
);
|
|
@@ -2224,7 +2308,7 @@ async function ensureSchema() {
|
|
|
2224
2308
|
ON notifications(read);
|
|
2225
2309
|
|
|
2226
2310
|
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
2227
|
-
ON notifications(agent_id);
|
|
2311
|
+
ON notifications(agent_id, session_scope);
|
|
2228
2312
|
|
|
2229
2313
|
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
2230
2314
|
ON notifications(task_file);
|
|
@@ -2262,6 +2346,7 @@ async function ensureSchema() {
|
|
|
2262
2346
|
target_agent TEXT NOT NULL,
|
|
2263
2347
|
target_project TEXT,
|
|
2264
2348
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
2349
|
+
session_scope TEXT,
|
|
2265
2350
|
content TEXT NOT NULL,
|
|
2266
2351
|
priority TEXT DEFAULT 'normal',
|
|
2267
2352
|
status TEXT DEFAULT 'pending',
|
|
@@ -2275,10 +2360,31 @@ async function ensureSchema() {
|
|
|
2275
2360
|
);
|
|
2276
2361
|
|
|
2277
2362
|
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
2278
|
-
ON messages(target_agent, status);
|
|
2363
|
+
ON messages(target_agent, session_scope, status);
|
|
2279
2364
|
|
|
2280
2365
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
2281
|
-
ON messages(target_agent, from_agent, server_seq);
|
|
2366
|
+
ON messages(target_agent, session_scope, from_agent, server_seq);
|
|
2367
|
+
`);
|
|
2368
|
+
try {
|
|
2369
|
+
await client.execute({
|
|
2370
|
+
sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
|
|
2371
|
+
args: []
|
|
2372
|
+
});
|
|
2373
|
+
} catch {
|
|
2374
|
+
}
|
|
2375
|
+
try {
|
|
2376
|
+
await client.execute({
|
|
2377
|
+
sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
|
|
2378
|
+
args: []
|
|
2379
|
+
});
|
|
2380
|
+
} catch {
|
|
2381
|
+
}
|
|
2382
|
+
await client.executeMultiple(`
|
|
2383
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
|
|
2384
|
+
ON notifications(agent_id, session_scope, read, created_at);
|
|
2385
|
+
|
|
2386
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
|
|
2387
|
+
ON messages(target_agent, session_scope, status, created_at);
|
|
2282
2388
|
`);
|
|
2283
2389
|
try {
|
|
2284
2390
|
await client.execute({
|
|
@@ -2862,6 +2968,13 @@ async function ensureSchema() {
|
|
|
2862
2968
|
} catch {
|
|
2863
2969
|
}
|
|
2864
2970
|
}
|
|
2971
|
+
try {
|
|
2972
|
+
await client.execute({
|
|
2973
|
+
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
2974
|
+
args: []
|
|
2975
|
+
});
|
|
2976
|
+
} catch {
|
|
2977
|
+
}
|
|
2865
2978
|
}
|
|
2866
2979
|
async function disposeDatabase() {
|
|
2867
2980
|
if (_walCheckpointTimer) {
|
|
@@ -2900,18 +3013,21 @@ var init_database = __esm({
|
|
|
2900
3013
|
});
|
|
2901
3014
|
|
|
2902
3015
|
// src/lib/license.ts
|
|
2903
|
-
import { readFileSync as
|
|
3016
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync9, mkdirSync as mkdirSync4 } from "fs";
|
|
2904
3017
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
2905
|
-
import
|
|
3018
|
+
import { createRequire as createRequire2 } from "module";
|
|
3019
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
3020
|
+
import os7 from "os";
|
|
3021
|
+
import path9 from "path";
|
|
2906
3022
|
import { jwtVerify, importSPKI } from "jose";
|
|
2907
3023
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
|
|
2908
3024
|
var init_license = __esm({
|
|
2909
3025
|
"src/lib/license.ts"() {
|
|
2910
3026
|
"use strict";
|
|
2911
3027
|
init_config();
|
|
2912
|
-
LICENSE_PATH =
|
|
2913
|
-
CACHE_PATH =
|
|
2914
|
-
DEVICE_ID_PATH =
|
|
3028
|
+
LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
|
|
3029
|
+
CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
|
|
3030
|
+
DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
|
|
2915
3031
|
PLAN_LIMITS = {
|
|
2916
3032
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
2917
3033
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -2923,12 +3039,12 @@ var init_license = __esm({
|
|
|
2923
3039
|
});
|
|
2924
3040
|
|
|
2925
3041
|
// src/lib/plan-limits.ts
|
|
2926
|
-
import { readFileSync as
|
|
2927
|
-
import
|
|
3042
|
+
import { readFileSync as readFileSync9, existsSync as existsSync10 } from "fs";
|
|
3043
|
+
import path10 from "path";
|
|
2928
3044
|
function getLicenseSync() {
|
|
2929
3045
|
try {
|
|
2930
|
-
if (!
|
|
2931
|
-
const raw = JSON.parse(
|
|
3046
|
+
if (!existsSync10(CACHE_PATH2)) return freeLicense();
|
|
3047
|
+
const raw = JSON.parse(readFileSync9(CACHE_PATH2, "utf8"));
|
|
2932
3048
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
2933
3049
|
const parts = raw.token.split(".");
|
|
2934
3050
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -2966,8 +3082,8 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
2966
3082
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
2967
3083
|
let count = 0;
|
|
2968
3084
|
try {
|
|
2969
|
-
if (
|
|
2970
|
-
const raw =
|
|
3085
|
+
if (existsSync10(filePath)) {
|
|
3086
|
+
const raw = readFileSync9(filePath, "utf8");
|
|
2971
3087
|
const employees = JSON.parse(raw);
|
|
2972
3088
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
2973
3089
|
}
|
|
@@ -2996,29 +3112,30 @@ var init_plan_limits = __esm({
|
|
|
2996
3112
|
this.name = "PlanLimitError";
|
|
2997
3113
|
}
|
|
2998
3114
|
};
|
|
2999
|
-
CACHE_PATH2 =
|
|
3115
|
+
CACHE_PATH2 = path10.join(EXE_AI_DIR, "license-cache.json");
|
|
3000
3116
|
}
|
|
3001
3117
|
});
|
|
3002
3118
|
|
|
3003
3119
|
// src/lib/notifications.ts
|
|
3004
|
-
import
|
|
3005
|
-
import
|
|
3006
|
-
import
|
|
3120
|
+
import crypto2 from "crypto";
|
|
3121
|
+
import path11 from "path";
|
|
3122
|
+
import os8 from "os";
|
|
3007
3123
|
import {
|
|
3008
|
-
readFileSync as
|
|
3124
|
+
readFileSync as readFileSync10,
|
|
3009
3125
|
readdirSync,
|
|
3010
3126
|
unlinkSync as unlinkSync3,
|
|
3011
|
-
existsSync as
|
|
3127
|
+
existsSync as existsSync11,
|
|
3012
3128
|
rmdirSync
|
|
3013
3129
|
} from "fs";
|
|
3014
3130
|
async function writeNotification(notification) {
|
|
3015
3131
|
try {
|
|
3016
3132
|
const client = getClient();
|
|
3017
|
-
const id =
|
|
3133
|
+
const id = crypto2.randomUUID();
|
|
3018
3134
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3135
|
+
const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
|
|
3019
3136
|
await client.execute({
|
|
3020
|
-
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
|
|
3021
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
3137
|
+
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
3138
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
3022
3139
|
args: [
|
|
3023
3140
|
id,
|
|
3024
3141
|
notification.agentId,
|
|
@@ -3027,6 +3144,7 @@ async function writeNotification(notification) {
|
|
|
3027
3144
|
notification.project,
|
|
3028
3145
|
notification.summary,
|
|
3029
3146
|
notification.taskFile ?? null,
|
|
3147
|
+
sessionScope,
|
|
3030
3148
|
now
|
|
3031
3149
|
]
|
|
3032
3150
|
});
|
|
@@ -3035,12 +3153,14 @@ async function writeNotification(notification) {
|
|
|
3035
3153
|
`);
|
|
3036
3154
|
}
|
|
3037
3155
|
}
|
|
3038
|
-
async function markAsReadByTaskFile(taskFile) {
|
|
3156
|
+
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
3039
3157
|
try {
|
|
3040
3158
|
const client = getClient();
|
|
3159
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
3041
3160
|
await client.execute({
|
|
3042
|
-
sql:
|
|
3043
|
-
|
|
3161
|
+
sql: `UPDATE notifications SET read = 1
|
|
3162
|
+
WHERE task_file = ? AND read = 0${scope.sql}`,
|
|
3163
|
+
args: [taskFile, ...scope.args]
|
|
3044
3164
|
});
|
|
3045
3165
|
} catch {
|
|
3046
3166
|
}
|
|
@@ -3049,11 +3169,12 @@ var init_notifications = __esm({
|
|
|
3049
3169
|
"src/lib/notifications.ts"() {
|
|
3050
3170
|
"use strict";
|
|
3051
3171
|
init_database();
|
|
3172
|
+
init_task_scope();
|
|
3052
3173
|
}
|
|
3053
3174
|
});
|
|
3054
3175
|
|
|
3055
3176
|
// src/lib/session-kill-telemetry.ts
|
|
3056
|
-
import
|
|
3177
|
+
import crypto3 from "crypto";
|
|
3057
3178
|
async function recordSessionKill(input) {
|
|
3058
3179
|
try {
|
|
3059
3180
|
const client = getClient();
|
|
@@ -3063,7 +3184,7 @@ async function recordSessionKill(input) {
|
|
|
3063
3184
|
ticks_idle, estimated_tokens_saved)
|
|
3064
3185
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
3065
3186
|
args: [
|
|
3066
|
-
|
|
3187
|
+
crypto3.randomUUID(),
|
|
3067
3188
|
input.sessionName,
|
|
3068
3189
|
input.agentId,
|
|
3069
3190
|
(/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -3142,12 +3263,12 @@ var init_state_bus = __esm({
|
|
|
3142
3263
|
});
|
|
3143
3264
|
|
|
3144
3265
|
// src/lib/tasks-crud.ts
|
|
3145
|
-
import
|
|
3146
|
-
import
|
|
3147
|
-
import
|
|
3266
|
+
import crypto4 from "crypto";
|
|
3267
|
+
import path12 from "path";
|
|
3268
|
+
import os9 from "os";
|
|
3148
3269
|
import { execSync as execSync4 } from "child_process";
|
|
3149
3270
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
3150
|
-
import { existsSync as
|
|
3271
|
+
import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
|
|
3151
3272
|
async function writeCheckpoint(input) {
|
|
3152
3273
|
const client = getClient();
|
|
3153
3274
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -3263,7 +3384,7 @@ async function resolveTask(client, identifier, scopeSession) {
|
|
|
3263
3384
|
}
|
|
3264
3385
|
async function createTaskCore(input) {
|
|
3265
3386
|
const client = getClient();
|
|
3266
|
-
const id =
|
|
3387
|
+
const id = crypto4.randomUUID();
|
|
3267
3388
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3268
3389
|
const slug = slugify(input.title);
|
|
3269
3390
|
let earlySessionScope = null;
|
|
@@ -3322,8 +3443,8 @@ ${laneWarning}` : laneWarning;
|
|
|
3322
3443
|
}
|
|
3323
3444
|
if (input.baseDir) {
|
|
3324
3445
|
try {
|
|
3325
|
-
await mkdir3(
|
|
3326
|
-
await mkdir3(
|
|
3446
|
+
await mkdir3(path12.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
3447
|
+
await mkdir3(path12.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
3327
3448
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
3328
3449
|
await ensureGitignoreExe(input.baseDir);
|
|
3329
3450
|
} catch {
|
|
@@ -3359,13 +3480,19 @@ ${laneWarning}` : laneWarning;
|
|
|
3359
3480
|
});
|
|
3360
3481
|
if (input.baseDir) {
|
|
3361
3482
|
try {
|
|
3362
|
-
const EXE_OS_DIR =
|
|
3363
|
-
const mdPath =
|
|
3364
|
-
const mdDir =
|
|
3365
|
-
if (!
|
|
3483
|
+
const EXE_OS_DIR = path12.join(os9.homedir(), ".exe-os");
|
|
3484
|
+
const mdPath = path12.join(EXE_OS_DIR, taskFile);
|
|
3485
|
+
const mdDir = path12.dirname(mdPath);
|
|
3486
|
+
if (!existsSync12(mdDir)) await mkdir3(mdDir, { recursive: true });
|
|
3366
3487
|
const reviewer = input.reviewer ?? input.assignedBy;
|
|
3367
3488
|
const mdContent = `# ${input.title}
|
|
3368
3489
|
|
|
3490
|
+
## MANDATORY: When done
|
|
3491
|
+
|
|
3492
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
3493
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
3494
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
3495
|
+
|
|
3369
3496
|
**ID:** ${id}
|
|
3370
3497
|
**Status:** ${initialStatus}
|
|
3371
3498
|
**Priority:** ${input.priority}
|
|
@@ -3379,12 +3506,6 @@ ${laneWarning}` : laneWarning;
|
|
|
3379
3506
|
## Context
|
|
3380
3507
|
|
|
3381
3508
|
${input.context}
|
|
3382
|
-
|
|
3383
|
-
## MANDATORY: When done
|
|
3384
|
-
|
|
3385
|
-
You MUST call update_task with status "done" and a result summary when finished.
|
|
3386
|
-
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
3387
|
-
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
3388
3509
|
`;
|
|
3389
3510
|
await writeFile3(mdPath, mdContent, "utf-8");
|
|
3390
3511
|
} catch (err) {
|
|
@@ -3633,7 +3754,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
3633
3754
|
await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
|
|
3634
3755
|
} catch {
|
|
3635
3756
|
}
|
|
3636
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
3757
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
3637
3758
|
try {
|
|
3638
3759
|
const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
3639
3760
|
clearQueueForAgent2(String(row.assigned_to));
|
|
@@ -3662,9 +3783,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
3662
3783
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
3663
3784
|
}
|
|
3664
3785
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
3665
|
-
const archPath =
|
|
3786
|
+
const archPath = path12.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
3666
3787
|
try {
|
|
3667
|
-
if (
|
|
3788
|
+
if (existsSync12(archPath)) return;
|
|
3668
3789
|
const template = [
|
|
3669
3790
|
`# ${projectName} \u2014 System Architecture`,
|
|
3670
3791
|
"",
|
|
@@ -3697,10 +3818,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
3697
3818
|
}
|
|
3698
3819
|
}
|
|
3699
3820
|
async function ensureGitignoreExe(baseDir) {
|
|
3700
|
-
const gitignorePath =
|
|
3821
|
+
const gitignorePath = path12.join(baseDir, ".gitignore");
|
|
3701
3822
|
try {
|
|
3702
|
-
if (
|
|
3703
|
-
const content =
|
|
3823
|
+
if (existsSync12(gitignorePath)) {
|
|
3824
|
+
const content = readFileSync11(gitignorePath, "utf-8");
|
|
3704
3825
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
3705
3826
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
3706
3827
|
} else {
|
|
@@ -3731,58 +3852,42 @@ var init_tasks_crud = __esm({
|
|
|
3731
3852
|
});
|
|
3732
3853
|
|
|
3733
3854
|
// src/lib/tasks-review.ts
|
|
3734
|
-
import
|
|
3735
|
-
import { existsSync as
|
|
3855
|
+
import path13 from "path";
|
|
3856
|
+
import { existsSync as existsSync13, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
|
|
3736
3857
|
async function countPendingReviews(sessionScope) {
|
|
3737
3858
|
const client = getClient();
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
args: [sessionScope]
|
|
3742
|
-
});
|
|
3743
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
3744
|
-
}
|
|
3859
|
+
const scope = strictSessionScopeFilter(
|
|
3860
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
3861
|
+
);
|
|
3745
3862
|
const result = await client.execute({
|
|
3746
|
-
sql:
|
|
3747
|
-
|
|
3863
|
+
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3864
|
+
WHERE status = 'needs_review'${scope.sql}`,
|
|
3865
|
+
args: [...scope.args]
|
|
3748
3866
|
});
|
|
3749
3867
|
return Number(result.rows[0]?.cnt) || 0;
|
|
3750
3868
|
}
|
|
3751
3869
|
async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
3752
3870
|
const client = getClient();
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
WHERE status = 'needs_review' AND updated_at > ?
|
|
3757
|
-
AND session_scope = ?`,
|
|
3758
|
-
args: [sinceIso, sessionScope]
|
|
3759
|
-
});
|
|
3760
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
3761
|
-
}
|
|
3871
|
+
const scope = strictSessionScopeFilter(
|
|
3872
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
3873
|
+
);
|
|
3762
3874
|
const result = await client.execute({
|
|
3763
3875
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3764
|
-
WHERE status = 'needs_review' AND updated_at >
|
|
3765
|
-
args: [sinceIso]
|
|
3876
|
+
WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
|
|
3877
|
+
args: [sinceIso, ...scope.args]
|
|
3766
3878
|
});
|
|
3767
3879
|
return Number(result.rows[0]?.cnt) || 0;
|
|
3768
3880
|
}
|
|
3769
3881
|
async function listPendingReviews(limit, sessionScope) {
|
|
3770
3882
|
const client = getClient();
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
WHERE status = 'needs_review'
|
|
3775
|
-
AND session_scope = ?
|
|
3776
|
-
ORDER BY updated_at ASC LIMIT ?`,
|
|
3777
|
-
args: [sessionScope, limit]
|
|
3778
|
-
});
|
|
3779
|
-
return result2.rows;
|
|
3780
|
-
}
|
|
3883
|
+
const scope = strictSessionScopeFilter(
|
|
3884
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
3885
|
+
);
|
|
3781
3886
|
const result = await client.execute({
|
|
3782
3887
|
sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
|
|
3783
|
-
WHERE status = 'needs_review'
|
|
3888
|
+
WHERE status = 'needs_review'${scope.sql}
|
|
3784
3889
|
ORDER BY updated_at ASC LIMIT ?`,
|
|
3785
|
-
args: [limit]
|
|
3890
|
+
args: [...scope.args, limit]
|
|
3786
3891
|
});
|
|
3787
3892
|
return result.rows;
|
|
3788
3893
|
}
|
|
@@ -3794,7 +3899,7 @@ async function cleanupOrphanedReviews() {
|
|
|
3794
3899
|
WHERE status IN ('open', 'needs_review', 'in_progress')
|
|
3795
3900
|
AND assigned_by = 'system'
|
|
3796
3901
|
AND title LIKE 'Review:%'
|
|
3797
|
-
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
|
|
3902
|
+
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
|
|
3798
3903
|
args: [now]
|
|
3799
3904
|
});
|
|
3800
3905
|
const r1b = await client.execute({
|
|
@@ -3913,11 +4018,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
3913
4018
|
);
|
|
3914
4019
|
}
|
|
3915
4020
|
try {
|
|
3916
|
-
const cacheDir =
|
|
3917
|
-
if (
|
|
4021
|
+
const cacheDir = path13.join(EXE_AI_DIR, "session-cache");
|
|
4022
|
+
if (existsSync13(cacheDir)) {
|
|
3918
4023
|
for (const f of readdirSync2(cacheDir)) {
|
|
3919
4024
|
if (f.startsWith("review-notified-")) {
|
|
3920
|
-
unlinkSync4(
|
|
4025
|
+
unlinkSync4(path13.join(cacheDir, f));
|
|
3921
4026
|
}
|
|
3922
4027
|
}
|
|
3923
4028
|
}
|
|
@@ -3934,11 +4039,12 @@ var init_tasks_review = __esm({
|
|
|
3934
4039
|
init_tmux_routing();
|
|
3935
4040
|
init_session_key();
|
|
3936
4041
|
init_state_bus();
|
|
4042
|
+
init_task_scope();
|
|
3937
4043
|
}
|
|
3938
4044
|
});
|
|
3939
4045
|
|
|
3940
4046
|
// src/lib/tasks-chain.ts
|
|
3941
|
-
import
|
|
4047
|
+
import path14 from "path";
|
|
3942
4048
|
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
3943
4049
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
3944
4050
|
const client = getClient();
|
|
@@ -3955,7 +4061,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
3955
4061
|
});
|
|
3956
4062
|
for (const ur of unblockedRows.rows) {
|
|
3957
4063
|
try {
|
|
3958
|
-
const ubFile =
|
|
4064
|
+
const ubFile = path14.join(baseDir, String(ur.task_file));
|
|
3959
4065
|
let ubContent = await readFile3(ubFile, "utf-8");
|
|
3960
4066
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
3961
4067
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -3990,7 +4096,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
|
|
|
3990
4096
|
const scScope = sessionScopeFilter();
|
|
3991
4097
|
const remaining = await client.execute({
|
|
3992
4098
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3993
|
-
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
|
|
4099
|
+
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
|
|
3994
4100
|
args: [parentTaskId, ...scScope.args]
|
|
3995
4101
|
});
|
|
3996
4102
|
const cnt = Number(remaining.rows[0]?.cnt ?? 1);
|
|
@@ -4029,7 +4135,7 @@ __export(project_name_exports, {
|
|
|
4029
4135
|
getProjectName: () => getProjectName
|
|
4030
4136
|
});
|
|
4031
4137
|
import { execSync as execSync5 } from "child_process";
|
|
4032
|
-
import
|
|
4138
|
+
import path15 from "path";
|
|
4033
4139
|
function getProjectName(cwd) {
|
|
4034
4140
|
const dir = cwd ?? process.cwd();
|
|
4035
4141
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
@@ -4042,7 +4148,7 @@ function getProjectName(cwd) {
|
|
|
4042
4148
|
timeout: 2e3,
|
|
4043
4149
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4044
4150
|
}).trim();
|
|
4045
|
-
repoRoot =
|
|
4151
|
+
repoRoot = path15.dirname(gitCommonDir);
|
|
4046
4152
|
} catch {
|
|
4047
4153
|
repoRoot = execSync5("git rev-parse --show-toplevel", {
|
|
4048
4154
|
cwd: dir,
|
|
@@ -4051,11 +4157,11 @@ function getProjectName(cwd) {
|
|
|
4051
4157
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4052
4158
|
}).trim();
|
|
4053
4159
|
}
|
|
4054
|
-
_cached2 =
|
|
4160
|
+
_cached2 = path15.basename(repoRoot);
|
|
4055
4161
|
_cachedCwd = dir;
|
|
4056
4162
|
return _cached2;
|
|
4057
4163
|
} catch {
|
|
4058
|
-
_cached2 =
|
|
4164
|
+
_cached2 = path15.basename(dir);
|
|
4059
4165
|
_cachedCwd = dir;
|
|
4060
4166
|
return _cached2;
|
|
4061
4167
|
}
|
|
@@ -4202,10 +4308,10 @@ var init_tasks_notify = __esm({
|
|
|
4202
4308
|
});
|
|
4203
4309
|
|
|
4204
4310
|
// src/lib/behaviors.ts
|
|
4205
|
-
import
|
|
4311
|
+
import crypto5 from "crypto";
|
|
4206
4312
|
async function storeBehavior(opts) {
|
|
4207
4313
|
const client = getClient();
|
|
4208
|
-
const id =
|
|
4314
|
+
const id = crypto5.randomUUID();
|
|
4209
4315
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4210
4316
|
await client.execute({
|
|
4211
4317
|
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
|
|
@@ -4234,7 +4340,7 @@ __export(skill_learning_exports, {
|
|
|
4234
4340
|
storeTrajectory: () => storeTrajectory,
|
|
4235
4341
|
sweepTrajectories: () => sweepTrajectories
|
|
4236
4342
|
});
|
|
4237
|
-
import
|
|
4343
|
+
import crypto6 from "crypto";
|
|
4238
4344
|
async function extractTrajectory(taskId, agentId) {
|
|
4239
4345
|
const client = getClient();
|
|
4240
4346
|
const result = await client.execute({
|
|
@@ -4263,11 +4369,11 @@ async function extractTrajectory(taskId, agentId) {
|
|
|
4263
4369
|
return signature;
|
|
4264
4370
|
}
|
|
4265
4371
|
function hashSignature(signature) {
|
|
4266
|
-
return
|
|
4372
|
+
return crypto6.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
|
|
4267
4373
|
}
|
|
4268
4374
|
async function storeTrajectory(opts) {
|
|
4269
4375
|
const client = getClient();
|
|
4270
|
-
const id =
|
|
4376
|
+
const id = crypto6.randomUUID();
|
|
4271
4377
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4272
4378
|
const signatureHash = hashSignature(opts.signature);
|
|
4273
4379
|
await client.execute({
|
|
@@ -4532,8 +4638,8 @@ __export(tasks_exports, {
|
|
|
4532
4638
|
updateTaskStatus: () => updateTaskStatus,
|
|
4533
4639
|
writeCheckpoint: () => writeCheckpoint
|
|
4534
4640
|
});
|
|
4535
|
-
import
|
|
4536
|
-
import { writeFileSync as
|
|
4641
|
+
import path16 from "path";
|
|
4642
|
+
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
|
|
4537
4643
|
async function createTask(input) {
|
|
4538
4644
|
const result = await createTaskCore(input);
|
|
4539
4645
|
if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -4552,12 +4658,12 @@ async function updateTask(input) {
|
|
|
4552
4658
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
4553
4659
|
try {
|
|
4554
4660
|
const agent = String(row.assigned_to);
|
|
4555
|
-
const cacheDir =
|
|
4556
|
-
const cachePath =
|
|
4661
|
+
const cacheDir = path16.join(EXE_AI_DIR, "session-cache");
|
|
4662
|
+
const cachePath = path16.join(cacheDir, `current-task-${agent}.json`);
|
|
4557
4663
|
if (input.status === "in_progress") {
|
|
4558
4664
|
mkdirSync5(cacheDir, { recursive: true });
|
|
4559
|
-
|
|
4560
|
-
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
4665
|
+
writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
4666
|
+
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
|
|
4561
4667
|
try {
|
|
4562
4668
|
unlinkSync5(cachePath);
|
|
4563
4669
|
} catch {
|
|
@@ -4565,10 +4671,10 @@ async function updateTask(input) {
|
|
|
4565
4671
|
}
|
|
4566
4672
|
} catch {
|
|
4567
4673
|
}
|
|
4568
|
-
if (input.status === "done") {
|
|
4674
|
+
if (input.status === "done" || input.status === "closed") {
|
|
4569
4675
|
await cleanupReviewFile(row, taskFile, input.baseDir);
|
|
4570
4676
|
}
|
|
4571
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
4677
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
4572
4678
|
try {
|
|
4573
4679
|
const client = getClient();
|
|
4574
4680
|
const taskTitle = String(row.title);
|
|
@@ -4584,7 +4690,7 @@ async function updateTask(input) {
|
|
|
4584
4690
|
if (!isCoordinatorName(assignedAgent)) {
|
|
4585
4691
|
try {
|
|
4586
4692
|
const draftClient = getClient();
|
|
4587
|
-
if (input.status === "done") {
|
|
4693
|
+
if (input.status === "done" || input.status === "closed") {
|
|
4588
4694
|
await draftClient.execute({
|
|
4589
4695
|
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
4590
4696
|
args: [assignedAgent]
|
|
@@ -4601,7 +4707,7 @@ async function updateTask(input) {
|
|
|
4601
4707
|
try {
|
|
4602
4708
|
const client = getClient();
|
|
4603
4709
|
const cascaded = await client.execute({
|
|
4604
|
-
sql: `UPDATE tasks SET status = '
|
|
4710
|
+
sql: `UPDATE tasks SET status = 'closed', updated_at = ?
|
|
4605
4711
|
WHERE parent_task_id = ? AND status = 'needs_review'`,
|
|
4606
4712
|
args: [now, taskId]
|
|
4607
4713
|
});
|
|
@@ -4614,14 +4720,14 @@ async function updateTask(input) {
|
|
|
4614
4720
|
} catch {
|
|
4615
4721
|
}
|
|
4616
4722
|
}
|
|
4617
|
-
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
4723
|
+
const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
|
|
4618
4724
|
if (isTerminal) {
|
|
4619
4725
|
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
4620
4726
|
if (!isCoordinator) {
|
|
4621
4727
|
notifyTaskDone();
|
|
4622
4728
|
}
|
|
4623
4729
|
await markTaskNotificationsRead(taskFile);
|
|
4624
|
-
if (input.status === "done") {
|
|
4730
|
+
if (input.status === "done" || input.status === "closed") {
|
|
4625
4731
|
try {
|
|
4626
4732
|
await cascadeUnblock(taskId, input.baseDir, now);
|
|
4627
4733
|
} catch {
|
|
@@ -4641,7 +4747,7 @@ async function updateTask(input) {
|
|
|
4641
4747
|
}
|
|
4642
4748
|
}
|
|
4643
4749
|
}
|
|
4644
|
-
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
4750
|
+
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
4645
4751
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
4646
4752
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
4647
4753
|
taskId,
|
|
@@ -5013,6 +5119,7 @@ __export(tmux_routing_exports, {
|
|
|
5013
5119
|
isEmployeeAlive: () => isEmployeeAlive,
|
|
5014
5120
|
isExeSession: () => isExeSession,
|
|
5015
5121
|
isSessionBusy: () => isSessionBusy,
|
|
5122
|
+
notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
|
|
5016
5123
|
notifyParentExe: () => notifyParentExe,
|
|
5017
5124
|
parseParentExe: () => parseParentExe,
|
|
5018
5125
|
registerParentExe: () => registerParentExe,
|
|
@@ -5023,13 +5130,13 @@ __export(tmux_routing_exports, {
|
|
|
5023
5130
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
5024
5131
|
});
|
|
5025
5132
|
import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
|
|
5026
|
-
import { readFileSync as
|
|
5027
|
-
import
|
|
5028
|
-
import
|
|
5133
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync6, existsSync as existsSync14, appendFileSync, readdirSync as readdirSync3 } from "fs";
|
|
5134
|
+
import path17 from "path";
|
|
5135
|
+
import os10 from "os";
|
|
5029
5136
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
5030
5137
|
import { unlinkSync as unlinkSync6 } from "fs";
|
|
5031
5138
|
function spawnLockPath(sessionName) {
|
|
5032
|
-
return
|
|
5139
|
+
return path17.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
5033
5140
|
}
|
|
5034
5141
|
function isProcessAlive(pid) {
|
|
5035
5142
|
try {
|
|
@@ -5040,13 +5147,13 @@ function isProcessAlive(pid) {
|
|
|
5040
5147
|
}
|
|
5041
5148
|
}
|
|
5042
5149
|
function acquireSpawnLock2(sessionName) {
|
|
5043
|
-
if (!
|
|
5150
|
+
if (!existsSync14(SPAWN_LOCK_DIR)) {
|
|
5044
5151
|
mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
|
|
5045
5152
|
}
|
|
5046
5153
|
const lockFile = spawnLockPath(sessionName);
|
|
5047
|
-
if (
|
|
5154
|
+
if (existsSync14(lockFile)) {
|
|
5048
5155
|
try {
|
|
5049
|
-
const lock = JSON.parse(
|
|
5156
|
+
const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
|
|
5050
5157
|
const age = Date.now() - lock.timestamp;
|
|
5051
5158
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
5052
5159
|
return false;
|
|
@@ -5054,7 +5161,7 @@ function acquireSpawnLock2(sessionName) {
|
|
|
5054
5161
|
} catch {
|
|
5055
5162
|
}
|
|
5056
5163
|
}
|
|
5057
|
-
|
|
5164
|
+
writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
5058
5165
|
return true;
|
|
5059
5166
|
}
|
|
5060
5167
|
function releaseSpawnLock2(sessionName) {
|
|
@@ -5066,13 +5173,13 @@ function releaseSpawnLock2(sessionName) {
|
|
|
5066
5173
|
function resolveBehaviorsExporterScript() {
|
|
5067
5174
|
try {
|
|
5068
5175
|
const thisFile = fileURLToPath3(import.meta.url);
|
|
5069
|
-
const scriptPath =
|
|
5070
|
-
|
|
5176
|
+
const scriptPath = path17.join(
|
|
5177
|
+
path17.dirname(thisFile),
|
|
5071
5178
|
"..",
|
|
5072
5179
|
"bin",
|
|
5073
5180
|
"exe-export-behaviors.js"
|
|
5074
5181
|
);
|
|
5075
|
-
return
|
|
5182
|
+
return existsSync14(scriptPath) ? scriptPath : null;
|
|
5076
5183
|
} catch {
|
|
5077
5184
|
return null;
|
|
5078
5185
|
}
|
|
@@ -5138,12 +5245,12 @@ function extractRootExe(name) {
|
|
|
5138
5245
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
5139
5246
|
}
|
|
5140
5247
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
5141
|
-
if (!
|
|
5248
|
+
if (!existsSync14(SESSION_CACHE)) {
|
|
5142
5249
|
mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
5143
5250
|
}
|
|
5144
5251
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
5145
|
-
const filePath =
|
|
5146
|
-
|
|
5252
|
+
const filePath = path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
5253
|
+
writeFileSync8(filePath, JSON.stringify({
|
|
5147
5254
|
parentExe: rootExe,
|
|
5148
5255
|
dispatchedBy: dispatchedBy || rootExe,
|
|
5149
5256
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -5151,7 +5258,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
5151
5258
|
}
|
|
5152
5259
|
function getParentExe(sessionKey) {
|
|
5153
5260
|
try {
|
|
5154
|
-
const data = JSON.parse(
|
|
5261
|
+
const data = JSON.parse(readFileSync12(path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
5155
5262
|
return data.parentExe || null;
|
|
5156
5263
|
} catch {
|
|
5157
5264
|
return null;
|
|
@@ -5159,8 +5266,8 @@ function getParentExe(sessionKey) {
|
|
|
5159
5266
|
}
|
|
5160
5267
|
function getDispatchedBy(sessionKey) {
|
|
5161
5268
|
try {
|
|
5162
|
-
const data = JSON.parse(
|
|
5163
|
-
|
|
5269
|
+
const data = JSON.parse(readFileSync12(
|
|
5270
|
+
path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
5164
5271
|
"utf8"
|
|
5165
5272
|
));
|
|
5166
5273
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -5230,8 +5337,8 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
5230
5337
|
}
|
|
5231
5338
|
function readDebounceState() {
|
|
5232
5339
|
try {
|
|
5233
|
-
if (!
|
|
5234
|
-
const raw = JSON.parse(
|
|
5340
|
+
if (!existsSync14(DEBOUNCE_FILE)) return {};
|
|
5341
|
+
const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
|
|
5235
5342
|
const state = {};
|
|
5236
5343
|
for (const [key, val] of Object.entries(raw)) {
|
|
5237
5344
|
if (typeof val === "number") {
|
|
@@ -5247,8 +5354,8 @@ function readDebounceState() {
|
|
|
5247
5354
|
}
|
|
5248
5355
|
function writeDebounceState(state) {
|
|
5249
5356
|
try {
|
|
5250
|
-
if (!
|
|
5251
|
-
|
|
5357
|
+
if (!existsSync14(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
5358
|
+
writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
|
|
5252
5359
|
} catch {
|
|
5253
5360
|
}
|
|
5254
5361
|
}
|
|
@@ -5346,8 +5453,8 @@ function sendIntercom(targetSession) {
|
|
|
5346
5453
|
try {
|
|
5347
5454
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
5348
5455
|
const agent = baseAgentName(rawAgent);
|
|
5349
|
-
const markerPath =
|
|
5350
|
-
if (
|
|
5456
|
+
const markerPath = path17.join(SESSION_CACHE, `current-task-${agent}.json`);
|
|
5457
|
+
if (existsSync14(markerPath)) {
|
|
5351
5458
|
logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
|
|
5352
5459
|
return "debounced";
|
|
5353
5460
|
}
|
|
@@ -5356,8 +5463,8 @@ function sendIntercom(targetSession) {
|
|
|
5356
5463
|
try {
|
|
5357
5464
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
5358
5465
|
const agent = baseAgentName(rawAgent);
|
|
5359
|
-
const taskDir =
|
|
5360
|
-
if (
|
|
5466
|
+
const taskDir = path17.join(process.cwd(), "exe", agent);
|
|
5467
|
+
if (existsSync14(taskDir)) {
|
|
5361
5468
|
const files = readdirSync3(taskDir).filter(
|
|
5362
5469
|
(f) => f.endsWith(".md") && f !== "DONE.txt"
|
|
5363
5470
|
);
|
|
@@ -5417,6 +5524,21 @@ function notifyParentExe(sessionKey) {
|
|
|
5417
5524
|
}
|
|
5418
5525
|
return true;
|
|
5419
5526
|
}
|
|
5527
|
+
function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
|
|
5528
|
+
const transport = getTransport();
|
|
5529
|
+
try {
|
|
5530
|
+
const sessions = transport.listSessions();
|
|
5531
|
+
if (!sessions.includes(coordinatorSession)) return false;
|
|
5532
|
+
execSync6(
|
|
5533
|
+
`tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
|
|
5534
|
+
{ timeout: 3e3 }
|
|
5535
|
+
);
|
|
5536
|
+
logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
|
|
5537
|
+
return true;
|
|
5538
|
+
} catch {
|
|
5539
|
+
return false;
|
|
5540
|
+
}
|
|
5541
|
+
}
|
|
5420
5542
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
5421
5543
|
if (isCoordinatorName(employeeName)) {
|
|
5422
5544
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
@@ -5490,26 +5612,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5490
5612
|
const transport = getTransport();
|
|
5491
5613
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
5492
5614
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
5493
|
-
const logDir =
|
|
5494
|
-
const logFile =
|
|
5495
|
-
if (!
|
|
5615
|
+
const logDir = path17.join(os10.homedir(), ".exe-os", "session-logs");
|
|
5616
|
+
const logFile = path17.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
5617
|
+
if (!existsSync14(logDir)) {
|
|
5496
5618
|
mkdirSync6(logDir, { recursive: true });
|
|
5497
5619
|
}
|
|
5498
5620
|
transport.kill(sessionName);
|
|
5499
5621
|
let cleanupSuffix = "";
|
|
5500
5622
|
try {
|
|
5501
5623
|
const thisFile = fileURLToPath3(import.meta.url);
|
|
5502
|
-
const cleanupScript =
|
|
5503
|
-
if (
|
|
5624
|
+
const cleanupScript = path17.join(path17.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
5625
|
+
if (existsSync14(cleanupScript)) {
|
|
5504
5626
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
5505
5627
|
}
|
|
5506
5628
|
} catch {
|
|
5507
5629
|
}
|
|
5508
5630
|
try {
|
|
5509
|
-
const claudeJsonPath =
|
|
5631
|
+
const claudeJsonPath = path17.join(os10.homedir(), ".claude.json");
|
|
5510
5632
|
let claudeJson = {};
|
|
5511
5633
|
try {
|
|
5512
|
-
claudeJson = JSON.parse(
|
|
5634
|
+
claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
|
|
5513
5635
|
} catch {
|
|
5514
5636
|
}
|
|
5515
5637
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -5517,17 +5639,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5517
5639
|
const trustDir = opts?.cwd ?? projectDir;
|
|
5518
5640
|
if (!projects[trustDir]) projects[trustDir] = {};
|
|
5519
5641
|
projects[trustDir].hasTrustDialogAccepted = true;
|
|
5520
|
-
|
|
5642
|
+
writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
5521
5643
|
} catch {
|
|
5522
5644
|
}
|
|
5523
5645
|
try {
|
|
5524
|
-
const settingsDir =
|
|
5646
|
+
const settingsDir = path17.join(os10.homedir(), ".claude", "projects");
|
|
5525
5647
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
5526
|
-
const projSettingsDir =
|
|
5527
|
-
const settingsPath =
|
|
5648
|
+
const projSettingsDir = path17.join(settingsDir, normalizedKey);
|
|
5649
|
+
const settingsPath = path17.join(projSettingsDir, "settings.json");
|
|
5528
5650
|
let settings = {};
|
|
5529
5651
|
try {
|
|
5530
|
-
settings = JSON.parse(
|
|
5652
|
+
settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
|
|
5531
5653
|
} catch {
|
|
5532
5654
|
}
|
|
5533
5655
|
const perms = settings.permissions ?? {};
|
|
@@ -5556,7 +5678,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5556
5678
|
perms.allow = allow;
|
|
5557
5679
|
settings.permissions = perms;
|
|
5558
5680
|
mkdirSync6(projSettingsDir, { recursive: true });
|
|
5559
|
-
|
|
5681
|
+
writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
5560
5682
|
}
|
|
5561
5683
|
} catch {
|
|
5562
5684
|
}
|
|
@@ -5571,8 +5693,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5571
5693
|
let behaviorsFlag = "";
|
|
5572
5694
|
let legacyFallbackWarned = false;
|
|
5573
5695
|
if (!useExeAgent && !useBinSymlink) {
|
|
5574
|
-
const identityPath =
|
|
5575
|
-
|
|
5696
|
+
const identityPath = path17.join(
|
|
5697
|
+
os10.homedir(),
|
|
5576
5698
|
".exe-os",
|
|
5577
5699
|
"identity",
|
|
5578
5700
|
`${employeeName}.md`
|
|
@@ -5581,13 +5703,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5581
5703
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
5582
5704
|
if (hasAgentFlag) {
|
|
5583
5705
|
identityFlag = ` --agent ${employeeName}`;
|
|
5584
|
-
} else if (
|
|
5706
|
+
} else if (existsSync14(identityPath)) {
|
|
5585
5707
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
5586
5708
|
legacyFallbackWarned = true;
|
|
5587
5709
|
}
|
|
5588
5710
|
const behaviorsFile = exportBehaviorsSync(
|
|
5589
5711
|
employeeName,
|
|
5590
|
-
|
|
5712
|
+
path17.basename(spawnCwd),
|
|
5591
5713
|
sessionName
|
|
5592
5714
|
);
|
|
5593
5715
|
if (behaviorsFile) {
|
|
@@ -5602,16 +5724,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5602
5724
|
}
|
|
5603
5725
|
let sessionContextFlag = "";
|
|
5604
5726
|
try {
|
|
5605
|
-
const ctxDir =
|
|
5727
|
+
const ctxDir = path17.join(os10.homedir(), ".exe-os", "session-cache");
|
|
5606
5728
|
mkdirSync6(ctxDir, { recursive: true });
|
|
5607
|
-
const ctxFile =
|
|
5729
|
+
const ctxFile = path17.join(ctxDir, `session-context-${sessionName}.md`);
|
|
5608
5730
|
const ctxContent = [
|
|
5609
5731
|
`## Session Context`,
|
|
5610
5732
|
`You are running in tmux session: ${sessionName}.`,
|
|
5611
5733
|
`Your parent coordinator session is ${exeSession}.`,
|
|
5612
5734
|
`Your employees (if any) use the -${exeSession} suffix.`
|
|
5613
5735
|
].join("\n");
|
|
5614
|
-
|
|
5736
|
+
writeFileSync8(ctxFile, ctxContent);
|
|
5615
5737
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
5616
5738
|
} catch {
|
|
5617
5739
|
}
|
|
@@ -5688,8 +5810,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5688
5810
|
transport.pipeLog(sessionName, logFile);
|
|
5689
5811
|
try {
|
|
5690
5812
|
const mySession = getMySession();
|
|
5691
|
-
const dispatchInfo =
|
|
5692
|
-
|
|
5813
|
+
const dispatchInfo = path17.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
5814
|
+
writeFileSync8(dispatchInfo, JSON.stringify({
|
|
5693
5815
|
dispatchedBy: mySession,
|
|
5694
5816
|
rootExe: exeSession,
|
|
5695
5817
|
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
|
|
@@ -5763,15 +5885,15 @@ var init_tmux_routing = __esm({
|
|
|
5763
5885
|
init_intercom_queue();
|
|
5764
5886
|
init_plan_limits();
|
|
5765
5887
|
init_employees();
|
|
5766
|
-
SPAWN_LOCK_DIR =
|
|
5767
|
-
SESSION_CACHE =
|
|
5888
|
+
SPAWN_LOCK_DIR = path17.join(os10.homedir(), ".exe-os", "spawn-locks");
|
|
5889
|
+
SESSION_CACHE = path17.join(os10.homedir(), ".exe-os", "session-cache");
|
|
5768
5890
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
5769
5891
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
5770
5892
|
VERIFY_PANE_LINES = 200;
|
|
5771
5893
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
5772
5894
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
5773
|
-
INTERCOM_LOG2 =
|
|
5774
|
-
DEBOUNCE_FILE =
|
|
5895
|
+
INTERCOM_LOG2 = path17.join(os10.homedir(), ".exe-os", "intercom.log");
|
|
5896
|
+
DEBOUNCE_FILE = path17.join(SESSION_CACHE, "intercom-debounce.json");
|
|
5775
5897
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
5776
5898
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
|
|
5777
5899
|
}
|
|
@@ -5794,6 +5916,15 @@ function sessionScopeFilter(sessionScope, tableAlias) {
|
|
|
5794
5916
|
args: [scope]
|
|
5795
5917
|
};
|
|
5796
5918
|
}
|
|
5919
|
+
function strictSessionScopeFilter(sessionScope, tableAlias) {
|
|
5920
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
5921
|
+
if (!scope) return { sql: "", args: [] };
|
|
5922
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
5923
|
+
return {
|
|
5924
|
+
sql: ` AND ${col} = ?`,
|
|
5925
|
+
args: [scope]
|
|
5926
|
+
};
|
|
5927
|
+
}
|
|
5797
5928
|
var init_task_scope = __esm({
|
|
5798
5929
|
"src/lib/task-scope.ts"() {
|
|
5799
5930
|
"use strict";
|
|
@@ -5812,14 +5943,14 @@ var init_memory = __esm({
|
|
|
5812
5943
|
|
|
5813
5944
|
// src/lib/keychain.ts
|
|
5814
5945
|
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
5815
|
-
import { existsSync as
|
|
5816
|
-
import
|
|
5817
|
-
import
|
|
5946
|
+
import { existsSync as existsSync15 } from "fs";
|
|
5947
|
+
import path18 from "path";
|
|
5948
|
+
import os11 from "os";
|
|
5818
5949
|
function getKeyDir() {
|
|
5819
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
5950
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path18.join(os11.homedir(), ".exe-os");
|
|
5820
5951
|
}
|
|
5821
5952
|
function getKeyPath() {
|
|
5822
|
-
return
|
|
5953
|
+
return path18.join(getKeyDir(), "master.key");
|
|
5823
5954
|
}
|
|
5824
5955
|
async function tryKeytar() {
|
|
5825
5956
|
try {
|
|
@@ -5840,9 +5971,9 @@ async function getMasterKey() {
|
|
|
5840
5971
|
}
|
|
5841
5972
|
}
|
|
5842
5973
|
const keyPath = getKeyPath();
|
|
5843
|
-
if (!
|
|
5974
|
+
if (!existsSync15(keyPath)) {
|
|
5844
5975
|
process.stderr.write(
|
|
5845
|
-
`[keychain] Key not found at ${keyPath} (HOME=${
|
|
5976
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os11.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
5846
5977
|
`
|
|
5847
5978
|
);
|
|
5848
5979
|
return null;
|
|
@@ -5872,6 +6003,7 @@ var shard_manager_exports = {};
|
|
|
5872
6003
|
__export(shard_manager_exports, {
|
|
5873
6004
|
disposeShards: () => disposeShards,
|
|
5874
6005
|
ensureShardSchema: () => ensureShardSchema,
|
|
6006
|
+
getOpenShardCount: () => getOpenShardCount,
|
|
5875
6007
|
getReadyShardClient: () => getReadyShardClient,
|
|
5876
6008
|
getShardClient: () => getShardClient,
|
|
5877
6009
|
getShardsDir: () => getShardsDir,
|
|
@@ -5880,15 +6012,18 @@ __export(shard_manager_exports, {
|
|
|
5880
6012
|
listShards: () => listShards,
|
|
5881
6013
|
shardExists: () => shardExists
|
|
5882
6014
|
});
|
|
5883
|
-
import
|
|
5884
|
-
import { existsSync as
|
|
6015
|
+
import path19 from "path";
|
|
6016
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
|
|
5885
6017
|
import { createClient as createClient2 } from "@libsql/client";
|
|
5886
6018
|
function initShardManager(encryptionKey) {
|
|
5887
6019
|
_encryptionKey = encryptionKey;
|
|
5888
|
-
if (!
|
|
6020
|
+
if (!existsSync16(SHARDS_DIR)) {
|
|
5889
6021
|
mkdirSync7(SHARDS_DIR, { recursive: true });
|
|
5890
6022
|
}
|
|
5891
6023
|
_shardingEnabled = true;
|
|
6024
|
+
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
6025
|
+
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
6026
|
+
_evictionTimer.unref();
|
|
5892
6027
|
}
|
|
5893
6028
|
function isShardingEnabled() {
|
|
5894
6029
|
return _shardingEnabled;
|
|
@@ -5905,21 +6040,28 @@ function getShardClient(projectName) {
|
|
|
5905
6040
|
throw new Error(`Invalid project name for shard: "${projectName}"`);
|
|
5906
6041
|
}
|
|
5907
6042
|
const cached = _shards.get(safeName);
|
|
5908
|
-
if (cached)
|
|
5909
|
-
|
|
6043
|
+
if (cached) {
|
|
6044
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
6045
|
+
return cached;
|
|
6046
|
+
}
|
|
6047
|
+
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
6048
|
+
evictLRU();
|
|
6049
|
+
}
|
|
6050
|
+
const dbPath = path19.join(SHARDS_DIR, `${safeName}.db`);
|
|
5910
6051
|
const client = createClient2({
|
|
5911
6052
|
url: `file:${dbPath}`,
|
|
5912
6053
|
encryptionKey: _encryptionKey
|
|
5913
6054
|
});
|
|
5914
6055
|
_shards.set(safeName, client);
|
|
6056
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
5915
6057
|
return client;
|
|
5916
6058
|
}
|
|
5917
6059
|
function shardExists(projectName) {
|
|
5918
6060
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
5919
|
-
return
|
|
6061
|
+
return existsSync16(path19.join(SHARDS_DIR, `${safeName}.db`));
|
|
5920
6062
|
}
|
|
5921
6063
|
function listShards() {
|
|
5922
|
-
if (!
|
|
6064
|
+
if (!existsSync16(SHARDS_DIR)) return [];
|
|
5923
6065
|
return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
5924
6066
|
}
|
|
5925
6067
|
async function ensureShardSchema(client) {
|
|
@@ -5971,6 +6113,8 @@ async function ensureShardSchema(client) {
|
|
|
5971
6113
|
for (const col of [
|
|
5972
6114
|
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
5973
6115
|
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
6116
|
+
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
6117
|
+
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
5974
6118
|
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
5975
6119
|
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
5976
6120
|
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
@@ -6108,21 +6252,69 @@ async function getReadyShardClient(projectName) {
|
|
|
6108
6252
|
await ensureShardSchema(client);
|
|
6109
6253
|
return client;
|
|
6110
6254
|
}
|
|
6255
|
+
function evictLRU() {
|
|
6256
|
+
let oldest = null;
|
|
6257
|
+
let oldestTime = Infinity;
|
|
6258
|
+
for (const [name, time] of _shardLastAccess) {
|
|
6259
|
+
if (time < oldestTime) {
|
|
6260
|
+
oldestTime = time;
|
|
6261
|
+
oldest = name;
|
|
6262
|
+
}
|
|
6263
|
+
}
|
|
6264
|
+
if (oldest) {
|
|
6265
|
+
const client = _shards.get(oldest);
|
|
6266
|
+
if (client) {
|
|
6267
|
+
client.close();
|
|
6268
|
+
}
|
|
6269
|
+
_shards.delete(oldest);
|
|
6270
|
+
_shardLastAccess.delete(oldest);
|
|
6271
|
+
}
|
|
6272
|
+
}
|
|
6273
|
+
function evictIdleShards() {
|
|
6274
|
+
const now = Date.now();
|
|
6275
|
+
const toEvict = [];
|
|
6276
|
+
for (const [name, lastAccess] of _shardLastAccess) {
|
|
6277
|
+
if (now - lastAccess > SHARD_IDLE_MS) {
|
|
6278
|
+
toEvict.push(name);
|
|
6279
|
+
}
|
|
6280
|
+
}
|
|
6281
|
+
for (const name of toEvict) {
|
|
6282
|
+
const client = _shards.get(name);
|
|
6283
|
+
if (client) {
|
|
6284
|
+
client.close();
|
|
6285
|
+
}
|
|
6286
|
+
_shards.delete(name);
|
|
6287
|
+
_shardLastAccess.delete(name);
|
|
6288
|
+
}
|
|
6289
|
+
}
|
|
6290
|
+
function getOpenShardCount() {
|
|
6291
|
+
return _shards.size;
|
|
6292
|
+
}
|
|
6111
6293
|
function disposeShards() {
|
|
6294
|
+
if (_evictionTimer) {
|
|
6295
|
+
clearInterval(_evictionTimer);
|
|
6296
|
+
_evictionTimer = null;
|
|
6297
|
+
}
|
|
6112
6298
|
for (const [, client] of _shards) {
|
|
6113
6299
|
client.close();
|
|
6114
6300
|
}
|
|
6115
6301
|
_shards.clear();
|
|
6302
|
+
_shardLastAccess.clear();
|
|
6116
6303
|
_shardingEnabled = false;
|
|
6117
6304
|
_encryptionKey = null;
|
|
6118
6305
|
}
|
|
6119
|
-
var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
|
|
6306
|
+
var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
|
|
6120
6307
|
var init_shard_manager = __esm({
|
|
6121
6308
|
"src/lib/shard-manager.ts"() {
|
|
6122
6309
|
"use strict";
|
|
6123
6310
|
init_config();
|
|
6124
|
-
SHARDS_DIR =
|
|
6311
|
+
SHARDS_DIR = path19.join(EXE_AI_DIR, "shards");
|
|
6312
|
+
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
6313
|
+
MAX_OPEN_SHARDS = 10;
|
|
6314
|
+
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
6125
6315
|
_shards = /* @__PURE__ */ new Map();
|
|
6316
|
+
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
6317
|
+
_evictionTimer = null;
|
|
6126
6318
|
_encryptionKey = null;
|
|
6127
6319
|
_shardingEnabled = false;
|
|
6128
6320
|
}
|
|
@@ -6886,9 +7078,9 @@ var init_store = __esm({
|
|
|
6886
7078
|
});
|
|
6887
7079
|
|
|
6888
7080
|
// src/bin/scan-tasks.ts
|
|
6889
|
-
import { existsSync as
|
|
6890
|
-
import
|
|
6891
|
-
import
|
|
7081
|
+
import { existsSync as existsSync17, readFileSync as readFileSync13 } from "fs";
|
|
7082
|
+
import path20 from "path";
|
|
7083
|
+
import os12 from "os";
|
|
6892
7084
|
|
|
6893
7085
|
// src/lib/is-main.ts
|
|
6894
7086
|
import { realpathSync } from "fs";
|
|
@@ -6908,27 +7100,27 @@ function isMainModule(importMetaUrl) {
|
|
|
6908
7100
|
// src/bin/scan-tasks.ts
|
|
6909
7101
|
init_session_key();
|
|
6910
7102
|
init_task_scope();
|
|
6911
|
-
function getMcpHealthWarning(runtime = getSessionRuntime(), homeDir =
|
|
7103
|
+
function getMcpHealthWarning(runtime = getSessionRuntime(), homeDir = os12.homedir()) {
|
|
6912
7104
|
if (runtime === "codex") {
|
|
6913
7105
|
return null;
|
|
6914
7106
|
}
|
|
6915
7107
|
try {
|
|
6916
7108
|
if (runtime === "opencode") {
|
|
6917
|
-
const opencodeJson =
|
|
6918
|
-
if (!
|
|
7109
|
+
const opencodeJson = path20.join(homeDir, ".config", "opencode", "opencode.json");
|
|
7110
|
+
if (!existsSync17(opencodeJson)) {
|
|
6919
7111
|
return "\u26A0\uFE0F MCP config missing (~/.config/opencode/opencode.json not found) \u2014 exe-os task tools may be unavailable. Run `exe-os opencode`.\n";
|
|
6920
7112
|
}
|
|
6921
|
-
const config2 = JSON.parse(
|
|
7113
|
+
const config2 = JSON.parse(readFileSync13(opencodeJson, "utf8"));
|
|
6922
7114
|
if (!config2.mcp?.["exe-os"]?.enabled) {
|
|
6923
7115
|
return "\u26A0\uFE0F MCP task tools not available \u2014 exe-os server is not enabled in ~/.config/opencode/opencode.json.\n";
|
|
6924
7116
|
}
|
|
6925
7117
|
return null;
|
|
6926
7118
|
}
|
|
6927
|
-
const claudeJson =
|
|
6928
|
-
if (!
|
|
7119
|
+
const claudeJson = path20.join(homeDir, ".claude.json");
|
|
7120
|
+
if (!existsSync17(claudeJson)) {
|
|
6929
7121
|
return "\u26A0\uFE0F MCP config missing (~/.claude.json not found) \u2014 close_task won't work. Run /exe-setup\n";
|
|
6930
7122
|
}
|
|
6931
|
-
const config = JSON.parse(
|
|
7123
|
+
const config = JSON.parse(readFileSync13(claudeJson, "utf8"));
|
|
6932
7124
|
const servers = config.mcpServers;
|
|
6933
7125
|
if (!servers?.["exe-os"] && !servers?.["exe-mem"]) {
|
|
6934
7126
|
return "\u26A0\uFE0F MCP task tools not available \u2014 exe-os server not configured in ~/.claude.json. close_task won't work.\n";
|