@askexenow/exe-os 0.9.8 → 0.9.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/backfill-conversations.js +222 -49
- package/dist/bin/backfill-responses.js +221 -48
- package/dist/bin/backfill-vectors.js +225 -52
- package/dist/bin/cleanup-stale-review-tasks.js +150 -28
- package/dist/bin/cli.js +1411 -953
- package/dist/bin/exe-agent-config.js +36 -8
- package/dist/bin/exe-agent.js +14 -4
- package/dist/bin/exe-assign.js +221 -48
- package/dist/bin/exe-boot.js +913 -543
- package/dist/bin/exe-call.js +41 -13
- package/dist/bin/exe-cloud.js +163 -58
- package/dist/bin/exe-dispatch.js +418 -262
- package/dist/bin/exe-doctor.js +145 -27
- package/dist/bin/exe-export-behaviors.js +141 -23
- package/dist/bin/exe-forget.js +137 -19
- package/dist/bin/exe-gateway.js +793 -485
- package/dist/bin/exe-heartbeat.js +227 -108
- package/dist/bin/exe-kill.js +138 -20
- package/dist/bin/exe-launch-agent.js +172 -39
- package/dist/bin/exe-link.js +291 -100
- package/dist/bin/exe-new-employee.js +214 -106
- package/dist/bin/exe-pending-messages.js +395 -33
- package/dist/bin/exe-pending-notifications.js +684 -99
- package/dist/bin/exe-pending-reviews.js +420 -74
- package/dist/bin/exe-rename.js +147 -49
- package/dist/bin/exe-review.js +138 -20
- package/dist/bin/exe-search.js +240 -69
- package/dist/bin/exe-session-cleanup.js +566 -357
- package/dist/bin/exe-settings.js +61 -17
- package/dist/bin/exe-start-codex.js +158 -39
- package/dist/bin/exe-start-opencode.js +157 -38
- package/dist/bin/exe-status.js +151 -29
- package/dist/bin/exe-team.js +138 -20
- package/dist/bin/git-sweep.js +530 -319
- package/dist/bin/graph-backfill.js +137 -19
- package/dist/bin/graph-export.js +140 -22
- package/dist/bin/install.js +90 -61
- package/dist/bin/scan-tasks.js +547 -336
- package/dist/bin/setup.js +564 -293
- package/dist/bin/shard-migrate.js +139 -21
- package/dist/bin/update.js +138 -49
- package/dist/bin/wiki-sync.js +137 -19
- package/dist/gateway/index.js +649 -417
- package/dist/hooks/bug-report-worker.js +486 -316
- package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
- package/dist/hooks/commit-complete.js +528 -317
- package/dist/hooks/error-recall.js +245 -74
- package/dist/hooks/exe-heartbeat-hook.js +16 -6
- package/dist/hooks/ingest-worker.js +3442 -3157
- package/dist/hooks/ingest.js +832 -97
- package/dist/hooks/instructions-loaded.js +227 -54
- package/dist/hooks/notification.js +216 -43
- package/dist/hooks/post-compact.js +239 -62
- package/dist/hooks/pre-compact.js +534 -323
- package/dist/hooks/pre-tool-use.js +268 -90
- package/dist/hooks/prompt-ingest-worker.js +352 -102
- package/dist/hooks/prompt-submit.js +614 -382
- package/dist/hooks/response-ingest-worker.js +372 -122
- package/dist/hooks/session-end.js +569 -347
- package/dist/hooks/session-start.js +313 -127
- package/dist/hooks/stop.js +293 -98
- package/dist/hooks/subagent-stop.js +239 -62
- package/dist/hooks/summary-worker.js +568 -236
- package/dist/index.js +664 -431
- package/dist/lib/agent-config.js +28 -6
- package/dist/lib/cloud-sync.js +284 -105
- package/dist/lib/config.js +30 -10
- package/dist/lib/consolidation.js +16 -6
- package/dist/lib/database.js +123 -25
- package/dist/lib/db-daemon-client.js +73 -19
- package/dist/lib/db.js +123 -25
- package/dist/lib/device-registry.js +133 -35
- package/dist/lib/embedder.js +107 -32
- package/dist/lib/employee-templates.js +14 -4
- package/dist/lib/employees.js +41 -13
- package/dist/lib/exe-daemon-client.js +88 -22
- package/dist/lib/exe-daemon.js +1049 -680
- package/dist/lib/hybrid-search.js +240 -69
- package/dist/lib/identity.js +18 -8
- package/dist/lib/license.js +133 -48
- package/dist/lib/messaging.js +116 -56
- package/dist/lib/reminders.js +14 -4
- package/dist/lib/schedules.js +137 -19
- package/dist/lib/skill-learning.js +33 -6
- package/dist/lib/store.js +137 -19
- package/dist/lib/task-router.js +14 -4
- package/dist/lib/tasks.js +422 -357
- package/dist/lib/tmux-routing.js +314 -248
- package/dist/lib/token-spend.js +26 -8
- package/dist/mcp/server.js +1408 -672
- package/dist/mcp/tools/complete-reminder.js +14 -4
- package/dist/mcp/tools/create-reminder.js +14 -4
- package/dist/mcp/tools/create-task.js +448 -371
- package/dist/mcp/tools/deactivate-behavior.js +16 -6
- package/dist/mcp/tools/list-reminders.js +14 -4
- package/dist/mcp/tools/list-tasks.js +123 -107
- package/dist/mcp/tools/send-message.js +75 -29
- package/dist/mcp/tools/update-task.js +1983 -315
- package/dist/runtime/index.js +567 -355
- package/dist/tui/App.js +887 -531
- package/package.json +4 -4
package/dist/bin/exe-boot.js
CHANGED
|
@@ -26,6 +26,44 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
26
26
|
};
|
|
27
27
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
28
|
|
|
29
|
+
// src/lib/secure-files.ts
|
|
30
|
+
import { chmodSync, existsSync, mkdirSync } from "fs";
|
|
31
|
+
import { chmod, mkdir } from "fs/promises";
|
|
32
|
+
async function ensurePrivateDir(dirPath) {
|
|
33
|
+
await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
34
|
+
try {
|
|
35
|
+
await chmod(dirPath, PRIVATE_DIR_MODE);
|
|
36
|
+
} catch {
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function ensurePrivateDirSync(dirPath) {
|
|
40
|
+
mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
41
|
+
try {
|
|
42
|
+
chmodSync(dirPath, PRIVATE_DIR_MODE);
|
|
43
|
+
} catch {
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async function enforcePrivateFile(filePath) {
|
|
47
|
+
try {
|
|
48
|
+
await chmod(filePath, PRIVATE_FILE_MODE);
|
|
49
|
+
} catch {
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function enforcePrivateFileSync(filePath) {
|
|
53
|
+
try {
|
|
54
|
+
if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
|
|
55
|
+
} catch {
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
|
|
59
|
+
var init_secure_files = __esm({
|
|
60
|
+
"src/lib/secure-files.ts"() {
|
|
61
|
+
"use strict";
|
|
62
|
+
PRIVATE_DIR_MODE = 448;
|
|
63
|
+
PRIVATE_FILE_MODE = 384;
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
29
67
|
// src/lib/config.ts
|
|
30
68
|
var config_exports = {};
|
|
31
69
|
__export(config_exports, {
|
|
@@ -42,8 +80,8 @@ __export(config_exports, {
|
|
|
42
80
|
migrateConfig: () => migrateConfig,
|
|
43
81
|
saveConfig: () => saveConfig
|
|
44
82
|
});
|
|
45
|
-
import { readFile, writeFile
|
|
46
|
-
import { readFileSync, existsSync, renameSync } from "fs";
|
|
83
|
+
import { readFile, writeFile } from "fs/promises";
|
|
84
|
+
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
47
85
|
import path from "path";
|
|
48
86
|
import os from "os";
|
|
49
87
|
function resolveDataDir() {
|
|
@@ -51,7 +89,7 @@ function resolveDataDir() {
|
|
|
51
89
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
52
90
|
const newDir = path.join(os.homedir(), ".exe-os");
|
|
53
91
|
const legacyDir = path.join(os.homedir(), ".exe-mem");
|
|
54
|
-
if (!
|
|
92
|
+
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
55
93
|
try {
|
|
56
94
|
renameSync(legacyDir, newDir);
|
|
57
95
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -114,9 +152,9 @@ function normalizeAutoUpdate(raw) {
|
|
|
114
152
|
}
|
|
115
153
|
async function loadConfig() {
|
|
116
154
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
117
|
-
await
|
|
155
|
+
await ensurePrivateDir(dir);
|
|
118
156
|
const configPath = path.join(dir, "config.json");
|
|
119
|
-
if (!
|
|
157
|
+
if (!existsSync2(configPath)) {
|
|
120
158
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
121
159
|
}
|
|
122
160
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -129,6 +167,7 @@ async function loadConfig() {
|
|
|
129
167
|
`);
|
|
130
168
|
try {
|
|
131
169
|
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
170
|
+
await enforcePrivateFile(configPath);
|
|
132
171
|
} catch {
|
|
133
172
|
}
|
|
134
173
|
}
|
|
@@ -147,7 +186,7 @@ async function loadConfig() {
|
|
|
147
186
|
function loadConfigSync() {
|
|
148
187
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
149
188
|
const configPath = path.join(dir, "config.json");
|
|
150
|
-
if (!
|
|
189
|
+
if (!existsSync2(configPath)) {
|
|
151
190
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
152
191
|
}
|
|
153
192
|
try {
|
|
@@ -165,12 +204,10 @@ function loadConfigSync() {
|
|
|
165
204
|
}
|
|
166
205
|
async function saveConfig(config) {
|
|
167
206
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
168
|
-
await
|
|
207
|
+
await ensurePrivateDir(dir);
|
|
169
208
|
const configPath = path.join(dir, "config.json");
|
|
170
209
|
await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
171
|
-
|
|
172
|
-
await chmod(configPath, 384);
|
|
173
|
-
}
|
|
210
|
+
await enforcePrivateFile(configPath);
|
|
174
211
|
}
|
|
175
212
|
async function loadConfigFrom(configPath) {
|
|
176
213
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -190,6 +227,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
|
|
|
190
227
|
var init_config = __esm({
|
|
191
228
|
"src/lib/config.ts"() {
|
|
192
229
|
"use strict";
|
|
230
|
+
init_secure_files();
|
|
193
231
|
EXE_AI_DIR = resolveDataDir();
|
|
194
232
|
DB_PATH = path.join(EXE_AI_DIR, "memories.db");
|
|
195
233
|
MODELS_DIR = path.join(EXE_AI_DIR, "models");
|
|
@@ -268,7 +306,7 @@ var init_config = __esm({
|
|
|
268
306
|
|
|
269
307
|
// src/lib/employees.ts
|
|
270
308
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
271
|
-
import { existsSync as
|
|
309
|
+
import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
|
|
272
310
|
import { execSync } from "child_process";
|
|
273
311
|
import path2 from "path";
|
|
274
312
|
import os2 from "os";
|
|
@@ -289,7 +327,7 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
|
289
327
|
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
290
328
|
}
|
|
291
329
|
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
292
|
-
if (!
|
|
330
|
+
if (!existsSync3(employeesPath)) {
|
|
293
331
|
return [];
|
|
294
332
|
}
|
|
295
333
|
const raw = await readFile2(employeesPath, "utf-8");
|
|
@@ -304,7 +342,7 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
|
304
342
|
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
305
343
|
}
|
|
306
344
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
307
|
-
if (!
|
|
345
|
+
if (!existsSync3(employeesPath)) return [];
|
|
308
346
|
try {
|
|
309
347
|
return JSON.parse(readFileSync2(employeesPath, "utf-8"));
|
|
310
348
|
} catch {
|
|
@@ -355,7 +393,7 @@ function registerBinSymlinks(name) {
|
|
|
355
393
|
for (const suffix of ["", "-opencode"]) {
|
|
356
394
|
const linkName = `${name}${suffix}`;
|
|
357
395
|
const linkPath = path2.join(binDir, linkName);
|
|
358
|
-
if (
|
|
396
|
+
if (existsSync3(linkPath)) {
|
|
359
397
|
skipped.push(linkName);
|
|
360
398
|
continue;
|
|
361
399
|
}
|
|
@@ -1020,13 +1058,50 @@ var init_database_adapter = __esm({
|
|
|
1020
1058
|
}
|
|
1021
1059
|
});
|
|
1022
1060
|
|
|
1061
|
+
// src/lib/daemon-auth.ts
|
|
1062
|
+
import crypto from "crypto";
|
|
1063
|
+
import path4 from "path";
|
|
1064
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
1065
|
+
function normalizeToken(token) {
|
|
1066
|
+
if (!token) return null;
|
|
1067
|
+
const trimmed = token.trim();
|
|
1068
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
1069
|
+
}
|
|
1070
|
+
function readDaemonToken() {
|
|
1071
|
+
try {
|
|
1072
|
+
if (!existsSync4(DAEMON_TOKEN_PATH)) return null;
|
|
1073
|
+
return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
|
|
1074
|
+
} catch {
|
|
1075
|
+
return null;
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
function ensureDaemonToken(seed) {
|
|
1079
|
+
const existing = readDaemonToken();
|
|
1080
|
+
if (existing) return existing;
|
|
1081
|
+
const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
|
|
1082
|
+
ensurePrivateDirSync(EXE_AI_DIR);
|
|
1083
|
+
writeFileSync2(DAEMON_TOKEN_PATH, `${token}
|
|
1084
|
+
`, "utf8");
|
|
1085
|
+
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
1086
|
+
return token;
|
|
1087
|
+
}
|
|
1088
|
+
var DAEMON_TOKEN_PATH;
|
|
1089
|
+
var init_daemon_auth = __esm({
|
|
1090
|
+
"src/lib/daemon-auth.ts"() {
|
|
1091
|
+
"use strict";
|
|
1092
|
+
init_config();
|
|
1093
|
+
init_secure_files();
|
|
1094
|
+
DAEMON_TOKEN_PATH = path4.join(EXE_AI_DIR, "exed.token");
|
|
1095
|
+
}
|
|
1096
|
+
});
|
|
1097
|
+
|
|
1023
1098
|
// src/lib/exe-daemon-client.ts
|
|
1024
1099
|
import net from "net";
|
|
1025
1100
|
import os4 from "os";
|
|
1026
1101
|
import { spawn } from "child_process";
|
|
1027
1102
|
import { randomUUID } from "crypto";
|
|
1028
|
-
import { existsSync as
|
|
1029
|
-
import
|
|
1103
|
+
import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
|
|
1104
|
+
import path5 from "path";
|
|
1030
1105
|
import { fileURLToPath } from "url";
|
|
1031
1106
|
function handleData(chunk) {
|
|
1032
1107
|
_buffer += chunk.toString();
|
|
@@ -1054,9 +1129,9 @@ function handleData(chunk) {
|
|
|
1054
1129
|
}
|
|
1055
1130
|
}
|
|
1056
1131
|
function cleanupStaleFiles() {
|
|
1057
|
-
if (
|
|
1132
|
+
if (existsSync5(PID_PATH)) {
|
|
1058
1133
|
try {
|
|
1059
|
-
const pid = parseInt(
|
|
1134
|
+
const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
|
|
1060
1135
|
if (pid > 0) {
|
|
1061
1136
|
try {
|
|
1062
1137
|
process.kill(pid, 0);
|
|
@@ -1077,11 +1152,11 @@ function cleanupStaleFiles() {
|
|
|
1077
1152
|
}
|
|
1078
1153
|
}
|
|
1079
1154
|
function findPackageRoot() {
|
|
1080
|
-
let dir =
|
|
1081
|
-
const { root } =
|
|
1155
|
+
let dir = path5.dirname(fileURLToPath(import.meta.url));
|
|
1156
|
+
const { root } = path5.parse(dir);
|
|
1082
1157
|
while (dir !== root) {
|
|
1083
|
-
if (
|
|
1084
|
-
dir =
|
|
1158
|
+
if (existsSync5(path5.join(dir, "package.json"))) return dir;
|
|
1159
|
+
dir = path5.dirname(dir);
|
|
1085
1160
|
}
|
|
1086
1161
|
return null;
|
|
1087
1162
|
}
|
|
@@ -1107,16 +1182,17 @@ function spawnDaemon() {
|
|
|
1107
1182
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
1108
1183
|
return;
|
|
1109
1184
|
}
|
|
1110
|
-
const daemonPath =
|
|
1111
|
-
if (!
|
|
1185
|
+
const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
1186
|
+
if (!existsSync5(daemonPath)) {
|
|
1112
1187
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
1113
1188
|
`);
|
|
1114
1189
|
return;
|
|
1115
1190
|
}
|
|
1116
1191
|
const resolvedPath = daemonPath;
|
|
1192
|
+
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
1117
1193
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
1118
1194
|
`);
|
|
1119
|
-
const logPath =
|
|
1195
|
+
const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
|
|
1120
1196
|
let stderrFd = "ignore";
|
|
1121
1197
|
try {
|
|
1122
1198
|
stderrFd = openSync(logPath, "a");
|
|
@@ -1134,7 +1210,8 @@ function spawnDaemon() {
|
|
|
1134
1210
|
TMUX_PANE: void 0,
|
|
1135
1211
|
// Prevents resolveExeSession() from scoping to one session
|
|
1136
1212
|
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
1137
|
-
EXE_DAEMON_PID: PID_PATH
|
|
1213
|
+
EXE_DAEMON_PID: PID_PATH,
|
|
1214
|
+
[DAEMON_TOKEN_ENV]: daemonToken
|
|
1138
1215
|
}
|
|
1139
1216
|
});
|
|
1140
1217
|
child.unref();
|
|
@@ -1241,13 +1318,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
1241
1318
|
return;
|
|
1242
1319
|
}
|
|
1243
1320
|
const id = randomUUID();
|
|
1321
|
+
const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
|
|
1244
1322
|
const timer = setTimeout(() => {
|
|
1245
1323
|
_pending.delete(id);
|
|
1246
1324
|
resolve({ error: "Request timeout" });
|
|
1247
1325
|
}, timeoutMs);
|
|
1248
1326
|
_pending.set(id, { resolve, timer });
|
|
1249
1327
|
try {
|
|
1250
|
-
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
1328
|
+
_socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
|
|
1251
1329
|
} catch {
|
|
1252
1330
|
clearTimeout(timer);
|
|
1253
1331
|
_pending.delete(id);
|
|
@@ -1258,17 +1336,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
1258
1336
|
function isClientConnected() {
|
|
1259
1337
|
return _connected;
|
|
1260
1338
|
}
|
|
1261
|
-
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
|
|
1339
|
+
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, DAEMON_TOKEN_ENV, _socket, _connected, _buffer, _pending, MAX_BUFFER;
|
|
1262
1340
|
var init_exe_daemon_client = __esm({
|
|
1263
1341
|
"src/lib/exe-daemon-client.ts"() {
|
|
1264
1342
|
"use strict";
|
|
1265
1343
|
init_config();
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1344
|
+
init_daemon_auth();
|
|
1345
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
|
|
1346
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
|
|
1347
|
+
SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
1269
1348
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
1270
1349
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
1271
1350
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
1351
|
+
DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
|
|
1272
1352
|
_socket = null;
|
|
1273
1353
|
_connected = false;
|
|
1274
1354
|
_buffer = "";
|
|
@@ -1847,6 +1927,7 @@ async function ensureSchema() {
|
|
|
1847
1927
|
project TEXT NOT NULL,
|
|
1848
1928
|
summary TEXT NOT NULL,
|
|
1849
1929
|
task_file TEXT,
|
|
1930
|
+
session_scope TEXT,
|
|
1850
1931
|
read INTEGER NOT NULL DEFAULT 0,
|
|
1851
1932
|
created_at TEXT NOT NULL
|
|
1852
1933
|
);
|
|
@@ -1855,7 +1936,7 @@ async function ensureSchema() {
|
|
|
1855
1936
|
ON notifications(read);
|
|
1856
1937
|
|
|
1857
1938
|
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
1858
|
-
ON notifications(agent_id);
|
|
1939
|
+
ON notifications(agent_id, session_scope);
|
|
1859
1940
|
|
|
1860
1941
|
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
1861
1942
|
ON notifications(task_file);
|
|
@@ -1893,6 +1974,7 @@ async function ensureSchema() {
|
|
|
1893
1974
|
target_agent TEXT NOT NULL,
|
|
1894
1975
|
target_project TEXT,
|
|
1895
1976
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
1977
|
+
session_scope TEXT,
|
|
1896
1978
|
content TEXT NOT NULL,
|
|
1897
1979
|
priority TEXT DEFAULT 'normal',
|
|
1898
1980
|
status TEXT DEFAULT 'pending',
|
|
@@ -1906,10 +1988,31 @@ async function ensureSchema() {
|
|
|
1906
1988
|
);
|
|
1907
1989
|
|
|
1908
1990
|
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
1909
|
-
ON messages(target_agent, status);
|
|
1991
|
+
ON messages(target_agent, session_scope, status);
|
|
1910
1992
|
|
|
1911
1993
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
1912
|
-
ON messages(target_agent, from_agent, server_seq);
|
|
1994
|
+
ON messages(target_agent, session_scope, from_agent, server_seq);
|
|
1995
|
+
`);
|
|
1996
|
+
try {
|
|
1997
|
+
await client.execute({
|
|
1998
|
+
sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
|
|
1999
|
+
args: []
|
|
2000
|
+
});
|
|
2001
|
+
} catch {
|
|
2002
|
+
}
|
|
2003
|
+
try {
|
|
2004
|
+
await client.execute({
|
|
2005
|
+
sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
|
|
2006
|
+
args: []
|
|
2007
|
+
});
|
|
2008
|
+
} catch {
|
|
2009
|
+
}
|
|
2010
|
+
await client.executeMultiple(`
|
|
2011
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
|
|
2012
|
+
ON notifications(agent_id, session_scope, read, created_at);
|
|
2013
|
+
|
|
2014
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
|
|
2015
|
+
ON messages(target_agent, session_scope, status, created_at);
|
|
1913
2016
|
`);
|
|
1914
2017
|
try {
|
|
1915
2018
|
await client.execute({
|
|
@@ -2493,6 +2596,13 @@ async function ensureSchema() {
|
|
|
2493
2596
|
} catch {
|
|
2494
2597
|
}
|
|
2495
2598
|
}
|
|
2599
|
+
try {
|
|
2600
|
+
await client.execute({
|
|
2601
|
+
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
2602
|
+
args: []
|
|
2603
|
+
});
|
|
2604
|
+
} catch {
|
|
2605
|
+
}
|
|
2496
2606
|
}
|
|
2497
2607
|
async function disposeDatabase() {
|
|
2498
2608
|
if (_walCheckpointTimer) {
|
|
@@ -2734,14 +2844,14 @@ __export(keychain_exports, {
|
|
|
2734
2844
|
setMasterKey: () => setMasterKey
|
|
2735
2845
|
});
|
|
2736
2846
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
2737
|
-
import { existsSync as
|
|
2738
|
-
import
|
|
2847
|
+
import { existsSync as existsSync6 } from "fs";
|
|
2848
|
+
import path6 from "path";
|
|
2739
2849
|
import os5 from "os";
|
|
2740
2850
|
function getKeyDir() {
|
|
2741
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
2851
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path6.join(os5.homedir(), ".exe-os");
|
|
2742
2852
|
}
|
|
2743
2853
|
function getKeyPath() {
|
|
2744
|
-
return
|
|
2854
|
+
return path6.join(getKeyDir(), "master.key");
|
|
2745
2855
|
}
|
|
2746
2856
|
async function tryKeytar() {
|
|
2747
2857
|
try {
|
|
@@ -2762,7 +2872,7 @@ async function getMasterKey() {
|
|
|
2762
2872
|
}
|
|
2763
2873
|
}
|
|
2764
2874
|
const keyPath = getKeyPath();
|
|
2765
|
-
if (!
|
|
2875
|
+
if (!existsSync6(keyPath)) {
|
|
2766
2876
|
process.stderr.write(
|
|
2767
2877
|
`[keychain] Key not found at ${keyPath} (HOME=${os5.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
2768
2878
|
`
|
|
@@ -2805,7 +2915,7 @@ async function deleteMasterKey() {
|
|
|
2805
2915
|
}
|
|
2806
2916
|
}
|
|
2807
2917
|
const keyPath = getKeyPath();
|
|
2808
|
-
if (
|
|
2918
|
+
if (existsSync6(keyPath)) {
|
|
2809
2919
|
await unlink(keyPath);
|
|
2810
2920
|
}
|
|
2811
2921
|
}
|
|
@@ -2907,6 +3017,7 @@ var shard_manager_exports = {};
|
|
|
2907
3017
|
__export(shard_manager_exports, {
|
|
2908
3018
|
disposeShards: () => disposeShards,
|
|
2909
3019
|
ensureShardSchema: () => ensureShardSchema,
|
|
3020
|
+
getOpenShardCount: () => getOpenShardCount,
|
|
2910
3021
|
getReadyShardClient: () => getReadyShardClient,
|
|
2911
3022
|
getShardClient: () => getShardClient,
|
|
2912
3023
|
getShardsDir: () => getShardsDir,
|
|
@@ -2915,15 +3026,18 @@ __export(shard_manager_exports, {
|
|
|
2915
3026
|
listShards: () => listShards,
|
|
2916
3027
|
shardExists: () => shardExists
|
|
2917
3028
|
});
|
|
2918
|
-
import
|
|
2919
|
-
import { existsSync as
|
|
3029
|
+
import path7 from "path";
|
|
3030
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync2, readdirSync } from "fs";
|
|
2920
3031
|
import { createClient as createClient2 } from "@libsql/client";
|
|
2921
3032
|
function initShardManager(encryptionKey) {
|
|
2922
3033
|
_encryptionKey = encryptionKey;
|
|
2923
|
-
if (!
|
|
2924
|
-
|
|
3034
|
+
if (!existsSync7(SHARDS_DIR)) {
|
|
3035
|
+
mkdirSync2(SHARDS_DIR, { recursive: true });
|
|
2925
3036
|
}
|
|
2926
3037
|
_shardingEnabled = true;
|
|
3038
|
+
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
3039
|
+
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
3040
|
+
_evictionTimer.unref();
|
|
2927
3041
|
}
|
|
2928
3042
|
function isShardingEnabled() {
|
|
2929
3043
|
return _shardingEnabled;
|
|
@@ -2940,21 +3054,28 @@ function getShardClient(projectName) {
|
|
|
2940
3054
|
throw new Error(`Invalid project name for shard: "${projectName}"`);
|
|
2941
3055
|
}
|
|
2942
3056
|
const cached = _shards.get(safeName);
|
|
2943
|
-
if (cached)
|
|
2944
|
-
|
|
3057
|
+
if (cached) {
|
|
3058
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
3059
|
+
return cached;
|
|
3060
|
+
}
|
|
3061
|
+
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
3062
|
+
evictLRU();
|
|
3063
|
+
}
|
|
3064
|
+
const dbPath = path7.join(SHARDS_DIR, `${safeName}.db`);
|
|
2945
3065
|
const client = createClient2({
|
|
2946
3066
|
url: `file:${dbPath}`,
|
|
2947
3067
|
encryptionKey: _encryptionKey
|
|
2948
3068
|
});
|
|
2949
3069
|
_shards.set(safeName, client);
|
|
3070
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
2950
3071
|
return client;
|
|
2951
3072
|
}
|
|
2952
3073
|
function shardExists(projectName) {
|
|
2953
3074
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
2954
|
-
return
|
|
3075
|
+
return existsSync7(path7.join(SHARDS_DIR, `${safeName}.db`));
|
|
2955
3076
|
}
|
|
2956
3077
|
function listShards() {
|
|
2957
|
-
if (!
|
|
3078
|
+
if (!existsSync7(SHARDS_DIR)) return [];
|
|
2958
3079
|
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
2959
3080
|
}
|
|
2960
3081
|
async function ensureShardSchema(client) {
|
|
@@ -3006,6 +3127,8 @@ async function ensureShardSchema(client) {
|
|
|
3006
3127
|
for (const col of [
|
|
3007
3128
|
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
3008
3129
|
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
3130
|
+
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
3131
|
+
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
3009
3132
|
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
3010
3133
|
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
3011
3134
|
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
@@ -3143,21 +3266,69 @@ async function getReadyShardClient(projectName) {
|
|
|
3143
3266
|
await ensureShardSchema(client);
|
|
3144
3267
|
return client;
|
|
3145
3268
|
}
|
|
3269
|
+
function evictLRU() {
|
|
3270
|
+
let oldest = null;
|
|
3271
|
+
let oldestTime = Infinity;
|
|
3272
|
+
for (const [name, time] of _shardLastAccess) {
|
|
3273
|
+
if (time < oldestTime) {
|
|
3274
|
+
oldestTime = time;
|
|
3275
|
+
oldest = name;
|
|
3276
|
+
}
|
|
3277
|
+
}
|
|
3278
|
+
if (oldest) {
|
|
3279
|
+
const client = _shards.get(oldest);
|
|
3280
|
+
if (client) {
|
|
3281
|
+
client.close();
|
|
3282
|
+
}
|
|
3283
|
+
_shards.delete(oldest);
|
|
3284
|
+
_shardLastAccess.delete(oldest);
|
|
3285
|
+
}
|
|
3286
|
+
}
|
|
3287
|
+
function evictIdleShards() {
|
|
3288
|
+
const now = Date.now();
|
|
3289
|
+
const toEvict = [];
|
|
3290
|
+
for (const [name, lastAccess] of _shardLastAccess) {
|
|
3291
|
+
if (now - lastAccess > SHARD_IDLE_MS) {
|
|
3292
|
+
toEvict.push(name);
|
|
3293
|
+
}
|
|
3294
|
+
}
|
|
3295
|
+
for (const name of toEvict) {
|
|
3296
|
+
const client = _shards.get(name);
|
|
3297
|
+
if (client) {
|
|
3298
|
+
client.close();
|
|
3299
|
+
}
|
|
3300
|
+
_shards.delete(name);
|
|
3301
|
+
_shardLastAccess.delete(name);
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
3304
|
+
function getOpenShardCount() {
|
|
3305
|
+
return _shards.size;
|
|
3306
|
+
}
|
|
3146
3307
|
function disposeShards() {
|
|
3308
|
+
if (_evictionTimer) {
|
|
3309
|
+
clearInterval(_evictionTimer);
|
|
3310
|
+
_evictionTimer = null;
|
|
3311
|
+
}
|
|
3147
3312
|
for (const [, client] of _shards) {
|
|
3148
3313
|
client.close();
|
|
3149
3314
|
}
|
|
3150
3315
|
_shards.clear();
|
|
3316
|
+
_shardLastAccess.clear();
|
|
3151
3317
|
_shardingEnabled = false;
|
|
3152
3318
|
_encryptionKey = null;
|
|
3153
3319
|
}
|
|
3154
|
-
var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
|
|
3320
|
+
var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
|
|
3155
3321
|
var init_shard_manager = __esm({
|
|
3156
3322
|
"src/lib/shard-manager.ts"() {
|
|
3157
3323
|
"use strict";
|
|
3158
3324
|
init_config();
|
|
3159
|
-
SHARDS_DIR =
|
|
3325
|
+
SHARDS_DIR = path7.join(EXE_AI_DIR, "shards");
|
|
3326
|
+
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
3327
|
+
MAX_OPEN_SHARDS = 10;
|
|
3328
|
+
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
3160
3329
|
_shards = /* @__PURE__ */ new Map();
|
|
3330
|
+
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
3331
|
+
_evictionTimer = null;
|
|
3161
3332
|
_encryptionKey = null;
|
|
3162
3333
|
_shardingEnabled = false;
|
|
3163
3334
|
}
|
|
@@ -3262,14 +3433,14 @@ __export(session_registry_exports, {
|
|
|
3262
3433
|
pruneStaleSessions: () => pruneStaleSessions,
|
|
3263
3434
|
registerSession: () => registerSession
|
|
3264
3435
|
});
|
|
3265
|
-
import { readFileSync as
|
|
3436
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync8 } from "fs";
|
|
3266
3437
|
import { execSync as execSync2 } from "child_process";
|
|
3267
|
-
import
|
|
3438
|
+
import path8 from "path";
|
|
3268
3439
|
import os6 from "os";
|
|
3269
3440
|
function registerSession(entry) {
|
|
3270
|
-
const dir =
|
|
3271
|
-
if (!
|
|
3272
|
-
|
|
3441
|
+
const dir = path8.dirname(REGISTRY_PATH);
|
|
3442
|
+
if (!existsSync8(dir)) {
|
|
3443
|
+
mkdirSync3(dir, { recursive: true });
|
|
3273
3444
|
}
|
|
3274
3445
|
const sessions = listSessions();
|
|
3275
3446
|
const idx = sessions.findIndex((s) => s.windowName === entry.windowName);
|
|
@@ -3278,11 +3449,11 @@ function registerSession(entry) {
|
|
|
3278
3449
|
} else {
|
|
3279
3450
|
sessions.push(entry);
|
|
3280
3451
|
}
|
|
3281
|
-
|
|
3452
|
+
writeFileSync3(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
|
|
3282
3453
|
}
|
|
3283
3454
|
function listSessions() {
|
|
3284
3455
|
try {
|
|
3285
|
-
const raw =
|
|
3456
|
+
const raw = readFileSync5(REGISTRY_PATH, "utf8");
|
|
3286
3457
|
return JSON.parse(raw);
|
|
3287
3458
|
} catch {
|
|
3288
3459
|
return [];
|
|
@@ -3303,7 +3474,7 @@ function pruneStaleSessions() {
|
|
|
3303
3474
|
const alive = sessions.filter((s) => liveSet.has(s.windowName));
|
|
3304
3475
|
const pruned = sessions.length - alive.length;
|
|
3305
3476
|
if (pruned > 0) {
|
|
3306
|
-
|
|
3477
|
+
writeFileSync3(REGISTRY_PATH, JSON.stringify(alive, null, 2));
|
|
3307
3478
|
}
|
|
3308
3479
|
return pruned;
|
|
3309
3480
|
}
|
|
@@ -3311,7 +3482,7 @@ var REGISTRY_PATH;
|
|
|
3311
3482
|
var init_session_registry = __esm({
|
|
3312
3483
|
"src/lib/session-registry.ts"() {
|
|
3313
3484
|
"use strict";
|
|
3314
|
-
REGISTRY_PATH =
|
|
3485
|
+
REGISTRY_PATH = path8.join(os6.homedir(), ".exe-os", "session-registry.json");
|
|
3315
3486
|
}
|
|
3316
3487
|
});
|
|
3317
3488
|
|
|
@@ -3591,12 +3762,12 @@ var init_runtime_table = __esm({
|
|
|
3591
3762
|
});
|
|
3592
3763
|
|
|
3593
3764
|
// src/lib/agent-config.ts
|
|
3594
|
-
import { readFileSync as
|
|
3595
|
-
import
|
|
3765
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync9 } from "fs";
|
|
3766
|
+
import path9 from "path";
|
|
3596
3767
|
function loadAgentConfig() {
|
|
3597
|
-
if (!
|
|
3768
|
+
if (!existsSync9(AGENT_CONFIG_PATH)) return {};
|
|
3598
3769
|
try {
|
|
3599
|
-
return JSON.parse(
|
|
3770
|
+
return JSON.parse(readFileSync6(AGENT_CONFIG_PATH, "utf-8"));
|
|
3600
3771
|
} catch {
|
|
3601
3772
|
return {};
|
|
3602
3773
|
}
|
|
@@ -3615,7 +3786,8 @@ var init_agent_config = __esm({
|
|
|
3615
3786
|
"use strict";
|
|
3616
3787
|
init_config();
|
|
3617
3788
|
init_runtime_table();
|
|
3618
|
-
|
|
3789
|
+
init_secure_files();
|
|
3790
|
+
AGENT_CONFIG_PATH = path9.join(EXE_AI_DIR, "agent-config.json");
|
|
3619
3791
|
DEFAULT_MODELS = {
|
|
3620
3792
|
claude: "claude-opus-4",
|
|
3621
3793
|
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
@@ -3633,17 +3805,17 @@ __export(intercom_queue_exports, {
|
|
|
3633
3805
|
queueIntercom: () => queueIntercom,
|
|
3634
3806
|
readQueue: () => readQueue
|
|
3635
3807
|
});
|
|
3636
|
-
import { readFileSync as
|
|
3637
|
-
import
|
|
3808
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, renameSync as renameSync3, existsSync as existsSync10, mkdirSync as mkdirSync4 } from "fs";
|
|
3809
|
+
import path10 from "path";
|
|
3638
3810
|
import os7 from "os";
|
|
3639
3811
|
function ensureDir() {
|
|
3640
|
-
const dir =
|
|
3641
|
-
if (!
|
|
3812
|
+
const dir = path10.dirname(QUEUE_PATH);
|
|
3813
|
+
if (!existsSync10(dir)) mkdirSync4(dir, { recursive: true });
|
|
3642
3814
|
}
|
|
3643
3815
|
function readQueue() {
|
|
3644
3816
|
try {
|
|
3645
|
-
if (!
|
|
3646
|
-
return JSON.parse(
|
|
3817
|
+
if (!existsSync10(QUEUE_PATH)) return [];
|
|
3818
|
+
return JSON.parse(readFileSync7(QUEUE_PATH, "utf8"));
|
|
3647
3819
|
} catch {
|
|
3648
3820
|
return [];
|
|
3649
3821
|
}
|
|
@@ -3651,7 +3823,7 @@ function readQueue() {
|
|
|
3651
3823
|
function writeQueue(queue) {
|
|
3652
3824
|
ensureDir();
|
|
3653
3825
|
const tmp = `${QUEUE_PATH}.tmp`;
|
|
3654
|
-
|
|
3826
|
+
writeFileSync5(tmp, JSON.stringify(queue, null, 2));
|
|
3655
3827
|
renameSync3(tmp, QUEUE_PATH);
|
|
3656
3828
|
}
|
|
3657
3829
|
function queueIntercom(targetSession, reason) {
|
|
@@ -3743,10 +3915,10 @@ var QUEUE_PATH, MAX_RETRIES2, TTL_MS, INTERCOM_LOG;
|
|
|
3743
3915
|
var init_intercom_queue = __esm({
|
|
3744
3916
|
"src/lib/intercom-queue.ts"() {
|
|
3745
3917
|
"use strict";
|
|
3746
|
-
QUEUE_PATH =
|
|
3918
|
+
QUEUE_PATH = path10.join(os7.homedir(), ".exe-os", "intercom-queue.json");
|
|
3747
3919
|
MAX_RETRIES2 = 5;
|
|
3748
3920
|
TTL_MS = 60 * 60 * 1e3;
|
|
3749
|
-
INTERCOM_LOG =
|
|
3921
|
+
INTERCOM_LOG = path10.join(os7.homedir(), ".exe-os", "intercom.log");
|
|
3750
3922
|
}
|
|
3751
3923
|
});
|
|
3752
3924
|
|
|
@@ -3767,9 +3939,12 @@ __export(license_exports, {
|
|
|
3767
3939
|
stopLicenseRevalidation: () => stopLicenseRevalidation,
|
|
3768
3940
|
validateLicense: () => validateLicense
|
|
3769
3941
|
});
|
|
3770
|
-
import { readFileSync as
|
|
3942
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync11, mkdirSync as mkdirSync5 } from "fs";
|
|
3771
3943
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
3772
|
-
import
|
|
3944
|
+
import { createRequire as createRequire2 } from "module";
|
|
3945
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
3946
|
+
import os8 from "os";
|
|
3947
|
+
import path11 from "path";
|
|
3773
3948
|
import { jwtVerify, importSPKI } from "jose";
|
|
3774
3949
|
async function fetchRetry(url, init) {
|
|
3775
3950
|
try {
|
|
@@ -3780,37 +3955,37 @@ async function fetchRetry(url, init) {
|
|
|
3780
3955
|
}
|
|
3781
3956
|
}
|
|
3782
3957
|
function loadDeviceId() {
|
|
3783
|
-
const deviceJsonPath =
|
|
3958
|
+
const deviceJsonPath = path11.join(EXE_AI_DIR, "device.json");
|
|
3784
3959
|
try {
|
|
3785
|
-
if (
|
|
3786
|
-
const data = JSON.parse(
|
|
3960
|
+
if (existsSync11(deviceJsonPath)) {
|
|
3961
|
+
const data = JSON.parse(readFileSync8(deviceJsonPath, "utf8"));
|
|
3787
3962
|
if (data.deviceId) return data.deviceId;
|
|
3788
3963
|
}
|
|
3789
3964
|
} catch {
|
|
3790
3965
|
}
|
|
3791
3966
|
try {
|
|
3792
|
-
if (
|
|
3793
|
-
const id2 =
|
|
3967
|
+
if (existsSync11(DEVICE_ID_PATH)) {
|
|
3968
|
+
const id2 = readFileSync8(DEVICE_ID_PATH, "utf8").trim();
|
|
3794
3969
|
if (id2) return id2;
|
|
3795
3970
|
}
|
|
3796
3971
|
} catch {
|
|
3797
3972
|
}
|
|
3798
3973
|
const id = randomUUID3();
|
|
3799
3974
|
mkdirSync5(EXE_AI_DIR, { recursive: true });
|
|
3800
|
-
|
|
3975
|
+
writeFileSync6(DEVICE_ID_PATH, id, "utf8");
|
|
3801
3976
|
return id;
|
|
3802
3977
|
}
|
|
3803
3978
|
function loadLicense() {
|
|
3804
3979
|
try {
|
|
3805
|
-
if (!
|
|
3806
|
-
return
|
|
3980
|
+
if (!existsSync11(LICENSE_PATH)) return null;
|
|
3981
|
+
return readFileSync8(LICENSE_PATH, "utf8").trim();
|
|
3807
3982
|
} catch {
|
|
3808
3983
|
return null;
|
|
3809
3984
|
}
|
|
3810
3985
|
}
|
|
3811
3986
|
function saveLicense(apiKey) {
|
|
3812
3987
|
mkdirSync5(EXE_AI_DIR, { recursive: true });
|
|
3813
|
-
|
|
3988
|
+
writeFileSync6(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
|
|
3814
3989
|
}
|
|
3815
3990
|
async function verifyLicenseJwt(token) {
|
|
3816
3991
|
try {
|
|
@@ -3836,8 +4011,8 @@ async function verifyLicenseJwt(token) {
|
|
|
3836
4011
|
}
|
|
3837
4012
|
async function getCachedLicense() {
|
|
3838
4013
|
try {
|
|
3839
|
-
if (!
|
|
3840
|
-
const raw = JSON.parse(
|
|
4014
|
+
if (!existsSync11(CACHE_PATH)) return null;
|
|
4015
|
+
const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
|
|
3841
4016
|
if (!raw.token || typeof raw.token !== "string") return null;
|
|
3842
4017
|
return await verifyLicenseJwt(raw.token);
|
|
3843
4018
|
} catch {
|
|
@@ -3846,8 +4021,8 @@ async function getCachedLicense() {
|
|
|
3846
4021
|
}
|
|
3847
4022
|
function readCachedToken() {
|
|
3848
4023
|
try {
|
|
3849
|
-
if (!
|
|
3850
|
-
const raw = JSON.parse(
|
|
4024
|
+
if (!existsSync11(CACHE_PATH)) return null;
|
|
4025
|
+
const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
|
|
3851
4026
|
return typeof raw.token === "string" ? raw.token : null;
|
|
3852
4027
|
} catch {
|
|
3853
4028
|
return null;
|
|
@@ -3881,56 +4056,130 @@ function getRawCachedPlan() {
|
|
|
3881
4056
|
}
|
|
3882
4057
|
function cacheResponse(token) {
|
|
3883
4058
|
try {
|
|
3884
|
-
|
|
4059
|
+
writeFileSync6(CACHE_PATH, JSON.stringify({ token }), "utf8");
|
|
3885
4060
|
} catch {
|
|
3886
4061
|
}
|
|
3887
4062
|
}
|
|
3888
|
-
|
|
3889
|
-
|
|
4063
|
+
function loadPrismaForLicense() {
|
|
4064
|
+
if (_prismaFailed) return null;
|
|
4065
|
+
const dbUrl = process.env.DATABASE_URL;
|
|
4066
|
+
if (!dbUrl) {
|
|
4067
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path11.join(os8.homedir(), "exe-db");
|
|
4068
|
+
if (!existsSync11(path11.join(exeDbRoot, "package.json"))) {
|
|
4069
|
+
_prismaFailed = true;
|
|
4070
|
+
return null;
|
|
4071
|
+
}
|
|
4072
|
+
}
|
|
4073
|
+
if (!_prismaPromise) {
|
|
4074
|
+
_prismaPromise = (async () => {
|
|
4075
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
4076
|
+
if (explicitPath) {
|
|
4077
|
+
const mod2 = await import(pathToFileURL2(explicitPath).href);
|
|
4078
|
+
const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
|
|
4079
|
+
if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
|
|
4080
|
+
return new Ctor2();
|
|
4081
|
+
}
|
|
4082
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path11.join(os8.homedir(), "exe-db");
|
|
4083
|
+
const req = createRequire2(path11.join(exeDbRoot, "package.json"));
|
|
4084
|
+
const entry = req.resolve("@prisma/client");
|
|
4085
|
+
const mod = await import(pathToFileURL2(entry).href);
|
|
4086
|
+
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
4087
|
+
if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
|
|
4088
|
+
return new Ctor();
|
|
4089
|
+
})().catch((err) => {
|
|
4090
|
+
_prismaFailed = true;
|
|
4091
|
+
_prismaPromise = null;
|
|
4092
|
+
throw err;
|
|
4093
|
+
});
|
|
4094
|
+
}
|
|
4095
|
+
return _prismaPromise;
|
|
4096
|
+
}
|
|
4097
|
+
async function validateViaPostgres(apiKey) {
|
|
4098
|
+
const loader = loadPrismaForLicense();
|
|
4099
|
+
if (!loader) return null;
|
|
4100
|
+
try {
|
|
4101
|
+
const prisma = await loader;
|
|
4102
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
4103
|
+
`SELECT plan, email, status, device_limit, employee_limit, memory_limit, expires_at
|
|
4104
|
+
FROM billing.licenses WHERE key = $1 LIMIT 1`,
|
|
4105
|
+
apiKey
|
|
4106
|
+
);
|
|
4107
|
+
if (!rows || rows.length === 0) return null;
|
|
4108
|
+
const row = rows[0];
|
|
4109
|
+
if (row.status !== "active") return null;
|
|
4110
|
+
if (row.expires_at && new Date(row.expires_at) < /* @__PURE__ */ new Date()) return null;
|
|
4111
|
+
const plan = row.plan;
|
|
4112
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
4113
|
+
return {
|
|
4114
|
+
valid: true,
|
|
4115
|
+
plan,
|
|
4116
|
+
email: row.email,
|
|
4117
|
+
expiresAt: row.expires_at ? new Date(row.expires_at).toISOString() : null,
|
|
4118
|
+
deviceLimit: row.device_limit ?? limits.devices,
|
|
4119
|
+
employeeLimit: row.employee_limit ?? limits.employees,
|
|
4120
|
+
memoryLimit: row.memory_limit ?? limits.memories
|
|
4121
|
+
};
|
|
4122
|
+
} catch {
|
|
4123
|
+
return null;
|
|
4124
|
+
}
|
|
4125
|
+
}
|
|
4126
|
+
async function validateViaCFWorker(apiKey, deviceId) {
|
|
3890
4127
|
try {
|
|
3891
4128
|
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
3892
4129
|
method: "POST",
|
|
3893
4130
|
headers: { "Content-Type": "application/json" },
|
|
3894
|
-
body: JSON.stringify({ apiKey, deviceId
|
|
4131
|
+
body: JSON.stringify({ apiKey, deviceId }),
|
|
3895
4132
|
signal: AbortSignal.timeout(1e4)
|
|
3896
4133
|
});
|
|
3897
|
-
if (res.ok)
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
4134
|
+
if (!res.ok) return null;
|
|
4135
|
+
const data = await res.json();
|
|
4136
|
+
if (data.error === "device_limit_exceeded") return null;
|
|
4137
|
+
if (!data.valid) return null;
|
|
4138
|
+
if (data.token) {
|
|
4139
|
+
cacheResponse(data.token);
|
|
4140
|
+
const verified = await verifyLicenseJwt(data.token);
|
|
4141
|
+
if (verified) return verified;
|
|
4142
|
+
}
|
|
4143
|
+
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
4144
|
+
return {
|
|
4145
|
+
valid: data.valid,
|
|
4146
|
+
plan: data.plan,
|
|
4147
|
+
email: data.email,
|
|
4148
|
+
expiresAt: data.expiresAt,
|
|
4149
|
+
deviceLimit: limits.devices,
|
|
4150
|
+
employeeLimit: limits.employees,
|
|
4151
|
+
memoryLimit: limits.memories
|
|
4152
|
+
};
|
|
4153
|
+
} catch {
|
|
4154
|
+
return null;
|
|
4155
|
+
}
|
|
4156
|
+
}
|
|
4157
|
+
async function validateLicense(apiKey, deviceId) {
|
|
4158
|
+
const did = deviceId ?? loadDeviceId();
|
|
4159
|
+
const pgResult = await validateViaPostgres(apiKey);
|
|
4160
|
+
if (pgResult) {
|
|
4161
|
+
try {
|
|
4162
|
+
writeFileSync6(CACHE_PATH, JSON.stringify({ pgLicense: pgResult, ts: Date.now() }), "utf8");
|
|
4163
|
+
} catch {
|
|
4164
|
+
}
|
|
4165
|
+
return pgResult;
|
|
4166
|
+
}
|
|
4167
|
+
const cfResult = await validateViaCFWorker(apiKey, did);
|
|
4168
|
+
if (cfResult) return cfResult;
|
|
4169
|
+
const cached = await getCachedLicense();
|
|
4170
|
+
if (cached) return cached;
|
|
4171
|
+
try {
|
|
4172
|
+
if (existsSync11(CACHE_PATH)) {
|
|
4173
|
+
const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
|
|
4174
|
+
if (raw.pgLicense && raw.ts && Date.now() - raw.ts < 7 * 24 * 60 * 60 * 1e3) {
|
|
4175
|
+
return raw.pgLicense;
|
|
4176
|
+
}
|
|
3921
4177
|
}
|
|
3922
|
-
const cached = await getCachedLicense();
|
|
3923
|
-
if (cached) return cached;
|
|
3924
|
-
const raw = getRawCachedPlan();
|
|
3925
|
-
if (raw) return raw;
|
|
3926
|
-
return { ...FREE_LICENSE, valid: false, plan: "free" };
|
|
3927
4178
|
} catch {
|
|
3928
|
-
const cached = await getCachedLicense();
|
|
3929
|
-
if (cached) return cached;
|
|
3930
|
-
const rawFallback = getRawCachedPlan();
|
|
3931
|
-
if (rawFallback) return rawFallback;
|
|
3932
|
-
return { ...FREE_LICENSE, valid: false, error: "offline" };
|
|
3933
4179
|
}
|
|
4180
|
+
const rawFallback = getRawCachedPlan();
|
|
4181
|
+
if (rawFallback) return rawFallback;
|
|
4182
|
+
return { ...FREE_LICENSE, valid: false };
|
|
3934
4183
|
}
|
|
3935
4184
|
function getCacheAgeMs() {
|
|
3936
4185
|
try {
|
|
@@ -3945,9 +4194,9 @@ async function checkLicense() {
|
|
|
3945
4194
|
let key = loadLicense();
|
|
3946
4195
|
if (!key) {
|
|
3947
4196
|
try {
|
|
3948
|
-
const configPath =
|
|
3949
|
-
if (
|
|
3950
|
-
const raw = JSON.parse(
|
|
4197
|
+
const configPath = path11.join(EXE_AI_DIR, "config.json");
|
|
4198
|
+
if (existsSync11(configPath)) {
|
|
4199
|
+
const raw = JSON.parse(readFileSync8(configPath, "utf8"));
|
|
3951
4200
|
const cloud = raw.cloud;
|
|
3952
4201
|
if (cloud?.apiKey) {
|
|
3953
4202
|
key = cloud.apiKey;
|
|
@@ -4101,14 +4350,14 @@ function stopLicenseRevalidation() {
|
|
|
4101
4350
|
_revalTimer = null;
|
|
4102
4351
|
}
|
|
4103
4352
|
}
|
|
4104
|
-
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, RETRY_DELAY_MS, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, CACHE_MAX_AGE_MS, _revalTimer;
|
|
4353
|
+
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, RETRY_DELAY_MS, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, _prismaPromise, _prismaFailed, CACHE_MAX_AGE_MS, _revalTimer;
|
|
4105
4354
|
var init_license = __esm({
|
|
4106
4355
|
"src/lib/license.ts"() {
|
|
4107
4356
|
"use strict";
|
|
4108
4357
|
init_config();
|
|
4109
|
-
LICENSE_PATH =
|
|
4110
|
-
CACHE_PATH =
|
|
4111
|
-
DEVICE_ID_PATH =
|
|
4358
|
+
LICENSE_PATH = path11.join(EXE_AI_DIR, "license.key");
|
|
4359
|
+
CACHE_PATH = path11.join(EXE_AI_DIR, "license-cache.json");
|
|
4360
|
+
DEVICE_ID_PATH = path11.join(EXE_AI_DIR, "device-id");
|
|
4112
4361
|
API_BASE = "https://askexe.com/cloud";
|
|
4113
4362
|
RETRY_DELAY_MS = 500;
|
|
4114
4363
|
LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
@@ -4132,18 +4381,20 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
|
4132
4381
|
employeeLimit: 1,
|
|
4133
4382
|
memoryLimit: 5e3
|
|
4134
4383
|
};
|
|
4384
|
+
_prismaPromise = null;
|
|
4385
|
+
_prismaFailed = false;
|
|
4135
4386
|
CACHE_MAX_AGE_MS = 36e5;
|
|
4136
4387
|
_revalTimer = null;
|
|
4137
4388
|
}
|
|
4138
4389
|
});
|
|
4139
4390
|
|
|
4140
4391
|
// src/lib/plan-limits.ts
|
|
4141
|
-
import { readFileSync as
|
|
4142
|
-
import
|
|
4392
|
+
import { readFileSync as readFileSync9, existsSync as existsSync12 } from "fs";
|
|
4393
|
+
import path12 from "path";
|
|
4143
4394
|
function getLicenseSync() {
|
|
4144
4395
|
try {
|
|
4145
|
-
if (!
|
|
4146
|
-
const raw = JSON.parse(
|
|
4396
|
+
if (!existsSync12(CACHE_PATH2)) return freeLicense();
|
|
4397
|
+
const raw = JSON.parse(readFileSync9(CACHE_PATH2, "utf8"));
|
|
4147
4398
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
4148
4399
|
const parts = raw.token.split(".");
|
|
4149
4400
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -4181,8 +4432,8 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
4181
4432
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
4182
4433
|
let count = 0;
|
|
4183
4434
|
try {
|
|
4184
|
-
if (
|
|
4185
|
-
const raw =
|
|
4435
|
+
if (existsSync12(filePath)) {
|
|
4436
|
+
const raw = readFileSync9(filePath, "utf8");
|
|
4186
4437
|
const employees = JSON.parse(raw);
|
|
4187
4438
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
4188
4439
|
}
|
|
@@ -4211,29 +4462,30 @@ var init_plan_limits = __esm({
|
|
|
4211
4462
|
this.name = "PlanLimitError";
|
|
4212
4463
|
}
|
|
4213
4464
|
};
|
|
4214
|
-
CACHE_PATH2 =
|
|
4465
|
+
CACHE_PATH2 = path12.join(EXE_AI_DIR, "license-cache.json");
|
|
4215
4466
|
}
|
|
4216
4467
|
});
|
|
4217
4468
|
|
|
4218
4469
|
// src/lib/notifications.ts
|
|
4219
|
-
import
|
|
4220
|
-
import
|
|
4221
|
-
import
|
|
4470
|
+
import crypto2 from "crypto";
|
|
4471
|
+
import path13 from "path";
|
|
4472
|
+
import os9 from "os";
|
|
4222
4473
|
import {
|
|
4223
|
-
readFileSync as
|
|
4474
|
+
readFileSync as readFileSync10,
|
|
4224
4475
|
readdirSync as readdirSync2,
|
|
4225
4476
|
unlinkSync as unlinkSync3,
|
|
4226
|
-
existsSync as
|
|
4477
|
+
existsSync as existsSync13,
|
|
4227
4478
|
rmdirSync
|
|
4228
4479
|
} from "fs";
|
|
4229
4480
|
async function writeNotification(notification) {
|
|
4230
4481
|
try {
|
|
4231
4482
|
const client = getClient();
|
|
4232
|
-
const id =
|
|
4483
|
+
const id = crypto2.randomUUID();
|
|
4233
4484
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4485
|
+
const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
|
|
4234
4486
|
await client.execute({
|
|
4235
|
-
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
|
|
4236
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
4487
|
+
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
4488
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
4237
4489
|
args: [
|
|
4238
4490
|
id,
|
|
4239
4491
|
notification.agentId,
|
|
@@ -4242,6 +4494,7 @@ async function writeNotification(notification) {
|
|
|
4242
4494
|
notification.project,
|
|
4243
4495
|
notification.summary,
|
|
4244
4496
|
notification.taskFile ?? null,
|
|
4497
|
+
sessionScope,
|
|
4245
4498
|
now
|
|
4246
4499
|
]
|
|
4247
4500
|
});
|
|
@@ -4250,21 +4503,22 @@ async function writeNotification(notification) {
|
|
|
4250
4503
|
`);
|
|
4251
4504
|
}
|
|
4252
4505
|
}
|
|
4253
|
-
async function readUnreadNotifications(agentFilter) {
|
|
4506
|
+
async function readUnreadNotifications(agentFilter, sessionScope) {
|
|
4254
4507
|
try {
|
|
4255
4508
|
const client = getClient();
|
|
4256
4509
|
const conditions = ["read = 0"];
|
|
4257
4510
|
const args = [];
|
|
4511
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
4258
4512
|
if (agentFilter) {
|
|
4259
4513
|
conditions.push("agent_id = ?");
|
|
4260
4514
|
args.push(agentFilter);
|
|
4261
4515
|
}
|
|
4262
4516
|
const result = await client.execute({
|
|
4263
|
-
sql: `SELECT id, agent_id, agent_role, event, project, summary, task_file, created_at
|
|
4517
|
+
sql: `SELECT id, agent_id, agent_role, event, project, summary, task_file, session_scope, created_at
|
|
4264
4518
|
FROM notifications
|
|
4265
|
-
WHERE ${conditions.join(" AND ")}
|
|
4519
|
+
WHERE ${conditions.join(" AND ")}${scope.sql}
|
|
4266
4520
|
ORDER BY created_at ASC`,
|
|
4267
|
-
args
|
|
4521
|
+
args: [...args, ...scope.args]
|
|
4268
4522
|
});
|
|
4269
4523
|
return result.rows.map((r) => ({
|
|
4270
4524
|
id: String(r.id),
|
|
@@ -4274,6 +4528,7 @@ async function readUnreadNotifications(agentFilter) {
|
|
|
4274
4528
|
project: String(r.project),
|
|
4275
4529
|
summary: String(r.summary),
|
|
4276
4530
|
taskFile: r.task_file ? String(r.task_file) : void 0,
|
|
4531
|
+
sessionScope: r.session_scope == null ? null : String(r.session_scope),
|
|
4277
4532
|
timestamp: String(r.created_at),
|
|
4278
4533
|
read: false
|
|
4279
4534
|
}));
|
|
@@ -4281,54 +4536,60 @@ async function readUnreadNotifications(agentFilter) {
|
|
|
4281
4536
|
return [];
|
|
4282
4537
|
}
|
|
4283
4538
|
}
|
|
4284
|
-
async function markAsRead(ids) {
|
|
4539
|
+
async function markAsRead(ids, sessionScope) {
|
|
4285
4540
|
if (ids.length === 0) return;
|
|
4286
4541
|
try {
|
|
4287
4542
|
const client = getClient();
|
|
4288
4543
|
const placeholders = ids.map(() => "?").join(", ");
|
|
4544
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
4289
4545
|
await client.execute({
|
|
4290
|
-
sql: `UPDATE notifications SET read = 1 WHERE id IN (${placeholders})`,
|
|
4291
|
-
args: ids
|
|
4546
|
+
sql: `UPDATE notifications SET read = 1 WHERE id IN (${placeholders})${scope.sql}`,
|
|
4547
|
+
args: [...ids, ...scope.args]
|
|
4292
4548
|
});
|
|
4293
4549
|
} catch {
|
|
4294
4550
|
}
|
|
4295
4551
|
}
|
|
4296
|
-
async function markAsReadByTaskFile(taskFile) {
|
|
4552
|
+
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
4297
4553
|
try {
|
|
4298
4554
|
const client = getClient();
|
|
4555
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
4299
4556
|
await client.execute({
|
|
4300
|
-
sql:
|
|
4301
|
-
|
|
4557
|
+
sql: `UPDATE notifications SET read = 1
|
|
4558
|
+
WHERE task_file = ? AND read = 0${scope.sql}`,
|
|
4559
|
+
args: [taskFile, ...scope.args]
|
|
4302
4560
|
});
|
|
4303
4561
|
} catch {
|
|
4304
4562
|
}
|
|
4305
4563
|
}
|
|
4306
|
-
async function cleanupOldNotifications(daysOld = CLEANUP_DAYS) {
|
|
4564
|
+
async function cleanupOldNotifications(daysOld = CLEANUP_DAYS, sessionScope) {
|
|
4307
4565
|
try {
|
|
4308
4566
|
const client = getClient();
|
|
4309
4567
|
const cutoff = new Date(
|
|
4310
4568
|
Date.now() - daysOld * 24 * 60 * 60 * 1e3
|
|
4311
4569
|
).toISOString();
|
|
4570
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
4312
4571
|
const result = await client.execute({
|
|
4313
|
-
sql:
|
|
4314
|
-
args: [cutoff]
|
|
4572
|
+
sql: `DELETE FROM notifications WHERE created_at < ?${scope.sql}`,
|
|
4573
|
+
args: [cutoff, ...scope.args]
|
|
4315
4574
|
});
|
|
4316
4575
|
return result.rowsAffected;
|
|
4317
4576
|
} catch {
|
|
4318
4577
|
return 0;
|
|
4319
4578
|
}
|
|
4320
4579
|
}
|
|
4321
|
-
async function markDoneTaskNotificationsAsRead() {
|
|
4580
|
+
async function markDoneTaskNotificationsAsRead(sessionScope) {
|
|
4322
4581
|
try {
|
|
4323
4582
|
const client = getClient();
|
|
4583
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
4324
4584
|
const result = await client.execute({
|
|
4325
4585
|
sql: `UPDATE notifications SET read = 1
|
|
4326
4586
|
WHERE read = 0
|
|
4327
4587
|
AND task_file IS NOT NULL
|
|
4588
|
+
${scope.sql}
|
|
4328
4589
|
AND task_file IN (
|
|
4329
|
-
SELECT task_file FROM tasks WHERE status = 'done'
|
|
4590
|
+
SELECT task_file FROM tasks WHERE status = 'done'${scope.sql}
|
|
4330
4591
|
)`,
|
|
4331
|
-
args: []
|
|
4592
|
+
args: [...scope.args, ...scope.args]
|
|
4332
4593
|
});
|
|
4333
4594
|
return result.rowsAffected;
|
|
4334
4595
|
} catch {
|
|
@@ -4336,9 +4597,9 @@ async function markDoneTaskNotificationsAsRead() {
|
|
|
4336
4597
|
}
|
|
4337
4598
|
}
|
|
4338
4599
|
async function migrateJsonNotifications() {
|
|
4339
|
-
const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR ||
|
|
4340
|
-
const notifDir =
|
|
4341
|
-
if (!
|
|
4600
|
+
const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR || path13.join(os9.homedir(), ".exe-os");
|
|
4601
|
+
const notifDir = path13.join(base, "notifications");
|
|
4602
|
+
if (!existsSync13(notifDir)) return 0;
|
|
4342
4603
|
let migrated = 0;
|
|
4343
4604
|
try {
|
|
4344
4605
|
const files = readdirSync2(notifDir).filter((f) => f.endsWith(".json"));
|
|
@@ -4346,19 +4607,20 @@ async function migrateJsonNotifications() {
|
|
|
4346
4607
|
const client = getClient();
|
|
4347
4608
|
for (const file of files) {
|
|
4348
4609
|
try {
|
|
4349
|
-
const filePath =
|
|
4350
|
-
const data = JSON.parse(
|
|
4610
|
+
const filePath = path13.join(notifDir, file);
|
|
4611
|
+
const data = JSON.parse(readFileSync10(filePath, "utf8"));
|
|
4351
4612
|
await client.execute({
|
|
4352
|
-
sql: `INSERT OR IGNORE INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
|
|
4353
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
4613
|
+
sql: `INSERT OR IGNORE INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
4614
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
4354
4615
|
args: [
|
|
4355
|
-
|
|
4616
|
+
crypto2.randomUUID(),
|
|
4356
4617
|
data.agentId ?? "unknown",
|
|
4357
4618
|
data.agentRole ?? "unknown",
|
|
4358
4619
|
data.event ?? "session_summary",
|
|
4359
4620
|
data.project ?? "unknown",
|
|
4360
4621
|
data.summary ?? "",
|
|
4361
4622
|
data.taskFile ?? null,
|
|
4623
|
+
null,
|
|
4362
4624
|
data.read ? 1 : 0,
|
|
4363
4625
|
data.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
4364
4626
|
]
|
|
@@ -4384,6 +4646,7 @@ var init_notifications = __esm({
|
|
|
4384
4646
|
"src/lib/notifications.ts"() {
|
|
4385
4647
|
"use strict";
|
|
4386
4648
|
init_database();
|
|
4649
|
+
init_task_scope();
|
|
4387
4650
|
CLEANUP_DAYS = 7;
|
|
4388
4651
|
}
|
|
4389
4652
|
});
|
|
@@ -4401,7 +4664,7 @@ __export(session_kill_telemetry_exports, {
|
|
|
4401
4664
|
recordSessionKill: () => recordSessionKill,
|
|
4402
4665
|
sumTokensSavedSince: () => sumTokensSavedSince
|
|
4403
4666
|
});
|
|
4404
|
-
import
|
|
4667
|
+
import crypto3 from "crypto";
|
|
4405
4668
|
async function recordSessionKill(input) {
|
|
4406
4669
|
try {
|
|
4407
4670
|
const client = getClient();
|
|
@@ -4411,7 +4674,7 @@ async function recordSessionKill(input) {
|
|
|
4411
4674
|
ticks_idle, estimated_tokens_saved)
|
|
4412
4675
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
4413
4676
|
args: [
|
|
4414
|
-
|
|
4677
|
+
crypto3.randomUUID(),
|
|
4415
4678
|
input.sessionName,
|
|
4416
4679
|
input.agentId,
|
|
4417
4680
|
(/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -4492,13 +4755,126 @@ var init_session_kill_telemetry = __esm({
|
|
|
4492
4755
|
}
|
|
4493
4756
|
});
|
|
4494
4757
|
|
|
4495
|
-
// src/lib/
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4758
|
+
// src/lib/project-name.ts
|
|
4759
|
+
var project_name_exports = {};
|
|
4760
|
+
__export(project_name_exports, {
|
|
4761
|
+
_resetCache: () => _resetCache,
|
|
4762
|
+
getProjectName: () => getProjectName
|
|
4763
|
+
});
|
|
4499
4764
|
import { execSync as execSync5 } from "child_process";
|
|
4765
|
+
import path14 from "path";
|
|
4766
|
+
function getProjectName(cwd) {
|
|
4767
|
+
const dir = cwd ?? process.cwd();
|
|
4768
|
+
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
4769
|
+
try {
|
|
4770
|
+
let repoRoot;
|
|
4771
|
+
try {
|
|
4772
|
+
const gitCommonDir = execSync5("git rev-parse --path-format=absolute --git-common-dir", {
|
|
4773
|
+
cwd: dir,
|
|
4774
|
+
encoding: "utf8",
|
|
4775
|
+
timeout: 2e3,
|
|
4776
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4777
|
+
}).trim();
|
|
4778
|
+
repoRoot = path14.dirname(gitCommonDir);
|
|
4779
|
+
} catch {
|
|
4780
|
+
repoRoot = execSync5("git rev-parse --show-toplevel", {
|
|
4781
|
+
cwd: dir,
|
|
4782
|
+
encoding: "utf8",
|
|
4783
|
+
timeout: 2e3,
|
|
4784
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4785
|
+
}).trim();
|
|
4786
|
+
}
|
|
4787
|
+
_cached2 = path14.basename(repoRoot);
|
|
4788
|
+
_cachedCwd = dir;
|
|
4789
|
+
return _cached2;
|
|
4790
|
+
} catch {
|
|
4791
|
+
_cached2 = path14.basename(dir);
|
|
4792
|
+
_cachedCwd = dir;
|
|
4793
|
+
return _cached2;
|
|
4794
|
+
}
|
|
4795
|
+
}
|
|
4796
|
+
function _resetCache() {
|
|
4797
|
+
_cached2 = null;
|
|
4798
|
+
_cachedCwd = null;
|
|
4799
|
+
}
|
|
4800
|
+
var _cached2, _cachedCwd;
|
|
4801
|
+
var init_project_name = __esm({
|
|
4802
|
+
"src/lib/project-name.ts"() {
|
|
4803
|
+
"use strict";
|
|
4804
|
+
_cached2 = null;
|
|
4805
|
+
_cachedCwd = null;
|
|
4806
|
+
}
|
|
4807
|
+
});
|
|
4808
|
+
|
|
4809
|
+
// src/lib/session-scope.ts
|
|
4810
|
+
var session_scope_exports = {};
|
|
4811
|
+
__export(session_scope_exports, {
|
|
4812
|
+
assertSessionScope: () => assertSessionScope,
|
|
4813
|
+
findSessionForProject: () => findSessionForProject,
|
|
4814
|
+
getSessionProject: () => getSessionProject
|
|
4815
|
+
});
|
|
4816
|
+
function getSessionProject(sessionName) {
|
|
4817
|
+
const sessions = listSessions();
|
|
4818
|
+
const entry = sessions.find((s) => s.windowName === sessionName);
|
|
4819
|
+
if (!entry) return null;
|
|
4820
|
+
const parts = entry.projectDir.split("/").filter(Boolean);
|
|
4821
|
+
return parts[parts.length - 1] ?? null;
|
|
4822
|
+
}
|
|
4823
|
+
function findSessionForProject(projectName) {
|
|
4824
|
+
const sessions = listSessions();
|
|
4825
|
+
for (const s of sessions) {
|
|
4826
|
+
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
4827
|
+
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
4828
|
+
}
|
|
4829
|
+
return null;
|
|
4830
|
+
}
|
|
4831
|
+
function assertSessionScope(actionType, targetProject) {
|
|
4832
|
+
try {
|
|
4833
|
+
const currentProject = getProjectName();
|
|
4834
|
+
const exeSession = resolveExeSession();
|
|
4835
|
+
if (!exeSession) {
|
|
4836
|
+
return { allowed: true, reason: "no_session" };
|
|
4837
|
+
}
|
|
4838
|
+
if (currentProject === targetProject) {
|
|
4839
|
+
return {
|
|
4840
|
+
allowed: true,
|
|
4841
|
+
reason: "same_session",
|
|
4842
|
+
currentProject,
|
|
4843
|
+
targetProject
|
|
4844
|
+
};
|
|
4845
|
+
}
|
|
4846
|
+
process.stderr.write(
|
|
4847
|
+
`[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
|
|
4848
|
+
`
|
|
4849
|
+
);
|
|
4850
|
+
return {
|
|
4851
|
+
allowed: false,
|
|
4852
|
+
reason: "cross_session_denied",
|
|
4853
|
+
currentProject,
|
|
4854
|
+
targetProject,
|
|
4855
|
+
targetSession: findSessionForProject(targetProject)?.windowName
|
|
4856
|
+
};
|
|
4857
|
+
} catch {
|
|
4858
|
+
return { allowed: true, reason: "no_session" };
|
|
4859
|
+
}
|
|
4860
|
+
}
|
|
4861
|
+
var init_session_scope = __esm({
|
|
4862
|
+
"src/lib/session-scope.ts"() {
|
|
4863
|
+
"use strict";
|
|
4864
|
+
init_session_registry();
|
|
4865
|
+
init_project_name();
|
|
4866
|
+
init_tmux_routing();
|
|
4867
|
+
init_employees();
|
|
4868
|
+
}
|
|
4869
|
+
});
|
|
4870
|
+
|
|
4871
|
+
// src/lib/tasks-crud.ts
|
|
4872
|
+
import crypto4 from "crypto";
|
|
4873
|
+
import path15 from "path";
|
|
4874
|
+
import os10 from "os";
|
|
4875
|
+
import { execSync as execSync6 } from "child_process";
|
|
4500
4876
|
import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
|
|
4501
|
-
import { existsSync as
|
|
4877
|
+
import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
|
|
4502
4878
|
async function writeCheckpoint(input) {
|
|
4503
4879
|
const client = getClient();
|
|
4504
4880
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -4614,13 +4990,28 @@ async function resolveTask(client, identifier, scopeSession) {
|
|
|
4614
4990
|
}
|
|
4615
4991
|
async function createTaskCore(input) {
|
|
4616
4992
|
const client = getClient();
|
|
4617
|
-
const id =
|
|
4993
|
+
const id = crypto4.randomUUID();
|
|
4618
4994
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4619
4995
|
const slug = slugify(input.title);
|
|
4620
4996
|
let earlySessionScope = null;
|
|
4997
|
+
let scopeMismatchWarning;
|
|
4621
4998
|
try {
|
|
4622
4999
|
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
4623
|
-
|
|
5000
|
+
const resolved = resolveExeSession2();
|
|
5001
|
+
if (resolved && input.projectName) {
|
|
5002
|
+
const { getSessionProject: getSessionProject2 } = await Promise.resolve().then(() => (init_session_scope(), session_scope_exports));
|
|
5003
|
+
const sessionProject = getSessionProject2(resolved);
|
|
5004
|
+
if (sessionProject && sessionProject !== input.projectName) {
|
|
5005
|
+
scopeMismatchWarning = `session/project mismatch: session "${resolved}" owns "${sessionProject}" but task targets "${input.projectName}". Routed to default scope.`;
|
|
5006
|
+
process.stderr.write(`[create_task] ${scopeMismatchWarning}
|
|
5007
|
+
`);
|
|
5008
|
+
earlySessionScope = null;
|
|
5009
|
+
} else {
|
|
5010
|
+
earlySessionScope = resolved;
|
|
5011
|
+
}
|
|
5012
|
+
} else {
|
|
5013
|
+
earlySessionScope = resolved;
|
|
5014
|
+
}
|
|
4624
5015
|
} catch {
|
|
4625
5016
|
}
|
|
4626
5017
|
const scope = earlySessionScope ?? "default";
|
|
@@ -4671,10 +5062,14 @@ async function createTaskCore(input) {
|
|
|
4671
5062
|
${laneWarning}` : laneWarning;
|
|
4672
5063
|
}
|
|
4673
5064
|
}
|
|
5065
|
+
if (scopeMismatchWarning) {
|
|
5066
|
+
warning = warning ? `${warning}
|
|
5067
|
+
${scopeMismatchWarning}` : scopeMismatchWarning;
|
|
5068
|
+
}
|
|
4674
5069
|
if (input.baseDir) {
|
|
4675
5070
|
try {
|
|
4676
|
-
await mkdir4(
|
|
4677
|
-
await mkdir4(
|
|
5071
|
+
await mkdir4(path15.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
5072
|
+
await mkdir4(path15.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
4678
5073
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
4679
5074
|
await ensureGitignoreExe(input.baseDir);
|
|
4680
5075
|
} catch {
|
|
@@ -4710,13 +5105,19 @@ ${laneWarning}` : laneWarning;
|
|
|
4710
5105
|
});
|
|
4711
5106
|
if (input.baseDir) {
|
|
4712
5107
|
try {
|
|
4713
|
-
const EXE_OS_DIR =
|
|
4714
|
-
const mdPath =
|
|
4715
|
-
const mdDir =
|
|
4716
|
-
if (!
|
|
5108
|
+
const EXE_OS_DIR = path15.join(os10.homedir(), ".exe-os");
|
|
5109
|
+
const mdPath = path15.join(EXE_OS_DIR, taskFile);
|
|
5110
|
+
const mdDir = path15.dirname(mdPath);
|
|
5111
|
+
if (!existsSync14(mdDir)) await mkdir4(mdDir, { recursive: true });
|
|
4717
5112
|
const reviewer = input.reviewer ?? input.assignedBy;
|
|
4718
5113
|
const mdContent = `# ${input.title}
|
|
4719
5114
|
|
|
5115
|
+
## MANDATORY: When done
|
|
5116
|
+
|
|
5117
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
5118
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
5119
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
5120
|
+
|
|
4720
5121
|
**ID:** ${id}
|
|
4721
5122
|
**Status:** ${initialStatus}
|
|
4722
5123
|
**Priority:** ${input.priority}
|
|
@@ -4730,12 +5131,6 @@ ${laneWarning}` : laneWarning;
|
|
|
4730
5131
|
## Context
|
|
4731
5132
|
|
|
4732
5133
|
${input.context}
|
|
4733
|
-
|
|
4734
|
-
## MANDATORY: When done
|
|
4735
|
-
|
|
4736
|
-
You MUST call update_task with status "done" and a result summary when finished.
|
|
4737
|
-
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
4738
|
-
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
4739
5134
|
`;
|
|
4740
5135
|
await writeFile4(mdPath, mdContent, "utf-8");
|
|
4741
5136
|
} catch (err) {
|
|
@@ -4817,14 +5212,14 @@ function isTmuxSessionAlive(identifier) {
|
|
|
4817
5212
|
if (!identifier || identifier === "unknown") return true;
|
|
4818
5213
|
try {
|
|
4819
5214
|
if (identifier.startsWith("%")) {
|
|
4820
|
-
const output =
|
|
5215
|
+
const output = execSync6("tmux list-panes -a -F '#{pane_id}'", {
|
|
4821
5216
|
timeout: 2e3,
|
|
4822
5217
|
encoding: "utf8",
|
|
4823
5218
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4824
5219
|
});
|
|
4825
5220
|
return output.split("\n").some((l) => l.trim() === identifier);
|
|
4826
5221
|
} else {
|
|
4827
|
-
|
|
5222
|
+
execSync6(`tmux has-session -t ${JSON.stringify(identifier)}`, {
|
|
4828
5223
|
timeout: 2e3,
|
|
4829
5224
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4830
5225
|
});
|
|
@@ -4833,7 +5228,7 @@ function isTmuxSessionAlive(identifier) {
|
|
|
4833
5228
|
} catch {
|
|
4834
5229
|
if (identifier.startsWith("%")) return true;
|
|
4835
5230
|
try {
|
|
4836
|
-
|
|
5231
|
+
execSync6("tmux list-sessions", {
|
|
4837
5232
|
timeout: 2e3,
|
|
4838
5233
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4839
5234
|
});
|
|
@@ -4848,12 +5243,12 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
|
|
|
4848
5243
|
if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
|
|
4849
5244
|
try {
|
|
4850
5245
|
const since = new Date(taskCreatedAt).toISOString();
|
|
4851
|
-
const branch =
|
|
5246
|
+
const branch = execSync6(
|
|
4852
5247
|
"git rev-parse --abbrev-ref HEAD 2>/dev/null",
|
|
4853
5248
|
{ encoding: "utf8", timeout: 3e3 }
|
|
4854
5249
|
).trim();
|
|
4855
5250
|
const branchArg = branch && branch !== "HEAD" ? branch : "";
|
|
4856
|
-
const commitCount =
|
|
5251
|
+
const commitCount = execSync6(
|
|
4857
5252
|
`git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
|
|
4858
5253
|
{ encoding: "utf8", timeout: 5e3 }
|
|
4859
5254
|
).trim();
|
|
@@ -4984,7 +5379,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
4984
5379
|
await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
|
|
4985
5380
|
} catch {
|
|
4986
5381
|
}
|
|
4987
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
5382
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
4988
5383
|
try {
|
|
4989
5384
|
const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
4990
5385
|
clearQueueForAgent2(String(row.assigned_to));
|
|
@@ -5013,9 +5408,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
5013
5408
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
5014
5409
|
}
|
|
5015
5410
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
5016
|
-
const archPath =
|
|
5411
|
+
const archPath = path15.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
5017
5412
|
try {
|
|
5018
|
-
if (
|
|
5413
|
+
if (existsSync14(archPath)) return;
|
|
5019
5414
|
const template = [
|
|
5020
5415
|
`# ${projectName} \u2014 System Architecture`,
|
|
5021
5416
|
"",
|
|
@@ -5048,10 +5443,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
5048
5443
|
}
|
|
5049
5444
|
}
|
|
5050
5445
|
async function ensureGitignoreExe(baseDir) {
|
|
5051
|
-
const gitignorePath =
|
|
5446
|
+
const gitignorePath = path15.join(baseDir, ".gitignore");
|
|
5052
5447
|
try {
|
|
5053
|
-
if (
|
|
5054
|
-
const content =
|
|
5448
|
+
if (existsSync14(gitignorePath)) {
|
|
5449
|
+
const content = readFileSync11(gitignorePath, "utf-8");
|
|
5055
5450
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
5056
5451
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
5057
5452
|
} else {
|
|
@@ -5082,58 +5477,42 @@ var init_tasks_crud = __esm({
|
|
|
5082
5477
|
});
|
|
5083
5478
|
|
|
5084
5479
|
// src/lib/tasks-review.ts
|
|
5085
|
-
import
|
|
5086
|
-
import { existsSync as
|
|
5480
|
+
import path16 from "path";
|
|
5481
|
+
import { existsSync as existsSync15, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
|
|
5087
5482
|
async function countPendingReviews(sessionScope) {
|
|
5088
5483
|
const client = getClient();
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
args: [sessionScope]
|
|
5093
|
-
});
|
|
5094
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
5095
|
-
}
|
|
5484
|
+
const scope = strictSessionScopeFilter(
|
|
5485
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
5486
|
+
);
|
|
5096
5487
|
const result = await client.execute({
|
|
5097
|
-
sql:
|
|
5098
|
-
|
|
5488
|
+
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
5489
|
+
WHERE status = 'needs_review'${scope.sql}`,
|
|
5490
|
+
args: [...scope.args]
|
|
5099
5491
|
});
|
|
5100
5492
|
return Number(result.rows[0]?.cnt) || 0;
|
|
5101
5493
|
}
|
|
5102
5494
|
async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
5103
5495
|
const client = getClient();
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
WHERE status = 'needs_review' AND updated_at > ?
|
|
5108
|
-
AND session_scope = ?`,
|
|
5109
|
-
args: [sinceIso, sessionScope]
|
|
5110
|
-
});
|
|
5111
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
5112
|
-
}
|
|
5496
|
+
const scope = strictSessionScopeFilter(
|
|
5497
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
5498
|
+
);
|
|
5113
5499
|
const result = await client.execute({
|
|
5114
5500
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
5115
|
-
WHERE status = 'needs_review' AND updated_at >
|
|
5116
|
-
args: [sinceIso]
|
|
5501
|
+
WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
|
|
5502
|
+
args: [sinceIso, ...scope.args]
|
|
5117
5503
|
});
|
|
5118
5504
|
return Number(result.rows[0]?.cnt) || 0;
|
|
5119
5505
|
}
|
|
5120
5506
|
async function listPendingReviews(limit, sessionScope) {
|
|
5121
5507
|
const client = getClient();
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
WHERE status = 'needs_review'
|
|
5126
|
-
AND session_scope = ?
|
|
5127
|
-
ORDER BY updated_at ASC LIMIT ?`,
|
|
5128
|
-
args: [sessionScope, limit]
|
|
5129
|
-
});
|
|
5130
|
-
return result2.rows;
|
|
5131
|
-
}
|
|
5508
|
+
const scope = strictSessionScopeFilter(
|
|
5509
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
5510
|
+
);
|
|
5132
5511
|
const result = await client.execute({
|
|
5133
5512
|
sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
|
|
5134
|
-
WHERE status = 'needs_review'
|
|
5513
|
+
WHERE status = 'needs_review'${scope.sql}
|
|
5135
5514
|
ORDER BY updated_at ASC LIMIT ?`,
|
|
5136
|
-
args: [limit]
|
|
5515
|
+
args: [...scope.args, limit]
|
|
5137
5516
|
});
|
|
5138
5517
|
return result.rows;
|
|
5139
5518
|
}
|
|
@@ -5145,7 +5524,7 @@ async function cleanupOrphanedReviews() {
|
|
|
5145
5524
|
WHERE status IN ('open', 'needs_review', 'in_progress')
|
|
5146
5525
|
AND assigned_by = 'system'
|
|
5147
5526
|
AND title LIKE 'Review:%'
|
|
5148
|
-
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
|
|
5527
|
+
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
|
|
5149
5528
|
args: [now]
|
|
5150
5529
|
});
|
|
5151
5530
|
const r1b = await client.execute({
|
|
@@ -5264,11 +5643,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
5264
5643
|
);
|
|
5265
5644
|
}
|
|
5266
5645
|
try {
|
|
5267
|
-
const cacheDir =
|
|
5268
|
-
if (
|
|
5646
|
+
const cacheDir = path16.join(EXE_AI_DIR, "session-cache");
|
|
5647
|
+
if (existsSync15(cacheDir)) {
|
|
5269
5648
|
for (const f of readdirSync3(cacheDir)) {
|
|
5270
5649
|
if (f.startsWith("review-notified-")) {
|
|
5271
|
-
unlinkSync4(
|
|
5650
|
+
unlinkSync4(path16.join(cacheDir, f));
|
|
5272
5651
|
}
|
|
5273
5652
|
}
|
|
5274
5653
|
}
|
|
@@ -5285,11 +5664,12 @@ var init_tasks_review = __esm({
|
|
|
5285
5664
|
init_tmux_routing();
|
|
5286
5665
|
init_session_key();
|
|
5287
5666
|
init_state_bus();
|
|
5667
|
+
init_task_scope();
|
|
5288
5668
|
}
|
|
5289
5669
|
});
|
|
5290
5670
|
|
|
5291
5671
|
// src/lib/tasks-chain.ts
|
|
5292
|
-
import
|
|
5672
|
+
import path17 from "path";
|
|
5293
5673
|
import { readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
|
|
5294
5674
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
5295
5675
|
const client = getClient();
|
|
@@ -5306,7 +5686,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
5306
5686
|
});
|
|
5307
5687
|
for (const ur of unblockedRows.rows) {
|
|
5308
5688
|
try {
|
|
5309
|
-
const ubFile =
|
|
5689
|
+
const ubFile = path17.join(baseDir, String(ur.task_file));
|
|
5310
5690
|
let ubContent = await readFile4(ubFile, "utf-8");
|
|
5311
5691
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
5312
5692
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -5341,7 +5721,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
|
|
|
5341
5721
|
const scScope = sessionScopeFilter();
|
|
5342
5722
|
const remaining = await client.execute({
|
|
5343
5723
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
5344
|
-
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
|
|
5724
|
+
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
|
|
5345
5725
|
args: [parentTaskId, ...scScope.args]
|
|
5346
5726
|
});
|
|
5347
5727
|
const cnt = Number(remaining.rows[0]?.cnt ?? 1);
|
|
@@ -5373,119 +5753,6 @@ var init_tasks_chain = __esm({
|
|
|
5373
5753
|
}
|
|
5374
5754
|
});
|
|
5375
5755
|
|
|
5376
|
-
// src/lib/project-name.ts
|
|
5377
|
-
var project_name_exports = {};
|
|
5378
|
-
__export(project_name_exports, {
|
|
5379
|
-
_resetCache: () => _resetCache,
|
|
5380
|
-
getProjectName: () => getProjectName
|
|
5381
|
-
});
|
|
5382
|
-
import { execSync as execSync6 } from "child_process";
|
|
5383
|
-
import path16 from "path";
|
|
5384
|
-
function getProjectName(cwd) {
|
|
5385
|
-
const dir = cwd ?? process.cwd();
|
|
5386
|
-
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
5387
|
-
try {
|
|
5388
|
-
let repoRoot;
|
|
5389
|
-
try {
|
|
5390
|
-
const gitCommonDir = execSync6("git rev-parse --path-format=absolute --git-common-dir", {
|
|
5391
|
-
cwd: dir,
|
|
5392
|
-
encoding: "utf8",
|
|
5393
|
-
timeout: 2e3,
|
|
5394
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
5395
|
-
}).trim();
|
|
5396
|
-
repoRoot = path16.dirname(gitCommonDir);
|
|
5397
|
-
} catch {
|
|
5398
|
-
repoRoot = execSync6("git rev-parse --show-toplevel", {
|
|
5399
|
-
cwd: dir,
|
|
5400
|
-
encoding: "utf8",
|
|
5401
|
-
timeout: 2e3,
|
|
5402
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
5403
|
-
}).trim();
|
|
5404
|
-
}
|
|
5405
|
-
_cached2 = path16.basename(repoRoot);
|
|
5406
|
-
_cachedCwd = dir;
|
|
5407
|
-
return _cached2;
|
|
5408
|
-
} catch {
|
|
5409
|
-
_cached2 = path16.basename(dir);
|
|
5410
|
-
_cachedCwd = dir;
|
|
5411
|
-
return _cached2;
|
|
5412
|
-
}
|
|
5413
|
-
}
|
|
5414
|
-
function _resetCache() {
|
|
5415
|
-
_cached2 = null;
|
|
5416
|
-
_cachedCwd = null;
|
|
5417
|
-
}
|
|
5418
|
-
var _cached2, _cachedCwd;
|
|
5419
|
-
var init_project_name = __esm({
|
|
5420
|
-
"src/lib/project-name.ts"() {
|
|
5421
|
-
"use strict";
|
|
5422
|
-
_cached2 = null;
|
|
5423
|
-
_cachedCwd = null;
|
|
5424
|
-
}
|
|
5425
|
-
});
|
|
5426
|
-
|
|
5427
|
-
// src/lib/session-scope.ts
|
|
5428
|
-
var session_scope_exports = {};
|
|
5429
|
-
__export(session_scope_exports, {
|
|
5430
|
-
assertSessionScope: () => assertSessionScope,
|
|
5431
|
-
findSessionForProject: () => findSessionForProject,
|
|
5432
|
-
getSessionProject: () => getSessionProject
|
|
5433
|
-
});
|
|
5434
|
-
function getSessionProject(sessionName) {
|
|
5435
|
-
const sessions = listSessions();
|
|
5436
|
-
const entry = sessions.find((s) => s.windowName === sessionName);
|
|
5437
|
-
if (!entry) return null;
|
|
5438
|
-
const parts = entry.projectDir.split("/").filter(Boolean);
|
|
5439
|
-
return parts[parts.length - 1] ?? null;
|
|
5440
|
-
}
|
|
5441
|
-
function findSessionForProject(projectName) {
|
|
5442
|
-
const sessions = listSessions();
|
|
5443
|
-
for (const s of sessions) {
|
|
5444
|
-
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
5445
|
-
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
5446
|
-
}
|
|
5447
|
-
return null;
|
|
5448
|
-
}
|
|
5449
|
-
function assertSessionScope(actionType, targetProject) {
|
|
5450
|
-
try {
|
|
5451
|
-
const currentProject = getProjectName();
|
|
5452
|
-
const exeSession = resolveExeSession();
|
|
5453
|
-
if (!exeSession) {
|
|
5454
|
-
return { allowed: true, reason: "no_session" };
|
|
5455
|
-
}
|
|
5456
|
-
if (currentProject === targetProject) {
|
|
5457
|
-
return {
|
|
5458
|
-
allowed: true,
|
|
5459
|
-
reason: "same_session",
|
|
5460
|
-
currentProject,
|
|
5461
|
-
targetProject
|
|
5462
|
-
};
|
|
5463
|
-
}
|
|
5464
|
-
process.stderr.write(
|
|
5465
|
-
`[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
|
|
5466
|
-
`
|
|
5467
|
-
);
|
|
5468
|
-
return {
|
|
5469
|
-
allowed: false,
|
|
5470
|
-
reason: "cross_session_denied",
|
|
5471
|
-
currentProject,
|
|
5472
|
-
targetProject,
|
|
5473
|
-
targetSession: findSessionForProject(targetProject)?.windowName
|
|
5474
|
-
};
|
|
5475
|
-
} catch {
|
|
5476
|
-
return { allowed: true, reason: "no_session" };
|
|
5477
|
-
}
|
|
5478
|
-
}
|
|
5479
|
-
var init_session_scope = __esm({
|
|
5480
|
-
"src/lib/session-scope.ts"() {
|
|
5481
|
-
"use strict";
|
|
5482
|
-
init_session_registry();
|
|
5483
|
-
init_project_name();
|
|
5484
|
-
init_tmux_routing();
|
|
5485
|
-
init_employees();
|
|
5486
|
-
}
|
|
5487
|
-
});
|
|
5488
|
-
|
|
5489
5756
|
// src/lib/tasks-notify.ts
|
|
5490
5757
|
async function dispatchTaskToEmployee(input) {
|
|
5491
5758
|
if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
|
|
@@ -5553,10 +5820,10 @@ var init_tasks_notify = __esm({
|
|
|
5553
5820
|
});
|
|
5554
5821
|
|
|
5555
5822
|
// src/lib/behaviors.ts
|
|
5556
|
-
import
|
|
5823
|
+
import crypto5 from "crypto";
|
|
5557
5824
|
async function storeBehavior(opts) {
|
|
5558
5825
|
const client = getClient();
|
|
5559
|
-
const id =
|
|
5826
|
+
const id = crypto5.randomUUID();
|
|
5560
5827
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5561
5828
|
await client.execute({
|
|
5562
5829
|
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
|
|
@@ -5585,7 +5852,7 @@ __export(skill_learning_exports, {
|
|
|
5585
5852
|
storeTrajectory: () => storeTrajectory,
|
|
5586
5853
|
sweepTrajectories: () => sweepTrajectories
|
|
5587
5854
|
});
|
|
5588
|
-
import
|
|
5855
|
+
import crypto6 from "crypto";
|
|
5589
5856
|
async function extractTrajectory(taskId, agentId) {
|
|
5590
5857
|
const client = getClient();
|
|
5591
5858
|
const result = await client.execute({
|
|
@@ -5614,11 +5881,11 @@ async function extractTrajectory(taskId, agentId) {
|
|
|
5614
5881
|
return signature;
|
|
5615
5882
|
}
|
|
5616
5883
|
function hashSignature(signature) {
|
|
5617
|
-
return
|
|
5884
|
+
return crypto6.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
|
|
5618
5885
|
}
|
|
5619
5886
|
async function storeTrajectory(opts) {
|
|
5620
5887
|
const client = getClient();
|
|
5621
|
-
const id =
|
|
5888
|
+
const id = crypto6.randomUUID();
|
|
5622
5889
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5623
5890
|
const signatureHash = hashSignature(opts.signature);
|
|
5624
5891
|
await client.execute({
|
|
@@ -5883,8 +6150,8 @@ __export(tasks_exports, {
|
|
|
5883
6150
|
updateTaskStatus: () => updateTaskStatus,
|
|
5884
6151
|
writeCheckpoint: () => writeCheckpoint
|
|
5885
6152
|
});
|
|
5886
|
-
import
|
|
5887
|
-
import { writeFileSync as
|
|
6153
|
+
import path18 from "path";
|
|
6154
|
+
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, unlinkSync as unlinkSync5 } from "fs";
|
|
5888
6155
|
async function createTask(input) {
|
|
5889
6156
|
const result = await createTaskCore(input);
|
|
5890
6157
|
if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -5903,12 +6170,12 @@ async function updateTask(input) {
|
|
|
5903
6170
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
5904
6171
|
try {
|
|
5905
6172
|
const agent = String(row.assigned_to);
|
|
5906
|
-
const cacheDir =
|
|
5907
|
-
const cachePath =
|
|
6173
|
+
const cacheDir = path18.join(EXE_AI_DIR, "session-cache");
|
|
6174
|
+
const cachePath = path18.join(cacheDir, `current-task-${agent}.json`);
|
|
5908
6175
|
if (input.status === "in_progress") {
|
|
5909
6176
|
mkdirSync6(cacheDir, { recursive: true });
|
|
5910
|
-
|
|
5911
|
-
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
6177
|
+
writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
6178
|
+
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
|
|
5912
6179
|
try {
|
|
5913
6180
|
unlinkSync5(cachePath);
|
|
5914
6181
|
} catch {
|
|
@@ -5916,10 +6183,10 @@ async function updateTask(input) {
|
|
|
5916
6183
|
}
|
|
5917
6184
|
} catch {
|
|
5918
6185
|
}
|
|
5919
|
-
if (input.status === "done") {
|
|
6186
|
+
if (input.status === "done" || input.status === "closed") {
|
|
5920
6187
|
await cleanupReviewFile(row, taskFile, input.baseDir);
|
|
5921
6188
|
}
|
|
5922
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
6189
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
5923
6190
|
try {
|
|
5924
6191
|
const client = getClient();
|
|
5925
6192
|
const taskTitle = String(row.title);
|
|
@@ -5935,7 +6202,7 @@ async function updateTask(input) {
|
|
|
5935
6202
|
if (!isCoordinatorName(assignedAgent)) {
|
|
5936
6203
|
try {
|
|
5937
6204
|
const draftClient = getClient();
|
|
5938
|
-
if (input.status === "done") {
|
|
6205
|
+
if (input.status === "done" || input.status === "closed") {
|
|
5939
6206
|
await draftClient.execute({
|
|
5940
6207
|
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
5941
6208
|
args: [assignedAgent]
|
|
@@ -5952,7 +6219,7 @@ async function updateTask(input) {
|
|
|
5952
6219
|
try {
|
|
5953
6220
|
const client = getClient();
|
|
5954
6221
|
const cascaded = await client.execute({
|
|
5955
|
-
sql: `UPDATE tasks SET status = '
|
|
6222
|
+
sql: `UPDATE tasks SET status = 'closed', updated_at = ?
|
|
5956
6223
|
WHERE parent_task_id = ? AND status = 'needs_review'`,
|
|
5957
6224
|
args: [now, taskId]
|
|
5958
6225
|
});
|
|
@@ -5965,14 +6232,14 @@ async function updateTask(input) {
|
|
|
5965
6232
|
} catch {
|
|
5966
6233
|
}
|
|
5967
6234
|
}
|
|
5968
|
-
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
6235
|
+
const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
|
|
5969
6236
|
if (isTerminal) {
|
|
5970
6237
|
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
5971
6238
|
if (!isCoordinator) {
|
|
5972
6239
|
notifyTaskDone();
|
|
5973
6240
|
}
|
|
5974
6241
|
await markTaskNotificationsRead(taskFile);
|
|
5975
|
-
if (input.status === "done") {
|
|
6242
|
+
if (input.status === "done" || input.status === "closed") {
|
|
5976
6243
|
try {
|
|
5977
6244
|
await cascadeUnblock(taskId, input.baseDir, now);
|
|
5978
6245
|
} catch {
|
|
@@ -5992,7 +6259,7 @@ async function updateTask(input) {
|
|
|
5992
6259
|
}
|
|
5993
6260
|
}
|
|
5994
6261
|
}
|
|
5995
|
-
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
6262
|
+
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
5996
6263
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
5997
6264
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
5998
6265
|
taskId,
|
|
@@ -6364,6 +6631,7 @@ __export(tmux_routing_exports, {
|
|
|
6364
6631
|
isEmployeeAlive: () => isEmployeeAlive,
|
|
6365
6632
|
isExeSession: () => isExeSession,
|
|
6366
6633
|
isSessionBusy: () => isSessionBusy,
|
|
6634
|
+
notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
|
|
6367
6635
|
notifyParentExe: () => notifyParentExe,
|
|
6368
6636
|
parseParentExe: () => parseParentExe,
|
|
6369
6637
|
registerParentExe: () => registerParentExe,
|
|
@@ -6374,13 +6642,13 @@ __export(tmux_routing_exports, {
|
|
|
6374
6642
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
6375
6643
|
});
|
|
6376
6644
|
import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
|
|
6377
|
-
import { readFileSync as
|
|
6378
|
-
import
|
|
6379
|
-
import
|
|
6645
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync7, existsSync as existsSync16, appendFileSync, readdirSync as readdirSync4 } from "fs";
|
|
6646
|
+
import path19 from "path";
|
|
6647
|
+
import os11 from "os";
|
|
6380
6648
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6381
6649
|
import { unlinkSync as unlinkSync6 } from "fs";
|
|
6382
6650
|
function spawnLockPath(sessionName) {
|
|
6383
|
-
return
|
|
6651
|
+
return path19.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
6384
6652
|
}
|
|
6385
6653
|
function isProcessAlive(pid) {
|
|
6386
6654
|
try {
|
|
@@ -6391,13 +6659,13 @@ function isProcessAlive(pid) {
|
|
|
6391
6659
|
}
|
|
6392
6660
|
}
|
|
6393
6661
|
function acquireSpawnLock2(sessionName) {
|
|
6394
|
-
if (!
|
|
6662
|
+
if (!existsSync16(SPAWN_LOCK_DIR)) {
|
|
6395
6663
|
mkdirSync7(SPAWN_LOCK_DIR, { recursive: true });
|
|
6396
6664
|
}
|
|
6397
6665
|
const lockFile = spawnLockPath(sessionName);
|
|
6398
|
-
if (
|
|
6666
|
+
if (existsSync16(lockFile)) {
|
|
6399
6667
|
try {
|
|
6400
|
-
const lock = JSON.parse(
|
|
6668
|
+
const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
|
|
6401
6669
|
const age = Date.now() - lock.timestamp;
|
|
6402
6670
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
6403
6671
|
return false;
|
|
@@ -6405,7 +6673,7 @@ function acquireSpawnLock2(sessionName) {
|
|
|
6405
6673
|
} catch {
|
|
6406
6674
|
}
|
|
6407
6675
|
}
|
|
6408
|
-
|
|
6676
|
+
writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
6409
6677
|
return true;
|
|
6410
6678
|
}
|
|
6411
6679
|
function releaseSpawnLock2(sessionName) {
|
|
@@ -6417,13 +6685,13 @@ function releaseSpawnLock2(sessionName) {
|
|
|
6417
6685
|
function resolveBehaviorsExporterScript() {
|
|
6418
6686
|
try {
|
|
6419
6687
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
6420
|
-
const scriptPath =
|
|
6421
|
-
|
|
6688
|
+
const scriptPath = path19.join(
|
|
6689
|
+
path19.dirname(thisFile),
|
|
6422
6690
|
"..",
|
|
6423
6691
|
"bin",
|
|
6424
6692
|
"exe-export-behaviors.js"
|
|
6425
6693
|
);
|
|
6426
|
-
return
|
|
6694
|
+
return existsSync16(scriptPath) ? scriptPath : null;
|
|
6427
6695
|
} catch {
|
|
6428
6696
|
return null;
|
|
6429
6697
|
}
|
|
@@ -6489,12 +6757,12 @@ function extractRootExe(name) {
|
|
|
6489
6757
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
6490
6758
|
}
|
|
6491
6759
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
6492
|
-
if (!
|
|
6760
|
+
if (!existsSync16(SESSION_CACHE)) {
|
|
6493
6761
|
mkdirSync7(SESSION_CACHE, { recursive: true });
|
|
6494
6762
|
}
|
|
6495
6763
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
6496
|
-
const filePath =
|
|
6497
|
-
|
|
6764
|
+
const filePath = path19.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
6765
|
+
writeFileSync8(filePath, JSON.stringify({
|
|
6498
6766
|
parentExe: rootExe,
|
|
6499
6767
|
dispatchedBy: dispatchedBy || rootExe,
|
|
6500
6768
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -6502,7 +6770,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
6502
6770
|
}
|
|
6503
6771
|
function getParentExe(sessionKey) {
|
|
6504
6772
|
try {
|
|
6505
|
-
const data = JSON.parse(
|
|
6773
|
+
const data = JSON.parse(readFileSync12(path19.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
6506
6774
|
return data.parentExe || null;
|
|
6507
6775
|
} catch {
|
|
6508
6776
|
return null;
|
|
@@ -6510,8 +6778,8 @@ function getParentExe(sessionKey) {
|
|
|
6510
6778
|
}
|
|
6511
6779
|
function getDispatchedBy(sessionKey) {
|
|
6512
6780
|
try {
|
|
6513
|
-
const data = JSON.parse(
|
|
6514
|
-
|
|
6781
|
+
const data = JSON.parse(readFileSync12(
|
|
6782
|
+
path19.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
6515
6783
|
"utf8"
|
|
6516
6784
|
));
|
|
6517
6785
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -6581,8 +6849,8 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
6581
6849
|
}
|
|
6582
6850
|
function readDebounceState() {
|
|
6583
6851
|
try {
|
|
6584
|
-
if (!
|
|
6585
|
-
const raw = JSON.parse(
|
|
6852
|
+
if (!existsSync16(DEBOUNCE_FILE)) return {};
|
|
6853
|
+
const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
|
|
6586
6854
|
const state = {};
|
|
6587
6855
|
for (const [key, val] of Object.entries(raw)) {
|
|
6588
6856
|
if (typeof val === "number") {
|
|
@@ -6598,8 +6866,8 @@ function readDebounceState() {
|
|
|
6598
6866
|
}
|
|
6599
6867
|
function writeDebounceState(state) {
|
|
6600
6868
|
try {
|
|
6601
|
-
if (!
|
|
6602
|
-
|
|
6869
|
+
if (!existsSync16(SESSION_CACHE)) mkdirSync7(SESSION_CACHE, { recursive: true });
|
|
6870
|
+
writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
|
|
6603
6871
|
} catch {
|
|
6604
6872
|
}
|
|
6605
6873
|
}
|
|
@@ -6697,8 +6965,8 @@ function sendIntercom(targetSession) {
|
|
|
6697
6965
|
try {
|
|
6698
6966
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
6699
6967
|
const agent = baseAgentName(rawAgent);
|
|
6700
|
-
const markerPath =
|
|
6701
|
-
if (
|
|
6968
|
+
const markerPath = path19.join(SESSION_CACHE, `current-task-${agent}.json`);
|
|
6969
|
+
if (existsSync16(markerPath)) {
|
|
6702
6970
|
logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
|
|
6703
6971
|
return "debounced";
|
|
6704
6972
|
}
|
|
@@ -6707,8 +6975,8 @@ function sendIntercom(targetSession) {
|
|
|
6707
6975
|
try {
|
|
6708
6976
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
6709
6977
|
const agent = baseAgentName(rawAgent);
|
|
6710
|
-
const taskDir =
|
|
6711
|
-
if (
|
|
6978
|
+
const taskDir = path19.join(process.cwd(), "exe", agent);
|
|
6979
|
+
if (existsSync16(taskDir)) {
|
|
6712
6980
|
const files = readdirSync4(taskDir).filter(
|
|
6713
6981
|
(f) => f.endsWith(".md") && f !== "DONE.txt"
|
|
6714
6982
|
);
|
|
@@ -6768,6 +7036,21 @@ function notifyParentExe(sessionKey) {
|
|
|
6768
7036
|
}
|
|
6769
7037
|
return true;
|
|
6770
7038
|
}
|
|
7039
|
+
function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
|
|
7040
|
+
const transport = getTransport();
|
|
7041
|
+
try {
|
|
7042
|
+
const sessions = transport.listSessions();
|
|
7043
|
+
if (!sessions.includes(coordinatorSession)) return false;
|
|
7044
|
+
execSync7(
|
|
7045
|
+
`tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
|
|
7046
|
+
{ timeout: 3e3 }
|
|
7047
|
+
);
|
|
7048
|
+
logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
|
|
7049
|
+
return true;
|
|
7050
|
+
} catch {
|
|
7051
|
+
return false;
|
|
7052
|
+
}
|
|
7053
|
+
}
|
|
6771
7054
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
6772
7055
|
if (isCoordinatorName(employeeName)) {
|
|
6773
7056
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
@@ -6841,26 +7124,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6841
7124
|
const transport = getTransport();
|
|
6842
7125
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
6843
7126
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
6844
|
-
const logDir =
|
|
6845
|
-
const logFile =
|
|
6846
|
-
if (!
|
|
7127
|
+
const logDir = path19.join(os11.homedir(), ".exe-os", "session-logs");
|
|
7128
|
+
const logFile = path19.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
7129
|
+
if (!existsSync16(logDir)) {
|
|
6847
7130
|
mkdirSync7(logDir, { recursive: true });
|
|
6848
7131
|
}
|
|
6849
7132
|
transport.kill(sessionName);
|
|
6850
7133
|
let cleanupSuffix = "";
|
|
6851
7134
|
try {
|
|
6852
7135
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
6853
|
-
const cleanupScript =
|
|
6854
|
-
if (
|
|
7136
|
+
const cleanupScript = path19.join(path19.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
7137
|
+
if (existsSync16(cleanupScript)) {
|
|
6855
7138
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
6856
7139
|
}
|
|
6857
7140
|
} catch {
|
|
6858
7141
|
}
|
|
6859
7142
|
try {
|
|
6860
|
-
const claudeJsonPath =
|
|
7143
|
+
const claudeJsonPath = path19.join(os11.homedir(), ".claude.json");
|
|
6861
7144
|
let claudeJson = {};
|
|
6862
7145
|
try {
|
|
6863
|
-
claudeJson = JSON.parse(
|
|
7146
|
+
claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
|
|
6864
7147
|
} catch {
|
|
6865
7148
|
}
|
|
6866
7149
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -6868,17 +7151,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6868
7151
|
const trustDir = opts?.cwd ?? projectDir;
|
|
6869
7152
|
if (!projects[trustDir]) projects[trustDir] = {};
|
|
6870
7153
|
projects[trustDir].hasTrustDialogAccepted = true;
|
|
6871
|
-
|
|
7154
|
+
writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
6872
7155
|
} catch {
|
|
6873
7156
|
}
|
|
6874
7157
|
try {
|
|
6875
|
-
const settingsDir =
|
|
7158
|
+
const settingsDir = path19.join(os11.homedir(), ".claude", "projects");
|
|
6876
7159
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
6877
|
-
const projSettingsDir =
|
|
6878
|
-
const settingsPath =
|
|
7160
|
+
const projSettingsDir = path19.join(settingsDir, normalizedKey);
|
|
7161
|
+
const settingsPath = path19.join(projSettingsDir, "settings.json");
|
|
6879
7162
|
let settings = {};
|
|
6880
7163
|
try {
|
|
6881
|
-
settings = JSON.parse(
|
|
7164
|
+
settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
|
|
6882
7165
|
} catch {
|
|
6883
7166
|
}
|
|
6884
7167
|
const perms = settings.permissions ?? {};
|
|
@@ -6907,7 +7190,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6907
7190
|
perms.allow = allow;
|
|
6908
7191
|
settings.permissions = perms;
|
|
6909
7192
|
mkdirSync7(projSettingsDir, { recursive: true });
|
|
6910
|
-
|
|
7193
|
+
writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
6911
7194
|
}
|
|
6912
7195
|
} catch {
|
|
6913
7196
|
}
|
|
@@ -6922,8 +7205,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6922
7205
|
let behaviorsFlag = "";
|
|
6923
7206
|
let legacyFallbackWarned = false;
|
|
6924
7207
|
if (!useExeAgent && !useBinSymlink) {
|
|
6925
|
-
const identityPath =
|
|
6926
|
-
|
|
7208
|
+
const identityPath = path19.join(
|
|
7209
|
+
os11.homedir(),
|
|
6927
7210
|
".exe-os",
|
|
6928
7211
|
"identity",
|
|
6929
7212
|
`${employeeName}.md`
|
|
@@ -6932,13 +7215,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6932
7215
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
6933
7216
|
if (hasAgentFlag) {
|
|
6934
7217
|
identityFlag = ` --agent ${employeeName}`;
|
|
6935
|
-
} else if (
|
|
7218
|
+
} else if (existsSync16(identityPath)) {
|
|
6936
7219
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
6937
7220
|
legacyFallbackWarned = true;
|
|
6938
7221
|
}
|
|
6939
7222
|
const behaviorsFile = exportBehaviorsSync(
|
|
6940
7223
|
employeeName,
|
|
6941
|
-
|
|
7224
|
+
path19.basename(spawnCwd),
|
|
6942
7225
|
sessionName
|
|
6943
7226
|
);
|
|
6944
7227
|
if (behaviorsFile) {
|
|
@@ -6953,16 +7236,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6953
7236
|
}
|
|
6954
7237
|
let sessionContextFlag = "";
|
|
6955
7238
|
try {
|
|
6956
|
-
const ctxDir =
|
|
7239
|
+
const ctxDir = path19.join(os11.homedir(), ".exe-os", "session-cache");
|
|
6957
7240
|
mkdirSync7(ctxDir, { recursive: true });
|
|
6958
|
-
const ctxFile =
|
|
7241
|
+
const ctxFile = path19.join(ctxDir, `session-context-${sessionName}.md`);
|
|
6959
7242
|
const ctxContent = [
|
|
6960
7243
|
`## Session Context`,
|
|
6961
7244
|
`You are running in tmux session: ${sessionName}.`,
|
|
6962
7245
|
`Your parent coordinator session is ${exeSession}.`,
|
|
6963
7246
|
`Your employees (if any) use the -${exeSession} suffix.`
|
|
6964
7247
|
].join("\n");
|
|
6965
|
-
|
|
7248
|
+
writeFileSync8(ctxFile, ctxContent);
|
|
6966
7249
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
6967
7250
|
} catch {
|
|
6968
7251
|
}
|
|
@@ -7039,8 +7322,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
7039
7322
|
transport.pipeLog(sessionName, logFile);
|
|
7040
7323
|
try {
|
|
7041
7324
|
const mySession = getMySession();
|
|
7042
|
-
const dispatchInfo =
|
|
7043
|
-
|
|
7325
|
+
const dispatchInfo = path19.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
7326
|
+
writeFileSync8(dispatchInfo, JSON.stringify({
|
|
7044
7327
|
dispatchedBy: mySession,
|
|
7045
7328
|
rootExe: exeSession,
|
|
7046
7329
|
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
|
|
@@ -7114,15 +7397,15 @@ var init_tmux_routing = __esm({
|
|
|
7114
7397
|
init_intercom_queue();
|
|
7115
7398
|
init_plan_limits();
|
|
7116
7399
|
init_employees();
|
|
7117
|
-
SPAWN_LOCK_DIR =
|
|
7118
|
-
SESSION_CACHE =
|
|
7400
|
+
SPAWN_LOCK_DIR = path19.join(os11.homedir(), ".exe-os", "spawn-locks");
|
|
7401
|
+
SESSION_CACHE = path19.join(os11.homedir(), ".exe-os", "session-cache");
|
|
7119
7402
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
7120
7403
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
7121
7404
|
VERIFY_PANE_LINES = 200;
|
|
7122
7405
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
7123
7406
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
7124
|
-
INTERCOM_LOG2 =
|
|
7125
|
-
DEBOUNCE_FILE =
|
|
7407
|
+
INTERCOM_LOG2 = path19.join(os11.homedir(), ".exe-os", "intercom.log");
|
|
7408
|
+
DEBOUNCE_FILE = path19.join(SESSION_CACHE, "intercom-debounce.json");
|
|
7126
7409
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
7127
7410
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
|
|
7128
7411
|
}
|
|
@@ -7145,6 +7428,15 @@ function sessionScopeFilter(sessionScope, tableAlias) {
|
|
|
7145
7428
|
args: [scope]
|
|
7146
7429
|
};
|
|
7147
7430
|
}
|
|
7431
|
+
function strictSessionScopeFilter(sessionScope, tableAlias) {
|
|
7432
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
7433
|
+
if (!scope) return { sql: "", args: [] };
|
|
7434
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
7435
|
+
return {
|
|
7436
|
+
sql: ` AND ${col} = ?`,
|
|
7437
|
+
args: [scope]
|
|
7438
|
+
};
|
|
7439
|
+
}
|
|
7148
7440
|
var init_task_scope = __esm({
|
|
7149
7441
|
"src/lib/task-scope.ts"() {
|
|
7150
7442
|
"use strict";
|
|
@@ -7179,14 +7471,14 @@ __export(worker_gate_exports, {
|
|
|
7179
7471
|
tryAcquireBackfillLock: () => tryAcquireBackfillLock,
|
|
7180
7472
|
tryAcquireWorkerSlot: () => tryAcquireWorkerSlot
|
|
7181
7473
|
});
|
|
7182
|
-
import { readdirSync as readdirSync6, writeFileSync as
|
|
7183
|
-
import
|
|
7474
|
+
import { readdirSync as readdirSync6, writeFileSync as writeFileSync10, unlinkSync as unlinkSync8, mkdirSync as mkdirSync9, existsSync as existsSync17 } from "fs";
|
|
7475
|
+
import path21 from "path";
|
|
7184
7476
|
function tryAcquireWorkerSlot() {
|
|
7185
7477
|
try {
|
|
7186
7478
|
mkdirSync9(WORKER_PID_DIR, { recursive: true });
|
|
7187
7479
|
const reservationId = `res-${process.pid}-${Date.now()}`;
|
|
7188
|
-
const reservationPath =
|
|
7189
|
-
|
|
7480
|
+
const reservationPath = path21.join(WORKER_PID_DIR, `${reservationId}.pid`);
|
|
7481
|
+
writeFileSync10(reservationPath, String(process.pid));
|
|
7190
7482
|
const files = readdirSync6(WORKER_PID_DIR);
|
|
7191
7483
|
let alive = 0;
|
|
7192
7484
|
for (const f of files) {
|
|
@@ -7203,7 +7495,7 @@ function tryAcquireWorkerSlot() {
|
|
|
7203
7495
|
alive++;
|
|
7204
7496
|
} catch {
|
|
7205
7497
|
try {
|
|
7206
|
-
unlinkSync8(
|
|
7498
|
+
unlinkSync8(path21.join(WORKER_PID_DIR, f));
|
|
7207
7499
|
} catch {
|
|
7208
7500
|
}
|
|
7209
7501
|
}
|
|
@@ -7227,20 +7519,20 @@ function tryAcquireWorkerSlot() {
|
|
|
7227
7519
|
function registerWorkerPid(pid) {
|
|
7228
7520
|
try {
|
|
7229
7521
|
mkdirSync9(WORKER_PID_DIR, { recursive: true });
|
|
7230
|
-
|
|
7522
|
+
writeFileSync10(path21.join(WORKER_PID_DIR, `worker-${pid}.pid`), String(pid));
|
|
7231
7523
|
} catch {
|
|
7232
7524
|
}
|
|
7233
7525
|
}
|
|
7234
7526
|
function cleanupWorkerPid() {
|
|
7235
7527
|
try {
|
|
7236
|
-
unlinkSync8(
|
|
7528
|
+
unlinkSync8(path21.join(WORKER_PID_DIR, `worker-${process.pid}.pid`));
|
|
7237
7529
|
} catch {
|
|
7238
7530
|
}
|
|
7239
7531
|
}
|
|
7240
7532
|
function tryAcquireBackfillLock() {
|
|
7241
7533
|
try {
|
|
7242
7534
|
mkdirSync9(WORKER_PID_DIR, { recursive: true });
|
|
7243
|
-
if (
|
|
7535
|
+
if (existsSync17(BACKFILL_LOCK)) {
|
|
7244
7536
|
try {
|
|
7245
7537
|
const pid = parseInt(
|
|
7246
7538
|
__require("fs").readFileSync(BACKFILL_LOCK, "utf8").trim(),
|
|
@@ -7256,7 +7548,7 @@ function tryAcquireBackfillLock() {
|
|
|
7256
7548
|
} catch {
|
|
7257
7549
|
}
|
|
7258
7550
|
}
|
|
7259
|
-
|
|
7551
|
+
writeFileSync10(BACKFILL_LOCK, String(process.pid));
|
|
7260
7552
|
return true;
|
|
7261
7553
|
} catch {
|
|
7262
7554
|
return true;
|
|
@@ -7273,9 +7565,9 @@ var init_worker_gate = __esm({
|
|
|
7273
7565
|
"src/lib/worker-gate.ts"() {
|
|
7274
7566
|
"use strict";
|
|
7275
7567
|
init_config();
|
|
7276
|
-
WORKER_PID_DIR =
|
|
7568
|
+
WORKER_PID_DIR = path21.join(EXE_AI_DIR, "worker-pids");
|
|
7277
7569
|
MAX_CONCURRENT_WORKERS = 3;
|
|
7278
|
-
BACKFILL_LOCK =
|
|
7570
|
+
BACKFILL_LOCK = path21.join(WORKER_PID_DIR, "backfill.lock");
|
|
7279
7571
|
}
|
|
7280
7572
|
});
|
|
7281
7573
|
|
|
@@ -7287,13 +7579,13 @@ __export(crypto_exports, {
|
|
|
7287
7579
|
initSyncCrypto: () => initSyncCrypto,
|
|
7288
7580
|
isSyncCryptoInitialized: () => isSyncCryptoInitialized
|
|
7289
7581
|
});
|
|
7290
|
-
import
|
|
7582
|
+
import crypto7 from "crypto";
|
|
7291
7583
|
function initSyncCrypto(masterKey) {
|
|
7292
7584
|
if (masterKey.length !== 32) {
|
|
7293
7585
|
throw new Error(`Master key must be 32 bytes, got ${masterKey.length}`);
|
|
7294
7586
|
}
|
|
7295
7587
|
_syncKey = Buffer.from(
|
|
7296
|
-
|
|
7588
|
+
crypto7.hkdfSync("sha256", masterKey, "", SYNC_HKDF_INFO, 32)
|
|
7297
7589
|
);
|
|
7298
7590
|
}
|
|
7299
7591
|
function isSyncCryptoInitialized() {
|
|
@@ -7307,8 +7599,8 @@ function requireSyncKey() {
|
|
|
7307
7599
|
}
|
|
7308
7600
|
function encryptSyncBlob(data) {
|
|
7309
7601
|
const key = requireSyncKey();
|
|
7310
|
-
const iv =
|
|
7311
|
-
const cipher =
|
|
7602
|
+
const iv = crypto7.randomBytes(IV_LENGTH);
|
|
7603
|
+
const cipher = crypto7.createCipheriv(ALGORITHM, key, iv);
|
|
7312
7604
|
const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
|
|
7313
7605
|
const tag = cipher.getAuthTag();
|
|
7314
7606
|
return Buffer.concat([iv, encrypted, tag]).toString("base64");
|
|
@@ -7322,7 +7614,7 @@ function decryptSyncBlob(ciphertext) {
|
|
|
7322
7614
|
const iv = combined.subarray(0, IV_LENGTH);
|
|
7323
7615
|
const tag = combined.subarray(combined.length - TAG_LENGTH);
|
|
7324
7616
|
const encrypted = combined.subarray(IV_LENGTH, combined.length - TAG_LENGTH);
|
|
7325
|
-
const decipher =
|
|
7617
|
+
const decipher = crypto7.createDecipheriv(ALGORITHM, key, iv);
|
|
7326
7618
|
decipher.setAuthTag(tag);
|
|
7327
7619
|
return Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
|
7328
7620
|
}
|
|
@@ -7377,8 +7669,8 @@ __export(crdt_sync_exports, {
|
|
|
7377
7669
|
rebuildFromDb: () => rebuildFromDb
|
|
7378
7670
|
});
|
|
7379
7671
|
import * as Y from "yjs";
|
|
7380
|
-
import { readFileSync as
|
|
7381
|
-
import
|
|
7672
|
+
import { readFileSync as readFileSync14, writeFileSync as writeFileSync11, existsSync as existsSync18, mkdirSync as mkdirSync10, unlinkSync as unlinkSync9 } from "fs";
|
|
7673
|
+
import path22 from "path";
|
|
7382
7674
|
import { homedir } from "os";
|
|
7383
7675
|
function getStatePath() {
|
|
7384
7676
|
return _statePathOverride ?? DEFAULT_STATE_PATH;
|
|
@@ -7390,9 +7682,9 @@ function initCrdtDoc() {
|
|
|
7390
7682
|
if (doc) return doc;
|
|
7391
7683
|
doc = new Y.Doc();
|
|
7392
7684
|
const sp = getStatePath();
|
|
7393
|
-
if (
|
|
7685
|
+
if (existsSync18(sp)) {
|
|
7394
7686
|
try {
|
|
7395
|
-
const state =
|
|
7687
|
+
const state = readFileSync14(sp);
|
|
7396
7688
|
Y.applyUpdate(doc, new Uint8Array(state));
|
|
7397
7689
|
} catch {
|
|
7398
7690
|
console.warn("[crdt-sync] WARN: corrupted state file, rebuilding from DB");
|
|
@@ -7534,10 +7826,10 @@ function persistState() {
|
|
|
7534
7826
|
if (!doc) return;
|
|
7535
7827
|
try {
|
|
7536
7828
|
const sp = getStatePath();
|
|
7537
|
-
const dir =
|
|
7538
|
-
if (!
|
|
7829
|
+
const dir = path22.dirname(sp);
|
|
7830
|
+
if (!existsSync18(dir)) mkdirSync10(dir, { recursive: true });
|
|
7539
7831
|
const state = Y.encodeStateAsUpdate(doc);
|
|
7540
|
-
|
|
7832
|
+
writeFileSync11(sp, Buffer.from(state));
|
|
7541
7833
|
} catch {
|
|
7542
7834
|
}
|
|
7543
7835
|
}
|
|
@@ -7578,7 +7870,7 @@ var DEFAULT_STATE_PATH, _statePathOverride, doc;
|
|
|
7578
7870
|
var init_crdt_sync = __esm({
|
|
7579
7871
|
"src/lib/crdt-sync.ts"() {
|
|
7580
7872
|
"use strict";
|
|
7581
|
-
DEFAULT_STATE_PATH =
|
|
7873
|
+
DEFAULT_STATE_PATH = path22.join(homedir(), ".exe-os", "crdt-state.bin");
|
|
7582
7874
|
_statePathOverride = null;
|
|
7583
7875
|
doc = null;
|
|
7584
7876
|
}
|
|
@@ -7610,39 +7902,107 @@ __export(cloud_sync_exports, {
|
|
|
7610
7902
|
cloudSync: () => cloudSync,
|
|
7611
7903
|
mergeConfig: () => mergeConfig,
|
|
7612
7904
|
mergeRosterFromRemote: () => mergeRosterFromRemote,
|
|
7905
|
+
pushToPostgres: () => pushToPostgres,
|
|
7613
7906
|
recordRosterDeletion: () => recordRosterDeletion
|
|
7614
7907
|
});
|
|
7615
|
-
import { readFileSync as
|
|
7616
|
-
import
|
|
7617
|
-
import
|
|
7908
|
+
import { readFileSync as readFileSync15, writeFileSync as writeFileSync12, existsSync as existsSync19, readdirSync as readdirSync7, mkdirSync as mkdirSync11, appendFileSync as appendFileSync2, unlinkSync as unlinkSync10, openSync as openSync2, closeSync as closeSync2 } from "fs";
|
|
7909
|
+
import crypto8 from "crypto";
|
|
7910
|
+
import path23 from "path";
|
|
7618
7911
|
import { homedir as homedir2 } from "os";
|
|
7619
7912
|
function sqlSafe(v) {
|
|
7620
7913
|
return v === void 0 ? null : v;
|
|
7621
7914
|
}
|
|
7622
7915
|
function logError(msg) {
|
|
7623
7916
|
try {
|
|
7624
|
-
const logPath =
|
|
7917
|
+
const logPath = path23.join(homedir2(), ".exe-os", "workers.log");
|
|
7625
7918
|
appendFileSync2(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
|
|
7626
7919
|
`);
|
|
7627
7920
|
} catch {
|
|
7628
7921
|
}
|
|
7629
7922
|
}
|
|
7923
|
+
function loadPgClient() {
|
|
7924
|
+
if (_pgFailed) return null;
|
|
7925
|
+
const postgresUrl = process.env.DATABASE_URL;
|
|
7926
|
+
const configPath = path23.join(EXE_AI_DIR, "config.json");
|
|
7927
|
+
let cloudPostgresUrl;
|
|
7928
|
+
try {
|
|
7929
|
+
if (existsSync19(configPath)) {
|
|
7930
|
+
const cfg = JSON.parse(readFileSync15(configPath, "utf8"));
|
|
7931
|
+
cloudPostgresUrl = cfg.cloud?.postgresUrl;
|
|
7932
|
+
if (cfg.cloud?.syncToPostgres === false) {
|
|
7933
|
+
_pgFailed = true;
|
|
7934
|
+
return null;
|
|
7935
|
+
}
|
|
7936
|
+
}
|
|
7937
|
+
} catch {
|
|
7938
|
+
}
|
|
7939
|
+
const url = postgresUrl || cloudPostgresUrl;
|
|
7940
|
+
if (!url) {
|
|
7941
|
+
_pgFailed = true;
|
|
7942
|
+
return null;
|
|
7943
|
+
}
|
|
7944
|
+
if (!_pgPromise) {
|
|
7945
|
+
_pgPromise = (async () => {
|
|
7946
|
+
const { createRequire: createRequire3 } = await import("module");
|
|
7947
|
+
const { pathToFileURL: pathToFileURL3 } = await import("url");
|
|
7948
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path23.join(homedir2(), "exe-db");
|
|
7949
|
+
const req = createRequire3(path23.join(exeDbRoot, "package.json"));
|
|
7950
|
+
const entry = req.resolve("@prisma/client");
|
|
7951
|
+
const mod = await import(pathToFileURL3(entry).href);
|
|
7952
|
+
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
7953
|
+
if (!Ctor) throw new Error("No PrismaClient");
|
|
7954
|
+
return new Ctor();
|
|
7955
|
+
})().catch(() => {
|
|
7956
|
+
_pgFailed = true;
|
|
7957
|
+
_pgPromise = null;
|
|
7958
|
+
throw new Error("pg_unavailable");
|
|
7959
|
+
});
|
|
7960
|
+
}
|
|
7961
|
+
return _pgPromise;
|
|
7962
|
+
}
|
|
7963
|
+
async function pushToPostgres(records) {
|
|
7964
|
+
const loader = loadPgClient();
|
|
7965
|
+
if (!loader) return 0;
|
|
7966
|
+
let prisma;
|
|
7967
|
+
try {
|
|
7968
|
+
prisma = await loader;
|
|
7969
|
+
} catch {
|
|
7970
|
+
return 0;
|
|
7971
|
+
}
|
|
7972
|
+
let inserted = 0;
|
|
7973
|
+
for (const rec of records) {
|
|
7974
|
+
try {
|
|
7975
|
+
await prisma.$executeRawUnsafe(
|
|
7976
|
+
`INSERT INTO raw.raw_events (id, source, source_id, event_type, payload, metadata, timestamp)
|
|
7977
|
+
VALUES (gen_random_uuid(), 'cloud_sync', $1, 'memory', $2::jsonb, $3::jsonb, $4)
|
|
7978
|
+
ON CONFLICT (source, source_id, event_type) DO NOTHING`,
|
|
7979
|
+
String(rec.id ?? ""),
|
|
7980
|
+
JSON.stringify(rec),
|
|
7981
|
+
JSON.stringify({ agent_id: rec.agent_id, project_name: rec.project_name, tool_name: rec.tool_name }),
|
|
7982
|
+
rec.timestamp ? new Date(String(rec.timestamp)) : /* @__PURE__ */ new Date()
|
|
7983
|
+
);
|
|
7984
|
+
inserted++;
|
|
7985
|
+
} catch {
|
|
7986
|
+
}
|
|
7987
|
+
}
|
|
7988
|
+
return inserted;
|
|
7989
|
+
}
|
|
7630
7990
|
async function withRosterLock(fn) {
|
|
7631
7991
|
try {
|
|
7632
7992
|
const fd = openSync2(ROSTER_LOCK_PATH, "wx");
|
|
7633
7993
|
closeSync2(fd);
|
|
7634
|
-
|
|
7994
|
+
writeFileSync12(ROSTER_LOCK_PATH, String(Date.now()));
|
|
7635
7995
|
} catch (err) {
|
|
7636
7996
|
if (err.code === "EEXIST") {
|
|
7637
7997
|
try {
|
|
7638
|
-
const ts = parseInt(
|
|
7998
|
+
const ts = parseInt(readFileSync15(ROSTER_LOCK_PATH, "utf-8"), 10);
|
|
7639
7999
|
if (Date.now() - ts < LOCK_STALE_MS) {
|
|
7640
8000
|
throw new Error("Roster merge already in progress \u2014 another sync is running");
|
|
7641
8001
|
}
|
|
7642
8002
|
unlinkSync10(ROSTER_LOCK_PATH);
|
|
7643
8003
|
const fd = openSync2(ROSTER_LOCK_PATH, "wx");
|
|
7644
8004
|
closeSync2(fd);
|
|
7645
|
-
|
|
8005
|
+
writeFileSync12(ROSTER_LOCK_PATH, String(Date.now()));
|
|
7646
8006
|
} catch (retryErr) {
|
|
7647
8007
|
if (retryErr instanceof Error && retryErr.message.includes("already in progress")) throw retryErr;
|
|
7648
8008
|
throw new Error("Roster merge already in progress \u2014 another sync is running");
|
|
@@ -7912,6 +8272,10 @@ async function cloudSync(config) {
|
|
|
7912
8272
|
const maxVersion = Number(records[records.length - 1].version);
|
|
7913
8273
|
const pushOk = await cloudPush(records, maxVersion, config);
|
|
7914
8274
|
if (!pushOk) break;
|
|
8275
|
+
try {
|
|
8276
|
+
await pushToPostgres(records);
|
|
8277
|
+
} catch {
|
|
8278
|
+
}
|
|
7915
8279
|
await client.execute({
|
|
7916
8280
|
sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_cloud_push_version', ?)",
|
|
7917
8281
|
args: [String(maxVersion)]
|
|
@@ -8016,8 +8380,8 @@ async function cloudSync(config) {
|
|
|
8016
8380
|
try {
|
|
8017
8381
|
const employees = await loadEmployees();
|
|
8018
8382
|
rosterResult.employees = employees.length;
|
|
8019
|
-
const idDir =
|
|
8020
|
-
if (
|
|
8383
|
+
const idDir = path23.join(EXE_AI_DIR, "identity");
|
|
8384
|
+
if (existsSync19(idDir)) {
|
|
8021
8385
|
rosterResult.identities = readdirSync7(idDir).filter((f) => f.endsWith(".md")).length;
|
|
8022
8386
|
}
|
|
8023
8387
|
} catch {
|
|
@@ -8038,62 +8402,62 @@ async function cloudSync(config) {
|
|
|
8038
8402
|
function recordRosterDeletion(name) {
|
|
8039
8403
|
let deletions = [];
|
|
8040
8404
|
try {
|
|
8041
|
-
if (
|
|
8042
|
-
deletions = JSON.parse(
|
|
8405
|
+
if (existsSync19(ROSTER_DELETIONS_PATH)) {
|
|
8406
|
+
deletions = JSON.parse(readFileSync15(ROSTER_DELETIONS_PATH, "utf-8"));
|
|
8043
8407
|
}
|
|
8044
8408
|
} catch {
|
|
8045
8409
|
}
|
|
8046
8410
|
if (!deletions.includes(name)) deletions.push(name);
|
|
8047
|
-
|
|
8411
|
+
writeFileSync12(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
|
|
8048
8412
|
}
|
|
8049
8413
|
function consumeRosterDeletions() {
|
|
8050
8414
|
try {
|
|
8051
|
-
if (!
|
|
8052
|
-
const deletions = JSON.parse(
|
|
8053
|
-
|
|
8415
|
+
if (!existsSync19(ROSTER_DELETIONS_PATH)) return [];
|
|
8416
|
+
const deletions = JSON.parse(readFileSync15(ROSTER_DELETIONS_PATH, "utf-8"));
|
|
8417
|
+
writeFileSync12(ROSTER_DELETIONS_PATH, "[]");
|
|
8054
8418
|
return deletions;
|
|
8055
8419
|
} catch {
|
|
8056
8420
|
return [];
|
|
8057
8421
|
}
|
|
8058
8422
|
}
|
|
8059
8423
|
function buildRosterBlob(paths) {
|
|
8060
|
-
const rosterPath = paths?.rosterPath ??
|
|
8061
|
-
const identityDir = paths?.identityDir ??
|
|
8062
|
-
const configPath = paths?.configPath ??
|
|
8424
|
+
const rosterPath = paths?.rosterPath ?? path23.join(EXE_AI_DIR, "exe-employees.json");
|
|
8425
|
+
const identityDir = paths?.identityDir ?? path23.join(EXE_AI_DIR, "identity");
|
|
8426
|
+
const configPath = paths?.configPath ?? path23.join(EXE_AI_DIR, "config.json");
|
|
8063
8427
|
let roster = [];
|
|
8064
|
-
if (
|
|
8428
|
+
if (existsSync19(rosterPath)) {
|
|
8065
8429
|
try {
|
|
8066
|
-
roster = JSON.parse(
|
|
8430
|
+
roster = JSON.parse(readFileSync15(rosterPath, "utf-8"));
|
|
8067
8431
|
} catch {
|
|
8068
8432
|
}
|
|
8069
8433
|
}
|
|
8070
8434
|
const identities = {};
|
|
8071
|
-
if (
|
|
8435
|
+
if (existsSync19(identityDir)) {
|
|
8072
8436
|
for (const file of readdirSync7(identityDir).filter((f) => f.endsWith(".md"))) {
|
|
8073
8437
|
try {
|
|
8074
|
-
identities[file] =
|
|
8438
|
+
identities[file] = readFileSync15(path23.join(identityDir, file), "utf-8");
|
|
8075
8439
|
} catch {
|
|
8076
8440
|
}
|
|
8077
8441
|
}
|
|
8078
8442
|
}
|
|
8079
8443
|
let config;
|
|
8080
|
-
if (
|
|
8444
|
+
if (existsSync19(configPath)) {
|
|
8081
8445
|
try {
|
|
8082
|
-
config = JSON.parse(
|
|
8446
|
+
config = JSON.parse(readFileSync15(configPath, "utf-8"));
|
|
8083
8447
|
} catch {
|
|
8084
8448
|
}
|
|
8085
8449
|
}
|
|
8086
8450
|
let agentConfig;
|
|
8087
|
-
const agentConfigPath =
|
|
8088
|
-
if (
|
|
8451
|
+
const agentConfigPath = path23.join(EXE_AI_DIR, "agent-config.json");
|
|
8452
|
+
if (existsSync19(agentConfigPath)) {
|
|
8089
8453
|
try {
|
|
8090
|
-
agentConfig = JSON.parse(
|
|
8454
|
+
agentConfig = JSON.parse(readFileSync15(agentConfigPath, "utf-8"));
|
|
8091
8455
|
} catch {
|
|
8092
8456
|
}
|
|
8093
8457
|
}
|
|
8094
8458
|
const deletedNames = consumeRosterDeletions();
|
|
8095
8459
|
const content = JSON.stringify({ roster, identities, config, agentConfig, deletedNames });
|
|
8096
|
-
const hash =
|
|
8460
|
+
const hash = crypto8.createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
8097
8461
|
return { roster, identities, config, agentConfig, deletedNames, version: hash };
|
|
8098
8462
|
}
|
|
8099
8463
|
async function cloudPushRoster(config) {
|
|
@@ -8163,23 +8527,24 @@ async function cloudPullRoster(config) {
|
|
|
8163
8527
|
}
|
|
8164
8528
|
}
|
|
8165
8529
|
function mergeConfig(remoteConfig, configPath) {
|
|
8166
|
-
const cfgPath = configPath ??
|
|
8530
|
+
const cfgPath = configPath ?? path23.join(EXE_AI_DIR, "config.json");
|
|
8167
8531
|
let local = {};
|
|
8168
|
-
if (
|
|
8532
|
+
if (existsSync19(cfgPath)) {
|
|
8169
8533
|
try {
|
|
8170
|
-
local = JSON.parse(
|
|
8534
|
+
local = JSON.parse(readFileSync15(cfgPath, "utf-8"));
|
|
8171
8535
|
} catch {
|
|
8172
8536
|
}
|
|
8173
8537
|
}
|
|
8174
8538
|
const merged = { ...remoteConfig, ...local };
|
|
8175
|
-
const dir =
|
|
8176
|
-
|
|
8177
|
-
|
|
8539
|
+
const dir = path23.dirname(cfgPath);
|
|
8540
|
+
ensurePrivateDirSync(dir);
|
|
8541
|
+
writeFileSync12(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
|
|
8542
|
+
enforcePrivateFileSync(cfgPath);
|
|
8178
8543
|
}
|
|
8179
8544
|
async function mergeRosterFromRemote(remote, paths) {
|
|
8180
8545
|
return withRosterLock(async () => {
|
|
8181
8546
|
const rosterPath = paths?.rosterPath ?? void 0;
|
|
8182
|
-
const identityDir = paths?.identityDir ??
|
|
8547
|
+
const identityDir = paths?.identityDir ?? path23.join(EXE_AI_DIR, "identity");
|
|
8183
8548
|
const localEmployees = await loadEmployees(rosterPath);
|
|
8184
8549
|
const localNames = new Set(localEmployees.map((e) => e.name));
|
|
8185
8550
|
let added = 0;
|
|
@@ -8200,15 +8565,15 @@ async function mergeRosterFromRemote(remote, paths) {
|
|
|
8200
8565
|
) ?? lookupKey;
|
|
8201
8566
|
const remoteIdentity = remote.identities[matchedKey];
|
|
8202
8567
|
if (remoteIdentity) {
|
|
8203
|
-
if (!
|
|
8204
|
-
const idPath =
|
|
8568
|
+
if (!existsSync19(identityDir)) mkdirSync11(identityDir, { recursive: true });
|
|
8569
|
+
const idPath = path23.join(identityDir, `${remoteEmp.name}.md`);
|
|
8205
8570
|
let localIdentity = null;
|
|
8206
8571
|
try {
|
|
8207
|
-
localIdentity =
|
|
8572
|
+
localIdentity = existsSync19(idPath) ? readFileSync15(idPath, "utf-8") : null;
|
|
8208
8573
|
} catch {
|
|
8209
8574
|
}
|
|
8210
8575
|
if (localIdentity !== remoteIdentity) {
|
|
8211
|
-
|
|
8576
|
+
writeFileSync12(idPath, remoteIdentity, "utf-8");
|
|
8212
8577
|
identitiesUpdated++;
|
|
8213
8578
|
}
|
|
8214
8579
|
}
|
|
@@ -8234,16 +8599,18 @@ async function mergeRosterFromRemote(remote, paths) {
|
|
|
8234
8599
|
}
|
|
8235
8600
|
if (remote.agentConfig && Object.keys(remote.agentConfig).length > 0) {
|
|
8236
8601
|
try {
|
|
8237
|
-
const agentConfigPath =
|
|
8602
|
+
const agentConfigPath = path23.join(EXE_AI_DIR, "agent-config.json");
|
|
8238
8603
|
let local = {};
|
|
8239
|
-
if (
|
|
8604
|
+
if (existsSync19(agentConfigPath)) {
|
|
8240
8605
|
try {
|
|
8241
|
-
local = JSON.parse(
|
|
8606
|
+
local = JSON.parse(readFileSync15(agentConfigPath, "utf-8"));
|
|
8242
8607
|
} catch {
|
|
8243
8608
|
}
|
|
8244
8609
|
}
|
|
8245
8610
|
const merged = { ...remote.agentConfig, ...local };
|
|
8246
|
-
|
|
8611
|
+
ensurePrivateDirSync(path23.dirname(agentConfigPath));
|
|
8612
|
+
writeFileSync12(agentConfigPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
8613
|
+
enforcePrivateFileSync(agentConfigPath);
|
|
8247
8614
|
} catch {
|
|
8248
8615
|
}
|
|
8249
8616
|
}
|
|
@@ -8667,7 +9034,7 @@ async function cloudPullDocuments(config) {
|
|
|
8667
9034
|
}
|
|
8668
9035
|
return { pulled };
|
|
8669
9036
|
}
|
|
8670
|
-
var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, ROSTER_DELETIONS_PATH;
|
|
9037
|
+
var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, _pgPromise, _pgFailed, ROSTER_DELETIONS_PATH;
|
|
8671
9038
|
var init_cloud_sync = __esm({
|
|
8672
9039
|
"src/lib/cloud-sync.ts"() {
|
|
8673
9040
|
"use strict";
|
|
@@ -8678,12 +9045,15 @@ var init_cloud_sync = __esm({
|
|
|
8678
9045
|
init_config();
|
|
8679
9046
|
init_crdt_sync();
|
|
8680
9047
|
init_employees();
|
|
9048
|
+
init_secure_files();
|
|
8681
9049
|
LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
|
|
8682
9050
|
FETCH_TIMEOUT_MS = 3e4;
|
|
8683
9051
|
PUSH_BATCH_SIZE = 5e3;
|
|
8684
|
-
ROSTER_LOCK_PATH =
|
|
9052
|
+
ROSTER_LOCK_PATH = path23.join(EXE_AI_DIR, "roster-merge.lock");
|
|
8685
9053
|
LOCK_STALE_MS = 3e4;
|
|
8686
|
-
|
|
9054
|
+
_pgPromise = null;
|
|
9055
|
+
_pgFailed = false;
|
|
9056
|
+
ROSTER_DELETIONS_PATH = path23.join(EXE_AI_DIR, "roster-deletions.json");
|
|
8687
9057
|
}
|
|
8688
9058
|
});
|
|
8689
9059
|
|
|
@@ -8695,7 +9065,7 @@ __export(schedules_exports, {
|
|
|
8695
9065
|
listSchedules: () => listSchedules,
|
|
8696
9066
|
parseHumanCron: () => parseHumanCron
|
|
8697
9067
|
});
|
|
8698
|
-
import
|
|
9068
|
+
import crypto9 from "crypto";
|
|
8699
9069
|
import { execSync as execSync9 } from "child_process";
|
|
8700
9070
|
async function ensureDb() {
|
|
8701
9071
|
if (!isInitialized()) {
|
|
@@ -8764,7 +9134,7 @@ function parseHumanCron(input) {
|
|
|
8764
9134
|
async function createSchedule(input) {
|
|
8765
9135
|
await ensureDb();
|
|
8766
9136
|
const client = getClient();
|
|
8767
|
-
const id =
|
|
9137
|
+
const id = crypto9.randomUUID().slice(0, 8);
|
|
8768
9138
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8769
9139
|
const prompt = input.prompt ?? input.description;
|
|
8770
9140
|
await client.execute({
|
|
@@ -8865,10 +9235,10 @@ var init_schedules = __esm({
|
|
|
8865
9235
|
|
|
8866
9236
|
// src/bin/exe-boot.ts
|
|
8867
9237
|
init_employees();
|
|
8868
|
-
import
|
|
9238
|
+
import path24 from "path";
|
|
8869
9239
|
import { mkdir as mkdir5, writeFile as writeFile6 } from "fs/promises";
|
|
8870
|
-
import { existsSync as
|
|
8871
|
-
import
|
|
9240
|
+
import { existsSync as existsSync20, readFileSync as readFileSync16, readdirSync as readdirSync8, unlinkSync as unlinkSync11 } from "fs";
|
|
9241
|
+
import os12 from "os";
|
|
8872
9242
|
|
|
8873
9243
|
// src/lib/employee-templates.ts
|
|
8874
9244
|
init_global_procedures();
|
|
@@ -9370,18 +9740,18 @@ init_notifications();
|
|
|
9370
9740
|
init_config();
|
|
9371
9741
|
init_session_key();
|
|
9372
9742
|
init_employees();
|
|
9373
|
-
import { readFileSync as
|
|
9743
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync9, mkdirSync as mkdirSync8, unlinkSync as unlinkSync7, readdirSync as readdirSync5 } from "fs";
|
|
9374
9744
|
import { execSync as execSync8 } from "child_process";
|
|
9375
|
-
import
|
|
9376
|
-
var CACHE_DIR =
|
|
9745
|
+
import path20 from "path";
|
|
9746
|
+
var CACHE_DIR = path20.join(EXE_AI_DIR, "session-cache");
|
|
9377
9747
|
var STALE_MS = 24 * 60 * 60 * 1e3;
|
|
9378
9748
|
function getMarkerPath() {
|
|
9379
|
-
return
|
|
9749
|
+
return path20.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
|
|
9380
9750
|
}
|
|
9381
9751
|
function writeActiveAgent(agentId, agentRole) {
|
|
9382
9752
|
try {
|
|
9383
9753
|
mkdirSync8(CACHE_DIR, { recursive: true });
|
|
9384
|
-
|
|
9754
|
+
writeFileSync9(
|
|
9385
9755
|
getMarkerPath(),
|
|
9386
9756
|
JSON.stringify({ agentId, agentRole, startedAt: (/* @__PURE__ */ new Date()).toISOString() })
|
|
9387
9757
|
);
|
|
@@ -9391,11 +9761,11 @@ function writeActiveAgent(agentId, agentRole) {
|
|
|
9391
9761
|
function cleanupSessionMarkers() {
|
|
9392
9762
|
const key = getSessionKey();
|
|
9393
9763
|
try {
|
|
9394
|
-
unlinkSync7(
|
|
9764
|
+
unlinkSync7(path20.join(CACHE_DIR, `active-agent-${key}.json`));
|
|
9395
9765
|
} catch {
|
|
9396
9766
|
}
|
|
9397
9767
|
try {
|
|
9398
|
-
unlinkSync7(
|
|
9768
|
+
unlinkSync7(path20.join(CACHE_DIR, "active-agent-undefined.json"));
|
|
9399
9769
|
} catch {
|
|
9400
9770
|
}
|
|
9401
9771
|
}
|
|
@@ -9485,7 +9855,7 @@ async function boot(options) {
|
|
|
9485
9855
|
const employeeDirs = entries.filter((e) => e.isDirectory() && !["output", "research"].includes(e.name));
|
|
9486
9856
|
for (const dir of employeeDirs) {
|
|
9487
9857
|
const employee = dir.name;
|
|
9488
|
-
const taskDir =
|
|
9858
|
+
const taskDir = path24.join(exeDir, employee);
|
|
9489
9859
|
let files;
|
|
9490
9860
|
try {
|
|
9491
9861
|
files = readdirSync9(taskDir).filter((f) => f.endsWith(".md"));
|
|
@@ -9496,7 +9866,7 @@ async function boot(options) {
|
|
|
9496
9866
|
const taskFilePath = `exe/${employee}/${file}`;
|
|
9497
9867
|
let content;
|
|
9498
9868
|
try {
|
|
9499
|
-
content = readFs(
|
|
9869
|
+
content = readFs(path24.join(taskDir, file), "utf8");
|
|
9500
9870
|
} catch {
|
|
9501
9871
|
continue;
|
|
9502
9872
|
}
|
|
@@ -9582,12 +9952,12 @@ async function boot(options) {
|
|
|
9582
9952
|
}
|
|
9583
9953
|
try {
|
|
9584
9954
|
for (const reviewDirName of /* @__PURE__ */ new Set(["exe", coordinatorName])) {
|
|
9585
|
-
const reviewDir =
|
|
9586
|
-
if (
|
|
9955
|
+
const reviewDir = path24.join(process.cwd(), "exe", reviewDirName);
|
|
9956
|
+
if (existsSync20(reviewDir)) {
|
|
9587
9957
|
for (const f of readdirSync8(reviewDir)) {
|
|
9588
9958
|
if (f.startsWith("review-") && f.endsWith(".md")) {
|
|
9589
9959
|
try {
|
|
9590
|
-
unlinkSync11(
|
|
9960
|
+
unlinkSync11(path24.join(reviewDir, f));
|
|
9591
9961
|
} catch {
|
|
9592
9962
|
}
|
|
9593
9963
|
}
|
|
@@ -9633,12 +10003,12 @@ async function boot(options) {
|
|
|
9633
10003
|
});
|
|
9634
10004
|
const taskFile = String(r.task_file);
|
|
9635
10005
|
try {
|
|
9636
|
-
const filePath =
|
|
9637
|
-
if (
|
|
9638
|
-
let content =
|
|
10006
|
+
const filePath = path24.join(process.cwd(), taskFile);
|
|
10007
|
+
if (existsSync20(filePath)) {
|
|
10008
|
+
let content = readFileSync16(filePath, "utf8");
|
|
9639
10009
|
content = content.replace(/\*\*Status:\*\* needs_review/, "**Status:** done");
|
|
9640
|
-
const { writeFileSync:
|
|
9641
|
-
|
|
10010
|
+
const { writeFileSync: writeFileSync13 } = await import("fs");
|
|
10011
|
+
writeFileSync13(filePath, content);
|
|
9642
10012
|
}
|
|
9643
10013
|
} catch {
|
|
9644
10014
|
}
|
|
@@ -10132,19 +10502,19 @@ async function boot(options) {
|
|
|
10132
10502
|
})()
|
|
10133
10503
|
]);
|
|
10134
10504
|
try {
|
|
10135
|
-
const configPath =
|
|
10136
|
-
process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
10505
|
+
const configPath = path24.join(
|
|
10506
|
+
process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path24.join(os12.homedir(), ".exe-os"),
|
|
10137
10507
|
"config.json"
|
|
10138
10508
|
);
|
|
10139
|
-
if (
|
|
10140
|
-
const raw = JSON.parse(
|
|
10509
|
+
if (existsSync20(configPath)) {
|
|
10510
|
+
const raw = JSON.parse(readFileSync16(configPath, "utf8"));
|
|
10141
10511
|
briefData.cloudConnected = !!(raw.cloud || raw.turso);
|
|
10142
10512
|
}
|
|
10143
10513
|
} catch {
|
|
10144
10514
|
}
|
|
10145
10515
|
try {
|
|
10146
|
-
const backfillFlagPath =
|
|
10147
|
-
const isBackfillNeeded = () =>
|
|
10516
|
+
const backfillFlagPath = path24.join(EXE_AI_DIR, "session-cache", "needs-backfill");
|
|
10517
|
+
const isBackfillNeeded = () => existsSync20(backfillFlagPath);
|
|
10148
10518
|
const coverageResult = await client.execute({
|
|
10149
10519
|
sql: `SELECT COUNT(*) as total,
|
|
10150
10520
|
SUM(CASE WHEN vector IS NOT NULL THEN 1 ELSE 0 END) as with_vectors
|
|
@@ -10166,8 +10536,8 @@ async function boot(options) {
|
|
|
10166
10536
|
let daemonRunning = false;
|
|
10167
10537
|
let daemonUptime;
|
|
10168
10538
|
let daemonRequestsServed;
|
|
10169
|
-
const socketPath =
|
|
10170
|
-
if (
|
|
10539
|
+
const socketPath = path24.join(EXE_AI_DIR, "exed.sock");
|
|
10540
|
+
if (existsSync20(socketPath)) {
|
|
10171
10541
|
try {
|
|
10172
10542
|
const net2 = await import("net");
|
|
10173
10543
|
const health = await new Promise((resolve) => {
|
|
@@ -10209,10 +10579,10 @@ async function boot(options) {
|
|
|
10209
10579
|
}
|
|
10210
10580
|
}
|
|
10211
10581
|
if (!daemonRunning) {
|
|
10212
|
-
const pidPath =
|
|
10213
|
-
if (
|
|
10582
|
+
const pidPath = path24.join(EXE_AI_DIR, "exed.pid");
|
|
10583
|
+
if (existsSync20(pidPath)) {
|
|
10214
10584
|
try {
|
|
10215
|
-
const pid = parseInt(
|
|
10585
|
+
const pid = parseInt(readFileSync16(pidPath, "utf8").trim(), 10);
|
|
10216
10586
|
if (pid > 0) {
|
|
10217
10587
|
process.kill(pid, 0);
|
|
10218
10588
|
daemonRunning = true;
|
|
@@ -10223,8 +10593,8 @@ async function boot(options) {
|
|
|
10223
10593
|
}
|
|
10224
10594
|
if (nullCount === 0) {
|
|
10225
10595
|
try {
|
|
10226
|
-
const flagPath =
|
|
10227
|
-
if (
|
|
10596
|
+
const flagPath = path24.join(EXE_AI_DIR, "session-cache", "needs-backfill");
|
|
10597
|
+
if (existsSync20(flagPath)) {
|
|
10228
10598
|
const { unlinkSync: unlinkSync12 } = await import("fs");
|
|
10229
10599
|
unlinkSync12(flagPath);
|
|
10230
10600
|
}
|
|
@@ -10250,10 +10620,10 @@ async function boot(options) {
|
|
|
10250
10620
|
const { spawn: spawn2 } = await import("child_process");
|
|
10251
10621
|
const { fileURLToPath: fileURLToPath4 } = await import("url");
|
|
10252
10622
|
const thisFile = fileURLToPath4(import.meta.url);
|
|
10253
|
-
const backfillPath =
|
|
10254
|
-
if (
|
|
10623
|
+
const backfillPath = path24.resolve(path24.dirname(thisFile), "backfill-vectors.js");
|
|
10624
|
+
if (existsSync20(backfillPath)) {
|
|
10255
10625
|
const { openSync: openSync3, closeSync: closeSync3 } = await import("fs");
|
|
10256
|
-
const workerLogPath =
|
|
10626
|
+
const workerLogPath = path24.join(EXE_AI_DIR, "workers.log");
|
|
10257
10627
|
let stderrFd = "ignore";
|
|
10258
10628
|
try {
|
|
10259
10629
|
stderrFd = openSync3(workerLogPath, "a");
|
|
@@ -10283,8 +10653,8 @@ async function boot(options) {
|
|
|
10283
10653
|
const criticalBinaries = ["backfill-vectors.js", "scan-tasks.js"];
|
|
10284
10654
|
const missing = [];
|
|
10285
10655
|
for (const bin of criticalBinaries) {
|
|
10286
|
-
const binPath =
|
|
10287
|
-
if (!
|
|
10656
|
+
const binPath = path24.resolve(path24.dirname(thisFile), bin);
|
|
10657
|
+
if (!existsSync20(binPath)) {
|
|
10288
10658
|
missing.push(`dist/bin/${bin}`);
|
|
10289
10659
|
}
|
|
10290
10660
|
}
|
|
@@ -10313,7 +10683,7 @@ async function boot(options) {
|
|
|
10313
10683
|
console.log(brief);
|
|
10314
10684
|
return;
|
|
10315
10685
|
}
|
|
10316
|
-
const sessionDir =
|
|
10686
|
+
const sessionDir = path24.join(EXE_AI_DIR, "sessions", coordinatorName);
|
|
10317
10687
|
await mkdir5(sessionDir, { recursive: true });
|
|
10318
10688
|
const claudeMdContent = `${getSessionPrompt(coordinatorEmployee.systemPrompt)}
|
|
10319
10689
|
|
|
@@ -10322,7 +10692,7 @@ async function boot(options) {
|
|
|
10322
10692
|
# Status Brief
|
|
10323
10693
|
|
|
10324
10694
|
${brief}`;
|
|
10325
|
-
await writeFile6(
|
|
10695
|
+
await writeFile6(path24.join(sessionDir, "CLAUDE.md"), claudeMdContent, "utf-8");
|
|
10326
10696
|
const unread = await readUnreadNotifications();
|
|
10327
10697
|
if (unread.length > 0) {
|
|
10328
10698
|
console.log(`\u{1F4EC} ${unread.length} unread notification${unread.length === 1 ? "" : "s"}`);
|
|
@@ -10331,12 +10701,12 @@ ${brief}`;
|
|
|
10331
10701
|
await cleanupOldNotifications();
|
|
10332
10702
|
console.log(brief);
|
|
10333
10703
|
try {
|
|
10334
|
-
const configPath2 =
|
|
10335
|
-
process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
10704
|
+
const configPath2 = path24.join(
|
|
10705
|
+
process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path24.join(os12.homedir(), ".exe-os"),
|
|
10336
10706
|
"config.json"
|
|
10337
10707
|
);
|
|
10338
|
-
if (
|
|
10339
|
-
const rawCfg = JSON.parse(
|
|
10708
|
+
if (existsSync20(configPath2)) {
|
|
10709
|
+
const rawCfg = JSON.parse(readFileSync16(configPath2, "utf8"));
|
|
10340
10710
|
const cloudCfg = rawCfg.cloud;
|
|
10341
10711
|
if (cloudCfg?.apiKey) {
|
|
10342
10712
|
const { initSyncCrypto: initSyncCrypto2, isSyncCryptoInitialized: isSyncCryptoInitialized2 } = await Promise.resolve().then(() => (init_crypto(), crypto_exports));
|