@askexenow/exe-os 0.9.10 → 0.9.11
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/cli.js +150 -8
- package/dist/bin/exe-boot.js +150 -8
- package/dist/bin/exe-dispatch.js +155 -8
- package/dist/bin/exe-gateway.js +531 -166
- package/dist/bin/exe-link.js +6 -0
- package/dist/bin/exe-session-cleanup.js +28 -8
- package/dist/bin/git-sweep.js +157 -10
- package/dist/bin/scan-tasks.js +155 -8
- package/dist/bin/setup.js +6 -0
- package/dist/gateway/index.js +144 -8
- package/dist/hooks/bug-report-worker.js +394 -258
- package/dist/hooks/codex-stop-task-finalizer.js +10 -8
- package/dist/hooks/commit-complete.js +155 -8
- package/dist/hooks/ingest-worker.js +155 -8
- package/dist/hooks/pre-compact.js +155 -8
- package/dist/hooks/prompt-submit.js +28 -8
- package/dist/hooks/session-end.js +157 -10
- package/dist/hooks/summary-worker.js +6 -0
- package/dist/index.js +144 -8
- package/dist/lib/cloud-sync.js +6 -0
- package/dist/lib/exe-daemon.js +28 -8
- package/dist/lib/messaging.js +10 -8
- package/dist/lib/tasks.js +460 -313
- package/dist/lib/tmux-routing.js +155 -8
- package/dist/mcp/server.js +396 -254
- package/dist/mcp/tools/create-task.js +449 -313
- package/dist/mcp/tools/send-message.js +10 -8
- package/dist/mcp/tools/update-task.js +460 -313
- package/dist/runtime/index.js +155 -8
- package/dist/tui/App.js +144 -8
- package/package.json +1 -1
package/dist/lib/tmux-routing.js
CHANGED
|
@@ -734,6 +734,17 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
|
734
734
|
if (!agentName) return false;
|
|
735
735
|
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
736
736
|
}
|
|
737
|
+
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
738
|
+
if (!existsSync6(employeesPath)) {
|
|
739
|
+
return [];
|
|
740
|
+
}
|
|
741
|
+
const raw = await readFile2(employeesPath, "utf-8");
|
|
742
|
+
try {
|
|
743
|
+
return JSON.parse(raw);
|
|
744
|
+
} catch {
|
|
745
|
+
return [];
|
|
746
|
+
}
|
|
747
|
+
}
|
|
737
748
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
738
749
|
if (!existsSync6(employeesPath)) return [];
|
|
739
750
|
try {
|
|
@@ -1819,8 +1830,35 @@ var init_tasks_crud = __esm({
|
|
|
1819
1830
|
});
|
|
1820
1831
|
|
|
1821
1832
|
// src/lib/tasks-review.ts
|
|
1833
|
+
var tasks_review_exports = {};
|
|
1834
|
+
__export(tasks_review_exports, {
|
|
1835
|
+
cleanupOrphanedReviews: () => cleanupOrphanedReviews,
|
|
1836
|
+
cleanupReviewFile: () => cleanupReviewFile,
|
|
1837
|
+
countNewPendingReviewsSince: () => countNewPendingReviewsSince,
|
|
1838
|
+
countPendingReviews: () => countPendingReviews,
|
|
1839
|
+
createReviewForCompletedTask: () => createReviewForCompletedTask,
|
|
1840
|
+
formatAge: () => formatAge,
|
|
1841
|
+
getReviewChecklist: () => getReviewChecklist,
|
|
1842
|
+
isStale: () => isStale,
|
|
1843
|
+
listPendingReviews: () => listPendingReviews
|
|
1844
|
+
});
|
|
1822
1845
|
import path12 from "path";
|
|
1823
1846
|
import { existsSync as existsSync11, readdirSync as readdirSync2, unlinkSync as unlinkSync3 } from "fs";
|
|
1847
|
+
function formatAge(isoTimestamp) {
|
|
1848
|
+
if (!isoTimestamp) return "";
|
|
1849
|
+
const ms = Date.now() - new Date(isoTimestamp).getTime();
|
|
1850
|
+
if (ms < 0) return "just now";
|
|
1851
|
+
const minutes = Math.floor(ms / 6e4);
|
|
1852
|
+
if (minutes < 60) return `${minutes}m ago`;
|
|
1853
|
+
const hours = Math.floor(minutes / 60);
|
|
1854
|
+
if (hours < 24) return `${hours}h ago`;
|
|
1855
|
+
const days = Math.floor(hours / 24);
|
|
1856
|
+
return `${days}d ago`;
|
|
1857
|
+
}
|
|
1858
|
+
function isStale(isoTimestamp) {
|
|
1859
|
+
if (!isoTimestamp) return false;
|
|
1860
|
+
return Date.now() - new Date(isoTimestamp).getTime() > 24 * 60 * 60 * 1e3;
|
|
1861
|
+
}
|
|
1824
1862
|
async function countPendingReviews(sessionScope) {
|
|
1825
1863
|
const client = getClient();
|
|
1826
1864
|
const scope = strictSessionScopeFilter(
|
|
@@ -1941,6 +1979,95 @@ function getReviewChecklist(role, agent, taskSlug) {
|
|
|
1941
1979
|
]
|
|
1942
1980
|
};
|
|
1943
1981
|
}
|
|
1982
|
+
async function createReviewForCompletedTask(row, result, _baseDir, now) {
|
|
1983
|
+
const taskFile = String(row.task_file);
|
|
1984
|
+
const employees = await loadEmployees();
|
|
1985
|
+
const coordinatorName = getCoordinatorName(employees);
|
|
1986
|
+
if (isCoordinatorName(String(row.assigned_to), employees)) return;
|
|
1987
|
+
if (String(row.title).startsWith("Review:")) return;
|
|
1988
|
+
const fileName = taskFile.split("/").pop() ?? "";
|
|
1989
|
+
if (fileName.startsWith("review-") && String(row.assigned_by) === "system") return;
|
|
1990
|
+
if (fileName.startsWith("review-") && String(row.assigned_to) === "system") return;
|
|
1991
|
+
const client = getClient();
|
|
1992
|
+
const agent = String(row.assigned_to);
|
|
1993
|
+
const rawReviewer = row.reviewer || row.assigned_by;
|
|
1994
|
+
const reviewer = rawReviewer ? String(rawReviewer) : coordinatorName;
|
|
1995
|
+
const currentStatus = String(row.status ?? "");
|
|
1996
|
+
if (currentStatus === "done") return;
|
|
1997
|
+
const existingResult = String(row.result ?? "");
|
|
1998
|
+
if (existingResult.includes("## Review notes")) return;
|
|
1999
|
+
let reviewerRole = "unknown";
|
|
2000
|
+
try {
|
|
2001
|
+
const emp = getEmployee(employees, reviewer);
|
|
2002
|
+
if (emp) reviewerRole = emp.role;
|
|
2003
|
+
} catch {
|
|
2004
|
+
}
|
|
2005
|
+
const taskTitle = String(row.title);
|
|
2006
|
+
const taskSlug = taskFile.split("/").pop()?.replace(".md", "") ?? "unknown";
|
|
2007
|
+
const { lens, checklist } = getReviewChecklist(reviewerRole, agent, taskSlug);
|
|
2008
|
+
process.stderr.write(
|
|
2009
|
+
`[review] Annotating "${taskTitle}" for review by ${reviewer} (${reviewerRole})
|
|
2010
|
+
`
|
|
2011
|
+
);
|
|
2012
|
+
const reviewNotes = [
|
|
2013
|
+
`
|
|
2014
|
+
---
|
|
2015
|
+
## Review notes`,
|
|
2016
|
+
`Review lens: ${lens}`,
|
|
2017
|
+
`Reviewer: **${reviewer}** (${reviewerRole})`,
|
|
2018
|
+
"",
|
|
2019
|
+
"### Checklist",
|
|
2020
|
+
...checklist,
|
|
2021
|
+
"",
|
|
2022
|
+
"### Verdict",
|
|
2023
|
+
"- **Approved:** mark this task as done",
|
|
2024
|
+
"- **Needs work:** re-open with notes"
|
|
2025
|
+
].join("\n");
|
|
2026
|
+
const originalTaskId = String(row.id);
|
|
2027
|
+
const updatedResult = (result ?? "No result summary provided") + reviewNotes;
|
|
2028
|
+
await client.execute({
|
|
2029
|
+
sql: `UPDATE tasks SET result = ?, status = 'needs_review', updated_at = ?
|
|
2030
|
+
WHERE id = ?`,
|
|
2031
|
+
args: [updatedResult, now, originalTaskId]
|
|
2032
|
+
});
|
|
2033
|
+
orgBus.emit({
|
|
2034
|
+
type: "review_created",
|
|
2035
|
+
reviewId: originalTaskId,
|
|
2036
|
+
employee: agent,
|
|
2037
|
+
reviewer,
|
|
2038
|
+
timestamp: now
|
|
2039
|
+
});
|
|
2040
|
+
await writeNotification({
|
|
2041
|
+
agentId: agent,
|
|
2042
|
+
agentRole: String(row.assigned_to),
|
|
2043
|
+
event: "task_complete",
|
|
2044
|
+
project: String(row.project_name),
|
|
2045
|
+
summary: `completed "${taskTitle}" \u2014 ready for review`,
|
|
2046
|
+
taskFile
|
|
2047
|
+
});
|
|
2048
|
+
const originalPriority = String(row.priority).toLowerCase();
|
|
2049
|
+
const autoApprove = originalPriority === "p2" && result?.toLowerCase().includes("tests pass");
|
|
2050
|
+
if (!autoApprove) {
|
|
2051
|
+
try {
|
|
2052
|
+
const key = getSessionKey();
|
|
2053
|
+
const exeSession = getParentExe(key);
|
|
2054
|
+
if (exeSession) {
|
|
2055
|
+
sendIntercom(exeSession);
|
|
2056
|
+
}
|
|
2057
|
+
} catch {
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
if (autoApprove) {
|
|
2061
|
+
process.stderr.write(
|
|
2062
|
+
`[review] Auto-approving "${taskTitle}" (P2 + tests pass)
|
|
2063
|
+
`
|
|
2064
|
+
);
|
|
2065
|
+
await client.execute({
|
|
2066
|
+
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE id = ?",
|
|
2067
|
+
args: [now, originalTaskId]
|
|
2068
|
+
});
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
1944
2071
|
async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
1945
2072
|
if (String(row.assigned_by) !== "system" || !taskFile.includes("review-")) return;
|
|
1946
2073
|
try {
|
|
@@ -3304,15 +3431,17 @@ function sendIntercom(targetSession) {
|
|
|
3304
3431
|
logIntercom(`QUEUED \u2192 ${targetSession} (session busy)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
|
|
3305
3432
|
return "queued";
|
|
3306
3433
|
}
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3434
|
+
if (sessionState !== "idle") {
|
|
3435
|
+
try {
|
|
3436
|
+
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
3437
|
+
const agent = baseAgentName(rawAgent);
|
|
3438
|
+
const markerPath = path15.join(SESSION_CACHE, `current-task-${agent}.json`);
|
|
3439
|
+
if (existsSync12(markerPath)) {
|
|
3440
|
+
logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker + not idle \u2014 will auto-chain)`);
|
|
3441
|
+
return "debounced";
|
|
3442
|
+
}
|
|
3443
|
+
} catch {
|
|
3314
3444
|
}
|
|
3315
|
-
} catch {
|
|
3316
3445
|
}
|
|
3317
3446
|
try {
|
|
3318
3447
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
@@ -3383,6 +3512,24 @@ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitl
|
|
|
3383
3512
|
try {
|
|
3384
3513
|
const sessions = transport.listSessions();
|
|
3385
3514
|
if (!sessions.includes(coordinatorSession)) return false;
|
|
3515
|
+
try {
|
|
3516
|
+
const { countPendingReviews: countPendingReviews2 } = (init_tasks_review(), __toCommonJS(tasks_review_exports));
|
|
3517
|
+
const pending = countPendingReviews2(coordinatorSession);
|
|
3518
|
+
if (pending instanceof Promise) {
|
|
3519
|
+
pending.then((count) => {
|
|
3520
|
+
if (count > 0) {
|
|
3521
|
+
execSync6(
|
|
3522
|
+
`tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
|
|
3523
|
+
{ timeout: 3e3 }
|
|
3524
|
+
);
|
|
3525
|
+
logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending)`);
|
|
3526
|
+
}
|
|
3527
|
+
}).catch(() => {
|
|
3528
|
+
});
|
|
3529
|
+
return true;
|
|
3530
|
+
}
|
|
3531
|
+
} catch {
|
|
3532
|
+
}
|
|
3386
3533
|
execSync6(
|
|
3387
3534
|
`tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
|
|
3388
3535
|
{ timeout: 3e3 }
|