@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
|
@@ -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,80 @@ 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 canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
|
|
235
|
+
return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
|
|
236
|
+
}
|
|
237
|
+
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
238
|
+
if (!existsSync2(employeesPath)) return [];
|
|
239
|
+
try {
|
|
240
|
+
return JSON.parse(readFileSync2(employeesPath, "utf-8"));
|
|
241
|
+
} catch {
|
|
242
|
+
return [];
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
function getEmployee(employees, name) {
|
|
246
|
+
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
247
|
+
}
|
|
248
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE;
|
|
249
|
+
var init_employees = __esm({
|
|
250
|
+
"src/lib/employees.ts"() {
|
|
251
|
+
"use strict";
|
|
252
|
+
init_config();
|
|
253
|
+
EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
|
|
254
|
+
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
255
|
+
COORDINATOR_ROLE = "COO";
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// src/lib/database.ts
|
|
260
|
+
import { createClient } from "@libsql/client";
|
|
261
|
+
function getClient() {
|
|
262
|
+
if (!_resilientClient) {
|
|
263
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
264
|
+
}
|
|
265
|
+
return _resilientClient;
|
|
266
|
+
}
|
|
267
|
+
var _resilientClient;
|
|
268
|
+
var init_database = __esm({
|
|
269
|
+
"src/lib/database.ts"() {
|
|
270
|
+
"use strict";
|
|
271
|
+
init_db_retry();
|
|
272
|
+
init_employees();
|
|
273
|
+
_resilientClient = null;
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// src/lib/notifications.ts
|
|
278
|
+
import crypto from "crypto";
|
|
279
|
+
import path3 from "path";
|
|
280
|
+
import os3 from "os";
|
|
239
281
|
import {
|
|
240
|
-
readFileSync as
|
|
282
|
+
readFileSync as readFileSync3,
|
|
241
283
|
readdirSync,
|
|
242
|
-
unlinkSync,
|
|
243
|
-
existsSync as
|
|
284
|
+
unlinkSync as unlinkSync2,
|
|
285
|
+
existsSync as existsSync3,
|
|
244
286
|
rmdirSync
|
|
245
287
|
} from "fs";
|
|
246
288
|
async function writeNotification(notification) {
|
|
@@ -340,24 +382,24 @@ var init_state_bus = __esm({
|
|
|
340
382
|
});
|
|
341
383
|
|
|
342
384
|
// src/lib/session-registry.ts
|
|
343
|
-
import
|
|
344
|
-
import
|
|
385
|
+
import path4 from "path";
|
|
386
|
+
import os4 from "os";
|
|
345
387
|
var REGISTRY_PATH;
|
|
346
388
|
var init_session_registry = __esm({
|
|
347
389
|
"src/lib/session-registry.ts"() {
|
|
348
390
|
"use strict";
|
|
349
|
-
REGISTRY_PATH =
|
|
391
|
+
REGISTRY_PATH = path4.join(os4.homedir(), ".exe-os", "session-registry.json");
|
|
350
392
|
}
|
|
351
393
|
});
|
|
352
394
|
|
|
353
395
|
// src/lib/session-key.ts
|
|
354
|
-
import { execSync } from "child_process";
|
|
396
|
+
import { execSync as execSync2 } from "child_process";
|
|
355
397
|
function getSessionKey() {
|
|
356
398
|
if (_cached) return _cached;
|
|
357
399
|
let pid = process.ppid;
|
|
358
400
|
for (let i = 0; i < 10; i++) {
|
|
359
401
|
try {
|
|
360
|
-
const info =
|
|
402
|
+
const info = execSync2(`ps -p ${pid} -o ppid=,comm=`, {
|
|
361
403
|
encoding: "utf8",
|
|
362
404
|
timeout: 2e3
|
|
363
405
|
}).trim();
|
|
@@ -493,7 +535,7 @@ var init_transport = __esm({
|
|
|
493
535
|
});
|
|
494
536
|
|
|
495
537
|
// src/lib/cc-agent-support.ts
|
|
496
|
-
import { execSync as
|
|
538
|
+
import { execSync as execSync3 } from "child_process";
|
|
497
539
|
var init_cc_agent_support = __esm({
|
|
498
540
|
"src/lib/cc-agent-support.ts"() {
|
|
499
541
|
"use strict";
|
|
@@ -522,17 +564,17 @@ var init_provider_table = __esm({
|
|
|
522
564
|
});
|
|
523
565
|
|
|
524
566
|
// src/lib/intercom-queue.ts
|
|
525
|
-
import { readFileSync as
|
|
526
|
-
import
|
|
527
|
-
import
|
|
567
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, renameSync as renameSync3, existsSync as existsSync4, mkdirSync } from "fs";
|
|
568
|
+
import path5 from "path";
|
|
569
|
+
import os5 from "os";
|
|
528
570
|
function ensureDir() {
|
|
529
|
-
const dir =
|
|
530
|
-
if (!
|
|
571
|
+
const dir = path5.dirname(QUEUE_PATH);
|
|
572
|
+
if (!existsSync4(dir)) mkdirSync(dir, { recursive: true });
|
|
531
573
|
}
|
|
532
574
|
function readQueue() {
|
|
533
575
|
try {
|
|
534
|
-
if (!
|
|
535
|
-
return JSON.parse(
|
|
576
|
+
if (!existsSync4(QUEUE_PATH)) return [];
|
|
577
|
+
return JSON.parse(readFileSync4(QUEUE_PATH, "utf8"));
|
|
536
578
|
} catch {
|
|
537
579
|
return [];
|
|
538
580
|
}
|
|
@@ -540,8 +582,8 @@ function readQueue() {
|
|
|
540
582
|
function writeQueue(queue) {
|
|
541
583
|
ensureDir();
|
|
542
584
|
const tmp = `${QUEUE_PATH}.tmp`;
|
|
543
|
-
|
|
544
|
-
|
|
585
|
+
writeFileSync2(tmp, JSON.stringify(queue, null, 2));
|
|
586
|
+
renameSync3(tmp, QUEUE_PATH);
|
|
545
587
|
}
|
|
546
588
|
function queueIntercom(targetSession, reason) {
|
|
547
589
|
const queue = readQueue();
|
|
@@ -564,24 +606,9 @@ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
|
|
|
564
606
|
var init_intercom_queue = __esm({
|
|
565
607
|
"src/lib/intercom-queue.ts"() {
|
|
566
608
|
"use strict";
|
|
567
|
-
QUEUE_PATH =
|
|
609
|
+
QUEUE_PATH = path5.join(os5.homedir(), ".exe-os", "intercom-queue.json");
|
|
568
610
|
TTL_MS = 60 * 60 * 1e3;
|
|
569
|
-
INTERCOM_LOG =
|
|
570
|
-
}
|
|
571
|
-
});
|
|
572
|
-
|
|
573
|
-
// src/lib/employees.ts
|
|
574
|
-
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
575
|
-
import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync4, renameSync as renameSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
576
|
-
import { execSync as execSync3 } from "child_process";
|
|
577
|
-
import path5 from "path";
|
|
578
|
-
import os5 from "os";
|
|
579
|
-
var EMPLOYEES_PATH;
|
|
580
|
-
var init_employees = __esm({
|
|
581
|
-
"src/lib/employees.ts"() {
|
|
582
|
-
"use strict";
|
|
583
|
-
init_config();
|
|
584
|
-
EMPLOYEES_PATH = path5.join(EXE_AI_DIR, "exe-employees.json");
|
|
611
|
+
INTERCOM_LOG = path5.join(os5.homedir(), ".exe-os", "intercom.log");
|
|
585
612
|
}
|
|
586
613
|
});
|
|
587
614
|
|
|
@@ -625,8 +652,10 @@ function getMySession() {
|
|
|
625
652
|
return getTransport().getMySession();
|
|
626
653
|
}
|
|
627
654
|
function extractRootExe(name) {
|
|
628
|
-
|
|
629
|
-
|
|
655
|
+
if (!name) return null;
|
|
656
|
+
if (!name.includes("-")) return name;
|
|
657
|
+
const parts = name.split("-").filter(Boolean);
|
|
658
|
+
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
630
659
|
}
|
|
631
660
|
function getParentExe(sessionKey) {
|
|
632
661
|
try {
|
|
@@ -717,12 +746,14 @@ function getSessionState(sessionName) {
|
|
|
717
746
|
}
|
|
718
747
|
}
|
|
719
748
|
function isExeSession(sessionName) {
|
|
720
|
-
|
|
749
|
+
const matchesBaseWithInstance = (baseName) => sessionName === baseName || sessionName.startsWith(baseName) && /^\d+$/.test(sessionName.slice(baseName.length));
|
|
750
|
+
const coordinatorName = getCoordinatorName();
|
|
751
|
+
return matchesBaseWithInstance(coordinatorName) || matchesBaseWithInstance("exe");
|
|
721
752
|
}
|
|
722
753
|
function sendIntercom(targetSession) {
|
|
723
754
|
const transport = getTransport();
|
|
724
755
|
if (isExeSession(targetSession)) {
|
|
725
|
-
logIntercom(`
|
|
756
|
+
logIntercom(`SKIP_COORDINATOR \u2192 ${targetSession} (coordinator sessions use prompt-submit hook)`);
|
|
726
757
|
return "skipped_exe";
|
|
727
758
|
}
|
|
728
759
|
if (isDebounced(targetSession)) {
|
|
@@ -774,7 +805,7 @@ function notifyParentExe(sessionKey) {
|
|
|
774
805
|
if (result === "failed") {
|
|
775
806
|
const rootExe = resolveExeSession();
|
|
776
807
|
if (rootExe && rootExe !== target) {
|
|
777
|
-
process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root
|
|
808
|
+
process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root coordinator session ${rootExe}
|
|
778
809
|
`);
|
|
779
810
|
const fallback = sendIntercom(rootExe);
|
|
780
811
|
return fallback !== "failed";
|
|
@@ -795,6 +826,7 @@ var init_tmux_routing = __esm({
|
|
|
795
826
|
init_provider_table();
|
|
796
827
|
init_intercom_queue();
|
|
797
828
|
init_plan_limits();
|
|
829
|
+
init_employees();
|
|
798
830
|
SPAWN_LOCK_DIR = path8.join(os6.homedir(), ".exe-os", "spawn-locks");
|
|
799
831
|
SESSION_CACHE = path8.join(os6.homedir(), ".exe-os", "session-cache");
|
|
800
832
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
@@ -909,6 +941,36 @@ async function resolveTask(client, identifier, scopeSession) {
|
|
|
909
941
|
}
|
|
910
942
|
throw new Error(`Task not found: ${identifier}`);
|
|
911
943
|
}
|
|
944
|
+
function isTmuxSessionAlive(identifier) {
|
|
945
|
+
if (!identifier || identifier === "unknown") return true;
|
|
946
|
+
try {
|
|
947
|
+
if (identifier.startsWith("%")) {
|
|
948
|
+
const output = execSync4("tmux list-panes -a -F '#{pane_id}'", {
|
|
949
|
+
timeout: 2e3,
|
|
950
|
+
encoding: "utf8",
|
|
951
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
952
|
+
});
|
|
953
|
+
return output.split("\n").some((l) => l.trim() === identifier);
|
|
954
|
+
} else {
|
|
955
|
+
execSync4(`tmux has-session -t ${JSON.stringify(identifier)}`, {
|
|
956
|
+
timeout: 2e3,
|
|
957
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
958
|
+
});
|
|
959
|
+
return true;
|
|
960
|
+
}
|
|
961
|
+
} catch {
|
|
962
|
+
if (identifier.startsWith("%")) return true;
|
|
963
|
+
try {
|
|
964
|
+
execSync4("tmux list-sessions", {
|
|
965
|
+
timeout: 2e3,
|
|
966
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
967
|
+
});
|
|
968
|
+
return false;
|
|
969
|
+
} catch {
|
|
970
|
+
return true;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
}
|
|
912
974
|
function checkStaleCompletion(taskContext, taskCreatedAt) {
|
|
913
975
|
if (!taskContext) return null;
|
|
914
976
|
if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
|
|
@@ -971,13 +1033,59 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
971
1033
|
});
|
|
972
1034
|
if (claim.rowsAffected === 0) {
|
|
973
1035
|
const current = await client.execute({
|
|
974
|
-
sql: "SELECT status, assigned_tmux FROM tasks WHERE id = ?",
|
|
1036
|
+
sql: "SELECT status, assigned_tmux, assigned_by FROM tasks WHERE id = ?",
|
|
975
1037
|
args: [taskId]
|
|
976
1038
|
});
|
|
977
1039
|
const cur = current.rows[0];
|
|
978
|
-
const
|
|
979
|
-
const
|
|
980
|
-
|
|
1040
|
+
const curStatus = cur?.status ?? "unknown";
|
|
1041
|
+
const claimedBySession = cur?.assigned_tmux ?? "";
|
|
1042
|
+
const assignedBy = cur?.assigned_by ?? "";
|
|
1043
|
+
if (curStatus === "in_progress" && claimedBySession && !isTmuxSessionAlive(claimedBySession)) {
|
|
1044
|
+
process.stderr.write(
|
|
1045
|
+
`[tasks] Auto-releasing dead claim on ${taskId} (was ${claimedBySession})
|
|
1046
|
+
`
|
|
1047
|
+
);
|
|
1048
|
+
await client.execute({
|
|
1049
|
+
sql: "UPDATE tasks SET status = 'open', assigned_tmux = NULL, updated_at = ? WHERE id = ?",
|
|
1050
|
+
args: [now, taskId]
|
|
1051
|
+
});
|
|
1052
|
+
const retried = await client.execute({
|
|
1053
|
+
sql: `UPDATE tasks SET status = 'in_progress', assigned_tmux = ?, updated_at = ? WHERE id = ? AND status = 'open'`,
|
|
1054
|
+
args: [tmuxSession, now, taskId]
|
|
1055
|
+
});
|
|
1056
|
+
if (retried.rowsAffected > 0) {
|
|
1057
|
+
try {
|
|
1058
|
+
await writeCheckpoint({
|
|
1059
|
+
taskId,
|
|
1060
|
+
step: "reclaimed_dead_session",
|
|
1061
|
+
contextSummary: `Task reclaimed after dead session ${claimedBySession} released.`
|
|
1062
|
+
});
|
|
1063
|
+
} catch {
|
|
1064
|
+
}
|
|
1065
|
+
return { row, taskFile, now, taskId };
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || input.callerAgentId === "exe")) {
|
|
1069
|
+
process.stderr.write(
|
|
1070
|
+
`[tasks] Assigner override: ${input.callerAgentId} reclaiming ${taskId}
|
|
1071
|
+
`
|
|
1072
|
+
);
|
|
1073
|
+
await client.execute({
|
|
1074
|
+
sql: `UPDATE tasks SET status = 'in_progress', assigned_tmux = ?, updated_at = ? WHERE id = ?`,
|
|
1075
|
+
args: [tmuxSession, now, taskId]
|
|
1076
|
+
});
|
|
1077
|
+
try {
|
|
1078
|
+
await writeCheckpoint({
|
|
1079
|
+
taskId,
|
|
1080
|
+
step: "assigner_override",
|
|
1081
|
+
contextSummary: `Task force-reclaimed by assigner ${input.callerAgentId}.`
|
|
1082
|
+
});
|
|
1083
|
+
} catch {
|
|
1084
|
+
}
|
|
1085
|
+
return { row, taskFile, now, taskId };
|
|
1086
|
+
}
|
|
1087
|
+
const claimedBy = claimedBySession ? ` (claimed by ${claimedBySession})` : "";
|
|
1088
|
+
throw new Error(`${TASK_ALREADY_CLAIMED_PREFIX}: task ${taskId} is ${curStatus}${claimedBy}`);
|
|
981
1089
|
}
|
|
982
1090
|
try {
|
|
983
1091
|
await writeCheckpoint({
|
|
@@ -1016,7 +1124,7 @@ var init_tasks_crud = __esm({
|
|
|
1016
1124
|
"use strict";
|
|
1017
1125
|
init_database();
|
|
1018
1126
|
init_task_scope();
|
|
1019
|
-
DELEGATION_KEYWORDS = /parallel|delegate|wave|
|
|
1127
|
+
DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
|
|
1020
1128
|
TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
|
|
1021
1129
|
}
|
|
1022
1130
|
});
|
|
@@ -1547,6 +1655,24 @@ async function updateTask(input) {
|
|
|
1547
1655
|
});
|
|
1548
1656
|
} catch {
|
|
1549
1657
|
}
|
|
1658
|
+
const assignedAgent = String(row.assigned_to);
|
|
1659
|
+
if (!isCoordinatorName(assignedAgent)) {
|
|
1660
|
+
try {
|
|
1661
|
+
const draftClient = getClient();
|
|
1662
|
+
if (input.status === "done") {
|
|
1663
|
+
await draftClient.execute({
|
|
1664
|
+
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
1665
|
+
args: [assignedAgent]
|
|
1666
|
+
});
|
|
1667
|
+
} else if (input.status === "cancelled") {
|
|
1668
|
+
await draftClient.execute({
|
|
1669
|
+
sql: `DELETE FROM memories WHERE agent_id = ? AND draft = 1`,
|
|
1670
|
+
args: [assignedAgent]
|
|
1671
|
+
});
|
|
1672
|
+
}
|
|
1673
|
+
} catch {
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1550
1676
|
try {
|
|
1551
1677
|
const client = getClient();
|
|
1552
1678
|
const cascaded = await client.execute({
|
|
@@ -1565,8 +1691,8 @@ async function updateTask(input) {
|
|
|
1565
1691
|
}
|
|
1566
1692
|
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
1567
1693
|
if (isTerminal) {
|
|
1568
|
-
const
|
|
1569
|
-
if (!
|
|
1694
|
+
const isCoordinator = String(row.assigned_to) === "exe" || isCoordinatorName(String(row.assigned_to));
|
|
1695
|
+
if (!isCoordinator) {
|
|
1570
1696
|
notifyTaskDone();
|
|
1571
1697
|
}
|
|
1572
1698
|
await markTaskNotificationsRead(taskFile);
|
|
@@ -1590,7 +1716,7 @@ async function updateTask(input) {
|
|
|
1590
1716
|
}
|
|
1591
1717
|
}
|
|
1592
1718
|
}
|
|
1593
|
-
if (input.status === "done" && String(row.assigned_to) !== "exe" && !process.env.VITEST) {
|
|
1719
|
+
if (input.status === "done" && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
1594
1720
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
1595
1721
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
1596
1722
|
taskId,
|
|
@@ -1606,7 +1732,7 @@ async function updateTask(input) {
|
|
|
1606
1732
|
});
|
|
1607
1733
|
}
|
|
1608
1734
|
let nextTask;
|
|
1609
|
-
if (isTerminal && String(row.assigned_to) !== "exe") {
|
|
1735
|
+
if (isTerminal && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to))) {
|
|
1610
1736
|
try {
|
|
1611
1737
|
nextTask = await findNextTask(String(row.assigned_to));
|
|
1612
1738
|
} catch {
|
|
@@ -1637,6 +1763,7 @@ var init_tasks = __esm({
|
|
|
1637
1763
|
init_config();
|
|
1638
1764
|
init_notifications();
|
|
1639
1765
|
init_state_bus();
|
|
1766
|
+
init_employees();
|
|
1640
1767
|
init_tasks_crud();
|
|
1641
1768
|
init_tasks_review();
|
|
1642
1769
|
init_tasks_crud();
|
|
@@ -1661,11 +1788,54 @@ __export(active_agent_exports, {
|
|
|
1661
1788
|
clearActiveAgent: () => clearActiveAgent,
|
|
1662
1789
|
getActiveAgent: () => getActiveAgent,
|
|
1663
1790
|
getAllActiveAgents: () => getAllActiveAgents,
|
|
1791
|
+
resolveActiveAgentFromTmuxSession: () => resolveActiveAgentFromTmuxSession,
|
|
1664
1792
|
writeActiveAgent: () => writeActiveAgent
|
|
1665
1793
|
});
|
|
1666
1794
|
import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5, readdirSync as readdirSync3 } from "fs";
|
|
1667
1795
|
import { execSync as execSync5 } from "child_process";
|
|
1668
1796
|
import path13 from "path";
|
|
1797
|
+
function isNameWithOptionalInstance(candidate, baseName) {
|
|
1798
|
+
if (candidate === baseName) return true;
|
|
1799
|
+
if (!candidate.startsWith(baseName)) return false;
|
|
1800
|
+
return /^\d+$/.test(candidate.slice(baseName.length));
|
|
1801
|
+
}
|
|
1802
|
+
function resolveEmployeeFromSessionPrefix(prefix, employees) {
|
|
1803
|
+
const sorted = [...employees].sort((a, b) => b.name.length - a.name.length);
|
|
1804
|
+
for (const employee of sorted) {
|
|
1805
|
+
if (isNameWithOptionalInstance(prefix, employee.name)) {
|
|
1806
|
+
return { agentId: employee.name, agentRole: employee.role };
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
return null;
|
|
1810
|
+
}
|
|
1811
|
+
function resolveActiveAgentFromTmuxSession(sessionName) {
|
|
1812
|
+
const employees = loadEmployeesSync();
|
|
1813
|
+
const coordinator = getCoordinatorEmployee(employees);
|
|
1814
|
+
const coordinatorName = coordinator?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
|
|
1815
|
+
if (isNameWithOptionalInstance(sessionName, coordinatorName)) {
|
|
1816
|
+
return {
|
|
1817
|
+
agentId: coordinatorName,
|
|
1818
|
+
agentRole: coordinator?.role ?? "COO"
|
|
1819
|
+
};
|
|
1820
|
+
}
|
|
1821
|
+
if (isNameWithOptionalInstance(sessionName, DEFAULT_COORDINATOR_TEMPLATE_NAME)) {
|
|
1822
|
+
return {
|
|
1823
|
+
agentId: coordinator?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME,
|
|
1824
|
+
agentRole: coordinator?.role ?? "COO"
|
|
1825
|
+
};
|
|
1826
|
+
}
|
|
1827
|
+
if (sessionName.includes("-")) {
|
|
1828
|
+
const prefix = sessionName.split("-")[0] ?? "";
|
|
1829
|
+
const employee = resolveEmployeeFromSessionPrefix(prefix, employees);
|
|
1830
|
+
if (employee) return employee;
|
|
1831
|
+
const legacy = prefix.match(/^([a-zA-Z]+)\d*$/);
|
|
1832
|
+
if (legacy?.[1] && legacy[1] !== DEFAULT_COORDINATOR_TEMPLATE_NAME) {
|
|
1833
|
+
const emp = getEmployee(employees, legacy[1]);
|
|
1834
|
+
return { agentId: emp?.name ?? legacy[1], agentRole: emp?.role ?? "employee" };
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
return null;
|
|
1838
|
+
}
|
|
1669
1839
|
function getMarkerPath() {
|
|
1670
1840
|
return path13.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
|
|
1671
1841
|
}
|
|
@@ -1718,13 +1888,8 @@ function getActiveAgent() {
|
|
|
1718
1888
|
"tmux display-message -p '#{session_name}' 2>/dev/null",
|
|
1719
1889
|
{ encoding: "utf8", timeout: 2e3 }
|
|
1720
1890
|
).trim();
|
|
1721
|
-
const
|
|
1722
|
-
if (
|
|
1723
|
-
return { agentId: empMatch[1], agentRole: "employee" };
|
|
1724
|
-
}
|
|
1725
|
-
if (/^exe\d+$/.test(sessionName)) {
|
|
1726
|
-
return { agentId: "exe", agentRole: "COO" };
|
|
1727
|
-
}
|
|
1891
|
+
const resolved = resolveActiveAgentFromTmuxSession(sessionName);
|
|
1892
|
+
if (resolved) return resolved;
|
|
1728
1893
|
} catch {
|
|
1729
1894
|
}
|
|
1730
1895
|
return {
|
|
@@ -1785,6 +1950,7 @@ var init_active_agent = __esm({
|
|
|
1785
1950
|
"use strict";
|
|
1786
1951
|
init_config();
|
|
1787
1952
|
init_session_key2();
|
|
1953
|
+
init_employees();
|
|
1788
1954
|
CACHE_DIR = path13.join(EXE_AI_DIR, "session-cache");
|
|
1789
1955
|
STALE_MS = 24 * 60 * 60 * 1e3;
|
|
1790
1956
|
}
|
|
@@ -1794,6 +1960,7 @@ var init_active_agent = __esm({
|
|
|
1794
1960
|
init_tasks();
|
|
1795
1961
|
init_database();
|
|
1796
1962
|
init_tasks_crud();
|
|
1963
|
+
init_employees();
|
|
1797
1964
|
import { z } from "zod";
|
|
1798
1965
|
function registerUpdateTask(server) {
|
|
1799
1966
|
server.registerTool(
|
|
@@ -1813,7 +1980,7 @@ function registerUpdateTask(server) {
|
|
|
1813
1980
|
try {
|
|
1814
1981
|
const { getActiveAgent: getActiveAgent2 } = await Promise.resolve().then(() => (init_active_agent(), active_agent_exports));
|
|
1815
1982
|
const agent = getActiveAgent2();
|
|
1816
|
-
if (agent.agentId
|
|
1983
|
+
if (!canCoordinate(agent.agentId, agent.agentRole)) {
|
|
1817
1984
|
let isOwnReview = false;
|
|
1818
1985
|
try {
|
|
1819
1986
|
const client = getClient();
|
|
@@ -1840,7 +2007,7 @@ function registerUpdateTask(server) {
|
|
|
1840
2007
|
if (assignedBy === "system" && taskFile.includes("review-")) {
|
|
1841
2008
|
const { getActiveAgent: getActiveAgent2 } = await Promise.resolve().then(() => (init_active_agent(), active_agent_exports));
|
|
1842
2009
|
const agent = getActiveAgent2();
|
|
1843
|
-
if (agent.agentId !== assignedTo && agent.agentId
|
|
2010
|
+
if (agent.agentId !== assignedTo && !canCoordinate(agent.agentId, agent.agentRole)) {
|
|
1844
2011
|
process.stderr.write(
|
|
1845
2012
|
`[update_task] BLOCKED: ${agent.agentId} tried to close review "${String(row.title)}" assigned to ${assignedTo}
|
|
1846
2013
|
`
|
|
@@ -1863,13 +2030,20 @@ function registerUpdateTask(server) {
|
|
|
1863
2030
|
};
|
|
1864
2031
|
}
|
|
1865
2032
|
}
|
|
2033
|
+
let callerAgentId;
|
|
2034
|
+
try {
|
|
2035
|
+
const { getActiveAgent: getAgent } = await Promise.resolve().then(() => (init_active_agent(), active_agent_exports));
|
|
2036
|
+
callerAgentId = getAgent().agentId;
|
|
2037
|
+
} catch {
|
|
2038
|
+
}
|
|
1866
2039
|
let task;
|
|
1867
2040
|
try {
|
|
1868
2041
|
task = await updateTask({
|
|
1869
2042
|
taskId: task_id,
|
|
1870
2043
|
status,
|
|
1871
2044
|
result,
|
|
1872
|
-
baseDir: process.cwd()
|
|
2045
|
+
baseDir: process.cwd(),
|
|
2046
|
+
callerAgentId
|
|
1873
2047
|
});
|
|
1874
2048
|
} catch (err) {
|
|
1875
2049
|
const msg = err instanceof Error ? err.message : String(err);
|