@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 +293 -132
- package/dist/bin/exe-boot.js +302 -141
- package/dist/bin/exe-dispatch.js +298 -132
- package/dist/bin/exe-gateway.js +657 -273
- package/dist/bin/exe-link.js +6 -0
- package/dist/bin/exe-session-cleanup.js +171 -132
- package/dist/bin/git-sweep.js +300 -134
- package/dist/bin/scan-tasks.js +307 -141
- package/dist/bin/setup.js +6 -0
- package/dist/gateway/index.js +287 -132
- package/dist/hooks/bug-report-worker.js +535 -380
- package/dist/hooks/codex-stop-task-finalizer.js +10 -8
- package/dist/hooks/commit-complete.js +298 -132
- package/dist/hooks/ingest-worker.js +237 -71
- package/dist/hooks/pre-compact.js +298 -132
- package/dist/hooks/prompt-submit.js +110 -71
- package/dist/hooks/session-end.js +300 -134
- package/dist/hooks/summary-worker.js +6 -0
- package/dist/index.js +287 -132
- package/dist/lib/cloud-sync.js +6 -0
- package/dist/lib/exe-daemon.js +179 -138
- package/dist/lib/messaging.js +10 -8
- package/dist/lib/tasks.js +660 -494
- package/dist/lib/tmux-routing.js +298 -132
- package/dist/mcp/server.js +478 -317
- package/dist/mcp/tools/create-task.js +799 -644
- package/dist/mcp/tools/send-message.js +10 -8
- package/dist/mcp/tools/update-task.js +660 -494
- package/dist/runtime/index.js +298 -132
- package/dist/tui/App.js +287 -132
- package/package.json +4 -4
package/dist/bin/exe-boot.js
CHANGED
|
@@ -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
|
|
4873
|
+
import path15 from "path";
|
|
4761
4874
|
import os10 from "os";
|
|
4762
|
-
import { execSync as
|
|
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
|
-
|
|
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(
|
|
4940
|
-
await mkdir4(
|
|
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 =
|
|
4977
|
-
const mdPath =
|
|
4978
|
-
const mdDir =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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(
|
|
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
|
|
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 =
|
|
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
|
-
|
|
6947
|
-
|
|
6948
|
-
|
|
6949
|
-
|
|
6950
|
-
|
|
6951
|
-
|
|
6952
|
-
|
|
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', ?)",
|