@askexenow/exe-os 0.9.8 → 0.9.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/backfill-conversations.js +222 -49
- package/dist/bin/backfill-responses.js +221 -48
- package/dist/bin/backfill-vectors.js +225 -52
- package/dist/bin/cleanup-stale-review-tasks.js +150 -28
- package/dist/bin/cli.js +1411 -953
- package/dist/bin/exe-agent-config.js +36 -8
- package/dist/bin/exe-agent.js +14 -4
- package/dist/bin/exe-assign.js +221 -48
- package/dist/bin/exe-boot.js +913 -543
- package/dist/bin/exe-call.js +41 -13
- package/dist/bin/exe-cloud.js +163 -58
- package/dist/bin/exe-dispatch.js +418 -262
- package/dist/bin/exe-doctor.js +145 -27
- package/dist/bin/exe-export-behaviors.js +141 -23
- package/dist/bin/exe-forget.js +137 -19
- package/dist/bin/exe-gateway.js +793 -485
- package/dist/bin/exe-heartbeat.js +227 -108
- package/dist/bin/exe-kill.js +138 -20
- package/dist/bin/exe-launch-agent.js +172 -39
- package/dist/bin/exe-link.js +291 -100
- package/dist/bin/exe-new-employee.js +214 -106
- package/dist/bin/exe-pending-messages.js +395 -33
- package/dist/bin/exe-pending-notifications.js +684 -99
- package/dist/bin/exe-pending-reviews.js +420 -74
- package/dist/bin/exe-rename.js +147 -49
- package/dist/bin/exe-review.js +138 -20
- package/dist/bin/exe-search.js +240 -69
- package/dist/bin/exe-session-cleanup.js +566 -357
- package/dist/bin/exe-settings.js +61 -17
- package/dist/bin/exe-start-codex.js +158 -39
- package/dist/bin/exe-start-opencode.js +157 -38
- package/dist/bin/exe-status.js +151 -29
- package/dist/bin/exe-team.js +138 -20
- package/dist/bin/git-sweep.js +530 -319
- package/dist/bin/graph-backfill.js +137 -19
- package/dist/bin/graph-export.js +140 -22
- package/dist/bin/install.js +90 -61
- package/dist/bin/scan-tasks.js +547 -336
- package/dist/bin/setup.js +564 -293
- package/dist/bin/shard-migrate.js +139 -21
- package/dist/bin/update.js +138 -49
- package/dist/bin/wiki-sync.js +137 -19
- package/dist/gateway/index.js +649 -417
- package/dist/hooks/bug-report-worker.js +486 -316
- package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
- package/dist/hooks/commit-complete.js +528 -317
- package/dist/hooks/error-recall.js +245 -74
- package/dist/hooks/exe-heartbeat-hook.js +16 -6
- package/dist/hooks/ingest-worker.js +3442 -3157
- package/dist/hooks/ingest.js +832 -97
- package/dist/hooks/instructions-loaded.js +227 -54
- package/dist/hooks/notification.js +216 -43
- package/dist/hooks/post-compact.js +239 -62
- package/dist/hooks/pre-compact.js +534 -323
- package/dist/hooks/pre-tool-use.js +268 -90
- package/dist/hooks/prompt-ingest-worker.js +352 -102
- package/dist/hooks/prompt-submit.js +614 -382
- package/dist/hooks/response-ingest-worker.js +372 -122
- package/dist/hooks/session-end.js +569 -347
- package/dist/hooks/session-start.js +313 -127
- package/dist/hooks/stop.js +293 -98
- package/dist/hooks/subagent-stop.js +239 -62
- package/dist/hooks/summary-worker.js +568 -236
- package/dist/index.js +664 -431
- package/dist/lib/agent-config.js +28 -6
- package/dist/lib/cloud-sync.js +284 -105
- package/dist/lib/config.js +30 -10
- package/dist/lib/consolidation.js +16 -6
- package/dist/lib/database.js +123 -25
- package/dist/lib/db-daemon-client.js +73 -19
- package/dist/lib/db.js +123 -25
- package/dist/lib/device-registry.js +133 -35
- package/dist/lib/embedder.js +107 -32
- package/dist/lib/employee-templates.js +14 -4
- package/dist/lib/employees.js +41 -13
- package/dist/lib/exe-daemon-client.js +88 -22
- package/dist/lib/exe-daemon.js +1049 -680
- package/dist/lib/hybrid-search.js +240 -69
- package/dist/lib/identity.js +18 -8
- package/dist/lib/license.js +133 -48
- package/dist/lib/messaging.js +116 -56
- package/dist/lib/reminders.js +14 -4
- package/dist/lib/schedules.js +137 -19
- package/dist/lib/skill-learning.js +33 -6
- package/dist/lib/store.js +137 -19
- package/dist/lib/task-router.js +14 -4
- package/dist/lib/tasks.js +422 -357
- package/dist/lib/tmux-routing.js +314 -248
- package/dist/lib/token-spend.js +26 -8
- package/dist/mcp/server.js +1408 -672
- package/dist/mcp/tools/complete-reminder.js +14 -4
- package/dist/mcp/tools/create-reminder.js +14 -4
- package/dist/mcp/tools/create-task.js +448 -371
- package/dist/mcp/tools/deactivate-behavior.js +16 -6
- package/dist/mcp/tools/list-reminders.js +14 -4
- package/dist/mcp/tools/list-tasks.js +123 -107
- package/dist/mcp/tools/send-message.js +75 -29
- package/dist/mcp/tools/update-task.js +1983 -315
- package/dist/runtime/index.js +567 -355
- package/dist/tui/App.js +887 -531
- package/package.json +4 -4
package/dist/bin/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(),
|
|
@@ -3141,13 +3262,126 @@ var init_state_bus = __esm({
|
|
|
3141
3262
|
}
|
|
3142
3263
|
});
|
|
3143
3264
|
|
|
3144
|
-
// src/lib/
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3265
|
+
// src/lib/project-name.ts
|
|
3266
|
+
var project_name_exports = {};
|
|
3267
|
+
__export(project_name_exports, {
|
|
3268
|
+
_resetCache: () => _resetCache,
|
|
3269
|
+
getProjectName: () => getProjectName
|
|
3270
|
+
});
|
|
3148
3271
|
import { execSync as execSync4 } from "child_process";
|
|
3272
|
+
import path12 from "path";
|
|
3273
|
+
function getProjectName(cwd) {
|
|
3274
|
+
const dir = cwd ?? process.cwd();
|
|
3275
|
+
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
3276
|
+
try {
|
|
3277
|
+
let repoRoot;
|
|
3278
|
+
try {
|
|
3279
|
+
const gitCommonDir = execSync4("git rev-parse --path-format=absolute --git-common-dir", {
|
|
3280
|
+
cwd: dir,
|
|
3281
|
+
encoding: "utf8",
|
|
3282
|
+
timeout: 2e3,
|
|
3283
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3284
|
+
}).trim();
|
|
3285
|
+
repoRoot = path12.dirname(gitCommonDir);
|
|
3286
|
+
} catch {
|
|
3287
|
+
repoRoot = execSync4("git rev-parse --show-toplevel", {
|
|
3288
|
+
cwd: dir,
|
|
3289
|
+
encoding: "utf8",
|
|
3290
|
+
timeout: 2e3,
|
|
3291
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3292
|
+
}).trim();
|
|
3293
|
+
}
|
|
3294
|
+
_cached2 = path12.basename(repoRoot);
|
|
3295
|
+
_cachedCwd = dir;
|
|
3296
|
+
return _cached2;
|
|
3297
|
+
} catch {
|
|
3298
|
+
_cached2 = path12.basename(dir);
|
|
3299
|
+
_cachedCwd = dir;
|
|
3300
|
+
return _cached2;
|
|
3301
|
+
}
|
|
3302
|
+
}
|
|
3303
|
+
function _resetCache() {
|
|
3304
|
+
_cached2 = null;
|
|
3305
|
+
_cachedCwd = null;
|
|
3306
|
+
}
|
|
3307
|
+
var _cached2, _cachedCwd;
|
|
3308
|
+
var init_project_name = __esm({
|
|
3309
|
+
"src/lib/project-name.ts"() {
|
|
3310
|
+
"use strict";
|
|
3311
|
+
_cached2 = null;
|
|
3312
|
+
_cachedCwd = null;
|
|
3313
|
+
}
|
|
3314
|
+
});
|
|
3315
|
+
|
|
3316
|
+
// src/lib/session-scope.ts
|
|
3317
|
+
var session_scope_exports = {};
|
|
3318
|
+
__export(session_scope_exports, {
|
|
3319
|
+
assertSessionScope: () => assertSessionScope,
|
|
3320
|
+
findSessionForProject: () => findSessionForProject,
|
|
3321
|
+
getSessionProject: () => getSessionProject
|
|
3322
|
+
});
|
|
3323
|
+
function getSessionProject(sessionName) {
|
|
3324
|
+
const sessions = listSessions();
|
|
3325
|
+
const entry = sessions.find((s) => s.windowName === sessionName);
|
|
3326
|
+
if (!entry) return null;
|
|
3327
|
+
const parts = entry.projectDir.split("/").filter(Boolean);
|
|
3328
|
+
return parts[parts.length - 1] ?? null;
|
|
3329
|
+
}
|
|
3330
|
+
function findSessionForProject(projectName) {
|
|
3331
|
+
const sessions = listSessions();
|
|
3332
|
+
for (const s of sessions) {
|
|
3333
|
+
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
3334
|
+
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
3335
|
+
}
|
|
3336
|
+
return null;
|
|
3337
|
+
}
|
|
3338
|
+
function assertSessionScope(actionType, targetProject) {
|
|
3339
|
+
try {
|
|
3340
|
+
const currentProject = getProjectName();
|
|
3341
|
+
const exeSession = resolveExeSession();
|
|
3342
|
+
if (!exeSession) {
|
|
3343
|
+
return { allowed: true, reason: "no_session" };
|
|
3344
|
+
}
|
|
3345
|
+
if (currentProject === targetProject) {
|
|
3346
|
+
return {
|
|
3347
|
+
allowed: true,
|
|
3348
|
+
reason: "same_session",
|
|
3349
|
+
currentProject,
|
|
3350
|
+
targetProject
|
|
3351
|
+
};
|
|
3352
|
+
}
|
|
3353
|
+
process.stderr.write(
|
|
3354
|
+
`[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
|
|
3355
|
+
`
|
|
3356
|
+
);
|
|
3357
|
+
return {
|
|
3358
|
+
allowed: false,
|
|
3359
|
+
reason: "cross_session_denied",
|
|
3360
|
+
currentProject,
|
|
3361
|
+
targetProject,
|
|
3362
|
+
targetSession: findSessionForProject(targetProject)?.windowName
|
|
3363
|
+
};
|
|
3364
|
+
} catch {
|
|
3365
|
+
return { allowed: true, reason: "no_session" };
|
|
3366
|
+
}
|
|
3367
|
+
}
|
|
3368
|
+
var init_session_scope = __esm({
|
|
3369
|
+
"src/lib/session-scope.ts"() {
|
|
3370
|
+
"use strict";
|
|
3371
|
+
init_session_registry();
|
|
3372
|
+
init_project_name();
|
|
3373
|
+
init_tmux_routing();
|
|
3374
|
+
init_employees();
|
|
3375
|
+
}
|
|
3376
|
+
});
|
|
3377
|
+
|
|
3378
|
+
// src/lib/tasks-crud.ts
|
|
3379
|
+
import crypto4 from "crypto";
|
|
3380
|
+
import path13 from "path";
|
|
3381
|
+
import os9 from "os";
|
|
3382
|
+
import { execSync as execSync5 } from "child_process";
|
|
3149
3383
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
3150
|
-
import { existsSync as
|
|
3384
|
+
import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
|
|
3151
3385
|
async function writeCheckpoint(input) {
|
|
3152
3386
|
const client = getClient();
|
|
3153
3387
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -3263,13 +3497,28 @@ async function resolveTask(client, identifier, scopeSession) {
|
|
|
3263
3497
|
}
|
|
3264
3498
|
async function createTaskCore(input) {
|
|
3265
3499
|
const client = getClient();
|
|
3266
|
-
const id =
|
|
3500
|
+
const id = crypto4.randomUUID();
|
|
3267
3501
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3268
3502
|
const slug = slugify(input.title);
|
|
3269
3503
|
let earlySessionScope = null;
|
|
3504
|
+
let scopeMismatchWarning;
|
|
3270
3505
|
try {
|
|
3271
3506
|
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
3272
|
-
|
|
3507
|
+
const resolved = resolveExeSession2();
|
|
3508
|
+
if (resolved && input.projectName) {
|
|
3509
|
+
const { getSessionProject: getSessionProject2 } = await Promise.resolve().then(() => (init_session_scope(), session_scope_exports));
|
|
3510
|
+
const sessionProject = getSessionProject2(resolved);
|
|
3511
|
+
if (sessionProject && sessionProject !== input.projectName) {
|
|
3512
|
+
scopeMismatchWarning = `session/project mismatch: session "${resolved}" owns "${sessionProject}" but task targets "${input.projectName}". Routed to default scope.`;
|
|
3513
|
+
process.stderr.write(`[create_task] ${scopeMismatchWarning}
|
|
3514
|
+
`);
|
|
3515
|
+
earlySessionScope = null;
|
|
3516
|
+
} else {
|
|
3517
|
+
earlySessionScope = resolved;
|
|
3518
|
+
}
|
|
3519
|
+
} else {
|
|
3520
|
+
earlySessionScope = resolved;
|
|
3521
|
+
}
|
|
3273
3522
|
} catch {
|
|
3274
3523
|
}
|
|
3275
3524
|
const scope = earlySessionScope ?? "default";
|
|
@@ -3320,10 +3569,14 @@ async function createTaskCore(input) {
|
|
|
3320
3569
|
${laneWarning}` : laneWarning;
|
|
3321
3570
|
}
|
|
3322
3571
|
}
|
|
3572
|
+
if (scopeMismatchWarning) {
|
|
3573
|
+
warning = warning ? `${warning}
|
|
3574
|
+
${scopeMismatchWarning}` : scopeMismatchWarning;
|
|
3575
|
+
}
|
|
3323
3576
|
if (input.baseDir) {
|
|
3324
3577
|
try {
|
|
3325
|
-
await mkdir3(
|
|
3326
|
-
await mkdir3(
|
|
3578
|
+
await mkdir3(path13.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
3579
|
+
await mkdir3(path13.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
3327
3580
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
3328
3581
|
await ensureGitignoreExe(input.baseDir);
|
|
3329
3582
|
} catch {
|
|
@@ -3359,13 +3612,19 @@ ${laneWarning}` : laneWarning;
|
|
|
3359
3612
|
});
|
|
3360
3613
|
if (input.baseDir) {
|
|
3361
3614
|
try {
|
|
3362
|
-
const EXE_OS_DIR =
|
|
3363
|
-
const mdPath =
|
|
3364
|
-
const mdDir =
|
|
3365
|
-
if (!
|
|
3615
|
+
const EXE_OS_DIR = path13.join(os9.homedir(), ".exe-os");
|
|
3616
|
+
const mdPath = path13.join(EXE_OS_DIR, taskFile);
|
|
3617
|
+
const mdDir = path13.dirname(mdPath);
|
|
3618
|
+
if (!existsSync12(mdDir)) await mkdir3(mdDir, { recursive: true });
|
|
3366
3619
|
const reviewer = input.reviewer ?? input.assignedBy;
|
|
3367
3620
|
const mdContent = `# ${input.title}
|
|
3368
3621
|
|
|
3622
|
+
## MANDATORY: When done
|
|
3623
|
+
|
|
3624
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
3625
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
3626
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
3627
|
+
|
|
3369
3628
|
**ID:** ${id}
|
|
3370
3629
|
**Status:** ${initialStatus}
|
|
3371
3630
|
**Priority:** ${input.priority}
|
|
@@ -3379,12 +3638,6 @@ ${laneWarning}` : laneWarning;
|
|
|
3379
3638
|
## Context
|
|
3380
3639
|
|
|
3381
3640
|
${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
3641
|
`;
|
|
3389
3642
|
await writeFile3(mdPath, mdContent, "utf-8");
|
|
3390
3643
|
} catch (err) {
|
|
@@ -3466,14 +3719,14 @@ function isTmuxSessionAlive(identifier) {
|
|
|
3466
3719
|
if (!identifier || identifier === "unknown") return true;
|
|
3467
3720
|
try {
|
|
3468
3721
|
if (identifier.startsWith("%")) {
|
|
3469
|
-
const output =
|
|
3722
|
+
const output = execSync5("tmux list-panes -a -F '#{pane_id}'", {
|
|
3470
3723
|
timeout: 2e3,
|
|
3471
3724
|
encoding: "utf8",
|
|
3472
3725
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3473
3726
|
});
|
|
3474
3727
|
return output.split("\n").some((l) => l.trim() === identifier);
|
|
3475
3728
|
} else {
|
|
3476
|
-
|
|
3729
|
+
execSync5(`tmux has-session -t ${JSON.stringify(identifier)}`, {
|
|
3477
3730
|
timeout: 2e3,
|
|
3478
3731
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3479
3732
|
});
|
|
@@ -3482,7 +3735,7 @@ function isTmuxSessionAlive(identifier) {
|
|
|
3482
3735
|
} catch {
|
|
3483
3736
|
if (identifier.startsWith("%")) return true;
|
|
3484
3737
|
try {
|
|
3485
|
-
|
|
3738
|
+
execSync5("tmux list-sessions", {
|
|
3486
3739
|
timeout: 2e3,
|
|
3487
3740
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3488
3741
|
});
|
|
@@ -3497,12 +3750,12 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
|
|
|
3497
3750
|
if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
|
|
3498
3751
|
try {
|
|
3499
3752
|
const since = new Date(taskCreatedAt).toISOString();
|
|
3500
|
-
const branch =
|
|
3753
|
+
const branch = execSync5(
|
|
3501
3754
|
"git rev-parse --abbrev-ref HEAD 2>/dev/null",
|
|
3502
3755
|
{ encoding: "utf8", timeout: 3e3 }
|
|
3503
3756
|
).trim();
|
|
3504
3757
|
const branchArg = branch && branch !== "HEAD" ? branch : "";
|
|
3505
|
-
const commitCount =
|
|
3758
|
+
const commitCount = execSync5(
|
|
3506
3759
|
`git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
|
|
3507
3760
|
{ encoding: "utf8", timeout: 5e3 }
|
|
3508
3761
|
).trim();
|
|
@@ -3633,7 +3886,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
3633
3886
|
await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
|
|
3634
3887
|
} catch {
|
|
3635
3888
|
}
|
|
3636
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
3889
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
3637
3890
|
try {
|
|
3638
3891
|
const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
3639
3892
|
clearQueueForAgent2(String(row.assigned_to));
|
|
@@ -3662,9 +3915,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
3662
3915
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
3663
3916
|
}
|
|
3664
3917
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
3665
|
-
const archPath =
|
|
3918
|
+
const archPath = path13.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
3666
3919
|
try {
|
|
3667
|
-
if (
|
|
3920
|
+
if (existsSync12(archPath)) return;
|
|
3668
3921
|
const template = [
|
|
3669
3922
|
`# ${projectName} \u2014 System Architecture`,
|
|
3670
3923
|
"",
|
|
@@ -3697,10 +3950,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
3697
3950
|
}
|
|
3698
3951
|
}
|
|
3699
3952
|
async function ensureGitignoreExe(baseDir) {
|
|
3700
|
-
const gitignorePath =
|
|
3953
|
+
const gitignorePath = path13.join(baseDir, ".gitignore");
|
|
3701
3954
|
try {
|
|
3702
|
-
if (
|
|
3703
|
-
const content =
|
|
3955
|
+
if (existsSync12(gitignorePath)) {
|
|
3956
|
+
const content = readFileSync11(gitignorePath, "utf-8");
|
|
3704
3957
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
3705
3958
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
3706
3959
|
} else {
|
|
@@ -3731,58 +3984,42 @@ var init_tasks_crud = __esm({
|
|
|
3731
3984
|
});
|
|
3732
3985
|
|
|
3733
3986
|
// src/lib/tasks-review.ts
|
|
3734
|
-
import
|
|
3735
|
-
import { existsSync as
|
|
3987
|
+
import path14 from "path";
|
|
3988
|
+
import { existsSync as existsSync13, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
|
|
3736
3989
|
async function countPendingReviews(sessionScope) {
|
|
3737
3990
|
const client = getClient();
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
args: [sessionScope]
|
|
3742
|
-
});
|
|
3743
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
3744
|
-
}
|
|
3991
|
+
const scope = strictSessionScopeFilter(
|
|
3992
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
3993
|
+
);
|
|
3745
3994
|
const result = await client.execute({
|
|
3746
|
-
sql:
|
|
3747
|
-
|
|
3995
|
+
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3996
|
+
WHERE status = 'needs_review'${scope.sql}`,
|
|
3997
|
+
args: [...scope.args]
|
|
3748
3998
|
});
|
|
3749
3999
|
return Number(result.rows[0]?.cnt) || 0;
|
|
3750
4000
|
}
|
|
3751
4001
|
async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
3752
4002
|
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
|
-
}
|
|
4003
|
+
const scope = strictSessionScopeFilter(
|
|
4004
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
4005
|
+
);
|
|
3762
4006
|
const result = await client.execute({
|
|
3763
4007
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3764
|
-
WHERE status = 'needs_review' AND updated_at >
|
|
3765
|
-
args: [sinceIso]
|
|
4008
|
+
WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
|
|
4009
|
+
args: [sinceIso, ...scope.args]
|
|
3766
4010
|
});
|
|
3767
4011
|
return Number(result.rows[0]?.cnt) || 0;
|
|
3768
4012
|
}
|
|
3769
4013
|
async function listPendingReviews(limit, sessionScope) {
|
|
3770
4014
|
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
|
-
}
|
|
4015
|
+
const scope = strictSessionScopeFilter(
|
|
4016
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
4017
|
+
);
|
|
3781
4018
|
const result = await client.execute({
|
|
3782
4019
|
sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
|
|
3783
|
-
WHERE status = 'needs_review'
|
|
4020
|
+
WHERE status = 'needs_review'${scope.sql}
|
|
3784
4021
|
ORDER BY updated_at ASC LIMIT ?`,
|
|
3785
|
-
args: [limit]
|
|
4022
|
+
args: [...scope.args, limit]
|
|
3786
4023
|
});
|
|
3787
4024
|
return result.rows;
|
|
3788
4025
|
}
|
|
@@ -3794,7 +4031,7 @@ async function cleanupOrphanedReviews() {
|
|
|
3794
4031
|
WHERE status IN ('open', 'needs_review', 'in_progress')
|
|
3795
4032
|
AND assigned_by = 'system'
|
|
3796
4033
|
AND title LIKE 'Review:%'
|
|
3797
|
-
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
|
|
4034
|
+
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
|
|
3798
4035
|
args: [now]
|
|
3799
4036
|
});
|
|
3800
4037
|
const r1b = await client.execute({
|
|
@@ -3913,11 +4150,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
3913
4150
|
);
|
|
3914
4151
|
}
|
|
3915
4152
|
try {
|
|
3916
|
-
const cacheDir =
|
|
3917
|
-
if (
|
|
4153
|
+
const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
|
|
4154
|
+
if (existsSync13(cacheDir)) {
|
|
3918
4155
|
for (const f of readdirSync2(cacheDir)) {
|
|
3919
4156
|
if (f.startsWith("review-notified-")) {
|
|
3920
|
-
unlinkSync4(
|
|
4157
|
+
unlinkSync4(path14.join(cacheDir, f));
|
|
3921
4158
|
}
|
|
3922
4159
|
}
|
|
3923
4160
|
}
|
|
@@ -3934,11 +4171,12 @@ var init_tasks_review = __esm({
|
|
|
3934
4171
|
init_tmux_routing();
|
|
3935
4172
|
init_session_key();
|
|
3936
4173
|
init_state_bus();
|
|
4174
|
+
init_task_scope();
|
|
3937
4175
|
}
|
|
3938
4176
|
});
|
|
3939
4177
|
|
|
3940
4178
|
// src/lib/tasks-chain.ts
|
|
3941
|
-
import
|
|
4179
|
+
import path15 from "path";
|
|
3942
4180
|
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
3943
4181
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
3944
4182
|
const client = getClient();
|
|
@@ -3955,7 +4193,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
3955
4193
|
});
|
|
3956
4194
|
for (const ur of unblockedRows.rows) {
|
|
3957
4195
|
try {
|
|
3958
|
-
const ubFile =
|
|
4196
|
+
const ubFile = path15.join(baseDir, String(ur.task_file));
|
|
3959
4197
|
let ubContent = await readFile3(ubFile, "utf-8");
|
|
3960
4198
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
3961
4199
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -3990,7 +4228,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
|
|
|
3990
4228
|
const scScope = sessionScopeFilter();
|
|
3991
4229
|
const remaining = await client.execute({
|
|
3992
4230
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3993
|
-
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
|
|
4231
|
+
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
|
|
3994
4232
|
args: [parentTaskId, ...scScope.args]
|
|
3995
4233
|
});
|
|
3996
4234
|
const cnt = Number(remaining.rows[0]?.cnt ?? 1);
|
|
@@ -4022,119 +4260,6 @@ var init_tasks_chain = __esm({
|
|
|
4022
4260
|
}
|
|
4023
4261
|
});
|
|
4024
4262
|
|
|
4025
|
-
// src/lib/project-name.ts
|
|
4026
|
-
var project_name_exports = {};
|
|
4027
|
-
__export(project_name_exports, {
|
|
4028
|
-
_resetCache: () => _resetCache,
|
|
4029
|
-
getProjectName: () => getProjectName
|
|
4030
|
-
});
|
|
4031
|
-
import { execSync as execSync5 } from "child_process";
|
|
4032
|
-
import path14 from "path";
|
|
4033
|
-
function getProjectName(cwd) {
|
|
4034
|
-
const dir = cwd ?? process.cwd();
|
|
4035
|
-
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
4036
|
-
try {
|
|
4037
|
-
let repoRoot;
|
|
4038
|
-
try {
|
|
4039
|
-
const gitCommonDir = execSync5("git rev-parse --path-format=absolute --git-common-dir", {
|
|
4040
|
-
cwd: dir,
|
|
4041
|
-
encoding: "utf8",
|
|
4042
|
-
timeout: 2e3,
|
|
4043
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
4044
|
-
}).trim();
|
|
4045
|
-
repoRoot = path14.dirname(gitCommonDir);
|
|
4046
|
-
} catch {
|
|
4047
|
-
repoRoot = execSync5("git rev-parse --show-toplevel", {
|
|
4048
|
-
cwd: dir,
|
|
4049
|
-
encoding: "utf8",
|
|
4050
|
-
timeout: 2e3,
|
|
4051
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
4052
|
-
}).trim();
|
|
4053
|
-
}
|
|
4054
|
-
_cached2 = path14.basename(repoRoot);
|
|
4055
|
-
_cachedCwd = dir;
|
|
4056
|
-
return _cached2;
|
|
4057
|
-
} catch {
|
|
4058
|
-
_cached2 = path14.basename(dir);
|
|
4059
|
-
_cachedCwd = dir;
|
|
4060
|
-
return _cached2;
|
|
4061
|
-
}
|
|
4062
|
-
}
|
|
4063
|
-
function _resetCache() {
|
|
4064
|
-
_cached2 = null;
|
|
4065
|
-
_cachedCwd = null;
|
|
4066
|
-
}
|
|
4067
|
-
var _cached2, _cachedCwd;
|
|
4068
|
-
var init_project_name = __esm({
|
|
4069
|
-
"src/lib/project-name.ts"() {
|
|
4070
|
-
"use strict";
|
|
4071
|
-
_cached2 = null;
|
|
4072
|
-
_cachedCwd = null;
|
|
4073
|
-
}
|
|
4074
|
-
});
|
|
4075
|
-
|
|
4076
|
-
// src/lib/session-scope.ts
|
|
4077
|
-
var session_scope_exports = {};
|
|
4078
|
-
__export(session_scope_exports, {
|
|
4079
|
-
assertSessionScope: () => assertSessionScope,
|
|
4080
|
-
findSessionForProject: () => findSessionForProject,
|
|
4081
|
-
getSessionProject: () => getSessionProject
|
|
4082
|
-
});
|
|
4083
|
-
function getSessionProject(sessionName) {
|
|
4084
|
-
const sessions = listSessions();
|
|
4085
|
-
const entry = sessions.find((s) => s.windowName === sessionName);
|
|
4086
|
-
if (!entry) return null;
|
|
4087
|
-
const parts = entry.projectDir.split("/").filter(Boolean);
|
|
4088
|
-
return parts[parts.length - 1] ?? null;
|
|
4089
|
-
}
|
|
4090
|
-
function findSessionForProject(projectName) {
|
|
4091
|
-
const sessions = listSessions();
|
|
4092
|
-
for (const s of sessions) {
|
|
4093
|
-
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
4094
|
-
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
4095
|
-
}
|
|
4096
|
-
return null;
|
|
4097
|
-
}
|
|
4098
|
-
function assertSessionScope(actionType, targetProject) {
|
|
4099
|
-
try {
|
|
4100
|
-
const currentProject = getProjectName();
|
|
4101
|
-
const exeSession = resolveExeSession();
|
|
4102
|
-
if (!exeSession) {
|
|
4103
|
-
return { allowed: true, reason: "no_session" };
|
|
4104
|
-
}
|
|
4105
|
-
if (currentProject === targetProject) {
|
|
4106
|
-
return {
|
|
4107
|
-
allowed: true,
|
|
4108
|
-
reason: "same_session",
|
|
4109
|
-
currentProject,
|
|
4110
|
-
targetProject
|
|
4111
|
-
};
|
|
4112
|
-
}
|
|
4113
|
-
process.stderr.write(
|
|
4114
|
-
`[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
|
|
4115
|
-
`
|
|
4116
|
-
);
|
|
4117
|
-
return {
|
|
4118
|
-
allowed: false,
|
|
4119
|
-
reason: "cross_session_denied",
|
|
4120
|
-
currentProject,
|
|
4121
|
-
targetProject,
|
|
4122
|
-
targetSession: findSessionForProject(targetProject)?.windowName
|
|
4123
|
-
};
|
|
4124
|
-
} catch {
|
|
4125
|
-
return { allowed: true, reason: "no_session" };
|
|
4126
|
-
}
|
|
4127
|
-
}
|
|
4128
|
-
var init_session_scope = __esm({
|
|
4129
|
-
"src/lib/session-scope.ts"() {
|
|
4130
|
-
"use strict";
|
|
4131
|
-
init_session_registry();
|
|
4132
|
-
init_project_name();
|
|
4133
|
-
init_tmux_routing();
|
|
4134
|
-
init_employees();
|
|
4135
|
-
}
|
|
4136
|
-
});
|
|
4137
|
-
|
|
4138
4263
|
// src/lib/tasks-notify.ts
|
|
4139
4264
|
async function dispatchTaskToEmployee(input) {
|
|
4140
4265
|
if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
|
|
@@ -4202,10 +4327,10 @@ var init_tasks_notify = __esm({
|
|
|
4202
4327
|
});
|
|
4203
4328
|
|
|
4204
4329
|
// src/lib/behaviors.ts
|
|
4205
|
-
import
|
|
4330
|
+
import crypto5 from "crypto";
|
|
4206
4331
|
async function storeBehavior(opts) {
|
|
4207
4332
|
const client = getClient();
|
|
4208
|
-
const id =
|
|
4333
|
+
const id = crypto5.randomUUID();
|
|
4209
4334
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4210
4335
|
await client.execute({
|
|
4211
4336
|
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
|
|
@@ -4234,7 +4359,7 @@ __export(skill_learning_exports, {
|
|
|
4234
4359
|
storeTrajectory: () => storeTrajectory,
|
|
4235
4360
|
sweepTrajectories: () => sweepTrajectories
|
|
4236
4361
|
});
|
|
4237
|
-
import
|
|
4362
|
+
import crypto6 from "crypto";
|
|
4238
4363
|
async function extractTrajectory(taskId, agentId) {
|
|
4239
4364
|
const client = getClient();
|
|
4240
4365
|
const result = await client.execute({
|
|
@@ -4263,11 +4388,11 @@ async function extractTrajectory(taskId, agentId) {
|
|
|
4263
4388
|
return signature;
|
|
4264
4389
|
}
|
|
4265
4390
|
function hashSignature(signature) {
|
|
4266
|
-
return
|
|
4391
|
+
return crypto6.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
|
|
4267
4392
|
}
|
|
4268
4393
|
async function storeTrajectory(opts) {
|
|
4269
4394
|
const client = getClient();
|
|
4270
|
-
const id =
|
|
4395
|
+
const id = crypto6.randomUUID();
|
|
4271
4396
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4272
4397
|
const signatureHash = hashSignature(opts.signature);
|
|
4273
4398
|
await client.execute({
|
|
@@ -4532,8 +4657,8 @@ __export(tasks_exports, {
|
|
|
4532
4657
|
updateTaskStatus: () => updateTaskStatus,
|
|
4533
4658
|
writeCheckpoint: () => writeCheckpoint
|
|
4534
4659
|
});
|
|
4535
|
-
import
|
|
4536
|
-
import { writeFileSync as
|
|
4660
|
+
import path16 from "path";
|
|
4661
|
+
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
|
|
4537
4662
|
async function createTask(input) {
|
|
4538
4663
|
const result = await createTaskCore(input);
|
|
4539
4664
|
if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -4552,12 +4677,12 @@ async function updateTask(input) {
|
|
|
4552
4677
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
4553
4678
|
try {
|
|
4554
4679
|
const agent = String(row.assigned_to);
|
|
4555
|
-
const cacheDir =
|
|
4556
|
-
const cachePath =
|
|
4680
|
+
const cacheDir = path16.join(EXE_AI_DIR, "session-cache");
|
|
4681
|
+
const cachePath = path16.join(cacheDir, `current-task-${agent}.json`);
|
|
4557
4682
|
if (input.status === "in_progress") {
|
|
4558
4683
|
mkdirSync5(cacheDir, { recursive: true });
|
|
4559
|
-
|
|
4560
|
-
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
4684
|
+
writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
4685
|
+
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
|
|
4561
4686
|
try {
|
|
4562
4687
|
unlinkSync5(cachePath);
|
|
4563
4688
|
} catch {
|
|
@@ -4565,10 +4690,10 @@ async function updateTask(input) {
|
|
|
4565
4690
|
}
|
|
4566
4691
|
} catch {
|
|
4567
4692
|
}
|
|
4568
|
-
if (input.status === "done") {
|
|
4693
|
+
if (input.status === "done" || input.status === "closed") {
|
|
4569
4694
|
await cleanupReviewFile(row, taskFile, input.baseDir);
|
|
4570
4695
|
}
|
|
4571
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
4696
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
4572
4697
|
try {
|
|
4573
4698
|
const client = getClient();
|
|
4574
4699
|
const taskTitle = String(row.title);
|
|
@@ -4584,7 +4709,7 @@ async function updateTask(input) {
|
|
|
4584
4709
|
if (!isCoordinatorName(assignedAgent)) {
|
|
4585
4710
|
try {
|
|
4586
4711
|
const draftClient = getClient();
|
|
4587
|
-
if (input.status === "done") {
|
|
4712
|
+
if (input.status === "done" || input.status === "closed") {
|
|
4588
4713
|
await draftClient.execute({
|
|
4589
4714
|
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
4590
4715
|
args: [assignedAgent]
|
|
@@ -4601,7 +4726,7 @@ async function updateTask(input) {
|
|
|
4601
4726
|
try {
|
|
4602
4727
|
const client = getClient();
|
|
4603
4728
|
const cascaded = await client.execute({
|
|
4604
|
-
sql: `UPDATE tasks SET status = '
|
|
4729
|
+
sql: `UPDATE tasks SET status = 'closed', updated_at = ?
|
|
4605
4730
|
WHERE parent_task_id = ? AND status = 'needs_review'`,
|
|
4606
4731
|
args: [now, taskId]
|
|
4607
4732
|
});
|
|
@@ -4614,14 +4739,14 @@ async function updateTask(input) {
|
|
|
4614
4739
|
} catch {
|
|
4615
4740
|
}
|
|
4616
4741
|
}
|
|
4617
|
-
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
4742
|
+
const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
|
|
4618
4743
|
if (isTerminal) {
|
|
4619
4744
|
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
4620
4745
|
if (!isCoordinator) {
|
|
4621
4746
|
notifyTaskDone();
|
|
4622
4747
|
}
|
|
4623
4748
|
await markTaskNotificationsRead(taskFile);
|
|
4624
|
-
if (input.status === "done") {
|
|
4749
|
+
if (input.status === "done" || input.status === "closed") {
|
|
4625
4750
|
try {
|
|
4626
4751
|
await cascadeUnblock(taskId, input.baseDir, now);
|
|
4627
4752
|
} catch {
|
|
@@ -4641,7 +4766,7 @@ async function updateTask(input) {
|
|
|
4641
4766
|
}
|
|
4642
4767
|
}
|
|
4643
4768
|
}
|
|
4644
|
-
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
4769
|
+
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
4645
4770
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
4646
4771
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
4647
4772
|
taskId,
|
|
@@ -5013,6 +5138,7 @@ __export(tmux_routing_exports, {
|
|
|
5013
5138
|
isEmployeeAlive: () => isEmployeeAlive,
|
|
5014
5139
|
isExeSession: () => isExeSession,
|
|
5015
5140
|
isSessionBusy: () => isSessionBusy,
|
|
5141
|
+
notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
|
|
5016
5142
|
notifyParentExe: () => notifyParentExe,
|
|
5017
5143
|
parseParentExe: () => parseParentExe,
|
|
5018
5144
|
registerParentExe: () => registerParentExe,
|
|
@@ -5023,13 +5149,13 @@ __export(tmux_routing_exports, {
|
|
|
5023
5149
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
5024
5150
|
});
|
|
5025
5151
|
import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
|
|
5026
|
-
import { readFileSync as
|
|
5027
|
-
import
|
|
5028
|
-
import
|
|
5152
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync6, existsSync as existsSync14, appendFileSync, readdirSync as readdirSync3 } from "fs";
|
|
5153
|
+
import path17 from "path";
|
|
5154
|
+
import os10 from "os";
|
|
5029
5155
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
5030
5156
|
import { unlinkSync as unlinkSync6 } from "fs";
|
|
5031
5157
|
function spawnLockPath(sessionName) {
|
|
5032
|
-
return
|
|
5158
|
+
return path17.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
5033
5159
|
}
|
|
5034
5160
|
function isProcessAlive(pid) {
|
|
5035
5161
|
try {
|
|
@@ -5040,13 +5166,13 @@ function isProcessAlive(pid) {
|
|
|
5040
5166
|
}
|
|
5041
5167
|
}
|
|
5042
5168
|
function acquireSpawnLock2(sessionName) {
|
|
5043
|
-
if (!
|
|
5169
|
+
if (!existsSync14(SPAWN_LOCK_DIR)) {
|
|
5044
5170
|
mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
|
|
5045
5171
|
}
|
|
5046
5172
|
const lockFile = spawnLockPath(sessionName);
|
|
5047
|
-
if (
|
|
5173
|
+
if (existsSync14(lockFile)) {
|
|
5048
5174
|
try {
|
|
5049
|
-
const lock = JSON.parse(
|
|
5175
|
+
const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
|
|
5050
5176
|
const age = Date.now() - lock.timestamp;
|
|
5051
5177
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
5052
5178
|
return false;
|
|
@@ -5054,7 +5180,7 @@ function acquireSpawnLock2(sessionName) {
|
|
|
5054
5180
|
} catch {
|
|
5055
5181
|
}
|
|
5056
5182
|
}
|
|
5057
|
-
|
|
5183
|
+
writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
5058
5184
|
return true;
|
|
5059
5185
|
}
|
|
5060
5186
|
function releaseSpawnLock2(sessionName) {
|
|
@@ -5066,13 +5192,13 @@ function releaseSpawnLock2(sessionName) {
|
|
|
5066
5192
|
function resolveBehaviorsExporterScript() {
|
|
5067
5193
|
try {
|
|
5068
5194
|
const thisFile = fileURLToPath3(import.meta.url);
|
|
5069
|
-
const scriptPath =
|
|
5070
|
-
|
|
5195
|
+
const scriptPath = path17.join(
|
|
5196
|
+
path17.dirname(thisFile),
|
|
5071
5197
|
"..",
|
|
5072
5198
|
"bin",
|
|
5073
5199
|
"exe-export-behaviors.js"
|
|
5074
5200
|
);
|
|
5075
|
-
return
|
|
5201
|
+
return existsSync14(scriptPath) ? scriptPath : null;
|
|
5076
5202
|
} catch {
|
|
5077
5203
|
return null;
|
|
5078
5204
|
}
|
|
@@ -5138,12 +5264,12 @@ function extractRootExe(name) {
|
|
|
5138
5264
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
5139
5265
|
}
|
|
5140
5266
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
5141
|
-
if (!
|
|
5267
|
+
if (!existsSync14(SESSION_CACHE)) {
|
|
5142
5268
|
mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
5143
5269
|
}
|
|
5144
5270
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
5145
|
-
const filePath =
|
|
5146
|
-
|
|
5271
|
+
const filePath = path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
5272
|
+
writeFileSync8(filePath, JSON.stringify({
|
|
5147
5273
|
parentExe: rootExe,
|
|
5148
5274
|
dispatchedBy: dispatchedBy || rootExe,
|
|
5149
5275
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -5151,7 +5277,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
5151
5277
|
}
|
|
5152
5278
|
function getParentExe(sessionKey) {
|
|
5153
5279
|
try {
|
|
5154
|
-
const data = JSON.parse(
|
|
5280
|
+
const data = JSON.parse(readFileSync12(path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
5155
5281
|
return data.parentExe || null;
|
|
5156
5282
|
} catch {
|
|
5157
5283
|
return null;
|
|
@@ -5159,8 +5285,8 @@ function getParentExe(sessionKey) {
|
|
|
5159
5285
|
}
|
|
5160
5286
|
function getDispatchedBy(sessionKey) {
|
|
5161
5287
|
try {
|
|
5162
|
-
const data = JSON.parse(
|
|
5163
|
-
|
|
5288
|
+
const data = JSON.parse(readFileSync12(
|
|
5289
|
+
path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
5164
5290
|
"utf8"
|
|
5165
5291
|
));
|
|
5166
5292
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -5230,8 +5356,8 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
5230
5356
|
}
|
|
5231
5357
|
function readDebounceState() {
|
|
5232
5358
|
try {
|
|
5233
|
-
if (!
|
|
5234
|
-
const raw = JSON.parse(
|
|
5359
|
+
if (!existsSync14(DEBOUNCE_FILE)) return {};
|
|
5360
|
+
const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
|
|
5235
5361
|
const state = {};
|
|
5236
5362
|
for (const [key, val] of Object.entries(raw)) {
|
|
5237
5363
|
if (typeof val === "number") {
|
|
@@ -5247,8 +5373,8 @@ function readDebounceState() {
|
|
|
5247
5373
|
}
|
|
5248
5374
|
function writeDebounceState(state) {
|
|
5249
5375
|
try {
|
|
5250
|
-
if (!
|
|
5251
|
-
|
|
5376
|
+
if (!existsSync14(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
5377
|
+
writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
|
|
5252
5378
|
} catch {
|
|
5253
5379
|
}
|
|
5254
5380
|
}
|
|
@@ -5346,8 +5472,8 @@ function sendIntercom(targetSession) {
|
|
|
5346
5472
|
try {
|
|
5347
5473
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
5348
5474
|
const agent = baseAgentName(rawAgent);
|
|
5349
|
-
const markerPath =
|
|
5350
|
-
if (
|
|
5475
|
+
const markerPath = path17.join(SESSION_CACHE, `current-task-${agent}.json`);
|
|
5476
|
+
if (existsSync14(markerPath)) {
|
|
5351
5477
|
logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
|
|
5352
5478
|
return "debounced";
|
|
5353
5479
|
}
|
|
@@ -5356,8 +5482,8 @@ function sendIntercom(targetSession) {
|
|
|
5356
5482
|
try {
|
|
5357
5483
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
5358
5484
|
const agent = baseAgentName(rawAgent);
|
|
5359
|
-
const taskDir =
|
|
5360
|
-
if (
|
|
5485
|
+
const taskDir = path17.join(process.cwd(), "exe", agent);
|
|
5486
|
+
if (existsSync14(taskDir)) {
|
|
5361
5487
|
const files = readdirSync3(taskDir).filter(
|
|
5362
5488
|
(f) => f.endsWith(".md") && f !== "DONE.txt"
|
|
5363
5489
|
);
|
|
@@ -5417,6 +5543,21 @@ function notifyParentExe(sessionKey) {
|
|
|
5417
5543
|
}
|
|
5418
5544
|
return true;
|
|
5419
5545
|
}
|
|
5546
|
+
function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
|
|
5547
|
+
const transport = getTransport();
|
|
5548
|
+
try {
|
|
5549
|
+
const sessions = transport.listSessions();
|
|
5550
|
+
if (!sessions.includes(coordinatorSession)) return false;
|
|
5551
|
+
execSync6(
|
|
5552
|
+
`tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
|
|
5553
|
+
{ timeout: 3e3 }
|
|
5554
|
+
);
|
|
5555
|
+
logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
|
|
5556
|
+
return true;
|
|
5557
|
+
} catch {
|
|
5558
|
+
return false;
|
|
5559
|
+
}
|
|
5560
|
+
}
|
|
5420
5561
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
5421
5562
|
if (isCoordinatorName(employeeName)) {
|
|
5422
5563
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
@@ -5490,26 +5631,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5490
5631
|
const transport = getTransport();
|
|
5491
5632
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
5492
5633
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
5493
|
-
const logDir =
|
|
5494
|
-
const logFile =
|
|
5495
|
-
if (!
|
|
5634
|
+
const logDir = path17.join(os10.homedir(), ".exe-os", "session-logs");
|
|
5635
|
+
const logFile = path17.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
5636
|
+
if (!existsSync14(logDir)) {
|
|
5496
5637
|
mkdirSync6(logDir, { recursive: true });
|
|
5497
5638
|
}
|
|
5498
5639
|
transport.kill(sessionName);
|
|
5499
5640
|
let cleanupSuffix = "";
|
|
5500
5641
|
try {
|
|
5501
5642
|
const thisFile = fileURLToPath3(import.meta.url);
|
|
5502
|
-
const cleanupScript =
|
|
5503
|
-
if (
|
|
5643
|
+
const cleanupScript = path17.join(path17.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
5644
|
+
if (existsSync14(cleanupScript)) {
|
|
5504
5645
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
5505
5646
|
}
|
|
5506
5647
|
} catch {
|
|
5507
5648
|
}
|
|
5508
5649
|
try {
|
|
5509
|
-
const claudeJsonPath =
|
|
5650
|
+
const claudeJsonPath = path17.join(os10.homedir(), ".claude.json");
|
|
5510
5651
|
let claudeJson = {};
|
|
5511
5652
|
try {
|
|
5512
|
-
claudeJson = JSON.parse(
|
|
5653
|
+
claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
|
|
5513
5654
|
} catch {
|
|
5514
5655
|
}
|
|
5515
5656
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -5517,17 +5658,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5517
5658
|
const trustDir = opts?.cwd ?? projectDir;
|
|
5518
5659
|
if (!projects[trustDir]) projects[trustDir] = {};
|
|
5519
5660
|
projects[trustDir].hasTrustDialogAccepted = true;
|
|
5520
|
-
|
|
5661
|
+
writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
5521
5662
|
} catch {
|
|
5522
5663
|
}
|
|
5523
5664
|
try {
|
|
5524
|
-
const settingsDir =
|
|
5665
|
+
const settingsDir = path17.join(os10.homedir(), ".claude", "projects");
|
|
5525
5666
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
5526
|
-
const projSettingsDir =
|
|
5527
|
-
const settingsPath =
|
|
5667
|
+
const projSettingsDir = path17.join(settingsDir, normalizedKey);
|
|
5668
|
+
const settingsPath = path17.join(projSettingsDir, "settings.json");
|
|
5528
5669
|
let settings = {};
|
|
5529
5670
|
try {
|
|
5530
|
-
settings = JSON.parse(
|
|
5671
|
+
settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
|
|
5531
5672
|
} catch {
|
|
5532
5673
|
}
|
|
5533
5674
|
const perms = settings.permissions ?? {};
|
|
@@ -5556,7 +5697,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5556
5697
|
perms.allow = allow;
|
|
5557
5698
|
settings.permissions = perms;
|
|
5558
5699
|
mkdirSync6(projSettingsDir, { recursive: true });
|
|
5559
|
-
|
|
5700
|
+
writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
5560
5701
|
}
|
|
5561
5702
|
} catch {
|
|
5562
5703
|
}
|
|
@@ -5571,8 +5712,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5571
5712
|
let behaviorsFlag = "";
|
|
5572
5713
|
let legacyFallbackWarned = false;
|
|
5573
5714
|
if (!useExeAgent && !useBinSymlink) {
|
|
5574
|
-
const identityPath =
|
|
5575
|
-
|
|
5715
|
+
const identityPath = path17.join(
|
|
5716
|
+
os10.homedir(),
|
|
5576
5717
|
".exe-os",
|
|
5577
5718
|
"identity",
|
|
5578
5719
|
`${employeeName}.md`
|
|
@@ -5581,13 +5722,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5581
5722
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
5582
5723
|
if (hasAgentFlag) {
|
|
5583
5724
|
identityFlag = ` --agent ${employeeName}`;
|
|
5584
|
-
} else if (
|
|
5725
|
+
} else if (existsSync14(identityPath)) {
|
|
5585
5726
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
5586
5727
|
legacyFallbackWarned = true;
|
|
5587
5728
|
}
|
|
5588
5729
|
const behaviorsFile = exportBehaviorsSync(
|
|
5589
5730
|
employeeName,
|
|
5590
|
-
|
|
5731
|
+
path17.basename(spawnCwd),
|
|
5591
5732
|
sessionName
|
|
5592
5733
|
);
|
|
5593
5734
|
if (behaviorsFile) {
|
|
@@ -5602,16 +5743,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5602
5743
|
}
|
|
5603
5744
|
let sessionContextFlag = "";
|
|
5604
5745
|
try {
|
|
5605
|
-
const ctxDir =
|
|
5746
|
+
const ctxDir = path17.join(os10.homedir(), ".exe-os", "session-cache");
|
|
5606
5747
|
mkdirSync6(ctxDir, { recursive: true });
|
|
5607
|
-
const ctxFile =
|
|
5748
|
+
const ctxFile = path17.join(ctxDir, `session-context-${sessionName}.md`);
|
|
5608
5749
|
const ctxContent = [
|
|
5609
5750
|
`## Session Context`,
|
|
5610
5751
|
`You are running in tmux session: ${sessionName}.`,
|
|
5611
5752
|
`Your parent coordinator session is ${exeSession}.`,
|
|
5612
5753
|
`Your employees (if any) use the -${exeSession} suffix.`
|
|
5613
5754
|
].join("\n");
|
|
5614
|
-
|
|
5755
|
+
writeFileSync8(ctxFile, ctxContent);
|
|
5615
5756
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
5616
5757
|
} catch {
|
|
5617
5758
|
}
|
|
@@ -5688,8 +5829,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5688
5829
|
transport.pipeLog(sessionName, logFile);
|
|
5689
5830
|
try {
|
|
5690
5831
|
const mySession = getMySession();
|
|
5691
|
-
const dispatchInfo =
|
|
5692
|
-
|
|
5832
|
+
const dispatchInfo = path17.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
5833
|
+
writeFileSync8(dispatchInfo, JSON.stringify({
|
|
5693
5834
|
dispatchedBy: mySession,
|
|
5694
5835
|
rootExe: exeSession,
|
|
5695
5836
|
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
|
|
@@ -5763,15 +5904,15 @@ var init_tmux_routing = __esm({
|
|
|
5763
5904
|
init_intercom_queue();
|
|
5764
5905
|
init_plan_limits();
|
|
5765
5906
|
init_employees();
|
|
5766
|
-
SPAWN_LOCK_DIR =
|
|
5767
|
-
SESSION_CACHE =
|
|
5907
|
+
SPAWN_LOCK_DIR = path17.join(os10.homedir(), ".exe-os", "spawn-locks");
|
|
5908
|
+
SESSION_CACHE = path17.join(os10.homedir(), ".exe-os", "session-cache");
|
|
5768
5909
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
5769
5910
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
5770
5911
|
VERIFY_PANE_LINES = 200;
|
|
5771
5912
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
5772
5913
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
5773
|
-
INTERCOM_LOG2 =
|
|
5774
|
-
DEBOUNCE_FILE =
|
|
5914
|
+
INTERCOM_LOG2 = path17.join(os10.homedir(), ".exe-os", "intercom.log");
|
|
5915
|
+
DEBOUNCE_FILE = path17.join(SESSION_CACHE, "intercom-debounce.json");
|
|
5775
5916
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
5776
5917
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
|
|
5777
5918
|
}
|
|
@@ -5794,6 +5935,15 @@ function sessionScopeFilter(sessionScope, tableAlias) {
|
|
|
5794
5935
|
args: [scope]
|
|
5795
5936
|
};
|
|
5796
5937
|
}
|
|
5938
|
+
function strictSessionScopeFilter(sessionScope, tableAlias) {
|
|
5939
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
5940
|
+
if (!scope) return { sql: "", args: [] };
|
|
5941
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
5942
|
+
return {
|
|
5943
|
+
sql: ` AND ${col} = ?`,
|
|
5944
|
+
args: [scope]
|
|
5945
|
+
};
|
|
5946
|
+
}
|
|
5797
5947
|
var init_task_scope = __esm({
|
|
5798
5948
|
"src/lib/task-scope.ts"() {
|
|
5799
5949
|
"use strict";
|
|
@@ -5812,14 +5962,14 @@ var init_memory = __esm({
|
|
|
5812
5962
|
|
|
5813
5963
|
// src/lib/keychain.ts
|
|
5814
5964
|
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
|
|
5965
|
+
import { existsSync as existsSync15 } from "fs";
|
|
5966
|
+
import path18 from "path";
|
|
5967
|
+
import os11 from "os";
|
|
5818
5968
|
function getKeyDir() {
|
|
5819
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
5969
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path18.join(os11.homedir(), ".exe-os");
|
|
5820
5970
|
}
|
|
5821
5971
|
function getKeyPath() {
|
|
5822
|
-
return
|
|
5972
|
+
return path18.join(getKeyDir(), "master.key");
|
|
5823
5973
|
}
|
|
5824
5974
|
async function tryKeytar() {
|
|
5825
5975
|
try {
|
|
@@ -5840,9 +5990,9 @@ async function getMasterKey() {
|
|
|
5840
5990
|
}
|
|
5841
5991
|
}
|
|
5842
5992
|
const keyPath = getKeyPath();
|
|
5843
|
-
if (!
|
|
5993
|
+
if (!existsSync15(keyPath)) {
|
|
5844
5994
|
process.stderr.write(
|
|
5845
|
-
`[keychain] Key not found at ${keyPath} (HOME=${
|
|
5995
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os11.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
5846
5996
|
`
|
|
5847
5997
|
);
|
|
5848
5998
|
return null;
|
|
@@ -5872,6 +6022,7 @@ var shard_manager_exports = {};
|
|
|
5872
6022
|
__export(shard_manager_exports, {
|
|
5873
6023
|
disposeShards: () => disposeShards,
|
|
5874
6024
|
ensureShardSchema: () => ensureShardSchema,
|
|
6025
|
+
getOpenShardCount: () => getOpenShardCount,
|
|
5875
6026
|
getReadyShardClient: () => getReadyShardClient,
|
|
5876
6027
|
getShardClient: () => getShardClient,
|
|
5877
6028
|
getShardsDir: () => getShardsDir,
|
|
@@ -5880,15 +6031,18 @@ __export(shard_manager_exports, {
|
|
|
5880
6031
|
listShards: () => listShards,
|
|
5881
6032
|
shardExists: () => shardExists
|
|
5882
6033
|
});
|
|
5883
|
-
import
|
|
5884
|
-
import { existsSync as
|
|
6034
|
+
import path19 from "path";
|
|
6035
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
|
|
5885
6036
|
import { createClient as createClient2 } from "@libsql/client";
|
|
5886
6037
|
function initShardManager(encryptionKey) {
|
|
5887
6038
|
_encryptionKey = encryptionKey;
|
|
5888
|
-
if (!
|
|
6039
|
+
if (!existsSync16(SHARDS_DIR)) {
|
|
5889
6040
|
mkdirSync7(SHARDS_DIR, { recursive: true });
|
|
5890
6041
|
}
|
|
5891
6042
|
_shardingEnabled = true;
|
|
6043
|
+
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
6044
|
+
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
6045
|
+
_evictionTimer.unref();
|
|
5892
6046
|
}
|
|
5893
6047
|
function isShardingEnabled() {
|
|
5894
6048
|
return _shardingEnabled;
|
|
@@ -5905,21 +6059,28 @@ function getShardClient(projectName) {
|
|
|
5905
6059
|
throw new Error(`Invalid project name for shard: "${projectName}"`);
|
|
5906
6060
|
}
|
|
5907
6061
|
const cached = _shards.get(safeName);
|
|
5908
|
-
if (cached)
|
|
5909
|
-
|
|
6062
|
+
if (cached) {
|
|
6063
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
6064
|
+
return cached;
|
|
6065
|
+
}
|
|
6066
|
+
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
6067
|
+
evictLRU();
|
|
6068
|
+
}
|
|
6069
|
+
const dbPath = path19.join(SHARDS_DIR, `${safeName}.db`);
|
|
5910
6070
|
const client = createClient2({
|
|
5911
6071
|
url: `file:${dbPath}`,
|
|
5912
6072
|
encryptionKey: _encryptionKey
|
|
5913
6073
|
});
|
|
5914
6074
|
_shards.set(safeName, client);
|
|
6075
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
5915
6076
|
return client;
|
|
5916
6077
|
}
|
|
5917
6078
|
function shardExists(projectName) {
|
|
5918
6079
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
5919
|
-
return
|
|
6080
|
+
return existsSync16(path19.join(SHARDS_DIR, `${safeName}.db`));
|
|
5920
6081
|
}
|
|
5921
6082
|
function listShards() {
|
|
5922
|
-
if (!
|
|
6083
|
+
if (!existsSync16(SHARDS_DIR)) return [];
|
|
5923
6084
|
return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
5924
6085
|
}
|
|
5925
6086
|
async function ensureShardSchema(client) {
|
|
@@ -5971,6 +6132,8 @@ async function ensureShardSchema(client) {
|
|
|
5971
6132
|
for (const col of [
|
|
5972
6133
|
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
5973
6134
|
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
6135
|
+
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
6136
|
+
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
5974
6137
|
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
5975
6138
|
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
5976
6139
|
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
@@ -6108,21 +6271,69 @@ async function getReadyShardClient(projectName) {
|
|
|
6108
6271
|
await ensureShardSchema(client);
|
|
6109
6272
|
return client;
|
|
6110
6273
|
}
|
|
6274
|
+
function evictLRU() {
|
|
6275
|
+
let oldest = null;
|
|
6276
|
+
let oldestTime = Infinity;
|
|
6277
|
+
for (const [name, time] of _shardLastAccess) {
|
|
6278
|
+
if (time < oldestTime) {
|
|
6279
|
+
oldestTime = time;
|
|
6280
|
+
oldest = name;
|
|
6281
|
+
}
|
|
6282
|
+
}
|
|
6283
|
+
if (oldest) {
|
|
6284
|
+
const client = _shards.get(oldest);
|
|
6285
|
+
if (client) {
|
|
6286
|
+
client.close();
|
|
6287
|
+
}
|
|
6288
|
+
_shards.delete(oldest);
|
|
6289
|
+
_shardLastAccess.delete(oldest);
|
|
6290
|
+
}
|
|
6291
|
+
}
|
|
6292
|
+
function evictIdleShards() {
|
|
6293
|
+
const now = Date.now();
|
|
6294
|
+
const toEvict = [];
|
|
6295
|
+
for (const [name, lastAccess] of _shardLastAccess) {
|
|
6296
|
+
if (now - lastAccess > SHARD_IDLE_MS) {
|
|
6297
|
+
toEvict.push(name);
|
|
6298
|
+
}
|
|
6299
|
+
}
|
|
6300
|
+
for (const name of toEvict) {
|
|
6301
|
+
const client = _shards.get(name);
|
|
6302
|
+
if (client) {
|
|
6303
|
+
client.close();
|
|
6304
|
+
}
|
|
6305
|
+
_shards.delete(name);
|
|
6306
|
+
_shardLastAccess.delete(name);
|
|
6307
|
+
}
|
|
6308
|
+
}
|
|
6309
|
+
function getOpenShardCount() {
|
|
6310
|
+
return _shards.size;
|
|
6311
|
+
}
|
|
6111
6312
|
function disposeShards() {
|
|
6313
|
+
if (_evictionTimer) {
|
|
6314
|
+
clearInterval(_evictionTimer);
|
|
6315
|
+
_evictionTimer = null;
|
|
6316
|
+
}
|
|
6112
6317
|
for (const [, client] of _shards) {
|
|
6113
6318
|
client.close();
|
|
6114
6319
|
}
|
|
6115
6320
|
_shards.clear();
|
|
6321
|
+
_shardLastAccess.clear();
|
|
6116
6322
|
_shardingEnabled = false;
|
|
6117
6323
|
_encryptionKey = null;
|
|
6118
6324
|
}
|
|
6119
|
-
var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
|
|
6325
|
+
var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
|
|
6120
6326
|
var init_shard_manager = __esm({
|
|
6121
6327
|
"src/lib/shard-manager.ts"() {
|
|
6122
6328
|
"use strict";
|
|
6123
6329
|
init_config();
|
|
6124
|
-
SHARDS_DIR =
|
|
6330
|
+
SHARDS_DIR = path19.join(EXE_AI_DIR, "shards");
|
|
6331
|
+
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
6332
|
+
MAX_OPEN_SHARDS = 10;
|
|
6333
|
+
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
6125
6334
|
_shards = /* @__PURE__ */ new Map();
|
|
6335
|
+
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
6336
|
+
_evictionTimer = null;
|
|
6126
6337
|
_encryptionKey = null;
|
|
6127
6338
|
_shardingEnabled = false;
|
|
6128
6339
|
}
|
|
@@ -6886,9 +7097,9 @@ var init_store = __esm({
|
|
|
6886
7097
|
});
|
|
6887
7098
|
|
|
6888
7099
|
// src/bin/scan-tasks.ts
|
|
6889
|
-
import { existsSync as
|
|
6890
|
-
import
|
|
6891
|
-
import
|
|
7100
|
+
import { existsSync as existsSync17, readFileSync as readFileSync13 } from "fs";
|
|
7101
|
+
import path20 from "path";
|
|
7102
|
+
import os12 from "os";
|
|
6892
7103
|
|
|
6893
7104
|
// src/lib/is-main.ts
|
|
6894
7105
|
import { realpathSync } from "fs";
|
|
@@ -6908,27 +7119,27 @@ function isMainModule(importMetaUrl) {
|
|
|
6908
7119
|
// src/bin/scan-tasks.ts
|
|
6909
7120
|
init_session_key();
|
|
6910
7121
|
init_task_scope();
|
|
6911
|
-
function getMcpHealthWarning(runtime = getSessionRuntime(), homeDir =
|
|
7122
|
+
function getMcpHealthWarning(runtime = getSessionRuntime(), homeDir = os12.homedir()) {
|
|
6912
7123
|
if (runtime === "codex") {
|
|
6913
7124
|
return null;
|
|
6914
7125
|
}
|
|
6915
7126
|
try {
|
|
6916
7127
|
if (runtime === "opencode") {
|
|
6917
|
-
const opencodeJson =
|
|
6918
|
-
if (!
|
|
7128
|
+
const opencodeJson = path20.join(homeDir, ".config", "opencode", "opencode.json");
|
|
7129
|
+
if (!existsSync17(opencodeJson)) {
|
|
6919
7130
|
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
7131
|
}
|
|
6921
|
-
const config2 = JSON.parse(
|
|
7132
|
+
const config2 = JSON.parse(readFileSync13(opencodeJson, "utf8"));
|
|
6922
7133
|
if (!config2.mcp?.["exe-os"]?.enabled) {
|
|
6923
7134
|
return "\u26A0\uFE0F MCP task tools not available \u2014 exe-os server is not enabled in ~/.config/opencode/opencode.json.\n";
|
|
6924
7135
|
}
|
|
6925
7136
|
return null;
|
|
6926
7137
|
}
|
|
6927
|
-
const claudeJson =
|
|
6928
|
-
if (!
|
|
7138
|
+
const claudeJson = path20.join(homeDir, ".claude.json");
|
|
7139
|
+
if (!existsSync17(claudeJson)) {
|
|
6929
7140
|
return "\u26A0\uFE0F MCP config missing (~/.claude.json not found) \u2014 close_task won't work. Run /exe-setup\n";
|
|
6930
7141
|
}
|
|
6931
|
-
const config = JSON.parse(
|
|
7142
|
+
const config = JSON.parse(readFileSync13(claudeJson, "utf8"));
|
|
6932
7143
|
const servers = config.mcpServers;
|
|
6933
7144
|
if (!servers?.["exe-os"] && !servers?.["exe-mem"]) {
|
|
6934
7145
|
return "\u26A0\uFE0F MCP task tools not available \u2014 exe-os server not configured in ~/.claude.json. close_task won't work.\n";
|