@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/lib/exe-daemon.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 config2 = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
|
|
183
195
|
if (config2.dbPath.startsWith("~")) {
|
|
184
196
|
config2.dbPath = config2.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 config2 = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
|
|
211
224
|
if (config2.dbPath.startsWith("~")) {
|
|
212
225
|
config2.dbPath = config2.dbPath.replace(/^~/, os.homedir());
|
|
@@ -567,6 +580,7 @@ __export(agent_config_exports, {
|
|
|
567
580
|
clearAgentRuntime: () => clearAgentRuntime,
|
|
568
581
|
getAgentRuntime: () => getAgentRuntime,
|
|
569
582
|
loadAgentConfig: () => loadAgentConfig,
|
|
583
|
+
normalizeCcModelName: () => normalizeCcModelName,
|
|
570
584
|
saveAgentConfig: () => saveAgentConfig,
|
|
571
585
|
setAgentMcps: () => setAgentMcps,
|
|
572
586
|
setAgentRuntime: () => setAgentRuntime
|
|
@@ -595,6 +609,13 @@ function getAgentRuntime(agentId) {
|
|
|
595
609
|
if (orgDefault) return orgDefault;
|
|
596
610
|
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
597
611
|
}
|
|
612
|
+
function normalizeCcModelName(model) {
|
|
613
|
+
let ccModel = model.replace(/(\d+)\.(\d+)/g, "$1-$2");
|
|
614
|
+
if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
|
|
615
|
+
ccModel += "[1m]";
|
|
616
|
+
}
|
|
617
|
+
return ccModel;
|
|
618
|
+
}
|
|
598
619
|
function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
|
|
599
620
|
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
600
621
|
if (!knownModels) {
|
|
@@ -5510,7 +5531,7 @@ __export(keychain_exports, {
|
|
|
5510
5531
|
importMnemonic: () => importMnemonic,
|
|
5511
5532
|
setMasterKey: () => setMasterKey
|
|
5512
5533
|
});
|
|
5513
|
-
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
5534
|
+
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2, rename, copyFile } from "fs/promises";
|
|
5514
5535
|
import { existsSync as existsSync12, statSync as statSync5 } from "fs";
|
|
5515
5536
|
import { execSync as execSync3 } from "child_process";
|
|
5516
5537
|
import path11 from "path";
|
|
@@ -5545,12 +5566,14 @@ function linuxSecretAvailable() {
|
|
|
5545
5566
|
function isRootOnlyTrustedServerKeyFile(keyPath) {
|
|
5546
5567
|
if (process.platform !== "linux") return false;
|
|
5547
5568
|
try {
|
|
5548
|
-
const uid = typeof os6.userInfo().uid === "number" ? os6.userInfo().uid : -1;
|
|
5549
5569
|
const st = statSync5(keyPath);
|
|
5550
5570
|
if (!st.isFile() || (st.mode & 63) !== 0) return false;
|
|
5571
|
+
const uid = typeof os6.userInfo().uid === "number" ? os6.userInfo().uid : -1;
|
|
5551
5572
|
if (uid === 0) return true;
|
|
5552
5573
|
const exeOsDir = process.env.EXE_OS_DIR;
|
|
5553
|
-
|
|
5574
|
+
if (exeOsDir && path11.resolve(keyPath).startsWith(path11.resolve(exeOsDir) + path11.sep)) return true;
|
|
5575
|
+
if (!linuxSecretAvailable()) return true;
|
|
5576
|
+
return false;
|
|
5554
5577
|
} catch {
|
|
5555
5578
|
return false;
|
|
5556
5579
|
}
|
|
@@ -5700,15 +5723,25 @@ async function writeMachineBoundFileFallback(b64) {
|
|
|
5700
5723
|
await mkdir3(dir, { recursive: true });
|
|
5701
5724
|
const keyPath = getKeyPath();
|
|
5702
5725
|
const machineKey = deriveMachineKey();
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
5726
|
+
const content = machineKey ? encryptWithMachineKey(b64, machineKey) + "\n" : b64 + "\n";
|
|
5727
|
+
const result3 = machineKey ? "encrypted" : "plaintext";
|
|
5728
|
+
const tmpPath = keyPath + ".tmp";
|
|
5729
|
+
try {
|
|
5730
|
+
if (existsSync12(keyPath)) {
|
|
5731
|
+
await copyFile(keyPath, keyPath + ".bak").catch(() => {
|
|
5732
|
+
});
|
|
5733
|
+
}
|
|
5734
|
+
await writeFile3(tmpPath, content, "utf-8");
|
|
5735
|
+
await chmod2(tmpPath, 384);
|
|
5736
|
+
await rename(tmpPath, keyPath);
|
|
5737
|
+
} catch (err) {
|
|
5738
|
+
try {
|
|
5739
|
+
await unlink(tmpPath);
|
|
5740
|
+
} catch {
|
|
5741
|
+
}
|
|
5742
|
+
throw err;
|
|
5708
5743
|
}
|
|
5709
|
-
|
|
5710
|
-
await chmod2(keyPath, 384);
|
|
5711
|
-
return "plaintext";
|
|
5744
|
+
return result3;
|
|
5712
5745
|
}
|
|
5713
5746
|
async function getMasterKey() {
|
|
5714
5747
|
let nativeValue = macKeychainGet() ?? linuxSecretGet();
|
|
@@ -5775,7 +5808,7 @@ async function getMasterKey() {
|
|
|
5775
5808
|
b64Value = content;
|
|
5776
5809
|
}
|
|
5777
5810
|
const key = Buffer.from(b64Value, "base64");
|
|
5778
|
-
if (
|
|
5811
|
+
if (isRootOnlyTrustedServerKeyFile(keyPath)) {
|
|
5779
5812
|
return key;
|
|
5780
5813
|
}
|
|
5781
5814
|
const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
|
|
@@ -12346,6 +12379,7 @@ var init_provider_table = __esm({
|
|
|
12346
12379
|
// src/lib/intercom-queue.ts
|
|
12347
12380
|
var intercom_queue_exports = {};
|
|
12348
12381
|
__export(intercom_queue_exports, {
|
|
12382
|
+
_resetDrainGuard: () => _resetDrainGuard,
|
|
12349
12383
|
clearQueueForAgent: () => clearQueueForAgent,
|
|
12350
12384
|
drainForSession: () => drainForSession,
|
|
12351
12385
|
drainQueue: () => drainQueue2,
|
|
@@ -12391,38 +12425,47 @@ function queueIntercom(targetSession, reason) {
|
|
|
12391
12425
|
writeQueue(queue);
|
|
12392
12426
|
}
|
|
12393
12427
|
function drainQueue2(isSessionBusy2, sendKeys) {
|
|
12428
|
+
if (_draining) {
|
|
12429
|
+
logQueue("SKIP_DRAIN \u2014 previous drain still running (possible tmux hang)");
|
|
12430
|
+
return { drained: 0, failed: 0 };
|
|
12431
|
+
}
|
|
12394
12432
|
const queue = readQueue();
|
|
12395
12433
|
if (queue.length === 0) return { drained: 0, failed: 0 };
|
|
12434
|
+
_draining = true;
|
|
12396
12435
|
const remaining = [];
|
|
12397
12436
|
let drained = 0;
|
|
12398
12437
|
let failed = 0;
|
|
12399
|
-
|
|
12400
|
-
const
|
|
12401
|
-
|
|
12402
|
-
|
|
12403
|
-
|
|
12404
|
-
|
|
12405
|
-
|
|
12406
|
-
|
|
12407
|
-
|
|
12408
|
-
|
|
12409
|
-
|
|
12410
|
-
|
|
12411
|
-
|
|
12412
|
-
|
|
12438
|
+
try {
|
|
12439
|
+
for (const item of queue) {
|
|
12440
|
+
const age = Date.now() - new Date(item.queuedAt).getTime();
|
|
12441
|
+
if (age > TTL_MS2) {
|
|
12442
|
+
logQueue(`EXPIRED \u2192 ${item.targetSession} (${Math.round(age / 6e4)}min old, reason: ${item.reason})`);
|
|
12443
|
+
failed++;
|
|
12444
|
+
continue;
|
|
12445
|
+
}
|
|
12446
|
+
try {
|
|
12447
|
+
if (!isSessionBusy2(item.targetSession)) {
|
|
12448
|
+
const success = sendKeys(item.targetSession);
|
|
12449
|
+
if (success) {
|
|
12450
|
+
logQueue(`DRAINED \u2192 ${item.targetSession} (after ${item.attempts} retries)`);
|
|
12451
|
+
drained++;
|
|
12452
|
+
continue;
|
|
12453
|
+
}
|
|
12413
12454
|
}
|
|
12455
|
+
} catch {
|
|
12414
12456
|
}
|
|
12415
|
-
|
|
12416
|
-
|
|
12417
|
-
|
|
12418
|
-
|
|
12419
|
-
|
|
12420
|
-
|
|
12421
|
-
|
|
12457
|
+
item.attempts++;
|
|
12458
|
+
if (item.attempts >= MAX_RETRIES2) {
|
|
12459
|
+
logQueue(`FAILED \u2192 ${item.targetSession} (${MAX_RETRIES2} retries exhausted, reason: ${item.reason})`);
|
|
12460
|
+
failed++;
|
|
12461
|
+
continue;
|
|
12462
|
+
}
|
|
12463
|
+
remaining.push(item);
|
|
12422
12464
|
}
|
|
12423
|
-
remaining
|
|
12465
|
+
writeQueue(remaining);
|
|
12466
|
+
} finally {
|
|
12467
|
+
_draining = false;
|
|
12424
12468
|
}
|
|
12425
|
-
writeQueue(remaining);
|
|
12426
12469
|
return { drained, failed };
|
|
12427
12470
|
}
|
|
12428
12471
|
function drainForSession(targetSession, sendKeys) {
|
|
@@ -12447,6 +12490,9 @@ function clearQueueForAgent(agentName) {
|
|
|
12447
12490
|
logQueue(`CLEARED ${before - filtered.length} stale item(s) for ${agentName}`);
|
|
12448
12491
|
}
|
|
12449
12492
|
}
|
|
12493
|
+
function _resetDrainGuard() {
|
|
12494
|
+
_draining = false;
|
|
12495
|
+
}
|
|
12450
12496
|
function logQueue(msg) {
|
|
12451
12497
|
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] [queue] ${msg}
|
|
12452
12498
|
`;
|
|
@@ -12458,13 +12504,14 @@ function logQueue(msg) {
|
|
|
12458
12504
|
} catch {
|
|
12459
12505
|
}
|
|
12460
12506
|
}
|
|
12461
|
-
var QUEUE_PATH2, MAX_RETRIES2, TTL_MS2, INTERCOM_LOG;
|
|
12507
|
+
var QUEUE_PATH2, MAX_RETRIES2, TTL_MS2, _draining, INTERCOM_LOG;
|
|
12462
12508
|
var init_intercom_queue = __esm({
|
|
12463
12509
|
"src/lib/intercom-queue.ts"() {
|
|
12464
12510
|
"use strict";
|
|
12465
12511
|
QUEUE_PATH2 = path22.join(os9.homedir(), ".exe-os", "intercom-queue.json");
|
|
12466
12512
|
MAX_RETRIES2 = 5;
|
|
12467
12513
|
TTL_MS2 = 60 * 60 * 1e3;
|
|
12514
|
+
_draining = false;
|
|
12468
12515
|
INTERCOM_LOG = path22.join(os9.homedir(), ".exe-os", "intercom.log");
|
|
12469
12516
|
}
|
|
12470
12517
|
});
|
|
@@ -13139,7 +13186,13 @@ async function createReviewForCompletedTask(row, result3, _baseDir, now2) {
|
|
|
13139
13186
|
taskFile
|
|
13140
13187
|
});
|
|
13141
13188
|
const originalPriority = String(row.priority).toLowerCase();
|
|
13142
|
-
const
|
|
13189
|
+
const resultLower = result3?.toLowerCase() ?? "";
|
|
13190
|
+
const hasTestEvidence = (
|
|
13191
|
+
// Vitest/Jest output patterns (hard to fake without actually running tests)
|
|
13192
|
+
/\d+\s+pass(ed|ing)/.test(resultLower) || /test files?\s+\d+\s+passed/.test(resultLower) || /tests?\s+\d+\s+passed/.test(resultLower)
|
|
13193
|
+
);
|
|
13194
|
+
const hasNoFailures = !/fail(ed|ure|ing)|error/i.test(resultLower);
|
|
13195
|
+
const autoApprove = originalPriority === "p2" && hasTestEvidence && hasNoFailures;
|
|
13143
13196
|
if (!autoApprove) {
|
|
13144
13197
|
try {
|
|
13145
13198
|
const key = getSessionKey();
|
|
@@ -13147,6 +13200,13 @@ async function createReviewForCompletedTask(row, result3, _baseDir, now2) {
|
|
|
13147
13200
|
if (exeSession) {
|
|
13148
13201
|
sendIntercom(exeSession);
|
|
13149
13202
|
}
|
|
13203
|
+
if (reviewer && reviewer !== coordinatorName && reviewer !== exeSession) {
|
|
13204
|
+
const { employeeSessionName: employeeSessionName2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
13205
|
+
if (exeSession) {
|
|
13206
|
+
const reviewerSession = employeeSessionName2(reviewer, exeSession);
|
|
13207
|
+
sendIntercom(reviewerSession);
|
|
13208
|
+
}
|
|
13209
|
+
}
|
|
13150
13210
|
} catch {
|
|
13151
13211
|
}
|
|
13152
13212
|
}
|
|
@@ -13277,18 +13337,31 @@ function acquireSpawnLock2(sessionName) {
|
|
|
13277
13337
|
mkdirSync10(SPAWN_LOCK_DIR, { recursive: true });
|
|
13278
13338
|
}
|
|
13279
13339
|
const lockFile = spawnLockPath(sessionName);
|
|
13280
|
-
|
|
13281
|
-
|
|
13282
|
-
|
|
13283
|
-
|
|
13284
|
-
|
|
13285
|
-
|
|
13286
|
-
|
|
13287
|
-
|
|
13340
|
+
const lockData = JSON.stringify({ pid: process.pid, timestamp: Date.now() });
|
|
13341
|
+
const { openSync: openSync5, closeSync: closeSync5, writeSync } = __require("fs");
|
|
13342
|
+
const { constants: constants2 } = __require("fs");
|
|
13343
|
+
try {
|
|
13344
|
+
const fd = openSync5(lockFile, constants2.O_WRONLY | constants2.O_CREAT | constants2.O_EXCL, 420);
|
|
13345
|
+
writeSync(fd, lockData);
|
|
13346
|
+
closeSync5(fd);
|
|
13347
|
+
return true;
|
|
13348
|
+
} catch (err) {
|
|
13349
|
+
if (err?.code !== "EEXIST") {
|
|
13350
|
+
return true;
|
|
13288
13351
|
}
|
|
13289
13352
|
}
|
|
13290
|
-
|
|
13291
|
-
|
|
13353
|
+
try {
|
|
13354
|
+
const lock = JSON.parse(readFileSync15(lockFile, "utf8"));
|
|
13355
|
+
const age = Date.now() - lock.timestamp;
|
|
13356
|
+
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
13357
|
+
return false;
|
|
13358
|
+
}
|
|
13359
|
+
writeFileSync12(lockFile, lockData);
|
|
13360
|
+
return true;
|
|
13361
|
+
} catch {
|
|
13362
|
+
writeFileSync12(lockFile, lockData);
|
|
13363
|
+
return true;
|
|
13364
|
+
}
|
|
13292
13365
|
}
|
|
13293
13366
|
function releaseSpawnLock2(sessionName) {
|
|
13294
13367
|
try {
|
|
@@ -13367,6 +13440,21 @@ function parseParentExe(sessionName, agentId) {
|
|
|
13367
13440
|
function extractRootExe(name) {
|
|
13368
13441
|
if (!name) return null;
|
|
13369
13442
|
if (!name.includes("-")) return name;
|
|
13443
|
+
try {
|
|
13444
|
+
const roster = (init_employees(), __toCommonJS(employees_exports)).loadEmployeesSync();
|
|
13445
|
+
if (roster.length > 0) {
|
|
13446
|
+
const sortedNames = roster.map((e) => e.name).sort((a, b) => b.length - a.length);
|
|
13447
|
+
for (const agentName of sortedNames) {
|
|
13448
|
+
const escaped = agentName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
13449
|
+
const regex = new RegExp(`^${escaped}\\d*-(.+)$`);
|
|
13450
|
+
const match = name.match(regex);
|
|
13451
|
+
if (match) {
|
|
13452
|
+
return extractRootExe(match[1]);
|
|
13453
|
+
}
|
|
13454
|
+
}
|
|
13455
|
+
}
|
|
13456
|
+
} catch {
|
|
13457
|
+
}
|
|
13370
13458
|
const parts = name.split("-").filter(Boolean);
|
|
13371
13459
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
13372
13460
|
}
|
|
@@ -13385,6 +13473,10 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
13385
13473
|
function getParentExe(sessionKey) {
|
|
13386
13474
|
try {
|
|
13387
13475
|
const data = JSON.parse(readFileSync15(path25.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
13476
|
+
if (data.registeredAt) {
|
|
13477
|
+
const age = Date.now() - new Date(data.registeredAt).getTime();
|
|
13478
|
+
if (age > PARENT_EXE_CACHE_TTL_MS) return null;
|
|
13479
|
+
}
|
|
13388
13480
|
return data.parentExe || null;
|
|
13389
13481
|
} catch {
|
|
13390
13482
|
return null;
|
|
@@ -13933,7 +14025,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
13933
14025
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
13934
14026
|
} catch {
|
|
13935
14027
|
}
|
|
13936
|
-
let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName}`;
|
|
14028
|
+
let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName} EXE_SESSION_START_ISO=${(/* @__PURE__ */ new Date()).toISOString()}`;
|
|
13937
14029
|
if (ccProvider !== DEFAULT_PROVIDER) {
|
|
13938
14030
|
const cfg = PROVIDER_TABLE[ccProvider];
|
|
13939
14031
|
if (cfg?.apiKeyEnv) {
|
|
@@ -13968,10 +14060,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
13968
14060
|
}
|
|
13969
14061
|
if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
|
|
13970
14062
|
if (agentRtConfig.runtime === "claude" && agentRtConfig.model) {
|
|
13971
|
-
|
|
13972
|
-
|
|
13973
|
-
ccModel += "[1m]";
|
|
13974
|
-
}
|
|
14063
|
+
const { normalizeCcModelName: normalizeCcModelName2 } = (init_agent_config(), __toCommonJS(agent_config_exports));
|
|
14064
|
+
const ccModel = normalizeCcModelName2(agentRtConfig.model);
|
|
13975
14065
|
envPrefix = `${envPrefix} ANTHROPIC_MODEL=${ccModel}`;
|
|
13976
14066
|
}
|
|
13977
14067
|
}
|
|
@@ -14072,7 +14162,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
14072
14162
|
releaseSpawnLock2(sessionName);
|
|
14073
14163
|
return { sessionName };
|
|
14074
14164
|
}
|
|
14075
|
-
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;
|
|
14165
|
+
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;
|
|
14076
14166
|
var init_tmux_routing = __esm({
|
|
14077
14167
|
"src/lib/tmux-routing.ts"() {
|
|
14078
14168
|
"use strict";
|
|
@@ -14092,6 +14182,7 @@ var init_tmux_routing = __esm({
|
|
|
14092
14182
|
SESSION_CACHE = path25.join(os11.homedir(), ".exe-os", "session-cache");
|
|
14093
14183
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
14094
14184
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
14185
|
+
PARENT_EXE_CACHE_TTL_MS = 4 * 60 * 60 * 1e3;
|
|
14095
14186
|
VERIFY_PANE_LINES = 200;
|
|
14096
14187
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
14097
14188
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
@@ -14142,6 +14233,17 @@ var init_task_scope = __esm({
|
|
|
14142
14233
|
});
|
|
14143
14234
|
|
|
14144
14235
|
// src/lib/notifications.ts
|
|
14236
|
+
var notifications_exports = {};
|
|
14237
|
+
__export(notifications_exports, {
|
|
14238
|
+
cleanupOldNotifications: () => cleanupOldNotifications,
|
|
14239
|
+
formatNotifications: () => formatNotifications,
|
|
14240
|
+
markAsRead: () => markAsRead,
|
|
14241
|
+
markAsReadByTaskFile: () => markAsReadByTaskFile,
|
|
14242
|
+
markDoneTaskNotificationsAsRead: () => markDoneTaskNotificationsAsRead,
|
|
14243
|
+
migrateJsonNotifications: () => migrateJsonNotifications,
|
|
14244
|
+
readUnreadNotifications: () => readUnreadNotifications,
|
|
14245
|
+
writeNotification: () => writeNotification
|
|
14246
|
+
});
|
|
14145
14247
|
import crypto7 from "crypto";
|
|
14146
14248
|
import path26 from "path";
|
|
14147
14249
|
import os12 from "os";
|
|
@@ -14178,6 +14280,52 @@ async function writeNotification(notification) {
|
|
|
14178
14280
|
`);
|
|
14179
14281
|
}
|
|
14180
14282
|
}
|
|
14283
|
+
async function readUnreadNotifications(agentFilter2, sessionScope) {
|
|
14284
|
+
try {
|
|
14285
|
+
const client = getClient();
|
|
14286
|
+
const conditions = ["read = 0"];
|
|
14287
|
+
const args = [];
|
|
14288
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
14289
|
+
if (agentFilter2) {
|
|
14290
|
+
conditions.push("agent_id = ?");
|
|
14291
|
+
args.push(agentFilter2);
|
|
14292
|
+
}
|
|
14293
|
+
const result3 = await client.execute({
|
|
14294
|
+
sql: `SELECT id, agent_id, agent_role, event, project, summary, task_file, session_scope, created_at
|
|
14295
|
+
FROM notifications
|
|
14296
|
+
WHERE ${conditions.join(" AND ")}${scope.sql}
|
|
14297
|
+
ORDER BY created_at ASC`,
|
|
14298
|
+
args: [...args, ...scope.args]
|
|
14299
|
+
});
|
|
14300
|
+
return result3.rows.map((r) => ({
|
|
14301
|
+
id: String(r.id),
|
|
14302
|
+
agentId: String(r.agent_id),
|
|
14303
|
+
agentRole: String(r.agent_role),
|
|
14304
|
+
event: String(r.event),
|
|
14305
|
+
project: String(r.project),
|
|
14306
|
+
summary: String(r.summary),
|
|
14307
|
+
taskFile: r.task_file ? String(r.task_file) : void 0,
|
|
14308
|
+
sessionScope: r.session_scope == null ? null : String(r.session_scope),
|
|
14309
|
+
timestamp: String(r.created_at),
|
|
14310
|
+
read: false
|
|
14311
|
+
}));
|
|
14312
|
+
} catch {
|
|
14313
|
+
return [];
|
|
14314
|
+
}
|
|
14315
|
+
}
|
|
14316
|
+
async function markAsRead(ids, sessionScope) {
|
|
14317
|
+
if (ids.length === 0) return;
|
|
14318
|
+
try {
|
|
14319
|
+
const client = getClient();
|
|
14320
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
14321
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
14322
|
+
await client.execute({
|
|
14323
|
+
sql: `UPDATE notifications SET read = 1 WHERE id IN (${placeholders})${scope.sql}`,
|
|
14324
|
+
args: [...ids, ...scope.args]
|
|
14325
|
+
});
|
|
14326
|
+
} catch {
|
|
14327
|
+
}
|
|
14328
|
+
}
|
|
14181
14329
|
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
14182
14330
|
try {
|
|
14183
14331
|
const client = getClient();
|
|
@@ -14190,11 +14338,144 @@ async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
|
14190
14338
|
} catch {
|
|
14191
14339
|
}
|
|
14192
14340
|
}
|
|
14341
|
+
async function cleanupOldNotifications(daysOld = CLEANUP_DAYS, sessionScope) {
|
|
14342
|
+
try {
|
|
14343
|
+
const client = getClient();
|
|
14344
|
+
const cutoff = new Date(
|
|
14345
|
+
Date.now() - daysOld * 24 * 60 * 60 * 1e3
|
|
14346
|
+
).toISOString();
|
|
14347
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
14348
|
+
const result3 = await client.execute({
|
|
14349
|
+
sql: `DELETE FROM notifications WHERE created_at < ?${scope.sql}`,
|
|
14350
|
+
args: [cutoff, ...scope.args]
|
|
14351
|
+
});
|
|
14352
|
+
return result3.rowsAffected;
|
|
14353
|
+
} catch {
|
|
14354
|
+
return 0;
|
|
14355
|
+
}
|
|
14356
|
+
}
|
|
14357
|
+
async function markDoneTaskNotificationsAsRead(sessionScope) {
|
|
14358
|
+
try {
|
|
14359
|
+
const client = getClient();
|
|
14360
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
14361
|
+
const result3 = await client.execute({
|
|
14362
|
+
sql: `UPDATE notifications SET read = 1
|
|
14363
|
+
WHERE read = 0
|
|
14364
|
+
AND task_file IS NOT NULL
|
|
14365
|
+
${scope.sql}
|
|
14366
|
+
AND task_file IN (
|
|
14367
|
+
SELECT task_file FROM tasks WHERE status = 'done'${scope.sql}
|
|
14368
|
+
)`,
|
|
14369
|
+
args: [...scope.args, ...scope.args]
|
|
14370
|
+
});
|
|
14371
|
+
return result3.rowsAffected;
|
|
14372
|
+
} catch {
|
|
14373
|
+
return 0;
|
|
14374
|
+
}
|
|
14375
|
+
}
|
|
14376
|
+
function formatNotifications(notifications) {
|
|
14377
|
+
if (notifications.length === 0) return "";
|
|
14378
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
14379
|
+
for (const n of notifications) {
|
|
14380
|
+
const key = `${n.agentId}|${n.agentRole}`;
|
|
14381
|
+
if (!grouped.has(key)) grouped.set(key, []);
|
|
14382
|
+
grouped.get(key).push(n);
|
|
14383
|
+
}
|
|
14384
|
+
const lines = [];
|
|
14385
|
+
lines.push(`## Notifications (${notifications.length} unread)
|
|
14386
|
+
`);
|
|
14387
|
+
for (const [key, items] of grouped) {
|
|
14388
|
+
const [agentId, agentRole] = key.split("|");
|
|
14389
|
+
lines.push(`**${agentId}** (${agentRole}):`);
|
|
14390
|
+
for (const item of items) {
|
|
14391
|
+
const ago = formatTimeAgo(item.timestamp);
|
|
14392
|
+
const icon = eventIcon(item.event);
|
|
14393
|
+
lines.push(`- ${icon} ${item.summary} (${item.project}) \u2014 ${ago}`);
|
|
14394
|
+
}
|
|
14395
|
+
lines.push("");
|
|
14396
|
+
}
|
|
14397
|
+
return lines.join("\n");
|
|
14398
|
+
}
|
|
14399
|
+
async function migrateJsonNotifications() {
|
|
14400
|
+
const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR || path26.join(os12.homedir(), ".exe-os");
|
|
14401
|
+
const notifDir = path26.join(base, "notifications");
|
|
14402
|
+
if (!existsSync23(notifDir)) return 0;
|
|
14403
|
+
let migrated = 0;
|
|
14404
|
+
try {
|
|
14405
|
+
const files = readdirSync6(notifDir).filter((f) => f.endsWith(".json"));
|
|
14406
|
+
if (files.length === 0) return 0;
|
|
14407
|
+
const client = getClient();
|
|
14408
|
+
for (const file of files) {
|
|
14409
|
+
try {
|
|
14410
|
+
const filePath = path26.join(notifDir, file);
|
|
14411
|
+
const data = JSON.parse(readFileSync16(filePath, "utf8"));
|
|
14412
|
+
await client.execute({
|
|
14413
|
+
sql: `INSERT OR IGNORE INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
14414
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
14415
|
+
args: [
|
|
14416
|
+
crypto7.randomUUID(),
|
|
14417
|
+
data.agentId ?? "unknown",
|
|
14418
|
+
data.agentRole ?? "unknown",
|
|
14419
|
+
data.event ?? "session_summary",
|
|
14420
|
+
data.project ?? "unknown",
|
|
14421
|
+
data.summary ?? "",
|
|
14422
|
+
data.taskFile ?? null,
|
|
14423
|
+
null,
|
|
14424
|
+
data.read ? 1 : 0,
|
|
14425
|
+
data.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
14426
|
+
]
|
|
14427
|
+
});
|
|
14428
|
+
unlinkSync8(filePath);
|
|
14429
|
+
migrated++;
|
|
14430
|
+
} catch {
|
|
14431
|
+
}
|
|
14432
|
+
}
|
|
14433
|
+
try {
|
|
14434
|
+
const remaining = readdirSync6(notifDir);
|
|
14435
|
+
if (remaining.length === 0) {
|
|
14436
|
+
rmdirSync(notifDir);
|
|
14437
|
+
}
|
|
14438
|
+
} catch {
|
|
14439
|
+
}
|
|
14440
|
+
} catch {
|
|
14441
|
+
}
|
|
14442
|
+
return migrated;
|
|
14443
|
+
}
|
|
14444
|
+
function eventIcon(event) {
|
|
14445
|
+
switch (event) {
|
|
14446
|
+
case "task_complete":
|
|
14447
|
+
return "Completed:";
|
|
14448
|
+
case "task_needs_fix":
|
|
14449
|
+
return "Needs fix:";
|
|
14450
|
+
case "session_summary":
|
|
14451
|
+
return "Session:";
|
|
14452
|
+
case "error_spike":
|
|
14453
|
+
return "Errors:";
|
|
14454
|
+
case "orphan_task":
|
|
14455
|
+
return "Orphan:";
|
|
14456
|
+
case "subtasks_complete":
|
|
14457
|
+
return "Subtasks done:";
|
|
14458
|
+
case "capacity_relaunch":
|
|
14459
|
+
return "Relaunched:";
|
|
14460
|
+
}
|
|
14461
|
+
}
|
|
14462
|
+
function formatTimeAgo(timestamp) {
|
|
14463
|
+
const diffMs = Date.now() - new Date(timestamp).getTime();
|
|
14464
|
+
const mins = Math.floor(diffMs / 6e4);
|
|
14465
|
+
if (mins < 1) return "just now";
|
|
14466
|
+
if (mins < 60) return `${mins}m ago`;
|
|
14467
|
+
const hours = Math.floor(mins / 60);
|
|
14468
|
+
if (hours < 24) return `${hours}h ago`;
|
|
14469
|
+
const days = Math.floor(hours / 24);
|
|
14470
|
+
return `${days}d ago`;
|
|
14471
|
+
}
|
|
14472
|
+
var CLEANUP_DAYS;
|
|
14193
14473
|
var init_notifications = __esm({
|
|
14194
14474
|
"src/lib/notifications.ts"() {
|
|
14195
14475
|
"use strict";
|
|
14196
14476
|
init_database();
|
|
14197
14477
|
init_task_scope();
|
|
14478
|
+
CLEANUP_DAYS = 7;
|
|
14198
14479
|
}
|
|
14199
14480
|
});
|
|
14200
14481
|
|
|
@@ -15564,6 +15845,20 @@ async function updateTask(input) {
|
|
|
15564
15845
|
notifyTaskDone();
|
|
15565
15846
|
}
|
|
15566
15847
|
await markTaskNotificationsRead(taskFile);
|
|
15848
|
+
if (input.status === "needs_review" && !isCoordinator) {
|
|
15849
|
+
try {
|
|
15850
|
+
const { writeNotification: writeNotification2 } = await Promise.resolve().then(() => (init_notifications(), notifications_exports));
|
|
15851
|
+
await writeNotification2({
|
|
15852
|
+
agentId: String(row.assigned_to),
|
|
15853
|
+
agentRole: String(row.assigned_to),
|
|
15854
|
+
event: "task_complete",
|
|
15855
|
+
project: String(row.project_name),
|
|
15856
|
+
summary: `"${String(row.title)}" is ready for review`,
|
|
15857
|
+
taskFile
|
|
15858
|
+
});
|
|
15859
|
+
} catch {
|
|
15860
|
+
}
|
|
15861
|
+
}
|
|
15567
15862
|
if (input.status === "done" || input.status === "closed") {
|
|
15568
15863
|
try {
|
|
15569
15864
|
await cascadeUnblock(taskId, input.baseDir, now2);
|
|
@@ -22316,6 +22611,7 @@ __export(daemon_orchestration_exports, {
|
|
|
22316
22611
|
REVIEW_NUDGE_COOLDOWN_MS: () => REVIEW_NUDGE_COOLDOWN_MS,
|
|
22317
22612
|
SESSION_CONTEXT_THRESHOLD_PCT: () => SESSION_CONTEXT_THRESHOLD_PCT,
|
|
22318
22613
|
SESSION_TTL_HOURS: () => SESSION_TTL_HOURS,
|
|
22614
|
+
STUCK_TASK_GRACE_MS: () => STUCK_TASK_GRACE_MS,
|
|
22319
22615
|
_resetAutoWakeState: () => _resetAutoWakeState,
|
|
22320
22616
|
checkSessionTTL: () => checkSessionTTL,
|
|
22321
22617
|
classifyTtlKillReason: () => classifyTtlKillReason,
|
|
@@ -22325,12 +22621,14 @@ __export(daemon_orchestration_exports, {
|
|
|
22325
22621
|
createOrphanReaperRealDeps: () => createOrphanReaperRealDeps,
|
|
22326
22622
|
createReviewNudgeRealDeps: () => createReviewNudgeRealDeps,
|
|
22327
22623
|
createSessionTTLRealDeps: () => createSessionTTLRealDeps,
|
|
22624
|
+
createStuckTaskRealDeps: () => createStuckTaskRealDeps,
|
|
22328
22625
|
loadNudgeState: () => loadNudgeState,
|
|
22329
22626
|
pollIdleEmployees: () => pollIdleEmployees,
|
|
22330
22627
|
pollIdleKill: () => pollIdleKill,
|
|
22331
22628
|
pollOrphanedTasks: () => pollOrphanedTasks,
|
|
22332
22629
|
pollReviewNudge: () => pollReviewNudge,
|
|
22333
22630
|
reapOrphanedMcpProcesses: () => reapOrphanedMcpProcesses,
|
|
22631
|
+
releaseStuckTasks: () => releaseStuckTasks,
|
|
22334
22632
|
saveNudgeState: () => saveNudgeState,
|
|
22335
22633
|
shouldAutoWake: () => shouldAutoWake,
|
|
22336
22634
|
shouldKillIdleSession: () => shouldKillIdleSession,
|
|
@@ -22818,6 +23116,102 @@ async function pollOrphanedTasks(deps, nowMs = Date.now()) {
|
|
|
22818
23116
|
}
|
|
22819
23117
|
return woken;
|
|
22820
23118
|
}
|
|
23119
|
+
async function releaseStuckTasks(deps, nowMs = Date.now()) {
|
|
23120
|
+
let liveSessions;
|
|
23121
|
+
try {
|
|
23122
|
+
liveSessions = deps.listTmuxSessions();
|
|
23123
|
+
} catch {
|
|
23124
|
+
return [];
|
|
23125
|
+
}
|
|
23126
|
+
const liveAgents = /* @__PURE__ */ new Set();
|
|
23127
|
+
for (const session of liveSessions) {
|
|
23128
|
+
const agent = deps.parseAgentFromSession(session);
|
|
23129
|
+
if (agent) liveAgents.add(agent);
|
|
23130
|
+
}
|
|
23131
|
+
for (const session of liveSessions) {
|
|
23132
|
+
if (isExeSession(session)) liveAgents.add(session);
|
|
23133
|
+
}
|
|
23134
|
+
let tasks;
|
|
23135
|
+
try {
|
|
23136
|
+
tasks = await deps.queryInProgressTasks();
|
|
23137
|
+
} catch {
|
|
23138
|
+
return [];
|
|
23139
|
+
}
|
|
23140
|
+
const released = [];
|
|
23141
|
+
for (const t of tasks) {
|
|
23142
|
+
if (liveAgents.has(t.agentId)) continue;
|
|
23143
|
+
const updatedMs = new Date(t.updatedAt).getTime();
|
|
23144
|
+
if (isNaN(updatedMs) || nowMs - updatedMs < STUCK_TASK_GRACE_MS) continue;
|
|
23145
|
+
const ageMinutes = Math.round((nowMs - updatedMs) / 6e4);
|
|
23146
|
+
const reason = `[auto-release] Agent "${t.agentId}" session is dead and task has been in_progress for ${ageMinutes}m with no update. Marked blocked for triage.`;
|
|
23147
|
+
try {
|
|
23148
|
+
await deps.markTaskBlocked(t.taskId, reason);
|
|
23149
|
+
released.push(t.taskId);
|
|
23150
|
+
process.stderr.write(
|
|
23151
|
+
`[stuck-release] Task ${t.taskId} (${t.agentId}) \u2014 in_progress for ${ageMinutes}m, agent dead \u2192 blocked
|
|
23152
|
+
`
|
|
23153
|
+
);
|
|
23154
|
+
if (deps.notifyOrphan) {
|
|
23155
|
+
try {
|
|
23156
|
+
await deps.notifyOrphan(t.taskId, t.agentId);
|
|
23157
|
+
} catch {
|
|
23158
|
+
}
|
|
23159
|
+
}
|
|
23160
|
+
} catch (err) {
|
|
23161
|
+
process.stderr.write(
|
|
23162
|
+
`[stuck-release] Failed to release ${t.taskId}: ${err instanceof Error ? err.message : String(err)}
|
|
23163
|
+
`
|
|
23164
|
+
);
|
|
23165
|
+
}
|
|
23166
|
+
}
|
|
23167
|
+
return released;
|
|
23168
|
+
}
|
|
23169
|
+
function createStuckTaskRealDeps(getClient2) {
|
|
23170
|
+
return {
|
|
23171
|
+
listTmuxSessions: () => {
|
|
23172
|
+
const { listTmuxSessions: listTmuxSessions2 } = (init_tmux_status(), __toCommonJS(tmux_status_exports));
|
|
23173
|
+
return listTmuxSessions2();
|
|
23174
|
+
},
|
|
23175
|
+
queryInProgressTasks: async () => {
|
|
23176
|
+
const client = getClient2();
|
|
23177
|
+
const result3 = await client.execute({
|
|
23178
|
+
sql: `SELECT id, assigned_to, session_scope, updated_at FROM tasks
|
|
23179
|
+
WHERE status = 'in_progress'
|
|
23180
|
+
ORDER BY updated_at ASC`,
|
|
23181
|
+
args: []
|
|
23182
|
+
});
|
|
23183
|
+
return result3.rows.map((r) => ({
|
|
23184
|
+
taskId: String(r.id),
|
|
23185
|
+
agentId: String(r.assigned_to),
|
|
23186
|
+
sessionScope: r.session_scope ? String(r.session_scope) : null,
|
|
23187
|
+
updatedAt: String(r.updated_at)
|
|
23188
|
+
}));
|
|
23189
|
+
},
|
|
23190
|
+
markTaskBlocked: async (taskId, reason) => {
|
|
23191
|
+
const client = getClient2();
|
|
23192
|
+
await client.execute({
|
|
23193
|
+
sql: `UPDATE tasks SET status = 'blocked', result = ?, updated_at = ? WHERE id = ?`,
|
|
23194
|
+
args: [reason, (/* @__PURE__ */ new Date()).toISOString(), taskId]
|
|
23195
|
+
});
|
|
23196
|
+
},
|
|
23197
|
+
parseAgentFromSession: (sessionName) => {
|
|
23198
|
+
const { baseAgentName: baseAgentName2 } = (init_employees(), __toCommonJS(employees_exports));
|
|
23199
|
+
if (!sessionName.includes("-")) return null;
|
|
23200
|
+
const agentPart = sessionName.split("-")[0];
|
|
23201
|
+
return baseAgentName2(agentPart);
|
|
23202
|
+
},
|
|
23203
|
+
notifyOrphan: async (taskId, agentId) => {
|
|
23204
|
+
const { writeNotification: writeNotification2 } = (init_notifications(), __toCommonJS(notifications_exports));
|
|
23205
|
+
await writeNotification2({
|
|
23206
|
+
agentId,
|
|
23207
|
+
agentRole: "employee",
|
|
23208
|
+
event: "orphan_task",
|
|
23209
|
+
project: "",
|
|
23210
|
+
summary: `Agent "${agentId}" session died \u2014 task ${taskId.slice(0, 8)} auto-released to blocked for triage`
|
|
23211
|
+
});
|
|
23212
|
+
}
|
|
23213
|
+
};
|
|
23214
|
+
}
|
|
22821
23215
|
function createAutoWakeRealDeps(getClient2, projectDir) {
|
|
22822
23216
|
return {
|
|
22823
23217
|
listTmuxSessions: () => {
|
|
@@ -22910,7 +23304,7 @@ function createOrphanReaperRealDeps() {
|
|
|
22910
23304
|
selfPid: process.pid
|
|
22911
23305
|
};
|
|
22912
23306
|
}
|
|
22913
|
-
var IDLE_NUDGE_DEDUP_MS, SESSION_TTL_HOURS, SESSION_CONTEXT_THRESHOLD_PCT, IDLE_KILL_INTERCOM_ACK_WINDOW_MS, REVIEW_NUDGE_COOLDOWN_MS, NUDGE_STATE_PATH, AUTO_WAKE_COOLDOWN_MS, AUTO_WAKE_MAX_RETRIES, _autoWakeLastSpawn, _autoWakeTaskRetries, ORPHAN_SIGKILL_DELAY_MS, ORPHAN_PATTERNS;
|
|
23307
|
+
var IDLE_NUDGE_DEDUP_MS, SESSION_TTL_HOURS, SESSION_CONTEXT_THRESHOLD_PCT, IDLE_KILL_INTERCOM_ACK_WINDOW_MS, REVIEW_NUDGE_COOLDOWN_MS, NUDGE_STATE_PATH, AUTO_WAKE_COOLDOWN_MS, AUTO_WAKE_MAX_RETRIES, _autoWakeLastSpawn, _autoWakeTaskRetries, STUCK_TASK_GRACE_MS, ORPHAN_SIGKILL_DELAY_MS, ORPHAN_PATTERNS;
|
|
22914
23308
|
var init_daemon_orchestration = __esm({
|
|
22915
23309
|
"src/lib/daemon-orchestration.ts"() {
|
|
22916
23310
|
"use strict";
|
|
@@ -22927,6 +23321,7 @@ var init_daemon_orchestration = __esm({
|
|
|
22927
23321
|
AUTO_WAKE_MAX_RETRIES = 3;
|
|
22928
23322
|
_autoWakeLastSpawn = /* @__PURE__ */ new Map();
|
|
22929
23323
|
_autoWakeTaskRetries = /* @__PURE__ */ new Map();
|
|
23324
|
+
STUCK_TASK_GRACE_MS = 5 * 60 * 1e3;
|
|
22930
23325
|
ORPHAN_SIGKILL_DELAY_MS = 5e3;
|
|
22931
23326
|
ORPHAN_PATTERNS = [
|
|
22932
23327
|
"exe-os/dist/mcp/server.js",
|
|
@@ -23057,7 +23452,21 @@ function tryAcquireWorkerSlot() {
|
|
|
23057
23452
|
for (const f of files) {
|
|
23058
23453
|
if (!f.endsWith(".pid")) continue;
|
|
23059
23454
|
if (f.startsWith("res-")) {
|
|
23060
|
-
|
|
23455
|
+
const resParts = f.replace(".pid", "").split("-");
|
|
23456
|
+
const resPid = parseInt(resParts[1] ?? "", 10);
|
|
23457
|
+
if (!isNaN(resPid) && resPid > 0) {
|
|
23458
|
+
try {
|
|
23459
|
+
process.kill(resPid, 0);
|
|
23460
|
+
alive++;
|
|
23461
|
+
} catch {
|
|
23462
|
+
try {
|
|
23463
|
+
unlinkSync10(path35.join(WORKER_PID_DIR, f));
|
|
23464
|
+
} catch {
|
|
23465
|
+
}
|
|
23466
|
+
}
|
|
23467
|
+
} else {
|
|
23468
|
+
alive++;
|
|
23469
|
+
}
|
|
23061
23470
|
continue;
|
|
23062
23471
|
}
|
|
23063
23472
|
const dashIdx = f.lastIndexOf("-");
|
|
@@ -25089,6 +25498,7 @@ __export(cloud_sync_exports, {
|
|
|
25089
25498
|
markCloudReuploadRequired: () => markCloudReuploadRequired,
|
|
25090
25499
|
mergeConfig: () => mergeConfig,
|
|
25091
25500
|
mergeRosterFromRemote: () => mergeRosterFromRemote,
|
|
25501
|
+
migrateEndpoint: () => migrateEndpoint,
|
|
25092
25502
|
pushToPostgres: () => pushToPostgres,
|
|
25093
25503
|
recordRosterDeletion: () => recordRosterDeletion
|
|
25094
25504
|
});
|
|
@@ -25259,6 +25669,15 @@ async function fetchWithRetry(url, init) {
|
|
|
25259
25669
|
}
|
|
25260
25670
|
throw lastError;
|
|
25261
25671
|
}
|
|
25672
|
+
function migrateEndpoint(endpoint2) {
|
|
25673
|
+
if (endpoint2 === "https://askexe.com/cloud" || endpoint2 === "https://askexe.com/cloud/") {
|
|
25674
|
+
process.stderr.write(
|
|
25675
|
+
"[cloud-sync] Auto-migrating endpoint from askexe.com/cloud to cloud.askexe.com (bypasses Cloudflare WAF for datacenter IPs)\n"
|
|
25676
|
+
);
|
|
25677
|
+
return "https://cloud.askexe.com";
|
|
25678
|
+
}
|
|
25679
|
+
return endpoint2;
|
|
25680
|
+
}
|
|
25262
25681
|
function assertSecureEndpoint(endpoint2) {
|
|
25263
25682
|
if (endpoint2.startsWith("https://")) return;
|
|
25264
25683
|
if (endpoint2.startsWith("http://")) {
|
|
@@ -25396,6 +25815,7 @@ async function markCloudReuploadRequired(client = getClient()) {
|
|
|
25396
25815
|
await client.execute("INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('cloud_reupload_required', '1')");
|
|
25397
25816
|
}
|
|
25398
25817
|
async function cloudSync(config2) {
|
|
25818
|
+
config2 = { ...config2, endpoint: migrateEndpoint(config2.endpoint) };
|
|
25399
25819
|
if (!isSyncCryptoInitialized()) {
|
|
25400
25820
|
try {
|
|
25401
25821
|
const { getMasterKey: getMasterKey2 } = await Promise.resolve().then(() => (init_keychain(), keychain_exports));
|
|
@@ -37163,7 +37583,25 @@ async function startMcpHttpServer() {
|
|
|
37163
37583
|
}
|
|
37164
37584
|
const agentId = req.headers["x-agent-id"] || "default";
|
|
37165
37585
|
const agentRole = req.headers["x-agent-role"] || "employee";
|
|
37166
|
-
|
|
37586
|
+
let sessionHint = req.headers["x-exe-session"] || "";
|
|
37587
|
+
if (sessionHint.includes("$(") || sessionHint.includes("#{") || sessionHint.includes("tmux")) {
|
|
37588
|
+
sessionHint = "";
|
|
37589
|
+
}
|
|
37590
|
+
if (!sessionHint) {
|
|
37591
|
+
try {
|
|
37592
|
+
const { getTransport: getTransport2 } = (init_transport(), __toCommonJS(transport_exports));
|
|
37593
|
+
const transport2 = getTransport2();
|
|
37594
|
+
const liveSessions = transport2.listSessions();
|
|
37595
|
+
const agentSessions = liveSessions.filter((s) => s.startsWith(agentId + "-"));
|
|
37596
|
+
if (agentSessions.length === 1) {
|
|
37597
|
+
sessionHint = agentSessions[0];
|
|
37598
|
+
} else {
|
|
37599
|
+
const roots = liveSessions.filter((s) => !s.includes("-") && /^[a-z]+\d*$/.test(s));
|
|
37600
|
+
if (roots.length === 1) sessionHint = roots[0];
|
|
37601
|
+
}
|
|
37602
|
+
} catch {
|
|
37603
|
+
}
|
|
37604
|
+
}
|
|
37167
37605
|
const runtime = inferMcpRuntime({
|
|
37168
37606
|
runtimeHeader: req.headers["x-agent-runtime"] || req.headers["x-runtime"],
|
|
37169
37607
|
userAgent: req.headers["user-agent"],
|
|
@@ -37792,10 +38230,10 @@ function startIntercomQueueDrain() {
|
|
|
37792
38230
|
const rtConfig = getAgentRuntime2(agentName);
|
|
37793
38231
|
const nudgeMsg = "You have pending notifications. Run list_tasks to check for assigned work.";
|
|
37794
38232
|
if (rtConfig.runtime === "codex" || rtConfig.runtime === "opencode") {
|
|
37795
|
-
transport.
|
|
37796
|
-
|
|
37797
|
-
|
|
37798
|
-
|
|
38233
|
+
if (transport.sendKeysLiteral) {
|
|
38234
|
+
transport.sendKeysLiteral(session, nudgeMsg);
|
|
38235
|
+
} else {
|
|
38236
|
+
transport.sendKeys(session, nudgeMsg);
|
|
37799
38237
|
}
|
|
37800
38238
|
} else {
|
|
37801
38239
|
transport.sendKeys(session, nudgeMsg);
|
|
@@ -37871,6 +38309,31 @@ function startAutoWake() {
|
|
|
37871
38309
|
process.stderr.write(`[exed] Auto-wake started (every ${AUTO_WAKE_INTERVAL_MS / 1e3}s)
|
|
37872
38310
|
`);
|
|
37873
38311
|
}
|
|
38312
|
+
var STUCK_TASK_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
|
|
38313
|
+
function startStuckTaskRelease() {
|
|
38314
|
+
const tick = async () => {
|
|
38315
|
+
fired("stuck_task_release");
|
|
38316
|
+
if (!await ensureStoreForPolling()) return;
|
|
38317
|
+
try {
|
|
38318
|
+
const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
38319
|
+
const { releaseStuckTasks: releaseStuckTasks2, createStuckTaskRealDeps: createStuckTaskRealDeps2 } = await Promise.resolve().then(() => (init_daemon_orchestration(), daemon_orchestration_exports));
|
|
38320
|
+
const deps = createStuckTaskRealDeps2(getClient2);
|
|
38321
|
+
const released = await releaseStuckTasks2(deps);
|
|
38322
|
+
if (released.length > 0) {
|
|
38323
|
+
acted("stuck_task_release");
|
|
38324
|
+
process.stderr.write(`[exed] Stuck-release: ${released.length} task(s) moved to blocked
|
|
38325
|
+
`);
|
|
38326
|
+
}
|
|
38327
|
+
} catch (err) {
|
|
38328
|
+
process.stderr.write(`[exed] Stuck-release error: ${err instanceof Error ? err.message : String(err)}
|
|
38329
|
+
`);
|
|
38330
|
+
}
|
|
38331
|
+
};
|
|
38332
|
+
const timer = setInterval(() => void tick(), STUCK_TASK_CHECK_INTERVAL_MS);
|
|
38333
|
+
timer.unref();
|
|
38334
|
+
process.stderr.write(`[exed] Stuck-task release started (every ${STUCK_TASK_CHECK_INTERVAL_MS / 1e3}s)
|
|
38335
|
+
`);
|
|
38336
|
+
}
|
|
37874
38337
|
var TOTAL_MEM_GB = os24.totalmem() / 1024 ** 3;
|
|
37875
38338
|
var RSS_WARN_BYTES = Number(process.env.EXE_RSS_WARN_MB) * 1024 * 1024 || (TOTAL_MEM_GB >= 64 ? 6 * 1024 ** 3 : TOTAL_MEM_GB >= 32 ? 4 * 1024 ** 3 : TOTAL_MEM_GB >= 16 ? 2.5 * 1024 ** 3 : 1.5 * 1024 ** 3);
|
|
37876
38339
|
var RSS_RESTART_BYTES = Number(process.env.EXE_RSS_RESTART_MB) * 1024 * 1024 || (TOTAL_MEM_GB >= 64 ? 8 * 1024 ** 3 : TOTAL_MEM_GB >= 32 ? 6 * 1024 ** 3 : TOTAL_MEM_GB >= 16 ? 4 * 1024 ** 3 : 3 * 1024 ** 3);
|
|
@@ -38211,6 +38674,7 @@ try {
|
|
|
38211
38674
|
startOrphanReaper();
|
|
38212
38675
|
startAgentStats();
|
|
38213
38676
|
startAutoWake();
|
|
38677
|
+
startStuckTaskRelease();
|
|
38214
38678
|
startGraphExtraction();
|
|
38215
38679
|
startMemoryQueueDrain();
|
|
38216
38680
|
startIntercomQueueDrain();
|