@askexenow/exe-os 0.8.83 → 0.8.86
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 +746 -595
- package/dist/bin/backfill-responses.js +745 -594
- package/dist/bin/backfill-vectors.js +312 -226
- package/dist/bin/cleanup-stale-review-tasks.js +154 -21
- package/dist/bin/cli.js +14678 -12676
- package/dist/bin/exe-agent-config.js +242 -0
- package/dist/bin/exe-agent.js +100 -91
- package/dist/bin/exe-assign.js +1003 -854
- package/dist/bin/exe-boot.js +1420 -485
- package/dist/bin/exe-call.js +10 -0
- package/dist/bin/exe-cloud.js +29 -6
- package/dist/bin/exe-dispatch.js +572 -271
- package/dist/bin/exe-doctor.js +403 -6
- package/dist/bin/exe-export-behaviors.js +175 -72
- package/dist/bin/exe-forget.js +102 -3
- package/dist/bin/exe-gateway.js +796 -292
- package/dist/bin/exe-healthcheck.js +134 -1
- package/dist/bin/exe-heartbeat.js +172 -36
- package/dist/bin/exe-kill.js +175 -72
- package/dist/bin/exe-launch-agent.js +189 -76
- package/dist/bin/exe-link.js +927 -82
- package/dist/bin/exe-new-employee.js +60 -8
- package/dist/bin/exe-pending-messages.js +151 -19
- package/dist/bin/exe-pending-notifications.js +97 -2
- package/dist/bin/exe-pending-reviews.js +155 -22
- package/dist/bin/exe-rename.js +564 -23
- package/dist/bin/exe-review.js +231 -73
- package/dist/bin/exe-search.js +995 -228
- package/dist/bin/exe-session-cleanup.js +4930 -1664
- package/dist/bin/exe-settings.js +20 -5
- package/dist/bin/exe-start-codex.js +2598 -0
- package/dist/bin/exe-start.sh +15 -3
- package/dist/bin/exe-status.js +154 -21
- package/dist/bin/exe-team.js +97 -2
- package/dist/bin/git-sweep.js +1180 -363
- package/dist/bin/graph-backfill.js +175 -72
- package/dist/bin/graph-export.js +175 -72
- package/dist/bin/install.js +60 -7
- package/dist/bin/list-providers.js +1 -0
- package/dist/bin/scan-tasks.js +1185 -367
- package/dist/bin/setup.js +914 -270
- package/dist/bin/shard-migrate.js +175 -72
- package/dist/bin/update.js +1 -0
- package/dist/bin/wiki-sync.js +175 -72
- package/dist/gateway/index.js +792 -285
- package/dist/hooks/bug-report-worker.js +445 -135
- package/dist/hooks/commit-complete.js +1178 -361
- package/dist/hooks/error-recall.js +994 -228
- package/dist/hooks/ingest-worker.js +1799 -1234
- package/dist/hooks/ingest.js +3 -0
- package/dist/hooks/instructions-loaded.js +707 -97
- package/dist/hooks/notification.js +699 -89
- package/dist/hooks/post-compact.js +757 -109
- package/dist/hooks/pre-compact.js +1061 -244
- package/dist/hooks/pre-tool-use.js +787 -130
- package/dist/hooks/prompt-ingest-worker.js +242 -101
- package/dist/hooks/prompt-submit.js +1121 -299
- package/dist/hooks/response-ingest-worker.js +242 -101
- package/dist/hooks/session-end.js +4063 -397
- package/dist/hooks/session-start.js +1071 -254
- package/dist/hooks/stop.js +768 -120
- package/dist/hooks/subagent-stop.js +757 -109
- package/dist/hooks/summary-worker.js +1706 -1011
- package/dist/index.js +1821 -1098
- package/dist/lib/agent-config.js +167 -0
- package/dist/lib/cloud-sync.js +932 -88
- package/dist/lib/consolidation.js +2 -1
- package/dist/lib/database.js +642 -87
- package/dist/lib/db-daemon-client.js +503 -0
- package/dist/lib/device-registry.js +547 -7
- package/dist/lib/embedder.js +14 -28
- package/dist/lib/employee-templates.js +84 -74
- package/dist/lib/employees.js +9 -0
- package/dist/lib/exe-daemon-client.js +16 -29
- package/dist/lib/exe-daemon.js +2733 -1575
- package/dist/lib/hybrid-search.js +995 -228
- package/dist/lib/identity.js +87 -67
- package/dist/lib/keychain.js +9 -1
- package/dist/lib/messaging.js +103 -40
- package/dist/lib/reminders.js +91 -74
- package/dist/lib/runtime-table.js +16 -0
- package/dist/lib/schedules.js +96 -2
- package/dist/lib/session-wrappers.js +22 -0
- package/dist/lib/skill-learning.js +103 -85
- package/dist/lib/store.js +234 -73
- package/dist/lib/tasks.js +348 -134
- package/dist/lib/tmux-routing.js +422 -208
- package/dist/lib/token-spend.js +273 -0
- package/dist/lib/ws-client.js +11 -0
- package/dist/mcp/server.js +5742 -696
- package/dist/mcp/tools/complete-reminder.js +94 -77
- package/dist/mcp/tools/create-reminder.js +94 -77
- package/dist/mcp/tools/create-task.js +375 -152
- package/dist/mcp/tools/deactivate-behavior.js +95 -77
- package/dist/mcp/tools/list-reminders.js +94 -77
- package/dist/mcp/tools/list-tasks.js +99 -31
- package/dist/mcp/tools/send-message.js +108 -45
- package/dist/mcp/tools/update-task.js +162 -77
- package/dist/runtime/index.js +1075 -258
- package/dist/tui/App.js +1333 -506
- package/package.json +6 -1
- package/src/commands/exe/agent-config.md +27 -0
- package/src/commands/exe/cc-doctor.md +10 -0
|
@@ -273,6 +273,7 @@ __export(employees_exports, {
|
|
|
273
273
|
DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
|
|
274
274
|
EMPLOYEES_PATH: () => EMPLOYEES_PATH,
|
|
275
275
|
addEmployee: () => addEmployee,
|
|
276
|
+
baseAgentName: () => baseAgentName,
|
|
276
277
|
canCoordinate: () => canCoordinate,
|
|
277
278
|
getCoordinatorEmployee: () => getCoordinatorEmployee,
|
|
278
279
|
getCoordinatorName: () => getCoordinatorName,
|
|
@@ -369,6 +370,14 @@ function hasRole(agentName, role) {
|
|
|
369
370
|
const emp = getEmployee(employees, agentName);
|
|
370
371
|
return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
|
|
371
372
|
}
|
|
373
|
+
function baseAgentName(name, employees) {
|
|
374
|
+
const match = name.match(/^([a-zA-Z]+)\d+$/);
|
|
375
|
+
if (!match) return name;
|
|
376
|
+
const base = match[1];
|
|
377
|
+
const roster = employees ?? loadEmployeesSync();
|
|
378
|
+
if (getEmployee(roster, base)) return base;
|
|
379
|
+
return name;
|
|
380
|
+
}
|
|
372
381
|
function isMultiInstance(agentName, employees) {
|
|
373
382
|
const roster = employees ?? loadEmployeesSync();
|
|
374
383
|
const emp = getEmployee(roster, agentName);
|
|
@@ -470,15 +479,22 @@ function getClient() {
|
|
|
470
479
|
if (!_resilientClient) {
|
|
471
480
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
472
481
|
}
|
|
482
|
+
if (process.env.EXE_IS_DAEMON === "1") {
|
|
483
|
+
return _resilientClient;
|
|
484
|
+
}
|
|
485
|
+
if (_daemonClient && _daemonClient._isDaemonActive()) {
|
|
486
|
+
return _daemonClient;
|
|
487
|
+
}
|
|
473
488
|
return _resilientClient;
|
|
474
489
|
}
|
|
475
|
-
var _resilientClient;
|
|
490
|
+
var _resilientClient, _daemonClient;
|
|
476
491
|
var init_database = __esm({
|
|
477
492
|
"src/lib/database.ts"() {
|
|
478
493
|
"use strict";
|
|
479
494
|
init_db_retry();
|
|
480
495
|
init_employees();
|
|
481
496
|
_resilientClient = null;
|
|
497
|
+
_daemonClient = null;
|
|
482
498
|
}
|
|
483
499
|
});
|
|
484
500
|
|
|
@@ -838,18 +854,69 @@ var init_provider_table = __esm({
|
|
|
838
854
|
}
|
|
839
855
|
});
|
|
840
856
|
|
|
841
|
-
// src/lib/
|
|
842
|
-
|
|
857
|
+
// src/lib/runtime-table.ts
|
|
858
|
+
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
859
|
+
var init_runtime_table = __esm({
|
|
860
|
+
"src/lib/runtime-table.ts"() {
|
|
861
|
+
"use strict";
|
|
862
|
+
RUNTIME_TABLE = {
|
|
863
|
+
codex: {
|
|
864
|
+
binary: "codex",
|
|
865
|
+
launchMode: "exec",
|
|
866
|
+
autoApproveFlag: "--full-auto",
|
|
867
|
+
inlineFlag: "--no-alt-screen",
|
|
868
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
869
|
+
defaultModel: "gpt-5.4"
|
|
870
|
+
}
|
|
871
|
+
};
|
|
872
|
+
DEFAULT_RUNTIME = "claude";
|
|
873
|
+
}
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
// src/lib/agent-config.ts
|
|
877
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
|
|
843
878
|
import path5 from "path";
|
|
879
|
+
function loadAgentConfig() {
|
|
880
|
+
if (!existsSync5(AGENT_CONFIG_PATH)) return {};
|
|
881
|
+
try {
|
|
882
|
+
return JSON.parse(readFileSync5(AGENT_CONFIG_PATH, "utf-8"));
|
|
883
|
+
} catch {
|
|
884
|
+
return {};
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
function getAgentRuntime(agentId) {
|
|
888
|
+
const config = loadAgentConfig();
|
|
889
|
+
const entry = config[agentId];
|
|
890
|
+
if (entry) return entry;
|
|
891
|
+
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
892
|
+
}
|
|
893
|
+
var AGENT_CONFIG_PATH, DEFAULT_MODELS;
|
|
894
|
+
var init_agent_config = __esm({
|
|
895
|
+
"src/lib/agent-config.ts"() {
|
|
896
|
+
"use strict";
|
|
897
|
+
init_config();
|
|
898
|
+
init_runtime_table();
|
|
899
|
+
AGENT_CONFIG_PATH = path5.join(EXE_AI_DIR, "agent-config.json");
|
|
900
|
+
DEFAULT_MODELS = {
|
|
901
|
+
claude: "claude-opus-4",
|
|
902
|
+
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
903
|
+
opencode: "minimax-m2.7"
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
});
|
|
907
|
+
|
|
908
|
+
// src/lib/intercom-queue.ts
|
|
909
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
|
|
910
|
+
import path6 from "path";
|
|
844
911
|
import os5 from "os";
|
|
845
912
|
function ensureDir() {
|
|
846
|
-
const dir =
|
|
847
|
-
if (!
|
|
913
|
+
const dir = path6.dirname(QUEUE_PATH);
|
|
914
|
+
if (!existsSync6(dir)) mkdirSync3(dir, { recursive: true });
|
|
848
915
|
}
|
|
849
916
|
function readQueue() {
|
|
850
917
|
try {
|
|
851
|
-
if (!
|
|
852
|
-
return JSON.parse(
|
|
918
|
+
if (!existsSync6(QUEUE_PATH)) return [];
|
|
919
|
+
return JSON.parse(readFileSync6(QUEUE_PATH, "utf8"));
|
|
853
920
|
} catch {
|
|
854
921
|
return [];
|
|
855
922
|
}
|
|
@@ -857,7 +924,7 @@ function readQueue() {
|
|
|
857
924
|
function writeQueue(queue) {
|
|
858
925
|
ensureDir();
|
|
859
926
|
const tmp = `${QUEUE_PATH}.tmp`;
|
|
860
|
-
|
|
927
|
+
writeFileSync4(tmp, JSON.stringify(queue, null, 2));
|
|
861
928
|
renameSync3(tmp, QUEUE_PATH);
|
|
862
929
|
}
|
|
863
930
|
function queueIntercom(targetSession, reason) {
|
|
@@ -881,25 +948,25 @@ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
|
|
|
881
948
|
var init_intercom_queue = __esm({
|
|
882
949
|
"src/lib/intercom-queue.ts"() {
|
|
883
950
|
"use strict";
|
|
884
|
-
QUEUE_PATH =
|
|
951
|
+
QUEUE_PATH = path6.join(os5.homedir(), ".exe-os", "intercom-queue.json");
|
|
885
952
|
TTL_MS = 60 * 60 * 1e3;
|
|
886
|
-
INTERCOM_LOG =
|
|
953
|
+
INTERCOM_LOG = path6.join(os5.homedir(), ".exe-os", "intercom.log");
|
|
887
954
|
}
|
|
888
955
|
});
|
|
889
956
|
|
|
890
957
|
// src/lib/license.ts
|
|
891
|
-
import { readFileSync as
|
|
958
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
|
|
892
959
|
import { randomUUID } from "crypto";
|
|
893
|
-
import
|
|
960
|
+
import path7 from "path";
|
|
894
961
|
import { jwtVerify, importSPKI } from "jose";
|
|
895
962
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
|
|
896
963
|
var init_license = __esm({
|
|
897
964
|
"src/lib/license.ts"() {
|
|
898
965
|
"use strict";
|
|
899
966
|
init_config();
|
|
900
|
-
LICENSE_PATH =
|
|
901
|
-
CACHE_PATH =
|
|
902
|
-
DEVICE_ID_PATH =
|
|
967
|
+
LICENSE_PATH = path7.join(EXE_AI_DIR, "license.key");
|
|
968
|
+
CACHE_PATH = path7.join(EXE_AI_DIR, "license-cache.json");
|
|
969
|
+
DEVICE_ID_PATH = path7.join(EXE_AI_DIR, "device-id");
|
|
903
970
|
PLAN_LIMITS = {
|
|
904
971
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
905
972
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -911,12 +978,12 @@ var init_license = __esm({
|
|
|
911
978
|
});
|
|
912
979
|
|
|
913
980
|
// src/lib/plan-limits.ts
|
|
914
|
-
import { readFileSync as
|
|
915
|
-
import
|
|
981
|
+
import { readFileSync as readFileSync8, existsSync as existsSync8 } from "fs";
|
|
982
|
+
import path8 from "path";
|
|
916
983
|
function getLicenseSync() {
|
|
917
984
|
try {
|
|
918
|
-
if (!
|
|
919
|
-
const raw = JSON.parse(
|
|
985
|
+
if (!existsSync8(CACHE_PATH2)) return freeLicense();
|
|
986
|
+
const raw = JSON.parse(readFileSync8(CACHE_PATH2, "utf8"));
|
|
920
987
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
921
988
|
const parts = raw.token.split(".");
|
|
922
989
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -954,8 +1021,8 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
954
1021
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
955
1022
|
let count = 0;
|
|
956
1023
|
try {
|
|
957
|
-
if (
|
|
958
|
-
const raw =
|
|
1024
|
+
if (existsSync8(filePath)) {
|
|
1025
|
+
const raw = readFileSync8(filePath, "utf8");
|
|
959
1026
|
const employees = JSON.parse(raw);
|
|
960
1027
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
961
1028
|
}
|
|
@@ -984,7 +1051,7 @@ var init_plan_limits = __esm({
|
|
|
984
1051
|
this.name = "PlanLimitError";
|
|
985
1052
|
}
|
|
986
1053
|
};
|
|
987
|
-
CACHE_PATH2 =
|
|
1054
|
+
CACHE_PATH2 = path8.join(EXE_AI_DIR, "license-cache.json");
|
|
988
1055
|
}
|
|
989
1056
|
});
|
|
990
1057
|
|
|
@@ -1332,13 +1399,13 @@ __export(tmux_routing_exports, {
|
|
|
1332
1399
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
1333
1400
|
});
|
|
1334
1401
|
import { execFileSync as execFileSync2, execSync as execSync4 } from "child_process";
|
|
1335
|
-
import { readFileSync as
|
|
1336
|
-
import
|
|
1402
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, existsSync as existsSync9, appendFileSync } from "fs";
|
|
1403
|
+
import path9 from "path";
|
|
1337
1404
|
import os6 from "os";
|
|
1338
1405
|
import { fileURLToPath } from "url";
|
|
1339
1406
|
import { unlinkSync as unlinkSync3 } from "fs";
|
|
1340
1407
|
function spawnLockPath(sessionName) {
|
|
1341
|
-
return
|
|
1408
|
+
return path9.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
1342
1409
|
}
|
|
1343
1410
|
function isProcessAlive(pid) {
|
|
1344
1411
|
try {
|
|
@@ -1349,13 +1416,13 @@ function isProcessAlive(pid) {
|
|
|
1349
1416
|
}
|
|
1350
1417
|
}
|
|
1351
1418
|
function acquireSpawnLock(sessionName) {
|
|
1352
|
-
if (!
|
|
1353
|
-
|
|
1419
|
+
if (!existsSync9(SPAWN_LOCK_DIR)) {
|
|
1420
|
+
mkdirSync5(SPAWN_LOCK_DIR, { recursive: true });
|
|
1354
1421
|
}
|
|
1355
1422
|
const lockFile = spawnLockPath(sessionName);
|
|
1356
|
-
if (
|
|
1423
|
+
if (existsSync9(lockFile)) {
|
|
1357
1424
|
try {
|
|
1358
|
-
const lock = JSON.parse(
|
|
1425
|
+
const lock = JSON.parse(readFileSync9(lockFile, "utf8"));
|
|
1359
1426
|
const age = Date.now() - lock.timestamp;
|
|
1360
1427
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
1361
1428
|
return false;
|
|
@@ -1363,7 +1430,7 @@ function acquireSpawnLock(sessionName) {
|
|
|
1363
1430
|
} catch {
|
|
1364
1431
|
}
|
|
1365
1432
|
}
|
|
1366
|
-
|
|
1433
|
+
writeFileSync6(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
1367
1434
|
return true;
|
|
1368
1435
|
}
|
|
1369
1436
|
function releaseSpawnLock(sessionName) {
|
|
@@ -1375,13 +1442,13 @@ function releaseSpawnLock(sessionName) {
|
|
|
1375
1442
|
function resolveBehaviorsExporterScript() {
|
|
1376
1443
|
try {
|
|
1377
1444
|
const thisFile = fileURLToPath(import.meta.url);
|
|
1378
|
-
const scriptPath =
|
|
1379
|
-
|
|
1445
|
+
const scriptPath = path9.join(
|
|
1446
|
+
path9.dirname(thisFile),
|
|
1380
1447
|
"..",
|
|
1381
1448
|
"bin",
|
|
1382
1449
|
"exe-export-behaviors.js"
|
|
1383
1450
|
);
|
|
1384
|
-
return
|
|
1451
|
+
return existsSync9(scriptPath) ? scriptPath : null;
|
|
1385
1452
|
} catch {
|
|
1386
1453
|
return null;
|
|
1387
1454
|
}
|
|
@@ -1447,12 +1514,12 @@ function extractRootExe(name) {
|
|
|
1447
1514
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
1448
1515
|
}
|
|
1449
1516
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
1450
|
-
if (!
|
|
1451
|
-
|
|
1517
|
+
if (!existsSync9(SESSION_CACHE)) {
|
|
1518
|
+
mkdirSync5(SESSION_CACHE, { recursive: true });
|
|
1452
1519
|
}
|
|
1453
1520
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
1454
|
-
const filePath =
|
|
1455
|
-
|
|
1521
|
+
const filePath = path9.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
1522
|
+
writeFileSync6(filePath, JSON.stringify({
|
|
1456
1523
|
parentExe: rootExe,
|
|
1457
1524
|
dispatchedBy: dispatchedBy || rootExe,
|
|
1458
1525
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -1460,7 +1527,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
1460
1527
|
}
|
|
1461
1528
|
function getParentExe(sessionKey) {
|
|
1462
1529
|
try {
|
|
1463
|
-
const data = JSON.parse(
|
|
1530
|
+
const data = JSON.parse(readFileSync9(path9.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
1464
1531
|
return data.parentExe || null;
|
|
1465
1532
|
} catch {
|
|
1466
1533
|
return null;
|
|
@@ -1468,8 +1535,8 @@ function getParentExe(sessionKey) {
|
|
|
1468
1535
|
}
|
|
1469
1536
|
function getDispatchedBy(sessionKey) {
|
|
1470
1537
|
try {
|
|
1471
|
-
const data = JSON.parse(
|
|
1472
|
-
|
|
1538
|
+
const data = JSON.parse(readFileSync9(
|
|
1539
|
+
path9.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
1473
1540
|
"utf8"
|
|
1474
1541
|
));
|
|
1475
1542
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -1530,32 +1597,50 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
1530
1597
|
}
|
|
1531
1598
|
function readDebounceState() {
|
|
1532
1599
|
try {
|
|
1533
|
-
if (!
|
|
1534
|
-
|
|
1600
|
+
if (!existsSync9(DEBOUNCE_FILE)) return {};
|
|
1601
|
+
const raw = JSON.parse(readFileSync9(DEBOUNCE_FILE, "utf8"));
|
|
1602
|
+
const state = {};
|
|
1603
|
+
for (const [key, val] of Object.entries(raw)) {
|
|
1604
|
+
if (typeof val === "number") {
|
|
1605
|
+
state[key] = { lastSent: val, pending: 0 };
|
|
1606
|
+
} else if (val && typeof val === "object" && "lastSent" in val) {
|
|
1607
|
+
state[key] = val;
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
return state;
|
|
1535
1611
|
} catch {
|
|
1536
1612
|
return {};
|
|
1537
1613
|
}
|
|
1538
1614
|
}
|
|
1539
1615
|
function writeDebounceState(state) {
|
|
1540
1616
|
try {
|
|
1541
|
-
if (!
|
|
1542
|
-
|
|
1617
|
+
if (!existsSync9(SESSION_CACHE)) mkdirSync5(SESSION_CACHE, { recursive: true });
|
|
1618
|
+
writeFileSync6(DEBOUNCE_FILE, JSON.stringify(state));
|
|
1543
1619
|
} catch {
|
|
1544
1620
|
}
|
|
1545
1621
|
}
|
|
1546
1622
|
function isDebounced(targetSession) {
|
|
1547
1623
|
const state = readDebounceState();
|
|
1548
|
-
const
|
|
1549
|
-
|
|
1624
|
+
const entry = state[targetSession];
|
|
1625
|
+
const lastSent = entry?.lastSent ?? 0;
|
|
1626
|
+
if (Date.now() - lastSent < INTERCOM_DEBOUNCE_MS) {
|
|
1627
|
+
if (!state[targetSession]) state[targetSession] = { lastSent, pending: 0 };
|
|
1628
|
+
state[targetSession].pending++;
|
|
1629
|
+
writeDebounceState(state);
|
|
1630
|
+
return true;
|
|
1631
|
+
}
|
|
1632
|
+
return false;
|
|
1550
1633
|
}
|
|
1551
1634
|
function recordDebounce(targetSession) {
|
|
1552
1635
|
const state = readDebounceState();
|
|
1553
|
-
state[targetSession]
|
|
1636
|
+
const batched = state[targetSession]?.pending ?? 0;
|
|
1637
|
+
state[targetSession] = { lastSent: Date.now(), pending: 0 };
|
|
1554
1638
|
const cutoff = Date.now() - DEBOUNCE_CLEANUP_AGE_MS;
|
|
1555
1639
|
for (const key of Object.keys(state)) {
|
|
1556
|
-
if ((state[key] ?? 0) < cutoff) delete state[key];
|
|
1640
|
+
if ((state[key]?.lastSent ?? 0) < cutoff) delete state[key];
|
|
1557
1641
|
}
|
|
1558
1642
|
writeDebounceState(state);
|
|
1643
|
+
return batched;
|
|
1559
1644
|
}
|
|
1560
1645
|
function logIntercom(msg) {
|
|
1561
1646
|
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}
|
|
@@ -1600,7 +1685,7 @@ function sendIntercom(targetSession) {
|
|
|
1600
1685
|
return "skipped_exe";
|
|
1601
1686
|
}
|
|
1602
1687
|
if (isDebounced(targetSession)) {
|
|
1603
|
-
logIntercom(`DEBOUNCE \u2192 ${targetSession} (
|
|
1688
|
+
logIntercom(`DEBOUNCE \u2192 ${targetSession} (nudge batched, task safe in DB)`);
|
|
1604
1689
|
return "debounced";
|
|
1605
1690
|
}
|
|
1606
1691
|
try {
|
|
@@ -1612,14 +1697,14 @@ function sendIntercom(targetSession) {
|
|
|
1612
1697
|
const sessionState = getSessionState(targetSession);
|
|
1613
1698
|
if (sessionState === "no_claude") {
|
|
1614
1699
|
queueIntercom(targetSession, "claude not running in session");
|
|
1615
|
-
recordDebounce(targetSession);
|
|
1616
|
-
logIntercom(`QUEUED \u2192 ${targetSession} (no claude process
|
|
1700
|
+
const batched2 = recordDebounce(targetSession);
|
|
1701
|
+
logIntercom(`QUEUED \u2192 ${targetSession} (no claude process)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
|
|
1617
1702
|
return "queued";
|
|
1618
1703
|
}
|
|
1619
1704
|
if (sessionState === "thinking" || sessionState === "tool") {
|
|
1620
1705
|
queueIntercom(targetSession, "session busy at send time");
|
|
1621
|
-
recordDebounce(targetSession);
|
|
1622
|
-
logIntercom(`QUEUED \u2192 ${targetSession} (session busy
|
|
1706
|
+
const batched2 = recordDebounce(targetSession);
|
|
1707
|
+
logIntercom(`QUEUED \u2192 ${targetSession} (session busy)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
|
|
1623
1708
|
return "queued";
|
|
1624
1709
|
}
|
|
1625
1710
|
if (transport.isPaneInCopyMode(targetSession)) {
|
|
@@ -1627,8 +1712,8 @@ function sendIntercom(targetSession) {
|
|
|
1627
1712
|
transport.sendKeys(targetSession, "q");
|
|
1628
1713
|
}
|
|
1629
1714
|
transport.sendKeys(targetSession, "/exe-intercom");
|
|
1630
|
-
recordDebounce(targetSession);
|
|
1631
|
-
logIntercom(`DELIVERED \u2192 ${targetSession} (fire-and-forget)`);
|
|
1715
|
+
const batched = recordDebounce(targetSession);
|
|
1716
|
+
logIntercom(`DELIVERED \u2192 ${targetSession}${batched > 0 ? ` [${batched} nudges batched during debounce]` : ""} (fire-and-forget)`);
|
|
1632
1717
|
return "delivered";
|
|
1633
1718
|
} catch {
|
|
1634
1719
|
logIntercom(`FAIL \u2192 ${targetSession}`);
|
|
@@ -1658,7 +1743,7 @@ function notifyParentExe(sessionKey) {
|
|
|
1658
1743
|
return true;
|
|
1659
1744
|
}
|
|
1660
1745
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
1661
|
-
if (
|
|
1746
|
+
if (isCoordinatorName(employeeName)) {
|
|
1662
1747
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
1663
1748
|
}
|
|
1664
1749
|
try {
|
|
@@ -1730,26 +1815,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
1730
1815
|
const transport = getTransport();
|
|
1731
1816
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
1732
1817
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
1733
|
-
const logDir =
|
|
1734
|
-
const logFile =
|
|
1735
|
-
if (!
|
|
1736
|
-
|
|
1818
|
+
const logDir = path9.join(os6.homedir(), ".exe-os", "session-logs");
|
|
1819
|
+
const logFile = path9.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
1820
|
+
if (!existsSync9(logDir)) {
|
|
1821
|
+
mkdirSync5(logDir, { recursive: true });
|
|
1737
1822
|
}
|
|
1738
1823
|
transport.kill(sessionName);
|
|
1739
1824
|
let cleanupSuffix = "";
|
|
1740
1825
|
try {
|
|
1741
1826
|
const thisFile = fileURLToPath(import.meta.url);
|
|
1742
|
-
const cleanupScript =
|
|
1743
|
-
if (
|
|
1827
|
+
const cleanupScript = path9.join(path9.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
1828
|
+
if (existsSync9(cleanupScript)) {
|
|
1744
1829
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
1745
1830
|
}
|
|
1746
1831
|
} catch {
|
|
1747
1832
|
}
|
|
1748
1833
|
try {
|
|
1749
|
-
const claudeJsonPath =
|
|
1834
|
+
const claudeJsonPath = path9.join(os6.homedir(), ".claude.json");
|
|
1750
1835
|
let claudeJson = {};
|
|
1751
1836
|
try {
|
|
1752
|
-
claudeJson = JSON.parse(
|
|
1837
|
+
claudeJson = JSON.parse(readFileSync9(claudeJsonPath, "utf8"));
|
|
1753
1838
|
} catch {
|
|
1754
1839
|
}
|
|
1755
1840
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -1757,17 +1842,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
1757
1842
|
const trustDir = opts?.cwd ?? projectDir;
|
|
1758
1843
|
if (!projects[trustDir]) projects[trustDir] = {};
|
|
1759
1844
|
projects[trustDir].hasTrustDialogAccepted = true;
|
|
1760
|
-
|
|
1845
|
+
writeFileSync6(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
1761
1846
|
} catch {
|
|
1762
1847
|
}
|
|
1763
1848
|
try {
|
|
1764
|
-
const settingsDir =
|
|
1849
|
+
const settingsDir = path9.join(os6.homedir(), ".claude", "projects");
|
|
1765
1850
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
1766
|
-
const projSettingsDir =
|
|
1767
|
-
const settingsPath =
|
|
1851
|
+
const projSettingsDir = path9.join(settingsDir, normalizedKey);
|
|
1852
|
+
const settingsPath = path9.join(projSettingsDir, "settings.json");
|
|
1768
1853
|
let settings = {};
|
|
1769
1854
|
try {
|
|
1770
|
-
settings = JSON.parse(
|
|
1855
|
+
settings = JSON.parse(readFileSync9(settingsPath, "utf8"));
|
|
1771
1856
|
} catch {
|
|
1772
1857
|
}
|
|
1773
1858
|
const perms = settings.permissions ?? {};
|
|
@@ -1795,20 +1880,23 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
1795
1880
|
if (changed) {
|
|
1796
1881
|
perms.allow = allow;
|
|
1797
1882
|
settings.permissions = perms;
|
|
1798
|
-
|
|
1799
|
-
|
|
1883
|
+
mkdirSync5(projSettingsDir, { recursive: true });
|
|
1884
|
+
writeFileSync6(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
1800
1885
|
}
|
|
1801
1886
|
} catch {
|
|
1802
1887
|
}
|
|
1803
1888
|
const spawnCwd = opts?.cwd ?? projectDir;
|
|
1804
1889
|
const useExeAgent = !!(opts?.model && opts?.provider);
|
|
1805
|
-
const
|
|
1890
|
+
const agentRtConfig = getAgentRuntime(employeeName);
|
|
1891
|
+
const useCodex = !useExeAgent && agentRtConfig.runtime === "codex";
|
|
1892
|
+
const useOpencode = !useExeAgent && !useCodex && agentRtConfig.runtime === "opencode";
|
|
1893
|
+
const ccProvider = useExeAgent || useCodex || useOpencode ? DEFAULT_PROVIDER : detectActiveProvider();
|
|
1806
1894
|
const useBinSymlink = ccProvider !== DEFAULT_PROVIDER;
|
|
1807
1895
|
let identityFlag = "";
|
|
1808
1896
|
let behaviorsFlag = "";
|
|
1809
1897
|
let legacyFallbackWarned = false;
|
|
1810
1898
|
if (!useExeAgent && !useBinSymlink) {
|
|
1811
|
-
const identityPath2 =
|
|
1899
|
+
const identityPath2 = path9.join(
|
|
1812
1900
|
os6.homedir(),
|
|
1813
1901
|
".exe-os",
|
|
1814
1902
|
"identity",
|
|
@@ -1818,13 +1906,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
1818
1906
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
1819
1907
|
if (hasAgentFlag) {
|
|
1820
1908
|
identityFlag = ` --agent ${employeeName}`;
|
|
1821
|
-
} else if (
|
|
1909
|
+
} else if (existsSync9(identityPath2)) {
|
|
1822
1910
|
identityFlag = ` --append-system-prompt-file ${identityPath2}`;
|
|
1823
1911
|
legacyFallbackWarned = true;
|
|
1824
1912
|
}
|
|
1825
1913
|
const behaviorsFile = exportBehaviorsSync(
|
|
1826
1914
|
employeeName,
|
|
1827
|
-
|
|
1915
|
+
path9.basename(spawnCwd),
|
|
1828
1916
|
sessionName
|
|
1829
1917
|
);
|
|
1830
1918
|
if (behaviorsFile) {
|
|
@@ -1839,16 +1927,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
1839
1927
|
}
|
|
1840
1928
|
let sessionContextFlag = "";
|
|
1841
1929
|
try {
|
|
1842
|
-
const ctxDir =
|
|
1843
|
-
|
|
1844
|
-
const ctxFile =
|
|
1930
|
+
const ctxDir = path9.join(os6.homedir(), ".exe-os", "session-cache");
|
|
1931
|
+
mkdirSync5(ctxDir, { recursive: true });
|
|
1932
|
+
const ctxFile = path9.join(ctxDir, `session-context-${sessionName}.md`);
|
|
1845
1933
|
const ctxContent = [
|
|
1846
1934
|
`## Session Context`,
|
|
1847
1935
|
`You are running in tmux session: ${sessionName}.`,
|
|
1848
1936
|
`Your parent coordinator session is ${exeSession}.`,
|
|
1849
1937
|
`Your employees (if any) use the -${exeSession} suffix.`
|
|
1850
1938
|
].join("\n");
|
|
1851
|
-
|
|
1939
|
+
writeFileSync6(ctxFile, ctxContent);
|
|
1852
1940
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
1853
1941
|
} catch {
|
|
1854
1942
|
}
|
|
@@ -1862,9 +1950,48 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
1862
1950
|
}
|
|
1863
1951
|
}
|
|
1864
1952
|
}
|
|
1953
|
+
if (useCodex) {
|
|
1954
|
+
const codexCfg = RUNTIME_TABLE.codex;
|
|
1955
|
+
if (codexCfg?.apiKeyEnv) {
|
|
1956
|
+
const keyVal = process.env[codexCfg.apiKeyEnv];
|
|
1957
|
+
if (keyVal) {
|
|
1958
|
+
envPrefix = `${envPrefix} ${codexCfg.apiKeyEnv}=${keyVal}`;
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
envPrefix = `${envPrefix} EXE_AGENT_MODEL=${agentRtConfig.model}`;
|
|
1962
|
+
}
|
|
1963
|
+
if (useOpencode) {
|
|
1964
|
+
const ocCfg = PROVIDER_TABLE.opencode;
|
|
1965
|
+
if (ocCfg?.apiKeyEnv) {
|
|
1966
|
+
const keyVal = process.env[ocCfg.apiKeyEnv];
|
|
1967
|
+
if (keyVal) {
|
|
1968
|
+
envPrefix = `${envPrefix} ${ocCfg.apiKeyEnv}=${keyVal}`;
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1971
|
+
envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
|
|
1972
|
+
}
|
|
1973
|
+
if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
|
|
1974
|
+
const defaultClaudeModel = DEFAULT_MODELS.claude;
|
|
1975
|
+
if (agentRtConfig.runtime === "claude" && agentRtConfig.model !== defaultClaudeModel) {
|
|
1976
|
+
envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1865
1979
|
let spawnCommand;
|
|
1866
1980
|
if (useExeAgent) {
|
|
1867
1981
|
spawnCommand = `${envPrefix} exe-agent --employee ${employeeName} --model ${opts.model} --provider ${opts.provider}${cleanupSuffix}`;
|
|
1982
|
+
} else if (useCodex) {
|
|
1983
|
+
process.stderr.write(
|
|
1984
|
+
`[tmux-routing] agent-config: ${employeeName} \u2192 codex (${agentRtConfig.model})
|
|
1985
|
+
`
|
|
1986
|
+
);
|
|
1987
|
+
spawnCommand = `${envPrefix} exe-start-codex --agent ${employeeName}${cleanupSuffix}`;
|
|
1988
|
+
} else if (useOpencode) {
|
|
1989
|
+
const binName = `${employeeName}-opencode`;
|
|
1990
|
+
process.stderr.write(
|
|
1991
|
+
`[tmux-routing] agent-config: ${employeeName} \u2192 opencode (${agentRtConfig.model})
|
|
1992
|
+
`
|
|
1993
|
+
);
|
|
1994
|
+
spawnCommand = `${envPrefix} ${binName}${cleanupSuffix}`;
|
|
1868
1995
|
} else if (useBinSymlink) {
|
|
1869
1996
|
const binName = `${employeeName}-${ccProvider}`;
|
|
1870
1997
|
process.stderr.write(
|
|
@@ -1886,11 +2013,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
1886
2013
|
transport.pipeLog(sessionName, logFile);
|
|
1887
2014
|
try {
|
|
1888
2015
|
const mySession = getMySession();
|
|
1889
|
-
const dispatchInfo =
|
|
1890
|
-
|
|
2016
|
+
const dispatchInfo = path9.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
2017
|
+
writeFileSync6(dispatchInfo, JSON.stringify({
|
|
1891
2018
|
dispatchedBy: mySession,
|
|
1892
2019
|
rootExe: exeSession,
|
|
1893
|
-
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : "anthropic",
|
|
2020
|
+
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
|
|
2021
|
+
runtime: useCodex ? "codex" : useOpencode ? "opencode" : useExeAgent ? "exe-agent" : "claude",
|
|
2022
|
+
model: useCodex ? agentRtConfig.model : useOpencode ? agentRtConfig.model : void 0,
|
|
1894
2023
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1895
2024
|
}));
|
|
1896
2025
|
} catch {
|
|
@@ -1908,6 +2037,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
1908
2037
|
booted = true;
|
|
1909
2038
|
break;
|
|
1910
2039
|
}
|
|
2040
|
+
} else if (useCodex) {
|
|
2041
|
+
if (pane.includes("codex") || pane.includes("Codex") || pane.includes("exe-start-codex")) {
|
|
2042
|
+
booted = true;
|
|
2043
|
+
break;
|
|
2044
|
+
}
|
|
1911
2045
|
} else {
|
|
1912
2046
|
if (pane.includes("Claude Code") || pane.includes("\u276F")) {
|
|
1913
2047
|
booted = true;
|
|
@@ -1919,9 +2053,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
1919
2053
|
}
|
|
1920
2054
|
if (!booted) {
|
|
1921
2055
|
releaseSpawnLock(sessionName);
|
|
1922
|
-
|
|
2056
|
+
const runtimeLabel = useExeAgent ? "exe-agent" : useCodex ? "codex" : "claude";
|
|
2057
|
+
return { sessionName, error: `${runtimeLabel} did not boot within 15s` };
|
|
1923
2058
|
}
|
|
1924
|
-
if (!useExeAgent) {
|
|
2059
|
+
if (!useExeAgent && !useCodex) {
|
|
1925
2060
|
try {
|
|
1926
2061
|
transport.sendKeys(sessionName, `/exe-call ${employeeName}`);
|
|
1927
2062
|
} catch {
|
|
@@ -1948,17 +2083,19 @@ var init_tmux_routing = __esm({
|
|
|
1948
2083
|
init_cc_agent_support();
|
|
1949
2084
|
init_mcp_prefix();
|
|
1950
2085
|
init_provider_table();
|
|
2086
|
+
init_agent_config();
|
|
2087
|
+
init_runtime_table();
|
|
1951
2088
|
init_intercom_queue();
|
|
1952
2089
|
init_plan_limits();
|
|
1953
2090
|
init_employees();
|
|
1954
|
-
SPAWN_LOCK_DIR =
|
|
1955
|
-
SESSION_CACHE =
|
|
2091
|
+
SPAWN_LOCK_DIR = path9.join(os6.homedir(), ".exe-os", "spawn-locks");
|
|
2092
|
+
SESSION_CACHE = path9.join(os6.homedir(), ".exe-os", "session-cache");
|
|
1956
2093
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
1957
2094
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
1958
2095
|
VERIFY_PANE_LINES = 200;
|
|
1959
2096
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
1960
|
-
INTERCOM_LOG2 =
|
|
1961
|
-
DEBOUNCE_FILE =
|
|
2097
|
+
INTERCOM_LOG2 = path9.join(os6.homedir(), ".exe-os", "intercom.log");
|
|
2098
|
+
DEBOUNCE_FILE = path9.join(SESSION_CACHE, "intercom-debounce.json");
|
|
1962
2099
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
1963
2100
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
|
|
1964
2101
|
}
|
|
@@ -1990,10 +2127,11 @@ var init_task_scope = __esm({
|
|
|
1990
2127
|
|
|
1991
2128
|
// src/lib/tasks-crud.ts
|
|
1992
2129
|
import crypto3 from "crypto";
|
|
1993
|
-
import
|
|
2130
|
+
import path10 from "path";
|
|
2131
|
+
import os7 from "os";
|
|
1994
2132
|
import { execSync as execSync5 } from "child_process";
|
|
1995
2133
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
1996
|
-
import { existsSync as
|
|
2134
|
+
import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
|
|
1997
2135
|
async function writeCheckpoint(input) {
|
|
1998
2136
|
const client = getClient();
|
|
1999
2137
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -2034,6 +2172,35 @@ function extractParentFromContext(contextBody) {
|
|
|
2034
2172
|
function slugify(title) {
|
|
2035
2173
|
return title.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
2036
2174
|
}
|
|
2175
|
+
function buildKeywordIndex() {
|
|
2176
|
+
const idx = /* @__PURE__ */ new Map();
|
|
2177
|
+
for (const [role, keywords] of Object.entries(LANE_KEYWORDS)) {
|
|
2178
|
+
for (const kw of keywords) {
|
|
2179
|
+
const existing = idx.get(kw) ?? [];
|
|
2180
|
+
existing.push(role);
|
|
2181
|
+
idx.set(kw, existing);
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
return idx;
|
|
2185
|
+
}
|
|
2186
|
+
function checkLaneAffinity(title, context, assigneeName) {
|
|
2187
|
+
const employees = loadEmployeesSync();
|
|
2188
|
+
const employee = employees.find((e) => e.name === assigneeName);
|
|
2189
|
+
if (!employee) return void 0;
|
|
2190
|
+
const assigneeRole = employee.role;
|
|
2191
|
+
const text = `${title} ${context}`.toLowerCase();
|
|
2192
|
+
const matchedRoles = /* @__PURE__ */ new Set();
|
|
2193
|
+
for (const [keyword, roles] of KEYWORD_INDEX) {
|
|
2194
|
+
if (text.includes(keyword)) {
|
|
2195
|
+
for (const role of roles) matchedRoles.add(role);
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
if (matchedRoles.size === 0) return void 0;
|
|
2199
|
+
if (matchedRoles.has(assigneeRole)) return void 0;
|
|
2200
|
+
if (assigneeRole === "COO") return void 0;
|
|
2201
|
+
const expectedRoles = Array.from(matchedRoles).join(" or ");
|
|
2202
|
+
return `\u26A0\uFE0F Lane mismatch: task content suggests ${expectedRoles}, but assigned to ${assigneeName} (${assigneeRole}).`;
|
|
2203
|
+
}
|
|
2037
2204
|
async function resolveTask(client, identifier, scopeSession) {
|
|
2038
2205
|
const scope = sessionScopeFilter(scopeSession);
|
|
2039
2206
|
let result = await client.execute({
|
|
@@ -2083,7 +2250,14 @@ async function createTaskCore(input) {
|
|
|
2083
2250
|
const id = crypto3.randomUUID();
|
|
2084
2251
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2085
2252
|
const slug = slugify(input.title);
|
|
2086
|
-
|
|
2253
|
+
let earlySessionScope = null;
|
|
2254
|
+
try {
|
|
2255
|
+
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
2256
|
+
earlySessionScope = resolveExeSession2();
|
|
2257
|
+
} catch {
|
|
2258
|
+
}
|
|
2259
|
+
const scope = earlySessionScope ?? "default";
|
|
2260
|
+
const taskFile = input.taskFile ?? `tasks/${scope}/${input.assignedTo}/${slug}.md`;
|
|
2087
2261
|
let blockedById = null;
|
|
2088
2262
|
const initialStatus = input.blockedBy ? "blocked" : "open";
|
|
2089
2263
|
if (input.blockedBy) {
|
|
@@ -2123,22 +2297,24 @@ async function createTaskCore(input) {
|
|
|
2123
2297
|
if (dupCheck.rows.length > 0) {
|
|
2124
2298
|
warning = `similar active task already exists (${String(dupCheck.rows[0].id)}). Created new task anyway.`;
|
|
2125
2299
|
}
|
|
2300
|
+
if (!process.env.DISABLE_LANE_AFFINITY) {
|
|
2301
|
+
const laneWarning = checkLaneAffinity(input.title, input.context, input.assignedTo);
|
|
2302
|
+
if (laneWarning) {
|
|
2303
|
+
warning = warning ? `${warning}
|
|
2304
|
+
${laneWarning}` : laneWarning;
|
|
2305
|
+
}
|
|
2306
|
+
}
|
|
2126
2307
|
if (input.baseDir) {
|
|
2127
2308
|
try {
|
|
2128
|
-
await mkdir3(
|
|
2129
|
-
await mkdir3(
|
|
2309
|
+
await mkdir3(path10.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
2310
|
+
await mkdir3(path10.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
2130
2311
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
2131
2312
|
await ensureGitignoreExe(input.baseDir);
|
|
2132
2313
|
} catch {
|
|
2133
2314
|
}
|
|
2134
2315
|
}
|
|
2135
2316
|
const complexity = input.complexity ?? "standard";
|
|
2136
|
-
|
|
2137
|
-
try {
|
|
2138
|
-
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
2139
|
-
sessionScope = resolveExeSession2();
|
|
2140
|
-
} catch {
|
|
2141
|
-
}
|
|
2317
|
+
const sessionScope = earlySessionScope;
|
|
2142
2318
|
await client.execute({
|
|
2143
2319
|
sql: `INSERT INTO tasks (id, title, assigned_to, assigned_by, project_name, priority, status, task_file, blocked_by, parent_task_id, reviewer, context, complexity, budget_tokens, budget_fallback_model, tokens_used, tokens_warned_at, session_scope, created_at, updated_at)
|
|
2144
2320
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
@@ -2165,6 +2341,43 @@ async function createTaskCore(input) {
|
|
|
2165
2341
|
now
|
|
2166
2342
|
]
|
|
2167
2343
|
});
|
|
2344
|
+
if (input.baseDir) {
|
|
2345
|
+
try {
|
|
2346
|
+
const EXE_OS_DIR = path10.join(os7.homedir(), ".exe-os");
|
|
2347
|
+
const mdPath = path10.join(EXE_OS_DIR, taskFile);
|
|
2348
|
+
const mdDir = path10.dirname(mdPath);
|
|
2349
|
+
if (!existsSync10(mdDir)) await mkdir3(mdDir, { recursive: true });
|
|
2350
|
+
const reviewer = input.reviewer ?? input.assignedBy;
|
|
2351
|
+
const mdContent = `# ${input.title}
|
|
2352
|
+
|
|
2353
|
+
**ID:** ${id}
|
|
2354
|
+
**Status:** ${initialStatus}
|
|
2355
|
+
**Priority:** ${input.priority}
|
|
2356
|
+
**Assigned by:** ${input.assignedBy}
|
|
2357
|
+
**Assigned to:** ${input.assignedTo}
|
|
2358
|
+
**Project:** ${input.projectName}
|
|
2359
|
+
**Created:** ${now.split("T")[0]}${parentTaskId ? `
|
|
2360
|
+
**Parent task:** ${parentTaskId}` : ""}
|
|
2361
|
+
**Reviewer:** ${reviewer}
|
|
2362
|
+
|
|
2363
|
+
## Context
|
|
2364
|
+
|
|
2365
|
+
${input.context}
|
|
2366
|
+
|
|
2367
|
+
## MANDATORY: When done
|
|
2368
|
+
|
|
2369
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
2370
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
2371
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
2372
|
+
`;
|
|
2373
|
+
await writeFile3(mdPath, mdContent, "utf-8");
|
|
2374
|
+
} catch (err) {
|
|
2375
|
+
process.stderr.write(
|
|
2376
|
+
`[create-task] WARNING: .md file write failed for ${taskFile}: ${err instanceof Error ? err.message : String(err)}
|
|
2377
|
+
`
|
|
2378
|
+
);
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2168
2381
|
return {
|
|
2169
2382
|
id,
|
|
2170
2383
|
title: input.title,
|
|
@@ -2357,7 +2570,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
2357
2570
|
return { row, taskFile, now, taskId };
|
|
2358
2571
|
}
|
|
2359
2572
|
}
|
|
2360
|
-
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || input.callerAgentId
|
|
2573
|
+
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || isCoordinatorName(input.callerAgentId))) {
|
|
2361
2574
|
process.stderr.write(
|
|
2362
2575
|
`[tasks] Assigner override: ${input.callerAgentId} reclaiming ${taskId}
|
|
2363
2576
|
`
|
|
@@ -2422,9 +2635,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
2422
2635
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
2423
2636
|
}
|
|
2424
2637
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
2425
|
-
const archPath =
|
|
2638
|
+
const archPath = path10.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
2426
2639
|
try {
|
|
2427
|
-
if (
|
|
2640
|
+
if (existsSync10(archPath)) return;
|
|
2428
2641
|
const template = [
|
|
2429
2642
|
`# ${projectName} \u2014 System Architecture`,
|
|
2430
2643
|
"",
|
|
@@ -2457,10 +2670,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
2457
2670
|
}
|
|
2458
2671
|
}
|
|
2459
2672
|
async function ensureGitignoreExe(baseDir) {
|
|
2460
|
-
const gitignorePath =
|
|
2673
|
+
const gitignorePath = path10.join(baseDir, ".gitignore");
|
|
2461
2674
|
try {
|
|
2462
|
-
if (
|
|
2463
|
-
const content =
|
|
2675
|
+
if (existsSync10(gitignorePath)) {
|
|
2676
|
+
const content = readFileSync10(gitignorePath, "utf-8");
|
|
2464
2677
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
2465
2678
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
2466
2679
|
} else {
|
|
@@ -2469,20 +2682,30 @@ async function ensureGitignoreExe(baseDir) {
|
|
|
2469
2682
|
} catch {
|
|
2470
2683
|
}
|
|
2471
2684
|
}
|
|
2472
|
-
var DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
2685
|
+
var LANE_KEYWORDS, KEYWORD_INDEX, DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
2473
2686
|
var init_tasks_crud = __esm({
|
|
2474
2687
|
"src/lib/tasks-crud.ts"() {
|
|
2475
2688
|
"use strict";
|
|
2476
2689
|
init_database();
|
|
2477
2690
|
init_task_scope();
|
|
2691
|
+
init_employees();
|
|
2692
|
+
LANE_KEYWORDS = {
|
|
2693
|
+
CMO: ["sales", "script", "pitch", "offer", "copy", "objection", "brand", "content", "seo", "marketing", "newsletter", "carousel", "social", "campaign"],
|
|
2694
|
+
CTO: ["spec", "architecture", "migration", "schema", "database", "design doc", "adr", "security audit", "tech stack"],
|
|
2695
|
+
"Principal Engineer": ["implement", "build", "fix", "commit", "refactor", "bug", "feature", "wire", "integration"],
|
|
2696
|
+
"Staff Code Reviewer": ["critique", "verdict", "review", "audit", "code quality"],
|
|
2697
|
+
"Content Production Specialist": ["render", "video", "image", "b-roll", "remotion", "animation", "thumbnail"],
|
|
2698
|
+
"AI Product Lead": ["competitive", "analysis", "benchmark", "compare", "scout", "evaluate", "poc"]
|
|
2699
|
+
};
|
|
2700
|
+
KEYWORD_INDEX = buildKeywordIndex();
|
|
2478
2701
|
DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
|
|
2479
2702
|
TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
|
|
2480
2703
|
}
|
|
2481
2704
|
});
|
|
2482
2705
|
|
|
2483
2706
|
// src/lib/tasks-review.ts
|
|
2484
|
-
import
|
|
2485
|
-
import { existsSync as
|
|
2707
|
+
import path11 from "path";
|
|
2708
|
+
import { existsSync as existsSync11, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
|
|
2486
2709
|
async function countPendingReviews(sessionScope) {
|
|
2487
2710
|
const client = getClient();
|
|
2488
2711
|
if (sessionScope) {
|
|
@@ -2504,7 +2727,7 @@ async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
|
2504
2727
|
const result2 = await client.execute({
|
|
2505
2728
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
2506
2729
|
WHERE status = 'needs_review' AND updated_at > ?
|
|
2507
|
-
AND
|
|
2730
|
+
AND session_scope = ?`,
|
|
2508
2731
|
args: [sinceIso, sessionScope]
|
|
2509
2732
|
});
|
|
2510
2733
|
return Number(result2.rows[0]?.cnt) || 0;
|
|
@@ -2522,7 +2745,7 @@ async function listPendingReviews(limit, sessionScope) {
|
|
|
2522
2745
|
const result2 = await client.execute({
|
|
2523
2746
|
sql: `SELECT title, assigned_to, project_name FROM tasks
|
|
2524
2747
|
WHERE status = 'needs_review'
|
|
2525
|
-
AND
|
|
2748
|
+
AND session_scope = ?
|
|
2526
2749
|
ORDER BY priority ASC, created_at DESC LIMIT ?`,
|
|
2527
2750
|
args: [sessionScope, limit]
|
|
2528
2751
|
});
|
|
@@ -2643,14 +2866,14 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
2643
2866
|
if (parts.length >= 3 && parts[0] === "review") {
|
|
2644
2867
|
const agent = parts[1];
|
|
2645
2868
|
const slug = parts.slice(2).join("-");
|
|
2646
|
-
const
|
|
2869
|
+
const legacyTaskFile = `exe/${agent}/${slug}.md`;
|
|
2647
2870
|
const result = await client.execute({
|
|
2648
|
-
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
|
|
2649
|
-
args: [now,
|
|
2871
|
+
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE (task_file = ? OR task_file LIKE ?) AND status = 'needs_review'",
|
|
2872
|
+
args: [now, legacyTaskFile, `tasks/%/${agent}/${slug}.md`]
|
|
2650
2873
|
});
|
|
2651
2874
|
if (result.rowsAffected > 0) {
|
|
2652
2875
|
process.stderr.write(
|
|
2653
|
-
`[review-cleanup] Cascaded original task to done
|
|
2876
|
+
`[review-cleanup] Cascaded original task to done: ${agent}/${slug}.md
|
|
2654
2877
|
`
|
|
2655
2878
|
);
|
|
2656
2879
|
}
|
|
@@ -2663,11 +2886,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
2663
2886
|
);
|
|
2664
2887
|
}
|
|
2665
2888
|
try {
|
|
2666
|
-
const cacheDir =
|
|
2667
|
-
if (
|
|
2889
|
+
const cacheDir = path11.join(EXE_AI_DIR, "session-cache");
|
|
2890
|
+
if (existsSync11(cacheDir)) {
|
|
2668
2891
|
for (const f of readdirSync2(cacheDir)) {
|
|
2669
2892
|
if (f.startsWith("review-notified-")) {
|
|
2670
|
-
unlinkSync4(
|
|
2893
|
+
unlinkSync4(path11.join(cacheDir, f));
|
|
2671
2894
|
}
|
|
2672
2895
|
}
|
|
2673
2896
|
}
|
|
@@ -2688,7 +2911,7 @@ var init_tasks_review = __esm({
|
|
|
2688
2911
|
});
|
|
2689
2912
|
|
|
2690
2913
|
// src/lib/tasks-chain.ts
|
|
2691
|
-
import
|
|
2914
|
+
import path12 from "path";
|
|
2692
2915
|
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
2693
2916
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
2694
2917
|
const client = getClient();
|
|
@@ -2705,7 +2928,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
2705
2928
|
});
|
|
2706
2929
|
for (const ur of unblockedRows.rows) {
|
|
2707
2930
|
try {
|
|
2708
|
-
const ubFile =
|
|
2931
|
+
const ubFile = path12.join(baseDir, String(ur.task_file));
|
|
2709
2932
|
let ubContent = await readFile3(ubFile, "utf-8");
|
|
2710
2933
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
2711
2934
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -2774,7 +2997,7 @@ var init_tasks_chain = __esm({
|
|
|
2774
2997
|
|
|
2775
2998
|
// src/lib/project-name.ts
|
|
2776
2999
|
import { execSync as execSync6 } from "child_process";
|
|
2777
|
-
import
|
|
3000
|
+
import path13 from "path";
|
|
2778
3001
|
function getProjectName(cwd) {
|
|
2779
3002
|
const dir = cwd ?? process.cwd();
|
|
2780
3003
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
@@ -2787,7 +3010,7 @@ function getProjectName(cwd) {
|
|
|
2787
3010
|
timeout: 2e3,
|
|
2788
3011
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2789
3012
|
}).trim();
|
|
2790
|
-
repoRoot =
|
|
3013
|
+
repoRoot = path13.dirname(gitCommonDir);
|
|
2791
3014
|
} catch {
|
|
2792
3015
|
repoRoot = execSync6("git rev-parse --show-toplevel", {
|
|
2793
3016
|
cwd: dir,
|
|
@@ -2796,11 +3019,11 @@ function getProjectName(cwd) {
|
|
|
2796
3019
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2797
3020
|
}).trim();
|
|
2798
3021
|
}
|
|
2799
|
-
_cached2 =
|
|
3022
|
+
_cached2 = path13.basename(repoRoot);
|
|
2800
3023
|
_cachedCwd = dir;
|
|
2801
3024
|
return _cached2;
|
|
2802
3025
|
} catch {
|
|
2803
|
-
_cached2 =
|
|
3026
|
+
_cached2 = path13.basename(dir);
|
|
2804
3027
|
_cachedCwd = dir;
|
|
2805
3028
|
return _cached2;
|
|
2806
3029
|
}
|
|
@@ -2832,7 +3055,7 @@ function findSessionForProject(projectName) {
|
|
|
2832
3055
|
const sessions = listSessions();
|
|
2833
3056
|
for (const s of sessions) {
|
|
2834
3057
|
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
2835
|
-
if (proj === projectName &&
|
|
3058
|
+
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
2836
3059
|
}
|
|
2837
3060
|
return null;
|
|
2838
3061
|
}
|
|
@@ -2878,7 +3101,7 @@ var init_session_scope = __esm({
|
|
|
2878
3101
|
|
|
2879
3102
|
// src/lib/tasks-notify.ts
|
|
2880
3103
|
async function dispatchTaskToEmployee(input) {
|
|
2881
|
-
if (
|
|
3104
|
+
if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
|
|
2882
3105
|
let crossProject = false;
|
|
2883
3106
|
if (input.projectName) {
|
|
2884
3107
|
try {
|
|
@@ -3273,8 +3496,8 @@ __export(tasks_exports, {
|
|
|
3273
3496
|
updateTaskStatus: () => updateTaskStatus,
|
|
3274
3497
|
writeCheckpoint: () => writeCheckpoint
|
|
3275
3498
|
});
|
|
3276
|
-
import
|
|
3277
|
-
import { writeFileSync as
|
|
3499
|
+
import path14 from "path";
|
|
3500
|
+
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, unlinkSync as unlinkSync5 } from "fs";
|
|
3278
3501
|
async function createTask(input) {
|
|
3279
3502
|
const result = await createTaskCore(input);
|
|
3280
3503
|
if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -3293,11 +3516,11 @@ async function updateTask(input) {
|
|
|
3293
3516
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
3294
3517
|
try {
|
|
3295
3518
|
const agent = String(row.assigned_to);
|
|
3296
|
-
const cacheDir =
|
|
3297
|
-
const cachePath =
|
|
3519
|
+
const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
|
|
3520
|
+
const cachePath = path14.join(cacheDir, `current-task-${agent}.json`);
|
|
3298
3521
|
if (input.status === "in_progress") {
|
|
3299
|
-
|
|
3300
|
-
|
|
3522
|
+
mkdirSync6(cacheDir, { recursive: true });
|
|
3523
|
+
writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
3301
3524
|
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
3302
3525
|
try {
|
|
3303
3526
|
unlinkSync5(cachePath);
|
|
@@ -3357,7 +3580,7 @@ async function updateTask(input) {
|
|
|
3357
3580
|
}
|
|
3358
3581
|
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
3359
3582
|
if (isTerminal) {
|
|
3360
|
-
const isCoordinator =
|
|
3583
|
+
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
3361
3584
|
if (!isCoordinator) {
|
|
3362
3585
|
notifyTaskDone();
|
|
3363
3586
|
}
|
|
@@ -3382,7 +3605,7 @@ async function updateTask(input) {
|
|
|
3382
3605
|
}
|
|
3383
3606
|
}
|
|
3384
3607
|
}
|
|
3385
|
-
if (input.status === "done" &&
|
|
3608
|
+
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
3386
3609
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
3387
3610
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
3388
3611
|
taskId,
|
|
@@ -3398,7 +3621,7 @@ async function updateTask(input) {
|
|
|
3398
3621
|
});
|
|
3399
3622
|
}
|
|
3400
3623
|
let nextTask;
|
|
3401
|
-
if (isTerminal &&
|
|
3624
|
+
if (isTerminal && !isCoordinatorName(String(row.assigned_to))) {
|
|
3402
3625
|
try {
|
|
3403
3626
|
nextTask = await findNextTask(String(row.assigned_to));
|
|
3404
3627
|
} catch {
|
|
@@ -3463,17 +3686,17 @@ __export(identity_exports, {
|
|
|
3463
3686
|
listIdentities: () => listIdentities,
|
|
3464
3687
|
updateIdentity: () => updateIdentity
|
|
3465
3688
|
});
|
|
3466
|
-
import { existsSync as
|
|
3689
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync8, readFileSync as readFileSync12, writeFileSync as writeFileSync9 } from "fs";
|
|
3467
3690
|
import { readdirSync as readdirSync4 } from "fs";
|
|
3468
|
-
import
|
|
3691
|
+
import path16 from "path";
|
|
3469
3692
|
import { createHash } from "crypto";
|
|
3470
3693
|
function ensureDir2() {
|
|
3471
|
-
if (!
|
|
3472
|
-
|
|
3694
|
+
if (!existsSync12(IDENTITY_DIR)) {
|
|
3695
|
+
mkdirSync8(IDENTITY_DIR, { recursive: true });
|
|
3473
3696
|
}
|
|
3474
3697
|
}
|
|
3475
3698
|
function identityPath(agentId) {
|
|
3476
|
-
return
|
|
3699
|
+
return path16.join(IDENTITY_DIR, `${agentId}.md`);
|
|
3477
3700
|
}
|
|
3478
3701
|
function parseFrontmatter(raw) {
|
|
3479
3702
|
const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
@@ -3514,8 +3737,8 @@ function contentHash(content) {
|
|
|
3514
3737
|
}
|
|
3515
3738
|
function getIdentity(agentId) {
|
|
3516
3739
|
const filePath = identityPath(agentId);
|
|
3517
|
-
if (!
|
|
3518
|
-
const raw =
|
|
3740
|
+
if (!existsSync12(filePath)) return null;
|
|
3741
|
+
const raw = readFileSync12(filePath, "utf-8");
|
|
3519
3742
|
const { frontmatter, body } = parseFrontmatter(raw);
|
|
3520
3743
|
return {
|
|
3521
3744
|
agentId,
|
|
@@ -3529,7 +3752,7 @@ async function updateIdentity(agentId, content, updatedBy) {
|
|
|
3529
3752
|
ensureDir2();
|
|
3530
3753
|
const filePath = identityPath(agentId);
|
|
3531
3754
|
const hash = contentHash(content);
|
|
3532
|
-
|
|
3755
|
+
writeFileSync9(filePath, content, "utf-8");
|
|
3533
3756
|
try {
|
|
3534
3757
|
const client = getClient();
|
|
3535
3758
|
await client.execute({
|
|
@@ -3585,7 +3808,7 @@ var init_identity = __esm({
|
|
|
3585
3808
|
"use strict";
|
|
3586
3809
|
init_config();
|
|
3587
3810
|
init_database();
|
|
3588
|
-
IDENTITY_DIR =
|
|
3811
|
+
IDENTITY_DIR = path16.join(EXE_AI_DIR, "identity");
|
|
3589
3812
|
}
|
|
3590
3813
|
});
|
|
3591
3814
|
|
|
@@ -4125,16 +4348,16 @@ import { z } from "zod";
|
|
|
4125
4348
|
|
|
4126
4349
|
// src/adapters/claude/active-agent.ts
|
|
4127
4350
|
init_config();
|
|
4128
|
-
import { readFileSync as
|
|
4351
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync8, mkdirSync as mkdirSync7, unlinkSync as unlinkSync6, readdirSync as readdirSync3 } from "fs";
|
|
4129
4352
|
import { execSync as execSync7 } from "child_process";
|
|
4130
|
-
import
|
|
4353
|
+
import path15 from "path";
|
|
4131
4354
|
|
|
4132
4355
|
// src/adapters/claude/session-key.ts
|
|
4133
4356
|
init_session_key();
|
|
4134
4357
|
|
|
4135
4358
|
// src/adapters/claude/active-agent.ts
|
|
4136
4359
|
init_employees();
|
|
4137
|
-
var CACHE_DIR =
|
|
4360
|
+
var CACHE_DIR = path15.join(EXE_AI_DIR, "session-cache");
|
|
4138
4361
|
var STALE_MS = 24 * 60 * 60 * 1e3;
|
|
4139
4362
|
function isNameWithOptionalInstance(candidate, baseName) {
|
|
4140
4363
|
if (candidate === baseName) return true;
|
|
@@ -4179,12 +4402,12 @@ function resolveActiveAgentFromTmuxSession(sessionName) {
|
|
|
4179
4402
|
return null;
|
|
4180
4403
|
}
|
|
4181
4404
|
function getMarkerPath() {
|
|
4182
|
-
return
|
|
4405
|
+
return path15.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
|
|
4183
4406
|
}
|
|
4184
4407
|
function getActiveAgent() {
|
|
4185
4408
|
try {
|
|
4186
4409
|
const markerPath = getMarkerPath();
|
|
4187
|
-
const raw =
|
|
4410
|
+
const raw = readFileSync11(markerPath, "utf8");
|
|
4188
4411
|
const data = JSON.parse(raw);
|
|
4189
4412
|
if (data.agentId) {
|
|
4190
4413
|
if (data.startedAt) {
|
|
@@ -4269,10 +4492,10 @@ function registerCreateTask(server) {
|
|
|
4269
4492
|
skipDispatch: true
|
|
4270
4493
|
});
|
|
4271
4494
|
try {
|
|
4272
|
-
const { existsSync:
|
|
4495
|
+
const { existsSync: existsSync13, mkdirSync: mkdirSync9, writeFileSync: writeFileSync10 } = await import("fs");
|
|
4273
4496
|
const { identityPath: identityPath2 } = await Promise.resolve().then(() => (init_identity(), identity_exports));
|
|
4274
4497
|
const idPath = identityPath2(assigned_to);
|
|
4275
|
-
if (!
|
|
4498
|
+
if (!existsSync13(idPath)) {
|
|
4276
4499
|
const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
4277
4500
|
const employees = await loadEmployees2();
|
|
4278
4501
|
const emp = employees.find((e) => e.name === assigned_to);
|
|
@@ -4281,8 +4504,8 @@ function registerCreateTask(server) {
|
|
|
4281
4504
|
const template = getTemplateForTitle2(emp.role);
|
|
4282
4505
|
if (template) {
|
|
4283
4506
|
const dir = (await import("path")).dirname(idPath);
|
|
4284
|
-
if (!
|
|
4285
|
-
|
|
4507
|
+
if (!existsSync13(dir)) mkdirSync9(dir, { recursive: true });
|
|
4508
|
+
writeFileSync10(idPath, template.replace(/^agent_id: \w+/m, `agent_id: ${assigned_to}`), "utf-8");
|
|
4286
4509
|
}
|
|
4287
4510
|
}
|
|
4288
4511
|
}
|