@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/git-sweep.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
|
});
|
|
@@ -4703,6 +4738,17 @@ var init_agent_symlinks = __esm({
|
|
|
4703
4738
|
});
|
|
4704
4739
|
|
|
4705
4740
|
// src/lib/notifications.ts
|
|
4741
|
+
var notifications_exports = {};
|
|
4742
|
+
__export(notifications_exports, {
|
|
4743
|
+
cleanupOldNotifications: () => cleanupOldNotifications,
|
|
4744
|
+
formatNotifications: () => formatNotifications,
|
|
4745
|
+
markAsRead: () => markAsRead,
|
|
4746
|
+
markAsReadByTaskFile: () => markAsReadByTaskFile,
|
|
4747
|
+
markDoneTaskNotificationsAsRead: () => markDoneTaskNotificationsAsRead,
|
|
4748
|
+
migrateJsonNotifications: () => migrateJsonNotifications,
|
|
4749
|
+
readUnreadNotifications: () => readUnreadNotifications,
|
|
4750
|
+
writeNotification: () => writeNotification
|
|
4751
|
+
});
|
|
4706
4752
|
import crypto2 from "crypto";
|
|
4707
4753
|
import path12 from "path";
|
|
4708
4754
|
import os9 from "os";
|
|
@@ -4739,6 +4785,52 @@ async function writeNotification(notification) {
|
|
|
4739
4785
|
`);
|
|
4740
4786
|
}
|
|
4741
4787
|
}
|
|
4788
|
+
async function readUnreadNotifications(agentFilter, sessionScope) {
|
|
4789
|
+
try {
|
|
4790
|
+
const client = getClient();
|
|
4791
|
+
const conditions = ["read = 0"];
|
|
4792
|
+
const args2 = [];
|
|
4793
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
4794
|
+
if (agentFilter) {
|
|
4795
|
+
conditions.push("agent_id = ?");
|
|
4796
|
+
args2.push(agentFilter);
|
|
4797
|
+
}
|
|
4798
|
+
const result = await client.execute({
|
|
4799
|
+
sql: `SELECT id, agent_id, agent_role, event, project, summary, task_file, session_scope, created_at
|
|
4800
|
+
FROM notifications
|
|
4801
|
+
WHERE ${conditions.join(" AND ")}${scope.sql}
|
|
4802
|
+
ORDER BY created_at ASC`,
|
|
4803
|
+
args: [...args2, ...scope.args]
|
|
4804
|
+
});
|
|
4805
|
+
return result.rows.map((r) => ({
|
|
4806
|
+
id: String(r.id),
|
|
4807
|
+
agentId: String(r.agent_id),
|
|
4808
|
+
agentRole: String(r.agent_role),
|
|
4809
|
+
event: String(r.event),
|
|
4810
|
+
project: String(r.project),
|
|
4811
|
+
summary: String(r.summary),
|
|
4812
|
+
taskFile: r.task_file ? String(r.task_file) : void 0,
|
|
4813
|
+
sessionScope: r.session_scope == null ? null : String(r.session_scope),
|
|
4814
|
+
timestamp: String(r.created_at),
|
|
4815
|
+
read: false
|
|
4816
|
+
}));
|
|
4817
|
+
} catch {
|
|
4818
|
+
return [];
|
|
4819
|
+
}
|
|
4820
|
+
}
|
|
4821
|
+
async function markAsRead(ids, sessionScope) {
|
|
4822
|
+
if (ids.length === 0) return;
|
|
4823
|
+
try {
|
|
4824
|
+
const client = getClient();
|
|
4825
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
4826
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
4827
|
+
await client.execute({
|
|
4828
|
+
sql: `UPDATE notifications SET read = 1 WHERE id IN (${placeholders})${scope.sql}`,
|
|
4829
|
+
args: [...ids, ...scope.args]
|
|
4830
|
+
});
|
|
4831
|
+
} catch {
|
|
4832
|
+
}
|
|
4833
|
+
}
|
|
4742
4834
|
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
4743
4835
|
try {
|
|
4744
4836
|
const client = getClient();
|
|
@@ -4751,11 +4843,144 @@ async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
|
4751
4843
|
} catch {
|
|
4752
4844
|
}
|
|
4753
4845
|
}
|
|
4846
|
+
async function cleanupOldNotifications(daysOld = CLEANUP_DAYS, sessionScope) {
|
|
4847
|
+
try {
|
|
4848
|
+
const client = getClient();
|
|
4849
|
+
const cutoff = new Date(
|
|
4850
|
+
Date.now() - daysOld * 24 * 60 * 60 * 1e3
|
|
4851
|
+
).toISOString();
|
|
4852
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
4853
|
+
const result = await client.execute({
|
|
4854
|
+
sql: `DELETE FROM notifications WHERE created_at < ?${scope.sql}`,
|
|
4855
|
+
args: [cutoff, ...scope.args]
|
|
4856
|
+
});
|
|
4857
|
+
return result.rowsAffected;
|
|
4858
|
+
} catch {
|
|
4859
|
+
return 0;
|
|
4860
|
+
}
|
|
4861
|
+
}
|
|
4862
|
+
async function markDoneTaskNotificationsAsRead(sessionScope) {
|
|
4863
|
+
try {
|
|
4864
|
+
const client = getClient();
|
|
4865
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
4866
|
+
const result = await client.execute({
|
|
4867
|
+
sql: `UPDATE notifications SET read = 1
|
|
4868
|
+
WHERE read = 0
|
|
4869
|
+
AND task_file IS NOT NULL
|
|
4870
|
+
${scope.sql}
|
|
4871
|
+
AND task_file IN (
|
|
4872
|
+
SELECT task_file FROM tasks WHERE status = 'done'${scope.sql}
|
|
4873
|
+
)`,
|
|
4874
|
+
args: [...scope.args, ...scope.args]
|
|
4875
|
+
});
|
|
4876
|
+
return result.rowsAffected;
|
|
4877
|
+
} catch {
|
|
4878
|
+
return 0;
|
|
4879
|
+
}
|
|
4880
|
+
}
|
|
4881
|
+
function formatNotifications(notifications) {
|
|
4882
|
+
if (notifications.length === 0) return "";
|
|
4883
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
4884
|
+
for (const n of notifications) {
|
|
4885
|
+
const key = `${n.agentId}|${n.agentRole}`;
|
|
4886
|
+
if (!grouped.has(key)) grouped.set(key, []);
|
|
4887
|
+
grouped.get(key).push(n);
|
|
4888
|
+
}
|
|
4889
|
+
const lines = [];
|
|
4890
|
+
lines.push(`## Notifications (${notifications.length} unread)
|
|
4891
|
+
`);
|
|
4892
|
+
for (const [key, items] of grouped) {
|
|
4893
|
+
const [agentId, agentRole] = key.split("|");
|
|
4894
|
+
lines.push(`**${agentId}** (${agentRole}):`);
|
|
4895
|
+
for (const item of items) {
|
|
4896
|
+
const ago = formatTimeAgo(item.timestamp);
|
|
4897
|
+
const icon = eventIcon(item.event);
|
|
4898
|
+
lines.push(`- ${icon} ${item.summary} (${item.project}) \u2014 ${ago}`);
|
|
4899
|
+
}
|
|
4900
|
+
lines.push("");
|
|
4901
|
+
}
|
|
4902
|
+
return lines.join("\n");
|
|
4903
|
+
}
|
|
4904
|
+
async function migrateJsonNotifications() {
|
|
4905
|
+
const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR || path12.join(os9.homedir(), ".exe-os");
|
|
4906
|
+
const notifDir = path12.join(base, "notifications");
|
|
4907
|
+
if (!existsSync13(notifDir)) return 0;
|
|
4908
|
+
let migrated = 0;
|
|
4909
|
+
try {
|
|
4910
|
+
const files = readdirSync(notifDir).filter((f) => f.endsWith(".json"));
|
|
4911
|
+
if (files.length === 0) return 0;
|
|
4912
|
+
const client = getClient();
|
|
4913
|
+
for (const file of files) {
|
|
4914
|
+
try {
|
|
4915
|
+
const filePath = path12.join(notifDir, file);
|
|
4916
|
+
const data = JSON.parse(readFileSync10(filePath, "utf8"));
|
|
4917
|
+
await client.execute({
|
|
4918
|
+
sql: `INSERT OR IGNORE INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
4919
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
4920
|
+
args: [
|
|
4921
|
+
crypto2.randomUUID(),
|
|
4922
|
+
data.agentId ?? "unknown",
|
|
4923
|
+
data.agentRole ?? "unknown",
|
|
4924
|
+
data.event ?? "session_summary",
|
|
4925
|
+
data.project ?? "unknown",
|
|
4926
|
+
data.summary ?? "",
|
|
4927
|
+
data.taskFile ?? null,
|
|
4928
|
+
null,
|
|
4929
|
+
data.read ? 1 : 0,
|
|
4930
|
+
data.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
4931
|
+
]
|
|
4932
|
+
});
|
|
4933
|
+
unlinkSync4(filePath);
|
|
4934
|
+
migrated++;
|
|
4935
|
+
} catch {
|
|
4936
|
+
}
|
|
4937
|
+
}
|
|
4938
|
+
try {
|
|
4939
|
+
const remaining = readdirSync(notifDir);
|
|
4940
|
+
if (remaining.length === 0) {
|
|
4941
|
+
rmdirSync(notifDir);
|
|
4942
|
+
}
|
|
4943
|
+
} catch {
|
|
4944
|
+
}
|
|
4945
|
+
} catch {
|
|
4946
|
+
}
|
|
4947
|
+
return migrated;
|
|
4948
|
+
}
|
|
4949
|
+
function eventIcon(event) {
|
|
4950
|
+
switch (event) {
|
|
4951
|
+
case "task_complete":
|
|
4952
|
+
return "Completed:";
|
|
4953
|
+
case "task_needs_fix":
|
|
4954
|
+
return "Needs fix:";
|
|
4955
|
+
case "session_summary":
|
|
4956
|
+
return "Session:";
|
|
4957
|
+
case "error_spike":
|
|
4958
|
+
return "Errors:";
|
|
4959
|
+
case "orphan_task":
|
|
4960
|
+
return "Orphan:";
|
|
4961
|
+
case "subtasks_complete":
|
|
4962
|
+
return "Subtasks done:";
|
|
4963
|
+
case "capacity_relaunch":
|
|
4964
|
+
return "Relaunched:";
|
|
4965
|
+
}
|
|
4966
|
+
}
|
|
4967
|
+
function formatTimeAgo(timestamp) {
|
|
4968
|
+
const diffMs = Date.now() - new Date(timestamp).getTime();
|
|
4969
|
+
const mins = Math.floor(diffMs / 6e4);
|
|
4970
|
+
if (mins < 1) return "just now";
|
|
4971
|
+
if (mins < 60) return `${mins}m ago`;
|
|
4972
|
+
const hours = Math.floor(mins / 60);
|
|
4973
|
+
if (hours < 24) return `${hours}h ago`;
|
|
4974
|
+
const days = Math.floor(hours / 24);
|
|
4975
|
+
return `${days}d ago`;
|
|
4976
|
+
}
|
|
4977
|
+
var CLEANUP_DAYS;
|
|
4754
4978
|
var init_notifications = __esm({
|
|
4755
4979
|
"src/lib/notifications.ts"() {
|
|
4756
4980
|
"use strict";
|
|
4757
4981
|
init_database();
|
|
4758
4982
|
init_task_scope();
|
|
4983
|
+
CLEANUP_DAYS = 7;
|
|
4759
4984
|
}
|
|
4760
4985
|
});
|
|
4761
4986
|
|
|
@@ -5817,7 +6042,13 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
|
|
|
5817
6042
|
taskFile
|
|
5818
6043
|
});
|
|
5819
6044
|
const originalPriority = String(row.priority).toLowerCase();
|
|
5820
|
-
const
|
|
6045
|
+
const resultLower = result?.toLowerCase() ?? "";
|
|
6046
|
+
const hasTestEvidence = (
|
|
6047
|
+
// Vitest/Jest output patterns (hard to fake without actually running tests)
|
|
6048
|
+
/\d+\s+pass(ed|ing)/.test(resultLower) || /test files?\s+\d+\s+passed/.test(resultLower) || /tests?\s+\d+\s+passed/.test(resultLower)
|
|
6049
|
+
);
|
|
6050
|
+
const hasNoFailures = !/fail(ed|ure|ing)|error/i.test(resultLower);
|
|
6051
|
+
const autoApprove = originalPriority === "p2" && hasTestEvidence && hasNoFailures;
|
|
5821
6052
|
if (!autoApprove) {
|
|
5822
6053
|
try {
|
|
5823
6054
|
const key = getSessionKey();
|
|
@@ -5825,6 +6056,13 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
|
|
|
5825
6056
|
if (exeSession) {
|
|
5826
6057
|
sendIntercom(exeSession);
|
|
5827
6058
|
}
|
|
6059
|
+
if (reviewer && reviewer !== coordinatorName && reviewer !== exeSession) {
|
|
6060
|
+
const { employeeSessionName: employeeSessionName2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
6061
|
+
if (exeSession) {
|
|
6062
|
+
const reviewerSession = employeeSessionName2(reviewer, exeSession);
|
|
6063
|
+
sendIntercom(reviewerSession);
|
|
6064
|
+
}
|
|
6065
|
+
}
|
|
5828
6066
|
} catch {
|
|
5829
6067
|
}
|
|
5830
6068
|
}
|
|
@@ -6600,6 +6838,20 @@ async function updateTask(input) {
|
|
|
6600
6838
|
notifyTaskDone();
|
|
6601
6839
|
}
|
|
6602
6840
|
await markTaskNotificationsRead(taskFile);
|
|
6841
|
+
if (input.status === "needs_review" && !isCoordinator) {
|
|
6842
|
+
try {
|
|
6843
|
+
const { writeNotification: writeNotification2 } = await Promise.resolve().then(() => (init_notifications(), notifications_exports));
|
|
6844
|
+
await writeNotification2({
|
|
6845
|
+
agentId: String(row.assigned_to),
|
|
6846
|
+
agentRole: String(row.assigned_to),
|
|
6847
|
+
event: "task_complete",
|
|
6848
|
+
project: String(row.project_name),
|
|
6849
|
+
summary: `"${String(row.title)}" is ready for review`,
|
|
6850
|
+
taskFile
|
|
6851
|
+
});
|
|
6852
|
+
} catch {
|
|
6853
|
+
}
|
|
6854
|
+
}
|
|
6603
6855
|
if (input.status === "done" || input.status === "closed") {
|
|
6604
6856
|
try {
|
|
6605
6857
|
await cascadeUnblock(taskId, input.baseDir, now);
|
|
@@ -7032,18 +7284,31 @@ function acquireSpawnLock2(sessionName) {
|
|
|
7032
7284
|
mkdirSync8(SPAWN_LOCK_DIR, { recursive: true });
|
|
7033
7285
|
}
|
|
7034
7286
|
const lockFile = spawnLockPath(sessionName);
|
|
7035
|
-
|
|
7036
|
-
|
|
7037
|
-
|
|
7038
|
-
|
|
7039
|
-
|
|
7040
|
-
|
|
7041
|
-
|
|
7042
|
-
|
|
7287
|
+
const lockData = JSON.stringify({ pid: process.pid, timestamp: Date.now() });
|
|
7288
|
+
const { openSync: openSync3, closeSync: closeSync3, writeSync } = __require("fs");
|
|
7289
|
+
const { constants } = __require("fs");
|
|
7290
|
+
try {
|
|
7291
|
+
const fd = openSync3(lockFile, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 420);
|
|
7292
|
+
writeSync(fd, lockData);
|
|
7293
|
+
closeSync3(fd);
|
|
7294
|
+
return true;
|
|
7295
|
+
} catch (err) {
|
|
7296
|
+
if (err?.code !== "EEXIST") {
|
|
7297
|
+
return true;
|
|
7043
7298
|
}
|
|
7044
7299
|
}
|
|
7045
|
-
|
|
7046
|
-
|
|
7300
|
+
try {
|
|
7301
|
+
const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
|
|
7302
|
+
const age = Date.now() - lock.timestamp;
|
|
7303
|
+
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
7304
|
+
return false;
|
|
7305
|
+
}
|
|
7306
|
+
writeFileSync8(lockFile, lockData);
|
|
7307
|
+
return true;
|
|
7308
|
+
} catch {
|
|
7309
|
+
writeFileSync8(lockFile, lockData);
|
|
7310
|
+
return true;
|
|
7311
|
+
}
|
|
7047
7312
|
}
|
|
7048
7313
|
function releaseSpawnLock2(sessionName) {
|
|
7049
7314
|
try {
|
|
@@ -7122,6 +7387,21 @@ function parseParentExe(sessionName, agentId) {
|
|
|
7122
7387
|
function extractRootExe(name) {
|
|
7123
7388
|
if (!name) return null;
|
|
7124
7389
|
if (!name.includes("-")) return name;
|
|
7390
|
+
try {
|
|
7391
|
+
const roster = (init_employees(), __toCommonJS(employees_exports)).loadEmployeesSync();
|
|
7392
|
+
if (roster.length > 0) {
|
|
7393
|
+
const sortedNames = roster.map((e) => e.name).sort((a, b) => b.length - a.length);
|
|
7394
|
+
for (const agentName of sortedNames) {
|
|
7395
|
+
const escaped = agentName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7396
|
+
const regex = new RegExp(`^${escaped}\\d*-(.+)$`);
|
|
7397
|
+
const match = name.match(regex);
|
|
7398
|
+
if (match) {
|
|
7399
|
+
return extractRootExe(match[1]);
|
|
7400
|
+
}
|
|
7401
|
+
}
|
|
7402
|
+
}
|
|
7403
|
+
} catch {
|
|
7404
|
+
}
|
|
7125
7405
|
const parts = name.split("-").filter(Boolean);
|
|
7126
7406
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
7127
7407
|
}
|
|
@@ -7140,6 +7420,10 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
7140
7420
|
function getParentExe(sessionKey) {
|
|
7141
7421
|
try {
|
|
7142
7422
|
const data = JSON.parse(readFileSync12(path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
7423
|
+
if (data.registeredAt) {
|
|
7424
|
+
const age = Date.now() - new Date(data.registeredAt).getTime();
|
|
7425
|
+
if (age > PARENT_EXE_CACHE_TTL_MS) return null;
|
|
7426
|
+
}
|
|
7143
7427
|
return data.parentExe || null;
|
|
7144
7428
|
} catch {
|
|
7145
7429
|
return null;
|
|
@@ -7688,7 +7972,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
7688
7972
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
7689
7973
|
} catch {
|
|
7690
7974
|
}
|
|
7691
|
-
let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName}`;
|
|
7975
|
+
let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName} EXE_SESSION_START_ISO=${(/* @__PURE__ */ new Date()).toISOString()}`;
|
|
7692
7976
|
if (ccProvider !== DEFAULT_PROVIDER) {
|
|
7693
7977
|
const cfg = PROVIDER_TABLE[ccProvider];
|
|
7694
7978
|
if (cfg?.apiKeyEnv) {
|
|
@@ -7723,10 +8007,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
7723
8007
|
}
|
|
7724
8008
|
if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
|
|
7725
8009
|
if (agentRtConfig.runtime === "claude" && agentRtConfig.model) {
|
|
7726
|
-
|
|
7727
|
-
|
|
7728
|
-
ccModel += "[1m]";
|
|
7729
|
-
}
|
|
8010
|
+
const { normalizeCcModelName: normalizeCcModelName2 } = (init_agent_config(), __toCommonJS(agent_config_exports));
|
|
8011
|
+
const ccModel = normalizeCcModelName2(agentRtConfig.model);
|
|
7730
8012
|
envPrefix = `${envPrefix} ANTHROPIC_MODEL=${ccModel}`;
|
|
7731
8013
|
}
|
|
7732
8014
|
}
|
|
@@ -7827,7 +8109,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
7827
8109
|
releaseSpawnLock2(sessionName);
|
|
7828
8110
|
return { sessionName };
|
|
7829
8111
|
}
|
|
7830
|
-
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;
|
|
8112
|
+
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;
|
|
7831
8113
|
var init_tmux_routing = __esm({
|
|
7832
8114
|
"src/lib/tmux-routing.ts"() {
|
|
7833
8115
|
"use strict";
|
|
@@ -7847,6 +8129,7 @@ var init_tmux_routing = __esm({
|
|
|
7847
8129
|
SESSION_CACHE = path18.join(os11.homedir(), ".exe-os", "session-cache");
|
|
7848
8130
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
7849
8131
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
8132
|
+
PARENT_EXE_CACHE_TTL_MS = 4 * 60 * 60 * 1e3;
|
|
7850
8133
|
VERIFY_PANE_LINES = 200;
|
|
7851
8134
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
7852
8135
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
@@ -7891,7 +8174,7 @@ var init_task_scope = __esm({
|
|
|
7891
8174
|
});
|
|
7892
8175
|
|
|
7893
8176
|
// src/lib/keychain.ts
|
|
7894
|
-
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
8177
|
+
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2, rename, copyFile } from "fs/promises";
|
|
7895
8178
|
import { existsSync as existsSync17, statSync as statSync3 } from "fs";
|
|
7896
8179
|
import { execSync as execSync8 } from "child_process";
|
|
7897
8180
|
import path19 from "path";
|
|
@@ -7926,12 +8209,14 @@ function linuxSecretAvailable() {
|
|
|
7926
8209
|
function isRootOnlyTrustedServerKeyFile(keyPath) {
|
|
7927
8210
|
if (process.platform !== "linux") return false;
|
|
7928
8211
|
try {
|
|
7929
|
-
const uid = typeof os12.userInfo().uid === "number" ? os12.userInfo().uid : -1;
|
|
7930
8212
|
const st = statSync3(keyPath);
|
|
7931
8213
|
if (!st.isFile() || (st.mode & 63) !== 0) return false;
|
|
8214
|
+
const uid = typeof os12.userInfo().uid === "number" ? os12.userInfo().uid : -1;
|
|
7932
8215
|
if (uid === 0) return true;
|
|
7933
8216
|
const exeOsDir = process.env.EXE_OS_DIR;
|
|
7934
|
-
|
|
8217
|
+
if (exeOsDir && path19.resolve(keyPath).startsWith(path19.resolve(exeOsDir) + path19.sep)) return true;
|
|
8218
|
+
if (!linuxSecretAvailable()) return true;
|
|
8219
|
+
return false;
|
|
7935
8220
|
} catch {
|
|
7936
8221
|
return false;
|
|
7937
8222
|
}
|
|
@@ -8081,15 +8366,25 @@ async function writeMachineBoundFileFallback(b64) {
|
|
|
8081
8366
|
await mkdir4(dir, { recursive: true });
|
|
8082
8367
|
const keyPath = getKeyPath();
|
|
8083
8368
|
const machineKey = deriveMachineKey();
|
|
8084
|
-
|
|
8085
|
-
|
|
8086
|
-
|
|
8087
|
-
|
|
8088
|
-
|
|
8369
|
+
const content = machineKey ? encryptWithMachineKey(b64, machineKey) + "\n" : b64 + "\n";
|
|
8370
|
+
const result = machineKey ? "encrypted" : "plaintext";
|
|
8371
|
+
const tmpPath = keyPath + ".tmp";
|
|
8372
|
+
try {
|
|
8373
|
+
if (existsSync17(keyPath)) {
|
|
8374
|
+
await copyFile(keyPath, keyPath + ".bak").catch(() => {
|
|
8375
|
+
});
|
|
8376
|
+
}
|
|
8377
|
+
await writeFile5(tmpPath, content, "utf-8");
|
|
8378
|
+
await chmod2(tmpPath, 384);
|
|
8379
|
+
await rename(tmpPath, keyPath);
|
|
8380
|
+
} catch (err) {
|
|
8381
|
+
try {
|
|
8382
|
+
await unlink(tmpPath);
|
|
8383
|
+
} catch {
|
|
8384
|
+
}
|
|
8385
|
+
throw err;
|
|
8089
8386
|
}
|
|
8090
|
-
|
|
8091
|
-
await chmod2(keyPath, 384);
|
|
8092
|
-
return "plaintext";
|
|
8387
|
+
return result;
|
|
8093
8388
|
}
|
|
8094
8389
|
async function getMasterKey() {
|
|
8095
8390
|
let nativeValue = macKeychainGet() ?? linuxSecretGet();
|
|
@@ -8156,7 +8451,7 @@ async function getMasterKey() {
|
|
|
8156
8451
|
b64Value = content;
|
|
8157
8452
|
}
|
|
8158
8453
|
const key = Buffer.from(b64Value, "base64");
|
|
8159
|
-
if (
|
|
8454
|
+
if (isRootOnlyTrustedServerKeyFile(keyPath)) {
|
|
8160
8455
|
return key;
|
|
8161
8456
|
}
|
|
8162
8457
|
const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
|