@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/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
|
|
9795
|
+
import path24 from "path";
|
|
9686
9796
|
import os14 from "os";
|
|
9687
|
-
import { execSync as
|
|
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
|
-
|
|
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(
|
|
9865
|
-
await mkdir5(
|
|
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 =
|
|
9902
|
-
const mdPath =
|
|
9903
|
-
const mdDir =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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(
|
|
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
|
|
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 =
|
|
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
|
-
|
|
11863
|
-
|
|
11864
|
-
|
|
11865
|
-
|
|
11866
|
-
|
|
11867
|
-
|
|
11868
|
-
|
|
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 }
|