@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
|
@@ -219,6 +219,17 @@ function normalizeOrchestration(raw) {
|
|
|
219
219
|
const userOrg = raw.orchestration ?? {};
|
|
220
220
|
raw.orchestration = { ...defaultOrg, ...userOrg };
|
|
221
221
|
}
|
|
222
|
+
function normalizeCloudEndpoint(raw) {
|
|
223
|
+
const cloud = raw.cloud;
|
|
224
|
+
if (!cloud?.endpoint) return;
|
|
225
|
+
const ep = String(cloud.endpoint);
|
|
226
|
+
if (ep === "https://askexe.com/cloud" || ep === "https://askexe.com/cloud/") {
|
|
227
|
+
cloud.endpoint = "https://cloud.askexe.com";
|
|
228
|
+
process.stderr.write(
|
|
229
|
+
"[config] Auto-migrated cloud endpoint: askexe.com/cloud \u2192 cloud.askexe.com\n"
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
222
233
|
async function loadConfig() {
|
|
223
234
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
224
235
|
await ensurePrivateDir(dir);
|
|
@@ -244,6 +255,7 @@ async function loadConfig() {
|
|
|
244
255
|
normalizeSessionLifecycle(migratedCfg);
|
|
245
256
|
normalizeAutoUpdate(migratedCfg);
|
|
246
257
|
normalizeOrchestration(migratedCfg);
|
|
258
|
+
normalizeCloudEndpoint(migratedCfg);
|
|
247
259
|
const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
|
|
248
260
|
if (config.dbPath.startsWith("~")) {
|
|
249
261
|
config.dbPath = config.dbPath.replace(/^~/, os.homedir());
|
|
@@ -272,6 +284,7 @@ function loadConfigSync() {
|
|
|
272
284
|
normalizeSessionLifecycle(migratedCfg);
|
|
273
285
|
normalizeAutoUpdate(migratedCfg);
|
|
274
286
|
normalizeOrchestration(migratedCfg);
|
|
287
|
+
normalizeCloudEndpoint(migratedCfg);
|
|
275
288
|
const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
|
|
276
289
|
if (config.dbPath.startsWith("~")) {
|
|
277
290
|
config.dbPath = config.dbPath.replace(/^~/, os.homedir());
|
|
@@ -432,6 +445,7 @@ __export(agent_config_exports, {
|
|
|
432
445
|
clearAgentRuntime: () => clearAgentRuntime,
|
|
433
446
|
getAgentRuntime: () => getAgentRuntime,
|
|
434
447
|
loadAgentConfig: () => loadAgentConfig,
|
|
448
|
+
normalizeCcModelName: () => normalizeCcModelName,
|
|
435
449
|
saveAgentConfig: () => saveAgentConfig,
|
|
436
450
|
setAgentMcps: () => setAgentMcps,
|
|
437
451
|
setAgentRuntime: () => setAgentRuntime
|
|
@@ -460,6 +474,13 @@ function getAgentRuntime(agentId) {
|
|
|
460
474
|
if (orgDefault) return orgDefault;
|
|
461
475
|
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
462
476
|
}
|
|
477
|
+
function normalizeCcModelName(model) {
|
|
478
|
+
let ccModel = model.replace(/(\d+)\.(\d+)/g, "$1-$2");
|
|
479
|
+
if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
|
|
480
|
+
ccModel += "[1m]";
|
|
481
|
+
}
|
|
482
|
+
return ccModel;
|
|
483
|
+
}
|
|
463
484
|
function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
|
|
464
485
|
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
465
486
|
if (!knownModels) {
|
|
@@ -3676,7 +3697,7 @@ var init_database = __esm({
|
|
|
3676
3697
|
});
|
|
3677
3698
|
|
|
3678
3699
|
// src/lib/keychain.ts
|
|
3679
|
-
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
3700
|
+
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2, rename, copyFile } from "fs/promises";
|
|
3680
3701
|
import { existsSync as existsSync8, statSync as statSync3 } from "fs";
|
|
3681
3702
|
import { execSync as execSync3 } from "child_process";
|
|
3682
3703
|
import path7 from "path";
|
|
@@ -3711,12 +3732,14 @@ function linuxSecretAvailable() {
|
|
|
3711
3732
|
function isRootOnlyTrustedServerKeyFile(keyPath) {
|
|
3712
3733
|
if (process.platform !== "linux") return false;
|
|
3713
3734
|
try {
|
|
3714
|
-
const uid = typeof os5.userInfo().uid === "number" ? os5.userInfo().uid : -1;
|
|
3715
3735
|
const st = statSync3(keyPath);
|
|
3716
3736
|
if (!st.isFile() || (st.mode & 63) !== 0) return false;
|
|
3737
|
+
const uid = typeof os5.userInfo().uid === "number" ? os5.userInfo().uid : -1;
|
|
3717
3738
|
if (uid === 0) return true;
|
|
3718
3739
|
const exeOsDir = process.env.EXE_OS_DIR;
|
|
3719
|
-
|
|
3740
|
+
if (exeOsDir && path7.resolve(keyPath).startsWith(path7.resolve(exeOsDir) + path7.sep)) return true;
|
|
3741
|
+
if (!linuxSecretAvailable()) return true;
|
|
3742
|
+
return false;
|
|
3720
3743
|
} catch {
|
|
3721
3744
|
return false;
|
|
3722
3745
|
}
|
|
@@ -3866,15 +3889,25 @@ async function writeMachineBoundFileFallback(b64) {
|
|
|
3866
3889
|
await mkdir3(dir, { recursive: true });
|
|
3867
3890
|
const keyPath = getKeyPath();
|
|
3868
3891
|
const machineKey = deriveMachineKey();
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3892
|
+
const content = machineKey ? encryptWithMachineKey(b64, machineKey) + "\n" : b64 + "\n";
|
|
3893
|
+
const result = machineKey ? "encrypted" : "plaintext";
|
|
3894
|
+
const tmpPath = keyPath + ".tmp";
|
|
3895
|
+
try {
|
|
3896
|
+
if (existsSync8(keyPath)) {
|
|
3897
|
+
await copyFile(keyPath, keyPath + ".bak").catch(() => {
|
|
3898
|
+
});
|
|
3899
|
+
}
|
|
3900
|
+
await writeFile3(tmpPath, content, "utf-8");
|
|
3901
|
+
await chmod2(tmpPath, 384);
|
|
3902
|
+
await rename(tmpPath, keyPath);
|
|
3903
|
+
} catch (err) {
|
|
3904
|
+
try {
|
|
3905
|
+
await unlink(tmpPath);
|
|
3906
|
+
} catch {
|
|
3907
|
+
}
|
|
3908
|
+
throw err;
|
|
3874
3909
|
}
|
|
3875
|
-
|
|
3876
|
-
await chmod2(keyPath, 384);
|
|
3877
|
-
return "plaintext";
|
|
3910
|
+
return result;
|
|
3878
3911
|
}
|
|
3879
3912
|
async function getMasterKey() {
|
|
3880
3913
|
let nativeValue = macKeychainGet() ?? linuxSecretGet();
|
|
@@ -3941,7 +3974,7 @@ async function getMasterKey() {
|
|
|
3941
3974
|
b64Value = content;
|
|
3942
3975
|
}
|
|
3943
3976
|
const key = Buffer.from(b64Value, "base64");
|
|
3944
|
-
if (
|
|
3977
|
+
if (isRootOnlyTrustedServerKeyFile(keyPath)) {
|
|
3945
3978
|
return key;
|
|
3946
3979
|
}
|
|
3947
3980
|
const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
|
|
@@ -6470,6 +6503,7 @@ var init_provider_table = __esm({
|
|
|
6470
6503
|
// src/lib/intercom-queue.ts
|
|
6471
6504
|
var intercom_queue_exports = {};
|
|
6472
6505
|
__export(intercom_queue_exports, {
|
|
6506
|
+
_resetDrainGuard: () => _resetDrainGuard,
|
|
6473
6507
|
clearQueueForAgent: () => clearQueueForAgent,
|
|
6474
6508
|
drainForSession: () => drainForSession,
|
|
6475
6509
|
drainQueue: () => drainQueue,
|
|
@@ -6515,38 +6549,47 @@ function queueIntercom(targetSession, reason) {
|
|
|
6515
6549
|
writeQueue(queue);
|
|
6516
6550
|
}
|
|
6517
6551
|
function drainQueue(isSessionBusy2, sendKeys) {
|
|
6552
|
+
if (_draining) {
|
|
6553
|
+
logQueue("SKIP_DRAIN \u2014 previous drain still running (possible tmux hang)");
|
|
6554
|
+
return { drained: 0, failed: 0 };
|
|
6555
|
+
}
|
|
6518
6556
|
const queue = readQueue();
|
|
6519
6557
|
if (queue.length === 0) return { drained: 0, failed: 0 };
|
|
6558
|
+
_draining = true;
|
|
6520
6559
|
const remaining = [];
|
|
6521
6560
|
let drained = 0;
|
|
6522
6561
|
let failed = 0;
|
|
6523
|
-
|
|
6524
|
-
const
|
|
6525
|
-
|
|
6526
|
-
|
|
6527
|
-
|
|
6528
|
-
|
|
6529
|
-
|
|
6530
|
-
|
|
6531
|
-
|
|
6532
|
-
|
|
6533
|
-
|
|
6534
|
-
|
|
6535
|
-
|
|
6536
|
-
|
|
6562
|
+
try {
|
|
6563
|
+
for (const item of queue) {
|
|
6564
|
+
const age = Date.now() - new Date(item.queuedAt).getTime();
|
|
6565
|
+
if (age > TTL_MS) {
|
|
6566
|
+
logQueue(`EXPIRED \u2192 ${item.targetSession} (${Math.round(age / 6e4)}min old, reason: ${item.reason})`);
|
|
6567
|
+
failed++;
|
|
6568
|
+
continue;
|
|
6569
|
+
}
|
|
6570
|
+
try {
|
|
6571
|
+
if (!isSessionBusy2(item.targetSession)) {
|
|
6572
|
+
const success = sendKeys(item.targetSession);
|
|
6573
|
+
if (success) {
|
|
6574
|
+
logQueue(`DRAINED \u2192 ${item.targetSession} (after ${item.attempts} retries)`);
|
|
6575
|
+
drained++;
|
|
6576
|
+
continue;
|
|
6577
|
+
}
|
|
6537
6578
|
}
|
|
6579
|
+
} catch {
|
|
6538
6580
|
}
|
|
6539
|
-
|
|
6540
|
-
|
|
6541
|
-
|
|
6542
|
-
|
|
6543
|
-
|
|
6544
|
-
|
|
6545
|
-
|
|
6581
|
+
item.attempts++;
|
|
6582
|
+
if (item.attempts >= MAX_RETRIES2) {
|
|
6583
|
+
logQueue(`FAILED \u2192 ${item.targetSession} (${MAX_RETRIES2} retries exhausted, reason: ${item.reason})`);
|
|
6584
|
+
failed++;
|
|
6585
|
+
continue;
|
|
6586
|
+
}
|
|
6587
|
+
remaining.push(item);
|
|
6546
6588
|
}
|
|
6547
|
-
remaining
|
|
6589
|
+
writeQueue(remaining);
|
|
6590
|
+
} finally {
|
|
6591
|
+
_draining = false;
|
|
6548
6592
|
}
|
|
6549
|
-
writeQueue(remaining);
|
|
6550
6593
|
return { drained, failed };
|
|
6551
6594
|
}
|
|
6552
6595
|
function drainForSession(targetSession, sendKeys) {
|
|
@@ -6571,6 +6614,9 @@ function clearQueueForAgent(agentName2) {
|
|
|
6571
6614
|
logQueue(`CLEARED ${before - filtered.length} stale item(s) for ${agentName2}`);
|
|
6572
6615
|
}
|
|
6573
6616
|
}
|
|
6617
|
+
function _resetDrainGuard() {
|
|
6618
|
+
_draining = false;
|
|
6619
|
+
}
|
|
6574
6620
|
function logQueue(msg) {
|
|
6575
6621
|
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] [queue] ${msg}
|
|
6576
6622
|
`;
|
|
@@ -6582,13 +6628,14 @@ function logQueue(msg) {
|
|
|
6582
6628
|
} catch {
|
|
6583
6629
|
}
|
|
6584
6630
|
}
|
|
6585
|
-
var QUEUE_PATH, MAX_RETRIES2, TTL_MS, INTERCOM_LOG;
|
|
6631
|
+
var QUEUE_PATH, MAX_RETRIES2, TTL_MS, _draining, INTERCOM_LOG;
|
|
6586
6632
|
var init_intercom_queue = __esm({
|
|
6587
6633
|
"src/lib/intercom-queue.ts"() {
|
|
6588
6634
|
"use strict";
|
|
6589
6635
|
QUEUE_PATH = path10.join(os7.homedir(), ".exe-os", "intercom-queue.json");
|
|
6590
6636
|
MAX_RETRIES2 = 5;
|
|
6591
6637
|
TTL_MS = 60 * 60 * 1e3;
|
|
6638
|
+
_draining = false;
|
|
6592
6639
|
INTERCOM_LOG = path10.join(os7.homedir(), ".exe-os", "intercom.log");
|
|
6593
6640
|
}
|
|
6594
6641
|
});
|
|
@@ -7203,6 +7250,17 @@ var init_agent_symlinks = __esm({
|
|
|
7203
7250
|
});
|
|
7204
7251
|
|
|
7205
7252
|
// src/lib/notifications.ts
|
|
7253
|
+
var notifications_exports = {};
|
|
7254
|
+
__export(notifications_exports, {
|
|
7255
|
+
cleanupOldNotifications: () => cleanupOldNotifications,
|
|
7256
|
+
formatNotifications: () => formatNotifications,
|
|
7257
|
+
markAsRead: () => markAsRead,
|
|
7258
|
+
markAsReadByTaskFile: () => markAsReadByTaskFile,
|
|
7259
|
+
markDoneTaskNotificationsAsRead: () => markDoneTaskNotificationsAsRead,
|
|
7260
|
+
migrateJsonNotifications: () => migrateJsonNotifications,
|
|
7261
|
+
readUnreadNotifications: () => readUnreadNotifications,
|
|
7262
|
+
writeNotification: () => writeNotification
|
|
7263
|
+
});
|
|
7206
7264
|
import crypto2 from "crypto";
|
|
7207
7265
|
import path14 from "path";
|
|
7208
7266
|
import os10 from "os";
|
|
@@ -7239,6 +7297,52 @@ async function writeNotification(notification) {
|
|
|
7239
7297
|
`);
|
|
7240
7298
|
}
|
|
7241
7299
|
}
|
|
7300
|
+
async function readUnreadNotifications(agentFilter, sessionScope) {
|
|
7301
|
+
try {
|
|
7302
|
+
const client = getClient();
|
|
7303
|
+
const conditions = ["read = 0"];
|
|
7304
|
+
const args = [];
|
|
7305
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
7306
|
+
if (agentFilter) {
|
|
7307
|
+
conditions.push("agent_id = ?");
|
|
7308
|
+
args.push(agentFilter);
|
|
7309
|
+
}
|
|
7310
|
+
const result = await client.execute({
|
|
7311
|
+
sql: `SELECT id, agent_id, agent_role, event, project, summary, task_file, session_scope, created_at
|
|
7312
|
+
FROM notifications
|
|
7313
|
+
WHERE ${conditions.join(" AND ")}${scope.sql}
|
|
7314
|
+
ORDER BY created_at ASC`,
|
|
7315
|
+
args: [...args, ...scope.args]
|
|
7316
|
+
});
|
|
7317
|
+
return result.rows.map((r) => ({
|
|
7318
|
+
id: String(r.id),
|
|
7319
|
+
agentId: String(r.agent_id),
|
|
7320
|
+
agentRole: String(r.agent_role),
|
|
7321
|
+
event: String(r.event),
|
|
7322
|
+
project: String(r.project),
|
|
7323
|
+
summary: String(r.summary),
|
|
7324
|
+
taskFile: r.task_file ? String(r.task_file) : void 0,
|
|
7325
|
+
sessionScope: r.session_scope == null ? null : String(r.session_scope),
|
|
7326
|
+
timestamp: String(r.created_at),
|
|
7327
|
+
read: false
|
|
7328
|
+
}));
|
|
7329
|
+
} catch {
|
|
7330
|
+
return [];
|
|
7331
|
+
}
|
|
7332
|
+
}
|
|
7333
|
+
async function markAsRead(ids, sessionScope) {
|
|
7334
|
+
if (ids.length === 0) return;
|
|
7335
|
+
try {
|
|
7336
|
+
const client = getClient();
|
|
7337
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
7338
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
7339
|
+
await client.execute({
|
|
7340
|
+
sql: `UPDATE notifications SET read = 1 WHERE id IN (${placeholders})${scope.sql}`,
|
|
7341
|
+
args: [...ids, ...scope.args]
|
|
7342
|
+
});
|
|
7343
|
+
} catch {
|
|
7344
|
+
}
|
|
7345
|
+
}
|
|
7242
7346
|
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
7243
7347
|
try {
|
|
7244
7348
|
const client = getClient();
|
|
@@ -7251,11 +7355,144 @@ async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
|
7251
7355
|
} catch {
|
|
7252
7356
|
}
|
|
7253
7357
|
}
|
|
7358
|
+
async function cleanupOldNotifications(daysOld = CLEANUP_DAYS, sessionScope) {
|
|
7359
|
+
try {
|
|
7360
|
+
const client = getClient();
|
|
7361
|
+
const cutoff = new Date(
|
|
7362
|
+
Date.now() - daysOld * 24 * 60 * 60 * 1e3
|
|
7363
|
+
).toISOString();
|
|
7364
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
7365
|
+
const result = await client.execute({
|
|
7366
|
+
sql: `DELETE FROM notifications WHERE created_at < ?${scope.sql}`,
|
|
7367
|
+
args: [cutoff, ...scope.args]
|
|
7368
|
+
});
|
|
7369
|
+
return result.rowsAffected;
|
|
7370
|
+
} catch {
|
|
7371
|
+
return 0;
|
|
7372
|
+
}
|
|
7373
|
+
}
|
|
7374
|
+
async function markDoneTaskNotificationsAsRead(sessionScope) {
|
|
7375
|
+
try {
|
|
7376
|
+
const client = getClient();
|
|
7377
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
7378
|
+
const result = await client.execute({
|
|
7379
|
+
sql: `UPDATE notifications SET read = 1
|
|
7380
|
+
WHERE read = 0
|
|
7381
|
+
AND task_file IS NOT NULL
|
|
7382
|
+
${scope.sql}
|
|
7383
|
+
AND task_file IN (
|
|
7384
|
+
SELECT task_file FROM tasks WHERE status = 'done'${scope.sql}
|
|
7385
|
+
)`,
|
|
7386
|
+
args: [...scope.args, ...scope.args]
|
|
7387
|
+
});
|
|
7388
|
+
return result.rowsAffected;
|
|
7389
|
+
} catch {
|
|
7390
|
+
return 0;
|
|
7391
|
+
}
|
|
7392
|
+
}
|
|
7393
|
+
function formatNotifications(notifications) {
|
|
7394
|
+
if (notifications.length === 0) return "";
|
|
7395
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
7396
|
+
for (const n of notifications) {
|
|
7397
|
+
const key = `${n.agentId}|${n.agentRole}`;
|
|
7398
|
+
if (!grouped.has(key)) grouped.set(key, []);
|
|
7399
|
+
grouped.get(key).push(n);
|
|
7400
|
+
}
|
|
7401
|
+
const lines = [];
|
|
7402
|
+
lines.push(`## Notifications (${notifications.length} unread)
|
|
7403
|
+
`);
|
|
7404
|
+
for (const [key, items] of grouped) {
|
|
7405
|
+
const [agentId, agentRole] = key.split("|");
|
|
7406
|
+
lines.push(`**${agentId}** (${agentRole}):`);
|
|
7407
|
+
for (const item of items) {
|
|
7408
|
+
const ago = formatTimeAgo(item.timestamp);
|
|
7409
|
+
const icon = eventIcon(item.event);
|
|
7410
|
+
lines.push(`- ${icon} ${item.summary} (${item.project}) \u2014 ${ago}`);
|
|
7411
|
+
}
|
|
7412
|
+
lines.push("");
|
|
7413
|
+
}
|
|
7414
|
+
return lines.join("\n");
|
|
7415
|
+
}
|
|
7416
|
+
async function migrateJsonNotifications() {
|
|
7417
|
+
const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR || path14.join(os10.homedir(), ".exe-os");
|
|
7418
|
+
const notifDir = path14.join(base, "notifications");
|
|
7419
|
+
if (!existsSync15(notifDir)) return 0;
|
|
7420
|
+
let migrated = 0;
|
|
7421
|
+
try {
|
|
7422
|
+
const files = readdirSync2(notifDir).filter((f) => f.endsWith(".json"));
|
|
7423
|
+
if (files.length === 0) return 0;
|
|
7424
|
+
const client = getClient();
|
|
7425
|
+
for (const file of files) {
|
|
7426
|
+
try {
|
|
7427
|
+
const filePath = path14.join(notifDir, file);
|
|
7428
|
+
const data = JSON.parse(readFileSync10(filePath, "utf8"));
|
|
7429
|
+
await client.execute({
|
|
7430
|
+
sql: `INSERT OR IGNORE INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
7431
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
7432
|
+
args: [
|
|
7433
|
+
crypto2.randomUUID(),
|
|
7434
|
+
data.agentId ?? "unknown",
|
|
7435
|
+
data.agentRole ?? "unknown",
|
|
7436
|
+
data.event ?? "session_summary",
|
|
7437
|
+
data.project ?? "unknown",
|
|
7438
|
+
data.summary ?? "",
|
|
7439
|
+
data.taskFile ?? null,
|
|
7440
|
+
null,
|
|
7441
|
+
data.read ? 1 : 0,
|
|
7442
|
+
data.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
7443
|
+
]
|
|
7444
|
+
});
|
|
7445
|
+
unlinkSync4(filePath);
|
|
7446
|
+
migrated++;
|
|
7447
|
+
} catch {
|
|
7448
|
+
}
|
|
7449
|
+
}
|
|
7450
|
+
try {
|
|
7451
|
+
const remaining = readdirSync2(notifDir);
|
|
7452
|
+
if (remaining.length === 0) {
|
|
7453
|
+
rmdirSync(notifDir);
|
|
7454
|
+
}
|
|
7455
|
+
} catch {
|
|
7456
|
+
}
|
|
7457
|
+
} catch {
|
|
7458
|
+
}
|
|
7459
|
+
return migrated;
|
|
7460
|
+
}
|
|
7461
|
+
function eventIcon(event) {
|
|
7462
|
+
switch (event) {
|
|
7463
|
+
case "task_complete":
|
|
7464
|
+
return "Completed:";
|
|
7465
|
+
case "task_needs_fix":
|
|
7466
|
+
return "Needs fix:";
|
|
7467
|
+
case "session_summary":
|
|
7468
|
+
return "Session:";
|
|
7469
|
+
case "error_spike":
|
|
7470
|
+
return "Errors:";
|
|
7471
|
+
case "orphan_task":
|
|
7472
|
+
return "Orphan:";
|
|
7473
|
+
case "subtasks_complete":
|
|
7474
|
+
return "Subtasks done:";
|
|
7475
|
+
case "capacity_relaunch":
|
|
7476
|
+
return "Relaunched:";
|
|
7477
|
+
}
|
|
7478
|
+
}
|
|
7479
|
+
function formatTimeAgo(timestamp) {
|
|
7480
|
+
const diffMs = Date.now() - new Date(timestamp).getTime();
|
|
7481
|
+
const mins = Math.floor(diffMs / 6e4);
|
|
7482
|
+
if (mins < 1) return "just now";
|
|
7483
|
+
if (mins < 60) return `${mins}m ago`;
|
|
7484
|
+
const hours = Math.floor(mins / 60);
|
|
7485
|
+
if (hours < 24) return `${hours}h ago`;
|
|
7486
|
+
const days = Math.floor(hours / 24);
|
|
7487
|
+
return `${days}d ago`;
|
|
7488
|
+
}
|
|
7489
|
+
var CLEANUP_DAYS;
|
|
7254
7490
|
var init_notifications = __esm({
|
|
7255
7491
|
"src/lib/notifications.ts"() {
|
|
7256
7492
|
"use strict";
|
|
7257
7493
|
init_database();
|
|
7258
7494
|
init_task_scope();
|
|
7495
|
+
CLEANUP_DAYS = 7;
|
|
7259
7496
|
}
|
|
7260
7497
|
});
|
|
7261
7498
|
|
|
@@ -8262,7 +8499,13 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
|
|
|
8262
8499
|
taskFile
|
|
8263
8500
|
});
|
|
8264
8501
|
const originalPriority = String(row.priority).toLowerCase();
|
|
8265
|
-
const
|
|
8502
|
+
const resultLower = result?.toLowerCase() ?? "";
|
|
8503
|
+
const hasTestEvidence = (
|
|
8504
|
+
// Vitest/Jest output patterns (hard to fake without actually running tests)
|
|
8505
|
+
/\d+\s+pass(ed|ing)/.test(resultLower) || /test files?\s+\d+\s+passed/.test(resultLower) || /tests?\s+\d+\s+passed/.test(resultLower)
|
|
8506
|
+
);
|
|
8507
|
+
const hasNoFailures = !/fail(ed|ure|ing)|error/i.test(resultLower);
|
|
8508
|
+
const autoApprove = originalPriority === "p2" && hasTestEvidence && hasNoFailures;
|
|
8266
8509
|
if (!autoApprove) {
|
|
8267
8510
|
try {
|
|
8268
8511
|
const key = getSessionKey();
|
|
@@ -8270,6 +8513,13 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
|
|
|
8270
8513
|
if (exeSession2) {
|
|
8271
8514
|
sendIntercom(exeSession2);
|
|
8272
8515
|
}
|
|
8516
|
+
if (reviewer && reviewer !== coordinatorName && reviewer !== exeSession2) {
|
|
8517
|
+
const { employeeSessionName: employeeSessionName2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
8518
|
+
if (exeSession2) {
|
|
8519
|
+
const reviewerSession = employeeSessionName2(reviewer, exeSession2);
|
|
8520
|
+
sendIntercom(reviewerSession);
|
|
8521
|
+
}
|
|
8522
|
+
}
|
|
8273
8523
|
} catch {
|
|
8274
8524
|
}
|
|
8275
8525
|
}
|
|
@@ -9045,6 +9295,20 @@ async function updateTask(input) {
|
|
|
9045
9295
|
notifyTaskDone();
|
|
9046
9296
|
}
|
|
9047
9297
|
await markTaskNotificationsRead(taskFile);
|
|
9298
|
+
if (input.status === "needs_review" && !isCoordinator) {
|
|
9299
|
+
try {
|
|
9300
|
+
const { writeNotification: writeNotification2 } = await Promise.resolve().then(() => (init_notifications(), notifications_exports));
|
|
9301
|
+
await writeNotification2({
|
|
9302
|
+
agentId: String(row.assigned_to),
|
|
9303
|
+
agentRole: String(row.assigned_to),
|
|
9304
|
+
event: "task_complete",
|
|
9305
|
+
project: String(row.project_name),
|
|
9306
|
+
summary: `"${String(row.title)}" is ready for review`,
|
|
9307
|
+
taskFile
|
|
9308
|
+
});
|
|
9309
|
+
} catch {
|
|
9310
|
+
}
|
|
9311
|
+
}
|
|
9048
9312
|
if (input.status === "done" || input.status === "closed") {
|
|
9049
9313
|
try {
|
|
9050
9314
|
await cascadeUnblock(taskId, input.baseDir, now);
|
|
@@ -9477,18 +9741,31 @@ function acquireSpawnLock2(sessionName) {
|
|
|
9477
9741
|
mkdirSync9(SPAWN_LOCK_DIR, { recursive: true });
|
|
9478
9742
|
}
|
|
9479
9743
|
const lockFile = spawnLockPath(sessionName);
|
|
9480
|
-
|
|
9481
|
-
|
|
9482
|
-
|
|
9483
|
-
|
|
9484
|
-
|
|
9485
|
-
|
|
9486
|
-
|
|
9487
|
-
|
|
9744
|
+
const lockData = JSON.stringify({ pid: process.pid, timestamp: Date.now() });
|
|
9745
|
+
const { openSync: openSync3, closeSync: closeSync3, writeSync } = __require("fs");
|
|
9746
|
+
const { constants } = __require("fs");
|
|
9747
|
+
try {
|
|
9748
|
+
const fd = openSync3(lockFile, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 420);
|
|
9749
|
+
writeSync(fd, lockData);
|
|
9750
|
+
closeSync3(fd);
|
|
9751
|
+
return true;
|
|
9752
|
+
} catch (err) {
|
|
9753
|
+
if (err?.code !== "EEXIST") {
|
|
9754
|
+
return true;
|
|
9488
9755
|
}
|
|
9489
9756
|
}
|
|
9490
|
-
|
|
9491
|
-
|
|
9757
|
+
try {
|
|
9758
|
+
const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
|
|
9759
|
+
const age = Date.now() - lock.timestamp;
|
|
9760
|
+
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
9761
|
+
return false;
|
|
9762
|
+
}
|
|
9763
|
+
writeFileSync8(lockFile, lockData);
|
|
9764
|
+
return true;
|
|
9765
|
+
} catch {
|
|
9766
|
+
writeFileSync8(lockFile, lockData);
|
|
9767
|
+
return true;
|
|
9768
|
+
}
|
|
9492
9769
|
}
|
|
9493
9770
|
function releaseSpawnLock2(sessionName) {
|
|
9494
9771
|
try {
|
|
@@ -9567,6 +9844,21 @@ function parseParentExe(sessionName, agentId) {
|
|
|
9567
9844
|
function extractRootExe(name) {
|
|
9568
9845
|
if (!name) return null;
|
|
9569
9846
|
if (!name.includes("-")) return name;
|
|
9847
|
+
try {
|
|
9848
|
+
const roster = (init_employees(), __toCommonJS(employees_exports)).loadEmployeesSync();
|
|
9849
|
+
if (roster.length > 0) {
|
|
9850
|
+
const sortedNames = roster.map((e) => e.name).sort((a, b) => b.length - a.length);
|
|
9851
|
+
for (const agentName2 of sortedNames) {
|
|
9852
|
+
const escaped = agentName2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
9853
|
+
const regex = new RegExp(`^${escaped}\\d*-(.+)$`);
|
|
9854
|
+
const match = name.match(regex);
|
|
9855
|
+
if (match) {
|
|
9856
|
+
return extractRootExe(match[1]);
|
|
9857
|
+
}
|
|
9858
|
+
}
|
|
9859
|
+
}
|
|
9860
|
+
} catch {
|
|
9861
|
+
}
|
|
9570
9862
|
const parts = name.split("-").filter(Boolean);
|
|
9571
9863
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
9572
9864
|
}
|
|
@@ -9585,6 +9877,10 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
9585
9877
|
function getParentExe(sessionKey) {
|
|
9586
9878
|
try {
|
|
9587
9879
|
const data = JSON.parse(readFileSync12(path20.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
9880
|
+
if (data.registeredAt) {
|
|
9881
|
+
const age = Date.now() - new Date(data.registeredAt).getTime();
|
|
9882
|
+
if (age > PARENT_EXE_CACHE_TTL_MS) return null;
|
|
9883
|
+
}
|
|
9588
9884
|
return data.parentExe || null;
|
|
9589
9885
|
} catch {
|
|
9590
9886
|
return null;
|
|
@@ -10133,7 +10429,7 @@ function spawnEmployee(employeeName, exeSession2, projectDir, opts) {
|
|
|
10133
10429
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
10134
10430
|
} catch {
|
|
10135
10431
|
}
|
|
10136
|
-
let envPrefix = `EXE_SESSION=${exeSession2} EXE_SESSION_NAME=${sessionName}`;
|
|
10432
|
+
let envPrefix = `EXE_SESSION=${exeSession2} EXE_SESSION_NAME=${sessionName} EXE_SESSION_START_ISO=${(/* @__PURE__ */ new Date()).toISOString()}`;
|
|
10137
10433
|
if (ccProvider !== DEFAULT_PROVIDER) {
|
|
10138
10434
|
const cfg = PROVIDER_TABLE[ccProvider];
|
|
10139
10435
|
if (cfg?.apiKeyEnv) {
|
|
@@ -10168,10 +10464,8 @@ function spawnEmployee(employeeName, exeSession2, projectDir, opts) {
|
|
|
10168
10464
|
}
|
|
10169
10465
|
if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
|
|
10170
10466
|
if (agentRtConfig.runtime === "claude" && agentRtConfig.model) {
|
|
10171
|
-
|
|
10172
|
-
|
|
10173
|
-
ccModel += "[1m]";
|
|
10174
|
-
}
|
|
10467
|
+
const { normalizeCcModelName: normalizeCcModelName2 } = (init_agent_config(), __toCommonJS(agent_config_exports));
|
|
10468
|
+
const ccModel = normalizeCcModelName2(agentRtConfig.model);
|
|
10175
10469
|
envPrefix = `${envPrefix} ANTHROPIC_MODEL=${ccModel}`;
|
|
10176
10470
|
}
|
|
10177
10471
|
}
|
|
@@ -10272,7 +10566,7 @@ function spawnEmployee(employeeName, exeSession2, projectDir, opts) {
|
|
|
10272
10566
|
releaseSpawnLock2(sessionName);
|
|
10273
10567
|
return { sessionName };
|
|
10274
10568
|
}
|
|
10275
|
-
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;
|
|
10569
|
+
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;
|
|
10276
10570
|
var init_tmux_routing = __esm({
|
|
10277
10571
|
"src/lib/tmux-routing.ts"() {
|
|
10278
10572
|
"use strict";
|
|
@@ -10292,6 +10586,7 @@ var init_tmux_routing = __esm({
|
|
|
10292
10586
|
SESSION_CACHE = path20.join(os12.homedir(), ".exe-os", "session-cache");
|
|
10293
10587
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
10294
10588
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
10589
|
+
PARENT_EXE_CACHE_TTL_MS = 4 * 60 * 60 * 1e3;
|
|
10295
10590
|
VERIFY_PANE_LINES = 200;
|
|
10296
10591
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
10297
10592
|
CODEX_DEBOUNCE_MS = 12e4;
|
package/dist/bin/exe-settings.js
CHANGED
|
@@ -117,6 +117,17 @@ function normalizeOrchestration(raw) {
|
|
|
117
117
|
const userOrg = raw.orchestration ?? {};
|
|
118
118
|
raw.orchestration = { ...defaultOrg, ...userOrg };
|
|
119
119
|
}
|
|
120
|
+
function normalizeCloudEndpoint(raw) {
|
|
121
|
+
const cloud = raw.cloud;
|
|
122
|
+
if (!cloud?.endpoint) return;
|
|
123
|
+
const ep = String(cloud.endpoint);
|
|
124
|
+
if (ep === "https://askexe.com/cloud" || ep === "https://askexe.com/cloud/") {
|
|
125
|
+
cloud.endpoint = "https://cloud.askexe.com";
|
|
126
|
+
process.stderr.write(
|
|
127
|
+
"[config] Auto-migrated cloud endpoint: askexe.com/cloud \u2192 cloud.askexe.com\n"
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
120
131
|
async function loadConfig() {
|
|
121
132
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
122
133
|
await ensurePrivateDir(dir);
|
|
@@ -142,6 +153,7 @@ async function loadConfig() {
|
|
|
142
153
|
normalizeSessionLifecycle(migratedCfg);
|
|
143
154
|
normalizeAutoUpdate(migratedCfg);
|
|
144
155
|
normalizeOrchestration(migratedCfg);
|
|
156
|
+
normalizeCloudEndpoint(migratedCfg);
|
|
145
157
|
const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
|
|
146
158
|
if (config.dbPath.startsWith("~")) {
|
|
147
159
|
config.dbPath = config.dbPath.replace(/^~/, os.homedir());
|