@askexenow/exe-os 0.8.80 → 0.8.82
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/backfill-conversations.js +359 -267
- package/dist/bin/backfill-responses.js +357 -265
- package/dist/bin/backfill-vectors.js +339 -264
- package/dist/bin/cleanup-stale-review-tasks.js +315 -256
- package/dist/bin/cli.js +494 -240
- package/dist/bin/exe-agent.js +141 -46
- package/dist/bin/exe-assign.js +151 -63
- package/dist/bin/exe-boot.js +294 -115
- package/dist/bin/exe-call.js +76 -51
- package/dist/bin/exe-cloud.js +58 -45
- package/dist/bin/exe-dispatch.js +434 -277
- package/dist/bin/exe-doctor.js +317 -246
- package/dist/bin/exe-export-behaviors.js +328 -248
- package/dist/bin/exe-forget.js +314 -231
- package/dist/bin/exe-gateway.js +2676 -1402
- package/dist/bin/exe-heartbeat.js +329 -264
- package/dist/bin/exe-kill.js +324 -244
- package/dist/bin/exe-launch-agent.js +574 -463
- package/dist/bin/exe-link.js +1055 -95
- package/dist/bin/exe-new-employee.js +49 -54
- package/dist/bin/exe-pending-messages.js +310 -253
- package/dist/bin/exe-pending-notifications.js +299 -228
- package/dist/bin/exe-pending-reviews.js +314 -245
- package/dist/bin/exe-rename.js +259 -195
- package/dist/bin/exe-review.js +140 -64
- package/dist/bin/exe-search.js +543 -356
- package/dist/bin/exe-session-cleanup.js +463 -382
- package/dist/bin/exe-settings.js +129 -99
- package/dist/bin/exe-start.sh +6 -6
- package/dist/bin/exe-status.js +95 -36
- package/dist/bin/exe-team.js +116 -51
- package/dist/bin/git-sweep.js +482 -307
- package/dist/bin/graph-backfill.js +357 -245
- package/dist/bin/graph-export.js +324 -244
- package/dist/bin/install.js +33 -10
- package/dist/bin/scan-tasks.js +481 -307
- package/dist/bin/setup.js +1147 -140
- package/dist/bin/shard-migrate.js +321 -241
- package/dist/bin/update.js +1 -7
- package/dist/bin/wiki-sync.js +318 -238
- package/dist/gateway/index.js +2656 -1383
- package/dist/hooks/bug-report-worker.js +641 -472
- package/dist/hooks/commit-complete.js +482 -307
- package/dist/hooks/error-recall.js +363 -135
- package/dist/hooks/exe-heartbeat-hook.js +97 -27
- package/dist/hooks/ingest-worker.js +584 -397
- package/dist/hooks/ingest.js +123 -58
- package/dist/hooks/instructions-loaded.js +212 -82
- package/dist/hooks/notification.js +200 -70
- package/dist/hooks/post-compact.js +199 -81
- package/dist/hooks/pre-compact.js +352 -140
- package/dist/hooks/pre-tool-use.js +416 -278
- package/dist/hooks/prompt-ingest-worker.js +376 -299
- package/dist/hooks/prompt-submit.js +414 -188
- package/dist/hooks/response-ingest-worker.js +408 -338
- package/dist/hooks/session-end.js +209 -83
- package/dist/hooks/session-start.js +382 -158
- package/dist/hooks/stop.js +209 -83
- package/dist/hooks/subagent-stop.js +209 -85
- package/dist/hooks/summary-worker.js +606 -510
- package/dist/index.js +2133 -855
- package/dist/lib/cloud-sync.js +1175 -184
- package/dist/lib/config.js +1 -9
- package/dist/lib/consolidation.js +71 -34
- package/dist/lib/database.js +166 -14
- package/dist/lib/device-registry.js +189 -117
- package/dist/lib/embedder.js +6 -10
- package/dist/lib/employee-templates.js +134 -39
- package/dist/lib/employees.js +30 -7
- package/dist/lib/exe-daemon-client.js +5 -7
- package/dist/lib/exe-daemon.js +514 -152
- package/dist/lib/hybrid-search.js +543 -356
- package/dist/lib/identity-templates.js +15 -15
- package/dist/lib/identity.js +19 -15
- package/dist/lib/license.js +1 -7
- package/dist/lib/messaging.js +157 -135
- package/dist/lib/reminders.js +97 -0
- package/dist/lib/schedules.js +302 -231
- package/dist/lib/skill-learning.js +33 -27
- package/dist/lib/status-brief.js +11 -14
- package/dist/lib/store.js +326 -237
- package/dist/lib/task-router.js +105 -1
- package/dist/lib/tasks.js +233 -116
- package/dist/lib/tmux-routing.js +173 -56
- package/dist/lib/ws-client.js +13 -3
- package/dist/mcp/server.js +2009 -1015
- package/dist/mcp/tools/complete-reminder.js +97 -0
- package/dist/mcp/tools/create-reminder.js +97 -0
- package/dist/mcp/tools/create-task.js +426 -262
- package/dist/mcp/tools/deactivate-behavior.js +119 -44
- package/dist/mcp/tools/list-reminders.js +97 -0
- package/dist/mcp/tools/list-tasks.js +56 -57
- package/dist/mcp/tools/send-message.js +206 -143
- package/dist/mcp/tools/update-task.js +259 -85
- package/dist/runtime/index.js +495 -316
- package/dist/tui/App.js +1128 -919
- package/package.json +2 -10
- package/src/commands/exe/afk.md +8 -8
- package/src/commands/exe/assign.md +1 -1
- package/src/commands/exe/build-adv.md +1 -1
- package/src/commands/exe/call.md +10 -10
- package/src/commands/exe/employee-heartbeat.md +9 -6
- package/src/commands/exe/heartbeat.md +5 -5
- package/src/commands/exe/intercom.md +26 -15
- package/src/commands/exe/launch.md +2 -2
- package/src/commands/exe/new-employee.md +1 -1
- package/src/commands/exe/review.md +2 -2
- package/src/commands/exe/schedule.md +1 -1
- package/src/commands/exe/sessions.md +2 -2
- package/src/commands/exe.md +22 -20
package/dist/lib/tmux-routing.js
CHANGED
|
@@ -324,23 +324,6 @@ var init_db_retry = __esm({
|
|
|
324
324
|
}
|
|
325
325
|
});
|
|
326
326
|
|
|
327
|
-
// src/lib/database.ts
|
|
328
|
-
import { createClient } from "@libsql/client";
|
|
329
|
-
function getClient() {
|
|
330
|
-
if (!_resilientClient) {
|
|
331
|
-
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
332
|
-
}
|
|
333
|
-
return _resilientClient;
|
|
334
|
-
}
|
|
335
|
-
var _resilientClient;
|
|
336
|
-
var init_database = __esm({
|
|
337
|
-
"src/lib/database.ts"() {
|
|
338
|
-
"use strict";
|
|
339
|
-
init_db_retry();
|
|
340
|
-
_resilientClient = null;
|
|
341
|
-
}
|
|
342
|
-
});
|
|
343
|
-
|
|
344
327
|
// src/lib/config.ts
|
|
345
328
|
import { readFile, writeFile, mkdir, chmod } from "fs/promises";
|
|
346
329
|
import { readFileSync as readFileSync3, existsSync as existsSync3, renameSync as renameSync2 } from "fs";
|
|
@@ -487,13 +470,7 @@ var init_config = __esm({
|
|
|
487
470
|
wikiUrl: "",
|
|
488
471
|
wikiApiKey: "",
|
|
489
472
|
wikiSyncIntervalMs: 30 * 60 * 1e3,
|
|
490
|
-
wikiWorkspaceMapping: {
|
|
491
|
-
exe: "Executive",
|
|
492
|
-
yoshi: "Engineering",
|
|
493
|
-
mari: "Marketing",
|
|
494
|
-
tom: "Engineering",
|
|
495
|
-
sasha: "Production"
|
|
496
|
-
},
|
|
473
|
+
wikiWorkspaceMapping: {},
|
|
497
474
|
wikiAutoUpdate: true,
|
|
498
475
|
wikiAutoUpdateThreshold: 0.5,
|
|
499
476
|
wikiAutoUpdateCreateNew: true,
|
|
@@ -536,6 +513,22 @@ import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as r
|
|
|
536
513
|
import { execSync as execSync3 } from "child_process";
|
|
537
514
|
import path4 from "path";
|
|
538
515
|
import os4 from "os";
|
|
516
|
+
function normalizeRole(role) {
|
|
517
|
+
return (role ?? "").trim().toLowerCase();
|
|
518
|
+
}
|
|
519
|
+
function isCoordinatorRole(role) {
|
|
520
|
+
return normalizeRole(role) === normalizeRole(COORDINATOR_ROLE);
|
|
521
|
+
}
|
|
522
|
+
function getCoordinatorEmployee(employees) {
|
|
523
|
+
return employees.find((e) => isCoordinatorRole(e.role));
|
|
524
|
+
}
|
|
525
|
+
function getCoordinatorName(employees = loadEmployeesSync()) {
|
|
526
|
+
return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
|
|
527
|
+
}
|
|
528
|
+
function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
529
|
+
if (!agentName) return false;
|
|
530
|
+
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
531
|
+
}
|
|
539
532
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
540
533
|
if (!existsSync4(employeesPath)) return [];
|
|
541
534
|
try {
|
|
@@ -553,16 +546,36 @@ function isMultiInstance(agentName, employees) {
|
|
|
553
546
|
if (!emp) return false;
|
|
554
547
|
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
555
548
|
}
|
|
556
|
-
var EMPLOYEES_PATH, MULTI_INSTANCE_ROLES;
|
|
549
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
|
|
557
550
|
var init_employees = __esm({
|
|
558
551
|
"src/lib/employees.ts"() {
|
|
559
552
|
"use strict";
|
|
560
553
|
init_config();
|
|
561
554
|
EMPLOYEES_PATH = path4.join(EXE_AI_DIR, "exe-employees.json");
|
|
555
|
+
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
556
|
+
COORDINATOR_ROLE = "COO";
|
|
562
557
|
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
563
558
|
}
|
|
564
559
|
});
|
|
565
560
|
|
|
561
|
+
// src/lib/database.ts
|
|
562
|
+
import { createClient } from "@libsql/client";
|
|
563
|
+
function getClient() {
|
|
564
|
+
if (!_resilientClient) {
|
|
565
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
566
|
+
}
|
|
567
|
+
return _resilientClient;
|
|
568
|
+
}
|
|
569
|
+
var _resilientClient;
|
|
570
|
+
var init_database = __esm({
|
|
571
|
+
"src/lib/database.ts"() {
|
|
572
|
+
"use strict";
|
|
573
|
+
init_db_retry();
|
|
574
|
+
init_employees();
|
|
575
|
+
_resilientClient = null;
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
|
|
566
579
|
// src/lib/license.ts
|
|
567
580
|
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
|
|
568
581
|
import { randomUUID } from "crypto";
|
|
@@ -1074,6 +1087,36 @@ async function listTasks(input) {
|
|
|
1074
1087
|
tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
|
|
1075
1088
|
}));
|
|
1076
1089
|
}
|
|
1090
|
+
function isTmuxSessionAlive(identifier) {
|
|
1091
|
+
if (!identifier || identifier === "unknown") return true;
|
|
1092
|
+
try {
|
|
1093
|
+
if (identifier.startsWith("%")) {
|
|
1094
|
+
const output = execSync4("tmux list-panes -a -F '#{pane_id}'", {
|
|
1095
|
+
timeout: 2e3,
|
|
1096
|
+
encoding: "utf8",
|
|
1097
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1098
|
+
});
|
|
1099
|
+
return output.split("\n").some((l) => l.trim() === identifier);
|
|
1100
|
+
} else {
|
|
1101
|
+
execSync4(`tmux has-session -t ${JSON.stringify(identifier)}`, {
|
|
1102
|
+
timeout: 2e3,
|
|
1103
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1104
|
+
});
|
|
1105
|
+
return true;
|
|
1106
|
+
}
|
|
1107
|
+
} catch {
|
|
1108
|
+
if (identifier.startsWith("%")) return true;
|
|
1109
|
+
try {
|
|
1110
|
+
execSync4("tmux list-sessions", {
|
|
1111
|
+
timeout: 2e3,
|
|
1112
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1113
|
+
});
|
|
1114
|
+
return false;
|
|
1115
|
+
} catch {
|
|
1116
|
+
return true;
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1077
1120
|
function checkStaleCompletion(taskContext, taskCreatedAt) {
|
|
1078
1121
|
if (!taskContext) return null;
|
|
1079
1122
|
if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
|
|
@@ -1136,13 +1179,59 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
1136
1179
|
});
|
|
1137
1180
|
if (claim.rowsAffected === 0) {
|
|
1138
1181
|
const current = await client.execute({
|
|
1139
|
-
sql: "SELECT status, assigned_tmux FROM tasks WHERE id = ?",
|
|
1182
|
+
sql: "SELECT status, assigned_tmux, assigned_by FROM tasks WHERE id = ?",
|
|
1140
1183
|
args: [taskId]
|
|
1141
1184
|
});
|
|
1142
1185
|
const cur = current.rows[0];
|
|
1143
|
-
const
|
|
1144
|
-
const
|
|
1145
|
-
|
|
1186
|
+
const curStatus = cur?.status ?? "unknown";
|
|
1187
|
+
const claimedBySession = cur?.assigned_tmux ?? "";
|
|
1188
|
+
const assignedBy = cur?.assigned_by ?? "";
|
|
1189
|
+
if (curStatus === "in_progress" && claimedBySession && !isTmuxSessionAlive(claimedBySession)) {
|
|
1190
|
+
process.stderr.write(
|
|
1191
|
+
`[tasks] Auto-releasing dead claim on ${taskId} (was ${claimedBySession})
|
|
1192
|
+
`
|
|
1193
|
+
);
|
|
1194
|
+
await client.execute({
|
|
1195
|
+
sql: "UPDATE tasks SET status = 'open', assigned_tmux = NULL, updated_at = ? WHERE id = ?",
|
|
1196
|
+
args: [now, taskId]
|
|
1197
|
+
});
|
|
1198
|
+
const retried = await client.execute({
|
|
1199
|
+
sql: `UPDATE tasks SET status = 'in_progress', assigned_tmux = ?, updated_at = ? WHERE id = ? AND status = 'open'`,
|
|
1200
|
+
args: [tmuxSession, now, taskId]
|
|
1201
|
+
});
|
|
1202
|
+
if (retried.rowsAffected > 0) {
|
|
1203
|
+
try {
|
|
1204
|
+
await writeCheckpoint({
|
|
1205
|
+
taskId,
|
|
1206
|
+
step: "reclaimed_dead_session",
|
|
1207
|
+
contextSummary: `Task reclaimed after dead session ${claimedBySession} released.`
|
|
1208
|
+
});
|
|
1209
|
+
} catch {
|
|
1210
|
+
}
|
|
1211
|
+
return { row, taskFile, now, taskId };
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || input.callerAgentId === "exe")) {
|
|
1215
|
+
process.stderr.write(
|
|
1216
|
+
`[tasks] Assigner override: ${input.callerAgentId} reclaiming ${taskId}
|
|
1217
|
+
`
|
|
1218
|
+
);
|
|
1219
|
+
await client.execute({
|
|
1220
|
+
sql: `UPDATE tasks SET status = 'in_progress', assigned_tmux = ?, updated_at = ? WHERE id = ?`,
|
|
1221
|
+
args: [tmuxSession, now, taskId]
|
|
1222
|
+
});
|
|
1223
|
+
try {
|
|
1224
|
+
await writeCheckpoint({
|
|
1225
|
+
taskId,
|
|
1226
|
+
step: "assigner_override",
|
|
1227
|
+
contextSummary: `Task force-reclaimed by assigner ${input.callerAgentId}.`
|
|
1228
|
+
});
|
|
1229
|
+
} catch {
|
|
1230
|
+
}
|
|
1231
|
+
return { row, taskFile, now, taskId };
|
|
1232
|
+
}
|
|
1233
|
+
const claimedBy = claimedBySession ? ` (claimed by ${claimedBySession})` : "";
|
|
1234
|
+
throw new Error(`${TASK_ALREADY_CLAIMED_PREFIX}: task ${taskId} is ${curStatus}${claimedBy}`);
|
|
1146
1235
|
}
|
|
1147
1236
|
try {
|
|
1148
1237
|
await writeCheckpoint({
|
|
@@ -1240,7 +1329,7 @@ var init_tasks_crud = __esm({
|
|
|
1240
1329
|
"use strict";
|
|
1241
1330
|
init_database();
|
|
1242
1331
|
init_task_scope();
|
|
1243
|
-
DELEGATION_KEYWORDS = /parallel|delegate|wave|
|
|
1332
|
+
DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
|
|
1244
1333
|
TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
|
|
1245
1334
|
}
|
|
1246
1335
|
});
|
|
@@ -1597,7 +1686,7 @@ function findSessionForProject(projectName) {
|
|
|
1597
1686
|
const sessions = listSessions();
|
|
1598
1687
|
for (const s of sessions) {
|
|
1599
1688
|
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
1600
|
-
if (proj === projectName && s.agentId === "exe") return s;
|
|
1689
|
+
if (proj === projectName && (s.agentId === "exe" || isCoordinatorName(s.agentId))) return s;
|
|
1601
1690
|
}
|
|
1602
1691
|
return null;
|
|
1603
1692
|
}
|
|
@@ -1637,12 +1726,13 @@ var init_session_scope = __esm({
|
|
|
1637
1726
|
init_session_registry();
|
|
1638
1727
|
init_project_name();
|
|
1639
1728
|
init_tmux_routing();
|
|
1729
|
+
init_employees();
|
|
1640
1730
|
}
|
|
1641
1731
|
});
|
|
1642
1732
|
|
|
1643
1733
|
// src/lib/tasks-notify.ts
|
|
1644
1734
|
async function dispatchTaskToEmployee(input) {
|
|
1645
|
-
if (input.assignedTo === "exe") return { dispatched: "skipped" };
|
|
1735
|
+
if (input.assignedTo === "exe" || isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
|
|
1646
1736
|
let crossProject = false;
|
|
1647
1737
|
if (input.projectName) {
|
|
1648
1738
|
try {
|
|
@@ -2085,6 +2175,24 @@ async function updateTask(input) {
|
|
|
2085
2175
|
});
|
|
2086
2176
|
} catch {
|
|
2087
2177
|
}
|
|
2178
|
+
const assignedAgent = String(row.assigned_to);
|
|
2179
|
+
if (!isCoordinatorName(assignedAgent)) {
|
|
2180
|
+
try {
|
|
2181
|
+
const draftClient = getClient();
|
|
2182
|
+
if (input.status === "done") {
|
|
2183
|
+
await draftClient.execute({
|
|
2184
|
+
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
2185
|
+
args: [assignedAgent]
|
|
2186
|
+
});
|
|
2187
|
+
} else if (input.status === "cancelled") {
|
|
2188
|
+
await draftClient.execute({
|
|
2189
|
+
sql: `DELETE FROM memories WHERE agent_id = ? AND draft = 1`,
|
|
2190
|
+
args: [assignedAgent]
|
|
2191
|
+
});
|
|
2192
|
+
}
|
|
2193
|
+
} catch {
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2088
2196
|
try {
|
|
2089
2197
|
const client = getClient();
|
|
2090
2198
|
const cascaded = await client.execute({
|
|
@@ -2103,8 +2211,8 @@ async function updateTask(input) {
|
|
|
2103
2211
|
}
|
|
2104
2212
|
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
2105
2213
|
if (isTerminal) {
|
|
2106
|
-
const
|
|
2107
|
-
if (!
|
|
2214
|
+
const isCoordinator = String(row.assigned_to) === "exe" || isCoordinatorName(String(row.assigned_to));
|
|
2215
|
+
if (!isCoordinator) {
|
|
2108
2216
|
notifyTaskDone();
|
|
2109
2217
|
}
|
|
2110
2218
|
await markTaskNotificationsRead(taskFile);
|
|
@@ -2128,7 +2236,7 @@ async function updateTask(input) {
|
|
|
2128
2236
|
}
|
|
2129
2237
|
}
|
|
2130
2238
|
}
|
|
2131
|
-
if (input.status === "done" && String(row.assigned_to) !== "exe" && !process.env.VITEST) {
|
|
2239
|
+
if (input.status === "done" && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
2132
2240
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
2133
2241
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
2134
2242
|
taskId,
|
|
@@ -2144,7 +2252,7 @@ async function updateTask(input) {
|
|
|
2144
2252
|
});
|
|
2145
2253
|
}
|
|
2146
2254
|
let nextTask;
|
|
2147
|
-
if (isTerminal && String(row.assigned_to) !== "exe") {
|
|
2255
|
+
if (isTerminal && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to))) {
|
|
2148
2256
|
try {
|
|
2149
2257
|
nextTask = await findNextTask(String(row.assigned_to));
|
|
2150
2258
|
} catch {
|
|
@@ -2171,12 +2279,14 @@ async function updateTask(input) {
|
|
|
2171
2279
|
async function deleteTask(taskId, baseDir) {
|
|
2172
2280
|
const client = getClient();
|
|
2173
2281
|
const { taskFile, assignedTo, assignedBy, taskSlug } = await deleteTaskCore(taskId, baseDir);
|
|
2174
|
-
const
|
|
2282
|
+
const coordinatorName = getCoordinatorName();
|
|
2283
|
+
const reviewer = assignedBy || coordinatorName;
|
|
2175
2284
|
const reviewSlug = `review-${assignedTo}-${taskSlug}`;
|
|
2176
2285
|
const reviewFile = `exe/${reviewer}/${reviewSlug}.md`;
|
|
2286
|
+
const legacyReviewFile = `exe/${coordinatorName}/${reviewSlug}.md`;
|
|
2177
2287
|
await client.execute({
|
|
2178
|
-
sql: "DELETE FROM tasks WHERE task_file = ? OR task_file = ?",
|
|
2179
|
-
args: [reviewFile, `exe/exe/${reviewSlug}.md`]
|
|
2288
|
+
sql: "DELETE FROM tasks WHERE task_file = ? OR task_file = ? OR task_file = ?",
|
|
2289
|
+
args: [reviewFile, legacyReviewFile, `exe/exe/${reviewSlug}.md`]
|
|
2180
2290
|
});
|
|
2181
2291
|
await markAsReadByTaskFile(taskFile);
|
|
2182
2292
|
await markAsReadByTaskFile(reviewFile);
|
|
@@ -2188,6 +2298,7 @@ var init_tasks = __esm({
|
|
|
2188
2298
|
init_config();
|
|
2189
2299
|
init_notifications();
|
|
2190
2300
|
init_state_bus();
|
|
2301
|
+
init_employees();
|
|
2191
2302
|
init_tasks_crud();
|
|
2192
2303
|
init_tasks_review();
|
|
2193
2304
|
init_tasks_crud();
|
|
@@ -2273,7 +2384,7 @@ function _resetLastRelaunchCache() {
|
|
|
2273
2384
|
}
|
|
2274
2385
|
async function lastResumeCreatedAtMs(agentId) {
|
|
2275
2386
|
const client = getClient();
|
|
2276
|
-
const cmScope = sessionScopeFilter();
|
|
2387
|
+
const cmScope = sessionScopeFilter(null);
|
|
2277
2388
|
const result = await client.execute({
|
|
2278
2389
|
sql: `SELECT MAX(created_at) AS last_created_at
|
|
2279
2390
|
FROM tasks
|
|
@@ -2298,7 +2409,7 @@ async function createOrRefreshResumeTask(agentId, projectDir, openTasks) {
|
|
|
2298
2409
|
const client = getClient();
|
|
2299
2410
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2300
2411
|
const context = buildResumeContext(agentId, openTasks);
|
|
2301
|
-
const rdScope = sessionScopeFilter();
|
|
2412
|
+
const rdScope = sessionScopeFilter(null);
|
|
2302
2413
|
const existing = await client.execute({
|
|
2303
2414
|
sql: `SELECT id FROM tasks
|
|
2304
2415
|
WHERE assigned_to = ?
|
|
@@ -2332,7 +2443,7 @@ async function pollCapacityDead() {
|
|
|
2332
2443
|
const transport = getTransport();
|
|
2333
2444
|
const relaunched = [];
|
|
2334
2445
|
const registered = listSessions().filter(
|
|
2335
|
-
(s) => s.agentId !== "exe"
|
|
2446
|
+
(s) => s.agentId !== "exe" && !isCoordinatorName(s.agentId)
|
|
2336
2447
|
);
|
|
2337
2448
|
if (registered.length === 0) return [];
|
|
2338
2449
|
let liveSessions;
|
|
@@ -2392,7 +2503,7 @@ async function pollCapacityDead() {
|
|
|
2392
2503
|
reason: "capacity"
|
|
2393
2504
|
});
|
|
2394
2505
|
const client = getClient();
|
|
2395
|
-
const rlScope = sessionScopeFilter();
|
|
2506
|
+
const rlScope = sessionScopeFilter(null);
|
|
2396
2507
|
const openTasks = await client.execute({
|
|
2397
2508
|
sql: `SELECT id, title, priority, task_file, status
|
|
2398
2509
|
FROM tasks
|
|
@@ -2446,6 +2557,7 @@ var init_capacity_monitor = __esm({
|
|
|
2446
2557
|
init_session_kill_telemetry();
|
|
2447
2558
|
init_tmux_routing();
|
|
2448
2559
|
init_task_scope();
|
|
2560
|
+
init_employees();
|
|
2449
2561
|
CAPACITY_PATTERNS = [
|
|
2450
2562
|
/conversation is too long/i,
|
|
2451
2563
|
/maximum context length/i,
|
|
@@ -2595,7 +2707,7 @@ function employeeSessionName(employee, exeSession, instance) {
|
|
|
2595
2707
|
exeSession = root;
|
|
2596
2708
|
} else {
|
|
2597
2709
|
throw new Error(
|
|
2598
|
-
`Invalid
|
|
2710
|
+
`Invalid coordinator session "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name.`
|
|
2599
2711
|
);
|
|
2600
2712
|
}
|
|
2601
2713
|
}
|
|
@@ -2615,8 +2727,10 @@ function parseParentExe(sessionName, agentId) {
|
|
|
2615
2727
|
return match?.[1] ?? null;
|
|
2616
2728
|
}
|
|
2617
2729
|
function extractRootExe(name) {
|
|
2618
|
-
|
|
2619
|
-
|
|
2730
|
+
if (!name) return null;
|
|
2731
|
+
if (!name.includes("-")) return name;
|
|
2732
|
+
const parts = name.split("-").filter(Boolean);
|
|
2733
|
+
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
2620
2734
|
}
|
|
2621
2735
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
2622
2736
|
if (!existsSync10(SESSION_CACHE)) {
|
|
@@ -2761,12 +2875,14 @@ function isSessionBusy(sessionName) {
|
|
|
2761
2875
|
return state === "thinking" || state === "tool";
|
|
2762
2876
|
}
|
|
2763
2877
|
function isExeSession(sessionName) {
|
|
2764
|
-
|
|
2878
|
+
const matchesBaseWithInstance = (baseName) => sessionName === baseName || sessionName.startsWith(baseName) && /^\d+$/.test(sessionName.slice(baseName.length));
|
|
2879
|
+
const coordinatorName = getCoordinatorName();
|
|
2880
|
+
return matchesBaseWithInstance(coordinatorName) || matchesBaseWithInstance("exe");
|
|
2765
2881
|
}
|
|
2766
2882
|
function sendIntercom(targetSession) {
|
|
2767
2883
|
const transport = getTransport();
|
|
2768
2884
|
if (isExeSession(targetSession)) {
|
|
2769
|
-
logIntercom(`
|
|
2885
|
+
logIntercom(`SKIP_COORDINATOR \u2192 ${targetSession} (coordinator sessions use prompt-submit hook)`);
|
|
2770
2886
|
return "skipped_exe";
|
|
2771
2887
|
}
|
|
2772
2888
|
if (isDebounced(targetSession)) {
|
|
@@ -2818,7 +2934,7 @@ function notifyParentExe(sessionKey) {
|
|
|
2818
2934
|
if (result === "failed") {
|
|
2819
2935
|
const rootExe = resolveExeSession();
|
|
2820
2936
|
if (rootExe && rootExe !== target) {
|
|
2821
|
-
process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root
|
|
2937
|
+
process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root coordinator session ${rootExe}
|
|
2822
2938
|
`);
|
|
2823
2939
|
const fallback = sendIntercom(rootExe);
|
|
2824
2940
|
return fallback !== "failed";
|
|
@@ -2828,8 +2944,8 @@ function notifyParentExe(sessionKey) {
|
|
|
2828
2944
|
return true;
|
|
2829
2945
|
}
|
|
2830
2946
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
2831
|
-
if (employeeName === "exe") {
|
|
2832
|
-
return { status: "failed", sessionName: "", error: "
|
|
2947
|
+
if (employeeName === "exe" || isCoordinatorName(employeeName)) {
|
|
2948
|
+
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
2833
2949
|
}
|
|
2834
2950
|
try {
|
|
2835
2951
|
assertEmployeeLimitSync();
|
|
@@ -2838,8 +2954,8 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
2838
2954
|
return { status: "failed", sessionName: "", error: err.message };
|
|
2839
2955
|
}
|
|
2840
2956
|
}
|
|
2841
|
-
if (
|
|
2842
|
-
const bare = employeeName.
|
|
2957
|
+
if (employeeName.includes("-")) {
|
|
2958
|
+
const bare = employeeName.split("-")[0].replace(/\d+$/, "");
|
|
2843
2959
|
return {
|
|
2844
2960
|
status: "failed",
|
|
2845
2961
|
sessionName: "",
|
|
@@ -2858,7 +2974,7 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
2858
2974
|
return {
|
|
2859
2975
|
status: "failed",
|
|
2860
2976
|
sessionName: "",
|
|
2861
|
-
error: `Invalid
|
|
2977
|
+
error: `Invalid coordinator session "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name.`
|
|
2862
2978
|
};
|
|
2863
2979
|
}
|
|
2864
2980
|
}
|
|
@@ -3015,8 +3131,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3015
3131
|
const ctxContent = [
|
|
3016
3132
|
`## Session Context`,
|
|
3017
3133
|
`You are running in tmux session: ${sessionName}.`,
|
|
3018
|
-
`Your parent
|
|
3019
|
-
`Your employees (if any) use the -${exeSession} suffix
|
|
3134
|
+
`Your parent coordinator session is ${exeSession}.`,
|
|
3135
|
+
`Your employees (if any) use the -${exeSession} suffix.`
|
|
3020
3136
|
].join("\n");
|
|
3021
3137
|
writeFileSync6(ctxFile, ctxContent);
|
|
3022
3138
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
@@ -3119,6 +3235,7 @@ var init_tmux_routing = __esm({
|
|
|
3119
3235
|
init_provider_table();
|
|
3120
3236
|
init_intercom_queue();
|
|
3121
3237
|
init_plan_limits();
|
|
3238
|
+
init_employees();
|
|
3122
3239
|
SPAWN_LOCK_DIR = path13.join(os6.homedir(), ".exe-os", "spawn-locks");
|
|
3123
3240
|
SESSION_CACHE = path13.join(os6.homedir(), ".exe-os", "session-cache");
|
|
3124
3241
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
package/dist/lib/ws-client.js
CHANGED
|
@@ -78,16 +78,25 @@ function createWsClient(config, handlers) {
|
|
|
78
78
|
} catch {
|
|
79
79
|
}
|
|
80
80
|
};
|
|
81
|
-
ws.onclose = () => {
|
|
81
|
+
ws.onclose = (event) => {
|
|
82
82
|
connected = false;
|
|
83
83
|
if (heartbeatTimer) {
|
|
84
84
|
clearInterval(heartbeatTimer);
|
|
85
85
|
heartbeatTimer = null;
|
|
86
86
|
}
|
|
87
|
+
const code = event.code ?? "unknown";
|
|
88
|
+
const reason = event.reason || "(none)";
|
|
89
|
+
process.stderr.write(
|
|
90
|
+
`[ws-client] Disconnected: code=${code} reason=${reason}
|
|
91
|
+
`
|
|
92
|
+
);
|
|
87
93
|
handlers.onDisconnect();
|
|
88
94
|
scheduleReconnect();
|
|
89
95
|
};
|
|
90
|
-
ws.onerror = () => {
|
|
96
|
+
ws.onerror = (event) => {
|
|
97
|
+
const errMsg = event.message ?? "unknown error";
|
|
98
|
+
process.stderr.write(`[ws-client] Error: ${errMsg}
|
|
99
|
+
`);
|
|
91
100
|
};
|
|
92
101
|
} catch {
|
|
93
102
|
connected = false;
|
|
@@ -104,11 +113,12 @@ function createWsClient(config, handlers) {
|
|
|
104
113
|
}
|
|
105
114
|
function scheduleReconnect() {
|
|
106
115
|
if (closed || reconnectTimer) return;
|
|
116
|
+
const jitter = reconnectDelay * (0.75 + Math.random() * 0.5);
|
|
107
117
|
reconnectTimer = setTimeout(() => {
|
|
108
118
|
reconnectTimer = null;
|
|
109
119
|
reconnectDelay = Math.min(reconnectDelay * 2, MAX_RECONNECT_MS);
|
|
110
120
|
connect();
|
|
111
|
-
},
|
|
121
|
+
}, jitter);
|
|
112
122
|
}
|
|
113
123
|
connect();
|
|
114
124
|
return {
|