@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());
|
|
@@ -435,6 +448,7 @@ __export(agent_config_exports, {
|
|
|
435
448
|
clearAgentRuntime: () => clearAgentRuntime,
|
|
436
449
|
getAgentRuntime: () => getAgentRuntime,
|
|
437
450
|
loadAgentConfig: () => loadAgentConfig,
|
|
451
|
+
normalizeCcModelName: () => normalizeCcModelName,
|
|
438
452
|
saveAgentConfig: () => saveAgentConfig,
|
|
439
453
|
setAgentMcps: () => setAgentMcps,
|
|
440
454
|
setAgentRuntime: () => setAgentRuntime
|
|
@@ -463,6 +477,13 @@ function getAgentRuntime(agentId) {
|
|
|
463
477
|
if (orgDefault) return orgDefault;
|
|
464
478
|
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
465
479
|
}
|
|
480
|
+
function normalizeCcModelName(model) {
|
|
481
|
+
let ccModel = model.replace(/(\d+)\.(\d+)/g, "$1-$2");
|
|
482
|
+
if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
|
|
483
|
+
ccModel += "[1m]";
|
|
484
|
+
}
|
|
485
|
+
return ccModel;
|
|
486
|
+
}
|
|
466
487
|
function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
|
|
467
488
|
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
468
489
|
if (!knownModels) {
|
|
@@ -1013,6 +1034,7 @@ var init_provider_table = __esm({
|
|
|
1013
1034
|
// src/lib/intercom-queue.ts
|
|
1014
1035
|
var intercom_queue_exports = {};
|
|
1015
1036
|
__export(intercom_queue_exports, {
|
|
1037
|
+
_resetDrainGuard: () => _resetDrainGuard,
|
|
1016
1038
|
clearQueueForAgent: () => clearQueueForAgent,
|
|
1017
1039
|
drainForSession: () => drainForSession,
|
|
1018
1040
|
drainQueue: () => drainQueue,
|
|
@@ -1058,38 +1080,47 @@ function queueIntercom(targetSession, reason) {
|
|
|
1058
1080
|
writeQueue(queue);
|
|
1059
1081
|
}
|
|
1060
1082
|
function drainQueue(isSessionBusy2, sendKeys) {
|
|
1083
|
+
if (_draining) {
|
|
1084
|
+
logQueue("SKIP_DRAIN \u2014 previous drain still running (possible tmux hang)");
|
|
1085
|
+
return { drained: 0, failed: 0 };
|
|
1086
|
+
}
|
|
1061
1087
|
const queue = readQueue();
|
|
1062
1088
|
if (queue.length === 0) return { drained: 0, failed: 0 };
|
|
1089
|
+
_draining = true;
|
|
1063
1090
|
const remaining = [];
|
|
1064
1091
|
let drained = 0;
|
|
1065
1092
|
let failed = 0;
|
|
1066
|
-
|
|
1067
|
-
const
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1093
|
+
try {
|
|
1094
|
+
for (const item of queue) {
|
|
1095
|
+
const age = Date.now() - new Date(item.queuedAt).getTime();
|
|
1096
|
+
if (age > TTL_MS) {
|
|
1097
|
+
logQueue(`EXPIRED \u2192 ${item.targetSession} (${Math.round(age / 6e4)}min old, reason: ${item.reason})`);
|
|
1098
|
+
failed++;
|
|
1099
|
+
continue;
|
|
1100
|
+
}
|
|
1101
|
+
try {
|
|
1102
|
+
if (!isSessionBusy2(item.targetSession)) {
|
|
1103
|
+
const success = sendKeys(item.targetSession);
|
|
1104
|
+
if (success) {
|
|
1105
|
+
logQueue(`DRAINED \u2192 ${item.targetSession} (after ${item.attempts} retries)`);
|
|
1106
|
+
drained++;
|
|
1107
|
+
continue;
|
|
1108
|
+
}
|
|
1080
1109
|
}
|
|
1110
|
+
} catch {
|
|
1081
1111
|
}
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1112
|
+
item.attempts++;
|
|
1113
|
+
if (item.attempts >= MAX_RETRIES) {
|
|
1114
|
+
logQueue(`FAILED \u2192 ${item.targetSession} (${MAX_RETRIES} retries exhausted, reason: ${item.reason})`);
|
|
1115
|
+
failed++;
|
|
1116
|
+
continue;
|
|
1117
|
+
}
|
|
1118
|
+
remaining.push(item);
|
|
1089
1119
|
}
|
|
1090
|
-
remaining
|
|
1120
|
+
writeQueue(remaining);
|
|
1121
|
+
} finally {
|
|
1122
|
+
_draining = false;
|
|
1091
1123
|
}
|
|
1092
|
-
writeQueue(remaining);
|
|
1093
1124
|
return { drained, failed };
|
|
1094
1125
|
}
|
|
1095
1126
|
function drainForSession(targetSession, sendKeys) {
|
|
@@ -1114,6 +1145,9 @@ function clearQueueForAgent(agentName) {
|
|
|
1114
1145
|
logQueue(`CLEARED ${before - filtered.length} stale item(s) for ${agentName}`);
|
|
1115
1146
|
}
|
|
1116
1147
|
}
|
|
1148
|
+
function _resetDrainGuard() {
|
|
1149
|
+
_draining = false;
|
|
1150
|
+
}
|
|
1117
1151
|
function logQueue(msg) {
|
|
1118
1152
|
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] [queue] ${msg}
|
|
1119
1153
|
`;
|
|
@@ -1125,13 +1159,14 @@ function logQueue(msg) {
|
|
|
1125
1159
|
} catch {
|
|
1126
1160
|
}
|
|
1127
1161
|
}
|
|
1128
|
-
var QUEUE_PATH, MAX_RETRIES, TTL_MS, INTERCOM_LOG;
|
|
1162
|
+
var QUEUE_PATH, MAX_RETRIES, TTL_MS, _draining, INTERCOM_LOG;
|
|
1129
1163
|
var init_intercom_queue = __esm({
|
|
1130
1164
|
"src/lib/intercom-queue.ts"() {
|
|
1131
1165
|
"use strict";
|
|
1132
1166
|
QUEUE_PATH = path6.join(os4.homedir(), ".exe-os", "intercom-queue.json");
|
|
1133
1167
|
MAX_RETRIES = 5;
|
|
1134
1168
|
TTL_MS = 60 * 60 * 1e3;
|
|
1169
|
+
_draining = false;
|
|
1135
1170
|
INTERCOM_LOG = path6.join(os4.homedir(), ".exe-os", "intercom.log");
|
|
1136
1171
|
}
|
|
1137
1172
|
});
|
|
@@ -4768,6 +4803,17 @@ var init_agent_symlinks = __esm({
|
|
|
4768
4803
|
});
|
|
4769
4804
|
|
|
4770
4805
|
// src/lib/notifications.ts
|
|
4806
|
+
var notifications_exports = {};
|
|
4807
|
+
__export(notifications_exports, {
|
|
4808
|
+
cleanupOldNotifications: () => cleanupOldNotifications,
|
|
4809
|
+
formatNotifications: () => formatNotifications,
|
|
4810
|
+
markAsRead: () => markAsRead,
|
|
4811
|
+
markAsReadByTaskFile: () => markAsReadByTaskFile,
|
|
4812
|
+
markDoneTaskNotificationsAsRead: () => markDoneTaskNotificationsAsRead,
|
|
4813
|
+
migrateJsonNotifications: () => migrateJsonNotifications,
|
|
4814
|
+
readUnreadNotifications: () => readUnreadNotifications,
|
|
4815
|
+
writeNotification: () => writeNotification
|
|
4816
|
+
});
|
|
4771
4817
|
import crypto2 from "crypto";
|
|
4772
4818
|
import path13 from "path";
|
|
4773
4819
|
import os9 from "os";
|
|
@@ -4804,6 +4850,52 @@ async function writeNotification(notification) {
|
|
|
4804
4850
|
`);
|
|
4805
4851
|
}
|
|
4806
4852
|
}
|
|
4853
|
+
async function readUnreadNotifications(agentFilter, sessionScope) {
|
|
4854
|
+
try {
|
|
4855
|
+
const client = getClient();
|
|
4856
|
+
const conditions = ["read = 0"];
|
|
4857
|
+
const args = [];
|
|
4858
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
4859
|
+
if (agentFilter) {
|
|
4860
|
+
conditions.push("agent_id = ?");
|
|
4861
|
+
args.push(agentFilter);
|
|
4862
|
+
}
|
|
4863
|
+
const result = await client.execute({
|
|
4864
|
+
sql: `SELECT id, agent_id, agent_role, event, project, summary, task_file, session_scope, created_at
|
|
4865
|
+
FROM notifications
|
|
4866
|
+
WHERE ${conditions.join(" AND ")}${scope.sql}
|
|
4867
|
+
ORDER BY created_at ASC`,
|
|
4868
|
+
args: [...args, ...scope.args]
|
|
4869
|
+
});
|
|
4870
|
+
return result.rows.map((r) => ({
|
|
4871
|
+
id: String(r.id),
|
|
4872
|
+
agentId: String(r.agent_id),
|
|
4873
|
+
agentRole: String(r.agent_role),
|
|
4874
|
+
event: String(r.event),
|
|
4875
|
+
project: String(r.project),
|
|
4876
|
+
summary: String(r.summary),
|
|
4877
|
+
taskFile: r.task_file ? String(r.task_file) : void 0,
|
|
4878
|
+
sessionScope: r.session_scope == null ? null : String(r.session_scope),
|
|
4879
|
+
timestamp: String(r.created_at),
|
|
4880
|
+
read: false
|
|
4881
|
+
}));
|
|
4882
|
+
} catch {
|
|
4883
|
+
return [];
|
|
4884
|
+
}
|
|
4885
|
+
}
|
|
4886
|
+
async function markAsRead(ids, sessionScope) {
|
|
4887
|
+
if (ids.length === 0) return;
|
|
4888
|
+
try {
|
|
4889
|
+
const client = getClient();
|
|
4890
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
4891
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
4892
|
+
await client.execute({
|
|
4893
|
+
sql: `UPDATE notifications SET read = 1 WHERE id IN (${placeholders})${scope.sql}`,
|
|
4894
|
+
args: [...ids, ...scope.args]
|
|
4895
|
+
});
|
|
4896
|
+
} catch {
|
|
4897
|
+
}
|
|
4898
|
+
}
|
|
4807
4899
|
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
4808
4900
|
try {
|
|
4809
4901
|
const client = getClient();
|
|
@@ -4816,11 +4908,144 @@ async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
|
4816
4908
|
} catch {
|
|
4817
4909
|
}
|
|
4818
4910
|
}
|
|
4911
|
+
async function cleanupOldNotifications(daysOld = CLEANUP_DAYS, sessionScope) {
|
|
4912
|
+
try {
|
|
4913
|
+
const client = getClient();
|
|
4914
|
+
const cutoff = new Date(
|
|
4915
|
+
Date.now() - daysOld * 24 * 60 * 60 * 1e3
|
|
4916
|
+
).toISOString();
|
|
4917
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
4918
|
+
const result = await client.execute({
|
|
4919
|
+
sql: `DELETE FROM notifications WHERE created_at < ?${scope.sql}`,
|
|
4920
|
+
args: [cutoff, ...scope.args]
|
|
4921
|
+
});
|
|
4922
|
+
return result.rowsAffected;
|
|
4923
|
+
} catch {
|
|
4924
|
+
return 0;
|
|
4925
|
+
}
|
|
4926
|
+
}
|
|
4927
|
+
async function markDoneTaskNotificationsAsRead(sessionScope) {
|
|
4928
|
+
try {
|
|
4929
|
+
const client = getClient();
|
|
4930
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
4931
|
+
const result = await client.execute({
|
|
4932
|
+
sql: `UPDATE notifications SET read = 1
|
|
4933
|
+
WHERE read = 0
|
|
4934
|
+
AND task_file IS NOT NULL
|
|
4935
|
+
${scope.sql}
|
|
4936
|
+
AND task_file IN (
|
|
4937
|
+
SELECT task_file FROM tasks WHERE status = 'done'${scope.sql}
|
|
4938
|
+
)`,
|
|
4939
|
+
args: [...scope.args, ...scope.args]
|
|
4940
|
+
});
|
|
4941
|
+
return result.rowsAffected;
|
|
4942
|
+
} catch {
|
|
4943
|
+
return 0;
|
|
4944
|
+
}
|
|
4945
|
+
}
|
|
4946
|
+
function formatNotifications(notifications) {
|
|
4947
|
+
if (notifications.length === 0) return "";
|
|
4948
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
4949
|
+
for (const n of notifications) {
|
|
4950
|
+
const key = `${n.agentId}|${n.agentRole}`;
|
|
4951
|
+
if (!grouped.has(key)) grouped.set(key, []);
|
|
4952
|
+
grouped.get(key).push(n);
|
|
4953
|
+
}
|
|
4954
|
+
const lines = [];
|
|
4955
|
+
lines.push(`## Notifications (${notifications.length} unread)
|
|
4956
|
+
`);
|
|
4957
|
+
for (const [key, items] of grouped) {
|
|
4958
|
+
const [agentId, agentRole] = key.split("|");
|
|
4959
|
+
lines.push(`**${agentId}** (${agentRole}):`);
|
|
4960
|
+
for (const item of items) {
|
|
4961
|
+
const ago = formatTimeAgo(item.timestamp);
|
|
4962
|
+
const icon = eventIcon(item.event);
|
|
4963
|
+
lines.push(`- ${icon} ${item.summary} (${item.project}) \u2014 ${ago}`);
|
|
4964
|
+
}
|
|
4965
|
+
lines.push("");
|
|
4966
|
+
}
|
|
4967
|
+
return lines.join("\n");
|
|
4968
|
+
}
|
|
4969
|
+
async function migrateJsonNotifications() {
|
|
4970
|
+
const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR || path13.join(os9.homedir(), ".exe-os");
|
|
4971
|
+
const notifDir = path13.join(base, "notifications");
|
|
4972
|
+
if (!existsSync13(notifDir)) return 0;
|
|
4973
|
+
let migrated = 0;
|
|
4974
|
+
try {
|
|
4975
|
+
const files = readdirSync2(notifDir).filter((f) => f.endsWith(".json"));
|
|
4976
|
+
if (files.length === 0) return 0;
|
|
4977
|
+
const client = getClient();
|
|
4978
|
+
for (const file of files) {
|
|
4979
|
+
try {
|
|
4980
|
+
const filePath = path13.join(notifDir, file);
|
|
4981
|
+
const data = JSON.parse(readFileSync11(filePath, "utf8"));
|
|
4982
|
+
await client.execute({
|
|
4983
|
+
sql: `INSERT OR IGNORE INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
4984
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
4985
|
+
args: [
|
|
4986
|
+
crypto2.randomUUID(),
|
|
4987
|
+
data.agentId ?? "unknown",
|
|
4988
|
+
data.agentRole ?? "unknown",
|
|
4989
|
+
data.event ?? "session_summary",
|
|
4990
|
+
data.project ?? "unknown",
|
|
4991
|
+
data.summary ?? "",
|
|
4992
|
+
data.taskFile ?? null,
|
|
4993
|
+
null,
|
|
4994
|
+
data.read ? 1 : 0,
|
|
4995
|
+
data.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
4996
|
+
]
|
|
4997
|
+
});
|
|
4998
|
+
unlinkSync5(filePath);
|
|
4999
|
+
migrated++;
|
|
5000
|
+
} catch {
|
|
5001
|
+
}
|
|
5002
|
+
}
|
|
5003
|
+
try {
|
|
5004
|
+
const remaining = readdirSync2(notifDir);
|
|
5005
|
+
if (remaining.length === 0) {
|
|
5006
|
+
rmdirSync(notifDir);
|
|
5007
|
+
}
|
|
5008
|
+
} catch {
|
|
5009
|
+
}
|
|
5010
|
+
} catch {
|
|
5011
|
+
}
|
|
5012
|
+
return migrated;
|
|
5013
|
+
}
|
|
5014
|
+
function eventIcon(event) {
|
|
5015
|
+
switch (event) {
|
|
5016
|
+
case "task_complete":
|
|
5017
|
+
return "Completed:";
|
|
5018
|
+
case "task_needs_fix":
|
|
5019
|
+
return "Needs fix:";
|
|
5020
|
+
case "session_summary":
|
|
5021
|
+
return "Session:";
|
|
5022
|
+
case "error_spike":
|
|
5023
|
+
return "Errors:";
|
|
5024
|
+
case "orphan_task":
|
|
5025
|
+
return "Orphan:";
|
|
5026
|
+
case "subtasks_complete":
|
|
5027
|
+
return "Subtasks done:";
|
|
5028
|
+
case "capacity_relaunch":
|
|
5029
|
+
return "Relaunched:";
|
|
5030
|
+
}
|
|
5031
|
+
}
|
|
5032
|
+
function formatTimeAgo(timestamp) {
|
|
5033
|
+
const diffMs = Date.now() - new Date(timestamp).getTime();
|
|
5034
|
+
const mins = Math.floor(diffMs / 6e4);
|
|
5035
|
+
if (mins < 1) return "just now";
|
|
5036
|
+
if (mins < 60) return `${mins}m ago`;
|
|
5037
|
+
const hours = Math.floor(mins / 60);
|
|
5038
|
+
if (hours < 24) return `${hours}h ago`;
|
|
5039
|
+
const days = Math.floor(hours / 24);
|
|
5040
|
+
return `${days}d ago`;
|
|
5041
|
+
}
|
|
5042
|
+
var CLEANUP_DAYS;
|
|
4819
5043
|
var init_notifications = __esm({
|
|
4820
5044
|
"src/lib/notifications.ts"() {
|
|
4821
5045
|
"use strict";
|
|
4822
5046
|
init_database();
|
|
4823
5047
|
init_task_scope();
|
|
5048
|
+
CLEANUP_DAYS = 7;
|
|
4824
5049
|
}
|
|
4825
5050
|
});
|
|
4826
5051
|
|
|
@@ -5866,7 +6091,13 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
|
|
|
5866
6091
|
taskFile
|
|
5867
6092
|
});
|
|
5868
6093
|
const originalPriority = String(row.priority).toLowerCase();
|
|
5869
|
-
const
|
|
6094
|
+
const resultLower = result?.toLowerCase() ?? "";
|
|
6095
|
+
const hasTestEvidence = (
|
|
6096
|
+
// Vitest/Jest output patterns (hard to fake without actually running tests)
|
|
6097
|
+
/\d+\s+pass(ed|ing)/.test(resultLower) || /test files?\s+\d+\s+passed/.test(resultLower) || /tests?\s+\d+\s+passed/.test(resultLower)
|
|
6098
|
+
);
|
|
6099
|
+
const hasNoFailures = !/fail(ed|ure|ing)|error/i.test(resultLower);
|
|
6100
|
+
const autoApprove = originalPriority === "p2" && hasTestEvidence && hasNoFailures;
|
|
5870
6101
|
if (!autoApprove) {
|
|
5871
6102
|
try {
|
|
5872
6103
|
const key = getSessionKey();
|
|
@@ -5874,6 +6105,13 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
|
|
|
5874
6105
|
if (exeSession) {
|
|
5875
6106
|
sendIntercom(exeSession);
|
|
5876
6107
|
}
|
|
6108
|
+
if (reviewer && reviewer !== coordinatorName && reviewer !== exeSession) {
|
|
6109
|
+
const { employeeSessionName: employeeSessionName2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
6110
|
+
if (exeSession) {
|
|
6111
|
+
const reviewerSession = employeeSessionName2(reviewer, exeSession);
|
|
6112
|
+
sendIntercom(reviewerSession);
|
|
6113
|
+
}
|
|
6114
|
+
}
|
|
5877
6115
|
} catch {
|
|
5878
6116
|
}
|
|
5879
6117
|
}
|
|
@@ -6649,6 +6887,20 @@ async function updateTask(input2) {
|
|
|
6649
6887
|
notifyTaskDone();
|
|
6650
6888
|
}
|
|
6651
6889
|
await markTaskNotificationsRead(taskFile);
|
|
6890
|
+
if (input2.status === "needs_review" && !isCoordinator) {
|
|
6891
|
+
try {
|
|
6892
|
+
const { writeNotification: writeNotification2 } = await Promise.resolve().then(() => (init_notifications(), notifications_exports));
|
|
6893
|
+
await writeNotification2({
|
|
6894
|
+
agentId: String(row.assigned_to),
|
|
6895
|
+
agentRole: String(row.assigned_to),
|
|
6896
|
+
event: "task_complete",
|
|
6897
|
+
project: String(row.project_name),
|
|
6898
|
+
summary: `"${String(row.title)}" is ready for review`,
|
|
6899
|
+
taskFile
|
|
6900
|
+
});
|
|
6901
|
+
} catch {
|
|
6902
|
+
}
|
|
6903
|
+
}
|
|
6652
6904
|
if (input2.status === "done" || input2.status === "closed") {
|
|
6653
6905
|
try {
|
|
6654
6906
|
await cascadeUnblock(taskId, input2.baseDir, now);
|
|
@@ -7081,18 +7333,31 @@ function acquireSpawnLock2(sessionName) {
|
|
|
7081
7333
|
mkdirSync9(SPAWN_LOCK_DIR, { recursive: true });
|
|
7082
7334
|
}
|
|
7083
7335
|
const lockFile = spawnLockPath(sessionName);
|
|
7084
|
-
|
|
7085
|
-
|
|
7086
|
-
|
|
7087
|
-
|
|
7088
|
-
|
|
7089
|
-
|
|
7090
|
-
|
|
7091
|
-
|
|
7336
|
+
const lockData = JSON.stringify({ pid: process.pid, timestamp: Date.now() });
|
|
7337
|
+
const { openSync: openSync3, closeSync: closeSync3, writeSync } = __require("fs");
|
|
7338
|
+
const { constants } = __require("fs");
|
|
7339
|
+
try {
|
|
7340
|
+
const fd = openSync3(lockFile, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 420);
|
|
7341
|
+
writeSync(fd, lockData);
|
|
7342
|
+
closeSync3(fd);
|
|
7343
|
+
return true;
|
|
7344
|
+
} catch (err) {
|
|
7345
|
+
if (err?.code !== "EEXIST") {
|
|
7346
|
+
return true;
|
|
7092
7347
|
}
|
|
7093
7348
|
}
|
|
7094
|
-
|
|
7095
|
-
|
|
7349
|
+
try {
|
|
7350
|
+
const lock = JSON.parse(readFileSync13(lockFile, "utf8"));
|
|
7351
|
+
const age = Date.now() - lock.timestamp;
|
|
7352
|
+
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
7353
|
+
return false;
|
|
7354
|
+
}
|
|
7355
|
+
writeFileSync9(lockFile, lockData);
|
|
7356
|
+
return true;
|
|
7357
|
+
} catch {
|
|
7358
|
+
writeFileSync9(lockFile, lockData);
|
|
7359
|
+
return true;
|
|
7360
|
+
}
|
|
7096
7361
|
}
|
|
7097
7362
|
function releaseSpawnLock2(sessionName) {
|
|
7098
7363
|
try {
|
|
@@ -7171,6 +7436,21 @@ function parseParentExe(sessionName, agentId) {
|
|
|
7171
7436
|
function extractRootExe(name) {
|
|
7172
7437
|
if (!name) return null;
|
|
7173
7438
|
if (!name.includes("-")) return name;
|
|
7439
|
+
try {
|
|
7440
|
+
const roster = (init_employees(), __toCommonJS(employees_exports)).loadEmployeesSync();
|
|
7441
|
+
if (roster.length > 0) {
|
|
7442
|
+
const sortedNames = roster.map((e) => e.name).sort((a, b) => b.length - a.length);
|
|
7443
|
+
for (const agentName of sortedNames) {
|
|
7444
|
+
const escaped = agentName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7445
|
+
const regex = new RegExp(`^${escaped}\\d*-(.+)$`);
|
|
7446
|
+
const match = name.match(regex);
|
|
7447
|
+
if (match) {
|
|
7448
|
+
return extractRootExe(match[1]);
|
|
7449
|
+
}
|
|
7450
|
+
}
|
|
7451
|
+
}
|
|
7452
|
+
} catch {
|
|
7453
|
+
}
|
|
7174
7454
|
const parts = name.split("-").filter(Boolean);
|
|
7175
7455
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
7176
7456
|
}
|
|
@@ -7189,6 +7469,10 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
7189
7469
|
function getParentExe(sessionKey) {
|
|
7190
7470
|
try {
|
|
7191
7471
|
const data = JSON.parse(readFileSync13(path19.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
7472
|
+
if (data.registeredAt) {
|
|
7473
|
+
const age = Date.now() - new Date(data.registeredAt).getTime();
|
|
7474
|
+
if (age > PARENT_EXE_CACHE_TTL_MS) return null;
|
|
7475
|
+
}
|
|
7192
7476
|
return data.parentExe || null;
|
|
7193
7477
|
} catch {
|
|
7194
7478
|
return null;
|
|
@@ -7737,7 +8021,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
7737
8021
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
7738
8022
|
} catch {
|
|
7739
8023
|
}
|
|
7740
|
-
let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName}`;
|
|
8024
|
+
let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName} EXE_SESSION_START_ISO=${(/* @__PURE__ */ new Date()).toISOString()}`;
|
|
7741
8025
|
if (ccProvider !== DEFAULT_PROVIDER) {
|
|
7742
8026
|
const cfg = PROVIDER_TABLE[ccProvider];
|
|
7743
8027
|
if (cfg?.apiKeyEnv) {
|
|
@@ -7772,10 +8056,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
7772
8056
|
}
|
|
7773
8057
|
if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
|
|
7774
8058
|
if (agentRtConfig.runtime === "claude" && agentRtConfig.model) {
|
|
7775
|
-
|
|
7776
|
-
|
|
7777
|
-
ccModel += "[1m]";
|
|
7778
|
-
}
|
|
8059
|
+
const { normalizeCcModelName: normalizeCcModelName2 } = (init_agent_config(), __toCommonJS(agent_config_exports));
|
|
8060
|
+
const ccModel = normalizeCcModelName2(agentRtConfig.model);
|
|
7779
8061
|
envPrefix = `${envPrefix} ANTHROPIC_MODEL=${ccModel}`;
|
|
7780
8062
|
}
|
|
7781
8063
|
}
|
|
@@ -7876,7 +8158,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
7876
8158
|
releaseSpawnLock2(sessionName);
|
|
7877
8159
|
return { sessionName };
|
|
7878
8160
|
}
|
|
7879
|
-
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;
|
|
8161
|
+
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;
|
|
7880
8162
|
var init_tmux_routing = __esm({
|
|
7881
8163
|
"src/lib/tmux-routing.ts"() {
|
|
7882
8164
|
"use strict";
|
|
@@ -7896,6 +8178,7 @@ var init_tmux_routing = __esm({
|
|
|
7896
8178
|
SESSION_CACHE = path19.join(os11.homedir(), ".exe-os", "session-cache");
|
|
7897
8179
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
7898
8180
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
8181
|
+
PARENT_EXE_CACHE_TTL_MS = 4 * 60 * 60 * 1e3;
|
|
7899
8182
|
VERIFY_PANE_LINES = 200;
|
|
7900
8183
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
7901
8184
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
@@ -7940,7 +8223,7 @@ var init_task_scope = __esm({
|
|
|
7940
8223
|
});
|
|
7941
8224
|
|
|
7942
8225
|
// src/lib/keychain.ts
|
|
7943
|
-
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
8226
|
+
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2, rename, copyFile } from "fs/promises";
|
|
7944
8227
|
import { existsSync as existsSync18, statSync as statSync4 } from "fs";
|
|
7945
8228
|
import { execSync as execSync9 } from "child_process";
|
|
7946
8229
|
import path21 from "path";
|
|
@@ -7975,12 +8258,14 @@ function linuxSecretAvailable() {
|
|
|
7975
8258
|
function isRootOnlyTrustedServerKeyFile(keyPath) {
|
|
7976
8259
|
if (process.platform !== "linux") return false;
|
|
7977
8260
|
try {
|
|
7978
|
-
const uid = typeof os12.userInfo().uid === "number" ? os12.userInfo().uid : -1;
|
|
7979
8261
|
const st = statSync4(keyPath);
|
|
7980
8262
|
if (!st.isFile() || (st.mode & 63) !== 0) return false;
|
|
8263
|
+
const uid = typeof os12.userInfo().uid === "number" ? os12.userInfo().uid : -1;
|
|
7981
8264
|
if (uid === 0) return true;
|
|
7982
8265
|
const exeOsDir = process.env.EXE_OS_DIR;
|
|
7983
|
-
|
|
8266
|
+
if (exeOsDir && path21.resolve(keyPath).startsWith(path21.resolve(exeOsDir) + path21.sep)) return true;
|
|
8267
|
+
if (!linuxSecretAvailable()) return true;
|
|
8268
|
+
return false;
|
|
7984
8269
|
} catch {
|
|
7985
8270
|
return false;
|
|
7986
8271
|
}
|
|
@@ -8130,15 +8415,25 @@ async function writeMachineBoundFileFallback(b64) {
|
|
|
8130
8415
|
await mkdir4(dir, { recursive: true });
|
|
8131
8416
|
const keyPath = getKeyPath();
|
|
8132
8417
|
const machineKey = deriveMachineKey();
|
|
8133
|
-
|
|
8134
|
-
|
|
8135
|
-
|
|
8136
|
-
|
|
8137
|
-
|
|
8418
|
+
const content = machineKey ? encryptWithMachineKey(b64, machineKey) + "\n" : b64 + "\n";
|
|
8419
|
+
const result = machineKey ? "encrypted" : "plaintext";
|
|
8420
|
+
const tmpPath = keyPath + ".tmp";
|
|
8421
|
+
try {
|
|
8422
|
+
if (existsSync18(keyPath)) {
|
|
8423
|
+
await copyFile(keyPath, keyPath + ".bak").catch(() => {
|
|
8424
|
+
});
|
|
8425
|
+
}
|
|
8426
|
+
await writeFile5(tmpPath, content, "utf-8");
|
|
8427
|
+
await chmod2(tmpPath, 384);
|
|
8428
|
+
await rename(tmpPath, keyPath);
|
|
8429
|
+
} catch (err) {
|
|
8430
|
+
try {
|
|
8431
|
+
await unlink(tmpPath);
|
|
8432
|
+
} catch {
|
|
8433
|
+
}
|
|
8434
|
+
throw err;
|
|
8138
8435
|
}
|
|
8139
|
-
|
|
8140
|
-
await chmod2(keyPath, 384);
|
|
8141
|
-
return "plaintext";
|
|
8436
|
+
return result;
|
|
8142
8437
|
}
|
|
8143
8438
|
async function getMasterKey() {
|
|
8144
8439
|
let nativeValue = macKeychainGet() ?? linuxSecretGet();
|
|
@@ -8205,7 +8500,7 @@ async function getMasterKey() {
|
|
|
8205
8500
|
b64Value = content;
|
|
8206
8501
|
}
|
|
8207
8502
|
const key = Buffer.from(b64Value, "base64");
|
|
8208
|
-
if (
|
|
8503
|
+
if (isRootOnlyTrustedServerKeyFile(keyPath)) {
|
|
8209
8504
|
return key;
|
|
8210
8505
|
}
|
|
8211
8506
|
const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
|