@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
|
@@ -446,6 +446,17 @@ function normalizeOrchestration(raw) {
|
|
|
446
446
|
const userOrg = raw.orchestration ?? {};
|
|
447
447
|
raw.orchestration = { ...defaultOrg, ...userOrg };
|
|
448
448
|
}
|
|
449
|
+
function normalizeCloudEndpoint(raw) {
|
|
450
|
+
const cloud = raw.cloud;
|
|
451
|
+
if (!cloud?.endpoint) return;
|
|
452
|
+
const ep = String(cloud.endpoint);
|
|
453
|
+
if (ep === "https://askexe.com/cloud" || ep === "https://askexe.com/cloud/") {
|
|
454
|
+
cloud.endpoint = "https://cloud.askexe.com";
|
|
455
|
+
process.stderr.write(
|
|
456
|
+
"[config] Auto-migrated cloud endpoint: askexe.com/cloud \u2192 cloud.askexe.com\n"
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
449
460
|
async function loadConfig() {
|
|
450
461
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
451
462
|
await ensurePrivateDir(dir);
|
|
@@ -471,6 +482,7 @@ async function loadConfig() {
|
|
|
471
482
|
normalizeSessionLifecycle(migratedCfg);
|
|
472
483
|
normalizeAutoUpdate(migratedCfg);
|
|
473
484
|
normalizeOrchestration(migratedCfg);
|
|
485
|
+
normalizeCloudEndpoint(migratedCfg);
|
|
474
486
|
const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
|
|
475
487
|
if (config.dbPath.startsWith("~")) {
|
|
476
488
|
config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
|
|
@@ -499,6 +511,7 @@ function loadConfigSync() {
|
|
|
499
511
|
normalizeSessionLifecycle(migratedCfg);
|
|
500
512
|
normalizeAutoUpdate(migratedCfg);
|
|
501
513
|
normalizeOrchestration(migratedCfg);
|
|
514
|
+
normalizeCloudEndpoint(migratedCfg);
|
|
502
515
|
const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
|
|
503
516
|
if (config.dbPath.startsWith("~")) {
|
|
504
517
|
config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
|
|
@@ -659,6 +672,7 @@ __export(agent_config_exports, {
|
|
|
659
672
|
clearAgentRuntime: () => clearAgentRuntime,
|
|
660
673
|
getAgentRuntime: () => getAgentRuntime,
|
|
661
674
|
loadAgentConfig: () => loadAgentConfig,
|
|
675
|
+
normalizeCcModelName: () => normalizeCcModelName,
|
|
662
676
|
saveAgentConfig: () => saveAgentConfig,
|
|
663
677
|
setAgentMcps: () => setAgentMcps,
|
|
664
678
|
setAgentRuntime: () => setAgentRuntime
|
|
@@ -687,6 +701,13 @@ function getAgentRuntime(agentId) {
|
|
|
687
701
|
if (orgDefault) return orgDefault;
|
|
688
702
|
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
689
703
|
}
|
|
704
|
+
function normalizeCcModelName(model) {
|
|
705
|
+
let ccModel = model.replace(/(\d+)\.(\d+)/g, "$1-$2");
|
|
706
|
+
if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
|
|
707
|
+
ccModel += "[1m]";
|
|
708
|
+
}
|
|
709
|
+
return ccModel;
|
|
710
|
+
}
|
|
690
711
|
function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
|
|
691
712
|
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
692
713
|
if (!knownModels) {
|
|
@@ -756,6 +777,7 @@ var init_agent_config = __esm({
|
|
|
756
777
|
// src/lib/intercom-queue.ts
|
|
757
778
|
var intercom_queue_exports = {};
|
|
758
779
|
__export(intercom_queue_exports, {
|
|
780
|
+
_resetDrainGuard: () => _resetDrainGuard,
|
|
759
781
|
clearQueueForAgent: () => clearQueueForAgent,
|
|
760
782
|
drainForSession: () => drainForSession,
|
|
761
783
|
drainQueue: () => drainQueue,
|
|
@@ -801,38 +823,47 @@ function queueIntercom(targetSession, reason) {
|
|
|
801
823
|
writeQueue(queue);
|
|
802
824
|
}
|
|
803
825
|
function drainQueue(isSessionBusy2, sendKeys) {
|
|
826
|
+
if (_draining) {
|
|
827
|
+
logQueue("SKIP_DRAIN \u2014 previous drain still running (possible tmux hang)");
|
|
828
|
+
return { drained: 0, failed: 0 };
|
|
829
|
+
}
|
|
804
830
|
const queue = readQueue();
|
|
805
831
|
if (queue.length === 0) return { drained: 0, failed: 0 };
|
|
832
|
+
_draining = true;
|
|
806
833
|
const remaining = [];
|
|
807
834
|
let drained = 0;
|
|
808
835
|
let failed = 0;
|
|
809
|
-
|
|
810
|
-
const
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
836
|
+
try {
|
|
837
|
+
for (const item of queue) {
|
|
838
|
+
const age = Date.now() - new Date(item.queuedAt).getTime();
|
|
839
|
+
if (age > TTL_MS) {
|
|
840
|
+
logQueue(`EXPIRED \u2192 ${item.targetSession} (${Math.round(age / 6e4)}min old, reason: ${item.reason})`);
|
|
841
|
+
failed++;
|
|
842
|
+
continue;
|
|
843
|
+
}
|
|
844
|
+
try {
|
|
845
|
+
if (!isSessionBusy2(item.targetSession)) {
|
|
846
|
+
const success = sendKeys(item.targetSession);
|
|
847
|
+
if (success) {
|
|
848
|
+
logQueue(`DRAINED \u2192 ${item.targetSession} (after ${item.attempts} retries)`);
|
|
849
|
+
drained++;
|
|
850
|
+
continue;
|
|
851
|
+
}
|
|
823
852
|
}
|
|
853
|
+
} catch {
|
|
824
854
|
}
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
855
|
+
item.attempts++;
|
|
856
|
+
if (item.attempts >= MAX_RETRIES) {
|
|
857
|
+
logQueue(`FAILED \u2192 ${item.targetSession} (${MAX_RETRIES} retries exhausted, reason: ${item.reason})`);
|
|
858
|
+
failed++;
|
|
859
|
+
continue;
|
|
860
|
+
}
|
|
861
|
+
remaining.push(item);
|
|
832
862
|
}
|
|
833
|
-
remaining
|
|
863
|
+
writeQueue(remaining);
|
|
864
|
+
} finally {
|
|
865
|
+
_draining = false;
|
|
834
866
|
}
|
|
835
|
-
writeQueue(remaining);
|
|
836
867
|
return { drained, failed };
|
|
837
868
|
}
|
|
838
869
|
function drainForSession(targetSession, sendKeys) {
|
|
@@ -857,6 +888,9 @@ function clearQueueForAgent(agentName) {
|
|
|
857
888
|
logQueue(`CLEARED ${before - filtered.length} stale item(s) for ${agentName}`);
|
|
858
889
|
}
|
|
859
890
|
}
|
|
891
|
+
function _resetDrainGuard() {
|
|
892
|
+
_draining = false;
|
|
893
|
+
}
|
|
860
894
|
function logQueue(msg) {
|
|
861
895
|
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] [queue] ${msg}
|
|
862
896
|
`;
|
|
@@ -868,13 +902,14 @@ function logQueue(msg) {
|
|
|
868
902
|
} catch {
|
|
869
903
|
}
|
|
870
904
|
}
|
|
871
|
-
var QUEUE_PATH, MAX_RETRIES, TTL_MS, INTERCOM_LOG;
|
|
905
|
+
var QUEUE_PATH, MAX_RETRIES, TTL_MS, _draining, INTERCOM_LOG;
|
|
872
906
|
var init_intercom_queue = __esm({
|
|
873
907
|
"src/lib/intercom-queue.ts"() {
|
|
874
908
|
"use strict";
|
|
875
909
|
QUEUE_PATH = path4.join(os3.homedir(), ".exe-os", "intercom-queue.json");
|
|
876
910
|
MAX_RETRIES = 5;
|
|
877
911
|
TTL_MS = 60 * 60 * 1e3;
|
|
912
|
+
_draining = false;
|
|
878
913
|
INTERCOM_LOG = path4.join(os3.homedir(), ".exe-os", "intercom.log");
|
|
879
914
|
}
|
|
880
915
|
});
|
|
@@ -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 path12 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 || path12.join(os9.homedir(), ".exe-os");
|
|
4971
|
+
const notifDir = path12.join(base, "notifications");
|
|
4972
|
+
if (!existsSync13(notifDir)) return 0;
|
|
4973
|
+
let migrated = 0;
|
|
4974
|
+
try {
|
|
4975
|
+
const files = readdirSync(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 = path12.join(notifDir, file);
|
|
4981
|
+
const data = JSON.parse(readFileSync10(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
|
+
unlinkSync4(filePath);
|
|
4999
|
+
migrated++;
|
|
5000
|
+
} catch {
|
|
5001
|
+
}
|
|
5002
|
+
}
|
|
5003
|
+
try {
|
|
5004
|
+
const remaining = readdirSync(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
|
|
|
@@ -5882,7 +6107,13 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
|
|
|
5882
6107
|
taskFile
|
|
5883
6108
|
});
|
|
5884
6109
|
const originalPriority = String(row.priority).toLowerCase();
|
|
5885
|
-
const
|
|
6110
|
+
const resultLower = result?.toLowerCase() ?? "";
|
|
6111
|
+
const hasTestEvidence = (
|
|
6112
|
+
// Vitest/Jest output patterns (hard to fake without actually running tests)
|
|
6113
|
+
/\d+\s+pass(ed|ing)/.test(resultLower) || /test files?\s+\d+\s+passed/.test(resultLower) || /tests?\s+\d+\s+passed/.test(resultLower)
|
|
6114
|
+
);
|
|
6115
|
+
const hasNoFailures = !/fail(ed|ure|ing)|error/i.test(resultLower);
|
|
6116
|
+
const autoApprove = originalPriority === "p2" && hasTestEvidence && hasNoFailures;
|
|
5886
6117
|
if (!autoApprove) {
|
|
5887
6118
|
try {
|
|
5888
6119
|
const key = getSessionKey();
|
|
@@ -5890,6 +6121,13 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
|
|
|
5890
6121
|
if (exeSession) {
|
|
5891
6122
|
sendIntercom(exeSession);
|
|
5892
6123
|
}
|
|
6124
|
+
if (reviewer && reviewer !== coordinatorName && reviewer !== exeSession) {
|
|
6125
|
+
const { employeeSessionName: employeeSessionName2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
6126
|
+
if (exeSession) {
|
|
6127
|
+
const reviewerSession = employeeSessionName2(reviewer, exeSession);
|
|
6128
|
+
sendIntercom(reviewerSession);
|
|
6129
|
+
}
|
|
6130
|
+
}
|
|
5893
6131
|
} catch {
|
|
5894
6132
|
}
|
|
5895
6133
|
}
|
|
@@ -6665,6 +6903,20 @@ async function updateTask(input) {
|
|
|
6665
6903
|
notifyTaskDone();
|
|
6666
6904
|
}
|
|
6667
6905
|
await markTaskNotificationsRead(taskFile);
|
|
6906
|
+
if (input.status === "needs_review" && !isCoordinator) {
|
|
6907
|
+
try {
|
|
6908
|
+
const { writeNotification: writeNotification2 } = await Promise.resolve().then(() => (init_notifications(), notifications_exports));
|
|
6909
|
+
await writeNotification2({
|
|
6910
|
+
agentId: String(row.assigned_to),
|
|
6911
|
+
agentRole: String(row.assigned_to),
|
|
6912
|
+
event: "task_complete",
|
|
6913
|
+
project: String(row.project_name),
|
|
6914
|
+
summary: `"${String(row.title)}" is ready for review`,
|
|
6915
|
+
taskFile
|
|
6916
|
+
});
|
|
6917
|
+
} catch {
|
|
6918
|
+
}
|
|
6919
|
+
}
|
|
6668
6920
|
if (input.status === "done" || input.status === "closed") {
|
|
6669
6921
|
try {
|
|
6670
6922
|
await cascadeUnblock(taskId, input.baseDir, now);
|
|
@@ -7097,18 +7349,31 @@ function acquireSpawnLock2(sessionName) {
|
|
|
7097
7349
|
mkdirSync8(SPAWN_LOCK_DIR, { recursive: true });
|
|
7098
7350
|
}
|
|
7099
7351
|
const lockFile = spawnLockPath(sessionName);
|
|
7100
|
-
|
|
7101
|
-
|
|
7102
|
-
|
|
7103
|
-
|
|
7104
|
-
|
|
7105
|
-
|
|
7106
|
-
|
|
7107
|
-
|
|
7352
|
+
const lockData = JSON.stringify({ pid: process.pid, timestamp: Date.now() });
|
|
7353
|
+
const { openSync: openSync3, closeSync: closeSync3, writeSync } = __require("fs");
|
|
7354
|
+
const { constants } = __require("fs");
|
|
7355
|
+
try {
|
|
7356
|
+
const fd = openSync3(lockFile, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 420);
|
|
7357
|
+
writeSync(fd, lockData);
|
|
7358
|
+
closeSync3(fd);
|
|
7359
|
+
return true;
|
|
7360
|
+
} catch (err) {
|
|
7361
|
+
if (err?.code !== "EEXIST") {
|
|
7362
|
+
return true;
|
|
7108
7363
|
}
|
|
7109
7364
|
}
|
|
7110
|
-
|
|
7111
|
-
|
|
7365
|
+
try {
|
|
7366
|
+
const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
|
|
7367
|
+
const age = Date.now() - lock.timestamp;
|
|
7368
|
+
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
7369
|
+
return false;
|
|
7370
|
+
}
|
|
7371
|
+
writeFileSync8(lockFile, lockData);
|
|
7372
|
+
return true;
|
|
7373
|
+
} catch {
|
|
7374
|
+
writeFileSync8(lockFile, lockData);
|
|
7375
|
+
return true;
|
|
7376
|
+
}
|
|
7112
7377
|
}
|
|
7113
7378
|
function releaseSpawnLock2(sessionName) {
|
|
7114
7379
|
try {
|
|
@@ -7187,6 +7452,21 @@ function parseParentExe(sessionName, agentId) {
|
|
|
7187
7452
|
function extractRootExe(name) {
|
|
7188
7453
|
if (!name) return null;
|
|
7189
7454
|
if (!name.includes("-")) return name;
|
|
7455
|
+
try {
|
|
7456
|
+
const roster = (init_employees(), __toCommonJS(employees_exports)).loadEmployeesSync();
|
|
7457
|
+
if (roster.length > 0) {
|
|
7458
|
+
const sortedNames = roster.map((e) => e.name).sort((a, b) => b.length - a.length);
|
|
7459
|
+
for (const agentName of sortedNames) {
|
|
7460
|
+
const escaped = agentName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7461
|
+
const regex = new RegExp(`^${escaped}\\d*-(.+)$`);
|
|
7462
|
+
const match = name.match(regex);
|
|
7463
|
+
if (match) {
|
|
7464
|
+
return extractRootExe(match[1]);
|
|
7465
|
+
}
|
|
7466
|
+
}
|
|
7467
|
+
}
|
|
7468
|
+
} catch {
|
|
7469
|
+
}
|
|
7190
7470
|
const parts = name.split("-").filter(Boolean);
|
|
7191
7471
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
7192
7472
|
}
|
|
@@ -7205,6 +7485,10 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
7205
7485
|
function getParentExe(sessionKey) {
|
|
7206
7486
|
try {
|
|
7207
7487
|
const data = JSON.parse(readFileSync12(path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
7488
|
+
if (data.registeredAt) {
|
|
7489
|
+
const age = Date.now() - new Date(data.registeredAt).getTime();
|
|
7490
|
+
if (age > PARENT_EXE_CACHE_TTL_MS) return null;
|
|
7491
|
+
}
|
|
7208
7492
|
return data.parentExe || null;
|
|
7209
7493
|
} catch {
|
|
7210
7494
|
return null;
|
|
@@ -7753,7 +8037,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
7753
8037
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
7754
8038
|
} catch {
|
|
7755
8039
|
}
|
|
7756
|
-
let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName}`;
|
|
8040
|
+
let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName} EXE_SESSION_START_ISO=${(/* @__PURE__ */ new Date()).toISOString()}`;
|
|
7757
8041
|
if (ccProvider !== DEFAULT_PROVIDER) {
|
|
7758
8042
|
const cfg = PROVIDER_TABLE[ccProvider];
|
|
7759
8043
|
if (cfg?.apiKeyEnv) {
|
|
@@ -7788,10 +8072,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
7788
8072
|
}
|
|
7789
8073
|
if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
|
|
7790
8074
|
if (agentRtConfig.runtime === "claude" && agentRtConfig.model) {
|
|
7791
|
-
|
|
7792
|
-
|
|
7793
|
-
ccModel += "[1m]";
|
|
7794
|
-
}
|
|
8075
|
+
const { normalizeCcModelName: normalizeCcModelName2 } = (init_agent_config(), __toCommonJS(agent_config_exports));
|
|
8076
|
+
const ccModel = normalizeCcModelName2(agentRtConfig.model);
|
|
7795
8077
|
envPrefix = `${envPrefix} ANTHROPIC_MODEL=${ccModel}`;
|
|
7796
8078
|
}
|
|
7797
8079
|
}
|
|
@@ -7892,7 +8174,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
7892
8174
|
releaseSpawnLock2(sessionName);
|
|
7893
8175
|
return { sessionName };
|
|
7894
8176
|
}
|
|
7895
|
-
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;
|
|
8177
|
+
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;
|
|
7896
8178
|
var init_tmux_routing = __esm({
|
|
7897
8179
|
"src/lib/tmux-routing.ts"() {
|
|
7898
8180
|
"use strict";
|
|
@@ -7912,6 +8194,7 @@ var init_tmux_routing = __esm({
|
|
|
7912
8194
|
SESSION_CACHE = path18.join(os11.homedir(), ".exe-os", "session-cache");
|
|
7913
8195
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
7914
8196
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
8197
|
+
PARENT_EXE_CACHE_TTL_MS = 4 * 60 * 60 * 1e3;
|
|
7915
8198
|
VERIFY_PANE_LINES = 200;
|
|
7916
8199
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
7917
8200
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
@@ -7956,7 +8239,7 @@ var init_task_scope = __esm({
|
|
|
7956
8239
|
});
|
|
7957
8240
|
|
|
7958
8241
|
// src/lib/keychain.ts
|
|
7959
|
-
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
8242
|
+
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2, rename, copyFile } from "fs/promises";
|
|
7960
8243
|
import { existsSync as existsSync17, statSync as statSync3 } from "fs";
|
|
7961
8244
|
import { execSync as execSync9 } from "child_process";
|
|
7962
8245
|
import path19 from "path";
|
|
@@ -7991,12 +8274,14 @@ function linuxSecretAvailable() {
|
|
|
7991
8274
|
function isRootOnlyTrustedServerKeyFile(keyPath) {
|
|
7992
8275
|
if (process.platform !== "linux") return false;
|
|
7993
8276
|
try {
|
|
7994
|
-
const uid = typeof os12.userInfo().uid === "number" ? os12.userInfo().uid : -1;
|
|
7995
8277
|
const st = statSync3(keyPath);
|
|
7996
8278
|
if (!st.isFile() || (st.mode & 63) !== 0) return false;
|
|
8279
|
+
const uid = typeof os12.userInfo().uid === "number" ? os12.userInfo().uid : -1;
|
|
7997
8280
|
if (uid === 0) return true;
|
|
7998
8281
|
const exeOsDir = process.env.EXE_OS_DIR;
|
|
7999
|
-
|
|
8282
|
+
if (exeOsDir && path19.resolve(keyPath).startsWith(path19.resolve(exeOsDir) + path19.sep)) return true;
|
|
8283
|
+
if (!linuxSecretAvailable()) return true;
|
|
8284
|
+
return false;
|
|
8000
8285
|
} catch {
|
|
8001
8286
|
return false;
|
|
8002
8287
|
}
|
|
@@ -8146,15 +8431,25 @@ async function writeMachineBoundFileFallback(b64) {
|
|
|
8146
8431
|
await mkdir4(dir, { recursive: true });
|
|
8147
8432
|
const keyPath = getKeyPath();
|
|
8148
8433
|
const machineKey = deriveMachineKey();
|
|
8149
|
-
|
|
8150
|
-
|
|
8151
|
-
|
|
8152
|
-
|
|
8153
|
-
|
|
8434
|
+
const content = machineKey ? encryptWithMachineKey(b64, machineKey) + "\n" : b64 + "\n";
|
|
8435
|
+
const result = machineKey ? "encrypted" : "plaintext";
|
|
8436
|
+
const tmpPath = keyPath + ".tmp";
|
|
8437
|
+
try {
|
|
8438
|
+
if (existsSync17(keyPath)) {
|
|
8439
|
+
await copyFile(keyPath, keyPath + ".bak").catch(() => {
|
|
8440
|
+
});
|
|
8441
|
+
}
|
|
8442
|
+
await writeFile5(tmpPath, content, "utf-8");
|
|
8443
|
+
await chmod2(tmpPath, 384);
|
|
8444
|
+
await rename(tmpPath, keyPath);
|
|
8445
|
+
} catch (err) {
|
|
8446
|
+
try {
|
|
8447
|
+
await unlink(tmpPath);
|
|
8448
|
+
} catch {
|
|
8449
|
+
}
|
|
8450
|
+
throw err;
|
|
8154
8451
|
}
|
|
8155
|
-
|
|
8156
|
-
await chmod2(keyPath, 384);
|
|
8157
|
-
return "plaintext";
|
|
8452
|
+
return result;
|
|
8158
8453
|
}
|
|
8159
8454
|
async function getMasterKey() {
|
|
8160
8455
|
let nativeValue = macKeychainGet() ?? linuxSecretGet();
|
|
@@ -8221,7 +8516,7 @@ async function getMasterKey() {
|
|
|
8221
8516
|
b64Value = content;
|
|
8222
8517
|
}
|
|
8223
8518
|
const key = Buffer.from(b64Value, "base64");
|
|
8224
|
-
if (
|
|
8519
|
+
if (isRootOnlyTrustedServerKeyFile(keyPath)) {
|
|
8225
8520
|
return key;
|
|
8226
8521
|
}
|
|
8227
8522
|
const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
|