@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/hooks/ingest.js
CHANGED
|
@@ -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());
|
|
@@ -435,6 +448,7 @@ __export(agent_config_exports, {
|
|
|
435
448
|
clearAgentRuntime: () => clearAgentRuntime,
|
|
436
449
|
getAgentRuntime: () => getAgentRuntime,
|
|
437
450
|
loadAgentConfig: () => loadAgentConfig,
|
|
451
|
+
normalizeCcModelName: () => normalizeCcModelName,
|
|
438
452
|
saveAgentConfig: () => saveAgentConfig,
|
|
439
453
|
setAgentMcps: () => setAgentMcps,
|
|
440
454
|
setAgentRuntime: () => setAgentRuntime
|
|
@@ -463,6 +477,13 @@ function getAgentRuntime(agentId) {
|
|
|
463
477
|
if (orgDefault) return orgDefault;
|
|
464
478
|
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
465
479
|
}
|
|
480
|
+
function normalizeCcModelName(model) {
|
|
481
|
+
let ccModel = model.replace(/(\d+)\.(\d+)/g, "$1-$2");
|
|
482
|
+
if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
|
|
483
|
+
ccModel += "[1m]";
|
|
484
|
+
}
|
|
485
|
+
return ccModel;
|
|
486
|
+
}
|
|
466
487
|
function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
|
|
467
488
|
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
468
489
|
if (!knownModels) {
|
|
@@ -3782,7 +3803,7 @@ var init_database = __esm({
|
|
|
3782
3803
|
});
|
|
3783
3804
|
|
|
3784
3805
|
// src/lib/keychain.ts
|
|
3785
|
-
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
3806
|
+
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2, rename, copyFile } from "fs/promises";
|
|
3786
3807
|
import { existsSync as existsSync10, statSync as statSync3 } from "fs";
|
|
3787
3808
|
import { execSync as execSync5 } from "child_process";
|
|
3788
3809
|
import path10 from "path";
|
|
@@ -3817,12 +3838,14 @@ function linuxSecretAvailable() {
|
|
|
3817
3838
|
function isRootOnlyTrustedServerKeyFile(keyPath) {
|
|
3818
3839
|
if (process.platform !== "linux") return false;
|
|
3819
3840
|
try {
|
|
3820
|
-
const uid = typeof os6.userInfo().uid === "number" ? os6.userInfo().uid : -1;
|
|
3821
3841
|
const st = statSync3(keyPath);
|
|
3822
3842
|
if (!st.isFile() || (st.mode & 63) !== 0) return false;
|
|
3843
|
+
const uid = typeof os6.userInfo().uid === "number" ? os6.userInfo().uid : -1;
|
|
3823
3844
|
if (uid === 0) return true;
|
|
3824
3845
|
const exeOsDir = process.env.EXE_OS_DIR;
|
|
3825
|
-
|
|
3846
|
+
if (exeOsDir && path10.resolve(keyPath).startsWith(path10.resolve(exeOsDir) + path10.sep)) return true;
|
|
3847
|
+
if (!linuxSecretAvailable()) return true;
|
|
3848
|
+
return false;
|
|
3826
3849
|
} catch {
|
|
3827
3850
|
return false;
|
|
3828
3851
|
}
|
|
@@ -3972,15 +3995,25 @@ async function writeMachineBoundFileFallback(b64) {
|
|
|
3972
3995
|
await mkdir3(dir, { recursive: true });
|
|
3973
3996
|
const keyPath = getKeyPath();
|
|
3974
3997
|
const machineKey = deriveMachineKey();
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3998
|
+
const content = machineKey ? encryptWithMachineKey(b64, machineKey) + "\n" : b64 + "\n";
|
|
3999
|
+
const result = machineKey ? "encrypted" : "plaintext";
|
|
4000
|
+
const tmpPath = keyPath + ".tmp";
|
|
4001
|
+
try {
|
|
4002
|
+
if (existsSync10(keyPath)) {
|
|
4003
|
+
await copyFile(keyPath, keyPath + ".bak").catch(() => {
|
|
4004
|
+
});
|
|
4005
|
+
}
|
|
4006
|
+
await writeFile3(tmpPath, content, "utf-8");
|
|
4007
|
+
await chmod2(tmpPath, 384);
|
|
4008
|
+
await rename(tmpPath, keyPath);
|
|
4009
|
+
} catch (err) {
|
|
4010
|
+
try {
|
|
4011
|
+
await unlink(tmpPath);
|
|
4012
|
+
} catch {
|
|
4013
|
+
}
|
|
4014
|
+
throw err;
|
|
3980
4015
|
}
|
|
3981
|
-
|
|
3982
|
-
await chmod2(keyPath, 384);
|
|
3983
|
-
return "plaintext";
|
|
4016
|
+
return result;
|
|
3984
4017
|
}
|
|
3985
4018
|
async function getMasterKey() {
|
|
3986
4019
|
let nativeValue = macKeychainGet() ?? linuxSecretGet();
|
|
@@ -4047,7 +4080,7 @@ async function getMasterKey() {
|
|
|
4047
4080
|
b64Value = content;
|
|
4048
4081
|
}
|
|
4049
4082
|
const key = Buffer.from(b64Value, "base64");
|
|
4050
|
-
if (
|
|
4083
|
+
if (isRootOnlyTrustedServerKeyFile(keyPath)) {
|
|
4051
4084
|
return key;
|
|
4052
4085
|
}
|
|
4053
4086
|
const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
|
|
@@ -6594,6 +6627,7 @@ var init_provider_table = __esm({
|
|
|
6594
6627
|
// src/lib/intercom-queue.ts
|
|
6595
6628
|
var intercom_queue_exports = {};
|
|
6596
6629
|
__export(intercom_queue_exports, {
|
|
6630
|
+
_resetDrainGuard: () => _resetDrainGuard,
|
|
6597
6631
|
clearQueueForAgent: () => clearQueueForAgent,
|
|
6598
6632
|
drainForSession: () => drainForSession,
|
|
6599
6633
|
drainQueue: () => drainQueue,
|
|
@@ -6639,38 +6673,47 @@ function queueIntercom(targetSession, reason) {
|
|
|
6639
6673
|
writeQueue(queue);
|
|
6640
6674
|
}
|
|
6641
6675
|
function drainQueue(isSessionBusy2, sendKeys) {
|
|
6676
|
+
if (_draining) {
|
|
6677
|
+
logQueue("SKIP_DRAIN \u2014 previous drain still running (possible tmux hang)");
|
|
6678
|
+
return { drained: 0, failed: 0 };
|
|
6679
|
+
}
|
|
6642
6680
|
const queue = readQueue();
|
|
6643
6681
|
if (queue.length === 0) return { drained: 0, failed: 0 };
|
|
6682
|
+
_draining = true;
|
|
6644
6683
|
const remaining = [];
|
|
6645
6684
|
let drained = 0;
|
|
6646
6685
|
let failed = 0;
|
|
6647
|
-
|
|
6648
|
-
const
|
|
6649
|
-
|
|
6650
|
-
|
|
6651
|
-
|
|
6652
|
-
|
|
6653
|
-
|
|
6654
|
-
|
|
6655
|
-
|
|
6656
|
-
|
|
6657
|
-
|
|
6658
|
-
|
|
6659
|
-
|
|
6660
|
-
|
|
6686
|
+
try {
|
|
6687
|
+
for (const item of queue) {
|
|
6688
|
+
const age = Date.now() - new Date(item.queuedAt).getTime();
|
|
6689
|
+
if (age > TTL_MS) {
|
|
6690
|
+
logQueue(`EXPIRED \u2192 ${item.targetSession} (${Math.round(age / 6e4)}min old, reason: ${item.reason})`);
|
|
6691
|
+
failed++;
|
|
6692
|
+
continue;
|
|
6693
|
+
}
|
|
6694
|
+
try {
|
|
6695
|
+
if (!isSessionBusy2(item.targetSession)) {
|
|
6696
|
+
const success = sendKeys(item.targetSession);
|
|
6697
|
+
if (success) {
|
|
6698
|
+
logQueue(`DRAINED \u2192 ${item.targetSession} (after ${item.attempts} retries)`);
|
|
6699
|
+
drained++;
|
|
6700
|
+
continue;
|
|
6701
|
+
}
|
|
6661
6702
|
}
|
|
6703
|
+
} catch {
|
|
6662
6704
|
}
|
|
6663
|
-
|
|
6664
|
-
|
|
6665
|
-
|
|
6666
|
-
|
|
6667
|
-
|
|
6668
|
-
|
|
6669
|
-
|
|
6705
|
+
item.attempts++;
|
|
6706
|
+
if (item.attempts >= MAX_RETRIES2) {
|
|
6707
|
+
logQueue(`FAILED \u2192 ${item.targetSession} (${MAX_RETRIES2} retries exhausted, reason: ${item.reason})`);
|
|
6708
|
+
failed++;
|
|
6709
|
+
continue;
|
|
6710
|
+
}
|
|
6711
|
+
remaining.push(item);
|
|
6670
6712
|
}
|
|
6671
|
-
remaining
|
|
6713
|
+
writeQueue(remaining);
|
|
6714
|
+
} finally {
|
|
6715
|
+
_draining = false;
|
|
6672
6716
|
}
|
|
6673
|
-
writeQueue(remaining);
|
|
6674
6717
|
return { drained, failed };
|
|
6675
6718
|
}
|
|
6676
6719
|
function drainForSession(targetSession, sendKeys) {
|
|
@@ -6695,6 +6738,9 @@ function clearQueueForAgent(agentName) {
|
|
|
6695
6738
|
logQueue(`CLEARED ${before - filtered.length} stale item(s) for ${agentName}`);
|
|
6696
6739
|
}
|
|
6697
6740
|
}
|
|
6741
|
+
function _resetDrainGuard() {
|
|
6742
|
+
_draining = false;
|
|
6743
|
+
}
|
|
6698
6744
|
function logQueue(msg) {
|
|
6699
6745
|
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] [queue] ${msg}
|
|
6700
6746
|
`;
|
|
@@ -6706,13 +6752,14 @@ function logQueue(msg) {
|
|
|
6706
6752
|
} catch {
|
|
6707
6753
|
}
|
|
6708
6754
|
}
|
|
6709
|
-
var QUEUE_PATH, MAX_RETRIES2, TTL_MS, INTERCOM_LOG;
|
|
6755
|
+
var QUEUE_PATH, MAX_RETRIES2, TTL_MS, _draining, INTERCOM_LOG;
|
|
6710
6756
|
var init_intercom_queue = __esm({
|
|
6711
6757
|
"src/lib/intercom-queue.ts"() {
|
|
6712
6758
|
"use strict";
|
|
6713
6759
|
QUEUE_PATH = path14.join(os8.homedir(), ".exe-os", "intercom-queue.json");
|
|
6714
6760
|
MAX_RETRIES2 = 5;
|
|
6715
6761
|
TTL_MS = 60 * 60 * 1e3;
|
|
6762
|
+
_draining = false;
|
|
6716
6763
|
INTERCOM_LOG = path14.join(os8.homedir(), ".exe-os", "intercom.log");
|
|
6717
6764
|
}
|
|
6718
6765
|
});
|
|
@@ -7327,6 +7374,17 @@ var init_agent_symlinks = __esm({
|
|
|
7327
7374
|
});
|
|
7328
7375
|
|
|
7329
7376
|
// src/lib/notifications.ts
|
|
7377
|
+
var notifications_exports = {};
|
|
7378
|
+
__export(notifications_exports, {
|
|
7379
|
+
cleanupOldNotifications: () => cleanupOldNotifications,
|
|
7380
|
+
formatNotifications: () => formatNotifications,
|
|
7381
|
+
markAsRead: () => markAsRead,
|
|
7382
|
+
markAsReadByTaskFile: () => markAsReadByTaskFile,
|
|
7383
|
+
markDoneTaskNotificationsAsRead: () => markDoneTaskNotificationsAsRead,
|
|
7384
|
+
migrateJsonNotifications: () => migrateJsonNotifications,
|
|
7385
|
+
readUnreadNotifications: () => readUnreadNotifications,
|
|
7386
|
+
writeNotification: () => writeNotification
|
|
7387
|
+
});
|
|
7330
7388
|
import crypto3 from "crypto";
|
|
7331
7389
|
import path18 from "path";
|
|
7332
7390
|
import os11 from "os";
|
|
@@ -7363,6 +7421,52 @@ async function writeNotification(notification) {
|
|
|
7363
7421
|
`);
|
|
7364
7422
|
}
|
|
7365
7423
|
}
|
|
7424
|
+
async function readUnreadNotifications(agentFilter, sessionScope) {
|
|
7425
|
+
try {
|
|
7426
|
+
const client = getClient();
|
|
7427
|
+
const conditions = ["read = 0"];
|
|
7428
|
+
const args = [];
|
|
7429
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
7430
|
+
if (agentFilter) {
|
|
7431
|
+
conditions.push("agent_id = ?");
|
|
7432
|
+
args.push(agentFilter);
|
|
7433
|
+
}
|
|
7434
|
+
const result = await client.execute({
|
|
7435
|
+
sql: `SELECT id, agent_id, agent_role, event, project, summary, task_file, session_scope, created_at
|
|
7436
|
+
FROM notifications
|
|
7437
|
+
WHERE ${conditions.join(" AND ")}${scope.sql}
|
|
7438
|
+
ORDER BY created_at ASC`,
|
|
7439
|
+
args: [...args, ...scope.args]
|
|
7440
|
+
});
|
|
7441
|
+
return result.rows.map((r) => ({
|
|
7442
|
+
id: String(r.id),
|
|
7443
|
+
agentId: String(r.agent_id),
|
|
7444
|
+
agentRole: String(r.agent_role),
|
|
7445
|
+
event: String(r.event),
|
|
7446
|
+
project: String(r.project),
|
|
7447
|
+
summary: String(r.summary),
|
|
7448
|
+
taskFile: r.task_file ? String(r.task_file) : void 0,
|
|
7449
|
+
sessionScope: r.session_scope == null ? null : String(r.session_scope),
|
|
7450
|
+
timestamp: String(r.created_at),
|
|
7451
|
+
read: false
|
|
7452
|
+
}));
|
|
7453
|
+
} catch {
|
|
7454
|
+
return [];
|
|
7455
|
+
}
|
|
7456
|
+
}
|
|
7457
|
+
async function markAsRead(ids, sessionScope) {
|
|
7458
|
+
if (ids.length === 0) return;
|
|
7459
|
+
try {
|
|
7460
|
+
const client = getClient();
|
|
7461
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
7462
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
7463
|
+
await client.execute({
|
|
7464
|
+
sql: `UPDATE notifications SET read = 1 WHERE id IN (${placeholders})${scope.sql}`,
|
|
7465
|
+
args: [...ids, ...scope.args]
|
|
7466
|
+
});
|
|
7467
|
+
} catch {
|
|
7468
|
+
}
|
|
7469
|
+
}
|
|
7366
7470
|
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
7367
7471
|
try {
|
|
7368
7472
|
const client = getClient();
|
|
@@ -7375,11 +7479,144 @@ async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
|
7375
7479
|
} catch {
|
|
7376
7480
|
}
|
|
7377
7481
|
}
|
|
7482
|
+
async function cleanupOldNotifications(daysOld = CLEANUP_DAYS, sessionScope) {
|
|
7483
|
+
try {
|
|
7484
|
+
const client = getClient();
|
|
7485
|
+
const cutoff = new Date(
|
|
7486
|
+
Date.now() - daysOld * 24 * 60 * 60 * 1e3
|
|
7487
|
+
).toISOString();
|
|
7488
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
7489
|
+
const result = await client.execute({
|
|
7490
|
+
sql: `DELETE FROM notifications WHERE created_at < ?${scope.sql}`,
|
|
7491
|
+
args: [cutoff, ...scope.args]
|
|
7492
|
+
});
|
|
7493
|
+
return result.rowsAffected;
|
|
7494
|
+
} catch {
|
|
7495
|
+
return 0;
|
|
7496
|
+
}
|
|
7497
|
+
}
|
|
7498
|
+
async function markDoneTaskNotificationsAsRead(sessionScope) {
|
|
7499
|
+
try {
|
|
7500
|
+
const client = getClient();
|
|
7501
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
7502
|
+
const result = await client.execute({
|
|
7503
|
+
sql: `UPDATE notifications SET read = 1
|
|
7504
|
+
WHERE read = 0
|
|
7505
|
+
AND task_file IS NOT NULL
|
|
7506
|
+
${scope.sql}
|
|
7507
|
+
AND task_file IN (
|
|
7508
|
+
SELECT task_file FROM tasks WHERE status = 'done'${scope.sql}
|
|
7509
|
+
)`,
|
|
7510
|
+
args: [...scope.args, ...scope.args]
|
|
7511
|
+
});
|
|
7512
|
+
return result.rowsAffected;
|
|
7513
|
+
} catch {
|
|
7514
|
+
return 0;
|
|
7515
|
+
}
|
|
7516
|
+
}
|
|
7517
|
+
function formatNotifications(notifications) {
|
|
7518
|
+
if (notifications.length === 0) return "";
|
|
7519
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
7520
|
+
for (const n of notifications) {
|
|
7521
|
+
const key = `${n.agentId}|${n.agentRole}`;
|
|
7522
|
+
if (!grouped.has(key)) grouped.set(key, []);
|
|
7523
|
+
grouped.get(key).push(n);
|
|
7524
|
+
}
|
|
7525
|
+
const lines = [];
|
|
7526
|
+
lines.push(`## Notifications (${notifications.length} unread)
|
|
7527
|
+
`);
|
|
7528
|
+
for (const [key, items] of grouped) {
|
|
7529
|
+
const [agentId, agentRole] = key.split("|");
|
|
7530
|
+
lines.push(`**${agentId}** (${agentRole}):`);
|
|
7531
|
+
for (const item of items) {
|
|
7532
|
+
const ago = formatTimeAgo(item.timestamp);
|
|
7533
|
+
const icon = eventIcon(item.event);
|
|
7534
|
+
lines.push(`- ${icon} ${item.summary} (${item.project}) \u2014 ${ago}`);
|
|
7535
|
+
}
|
|
7536
|
+
lines.push("");
|
|
7537
|
+
}
|
|
7538
|
+
return lines.join("\n");
|
|
7539
|
+
}
|
|
7540
|
+
async function migrateJsonNotifications() {
|
|
7541
|
+
const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR || path18.join(os11.homedir(), ".exe-os");
|
|
7542
|
+
const notifDir = path18.join(base, "notifications");
|
|
7543
|
+
if (!existsSync17(notifDir)) return 0;
|
|
7544
|
+
let migrated = 0;
|
|
7545
|
+
try {
|
|
7546
|
+
const files = readdirSync4(notifDir).filter((f) => f.endsWith(".json"));
|
|
7547
|
+
if (files.length === 0) return 0;
|
|
7548
|
+
const client = getClient();
|
|
7549
|
+
for (const file of files) {
|
|
7550
|
+
try {
|
|
7551
|
+
const filePath = path18.join(notifDir, file);
|
|
7552
|
+
const data = JSON.parse(readFileSync12(filePath, "utf8"));
|
|
7553
|
+
await client.execute({
|
|
7554
|
+
sql: `INSERT OR IGNORE INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
7555
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
7556
|
+
args: [
|
|
7557
|
+
crypto3.randomUUID(),
|
|
7558
|
+
data.agentId ?? "unknown",
|
|
7559
|
+
data.agentRole ?? "unknown",
|
|
7560
|
+
data.event ?? "session_summary",
|
|
7561
|
+
data.project ?? "unknown",
|
|
7562
|
+
data.summary ?? "",
|
|
7563
|
+
data.taskFile ?? null,
|
|
7564
|
+
null,
|
|
7565
|
+
data.read ? 1 : 0,
|
|
7566
|
+
data.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
7567
|
+
]
|
|
7568
|
+
});
|
|
7569
|
+
unlinkSync7(filePath);
|
|
7570
|
+
migrated++;
|
|
7571
|
+
} catch {
|
|
7572
|
+
}
|
|
7573
|
+
}
|
|
7574
|
+
try {
|
|
7575
|
+
const remaining = readdirSync4(notifDir);
|
|
7576
|
+
if (remaining.length === 0) {
|
|
7577
|
+
rmdirSync(notifDir);
|
|
7578
|
+
}
|
|
7579
|
+
} catch {
|
|
7580
|
+
}
|
|
7581
|
+
} catch {
|
|
7582
|
+
}
|
|
7583
|
+
return migrated;
|
|
7584
|
+
}
|
|
7585
|
+
function eventIcon(event) {
|
|
7586
|
+
switch (event) {
|
|
7587
|
+
case "task_complete":
|
|
7588
|
+
return "Completed:";
|
|
7589
|
+
case "task_needs_fix":
|
|
7590
|
+
return "Needs fix:";
|
|
7591
|
+
case "session_summary":
|
|
7592
|
+
return "Session:";
|
|
7593
|
+
case "error_spike":
|
|
7594
|
+
return "Errors:";
|
|
7595
|
+
case "orphan_task":
|
|
7596
|
+
return "Orphan:";
|
|
7597
|
+
case "subtasks_complete":
|
|
7598
|
+
return "Subtasks done:";
|
|
7599
|
+
case "capacity_relaunch":
|
|
7600
|
+
return "Relaunched:";
|
|
7601
|
+
}
|
|
7602
|
+
}
|
|
7603
|
+
function formatTimeAgo(timestamp) {
|
|
7604
|
+
const diffMs = Date.now() - new Date(timestamp).getTime();
|
|
7605
|
+
const mins = Math.floor(diffMs / 6e4);
|
|
7606
|
+
if (mins < 1) return "just now";
|
|
7607
|
+
if (mins < 60) return `${mins}m ago`;
|
|
7608
|
+
const hours = Math.floor(mins / 60);
|
|
7609
|
+
if (hours < 24) return `${hours}h ago`;
|
|
7610
|
+
const days = Math.floor(hours / 24);
|
|
7611
|
+
return `${days}d ago`;
|
|
7612
|
+
}
|
|
7613
|
+
var CLEANUP_DAYS;
|
|
7378
7614
|
var init_notifications = __esm({
|
|
7379
7615
|
"src/lib/notifications.ts"() {
|
|
7380
7616
|
"use strict";
|
|
7381
7617
|
init_database();
|
|
7382
7618
|
init_task_scope();
|
|
7619
|
+
CLEANUP_DAYS = 7;
|
|
7383
7620
|
}
|
|
7384
7621
|
});
|
|
7385
7622
|
|
|
@@ -7634,7 +7871,13 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
|
|
|
7634
7871
|
taskFile
|
|
7635
7872
|
});
|
|
7636
7873
|
const originalPriority = String(row.priority).toLowerCase();
|
|
7637
|
-
const
|
|
7874
|
+
const resultLower = result?.toLowerCase() ?? "";
|
|
7875
|
+
const hasTestEvidence = (
|
|
7876
|
+
// Vitest/Jest output patterns (hard to fake without actually running tests)
|
|
7877
|
+
/\d+\s+pass(ed|ing)/.test(resultLower) || /test files?\s+\d+\s+passed/.test(resultLower) || /tests?\s+\d+\s+passed/.test(resultLower)
|
|
7878
|
+
);
|
|
7879
|
+
const hasNoFailures = !/fail(ed|ure|ing)|error/i.test(resultLower);
|
|
7880
|
+
const autoApprove = originalPriority === "p2" && hasTestEvidence && hasNoFailures;
|
|
7638
7881
|
if (!autoApprove) {
|
|
7639
7882
|
try {
|
|
7640
7883
|
const key = getSessionKey();
|
|
@@ -7642,6 +7885,13 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
|
|
|
7642
7885
|
if (exeSession) {
|
|
7643
7886
|
sendIntercom(exeSession);
|
|
7644
7887
|
}
|
|
7888
|
+
if (reviewer && reviewer !== coordinatorName && reviewer !== exeSession) {
|
|
7889
|
+
const { employeeSessionName: employeeSessionName2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
7890
|
+
if (exeSession) {
|
|
7891
|
+
const reviewerSession = employeeSessionName2(reviewer, exeSession);
|
|
7892
|
+
sendIntercom(reviewerSession);
|
|
7893
|
+
}
|
|
7894
|
+
}
|
|
7645
7895
|
} catch {
|
|
7646
7896
|
}
|
|
7647
7897
|
}
|
|
@@ -8530,6 +8780,20 @@ async function updateTask(input2) {
|
|
|
8530
8780
|
notifyTaskDone();
|
|
8531
8781
|
}
|
|
8532
8782
|
await markTaskNotificationsRead(taskFile);
|
|
8783
|
+
if (input2.status === "needs_review" && !isCoordinator) {
|
|
8784
|
+
try {
|
|
8785
|
+
const { writeNotification: writeNotification2 } = await Promise.resolve().then(() => (init_notifications(), notifications_exports));
|
|
8786
|
+
await writeNotification2({
|
|
8787
|
+
agentId: String(row.assigned_to),
|
|
8788
|
+
agentRole: String(row.assigned_to),
|
|
8789
|
+
event: "task_complete",
|
|
8790
|
+
project: String(row.project_name),
|
|
8791
|
+
summary: `"${String(row.title)}" is ready for review`,
|
|
8792
|
+
taskFile
|
|
8793
|
+
});
|
|
8794
|
+
} catch {
|
|
8795
|
+
}
|
|
8796
|
+
}
|
|
8533
8797
|
if (input2.status === "done" || input2.status === "closed") {
|
|
8534
8798
|
try {
|
|
8535
8799
|
await cascadeUnblock(taskId, input2.baseDir, now);
|
|
@@ -8962,18 +9226,31 @@ function acquireSpawnLock2(sessionName) {
|
|
|
8962
9226
|
mkdirSync12(SPAWN_LOCK_DIR, { recursive: true });
|
|
8963
9227
|
}
|
|
8964
9228
|
const lockFile = spawnLockPath(sessionName);
|
|
8965
|
-
|
|
8966
|
-
|
|
8967
|
-
|
|
8968
|
-
|
|
8969
|
-
|
|
8970
|
-
|
|
8971
|
-
|
|
8972
|
-
|
|
9229
|
+
const lockData = JSON.stringify({ pid: process.pid, timestamp: Date.now() });
|
|
9230
|
+
const { openSync: openSync4, closeSync: closeSync4, writeSync } = __require("fs");
|
|
9231
|
+
const { constants } = __require("fs");
|
|
9232
|
+
try {
|
|
9233
|
+
const fd = openSync4(lockFile, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 420);
|
|
9234
|
+
writeSync(fd, lockData);
|
|
9235
|
+
closeSync4(fd);
|
|
9236
|
+
return true;
|
|
9237
|
+
} catch (err) {
|
|
9238
|
+
if (err?.code !== "EEXIST") {
|
|
9239
|
+
return true;
|
|
8973
9240
|
}
|
|
8974
9241
|
}
|
|
8975
|
-
|
|
8976
|
-
|
|
9242
|
+
try {
|
|
9243
|
+
const lock = JSON.parse(readFileSync13(lockFile, "utf8"));
|
|
9244
|
+
const age = Date.now() - lock.timestamp;
|
|
9245
|
+
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
9246
|
+
return false;
|
|
9247
|
+
}
|
|
9248
|
+
writeFileSync11(lockFile, lockData);
|
|
9249
|
+
return true;
|
|
9250
|
+
} catch {
|
|
9251
|
+
writeFileSync11(lockFile, lockData);
|
|
9252
|
+
return true;
|
|
9253
|
+
}
|
|
8977
9254
|
}
|
|
8978
9255
|
function releaseSpawnLock2(sessionName) {
|
|
8979
9256
|
try {
|
|
@@ -9052,6 +9329,21 @@ function parseParentExe(sessionName, agentId) {
|
|
|
9052
9329
|
function extractRootExe(name) {
|
|
9053
9330
|
if (!name) return null;
|
|
9054
9331
|
if (!name.includes("-")) return name;
|
|
9332
|
+
try {
|
|
9333
|
+
const roster = (init_employees(), __toCommonJS(employees_exports)).loadEmployeesSync();
|
|
9334
|
+
if (roster.length > 0) {
|
|
9335
|
+
const sortedNames = roster.map((e) => e.name).sort((a, b) => b.length - a.length);
|
|
9336
|
+
for (const agentName of sortedNames) {
|
|
9337
|
+
const escaped = agentName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
9338
|
+
const regex = new RegExp(`^${escaped}\\d*-(.+)$`);
|
|
9339
|
+
const match = name.match(regex);
|
|
9340
|
+
if (match) {
|
|
9341
|
+
return extractRootExe(match[1]);
|
|
9342
|
+
}
|
|
9343
|
+
}
|
|
9344
|
+
}
|
|
9345
|
+
} catch {
|
|
9346
|
+
}
|
|
9055
9347
|
const parts = name.split("-").filter(Boolean);
|
|
9056
9348
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
9057
9349
|
}
|
|
@@ -9070,6 +9362,10 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
9070
9362
|
function getParentExe(sessionKey) {
|
|
9071
9363
|
try {
|
|
9072
9364
|
const data = JSON.parse(readFileSync13(path23.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
9365
|
+
if (data.registeredAt) {
|
|
9366
|
+
const age = Date.now() - new Date(data.registeredAt).getTime();
|
|
9367
|
+
if (age > PARENT_EXE_CACHE_TTL_MS) return null;
|
|
9368
|
+
}
|
|
9073
9369
|
return data.parentExe || null;
|
|
9074
9370
|
} catch {
|
|
9075
9371
|
return null;
|
|
@@ -9618,7 +9914,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
9618
9914
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
9619
9915
|
} catch {
|
|
9620
9916
|
}
|
|
9621
|
-
let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName}`;
|
|
9917
|
+
let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName} EXE_SESSION_START_ISO=${(/* @__PURE__ */ new Date()).toISOString()}`;
|
|
9622
9918
|
if (ccProvider !== DEFAULT_PROVIDER) {
|
|
9623
9919
|
const cfg = PROVIDER_TABLE[ccProvider];
|
|
9624
9920
|
if (cfg?.apiKeyEnv) {
|
|
@@ -9653,10 +9949,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
9653
9949
|
}
|
|
9654
9950
|
if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
|
|
9655
9951
|
if (agentRtConfig.runtime === "claude" && agentRtConfig.model) {
|
|
9656
|
-
|
|
9657
|
-
|
|
9658
|
-
ccModel += "[1m]";
|
|
9659
|
-
}
|
|
9952
|
+
const { normalizeCcModelName: normalizeCcModelName2 } = (init_agent_config(), __toCommonJS(agent_config_exports));
|
|
9953
|
+
const ccModel = normalizeCcModelName2(agentRtConfig.model);
|
|
9660
9954
|
envPrefix = `${envPrefix} ANTHROPIC_MODEL=${ccModel}`;
|
|
9661
9955
|
}
|
|
9662
9956
|
}
|
|
@@ -9757,7 +10051,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
9757
10051
|
releaseSpawnLock2(sessionName);
|
|
9758
10052
|
return { sessionName };
|
|
9759
10053
|
}
|
|
9760
|
-
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;
|
|
10054
|
+
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;
|
|
9761
10055
|
var init_tmux_routing = __esm({
|
|
9762
10056
|
"src/lib/tmux-routing.ts"() {
|
|
9763
10057
|
"use strict";
|
|
@@ -9777,6 +10071,7 @@ var init_tmux_routing = __esm({
|
|
|
9777
10071
|
SESSION_CACHE = path23.join(os12.homedir(), ".exe-os", "session-cache");
|
|
9778
10072
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
9779
10073
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
10074
|
+
PARENT_EXE_CACHE_TTL_MS = 4 * 60 * 60 * 1e3;
|
|
9780
10075
|
VERIFY_PANE_LINES = 200;
|
|
9781
10076
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
9782
10077
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
@@ -10962,7 +11257,21 @@ function tryAcquireWorkerSlot() {
|
|
|
10962
11257
|
for (const f of files) {
|
|
10963
11258
|
if (!f.endsWith(".pid")) continue;
|
|
10964
11259
|
if (f.startsWith("res-")) {
|
|
10965
|
-
|
|
11260
|
+
const resParts = f.replace(".pid", "").split("-");
|
|
11261
|
+
const resPid = parseInt(resParts[1] ?? "", 10);
|
|
11262
|
+
if (!isNaN(resPid) && resPid > 0) {
|
|
11263
|
+
try {
|
|
11264
|
+
process.kill(resPid, 0);
|
|
11265
|
+
alive++;
|
|
11266
|
+
} catch {
|
|
11267
|
+
try {
|
|
11268
|
+
unlinkSync4(path6.join(WORKER_PID_DIR, f));
|
|
11269
|
+
} catch {
|
|
11270
|
+
}
|
|
11271
|
+
}
|
|
11272
|
+
} else {
|
|
11273
|
+
alive++;
|
|
11274
|
+
}
|
|
10966
11275
|
continue;
|
|
10967
11276
|
}
|
|
10968
11277
|
const dashIdx = f.lastIndexOf("-");
|