@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/exe-dispatch.js
CHANGED
|
@@ -447,6 +447,17 @@ function normalizeOrchestration(raw) {
|
|
|
447
447
|
const userOrg = raw.orchestration ?? {};
|
|
448
448
|
raw.orchestration = { ...defaultOrg, ...userOrg };
|
|
449
449
|
}
|
|
450
|
+
function normalizeCloudEndpoint(raw) {
|
|
451
|
+
const cloud = raw.cloud;
|
|
452
|
+
if (!cloud?.endpoint) return;
|
|
453
|
+
const ep = String(cloud.endpoint);
|
|
454
|
+
if (ep === "https://askexe.com/cloud" || ep === "https://askexe.com/cloud/") {
|
|
455
|
+
cloud.endpoint = "https://cloud.askexe.com";
|
|
456
|
+
process.stderr.write(
|
|
457
|
+
"[config] Auto-migrated cloud endpoint: askexe.com/cloud \u2192 cloud.askexe.com\n"
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
450
461
|
async function loadConfig() {
|
|
451
462
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
452
463
|
await ensurePrivateDir(dir);
|
|
@@ -472,6 +483,7 @@ async function loadConfig() {
|
|
|
472
483
|
normalizeSessionLifecycle(migratedCfg);
|
|
473
484
|
normalizeAutoUpdate(migratedCfg);
|
|
474
485
|
normalizeOrchestration(migratedCfg);
|
|
486
|
+
normalizeCloudEndpoint(migratedCfg);
|
|
475
487
|
const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
|
|
476
488
|
if (config.dbPath.startsWith("~")) {
|
|
477
489
|
config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
|
|
@@ -500,6 +512,7 @@ function loadConfigSync() {
|
|
|
500
512
|
normalizeSessionLifecycle(migratedCfg);
|
|
501
513
|
normalizeAutoUpdate(migratedCfg);
|
|
502
514
|
normalizeOrchestration(migratedCfg);
|
|
515
|
+
normalizeCloudEndpoint(migratedCfg);
|
|
503
516
|
const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
|
|
504
517
|
if (config.dbPath.startsWith("~")) {
|
|
505
518
|
config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
|
|
@@ -660,6 +673,7 @@ __export(agent_config_exports, {
|
|
|
660
673
|
clearAgentRuntime: () => clearAgentRuntime,
|
|
661
674
|
getAgentRuntime: () => getAgentRuntime,
|
|
662
675
|
loadAgentConfig: () => loadAgentConfig,
|
|
676
|
+
normalizeCcModelName: () => normalizeCcModelName,
|
|
663
677
|
saveAgentConfig: () => saveAgentConfig,
|
|
664
678
|
setAgentMcps: () => setAgentMcps,
|
|
665
679
|
setAgentRuntime: () => setAgentRuntime
|
|
@@ -688,6 +702,13 @@ function getAgentRuntime(agentId) {
|
|
|
688
702
|
if (orgDefault) return orgDefault;
|
|
689
703
|
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
690
704
|
}
|
|
705
|
+
function normalizeCcModelName(model) {
|
|
706
|
+
let ccModel = model.replace(/(\d+)\.(\d+)/g, "$1-$2");
|
|
707
|
+
if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
|
|
708
|
+
ccModel += "[1m]";
|
|
709
|
+
}
|
|
710
|
+
return ccModel;
|
|
711
|
+
}
|
|
691
712
|
function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
|
|
692
713
|
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
693
714
|
if (!knownModels) {
|
|
@@ -757,6 +778,7 @@ var init_agent_config = __esm({
|
|
|
757
778
|
// src/lib/intercom-queue.ts
|
|
758
779
|
var intercom_queue_exports = {};
|
|
759
780
|
__export(intercom_queue_exports, {
|
|
781
|
+
_resetDrainGuard: () => _resetDrainGuard,
|
|
760
782
|
clearQueueForAgent: () => clearQueueForAgent,
|
|
761
783
|
drainForSession: () => drainForSession,
|
|
762
784
|
drainQueue: () => drainQueue,
|
|
@@ -802,38 +824,47 @@ function queueIntercom(targetSession, reason) {
|
|
|
802
824
|
writeQueue(queue);
|
|
803
825
|
}
|
|
804
826
|
function drainQueue(isSessionBusy2, sendKeys) {
|
|
827
|
+
if (_draining) {
|
|
828
|
+
logQueue("SKIP_DRAIN \u2014 previous drain still running (possible tmux hang)");
|
|
829
|
+
return { drained: 0, failed: 0 };
|
|
830
|
+
}
|
|
805
831
|
const queue = readQueue();
|
|
806
832
|
if (queue.length === 0) return { drained: 0, failed: 0 };
|
|
833
|
+
_draining = true;
|
|
807
834
|
const remaining = [];
|
|
808
835
|
let drained = 0;
|
|
809
836
|
let failed = 0;
|
|
810
|
-
|
|
811
|
-
const
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
837
|
+
try {
|
|
838
|
+
for (const item of queue) {
|
|
839
|
+
const age = Date.now() - new Date(item.queuedAt).getTime();
|
|
840
|
+
if (age > TTL_MS) {
|
|
841
|
+
logQueue(`EXPIRED \u2192 ${item.targetSession} (${Math.round(age / 6e4)}min old, reason: ${item.reason})`);
|
|
842
|
+
failed++;
|
|
843
|
+
continue;
|
|
844
|
+
}
|
|
845
|
+
try {
|
|
846
|
+
if (!isSessionBusy2(item.targetSession)) {
|
|
847
|
+
const success = sendKeys(item.targetSession);
|
|
848
|
+
if (success) {
|
|
849
|
+
logQueue(`DRAINED \u2192 ${item.targetSession} (after ${item.attempts} retries)`);
|
|
850
|
+
drained++;
|
|
851
|
+
continue;
|
|
852
|
+
}
|
|
824
853
|
}
|
|
854
|
+
} catch {
|
|
825
855
|
}
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
856
|
+
item.attempts++;
|
|
857
|
+
if (item.attempts >= MAX_RETRIES) {
|
|
858
|
+
logQueue(`FAILED \u2192 ${item.targetSession} (${MAX_RETRIES} retries exhausted, reason: ${item.reason})`);
|
|
859
|
+
failed++;
|
|
860
|
+
continue;
|
|
861
|
+
}
|
|
862
|
+
remaining.push(item);
|
|
833
863
|
}
|
|
834
|
-
remaining
|
|
864
|
+
writeQueue(remaining);
|
|
865
|
+
} finally {
|
|
866
|
+
_draining = false;
|
|
835
867
|
}
|
|
836
|
-
writeQueue(remaining);
|
|
837
868
|
return { drained, failed };
|
|
838
869
|
}
|
|
839
870
|
function drainForSession(targetSession, sendKeys) {
|
|
@@ -858,6 +889,9 @@ function clearQueueForAgent(agentName) {
|
|
|
858
889
|
logQueue(`CLEARED ${before - filtered.length} stale item(s) for ${agentName}`);
|
|
859
890
|
}
|
|
860
891
|
}
|
|
892
|
+
function _resetDrainGuard() {
|
|
893
|
+
_draining = false;
|
|
894
|
+
}
|
|
861
895
|
function logQueue(msg) {
|
|
862
896
|
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] [queue] ${msg}
|
|
863
897
|
`;
|
|
@@ -869,13 +903,14 @@ function logQueue(msg) {
|
|
|
869
903
|
} catch {
|
|
870
904
|
}
|
|
871
905
|
}
|
|
872
|
-
var QUEUE_PATH, MAX_RETRIES, TTL_MS, INTERCOM_LOG;
|
|
906
|
+
var QUEUE_PATH, MAX_RETRIES, TTL_MS, _draining, INTERCOM_LOG;
|
|
873
907
|
var init_intercom_queue = __esm({
|
|
874
908
|
"src/lib/intercom-queue.ts"() {
|
|
875
909
|
"use strict";
|
|
876
910
|
QUEUE_PATH = path4.join(os3.homedir(), ".exe-os", "intercom-queue.json");
|
|
877
911
|
MAX_RETRIES = 5;
|
|
878
912
|
TTL_MS = 60 * 60 * 1e3;
|
|
913
|
+
_draining = false;
|
|
879
914
|
INTERCOM_LOG = path4.join(os3.homedir(), ".exe-os", "intercom.log");
|
|
880
915
|
}
|
|
881
916
|
});
|
|
@@ -4802,6 +4837,17 @@ var init_task_scope = __esm({
|
|
|
4802
4837
|
});
|
|
4803
4838
|
|
|
4804
4839
|
// src/lib/notifications.ts
|
|
4840
|
+
var notifications_exports = {};
|
|
4841
|
+
__export(notifications_exports, {
|
|
4842
|
+
cleanupOldNotifications: () => cleanupOldNotifications,
|
|
4843
|
+
formatNotifications: () => formatNotifications,
|
|
4844
|
+
markAsRead: () => markAsRead,
|
|
4845
|
+
markAsReadByTaskFile: () => markAsReadByTaskFile,
|
|
4846
|
+
markDoneTaskNotificationsAsRead: () => markDoneTaskNotificationsAsRead,
|
|
4847
|
+
migrateJsonNotifications: () => migrateJsonNotifications,
|
|
4848
|
+
readUnreadNotifications: () => readUnreadNotifications,
|
|
4849
|
+
writeNotification: () => writeNotification
|
|
4850
|
+
});
|
|
4805
4851
|
import crypto2 from "crypto";
|
|
4806
4852
|
import path12 from "path";
|
|
4807
4853
|
import os9 from "os";
|
|
@@ -4838,6 +4884,52 @@ async function writeNotification(notification) {
|
|
|
4838
4884
|
`);
|
|
4839
4885
|
}
|
|
4840
4886
|
}
|
|
4887
|
+
async function readUnreadNotifications(agentFilter, sessionScope) {
|
|
4888
|
+
try {
|
|
4889
|
+
const client = getClient();
|
|
4890
|
+
const conditions = ["read = 0"];
|
|
4891
|
+
const args = [];
|
|
4892
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
4893
|
+
if (agentFilter) {
|
|
4894
|
+
conditions.push("agent_id = ?");
|
|
4895
|
+
args.push(agentFilter);
|
|
4896
|
+
}
|
|
4897
|
+
const result2 = await client.execute({
|
|
4898
|
+
sql: `SELECT id, agent_id, agent_role, event, project, summary, task_file, session_scope, created_at
|
|
4899
|
+
FROM notifications
|
|
4900
|
+
WHERE ${conditions.join(" AND ")}${scope.sql}
|
|
4901
|
+
ORDER BY created_at ASC`,
|
|
4902
|
+
args: [...args, ...scope.args]
|
|
4903
|
+
});
|
|
4904
|
+
return result2.rows.map((r) => ({
|
|
4905
|
+
id: String(r.id),
|
|
4906
|
+
agentId: String(r.agent_id),
|
|
4907
|
+
agentRole: String(r.agent_role),
|
|
4908
|
+
event: String(r.event),
|
|
4909
|
+
project: String(r.project),
|
|
4910
|
+
summary: String(r.summary),
|
|
4911
|
+
taskFile: r.task_file ? String(r.task_file) : void 0,
|
|
4912
|
+
sessionScope: r.session_scope == null ? null : String(r.session_scope),
|
|
4913
|
+
timestamp: String(r.created_at),
|
|
4914
|
+
read: false
|
|
4915
|
+
}));
|
|
4916
|
+
} catch {
|
|
4917
|
+
return [];
|
|
4918
|
+
}
|
|
4919
|
+
}
|
|
4920
|
+
async function markAsRead(ids, sessionScope) {
|
|
4921
|
+
if (ids.length === 0) return;
|
|
4922
|
+
try {
|
|
4923
|
+
const client = getClient();
|
|
4924
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
4925
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
4926
|
+
await client.execute({
|
|
4927
|
+
sql: `UPDATE notifications SET read = 1 WHERE id IN (${placeholders})${scope.sql}`,
|
|
4928
|
+
args: [...ids, ...scope.args]
|
|
4929
|
+
});
|
|
4930
|
+
} catch {
|
|
4931
|
+
}
|
|
4932
|
+
}
|
|
4841
4933
|
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
4842
4934
|
try {
|
|
4843
4935
|
const client = getClient();
|
|
@@ -4850,11 +4942,144 @@ async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
|
4850
4942
|
} catch {
|
|
4851
4943
|
}
|
|
4852
4944
|
}
|
|
4945
|
+
async function cleanupOldNotifications(daysOld = CLEANUP_DAYS, sessionScope) {
|
|
4946
|
+
try {
|
|
4947
|
+
const client = getClient();
|
|
4948
|
+
const cutoff = new Date(
|
|
4949
|
+
Date.now() - daysOld * 24 * 60 * 60 * 1e3
|
|
4950
|
+
).toISOString();
|
|
4951
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
4952
|
+
const result2 = await client.execute({
|
|
4953
|
+
sql: `DELETE FROM notifications WHERE created_at < ?${scope.sql}`,
|
|
4954
|
+
args: [cutoff, ...scope.args]
|
|
4955
|
+
});
|
|
4956
|
+
return result2.rowsAffected;
|
|
4957
|
+
} catch {
|
|
4958
|
+
return 0;
|
|
4959
|
+
}
|
|
4960
|
+
}
|
|
4961
|
+
async function markDoneTaskNotificationsAsRead(sessionScope) {
|
|
4962
|
+
try {
|
|
4963
|
+
const client = getClient();
|
|
4964
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
4965
|
+
const result2 = await client.execute({
|
|
4966
|
+
sql: `UPDATE notifications SET read = 1
|
|
4967
|
+
WHERE read = 0
|
|
4968
|
+
AND task_file IS NOT NULL
|
|
4969
|
+
${scope.sql}
|
|
4970
|
+
AND task_file IN (
|
|
4971
|
+
SELECT task_file FROM tasks WHERE status = 'done'${scope.sql}
|
|
4972
|
+
)`,
|
|
4973
|
+
args: [...scope.args, ...scope.args]
|
|
4974
|
+
});
|
|
4975
|
+
return result2.rowsAffected;
|
|
4976
|
+
} catch {
|
|
4977
|
+
return 0;
|
|
4978
|
+
}
|
|
4979
|
+
}
|
|
4980
|
+
function formatNotifications(notifications) {
|
|
4981
|
+
if (notifications.length === 0) return "";
|
|
4982
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
4983
|
+
for (const n of notifications) {
|
|
4984
|
+
const key = `${n.agentId}|${n.agentRole}`;
|
|
4985
|
+
if (!grouped.has(key)) grouped.set(key, []);
|
|
4986
|
+
grouped.get(key).push(n);
|
|
4987
|
+
}
|
|
4988
|
+
const lines = [];
|
|
4989
|
+
lines.push(`## Notifications (${notifications.length} unread)
|
|
4990
|
+
`);
|
|
4991
|
+
for (const [key, items] of grouped) {
|
|
4992
|
+
const [agentId, agentRole] = key.split("|");
|
|
4993
|
+
lines.push(`**${agentId}** (${agentRole}):`);
|
|
4994
|
+
for (const item of items) {
|
|
4995
|
+
const ago = formatTimeAgo(item.timestamp);
|
|
4996
|
+
const icon = eventIcon(item.event);
|
|
4997
|
+
lines.push(`- ${icon} ${item.summary} (${item.project}) \u2014 ${ago}`);
|
|
4998
|
+
}
|
|
4999
|
+
lines.push("");
|
|
5000
|
+
}
|
|
5001
|
+
return lines.join("\n");
|
|
5002
|
+
}
|
|
5003
|
+
async function migrateJsonNotifications() {
|
|
5004
|
+
const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR || path12.join(os9.homedir(), ".exe-os");
|
|
5005
|
+
const notifDir = path12.join(base, "notifications");
|
|
5006
|
+
if (!existsSync13(notifDir)) return 0;
|
|
5007
|
+
let migrated = 0;
|
|
5008
|
+
try {
|
|
5009
|
+
const files = readdirSync(notifDir).filter((f) => f.endsWith(".json"));
|
|
5010
|
+
if (files.length === 0) return 0;
|
|
5011
|
+
const client = getClient();
|
|
5012
|
+
for (const file of files) {
|
|
5013
|
+
try {
|
|
5014
|
+
const filePath = path12.join(notifDir, file);
|
|
5015
|
+
const data = JSON.parse(readFileSync10(filePath, "utf8"));
|
|
5016
|
+
await client.execute({
|
|
5017
|
+
sql: `INSERT OR IGNORE INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
5018
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
5019
|
+
args: [
|
|
5020
|
+
crypto2.randomUUID(),
|
|
5021
|
+
data.agentId ?? "unknown",
|
|
5022
|
+
data.agentRole ?? "unknown",
|
|
5023
|
+
data.event ?? "session_summary",
|
|
5024
|
+
data.project ?? "unknown",
|
|
5025
|
+
data.summary ?? "",
|
|
5026
|
+
data.taskFile ?? null,
|
|
5027
|
+
null,
|
|
5028
|
+
data.read ? 1 : 0,
|
|
5029
|
+
data.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
5030
|
+
]
|
|
5031
|
+
});
|
|
5032
|
+
unlinkSync4(filePath);
|
|
5033
|
+
migrated++;
|
|
5034
|
+
} catch {
|
|
5035
|
+
}
|
|
5036
|
+
}
|
|
5037
|
+
try {
|
|
5038
|
+
const remaining = readdirSync(notifDir);
|
|
5039
|
+
if (remaining.length === 0) {
|
|
5040
|
+
rmdirSync(notifDir);
|
|
5041
|
+
}
|
|
5042
|
+
} catch {
|
|
5043
|
+
}
|
|
5044
|
+
} catch {
|
|
5045
|
+
}
|
|
5046
|
+
return migrated;
|
|
5047
|
+
}
|
|
5048
|
+
function eventIcon(event) {
|
|
5049
|
+
switch (event) {
|
|
5050
|
+
case "task_complete":
|
|
5051
|
+
return "Completed:";
|
|
5052
|
+
case "task_needs_fix":
|
|
5053
|
+
return "Needs fix:";
|
|
5054
|
+
case "session_summary":
|
|
5055
|
+
return "Session:";
|
|
5056
|
+
case "error_spike":
|
|
5057
|
+
return "Errors:";
|
|
5058
|
+
case "orphan_task":
|
|
5059
|
+
return "Orphan:";
|
|
5060
|
+
case "subtasks_complete":
|
|
5061
|
+
return "Subtasks done:";
|
|
5062
|
+
case "capacity_relaunch":
|
|
5063
|
+
return "Relaunched:";
|
|
5064
|
+
}
|
|
5065
|
+
}
|
|
5066
|
+
function formatTimeAgo(timestamp) {
|
|
5067
|
+
const diffMs = Date.now() - new Date(timestamp).getTime();
|
|
5068
|
+
const mins = Math.floor(diffMs / 6e4);
|
|
5069
|
+
if (mins < 1) return "just now";
|
|
5070
|
+
if (mins < 60) return `${mins}m ago`;
|
|
5071
|
+
const hours = Math.floor(mins / 60);
|
|
5072
|
+
if (hours < 24) return `${hours}h ago`;
|
|
5073
|
+
const days = Math.floor(hours / 24);
|
|
5074
|
+
return `${days}d ago`;
|
|
5075
|
+
}
|
|
5076
|
+
var CLEANUP_DAYS;
|
|
4853
5077
|
var init_notifications = __esm({
|
|
4854
5078
|
"src/lib/notifications.ts"() {
|
|
4855
5079
|
"use strict";
|
|
4856
5080
|
init_database();
|
|
4857
5081
|
init_task_scope();
|
|
5082
|
+
CLEANUP_DAYS = 7;
|
|
4858
5083
|
}
|
|
4859
5084
|
});
|
|
4860
5085
|
|
|
@@ -5900,7 +6125,13 @@ async function createReviewForCompletedTask(row, result2, _baseDir, now) {
|
|
|
5900
6125
|
taskFile
|
|
5901
6126
|
});
|
|
5902
6127
|
const originalPriority = String(row.priority).toLowerCase();
|
|
5903
|
-
const
|
|
6128
|
+
const resultLower = result2?.toLowerCase() ?? "";
|
|
6129
|
+
const hasTestEvidence = (
|
|
6130
|
+
// Vitest/Jest output patterns (hard to fake without actually running tests)
|
|
6131
|
+
/\d+\s+pass(ed|ing)/.test(resultLower) || /test files?\s+\d+\s+passed/.test(resultLower) || /tests?\s+\d+\s+passed/.test(resultLower)
|
|
6132
|
+
);
|
|
6133
|
+
const hasNoFailures = !/fail(ed|ure|ing)|error/i.test(resultLower);
|
|
6134
|
+
const autoApprove = originalPriority === "p2" && hasTestEvidence && hasNoFailures;
|
|
5904
6135
|
if (!autoApprove) {
|
|
5905
6136
|
try {
|
|
5906
6137
|
const key = getSessionKey();
|
|
@@ -5908,6 +6139,13 @@ async function createReviewForCompletedTask(row, result2, _baseDir, now) {
|
|
|
5908
6139
|
if (exeSession2) {
|
|
5909
6140
|
sendIntercom(exeSession2);
|
|
5910
6141
|
}
|
|
6142
|
+
if (reviewer && reviewer !== coordinatorName && reviewer !== exeSession2) {
|
|
6143
|
+
const { employeeSessionName: employeeSessionName2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
6144
|
+
if (exeSession2) {
|
|
6145
|
+
const reviewerSession = employeeSessionName2(reviewer, exeSession2);
|
|
6146
|
+
sendIntercom(reviewerSession);
|
|
6147
|
+
}
|
|
6148
|
+
}
|
|
5911
6149
|
} catch {
|
|
5912
6150
|
}
|
|
5913
6151
|
}
|
|
@@ -6683,6 +6921,20 @@ async function updateTask(input) {
|
|
|
6683
6921
|
notifyTaskDone();
|
|
6684
6922
|
}
|
|
6685
6923
|
await markTaskNotificationsRead(taskFile);
|
|
6924
|
+
if (input.status === "needs_review" && !isCoordinator) {
|
|
6925
|
+
try {
|
|
6926
|
+
const { writeNotification: writeNotification2 } = await Promise.resolve().then(() => (init_notifications(), notifications_exports));
|
|
6927
|
+
await writeNotification2({
|
|
6928
|
+
agentId: String(row.assigned_to),
|
|
6929
|
+
agentRole: String(row.assigned_to),
|
|
6930
|
+
event: "task_complete",
|
|
6931
|
+
project: String(row.project_name),
|
|
6932
|
+
summary: `"${String(row.title)}" is ready for review`,
|
|
6933
|
+
taskFile
|
|
6934
|
+
});
|
|
6935
|
+
} catch {
|
|
6936
|
+
}
|
|
6937
|
+
}
|
|
6686
6938
|
if (input.status === "done" || input.status === "closed") {
|
|
6687
6939
|
try {
|
|
6688
6940
|
await cascadeUnblock(taskId, input.baseDir, now);
|
|
@@ -7115,18 +7367,31 @@ function acquireSpawnLock2(sessionName) {
|
|
|
7115
7367
|
mkdirSync8(SPAWN_LOCK_DIR, { recursive: true });
|
|
7116
7368
|
}
|
|
7117
7369
|
const lockFile = spawnLockPath(sessionName);
|
|
7118
|
-
|
|
7119
|
-
|
|
7120
|
-
|
|
7121
|
-
|
|
7122
|
-
|
|
7123
|
-
|
|
7124
|
-
|
|
7125
|
-
|
|
7370
|
+
const lockData = JSON.stringify({ pid: process.pid, timestamp: Date.now() });
|
|
7371
|
+
const { openSync: openSync3, closeSync: closeSync3, writeSync } = __require("fs");
|
|
7372
|
+
const { constants } = __require("fs");
|
|
7373
|
+
try {
|
|
7374
|
+
const fd = openSync3(lockFile, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 420);
|
|
7375
|
+
writeSync(fd, lockData);
|
|
7376
|
+
closeSync3(fd);
|
|
7377
|
+
return true;
|
|
7378
|
+
} catch (err) {
|
|
7379
|
+
if (err?.code !== "EEXIST") {
|
|
7380
|
+
return true;
|
|
7126
7381
|
}
|
|
7127
7382
|
}
|
|
7128
|
-
|
|
7129
|
-
|
|
7383
|
+
try {
|
|
7384
|
+
const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
|
|
7385
|
+
const age = Date.now() - lock.timestamp;
|
|
7386
|
+
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
7387
|
+
return false;
|
|
7388
|
+
}
|
|
7389
|
+
writeFileSync8(lockFile, lockData);
|
|
7390
|
+
return true;
|
|
7391
|
+
} catch {
|
|
7392
|
+
writeFileSync8(lockFile, lockData);
|
|
7393
|
+
return true;
|
|
7394
|
+
}
|
|
7130
7395
|
}
|
|
7131
7396
|
function releaseSpawnLock2(sessionName) {
|
|
7132
7397
|
try {
|
|
@@ -7205,6 +7470,21 @@ function parseParentExe(sessionName, agentId) {
|
|
|
7205
7470
|
function extractRootExe(name) {
|
|
7206
7471
|
if (!name) return null;
|
|
7207
7472
|
if (!name.includes("-")) return name;
|
|
7473
|
+
try {
|
|
7474
|
+
const roster = (init_employees(), __toCommonJS(employees_exports)).loadEmployeesSync();
|
|
7475
|
+
if (roster.length > 0) {
|
|
7476
|
+
const sortedNames = roster.map((e) => e.name).sort((a, b) => b.length - a.length);
|
|
7477
|
+
for (const agentName of sortedNames) {
|
|
7478
|
+
const escaped = agentName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7479
|
+
const regex = new RegExp(`^${escaped}\\d*-(.+)$`);
|
|
7480
|
+
const match = name.match(regex);
|
|
7481
|
+
if (match) {
|
|
7482
|
+
return extractRootExe(match[1]);
|
|
7483
|
+
}
|
|
7484
|
+
}
|
|
7485
|
+
}
|
|
7486
|
+
} catch {
|
|
7487
|
+
}
|
|
7208
7488
|
const parts = name.split("-").filter(Boolean);
|
|
7209
7489
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
7210
7490
|
}
|
|
@@ -7223,6 +7503,10 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
7223
7503
|
function getParentExe(sessionKey) {
|
|
7224
7504
|
try {
|
|
7225
7505
|
const data = JSON.parse(readFileSync12(path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
7506
|
+
if (data.registeredAt) {
|
|
7507
|
+
const age = Date.now() - new Date(data.registeredAt).getTime();
|
|
7508
|
+
if (age > PARENT_EXE_CACHE_TTL_MS) return null;
|
|
7509
|
+
}
|
|
7226
7510
|
return data.parentExe || null;
|
|
7227
7511
|
} catch {
|
|
7228
7512
|
return null;
|
|
@@ -7771,7 +8055,7 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
|
|
|
7771
8055
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
7772
8056
|
} catch {
|
|
7773
8057
|
}
|
|
7774
|
-
let envPrefix = `EXE_SESSION=${exeSession2} EXE_SESSION_NAME=${sessionName}`;
|
|
8058
|
+
let envPrefix = `EXE_SESSION=${exeSession2} EXE_SESSION_NAME=${sessionName} EXE_SESSION_START_ISO=${(/* @__PURE__ */ new Date()).toISOString()}`;
|
|
7775
8059
|
if (ccProvider !== DEFAULT_PROVIDER) {
|
|
7776
8060
|
const cfg = PROVIDER_TABLE[ccProvider];
|
|
7777
8061
|
if (cfg?.apiKeyEnv) {
|
|
@@ -7806,10 +8090,8 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
|
|
|
7806
8090
|
}
|
|
7807
8091
|
if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
|
|
7808
8092
|
if (agentRtConfig.runtime === "claude" && agentRtConfig.model) {
|
|
7809
|
-
|
|
7810
|
-
|
|
7811
|
-
ccModel += "[1m]";
|
|
7812
|
-
}
|
|
8093
|
+
const { normalizeCcModelName: normalizeCcModelName2 } = (init_agent_config(), __toCommonJS(agent_config_exports));
|
|
8094
|
+
const ccModel = normalizeCcModelName2(agentRtConfig.model);
|
|
7813
8095
|
envPrefix = `${envPrefix} ANTHROPIC_MODEL=${ccModel}`;
|
|
7814
8096
|
}
|
|
7815
8097
|
}
|
|
@@ -7910,7 +8192,7 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
|
|
|
7910
8192
|
releaseSpawnLock2(sessionName);
|
|
7911
8193
|
return { sessionName };
|
|
7912
8194
|
}
|
|
7913
|
-
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;
|
|
8195
|
+
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;
|
|
7914
8196
|
var init_tmux_routing = __esm({
|
|
7915
8197
|
"src/lib/tmux-routing.ts"() {
|
|
7916
8198
|
"use strict";
|
|
@@ -7930,6 +8212,7 @@ var init_tmux_routing = __esm({
|
|
|
7930
8212
|
SESSION_CACHE = path18.join(os11.homedir(), ".exe-os", "session-cache");
|
|
7931
8213
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
7932
8214
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
8215
|
+
PARENT_EXE_CACHE_TTL_MS = 4 * 60 * 60 * 1e3;
|
|
7933
8216
|
VERIFY_PANE_LINES = 200;
|
|
7934
8217
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
7935
8218
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
@@ -7941,7 +8224,7 @@ var init_tmux_routing = __esm({
|
|
|
7941
8224
|
});
|
|
7942
8225
|
|
|
7943
8226
|
// src/lib/keychain.ts
|
|
7944
|
-
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
8227
|
+
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2, rename, copyFile } from "fs/promises";
|
|
7945
8228
|
import { existsSync as existsSync17, statSync as statSync3 } from "fs";
|
|
7946
8229
|
import { execSync as execSync8 } from "child_process";
|
|
7947
8230
|
import path19 from "path";
|
|
@@ -7976,12 +8259,14 @@ function linuxSecretAvailable() {
|
|
|
7976
8259
|
function isRootOnlyTrustedServerKeyFile(keyPath) {
|
|
7977
8260
|
if (process.platform !== "linux") return false;
|
|
7978
8261
|
try {
|
|
7979
|
-
const uid = typeof os12.userInfo().uid === "number" ? os12.userInfo().uid : -1;
|
|
7980
8262
|
const st = statSync3(keyPath);
|
|
7981
8263
|
if (!st.isFile() || (st.mode & 63) !== 0) return false;
|
|
8264
|
+
const uid = typeof os12.userInfo().uid === "number" ? os12.userInfo().uid : -1;
|
|
7982
8265
|
if (uid === 0) return true;
|
|
7983
8266
|
const exeOsDir = process.env.EXE_OS_DIR;
|
|
7984
|
-
|
|
8267
|
+
if (exeOsDir && path19.resolve(keyPath).startsWith(path19.resolve(exeOsDir) + path19.sep)) return true;
|
|
8268
|
+
if (!linuxSecretAvailable()) return true;
|
|
8269
|
+
return false;
|
|
7985
8270
|
} catch {
|
|
7986
8271
|
return false;
|
|
7987
8272
|
}
|
|
@@ -8131,15 +8416,25 @@ async function writeMachineBoundFileFallback(b64) {
|
|
|
8131
8416
|
await mkdir4(dir, { recursive: true });
|
|
8132
8417
|
const keyPath = getKeyPath();
|
|
8133
8418
|
const machineKey = deriveMachineKey();
|
|
8134
|
-
|
|
8135
|
-
|
|
8136
|
-
|
|
8137
|
-
|
|
8138
|
-
|
|
8419
|
+
const content = machineKey ? encryptWithMachineKey(b64, machineKey) + "\n" : b64 + "\n";
|
|
8420
|
+
const result2 = machineKey ? "encrypted" : "plaintext";
|
|
8421
|
+
const tmpPath = keyPath + ".tmp";
|
|
8422
|
+
try {
|
|
8423
|
+
if (existsSync17(keyPath)) {
|
|
8424
|
+
await copyFile(keyPath, keyPath + ".bak").catch(() => {
|
|
8425
|
+
});
|
|
8426
|
+
}
|
|
8427
|
+
await writeFile5(tmpPath, content, "utf-8");
|
|
8428
|
+
await chmod2(tmpPath, 384);
|
|
8429
|
+
await rename(tmpPath, keyPath);
|
|
8430
|
+
} catch (err) {
|
|
8431
|
+
try {
|
|
8432
|
+
await unlink(tmpPath);
|
|
8433
|
+
} catch {
|
|
8434
|
+
}
|
|
8435
|
+
throw err;
|
|
8139
8436
|
}
|
|
8140
|
-
|
|
8141
|
-
await chmod2(keyPath, 384);
|
|
8142
|
-
return "plaintext";
|
|
8437
|
+
return result2;
|
|
8143
8438
|
}
|
|
8144
8439
|
async function getMasterKey() {
|
|
8145
8440
|
let nativeValue = macKeychainGet() ?? linuxSecretGet();
|
|
@@ -8206,7 +8501,7 @@ async function getMasterKey() {
|
|
|
8206
8501
|
b64Value = content;
|
|
8207
8502
|
}
|
|
8208
8503
|
const key = Buffer.from(b64Value, "base64");
|
|
8209
|
-
if (
|
|
8504
|
+
if (isRootOnlyTrustedServerKeyFile(keyPath)) {
|
|
8210
8505
|
return key;
|
|
8211
8506
|
}
|
|
8212
8507
|
const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
|