@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
|
@@ -264,6 +264,7 @@ __export(employees_exports, {
|
|
|
264
264
|
DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
|
|
265
265
|
EMPLOYEES_PATH: () => EMPLOYEES_PATH,
|
|
266
266
|
addEmployee: () => addEmployee,
|
|
267
|
+
baseAgentName: () => baseAgentName,
|
|
267
268
|
canCoordinate: () => canCoordinate,
|
|
268
269
|
getCoordinatorEmployee: () => getCoordinatorEmployee,
|
|
269
270
|
getCoordinatorName: () => getCoordinatorName,
|
|
@@ -360,6 +361,14 @@ function hasRole(agentName, role) {
|
|
|
360
361
|
const emp = getEmployee(employees, agentName);
|
|
361
362
|
return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
|
|
362
363
|
}
|
|
364
|
+
function baseAgentName(name, employees) {
|
|
365
|
+
const match = name.match(/^([a-zA-Z]+)\d+$/);
|
|
366
|
+
if (!match) return name;
|
|
367
|
+
const base = match[1];
|
|
368
|
+
const roster = employees ?? loadEmployeesSync();
|
|
369
|
+
if (getEmployee(roster, base)) return base;
|
|
370
|
+
return name;
|
|
371
|
+
}
|
|
363
372
|
function isMultiInstance(agentName, employees) {
|
|
364
373
|
const roster = employees ?? loadEmployeesSync();
|
|
365
374
|
const emp = getEmployee(roster, agentName);
|
|
@@ -476,6 +485,12 @@ function getClient() {
|
|
|
476
485
|
if (!_resilientClient) {
|
|
477
486
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
478
487
|
}
|
|
488
|
+
if (process.env.EXE_IS_DAEMON === "1") {
|
|
489
|
+
return _resilientClient;
|
|
490
|
+
}
|
|
491
|
+
if (_daemonClient && _daemonClient._isDaemonActive()) {
|
|
492
|
+
return _daemonClient;
|
|
493
|
+
}
|
|
479
494
|
return _resilientClient;
|
|
480
495
|
}
|
|
481
496
|
function getRawClient() {
|
|
@@ -964,6 +979,12 @@ async function ensureSchema() {
|
|
|
964
979
|
} catch {
|
|
965
980
|
}
|
|
966
981
|
}
|
|
982
|
+
try {
|
|
983
|
+
await client.execute(
|
|
984
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_content_hash ON memories(content_hash, agent_id)`
|
|
985
|
+
);
|
|
986
|
+
} catch {
|
|
987
|
+
}
|
|
967
988
|
await client.executeMultiple(`
|
|
968
989
|
CREATE TABLE IF NOT EXISTS entities (
|
|
969
990
|
id TEXT PRIMARY KEY,
|
|
@@ -1016,7 +1037,30 @@ async function ensureSchema() {
|
|
|
1016
1037
|
entity_id TEXT NOT NULL,
|
|
1017
1038
|
PRIMARY KEY (hyperedge_id, entity_id)
|
|
1018
1039
|
);
|
|
1040
|
+
|
|
1041
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
|
|
1042
|
+
name,
|
|
1043
|
+
content=entities,
|
|
1044
|
+
content_rowid=rowid
|
|
1045
|
+
);
|
|
1046
|
+
|
|
1047
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_ai AFTER INSERT ON entities BEGIN
|
|
1048
|
+
INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
|
|
1049
|
+
END;
|
|
1050
|
+
|
|
1051
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_ad AFTER DELETE ON entities BEGIN
|
|
1052
|
+
INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
|
|
1053
|
+
END;
|
|
1054
|
+
|
|
1055
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_au AFTER UPDATE ON entities BEGIN
|
|
1056
|
+
INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
|
|
1057
|
+
INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
|
|
1058
|
+
END;
|
|
1019
1059
|
`);
|
|
1060
|
+
try {
|
|
1061
|
+
await client.execute("INSERT INTO entities_fts(entities_fts) VALUES('rebuild')");
|
|
1062
|
+
} catch {
|
|
1063
|
+
}
|
|
1020
1064
|
await client.executeMultiple(`
|
|
1021
1065
|
CREATE TABLE IF NOT EXISTS entity_aliases (
|
|
1022
1066
|
alias TEXT NOT NULL PRIMARY KEY,
|
|
@@ -1197,6 +1241,33 @@ async function ensureSchema() {
|
|
|
1197
1241
|
CREATE INDEX IF NOT EXISTS idx_conversations_channel
|
|
1198
1242
|
ON conversations(channel_id);
|
|
1199
1243
|
`);
|
|
1244
|
+
await client.executeMultiple(`
|
|
1245
|
+
CREATE TABLE IF NOT EXISTS session_agent_map (
|
|
1246
|
+
session_uuid TEXT PRIMARY KEY,
|
|
1247
|
+
agent_id TEXT NOT NULL,
|
|
1248
|
+
session_name TEXT,
|
|
1249
|
+
task_id TEXT,
|
|
1250
|
+
project_name TEXT,
|
|
1251
|
+
started_at TEXT NOT NULL
|
|
1252
|
+
);
|
|
1253
|
+
|
|
1254
|
+
CREATE INDEX IF NOT EXISTS idx_session_agent_map_agent
|
|
1255
|
+
ON session_agent_map(agent_id);
|
|
1256
|
+
`);
|
|
1257
|
+
try {
|
|
1258
|
+
const mapCount = await client.execute({ sql: `SELECT COUNT(*) as cnt FROM session_agent_map`, args: [] });
|
|
1259
|
+
if (Number(mapCount.rows[0]?.cnt ?? 0) === 0) {
|
|
1260
|
+
await client.execute({
|
|
1261
|
+
sql: `INSERT OR IGNORE INTO session_agent_map (session_uuid, agent_id, session_name, started_at)
|
|
1262
|
+
SELECT session_id, agent_id, '', MIN(timestamp)
|
|
1263
|
+
FROM memories
|
|
1264
|
+
WHERE session_id IS NOT NULL AND session_id != '' AND agent_id IS NOT NULL AND agent_id != ''
|
|
1265
|
+
GROUP BY session_id, agent_id`,
|
|
1266
|
+
args: []
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
} catch {
|
|
1270
|
+
}
|
|
1200
1271
|
try {
|
|
1201
1272
|
await client.execute({
|
|
1202
1273
|
sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
|
|
@@ -1330,8 +1401,30 @@ async function ensureSchema() {
|
|
|
1330
1401
|
});
|
|
1331
1402
|
} catch {
|
|
1332
1403
|
}
|
|
1404
|
+
for (const col of [
|
|
1405
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
1406
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
1407
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
1408
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
1409
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
1410
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
1411
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
1412
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
1413
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
1414
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
1415
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
1416
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
1417
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
1418
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
1419
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
1420
|
+
]) {
|
|
1421
|
+
try {
|
|
1422
|
+
await client.execute(col);
|
|
1423
|
+
} catch {
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1333
1426
|
}
|
|
1334
|
-
var _client, _resilientClient, initTurso;
|
|
1427
|
+
var _client, _resilientClient, _daemonClient, initTurso;
|
|
1335
1428
|
var init_database = __esm({
|
|
1336
1429
|
"src/lib/database.ts"() {
|
|
1337
1430
|
"use strict";
|
|
@@ -1339,6 +1432,7 @@ var init_database = __esm({
|
|
|
1339
1432
|
init_employees();
|
|
1340
1433
|
_client = null;
|
|
1341
1434
|
_resilientClient = null;
|
|
1435
|
+
_daemonClient = null;
|
|
1342
1436
|
initTurso = initDatabase;
|
|
1343
1437
|
}
|
|
1344
1438
|
});
|
|
@@ -2125,18 +2219,69 @@ var init_provider_table = __esm({
|
|
|
2125
2219
|
}
|
|
2126
2220
|
});
|
|
2127
2221
|
|
|
2128
|
-
// src/lib/
|
|
2129
|
-
|
|
2222
|
+
// src/lib/runtime-table.ts
|
|
2223
|
+
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
2224
|
+
var init_runtime_table = __esm({
|
|
2225
|
+
"src/lib/runtime-table.ts"() {
|
|
2226
|
+
"use strict";
|
|
2227
|
+
RUNTIME_TABLE = {
|
|
2228
|
+
codex: {
|
|
2229
|
+
binary: "codex",
|
|
2230
|
+
launchMode: "exec",
|
|
2231
|
+
autoApproveFlag: "--full-auto",
|
|
2232
|
+
inlineFlag: "--no-alt-screen",
|
|
2233
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
2234
|
+
defaultModel: "gpt-5.4"
|
|
2235
|
+
}
|
|
2236
|
+
};
|
|
2237
|
+
DEFAULT_RUNTIME = "claude";
|
|
2238
|
+
}
|
|
2239
|
+
});
|
|
2240
|
+
|
|
2241
|
+
// src/lib/agent-config.ts
|
|
2242
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync7, mkdirSync as mkdirSync3 } from "fs";
|
|
2130
2243
|
import path7 from "path";
|
|
2244
|
+
function loadAgentConfig() {
|
|
2245
|
+
if (!existsSync7(AGENT_CONFIG_PATH)) return {};
|
|
2246
|
+
try {
|
|
2247
|
+
return JSON.parse(readFileSync5(AGENT_CONFIG_PATH, "utf-8"));
|
|
2248
|
+
} catch {
|
|
2249
|
+
return {};
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
function getAgentRuntime(agentId) {
|
|
2253
|
+
const config = loadAgentConfig();
|
|
2254
|
+
const entry = config[agentId];
|
|
2255
|
+
if (entry) return entry;
|
|
2256
|
+
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
2257
|
+
}
|
|
2258
|
+
var AGENT_CONFIG_PATH, DEFAULT_MODELS;
|
|
2259
|
+
var init_agent_config = __esm({
|
|
2260
|
+
"src/lib/agent-config.ts"() {
|
|
2261
|
+
"use strict";
|
|
2262
|
+
init_config();
|
|
2263
|
+
init_runtime_table();
|
|
2264
|
+
AGENT_CONFIG_PATH = path7.join(EXE_AI_DIR, "agent-config.json");
|
|
2265
|
+
DEFAULT_MODELS = {
|
|
2266
|
+
claude: "claude-opus-4",
|
|
2267
|
+
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
2268
|
+
opencode: "minimax-m2.7"
|
|
2269
|
+
};
|
|
2270
|
+
}
|
|
2271
|
+
});
|
|
2272
|
+
|
|
2273
|
+
// src/lib/intercom-queue.ts
|
|
2274
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync8, mkdirSync as mkdirSync4 } from "fs";
|
|
2275
|
+
import path8 from "path";
|
|
2131
2276
|
import os6 from "os";
|
|
2132
2277
|
function ensureDir() {
|
|
2133
|
-
const dir =
|
|
2134
|
-
if (!
|
|
2278
|
+
const dir = path8.dirname(QUEUE_PATH);
|
|
2279
|
+
if (!existsSync8(dir)) mkdirSync4(dir, { recursive: true });
|
|
2135
2280
|
}
|
|
2136
2281
|
function readQueue() {
|
|
2137
2282
|
try {
|
|
2138
|
-
if (!
|
|
2139
|
-
return JSON.parse(
|
|
2283
|
+
if (!existsSync8(QUEUE_PATH)) return [];
|
|
2284
|
+
return JSON.parse(readFileSync6(QUEUE_PATH, "utf8"));
|
|
2140
2285
|
} catch {
|
|
2141
2286
|
return [];
|
|
2142
2287
|
}
|
|
@@ -2144,7 +2289,7 @@ function readQueue() {
|
|
|
2144
2289
|
function writeQueue(queue) {
|
|
2145
2290
|
ensureDir();
|
|
2146
2291
|
const tmp = `${QUEUE_PATH}.tmp`;
|
|
2147
|
-
|
|
2292
|
+
writeFileSync4(tmp, JSON.stringify(queue, null, 2));
|
|
2148
2293
|
renameSync3(tmp, QUEUE_PATH);
|
|
2149
2294
|
}
|
|
2150
2295
|
function queueIntercom(targetSession, reason) {
|
|
@@ -2168,25 +2313,25 @@ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
|
|
|
2168
2313
|
var init_intercom_queue = __esm({
|
|
2169
2314
|
"src/lib/intercom-queue.ts"() {
|
|
2170
2315
|
"use strict";
|
|
2171
|
-
QUEUE_PATH =
|
|
2316
|
+
QUEUE_PATH = path8.join(os6.homedir(), ".exe-os", "intercom-queue.json");
|
|
2172
2317
|
TTL_MS = 60 * 60 * 1e3;
|
|
2173
|
-
INTERCOM_LOG =
|
|
2318
|
+
INTERCOM_LOG = path8.join(os6.homedir(), ".exe-os", "intercom.log");
|
|
2174
2319
|
}
|
|
2175
2320
|
});
|
|
2176
2321
|
|
|
2177
2322
|
// src/lib/license.ts
|
|
2178
|
-
import { readFileSync as
|
|
2323
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync9, mkdirSync as mkdirSync5 } from "fs";
|
|
2179
2324
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
2180
|
-
import
|
|
2325
|
+
import path9 from "path";
|
|
2181
2326
|
import { jwtVerify, importSPKI } from "jose";
|
|
2182
2327
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
|
|
2183
2328
|
var init_license = __esm({
|
|
2184
2329
|
"src/lib/license.ts"() {
|
|
2185
2330
|
"use strict";
|
|
2186
2331
|
init_config();
|
|
2187
|
-
LICENSE_PATH =
|
|
2188
|
-
CACHE_PATH =
|
|
2189
|
-
DEVICE_ID_PATH =
|
|
2332
|
+
LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
|
|
2333
|
+
CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
|
|
2334
|
+
DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
|
|
2190
2335
|
PLAN_LIMITS = {
|
|
2191
2336
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
2192
2337
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -2198,12 +2343,12 @@ var init_license = __esm({
|
|
|
2198
2343
|
});
|
|
2199
2344
|
|
|
2200
2345
|
// src/lib/plan-limits.ts
|
|
2201
|
-
import { readFileSync as
|
|
2202
|
-
import
|
|
2346
|
+
import { readFileSync as readFileSync8, existsSync as existsSync10 } from "fs";
|
|
2347
|
+
import path10 from "path";
|
|
2203
2348
|
function getLicenseSync() {
|
|
2204
2349
|
try {
|
|
2205
|
-
if (!
|
|
2206
|
-
const raw = JSON.parse(
|
|
2350
|
+
if (!existsSync10(CACHE_PATH2)) return freeLicense();
|
|
2351
|
+
const raw = JSON.parse(readFileSync8(CACHE_PATH2, "utf8"));
|
|
2207
2352
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
2208
2353
|
const parts = raw.token.split(".");
|
|
2209
2354
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -2241,8 +2386,8 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
2241
2386
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
2242
2387
|
let count = 0;
|
|
2243
2388
|
try {
|
|
2244
|
-
if (
|
|
2245
|
-
const raw =
|
|
2389
|
+
if (existsSync10(filePath)) {
|
|
2390
|
+
const raw = readFileSync8(filePath, "utf8");
|
|
2246
2391
|
const employees = JSON.parse(raw);
|
|
2247
2392
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
2248
2393
|
}
|
|
@@ -2271,7 +2416,7 @@ var init_plan_limits = __esm({
|
|
|
2271
2416
|
this.name = "PlanLimitError";
|
|
2272
2417
|
}
|
|
2273
2418
|
};
|
|
2274
|
-
CACHE_PATH2 =
|
|
2419
|
+
CACHE_PATH2 = path10.join(EXE_AI_DIR, "license-cache.json");
|
|
2275
2420
|
}
|
|
2276
2421
|
});
|
|
2277
2422
|
|
|
@@ -2619,13 +2764,13 @@ __export(tmux_routing_exports, {
|
|
|
2619
2764
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
2620
2765
|
});
|
|
2621
2766
|
import { execFileSync as execFileSync2, execSync as execSync4 } from "child_process";
|
|
2622
|
-
import { readFileSync as
|
|
2623
|
-
import
|
|
2767
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6, existsSync as existsSync11, appendFileSync } from "fs";
|
|
2768
|
+
import path11 from "path";
|
|
2624
2769
|
import os7 from "os";
|
|
2625
2770
|
import { fileURLToPath } from "url";
|
|
2626
2771
|
import { unlinkSync as unlinkSync3 } from "fs";
|
|
2627
2772
|
function spawnLockPath(sessionName) {
|
|
2628
|
-
return
|
|
2773
|
+
return path11.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
2629
2774
|
}
|
|
2630
2775
|
function isProcessAlive(pid) {
|
|
2631
2776
|
try {
|
|
@@ -2636,13 +2781,13 @@ function isProcessAlive(pid) {
|
|
|
2636
2781
|
}
|
|
2637
2782
|
}
|
|
2638
2783
|
function acquireSpawnLock(sessionName) {
|
|
2639
|
-
if (!
|
|
2640
|
-
|
|
2784
|
+
if (!existsSync11(SPAWN_LOCK_DIR)) {
|
|
2785
|
+
mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
|
|
2641
2786
|
}
|
|
2642
2787
|
const lockFile = spawnLockPath(sessionName);
|
|
2643
|
-
if (
|
|
2788
|
+
if (existsSync11(lockFile)) {
|
|
2644
2789
|
try {
|
|
2645
|
-
const lock = JSON.parse(
|
|
2790
|
+
const lock = JSON.parse(readFileSync9(lockFile, "utf8"));
|
|
2646
2791
|
const age = Date.now() - lock.timestamp;
|
|
2647
2792
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
2648
2793
|
return false;
|
|
@@ -2650,7 +2795,7 @@ function acquireSpawnLock(sessionName) {
|
|
|
2650
2795
|
} catch {
|
|
2651
2796
|
}
|
|
2652
2797
|
}
|
|
2653
|
-
|
|
2798
|
+
writeFileSync6(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
2654
2799
|
return true;
|
|
2655
2800
|
}
|
|
2656
2801
|
function releaseSpawnLock(sessionName) {
|
|
@@ -2662,13 +2807,13 @@ function releaseSpawnLock(sessionName) {
|
|
|
2662
2807
|
function resolveBehaviorsExporterScript() {
|
|
2663
2808
|
try {
|
|
2664
2809
|
const thisFile = fileURLToPath(import.meta.url);
|
|
2665
|
-
const scriptPath =
|
|
2666
|
-
|
|
2810
|
+
const scriptPath = path11.join(
|
|
2811
|
+
path11.dirname(thisFile),
|
|
2667
2812
|
"..",
|
|
2668
2813
|
"bin",
|
|
2669
2814
|
"exe-export-behaviors.js"
|
|
2670
2815
|
);
|
|
2671
|
-
return
|
|
2816
|
+
return existsSync11(scriptPath) ? scriptPath : null;
|
|
2672
2817
|
} catch {
|
|
2673
2818
|
return null;
|
|
2674
2819
|
}
|
|
@@ -2734,12 +2879,12 @@ function extractRootExe(name) {
|
|
|
2734
2879
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
2735
2880
|
}
|
|
2736
2881
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
2737
|
-
if (!
|
|
2738
|
-
|
|
2882
|
+
if (!existsSync11(SESSION_CACHE)) {
|
|
2883
|
+
mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
2739
2884
|
}
|
|
2740
2885
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
2741
|
-
const filePath =
|
|
2742
|
-
|
|
2886
|
+
const filePath = path11.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
2887
|
+
writeFileSync6(filePath, JSON.stringify({
|
|
2743
2888
|
parentExe: rootExe,
|
|
2744
2889
|
dispatchedBy: dispatchedBy || rootExe,
|
|
2745
2890
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -2747,7 +2892,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
2747
2892
|
}
|
|
2748
2893
|
function getParentExe(sessionKey) {
|
|
2749
2894
|
try {
|
|
2750
|
-
const data = JSON.parse(
|
|
2895
|
+
const data = JSON.parse(readFileSync9(path11.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
2751
2896
|
return data.parentExe || null;
|
|
2752
2897
|
} catch {
|
|
2753
2898
|
return null;
|
|
@@ -2755,8 +2900,8 @@ function getParentExe(sessionKey) {
|
|
|
2755
2900
|
}
|
|
2756
2901
|
function getDispatchedBy(sessionKey) {
|
|
2757
2902
|
try {
|
|
2758
|
-
const data = JSON.parse(
|
|
2759
|
-
|
|
2903
|
+
const data = JSON.parse(readFileSync9(
|
|
2904
|
+
path11.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
2760
2905
|
"utf8"
|
|
2761
2906
|
));
|
|
2762
2907
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -2817,32 +2962,50 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
2817
2962
|
}
|
|
2818
2963
|
function readDebounceState() {
|
|
2819
2964
|
try {
|
|
2820
|
-
if (!
|
|
2821
|
-
|
|
2965
|
+
if (!existsSync11(DEBOUNCE_FILE)) return {};
|
|
2966
|
+
const raw = JSON.parse(readFileSync9(DEBOUNCE_FILE, "utf8"));
|
|
2967
|
+
const state = {};
|
|
2968
|
+
for (const [key, val] of Object.entries(raw)) {
|
|
2969
|
+
if (typeof val === "number") {
|
|
2970
|
+
state[key] = { lastSent: val, pending: 0 };
|
|
2971
|
+
} else if (val && typeof val === "object" && "lastSent" in val) {
|
|
2972
|
+
state[key] = val;
|
|
2973
|
+
}
|
|
2974
|
+
}
|
|
2975
|
+
return state;
|
|
2822
2976
|
} catch {
|
|
2823
2977
|
return {};
|
|
2824
2978
|
}
|
|
2825
2979
|
}
|
|
2826
2980
|
function writeDebounceState(state) {
|
|
2827
2981
|
try {
|
|
2828
|
-
if (!
|
|
2829
|
-
|
|
2982
|
+
if (!existsSync11(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
2983
|
+
writeFileSync6(DEBOUNCE_FILE, JSON.stringify(state));
|
|
2830
2984
|
} catch {
|
|
2831
2985
|
}
|
|
2832
2986
|
}
|
|
2833
2987
|
function isDebounced(targetSession) {
|
|
2834
2988
|
const state = readDebounceState();
|
|
2835
|
-
const
|
|
2836
|
-
|
|
2989
|
+
const entry = state[targetSession];
|
|
2990
|
+
const lastSent = entry?.lastSent ?? 0;
|
|
2991
|
+
if (Date.now() - lastSent < INTERCOM_DEBOUNCE_MS) {
|
|
2992
|
+
if (!state[targetSession]) state[targetSession] = { lastSent, pending: 0 };
|
|
2993
|
+
state[targetSession].pending++;
|
|
2994
|
+
writeDebounceState(state);
|
|
2995
|
+
return true;
|
|
2996
|
+
}
|
|
2997
|
+
return false;
|
|
2837
2998
|
}
|
|
2838
2999
|
function recordDebounce(targetSession) {
|
|
2839
3000
|
const state = readDebounceState();
|
|
2840
|
-
state[targetSession]
|
|
3001
|
+
const batched = state[targetSession]?.pending ?? 0;
|
|
3002
|
+
state[targetSession] = { lastSent: Date.now(), pending: 0 };
|
|
2841
3003
|
const cutoff = Date.now() - DEBOUNCE_CLEANUP_AGE_MS;
|
|
2842
3004
|
for (const key of Object.keys(state)) {
|
|
2843
|
-
if ((state[key] ?? 0) < cutoff) delete state[key];
|
|
3005
|
+
if ((state[key]?.lastSent ?? 0) < cutoff) delete state[key];
|
|
2844
3006
|
}
|
|
2845
3007
|
writeDebounceState(state);
|
|
3008
|
+
return batched;
|
|
2846
3009
|
}
|
|
2847
3010
|
function logIntercom(msg) {
|
|
2848
3011
|
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}
|
|
@@ -2887,7 +3050,7 @@ function sendIntercom(targetSession) {
|
|
|
2887
3050
|
return "skipped_exe";
|
|
2888
3051
|
}
|
|
2889
3052
|
if (isDebounced(targetSession)) {
|
|
2890
|
-
logIntercom(`DEBOUNCE \u2192 ${targetSession} (
|
|
3053
|
+
logIntercom(`DEBOUNCE \u2192 ${targetSession} (nudge batched, task safe in DB)`);
|
|
2891
3054
|
return "debounced";
|
|
2892
3055
|
}
|
|
2893
3056
|
try {
|
|
@@ -2899,14 +3062,14 @@ function sendIntercom(targetSession) {
|
|
|
2899
3062
|
const sessionState = getSessionState(targetSession);
|
|
2900
3063
|
if (sessionState === "no_claude") {
|
|
2901
3064
|
queueIntercom(targetSession, "claude not running in session");
|
|
2902
|
-
recordDebounce(targetSession);
|
|
2903
|
-
logIntercom(`QUEUED \u2192 ${targetSession} (no claude process
|
|
3065
|
+
const batched2 = recordDebounce(targetSession);
|
|
3066
|
+
logIntercom(`QUEUED \u2192 ${targetSession} (no claude process)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
|
|
2904
3067
|
return "queued";
|
|
2905
3068
|
}
|
|
2906
3069
|
if (sessionState === "thinking" || sessionState === "tool") {
|
|
2907
3070
|
queueIntercom(targetSession, "session busy at send time");
|
|
2908
|
-
recordDebounce(targetSession);
|
|
2909
|
-
logIntercom(`QUEUED \u2192 ${targetSession} (session busy
|
|
3071
|
+
const batched2 = recordDebounce(targetSession);
|
|
3072
|
+
logIntercom(`QUEUED \u2192 ${targetSession} (session busy)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
|
|
2910
3073
|
return "queued";
|
|
2911
3074
|
}
|
|
2912
3075
|
if (transport.isPaneInCopyMode(targetSession)) {
|
|
@@ -2914,8 +3077,8 @@ function sendIntercom(targetSession) {
|
|
|
2914
3077
|
transport.sendKeys(targetSession, "q");
|
|
2915
3078
|
}
|
|
2916
3079
|
transport.sendKeys(targetSession, "/exe-intercom");
|
|
2917
|
-
recordDebounce(targetSession);
|
|
2918
|
-
logIntercom(`DELIVERED \u2192 ${targetSession} (fire-and-forget)`);
|
|
3080
|
+
const batched = recordDebounce(targetSession);
|
|
3081
|
+
logIntercom(`DELIVERED \u2192 ${targetSession}${batched > 0 ? ` [${batched} nudges batched during debounce]` : ""} (fire-and-forget)`);
|
|
2919
3082
|
return "delivered";
|
|
2920
3083
|
} catch {
|
|
2921
3084
|
logIntercom(`FAIL \u2192 ${targetSession}`);
|
|
@@ -2945,7 +3108,7 @@ function notifyParentExe(sessionKey) {
|
|
|
2945
3108
|
return true;
|
|
2946
3109
|
}
|
|
2947
3110
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
2948
|
-
if (
|
|
3111
|
+
if (isCoordinatorName(employeeName)) {
|
|
2949
3112
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
2950
3113
|
}
|
|
2951
3114
|
try {
|
|
@@ -3017,26 +3180,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3017
3180
|
const transport = getTransport();
|
|
3018
3181
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
3019
3182
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
3020
|
-
const logDir =
|
|
3021
|
-
const logFile =
|
|
3022
|
-
if (!
|
|
3023
|
-
|
|
3183
|
+
const logDir = path11.join(os7.homedir(), ".exe-os", "session-logs");
|
|
3184
|
+
const logFile = path11.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
3185
|
+
if (!existsSync11(logDir)) {
|
|
3186
|
+
mkdirSync6(logDir, { recursive: true });
|
|
3024
3187
|
}
|
|
3025
3188
|
transport.kill(sessionName);
|
|
3026
3189
|
let cleanupSuffix = "";
|
|
3027
3190
|
try {
|
|
3028
3191
|
const thisFile = fileURLToPath(import.meta.url);
|
|
3029
|
-
const cleanupScript =
|
|
3030
|
-
if (
|
|
3192
|
+
const cleanupScript = path11.join(path11.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
3193
|
+
if (existsSync11(cleanupScript)) {
|
|
3031
3194
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
3032
3195
|
}
|
|
3033
3196
|
} catch {
|
|
3034
3197
|
}
|
|
3035
3198
|
try {
|
|
3036
|
-
const claudeJsonPath =
|
|
3199
|
+
const claudeJsonPath = path11.join(os7.homedir(), ".claude.json");
|
|
3037
3200
|
let claudeJson = {};
|
|
3038
3201
|
try {
|
|
3039
|
-
claudeJson = JSON.parse(
|
|
3202
|
+
claudeJson = JSON.parse(readFileSync9(claudeJsonPath, "utf8"));
|
|
3040
3203
|
} catch {
|
|
3041
3204
|
}
|
|
3042
3205
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -3044,17 +3207,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3044
3207
|
const trustDir = opts?.cwd ?? projectDir;
|
|
3045
3208
|
if (!projects[trustDir]) projects[trustDir] = {};
|
|
3046
3209
|
projects[trustDir].hasTrustDialogAccepted = true;
|
|
3047
|
-
|
|
3210
|
+
writeFileSync6(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
3048
3211
|
} catch {
|
|
3049
3212
|
}
|
|
3050
3213
|
try {
|
|
3051
|
-
const settingsDir =
|
|
3214
|
+
const settingsDir = path11.join(os7.homedir(), ".claude", "projects");
|
|
3052
3215
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
3053
|
-
const projSettingsDir =
|
|
3054
|
-
const settingsPath =
|
|
3216
|
+
const projSettingsDir = path11.join(settingsDir, normalizedKey);
|
|
3217
|
+
const settingsPath = path11.join(projSettingsDir, "settings.json");
|
|
3055
3218
|
let settings = {};
|
|
3056
3219
|
try {
|
|
3057
|
-
settings = JSON.parse(
|
|
3220
|
+
settings = JSON.parse(readFileSync9(settingsPath, "utf8"));
|
|
3058
3221
|
} catch {
|
|
3059
3222
|
}
|
|
3060
3223
|
const perms = settings.permissions ?? {};
|
|
@@ -3082,20 +3245,23 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3082
3245
|
if (changed) {
|
|
3083
3246
|
perms.allow = allow;
|
|
3084
3247
|
settings.permissions = perms;
|
|
3085
|
-
|
|
3086
|
-
|
|
3248
|
+
mkdirSync6(projSettingsDir, { recursive: true });
|
|
3249
|
+
writeFileSync6(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
3087
3250
|
}
|
|
3088
3251
|
} catch {
|
|
3089
3252
|
}
|
|
3090
3253
|
const spawnCwd = opts?.cwd ?? projectDir;
|
|
3091
3254
|
const useExeAgent = !!(opts?.model && opts?.provider);
|
|
3092
|
-
const
|
|
3255
|
+
const agentRtConfig = getAgentRuntime(employeeName);
|
|
3256
|
+
const useCodex = !useExeAgent && agentRtConfig.runtime === "codex";
|
|
3257
|
+
const useOpencode = !useExeAgent && !useCodex && agentRtConfig.runtime === "opencode";
|
|
3258
|
+
const ccProvider = useExeAgent || useCodex || useOpencode ? DEFAULT_PROVIDER : detectActiveProvider();
|
|
3093
3259
|
const useBinSymlink = ccProvider !== DEFAULT_PROVIDER;
|
|
3094
3260
|
let identityFlag = "";
|
|
3095
3261
|
let behaviorsFlag = "";
|
|
3096
3262
|
let legacyFallbackWarned = false;
|
|
3097
3263
|
if (!useExeAgent && !useBinSymlink) {
|
|
3098
|
-
const identityPath =
|
|
3264
|
+
const identityPath = path11.join(
|
|
3099
3265
|
os7.homedir(),
|
|
3100
3266
|
".exe-os",
|
|
3101
3267
|
"identity",
|
|
@@ -3105,13 +3271,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3105
3271
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
3106
3272
|
if (hasAgentFlag) {
|
|
3107
3273
|
identityFlag = ` --agent ${employeeName}`;
|
|
3108
|
-
} else if (
|
|
3274
|
+
} else if (existsSync11(identityPath)) {
|
|
3109
3275
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
3110
3276
|
legacyFallbackWarned = true;
|
|
3111
3277
|
}
|
|
3112
3278
|
const behaviorsFile = exportBehaviorsSync(
|
|
3113
3279
|
employeeName,
|
|
3114
|
-
|
|
3280
|
+
path11.basename(spawnCwd),
|
|
3115
3281
|
sessionName
|
|
3116
3282
|
);
|
|
3117
3283
|
if (behaviorsFile) {
|
|
@@ -3126,16 +3292,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3126
3292
|
}
|
|
3127
3293
|
let sessionContextFlag = "";
|
|
3128
3294
|
try {
|
|
3129
|
-
const ctxDir =
|
|
3130
|
-
|
|
3131
|
-
const ctxFile =
|
|
3295
|
+
const ctxDir = path11.join(os7.homedir(), ".exe-os", "session-cache");
|
|
3296
|
+
mkdirSync6(ctxDir, { recursive: true });
|
|
3297
|
+
const ctxFile = path11.join(ctxDir, `session-context-${sessionName}.md`);
|
|
3132
3298
|
const ctxContent = [
|
|
3133
3299
|
`## Session Context`,
|
|
3134
3300
|
`You are running in tmux session: ${sessionName}.`,
|
|
3135
3301
|
`Your parent coordinator session is ${exeSession}.`,
|
|
3136
3302
|
`Your employees (if any) use the -${exeSession} suffix.`
|
|
3137
3303
|
].join("\n");
|
|
3138
|
-
|
|
3304
|
+
writeFileSync6(ctxFile, ctxContent);
|
|
3139
3305
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
3140
3306
|
} catch {
|
|
3141
3307
|
}
|
|
@@ -3149,9 +3315,48 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3149
3315
|
}
|
|
3150
3316
|
}
|
|
3151
3317
|
}
|
|
3318
|
+
if (useCodex) {
|
|
3319
|
+
const codexCfg = RUNTIME_TABLE.codex;
|
|
3320
|
+
if (codexCfg?.apiKeyEnv) {
|
|
3321
|
+
const keyVal = process.env[codexCfg.apiKeyEnv];
|
|
3322
|
+
if (keyVal) {
|
|
3323
|
+
envPrefix = `${envPrefix} ${codexCfg.apiKeyEnv}=${keyVal}`;
|
|
3324
|
+
}
|
|
3325
|
+
}
|
|
3326
|
+
envPrefix = `${envPrefix} EXE_AGENT_MODEL=${agentRtConfig.model}`;
|
|
3327
|
+
}
|
|
3328
|
+
if (useOpencode) {
|
|
3329
|
+
const ocCfg = PROVIDER_TABLE.opencode;
|
|
3330
|
+
if (ocCfg?.apiKeyEnv) {
|
|
3331
|
+
const keyVal = process.env[ocCfg.apiKeyEnv];
|
|
3332
|
+
if (keyVal) {
|
|
3333
|
+
envPrefix = `${envPrefix} ${ocCfg.apiKeyEnv}=${keyVal}`;
|
|
3334
|
+
}
|
|
3335
|
+
}
|
|
3336
|
+
envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
|
|
3337
|
+
}
|
|
3338
|
+
if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
|
|
3339
|
+
const defaultClaudeModel = DEFAULT_MODELS.claude;
|
|
3340
|
+
if (agentRtConfig.runtime === "claude" && agentRtConfig.model !== defaultClaudeModel) {
|
|
3341
|
+
envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
|
|
3342
|
+
}
|
|
3343
|
+
}
|
|
3152
3344
|
let spawnCommand;
|
|
3153
3345
|
if (useExeAgent) {
|
|
3154
3346
|
spawnCommand = `${envPrefix} exe-agent --employee ${employeeName} --model ${opts.model} --provider ${opts.provider}${cleanupSuffix}`;
|
|
3347
|
+
} else if (useCodex) {
|
|
3348
|
+
process.stderr.write(
|
|
3349
|
+
`[tmux-routing] agent-config: ${employeeName} \u2192 codex (${agentRtConfig.model})
|
|
3350
|
+
`
|
|
3351
|
+
);
|
|
3352
|
+
spawnCommand = `${envPrefix} exe-start-codex --agent ${employeeName}${cleanupSuffix}`;
|
|
3353
|
+
} else if (useOpencode) {
|
|
3354
|
+
const binName = `${employeeName}-opencode`;
|
|
3355
|
+
process.stderr.write(
|
|
3356
|
+
`[tmux-routing] agent-config: ${employeeName} \u2192 opencode (${agentRtConfig.model})
|
|
3357
|
+
`
|
|
3358
|
+
);
|
|
3359
|
+
spawnCommand = `${envPrefix} ${binName}${cleanupSuffix}`;
|
|
3155
3360
|
} else if (useBinSymlink) {
|
|
3156
3361
|
const binName = `${employeeName}-${ccProvider}`;
|
|
3157
3362
|
process.stderr.write(
|
|
@@ -3173,11 +3378,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3173
3378
|
transport.pipeLog(sessionName, logFile);
|
|
3174
3379
|
try {
|
|
3175
3380
|
const mySession = getMySession();
|
|
3176
|
-
const dispatchInfo =
|
|
3177
|
-
|
|
3381
|
+
const dispatchInfo = path11.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
3382
|
+
writeFileSync6(dispatchInfo, JSON.stringify({
|
|
3178
3383
|
dispatchedBy: mySession,
|
|
3179
3384
|
rootExe: exeSession,
|
|
3180
|
-
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : "anthropic",
|
|
3385
|
+
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
|
|
3386
|
+
runtime: useCodex ? "codex" : useOpencode ? "opencode" : useExeAgent ? "exe-agent" : "claude",
|
|
3387
|
+
model: useCodex ? agentRtConfig.model : useOpencode ? agentRtConfig.model : void 0,
|
|
3181
3388
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3182
3389
|
}));
|
|
3183
3390
|
} catch {
|
|
@@ -3195,6 +3402,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3195
3402
|
booted = true;
|
|
3196
3403
|
break;
|
|
3197
3404
|
}
|
|
3405
|
+
} else if (useCodex) {
|
|
3406
|
+
if (pane.includes("codex") || pane.includes("Codex") || pane.includes("exe-start-codex")) {
|
|
3407
|
+
booted = true;
|
|
3408
|
+
break;
|
|
3409
|
+
}
|
|
3198
3410
|
} else {
|
|
3199
3411
|
if (pane.includes("Claude Code") || pane.includes("\u276F")) {
|
|
3200
3412
|
booted = true;
|
|
@@ -3206,9 +3418,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3206
3418
|
}
|
|
3207
3419
|
if (!booted) {
|
|
3208
3420
|
releaseSpawnLock(sessionName);
|
|
3209
|
-
|
|
3421
|
+
const runtimeLabel = useExeAgent ? "exe-agent" : useCodex ? "codex" : "claude";
|
|
3422
|
+
return { sessionName, error: `${runtimeLabel} did not boot within 15s` };
|
|
3210
3423
|
}
|
|
3211
|
-
if (!useExeAgent) {
|
|
3424
|
+
if (!useExeAgent && !useCodex) {
|
|
3212
3425
|
try {
|
|
3213
3426
|
transport.sendKeys(sessionName, `/exe-call ${employeeName}`);
|
|
3214
3427
|
} catch {
|
|
@@ -3235,17 +3448,19 @@ var init_tmux_routing = __esm({
|
|
|
3235
3448
|
init_cc_agent_support();
|
|
3236
3449
|
init_mcp_prefix();
|
|
3237
3450
|
init_provider_table();
|
|
3451
|
+
init_agent_config();
|
|
3452
|
+
init_runtime_table();
|
|
3238
3453
|
init_intercom_queue();
|
|
3239
3454
|
init_plan_limits();
|
|
3240
3455
|
init_employees();
|
|
3241
|
-
SPAWN_LOCK_DIR =
|
|
3242
|
-
SESSION_CACHE =
|
|
3456
|
+
SPAWN_LOCK_DIR = path11.join(os7.homedir(), ".exe-os", "spawn-locks");
|
|
3457
|
+
SESSION_CACHE = path11.join(os7.homedir(), ".exe-os", "session-cache");
|
|
3243
3458
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
3244
3459
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
3245
3460
|
VERIFY_PANE_LINES = 200;
|
|
3246
3461
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
3247
|
-
INTERCOM_LOG2 =
|
|
3248
|
-
DEBOUNCE_FILE =
|
|
3462
|
+
INTERCOM_LOG2 = path11.join(os7.homedir(), ".exe-os", "intercom.log");
|
|
3463
|
+
DEBOUNCE_FILE = path11.join(SESSION_CACHE, "intercom-debounce.json");
|
|
3249
3464
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
3250
3465
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
|
|
3251
3466
|
}
|
|
@@ -3277,10 +3492,11 @@ var init_task_scope = __esm({
|
|
|
3277
3492
|
|
|
3278
3493
|
// src/lib/tasks-crud.ts
|
|
3279
3494
|
import crypto3 from "crypto";
|
|
3280
|
-
import
|
|
3495
|
+
import path12 from "path";
|
|
3496
|
+
import os8 from "os";
|
|
3281
3497
|
import { execSync as execSync5 } from "child_process";
|
|
3282
3498
|
import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
|
|
3283
|
-
import { existsSync as
|
|
3499
|
+
import { existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
|
|
3284
3500
|
async function writeCheckpoint(input) {
|
|
3285
3501
|
const client = getClient();
|
|
3286
3502
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -3321,6 +3537,35 @@ function extractParentFromContext(contextBody) {
|
|
|
3321
3537
|
function slugify(title) {
|
|
3322
3538
|
return title.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
3323
3539
|
}
|
|
3540
|
+
function buildKeywordIndex() {
|
|
3541
|
+
const idx = /* @__PURE__ */ new Map();
|
|
3542
|
+
for (const [role, keywords] of Object.entries(LANE_KEYWORDS)) {
|
|
3543
|
+
for (const kw of keywords) {
|
|
3544
|
+
const existing = idx.get(kw) ?? [];
|
|
3545
|
+
existing.push(role);
|
|
3546
|
+
idx.set(kw, existing);
|
|
3547
|
+
}
|
|
3548
|
+
}
|
|
3549
|
+
return idx;
|
|
3550
|
+
}
|
|
3551
|
+
function checkLaneAffinity(title, context, assigneeName) {
|
|
3552
|
+
const employees = loadEmployeesSync();
|
|
3553
|
+
const employee = employees.find((e) => e.name === assigneeName);
|
|
3554
|
+
if (!employee) return void 0;
|
|
3555
|
+
const assigneeRole = employee.role;
|
|
3556
|
+
const text = `${title} ${context}`.toLowerCase();
|
|
3557
|
+
const matchedRoles = /* @__PURE__ */ new Set();
|
|
3558
|
+
for (const [keyword, roles] of KEYWORD_INDEX) {
|
|
3559
|
+
if (text.includes(keyword)) {
|
|
3560
|
+
for (const role of roles) matchedRoles.add(role);
|
|
3561
|
+
}
|
|
3562
|
+
}
|
|
3563
|
+
if (matchedRoles.size === 0) return void 0;
|
|
3564
|
+
if (matchedRoles.has(assigneeRole)) return void 0;
|
|
3565
|
+
if (assigneeRole === "COO") return void 0;
|
|
3566
|
+
const expectedRoles = Array.from(matchedRoles).join(" or ");
|
|
3567
|
+
return `\u26A0\uFE0F Lane mismatch: task content suggests ${expectedRoles}, but assigned to ${assigneeName} (${assigneeRole}).`;
|
|
3568
|
+
}
|
|
3324
3569
|
async function resolveTask(client, identifier, scopeSession) {
|
|
3325
3570
|
const scope = sessionScopeFilter(scopeSession);
|
|
3326
3571
|
let result = await client.execute({
|
|
@@ -3370,7 +3615,14 @@ async function createTaskCore(input) {
|
|
|
3370
3615
|
const id = crypto3.randomUUID();
|
|
3371
3616
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3372
3617
|
const slug = slugify(input.title);
|
|
3373
|
-
|
|
3618
|
+
let earlySessionScope = null;
|
|
3619
|
+
try {
|
|
3620
|
+
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
3621
|
+
earlySessionScope = resolveExeSession2();
|
|
3622
|
+
} catch {
|
|
3623
|
+
}
|
|
3624
|
+
const scope = earlySessionScope ?? "default";
|
|
3625
|
+
const taskFile = input.taskFile ?? `tasks/${scope}/${input.assignedTo}/${slug}.md`;
|
|
3374
3626
|
let blockedById = null;
|
|
3375
3627
|
const initialStatus = input.blockedBy ? "blocked" : "open";
|
|
3376
3628
|
if (input.blockedBy) {
|
|
@@ -3410,22 +3662,24 @@ async function createTaskCore(input) {
|
|
|
3410
3662
|
if (dupCheck.rows.length > 0) {
|
|
3411
3663
|
warning = `similar active task already exists (${String(dupCheck.rows[0].id)}). Created new task anyway.`;
|
|
3412
3664
|
}
|
|
3665
|
+
if (!process.env.DISABLE_LANE_AFFINITY) {
|
|
3666
|
+
const laneWarning = checkLaneAffinity(input.title, input.context, input.assignedTo);
|
|
3667
|
+
if (laneWarning) {
|
|
3668
|
+
warning = warning ? `${warning}
|
|
3669
|
+
${laneWarning}` : laneWarning;
|
|
3670
|
+
}
|
|
3671
|
+
}
|
|
3413
3672
|
if (input.baseDir) {
|
|
3414
3673
|
try {
|
|
3415
|
-
await mkdir4(
|
|
3416
|
-
await mkdir4(
|
|
3674
|
+
await mkdir4(path12.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
3675
|
+
await mkdir4(path12.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
3417
3676
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
3418
3677
|
await ensureGitignoreExe(input.baseDir);
|
|
3419
3678
|
} catch {
|
|
3420
3679
|
}
|
|
3421
3680
|
}
|
|
3422
3681
|
const complexity = input.complexity ?? "standard";
|
|
3423
|
-
|
|
3424
|
-
try {
|
|
3425
|
-
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
3426
|
-
sessionScope = resolveExeSession2();
|
|
3427
|
-
} catch {
|
|
3428
|
-
}
|
|
3682
|
+
const sessionScope = earlySessionScope;
|
|
3429
3683
|
await client.execute({
|
|
3430
3684
|
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)
|
|
3431
3685
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
@@ -3452,6 +3706,43 @@ async function createTaskCore(input) {
|
|
|
3452
3706
|
now
|
|
3453
3707
|
]
|
|
3454
3708
|
});
|
|
3709
|
+
if (input.baseDir) {
|
|
3710
|
+
try {
|
|
3711
|
+
const EXE_OS_DIR = path12.join(os8.homedir(), ".exe-os");
|
|
3712
|
+
const mdPath = path12.join(EXE_OS_DIR, taskFile);
|
|
3713
|
+
const mdDir = path12.dirname(mdPath);
|
|
3714
|
+
if (!existsSync12(mdDir)) await mkdir4(mdDir, { recursive: true });
|
|
3715
|
+
const reviewer = input.reviewer ?? input.assignedBy;
|
|
3716
|
+
const mdContent = `# ${input.title}
|
|
3717
|
+
|
|
3718
|
+
**ID:** ${id}
|
|
3719
|
+
**Status:** ${initialStatus}
|
|
3720
|
+
**Priority:** ${input.priority}
|
|
3721
|
+
**Assigned by:** ${input.assignedBy}
|
|
3722
|
+
**Assigned to:** ${input.assignedTo}
|
|
3723
|
+
**Project:** ${input.projectName}
|
|
3724
|
+
**Created:** ${now.split("T")[0]}${parentTaskId ? `
|
|
3725
|
+
**Parent task:** ${parentTaskId}` : ""}
|
|
3726
|
+
**Reviewer:** ${reviewer}
|
|
3727
|
+
|
|
3728
|
+
## Context
|
|
3729
|
+
|
|
3730
|
+
${input.context}
|
|
3731
|
+
|
|
3732
|
+
## MANDATORY: When done
|
|
3733
|
+
|
|
3734
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
3735
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
3736
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
3737
|
+
`;
|
|
3738
|
+
await writeFile4(mdPath, mdContent, "utf-8");
|
|
3739
|
+
} catch (err) {
|
|
3740
|
+
process.stderr.write(
|
|
3741
|
+
`[create-task] WARNING: .md file write failed for ${taskFile}: ${err instanceof Error ? err.message : String(err)}
|
|
3742
|
+
`
|
|
3743
|
+
);
|
|
3744
|
+
}
|
|
3745
|
+
}
|
|
3455
3746
|
return {
|
|
3456
3747
|
id,
|
|
3457
3748
|
title: input.title,
|
|
@@ -3644,7 +3935,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
3644
3935
|
return { row, taskFile, now, taskId };
|
|
3645
3936
|
}
|
|
3646
3937
|
}
|
|
3647
|
-
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || input.callerAgentId
|
|
3938
|
+
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || isCoordinatorName(input.callerAgentId))) {
|
|
3648
3939
|
process.stderr.write(
|
|
3649
3940
|
`[tasks] Assigner override: ${input.callerAgentId} reclaiming ${taskId}
|
|
3650
3941
|
`
|
|
@@ -3709,9 +4000,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
3709
4000
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
3710
4001
|
}
|
|
3711
4002
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
3712
|
-
const archPath =
|
|
4003
|
+
const archPath = path12.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
3713
4004
|
try {
|
|
3714
|
-
if (
|
|
4005
|
+
if (existsSync12(archPath)) return;
|
|
3715
4006
|
const template = [
|
|
3716
4007
|
`# ${projectName} \u2014 System Architecture`,
|
|
3717
4008
|
"",
|
|
@@ -3744,10 +4035,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
3744
4035
|
}
|
|
3745
4036
|
}
|
|
3746
4037
|
async function ensureGitignoreExe(baseDir) {
|
|
3747
|
-
const gitignorePath =
|
|
4038
|
+
const gitignorePath = path12.join(baseDir, ".gitignore");
|
|
3748
4039
|
try {
|
|
3749
|
-
if (
|
|
3750
|
-
const content =
|
|
4040
|
+
if (existsSync12(gitignorePath)) {
|
|
4041
|
+
const content = readFileSync10(gitignorePath, "utf-8");
|
|
3751
4042
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
3752
4043
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
3753
4044
|
} else {
|
|
@@ -3756,20 +4047,30 @@ async function ensureGitignoreExe(baseDir) {
|
|
|
3756
4047
|
} catch {
|
|
3757
4048
|
}
|
|
3758
4049
|
}
|
|
3759
|
-
var DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
4050
|
+
var LANE_KEYWORDS, KEYWORD_INDEX, DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
3760
4051
|
var init_tasks_crud = __esm({
|
|
3761
4052
|
"src/lib/tasks-crud.ts"() {
|
|
3762
4053
|
"use strict";
|
|
3763
4054
|
init_database();
|
|
3764
4055
|
init_task_scope();
|
|
4056
|
+
init_employees();
|
|
4057
|
+
LANE_KEYWORDS = {
|
|
4058
|
+
CMO: ["sales", "script", "pitch", "offer", "copy", "objection", "brand", "content", "seo", "marketing", "newsletter", "carousel", "social", "campaign"],
|
|
4059
|
+
CTO: ["spec", "architecture", "migration", "schema", "database", "design doc", "adr", "security audit", "tech stack"],
|
|
4060
|
+
"Principal Engineer": ["implement", "build", "fix", "commit", "refactor", "bug", "feature", "wire", "integration"],
|
|
4061
|
+
"Staff Code Reviewer": ["critique", "verdict", "review", "audit", "code quality"],
|
|
4062
|
+
"Content Production Specialist": ["render", "video", "image", "b-roll", "remotion", "animation", "thumbnail"],
|
|
4063
|
+
"AI Product Lead": ["competitive", "analysis", "benchmark", "compare", "scout", "evaluate", "poc"]
|
|
4064
|
+
};
|
|
4065
|
+
KEYWORD_INDEX = buildKeywordIndex();
|
|
3765
4066
|
DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
|
|
3766
4067
|
TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
|
|
3767
4068
|
}
|
|
3768
4069
|
});
|
|
3769
4070
|
|
|
3770
4071
|
// src/lib/tasks-review.ts
|
|
3771
|
-
import
|
|
3772
|
-
import { existsSync as
|
|
4072
|
+
import path13 from "path";
|
|
4073
|
+
import { existsSync as existsSync13, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
|
|
3773
4074
|
async function countPendingReviews(sessionScope) {
|
|
3774
4075
|
const client = getClient();
|
|
3775
4076
|
if (sessionScope) {
|
|
@@ -3791,7 +4092,7 @@ async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
|
3791
4092
|
const result2 = await client.execute({
|
|
3792
4093
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3793
4094
|
WHERE status = 'needs_review' AND updated_at > ?
|
|
3794
|
-
AND
|
|
4095
|
+
AND session_scope = ?`,
|
|
3795
4096
|
args: [sinceIso, sessionScope]
|
|
3796
4097
|
});
|
|
3797
4098
|
return Number(result2.rows[0]?.cnt) || 0;
|
|
@@ -3809,7 +4110,7 @@ async function listPendingReviews(limit, sessionScope) {
|
|
|
3809
4110
|
const result2 = await client.execute({
|
|
3810
4111
|
sql: `SELECT title, assigned_to, project_name FROM tasks
|
|
3811
4112
|
WHERE status = 'needs_review'
|
|
3812
|
-
AND
|
|
4113
|
+
AND session_scope = ?
|
|
3813
4114
|
ORDER BY priority ASC, created_at DESC LIMIT ?`,
|
|
3814
4115
|
args: [sessionScope, limit]
|
|
3815
4116
|
});
|
|
@@ -3930,14 +4231,14 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
3930
4231
|
if (parts.length >= 3 && parts[0] === "review") {
|
|
3931
4232
|
const agent = parts[1];
|
|
3932
4233
|
const slug = parts.slice(2).join("-");
|
|
3933
|
-
const
|
|
4234
|
+
const legacyTaskFile = `exe/${agent}/${slug}.md`;
|
|
3934
4235
|
const result = await client.execute({
|
|
3935
|
-
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
|
|
3936
|
-
args: [now,
|
|
4236
|
+
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE (task_file = ? OR task_file LIKE ?) AND status = 'needs_review'",
|
|
4237
|
+
args: [now, legacyTaskFile, `tasks/%/${agent}/${slug}.md`]
|
|
3937
4238
|
});
|
|
3938
4239
|
if (result.rowsAffected > 0) {
|
|
3939
4240
|
process.stderr.write(
|
|
3940
|
-
`[review-cleanup] Cascaded original task to done
|
|
4241
|
+
`[review-cleanup] Cascaded original task to done: ${agent}/${slug}.md
|
|
3941
4242
|
`
|
|
3942
4243
|
);
|
|
3943
4244
|
}
|
|
@@ -3950,11 +4251,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
3950
4251
|
);
|
|
3951
4252
|
}
|
|
3952
4253
|
try {
|
|
3953
|
-
const cacheDir =
|
|
3954
|
-
if (
|
|
4254
|
+
const cacheDir = path13.join(EXE_AI_DIR, "session-cache");
|
|
4255
|
+
if (existsSync13(cacheDir)) {
|
|
3955
4256
|
for (const f of readdirSync3(cacheDir)) {
|
|
3956
4257
|
if (f.startsWith("review-notified-")) {
|
|
3957
|
-
unlinkSync4(
|
|
4258
|
+
unlinkSync4(path13.join(cacheDir, f));
|
|
3958
4259
|
}
|
|
3959
4260
|
}
|
|
3960
4261
|
}
|
|
@@ -3975,7 +4276,7 @@ var init_tasks_review = __esm({
|
|
|
3975
4276
|
});
|
|
3976
4277
|
|
|
3977
4278
|
// src/lib/tasks-chain.ts
|
|
3978
|
-
import
|
|
4279
|
+
import path14 from "path";
|
|
3979
4280
|
import { readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
|
|
3980
4281
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
3981
4282
|
const client = getClient();
|
|
@@ -3992,7 +4293,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
3992
4293
|
});
|
|
3993
4294
|
for (const ur of unblockedRows.rows) {
|
|
3994
4295
|
try {
|
|
3995
|
-
const ubFile =
|
|
4296
|
+
const ubFile = path14.join(baseDir, String(ur.task_file));
|
|
3996
4297
|
let ubContent = await readFile4(ubFile, "utf-8");
|
|
3997
4298
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
3998
4299
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -4061,7 +4362,7 @@ var init_tasks_chain = __esm({
|
|
|
4061
4362
|
|
|
4062
4363
|
// src/lib/project-name.ts
|
|
4063
4364
|
import { execSync as execSync6 } from "child_process";
|
|
4064
|
-
import
|
|
4365
|
+
import path15 from "path";
|
|
4065
4366
|
function getProjectName(cwd) {
|
|
4066
4367
|
const dir = cwd ?? process.cwd();
|
|
4067
4368
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
@@ -4074,7 +4375,7 @@ function getProjectName(cwd) {
|
|
|
4074
4375
|
timeout: 2e3,
|
|
4075
4376
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4076
4377
|
}).trim();
|
|
4077
|
-
repoRoot =
|
|
4378
|
+
repoRoot = path15.dirname(gitCommonDir);
|
|
4078
4379
|
} catch {
|
|
4079
4380
|
repoRoot = execSync6("git rev-parse --show-toplevel", {
|
|
4080
4381
|
cwd: dir,
|
|
@@ -4083,11 +4384,11 @@ function getProjectName(cwd) {
|
|
|
4083
4384
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4084
4385
|
}).trim();
|
|
4085
4386
|
}
|
|
4086
|
-
_cached2 =
|
|
4387
|
+
_cached2 = path15.basename(repoRoot);
|
|
4087
4388
|
_cachedCwd = dir;
|
|
4088
4389
|
return _cached2;
|
|
4089
4390
|
} catch {
|
|
4090
|
-
_cached2 =
|
|
4391
|
+
_cached2 = path15.basename(dir);
|
|
4091
4392
|
_cachedCwd = dir;
|
|
4092
4393
|
return _cached2;
|
|
4093
4394
|
}
|
|
@@ -4119,7 +4420,7 @@ function findSessionForProject(projectName) {
|
|
|
4119
4420
|
const sessions = listSessions();
|
|
4120
4421
|
for (const s of sessions) {
|
|
4121
4422
|
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
4122
|
-
if (proj === projectName &&
|
|
4423
|
+
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
4123
4424
|
}
|
|
4124
4425
|
return null;
|
|
4125
4426
|
}
|
|
@@ -4165,7 +4466,7 @@ var init_session_scope = __esm({
|
|
|
4165
4466
|
|
|
4166
4467
|
// src/lib/tasks-notify.ts
|
|
4167
4468
|
async function dispatchTaskToEmployee(input) {
|
|
4168
|
-
if (
|
|
4469
|
+
if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
|
|
4169
4470
|
let crossProject = false;
|
|
4170
4471
|
if (input.projectName) {
|
|
4171
4472
|
try {
|
|
@@ -4560,8 +4861,8 @@ __export(tasks_exports, {
|
|
|
4560
4861
|
updateTaskStatus: () => updateTaskStatus,
|
|
4561
4862
|
writeCheckpoint: () => writeCheckpoint
|
|
4562
4863
|
});
|
|
4563
|
-
import
|
|
4564
|
-
import { writeFileSync as
|
|
4864
|
+
import path16 from "path";
|
|
4865
|
+
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync7, unlinkSync as unlinkSync5 } from "fs";
|
|
4565
4866
|
async function createTask(input) {
|
|
4566
4867
|
const result = await createTaskCore(input);
|
|
4567
4868
|
if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -4580,11 +4881,11 @@ async function updateTask(input) {
|
|
|
4580
4881
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
4581
4882
|
try {
|
|
4582
4883
|
const agent = String(row.assigned_to);
|
|
4583
|
-
const cacheDir =
|
|
4584
|
-
const cachePath =
|
|
4884
|
+
const cacheDir = path16.join(EXE_AI_DIR, "session-cache");
|
|
4885
|
+
const cachePath = path16.join(cacheDir, `current-task-${agent}.json`);
|
|
4585
4886
|
if (input.status === "in_progress") {
|
|
4586
|
-
|
|
4587
|
-
|
|
4887
|
+
mkdirSync7(cacheDir, { recursive: true });
|
|
4888
|
+
writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
4588
4889
|
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
4589
4890
|
try {
|
|
4590
4891
|
unlinkSync5(cachePath);
|
|
@@ -4644,7 +4945,7 @@ async function updateTask(input) {
|
|
|
4644
4945
|
}
|
|
4645
4946
|
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
4646
4947
|
if (isTerminal) {
|
|
4647
|
-
const isCoordinator =
|
|
4948
|
+
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
4648
4949
|
if (!isCoordinator) {
|
|
4649
4950
|
notifyTaskDone();
|
|
4650
4951
|
}
|
|
@@ -4669,7 +4970,7 @@ async function updateTask(input) {
|
|
|
4669
4970
|
}
|
|
4670
4971
|
}
|
|
4671
4972
|
}
|
|
4672
|
-
if (input.status === "done" &&
|
|
4973
|
+
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
4673
4974
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
4674
4975
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
4675
4976
|
taskId,
|
|
@@ -4685,7 +4986,7 @@ async function updateTask(input) {
|
|
|
4685
4986
|
});
|
|
4686
4987
|
}
|
|
4687
4988
|
let nextTask;
|
|
4688
|
-
if (isTerminal &&
|
|
4989
|
+
if (isTerminal && !isCoordinatorName(String(row.assigned_to))) {
|
|
4689
4990
|
try {
|
|
4690
4991
|
nextTask = await findNextTask(String(row.assigned_to));
|
|
4691
4992
|
} catch {
|
|
@@ -4742,6 +5043,7 @@ var init_tasks = __esm({
|
|
|
4742
5043
|
});
|
|
4743
5044
|
|
|
4744
5045
|
// src/lib/store.ts
|
|
5046
|
+
import { createHash } from "crypto";
|
|
4745
5047
|
init_database();
|
|
4746
5048
|
|
|
4747
5049
|
// src/lib/keychain.ts
|
|
@@ -4777,12 +5079,20 @@ async function getMasterKey() {
|
|
|
4777
5079
|
}
|
|
4778
5080
|
const keyPath = getKeyPath();
|
|
4779
5081
|
if (!existsSync3(keyPath)) {
|
|
5082
|
+
process.stderr.write(
|
|
5083
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os3.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
5084
|
+
`
|
|
5085
|
+
);
|
|
4780
5086
|
return null;
|
|
4781
5087
|
}
|
|
4782
5088
|
try {
|
|
4783
5089
|
const content = await readFile3(keyPath, "utf-8");
|
|
4784
5090
|
return Buffer.from(content.trim(), "base64");
|
|
4785
|
-
} catch {
|
|
5091
|
+
} catch (err) {
|
|
5092
|
+
process.stderr.write(
|
|
5093
|
+
`[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
|
|
5094
|
+
`
|
|
5095
|
+
);
|
|
4786
5096
|
return null;
|
|
4787
5097
|
}
|
|
4788
5098
|
}
|