@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.
@@ -4755,11 +4755,124 @@ var init_session_kill_telemetry = __esm({
4755
4755
  }
4756
4756
  });
4757
4757
 
4758
+ // src/lib/project-name.ts
4759
+ var project_name_exports = {};
4760
+ __export(project_name_exports, {
4761
+ _resetCache: () => _resetCache,
4762
+ getProjectName: () => getProjectName
4763
+ });
4764
+ import { execSync as execSync5 } from "child_process";
4765
+ import path14 from "path";
4766
+ function getProjectName(cwd) {
4767
+ const dir = cwd ?? process.cwd();
4768
+ if (_cached2 && _cachedCwd === dir) return _cached2;
4769
+ try {
4770
+ let repoRoot;
4771
+ try {
4772
+ const gitCommonDir = execSync5("git rev-parse --path-format=absolute --git-common-dir", {
4773
+ cwd: dir,
4774
+ encoding: "utf8",
4775
+ timeout: 2e3,
4776
+ stdio: ["pipe", "pipe", "pipe"]
4777
+ }).trim();
4778
+ repoRoot = path14.dirname(gitCommonDir);
4779
+ } catch {
4780
+ repoRoot = execSync5("git rev-parse --show-toplevel", {
4781
+ cwd: dir,
4782
+ encoding: "utf8",
4783
+ timeout: 2e3,
4784
+ stdio: ["pipe", "pipe", "pipe"]
4785
+ }).trim();
4786
+ }
4787
+ _cached2 = path14.basename(repoRoot);
4788
+ _cachedCwd = dir;
4789
+ return _cached2;
4790
+ } catch {
4791
+ _cached2 = path14.basename(dir);
4792
+ _cachedCwd = dir;
4793
+ return _cached2;
4794
+ }
4795
+ }
4796
+ function _resetCache() {
4797
+ _cached2 = null;
4798
+ _cachedCwd = null;
4799
+ }
4800
+ var _cached2, _cachedCwd;
4801
+ var init_project_name = __esm({
4802
+ "src/lib/project-name.ts"() {
4803
+ "use strict";
4804
+ _cached2 = null;
4805
+ _cachedCwd = null;
4806
+ }
4807
+ });
4808
+
4809
+ // src/lib/session-scope.ts
4810
+ var session_scope_exports = {};
4811
+ __export(session_scope_exports, {
4812
+ assertSessionScope: () => assertSessionScope,
4813
+ findSessionForProject: () => findSessionForProject,
4814
+ getSessionProject: () => getSessionProject
4815
+ });
4816
+ function getSessionProject(sessionName) {
4817
+ const sessions = listSessions();
4818
+ const entry = sessions.find((s) => s.windowName === sessionName);
4819
+ if (!entry) return null;
4820
+ const parts = entry.projectDir.split("/").filter(Boolean);
4821
+ return parts[parts.length - 1] ?? null;
4822
+ }
4823
+ function findSessionForProject(projectName) {
4824
+ const sessions = listSessions();
4825
+ for (const s of sessions) {
4826
+ const proj = s.projectDir.split("/").filter(Boolean).pop();
4827
+ if (proj === projectName && isCoordinatorName(s.agentId)) return s;
4828
+ }
4829
+ return null;
4830
+ }
4831
+ function assertSessionScope(actionType, targetProject) {
4832
+ try {
4833
+ const currentProject = getProjectName();
4834
+ const exeSession = resolveExeSession();
4835
+ if (!exeSession) {
4836
+ return { allowed: true, reason: "no_session" };
4837
+ }
4838
+ if (currentProject === targetProject) {
4839
+ return {
4840
+ allowed: true,
4841
+ reason: "same_session",
4842
+ currentProject,
4843
+ targetProject
4844
+ };
4845
+ }
4846
+ process.stderr.write(
4847
+ `[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
4848
+ `
4849
+ );
4850
+ return {
4851
+ allowed: false,
4852
+ reason: "cross_session_denied",
4853
+ currentProject,
4854
+ targetProject,
4855
+ targetSession: findSessionForProject(targetProject)?.windowName
4856
+ };
4857
+ } catch {
4858
+ return { allowed: true, reason: "no_session" };
4859
+ }
4860
+ }
4861
+ var init_session_scope = __esm({
4862
+ "src/lib/session-scope.ts"() {
4863
+ "use strict";
4864
+ init_session_registry();
4865
+ init_project_name();
4866
+ init_tmux_routing();
4867
+ init_employees();
4868
+ }
4869
+ });
4870
+
4758
4871
  // src/lib/tasks-crud.ts
4759
4872
  import crypto4 from "crypto";
4760
- import path14 from "path";
4873
+ import path15 from "path";
4761
4874
  import os10 from "os";
4762
- import { execSync as execSync5 } from "child_process";
4875
+ import { execSync as execSync6 } from "child_process";
4763
4876
  import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
4764
4877
  import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
4765
4878
  async function writeCheckpoint(input) {
@@ -4881,9 +4994,24 @@ async function createTaskCore(input) {
4881
4994
  const now = (/* @__PURE__ */ new Date()).toISOString();
4882
4995
  const slug = slugify(input.title);
4883
4996
  let earlySessionScope = null;
4997
+ let scopeMismatchWarning;
4884
4998
  try {
4885
4999
  const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
4886
- earlySessionScope = resolveExeSession2();
5000
+ const resolved = resolveExeSession2();
5001
+ if (resolved && input.projectName) {
5002
+ const { getSessionProject: getSessionProject2 } = await Promise.resolve().then(() => (init_session_scope(), session_scope_exports));
5003
+ const sessionProject = getSessionProject2(resolved);
5004
+ if (sessionProject && sessionProject !== input.projectName) {
5005
+ scopeMismatchWarning = `session/project mismatch: session "${resolved}" owns "${sessionProject}" but task targets "${input.projectName}". Routed to default scope.`;
5006
+ process.stderr.write(`[create_task] ${scopeMismatchWarning}
5007
+ `);
5008
+ earlySessionScope = null;
5009
+ } else {
5010
+ earlySessionScope = resolved;
5011
+ }
5012
+ } else {
5013
+ earlySessionScope = resolved;
5014
+ }
4887
5015
  } catch {
4888
5016
  }
4889
5017
  const scope = earlySessionScope ?? "default";
@@ -4934,10 +5062,14 @@ async function createTaskCore(input) {
4934
5062
  ${laneWarning}` : laneWarning;
4935
5063
  }
4936
5064
  }
5065
+ if (scopeMismatchWarning) {
5066
+ warning = warning ? `${warning}
5067
+ ${scopeMismatchWarning}` : scopeMismatchWarning;
5068
+ }
4937
5069
  if (input.baseDir) {
4938
5070
  try {
4939
- await mkdir4(path14.join(input.baseDir, "exe", "output"), { recursive: true });
4940
- await mkdir4(path14.join(input.baseDir, "exe", "research"), { recursive: true });
5071
+ await mkdir4(path15.join(input.baseDir, "exe", "output"), { recursive: true });
5072
+ await mkdir4(path15.join(input.baseDir, "exe", "research"), { recursive: true });
4941
5073
  await ensureArchitectureDoc(input.baseDir, input.projectName);
4942
5074
  await ensureGitignoreExe(input.baseDir);
4943
5075
  } catch {
@@ -4973,9 +5105,9 @@ ${laneWarning}` : laneWarning;
4973
5105
  });
4974
5106
  if (input.baseDir) {
4975
5107
  try {
4976
- const EXE_OS_DIR = path14.join(os10.homedir(), ".exe-os");
4977
- const mdPath = path14.join(EXE_OS_DIR, taskFile);
4978
- const mdDir = path14.dirname(mdPath);
5108
+ const EXE_OS_DIR = path15.join(os10.homedir(), ".exe-os");
5109
+ const mdPath = path15.join(EXE_OS_DIR, taskFile);
5110
+ const mdDir = path15.dirname(mdPath);
4979
5111
  if (!existsSync14(mdDir)) await mkdir4(mdDir, { recursive: true });
4980
5112
  const reviewer = input.reviewer ?? input.assignedBy;
4981
5113
  const mdContent = `# ${input.title}
@@ -5080,14 +5212,14 @@ function isTmuxSessionAlive(identifier) {
5080
5212
  if (!identifier || identifier === "unknown") return true;
5081
5213
  try {
5082
5214
  if (identifier.startsWith("%")) {
5083
- const output = execSync5("tmux list-panes -a -F '#{pane_id}'", {
5215
+ const output = execSync6("tmux list-panes -a -F '#{pane_id}'", {
5084
5216
  timeout: 2e3,
5085
5217
  encoding: "utf8",
5086
5218
  stdio: ["pipe", "pipe", "pipe"]
5087
5219
  });
5088
5220
  return output.split("\n").some((l) => l.trim() === identifier);
5089
5221
  } else {
5090
- execSync5(`tmux has-session -t ${JSON.stringify(identifier)}`, {
5222
+ execSync6(`tmux has-session -t ${JSON.stringify(identifier)}`, {
5091
5223
  timeout: 2e3,
5092
5224
  stdio: ["pipe", "pipe", "pipe"]
5093
5225
  });
@@ -5096,7 +5228,7 @@ function isTmuxSessionAlive(identifier) {
5096
5228
  } catch {
5097
5229
  if (identifier.startsWith("%")) return true;
5098
5230
  try {
5099
- execSync5("tmux list-sessions", {
5231
+ execSync6("tmux list-sessions", {
5100
5232
  timeout: 2e3,
5101
5233
  stdio: ["pipe", "pipe", "pipe"]
5102
5234
  });
@@ -5111,12 +5243,12 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
5111
5243
  if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
5112
5244
  try {
5113
5245
  const since = new Date(taskCreatedAt).toISOString();
5114
- const branch = execSync5(
5246
+ const branch = execSync6(
5115
5247
  "git rev-parse --abbrev-ref HEAD 2>/dev/null",
5116
5248
  { encoding: "utf8", timeout: 3e3 }
5117
5249
  ).trim();
5118
5250
  const branchArg = branch && branch !== "HEAD" ? branch : "";
5119
- const commitCount = execSync5(
5251
+ const commitCount = execSync6(
5120
5252
  `git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
5121
5253
  { encoding: "utf8", timeout: 5e3 }
5122
5254
  ).trim();
@@ -5276,7 +5408,7 @@ async function deleteTaskCore(taskId, _baseDir) {
5276
5408
  return { taskFile, assignedTo, assignedBy, taskSlug };
5277
5409
  }
5278
5410
  async function ensureArchitectureDoc(baseDir, projectName) {
5279
- const archPath = path14.join(baseDir, "exe", "ARCHITECTURE.md");
5411
+ const archPath = path15.join(baseDir, "exe", "ARCHITECTURE.md");
5280
5412
  try {
5281
5413
  if (existsSync14(archPath)) return;
5282
5414
  const template = [
@@ -5311,7 +5443,7 @@ async function ensureArchitectureDoc(baseDir, projectName) {
5311
5443
  }
5312
5444
  }
5313
5445
  async function ensureGitignoreExe(baseDir) {
5314
- const gitignorePath = path14.join(baseDir, ".gitignore");
5446
+ const gitignorePath = path15.join(baseDir, ".gitignore");
5315
5447
  try {
5316
5448
  if (existsSync14(gitignorePath)) {
5317
5449
  const content = readFileSync11(gitignorePath, "utf-8");
@@ -5345,8 +5477,35 @@ var init_tasks_crud = __esm({
5345
5477
  });
5346
5478
 
5347
5479
  // src/lib/tasks-review.ts
5348
- import path15 from "path";
5480
+ var tasks_review_exports = {};
5481
+ __export(tasks_review_exports, {
5482
+ cleanupOrphanedReviews: () => cleanupOrphanedReviews,
5483
+ cleanupReviewFile: () => cleanupReviewFile,
5484
+ countNewPendingReviewsSince: () => countNewPendingReviewsSince,
5485
+ countPendingReviews: () => countPendingReviews,
5486
+ createReviewForCompletedTask: () => createReviewForCompletedTask,
5487
+ formatAge: () => formatAge,
5488
+ getReviewChecklist: () => getReviewChecklist,
5489
+ isStale: () => isStale,
5490
+ listPendingReviews: () => listPendingReviews
5491
+ });
5492
+ import path16 from "path";
5349
5493
  import { existsSync as existsSync15, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
5494
+ function formatAge(isoTimestamp) {
5495
+ if (!isoTimestamp) return "";
5496
+ const ms = Date.now() - new Date(isoTimestamp).getTime();
5497
+ if (ms < 0) return "just now";
5498
+ const minutes = Math.floor(ms / 6e4);
5499
+ if (minutes < 60) return `${minutes}m ago`;
5500
+ const hours = Math.floor(minutes / 60);
5501
+ if (hours < 24) return `${hours}h ago`;
5502
+ const days = Math.floor(hours / 24);
5503
+ return `${days}d ago`;
5504
+ }
5505
+ function isStale(isoTimestamp) {
5506
+ if (!isoTimestamp) return false;
5507
+ return Date.now() - new Date(isoTimestamp).getTime() > 24 * 60 * 60 * 1e3;
5508
+ }
5350
5509
  async function countPendingReviews(sessionScope) {
5351
5510
  const client = getClient();
5352
5511
  const scope = strictSessionScopeFilter(
@@ -5467,6 +5626,95 @@ function getReviewChecklist(role, agent, taskSlug) {
5467
5626
  ]
5468
5627
  };
5469
5628
  }
5629
+ async function createReviewForCompletedTask(row, result, _baseDir, now) {
5630
+ const taskFile = String(row.task_file);
5631
+ const employees = await loadEmployees();
5632
+ const coordinatorName = getCoordinatorName(employees);
5633
+ if (isCoordinatorName(String(row.assigned_to), employees)) return;
5634
+ if (String(row.title).startsWith("Review:")) return;
5635
+ const fileName = taskFile.split("/").pop() ?? "";
5636
+ if (fileName.startsWith("review-") && String(row.assigned_by) === "system") return;
5637
+ if (fileName.startsWith("review-") && String(row.assigned_to) === "system") return;
5638
+ const client = getClient();
5639
+ const agent = String(row.assigned_to);
5640
+ const rawReviewer = row.reviewer || row.assigned_by;
5641
+ const reviewer = rawReviewer ? String(rawReviewer) : coordinatorName;
5642
+ const currentStatus = String(row.status ?? "");
5643
+ if (currentStatus === "done") return;
5644
+ const existingResult = String(row.result ?? "");
5645
+ if (existingResult.includes("## Review notes")) return;
5646
+ let reviewerRole = "unknown";
5647
+ try {
5648
+ const emp = getEmployee(employees, reviewer);
5649
+ if (emp) reviewerRole = emp.role;
5650
+ } catch {
5651
+ }
5652
+ const taskTitle = String(row.title);
5653
+ const taskSlug = taskFile.split("/").pop()?.replace(".md", "") ?? "unknown";
5654
+ const { lens, checklist } = getReviewChecklist(reviewerRole, agent, taskSlug);
5655
+ process.stderr.write(
5656
+ `[review] Annotating "${taskTitle}" for review by ${reviewer} (${reviewerRole})
5657
+ `
5658
+ );
5659
+ const reviewNotes = [
5660
+ `
5661
+ ---
5662
+ ## Review notes`,
5663
+ `Review lens: ${lens}`,
5664
+ `Reviewer: **${reviewer}** (${reviewerRole})`,
5665
+ "",
5666
+ "### Checklist",
5667
+ ...checklist,
5668
+ "",
5669
+ "### Verdict",
5670
+ "- **Approved:** mark this task as done",
5671
+ "- **Needs work:** re-open with notes"
5672
+ ].join("\n");
5673
+ const originalTaskId = String(row.id);
5674
+ const updatedResult = (result ?? "No result summary provided") + reviewNotes;
5675
+ await client.execute({
5676
+ sql: `UPDATE tasks SET result = ?, status = 'needs_review', updated_at = ?
5677
+ WHERE id = ?`,
5678
+ args: [updatedResult, now, originalTaskId]
5679
+ });
5680
+ orgBus.emit({
5681
+ type: "review_created",
5682
+ reviewId: originalTaskId,
5683
+ employee: agent,
5684
+ reviewer,
5685
+ timestamp: now
5686
+ });
5687
+ await writeNotification({
5688
+ agentId: agent,
5689
+ agentRole: String(row.assigned_to),
5690
+ event: "task_complete",
5691
+ project: String(row.project_name),
5692
+ summary: `completed "${taskTitle}" \u2014 ready for review`,
5693
+ taskFile
5694
+ });
5695
+ const originalPriority = String(row.priority).toLowerCase();
5696
+ const autoApprove = originalPriority === "p2" && result?.toLowerCase().includes("tests pass");
5697
+ if (!autoApprove) {
5698
+ try {
5699
+ const key = getSessionKey();
5700
+ const exeSession = getParentExe(key);
5701
+ if (exeSession) {
5702
+ sendIntercom(exeSession);
5703
+ }
5704
+ } catch {
5705
+ }
5706
+ }
5707
+ if (autoApprove) {
5708
+ process.stderr.write(
5709
+ `[review] Auto-approving "${taskTitle}" (P2 + tests pass)
5710
+ `
5711
+ );
5712
+ await client.execute({
5713
+ sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE id = ?",
5714
+ args: [now, originalTaskId]
5715
+ });
5716
+ }
5717
+ }
5470
5718
  async function cleanupReviewFile(row, taskFile, _baseDir) {
5471
5719
  if (String(row.assigned_by) !== "system" || !taskFile.includes("review-")) return;
5472
5720
  try {
@@ -5511,11 +5759,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
5511
5759
  );
5512
5760
  }
5513
5761
  try {
5514
- const cacheDir = path15.join(EXE_AI_DIR, "session-cache");
5762
+ const cacheDir = path16.join(EXE_AI_DIR, "session-cache");
5515
5763
  if (existsSync15(cacheDir)) {
5516
5764
  for (const f of readdirSync3(cacheDir)) {
5517
5765
  if (f.startsWith("review-notified-")) {
5518
- unlinkSync4(path15.join(cacheDir, f));
5766
+ unlinkSync4(path16.join(cacheDir, f));
5519
5767
  }
5520
5768
  }
5521
5769
  }
@@ -5537,7 +5785,7 @@ var init_tasks_review = __esm({
5537
5785
  });
5538
5786
 
5539
5787
  // src/lib/tasks-chain.ts
5540
- import path16 from "path";
5788
+ import path17 from "path";
5541
5789
  import { readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
5542
5790
  async function cascadeUnblock(taskId, baseDir, now) {
5543
5791
  const client = getClient();
@@ -5554,7 +5802,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
5554
5802
  });
5555
5803
  for (const ur of unblockedRows.rows) {
5556
5804
  try {
5557
- const ubFile = path16.join(baseDir, String(ur.task_file));
5805
+ const ubFile = path17.join(baseDir, String(ur.task_file));
5558
5806
  let ubContent = await readFile4(ubFile, "utf-8");
5559
5807
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
5560
5808
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -5621,119 +5869,6 @@ var init_tasks_chain = __esm({
5621
5869
  }
5622
5870
  });
5623
5871
 
5624
- // src/lib/project-name.ts
5625
- var project_name_exports = {};
5626
- __export(project_name_exports, {
5627
- _resetCache: () => _resetCache,
5628
- getProjectName: () => getProjectName
5629
- });
5630
- import { execSync as execSync6 } from "child_process";
5631
- import path17 from "path";
5632
- function getProjectName(cwd) {
5633
- const dir = cwd ?? process.cwd();
5634
- if (_cached2 && _cachedCwd === dir) return _cached2;
5635
- try {
5636
- let repoRoot;
5637
- try {
5638
- const gitCommonDir = execSync6("git rev-parse --path-format=absolute --git-common-dir", {
5639
- cwd: dir,
5640
- encoding: "utf8",
5641
- timeout: 2e3,
5642
- stdio: ["pipe", "pipe", "pipe"]
5643
- }).trim();
5644
- repoRoot = path17.dirname(gitCommonDir);
5645
- } catch {
5646
- repoRoot = execSync6("git rev-parse --show-toplevel", {
5647
- cwd: dir,
5648
- encoding: "utf8",
5649
- timeout: 2e3,
5650
- stdio: ["pipe", "pipe", "pipe"]
5651
- }).trim();
5652
- }
5653
- _cached2 = path17.basename(repoRoot);
5654
- _cachedCwd = dir;
5655
- return _cached2;
5656
- } catch {
5657
- _cached2 = path17.basename(dir);
5658
- _cachedCwd = dir;
5659
- return _cached2;
5660
- }
5661
- }
5662
- function _resetCache() {
5663
- _cached2 = null;
5664
- _cachedCwd = null;
5665
- }
5666
- var _cached2, _cachedCwd;
5667
- var init_project_name = __esm({
5668
- "src/lib/project-name.ts"() {
5669
- "use strict";
5670
- _cached2 = null;
5671
- _cachedCwd = null;
5672
- }
5673
- });
5674
-
5675
- // src/lib/session-scope.ts
5676
- var session_scope_exports = {};
5677
- __export(session_scope_exports, {
5678
- assertSessionScope: () => assertSessionScope,
5679
- findSessionForProject: () => findSessionForProject,
5680
- getSessionProject: () => getSessionProject
5681
- });
5682
- function getSessionProject(sessionName) {
5683
- const sessions = listSessions();
5684
- const entry = sessions.find((s) => s.windowName === sessionName);
5685
- if (!entry) return null;
5686
- const parts = entry.projectDir.split("/").filter(Boolean);
5687
- return parts[parts.length - 1] ?? null;
5688
- }
5689
- function findSessionForProject(projectName) {
5690
- const sessions = listSessions();
5691
- for (const s of sessions) {
5692
- const proj = s.projectDir.split("/").filter(Boolean).pop();
5693
- if (proj === projectName && isCoordinatorName(s.agentId)) return s;
5694
- }
5695
- return null;
5696
- }
5697
- function assertSessionScope(actionType, targetProject) {
5698
- try {
5699
- const currentProject = getProjectName();
5700
- const exeSession = resolveExeSession();
5701
- if (!exeSession) {
5702
- return { allowed: true, reason: "no_session" };
5703
- }
5704
- if (currentProject === targetProject) {
5705
- return {
5706
- allowed: true,
5707
- reason: "same_session",
5708
- currentProject,
5709
- targetProject
5710
- };
5711
- }
5712
- process.stderr.write(
5713
- `[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
5714
- `
5715
- );
5716
- return {
5717
- allowed: false,
5718
- reason: "cross_session_denied",
5719
- currentProject,
5720
- targetProject,
5721
- targetSession: findSessionForProject(targetProject)?.windowName
5722
- };
5723
- } catch {
5724
- return { allowed: true, reason: "no_session" };
5725
- }
5726
- }
5727
- var init_session_scope = __esm({
5728
- "src/lib/session-scope.ts"() {
5729
- "use strict";
5730
- init_session_registry();
5731
- init_project_name();
5732
- init_tmux_routing();
5733
- init_employees();
5734
- }
5735
- });
5736
-
5737
5872
  // src/lib/tasks-notify.ts
5738
5873
  async function dispatchTaskToEmployee(input) {
5739
5874
  if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
@@ -6943,15 +7078,17 @@ function sendIntercom(targetSession) {
6943
7078
  logIntercom(`QUEUED \u2192 ${targetSession} (session busy)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
6944
7079
  return "queued";
6945
7080
  }
6946
- try {
6947
- const rawAgent = targetSession.split("-")[0] ?? targetSession;
6948
- const agent = baseAgentName(rawAgent);
6949
- const markerPath = path19.join(SESSION_CACHE, `current-task-${agent}.json`);
6950
- if (existsSync16(markerPath)) {
6951
- logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
6952
- return "debounced";
7081
+ if (sessionState !== "idle") {
7082
+ try {
7083
+ const rawAgent = targetSession.split("-")[0] ?? targetSession;
7084
+ const agent = baseAgentName(rawAgent);
7085
+ const markerPath = path19.join(SESSION_CACHE, `current-task-${agent}.json`);
7086
+ if (existsSync16(markerPath)) {
7087
+ logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker + not idle \u2014 will auto-chain)`);
7088
+ return "debounced";
7089
+ }
7090
+ } catch {
6953
7091
  }
6954
- } catch {
6955
7092
  }
6956
7093
  try {
6957
7094
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
@@ -7022,6 +7159,24 @@ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitl
7022
7159
  try {
7023
7160
  const sessions = transport.listSessions();
7024
7161
  if (!sessions.includes(coordinatorSession)) return false;
7162
+ try {
7163
+ const { countPendingReviews: countPendingReviews2 } = (init_tasks_review(), __toCommonJS(tasks_review_exports));
7164
+ const pending = countPendingReviews2(coordinatorSession);
7165
+ if (pending instanceof Promise) {
7166
+ pending.then((count) => {
7167
+ if (count > 0) {
7168
+ execSync7(
7169
+ `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
7170
+ { timeout: 3e3 }
7171
+ );
7172
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending)`);
7173
+ }
7174
+ }).catch(() => {
7175
+ });
7176
+ return true;
7177
+ }
7178
+ } catch {
7179
+ }
7025
7180
  execSync7(
7026
7181
  `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
7027
7182
  { timeout: 3e3 }
@@ -8211,6 +8366,12 @@ async function cloudSync(config) {
8211
8366
  pulled = pullResult.records.length;
8212
8367
  }
8213
8368
  }
8369
+ if (pulled > 0) {
8370
+ try {
8371
+ await pushToPostgres(pullResult.records);
8372
+ } catch {
8373
+ }
8374
+ }
8214
8375
  if (pullResult.maxVersion > lastPullVersion) {
8215
8376
  await client.execute({
8216
8377
  sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_cloud_pull_version', ?)",