@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/tasks.js
CHANGED
|
@@ -26,23 +26,6 @@ var init_db_retry = __esm({
|
|
|
26
26
|
}
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
-
// src/lib/database.ts
|
|
30
|
-
import { createClient } from "@libsql/client";
|
|
31
|
-
function getClient() {
|
|
32
|
-
if (!_resilientClient) {
|
|
33
|
-
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
34
|
-
}
|
|
35
|
-
return _resilientClient;
|
|
36
|
-
}
|
|
37
|
-
var _resilientClient;
|
|
38
|
-
var init_database = __esm({
|
|
39
|
-
"src/lib/database.ts"() {
|
|
40
|
-
"use strict";
|
|
41
|
-
init_db_retry();
|
|
42
|
-
_resilientClient = null;
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
|
|
46
29
|
// src/lib/config.ts
|
|
47
30
|
import { readFile, writeFile, mkdir, chmod } from "fs/promises";
|
|
48
31
|
import { readFileSync, existsSync, renameSync } from "fs";
|
|
@@ -189,13 +172,7 @@ var init_config = __esm({
|
|
|
189
172
|
wikiUrl: "",
|
|
190
173
|
wikiApiKey: "",
|
|
191
174
|
wikiSyncIntervalMs: 30 * 60 * 1e3,
|
|
192
|
-
wikiWorkspaceMapping: {
|
|
193
|
-
exe: "Executive",
|
|
194
|
-
yoshi: "Engineering",
|
|
195
|
-
mari: "Marketing",
|
|
196
|
-
tom: "Engineering",
|
|
197
|
-
sasha: "Production"
|
|
198
|
-
},
|
|
175
|
+
wikiWorkspaceMapping: {},
|
|
199
176
|
wikiAutoUpdate: true,
|
|
200
177
|
wikiAutoUpdateThreshold: 0.5,
|
|
201
178
|
wikiAutoUpdateCreateNew: true,
|
|
@@ -232,15 +209,84 @@ var init_config = __esm({
|
|
|
232
209
|
}
|
|
233
210
|
});
|
|
234
211
|
|
|
235
|
-
// src/lib/
|
|
236
|
-
import
|
|
212
|
+
// src/lib/employees.ts
|
|
213
|
+
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
214
|
+
import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
|
|
215
|
+
import { execSync } from "child_process";
|
|
237
216
|
import path2 from "path";
|
|
238
217
|
import os2 from "os";
|
|
218
|
+
function normalizeRole(role) {
|
|
219
|
+
return (role ?? "").trim().toLowerCase();
|
|
220
|
+
}
|
|
221
|
+
function isCoordinatorRole(role) {
|
|
222
|
+
return normalizeRole(role) === normalizeRole(COORDINATOR_ROLE);
|
|
223
|
+
}
|
|
224
|
+
function getCoordinatorEmployee(employees) {
|
|
225
|
+
return employees.find((e) => isCoordinatorRole(e.role));
|
|
226
|
+
}
|
|
227
|
+
function getCoordinatorName(employees = loadEmployeesSync()) {
|
|
228
|
+
return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
|
|
229
|
+
}
|
|
230
|
+
function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
231
|
+
if (!agentName) return false;
|
|
232
|
+
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
233
|
+
}
|
|
234
|
+
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
235
|
+
if (!existsSync2(employeesPath)) return [];
|
|
236
|
+
try {
|
|
237
|
+
return JSON.parse(readFileSync2(employeesPath, "utf-8"));
|
|
238
|
+
} catch {
|
|
239
|
+
return [];
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
function getEmployee(employees, name) {
|
|
243
|
+
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
244
|
+
}
|
|
245
|
+
function isMultiInstance(agentName, employees) {
|
|
246
|
+
const roster = employees ?? loadEmployeesSync();
|
|
247
|
+
const emp = getEmployee(roster, agentName);
|
|
248
|
+
if (!emp) return false;
|
|
249
|
+
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
250
|
+
}
|
|
251
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
|
|
252
|
+
var init_employees = __esm({
|
|
253
|
+
"src/lib/employees.ts"() {
|
|
254
|
+
"use strict";
|
|
255
|
+
init_config();
|
|
256
|
+
EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
|
|
257
|
+
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
258
|
+
COORDINATOR_ROLE = "COO";
|
|
259
|
+
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
// src/lib/database.ts
|
|
264
|
+
import { createClient } from "@libsql/client";
|
|
265
|
+
function getClient() {
|
|
266
|
+
if (!_resilientClient) {
|
|
267
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
268
|
+
}
|
|
269
|
+
return _resilientClient;
|
|
270
|
+
}
|
|
271
|
+
var _resilientClient;
|
|
272
|
+
var init_database = __esm({
|
|
273
|
+
"src/lib/database.ts"() {
|
|
274
|
+
"use strict";
|
|
275
|
+
init_db_retry();
|
|
276
|
+
init_employees();
|
|
277
|
+
_resilientClient = null;
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// src/lib/notifications.ts
|
|
282
|
+
import crypto from "crypto";
|
|
283
|
+
import path3 from "path";
|
|
284
|
+
import os3 from "os";
|
|
239
285
|
import {
|
|
240
|
-
readFileSync as
|
|
286
|
+
readFileSync as readFileSync3,
|
|
241
287
|
readdirSync,
|
|
242
|
-
unlinkSync,
|
|
243
|
-
existsSync as
|
|
288
|
+
unlinkSync as unlinkSync2,
|
|
289
|
+
existsSync as existsSync3,
|
|
244
290
|
rmdirSync
|
|
245
291
|
} from "fs";
|
|
246
292
|
async function writeNotification(notification) {
|
|
@@ -340,12 +386,12 @@ var init_state_bus = __esm({
|
|
|
340
386
|
});
|
|
341
387
|
|
|
342
388
|
// src/lib/session-registry.ts
|
|
343
|
-
import { readFileSync as
|
|
344
|
-
import
|
|
345
|
-
import
|
|
389
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync, existsSync as existsSync4 } from "fs";
|
|
390
|
+
import path4 from "path";
|
|
391
|
+
import os4 from "os";
|
|
346
392
|
function registerSession(entry) {
|
|
347
|
-
const dir =
|
|
348
|
-
if (!
|
|
393
|
+
const dir = path4.dirname(REGISTRY_PATH);
|
|
394
|
+
if (!existsSync4(dir)) {
|
|
349
395
|
mkdirSync(dir, { recursive: true });
|
|
350
396
|
}
|
|
351
397
|
const sessions = listSessions();
|
|
@@ -355,11 +401,11 @@ function registerSession(entry) {
|
|
|
355
401
|
} else {
|
|
356
402
|
sessions.push(entry);
|
|
357
403
|
}
|
|
358
|
-
|
|
404
|
+
writeFileSync2(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
|
|
359
405
|
}
|
|
360
406
|
function listSessions() {
|
|
361
407
|
try {
|
|
362
|
-
const raw =
|
|
408
|
+
const raw = readFileSync4(REGISTRY_PATH, "utf8");
|
|
363
409
|
return JSON.parse(raw);
|
|
364
410
|
} catch {
|
|
365
411
|
return [];
|
|
@@ -369,18 +415,18 @@ var REGISTRY_PATH;
|
|
|
369
415
|
var init_session_registry = __esm({
|
|
370
416
|
"src/lib/session-registry.ts"() {
|
|
371
417
|
"use strict";
|
|
372
|
-
REGISTRY_PATH =
|
|
418
|
+
REGISTRY_PATH = path4.join(os4.homedir(), ".exe-os", "session-registry.json");
|
|
373
419
|
}
|
|
374
420
|
});
|
|
375
421
|
|
|
376
422
|
// src/lib/session-key.ts
|
|
377
|
-
import { execSync } from "child_process";
|
|
423
|
+
import { execSync as execSync2 } from "child_process";
|
|
378
424
|
function getSessionKey() {
|
|
379
425
|
if (_cached) return _cached;
|
|
380
426
|
let pid = process.ppid;
|
|
381
427
|
for (let i = 0; i < 10; i++) {
|
|
382
428
|
try {
|
|
383
|
-
const info =
|
|
429
|
+
const info = execSync2(`ps -p ${pid} -o ppid=,comm=`, {
|
|
384
430
|
encoding: "utf8",
|
|
385
431
|
timeout: 2e3
|
|
386
432
|
}).trim();
|
|
@@ -516,14 +562,14 @@ var init_transport = __esm({
|
|
|
516
562
|
});
|
|
517
563
|
|
|
518
564
|
// src/lib/cc-agent-support.ts
|
|
519
|
-
import { execSync as
|
|
565
|
+
import { execSync as execSync3 } from "child_process";
|
|
520
566
|
function _resetCcAgentSupportCache() {
|
|
521
567
|
_cachedSupport = null;
|
|
522
568
|
}
|
|
523
569
|
function claudeSupportsAgentFlag() {
|
|
524
570
|
if (_cachedSupport !== null) return _cachedSupport;
|
|
525
571
|
try {
|
|
526
|
-
const helpOutput =
|
|
572
|
+
const helpOutput = execSync3("claude --help 2>&1", {
|
|
527
573
|
encoding: "utf-8",
|
|
528
574
|
timeout: 5e3
|
|
529
575
|
});
|
|
@@ -589,17 +635,17 @@ var init_provider_table = __esm({
|
|
|
589
635
|
});
|
|
590
636
|
|
|
591
637
|
// src/lib/intercom-queue.ts
|
|
592
|
-
import { readFileSync as
|
|
593
|
-
import
|
|
594
|
-
import
|
|
638
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, renameSync as renameSync3, existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
|
|
639
|
+
import path5 from "path";
|
|
640
|
+
import os5 from "os";
|
|
595
641
|
function ensureDir() {
|
|
596
|
-
const dir =
|
|
597
|
-
if (!
|
|
642
|
+
const dir = path5.dirname(QUEUE_PATH);
|
|
643
|
+
if (!existsSync5(dir)) mkdirSync2(dir, { recursive: true });
|
|
598
644
|
}
|
|
599
645
|
function readQueue() {
|
|
600
646
|
try {
|
|
601
|
-
if (!
|
|
602
|
-
return JSON.parse(
|
|
647
|
+
if (!existsSync5(QUEUE_PATH)) return [];
|
|
648
|
+
return JSON.parse(readFileSync5(QUEUE_PATH, "utf8"));
|
|
603
649
|
} catch {
|
|
604
650
|
return [];
|
|
605
651
|
}
|
|
@@ -607,8 +653,8 @@ function readQueue() {
|
|
|
607
653
|
function writeQueue(queue) {
|
|
608
654
|
ensureDir();
|
|
609
655
|
const tmp = `${QUEUE_PATH}.tmp`;
|
|
610
|
-
|
|
611
|
-
|
|
656
|
+
writeFileSync3(tmp, JSON.stringify(queue, null, 2));
|
|
657
|
+
renameSync3(tmp, QUEUE_PATH);
|
|
612
658
|
}
|
|
613
659
|
function queueIntercom(targetSession, reason) {
|
|
614
660
|
const queue = readQueue();
|
|
@@ -631,42 +677,9 @@ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
|
|
|
631
677
|
var init_intercom_queue = __esm({
|
|
632
678
|
"src/lib/intercom-queue.ts"() {
|
|
633
679
|
"use strict";
|
|
634
|
-
QUEUE_PATH =
|
|
680
|
+
QUEUE_PATH = path5.join(os5.homedir(), ".exe-os", "intercom-queue.json");
|
|
635
681
|
TTL_MS = 60 * 60 * 1e3;
|
|
636
|
-
INTERCOM_LOG =
|
|
637
|
-
}
|
|
638
|
-
});
|
|
639
|
-
|
|
640
|
-
// src/lib/employees.ts
|
|
641
|
-
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
642
|
-
import { existsSync as existsSync5, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
643
|
-
import { execSync as execSync3 } from "child_process";
|
|
644
|
-
import path5 from "path";
|
|
645
|
-
import os5 from "os";
|
|
646
|
-
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
647
|
-
if (!existsSync5(employeesPath)) return [];
|
|
648
|
-
try {
|
|
649
|
-
return JSON.parse(readFileSync5(employeesPath, "utf-8"));
|
|
650
|
-
} catch {
|
|
651
|
-
return [];
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
function getEmployee(employees, name) {
|
|
655
|
-
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
656
|
-
}
|
|
657
|
-
function isMultiInstance(agentName, employees) {
|
|
658
|
-
const roster = employees ?? loadEmployeesSync();
|
|
659
|
-
const emp = getEmployee(roster, agentName);
|
|
660
|
-
if (!emp) return false;
|
|
661
|
-
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
662
|
-
}
|
|
663
|
-
var EMPLOYEES_PATH, MULTI_INSTANCE_ROLES;
|
|
664
|
-
var init_employees = __esm({
|
|
665
|
-
"src/lib/employees.ts"() {
|
|
666
|
-
"use strict";
|
|
667
|
-
init_config();
|
|
668
|
-
EMPLOYEES_PATH = path5.join(EXE_AI_DIR, "exe-employees.json");
|
|
669
|
-
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
682
|
+
INTERCOM_LOG = path5.join(os5.homedir(), ".exe-os", "intercom.log");
|
|
670
683
|
}
|
|
671
684
|
});
|
|
672
685
|
|
|
@@ -881,7 +894,7 @@ function _resetLastRelaunchCache() {
|
|
|
881
894
|
}
|
|
882
895
|
async function lastResumeCreatedAtMs(agentId) {
|
|
883
896
|
const client = getClient();
|
|
884
|
-
const cmScope = sessionScopeFilter();
|
|
897
|
+
const cmScope = sessionScopeFilter(null);
|
|
885
898
|
const result = await client.execute({
|
|
886
899
|
sql: `SELECT MAX(created_at) AS last_created_at
|
|
887
900
|
FROM tasks
|
|
@@ -906,7 +919,7 @@ async function createOrRefreshResumeTask(agentId, projectDir, openTasks) {
|
|
|
906
919
|
const client = getClient();
|
|
907
920
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
908
921
|
const context = buildResumeContext(agentId, openTasks);
|
|
909
|
-
const rdScope = sessionScopeFilter();
|
|
922
|
+
const rdScope = sessionScopeFilter(null);
|
|
910
923
|
const existing = await client.execute({
|
|
911
924
|
sql: `SELECT id FROM tasks
|
|
912
925
|
WHERE assigned_to = ?
|
|
@@ -940,7 +953,7 @@ async function pollCapacityDead() {
|
|
|
940
953
|
const transport = getTransport();
|
|
941
954
|
const relaunched = [];
|
|
942
955
|
const registered = listSessions().filter(
|
|
943
|
-
(s) => s.agentId !== "exe"
|
|
956
|
+
(s) => s.agentId !== "exe" && !isCoordinatorName(s.agentId)
|
|
944
957
|
);
|
|
945
958
|
if (registered.length === 0) return [];
|
|
946
959
|
let liveSessions;
|
|
@@ -1000,7 +1013,7 @@ async function pollCapacityDead() {
|
|
|
1000
1013
|
reason: "capacity"
|
|
1001
1014
|
});
|
|
1002
1015
|
const client = getClient();
|
|
1003
|
-
const rlScope = sessionScopeFilter();
|
|
1016
|
+
const rlScope = sessionScopeFilter(null);
|
|
1004
1017
|
const openTasks = await client.execute({
|
|
1005
1018
|
sql: `SELECT id, title, priority, task_file, status
|
|
1006
1019
|
FROM tasks
|
|
@@ -1054,6 +1067,7 @@ var init_capacity_monitor = __esm({
|
|
|
1054
1067
|
init_session_kill_telemetry();
|
|
1055
1068
|
init_tmux_routing();
|
|
1056
1069
|
init_task_scope();
|
|
1070
|
+
init_employees();
|
|
1057
1071
|
CAPACITY_PATTERNS = [
|
|
1058
1072
|
/conversation is too long/i,
|
|
1059
1073
|
/maximum context length/i,
|
|
@@ -1203,7 +1217,7 @@ function employeeSessionName(employee, exeSession, instance) {
|
|
|
1203
1217
|
exeSession = root;
|
|
1204
1218
|
} else {
|
|
1205
1219
|
throw new Error(
|
|
1206
|
-
`Invalid
|
|
1220
|
+
`Invalid coordinator session "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name.`
|
|
1207
1221
|
);
|
|
1208
1222
|
}
|
|
1209
1223
|
}
|
|
@@ -1223,8 +1237,10 @@ function parseParentExe(sessionName, agentId) {
|
|
|
1223
1237
|
return match?.[1] ?? null;
|
|
1224
1238
|
}
|
|
1225
1239
|
function extractRootExe(name) {
|
|
1226
|
-
|
|
1227
|
-
|
|
1240
|
+
if (!name) return null;
|
|
1241
|
+
if (!name.includes("-")) return name;
|
|
1242
|
+
const parts = name.split("-").filter(Boolean);
|
|
1243
|
+
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
1228
1244
|
}
|
|
1229
1245
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
1230
1246
|
if (!existsSync8(SESSION_CACHE)) {
|
|
@@ -1369,12 +1385,14 @@ function isSessionBusy(sessionName) {
|
|
|
1369
1385
|
return state === "thinking" || state === "tool";
|
|
1370
1386
|
}
|
|
1371
1387
|
function isExeSession(sessionName) {
|
|
1372
|
-
|
|
1388
|
+
const matchesBaseWithInstance = (baseName) => sessionName === baseName || sessionName.startsWith(baseName) && /^\d+$/.test(sessionName.slice(baseName.length));
|
|
1389
|
+
const coordinatorName = getCoordinatorName();
|
|
1390
|
+
return matchesBaseWithInstance(coordinatorName) || matchesBaseWithInstance("exe");
|
|
1373
1391
|
}
|
|
1374
1392
|
function sendIntercom(targetSession) {
|
|
1375
1393
|
const transport = getTransport();
|
|
1376
1394
|
if (isExeSession(targetSession)) {
|
|
1377
|
-
logIntercom(`
|
|
1395
|
+
logIntercom(`SKIP_COORDINATOR \u2192 ${targetSession} (coordinator sessions use prompt-submit hook)`);
|
|
1378
1396
|
return "skipped_exe";
|
|
1379
1397
|
}
|
|
1380
1398
|
if (isDebounced(targetSession)) {
|
|
@@ -1426,7 +1444,7 @@ function notifyParentExe(sessionKey) {
|
|
|
1426
1444
|
if (result === "failed") {
|
|
1427
1445
|
const rootExe = resolveExeSession();
|
|
1428
1446
|
if (rootExe && rootExe !== target) {
|
|
1429
|
-
process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root
|
|
1447
|
+
process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root coordinator session ${rootExe}
|
|
1430
1448
|
`);
|
|
1431
1449
|
const fallback = sendIntercom(rootExe);
|
|
1432
1450
|
return fallback !== "failed";
|
|
@@ -1436,8 +1454,8 @@ function notifyParentExe(sessionKey) {
|
|
|
1436
1454
|
return true;
|
|
1437
1455
|
}
|
|
1438
1456
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
1439
|
-
if (employeeName === "exe") {
|
|
1440
|
-
return { status: "failed", sessionName: "", error: "
|
|
1457
|
+
if (employeeName === "exe" || isCoordinatorName(employeeName)) {
|
|
1458
|
+
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
1441
1459
|
}
|
|
1442
1460
|
try {
|
|
1443
1461
|
assertEmployeeLimitSync();
|
|
@@ -1446,8 +1464,8 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
1446
1464
|
return { status: "failed", sessionName: "", error: err.message };
|
|
1447
1465
|
}
|
|
1448
1466
|
}
|
|
1449
|
-
if (
|
|
1450
|
-
const bare = employeeName.
|
|
1467
|
+
if (employeeName.includes("-")) {
|
|
1468
|
+
const bare = employeeName.split("-")[0].replace(/\d+$/, "");
|
|
1451
1469
|
return {
|
|
1452
1470
|
status: "failed",
|
|
1453
1471
|
sessionName: "",
|
|
@@ -1466,7 +1484,7 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
1466
1484
|
return {
|
|
1467
1485
|
status: "failed",
|
|
1468
1486
|
sessionName: "",
|
|
1469
|
-
error: `Invalid
|
|
1487
|
+
error: `Invalid coordinator session "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name.`
|
|
1470
1488
|
};
|
|
1471
1489
|
}
|
|
1472
1490
|
}
|
|
@@ -1623,8 +1641,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
1623
1641
|
const ctxContent = [
|
|
1624
1642
|
`## Session Context`,
|
|
1625
1643
|
`You are running in tmux session: ${sessionName}.`,
|
|
1626
|
-
`Your parent
|
|
1627
|
-
`Your employees (if any) use the -${exeSession} suffix
|
|
1644
|
+
`Your parent coordinator session is ${exeSession}.`,
|
|
1645
|
+
`Your employees (if any) use the -${exeSession} suffix.`
|
|
1628
1646
|
].join("\n");
|
|
1629
1647
|
writeFileSync5(ctxFile, ctxContent);
|
|
1630
1648
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
@@ -1728,6 +1746,7 @@ var init_tmux_routing = __esm({
|
|
|
1728
1746
|
init_provider_table();
|
|
1729
1747
|
init_intercom_queue();
|
|
1730
1748
|
init_plan_limits();
|
|
1749
|
+
init_employees();
|
|
1731
1750
|
SPAWN_LOCK_DIR = path8.join(os6.homedir(), ".exe-os", "spawn-locks");
|
|
1732
1751
|
SESSION_CACHE = path8.join(os6.homedir(), ".exe-os", "session-cache");
|
|
1733
1752
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
@@ -2010,6 +2029,36 @@ async function listTasks(input) {
|
|
|
2010
2029
|
tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
|
|
2011
2030
|
}));
|
|
2012
2031
|
}
|
|
2032
|
+
function isTmuxSessionAlive(identifier) {
|
|
2033
|
+
if (!identifier || identifier === "unknown") return true;
|
|
2034
|
+
try {
|
|
2035
|
+
if (identifier.startsWith("%")) {
|
|
2036
|
+
const output = execSync5("tmux list-panes -a -F '#{pane_id}'", {
|
|
2037
|
+
timeout: 2e3,
|
|
2038
|
+
encoding: "utf8",
|
|
2039
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2040
|
+
});
|
|
2041
|
+
return output.split("\n").some((l) => l.trim() === identifier);
|
|
2042
|
+
} else {
|
|
2043
|
+
execSync5(`tmux has-session -t ${JSON.stringify(identifier)}`, {
|
|
2044
|
+
timeout: 2e3,
|
|
2045
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2046
|
+
});
|
|
2047
|
+
return true;
|
|
2048
|
+
}
|
|
2049
|
+
} catch {
|
|
2050
|
+
if (identifier.startsWith("%")) return true;
|
|
2051
|
+
try {
|
|
2052
|
+
execSync5("tmux list-sessions", {
|
|
2053
|
+
timeout: 2e3,
|
|
2054
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2055
|
+
});
|
|
2056
|
+
return false;
|
|
2057
|
+
} catch {
|
|
2058
|
+
return true;
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2013
2062
|
function checkStaleCompletion(taskContext, taskCreatedAt) {
|
|
2014
2063
|
if (!taskContext) return null;
|
|
2015
2064
|
if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
|
|
@@ -2072,13 +2121,59 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
2072
2121
|
});
|
|
2073
2122
|
if (claim.rowsAffected === 0) {
|
|
2074
2123
|
const current = await client.execute({
|
|
2075
|
-
sql: "SELECT status, assigned_tmux FROM tasks WHERE id = ?",
|
|
2124
|
+
sql: "SELECT status, assigned_tmux, assigned_by FROM tasks WHERE id = ?",
|
|
2076
2125
|
args: [taskId]
|
|
2077
2126
|
});
|
|
2078
2127
|
const cur = current.rows[0];
|
|
2079
|
-
const
|
|
2080
|
-
const
|
|
2081
|
-
|
|
2128
|
+
const curStatus = cur?.status ?? "unknown";
|
|
2129
|
+
const claimedBySession = cur?.assigned_tmux ?? "";
|
|
2130
|
+
const assignedBy = cur?.assigned_by ?? "";
|
|
2131
|
+
if (curStatus === "in_progress" && claimedBySession && !isTmuxSessionAlive(claimedBySession)) {
|
|
2132
|
+
process.stderr.write(
|
|
2133
|
+
`[tasks] Auto-releasing dead claim on ${taskId} (was ${claimedBySession})
|
|
2134
|
+
`
|
|
2135
|
+
);
|
|
2136
|
+
await client.execute({
|
|
2137
|
+
sql: "UPDATE tasks SET status = 'open', assigned_tmux = NULL, updated_at = ? WHERE id = ?",
|
|
2138
|
+
args: [now, taskId]
|
|
2139
|
+
});
|
|
2140
|
+
const retried = await client.execute({
|
|
2141
|
+
sql: `UPDATE tasks SET status = 'in_progress', assigned_tmux = ?, updated_at = ? WHERE id = ? AND status = 'open'`,
|
|
2142
|
+
args: [tmuxSession, now, taskId]
|
|
2143
|
+
});
|
|
2144
|
+
if (retried.rowsAffected > 0) {
|
|
2145
|
+
try {
|
|
2146
|
+
await writeCheckpoint({
|
|
2147
|
+
taskId,
|
|
2148
|
+
step: "reclaimed_dead_session",
|
|
2149
|
+
contextSummary: `Task reclaimed after dead session ${claimedBySession} released.`
|
|
2150
|
+
});
|
|
2151
|
+
} catch {
|
|
2152
|
+
}
|
|
2153
|
+
return { row, taskFile, now, taskId };
|
|
2154
|
+
}
|
|
2155
|
+
}
|
|
2156
|
+
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || input.callerAgentId === "exe")) {
|
|
2157
|
+
process.stderr.write(
|
|
2158
|
+
`[tasks] Assigner override: ${input.callerAgentId} reclaiming ${taskId}
|
|
2159
|
+
`
|
|
2160
|
+
);
|
|
2161
|
+
await client.execute({
|
|
2162
|
+
sql: `UPDATE tasks SET status = 'in_progress', assigned_tmux = ?, updated_at = ? WHERE id = ?`,
|
|
2163
|
+
args: [tmuxSession, now, taskId]
|
|
2164
|
+
});
|
|
2165
|
+
try {
|
|
2166
|
+
await writeCheckpoint({
|
|
2167
|
+
taskId,
|
|
2168
|
+
step: "assigner_override",
|
|
2169
|
+
contextSummary: `Task force-reclaimed by assigner ${input.callerAgentId}.`
|
|
2170
|
+
});
|
|
2171
|
+
} catch {
|
|
2172
|
+
}
|
|
2173
|
+
return { row, taskFile, now, taskId };
|
|
2174
|
+
}
|
|
2175
|
+
const claimedBy = claimedBySession ? ` (claimed by ${claimedBySession})` : "";
|
|
2176
|
+
throw new Error(`${TASK_ALREADY_CLAIMED_PREFIX}: task ${taskId} is ${curStatus}${claimedBy}`);
|
|
2082
2177
|
}
|
|
2083
2178
|
try {
|
|
2084
2179
|
await writeCheckpoint({
|
|
@@ -2176,7 +2271,7 @@ var init_tasks_crud = __esm({
|
|
|
2176
2271
|
"use strict";
|
|
2177
2272
|
init_database();
|
|
2178
2273
|
init_task_scope();
|
|
2179
|
-
DELEGATION_KEYWORDS = /parallel|delegate|wave|
|
|
2274
|
+
DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
|
|
2180
2275
|
TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
|
|
2181
2276
|
}
|
|
2182
2277
|
});
|
|
@@ -2533,7 +2628,7 @@ function findSessionForProject(projectName) {
|
|
|
2533
2628
|
const sessions = listSessions();
|
|
2534
2629
|
for (const s of sessions) {
|
|
2535
2630
|
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
2536
|
-
if (proj === projectName && s.agentId === "exe") return s;
|
|
2631
|
+
if (proj === projectName && (s.agentId === "exe" || isCoordinatorName(s.agentId))) return s;
|
|
2537
2632
|
}
|
|
2538
2633
|
return null;
|
|
2539
2634
|
}
|
|
@@ -2573,12 +2668,13 @@ var init_session_scope = __esm({
|
|
|
2573
2668
|
init_session_registry();
|
|
2574
2669
|
init_project_name();
|
|
2575
2670
|
init_tmux_routing();
|
|
2671
|
+
init_employees();
|
|
2576
2672
|
}
|
|
2577
2673
|
});
|
|
2578
2674
|
|
|
2579
2675
|
// src/lib/tasks-notify.ts
|
|
2580
2676
|
async function dispatchTaskToEmployee(input) {
|
|
2581
|
-
if (input.assignedTo === "exe") return { dispatched: "skipped" };
|
|
2677
|
+
if (input.assignedTo === "exe" || isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
|
|
2582
2678
|
let crossProject = false;
|
|
2583
2679
|
if (input.projectName) {
|
|
2584
2680
|
try {
|
|
@@ -3021,6 +3117,24 @@ async function updateTask(input) {
|
|
|
3021
3117
|
});
|
|
3022
3118
|
} catch {
|
|
3023
3119
|
}
|
|
3120
|
+
const assignedAgent = String(row.assigned_to);
|
|
3121
|
+
if (!isCoordinatorName(assignedAgent)) {
|
|
3122
|
+
try {
|
|
3123
|
+
const draftClient = getClient();
|
|
3124
|
+
if (input.status === "done") {
|
|
3125
|
+
await draftClient.execute({
|
|
3126
|
+
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
3127
|
+
args: [assignedAgent]
|
|
3128
|
+
});
|
|
3129
|
+
} else if (input.status === "cancelled") {
|
|
3130
|
+
await draftClient.execute({
|
|
3131
|
+
sql: `DELETE FROM memories WHERE agent_id = ? AND draft = 1`,
|
|
3132
|
+
args: [assignedAgent]
|
|
3133
|
+
});
|
|
3134
|
+
}
|
|
3135
|
+
} catch {
|
|
3136
|
+
}
|
|
3137
|
+
}
|
|
3024
3138
|
try {
|
|
3025
3139
|
const client = getClient();
|
|
3026
3140
|
const cascaded = await client.execute({
|
|
@@ -3039,8 +3153,8 @@ async function updateTask(input) {
|
|
|
3039
3153
|
}
|
|
3040
3154
|
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
3041
3155
|
if (isTerminal) {
|
|
3042
|
-
const
|
|
3043
|
-
if (!
|
|
3156
|
+
const isCoordinator = String(row.assigned_to) === "exe" || isCoordinatorName(String(row.assigned_to));
|
|
3157
|
+
if (!isCoordinator) {
|
|
3044
3158
|
notifyTaskDone();
|
|
3045
3159
|
}
|
|
3046
3160
|
await markTaskNotificationsRead(taskFile);
|
|
@@ -3064,7 +3178,7 @@ async function updateTask(input) {
|
|
|
3064
3178
|
}
|
|
3065
3179
|
}
|
|
3066
3180
|
}
|
|
3067
|
-
if (input.status === "done" && String(row.assigned_to) !== "exe" && !process.env.VITEST) {
|
|
3181
|
+
if (input.status === "done" && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
3068
3182
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
3069
3183
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
3070
3184
|
taskId,
|
|
@@ -3080,7 +3194,7 @@ async function updateTask(input) {
|
|
|
3080
3194
|
});
|
|
3081
3195
|
}
|
|
3082
3196
|
let nextTask;
|
|
3083
|
-
if (isTerminal && String(row.assigned_to) !== "exe") {
|
|
3197
|
+
if (isTerminal && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to))) {
|
|
3084
3198
|
try {
|
|
3085
3199
|
nextTask = await findNextTask(String(row.assigned_to));
|
|
3086
3200
|
} catch {
|
|
@@ -3107,12 +3221,14 @@ async function updateTask(input) {
|
|
|
3107
3221
|
async function deleteTask(taskId, baseDir) {
|
|
3108
3222
|
const client = getClient();
|
|
3109
3223
|
const { taskFile, assignedTo, assignedBy, taskSlug } = await deleteTaskCore(taskId, baseDir);
|
|
3110
|
-
const
|
|
3224
|
+
const coordinatorName = getCoordinatorName();
|
|
3225
|
+
const reviewer = assignedBy || coordinatorName;
|
|
3111
3226
|
const reviewSlug = `review-${assignedTo}-${taskSlug}`;
|
|
3112
3227
|
const reviewFile = `exe/${reviewer}/${reviewSlug}.md`;
|
|
3228
|
+
const legacyReviewFile = `exe/${coordinatorName}/${reviewSlug}.md`;
|
|
3113
3229
|
await client.execute({
|
|
3114
|
-
sql: "DELETE FROM tasks WHERE task_file = ? OR task_file = ?",
|
|
3115
|
-
args: [reviewFile, `exe/exe/${reviewSlug}.md`]
|
|
3230
|
+
sql: "DELETE FROM tasks WHERE task_file = ? OR task_file = ? OR task_file = ?",
|
|
3231
|
+
args: [reviewFile, legacyReviewFile, `exe/exe/${reviewSlug}.md`]
|
|
3116
3232
|
});
|
|
3117
3233
|
await markAsReadByTaskFile(taskFile);
|
|
3118
3234
|
await markAsReadByTaskFile(reviewFile);
|
|
@@ -3123,6 +3239,7 @@ var init_tasks = __esm({
|
|
|
3123
3239
|
init_config();
|
|
3124
3240
|
init_notifications();
|
|
3125
3241
|
init_state_bus();
|
|
3242
|
+
init_employees();
|
|
3126
3243
|
init_tasks_crud();
|
|
3127
3244
|
init_tasks_review();
|
|
3128
3245
|
init_tasks_crud();
|