@askexenow/exe-os 0.9.9 → 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 CHANGED
@@ -5261,6 +5261,12 @@ async function cloudSync(config) {
5261
5261
  pulled = pullResult.records.length;
5262
5262
  }
5263
5263
  }
5264
+ if (pulled > 0) {
5265
+ try {
5266
+ await pushToPostgres(pullResult.records);
5267
+ } catch {
5268
+ }
5269
+ }
5264
5270
  if (pullResult.maxVersion > lastPullVersion) {
5265
5271
  await client.execute({
5266
5272
  sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_cloud_pull_version', ?)",
@@ -9664,6 +9670,110 @@ var init_session_kill_telemetry = __esm({
9664
9670
  }
9665
9671
  });
9666
9672
 
9673
+ // src/lib/project-name.ts
9674
+ import { execSync as execSync6 } from "child_process";
9675
+ import path23 from "path";
9676
+ function getProjectName(cwd2) {
9677
+ const dir = cwd2 ?? process.cwd();
9678
+ if (_cached2 && _cachedCwd === dir) return _cached2;
9679
+ try {
9680
+ let repoRoot;
9681
+ try {
9682
+ const gitCommonDir = execSync6("git rev-parse --path-format=absolute --git-common-dir", {
9683
+ cwd: dir,
9684
+ encoding: "utf8",
9685
+ timeout: 2e3,
9686
+ stdio: ["pipe", "pipe", "pipe"]
9687
+ }).trim();
9688
+ repoRoot = path23.dirname(gitCommonDir);
9689
+ } catch {
9690
+ repoRoot = execSync6("git rev-parse --show-toplevel", {
9691
+ cwd: dir,
9692
+ encoding: "utf8",
9693
+ timeout: 2e3,
9694
+ stdio: ["pipe", "pipe", "pipe"]
9695
+ }).trim();
9696
+ }
9697
+ _cached2 = path23.basename(repoRoot);
9698
+ _cachedCwd = dir;
9699
+ return _cached2;
9700
+ } catch {
9701
+ _cached2 = path23.basename(dir);
9702
+ _cachedCwd = dir;
9703
+ return _cached2;
9704
+ }
9705
+ }
9706
+ var _cached2, _cachedCwd;
9707
+ var init_project_name = __esm({
9708
+ "src/lib/project-name.ts"() {
9709
+ "use strict";
9710
+ _cached2 = null;
9711
+ _cachedCwd = null;
9712
+ }
9713
+ });
9714
+
9715
+ // src/lib/session-scope.ts
9716
+ var session_scope_exports = {};
9717
+ __export(session_scope_exports, {
9718
+ assertSessionScope: () => assertSessionScope,
9719
+ findSessionForProject: () => findSessionForProject,
9720
+ getSessionProject: () => getSessionProject
9721
+ });
9722
+ function getSessionProject(sessionName) {
9723
+ const sessions = listSessions();
9724
+ const entry = sessions.find((s) => s.windowName === sessionName);
9725
+ if (!entry) return null;
9726
+ const parts = entry.projectDir.split("/").filter(Boolean);
9727
+ return parts[parts.length - 1] ?? null;
9728
+ }
9729
+ function findSessionForProject(projectName) {
9730
+ const sessions = listSessions();
9731
+ for (const s of sessions) {
9732
+ const proj = s.projectDir.split("/").filter(Boolean).pop();
9733
+ if (proj === projectName && isCoordinatorName(s.agentId)) return s;
9734
+ }
9735
+ return null;
9736
+ }
9737
+ function assertSessionScope(actionType, targetProject) {
9738
+ try {
9739
+ const currentProject = getProjectName();
9740
+ const exeSession = resolveExeSession();
9741
+ if (!exeSession) {
9742
+ return { allowed: true, reason: "no_session" };
9743
+ }
9744
+ if (currentProject === targetProject) {
9745
+ return {
9746
+ allowed: true,
9747
+ reason: "same_session",
9748
+ currentProject,
9749
+ targetProject
9750
+ };
9751
+ }
9752
+ process.stderr.write(
9753
+ `[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
9754
+ `
9755
+ );
9756
+ return {
9757
+ allowed: false,
9758
+ reason: "cross_session_denied",
9759
+ currentProject,
9760
+ targetProject,
9761
+ targetSession: findSessionForProject(targetProject)?.windowName
9762
+ };
9763
+ } catch {
9764
+ return { allowed: true, reason: "no_session" };
9765
+ }
9766
+ }
9767
+ var init_session_scope = __esm({
9768
+ "src/lib/session-scope.ts"() {
9769
+ "use strict";
9770
+ init_session_registry();
9771
+ init_project_name();
9772
+ init_tmux_routing();
9773
+ init_employees();
9774
+ }
9775
+ });
9776
+
9667
9777
  // src/lib/tasks-crud.ts
9668
9778
  var tasks_crud_exports = {};
9669
9779
  __export(tasks_crud_exports, {
@@ -9682,9 +9792,9 @@ __export(tasks_crud_exports, {
9682
9792
  writeCheckpoint: () => writeCheckpoint
9683
9793
  });
9684
9794
  import crypto8 from "crypto";
9685
- import path23 from "path";
9795
+ import path24 from "path";
9686
9796
  import os14 from "os";
9687
- import { execSync as execSync6 } from "child_process";
9797
+ import { execSync as execSync7 } from "child_process";
9688
9798
  import { mkdir as mkdir5, writeFile as writeFile5, appendFile } from "fs/promises";
9689
9799
  import { existsSync as existsSync21, readFileSync as readFileSync18 } from "fs";
9690
9800
  async function writeCheckpoint(input) {
@@ -9806,9 +9916,24 @@ async function createTaskCore(input) {
9806
9916
  const now = (/* @__PURE__ */ new Date()).toISOString();
9807
9917
  const slug = slugify(input.title);
9808
9918
  let earlySessionScope = null;
9919
+ let scopeMismatchWarning;
9809
9920
  try {
9810
9921
  const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
9811
- earlySessionScope = resolveExeSession2();
9922
+ const resolved = resolveExeSession2();
9923
+ if (resolved && input.projectName) {
9924
+ const { getSessionProject: getSessionProject2 } = await Promise.resolve().then(() => (init_session_scope(), session_scope_exports));
9925
+ const sessionProject = getSessionProject2(resolved);
9926
+ if (sessionProject && sessionProject !== input.projectName) {
9927
+ scopeMismatchWarning = `session/project mismatch: session "${resolved}" owns "${sessionProject}" but task targets "${input.projectName}". Routed to default scope.`;
9928
+ process.stderr.write(`[create_task] ${scopeMismatchWarning}
9929
+ `);
9930
+ earlySessionScope = null;
9931
+ } else {
9932
+ earlySessionScope = resolved;
9933
+ }
9934
+ } else {
9935
+ earlySessionScope = resolved;
9936
+ }
9812
9937
  } catch {
9813
9938
  }
9814
9939
  const scope = earlySessionScope ?? "default";
@@ -9859,10 +9984,14 @@ async function createTaskCore(input) {
9859
9984
  ${laneWarning}` : laneWarning;
9860
9985
  }
9861
9986
  }
9987
+ if (scopeMismatchWarning) {
9988
+ warning = warning ? `${warning}
9989
+ ${scopeMismatchWarning}` : scopeMismatchWarning;
9990
+ }
9862
9991
  if (input.baseDir) {
9863
9992
  try {
9864
- await mkdir5(path23.join(input.baseDir, "exe", "output"), { recursive: true });
9865
- await mkdir5(path23.join(input.baseDir, "exe", "research"), { recursive: true });
9993
+ await mkdir5(path24.join(input.baseDir, "exe", "output"), { recursive: true });
9994
+ await mkdir5(path24.join(input.baseDir, "exe", "research"), { recursive: true });
9866
9995
  await ensureArchitectureDoc(input.baseDir, input.projectName);
9867
9996
  await ensureGitignoreExe(input.baseDir);
9868
9997
  } catch {
@@ -9898,9 +10027,9 @@ ${laneWarning}` : laneWarning;
9898
10027
  });
9899
10028
  if (input.baseDir) {
9900
10029
  try {
9901
- const EXE_OS_DIR = path23.join(os14.homedir(), ".exe-os");
9902
- const mdPath = path23.join(EXE_OS_DIR, taskFile);
9903
- const mdDir = path23.dirname(mdPath);
10030
+ const EXE_OS_DIR = path24.join(os14.homedir(), ".exe-os");
10031
+ const mdPath = path24.join(EXE_OS_DIR, taskFile);
10032
+ const mdDir = path24.dirname(mdPath);
9904
10033
  if (!existsSync21(mdDir)) await mkdir5(mdDir, { recursive: true });
9905
10034
  const reviewer = input.reviewer ?? input.assignedBy;
9906
10035
  const mdContent = `# ${input.title}
@@ -10005,14 +10134,14 @@ function isTmuxSessionAlive(identifier) {
10005
10134
  if (!identifier || identifier === "unknown") return true;
10006
10135
  try {
10007
10136
  if (identifier.startsWith("%")) {
10008
- const output = execSync6("tmux list-panes -a -F '#{pane_id}'", {
10137
+ const output = execSync7("tmux list-panes -a -F '#{pane_id}'", {
10009
10138
  timeout: 2e3,
10010
10139
  encoding: "utf8",
10011
10140
  stdio: ["pipe", "pipe", "pipe"]
10012
10141
  });
10013
10142
  return output.split("\n").some((l) => l.trim() === identifier);
10014
10143
  } else {
10015
- execSync6(`tmux has-session -t ${JSON.stringify(identifier)}`, {
10144
+ execSync7(`tmux has-session -t ${JSON.stringify(identifier)}`, {
10016
10145
  timeout: 2e3,
10017
10146
  stdio: ["pipe", "pipe", "pipe"]
10018
10147
  });
@@ -10021,7 +10150,7 @@ function isTmuxSessionAlive(identifier) {
10021
10150
  } catch {
10022
10151
  if (identifier.startsWith("%")) return true;
10023
10152
  try {
10024
- execSync6("tmux list-sessions", {
10153
+ execSync7("tmux list-sessions", {
10025
10154
  timeout: 2e3,
10026
10155
  stdio: ["pipe", "pipe", "pipe"]
10027
10156
  });
@@ -10036,12 +10165,12 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
10036
10165
  if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
10037
10166
  try {
10038
10167
  const since = new Date(taskCreatedAt).toISOString();
10039
- const branch = execSync6(
10168
+ const branch = execSync7(
10040
10169
  "git rev-parse --abbrev-ref HEAD 2>/dev/null",
10041
10170
  { encoding: "utf8", timeout: 3e3 }
10042
10171
  ).trim();
10043
10172
  const branchArg = branch && branch !== "HEAD" ? branch : "";
10044
- const commitCount = execSync6(
10173
+ const commitCount = execSync7(
10045
10174
  `git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
10046
10175
  { encoding: "utf8", timeout: 5e3 }
10047
10176
  ).trim();
@@ -10201,7 +10330,7 @@ async function deleteTaskCore(taskId, _baseDir) {
10201
10330
  return { taskFile, assignedTo, assignedBy, taskSlug };
10202
10331
  }
10203
10332
  async function ensureArchitectureDoc(baseDir, projectName) {
10204
- const archPath = path23.join(baseDir, "exe", "ARCHITECTURE.md");
10333
+ const archPath = path24.join(baseDir, "exe", "ARCHITECTURE.md");
10205
10334
  try {
10206
10335
  if (existsSync21(archPath)) return;
10207
10336
  const template = [
@@ -10236,7 +10365,7 @@ async function ensureArchitectureDoc(baseDir, projectName) {
10236
10365
  }
10237
10366
  }
10238
10367
  async function ensureGitignoreExe(baseDir) {
10239
- const gitignorePath = path23.join(baseDir, ".gitignore");
10368
+ const gitignorePath = path24.join(baseDir, ".gitignore");
10240
10369
  try {
10241
10370
  if (existsSync21(gitignorePath)) {
10242
10371
  const content = readFileSync18(gitignorePath, "utf-8");
@@ -10270,8 +10399,35 @@ var init_tasks_crud = __esm({
10270
10399
  });
10271
10400
 
10272
10401
  // src/lib/tasks-review.ts
10273
- import path24 from "path";
10402
+ var tasks_review_exports = {};
10403
+ __export(tasks_review_exports, {
10404
+ cleanupOrphanedReviews: () => cleanupOrphanedReviews,
10405
+ cleanupReviewFile: () => cleanupReviewFile,
10406
+ countNewPendingReviewsSince: () => countNewPendingReviewsSince,
10407
+ countPendingReviews: () => countPendingReviews,
10408
+ createReviewForCompletedTask: () => createReviewForCompletedTask,
10409
+ formatAge: () => formatAge,
10410
+ getReviewChecklist: () => getReviewChecklist,
10411
+ isStale: () => isStale,
10412
+ listPendingReviews: () => listPendingReviews
10413
+ });
10414
+ import path25 from "path";
10274
10415
  import { existsSync as existsSync22, readdirSync as readdirSync5, unlinkSync as unlinkSync6 } from "fs";
10416
+ function formatAge(isoTimestamp) {
10417
+ if (!isoTimestamp) return "";
10418
+ const ms = Date.now() - new Date(isoTimestamp).getTime();
10419
+ if (ms < 0) return "just now";
10420
+ const minutes = Math.floor(ms / 6e4);
10421
+ if (minutes < 60) return `${minutes}m ago`;
10422
+ const hours = Math.floor(minutes / 60);
10423
+ if (hours < 24) return `${hours}h ago`;
10424
+ const days = Math.floor(hours / 24);
10425
+ return `${days}d ago`;
10426
+ }
10427
+ function isStale(isoTimestamp) {
10428
+ if (!isoTimestamp) return false;
10429
+ return Date.now() - new Date(isoTimestamp).getTime() > 24 * 60 * 60 * 1e3;
10430
+ }
10275
10431
  async function countPendingReviews(sessionScope) {
10276
10432
  const client = getClient();
10277
10433
  const scope = strictSessionScopeFilter(
@@ -10392,6 +10548,95 @@ function getReviewChecklist(role, agent, taskSlug) {
10392
10548
  ]
10393
10549
  };
10394
10550
  }
10551
+ async function createReviewForCompletedTask(row, result, _baseDir, now) {
10552
+ const taskFile = String(row.task_file);
10553
+ const employees = await loadEmployees();
10554
+ const coordinatorName = getCoordinatorName(employees);
10555
+ if (isCoordinatorName(String(row.assigned_to), employees)) return;
10556
+ if (String(row.title).startsWith("Review:")) return;
10557
+ const fileName = taskFile.split("/").pop() ?? "";
10558
+ if (fileName.startsWith("review-") && String(row.assigned_by) === "system") return;
10559
+ if (fileName.startsWith("review-") && String(row.assigned_to) === "system") return;
10560
+ const client = getClient();
10561
+ const agent = String(row.assigned_to);
10562
+ const rawReviewer = row.reviewer || row.assigned_by;
10563
+ const reviewer = rawReviewer ? String(rawReviewer) : coordinatorName;
10564
+ const currentStatus = String(row.status ?? "");
10565
+ if (currentStatus === "done") return;
10566
+ const existingResult = String(row.result ?? "");
10567
+ if (existingResult.includes("## Review notes")) return;
10568
+ let reviewerRole = "unknown";
10569
+ try {
10570
+ const emp = getEmployee(employees, reviewer);
10571
+ if (emp) reviewerRole = emp.role;
10572
+ } catch {
10573
+ }
10574
+ const taskTitle = String(row.title);
10575
+ const taskSlug = taskFile.split("/").pop()?.replace(".md", "") ?? "unknown";
10576
+ const { lens, checklist } = getReviewChecklist(reviewerRole, agent, taskSlug);
10577
+ process.stderr.write(
10578
+ `[review] Annotating "${taskTitle}" for review by ${reviewer} (${reviewerRole})
10579
+ `
10580
+ );
10581
+ const reviewNotes = [
10582
+ `
10583
+ ---
10584
+ ## Review notes`,
10585
+ `Review lens: ${lens}`,
10586
+ `Reviewer: **${reviewer}** (${reviewerRole})`,
10587
+ "",
10588
+ "### Checklist",
10589
+ ...checklist,
10590
+ "",
10591
+ "### Verdict",
10592
+ "- **Approved:** mark this task as done",
10593
+ "- **Needs work:** re-open with notes"
10594
+ ].join("\n");
10595
+ const originalTaskId = String(row.id);
10596
+ const updatedResult = (result ?? "No result summary provided") + reviewNotes;
10597
+ await client.execute({
10598
+ sql: `UPDATE tasks SET result = ?, status = 'needs_review', updated_at = ?
10599
+ WHERE id = ?`,
10600
+ args: [updatedResult, now, originalTaskId]
10601
+ });
10602
+ orgBus.emit({
10603
+ type: "review_created",
10604
+ reviewId: originalTaskId,
10605
+ employee: agent,
10606
+ reviewer,
10607
+ timestamp: now
10608
+ });
10609
+ await writeNotification({
10610
+ agentId: agent,
10611
+ agentRole: String(row.assigned_to),
10612
+ event: "task_complete",
10613
+ project: String(row.project_name),
10614
+ summary: `completed "${taskTitle}" \u2014 ready for review`,
10615
+ taskFile
10616
+ });
10617
+ const originalPriority = String(row.priority).toLowerCase();
10618
+ const autoApprove = originalPriority === "p2" && result?.toLowerCase().includes("tests pass");
10619
+ if (!autoApprove) {
10620
+ try {
10621
+ const key = getSessionKey();
10622
+ const exeSession = getParentExe(key);
10623
+ if (exeSession) {
10624
+ sendIntercom(exeSession);
10625
+ }
10626
+ } catch {
10627
+ }
10628
+ }
10629
+ if (autoApprove) {
10630
+ process.stderr.write(
10631
+ `[review] Auto-approving "${taskTitle}" (P2 + tests pass)
10632
+ `
10633
+ );
10634
+ await client.execute({
10635
+ sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE id = ?",
10636
+ args: [now, originalTaskId]
10637
+ });
10638
+ }
10639
+ }
10395
10640
  async function cleanupReviewFile(row, taskFile, _baseDir) {
10396
10641
  if (String(row.assigned_by) !== "system" || !taskFile.includes("review-")) return;
10397
10642
  try {
@@ -10436,11 +10681,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
10436
10681
  );
10437
10682
  }
10438
10683
  try {
10439
- const cacheDir = path24.join(EXE_AI_DIR, "session-cache");
10684
+ const cacheDir = path25.join(EXE_AI_DIR, "session-cache");
10440
10685
  if (existsSync22(cacheDir)) {
10441
10686
  for (const f of readdirSync5(cacheDir)) {
10442
10687
  if (f.startsWith("review-notified-")) {
10443
- unlinkSync6(path24.join(cacheDir, f));
10688
+ unlinkSync6(path25.join(cacheDir, f));
10444
10689
  }
10445
10690
  }
10446
10691
  }
@@ -10462,7 +10707,7 @@ var init_tasks_review = __esm({
10462
10707
  });
10463
10708
 
10464
10709
  // src/lib/tasks-chain.ts
10465
- import path25 from "path";
10710
+ import path26 from "path";
10466
10711
  import { readFile as readFile5, writeFile as writeFile6 } from "fs/promises";
10467
10712
  async function cascadeUnblock(taskId, baseDir, now) {
10468
10713
  const client = getClient();
@@ -10479,7 +10724,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
10479
10724
  });
10480
10725
  for (const ur of unblockedRows.rows) {
10481
10726
  try {
10482
- const ubFile = path25.join(baseDir, String(ur.task_file));
10727
+ const ubFile = path26.join(baseDir, String(ur.task_file));
10483
10728
  let ubContent = await readFile5(ubFile, "utf-8");
10484
10729
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
10485
10730
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -10546,110 +10791,6 @@ var init_tasks_chain = __esm({
10546
10791
  }
10547
10792
  });
10548
10793
 
10549
- // src/lib/project-name.ts
10550
- import { execSync as execSync7 } from "child_process";
10551
- import path26 from "path";
10552
- function getProjectName(cwd2) {
10553
- const dir = cwd2 ?? process.cwd();
10554
- if (_cached2 && _cachedCwd === dir) return _cached2;
10555
- try {
10556
- let repoRoot;
10557
- try {
10558
- const gitCommonDir = execSync7("git rev-parse --path-format=absolute --git-common-dir", {
10559
- cwd: dir,
10560
- encoding: "utf8",
10561
- timeout: 2e3,
10562
- stdio: ["pipe", "pipe", "pipe"]
10563
- }).trim();
10564
- repoRoot = path26.dirname(gitCommonDir);
10565
- } catch {
10566
- repoRoot = execSync7("git rev-parse --show-toplevel", {
10567
- cwd: dir,
10568
- encoding: "utf8",
10569
- timeout: 2e3,
10570
- stdio: ["pipe", "pipe", "pipe"]
10571
- }).trim();
10572
- }
10573
- _cached2 = path26.basename(repoRoot);
10574
- _cachedCwd = dir;
10575
- return _cached2;
10576
- } catch {
10577
- _cached2 = path26.basename(dir);
10578
- _cachedCwd = dir;
10579
- return _cached2;
10580
- }
10581
- }
10582
- var _cached2, _cachedCwd;
10583
- var init_project_name = __esm({
10584
- "src/lib/project-name.ts"() {
10585
- "use strict";
10586
- _cached2 = null;
10587
- _cachedCwd = null;
10588
- }
10589
- });
10590
-
10591
- // src/lib/session-scope.ts
10592
- var session_scope_exports = {};
10593
- __export(session_scope_exports, {
10594
- assertSessionScope: () => assertSessionScope,
10595
- findSessionForProject: () => findSessionForProject,
10596
- getSessionProject: () => getSessionProject
10597
- });
10598
- function getSessionProject(sessionName) {
10599
- const sessions = listSessions();
10600
- const entry = sessions.find((s) => s.windowName === sessionName);
10601
- if (!entry) return null;
10602
- const parts = entry.projectDir.split("/").filter(Boolean);
10603
- return parts[parts.length - 1] ?? null;
10604
- }
10605
- function findSessionForProject(projectName) {
10606
- const sessions = listSessions();
10607
- for (const s of sessions) {
10608
- const proj = s.projectDir.split("/").filter(Boolean).pop();
10609
- if (proj === projectName && isCoordinatorName(s.agentId)) return s;
10610
- }
10611
- return null;
10612
- }
10613
- function assertSessionScope(actionType, targetProject) {
10614
- try {
10615
- const currentProject = getProjectName();
10616
- const exeSession = resolveExeSession();
10617
- if (!exeSession) {
10618
- return { allowed: true, reason: "no_session" };
10619
- }
10620
- if (currentProject === targetProject) {
10621
- return {
10622
- allowed: true,
10623
- reason: "same_session",
10624
- currentProject,
10625
- targetProject
10626
- };
10627
- }
10628
- process.stderr.write(
10629
- `[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
10630
- `
10631
- );
10632
- return {
10633
- allowed: false,
10634
- reason: "cross_session_denied",
10635
- currentProject,
10636
- targetProject,
10637
- targetSession: findSessionForProject(targetProject)?.windowName
10638
- };
10639
- } catch {
10640
- return { allowed: true, reason: "no_session" };
10641
- }
10642
- }
10643
- var init_session_scope = __esm({
10644
- "src/lib/session-scope.ts"() {
10645
- "use strict";
10646
- init_session_registry();
10647
- init_project_name();
10648
- init_tmux_routing();
10649
- init_employees();
10650
- }
10651
- });
10652
-
10653
10794
  // src/lib/tasks-notify.ts
10654
10795
  async function dispatchTaskToEmployee(input) {
10655
10796
  if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
@@ -11859,15 +12000,17 @@ function sendIntercom(targetSession) {
11859
12000
  logIntercom(`QUEUED \u2192 ${targetSession} (session busy)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
11860
12001
  return "queued";
11861
12002
  }
11862
- try {
11863
- const rawAgent = targetSession.split("-")[0] ?? targetSession;
11864
- const agent = baseAgentName(rawAgent);
11865
- const markerPath = path28.join(SESSION_CACHE, `current-task-${agent}.json`);
11866
- if (existsSync23(markerPath)) {
11867
- logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
11868
- return "debounced";
12003
+ if (sessionState !== "idle") {
12004
+ try {
12005
+ const rawAgent = targetSession.split("-")[0] ?? targetSession;
12006
+ const agent = baseAgentName(rawAgent);
12007
+ const markerPath = path28.join(SESSION_CACHE, `current-task-${agent}.json`);
12008
+ if (existsSync23(markerPath)) {
12009
+ logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker + not idle \u2014 will auto-chain)`);
12010
+ return "debounced";
12011
+ }
12012
+ } catch {
11869
12013
  }
11870
- } catch {
11871
12014
  }
11872
12015
  try {
11873
12016
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
@@ -11938,6 +12081,24 @@ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitl
11938
12081
  try {
11939
12082
  const sessions = transport.listSessions();
11940
12083
  if (!sessions.includes(coordinatorSession)) return false;
12084
+ try {
12085
+ const { countPendingReviews: countPendingReviews2 } = (init_tasks_review(), __toCommonJS(tasks_review_exports));
12086
+ const pending = countPendingReviews2(coordinatorSession);
12087
+ if (pending instanceof Promise) {
12088
+ pending.then((count) => {
12089
+ if (count > 0) {
12090
+ execSync8(
12091
+ `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
12092
+ { timeout: 3e3 }
12093
+ );
12094
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending)`);
12095
+ }
12096
+ }).catch(() => {
12097
+ });
12098
+ return true;
12099
+ }
12100
+ } catch {
12101
+ }
11941
12102
  execSync8(
11942
12103
  `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
11943
12104
  { timeout: 3e3 }