@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/mcp/server.js
CHANGED
|
@@ -163,6 +163,17 @@ function normalizeOrchestration(raw) {
|
|
|
163
163
|
const userOrg = raw.orchestration ?? {};
|
|
164
164
|
raw.orchestration = { ...defaultOrg, ...userOrg };
|
|
165
165
|
}
|
|
166
|
+
function normalizeCloudEndpoint(raw) {
|
|
167
|
+
const cloud = raw.cloud;
|
|
168
|
+
if (!cloud?.endpoint) return;
|
|
169
|
+
const ep = String(cloud.endpoint);
|
|
170
|
+
if (ep === "https://askexe.com/cloud" || ep === "https://askexe.com/cloud/") {
|
|
171
|
+
cloud.endpoint = "https://cloud.askexe.com";
|
|
172
|
+
process.stderr.write(
|
|
173
|
+
"[config] Auto-migrated cloud endpoint: askexe.com/cloud \u2192 cloud.askexe.com\n"
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
166
177
|
async function loadConfig() {
|
|
167
178
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
168
179
|
await ensurePrivateDir(dir);
|
|
@@ -188,6 +199,7 @@ async function loadConfig() {
|
|
|
188
199
|
normalizeSessionLifecycle(migratedCfg);
|
|
189
200
|
normalizeAutoUpdate(migratedCfg);
|
|
190
201
|
normalizeOrchestration(migratedCfg);
|
|
202
|
+
normalizeCloudEndpoint(migratedCfg);
|
|
191
203
|
const config2 = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
|
|
192
204
|
if (config2.dbPath.startsWith("~")) {
|
|
193
205
|
config2.dbPath = config2.dbPath.replace(/^~/, os.homedir());
|
|
@@ -216,6 +228,7 @@ function loadConfigSync() {
|
|
|
216
228
|
normalizeSessionLifecycle(migratedCfg);
|
|
217
229
|
normalizeAutoUpdate(migratedCfg);
|
|
218
230
|
normalizeOrchestration(migratedCfg);
|
|
231
|
+
normalizeCloudEndpoint(migratedCfg);
|
|
219
232
|
const config2 = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
|
|
220
233
|
if (config2.dbPath.startsWith("~")) {
|
|
221
234
|
config2.dbPath = config2.dbPath.replace(/^~/, os.homedir());
|
|
@@ -982,6 +995,7 @@ __export(agent_config_exports, {
|
|
|
982
995
|
clearAgentRuntime: () => clearAgentRuntime,
|
|
983
996
|
getAgentRuntime: () => getAgentRuntime,
|
|
984
997
|
loadAgentConfig: () => loadAgentConfig,
|
|
998
|
+
normalizeCcModelName: () => normalizeCcModelName,
|
|
985
999
|
saveAgentConfig: () => saveAgentConfig,
|
|
986
1000
|
setAgentMcps: () => setAgentMcps,
|
|
987
1001
|
setAgentRuntime: () => setAgentRuntime
|
|
@@ -1010,6 +1024,13 @@ function getAgentRuntime(agentId) {
|
|
|
1010
1024
|
if (orgDefault) return orgDefault;
|
|
1011
1025
|
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
1012
1026
|
}
|
|
1027
|
+
function normalizeCcModelName(model) {
|
|
1028
|
+
let ccModel = model.replace(/(\d+)\.(\d+)/g, "$1-$2");
|
|
1029
|
+
if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
|
|
1030
|
+
ccModel += "[1m]";
|
|
1031
|
+
}
|
|
1032
|
+
return ccModel;
|
|
1033
|
+
}
|
|
1013
1034
|
function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
|
|
1014
1035
|
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
1015
1036
|
if (!knownModels) {
|
|
@@ -3818,7 +3839,7 @@ __export(keychain_exports, {
|
|
|
3818
3839
|
importMnemonic: () => importMnemonic,
|
|
3819
3840
|
setMasterKey: () => setMasterKey
|
|
3820
3841
|
});
|
|
3821
|
-
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
3842
|
+
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2, rename, copyFile } from "fs/promises";
|
|
3822
3843
|
import { existsSync as existsSync8, statSync as statSync3 } from "fs";
|
|
3823
3844
|
import { execSync as execSync3 } from "child_process";
|
|
3824
3845
|
import path7 from "path";
|
|
@@ -3853,12 +3874,14 @@ function linuxSecretAvailable() {
|
|
|
3853
3874
|
function isRootOnlyTrustedServerKeyFile(keyPath) {
|
|
3854
3875
|
if (process.platform !== "linux") return false;
|
|
3855
3876
|
try {
|
|
3856
|
-
const uid = typeof os5.userInfo().uid === "number" ? os5.userInfo().uid : -1;
|
|
3857
3877
|
const st = statSync3(keyPath);
|
|
3858
3878
|
if (!st.isFile() || (st.mode & 63) !== 0) return false;
|
|
3879
|
+
const uid = typeof os5.userInfo().uid === "number" ? os5.userInfo().uid : -1;
|
|
3859
3880
|
if (uid === 0) return true;
|
|
3860
3881
|
const exeOsDir = process.env.EXE_OS_DIR;
|
|
3861
|
-
|
|
3882
|
+
if (exeOsDir && path7.resolve(keyPath).startsWith(path7.resolve(exeOsDir) + path7.sep)) return true;
|
|
3883
|
+
if (!linuxSecretAvailable()) return true;
|
|
3884
|
+
return false;
|
|
3862
3885
|
} catch {
|
|
3863
3886
|
return false;
|
|
3864
3887
|
}
|
|
@@ -4008,15 +4031,25 @@ async function writeMachineBoundFileFallback(b64) {
|
|
|
4008
4031
|
await mkdir3(dir, { recursive: true });
|
|
4009
4032
|
const keyPath = getKeyPath();
|
|
4010
4033
|
const machineKey = deriveMachineKey();
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4034
|
+
const content = machineKey ? encryptWithMachineKey(b64, machineKey) + "\n" : b64 + "\n";
|
|
4035
|
+
const result3 = machineKey ? "encrypted" : "plaintext";
|
|
4036
|
+
const tmpPath = keyPath + ".tmp";
|
|
4037
|
+
try {
|
|
4038
|
+
if (existsSync8(keyPath)) {
|
|
4039
|
+
await copyFile(keyPath, keyPath + ".bak").catch(() => {
|
|
4040
|
+
});
|
|
4041
|
+
}
|
|
4042
|
+
await writeFile3(tmpPath, content, "utf-8");
|
|
4043
|
+
await chmod2(tmpPath, 384);
|
|
4044
|
+
await rename(tmpPath, keyPath);
|
|
4045
|
+
} catch (err) {
|
|
4046
|
+
try {
|
|
4047
|
+
await unlink(tmpPath);
|
|
4048
|
+
} catch {
|
|
4049
|
+
}
|
|
4050
|
+
throw err;
|
|
4016
4051
|
}
|
|
4017
|
-
|
|
4018
|
-
await chmod2(keyPath, 384);
|
|
4019
|
-
return "plaintext";
|
|
4052
|
+
return result3;
|
|
4020
4053
|
}
|
|
4021
4054
|
async function getMasterKey() {
|
|
4022
4055
|
let nativeValue = macKeychainGet() ?? linuxSecretGet();
|
|
@@ -4083,7 +4116,7 @@ async function getMasterKey() {
|
|
|
4083
4116
|
b64Value = content;
|
|
4084
4117
|
}
|
|
4085
4118
|
const key = Buffer.from(b64Value, "base64");
|
|
4086
|
-
if (
|
|
4119
|
+
if (isRootOnlyTrustedServerKeyFile(keyPath)) {
|
|
4087
4120
|
return key;
|
|
4088
4121
|
}
|
|
4089
4122
|
const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
|
|
@@ -9896,6 +9929,7 @@ var init_provider_table = __esm({
|
|
|
9896
9929
|
// src/lib/intercom-queue.ts
|
|
9897
9930
|
var intercom_queue_exports = {};
|
|
9898
9931
|
__export(intercom_queue_exports, {
|
|
9932
|
+
_resetDrainGuard: () => _resetDrainGuard,
|
|
9899
9933
|
clearQueueForAgent: () => clearQueueForAgent,
|
|
9900
9934
|
drainForSession: () => drainForSession,
|
|
9901
9935
|
drainQueue: () => drainQueue,
|
|
@@ -9941,38 +9975,47 @@ function queueIntercom(targetSession, reason) {
|
|
|
9941
9975
|
writeQueue(queue);
|
|
9942
9976
|
}
|
|
9943
9977
|
function drainQueue(isSessionBusy2, sendKeys) {
|
|
9978
|
+
if (_draining) {
|
|
9979
|
+
logQueue("SKIP_DRAIN \u2014 previous drain still running (possible tmux hang)");
|
|
9980
|
+
return { drained: 0, failed: 0 };
|
|
9981
|
+
}
|
|
9944
9982
|
const queue = readQueue();
|
|
9945
9983
|
if (queue.length === 0) return { drained: 0, failed: 0 };
|
|
9984
|
+
_draining = true;
|
|
9946
9985
|
const remaining = [];
|
|
9947
9986
|
let drained = 0;
|
|
9948
9987
|
let failed = 0;
|
|
9949
|
-
|
|
9950
|
-
const
|
|
9951
|
-
|
|
9952
|
-
|
|
9953
|
-
|
|
9954
|
-
|
|
9955
|
-
|
|
9956
|
-
|
|
9957
|
-
|
|
9958
|
-
|
|
9959
|
-
|
|
9960
|
-
|
|
9961
|
-
|
|
9962
|
-
|
|
9988
|
+
try {
|
|
9989
|
+
for (const item of queue) {
|
|
9990
|
+
const age = Date.now() - new Date(item.queuedAt).getTime();
|
|
9991
|
+
if (age > TTL_MS) {
|
|
9992
|
+
logQueue(`EXPIRED \u2192 ${item.targetSession} (${Math.round(age / 6e4)}min old, reason: ${item.reason})`);
|
|
9993
|
+
failed++;
|
|
9994
|
+
continue;
|
|
9995
|
+
}
|
|
9996
|
+
try {
|
|
9997
|
+
if (!isSessionBusy2(item.targetSession)) {
|
|
9998
|
+
const success = sendKeys(item.targetSession);
|
|
9999
|
+
if (success) {
|
|
10000
|
+
logQueue(`DRAINED \u2192 ${item.targetSession} (after ${item.attempts} retries)`);
|
|
10001
|
+
drained++;
|
|
10002
|
+
continue;
|
|
10003
|
+
}
|
|
9963
10004
|
}
|
|
10005
|
+
} catch {
|
|
9964
10006
|
}
|
|
9965
|
-
|
|
9966
|
-
|
|
9967
|
-
|
|
9968
|
-
|
|
9969
|
-
|
|
9970
|
-
|
|
9971
|
-
|
|
10007
|
+
item.attempts++;
|
|
10008
|
+
if (item.attempts >= MAX_RETRIES2) {
|
|
10009
|
+
logQueue(`FAILED \u2192 ${item.targetSession} (${MAX_RETRIES2} retries exhausted, reason: ${item.reason})`);
|
|
10010
|
+
failed++;
|
|
10011
|
+
continue;
|
|
10012
|
+
}
|
|
10013
|
+
remaining.push(item);
|
|
9972
10014
|
}
|
|
9973
|
-
remaining
|
|
10015
|
+
writeQueue(remaining);
|
|
10016
|
+
} finally {
|
|
10017
|
+
_draining = false;
|
|
9974
10018
|
}
|
|
9975
|
-
writeQueue(remaining);
|
|
9976
10019
|
return { drained, failed };
|
|
9977
10020
|
}
|
|
9978
10021
|
function drainForSession(targetSession, sendKeys) {
|
|
@@ -9997,6 +10040,9 @@ function clearQueueForAgent(agentName) {
|
|
|
9997
10040
|
logQueue(`CLEARED ${before - filtered.length} stale item(s) for ${agentName}`);
|
|
9998
10041
|
}
|
|
9999
10042
|
}
|
|
10043
|
+
function _resetDrainGuard() {
|
|
10044
|
+
_draining = false;
|
|
10045
|
+
}
|
|
10000
10046
|
function logQueue(msg) {
|
|
10001
10047
|
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] [queue] ${msg}
|
|
10002
10048
|
`;
|
|
@@ -10008,13 +10054,14 @@ function logQueue(msg) {
|
|
|
10008
10054
|
} catch {
|
|
10009
10055
|
}
|
|
10010
10056
|
}
|
|
10011
|
-
var QUEUE_PATH, MAX_RETRIES2, TTL_MS, INTERCOM_LOG;
|
|
10057
|
+
var QUEUE_PATH, MAX_RETRIES2, TTL_MS, _draining, INTERCOM_LOG;
|
|
10012
10058
|
var init_intercom_queue = __esm({
|
|
10013
10059
|
"src/lib/intercom-queue.ts"() {
|
|
10014
10060
|
"use strict";
|
|
10015
10061
|
QUEUE_PATH = path18.join(os8.homedir(), ".exe-os", "intercom-queue.json");
|
|
10016
10062
|
MAX_RETRIES2 = 5;
|
|
10017
10063
|
TTL_MS = 60 * 60 * 1e3;
|
|
10064
|
+
_draining = false;
|
|
10018
10065
|
INTERCOM_LOG = path18.join(os8.homedir(), ".exe-os", "intercom.log");
|
|
10019
10066
|
}
|
|
10020
10067
|
});
|
|
@@ -10649,7 +10696,13 @@ async function createReviewForCompletedTask(row, result3, _baseDir, now) {
|
|
|
10649
10696
|
taskFile
|
|
10650
10697
|
});
|
|
10651
10698
|
const originalPriority = String(row.priority).toLowerCase();
|
|
10652
|
-
const
|
|
10699
|
+
const resultLower = result3?.toLowerCase() ?? "";
|
|
10700
|
+
const hasTestEvidence = (
|
|
10701
|
+
// Vitest/Jest output patterns (hard to fake without actually running tests)
|
|
10702
|
+
/\d+\s+pass(ed|ing)/.test(resultLower) || /test files?\s+\d+\s+passed/.test(resultLower) || /tests?\s+\d+\s+passed/.test(resultLower)
|
|
10703
|
+
);
|
|
10704
|
+
const hasNoFailures = !/fail(ed|ure|ing)|error/i.test(resultLower);
|
|
10705
|
+
const autoApprove = originalPriority === "p2" && hasTestEvidence && hasNoFailures;
|
|
10653
10706
|
if (!autoApprove) {
|
|
10654
10707
|
try {
|
|
10655
10708
|
const key = getSessionKey();
|
|
@@ -10657,6 +10710,13 @@ async function createReviewForCompletedTask(row, result3, _baseDir, now) {
|
|
|
10657
10710
|
if (exeSession) {
|
|
10658
10711
|
sendIntercom(exeSession);
|
|
10659
10712
|
}
|
|
10713
|
+
if (reviewer && reviewer !== coordinatorName && reviewer !== exeSession) {
|
|
10714
|
+
const { employeeSessionName: employeeSessionName2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
10715
|
+
if (exeSession) {
|
|
10716
|
+
const reviewerSession = employeeSessionName2(reviewer, exeSession);
|
|
10717
|
+
sendIntercom(reviewerSession);
|
|
10718
|
+
}
|
|
10719
|
+
}
|
|
10660
10720
|
} catch {
|
|
10661
10721
|
}
|
|
10662
10722
|
}
|
|
@@ -10787,18 +10847,31 @@ function acquireSpawnLock2(sessionName) {
|
|
|
10787
10847
|
mkdirSync9(SPAWN_LOCK_DIR, { recursive: true });
|
|
10788
10848
|
}
|
|
10789
10849
|
const lockFile = spawnLockPath(sessionName);
|
|
10790
|
-
|
|
10791
|
-
|
|
10792
|
-
|
|
10793
|
-
|
|
10794
|
-
|
|
10795
|
-
|
|
10796
|
-
|
|
10797
|
-
|
|
10850
|
+
const lockData = JSON.stringify({ pid: process.pid, timestamp: Date.now() });
|
|
10851
|
+
const { openSync: openSync6, closeSync: closeSync6, writeSync } = __require("fs");
|
|
10852
|
+
const { constants: constants2 } = __require("fs");
|
|
10853
|
+
try {
|
|
10854
|
+
const fd = openSync6(lockFile, constants2.O_WRONLY | constants2.O_CREAT | constants2.O_EXCL, 420);
|
|
10855
|
+
writeSync(fd, lockData);
|
|
10856
|
+
closeSync6(fd);
|
|
10857
|
+
return true;
|
|
10858
|
+
} catch (err) {
|
|
10859
|
+
if (err?.code !== "EEXIST") {
|
|
10860
|
+
return true;
|
|
10798
10861
|
}
|
|
10799
10862
|
}
|
|
10800
|
-
|
|
10801
|
-
|
|
10863
|
+
try {
|
|
10864
|
+
const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
|
|
10865
|
+
const age = Date.now() - lock.timestamp;
|
|
10866
|
+
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
10867
|
+
return false;
|
|
10868
|
+
}
|
|
10869
|
+
writeFileSync10(lockFile, lockData);
|
|
10870
|
+
return true;
|
|
10871
|
+
} catch {
|
|
10872
|
+
writeFileSync10(lockFile, lockData);
|
|
10873
|
+
return true;
|
|
10874
|
+
}
|
|
10802
10875
|
}
|
|
10803
10876
|
function releaseSpawnLock2(sessionName) {
|
|
10804
10877
|
try {
|
|
@@ -10877,6 +10950,21 @@ function parseParentExe(sessionName, agentId) {
|
|
|
10877
10950
|
function extractRootExe(name) {
|
|
10878
10951
|
if (!name) return null;
|
|
10879
10952
|
if (!name.includes("-")) return name;
|
|
10953
|
+
try {
|
|
10954
|
+
const roster = (init_employees(), __toCommonJS(employees_exports)).loadEmployeesSync();
|
|
10955
|
+
if (roster.length > 0) {
|
|
10956
|
+
const sortedNames = roster.map((e) => e.name).sort((a, b) => b.length - a.length);
|
|
10957
|
+
for (const agentName of sortedNames) {
|
|
10958
|
+
const escaped = agentName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
10959
|
+
const regex = new RegExp(`^${escaped}\\d*-(.+)$`);
|
|
10960
|
+
const match = name.match(regex);
|
|
10961
|
+
if (match) {
|
|
10962
|
+
return extractRootExe(match[1]);
|
|
10963
|
+
}
|
|
10964
|
+
}
|
|
10965
|
+
}
|
|
10966
|
+
} catch {
|
|
10967
|
+
}
|
|
10880
10968
|
const parts = name.split("-").filter(Boolean);
|
|
10881
10969
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
10882
10970
|
}
|
|
@@ -10895,6 +10983,10 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
10895
10983
|
function getParentExe(sessionKey) {
|
|
10896
10984
|
try {
|
|
10897
10985
|
const data = JSON.parse(readFileSync12(path21.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
10986
|
+
if (data.registeredAt) {
|
|
10987
|
+
const age = Date.now() - new Date(data.registeredAt).getTime();
|
|
10988
|
+
if (age > PARENT_EXE_CACHE_TTL_MS) return null;
|
|
10989
|
+
}
|
|
10898
10990
|
return data.parentExe || null;
|
|
10899
10991
|
} catch {
|
|
10900
10992
|
return null;
|
|
@@ -11443,7 +11535,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
11443
11535
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
11444
11536
|
} catch {
|
|
11445
11537
|
}
|
|
11446
|
-
let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName}`;
|
|
11538
|
+
let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName} EXE_SESSION_START_ISO=${(/* @__PURE__ */ new Date()).toISOString()}`;
|
|
11447
11539
|
if (ccProvider !== DEFAULT_PROVIDER) {
|
|
11448
11540
|
const cfg = PROVIDER_TABLE[ccProvider];
|
|
11449
11541
|
if (cfg?.apiKeyEnv) {
|
|
@@ -11478,10 +11570,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
11478
11570
|
}
|
|
11479
11571
|
if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
|
|
11480
11572
|
if (agentRtConfig.runtime === "claude" && agentRtConfig.model) {
|
|
11481
|
-
|
|
11482
|
-
|
|
11483
|
-
ccModel += "[1m]";
|
|
11484
|
-
}
|
|
11573
|
+
const { normalizeCcModelName: normalizeCcModelName2 } = (init_agent_config(), __toCommonJS(agent_config_exports));
|
|
11574
|
+
const ccModel = normalizeCcModelName2(agentRtConfig.model);
|
|
11485
11575
|
envPrefix = `${envPrefix} ANTHROPIC_MODEL=${ccModel}`;
|
|
11486
11576
|
}
|
|
11487
11577
|
}
|
|
@@ -11582,7 +11672,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
11582
11672
|
releaseSpawnLock2(sessionName);
|
|
11583
11673
|
return { sessionName };
|
|
11584
11674
|
}
|
|
11585
|
-
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;
|
|
11675
|
+
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;
|
|
11586
11676
|
var init_tmux_routing = __esm({
|
|
11587
11677
|
"src/lib/tmux-routing.ts"() {
|
|
11588
11678
|
"use strict";
|
|
@@ -11602,6 +11692,7 @@ var init_tmux_routing = __esm({
|
|
|
11602
11692
|
SESSION_CACHE = path21.join(os10.homedir(), ".exe-os", "session-cache");
|
|
11603
11693
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
11604
11694
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
11695
|
+
PARENT_EXE_CACHE_TTL_MS = 4 * 60 * 60 * 1e3;
|
|
11605
11696
|
VERIFY_PANE_LINES = 200;
|
|
11606
11697
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
11607
11698
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
@@ -11646,6 +11737,17 @@ var init_task_scope = __esm({
|
|
|
11646
11737
|
});
|
|
11647
11738
|
|
|
11648
11739
|
// src/lib/notifications.ts
|
|
11740
|
+
var notifications_exports = {};
|
|
11741
|
+
__export(notifications_exports, {
|
|
11742
|
+
cleanupOldNotifications: () => cleanupOldNotifications,
|
|
11743
|
+
formatNotifications: () => formatNotifications,
|
|
11744
|
+
markAsRead: () => markAsRead,
|
|
11745
|
+
markAsReadByTaskFile: () => markAsReadByTaskFile,
|
|
11746
|
+
markDoneTaskNotificationsAsRead: () => markDoneTaskNotificationsAsRead,
|
|
11747
|
+
migrateJsonNotifications: () => migrateJsonNotifications,
|
|
11748
|
+
readUnreadNotifications: () => readUnreadNotifications,
|
|
11749
|
+
writeNotification: () => writeNotification
|
|
11750
|
+
});
|
|
11649
11751
|
import crypto6 from "crypto";
|
|
11650
11752
|
import path22 from "path";
|
|
11651
11753
|
import os11 from "os";
|
|
@@ -11682,6 +11784,52 @@ async function writeNotification(notification) {
|
|
|
11682
11784
|
`);
|
|
11683
11785
|
}
|
|
11684
11786
|
}
|
|
11787
|
+
async function readUnreadNotifications(agentFilter2, sessionScope) {
|
|
11788
|
+
try {
|
|
11789
|
+
const client = getClient();
|
|
11790
|
+
const conditions = ["read = 0"];
|
|
11791
|
+
const args = [];
|
|
11792
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
11793
|
+
if (agentFilter2) {
|
|
11794
|
+
conditions.push("agent_id = ?");
|
|
11795
|
+
args.push(agentFilter2);
|
|
11796
|
+
}
|
|
11797
|
+
const result3 = await client.execute({
|
|
11798
|
+
sql: `SELECT id, agent_id, agent_role, event, project, summary, task_file, session_scope, created_at
|
|
11799
|
+
FROM notifications
|
|
11800
|
+
WHERE ${conditions.join(" AND ")}${scope.sql}
|
|
11801
|
+
ORDER BY created_at ASC`,
|
|
11802
|
+
args: [...args, ...scope.args]
|
|
11803
|
+
});
|
|
11804
|
+
return result3.rows.map((r) => ({
|
|
11805
|
+
id: String(r.id),
|
|
11806
|
+
agentId: String(r.agent_id),
|
|
11807
|
+
agentRole: String(r.agent_role),
|
|
11808
|
+
event: String(r.event),
|
|
11809
|
+
project: String(r.project),
|
|
11810
|
+
summary: String(r.summary),
|
|
11811
|
+
taskFile: r.task_file ? String(r.task_file) : void 0,
|
|
11812
|
+
sessionScope: r.session_scope == null ? null : String(r.session_scope),
|
|
11813
|
+
timestamp: String(r.created_at),
|
|
11814
|
+
read: false
|
|
11815
|
+
}));
|
|
11816
|
+
} catch {
|
|
11817
|
+
return [];
|
|
11818
|
+
}
|
|
11819
|
+
}
|
|
11820
|
+
async function markAsRead(ids, sessionScope) {
|
|
11821
|
+
if (ids.length === 0) return;
|
|
11822
|
+
try {
|
|
11823
|
+
const client = getClient();
|
|
11824
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
11825
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
11826
|
+
await client.execute({
|
|
11827
|
+
sql: `UPDATE notifications SET read = 1 WHERE id IN (${placeholders})${scope.sql}`,
|
|
11828
|
+
args: [...ids, ...scope.args]
|
|
11829
|
+
});
|
|
11830
|
+
} catch {
|
|
11831
|
+
}
|
|
11832
|
+
}
|
|
11685
11833
|
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
11686
11834
|
try {
|
|
11687
11835
|
const client = getClient();
|
|
@@ -11694,11 +11842,144 @@ async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
|
11694
11842
|
} catch {
|
|
11695
11843
|
}
|
|
11696
11844
|
}
|
|
11845
|
+
async function cleanupOldNotifications(daysOld = CLEANUP_DAYS, sessionScope) {
|
|
11846
|
+
try {
|
|
11847
|
+
const client = getClient();
|
|
11848
|
+
const cutoff = new Date(
|
|
11849
|
+
Date.now() - daysOld * 24 * 60 * 60 * 1e3
|
|
11850
|
+
).toISOString();
|
|
11851
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
11852
|
+
const result3 = await client.execute({
|
|
11853
|
+
sql: `DELETE FROM notifications WHERE created_at < ?${scope.sql}`,
|
|
11854
|
+
args: [cutoff, ...scope.args]
|
|
11855
|
+
});
|
|
11856
|
+
return result3.rowsAffected;
|
|
11857
|
+
} catch {
|
|
11858
|
+
return 0;
|
|
11859
|
+
}
|
|
11860
|
+
}
|
|
11861
|
+
async function markDoneTaskNotificationsAsRead(sessionScope) {
|
|
11862
|
+
try {
|
|
11863
|
+
const client = getClient();
|
|
11864
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
11865
|
+
const result3 = await client.execute({
|
|
11866
|
+
sql: `UPDATE notifications SET read = 1
|
|
11867
|
+
WHERE read = 0
|
|
11868
|
+
AND task_file IS NOT NULL
|
|
11869
|
+
${scope.sql}
|
|
11870
|
+
AND task_file IN (
|
|
11871
|
+
SELECT task_file FROM tasks WHERE status = 'done'${scope.sql}
|
|
11872
|
+
)`,
|
|
11873
|
+
args: [...scope.args, ...scope.args]
|
|
11874
|
+
});
|
|
11875
|
+
return result3.rowsAffected;
|
|
11876
|
+
} catch {
|
|
11877
|
+
return 0;
|
|
11878
|
+
}
|
|
11879
|
+
}
|
|
11880
|
+
function formatNotifications(notifications) {
|
|
11881
|
+
if (notifications.length === 0) return "";
|
|
11882
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
11883
|
+
for (const n of notifications) {
|
|
11884
|
+
const key = `${n.agentId}|${n.agentRole}`;
|
|
11885
|
+
if (!grouped.has(key)) grouped.set(key, []);
|
|
11886
|
+
grouped.get(key).push(n);
|
|
11887
|
+
}
|
|
11888
|
+
const lines = [];
|
|
11889
|
+
lines.push(`## Notifications (${notifications.length} unread)
|
|
11890
|
+
`);
|
|
11891
|
+
for (const [key, items] of grouped) {
|
|
11892
|
+
const [agentId, agentRole] = key.split("|");
|
|
11893
|
+
lines.push(`**${agentId}** (${agentRole}):`);
|
|
11894
|
+
for (const item of items) {
|
|
11895
|
+
const ago = formatTimeAgo(item.timestamp);
|
|
11896
|
+
const icon = eventIcon(item.event);
|
|
11897
|
+
lines.push(`- ${icon} ${item.summary} (${item.project}) \u2014 ${ago}`);
|
|
11898
|
+
}
|
|
11899
|
+
lines.push("");
|
|
11900
|
+
}
|
|
11901
|
+
return lines.join("\n");
|
|
11902
|
+
}
|
|
11903
|
+
async function migrateJsonNotifications() {
|
|
11904
|
+
const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR || path22.join(os11.homedir(), ".exe-os");
|
|
11905
|
+
const notifDir = path22.join(base, "notifications");
|
|
11906
|
+
if (!existsSync19(notifDir)) return 0;
|
|
11907
|
+
let migrated = 0;
|
|
11908
|
+
try {
|
|
11909
|
+
const files = readdirSync6(notifDir).filter((f) => f.endsWith(".json"));
|
|
11910
|
+
if (files.length === 0) return 0;
|
|
11911
|
+
const client = getClient();
|
|
11912
|
+
for (const file of files) {
|
|
11913
|
+
try {
|
|
11914
|
+
const filePath = path22.join(notifDir, file);
|
|
11915
|
+
const data = JSON.parse(readFileSync13(filePath, "utf8"));
|
|
11916
|
+
await client.execute({
|
|
11917
|
+
sql: `INSERT OR IGNORE INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
11918
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
11919
|
+
args: [
|
|
11920
|
+
crypto6.randomUUID(),
|
|
11921
|
+
data.agentId ?? "unknown",
|
|
11922
|
+
data.agentRole ?? "unknown",
|
|
11923
|
+
data.event ?? "session_summary",
|
|
11924
|
+
data.project ?? "unknown",
|
|
11925
|
+
data.summary ?? "",
|
|
11926
|
+
data.taskFile ?? null,
|
|
11927
|
+
null,
|
|
11928
|
+
data.read ? 1 : 0,
|
|
11929
|
+
data.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
11930
|
+
]
|
|
11931
|
+
});
|
|
11932
|
+
unlinkSync7(filePath);
|
|
11933
|
+
migrated++;
|
|
11934
|
+
} catch {
|
|
11935
|
+
}
|
|
11936
|
+
}
|
|
11937
|
+
try {
|
|
11938
|
+
const remaining = readdirSync6(notifDir);
|
|
11939
|
+
if (remaining.length === 0) {
|
|
11940
|
+
rmdirSync(notifDir);
|
|
11941
|
+
}
|
|
11942
|
+
} catch {
|
|
11943
|
+
}
|
|
11944
|
+
} catch {
|
|
11945
|
+
}
|
|
11946
|
+
return migrated;
|
|
11947
|
+
}
|
|
11948
|
+
function eventIcon(event) {
|
|
11949
|
+
switch (event) {
|
|
11950
|
+
case "task_complete":
|
|
11951
|
+
return "Completed:";
|
|
11952
|
+
case "task_needs_fix":
|
|
11953
|
+
return "Needs fix:";
|
|
11954
|
+
case "session_summary":
|
|
11955
|
+
return "Session:";
|
|
11956
|
+
case "error_spike":
|
|
11957
|
+
return "Errors:";
|
|
11958
|
+
case "orphan_task":
|
|
11959
|
+
return "Orphan:";
|
|
11960
|
+
case "subtasks_complete":
|
|
11961
|
+
return "Subtasks done:";
|
|
11962
|
+
case "capacity_relaunch":
|
|
11963
|
+
return "Relaunched:";
|
|
11964
|
+
}
|
|
11965
|
+
}
|
|
11966
|
+
function formatTimeAgo(timestamp) {
|
|
11967
|
+
const diffMs = Date.now() - new Date(timestamp).getTime();
|
|
11968
|
+
const mins = Math.floor(diffMs / 6e4);
|
|
11969
|
+
if (mins < 1) return "just now";
|
|
11970
|
+
if (mins < 60) return `${mins}m ago`;
|
|
11971
|
+
const hours = Math.floor(mins / 60);
|
|
11972
|
+
if (hours < 24) return `${hours}h ago`;
|
|
11973
|
+
const days = Math.floor(hours / 24);
|
|
11974
|
+
return `${days}d ago`;
|
|
11975
|
+
}
|
|
11976
|
+
var CLEANUP_DAYS;
|
|
11697
11977
|
var init_notifications = __esm({
|
|
11698
11978
|
"src/lib/notifications.ts"() {
|
|
11699
11979
|
"use strict";
|
|
11700
11980
|
init_database();
|
|
11701
11981
|
init_task_scope();
|
|
11982
|
+
CLEANUP_DAYS = 7;
|
|
11702
11983
|
}
|
|
11703
11984
|
});
|
|
11704
11985
|
|
|
@@ -13068,6 +13349,20 @@ async function updateTask(input) {
|
|
|
13068
13349
|
notifyTaskDone();
|
|
13069
13350
|
}
|
|
13070
13351
|
await markTaskNotificationsRead(taskFile);
|
|
13352
|
+
if (input.status === "needs_review" && !isCoordinator) {
|
|
13353
|
+
try {
|
|
13354
|
+
const { writeNotification: writeNotification2 } = await Promise.resolve().then(() => (init_notifications(), notifications_exports));
|
|
13355
|
+
await writeNotification2({
|
|
13356
|
+
agentId: String(row.assigned_to),
|
|
13357
|
+
agentRole: String(row.assigned_to),
|
|
13358
|
+
event: "task_complete",
|
|
13359
|
+
project: String(row.project_name),
|
|
13360
|
+
summary: `"${String(row.title)}" is ready for review`,
|
|
13361
|
+
taskFile
|
|
13362
|
+
});
|
|
13363
|
+
} catch {
|
|
13364
|
+
}
|
|
13365
|
+
}
|
|
13071
13366
|
if (input.status === "done" || input.status === "closed") {
|
|
13072
13367
|
try {
|
|
13073
13368
|
await cascadeUnblock(taskId, input.baseDir, now);
|
|
@@ -14072,7 +14367,21 @@ function tryAcquireWorkerSlot() {
|
|
|
14072
14367
|
for (const f of files) {
|
|
14073
14368
|
if (!f.endsWith(".pid")) continue;
|
|
14074
14369
|
if (f.startsWith("res-")) {
|
|
14075
|
-
|
|
14370
|
+
const resParts = f.replace(".pid", "").split("-");
|
|
14371
|
+
const resPid = parseInt(resParts[1] ?? "", 10);
|
|
14372
|
+
if (!isNaN(resPid) && resPid > 0) {
|
|
14373
|
+
try {
|
|
14374
|
+
process.kill(resPid, 0);
|
|
14375
|
+
alive++;
|
|
14376
|
+
} catch {
|
|
14377
|
+
try {
|
|
14378
|
+
unlinkSync9(path31.join(WORKER_PID_DIR, f));
|
|
14379
|
+
} catch {
|
|
14380
|
+
}
|
|
14381
|
+
}
|
|
14382
|
+
} else {
|
|
14383
|
+
alive++;
|
|
14384
|
+
}
|
|
14076
14385
|
continue;
|
|
14077
14386
|
}
|
|
14078
14387
|
const dashIdx = f.lastIndexOf("-");
|
|
@@ -20871,6 +21180,7 @@ var IDLE_KILL_INTERCOM_ACK_WINDOW_MS = Number(process.env.EXE_INTERCOM_ACK_WINDO
|
|
|
20871
21180
|
var NUDGE_STATE_PATH = join4(homedir6(), ".exe-os", "review-nudge-state.json");
|
|
20872
21181
|
var AUTO_WAKE_COOLDOWN_MS = 5 * 60 * 1e3;
|
|
20873
21182
|
var AUTO_WAKE_MAX_RETRIES = 3;
|
|
21183
|
+
var STUCK_TASK_GRACE_MS = 5 * 60 * 1e3;
|
|
20874
21184
|
|
|
20875
21185
|
// src/mcp/tools/get-auto-wake-status.ts
|
|
20876
21186
|
init_employees();
|
|
@@ -22569,6 +22879,15 @@ async function fetchWithRetry(url, init) {
|
|
|
22569
22879
|
}
|
|
22570
22880
|
throw lastError;
|
|
22571
22881
|
}
|
|
22882
|
+
function migrateEndpoint(endpoint2) {
|
|
22883
|
+
if (endpoint2 === "https://askexe.com/cloud" || endpoint2 === "https://askexe.com/cloud/") {
|
|
22884
|
+
process.stderr.write(
|
|
22885
|
+
"[cloud-sync] Auto-migrating endpoint from askexe.com/cloud to cloud.askexe.com (bypasses Cloudflare WAF for datacenter IPs)\n"
|
|
22886
|
+
);
|
|
22887
|
+
return "https://cloud.askexe.com";
|
|
22888
|
+
}
|
|
22889
|
+
return endpoint2;
|
|
22890
|
+
}
|
|
22572
22891
|
function assertSecureEndpoint(endpoint2) {
|
|
22573
22892
|
if (endpoint2.startsWith("https://")) return;
|
|
22574
22893
|
if (endpoint2.startsWith("http://")) {
|
|
@@ -22690,6 +23009,7 @@ async function markCloudReuploadRequired(client = getClient()) {
|
|
|
22690
23009
|
await client.execute("INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('cloud_reupload_required', '1')");
|
|
22691
23010
|
}
|
|
22692
23011
|
async function cloudSync(config2) {
|
|
23012
|
+
config2 = { ...config2, endpoint: migrateEndpoint(config2.endpoint) };
|
|
22693
23013
|
if (!isSyncCryptoInitialized()) {
|
|
22694
23014
|
try {
|
|
22695
23015
|
const { getMasterKey: getMasterKey2 } = await Promise.resolve().then(() => (init_keychain(), keychain_exports));
|