@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/bin/scan-tasks.js
CHANGED
|
@@ -459,6 +459,17 @@ function normalizeOrchestration(raw) {
|
|
|
459
459
|
const userOrg = raw.orchestration ?? {};
|
|
460
460
|
raw.orchestration = { ...defaultOrg, ...userOrg };
|
|
461
461
|
}
|
|
462
|
+
function normalizeCloudEndpoint(raw) {
|
|
463
|
+
const cloud = raw.cloud;
|
|
464
|
+
if (!cloud?.endpoint) return;
|
|
465
|
+
const ep = String(cloud.endpoint);
|
|
466
|
+
if (ep === "https://askexe.com/cloud" || ep === "https://askexe.com/cloud/") {
|
|
467
|
+
cloud.endpoint = "https://cloud.askexe.com";
|
|
468
|
+
process.stderr.write(
|
|
469
|
+
"[config] Auto-migrated cloud endpoint: askexe.com/cloud \u2192 cloud.askexe.com\n"
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
462
473
|
async function loadConfig() {
|
|
463
474
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
464
475
|
await ensurePrivateDir(dir);
|
|
@@ -484,6 +495,7 @@ async function loadConfig() {
|
|
|
484
495
|
normalizeSessionLifecycle(migratedCfg);
|
|
485
496
|
normalizeAutoUpdate(migratedCfg);
|
|
486
497
|
normalizeOrchestration(migratedCfg);
|
|
498
|
+
normalizeCloudEndpoint(migratedCfg);
|
|
487
499
|
const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
|
|
488
500
|
if (config.dbPath.startsWith("~")) {
|
|
489
501
|
config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
|
|
@@ -512,6 +524,7 @@ function loadConfigSync() {
|
|
|
512
524
|
normalizeSessionLifecycle(migratedCfg);
|
|
513
525
|
normalizeAutoUpdate(migratedCfg);
|
|
514
526
|
normalizeOrchestration(migratedCfg);
|
|
527
|
+
normalizeCloudEndpoint(migratedCfg);
|
|
515
528
|
const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
|
|
516
529
|
if (config.dbPath.startsWith("~")) {
|
|
517
530
|
config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
|
|
@@ -672,6 +685,7 @@ __export(agent_config_exports, {
|
|
|
672
685
|
clearAgentRuntime: () => clearAgentRuntime,
|
|
673
686
|
getAgentRuntime: () => getAgentRuntime,
|
|
674
687
|
loadAgentConfig: () => loadAgentConfig,
|
|
688
|
+
normalizeCcModelName: () => normalizeCcModelName,
|
|
675
689
|
saveAgentConfig: () => saveAgentConfig,
|
|
676
690
|
setAgentMcps: () => setAgentMcps,
|
|
677
691
|
setAgentRuntime: () => setAgentRuntime
|
|
@@ -700,6 +714,13 @@ function getAgentRuntime(agentId) {
|
|
|
700
714
|
if (orgDefault) return orgDefault;
|
|
701
715
|
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
702
716
|
}
|
|
717
|
+
function normalizeCcModelName(model) {
|
|
718
|
+
let ccModel = model.replace(/(\d+)\.(\d+)/g, "$1-$2");
|
|
719
|
+
if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
|
|
720
|
+
ccModel += "[1m]";
|
|
721
|
+
}
|
|
722
|
+
return ccModel;
|
|
723
|
+
}
|
|
703
724
|
function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
|
|
704
725
|
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
705
726
|
if (!knownModels) {
|
|
@@ -769,6 +790,7 @@ var init_agent_config = __esm({
|
|
|
769
790
|
// src/lib/intercom-queue.ts
|
|
770
791
|
var intercom_queue_exports = {};
|
|
771
792
|
__export(intercom_queue_exports, {
|
|
793
|
+
_resetDrainGuard: () => _resetDrainGuard,
|
|
772
794
|
clearQueueForAgent: () => clearQueueForAgent,
|
|
773
795
|
drainForSession: () => drainForSession,
|
|
774
796
|
drainQueue: () => drainQueue,
|
|
@@ -814,38 +836,47 @@ function queueIntercom(targetSession, reason) {
|
|
|
814
836
|
writeQueue(queue);
|
|
815
837
|
}
|
|
816
838
|
function drainQueue(isSessionBusy2, sendKeys) {
|
|
839
|
+
if (_draining) {
|
|
840
|
+
logQueue("SKIP_DRAIN \u2014 previous drain still running (possible tmux hang)");
|
|
841
|
+
return { drained: 0, failed: 0 };
|
|
842
|
+
}
|
|
817
843
|
const queue = readQueue();
|
|
818
844
|
if (queue.length === 0) return { drained: 0, failed: 0 };
|
|
845
|
+
_draining = true;
|
|
819
846
|
const remaining = [];
|
|
820
847
|
let drained = 0;
|
|
821
848
|
let failed = 0;
|
|
822
|
-
|
|
823
|
-
const
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
849
|
+
try {
|
|
850
|
+
for (const item of queue) {
|
|
851
|
+
const age = Date.now() - new Date(item.queuedAt).getTime();
|
|
852
|
+
if (age > TTL_MS) {
|
|
853
|
+
logQueue(`EXPIRED \u2192 ${item.targetSession} (${Math.round(age / 6e4)}min old, reason: ${item.reason})`);
|
|
854
|
+
failed++;
|
|
855
|
+
continue;
|
|
856
|
+
}
|
|
857
|
+
try {
|
|
858
|
+
if (!isSessionBusy2(item.targetSession)) {
|
|
859
|
+
const success = sendKeys(item.targetSession);
|
|
860
|
+
if (success) {
|
|
861
|
+
logQueue(`DRAINED \u2192 ${item.targetSession} (after ${item.attempts} retries)`);
|
|
862
|
+
drained++;
|
|
863
|
+
continue;
|
|
864
|
+
}
|
|
836
865
|
}
|
|
866
|
+
} catch {
|
|
837
867
|
}
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
868
|
+
item.attempts++;
|
|
869
|
+
if (item.attempts >= MAX_RETRIES) {
|
|
870
|
+
logQueue(`FAILED \u2192 ${item.targetSession} (${MAX_RETRIES} retries exhausted, reason: ${item.reason})`);
|
|
871
|
+
failed++;
|
|
872
|
+
continue;
|
|
873
|
+
}
|
|
874
|
+
remaining.push(item);
|
|
845
875
|
}
|
|
846
|
-
remaining
|
|
876
|
+
writeQueue(remaining);
|
|
877
|
+
} finally {
|
|
878
|
+
_draining = false;
|
|
847
879
|
}
|
|
848
|
-
writeQueue(remaining);
|
|
849
880
|
return { drained, failed };
|
|
850
881
|
}
|
|
851
882
|
function drainForSession(targetSession, sendKeys) {
|
|
@@ -870,6 +901,9 @@ function clearQueueForAgent(agentName) {
|
|
|
870
901
|
logQueue(`CLEARED ${before - filtered.length} stale item(s) for ${agentName}`);
|
|
871
902
|
}
|
|
872
903
|
}
|
|
904
|
+
function _resetDrainGuard() {
|
|
905
|
+
_draining = false;
|
|
906
|
+
}
|
|
873
907
|
function logQueue(msg) {
|
|
874
908
|
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] [queue] ${msg}
|
|
875
909
|
`;
|
|
@@ -881,13 +915,14 @@ function logQueue(msg) {
|
|
|
881
915
|
} catch {
|
|
882
916
|
}
|
|
883
917
|
}
|
|
884
|
-
var QUEUE_PATH, MAX_RETRIES, TTL_MS, INTERCOM_LOG;
|
|
918
|
+
var QUEUE_PATH, MAX_RETRIES, TTL_MS, _draining, INTERCOM_LOG;
|
|
885
919
|
var init_intercom_queue = __esm({
|
|
886
920
|
"src/lib/intercom-queue.ts"() {
|
|
887
921
|
"use strict";
|
|
888
922
|
QUEUE_PATH = path4.join(os3.homedir(), ".exe-os", "intercom-queue.json");
|
|
889
923
|
MAX_RETRIES = 5;
|
|
890
924
|
TTL_MS = 60 * 60 * 1e3;
|
|
925
|
+
_draining = false;
|
|
891
926
|
INTERCOM_LOG = path4.join(os3.homedir(), ".exe-os", "intercom.log");
|
|
892
927
|
}
|
|
893
928
|
});
|
|
@@ -4781,6 +4816,17 @@ var init_agent_symlinks = __esm({
|
|
|
4781
4816
|
});
|
|
4782
4817
|
|
|
4783
4818
|
// src/lib/notifications.ts
|
|
4819
|
+
var notifications_exports = {};
|
|
4820
|
+
__export(notifications_exports, {
|
|
4821
|
+
cleanupOldNotifications: () => cleanupOldNotifications,
|
|
4822
|
+
formatNotifications: () => formatNotifications,
|
|
4823
|
+
markAsRead: () => markAsRead,
|
|
4824
|
+
markAsReadByTaskFile: () => markAsReadByTaskFile,
|
|
4825
|
+
markDoneTaskNotificationsAsRead: () => markDoneTaskNotificationsAsRead,
|
|
4826
|
+
migrateJsonNotifications: () => migrateJsonNotifications,
|
|
4827
|
+
readUnreadNotifications: () => readUnreadNotifications,
|
|
4828
|
+
writeNotification: () => writeNotification
|
|
4829
|
+
});
|
|
4784
4830
|
import crypto2 from "crypto";
|
|
4785
4831
|
import path12 from "path";
|
|
4786
4832
|
import os9 from "os";
|
|
@@ -4817,6 +4863,52 @@ async function writeNotification(notification) {
|
|
|
4817
4863
|
`);
|
|
4818
4864
|
}
|
|
4819
4865
|
}
|
|
4866
|
+
async function readUnreadNotifications(agentFilter, sessionScope) {
|
|
4867
|
+
try {
|
|
4868
|
+
const client = getClient();
|
|
4869
|
+
const conditions = ["read = 0"];
|
|
4870
|
+
const args = [];
|
|
4871
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
4872
|
+
if (agentFilter) {
|
|
4873
|
+
conditions.push("agent_id = ?");
|
|
4874
|
+
args.push(agentFilter);
|
|
4875
|
+
}
|
|
4876
|
+
const result = await client.execute({
|
|
4877
|
+
sql: `SELECT id, agent_id, agent_role, event, project, summary, task_file, session_scope, created_at
|
|
4878
|
+
FROM notifications
|
|
4879
|
+
WHERE ${conditions.join(" AND ")}${scope.sql}
|
|
4880
|
+
ORDER BY created_at ASC`,
|
|
4881
|
+
args: [...args, ...scope.args]
|
|
4882
|
+
});
|
|
4883
|
+
return result.rows.map((r) => ({
|
|
4884
|
+
id: String(r.id),
|
|
4885
|
+
agentId: String(r.agent_id),
|
|
4886
|
+
agentRole: String(r.agent_role),
|
|
4887
|
+
event: String(r.event),
|
|
4888
|
+
project: String(r.project),
|
|
4889
|
+
summary: String(r.summary),
|
|
4890
|
+
taskFile: r.task_file ? String(r.task_file) : void 0,
|
|
4891
|
+
sessionScope: r.session_scope == null ? null : String(r.session_scope),
|
|
4892
|
+
timestamp: String(r.created_at),
|
|
4893
|
+
read: false
|
|
4894
|
+
}));
|
|
4895
|
+
} catch {
|
|
4896
|
+
return [];
|
|
4897
|
+
}
|
|
4898
|
+
}
|
|
4899
|
+
async function markAsRead(ids, sessionScope) {
|
|
4900
|
+
if (ids.length === 0) return;
|
|
4901
|
+
try {
|
|
4902
|
+
const client = getClient();
|
|
4903
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
4904
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
4905
|
+
await client.execute({
|
|
4906
|
+
sql: `UPDATE notifications SET read = 1 WHERE id IN (${placeholders})${scope.sql}`,
|
|
4907
|
+
args: [...ids, ...scope.args]
|
|
4908
|
+
});
|
|
4909
|
+
} catch {
|
|
4910
|
+
}
|
|
4911
|
+
}
|
|
4820
4912
|
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
4821
4913
|
try {
|
|
4822
4914
|
const client = getClient();
|
|
@@ -4829,11 +4921,144 @@ async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
|
4829
4921
|
} catch {
|
|
4830
4922
|
}
|
|
4831
4923
|
}
|
|
4924
|
+
async function cleanupOldNotifications(daysOld = CLEANUP_DAYS, sessionScope) {
|
|
4925
|
+
try {
|
|
4926
|
+
const client = getClient();
|
|
4927
|
+
const cutoff = new Date(
|
|
4928
|
+
Date.now() - daysOld * 24 * 60 * 60 * 1e3
|
|
4929
|
+
).toISOString();
|
|
4930
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
4931
|
+
const result = await client.execute({
|
|
4932
|
+
sql: `DELETE FROM notifications WHERE created_at < ?${scope.sql}`,
|
|
4933
|
+
args: [cutoff, ...scope.args]
|
|
4934
|
+
});
|
|
4935
|
+
return result.rowsAffected;
|
|
4936
|
+
} catch {
|
|
4937
|
+
return 0;
|
|
4938
|
+
}
|
|
4939
|
+
}
|
|
4940
|
+
async function markDoneTaskNotificationsAsRead(sessionScope) {
|
|
4941
|
+
try {
|
|
4942
|
+
const client = getClient();
|
|
4943
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
4944
|
+
const result = await client.execute({
|
|
4945
|
+
sql: `UPDATE notifications SET read = 1
|
|
4946
|
+
WHERE read = 0
|
|
4947
|
+
AND task_file IS NOT NULL
|
|
4948
|
+
${scope.sql}
|
|
4949
|
+
AND task_file IN (
|
|
4950
|
+
SELECT task_file FROM tasks WHERE status = 'done'${scope.sql}
|
|
4951
|
+
)`,
|
|
4952
|
+
args: [...scope.args, ...scope.args]
|
|
4953
|
+
});
|
|
4954
|
+
return result.rowsAffected;
|
|
4955
|
+
} catch {
|
|
4956
|
+
return 0;
|
|
4957
|
+
}
|
|
4958
|
+
}
|
|
4959
|
+
function formatNotifications(notifications) {
|
|
4960
|
+
if (notifications.length === 0) return "";
|
|
4961
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
4962
|
+
for (const n of notifications) {
|
|
4963
|
+
const key = `${n.agentId}|${n.agentRole}`;
|
|
4964
|
+
if (!grouped.has(key)) grouped.set(key, []);
|
|
4965
|
+
grouped.get(key).push(n);
|
|
4966
|
+
}
|
|
4967
|
+
const lines = [];
|
|
4968
|
+
lines.push(`## Notifications (${notifications.length} unread)
|
|
4969
|
+
`);
|
|
4970
|
+
for (const [key, items] of grouped) {
|
|
4971
|
+
const [agentId, agentRole] = key.split("|");
|
|
4972
|
+
lines.push(`**${agentId}** (${agentRole}):`);
|
|
4973
|
+
for (const item of items) {
|
|
4974
|
+
const ago = formatTimeAgo(item.timestamp);
|
|
4975
|
+
const icon = eventIcon(item.event);
|
|
4976
|
+
lines.push(`- ${icon} ${item.summary} (${item.project}) \u2014 ${ago}`);
|
|
4977
|
+
}
|
|
4978
|
+
lines.push("");
|
|
4979
|
+
}
|
|
4980
|
+
return lines.join("\n");
|
|
4981
|
+
}
|
|
4982
|
+
async function migrateJsonNotifications() {
|
|
4983
|
+
const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR || path12.join(os9.homedir(), ".exe-os");
|
|
4984
|
+
const notifDir = path12.join(base, "notifications");
|
|
4985
|
+
if (!existsSync13(notifDir)) return 0;
|
|
4986
|
+
let migrated = 0;
|
|
4987
|
+
try {
|
|
4988
|
+
const files = readdirSync(notifDir).filter((f) => f.endsWith(".json"));
|
|
4989
|
+
if (files.length === 0) return 0;
|
|
4990
|
+
const client = getClient();
|
|
4991
|
+
for (const file of files) {
|
|
4992
|
+
try {
|
|
4993
|
+
const filePath = path12.join(notifDir, file);
|
|
4994
|
+
const data = JSON.parse(readFileSync10(filePath, "utf8"));
|
|
4995
|
+
await client.execute({
|
|
4996
|
+
sql: `INSERT OR IGNORE INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
4997
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
4998
|
+
args: [
|
|
4999
|
+
crypto2.randomUUID(),
|
|
5000
|
+
data.agentId ?? "unknown",
|
|
5001
|
+
data.agentRole ?? "unknown",
|
|
5002
|
+
data.event ?? "session_summary",
|
|
5003
|
+
data.project ?? "unknown",
|
|
5004
|
+
data.summary ?? "",
|
|
5005
|
+
data.taskFile ?? null,
|
|
5006
|
+
null,
|
|
5007
|
+
data.read ? 1 : 0,
|
|
5008
|
+
data.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
5009
|
+
]
|
|
5010
|
+
});
|
|
5011
|
+
unlinkSync4(filePath);
|
|
5012
|
+
migrated++;
|
|
5013
|
+
} catch {
|
|
5014
|
+
}
|
|
5015
|
+
}
|
|
5016
|
+
try {
|
|
5017
|
+
const remaining = readdirSync(notifDir);
|
|
5018
|
+
if (remaining.length === 0) {
|
|
5019
|
+
rmdirSync(notifDir);
|
|
5020
|
+
}
|
|
5021
|
+
} catch {
|
|
5022
|
+
}
|
|
5023
|
+
} catch {
|
|
5024
|
+
}
|
|
5025
|
+
return migrated;
|
|
5026
|
+
}
|
|
5027
|
+
function eventIcon(event) {
|
|
5028
|
+
switch (event) {
|
|
5029
|
+
case "task_complete":
|
|
5030
|
+
return "Completed:";
|
|
5031
|
+
case "task_needs_fix":
|
|
5032
|
+
return "Needs fix:";
|
|
5033
|
+
case "session_summary":
|
|
5034
|
+
return "Session:";
|
|
5035
|
+
case "error_spike":
|
|
5036
|
+
return "Errors:";
|
|
5037
|
+
case "orphan_task":
|
|
5038
|
+
return "Orphan:";
|
|
5039
|
+
case "subtasks_complete":
|
|
5040
|
+
return "Subtasks done:";
|
|
5041
|
+
case "capacity_relaunch":
|
|
5042
|
+
return "Relaunched:";
|
|
5043
|
+
}
|
|
5044
|
+
}
|
|
5045
|
+
function formatTimeAgo(timestamp) {
|
|
5046
|
+
const diffMs = Date.now() - new Date(timestamp).getTime();
|
|
5047
|
+
const mins = Math.floor(diffMs / 6e4);
|
|
5048
|
+
if (mins < 1) return "just now";
|
|
5049
|
+
if (mins < 60) return `${mins}m ago`;
|
|
5050
|
+
const hours = Math.floor(mins / 60);
|
|
5051
|
+
if (hours < 24) return `${hours}h ago`;
|
|
5052
|
+
const days = Math.floor(hours / 24);
|
|
5053
|
+
return `${days}d ago`;
|
|
5054
|
+
}
|
|
5055
|
+
var CLEANUP_DAYS;
|
|
4832
5056
|
var init_notifications = __esm({
|
|
4833
5057
|
"src/lib/notifications.ts"() {
|
|
4834
5058
|
"use strict";
|
|
4835
5059
|
init_database();
|
|
4836
5060
|
init_task_scope();
|
|
5061
|
+
CLEANUP_DAYS = 7;
|
|
4837
5062
|
}
|
|
4838
5063
|
});
|
|
4839
5064
|
|
|
@@ -5888,7 +6113,13 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
|
|
|
5888
6113
|
taskFile
|
|
5889
6114
|
});
|
|
5890
6115
|
const originalPriority = String(row.priority).toLowerCase();
|
|
5891
|
-
const
|
|
6116
|
+
const resultLower = result?.toLowerCase() ?? "";
|
|
6117
|
+
const hasTestEvidence = (
|
|
6118
|
+
// Vitest/Jest output patterns (hard to fake without actually running tests)
|
|
6119
|
+
/\d+\s+pass(ed|ing)/.test(resultLower) || /test files?\s+\d+\s+passed/.test(resultLower) || /tests?\s+\d+\s+passed/.test(resultLower)
|
|
6120
|
+
);
|
|
6121
|
+
const hasNoFailures = !/fail(ed|ure|ing)|error/i.test(resultLower);
|
|
6122
|
+
const autoApprove = originalPriority === "p2" && hasTestEvidence && hasNoFailures;
|
|
5892
6123
|
if (!autoApprove) {
|
|
5893
6124
|
try {
|
|
5894
6125
|
const key = getSessionKey();
|
|
@@ -5896,6 +6127,13 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
|
|
|
5896
6127
|
if (exeSession) {
|
|
5897
6128
|
sendIntercom(exeSession);
|
|
5898
6129
|
}
|
|
6130
|
+
if (reviewer && reviewer !== coordinatorName && reviewer !== exeSession) {
|
|
6131
|
+
const { employeeSessionName: employeeSessionName2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
6132
|
+
if (exeSession) {
|
|
6133
|
+
const reviewerSession = employeeSessionName2(reviewer, exeSession);
|
|
6134
|
+
sendIntercom(reviewerSession);
|
|
6135
|
+
}
|
|
6136
|
+
}
|
|
5899
6137
|
} catch {
|
|
5900
6138
|
}
|
|
5901
6139
|
}
|
|
@@ -6671,6 +6909,20 @@ async function updateTask(input) {
|
|
|
6671
6909
|
notifyTaskDone();
|
|
6672
6910
|
}
|
|
6673
6911
|
await markTaskNotificationsRead(taskFile);
|
|
6912
|
+
if (input.status === "needs_review" && !isCoordinator) {
|
|
6913
|
+
try {
|
|
6914
|
+
const { writeNotification: writeNotification2 } = await Promise.resolve().then(() => (init_notifications(), notifications_exports));
|
|
6915
|
+
await writeNotification2({
|
|
6916
|
+
agentId: String(row.assigned_to),
|
|
6917
|
+
agentRole: String(row.assigned_to),
|
|
6918
|
+
event: "task_complete",
|
|
6919
|
+
project: String(row.project_name),
|
|
6920
|
+
summary: `"${String(row.title)}" is ready for review`,
|
|
6921
|
+
taskFile
|
|
6922
|
+
});
|
|
6923
|
+
} catch {
|
|
6924
|
+
}
|
|
6925
|
+
}
|
|
6674
6926
|
if (input.status === "done" || input.status === "closed") {
|
|
6675
6927
|
try {
|
|
6676
6928
|
await cascadeUnblock(taskId, input.baseDir, now);
|
|
@@ -7103,18 +7355,31 @@ function acquireSpawnLock2(sessionName) {
|
|
|
7103
7355
|
mkdirSync8(SPAWN_LOCK_DIR, { recursive: true });
|
|
7104
7356
|
}
|
|
7105
7357
|
const lockFile = spawnLockPath(sessionName);
|
|
7106
|
-
|
|
7107
|
-
|
|
7108
|
-
|
|
7109
|
-
|
|
7110
|
-
|
|
7111
|
-
|
|
7112
|
-
|
|
7113
|
-
|
|
7358
|
+
const lockData = JSON.stringify({ pid: process.pid, timestamp: Date.now() });
|
|
7359
|
+
const { openSync: openSync3, closeSync: closeSync3, writeSync } = __require("fs");
|
|
7360
|
+
const { constants } = __require("fs");
|
|
7361
|
+
try {
|
|
7362
|
+
const fd = openSync3(lockFile, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 420);
|
|
7363
|
+
writeSync(fd, lockData);
|
|
7364
|
+
closeSync3(fd);
|
|
7365
|
+
return true;
|
|
7366
|
+
} catch (err) {
|
|
7367
|
+
if (err?.code !== "EEXIST") {
|
|
7368
|
+
return true;
|
|
7114
7369
|
}
|
|
7115
7370
|
}
|
|
7116
|
-
|
|
7117
|
-
|
|
7371
|
+
try {
|
|
7372
|
+
const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
|
|
7373
|
+
const age = Date.now() - lock.timestamp;
|
|
7374
|
+
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
7375
|
+
return false;
|
|
7376
|
+
}
|
|
7377
|
+
writeFileSync8(lockFile, lockData);
|
|
7378
|
+
return true;
|
|
7379
|
+
} catch {
|
|
7380
|
+
writeFileSync8(lockFile, lockData);
|
|
7381
|
+
return true;
|
|
7382
|
+
}
|
|
7118
7383
|
}
|
|
7119
7384
|
function releaseSpawnLock2(sessionName) {
|
|
7120
7385
|
try {
|
|
@@ -7193,6 +7458,21 @@ function parseParentExe(sessionName, agentId) {
|
|
|
7193
7458
|
function extractRootExe(name) {
|
|
7194
7459
|
if (!name) return null;
|
|
7195
7460
|
if (!name.includes("-")) return name;
|
|
7461
|
+
try {
|
|
7462
|
+
const roster = (init_employees(), __toCommonJS(employees_exports)).loadEmployeesSync();
|
|
7463
|
+
if (roster.length > 0) {
|
|
7464
|
+
const sortedNames = roster.map((e) => e.name).sort((a, b) => b.length - a.length);
|
|
7465
|
+
for (const agentName of sortedNames) {
|
|
7466
|
+
const escaped = agentName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7467
|
+
const regex = new RegExp(`^${escaped}\\d*-(.+)$`);
|
|
7468
|
+
const match = name.match(regex);
|
|
7469
|
+
if (match) {
|
|
7470
|
+
return extractRootExe(match[1]);
|
|
7471
|
+
}
|
|
7472
|
+
}
|
|
7473
|
+
}
|
|
7474
|
+
} catch {
|
|
7475
|
+
}
|
|
7196
7476
|
const parts = name.split("-").filter(Boolean);
|
|
7197
7477
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
7198
7478
|
}
|
|
@@ -7211,6 +7491,10 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
7211
7491
|
function getParentExe(sessionKey) {
|
|
7212
7492
|
try {
|
|
7213
7493
|
const data = JSON.parse(readFileSync12(path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
7494
|
+
if (data.registeredAt) {
|
|
7495
|
+
const age = Date.now() - new Date(data.registeredAt).getTime();
|
|
7496
|
+
if (age > PARENT_EXE_CACHE_TTL_MS) return null;
|
|
7497
|
+
}
|
|
7214
7498
|
return data.parentExe || null;
|
|
7215
7499
|
} catch {
|
|
7216
7500
|
return null;
|
|
@@ -7759,7 +8043,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
7759
8043
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
7760
8044
|
} catch {
|
|
7761
8045
|
}
|
|
7762
|
-
let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName}`;
|
|
8046
|
+
let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName} EXE_SESSION_START_ISO=${(/* @__PURE__ */ new Date()).toISOString()}`;
|
|
7763
8047
|
if (ccProvider !== DEFAULT_PROVIDER) {
|
|
7764
8048
|
const cfg = PROVIDER_TABLE[ccProvider];
|
|
7765
8049
|
if (cfg?.apiKeyEnv) {
|
|
@@ -7794,10 +8078,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
7794
8078
|
}
|
|
7795
8079
|
if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
|
|
7796
8080
|
if (agentRtConfig.runtime === "claude" && agentRtConfig.model) {
|
|
7797
|
-
|
|
7798
|
-
|
|
7799
|
-
ccModel += "[1m]";
|
|
7800
|
-
}
|
|
8081
|
+
const { normalizeCcModelName: normalizeCcModelName2 } = (init_agent_config(), __toCommonJS(agent_config_exports));
|
|
8082
|
+
const ccModel = normalizeCcModelName2(agentRtConfig.model);
|
|
7801
8083
|
envPrefix = `${envPrefix} ANTHROPIC_MODEL=${ccModel}`;
|
|
7802
8084
|
}
|
|
7803
8085
|
}
|
|
@@ -7898,7 +8180,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
7898
8180
|
releaseSpawnLock2(sessionName);
|
|
7899
8181
|
return { sessionName };
|
|
7900
8182
|
}
|
|
7901
|
-
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;
|
|
8183
|
+
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;
|
|
7902
8184
|
var init_tmux_routing = __esm({
|
|
7903
8185
|
"src/lib/tmux-routing.ts"() {
|
|
7904
8186
|
"use strict";
|
|
@@ -7918,6 +8200,7 @@ var init_tmux_routing = __esm({
|
|
|
7918
8200
|
SESSION_CACHE = path18.join(os11.homedir(), ".exe-os", "session-cache");
|
|
7919
8201
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
7920
8202
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
8203
|
+
PARENT_EXE_CACHE_TTL_MS = 4 * 60 * 60 * 1e3;
|
|
7921
8204
|
VERIFY_PANE_LINES = 200;
|
|
7922
8205
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
7923
8206
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
@@ -7962,7 +8245,7 @@ var init_task_scope = __esm({
|
|
|
7962
8245
|
});
|
|
7963
8246
|
|
|
7964
8247
|
// src/lib/keychain.ts
|
|
7965
|
-
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
8248
|
+
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2, rename, copyFile } from "fs/promises";
|
|
7966
8249
|
import { existsSync as existsSync17, statSync as statSync3 } from "fs";
|
|
7967
8250
|
import { execSync as execSync8 } from "child_process";
|
|
7968
8251
|
import path19 from "path";
|
|
@@ -7997,12 +8280,14 @@ function linuxSecretAvailable() {
|
|
|
7997
8280
|
function isRootOnlyTrustedServerKeyFile(keyPath) {
|
|
7998
8281
|
if (process.platform !== "linux") return false;
|
|
7999
8282
|
try {
|
|
8000
|
-
const uid = typeof os12.userInfo().uid === "number" ? os12.userInfo().uid : -1;
|
|
8001
8283
|
const st = statSync3(keyPath);
|
|
8002
8284
|
if (!st.isFile() || (st.mode & 63) !== 0) return false;
|
|
8285
|
+
const uid = typeof os12.userInfo().uid === "number" ? os12.userInfo().uid : -1;
|
|
8003
8286
|
if (uid === 0) return true;
|
|
8004
8287
|
const exeOsDir = process.env.EXE_OS_DIR;
|
|
8005
|
-
|
|
8288
|
+
if (exeOsDir && path19.resolve(keyPath).startsWith(path19.resolve(exeOsDir) + path19.sep)) return true;
|
|
8289
|
+
if (!linuxSecretAvailable()) return true;
|
|
8290
|
+
return false;
|
|
8006
8291
|
} catch {
|
|
8007
8292
|
return false;
|
|
8008
8293
|
}
|
|
@@ -8152,15 +8437,25 @@ async function writeMachineBoundFileFallback(b64) {
|
|
|
8152
8437
|
await mkdir4(dir, { recursive: true });
|
|
8153
8438
|
const keyPath = getKeyPath();
|
|
8154
8439
|
const machineKey = deriveMachineKey();
|
|
8155
|
-
|
|
8156
|
-
|
|
8157
|
-
|
|
8158
|
-
|
|
8159
|
-
|
|
8440
|
+
const content = machineKey ? encryptWithMachineKey(b64, machineKey) + "\n" : b64 + "\n";
|
|
8441
|
+
const result = machineKey ? "encrypted" : "plaintext";
|
|
8442
|
+
const tmpPath = keyPath + ".tmp";
|
|
8443
|
+
try {
|
|
8444
|
+
if (existsSync17(keyPath)) {
|
|
8445
|
+
await copyFile(keyPath, keyPath + ".bak").catch(() => {
|
|
8446
|
+
});
|
|
8447
|
+
}
|
|
8448
|
+
await writeFile5(tmpPath, content, "utf-8");
|
|
8449
|
+
await chmod2(tmpPath, 384);
|
|
8450
|
+
await rename(tmpPath, keyPath);
|
|
8451
|
+
} catch (err) {
|
|
8452
|
+
try {
|
|
8453
|
+
await unlink(tmpPath);
|
|
8454
|
+
} catch {
|
|
8455
|
+
}
|
|
8456
|
+
throw err;
|
|
8160
8457
|
}
|
|
8161
|
-
|
|
8162
|
-
await chmod2(keyPath, 384);
|
|
8163
|
-
return "plaintext";
|
|
8458
|
+
return result;
|
|
8164
8459
|
}
|
|
8165
8460
|
async function getMasterKey() {
|
|
8166
8461
|
let nativeValue = macKeychainGet() ?? linuxSecretGet();
|
|
@@ -8227,7 +8522,7 @@ async function getMasterKey() {
|
|
|
8227
8522
|
b64Value = content;
|
|
8228
8523
|
}
|
|
8229
8524
|
const key = Buffer.from(b64Value, "base64");
|
|
8230
|
-
if (
|
|
8525
|
+
if (isRootOnlyTrustedServerKeyFile(keyPath)) {
|
|
8231
8526
|
return key;
|
|
8232
8527
|
}
|
|
8233
8528
|
const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
|