@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/index.js
CHANGED
|
@@ -25,6 +25,44 @@ 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
67
|
var config_exports = {};
|
|
30
68
|
__export(config_exports, {
|
|
@@ -41,8 +79,8 @@ __export(config_exports, {
|
|
|
41
79
|
migrateConfig: () => migrateConfig,
|
|
42
80
|
saveConfig: () => saveConfig
|
|
43
81
|
});
|
|
44
|
-
import { readFile, writeFile
|
|
45
|
-
import { readFileSync, existsSync, renameSync } from "fs";
|
|
82
|
+
import { readFile, writeFile } from "fs/promises";
|
|
83
|
+
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
46
84
|
import path2 from "path";
|
|
47
85
|
import os2 from "os";
|
|
48
86
|
function resolveDataDir() {
|
|
@@ -50,7 +88,7 @@ function resolveDataDir() {
|
|
|
50
88
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
51
89
|
const newDir = path2.join(os2.homedir(), ".exe-os");
|
|
52
90
|
const legacyDir = path2.join(os2.homedir(), ".exe-mem");
|
|
53
|
-
if (!
|
|
91
|
+
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
54
92
|
try {
|
|
55
93
|
renameSync(legacyDir, newDir);
|
|
56
94
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -113,9 +151,9 @@ function normalizeAutoUpdate(raw) {
|
|
|
113
151
|
}
|
|
114
152
|
async function loadConfig() {
|
|
115
153
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
116
|
-
await
|
|
154
|
+
await ensurePrivateDir(dir);
|
|
117
155
|
const configPath = path2.join(dir, "config.json");
|
|
118
|
-
if (!
|
|
156
|
+
if (!existsSync2(configPath)) {
|
|
119
157
|
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
120
158
|
}
|
|
121
159
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -128,6 +166,7 @@ async function loadConfig() {
|
|
|
128
166
|
`);
|
|
129
167
|
try {
|
|
130
168
|
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
169
|
+
await enforcePrivateFile(configPath);
|
|
131
170
|
} catch {
|
|
132
171
|
}
|
|
133
172
|
}
|
|
@@ -146,7 +185,7 @@ async function loadConfig() {
|
|
|
146
185
|
function loadConfigSync() {
|
|
147
186
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
148
187
|
const configPath = path2.join(dir, "config.json");
|
|
149
|
-
if (!
|
|
188
|
+
if (!existsSync2(configPath)) {
|
|
150
189
|
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
151
190
|
}
|
|
152
191
|
try {
|
|
@@ -164,12 +203,10 @@ function loadConfigSync() {
|
|
|
164
203
|
}
|
|
165
204
|
async function saveConfig(config2) {
|
|
166
205
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
167
|
-
await
|
|
206
|
+
await ensurePrivateDir(dir);
|
|
168
207
|
const configPath = path2.join(dir, "config.json");
|
|
169
208
|
await writeFile(configPath, JSON.stringify(config2, null, 2) + "\n");
|
|
170
|
-
|
|
171
|
-
await chmod(configPath, 384);
|
|
172
|
-
}
|
|
209
|
+
await enforcePrivateFile(configPath);
|
|
173
210
|
}
|
|
174
211
|
async function loadConfigFrom(configPath) {
|
|
175
212
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -189,6 +226,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
|
|
|
189
226
|
var init_config = __esm({
|
|
190
227
|
"src/lib/config.ts"() {
|
|
191
228
|
"use strict";
|
|
229
|
+
init_secure_files();
|
|
192
230
|
EXE_AI_DIR = resolveDataDir();
|
|
193
231
|
DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
|
|
194
232
|
MODELS_DIR = path2.join(EXE_AI_DIR, "models");
|
|
@@ -305,10 +343,10 @@ __export(agent_config_exports, {
|
|
|
305
343
|
saveAgentConfig: () => saveAgentConfig,
|
|
306
344
|
setAgentRuntime: () => setAgentRuntime
|
|
307
345
|
});
|
|
308
|
-
import { readFileSync as readFileSync2, writeFileSync, existsSync as
|
|
346
|
+
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
|
|
309
347
|
import path3 from "path";
|
|
310
348
|
function loadAgentConfig() {
|
|
311
|
-
if (!
|
|
349
|
+
if (!existsSync3(AGENT_CONFIG_PATH)) return {};
|
|
312
350
|
try {
|
|
313
351
|
return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
|
|
314
352
|
} catch {
|
|
@@ -317,8 +355,9 @@ function loadAgentConfig() {
|
|
|
317
355
|
}
|
|
318
356
|
function saveAgentConfig(config2) {
|
|
319
357
|
const dir = path3.dirname(AGENT_CONFIG_PATH);
|
|
320
|
-
|
|
358
|
+
ensurePrivateDirSync(dir);
|
|
321
359
|
writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config2, null, 2) + "\n", "utf-8");
|
|
360
|
+
enforcePrivateFileSync(AGENT_CONFIG_PATH);
|
|
322
361
|
}
|
|
323
362
|
function getAgentRuntime(agentId) {
|
|
324
363
|
const config2 = loadAgentConfig();
|
|
@@ -358,6 +397,7 @@ var init_agent_config = __esm({
|
|
|
358
397
|
"use strict";
|
|
359
398
|
init_config();
|
|
360
399
|
init_runtime_table();
|
|
400
|
+
init_secure_files();
|
|
361
401
|
AGENT_CONFIG_PATH = path3.join(EXE_AI_DIR, "agent-config.json");
|
|
362
402
|
KNOWN_RUNTIMES = {
|
|
363
403
|
claude: ["claude-opus-4", "claude-sonnet-4", "claude-haiku-4.5"],
|
|
@@ -405,7 +445,7 @@ __export(employees_exports, {
|
|
|
405
445
|
validateEmployeeName: () => validateEmployeeName
|
|
406
446
|
});
|
|
407
447
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
408
|
-
import { existsSync as
|
|
448
|
+
import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
409
449
|
import { execSync } from "child_process";
|
|
410
450
|
import path4 from "path";
|
|
411
451
|
import os3 from "os";
|
|
@@ -444,7 +484,7 @@ function validateEmployeeName(name) {
|
|
|
444
484
|
return { valid: true };
|
|
445
485
|
}
|
|
446
486
|
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
447
|
-
if (!
|
|
487
|
+
if (!existsSync4(employeesPath)) {
|
|
448
488
|
return [];
|
|
449
489
|
}
|
|
450
490
|
const raw = await readFile2(employeesPath, "utf-8");
|
|
@@ -459,7 +499,7 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
|
459
499
|
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
460
500
|
}
|
|
461
501
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
462
|
-
if (!
|
|
502
|
+
if (!existsSync4(employeesPath)) return [];
|
|
463
503
|
try {
|
|
464
504
|
return JSON.parse(readFileSync3(employeesPath, "utf-8"));
|
|
465
505
|
} catch {
|
|
@@ -507,7 +547,7 @@ function appendToCoordinatorTeam(employee) {
|
|
|
507
547
|
const coordinator = getCoordinatorEmployee(loadEmployeesSync());
|
|
508
548
|
if (!coordinator) return;
|
|
509
549
|
const idPath = path4.join(IDENTITY_DIR, `${coordinator.name}.md`);
|
|
510
|
-
if (!
|
|
550
|
+
if (!existsSync4(idPath)) return;
|
|
511
551
|
const content = readFileSync3(idPath, "utf-8");
|
|
512
552
|
if (content.includes(`**${capitalize(employee.name)}`)) return;
|
|
513
553
|
const teamMatch = content.match(TEAM_SECTION_RE);
|
|
@@ -561,9 +601,9 @@ async function normalizeRosterCase(rosterPath) {
|
|
|
561
601
|
const identityDir = path4.join(os3.homedir(), ".exe-os", "identity");
|
|
562
602
|
const oldPath = path4.join(identityDir, `${oldName}.md`);
|
|
563
603
|
const newPath = path4.join(identityDir, `${emp.name}.md`);
|
|
564
|
-
if (
|
|
604
|
+
if (existsSync4(oldPath) && !existsSync4(newPath)) {
|
|
565
605
|
renameSync2(oldPath, newPath);
|
|
566
|
-
} else if (
|
|
606
|
+
} else if (existsSync4(oldPath) && oldPath !== newPath) {
|
|
567
607
|
const content = readFileSync3(oldPath, "utf-8");
|
|
568
608
|
writeFileSync2(newPath, content, "utf-8");
|
|
569
609
|
if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
|
|
@@ -606,7 +646,7 @@ function registerBinSymlinks(name) {
|
|
|
606
646
|
for (const suffix of ["", "-opencode"]) {
|
|
607
647
|
const linkName = `${name}${suffix}`;
|
|
608
648
|
const linkPath = path4.join(binDir, linkName);
|
|
609
|
-
if (
|
|
649
|
+
if (existsSync4(linkPath)) {
|
|
610
650
|
skipped.push(linkName);
|
|
611
651
|
continue;
|
|
612
652
|
}
|
|
@@ -640,13 +680,13 @@ __export(session_registry_exports, {
|
|
|
640
680
|
pruneStaleSessions: () => pruneStaleSessions,
|
|
641
681
|
registerSession: () => registerSession
|
|
642
682
|
});
|
|
643
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as
|
|
683
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync5 } from "fs";
|
|
644
684
|
import { execSync as execSync2 } from "child_process";
|
|
645
685
|
import path5 from "path";
|
|
646
686
|
import os4 from "os";
|
|
647
687
|
function registerSession(entry) {
|
|
648
688
|
const dir = path5.dirname(REGISTRY_PATH);
|
|
649
|
-
if (!
|
|
689
|
+
if (!existsSync5(dir)) {
|
|
650
690
|
mkdirSync2(dir, { recursive: true });
|
|
651
691
|
}
|
|
652
692
|
const sessions = listSessions();
|
|
@@ -960,16 +1000,16 @@ __export(intercom_queue_exports, {
|
|
|
960
1000
|
queueIntercom: () => queueIntercom,
|
|
961
1001
|
readQueue: () => readQueue
|
|
962
1002
|
});
|
|
963
|
-
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as
|
|
1003
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
|
|
964
1004
|
import path6 from "path";
|
|
965
1005
|
import os5 from "os";
|
|
966
1006
|
function ensureDir() {
|
|
967
1007
|
const dir = path6.dirname(QUEUE_PATH);
|
|
968
|
-
if (!
|
|
1008
|
+
if (!existsSync6(dir)) mkdirSync3(dir, { recursive: true });
|
|
969
1009
|
}
|
|
970
1010
|
function readQueue() {
|
|
971
1011
|
try {
|
|
972
|
-
if (!
|
|
1012
|
+
if (!existsSync6(QUEUE_PATH)) return [];
|
|
973
1013
|
return JSON.parse(readFileSync5(QUEUE_PATH, "utf8"));
|
|
974
1014
|
} catch {
|
|
975
1015
|
return [];
|
|
@@ -1716,13 +1756,50 @@ var init_database_adapter = __esm({
|
|
|
1716
1756
|
}
|
|
1717
1757
|
});
|
|
1718
1758
|
|
|
1759
|
+
// src/lib/daemon-auth.ts
|
|
1760
|
+
import crypto from "crypto";
|
|
1761
|
+
import path8 from "path";
|
|
1762
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
1763
|
+
function normalizeToken(token) {
|
|
1764
|
+
if (!token) return null;
|
|
1765
|
+
const trimmed = token.trim();
|
|
1766
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
1767
|
+
}
|
|
1768
|
+
function readDaemonToken() {
|
|
1769
|
+
try {
|
|
1770
|
+
if (!existsSync7(DAEMON_TOKEN_PATH)) return null;
|
|
1771
|
+
return normalizeToken(readFileSync6(DAEMON_TOKEN_PATH, "utf8"));
|
|
1772
|
+
} catch {
|
|
1773
|
+
return null;
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
function ensureDaemonToken(seed) {
|
|
1777
|
+
const existing = readDaemonToken();
|
|
1778
|
+
if (existing) return existing;
|
|
1779
|
+
const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
|
|
1780
|
+
ensurePrivateDirSync(EXE_AI_DIR);
|
|
1781
|
+
writeFileSync5(DAEMON_TOKEN_PATH, `${token}
|
|
1782
|
+
`, "utf8");
|
|
1783
|
+
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
1784
|
+
return token;
|
|
1785
|
+
}
|
|
1786
|
+
var DAEMON_TOKEN_PATH;
|
|
1787
|
+
var init_daemon_auth = __esm({
|
|
1788
|
+
"src/lib/daemon-auth.ts"() {
|
|
1789
|
+
"use strict";
|
|
1790
|
+
init_config();
|
|
1791
|
+
init_secure_files();
|
|
1792
|
+
DAEMON_TOKEN_PATH = path8.join(EXE_AI_DIR, "exed.token");
|
|
1793
|
+
}
|
|
1794
|
+
});
|
|
1795
|
+
|
|
1719
1796
|
// src/lib/exe-daemon-client.ts
|
|
1720
1797
|
import net from "net";
|
|
1721
1798
|
import os7 from "os";
|
|
1722
1799
|
import { spawn } from "child_process";
|
|
1723
1800
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
1724
|
-
import { existsSync as
|
|
1725
|
-
import
|
|
1801
|
+
import { existsSync as existsSync8, unlinkSync as unlinkSync2, readFileSync as readFileSync7, openSync, closeSync, statSync } from "fs";
|
|
1802
|
+
import path9 from "path";
|
|
1726
1803
|
import { fileURLToPath } from "url";
|
|
1727
1804
|
function handleData(chunk) {
|
|
1728
1805
|
_buffer += chunk.toString();
|
|
@@ -1750,9 +1827,9 @@ function handleData(chunk) {
|
|
|
1750
1827
|
}
|
|
1751
1828
|
}
|
|
1752
1829
|
function cleanupStaleFiles() {
|
|
1753
|
-
if (
|
|
1830
|
+
if (existsSync8(PID_PATH)) {
|
|
1754
1831
|
try {
|
|
1755
|
-
const pid = parseInt(
|
|
1832
|
+
const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
|
|
1756
1833
|
if (pid > 0) {
|
|
1757
1834
|
try {
|
|
1758
1835
|
process.kill(pid, 0);
|
|
@@ -1773,11 +1850,11 @@ function cleanupStaleFiles() {
|
|
|
1773
1850
|
}
|
|
1774
1851
|
}
|
|
1775
1852
|
function findPackageRoot() {
|
|
1776
|
-
let dir =
|
|
1777
|
-
const { root } =
|
|
1853
|
+
let dir = path9.dirname(fileURLToPath(import.meta.url));
|
|
1854
|
+
const { root } = path9.parse(dir);
|
|
1778
1855
|
while (dir !== root) {
|
|
1779
|
-
if (
|
|
1780
|
-
dir =
|
|
1856
|
+
if (existsSync8(path9.join(dir, "package.json"))) return dir;
|
|
1857
|
+
dir = path9.dirname(dir);
|
|
1781
1858
|
}
|
|
1782
1859
|
return null;
|
|
1783
1860
|
}
|
|
@@ -1803,16 +1880,17 @@ function spawnDaemon() {
|
|
|
1803
1880
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
1804
1881
|
return;
|
|
1805
1882
|
}
|
|
1806
|
-
const daemonPath =
|
|
1807
|
-
if (!
|
|
1883
|
+
const daemonPath = path9.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
1884
|
+
if (!existsSync8(daemonPath)) {
|
|
1808
1885
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
1809
1886
|
`);
|
|
1810
1887
|
return;
|
|
1811
1888
|
}
|
|
1812
1889
|
const resolvedPath = daemonPath;
|
|
1890
|
+
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
1813
1891
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
1814
1892
|
`);
|
|
1815
|
-
const logPath =
|
|
1893
|
+
const logPath = path9.join(path9.dirname(SOCKET_PATH), "exed.log");
|
|
1816
1894
|
let stderrFd = "ignore";
|
|
1817
1895
|
try {
|
|
1818
1896
|
stderrFd = openSync(logPath, "a");
|
|
@@ -1830,7 +1908,8 @@ function spawnDaemon() {
|
|
|
1830
1908
|
TMUX_PANE: void 0,
|
|
1831
1909
|
// Prevents resolveExeSession() from scoping to one session
|
|
1832
1910
|
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
1833
|
-
EXE_DAEMON_PID: PID_PATH
|
|
1911
|
+
EXE_DAEMON_PID: PID_PATH,
|
|
1912
|
+
[DAEMON_TOKEN_ENV]: daemonToken
|
|
1834
1913
|
}
|
|
1835
1914
|
});
|
|
1836
1915
|
child.unref();
|
|
@@ -1940,13 +2019,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
1940
2019
|
return;
|
|
1941
2020
|
}
|
|
1942
2021
|
const id = randomUUID2();
|
|
2022
|
+
const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
|
|
1943
2023
|
const timer = setTimeout(() => {
|
|
1944
2024
|
_pending.delete(id);
|
|
1945
2025
|
resolve({ error: "Request timeout" });
|
|
1946
2026
|
}, timeoutMs);
|
|
1947
2027
|
_pending.set(id, { resolve, timer });
|
|
1948
2028
|
try {
|
|
1949
|
-
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
2029
|
+
_socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
|
|
1950
2030
|
} catch {
|
|
1951
2031
|
clearTimeout(timer);
|
|
1952
2032
|
_pending.delete(id);
|
|
@@ -1975,9 +2055,9 @@ function killAndRespawnDaemon() {
|
|
|
1975
2055
|
}
|
|
1976
2056
|
try {
|
|
1977
2057
|
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
1978
|
-
if (
|
|
2058
|
+
if (existsSync8(PID_PATH)) {
|
|
1979
2059
|
try {
|
|
1980
|
-
const pid = parseInt(
|
|
2060
|
+
const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
|
|
1981
2061
|
if (pid > 0) {
|
|
1982
2062
|
try {
|
|
1983
2063
|
process.kill(pid, "SIGKILL");
|
|
@@ -2097,17 +2177,19 @@ function disconnectClient() {
|
|
|
2097
2177
|
function isClientConnected() {
|
|
2098
2178
|
return _connected;
|
|
2099
2179
|
}
|
|
2100
|
-
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, _consecutiveFailures, HEALTH_CHECK_INTERVAL, MAX_RETRIES_BEFORE_RESTART, RETRY_DELAYS_MS, MIN_DAEMON_AGE_MS, _pending, MAX_BUFFER;
|
|
2180
|
+
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, DAEMON_TOKEN_ENV, _socket, _connected, _buffer, _requestCount, _consecutiveFailures, HEALTH_CHECK_INTERVAL, MAX_RETRIES_BEFORE_RESTART, RETRY_DELAYS_MS, MIN_DAEMON_AGE_MS, _pending, MAX_BUFFER;
|
|
2101
2181
|
var init_exe_daemon_client = __esm({
|
|
2102
2182
|
"src/lib/exe-daemon-client.ts"() {
|
|
2103
2183
|
"use strict";
|
|
2104
2184
|
init_config();
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2185
|
+
init_daemon_auth();
|
|
2186
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path9.join(EXE_AI_DIR, "exed.sock");
|
|
2187
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path9.join(EXE_AI_DIR, "exed.pid");
|
|
2188
|
+
SPAWN_LOCK_PATH = path9.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
2108
2189
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
2109
2190
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
2110
2191
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
2192
|
+
DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
|
|
2111
2193
|
_socket = null;
|
|
2112
2194
|
_connected = false;
|
|
2113
2195
|
_buffer = "";
|
|
@@ -2692,6 +2774,7 @@ async function ensureSchema() {
|
|
|
2692
2774
|
project TEXT NOT NULL,
|
|
2693
2775
|
summary TEXT NOT NULL,
|
|
2694
2776
|
task_file TEXT,
|
|
2777
|
+
session_scope TEXT,
|
|
2695
2778
|
read INTEGER NOT NULL DEFAULT 0,
|
|
2696
2779
|
created_at TEXT NOT NULL
|
|
2697
2780
|
);
|
|
@@ -2700,7 +2783,7 @@ async function ensureSchema() {
|
|
|
2700
2783
|
ON notifications(read);
|
|
2701
2784
|
|
|
2702
2785
|
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
2703
|
-
ON notifications(agent_id);
|
|
2786
|
+
ON notifications(agent_id, session_scope);
|
|
2704
2787
|
|
|
2705
2788
|
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
2706
2789
|
ON notifications(task_file);
|
|
@@ -2738,6 +2821,7 @@ async function ensureSchema() {
|
|
|
2738
2821
|
target_agent TEXT NOT NULL,
|
|
2739
2822
|
target_project TEXT,
|
|
2740
2823
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
2824
|
+
session_scope TEXT,
|
|
2741
2825
|
content TEXT NOT NULL,
|
|
2742
2826
|
priority TEXT DEFAULT 'normal',
|
|
2743
2827
|
status TEXT DEFAULT 'pending',
|
|
@@ -2751,10 +2835,31 @@ async function ensureSchema() {
|
|
|
2751
2835
|
);
|
|
2752
2836
|
|
|
2753
2837
|
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
2754
|
-
ON messages(target_agent, status);
|
|
2838
|
+
ON messages(target_agent, session_scope, status);
|
|
2755
2839
|
|
|
2756
2840
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
2757
|
-
ON messages(target_agent, from_agent, server_seq);
|
|
2841
|
+
ON messages(target_agent, session_scope, from_agent, server_seq);
|
|
2842
|
+
`);
|
|
2843
|
+
try {
|
|
2844
|
+
await client.execute({
|
|
2845
|
+
sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
|
|
2846
|
+
args: []
|
|
2847
|
+
});
|
|
2848
|
+
} catch {
|
|
2849
|
+
}
|
|
2850
|
+
try {
|
|
2851
|
+
await client.execute({
|
|
2852
|
+
sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
|
|
2853
|
+
args: []
|
|
2854
|
+
});
|
|
2855
|
+
} catch {
|
|
2856
|
+
}
|
|
2857
|
+
await client.executeMultiple(`
|
|
2858
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
|
|
2859
|
+
ON notifications(agent_id, session_scope, read, created_at);
|
|
2860
|
+
|
|
2861
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
|
|
2862
|
+
ON messages(target_agent, session_scope, status, created_at);
|
|
2758
2863
|
`);
|
|
2759
2864
|
try {
|
|
2760
2865
|
await client.execute({
|
|
@@ -3338,6 +3443,13 @@ async function ensureSchema() {
|
|
|
3338
3443
|
} catch {
|
|
3339
3444
|
}
|
|
3340
3445
|
}
|
|
3446
|
+
try {
|
|
3447
|
+
await client.execute({
|
|
3448
|
+
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
3449
|
+
args: []
|
|
3450
|
+
});
|
|
3451
|
+
} catch {
|
|
3452
|
+
}
|
|
3341
3453
|
}
|
|
3342
3454
|
async function disposeDatabase() {
|
|
3343
3455
|
if (_walCheckpointTimer) {
|
|
@@ -3376,18 +3488,21 @@ var init_database = __esm({
|
|
|
3376
3488
|
});
|
|
3377
3489
|
|
|
3378
3490
|
// src/lib/license.ts
|
|
3379
|
-
import { readFileSync as
|
|
3491
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync9, mkdirSync as mkdirSync4 } from "fs";
|
|
3380
3492
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
3381
|
-
import
|
|
3493
|
+
import { createRequire as createRequire2 } from "module";
|
|
3494
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
3495
|
+
import os8 from "os";
|
|
3496
|
+
import path10 from "path";
|
|
3382
3497
|
import { jwtVerify, importSPKI } from "jose";
|
|
3383
3498
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
|
|
3384
3499
|
var init_license = __esm({
|
|
3385
3500
|
"src/lib/license.ts"() {
|
|
3386
3501
|
"use strict";
|
|
3387
3502
|
init_config();
|
|
3388
|
-
LICENSE_PATH =
|
|
3389
|
-
CACHE_PATH =
|
|
3390
|
-
DEVICE_ID_PATH =
|
|
3503
|
+
LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
|
|
3504
|
+
CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
|
|
3505
|
+
DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
|
|
3391
3506
|
PLAN_LIMITS = {
|
|
3392
3507
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
3393
3508
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -3399,12 +3514,12 @@ var init_license = __esm({
|
|
|
3399
3514
|
});
|
|
3400
3515
|
|
|
3401
3516
|
// src/lib/plan-limits.ts
|
|
3402
|
-
import { readFileSync as
|
|
3403
|
-
import
|
|
3517
|
+
import { readFileSync as readFileSync9, existsSync as existsSync10 } from "fs";
|
|
3518
|
+
import path11 from "path";
|
|
3404
3519
|
function getLicenseSync() {
|
|
3405
3520
|
try {
|
|
3406
|
-
if (!
|
|
3407
|
-
const raw = JSON.parse(
|
|
3521
|
+
if (!existsSync10(CACHE_PATH2)) return freeLicense();
|
|
3522
|
+
const raw = JSON.parse(readFileSync9(CACHE_PATH2, "utf8"));
|
|
3408
3523
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
3409
3524
|
const parts = raw.token.split(".");
|
|
3410
3525
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -3442,8 +3557,8 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
3442
3557
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
3443
3558
|
let count = 0;
|
|
3444
3559
|
try {
|
|
3445
|
-
if (
|
|
3446
|
-
const raw =
|
|
3560
|
+
if (existsSync10(filePath)) {
|
|
3561
|
+
const raw = readFileSync9(filePath, "utf8");
|
|
3447
3562
|
const employees = JSON.parse(raw);
|
|
3448
3563
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
3449
3564
|
}
|
|
@@ -3472,29 +3587,69 @@ var init_plan_limits = __esm({
|
|
|
3472
3587
|
this.name = "PlanLimitError";
|
|
3473
3588
|
}
|
|
3474
3589
|
};
|
|
3475
|
-
CACHE_PATH2 =
|
|
3590
|
+
CACHE_PATH2 = path11.join(EXE_AI_DIR, "license-cache.json");
|
|
3591
|
+
}
|
|
3592
|
+
});
|
|
3593
|
+
|
|
3594
|
+
// src/lib/task-scope.ts
|
|
3595
|
+
var task_scope_exports = {};
|
|
3596
|
+
__export(task_scope_exports, {
|
|
3597
|
+
getCurrentSessionScope: () => getCurrentSessionScope,
|
|
3598
|
+
sessionScopeFilter: () => sessionScopeFilter,
|
|
3599
|
+
strictSessionScopeFilter: () => strictSessionScopeFilter
|
|
3600
|
+
});
|
|
3601
|
+
function getCurrentSessionScope() {
|
|
3602
|
+
try {
|
|
3603
|
+
return resolveExeSession();
|
|
3604
|
+
} catch {
|
|
3605
|
+
return null;
|
|
3606
|
+
}
|
|
3607
|
+
}
|
|
3608
|
+
function sessionScopeFilter(sessionScope, tableAlias) {
|
|
3609
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
3610
|
+
if (!scope) return { sql: "", args: [] };
|
|
3611
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
3612
|
+
return {
|
|
3613
|
+
sql: ` AND (${col} IS NULL OR ${col} = ?)`,
|
|
3614
|
+
args: [scope]
|
|
3615
|
+
};
|
|
3616
|
+
}
|
|
3617
|
+
function strictSessionScopeFilter(sessionScope, tableAlias) {
|
|
3618
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
3619
|
+
if (!scope) return { sql: "", args: [] };
|
|
3620
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
3621
|
+
return {
|
|
3622
|
+
sql: ` AND ${col} = ?`,
|
|
3623
|
+
args: [scope]
|
|
3624
|
+
};
|
|
3625
|
+
}
|
|
3626
|
+
var init_task_scope = __esm({
|
|
3627
|
+
"src/lib/task-scope.ts"() {
|
|
3628
|
+
"use strict";
|
|
3629
|
+
init_tmux_routing();
|
|
3476
3630
|
}
|
|
3477
3631
|
});
|
|
3478
3632
|
|
|
3479
3633
|
// src/lib/notifications.ts
|
|
3480
|
-
import
|
|
3481
|
-
import
|
|
3482
|
-
import
|
|
3634
|
+
import crypto2 from "crypto";
|
|
3635
|
+
import path12 from "path";
|
|
3636
|
+
import os9 from "os";
|
|
3483
3637
|
import {
|
|
3484
|
-
readFileSync as
|
|
3638
|
+
readFileSync as readFileSync10,
|
|
3485
3639
|
readdirSync,
|
|
3486
3640
|
unlinkSync as unlinkSync3,
|
|
3487
|
-
existsSync as
|
|
3641
|
+
existsSync as existsSync11,
|
|
3488
3642
|
rmdirSync
|
|
3489
3643
|
} from "fs";
|
|
3490
3644
|
async function writeNotification(notification) {
|
|
3491
3645
|
try {
|
|
3492
3646
|
const client = getClient();
|
|
3493
|
-
const id =
|
|
3647
|
+
const id = crypto2.randomUUID();
|
|
3494
3648
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3649
|
+
const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
|
|
3495
3650
|
await client.execute({
|
|
3496
|
-
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
|
|
3497
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
3651
|
+
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
3652
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
3498
3653
|
args: [
|
|
3499
3654
|
id,
|
|
3500
3655
|
notification.agentId,
|
|
@@ -3503,6 +3658,7 @@ async function writeNotification(notification) {
|
|
|
3503
3658
|
notification.project,
|
|
3504
3659
|
notification.summary,
|
|
3505
3660
|
notification.taskFile ?? null,
|
|
3661
|
+
sessionScope,
|
|
3506
3662
|
now
|
|
3507
3663
|
]
|
|
3508
3664
|
});
|
|
@@ -3511,12 +3667,14 @@ async function writeNotification(notification) {
|
|
|
3511
3667
|
`);
|
|
3512
3668
|
}
|
|
3513
3669
|
}
|
|
3514
|
-
async function markAsReadByTaskFile(taskFile) {
|
|
3670
|
+
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
3515
3671
|
try {
|
|
3516
3672
|
const client = getClient();
|
|
3673
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
3517
3674
|
await client.execute({
|
|
3518
|
-
sql:
|
|
3519
|
-
|
|
3675
|
+
sql: `UPDATE notifications SET read = 1
|
|
3676
|
+
WHERE task_file = ? AND read = 0${scope.sql}`,
|
|
3677
|
+
args: [taskFile, ...scope.args]
|
|
3520
3678
|
});
|
|
3521
3679
|
} catch {
|
|
3522
3680
|
}
|
|
@@ -3525,11 +3683,12 @@ var init_notifications = __esm({
|
|
|
3525
3683
|
"src/lib/notifications.ts"() {
|
|
3526
3684
|
"use strict";
|
|
3527
3685
|
init_database();
|
|
3686
|
+
init_task_scope();
|
|
3528
3687
|
}
|
|
3529
3688
|
});
|
|
3530
3689
|
|
|
3531
3690
|
// src/lib/session-kill-telemetry.ts
|
|
3532
|
-
import
|
|
3691
|
+
import crypto3 from "crypto";
|
|
3533
3692
|
async function recordSessionKill(input) {
|
|
3534
3693
|
try {
|
|
3535
3694
|
const client = getClient();
|
|
@@ -3539,7 +3698,7 @@ async function recordSessionKill(input) {
|
|
|
3539
3698
|
ticks_idle, estimated_tokens_saved)
|
|
3540
3699
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
3541
3700
|
args: [
|
|
3542
|
-
|
|
3701
|
+
crypto3.randomUUID(),
|
|
3543
3702
|
input.sessionName,
|
|
3544
3703
|
input.agentId,
|
|
3545
3704
|
(/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -3562,35 +3721,6 @@ var init_session_kill_telemetry = __esm({
|
|
|
3562
3721
|
}
|
|
3563
3722
|
});
|
|
3564
3723
|
|
|
3565
|
-
// src/lib/task-scope.ts
|
|
3566
|
-
var task_scope_exports = {};
|
|
3567
|
-
__export(task_scope_exports, {
|
|
3568
|
-
getCurrentSessionScope: () => getCurrentSessionScope,
|
|
3569
|
-
sessionScopeFilter: () => sessionScopeFilter
|
|
3570
|
-
});
|
|
3571
|
-
function getCurrentSessionScope() {
|
|
3572
|
-
try {
|
|
3573
|
-
return resolveExeSession();
|
|
3574
|
-
} catch {
|
|
3575
|
-
return null;
|
|
3576
|
-
}
|
|
3577
|
-
}
|
|
3578
|
-
function sessionScopeFilter(sessionScope, tableAlias) {
|
|
3579
|
-
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
3580
|
-
if (!scope) return { sql: "", args: [] };
|
|
3581
|
-
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
3582
|
-
return {
|
|
3583
|
-
sql: ` AND (${col} IS NULL OR ${col} = ?)`,
|
|
3584
|
-
args: [scope]
|
|
3585
|
-
};
|
|
3586
|
-
}
|
|
3587
|
-
var init_task_scope = __esm({
|
|
3588
|
-
"src/lib/task-scope.ts"() {
|
|
3589
|
-
"use strict";
|
|
3590
|
-
init_tmux_routing();
|
|
3591
|
-
}
|
|
3592
|
-
});
|
|
3593
|
-
|
|
3594
3724
|
// src/lib/state-bus.ts
|
|
3595
3725
|
var StateBus, orgBus;
|
|
3596
3726
|
var init_state_bus = __esm({
|
|
@@ -3646,13 +3776,117 @@ var init_state_bus = __esm({
|
|
|
3646
3776
|
}
|
|
3647
3777
|
});
|
|
3648
3778
|
|
|
3649
|
-
// src/lib/
|
|
3650
|
-
import crypto3 from "crypto";
|
|
3651
|
-
import path12 from "path";
|
|
3652
|
-
import os9 from "os";
|
|
3779
|
+
// src/lib/project-name.ts
|
|
3653
3780
|
import { execSync as execSync5 } from "child_process";
|
|
3781
|
+
import path13 from "path";
|
|
3782
|
+
function getProjectName(cwd) {
|
|
3783
|
+
const dir = cwd ?? process.cwd();
|
|
3784
|
+
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
3785
|
+
try {
|
|
3786
|
+
let repoRoot;
|
|
3787
|
+
try {
|
|
3788
|
+
const gitCommonDir = execSync5("git rev-parse --path-format=absolute --git-common-dir", {
|
|
3789
|
+
cwd: dir,
|
|
3790
|
+
encoding: "utf8",
|
|
3791
|
+
timeout: 2e3,
|
|
3792
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3793
|
+
}).trim();
|
|
3794
|
+
repoRoot = path13.dirname(gitCommonDir);
|
|
3795
|
+
} catch {
|
|
3796
|
+
repoRoot = execSync5("git rev-parse --show-toplevel", {
|
|
3797
|
+
cwd: dir,
|
|
3798
|
+
encoding: "utf8",
|
|
3799
|
+
timeout: 2e3,
|
|
3800
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3801
|
+
}).trim();
|
|
3802
|
+
}
|
|
3803
|
+
_cached2 = path13.basename(repoRoot);
|
|
3804
|
+
_cachedCwd = dir;
|
|
3805
|
+
return _cached2;
|
|
3806
|
+
} catch {
|
|
3807
|
+
_cached2 = path13.basename(dir);
|
|
3808
|
+
_cachedCwd = dir;
|
|
3809
|
+
return _cached2;
|
|
3810
|
+
}
|
|
3811
|
+
}
|
|
3812
|
+
var _cached2, _cachedCwd;
|
|
3813
|
+
var init_project_name = __esm({
|
|
3814
|
+
"src/lib/project-name.ts"() {
|
|
3815
|
+
"use strict";
|
|
3816
|
+
_cached2 = null;
|
|
3817
|
+
_cachedCwd = null;
|
|
3818
|
+
}
|
|
3819
|
+
});
|
|
3820
|
+
|
|
3821
|
+
// src/lib/session-scope.ts
|
|
3822
|
+
var session_scope_exports = {};
|
|
3823
|
+
__export(session_scope_exports, {
|
|
3824
|
+
assertSessionScope: () => assertSessionScope,
|
|
3825
|
+
findSessionForProject: () => findSessionForProject,
|
|
3826
|
+
getSessionProject: () => getSessionProject
|
|
3827
|
+
});
|
|
3828
|
+
function getSessionProject(sessionName) {
|
|
3829
|
+
const sessions = listSessions();
|
|
3830
|
+
const entry = sessions.find((s) => s.windowName === sessionName);
|
|
3831
|
+
if (!entry) return null;
|
|
3832
|
+
const parts = entry.projectDir.split("/").filter(Boolean);
|
|
3833
|
+
return parts[parts.length - 1] ?? null;
|
|
3834
|
+
}
|
|
3835
|
+
function findSessionForProject(projectName) {
|
|
3836
|
+
const sessions = listSessions();
|
|
3837
|
+
for (const s of sessions) {
|
|
3838
|
+
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
3839
|
+
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
3840
|
+
}
|
|
3841
|
+
return null;
|
|
3842
|
+
}
|
|
3843
|
+
function assertSessionScope(actionType, targetProject) {
|
|
3844
|
+
try {
|
|
3845
|
+
const currentProject = getProjectName();
|
|
3846
|
+
const exeSession = resolveExeSession();
|
|
3847
|
+
if (!exeSession) {
|
|
3848
|
+
return { allowed: true, reason: "no_session" };
|
|
3849
|
+
}
|
|
3850
|
+
if (currentProject === targetProject) {
|
|
3851
|
+
return {
|
|
3852
|
+
allowed: true,
|
|
3853
|
+
reason: "same_session",
|
|
3854
|
+
currentProject,
|
|
3855
|
+
targetProject
|
|
3856
|
+
};
|
|
3857
|
+
}
|
|
3858
|
+
process.stderr.write(
|
|
3859
|
+
`[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
|
|
3860
|
+
`
|
|
3861
|
+
);
|
|
3862
|
+
return {
|
|
3863
|
+
allowed: false,
|
|
3864
|
+
reason: "cross_session_denied",
|
|
3865
|
+
currentProject,
|
|
3866
|
+
targetProject,
|
|
3867
|
+
targetSession: findSessionForProject(targetProject)?.windowName
|
|
3868
|
+
};
|
|
3869
|
+
} catch {
|
|
3870
|
+
return { allowed: true, reason: "no_session" };
|
|
3871
|
+
}
|
|
3872
|
+
}
|
|
3873
|
+
var init_session_scope = __esm({
|
|
3874
|
+
"src/lib/session-scope.ts"() {
|
|
3875
|
+
"use strict";
|
|
3876
|
+
init_session_registry();
|
|
3877
|
+
init_project_name();
|
|
3878
|
+
init_tmux_routing();
|
|
3879
|
+
init_employees();
|
|
3880
|
+
}
|
|
3881
|
+
});
|
|
3882
|
+
|
|
3883
|
+
// src/lib/tasks-crud.ts
|
|
3884
|
+
import crypto4 from "crypto";
|
|
3885
|
+
import path14 from "path";
|
|
3886
|
+
import os10 from "os";
|
|
3887
|
+
import { execSync as execSync6 } from "child_process";
|
|
3654
3888
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
3655
|
-
import { existsSync as
|
|
3889
|
+
import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
|
|
3656
3890
|
async function writeCheckpoint(input) {
|
|
3657
3891
|
const client = getClient();
|
|
3658
3892
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -3768,13 +4002,28 @@ async function resolveTask(client, identifier, scopeSession) {
|
|
|
3768
4002
|
}
|
|
3769
4003
|
async function createTaskCore(input) {
|
|
3770
4004
|
const client = getClient();
|
|
3771
|
-
const id =
|
|
4005
|
+
const id = crypto4.randomUUID();
|
|
3772
4006
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3773
4007
|
const slug = slugify(input.title);
|
|
3774
4008
|
let earlySessionScope = null;
|
|
4009
|
+
let scopeMismatchWarning;
|
|
3775
4010
|
try {
|
|
3776
4011
|
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
3777
|
-
|
|
4012
|
+
const resolved = resolveExeSession2();
|
|
4013
|
+
if (resolved && input.projectName) {
|
|
4014
|
+
const { getSessionProject: getSessionProject2 } = await Promise.resolve().then(() => (init_session_scope(), session_scope_exports));
|
|
4015
|
+
const sessionProject = getSessionProject2(resolved);
|
|
4016
|
+
if (sessionProject && sessionProject !== input.projectName) {
|
|
4017
|
+
scopeMismatchWarning = `session/project mismatch: session "${resolved}" owns "${sessionProject}" but task targets "${input.projectName}". Routed to default scope.`;
|
|
4018
|
+
process.stderr.write(`[create_task] ${scopeMismatchWarning}
|
|
4019
|
+
`);
|
|
4020
|
+
earlySessionScope = null;
|
|
4021
|
+
} else {
|
|
4022
|
+
earlySessionScope = resolved;
|
|
4023
|
+
}
|
|
4024
|
+
} else {
|
|
4025
|
+
earlySessionScope = resolved;
|
|
4026
|
+
}
|
|
3778
4027
|
} catch {
|
|
3779
4028
|
}
|
|
3780
4029
|
const scope = earlySessionScope ?? "default";
|
|
@@ -3825,10 +4074,14 @@ async function createTaskCore(input) {
|
|
|
3825
4074
|
${laneWarning}` : laneWarning;
|
|
3826
4075
|
}
|
|
3827
4076
|
}
|
|
4077
|
+
if (scopeMismatchWarning) {
|
|
4078
|
+
warning = warning ? `${warning}
|
|
4079
|
+
${scopeMismatchWarning}` : scopeMismatchWarning;
|
|
4080
|
+
}
|
|
3828
4081
|
if (input.baseDir) {
|
|
3829
4082
|
try {
|
|
3830
|
-
await mkdir3(
|
|
3831
|
-
await mkdir3(
|
|
4083
|
+
await mkdir3(path14.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
4084
|
+
await mkdir3(path14.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
3832
4085
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
3833
4086
|
await ensureGitignoreExe(input.baseDir);
|
|
3834
4087
|
} catch {
|
|
@@ -3864,13 +4117,19 @@ ${laneWarning}` : laneWarning;
|
|
|
3864
4117
|
});
|
|
3865
4118
|
if (input.baseDir) {
|
|
3866
4119
|
try {
|
|
3867
|
-
const EXE_OS_DIR =
|
|
3868
|
-
const mdPath =
|
|
3869
|
-
const mdDir =
|
|
3870
|
-
if (!
|
|
4120
|
+
const EXE_OS_DIR = path14.join(os10.homedir(), ".exe-os");
|
|
4121
|
+
const mdPath = path14.join(EXE_OS_DIR, taskFile);
|
|
4122
|
+
const mdDir = path14.dirname(mdPath);
|
|
4123
|
+
if (!existsSync12(mdDir)) await mkdir3(mdDir, { recursive: true });
|
|
3871
4124
|
const reviewer = input.reviewer ?? input.assignedBy;
|
|
3872
4125
|
const mdContent = `# ${input.title}
|
|
3873
4126
|
|
|
4127
|
+
## MANDATORY: When done
|
|
4128
|
+
|
|
4129
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
4130
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
4131
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
4132
|
+
|
|
3874
4133
|
**ID:** ${id}
|
|
3875
4134
|
**Status:** ${initialStatus}
|
|
3876
4135
|
**Priority:** ${input.priority}
|
|
@@ -3884,12 +4143,6 @@ ${laneWarning}` : laneWarning;
|
|
|
3884
4143
|
## Context
|
|
3885
4144
|
|
|
3886
4145
|
${input.context}
|
|
3887
|
-
|
|
3888
|
-
## MANDATORY: When done
|
|
3889
|
-
|
|
3890
|
-
You MUST call update_task with status "done" and a result summary when finished.
|
|
3891
|
-
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
3892
|
-
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
3893
4146
|
`;
|
|
3894
4147
|
await writeFile3(mdPath, mdContent, "utf-8");
|
|
3895
4148
|
} catch (err) {
|
|
@@ -3971,14 +4224,14 @@ function isTmuxSessionAlive(identifier) {
|
|
|
3971
4224
|
if (!identifier || identifier === "unknown") return true;
|
|
3972
4225
|
try {
|
|
3973
4226
|
if (identifier.startsWith("%")) {
|
|
3974
|
-
const output =
|
|
4227
|
+
const output = execSync6("tmux list-panes -a -F '#{pane_id}'", {
|
|
3975
4228
|
timeout: 2e3,
|
|
3976
4229
|
encoding: "utf8",
|
|
3977
4230
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3978
4231
|
});
|
|
3979
4232
|
return output.split("\n").some((l) => l.trim() === identifier);
|
|
3980
4233
|
} else {
|
|
3981
|
-
|
|
4234
|
+
execSync6(`tmux has-session -t ${JSON.stringify(identifier)}`, {
|
|
3982
4235
|
timeout: 2e3,
|
|
3983
4236
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3984
4237
|
});
|
|
@@ -3987,7 +4240,7 @@ function isTmuxSessionAlive(identifier) {
|
|
|
3987
4240
|
} catch {
|
|
3988
4241
|
if (identifier.startsWith("%")) return true;
|
|
3989
4242
|
try {
|
|
3990
|
-
|
|
4243
|
+
execSync6("tmux list-sessions", {
|
|
3991
4244
|
timeout: 2e3,
|
|
3992
4245
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3993
4246
|
});
|
|
@@ -4002,12 +4255,12 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
|
|
|
4002
4255
|
if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
|
|
4003
4256
|
try {
|
|
4004
4257
|
const since = new Date(taskCreatedAt).toISOString();
|
|
4005
|
-
const branch =
|
|
4258
|
+
const branch = execSync6(
|
|
4006
4259
|
"git rev-parse --abbrev-ref HEAD 2>/dev/null",
|
|
4007
4260
|
{ encoding: "utf8", timeout: 3e3 }
|
|
4008
4261
|
).trim();
|
|
4009
4262
|
const branchArg = branch && branch !== "HEAD" ? branch : "";
|
|
4010
|
-
const commitCount =
|
|
4263
|
+
const commitCount = execSync6(
|
|
4011
4264
|
`git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
|
|
4012
4265
|
{ encoding: "utf8", timeout: 5e3 }
|
|
4013
4266
|
).trim();
|
|
@@ -4138,7 +4391,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
4138
4391
|
await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
|
|
4139
4392
|
} catch {
|
|
4140
4393
|
}
|
|
4141
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
4394
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
4142
4395
|
try {
|
|
4143
4396
|
const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
4144
4397
|
clearQueueForAgent2(String(row.assigned_to));
|
|
@@ -4167,9 +4420,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
4167
4420
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
4168
4421
|
}
|
|
4169
4422
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
4170
|
-
const archPath =
|
|
4423
|
+
const archPath = path14.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
4171
4424
|
try {
|
|
4172
|
-
if (
|
|
4425
|
+
if (existsSync12(archPath)) return;
|
|
4173
4426
|
const template = [
|
|
4174
4427
|
`# ${projectName} \u2014 System Architecture`,
|
|
4175
4428
|
"",
|
|
@@ -4202,10 +4455,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
4202
4455
|
}
|
|
4203
4456
|
}
|
|
4204
4457
|
async function ensureGitignoreExe(baseDir) {
|
|
4205
|
-
const gitignorePath =
|
|
4458
|
+
const gitignorePath = path14.join(baseDir, ".gitignore");
|
|
4206
4459
|
try {
|
|
4207
|
-
if (
|
|
4208
|
-
const content =
|
|
4460
|
+
if (existsSync12(gitignorePath)) {
|
|
4461
|
+
const content = readFileSync11(gitignorePath, "utf-8");
|
|
4209
4462
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
4210
4463
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
4211
4464
|
} else {
|
|
@@ -4236,58 +4489,42 @@ var init_tasks_crud = __esm({
|
|
|
4236
4489
|
});
|
|
4237
4490
|
|
|
4238
4491
|
// src/lib/tasks-review.ts
|
|
4239
|
-
import
|
|
4240
|
-
import { existsSync as
|
|
4492
|
+
import path15 from "path";
|
|
4493
|
+
import { existsSync as existsSync13, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
|
|
4241
4494
|
async function countPendingReviews(sessionScope) {
|
|
4242
4495
|
const client = getClient();
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
args: [sessionScope]
|
|
4247
|
-
});
|
|
4248
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
4249
|
-
}
|
|
4496
|
+
const scope = strictSessionScopeFilter(
|
|
4497
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
4498
|
+
);
|
|
4250
4499
|
const result = await client.execute({
|
|
4251
|
-
sql:
|
|
4252
|
-
|
|
4500
|
+
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
4501
|
+
WHERE status = 'needs_review'${scope.sql}`,
|
|
4502
|
+
args: [...scope.args]
|
|
4253
4503
|
});
|
|
4254
4504
|
return Number(result.rows[0]?.cnt) || 0;
|
|
4255
4505
|
}
|
|
4256
4506
|
async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
4257
4507
|
const client = getClient();
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
WHERE status = 'needs_review' AND updated_at > ?
|
|
4262
|
-
AND session_scope = ?`,
|
|
4263
|
-
args: [sinceIso, sessionScope]
|
|
4264
|
-
});
|
|
4265
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
4266
|
-
}
|
|
4508
|
+
const scope = strictSessionScopeFilter(
|
|
4509
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
4510
|
+
);
|
|
4267
4511
|
const result = await client.execute({
|
|
4268
4512
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
4269
|
-
WHERE status = 'needs_review' AND updated_at >
|
|
4270
|
-
args: [sinceIso]
|
|
4513
|
+
WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
|
|
4514
|
+
args: [sinceIso, ...scope.args]
|
|
4271
4515
|
});
|
|
4272
4516
|
return Number(result.rows[0]?.cnt) || 0;
|
|
4273
4517
|
}
|
|
4274
4518
|
async function listPendingReviews(limit, sessionScope) {
|
|
4275
4519
|
const client = getClient();
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
WHERE status = 'needs_review'
|
|
4280
|
-
AND session_scope = ?
|
|
4281
|
-
ORDER BY updated_at ASC LIMIT ?`,
|
|
4282
|
-
args: [sessionScope, limit]
|
|
4283
|
-
});
|
|
4284
|
-
return result2.rows;
|
|
4285
|
-
}
|
|
4520
|
+
const scope = strictSessionScopeFilter(
|
|
4521
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
4522
|
+
);
|
|
4286
4523
|
const result = await client.execute({
|
|
4287
4524
|
sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
|
|
4288
|
-
WHERE status = 'needs_review'
|
|
4525
|
+
WHERE status = 'needs_review'${scope.sql}
|
|
4289
4526
|
ORDER BY updated_at ASC LIMIT ?`,
|
|
4290
|
-
args: [limit]
|
|
4527
|
+
args: [...scope.args, limit]
|
|
4291
4528
|
});
|
|
4292
4529
|
return result.rows;
|
|
4293
4530
|
}
|
|
@@ -4299,7 +4536,7 @@ async function cleanupOrphanedReviews() {
|
|
|
4299
4536
|
WHERE status IN ('open', 'needs_review', 'in_progress')
|
|
4300
4537
|
AND assigned_by = 'system'
|
|
4301
4538
|
AND title LIKE 'Review:%'
|
|
4302
|
-
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
|
|
4539
|
+
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
|
|
4303
4540
|
args: [now]
|
|
4304
4541
|
});
|
|
4305
4542
|
const r1b = await client.execute({
|
|
@@ -4418,11 +4655,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
4418
4655
|
);
|
|
4419
4656
|
}
|
|
4420
4657
|
try {
|
|
4421
|
-
const cacheDir =
|
|
4422
|
-
if (
|
|
4658
|
+
const cacheDir = path15.join(EXE_AI_DIR, "session-cache");
|
|
4659
|
+
if (existsSync13(cacheDir)) {
|
|
4423
4660
|
for (const f of readdirSync2(cacheDir)) {
|
|
4424
4661
|
if (f.startsWith("review-notified-")) {
|
|
4425
|
-
unlinkSync4(
|
|
4662
|
+
unlinkSync4(path15.join(cacheDir, f));
|
|
4426
4663
|
}
|
|
4427
4664
|
}
|
|
4428
4665
|
}
|
|
@@ -4439,11 +4676,12 @@ var init_tasks_review = __esm({
|
|
|
4439
4676
|
init_tmux_routing();
|
|
4440
4677
|
init_session_key();
|
|
4441
4678
|
init_state_bus();
|
|
4679
|
+
init_task_scope();
|
|
4442
4680
|
}
|
|
4443
4681
|
});
|
|
4444
4682
|
|
|
4445
4683
|
// src/lib/tasks-chain.ts
|
|
4446
|
-
import
|
|
4684
|
+
import path16 from "path";
|
|
4447
4685
|
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
4448
4686
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
4449
4687
|
const client = getClient();
|
|
@@ -4460,7 +4698,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
4460
4698
|
});
|
|
4461
4699
|
for (const ur of unblockedRows.rows) {
|
|
4462
4700
|
try {
|
|
4463
|
-
const ubFile =
|
|
4701
|
+
const ubFile = path16.join(baseDir, String(ur.task_file));
|
|
4464
4702
|
let ubContent = await readFile3(ubFile, "utf-8");
|
|
4465
4703
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
4466
4704
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -4495,7 +4733,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
|
|
|
4495
4733
|
const scScope = sessionScopeFilter();
|
|
4496
4734
|
const remaining = await client.execute({
|
|
4497
4735
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
4498
|
-
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
|
|
4736
|
+
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
|
|
4499
4737
|
args: [parentTaskId, ...scScope.args]
|
|
4500
4738
|
});
|
|
4501
4739
|
const cnt = Number(remaining.rows[0]?.cnt ?? 1);
|
|
@@ -4527,110 +4765,6 @@ var init_tasks_chain = __esm({
|
|
|
4527
4765
|
}
|
|
4528
4766
|
});
|
|
4529
4767
|
|
|
4530
|
-
// src/lib/project-name.ts
|
|
4531
|
-
import { execSync as execSync6 } from "child_process";
|
|
4532
|
-
import path15 from "path";
|
|
4533
|
-
function getProjectName(cwd) {
|
|
4534
|
-
const dir = cwd ?? process.cwd();
|
|
4535
|
-
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
4536
|
-
try {
|
|
4537
|
-
let repoRoot;
|
|
4538
|
-
try {
|
|
4539
|
-
const gitCommonDir = execSync6("git rev-parse --path-format=absolute --git-common-dir", {
|
|
4540
|
-
cwd: dir,
|
|
4541
|
-
encoding: "utf8",
|
|
4542
|
-
timeout: 2e3,
|
|
4543
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
4544
|
-
}).trim();
|
|
4545
|
-
repoRoot = path15.dirname(gitCommonDir);
|
|
4546
|
-
} catch {
|
|
4547
|
-
repoRoot = execSync6("git rev-parse --show-toplevel", {
|
|
4548
|
-
cwd: dir,
|
|
4549
|
-
encoding: "utf8",
|
|
4550
|
-
timeout: 2e3,
|
|
4551
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
4552
|
-
}).trim();
|
|
4553
|
-
}
|
|
4554
|
-
_cached2 = path15.basename(repoRoot);
|
|
4555
|
-
_cachedCwd = dir;
|
|
4556
|
-
return _cached2;
|
|
4557
|
-
} catch {
|
|
4558
|
-
_cached2 = path15.basename(dir);
|
|
4559
|
-
_cachedCwd = dir;
|
|
4560
|
-
return _cached2;
|
|
4561
|
-
}
|
|
4562
|
-
}
|
|
4563
|
-
var _cached2, _cachedCwd;
|
|
4564
|
-
var init_project_name = __esm({
|
|
4565
|
-
"src/lib/project-name.ts"() {
|
|
4566
|
-
"use strict";
|
|
4567
|
-
_cached2 = null;
|
|
4568
|
-
_cachedCwd = null;
|
|
4569
|
-
}
|
|
4570
|
-
});
|
|
4571
|
-
|
|
4572
|
-
// src/lib/session-scope.ts
|
|
4573
|
-
var session_scope_exports = {};
|
|
4574
|
-
__export(session_scope_exports, {
|
|
4575
|
-
assertSessionScope: () => assertSessionScope,
|
|
4576
|
-
findSessionForProject: () => findSessionForProject,
|
|
4577
|
-
getSessionProject: () => getSessionProject
|
|
4578
|
-
});
|
|
4579
|
-
function getSessionProject(sessionName) {
|
|
4580
|
-
const sessions = listSessions();
|
|
4581
|
-
const entry = sessions.find((s) => s.windowName === sessionName);
|
|
4582
|
-
if (!entry) return null;
|
|
4583
|
-
const parts = entry.projectDir.split("/").filter(Boolean);
|
|
4584
|
-
return parts[parts.length - 1] ?? null;
|
|
4585
|
-
}
|
|
4586
|
-
function findSessionForProject(projectName) {
|
|
4587
|
-
const sessions = listSessions();
|
|
4588
|
-
for (const s of sessions) {
|
|
4589
|
-
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
4590
|
-
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
4591
|
-
}
|
|
4592
|
-
return null;
|
|
4593
|
-
}
|
|
4594
|
-
function assertSessionScope(actionType, targetProject) {
|
|
4595
|
-
try {
|
|
4596
|
-
const currentProject = getProjectName();
|
|
4597
|
-
const exeSession = resolveExeSession();
|
|
4598
|
-
if (!exeSession) {
|
|
4599
|
-
return { allowed: true, reason: "no_session" };
|
|
4600
|
-
}
|
|
4601
|
-
if (currentProject === targetProject) {
|
|
4602
|
-
return {
|
|
4603
|
-
allowed: true,
|
|
4604
|
-
reason: "same_session",
|
|
4605
|
-
currentProject,
|
|
4606
|
-
targetProject
|
|
4607
|
-
};
|
|
4608
|
-
}
|
|
4609
|
-
process.stderr.write(
|
|
4610
|
-
`[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
|
|
4611
|
-
`
|
|
4612
|
-
);
|
|
4613
|
-
return {
|
|
4614
|
-
allowed: false,
|
|
4615
|
-
reason: "cross_session_denied",
|
|
4616
|
-
currentProject,
|
|
4617
|
-
targetProject,
|
|
4618
|
-
targetSession: findSessionForProject(targetProject)?.windowName
|
|
4619
|
-
};
|
|
4620
|
-
} catch {
|
|
4621
|
-
return { allowed: true, reason: "no_session" };
|
|
4622
|
-
}
|
|
4623
|
-
}
|
|
4624
|
-
var init_session_scope = __esm({
|
|
4625
|
-
"src/lib/session-scope.ts"() {
|
|
4626
|
-
"use strict";
|
|
4627
|
-
init_session_registry();
|
|
4628
|
-
init_project_name();
|
|
4629
|
-
init_tmux_routing();
|
|
4630
|
-
init_employees();
|
|
4631
|
-
}
|
|
4632
|
-
});
|
|
4633
|
-
|
|
4634
4768
|
// src/lib/tasks-notify.ts
|
|
4635
4769
|
async function dispatchTaskToEmployee(input) {
|
|
4636
4770
|
if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
|
|
@@ -4705,10 +4839,10 @@ __export(behaviors_exports, {
|
|
|
4705
4839
|
listBehaviorsByDomain: () => listBehaviorsByDomain,
|
|
4706
4840
|
storeBehavior: () => storeBehavior
|
|
4707
4841
|
});
|
|
4708
|
-
import
|
|
4842
|
+
import crypto5 from "crypto";
|
|
4709
4843
|
async function storeBehavior(opts) {
|
|
4710
4844
|
const client = getClient();
|
|
4711
|
-
const id =
|
|
4845
|
+
const id = crypto5.randomUUID();
|
|
4712
4846
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4713
4847
|
await client.execute({
|
|
4714
4848
|
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
|
|
@@ -4792,7 +4926,7 @@ __export(skill_learning_exports, {
|
|
|
4792
4926
|
storeTrajectory: () => storeTrajectory,
|
|
4793
4927
|
sweepTrajectories: () => sweepTrajectories
|
|
4794
4928
|
});
|
|
4795
|
-
import
|
|
4929
|
+
import crypto6 from "crypto";
|
|
4796
4930
|
async function extractTrajectory(taskId, agentId) {
|
|
4797
4931
|
const client = getClient();
|
|
4798
4932
|
const result = await client.execute({
|
|
@@ -4821,11 +4955,11 @@ async function extractTrajectory(taskId, agentId) {
|
|
|
4821
4955
|
return signature;
|
|
4822
4956
|
}
|
|
4823
4957
|
function hashSignature(signature) {
|
|
4824
|
-
return
|
|
4958
|
+
return crypto6.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
|
|
4825
4959
|
}
|
|
4826
4960
|
async function storeTrajectory(opts) {
|
|
4827
4961
|
const client = getClient();
|
|
4828
|
-
const id =
|
|
4962
|
+
const id = crypto6.randomUUID();
|
|
4829
4963
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4830
4964
|
const signatureHash = hashSignature(opts.signature);
|
|
4831
4965
|
await client.execute({
|
|
@@ -5090,8 +5224,8 @@ __export(tasks_exports, {
|
|
|
5090
5224
|
updateTaskStatus: () => updateTaskStatus,
|
|
5091
5225
|
writeCheckpoint: () => writeCheckpoint
|
|
5092
5226
|
});
|
|
5093
|
-
import
|
|
5094
|
-
import { writeFileSync as
|
|
5227
|
+
import path17 from "path";
|
|
5228
|
+
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
|
|
5095
5229
|
async function createTask(input) {
|
|
5096
5230
|
const result = await createTaskCore(input);
|
|
5097
5231
|
if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -5110,12 +5244,12 @@ async function updateTask(input) {
|
|
|
5110
5244
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
5111
5245
|
try {
|
|
5112
5246
|
const agent = String(row.assigned_to);
|
|
5113
|
-
const cacheDir =
|
|
5114
|
-
const cachePath =
|
|
5247
|
+
const cacheDir = path17.join(EXE_AI_DIR, "session-cache");
|
|
5248
|
+
const cachePath = path17.join(cacheDir, `current-task-${agent}.json`);
|
|
5115
5249
|
if (input.status === "in_progress") {
|
|
5116
5250
|
mkdirSync5(cacheDir, { recursive: true });
|
|
5117
|
-
|
|
5118
|
-
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
5251
|
+
writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
5252
|
+
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
|
|
5119
5253
|
try {
|
|
5120
5254
|
unlinkSync5(cachePath);
|
|
5121
5255
|
} catch {
|
|
@@ -5123,10 +5257,10 @@ async function updateTask(input) {
|
|
|
5123
5257
|
}
|
|
5124
5258
|
} catch {
|
|
5125
5259
|
}
|
|
5126
|
-
if (input.status === "done") {
|
|
5260
|
+
if (input.status === "done" || input.status === "closed") {
|
|
5127
5261
|
await cleanupReviewFile(row, taskFile, input.baseDir);
|
|
5128
5262
|
}
|
|
5129
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
5263
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
5130
5264
|
try {
|
|
5131
5265
|
const client = getClient();
|
|
5132
5266
|
const taskTitle = String(row.title);
|
|
@@ -5142,7 +5276,7 @@ async function updateTask(input) {
|
|
|
5142
5276
|
if (!isCoordinatorName(assignedAgent)) {
|
|
5143
5277
|
try {
|
|
5144
5278
|
const draftClient = getClient();
|
|
5145
|
-
if (input.status === "done") {
|
|
5279
|
+
if (input.status === "done" || input.status === "closed") {
|
|
5146
5280
|
await draftClient.execute({
|
|
5147
5281
|
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
5148
5282
|
args: [assignedAgent]
|
|
@@ -5159,7 +5293,7 @@ async function updateTask(input) {
|
|
|
5159
5293
|
try {
|
|
5160
5294
|
const client = getClient();
|
|
5161
5295
|
const cascaded = await client.execute({
|
|
5162
|
-
sql: `UPDATE tasks SET status = '
|
|
5296
|
+
sql: `UPDATE tasks SET status = 'closed', updated_at = ?
|
|
5163
5297
|
WHERE parent_task_id = ? AND status = 'needs_review'`,
|
|
5164
5298
|
args: [now, taskId]
|
|
5165
5299
|
});
|
|
@@ -5172,14 +5306,14 @@ async function updateTask(input) {
|
|
|
5172
5306
|
} catch {
|
|
5173
5307
|
}
|
|
5174
5308
|
}
|
|
5175
|
-
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
5309
|
+
const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
|
|
5176
5310
|
if (isTerminal) {
|
|
5177
5311
|
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
5178
5312
|
if (!isCoordinator) {
|
|
5179
5313
|
notifyTaskDone();
|
|
5180
5314
|
}
|
|
5181
5315
|
await markTaskNotificationsRead(taskFile);
|
|
5182
|
-
if (input.status === "done") {
|
|
5316
|
+
if (input.status === "done" || input.status === "closed") {
|
|
5183
5317
|
try {
|
|
5184
5318
|
await cascadeUnblock(taskId, input.baseDir, now);
|
|
5185
5319
|
} catch {
|
|
@@ -5199,7 +5333,7 @@ async function updateTask(input) {
|
|
|
5199
5333
|
}
|
|
5200
5334
|
}
|
|
5201
5335
|
}
|
|
5202
|
-
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
5336
|
+
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
5203
5337
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
5204
5338
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
5205
5339
|
taskId,
|
|
@@ -5571,6 +5705,7 @@ __export(tmux_routing_exports, {
|
|
|
5571
5705
|
isEmployeeAlive: () => isEmployeeAlive,
|
|
5572
5706
|
isExeSession: () => isExeSession,
|
|
5573
5707
|
isSessionBusy: () => isSessionBusy,
|
|
5708
|
+
notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
|
|
5574
5709
|
notifyParentExe: () => notifyParentExe,
|
|
5575
5710
|
parseParentExe: () => parseParentExe,
|
|
5576
5711
|
registerParentExe: () => registerParentExe,
|
|
@@ -5581,13 +5716,13 @@ __export(tmux_routing_exports, {
|
|
|
5581
5716
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
5582
5717
|
});
|
|
5583
5718
|
import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
|
|
5584
|
-
import { readFileSync as
|
|
5585
|
-
import
|
|
5586
|
-
import
|
|
5719
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync6, existsSync as existsSync14, appendFileSync, readdirSync as readdirSync3 } from "fs";
|
|
5720
|
+
import path18 from "path";
|
|
5721
|
+
import os11 from "os";
|
|
5587
5722
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5588
5723
|
import { unlinkSync as unlinkSync6 } from "fs";
|
|
5589
5724
|
function spawnLockPath(sessionName) {
|
|
5590
|
-
return
|
|
5725
|
+
return path18.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
5591
5726
|
}
|
|
5592
5727
|
function isProcessAlive(pid) {
|
|
5593
5728
|
try {
|
|
@@ -5598,13 +5733,13 @@ function isProcessAlive(pid) {
|
|
|
5598
5733
|
}
|
|
5599
5734
|
}
|
|
5600
5735
|
function acquireSpawnLock2(sessionName) {
|
|
5601
|
-
if (!
|
|
5736
|
+
if (!existsSync14(SPAWN_LOCK_DIR)) {
|
|
5602
5737
|
mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
|
|
5603
5738
|
}
|
|
5604
5739
|
const lockFile = spawnLockPath(sessionName);
|
|
5605
|
-
if (
|
|
5740
|
+
if (existsSync14(lockFile)) {
|
|
5606
5741
|
try {
|
|
5607
|
-
const lock = JSON.parse(
|
|
5742
|
+
const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
|
|
5608
5743
|
const age = Date.now() - lock.timestamp;
|
|
5609
5744
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
5610
5745
|
return false;
|
|
@@ -5612,7 +5747,7 @@ function acquireSpawnLock2(sessionName) {
|
|
|
5612
5747
|
} catch {
|
|
5613
5748
|
}
|
|
5614
5749
|
}
|
|
5615
|
-
|
|
5750
|
+
writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
5616
5751
|
return true;
|
|
5617
5752
|
}
|
|
5618
5753
|
function releaseSpawnLock2(sessionName) {
|
|
@@ -5624,13 +5759,13 @@ function releaseSpawnLock2(sessionName) {
|
|
|
5624
5759
|
function resolveBehaviorsExporterScript() {
|
|
5625
5760
|
try {
|
|
5626
5761
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
5627
|
-
const scriptPath =
|
|
5628
|
-
|
|
5762
|
+
const scriptPath = path18.join(
|
|
5763
|
+
path18.dirname(thisFile),
|
|
5629
5764
|
"..",
|
|
5630
5765
|
"bin",
|
|
5631
5766
|
"exe-export-behaviors.js"
|
|
5632
5767
|
);
|
|
5633
|
-
return
|
|
5768
|
+
return existsSync14(scriptPath) ? scriptPath : null;
|
|
5634
5769
|
} catch {
|
|
5635
5770
|
return null;
|
|
5636
5771
|
}
|
|
@@ -5696,12 +5831,12 @@ function extractRootExe(name) {
|
|
|
5696
5831
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
5697
5832
|
}
|
|
5698
5833
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
5699
|
-
if (!
|
|
5834
|
+
if (!existsSync14(SESSION_CACHE)) {
|
|
5700
5835
|
mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
5701
5836
|
}
|
|
5702
5837
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
5703
|
-
const filePath =
|
|
5704
|
-
|
|
5838
|
+
const filePath = path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
5839
|
+
writeFileSync8(filePath, JSON.stringify({
|
|
5705
5840
|
parentExe: rootExe,
|
|
5706
5841
|
dispatchedBy: dispatchedBy || rootExe,
|
|
5707
5842
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -5709,7 +5844,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
5709
5844
|
}
|
|
5710
5845
|
function getParentExe(sessionKey) {
|
|
5711
5846
|
try {
|
|
5712
|
-
const data = JSON.parse(
|
|
5847
|
+
const data = JSON.parse(readFileSync12(path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
5713
5848
|
return data.parentExe || null;
|
|
5714
5849
|
} catch {
|
|
5715
5850
|
return null;
|
|
@@ -5717,8 +5852,8 @@ function getParentExe(sessionKey) {
|
|
|
5717
5852
|
}
|
|
5718
5853
|
function getDispatchedBy(sessionKey) {
|
|
5719
5854
|
try {
|
|
5720
|
-
const data = JSON.parse(
|
|
5721
|
-
|
|
5855
|
+
const data = JSON.parse(readFileSync12(
|
|
5856
|
+
path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
5722
5857
|
"utf8"
|
|
5723
5858
|
));
|
|
5724
5859
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -5788,8 +5923,8 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
5788
5923
|
}
|
|
5789
5924
|
function readDebounceState() {
|
|
5790
5925
|
try {
|
|
5791
|
-
if (!
|
|
5792
|
-
const raw = JSON.parse(
|
|
5926
|
+
if (!existsSync14(DEBOUNCE_FILE)) return {};
|
|
5927
|
+
const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
|
|
5793
5928
|
const state = {};
|
|
5794
5929
|
for (const [key, val] of Object.entries(raw)) {
|
|
5795
5930
|
if (typeof val === "number") {
|
|
@@ -5805,8 +5940,8 @@ function readDebounceState() {
|
|
|
5805
5940
|
}
|
|
5806
5941
|
function writeDebounceState(state) {
|
|
5807
5942
|
try {
|
|
5808
|
-
if (!
|
|
5809
|
-
|
|
5943
|
+
if (!existsSync14(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
5944
|
+
writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
|
|
5810
5945
|
} catch {
|
|
5811
5946
|
}
|
|
5812
5947
|
}
|
|
@@ -5904,8 +6039,8 @@ function sendIntercom(targetSession) {
|
|
|
5904
6039
|
try {
|
|
5905
6040
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
5906
6041
|
const agent = baseAgentName(rawAgent);
|
|
5907
|
-
const markerPath =
|
|
5908
|
-
if (
|
|
6042
|
+
const markerPath = path18.join(SESSION_CACHE, `current-task-${agent}.json`);
|
|
6043
|
+
if (existsSync14(markerPath)) {
|
|
5909
6044
|
logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
|
|
5910
6045
|
return "debounced";
|
|
5911
6046
|
}
|
|
@@ -5914,8 +6049,8 @@ function sendIntercom(targetSession) {
|
|
|
5914
6049
|
try {
|
|
5915
6050
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
5916
6051
|
const agent = baseAgentName(rawAgent);
|
|
5917
|
-
const taskDir =
|
|
5918
|
-
if (
|
|
6052
|
+
const taskDir = path18.join(process.cwd(), "exe", agent);
|
|
6053
|
+
if (existsSync14(taskDir)) {
|
|
5919
6054
|
const files = readdirSync3(taskDir).filter(
|
|
5920
6055
|
(f) => f.endsWith(".md") && f !== "DONE.txt"
|
|
5921
6056
|
);
|
|
@@ -5975,6 +6110,21 @@ function notifyParentExe(sessionKey) {
|
|
|
5975
6110
|
}
|
|
5976
6111
|
return true;
|
|
5977
6112
|
}
|
|
6113
|
+
function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
|
|
6114
|
+
const transport = getTransport();
|
|
6115
|
+
try {
|
|
6116
|
+
const sessions = transport.listSessions();
|
|
6117
|
+
if (!sessions.includes(coordinatorSession)) return false;
|
|
6118
|
+
execSync7(
|
|
6119
|
+
`tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
|
|
6120
|
+
{ timeout: 3e3 }
|
|
6121
|
+
);
|
|
6122
|
+
logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
|
|
6123
|
+
return true;
|
|
6124
|
+
} catch {
|
|
6125
|
+
return false;
|
|
6126
|
+
}
|
|
6127
|
+
}
|
|
5978
6128
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
5979
6129
|
if (isCoordinatorName(employeeName)) {
|
|
5980
6130
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
@@ -6048,26 +6198,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6048
6198
|
const transport = getTransport();
|
|
6049
6199
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
6050
6200
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
6051
|
-
const logDir =
|
|
6052
|
-
const logFile =
|
|
6053
|
-
if (!
|
|
6201
|
+
const logDir = path18.join(os11.homedir(), ".exe-os", "session-logs");
|
|
6202
|
+
const logFile = path18.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
6203
|
+
if (!existsSync14(logDir)) {
|
|
6054
6204
|
mkdirSync6(logDir, { recursive: true });
|
|
6055
6205
|
}
|
|
6056
6206
|
transport.kill(sessionName);
|
|
6057
6207
|
let cleanupSuffix = "";
|
|
6058
6208
|
try {
|
|
6059
6209
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
6060
|
-
const cleanupScript =
|
|
6061
|
-
if (
|
|
6210
|
+
const cleanupScript = path18.join(path18.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
6211
|
+
if (existsSync14(cleanupScript)) {
|
|
6062
6212
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
6063
6213
|
}
|
|
6064
6214
|
} catch {
|
|
6065
6215
|
}
|
|
6066
6216
|
try {
|
|
6067
|
-
const claudeJsonPath =
|
|
6217
|
+
const claudeJsonPath = path18.join(os11.homedir(), ".claude.json");
|
|
6068
6218
|
let claudeJson = {};
|
|
6069
6219
|
try {
|
|
6070
|
-
claudeJson = JSON.parse(
|
|
6220
|
+
claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
|
|
6071
6221
|
} catch {
|
|
6072
6222
|
}
|
|
6073
6223
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -6075,17 +6225,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6075
6225
|
const trustDir = opts?.cwd ?? projectDir;
|
|
6076
6226
|
if (!projects[trustDir]) projects[trustDir] = {};
|
|
6077
6227
|
projects[trustDir].hasTrustDialogAccepted = true;
|
|
6078
|
-
|
|
6228
|
+
writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
6079
6229
|
} catch {
|
|
6080
6230
|
}
|
|
6081
6231
|
try {
|
|
6082
|
-
const settingsDir =
|
|
6232
|
+
const settingsDir = path18.join(os11.homedir(), ".claude", "projects");
|
|
6083
6233
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
6084
|
-
const projSettingsDir =
|
|
6085
|
-
const settingsPath =
|
|
6234
|
+
const projSettingsDir = path18.join(settingsDir, normalizedKey);
|
|
6235
|
+
const settingsPath = path18.join(projSettingsDir, "settings.json");
|
|
6086
6236
|
let settings = {};
|
|
6087
6237
|
try {
|
|
6088
|
-
settings = JSON.parse(
|
|
6238
|
+
settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
|
|
6089
6239
|
} catch {
|
|
6090
6240
|
}
|
|
6091
6241
|
const perms = settings.permissions ?? {};
|
|
@@ -6114,7 +6264,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6114
6264
|
perms.allow = allow;
|
|
6115
6265
|
settings.permissions = perms;
|
|
6116
6266
|
mkdirSync6(projSettingsDir, { recursive: true });
|
|
6117
|
-
|
|
6267
|
+
writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
6118
6268
|
}
|
|
6119
6269
|
} catch {
|
|
6120
6270
|
}
|
|
@@ -6129,8 +6279,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6129
6279
|
let behaviorsFlag = "";
|
|
6130
6280
|
let legacyFallbackWarned = false;
|
|
6131
6281
|
if (!useExeAgent && !useBinSymlink) {
|
|
6132
|
-
const identityPath =
|
|
6133
|
-
|
|
6282
|
+
const identityPath = path18.join(
|
|
6283
|
+
os11.homedir(),
|
|
6134
6284
|
".exe-os",
|
|
6135
6285
|
"identity",
|
|
6136
6286
|
`${employeeName}.md`
|
|
@@ -6139,13 +6289,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6139
6289
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
6140
6290
|
if (hasAgentFlag) {
|
|
6141
6291
|
identityFlag = ` --agent ${employeeName}`;
|
|
6142
|
-
} else if (
|
|
6292
|
+
} else if (existsSync14(identityPath)) {
|
|
6143
6293
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
6144
6294
|
legacyFallbackWarned = true;
|
|
6145
6295
|
}
|
|
6146
6296
|
const behaviorsFile = exportBehaviorsSync(
|
|
6147
6297
|
employeeName,
|
|
6148
|
-
|
|
6298
|
+
path18.basename(spawnCwd),
|
|
6149
6299
|
sessionName
|
|
6150
6300
|
);
|
|
6151
6301
|
if (behaviorsFile) {
|
|
@@ -6160,16 +6310,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6160
6310
|
}
|
|
6161
6311
|
let sessionContextFlag = "";
|
|
6162
6312
|
try {
|
|
6163
|
-
const ctxDir =
|
|
6313
|
+
const ctxDir = path18.join(os11.homedir(), ".exe-os", "session-cache");
|
|
6164
6314
|
mkdirSync6(ctxDir, { recursive: true });
|
|
6165
|
-
const ctxFile =
|
|
6315
|
+
const ctxFile = path18.join(ctxDir, `session-context-${sessionName}.md`);
|
|
6166
6316
|
const ctxContent = [
|
|
6167
6317
|
`## Session Context`,
|
|
6168
6318
|
`You are running in tmux session: ${sessionName}.`,
|
|
6169
6319
|
`Your parent coordinator session is ${exeSession}.`,
|
|
6170
6320
|
`Your employees (if any) use the -${exeSession} suffix.`
|
|
6171
6321
|
].join("\n");
|
|
6172
|
-
|
|
6322
|
+
writeFileSync8(ctxFile, ctxContent);
|
|
6173
6323
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
6174
6324
|
} catch {
|
|
6175
6325
|
}
|
|
@@ -6246,8 +6396,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6246
6396
|
transport.pipeLog(sessionName, logFile);
|
|
6247
6397
|
try {
|
|
6248
6398
|
const mySession = getMySession();
|
|
6249
|
-
const dispatchInfo =
|
|
6250
|
-
|
|
6399
|
+
const dispatchInfo = path18.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
6400
|
+
writeFileSync8(dispatchInfo, JSON.stringify({
|
|
6251
6401
|
dispatchedBy: mySession,
|
|
6252
6402
|
rootExe: exeSession,
|
|
6253
6403
|
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
|
|
@@ -6321,15 +6471,15 @@ var init_tmux_routing = __esm({
|
|
|
6321
6471
|
init_intercom_queue();
|
|
6322
6472
|
init_plan_limits();
|
|
6323
6473
|
init_employees();
|
|
6324
|
-
SPAWN_LOCK_DIR =
|
|
6325
|
-
SESSION_CACHE =
|
|
6474
|
+
SPAWN_LOCK_DIR = path18.join(os11.homedir(), ".exe-os", "spawn-locks");
|
|
6475
|
+
SESSION_CACHE = path18.join(os11.homedir(), ".exe-os", "session-cache");
|
|
6326
6476
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
6327
6477
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
6328
6478
|
VERIFY_PANE_LINES = 200;
|
|
6329
6479
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
6330
6480
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
6331
|
-
INTERCOM_LOG2 =
|
|
6332
|
-
DEBOUNCE_FILE =
|
|
6481
|
+
INTERCOM_LOG2 = path18.join(os11.homedir(), ".exe-os", "intercom.log");
|
|
6482
|
+
DEBOUNCE_FILE = path18.join(SESSION_CACHE, "intercom-debounce.json");
|
|
6333
6483
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
6334
6484
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
|
|
6335
6485
|
}
|
|
@@ -6346,14 +6496,14 @@ var init_memory = __esm({
|
|
|
6346
6496
|
|
|
6347
6497
|
// src/lib/keychain.ts
|
|
6348
6498
|
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
6349
|
-
import { existsSync as
|
|
6350
|
-
import
|
|
6351
|
-
import
|
|
6499
|
+
import { existsSync as existsSync15 } from "fs";
|
|
6500
|
+
import path19 from "path";
|
|
6501
|
+
import os12 from "os";
|
|
6352
6502
|
function getKeyDir() {
|
|
6353
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
6503
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path19.join(os12.homedir(), ".exe-os");
|
|
6354
6504
|
}
|
|
6355
6505
|
function getKeyPath() {
|
|
6356
|
-
return
|
|
6506
|
+
return path19.join(getKeyDir(), "master.key");
|
|
6357
6507
|
}
|
|
6358
6508
|
async function tryKeytar() {
|
|
6359
6509
|
try {
|
|
@@ -6374,9 +6524,9 @@ async function getMasterKey() {
|
|
|
6374
6524
|
}
|
|
6375
6525
|
}
|
|
6376
6526
|
const keyPath = getKeyPath();
|
|
6377
|
-
if (!
|
|
6527
|
+
if (!existsSync15(keyPath)) {
|
|
6378
6528
|
process.stderr.write(
|
|
6379
|
-
`[keychain] Key not found at ${keyPath} (HOME=${
|
|
6529
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os12.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
6380
6530
|
`
|
|
6381
6531
|
);
|
|
6382
6532
|
return null;
|
|
@@ -6406,6 +6556,7 @@ var shard_manager_exports = {};
|
|
|
6406
6556
|
__export(shard_manager_exports, {
|
|
6407
6557
|
disposeShards: () => disposeShards,
|
|
6408
6558
|
ensureShardSchema: () => ensureShardSchema,
|
|
6559
|
+
getOpenShardCount: () => getOpenShardCount,
|
|
6409
6560
|
getReadyShardClient: () => getReadyShardClient,
|
|
6410
6561
|
getShardClient: () => getShardClient,
|
|
6411
6562
|
getShardsDir: () => getShardsDir,
|
|
@@ -6414,15 +6565,18 @@ __export(shard_manager_exports, {
|
|
|
6414
6565
|
listShards: () => listShards,
|
|
6415
6566
|
shardExists: () => shardExists
|
|
6416
6567
|
});
|
|
6417
|
-
import
|
|
6418
|
-
import { existsSync as
|
|
6568
|
+
import path20 from "path";
|
|
6569
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
|
|
6419
6570
|
import { createClient as createClient2 } from "@libsql/client";
|
|
6420
6571
|
function initShardManager(encryptionKey) {
|
|
6421
6572
|
_encryptionKey = encryptionKey;
|
|
6422
|
-
if (!
|
|
6573
|
+
if (!existsSync16(SHARDS_DIR)) {
|
|
6423
6574
|
mkdirSync7(SHARDS_DIR, { recursive: true });
|
|
6424
6575
|
}
|
|
6425
6576
|
_shardingEnabled = true;
|
|
6577
|
+
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
6578
|
+
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
6579
|
+
_evictionTimer.unref();
|
|
6426
6580
|
}
|
|
6427
6581
|
function isShardingEnabled() {
|
|
6428
6582
|
return _shardingEnabled;
|
|
@@ -6439,21 +6593,28 @@ function getShardClient(projectName) {
|
|
|
6439
6593
|
throw new Error(`Invalid project name for shard: "${projectName}"`);
|
|
6440
6594
|
}
|
|
6441
6595
|
const cached = _shards.get(safeName);
|
|
6442
|
-
if (cached)
|
|
6443
|
-
|
|
6596
|
+
if (cached) {
|
|
6597
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
6598
|
+
return cached;
|
|
6599
|
+
}
|
|
6600
|
+
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
6601
|
+
evictLRU();
|
|
6602
|
+
}
|
|
6603
|
+
const dbPath = path20.join(SHARDS_DIR, `${safeName}.db`);
|
|
6444
6604
|
const client = createClient2({
|
|
6445
6605
|
url: `file:${dbPath}`,
|
|
6446
6606
|
encryptionKey: _encryptionKey
|
|
6447
6607
|
});
|
|
6448
6608
|
_shards.set(safeName, client);
|
|
6609
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
6449
6610
|
return client;
|
|
6450
6611
|
}
|
|
6451
6612
|
function shardExists(projectName) {
|
|
6452
6613
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
6453
|
-
return
|
|
6614
|
+
return existsSync16(path20.join(SHARDS_DIR, `${safeName}.db`));
|
|
6454
6615
|
}
|
|
6455
6616
|
function listShards() {
|
|
6456
|
-
if (!
|
|
6617
|
+
if (!existsSync16(SHARDS_DIR)) return [];
|
|
6457
6618
|
return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
6458
6619
|
}
|
|
6459
6620
|
async function ensureShardSchema(client) {
|
|
@@ -6505,6 +6666,8 @@ async function ensureShardSchema(client) {
|
|
|
6505
6666
|
for (const col of [
|
|
6506
6667
|
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
6507
6668
|
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
6669
|
+
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
6670
|
+
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
6508
6671
|
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
6509
6672
|
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
6510
6673
|
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
@@ -6642,21 +6805,69 @@ async function getReadyShardClient(projectName) {
|
|
|
6642
6805
|
await ensureShardSchema(client);
|
|
6643
6806
|
return client;
|
|
6644
6807
|
}
|
|
6808
|
+
function evictLRU() {
|
|
6809
|
+
let oldest = null;
|
|
6810
|
+
let oldestTime = Infinity;
|
|
6811
|
+
for (const [name, time] of _shardLastAccess) {
|
|
6812
|
+
if (time < oldestTime) {
|
|
6813
|
+
oldestTime = time;
|
|
6814
|
+
oldest = name;
|
|
6815
|
+
}
|
|
6816
|
+
}
|
|
6817
|
+
if (oldest) {
|
|
6818
|
+
const client = _shards.get(oldest);
|
|
6819
|
+
if (client) {
|
|
6820
|
+
client.close();
|
|
6821
|
+
}
|
|
6822
|
+
_shards.delete(oldest);
|
|
6823
|
+
_shardLastAccess.delete(oldest);
|
|
6824
|
+
}
|
|
6825
|
+
}
|
|
6826
|
+
function evictIdleShards() {
|
|
6827
|
+
const now = Date.now();
|
|
6828
|
+
const toEvict = [];
|
|
6829
|
+
for (const [name, lastAccess] of _shardLastAccess) {
|
|
6830
|
+
if (now - lastAccess > SHARD_IDLE_MS) {
|
|
6831
|
+
toEvict.push(name);
|
|
6832
|
+
}
|
|
6833
|
+
}
|
|
6834
|
+
for (const name of toEvict) {
|
|
6835
|
+
const client = _shards.get(name);
|
|
6836
|
+
if (client) {
|
|
6837
|
+
client.close();
|
|
6838
|
+
}
|
|
6839
|
+
_shards.delete(name);
|
|
6840
|
+
_shardLastAccess.delete(name);
|
|
6841
|
+
}
|
|
6842
|
+
}
|
|
6843
|
+
function getOpenShardCount() {
|
|
6844
|
+
return _shards.size;
|
|
6845
|
+
}
|
|
6645
6846
|
function disposeShards() {
|
|
6847
|
+
if (_evictionTimer) {
|
|
6848
|
+
clearInterval(_evictionTimer);
|
|
6849
|
+
_evictionTimer = null;
|
|
6850
|
+
}
|
|
6646
6851
|
for (const [, client] of _shards) {
|
|
6647
6852
|
client.close();
|
|
6648
6853
|
}
|
|
6649
6854
|
_shards.clear();
|
|
6855
|
+
_shardLastAccess.clear();
|
|
6650
6856
|
_shardingEnabled = false;
|
|
6651
6857
|
_encryptionKey = null;
|
|
6652
6858
|
}
|
|
6653
|
-
var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
|
|
6859
|
+
var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
|
|
6654
6860
|
var init_shard_manager = __esm({
|
|
6655
6861
|
"src/lib/shard-manager.ts"() {
|
|
6656
6862
|
"use strict";
|
|
6657
6863
|
init_config();
|
|
6658
|
-
SHARDS_DIR =
|
|
6864
|
+
SHARDS_DIR = path20.join(EXE_AI_DIR, "shards");
|
|
6865
|
+
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
6866
|
+
MAX_OPEN_SHARDS = 10;
|
|
6867
|
+
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
6659
6868
|
_shards = /* @__PURE__ */ new Map();
|
|
6869
|
+
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
6870
|
+
_evictionTimer = null;
|
|
6660
6871
|
_encryptionKey = null;
|
|
6661
6872
|
_shardingEnabled = false;
|
|
6662
6873
|
}
|
|
@@ -7452,8 +7663,8 @@ function findContainingChunk(filePath, snippet) {
|
|
|
7452
7663
|
try {
|
|
7453
7664
|
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
7454
7665
|
if (ext !== "ts" && ext !== "tsx" && ext !== "js" && ext !== "jsx") return "";
|
|
7455
|
-
const { readFileSync:
|
|
7456
|
-
const source =
|
|
7666
|
+
const { readFileSync: readFileSync15 } = __require("fs");
|
|
7667
|
+
const source = readFileSync15(filePath, "utf8");
|
|
7457
7668
|
const lines = source.split("\n");
|
|
7458
7669
|
const lowerSnippet = snippet.toLowerCase().slice(0, 80);
|
|
7459
7670
|
let matchLine = -1;
|
|
@@ -7519,9 +7730,9 @@ function extractBash(input, response) {
|
|
|
7519
7730
|
}
|
|
7520
7731
|
function extractGrep(input, response) {
|
|
7521
7732
|
const pattern = String(input.pattern ?? "");
|
|
7522
|
-
const
|
|
7733
|
+
const path23 = input.path ? String(input.path) : "";
|
|
7523
7734
|
const output = String(response.text ?? response.content ?? JSON.stringify(response).slice(0, MAX_OUTPUT));
|
|
7524
|
-
return `Searched for "${pattern}"${
|
|
7735
|
+
return `Searched for "${pattern}"${path23 ? ` in ${path23}` : ""}
|
|
7525
7736
|
${output.slice(0, MAX_OUTPUT)}`;
|
|
7526
7737
|
}
|
|
7527
7738
|
function extractGlob(input, response) {
|
|
@@ -7624,7 +7835,7 @@ __export(error_detector_exports, {
|
|
|
7624
7835
|
errorFingerprint: () => errorFingerprint,
|
|
7625
7836
|
isExeOsError: () => isExeOsError
|
|
7626
7837
|
});
|
|
7627
|
-
import
|
|
7838
|
+
import crypto7 from "crypto";
|
|
7628
7839
|
function isRealStderr(stderr) {
|
|
7629
7840
|
const lines = stderr.trim().split("\n");
|
|
7630
7841
|
const meaningful = lines.filter(
|
|
@@ -7695,7 +7906,7 @@ function classifyError(errorText) {
|
|
|
7695
7906
|
}
|
|
7696
7907
|
function errorFingerprint(toolName, errorText) {
|
|
7697
7908
|
const normalized = errorText.replace(/\d{4}-\d{2}-\d{2}T[\d:.]+Z/g, "TIMESTAMP").replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, "UUID").replace(/\/Users\/[^\s]+/g, "PATH").replace(/:\d+:\d+/g, ":LINE:COL").slice(0, 200);
|
|
7698
|
-
return
|
|
7909
|
+
return crypto7.createHash("sha256").update(`${toolName}:${normalized}`).digest("hex").slice(0, 16);
|
|
7699
7910
|
}
|
|
7700
7911
|
var ERROR_PATTERNS, FILE_CONTENT_TOOLS, STDERR_IGNORE_PATTERNS, USER_ERROR_PATTERNS, SYSTEM_BUG_PATTERNS;
|
|
7701
7912
|
var init_error_detector = __esm({
|
|
@@ -8059,10 +8270,10 @@ async function disposeEmbedder() {
|
|
|
8059
8270
|
async function embedDirect(text) {
|
|
8060
8271
|
const llamaCpp = await import("node-llama-cpp");
|
|
8061
8272
|
const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
8062
|
-
const { existsSync:
|
|
8063
|
-
const
|
|
8064
|
-
const modelPath =
|
|
8065
|
-
if (!
|
|
8273
|
+
const { existsSync: existsSync18 } = await import("fs");
|
|
8274
|
+
const path23 = await import("path");
|
|
8275
|
+
const modelPath = path23.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
|
|
8276
|
+
if (!existsSync18(modelPath)) {
|
|
8066
8277
|
throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
|
|
8067
8278
|
}
|
|
8068
8279
|
const llama = await llamaCpp.getLlama();
|
|
@@ -8099,8 +8310,8 @@ __export(wiki_client_exports, {
|
|
|
8099
8310
|
listDocuments: () => listDocuments,
|
|
8100
8311
|
listWorkspaces: () => listWorkspaces
|
|
8101
8312
|
});
|
|
8102
|
-
async function wikiFetch(config2,
|
|
8103
|
-
const url = `${config2.baseUrl}/api/v1${
|
|
8313
|
+
async function wikiFetch(config2, path23, method = "GET", body) {
|
|
8314
|
+
const url = `${config2.baseUrl}/api/v1${path23}`;
|
|
8104
8315
|
const headers = {
|
|
8105
8316
|
Authorization: `Bearer ${config2.apiKey}`,
|
|
8106
8317
|
"Content-Type": "application/json"
|
|
@@ -8133,7 +8344,7 @@ async function wikiFetch(config2, path22, method = "GET", body) {
|
|
|
8133
8344
|
}
|
|
8134
8345
|
}
|
|
8135
8346
|
if (!response.ok) {
|
|
8136
|
-
throw new Error(`Wiki API ${method} ${
|
|
8347
|
+
throw new Error(`Wiki API ${method} ${path23}: ${response.status} ${response.statusText}`);
|
|
8137
8348
|
}
|
|
8138
8349
|
return response.json();
|
|
8139
8350
|
} finally {
|
|
@@ -8426,13 +8637,13 @@ __export(graph_rag_exports, {
|
|
|
8426
8637
|
resolveAlias: () => resolveAlias,
|
|
8427
8638
|
storeExtraction: () => storeExtraction
|
|
8428
8639
|
});
|
|
8429
|
-
import
|
|
8640
|
+
import crypto8 from "crypto";
|
|
8430
8641
|
function normalizeEntityName(name) {
|
|
8431
8642
|
return name.replace(/\s*\([^)]*\)\s*/g, "").trim().toLowerCase();
|
|
8432
8643
|
}
|
|
8433
8644
|
function entityId(name, type) {
|
|
8434
8645
|
const normalized = normalizeEntityName(name);
|
|
8435
|
-
return
|
|
8646
|
+
return crypto8.createHash("sha256").update(`${normalized}::${type.toLowerCase()}`).digest("hex").slice(0, 16);
|
|
8436
8647
|
}
|
|
8437
8648
|
async function resolveAlias(client, name) {
|
|
8438
8649
|
const normalized = normalizeEntityName(name);
|
|
@@ -8682,7 +8893,7 @@ async function storeExtraction(client, extraction, memoryId, timestamp) {
|
|
|
8682
8893
|
const targetAlias = await resolveAlias(client, r.target);
|
|
8683
8894
|
const sourceId = sourceAlias ?? entityId(r.source, r.sourceType);
|
|
8684
8895
|
const targetId = targetAlias ?? entityId(r.target, r.targetType);
|
|
8685
|
-
const relId =
|
|
8896
|
+
const relId = crypto8.randomUUID().slice(0, 16);
|
|
8686
8897
|
try {
|
|
8687
8898
|
await client.execute({
|
|
8688
8899
|
sql: `INSERT OR IGNORE INTO entities (id, name, type, first_seen, last_seen)
|
|
@@ -8745,7 +8956,7 @@ async function storeExtraction(client, extraction, memoryId, timestamp) {
|
|
|
8745
8956
|
}
|
|
8746
8957
|
}
|
|
8747
8958
|
for (const h of extraction.hyperedges) {
|
|
8748
|
-
const hId =
|
|
8959
|
+
const hId = crypto8.randomUUID().slice(0, 16);
|
|
8749
8960
|
try {
|
|
8750
8961
|
await client.execute({
|
|
8751
8962
|
sql: `INSERT OR IGNORE INTO hyperedges (id, label, relation, confidence, timestamp)
|
|
@@ -8809,7 +9020,7 @@ async function extractBatch(client, batchSize = 50, model = "claude-haiku-4-5-20
|
|
|
8809
9020
|
totalEntities += stored.entitiesStored;
|
|
8810
9021
|
totalRelationships += stored.relationshipsStored;
|
|
8811
9022
|
}
|
|
8812
|
-
const contentHash =
|
|
9023
|
+
const contentHash = crypto8.createHash("sha256").update(rawContent).digest("hex").slice(0, 32);
|
|
8813
9024
|
await client.execute({
|
|
8814
9025
|
sql: "UPDATE memories SET graph_extracted = 1, content_hash = ?, graph_extracted_hash = ? WHERE id = ?",
|
|
8815
9026
|
args: [contentHash, contentHash, memoryId]
|
|
@@ -9154,13 +9365,13 @@ __export(whatsapp_accounts_exports, {
|
|
|
9154
9365
|
getDefaultAccount: () => getDefaultAccount,
|
|
9155
9366
|
loadAccounts: () => loadAccounts
|
|
9156
9367
|
});
|
|
9157
|
-
import { readFileSync as
|
|
9368
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
9158
9369
|
import { join as join2 } from "path";
|
|
9159
9370
|
import { homedir as homedir2 } from "os";
|
|
9160
9371
|
function loadAccounts() {
|
|
9161
9372
|
if (cachedAccounts !== null) return cachedAccounts;
|
|
9162
9373
|
try {
|
|
9163
|
-
const raw =
|
|
9374
|
+
const raw = readFileSync13(CONFIG_PATH2, "utf8");
|
|
9164
9375
|
const parsed = JSON.parse(raw);
|
|
9165
9376
|
if (!Array.isArray(parsed)) {
|
|
9166
9377
|
console.warn("[whatsapp] Config is not an array, ignoring");
|
|
@@ -9216,10 +9427,10 @@ __export(messaging_exports, {
|
|
|
9216
9427
|
sendMessage: () => sendMessage,
|
|
9217
9428
|
setWsClientSend: () => setWsClientSend
|
|
9218
9429
|
});
|
|
9219
|
-
import
|
|
9430
|
+
import crypto10 from "crypto";
|
|
9220
9431
|
function generateUlid() {
|
|
9221
9432
|
const timestamp = Date.now().toString(36).padStart(10, "0");
|
|
9222
|
-
const random =
|
|
9433
|
+
const random = crypto10.randomBytes(10).toString("hex").slice(0, 16);
|
|
9223
9434
|
return (timestamp + random).toUpperCase();
|
|
9224
9435
|
}
|
|
9225
9436
|
function rowToMessage(row) {
|
|
@@ -9230,6 +9441,7 @@ function rowToMessage(row) {
|
|
|
9230
9441
|
targetAgent: row.target_agent,
|
|
9231
9442
|
targetProject: row.target_project ?? null,
|
|
9232
9443
|
targetDevice: row.target_device,
|
|
9444
|
+
sessionScope: row.session_scope ?? null,
|
|
9233
9445
|
content: row.content,
|
|
9234
9446
|
priority: row.priority ?? "normal",
|
|
9235
9447
|
status: row.status ?? "pending",
|
|
@@ -9247,15 +9459,17 @@ async function sendMessage(input) {
|
|
|
9247
9459
|
const id = generateUlid();
|
|
9248
9460
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9249
9461
|
const targetDevice = input.targetDevice ?? "local";
|
|
9462
|
+
const sessionScope = input.sessionScope === void 0 ? resolveExeSession() : input.sessionScope;
|
|
9250
9463
|
await client.execute({
|
|
9251
|
-
sql: `INSERT INTO messages (id, from_agent, from_device, target_agent, target_project, target_device, content, priority, status, created_at)
|
|
9252
|
-
VALUES (?, ?, 'local', ?, ?, ?, ?, ?, 'pending', ?)`,
|
|
9464
|
+
sql: `INSERT INTO messages (id, from_agent, from_device, target_agent, target_project, target_device, session_scope, content, priority, status, created_at)
|
|
9465
|
+
VALUES (?, ?, 'local', ?, ?, ?, ?, ?, ?, 'pending', ?)`,
|
|
9253
9466
|
args: [
|
|
9254
9467
|
id,
|
|
9255
9468
|
input.fromAgent,
|
|
9256
9469
|
input.targetAgent,
|
|
9257
9470
|
input.targetProject ?? null,
|
|
9258
9471
|
targetDevice,
|
|
9472
|
+
sessionScope,
|
|
9259
9473
|
input.content,
|
|
9260
9474
|
input.priority ?? "normal",
|
|
9261
9475
|
now
|
|
@@ -9269,9 +9483,10 @@ async function sendMessage(input) {
|
|
|
9269
9483
|
}
|
|
9270
9484
|
} catch {
|
|
9271
9485
|
}
|
|
9486
|
+
const sentScope = strictSessionScopeFilter(sessionScope);
|
|
9272
9487
|
const result = await client.execute({
|
|
9273
|
-
sql:
|
|
9274
|
-
args: [id]
|
|
9488
|
+
sql: `SELECT * FROM messages WHERE id = ?${sentScope.sql}`,
|
|
9489
|
+
args: [id, ...sentScope.args]
|
|
9275
9490
|
});
|
|
9276
9491
|
return rowToMessage(result.rows[0]);
|
|
9277
9492
|
}
|
|
@@ -9295,6 +9510,7 @@ async function deliverCrossMachineMessage(messageId, targetDevice) {
|
|
|
9295
9510
|
fromAgent: msg.fromAgent,
|
|
9296
9511
|
targetAgent: msg.targetAgent,
|
|
9297
9512
|
targetProject: msg.targetProject,
|
|
9513
|
+
sessionScope: msg.sessionScope,
|
|
9298
9514
|
content: msg.content,
|
|
9299
9515
|
priority: msg.priority,
|
|
9300
9516
|
createdAt: msg.createdAt
|
|
@@ -9338,7 +9554,7 @@ async function deliverLocalMessage(messageId) {
|
|
|
9338
9554
|
} catch {
|
|
9339
9555
|
const newRetryCount = msg.retryCount + 1;
|
|
9340
9556
|
if (newRetryCount >= MAX_RETRIES3) {
|
|
9341
|
-
await markFailed(messageId, "session unavailable after 10 retries");
|
|
9557
|
+
await markFailed(messageId, "session unavailable after 10 retries", msg.sessionScope);
|
|
9342
9558
|
} else {
|
|
9343
9559
|
await client.execute({
|
|
9344
9560
|
sql: "UPDATE messages SET retry_count = ? WHERE id = ?",
|
|
@@ -9348,85 +9564,101 @@ async function deliverLocalMessage(messageId) {
|
|
|
9348
9564
|
return false;
|
|
9349
9565
|
}
|
|
9350
9566
|
}
|
|
9351
|
-
async function getPendingMessages(targetAgent) {
|
|
9567
|
+
async function getPendingMessages(targetAgent, sessionScope) {
|
|
9352
9568
|
const client = getClient();
|
|
9569
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
9353
9570
|
const result = await client.execute({
|
|
9354
9571
|
sql: `SELECT * FROM messages
|
|
9355
|
-
WHERE target_agent = ? AND status IN ('pending', 'delivered')
|
|
9572
|
+
WHERE target_agent = ? AND status IN ('pending', 'delivered')${scope.sql}
|
|
9356
9573
|
ORDER BY id`,
|
|
9357
|
-
args: [targetAgent]
|
|
9574
|
+
args: [targetAgent, ...scope.args]
|
|
9358
9575
|
});
|
|
9359
9576
|
return result.rows.map((row) => rowToMessage(row));
|
|
9360
9577
|
}
|
|
9361
|
-
async function markRead(messageId) {
|
|
9578
|
+
async function markRead(messageId, sessionScope) {
|
|
9362
9579
|
const client = getClient();
|
|
9580
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
9363
9581
|
await client.execute({
|
|
9364
|
-
sql:
|
|
9365
|
-
|
|
9582
|
+
sql: `UPDATE messages SET status = 'read'
|
|
9583
|
+
WHERE id = ? AND status IN ('pending', 'delivered')${scope.sql}`,
|
|
9584
|
+
args: [messageId, ...scope.args]
|
|
9366
9585
|
});
|
|
9367
9586
|
}
|
|
9368
|
-
async function markAcknowledged(messageId) {
|
|
9587
|
+
async function markAcknowledged(messageId, sessionScope) {
|
|
9369
9588
|
const client = getClient();
|
|
9589
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
9370
9590
|
await client.execute({
|
|
9371
|
-
sql:
|
|
9372
|
-
|
|
9591
|
+
sql: `UPDATE messages SET status = 'acknowledged', processed_at = ?
|
|
9592
|
+
WHERE id = ? AND status = 'read'${scope.sql}`,
|
|
9593
|
+
args: [(/* @__PURE__ */ new Date()).toISOString(), messageId, ...scope.args]
|
|
9373
9594
|
});
|
|
9374
9595
|
}
|
|
9375
|
-
async function markProcessed(messageId) {
|
|
9596
|
+
async function markProcessed(messageId, sessionScope) {
|
|
9376
9597
|
const client = getClient();
|
|
9598
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
9377
9599
|
await client.execute({
|
|
9378
|
-
sql:
|
|
9379
|
-
|
|
9600
|
+
sql: `UPDATE messages SET status = 'processed', processed_at = ?
|
|
9601
|
+
WHERE id = ?${scope.sql}`,
|
|
9602
|
+
args: [(/* @__PURE__ */ new Date()).toISOString(), messageId, ...scope.args]
|
|
9380
9603
|
});
|
|
9381
9604
|
}
|
|
9382
|
-
async function getMessageStatus(messageId) {
|
|
9605
|
+
async function getMessageStatus(messageId, sessionScope) {
|
|
9383
9606
|
const client = getClient();
|
|
9607
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
9384
9608
|
const result = await client.execute({
|
|
9385
|
-
sql:
|
|
9386
|
-
args: [messageId]
|
|
9609
|
+
sql: `SELECT status FROM messages WHERE id = ?${scope.sql}`,
|
|
9610
|
+
args: [messageId, ...scope.args]
|
|
9387
9611
|
});
|
|
9388
9612
|
return result.rows[0]?.status ?? null;
|
|
9389
9613
|
}
|
|
9390
|
-
async function getUnacknowledgedMessages(targetAgent) {
|
|
9614
|
+
async function getUnacknowledgedMessages(targetAgent, sessionScope) {
|
|
9391
9615
|
const client = getClient();
|
|
9616
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
9392
9617
|
const result = await client.execute({
|
|
9393
9618
|
sql: `SELECT * FROM messages
|
|
9394
|
-
WHERE target_agent = ? AND status IN ('pending', 'delivered', 'read')
|
|
9619
|
+
WHERE target_agent = ? AND status IN ('pending', 'delivered', 'read')${scope.sql}
|
|
9395
9620
|
ORDER BY id`,
|
|
9396
|
-
args: [targetAgent]
|
|
9621
|
+
args: [targetAgent, ...scope.args]
|
|
9397
9622
|
});
|
|
9398
9623
|
return result.rows.map((row) => rowToMessage(row));
|
|
9399
9624
|
}
|
|
9400
|
-
async function getReadMessages(targetAgent) {
|
|
9625
|
+
async function getReadMessages(targetAgent, sessionScope) {
|
|
9401
9626
|
const client = getClient();
|
|
9627
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
9402
9628
|
const result = await client.execute({
|
|
9403
|
-
sql:
|
|
9404
|
-
|
|
9629
|
+
sql: `SELECT * FROM messages
|
|
9630
|
+
WHERE target_agent = ? AND status = 'read'${scope.sql}
|
|
9631
|
+
ORDER BY id`,
|
|
9632
|
+
args: [targetAgent, ...scope.args]
|
|
9405
9633
|
});
|
|
9406
9634
|
return result.rows.map((row) => rowToMessage(row));
|
|
9407
9635
|
}
|
|
9408
|
-
async function markFailed(messageId, reason) {
|
|
9636
|
+
async function markFailed(messageId, reason, sessionScope) {
|
|
9409
9637
|
const client = getClient();
|
|
9638
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
9410
9639
|
await client.execute({
|
|
9411
|
-
sql:
|
|
9412
|
-
|
|
9640
|
+
sql: `UPDATE messages SET status = 'failed', failed_at = ?, failure_reason = ?
|
|
9641
|
+
WHERE id = ?${scope.sql}`,
|
|
9642
|
+
args: [(/* @__PURE__ */ new Date()).toISOString(), reason, messageId, ...scope.args]
|
|
9413
9643
|
});
|
|
9414
9644
|
}
|
|
9415
|
-
async function getFailedMessages() {
|
|
9645
|
+
async function getFailedMessages(sessionScope) {
|
|
9416
9646
|
const client = getClient();
|
|
9647
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
9417
9648
|
const result = await client.execute({
|
|
9418
|
-
sql:
|
|
9419
|
-
args: []
|
|
9649
|
+
sql: `SELECT * FROM messages WHERE status = 'failed'${scope.sql} ORDER BY created_at DESC`,
|
|
9650
|
+
args: [...scope.args]
|
|
9420
9651
|
});
|
|
9421
9652
|
return result.rows.map((row) => rowToMessage(row));
|
|
9422
9653
|
}
|
|
9423
|
-
async function retryPendingMessages() {
|
|
9654
|
+
async function retryPendingMessages(sessionScope) {
|
|
9424
9655
|
const client = getClient();
|
|
9656
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
9425
9657
|
const result = await client.execute({
|
|
9426
9658
|
sql: `SELECT * FROM messages
|
|
9427
|
-
WHERE status = 'pending' AND retry_count <
|
|
9659
|
+
WHERE status = 'pending' AND retry_count < ?${scope.sql}
|
|
9428
9660
|
ORDER BY id`,
|
|
9429
|
-
args: [MAX_RETRIES3]
|
|
9661
|
+
args: [MAX_RETRIES3, ...scope.args]
|
|
9430
9662
|
});
|
|
9431
9663
|
let delivered = 0;
|
|
9432
9664
|
for (const row of result.rows) {
|
|
@@ -9445,6 +9677,7 @@ var init_messaging = __esm({
|
|
|
9445
9677
|
"use strict";
|
|
9446
9678
|
init_database();
|
|
9447
9679
|
init_tmux_routing();
|
|
9680
|
+
init_task_scope();
|
|
9448
9681
|
MAX_RETRIES3 = 10;
|
|
9449
9682
|
_wsClientSend = null;
|
|
9450
9683
|
}
|
|
@@ -11925,11 +12158,11 @@ init_crm_bridge();
|
|
|
11925
12158
|
|
|
11926
12159
|
// src/lib/pipeline-router.ts
|
|
11927
12160
|
init_database();
|
|
11928
|
-
import
|
|
12161
|
+
import crypto9 from "crypto";
|
|
11929
12162
|
async function sinkConversationStore(msg, agentResponse, agentName) {
|
|
11930
12163
|
try {
|
|
11931
12164
|
const client = getClient();
|
|
11932
|
-
const id =
|
|
12165
|
+
const id = crypto9.randomUUID();
|
|
11933
12166
|
const mediaJson = msg.media ? JSON.stringify(msg.media) : null;
|
|
11934
12167
|
await client.execute({
|
|
11935
12168
|
sql: `INSERT INTO conversations
|
|
@@ -11979,7 +12212,7 @@ async function sinkMemory(msg, agentResponse, agentName) {
|
|
|
11979
12212
|
].filter(Boolean).join("\n");
|
|
11980
12213
|
const vector = await embed2(rawText);
|
|
11981
12214
|
await writeMemory2({
|
|
11982
|
-
id:
|
|
12215
|
+
id: crypto9.randomUUID(),
|
|
11983
12216
|
agent_id: agentName ?? "gateway",
|
|
11984
12217
|
agent_role: "gateway",
|
|
11985
12218
|
session_id: `gateway-${msg.platform}`,
|
|
@@ -14660,12 +14893,12 @@ var SlackAdapter = class {
|
|
|
14660
14893
|
// src/gateway/adapters/imessage.ts
|
|
14661
14894
|
import { execFile } from "child_process";
|
|
14662
14895
|
import { promisify } from "util";
|
|
14663
|
-
import
|
|
14664
|
-
import
|
|
14896
|
+
import os13 from "os";
|
|
14897
|
+
import path21 from "path";
|
|
14665
14898
|
var execFileAsync = promisify(execFile);
|
|
14666
14899
|
var POLL_INTERVAL_MS = 5e3;
|
|
14667
|
-
var MESSAGES_DB_PATH =
|
|
14668
|
-
process.env.HOME ??
|
|
14900
|
+
var MESSAGES_DB_PATH = path21.join(
|
|
14901
|
+
process.env.HOME ?? os13.homedir(),
|
|
14669
14902
|
"Library/Messages/chat.db"
|
|
14670
14903
|
);
|
|
14671
14904
|
var IMessageAdapter = class {
|
|
@@ -15508,11 +15741,11 @@ async function ensureCRMContact(info) {
|
|
|
15508
15741
|
}
|
|
15509
15742
|
|
|
15510
15743
|
// src/automation/trigger-engine.ts
|
|
15511
|
-
import { readFileSync as
|
|
15744
|
+
import { readFileSync as readFileSync14, writeFileSync as writeFileSync9, existsSync as existsSync17, mkdirSync as mkdirSync9 } from "fs";
|
|
15512
15745
|
import { randomUUID as randomUUID15 } from "crypto";
|
|
15513
|
-
import
|
|
15514
|
-
import
|
|
15515
|
-
var TRIGGERS_PATH =
|
|
15746
|
+
import path22 from "path";
|
|
15747
|
+
import os14 from "os";
|
|
15748
|
+
var TRIGGERS_PATH = path22.join(os14.homedir(), ".exe-os", "triggers.json");
|
|
15516
15749
|
var GRAPH_API_VERSION = "v21.0";
|
|
15517
15750
|
function substituteTemplate(template, record) {
|
|
15518
15751
|
return template.replace(
|
|
@@ -15566,9 +15799,9 @@ function evaluateConditions(conditions, record) {
|
|
|
15566
15799
|
return conditions.every((c) => evaluateCondition(c, record));
|
|
15567
15800
|
}
|
|
15568
15801
|
function loadTriggers(project) {
|
|
15569
|
-
if (!
|
|
15802
|
+
if (!existsSync17(TRIGGERS_PATH)) return [];
|
|
15570
15803
|
try {
|
|
15571
|
-
const raw =
|
|
15804
|
+
const raw = readFileSync14(TRIGGERS_PATH, "utf-8");
|
|
15572
15805
|
const all = JSON.parse(raw);
|
|
15573
15806
|
if (!Array.isArray(all)) return [];
|
|
15574
15807
|
if (project) {
|