@askexenow/exe-os 0.9.114 → 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 +13 -1
- package/dist/bin/agentic-reflection-backfill.js +13 -1
- package/dist/bin/agentic-semantic-label.js +13 -1
- package/dist/bin/backfill-conversations.js +13 -1
- package/dist/bin/backfill-responses.js +13 -1
- package/dist/bin/backfill-vectors.js +13 -1
- package/dist/bin/bulk-sync-postgres.js +13 -1
- package/dist/bin/cleanup-stale-review-tasks.js +449 -104
- package/dist/bin/cli.js +317 -40
- package/dist/bin/exe-assign.js +13 -1
- package/dist/bin/exe-boot.js +202 -39
- package/dist/bin/exe-cloud.js +13 -1
- package/dist/bin/exe-dispatch.js +315 -38
- package/dist/bin/exe-doctor.js +28 -2
- package/dist/bin/exe-export-behaviors.js +14 -1
- package/dist/bin/exe-forget.js +13 -1
- package/dist/bin/exe-gateway.js +315 -38
- package/dist/bin/exe-heartbeat.js +450 -104
- package/dist/bin/exe-kill.js +13 -1
- package/dist/bin/exe-launch-agent.js +14 -1
- package/dist/bin/exe-pending-messages.js +429 -84
- package/dist/bin/exe-pending-notifications.js +429 -84
- package/dist/bin/exe-pending-reviews.js +429 -84
- package/dist/bin/exe-rename.js +13 -1
- package/dist/bin/exe-review.js +13 -1
- package/dist/bin/exe-search.js +14 -1
- package/dist/bin/exe-session-cleanup.js +315 -38
- package/dist/bin/exe-settings.js +12 -0
- package/dist/bin/exe-start-codex.js +14 -1
- package/dist/bin/exe-start-opencode.js +14 -1
- package/dist/bin/exe-status.js +439 -105
- package/dist/bin/exe-support.js +12 -0
- package/dist/bin/exe-team.js +13 -1
- package/dist/bin/git-sweep.js +315 -38
- package/dist/bin/graph-backfill.js +13 -1
- package/dist/bin/graph-export.js +13 -1
- package/dist/bin/intercom-check.js +222 -38
- package/dist/bin/scan-tasks.js +315 -38
- package/dist/bin/setup.js +14 -1
- package/dist/bin/shard-migrate.js +13 -1
- package/dist/gateway/index.js +315 -38
- package/dist/hooks/bug-report-worker.js +315 -38
- package/dist/hooks/codex-stop-task-finalizer.js +277 -26
- package/dist/hooks/commit-complete.js +315 -38
- package/dist/hooks/error-recall.js +14 -1
- package/dist/hooks/ingest.js +330 -39
- package/dist/hooks/instructions-loaded.js +13 -1
- package/dist/hooks/notification.js +13 -1
- package/dist/hooks/post-compact.js +403 -61
- package/dist/hooks/post-tool-combined.js +480 -137
- package/dist/hooks/pre-compact.js +315 -38
- package/dist/hooks/pre-tool-use.js +34 -2
- package/dist/hooks/prompt-submit.js +315 -38
- package/dist/hooks/session-end.js +125 -38
- package/dist/hooks/session-start.js +35 -2
- package/dist/hooks/stop.js +397 -61
- package/dist/hooks/subagent-stop.js +396 -61
- package/dist/hooks/summary-worker.js +410 -112
- package/dist/index.js +315 -38
- package/dist/lib/cloud-sync.js +1 -1
- package/dist/lib/config.js +13 -0
- package/dist/lib/embedder.js +13 -0
- package/dist/lib/exe-daemon.js +351 -44
- package/dist/lib/hybrid-search.js +14 -1
- package/dist/lib/keychain.js +1 -1
- package/dist/lib/messaging.js +395 -74
- package/dist/lib/schedules.js +13 -1
- package/dist/lib/skill-learning.js +13 -0
- package/dist/lib/store.js +13 -1
- package/dist/lib/tasks.js +314 -37
- package/dist/lib/tmux-routing.js +314 -37
- package/dist/mcp/server.js +331 -40
- package/dist/mcp/tools/create-task.js +314 -37
- 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 +314 -37
- package/dist/runtime/index.js +315 -38
- package/dist/tui/App.js +317 -40
- package/package.json +1 -1
package/dist/bin/cli.js
CHANGED
|
@@ -155,6 +155,17 @@ function normalizeOrchestration(raw) {
|
|
|
155
155
|
const userOrg = raw.orchestration ?? {};
|
|
156
156
|
raw.orchestration = { ...defaultOrg, ...userOrg };
|
|
157
157
|
}
|
|
158
|
+
function normalizeCloudEndpoint(raw) {
|
|
159
|
+
const cloud = raw.cloud;
|
|
160
|
+
if (!cloud?.endpoint) return;
|
|
161
|
+
const ep = String(cloud.endpoint);
|
|
162
|
+
if (ep === "https://askexe.com/cloud" || ep === "https://askexe.com/cloud/") {
|
|
163
|
+
cloud.endpoint = "https://cloud.askexe.com";
|
|
164
|
+
process.stderr.write(
|
|
165
|
+
"[config] Auto-migrated cloud endpoint: askexe.com/cloud \u2192 cloud.askexe.com\n"
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
158
169
|
async function loadConfig() {
|
|
159
170
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
160
171
|
await ensurePrivateDir(dir);
|
|
@@ -180,6 +191,7 @@ async function loadConfig() {
|
|
|
180
191
|
normalizeSessionLifecycle(migratedCfg);
|
|
181
192
|
normalizeAutoUpdate(migratedCfg);
|
|
182
193
|
normalizeOrchestration(migratedCfg);
|
|
194
|
+
normalizeCloudEndpoint(migratedCfg);
|
|
183
195
|
const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
|
|
184
196
|
if (config.dbPath.startsWith("~")) {
|
|
185
197
|
config.dbPath = config.dbPath.replace(/^~/, os.homedir());
|
|
@@ -208,6 +220,7 @@ function loadConfigSync() {
|
|
|
208
220
|
normalizeSessionLifecycle(migratedCfg);
|
|
209
221
|
normalizeAutoUpdate(migratedCfg);
|
|
210
222
|
normalizeOrchestration(migratedCfg);
|
|
223
|
+
normalizeCloudEndpoint(migratedCfg);
|
|
211
224
|
const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
|
|
212
225
|
if (config.dbPath.startsWith("~")) {
|
|
213
226
|
config.dbPath = config.dbPath.replace(/^~/, os.homedir());
|
|
@@ -2840,7 +2853,7 @@ async function getMasterKey() {
|
|
|
2840
2853
|
b64Value = content;
|
|
2841
2854
|
}
|
|
2842
2855
|
const key = Buffer.from(b64Value, "base64");
|
|
2843
|
-
if (
|
|
2856
|
+
if (isRootOnlyTrustedServerKeyFile(keyPath)) {
|
|
2844
2857
|
return key;
|
|
2845
2858
|
}
|
|
2846
2859
|
const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
|
|
@@ -13960,6 +13973,7 @@ var init_provider_table = __esm({
|
|
|
13960
13973
|
// src/lib/intercom-queue.ts
|
|
13961
13974
|
var intercom_queue_exports = {};
|
|
13962
13975
|
__export(intercom_queue_exports, {
|
|
13976
|
+
_resetDrainGuard: () => _resetDrainGuard,
|
|
13963
13977
|
clearQueueForAgent: () => clearQueueForAgent,
|
|
13964
13978
|
drainForSession: () => drainForSession,
|
|
13965
13979
|
drainQueue: () => drainQueue,
|
|
@@ -14005,38 +14019,47 @@ function queueIntercom(targetSession, reason) {
|
|
|
14005
14019
|
writeQueue(queue);
|
|
14006
14020
|
}
|
|
14007
14021
|
function drainQueue(isSessionBusy2, sendKeys) {
|
|
14022
|
+
if (_draining) {
|
|
14023
|
+
logQueue("SKIP_DRAIN \u2014 previous drain still running (possible tmux hang)");
|
|
14024
|
+
return { drained: 0, failed: 0 };
|
|
14025
|
+
}
|
|
14008
14026
|
const queue = readQueue();
|
|
14009
14027
|
if (queue.length === 0) return { drained: 0, failed: 0 };
|
|
14028
|
+
_draining = true;
|
|
14010
14029
|
const remaining = [];
|
|
14011
14030
|
let drained = 0;
|
|
14012
14031
|
let failed = 0;
|
|
14013
|
-
|
|
14014
|
-
const
|
|
14015
|
-
|
|
14016
|
-
|
|
14017
|
-
|
|
14018
|
-
|
|
14019
|
-
|
|
14020
|
-
|
|
14021
|
-
|
|
14022
|
-
|
|
14023
|
-
|
|
14024
|
-
|
|
14025
|
-
|
|
14026
|
-
|
|
14032
|
+
try {
|
|
14033
|
+
for (const item of queue) {
|
|
14034
|
+
const age = Date.now() - new Date(item.queuedAt).getTime();
|
|
14035
|
+
if (age > TTL_MS) {
|
|
14036
|
+
logQueue(`EXPIRED \u2192 ${item.targetSession} (${Math.round(age / 6e4)}min old, reason: ${item.reason})`);
|
|
14037
|
+
failed++;
|
|
14038
|
+
continue;
|
|
14039
|
+
}
|
|
14040
|
+
try {
|
|
14041
|
+
if (!isSessionBusy2(item.targetSession)) {
|
|
14042
|
+
const success = sendKeys(item.targetSession);
|
|
14043
|
+
if (success) {
|
|
14044
|
+
logQueue(`DRAINED \u2192 ${item.targetSession} (after ${item.attempts} retries)`);
|
|
14045
|
+
drained++;
|
|
14046
|
+
continue;
|
|
14047
|
+
}
|
|
14027
14048
|
}
|
|
14049
|
+
} catch {
|
|
14028
14050
|
}
|
|
14029
|
-
|
|
14030
|
-
|
|
14031
|
-
|
|
14032
|
-
|
|
14033
|
-
|
|
14034
|
-
|
|
14035
|
-
|
|
14051
|
+
item.attempts++;
|
|
14052
|
+
if (item.attempts >= MAX_RETRIES2) {
|
|
14053
|
+
logQueue(`FAILED \u2192 ${item.targetSession} (${MAX_RETRIES2} retries exhausted, reason: ${item.reason})`);
|
|
14054
|
+
failed++;
|
|
14055
|
+
continue;
|
|
14056
|
+
}
|
|
14057
|
+
remaining.push(item);
|
|
14036
14058
|
}
|
|
14037
|
-
remaining
|
|
14059
|
+
writeQueue(remaining);
|
|
14060
|
+
} finally {
|
|
14061
|
+
_draining = false;
|
|
14038
14062
|
}
|
|
14039
|
-
writeQueue(remaining);
|
|
14040
14063
|
return { drained, failed };
|
|
14041
14064
|
}
|
|
14042
14065
|
function drainForSession(targetSession, sendKeys) {
|
|
@@ -14061,6 +14084,9 @@ function clearQueueForAgent(agentName) {
|
|
|
14061
14084
|
logQueue(`CLEARED ${before - filtered.length} stale item(s) for ${agentName}`);
|
|
14062
14085
|
}
|
|
14063
14086
|
}
|
|
14087
|
+
function _resetDrainGuard() {
|
|
14088
|
+
_draining = false;
|
|
14089
|
+
}
|
|
14064
14090
|
function logQueue(msg) {
|
|
14065
14091
|
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] [queue] ${msg}
|
|
14066
14092
|
`;
|
|
@@ -14072,13 +14098,14 @@ function logQueue(msg) {
|
|
|
14072
14098
|
} catch {
|
|
14073
14099
|
}
|
|
14074
14100
|
}
|
|
14075
|
-
var QUEUE_PATH, MAX_RETRIES2, TTL_MS, INTERCOM_LOG;
|
|
14101
|
+
var QUEUE_PATH, MAX_RETRIES2, TTL_MS, _draining, INTERCOM_LOG;
|
|
14076
14102
|
var init_intercom_queue = __esm({
|
|
14077
14103
|
"src/lib/intercom-queue.ts"() {
|
|
14078
14104
|
"use strict";
|
|
14079
14105
|
QUEUE_PATH = path26.join(os15.homedir(), ".exe-os", "intercom-queue.json");
|
|
14080
14106
|
MAX_RETRIES2 = 5;
|
|
14081
14107
|
TTL_MS = 60 * 60 * 1e3;
|
|
14108
|
+
_draining = false;
|
|
14082
14109
|
INTERCOM_LOG = path26.join(os15.homedir(), ".exe-os", "intercom.log");
|
|
14083
14110
|
}
|
|
14084
14111
|
});
|
|
@@ -14201,6 +14228,17 @@ var init_task_scope = __esm({
|
|
|
14201
14228
|
});
|
|
14202
14229
|
|
|
14203
14230
|
// src/lib/notifications.ts
|
|
14231
|
+
var notifications_exports = {};
|
|
14232
|
+
__export(notifications_exports, {
|
|
14233
|
+
cleanupOldNotifications: () => cleanupOldNotifications,
|
|
14234
|
+
formatNotifications: () => formatNotifications,
|
|
14235
|
+
markAsRead: () => markAsRead,
|
|
14236
|
+
markAsReadByTaskFile: () => markAsReadByTaskFile,
|
|
14237
|
+
markDoneTaskNotificationsAsRead: () => markDoneTaskNotificationsAsRead,
|
|
14238
|
+
migrateJsonNotifications: () => migrateJsonNotifications,
|
|
14239
|
+
readUnreadNotifications: () => readUnreadNotifications,
|
|
14240
|
+
writeNotification: () => writeNotification
|
|
14241
|
+
});
|
|
14204
14242
|
import crypto7 from "crypto";
|
|
14205
14243
|
import path28 from "path";
|
|
14206
14244
|
import os16 from "os";
|
|
@@ -14237,6 +14275,52 @@ async function writeNotification(notification) {
|
|
|
14237
14275
|
`);
|
|
14238
14276
|
}
|
|
14239
14277
|
}
|
|
14278
|
+
async function readUnreadNotifications(agentFilter, sessionScope) {
|
|
14279
|
+
try {
|
|
14280
|
+
const client = getClient();
|
|
14281
|
+
const conditions = ["read = 0"];
|
|
14282
|
+
const args2 = [];
|
|
14283
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
14284
|
+
if (agentFilter) {
|
|
14285
|
+
conditions.push("agent_id = ?");
|
|
14286
|
+
args2.push(agentFilter);
|
|
14287
|
+
}
|
|
14288
|
+
const result = await client.execute({
|
|
14289
|
+
sql: `SELECT id, agent_id, agent_role, event, project, summary, task_file, session_scope, created_at
|
|
14290
|
+
FROM notifications
|
|
14291
|
+
WHERE ${conditions.join(" AND ")}${scope.sql}
|
|
14292
|
+
ORDER BY created_at ASC`,
|
|
14293
|
+
args: [...args2, ...scope.args]
|
|
14294
|
+
});
|
|
14295
|
+
return result.rows.map((r) => ({
|
|
14296
|
+
id: String(r.id),
|
|
14297
|
+
agentId: String(r.agent_id),
|
|
14298
|
+
agentRole: String(r.agent_role),
|
|
14299
|
+
event: String(r.event),
|
|
14300
|
+
project: String(r.project),
|
|
14301
|
+
summary: String(r.summary),
|
|
14302
|
+
taskFile: r.task_file ? String(r.task_file) : void 0,
|
|
14303
|
+
sessionScope: r.session_scope == null ? null : String(r.session_scope),
|
|
14304
|
+
timestamp: String(r.created_at),
|
|
14305
|
+
read: false
|
|
14306
|
+
}));
|
|
14307
|
+
} catch {
|
|
14308
|
+
return [];
|
|
14309
|
+
}
|
|
14310
|
+
}
|
|
14311
|
+
async function markAsRead(ids, sessionScope) {
|
|
14312
|
+
if (ids.length === 0) return;
|
|
14313
|
+
try {
|
|
14314
|
+
const client = getClient();
|
|
14315
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
14316
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
14317
|
+
await client.execute({
|
|
14318
|
+
sql: `UPDATE notifications SET read = 1 WHERE id IN (${placeholders})${scope.sql}`,
|
|
14319
|
+
args: [...ids, ...scope.args]
|
|
14320
|
+
});
|
|
14321
|
+
} catch {
|
|
14322
|
+
}
|
|
14323
|
+
}
|
|
14240
14324
|
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
14241
14325
|
try {
|
|
14242
14326
|
const client = getClient();
|
|
@@ -14249,11 +14333,144 @@ async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
|
14249
14333
|
} catch {
|
|
14250
14334
|
}
|
|
14251
14335
|
}
|
|
14336
|
+
async function cleanupOldNotifications(daysOld = CLEANUP_DAYS, sessionScope) {
|
|
14337
|
+
try {
|
|
14338
|
+
const client = getClient();
|
|
14339
|
+
const cutoff = new Date(
|
|
14340
|
+
Date.now() - daysOld * 24 * 60 * 60 * 1e3
|
|
14341
|
+
).toISOString();
|
|
14342
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
14343
|
+
const result = await client.execute({
|
|
14344
|
+
sql: `DELETE FROM notifications WHERE created_at < ?${scope.sql}`,
|
|
14345
|
+
args: [cutoff, ...scope.args]
|
|
14346
|
+
});
|
|
14347
|
+
return result.rowsAffected;
|
|
14348
|
+
} catch {
|
|
14349
|
+
return 0;
|
|
14350
|
+
}
|
|
14351
|
+
}
|
|
14352
|
+
async function markDoneTaskNotificationsAsRead(sessionScope) {
|
|
14353
|
+
try {
|
|
14354
|
+
const client = getClient();
|
|
14355
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
14356
|
+
const result = await client.execute({
|
|
14357
|
+
sql: `UPDATE notifications SET read = 1
|
|
14358
|
+
WHERE read = 0
|
|
14359
|
+
AND task_file IS NOT NULL
|
|
14360
|
+
${scope.sql}
|
|
14361
|
+
AND task_file IN (
|
|
14362
|
+
SELECT task_file FROM tasks WHERE status = 'done'${scope.sql}
|
|
14363
|
+
)`,
|
|
14364
|
+
args: [...scope.args, ...scope.args]
|
|
14365
|
+
});
|
|
14366
|
+
return result.rowsAffected;
|
|
14367
|
+
} catch {
|
|
14368
|
+
return 0;
|
|
14369
|
+
}
|
|
14370
|
+
}
|
|
14371
|
+
function formatNotifications(notifications) {
|
|
14372
|
+
if (notifications.length === 0) return "";
|
|
14373
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
14374
|
+
for (const n of notifications) {
|
|
14375
|
+
const key = `${n.agentId}|${n.agentRole}`;
|
|
14376
|
+
if (!grouped.has(key)) grouped.set(key, []);
|
|
14377
|
+
grouped.get(key).push(n);
|
|
14378
|
+
}
|
|
14379
|
+
const lines = [];
|
|
14380
|
+
lines.push(`## Notifications (${notifications.length} unread)
|
|
14381
|
+
`);
|
|
14382
|
+
for (const [key, items] of grouped) {
|
|
14383
|
+
const [agentId, agentRole] = key.split("|");
|
|
14384
|
+
lines.push(`**${agentId}** (${agentRole}):`);
|
|
14385
|
+
for (const item of items) {
|
|
14386
|
+
const ago = formatTimeAgo(item.timestamp);
|
|
14387
|
+
const icon = eventIcon(item.event);
|
|
14388
|
+
lines.push(`- ${icon} ${item.summary} (${item.project}) \u2014 ${ago}`);
|
|
14389
|
+
}
|
|
14390
|
+
lines.push("");
|
|
14391
|
+
}
|
|
14392
|
+
return lines.join("\n");
|
|
14393
|
+
}
|
|
14394
|
+
async function migrateJsonNotifications() {
|
|
14395
|
+
const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR || path28.join(os16.homedir(), ".exe-os");
|
|
14396
|
+
const notifDir = path28.join(base, "notifications");
|
|
14397
|
+
if (!existsSync26(notifDir)) return 0;
|
|
14398
|
+
let migrated = 0;
|
|
14399
|
+
try {
|
|
14400
|
+
const files = readdirSync5(notifDir).filter((f) => f.endsWith(".json"));
|
|
14401
|
+
if (files.length === 0) return 0;
|
|
14402
|
+
const client = getClient();
|
|
14403
|
+
for (const file of files) {
|
|
14404
|
+
try {
|
|
14405
|
+
const filePath = path28.join(notifDir, file);
|
|
14406
|
+
const data = JSON.parse(readFileSync21(filePath, "utf8"));
|
|
14407
|
+
await client.execute({
|
|
14408
|
+
sql: `INSERT OR IGNORE INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
14409
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
14410
|
+
args: [
|
|
14411
|
+
crypto7.randomUUID(),
|
|
14412
|
+
data.agentId ?? "unknown",
|
|
14413
|
+
data.agentRole ?? "unknown",
|
|
14414
|
+
data.event ?? "session_summary",
|
|
14415
|
+
data.project ?? "unknown",
|
|
14416
|
+
data.summary ?? "",
|
|
14417
|
+
data.taskFile ?? null,
|
|
14418
|
+
null,
|
|
14419
|
+
data.read ? 1 : 0,
|
|
14420
|
+
data.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
14421
|
+
]
|
|
14422
|
+
});
|
|
14423
|
+
unlinkSync9(filePath);
|
|
14424
|
+
migrated++;
|
|
14425
|
+
} catch {
|
|
14426
|
+
}
|
|
14427
|
+
}
|
|
14428
|
+
try {
|
|
14429
|
+
const remaining = readdirSync5(notifDir);
|
|
14430
|
+
if (remaining.length === 0) {
|
|
14431
|
+
rmdirSync(notifDir);
|
|
14432
|
+
}
|
|
14433
|
+
} catch {
|
|
14434
|
+
}
|
|
14435
|
+
} catch {
|
|
14436
|
+
}
|
|
14437
|
+
return migrated;
|
|
14438
|
+
}
|
|
14439
|
+
function eventIcon(event) {
|
|
14440
|
+
switch (event) {
|
|
14441
|
+
case "task_complete":
|
|
14442
|
+
return "Completed:";
|
|
14443
|
+
case "task_needs_fix":
|
|
14444
|
+
return "Needs fix:";
|
|
14445
|
+
case "session_summary":
|
|
14446
|
+
return "Session:";
|
|
14447
|
+
case "error_spike":
|
|
14448
|
+
return "Errors:";
|
|
14449
|
+
case "orphan_task":
|
|
14450
|
+
return "Orphan:";
|
|
14451
|
+
case "subtasks_complete":
|
|
14452
|
+
return "Subtasks done:";
|
|
14453
|
+
case "capacity_relaunch":
|
|
14454
|
+
return "Relaunched:";
|
|
14455
|
+
}
|
|
14456
|
+
}
|
|
14457
|
+
function formatTimeAgo(timestamp) {
|
|
14458
|
+
const diffMs = Date.now() - new Date(timestamp).getTime();
|
|
14459
|
+
const mins = Math.floor(diffMs / 6e4);
|
|
14460
|
+
if (mins < 1) return "just now";
|
|
14461
|
+
if (mins < 60) return `${mins}m ago`;
|
|
14462
|
+
const hours = Math.floor(mins / 60);
|
|
14463
|
+
if (hours < 24) return `${hours}h ago`;
|
|
14464
|
+
const days = Math.floor(hours / 24);
|
|
14465
|
+
return `${days}d ago`;
|
|
14466
|
+
}
|
|
14467
|
+
var CLEANUP_DAYS;
|
|
14252
14468
|
var init_notifications = __esm({
|
|
14253
14469
|
"src/lib/notifications.ts"() {
|
|
14254
14470
|
"use strict";
|
|
14255
14471
|
init_database();
|
|
14256
14472
|
init_task_scope();
|
|
14473
|
+
CLEANUP_DAYS = 7;
|
|
14257
14474
|
}
|
|
14258
14475
|
});
|
|
14259
14476
|
|
|
@@ -15260,7 +15477,13 @@ async function createReviewForCompletedTask(row, result, _baseDir, now2) {
|
|
|
15260
15477
|
taskFile
|
|
15261
15478
|
});
|
|
15262
15479
|
const originalPriority = String(row.priority).toLowerCase();
|
|
15263
|
-
const
|
|
15480
|
+
const resultLower = result?.toLowerCase() ?? "";
|
|
15481
|
+
const hasTestEvidence = (
|
|
15482
|
+
// Vitest/Jest output patterns (hard to fake without actually running tests)
|
|
15483
|
+
/\d+\s+pass(ed|ing)/.test(resultLower) || /test files?\s+\d+\s+passed/.test(resultLower) || /tests?\s+\d+\s+passed/.test(resultLower)
|
|
15484
|
+
);
|
|
15485
|
+
const hasNoFailures = !/fail(ed|ure|ing)|error/i.test(resultLower);
|
|
15486
|
+
const autoApprove = originalPriority === "p2" && hasTestEvidence && hasNoFailures;
|
|
15264
15487
|
if (!autoApprove) {
|
|
15265
15488
|
try {
|
|
15266
15489
|
const key = getSessionKey();
|
|
@@ -15268,6 +15491,13 @@ async function createReviewForCompletedTask(row, result, _baseDir, now2) {
|
|
|
15268
15491
|
if (exeSession) {
|
|
15269
15492
|
sendIntercom(exeSession);
|
|
15270
15493
|
}
|
|
15494
|
+
if (reviewer && reviewer !== coordinatorName && reviewer !== exeSession) {
|
|
15495
|
+
const { employeeSessionName: employeeSessionName2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
15496
|
+
if (exeSession) {
|
|
15497
|
+
const reviewerSession = employeeSessionName2(reviewer, exeSession);
|
|
15498
|
+
sendIntercom(reviewerSession);
|
|
15499
|
+
}
|
|
15500
|
+
}
|
|
15271
15501
|
} catch {
|
|
15272
15502
|
}
|
|
15273
15503
|
}
|
|
@@ -16043,6 +16273,20 @@ async function updateTask(input) {
|
|
|
16043
16273
|
notifyTaskDone();
|
|
16044
16274
|
}
|
|
16045
16275
|
await markTaskNotificationsRead(taskFile);
|
|
16276
|
+
if (input.status === "needs_review" && !isCoordinator) {
|
|
16277
|
+
try {
|
|
16278
|
+
const { writeNotification: writeNotification2 } = await Promise.resolve().then(() => (init_notifications(), notifications_exports));
|
|
16279
|
+
await writeNotification2({
|
|
16280
|
+
agentId: String(row.assigned_to),
|
|
16281
|
+
agentRole: String(row.assigned_to),
|
|
16282
|
+
event: "task_complete",
|
|
16283
|
+
project: String(row.project_name),
|
|
16284
|
+
summary: `"${String(row.title)}" is ready for review`,
|
|
16285
|
+
taskFile
|
|
16286
|
+
});
|
|
16287
|
+
} catch {
|
|
16288
|
+
}
|
|
16289
|
+
}
|
|
16046
16290
|
if (input.status === "done" || input.status === "closed") {
|
|
16047
16291
|
try {
|
|
16048
16292
|
await cascadeUnblock(taskId, input.baseDir, now2);
|
|
@@ -16475,18 +16719,31 @@ function acquireSpawnLock2(sessionName) {
|
|
|
16475
16719
|
mkdirSync21(SPAWN_LOCK_DIR, { recursive: true });
|
|
16476
16720
|
}
|
|
16477
16721
|
const lockFile = spawnLockPath(sessionName);
|
|
16478
|
-
|
|
16479
|
-
|
|
16480
|
-
|
|
16481
|
-
|
|
16482
|
-
|
|
16483
|
-
|
|
16484
|
-
|
|
16485
|
-
|
|
16722
|
+
const lockData = JSON.stringify({ pid: process.pid, timestamp: Date.now() });
|
|
16723
|
+
const { openSync: openSync5, closeSync: closeSync5, writeSync } = __require("fs");
|
|
16724
|
+
const { constants: constants3 } = __require("fs");
|
|
16725
|
+
try {
|
|
16726
|
+
const fd = openSync5(lockFile, constants3.O_WRONLY | constants3.O_CREAT | constants3.O_EXCL, 420);
|
|
16727
|
+
writeSync(fd, lockData);
|
|
16728
|
+
closeSync5(fd);
|
|
16729
|
+
return true;
|
|
16730
|
+
} catch (err) {
|
|
16731
|
+
if (err?.code !== "EEXIST") {
|
|
16732
|
+
return true;
|
|
16486
16733
|
}
|
|
16487
16734
|
}
|
|
16488
|
-
|
|
16489
|
-
|
|
16735
|
+
try {
|
|
16736
|
+
const lock = JSON.parse(readFileSync23(lockFile, "utf8"));
|
|
16737
|
+
const age = Date.now() - lock.timestamp;
|
|
16738
|
+
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
16739
|
+
return false;
|
|
16740
|
+
}
|
|
16741
|
+
writeFileSync20(lockFile, lockData);
|
|
16742
|
+
return true;
|
|
16743
|
+
} catch {
|
|
16744
|
+
writeFileSync20(lockFile, lockData);
|
|
16745
|
+
return true;
|
|
16746
|
+
}
|
|
16490
16747
|
}
|
|
16491
16748
|
function releaseSpawnLock2(sessionName) {
|
|
16492
16749
|
try {
|
|
@@ -16565,6 +16822,21 @@ function parseParentExe(sessionName, agentId) {
|
|
|
16565
16822
|
function extractRootExe(name) {
|
|
16566
16823
|
if (!name) return null;
|
|
16567
16824
|
if (!name.includes("-")) return name;
|
|
16825
|
+
try {
|
|
16826
|
+
const roster = (init_employees(), __toCommonJS(employees_exports)).loadEmployeesSync();
|
|
16827
|
+
if (roster.length > 0) {
|
|
16828
|
+
const sortedNames = roster.map((e) => e.name).sort((a, b) => b.length - a.length);
|
|
16829
|
+
for (const agentName of sortedNames) {
|
|
16830
|
+
const escaped = agentName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
16831
|
+
const regex = new RegExp(`^${escaped}\\d*-(.+)$`);
|
|
16832
|
+
const match = name.match(regex);
|
|
16833
|
+
if (match) {
|
|
16834
|
+
return extractRootExe(match[1]);
|
|
16835
|
+
}
|
|
16836
|
+
}
|
|
16837
|
+
}
|
|
16838
|
+
} catch {
|
|
16839
|
+
}
|
|
16568
16840
|
const parts = name.split("-").filter(Boolean);
|
|
16569
16841
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
16570
16842
|
}
|
|
@@ -16583,6 +16855,10 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
16583
16855
|
function getParentExe(sessionKey) {
|
|
16584
16856
|
try {
|
|
16585
16857
|
const data = JSON.parse(readFileSync23(path34.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
16858
|
+
if (data.registeredAt) {
|
|
16859
|
+
const age = Date.now() - new Date(data.registeredAt).getTime();
|
|
16860
|
+
if (age > PARENT_EXE_CACHE_TTL_MS) return null;
|
|
16861
|
+
}
|
|
16586
16862
|
return data.parentExe || null;
|
|
16587
16863
|
} catch {
|
|
16588
16864
|
return null;
|
|
@@ -17131,7 +17407,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
17131
17407
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
17132
17408
|
} catch {
|
|
17133
17409
|
}
|
|
17134
|
-
let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName}`;
|
|
17410
|
+
let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName} EXE_SESSION_START_ISO=${(/* @__PURE__ */ new Date()).toISOString()}`;
|
|
17135
17411
|
if (ccProvider !== DEFAULT_PROVIDER) {
|
|
17136
17412
|
const cfg = PROVIDER_TABLE[ccProvider];
|
|
17137
17413
|
if (cfg?.apiKeyEnv) {
|
|
@@ -17268,7 +17544,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
17268
17544
|
releaseSpawnLock2(sessionName);
|
|
17269
17545
|
return { sessionName };
|
|
17270
17546
|
}
|
|
17271
|
-
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;
|
|
17547
|
+
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;
|
|
17272
17548
|
var init_tmux_routing = __esm({
|
|
17273
17549
|
"src/lib/tmux-routing.ts"() {
|
|
17274
17550
|
"use strict";
|
|
@@ -17288,6 +17564,7 @@ var init_tmux_routing = __esm({
|
|
|
17288
17564
|
SESSION_CACHE = path34.join(os18.homedir(), ".exe-os", "session-cache");
|
|
17289
17565
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
17290
17566
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
17567
|
+
PARENT_EXE_CACHE_TTL_MS = 4 * 60 * 60 * 1e3;
|
|
17291
17568
|
VERIFY_PANE_LINES = 200;
|
|
17292
17569
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
17293
17570
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
@@ -35767,7 +36044,7 @@ function agentColor(agentId) {
|
|
|
35767
36044
|
return "#F0EDE8";
|
|
35768
36045
|
}
|
|
35769
36046
|
}
|
|
35770
|
-
function
|
|
36047
|
+
function formatTimeAgo2(iso) {
|
|
35771
36048
|
const diff2 = Date.now() - new Date(iso).getTime();
|
|
35772
36049
|
const hours = Math.floor(diff2 / 36e5);
|
|
35773
36050
|
if (hours < 1) return "just now";
|
|
@@ -36093,7 +36370,7 @@ function WikiView({ onBack }) {
|
|
|
36093
36370
|
result.rawText.length > 80 ? "..." : ""
|
|
36094
36371
|
] }),
|
|
36095
36372
|
" ",
|
|
36096
|
-
/* @__PURE__ */ jsx13(Text, { color: isSelected ? "#F5D76E" : "#3D3660", dimColor: !isSelected, children:
|
|
36373
|
+
/* @__PURE__ */ jsx13(Text, { color: isSelected ? "#F5D76E" : "#3D3660", dimColor: !isSelected, children: formatTimeAgo2(result.timestamp) })
|
|
36097
36374
|
]
|
|
36098
36375
|
}
|
|
36099
36376
|
),
|
package/dist/bin/exe-assign.js
CHANGED
|
@@ -129,6 +129,17 @@ function normalizeOrchestration(raw) {
|
|
|
129
129
|
const userOrg = raw.orchestration ?? {};
|
|
130
130
|
raw.orchestration = { ...defaultOrg, ...userOrg };
|
|
131
131
|
}
|
|
132
|
+
function normalizeCloudEndpoint(raw) {
|
|
133
|
+
const cloud = raw.cloud;
|
|
134
|
+
if (!cloud?.endpoint) return;
|
|
135
|
+
const ep = String(cloud.endpoint);
|
|
136
|
+
if (ep === "https://askexe.com/cloud" || ep === "https://askexe.com/cloud/") {
|
|
137
|
+
cloud.endpoint = "https://cloud.askexe.com";
|
|
138
|
+
process.stderr.write(
|
|
139
|
+
"[config] Auto-migrated cloud endpoint: askexe.com/cloud \u2192 cloud.askexe.com\n"
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
132
143
|
async function loadConfig() {
|
|
133
144
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
134
145
|
await ensurePrivateDir(dir);
|
|
@@ -154,6 +165,7 @@ async function loadConfig() {
|
|
|
154
165
|
normalizeSessionLifecycle(migratedCfg);
|
|
155
166
|
normalizeAutoUpdate(migratedCfg);
|
|
156
167
|
normalizeOrchestration(migratedCfg);
|
|
168
|
+
normalizeCloudEndpoint(migratedCfg);
|
|
157
169
|
const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
|
|
158
170
|
if (config.dbPath.startsWith("~")) {
|
|
159
171
|
config.dbPath = config.dbPath.replace(/^~/, os.homedir());
|
|
@@ -4963,7 +4975,7 @@ async function getMasterKey() {
|
|
|
4963
4975
|
b64Value = content;
|
|
4964
4976
|
}
|
|
4965
4977
|
const key = Buffer.from(b64Value, "base64");
|
|
4966
|
-
if (
|
|
4978
|
+
if (isRootOnlyTrustedServerKeyFile(keyPath)) {
|
|
4967
4979
|
return key;
|
|
4968
4980
|
}
|
|
4969
4981
|
const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
|