@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
|
@@ -154,6 +154,17 @@ function normalizeOrchestration(raw) {
|
|
|
154
154
|
const userOrg = raw.orchestration ?? {};
|
|
155
155
|
raw.orchestration = { ...defaultOrg, ...userOrg };
|
|
156
156
|
}
|
|
157
|
+
function normalizeCloudEndpoint(raw) {
|
|
158
|
+
const cloud = raw.cloud;
|
|
159
|
+
if (!cloud?.endpoint) return;
|
|
160
|
+
const ep = String(cloud.endpoint);
|
|
161
|
+
if (ep === "https://askexe.com/cloud" || ep === "https://askexe.com/cloud/") {
|
|
162
|
+
cloud.endpoint = "https://cloud.askexe.com";
|
|
163
|
+
process.stderr.write(
|
|
164
|
+
"[config] Auto-migrated cloud endpoint: askexe.com/cloud \u2192 cloud.askexe.com\n"
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
157
168
|
async function loadConfig() {
|
|
158
169
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
159
170
|
await ensurePrivateDir(dir);
|
|
@@ -179,6 +190,7 @@ async function loadConfig() {
|
|
|
179
190
|
normalizeSessionLifecycle(migratedCfg);
|
|
180
191
|
normalizeAutoUpdate(migratedCfg);
|
|
181
192
|
normalizeOrchestration(migratedCfg);
|
|
193
|
+
normalizeCloudEndpoint(migratedCfg);
|
|
182
194
|
const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
|
|
183
195
|
if (config.dbPath.startsWith("~")) {
|
|
184
196
|
config.dbPath = config.dbPath.replace(/^~/, os.homedir());
|
|
@@ -207,6 +219,7 @@ function loadConfigSync() {
|
|
|
207
219
|
normalizeSessionLifecycle(migratedCfg);
|
|
208
220
|
normalizeAutoUpdate(migratedCfg);
|
|
209
221
|
normalizeOrchestration(migratedCfg);
|
|
222
|
+
normalizeCloudEndpoint(migratedCfg);
|
|
210
223
|
const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
|
|
211
224
|
if (config.dbPath.startsWith("~")) {
|
|
212
225
|
config.dbPath = config.dbPath.replace(/^~/, os.homedir());
|
|
@@ -431,6 +444,7 @@ __export(agent_config_exports, {
|
|
|
431
444
|
clearAgentRuntime: () => clearAgentRuntime,
|
|
432
445
|
getAgentRuntime: () => getAgentRuntime,
|
|
433
446
|
loadAgentConfig: () => loadAgentConfig,
|
|
447
|
+
normalizeCcModelName: () => normalizeCcModelName,
|
|
434
448
|
saveAgentConfig: () => saveAgentConfig,
|
|
435
449
|
setAgentMcps: () => setAgentMcps,
|
|
436
450
|
setAgentRuntime: () => setAgentRuntime
|
|
@@ -459,6 +473,13 @@ function getAgentRuntime(agentId) {
|
|
|
459
473
|
if (orgDefault) return orgDefault;
|
|
460
474
|
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
461
475
|
}
|
|
476
|
+
function normalizeCcModelName(model) {
|
|
477
|
+
let ccModel = model.replace(/(\d+)\.(\d+)/g, "$1-$2");
|
|
478
|
+
if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
|
|
479
|
+
ccModel += "[1m]";
|
|
480
|
+
}
|
|
481
|
+
return ccModel;
|
|
482
|
+
}
|
|
462
483
|
function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
|
|
463
484
|
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
464
485
|
if (!knownModels) {
|
|
@@ -3741,7 +3762,7 @@ var init_database = __esm({
|
|
|
3741
3762
|
});
|
|
3742
3763
|
|
|
3743
3764
|
// src/lib/keychain.ts
|
|
3744
|
-
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
3765
|
+
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2, rename, copyFile } from "fs/promises";
|
|
3745
3766
|
import { existsSync as existsSync8, statSync as statSync3 } from "fs";
|
|
3746
3767
|
import { execSync as execSync3 } from "child_process";
|
|
3747
3768
|
import path7 from "path";
|
|
@@ -3776,12 +3797,14 @@ function linuxSecretAvailable() {
|
|
|
3776
3797
|
function isRootOnlyTrustedServerKeyFile(keyPath) {
|
|
3777
3798
|
if (process.platform !== "linux") return false;
|
|
3778
3799
|
try {
|
|
3779
|
-
const uid = typeof os5.userInfo().uid === "number" ? os5.userInfo().uid : -1;
|
|
3780
3800
|
const st = statSync3(keyPath);
|
|
3781
3801
|
if (!st.isFile() || (st.mode & 63) !== 0) return false;
|
|
3802
|
+
const uid = typeof os5.userInfo().uid === "number" ? os5.userInfo().uid : -1;
|
|
3782
3803
|
if (uid === 0) return true;
|
|
3783
3804
|
const exeOsDir = process.env.EXE_OS_DIR;
|
|
3784
|
-
|
|
3805
|
+
if (exeOsDir && path7.resolve(keyPath).startsWith(path7.resolve(exeOsDir) + path7.sep)) return true;
|
|
3806
|
+
if (!linuxSecretAvailable()) return true;
|
|
3807
|
+
return false;
|
|
3785
3808
|
} catch {
|
|
3786
3809
|
return false;
|
|
3787
3810
|
}
|
|
@@ -3931,15 +3954,25 @@ async function writeMachineBoundFileFallback(b64) {
|
|
|
3931
3954
|
await mkdir3(dir, { recursive: true });
|
|
3932
3955
|
const keyPath = getKeyPath();
|
|
3933
3956
|
const machineKey = deriveMachineKey();
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3957
|
+
const content = machineKey ? encryptWithMachineKey(b64, machineKey) + "\n" : b64 + "\n";
|
|
3958
|
+
const result = machineKey ? "encrypted" : "plaintext";
|
|
3959
|
+
const tmpPath = keyPath + ".tmp";
|
|
3960
|
+
try {
|
|
3961
|
+
if (existsSync8(keyPath)) {
|
|
3962
|
+
await copyFile(keyPath, keyPath + ".bak").catch(() => {
|
|
3963
|
+
});
|
|
3964
|
+
}
|
|
3965
|
+
await writeFile3(tmpPath, content, "utf-8");
|
|
3966
|
+
await chmod2(tmpPath, 384);
|
|
3967
|
+
await rename(tmpPath, keyPath);
|
|
3968
|
+
} catch (err) {
|
|
3969
|
+
try {
|
|
3970
|
+
await unlink(tmpPath);
|
|
3971
|
+
} catch {
|
|
3972
|
+
}
|
|
3973
|
+
throw err;
|
|
3939
3974
|
}
|
|
3940
|
-
|
|
3941
|
-
await chmod2(keyPath, 384);
|
|
3942
|
-
return "plaintext";
|
|
3975
|
+
return result;
|
|
3943
3976
|
}
|
|
3944
3977
|
async function getMasterKey() {
|
|
3945
3978
|
let nativeValue = macKeychainGet() ?? linuxSecretGet();
|
|
@@ -4006,7 +4039,7 @@ async function getMasterKey() {
|
|
|
4006
4039
|
b64Value = content;
|
|
4007
4040
|
}
|
|
4008
4041
|
const key = Buffer.from(b64Value, "base64");
|
|
4009
|
-
if (
|
|
4042
|
+
if (isRootOnlyTrustedServerKeyFile(keyPath)) {
|
|
4010
4043
|
return key;
|
|
4011
4044
|
}
|
|
4012
4045
|
const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
|
|
@@ -7718,6 +7751,7 @@ var init_provider_table = __esm({
|
|
|
7718
7751
|
// src/lib/intercom-queue.ts
|
|
7719
7752
|
var intercom_queue_exports = {};
|
|
7720
7753
|
__export(intercom_queue_exports, {
|
|
7754
|
+
_resetDrainGuard: () => _resetDrainGuard,
|
|
7721
7755
|
clearQueueForAgent: () => clearQueueForAgent,
|
|
7722
7756
|
drainForSession: () => drainForSession,
|
|
7723
7757
|
drainQueue: () => drainQueue,
|
|
@@ -7763,38 +7797,47 @@ function queueIntercom(targetSession, reason) {
|
|
|
7763
7797
|
writeQueue(queue);
|
|
7764
7798
|
}
|
|
7765
7799
|
function drainQueue(isSessionBusy2, sendKeys) {
|
|
7800
|
+
if (_draining) {
|
|
7801
|
+
logQueue("SKIP_DRAIN \u2014 previous drain still running (possible tmux hang)");
|
|
7802
|
+
return { drained: 0, failed: 0 };
|
|
7803
|
+
}
|
|
7766
7804
|
const queue = readQueue();
|
|
7767
7805
|
if (queue.length === 0) return { drained: 0, failed: 0 };
|
|
7806
|
+
_draining = true;
|
|
7768
7807
|
const remaining = [];
|
|
7769
7808
|
let drained = 0;
|
|
7770
7809
|
let failed = 0;
|
|
7771
|
-
|
|
7772
|
-
const
|
|
7773
|
-
|
|
7774
|
-
|
|
7775
|
-
|
|
7776
|
-
|
|
7777
|
-
|
|
7778
|
-
|
|
7779
|
-
|
|
7780
|
-
|
|
7781
|
-
|
|
7782
|
-
|
|
7783
|
-
|
|
7784
|
-
|
|
7810
|
+
try {
|
|
7811
|
+
for (const item of queue) {
|
|
7812
|
+
const age = Date.now() - new Date(item.queuedAt).getTime();
|
|
7813
|
+
if (age > TTL_MS) {
|
|
7814
|
+
logQueue(`EXPIRED \u2192 ${item.targetSession} (${Math.round(age / 6e4)}min old, reason: ${item.reason})`);
|
|
7815
|
+
failed++;
|
|
7816
|
+
continue;
|
|
7817
|
+
}
|
|
7818
|
+
try {
|
|
7819
|
+
if (!isSessionBusy2(item.targetSession)) {
|
|
7820
|
+
const success = sendKeys(item.targetSession);
|
|
7821
|
+
if (success) {
|
|
7822
|
+
logQueue(`DRAINED \u2192 ${item.targetSession} (after ${item.attempts} retries)`);
|
|
7823
|
+
drained++;
|
|
7824
|
+
continue;
|
|
7825
|
+
}
|
|
7785
7826
|
}
|
|
7827
|
+
} catch {
|
|
7786
7828
|
}
|
|
7787
|
-
|
|
7788
|
-
|
|
7789
|
-
|
|
7790
|
-
|
|
7791
|
-
|
|
7792
|
-
|
|
7793
|
-
|
|
7829
|
+
item.attempts++;
|
|
7830
|
+
if (item.attempts >= MAX_RETRIES2) {
|
|
7831
|
+
logQueue(`FAILED \u2192 ${item.targetSession} (${MAX_RETRIES2} retries exhausted, reason: ${item.reason})`);
|
|
7832
|
+
failed++;
|
|
7833
|
+
continue;
|
|
7834
|
+
}
|
|
7835
|
+
remaining.push(item);
|
|
7794
7836
|
}
|
|
7795
|
-
remaining
|
|
7837
|
+
writeQueue(remaining);
|
|
7838
|
+
} finally {
|
|
7839
|
+
_draining = false;
|
|
7796
7840
|
}
|
|
7797
|
-
writeQueue(remaining);
|
|
7798
7841
|
return { drained, failed };
|
|
7799
7842
|
}
|
|
7800
7843
|
function drainForSession(targetSession, sendKeys) {
|
|
@@ -7819,6 +7862,9 @@ function clearQueueForAgent(agentName) {
|
|
|
7819
7862
|
logQueue(`CLEARED ${before - filtered.length} stale item(s) for ${agentName}`);
|
|
7820
7863
|
}
|
|
7821
7864
|
}
|
|
7865
|
+
function _resetDrainGuard() {
|
|
7866
|
+
_draining = false;
|
|
7867
|
+
}
|
|
7822
7868
|
function logQueue(msg) {
|
|
7823
7869
|
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] [queue] ${msg}
|
|
7824
7870
|
`;
|
|
@@ -7830,13 +7876,14 @@ function logQueue(msg) {
|
|
|
7830
7876
|
} catch {
|
|
7831
7877
|
}
|
|
7832
7878
|
}
|
|
7833
|
-
var QUEUE_PATH, MAX_RETRIES2, TTL_MS, INTERCOM_LOG;
|
|
7879
|
+
var QUEUE_PATH, MAX_RETRIES2, TTL_MS, _draining, INTERCOM_LOG;
|
|
7834
7880
|
var init_intercom_queue = __esm({
|
|
7835
7881
|
"src/lib/intercom-queue.ts"() {
|
|
7836
7882
|
"use strict";
|
|
7837
7883
|
QUEUE_PATH = path15.join(os8.homedir(), ".exe-os", "intercom-queue.json");
|
|
7838
7884
|
MAX_RETRIES2 = 5;
|
|
7839
7885
|
TTL_MS = 60 * 60 * 1e3;
|
|
7886
|
+
_draining = false;
|
|
7840
7887
|
INTERCOM_LOG = path15.join(os8.homedir(), ".exe-os", "intercom.log");
|
|
7841
7888
|
}
|
|
7842
7889
|
});
|
|
@@ -8484,6 +8531,17 @@ var init_task_scope = __esm({
|
|
|
8484
8531
|
});
|
|
8485
8532
|
|
|
8486
8533
|
// src/lib/notifications.ts
|
|
8534
|
+
var notifications_exports = {};
|
|
8535
|
+
__export(notifications_exports, {
|
|
8536
|
+
cleanupOldNotifications: () => cleanupOldNotifications,
|
|
8537
|
+
formatNotifications: () => formatNotifications,
|
|
8538
|
+
markAsRead: () => markAsRead,
|
|
8539
|
+
markAsReadByTaskFile: () => markAsReadByTaskFile,
|
|
8540
|
+
markDoneTaskNotificationsAsRead: () => markDoneTaskNotificationsAsRead,
|
|
8541
|
+
migrateJsonNotifications: () => migrateJsonNotifications,
|
|
8542
|
+
readUnreadNotifications: () => readUnreadNotifications,
|
|
8543
|
+
writeNotification: () => writeNotification
|
|
8544
|
+
});
|
|
8487
8545
|
import crypto3 from "crypto";
|
|
8488
8546
|
import path19 from "path";
|
|
8489
8547
|
import os11 from "os";
|
|
@@ -8520,6 +8578,52 @@ async function writeNotification(notification) {
|
|
|
8520
8578
|
`);
|
|
8521
8579
|
}
|
|
8522
8580
|
}
|
|
8581
|
+
async function readUnreadNotifications(agentFilter, sessionScope) {
|
|
8582
|
+
try {
|
|
8583
|
+
const client = getClient();
|
|
8584
|
+
const conditions = ["read = 0"];
|
|
8585
|
+
const args = [];
|
|
8586
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
8587
|
+
if (agentFilter) {
|
|
8588
|
+
conditions.push("agent_id = ?");
|
|
8589
|
+
args.push(agentFilter);
|
|
8590
|
+
}
|
|
8591
|
+
const result = await client.execute({
|
|
8592
|
+
sql: `SELECT id, agent_id, agent_role, event, project, summary, task_file, session_scope, created_at
|
|
8593
|
+
FROM notifications
|
|
8594
|
+
WHERE ${conditions.join(" AND ")}${scope.sql}
|
|
8595
|
+
ORDER BY created_at ASC`,
|
|
8596
|
+
args: [...args, ...scope.args]
|
|
8597
|
+
});
|
|
8598
|
+
return result.rows.map((r) => ({
|
|
8599
|
+
id: String(r.id),
|
|
8600
|
+
agentId: String(r.agent_id),
|
|
8601
|
+
agentRole: String(r.agent_role),
|
|
8602
|
+
event: String(r.event),
|
|
8603
|
+
project: String(r.project),
|
|
8604
|
+
summary: String(r.summary),
|
|
8605
|
+
taskFile: r.task_file ? String(r.task_file) : void 0,
|
|
8606
|
+
sessionScope: r.session_scope == null ? null : String(r.session_scope),
|
|
8607
|
+
timestamp: String(r.created_at),
|
|
8608
|
+
read: false
|
|
8609
|
+
}));
|
|
8610
|
+
} catch {
|
|
8611
|
+
return [];
|
|
8612
|
+
}
|
|
8613
|
+
}
|
|
8614
|
+
async function markAsRead(ids, sessionScope) {
|
|
8615
|
+
if (ids.length === 0) return;
|
|
8616
|
+
try {
|
|
8617
|
+
const client = getClient();
|
|
8618
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
8619
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
8620
|
+
await client.execute({
|
|
8621
|
+
sql: `UPDATE notifications SET read = 1 WHERE id IN (${placeholders})${scope.sql}`,
|
|
8622
|
+
args: [...ids, ...scope.args]
|
|
8623
|
+
});
|
|
8624
|
+
} catch {
|
|
8625
|
+
}
|
|
8626
|
+
}
|
|
8523
8627
|
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
8524
8628
|
try {
|
|
8525
8629
|
const client = getClient();
|
|
@@ -8532,11 +8636,144 @@ async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
|
8532
8636
|
} catch {
|
|
8533
8637
|
}
|
|
8534
8638
|
}
|
|
8639
|
+
async function cleanupOldNotifications(daysOld = CLEANUP_DAYS, sessionScope) {
|
|
8640
|
+
try {
|
|
8641
|
+
const client = getClient();
|
|
8642
|
+
const cutoff = new Date(
|
|
8643
|
+
Date.now() - daysOld * 24 * 60 * 60 * 1e3
|
|
8644
|
+
).toISOString();
|
|
8645
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
8646
|
+
const result = await client.execute({
|
|
8647
|
+
sql: `DELETE FROM notifications WHERE created_at < ?${scope.sql}`,
|
|
8648
|
+
args: [cutoff, ...scope.args]
|
|
8649
|
+
});
|
|
8650
|
+
return result.rowsAffected;
|
|
8651
|
+
} catch {
|
|
8652
|
+
return 0;
|
|
8653
|
+
}
|
|
8654
|
+
}
|
|
8655
|
+
async function markDoneTaskNotificationsAsRead(sessionScope) {
|
|
8656
|
+
try {
|
|
8657
|
+
const client = getClient();
|
|
8658
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
8659
|
+
const result = await client.execute({
|
|
8660
|
+
sql: `UPDATE notifications SET read = 1
|
|
8661
|
+
WHERE read = 0
|
|
8662
|
+
AND task_file IS NOT NULL
|
|
8663
|
+
${scope.sql}
|
|
8664
|
+
AND task_file IN (
|
|
8665
|
+
SELECT task_file FROM tasks WHERE status = 'done'${scope.sql}
|
|
8666
|
+
)`,
|
|
8667
|
+
args: [...scope.args, ...scope.args]
|
|
8668
|
+
});
|
|
8669
|
+
return result.rowsAffected;
|
|
8670
|
+
} catch {
|
|
8671
|
+
return 0;
|
|
8672
|
+
}
|
|
8673
|
+
}
|
|
8674
|
+
function formatNotifications(notifications) {
|
|
8675
|
+
if (notifications.length === 0) return "";
|
|
8676
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
8677
|
+
for (const n of notifications) {
|
|
8678
|
+
const key = `${n.agentId}|${n.agentRole}`;
|
|
8679
|
+
if (!grouped.has(key)) grouped.set(key, []);
|
|
8680
|
+
grouped.get(key).push(n);
|
|
8681
|
+
}
|
|
8682
|
+
const lines = [];
|
|
8683
|
+
lines.push(`## Notifications (${notifications.length} unread)
|
|
8684
|
+
`);
|
|
8685
|
+
for (const [key, items] of grouped) {
|
|
8686
|
+
const [agentId, agentRole] = key.split("|");
|
|
8687
|
+
lines.push(`**${agentId}** (${agentRole}):`);
|
|
8688
|
+
for (const item of items) {
|
|
8689
|
+
const ago = formatTimeAgo(item.timestamp);
|
|
8690
|
+
const icon = eventIcon(item.event);
|
|
8691
|
+
lines.push(`- ${icon} ${item.summary} (${item.project}) \u2014 ${ago}`);
|
|
8692
|
+
}
|
|
8693
|
+
lines.push("");
|
|
8694
|
+
}
|
|
8695
|
+
return lines.join("\n");
|
|
8696
|
+
}
|
|
8697
|
+
async function migrateJsonNotifications() {
|
|
8698
|
+
const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR || path19.join(os11.homedir(), ".exe-os");
|
|
8699
|
+
const notifDir = path19.join(base, "notifications");
|
|
8700
|
+
if (!existsSync18(notifDir)) return 0;
|
|
8701
|
+
let migrated = 0;
|
|
8702
|
+
try {
|
|
8703
|
+
const files = readdirSync4(notifDir).filter((f) => f.endsWith(".json"));
|
|
8704
|
+
if (files.length === 0) return 0;
|
|
8705
|
+
const client = getClient();
|
|
8706
|
+
for (const file of files) {
|
|
8707
|
+
try {
|
|
8708
|
+
const filePath = path19.join(notifDir, file);
|
|
8709
|
+
const data = JSON.parse(readFileSync13(filePath, "utf8"));
|
|
8710
|
+
await client.execute({
|
|
8711
|
+
sql: `INSERT OR IGNORE INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
8712
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
8713
|
+
args: [
|
|
8714
|
+
crypto3.randomUUID(),
|
|
8715
|
+
data.agentId ?? "unknown",
|
|
8716
|
+
data.agentRole ?? "unknown",
|
|
8717
|
+
data.event ?? "session_summary",
|
|
8718
|
+
data.project ?? "unknown",
|
|
8719
|
+
data.summary ?? "",
|
|
8720
|
+
data.taskFile ?? null,
|
|
8721
|
+
null,
|
|
8722
|
+
data.read ? 1 : 0,
|
|
8723
|
+
data.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
8724
|
+
]
|
|
8725
|
+
});
|
|
8726
|
+
unlinkSync6(filePath);
|
|
8727
|
+
migrated++;
|
|
8728
|
+
} catch {
|
|
8729
|
+
}
|
|
8730
|
+
}
|
|
8731
|
+
try {
|
|
8732
|
+
const remaining = readdirSync4(notifDir);
|
|
8733
|
+
if (remaining.length === 0) {
|
|
8734
|
+
rmdirSync(notifDir);
|
|
8735
|
+
}
|
|
8736
|
+
} catch {
|
|
8737
|
+
}
|
|
8738
|
+
} catch {
|
|
8739
|
+
}
|
|
8740
|
+
return migrated;
|
|
8741
|
+
}
|
|
8742
|
+
function eventIcon(event) {
|
|
8743
|
+
switch (event) {
|
|
8744
|
+
case "task_complete":
|
|
8745
|
+
return "Completed:";
|
|
8746
|
+
case "task_needs_fix":
|
|
8747
|
+
return "Needs fix:";
|
|
8748
|
+
case "session_summary":
|
|
8749
|
+
return "Session:";
|
|
8750
|
+
case "error_spike":
|
|
8751
|
+
return "Errors:";
|
|
8752
|
+
case "orphan_task":
|
|
8753
|
+
return "Orphan:";
|
|
8754
|
+
case "subtasks_complete":
|
|
8755
|
+
return "Subtasks done:";
|
|
8756
|
+
case "capacity_relaunch":
|
|
8757
|
+
return "Relaunched:";
|
|
8758
|
+
}
|
|
8759
|
+
}
|
|
8760
|
+
function formatTimeAgo(timestamp) {
|
|
8761
|
+
const diffMs = Date.now() - new Date(timestamp).getTime();
|
|
8762
|
+
const mins = Math.floor(diffMs / 6e4);
|
|
8763
|
+
if (mins < 1) return "just now";
|
|
8764
|
+
if (mins < 60) return `${mins}m ago`;
|
|
8765
|
+
const hours = Math.floor(mins / 60);
|
|
8766
|
+
if (hours < 24) return `${hours}h ago`;
|
|
8767
|
+
const days = Math.floor(hours / 24);
|
|
8768
|
+
return `${days}d ago`;
|
|
8769
|
+
}
|
|
8770
|
+
var CLEANUP_DAYS;
|
|
8535
8771
|
var init_notifications = __esm({
|
|
8536
8772
|
"src/lib/notifications.ts"() {
|
|
8537
8773
|
"use strict";
|
|
8538
8774
|
init_database();
|
|
8539
8775
|
init_task_scope();
|
|
8776
|
+
CLEANUP_DAYS = 7;
|
|
8540
8777
|
}
|
|
8541
8778
|
});
|
|
8542
8779
|
|
|
@@ -9485,7 +9722,13 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
|
|
|
9485
9722
|
taskFile
|
|
9486
9723
|
});
|
|
9487
9724
|
const originalPriority = String(row.priority).toLowerCase();
|
|
9488
|
-
const
|
|
9725
|
+
const resultLower = result?.toLowerCase() ?? "";
|
|
9726
|
+
const hasTestEvidence = (
|
|
9727
|
+
// Vitest/Jest output patterns (hard to fake without actually running tests)
|
|
9728
|
+
/\d+\s+pass(ed|ing)/.test(resultLower) || /test files?\s+\d+\s+passed/.test(resultLower) || /tests?\s+\d+\s+passed/.test(resultLower)
|
|
9729
|
+
);
|
|
9730
|
+
const hasNoFailures = !/fail(ed|ure|ing)|error/i.test(resultLower);
|
|
9731
|
+
const autoApprove = originalPriority === "p2" && hasTestEvidence && hasNoFailures;
|
|
9489
9732
|
if (!autoApprove) {
|
|
9490
9733
|
try {
|
|
9491
9734
|
const key = getSessionKey();
|
|
@@ -9493,6 +9736,13 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
|
|
|
9493
9736
|
if (exeSession) {
|
|
9494
9737
|
sendIntercom(exeSession);
|
|
9495
9738
|
}
|
|
9739
|
+
if (reviewer && reviewer !== coordinatorName && reviewer !== exeSession) {
|
|
9740
|
+
const { employeeSessionName: employeeSessionName2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
9741
|
+
if (exeSession) {
|
|
9742
|
+
const reviewerSession = employeeSessionName2(reviewer, exeSession);
|
|
9743
|
+
sendIntercom(reviewerSession);
|
|
9744
|
+
}
|
|
9745
|
+
}
|
|
9496
9746
|
} catch {
|
|
9497
9747
|
}
|
|
9498
9748
|
}
|
|
@@ -10277,6 +10527,20 @@ async function updateTask(input2) {
|
|
|
10277
10527
|
notifyTaskDone();
|
|
10278
10528
|
}
|
|
10279
10529
|
await markTaskNotificationsRead(taskFile);
|
|
10530
|
+
if (input2.status === "needs_review" && !isCoordinator) {
|
|
10531
|
+
try {
|
|
10532
|
+
const { writeNotification: writeNotification2 } = await Promise.resolve().then(() => (init_notifications(), notifications_exports));
|
|
10533
|
+
await writeNotification2({
|
|
10534
|
+
agentId: String(row.assigned_to),
|
|
10535
|
+
agentRole: String(row.assigned_to),
|
|
10536
|
+
event: "task_complete",
|
|
10537
|
+
project: String(row.project_name),
|
|
10538
|
+
summary: `"${String(row.title)}" is ready for review`,
|
|
10539
|
+
taskFile
|
|
10540
|
+
});
|
|
10541
|
+
} catch {
|
|
10542
|
+
}
|
|
10543
|
+
}
|
|
10280
10544
|
if (input2.status === "done" || input2.status === "closed") {
|
|
10281
10545
|
try {
|
|
10282
10546
|
await cascadeUnblock(taskId, input2.baseDir, now);
|
|
@@ -10709,18 +10973,31 @@ function acquireSpawnLock2(sessionName) {
|
|
|
10709
10973
|
mkdirSync11(SPAWN_LOCK_DIR, { recursive: true });
|
|
10710
10974
|
}
|
|
10711
10975
|
const lockFile = spawnLockPath(sessionName);
|
|
10712
|
-
|
|
10713
|
-
|
|
10714
|
-
|
|
10715
|
-
|
|
10716
|
-
|
|
10717
|
-
|
|
10718
|
-
|
|
10719
|
-
|
|
10976
|
+
const lockData = JSON.stringify({ pid: process.pid, timestamp: Date.now() });
|
|
10977
|
+
const { openSync: openSync3, closeSync: closeSync3, writeSync } = __require("fs");
|
|
10978
|
+
const { constants } = __require("fs");
|
|
10979
|
+
try {
|
|
10980
|
+
const fd = openSync3(lockFile, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 420);
|
|
10981
|
+
writeSync(fd, lockData);
|
|
10982
|
+
closeSync3(fd);
|
|
10983
|
+
return true;
|
|
10984
|
+
} catch (err) {
|
|
10985
|
+
if (err?.code !== "EEXIST") {
|
|
10986
|
+
return true;
|
|
10720
10987
|
}
|
|
10721
10988
|
}
|
|
10722
|
-
|
|
10723
|
-
|
|
10989
|
+
try {
|
|
10990
|
+
const lock = JSON.parse(readFileSync15(lockFile, "utf8"));
|
|
10991
|
+
const age = Date.now() - lock.timestamp;
|
|
10992
|
+
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
10993
|
+
return false;
|
|
10994
|
+
}
|
|
10995
|
+
writeFileSync10(lockFile, lockData);
|
|
10996
|
+
return true;
|
|
10997
|
+
} catch {
|
|
10998
|
+
writeFileSync10(lockFile, lockData);
|
|
10999
|
+
return true;
|
|
11000
|
+
}
|
|
10724
11001
|
}
|
|
10725
11002
|
function releaseSpawnLock2(sessionName) {
|
|
10726
11003
|
try {
|
|
@@ -10799,6 +11076,21 @@ function parseParentExe(sessionName, agentId) {
|
|
|
10799
11076
|
function extractRootExe(name) {
|
|
10800
11077
|
if (!name) return null;
|
|
10801
11078
|
if (!name.includes("-")) return name;
|
|
11079
|
+
try {
|
|
11080
|
+
const roster = (init_employees(), __toCommonJS(employees_exports)).loadEmployeesSync();
|
|
11081
|
+
if (roster.length > 0) {
|
|
11082
|
+
const sortedNames = roster.map((e) => e.name).sort((a, b) => b.length - a.length);
|
|
11083
|
+
for (const agentName of sortedNames) {
|
|
11084
|
+
const escaped = agentName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
11085
|
+
const regex = new RegExp(`^${escaped}\\d*-(.+)$`);
|
|
11086
|
+
const match = name.match(regex);
|
|
11087
|
+
if (match) {
|
|
11088
|
+
return extractRootExe(match[1]);
|
|
11089
|
+
}
|
|
11090
|
+
}
|
|
11091
|
+
}
|
|
11092
|
+
} catch {
|
|
11093
|
+
}
|
|
10802
11094
|
const parts = name.split("-").filter(Boolean);
|
|
10803
11095
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
10804
11096
|
}
|
|
@@ -10817,6 +11109,10 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
10817
11109
|
function getParentExe(sessionKey) {
|
|
10818
11110
|
try {
|
|
10819
11111
|
const data = JSON.parse(readFileSync15(path24.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
11112
|
+
if (data.registeredAt) {
|
|
11113
|
+
const age = Date.now() - new Date(data.registeredAt).getTime();
|
|
11114
|
+
if (age > PARENT_EXE_CACHE_TTL_MS) return null;
|
|
11115
|
+
}
|
|
10820
11116
|
return data.parentExe || null;
|
|
10821
11117
|
} catch {
|
|
10822
11118
|
return null;
|
|
@@ -11365,7 +11661,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
11365
11661
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
11366
11662
|
} catch {
|
|
11367
11663
|
}
|
|
11368
|
-
let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName}`;
|
|
11664
|
+
let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName} EXE_SESSION_START_ISO=${(/* @__PURE__ */ new Date()).toISOString()}`;
|
|
11369
11665
|
if (ccProvider !== DEFAULT_PROVIDER) {
|
|
11370
11666
|
const cfg = PROVIDER_TABLE[ccProvider];
|
|
11371
11667
|
if (cfg?.apiKeyEnv) {
|
|
@@ -11400,10 +11696,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
11400
11696
|
}
|
|
11401
11697
|
if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
|
|
11402
11698
|
if (agentRtConfig.runtime === "claude" && agentRtConfig.model) {
|
|
11403
|
-
|
|
11404
|
-
|
|
11405
|
-
ccModel += "[1m]";
|
|
11406
|
-
}
|
|
11699
|
+
const { normalizeCcModelName: normalizeCcModelName2 } = (init_agent_config(), __toCommonJS(agent_config_exports));
|
|
11700
|
+
const ccModel = normalizeCcModelName2(agentRtConfig.model);
|
|
11407
11701
|
envPrefix = `${envPrefix} ANTHROPIC_MODEL=${ccModel}`;
|
|
11408
11702
|
}
|
|
11409
11703
|
}
|
|
@@ -11504,7 +11798,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
11504
11798
|
releaseSpawnLock2(sessionName);
|
|
11505
11799
|
return { sessionName };
|
|
11506
11800
|
}
|
|
11507
|
-
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;
|
|
11801
|
+
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;
|
|
11508
11802
|
var init_tmux_routing = __esm({
|
|
11509
11803
|
"src/lib/tmux-routing.ts"() {
|
|
11510
11804
|
"use strict";
|
|
@@ -11524,6 +11818,7 @@ var init_tmux_routing = __esm({
|
|
|
11524
11818
|
SESSION_CACHE = path24.join(os13.homedir(), ".exe-os", "session-cache");
|
|
11525
11819
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
11526
11820
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
11821
|
+
PARENT_EXE_CACHE_TTL_MS = 4 * 60 * 60 * 1e3;
|
|
11527
11822
|
VERIFY_PANE_LINES = 200;
|
|
11528
11823
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
11529
11824
|
CODEX_DEBOUNCE_MS = 12e4;
|