@askexenow/exe-os 0.9.8 → 0.9.10
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 +222 -49
- package/dist/bin/backfill-responses.js +221 -48
- package/dist/bin/backfill-vectors.js +225 -52
- package/dist/bin/cleanup-stale-review-tasks.js +150 -28
- package/dist/bin/cli.js +1411 -953
- package/dist/bin/exe-agent-config.js +36 -8
- package/dist/bin/exe-agent.js +14 -4
- package/dist/bin/exe-assign.js +221 -48
- package/dist/bin/exe-boot.js +913 -543
- package/dist/bin/exe-call.js +41 -13
- package/dist/bin/exe-cloud.js +163 -58
- package/dist/bin/exe-dispatch.js +418 -262
- package/dist/bin/exe-doctor.js +145 -27
- package/dist/bin/exe-export-behaviors.js +141 -23
- package/dist/bin/exe-forget.js +137 -19
- package/dist/bin/exe-gateway.js +793 -485
- package/dist/bin/exe-heartbeat.js +227 -108
- package/dist/bin/exe-kill.js +138 -20
- package/dist/bin/exe-launch-agent.js +172 -39
- package/dist/bin/exe-link.js +291 -100
- package/dist/bin/exe-new-employee.js +214 -106
- package/dist/bin/exe-pending-messages.js +395 -33
- package/dist/bin/exe-pending-notifications.js +684 -99
- package/dist/bin/exe-pending-reviews.js +420 -74
- package/dist/bin/exe-rename.js +147 -49
- package/dist/bin/exe-review.js +138 -20
- package/dist/bin/exe-search.js +240 -69
- package/dist/bin/exe-session-cleanup.js +566 -357
- package/dist/bin/exe-settings.js +61 -17
- package/dist/bin/exe-start-codex.js +158 -39
- package/dist/bin/exe-start-opencode.js +157 -38
- package/dist/bin/exe-status.js +151 -29
- package/dist/bin/exe-team.js +138 -20
- package/dist/bin/git-sweep.js +530 -319
- package/dist/bin/graph-backfill.js +137 -19
- package/dist/bin/graph-export.js +140 -22
- package/dist/bin/install.js +90 -61
- package/dist/bin/scan-tasks.js +547 -336
- package/dist/bin/setup.js +564 -293
- package/dist/bin/shard-migrate.js +139 -21
- package/dist/bin/update.js +138 -49
- package/dist/bin/wiki-sync.js +137 -19
- package/dist/gateway/index.js +649 -417
- package/dist/hooks/bug-report-worker.js +486 -316
- package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
- package/dist/hooks/commit-complete.js +528 -317
- package/dist/hooks/error-recall.js +245 -74
- package/dist/hooks/exe-heartbeat-hook.js +16 -6
- package/dist/hooks/ingest-worker.js +3442 -3157
- package/dist/hooks/ingest.js +832 -97
- package/dist/hooks/instructions-loaded.js +227 -54
- package/dist/hooks/notification.js +216 -43
- package/dist/hooks/post-compact.js +239 -62
- package/dist/hooks/pre-compact.js +534 -323
- package/dist/hooks/pre-tool-use.js +268 -90
- package/dist/hooks/prompt-ingest-worker.js +352 -102
- package/dist/hooks/prompt-submit.js +614 -382
- package/dist/hooks/response-ingest-worker.js +372 -122
- package/dist/hooks/session-end.js +569 -347
- package/dist/hooks/session-start.js +313 -127
- package/dist/hooks/stop.js +293 -98
- package/dist/hooks/subagent-stop.js +239 -62
- package/dist/hooks/summary-worker.js +568 -236
- package/dist/index.js +664 -431
- package/dist/lib/agent-config.js +28 -6
- package/dist/lib/cloud-sync.js +284 -105
- package/dist/lib/config.js +30 -10
- package/dist/lib/consolidation.js +16 -6
- package/dist/lib/database.js +123 -25
- package/dist/lib/db-daemon-client.js +73 -19
- package/dist/lib/db.js +123 -25
- package/dist/lib/device-registry.js +133 -35
- package/dist/lib/embedder.js +107 -32
- package/dist/lib/employee-templates.js +14 -4
- package/dist/lib/employees.js +41 -13
- package/dist/lib/exe-daemon-client.js +88 -22
- package/dist/lib/exe-daemon.js +1049 -680
- package/dist/lib/hybrid-search.js +240 -69
- package/dist/lib/identity.js +18 -8
- package/dist/lib/license.js +133 -48
- package/dist/lib/messaging.js +116 -56
- package/dist/lib/reminders.js +14 -4
- package/dist/lib/schedules.js +137 -19
- package/dist/lib/skill-learning.js +33 -6
- package/dist/lib/store.js +137 -19
- package/dist/lib/task-router.js +14 -4
- package/dist/lib/tasks.js +422 -357
- package/dist/lib/tmux-routing.js +314 -248
- package/dist/lib/token-spend.js +26 -8
- package/dist/mcp/server.js +1408 -672
- package/dist/mcp/tools/complete-reminder.js +14 -4
- package/dist/mcp/tools/create-reminder.js +14 -4
- package/dist/mcp/tools/create-task.js +448 -371
- package/dist/mcp/tools/deactivate-behavior.js +16 -6
- package/dist/mcp/tools/list-reminders.js +14 -4
- package/dist/mcp/tools/list-tasks.js +123 -107
- package/dist/mcp/tools/send-message.js +75 -29
- package/dist/mcp/tools/update-task.js +1983 -315
- package/dist/runtime/index.js +567 -355
- package/dist/tui/App.js +887 -531
- package/package.json +4 -4
package/dist/lib/tmux-routing.js
CHANGED
|
@@ -307,9 +307,34 @@ var init_provider_table = __esm({
|
|
|
307
307
|
}
|
|
308
308
|
});
|
|
309
309
|
|
|
310
|
+
// src/lib/secure-files.ts
|
|
311
|
+
import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
312
|
+
import { chmod, mkdir } from "fs/promises";
|
|
313
|
+
async function ensurePrivateDir(dirPath) {
|
|
314
|
+
await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
315
|
+
try {
|
|
316
|
+
await chmod(dirPath, PRIVATE_DIR_MODE);
|
|
317
|
+
} catch {
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
async function enforcePrivateFile(filePath) {
|
|
321
|
+
try {
|
|
322
|
+
await chmod(filePath, PRIVATE_FILE_MODE);
|
|
323
|
+
} catch {
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
|
|
327
|
+
var init_secure_files = __esm({
|
|
328
|
+
"src/lib/secure-files.ts"() {
|
|
329
|
+
"use strict";
|
|
330
|
+
PRIVATE_DIR_MODE = 448;
|
|
331
|
+
PRIVATE_FILE_MODE = 384;
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
|
|
310
335
|
// src/lib/config.ts
|
|
311
|
-
import { readFile, writeFile
|
|
312
|
-
import { readFileSync as readFileSync2, existsSync as
|
|
336
|
+
import { readFile, writeFile } from "fs/promises";
|
|
337
|
+
import { readFileSync as readFileSync2, existsSync as existsSync3, renameSync } from "fs";
|
|
313
338
|
import path2 from "path";
|
|
314
339
|
import os2 from "os";
|
|
315
340
|
function resolveDataDir() {
|
|
@@ -317,7 +342,7 @@ function resolveDataDir() {
|
|
|
317
342
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
318
343
|
const newDir = path2.join(os2.homedir(), ".exe-os");
|
|
319
344
|
const legacyDir = path2.join(os2.homedir(), ".exe-mem");
|
|
320
|
-
if (!
|
|
345
|
+
if (!existsSync3(newDir) && existsSync3(legacyDir)) {
|
|
321
346
|
try {
|
|
322
347
|
renameSync(legacyDir, newDir);
|
|
323
348
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -380,9 +405,9 @@ function normalizeAutoUpdate(raw) {
|
|
|
380
405
|
}
|
|
381
406
|
async function loadConfig() {
|
|
382
407
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
383
|
-
await
|
|
408
|
+
await ensurePrivateDir(dir);
|
|
384
409
|
const configPath = path2.join(dir, "config.json");
|
|
385
|
-
if (!
|
|
410
|
+
if (!existsSync3(configPath)) {
|
|
386
411
|
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
387
412
|
}
|
|
388
413
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -395,6 +420,7 @@ async function loadConfig() {
|
|
|
395
420
|
`);
|
|
396
421
|
try {
|
|
397
422
|
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
423
|
+
await enforcePrivateFile(configPath);
|
|
398
424
|
} catch {
|
|
399
425
|
}
|
|
400
426
|
}
|
|
@@ -414,6 +440,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
|
|
|
414
440
|
var init_config = __esm({
|
|
415
441
|
"src/lib/config.ts"() {
|
|
416
442
|
"use strict";
|
|
443
|
+
init_secure_files();
|
|
417
444
|
EXE_AI_DIR = resolveDataDir();
|
|
418
445
|
DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
|
|
419
446
|
MODELS_DIR = path2.join(EXE_AI_DIR, "models");
|
|
@@ -518,10 +545,10 @@ var init_runtime_table = __esm({
|
|
|
518
545
|
});
|
|
519
546
|
|
|
520
547
|
// src/lib/agent-config.ts
|
|
521
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as
|
|
548
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync4 } from "fs";
|
|
522
549
|
import path3 from "path";
|
|
523
550
|
function loadAgentConfig() {
|
|
524
|
-
if (!
|
|
551
|
+
if (!existsSync4(AGENT_CONFIG_PATH)) return {};
|
|
525
552
|
try {
|
|
526
553
|
return JSON.parse(readFileSync3(AGENT_CONFIG_PATH, "utf-8"));
|
|
527
554
|
} catch {
|
|
@@ -542,6 +569,7 @@ var init_agent_config = __esm({
|
|
|
542
569
|
"use strict";
|
|
543
570
|
init_config();
|
|
544
571
|
init_runtime_table();
|
|
572
|
+
init_secure_files();
|
|
545
573
|
AGENT_CONFIG_PATH = path3.join(EXE_AI_DIR, "agent-config.json");
|
|
546
574
|
DEFAULT_MODELS = {
|
|
547
575
|
claude: "claude-opus-4",
|
|
@@ -560,16 +588,16 @@ __export(intercom_queue_exports, {
|
|
|
560
588
|
queueIntercom: () => queueIntercom,
|
|
561
589
|
readQueue: () => readQueue
|
|
562
590
|
});
|
|
563
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as
|
|
591
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
|
|
564
592
|
import path4 from "path";
|
|
565
593
|
import os3 from "os";
|
|
566
594
|
function ensureDir() {
|
|
567
595
|
const dir = path4.dirname(QUEUE_PATH);
|
|
568
|
-
if (!
|
|
596
|
+
if (!existsSync5(dir)) mkdirSync3(dir, { recursive: true });
|
|
569
597
|
}
|
|
570
598
|
function readQueue() {
|
|
571
599
|
try {
|
|
572
|
-
if (!
|
|
600
|
+
if (!existsSync5(QUEUE_PATH)) return [];
|
|
573
601
|
return JSON.parse(readFileSync4(QUEUE_PATH, "utf8"));
|
|
574
602
|
} catch {
|
|
575
603
|
return [];
|
|
@@ -686,7 +714,7 @@ var init_db_retry = __esm({
|
|
|
686
714
|
|
|
687
715
|
// src/lib/employees.ts
|
|
688
716
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
689
|
-
import { existsSync as
|
|
717
|
+
import { existsSync as existsSync6, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
|
|
690
718
|
import { execSync as execSync3 } from "child_process";
|
|
691
719
|
import path5 from "path";
|
|
692
720
|
import os4 from "os";
|
|
@@ -707,7 +735,7 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
|
707
735
|
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
708
736
|
}
|
|
709
737
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
710
|
-
if (!
|
|
738
|
+
if (!existsSync6(employeesPath)) return [];
|
|
711
739
|
try {
|
|
712
740
|
return JSON.parse(readFileSync5(employeesPath, "utf-8"));
|
|
713
741
|
} catch {
|
|
@@ -796,8 +824,11 @@ var init_database = __esm({
|
|
|
796
824
|
});
|
|
797
825
|
|
|
798
826
|
// src/lib/license.ts
|
|
799
|
-
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as
|
|
827
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
|
|
800
828
|
import { randomUUID } from "crypto";
|
|
829
|
+
import { createRequire as createRequire2 } from "module";
|
|
830
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
831
|
+
import os6 from "os";
|
|
801
832
|
import path7 from "path";
|
|
802
833
|
import { jwtVerify, importSPKI } from "jose";
|
|
803
834
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
|
|
@@ -819,11 +850,11 @@ var init_license = __esm({
|
|
|
819
850
|
});
|
|
820
851
|
|
|
821
852
|
// src/lib/plan-limits.ts
|
|
822
|
-
import { readFileSync as readFileSync7, existsSync as
|
|
853
|
+
import { readFileSync as readFileSync7, existsSync as existsSync8 } from "fs";
|
|
823
854
|
import path8 from "path";
|
|
824
855
|
function getLicenseSync() {
|
|
825
856
|
try {
|
|
826
|
-
if (!
|
|
857
|
+
if (!existsSync8(CACHE_PATH2)) return freeLicense();
|
|
827
858
|
const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
|
|
828
859
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
829
860
|
const parts = raw.token.split(".");
|
|
@@ -862,7 +893,7 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
862
893
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
863
894
|
let count = 0;
|
|
864
895
|
try {
|
|
865
|
-
if (
|
|
896
|
+
if (existsSync8(filePath)) {
|
|
866
897
|
const raw = readFileSync7(filePath, "utf8");
|
|
867
898
|
const employees = JSON.parse(raw);
|
|
868
899
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
@@ -896,15 +927,48 @@ var init_plan_limits = __esm({
|
|
|
896
927
|
}
|
|
897
928
|
});
|
|
898
929
|
|
|
930
|
+
// src/lib/task-scope.ts
|
|
931
|
+
function getCurrentSessionScope() {
|
|
932
|
+
try {
|
|
933
|
+
return resolveExeSession();
|
|
934
|
+
} catch {
|
|
935
|
+
return null;
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
function sessionScopeFilter(sessionScope, tableAlias) {
|
|
939
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
940
|
+
if (!scope) return { sql: "", args: [] };
|
|
941
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
942
|
+
return {
|
|
943
|
+
sql: ` AND (${col} IS NULL OR ${col} = ?)`,
|
|
944
|
+
args: [scope]
|
|
945
|
+
};
|
|
946
|
+
}
|
|
947
|
+
function strictSessionScopeFilter(sessionScope, tableAlias) {
|
|
948
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
949
|
+
if (!scope) return { sql: "", args: [] };
|
|
950
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
951
|
+
return {
|
|
952
|
+
sql: ` AND ${col} = ?`,
|
|
953
|
+
args: [scope]
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
var init_task_scope = __esm({
|
|
957
|
+
"src/lib/task-scope.ts"() {
|
|
958
|
+
"use strict";
|
|
959
|
+
init_tmux_routing();
|
|
960
|
+
}
|
|
961
|
+
});
|
|
962
|
+
|
|
899
963
|
// src/lib/notifications.ts
|
|
900
964
|
import crypto from "crypto";
|
|
901
965
|
import path9 from "path";
|
|
902
|
-
import
|
|
966
|
+
import os7 from "os";
|
|
903
967
|
import {
|
|
904
968
|
readFileSync as readFileSync8,
|
|
905
969
|
readdirSync,
|
|
906
970
|
unlinkSync as unlinkSync2,
|
|
907
|
-
existsSync as
|
|
971
|
+
existsSync as existsSync9,
|
|
908
972
|
rmdirSync
|
|
909
973
|
} from "fs";
|
|
910
974
|
async function writeNotification(notification) {
|
|
@@ -912,9 +976,10 @@ async function writeNotification(notification) {
|
|
|
912
976
|
const client = getClient();
|
|
913
977
|
const id = crypto.randomUUID();
|
|
914
978
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
979
|
+
const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
|
|
915
980
|
await client.execute({
|
|
916
|
-
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
|
|
917
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
981
|
+
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
982
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
918
983
|
args: [
|
|
919
984
|
id,
|
|
920
985
|
notification.agentId,
|
|
@@ -923,6 +988,7 @@ async function writeNotification(notification) {
|
|
|
923
988
|
notification.project,
|
|
924
989
|
notification.summary,
|
|
925
990
|
notification.taskFile ?? null,
|
|
991
|
+
sessionScope,
|
|
926
992
|
now
|
|
927
993
|
]
|
|
928
994
|
});
|
|
@@ -931,12 +997,14 @@ async function writeNotification(notification) {
|
|
|
931
997
|
`);
|
|
932
998
|
}
|
|
933
999
|
}
|
|
934
|
-
async function markAsReadByTaskFile(taskFile) {
|
|
1000
|
+
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
935
1001
|
try {
|
|
936
1002
|
const client = getClient();
|
|
1003
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
937
1004
|
await client.execute({
|
|
938
|
-
sql:
|
|
939
|
-
|
|
1005
|
+
sql: `UPDATE notifications SET read = 1
|
|
1006
|
+
WHERE task_file = ? AND read = 0${scope.sql}`,
|
|
1007
|
+
args: [taskFile, ...scope.args]
|
|
940
1008
|
});
|
|
941
1009
|
} catch {
|
|
942
1010
|
}
|
|
@@ -945,6 +1013,7 @@ var init_notifications = __esm({
|
|
|
945
1013
|
"src/lib/notifications.ts"() {
|
|
946
1014
|
"use strict";
|
|
947
1015
|
init_database();
|
|
1016
|
+
init_task_scope();
|
|
948
1017
|
}
|
|
949
1018
|
});
|
|
950
1019
|
|
|
@@ -982,30 +1051,6 @@ var init_session_kill_telemetry = __esm({
|
|
|
982
1051
|
}
|
|
983
1052
|
});
|
|
984
1053
|
|
|
985
|
-
// src/lib/task-scope.ts
|
|
986
|
-
function getCurrentSessionScope() {
|
|
987
|
-
try {
|
|
988
|
-
return resolveExeSession();
|
|
989
|
-
} catch {
|
|
990
|
-
return null;
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
function sessionScopeFilter(sessionScope, tableAlias) {
|
|
994
|
-
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
995
|
-
if (!scope) return { sql: "", args: [] };
|
|
996
|
-
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
997
|
-
return {
|
|
998
|
-
sql: ` AND (${col} IS NULL OR ${col} = ?)`,
|
|
999
|
-
args: [scope]
|
|
1000
|
-
};
|
|
1001
|
-
}
|
|
1002
|
-
var init_task_scope = __esm({
|
|
1003
|
-
"src/lib/task-scope.ts"() {
|
|
1004
|
-
"use strict";
|
|
1005
|
-
init_tmux_routing();
|
|
1006
|
-
}
|
|
1007
|
-
});
|
|
1008
|
-
|
|
1009
1054
|
// src/lib/state-bus.ts
|
|
1010
1055
|
var StateBus, orgBus;
|
|
1011
1056
|
var init_state_bus = __esm({
|
|
@@ -1061,13 +1106,117 @@ var init_state_bus = __esm({
|
|
|
1061
1106
|
}
|
|
1062
1107
|
});
|
|
1063
1108
|
|
|
1109
|
+
// src/lib/project-name.ts
|
|
1110
|
+
import { execSync as execSync4 } from "child_process";
|
|
1111
|
+
import path10 from "path";
|
|
1112
|
+
function getProjectName(cwd) {
|
|
1113
|
+
const dir = cwd ?? process.cwd();
|
|
1114
|
+
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
1115
|
+
try {
|
|
1116
|
+
let repoRoot;
|
|
1117
|
+
try {
|
|
1118
|
+
const gitCommonDir = execSync4("git rev-parse --path-format=absolute --git-common-dir", {
|
|
1119
|
+
cwd: dir,
|
|
1120
|
+
encoding: "utf8",
|
|
1121
|
+
timeout: 2e3,
|
|
1122
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1123
|
+
}).trim();
|
|
1124
|
+
repoRoot = path10.dirname(gitCommonDir);
|
|
1125
|
+
} catch {
|
|
1126
|
+
repoRoot = execSync4("git rev-parse --show-toplevel", {
|
|
1127
|
+
cwd: dir,
|
|
1128
|
+
encoding: "utf8",
|
|
1129
|
+
timeout: 2e3,
|
|
1130
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1131
|
+
}).trim();
|
|
1132
|
+
}
|
|
1133
|
+
_cached2 = path10.basename(repoRoot);
|
|
1134
|
+
_cachedCwd = dir;
|
|
1135
|
+
return _cached2;
|
|
1136
|
+
} catch {
|
|
1137
|
+
_cached2 = path10.basename(dir);
|
|
1138
|
+
_cachedCwd = dir;
|
|
1139
|
+
return _cached2;
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
var _cached2, _cachedCwd;
|
|
1143
|
+
var init_project_name = __esm({
|
|
1144
|
+
"src/lib/project-name.ts"() {
|
|
1145
|
+
"use strict";
|
|
1146
|
+
_cached2 = null;
|
|
1147
|
+
_cachedCwd = null;
|
|
1148
|
+
}
|
|
1149
|
+
});
|
|
1150
|
+
|
|
1151
|
+
// src/lib/session-scope.ts
|
|
1152
|
+
var session_scope_exports = {};
|
|
1153
|
+
__export(session_scope_exports, {
|
|
1154
|
+
assertSessionScope: () => assertSessionScope,
|
|
1155
|
+
findSessionForProject: () => findSessionForProject,
|
|
1156
|
+
getSessionProject: () => getSessionProject
|
|
1157
|
+
});
|
|
1158
|
+
function getSessionProject(sessionName) {
|
|
1159
|
+
const sessions = listSessions();
|
|
1160
|
+
const entry = sessions.find((s) => s.windowName === sessionName);
|
|
1161
|
+
if (!entry) return null;
|
|
1162
|
+
const parts = entry.projectDir.split("/").filter(Boolean);
|
|
1163
|
+
return parts[parts.length - 1] ?? null;
|
|
1164
|
+
}
|
|
1165
|
+
function findSessionForProject(projectName) {
|
|
1166
|
+
const sessions = listSessions();
|
|
1167
|
+
for (const s of sessions) {
|
|
1168
|
+
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
1169
|
+
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
1170
|
+
}
|
|
1171
|
+
return null;
|
|
1172
|
+
}
|
|
1173
|
+
function assertSessionScope(actionType, targetProject) {
|
|
1174
|
+
try {
|
|
1175
|
+
const currentProject = getProjectName();
|
|
1176
|
+
const exeSession = resolveExeSession();
|
|
1177
|
+
if (!exeSession) {
|
|
1178
|
+
return { allowed: true, reason: "no_session" };
|
|
1179
|
+
}
|
|
1180
|
+
if (currentProject === targetProject) {
|
|
1181
|
+
return {
|
|
1182
|
+
allowed: true,
|
|
1183
|
+
reason: "same_session",
|
|
1184
|
+
currentProject,
|
|
1185
|
+
targetProject
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
process.stderr.write(
|
|
1189
|
+
`[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
|
|
1190
|
+
`
|
|
1191
|
+
);
|
|
1192
|
+
return {
|
|
1193
|
+
allowed: false,
|
|
1194
|
+
reason: "cross_session_denied",
|
|
1195
|
+
currentProject,
|
|
1196
|
+
targetProject,
|
|
1197
|
+
targetSession: findSessionForProject(targetProject)?.windowName
|
|
1198
|
+
};
|
|
1199
|
+
} catch {
|
|
1200
|
+
return { allowed: true, reason: "no_session" };
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
var init_session_scope = __esm({
|
|
1204
|
+
"src/lib/session-scope.ts"() {
|
|
1205
|
+
"use strict";
|
|
1206
|
+
init_session_registry();
|
|
1207
|
+
init_project_name();
|
|
1208
|
+
init_tmux_routing();
|
|
1209
|
+
init_employees();
|
|
1210
|
+
}
|
|
1211
|
+
});
|
|
1212
|
+
|
|
1064
1213
|
// src/lib/tasks-crud.ts
|
|
1065
1214
|
import crypto3 from "crypto";
|
|
1066
|
-
import
|
|
1067
|
-
import
|
|
1068
|
-
import { execSync as
|
|
1215
|
+
import path11 from "path";
|
|
1216
|
+
import os8 from "os";
|
|
1217
|
+
import { execSync as execSync5 } from "child_process";
|
|
1069
1218
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
1070
|
-
import { existsSync as
|
|
1219
|
+
import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
|
|
1071
1220
|
async function writeCheckpoint(input) {
|
|
1072
1221
|
const client = getClient();
|
|
1073
1222
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -1187,9 +1336,24 @@ async function createTaskCore(input) {
|
|
|
1187
1336
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1188
1337
|
const slug = slugify(input.title);
|
|
1189
1338
|
let earlySessionScope = null;
|
|
1339
|
+
let scopeMismatchWarning;
|
|
1190
1340
|
try {
|
|
1191
1341
|
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
1192
|
-
|
|
1342
|
+
const resolved = resolveExeSession2();
|
|
1343
|
+
if (resolved && input.projectName) {
|
|
1344
|
+
const { getSessionProject: getSessionProject2 } = await Promise.resolve().then(() => (init_session_scope(), session_scope_exports));
|
|
1345
|
+
const sessionProject = getSessionProject2(resolved);
|
|
1346
|
+
if (sessionProject && sessionProject !== input.projectName) {
|
|
1347
|
+
scopeMismatchWarning = `session/project mismatch: session "${resolved}" owns "${sessionProject}" but task targets "${input.projectName}". Routed to default scope.`;
|
|
1348
|
+
process.stderr.write(`[create_task] ${scopeMismatchWarning}
|
|
1349
|
+
`);
|
|
1350
|
+
earlySessionScope = null;
|
|
1351
|
+
} else {
|
|
1352
|
+
earlySessionScope = resolved;
|
|
1353
|
+
}
|
|
1354
|
+
} else {
|
|
1355
|
+
earlySessionScope = resolved;
|
|
1356
|
+
}
|
|
1193
1357
|
} catch {
|
|
1194
1358
|
}
|
|
1195
1359
|
const scope = earlySessionScope ?? "default";
|
|
@@ -1240,10 +1404,14 @@ async function createTaskCore(input) {
|
|
|
1240
1404
|
${laneWarning}` : laneWarning;
|
|
1241
1405
|
}
|
|
1242
1406
|
}
|
|
1407
|
+
if (scopeMismatchWarning) {
|
|
1408
|
+
warning = warning ? `${warning}
|
|
1409
|
+
${scopeMismatchWarning}` : scopeMismatchWarning;
|
|
1410
|
+
}
|
|
1243
1411
|
if (input.baseDir) {
|
|
1244
1412
|
try {
|
|
1245
|
-
await mkdir3(
|
|
1246
|
-
await mkdir3(
|
|
1413
|
+
await mkdir3(path11.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
1414
|
+
await mkdir3(path11.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
1247
1415
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
1248
1416
|
await ensureGitignoreExe(input.baseDir);
|
|
1249
1417
|
} catch {
|
|
@@ -1279,13 +1447,19 @@ ${laneWarning}` : laneWarning;
|
|
|
1279
1447
|
});
|
|
1280
1448
|
if (input.baseDir) {
|
|
1281
1449
|
try {
|
|
1282
|
-
const EXE_OS_DIR =
|
|
1283
|
-
const mdPath =
|
|
1284
|
-
const mdDir =
|
|
1285
|
-
if (!
|
|
1450
|
+
const EXE_OS_DIR = path11.join(os8.homedir(), ".exe-os");
|
|
1451
|
+
const mdPath = path11.join(EXE_OS_DIR, taskFile);
|
|
1452
|
+
const mdDir = path11.dirname(mdPath);
|
|
1453
|
+
if (!existsSync10(mdDir)) await mkdir3(mdDir, { recursive: true });
|
|
1286
1454
|
const reviewer = input.reviewer ?? input.assignedBy;
|
|
1287
1455
|
const mdContent = `# ${input.title}
|
|
1288
1456
|
|
|
1457
|
+
## MANDATORY: When done
|
|
1458
|
+
|
|
1459
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
1460
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
1461
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
1462
|
+
|
|
1289
1463
|
**ID:** ${id}
|
|
1290
1464
|
**Status:** ${initialStatus}
|
|
1291
1465
|
**Priority:** ${input.priority}
|
|
@@ -1299,12 +1473,6 @@ ${laneWarning}` : laneWarning;
|
|
|
1299
1473
|
## Context
|
|
1300
1474
|
|
|
1301
1475
|
${input.context}
|
|
1302
|
-
|
|
1303
|
-
## MANDATORY: When done
|
|
1304
|
-
|
|
1305
|
-
You MUST call update_task with status "done" and a result summary when finished.
|
|
1306
|
-
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
1307
|
-
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
1308
1476
|
`;
|
|
1309
1477
|
await writeFile3(mdPath, mdContent, "utf-8");
|
|
1310
1478
|
} catch (err) {
|
|
@@ -1386,14 +1554,14 @@ function isTmuxSessionAlive(identifier) {
|
|
|
1386
1554
|
if (!identifier || identifier === "unknown") return true;
|
|
1387
1555
|
try {
|
|
1388
1556
|
if (identifier.startsWith("%")) {
|
|
1389
|
-
const output =
|
|
1557
|
+
const output = execSync5("tmux list-panes -a -F '#{pane_id}'", {
|
|
1390
1558
|
timeout: 2e3,
|
|
1391
1559
|
encoding: "utf8",
|
|
1392
1560
|
stdio: ["pipe", "pipe", "pipe"]
|
|
1393
1561
|
});
|
|
1394
1562
|
return output.split("\n").some((l) => l.trim() === identifier);
|
|
1395
1563
|
} else {
|
|
1396
|
-
|
|
1564
|
+
execSync5(`tmux has-session -t ${JSON.stringify(identifier)}`, {
|
|
1397
1565
|
timeout: 2e3,
|
|
1398
1566
|
stdio: ["pipe", "pipe", "pipe"]
|
|
1399
1567
|
});
|
|
@@ -1402,7 +1570,7 @@ function isTmuxSessionAlive(identifier) {
|
|
|
1402
1570
|
} catch {
|
|
1403
1571
|
if (identifier.startsWith("%")) return true;
|
|
1404
1572
|
try {
|
|
1405
|
-
|
|
1573
|
+
execSync5("tmux list-sessions", {
|
|
1406
1574
|
timeout: 2e3,
|
|
1407
1575
|
stdio: ["pipe", "pipe", "pipe"]
|
|
1408
1576
|
});
|
|
@@ -1417,12 +1585,12 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
|
|
|
1417
1585
|
if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
|
|
1418
1586
|
try {
|
|
1419
1587
|
const since = new Date(taskCreatedAt).toISOString();
|
|
1420
|
-
const branch =
|
|
1588
|
+
const branch = execSync5(
|
|
1421
1589
|
"git rev-parse --abbrev-ref HEAD 2>/dev/null",
|
|
1422
1590
|
{ encoding: "utf8", timeout: 3e3 }
|
|
1423
1591
|
).trim();
|
|
1424
1592
|
const branchArg = branch && branch !== "HEAD" ? branch : "";
|
|
1425
|
-
const commitCount =
|
|
1593
|
+
const commitCount = execSync5(
|
|
1426
1594
|
`git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
|
|
1427
1595
|
{ encoding: "utf8", timeout: 5e3 }
|
|
1428
1596
|
).trim();
|
|
@@ -1553,7 +1721,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
1553
1721
|
await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
|
|
1554
1722
|
} catch {
|
|
1555
1723
|
}
|
|
1556
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
1724
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
1557
1725
|
try {
|
|
1558
1726
|
const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
1559
1727
|
clearQueueForAgent2(String(row.assigned_to));
|
|
@@ -1582,9 +1750,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
1582
1750
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
1583
1751
|
}
|
|
1584
1752
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
1585
|
-
const archPath =
|
|
1753
|
+
const archPath = path11.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
1586
1754
|
try {
|
|
1587
|
-
if (
|
|
1755
|
+
if (existsSync10(archPath)) return;
|
|
1588
1756
|
const template = [
|
|
1589
1757
|
`# ${projectName} \u2014 System Architecture`,
|
|
1590
1758
|
"",
|
|
@@ -1617,9 +1785,9 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
1617
1785
|
}
|
|
1618
1786
|
}
|
|
1619
1787
|
async function ensureGitignoreExe(baseDir) {
|
|
1620
|
-
const gitignorePath =
|
|
1788
|
+
const gitignorePath = path11.join(baseDir, ".gitignore");
|
|
1621
1789
|
try {
|
|
1622
|
-
if (
|
|
1790
|
+
if (existsSync10(gitignorePath)) {
|
|
1623
1791
|
const content = readFileSync9(gitignorePath, "utf-8");
|
|
1624
1792
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
1625
1793
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
@@ -1651,58 +1819,42 @@ var init_tasks_crud = __esm({
|
|
|
1651
1819
|
});
|
|
1652
1820
|
|
|
1653
1821
|
// src/lib/tasks-review.ts
|
|
1654
|
-
import
|
|
1655
|
-
import { existsSync as
|
|
1822
|
+
import path12 from "path";
|
|
1823
|
+
import { existsSync as existsSync11, readdirSync as readdirSync2, unlinkSync as unlinkSync3 } from "fs";
|
|
1656
1824
|
async function countPendingReviews(sessionScope) {
|
|
1657
1825
|
const client = getClient();
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
args: [sessionScope]
|
|
1662
|
-
});
|
|
1663
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
1664
|
-
}
|
|
1826
|
+
const scope = strictSessionScopeFilter(
|
|
1827
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
1828
|
+
);
|
|
1665
1829
|
const result = await client.execute({
|
|
1666
|
-
sql:
|
|
1667
|
-
|
|
1830
|
+
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
1831
|
+
WHERE status = 'needs_review'${scope.sql}`,
|
|
1832
|
+
args: [...scope.args]
|
|
1668
1833
|
});
|
|
1669
1834
|
return Number(result.rows[0]?.cnt) || 0;
|
|
1670
1835
|
}
|
|
1671
1836
|
async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
1672
1837
|
const client = getClient();
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
WHERE status = 'needs_review' AND updated_at > ?
|
|
1677
|
-
AND session_scope = ?`,
|
|
1678
|
-
args: [sinceIso, sessionScope]
|
|
1679
|
-
});
|
|
1680
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
1681
|
-
}
|
|
1838
|
+
const scope = strictSessionScopeFilter(
|
|
1839
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
1840
|
+
);
|
|
1682
1841
|
const result = await client.execute({
|
|
1683
1842
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
1684
|
-
WHERE status = 'needs_review' AND updated_at >
|
|
1685
|
-
args: [sinceIso]
|
|
1843
|
+
WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
|
|
1844
|
+
args: [sinceIso, ...scope.args]
|
|
1686
1845
|
});
|
|
1687
1846
|
return Number(result.rows[0]?.cnt) || 0;
|
|
1688
1847
|
}
|
|
1689
1848
|
async function listPendingReviews(limit, sessionScope) {
|
|
1690
1849
|
const client = getClient();
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
WHERE status = 'needs_review'
|
|
1695
|
-
AND session_scope = ?
|
|
1696
|
-
ORDER BY updated_at ASC LIMIT ?`,
|
|
1697
|
-
args: [sessionScope, limit]
|
|
1698
|
-
});
|
|
1699
|
-
return result2.rows;
|
|
1700
|
-
}
|
|
1850
|
+
const scope = strictSessionScopeFilter(
|
|
1851
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
1852
|
+
);
|
|
1701
1853
|
const result = await client.execute({
|
|
1702
1854
|
sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
|
|
1703
|
-
WHERE status = 'needs_review'
|
|
1855
|
+
WHERE status = 'needs_review'${scope.sql}
|
|
1704
1856
|
ORDER BY updated_at ASC LIMIT ?`,
|
|
1705
|
-
args: [limit]
|
|
1857
|
+
args: [...scope.args, limit]
|
|
1706
1858
|
});
|
|
1707
1859
|
return result.rows;
|
|
1708
1860
|
}
|
|
@@ -1714,7 +1866,7 @@ async function cleanupOrphanedReviews() {
|
|
|
1714
1866
|
WHERE status IN ('open', 'needs_review', 'in_progress')
|
|
1715
1867
|
AND assigned_by = 'system'
|
|
1716
1868
|
AND title LIKE 'Review:%'
|
|
1717
|
-
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
|
|
1869
|
+
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
|
|
1718
1870
|
args: [now]
|
|
1719
1871
|
});
|
|
1720
1872
|
const r1b = await client.execute({
|
|
@@ -1833,11 +1985,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
1833
1985
|
);
|
|
1834
1986
|
}
|
|
1835
1987
|
try {
|
|
1836
|
-
const cacheDir =
|
|
1837
|
-
if (
|
|
1988
|
+
const cacheDir = path12.join(EXE_AI_DIR, "session-cache");
|
|
1989
|
+
if (existsSync11(cacheDir)) {
|
|
1838
1990
|
for (const f of readdirSync2(cacheDir)) {
|
|
1839
1991
|
if (f.startsWith("review-notified-")) {
|
|
1840
|
-
unlinkSync3(
|
|
1992
|
+
unlinkSync3(path12.join(cacheDir, f));
|
|
1841
1993
|
}
|
|
1842
1994
|
}
|
|
1843
1995
|
}
|
|
@@ -1854,11 +2006,12 @@ var init_tasks_review = __esm({
|
|
|
1854
2006
|
init_tmux_routing();
|
|
1855
2007
|
init_session_key();
|
|
1856
2008
|
init_state_bus();
|
|
2009
|
+
init_task_scope();
|
|
1857
2010
|
}
|
|
1858
2011
|
});
|
|
1859
2012
|
|
|
1860
2013
|
// src/lib/tasks-chain.ts
|
|
1861
|
-
import
|
|
2014
|
+
import path13 from "path";
|
|
1862
2015
|
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
1863
2016
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
1864
2017
|
const client = getClient();
|
|
@@ -1875,7 +2028,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
1875
2028
|
});
|
|
1876
2029
|
for (const ur of unblockedRows.rows) {
|
|
1877
2030
|
try {
|
|
1878
|
-
const ubFile =
|
|
2031
|
+
const ubFile = path13.join(baseDir, String(ur.task_file));
|
|
1879
2032
|
let ubContent = await readFile3(ubFile, "utf-8");
|
|
1880
2033
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
1881
2034
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -1910,7 +2063,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
|
|
|
1910
2063
|
const scScope = sessionScopeFilter();
|
|
1911
2064
|
const remaining = await client.execute({
|
|
1912
2065
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
1913
|
-
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
|
|
2066
|
+
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
|
|
1914
2067
|
args: [parentTaskId, ...scScope.args]
|
|
1915
2068
|
});
|
|
1916
2069
|
const cnt = Number(remaining.rows[0]?.cnt ?? 1);
|
|
@@ -1942,110 +2095,6 @@ var init_tasks_chain = __esm({
|
|
|
1942
2095
|
}
|
|
1943
2096
|
});
|
|
1944
2097
|
|
|
1945
|
-
// src/lib/project-name.ts
|
|
1946
|
-
import { execSync as execSync5 } from "child_process";
|
|
1947
|
-
import path13 from "path";
|
|
1948
|
-
function getProjectName(cwd) {
|
|
1949
|
-
const dir = cwd ?? process.cwd();
|
|
1950
|
-
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
1951
|
-
try {
|
|
1952
|
-
let repoRoot;
|
|
1953
|
-
try {
|
|
1954
|
-
const gitCommonDir = execSync5("git rev-parse --path-format=absolute --git-common-dir", {
|
|
1955
|
-
cwd: dir,
|
|
1956
|
-
encoding: "utf8",
|
|
1957
|
-
timeout: 2e3,
|
|
1958
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
1959
|
-
}).trim();
|
|
1960
|
-
repoRoot = path13.dirname(gitCommonDir);
|
|
1961
|
-
} catch {
|
|
1962
|
-
repoRoot = execSync5("git rev-parse --show-toplevel", {
|
|
1963
|
-
cwd: dir,
|
|
1964
|
-
encoding: "utf8",
|
|
1965
|
-
timeout: 2e3,
|
|
1966
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
1967
|
-
}).trim();
|
|
1968
|
-
}
|
|
1969
|
-
_cached2 = path13.basename(repoRoot);
|
|
1970
|
-
_cachedCwd = dir;
|
|
1971
|
-
return _cached2;
|
|
1972
|
-
} catch {
|
|
1973
|
-
_cached2 = path13.basename(dir);
|
|
1974
|
-
_cachedCwd = dir;
|
|
1975
|
-
return _cached2;
|
|
1976
|
-
}
|
|
1977
|
-
}
|
|
1978
|
-
var _cached2, _cachedCwd;
|
|
1979
|
-
var init_project_name = __esm({
|
|
1980
|
-
"src/lib/project-name.ts"() {
|
|
1981
|
-
"use strict";
|
|
1982
|
-
_cached2 = null;
|
|
1983
|
-
_cachedCwd = null;
|
|
1984
|
-
}
|
|
1985
|
-
});
|
|
1986
|
-
|
|
1987
|
-
// src/lib/session-scope.ts
|
|
1988
|
-
var session_scope_exports = {};
|
|
1989
|
-
__export(session_scope_exports, {
|
|
1990
|
-
assertSessionScope: () => assertSessionScope,
|
|
1991
|
-
findSessionForProject: () => findSessionForProject,
|
|
1992
|
-
getSessionProject: () => getSessionProject
|
|
1993
|
-
});
|
|
1994
|
-
function getSessionProject(sessionName) {
|
|
1995
|
-
const sessions = listSessions();
|
|
1996
|
-
const entry = sessions.find((s) => s.windowName === sessionName);
|
|
1997
|
-
if (!entry) return null;
|
|
1998
|
-
const parts = entry.projectDir.split("/").filter(Boolean);
|
|
1999
|
-
return parts[parts.length - 1] ?? null;
|
|
2000
|
-
}
|
|
2001
|
-
function findSessionForProject(projectName) {
|
|
2002
|
-
const sessions = listSessions();
|
|
2003
|
-
for (const s of sessions) {
|
|
2004
|
-
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
2005
|
-
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
2006
|
-
}
|
|
2007
|
-
return null;
|
|
2008
|
-
}
|
|
2009
|
-
function assertSessionScope(actionType, targetProject) {
|
|
2010
|
-
try {
|
|
2011
|
-
const currentProject = getProjectName();
|
|
2012
|
-
const exeSession = resolveExeSession();
|
|
2013
|
-
if (!exeSession) {
|
|
2014
|
-
return { allowed: true, reason: "no_session" };
|
|
2015
|
-
}
|
|
2016
|
-
if (currentProject === targetProject) {
|
|
2017
|
-
return {
|
|
2018
|
-
allowed: true,
|
|
2019
|
-
reason: "same_session",
|
|
2020
|
-
currentProject,
|
|
2021
|
-
targetProject
|
|
2022
|
-
};
|
|
2023
|
-
}
|
|
2024
|
-
process.stderr.write(
|
|
2025
|
-
`[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
|
|
2026
|
-
`
|
|
2027
|
-
);
|
|
2028
|
-
return {
|
|
2029
|
-
allowed: false,
|
|
2030
|
-
reason: "cross_session_denied",
|
|
2031
|
-
currentProject,
|
|
2032
|
-
targetProject,
|
|
2033
|
-
targetSession: findSessionForProject(targetProject)?.windowName
|
|
2034
|
-
};
|
|
2035
|
-
} catch {
|
|
2036
|
-
return { allowed: true, reason: "no_session" };
|
|
2037
|
-
}
|
|
2038
|
-
}
|
|
2039
|
-
var init_session_scope = __esm({
|
|
2040
|
-
"src/lib/session-scope.ts"() {
|
|
2041
|
-
"use strict";
|
|
2042
|
-
init_session_registry();
|
|
2043
|
-
init_project_name();
|
|
2044
|
-
init_tmux_routing();
|
|
2045
|
-
init_employees();
|
|
2046
|
-
}
|
|
2047
|
-
});
|
|
2048
|
-
|
|
2049
2098
|
// src/lib/tasks-notify.ts
|
|
2050
2099
|
async function dispatchTaskToEmployee(input) {
|
|
2051
2100
|
if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
|
|
@@ -2468,7 +2517,7 @@ async function updateTask(input) {
|
|
|
2468
2517
|
if (input.status === "in_progress") {
|
|
2469
2518
|
mkdirSync5(cacheDir, { recursive: true });
|
|
2470
2519
|
writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
2471
|
-
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
2520
|
+
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
|
|
2472
2521
|
try {
|
|
2473
2522
|
unlinkSync4(cachePath);
|
|
2474
2523
|
} catch {
|
|
@@ -2476,10 +2525,10 @@ async function updateTask(input) {
|
|
|
2476
2525
|
}
|
|
2477
2526
|
} catch {
|
|
2478
2527
|
}
|
|
2479
|
-
if (input.status === "done") {
|
|
2528
|
+
if (input.status === "done" || input.status === "closed") {
|
|
2480
2529
|
await cleanupReviewFile(row, taskFile, input.baseDir);
|
|
2481
2530
|
}
|
|
2482
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
2531
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
2483
2532
|
try {
|
|
2484
2533
|
const client = getClient();
|
|
2485
2534
|
const taskTitle = String(row.title);
|
|
@@ -2495,7 +2544,7 @@ async function updateTask(input) {
|
|
|
2495
2544
|
if (!isCoordinatorName(assignedAgent)) {
|
|
2496
2545
|
try {
|
|
2497
2546
|
const draftClient = getClient();
|
|
2498
|
-
if (input.status === "done") {
|
|
2547
|
+
if (input.status === "done" || input.status === "closed") {
|
|
2499
2548
|
await draftClient.execute({
|
|
2500
2549
|
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
2501
2550
|
args: [assignedAgent]
|
|
@@ -2512,7 +2561,7 @@ async function updateTask(input) {
|
|
|
2512
2561
|
try {
|
|
2513
2562
|
const client = getClient();
|
|
2514
2563
|
const cascaded = await client.execute({
|
|
2515
|
-
sql: `UPDATE tasks SET status = '
|
|
2564
|
+
sql: `UPDATE tasks SET status = 'closed', updated_at = ?
|
|
2516
2565
|
WHERE parent_task_id = ? AND status = 'needs_review'`,
|
|
2517
2566
|
args: [now, taskId]
|
|
2518
2567
|
});
|
|
@@ -2525,14 +2574,14 @@ async function updateTask(input) {
|
|
|
2525
2574
|
} catch {
|
|
2526
2575
|
}
|
|
2527
2576
|
}
|
|
2528
|
-
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
2577
|
+
const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
|
|
2529
2578
|
if (isTerminal) {
|
|
2530
2579
|
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
2531
2580
|
if (!isCoordinator) {
|
|
2532
2581
|
notifyTaskDone();
|
|
2533
2582
|
}
|
|
2534
2583
|
await markTaskNotificationsRead(taskFile);
|
|
2535
|
-
if (input.status === "done") {
|
|
2584
|
+
if (input.status === "done" || input.status === "closed") {
|
|
2536
2585
|
try {
|
|
2537
2586
|
await cascadeUnblock(taskId, input.baseDir, now);
|
|
2538
2587
|
} catch {
|
|
@@ -2552,7 +2601,7 @@ async function updateTask(input) {
|
|
|
2552
2601
|
}
|
|
2553
2602
|
}
|
|
2554
2603
|
}
|
|
2555
|
-
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
2604
|
+
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
2556
2605
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
2557
2606
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
2558
2607
|
taskId,
|
|
@@ -2924,6 +2973,7 @@ __export(tmux_routing_exports, {
|
|
|
2924
2973
|
isEmployeeAlive: () => isEmployeeAlive,
|
|
2925
2974
|
isExeSession: () => isExeSession,
|
|
2926
2975
|
isSessionBusy: () => isSessionBusy,
|
|
2976
|
+
notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
|
|
2927
2977
|
notifyParentExe: () => notifyParentExe,
|
|
2928
2978
|
parseParentExe: () => parseParentExe,
|
|
2929
2979
|
registerParentExe: () => registerParentExe,
|
|
@@ -2934,9 +2984,9 @@ __export(tmux_routing_exports, {
|
|
|
2934
2984
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
2935
2985
|
});
|
|
2936
2986
|
import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
|
|
2937
|
-
import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as
|
|
2987
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync12, appendFileSync, readdirSync as readdirSync3 } from "fs";
|
|
2938
2988
|
import path15 from "path";
|
|
2939
|
-
import
|
|
2989
|
+
import os9 from "os";
|
|
2940
2990
|
import { fileURLToPath } from "url";
|
|
2941
2991
|
import { unlinkSync as unlinkSync5 } from "fs";
|
|
2942
2992
|
function spawnLockPath(sessionName) {
|
|
@@ -2951,11 +3001,11 @@ function isProcessAlive(pid) {
|
|
|
2951
3001
|
}
|
|
2952
3002
|
}
|
|
2953
3003
|
function acquireSpawnLock(sessionName) {
|
|
2954
|
-
if (!
|
|
3004
|
+
if (!existsSync12(SPAWN_LOCK_DIR)) {
|
|
2955
3005
|
mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
|
|
2956
3006
|
}
|
|
2957
3007
|
const lockFile = spawnLockPath(sessionName);
|
|
2958
|
-
if (
|
|
3008
|
+
if (existsSync12(lockFile)) {
|
|
2959
3009
|
try {
|
|
2960
3010
|
const lock = JSON.parse(readFileSync10(lockFile, "utf8"));
|
|
2961
3011
|
const age = Date.now() - lock.timestamp;
|
|
@@ -2983,7 +3033,7 @@ function resolveBehaviorsExporterScript() {
|
|
|
2983
3033
|
"bin",
|
|
2984
3034
|
"exe-export-behaviors.js"
|
|
2985
3035
|
);
|
|
2986
|
-
return
|
|
3036
|
+
return existsSync12(scriptPath) ? scriptPath : null;
|
|
2987
3037
|
} catch {
|
|
2988
3038
|
return null;
|
|
2989
3039
|
}
|
|
@@ -3049,7 +3099,7 @@ function extractRootExe(name) {
|
|
|
3049
3099
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
3050
3100
|
}
|
|
3051
3101
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
3052
|
-
if (!
|
|
3102
|
+
if (!existsSync12(SESSION_CACHE)) {
|
|
3053
3103
|
mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
3054
3104
|
}
|
|
3055
3105
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
@@ -3141,7 +3191,7 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
3141
3191
|
}
|
|
3142
3192
|
function readDebounceState() {
|
|
3143
3193
|
try {
|
|
3144
|
-
if (!
|
|
3194
|
+
if (!existsSync12(DEBOUNCE_FILE)) return {};
|
|
3145
3195
|
const raw = JSON.parse(readFileSync10(DEBOUNCE_FILE, "utf8"));
|
|
3146
3196
|
const state = {};
|
|
3147
3197
|
for (const [key, val] of Object.entries(raw)) {
|
|
@@ -3158,7 +3208,7 @@ function readDebounceState() {
|
|
|
3158
3208
|
}
|
|
3159
3209
|
function writeDebounceState(state) {
|
|
3160
3210
|
try {
|
|
3161
|
-
if (!
|
|
3211
|
+
if (!existsSync12(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
3162
3212
|
writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
|
|
3163
3213
|
} catch {
|
|
3164
3214
|
}
|
|
@@ -3258,7 +3308,7 @@ function sendIntercom(targetSession) {
|
|
|
3258
3308
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
3259
3309
|
const agent = baseAgentName(rawAgent);
|
|
3260
3310
|
const markerPath = path15.join(SESSION_CACHE, `current-task-${agent}.json`);
|
|
3261
|
-
if (
|
|
3311
|
+
if (existsSync12(markerPath)) {
|
|
3262
3312
|
logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
|
|
3263
3313
|
return "debounced";
|
|
3264
3314
|
}
|
|
@@ -3268,7 +3318,7 @@ function sendIntercom(targetSession) {
|
|
|
3268
3318
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
3269
3319
|
const agent = baseAgentName(rawAgent);
|
|
3270
3320
|
const taskDir = path15.join(process.cwd(), "exe", agent);
|
|
3271
|
-
if (
|
|
3321
|
+
if (existsSync12(taskDir)) {
|
|
3272
3322
|
const files = readdirSync3(taskDir).filter(
|
|
3273
3323
|
(f) => f.endsWith(".md") && f !== "DONE.txt"
|
|
3274
3324
|
);
|
|
@@ -3328,6 +3378,21 @@ function notifyParentExe(sessionKey) {
|
|
|
3328
3378
|
}
|
|
3329
3379
|
return true;
|
|
3330
3380
|
}
|
|
3381
|
+
function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
|
|
3382
|
+
const transport = getTransport();
|
|
3383
|
+
try {
|
|
3384
|
+
const sessions = transport.listSessions();
|
|
3385
|
+
if (!sessions.includes(coordinatorSession)) return false;
|
|
3386
|
+
execSync6(
|
|
3387
|
+
`tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
|
|
3388
|
+
{ timeout: 3e3 }
|
|
3389
|
+
);
|
|
3390
|
+
logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
|
|
3391
|
+
return true;
|
|
3392
|
+
} catch {
|
|
3393
|
+
return false;
|
|
3394
|
+
}
|
|
3395
|
+
}
|
|
3331
3396
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
3332
3397
|
if (isCoordinatorName(employeeName)) {
|
|
3333
3398
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
@@ -3401,9 +3466,9 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3401
3466
|
const transport = getTransport();
|
|
3402
3467
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
3403
3468
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
3404
|
-
const logDir = path15.join(
|
|
3469
|
+
const logDir = path15.join(os9.homedir(), ".exe-os", "session-logs");
|
|
3405
3470
|
const logFile = path15.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
3406
|
-
if (!
|
|
3471
|
+
if (!existsSync12(logDir)) {
|
|
3407
3472
|
mkdirSync6(logDir, { recursive: true });
|
|
3408
3473
|
}
|
|
3409
3474
|
transport.kill(sessionName);
|
|
@@ -3411,13 +3476,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3411
3476
|
try {
|
|
3412
3477
|
const thisFile = fileURLToPath(import.meta.url);
|
|
3413
3478
|
const cleanupScript = path15.join(path15.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
3414
|
-
if (
|
|
3479
|
+
if (existsSync12(cleanupScript)) {
|
|
3415
3480
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
3416
3481
|
}
|
|
3417
3482
|
} catch {
|
|
3418
3483
|
}
|
|
3419
3484
|
try {
|
|
3420
|
-
const claudeJsonPath = path15.join(
|
|
3485
|
+
const claudeJsonPath = path15.join(os9.homedir(), ".claude.json");
|
|
3421
3486
|
let claudeJson = {};
|
|
3422
3487
|
try {
|
|
3423
3488
|
claudeJson = JSON.parse(readFileSync10(claudeJsonPath, "utf8"));
|
|
@@ -3432,7 +3497,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3432
3497
|
} catch {
|
|
3433
3498
|
}
|
|
3434
3499
|
try {
|
|
3435
|
-
const settingsDir = path15.join(
|
|
3500
|
+
const settingsDir = path15.join(os9.homedir(), ".claude", "projects");
|
|
3436
3501
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
3437
3502
|
const projSettingsDir = path15.join(settingsDir, normalizedKey);
|
|
3438
3503
|
const settingsPath = path15.join(projSettingsDir, "settings.json");
|
|
@@ -3483,7 +3548,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3483
3548
|
let legacyFallbackWarned = false;
|
|
3484
3549
|
if (!useExeAgent && !useBinSymlink) {
|
|
3485
3550
|
const identityPath = path15.join(
|
|
3486
|
-
|
|
3551
|
+
os9.homedir(),
|
|
3487
3552
|
".exe-os",
|
|
3488
3553
|
"identity",
|
|
3489
3554
|
`${employeeName}.md`
|
|
@@ -3492,7 +3557,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3492
3557
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
3493
3558
|
if (hasAgentFlag) {
|
|
3494
3559
|
identityFlag = ` --agent ${employeeName}`;
|
|
3495
|
-
} else if (
|
|
3560
|
+
} else if (existsSync12(identityPath)) {
|
|
3496
3561
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
3497
3562
|
legacyFallbackWarned = true;
|
|
3498
3563
|
}
|
|
@@ -3513,7 +3578,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3513
3578
|
}
|
|
3514
3579
|
let sessionContextFlag = "";
|
|
3515
3580
|
try {
|
|
3516
|
-
const ctxDir = path15.join(
|
|
3581
|
+
const ctxDir = path15.join(os9.homedir(), ".exe-os", "session-cache");
|
|
3517
3582
|
mkdirSync6(ctxDir, { recursive: true });
|
|
3518
3583
|
const ctxFile = path15.join(ctxDir, `session-context-${sessionName}.md`);
|
|
3519
3584
|
const ctxContent = [
|
|
@@ -3673,14 +3738,14 @@ var init_tmux_routing = __esm({
|
|
|
3673
3738
|
init_intercom_queue();
|
|
3674
3739
|
init_plan_limits();
|
|
3675
3740
|
init_employees();
|
|
3676
|
-
SPAWN_LOCK_DIR = path15.join(
|
|
3677
|
-
SESSION_CACHE = path15.join(
|
|
3741
|
+
SPAWN_LOCK_DIR = path15.join(os9.homedir(), ".exe-os", "spawn-locks");
|
|
3742
|
+
SESSION_CACHE = path15.join(os9.homedir(), ".exe-os", "session-cache");
|
|
3678
3743
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
3679
3744
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
3680
3745
|
VERIFY_PANE_LINES = 200;
|
|
3681
3746
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
3682
3747
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
3683
|
-
INTERCOM_LOG2 = path15.join(
|
|
3748
|
+
INTERCOM_LOG2 = path15.join(os9.homedir(), ".exe-os", "intercom.log");
|
|
3684
3749
|
DEBOUNCE_FILE = path15.join(SESSION_CACHE, "intercom-debounce.json");
|
|
3685
3750
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
3686
3751
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
|
|
@@ -3700,6 +3765,7 @@ export {
|
|
|
3700
3765
|
isEmployeeAlive,
|
|
3701
3766
|
isExeSession,
|
|
3702
3767
|
isSessionBusy,
|
|
3768
|
+
notifyCoordinatorTaskCompletion,
|
|
3703
3769
|
notifyParentExe,
|
|
3704
3770
|
parseParentExe,
|
|
3705
3771
|
registerParentExe,
|