@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.
@@ -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
- try {
3308
- const rawAgent = targetSession.split("-")[0] ?? targetSession;
3309
- const agent = baseAgentName(rawAgent);
3310
- const markerPath = path15.join(SESSION_CACHE, `current-task-${agent}.json`);
3311
- if (existsSync12(markerPath)) {
3312
- logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
3313
- return "debounced";
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 }