@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
|
@@ -209,6 +209,17 @@ function normalizeOrchestration(raw) {
|
|
|
209
209
|
const userOrg = raw.orchestration ?? {};
|
|
210
210
|
raw.orchestration = { ...defaultOrg, ...userOrg };
|
|
211
211
|
}
|
|
212
|
+
function normalizeCloudEndpoint(raw) {
|
|
213
|
+
const cloud = raw.cloud;
|
|
214
|
+
if (!cloud?.endpoint) return;
|
|
215
|
+
const ep = String(cloud.endpoint);
|
|
216
|
+
if (ep === "https://askexe.com/cloud" || ep === "https://askexe.com/cloud/") {
|
|
217
|
+
cloud.endpoint = "https://cloud.askexe.com";
|
|
218
|
+
process.stderr.write(
|
|
219
|
+
"[config] Auto-migrated cloud endpoint: askexe.com/cloud \u2192 cloud.askexe.com\n"
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
212
223
|
async function loadConfig() {
|
|
213
224
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
214
225
|
await ensurePrivateDir(dir);
|
|
@@ -234,6 +245,7 @@ async function loadConfig() {
|
|
|
234
245
|
normalizeSessionLifecycle(migratedCfg);
|
|
235
246
|
normalizeAutoUpdate(migratedCfg);
|
|
236
247
|
normalizeOrchestration(migratedCfg);
|
|
248
|
+
normalizeCloudEndpoint(migratedCfg);
|
|
237
249
|
const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
|
|
238
250
|
if (config.dbPath.startsWith("~")) {
|
|
239
251
|
config.dbPath = config.dbPath.replace(/^~/, os.homedir());
|
|
@@ -262,6 +274,7 @@ function loadConfigSync() {
|
|
|
262
274
|
normalizeSessionLifecycle(migratedCfg);
|
|
263
275
|
normalizeAutoUpdate(migratedCfg);
|
|
264
276
|
normalizeOrchestration(migratedCfg);
|
|
277
|
+
normalizeCloudEndpoint(migratedCfg);
|
|
265
278
|
const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
|
|
266
279
|
if (config.dbPath.startsWith("~")) {
|
|
267
280
|
config.dbPath = config.dbPath.replace(/^~/, os.homedir());
|
|
@@ -422,6 +435,7 @@ __export(agent_config_exports, {
|
|
|
422
435
|
clearAgentRuntime: () => clearAgentRuntime,
|
|
423
436
|
getAgentRuntime: () => getAgentRuntime,
|
|
424
437
|
loadAgentConfig: () => loadAgentConfig,
|
|
438
|
+
normalizeCcModelName: () => normalizeCcModelName,
|
|
425
439
|
saveAgentConfig: () => saveAgentConfig,
|
|
426
440
|
setAgentMcps: () => setAgentMcps,
|
|
427
441
|
setAgentRuntime: () => setAgentRuntime
|
|
@@ -450,6 +464,13 @@ function getAgentRuntime(agentId) {
|
|
|
450
464
|
if (orgDefault) return orgDefault;
|
|
451
465
|
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
452
466
|
}
|
|
467
|
+
function normalizeCcModelName(model) {
|
|
468
|
+
let ccModel = model.replace(/(\d+)\.(\d+)/g, "$1-$2");
|
|
469
|
+
if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
|
|
470
|
+
ccModel += "[1m]";
|
|
471
|
+
}
|
|
472
|
+
return ccModel;
|
|
473
|
+
}
|
|
453
474
|
function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
|
|
454
475
|
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
455
476
|
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);
|
|
@@ -6535,6 +6568,7 @@ var init_provider_table = __esm({
|
|
|
6535
6568
|
// src/lib/intercom-queue.ts
|
|
6536
6569
|
var intercom_queue_exports = {};
|
|
6537
6570
|
__export(intercom_queue_exports, {
|
|
6571
|
+
_resetDrainGuard: () => _resetDrainGuard,
|
|
6538
6572
|
clearQueueForAgent: () => clearQueueForAgent,
|
|
6539
6573
|
drainForSession: () => drainForSession,
|
|
6540
6574
|
drainQueue: () => drainQueue,
|
|
@@ -6580,38 +6614,47 @@ function queueIntercom(targetSession, reason) {
|
|
|
6580
6614
|
writeQueue(queue);
|
|
6581
6615
|
}
|
|
6582
6616
|
function drainQueue(isSessionBusy2, sendKeys) {
|
|
6617
|
+
if (_draining) {
|
|
6618
|
+
logQueue("SKIP_DRAIN \u2014 previous drain still running (possible tmux hang)");
|
|
6619
|
+
return { drained: 0, failed: 0 };
|
|
6620
|
+
}
|
|
6583
6621
|
const queue = readQueue();
|
|
6584
6622
|
if (queue.length === 0) return { drained: 0, failed: 0 };
|
|
6623
|
+
_draining = true;
|
|
6585
6624
|
const remaining = [];
|
|
6586
6625
|
let drained = 0;
|
|
6587
6626
|
let failed = 0;
|
|
6588
|
-
|
|
6589
|
-
const
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
|
|
6593
|
-
|
|
6594
|
-
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
|
|
6599
|
-
|
|
6600
|
-
|
|
6601
|
-
|
|
6627
|
+
try {
|
|
6628
|
+
for (const item of queue) {
|
|
6629
|
+
const age = Date.now() - new Date(item.queuedAt).getTime();
|
|
6630
|
+
if (age > TTL_MS) {
|
|
6631
|
+
logQueue(`EXPIRED \u2192 ${item.targetSession} (${Math.round(age / 6e4)}min old, reason: ${item.reason})`);
|
|
6632
|
+
failed++;
|
|
6633
|
+
continue;
|
|
6634
|
+
}
|
|
6635
|
+
try {
|
|
6636
|
+
if (!isSessionBusy2(item.targetSession)) {
|
|
6637
|
+
const success = sendKeys(item.targetSession);
|
|
6638
|
+
if (success) {
|
|
6639
|
+
logQueue(`DRAINED \u2192 ${item.targetSession} (after ${item.attempts} retries)`);
|
|
6640
|
+
drained++;
|
|
6641
|
+
continue;
|
|
6642
|
+
}
|
|
6602
6643
|
}
|
|
6644
|
+
} catch {
|
|
6603
6645
|
}
|
|
6604
|
-
|
|
6605
|
-
|
|
6606
|
-
|
|
6607
|
-
|
|
6608
|
-
|
|
6609
|
-
|
|
6610
|
-
|
|
6646
|
+
item.attempts++;
|
|
6647
|
+
if (item.attempts >= MAX_RETRIES2) {
|
|
6648
|
+
logQueue(`FAILED \u2192 ${item.targetSession} (${MAX_RETRIES2} retries exhausted, reason: ${item.reason})`);
|
|
6649
|
+
failed++;
|
|
6650
|
+
continue;
|
|
6651
|
+
}
|
|
6652
|
+
remaining.push(item);
|
|
6611
6653
|
}
|
|
6612
|
-
remaining
|
|
6654
|
+
writeQueue(remaining);
|
|
6655
|
+
} finally {
|
|
6656
|
+
_draining = false;
|
|
6613
6657
|
}
|
|
6614
|
-
writeQueue(remaining);
|
|
6615
6658
|
return { drained, failed };
|
|
6616
6659
|
}
|
|
6617
6660
|
function drainForSession(targetSession, sendKeys) {
|
|
@@ -6636,6 +6679,9 @@ function clearQueueForAgent(agentName) {
|
|
|
6636
6679
|
logQueue(`CLEARED ${before - filtered.length} stale item(s) for ${agentName}`);
|
|
6637
6680
|
}
|
|
6638
6681
|
}
|
|
6682
|
+
function _resetDrainGuard() {
|
|
6683
|
+
_draining = false;
|
|
6684
|
+
}
|
|
6639
6685
|
function logQueue(msg) {
|
|
6640
6686
|
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] [queue] ${msg}
|
|
6641
6687
|
`;
|
|
@@ -6647,13 +6693,14 @@ function logQueue(msg) {
|
|
|
6647
6693
|
} catch {
|
|
6648
6694
|
}
|
|
6649
6695
|
}
|
|
6650
|
-
var QUEUE_PATH, MAX_RETRIES2, TTL_MS, INTERCOM_LOG;
|
|
6696
|
+
var QUEUE_PATH, MAX_RETRIES2, TTL_MS, _draining, INTERCOM_LOG;
|
|
6651
6697
|
var init_intercom_queue = __esm({
|
|
6652
6698
|
"src/lib/intercom-queue.ts"() {
|
|
6653
6699
|
"use strict";
|
|
6654
6700
|
QUEUE_PATH = path10.join(os7.homedir(), ".exe-os", "intercom-queue.json");
|
|
6655
6701
|
MAX_RETRIES2 = 5;
|
|
6656
6702
|
TTL_MS = 60 * 60 * 1e3;
|
|
6703
|
+
_draining = false;
|
|
6657
6704
|
INTERCOM_LOG = path10.join(os7.homedir(), ".exe-os", "intercom.log");
|
|
6658
6705
|
}
|
|
6659
6706
|
});
|
|
@@ -7803,7 +7850,13 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
|
|
|
7803
7850
|
taskFile
|
|
7804
7851
|
});
|
|
7805
7852
|
const originalPriority = String(row.priority).toLowerCase();
|
|
7806
|
-
const
|
|
7853
|
+
const resultLower = result?.toLowerCase() ?? "";
|
|
7854
|
+
const hasTestEvidence = (
|
|
7855
|
+
// Vitest/Jest output patterns (hard to fake without actually running tests)
|
|
7856
|
+
/\d+\s+pass(ed|ing)/.test(resultLower) || /test files?\s+\d+\s+passed/.test(resultLower) || /tests?\s+\d+\s+passed/.test(resultLower)
|
|
7857
|
+
);
|
|
7858
|
+
const hasNoFailures = !/fail(ed|ure|ing)|error/i.test(resultLower);
|
|
7859
|
+
const autoApprove = originalPriority === "p2" && hasTestEvidence && hasNoFailures;
|
|
7807
7860
|
if (!autoApprove) {
|
|
7808
7861
|
try {
|
|
7809
7862
|
const key = getSessionKey();
|
|
@@ -7811,6 +7864,13 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
|
|
|
7811
7864
|
if (exeSession) {
|
|
7812
7865
|
sendIntercom(exeSession);
|
|
7813
7866
|
}
|
|
7867
|
+
if (reviewer && reviewer !== coordinatorName && reviewer !== exeSession) {
|
|
7868
|
+
const { employeeSessionName: employeeSessionName2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
7869
|
+
if (exeSession) {
|
|
7870
|
+
const reviewerSession = employeeSessionName2(reviewer, exeSession);
|
|
7871
|
+
sendIntercom(reviewerSession);
|
|
7872
|
+
}
|
|
7873
|
+
}
|
|
7814
7874
|
} catch {
|
|
7815
7875
|
}
|
|
7816
7876
|
}
|
|
@@ -7941,18 +8001,31 @@ function acquireSpawnLock2(sessionName) {
|
|
|
7941
8001
|
mkdirSync8(SPAWN_LOCK_DIR, { recursive: true });
|
|
7942
8002
|
}
|
|
7943
8003
|
const lockFile = spawnLockPath(sessionName);
|
|
7944
|
-
|
|
7945
|
-
|
|
7946
|
-
|
|
7947
|
-
|
|
7948
|
-
|
|
7949
|
-
|
|
7950
|
-
|
|
7951
|
-
|
|
8004
|
+
const lockData = JSON.stringify({ pid: process.pid, timestamp: Date.now() });
|
|
8005
|
+
const { openSync: openSync3, closeSync: closeSync3, writeSync } = __require("fs");
|
|
8006
|
+
const { constants } = __require("fs");
|
|
8007
|
+
try {
|
|
8008
|
+
const fd = openSync3(lockFile, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 420);
|
|
8009
|
+
writeSync(fd, lockData);
|
|
8010
|
+
closeSync3(fd);
|
|
8011
|
+
return true;
|
|
8012
|
+
} catch (err) {
|
|
8013
|
+
if (err?.code !== "EEXIST") {
|
|
8014
|
+
return true;
|
|
7952
8015
|
}
|
|
7953
8016
|
}
|
|
7954
|
-
|
|
7955
|
-
|
|
8017
|
+
try {
|
|
8018
|
+
const lock = JSON.parse(readFileSync10(lockFile, "utf8"));
|
|
8019
|
+
const age = Date.now() - lock.timestamp;
|
|
8020
|
+
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
8021
|
+
return false;
|
|
8022
|
+
}
|
|
8023
|
+
writeFileSync7(lockFile, lockData);
|
|
8024
|
+
return true;
|
|
8025
|
+
} catch {
|
|
8026
|
+
writeFileSync7(lockFile, lockData);
|
|
8027
|
+
return true;
|
|
8028
|
+
}
|
|
7956
8029
|
}
|
|
7957
8030
|
function releaseSpawnLock2(sessionName) {
|
|
7958
8031
|
try {
|
|
@@ -8031,6 +8104,21 @@ function parseParentExe(sessionName, agentId) {
|
|
|
8031
8104
|
function extractRootExe(name) {
|
|
8032
8105
|
if (!name) return null;
|
|
8033
8106
|
if (!name.includes("-")) return name;
|
|
8107
|
+
try {
|
|
8108
|
+
const roster = (init_employees(), __toCommonJS(employees_exports)).loadEmployeesSync();
|
|
8109
|
+
if (roster.length > 0) {
|
|
8110
|
+
const sortedNames = roster.map((e) => e.name).sort((a, b) => b.length - a.length);
|
|
8111
|
+
for (const agentName of sortedNames) {
|
|
8112
|
+
const escaped = agentName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8113
|
+
const regex = new RegExp(`^${escaped}\\d*-(.+)$`);
|
|
8114
|
+
const match = name.match(regex);
|
|
8115
|
+
if (match) {
|
|
8116
|
+
return extractRootExe(match[1]);
|
|
8117
|
+
}
|
|
8118
|
+
}
|
|
8119
|
+
}
|
|
8120
|
+
} catch {
|
|
8121
|
+
}
|
|
8034
8122
|
const parts = name.split("-").filter(Boolean);
|
|
8035
8123
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
8036
8124
|
}
|
|
@@ -8049,6 +8137,10 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
8049
8137
|
function getParentExe(sessionKey) {
|
|
8050
8138
|
try {
|
|
8051
8139
|
const data = JSON.parse(readFileSync10(path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
8140
|
+
if (data.registeredAt) {
|
|
8141
|
+
const age = Date.now() - new Date(data.registeredAt).getTime();
|
|
8142
|
+
if (age > PARENT_EXE_CACHE_TTL_MS) return null;
|
|
8143
|
+
}
|
|
8052
8144
|
return data.parentExe || null;
|
|
8053
8145
|
} catch {
|
|
8054
8146
|
return null;
|
|
@@ -8597,7 +8689,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
8597
8689
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
8598
8690
|
} catch {
|
|
8599
8691
|
}
|
|
8600
|
-
let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName}`;
|
|
8692
|
+
let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName} EXE_SESSION_START_ISO=${(/* @__PURE__ */ new Date()).toISOString()}`;
|
|
8601
8693
|
if (ccProvider !== DEFAULT_PROVIDER) {
|
|
8602
8694
|
const cfg = PROVIDER_TABLE[ccProvider];
|
|
8603
8695
|
if (cfg?.apiKeyEnv) {
|
|
@@ -8632,10 +8724,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
8632
8724
|
}
|
|
8633
8725
|
if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
|
|
8634
8726
|
if (agentRtConfig.runtime === "claude" && agentRtConfig.model) {
|
|
8635
|
-
|
|
8636
|
-
|
|
8637
|
-
ccModel += "[1m]";
|
|
8638
|
-
}
|
|
8727
|
+
const { normalizeCcModelName: normalizeCcModelName2 } = (init_agent_config(), __toCommonJS(agent_config_exports));
|
|
8728
|
+
const ccModel = normalizeCcModelName2(agentRtConfig.model);
|
|
8639
8729
|
envPrefix = `${envPrefix} ANTHROPIC_MODEL=${ccModel}`;
|
|
8640
8730
|
}
|
|
8641
8731
|
}
|
|
@@ -8736,7 +8826,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
8736
8826
|
releaseSpawnLock2(sessionName);
|
|
8737
8827
|
return { sessionName };
|
|
8738
8828
|
}
|
|
8739
|
-
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;
|
|
8829
|
+
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;
|
|
8740
8830
|
var init_tmux_routing = __esm({
|
|
8741
8831
|
"src/lib/tmux-routing.ts"() {
|
|
8742
8832
|
"use strict";
|
|
@@ -8756,6 +8846,7 @@ var init_tmux_routing = __esm({
|
|
|
8756
8846
|
SESSION_CACHE = path15.join(os10.homedir(), ".exe-os", "session-cache");
|
|
8757
8847
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
8758
8848
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
8849
|
+
PARENT_EXE_CACHE_TTL_MS = 4 * 60 * 60 * 1e3;
|
|
8759
8850
|
VERIFY_PANE_LINES = 200;
|
|
8760
8851
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
8761
8852
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
@@ -8800,6 +8891,17 @@ var init_task_scope = __esm({
|
|
|
8800
8891
|
});
|
|
8801
8892
|
|
|
8802
8893
|
// src/lib/notifications.ts
|
|
8894
|
+
var notifications_exports = {};
|
|
8895
|
+
__export(notifications_exports, {
|
|
8896
|
+
cleanupOldNotifications: () => cleanupOldNotifications,
|
|
8897
|
+
formatNotifications: () => formatNotifications,
|
|
8898
|
+
markAsRead: () => markAsRead,
|
|
8899
|
+
markAsReadByTaskFile: () => markAsReadByTaskFile,
|
|
8900
|
+
markDoneTaskNotificationsAsRead: () => markDoneTaskNotificationsAsRead,
|
|
8901
|
+
migrateJsonNotifications: () => migrateJsonNotifications,
|
|
8902
|
+
readUnreadNotifications: () => readUnreadNotifications,
|
|
8903
|
+
writeNotification: () => writeNotification
|
|
8904
|
+
});
|
|
8803
8905
|
import crypto3 from "crypto";
|
|
8804
8906
|
import path16 from "path";
|
|
8805
8907
|
import os11 from "os";
|
|
@@ -8836,6 +8938,52 @@ async function writeNotification(notification) {
|
|
|
8836
8938
|
`);
|
|
8837
8939
|
}
|
|
8838
8940
|
}
|
|
8941
|
+
async function readUnreadNotifications(agentFilter, sessionScope) {
|
|
8942
|
+
try {
|
|
8943
|
+
const client = getClient();
|
|
8944
|
+
const conditions = ["read = 0"];
|
|
8945
|
+
const args = [];
|
|
8946
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
8947
|
+
if (agentFilter) {
|
|
8948
|
+
conditions.push("agent_id = ?");
|
|
8949
|
+
args.push(agentFilter);
|
|
8950
|
+
}
|
|
8951
|
+
const result = await client.execute({
|
|
8952
|
+
sql: `SELECT id, agent_id, agent_role, event, project, summary, task_file, session_scope, created_at
|
|
8953
|
+
FROM notifications
|
|
8954
|
+
WHERE ${conditions.join(" AND ")}${scope.sql}
|
|
8955
|
+
ORDER BY created_at ASC`,
|
|
8956
|
+
args: [...args, ...scope.args]
|
|
8957
|
+
});
|
|
8958
|
+
return result.rows.map((r) => ({
|
|
8959
|
+
id: String(r.id),
|
|
8960
|
+
agentId: String(r.agent_id),
|
|
8961
|
+
agentRole: String(r.agent_role),
|
|
8962
|
+
event: String(r.event),
|
|
8963
|
+
project: String(r.project),
|
|
8964
|
+
summary: String(r.summary),
|
|
8965
|
+
taskFile: r.task_file ? String(r.task_file) : void 0,
|
|
8966
|
+
sessionScope: r.session_scope == null ? null : String(r.session_scope),
|
|
8967
|
+
timestamp: String(r.created_at),
|
|
8968
|
+
read: false
|
|
8969
|
+
}));
|
|
8970
|
+
} catch {
|
|
8971
|
+
return [];
|
|
8972
|
+
}
|
|
8973
|
+
}
|
|
8974
|
+
async function markAsRead(ids, sessionScope) {
|
|
8975
|
+
if (ids.length === 0) return;
|
|
8976
|
+
try {
|
|
8977
|
+
const client = getClient();
|
|
8978
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
8979
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
8980
|
+
await client.execute({
|
|
8981
|
+
sql: `UPDATE notifications SET read = 1 WHERE id IN (${placeholders})${scope.sql}`,
|
|
8982
|
+
args: [...ids, ...scope.args]
|
|
8983
|
+
});
|
|
8984
|
+
} catch {
|
|
8985
|
+
}
|
|
8986
|
+
}
|
|
8839
8987
|
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
8840
8988
|
try {
|
|
8841
8989
|
const client = getClient();
|
|
@@ -8848,11 +8996,144 @@ async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
|
8848
8996
|
} catch {
|
|
8849
8997
|
}
|
|
8850
8998
|
}
|
|
8999
|
+
async function cleanupOldNotifications(daysOld = CLEANUP_DAYS, sessionScope) {
|
|
9000
|
+
try {
|
|
9001
|
+
const client = getClient();
|
|
9002
|
+
const cutoff = new Date(
|
|
9003
|
+
Date.now() - daysOld * 24 * 60 * 60 * 1e3
|
|
9004
|
+
).toISOString();
|
|
9005
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
9006
|
+
const result = await client.execute({
|
|
9007
|
+
sql: `DELETE FROM notifications WHERE created_at < ?${scope.sql}`,
|
|
9008
|
+
args: [cutoff, ...scope.args]
|
|
9009
|
+
});
|
|
9010
|
+
return result.rowsAffected;
|
|
9011
|
+
} catch {
|
|
9012
|
+
return 0;
|
|
9013
|
+
}
|
|
9014
|
+
}
|
|
9015
|
+
async function markDoneTaskNotificationsAsRead(sessionScope) {
|
|
9016
|
+
try {
|
|
9017
|
+
const client = getClient();
|
|
9018
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
9019
|
+
const result = await client.execute({
|
|
9020
|
+
sql: `UPDATE notifications SET read = 1
|
|
9021
|
+
WHERE read = 0
|
|
9022
|
+
AND task_file IS NOT NULL
|
|
9023
|
+
${scope.sql}
|
|
9024
|
+
AND task_file IN (
|
|
9025
|
+
SELECT task_file FROM tasks WHERE status = 'done'${scope.sql}
|
|
9026
|
+
)`,
|
|
9027
|
+
args: [...scope.args, ...scope.args]
|
|
9028
|
+
});
|
|
9029
|
+
return result.rowsAffected;
|
|
9030
|
+
} catch {
|
|
9031
|
+
return 0;
|
|
9032
|
+
}
|
|
9033
|
+
}
|
|
9034
|
+
function formatNotifications(notifications) {
|
|
9035
|
+
if (notifications.length === 0) return "";
|
|
9036
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
9037
|
+
for (const n of notifications) {
|
|
9038
|
+
const key = `${n.agentId}|${n.agentRole}`;
|
|
9039
|
+
if (!grouped.has(key)) grouped.set(key, []);
|
|
9040
|
+
grouped.get(key).push(n);
|
|
9041
|
+
}
|
|
9042
|
+
const lines = [];
|
|
9043
|
+
lines.push(`## Notifications (${notifications.length} unread)
|
|
9044
|
+
`);
|
|
9045
|
+
for (const [key, items] of grouped) {
|
|
9046
|
+
const [agentId, agentRole] = key.split("|");
|
|
9047
|
+
lines.push(`**${agentId}** (${agentRole}):`);
|
|
9048
|
+
for (const item of items) {
|
|
9049
|
+
const ago = formatTimeAgo(item.timestamp);
|
|
9050
|
+
const icon = eventIcon(item.event);
|
|
9051
|
+
lines.push(`- ${icon} ${item.summary} (${item.project}) \u2014 ${ago}`);
|
|
9052
|
+
}
|
|
9053
|
+
lines.push("");
|
|
9054
|
+
}
|
|
9055
|
+
return lines.join("\n");
|
|
9056
|
+
}
|
|
9057
|
+
async function migrateJsonNotifications() {
|
|
9058
|
+
const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR || path16.join(os11.homedir(), ".exe-os");
|
|
9059
|
+
const notifDir = path16.join(base, "notifications");
|
|
9060
|
+
if (!existsSync17(notifDir)) return 0;
|
|
9061
|
+
let migrated = 0;
|
|
9062
|
+
try {
|
|
9063
|
+
const files = readdirSync4(notifDir).filter((f) => f.endsWith(".json"));
|
|
9064
|
+
if (files.length === 0) return 0;
|
|
9065
|
+
const client = getClient();
|
|
9066
|
+
for (const file of files) {
|
|
9067
|
+
try {
|
|
9068
|
+
const filePath = path16.join(notifDir, file);
|
|
9069
|
+
const data = JSON.parse(readFileSync11(filePath, "utf8"));
|
|
9070
|
+
await client.execute({
|
|
9071
|
+
sql: `INSERT OR IGNORE INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
9072
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
9073
|
+
args: [
|
|
9074
|
+
crypto3.randomUUID(),
|
|
9075
|
+
data.agentId ?? "unknown",
|
|
9076
|
+
data.agentRole ?? "unknown",
|
|
9077
|
+
data.event ?? "session_summary",
|
|
9078
|
+
data.project ?? "unknown",
|
|
9079
|
+
data.summary ?? "",
|
|
9080
|
+
data.taskFile ?? null,
|
|
9081
|
+
null,
|
|
9082
|
+
data.read ? 1 : 0,
|
|
9083
|
+
data.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
9084
|
+
]
|
|
9085
|
+
});
|
|
9086
|
+
unlinkSync6(filePath);
|
|
9087
|
+
migrated++;
|
|
9088
|
+
} catch {
|
|
9089
|
+
}
|
|
9090
|
+
}
|
|
9091
|
+
try {
|
|
9092
|
+
const remaining = readdirSync4(notifDir);
|
|
9093
|
+
if (remaining.length === 0) {
|
|
9094
|
+
rmdirSync(notifDir);
|
|
9095
|
+
}
|
|
9096
|
+
} catch {
|
|
9097
|
+
}
|
|
9098
|
+
} catch {
|
|
9099
|
+
}
|
|
9100
|
+
return migrated;
|
|
9101
|
+
}
|
|
9102
|
+
function eventIcon(event) {
|
|
9103
|
+
switch (event) {
|
|
9104
|
+
case "task_complete":
|
|
9105
|
+
return "Completed:";
|
|
9106
|
+
case "task_needs_fix":
|
|
9107
|
+
return "Needs fix:";
|
|
9108
|
+
case "session_summary":
|
|
9109
|
+
return "Session:";
|
|
9110
|
+
case "error_spike":
|
|
9111
|
+
return "Errors:";
|
|
9112
|
+
case "orphan_task":
|
|
9113
|
+
return "Orphan:";
|
|
9114
|
+
case "subtasks_complete":
|
|
9115
|
+
return "Subtasks done:";
|
|
9116
|
+
case "capacity_relaunch":
|
|
9117
|
+
return "Relaunched:";
|
|
9118
|
+
}
|
|
9119
|
+
}
|
|
9120
|
+
function formatTimeAgo(timestamp) {
|
|
9121
|
+
const diffMs = Date.now() - new Date(timestamp).getTime();
|
|
9122
|
+
const mins = Math.floor(diffMs / 6e4);
|
|
9123
|
+
if (mins < 1) return "just now";
|
|
9124
|
+
if (mins < 60) return `${mins}m ago`;
|
|
9125
|
+
const hours = Math.floor(mins / 60);
|
|
9126
|
+
if (hours < 24) return `${hours}h ago`;
|
|
9127
|
+
const days = Math.floor(hours / 24);
|
|
9128
|
+
return `${days}d ago`;
|
|
9129
|
+
}
|
|
9130
|
+
var CLEANUP_DAYS;
|
|
8851
9131
|
var init_notifications = __esm({
|
|
8852
9132
|
"src/lib/notifications.ts"() {
|
|
8853
9133
|
"use strict";
|
|
8854
9134
|
init_database();
|
|
8855
9135
|
init_task_scope();
|
|
9136
|
+
CLEANUP_DAYS = 7;
|
|
8856
9137
|
}
|
|
8857
9138
|
});
|
|
8858
9139
|
|
|
@@ -10284,6 +10565,20 @@ async function updateTask(input) {
|
|
|
10284
10565
|
notifyTaskDone();
|
|
10285
10566
|
}
|
|
10286
10567
|
await markTaskNotificationsRead(taskFile);
|
|
10568
|
+
if (input.status === "needs_review" && !isCoordinator) {
|
|
10569
|
+
try {
|
|
10570
|
+
const { writeNotification: writeNotification2 } = await Promise.resolve().then(() => (init_notifications(), notifications_exports));
|
|
10571
|
+
await writeNotification2({
|
|
10572
|
+
agentId: String(row.assigned_to),
|
|
10573
|
+
agentRole: String(row.assigned_to),
|
|
10574
|
+
event: "task_complete",
|
|
10575
|
+
project: String(row.project_name),
|
|
10576
|
+
summary: `"${String(row.title)}" is ready for review`,
|
|
10577
|
+
taskFile
|
|
10578
|
+
});
|
|
10579
|
+
} catch {
|
|
10580
|
+
}
|
|
10581
|
+
}
|
|
10287
10582
|
if (input.status === "done" || input.status === "closed") {
|
|
10288
10583
|
try {
|
|
10289
10584
|
await cascadeUnblock(taskId, input.baseDir, now);
|