@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
|
@@ -154,6 +154,17 @@ function normalizeOrchestration(raw) {
|
|
|
154
154
|
const userOrg = raw.orchestration ?? {};
|
|
155
155
|
raw.orchestration = { ...defaultOrg, ...userOrg };
|
|
156
156
|
}
|
|
157
|
+
function normalizeCloudEndpoint(raw) {
|
|
158
|
+
const cloud = raw.cloud;
|
|
159
|
+
if (!cloud?.endpoint) return;
|
|
160
|
+
const ep = String(cloud.endpoint);
|
|
161
|
+
if (ep === "https://askexe.com/cloud" || ep === "https://askexe.com/cloud/") {
|
|
162
|
+
cloud.endpoint = "https://cloud.askexe.com";
|
|
163
|
+
process.stderr.write(
|
|
164
|
+
"[config] Auto-migrated cloud endpoint: askexe.com/cloud \u2192 cloud.askexe.com\n"
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
157
168
|
async function loadConfig() {
|
|
158
169
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
159
170
|
await ensurePrivateDir(dir);
|
|
@@ -179,6 +190,7 @@ async function loadConfig() {
|
|
|
179
190
|
normalizeSessionLifecycle(migratedCfg);
|
|
180
191
|
normalizeAutoUpdate(migratedCfg);
|
|
181
192
|
normalizeOrchestration(migratedCfg);
|
|
193
|
+
normalizeCloudEndpoint(migratedCfg);
|
|
182
194
|
const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
|
|
183
195
|
if (config.dbPath.startsWith("~")) {
|
|
184
196
|
config.dbPath = config.dbPath.replace(/^~/, os.homedir());
|
|
@@ -207,6 +219,7 @@ function loadConfigSync() {
|
|
|
207
219
|
normalizeSessionLifecycle(migratedCfg);
|
|
208
220
|
normalizeAutoUpdate(migratedCfg);
|
|
209
221
|
normalizeOrchestration(migratedCfg);
|
|
222
|
+
normalizeCloudEndpoint(migratedCfg);
|
|
210
223
|
const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
|
|
211
224
|
if (config.dbPath.startsWith("~")) {
|
|
212
225
|
config.dbPath = config.dbPath.replace(/^~/, os.homedir());
|
|
@@ -367,6 +380,7 @@ __export(agent_config_exports, {
|
|
|
367
380
|
clearAgentRuntime: () => clearAgentRuntime,
|
|
368
381
|
getAgentRuntime: () => getAgentRuntime,
|
|
369
382
|
loadAgentConfig: () => loadAgentConfig,
|
|
383
|
+
normalizeCcModelName: () => normalizeCcModelName,
|
|
370
384
|
saveAgentConfig: () => saveAgentConfig,
|
|
371
385
|
setAgentMcps: () => setAgentMcps,
|
|
372
386
|
setAgentRuntime: () => setAgentRuntime
|
|
@@ -395,6 +409,13 @@ function getAgentRuntime(agentId) {
|
|
|
395
409
|
if (orgDefault) return orgDefault;
|
|
396
410
|
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
397
411
|
}
|
|
412
|
+
function normalizeCcModelName(model) {
|
|
413
|
+
let ccModel = model.replace(/(\d+)\.(\d+)/g, "$1-$2");
|
|
414
|
+
if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
|
|
415
|
+
ccModel += "[1m]";
|
|
416
|
+
}
|
|
417
|
+
return ccModel;
|
|
418
|
+
}
|
|
398
419
|
function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
|
|
399
420
|
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
400
421
|
if (!knownModels) {
|
|
@@ -3741,7 +3762,7 @@ var init_database = __esm({
|
|
|
3741
3762
|
});
|
|
3742
3763
|
|
|
3743
3764
|
// src/lib/keychain.ts
|
|
3744
|
-
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
3765
|
+
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2, rename, copyFile } from "fs/promises";
|
|
3745
3766
|
import { existsSync as existsSync8, statSync as statSync3 } from "fs";
|
|
3746
3767
|
import { execSync as execSync3 } from "child_process";
|
|
3747
3768
|
import path7 from "path";
|
|
@@ -3776,12 +3797,14 @@ function linuxSecretAvailable() {
|
|
|
3776
3797
|
function isRootOnlyTrustedServerKeyFile(keyPath) {
|
|
3777
3798
|
if (process.platform !== "linux") return false;
|
|
3778
3799
|
try {
|
|
3779
|
-
const uid = typeof os5.userInfo().uid === "number" ? os5.userInfo().uid : -1;
|
|
3780
3800
|
const st = statSync3(keyPath);
|
|
3781
3801
|
if (!st.isFile() || (st.mode & 63) !== 0) return false;
|
|
3802
|
+
const uid = typeof os5.userInfo().uid === "number" ? os5.userInfo().uid : -1;
|
|
3782
3803
|
if (uid === 0) return true;
|
|
3783
3804
|
const exeOsDir = process.env.EXE_OS_DIR;
|
|
3784
|
-
|
|
3805
|
+
if (exeOsDir && path7.resolve(keyPath).startsWith(path7.resolve(exeOsDir) + path7.sep)) return true;
|
|
3806
|
+
if (!linuxSecretAvailable()) return true;
|
|
3807
|
+
return false;
|
|
3785
3808
|
} catch {
|
|
3786
3809
|
return false;
|
|
3787
3810
|
}
|
|
@@ -3931,15 +3954,25 @@ async function writeMachineBoundFileFallback(b64) {
|
|
|
3931
3954
|
await mkdir3(dir, { recursive: true });
|
|
3932
3955
|
const keyPath = getKeyPath();
|
|
3933
3956
|
const machineKey = deriveMachineKey();
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3957
|
+
const content = machineKey ? encryptWithMachineKey(b64, machineKey) + "\n" : b64 + "\n";
|
|
3958
|
+
const result = machineKey ? "encrypted" : "plaintext";
|
|
3959
|
+
const tmpPath = keyPath + ".tmp";
|
|
3960
|
+
try {
|
|
3961
|
+
if (existsSync8(keyPath)) {
|
|
3962
|
+
await copyFile(keyPath, keyPath + ".bak").catch(() => {
|
|
3963
|
+
});
|
|
3964
|
+
}
|
|
3965
|
+
await writeFile3(tmpPath, content, "utf-8");
|
|
3966
|
+
await chmod2(tmpPath, 384);
|
|
3967
|
+
await rename(tmpPath, keyPath);
|
|
3968
|
+
} catch (err) {
|
|
3969
|
+
try {
|
|
3970
|
+
await unlink(tmpPath);
|
|
3971
|
+
} catch {
|
|
3972
|
+
}
|
|
3973
|
+
throw err;
|
|
3939
3974
|
}
|
|
3940
|
-
|
|
3941
|
-
await chmod2(keyPath, 384);
|
|
3942
|
-
return "plaintext";
|
|
3975
|
+
return result;
|
|
3943
3976
|
}
|
|
3944
3977
|
async function getMasterKey() {
|
|
3945
3978
|
let nativeValue = macKeychainGet() ?? linuxSecretGet();
|
|
@@ -4006,7 +4039,7 @@ async function getMasterKey() {
|
|
|
4006
4039
|
b64Value = content;
|
|
4007
4040
|
}
|
|
4008
4041
|
const key = Buffer.from(b64Value, "base64");
|
|
4009
|
-
if (
|
|
4042
|
+
if (isRootOnlyTrustedServerKeyFile(keyPath)) {
|
|
4010
4043
|
return key;
|
|
4011
4044
|
}
|
|
4012
4045
|
const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
|
|
@@ -6468,6 +6501,7 @@ var init_provider_table = __esm({
|
|
|
6468
6501
|
// src/lib/intercom-queue.ts
|
|
6469
6502
|
var intercom_queue_exports = {};
|
|
6470
6503
|
__export(intercom_queue_exports, {
|
|
6504
|
+
_resetDrainGuard: () => _resetDrainGuard,
|
|
6471
6505
|
clearQueueForAgent: () => clearQueueForAgent,
|
|
6472
6506
|
drainForSession: () => drainForSession,
|
|
6473
6507
|
drainQueue: () => drainQueue,
|
|
@@ -6513,38 +6547,47 @@ function queueIntercom(targetSession, reason) {
|
|
|
6513
6547
|
writeQueue(queue);
|
|
6514
6548
|
}
|
|
6515
6549
|
function drainQueue(isSessionBusy, sendKeys) {
|
|
6550
|
+
if (_draining) {
|
|
6551
|
+
logQueue("SKIP_DRAIN \u2014 previous drain still running (possible tmux hang)");
|
|
6552
|
+
return { drained: 0, failed: 0 };
|
|
6553
|
+
}
|
|
6516
6554
|
const queue = readQueue();
|
|
6517
6555
|
if (queue.length === 0) return { drained: 0, failed: 0 };
|
|
6556
|
+
_draining = true;
|
|
6518
6557
|
const remaining = [];
|
|
6519
6558
|
let drained = 0;
|
|
6520
6559
|
let failed = 0;
|
|
6521
|
-
|
|
6522
|
-
const
|
|
6523
|
-
|
|
6524
|
-
|
|
6525
|
-
|
|
6526
|
-
|
|
6527
|
-
|
|
6528
|
-
|
|
6529
|
-
|
|
6530
|
-
|
|
6531
|
-
|
|
6532
|
-
|
|
6533
|
-
|
|
6534
|
-
|
|
6560
|
+
try {
|
|
6561
|
+
for (const item of queue) {
|
|
6562
|
+
const age = Date.now() - new Date(item.queuedAt).getTime();
|
|
6563
|
+
if (age > TTL_MS) {
|
|
6564
|
+
logQueue(`EXPIRED \u2192 ${item.targetSession} (${Math.round(age / 6e4)}min old, reason: ${item.reason})`);
|
|
6565
|
+
failed++;
|
|
6566
|
+
continue;
|
|
6567
|
+
}
|
|
6568
|
+
try {
|
|
6569
|
+
if (!isSessionBusy(item.targetSession)) {
|
|
6570
|
+
const success = sendKeys(item.targetSession);
|
|
6571
|
+
if (success) {
|
|
6572
|
+
logQueue(`DRAINED \u2192 ${item.targetSession} (after ${item.attempts} retries)`);
|
|
6573
|
+
drained++;
|
|
6574
|
+
continue;
|
|
6575
|
+
}
|
|
6535
6576
|
}
|
|
6577
|
+
} catch {
|
|
6536
6578
|
}
|
|
6537
|
-
|
|
6538
|
-
|
|
6539
|
-
|
|
6540
|
-
|
|
6541
|
-
|
|
6542
|
-
|
|
6543
|
-
|
|
6579
|
+
item.attempts++;
|
|
6580
|
+
if (item.attempts >= MAX_RETRIES2) {
|
|
6581
|
+
logQueue(`FAILED \u2192 ${item.targetSession} (${MAX_RETRIES2} retries exhausted, reason: ${item.reason})`);
|
|
6582
|
+
failed++;
|
|
6583
|
+
continue;
|
|
6584
|
+
}
|
|
6585
|
+
remaining.push(item);
|
|
6544
6586
|
}
|
|
6545
|
-
remaining
|
|
6587
|
+
writeQueue(remaining);
|
|
6588
|
+
} finally {
|
|
6589
|
+
_draining = false;
|
|
6546
6590
|
}
|
|
6547
|
-
writeQueue(remaining);
|
|
6548
6591
|
return { drained, failed };
|
|
6549
6592
|
}
|
|
6550
6593
|
function drainForSession(targetSession, sendKeys) {
|
|
@@ -6569,6 +6612,9 @@ function clearQueueForAgent(agentName) {
|
|
|
6569
6612
|
logQueue(`CLEARED ${before - filtered.length} stale item(s) for ${agentName}`);
|
|
6570
6613
|
}
|
|
6571
6614
|
}
|
|
6615
|
+
function _resetDrainGuard() {
|
|
6616
|
+
_draining = false;
|
|
6617
|
+
}
|
|
6572
6618
|
function logQueue(msg) {
|
|
6573
6619
|
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] [queue] ${msg}
|
|
6574
6620
|
`;
|
|
@@ -6580,13 +6626,14 @@ function logQueue(msg) {
|
|
|
6580
6626
|
} catch {
|
|
6581
6627
|
}
|
|
6582
6628
|
}
|
|
6583
|
-
var QUEUE_PATH, MAX_RETRIES2, TTL_MS, INTERCOM_LOG;
|
|
6629
|
+
var QUEUE_PATH, MAX_RETRIES2, TTL_MS, _draining, INTERCOM_LOG;
|
|
6584
6630
|
var init_intercom_queue = __esm({
|
|
6585
6631
|
"src/lib/intercom-queue.ts"() {
|
|
6586
6632
|
"use strict";
|
|
6587
6633
|
QUEUE_PATH = path10.join(os7.homedir(), ".exe-os", "intercom-queue.json");
|
|
6588
6634
|
MAX_RETRIES2 = 5;
|
|
6589
6635
|
TTL_MS = 60 * 60 * 1e3;
|
|
6636
|
+
_draining = false;
|
|
6590
6637
|
INTERCOM_LOG = path10.join(os7.homedir(), ".exe-os", "intercom.log");
|
|
6591
6638
|
}
|
|
6592
6639
|
});
|
|
@@ -7104,6 +7151,21 @@ function isRootSession(name) {
|
|
|
7104
7151
|
function extractRootExe(name) {
|
|
7105
7152
|
if (!name) return null;
|
|
7106
7153
|
if (!name.includes("-")) return name;
|
|
7154
|
+
try {
|
|
7155
|
+
const roster = (init_employees(), __toCommonJS(employees_exports)).loadEmployeesSync();
|
|
7156
|
+
if (roster.length > 0) {
|
|
7157
|
+
const sortedNames = roster.map((e) => e.name).sort((a, b) => b.length - a.length);
|
|
7158
|
+
for (const agentName of sortedNames) {
|
|
7159
|
+
const escaped = agentName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7160
|
+
const regex = new RegExp(`^${escaped}\\d*-(.+)$`);
|
|
7161
|
+
const match = name.match(regex);
|
|
7162
|
+
if (match) {
|
|
7163
|
+
return extractRootExe(match[1]);
|
|
7164
|
+
}
|
|
7165
|
+
}
|
|
7166
|
+
}
|
|
7167
|
+
} catch {
|
|
7168
|
+
}
|
|
7107
7169
|
const parts = name.split("-").filter(Boolean);
|
|
7108
7170
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
7109
7171
|
}
|
|
@@ -7122,6 +7184,10 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
7122
7184
|
function getParentExe(sessionKey) {
|
|
7123
7185
|
try {
|
|
7124
7186
|
const data = JSON.parse(readFileSync9(path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
7187
|
+
if (data.registeredAt) {
|
|
7188
|
+
const age = Date.now() - new Date(data.registeredAt).getTime();
|
|
7189
|
+
if (age > PARENT_EXE_CACHE_TTL_MS) return null;
|
|
7190
|
+
}
|
|
7125
7191
|
return data.parentExe || null;
|
|
7126
7192
|
} catch {
|
|
7127
7193
|
return null;
|
|
@@ -7393,7 +7459,7 @@ function notifyParentExe(sessionKey) {
|
|
|
7393
7459
|
}
|
|
7394
7460
|
return true;
|
|
7395
7461
|
}
|
|
7396
|
-
var SPAWN_LOCK_DIR, SESSION_CACHE, INTERCOM_DEBOUNCE_MS, CODEX_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
7462
|
+
var SPAWN_LOCK_DIR, SESSION_CACHE, PARENT_EXE_CACHE_TTL_MS, INTERCOM_DEBOUNCE_MS, CODEX_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
7397
7463
|
var init_tmux_routing = __esm({
|
|
7398
7464
|
"src/lib/tmux-routing.ts"() {
|
|
7399
7465
|
"use strict";
|
|
@@ -7411,6 +7477,7 @@ var init_tmux_routing = __esm({
|
|
|
7411
7477
|
init_agent_symlinks();
|
|
7412
7478
|
SPAWN_LOCK_DIR = path14.join(os10.homedir(), ".exe-os", "spawn-locks");
|
|
7413
7479
|
SESSION_CACHE = path14.join(os10.homedir(), ".exe-os", "session-cache");
|
|
7480
|
+
PARENT_EXE_CACHE_TTL_MS = 4 * 60 * 60 * 1e3;
|
|
7414
7481
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
7415
7482
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
7416
7483
|
INTERCOM_LOG2 = path14.join(os10.homedir(), ".exe-os", "intercom.log");
|
|
@@ -7454,6 +7521,17 @@ var init_task_scope = __esm({
|
|
|
7454
7521
|
});
|
|
7455
7522
|
|
|
7456
7523
|
// src/lib/notifications.ts
|
|
7524
|
+
var notifications_exports = {};
|
|
7525
|
+
__export(notifications_exports, {
|
|
7526
|
+
cleanupOldNotifications: () => cleanupOldNotifications,
|
|
7527
|
+
formatNotifications: () => formatNotifications,
|
|
7528
|
+
markAsRead: () => markAsRead,
|
|
7529
|
+
markAsReadByTaskFile: () => markAsReadByTaskFile,
|
|
7530
|
+
markDoneTaskNotificationsAsRead: () => markDoneTaskNotificationsAsRead,
|
|
7531
|
+
migrateJsonNotifications: () => migrateJsonNotifications,
|
|
7532
|
+
readUnreadNotifications: () => readUnreadNotifications,
|
|
7533
|
+
writeNotification: () => writeNotification
|
|
7534
|
+
});
|
|
7457
7535
|
import crypto2 from "crypto";
|
|
7458
7536
|
import path15 from "path";
|
|
7459
7537
|
import os11 from "os";
|
|
@@ -7490,6 +7568,52 @@ async function writeNotification(notification) {
|
|
|
7490
7568
|
`);
|
|
7491
7569
|
}
|
|
7492
7570
|
}
|
|
7571
|
+
async function readUnreadNotifications(agentFilter, sessionScope) {
|
|
7572
|
+
try {
|
|
7573
|
+
const client = getClient();
|
|
7574
|
+
const conditions = ["read = 0"];
|
|
7575
|
+
const args = [];
|
|
7576
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
7577
|
+
if (agentFilter) {
|
|
7578
|
+
conditions.push("agent_id = ?");
|
|
7579
|
+
args.push(agentFilter);
|
|
7580
|
+
}
|
|
7581
|
+
const result = await client.execute({
|
|
7582
|
+
sql: `SELECT id, agent_id, agent_role, event, project, summary, task_file, session_scope, created_at
|
|
7583
|
+
FROM notifications
|
|
7584
|
+
WHERE ${conditions.join(" AND ")}${scope.sql}
|
|
7585
|
+
ORDER BY created_at ASC`,
|
|
7586
|
+
args: [...args, ...scope.args]
|
|
7587
|
+
});
|
|
7588
|
+
return result.rows.map((r) => ({
|
|
7589
|
+
id: String(r.id),
|
|
7590
|
+
agentId: String(r.agent_id),
|
|
7591
|
+
agentRole: String(r.agent_role),
|
|
7592
|
+
event: String(r.event),
|
|
7593
|
+
project: String(r.project),
|
|
7594
|
+
summary: String(r.summary),
|
|
7595
|
+
taskFile: r.task_file ? String(r.task_file) : void 0,
|
|
7596
|
+
sessionScope: r.session_scope == null ? null : String(r.session_scope),
|
|
7597
|
+
timestamp: String(r.created_at),
|
|
7598
|
+
read: false
|
|
7599
|
+
}));
|
|
7600
|
+
} catch {
|
|
7601
|
+
return [];
|
|
7602
|
+
}
|
|
7603
|
+
}
|
|
7604
|
+
async function markAsRead(ids, sessionScope) {
|
|
7605
|
+
if (ids.length === 0) return;
|
|
7606
|
+
try {
|
|
7607
|
+
const client = getClient();
|
|
7608
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
7609
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
7610
|
+
await client.execute({
|
|
7611
|
+
sql: `UPDATE notifications SET read = 1 WHERE id IN (${placeholders})${scope.sql}`,
|
|
7612
|
+
args: [...ids, ...scope.args]
|
|
7613
|
+
});
|
|
7614
|
+
} catch {
|
|
7615
|
+
}
|
|
7616
|
+
}
|
|
7493
7617
|
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
7494
7618
|
try {
|
|
7495
7619
|
const client = getClient();
|
|
@@ -7502,11 +7626,144 @@ async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
|
7502
7626
|
} catch {
|
|
7503
7627
|
}
|
|
7504
7628
|
}
|
|
7629
|
+
async function cleanupOldNotifications(daysOld = CLEANUP_DAYS, sessionScope) {
|
|
7630
|
+
try {
|
|
7631
|
+
const client = getClient();
|
|
7632
|
+
const cutoff = new Date(
|
|
7633
|
+
Date.now() - daysOld * 24 * 60 * 60 * 1e3
|
|
7634
|
+
).toISOString();
|
|
7635
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
7636
|
+
const result = await client.execute({
|
|
7637
|
+
sql: `DELETE FROM notifications WHERE created_at < ?${scope.sql}`,
|
|
7638
|
+
args: [cutoff, ...scope.args]
|
|
7639
|
+
});
|
|
7640
|
+
return result.rowsAffected;
|
|
7641
|
+
} catch {
|
|
7642
|
+
return 0;
|
|
7643
|
+
}
|
|
7644
|
+
}
|
|
7645
|
+
async function markDoneTaskNotificationsAsRead(sessionScope) {
|
|
7646
|
+
try {
|
|
7647
|
+
const client = getClient();
|
|
7648
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
7649
|
+
const result = await client.execute({
|
|
7650
|
+
sql: `UPDATE notifications SET read = 1
|
|
7651
|
+
WHERE read = 0
|
|
7652
|
+
AND task_file IS NOT NULL
|
|
7653
|
+
${scope.sql}
|
|
7654
|
+
AND task_file IN (
|
|
7655
|
+
SELECT task_file FROM tasks WHERE status = 'done'${scope.sql}
|
|
7656
|
+
)`,
|
|
7657
|
+
args: [...scope.args, ...scope.args]
|
|
7658
|
+
});
|
|
7659
|
+
return result.rowsAffected;
|
|
7660
|
+
} catch {
|
|
7661
|
+
return 0;
|
|
7662
|
+
}
|
|
7663
|
+
}
|
|
7664
|
+
function formatNotifications(notifications) {
|
|
7665
|
+
if (notifications.length === 0) return "";
|
|
7666
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
7667
|
+
for (const n of notifications) {
|
|
7668
|
+
const key = `${n.agentId}|${n.agentRole}`;
|
|
7669
|
+
if (!grouped.has(key)) grouped.set(key, []);
|
|
7670
|
+
grouped.get(key).push(n);
|
|
7671
|
+
}
|
|
7672
|
+
const lines = [];
|
|
7673
|
+
lines.push(`## Notifications (${notifications.length} unread)
|
|
7674
|
+
`);
|
|
7675
|
+
for (const [key, items] of grouped) {
|
|
7676
|
+
const [agentId, agentRole] = key.split("|");
|
|
7677
|
+
lines.push(`**${agentId}** (${agentRole}):`);
|
|
7678
|
+
for (const item of items) {
|
|
7679
|
+
const ago = formatTimeAgo(item.timestamp);
|
|
7680
|
+
const icon = eventIcon(item.event);
|
|
7681
|
+
lines.push(`- ${icon} ${item.summary} (${item.project}) \u2014 ${ago}`);
|
|
7682
|
+
}
|
|
7683
|
+
lines.push("");
|
|
7684
|
+
}
|
|
7685
|
+
return lines.join("\n");
|
|
7686
|
+
}
|
|
7687
|
+
async function migrateJsonNotifications() {
|
|
7688
|
+
const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR || path15.join(os11.homedir(), ".exe-os");
|
|
7689
|
+
const notifDir = path15.join(base, "notifications");
|
|
7690
|
+
if (!existsSync15(notifDir)) return 0;
|
|
7691
|
+
let migrated = 0;
|
|
7692
|
+
try {
|
|
7693
|
+
const files = readdirSync3(notifDir).filter((f) => f.endsWith(".json"));
|
|
7694
|
+
if (files.length === 0) return 0;
|
|
7695
|
+
const client = getClient();
|
|
7696
|
+
for (const file of files) {
|
|
7697
|
+
try {
|
|
7698
|
+
const filePath = path15.join(notifDir, file);
|
|
7699
|
+
const data = JSON.parse(readFileSync10(filePath, "utf8"));
|
|
7700
|
+
await client.execute({
|
|
7701
|
+
sql: `INSERT OR IGNORE INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
7702
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
7703
|
+
args: [
|
|
7704
|
+
crypto2.randomUUID(),
|
|
7705
|
+
data.agentId ?? "unknown",
|
|
7706
|
+
data.agentRole ?? "unknown",
|
|
7707
|
+
data.event ?? "session_summary",
|
|
7708
|
+
data.project ?? "unknown",
|
|
7709
|
+
data.summary ?? "",
|
|
7710
|
+
data.taskFile ?? null,
|
|
7711
|
+
null,
|
|
7712
|
+
data.read ? 1 : 0,
|
|
7713
|
+
data.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
7714
|
+
]
|
|
7715
|
+
});
|
|
7716
|
+
unlinkSync4(filePath);
|
|
7717
|
+
migrated++;
|
|
7718
|
+
} catch {
|
|
7719
|
+
}
|
|
7720
|
+
}
|
|
7721
|
+
try {
|
|
7722
|
+
const remaining = readdirSync3(notifDir);
|
|
7723
|
+
if (remaining.length === 0) {
|
|
7724
|
+
rmdirSync(notifDir);
|
|
7725
|
+
}
|
|
7726
|
+
} catch {
|
|
7727
|
+
}
|
|
7728
|
+
} catch {
|
|
7729
|
+
}
|
|
7730
|
+
return migrated;
|
|
7731
|
+
}
|
|
7732
|
+
function eventIcon(event) {
|
|
7733
|
+
switch (event) {
|
|
7734
|
+
case "task_complete":
|
|
7735
|
+
return "Completed:";
|
|
7736
|
+
case "task_needs_fix":
|
|
7737
|
+
return "Needs fix:";
|
|
7738
|
+
case "session_summary":
|
|
7739
|
+
return "Session:";
|
|
7740
|
+
case "error_spike":
|
|
7741
|
+
return "Errors:";
|
|
7742
|
+
case "orphan_task":
|
|
7743
|
+
return "Orphan:";
|
|
7744
|
+
case "subtasks_complete":
|
|
7745
|
+
return "Subtasks done:";
|
|
7746
|
+
case "capacity_relaunch":
|
|
7747
|
+
return "Relaunched:";
|
|
7748
|
+
}
|
|
7749
|
+
}
|
|
7750
|
+
function formatTimeAgo(timestamp) {
|
|
7751
|
+
const diffMs = Date.now() - new Date(timestamp).getTime();
|
|
7752
|
+
const mins = Math.floor(diffMs / 6e4);
|
|
7753
|
+
if (mins < 1) return "just now";
|
|
7754
|
+
if (mins < 60) return `${mins}m ago`;
|
|
7755
|
+
const hours = Math.floor(mins / 60);
|
|
7756
|
+
if (hours < 24) return `${hours}h ago`;
|
|
7757
|
+
const days = Math.floor(hours / 24);
|
|
7758
|
+
return `${days}d ago`;
|
|
7759
|
+
}
|
|
7760
|
+
var CLEANUP_DAYS;
|
|
7505
7761
|
var init_notifications = __esm({
|
|
7506
7762
|
"src/lib/notifications.ts"() {
|
|
7507
7763
|
"use strict";
|
|
7508
7764
|
init_database();
|
|
7509
7765
|
init_task_scope();
|
|
7766
|
+
CLEANUP_DAYS = 7;
|
|
7510
7767
|
}
|
|
7511
7768
|
});
|
|
7512
7769
|
|
|
@@ -8557,6 +8814,20 @@ async function updateTask(input) {
|
|
|
8557
8814
|
notifyTaskDone();
|
|
8558
8815
|
}
|
|
8559
8816
|
await markTaskNotificationsRead(taskFile);
|
|
8817
|
+
if (input.status === "needs_review" && !isCoordinator) {
|
|
8818
|
+
try {
|
|
8819
|
+
const { writeNotification: writeNotification2 } = await Promise.resolve().then(() => (init_notifications(), notifications_exports));
|
|
8820
|
+
await writeNotification2({
|
|
8821
|
+
agentId: String(row.assigned_to),
|
|
8822
|
+
agentRole: String(row.assigned_to),
|
|
8823
|
+
event: "task_complete",
|
|
8824
|
+
project: String(row.project_name),
|
|
8825
|
+
summary: `"${String(row.title)}" is ready for review`,
|
|
8826
|
+
taskFile
|
|
8827
|
+
});
|
|
8828
|
+
} catch {
|
|
8829
|
+
}
|
|
8830
|
+
}
|
|
8560
8831
|
if (input.status === "done" || input.status === "closed") {
|
|
8561
8832
|
try {
|
|
8562
8833
|
await cascadeUnblock(taskId, input.baseDir, now);
|