@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
|
@@ -307,9 +307,47 @@ var init_provider_table = __esm({
|
|
|
307
307
|
}
|
|
308
308
|
});
|
|
309
309
|
|
|
310
|
+
// src/lib/secure-files.ts
|
|
311
|
+
import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
312
|
+
import { chmod, mkdir } from "fs/promises";
|
|
313
|
+
async function ensurePrivateDir(dirPath) {
|
|
314
|
+
await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
315
|
+
try {
|
|
316
|
+
await chmod(dirPath, PRIVATE_DIR_MODE);
|
|
317
|
+
} catch {
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
function ensurePrivateDirSync(dirPath) {
|
|
321
|
+
mkdirSync2(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
322
|
+
try {
|
|
323
|
+
chmodSync(dirPath, PRIVATE_DIR_MODE);
|
|
324
|
+
} catch {
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
async function enforcePrivateFile(filePath) {
|
|
328
|
+
try {
|
|
329
|
+
await chmod(filePath, PRIVATE_FILE_MODE);
|
|
330
|
+
} catch {
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
function enforcePrivateFileSync(filePath) {
|
|
334
|
+
try {
|
|
335
|
+
if (existsSync2(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
|
|
336
|
+
} catch {
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
|
|
340
|
+
var init_secure_files = __esm({
|
|
341
|
+
"src/lib/secure-files.ts"() {
|
|
342
|
+
"use strict";
|
|
343
|
+
PRIVATE_DIR_MODE = 448;
|
|
344
|
+
PRIVATE_FILE_MODE = 384;
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
|
|
310
348
|
// src/lib/config.ts
|
|
311
|
-
import { readFile, writeFile
|
|
312
|
-
import { readFileSync as readFileSync2, existsSync as
|
|
349
|
+
import { readFile, writeFile } from "fs/promises";
|
|
350
|
+
import { readFileSync as readFileSync2, existsSync as existsSync3, renameSync } from "fs";
|
|
313
351
|
import path2 from "path";
|
|
314
352
|
import os2 from "os";
|
|
315
353
|
function resolveDataDir() {
|
|
@@ -317,7 +355,7 @@ function resolveDataDir() {
|
|
|
317
355
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
318
356
|
const newDir = path2.join(os2.homedir(), ".exe-os");
|
|
319
357
|
const legacyDir = path2.join(os2.homedir(), ".exe-mem");
|
|
320
|
-
if (!
|
|
358
|
+
if (!existsSync3(newDir) && existsSync3(legacyDir)) {
|
|
321
359
|
try {
|
|
322
360
|
renameSync(legacyDir, newDir);
|
|
323
361
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -380,9 +418,9 @@ function normalizeAutoUpdate(raw) {
|
|
|
380
418
|
}
|
|
381
419
|
async function loadConfig() {
|
|
382
420
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
383
|
-
await
|
|
421
|
+
await ensurePrivateDir(dir);
|
|
384
422
|
const configPath = path2.join(dir, "config.json");
|
|
385
|
-
if (!
|
|
423
|
+
if (!existsSync3(configPath)) {
|
|
386
424
|
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
387
425
|
}
|
|
388
426
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -395,6 +433,7 @@ async function loadConfig() {
|
|
|
395
433
|
`);
|
|
396
434
|
try {
|
|
397
435
|
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
436
|
+
await enforcePrivateFile(configPath);
|
|
398
437
|
} catch {
|
|
399
438
|
}
|
|
400
439
|
}
|
|
@@ -414,6 +453,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
|
|
|
414
453
|
var init_config = __esm({
|
|
415
454
|
"src/lib/config.ts"() {
|
|
416
455
|
"use strict";
|
|
456
|
+
init_secure_files();
|
|
417
457
|
EXE_AI_DIR = resolveDataDir();
|
|
418
458
|
DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
|
|
419
459
|
MODELS_DIR = path2.join(EXE_AI_DIR, "models");
|
|
@@ -518,10 +558,10 @@ var init_runtime_table = __esm({
|
|
|
518
558
|
});
|
|
519
559
|
|
|
520
560
|
// src/lib/agent-config.ts
|
|
521
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as
|
|
561
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync4 } from "fs";
|
|
522
562
|
import path3 from "path";
|
|
523
563
|
function loadAgentConfig() {
|
|
524
|
-
if (!
|
|
564
|
+
if (!existsSync4(AGENT_CONFIG_PATH)) return {};
|
|
525
565
|
try {
|
|
526
566
|
return JSON.parse(readFileSync3(AGENT_CONFIG_PATH, "utf-8"));
|
|
527
567
|
} catch {
|
|
@@ -542,6 +582,7 @@ var init_agent_config = __esm({
|
|
|
542
582
|
"use strict";
|
|
543
583
|
init_config();
|
|
544
584
|
init_runtime_table();
|
|
585
|
+
init_secure_files();
|
|
545
586
|
AGENT_CONFIG_PATH = path3.join(EXE_AI_DIR, "agent-config.json");
|
|
546
587
|
DEFAULT_MODELS = {
|
|
547
588
|
claude: "claude-opus-4",
|
|
@@ -560,16 +601,16 @@ __export(intercom_queue_exports, {
|
|
|
560
601
|
queueIntercom: () => queueIntercom,
|
|
561
602
|
readQueue: () => readQueue
|
|
562
603
|
});
|
|
563
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as
|
|
604
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
|
|
564
605
|
import path4 from "path";
|
|
565
606
|
import os3 from "os";
|
|
566
607
|
function ensureDir() {
|
|
567
608
|
const dir = path4.dirname(QUEUE_PATH);
|
|
568
|
-
if (!
|
|
609
|
+
if (!existsSync5(dir)) mkdirSync3(dir, { recursive: true });
|
|
569
610
|
}
|
|
570
611
|
function readQueue() {
|
|
571
612
|
try {
|
|
572
|
-
if (!
|
|
613
|
+
if (!existsSync5(QUEUE_PATH)) return [];
|
|
573
614
|
return JSON.parse(readFileSync4(QUEUE_PATH, "utf8"));
|
|
574
615
|
} catch {
|
|
575
616
|
return [];
|
|
@@ -734,7 +775,7 @@ var init_db_retry = __esm({
|
|
|
734
775
|
|
|
735
776
|
// src/lib/employees.ts
|
|
736
777
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
737
|
-
import { existsSync as
|
|
778
|
+
import { existsSync as existsSync6, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
|
|
738
779
|
import { execSync as execSync3 } from "child_process";
|
|
739
780
|
import path5 from "path";
|
|
740
781
|
import os4 from "os";
|
|
@@ -755,7 +796,7 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
|
755
796
|
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
756
797
|
}
|
|
757
798
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
758
|
-
if (!
|
|
799
|
+
if (!existsSync6(employeesPath)) return [];
|
|
759
800
|
try {
|
|
760
801
|
return JSON.parse(readFileSync5(employeesPath, "utf-8"));
|
|
761
802
|
} catch {
|
|
@@ -1376,13 +1417,50 @@ var init_database_adapter = __esm({
|
|
|
1376
1417
|
}
|
|
1377
1418
|
});
|
|
1378
1419
|
|
|
1420
|
+
// src/lib/daemon-auth.ts
|
|
1421
|
+
import crypto from "crypto";
|
|
1422
|
+
import path7 from "path";
|
|
1423
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
1424
|
+
function normalizeToken(token) {
|
|
1425
|
+
if (!token) return null;
|
|
1426
|
+
const trimmed = token.trim();
|
|
1427
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
1428
|
+
}
|
|
1429
|
+
function readDaemonToken() {
|
|
1430
|
+
try {
|
|
1431
|
+
if (!existsSync7(DAEMON_TOKEN_PATH)) return null;
|
|
1432
|
+
return normalizeToken(readFileSync6(DAEMON_TOKEN_PATH, "utf8"));
|
|
1433
|
+
} catch {
|
|
1434
|
+
return null;
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
function ensureDaemonToken(seed) {
|
|
1438
|
+
const existing = readDaemonToken();
|
|
1439
|
+
if (existing) return existing;
|
|
1440
|
+
const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
|
|
1441
|
+
ensurePrivateDirSync(EXE_AI_DIR);
|
|
1442
|
+
writeFileSync5(DAEMON_TOKEN_PATH, `${token}
|
|
1443
|
+
`, "utf8");
|
|
1444
|
+
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
1445
|
+
return token;
|
|
1446
|
+
}
|
|
1447
|
+
var DAEMON_TOKEN_PATH;
|
|
1448
|
+
var init_daemon_auth = __esm({
|
|
1449
|
+
"src/lib/daemon-auth.ts"() {
|
|
1450
|
+
"use strict";
|
|
1451
|
+
init_config();
|
|
1452
|
+
init_secure_files();
|
|
1453
|
+
DAEMON_TOKEN_PATH = path7.join(EXE_AI_DIR, "exed.token");
|
|
1454
|
+
}
|
|
1455
|
+
});
|
|
1456
|
+
|
|
1379
1457
|
// src/lib/exe-daemon-client.ts
|
|
1380
1458
|
import net from "net";
|
|
1381
1459
|
import os6 from "os";
|
|
1382
1460
|
import { spawn } from "child_process";
|
|
1383
1461
|
import { randomUUID } from "crypto";
|
|
1384
|
-
import { existsSync as
|
|
1385
|
-
import
|
|
1462
|
+
import { existsSync as existsSync8, unlinkSync as unlinkSync2, readFileSync as readFileSync7, openSync, closeSync, statSync } from "fs";
|
|
1463
|
+
import path8 from "path";
|
|
1386
1464
|
import { fileURLToPath } from "url";
|
|
1387
1465
|
function handleData(chunk) {
|
|
1388
1466
|
_buffer += chunk.toString();
|
|
@@ -1410,9 +1488,9 @@ function handleData(chunk) {
|
|
|
1410
1488
|
}
|
|
1411
1489
|
}
|
|
1412
1490
|
function cleanupStaleFiles() {
|
|
1413
|
-
if (
|
|
1491
|
+
if (existsSync8(PID_PATH)) {
|
|
1414
1492
|
try {
|
|
1415
|
-
const pid = parseInt(
|
|
1493
|
+
const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
|
|
1416
1494
|
if (pid > 0) {
|
|
1417
1495
|
try {
|
|
1418
1496
|
process.kill(pid, 0);
|
|
@@ -1433,11 +1511,11 @@ function cleanupStaleFiles() {
|
|
|
1433
1511
|
}
|
|
1434
1512
|
}
|
|
1435
1513
|
function findPackageRoot() {
|
|
1436
|
-
let dir =
|
|
1437
|
-
const { root } =
|
|
1514
|
+
let dir = path8.dirname(fileURLToPath(import.meta.url));
|
|
1515
|
+
const { root } = path8.parse(dir);
|
|
1438
1516
|
while (dir !== root) {
|
|
1439
|
-
if (
|
|
1440
|
-
dir =
|
|
1517
|
+
if (existsSync8(path8.join(dir, "package.json"))) return dir;
|
|
1518
|
+
dir = path8.dirname(dir);
|
|
1441
1519
|
}
|
|
1442
1520
|
return null;
|
|
1443
1521
|
}
|
|
@@ -1463,16 +1541,17 @@ function spawnDaemon() {
|
|
|
1463
1541
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
1464
1542
|
return;
|
|
1465
1543
|
}
|
|
1466
|
-
const daemonPath =
|
|
1467
|
-
if (!
|
|
1544
|
+
const daemonPath = path8.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
1545
|
+
if (!existsSync8(daemonPath)) {
|
|
1468
1546
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
1469
1547
|
`);
|
|
1470
1548
|
return;
|
|
1471
1549
|
}
|
|
1472
1550
|
const resolvedPath = daemonPath;
|
|
1551
|
+
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
1473
1552
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
1474
1553
|
`);
|
|
1475
|
-
const logPath =
|
|
1554
|
+
const logPath = path8.join(path8.dirname(SOCKET_PATH), "exed.log");
|
|
1476
1555
|
let stderrFd = "ignore";
|
|
1477
1556
|
try {
|
|
1478
1557
|
stderrFd = openSync(logPath, "a");
|
|
@@ -1490,7 +1569,8 @@ function spawnDaemon() {
|
|
|
1490
1569
|
TMUX_PANE: void 0,
|
|
1491
1570
|
// Prevents resolveExeSession() from scoping to one session
|
|
1492
1571
|
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
1493
|
-
EXE_DAEMON_PID: PID_PATH
|
|
1572
|
+
EXE_DAEMON_PID: PID_PATH,
|
|
1573
|
+
[DAEMON_TOKEN_ENV]: daemonToken
|
|
1494
1574
|
}
|
|
1495
1575
|
});
|
|
1496
1576
|
child.unref();
|
|
@@ -1597,13 +1677,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
1597
1677
|
return;
|
|
1598
1678
|
}
|
|
1599
1679
|
const id = randomUUID();
|
|
1680
|
+
const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
|
|
1600
1681
|
const timer = setTimeout(() => {
|
|
1601
1682
|
_pending.delete(id);
|
|
1602
1683
|
resolve({ error: "Request timeout" });
|
|
1603
1684
|
}, timeoutMs);
|
|
1604
1685
|
_pending.set(id, { resolve, timer });
|
|
1605
1686
|
try {
|
|
1606
|
-
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
1687
|
+
_socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
|
|
1607
1688
|
} catch {
|
|
1608
1689
|
clearTimeout(timer);
|
|
1609
1690
|
_pending.delete(id);
|
|
@@ -1614,17 +1695,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
1614
1695
|
function isClientConnected() {
|
|
1615
1696
|
return _connected;
|
|
1616
1697
|
}
|
|
1617
|
-
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
|
|
1698
|
+
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, DAEMON_TOKEN_ENV, _socket, _connected, _buffer, _pending, MAX_BUFFER;
|
|
1618
1699
|
var init_exe_daemon_client = __esm({
|
|
1619
1700
|
"src/lib/exe-daemon-client.ts"() {
|
|
1620
1701
|
"use strict";
|
|
1621
1702
|
init_config();
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1703
|
+
init_daemon_auth();
|
|
1704
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path8.join(EXE_AI_DIR, "exed.sock");
|
|
1705
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path8.join(EXE_AI_DIR, "exed.pid");
|
|
1706
|
+
SPAWN_LOCK_PATH = path8.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
1625
1707
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
1626
1708
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
1627
1709
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
1710
|
+
DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
|
|
1628
1711
|
_socket = null;
|
|
1629
1712
|
_connected = false;
|
|
1630
1713
|
_buffer = "";
|
|
@@ -2203,6 +2286,7 @@ async function ensureSchema() {
|
|
|
2203
2286
|
project TEXT NOT NULL,
|
|
2204
2287
|
summary TEXT NOT NULL,
|
|
2205
2288
|
task_file TEXT,
|
|
2289
|
+
session_scope TEXT,
|
|
2206
2290
|
read INTEGER NOT NULL DEFAULT 0,
|
|
2207
2291
|
created_at TEXT NOT NULL
|
|
2208
2292
|
);
|
|
@@ -2211,7 +2295,7 @@ async function ensureSchema() {
|
|
|
2211
2295
|
ON notifications(read);
|
|
2212
2296
|
|
|
2213
2297
|
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
2214
|
-
ON notifications(agent_id);
|
|
2298
|
+
ON notifications(agent_id, session_scope);
|
|
2215
2299
|
|
|
2216
2300
|
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
2217
2301
|
ON notifications(task_file);
|
|
@@ -2249,6 +2333,7 @@ async function ensureSchema() {
|
|
|
2249
2333
|
target_agent TEXT NOT NULL,
|
|
2250
2334
|
target_project TEXT,
|
|
2251
2335
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
2336
|
+
session_scope TEXT,
|
|
2252
2337
|
content TEXT NOT NULL,
|
|
2253
2338
|
priority TEXT DEFAULT 'normal',
|
|
2254
2339
|
status TEXT DEFAULT 'pending',
|
|
@@ -2262,10 +2347,31 @@ async function ensureSchema() {
|
|
|
2262
2347
|
);
|
|
2263
2348
|
|
|
2264
2349
|
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
2265
|
-
ON messages(target_agent, status);
|
|
2350
|
+
ON messages(target_agent, session_scope, status);
|
|
2266
2351
|
|
|
2267
2352
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
2268
|
-
ON messages(target_agent, from_agent, server_seq);
|
|
2353
|
+
ON messages(target_agent, session_scope, from_agent, server_seq);
|
|
2354
|
+
`);
|
|
2355
|
+
try {
|
|
2356
|
+
await client.execute({
|
|
2357
|
+
sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
|
|
2358
|
+
args: []
|
|
2359
|
+
});
|
|
2360
|
+
} catch {
|
|
2361
|
+
}
|
|
2362
|
+
try {
|
|
2363
|
+
await client.execute({
|
|
2364
|
+
sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
|
|
2365
|
+
args: []
|
|
2366
|
+
});
|
|
2367
|
+
} catch {
|
|
2368
|
+
}
|
|
2369
|
+
await client.executeMultiple(`
|
|
2370
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
|
|
2371
|
+
ON notifications(agent_id, session_scope, read, created_at);
|
|
2372
|
+
|
|
2373
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
|
|
2374
|
+
ON messages(target_agent, session_scope, status, created_at);
|
|
2269
2375
|
`);
|
|
2270
2376
|
try {
|
|
2271
2377
|
await client.execute({
|
|
@@ -2849,6 +2955,13 @@ async function ensureSchema() {
|
|
|
2849
2955
|
} catch {
|
|
2850
2956
|
}
|
|
2851
2957
|
}
|
|
2958
|
+
try {
|
|
2959
|
+
await client.execute({
|
|
2960
|
+
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
2961
|
+
args: []
|
|
2962
|
+
});
|
|
2963
|
+
} catch {
|
|
2964
|
+
}
|
|
2852
2965
|
}
|
|
2853
2966
|
async function disposeDatabase() {
|
|
2854
2967
|
if (_walCheckpointTimer) {
|
|
@@ -2887,18 +3000,21 @@ var init_database = __esm({
|
|
|
2887
3000
|
});
|
|
2888
3001
|
|
|
2889
3002
|
// src/lib/license.ts
|
|
2890
|
-
import { readFileSync as
|
|
3003
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync9, mkdirSync as mkdirSync4 } from "fs";
|
|
2891
3004
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
2892
|
-
import
|
|
3005
|
+
import { createRequire as createRequire2 } from "module";
|
|
3006
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
3007
|
+
import os7 from "os";
|
|
3008
|
+
import path9 from "path";
|
|
2893
3009
|
import { jwtVerify, importSPKI } from "jose";
|
|
2894
3010
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
|
|
2895
3011
|
var init_license = __esm({
|
|
2896
3012
|
"src/lib/license.ts"() {
|
|
2897
3013
|
"use strict";
|
|
2898
3014
|
init_config();
|
|
2899
|
-
LICENSE_PATH =
|
|
2900
|
-
CACHE_PATH =
|
|
2901
|
-
DEVICE_ID_PATH =
|
|
3015
|
+
LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
|
|
3016
|
+
CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
|
|
3017
|
+
DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
|
|
2902
3018
|
PLAN_LIMITS = {
|
|
2903
3019
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
2904
3020
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -2910,12 +3026,12 @@ var init_license = __esm({
|
|
|
2910
3026
|
});
|
|
2911
3027
|
|
|
2912
3028
|
// src/lib/plan-limits.ts
|
|
2913
|
-
import { readFileSync as
|
|
2914
|
-
import
|
|
3029
|
+
import { readFileSync as readFileSync9, existsSync as existsSync10 } from "fs";
|
|
3030
|
+
import path10 from "path";
|
|
2915
3031
|
function getLicenseSync() {
|
|
2916
3032
|
try {
|
|
2917
|
-
if (!
|
|
2918
|
-
const raw = JSON.parse(
|
|
3033
|
+
if (!existsSync10(CACHE_PATH2)) return freeLicense();
|
|
3034
|
+
const raw = JSON.parse(readFileSync9(CACHE_PATH2, "utf8"));
|
|
2919
3035
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
2920
3036
|
const parts = raw.token.split(".");
|
|
2921
3037
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -2953,8 +3069,8 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
2953
3069
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
2954
3070
|
let count = 0;
|
|
2955
3071
|
try {
|
|
2956
|
-
if (
|
|
2957
|
-
const raw =
|
|
3072
|
+
if (existsSync10(filePath)) {
|
|
3073
|
+
const raw = readFileSync9(filePath, "utf8");
|
|
2958
3074
|
const employees = JSON.parse(raw);
|
|
2959
3075
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
2960
3076
|
}
|
|
@@ -2983,29 +3099,30 @@ var init_plan_limits = __esm({
|
|
|
2983
3099
|
this.name = "PlanLimitError";
|
|
2984
3100
|
}
|
|
2985
3101
|
};
|
|
2986
|
-
CACHE_PATH2 =
|
|
3102
|
+
CACHE_PATH2 = path10.join(EXE_AI_DIR, "license-cache.json");
|
|
2987
3103
|
}
|
|
2988
3104
|
});
|
|
2989
3105
|
|
|
2990
3106
|
// src/lib/notifications.ts
|
|
2991
|
-
import
|
|
2992
|
-
import
|
|
2993
|
-
import
|
|
3107
|
+
import crypto2 from "crypto";
|
|
3108
|
+
import path11 from "path";
|
|
3109
|
+
import os8 from "os";
|
|
2994
3110
|
import {
|
|
2995
|
-
readFileSync as
|
|
3111
|
+
readFileSync as readFileSync10,
|
|
2996
3112
|
readdirSync,
|
|
2997
3113
|
unlinkSync as unlinkSync3,
|
|
2998
|
-
existsSync as
|
|
3114
|
+
existsSync as existsSync11,
|
|
2999
3115
|
rmdirSync
|
|
3000
3116
|
} from "fs";
|
|
3001
3117
|
async function writeNotification(notification) {
|
|
3002
3118
|
try {
|
|
3003
3119
|
const client = getClient();
|
|
3004
|
-
const id =
|
|
3120
|
+
const id = crypto2.randomUUID();
|
|
3005
3121
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3122
|
+
const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
|
|
3006
3123
|
await client.execute({
|
|
3007
|
-
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
|
|
3008
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
3124
|
+
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
3125
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
3009
3126
|
args: [
|
|
3010
3127
|
id,
|
|
3011
3128
|
notification.agentId,
|
|
@@ -3014,6 +3131,7 @@ async function writeNotification(notification) {
|
|
|
3014
3131
|
notification.project,
|
|
3015
3132
|
notification.summary,
|
|
3016
3133
|
notification.taskFile ?? null,
|
|
3134
|
+
sessionScope,
|
|
3017
3135
|
now
|
|
3018
3136
|
]
|
|
3019
3137
|
});
|
|
@@ -3022,12 +3140,14 @@ async function writeNotification(notification) {
|
|
|
3022
3140
|
`);
|
|
3023
3141
|
}
|
|
3024
3142
|
}
|
|
3025
|
-
async function markAsReadByTaskFile(taskFile) {
|
|
3143
|
+
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
3026
3144
|
try {
|
|
3027
3145
|
const client = getClient();
|
|
3146
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
3028
3147
|
await client.execute({
|
|
3029
|
-
sql:
|
|
3030
|
-
|
|
3148
|
+
sql: `UPDATE notifications SET read = 1
|
|
3149
|
+
WHERE task_file = ? AND read = 0${scope.sql}`,
|
|
3150
|
+
args: [taskFile, ...scope.args]
|
|
3031
3151
|
});
|
|
3032
3152
|
} catch {
|
|
3033
3153
|
}
|
|
@@ -3036,11 +3156,12 @@ var init_notifications = __esm({
|
|
|
3036
3156
|
"src/lib/notifications.ts"() {
|
|
3037
3157
|
"use strict";
|
|
3038
3158
|
init_database();
|
|
3159
|
+
init_task_scope();
|
|
3039
3160
|
}
|
|
3040
3161
|
});
|
|
3041
3162
|
|
|
3042
3163
|
// src/lib/session-kill-telemetry.ts
|
|
3043
|
-
import
|
|
3164
|
+
import crypto3 from "crypto";
|
|
3044
3165
|
async function recordSessionKill(input) {
|
|
3045
3166
|
try {
|
|
3046
3167
|
const client = getClient();
|
|
@@ -3050,7 +3171,7 @@ async function recordSessionKill(input) {
|
|
|
3050
3171
|
ticks_idle, estimated_tokens_saved)
|
|
3051
3172
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
3052
3173
|
args: [
|
|
3053
|
-
|
|
3174
|
+
crypto3.randomUUID(),
|
|
3054
3175
|
input.sessionName,
|
|
3055
3176
|
input.agentId,
|
|
3056
3177
|
(/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -3128,6 +3249,110 @@ var init_state_bus = __esm({
|
|
|
3128
3249
|
}
|
|
3129
3250
|
});
|
|
3130
3251
|
|
|
3252
|
+
// src/lib/project-name.ts
|
|
3253
|
+
import { execSync as execSync4 } from "child_process";
|
|
3254
|
+
import path12 from "path";
|
|
3255
|
+
function getProjectName(cwd) {
|
|
3256
|
+
const dir = cwd ?? process.cwd();
|
|
3257
|
+
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
3258
|
+
try {
|
|
3259
|
+
let repoRoot;
|
|
3260
|
+
try {
|
|
3261
|
+
const gitCommonDir = execSync4("git rev-parse --path-format=absolute --git-common-dir", {
|
|
3262
|
+
cwd: dir,
|
|
3263
|
+
encoding: "utf8",
|
|
3264
|
+
timeout: 2e3,
|
|
3265
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3266
|
+
}).trim();
|
|
3267
|
+
repoRoot = path12.dirname(gitCommonDir);
|
|
3268
|
+
} catch {
|
|
3269
|
+
repoRoot = execSync4("git rev-parse --show-toplevel", {
|
|
3270
|
+
cwd: dir,
|
|
3271
|
+
encoding: "utf8",
|
|
3272
|
+
timeout: 2e3,
|
|
3273
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3274
|
+
}).trim();
|
|
3275
|
+
}
|
|
3276
|
+
_cached2 = path12.basename(repoRoot);
|
|
3277
|
+
_cachedCwd = dir;
|
|
3278
|
+
return _cached2;
|
|
3279
|
+
} catch {
|
|
3280
|
+
_cached2 = path12.basename(dir);
|
|
3281
|
+
_cachedCwd = dir;
|
|
3282
|
+
return _cached2;
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
3285
|
+
var _cached2, _cachedCwd;
|
|
3286
|
+
var init_project_name = __esm({
|
|
3287
|
+
"src/lib/project-name.ts"() {
|
|
3288
|
+
"use strict";
|
|
3289
|
+
_cached2 = null;
|
|
3290
|
+
_cachedCwd = null;
|
|
3291
|
+
}
|
|
3292
|
+
});
|
|
3293
|
+
|
|
3294
|
+
// src/lib/session-scope.ts
|
|
3295
|
+
var session_scope_exports = {};
|
|
3296
|
+
__export(session_scope_exports, {
|
|
3297
|
+
assertSessionScope: () => assertSessionScope,
|
|
3298
|
+
findSessionForProject: () => findSessionForProject,
|
|
3299
|
+
getSessionProject: () => getSessionProject
|
|
3300
|
+
});
|
|
3301
|
+
function getSessionProject(sessionName) {
|
|
3302
|
+
const sessions = listSessions();
|
|
3303
|
+
const entry = sessions.find((s) => s.windowName === sessionName);
|
|
3304
|
+
if (!entry) return null;
|
|
3305
|
+
const parts = entry.projectDir.split("/").filter(Boolean);
|
|
3306
|
+
return parts[parts.length - 1] ?? null;
|
|
3307
|
+
}
|
|
3308
|
+
function findSessionForProject(projectName) {
|
|
3309
|
+
const sessions = listSessions();
|
|
3310
|
+
for (const s of sessions) {
|
|
3311
|
+
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
3312
|
+
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
3313
|
+
}
|
|
3314
|
+
return null;
|
|
3315
|
+
}
|
|
3316
|
+
function assertSessionScope(actionType, targetProject) {
|
|
3317
|
+
try {
|
|
3318
|
+
const currentProject = getProjectName();
|
|
3319
|
+
const exeSession = resolveExeSession();
|
|
3320
|
+
if (!exeSession) {
|
|
3321
|
+
return { allowed: true, reason: "no_session" };
|
|
3322
|
+
}
|
|
3323
|
+
if (currentProject === targetProject) {
|
|
3324
|
+
return {
|
|
3325
|
+
allowed: true,
|
|
3326
|
+
reason: "same_session",
|
|
3327
|
+
currentProject,
|
|
3328
|
+
targetProject
|
|
3329
|
+
};
|
|
3330
|
+
}
|
|
3331
|
+
process.stderr.write(
|
|
3332
|
+
`[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
|
|
3333
|
+
`
|
|
3334
|
+
);
|
|
3335
|
+
return {
|
|
3336
|
+
allowed: false,
|
|
3337
|
+
reason: "cross_session_denied",
|
|
3338
|
+
currentProject,
|
|
3339
|
+
targetProject,
|
|
3340
|
+
targetSession: findSessionForProject(targetProject)?.windowName
|
|
3341
|
+
};
|
|
3342
|
+
} catch {
|
|
3343
|
+
return { allowed: true, reason: "no_session" };
|
|
3344
|
+
}
|
|
3345
|
+
}
|
|
3346
|
+
var init_session_scope = __esm({
|
|
3347
|
+
"src/lib/session-scope.ts"() {
|
|
3348
|
+
"use strict";
|
|
3349
|
+
init_session_registry();
|
|
3350
|
+
init_project_name();
|
|
3351
|
+
init_tmux_routing();
|
|
3352
|
+
init_employees();
|
|
3353
|
+
}
|
|
3354
|
+
});
|
|
3355
|
+
|
|
3131
3356
|
// src/lib/tasks-crud.ts
|
|
3132
3357
|
var tasks_crud_exports = {};
|
|
3133
3358
|
__export(tasks_crud_exports, {
|
|
@@ -3145,12 +3370,12 @@ __export(tasks_crud_exports, {
|
|
|
3145
3370
|
updateTaskStatus: () => updateTaskStatus,
|
|
3146
3371
|
writeCheckpoint: () => writeCheckpoint
|
|
3147
3372
|
});
|
|
3148
|
-
import
|
|
3149
|
-
import
|
|
3150
|
-
import
|
|
3151
|
-
import { execSync as
|
|
3373
|
+
import crypto4 from "crypto";
|
|
3374
|
+
import path13 from "path";
|
|
3375
|
+
import os9 from "os";
|
|
3376
|
+
import { execSync as execSync5 } from "child_process";
|
|
3152
3377
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
3153
|
-
import { existsSync as
|
|
3378
|
+
import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
|
|
3154
3379
|
async function writeCheckpoint(input) {
|
|
3155
3380
|
const client = getClient();
|
|
3156
3381
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -3266,13 +3491,28 @@ async function resolveTask(client, identifier, scopeSession) {
|
|
|
3266
3491
|
}
|
|
3267
3492
|
async function createTaskCore(input) {
|
|
3268
3493
|
const client = getClient();
|
|
3269
|
-
const id =
|
|
3494
|
+
const id = crypto4.randomUUID();
|
|
3270
3495
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3271
3496
|
const slug = slugify(input.title);
|
|
3272
3497
|
let earlySessionScope = null;
|
|
3498
|
+
let scopeMismatchWarning;
|
|
3273
3499
|
try {
|
|
3274
3500
|
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
3275
|
-
|
|
3501
|
+
const resolved = resolveExeSession2();
|
|
3502
|
+
if (resolved && input.projectName) {
|
|
3503
|
+
const { getSessionProject: getSessionProject2 } = await Promise.resolve().then(() => (init_session_scope(), session_scope_exports));
|
|
3504
|
+
const sessionProject = getSessionProject2(resolved);
|
|
3505
|
+
if (sessionProject && sessionProject !== input.projectName) {
|
|
3506
|
+
scopeMismatchWarning = `session/project mismatch: session "${resolved}" owns "${sessionProject}" but task targets "${input.projectName}". Routed to default scope.`;
|
|
3507
|
+
process.stderr.write(`[create_task] ${scopeMismatchWarning}
|
|
3508
|
+
`);
|
|
3509
|
+
earlySessionScope = null;
|
|
3510
|
+
} else {
|
|
3511
|
+
earlySessionScope = resolved;
|
|
3512
|
+
}
|
|
3513
|
+
} else {
|
|
3514
|
+
earlySessionScope = resolved;
|
|
3515
|
+
}
|
|
3276
3516
|
} catch {
|
|
3277
3517
|
}
|
|
3278
3518
|
const scope = earlySessionScope ?? "default";
|
|
@@ -3323,10 +3563,14 @@ async function createTaskCore(input) {
|
|
|
3323
3563
|
${laneWarning}` : laneWarning;
|
|
3324
3564
|
}
|
|
3325
3565
|
}
|
|
3566
|
+
if (scopeMismatchWarning) {
|
|
3567
|
+
warning = warning ? `${warning}
|
|
3568
|
+
${scopeMismatchWarning}` : scopeMismatchWarning;
|
|
3569
|
+
}
|
|
3326
3570
|
if (input.baseDir) {
|
|
3327
3571
|
try {
|
|
3328
|
-
await mkdir3(
|
|
3329
|
-
await mkdir3(
|
|
3572
|
+
await mkdir3(path13.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
3573
|
+
await mkdir3(path13.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
3330
3574
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
3331
3575
|
await ensureGitignoreExe(input.baseDir);
|
|
3332
3576
|
} catch {
|
|
@@ -3362,13 +3606,19 @@ ${laneWarning}` : laneWarning;
|
|
|
3362
3606
|
});
|
|
3363
3607
|
if (input.baseDir) {
|
|
3364
3608
|
try {
|
|
3365
|
-
const EXE_OS_DIR =
|
|
3366
|
-
const mdPath =
|
|
3367
|
-
const mdDir =
|
|
3368
|
-
if (!
|
|
3609
|
+
const EXE_OS_DIR = path13.join(os9.homedir(), ".exe-os");
|
|
3610
|
+
const mdPath = path13.join(EXE_OS_DIR, taskFile);
|
|
3611
|
+
const mdDir = path13.dirname(mdPath);
|
|
3612
|
+
if (!existsSync12(mdDir)) await mkdir3(mdDir, { recursive: true });
|
|
3369
3613
|
const reviewer = input.reviewer ?? input.assignedBy;
|
|
3370
3614
|
const mdContent = `# ${input.title}
|
|
3371
3615
|
|
|
3616
|
+
## MANDATORY: When done
|
|
3617
|
+
|
|
3618
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
3619
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
3620
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
3621
|
+
|
|
3372
3622
|
**ID:** ${id}
|
|
3373
3623
|
**Status:** ${initialStatus}
|
|
3374
3624
|
**Priority:** ${input.priority}
|
|
@@ -3382,12 +3632,6 @@ ${laneWarning}` : laneWarning;
|
|
|
3382
3632
|
## Context
|
|
3383
3633
|
|
|
3384
3634
|
${input.context}
|
|
3385
|
-
|
|
3386
|
-
## MANDATORY: When done
|
|
3387
|
-
|
|
3388
|
-
You MUST call update_task with status "done" and a result summary when finished.
|
|
3389
|
-
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
3390
|
-
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
3391
3635
|
`;
|
|
3392
3636
|
await writeFile3(mdPath, mdContent, "utf-8");
|
|
3393
3637
|
} catch (err) {
|
|
@@ -3469,14 +3713,14 @@ function isTmuxSessionAlive(identifier) {
|
|
|
3469
3713
|
if (!identifier || identifier === "unknown") return true;
|
|
3470
3714
|
try {
|
|
3471
3715
|
if (identifier.startsWith("%")) {
|
|
3472
|
-
const output =
|
|
3716
|
+
const output = execSync5("tmux list-panes -a -F '#{pane_id}'", {
|
|
3473
3717
|
timeout: 2e3,
|
|
3474
3718
|
encoding: "utf8",
|
|
3475
3719
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3476
3720
|
});
|
|
3477
3721
|
return output.split("\n").some((l) => l.trim() === identifier);
|
|
3478
3722
|
} else {
|
|
3479
|
-
|
|
3723
|
+
execSync5(`tmux has-session -t ${JSON.stringify(identifier)}`, {
|
|
3480
3724
|
timeout: 2e3,
|
|
3481
3725
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3482
3726
|
});
|
|
@@ -3485,7 +3729,7 @@ function isTmuxSessionAlive(identifier) {
|
|
|
3485
3729
|
} catch {
|
|
3486
3730
|
if (identifier.startsWith("%")) return true;
|
|
3487
3731
|
try {
|
|
3488
|
-
|
|
3732
|
+
execSync5("tmux list-sessions", {
|
|
3489
3733
|
timeout: 2e3,
|
|
3490
3734
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3491
3735
|
});
|
|
@@ -3500,12 +3744,12 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
|
|
|
3500
3744
|
if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
|
|
3501
3745
|
try {
|
|
3502
3746
|
const since = new Date(taskCreatedAt).toISOString();
|
|
3503
|
-
const branch =
|
|
3747
|
+
const branch = execSync5(
|
|
3504
3748
|
"git rev-parse --abbrev-ref HEAD 2>/dev/null",
|
|
3505
3749
|
{ encoding: "utf8", timeout: 3e3 }
|
|
3506
3750
|
).trim();
|
|
3507
3751
|
const branchArg = branch && branch !== "HEAD" ? branch : "";
|
|
3508
|
-
const commitCount =
|
|
3752
|
+
const commitCount = execSync5(
|
|
3509
3753
|
`git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
|
|
3510
3754
|
{ encoding: "utf8", timeout: 5e3 }
|
|
3511
3755
|
).trim();
|
|
@@ -3636,7 +3880,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
3636
3880
|
await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
|
|
3637
3881
|
} catch {
|
|
3638
3882
|
}
|
|
3639
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
3883
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
3640
3884
|
try {
|
|
3641
3885
|
const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
3642
3886
|
clearQueueForAgent2(String(row.assigned_to));
|
|
@@ -3665,9 +3909,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
3665
3909
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
3666
3910
|
}
|
|
3667
3911
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
3668
|
-
const archPath =
|
|
3912
|
+
const archPath = path13.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
3669
3913
|
try {
|
|
3670
|
-
if (
|
|
3914
|
+
if (existsSync12(archPath)) return;
|
|
3671
3915
|
const template = [
|
|
3672
3916
|
`# ${projectName} \u2014 System Architecture`,
|
|
3673
3917
|
"",
|
|
@@ -3700,10 +3944,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
3700
3944
|
}
|
|
3701
3945
|
}
|
|
3702
3946
|
async function ensureGitignoreExe(baseDir) {
|
|
3703
|
-
const gitignorePath =
|
|
3947
|
+
const gitignorePath = path13.join(baseDir, ".gitignore");
|
|
3704
3948
|
try {
|
|
3705
|
-
if (
|
|
3706
|
-
const content =
|
|
3949
|
+
if (existsSync12(gitignorePath)) {
|
|
3950
|
+
const content = readFileSync11(gitignorePath, "utf-8");
|
|
3707
3951
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
3708
3952
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
3709
3953
|
} else {
|
|
@@ -3734,58 +3978,42 @@ var init_tasks_crud = __esm({
|
|
|
3734
3978
|
});
|
|
3735
3979
|
|
|
3736
3980
|
// src/lib/tasks-review.ts
|
|
3737
|
-
import
|
|
3738
|
-
import { existsSync as
|
|
3981
|
+
import path14 from "path";
|
|
3982
|
+
import { existsSync as existsSync13, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
|
|
3739
3983
|
async function countPendingReviews(sessionScope) {
|
|
3740
3984
|
const client = getClient();
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
args: [sessionScope]
|
|
3745
|
-
});
|
|
3746
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
3747
|
-
}
|
|
3985
|
+
const scope = strictSessionScopeFilter(
|
|
3986
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
3987
|
+
);
|
|
3748
3988
|
const result = await client.execute({
|
|
3749
|
-
sql:
|
|
3750
|
-
|
|
3989
|
+
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3990
|
+
WHERE status = 'needs_review'${scope.sql}`,
|
|
3991
|
+
args: [...scope.args]
|
|
3751
3992
|
});
|
|
3752
3993
|
return Number(result.rows[0]?.cnt) || 0;
|
|
3753
3994
|
}
|
|
3754
3995
|
async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
3755
3996
|
const client = getClient();
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
WHERE status = 'needs_review' AND updated_at > ?
|
|
3760
|
-
AND session_scope = ?`,
|
|
3761
|
-
args: [sinceIso, sessionScope]
|
|
3762
|
-
});
|
|
3763
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
3764
|
-
}
|
|
3997
|
+
const scope = strictSessionScopeFilter(
|
|
3998
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
3999
|
+
);
|
|
3765
4000
|
const result = await client.execute({
|
|
3766
4001
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3767
|
-
WHERE status = 'needs_review' AND updated_at >
|
|
3768
|
-
args: [sinceIso]
|
|
4002
|
+
WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
|
|
4003
|
+
args: [sinceIso, ...scope.args]
|
|
3769
4004
|
});
|
|
3770
4005
|
return Number(result.rows[0]?.cnt) || 0;
|
|
3771
4006
|
}
|
|
3772
4007
|
async function listPendingReviews(limit, sessionScope) {
|
|
3773
4008
|
const client = getClient();
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
WHERE status = 'needs_review'
|
|
3778
|
-
AND session_scope = ?
|
|
3779
|
-
ORDER BY updated_at ASC LIMIT ?`,
|
|
3780
|
-
args: [sessionScope, limit]
|
|
3781
|
-
});
|
|
3782
|
-
return result2.rows;
|
|
3783
|
-
}
|
|
4009
|
+
const scope = strictSessionScopeFilter(
|
|
4010
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
4011
|
+
);
|
|
3784
4012
|
const result = await client.execute({
|
|
3785
4013
|
sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
|
|
3786
|
-
WHERE status = 'needs_review'
|
|
4014
|
+
WHERE status = 'needs_review'${scope.sql}
|
|
3787
4015
|
ORDER BY updated_at ASC LIMIT ?`,
|
|
3788
|
-
args: [limit]
|
|
4016
|
+
args: [...scope.args, limit]
|
|
3789
4017
|
});
|
|
3790
4018
|
return result.rows;
|
|
3791
4019
|
}
|
|
@@ -3797,7 +4025,7 @@ async function cleanupOrphanedReviews() {
|
|
|
3797
4025
|
WHERE status IN ('open', 'needs_review', 'in_progress')
|
|
3798
4026
|
AND assigned_by = 'system'
|
|
3799
4027
|
AND title LIKE 'Review:%'
|
|
3800
|
-
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
|
|
4028
|
+
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
|
|
3801
4029
|
args: [now]
|
|
3802
4030
|
});
|
|
3803
4031
|
const r1b = await client.execute({
|
|
@@ -3916,11 +4144,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
3916
4144
|
);
|
|
3917
4145
|
}
|
|
3918
4146
|
try {
|
|
3919
|
-
const cacheDir =
|
|
3920
|
-
if (
|
|
4147
|
+
const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
|
|
4148
|
+
if (existsSync13(cacheDir)) {
|
|
3921
4149
|
for (const f of readdirSync2(cacheDir)) {
|
|
3922
4150
|
if (f.startsWith("review-notified-")) {
|
|
3923
|
-
unlinkSync4(
|
|
4151
|
+
unlinkSync4(path14.join(cacheDir, f));
|
|
3924
4152
|
}
|
|
3925
4153
|
}
|
|
3926
4154
|
}
|
|
@@ -3937,11 +4165,12 @@ var init_tasks_review = __esm({
|
|
|
3937
4165
|
init_tmux_routing();
|
|
3938
4166
|
init_session_key();
|
|
3939
4167
|
init_state_bus();
|
|
4168
|
+
init_task_scope();
|
|
3940
4169
|
}
|
|
3941
4170
|
});
|
|
3942
4171
|
|
|
3943
4172
|
// src/lib/tasks-chain.ts
|
|
3944
|
-
import
|
|
4173
|
+
import path15 from "path";
|
|
3945
4174
|
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
3946
4175
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
3947
4176
|
const client = getClient();
|
|
@@ -3958,7 +4187,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
3958
4187
|
});
|
|
3959
4188
|
for (const ur of unblockedRows.rows) {
|
|
3960
4189
|
try {
|
|
3961
|
-
const ubFile =
|
|
4190
|
+
const ubFile = path15.join(baseDir, String(ur.task_file));
|
|
3962
4191
|
let ubContent = await readFile3(ubFile, "utf-8");
|
|
3963
4192
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
3964
4193
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -3993,7 +4222,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
|
|
|
3993
4222
|
const scScope = sessionScopeFilter();
|
|
3994
4223
|
const remaining = await client.execute({
|
|
3995
4224
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3996
|
-
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
|
|
4225
|
+
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
|
|
3997
4226
|
args: [parentTaskId, ...scScope.args]
|
|
3998
4227
|
});
|
|
3999
4228
|
const cnt = Number(remaining.rows[0]?.cnt ?? 1);
|
|
@@ -4025,110 +4254,6 @@ var init_tasks_chain = __esm({
|
|
|
4025
4254
|
}
|
|
4026
4255
|
});
|
|
4027
4256
|
|
|
4028
|
-
// src/lib/project-name.ts
|
|
4029
|
-
import { execSync as execSync5 } from "child_process";
|
|
4030
|
-
import path14 from "path";
|
|
4031
|
-
function getProjectName(cwd) {
|
|
4032
|
-
const dir = cwd ?? process.cwd();
|
|
4033
|
-
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
4034
|
-
try {
|
|
4035
|
-
let repoRoot;
|
|
4036
|
-
try {
|
|
4037
|
-
const gitCommonDir = execSync5("git rev-parse --path-format=absolute --git-common-dir", {
|
|
4038
|
-
cwd: dir,
|
|
4039
|
-
encoding: "utf8",
|
|
4040
|
-
timeout: 2e3,
|
|
4041
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
4042
|
-
}).trim();
|
|
4043
|
-
repoRoot = path14.dirname(gitCommonDir);
|
|
4044
|
-
} catch {
|
|
4045
|
-
repoRoot = execSync5("git rev-parse --show-toplevel", {
|
|
4046
|
-
cwd: dir,
|
|
4047
|
-
encoding: "utf8",
|
|
4048
|
-
timeout: 2e3,
|
|
4049
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
4050
|
-
}).trim();
|
|
4051
|
-
}
|
|
4052
|
-
_cached2 = path14.basename(repoRoot);
|
|
4053
|
-
_cachedCwd = dir;
|
|
4054
|
-
return _cached2;
|
|
4055
|
-
} catch {
|
|
4056
|
-
_cached2 = path14.basename(dir);
|
|
4057
|
-
_cachedCwd = dir;
|
|
4058
|
-
return _cached2;
|
|
4059
|
-
}
|
|
4060
|
-
}
|
|
4061
|
-
var _cached2, _cachedCwd;
|
|
4062
|
-
var init_project_name = __esm({
|
|
4063
|
-
"src/lib/project-name.ts"() {
|
|
4064
|
-
"use strict";
|
|
4065
|
-
_cached2 = null;
|
|
4066
|
-
_cachedCwd = null;
|
|
4067
|
-
}
|
|
4068
|
-
});
|
|
4069
|
-
|
|
4070
|
-
// src/lib/session-scope.ts
|
|
4071
|
-
var session_scope_exports = {};
|
|
4072
|
-
__export(session_scope_exports, {
|
|
4073
|
-
assertSessionScope: () => assertSessionScope,
|
|
4074
|
-
findSessionForProject: () => findSessionForProject,
|
|
4075
|
-
getSessionProject: () => getSessionProject
|
|
4076
|
-
});
|
|
4077
|
-
function getSessionProject(sessionName) {
|
|
4078
|
-
const sessions = listSessions();
|
|
4079
|
-
const entry = sessions.find((s) => s.windowName === sessionName);
|
|
4080
|
-
if (!entry) return null;
|
|
4081
|
-
const parts = entry.projectDir.split("/").filter(Boolean);
|
|
4082
|
-
return parts[parts.length - 1] ?? null;
|
|
4083
|
-
}
|
|
4084
|
-
function findSessionForProject(projectName) {
|
|
4085
|
-
const sessions = listSessions();
|
|
4086
|
-
for (const s of sessions) {
|
|
4087
|
-
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
4088
|
-
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
4089
|
-
}
|
|
4090
|
-
return null;
|
|
4091
|
-
}
|
|
4092
|
-
function assertSessionScope(actionType, targetProject) {
|
|
4093
|
-
try {
|
|
4094
|
-
const currentProject = getProjectName();
|
|
4095
|
-
const exeSession = resolveExeSession();
|
|
4096
|
-
if (!exeSession) {
|
|
4097
|
-
return { allowed: true, reason: "no_session" };
|
|
4098
|
-
}
|
|
4099
|
-
if (currentProject === targetProject) {
|
|
4100
|
-
return {
|
|
4101
|
-
allowed: true,
|
|
4102
|
-
reason: "same_session",
|
|
4103
|
-
currentProject,
|
|
4104
|
-
targetProject
|
|
4105
|
-
};
|
|
4106
|
-
}
|
|
4107
|
-
process.stderr.write(
|
|
4108
|
-
`[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
|
|
4109
|
-
`
|
|
4110
|
-
);
|
|
4111
|
-
return {
|
|
4112
|
-
allowed: false,
|
|
4113
|
-
reason: "cross_session_denied",
|
|
4114
|
-
currentProject,
|
|
4115
|
-
targetProject,
|
|
4116
|
-
targetSession: findSessionForProject(targetProject)?.windowName
|
|
4117
|
-
};
|
|
4118
|
-
} catch {
|
|
4119
|
-
return { allowed: true, reason: "no_session" };
|
|
4120
|
-
}
|
|
4121
|
-
}
|
|
4122
|
-
var init_session_scope = __esm({
|
|
4123
|
-
"src/lib/session-scope.ts"() {
|
|
4124
|
-
"use strict";
|
|
4125
|
-
init_session_registry();
|
|
4126
|
-
init_project_name();
|
|
4127
|
-
init_tmux_routing();
|
|
4128
|
-
init_employees();
|
|
4129
|
-
}
|
|
4130
|
-
});
|
|
4131
|
-
|
|
4132
4257
|
// src/lib/tasks-notify.ts
|
|
4133
4258
|
async function dispatchTaskToEmployee(input) {
|
|
4134
4259
|
if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
|
|
@@ -4196,10 +4321,10 @@ var init_tasks_notify = __esm({
|
|
|
4196
4321
|
});
|
|
4197
4322
|
|
|
4198
4323
|
// src/lib/behaviors.ts
|
|
4199
|
-
import
|
|
4324
|
+
import crypto5 from "crypto";
|
|
4200
4325
|
async function storeBehavior(opts) {
|
|
4201
4326
|
const client = getClient();
|
|
4202
|
-
const id =
|
|
4327
|
+
const id = crypto5.randomUUID();
|
|
4203
4328
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4204
4329
|
await client.execute({
|
|
4205
4330
|
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
|
|
@@ -4228,7 +4353,7 @@ __export(skill_learning_exports, {
|
|
|
4228
4353
|
storeTrajectory: () => storeTrajectory,
|
|
4229
4354
|
sweepTrajectories: () => sweepTrajectories
|
|
4230
4355
|
});
|
|
4231
|
-
import
|
|
4356
|
+
import crypto6 from "crypto";
|
|
4232
4357
|
async function extractTrajectory(taskId, agentId) {
|
|
4233
4358
|
const client = getClient();
|
|
4234
4359
|
const result = await client.execute({
|
|
@@ -4257,11 +4382,11 @@ async function extractTrajectory(taskId, agentId) {
|
|
|
4257
4382
|
return signature;
|
|
4258
4383
|
}
|
|
4259
4384
|
function hashSignature(signature) {
|
|
4260
|
-
return
|
|
4385
|
+
return crypto6.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
|
|
4261
4386
|
}
|
|
4262
4387
|
async function storeTrajectory(opts) {
|
|
4263
4388
|
const client = getClient();
|
|
4264
|
-
const id =
|
|
4389
|
+
const id = crypto6.randomUUID();
|
|
4265
4390
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4266
4391
|
const signatureHash = hashSignature(opts.signature);
|
|
4267
4392
|
await client.execute({
|
|
@@ -4526,8 +4651,8 @@ __export(tasks_exports, {
|
|
|
4526
4651
|
updateTaskStatus: () => updateTaskStatus,
|
|
4527
4652
|
writeCheckpoint: () => writeCheckpoint
|
|
4528
4653
|
});
|
|
4529
|
-
import
|
|
4530
|
-
import { writeFileSync as
|
|
4654
|
+
import path16 from "path";
|
|
4655
|
+
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
|
|
4531
4656
|
async function createTask(input) {
|
|
4532
4657
|
const result = await createTaskCore(input);
|
|
4533
4658
|
if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -4546,12 +4671,12 @@ async function updateTask(input) {
|
|
|
4546
4671
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
4547
4672
|
try {
|
|
4548
4673
|
const agent = String(row.assigned_to);
|
|
4549
|
-
const cacheDir =
|
|
4550
|
-
const cachePath =
|
|
4674
|
+
const cacheDir = path16.join(EXE_AI_DIR, "session-cache");
|
|
4675
|
+
const cachePath = path16.join(cacheDir, `current-task-${agent}.json`);
|
|
4551
4676
|
if (input.status === "in_progress") {
|
|
4552
4677
|
mkdirSync5(cacheDir, { recursive: true });
|
|
4553
|
-
|
|
4554
|
-
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
4678
|
+
writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
4679
|
+
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
|
|
4555
4680
|
try {
|
|
4556
4681
|
unlinkSync5(cachePath);
|
|
4557
4682
|
} catch {
|
|
@@ -4559,10 +4684,10 @@ async function updateTask(input) {
|
|
|
4559
4684
|
}
|
|
4560
4685
|
} catch {
|
|
4561
4686
|
}
|
|
4562
|
-
if (input.status === "done") {
|
|
4687
|
+
if (input.status === "done" || input.status === "closed") {
|
|
4563
4688
|
await cleanupReviewFile(row, taskFile, input.baseDir);
|
|
4564
4689
|
}
|
|
4565
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
4690
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
4566
4691
|
try {
|
|
4567
4692
|
const client = getClient();
|
|
4568
4693
|
const taskTitle = String(row.title);
|
|
@@ -4578,7 +4703,7 @@ async function updateTask(input) {
|
|
|
4578
4703
|
if (!isCoordinatorName(assignedAgent)) {
|
|
4579
4704
|
try {
|
|
4580
4705
|
const draftClient = getClient();
|
|
4581
|
-
if (input.status === "done") {
|
|
4706
|
+
if (input.status === "done" || input.status === "closed") {
|
|
4582
4707
|
await draftClient.execute({
|
|
4583
4708
|
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
4584
4709
|
args: [assignedAgent]
|
|
@@ -4595,7 +4720,7 @@ async function updateTask(input) {
|
|
|
4595
4720
|
try {
|
|
4596
4721
|
const client = getClient();
|
|
4597
4722
|
const cascaded = await client.execute({
|
|
4598
|
-
sql: `UPDATE tasks SET status = '
|
|
4723
|
+
sql: `UPDATE tasks SET status = 'closed', updated_at = ?
|
|
4599
4724
|
WHERE parent_task_id = ? AND status = 'needs_review'`,
|
|
4600
4725
|
args: [now, taskId]
|
|
4601
4726
|
});
|
|
@@ -4608,14 +4733,14 @@ async function updateTask(input) {
|
|
|
4608
4733
|
} catch {
|
|
4609
4734
|
}
|
|
4610
4735
|
}
|
|
4611
|
-
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
4736
|
+
const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
|
|
4612
4737
|
if (isTerminal) {
|
|
4613
4738
|
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
4614
4739
|
if (!isCoordinator) {
|
|
4615
4740
|
notifyTaskDone();
|
|
4616
4741
|
}
|
|
4617
4742
|
await markTaskNotificationsRead(taskFile);
|
|
4618
|
-
if (input.status === "done") {
|
|
4743
|
+
if (input.status === "done" || input.status === "closed") {
|
|
4619
4744
|
try {
|
|
4620
4745
|
await cascadeUnblock(taskId, input.baseDir, now);
|
|
4621
4746
|
} catch {
|
|
@@ -4635,7 +4760,7 @@ async function updateTask(input) {
|
|
|
4635
4760
|
}
|
|
4636
4761
|
}
|
|
4637
4762
|
}
|
|
4638
|
-
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
4763
|
+
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
4639
4764
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
4640
4765
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
4641
4766
|
taskId,
|
|
@@ -5007,6 +5132,7 @@ __export(tmux_routing_exports, {
|
|
|
5007
5132
|
isEmployeeAlive: () => isEmployeeAlive,
|
|
5008
5133
|
isExeSession: () => isExeSession,
|
|
5009
5134
|
isSessionBusy: () => isSessionBusy,
|
|
5135
|
+
notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
|
|
5010
5136
|
notifyParentExe: () => notifyParentExe,
|
|
5011
5137
|
parseParentExe: () => parseParentExe,
|
|
5012
5138
|
registerParentExe: () => registerParentExe,
|
|
@@ -5017,13 +5143,13 @@ __export(tmux_routing_exports, {
|
|
|
5017
5143
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
5018
5144
|
});
|
|
5019
5145
|
import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
|
|
5020
|
-
import { readFileSync as
|
|
5021
|
-
import
|
|
5022
|
-
import
|
|
5146
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync6, existsSync as existsSync14, appendFileSync, readdirSync as readdirSync3 } from "fs";
|
|
5147
|
+
import path17 from "path";
|
|
5148
|
+
import os10 from "os";
|
|
5023
5149
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5024
5150
|
import { unlinkSync as unlinkSync6 } from "fs";
|
|
5025
5151
|
function spawnLockPath(sessionName) {
|
|
5026
|
-
return
|
|
5152
|
+
return path17.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
5027
5153
|
}
|
|
5028
5154
|
function isProcessAlive(pid) {
|
|
5029
5155
|
try {
|
|
@@ -5034,13 +5160,13 @@ function isProcessAlive(pid) {
|
|
|
5034
5160
|
}
|
|
5035
5161
|
}
|
|
5036
5162
|
function acquireSpawnLock2(sessionName) {
|
|
5037
|
-
if (!
|
|
5163
|
+
if (!existsSync14(SPAWN_LOCK_DIR)) {
|
|
5038
5164
|
mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
|
|
5039
5165
|
}
|
|
5040
5166
|
const lockFile = spawnLockPath(sessionName);
|
|
5041
|
-
if (
|
|
5167
|
+
if (existsSync14(lockFile)) {
|
|
5042
5168
|
try {
|
|
5043
|
-
const lock = JSON.parse(
|
|
5169
|
+
const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
|
|
5044
5170
|
const age = Date.now() - lock.timestamp;
|
|
5045
5171
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
5046
5172
|
return false;
|
|
@@ -5048,7 +5174,7 @@ function acquireSpawnLock2(sessionName) {
|
|
|
5048
5174
|
} catch {
|
|
5049
5175
|
}
|
|
5050
5176
|
}
|
|
5051
|
-
|
|
5177
|
+
writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
5052
5178
|
return true;
|
|
5053
5179
|
}
|
|
5054
5180
|
function releaseSpawnLock2(sessionName) {
|
|
@@ -5060,13 +5186,13 @@ function releaseSpawnLock2(sessionName) {
|
|
|
5060
5186
|
function resolveBehaviorsExporterScript() {
|
|
5061
5187
|
try {
|
|
5062
5188
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
5063
|
-
const scriptPath =
|
|
5064
|
-
|
|
5189
|
+
const scriptPath = path17.join(
|
|
5190
|
+
path17.dirname(thisFile),
|
|
5065
5191
|
"..",
|
|
5066
5192
|
"bin",
|
|
5067
5193
|
"exe-export-behaviors.js"
|
|
5068
5194
|
);
|
|
5069
|
-
return
|
|
5195
|
+
return existsSync14(scriptPath) ? scriptPath : null;
|
|
5070
5196
|
} catch {
|
|
5071
5197
|
return null;
|
|
5072
5198
|
}
|
|
@@ -5132,12 +5258,12 @@ function extractRootExe(name) {
|
|
|
5132
5258
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
5133
5259
|
}
|
|
5134
5260
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
5135
|
-
if (!
|
|
5261
|
+
if (!existsSync14(SESSION_CACHE)) {
|
|
5136
5262
|
mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
5137
5263
|
}
|
|
5138
5264
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
5139
|
-
const filePath =
|
|
5140
|
-
|
|
5265
|
+
const filePath = path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
5266
|
+
writeFileSync8(filePath, JSON.stringify({
|
|
5141
5267
|
parentExe: rootExe,
|
|
5142
5268
|
dispatchedBy: dispatchedBy || rootExe,
|
|
5143
5269
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -5145,7 +5271,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
5145
5271
|
}
|
|
5146
5272
|
function getParentExe(sessionKey) {
|
|
5147
5273
|
try {
|
|
5148
|
-
const data = JSON.parse(
|
|
5274
|
+
const data = JSON.parse(readFileSync12(path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
5149
5275
|
return data.parentExe || null;
|
|
5150
5276
|
} catch {
|
|
5151
5277
|
return null;
|
|
@@ -5153,8 +5279,8 @@ function getParentExe(sessionKey) {
|
|
|
5153
5279
|
}
|
|
5154
5280
|
function getDispatchedBy(sessionKey) {
|
|
5155
5281
|
try {
|
|
5156
|
-
const data = JSON.parse(
|
|
5157
|
-
|
|
5282
|
+
const data = JSON.parse(readFileSync12(
|
|
5283
|
+
path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
5158
5284
|
"utf8"
|
|
5159
5285
|
));
|
|
5160
5286
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -5224,8 +5350,8 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
5224
5350
|
}
|
|
5225
5351
|
function readDebounceState() {
|
|
5226
5352
|
try {
|
|
5227
|
-
if (!
|
|
5228
|
-
const raw = JSON.parse(
|
|
5353
|
+
if (!existsSync14(DEBOUNCE_FILE)) return {};
|
|
5354
|
+
const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
|
|
5229
5355
|
const state = {};
|
|
5230
5356
|
for (const [key, val] of Object.entries(raw)) {
|
|
5231
5357
|
if (typeof val === "number") {
|
|
@@ -5241,8 +5367,8 @@ function readDebounceState() {
|
|
|
5241
5367
|
}
|
|
5242
5368
|
function writeDebounceState(state) {
|
|
5243
5369
|
try {
|
|
5244
|
-
if (!
|
|
5245
|
-
|
|
5370
|
+
if (!existsSync14(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
5371
|
+
writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
|
|
5246
5372
|
} catch {
|
|
5247
5373
|
}
|
|
5248
5374
|
}
|
|
@@ -5340,8 +5466,8 @@ function sendIntercom(targetSession) {
|
|
|
5340
5466
|
try {
|
|
5341
5467
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
5342
5468
|
const agent = baseAgentName(rawAgent);
|
|
5343
|
-
const markerPath =
|
|
5344
|
-
if (
|
|
5469
|
+
const markerPath = path17.join(SESSION_CACHE, `current-task-${agent}.json`);
|
|
5470
|
+
if (existsSync14(markerPath)) {
|
|
5345
5471
|
logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
|
|
5346
5472
|
return "debounced";
|
|
5347
5473
|
}
|
|
@@ -5350,8 +5476,8 @@ function sendIntercom(targetSession) {
|
|
|
5350
5476
|
try {
|
|
5351
5477
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
5352
5478
|
const agent = baseAgentName(rawAgent);
|
|
5353
|
-
const taskDir =
|
|
5354
|
-
if (
|
|
5479
|
+
const taskDir = path17.join(process.cwd(), "exe", agent);
|
|
5480
|
+
if (existsSync14(taskDir)) {
|
|
5355
5481
|
const files = readdirSync3(taskDir).filter(
|
|
5356
5482
|
(f) => f.endsWith(".md") && f !== "DONE.txt"
|
|
5357
5483
|
);
|
|
@@ -5411,6 +5537,21 @@ function notifyParentExe(sessionKey) {
|
|
|
5411
5537
|
}
|
|
5412
5538
|
return true;
|
|
5413
5539
|
}
|
|
5540
|
+
function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
|
|
5541
|
+
const transport = getTransport();
|
|
5542
|
+
try {
|
|
5543
|
+
const sessions = transport.listSessions();
|
|
5544
|
+
if (!sessions.includes(coordinatorSession)) return false;
|
|
5545
|
+
execSync6(
|
|
5546
|
+
`tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
|
|
5547
|
+
{ timeout: 3e3 }
|
|
5548
|
+
);
|
|
5549
|
+
logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
|
|
5550
|
+
return true;
|
|
5551
|
+
} catch {
|
|
5552
|
+
return false;
|
|
5553
|
+
}
|
|
5554
|
+
}
|
|
5414
5555
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
5415
5556
|
if (isCoordinatorName(employeeName)) {
|
|
5416
5557
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
@@ -5484,26 +5625,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5484
5625
|
const transport = getTransport();
|
|
5485
5626
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
5486
5627
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
5487
|
-
const logDir =
|
|
5488
|
-
const logFile =
|
|
5489
|
-
if (!
|
|
5628
|
+
const logDir = path17.join(os10.homedir(), ".exe-os", "session-logs");
|
|
5629
|
+
const logFile = path17.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
5630
|
+
if (!existsSync14(logDir)) {
|
|
5490
5631
|
mkdirSync6(logDir, { recursive: true });
|
|
5491
5632
|
}
|
|
5492
5633
|
transport.kill(sessionName);
|
|
5493
5634
|
let cleanupSuffix = "";
|
|
5494
5635
|
try {
|
|
5495
5636
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
5496
|
-
const cleanupScript =
|
|
5497
|
-
if (
|
|
5637
|
+
const cleanupScript = path17.join(path17.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
5638
|
+
if (existsSync14(cleanupScript)) {
|
|
5498
5639
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
5499
5640
|
}
|
|
5500
5641
|
} catch {
|
|
5501
5642
|
}
|
|
5502
5643
|
try {
|
|
5503
|
-
const claudeJsonPath =
|
|
5644
|
+
const claudeJsonPath = path17.join(os10.homedir(), ".claude.json");
|
|
5504
5645
|
let claudeJson = {};
|
|
5505
5646
|
try {
|
|
5506
|
-
claudeJson = JSON.parse(
|
|
5647
|
+
claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
|
|
5507
5648
|
} catch {
|
|
5508
5649
|
}
|
|
5509
5650
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -5511,17 +5652,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5511
5652
|
const trustDir = opts?.cwd ?? projectDir;
|
|
5512
5653
|
if (!projects[trustDir]) projects[trustDir] = {};
|
|
5513
5654
|
projects[trustDir].hasTrustDialogAccepted = true;
|
|
5514
|
-
|
|
5655
|
+
writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
5515
5656
|
} catch {
|
|
5516
5657
|
}
|
|
5517
5658
|
try {
|
|
5518
|
-
const settingsDir =
|
|
5659
|
+
const settingsDir = path17.join(os10.homedir(), ".claude", "projects");
|
|
5519
5660
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
5520
|
-
const projSettingsDir =
|
|
5521
|
-
const settingsPath =
|
|
5661
|
+
const projSettingsDir = path17.join(settingsDir, normalizedKey);
|
|
5662
|
+
const settingsPath = path17.join(projSettingsDir, "settings.json");
|
|
5522
5663
|
let settings = {};
|
|
5523
5664
|
try {
|
|
5524
|
-
settings = JSON.parse(
|
|
5665
|
+
settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
|
|
5525
5666
|
} catch {
|
|
5526
5667
|
}
|
|
5527
5668
|
const perms = settings.permissions ?? {};
|
|
@@ -5550,7 +5691,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5550
5691
|
perms.allow = allow;
|
|
5551
5692
|
settings.permissions = perms;
|
|
5552
5693
|
mkdirSync6(projSettingsDir, { recursive: true });
|
|
5553
|
-
|
|
5694
|
+
writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
5554
5695
|
}
|
|
5555
5696
|
} catch {
|
|
5556
5697
|
}
|
|
@@ -5565,8 +5706,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5565
5706
|
let behaviorsFlag = "";
|
|
5566
5707
|
let legacyFallbackWarned = false;
|
|
5567
5708
|
if (!useExeAgent && !useBinSymlink) {
|
|
5568
|
-
const identityPath =
|
|
5569
|
-
|
|
5709
|
+
const identityPath = path17.join(
|
|
5710
|
+
os10.homedir(),
|
|
5570
5711
|
".exe-os",
|
|
5571
5712
|
"identity",
|
|
5572
5713
|
`${employeeName}.md`
|
|
@@ -5575,13 +5716,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5575
5716
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
5576
5717
|
if (hasAgentFlag) {
|
|
5577
5718
|
identityFlag = ` --agent ${employeeName}`;
|
|
5578
|
-
} else if (
|
|
5719
|
+
} else if (existsSync14(identityPath)) {
|
|
5579
5720
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
5580
5721
|
legacyFallbackWarned = true;
|
|
5581
5722
|
}
|
|
5582
5723
|
const behaviorsFile = exportBehaviorsSync(
|
|
5583
5724
|
employeeName,
|
|
5584
|
-
|
|
5725
|
+
path17.basename(spawnCwd),
|
|
5585
5726
|
sessionName
|
|
5586
5727
|
);
|
|
5587
5728
|
if (behaviorsFile) {
|
|
@@ -5596,16 +5737,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5596
5737
|
}
|
|
5597
5738
|
let sessionContextFlag = "";
|
|
5598
5739
|
try {
|
|
5599
|
-
const ctxDir =
|
|
5740
|
+
const ctxDir = path17.join(os10.homedir(), ".exe-os", "session-cache");
|
|
5600
5741
|
mkdirSync6(ctxDir, { recursive: true });
|
|
5601
|
-
const ctxFile =
|
|
5742
|
+
const ctxFile = path17.join(ctxDir, `session-context-${sessionName}.md`);
|
|
5602
5743
|
const ctxContent = [
|
|
5603
5744
|
`## Session Context`,
|
|
5604
5745
|
`You are running in tmux session: ${sessionName}.`,
|
|
5605
5746
|
`Your parent coordinator session is ${exeSession}.`,
|
|
5606
5747
|
`Your employees (if any) use the -${exeSession} suffix.`
|
|
5607
5748
|
].join("\n");
|
|
5608
|
-
|
|
5749
|
+
writeFileSync8(ctxFile, ctxContent);
|
|
5609
5750
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
5610
5751
|
} catch {
|
|
5611
5752
|
}
|
|
@@ -5682,8 +5823,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5682
5823
|
transport.pipeLog(sessionName, logFile);
|
|
5683
5824
|
try {
|
|
5684
5825
|
const mySession = getMySession();
|
|
5685
|
-
const dispatchInfo =
|
|
5686
|
-
|
|
5826
|
+
const dispatchInfo = path17.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
5827
|
+
writeFileSync8(dispatchInfo, JSON.stringify({
|
|
5687
5828
|
dispatchedBy: mySession,
|
|
5688
5829
|
rootExe: exeSession,
|
|
5689
5830
|
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
|
|
@@ -5757,15 +5898,15 @@ var init_tmux_routing = __esm({
|
|
|
5757
5898
|
init_intercom_queue();
|
|
5758
5899
|
init_plan_limits();
|
|
5759
5900
|
init_employees();
|
|
5760
|
-
SPAWN_LOCK_DIR =
|
|
5761
|
-
SESSION_CACHE =
|
|
5901
|
+
SPAWN_LOCK_DIR = path17.join(os10.homedir(), ".exe-os", "spawn-locks");
|
|
5902
|
+
SESSION_CACHE = path17.join(os10.homedir(), ".exe-os", "session-cache");
|
|
5762
5903
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
5763
5904
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
5764
5905
|
VERIFY_PANE_LINES = 200;
|
|
5765
5906
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
5766
5907
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
5767
|
-
INTERCOM_LOG2 =
|
|
5768
|
-
DEBOUNCE_FILE =
|
|
5908
|
+
INTERCOM_LOG2 = path17.join(os10.homedir(), ".exe-os", "intercom.log");
|
|
5909
|
+
DEBOUNCE_FILE = path17.join(SESSION_CACHE, "intercom-debounce.json");
|
|
5769
5910
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
5770
5911
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
|
|
5771
5912
|
}
|
|
@@ -5788,6 +5929,15 @@ function sessionScopeFilter(sessionScope, tableAlias) {
|
|
|
5788
5929
|
args: [scope]
|
|
5789
5930
|
};
|
|
5790
5931
|
}
|
|
5932
|
+
function strictSessionScopeFilter(sessionScope, tableAlias) {
|
|
5933
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
5934
|
+
if (!scope) return { sql: "", args: [] };
|
|
5935
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
5936
|
+
return {
|
|
5937
|
+
sql: ` AND ${col} = ?`,
|
|
5938
|
+
args: [scope]
|
|
5939
|
+
};
|
|
5940
|
+
}
|
|
5791
5941
|
var init_task_scope = __esm({
|
|
5792
5942
|
"src/lib/task-scope.ts"() {
|
|
5793
5943
|
"use strict";
|
|
@@ -5806,14 +5956,14 @@ var init_memory = __esm({
|
|
|
5806
5956
|
|
|
5807
5957
|
// src/lib/keychain.ts
|
|
5808
5958
|
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
5809
|
-
import { existsSync as
|
|
5810
|
-
import
|
|
5811
|
-
import
|
|
5959
|
+
import { existsSync as existsSync15 } from "fs";
|
|
5960
|
+
import path18 from "path";
|
|
5961
|
+
import os11 from "os";
|
|
5812
5962
|
function getKeyDir() {
|
|
5813
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
5963
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path18.join(os11.homedir(), ".exe-os");
|
|
5814
5964
|
}
|
|
5815
5965
|
function getKeyPath() {
|
|
5816
|
-
return
|
|
5966
|
+
return path18.join(getKeyDir(), "master.key");
|
|
5817
5967
|
}
|
|
5818
5968
|
async function tryKeytar() {
|
|
5819
5969
|
try {
|
|
@@ -5834,9 +5984,9 @@ async function getMasterKey() {
|
|
|
5834
5984
|
}
|
|
5835
5985
|
}
|
|
5836
5986
|
const keyPath = getKeyPath();
|
|
5837
|
-
if (!
|
|
5987
|
+
if (!existsSync15(keyPath)) {
|
|
5838
5988
|
process.stderr.write(
|
|
5839
|
-
`[keychain] Key not found at ${keyPath} (HOME=${
|
|
5989
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os11.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
5840
5990
|
`
|
|
5841
5991
|
);
|
|
5842
5992
|
return null;
|
|
@@ -5866,6 +6016,7 @@ var shard_manager_exports = {};
|
|
|
5866
6016
|
__export(shard_manager_exports, {
|
|
5867
6017
|
disposeShards: () => disposeShards,
|
|
5868
6018
|
ensureShardSchema: () => ensureShardSchema,
|
|
6019
|
+
getOpenShardCount: () => getOpenShardCount,
|
|
5869
6020
|
getReadyShardClient: () => getReadyShardClient,
|
|
5870
6021
|
getShardClient: () => getShardClient,
|
|
5871
6022
|
getShardsDir: () => getShardsDir,
|
|
@@ -5874,15 +6025,18 @@ __export(shard_manager_exports, {
|
|
|
5874
6025
|
listShards: () => listShards,
|
|
5875
6026
|
shardExists: () => shardExists
|
|
5876
6027
|
});
|
|
5877
|
-
import
|
|
5878
|
-
import { existsSync as
|
|
6028
|
+
import path19 from "path";
|
|
6029
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
|
|
5879
6030
|
import { createClient as createClient2 } from "@libsql/client";
|
|
5880
6031
|
function initShardManager(encryptionKey) {
|
|
5881
6032
|
_encryptionKey = encryptionKey;
|
|
5882
|
-
if (!
|
|
6033
|
+
if (!existsSync16(SHARDS_DIR)) {
|
|
5883
6034
|
mkdirSync7(SHARDS_DIR, { recursive: true });
|
|
5884
6035
|
}
|
|
5885
6036
|
_shardingEnabled = true;
|
|
6037
|
+
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
6038
|
+
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
6039
|
+
_evictionTimer.unref();
|
|
5886
6040
|
}
|
|
5887
6041
|
function isShardingEnabled() {
|
|
5888
6042
|
return _shardingEnabled;
|
|
@@ -5899,21 +6053,28 @@ function getShardClient(projectName) {
|
|
|
5899
6053
|
throw new Error(`Invalid project name for shard: "${projectName}"`);
|
|
5900
6054
|
}
|
|
5901
6055
|
const cached = _shards.get(safeName);
|
|
5902
|
-
if (cached)
|
|
5903
|
-
|
|
6056
|
+
if (cached) {
|
|
6057
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
6058
|
+
return cached;
|
|
6059
|
+
}
|
|
6060
|
+
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
6061
|
+
evictLRU();
|
|
6062
|
+
}
|
|
6063
|
+
const dbPath = path19.join(SHARDS_DIR, `${safeName}.db`);
|
|
5904
6064
|
const client = createClient2({
|
|
5905
6065
|
url: `file:${dbPath}`,
|
|
5906
6066
|
encryptionKey: _encryptionKey
|
|
5907
6067
|
});
|
|
5908
6068
|
_shards.set(safeName, client);
|
|
6069
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
5909
6070
|
return client;
|
|
5910
6071
|
}
|
|
5911
6072
|
function shardExists(projectName) {
|
|
5912
6073
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
5913
|
-
return
|
|
6074
|
+
return existsSync16(path19.join(SHARDS_DIR, `${safeName}.db`));
|
|
5914
6075
|
}
|
|
5915
6076
|
function listShards() {
|
|
5916
|
-
if (!
|
|
6077
|
+
if (!existsSync16(SHARDS_DIR)) return [];
|
|
5917
6078
|
return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
5918
6079
|
}
|
|
5919
6080
|
async function ensureShardSchema(client) {
|
|
@@ -5965,6 +6126,8 @@ async function ensureShardSchema(client) {
|
|
|
5965
6126
|
for (const col of [
|
|
5966
6127
|
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
5967
6128
|
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
6129
|
+
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
6130
|
+
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
5968
6131
|
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
5969
6132
|
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
5970
6133
|
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
@@ -6102,21 +6265,69 @@ async function getReadyShardClient(projectName) {
|
|
|
6102
6265
|
await ensureShardSchema(client);
|
|
6103
6266
|
return client;
|
|
6104
6267
|
}
|
|
6268
|
+
function evictLRU() {
|
|
6269
|
+
let oldest = null;
|
|
6270
|
+
let oldestTime = Infinity;
|
|
6271
|
+
for (const [name, time] of _shardLastAccess) {
|
|
6272
|
+
if (time < oldestTime) {
|
|
6273
|
+
oldestTime = time;
|
|
6274
|
+
oldest = name;
|
|
6275
|
+
}
|
|
6276
|
+
}
|
|
6277
|
+
if (oldest) {
|
|
6278
|
+
const client = _shards.get(oldest);
|
|
6279
|
+
if (client) {
|
|
6280
|
+
client.close();
|
|
6281
|
+
}
|
|
6282
|
+
_shards.delete(oldest);
|
|
6283
|
+
_shardLastAccess.delete(oldest);
|
|
6284
|
+
}
|
|
6285
|
+
}
|
|
6286
|
+
function evictIdleShards() {
|
|
6287
|
+
const now = Date.now();
|
|
6288
|
+
const toEvict = [];
|
|
6289
|
+
for (const [name, lastAccess] of _shardLastAccess) {
|
|
6290
|
+
if (now - lastAccess > SHARD_IDLE_MS) {
|
|
6291
|
+
toEvict.push(name);
|
|
6292
|
+
}
|
|
6293
|
+
}
|
|
6294
|
+
for (const name of toEvict) {
|
|
6295
|
+
const client = _shards.get(name);
|
|
6296
|
+
if (client) {
|
|
6297
|
+
client.close();
|
|
6298
|
+
}
|
|
6299
|
+
_shards.delete(name);
|
|
6300
|
+
_shardLastAccess.delete(name);
|
|
6301
|
+
}
|
|
6302
|
+
}
|
|
6303
|
+
function getOpenShardCount() {
|
|
6304
|
+
return _shards.size;
|
|
6305
|
+
}
|
|
6105
6306
|
function disposeShards() {
|
|
6307
|
+
if (_evictionTimer) {
|
|
6308
|
+
clearInterval(_evictionTimer);
|
|
6309
|
+
_evictionTimer = null;
|
|
6310
|
+
}
|
|
6106
6311
|
for (const [, client] of _shards) {
|
|
6107
6312
|
client.close();
|
|
6108
6313
|
}
|
|
6109
6314
|
_shards.clear();
|
|
6315
|
+
_shardLastAccess.clear();
|
|
6110
6316
|
_shardingEnabled = false;
|
|
6111
6317
|
_encryptionKey = null;
|
|
6112
6318
|
}
|
|
6113
|
-
var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
|
|
6319
|
+
var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
|
|
6114
6320
|
var init_shard_manager = __esm({
|
|
6115
6321
|
"src/lib/shard-manager.ts"() {
|
|
6116
6322
|
"use strict";
|
|
6117
6323
|
init_config();
|
|
6118
|
-
SHARDS_DIR =
|
|
6324
|
+
SHARDS_DIR = path19.join(EXE_AI_DIR, "shards");
|
|
6325
|
+
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
6326
|
+
MAX_OPEN_SHARDS = 10;
|
|
6327
|
+
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
6119
6328
|
_shards = /* @__PURE__ */ new Map();
|
|
6329
|
+
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
6330
|
+
_evictionTimer = null;
|
|
6120
6331
|
_encryptionKey = null;
|
|
6121
6332
|
_shardingEnabled = false;
|
|
6122
6333
|
}
|