@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/tui/App.js
CHANGED
|
@@ -505,6 +505,17 @@ function normalizeOrchestration(raw) {
|
|
|
505
505
|
const userOrg = raw.orchestration ?? {};
|
|
506
506
|
raw.orchestration = { ...defaultOrg, ...userOrg };
|
|
507
507
|
}
|
|
508
|
+
function normalizeCloudEndpoint(raw) {
|
|
509
|
+
const cloud = raw.cloud;
|
|
510
|
+
if (!cloud?.endpoint) return;
|
|
511
|
+
const ep = String(cloud.endpoint);
|
|
512
|
+
if (ep === "https://askexe.com/cloud" || ep === "https://askexe.com/cloud/") {
|
|
513
|
+
cloud.endpoint = "https://cloud.askexe.com";
|
|
514
|
+
process.stderr.write(
|
|
515
|
+
"[config] Auto-migrated cloud endpoint: askexe.com/cloud \u2192 cloud.askexe.com\n"
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
508
519
|
async function loadConfig() {
|
|
509
520
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
510
521
|
await ensurePrivateDir(dir);
|
|
@@ -530,6 +541,7 @@ async function loadConfig() {
|
|
|
530
541
|
normalizeSessionLifecycle(migratedCfg);
|
|
531
542
|
normalizeAutoUpdate(migratedCfg);
|
|
532
543
|
normalizeOrchestration(migratedCfg);
|
|
544
|
+
normalizeCloudEndpoint(migratedCfg);
|
|
533
545
|
const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
|
|
534
546
|
if (config.dbPath.startsWith("~")) {
|
|
535
547
|
config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
|
|
@@ -558,6 +570,7 @@ function loadConfigSync() {
|
|
|
558
570
|
normalizeSessionLifecycle(migratedCfg);
|
|
559
571
|
normalizeAutoUpdate(migratedCfg);
|
|
560
572
|
normalizeOrchestration(migratedCfg);
|
|
573
|
+
normalizeCloudEndpoint(migratedCfg);
|
|
561
574
|
const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
|
|
562
575
|
if (config.dbPath.startsWith("~")) {
|
|
563
576
|
config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
|
|
@@ -718,6 +731,7 @@ __export(agent_config_exports, {
|
|
|
718
731
|
clearAgentRuntime: () => clearAgentRuntime,
|
|
719
732
|
getAgentRuntime: () => getAgentRuntime,
|
|
720
733
|
loadAgentConfig: () => loadAgentConfig,
|
|
734
|
+
normalizeCcModelName: () => normalizeCcModelName,
|
|
721
735
|
saveAgentConfig: () => saveAgentConfig,
|
|
722
736
|
setAgentMcps: () => setAgentMcps,
|
|
723
737
|
setAgentRuntime: () => setAgentRuntime
|
|
@@ -746,6 +760,13 @@ function getAgentRuntime(agentId) {
|
|
|
746
760
|
if (orgDefault) return orgDefault;
|
|
747
761
|
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
748
762
|
}
|
|
763
|
+
function normalizeCcModelName(model) {
|
|
764
|
+
let ccModel = model.replace(/(\d+)\.(\d+)/g, "$1-$2");
|
|
765
|
+
if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
|
|
766
|
+
ccModel += "[1m]";
|
|
767
|
+
}
|
|
768
|
+
return ccModel;
|
|
769
|
+
}
|
|
749
770
|
function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
|
|
750
771
|
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
751
772
|
if (!knownModels) {
|
|
@@ -815,6 +836,7 @@ var init_agent_config = __esm({
|
|
|
815
836
|
// src/lib/intercom-queue.ts
|
|
816
837
|
var intercom_queue_exports = {};
|
|
817
838
|
__export(intercom_queue_exports, {
|
|
839
|
+
_resetDrainGuard: () => _resetDrainGuard,
|
|
818
840
|
clearQueueForAgent: () => clearQueueForAgent,
|
|
819
841
|
drainForSession: () => drainForSession,
|
|
820
842
|
drainQueue: () => drainQueue,
|
|
@@ -860,38 +882,47 @@ function queueIntercom(targetSession, reason) {
|
|
|
860
882
|
writeQueue(queue);
|
|
861
883
|
}
|
|
862
884
|
function drainQueue(isSessionBusy2, sendKeys) {
|
|
885
|
+
if (_draining) {
|
|
886
|
+
logQueue("SKIP_DRAIN \u2014 previous drain still running (possible tmux hang)");
|
|
887
|
+
return { drained: 0, failed: 0 };
|
|
888
|
+
}
|
|
863
889
|
const queue = readQueue();
|
|
864
890
|
if (queue.length === 0) return { drained: 0, failed: 0 };
|
|
891
|
+
_draining = true;
|
|
865
892
|
const remaining = [];
|
|
866
893
|
let drained = 0;
|
|
867
894
|
let failed = 0;
|
|
868
|
-
|
|
869
|
-
const
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
895
|
+
try {
|
|
896
|
+
for (const item of queue) {
|
|
897
|
+
const age = Date.now() - new Date(item.queuedAt).getTime();
|
|
898
|
+
if (age > TTL_MS) {
|
|
899
|
+
logQueue(`EXPIRED \u2192 ${item.targetSession} (${Math.round(age / 6e4)}min old, reason: ${item.reason})`);
|
|
900
|
+
failed++;
|
|
901
|
+
continue;
|
|
902
|
+
}
|
|
903
|
+
try {
|
|
904
|
+
if (!isSessionBusy2(item.targetSession)) {
|
|
905
|
+
const success = sendKeys(item.targetSession);
|
|
906
|
+
if (success) {
|
|
907
|
+
logQueue(`DRAINED \u2192 ${item.targetSession} (after ${item.attempts} retries)`);
|
|
908
|
+
drained++;
|
|
909
|
+
continue;
|
|
910
|
+
}
|
|
882
911
|
}
|
|
912
|
+
} catch {
|
|
883
913
|
}
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
914
|
+
item.attempts++;
|
|
915
|
+
if (item.attempts >= MAX_RETRIES) {
|
|
916
|
+
logQueue(`FAILED \u2192 ${item.targetSession} (${MAX_RETRIES} retries exhausted, reason: ${item.reason})`);
|
|
917
|
+
failed++;
|
|
918
|
+
continue;
|
|
919
|
+
}
|
|
920
|
+
remaining.push(item);
|
|
891
921
|
}
|
|
892
|
-
remaining
|
|
922
|
+
writeQueue(remaining);
|
|
923
|
+
} finally {
|
|
924
|
+
_draining = false;
|
|
893
925
|
}
|
|
894
|
-
writeQueue(remaining);
|
|
895
926
|
return { drained, failed };
|
|
896
927
|
}
|
|
897
928
|
function drainForSession(targetSession, sendKeys) {
|
|
@@ -916,6 +947,9 @@ function clearQueueForAgent(agentName) {
|
|
|
916
947
|
logQueue(`CLEARED ${before - filtered.length} stale item(s) for ${agentName}`);
|
|
917
948
|
}
|
|
918
949
|
}
|
|
950
|
+
function _resetDrainGuard() {
|
|
951
|
+
_draining = false;
|
|
952
|
+
}
|
|
919
953
|
function logQueue(msg) {
|
|
920
954
|
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] [queue] ${msg}
|
|
921
955
|
`;
|
|
@@ -927,13 +961,14 @@ function logQueue(msg) {
|
|
|
927
961
|
} catch {
|
|
928
962
|
}
|
|
929
963
|
}
|
|
930
|
-
var QUEUE_PATH, MAX_RETRIES, TTL_MS, INTERCOM_LOG;
|
|
964
|
+
var QUEUE_PATH, MAX_RETRIES, TTL_MS, _draining, INTERCOM_LOG;
|
|
931
965
|
var init_intercom_queue = __esm({
|
|
932
966
|
"src/lib/intercom-queue.ts"() {
|
|
933
967
|
"use strict";
|
|
934
968
|
QUEUE_PATH = path4.join(os3.homedir(), ".exe-os", "intercom-queue.json");
|
|
935
969
|
MAX_RETRIES = 5;
|
|
936
970
|
TTL_MS = 60 * 60 * 1e3;
|
|
971
|
+
_draining = false;
|
|
937
972
|
INTERCOM_LOG = path4.join(os3.homedir(), ".exe-os", "intercom.log");
|
|
938
973
|
}
|
|
939
974
|
});
|
|
@@ -4761,6 +4796,17 @@ var init_agent_symlinks = __esm({
|
|
|
4761
4796
|
});
|
|
4762
4797
|
|
|
4763
4798
|
// src/lib/notifications.ts
|
|
4799
|
+
var notifications_exports = {};
|
|
4800
|
+
__export(notifications_exports, {
|
|
4801
|
+
cleanupOldNotifications: () => cleanupOldNotifications,
|
|
4802
|
+
formatNotifications: () => formatNotifications,
|
|
4803
|
+
markAsRead: () => markAsRead,
|
|
4804
|
+
markAsReadByTaskFile: () => markAsReadByTaskFile,
|
|
4805
|
+
markDoneTaskNotificationsAsRead: () => markDoneTaskNotificationsAsRead,
|
|
4806
|
+
migrateJsonNotifications: () => migrateJsonNotifications,
|
|
4807
|
+
readUnreadNotifications: () => readUnreadNotifications,
|
|
4808
|
+
writeNotification: () => writeNotification
|
|
4809
|
+
});
|
|
4764
4810
|
import crypto2 from "crypto";
|
|
4765
4811
|
import path12 from "path";
|
|
4766
4812
|
import os9 from "os";
|
|
@@ -4797,6 +4843,52 @@ async function writeNotification(notification) {
|
|
|
4797
4843
|
`);
|
|
4798
4844
|
}
|
|
4799
4845
|
}
|
|
4846
|
+
async function readUnreadNotifications(agentFilter, sessionScope) {
|
|
4847
|
+
try {
|
|
4848
|
+
const client = getClient();
|
|
4849
|
+
const conditions = ["read = 0"];
|
|
4850
|
+
const args = [];
|
|
4851
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
4852
|
+
if (agentFilter) {
|
|
4853
|
+
conditions.push("agent_id = ?");
|
|
4854
|
+
args.push(agentFilter);
|
|
4855
|
+
}
|
|
4856
|
+
const result = await client.execute({
|
|
4857
|
+
sql: `SELECT id, agent_id, agent_role, event, project, summary, task_file, session_scope, created_at
|
|
4858
|
+
FROM notifications
|
|
4859
|
+
WHERE ${conditions.join(" AND ")}${scope.sql}
|
|
4860
|
+
ORDER BY created_at ASC`,
|
|
4861
|
+
args: [...args, ...scope.args]
|
|
4862
|
+
});
|
|
4863
|
+
return result.rows.map((r) => ({
|
|
4864
|
+
id: String(r.id),
|
|
4865
|
+
agentId: String(r.agent_id),
|
|
4866
|
+
agentRole: String(r.agent_role),
|
|
4867
|
+
event: String(r.event),
|
|
4868
|
+
project: String(r.project),
|
|
4869
|
+
summary: String(r.summary),
|
|
4870
|
+
taskFile: r.task_file ? String(r.task_file) : void 0,
|
|
4871
|
+
sessionScope: r.session_scope == null ? null : String(r.session_scope),
|
|
4872
|
+
timestamp: String(r.created_at),
|
|
4873
|
+
read: false
|
|
4874
|
+
}));
|
|
4875
|
+
} catch {
|
|
4876
|
+
return [];
|
|
4877
|
+
}
|
|
4878
|
+
}
|
|
4879
|
+
async function markAsRead(ids, sessionScope) {
|
|
4880
|
+
if (ids.length === 0) return;
|
|
4881
|
+
try {
|
|
4882
|
+
const client = getClient();
|
|
4883
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
4884
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
4885
|
+
await client.execute({
|
|
4886
|
+
sql: `UPDATE notifications SET read = 1 WHERE id IN (${placeholders})${scope.sql}`,
|
|
4887
|
+
args: [...ids, ...scope.args]
|
|
4888
|
+
});
|
|
4889
|
+
} catch {
|
|
4890
|
+
}
|
|
4891
|
+
}
|
|
4800
4892
|
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
4801
4893
|
try {
|
|
4802
4894
|
const client = getClient();
|
|
@@ -4809,11 +4901,144 @@ async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
|
4809
4901
|
} catch {
|
|
4810
4902
|
}
|
|
4811
4903
|
}
|
|
4904
|
+
async function cleanupOldNotifications(daysOld = CLEANUP_DAYS, sessionScope) {
|
|
4905
|
+
try {
|
|
4906
|
+
const client = getClient();
|
|
4907
|
+
const cutoff = new Date(
|
|
4908
|
+
Date.now() - daysOld * 24 * 60 * 60 * 1e3
|
|
4909
|
+
).toISOString();
|
|
4910
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
4911
|
+
const result = await client.execute({
|
|
4912
|
+
sql: `DELETE FROM notifications WHERE created_at < ?${scope.sql}`,
|
|
4913
|
+
args: [cutoff, ...scope.args]
|
|
4914
|
+
});
|
|
4915
|
+
return result.rowsAffected;
|
|
4916
|
+
} catch {
|
|
4917
|
+
return 0;
|
|
4918
|
+
}
|
|
4919
|
+
}
|
|
4920
|
+
async function markDoneTaskNotificationsAsRead(sessionScope) {
|
|
4921
|
+
try {
|
|
4922
|
+
const client = getClient();
|
|
4923
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
4924
|
+
const result = await client.execute({
|
|
4925
|
+
sql: `UPDATE notifications SET read = 1
|
|
4926
|
+
WHERE read = 0
|
|
4927
|
+
AND task_file IS NOT NULL
|
|
4928
|
+
${scope.sql}
|
|
4929
|
+
AND task_file IN (
|
|
4930
|
+
SELECT task_file FROM tasks WHERE status = 'done'${scope.sql}
|
|
4931
|
+
)`,
|
|
4932
|
+
args: [...scope.args, ...scope.args]
|
|
4933
|
+
});
|
|
4934
|
+
return result.rowsAffected;
|
|
4935
|
+
} catch {
|
|
4936
|
+
return 0;
|
|
4937
|
+
}
|
|
4938
|
+
}
|
|
4939
|
+
function formatNotifications(notifications) {
|
|
4940
|
+
if (notifications.length === 0) return "";
|
|
4941
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
4942
|
+
for (const n of notifications) {
|
|
4943
|
+
const key = `${n.agentId}|${n.agentRole}`;
|
|
4944
|
+
if (!grouped.has(key)) grouped.set(key, []);
|
|
4945
|
+
grouped.get(key).push(n);
|
|
4946
|
+
}
|
|
4947
|
+
const lines = [];
|
|
4948
|
+
lines.push(`## Notifications (${notifications.length} unread)
|
|
4949
|
+
`);
|
|
4950
|
+
for (const [key, items] of grouped) {
|
|
4951
|
+
const [agentId, agentRole] = key.split("|");
|
|
4952
|
+
lines.push(`**${agentId}** (${agentRole}):`);
|
|
4953
|
+
for (const item of items) {
|
|
4954
|
+
const ago = formatTimeAgo(item.timestamp);
|
|
4955
|
+
const icon = eventIcon(item.event);
|
|
4956
|
+
lines.push(`- ${icon} ${item.summary} (${item.project}) \u2014 ${ago}`);
|
|
4957
|
+
}
|
|
4958
|
+
lines.push("");
|
|
4959
|
+
}
|
|
4960
|
+
return lines.join("\n");
|
|
4961
|
+
}
|
|
4962
|
+
async function migrateJsonNotifications() {
|
|
4963
|
+
const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR || path12.join(os9.homedir(), ".exe-os");
|
|
4964
|
+
const notifDir = path12.join(base, "notifications");
|
|
4965
|
+
if (!existsSync14(notifDir)) return 0;
|
|
4966
|
+
let migrated = 0;
|
|
4967
|
+
try {
|
|
4968
|
+
const files = readdirSync(notifDir).filter((f) => f.endsWith(".json"));
|
|
4969
|
+
if (files.length === 0) return 0;
|
|
4970
|
+
const client = getClient();
|
|
4971
|
+
for (const file of files) {
|
|
4972
|
+
try {
|
|
4973
|
+
const filePath = path12.join(notifDir, file);
|
|
4974
|
+
const data = JSON.parse(readFileSync11(filePath, "utf8"));
|
|
4975
|
+
await client.execute({
|
|
4976
|
+
sql: `INSERT OR IGNORE INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
4977
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
4978
|
+
args: [
|
|
4979
|
+
crypto2.randomUUID(),
|
|
4980
|
+
data.agentId ?? "unknown",
|
|
4981
|
+
data.agentRole ?? "unknown",
|
|
4982
|
+
data.event ?? "session_summary",
|
|
4983
|
+
data.project ?? "unknown",
|
|
4984
|
+
data.summary ?? "",
|
|
4985
|
+
data.taskFile ?? null,
|
|
4986
|
+
null,
|
|
4987
|
+
data.read ? 1 : 0,
|
|
4988
|
+
data.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
4989
|
+
]
|
|
4990
|
+
});
|
|
4991
|
+
unlinkSync4(filePath);
|
|
4992
|
+
migrated++;
|
|
4993
|
+
} catch {
|
|
4994
|
+
}
|
|
4995
|
+
}
|
|
4996
|
+
try {
|
|
4997
|
+
const remaining = readdirSync(notifDir);
|
|
4998
|
+
if (remaining.length === 0) {
|
|
4999
|
+
rmdirSync(notifDir);
|
|
5000
|
+
}
|
|
5001
|
+
} catch {
|
|
5002
|
+
}
|
|
5003
|
+
} catch {
|
|
5004
|
+
}
|
|
5005
|
+
return migrated;
|
|
5006
|
+
}
|
|
5007
|
+
function eventIcon(event) {
|
|
5008
|
+
switch (event) {
|
|
5009
|
+
case "task_complete":
|
|
5010
|
+
return "Completed:";
|
|
5011
|
+
case "task_needs_fix":
|
|
5012
|
+
return "Needs fix:";
|
|
5013
|
+
case "session_summary":
|
|
5014
|
+
return "Session:";
|
|
5015
|
+
case "error_spike":
|
|
5016
|
+
return "Errors:";
|
|
5017
|
+
case "orphan_task":
|
|
5018
|
+
return "Orphan:";
|
|
5019
|
+
case "subtasks_complete":
|
|
5020
|
+
return "Subtasks done:";
|
|
5021
|
+
case "capacity_relaunch":
|
|
5022
|
+
return "Relaunched:";
|
|
5023
|
+
}
|
|
5024
|
+
}
|
|
5025
|
+
function formatTimeAgo(timestamp) {
|
|
5026
|
+
const diffMs = Date.now() - new Date(timestamp).getTime();
|
|
5027
|
+
const mins = Math.floor(diffMs / 6e4);
|
|
5028
|
+
if (mins < 1) return "just now";
|
|
5029
|
+
if (mins < 60) return `${mins}m ago`;
|
|
5030
|
+
const hours = Math.floor(mins / 60);
|
|
5031
|
+
if (hours < 24) return `${hours}h ago`;
|
|
5032
|
+
const days = Math.floor(hours / 24);
|
|
5033
|
+
return `${days}d ago`;
|
|
5034
|
+
}
|
|
5035
|
+
var CLEANUP_DAYS;
|
|
4812
5036
|
var init_notifications = __esm({
|
|
4813
5037
|
"src/lib/notifications.ts"() {
|
|
4814
5038
|
"use strict";
|
|
4815
5039
|
init_database();
|
|
4816
5040
|
init_task_scope();
|
|
5041
|
+
CLEANUP_DAYS = 7;
|
|
4817
5042
|
}
|
|
4818
5043
|
});
|
|
4819
5044
|
|
|
@@ -5875,7 +6100,13 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
|
|
|
5875
6100
|
taskFile
|
|
5876
6101
|
});
|
|
5877
6102
|
const originalPriority = String(row.priority).toLowerCase();
|
|
5878
|
-
const
|
|
6103
|
+
const resultLower = result?.toLowerCase() ?? "";
|
|
6104
|
+
const hasTestEvidence = (
|
|
6105
|
+
// Vitest/Jest output patterns (hard to fake without actually running tests)
|
|
6106
|
+
/\d+\s+pass(ed|ing)/.test(resultLower) || /test files?\s+\d+\s+passed/.test(resultLower) || /tests?\s+\d+\s+passed/.test(resultLower)
|
|
6107
|
+
);
|
|
6108
|
+
const hasNoFailures = !/fail(ed|ure|ing)|error/i.test(resultLower);
|
|
6109
|
+
const autoApprove = originalPriority === "p2" && hasTestEvidence && hasNoFailures;
|
|
5879
6110
|
if (!autoApprove) {
|
|
5880
6111
|
try {
|
|
5881
6112
|
const key = getSessionKey();
|
|
@@ -5883,6 +6114,13 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
|
|
|
5883
6114
|
if (exeSession) {
|
|
5884
6115
|
sendIntercom(exeSession);
|
|
5885
6116
|
}
|
|
6117
|
+
if (reviewer && reviewer !== coordinatorName && reviewer !== exeSession) {
|
|
6118
|
+
const { employeeSessionName: employeeSessionName2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
6119
|
+
if (exeSession) {
|
|
6120
|
+
const reviewerSession = employeeSessionName2(reviewer, exeSession);
|
|
6121
|
+
sendIntercom(reviewerSession);
|
|
6122
|
+
}
|
|
6123
|
+
}
|
|
5886
6124
|
} catch {
|
|
5887
6125
|
}
|
|
5888
6126
|
}
|
|
@@ -6658,6 +6896,20 @@ async function updateTask(input) {
|
|
|
6658
6896
|
notifyTaskDone();
|
|
6659
6897
|
}
|
|
6660
6898
|
await markTaskNotificationsRead(taskFile);
|
|
6899
|
+
if (input.status === "needs_review" && !isCoordinator) {
|
|
6900
|
+
try {
|
|
6901
|
+
const { writeNotification: writeNotification2 } = await Promise.resolve().then(() => (init_notifications(), notifications_exports));
|
|
6902
|
+
await writeNotification2({
|
|
6903
|
+
agentId: String(row.assigned_to),
|
|
6904
|
+
agentRole: String(row.assigned_to),
|
|
6905
|
+
event: "task_complete",
|
|
6906
|
+
project: String(row.project_name),
|
|
6907
|
+
summary: `"${String(row.title)}" is ready for review`,
|
|
6908
|
+
taskFile
|
|
6909
|
+
});
|
|
6910
|
+
} catch {
|
|
6911
|
+
}
|
|
6912
|
+
}
|
|
6661
6913
|
if (input.status === "done" || input.status === "closed") {
|
|
6662
6914
|
try {
|
|
6663
6915
|
await cascadeUnblock(taskId, input.baseDir, now);
|
|
@@ -7090,18 +7342,31 @@ function acquireSpawnLock2(sessionName) {
|
|
|
7090
7342
|
mkdirSync8(SPAWN_LOCK_DIR, { recursive: true });
|
|
7091
7343
|
}
|
|
7092
7344
|
const lockFile = spawnLockPath(sessionName);
|
|
7093
|
-
|
|
7094
|
-
|
|
7095
|
-
|
|
7096
|
-
|
|
7097
|
-
|
|
7098
|
-
|
|
7099
|
-
|
|
7100
|
-
|
|
7345
|
+
const lockData = JSON.stringify({ pid: process.pid, timestamp: Date.now() });
|
|
7346
|
+
const { openSync: openSync3, closeSync: closeSync3, writeSync } = __require("fs");
|
|
7347
|
+
const { constants: constants2 } = __require("fs");
|
|
7348
|
+
try {
|
|
7349
|
+
const fd = openSync3(lockFile, constants2.O_WRONLY | constants2.O_CREAT | constants2.O_EXCL, 420);
|
|
7350
|
+
writeSync(fd, lockData);
|
|
7351
|
+
closeSync3(fd);
|
|
7352
|
+
return true;
|
|
7353
|
+
} catch (err) {
|
|
7354
|
+
if (err?.code !== "EEXIST") {
|
|
7355
|
+
return true;
|
|
7101
7356
|
}
|
|
7102
7357
|
}
|
|
7103
|
-
|
|
7104
|
-
|
|
7358
|
+
try {
|
|
7359
|
+
const lock = JSON.parse(readFileSync13(lockFile, "utf8"));
|
|
7360
|
+
const age = Date.now() - lock.timestamp;
|
|
7361
|
+
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
7362
|
+
return false;
|
|
7363
|
+
}
|
|
7364
|
+
writeFileSync8(lockFile, lockData);
|
|
7365
|
+
return true;
|
|
7366
|
+
} catch {
|
|
7367
|
+
writeFileSync8(lockFile, lockData);
|
|
7368
|
+
return true;
|
|
7369
|
+
}
|
|
7105
7370
|
}
|
|
7106
7371
|
function releaseSpawnLock2(sessionName) {
|
|
7107
7372
|
try {
|
|
@@ -7180,6 +7445,21 @@ function parseParentExe(sessionName, agentId) {
|
|
|
7180
7445
|
function extractRootExe(name) {
|
|
7181
7446
|
if (!name) return null;
|
|
7182
7447
|
if (!name.includes("-")) return name;
|
|
7448
|
+
try {
|
|
7449
|
+
const roster = (init_employees(), __toCommonJS(employees_exports)).loadEmployeesSync();
|
|
7450
|
+
if (roster.length > 0) {
|
|
7451
|
+
const sortedNames = roster.map((e) => e.name).sort((a, b) => b.length - a.length);
|
|
7452
|
+
for (const agentName of sortedNames) {
|
|
7453
|
+
const escaped = agentName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7454
|
+
const regex = new RegExp(`^${escaped}\\d*-(.+)$`);
|
|
7455
|
+
const match = name.match(regex);
|
|
7456
|
+
if (match) {
|
|
7457
|
+
return extractRootExe(match[1]);
|
|
7458
|
+
}
|
|
7459
|
+
}
|
|
7460
|
+
}
|
|
7461
|
+
} catch {
|
|
7462
|
+
}
|
|
7183
7463
|
const parts = name.split("-").filter(Boolean);
|
|
7184
7464
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
7185
7465
|
}
|
|
@@ -7198,6 +7478,10 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
7198
7478
|
function getParentExe(sessionKey) {
|
|
7199
7479
|
try {
|
|
7200
7480
|
const data = JSON.parse(readFileSync13(path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
7481
|
+
if (data.registeredAt) {
|
|
7482
|
+
const age = Date.now() - new Date(data.registeredAt).getTime();
|
|
7483
|
+
if (age > PARENT_EXE_CACHE_TTL_MS) return null;
|
|
7484
|
+
}
|
|
7201
7485
|
return data.parentExe || null;
|
|
7202
7486
|
} catch {
|
|
7203
7487
|
return null;
|
|
@@ -7746,7 +8030,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
7746
8030
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
7747
8031
|
} catch {
|
|
7748
8032
|
}
|
|
7749
|
-
let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName}`;
|
|
8033
|
+
let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName} EXE_SESSION_START_ISO=${(/* @__PURE__ */ new Date()).toISOString()}`;
|
|
7750
8034
|
if (ccProvider !== DEFAULT_PROVIDER) {
|
|
7751
8035
|
const cfg = PROVIDER_TABLE[ccProvider];
|
|
7752
8036
|
if (cfg?.apiKeyEnv) {
|
|
@@ -7781,10 +8065,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
7781
8065
|
}
|
|
7782
8066
|
if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
|
|
7783
8067
|
if (agentRtConfig.runtime === "claude" && agentRtConfig.model) {
|
|
7784
|
-
|
|
7785
|
-
|
|
7786
|
-
ccModel += "[1m]";
|
|
7787
|
-
}
|
|
8068
|
+
const { normalizeCcModelName: normalizeCcModelName2 } = (init_agent_config(), __toCommonJS(agent_config_exports));
|
|
8069
|
+
const ccModel = normalizeCcModelName2(agentRtConfig.model);
|
|
7788
8070
|
envPrefix = `${envPrefix} ANTHROPIC_MODEL=${ccModel}`;
|
|
7789
8071
|
}
|
|
7790
8072
|
}
|
|
@@ -7885,7 +8167,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
7885
8167
|
releaseSpawnLock2(sessionName);
|
|
7886
8168
|
return { sessionName };
|
|
7887
8169
|
}
|
|
7888
|
-
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;
|
|
8170
|
+
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;
|
|
7889
8171
|
var init_tmux_routing = __esm({
|
|
7890
8172
|
"src/lib/tmux-routing.ts"() {
|
|
7891
8173
|
"use strict";
|
|
@@ -7905,6 +8187,7 @@ var init_tmux_routing = __esm({
|
|
|
7905
8187
|
SESSION_CACHE = path18.join(os11.homedir(), ".exe-os", "session-cache");
|
|
7906
8188
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
7907
8189
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
8190
|
+
PARENT_EXE_CACHE_TTL_MS = 4 * 60 * 60 * 1e3;
|
|
7908
8191
|
VERIFY_PANE_LINES = 200;
|
|
7909
8192
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
7910
8193
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
@@ -10605,6 +10888,33 @@ var init_dangerous_patterns = __esm({
|
|
|
10605
10888
|
regex: /\bkill\s+-9\b/,
|
|
10606
10889
|
severity: "warning",
|
|
10607
10890
|
reason: "Force kill signal"
|
|
10891
|
+
},
|
|
10892
|
+
// MCP bypass — agents must use MCP tools, never access the DB directly.
|
|
10893
|
+
// These patterns catch attempts to work around a disconnected MCP server.
|
|
10894
|
+
{
|
|
10895
|
+
regex: /\bsqlite3\b.*\bmemories\.db\b/,
|
|
10896
|
+
severity: "critical",
|
|
10897
|
+
reason: "Direct SQLite access bypasses MCP contract boundary \u2014 use MCP tools"
|
|
10898
|
+
},
|
|
10899
|
+
{
|
|
10900
|
+
regex: /\bsqlite3\b.*\.exe-os\b/,
|
|
10901
|
+
severity: "critical",
|
|
10902
|
+
reason: "Direct SQLite access to exe-os database \u2014 use MCP tools"
|
|
10903
|
+
},
|
|
10904
|
+
{
|
|
10905
|
+
regex: /\bnode\s+-e\b.*\b(better-sqlite3|libsql|sqlite3)\b/,
|
|
10906
|
+
severity: "critical",
|
|
10907
|
+
reason: "Inline Node.js script accessing SQLite directly \u2014 use MCP tools"
|
|
10908
|
+
},
|
|
10909
|
+
{
|
|
10910
|
+
regex: /\brequire\s*\(\s*['"].*memories\.db['"]\s*\)/,
|
|
10911
|
+
severity: "critical",
|
|
10912
|
+
reason: "Direct require of memories database \u2014 use MCP tools"
|
|
10913
|
+
},
|
|
10914
|
+
{
|
|
10915
|
+
regex: /\bcat\b.*\bmemories\.db\b/,
|
|
10916
|
+
severity: "warning",
|
|
10917
|
+
reason: "Reading raw database file \u2014 encrypted data, use MCP tools instead"
|
|
10608
10918
|
}
|
|
10609
10919
|
];
|
|
10610
10920
|
}
|
|
@@ -11263,7 +11573,7 @@ __export(keychain_exports, {
|
|
|
11263
11573
|
importMnemonic: () => importMnemonic,
|
|
11264
11574
|
setMasterKey: () => setMasterKey
|
|
11265
11575
|
});
|
|
11266
|
-
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
11576
|
+
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2, rename, copyFile } from "fs/promises";
|
|
11267
11577
|
import { existsSync as existsSync18, statSync as statSync3 } from "fs";
|
|
11268
11578
|
import { execSync as execSync10 } from "child_process";
|
|
11269
11579
|
import path27 from "path";
|
|
@@ -11298,12 +11608,14 @@ function linuxSecretAvailable() {
|
|
|
11298
11608
|
function isRootOnlyTrustedServerKeyFile(keyPath) {
|
|
11299
11609
|
if (process.platform !== "linux") return false;
|
|
11300
11610
|
try {
|
|
11301
|
-
const uid = typeof os13.userInfo().uid === "number" ? os13.userInfo().uid : -1;
|
|
11302
11611
|
const st = statSync3(keyPath);
|
|
11303
11612
|
if (!st.isFile() || (st.mode & 63) !== 0) return false;
|
|
11613
|
+
const uid = typeof os13.userInfo().uid === "number" ? os13.userInfo().uid : -1;
|
|
11304
11614
|
if (uid === 0) return true;
|
|
11305
11615
|
const exeOsDir = process.env.EXE_OS_DIR;
|
|
11306
|
-
|
|
11616
|
+
if (exeOsDir && path27.resolve(keyPath).startsWith(path27.resolve(exeOsDir) + path27.sep)) return true;
|
|
11617
|
+
if (!linuxSecretAvailable()) return true;
|
|
11618
|
+
return false;
|
|
11307
11619
|
} catch {
|
|
11308
11620
|
return false;
|
|
11309
11621
|
}
|
|
@@ -11453,15 +11765,25 @@ async function writeMachineBoundFileFallback(b64) {
|
|
|
11453
11765
|
await mkdir4(dir, { recursive: true });
|
|
11454
11766
|
const keyPath = getKeyPath();
|
|
11455
11767
|
const machineKey = deriveMachineKey();
|
|
11456
|
-
|
|
11457
|
-
|
|
11458
|
-
|
|
11459
|
-
|
|
11460
|
-
|
|
11768
|
+
const content = machineKey ? encryptWithMachineKey(b64, machineKey) + "\n" : b64 + "\n";
|
|
11769
|
+
const result = machineKey ? "encrypted" : "plaintext";
|
|
11770
|
+
const tmpPath = keyPath + ".tmp";
|
|
11771
|
+
try {
|
|
11772
|
+
if (existsSync18(keyPath)) {
|
|
11773
|
+
await copyFile(keyPath, keyPath + ".bak").catch(() => {
|
|
11774
|
+
});
|
|
11775
|
+
}
|
|
11776
|
+
await writeFile5(tmpPath, content, "utf-8");
|
|
11777
|
+
await chmod2(tmpPath, 384);
|
|
11778
|
+
await rename(tmpPath, keyPath);
|
|
11779
|
+
} catch (err) {
|
|
11780
|
+
try {
|
|
11781
|
+
await unlink(tmpPath);
|
|
11782
|
+
} catch {
|
|
11783
|
+
}
|
|
11784
|
+
throw err;
|
|
11461
11785
|
}
|
|
11462
|
-
|
|
11463
|
-
await chmod2(keyPath, 384);
|
|
11464
|
-
return "plaintext";
|
|
11786
|
+
return result;
|
|
11465
11787
|
}
|
|
11466
11788
|
async function getMasterKey() {
|
|
11467
11789
|
let nativeValue = macKeychainGet() ?? linuxSecretGet();
|
|
@@ -11528,7 +11850,7 @@ async function getMasterKey() {
|
|
|
11528
11850
|
b64Value = content;
|
|
11529
11851
|
}
|
|
11530
11852
|
const key = Buffer.from(b64Value, "base64");
|
|
11531
|
-
if (
|
|
11853
|
+
if (isRootOnlyTrustedServerKeyFile(keyPath)) {
|
|
11532
11854
|
return key;
|
|
11533
11855
|
}
|
|
11534
11856
|
const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
|
|
@@ -23181,7 +23503,7 @@ function agentColor(agentId) {
|
|
|
23181
23503
|
return "#F0EDE8";
|
|
23182
23504
|
}
|
|
23183
23505
|
}
|
|
23184
|
-
function
|
|
23506
|
+
function formatTimeAgo2(iso) {
|
|
23185
23507
|
const diff2 = Date.now() - new Date(iso).getTime();
|
|
23186
23508
|
const hours = Math.floor(diff2 / 36e5);
|
|
23187
23509
|
if (hours < 1) return "just now";
|
|
@@ -23507,7 +23829,7 @@ function WikiView({ onBack }) {
|
|
|
23507
23829
|
result.rawText.length > 80 ? "..." : ""
|
|
23508
23830
|
] }),
|
|
23509
23831
|
" ",
|
|
23510
|
-
/* @__PURE__ */ jsx13(Text, { color: isSelected ? "#F5D76E" : "#3D3660", dimColor: !isSelected, children:
|
|
23832
|
+
/* @__PURE__ */ jsx13(Text, { color: isSelected ? "#F5D76E" : "#3D3660", dimColor: !isSelected, children: formatTimeAgo2(result.timestamp) })
|
|
23511
23833
|
]
|
|
23512
23834
|
}
|
|
23513
23835
|
),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askexenow/exe-os",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.115",
|
|
4
4
|
"description": "AI employee operating system — persistent memory, task management, and multi-agent coordination for Claude Code.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"type": "module",
|