@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
|
@@ -25,9 +25,47 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
25
25
|
};
|
|
26
26
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
27
27
|
|
|
28
|
+
// src/lib/secure-files.ts
|
|
29
|
+
import { chmodSync, existsSync, mkdirSync } from "fs";
|
|
30
|
+
import { chmod, mkdir } from "fs/promises";
|
|
31
|
+
async function ensurePrivateDir(dirPath) {
|
|
32
|
+
await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
33
|
+
try {
|
|
34
|
+
await chmod(dirPath, PRIVATE_DIR_MODE);
|
|
35
|
+
} catch {
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function ensurePrivateDirSync(dirPath) {
|
|
39
|
+
mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
40
|
+
try {
|
|
41
|
+
chmodSync(dirPath, PRIVATE_DIR_MODE);
|
|
42
|
+
} catch {
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async function enforcePrivateFile(filePath) {
|
|
46
|
+
try {
|
|
47
|
+
await chmod(filePath, PRIVATE_FILE_MODE);
|
|
48
|
+
} catch {
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function enforcePrivateFileSync(filePath) {
|
|
52
|
+
try {
|
|
53
|
+
if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
|
|
54
|
+
} catch {
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
|
|
58
|
+
var init_secure_files = __esm({
|
|
59
|
+
"src/lib/secure-files.ts"() {
|
|
60
|
+
"use strict";
|
|
61
|
+
PRIVATE_DIR_MODE = 448;
|
|
62
|
+
PRIVATE_FILE_MODE = 384;
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
28
66
|
// src/lib/config.ts
|
|
29
|
-
import { readFile, writeFile
|
|
30
|
-
import { readFileSync, existsSync, renameSync } from "fs";
|
|
67
|
+
import { readFile, writeFile } from "fs/promises";
|
|
68
|
+
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
31
69
|
import path from "path";
|
|
32
70
|
import os from "os";
|
|
33
71
|
function resolveDataDir() {
|
|
@@ -35,7 +73,7 @@ function resolveDataDir() {
|
|
|
35
73
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
36
74
|
const newDir = path.join(os.homedir(), ".exe-os");
|
|
37
75
|
const legacyDir = path.join(os.homedir(), ".exe-mem");
|
|
38
|
-
if (!
|
|
76
|
+
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
39
77
|
try {
|
|
40
78
|
renameSync(legacyDir, newDir);
|
|
41
79
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -98,9 +136,9 @@ function normalizeAutoUpdate(raw) {
|
|
|
98
136
|
}
|
|
99
137
|
async function loadConfig() {
|
|
100
138
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
101
|
-
await
|
|
139
|
+
await ensurePrivateDir(dir);
|
|
102
140
|
const configPath = path.join(dir, "config.json");
|
|
103
|
-
if (!
|
|
141
|
+
if (!existsSync2(configPath)) {
|
|
104
142
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
105
143
|
}
|
|
106
144
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -113,6 +151,7 @@ async function loadConfig() {
|
|
|
113
151
|
`);
|
|
114
152
|
try {
|
|
115
153
|
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
154
|
+
await enforcePrivateFile(configPath);
|
|
116
155
|
} catch {
|
|
117
156
|
}
|
|
118
157
|
}
|
|
@@ -132,6 +171,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
|
|
|
132
171
|
var init_config = __esm({
|
|
133
172
|
"src/lib/config.ts"() {
|
|
134
173
|
"use strict";
|
|
174
|
+
init_secure_files();
|
|
135
175
|
EXE_AI_DIR = resolveDataDir();
|
|
136
176
|
DB_PATH = path.join(EXE_AI_DIR, "memories.db");
|
|
137
177
|
MODELS_DIR = path.join(EXE_AI_DIR, "models");
|
|
@@ -278,7 +318,7 @@ var init_session_key = __esm({
|
|
|
278
318
|
|
|
279
319
|
// src/lib/employees.ts
|
|
280
320
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
281
|
-
import { existsSync as
|
|
321
|
+
import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
|
|
282
322
|
import { execSync as execSync2 } from "child_process";
|
|
283
323
|
import path2 from "path";
|
|
284
324
|
import os2 from "os";
|
|
@@ -302,7 +342,7 @@ function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
|
|
|
302
342
|
return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
|
|
303
343
|
}
|
|
304
344
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
305
|
-
if (!
|
|
345
|
+
if (!existsSync3(employeesPath)) return [];
|
|
306
346
|
try {
|
|
307
347
|
return JSON.parse(readFileSync2(employeesPath, "utf-8"));
|
|
308
348
|
} catch {
|
|
@@ -340,13 +380,13 @@ var init_employees = __esm({
|
|
|
340
380
|
});
|
|
341
381
|
|
|
342
382
|
// src/lib/session-registry.ts
|
|
343
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as
|
|
383
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync4 } from "fs";
|
|
344
384
|
import path4 from "path";
|
|
345
385
|
import os3 from "os";
|
|
346
386
|
function registerSession(entry) {
|
|
347
387
|
const dir = path4.dirname(REGISTRY_PATH);
|
|
348
|
-
if (!
|
|
349
|
-
|
|
388
|
+
if (!existsSync4(dir)) {
|
|
389
|
+
mkdirSync3(dir, { recursive: true });
|
|
350
390
|
}
|
|
351
391
|
const sessions = listSessions();
|
|
352
392
|
const idx = sessions.findIndex((s) => s.windowName === entry.windowName);
|
|
@@ -581,10 +621,10 @@ var init_runtime_table = __esm({
|
|
|
581
621
|
});
|
|
582
622
|
|
|
583
623
|
// src/lib/agent-config.ts
|
|
584
|
-
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as
|
|
624
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync5 } from "fs";
|
|
585
625
|
import path5 from "path";
|
|
586
626
|
function loadAgentConfig() {
|
|
587
|
-
if (!
|
|
627
|
+
if (!existsSync5(AGENT_CONFIG_PATH)) return {};
|
|
588
628
|
try {
|
|
589
629
|
return JSON.parse(readFileSync5(AGENT_CONFIG_PATH, "utf-8"));
|
|
590
630
|
} catch {
|
|
@@ -605,6 +645,7 @@ var init_agent_config = __esm({
|
|
|
605
645
|
"use strict";
|
|
606
646
|
init_config();
|
|
607
647
|
init_runtime_table();
|
|
648
|
+
init_secure_files();
|
|
608
649
|
AGENT_CONFIG_PATH = path5.join(EXE_AI_DIR, "agent-config.json");
|
|
609
650
|
DEFAULT_MODELS = {
|
|
610
651
|
claude: "claude-opus-4",
|
|
@@ -623,16 +664,16 @@ __export(intercom_queue_exports, {
|
|
|
623
664
|
queueIntercom: () => queueIntercom,
|
|
624
665
|
readQueue: () => readQueue
|
|
625
666
|
});
|
|
626
|
-
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, renameSync as renameSync3, existsSync as
|
|
667
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, renameSync as renameSync3, existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
|
|
627
668
|
import path6 from "path";
|
|
628
669
|
import os4 from "os";
|
|
629
670
|
function ensureDir() {
|
|
630
671
|
const dir = path6.dirname(QUEUE_PATH);
|
|
631
|
-
if (!
|
|
672
|
+
if (!existsSync6(dir)) mkdirSync4(dir, { recursive: true });
|
|
632
673
|
}
|
|
633
674
|
function readQueue() {
|
|
634
675
|
try {
|
|
635
|
-
if (!
|
|
676
|
+
if (!existsSync6(QUEUE_PATH)) return [];
|
|
636
677
|
return JSON.parse(readFileSync6(QUEUE_PATH, "utf8"));
|
|
637
678
|
} catch {
|
|
638
679
|
return [];
|
|
@@ -1379,13 +1420,50 @@ var init_database_adapter = __esm({
|
|
|
1379
1420
|
}
|
|
1380
1421
|
});
|
|
1381
1422
|
|
|
1423
|
+
// src/lib/daemon-auth.ts
|
|
1424
|
+
import crypto from "crypto";
|
|
1425
|
+
import path8 from "path";
|
|
1426
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
|
|
1427
|
+
function normalizeToken(token) {
|
|
1428
|
+
if (!token) return null;
|
|
1429
|
+
const trimmed = token.trim();
|
|
1430
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
1431
|
+
}
|
|
1432
|
+
function readDaemonToken() {
|
|
1433
|
+
try {
|
|
1434
|
+
if (!existsSync7(DAEMON_TOKEN_PATH)) return null;
|
|
1435
|
+
return normalizeToken(readFileSync7(DAEMON_TOKEN_PATH, "utf8"));
|
|
1436
|
+
} catch {
|
|
1437
|
+
return null;
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
function ensureDaemonToken(seed) {
|
|
1441
|
+
const existing = readDaemonToken();
|
|
1442
|
+
if (existing) return existing;
|
|
1443
|
+
const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
|
|
1444
|
+
ensurePrivateDirSync(EXE_AI_DIR);
|
|
1445
|
+
writeFileSync6(DAEMON_TOKEN_PATH, `${token}
|
|
1446
|
+
`, "utf8");
|
|
1447
|
+
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
1448
|
+
return token;
|
|
1449
|
+
}
|
|
1450
|
+
var DAEMON_TOKEN_PATH;
|
|
1451
|
+
var init_daemon_auth = __esm({
|
|
1452
|
+
"src/lib/daemon-auth.ts"() {
|
|
1453
|
+
"use strict";
|
|
1454
|
+
init_config();
|
|
1455
|
+
init_secure_files();
|
|
1456
|
+
DAEMON_TOKEN_PATH = path8.join(EXE_AI_DIR, "exed.token");
|
|
1457
|
+
}
|
|
1458
|
+
});
|
|
1459
|
+
|
|
1382
1460
|
// src/lib/exe-daemon-client.ts
|
|
1383
1461
|
import net from "net";
|
|
1384
1462
|
import os6 from "os";
|
|
1385
1463
|
import { spawn } from "child_process";
|
|
1386
1464
|
import { randomUUID } from "crypto";
|
|
1387
|
-
import { existsSync as
|
|
1388
|
-
import
|
|
1465
|
+
import { existsSync as existsSync8, unlinkSync as unlinkSync3, readFileSync as readFileSync8, openSync, closeSync, statSync } from "fs";
|
|
1466
|
+
import path9 from "path";
|
|
1389
1467
|
import { fileURLToPath } from "url";
|
|
1390
1468
|
function handleData(chunk) {
|
|
1391
1469
|
_buffer += chunk.toString();
|
|
@@ -1413,9 +1491,9 @@ function handleData(chunk) {
|
|
|
1413
1491
|
}
|
|
1414
1492
|
}
|
|
1415
1493
|
function cleanupStaleFiles() {
|
|
1416
|
-
if (
|
|
1494
|
+
if (existsSync8(PID_PATH)) {
|
|
1417
1495
|
try {
|
|
1418
|
-
const pid = parseInt(
|
|
1496
|
+
const pid = parseInt(readFileSync8(PID_PATH, "utf8").trim(), 10);
|
|
1419
1497
|
if (pid > 0) {
|
|
1420
1498
|
try {
|
|
1421
1499
|
process.kill(pid, 0);
|
|
@@ -1436,11 +1514,11 @@ function cleanupStaleFiles() {
|
|
|
1436
1514
|
}
|
|
1437
1515
|
}
|
|
1438
1516
|
function findPackageRoot() {
|
|
1439
|
-
let dir =
|
|
1440
|
-
const { root } =
|
|
1517
|
+
let dir = path9.dirname(fileURLToPath(import.meta.url));
|
|
1518
|
+
const { root } = path9.parse(dir);
|
|
1441
1519
|
while (dir !== root) {
|
|
1442
|
-
if (
|
|
1443
|
-
dir =
|
|
1520
|
+
if (existsSync8(path9.join(dir, "package.json"))) return dir;
|
|
1521
|
+
dir = path9.dirname(dir);
|
|
1444
1522
|
}
|
|
1445
1523
|
return null;
|
|
1446
1524
|
}
|
|
@@ -1466,16 +1544,17 @@ function spawnDaemon() {
|
|
|
1466
1544
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
1467
1545
|
return;
|
|
1468
1546
|
}
|
|
1469
|
-
const daemonPath =
|
|
1470
|
-
if (!
|
|
1547
|
+
const daemonPath = path9.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
1548
|
+
if (!existsSync8(daemonPath)) {
|
|
1471
1549
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
1472
1550
|
`);
|
|
1473
1551
|
return;
|
|
1474
1552
|
}
|
|
1475
1553
|
const resolvedPath = daemonPath;
|
|
1554
|
+
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
1476
1555
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
1477
1556
|
`);
|
|
1478
|
-
const logPath =
|
|
1557
|
+
const logPath = path9.join(path9.dirname(SOCKET_PATH), "exed.log");
|
|
1479
1558
|
let stderrFd = "ignore";
|
|
1480
1559
|
try {
|
|
1481
1560
|
stderrFd = openSync(logPath, "a");
|
|
@@ -1493,7 +1572,8 @@ function spawnDaemon() {
|
|
|
1493
1572
|
TMUX_PANE: void 0,
|
|
1494
1573
|
// Prevents resolveExeSession() from scoping to one session
|
|
1495
1574
|
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
1496
|
-
EXE_DAEMON_PID: PID_PATH
|
|
1575
|
+
EXE_DAEMON_PID: PID_PATH,
|
|
1576
|
+
[DAEMON_TOKEN_ENV]: daemonToken
|
|
1497
1577
|
}
|
|
1498
1578
|
});
|
|
1499
1579
|
child.unref();
|
|
@@ -1600,13 +1680,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
1600
1680
|
return;
|
|
1601
1681
|
}
|
|
1602
1682
|
const id = randomUUID();
|
|
1683
|
+
const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
|
|
1603
1684
|
const timer = setTimeout(() => {
|
|
1604
1685
|
_pending.delete(id);
|
|
1605
1686
|
resolve({ error: "Request timeout" });
|
|
1606
1687
|
}, timeoutMs);
|
|
1607
1688
|
_pending.set(id, { resolve, timer });
|
|
1608
1689
|
try {
|
|
1609
|
-
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
1690
|
+
_socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
|
|
1610
1691
|
} catch {
|
|
1611
1692
|
clearTimeout(timer);
|
|
1612
1693
|
_pending.delete(id);
|
|
@@ -1617,17 +1698,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
1617
1698
|
function isClientConnected() {
|
|
1618
1699
|
return _connected;
|
|
1619
1700
|
}
|
|
1620
|
-
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
|
|
1701
|
+
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;
|
|
1621
1702
|
var init_exe_daemon_client = __esm({
|
|
1622
1703
|
"src/lib/exe-daemon-client.ts"() {
|
|
1623
1704
|
"use strict";
|
|
1624
1705
|
init_config();
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1706
|
+
init_daemon_auth();
|
|
1707
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path9.join(EXE_AI_DIR, "exed.sock");
|
|
1708
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path9.join(EXE_AI_DIR, "exed.pid");
|
|
1709
|
+
SPAWN_LOCK_PATH = path9.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
1628
1710
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
1629
1711
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
1630
1712
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
1713
|
+
DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
|
|
1631
1714
|
_socket = null;
|
|
1632
1715
|
_connected = false;
|
|
1633
1716
|
_buffer = "";
|
|
@@ -2206,6 +2289,7 @@ async function ensureSchema() {
|
|
|
2206
2289
|
project TEXT NOT NULL,
|
|
2207
2290
|
summary TEXT NOT NULL,
|
|
2208
2291
|
task_file TEXT,
|
|
2292
|
+
session_scope TEXT,
|
|
2209
2293
|
read INTEGER NOT NULL DEFAULT 0,
|
|
2210
2294
|
created_at TEXT NOT NULL
|
|
2211
2295
|
);
|
|
@@ -2214,7 +2298,7 @@ async function ensureSchema() {
|
|
|
2214
2298
|
ON notifications(read);
|
|
2215
2299
|
|
|
2216
2300
|
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
2217
|
-
ON notifications(agent_id);
|
|
2301
|
+
ON notifications(agent_id, session_scope);
|
|
2218
2302
|
|
|
2219
2303
|
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
2220
2304
|
ON notifications(task_file);
|
|
@@ -2252,6 +2336,7 @@ async function ensureSchema() {
|
|
|
2252
2336
|
target_agent TEXT NOT NULL,
|
|
2253
2337
|
target_project TEXT,
|
|
2254
2338
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
2339
|
+
session_scope TEXT,
|
|
2255
2340
|
content TEXT NOT NULL,
|
|
2256
2341
|
priority TEXT DEFAULT 'normal',
|
|
2257
2342
|
status TEXT DEFAULT 'pending',
|
|
@@ -2265,10 +2350,31 @@ async function ensureSchema() {
|
|
|
2265
2350
|
);
|
|
2266
2351
|
|
|
2267
2352
|
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
2268
|
-
ON messages(target_agent, status);
|
|
2353
|
+
ON messages(target_agent, session_scope, status);
|
|
2269
2354
|
|
|
2270
2355
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
2271
|
-
ON messages(target_agent, from_agent, server_seq);
|
|
2356
|
+
ON messages(target_agent, session_scope, from_agent, server_seq);
|
|
2357
|
+
`);
|
|
2358
|
+
try {
|
|
2359
|
+
await client.execute({
|
|
2360
|
+
sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
|
|
2361
|
+
args: []
|
|
2362
|
+
});
|
|
2363
|
+
} catch {
|
|
2364
|
+
}
|
|
2365
|
+
try {
|
|
2366
|
+
await client.execute({
|
|
2367
|
+
sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
|
|
2368
|
+
args: []
|
|
2369
|
+
});
|
|
2370
|
+
} catch {
|
|
2371
|
+
}
|
|
2372
|
+
await client.executeMultiple(`
|
|
2373
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
|
|
2374
|
+
ON notifications(agent_id, session_scope, read, created_at);
|
|
2375
|
+
|
|
2376
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
|
|
2377
|
+
ON messages(target_agent, session_scope, status, created_at);
|
|
2272
2378
|
`);
|
|
2273
2379
|
try {
|
|
2274
2380
|
await client.execute({
|
|
@@ -2852,6 +2958,13 @@ async function ensureSchema() {
|
|
|
2852
2958
|
} catch {
|
|
2853
2959
|
}
|
|
2854
2960
|
}
|
|
2961
|
+
try {
|
|
2962
|
+
await client.execute({
|
|
2963
|
+
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
2964
|
+
args: []
|
|
2965
|
+
});
|
|
2966
|
+
} catch {
|
|
2967
|
+
}
|
|
2855
2968
|
}
|
|
2856
2969
|
async function disposeDatabase() {
|
|
2857
2970
|
if (_walCheckpointTimer) {
|
|
@@ -2890,18 +3003,21 @@ var init_database = __esm({
|
|
|
2890
3003
|
});
|
|
2891
3004
|
|
|
2892
3005
|
// src/lib/license.ts
|
|
2893
|
-
import { readFileSync as
|
|
3006
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync7, existsSync as existsSync9, mkdirSync as mkdirSync5 } from "fs";
|
|
2894
3007
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
2895
|
-
import
|
|
3008
|
+
import { createRequire as createRequire2 } from "module";
|
|
3009
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
3010
|
+
import os7 from "os";
|
|
3011
|
+
import path10 from "path";
|
|
2896
3012
|
import { jwtVerify, importSPKI } from "jose";
|
|
2897
3013
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
|
|
2898
3014
|
var init_license = __esm({
|
|
2899
3015
|
"src/lib/license.ts"() {
|
|
2900
3016
|
"use strict";
|
|
2901
3017
|
init_config();
|
|
2902
|
-
LICENSE_PATH =
|
|
2903
|
-
CACHE_PATH =
|
|
2904
|
-
DEVICE_ID_PATH =
|
|
3018
|
+
LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
|
|
3019
|
+
CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
|
|
3020
|
+
DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
|
|
2905
3021
|
PLAN_LIMITS = {
|
|
2906
3022
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
2907
3023
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -2913,12 +3029,12 @@ var init_license = __esm({
|
|
|
2913
3029
|
});
|
|
2914
3030
|
|
|
2915
3031
|
// src/lib/plan-limits.ts
|
|
2916
|
-
import { readFileSync as
|
|
2917
|
-
import
|
|
3032
|
+
import { readFileSync as readFileSync10, existsSync as existsSync10 } from "fs";
|
|
3033
|
+
import path11 from "path";
|
|
2918
3034
|
function getLicenseSync() {
|
|
2919
3035
|
try {
|
|
2920
|
-
if (!
|
|
2921
|
-
const raw = JSON.parse(
|
|
3036
|
+
if (!existsSync10(CACHE_PATH2)) return freeLicense();
|
|
3037
|
+
const raw = JSON.parse(readFileSync10(CACHE_PATH2, "utf8"));
|
|
2922
3038
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
2923
3039
|
const parts = raw.token.split(".");
|
|
2924
3040
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -2956,8 +3072,8 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
2956
3072
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
2957
3073
|
let count = 0;
|
|
2958
3074
|
try {
|
|
2959
|
-
if (
|
|
2960
|
-
const raw =
|
|
3075
|
+
if (existsSync10(filePath)) {
|
|
3076
|
+
const raw = readFileSync10(filePath, "utf8");
|
|
2961
3077
|
const employees = JSON.parse(raw);
|
|
2962
3078
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
2963
3079
|
}
|
|
@@ -2986,7 +3102,7 @@ var init_plan_limits = __esm({
|
|
|
2986
3102
|
this.name = "PlanLimitError";
|
|
2987
3103
|
}
|
|
2988
3104
|
};
|
|
2989
|
-
CACHE_PATH2 =
|
|
3105
|
+
CACHE_PATH2 = path11.join(EXE_AI_DIR, "license-cache.json");
|
|
2990
3106
|
}
|
|
2991
3107
|
});
|
|
2992
3108
|
|
|
@@ -3002,24 +3118,25 @@ __export(notifications_exports, {
|
|
|
3002
3118
|
readUnreadNotifications: () => readUnreadNotifications,
|
|
3003
3119
|
writeNotification: () => writeNotification
|
|
3004
3120
|
});
|
|
3005
|
-
import
|
|
3006
|
-
import
|
|
3007
|
-
import
|
|
3121
|
+
import crypto2 from "crypto";
|
|
3122
|
+
import path12 from "path";
|
|
3123
|
+
import os8 from "os";
|
|
3008
3124
|
import {
|
|
3009
|
-
readFileSync as
|
|
3125
|
+
readFileSync as readFileSync11,
|
|
3010
3126
|
readdirSync as readdirSync2,
|
|
3011
3127
|
unlinkSync as unlinkSync4,
|
|
3012
|
-
existsSync as
|
|
3128
|
+
existsSync as existsSync11,
|
|
3013
3129
|
rmdirSync
|
|
3014
3130
|
} from "fs";
|
|
3015
3131
|
async function writeNotification(notification) {
|
|
3016
3132
|
try {
|
|
3017
3133
|
const client = getClient();
|
|
3018
|
-
const id =
|
|
3134
|
+
const id = crypto2.randomUUID();
|
|
3019
3135
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3136
|
+
const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
|
|
3020
3137
|
await client.execute({
|
|
3021
|
-
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
|
|
3022
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
3138
|
+
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
3139
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
3023
3140
|
args: [
|
|
3024
3141
|
id,
|
|
3025
3142
|
notification.agentId,
|
|
@@ -3028,6 +3145,7 @@ async function writeNotification(notification) {
|
|
|
3028
3145
|
notification.project,
|
|
3029
3146
|
notification.summary,
|
|
3030
3147
|
notification.taskFile ?? null,
|
|
3148
|
+
sessionScope,
|
|
3031
3149
|
now
|
|
3032
3150
|
]
|
|
3033
3151
|
});
|
|
@@ -3036,21 +3154,22 @@ async function writeNotification(notification) {
|
|
|
3036
3154
|
`);
|
|
3037
3155
|
}
|
|
3038
3156
|
}
|
|
3039
|
-
async function readUnreadNotifications(agentFilter) {
|
|
3157
|
+
async function readUnreadNotifications(agentFilter, sessionScope) {
|
|
3040
3158
|
try {
|
|
3041
3159
|
const client = getClient();
|
|
3042
3160
|
const conditions = ["read = 0"];
|
|
3043
3161
|
const args = [];
|
|
3162
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
3044
3163
|
if (agentFilter) {
|
|
3045
3164
|
conditions.push("agent_id = ?");
|
|
3046
3165
|
args.push(agentFilter);
|
|
3047
3166
|
}
|
|
3048
3167
|
const result = await client.execute({
|
|
3049
|
-
sql: `SELECT id, agent_id, agent_role, event, project, summary, task_file, created_at
|
|
3168
|
+
sql: `SELECT id, agent_id, agent_role, event, project, summary, task_file, session_scope, created_at
|
|
3050
3169
|
FROM notifications
|
|
3051
|
-
WHERE ${conditions.join(" AND ")}
|
|
3170
|
+
WHERE ${conditions.join(" AND ")}${scope.sql}
|
|
3052
3171
|
ORDER BY created_at ASC`,
|
|
3053
|
-
args
|
|
3172
|
+
args: [...args, ...scope.args]
|
|
3054
3173
|
});
|
|
3055
3174
|
return result.rows.map((r) => ({
|
|
3056
3175
|
id: String(r.id),
|
|
@@ -3060,6 +3179,7 @@ async function readUnreadNotifications(agentFilter) {
|
|
|
3060
3179
|
project: String(r.project),
|
|
3061
3180
|
summary: String(r.summary),
|
|
3062
3181
|
taskFile: r.task_file ? String(r.task_file) : void 0,
|
|
3182
|
+
sessionScope: r.session_scope == null ? null : String(r.session_scope),
|
|
3063
3183
|
timestamp: String(r.created_at),
|
|
3064
3184
|
read: false
|
|
3065
3185
|
}));
|
|
@@ -3067,54 +3187,60 @@ async function readUnreadNotifications(agentFilter) {
|
|
|
3067
3187
|
return [];
|
|
3068
3188
|
}
|
|
3069
3189
|
}
|
|
3070
|
-
async function markAsRead(ids) {
|
|
3190
|
+
async function markAsRead(ids, sessionScope) {
|
|
3071
3191
|
if (ids.length === 0) return;
|
|
3072
3192
|
try {
|
|
3073
3193
|
const client = getClient();
|
|
3074
3194
|
const placeholders = ids.map(() => "?").join(", ");
|
|
3195
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
3075
3196
|
await client.execute({
|
|
3076
|
-
sql: `UPDATE notifications SET read = 1 WHERE id IN (${placeholders})`,
|
|
3077
|
-
args: ids
|
|
3197
|
+
sql: `UPDATE notifications SET read = 1 WHERE id IN (${placeholders})${scope.sql}`,
|
|
3198
|
+
args: [...ids, ...scope.args]
|
|
3078
3199
|
});
|
|
3079
3200
|
} catch {
|
|
3080
3201
|
}
|
|
3081
3202
|
}
|
|
3082
|
-
async function markAsReadByTaskFile(taskFile) {
|
|
3203
|
+
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
3083
3204
|
try {
|
|
3084
3205
|
const client = getClient();
|
|
3206
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
3085
3207
|
await client.execute({
|
|
3086
|
-
sql:
|
|
3087
|
-
|
|
3208
|
+
sql: `UPDATE notifications SET read = 1
|
|
3209
|
+
WHERE task_file = ? AND read = 0${scope.sql}`,
|
|
3210
|
+
args: [taskFile, ...scope.args]
|
|
3088
3211
|
});
|
|
3089
3212
|
} catch {
|
|
3090
3213
|
}
|
|
3091
3214
|
}
|
|
3092
|
-
async function cleanupOldNotifications(daysOld = CLEANUP_DAYS) {
|
|
3215
|
+
async function cleanupOldNotifications(daysOld = CLEANUP_DAYS, sessionScope) {
|
|
3093
3216
|
try {
|
|
3094
3217
|
const client = getClient();
|
|
3095
3218
|
const cutoff = new Date(
|
|
3096
3219
|
Date.now() - daysOld * 24 * 60 * 60 * 1e3
|
|
3097
3220
|
).toISOString();
|
|
3221
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
3098
3222
|
const result = await client.execute({
|
|
3099
|
-
sql:
|
|
3100
|
-
args: [cutoff]
|
|
3223
|
+
sql: `DELETE FROM notifications WHERE created_at < ?${scope.sql}`,
|
|
3224
|
+
args: [cutoff, ...scope.args]
|
|
3101
3225
|
});
|
|
3102
3226
|
return result.rowsAffected;
|
|
3103
3227
|
} catch {
|
|
3104
3228
|
return 0;
|
|
3105
3229
|
}
|
|
3106
3230
|
}
|
|
3107
|
-
async function markDoneTaskNotificationsAsRead() {
|
|
3231
|
+
async function markDoneTaskNotificationsAsRead(sessionScope) {
|
|
3108
3232
|
try {
|
|
3109
3233
|
const client = getClient();
|
|
3234
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
3110
3235
|
const result = await client.execute({
|
|
3111
3236
|
sql: `UPDATE notifications SET read = 1
|
|
3112
3237
|
WHERE read = 0
|
|
3113
3238
|
AND task_file IS NOT NULL
|
|
3239
|
+
${scope.sql}
|
|
3114
3240
|
AND task_file IN (
|
|
3115
|
-
SELECT task_file FROM tasks WHERE status = 'done'
|
|
3241
|
+
SELECT task_file FROM tasks WHERE status = 'done'${scope.sql}
|
|
3116
3242
|
)`,
|
|
3117
|
-
args: []
|
|
3243
|
+
args: [...scope.args, ...scope.args]
|
|
3118
3244
|
});
|
|
3119
3245
|
return result.rowsAffected;
|
|
3120
3246
|
} catch {
|
|
@@ -3145,9 +3271,9 @@ function formatNotifications(notifications) {
|
|
|
3145
3271
|
return lines.join("\n");
|
|
3146
3272
|
}
|
|
3147
3273
|
async function migrateJsonNotifications() {
|
|
3148
|
-
const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR ||
|
|
3149
|
-
const notifDir =
|
|
3150
|
-
if (!
|
|
3274
|
+
const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR || path12.join(os8.homedir(), ".exe-os");
|
|
3275
|
+
const notifDir = path12.join(base, "notifications");
|
|
3276
|
+
if (!existsSync11(notifDir)) return 0;
|
|
3151
3277
|
let migrated = 0;
|
|
3152
3278
|
try {
|
|
3153
3279
|
const files = readdirSync2(notifDir).filter((f) => f.endsWith(".json"));
|
|
@@ -3155,19 +3281,20 @@ async function migrateJsonNotifications() {
|
|
|
3155
3281
|
const client = getClient();
|
|
3156
3282
|
for (const file of files) {
|
|
3157
3283
|
try {
|
|
3158
|
-
const filePath =
|
|
3159
|
-
const data = JSON.parse(
|
|
3284
|
+
const filePath = path12.join(notifDir, file);
|
|
3285
|
+
const data = JSON.parse(readFileSync11(filePath, "utf8"));
|
|
3160
3286
|
await client.execute({
|
|
3161
|
-
sql: `INSERT OR IGNORE INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
|
|
3162
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
3287
|
+
sql: `INSERT OR IGNORE INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
3288
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
3163
3289
|
args: [
|
|
3164
|
-
|
|
3290
|
+
crypto2.randomUUID(),
|
|
3165
3291
|
data.agentId ?? "unknown",
|
|
3166
3292
|
data.agentRole ?? "unknown",
|
|
3167
3293
|
data.event ?? "session_summary",
|
|
3168
3294
|
data.project ?? "unknown",
|
|
3169
3295
|
data.summary ?? "",
|
|
3170
3296
|
data.taskFile ?? null,
|
|
3297
|
+
null,
|
|
3171
3298
|
data.read ? 1 : 0,
|
|
3172
3299
|
data.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
3173
3300
|
]
|
|
@@ -3221,12 +3348,13 @@ var init_notifications = __esm({
|
|
|
3221
3348
|
"src/lib/notifications.ts"() {
|
|
3222
3349
|
"use strict";
|
|
3223
3350
|
init_database();
|
|
3351
|
+
init_task_scope();
|
|
3224
3352
|
CLEANUP_DAYS = 7;
|
|
3225
3353
|
}
|
|
3226
3354
|
});
|
|
3227
3355
|
|
|
3228
3356
|
// src/lib/session-kill-telemetry.ts
|
|
3229
|
-
import
|
|
3357
|
+
import crypto3 from "crypto";
|
|
3230
3358
|
async function recordSessionKill(input2) {
|
|
3231
3359
|
try {
|
|
3232
3360
|
const client = getClient();
|
|
@@ -3236,7 +3364,7 @@ async function recordSessionKill(input2) {
|
|
|
3236
3364
|
ticks_idle, estimated_tokens_saved)
|
|
3237
3365
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
3238
3366
|
args: [
|
|
3239
|
-
|
|
3367
|
+
crypto3.randomUUID(),
|
|
3240
3368
|
input2.sessionName,
|
|
3241
3369
|
input2.agentId,
|
|
3242
3370
|
(/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -3314,6 +3442,110 @@ var init_state_bus = __esm({
|
|
|
3314
3442
|
}
|
|
3315
3443
|
});
|
|
3316
3444
|
|
|
3445
|
+
// src/lib/project-name.ts
|
|
3446
|
+
import { execSync as execSync5 } from "child_process";
|
|
3447
|
+
import path13 from "path";
|
|
3448
|
+
function getProjectName(cwd) {
|
|
3449
|
+
const dir = cwd ?? process.cwd();
|
|
3450
|
+
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
3451
|
+
try {
|
|
3452
|
+
let repoRoot;
|
|
3453
|
+
try {
|
|
3454
|
+
const gitCommonDir = execSync5("git rev-parse --path-format=absolute --git-common-dir", {
|
|
3455
|
+
cwd: dir,
|
|
3456
|
+
encoding: "utf8",
|
|
3457
|
+
timeout: 2e3,
|
|
3458
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3459
|
+
}).trim();
|
|
3460
|
+
repoRoot = path13.dirname(gitCommonDir);
|
|
3461
|
+
} catch {
|
|
3462
|
+
repoRoot = execSync5("git rev-parse --show-toplevel", {
|
|
3463
|
+
cwd: dir,
|
|
3464
|
+
encoding: "utf8",
|
|
3465
|
+
timeout: 2e3,
|
|
3466
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3467
|
+
}).trim();
|
|
3468
|
+
}
|
|
3469
|
+
_cached2 = path13.basename(repoRoot);
|
|
3470
|
+
_cachedCwd = dir;
|
|
3471
|
+
return _cached2;
|
|
3472
|
+
} catch {
|
|
3473
|
+
_cached2 = path13.basename(dir);
|
|
3474
|
+
_cachedCwd = dir;
|
|
3475
|
+
return _cached2;
|
|
3476
|
+
}
|
|
3477
|
+
}
|
|
3478
|
+
var _cached2, _cachedCwd;
|
|
3479
|
+
var init_project_name = __esm({
|
|
3480
|
+
"src/lib/project-name.ts"() {
|
|
3481
|
+
"use strict";
|
|
3482
|
+
_cached2 = null;
|
|
3483
|
+
_cachedCwd = null;
|
|
3484
|
+
}
|
|
3485
|
+
});
|
|
3486
|
+
|
|
3487
|
+
// src/lib/session-scope.ts
|
|
3488
|
+
var session_scope_exports = {};
|
|
3489
|
+
__export(session_scope_exports, {
|
|
3490
|
+
assertSessionScope: () => assertSessionScope,
|
|
3491
|
+
findSessionForProject: () => findSessionForProject,
|
|
3492
|
+
getSessionProject: () => getSessionProject
|
|
3493
|
+
});
|
|
3494
|
+
function getSessionProject(sessionName) {
|
|
3495
|
+
const sessions = listSessions();
|
|
3496
|
+
const entry = sessions.find((s) => s.windowName === sessionName);
|
|
3497
|
+
if (!entry) return null;
|
|
3498
|
+
const parts = entry.projectDir.split("/").filter(Boolean);
|
|
3499
|
+
return parts[parts.length - 1] ?? null;
|
|
3500
|
+
}
|
|
3501
|
+
function findSessionForProject(projectName) {
|
|
3502
|
+
const sessions = listSessions();
|
|
3503
|
+
for (const s of sessions) {
|
|
3504
|
+
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
3505
|
+
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
3506
|
+
}
|
|
3507
|
+
return null;
|
|
3508
|
+
}
|
|
3509
|
+
function assertSessionScope(actionType, targetProject) {
|
|
3510
|
+
try {
|
|
3511
|
+
const currentProject = getProjectName();
|
|
3512
|
+
const exeSession = resolveExeSession();
|
|
3513
|
+
if (!exeSession) {
|
|
3514
|
+
return { allowed: true, reason: "no_session" };
|
|
3515
|
+
}
|
|
3516
|
+
if (currentProject === targetProject) {
|
|
3517
|
+
return {
|
|
3518
|
+
allowed: true,
|
|
3519
|
+
reason: "same_session",
|
|
3520
|
+
currentProject,
|
|
3521
|
+
targetProject
|
|
3522
|
+
};
|
|
3523
|
+
}
|
|
3524
|
+
process.stderr.write(
|
|
3525
|
+
`[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
|
|
3526
|
+
`
|
|
3527
|
+
);
|
|
3528
|
+
return {
|
|
3529
|
+
allowed: false,
|
|
3530
|
+
reason: "cross_session_denied",
|
|
3531
|
+
currentProject,
|
|
3532
|
+
targetProject,
|
|
3533
|
+
targetSession: findSessionForProject(targetProject)?.windowName
|
|
3534
|
+
};
|
|
3535
|
+
} catch {
|
|
3536
|
+
return { allowed: true, reason: "no_session" };
|
|
3537
|
+
}
|
|
3538
|
+
}
|
|
3539
|
+
var init_session_scope = __esm({
|
|
3540
|
+
"src/lib/session-scope.ts"() {
|
|
3541
|
+
"use strict";
|
|
3542
|
+
init_session_registry();
|
|
3543
|
+
init_project_name();
|
|
3544
|
+
init_tmux_routing();
|
|
3545
|
+
init_employees();
|
|
3546
|
+
}
|
|
3547
|
+
});
|
|
3548
|
+
|
|
3317
3549
|
// src/lib/tasks-crud.ts
|
|
3318
3550
|
var tasks_crud_exports = {};
|
|
3319
3551
|
__export(tasks_crud_exports, {
|
|
@@ -3331,12 +3563,12 @@ __export(tasks_crud_exports, {
|
|
|
3331
3563
|
updateTaskStatus: () => updateTaskStatus,
|
|
3332
3564
|
writeCheckpoint: () => writeCheckpoint
|
|
3333
3565
|
});
|
|
3334
|
-
import
|
|
3335
|
-
import
|
|
3336
|
-
import
|
|
3337
|
-
import { execSync as
|
|
3566
|
+
import crypto4 from "crypto";
|
|
3567
|
+
import path14 from "path";
|
|
3568
|
+
import os9 from "os";
|
|
3569
|
+
import { execSync as execSync6 } from "child_process";
|
|
3338
3570
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
3339
|
-
import { existsSync as
|
|
3571
|
+
import { existsSync as existsSync12, readFileSync as readFileSync12 } from "fs";
|
|
3340
3572
|
async function writeCheckpoint(input2) {
|
|
3341
3573
|
const client = getClient();
|
|
3342
3574
|
const row = await resolveTask(client, input2.taskId);
|
|
@@ -3452,13 +3684,28 @@ async function resolveTask(client, identifier, scopeSession) {
|
|
|
3452
3684
|
}
|
|
3453
3685
|
async function createTaskCore(input2) {
|
|
3454
3686
|
const client = getClient();
|
|
3455
|
-
const id =
|
|
3687
|
+
const id = crypto4.randomUUID();
|
|
3456
3688
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3457
3689
|
const slug = slugify(input2.title);
|
|
3458
3690
|
let earlySessionScope = null;
|
|
3691
|
+
let scopeMismatchWarning;
|
|
3459
3692
|
try {
|
|
3460
3693
|
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
3461
|
-
|
|
3694
|
+
const resolved = resolveExeSession2();
|
|
3695
|
+
if (resolved && input2.projectName) {
|
|
3696
|
+
const { getSessionProject: getSessionProject2 } = await Promise.resolve().then(() => (init_session_scope(), session_scope_exports));
|
|
3697
|
+
const sessionProject = getSessionProject2(resolved);
|
|
3698
|
+
if (sessionProject && sessionProject !== input2.projectName) {
|
|
3699
|
+
scopeMismatchWarning = `session/project mismatch: session "${resolved}" owns "${sessionProject}" but task targets "${input2.projectName}". Routed to default scope.`;
|
|
3700
|
+
process.stderr.write(`[create_task] ${scopeMismatchWarning}
|
|
3701
|
+
`);
|
|
3702
|
+
earlySessionScope = null;
|
|
3703
|
+
} else {
|
|
3704
|
+
earlySessionScope = resolved;
|
|
3705
|
+
}
|
|
3706
|
+
} else {
|
|
3707
|
+
earlySessionScope = resolved;
|
|
3708
|
+
}
|
|
3462
3709
|
} catch {
|
|
3463
3710
|
}
|
|
3464
3711
|
const scope = earlySessionScope ?? "default";
|
|
@@ -3509,10 +3756,14 @@ async function createTaskCore(input2) {
|
|
|
3509
3756
|
${laneWarning}` : laneWarning;
|
|
3510
3757
|
}
|
|
3511
3758
|
}
|
|
3759
|
+
if (scopeMismatchWarning) {
|
|
3760
|
+
warning = warning ? `${warning}
|
|
3761
|
+
${scopeMismatchWarning}` : scopeMismatchWarning;
|
|
3762
|
+
}
|
|
3512
3763
|
if (input2.baseDir) {
|
|
3513
3764
|
try {
|
|
3514
|
-
await mkdir3(
|
|
3515
|
-
await mkdir3(
|
|
3765
|
+
await mkdir3(path14.join(input2.baseDir, "exe", "output"), { recursive: true });
|
|
3766
|
+
await mkdir3(path14.join(input2.baseDir, "exe", "research"), { recursive: true });
|
|
3516
3767
|
await ensureArchitectureDoc(input2.baseDir, input2.projectName);
|
|
3517
3768
|
await ensureGitignoreExe(input2.baseDir);
|
|
3518
3769
|
} catch {
|
|
@@ -3548,13 +3799,19 @@ ${laneWarning}` : laneWarning;
|
|
|
3548
3799
|
});
|
|
3549
3800
|
if (input2.baseDir) {
|
|
3550
3801
|
try {
|
|
3551
|
-
const EXE_OS_DIR =
|
|
3552
|
-
const mdPath =
|
|
3553
|
-
const mdDir =
|
|
3554
|
-
if (!
|
|
3802
|
+
const EXE_OS_DIR = path14.join(os9.homedir(), ".exe-os");
|
|
3803
|
+
const mdPath = path14.join(EXE_OS_DIR, taskFile);
|
|
3804
|
+
const mdDir = path14.dirname(mdPath);
|
|
3805
|
+
if (!existsSync12(mdDir)) await mkdir3(mdDir, { recursive: true });
|
|
3555
3806
|
const reviewer = input2.reviewer ?? input2.assignedBy;
|
|
3556
3807
|
const mdContent = `# ${input2.title}
|
|
3557
3808
|
|
|
3809
|
+
## MANDATORY: When done
|
|
3810
|
+
|
|
3811
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
3812
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
3813
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
3814
|
+
|
|
3558
3815
|
**ID:** ${id}
|
|
3559
3816
|
**Status:** ${initialStatus}
|
|
3560
3817
|
**Priority:** ${input2.priority}
|
|
@@ -3568,12 +3825,6 @@ ${laneWarning}` : laneWarning;
|
|
|
3568
3825
|
## Context
|
|
3569
3826
|
|
|
3570
3827
|
${input2.context}
|
|
3571
|
-
|
|
3572
|
-
## MANDATORY: When done
|
|
3573
|
-
|
|
3574
|
-
You MUST call update_task with status "done" and a result summary when finished.
|
|
3575
|
-
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
3576
|
-
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
3577
3828
|
`;
|
|
3578
3829
|
await writeFile3(mdPath, mdContent, "utf-8");
|
|
3579
3830
|
} catch (err) {
|
|
@@ -3655,14 +3906,14 @@ function isTmuxSessionAlive(identifier) {
|
|
|
3655
3906
|
if (!identifier || identifier === "unknown") return true;
|
|
3656
3907
|
try {
|
|
3657
3908
|
if (identifier.startsWith("%")) {
|
|
3658
|
-
const output =
|
|
3909
|
+
const output = execSync6("tmux list-panes -a -F '#{pane_id}'", {
|
|
3659
3910
|
timeout: 2e3,
|
|
3660
3911
|
encoding: "utf8",
|
|
3661
3912
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3662
3913
|
});
|
|
3663
3914
|
return output.split("\n").some((l) => l.trim() === identifier);
|
|
3664
3915
|
} else {
|
|
3665
|
-
|
|
3916
|
+
execSync6(`tmux has-session -t ${JSON.stringify(identifier)}`, {
|
|
3666
3917
|
timeout: 2e3,
|
|
3667
3918
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3668
3919
|
});
|
|
@@ -3671,7 +3922,7 @@ function isTmuxSessionAlive(identifier) {
|
|
|
3671
3922
|
} catch {
|
|
3672
3923
|
if (identifier.startsWith("%")) return true;
|
|
3673
3924
|
try {
|
|
3674
|
-
|
|
3925
|
+
execSync6("tmux list-sessions", {
|
|
3675
3926
|
timeout: 2e3,
|
|
3676
3927
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3677
3928
|
});
|
|
@@ -3686,12 +3937,12 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
|
|
|
3686
3937
|
if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
|
|
3687
3938
|
try {
|
|
3688
3939
|
const since = new Date(taskCreatedAt).toISOString();
|
|
3689
|
-
const branch =
|
|
3940
|
+
const branch = execSync6(
|
|
3690
3941
|
"git rev-parse --abbrev-ref HEAD 2>/dev/null",
|
|
3691
3942
|
{ encoding: "utf8", timeout: 3e3 }
|
|
3692
3943
|
).trim();
|
|
3693
3944
|
const branchArg = branch && branch !== "HEAD" ? branch : "";
|
|
3694
|
-
const commitCount =
|
|
3945
|
+
const commitCount = execSync6(
|
|
3695
3946
|
`git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
|
|
3696
3947
|
{ encoding: "utf8", timeout: 5e3 }
|
|
3697
3948
|
).trim();
|
|
@@ -3822,7 +4073,7 @@ ${input2.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
3822
4073
|
await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
|
|
3823
4074
|
} catch {
|
|
3824
4075
|
}
|
|
3825
|
-
if (input2.status === "done" || input2.status === "cancelled") {
|
|
4076
|
+
if (input2.status === "done" || input2.status === "cancelled" || input2.status === "closed") {
|
|
3826
4077
|
try {
|
|
3827
4078
|
const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
3828
4079
|
clearQueueForAgent2(String(row.assigned_to));
|
|
@@ -3851,9 +4102,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
3851
4102
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
3852
4103
|
}
|
|
3853
4104
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
3854
|
-
const archPath =
|
|
4105
|
+
const archPath = path14.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
3855
4106
|
try {
|
|
3856
|
-
if (
|
|
4107
|
+
if (existsSync12(archPath)) return;
|
|
3857
4108
|
const template = [
|
|
3858
4109
|
`# ${projectName} \u2014 System Architecture`,
|
|
3859
4110
|
"",
|
|
@@ -3886,10 +4137,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
3886
4137
|
}
|
|
3887
4138
|
}
|
|
3888
4139
|
async function ensureGitignoreExe(baseDir) {
|
|
3889
|
-
const gitignorePath =
|
|
4140
|
+
const gitignorePath = path14.join(baseDir, ".gitignore");
|
|
3890
4141
|
try {
|
|
3891
|
-
if (
|
|
3892
|
-
const content =
|
|
4142
|
+
if (existsSync12(gitignorePath)) {
|
|
4143
|
+
const content = readFileSync12(gitignorePath, "utf-8");
|
|
3893
4144
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
3894
4145
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
3895
4146
|
} else {
|
|
@@ -3920,58 +4171,42 @@ var init_tasks_crud = __esm({
|
|
|
3920
4171
|
});
|
|
3921
4172
|
|
|
3922
4173
|
// src/lib/tasks-review.ts
|
|
3923
|
-
import
|
|
3924
|
-
import { existsSync as
|
|
4174
|
+
import path15 from "path";
|
|
4175
|
+
import { existsSync as existsSync13, readdirSync as readdirSync3, unlinkSync as unlinkSync5 } from "fs";
|
|
3925
4176
|
async function countPendingReviews(sessionScope) {
|
|
3926
4177
|
const client = getClient();
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
args: [sessionScope]
|
|
3931
|
-
});
|
|
3932
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
3933
|
-
}
|
|
4178
|
+
const scope = strictSessionScopeFilter(
|
|
4179
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
4180
|
+
);
|
|
3934
4181
|
const result = await client.execute({
|
|
3935
|
-
sql:
|
|
3936
|
-
|
|
4182
|
+
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
4183
|
+
WHERE status = 'needs_review'${scope.sql}`,
|
|
4184
|
+
args: [...scope.args]
|
|
3937
4185
|
});
|
|
3938
4186
|
return Number(result.rows[0]?.cnt) || 0;
|
|
3939
4187
|
}
|
|
3940
4188
|
async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
3941
4189
|
const client = getClient();
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
WHERE status = 'needs_review' AND updated_at > ?
|
|
3946
|
-
AND session_scope = ?`,
|
|
3947
|
-
args: [sinceIso, sessionScope]
|
|
3948
|
-
});
|
|
3949
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
3950
|
-
}
|
|
4190
|
+
const scope = strictSessionScopeFilter(
|
|
4191
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
4192
|
+
);
|
|
3951
4193
|
const result = await client.execute({
|
|
3952
4194
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3953
|
-
WHERE status = 'needs_review' AND updated_at >
|
|
3954
|
-
args: [sinceIso]
|
|
4195
|
+
WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
|
|
4196
|
+
args: [sinceIso, ...scope.args]
|
|
3955
4197
|
});
|
|
3956
4198
|
return Number(result.rows[0]?.cnt) || 0;
|
|
3957
4199
|
}
|
|
3958
4200
|
async function listPendingReviews(limit, sessionScope) {
|
|
3959
4201
|
const client = getClient();
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
WHERE status = 'needs_review'
|
|
3964
|
-
AND session_scope = ?
|
|
3965
|
-
ORDER BY updated_at ASC LIMIT ?`,
|
|
3966
|
-
args: [sessionScope, limit]
|
|
3967
|
-
});
|
|
3968
|
-
return result2.rows;
|
|
3969
|
-
}
|
|
4202
|
+
const scope = strictSessionScopeFilter(
|
|
4203
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
4204
|
+
);
|
|
3970
4205
|
const result = await client.execute({
|
|
3971
4206
|
sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
|
|
3972
|
-
WHERE status = 'needs_review'
|
|
4207
|
+
WHERE status = 'needs_review'${scope.sql}
|
|
3973
4208
|
ORDER BY updated_at ASC LIMIT ?`,
|
|
3974
|
-
args: [limit]
|
|
4209
|
+
args: [...scope.args, limit]
|
|
3975
4210
|
});
|
|
3976
4211
|
return result.rows;
|
|
3977
4212
|
}
|
|
@@ -3983,7 +4218,7 @@ async function cleanupOrphanedReviews() {
|
|
|
3983
4218
|
WHERE status IN ('open', 'needs_review', 'in_progress')
|
|
3984
4219
|
AND assigned_by = 'system'
|
|
3985
4220
|
AND title LIKE 'Review:%'
|
|
3986
|
-
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
|
|
4221
|
+
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
|
|
3987
4222
|
args: [now]
|
|
3988
4223
|
});
|
|
3989
4224
|
const r1b = await client.execute({
|
|
@@ -4102,11 +4337,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
4102
4337
|
);
|
|
4103
4338
|
}
|
|
4104
4339
|
try {
|
|
4105
|
-
const cacheDir =
|
|
4106
|
-
if (
|
|
4340
|
+
const cacheDir = path15.join(EXE_AI_DIR, "session-cache");
|
|
4341
|
+
if (existsSync13(cacheDir)) {
|
|
4107
4342
|
for (const f of readdirSync3(cacheDir)) {
|
|
4108
4343
|
if (f.startsWith("review-notified-")) {
|
|
4109
|
-
unlinkSync5(
|
|
4344
|
+
unlinkSync5(path15.join(cacheDir, f));
|
|
4110
4345
|
}
|
|
4111
4346
|
}
|
|
4112
4347
|
}
|
|
@@ -4123,11 +4358,12 @@ var init_tasks_review = __esm({
|
|
|
4123
4358
|
init_tmux_routing();
|
|
4124
4359
|
init_session_key();
|
|
4125
4360
|
init_state_bus();
|
|
4361
|
+
init_task_scope();
|
|
4126
4362
|
}
|
|
4127
4363
|
});
|
|
4128
4364
|
|
|
4129
4365
|
// src/lib/tasks-chain.ts
|
|
4130
|
-
import
|
|
4366
|
+
import path16 from "path";
|
|
4131
4367
|
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
4132
4368
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
4133
4369
|
const client = getClient();
|
|
@@ -4144,7 +4380,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
4144
4380
|
});
|
|
4145
4381
|
for (const ur of unblockedRows.rows) {
|
|
4146
4382
|
try {
|
|
4147
|
-
const ubFile =
|
|
4383
|
+
const ubFile = path16.join(baseDir, String(ur.task_file));
|
|
4148
4384
|
let ubContent = await readFile3(ubFile, "utf-8");
|
|
4149
4385
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
4150
4386
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -4179,7 +4415,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
|
|
|
4179
4415
|
const scScope = sessionScopeFilter();
|
|
4180
4416
|
const remaining = await client.execute({
|
|
4181
4417
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
4182
|
-
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
|
|
4418
|
+
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
|
|
4183
4419
|
args: [parentTaskId, ...scScope.args]
|
|
4184
4420
|
});
|
|
4185
4421
|
const cnt = Number(remaining.rows[0]?.cnt ?? 1);
|
|
@@ -4211,110 +4447,6 @@ var init_tasks_chain = __esm({
|
|
|
4211
4447
|
}
|
|
4212
4448
|
});
|
|
4213
4449
|
|
|
4214
|
-
// src/lib/project-name.ts
|
|
4215
|
-
import { execSync as execSync6 } from "child_process";
|
|
4216
|
-
import path15 from "path";
|
|
4217
|
-
function getProjectName(cwd) {
|
|
4218
|
-
const dir = cwd ?? process.cwd();
|
|
4219
|
-
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
4220
|
-
try {
|
|
4221
|
-
let repoRoot;
|
|
4222
|
-
try {
|
|
4223
|
-
const gitCommonDir = execSync6("git rev-parse --path-format=absolute --git-common-dir", {
|
|
4224
|
-
cwd: dir,
|
|
4225
|
-
encoding: "utf8",
|
|
4226
|
-
timeout: 2e3,
|
|
4227
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
4228
|
-
}).trim();
|
|
4229
|
-
repoRoot = path15.dirname(gitCommonDir);
|
|
4230
|
-
} catch {
|
|
4231
|
-
repoRoot = execSync6("git rev-parse --show-toplevel", {
|
|
4232
|
-
cwd: dir,
|
|
4233
|
-
encoding: "utf8",
|
|
4234
|
-
timeout: 2e3,
|
|
4235
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
4236
|
-
}).trim();
|
|
4237
|
-
}
|
|
4238
|
-
_cached2 = path15.basename(repoRoot);
|
|
4239
|
-
_cachedCwd = dir;
|
|
4240
|
-
return _cached2;
|
|
4241
|
-
} catch {
|
|
4242
|
-
_cached2 = path15.basename(dir);
|
|
4243
|
-
_cachedCwd = dir;
|
|
4244
|
-
return _cached2;
|
|
4245
|
-
}
|
|
4246
|
-
}
|
|
4247
|
-
var _cached2, _cachedCwd;
|
|
4248
|
-
var init_project_name = __esm({
|
|
4249
|
-
"src/lib/project-name.ts"() {
|
|
4250
|
-
"use strict";
|
|
4251
|
-
_cached2 = null;
|
|
4252
|
-
_cachedCwd = null;
|
|
4253
|
-
}
|
|
4254
|
-
});
|
|
4255
|
-
|
|
4256
|
-
// src/lib/session-scope.ts
|
|
4257
|
-
var session_scope_exports = {};
|
|
4258
|
-
__export(session_scope_exports, {
|
|
4259
|
-
assertSessionScope: () => assertSessionScope,
|
|
4260
|
-
findSessionForProject: () => findSessionForProject,
|
|
4261
|
-
getSessionProject: () => getSessionProject
|
|
4262
|
-
});
|
|
4263
|
-
function getSessionProject(sessionName) {
|
|
4264
|
-
const sessions = listSessions();
|
|
4265
|
-
const entry = sessions.find((s) => s.windowName === sessionName);
|
|
4266
|
-
if (!entry) return null;
|
|
4267
|
-
const parts = entry.projectDir.split("/").filter(Boolean);
|
|
4268
|
-
return parts[parts.length - 1] ?? null;
|
|
4269
|
-
}
|
|
4270
|
-
function findSessionForProject(projectName) {
|
|
4271
|
-
const sessions = listSessions();
|
|
4272
|
-
for (const s of sessions) {
|
|
4273
|
-
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
4274
|
-
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
4275
|
-
}
|
|
4276
|
-
return null;
|
|
4277
|
-
}
|
|
4278
|
-
function assertSessionScope(actionType, targetProject) {
|
|
4279
|
-
try {
|
|
4280
|
-
const currentProject = getProjectName();
|
|
4281
|
-
const exeSession = resolveExeSession();
|
|
4282
|
-
if (!exeSession) {
|
|
4283
|
-
return { allowed: true, reason: "no_session" };
|
|
4284
|
-
}
|
|
4285
|
-
if (currentProject === targetProject) {
|
|
4286
|
-
return {
|
|
4287
|
-
allowed: true,
|
|
4288
|
-
reason: "same_session",
|
|
4289
|
-
currentProject,
|
|
4290
|
-
targetProject
|
|
4291
|
-
};
|
|
4292
|
-
}
|
|
4293
|
-
process.stderr.write(
|
|
4294
|
-
`[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
|
|
4295
|
-
`
|
|
4296
|
-
);
|
|
4297
|
-
return {
|
|
4298
|
-
allowed: false,
|
|
4299
|
-
reason: "cross_session_denied",
|
|
4300
|
-
currentProject,
|
|
4301
|
-
targetProject,
|
|
4302
|
-
targetSession: findSessionForProject(targetProject)?.windowName
|
|
4303
|
-
};
|
|
4304
|
-
} catch {
|
|
4305
|
-
return { allowed: true, reason: "no_session" };
|
|
4306
|
-
}
|
|
4307
|
-
}
|
|
4308
|
-
var init_session_scope = __esm({
|
|
4309
|
-
"src/lib/session-scope.ts"() {
|
|
4310
|
-
"use strict";
|
|
4311
|
-
init_session_registry();
|
|
4312
|
-
init_project_name();
|
|
4313
|
-
init_tmux_routing();
|
|
4314
|
-
init_employees();
|
|
4315
|
-
}
|
|
4316
|
-
});
|
|
4317
|
-
|
|
4318
4450
|
// src/lib/tasks-notify.ts
|
|
4319
4451
|
async function dispatchTaskToEmployee(input2) {
|
|
4320
4452
|
if (isCoordinatorName(input2.assignedTo)) return { dispatched: "skipped" };
|
|
@@ -4382,10 +4514,10 @@ var init_tasks_notify = __esm({
|
|
|
4382
4514
|
});
|
|
4383
4515
|
|
|
4384
4516
|
// src/lib/behaviors.ts
|
|
4385
|
-
import
|
|
4517
|
+
import crypto5 from "crypto";
|
|
4386
4518
|
async function storeBehavior(opts) {
|
|
4387
4519
|
const client = getClient();
|
|
4388
|
-
const id =
|
|
4520
|
+
const id = crypto5.randomUUID();
|
|
4389
4521
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4390
4522
|
await client.execute({
|
|
4391
4523
|
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
|
|
@@ -4414,7 +4546,7 @@ __export(skill_learning_exports, {
|
|
|
4414
4546
|
storeTrajectory: () => storeTrajectory,
|
|
4415
4547
|
sweepTrajectories: () => sweepTrajectories
|
|
4416
4548
|
});
|
|
4417
|
-
import
|
|
4549
|
+
import crypto6 from "crypto";
|
|
4418
4550
|
async function extractTrajectory(taskId, agentId) {
|
|
4419
4551
|
const client = getClient();
|
|
4420
4552
|
const result = await client.execute({
|
|
@@ -4443,11 +4575,11 @@ async function extractTrajectory(taskId, agentId) {
|
|
|
4443
4575
|
return signature;
|
|
4444
4576
|
}
|
|
4445
4577
|
function hashSignature(signature) {
|
|
4446
|
-
return
|
|
4578
|
+
return crypto6.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
|
|
4447
4579
|
}
|
|
4448
4580
|
async function storeTrajectory(opts) {
|
|
4449
4581
|
const client = getClient();
|
|
4450
|
-
const id =
|
|
4582
|
+
const id = crypto6.randomUUID();
|
|
4451
4583
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4452
4584
|
const signatureHash = hashSignature(opts.signature);
|
|
4453
4585
|
await client.execute({
|
|
@@ -4712,8 +4844,8 @@ __export(tasks_exports, {
|
|
|
4712
4844
|
updateTaskStatus: () => updateTaskStatus,
|
|
4713
4845
|
writeCheckpoint: () => writeCheckpoint
|
|
4714
4846
|
});
|
|
4715
|
-
import
|
|
4716
|
-
import { writeFileSync as
|
|
4847
|
+
import path17 from "path";
|
|
4848
|
+
import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync6, unlinkSync as unlinkSync6 } from "fs";
|
|
4717
4849
|
async function createTask(input2) {
|
|
4718
4850
|
const result = await createTaskCore(input2);
|
|
4719
4851
|
if (!input2.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -4732,12 +4864,12 @@ async function updateTask(input2) {
|
|
|
4732
4864
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input2);
|
|
4733
4865
|
try {
|
|
4734
4866
|
const agent = String(row.assigned_to);
|
|
4735
|
-
const cacheDir =
|
|
4736
|
-
const cachePath =
|
|
4867
|
+
const cacheDir = path17.join(EXE_AI_DIR, "session-cache");
|
|
4868
|
+
const cachePath = path17.join(cacheDir, `current-task-${agent}.json`);
|
|
4737
4869
|
if (input2.status === "in_progress") {
|
|
4738
4870
|
mkdirSync6(cacheDir, { recursive: true });
|
|
4739
|
-
|
|
4740
|
-
} else if (input2.status === "done" || input2.status === "blocked" || input2.status === "cancelled") {
|
|
4871
|
+
writeFileSync8(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
4872
|
+
} else if (input2.status === "done" || input2.status === "blocked" || input2.status === "cancelled" || input2.status === "closed") {
|
|
4741
4873
|
try {
|
|
4742
4874
|
unlinkSync6(cachePath);
|
|
4743
4875
|
} catch {
|
|
@@ -4745,10 +4877,10 @@ async function updateTask(input2) {
|
|
|
4745
4877
|
}
|
|
4746
4878
|
} catch {
|
|
4747
4879
|
}
|
|
4748
|
-
if (input2.status === "done") {
|
|
4880
|
+
if (input2.status === "done" || input2.status === "closed") {
|
|
4749
4881
|
await cleanupReviewFile(row, taskFile, input2.baseDir);
|
|
4750
4882
|
}
|
|
4751
|
-
if (input2.status === "done" || input2.status === "cancelled") {
|
|
4883
|
+
if (input2.status === "done" || input2.status === "cancelled" || input2.status === "closed") {
|
|
4752
4884
|
try {
|
|
4753
4885
|
const client = getClient();
|
|
4754
4886
|
const taskTitle = String(row.title);
|
|
@@ -4764,7 +4896,7 @@ async function updateTask(input2) {
|
|
|
4764
4896
|
if (!isCoordinatorName(assignedAgent)) {
|
|
4765
4897
|
try {
|
|
4766
4898
|
const draftClient = getClient();
|
|
4767
|
-
if (input2.status === "done") {
|
|
4899
|
+
if (input2.status === "done" || input2.status === "closed") {
|
|
4768
4900
|
await draftClient.execute({
|
|
4769
4901
|
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
4770
4902
|
args: [assignedAgent]
|
|
@@ -4781,7 +4913,7 @@ async function updateTask(input2) {
|
|
|
4781
4913
|
try {
|
|
4782
4914
|
const client = getClient();
|
|
4783
4915
|
const cascaded = await client.execute({
|
|
4784
|
-
sql: `UPDATE tasks SET status = '
|
|
4916
|
+
sql: `UPDATE tasks SET status = 'closed', updated_at = ?
|
|
4785
4917
|
WHERE parent_task_id = ? AND status = 'needs_review'`,
|
|
4786
4918
|
args: [now, taskId]
|
|
4787
4919
|
});
|
|
@@ -4794,14 +4926,14 @@ async function updateTask(input2) {
|
|
|
4794
4926
|
} catch {
|
|
4795
4927
|
}
|
|
4796
4928
|
}
|
|
4797
|
-
const isTerminal = input2.status === "done" || input2.status === "needs_review";
|
|
4929
|
+
const isTerminal = input2.status === "done" || input2.status === "needs_review" || input2.status === "closed";
|
|
4798
4930
|
if (isTerminal) {
|
|
4799
4931
|
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
4800
4932
|
if (!isCoordinator) {
|
|
4801
4933
|
notifyTaskDone();
|
|
4802
4934
|
}
|
|
4803
4935
|
await markTaskNotificationsRead(taskFile);
|
|
4804
|
-
if (input2.status === "done") {
|
|
4936
|
+
if (input2.status === "done" || input2.status === "closed") {
|
|
4805
4937
|
try {
|
|
4806
4938
|
await cascadeUnblock(taskId, input2.baseDir, now);
|
|
4807
4939
|
} catch {
|
|
@@ -4821,7 +4953,7 @@ async function updateTask(input2) {
|
|
|
4821
4953
|
}
|
|
4822
4954
|
}
|
|
4823
4955
|
}
|
|
4824
|
-
if (input2.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
4956
|
+
if ((input2.status === "done" || input2.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
4825
4957
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
4826
4958
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
4827
4959
|
taskId,
|
|
@@ -5193,6 +5325,7 @@ __export(tmux_routing_exports, {
|
|
|
5193
5325
|
isEmployeeAlive: () => isEmployeeAlive,
|
|
5194
5326
|
isExeSession: () => isExeSession,
|
|
5195
5327
|
isSessionBusy: () => isSessionBusy,
|
|
5328
|
+
notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
|
|
5196
5329
|
notifyParentExe: () => notifyParentExe,
|
|
5197
5330
|
parseParentExe: () => parseParentExe,
|
|
5198
5331
|
registerParentExe: () => registerParentExe,
|
|
@@ -5203,13 +5336,13 @@ __export(tmux_routing_exports, {
|
|
|
5203
5336
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
5204
5337
|
});
|
|
5205
5338
|
import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
|
|
5206
|
-
import { readFileSync as
|
|
5207
|
-
import
|
|
5208
|
-
import
|
|
5339
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync9, mkdirSync as mkdirSync7, existsSync as existsSync14, appendFileSync, readdirSync as readdirSync4 } from "fs";
|
|
5340
|
+
import path18 from "path";
|
|
5341
|
+
import os10 from "os";
|
|
5209
5342
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5210
5343
|
import { unlinkSync as unlinkSync7 } from "fs";
|
|
5211
5344
|
function spawnLockPath(sessionName) {
|
|
5212
|
-
return
|
|
5345
|
+
return path18.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
5213
5346
|
}
|
|
5214
5347
|
function isProcessAlive(pid) {
|
|
5215
5348
|
try {
|
|
@@ -5220,13 +5353,13 @@ function isProcessAlive(pid) {
|
|
|
5220
5353
|
}
|
|
5221
5354
|
}
|
|
5222
5355
|
function acquireSpawnLock2(sessionName) {
|
|
5223
|
-
if (!
|
|
5356
|
+
if (!existsSync14(SPAWN_LOCK_DIR)) {
|
|
5224
5357
|
mkdirSync7(SPAWN_LOCK_DIR, { recursive: true });
|
|
5225
5358
|
}
|
|
5226
5359
|
const lockFile = spawnLockPath(sessionName);
|
|
5227
|
-
if (
|
|
5360
|
+
if (existsSync14(lockFile)) {
|
|
5228
5361
|
try {
|
|
5229
|
-
const lock = JSON.parse(
|
|
5362
|
+
const lock = JSON.parse(readFileSync13(lockFile, "utf8"));
|
|
5230
5363
|
const age = Date.now() - lock.timestamp;
|
|
5231
5364
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
5232
5365
|
return false;
|
|
@@ -5234,7 +5367,7 @@ function acquireSpawnLock2(sessionName) {
|
|
|
5234
5367
|
} catch {
|
|
5235
5368
|
}
|
|
5236
5369
|
}
|
|
5237
|
-
|
|
5370
|
+
writeFileSync9(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
5238
5371
|
return true;
|
|
5239
5372
|
}
|
|
5240
5373
|
function releaseSpawnLock2(sessionName) {
|
|
@@ -5246,13 +5379,13 @@ function releaseSpawnLock2(sessionName) {
|
|
|
5246
5379
|
function resolveBehaviorsExporterScript() {
|
|
5247
5380
|
try {
|
|
5248
5381
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
5249
|
-
const scriptPath =
|
|
5250
|
-
|
|
5382
|
+
const scriptPath = path18.join(
|
|
5383
|
+
path18.dirname(thisFile),
|
|
5251
5384
|
"..",
|
|
5252
5385
|
"bin",
|
|
5253
5386
|
"exe-export-behaviors.js"
|
|
5254
5387
|
);
|
|
5255
|
-
return
|
|
5388
|
+
return existsSync14(scriptPath) ? scriptPath : null;
|
|
5256
5389
|
} catch {
|
|
5257
5390
|
return null;
|
|
5258
5391
|
}
|
|
@@ -5318,12 +5451,12 @@ function extractRootExe(name) {
|
|
|
5318
5451
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
5319
5452
|
}
|
|
5320
5453
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
5321
|
-
if (!
|
|
5454
|
+
if (!existsSync14(SESSION_CACHE)) {
|
|
5322
5455
|
mkdirSync7(SESSION_CACHE, { recursive: true });
|
|
5323
5456
|
}
|
|
5324
5457
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
5325
|
-
const filePath =
|
|
5326
|
-
|
|
5458
|
+
const filePath = path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
5459
|
+
writeFileSync9(filePath, JSON.stringify({
|
|
5327
5460
|
parentExe: rootExe,
|
|
5328
5461
|
dispatchedBy: dispatchedBy || rootExe,
|
|
5329
5462
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -5331,7 +5464,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
5331
5464
|
}
|
|
5332
5465
|
function getParentExe(sessionKey) {
|
|
5333
5466
|
try {
|
|
5334
|
-
const data = JSON.parse(
|
|
5467
|
+
const data = JSON.parse(readFileSync13(path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
5335
5468
|
return data.parentExe || null;
|
|
5336
5469
|
} catch {
|
|
5337
5470
|
return null;
|
|
@@ -5339,8 +5472,8 @@ function getParentExe(sessionKey) {
|
|
|
5339
5472
|
}
|
|
5340
5473
|
function getDispatchedBy(sessionKey) {
|
|
5341
5474
|
try {
|
|
5342
|
-
const data = JSON.parse(
|
|
5343
|
-
|
|
5475
|
+
const data = JSON.parse(readFileSync13(
|
|
5476
|
+
path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
5344
5477
|
"utf8"
|
|
5345
5478
|
));
|
|
5346
5479
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -5410,8 +5543,8 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
5410
5543
|
}
|
|
5411
5544
|
function readDebounceState() {
|
|
5412
5545
|
try {
|
|
5413
|
-
if (!
|
|
5414
|
-
const raw = JSON.parse(
|
|
5546
|
+
if (!existsSync14(DEBOUNCE_FILE)) return {};
|
|
5547
|
+
const raw = JSON.parse(readFileSync13(DEBOUNCE_FILE, "utf8"));
|
|
5415
5548
|
const state = {};
|
|
5416
5549
|
for (const [key, val] of Object.entries(raw)) {
|
|
5417
5550
|
if (typeof val === "number") {
|
|
@@ -5427,8 +5560,8 @@ function readDebounceState() {
|
|
|
5427
5560
|
}
|
|
5428
5561
|
function writeDebounceState(state) {
|
|
5429
5562
|
try {
|
|
5430
|
-
if (!
|
|
5431
|
-
|
|
5563
|
+
if (!existsSync14(SESSION_CACHE)) mkdirSync7(SESSION_CACHE, { recursive: true });
|
|
5564
|
+
writeFileSync9(DEBOUNCE_FILE, JSON.stringify(state));
|
|
5432
5565
|
} catch {
|
|
5433
5566
|
}
|
|
5434
5567
|
}
|
|
@@ -5526,8 +5659,8 @@ function sendIntercom(targetSession) {
|
|
|
5526
5659
|
try {
|
|
5527
5660
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
5528
5661
|
const agent = baseAgentName(rawAgent);
|
|
5529
|
-
const markerPath =
|
|
5530
|
-
if (
|
|
5662
|
+
const markerPath = path18.join(SESSION_CACHE, `current-task-${agent}.json`);
|
|
5663
|
+
if (existsSync14(markerPath)) {
|
|
5531
5664
|
logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
|
|
5532
5665
|
return "debounced";
|
|
5533
5666
|
}
|
|
@@ -5536,8 +5669,8 @@ function sendIntercom(targetSession) {
|
|
|
5536
5669
|
try {
|
|
5537
5670
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
5538
5671
|
const agent = baseAgentName(rawAgent);
|
|
5539
|
-
const taskDir =
|
|
5540
|
-
if (
|
|
5672
|
+
const taskDir = path18.join(process.cwd(), "exe", agent);
|
|
5673
|
+
if (existsSync14(taskDir)) {
|
|
5541
5674
|
const files = readdirSync4(taskDir).filter(
|
|
5542
5675
|
(f) => f.endsWith(".md") && f !== "DONE.txt"
|
|
5543
5676
|
);
|
|
@@ -5597,6 +5730,21 @@ function notifyParentExe(sessionKey) {
|
|
|
5597
5730
|
}
|
|
5598
5731
|
return true;
|
|
5599
5732
|
}
|
|
5733
|
+
function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
|
|
5734
|
+
const transport = getTransport();
|
|
5735
|
+
try {
|
|
5736
|
+
const sessions = transport.listSessions();
|
|
5737
|
+
if (!sessions.includes(coordinatorSession)) return false;
|
|
5738
|
+
execSync7(
|
|
5739
|
+
`tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
|
|
5740
|
+
{ timeout: 3e3 }
|
|
5741
|
+
);
|
|
5742
|
+
logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
|
|
5743
|
+
return true;
|
|
5744
|
+
} catch {
|
|
5745
|
+
return false;
|
|
5746
|
+
}
|
|
5747
|
+
}
|
|
5600
5748
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
5601
5749
|
if (isCoordinatorName(employeeName)) {
|
|
5602
5750
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
@@ -5670,26 +5818,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5670
5818
|
const transport = getTransport();
|
|
5671
5819
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
5672
5820
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
5673
|
-
const logDir =
|
|
5674
|
-
const logFile =
|
|
5675
|
-
if (!
|
|
5821
|
+
const logDir = path18.join(os10.homedir(), ".exe-os", "session-logs");
|
|
5822
|
+
const logFile = path18.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
5823
|
+
if (!existsSync14(logDir)) {
|
|
5676
5824
|
mkdirSync7(logDir, { recursive: true });
|
|
5677
5825
|
}
|
|
5678
5826
|
transport.kill(sessionName);
|
|
5679
5827
|
let cleanupSuffix = "";
|
|
5680
5828
|
try {
|
|
5681
5829
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
5682
|
-
const cleanupScript =
|
|
5683
|
-
if (
|
|
5830
|
+
const cleanupScript = path18.join(path18.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
5831
|
+
if (existsSync14(cleanupScript)) {
|
|
5684
5832
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
5685
5833
|
}
|
|
5686
5834
|
} catch {
|
|
5687
5835
|
}
|
|
5688
5836
|
try {
|
|
5689
|
-
const claudeJsonPath =
|
|
5837
|
+
const claudeJsonPath = path18.join(os10.homedir(), ".claude.json");
|
|
5690
5838
|
let claudeJson = {};
|
|
5691
5839
|
try {
|
|
5692
|
-
claudeJson = JSON.parse(
|
|
5840
|
+
claudeJson = JSON.parse(readFileSync13(claudeJsonPath, "utf8"));
|
|
5693
5841
|
} catch {
|
|
5694
5842
|
}
|
|
5695
5843
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -5697,17 +5845,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5697
5845
|
const trustDir = opts?.cwd ?? projectDir;
|
|
5698
5846
|
if (!projects[trustDir]) projects[trustDir] = {};
|
|
5699
5847
|
projects[trustDir].hasTrustDialogAccepted = true;
|
|
5700
|
-
|
|
5848
|
+
writeFileSync9(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
5701
5849
|
} catch {
|
|
5702
5850
|
}
|
|
5703
5851
|
try {
|
|
5704
|
-
const settingsDir =
|
|
5852
|
+
const settingsDir = path18.join(os10.homedir(), ".claude", "projects");
|
|
5705
5853
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
5706
|
-
const projSettingsDir =
|
|
5707
|
-
const settingsPath =
|
|
5854
|
+
const projSettingsDir = path18.join(settingsDir, normalizedKey);
|
|
5855
|
+
const settingsPath = path18.join(projSettingsDir, "settings.json");
|
|
5708
5856
|
let settings = {};
|
|
5709
5857
|
try {
|
|
5710
|
-
settings = JSON.parse(
|
|
5858
|
+
settings = JSON.parse(readFileSync13(settingsPath, "utf8"));
|
|
5711
5859
|
} catch {
|
|
5712
5860
|
}
|
|
5713
5861
|
const perms = settings.permissions ?? {};
|
|
@@ -5736,7 +5884,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5736
5884
|
perms.allow = allow;
|
|
5737
5885
|
settings.permissions = perms;
|
|
5738
5886
|
mkdirSync7(projSettingsDir, { recursive: true });
|
|
5739
|
-
|
|
5887
|
+
writeFileSync9(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
5740
5888
|
}
|
|
5741
5889
|
} catch {
|
|
5742
5890
|
}
|
|
@@ -5751,8 +5899,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5751
5899
|
let behaviorsFlag = "";
|
|
5752
5900
|
let legacyFallbackWarned = false;
|
|
5753
5901
|
if (!useExeAgent && !useBinSymlink) {
|
|
5754
|
-
const identityPath =
|
|
5755
|
-
|
|
5902
|
+
const identityPath = path18.join(
|
|
5903
|
+
os10.homedir(),
|
|
5756
5904
|
".exe-os",
|
|
5757
5905
|
"identity",
|
|
5758
5906
|
`${employeeName}.md`
|
|
@@ -5761,13 +5909,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5761
5909
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
5762
5910
|
if (hasAgentFlag) {
|
|
5763
5911
|
identityFlag = ` --agent ${employeeName}`;
|
|
5764
|
-
} else if (
|
|
5912
|
+
} else if (existsSync14(identityPath)) {
|
|
5765
5913
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
5766
5914
|
legacyFallbackWarned = true;
|
|
5767
5915
|
}
|
|
5768
5916
|
const behaviorsFile = exportBehaviorsSync(
|
|
5769
5917
|
employeeName,
|
|
5770
|
-
|
|
5918
|
+
path18.basename(spawnCwd),
|
|
5771
5919
|
sessionName
|
|
5772
5920
|
);
|
|
5773
5921
|
if (behaviorsFile) {
|
|
@@ -5782,16 +5930,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5782
5930
|
}
|
|
5783
5931
|
let sessionContextFlag = "";
|
|
5784
5932
|
try {
|
|
5785
|
-
const ctxDir =
|
|
5933
|
+
const ctxDir = path18.join(os10.homedir(), ".exe-os", "session-cache");
|
|
5786
5934
|
mkdirSync7(ctxDir, { recursive: true });
|
|
5787
|
-
const ctxFile =
|
|
5935
|
+
const ctxFile = path18.join(ctxDir, `session-context-${sessionName}.md`);
|
|
5788
5936
|
const ctxContent = [
|
|
5789
5937
|
`## Session Context`,
|
|
5790
5938
|
`You are running in tmux session: ${sessionName}.`,
|
|
5791
5939
|
`Your parent coordinator session is ${exeSession}.`,
|
|
5792
5940
|
`Your employees (if any) use the -${exeSession} suffix.`
|
|
5793
5941
|
].join("\n");
|
|
5794
|
-
|
|
5942
|
+
writeFileSync9(ctxFile, ctxContent);
|
|
5795
5943
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
5796
5944
|
} catch {
|
|
5797
5945
|
}
|
|
@@ -5868,8 +6016,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5868
6016
|
transport.pipeLog(sessionName, logFile);
|
|
5869
6017
|
try {
|
|
5870
6018
|
const mySession = getMySession();
|
|
5871
|
-
const dispatchInfo =
|
|
5872
|
-
|
|
6019
|
+
const dispatchInfo = path18.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
6020
|
+
writeFileSync9(dispatchInfo, JSON.stringify({
|
|
5873
6021
|
dispatchedBy: mySession,
|
|
5874
6022
|
rootExe: exeSession,
|
|
5875
6023
|
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
|
|
@@ -5943,15 +6091,15 @@ var init_tmux_routing = __esm({
|
|
|
5943
6091
|
init_intercom_queue();
|
|
5944
6092
|
init_plan_limits();
|
|
5945
6093
|
init_employees();
|
|
5946
|
-
SPAWN_LOCK_DIR =
|
|
5947
|
-
SESSION_CACHE =
|
|
6094
|
+
SPAWN_LOCK_DIR = path18.join(os10.homedir(), ".exe-os", "spawn-locks");
|
|
6095
|
+
SESSION_CACHE = path18.join(os10.homedir(), ".exe-os", "session-cache");
|
|
5948
6096
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
5949
6097
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
5950
6098
|
VERIFY_PANE_LINES = 200;
|
|
5951
6099
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
5952
6100
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
5953
|
-
INTERCOM_LOG2 =
|
|
5954
|
-
DEBOUNCE_FILE =
|
|
6101
|
+
INTERCOM_LOG2 = path18.join(os10.homedir(), ".exe-os", "intercom.log");
|
|
6102
|
+
DEBOUNCE_FILE = path18.join(SESSION_CACHE, "intercom-debounce.json");
|
|
5955
6103
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
5956
6104
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
|
|
5957
6105
|
}
|
|
@@ -5974,6 +6122,15 @@ function sessionScopeFilter(sessionScope, tableAlias) {
|
|
|
5974
6122
|
args: [scope]
|
|
5975
6123
|
};
|
|
5976
6124
|
}
|
|
6125
|
+
function strictSessionScopeFilter(sessionScope, tableAlias) {
|
|
6126
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
6127
|
+
if (!scope) return { sql: "", args: [] };
|
|
6128
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
6129
|
+
return {
|
|
6130
|
+
sql: ` AND ${col} = ?`,
|
|
6131
|
+
args: [scope]
|
|
6132
|
+
};
|
|
6133
|
+
}
|
|
5977
6134
|
var init_task_scope = __esm({
|
|
5978
6135
|
"src/lib/task-scope.ts"() {
|
|
5979
6136
|
"use strict";
|
|
@@ -5992,14 +6149,14 @@ var init_memory = __esm({
|
|
|
5992
6149
|
|
|
5993
6150
|
// src/lib/keychain.ts
|
|
5994
6151
|
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
5995
|
-
import { existsSync as
|
|
5996
|
-
import
|
|
5997
|
-
import
|
|
6152
|
+
import { existsSync as existsSync15 } from "fs";
|
|
6153
|
+
import path19 from "path";
|
|
6154
|
+
import os11 from "os";
|
|
5998
6155
|
function getKeyDir() {
|
|
5999
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
6156
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path19.join(os11.homedir(), ".exe-os");
|
|
6000
6157
|
}
|
|
6001
6158
|
function getKeyPath() {
|
|
6002
|
-
return
|
|
6159
|
+
return path19.join(getKeyDir(), "master.key");
|
|
6003
6160
|
}
|
|
6004
6161
|
async function tryKeytar() {
|
|
6005
6162
|
try {
|
|
@@ -6020,9 +6177,9 @@ async function getMasterKey() {
|
|
|
6020
6177
|
}
|
|
6021
6178
|
}
|
|
6022
6179
|
const keyPath = getKeyPath();
|
|
6023
|
-
if (!
|
|
6180
|
+
if (!existsSync15(keyPath)) {
|
|
6024
6181
|
process.stderr.write(
|
|
6025
|
-
`[keychain] Key not found at ${keyPath} (HOME=${
|
|
6182
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os11.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
6026
6183
|
`
|
|
6027
6184
|
);
|
|
6028
6185
|
return null;
|
|
@@ -6052,6 +6209,7 @@ var shard_manager_exports = {};
|
|
|
6052
6209
|
__export(shard_manager_exports, {
|
|
6053
6210
|
disposeShards: () => disposeShards,
|
|
6054
6211
|
ensureShardSchema: () => ensureShardSchema,
|
|
6212
|
+
getOpenShardCount: () => getOpenShardCount,
|
|
6055
6213
|
getReadyShardClient: () => getReadyShardClient,
|
|
6056
6214
|
getShardClient: () => getShardClient,
|
|
6057
6215
|
getShardsDir: () => getShardsDir,
|
|
@@ -6060,15 +6218,18 @@ __export(shard_manager_exports, {
|
|
|
6060
6218
|
listShards: () => listShards,
|
|
6061
6219
|
shardExists: () => shardExists
|
|
6062
6220
|
});
|
|
6063
|
-
import
|
|
6064
|
-
import { existsSync as
|
|
6221
|
+
import path20 from "path";
|
|
6222
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync8, readdirSync as readdirSync5 } from "fs";
|
|
6065
6223
|
import { createClient as createClient2 } from "@libsql/client";
|
|
6066
6224
|
function initShardManager(encryptionKey) {
|
|
6067
6225
|
_encryptionKey = encryptionKey;
|
|
6068
|
-
if (!
|
|
6226
|
+
if (!existsSync16(SHARDS_DIR)) {
|
|
6069
6227
|
mkdirSync8(SHARDS_DIR, { recursive: true });
|
|
6070
6228
|
}
|
|
6071
6229
|
_shardingEnabled = true;
|
|
6230
|
+
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
6231
|
+
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
6232
|
+
_evictionTimer.unref();
|
|
6072
6233
|
}
|
|
6073
6234
|
function isShardingEnabled() {
|
|
6074
6235
|
return _shardingEnabled;
|
|
@@ -6085,21 +6246,28 @@ function getShardClient(projectName) {
|
|
|
6085
6246
|
throw new Error(`Invalid project name for shard: "${projectName}"`);
|
|
6086
6247
|
}
|
|
6087
6248
|
const cached = _shards.get(safeName);
|
|
6088
|
-
if (cached)
|
|
6089
|
-
|
|
6249
|
+
if (cached) {
|
|
6250
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
6251
|
+
return cached;
|
|
6252
|
+
}
|
|
6253
|
+
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
6254
|
+
evictLRU();
|
|
6255
|
+
}
|
|
6256
|
+
const dbPath = path20.join(SHARDS_DIR, `${safeName}.db`);
|
|
6090
6257
|
const client = createClient2({
|
|
6091
6258
|
url: `file:${dbPath}`,
|
|
6092
6259
|
encryptionKey: _encryptionKey
|
|
6093
6260
|
});
|
|
6094
6261
|
_shards.set(safeName, client);
|
|
6262
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
6095
6263
|
return client;
|
|
6096
6264
|
}
|
|
6097
6265
|
function shardExists(projectName) {
|
|
6098
6266
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
6099
|
-
return
|
|
6267
|
+
return existsSync16(path20.join(SHARDS_DIR, `${safeName}.db`));
|
|
6100
6268
|
}
|
|
6101
6269
|
function listShards() {
|
|
6102
|
-
if (!
|
|
6270
|
+
if (!existsSync16(SHARDS_DIR)) return [];
|
|
6103
6271
|
return readdirSync5(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
6104
6272
|
}
|
|
6105
6273
|
async function ensureShardSchema(client) {
|
|
@@ -6151,6 +6319,8 @@ async function ensureShardSchema(client) {
|
|
|
6151
6319
|
for (const col of [
|
|
6152
6320
|
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
6153
6321
|
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
6322
|
+
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
6323
|
+
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
6154
6324
|
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
6155
6325
|
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
6156
6326
|
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
@@ -6288,21 +6458,69 @@ async function getReadyShardClient(projectName) {
|
|
|
6288
6458
|
await ensureShardSchema(client);
|
|
6289
6459
|
return client;
|
|
6290
6460
|
}
|
|
6461
|
+
function evictLRU() {
|
|
6462
|
+
let oldest = null;
|
|
6463
|
+
let oldestTime = Infinity;
|
|
6464
|
+
for (const [name, time] of _shardLastAccess) {
|
|
6465
|
+
if (time < oldestTime) {
|
|
6466
|
+
oldestTime = time;
|
|
6467
|
+
oldest = name;
|
|
6468
|
+
}
|
|
6469
|
+
}
|
|
6470
|
+
if (oldest) {
|
|
6471
|
+
const client = _shards.get(oldest);
|
|
6472
|
+
if (client) {
|
|
6473
|
+
client.close();
|
|
6474
|
+
}
|
|
6475
|
+
_shards.delete(oldest);
|
|
6476
|
+
_shardLastAccess.delete(oldest);
|
|
6477
|
+
}
|
|
6478
|
+
}
|
|
6479
|
+
function evictIdleShards() {
|
|
6480
|
+
const now = Date.now();
|
|
6481
|
+
const toEvict = [];
|
|
6482
|
+
for (const [name, lastAccess] of _shardLastAccess) {
|
|
6483
|
+
if (now - lastAccess > SHARD_IDLE_MS) {
|
|
6484
|
+
toEvict.push(name);
|
|
6485
|
+
}
|
|
6486
|
+
}
|
|
6487
|
+
for (const name of toEvict) {
|
|
6488
|
+
const client = _shards.get(name);
|
|
6489
|
+
if (client) {
|
|
6490
|
+
client.close();
|
|
6491
|
+
}
|
|
6492
|
+
_shards.delete(name);
|
|
6493
|
+
_shardLastAccess.delete(name);
|
|
6494
|
+
}
|
|
6495
|
+
}
|
|
6496
|
+
function getOpenShardCount() {
|
|
6497
|
+
return _shards.size;
|
|
6498
|
+
}
|
|
6291
6499
|
function disposeShards() {
|
|
6500
|
+
if (_evictionTimer) {
|
|
6501
|
+
clearInterval(_evictionTimer);
|
|
6502
|
+
_evictionTimer = null;
|
|
6503
|
+
}
|
|
6292
6504
|
for (const [, client] of _shards) {
|
|
6293
6505
|
client.close();
|
|
6294
6506
|
}
|
|
6295
6507
|
_shards.clear();
|
|
6508
|
+
_shardLastAccess.clear();
|
|
6296
6509
|
_shardingEnabled = false;
|
|
6297
6510
|
_encryptionKey = null;
|
|
6298
6511
|
}
|
|
6299
|
-
var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
|
|
6512
|
+
var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
|
|
6300
6513
|
var init_shard_manager = __esm({
|
|
6301
6514
|
"src/lib/shard-manager.ts"() {
|
|
6302
6515
|
"use strict";
|
|
6303
6516
|
init_config();
|
|
6304
|
-
SHARDS_DIR =
|
|
6517
|
+
SHARDS_DIR = path20.join(EXE_AI_DIR, "shards");
|
|
6518
|
+
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
6519
|
+
MAX_OPEN_SHARDS = 10;
|
|
6520
|
+
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
6305
6521
|
_shards = /* @__PURE__ */ new Map();
|
|
6522
|
+
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
6523
|
+
_evictionTimer = null;
|
|
6306
6524
|
_encryptionKey = null;
|
|
6307
6525
|
_shardingEnabled = false;
|
|
6308
6526
|
}
|
|
@@ -7287,7 +7505,7 @@ var init_git_task_sweep = __esm({
|
|
|
7287
7505
|
init_config();
|
|
7288
7506
|
init_session_key();
|
|
7289
7507
|
init_employees();
|
|
7290
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync, unlinkSync as unlinkSync2, readdirSync } from "fs";
|
|
7508
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2, readdirSync } from "fs";
|
|
7291
7509
|
import { execSync as execSync3 } from "child_process";
|
|
7292
7510
|
import path3 from "path";
|
|
7293
7511
|
var CACHE_DIR = path3.join(EXE_AI_DIR, "session-cache");
|
|
@@ -7418,7 +7636,7 @@ process.stdin.on("end", async () => {
|
|
|
7418
7636
|
const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
7419
7637
|
const { randomUUID: randomUUID4 } = await import("crypto");
|
|
7420
7638
|
const client = getClient2();
|
|
7421
|
-
const seScope =
|
|
7639
|
+
const seScope = strictSessionScopeFilter();
|
|
7422
7640
|
const orphanResult = await client.execute({
|
|
7423
7641
|
sql: `SELECT title, status FROM tasks WHERE assigned_to = ? AND status IN ('open', 'in_progress')${seScope.sql}`,
|
|
7424
7642
|
args: [agent.agentId, ...seScope.args]
|
|
@@ -7462,8 +7680,10 @@ Orphaned tasks at session end: ${orphanResult.rows.map((r) => `"${String(r.title
|
|
|
7462
7680
|
let context;
|
|
7463
7681
|
try {
|
|
7464
7682
|
const ctxResult = await client.execute({
|
|
7465
|
-
sql:
|
|
7466
|
-
|
|
7683
|
+
sql: `SELECT id, context FROM tasks
|
|
7684
|
+
WHERE title = ? AND assigned_to = ? AND status = 'in_progress'${seScope.sql}
|
|
7685
|
+
LIMIT 1`,
|
|
7686
|
+
args: [title, agent.agentId, ...seScope.args]
|
|
7467
7687
|
});
|
|
7468
7688
|
if (ctxResult.rows.length > 0) {
|
|
7469
7689
|
context = ctxResult.rows[0].context ? String(ctxResult.rows[0].context) : void 0;
|
|
@@ -7474,12 +7694,14 @@ Orphaned tasks at session end: ${orphanResult.rows.map((r) => `"${String(r.title
|
|
|
7474
7694
|
const match = findBestMatch2(taskForMatch, commits);
|
|
7475
7695
|
if (match) {
|
|
7476
7696
|
await client.execute({
|
|
7477
|
-
sql:
|
|
7697
|
+
sql: `UPDATE tasks SET status = 'done', result = ?, updated_at = ?
|
|
7698
|
+
WHERE title = ? AND assigned_to = ? AND status = 'in_progress'${seScope.sql}`,
|
|
7478
7699
|
args: [
|
|
7479
7700
|
`Auto-closed: session ended but matching commit ${match.commit.hash} found (score: ${match.score.toFixed(2)}). Message: "${match.commit.message}"`,
|
|
7480
7701
|
(/* @__PURE__ */ new Date()).toISOString(),
|
|
7481
7702
|
title,
|
|
7482
|
-
agent.agentId
|
|
7703
|
+
agent.agentId,
|
|
7704
|
+
...seScope.args
|
|
7483
7705
|
]
|
|
7484
7706
|
});
|
|
7485
7707
|
autoClosed.push(`"${title}" \u2192 commit ${match.commit.hash}`);
|