@askexenow/exe-os 0.9.113 → 0.9.115
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/agentic-ontology-backfill.js +36 -12
- package/dist/bin/agentic-reflection-backfill.js +36 -12
- package/dist/bin/agentic-semantic-label.js +36 -12
- package/dist/bin/backfill-conversations.js +36 -12
- package/dist/bin/backfill-responses.js +36 -12
- package/dist/bin/backfill-vectors.js +36 -12
- package/dist/bin/bulk-sync-postgres.js +36 -12
- package/dist/bin/cleanup-stale-review-tasks.js +470 -113
- package/dist/bin/cli.js +413 -62
- package/dist/bin/exe-agent.js +27 -0
- package/dist/bin/exe-assign.js +36 -12
- package/dist/bin/exe-boot.js +246 -54
- package/dist/bin/exe-call.js +8 -0
- package/dist/bin/exe-cloud.js +47 -12
- package/dist/bin/exe-dispatch.js +348 -53
- package/dist/bin/exe-doctor.js +51 -13
- package/dist/bin/exe-export-behaviors.js +37 -12
- package/dist/bin/exe-forget.js +36 -12
- package/dist/bin/exe-gateway.js +348 -53
- package/dist/bin/exe-heartbeat.js +471 -113
- package/dist/bin/exe-kill.js +36 -12
- package/dist/bin/exe-launch-agent.js +117 -18
- package/dist/bin/exe-new-employee.js +9 -1
- package/dist/bin/exe-pending-messages.js +452 -95
- package/dist/bin/exe-pending-notifications.js +452 -95
- package/dist/bin/exe-pending-reviews.js +452 -95
- package/dist/bin/exe-rename.js +36 -12
- package/dist/bin/exe-review.js +36 -12
- package/dist/bin/exe-search.js +37 -12
- package/dist/bin/exe-session-cleanup.js +348 -53
- package/dist/bin/exe-settings.js +12 -0
- package/dist/bin/exe-start-codex.js +46 -13
- package/dist/bin/exe-start-opencode.js +46 -13
- package/dist/bin/exe-status.js +460 -114
- package/dist/bin/exe-support.js +12 -0
- package/dist/bin/exe-team.js +36 -12
- package/dist/bin/git-sweep.js +348 -53
- package/dist/bin/graph-backfill.js +36 -12
- package/dist/bin/graph-export.js +36 -12
- package/dist/bin/install.js +9 -1
- package/dist/bin/intercom-check.js +255 -53
- package/dist/bin/scan-tasks.js +348 -53
- package/dist/bin/setup.js +74 -12
- package/dist/bin/shard-migrate.js +36 -12
- package/dist/gateway/index.js +348 -53
- package/dist/hooks/bug-report-worker.js +348 -53
- package/dist/hooks/codex-stop-task-finalizer.js +308 -37
- package/dist/hooks/commit-complete.js +348 -53
- package/dist/hooks/error-recall.js +37 -12
- package/dist/hooks/ingest.js +363 -54
- package/dist/hooks/instructions-loaded.js +36 -12
- package/dist/hooks/notification.js +36 -12
- package/dist/hooks/post-compact.js +426 -72
- package/dist/hooks/post-tool-combined.js +501 -146
- package/dist/hooks/pre-compact.js +348 -53
- package/dist/hooks/pre-tool-use.js +92 -13
- package/dist/hooks/prompt-submit.js +348 -53
- package/dist/hooks/session-end.js +158 -53
- package/dist/hooks/session-start.js +66 -13
- package/dist/hooks/stop.js +420 -72
- package/dist/hooks/subagent-stop.js +419 -72
- package/dist/hooks/summary-worker.js +442 -121
- package/dist/index.js +375 -53
- package/dist/lib/agent-config.js +8 -0
- package/dist/lib/cloud-sync.js +35 -12
- package/dist/lib/config.js +13 -0
- package/dist/lib/consolidation.js +9 -1
- package/dist/lib/embedder.js +13 -0
- package/dist/lib/employees.js +8 -0
- package/dist/lib/exe-daemon.js +524 -60
- package/dist/lib/hybrid-search.js +37 -12
- package/dist/lib/keychain.js +25 -13
- package/dist/lib/messaging.js +395 -74
- package/dist/lib/schedules.js +36 -12
- package/dist/lib/skill-learning.js +21 -0
- package/dist/lib/store.js +36 -12
- package/dist/lib/tasks.js +324 -41
- package/dist/lib/tmux-routing.js +324 -41
- package/dist/mcp/server.js +374 -54
- package/dist/mcp/tools/create-task.js +324 -41
- package/dist/mcp/tools/list-tasks.js +406 -57
- package/dist/mcp/tools/send-message.js +395 -74
- package/dist/mcp/tools/update-task.js +324 -41
- package/dist/runtime/index.js +375 -53
- package/dist/tui/App.js +377 -55
- package/package.json +1 -1
|
@@ -161,6 +161,17 @@ function normalizeOrchestration(raw) {
|
|
|
161
161
|
const userOrg = raw.orchestration ?? {};
|
|
162
162
|
raw.orchestration = { ...defaultOrg, ...userOrg };
|
|
163
163
|
}
|
|
164
|
+
function normalizeCloudEndpoint(raw) {
|
|
165
|
+
const cloud = raw.cloud;
|
|
166
|
+
if (!cloud?.endpoint) return;
|
|
167
|
+
const ep = String(cloud.endpoint);
|
|
168
|
+
if (ep === "https://askexe.com/cloud" || ep === "https://askexe.com/cloud/") {
|
|
169
|
+
cloud.endpoint = "https://cloud.askexe.com";
|
|
170
|
+
process.stderr.write(
|
|
171
|
+
"[config] Auto-migrated cloud endpoint: askexe.com/cloud \u2192 cloud.askexe.com\n"
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
164
175
|
async function loadConfig() {
|
|
165
176
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
166
177
|
await ensurePrivateDir(dir);
|
|
@@ -186,6 +197,7 @@ async function loadConfig() {
|
|
|
186
197
|
normalizeSessionLifecycle(migratedCfg);
|
|
187
198
|
normalizeAutoUpdate(migratedCfg);
|
|
188
199
|
normalizeOrchestration(migratedCfg);
|
|
200
|
+
normalizeCloudEndpoint(migratedCfg);
|
|
189
201
|
const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
|
|
190
202
|
if (config.dbPath.startsWith("~")) {
|
|
191
203
|
config.dbPath = config.dbPath.replace(/^~/, os.homedir());
|
|
@@ -214,6 +226,7 @@ function loadConfigSync() {
|
|
|
214
226
|
normalizeSessionLifecycle(migratedCfg);
|
|
215
227
|
normalizeAutoUpdate(migratedCfg);
|
|
216
228
|
normalizeOrchestration(migratedCfg);
|
|
229
|
+
normalizeCloudEndpoint(migratedCfg);
|
|
217
230
|
const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
|
|
218
231
|
if (config.dbPath.startsWith("~")) {
|
|
219
232
|
config.dbPath = config.dbPath.replace(/^~/, os.homedir());
|
|
@@ -374,6 +387,7 @@ __export(agent_config_exports, {
|
|
|
374
387
|
clearAgentRuntime: () => clearAgentRuntime,
|
|
375
388
|
getAgentRuntime: () => getAgentRuntime,
|
|
376
389
|
loadAgentConfig: () => loadAgentConfig,
|
|
390
|
+
normalizeCcModelName: () => normalizeCcModelName,
|
|
377
391
|
saveAgentConfig: () => saveAgentConfig,
|
|
378
392
|
setAgentMcps: () => setAgentMcps,
|
|
379
393
|
setAgentRuntime: () => setAgentRuntime
|
|
@@ -402,6 +416,13 @@ function getAgentRuntime(agentId) {
|
|
|
402
416
|
if (orgDefault) return orgDefault;
|
|
403
417
|
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
404
418
|
}
|
|
419
|
+
function normalizeCcModelName(model) {
|
|
420
|
+
let ccModel = model.replace(/(\d+)\.(\d+)/g, "$1-$2");
|
|
421
|
+
if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
|
|
422
|
+
ccModel += "[1m]";
|
|
423
|
+
}
|
|
424
|
+
return ccModel;
|
|
425
|
+
}
|
|
405
426
|
function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
|
|
406
427
|
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
407
428
|
if (!knownModels) {
|
|
@@ -1103,6 +1124,7 @@ var init_provider_table = __esm({
|
|
|
1103
1124
|
// src/lib/intercom-queue.ts
|
|
1104
1125
|
var intercom_queue_exports = {};
|
|
1105
1126
|
__export(intercom_queue_exports, {
|
|
1127
|
+
_resetDrainGuard: () => _resetDrainGuard,
|
|
1106
1128
|
clearQueueForAgent: () => clearQueueForAgent,
|
|
1107
1129
|
drainForSession: () => drainForSession,
|
|
1108
1130
|
drainQueue: () => drainQueue,
|
|
@@ -1148,38 +1170,47 @@ function queueIntercom(targetSession, reason) {
|
|
|
1148
1170
|
writeQueue(queue);
|
|
1149
1171
|
}
|
|
1150
1172
|
function drainQueue(isSessionBusy2, sendKeys) {
|
|
1173
|
+
if (_draining) {
|
|
1174
|
+
logQueue("SKIP_DRAIN \u2014 previous drain still running (possible tmux hang)");
|
|
1175
|
+
return { drained: 0, failed: 0 };
|
|
1176
|
+
}
|
|
1151
1177
|
const queue = readQueue();
|
|
1152
1178
|
if (queue.length === 0) return { drained: 0, failed: 0 };
|
|
1179
|
+
_draining = true;
|
|
1153
1180
|
const remaining = [];
|
|
1154
1181
|
let drained = 0;
|
|
1155
1182
|
let failed = 0;
|
|
1156
|
-
|
|
1157
|
-
const
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1183
|
+
try {
|
|
1184
|
+
for (const item of queue) {
|
|
1185
|
+
const age = Date.now() - new Date(item.queuedAt).getTime();
|
|
1186
|
+
if (age > TTL_MS) {
|
|
1187
|
+
logQueue(`EXPIRED \u2192 ${item.targetSession} (${Math.round(age / 6e4)}min old, reason: ${item.reason})`);
|
|
1188
|
+
failed++;
|
|
1189
|
+
continue;
|
|
1190
|
+
}
|
|
1191
|
+
try {
|
|
1192
|
+
if (!isSessionBusy2(item.targetSession)) {
|
|
1193
|
+
const success = sendKeys(item.targetSession);
|
|
1194
|
+
if (success) {
|
|
1195
|
+
logQueue(`DRAINED \u2192 ${item.targetSession} (after ${item.attempts} retries)`);
|
|
1196
|
+
drained++;
|
|
1197
|
+
continue;
|
|
1198
|
+
}
|
|
1170
1199
|
}
|
|
1200
|
+
} catch {
|
|
1171
1201
|
}
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1202
|
+
item.attempts++;
|
|
1203
|
+
if (item.attempts >= MAX_RETRIES) {
|
|
1204
|
+
logQueue(`FAILED \u2192 ${item.targetSession} (${MAX_RETRIES} retries exhausted, reason: ${item.reason})`);
|
|
1205
|
+
failed++;
|
|
1206
|
+
continue;
|
|
1207
|
+
}
|
|
1208
|
+
remaining.push(item);
|
|
1179
1209
|
}
|
|
1180
|
-
remaining
|
|
1210
|
+
writeQueue(remaining);
|
|
1211
|
+
} finally {
|
|
1212
|
+
_draining = false;
|
|
1181
1213
|
}
|
|
1182
|
-
writeQueue(remaining);
|
|
1183
1214
|
return { drained, failed };
|
|
1184
1215
|
}
|
|
1185
1216
|
function drainForSession(targetSession, sendKeys) {
|
|
@@ -1204,6 +1235,9 @@ function clearQueueForAgent(agentName) {
|
|
|
1204
1235
|
logQueue(`CLEARED ${before - filtered.length} stale item(s) for ${agentName}`);
|
|
1205
1236
|
}
|
|
1206
1237
|
}
|
|
1238
|
+
function _resetDrainGuard() {
|
|
1239
|
+
_draining = false;
|
|
1240
|
+
}
|
|
1207
1241
|
function logQueue(msg) {
|
|
1208
1242
|
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] [queue] ${msg}
|
|
1209
1243
|
`;
|
|
@@ -1215,13 +1249,14 @@ function logQueue(msg) {
|
|
|
1215
1249
|
} catch {
|
|
1216
1250
|
}
|
|
1217
1251
|
}
|
|
1218
|
-
var QUEUE_PATH, MAX_RETRIES, TTL_MS, INTERCOM_LOG;
|
|
1252
|
+
var QUEUE_PATH, MAX_RETRIES, TTL_MS, _draining, INTERCOM_LOG;
|
|
1219
1253
|
var init_intercom_queue = __esm({
|
|
1220
1254
|
"src/lib/intercom-queue.ts"() {
|
|
1221
1255
|
"use strict";
|
|
1222
1256
|
QUEUE_PATH = path6.join(os5.homedir(), ".exe-os", "intercom-queue.json");
|
|
1223
1257
|
MAX_RETRIES = 5;
|
|
1224
1258
|
TTL_MS = 60 * 60 * 1e3;
|
|
1259
|
+
_draining = false;
|
|
1225
1260
|
INTERCOM_LOG = path6.join(os5.homedir(), ".exe-os", "intercom.log");
|
|
1226
1261
|
}
|
|
1227
1262
|
});
|
|
@@ -2426,7 +2461,13 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
|
|
|
2426
2461
|
taskFile
|
|
2427
2462
|
});
|
|
2428
2463
|
const originalPriority = String(row.priority).toLowerCase();
|
|
2429
|
-
const
|
|
2464
|
+
const resultLower = result?.toLowerCase() ?? "";
|
|
2465
|
+
const hasTestEvidence = (
|
|
2466
|
+
// Vitest/Jest output patterns (hard to fake without actually running tests)
|
|
2467
|
+
/\d+\s+pass(ed|ing)/.test(resultLower) || /test files?\s+\d+\s+passed/.test(resultLower) || /tests?\s+\d+\s+passed/.test(resultLower)
|
|
2468
|
+
);
|
|
2469
|
+
const hasNoFailures = !/fail(ed|ure|ing)|error/i.test(resultLower);
|
|
2470
|
+
const autoApprove = originalPriority === "p2" && hasTestEvidence && hasNoFailures;
|
|
2430
2471
|
if (!autoApprove) {
|
|
2431
2472
|
try {
|
|
2432
2473
|
const key = getSessionKey();
|
|
@@ -2434,6 +2475,13 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
|
|
|
2434
2475
|
if (exeSession) {
|
|
2435
2476
|
sendIntercom(exeSession);
|
|
2436
2477
|
}
|
|
2478
|
+
if (reviewer && reviewer !== coordinatorName && reviewer !== exeSession) {
|
|
2479
|
+
const { employeeSessionName: employeeSessionName2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
2480
|
+
if (exeSession) {
|
|
2481
|
+
const reviewerSession = employeeSessionName2(reviewer, exeSession);
|
|
2482
|
+
sendIntercom(reviewerSession);
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2437
2485
|
} catch {
|
|
2438
2486
|
}
|
|
2439
2487
|
}
|
|
@@ -2564,18 +2612,31 @@ function acquireSpawnLock(sessionName) {
|
|
|
2564
2612
|
mkdirSync7(SPAWN_LOCK_DIR, { recursive: true });
|
|
2565
2613
|
}
|
|
2566
2614
|
const lockFile = spawnLockPath(sessionName);
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2615
|
+
const lockData = JSON.stringify({ pid: process.pid, timestamp: Date.now() });
|
|
2616
|
+
const { openSync: openSync3, closeSync: closeSync3, writeSync } = __require("fs");
|
|
2617
|
+
const { constants } = __require("fs");
|
|
2618
|
+
try {
|
|
2619
|
+
const fd = openSync3(lockFile, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 420);
|
|
2620
|
+
writeSync(fd, lockData);
|
|
2621
|
+
closeSync3(fd);
|
|
2622
|
+
return true;
|
|
2623
|
+
} catch (err) {
|
|
2624
|
+
if (err?.code !== "EEXIST") {
|
|
2625
|
+
return true;
|
|
2575
2626
|
}
|
|
2576
2627
|
}
|
|
2577
|
-
|
|
2578
|
-
|
|
2628
|
+
try {
|
|
2629
|
+
const lock = JSON.parse(readFileSync8(lockFile, "utf8"));
|
|
2630
|
+
const age = Date.now() - lock.timestamp;
|
|
2631
|
+
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
2632
|
+
return false;
|
|
2633
|
+
}
|
|
2634
|
+
writeFileSync6(lockFile, lockData);
|
|
2635
|
+
return true;
|
|
2636
|
+
} catch {
|
|
2637
|
+
writeFileSync6(lockFile, lockData);
|
|
2638
|
+
return true;
|
|
2639
|
+
}
|
|
2579
2640
|
}
|
|
2580
2641
|
function releaseSpawnLock(sessionName) {
|
|
2581
2642
|
try {
|
|
@@ -2654,6 +2715,21 @@ function parseParentExe(sessionName, agentId) {
|
|
|
2654
2715
|
function extractRootExe(name) {
|
|
2655
2716
|
if (!name) return null;
|
|
2656
2717
|
if (!name.includes("-")) return name;
|
|
2718
|
+
try {
|
|
2719
|
+
const roster = (init_employees(), __toCommonJS(employees_exports)).loadEmployeesSync();
|
|
2720
|
+
if (roster.length > 0) {
|
|
2721
|
+
const sortedNames = roster.map((e) => e.name).sort((a, b) => b.length - a.length);
|
|
2722
|
+
for (const agentName of sortedNames) {
|
|
2723
|
+
const escaped = agentName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2724
|
+
const regex = new RegExp(`^${escaped}\\d*-(.+)$`);
|
|
2725
|
+
const match = name.match(regex);
|
|
2726
|
+
if (match) {
|
|
2727
|
+
return extractRootExe(match[1]);
|
|
2728
|
+
}
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2731
|
+
} catch {
|
|
2732
|
+
}
|
|
2657
2733
|
const parts = name.split("-").filter(Boolean);
|
|
2658
2734
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
2659
2735
|
}
|
|
@@ -2672,6 +2748,10 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
2672
2748
|
function getParentExe(sessionKey) {
|
|
2673
2749
|
try {
|
|
2674
2750
|
const data = JSON.parse(readFileSync8(path11.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
2751
|
+
if (data.registeredAt) {
|
|
2752
|
+
const age = Date.now() - new Date(data.registeredAt).getTime();
|
|
2753
|
+
if (age > PARENT_EXE_CACHE_TTL_MS) return null;
|
|
2754
|
+
}
|
|
2675
2755
|
return data.parentExe || null;
|
|
2676
2756
|
} catch {
|
|
2677
2757
|
return null;
|
|
@@ -3220,7 +3300,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3220
3300
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
3221
3301
|
} catch {
|
|
3222
3302
|
}
|
|
3223
|
-
let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName}`;
|
|
3303
|
+
let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName} EXE_SESSION_START_ISO=${(/* @__PURE__ */ new Date()).toISOString()}`;
|
|
3224
3304
|
if (ccProvider !== DEFAULT_PROVIDER) {
|
|
3225
3305
|
const cfg = PROVIDER_TABLE[ccProvider];
|
|
3226
3306
|
if (cfg?.apiKeyEnv) {
|
|
@@ -3255,10 +3335,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3255
3335
|
}
|
|
3256
3336
|
if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
|
|
3257
3337
|
if (agentRtConfig.runtime === "claude" && agentRtConfig.model) {
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
ccModel += "[1m]";
|
|
3261
|
-
}
|
|
3338
|
+
const { normalizeCcModelName: normalizeCcModelName2 } = (init_agent_config(), __toCommonJS(agent_config_exports));
|
|
3339
|
+
const ccModel = normalizeCcModelName2(agentRtConfig.model);
|
|
3262
3340
|
envPrefix = `${envPrefix} ANTHROPIC_MODEL=${ccModel}`;
|
|
3263
3341
|
}
|
|
3264
3342
|
}
|
|
@@ -3359,7 +3437,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3359
3437
|
releaseSpawnLock(sessionName);
|
|
3360
3438
|
return { sessionName };
|
|
3361
3439
|
}
|
|
3362
|
-
var SPAWN_LOCK_DIR, SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, VALID_SESSION_NAME, VERIFY_PANE_LINES, INTERCOM_DEBOUNCE_MS, CODEX_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
3440
|
+
var SPAWN_LOCK_DIR, SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, VALID_SESSION_NAME, PARENT_EXE_CACHE_TTL_MS, VERIFY_PANE_LINES, INTERCOM_DEBOUNCE_MS, CODEX_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
3363
3441
|
var init_tmux_routing = __esm({
|
|
3364
3442
|
"src/lib/tmux-routing.ts"() {
|
|
3365
3443
|
"use strict";
|
|
@@ -3379,6 +3457,7 @@ var init_tmux_routing = __esm({
|
|
|
3379
3457
|
SESSION_CACHE = path11.join(os8.homedir(), ".exe-os", "session-cache");
|
|
3380
3458
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
3381
3459
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
3460
|
+
PARENT_EXE_CACHE_TTL_MS = 4 * 60 * 60 * 1e3;
|
|
3382
3461
|
VERIFY_PANE_LINES = 200;
|
|
3383
3462
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
3384
3463
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
@@ -3423,6 +3502,17 @@ var init_task_scope = __esm({
|
|
|
3423
3502
|
});
|
|
3424
3503
|
|
|
3425
3504
|
// src/lib/notifications.ts
|
|
3505
|
+
var notifications_exports = {};
|
|
3506
|
+
__export(notifications_exports, {
|
|
3507
|
+
cleanupOldNotifications: () => cleanupOldNotifications,
|
|
3508
|
+
formatNotifications: () => formatNotifications,
|
|
3509
|
+
markAsRead: () => markAsRead,
|
|
3510
|
+
markAsReadByTaskFile: () => markAsReadByTaskFile,
|
|
3511
|
+
markDoneTaskNotificationsAsRead: () => markDoneTaskNotificationsAsRead,
|
|
3512
|
+
migrateJsonNotifications: () => migrateJsonNotifications,
|
|
3513
|
+
readUnreadNotifications: () => readUnreadNotifications,
|
|
3514
|
+
writeNotification: () => writeNotification
|
|
3515
|
+
});
|
|
3426
3516
|
import crypto2 from "crypto";
|
|
3427
3517
|
import path12 from "path";
|
|
3428
3518
|
import os9 from "os";
|
|
@@ -3459,6 +3549,52 @@ async function writeNotification(notification) {
|
|
|
3459
3549
|
`);
|
|
3460
3550
|
}
|
|
3461
3551
|
}
|
|
3552
|
+
async function readUnreadNotifications(agentFilter, sessionScope) {
|
|
3553
|
+
try {
|
|
3554
|
+
const client = getClient();
|
|
3555
|
+
const conditions = ["read = 0"];
|
|
3556
|
+
const args = [];
|
|
3557
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
3558
|
+
if (agentFilter) {
|
|
3559
|
+
conditions.push("agent_id = ?");
|
|
3560
|
+
args.push(agentFilter);
|
|
3561
|
+
}
|
|
3562
|
+
const result = await client.execute({
|
|
3563
|
+
sql: `SELECT id, agent_id, agent_role, event, project, summary, task_file, session_scope, created_at
|
|
3564
|
+
FROM notifications
|
|
3565
|
+
WHERE ${conditions.join(" AND ")}${scope.sql}
|
|
3566
|
+
ORDER BY created_at ASC`,
|
|
3567
|
+
args: [...args, ...scope.args]
|
|
3568
|
+
});
|
|
3569
|
+
return result.rows.map((r) => ({
|
|
3570
|
+
id: String(r.id),
|
|
3571
|
+
agentId: String(r.agent_id),
|
|
3572
|
+
agentRole: String(r.agent_role),
|
|
3573
|
+
event: String(r.event),
|
|
3574
|
+
project: String(r.project),
|
|
3575
|
+
summary: String(r.summary),
|
|
3576
|
+
taskFile: r.task_file ? String(r.task_file) : void 0,
|
|
3577
|
+
sessionScope: r.session_scope == null ? null : String(r.session_scope),
|
|
3578
|
+
timestamp: String(r.created_at),
|
|
3579
|
+
read: false
|
|
3580
|
+
}));
|
|
3581
|
+
} catch {
|
|
3582
|
+
return [];
|
|
3583
|
+
}
|
|
3584
|
+
}
|
|
3585
|
+
async function markAsRead(ids, sessionScope) {
|
|
3586
|
+
if (ids.length === 0) return;
|
|
3587
|
+
try {
|
|
3588
|
+
const client = getClient();
|
|
3589
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
3590
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
3591
|
+
await client.execute({
|
|
3592
|
+
sql: `UPDATE notifications SET read = 1 WHERE id IN (${placeholders})${scope.sql}`,
|
|
3593
|
+
args: [...ids, ...scope.args]
|
|
3594
|
+
});
|
|
3595
|
+
} catch {
|
|
3596
|
+
}
|
|
3597
|
+
}
|
|
3462
3598
|
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
3463
3599
|
try {
|
|
3464
3600
|
const client = getClient();
|
|
@@ -3471,11 +3607,144 @@ async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
|
3471
3607
|
} catch {
|
|
3472
3608
|
}
|
|
3473
3609
|
}
|
|
3610
|
+
async function cleanupOldNotifications(daysOld = CLEANUP_DAYS, sessionScope) {
|
|
3611
|
+
try {
|
|
3612
|
+
const client = getClient();
|
|
3613
|
+
const cutoff = new Date(
|
|
3614
|
+
Date.now() - daysOld * 24 * 60 * 60 * 1e3
|
|
3615
|
+
).toISOString();
|
|
3616
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
3617
|
+
const result = await client.execute({
|
|
3618
|
+
sql: `DELETE FROM notifications WHERE created_at < ?${scope.sql}`,
|
|
3619
|
+
args: [cutoff, ...scope.args]
|
|
3620
|
+
});
|
|
3621
|
+
return result.rowsAffected;
|
|
3622
|
+
} catch {
|
|
3623
|
+
return 0;
|
|
3624
|
+
}
|
|
3625
|
+
}
|
|
3626
|
+
async function markDoneTaskNotificationsAsRead(sessionScope) {
|
|
3627
|
+
try {
|
|
3628
|
+
const client = getClient();
|
|
3629
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
3630
|
+
const result = await client.execute({
|
|
3631
|
+
sql: `UPDATE notifications SET read = 1
|
|
3632
|
+
WHERE read = 0
|
|
3633
|
+
AND task_file IS NOT NULL
|
|
3634
|
+
${scope.sql}
|
|
3635
|
+
AND task_file IN (
|
|
3636
|
+
SELECT task_file FROM tasks WHERE status = 'done'${scope.sql}
|
|
3637
|
+
)`,
|
|
3638
|
+
args: [...scope.args, ...scope.args]
|
|
3639
|
+
});
|
|
3640
|
+
return result.rowsAffected;
|
|
3641
|
+
} catch {
|
|
3642
|
+
return 0;
|
|
3643
|
+
}
|
|
3644
|
+
}
|
|
3645
|
+
function formatNotifications(notifications) {
|
|
3646
|
+
if (notifications.length === 0) return "";
|
|
3647
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
3648
|
+
for (const n of notifications) {
|
|
3649
|
+
const key = `${n.agentId}|${n.agentRole}`;
|
|
3650
|
+
if (!grouped.has(key)) grouped.set(key, []);
|
|
3651
|
+
grouped.get(key).push(n);
|
|
3652
|
+
}
|
|
3653
|
+
const lines = [];
|
|
3654
|
+
lines.push(`## Notifications (${notifications.length} unread)
|
|
3655
|
+
`);
|
|
3656
|
+
for (const [key, items] of grouped) {
|
|
3657
|
+
const [agentId, agentRole] = key.split("|");
|
|
3658
|
+
lines.push(`**${agentId}** (${agentRole}):`);
|
|
3659
|
+
for (const item of items) {
|
|
3660
|
+
const ago = formatTimeAgo(item.timestamp);
|
|
3661
|
+
const icon = eventIcon(item.event);
|
|
3662
|
+
lines.push(`- ${icon} ${item.summary} (${item.project}) \u2014 ${ago}`);
|
|
3663
|
+
}
|
|
3664
|
+
lines.push("");
|
|
3665
|
+
}
|
|
3666
|
+
return lines.join("\n");
|
|
3667
|
+
}
|
|
3668
|
+
async function migrateJsonNotifications() {
|
|
3669
|
+
const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR || path12.join(os9.homedir(), ".exe-os");
|
|
3670
|
+
const notifDir = path12.join(base, "notifications");
|
|
3671
|
+
if (!existsSync13(notifDir)) return 0;
|
|
3672
|
+
let migrated = 0;
|
|
3673
|
+
try {
|
|
3674
|
+
const files = readdirSync3(notifDir).filter((f) => f.endsWith(".json"));
|
|
3675
|
+
if (files.length === 0) return 0;
|
|
3676
|
+
const client = getClient();
|
|
3677
|
+
for (const file of files) {
|
|
3678
|
+
try {
|
|
3679
|
+
const filePath = path12.join(notifDir, file);
|
|
3680
|
+
const data = JSON.parse(readFileSync9(filePath, "utf8"));
|
|
3681
|
+
await client.execute({
|
|
3682
|
+
sql: `INSERT OR IGNORE INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
3683
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
3684
|
+
args: [
|
|
3685
|
+
crypto2.randomUUID(),
|
|
3686
|
+
data.agentId ?? "unknown",
|
|
3687
|
+
data.agentRole ?? "unknown",
|
|
3688
|
+
data.event ?? "session_summary",
|
|
3689
|
+
data.project ?? "unknown",
|
|
3690
|
+
data.summary ?? "",
|
|
3691
|
+
data.taskFile ?? null,
|
|
3692
|
+
null,
|
|
3693
|
+
data.read ? 1 : 0,
|
|
3694
|
+
data.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
3695
|
+
]
|
|
3696
|
+
});
|
|
3697
|
+
unlinkSync5(filePath);
|
|
3698
|
+
migrated++;
|
|
3699
|
+
} catch {
|
|
3700
|
+
}
|
|
3701
|
+
}
|
|
3702
|
+
try {
|
|
3703
|
+
const remaining = readdirSync3(notifDir);
|
|
3704
|
+
if (remaining.length === 0) {
|
|
3705
|
+
rmdirSync(notifDir);
|
|
3706
|
+
}
|
|
3707
|
+
} catch {
|
|
3708
|
+
}
|
|
3709
|
+
} catch {
|
|
3710
|
+
}
|
|
3711
|
+
return migrated;
|
|
3712
|
+
}
|
|
3713
|
+
function eventIcon(event) {
|
|
3714
|
+
switch (event) {
|
|
3715
|
+
case "task_complete":
|
|
3716
|
+
return "Completed:";
|
|
3717
|
+
case "task_needs_fix":
|
|
3718
|
+
return "Needs fix:";
|
|
3719
|
+
case "session_summary":
|
|
3720
|
+
return "Session:";
|
|
3721
|
+
case "error_spike":
|
|
3722
|
+
return "Errors:";
|
|
3723
|
+
case "orphan_task":
|
|
3724
|
+
return "Orphan:";
|
|
3725
|
+
case "subtasks_complete":
|
|
3726
|
+
return "Subtasks done:";
|
|
3727
|
+
case "capacity_relaunch":
|
|
3728
|
+
return "Relaunched:";
|
|
3729
|
+
}
|
|
3730
|
+
}
|
|
3731
|
+
function formatTimeAgo(timestamp) {
|
|
3732
|
+
const diffMs = Date.now() - new Date(timestamp).getTime();
|
|
3733
|
+
const mins = Math.floor(diffMs / 6e4);
|
|
3734
|
+
if (mins < 1) return "just now";
|
|
3735
|
+
if (mins < 60) return `${mins}m ago`;
|
|
3736
|
+
const hours = Math.floor(mins / 60);
|
|
3737
|
+
if (hours < 24) return `${hours}h ago`;
|
|
3738
|
+
const days = Math.floor(hours / 24);
|
|
3739
|
+
return `${days}d ago`;
|
|
3740
|
+
}
|
|
3741
|
+
var CLEANUP_DAYS;
|
|
3474
3742
|
var init_notifications = __esm({
|
|
3475
3743
|
"src/lib/notifications.ts"() {
|
|
3476
3744
|
"use strict";
|
|
3477
3745
|
init_database();
|
|
3478
3746
|
init_task_scope();
|
|
3747
|
+
CLEANUP_DAYS = 7;
|
|
3479
3748
|
}
|
|
3480
3749
|
});
|
|
3481
3750
|
|
|
@@ -5356,6 +5625,20 @@ async function updateTask(input) {
|
|
|
5356
5625
|
notifyTaskDone();
|
|
5357
5626
|
}
|
|
5358
5627
|
await markTaskNotificationsRead(taskFile);
|
|
5628
|
+
if (input.status === "needs_review" && !isCoordinator) {
|
|
5629
|
+
try {
|
|
5630
|
+
const { writeNotification: writeNotification2 } = await Promise.resolve().then(() => (init_notifications(), notifications_exports));
|
|
5631
|
+
await writeNotification2({
|
|
5632
|
+
agentId: String(row.assigned_to),
|
|
5633
|
+
agentRole: String(row.assigned_to),
|
|
5634
|
+
event: "task_complete",
|
|
5635
|
+
project: String(row.project_name),
|
|
5636
|
+
summary: `"${String(row.title)}" is ready for review`,
|
|
5637
|
+
taskFile
|
|
5638
|
+
});
|
|
5639
|
+
} catch {
|
|
5640
|
+
}
|
|
5641
|
+
}
|
|
5359
5642
|
if (input.status === "done" || input.status === "closed") {
|
|
5360
5643
|
try {
|
|
5361
5644
|
await cascadeUnblock(taskId, input.baseDir, now);
|