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