@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/index.js
CHANGED
|
@@ -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 config2 = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
|
|
183
195
|
if (config2.dbPath.startsWith("~")) {
|
|
184
196
|
config2.dbPath = config2.dbPath.replace(/^~/, os2.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 config2 = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
|
|
211
224
|
if (config2.dbPath.startsWith("~")) {
|
|
212
225
|
config2.dbPath = config2.dbPath.replace(/^~/, os2.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) {
|
|
@@ -1060,6 +1081,7 @@ var init_provider_table = __esm({
|
|
|
1060
1081
|
// src/lib/intercom-queue.ts
|
|
1061
1082
|
var intercom_queue_exports = {};
|
|
1062
1083
|
__export(intercom_queue_exports, {
|
|
1084
|
+
_resetDrainGuard: () => _resetDrainGuard,
|
|
1063
1085
|
clearQueueForAgent: () => clearQueueForAgent,
|
|
1064
1086
|
drainForSession: () => drainForSession,
|
|
1065
1087
|
drainQueue: () => drainQueue,
|
|
@@ -1105,38 +1127,47 @@ function queueIntercom(targetSession, reason) {
|
|
|
1105
1127
|
writeQueue(queue);
|
|
1106
1128
|
}
|
|
1107
1129
|
function drainQueue(isSessionBusy2, sendKeys) {
|
|
1130
|
+
if (_draining) {
|
|
1131
|
+
logQueue("SKIP_DRAIN \u2014 previous drain still running (possible tmux hang)");
|
|
1132
|
+
return { drained: 0, failed: 0 };
|
|
1133
|
+
}
|
|
1108
1134
|
const queue = readQueue();
|
|
1109
1135
|
if (queue.length === 0) return { drained: 0, failed: 0 };
|
|
1136
|
+
_draining = true;
|
|
1110
1137
|
const remaining = [];
|
|
1111
1138
|
let drained = 0;
|
|
1112
1139
|
let failed = 0;
|
|
1113
|
-
|
|
1114
|
-
const
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1140
|
+
try {
|
|
1141
|
+
for (const item of queue) {
|
|
1142
|
+
const age = Date.now() - new Date(item.queuedAt).getTime();
|
|
1143
|
+
if (age > TTL_MS) {
|
|
1144
|
+
logQueue(`EXPIRED \u2192 ${item.targetSession} (${Math.round(age / 6e4)}min old, reason: ${item.reason})`);
|
|
1145
|
+
failed++;
|
|
1146
|
+
continue;
|
|
1147
|
+
}
|
|
1148
|
+
try {
|
|
1149
|
+
if (!isSessionBusy2(item.targetSession)) {
|
|
1150
|
+
const success = sendKeys(item.targetSession);
|
|
1151
|
+
if (success) {
|
|
1152
|
+
logQueue(`DRAINED \u2192 ${item.targetSession} (after ${item.attempts} retries)`);
|
|
1153
|
+
drained++;
|
|
1154
|
+
continue;
|
|
1155
|
+
}
|
|
1127
1156
|
}
|
|
1157
|
+
} catch {
|
|
1128
1158
|
}
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1159
|
+
item.attempts++;
|
|
1160
|
+
if (item.attempts >= MAX_RETRIES) {
|
|
1161
|
+
logQueue(`FAILED \u2192 ${item.targetSession} (${MAX_RETRIES} retries exhausted, reason: ${item.reason})`);
|
|
1162
|
+
failed++;
|
|
1163
|
+
continue;
|
|
1164
|
+
}
|
|
1165
|
+
remaining.push(item);
|
|
1136
1166
|
}
|
|
1137
|
-
remaining
|
|
1167
|
+
writeQueue(remaining);
|
|
1168
|
+
} finally {
|
|
1169
|
+
_draining = false;
|
|
1138
1170
|
}
|
|
1139
|
-
writeQueue(remaining);
|
|
1140
1171
|
return { drained, failed };
|
|
1141
1172
|
}
|
|
1142
1173
|
function drainForSession(targetSession, sendKeys) {
|
|
@@ -1161,6 +1192,9 @@ function clearQueueForAgent(agentName) {
|
|
|
1161
1192
|
logQueue(`CLEARED ${before - filtered.length} stale item(s) for ${agentName}`);
|
|
1162
1193
|
}
|
|
1163
1194
|
}
|
|
1195
|
+
function _resetDrainGuard() {
|
|
1196
|
+
_draining = false;
|
|
1197
|
+
}
|
|
1164
1198
|
function logQueue(msg) {
|
|
1165
1199
|
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] [queue] ${msg}
|
|
1166
1200
|
`;
|
|
@@ -1172,13 +1206,14 @@ function logQueue(msg) {
|
|
|
1172
1206
|
} catch {
|
|
1173
1207
|
}
|
|
1174
1208
|
}
|
|
1175
|
-
var QUEUE_PATH, MAX_RETRIES, TTL_MS, INTERCOM_LOG;
|
|
1209
|
+
var QUEUE_PATH, MAX_RETRIES, TTL_MS, _draining, INTERCOM_LOG;
|
|
1176
1210
|
var init_intercom_queue = __esm({
|
|
1177
1211
|
"src/lib/intercom-queue.ts"() {
|
|
1178
1212
|
"use strict";
|
|
1179
1213
|
QUEUE_PATH = path6.join(os5.homedir(), ".exe-os", "intercom-queue.json");
|
|
1180
1214
|
MAX_RETRIES = 5;
|
|
1181
1215
|
TTL_MS = 60 * 60 * 1e3;
|
|
1216
|
+
_draining = false;
|
|
1182
1217
|
INTERCOM_LOG = path6.join(os5.homedir(), ".exe-os", "intercom.log");
|
|
1183
1218
|
}
|
|
1184
1219
|
});
|
|
@@ -4788,6 +4823,17 @@ var init_task_scope = __esm({
|
|
|
4788
4823
|
});
|
|
4789
4824
|
|
|
4790
4825
|
// src/lib/notifications.ts
|
|
4826
|
+
var notifications_exports = {};
|
|
4827
|
+
__export(notifications_exports, {
|
|
4828
|
+
cleanupOldNotifications: () => cleanupOldNotifications,
|
|
4829
|
+
formatNotifications: () => formatNotifications,
|
|
4830
|
+
markAsRead: () => markAsRead,
|
|
4831
|
+
markAsReadByTaskFile: () => markAsReadByTaskFile,
|
|
4832
|
+
markDoneTaskNotificationsAsRead: () => markDoneTaskNotificationsAsRead,
|
|
4833
|
+
migrateJsonNotifications: () => migrateJsonNotifications,
|
|
4834
|
+
readUnreadNotifications: () => readUnreadNotifications,
|
|
4835
|
+
writeNotification: () => writeNotification
|
|
4836
|
+
});
|
|
4791
4837
|
import crypto2 from "crypto";
|
|
4792
4838
|
import path13 from "path";
|
|
4793
4839
|
import os10 from "os";
|
|
@@ -4824,6 +4870,52 @@ async function writeNotification(notification) {
|
|
|
4824
4870
|
`);
|
|
4825
4871
|
}
|
|
4826
4872
|
}
|
|
4873
|
+
async function readUnreadNotifications(agentFilter, sessionScope) {
|
|
4874
|
+
try {
|
|
4875
|
+
const client = getClient();
|
|
4876
|
+
const conditions = ["read = 0"];
|
|
4877
|
+
const args = [];
|
|
4878
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
4879
|
+
if (agentFilter) {
|
|
4880
|
+
conditions.push("agent_id = ?");
|
|
4881
|
+
args.push(agentFilter);
|
|
4882
|
+
}
|
|
4883
|
+
const result = await client.execute({
|
|
4884
|
+
sql: `SELECT id, agent_id, agent_role, event, project, summary, task_file, session_scope, created_at
|
|
4885
|
+
FROM notifications
|
|
4886
|
+
WHERE ${conditions.join(" AND ")}${scope.sql}
|
|
4887
|
+
ORDER BY created_at ASC`,
|
|
4888
|
+
args: [...args, ...scope.args]
|
|
4889
|
+
});
|
|
4890
|
+
return result.rows.map((r) => ({
|
|
4891
|
+
id: String(r.id),
|
|
4892
|
+
agentId: String(r.agent_id),
|
|
4893
|
+
agentRole: String(r.agent_role),
|
|
4894
|
+
event: String(r.event),
|
|
4895
|
+
project: String(r.project),
|
|
4896
|
+
summary: String(r.summary),
|
|
4897
|
+
taskFile: r.task_file ? String(r.task_file) : void 0,
|
|
4898
|
+
sessionScope: r.session_scope == null ? null : String(r.session_scope),
|
|
4899
|
+
timestamp: String(r.created_at),
|
|
4900
|
+
read: false
|
|
4901
|
+
}));
|
|
4902
|
+
} catch {
|
|
4903
|
+
return [];
|
|
4904
|
+
}
|
|
4905
|
+
}
|
|
4906
|
+
async function markAsRead(ids, sessionScope) {
|
|
4907
|
+
if (ids.length === 0) return;
|
|
4908
|
+
try {
|
|
4909
|
+
const client = getClient();
|
|
4910
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
4911
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
4912
|
+
await client.execute({
|
|
4913
|
+
sql: `UPDATE notifications SET read = 1 WHERE id IN (${placeholders})${scope.sql}`,
|
|
4914
|
+
args: [...ids, ...scope.args]
|
|
4915
|
+
});
|
|
4916
|
+
} catch {
|
|
4917
|
+
}
|
|
4918
|
+
}
|
|
4827
4919
|
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
4828
4920
|
try {
|
|
4829
4921
|
const client = getClient();
|
|
@@ -4836,11 +4928,144 @@ async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
|
4836
4928
|
} catch {
|
|
4837
4929
|
}
|
|
4838
4930
|
}
|
|
4931
|
+
async function cleanupOldNotifications(daysOld = CLEANUP_DAYS, sessionScope) {
|
|
4932
|
+
try {
|
|
4933
|
+
const client = getClient();
|
|
4934
|
+
const cutoff = new Date(
|
|
4935
|
+
Date.now() - daysOld * 24 * 60 * 60 * 1e3
|
|
4936
|
+
).toISOString();
|
|
4937
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
4938
|
+
const result = await client.execute({
|
|
4939
|
+
sql: `DELETE FROM notifications WHERE created_at < ?${scope.sql}`,
|
|
4940
|
+
args: [cutoff, ...scope.args]
|
|
4941
|
+
});
|
|
4942
|
+
return result.rowsAffected;
|
|
4943
|
+
} catch {
|
|
4944
|
+
return 0;
|
|
4945
|
+
}
|
|
4946
|
+
}
|
|
4947
|
+
async function markDoneTaskNotificationsAsRead(sessionScope) {
|
|
4948
|
+
try {
|
|
4949
|
+
const client = getClient();
|
|
4950
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
4951
|
+
const result = await client.execute({
|
|
4952
|
+
sql: `UPDATE notifications SET read = 1
|
|
4953
|
+
WHERE read = 0
|
|
4954
|
+
AND task_file IS NOT NULL
|
|
4955
|
+
${scope.sql}
|
|
4956
|
+
AND task_file IN (
|
|
4957
|
+
SELECT task_file FROM tasks WHERE status = 'done'${scope.sql}
|
|
4958
|
+
)`,
|
|
4959
|
+
args: [...scope.args, ...scope.args]
|
|
4960
|
+
});
|
|
4961
|
+
return result.rowsAffected;
|
|
4962
|
+
} catch {
|
|
4963
|
+
return 0;
|
|
4964
|
+
}
|
|
4965
|
+
}
|
|
4966
|
+
function formatNotifications(notifications) {
|
|
4967
|
+
if (notifications.length === 0) return "";
|
|
4968
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
4969
|
+
for (const n of notifications) {
|
|
4970
|
+
const key = `${n.agentId}|${n.agentRole}`;
|
|
4971
|
+
if (!grouped.has(key)) grouped.set(key, []);
|
|
4972
|
+
grouped.get(key).push(n);
|
|
4973
|
+
}
|
|
4974
|
+
const lines = [];
|
|
4975
|
+
lines.push(`## Notifications (${notifications.length} unread)
|
|
4976
|
+
`);
|
|
4977
|
+
for (const [key, items] of grouped) {
|
|
4978
|
+
const [agentId, agentRole] = key.split("|");
|
|
4979
|
+
lines.push(`**${agentId}** (${agentRole}):`);
|
|
4980
|
+
for (const item of items) {
|
|
4981
|
+
const ago = formatTimeAgo(item.timestamp);
|
|
4982
|
+
const icon = eventIcon(item.event);
|
|
4983
|
+
lines.push(`- ${icon} ${item.summary} (${item.project}) \u2014 ${ago}`);
|
|
4984
|
+
}
|
|
4985
|
+
lines.push("");
|
|
4986
|
+
}
|
|
4987
|
+
return lines.join("\n");
|
|
4988
|
+
}
|
|
4989
|
+
async function migrateJsonNotifications() {
|
|
4990
|
+
const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR || path13.join(os10.homedir(), ".exe-os");
|
|
4991
|
+
const notifDir = path13.join(base, "notifications");
|
|
4992
|
+
if (!existsSync13(notifDir)) return 0;
|
|
4993
|
+
let migrated = 0;
|
|
4994
|
+
try {
|
|
4995
|
+
const files = readdirSync(notifDir).filter((f) => f.endsWith(".json"));
|
|
4996
|
+
if (files.length === 0) return 0;
|
|
4997
|
+
const client = getClient();
|
|
4998
|
+
for (const file of files) {
|
|
4999
|
+
try {
|
|
5000
|
+
const filePath = path13.join(notifDir, file);
|
|
5001
|
+
const data = JSON.parse(readFileSync10(filePath, "utf8"));
|
|
5002
|
+
await client.execute({
|
|
5003
|
+
sql: `INSERT OR IGNORE INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
5004
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
5005
|
+
args: [
|
|
5006
|
+
crypto2.randomUUID(),
|
|
5007
|
+
data.agentId ?? "unknown",
|
|
5008
|
+
data.agentRole ?? "unknown",
|
|
5009
|
+
data.event ?? "session_summary",
|
|
5010
|
+
data.project ?? "unknown",
|
|
5011
|
+
data.summary ?? "",
|
|
5012
|
+
data.taskFile ?? null,
|
|
5013
|
+
null,
|
|
5014
|
+
data.read ? 1 : 0,
|
|
5015
|
+
data.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
5016
|
+
]
|
|
5017
|
+
});
|
|
5018
|
+
unlinkSync4(filePath);
|
|
5019
|
+
migrated++;
|
|
5020
|
+
} catch {
|
|
5021
|
+
}
|
|
5022
|
+
}
|
|
5023
|
+
try {
|
|
5024
|
+
const remaining = readdirSync(notifDir);
|
|
5025
|
+
if (remaining.length === 0) {
|
|
5026
|
+
rmdirSync(notifDir);
|
|
5027
|
+
}
|
|
5028
|
+
} catch {
|
|
5029
|
+
}
|
|
5030
|
+
} catch {
|
|
5031
|
+
}
|
|
5032
|
+
return migrated;
|
|
5033
|
+
}
|
|
5034
|
+
function eventIcon(event) {
|
|
5035
|
+
switch (event) {
|
|
5036
|
+
case "task_complete":
|
|
5037
|
+
return "Completed:";
|
|
5038
|
+
case "task_needs_fix":
|
|
5039
|
+
return "Needs fix:";
|
|
5040
|
+
case "session_summary":
|
|
5041
|
+
return "Session:";
|
|
5042
|
+
case "error_spike":
|
|
5043
|
+
return "Errors:";
|
|
5044
|
+
case "orphan_task":
|
|
5045
|
+
return "Orphan:";
|
|
5046
|
+
case "subtasks_complete":
|
|
5047
|
+
return "Subtasks done:";
|
|
5048
|
+
case "capacity_relaunch":
|
|
5049
|
+
return "Relaunched:";
|
|
5050
|
+
}
|
|
5051
|
+
}
|
|
5052
|
+
function formatTimeAgo(timestamp) {
|
|
5053
|
+
const diffMs = Date.now() - new Date(timestamp).getTime();
|
|
5054
|
+
const mins = Math.floor(diffMs / 6e4);
|
|
5055
|
+
if (mins < 1) return "just now";
|
|
5056
|
+
if (mins < 60) return `${mins}m ago`;
|
|
5057
|
+
const hours = Math.floor(mins / 60);
|
|
5058
|
+
if (hours < 24) return `${hours}h ago`;
|
|
5059
|
+
const days = Math.floor(hours / 24);
|
|
5060
|
+
return `${days}d ago`;
|
|
5061
|
+
}
|
|
5062
|
+
var CLEANUP_DAYS;
|
|
4839
5063
|
var init_notifications = __esm({
|
|
4840
5064
|
"src/lib/notifications.ts"() {
|
|
4841
5065
|
"use strict";
|
|
4842
5066
|
init_database();
|
|
4843
5067
|
init_task_scope();
|
|
5068
|
+
CLEANUP_DAYS = 7;
|
|
4844
5069
|
}
|
|
4845
5070
|
});
|
|
4846
5071
|
|
|
@@ -5886,7 +6111,13 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
|
|
|
5886
6111
|
taskFile
|
|
5887
6112
|
});
|
|
5888
6113
|
const originalPriority = String(row.priority).toLowerCase();
|
|
5889
|
-
const
|
|
6114
|
+
const resultLower = result?.toLowerCase() ?? "";
|
|
6115
|
+
const hasTestEvidence = (
|
|
6116
|
+
// Vitest/Jest output patterns (hard to fake without actually running tests)
|
|
6117
|
+
/\d+\s+pass(ed|ing)/.test(resultLower) || /test files?\s+\d+\s+passed/.test(resultLower) || /tests?\s+\d+\s+passed/.test(resultLower)
|
|
6118
|
+
);
|
|
6119
|
+
const hasNoFailures = !/fail(ed|ure|ing)|error/i.test(resultLower);
|
|
6120
|
+
const autoApprove = originalPriority === "p2" && hasTestEvidence && hasNoFailures;
|
|
5890
6121
|
if (!autoApprove) {
|
|
5891
6122
|
try {
|
|
5892
6123
|
const key = getSessionKey();
|
|
@@ -5894,6 +6125,13 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
|
|
|
5894
6125
|
if (exeSession) {
|
|
5895
6126
|
sendIntercom(exeSession);
|
|
5896
6127
|
}
|
|
6128
|
+
if (reviewer && reviewer !== coordinatorName && reviewer !== exeSession) {
|
|
6129
|
+
const { employeeSessionName: employeeSessionName2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
6130
|
+
if (exeSession) {
|
|
6131
|
+
const reviewerSession = employeeSessionName2(reviewer, exeSession);
|
|
6132
|
+
sendIntercom(reviewerSession);
|
|
6133
|
+
}
|
|
6134
|
+
}
|
|
5897
6135
|
} catch {
|
|
5898
6136
|
}
|
|
5899
6137
|
}
|
|
@@ -6746,6 +6984,20 @@ async function updateTask(input) {
|
|
|
6746
6984
|
notifyTaskDone();
|
|
6747
6985
|
}
|
|
6748
6986
|
await markTaskNotificationsRead(taskFile);
|
|
6987
|
+
if (input.status === "needs_review" && !isCoordinator) {
|
|
6988
|
+
try {
|
|
6989
|
+
const { writeNotification: writeNotification2 } = await Promise.resolve().then(() => (init_notifications(), notifications_exports));
|
|
6990
|
+
await writeNotification2({
|
|
6991
|
+
agentId: String(row.assigned_to),
|
|
6992
|
+
agentRole: String(row.assigned_to),
|
|
6993
|
+
event: "task_complete",
|
|
6994
|
+
project: String(row.project_name),
|
|
6995
|
+
summary: `"${String(row.title)}" is ready for review`,
|
|
6996
|
+
taskFile
|
|
6997
|
+
});
|
|
6998
|
+
} catch {
|
|
6999
|
+
}
|
|
7000
|
+
}
|
|
6749
7001
|
if (input.status === "done" || input.status === "closed") {
|
|
6750
7002
|
try {
|
|
6751
7003
|
await cascadeUnblock(taskId, input.baseDir, now);
|
|
@@ -7178,18 +7430,31 @@ function acquireSpawnLock2(sessionName) {
|
|
|
7178
7430
|
mkdirSync8(SPAWN_LOCK_DIR, { recursive: true });
|
|
7179
7431
|
}
|
|
7180
7432
|
const lockFile = spawnLockPath(sessionName);
|
|
7181
|
-
|
|
7182
|
-
|
|
7183
|
-
|
|
7184
|
-
|
|
7185
|
-
|
|
7186
|
-
|
|
7187
|
-
|
|
7188
|
-
|
|
7433
|
+
const lockData = JSON.stringify({ pid: process.pid, timestamp: Date.now() });
|
|
7434
|
+
const { openSync: openSync3, closeSync: closeSync3, writeSync } = __require("fs");
|
|
7435
|
+
const { constants } = __require("fs");
|
|
7436
|
+
try {
|
|
7437
|
+
const fd = openSync3(lockFile, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 420);
|
|
7438
|
+
writeSync(fd, lockData);
|
|
7439
|
+
closeSync3(fd);
|
|
7440
|
+
return true;
|
|
7441
|
+
} catch (err) {
|
|
7442
|
+
if (err?.code !== "EEXIST") {
|
|
7443
|
+
return true;
|
|
7189
7444
|
}
|
|
7190
7445
|
}
|
|
7191
|
-
|
|
7192
|
-
|
|
7446
|
+
try {
|
|
7447
|
+
const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
|
|
7448
|
+
const age = Date.now() - lock.timestamp;
|
|
7449
|
+
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
7450
|
+
return false;
|
|
7451
|
+
}
|
|
7452
|
+
writeFileSync8(lockFile, lockData);
|
|
7453
|
+
return true;
|
|
7454
|
+
} catch {
|
|
7455
|
+
writeFileSync8(lockFile, lockData);
|
|
7456
|
+
return true;
|
|
7457
|
+
}
|
|
7193
7458
|
}
|
|
7194
7459
|
function releaseSpawnLock2(sessionName) {
|
|
7195
7460
|
try {
|
|
@@ -7268,6 +7533,21 @@ function parseParentExe(sessionName, agentId) {
|
|
|
7268
7533
|
function extractRootExe(name) {
|
|
7269
7534
|
if (!name) return null;
|
|
7270
7535
|
if (!name.includes("-")) return name;
|
|
7536
|
+
try {
|
|
7537
|
+
const roster = (init_employees(), __toCommonJS(employees_exports)).loadEmployeesSync();
|
|
7538
|
+
if (roster.length > 0) {
|
|
7539
|
+
const sortedNames = roster.map((e) => e.name).sort((a, b) => b.length - a.length);
|
|
7540
|
+
for (const agentName of sortedNames) {
|
|
7541
|
+
const escaped = agentName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7542
|
+
const regex = new RegExp(`^${escaped}\\d*-(.+)$`);
|
|
7543
|
+
const match = name.match(regex);
|
|
7544
|
+
if (match) {
|
|
7545
|
+
return extractRootExe(match[1]);
|
|
7546
|
+
}
|
|
7547
|
+
}
|
|
7548
|
+
}
|
|
7549
|
+
} catch {
|
|
7550
|
+
}
|
|
7271
7551
|
const parts = name.split("-").filter(Boolean);
|
|
7272
7552
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
7273
7553
|
}
|
|
@@ -7286,6 +7566,10 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
7286
7566
|
function getParentExe(sessionKey) {
|
|
7287
7567
|
try {
|
|
7288
7568
|
const data = JSON.parse(readFileSync12(path19.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
7569
|
+
if (data.registeredAt) {
|
|
7570
|
+
const age = Date.now() - new Date(data.registeredAt).getTime();
|
|
7571
|
+
if (age > PARENT_EXE_CACHE_TTL_MS) return null;
|
|
7572
|
+
}
|
|
7289
7573
|
return data.parentExe || null;
|
|
7290
7574
|
} catch {
|
|
7291
7575
|
return null;
|
|
@@ -7834,7 +8118,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
7834
8118
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
7835
8119
|
} catch {
|
|
7836
8120
|
}
|
|
7837
|
-
let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName}`;
|
|
8121
|
+
let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName} EXE_SESSION_START_ISO=${(/* @__PURE__ */ new Date()).toISOString()}`;
|
|
7838
8122
|
if (ccProvider !== DEFAULT_PROVIDER) {
|
|
7839
8123
|
const cfg = PROVIDER_TABLE[ccProvider];
|
|
7840
8124
|
if (cfg?.apiKeyEnv) {
|
|
@@ -7869,10 +8153,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
7869
8153
|
}
|
|
7870
8154
|
if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
|
|
7871
8155
|
if (agentRtConfig.runtime === "claude" && agentRtConfig.model) {
|
|
7872
|
-
|
|
7873
|
-
|
|
7874
|
-
ccModel += "[1m]";
|
|
7875
|
-
}
|
|
8156
|
+
const { normalizeCcModelName: normalizeCcModelName2 } = (init_agent_config(), __toCommonJS(agent_config_exports));
|
|
8157
|
+
const ccModel = normalizeCcModelName2(agentRtConfig.model);
|
|
7876
8158
|
envPrefix = `${envPrefix} ANTHROPIC_MODEL=${ccModel}`;
|
|
7877
8159
|
}
|
|
7878
8160
|
}
|
|
@@ -7973,7 +8255,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
7973
8255
|
releaseSpawnLock2(sessionName);
|
|
7974
8256
|
return { sessionName };
|
|
7975
8257
|
}
|
|
7976
|
-
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;
|
|
8258
|
+
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;
|
|
7977
8259
|
var init_tmux_routing = __esm({
|
|
7978
8260
|
"src/lib/tmux-routing.ts"() {
|
|
7979
8261
|
"use strict";
|
|
@@ -7993,6 +8275,7 @@ var init_tmux_routing = __esm({
|
|
|
7993
8275
|
SESSION_CACHE = path19.join(os12.homedir(), ".exe-os", "session-cache");
|
|
7994
8276
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
7995
8277
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
8278
|
+
PARENT_EXE_CACHE_TTL_MS = 4 * 60 * 60 * 1e3;
|
|
7996
8279
|
VERIFY_PANE_LINES = 200;
|
|
7997
8280
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
7998
8281
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
@@ -8004,7 +8287,7 @@ var init_tmux_routing = __esm({
|
|
|
8004
8287
|
});
|
|
8005
8288
|
|
|
8006
8289
|
// src/lib/keychain.ts
|
|
8007
|
-
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
8290
|
+
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2, rename, copyFile } from "fs/promises";
|
|
8008
8291
|
import { existsSync as existsSync17, statSync as statSync3 } from "fs";
|
|
8009
8292
|
import { execSync as execSync9 } from "child_process";
|
|
8010
8293
|
import path20 from "path";
|
|
@@ -8039,12 +8322,14 @@ function linuxSecretAvailable() {
|
|
|
8039
8322
|
function isRootOnlyTrustedServerKeyFile(keyPath) {
|
|
8040
8323
|
if (process.platform !== "linux") return false;
|
|
8041
8324
|
try {
|
|
8042
|
-
const uid = typeof os13.userInfo().uid === "number" ? os13.userInfo().uid : -1;
|
|
8043
8325
|
const st = statSync3(keyPath);
|
|
8044
8326
|
if (!st.isFile() || (st.mode & 63) !== 0) return false;
|
|
8327
|
+
const uid = typeof os13.userInfo().uid === "number" ? os13.userInfo().uid : -1;
|
|
8045
8328
|
if (uid === 0) return true;
|
|
8046
8329
|
const exeOsDir = process.env.EXE_OS_DIR;
|
|
8047
|
-
|
|
8330
|
+
if (exeOsDir && path20.resolve(keyPath).startsWith(path20.resolve(exeOsDir) + path20.sep)) return true;
|
|
8331
|
+
if (!linuxSecretAvailable()) return true;
|
|
8332
|
+
return false;
|
|
8048
8333
|
} catch {
|
|
8049
8334
|
return false;
|
|
8050
8335
|
}
|
|
@@ -8194,15 +8479,25 @@ async function writeMachineBoundFileFallback(b64) {
|
|
|
8194
8479
|
await mkdir4(dir, { recursive: true });
|
|
8195
8480
|
const keyPath = getKeyPath();
|
|
8196
8481
|
const machineKey = deriveMachineKey();
|
|
8197
|
-
|
|
8198
|
-
|
|
8199
|
-
|
|
8200
|
-
|
|
8201
|
-
|
|
8482
|
+
const content = machineKey ? encryptWithMachineKey(b64, machineKey) + "\n" : b64 + "\n";
|
|
8483
|
+
const result = machineKey ? "encrypted" : "plaintext";
|
|
8484
|
+
const tmpPath = keyPath + ".tmp";
|
|
8485
|
+
try {
|
|
8486
|
+
if (existsSync17(keyPath)) {
|
|
8487
|
+
await copyFile(keyPath, keyPath + ".bak").catch(() => {
|
|
8488
|
+
});
|
|
8489
|
+
}
|
|
8490
|
+
await writeFile5(tmpPath, content, "utf-8");
|
|
8491
|
+
await chmod2(tmpPath, 384);
|
|
8492
|
+
await rename(tmpPath, keyPath);
|
|
8493
|
+
} catch (err) {
|
|
8494
|
+
try {
|
|
8495
|
+
await unlink(tmpPath);
|
|
8496
|
+
} catch {
|
|
8497
|
+
}
|
|
8498
|
+
throw err;
|
|
8202
8499
|
}
|
|
8203
|
-
|
|
8204
|
-
await chmod2(keyPath, 384);
|
|
8205
|
-
return "plaintext";
|
|
8500
|
+
return result;
|
|
8206
8501
|
}
|
|
8207
8502
|
async function getMasterKey() {
|
|
8208
8503
|
let nativeValue = macKeychainGet() ?? linuxSecretGet();
|
|
@@ -8269,7 +8564,7 @@ async function getMasterKey() {
|
|
|
8269
8564
|
b64Value = content;
|
|
8270
8565
|
}
|
|
8271
8566
|
const key = Buffer.from(b64Value, "base64");
|
|
8272
|
-
if (
|
|
8567
|
+
if (isRootOnlyTrustedServerKeyFile(keyPath)) {
|
|
8273
8568
|
return key;
|
|
8274
8569
|
}
|
|
8275
8570
|
const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
|
|
@@ -13555,6 +13850,33 @@ var DANGEROUS_PATTERNS = [
|
|
|
13555
13850
|
regex: /\bkill\s+-9\b/,
|
|
13556
13851
|
severity: "warning",
|
|
13557
13852
|
reason: "Force kill signal"
|
|
13853
|
+
},
|
|
13854
|
+
// MCP bypass — agents must use MCP tools, never access the DB directly.
|
|
13855
|
+
// These patterns catch attempts to work around a disconnected MCP server.
|
|
13856
|
+
{
|
|
13857
|
+
regex: /\bsqlite3\b.*\bmemories\.db\b/,
|
|
13858
|
+
severity: "critical",
|
|
13859
|
+
reason: "Direct SQLite access bypasses MCP contract boundary \u2014 use MCP tools"
|
|
13860
|
+
},
|
|
13861
|
+
{
|
|
13862
|
+
regex: /\bsqlite3\b.*\.exe-os\b/,
|
|
13863
|
+
severity: "critical",
|
|
13864
|
+
reason: "Direct SQLite access to exe-os database \u2014 use MCP tools"
|
|
13865
|
+
},
|
|
13866
|
+
{
|
|
13867
|
+
regex: /\bnode\s+-e\b.*\b(better-sqlite3|libsql|sqlite3)\b/,
|
|
13868
|
+
severity: "critical",
|
|
13869
|
+
reason: "Inline Node.js script accessing SQLite directly \u2014 use MCP tools"
|
|
13870
|
+
},
|
|
13871
|
+
{
|
|
13872
|
+
regex: /\brequire\s*\(\s*['"].*memories\.db['"]\s*\)/,
|
|
13873
|
+
severity: "critical",
|
|
13874
|
+
reason: "Direct require of memories database \u2014 use MCP tools"
|
|
13875
|
+
},
|
|
13876
|
+
{
|
|
13877
|
+
regex: /\bcat\b.*\bmemories\.db\b/,
|
|
13878
|
+
severity: "warning",
|
|
13879
|
+
reason: "Reading raw database file \u2014 encrypted data, use MCP tools instead"
|
|
13558
13880
|
}
|
|
13559
13881
|
];
|
|
13560
13882
|
function checkDangerousPatterns(command) {
|
package/dist/lib/agent-config.js
CHANGED
|
@@ -175,6 +175,13 @@ function getAgentRuntime(agentId) {
|
|
|
175
175
|
if (orgDefault) return orgDefault;
|
|
176
176
|
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
177
177
|
}
|
|
178
|
+
function normalizeCcModelName(model) {
|
|
179
|
+
let ccModel = model.replace(/(\d+)\.(\d+)/g, "$1-$2");
|
|
180
|
+
if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
|
|
181
|
+
ccModel += "[1m]";
|
|
182
|
+
}
|
|
183
|
+
return ccModel;
|
|
184
|
+
}
|
|
178
185
|
function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
|
|
179
186
|
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
180
187
|
if (!knownModels) {
|
|
@@ -223,6 +230,7 @@ export {
|
|
|
223
230
|
clearAgentRuntime,
|
|
224
231
|
getAgentRuntime,
|
|
225
232
|
loadAgentConfig,
|
|
233
|
+
normalizeCcModelName,
|
|
226
234
|
saveAgentConfig,
|
|
227
235
|
setAgentMcps,
|
|
228
236
|
setAgentRuntime
|