@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
package/dist/bin/exe-gateway.js
CHANGED
|
@@ -536,6 +536,17 @@ function normalizeOrchestration(raw) {
|
|
|
536
536
|
const userOrg = raw.orchestration ?? {};
|
|
537
537
|
raw.orchestration = { ...defaultOrg, ...userOrg };
|
|
538
538
|
}
|
|
539
|
+
function normalizeCloudEndpoint(raw) {
|
|
540
|
+
const cloud = raw.cloud;
|
|
541
|
+
if (!cloud?.endpoint) return;
|
|
542
|
+
const ep = String(cloud.endpoint);
|
|
543
|
+
if (ep === "https://askexe.com/cloud" || ep === "https://askexe.com/cloud/") {
|
|
544
|
+
cloud.endpoint = "https://cloud.askexe.com";
|
|
545
|
+
process.stderr.write(
|
|
546
|
+
"[config] Auto-migrated cloud endpoint: askexe.com/cloud \u2192 cloud.askexe.com\n"
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
539
550
|
async function loadConfig() {
|
|
540
551
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
541
552
|
await ensurePrivateDir(dir);
|
|
@@ -561,6 +572,7 @@ async function loadConfig() {
|
|
|
561
572
|
normalizeSessionLifecycle(migratedCfg);
|
|
562
573
|
normalizeAutoUpdate(migratedCfg);
|
|
563
574
|
normalizeOrchestration(migratedCfg);
|
|
575
|
+
normalizeCloudEndpoint(migratedCfg);
|
|
564
576
|
const config2 = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
|
|
565
577
|
if (config2.dbPath.startsWith("~")) {
|
|
566
578
|
config2.dbPath = config2.dbPath.replace(/^~/, os2.homedir());
|
|
@@ -589,6 +601,7 @@ function loadConfigSync() {
|
|
|
589
601
|
normalizeSessionLifecycle(migratedCfg);
|
|
590
602
|
normalizeAutoUpdate(migratedCfg);
|
|
591
603
|
normalizeOrchestration(migratedCfg);
|
|
604
|
+
normalizeCloudEndpoint(migratedCfg);
|
|
592
605
|
const config2 = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
|
|
593
606
|
if (config2.dbPath.startsWith("~")) {
|
|
594
607
|
config2.dbPath = config2.dbPath.replace(/^~/, os2.homedir());
|
|
@@ -749,6 +762,7 @@ __export(agent_config_exports, {
|
|
|
749
762
|
clearAgentRuntime: () => clearAgentRuntime,
|
|
750
763
|
getAgentRuntime: () => getAgentRuntime,
|
|
751
764
|
loadAgentConfig: () => loadAgentConfig,
|
|
765
|
+
normalizeCcModelName: () => normalizeCcModelName,
|
|
752
766
|
saveAgentConfig: () => saveAgentConfig,
|
|
753
767
|
setAgentMcps: () => setAgentMcps,
|
|
754
768
|
setAgentRuntime: () => setAgentRuntime
|
|
@@ -777,6 +791,13 @@ function getAgentRuntime(agentId) {
|
|
|
777
791
|
if (orgDefault) return orgDefault;
|
|
778
792
|
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
779
793
|
}
|
|
794
|
+
function normalizeCcModelName(model) {
|
|
795
|
+
let ccModel = model.replace(/(\d+)\.(\d+)/g, "$1-$2");
|
|
796
|
+
if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
|
|
797
|
+
ccModel += "[1m]";
|
|
798
|
+
}
|
|
799
|
+
return ccModel;
|
|
800
|
+
}
|
|
780
801
|
function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
|
|
781
802
|
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
782
803
|
if (!knownModels) {
|
|
@@ -4070,7 +4091,7 @@ var init_embedder = __esm({
|
|
|
4070
4091
|
});
|
|
4071
4092
|
|
|
4072
4093
|
// src/lib/keychain.ts
|
|
4073
|
-
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
4094
|
+
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2, rename, copyFile } from "fs/promises";
|
|
4074
4095
|
import { existsSync as existsSync8, statSync as statSync3 } from "fs";
|
|
4075
4096
|
import { execSync as execSync3 } from "child_process";
|
|
4076
4097
|
import path8 from "path";
|
|
@@ -4105,12 +4126,14 @@ function linuxSecretAvailable() {
|
|
|
4105
4126
|
function isRootOnlyTrustedServerKeyFile(keyPath) {
|
|
4106
4127
|
if (process.platform !== "linux") return false;
|
|
4107
4128
|
try {
|
|
4108
|
-
const uid = typeof os6.userInfo().uid === "number" ? os6.userInfo().uid : -1;
|
|
4109
4129
|
const st = statSync3(keyPath);
|
|
4110
4130
|
if (!st.isFile() || (st.mode & 63) !== 0) return false;
|
|
4131
|
+
const uid = typeof os6.userInfo().uid === "number" ? os6.userInfo().uid : -1;
|
|
4111
4132
|
if (uid === 0) return true;
|
|
4112
4133
|
const exeOsDir = process.env.EXE_OS_DIR;
|
|
4113
|
-
|
|
4134
|
+
if (exeOsDir && path8.resolve(keyPath).startsWith(path8.resolve(exeOsDir) + path8.sep)) return true;
|
|
4135
|
+
if (!linuxSecretAvailable()) return true;
|
|
4136
|
+
return false;
|
|
4114
4137
|
} catch {
|
|
4115
4138
|
return false;
|
|
4116
4139
|
}
|
|
@@ -4260,15 +4283,25 @@ async function writeMachineBoundFileFallback(b64) {
|
|
|
4260
4283
|
await mkdir3(dir, { recursive: true });
|
|
4261
4284
|
const keyPath = getKeyPath();
|
|
4262
4285
|
const machineKey = deriveMachineKey();
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4286
|
+
const content = machineKey ? encryptWithMachineKey(b64, machineKey) + "\n" : b64 + "\n";
|
|
4287
|
+
const result = machineKey ? "encrypted" : "plaintext";
|
|
4288
|
+
const tmpPath = keyPath + ".tmp";
|
|
4289
|
+
try {
|
|
4290
|
+
if (existsSync8(keyPath)) {
|
|
4291
|
+
await copyFile(keyPath, keyPath + ".bak").catch(() => {
|
|
4292
|
+
});
|
|
4293
|
+
}
|
|
4294
|
+
await writeFile3(tmpPath, content, "utf-8");
|
|
4295
|
+
await chmod2(tmpPath, 384);
|
|
4296
|
+
await rename(tmpPath, keyPath);
|
|
4297
|
+
} catch (err) {
|
|
4298
|
+
try {
|
|
4299
|
+
await unlink(tmpPath);
|
|
4300
|
+
} catch {
|
|
4301
|
+
}
|
|
4302
|
+
throw err;
|
|
4268
4303
|
}
|
|
4269
|
-
|
|
4270
|
-
await chmod2(keyPath, 384);
|
|
4271
|
-
return "plaintext";
|
|
4304
|
+
return result;
|
|
4272
4305
|
}
|
|
4273
4306
|
async function getMasterKey() {
|
|
4274
4307
|
let nativeValue = macKeychainGet() ?? linuxSecretGet();
|
|
@@ -4335,7 +4368,7 @@ async function getMasterKey() {
|
|
|
4335
4368
|
b64Value = content;
|
|
4336
4369
|
}
|
|
4337
4370
|
const key = Buffer.from(b64Value, "base64");
|
|
4338
|
-
if (
|
|
4371
|
+
if (isRootOnlyTrustedServerKeyFile(keyPath)) {
|
|
4339
4372
|
return key;
|
|
4340
4373
|
}
|
|
4341
4374
|
const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
|
|
@@ -10136,6 +10169,7 @@ var init_provider_table = __esm({
|
|
|
10136
10169
|
// src/lib/intercom-queue.ts
|
|
10137
10170
|
var intercom_queue_exports = {};
|
|
10138
10171
|
__export(intercom_queue_exports, {
|
|
10172
|
+
_resetDrainGuard: () => _resetDrainGuard,
|
|
10139
10173
|
clearQueueForAgent: () => clearQueueForAgent,
|
|
10140
10174
|
drainForSession: () => drainForSession,
|
|
10141
10175
|
drainQueue: () => drainQueue,
|
|
@@ -10181,38 +10215,47 @@ function queueIntercom(targetSession, reason) {
|
|
|
10181
10215
|
writeQueue(queue);
|
|
10182
10216
|
}
|
|
10183
10217
|
function drainQueue(isSessionBusy2, sendKeys) {
|
|
10218
|
+
if (_draining) {
|
|
10219
|
+
logQueue("SKIP_DRAIN \u2014 previous drain still running (possible tmux hang)");
|
|
10220
|
+
return { drained: 0, failed: 0 };
|
|
10221
|
+
}
|
|
10184
10222
|
const queue = readQueue();
|
|
10185
10223
|
if (queue.length === 0) return { drained: 0, failed: 0 };
|
|
10224
|
+
_draining = true;
|
|
10186
10225
|
const remaining = [];
|
|
10187
10226
|
let drained = 0;
|
|
10188
10227
|
let failed = 0;
|
|
10189
|
-
|
|
10190
|
-
const
|
|
10191
|
-
|
|
10192
|
-
|
|
10193
|
-
|
|
10194
|
-
|
|
10195
|
-
|
|
10196
|
-
|
|
10197
|
-
|
|
10198
|
-
|
|
10199
|
-
|
|
10200
|
-
|
|
10201
|
-
|
|
10202
|
-
|
|
10228
|
+
try {
|
|
10229
|
+
for (const item of queue) {
|
|
10230
|
+
const age = Date.now() - new Date(item.queuedAt).getTime();
|
|
10231
|
+
if (age > TTL_MS) {
|
|
10232
|
+
logQueue(`EXPIRED \u2192 ${item.targetSession} (${Math.round(age / 6e4)}min old, reason: ${item.reason})`);
|
|
10233
|
+
failed++;
|
|
10234
|
+
continue;
|
|
10235
|
+
}
|
|
10236
|
+
try {
|
|
10237
|
+
if (!isSessionBusy2(item.targetSession)) {
|
|
10238
|
+
const success = sendKeys(item.targetSession);
|
|
10239
|
+
if (success) {
|
|
10240
|
+
logQueue(`DRAINED \u2192 ${item.targetSession} (after ${item.attempts} retries)`);
|
|
10241
|
+
drained++;
|
|
10242
|
+
continue;
|
|
10243
|
+
}
|
|
10203
10244
|
}
|
|
10245
|
+
} catch {
|
|
10204
10246
|
}
|
|
10205
|
-
|
|
10206
|
-
|
|
10207
|
-
|
|
10208
|
-
|
|
10209
|
-
|
|
10210
|
-
|
|
10211
|
-
|
|
10247
|
+
item.attempts++;
|
|
10248
|
+
if (item.attempts >= MAX_RETRIES2) {
|
|
10249
|
+
logQueue(`FAILED \u2192 ${item.targetSession} (${MAX_RETRIES2} retries exhausted, reason: ${item.reason})`);
|
|
10250
|
+
failed++;
|
|
10251
|
+
continue;
|
|
10252
|
+
}
|
|
10253
|
+
remaining.push(item);
|
|
10212
10254
|
}
|
|
10213
|
-
remaining
|
|
10255
|
+
writeQueue(remaining);
|
|
10256
|
+
} finally {
|
|
10257
|
+
_draining = false;
|
|
10214
10258
|
}
|
|
10215
|
-
writeQueue(remaining);
|
|
10216
10259
|
return { drained, failed };
|
|
10217
10260
|
}
|
|
10218
10261
|
function drainForSession(targetSession, sendKeys) {
|
|
@@ -10237,6 +10280,9 @@ function clearQueueForAgent(agentName) {
|
|
|
10237
10280
|
logQueue(`CLEARED ${before - filtered.length} stale item(s) for ${agentName}`);
|
|
10238
10281
|
}
|
|
10239
10282
|
}
|
|
10283
|
+
function _resetDrainGuard() {
|
|
10284
|
+
_draining = false;
|
|
10285
|
+
}
|
|
10240
10286
|
function logQueue(msg) {
|
|
10241
10287
|
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] [queue] ${msg}
|
|
10242
10288
|
`;
|
|
@@ -10248,13 +10294,14 @@ function logQueue(msg) {
|
|
|
10248
10294
|
} catch {
|
|
10249
10295
|
}
|
|
10250
10296
|
}
|
|
10251
|
-
var QUEUE_PATH, MAX_RETRIES2, TTL_MS, INTERCOM_LOG;
|
|
10297
|
+
var QUEUE_PATH, MAX_RETRIES2, TTL_MS, _draining, INTERCOM_LOG;
|
|
10252
10298
|
var init_intercom_queue = __esm({
|
|
10253
10299
|
"src/lib/intercom-queue.ts"() {
|
|
10254
10300
|
"use strict";
|
|
10255
10301
|
QUEUE_PATH = path13.join(os10.homedir(), ".exe-os", "intercom-queue.json");
|
|
10256
10302
|
MAX_RETRIES2 = 5;
|
|
10257
10303
|
TTL_MS = 60 * 60 * 1e3;
|
|
10304
|
+
_draining = false;
|
|
10258
10305
|
INTERCOM_LOG = path13.join(os10.homedir(), ".exe-os", "intercom.log");
|
|
10259
10306
|
}
|
|
10260
10307
|
});
|
|
@@ -10435,6 +10482,17 @@ var init_task_scope = __esm({
|
|
|
10435
10482
|
});
|
|
10436
10483
|
|
|
10437
10484
|
// src/lib/notifications.ts
|
|
10485
|
+
var notifications_exports = {};
|
|
10486
|
+
__export(notifications_exports, {
|
|
10487
|
+
cleanupOldNotifications: () => cleanupOldNotifications,
|
|
10488
|
+
formatNotifications: () => formatNotifications,
|
|
10489
|
+
markAsRead: () => markAsRead,
|
|
10490
|
+
markAsReadByTaskFile: () => markAsReadByTaskFile,
|
|
10491
|
+
markDoneTaskNotificationsAsRead: () => markDoneTaskNotificationsAsRead,
|
|
10492
|
+
migrateJsonNotifications: () => migrateJsonNotifications,
|
|
10493
|
+
readUnreadNotifications: () => readUnreadNotifications,
|
|
10494
|
+
writeNotification: () => writeNotification
|
|
10495
|
+
});
|
|
10438
10496
|
import crypto5 from "crypto";
|
|
10439
10497
|
import path16 from "path";
|
|
10440
10498
|
import os12 from "os";
|
|
@@ -10471,6 +10529,52 @@ async function writeNotification(notification) {
|
|
|
10471
10529
|
`);
|
|
10472
10530
|
}
|
|
10473
10531
|
}
|
|
10532
|
+
async function readUnreadNotifications(agentFilter, sessionScope) {
|
|
10533
|
+
try {
|
|
10534
|
+
const client = getClient();
|
|
10535
|
+
const conditions = ["read = 0"];
|
|
10536
|
+
const args = [];
|
|
10537
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
10538
|
+
if (agentFilter) {
|
|
10539
|
+
conditions.push("agent_id = ?");
|
|
10540
|
+
args.push(agentFilter);
|
|
10541
|
+
}
|
|
10542
|
+
const result = await client.execute({
|
|
10543
|
+
sql: `SELECT id, agent_id, agent_role, event, project, summary, task_file, session_scope, created_at
|
|
10544
|
+
FROM notifications
|
|
10545
|
+
WHERE ${conditions.join(" AND ")}${scope.sql}
|
|
10546
|
+
ORDER BY created_at ASC`,
|
|
10547
|
+
args: [...args, ...scope.args]
|
|
10548
|
+
});
|
|
10549
|
+
return result.rows.map((r) => ({
|
|
10550
|
+
id: String(r.id),
|
|
10551
|
+
agentId: String(r.agent_id),
|
|
10552
|
+
agentRole: String(r.agent_role),
|
|
10553
|
+
event: String(r.event),
|
|
10554
|
+
project: String(r.project),
|
|
10555
|
+
summary: String(r.summary),
|
|
10556
|
+
taskFile: r.task_file ? String(r.task_file) : void 0,
|
|
10557
|
+
sessionScope: r.session_scope == null ? null : String(r.session_scope),
|
|
10558
|
+
timestamp: String(r.created_at),
|
|
10559
|
+
read: false
|
|
10560
|
+
}));
|
|
10561
|
+
} catch {
|
|
10562
|
+
return [];
|
|
10563
|
+
}
|
|
10564
|
+
}
|
|
10565
|
+
async function markAsRead(ids, sessionScope) {
|
|
10566
|
+
if (ids.length === 0) return;
|
|
10567
|
+
try {
|
|
10568
|
+
const client = getClient();
|
|
10569
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
10570
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
10571
|
+
await client.execute({
|
|
10572
|
+
sql: `UPDATE notifications SET read = 1 WHERE id IN (${placeholders})${scope.sql}`,
|
|
10573
|
+
args: [...ids, ...scope.args]
|
|
10574
|
+
});
|
|
10575
|
+
} catch {
|
|
10576
|
+
}
|
|
10577
|
+
}
|
|
10474
10578
|
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
10475
10579
|
try {
|
|
10476
10580
|
const client = getClient();
|
|
@@ -10483,11 +10587,144 @@ async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
|
10483
10587
|
} catch {
|
|
10484
10588
|
}
|
|
10485
10589
|
}
|
|
10590
|
+
async function cleanupOldNotifications(daysOld = CLEANUP_DAYS, sessionScope) {
|
|
10591
|
+
try {
|
|
10592
|
+
const client = getClient();
|
|
10593
|
+
const cutoff = new Date(
|
|
10594
|
+
Date.now() - daysOld * 24 * 60 * 60 * 1e3
|
|
10595
|
+
).toISOString();
|
|
10596
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
10597
|
+
const result = await client.execute({
|
|
10598
|
+
sql: `DELETE FROM notifications WHERE created_at < ?${scope.sql}`,
|
|
10599
|
+
args: [cutoff, ...scope.args]
|
|
10600
|
+
});
|
|
10601
|
+
return result.rowsAffected;
|
|
10602
|
+
} catch {
|
|
10603
|
+
return 0;
|
|
10604
|
+
}
|
|
10605
|
+
}
|
|
10606
|
+
async function markDoneTaskNotificationsAsRead(sessionScope) {
|
|
10607
|
+
try {
|
|
10608
|
+
const client = getClient();
|
|
10609
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
10610
|
+
const result = await client.execute({
|
|
10611
|
+
sql: `UPDATE notifications SET read = 1
|
|
10612
|
+
WHERE read = 0
|
|
10613
|
+
AND task_file IS NOT NULL
|
|
10614
|
+
${scope.sql}
|
|
10615
|
+
AND task_file IN (
|
|
10616
|
+
SELECT task_file FROM tasks WHERE status = 'done'${scope.sql}
|
|
10617
|
+
)`,
|
|
10618
|
+
args: [...scope.args, ...scope.args]
|
|
10619
|
+
});
|
|
10620
|
+
return result.rowsAffected;
|
|
10621
|
+
} catch {
|
|
10622
|
+
return 0;
|
|
10623
|
+
}
|
|
10624
|
+
}
|
|
10625
|
+
function formatNotifications(notifications) {
|
|
10626
|
+
if (notifications.length === 0) return "";
|
|
10627
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
10628
|
+
for (const n of notifications) {
|
|
10629
|
+
const key = `${n.agentId}|${n.agentRole}`;
|
|
10630
|
+
if (!grouped.has(key)) grouped.set(key, []);
|
|
10631
|
+
grouped.get(key).push(n);
|
|
10632
|
+
}
|
|
10633
|
+
const lines = [];
|
|
10634
|
+
lines.push(`## Notifications (${notifications.length} unread)
|
|
10635
|
+
`);
|
|
10636
|
+
for (const [key, items] of grouped) {
|
|
10637
|
+
const [agentId, agentRole] = key.split("|");
|
|
10638
|
+
lines.push(`**${agentId}** (${agentRole}):`);
|
|
10639
|
+
for (const item of items) {
|
|
10640
|
+
const ago = formatTimeAgo(item.timestamp);
|
|
10641
|
+
const icon = eventIcon(item.event);
|
|
10642
|
+
lines.push(`- ${icon} ${item.summary} (${item.project}) \u2014 ${ago}`);
|
|
10643
|
+
}
|
|
10644
|
+
lines.push("");
|
|
10645
|
+
}
|
|
10646
|
+
return lines.join("\n");
|
|
10647
|
+
}
|
|
10648
|
+
async function migrateJsonNotifications() {
|
|
10649
|
+
const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR || path16.join(os12.homedir(), ".exe-os");
|
|
10650
|
+
const notifDir = path16.join(base, "notifications");
|
|
10651
|
+
if (!existsSync15(notifDir)) return 0;
|
|
10652
|
+
let migrated = 0;
|
|
10653
|
+
try {
|
|
10654
|
+
const files = readdirSync2(notifDir).filter((f) => f.endsWith(".json"));
|
|
10655
|
+
if (files.length === 0) return 0;
|
|
10656
|
+
const client = getClient();
|
|
10657
|
+
for (const file of files) {
|
|
10658
|
+
try {
|
|
10659
|
+
const filePath = path16.join(notifDir, file);
|
|
10660
|
+
const data = JSON.parse(readFileSync11(filePath, "utf8"));
|
|
10661
|
+
await client.execute({
|
|
10662
|
+
sql: `INSERT OR IGNORE INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
10663
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
10664
|
+
args: [
|
|
10665
|
+
crypto5.randomUUID(),
|
|
10666
|
+
data.agentId ?? "unknown",
|
|
10667
|
+
data.agentRole ?? "unknown",
|
|
10668
|
+
data.event ?? "session_summary",
|
|
10669
|
+
data.project ?? "unknown",
|
|
10670
|
+
data.summary ?? "",
|
|
10671
|
+
data.taskFile ?? null,
|
|
10672
|
+
null,
|
|
10673
|
+
data.read ? 1 : 0,
|
|
10674
|
+
data.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
10675
|
+
]
|
|
10676
|
+
});
|
|
10677
|
+
unlinkSync4(filePath);
|
|
10678
|
+
migrated++;
|
|
10679
|
+
} catch {
|
|
10680
|
+
}
|
|
10681
|
+
}
|
|
10682
|
+
try {
|
|
10683
|
+
const remaining = readdirSync2(notifDir);
|
|
10684
|
+
if (remaining.length === 0) {
|
|
10685
|
+
rmdirSync(notifDir);
|
|
10686
|
+
}
|
|
10687
|
+
} catch {
|
|
10688
|
+
}
|
|
10689
|
+
} catch {
|
|
10690
|
+
}
|
|
10691
|
+
return migrated;
|
|
10692
|
+
}
|
|
10693
|
+
function eventIcon(event) {
|
|
10694
|
+
switch (event) {
|
|
10695
|
+
case "task_complete":
|
|
10696
|
+
return "Completed:";
|
|
10697
|
+
case "task_needs_fix":
|
|
10698
|
+
return "Needs fix:";
|
|
10699
|
+
case "session_summary":
|
|
10700
|
+
return "Session:";
|
|
10701
|
+
case "error_spike":
|
|
10702
|
+
return "Errors:";
|
|
10703
|
+
case "orphan_task":
|
|
10704
|
+
return "Orphan:";
|
|
10705
|
+
case "subtasks_complete":
|
|
10706
|
+
return "Subtasks done:";
|
|
10707
|
+
case "capacity_relaunch":
|
|
10708
|
+
return "Relaunched:";
|
|
10709
|
+
}
|
|
10710
|
+
}
|
|
10711
|
+
function formatTimeAgo(timestamp) {
|
|
10712
|
+
const diffMs = Date.now() - new Date(timestamp).getTime();
|
|
10713
|
+
const mins = Math.floor(diffMs / 6e4);
|
|
10714
|
+
if (mins < 1) return "just now";
|
|
10715
|
+
if (mins < 60) return `${mins}m ago`;
|
|
10716
|
+
const hours = Math.floor(mins / 60);
|
|
10717
|
+
if (hours < 24) return `${hours}h ago`;
|
|
10718
|
+
const days = Math.floor(hours / 24);
|
|
10719
|
+
return `${days}d ago`;
|
|
10720
|
+
}
|
|
10721
|
+
var CLEANUP_DAYS;
|
|
10486
10722
|
var init_notifications = __esm({
|
|
10487
10723
|
"src/lib/notifications.ts"() {
|
|
10488
10724
|
"use strict";
|
|
10489
10725
|
init_database();
|
|
10490
10726
|
init_task_scope();
|
|
10727
|
+
CLEANUP_DAYS = 7;
|
|
10491
10728
|
}
|
|
10492
10729
|
});
|
|
10493
10730
|
|
|
@@ -11478,7 +11715,13 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
|
|
|
11478
11715
|
taskFile
|
|
11479
11716
|
});
|
|
11480
11717
|
const originalPriority = String(row.priority).toLowerCase();
|
|
11481
|
-
const
|
|
11718
|
+
const resultLower = result?.toLowerCase() ?? "";
|
|
11719
|
+
const hasTestEvidence = (
|
|
11720
|
+
// Vitest/Jest output patterns (hard to fake without actually running tests)
|
|
11721
|
+
/\d+\s+pass(ed|ing)/.test(resultLower) || /test files?\s+\d+\s+passed/.test(resultLower) || /tests?\s+\d+\s+passed/.test(resultLower)
|
|
11722
|
+
);
|
|
11723
|
+
const hasNoFailures = !/fail(ed|ure|ing)|error/i.test(resultLower);
|
|
11724
|
+
const autoApprove = originalPriority === "p2" && hasTestEvidence && hasNoFailures;
|
|
11482
11725
|
if (!autoApprove) {
|
|
11483
11726
|
try {
|
|
11484
11727
|
const key = getSessionKey();
|
|
@@ -11486,6 +11729,13 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
|
|
|
11486
11729
|
if (exeSession) {
|
|
11487
11730
|
sendIntercom(exeSession);
|
|
11488
11731
|
}
|
|
11732
|
+
if (reviewer && reviewer !== coordinatorName && reviewer !== exeSession) {
|
|
11733
|
+
const { employeeSessionName: employeeSessionName2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
11734
|
+
if (exeSession) {
|
|
11735
|
+
const reviewerSession = employeeSessionName2(reviewer, exeSession);
|
|
11736
|
+
sendIntercom(reviewerSession);
|
|
11737
|
+
}
|
|
11738
|
+
}
|
|
11489
11739
|
} catch {
|
|
11490
11740
|
}
|
|
11491
11741
|
}
|
|
@@ -12193,6 +12443,20 @@ async function updateTask(input) {
|
|
|
12193
12443
|
notifyTaskDone();
|
|
12194
12444
|
}
|
|
12195
12445
|
await markTaskNotificationsRead(taskFile);
|
|
12446
|
+
if (input.status === "needs_review" && !isCoordinator) {
|
|
12447
|
+
try {
|
|
12448
|
+
const { writeNotification: writeNotification2 } = await Promise.resolve().then(() => (init_notifications(), notifications_exports));
|
|
12449
|
+
await writeNotification2({
|
|
12450
|
+
agentId: String(row.assigned_to),
|
|
12451
|
+
agentRole: String(row.assigned_to),
|
|
12452
|
+
event: "task_complete",
|
|
12453
|
+
project: String(row.project_name),
|
|
12454
|
+
summary: `"${String(row.title)}" is ready for review`,
|
|
12455
|
+
taskFile
|
|
12456
|
+
});
|
|
12457
|
+
} catch {
|
|
12458
|
+
}
|
|
12459
|
+
}
|
|
12196
12460
|
if (input.status === "done" || input.status === "closed") {
|
|
12197
12461
|
try {
|
|
12198
12462
|
await cascadeUnblock(taskId, input.baseDir, now);
|
|
@@ -12625,18 +12889,31 @@ function acquireSpawnLock2(sessionName) {
|
|
|
12625
12889
|
mkdirSync10(SPAWN_LOCK_DIR, { recursive: true });
|
|
12626
12890
|
}
|
|
12627
12891
|
const lockFile = spawnLockPath(sessionName);
|
|
12628
|
-
|
|
12629
|
-
|
|
12630
|
-
|
|
12631
|
-
|
|
12632
|
-
|
|
12633
|
-
|
|
12634
|
-
|
|
12635
|
-
|
|
12892
|
+
const lockData = JSON.stringify({ pid: process.pid, timestamp: Date.now() });
|
|
12893
|
+
const { openSync: openSync3, closeSync: closeSync3, writeSync } = __require("fs");
|
|
12894
|
+
const { constants } = __require("fs");
|
|
12895
|
+
try {
|
|
12896
|
+
const fd = openSync3(lockFile, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 420);
|
|
12897
|
+
writeSync(fd, lockData);
|
|
12898
|
+
closeSync3(fd);
|
|
12899
|
+
return true;
|
|
12900
|
+
} catch (err) {
|
|
12901
|
+
if (err?.code !== "EEXIST") {
|
|
12902
|
+
return true;
|
|
12636
12903
|
}
|
|
12637
12904
|
}
|
|
12638
|
-
|
|
12639
|
-
|
|
12905
|
+
try {
|
|
12906
|
+
const lock = JSON.parse(readFileSync13(lockFile, "utf8"));
|
|
12907
|
+
const age = Date.now() - lock.timestamp;
|
|
12908
|
+
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
12909
|
+
return false;
|
|
12910
|
+
}
|
|
12911
|
+
writeFileSync8(lockFile, lockData);
|
|
12912
|
+
return true;
|
|
12913
|
+
} catch {
|
|
12914
|
+
writeFileSync8(lockFile, lockData);
|
|
12915
|
+
return true;
|
|
12916
|
+
}
|
|
12640
12917
|
}
|
|
12641
12918
|
function releaseSpawnLock2(sessionName) {
|
|
12642
12919
|
try {
|
|
@@ -12715,6 +12992,21 @@ function parseParentExe(sessionName, agentId) {
|
|
|
12715
12992
|
function extractRootExe(name) {
|
|
12716
12993
|
if (!name) return null;
|
|
12717
12994
|
if (!name.includes("-")) return name;
|
|
12995
|
+
try {
|
|
12996
|
+
const roster = (init_employees(), __toCommonJS(employees_exports)).loadEmployeesSync();
|
|
12997
|
+
if (roster.length > 0) {
|
|
12998
|
+
const sortedNames = roster.map((e) => e.name).sort((a, b) => b.length - a.length);
|
|
12999
|
+
for (const agentName of sortedNames) {
|
|
13000
|
+
const escaped = agentName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
13001
|
+
const regex = new RegExp(`^${escaped}\\d*-(.+)$`);
|
|
13002
|
+
const match = name.match(regex);
|
|
13003
|
+
if (match) {
|
|
13004
|
+
return extractRootExe(match[1]);
|
|
13005
|
+
}
|
|
13006
|
+
}
|
|
13007
|
+
}
|
|
13008
|
+
} catch {
|
|
13009
|
+
}
|
|
12718
13010
|
const parts = name.split("-").filter(Boolean);
|
|
12719
13011
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
12720
13012
|
}
|
|
@@ -12733,6 +13025,10 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
12733
13025
|
function getParentExe(sessionKey) {
|
|
12734
13026
|
try {
|
|
12735
13027
|
const data = JSON.parse(readFileSync13(path22.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
13028
|
+
if (data.registeredAt) {
|
|
13029
|
+
const age = Date.now() - new Date(data.registeredAt).getTime();
|
|
13030
|
+
if (age > PARENT_EXE_CACHE_TTL_MS) return null;
|
|
13031
|
+
}
|
|
12736
13032
|
return data.parentExe || null;
|
|
12737
13033
|
} catch {
|
|
12738
13034
|
return null;
|
|
@@ -13281,7 +13577,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
13281
13577
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
13282
13578
|
} catch {
|
|
13283
13579
|
}
|
|
13284
|
-
let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName}`;
|
|
13580
|
+
let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName} EXE_SESSION_START_ISO=${(/* @__PURE__ */ new Date()).toISOString()}`;
|
|
13285
13581
|
if (ccProvider !== DEFAULT_PROVIDER) {
|
|
13286
13582
|
const cfg = PROVIDER_TABLE[ccProvider];
|
|
13287
13583
|
if (cfg?.apiKeyEnv) {
|
|
@@ -13316,10 +13612,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
13316
13612
|
}
|
|
13317
13613
|
if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
|
|
13318
13614
|
if (agentRtConfig.runtime === "claude" && agentRtConfig.model) {
|
|
13319
|
-
|
|
13320
|
-
|
|
13321
|
-
ccModel += "[1m]";
|
|
13322
|
-
}
|
|
13615
|
+
const { normalizeCcModelName: normalizeCcModelName2 } = (init_agent_config(), __toCommonJS(agent_config_exports));
|
|
13616
|
+
const ccModel = normalizeCcModelName2(agentRtConfig.model);
|
|
13323
13617
|
envPrefix = `${envPrefix} ANTHROPIC_MODEL=${ccModel}`;
|
|
13324
13618
|
}
|
|
13325
13619
|
}
|
|
@@ -13420,7 +13714,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
13420
13714
|
releaseSpawnLock2(sessionName);
|
|
13421
13715
|
return { sessionName };
|
|
13422
13716
|
}
|
|
13423
|
-
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;
|
|
13717
|
+
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;
|
|
13424
13718
|
var init_tmux_routing = __esm({
|
|
13425
13719
|
"src/lib/tmux-routing.ts"() {
|
|
13426
13720
|
"use strict";
|
|
@@ -13440,6 +13734,7 @@ var init_tmux_routing = __esm({
|
|
|
13440
13734
|
SESSION_CACHE = path22.join(os14.homedir(), ".exe-os", "session-cache");
|
|
13441
13735
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
13442
13736
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
13737
|
+
PARENT_EXE_CACHE_TTL_MS = 4 * 60 * 60 * 1e3;
|
|
13443
13738
|
VERIFY_PANE_LINES = 200;
|
|
13444
13739
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
13445
13740
|
CODEX_DEBOUNCE_MS = 12e4;
|