@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/gateway/index.js
CHANGED
|
@@ -520,6 +520,17 @@ function normalizeOrchestration(raw) {
|
|
|
520
520
|
const userOrg = raw.orchestration ?? {};
|
|
521
521
|
raw.orchestration = { ...defaultOrg, ...userOrg };
|
|
522
522
|
}
|
|
523
|
+
function normalizeCloudEndpoint(raw) {
|
|
524
|
+
const cloud = raw.cloud;
|
|
525
|
+
if (!cloud?.endpoint) return;
|
|
526
|
+
const ep = String(cloud.endpoint);
|
|
527
|
+
if (ep === "https://askexe.com/cloud" || ep === "https://askexe.com/cloud/") {
|
|
528
|
+
cloud.endpoint = "https://cloud.askexe.com";
|
|
529
|
+
process.stderr.write(
|
|
530
|
+
"[config] Auto-migrated cloud endpoint: askexe.com/cloud \u2192 cloud.askexe.com\n"
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
523
534
|
async function loadConfig() {
|
|
524
535
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
525
536
|
await ensurePrivateDir(dir);
|
|
@@ -545,6 +556,7 @@ async function loadConfig() {
|
|
|
545
556
|
normalizeSessionLifecycle(migratedCfg);
|
|
546
557
|
normalizeAutoUpdate(migratedCfg);
|
|
547
558
|
normalizeOrchestration(migratedCfg);
|
|
559
|
+
normalizeCloudEndpoint(migratedCfg);
|
|
548
560
|
const config2 = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
|
|
549
561
|
if (config2.dbPath.startsWith("~")) {
|
|
550
562
|
config2.dbPath = config2.dbPath.replace(/^~/, os.homedir());
|
|
@@ -573,6 +585,7 @@ function loadConfigSync() {
|
|
|
573
585
|
normalizeSessionLifecycle(migratedCfg);
|
|
574
586
|
normalizeAutoUpdate(migratedCfg);
|
|
575
587
|
normalizeOrchestration(migratedCfg);
|
|
588
|
+
normalizeCloudEndpoint(migratedCfg);
|
|
576
589
|
const config2 = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
|
|
577
590
|
if (config2.dbPath.startsWith("~")) {
|
|
578
591
|
config2.dbPath = config2.dbPath.replace(/^~/, os.homedir());
|
|
@@ -733,6 +746,7 @@ __export(agent_config_exports, {
|
|
|
733
746
|
clearAgentRuntime: () => clearAgentRuntime,
|
|
734
747
|
getAgentRuntime: () => getAgentRuntime,
|
|
735
748
|
loadAgentConfig: () => loadAgentConfig,
|
|
749
|
+
normalizeCcModelName: () => normalizeCcModelName,
|
|
736
750
|
saveAgentConfig: () => saveAgentConfig,
|
|
737
751
|
setAgentMcps: () => setAgentMcps,
|
|
738
752
|
setAgentRuntime: () => setAgentRuntime
|
|
@@ -761,6 +775,13 @@ function getAgentRuntime(agentId) {
|
|
|
761
775
|
if (orgDefault) return orgDefault;
|
|
762
776
|
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
763
777
|
}
|
|
778
|
+
function normalizeCcModelName(model) {
|
|
779
|
+
let ccModel = model.replace(/(\d+)\.(\d+)/g, "$1-$2");
|
|
780
|
+
if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
|
|
781
|
+
ccModel += "[1m]";
|
|
782
|
+
}
|
|
783
|
+
return ccModel;
|
|
784
|
+
}
|
|
764
785
|
function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
|
|
765
786
|
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
766
787
|
if (!knownModels) {
|
|
@@ -4054,7 +4075,7 @@ var init_embedder = __esm({
|
|
|
4054
4075
|
});
|
|
4055
4076
|
|
|
4056
4077
|
// src/lib/keychain.ts
|
|
4057
|
-
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
4078
|
+
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2, rename, copyFile } from "fs/promises";
|
|
4058
4079
|
import { existsSync as existsSync8, statSync as statSync3 } from "fs";
|
|
4059
4080
|
import { execSync as execSync3 } from "child_process";
|
|
4060
4081
|
import path7 from "path";
|
|
@@ -4089,12 +4110,14 @@ function linuxSecretAvailable() {
|
|
|
4089
4110
|
function isRootOnlyTrustedServerKeyFile(keyPath) {
|
|
4090
4111
|
if (process.platform !== "linux") return false;
|
|
4091
4112
|
try {
|
|
4092
|
-
const uid = typeof os5.userInfo().uid === "number" ? os5.userInfo().uid : -1;
|
|
4093
4113
|
const st = statSync3(keyPath);
|
|
4094
4114
|
if (!st.isFile() || (st.mode & 63) !== 0) return false;
|
|
4115
|
+
const uid = typeof os5.userInfo().uid === "number" ? os5.userInfo().uid : -1;
|
|
4095
4116
|
if (uid === 0) return true;
|
|
4096
4117
|
const exeOsDir = process.env.EXE_OS_DIR;
|
|
4097
|
-
|
|
4118
|
+
if (exeOsDir && path7.resolve(keyPath).startsWith(path7.resolve(exeOsDir) + path7.sep)) return true;
|
|
4119
|
+
if (!linuxSecretAvailable()) return true;
|
|
4120
|
+
return false;
|
|
4098
4121
|
} catch {
|
|
4099
4122
|
return false;
|
|
4100
4123
|
}
|
|
@@ -4244,15 +4267,25 @@ async function writeMachineBoundFileFallback(b64) {
|
|
|
4244
4267
|
await mkdir3(dir, { recursive: true });
|
|
4245
4268
|
const keyPath = getKeyPath();
|
|
4246
4269
|
const machineKey = deriveMachineKey();
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4270
|
+
const content = machineKey ? encryptWithMachineKey(b64, machineKey) + "\n" : b64 + "\n";
|
|
4271
|
+
const result = machineKey ? "encrypted" : "plaintext";
|
|
4272
|
+
const tmpPath = keyPath + ".tmp";
|
|
4273
|
+
try {
|
|
4274
|
+
if (existsSync8(keyPath)) {
|
|
4275
|
+
await copyFile(keyPath, keyPath + ".bak").catch(() => {
|
|
4276
|
+
});
|
|
4277
|
+
}
|
|
4278
|
+
await writeFile3(tmpPath, content, "utf-8");
|
|
4279
|
+
await chmod2(tmpPath, 384);
|
|
4280
|
+
await rename(tmpPath, keyPath);
|
|
4281
|
+
} catch (err) {
|
|
4282
|
+
try {
|
|
4283
|
+
await unlink(tmpPath);
|
|
4284
|
+
} catch {
|
|
4285
|
+
}
|
|
4286
|
+
throw err;
|
|
4252
4287
|
}
|
|
4253
|
-
|
|
4254
|
-
await chmod2(keyPath, 384);
|
|
4255
|
-
return "plaintext";
|
|
4288
|
+
return result;
|
|
4256
4289
|
}
|
|
4257
4290
|
async function getMasterKey() {
|
|
4258
4291
|
let nativeValue = macKeychainGet() ?? linuxSecretGet();
|
|
@@ -4319,7 +4352,7 @@ async function getMasterKey() {
|
|
|
4319
4352
|
b64Value = content;
|
|
4320
4353
|
}
|
|
4321
4354
|
const key = Buffer.from(b64Value, "base64");
|
|
4322
|
-
if (
|
|
4355
|
+
if (isRootOnlyTrustedServerKeyFile(keyPath)) {
|
|
4323
4356
|
return key;
|
|
4324
4357
|
}
|
|
4325
4358
|
const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
|
|
@@ -7989,6 +8022,7 @@ var init_provider_table = __esm({
|
|
|
7989
8022
|
// src/lib/intercom-queue.ts
|
|
7990
8023
|
var intercom_queue_exports = {};
|
|
7991
8024
|
__export(intercom_queue_exports, {
|
|
8025
|
+
_resetDrainGuard: () => _resetDrainGuard,
|
|
7992
8026
|
clearQueueForAgent: () => clearQueueForAgent,
|
|
7993
8027
|
drainForSession: () => drainForSession,
|
|
7994
8028
|
drainQueue: () => drainQueue,
|
|
@@ -8034,38 +8068,47 @@ function queueIntercom(targetSession, reason) {
|
|
|
8034
8068
|
writeQueue(queue);
|
|
8035
8069
|
}
|
|
8036
8070
|
function drainQueue(isSessionBusy2, sendKeys) {
|
|
8071
|
+
if (_draining) {
|
|
8072
|
+
logQueue("SKIP_DRAIN \u2014 previous drain still running (possible tmux hang)");
|
|
8073
|
+
return { drained: 0, failed: 0 };
|
|
8074
|
+
}
|
|
8037
8075
|
const queue = readQueue();
|
|
8038
8076
|
if (queue.length === 0) return { drained: 0, failed: 0 };
|
|
8077
|
+
_draining = true;
|
|
8039
8078
|
const remaining = [];
|
|
8040
8079
|
let drained = 0;
|
|
8041
8080
|
let failed = 0;
|
|
8042
|
-
|
|
8043
|
-
const
|
|
8044
|
-
|
|
8045
|
-
|
|
8046
|
-
|
|
8047
|
-
|
|
8048
|
-
|
|
8049
|
-
|
|
8050
|
-
|
|
8051
|
-
|
|
8052
|
-
|
|
8053
|
-
|
|
8054
|
-
|
|
8055
|
-
|
|
8081
|
+
try {
|
|
8082
|
+
for (const item of queue) {
|
|
8083
|
+
const age = Date.now() - new Date(item.queuedAt).getTime();
|
|
8084
|
+
if (age > TTL_MS) {
|
|
8085
|
+
logQueue(`EXPIRED \u2192 ${item.targetSession} (${Math.round(age / 6e4)}min old, reason: ${item.reason})`);
|
|
8086
|
+
failed++;
|
|
8087
|
+
continue;
|
|
8088
|
+
}
|
|
8089
|
+
try {
|
|
8090
|
+
if (!isSessionBusy2(item.targetSession)) {
|
|
8091
|
+
const success = sendKeys(item.targetSession);
|
|
8092
|
+
if (success) {
|
|
8093
|
+
logQueue(`DRAINED \u2192 ${item.targetSession} (after ${item.attempts} retries)`);
|
|
8094
|
+
drained++;
|
|
8095
|
+
continue;
|
|
8096
|
+
}
|
|
8056
8097
|
}
|
|
8098
|
+
} catch {
|
|
8057
8099
|
}
|
|
8058
|
-
|
|
8059
|
-
|
|
8060
|
-
|
|
8061
|
-
|
|
8062
|
-
|
|
8063
|
-
|
|
8064
|
-
|
|
8100
|
+
item.attempts++;
|
|
8101
|
+
if (item.attempts >= MAX_RETRIES2) {
|
|
8102
|
+
logQueue(`FAILED \u2192 ${item.targetSession} (${MAX_RETRIES2} retries exhausted, reason: ${item.reason})`);
|
|
8103
|
+
failed++;
|
|
8104
|
+
continue;
|
|
8105
|
+
}
|
|
8106
|
+
remaining.push(item);
|
|
8065
8107
|
}
|
|
8066
|
-
remaining
|
|
8108
|
+
writeQueue(remaining);
|
|
8109
|
+
} finally {
|
|
8110
|
+
_draining = false;
|
|
8067
8111
|
}
|
|
8068
|
-
writeQueue(remaining);
|
|
8069
8112
|
return { drained, failed };
|
|
8070
8113
|
}
|
|
8071
8114
|
function drainForSession(targetSession, sendKeys) {
|
|
@@ -8090,6 +8133,9 @@ function clearQueueForAgent(agentName) {
|
|
|
8090
8133
|
logQueue(`CLEARED ${before - filtered.length} stale item(s) for ${agentName}`);
|
|
8091
8134
|
}
|
|
8092
8135
|
}
|
|
8136
|
+
function _resetDrainGuard() {
|
|
8137
|
+
_draining = false;
|
|
8138
|
+
}
|
|
8093
8139
|
function logQueue(msg) {
|
|
8094
8140
|
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] [queue] ${msg}
|
|
8095
8141
|
`;
|
|
@@ -8101,13 +8147,14 @@ function logQueue(msg) {
|
|
|
8101
8147
|
} catch {
|
|
8102
8148
|
}
|
|
8103
8149
|
}
|
|
8104
|
-
var QUEUE_PATH, MAX_RETRIES2, TTL_MS, INTERCOM_LOG;
|
|
8150
|
+
var QUEUE_PATH, MAX_RETRIES2, TTL_MS, _draining, INTERCOM_LOG;
|
|
8105
8151
|
var init_intercom_queue = __esm({
|
|
8106
8152
|
"src/lib/intercom-queue.ts"() {
|
|
8107
8153
|
"use strict";
|
|
8108
8154
|
QUEUE_PATH = path11.join(os8.homedir(), ".exe-os", "intercom-queue.json");
|
|
8109
8155
|
MAX_RETRIES2 = 5;
|
|
8110
8156
|
TTL_MS = 60 * 60 * 1e3;
|
|
8157
|
+
_draining = false;
|
|
8111
8158
|
INTERCOM_LOG = path11.join(os8.homedir(), ".exe-os", "intercom.log");
|
|
8112
8159
|
}
|
|
8113
8160
|
});
|
|
@@ -8755,6 +8802,17 @@ var init_task_scope = __esm({
|
|
|
8755
8802
|
});
|
|
8756
8803
|
|
|
8757
8804
|
// src/lib/notifications.ts
|
|
8805
|
+
var notifications_exports = {};
|
|
8806
|
+
__export(notifications_exports, {
|
|
8807
|
+
cleanupOldNotifications: () => cleanupOldNotifications,
|
|
8808
|
+
formatNotifications: () => formatNotifications,
|
|
8809
|
+
markAsRead: () => markAsRead,
|
|
8810
|
+
markAsReadByTaskFile: () => markAsReadByTaskFile,
|
|
8811
|
+
markDoneTaskNotificationsAsRead: () => markDoneTaskNotificationsAsRead,
|
|
8812
|
+
migrateJsonNotifications: () => migrateJsonNotifications,
|
|
8813
|
+
readUnreadNotifications: () => readUnreadNotifications,
|
|
8814
|
+
writeNotification: () => writeNotification
|
|
8815
|
+
});
|
|
8758
8816
|
import crypto4 from "crypto";
|
|
8759
8817
|
import path15 from "path";
|
|
8760
8818
|
import os11 from "os";
|
|
@@ -8791,6 +8849,52 @@ async function writeNotification(notification) {
|
|
|
8791
8849
|
`);
|
|
8792
8850
|
}
|
|
8793
8851
|
}
|
|
8852
|
+
async function readUnreadNotifications(agentFilter, sessionScope) {
|
|
8853
|
+
try {
|
|
8854
|
+
const client = getClient();
|
|
8855
|
+
const conditions = ["read = 0"];
|
|
8856
|
+
const args = [];
|
|
8857
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
8858
|
+
if (agentFilter) {
|
|
8859
|
+
conditions.push("agent_id = ?");
|
|
8860
|
+
args.push(agentFilter);
|
|
8861
|
+
}
|
|
8862
|
+
const result = await client.execute({
|
|
8863
|
+
sql: `SELECT id, agent_id, agent_role, event, project, summary, task_file, session_scope, created_at
|
|
8864
|
+
FROM notifications
|
|
8865
|
+
WHERE ${conditions.join(" AND ")}${scope.sql}
|
|
8866
|
+
ORDER BY created_at ASC`,
|
|
8867
|
+
args: [...args, ...scope.args]
|
|
8868
|
+
});
|
|
8869
|
+
return result.rows.map((r) => ({
|
|
8870
|
+
id: String(r.id),
|
|
8871
|
+
agentId: String(r.agent_id),
|
|
8872
|
+
agentRole: String(r.agent_role),
|
|
8873
|
+
event: String(r.event),
|
|
8874
|
+
project: String(r.project),
|
|
8875
|
+
summary: String(r.summary),
|
|
8876
|
+
taskFile: r.task_file ? String(r.task_file) : void 0,
|
|
8877
|
+
sessionScope: r.session_scope == null ? null : String(r.session_scope),
|
|
8878
|
+
timestamp: String(r.created_at),
|
|
8879
|
+
read: false
|
|
8880
|
+
}));
|
|
8881
|
+
} catch {
|
|
8882
|
+
return [];
|
|
8883
|
+
}
|
|
8884
|
+
}
|
|
8885
|
+
async function markAsRead(ids, sessionScope) {
|
|
8886
|
+
if (ids.length === 0) return;
|
|
8887
|
+
try {
|
|
8888
|
+
const client = getClient();
|
|
8889
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
8890
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
8891
|
+
await client.execute({
|
|
8892
|
+
sql: `UPDATE notifications SET read = 1 WHERE id IN (${placeholders})${scope.sql}`,
|
|
8893
|
+
args: [...ids, ...scope.args]
|
|
8894
|
+
});
|
|
8895
|
+
} catch {
|
|
8896
|
+
}
|
|
8897
|
+
}
|
|
8794
8898
|
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
8795
8899
|
try {
|
|
8796
8900
|
const client = getClient();
|
|
@@ -8803,11 +8907,144 @@ async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
|
8803
8907
|
} catch {
|
|
8804
8908
|
}
|
|
8805
8909
|
}
|
|
8910
|
+
async function cleanupOldNotifications(daysOld = CLEANUP_DAYS, sessionScope) {
|
|
8911
|
+
try {
|
|
8912
|
+
const client = getClient();
|
|
8913
|
+
const cutoff = new Date(
|
|
8914
|
+
Date.now() - daysOld * 24 * 60 * 60 * 1e3
|
|
8915
|
+
).toISOString();
|
|
8916
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
8917
|
+
const result = await client.execute({
|
|
8918
|
+
sql: `DELETE FROM notifications WHERE created_at < ?${scope.sql}`,
|
|
8919
|
+
args: [cutoff, ...scope.args]
|
|
8920
|
+
});
|
|
8921
|
+
return result.rowsAffected;
|
|
8922
|
+
} catch {
|
|
8923
|
+
return 0;
|
|
8924
|
+
}
|
|
8925
|
+
}
|
|
8926
|
+
async function markDoneTaskNotificationsAsRead(sessionScope) {
|
|
8927
|
+
try {
|
|
8928
|
+
const client = getClient();
|
|
8929
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
8930
|
+
const result = await client.execute({
|
|
8931
|
+
sql: `UPDATE notifications SET read = 1
|
|
8932
|
+
WHERE read = 0
|
|
8933
|
+
AND task_file IS NOT NULL
|
|
8934
|
+
${scope.sql}
|
|
8935
|
+
AND task_file IN (
|
|
8936
|
+
SELECT task_file FROM tasks WHERE status = 'done'${scope.sql}
|
|
8937
|
+
)`,
|
|
8938
|
+
args: [...scope.args, ...scope.args]
|
|
8939
|
+
});
|
|
8940
|
+
return result.rowsAffected;
|
|
8941
|
+
} catch {
|
|
8942
|
+
return 0;
|
|
8943
|
+
}
|
|
8944
|
+
}
|
|
8945
|
+
function formatNotifications(notifications) {
|
|
8946
|
+
if (notifications.length === 0) return "";
|
|
8947
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
8948
|
+
for (const n of notifications) {
|
|
8949
|
+
const key = `${n.agentId}|${n.agentRole}`;
|
|
8950
|
+
if (!grouped.has(key)) grouped.set(key, []);
|
|
8951
|
+
grouped.get(key).push(n);
|
|
8952
|
+
}
|
|
8953
|
+
const lines = [];
|
|
8954
|
+
lines.push(`## Notifications (${notifications.length} unread)
|
|
8955
|
+
`);
|
|
8956
|
+
for (const [key, items] of grouped) {
|
|
8957
|
+
const [agentId, agentRole] = key.split("|");
|
|
8958
|
+
lines.push(`**${agentId}** (${agentRole}):`);
|
|
8959
|
+
for (const item of items) {
|
|
8960
|
+
const ago = formatTimeAgo(item.timestamp);
|
|
8961
|
+
const icon = eventIcon(item.event);
|
|
8962
|
+
lines.push(`- ${icon} ${item.summary} (${item.project}) \u2014 ${ago}`);
|
|
8963
|
+
}
|
|
8964
|
+
lines.push("");
|
|
8965
|
+
}
|
|
8966
|
+
return lines.join("\n");
|
|
8967
|
+
}
|
|
8968
|
+
async function migrateJsonNotifications() {
|
|
8969
|
+
const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR || path15.join(os11.homedir(), ".exe-os");
|
|
8970
|
+
const notifDir = path15.join(base, "notifications");
|
|
8971
|
+
if (!existsSync15(notifDir)) return 0;
|
|
8972
|
+
let migrated = 0;
|
|
8973
|
+
try {
|
|
8974
|
+
const files = readdirSync2(notifDir).filter((f) => f.endsWith(".json"));
|
|
8975
|
+
if (files.length === 0) return 0;
|
|
8976
|
+
const client = getClient();
|
|
8977
|
+
for (const file of files) {
|
|
8978
|
+
try {
|
|
8979
|
+
const filePath = path15.join(notifDir, file);
|
|
8980
|
+
const data = JSON.parse(readFileSync11(filePath, "utf8"));
|
|
8981
|
+
await client.execute({
|
|
8982
|
+
sql: `INSERT OR IGNORE INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
8983
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
8984
|
+
args: [
|
|
8985
|
+
crypto4.randomUUID(),
|
|
8986
|
+
data.agentId ?? "unknown",
|
|
8987
|
+
data.agentRole ?? "unknown",
|
|
8988
|
+
data.event ?? "session_summary",
|
|
8989
|
+
data.project ?? "unknown",
|
|
8990
|
+
data.summary ?? "",
|
|
8991
|
+
data.taskFile ?? null,
|
|
8992
|
+
null,
|
|
8993
|
+
data.read ? 1 : 0,
|
|
8994
|
+
data.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
8995
|
+
]
|
|
8996
|
+
});
|
|
8997
|
+
unlinkSync4(filePath);
|
|
8998
|
+
migrated++;
|
|
8999
|
+
} catch {
|
|
9000
|
+
}
|
|
9001
|
+
}
|
|
9002
|
+
try {
|
|
9003
|
+
const remaining = readdirSync2(notifDir);
|
|
9004
|
+
if (remaining.length === 0) {
|
|
9005
|
+
rmdirSync(notifDir);
|
|
9006
|
+
}
|
|
9007
|
+
} catch {
|
|
9008
|
+
}
|
|
9009
|
+
} catch {
|
|
9010
|
+
}
|
|
9011
|
+
return migrated;
|
|
9012
|
+
}
|
|
9013
|
+
function eventIcon(event) {
|
|
9014
|
+
switch (event) {
|
|
9015
|
+
case "task_complete":
|
|
9016
|
+
return "Completed:";
|
|
9017
|
+
case "task_needs_fix":
|
|
9018
|
+
return "Needs fix:";
|
|
9019
|
+
case "session_summary":
|
|
9020
|
+
return "Session:";
|
|
9021
|
+
case "error_spike":
|
|
9022
|
+
return "Errors:";
|
|
9023
|
+
case "orphan_task":
|
|
9024
|
+
return "Orphan:";
|
|
9025
|
+
case "subtasks_complete":
|
|
9026
|
+
return "Subtasks done:";
|
|
9027
|
+
case "capacity_relaunch":
|
|
9028
|
+
return "Relaunched:";
|
|
9029
|
+
}
|
|
9030
|
+
}
|
|
9031
|
+
function formatTimeAgo(timestamp) {
|
|
9032
|
+
const diffMs = Date.now() - new Date(timestamp).getTime();
|
|
9033
|
+
const mins = Math.floor(diffMs / 6e4);
|
|
9034
|
+
if (mins < 1) return "just now";
|
|
9035
|
+
if (mins < 60) return `${mins}m ago`;
|
|
9036
|
+
const hours = Math.floor(mins / 60);
|
|
9037
|
+
if (hours < 24) return `${hours}h ago`;
|
|
9038
|
+
const days = Math.floor(hours / 24);
|
|
9039
|
+
return `${days}d ago`;
|
|
9040
|
+
}
|
|
9041
|
+
var CLEANUP_DAYS;
|
|
8806
9042
|
var init_notifications = __esm({
|
|
8807
9043
|
"src/lib/notifications.ts"() {
|
|
8808
9044
|
"use strict";
|
|
8809
9045
|
init_database();
|
|
8810
9046
|
init_task_scope();
|
|
9047
|
+
CLEANUP_DAYS = 7;
|
|
8811
9048
|
}
|
|
8812
9049
|
});
|
|
8813
9050
|
|
|
@@ -9798,7 +10035,13 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
|
|
|
9798
10035
|
taskFile
|
|
9799
10036
|
});
|
|
9800
10037
|
const originalPriority = String(row.priority).toLowerCase();
|
|
9801
|
-
const
|
|
10038
|
+
const resultLower = result?.toLowerCase() ?? "";
|
|
10039
|
+
const hasTestEvidence = (
|
|
10040
|
+
// Vitest/Jest output patterns (hard to fake without actually running tests)
|
|
10041
|
+
/\d+\s+pass(ed|ing)/.test(resultLower) || /test files?\s+\d+\s+passed/.test(resultLower) || /tests?\s+\d+\s+passed/.test(resultLower)
|
|
10042
|
+
);
|
|
10043
|
+
const hasNoFailures = !/fail(ed|ure|ing)|error/i.test(resultLower);
|
|
10044
|
+
const autoApprove = originalPriority === "p2" && hasTestEvidence && hasNoFailures;
|
|
9802
10045
|
if (!autoApprove) {
|
|
9803
10046
|
try {
|
|
9804
10047
|
const key = getSessionKey();
|
|
@@ -9806,6 +10049,13 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
|
|
|
9806
10049
|
if (exeSession) {
|
|
9807
10050
|
sendIntercom(exeSession);
|
|
9808
10051
|
}
|
|
10052
|
+
if (reviewer && reviewer !== coordinatorName && reviewer !== exeSession) {
|
|
10053
|
+
const { employeeSessionName: employeeSessionName2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
10054
|
+
if (exeSession) {
|
|
10055
|
+
const reviewerSession = employeeSessionName2(reviewer, exeSession);
|
|
10056
|
+
sendIntercom(reviewerSession);
|
|
10057
|
+
}
|
|
10058
|
+
}
|
|
9809
10059
|
} catch {
|
|
9810
10060
|
}
|
|
9811
10061
|
}
|
|
@@ -10513,6 +10763,20 @@ async function updateTask(input) {
|
|
|
10513
10763
|
notifyTaskDone();
|
|
10514
10764
|
}
|
|
10515
10765
|
await markTaskNotificationsRead(taskFile);
|
|
10766
|
+
if (input.status === "needs_review" && !isCoordinator) {
|
|
10767
|
+
try {
|
|
10768
|
+
const { writeNotification: writeNotification2 } = await Promise.resolve().then(() => (init_notifications(), notifications_exports));
|
|
10769
|
+
await writeNotification2({
|
|
10770
|
+
agentId: String(row.assigned_to),
|
|
10771
|
+
agentRole: String(row.assigned_to),
|
|
10772
|
+
event: "task_complete",
|
|
10773
|
+
project: String(row.project_name),
|
|
10774
|
+
summary: `"${String(row.title)}" is ready for review`,
|
|
10775
|
+
taskFile
|
|
10776
|
+
});
|
|
10777
|
+
} catch {
|
|
10778
|
+
}
|
|
10779
|
+
}
|
|
10516
10780
|
if (input.status === "done" || input.status === "closed") {
|
|
10517
10781
|
try {
|
|
10518
10782
|
await cascadeUnblock(taskId, input.baseDir, now);
|
|
@@ -10945,18 +11209,31 @@ function acquireSpawnLock2(sessionName) {
|
|
|
10945
11209
|
mkdirSync10(SPAWN_LOCK_DIR, { recursive: true });
|
|
10946
11210
|
}
|
|
10947
11211
|
const lockFile = spawnLockPath(sessionName);
|
|
10948
|
-
|
|
10949
|
-
|
|
10950
|
-
|
|
10951
|
-
|
|
10952
|
-
|
|
10953
|
-
|
|
10954
|
-
|
|
10955
|
-
|
|
11212
|
+
const lockData = JSON.stringify({ pid: process.pid, timestamp: Date.now() });
|
|
11213
|
+
const { openSync: openSync3, closeSync: closeSync3, writeSync } = __require("fs");
|
|
11214
|
+
const { constants } = __require("fs");
|
|
11215
|
+
try {
|
|
11216
|
+
const fd = openSync3(lockFile, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 420);
|
|
11217
|
+
writeSync(fd, lockData);
|
|
11218
|
+
closeSync3(fd);
|
|
11219
|
+
return true;
|
|
11220
|
+
} catch (err) {
|
|
11221
|
+
if (err?.code !== "EEXIST") {
|
|
11222
|
+
return true;
|
|
10956
11223
|
}
|
|
10957
11224
|
}
|
|
10958
|
-
|
|
10959
|
-
|
|
11225
|
+
try {
|
|
11226
|
+
const lock = JSON.parse(readFileSync13(lockFile, "utf8"));
|
|
11227
|
+
const age = Date.now() - lock.timestamp;
|
|
11228
|
+
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
11229
|
+
return false;
|
|
11230
|
+
}
|
|
11231
|
+
writeFileSync8(lockFile, lockData);
|
|
11232
|
+
return true;
|
|
11233
|
+
} catch {
|
|
11234
|
+
writeFileSync8(lockFile, lockData);
|
|
11235
|
+
return true;
|
|
11236
|
+
}
|
|
10960
11237
|
}
|
|
10961
11238
|
function releaseSpawnLock2(sessionName) {
|
|
10962
11239
|
try {
|
|
@@ -11035,6 +11312,21 @@ function parseParentExe(sessionName, agentId) {
|
|
|
11035
11312
|
function extractRootExe(name) {
|
|
11036
11313
|
if (!name) return null;
|
|
11037
11314
|
if (!name.includes("-")) return name;
|
|
11315
|
+
try {
|
|
11316
|
+
const roster = (init_employees(), __toCommonJS(employees_exports)).loadEmployeesSync();
|
|
11317
|
+
if (roster.length > 0) {
|
|
11318
|
+
const sortedNames = roster.map((e) => e.name).sort((a, b) => b.length - a.length);
|
|
11319
|
+
for (const agentName of sortedNames) {
|
|
11320
|
+
const escaped = agentName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
11321
|
+
const regex = new RegExp(`^${escaped}\\d*-(.+)$`);
|
|
11322
|
+
const match = name.match(regex);
|
|
11323
|
+
if (match) {
|
|
11324
|
+
return extractRootExe(match[1]);
|
|
11325
|
+
}
|
|
11326
|
+
}
|
|
11327
|
+
}
|
|
11328
|
+
} catch {
|
|
11329
|
+
}
|
|
11038
11330
|
const parts = name.split("-").filter(Boolean);
|
|
11039
11331
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
11040
11332
|
}
|
|
@@ -11053,6 +11345,10 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
11053
11345
|
function getParentExe(sessionKey) {
|
|
11054
11346
|
try {
|
|
11055
11347
|
const data = JSON.parse(readFileSync13(path21.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
11348
|
+
if (data.registeredAt) {
|
|
11349
|
+
const age = Date.now() - new Date(data.registeredAt).getTime();
|
|
11350
|
+
if (age > PARENT_EXE_CACHE_TTL_MS) return null;
|
|
11351
|
+
}
|
|
11056
11352
|
return data.parentExe || null;
|
|
11057
11353
|
} catch {
|
|
11058
11354
|
return null;
|
|
@@ -11601,7 +11897,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
11601
11897
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
11602
11898
|
} catch {
|
|
11603
11899
|
}
|
|
11604
|
-
let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName}`;
|
|
11900
|
+
let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName} EXE_SESSION_START_ISO=${(/* @__PURE__ */ new Date()).toISOString()}`;
|
|
11605
11901
|
if (ccProvider !== DEFAULT_PROVIDER) {
|
|
11606
11902
|
const cfg = PROVIDER_TABLE[ccProvider];
|
|
11607
11903
|
if (cfg?.apiKeyEnv) {
|
|
@@ -11636,10 +11932,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
11636
11932
|
}
|
|
11637
11933
|
if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
|
|
11638
11934
|
if (agentRtConfig.runtime === "claude" && agentRtConfig.model) {
|
|
11639
|
-
|
|
11640
|
-
|
|
11641
|
-
ccModel += "[1m]";
|
|
11642
|
-
}
|
|
11935
|
+
const { normalizeCcModelName: normalizeCcModelName2 } = (init_agent_config(), __toCommonJS(agent_config_exports));
|
|
11936
|
+
const ccModel = normalizeCcModelName2(agentRtConfig.model);
|
|
11643
11937
|
envPrefix = `${envPrefix} ANTHROPIC_MODEL=${ccModel}`;
|
|
11644
11938
|
}
|
|
11645
11939
|
}
|
|
@@ -11740,7 +12034,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
11740
12034
|
releaseSpawnLock2(sessionName);
|
|
11741
12035
|
return { sessionName };
|
|
11742
12036
|
}
|
|
11743
|
-
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;
|
|
12037
|
+
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;
|
|
11744
12038
|
var init_tmux_routing = __esm({
|
|
11745
12039
|
"src/lib/tmux-routing.ts"() {
|
|
11746
12040
|
"use strict";
|
|
@@ -11760,6 +12054,7 @@ var init_tmux_routing = __esm({
|
|
|
11760
12054
|
SESSION_CACHE = path21.join(os13.homedir(), ".exe-os", "session-cache");
|
|
11761
12055
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
11762
12056
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
12057
|
+
PARENT_EXE_CACHE_TTL_MS = 4 * 60 * 60 * 1e3;
|
|
11763
12058
|
VERIFY_PANE_LINES = 200;
|
|
11764
12059
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
11765
12060
|
CODEX_DEBOUNCE_MS = 12e4;
|